summaryrefslogtreecommitdiff
path: root/alpine
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /alpine
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'alpine')
-rw-r--r--alpine/Makefile.am48
-rw-r--r--alpine/Makefile.in823
-rw-r--r--alpine/addrbook.c7433
-rw-r--r--alpine/addrbook.h69
-rw-r--r--alpine/adrbkcmd.c7694
-rw-r--r--alpine/adrbkcmd.h64
-rw-r--r--alpine/after.c276
-rw-r--r--alpine/after.h44
-rw-r--r--alpine/alpine.c3587
-rw-r--r--alpine/alpine.h41
-rw-r--r--alpine/arg.c1325
-rw-r--r--alpine/arg.h52
-rwxr-xr-xalpine/brk2pine.sh67
-rw-r--r--alpine/busy.c473
-rw-r--r--alpine/busy.h30
-rw-r--r--alpine/colorconf.c2788
-rw-r--r--alpine/colorconf.h79
-rw-r--r--alpine/confscroll.c5968
-rw-r--r--alpine/confscroll.h114
-rw-r--r--alpine/conftype.h141
-rw-r--r--alpine/context.c1019
-rw-r--r--alpine/context.h51
-rw-r--r--alpine/dispfilt.c454
-rw-r--r--alpine/dispfilt.h30
-rw-r--r--alpine/flagmaint.c332
-rw-r--r--alpine/flagmaint.h55
-rw-r--r--alpine/folder.c6922
-rw-r--r--alpine/folder.h39
-rw-r--r--alpine/headers.h68
-rw-r--r--alpine/help.c1408
-rw-r--r--alpine/help.h51
-rw-r--r--alpine/imap.c3011
-rw-r--r--alpine/imap.h51
-rw-r--r--alpine/init.c396
-rw-r--r--alpine/init.h27
-rw-r--r--alpine/kblock.c196
-rw-r--r--alpine/kblock.h27
-rw-r--r--alpine/keymenu.c3961
-rw-r--r--alpine/keymenu.h670
-rwxr-xr-xalpine/ldap32.dllbin0 -> 138752 bytes
-rw-r--r--alpine/ldapconf.c2432
-rw-r--r--alpine/ldapconf.h34
-rw-r--r--alpine/listsel.c332
-rw-r--r--alpine/listsel.h47
-rw-r--r--alpine/mailcmd.c9472
-rw-r--r--alpine/mailcmd.h107
-rw-r--r--alpine/mailindx.c3644
-rw-r--r--alpine/mailindx.h114
-rw-r--r--alpine/mailpart.c3981
-rw-r--r--alpine/mailpart.h43
-rwxr-xr-xalpine/mailtrfc.sh124
-rw-r--r--alpine/mailview.c5662
-rw-r--r--alpine/mailview.h129
-rw-r--r--alpine/makefile.wnt89
-rw-r--r--alpine/newmail.c333
-rw-r--r--alpine/newmail.h22
-rw-r--r--alpine/newuser.c204
-rw-r--r--alpine/newuser.h28
-rw-r--r--alpine/osdep/Makefile.am22
-rw-r--r--alpine/osdep/Makefile.in544
-rw-r--r--alpine/osdep/ReadMe14
-rw-r--r--alpine/osdep/alpine-splash.bmpbin0 -> 532014 bytes
-rw-r--r--alpine/osdep/alpine.icobin0 -> 3262 bytes
-rw-r--r--alpine/osdep/chnge_pw.c65
-rw-r--r--alpine/osdep/chnge_pw.h25
-rw-r--r--alpine/osdep/debuging.c604
-rw-r--r--alpine/osdep/debuging.h30
-rw-r--r--alpine/osdep/diskquot78
-rw-r--r--alpine/osdep/diskquot.a328
-rw-r--r--alpine/osdep/diskquot.a416
-rw-r--r--alpine/osdep/diskquot.hpp9
-rw-r--r--alpine/osdep/diskquot.non.c46
-rw-r--r--alpine/osdep/diskquot.ptx9
-rw-r--r--alpine/osdep/diskquot.sgi8
-rw-r--r--alpine/osdep/diskquot.so59
-rw-r--r--alpine/osdep/diskquot.sun8
-rw-r--r--alpine/osdep/diskquot.sv49
-rw-r--r--alpine/osdep/execview.c555
-rw-r--r--alpine/osdep/execview.h27
-rw-r--r--alpine/osdep/fltrname.c119
-rw-r--r--alpine/osdep/fltrname.h26
-rw-r--r--alpine/osdep/jobcntrl.c55
-rw-r--r--alpine/osdep/jobcntrl.h25
-rw-r--r--alpine/osdep/makefile.wnt66
-rw-r--r--alpine/osdep/mclosed.icobin0 -> 3262 bytes
-rw-r--r--alpine/osdep/mswin.def17
-rw-r--r--alpine/osdep/mswin.rc605
-rw-r--r--alpine/osdep/mswinver.c74
-rw-r--r--alpine/osdep/newmail.icobin0 -> 3262 bytes
-rw-r--r--alpine/osdep/print.c529
-rw-r--r--alpine/osdep/print.h28
-rw-r--r--alpine/osdep/resource.h1
-rw-r--r--alpine/osdep/solquota174
-rw-r--r--alpine/osdep/sunquota136
-rw-r--r--alpine/osdep/termin.gen.c1252
-rw-r--r--alpine/osdep/termin.gen.h48
-rw-r--r--alpine/osdep/termin.unx.c752
-rw-r--r--alpine/osdep/termin.unx.h29
-rw-r--r--alpine/osdep/termin.wnt.c352
-rw-r--r--alpine/osdep/termin.wnt.h32
-rw-r--r--alpine/osdep/termout.gen.c584
-rw-r--r--alpine/osdep/termout.gen.h54
-rw-r--r--alpine/osdep/termout.unx.c1002
-rw-r--r--alpine/osdep/termout.unx.h24
-rw-r--r--alpine/osdep/termout.win127
-rw-r--r--alpine/osdep/termout.wnt.c1862
-rw-r--r--alpine/osdep/termout.wnt.h47
-rw-r--r--alpine/osdep/windlg.h10
-rw-r--r--alpine/pattern.c223
-rw-r--r--alpine/pattern.h25
-rw-r--r--alpine/pine-use.c180
-rw-r--r--alpine/pine-win.lnk11
-rw-r--r--alpine/pipe.c150
-rw-r--r--alpine/pipe.h29
-rw-r--r--alpine/print.c1288
-rw-r--r--alpine/print.h30
-rw-r--r--alpine/radio.c918
-rw-r--r--alpine/radio.h81
-rw-r--r--alpine/remote.c347
-rw-r--r--alpine/remote.h27
-rw-r--r--alpine/reply.c2549
-rw-r--r--alpine/reply.h44
-rw-r--r--alpine/roleconf.c8187
-rw-r--r--alpine/roleconf.h33
-rw-r--r--alpine/rpdump.c818
-rw-r--r--alpine/rpload.c1008
-rw-r--r--alpine/send.c7155
-rw-r--r--alpine/send.h53
-rw-r--r--alpine/setup.c1131
-rw-r--r--alpine/setup.h32
-rw-r--r--alpine/signal.c921
-rw-r--r--alpine/signal.h44
-rw-r--r--alpine/smime.c1151
-rw-r--r--alpine/smime.h34
-rw-r--r--alpine/status.c1282
-rw-r--r--alpine/status.h32
-rw-r--r--alpine/takeaddr.c3520
-rw-r--r--alpine/takeaddr.h40
-rw-r--r--alpine/talk.h37
-rw-r--r--alpine/titlebar.c1236
-rw-r--r--alpine/titlebar.h55
141 files changed, 119458 insertions, 0 deletions
diff --git a/alpine/Makefile.am b/alpine/Makefile.am
new file mode 100644
index 00000000..c80a97ff
--- /dev/null
+++ b/alpine/Makefile.am
@@ -0,0 +1,48 @@
+## Process this file with automake to produce Makefile.in
+## Use aclocal -I m4; automake
+
+# ========================================================================
+# Copyright 2006-2008 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+SUBDIRS = osdep
+
+bin_PROGRAMS = alpine rpdump rpload
+
+alpine_SOURCES = addrbook.c adrbkcmd.c after.c alpine.c arg.c busy.c colorconf.c \
+ confscroll.c context.c dispfilt.c flagmaint.c folder.c help.c imap.c \
+ init.c kblock.c keymenu.c ldapconf.c listsel.c mailcmd.c mailindx.c \
+ mailpart.c mailview.c newuser.c pattern.c pipe.c print.c radio.c remote.c reply.c \
+ roleconf.c send.c setup.c signal.c status.c takeaddr.c titlebar.c smime.c newmail.c
+
+alpine_LDADD = $(LDADD) $(INTLLIBS)
+
+nodist_alpine_SOURCES = date.c
+
+rpdump_SOURCES = rpdump.c
+
+rpload_SOURCES = rpload.c
+
+BUILT_SOURCES = date.c
+
+LDADD = ../pico/libpico.a ../pico/osdep/libpicoosd.a \
+ ../pith/libpith.a ../pith/osdep/libpithosd.a \
+ ../pith/charconv/libpithcc.a \
+ osdep/libpineosd.a ../c-client/c-client.a
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include -DLOCALEDIR=\"$(localedir)\"
+
+AM_LDFLAGS = `cat @top_srcdir@/c-client/LDFLAGS`
+
+CLEANFILES = date.c
+
+date.c:
+ echo "char datestamp[]="\"`date`\"";" > date.c
+ echo "char hoststamp[]="\"`hostname`\"";" >> date.c
diff --git a/alpine/Makefile.in b/alpine/Makefile.in
new file mode 100644
index 00000000..4bb886ec
--- /dev/null
+++ b/alpine/Makefile.in
@@ -0,0 +1,823 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 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@
+
+# ========================================================================
+# Copyright 2006-2008 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+VPATH = @srcdir@
+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 = alpine$(EXEEXT) rpdump$(EXEEXT) rpload$(EXEEXT)
+subdir = alpine
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/acx_pthread.m4 \
+ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.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/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/VERSION \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/include/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_alpine_OBJECTS = addrbook.$(OBJEXT) adrbkcmd.$(OBJEXT) \
+ after.$(OBJEXT) alpine.$(OBJEXT) arg.$(OBJEXT) busy.$(OBJEXT) \
+ colorconf.$(OBJEXT) confscroll.$(OBJEXT) context.$(OBJEXT) \
+ dispfilt.$(OBJEXT) flagmaint.$(OBJEXT) folder.$(OBJEXT) \
+ help.$(OBJEXT) imap.$(OBJEXT) init.$(OBJEXT) kblock.$(OBJEXT) \
+ keymenu.$(OBJEXT) ldapconf.$(OBJEXT) listsel.$(OBJEXT) \
+ mailcmd.$(OBJEXT) mailindx.$(OBJEXT) mailpart.$(OBJEXT) \
+ mailview.$(OBJEXT) newuser.$(OBJEXT) pattern.$(OBJEXT) \
+ pipe.$(OBJEXT) print.$(OBJEXT) radio.$(OBJEXT) \
+ remote.$(OBJEXT) reply.$(OBJEXT) roleconf.$(OBJEXT) \
+ send.$(OBJEXT) setup.$(OBJEXT) signal.$(OBJEXT) \
+ status.$(OBJEXT) takeaddr.$(OBJEXT) titlebar.$(OBJEXT) \
+ smime.$(OBJEXT) newmail.$(OBJEXT)
+nodist_alpine_OBJECTS = date.$(OBJEXT)
+alpine_OBJECTS = $(am_alpine_OBJECTS) $(nodist_alpine_OBJECTS)
+am__DEPENDENCIES_1 =
+alpine_DEPENDENCIES = $(LDADD) $(am__DEPENDENCIES_1)
+am_rpdump_OBJECTS = rpdump.$(OBJEXT)
+rpdump_OBJECTS = $(am_rpdump_OBJECTS)
+rpdump_LDADD = $(LDADD)
+rpdump_DEPENDENCIES = ../pico/libpico.a ../pico/osdep/libpicoosd.a \
+ ../pith/libpith.a ../pith/osdep/libpithosd.a \
+ ../pith/charconv/libpithcc.a osdep/libpineosd.a \
+ ../c-client/c-client.a
+am_rpload_OBJECTS = rpload.$(OBJEXT)
+rpload_OBJECTS = $(am_rpload_OBJECTS)
+rpload_LDADD = $(LDADD)
+rpload_DEPENDENCIES = ../pico/libpico.a ../pico/osdep/libpicoosd.a \
+ ../pith/libpith.a ../pith/osdep/libpithosd.a \
+ ../pith/charconv/libpithcc.a osdep/libpineosd.a \
+ ../c-client/c-client.a
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(alpine_SOURCES) $(nodist_alpine_SOURCES) $(rpdump_SOURCES) \
+ $(rpload_SOURCES)
+DIST_SOURCES = $(alpine_SOURCES) $(rpdump_SOURCES) $(rpload_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+ html-recursive info-recursive install-data-recursive \
+ install-dvi-recursive install-exec-recursive \
+ install-html-recursive install-info-recursive \
+ install-pdf-recursive install-ps-recursive install-recursive \
+ installcheck-recursive installdirs-recursive pdf-recursive \
+ ps-recursive uninstall-recursive
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
+ $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
+ distdir
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_CFLAGS = @AM_CFLAGS@
+AM_LDFLAGS = `cat @top_srcdir@/c-client/LDFLAGS`
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CP = @CP@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+C_CLIENT_CFLAGS = @C_CLIENT_CFLAGS@
+C_CLIENT_GCCOPTLEVEL = @C_CLIENT_GCCOPTLEVEL@
+C_CLIENT_LDFLAGS = @C_CLIENT_LDFLAGS@
+C_CLIENT_SPECIALS = @C_CLIENT_SPECIALS@
+C_CLIENT_TARGET = @C_CLIENT_TARGET@
+C_CLIENT_WITH_IPV6 = @C_CLIENT_WITH_IPV6@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISPELLPROG = @ISPELLPROG@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN = @LN@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKE = @MAKE@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NPA_PROG = @NPA_PROG@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+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@
+POSUB = @POSUB@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+PWPROG = @PWPROG@
+RANLIB = @RANLIB@
+REGEX_BUILD = @REGEX_BUILD@
+RM = @RM@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPELLPROG = @SPELLPROG@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEB_BINDIR = @WEB_BINDIR@
+WEB_BUILD = @WEB_BUILD@
+WEB_PUBCOOKIE_BUILD = @WEB_PUBCOOKIE_BUILD@
+WEB_PUBCOOKIE_LIB = @WEB_PUBCOOKIE_LIB@
+WEB_PUBCOOKIE_LINK = @WEB_PUBCOOKIE_LINK@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+acx_pthread_config = @acx_pthread_config@
+alpine_interactive_spellcheck = @alpine_interactive_spellcheck@
+alpine_simple_spellcheck = @alpine_simple_spellcheck@
+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@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_ECHO = @lt_ECHO@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = osdep
+alpine_SOURCES = addrbook.c adrbkcmd.c after.c alpine.c arg.c busy.c colorconf.c \
+ confscroll.c context.c dispfilt.c flagmaint.c folder.c help.c imap.c \
+ init.c kblock.c keymenu.c ldapconf.c listsel.c mailcmd.c mailindx.c \
+ mailpart.c mailview.c newuser.c pattern.c pipe.c print.c radio.c remote.c reply.c \
+ roleconf.c send.c setup.c signal.c status.c takeaddr.c titlebar.c smime.c newmail.c
+
+alpine_LDADD = $(LDADD) $(INTLLIBS)
+nodist_alpine_SOURCES = date.c
+rpdump_SOURCES = rpdump.c
+rpload_SOURCES = rpload.c
+BUILT_SOURCES = date.c
+LDADD = ../pico/libpico.a ../pico/osdep/libpicoosd.a \
+ ../pith/libpith.a ../pith/osdep/libpithosd.a \
+ ../pith/charconv/libpithcc.a \
+ osdep/libpineosd.a ../c-client/c-client.a
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include -DLOCALEDIR=\"$(localedir)\"
+CLEANFILES = date.c
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+.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 alpine/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign alpine/Makefile
+.PRECIOUS: 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__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ 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)
+ test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ 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
+alpine$(EXEEXT): $(alpine_OBJECTS) $(alpine_DEPENDENCIES)
+ @rm -f alpine$(EXEEXT)
+ $(LINK) $(alpine_OBJECTS) $(alpine_LDADD) $(LIBS)
+rpdump$(EXEEXT): $(rpdump_OBJECTS) $(rpdump_DEPENDENCIES)
+ @rm -f rpdump$(EXEEXT)
+ $(LINK) $(rpdump_OBJECTS) $(rpdump_LDADD) $(LIBS)
+rpload$(EXEEXT): $(rpload_OBJECTS) $(rpload_DEPENDENCIES)
+ @rm -f rpload$(EXEEXT)
+ $(LINK) $(rpload_OBJECTS) $(rpload_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/addrbook.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adrbkcmd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/after.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alpine.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/arg.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/busy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colorconf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/confscroll.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/context.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/date.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dispfilt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/flagmaint.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/help.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/init.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kblock.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keymenu.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldapconf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listsel.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailcmd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailindx.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailpart.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailview.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newmail.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newuser.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pattern.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pipe.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/radio.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remote.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reply.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/roleconf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpdump.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rpload.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/send.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/setup.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signal.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smime.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/status.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/takeaddr.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/titlebar.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+# (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+ @fail= failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+ @fail= failcom='exit 1'; \
+ for f in x $$MAKEFLAGS; do \
+ case $$f in \
+ *=* | --[!k]*);; \
+ *k*) failcom='fail=yes';; \
+ esac; \
+ done; \
+ dot_seen=no; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ rev=''; for subdir in $$list; do \
+ if test "$$subdir" = "."; then :; else \
+ rev="$$subdir $$rev"; \
+ fi; \
+ done; \
+ rev="$$rev ."; \
+ target=`echo $@ | sed s/-recursive//`; \
+ for subdir in $$rev; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done && test -z "$$fail"
+tags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+ done
+ctags-recursive:
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+ done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ 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
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ 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"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(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
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+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."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-recursive
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) all check \
+ ctags-recursive install install-am install-strip \
+ tags-recursive
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+ all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic clean-libtool ctags ctags-recursive 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-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-recursive uninstall uninstall-am \
+ uninstall-binPROGRAMS
+
+
+date.c:
+ echo "char datestamp[]="\"`date`\"";" > date.c
+ echo "char hoststamp[]="\"`hostname`\"";" >> date.c
+
+# 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/alpine/addrbook.c b/alpine/addrbook.c
new file mode 100644
index 00000000..ceb746d5
--- /dev/null
+++ b/alpine/addrbook.c
@@ -0,0 +1,7433 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: addrbook.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ addrbook.c
+ display, browse and edit the address book.
+ ====*/
+
+
+#include "headers.h"
+#include "addrbook.h"
+#include "adrbkcmd.h"
+#include "mailindx.h"
+#include "mailcmd.h"
+#include "status.h"
+#include "confscroll.h"
+#include "keymenu.h"
+#include "radio.h"
+#include "titlebar.h"
+#include "help.h"
+#include "folder.h"
+#include "signal.h"
+#include "alpine.h"
+#include "../pith/adrbklib.h"
+#include "../pith/abdlc.h"
+#include "../pith/addrbook.h"
+#include "../pith/ablookup.h"
+#include "../pith/addrstring.h"
+#include "../pith/bldaddr.h"
+#include "../pith/state.h"
+#include "../pith/bitmap.h"
+#include "../pith/newmail.h"
+#include "../pith/remote.h"
+#include "../pith/util.h"
+#include "../pith/thread.h"
+#include "../pith/hist.h"
+#include "../pith/list.h"
+
+
+HelpType gAbookHelp = NO_HELP;
+
+
+/* internal prototypes */
+void end_adrbks(void);
+int init_disp_form_prefix(PerAddrBook *, int *);
+void display_book(int, int, int, int, Pos *);
+void paint_line(int, long, int, Pos *);
+void redraw_addr_screen(void);
+char *addr_book(AddrBookArg, char *, char **);
+void ab_zoom(EXPANDED_S *, int *);
+void ab_unzoom(int *);
+void empty_warning(long);
+void clickable_warning(long);
+void no_tabs_warning(void);
+int ab_apply_cmd(struct pine *, AdrBk *, long, int);
+void ab_select(struct pine *, AdrBk *, long, int, int *);
+int ab_select_type(AdrBk *, int);
+int ab_select_text(AdrBk *, int);
+int match_check(AdrBk_Entry *, int, char *);
+void ab_goto_folder(int);
+long ab_whereis(int *, int);
+int any_addrs_avail(long);
+int entry_is_clickable(long);
+int entry_is_clickable_title(long);
+int is_empty(long);
+int entry_is_listent(long);
+int entry_is_addkey(long);
+int entry_is_askserver(long);
+int add_is_global(long);
+int next_selectable_line(long, long *);
+int prev_selectable_line(long, long *);
+void erase_checks(void);
+int search_book(long, int, long *, int *, int *);
+int find_in_book(long, char *, long *, int *);
+int search_in_one_line(AddrScrn_Disp *, AdrBk_Entry *, char *, char *);
+int abook_select_tool(struct pine *, int, CONF_S **, unsigned);
+char *choose_an_address_with_this_prefix(COMPLETE_S *);
+#ifdef _WINDOWS
+int addr_scroll_up(long);
+int addr_scroll_down(long);
+int addr_scroll_to_pos(long);
+int addr_scroll_callback(int, long);
+char *pcpine_help_addrbook(char *);
+#endif
+
+
+/* TRANSLATORS: This is a placeholder for a list of addresses in address book */
+#define CLICKHERE _("[ Address List ]")
+/* TRANSLATORS: This is an empty address list */
+#define EMPTY _("[ Empty ]")
+#define ZOOM_EMPTY _("[ No Selected Entries in this Address Book ]")
+/* TRANSLATORS: Move here means to move the cursor to this line of the screen */
+#define ADD_PERSONAL _(" [ Move here to add a Personal Address Book ]")
+/* TRANSLATORS: A global address book is a shared address book */
+#define ADD_GLOBAL _(" [ Move here to add a Global Address Book ]")
+/* TRANSLATORS: A heading for an address book distribution list */
+#define DISTLIST _("DISTRIBUTION LIST:")
+#define NOABOOKS _("[ No Address Book Configured ]")
+/* TRANSLATORS: Select here means to move the cursor to this line and then type
+ the Select command. */
+#define CLICKHERECMB _("[ Select Here to See Expanded List ]")
+
+
+
+/*
+ * Returns the index of the current address book.
+ */
+int
+cur_addr_book(void)
+{
+ return(adrbk_num_from_lineno(as.top_ent + as.cur_row));
+}
+
+
+/*
+ * Returns 1 if current abook is open, else 0.
+ */
+int
+cur_is_open(void)
+{
+ int current, ret = 0;
+
+ if(as.initialized &&
+ as.n_addrbk > 0 &&
+ (current = cur_addr_book()) >= 0 &&
+ current < as.n_addrbk &&
+ !entry_is_askserver(as.top_ent+as.cur_row))
+ ret = (as.adrbks[current].ostatus == Open);
+
+ return(ret);
+}
+
+
+void
+end_adrbks(void)
+{
+ int i;
+
+ dprint((2, "- end_adrbks -\n"));
+
+ if(!as.initialized)
+ return;
+
+ for(i = 0; i < as.n_addrbk; i++)
+ init_abook(&as.adrbks[i], Closed);
+
+ as.selections = 0;
+ as.checkboxes = 0;
+ ab_nesting_level = 0;
+}
+
+
+
+/*
+ * We have to call this to set up the format of the columns. There is a
+ * separate format for each addrbook, so we need to call this for each
+ * addrbook. We call it when the pab's are built. It also depends on
+ * whether or not as.checkboxes is set, so if we go into a Select mode
+ * from the address book maintenance screen we need to re-call this. Since
+ * we can't go back out of ListMode we don't have that problem. Restore_state
+ * has to call it because of the as.checkboxes possibly being different in
+ * the two states.
+ */
+void
+init_disp_form(PerAddrBook *pab, char **list, int addrbook_num)
+{
+ addrbook_new_disp_form(pab, list, addrbook_num, init_disp_form_prefix);
+}
+
+
+int
+init_disp_form_prefix(PerAddrBook *pab, int *columnp)
+{
+ int rv = 0;
+
+ if(as.checkboxes){
+ pab->disp_form[(*columnp)].wtype = Fixed;
+ pab->disp_form[(*columnp)].req_width = 3;
+ pab->disp_form[(*columnp)++].type = Checkbox;
+ }
+ else if(as.selections){
+ if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global) && StartBold()){
+ EndBold();
+ rv = 1;
+ }
+ else{
+ pab->disp_form[(*columnp)].wtype = Fixed;
+ pab->disp_form[(*columnp)].req_width = 1;
+ pab->disp_form[(*columnp)++].type = Selected;
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * save_and_restore - single interface to save_state and restore_state
+ */
+void
+save_and_restore(int cmd, SAVE_STATE_S *state)
+{
+ switch(cmd){
+ case SAR_SAVE :
+ save_state(state);
+ break;
+ case SAR_RESTORE :
+ restore_state(state);
+ break;
+ }
+}
+
+
+/*
+ * Save the screen state and the Open or Closed status of the addrbooks.
+ */
+void
+save_state(SAVE_STATE_S *state)
+{
+ int i;
+ DL_CACHE_S *dlc;
+
+ dprint((9, "- save_state -\n"));
+
+ /* allocate space for saving the screen structure and save it */
+ state->savep = (AddrScrState *)fs_get(sizeof(AddrScrState));
+ *(state->savep) = as; /* copy the struct */
+
+
+ if(as.initialized){
+ /* allocate space for saving the ostatus for each addrbook */
+ state->stp = (OpenStatus *)fs_get(as.n_addrbk * sizeof(OpenStatus));
+
+ for(i = 0; i < as.n_addrbk; i++)
+ (state->stp)[i] = as.adrbks[i].ostatus;
+
+
+ state->dlc_to_warp_to = (DL_CACHE_S *)fs_get(sizeof(DL_CACHE_S));
+ dlc = get_dlc(as.top_ent + as.cur_row);
+ *(state->dlc_to_warp_to) = *dlc; /* copy the struct */
+ }
+}
+
+
+/*
+ * Restore the state.
+ *
+ * Side effect: Flushes addrbook entry cache entries so they need to be
+ * re-fetched afterwords. This only applies to entries obtained since
+ * the call to save_state.
+ * Also flushes all dlc cache entries, so dlist calls need to be repeated.
+ */
+void
+restore_state(SAVE_STATE_S *state)
+{
+ int i;
+
+ dprint((9, "- restore_state -\n"));
+
+ as = *(state->savep); /* put back cur_row and all that */
+
+ if(as.initialized){
+ /* restore addressbook OpenStatus to what it was before */
+ for(i = 0; i < as.n_addrbk; i++){
+ init_disp_form(&as.adrbks[i], ps_global->VAR_ABOOK_FORMATS, i);
+ init_abook(&as.adrbks[i], (state->stp)[i]);
+ }
+
+ /*
+ * jump cache back to where we were
+ */
+ warp_to_dlc(state->dlc_to_warp_to, as.top_ent+as.cur_row);
+
+ fs_give((void **)&state->dlc_to_warp_to);
+ fs_give((void **)&state->stp);
+ }
+
+ if(state->savep)
+ fs_give((void **)&state->savep);
+}
+
+
+/*
+ * Returns the addrbook entry for this display row.
+ */
+AdrBk_Entry *
+ae(long int row)
+{
+ PerAddrBook *pab;
+ LineType type;
+ AddrScrn_Disp *dl;
+
+ dl = dlist(row);
+ type = dl->type;
+ if(!(type == Simple || type == ListHead ||
+ type == ListEnt || type == ListClickHere))
+ return((AdrBk_Entry *)NULL);
+
+ pab = &as.adrbks[adrbk_num_from_lineno(row)];
+
+ return(adrbk_get_ae(pab->address_book, (a_c_arg_t) dl->elnum));
+}
+
+
+/*
+ * Args: start_disp -- line to start displaying on when redrawing, 0 is
+ * the top_of_screen
+ * cur_line -- current line number (0 is 1st line we display)
+ * old_line -- old line number
+ * redraw -- flag requesting redraw as opposed to update of
+ * current line
+ * start_pos -- return position where highlighted text begins here
+ *
+ * Result: lines painted on the screen
+ *
+ * It either redraws the screen from line "start_disp" down or
+ * moves the cursor from one field to another.
+ */
+void
+display_book(int start_disp, int cur_line, int old_line, int redraw, Pos *start_pos)
+{
+ int screen_row, highlight;
+ long global_row;
+ Pos sp;
+
+ dprint((9,
+ "- display_book() -\n top %d start %d cur_line %d old_line %d redraw %d\n",
+ as.top_ent, start_disp, cur_line, old_line, redraw));
+
+ if(start_pos){
+ start_pos->row = 0;
+ start_pos->col = 0;
+ }
+
+ if(as.l_p_page <= 0)
+ return;
+
+#ifdef _WINDOWS
+ mswin_beginupdate();
+#endif
+ if(redraw){
+ /*--- Repaint all of the screen or bottom part of screen ---*/
+ global_row = as.top_ent + start_disp;
+ for(screen_row = start_disp;
+ screen_row < as.l_p_page;
+ screen_row++, global_row++){
+
+ highlight = (screen_row == cur_line);
+ ClearLine(screen_row + HEADER_ROWS(ps_global));
+ paint_line(screen_row + HEADER_ROWS(ps_global), global_row,
+ highlight, &sp);
+ if(start_pos && highlight)
+ *start_pos = sp;
+ }
+ }
+ else{
+
+ /*--- Only update current, or move the cursor ---*/
+ if(cur_line != old_line){
+
+ /*--- Repaint old position to erase "cursor" ---*/
+ paint_line(old_line + HEADER_ROWS(ps_global), as.top_ent + old_line,
+ 0, &sp);
+ }
+
+ /*--- paint the position with the cursor ---*/
+ paint_line(cur_line + HEADER_ROWS(ps_global), as.top_ent + cur_line,
+ 1, &sp);
+ if(start_pos)
+ *start_pos = sp;
+ }
+
+#ifdef _WINDOWS
+ scroll_setpos(as.top_ent);
+ mswin_endupdate();
+#endif
+ fflush(stdout);
+}
+
+
+/*
+ * Paint a line on the screen
+ *
+ * Args: line -- Line on screen to paint
+ * global_row -- Row number of line to paint
+ * highlight -- Line should be highlighted
+ * start_pos -- return position where text begins here
+ *
+ * Result: Line is painted
+ *
+ * The three field widths for the formatting are passed in. There is an
+ * implicit 2 spaces between the fields.
+ *
+ * | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] | ...
+ */
+void
+paint_line(int line, long int global_row, int highlight, Pos *start_pos)
+{
+ int scol, bolden = 0;
+ char *start_hilite_here, *end_hilite_here;
+ AddrScrn_Disp *dl;
+ char lbuf[6*MAX_SCREEN_COLS + 1];
+ register char *p;
+
+ dprint((10, "- paint_line(%d, %d) -\n", line, highlight));
+
+ dl = dlist(global_row);
+ start_pos->row = line;
+ start_pos->col = 0; /* default */
+
+ switch(dl->type){
+ case Beginning:
+ case End:
+ return;
+
+ default:
+ break;
+ }
+
+ p = get_abook_display_line(global_row, line == HEADER_ROWS(ps_global),
+ highlight ? &start_hilite_here : NULL,
+ highlight ? &end_hilite_here : NULL,
+ &scol, lbuf, sizeof(lbuf));
+
+ if(as.selections &&
+ as.do_bold &&
+ (dl->type == ListHead || dl->type == Simple)){
+ PerAddrBook *pab;
+
+ pab = &as.adrbks[adrbk_num_from_lineno(global_row)];
+ if(entry_is_selected(pab->address_book->selects, (a_c_arg_t)dl->elnum)){
+ bolden++;
+ StartBold();
+ }
+ }
+
+ if(p && *p){
+ if(highlight){
+ char save_char;
+
+ MoveCursor(line, 0);
+
+ /*
+ * print part before highlight starts
+ */
+ if(start_hilite_here != NULL){
+ save_char = *start_hilite_here;
+ *start_hilite_here = '\0';
+ Write_to_screen(p);
+ *start_hilite_here = save_char;
+ }
+
+ /*
+ * print highlighted part
+ */
+
+ if(end_hilite_here != NULL){
+ save_char = *end_hilite_here;
+ *end_hilite_here = '\0';
+ }
+
+ StartInverse();
+ Write_to_screen(start_hilite_here ? start_hilite_here : p);
+ EndInverse();
+
+ /*
+ * print part after highlight ends
+ */
+ if(end_hilite_here != NULL){
+ *end_hilite_here = save_char;
+ Write_to_screen(end_hilite_here);
+ }
+ }
+ else
+ PutLine0(line, 0, p);
+ }
+
+ if(bolden)
+ EndBold();
+
+ if(scol > 0)
+ start_pos->col = scol;
+}
+
+
+/*
+ * Assemble a line suitable for displaying on screen
+ *
+ * Args: global_row -- Row number of line to assemble.
+ * continuation -- This is the top line of screen display
+ * s_hilite -- Location in the returned line where highlight
+ * should start, if desired
+ * e_hilite -- Location in the returned line where highlight
+ * should end, if desired
+ * retcol -- Return column where text begins here.
+ * lbuf -- Put the output here. Lbuf should be at least
+ * size 6*screen_cols+1.
+ * lbufsize -- Size of lbuf array
+ *
+ * Result: Pointer to lbuf is returned.
+ *
+ * There is an implicit 2 spaces between the fields.
+ *
+ * | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] | ...
+ */
+char *
+get_abook_display_line(long int global_row, int continuation, char **s_hilite,
+ char **e_hilite, int *retcol, char *lbuf, size_t lbufsize)
+{
+ int fld_col[NFIELDS],
+ fld_width[NFIELDS],
+ screen_width, width,
+ col, special_col = 0,
+ width_consumed,
+ scol = -1,
+ fld;
+ char *string, *writeptr;
+ char special[6*MAX_SCREEN_COLS-1];
+ AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ PerAddrBook *pab;
+#define LSPACE() (lbufsize - (writeptr - lbuf) - 1)
+
+ dprint((10, "- get_display_line(%d) -\n", global_row));
+
+ dl = dlist(global_row);
+ if(retcol)
+ *retcol = 0; /* default */
+
+ if(s_hilite){
+ *s_hilite = NULL; /* NULL in these means to hilight whole line */
+ *e_hilite = NULL;
+ }
+
+ writeptr = lbuf;
+ memset(writeptr, 0, lbufsize);
+ memset(special, 0, sizeof(special));
+
+ switch(dl->type){
+ case Beginning:
+ case End:
+ return lbuf;
+
+ default:
+ break;
+ }
+
+ screen_width = ps_global->ttyo->screen_cols;
+
+ /* the types in this set span all columns */
+ switch(dl->type){
+ /* center these */
+ case Text:
+ case Title:
+ case TitleCmb:
+ case ClickHereCmb:
+ case Empty:
+ case ZoomEmpty:
+ case NoAbooks:
+ if(dl->type == Empty)
+ string = EMPTY;
+ else if(dl->type == ZoomEmpty)
+ string = ZOOM_EMPTY;
+ else if(dl->type == NoAbooks)
+ string = NOABOOKS;
+ else if(dl->type == ClickHereCmb)
+ string = CLICKHERECMB;
+ else
+ string = dl->usst;
+
+ /* center it */
+ col = (screen_width - (int) utf8_width(string))/2;
+ col = MAX(col, 0);
+ width_consumed = 0;
+
+ /* col spaces to start */
+ if(col > 0 && LSPACE() >= col){
+ memset(writeptr, ' ', col);
+ writeptr += col;
+ width_consumed += col;
+ }
+
+ if((width=utf8_width(string)) <= screen_width-col){
+ strncpy(writeptr, string, LSPACE());
+ width_consumed += width;
+ }
+ else{
+ utf8_pad_to_width(writeptr, string, LSPACE(), screen_width-col, 1);
+ width_consumed = screen_width;
+ }
+
+ if(s_hilite && *s_hilite == NULL){
+ *s_hilite = writeptr;
+ *e_hilite = writeptr + strlen(writeptr);
+ }
+
+ writeptr += strlen(writeptr);
+ if(width_consumed < screen_width)
+ memset(writeptr, ' ', screen_width-width_consumed);
+
+ if(retcol)
+ *retcol = col;
+
+ return lbuf;
+
+ /* left adjust these */
+ case AddFirstPers:
+ case AddFirstGlob:
+ case AskServer:
+ if(dl->type == AddFirstPers)
+ string = ADD_PERSONAL;
+ else if(dl->type == AddFirstGlob)
+ string = ADD_GLOBAL;
+ else
+ string = dl->usst;
+
+ /* left adjust it */
+ col = 0;
+ width_consumed = 0;
+ if((width=utf8_width(string)) <= screen_width){
+ strncpy(writeptr, string, LSPACE());
+ width_consumed += width;
+ }
+ else{
+ utf8_pad_to_width(writeptr, string, LSPACE(), screen_width, 1);
+ width_consumed = screen_width;
+ }
+
+ if(s_hilite && *s_hilite == NULL){
+ *s_hilite = writeptr;
+ *e_hilite = writeptr + strlen(writeptr);
+ }
+
+ writeptr += strlen(writeptr);
+ if(width_consumed < screen_width)
+ memset(writeptr, ' ', screen_width-width_consumed);
+
+ if(retcol)
+ *retcol = col;
+
+ return lbuf;
+
+ default:
+ break;
+ }
+
+ pab = &as.adrbks[adrbk_num_from_lineno(global_row)];
+ for(fld = 0; fld < NFIELDS; fld++)
+ fld_width[fld] = pab->disp_form[fld].width;
+
+ fld_col[0] = 0;
+ for(fld = 1; fld < NFIELDS; fld++)
+ fld_col[fld] = MIN(fld_col[fld-1]+fld_width[fld-1]+2, screen_width);
+
+ width_consumed = 0;
+
+ /* fill in the fields */
+ for(fld = 0; fld < NFIELDS; fld++){
+ if(fld_width[fld] == 0
+ && !(pab->disp_form[fld].type == Addr
+ && (dl->type == ListClickHere || dl->type == ListEmpty)))
+ continue;
+
+ switch(pab->disp_form[fld].type){
+ case Notused:
+ break;
+
+ case Nickname:
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ abe = ae(global_row);
+ string = (abe && abe->nickname) ? abe->nickname : "";
+ if(scol == -1)
+ scol = fld_col[fld];
+
+ /* left adjust string in field */
+ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
+ writeptr += strlen(writeptr);
+ width_consumed += fld_width[fld];
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case Fullname:
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ abe = ae(global_row);
+ string = (abe && abe->fullname) ? abe->fullname : "";
+ if(scol == -1)
+ scol = fld_col[fld];
+
+ /* left adjust string in field */
+ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
+ writeptr += strlen(writeptr);
+ width_consumed += fld_width[fld];
+ break;
+
+ case ListEnt:
+ /* continuation line */
+ if(continuation){
+ int width1, width2;
+
+ width2 = MIN(11, fld_width[fld]);
+ width1 = MAX(fld_width[fld] - width2 - 1, 0);
+
+ abe = ae(global_row);
+ string = (abe && abe->fullname) ? abe->fullname : "";
+
+ if(width1){
+ utf8_pad_to_width(writeptr, string, LSPACE(), width1, 1);
+ writeptr += strlen(writeptr);
+ if(LSPACE() > 0)
+ *writeptr++ = ' ';
+ }
+
+ /* TRANSLATORS: continuation line meaning there is more on the next page */
+ if(width2 && LSPACE() >= strlen(_("(continued)"))){
+ strncpy(writeptr, _("(continued)"), width2);
+ lbuf[lbufsize-1] = '\0';
+ writeptr += strlen(writeptr);
+ }
+
+ width_consumed += fld_width[fld];
+
+ lbuf[lbufsize-1] = '\0';
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case Addr:
+ switch(dl->type){
+ case ListClickHere:
+ case ListEmpty:
+ if(dl->type == ListClickHere)
+ string = CLICKHERE;
+ else
+ string = EMPTY;
+
+ if((width=utf8_width(string)) <= fld_width[fld]){
+
+ strncpy(writeptr, string, LSPACE());
+
+ if(s_hilite && *s_hilite == NULL){
+ *s_hilite = writeptr;
+ *e_hilite = writeptr + strlen(writeptr);
+ }
+
+ writeptr += strlen(writeptr);
+ width_consumed += width;
+
+ if(scol == -1)
+ scol = fld_col[fld];
+ }
+ else{
+ /*
+ * Place the string in special array and overlay it
+ * onto the right edge of the screen at the end.
+ */
+ if(width <= screen_width){
+ strncpy(special, string, sizeof(special));
+ special_col = screen_width - width;
+ }
+ else{
+ utf8_pad_to_width(special, string, sizeof(special), screen_width, 1);
+ special_col = 0;
+ }
+
+ special[sizeof(special)-1] = '\0';
+ }
+
+ break;
+
+ case ListHead:
+ if(scol == -1)
+ scol = fld_col[fld];
+
+ string = DISTLIST;
+ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
+ writeptr += strlen(writeptr);
+ width_consumed += fld_width[fld];
+ break;
+
+ case Simple:
+ abe = ae(global_row);
+#ifdef ENABLE_LDAP
+ if(abe && abe->addr.addr &&
+ !strncmp(abe->addr.addr, QRUN_LDAP, LEN_QRL))
+ string = LDAP_DISP;
+ else
+#endif
+ string = (abe && abe->tag == Single && abe->addr.addr) ?
+ abe->addr.addr : "";
+ if(scol == -1)
+ scol = fld_col[fld];
+
+ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
+ writeptr += strlen(writeptr);
+ width_consumed += fld_width[fld];
+ break;
+
+ case ListEnt:
+ string = listmem(global_row) ? listmem(global_row) : "";
+ if(scol == -1)
+ scol = fld_col[fld];
+
+ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
+
+ if(s_hilite && *s_hilite == NULL){
+ *s_hilite = writeptr;
+ *e_hilite = writeptr + strlen(writeptr);
+ }
+
+ writeptr += strlen(writeptr);
+ width_consumed += fld_width[fld];
+
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case Filecopy:
+ case Comment:
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ abe = ae(global_row);
+ if(pab->disp_form[fld].type == Filecopy)
+ string = (abe && abe->fcc) ? abe->fcc : "";
+ else
+ string = (abe && abe->extra) ? abe->extra : "";
+
+ if(scol == -1)
+ scol = fld_col[fld];
+
+ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
+ writeptr += strlen(writeptr);
+ width_consumed += fld_width[fld];
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case Checkbox:
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ if(entry_is_checked(pab->address_book->checks,
+ (a_c_arg_t)dl->elnum))
+ string = "[X]";
+ else
+ string = "[ ]";
+
+ if(scol == -1)
+ scol = fld_col[fld];
+
+ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
+ writeptr += strlen(writeptr);
+ width_consumed += fld_width[fld];
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case Selected:
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ if(entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)dl->elnum))
+ string = "X";
+ else
+ string = " ";
+
+ if(scol == -1)
+ scol = fld_col[fld];
+
+ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
+ writeptr += strlen(writeptr);
+ width_consumed += fld_width[fld];
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case WhenNoAddrDisplayed:
+ switch(dl->type){
+ case ListClickHere:
+ case ListEmpty:
+ case ListEnt:
+ if(dl->type == ListClickHere)
+ string = CLICKHERE;
+ else if(dl->type == ListEmpty)
+ string = EMPTY;
+ else
+ string = listmem(global_row) ? listmem(global_row) : "";
+
+ if((width=utf8_width(string)) <= fld_width[fld])
+ strncpy(special, string, sizeof(special));
+ else
+ utf8_pad_to_width(special, string, sizeof(special), fld_width[fld], 1);
+
+ special[sizeof(special)-1] = '\0';
+ special_col = screen_width - fld_width[fld];
+ special_col = MAX(0, special_col);
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ if(fld < NFIELDS-1)
+ while(width_consumed < fld_col[fld+1] && LSPACE() > 0){
+ *writeptr++ = ' ';
+ width_consumed++;
+ }
+ }
+
+ /*
+ * Right adjust the special string in lbuf, writing over
+ * anything in our way.
+ */
+ if(special[0]){
+ unsigned got_width;
+
+ writeptr = utf8_count_forw_width(lbuf, special_col, &got_width);
+
+ strncpy(writeptr, special, LSPACE());
+
+ if(scol == -1)
+ scol = got_width;
+
+ if(s_hilite && *s_hilite == NULL){
+ *s_hilite = writeptr;
+ *e_hilite = writeptr + strlen(writeptr);
+ }
+
+ writeptr += strlen(writeptr);
+
+ width_consumed = (int) utf8_width(lbuf);
+ while(width_consumed++ < screen_width && LSPACE() > 0)
+ *writeptr++ = ' ';
+
+ if(LSPACE() > 0)
+ *writeptr = '\0';
+ }
+
+ if(scol > 0 && retcol)
+ *retcol = scol;
+
+ lbuf[lbufsize-1] = '\0';
+ return lbuf;
+}
+
+
+/*
+ * Set field widths for the columns of the display. The idea is to
+ * try to come up with something that works pretty well. Getting it just
+ * right isn't important.
+ *
+ * Col1 and col2 are arrays which contain some widths useful for
+ * formatting the screen. The 0th element is the max
+ * width in that column. The 1st element is the max of the third largest
+ * width in each addressbook (yup, strange).
+ *
+ * The info above applies to the default case (AllAuto). The newer methods
+ * where the user specifies the format is the else part of the big if and
+ * is quite a bit different. It's all sort of ad hoc when we're asked
+ * to calculate one of the fields for the user.
+ *
+ * Returns non-zero if the widths changed since the last time called.
+ */
+int
+calculate_field_widths(void)
+{
+ int space_left, i, j, screen_width;
+ int ret = 0;
+ PerAddrBook *pab;
+ WIDTH_INFO_S *widths;
+ int max_nick, max_full, max_addr, third_full, third_addr, third_fcc;
+ int col1[5], col2[5];
+ int nick = -1, full = -1, addr = -1;
+#define SEP 2 /* space between columns */
+
+ dprint((9, "- calculate_field_widths -\n"));
+
+ screen_width = ps_global->ttyo->screen_cols;
+
+ /* calculate widths for each addrbook independently */
+ for(j = 0; j < as.n_addrbk; j++){
+ pab = &as.adrbks[j];
+
+ max_nick = 0;
+ max_full = 2;
+ max_addr = 2;
+ third_full = 2;
+ third_addr = 2;
+ third_fcc = 2;
+
+ if(pab->address_book){
+ widths = &pab->address_book->widths;
+ max_nick = MIN(MAX(max_nick, widths->max_nickname_width), 25);
+ max_full = MAX(max_full, widths->max_fullname_width);
+ max_addr = MAX(max_addr, widths->max_addrfield_width);
+ third_full = MAX(third_full, widths->third_biggest_fullname_width);
+ if(third_full == 2)
+ third_full = MAX(third_full, 2*max_full/3);
+
+ third_addr = MAX(third_addr, widths->third_biggest_addrfield_width);
+ if(third_addr == 2)
+ third_addr = MAX(third_addr, 2*max_addr/3);
+
+ third_fcc = MAX(third_fcc, widths->third_biggest_fccfield_width);
+ if(third_fcc == 2)
+ third_fcc = MAX(third_fcc, 2*widths->max_fccfield_width/3);
+ }
+
+ /* figure out which order they're in and reset widths */
+ for(i = 0; i < NFIELDS; i++){
+ pab->disp_form[i].width = 0;
+ switch(pab->disp_form[i].type){
+ case Nickname:
+ nick = i;
+ break;
+
+ case Fullname:
+ full = i;
+ break;
+
+ case Addr:
+ addr = i;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Compute default format */
+ if(pab->disp_form[1].wtype == AllAuto){
+
+ col1[0] = max_full;
+ col2[0] = max_addr;
+ col1[1] = third_full;
+ col2[1] = third_addr;
+ col1[2] = 3;
+ col2[2] = 3;
+ col1[3] = 2;
+ col2[3] = 2;
+ col1[4] = 1;
+ col2[4] = 1;
+
+ space_left = screen_width;
+
+ if(pab->disp_form[0].type == Selected ||
+ pab->disp_form[0].type == Checkbox){
+ pab->disp_form[0].width = MIN(pab->disp_form[0].req_width,space_left);
+ space_left = MAX(space_left-(pab->disp_form[0].width + SEP), 0);
+ }
+
+ /*
+ * All of the nickname field should be visible,
+ * and make it at least 3.
+ */
+ pab->disp_form[nick].width = MIN(MAX(max_nick, 3), space_left);
+
+ /*
+ * The SEP is for two blank columns between nickname and next field.
+ * Those blank columns are taken automatically in paint_line().
+ */
+ space_left -= (pab->disp_form[nick].width + SEP);
+
+ if(space_left > 0){
+ for(i = 0; i < 5; i++){
+ /* try fitting most of each field in if possible */
+ if(col1[i] + SEP + col2[i] <= space_left){
+ int extra;
+
+ extra = space_left - col1[i] - SEP - col2[i];
+ /*
+ * try to stabilize nickname column shifts
+ * so that screen doesn't jump around when we make changes
+ */
+ if(i == 0 && pab->disp_form[nick].width < 7 &&
+ extra >= (7 - pab->disp_form[nick].width)){
+ extra -= (7 - pab->disp_form[nick].width);
+ space_left -= (7 - pab->disp_form[nick].width);
+ pab->disp_form[nick].width = 7;
+ }
+
+ pab->disp_form[addr].width = col2[i] + extra/2;
+ pab->disp_form[full].width =
+ space_left - SEP - pab->disp_form[addr].width;
+ break;
+ }
+ }
+
+ /*
+ * None of them would fit. Toss addr field.
+ */
+ if(i == 5){
+ pab->disp_form[full].width = space_left;
+ pab->disp_form[addr].width = 0;
+ }
+ }
+ else{
+ pab->disp_form[full].width = 0;
+ pab->disp_form[addr].width = 0;
+ }
+
+ dprint((10, "Using %s choice: %d %d %d", enth_string(i+1),
+ pab->disp_form[nick].width, pab->disp_form[full].width,
+ pab->disp_form[addr].width));
+ }
+ else{ /* non-default case */
+ int some_to_calculate = 0;
+ int columns = 0;
+ int used = 0;
+ int avail_screen;
+ int all_percents = 1;
+ int pc_tot;
+
+ /*
+ * First count how many fields there are.
+ * Fill in all the Fixed's while we're at it.
+ */
+ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
+ if(pab->disp_form[i].wtype == Fixed){
+ pab->disp_form[i].width = pab->disp_form[i].req_width;
+ all_percents = 0;
+ }
+ else if(pab->disp_form[i].wtype == WeCalculate){
+ pab->disp_form[i].width = pab->disp_form[i].req_width; /* for now */
+ some_to_calculate++;
+ all_percents = 0;
+ }
+
+ if(pab->disp_form[i].wtype != Special){
+ used += pab->disp_form[i].width;
+ columns++;
+ }
+ }
+
+ used += ((columns-1) * SEP);
+ avail_screen = screen_width - used;
+
+ /*
+ * Now that we know how much space we've got, we can
+ * calculate the Percent columns.
+ */
+ if(avail_screen > 0){
+ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
+ if(pab->disp_form[i].wtype == Percent){
+ /* The 2, 200, and +100 are because we're rounding */
+ pab->disp_form[i].width =
+ ((2*pab->disp_form[i].req_width*avail_screen)+100) / 200;
+ used += pab->disp_form[i].width;
+ }
+ }
+ }
+
+ space_left = screen_width - used;
+
+ if(space_left < 0){
+ /*
+ * If they're all percentages, and the percentages add up to 100,
+ * then we should fix the rounding problem.
+ */
+ pc_tot = 0;
+ if(all_percents){
+ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++)
+ if(pab->disp_form[i].wtype == Percent)
+ pc_tot += pab->disp_form[i].req_width;
+ }
+
+ /* fix the rounding problem */
+ if(all_percents && pc_tot <= 100){
+ int col = columns;
+ int this_col = 0;
+ int fix = used - screen_width;
+
+ while(fix--){
+ if(col < 0)
+ col = columns;
+
+ /* find col'th column */
+ for(i=0, this_col=0; i < NFIELDS; i++){
+ if(pab->disp_form[i].wtype == Percent){
+ if(col == ++this_col)
+ break;
+ }
+ }
+
+ pab->disp_form[i].width--;
+ col--;
+ }
+ }
+ /*
+ * Assume they meant to have them add up to over 100%, so we
+ * just truncate the right hand edge.
+ */
+ else{
+ int this_fix, space_over;
+
+ /* have to reduce space_over down to zero. */
+ space_over = used - screen_width;
+ for(i=NFIELDS-1; i >= 0 && space_over > 0; i--){
+ if(pab->disp_form[i].type != Notused){
+ this_fix = MIN(pab->disp_form[i].width, space_over);
+ pab->disp_form[i].width -= this_fix;
+ space_over -= this_fix;
+ }
+ }
+ }
+ }
+ else if(space_left > 0){
+ if(some_to_calculate){
+ /* make nickname big enough to show all nicknames */
+ if(nick >= 0 && pab->disp_form[nick].wtype == WeCalculate){
+ --some_to_calculate;
+ if(pab->disp_form[nick].width != max_nick){
+ int this_fix;
+
+ this_fix = MIN(max_nick-pab->disp_form[nick].width, space_left);
+ pab->disp_form[nick].width += this_fix;
+ space_left -= this_fix;
+ }
+ }
+
+ if(!some_to_calculate && space_left > 0)
+ goto none_to_calculate;
+
+ if(space_left > 0){
+ int weight = 0;
+ int used_wt = 0;
+
+ /* add up total weight */
+ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
+ if(i != nick && pab->disp_form[i].wtype == WeCalculate){
+ switch(pab->disp_form[i].type){
+ case Fullname:
+ weight += MAX(third_full, pab->disp_form[i].width);
+ used_wt += pab->disp_form[i].width;
+ break;
+
+ case Addr:
+ weight += MAX(third_addr, pab->disp_form[i].width);
+ used_wt += pab->disp_form[i].width;
+ break;
+
+ case Filecopy:
+ weight += MAX(third_fcc, pab->disp_form[i].width);
+ used_wt += pab->disp_form[i].width;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if(weight > 0){
+ int this_fix;
+
+ if(weight - used_wt <= space_left){
+ for(i = 0;
+ i < NFIELDS && pab->disp_form[i].type != Notused;
+ i++){
+ if(i != nick && pab->disp_form[i].wtype == WeCalculate){
+ switch(pab->disp_form[i].type){
+ case Fullname:
+ this_fix = third_full - pab->disp_form[i].width;
+ space_left -= this_fix;
+ pab->disp_form[i].width += this_fix;
+ break;
+
+ case Addr:
+ this_fix = third_addr - pab->disp_form[i].width;
+ space_left -= this_fix;
+ pab->disp_form[i].width += this_fix;
+ break;
+
+ case Filecopy:
+ this_fix = third_fcc - pab->disp_form[i].width;
+ space_left -= this_fix;
+ pab->disp_form[i].width += this_fix;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /* if still space left and a comment field, all to comment */
+ if(space_left){
+ for(i = 0;
+ i < NFIELDS && pab->disp_form[i].type != Notused;
+ i++){
+ if(pab->disp_form[i].type == Comment &&
+ pab->disp_form[i].wtype == WeCalculate){
+ pab->disp_form[i].width += space_left;
+ space_left = 0;
+ }
+ }
+ }
+ }
+ else{ /* not enough space, dole out weighted pieces */
+ int was_sl = space_left;
+
+ for(i = 0;
+ i < NFIELDS && pab->disp_form[i].type != Notused;
+ i++){
+ if(i != nick && pab->disp_form[i].wtype == WeCalculate){
+ switch(pab->disp_form[i].type){
+ case Fullname:
+ /* round down */
+ this_fix = (third_full * was_sl)/weight;
+ space_left -= this_fix;
+ pab->disp_form[i].width += this_fix;
+ break;
+
+ case Addr:
+ this_fix = (third_addr * was_sl)/weight;
+ space_left -= this_fix;
+ pab->disp_form[i].width += this_fix;
+ break;
+
+ case Filecopy:
+ this_fix = (third_fcc * was_sl)/weight;
+ space_left -= this_fix;
+ pab->disp_form[i].width += this_fix;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* give out rest */
+ while(space_left > 0){
+ for(i=NFIELDS-1; i >= 0 && space_left > 0; i--){
+ if(i != nick && pab->disp_form[i].wtype == WeCalculate){
+ pab->disp_form[i].width++;
+ space_left--;
+ }
+ }
+ }
+ }
+ }
+ else{
+ /*
+ * If they're all percentages, and the percentages add up to 100,
+ * then we just have to fix a rounding problem. Otherwise, we'll
+ * assume the user meant to have them add up to less than 100.
+ */
+none_to_calculate:
+ pc_tot = 0;
+ if(all_percents){
+ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++)
+ if(pab->disp_form[i].wtype == Percent)
+ pc_tot += pab->disp_form[i].req_width;
+ }
+
+ if(all_percents && pc_tot >= 100){
+ int col = columns;
+ int this_col = 0;
+ int fix = screen_width - used;
+
+ while(fix--){
+ if(col < 0)
+ col = columns;
+
+ /* find col'th column */
+ for(i=0, this_col=0; i < NFIELDS; i++){
+ if(pab->disp_form[i].wtype == Percent){
+ if(col == ++this_col)
+ break;
+ }
+ }
+
+ pab->disp_form[i].width++;
+ col--;
+ }
+ }
+ /* else, user specified less than 100%, leave it */
+ }
+ }
+ /* else space_left == zero, nothing to do */
+
+ /*
+ * Check for special case. If we find it, this is the case where
+ * we want to display the list entry field even though there is no
+ * address field displayed. All of the display width is probably
+ * used up by now, so we just need to pick some arbitrary width
+ * for these lines. Since these lines are separate from the other
+ * lines we've been calculating, we don't have to worry about running
+ * into them, except for list continuation lines which we're not
+ * going to worry about.
+ */
+ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
+ if(pab->disp_form[i].wtype == Special){
+ pab->disp_form[i].width = MIN(utf8_width(CLICKHERE), screen_width);
+ break;
+ }
+ }
+ }
+
+ /* check for width changes */
+ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
+ if(pab->disp_form[i].width != pab->disp_form[i].old_width){
+ ret++; /* Tell the caller the screen changed */
+ pab->disp_form[i].old_width = pab->disp_form[i].width;
+ }
+ }
+
+ pab->nick_is_displayed = 0;
+ pab->full_is_displayed = 0;
+ pab->addr_is_displayed = 0;
+ pab->fcc_is_displayed = 0;
+ pab->comment_is_displayed = 0;
+ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
+ if(pab->disp_form[i].width > 0){
+ switch(pab->disp_form[i].type){
+ case Nickname:
+ pab->nick_is_displayed++;
+ break;
+ case Fullname:
+ pab->full_is_displayed++;
+ break;
+ case Addr:
+ pab->addr_is_displayed++;
+ break;
+ case Filecopy:
+ pab->fcc_is_displayed++;
+ break;
+ case Comment:
+ pab->comment_is_displayed++;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ if(ret)
+ dprint((9, " some widths changed\n"));
+
+ return(ret);
+}
+
+
+void
+redraw_addr_screen(void)
+{
+ dprint((7, "- redraw_addr_screen -\n"));
+
+ ab_resize();
+ if(as.l_p_page <= 0)
+ return;
+
+ (void)calculate_field_widths();
+ display_book(0, as.cur_row, -1, 1, (Pos *)NULL);
+}
+
+
+/*
+ * Little front end for address book screen so it can be called out
+ * of the main command loop in alpine.c
+ */
+void
+addr_book_screen(struct pine *pine_state)
+{
+ dprint((3, "\n\n --- ADDR_BOOK_SCREEN ---\n\n"));
+
+ mailcap_free(); /* free resources we won't be using for a while */
+
+ if(setjmp(addrbook_changed_unexpectedly)){
+ /* TRANSLATORS: a warning message telling the user that the address book
+ is being reset (re-sychronized, restarted) */
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book... addr_book_screen!\n"));
+ addrbook_reset();
+ }
+
+ ab_nesting_level = 1; /* come here only from main menu */
+
+ /* TRANSLATORS: a screen title for address book screen. Almost all of
+ the translatable strings which are all upper case are screen titles. */
+ (void)addr_book(AddrBookScreen, _("ADDRESS BOOK"), NULL);
+ end_adrbks();
+
+ pine_state->prev_screen = addr_book_screen;
+}
+
+
+void
+addr_book_config(struct pine *pine_state, int edit_exceptions)
+{
+ if(edit_exceptions){
+ q_status_message(SM_ORDER, 3, 7,
+ _("Exception Setup not implemented for address books"));
+ return;
+ }
+
+ dprint((3, "\n\n --- ADDR_BOOK_CONFIG ---\n\n"));
+
+ mailcap_free(); /* free resources we won't be using for a while */
+
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book... addr_book_config!\n"));
+ addrbook_reset();
+ }
+
+ ab_nesting_level = 1;
+
+ /* TRANSLATORS: A screen title */
+ (void)addr_book(AddrBookConfig, _("SETUP ADDRESS BOOKS"), NULL);
+ end_adrbks();
+
+ pine_state->prev_screen = addr_book_screen;
+}
+
+
+/*
+ * Return a single address
+ *
+ * Returns: pointer to returned address, or NULL if nothing returned
+ */
+char *
+addr_book_oneaddr(void)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "--- addr_book_oneaddr ---\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book... addr_book_oneaddr!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ /* TRANSLATORS: a screen title */
+ p = addr_book(SelectAddr, _("SELECT ADDRESS"), NULL);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Return a list of addresses without fullname
+ *
+ * Returns: pointer to returned address, or NULL if nothing returned
+ */
+char *
+addr_book_multaddr_nf(void)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "--- addr_book_multaddr_nf ---\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book... addr_book_multaddr_nf!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ p = addr_book(SelectMultNoFull, _("SELECT ADDRESS"), NULL);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Return a single address without fullname phrase
+ *
+ * Returns: pointer to returned address, or NULL if nothing returned
+ */
+char *
+addr_book_oneaddr_nf(void)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "--- addr_book_oneaddr_nf ---\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book... addr_book_oneaddr_nf!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ p = addr_book(SelectAddrNoFull, _("SELECT ADDRESS"), NULL);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Call address book from message composer
+ *
+ * Args: error_mess -- pointer to return error messages in (unused here)
+ *
+ * Returns: pointer to returned address, or NULL if nothing returned
+ */
+char *
+addr_book_compose(char **error)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "--- addr_book_compose ---\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book... addr_book_compose!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ /* TRANSLATORS: a screen title */
+ p = addr_book(SelectNicksCom, _("COMPOSER: SELECT ADDRESS"), error);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Call address book from message composer for Lcc line
+ *
+ * Args: error_mess -- pointer to return error messages in (unused here)
+ *
+ * Returns: pointer to returned address, or NULL if nothing returned
+ */
+char *
+addr_book_compose_lcc(char **error)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "--- addr_book_compose_lcc ---\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book... addr_book_compose_lcc!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ /*
+ * We used to use SelectAddrLccCom here but decided it wasn't necessary
+ * to restrict the selection to a list.
+ */
+ /* TRANSLATORS: a screen title, user is composing a message and should select
+ a distribution list from a list of addresses in the address book. */
+ p = addr_book(SelectNicksCom, _("COMPOSER: SELECT LIST"), error);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Call address book from message composer for Lcc line
+ *
+ * Args: error_mess -- pointer to return error messages in (unused here)
+ *
+ * Returns: pointer to returned address, or NULL if nothing returned
+ */
+char *
+addr_book_change_list(char **error)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "--- addr_book_change_list ---\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book... addr_book_change_list!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ /* TRANSLATORS: a screen title */
+ p = addr_book(SelectNicksCom, _("ADDRESS BOOK (Update): SELECT ADDRESSES"),
+ error);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Call address book from pine_simple_send
+ *
+ * Returns: pointer to returned address, or NULL if nothing returned
+ */
+char *
+addr_book_bounce(void)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "- addr_book_bounce -\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book...addr_book_bounce!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ p = addr_book(SelectManyNicks, _("SELECT ADDRESSES"), NULL);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Call address book from take address screen
+ *
+ * Returns: pointer to returned nickname, or NULL if nothing returned
+ */
+char *
+addr_book_takeaddr(void)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "- addr_book_takeaddr -\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book...addr_book_takeaddr!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ /* TRANSLATORS: a screen title */
+ p = addr_book(SelectNickTake, _("TAKEADDR: SELECT NICKNAME"), NULL);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Call address book from editing screen for nickname field.
+ *
+ * Returns: pointer to returned nickname, or NULL if nothing returned
+ */
+char *
+addr_book_nick_for_edit(char **error)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+ int save_n_serv;
+
+ dprint((3, "- addr_book_nick_for_edit -\n"));
+
+ save_n_serv = as.n_serv;
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book...addr_book_nick_for_edit!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ /*
+ * This is kind of hoaky. We want to prevent the Directory Query
+ * options from turning on when we're coming in looking for a nickname
+ * and this seemed to be the easiest way to accomplish that.
+ */
+ as.n_serv = 0;
+ /* TRANSLATORS: a screen title, user selecting a nickname from the address book */
+ p = addr_book(SelectNickCom, _("SELECT NICKNAME"), error);
+ as.n_serv = save_n_serv;
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Call address book for generic nickname select
+ *
+ * Returns: pointer to returned nickname, or NULL if nothing returned
+ */
+char *
+addr_book_selnick(void)
+{
+ char *p;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((3, "- addr_book_selnick -\n"));
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1, "RESETTING address book...addr_book_selnick!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+
+ p = addr_book(SelectNick, _("SELECT NICKNAME"), NULL);
+
+ if(ab_nesting_level <= 1)
+ end_adrbks();
+ else
+ ab_nesting_level--;
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ return(p);
+}
+
+
+/*
+ * Main address book screen
+ *
+ * Control loop for address book. Commands are executed out of a big
+ * switch and screen painting is done.
+ * The style argument controls whether or not it is called to return an address
+ * to the composer, a nickname to the TakeAddr screen, or just for address
+ * book maintenance.
+ *
+ * Args: style -- how we were called
+ *
+ * Return: might return a string for the composer to edit, or a nickname, ...
+ */
+char *
+addr_book(AddrBookArg style, char *title, char **error_message)
+{
+ UCS c;
+ int cmd, r, i,
+ command_line,
+ did_delete,
+ quit, /* loop control */
+ km_popped, /* menu is popped up in blank menu mode */
+ current_changed_flag, /* only current row needs update */
+ was_clickable, is_clickable,
+ was_collapsible_listent, is_collapsible_listent,
+ was_global_config, is_global_config,
+ was_addkey, is_addkey,
+ was_opened, is_opened,
+ was_custom_title, is_custom_title,
+ setall_changed,
+ start_disp, /* Paint from this line down (0 is top) */
+ rdonly, /* cur addrbook read only */
+ empty, /* cur addrbook empty */
+ are_selecting, /* called as ^T selector */
+#ifdef ENABLE_LDAP
+ directory_ok, /* called from composer, not Lcc */
+#endif
+ from_composer, /* from composer */
+ listmode_ok, /* ok to do ListMode with this style */
+ selecting_one_nick,
+ selecting_mult_nicks,
+ checkedn, /* how many are checked */
+ def_key, /* default key */
+ warped; /* we warped through hyperspace to a
+ new location in the display list */
+ long fl,
+ new_top_ent, /* entry on top of screen after oper */
+ new_line; /* new line number after operation */
+ char *addr;
+ char *utf8str;
+ bitmap_t bitmap;
+ struct key_menu *km;
+ OtherMenu what;
+ PerAddrBook *pab = NULL;
+ AddrScrn_Disp *dl;
+ struct pine *ps;
+ Pos cursor_pos;
+
+ dprint((2, "--- addr_book --- (%s)\n",
+ style==AddrBookScreen ? "AddrBookScreen" :
+ style==SelectAddrLccCom ? "SelectAddrLccCom" :
+ style==SelectNicksCom ? "SelectNicksCom" :
+ style==SelectNick ? "SelectNick" :
+ style==SelectNickTake ? "SelectNickTake" :
+ style==SelectNickCom ? "SelectNickCom" :
+ style==AddrBookConfig ? "AddrBookConfig" :
+ style==SelectManyNicks ? "SelectManyNicks" :
+ style==SelectAddr ? "SelectAddr" :
+ style==SelectAddrNoFull ? "SelectAddrNoFull" :
+ style==SelectMultNoFull ? "SelectMultNoFull":
+ "UnknownStyle"));
+
+ km = &ab_keymenu;
+ ps = ps_global;
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ from_composer = (style == SelectAddrLccCom
+ || style == SelectNicksCom
+ || style == SelectNickCom);
+ are_selecting = (style != AddrBookScreen
+ && style != AddrBookConfig);
+ selecting_one_nick = (style == SelectNick
+ || style == SelectNickTake
+ || style == SelectNickCom);
+ selecting_mult_nicks = (style == SelectAddrLccCom
+ || style == SelectNicksCom
+ || style == SelectManyNicks);
+ listmode_ok = (style == SelectAddrLccCom
+ || style == SelectNicksCom
+ || style == SelectManyNicks
+ || style == SelectMultNoFull);
+ as.config = (style == AddrBookConfig);
+#ifdef ENABLE_LDAP
+ directory_ok = (style == SelectNicksCom
+ || style == AddrBookScreen
+ || style == SelectManyNicks);
+#endif
+
+ /* Coming in from the composer, may need to reset the window */
+ if(from_composer){
+ fix_windsize(ps);
+ init_sigwinch();
+ mark_status_dirty();
+ mark_titlebar_dirty();
+ mark_keymenu_dirty();
+ }
+
+ command_line = -FOOTER_ROWS(ps); /* third line from the bottom */
+ what = FirstMenu;
+ c = 'x'; /* For display_message the first time through */
+
+ if(ps->remote_abook_validity > 0)
+ (void)adrbk_check_and_fix_all(ab_nesting_level == 1, 0, 0);
+
+ if(!init_addrbooks(HalfOpen, 1, !as.config, !are_selecting)){
+ if(are_selecting){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ _("No Address Book Configured"));
+ display_message(c);
+ sleep(2);
+ return NULL;
+ }
+ else if(!as.config){
+ ps->next_screen = main_menu_screen;
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("No Address Book Configured, Use SETUP Addressbook screen"));
+ ps->mangled_screen = 1;
+ return NULL;
+ }
+ }
+ else if(style == AddrBookScreen && as.n_addrbk == 1 && as.n_serv == 0){
+ if(as.adrbks[0].access == ReadOnly)
+ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
+ else if(as.adrbks[0].access == NoAccess)
+ q_status_message(SM_ORDER, 0, 4,
+ _("AddressBook not accessible, permission denied"));
+ }
+
+ if(as.l_p_page < 1){
+ q_status_message(SM_ORDER, 3, 3,
+ _("Screen too small to use Address book"));
+ return NULL;
+ }
+
+ erase_checks();
+ as.selections = 0;
+ as.zoomed = 0;
+
+ (void) calculate_field_widths();
+
+ quit = 0;
+ km_popped = 0;
+ ps->mangled_screen = 1;
+ current_changed_flag = 0;
+ start_disp = 0;
+ was_clickable = 0;
+ is_clickable = 0;
+ was_collapsible_listent = 0;
+ is_collapsible_listent = 0;
+ was_global_config = 0;
+ is_global_config = 0;
+ was_addkey = 0;
+ is_addkey = 0;
+ was_opened = 0;
+ is_opened = 0;
+ was_custom_title = 0;
+ is_custom_title = 0;
+ setall_changed = 0;
+ checkedn = 0;
+
+
+ while(!quit){
+ ps->user_says_cancel = 0;
+ if(km_popped){
+ km_popped--;
+ if(km_popped == 0){
+ clearfooter(ps);
+ /*
+ * Have to repaint from earliest change down, including
+ * at least the last two body lines.
+ */
+ if(ps->mangled_body) /* it was already mangled */
+ start_disp = MIN(start_disp, as.l_p_page -2);
+ else if(current_changed_flag){
+ ps->mangled_body = 1;
+ start_disp = MIN(MIN(as.cur_row, as.l_p_page -2),
+ as.old_cur_row);
+ }
+ else{
+ ps->mangled_body = 1;
+ start_disp = as.l_p_page -2;
+ }
+ }
+ }
+
+ ps->redrawer = redraw_addr_screen;
+
+ if(new_mail(0, NM_TIMING(c), NM_STATUS_MSG | NM_DEFER_SORT) >= 0)
+ ps->mangled_header = 1;
+
+ if(streams_died())
+ ps->mangled_header = 1;
+
+ if(ps->mangled_screen){
+ ps->mangled_header = 1;
+ ps->mangled_body = 1;
+ ps->mangled_footer = 1;
+ start_disp = 0;
+ current_changed_flag = 0;
+ ps->mangled_screen = 0;
+ }
+
+ if(ps->mangled_body){
+
+ if(calculate_field_widths())
+ start_disp = 0;
+
+ display_book(start_disp,
+ as.cur_row,
+ as.old_cur_row,
+ 1,
+ &cursor_pos);
+
+ as.old_cur_row = as.cur_row;
+ ps->mangled_body = 0;
+ start_disp = 0;
+ as.cur = cur_addr_book();
+ pab = (as.n_addrbk &&
+ as.cur >= 0 && as.cur < as.n_addrbk &&
+ !entry_is_askserver(as.top_ent+as.cur_row))
+ ? &as.adrbks[as.cur] : NULL;
+
+#ifdef _WINDOWS
+ {
+ long i, last_sel;
+
+ for(i = as.top_ent; dlist(i)->type != End; i++)
+ next_selectable_line(i,&last_sel);
+ as.last_ent = i;
+
+ scroll_setrange(as.l_p_page, last_sel);
+ }
+#endif
+ }
+ /* current entry has been changed */
+ else if(current_changed_flag){
+ int need_redraw;
+
+ need_redraw = calculate_field_widths();
+
+ /*---------- Update the current entry, (move or change) -------*/
+ display_book(need_redraw ? 0 : as.cur_row,
+ as.cur_row,
+ as.old_cur_row,
+ need_redraw,
+ &cursor_pos);
+
+ as.old_cur_row = as.cur_row;
+ current_changed_flag = 0;
+ as.cur = cur_addr_book();
+ pab = (as.n_addrbk &&
+ as.cur >= 0 && as.cur < as.n_addrbk &&
+ !entry_is_askserver(as.top_ent+as.cur_row))
+ ? &as.adrbks[as.cur] : NULL;
+ }
+
+ is_custom_title = (F_OFF(F_CMBND_ABOOK_DISP,ps_global) &&
+ style == AddrBookScreen &&
+ as.n_addrbk > 1 &&
+ cur_is_open() &&
+ pab->abnick &&
+ pab->abnick[0]);
+ if(( was_custom_title && !is_custom_title) ||
+ (!was_custom_title && is_custom_title)){
+ ps->mangled_header = 1;
+ was_custom_title = is_custom_title;
+ }
+
+
+ if(ps->mangled_header){
+ char buf[80], *bp;
+
+ if(style == AddrBookScreen){
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(as.n_addrbk > 1)
+ snprintf(buf, sizeof(buf), _("ADDRESS BOOKS"));
+ else
+ snprintf(buf, sizeof(buf), _("ADDRESS BOOK"));
+ }
+ else
+ /* TRANSLATORS: a screen title, the %s arguments are for a custom part
+ of the title. Move them as a group. There are two normal cases.
+ Either ADDRESS BOOK LIST when a list of names of address books is
+ displayed, or ADDRESS BOOK <name of address book> when one is open. */
+ snprintf(buf, sizeof(buf), _("ADDRESS BOOK%s%s%s"),
+ /* TRANSLATORS: This is the LIST that goes with title above */
+ is_custom_title ? " <" : cur_is_open() ? "" : _(" LIST"),
+ is_custom_title ? pab->abnick : "",
+ is_custom_title ? ">" : "");
+
+ buf[sizeof(buf)-1] = '\0';
+
+ bp = buf;
+ }
+ else
+ bp = title;
+
+ set_titlebar(bp, ps->mail_stream,
+ ps->context_current, ps->cur_folder,
+ ps->msgmap, 1,
+ FolderName, 0, 0, NULL);
+ ps->mangled_header = 0;
+ }
+
+ dprint((9, "addr_book: top of loop, addrbk %d top_ent %ld cur_row %d\n", as.cur, as.top_ent, as.cur_row));
+
+ /*
+ * This is a check to catch when a move from one row to another
+ * should cause the list of commands to change.
+ */
+ is_clickable = entry_is_clickable(as.top_ent+as.cur_row);
+ is_collapsible_listent = F_OFF(F_EXPANDED_DISTLISTS,ps) &&
+ entry_is_listent(as.top_ent+as.cur_row);
+ is_global_config = as.config && pab && pab->type & GLOBAL;
+ is_addkey = entry_is_addkey(as.top_ent+as.cur_row);
+ is_opened = (F_OFF(F_CMBND_ABOOK_DISP,ps_global) &&
+ cur_is_open());
+ if(( was_clickable && !is_clickable) ||
+ (!was_clickable && is_clickable) ||
+ ( was_collapsible_listent && !is_collapsible_listent) ||
+ (!was_collapsible_listent && is_collapsible_listent) ||
+ ( was_global_config && !is_global_config) ||
+ (!was_global_config && is_global_config) ||
+ ( was_addkey && !is_addkey) ||
+ (!was_addkey && is_addkey) ||
+ ( was_opened && !is_opened) ||
+ (!was_opened && is_opened) ||
+ ( setall_changed)){
+
+ ps->mangled_footer = 1;
+ was_clickable = is_clickable;
+ was_collapsible_listent = is_collapsible_listent;
+ was_global_config = is_global_config;
+ was_addkey = is_addkey;
+ was_opened = is_opened;
+ setall_changed = 0;
+ }
+
+
+ if(ps->mangled_footer){
+
+ setbitmap(bitmap);
+ menu_clear_binding(km, '>');
+ menu_clear_binding(km, '.');
+ menu_clear_binding(km, KEY_RIGHT);
+ menu_clear_binding(km, ctrl('M'));
+ menu_clear_binding(km, ctrl('J'));
+ menu_clear_binding(km, KEY_LEFT);
+ menu_clear_binding(km, '<');
+ menu_clear_binding(km, ',');
+ def_key = THREE_KEY; /* default default key */
+ if(as.config){
+ km->how_many = 1;
+
+ clrbitn(OTHER_KEY, bitmap);
+ /* TRANSLATORS: a command name for a particular key */
+ menu_init_binding(km, 'E', MC_EXIT, "E", N_("Exit Setup"), TWO_KEY);
+ KS_OSDATASET(&km->keys[TWO_KEY], KS_EXITMODE);
+
+ /*
+ * Don't show Delete or Shuffle command if there is nothing
+ * to delete or shuffle.
+ */
+ if(is_addkey){
+ clrbitn(DELETE_KEY, bitmap);
+ clrbitn(SENDTO_KEY, bitmap);
+ clrbitn(THREE_KEY, bitmap);
+ menu_init_binding(km, 'A', MC_ADDABOOK, "A",
+ add_is_global(as.top_ent+as.cur_row)
+ /* TRANSLATORS: Add Global Address book (one defined by someone else) */
+ ? "[" N_("Add Glob Abook") "]"
+ /* TRANSLATORS: Add Personal Address book */
+ : "[" N_("Add Pers Abook") "]",
+ ADD_KEY);
+ def_key = ADD_KEY;
+ }
+ else{
+ /* TRANSLATORS: Delete Address book command */
+ menu_init_binding(km, 'D', MC_DELABOOK, "D", N_("Del Abook"),
+ DELETE_KEY);
+ /* TRANSLATORS: Shuffle refers to shuffling the order of things,
+ that is, changing the order */
+ menu_init_binding(km, '$', MC_SHUFFLE, "$", N_("Shuffle"),
+ SENDTO_KEY);
+ /* TRANSLATORS: Change is a command meaning Change some item,
+ or Edit some item to change it */
+ menu_init_binding(km, 'C', MC_EDITABOOK, "C", "[" N_("Change") "]",
+ THREE_KEY);
+ menu_init_binding(km, 'A', MC_ADDABOOK, "A",
+ add_is_global(as.top_ent+as.cur_row)
+ ? N_("Add Glob Abook")
+ : N_("Add Pers Abook"),
+ ADD_KEY);
+ }
+ }
+ else if(are_selecting){
+ km->how_many = 1;
+
+ /*
+ * The OTHER_KEY is used as the Exit key in selection mode.
+ * This is because the TWO_KEY is being used for < actions.
+ */
+ /* TRANSLATORS: Command to Exit the Select screen. This would
+ be a way to make no Selection and go back to where you
+ came from. */
+ menu_init_binding(km, 'E', MC_EXIT, "E", N_("ExitSelect"),
+ OTHER_KEY);
+ KS_OSDATASET(&km->keys[OTHER_KEY], KS_EXITMODE);
+
+ /*
+ * Use the TWO_KEY for the go back key.
+ */
+ if(cur_is_open() && (as.n_addrbk > 1 || as.n_serv)){
+ if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
+ entry_is_listent(as.top_ent+as.cur_row))
+ cmd = MC_UNEXPAND;
+ else if(F_OFF(F_CMBND_ABOOK_DISP,ps_global))
+ cmd = MC_POPUP;
+ else
+ cmd = MC_NONE;
+
+ if(cmd == MC_NONE)
+ clrbitn(TWO_KEY, bitmap);
+ else{
+ menu_init_binding(km, '<', cmd, "<",
+ cmd == MC_POPUP ? N_("AddressBkList")
+ /* TRANSLATORS: Unexpand is the opposite of Expand.
+ We might expand an address book distribution
+ list to see all the members of the list. */
+ : N_("Unexpand"),
+ TWO_KEY);
+ menu_add_binding(km, ',', cmd);
+ if(F_ON(F_ARROW_NAV,ps))
+ menu_add_binding(km, KEY_LEFT, cmd);
+ }
+ }
+ else if(as.checkboxes && (as.n_addrbk > 1 || as.n_serv)){
+ if(checkedn){
+ if(entry_is_clickable_title(as.top_ent+as.cur_row)){
+ menu_init_binding(km, 'S', MC_CHOICE, "S",
+ /* TRANSLATORS: Select something, choose something */
+ N_("Select"), TWO_KEY);
+ }
+ else{
+ menu_init_binding(km, 'S', MC_CHOICE, "S",
+ "[" N_("Select") "]", TWO_KEY);
+ def_key = TWO_KEY;
+ }
+ }
+ else
+ menu_init_binding(km, 'S', MC_CHOICE, "S", N_("Select"),
+ TWO_KEY);
+ }
+ else
+ clrbitn(TWO_KEY, bitmap);
+
+ /*
+ * The THREE_KEY is used as the select key in selection mode,
+ * but it doesn't show up at the top-level. Instead, the
+ * key becomes the ViewAbook key.
+ */
+ if(entry_is_askserver(as.top_ent+as.cur_row) && !as.checkboxes){
+ menu_init_binding(km, '>', MC_QUERY_SERV, ">", "[" N_("Search") "]",
+ THREE_KEY);
+ menu_add_binding(km, 's', MC_QUERY_SERV);
+ menu_add_binding(km, '.', MC_QUERY_SERV);
+ if(F_ON(F_ARROW_NAV,ps))
+ menu_add_binding(km, KEY_RIGHT, MC_QUERY_SERV);
+ }
+ else if(entry_is_clickable_title(as.top_ent+as.cur_row)){
+ /* TRANSLATORS: View this address book */
+ menu_init_binding(km, '>', MC_OPENABOOK, ">", "[" N_("ViewAbook") "]",
+ THREE_KEY);
+ menu_add_binding(km, 'v', MC_OPENABOOK);
+ menu_add_binding(km, '.', MC_OPENABOOK);
+ if(F_ON(F_ARROW_NAV,ps))
+ menu_add_binding(km, KEY_RIGHT, MC_OPENABOOK);
+ }
+ else if(cur_is_open()){
+ menu_init_binding(km, 'S', MC_CHOICE, "S", "[" N_("Select") "]",
+ THREE_KEY);
+ }
+ else
+ clrbitn(THREE_KEY, bitmap);
+
+ KS_OSDATASET(&km->keys[THREE_KEY], KS_NONE);
+
+ /*
+ * The Expand command gets stuck out in right field.
+ */
+ if(entry_is_clickable(as.top_ent+as.cur_row) &&
+ !entry_is_clickable_title(as.top_ent+as.cur_row)){
+ menu_init_binding(km, '>', MC_EXPAND, ">", N_("Expand"),
+ SENDTO_KEY);
+ menu_add_binding(km, '.', MC_EXPAND);
+ if(F_ON(F_ARROW_NAV,ps))
+ menu_add_binding(km, KEY_RIGHT, MC_EXPAND);
+ }
+ else
+ clrbitn(SENDTO_KEY, bitmap);
+
+ if(cur_is_open() && as.checkboxes){
+ /* TRANSLATORS: Set/Unset means that this particular command
+ will toggle between setting something (turning it on) and
+ unsetting it (turning it off). For example, it might be
+ a program option that can be turned on or off or it might
+ be a way to mark which addresses to send a message to. */
+ menu_init_binding(km, 'X', MC_TOGGLE, "X", N_("Set/Unset"),
+ DELETE_KEY);
+
+ }
+ else if(cur_is_open() && listmode_ok){
+ /* TRANSLATORS: List mode is a type of screen in pine that
+ allows the user to select several of something. This is
+ the name of the command to go into the List mode style
+ of operating. */
+ menu_init_binding(km, 'L', MC_LISTMODE, "L", N_("ListMode"),
+ DELETE_KEY);
+ }
+ else
+ clrbitn(DELETE_KEY, bitmap);
+
+ if(cur_is_open() && as.checkboxes){
+ menu_init_binding(km, 'A', MC_SELALL, "A",
+ /* TRANSLATORS: when selecting from a list of items
+ the unsetall (unset all) means to start over
+ with nothing selected.
+ The set all command means select everything
+ in the list. */
+ checkedn ? N_("unsetAll") : N_("setAll"),
+ ADD_KEY);
+ }
+ else
+ clrbitn(ADD_KEY, bitmap);
+
+ KS_OSDATASET(&km->keys[DELETE_KEY], KS_NONE);
+ }
+ else{
+ /*
+ * Reset first Other key. Selection screen may have
+ * blasted it. Do this by hand because menu_init_binding
+ * will remove the other two OTHER CMDS bindings.
+ * Should figure out how to do this correctly with a
+ * reasonable function call.
+ */
+ km->keys[OTHER_KEY].name = "O";
+ /* TRANSLATORS: This is the name of the command that will show
+ which other commands are available. 12 commands are shown at
+ the bottom of the screen, this command would show the next set
+ of 12 */
+ km->keys[OTHER_KEY].label = N_("OTHER CMDS");
+ km->keys[OTHER_KEY].bind.cmd = MC_OTHER;
+ km->keys[OTHER_KEY].bind.ch[0] = 'O';
+ km->keys[OTHER_KEY].bind.nch = 1;
+ KS_OSDATASET(&km->keys[OTHER_KEY], KS_NONE);
+
+ km->how_many = 2;
+
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ if(F_ON(F_ENABLE_AGG_OPS, ps))
+ km->how_many = 3;
+
+ /*
+ * The TWO_KEY is used as the go back key in
+ * non-selection mode.
+ */
+ if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
+ entry_is_listent(as.top_ent+as.cur_row)){
+ cmd = MC_UNEXPAND;
+ menu_init_binding(km, '<', cmd, "<", N_("Unexpand"),
+ TWO_KEY);
+ KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
+ }
+ else{
+ cmd = MC_MAIN;
+ menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
+ TWO_KEY);
+ KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
+ }
+
+ if(cur_is_open()){
+ /*
+ * Add or delete entries from this address book.
+ */
+ /* TRANSLATORS: Add a new entry (this is a command) */
+ menu_init_binding(km, '@', MC_ADD, "@", N_("AddNew"),
+ ADD_KEY);
+ menu_init_binding(km, 'D', MC_DELETE, "D", N_("Delete"),
+ DELETE_KEY);
+ /* TRANSLATORS: Compose a message to be sent to the current address
+ book entry */
+ menu_init_binding(km, 'C', MC_COMPOSE, "C", N_("ComposeTo"),
+ SENDTO_KEY);
+ KS_OSDATASET(&km->keys[SENDTO_KEY], KS_COMPOSER);
+ menu_init_binding(km, '#', MC_ROLE, "#", N_("Role"),
+ RCOMPOSE_KEY);
+ }
+ else{
+ clrbitn(ADD_KEY, bitmap);
+ clrbitn(DELETE_KEY, bitmap);
+ clrbitn(SENDTO_KEY, bitmap);
+ clrbitn(RCOMPOSE_KEY, bitmap);
+ clrbitn(SAVE_KEY, bitmap);
+ clrbitn(TAKE_KEY, bitmap);
+ clrbitn(FORW_KEY, bitmap);
+ }
+
+ clrbitn(SECONDARY_MAIN_KEY, bitmap);
+ }
+ else if(cur_is_open()){
+ if(F_ON(F_ENABLE_AGG_OPS, ps))
+ km->how_many = 3;
+
+ /*
+ * The TWO_KEY is used as the go back key in
+ * non-selection mode.
+ */
+ if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
+ entry_is_listent(as.top_ent+as.cur_row)){
+ cmd = MC_UNEXPAND;
+ menu_init_binding(km, '<', cmd, "<", N_("Unexpand"),
+ TWO_KEY);
+ KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
+ }
+ else{
+ if(as.n_addrbk > 1 || as.n_serv){
+ cmd = MC_POPUP;
+ menu_init_binding(km, '<', cmd, "<",
+ N_("AddressBkList"), TWO_KEY);
+ KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
+ }
+ else{
+ cmd = MC_MAIN;
+ menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
+ TWO_KEY);
+ KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
+ }
+ }
+
+ if(pab->access != NoAccess){
+ /*
+ * Add or delete entries from this address book.
+ */
+ menu_init_binding(km, '@', MC_ADD, "@", N_("AddNew"),
+ ADD_KEY);
+ menu_init_binding(km, 'D', MC_DELETE, "D", N_("Delete"),
+ DELETE_KEY);
+ }
+ else{
+ clrbitn(ADD_KEY, bitmap);
+ clrbitn(DELETE_KEY, bitmap);
+ }
+
+ /* Find someplace to put Main Menu command */
+ if(cmd == MC_POPUP){
+ menu_init_binding(km, 'M', MC_MAIN, "M", N_("Main Menu"),
+ SECONDARY_MAIN_KEY);
+ KS_OSDATASET(&km->keys[SECONDARY_MAIN_KEY],KS_MAINMENU);
+ }
+ else
+ clrbitn(SECONDARY_MAIN_KEY, bitmap);
+
+ menu_init_binding(km, 'C', MC_COMPOSE, "C", N_("ComposeTo"),
+ SENDTO_KEY);
+ KS_OSDATASET(&km->keys[SENDTO_KEY], KS_COMPOSER);
+ menu_init_binding(km, '#', MC_ROLE, "#", N_("Role"),
+ RCOMPOSE_KEY);
+ }
+ else{
+ /*
+ * The TWO_KEY is used as the go back key in
+ * non-selection mode.
+ */
+ cmd = MC_MAIN;
+ menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
+ TWO_KEY);
+ KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
+
+ clrbitn(SENDTO_KEY, bitmap);
+ clrbitn(RCOMPOSE_KEY, bitmap);
+ clrbitn(ADD_KEY, bitmap);
+ clrbitn(DELETE_KEY, bitmap);
+ clrbitn(SECONDARY_MAIN_KEY, bitmap);
+ clrbitn(SAVE_KEY, bitmap);
+ clrbitn(TAKE_KEY, bitmap);
+ clrbitn(FORW_KEY, bitmap);
+ }
+
+ /* can't be on third menu if we just reduced to 2 */
+ if(km->how_many == 2 && km->which == 2)
+ km->which = 0;
+
+ menu_add_binding(km, '<', cmd);
+ menu_add_binding(km, ',', cmd);
+ if(F_ON(F_ARROW_NAV,ps))
+ menu_add_binding(km, KEY_LEFT, cmd);
+
+ KS_OSDATASET(&km->keys[DELETE_KEY], KS_DELETE);
+
+ /*
+ * The THREE_KEY is the burrow into the hierarchy key.
+ */
+ if(entry_is_askserver(as.top_ent+as.cur_row))
+ cmd = MC_QUERY_SERV;
+ else if(entry_is_clickable(as.top_ent+as.cur_row)){
+ if(entry_is_clickable_title(as.top_ent+as.cur_row))
+ cmd = MC_OPENABOOK;
+ else
+ cmd = MC_EXPAND;
+ }
+ else
+ cmd = MC_VIEW_ENTRY;
+
+ menu_init_binding(km, '>', cmd, ">",
+ cmd == MC_EXPAND ? "[" N_("Expand") "]" :
+ cmd == MC_QUERY_SERV ? "[" N_("Search") "]" :
+ /* TRANSLATORS: In the address book the user can
+ view a particular address book entry. It is
+ called View/Update because the way to update
+ an entry is to first view it and then there
+ will be an opportunity to update it from there. */
+ cur_is_open() ? "[" N_("View/Update") "]"
+ : "[" N_("ViewAbook") "]",
+ THREE_KEY);
+
+ if(cmd == MC_QUERY_SERV)
+ menu_add_binding(km, 's', cmd);
+ else if(cmd == MC_OPENABOOK || cmd == MC_VIEW_ENTRY)
+ menu_add_binding(km, 'v', cmd);
+
+ menu_add_binding(km, '.', cmd);
+ if(F_ON(F_ARROW_NAV,ps))
+ menu_add_binding(km, KEY_RIGHT, cmd);
+ }
+
+ menu_add_binding(km, ctrl('M'), km->keys[def_key].bind.cmd);
+ menu_add_binding(km, ctrl('J'), km->keys[def_key].bind.cmd);
+
+ if(km_popped){
+ FOOTER_ROWS(ps) = 3;
+ clearfooter(ps);
+ }
+
+ draw_keymenu(km, bitmap, ps->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps), 0, what);
+ ps->mangled_footer = 0;
+ what = SameMenu;
+ if(km_popped){
+ FOOTER_ROWS(ps) = 1;
+ mark_keymenu_dirty();
+ }
+ }
+
+ rdonly = (pab && pab->access == ReadOnly);
+ empty = is_empty(as.cur_row+as.top_ent);
+
+ /*------------ display any status messages ------------------*/
+ if(km_popped){
+ FOOTER_ROWS(ps) = 3;
+ mark_status_unknown();
+ }
+
+ display_message(c);
+ if(km_popped){
+ FOOTER_ROWS(ps) = 1;
+ mark_status_unknown();
+ }
+
+ if(F_OFF(F_SHOW_CURSOR, ps)){
+ /* reset each time through to catch screen size changes */
+ cursor_pos.row = ps->ttyo->screen_rows-FOOTER_ROWS(ps);
+ cursor_pos.col = 0;
+ }
+
+ MoveCursor(cursor_pos.row, cursor_pos.col);
+
+
+ /*---------------- Get command and validate -------------------*/
+#ifdef MOUSE
+ mouse_in_content(KEY_MOUSE, -1, -1, 0, 0);
+ register_mfunc(mouse_in_content, HEADER_ROWS(ps), 0,
+ ps->ttyo->screen_rows-(FOOTER_ROWS(ps)+1),
+ ps->ttyo->screen_cols);
+#endif
+
+ /* sort out what help needs to be displayed if asked for */
+ if(as.config)
+ gAbookHelp = h_abook_config;
+ else if(are_selecting){
+ if(cur_is_open()){
+ if(style == SelectNickTake)
+ gAbookHelp = h_abook_select_nicks_take;
+ else if(selecting_one_nick)
+ gAbookHelp = h_abook_select_nick;
+ else if(as.checkboxes)
+ gAbookHelp = h_abook_select_checks;
+ else if(listmode_ok)
+ gAbookHelp = h_abook_select_listmode;
+ else
+ gAbookHelp = h_abook_select_addr;
+ }
+ else
+ gAbookHelp = h_abook_select_top;
+ }
+ else
+ gAbookHelp = cur_is_open() ? h_abook_opened : h_abook_top;
+
+#ifdef _WINDOWS
+ mswin_setscrollcallback(addr_scroll_callback);
+ mswin_sethelptextcallback(pcpine_help_addrbook);
+#endif
+ c = READ_COMMAND(&utf8str);
+#ifdef MOUSE
+ clear_mfunc(mouse_in_content);
+#endif
+#ifdef _WINDOWS
+ mswin_setscrollcallback(NULL);
+ mswin_sethelptextcallback(NULL);
+#endif
+ cmd = menu_command(c, km);
+
+ dprint((2, "Addrbook command: %d (0x%x %s)\n", cmd,
+ c, pretty_command(c)));
+
+ /* this may be a safe place to update addrbooks */
+ if(ps->remote_abook_validity > 0 &&
+ adrbk_check_and_fix_all(ab_nesting_level < 2 &&
+ !any_ab_open() && checkedn == 0, 0, 0))
+ ps->mangled_footer = 1;
+
+ if(km_popped)
+ switch(cmd){
+ case MC_NONE:
+ case MC_OTHER:
+ case MC_RESIZE:
+ case MC_REPAINT:
+ km_popped++;
+ break;
+
+ default:
+ clearfooter(ps);
+ break;
+ }
+
+ /*------------- execute command ----------------*/
+ switch(cmd){
+
+ /*------------ Noop (new mail check) --------------*/
+ case MC_NONE:
+ break;
+
+
+ /*----------- Help -------------------*/
+ case MC_HELP:
+ if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
+ km_popped = 2;
+ ps->mangled_footer = 1;
+ break;
+ }
+
+ if(as.config)
+ helper(gAbookHelp, _("HELP ON CONFIGURING ADDRESS BOOKS"),
+ HLPD_NONE);
+ else if(are_selecting)
+ helper(gAbookHelp, _("HELP ON ADDRESS BOOK"),
+ HLPD_SIMPLE | HLPD_NEWWIN);
+ else /* general maintenance screen */
+ helper(gAbookHelp, _("HELP ON ADDRESS BOOK"), HLPD_NONE);
+
+ /*
+ * Helper() may have a Main Menu key. If user types that
+ * they'll set next_screen. We don't have to do anything
+ * special but we want to make sure that that doesn't happen
+ * when we're selecting, even though it shouldn't be
+ * possible because HLPD_SIMPLE is set.
+ */
+ if(are_selecting)
+ ps->next_screen = SCREEN_FUN_NULL; /* probably not needed */
+
+ ps->mangled_screen = 1;
+ break;
+
+
+ /*---------- display other key bindings ------*/
+ case MC_OTHER:
+ warn_other_cmds();
+ what = NextMenu;
+ ps->mangled_footer = 1;
+ break;
+
+
+ /*------------ Unexpand list -----------------*/
+ case MC_UNEXPAND:
+ if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
+ entry_is_listent(as.top_ent+as.cur_row)){
+ DL_CACHE_S *dlc_to_flush;
+ long global_row_num;
+
+ /*
+ * unexpand list
+ */
+
+ /*
+ * redraw screen starting with first ListEnt
+ */
+ dl = dlist(as.top_ent+as.cur_row);
+ dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
+
+ /*
+ * New cur_row should be the line after the ListHead line
+ * that takes the place of the first address in the list.
+ */
+ as.cur_row = as.cur_row - dl->l_offset;
+
+ /*
+ * If the list header for the new row is off the screen,
+ * adjust it.
+ */
+ if(as.cur_row < 0){ /* center it */
+ global_row_num = dlc_to_flush->global_row -
+ dlc_to_flush->dlcoffset;
+ as.top_ent = first_line(global_row_num - as.l_p_page/2L);
+ as.cur_row = global_row_num - as.top_ent;
+ start_disp = 0;
+ }
+ else if(as.cur_row == 0){ /* just slide up in this case */
+ as.top_ent -= 1;
+ as.cur_row = 1;
+ start_disp = 0;
+ }
+ else
+ start_disp = as.cur_row;
+
+ exp_unset_expanded(pab->address_book->exp,
+ (a_c_arg_t)dlc_to_flush->dlcelnum);
+ flush_dlc_from_cache(dlc_to_flush);
+ ps->mangled_body = 1;
+ ps->mangled_footer = 1;
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Can't happen in MC_UNEXPAND");
+
+ break;
+
+ /*------ Popup to top level display ----------*/
+ case MC_POPUP:
+ if(F_ON(F_EXPANDED_DISTLISTS,ps) ||
+ !entry_is_listent(as.top_ent+as.cur_row)){
+ DL_CACHE_S dlc_restart;
+ long new_row;
+
+ (void)init_addrbooks((checkedn || as.selections)
+ ? ThreeQuartOpen : HalfOpen,
+ 0, 0, !are_selecting);
+ dlc_restart.adrbk_num = as.cur;
+ dlc_restart.type = DlcTitle;
+ warp_to_dlc(&dlc_restart, 0L);
+ /*
+ * Put the current entry in a nice spot on the screen.
+ * Will everything above fit and still leave ours on screen?
+ */
+ new_row = LINES_PER_ABOOK * as.cur +
+ (((pab->type & GLOBAL) &&
+ (as.how_many_personals > 0 || as.config))
+ ? XTRA_LINES_BETWEEN : 0) +
+ ((as.how_many_personals == 0 && as.config)
+ ? LINES_PER_ADD_LINE : 0);
+ new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
+
+ as.cur_row = new_row;
+ as.top_ent = 0L - as.cur_row;
+ start_disp = 0;
+ ps->mangled_screen = 1;
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Can't happen in MC_POPUP");
+
+ break;
+
+
+ /*------------- Back to main menu or exit to caller -------*/
+ case MC_EXIT:
+ case MC_MAIN:
+ if(!are_selecting)
+ ps->next_screen = main_menu_screen;
+
+ if(!(are_selecting && as.checkboxes && checkedn > 0)
+ /* TRANSLATORS: we are asking for confirmation about abandonding selections
+ in the address book. */
+ || want_to(_("Really abandon your selections "),
+ 'y', 'x', NO_HELP, WT_NORM) == 'y')
+ quit++;
+ else
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ break;
+
+
+ /*------- Open an address book ----------*/
+ case MC_OPENABOOK:
+openabook:
+ if(entry_is_clickable_title(as.top_ent+as.cur_row)){
+ DL_CACHE_S *dlc_to_flush;
+
+ dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
+ if(dlc_to_flush->type == DlcTitle ||
+ dlc_to_flush->type == DlcClickHereCmb){
+
+ /*
+ * open this addrbook and fill in display list
+ */
+
+ init_abook(pab, Open);
+
+ if(pab->access == ReadWrite || pab->access == ReadOnly){
+ if(!are_selecting && pab->access == ReadOnly)
+ q_status_message1(SM_ORDER, 0, 4, _("AddressBook %s is Read Only"), pab->abnick);
+
+ /*
+ * successful open, burrow into addrbook
+ */
+ if(F_OFF(F_CMBND_ABOOK_DISP,ps_global)){
+ warp_to_top_of_abook(as.cur);
+ as.top_ent = 0L;
+ as.cur_row = 0L;
+ start_disp = 0;
+ ps->mangled_screen = 1;
+ }
+ else{
+ flush_dlc_from_cache(dlc_to_flush);
+ start_disp = as.cur_row;
+ ps->mangled_footer = 1;
+ ps->mangled_body = 1;
+ }
+ }
+ else{ /* open failed */
+ /*
+ * Flush the title line so that it will change into
+ * a permission denied line,
+ */
+ flush_dlc_from_cache(dlc_to_flush);
+ start_disp = as.cur_row;
+ ps->mangled_footer = 1;
+ ps->mangled_body = 1;
+ }
+ }
+ else if(dlc_to_flush->type == DlcTitleNoPerm)
+ q_status_message(SM_ORDER, 0, 4,
+ _("Cannot access address book."));
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Can't happen in MC_OPENABOOK");
+
+ break;
+
+
+ /*------- Expand addresses in List ------*/
+ case MC_EXPAND:
+expand:
+ if(entry_is_clickable(as.top_ent+as.cur_row)){
+ DL_CACHE_S *dlc_to_flush;
+
+ dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
+ if(dlc_to_flush->type == DlcListClickHere){
+ start_disp = as.cur_row; /* will redraw from here down */
+
+ /*
+ * Mark this list expanded, then flush the
+ * current line from dlc cache. When we get the
+ * line again it will notice the expanded flag and change
+ * the type to DlcListEnt (if any entries).
+ */
+
+ if(F_OFF(F_EXPANDED_DISTLISTS,ps))
+ exp_set_expanded(pab->address_book->exp,
+ (a_c_arg_t)dlc_to_flush->dlcelnum);
+
+ flush_dlc_from_cache(dlc_to_flush);
+ dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
+ /*
+ * If list is empty, back cursor up one.
+ */
+ if(dlc_to_flush->type == DlcListEmpty){
+ as.cur_row--;
+ start_disp--;
+ if(as.cur_row < 0){
+ as.top_ent--;
+ as.cur_row = 0;
+ start_disp = 0;
+ }
+ }
+
+ ps->mangled_body = 1;
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Can't happen in MC_EXPAND");
+
+ break;
+
+
+ /*------- Select ---------------*/
+ case MC_CHOICE:
+select:
+ if(are_selecting){
+ /* Select an entry to mail to or a nickname to add to */
+ if(!any_addrs_avail(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ _("No entries in address book. Use ExitSelect to leave address books"));
+ break;
+ }
+
+ if(as.checkboxes || is_addr(as.top_ent+as.cur_row)){
+ BuildTo bldto;
+ char *to = NULL;
+ char *error = NULL;
+ AdrBk_Entry *abe;
+
+ dl = dlist(as.top_ent+as.cur_row);
+
+ if(selecting_one_nick){
+ char nickbuf[MAX_NICKNAME + 1];
+
+ strncpy(nickbuf,
+ ae(as.top_ent+as.cur_row)->nickname,
+ sizeof(nickbuf)-1);
+ nickbuf[sizeof(nickbuf)-1] = '\0';
+ return(cpystr(nickbuf));
+ }
+ else if(as.checkboxes && checkedn <= 0){
+ q_status_message(SM_ORDER, 0, 1,
+ _("Use \"X\" to mark addresses or lists"));
+ break;
+ }
+ else if(as.checkboxes){
+ size_t incr = 100, avail, alloced;
+
+ /*
+ * Have to run through all of the checked entries
+ * in all of the address books.
+ * Put the nicknames together into one long
+ * string with comma separators and let
+ * our_build_address handle the parsing.
+ */
+ to = (char *)fs_get(incr);
+ *to = '\0';
+ avail = incr;
+ alloced = incr;
+
+ for(i = 0; i < as.n_addrbk; i++){
+ EXPANDED_S *next_one;
+ adrbk_cntr_t num;
+ AddrScrn_Disp fake_dl;
+ char *a_string;
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->checks;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT){
+ abe = adrbk_get_ae(pab->address_book,
+ (a_c_arg_t) num);
+ /*
+ * Since we're picking up address book entries
+ * directly from the address books and have
+ * no knowledge of the display lines they came
+ * from, we don't know the dl's that go with
+ * them. We need to pass a dl to abe_to_nick
+ * but it really is only going to use the
+ * type in this case.
+ */
+ dl = &fake_dl;
+ dl->type = (abe->tag == Single) ? Simple
+ : ListHead;
+ a_string = abe_to_nick_or_addr_string(abe, dl, i);
+
+ while(abe && avail < (size_t)strlen(a_string)+1){
+ alloced += incr;
+ avail += incr;
+ fs_resize((void **)&to, alloced);
+ }
+
+ if(!*to){
+ strncpy(to, a_string, alloced);
+ to[alloced-1] = '\0';
+ }
+ else{
+ strncat(to, ",", alloced-strlen(to)-1);
+ strncat(to, a_string, alloced-strlen(to)-1);
+ }
+
+ avail -= (strlen(a_string) + 1);
+ }
+ }
+
+ /*
+ * Return the nickname list for lcc so that the
+ * correct fullname can make it to the To line.
+ * If we expand it ahead of time, the list name
+ * and first user's fullname will get mushed together.
+ * If an entry doesn't have a nickname then we're
+ * out of luck as far as getting the right entry
+ * in the To line goes.
+ */
+ if(selecting_mult_nicks)
+ return(to);
+
+ bldto.type = Str;
+ bldto.arg.str = to;
+ }
+ else{
+ /* Select an address, but not using checkboxes */
+ if(selecting_mult_nicks){
+ if(dl->type != ListHead && style == SelectAddrLccCom){
+ q_status_message(SM_ORDER, 0, 4,
+ _("You may only select lists for Lcc, use Bcc for other addresses"));
+ break;
+ }
+ else{
+ /*
+ * Even though we're supposedly selecting
+ * nicknames, we have a special case here to
+ * select a single member of a distribution
+ * list. This happens with style SelectNicksCom
+ * which is the regular ^T entry from the
+ * composer, and it allows somebody to mail to
+ * a single member of a distribution list.
+ */
+ abe = ae(as.top_ent+as.cur_row);
+ return(abe_to_nick_or_addr_string(abe, dl, as.cur));
+ }
+ }
+ else{
+ if(dl->type == ListEnt){
+ bldto.type = Str;
+ bldto.arg.str =
+ listmem_from_dl(pab->address_book, dl);
+ }
+ else{
+ bldto.type = Abe;
+ bldto.arg.abe = ae(as.top_ent+as.cur_row);
+ }
+ }
+ }
+
+ (void)our_build_address(bldto, &addr, &error, NULL, save_and_restore);
+ /* Have to rfc1522_decode the addr */
+ if(addr){
+ char *tmp_a_string, *p;
+ ADDRESS *a = NULL;
+
+ if(style == SelectAddrNoFull){
+ tmp_a_string = cpystr(addr);
+ rfc822_parse_adrlist(&a,tmp_a_string,ps->maildomain);
+ fs_give((void **)&tmp_a_string);
+ if(a){
+ fs_give((void **)&addr);
+ addr = cpystr(simple_addr_string(a, tmp_20k_buf,
+ SIZEOF_20KBUF));
+ mail_free_address(&a);
+ }
+ }
+ else if(style == SelectMultNoFull){
+ tmp_a_string = cpystr(addr);
+ rfc822_parse_adrlist(&a,tmp_a_string,ps->maildomain);
+ fs_give((void **)&tmp_a_string);
+ if(a){
+ fs_give((void **)&addr);
+ addr = cpystr(simple_mult_addr_string(a,
+ tmp_20k_buf,
+ SIZEOF_20KBUF,
+ ","));
+ mail_free_address(&a);
+ }
+ }
+ else{
+ size_t len;
+ len = 4*strlen(addr)+1;
+ p = (char *)fs_get(len * sizeof(char));
+ if(rfc1522_decode_to_utf8((unsigned char *)p, len, addr) == (unsigned char *)p){
+ fs_give((void **)&addr);
+ addr = p;
+ }
+ else
+ fs_give((void **)&p);
+ }
+ }
+
+ if(to)
+ fs_give((void **)&to);
+
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ return(addr); /* Caller frees this */
+ }
+ else{
+ if(entry_is_clickable(as.top_ent+as.cur_row))
+ clickable_warning(as.top_ent+as.cur_row);
+ else if(entry_is_askserver(as.top_ent+as.cur_row))
+ q_status_message(SM_ORDER, 3, 4, _("Use select to select an address or addresses from address books"));
+ else
+ q_status_message(SM_ORDER, 3, 4, _("No address selected"));
+
+ break;
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Can't happen in MC_CHOICE");
+
+ break;
+
+
+ /*----- View an entry --------------------*/
+ case MC_VIEW_ENTRY:
+view:
+ if(empty){
+ empty_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ view_abook_entry(ps, as.top_ent+as.cur_row);
+ break;
+
+
+ /*----- Add new ---------*/
+ case MC_ADD:
+ {long old_l_p_p, old_top_ent, old_cur_row;
+
+ if(adrbk_check_all_validity_now()){
+ if(resync_screen(pab, style, checkedn)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Address book changed. AddNew cancelled. Try again."));
+ ps->mangled_screen = 1;
+ break;
+ }
+ }
+
+ if(rdonly){
+ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
+ break;
+ }
+
+ warped = 0;
+ dprint((9,
+ "Calling edit_entry to add entry manually\n"));
+ /* TRANSLATORS: add as in add a new entry to something */
+ edit_entry(pab->address_book, (AdrBk_Entry *)NULL, NO_NEXT,
+ NotSet, 0, &warped, _("add"));
+
+ /*
+ * Warped means we got plopped down somewhere in the display
+ * list so that we don't know where we are relative to where
+ * we were before we warped. The current line number will
+ * be zero, since that is what the warp would have set.
+ */
+ if(warped){
+ as.top_ent = first_line(0L - as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+ else{
+ /*
+ * If we didn't warp, that means we didn't change at all,
+ * so keep old screen.
+ */
+ old_l_p_p = as.l_p_page;
+ old_top_ent = as.top_ent;
+ old_cur_row = as.cur_row;
+ }
+
+ /* Window size may have changed while in pico. */
+ ab_resize();
+
+ /* fix up what ab_resize messed up */
+ if(!warped && old_l_p_p == as.l_p_page){
+ as.top_ent = old_top_ent;
+ as.cur_row = old_cur_row;
+ as.old_cur_row = old_cur_row;
+ }
+ }
+
+ ps->mangled_screen = 1;
+ break;
+
+
+ /*---------- Add a new address book -------------------*/
+ case MC_ADDABOOK:
+ {int old_abook_num, new_abook_num, new_row, global;
+ int stay_put, old_cur_row;
+
+add_abook:
+ stay_put = (dlist(as.top_ent+as.cur_row)->type == AddFirstPers ||
+ dlist(as.top_ent+as.cur_row)->type == AddFirstGlob);
+ old_cur_row = as.cur_row;
+ old_abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
+ global = (dlist(as.top_ent+as.cur_row)->type == AddFirstGlob ||
+ (dlist(as.top_ent+as.cur_row)->type != AddFirstPers &&
+ (old_abook_num >= as.how_many_personals) &&
+ as.n_addrbk > 0));
+ if((new_abook_num =
+ ab_add_abook(global,
+ stay_put ? -1
+ : adrbk_num_from_lineno(as.top_ent+as.cur_row)))
+ >= 0){
+ DL_CACHE_S dlc_restart;
+
+ (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
+
+ erase_checks();
+ erase_selections();
+
+ dlc_restart.adrbk_num = new_abook_num;
+ dlc_restart.type = DlcTitle;
+ warp_to_dlc(&dlc_restart, 0L);
+
+ /*
+ * Put the current entry in a nice spot on the screen.
+ */
+ new_row = old_cur_row +
+ (stay_put
+ ? 0
+ : LINES_PER_ABOOK*(new_abook_num - old_abook_num));
+
+ if(new_row >= 0 &&
+ new_row <= (as.l_p_page-VIS_LINES_PER_ABOOK))/* ok, use it */
+ as.cur_row = new_row;
+ else{
+ /*
+ * Will everything above fit and still leave ours on screen?
+ */
+ new_row = LINES_PER_ABOOK * new_abook_num +
+ ((global &&
+ (as.how_many_personals > 0 || as.config))
+ ? XTRA_LINES_BETWEEN : 0) +
+ ((as.how_many_personals == 0 && as.config)
+ ? LINES_PER_ADD_LINE : 0);
+ new_row = MAX(MIN(new_row,
+ as.l_p_page - VIS_LINES_PER_ABOOK), 0);
+ as.cur_row = new_row;
+ }
+
+ as.top_ent = 0L - as.cur_row;
+ dprint((5, "addrbook added: %s\n",
+ as.adrbks[new_abook_num].filename
+ ? as.adrbks[new_abook_num].filename : "?"));
+ }
+
+ ps->mangled_screen = 1;
+
+ }
+
+ break;
+
+
+ /*---------- Change address book config -------------------*/
+ case MC_EDITABOOK:
+change_abook:
+ if(entry_is_clickable_title(as.top_ent+as.cur_row)){
+ int abook_num, old_cur_row, global;
+ long old_top_ent;
+ DL_CACHE_S dlc_restart, *dlc;
+ char *serv = NULL, *folder = NULL, *p, *q;
+ char *nick = NULL, *file = NULL;
+
+ abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
+ global = (abook_num >= as.how_many_personals);
+ old_cur_row = as.cur_row;
+ old_top_ent = as.top_ent;
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ dlc_restart = *dlc;
+
+ if(global)
+ q = ps_global->VAR_GLOB_ADDRBOOK[abook_num -
+ as.how_many_personals];
+ else
+ q = ps_global->VAR_ADDRESSBOOK[abook_num];
+
+ get_pair(q, &nick, &file, 0, 0);
+
+ if(nick && !*nick)
+ fs_give((void **)&nick);
+
+ if(file && *file == '{'){
+ q = file + 1;
+ if((p = strindex(file, '}'))){
+ *p = '\0';
+ serv = q;
+ folder = p+1;
+ }
+ else{
+ q_status_message1(SM_ORDER|SM_DING, 0, 4,
+ _("Missing \"}\" in config: %s"), q);
+ if(nick)
+ fs_give((void **)&nick);
+ if(file)
+ fs_give((void **)&file);
+
+ break;
+ }
+ }
+ else
+ folder = file;
+
+ if(ab_edit_abook(global, abook_num, serv, folder, nick) >= 0){
+ (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
+
+ erase_checks();
+ erase_selections();
+
+ dlc_restart.type = DlcTitle;
+ warp_to_dlc(&dlc_restart, old_top_ent+(long)old_cur_row);
+ as.cur_row = old_cur_row;
+ as.top_ent = old_top_ent;
+
+ dprint((5, "addrbook config edited: %s\n",
+ as.adrbks[dlc_restart.adrbk_num].filename
+ ? as.adrbks[dlc_restart.adrbk_num].filename : "?"));
+ }
+
+ if(nick)
+ fs_give((void **)&nick);
+ if(file)
+ fs_give((void **)&file);
+
+ ps->mangled_screen = 1;
+ }
+ else
+ /* TRANSLATORS: the user tried to change the current line of the address
+ book but the line could not be changed */
+ q_status_message(SM_ORDER, 0, 4, _("Not a changeable line"));
+
+ break;
+
+
+ /*---------- Delete an address book -------------------*/
+ case MC_DELABOOK:
+ if(as.n_addrbk == 0){
+ q_status_message(SM_ORDER, 0, 4, _("Nothing to delete"));
+ break;
+ }
+
+ {char *err = NULL;
+
+ if(ab_del_abook(as.top_ent+as.cur_row, command_line, &err) >= 0){
+ DL_CACHE_S dlc_restart;
+ int new_abook_num, old_abook_num, old_pers, old_glob, new_row;
+
+ /*
+ * Careful, these are only ok because ab_del_abook didn't
+ * mess with the as globals, like as.how_many_personals.
+ * The addrbook_reset does reset them, of course.
+ */
+ old_abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
+ old_pers = as.how_many_personals;
+ old_glob = as.n_addrbk - as.how_many_personals;
+ addrbook_reset();
+ (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
+
+ erase_checks();
+ erase_selections();
+
+ if(old_abook_num >= as.n_addrbk) /* we deleted last addrbook */
+ new_abook_num = as.n_addrbk - 1;
+ else
+ new_abook_num = old_abook_num;
+
+ /*
+ * Pick a line to highlight and center
+ */
+ if(as.how_many_personals == 0 && old_pers == 1)
+ dlc_restart.type = DlcPersAdd;
+ else if((as.n_addrbk - as.how_many_personals) == 0 &&
+ old_glob == 1)
+ dlc_restart.type = DlcGlobAdd;
+ else if(as.n_addrbk == 0)
+ dlc_restart.type = DlcPersAdd;
+ else{
+ dlc_restart.adrbk_num = new_abook_num;
+ dlc_restart.type = DlcTitle;
+ }
+
+ warp_to_dlc(&dlc_restart, 0L);
+
+ /*
+ * Will everything above fit and still leave ours on screen?
+ */
+ if(dlc_restart.type == DlcTitle)
+ new_row = LINES_PER_ABOOK * new_abook_num +
+ (((as.adrbks[new_abook_num].type & GLOBAL) &&
+ (as.how_many_personals > 0 || as.config))
+ ? XTRA_LINES_BETWEEN : 0) +
+ ((as.how_many_personals == 0 && as.config)
+ ? LINES_PER_ADD_LINE : 0);
+ else if(dlc_restart.type == DlcGlobAdd)
+ new_row = LINES_PER_ABOOK * as.n_addrbk +
+ ((as.how_many_personals > 0 || as.config)
+ ? XTRA_LINES_BETWEEN : 0) +
+ ((as.how_many_personals == 0 && as.config)
+ ? LINES_PER_ADD_LINE : 0);
+ else
+ new_row = 0;
+
+ new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
+ as.cur_row = new_row;
+ as.top_ent = 0L - as.cur_row;
+ start_disp = 0;
+ ps->mangled_body = 1;
+ ps->mangled_footer = 1;
+ /* TRANSLATORS: This is just comforting confirmation that the
+ address book being deleted was successfully deleted */
+ q_status_message(SM_ORDER, 0, 3, _("Address book deleted"));
+ }
+ else{
+ if(err){
+ q_status_message(SM_ORDER, 0, 4, err);
+ dprint((5, "addrbook delete failed: %s\n",
+ err ? err : "?"));
+ }
+ }
+
+ }
+
+ break;
+
+
+ /*---- Reorder an addressbook list ---------*/
+ case MC_SHUFFLE:
+ if(entry_is_addkey(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4,
+ _("Highlight entry you wish to shuffle"));
+ break;
+ }
+
+ {int slide;
+ char *msg = NULL;
+ int ret, new_anum;
+
+ if((ret = ab_shuffle(pab, &slide, command_line, &msg)) > 0){
+ DL_CACHE_S dlc_restart;
+ int new_row;
+
+ new_anum = ret - 1; /* see ab_shuffle return value */
+ addrbook_reset();
+ (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
+
+ erase_checks();
+ erase_selections();
+
+ /* put cursor on new_anum */
+ dlc_restart.adrbk_num = new_anum;
+ dlc_restart.type = DlcTitle;
+ warp_to_dlc(&dlc_restart, 0L);
+ /*
+ * Will everything above fit and still leave ours on screen?
+ */
+ new_row = LINES_PER_ABOOK * new_anum +
+ (((as.adrbks[new_anum].type & GLOBAL) &&
+ (as.how_many_personals > 0 || as.config))
+ ? XTRA_LINES_BETWEEN : 0) +
+ ((as.how_many_personals == 0 && as.config)
+ ? LINES_PER_ADD_LINE : 0);
+ new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
+ as.cur_row = new_row;
+ as.top_ent = 0L - as.cur_row;
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+ else if(ret == 0){
+ DL_CACHE_S *dlc_to_flush;
+
+ if(slide < 0){ /* moved it up */
+ as.cur_row += slide;
+ start_disp = MAX(as.cur_row - 1, 0);
+ dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
+ /*
+ * If we backed off the top of the screen, just
+ * inch the display up so we can see it.
+ */
+ if(as.cur_row < 0){
+ as.top_ent += as.cur_row; /* cur_row is negative */
+ as.cur_row = 0;
+ start_disp = 0;
+ }
+ }
+ else{ /* moved it down */
+ start_disp = as.cur_row;
+ dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
+ as.cur_row += slide;
+ if(as.cur_row > as.l_p_page - VIS_LINES_PER_ABOOK){
+ as.top_ent += (as.cur_row -
+ (as.l_p_page - VIS_LINES_PER_ABOOK));
+ as.cur_row = MAX(as.l_p_page-VIS_LINES_PER_ABOOK, 0);
+ start_disp = 0;
+ }
+ }
+
+ flush_dlc_from_cache(dlc_to_flush);
+ ps->mangled_body = 1;
+ }
+
+ q_status_message(SM_ORDER, 0, 3,
+ msg ? msg :
+ (ret < 0) ? _("Shuffle failed") :
+ _("Address books shuffled"));
+ if(ret < 0)
+ dprint((5, "addrbook shuffle failed: %s\n",
+ msg ? msg : "?"));
+
+ if(msg)
+ fs_give((void **)&msg);
+
+ }
+
+ break;
+
+
+ /*----------------------- Move Up ---------------------*/
+ case MC_CHARUP:
+ case MC_PREVITEM:
+ r = prev_selectable_line(as.cur_row+as.top_ent, &new_line);
+ if(r == 0){
+ /* find first line so everything is displayed */
+ new_top_ent = as.cur_row+as.top_ent;
+ for(dl=dlist(new_top_ent-1);
+ dl->type != Beginning;
+ dl = dlist((--new_top_ent) - 1))
+ ;
+
+ if(new_top_ent == as.top_ent ||
+ (as.cur_row + (as.top_ent-new_top_ent) > as.l_p_page - 1)){
+ q_status_message(SM_INFO, 0, 1, _("Already on first line."));
+ }
+ else{
+ as.cur_row += (as.top_ent - new_top_ent);
+ as.top_ent = new_top_ent;
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+
+ break;
+ }
+
+ i = ((cmd == MC_CHARUP)
+ && dlist(as.top_ent - 1L)->type != Beginning)
+ ? MIN(HS_MARGIN(ps), as.cur_row) : 0;
+ as.cur_row = new_line - as.top_ent;
+ if(as.cur_row - i < 0){
+ if(cmd == MC_CHARUP){
+ /*-- Past top of page --*/
+ as.top_ent += (as.cur_row - i);
+ as.cur_row = i;
+ }
+ else{
+ new_top_ent = first_line(as.top_ent - as.l_p_page);
+ as.cur_row += (as.top_ent - new_top_ent);
+ as.top_ent = new_top_ent;
+ /* if it is still off screen */
+ if(as.cur_row - i < 0){
+ as.top_ent += (as.cur_row - i);
+ as.cur_row = i;
+ }
+ }
+
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+ else
+ current_changed_flag++;
+
+ break;
+
+
+ /*------------------- Move Down -------------------*/
+ case MC_CHARDOWN:
+ case MC_NEXTITEM:
+ r = next_selectable_line(as.cur_row+as.top_ent, &new_line);
+ if(r == 0){
+ long new_end_line;
+
+ /* find last line so everything is displayed */
+ new_end_line = as.cur_row+as.top_ent;
+ for(dl=dlist(new_end_line+1);
+ dl->type != End;
+ dl = dlist((++new_end_line)+1))
+ ;
+
+ if(new_end_line - as.top_ent <= as.l_p_page - 1 ||
+ as.cur_row - (new_end_line-as.top_ent-(as.l_p_page-1)) < 0){
+ q_status_message(SM_INFO, 0, 1, _("Already on last line."));
+ }
+ else{
+ as.cur_row -= (new_end_line-as.top_ent-(as.l_p_page-1));
+ as.top_ent += (new_end_line-as.top_ent-(as.l_p_page-1));
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+
+ break;
+ }
+
+ /* adjust for scrolling margin */
+ i = (cmd == MC_CHARDOWN) ? HS_MARGIN(ps) : 0;
+ as.cur_row = new_line - as.top_ent;
+ if(as.cur_row >= as.l_p_page - i){
+ if(cmd == MC_CHARDOWN){
+ /*-- Past bottom of page --*/
+ as.top_ent += (as.cur_row - (as.l_p_page - i) + 1);
+ as.cur_row = (as.l_p_page - i) - 1;
+ }
+ else{
+ /*-- Changed pages --*/
+ as.top_ent += as.l_p_page;
+ as.cur_row -= as.l_p_page;
+ /* if it is still off screen */
+ if(as.cur_row >= as.l_p_page - i){
+ as.top_ent += (as.cur_row - (as.l_p_page - i) + 1);
+ as.cur_row = (as.l_p_page - i) - 1;
+ }
+ }
+
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+ else
+ current_changed_flag++;
+
+ break;
+
+
+#ifdef MOUSE
+ case MC_MOUSE:
+ {
+ MOUSEPRESS mp;
+
+ /*
+ * Get the mouse down. Convert to content row number.
+ * If the row is selectable, do the single or double click
+ * operation.
+ */
+ mouse_get_last(NULL, &mp);
+ mp.row -= HEADER_ROWS(ps);
+ if(line_is_selectable(as.top_ent + mp.row)){
+ if(mp.button == M_BUTTON_LEFT){
+ if(mp.doubleclick){
+ /*
+ * A mouse double click does the default action to
+ * the selected line. Since we need to do a goto
+ * to get there, we have this ugly bit of code.
+ *
+ * On the first mouse click we go around the loop
+ * once and set def_key appropriately for the
+ * line as.top_ent+mp.row, which will then be
+ * the same as as.top_ent+as.cur_row.
+ */
+ switch(km->keys[def_key].bind.cmd){
+ case MC_CHOICE:
+ if (as.checkboxes) goto togglex;
+ else goto select;
+ case MC_OPENABOOK:
+ goto openabook;
+ case MC_EXPAND:
+ goto expand;
+ case MC_TOGGLE:
+ goto togglex;
+ case MC_SELALL:
+ goto selall;
+ case MC_VIEW_ENTRY:
+ goto view;
+ case MC_ADDABOOK:
+ goto add_abook;
+ case MC_EDITABOOK:
+ goto change_abook;
+#ifdef ENABLE_LDAP
+ case MC_QUERY_SERV:
+ goto q_server;
+#endif
+ default:
+ q_status_message(SM_INFO, 0, 1,
+ "Can't happen in MC_MOUSE");
+ break;
+ }
+ }
+
+ as.cur_row = mp.row;
+ current_changed_flag++;
+ }
+ else if(mp.button == M_BUTTON_RIGHT){
+#ifdef _WINDOWS
+ int need_redraw;
+#endif
+ as.cur_row = mp.row;
+ current_changed_flag++;
+
+
+#ifdef _WINDOWS
+ need_redraw = calculate_field_widths();
+
+ /*---------- Update the current entry, (move or change) -------*/
+ display_book(need_redraw ? 0 : as.cur_row,
+ as.cur_row,
+ as.old_cur_row,
+ need_redraw,
+ &cursor_pos);
+
+ as.old_cur_row = as.cur_row;
+ current_changed_flag = 0;
+ as.cur = cur_addr_book();
+ pab = (as.n_addrbk &&
+ !entry_is_askserver(as.top_ent+as.cur_row))
+ ? &as.adrbks[as.cur] : NULL;
+
+ if(!mp.doubleclick){
+ MPopup addr_pu[20];
+ int n, i;
+
+ /* Make what they clicked "current" */
+ memset(addr_pu, 0, 20 * sizeof(MPopup));
+
+ addr_pu[n = 0].type = tQueue;
+ addr_pu[n].data.val = km->keys[def_key].bind.ch[0];
+ addr_pu[n].label.style = lNormal;
+ addr_pu[n++].label.string = km->keys[def_key].label;
+
+ if((i = menu_clear_binding(km, '<')) != MC_UNKNOWN){
+ menu_add_binding(km, '<', i);
+
+ if((i = menu_binding_index(km, i)) >= 0){
+ addr_pu[n++].type = tSeparator;
+
+ addr_pu[n].type = tQueue;
+ addr_pu[n].data.val = km->keys[i].bind.ch[0];
+ addr_pu[n].label.style = lNormal;
+ addr_pu[n++].label.string = km->keys[i].label;
+ }
+ }
+
+ addr_pu[n].type = tTail;
+
+ mswin_popup(addr_pu);
+ }
+#endif
+ }
+ }
+ }
+ break;
+#endif
+
+
+ /*------------- Page Up or Down --------*/
+ case MC_PAGEUP:
+ case MC_PAGEDN:
+ if(cmd == MC_PAGEUP){
+ /* find first line on prev page */
+ new_top_ent = first_line(as.top_ent - as.l_p_page);
+ if(new_top_ent == NO_LINE)
+ break;
+
+ /* find first selectable line */
+ fl = first_selectable_line(new_top_ent);
+
+ /* If we didn't move, we'd better move now */
+ if(fl == as.top_ent+as.cur_row){
+ if(!prev_selectable_line(as.cur_row+as.top_ent, &new_line)){
+ long lineno;
+
+ /* find first line so everything is displayed */
+ lineno = as.cur_row+as.top_ent;
+ for(dl=dlist(lineno);
+ dl->type != Beginning;
+ dl = dlist(--lineno))
+ ;
+
+ /*
+ * If this new_top_ent is the same as the old_top_ent
+ * we'll get the warning message.
+ */
+ new_top_ent = first_line(lineno);
+ if(fl - new_top_ent >= as.l_p_page)
+ new_top_ent += (fl - new_top_ent - as.l_p_page + 1);
+ }
+ else
+ fl = new_line;
+ }
+
+ if(fl == NO_LINE)
+ break;
+
+ if(as.top_ent == new_top_ent && as.cur_row == (fl-as.top_ent)){
+ q_status_message(SM_INFO, 0, 1, _("Already on first page."));
+ break;
+ }
+
+ if(as.top_ent == new_top_ent)
+ current_changed_flag++;
+ else
+ as.top_ent = new_top_ent;
+ }
+ else{ /* Down */
+ /* find first selectable line on next page */
+ fl = first_selectable_line(as.top_ent + as.l_p_page);
+ if(fl == NO_LINE)
+ break;
+
+ /* if there is another page, scroll */
+ if(fl - as.top_ent >= as.l_p_page){
+ new_top_ent = as.top_ent + as.l_p_page;
+ }
+ /* on last page already */
+ else{
+ new_top_ent = as.top_ent;
+ if(as.cur_row == (fl - as.top_ent)){ /* no change */
+ long new_end_line;
+
+ /* find last line so everything is displayed */
+ new_end_line = as.cur_row+as.top_ent;
+ for(dl=dlist(new_end_line+1);
+ dl->type != End;
+ dl = dlist((++new_end_line)+1))
+ ;
+
+ if(new_end_line - as.top_ent <= as.l_p_page - 1 ||
+ as.cur_row -
+ (new_end_line-as.top_ent-(as.l_p_page-1)) < 0){
+ q_status_message(SM_INFO, 0, 1,
+ _("Already on last page."));
+ }
+ else{
+ as.cur_row -=
+ (new_end_line-as.top_ent-(as.l_p_page-1));
+ as.top_ent +=
+ (new_end_line-as.top_ent-(as.l_p_page-1));
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+
+ break;
+ }
+ }
+
+ if(as.top_ent == new_top_ent)
+ current_changed_flag++;
+ else
+ as.top_ent = new_top_ent;
+ }
+
+ /*
+ * Stuff in common for up or down.
+ */
+ as.cur_row = fl - as.top_ent;
+
+ /* if it is still off screen */
+ if(as.cur_row < 0){
+ as.top_ent += as.cur_row;
+ as.cur_row = 0;
+ }
+ else if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row - as.l_p_page + 1);
+ as.cur_row = as.l_p_page - 1;
+ }
+
+ if(!current_changed_flag){
+ ps->mangled_body = 1;
+ start_disp = 0;
+ }
+
+ break;
+
+
+ /*------------- Delete item from addrbook ---------*/
+ case MC_DELETE:
+ if(adrbk_check_all_validity_now()){
+ if(resync_screen(pab, style, checkedn)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Address book changed. Delete cancelled. Try again."));
+ ps->mangled_screen = 1;
+ break;
+ }
+ }
+
+ if(!any_addrs_avail(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4, _("No entries to delete"));
+ break;
+ }
+
+ if(entry_is_clickable(as.top_ent+as.cur_row)){
+ clickable_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(rdonly){
+ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
+ break;
+ }
+
+ if(empty){
+ empty_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ warped = 0;
+ did_delete = single_entry_delete(pab->address_book,
+ as.cur_row+as.top_ent,
+ &warped);
+ ps->mangled_footer = 1;
+ if(did_delete){
+ if(warped){
+ as.top_ent = first_line(0L - as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ start_disp = 0;
+ }
+ else{
+ /*
+ * In case the line we're now at is not a selectable
+ * field.
+ */
+ new_line = first_selectable_line(as.cur_row+as.top_ent);
+ if(new_line != NO_LINE
+ && new_line != as.cur_row+as.top_ent){
+ as.cur_row = new_line - as.top_ent;
+ if(as.cur_row < 0){
+ as.top_ent -= as.l_p_page;
+ as.cur_row += as.l_p_page;
+ }
+ else if(as.cur_row >= as.l_p_page){
+ as.top_ent += as.l_p_page;
+ as.cur_row -= as.l_p_page;
+ }
+ }
+
+ start_disp = MIN(as.cur_row, as.old_cur_row);
+ }
+
+ ps->mangled_body = 1;
+ }
+
+ break;
+
+
+ /*------------- Toggle checkbox ---------*/
+ case MC_TOGGLE:
+togglex:
+ if(!any_addrs_avail(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
+ break;
+ }
+
+ if(entry_is_clickable(as.top_ent+as.cur_row)){
+ clickable_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(empty){
+ empty_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(is_addr(as.top_ent+as.cur_row)){
+ dl = dlist(as.top_ent+as.cur_row);
+
+ if(style == SelectAddrLccCom && dl->type == ListEnt)
+ q_status_message(SM_ORDER, 0, 4,
+ _("You may only select whole lists for Lcc"));
+ else if(style == SelectAddrLccCom && dl->type != ListHead)
+ q_status_message(SM_ORDER, 0, 4,
+ _("You may only select lists for Lcc, use Bcc for personal entries"));
+ else if(dl->type == ListHead || dl->type == Simple){
+ current_changed_flag++;
+ if(entry_is_checked(pab->address_book->checks,
+ (a_c_arg_t)dl->elnum)){
+ entry_unset_checked(pab->address_book->checks,
+ (a_c_arg_t)dl->elnum);
+ checkedn--;
+ if(checkedn == 0)
+ setall_changed++;
+ }
+ else{
+ entry_set_checked(pab->address_book->checks,
+ (a_c_arg_t)dl->elnum);
+ if(checkedn == 0)
+ setall_changed++;
+
+ checkedn++;
+ }
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4,
+ _("You may not select list members, only whole lists or personal entries"));
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4,
+ _("You may only select addresses or lists"));
+
+ break;
+
+
+ /*------ Turn all checkboxes on ---------*/
+ case MC_SELALL:
+selall:
+ if(!any_addrs_avail(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
+ break;
+ }
+
+ if(entry_is_clickable(as.top_ent+as.cur_row)){
+ clickable_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(empty){
+ empty_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ {
+ adrbk_cntr_t num, ab_count;
+
+ ab_count = adrbk_count(pab->address_book);
+ setall_changed++;
+ if(checkedn){ /* unset All */
+ for(num = 0; num < ab_count; num++){
+ if(entry_is_checked(pab->address_book->checks,
+ (a_c_arg_t)num)){
+ entry_unset_checked(pab->address_book->checks,
+ (a_c_arg_t)num);
+ checkedn--;
+ }
+ }
+ }
+ else{ /* set All */
+ for(num = 0; num < ab_count; num++){
+ if(!entry_is_checked(pab->address_book->checks,
+ (a_c_arg_t)num)){
+ entry_set_checked(pab->address_book->checks,
+ (a_c_arg_t)num);
+ checkedn++;
+ }
+ }
+ }
+
+ ps->mangled_body = 1;
+ start_disp = 0;
+ }
+
+ break;
+
+
+ /*---------- Turn on ListMode -----------*/
+ case MC_LISTMODE:
+ as.checkboxes = 1;
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ init_disp_form(pab, ps->VAR_ABOOK_FORMATS, i);
+ }
+
+ (void)calculate_field_widths();
+ ps->mangled_footer = 1;
+ ps->mangled_body = 1;
+ start_disp = 0;
+ q_status_message(SM_ORDER, 0, 4,
+ _("Use \"X\" to select addresses or lists"));
+ break;
+
+
+ /*--------- Compose -----------*/
+ case MC_COMPOSE:
+ (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 0);
+ break;
+
+
+ /*--------- Alt Compose -----------*/
+ case MC_ROLE:
+ (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 1);
+ break;
+
+
+#ifdef ENABLE_LDAP
+ /*------ Query Directory ------*/
+ case MC_QUERY_SERV:
+q_server:
+ {char *err_mess = NULL, **err_mess_p;
+ int query_type;
+
+ if(!directory_ok){
+ q_status_message(SM_ORDER, 0, 4,
+ (style == SelectAddrLccCom)
+ ? "Can't search server for Lcc"
+ : "Can't search server from here");
+ break;
+ }
+ else if(as.checkboxes){
+ q_status_message(SM_ORDER, 0, 4,
+ "Can't search server when using ListMode");
+ break;
+ }
+
+ if(error_message)
+ err_mess_p = error_message;
+ else
+ err_mess_p = &err_mess;
+
+ /*
+ * The query lines are indexed by their adrbk_num. (Just using
+ * that because it was handy and not used for addrbooks.)
+ */
+ query_type = adrbk_num_from_lineno(as.top_ent+as.cur_row);
+
+ if((addr = query_server(ps, are_selecting, &quit, query_type,
+ err_mess_p)) != NULL){
+ if(are_selecting)
+ return(addr);
+
+ fs_give((void **)&addr);
+ }
+
+ if(err_mess_p && *err_mess_p && !error_message){
+ q_status_message(SM_ORDER, 0, 4, *err_mess_p);
+ fs_give((void **)err_mess_p);
+ }
+ }
+
+ break;
+#endif /* ENABLE_LDAP */
+
+
+ /*----------- Where is (search) ----------------*/
+ case MC_WHEREIS:
+ warped = 0;
+ new_top_ent = ab_whereis(&warped, command_line);
+
+ if(new_top_ent != NO_LINE){
+ if(warped || new_top_ent != as.top_ent){
+ as.top_ent = new_top_ent;
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+ else
+ current_changed_flag++;
+ }
+
+ ps->mangled_footer = 1;
+ break;
+
+
+ /*----- Select entries to work on --*/
+ case MC_SELECT:
+ if(!any_addrs_avail(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
+ break;
+ }
+
+ if(!cur_is_open()){
+ if(entry_is_askserver(as.top_ent+as.cur_row))
+ q_status_message(SM_ORDER, 0, 4,
+ _("Select is only available from within an expanded address book"));
+ else
+ clickable_warning(as.top_ent+as.cur_row);
+
+ break;
+ }
+
+ dl = dlist(as.top_ent+as.cur_row);
+ if(dl->type == Empty){
+ empty_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ {int were_selections = as.selections;
+
+ ab_select(ps, pab ? pab->address_book : NULL,
+ as.top_ent+as.cur_row, command_line, &start_disp);
+
+ if((!were_selections && as.selections)
+ || (were_selections && !as.selections)){
+ ps->mangled_footer = 1;
+ for(i = 0; i < as.n_addrbk; i++)
+ init_disp_form(&as.adrbks[i],
+ ps->VAR_ABOOK_FORMATS, i);
+ }
+ }
+
+ break;
+
+
+ /*----------- Select current entry ----------*/
+ case MC_SELCUR:
+ if(!any_addrs_avail(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
+ break;
+ }
+
+ if(entry_is_clickable(as.top_ent+as.cur_row)){
+ clickable_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(empty){
+ empty_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(is_addr(as.top_ent+as.cur_row)){
+ dl = dlist(as.top_ent+as.cur_row);
+
+ if(dl->type == ListHead || dl->type == Simple){
+ int do_init_disp = 0;
+ long ll;
+
+ current_changed_flag++;
+
+ if(entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)dl->elnum)){
+ DL_CACHE_S *dlc, dlc_restart;
+
+ as.selections--;
+ if(as.selections == 0)
+ do_init_disp++;
+
+ entry_unset_selected(pab->address_book->selects,
+ (a_c_arg_t)dl->elnum);
+
+ if(as.zoomed){
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ if(as.selections){
+ flush_dlc_from_cache(dlc);
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ ps->mangled_body = 1;
+ if(dlc->type == DlcEnd){
+ r = prev_selectable_line(as.cur_row +
+ as.top_ent,
+ &new_line);
+ if(r){
+ as.cur_row = new_line - as.top_ent;
+ if(as.cur_row < 0){
+ as.top_ent += as.cur_row;
+ as.cur_row = 0;
+ start_disp = 0;
+ }
+ else
+ start_disp = as.cur_row;
+ }
+ }
+ else
+ start_disp = MAX(as.cur_row-1,0);
+ }
+ else{
+ dlc_restart = *dlc;
+ as.zoomed = 0;
+ q_status_message(SM_ORDER, 0, 2,
+ _("Zoom Mode is now off, no entries selected"));
+
+ warp_to_dlc(&dlc_restart, 0L);
+ /* put current entry in middle of screen */
+ as.top_ent =
+ first_line(0L - (long)as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ ps->mangled_body = 1;
+ start_disp = 0;
+ }
+ }
+ else if(F_OFF(F_UNSELECT_WONT_ADVANCE,ps_global)){
+
+ r = next_selectable_line(as.cur_row+as.top_ent,
+ &new_line);
+ if(r){
+
+ for(ll = new_line;
+ (dl=dlist(ll))->type != End;
+ ll++)
+ if(dl->type == ListHead || dl->type == Simple)
+ break;
+
+ if(dl->type != End)
+ new_line = ll;
+
+ as.cur_row = new_line - as.top_ent;
+ if(as.cur_row >= as.l_p_page){
+ /*-- Changed pages --*/
+ as.top_ent += as.l_p_page;
+ as.cur_row -= as.l_p_page;
+ /* if it is still off screen */
+ if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row-as.l_p_page+1);
+ as.cur_row = (as.l_p_page - 1);
+ }
+
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+ }
+ }
+ }
+ else{
+ if(as.selections == 0)
+ do_init_disp++;
+
+ as.selections++;
+
+ entry_set_selected(pab->address_book->selects,
+ (a_c_arg_t)dl->elnum);
+ r = next_selectable_line(as.cur_row+as.top_ent,
+ &new_line);
+ if(r){
+
+ for(ll = new_line;
+ (dl=dlist(ll))->type != End;
+ ll++)
+ if(dl->type == ListHead || dl->type == Simple)
+ break;
+
+ if(dl->type != End)
+ new_line = ll;
+
+ as.cur_row = new_line - as.top_ent;
+ if(as.cur_row >= as.l_p_page){
+ /*-- Changed pages --*/
+ as.top_ent += as.l_p_page;
+ as.cur_row -= as.l_p_page;
+ /* if it is still off screen */
+ if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row-as.l_p_page+1);
+ as.cur_row = (as.l_p_page - 1);
+ }
+
+ start_disp = 0;
+ ps->mangled_body = 1;
+ }
+ }
+ }
+
+ /*
+ * If we switch from selected to non-selected or
+ * vice versa, we have to init_disp_form() for all
+ * the addrbooks, in case we're using the X instead
+ * of bold.
+ */
+ if(do_init_disp){
+ ps->mangled_footer = 1;
+ for(i = 0; i < as.n_addrbk; i++)
+ init_disp_form(&as.adrbks[i],
+ ps->VAR_ABOOK_FORMATS, i);
+ }
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4,
+ _("You may not select list members, only whole lists or personal entries"));
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4,
+ _("You may only select addresses or lists"));
+
+ break;
+
+
+ /*--- Zoom in and look only at selected entries (or zoom out) --*/
+ case MC_ZOOM:
+ as.zoomed = (1 - as.zoomed);
+ if(as.zoomed)
+ ab_zoom((pab && pab->address_book) ? pab->address_book->selects
+ : NULL,
+ &start_disp);
+ else{
+ q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now off"));
+ ab_unzoom(&start_disp);
+ }
+
+ break;
+
+
+ /*--- Apply a command -----------*/
+ case MC_APPLY:
+ if(as.selections){
+ if(((ab_apply_cmd(ps, pab ? pab->address_book : NULL,
+ as.top_ent+as.cur_row, command_line) &&
+ F_ON(F_AUTO_UNZOOM, ps)) || !as.selections) && as.zoomed){
+
+ ab_unzoom(NULL);
+ ps_global->mangled_body = 1;
+ }
+
+ /*
+ * In case the line we're now at is not a selectable
+ * field.
+ *
+ * We set start_disp to zero here but rely on the called
+ * routine to set mangled_body if appropriate.
+ */
+ start_disp = 0;
+ new_line = first_selectable_line(as.cur_row+as.top_ent);
+ if(new_line != NO_LINE
+ && new_line != as.cur_row+as.top_ent){
+ as.cur_row = new_line - as.top_ent;
+ if(as.cur_row < 0){
+ as.top_ent += as.cur_row;
+ as.cur_row = 0;
+ }
+ else if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row - as.l_p_page + 1);
+ as.cur_row = as.l_p_page - 1;
+ }
+ }
+ }
+ else
+ q_status_message(SM_ORDER, 0, 2,
+ _("No selected entries to apply command to"));
+
+ break;
+
+
+ /*--------- QUIT pine -----------*/
+ case MC_QUIT:
+ dprint((7, "Quitting pine from addrbook\n"));
+ ps->next_screen = quit_screen;
+ break;
+
+
+ /*--------- Top of Folder list -----------*/
+ case MC_COLLECTIONS:
+ dprint((7, "Goto folder lister from addrbook\n"));
+ ps->next_screen = folder_screen;
+ break;
+
+
+ /*---------- Open specific new folder ----------*/
+ case MC_GOTO:
+ dprint((7, "Goto from addrbook\n"));
+ ab_goto_folder(command_line);
+ break;
+
+
+ /*--------- Index -----------*/
+ case MC_INDEX:
+ dprint((7, "Goto message index from addrbook\n"));
+ if(THREADING()
+ && sp_viewing_a_thread(ps->mail_stream)
+ && unview_thread(ps, ps->mail_stream, ps->msgmap)){
+ ps->next_screen = mail_index_screen;
+ ps->view_skipped_index = 0;
+ ps->mangled_screen = 1;
+ }
+
+ ps->next_screen = mail_index_screen;
+ break;
+
+
+ /*----------------- Print --------------------*/
+ case MC_PRINTTXT:
+ (void)ab_print(0);
+ ps->mangled_screen = 1;
+ break;
+
+
+ /*------ Copy entries into an abook ----*/
+ case MC_SAVE:
+ if(!any_addrs_avail(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4, _("No entries to save"));
+ break;
+ }
+
+ if(entry_is_clickable(as.top_ent+as.cur_row)){
+ clickable_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(empty){
+ empty_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ (void)ab_save(ps, pab ? pab->address_book : NULL,
+ as.top_ent+as.cur_row, command_line, 0);
+ break;
+
+
+ /*------ Forward an entry in mail -----------*/
+ case MC_FORWARD:
+ if(!any_addrs_avail(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4, _("No entries to forward"));
+ break;
+ }
+
+ if(entry_is_clickable(as.top_ent+as.cur_row)){
+ clickable_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(empty){
+ empty_warning(as.top_ent+as.cur_row);
+ break;
+ }
+
+ if(!is_addr(as.top_ent+as.cur_row)){
+ q_status_message(SM_ORDER, 0, 4, _("Nothing to forward"));
+ break;
+ }
+
+ dl = dlist(as.top_ent+as.cur_row);
+ if(dl->type != ListHead && dl->type != Simple){
+ q_status_message(SM_ORDER, 0, 4,
+ _("Can only forward whole entries"));
+ break;
+ }
+
+ (void)ab_forward(ps, as.top_ent+as.cur_row, 0);
+ ps->mangled_footer = 1;
+ break;
+
+
+ case MC_REPAINT:
+ /* ^L attempts to resynchronize with changed addrbooks */
+ if(adrbk_check_all_validity_now())
+ (void)resync_screen(pab, style, checkedn);
+
+ /* fall through */
+
+ case MC_RESIZE:
+ mark_status_dirty();
+ mark_titlebar_dirty();
+ mark_keymenu_dirty();
+ ClearBody();
+ ps->mangled_screen = 1;
+ if(c == KEY_RESIZE)
+ ab_resize();
+
+ break;
+
+
+ case MC_UTF8:
+ bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps) ? "F1" : "?");
+ break;
+
+
+ /*------ Some back compatibility messages -----*/
+ case MC_UNKNOWN:
+ if(c == 'e' && !are_selecting){
+ q_status_message(SM_ORDER | SM_DING, 0, 2,
+ _("Command \"E\" not defined. Use \"View/Update\" to edit an entry"));
+ break;
+ }
+ else if(c == 's'
+ && !(are_selecting || entry_is_clickable(as.top_ent+as.cur_row))){
+ q_status_message(SM_ORDER | SM_DING, 0, 2,
+ _("Command \"S\" not defined. Use \"AddNew\" to create a list"));
+ break;
+ }
+ else if(c == 'z' && !are_selecting){
+ q_status_message(SM_ORDER | SM_DING, 0, 2,
+ _("Command \"Z\" not defined. Use \"View/Update\" to add to a list"));
+ break;
+ }
+ /* else, fall through */
+
+ default:
+ bogus_command(c, F_ON(F_USE_FK, ps) ? "F1" : "?");
+ break;
+ }
+
+ if(ps->next_screen != SCREEN_FUN_NULL)
+ quit++;
+ }
+
+ erase_selections();
+ return NULL;
+}
+
+
+/*
+ * Turn on zoom mode and zoom in if applicable.
+ *
+ * Args selecteds -- tells which entries are selected in current abook
+ * start_disp -- Passed in so we can set it back in the caller
+ */
+void
+ab_zoom(EXPANDED_S *selecteds, int *start_disp)
+{
+ AddrScrn_Disp *dl;
+ DL_CACHE_S *dlc, dlc_restart;
+
+ as.zoomed = 1;
+
+ if(as.selections){
+ q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now on"));
+ if(cur_is_open()){
+ dl = dlist(as.top_ent+as.cur_row);
+ if((dl->type == ListHead ||
+ dl->type == Simple ||
+ dl->type == ListEmpty ||
+ dl->type == ListClickHere ||
+ dl->type == ListEnt) &&
+ entry_is_selected(selecteds, (a_c_arg_t)dl->elnum)){
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ dlc_restart = *dlc;
+ warp_to_dlc(&dlc_restart, 0L);
+ /* put current entry in middle of screen */
+ as.top_ent = first_line(0L - (long)as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+ else{
+ long new_ent;
+
+ warp_to_top_of_abook(as.cur);
+ as.top_ent = 0L;
+ new_ent = first_selectable_line(0L);
+ if(new_ent == NO_LINE)
+ as.cur_row = 0L;
+ else
+ as.cur_row = new_ent;
+
+ /* if it is off screen */
+ if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row - as.l_p_page + 1);
+ as.cur_row = (as.l_p_page - 1);
+ }
+ }
+ }
+ else{
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ dlc_restart = *dlc;
+ warp_to_dlc(&dlc_restart, 0L);
+ /* put current entry in middle of screen */
+ as.top_ent = first_line(0L - (long)as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+
+ ps_global->mangled_body = 1;
+ *start_disp = 0;
+ }
+ else{
+ as.zoomed = 0;
+ q_status_message(SM_ORDER, 0, 2, _("No selected entries to zoom on"));
+ }
+}
+
+
+/*
+ * Turn off zoom mode and zoom out if applicable.
+ *
+ * Args start_disp -- Passed in so we can set it back in the caller
+ */
+void
+ab_unzoom(int *start_disp)
+{
+ DL_CACHE_S *dlc, dlc_restart;
+
+ as.zoomed = 0;
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ if(dlc->type == DlcZoomEmpty){
+ long new_ent;
+
+ warp_to_beginning();
+ as.top_ent = 0L;
+ new_ent = first_selectable_line(0L);
+ if(new_ent == NO_LINE)
+ as.cur_row = 0L;
+ else
+ as.cur_row = new_ent;
+
+ /* if it is off screen */
+ if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row - as.l_p_page + 1);
+ as.cur_row = (as.l_p_page - 1);
+ }
+ }
+ else{
+ dlc_restart = *dlc;
+ warp_to_dlc(&dlc_restart, 0L);
+ /* put current entry in middle of screen */
+ as.top_ent = first_line(0L - (long)as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+
+ ps_global->mangled_body = 1;
+ if(start_disp)
+ *start_disp = 0;
+}
+
+
+/*
+ * Post an empty addrbook warning.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ */
+void
+empty_warning(long int cur_line)
+{
+ register AddrScrn_Disp *dl;
+
+ dl = dlist(cur_line);
+ if(dl->type == NoAbooks)
+ q_status_message(SM_ORDER, 0, 4,
+ _("No address books configured, use Setup"));
+ else if(dl->type == Empty)
+ q_status_message(SM_ORDER, 0, 4, _("Address Book is Empty"));
+ else
+ q_status_message(SM_ORDER, 0, 4, _("Distribution List is Empty"));
+}
+
+
+/*
+ * Tell user to click on this to expand.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ */
+void
+clickable_warning(long int cur_line)
+{
+ register AddrScrn_Disp *dl;
+
+ dl = dlist(cur_line);
+ if(dl->type == Title || dl->type == ClickHereCmb)
+ q_status_message(SM_ORDER, 0, 4, _("Address Book not expanded, use \">\" to expand"));
+ else
+ q_status_message(SM_ORDER, 0, 4, _("Distribution List not expanded, use \">\" to expand"));
+}
+
+
+/*
+ * Post a no tabs warning.
+ */
+void
+no_tabs_warning(void)
+{
+ q_status_message(SM_ORDER, 0, 4, "Tabs not allowed in address book");
+}
+
+
+/*
+ * Prompt for command to apply to selected entries
+ *
+ * Returns: 1 if the entries are successfully commanded.
+ * 0 otherwise
+ */
+int
+ab_apply_cmd(struct pine *ps, AdrBk *abook, long int cur_line, int command_line)
+{
+ int ret = 0;
+ static ESCKEY_S opts[] = {
+ {'c', 'c', "C", "ComposeTo"},
+ {'d', 'd', "D", "Delete"},
+ {'%', '%', "%", "Print"},
+ {'f', 'f', "F", "Forward"},
+ {'s', 's', "S", "Save"},
+ {'#', '#', "#", "Role"},
+ { 0, '%', "", ""},
+ {-1, 0, NULL, NULL}};
+#define PHANTOM_PRINT 6
+
+ dprint((7, "- ab_apply_cmd -\n"));
+
+
+ opts[PHANTOM_PRINT].ch = (F_ON(F_ENABLE_PRYNT, ps_global)) ? 'y' : -1;
+
+ switch(radio_buttons("APPLY command : ", command_line, opts, 'z', 'x',
+ NO_HELP, RB_NORM)){
+ case 'c':
+ ret = ab_compose_to_addr(cur_line, 1, 0);
+ break;
+
+ case '#':
+ ret = ab_compose_to_addr(cur_line, 1, 1);
+ break;
+
+ case 'd':
+ ret = ab_agg_delete(ps, 1);
+ break;
+
+ case '%':
+ ret = ab_print(1);
+ break;
+
+ case 'f':
+ ret = ab_forward(ps, cur_line, 1);
+ break;
+
+ case 's':
+ ret = ab_save(ps, abook, cur_line, command_line, 1);
+ break;
+
+ case 'x':
+ cmd_cancelled("Apply command");
+ break;
+
+ case 'z':
+ q_status_message(SM_INFO, 0, 2,
+ "Cancelled, there is no default command");
+ break;
+ }
+
+ ps_global->mangled_footer = 1;
+
+ return(ret);
+}
+
+
+/*
+ * Allow user to mark some entries "selected".
+ */
+void
+ab_select(struct pine *ps, AdrBk *abook, long int cur_line, int command_line, int *start_disp)
+{
+ static ESCKEY_S sel_opts1[] = {
+ {'a', 'a', "A", "unselect All"},
+ { 0 , 'c', "C", NULL},
+ {'b', 'b', "B", "Broaden selctn"},
+ {'n', 'n', "N", "Narrow selctn"},
+ {'f', 'f', "F", "Flip selected"},
+ {-1, 0, NULL, NULL}
+ };
+ static char *sel_pmt1 = "ALTER selection : ";
+ static ESCKEY_S sel_opts2[] = {
+ {'a', 'a', "A", "select All"},
+ {'c', 'c', "C", "select Cur"},
+ {'t', 't', "T", "Text"},
+ {'s', 's', "S", "Status"},
+ {-1, 0, NULL, NULL}
+ };
+ static char *sel_pmt2 = "SELECT criteria : ";
+ ESCKEY_S *sel_opts;
+ HelpType help = NO_HELP;
+ adrbk_cntr_t num, ab_count;
+ int q = 0, rv = 0, narrow = 0,
+ do_flush = 0, do_warp = 0, prevsel,
+ move_current = 0, do_beginning = 0;
+ long new_ent;
+ AddrScrn_Disp *dl;
+ DL_CACHE_S *dlc, dlc_restart;
+
+ dprint((5, "- ab_select -\n"));
+
+ if(cur_is_open()){ /* select applies only to this addrbook */
+
+ ps->mangled_footer = 1;
+ prevsel = as.selections;
+ dl = dlist(cur_line);
+ sel_opts = sel_opts2;
+
+ /*
+ * If already some selected, ask how to alter that selection.
+ */
+ if(as.selections){
+ sel_opts += 2; /* don't offer all or current below */
+ if(dl && (dl->type == ListHead || dl->type == Simple)){
+ sel_opts1[1].label = entry_is_selected(abook->selects,
+ (a_c_arg_t)dl->elnum)
+ ? "unselect Cur"
+ : "select Cur";
+ sel_opts1[1].ch = 'c';
+ }
+ else
+ sel_opts1[1].ch = -2; /* don't offer this choice */
+
+ switch(q = radio_buttons(sel_pmt1, command_line, sel_opts1,
+ 'a', 'x', help, RB_NORM)){
+ case 'n': /* narrow selection */
+ narrow++;
+ case 'b': /* broaden selection */
+ q = 0; /* but don't offer criteria prompt */
+ break;
+
+ case 'c': /* select or unselect current */
+ case 'a': /* select or unselect all */
+ case 'f': /* flip selections */
+ case 'x': /* cancel */
+ break;
+
+ default:
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Unsupported Select option");
+ return;
+ }
+ }
+
+ if(abook && dl &&
+ (dl->type == ListHead || dl->type == Simple)){
+ sel_opts1[1].label = entry_is_selected(abook->selects,
+ (a_c_arg_t)dl->elnum)
+ ? "unselect Cur"
+ : "select Cur";
+ sel_opts1[1].ch = 'c';
+ }
+ else
+ sel_opts1[1].ch = -2; /* don't offer this choice */
+
+ if(!q)
+ q = radio_buttons(sel_pmt2, command_line, sel_opts,
+ 'c', 'x', help, RB_NORM);
+
+ *start_disp = 0;
+ dlc = get_dlc(cur_line);
+ dlc_restart = *dlc;
+ ab_count = adrbk_count(abook);
+
+ switch(q){
+ case 'x': /* cancel */
+ cmd_cancelled("Select command");
+ break;
+
+ case 'c': /* select/unselect current */
+ ps->mangled_body = 1;
+ if(entry_is_selected(abook->selects, (a_c_arg_t)dl->elnum)){
+ entry_unset_selected(abook->selects, (a_c_arg_t)dl->elnum);
+ as.selections--;
+
+ if(as.selections == 0 && as.zoomed){
+ as.zoomed = 0;
+ q_status_message(SM_ORDER, 0, 2,
+ "Zoom Mode is now off, no entries selected");
+ do_warp++;
+ }
+ else if(as.zoomed){
+ move_current++;
+ do_flush++;
+ }
+ else
+ do_flush++;
+ }
+ else{
+ entry_set_selected(abook->selects, (a_c_arg_t)dl->elnum);
+ as.selections++;
+
+ if(as.selections == 1 && !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
+ as.zoomed = 1;
+ as.top_ent = dlc_restart.global_row;
+ as.cur_row = 0;
+ do_warp++;
+ }
+ else
+ do_flush++;
+ }
+
+ break;
+
+ case 'a':
+ ps->mangled_body = 1;
+ if(any_selected(abook->selects)){ /* unselect all */
+ for(num = 0; num < ab_count; num++){
+ if(entry_is_selected(abook->selects, (a_c_arg_t)num)){
+ as.selections--;
+ entry_unset_selected(abook->selects, (a_c_arg_t)num);
+ }
+ }
+
+ if(as.selections == 0 && as.zoomed){
+ as.zoomed = 0;
+ q_status_message(SM_ORDER, 0, 2,
+ "Zoom Mode is now off, all entries UNselected");
+ do_warp++;
+ }
+ else{
+ char bb[100];
+
+ snprintf(bb, sizeof(bb), "%s entries UNselected%s%s%s",
+ comatose(prevsel-as.selections),
+ as.selections ? ", still " : "",
+ as.selections ? comatose(as.selections) : "",
+ as.selections ? " selected in other addrbooks" : "");
+ bb[sizeof(bb)-1] = '\0';
+ q_status_message(SM_ORDER, 0, 2, bb);
+ if(as.zoomed)
+ do_beginning++;
+ else
+ do_flush++;
+ }
+ }
+ else{ /* select all */
+ for(num = 0; num < ab_count; num++){
+ if(!entry_is_selected(abook->selects, (a_c_arg_t)num)){
+ as.selections++;
+ entry_set_selected(abook->selects, (a_c_arg_t)num);
+ }
+ }
+
+ q_status_message1(SM_ORDER, 0, 2, "All %s entries selected",
+ comatose(ab_count));
+ if(prevsel == 0 && as.selections > 0 &&
+ !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
+ as.zoomed = 1;
+ as.top_ent = dlc_restart.global_row - as.cur_row;
+ do_warp++;
+ }
+ else if(dlc_restart.type == DlcZoomEmpty &&
+ as.selections > prevsel)
+ do_beginning++;
+ else if(as.zoomed)
+ do_warp++;
+ else
+ do_flush++;
+ }
+
+ break;
+
+ case 'f': /* flip selections in this abook */
+ ps->mangled_body = 1;
+ for(num = 0; num < ab_count; num++){
+ if(entry_is_selected(abook->selects, (a_c_arg_t)num)){
+ entry_unset_selected(abook->selects, (a_c_arg_t)num);
+ as.selections--;
+ }
+ else{
+ entry_set_selected(abook->selects, (a_c_arg_t)num);
+ as.selections++;
+ }
+ }
+
+ if(as.zoomed){
+ if(as.selections)
+ do_beginning++;
+ else{
+ as.zoomed = 0;
+ q_status_message(SM_ORDER, 0, 2, "Zoom Mode is now off");
+ do_warp++;
+ }
+ }
+ else
+ do_warp++;
+
+ q_status_message1(SM_ORDER, 0, 2, "%s entries now selected",
+ comatose(as.selections));
+
+ break;
+
+ case 't':
+ case 's':
+ switch(q){
+ case 't':
+ rv = ab_select_text(abook, narrow);
+ break;
+
+ case 's':
+ rv = ab_select_type(abook, narrow);
+ break;
+ }
+
+ if(!rv){
+ ps->mangled_body = 1;
+ if(dlc_restart.type == DlcZoomEmpty &&
+ as.selections > prevsel)
+ do_beginning++;
+ else if(as.zoomed){
+ if(as.selections == 0){
+ as.zoomed = 0;
+ q_status_message(SM_ORDER, 0, 2,
+ "Zoom Mode is now off");
+ do_warp++;
+ }
+ else
+ do_beginning++;
+ }
+ else{
+ if(prevsel == 0 && as.selections > 0 &&
+ !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
+ as.zoomed = 1;
+ do_beginning++;
+ }
+ else
+ do_warp++;
+ }
+
+ if(prevsel == as.selections && prevsel > 0){
+ if(as.selections == 1)
+ q_status_message(SM_ORDER, 0, 2,
+ "No change resulted, 1 entry remains selected");
+ else
+ q_status_message1(SM_ORDER, 0, 2,
+ "No change resulted, %s entries remain selected",
+ comatose(as.selections));
+ }
+ else if(prevsel == 0){
+ if(as.selections == 1)
+ q_status_message(SM_ORDER, 0, 2,
+ "Select matched 1 entry");
+ else if(as.selections > 1)
+ q_status_message1(SM_ORDER, 0, 2,
+ "Select matched %s entries",
+ comatose(as.selections));
+ else
+ q_status_message(SM_ORDER, 0, 2,
+ "Select failed! No entries selected");
+ }
+ else if(as.selections == 0){
+ if(prevsel == 1)
+ q_status_message(SM_ORDER, 0, 2,
+ "The single selected entry is UNselected");
+ else
+ q_status_message1(SM_ORDER, 0, 2,
+ "All %s entries UNselected",
+ comatose(prevsel));
+ }
+ else if(narrow){
+ if(as.selections == 1 && (prevsel-as.selections) == 1)
+ q_status_message(SM_ORDER, 0, 2,
+ "1 entry now selected, 1 entry was UNselected");
+ else if(as.selections == 1)
+ q_status_message1(SM_ORDER, 0, 2,
+ "1 entry now selected, %s entries were UNselected",
+ comatose(prevsel-as.selections));
+ else if((prevsel-as.selections) == 1)
+ q_status_message1(SM_ORDER, 0, 2,
+ "%s entries now selected, 1 entry was UNselected",
+ comatose(as.selections));
+ else
+ q_status_message2(SM_ORDER, 0, 2,
+ "%s entries now selected, %s entries were UNselected",
+ comatose(as.selections),
+ comatose(prevsel-as.selections));
+ }
+ else{
+ if((as.selections-prevsel) == 1)
+ q_status_message1(SM_ORDER, 0, 2,
+ "1 new entry selected, %s entries now selected",
+ comatose(as.selections));
+ else if(as.selections == 1)
+ q_status_message1(SM_ORDER, 0, 2,
+ "%s new entries selected, 1 entry now selected",
+ comatose(as.selections-prevsel));
+ else
+ q_status_message2(SM_ORDER, 0, 2,
+ "%s new entries selected, %s entries now selected",
+ comatose(as.selections-prevsel),
+ comatose(as.selections));
+ }
+ }
+
+ break;
+
+ default :
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Unsupported Select option");
+ break;
+ }
+ }
+ else{
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global))
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Select is only available from within an expanded address book");
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Select is only available when viewing an individual address book");
+
+ return;
+ }
+
+ if(rv)
+ return;
+
+ if(do_beginning){
+ warp_to_beginning(); /* just go to top */
+ as.top_ent = 0L;
+ new_ent = first_selectable_line(0L);
+ if(new_ent == NO_LINE)
+ as.cur_row = 0L;
+ else
+ as.cur_row = new_ent;
+
+ /* if it is off screen */
+ if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row - as.l_p_page + 1);
+ as.cur_row = (as.l_p_page - 1);
+ }
+ }
+ else if(do_flush)
+ flush_dlc_from_cache(&dlc_restart);
+ else if(do_warp)
+ warp_to_dlc(&dlc_restart, dlc_restart.global_row);
+
+ if(move_current){
+ dlc = get_dlc(cur_line);
+ if(dlc->type == DlcEnd){
+ as.cur_row--;
+ if(as.cur_row < 0){
+ as.top_ent += as.cur_row; /* plus a negative number */
+ as.cur_row = 0;
+ *start_disp = 0;
+ }
+ else
+ *start_disp = as.cur_row;
+
+ }
+ else
+ *start_disp = as.cur_row;
+ }
+}
+
+
+/*
+ * Selects based on whether an entry is a list or not.
+ *
+ * Returns: 0 if search went ok
+ * -1 if there was a problem
+ */
+int
+ab_select_type(AdrBk *abook, int narrow)
+{
+ static ESCKEY_S ab_sel_type_opt[] = {
+ {'s', 's', "S", "Simple"},
+ {'l', 'l', "L", "List"},
+ {-1, 0, NULL, NULL}
+ };
+ static char *ab_sel_type = "Select Lists or Simples (non Lists) ? ";
+ int type;
+ adrbk_cntr_t num, ab_count;
+
+ dprint((6, "- ab_select_type -\n"));
+
+ if(!abook)
+ return -1;
+
+ switch(type = radio_buttons(ab_sel_type, -FOOTER_ROWS(ps_global),
+ ab_sel_type_opt, 'l', 'x', NO_HELP, RB_NORM)){
+ case 'l':
+ break;
+
+ case 's':
+ break;
+
+ case 'x':
+ cmd_cancelled("Select");
+ return -1;
+
+ default:
+ dprint((1,"\n - BOTCH: ab_select_type unknown option\n"));
+ return -1;
+ }
+
+ ab_count = adrbk_count(abook);
+ for(num = 0; num < ab_count; num++){
+ AdrBk_Entry *abe;
+ int matched;
+
+ /*
+ * If it won't possibly change state, don't look at it.
+ */
+ if((narrow && !entry_is_selected(abook->selects, (a_c_arg_t)num)) ||
+ (!narrow && entry_is_selected(abook->selects, (a_c_arg_t)num)))
+ continue;
+
+ abe = adrbk_get_ae(abook, (a_c_arg_t) num);
+ matched = ((type == 's' && abe->tag == Single) ||
+ (type == 'l' && abe->tag == List));
+
+ if(narrow && !matched){
+ entry_unset_selected(abook->selects, (a_c_arg_t)num);
+ as.selections--;
+ }
+ else if(!narrow && matched){
+ entry_set_selected(abook->selects, (a_c_arg_t)num);
+ as.selections++;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Selects based on string matches in various addrbook fields.
+ *
+ * Returns: 0 if search went ok
+ * -1 if there was a problem
+ */
+int
+ab_select_text(AdrBk *abook, int narrow)
+{
+ static ESCKEY_S ab_sel_text_opt[] = {
+ {'n', 'n', "N", "Nickname"},
+ {'a', 'a', "A", "All Text"},
+ {'f', 'f', "F", "Fullname"},
+ {'e', 'e', "E", "Email Addrs"},
+ {'c', 'c', "C", "Comment"},
+ {'z', 'z', "Z", "Fcc"},
+ {-1, 0, NULL, NULL}
+ };
+ static char *ab_sel_text =
+ "Select based on Nickname, All text, Fullname, Addrs, Comment, or Fcc ? ";
+ HelpType help = NO_HELP;
+ int type, r;
+ char sstring[80+1], prompt[80];
+ adrbk_cntr_t num, ab_count;
+ char *fmt = "String in \"%s\" to match : ";
+
+ dprint((6, "- ab_select_text -\n"));
+
+ if(!abook)
+ return -1;
+
+ switch(type = radio_buttons(ab_sel_text, -FOOTER_ROWS(ps_global),
+ ab_sel_text_opt, 'a', 'x', NO_HELP, RB_NORM)){
+ case 'n':
+ snprintf(prompt, sizeof(prompt), fmt, "Nickname");
+ break;
+ case 'a':
+ snprintf(prompt, sizeof(prompt), fmt, "All Text");
+ break;
+ case 'f':
+ snprintf(prompt, sizeof(prompt), fmt, "Fullname");
+ break;
+ case 'e':
+ snprintf(prompt, sizeof(prompt), fmt, "addresses");
+ break;
+ case 'c':
+ snprintf(prompt, sizeof(prompt), fmt, "Comment");
+ break;
+ case 'z':
+ snprintf(prompt, sizeof(prompt), fmt, "Fcc");
+ break;
+ case 'x':
+ break;
+ default:
+ dprint((1,"\n - BOTCH: ab_select_text unknown option\n"));
+ return -1;
+ }
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ sstring[0] = '\0';
+ while(type != 'x'){
+ int flags = OE_APPEND_CURRENT;
+
+ r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
+ sizeof(sstring), prompt, NULL, help, &flags);
+ switch(r){
+ case 3: /* BUG, no help */
+ case 4:
+ continue;
+
+ default:
+ break;
+ }
+
+ if(r == 1 || sstring[0] == '\0')
+ r = 'x';
+
+ break;
+ }
+
+ if(type == 'x' || r == 'x'){
+ cmd_cancelled("Select");
+ return -1;
+ }
+
+ ab_count = adrbk_count(abook);
+ for(num = 0; num < ab_count; num++){
+ AdrBk_Entry *abe;
+ int matched;
+
+ /*
+ * If it won't possibly change state, don't look at it.
+ */
+ if((narrow && !entry_is_selected(abook->selects, (a_c_arg_t)num)) ||
+ (!narrow && entry_is_selected(abook->selects, (a_c_arg_t)num)))
+ continue;
+
+ abe = adrbk_get_ae(abook, (a_c_arg_t) num);
+ matched = match_check(abe, type, sstring);
+
+ if(narrow && !matched){
+ entry_unset_selected(abook->selects, (a_c_arg_t)num);
+ as.selections--;
+ }
+ else if(!narrow && matched){
+ entry_set_selected(abook->selects, (a_c_arg_t)num);
+ as.selections++;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Returns: 1 if a match is found for the entry
+ * 0 no match
+ * -1 error
+ */
+int
+match_check(AdrBk_Entry *abe, int type, char *string)
+{
+ static int err = -1;
+ static int match = 1;
+ static int nomatch = 0;
+ unsigned int checks;
+#define CK_NICKNAME 0x01
+#define CK_FULLNAME 0x02
+#define CK_ADDRESSES 0x04
+#define CK_FCC 0x08
+#define CK_COMMENT 0x10
+#define CK_ALL 0x1f
+
+ checks = 0;
+
+ switch(type){
+ case 'n': /* Nickname */
+ checks |= CK_NICKNAME;
+ break;
+
+ case 'f': /* Fullname */
+ checks |= CK_FULLNAME;
+ break;
+
+ case 'e': /* Addrs */
+ checks |= CK_ADDRESSES;
+ break;
+
+ case 'a': /* All Text */
+ checks |= CK_ALL;
+ break;
+
+ case 'z': /* Fcc */
+ checks |= CK_FCC;
+ break;
+
+ case 'c': /* Comment */
+ checks |= CK_COMMENT;
+ break;
+
+ default:
+ q_status_message(SM_ORDER | SM_DING, 3, 3, "Unknown type");
+ return(err);
+ }
+
+ if(checks & CK_NICKNAME){
+ if(abe && abe->nickname && srchstr(abe->nickname, string))
+ return(match);
+ }
+
+ if(checks & CK_FULLNAME){
+ if(abe &&
+ abe->fullname &&
+ abe->fullname[0] &&
+ srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fullname),
+ string))
+ return(match);
+ }
+
+ if(checks & CK_ADDRESSES){
+ if(abe &&
+ abe->tag == Single &&
+ abe->addr.addr &&
+ abe->addr.addr[0] &&
+ srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->addr.addr),
+ string))
+ return(match);
+ else if(abe &&
+ abe->tag == List &&
+ abe->addr.list){
+ char **p;
+
+ for(p = abe->addr.list; p != NULL && *p != NULL; p++){
+ if(srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, *p),
+ string))
+ return(match);
+ }
+ }
+ }
+
+ if(checks & CK_FCC){
+ if(abe &&
+ abe->fcc &&
+ abe->fcc[0] &&
+ srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fcc),
+ string))
+ return(match);
+ }
+
+ if(checks & CK_COMMENT && abe && abe->extra && abe->extra[0]){
+ size_t n, len;
+ unsigned char *p, *tmp = NULL;
+ int found_it = 0;
+
+ if((n = 4*strlen(abe->extra)) > SIZEOF_20KBUF-1){
+ len = n+1;
+ p = tmp = (unsigned char *)fs_get(len * sizeof(char));
+ }
+ else{
+ len = SIZEOF_20KBUF;
+ p = (unsigned char *)tmp_20k_buf;
+ }
+
+ if(srchstr((char *)rfc1522_decode_to_utf8(p, len, abe->extra), string))
+ found_it++;
+
+ if(tmp)
+ fs_give((void **)&tmp);
+
+ if(found_it)
+ return(match);
+ }
+
+ return(nomatch);
+}
+
+
+/*
+ * Go to folder.
+ *
+ * command_line -- The screen line on which to prompt
+ */
+void
+ab_goto_folder(int command_line)
+{
+ char *go_folder;
+ CONTEXT_S *tc;
+ int notrealinbox = 0;
+
+ dprint((2, "- ab_goto_folder -\n"));
+
+ tc = ps_global->context_current;
+
+ go_folder = broach_folder(command_line, 1, &notrealinbox, &tc);
+
+ if(go_folder != NULL)
+ visit_folder(ps_global, go_folder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
+}
+
+
+/*
+ * Execute whereis command.
+ *
+ * Returns value of the new top entry, or NO_LINE if cancelled.
+ */
+long
+ab_whereis(int *warped, int command_line)
+{
+ int rc, wrapped = 0;
+ long new_top_ent, new_line;
+
+ dprint((5, "- ab_whereis -\n"));
+
+ rc = search_book(as.top_ent+as.cur_row, command_line,
+ &new_line, &wrapped, warped);
+
+ new_top_ent = NO_LINE;
+
+ if(rc == -2)
+ q_status_message(SM_INFO, 0, 2, _("Address book search cancelled"));
+
+ else if(rc == -1)
+ q_status_message(SM_ORDER, 0, 4, _("Word not found"));
+
+ else if(rc == 0){ /* search succeeded */
+
+ if(wrapped == 1)
+ q_status_message(SM_INFO, 0, 2, _("Search wrapped to beginning"));
+ else if(wrapped == 2)
+ q_status_message(SM_INFO, 0, 2,
+ _("Current line contains the only match"));
+
+ /* know match is on the same page */
+ if(!*warped &&
+ new_line >= as.top_ent &&
+ new_line < as.top_ent+as.l_p_page)
+ new_top_ent = as.top_ent;
+ /* don't know whether it is or not, reset top_ent */
+ else
+ new_top_ent = first_line(new_line - as.l_p_page/2);
+
+ as.cur_row = new_line - new_top_ent;
+ }
+
+ return(new_top_ent);
+}
+
+
+/*
+ * recalculate display parameters for window size change
+ */
+void
+ab_resize(void)
+{
+ long new_line;
+ int old_l_p_p;
+ DL_CACHE_S dlc_buf, *dlc_restart;
+
+ old_l_p_p = as.l_p_page;
+ as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)
+ - HEADER_ROWS(ps_global);
+
+ dprint((9, "- ab_resize -\n l_p_p was %d, now %d\n",
+ old_l_p_p, as.l_p_page));
+
+ if(as.l_p_page <= 0){
+ as.no_op_possbl++;
+ return;
+ }
+ else
+ as.no_op_possbl = 0;
+
+ new_line = as.top_ent + as.cur_row;
+ as.top_ent = first_line(new_line - as.l_p_page/2);
+ as.cur_row = new_line - as.top_ent;
+ as.old_cur_row = as.cur_row;
+
+ /* need this to re-initialize Text and Title lines in display */
+ /* get the old current line (which may be the wrong width) */
+ dlc_restart = get_dlc(new_line);
+ /* flush it from cache */
+ flush_dlc_from_cache(dlc_restart);
+ /* re-get it (should be right now) */
+ dlc_restart = get_dlc(new_line);
+ /* copy it to local storage */
+ dlc_buf = *dlc_restart;
+ dlc_restart = &dlc_buf;
+ /* flush everything from cache and add that one line back in */
+ warp_to_dlc(dlc_restart, new_line);
+}
+
+
+/*
+ * Returns 0 if we know for sure that there are no
+ * addresses available in any of the addressbooks.
+ *
+ * Easiest would be to start at 0 and go through the addrbook, but that will
+ * be very slow for big addrbooks if we're not close to 0 already. Instead,
+ * starting_hint is a hint at a good place to start looking.
+ */
+int
+any_addrs_avail(long int starting_hint)
+{
+ register AddrScrn_Disp *dl;
+ long lineno;
+
+ /*
+ * Look from lineno backwards first, in hopes of finding it in cache.
+ */
+ lineno = starting_hint;
+ for(dl=dlist(lineno);
+ dl->type != Beginning;
+ dl = dlist(--lineno)){
+ if(dl->type == NoAbooks)
+ return 0;
+
+ switch(dl->type){
+ case Simple:
+ case ListEnt:
+ case ListHead:
+ case ZoomEmpty:
+ case Title:
+ case ListClickHere:
+ case ClickHereCmb:
+ return 1;
+ default:
+ break;
+ }
+ }
+
+ /* search from here forward if we still don't know */
+ lineno = starting_hint;
+ for(dl=dlist(lineno);
+ dl->type != End;
+ dl = dlist(++lineno)){
+ if(dl->type == NoAbooks)
+ return 0;
+
+ switch(dl->type){
+ case Simple:
+ case ListEnt:
+ case ListHead:
+ case ZoomEmpty:
+ case Title:
+ case ListClickHere:
+ case ClickHereCmb:
+ return 1;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if this line is a clickable line.
+ */
+int
+entry_is_clickable(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ if((dl = dlist(lineno)) &&
+ (dl->type == Title || dl->type == ListClickHere ||
+ dl->type == ClickHereCmb))
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if this line is a clickable Title line.
+ */
+int
+entry_is_clickable_title(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ if((dl = dlist(lineno)) && (dl->type == Title || dl->type == ClickHereCmb))
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if an address or list is selected.
+ */
+int
+is_addr(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ if((dl = dlist(lineno)) && (dl->type == ListHead ||
+ dl->type == ListEnt ||
+ dl->type == Simple))
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if type of line is Empty.
+ */
+int
+is_empty(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ if((dl = dlist(lineno)) &&
+ (dl->type == Empty || dl->type == ListEmpty || dl->type == ZoomEmpty))
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if lineno is a list entry
+ */
+int
+entry_is_listent(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ if((dl = dlist(lineno)) && dl->type == ListEnt)
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if lineno is a fake addrbook for config screen add
+ * or if it is an AskServer line for LDAP query
+ */
+int
+entry_is_addkey(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ if((dl = dlist(lineno)) && (dl->type == AddFirstPers ||
+ dl->type == AddFirstGlob ||
+ dl->type == AskServer))
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if lineno is the line to ask for directory server query
+ */
+int
+entry_is_askserver(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ if((dl = dlist(lineno)) && dl->type == AskServer)
+ return 1;
+
+ return 0;
+}
+
+
+/*
+ * Returns 1 if an add abook here would be global, not personal
+ */
+int
+add_is_global(long int lineno)
+{
+ register AddrScrn_Disp *dl;
+
+ dl = dlist(lineno);
+
+ if(dl){
+ if(dl->type == Title){
+ register PerAddrBook *pab;
+
+ pab = &as.adrbks[as.cur];
+ if(pab && pab->type & GLOBAL)
+ return 1;
+ }
+ else if(dl->type == AddFirstGlob)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Find the first line greater than or equal to line. (Any line, not
+ * necessarily selectable.)
+ *
+ * Returns the line number of the found line or NO_LINE if there is none.
+ *
+ * Warning: This just starts at the passed in line and goes forward until
+ * it runs into a line that isn't a Beginning line. If the line passed in
+ * is not in the dlc cache, it will have no way to know when it gets to the
+ * real beginning.
+ */
+long
+first_line(long int line)
+{
+ long lineno;
+ register PerAddrBook *pab;
+ int i;
+
+ for(lineno=line;
+ dlist(lineno)->type == Beginning;
+ lineno++)
+ ;/* do nothing */
+
+ if(dlist(lineno)->type != End)
+ return(lineno);
+ else{
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->ostatus != Open &&
+ pab->ostatus != HalfOpen &&
+ pab->ostatus != ThreeQuartOpen)
+ return NO_LINE;
+ }
+
+ as.no_op_possbl++;
+ return(NO_LINE);
+ }
+}
+
+
+/*
+ * Find the line number of the next selectable line.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * new_line -- Return value: new line position
+ *
+ * Result: The new line number is set.
+ * The value 1 is returned if OK or 0 if there is no next line.
+ */
+int
+next_selectable_line(long int cur_line, long int *new_line)
+{
+ /* skip over non-selectable lines */
+ for(cur_line++;
+ !line_is_selectable(cur_line) && dlist(cur_line)->type != End;
+ cur_line++)
+ ;/* do nothing */
+
+ if(dlist(cur_line)->type == End)
+ return 0;
+
+ *new_line = cur_line;
+ return 1;
+}
+
+
+/*
+ * Find the line number of the previous selectable line.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * new_line -- Return value: new line position
+ *
+ * Result: The new line number is set.
+ * The value 1 is returned if OK or 0 if there is no previous line.
+ */
+int
+prev_selectable_line(long int cur_line, long int *new_line)
+{
+ /* skip backwards over non-selectable lines */
+ for(cur_line--;
+ !line_is_selectable(cur_line) && dlist(cur_line)->type != Beginning;
+ cur_line--)
+ ;/* do nothing */
+
+ if(dlist(cur_line)->type == Beginning)
+ return 0;
+
+ *new_line = cur_line;
+
+ return 1;
+}
+
+
+/*
+ * Resync the display with the addrbooks, which were just discovered
+ * to be out of sync with the display.
+ *
+ * Returns 1 -- current address book had to be resynced
+ * 0 -- current address book not resynced
+ */
+int
+resync_screen(PerAddrBook *pab, AddrBookArg style, int checkedn)
+{
+ AddrScrn_Disp *dl;
+ int current_resynced = 0;
+ DL_CACHE_S dlc_restart, *dlc = NULL;
+
+ /*
+ * The test below gives conditions under which it is safe to go ahead
+ * and resync all the addrbooks that are out of sync now, and the
+ * display won't change. Otherwise, we have to be careful to preserve
+ * some of our state so that we can attempt to restore the screen to
+ * a state that is as close as possible to what we have now. If the
+ * currently opened address book (pab) is out of date we will lose
+ * the expanded state of its distribution lists, which is no big deal.
+ * Since resyncing also loses the checked status if we're selecting with
+ * ListMode, we don't even attempt it in that case.
+ */
+ if((ab_nesting_level < 2 && !cur_is_open() && checkedn == 0) ||
+ (style == AddrBookScreen &&
+ pab &&
+ pab->address_book &&
+ !(pab->address_book->flags & FILE_OUTOFDATE ||
+ (pab->address_book->rd &&
+ pab->address_book->rd->flags & REM_OUTOFDATE)))){
+
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ dlc_restart = *dlc;
+ }
+
+ if(adrbk_check_and_fix_all(1, 0, 1)){
+ ps_global->mangled_footer = 1; /* why? */
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ warp_to_dlc(&dlc_restart, 0L);
+ /* put current entry in middle of screen */
+ as.top_ent = first_line(0L - (long)as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ ps_global->mangled_screen = 1;
+ }
+ }
+ }
+ else if(style == AddrBookScreen){
+ char *savenick = NULL;
+ AdrBk_Entry *abe;
+ adrbk_cntr_t old_entry_num, new_entry_num;
+ long old_global_row;
+
+ current_resynced++;
+
+ /*
+ * We're going to try to get the nickname of the current
+ * entry and find it again after the resync.
+ */
+ dl = dlist(as.top_ent+as.cur_row);
+ if(dl->type == ListEnt ||
+ dl->type == ListEmpty ||
+ dl->type == ListClickHere ||
+ dl->type == ListHead ||
+ dl->type == Simple){
+ abe = ae(as.top_ent+as.cur_row);
+ old_entry_num = dl->elnum;
+ old_global_row = as.top_ent+as.cur_row;
+ if(abe && abe->nickname && abe->nickname[0])
+ savenick = cpystr(abe->nickname);
+ }
+
+ /* this will close and re-open current addrbook */
+ (void)adrbk_check_and_fix_all(1, 0, 1);
+
+ abe = NULL;
+ if(savenick){
+ abe = adrbk_lookup_by_nick(pab->address_book, savenick,
+ &new_entry_num);
+ fs_give((void **)&savenick);
+ }
+
+ if(abe){ /* If we found the same nickname, move to it */
+ dlc_restart.adrbk_num = as.cur;
+ dlc_restart.dlcelnum = new_entry_num;
+ dlc_restart.type = (abe->tag == Single)
+ ? DlcSimple : DlcListHead;
+ if(old_entry_num == new_entry_num)
+ warp_to_dlc(&dlc_restart, old_global_row);
+ else
+ warp_to_dlc(&dlc_restart, 0L);
+ }
+ else
+ warp_to_top_of_abook(as.cur);
+
+ if(!abe || old_entry_num != new_entry_num){
+ /* put current entry in middle of screen */
+ as.top_ent = first_line(0L - (long)as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+ }
+
+ return(current_resynced);
+}
+
+
+/*
+ * Erase all the check marks.
+ */
+void
+erase_checks(void)
+{
+ int i;
+ PerAddrBook *pab;
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->address_book && pab->address_book->checks)
+ exp_free(pab->address_book->checks);
+
+ init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
+ }
+}
+
+
+/*
+ * Erase all the selections.
+ */
+void
+erase_selections(void)
+{
+ int i;
+ PerAddrBook *pab;
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab->address_book && pab->address_book->selects)
+ exp_free(pab->address_book->selects);
+ }
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
+ }
+
+ as.selections = 0;
+}
+
+
+/*
+ * return values of search_in_one_line are or'd combination of these
+ */
+#define MATCH_NICK 0x1 /* match in field 0 */
+#define MATCH_FULL 0x2 /* match in field 1 */
+#define MATCH_ADDR 0x4 /* match in field 2 */
+#define MATCH_FCC 0x8 /* match in fcc field */
+#define MATCH_COMMENT 0x10 /* match in comment field */
+#define MATCH_BIGFIELD 0x20 /* match in one of the fields that crosses the
+ whole screen, like a Title field */
+#define MATCH_LISTMEM 0x40 /* match list member */
+/*
+ * Prompt user for search string and call find_in_book.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * command_line -- The screen line to prompt on
+ * new_line -- Return value: new line position
+ * wrapped -- Wrapped to beginning of display, tell user
+ * warped -- Warped to a new location in the addrbook
+ *
+ * Result: The new line number is set if the search is successful.
+ * Returns 0 if found, -1 if not, -2 if cancelled.
+ *
+ */
+int
+search_book(long int cur_line, int command_line, long int *new_line, int *wrapped, int *warped)
+{
+ int i=0, find_result, rc, flags, ku;
+ static HISTORY_S *history = NULL;
+ char search_string[MAX_SEARCH + 1];
+ char prompt[MAX_SEARCH + 50], nsearch_string[MAX_SEARCH+1], *p;
+ HelpType help;
+ ESCKEY_S ekey[6];
+ PerAddrBook *pab;
+ long nl;
+
+ dprint((7, "- search_book -\n"));
+
+ init_hist(&history, HISTSIZE);
+
+ search_string[0] = '\0';
+ if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
+ strncpy(search_string, p, sizeof(search_string));
+ search_string[sizeof(search_string)-1] = '\0';
+ }
+
+ snprintf(prompt, sizeof(prompt), _("Word to search for [%.*s]: "), MAX_SEARCH, search_string);
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ nsearch_string[0] = '\0';
+
+ ekey[i].ch = 0;
+ ekey[i].rval = 0;
+ ekey[i].name = "";
+ ekey[i++].label = "";
+
+ ekey[i].ch = ctrl('Y');
+ ekey[i].rval = 10;
+ ekey[i].name = "^Y";
+ /* TRANSLATORS: User is searching in address book. One of the options is to
+ search for the First Address */
+ ekey[i++].label = _("First Adr");
+
+ ekey[i].ch = ctrl('V');
+ ekey[i].rval = 11;
+ ekey[i].name = "^V";
+ /* TRANSLATORS: Last Address */
+ ekey[i++].label = _("Last Adr");
+
+ ekey[i].ch = KEY_UP;
+ ekey[i].rval = 30;
+ ekey[i].name = "";
+ ku = i;
+ ekey[i++].label = "";
+
+ ekey[i].ch = KEY_DOWN;
+ ekey[i].rval = 31;
+ ekey[i].name = "";
+ ekey[i++].label = "";
+
+ ekey[i].ch = -1;
+
+ flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
+ while(1){
+
+ /*
+ * 2 is really 1 because there will be one real entry and
+ * one entry of "" because of the get_prev_hist above.
+ */
+ if(items_in_hist(history) > 2){
+ ekey[ku].name = HISTORY_UP_KEYNAME;
+ ekey[ku].label = HISTORY_KEYLABEL;
+ ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
+ ekey[ku+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ ekey[ku].name = "";
+ ekey[ku].label = "";
+ ekey[ku+1].name = "";
+ ekey[ku+1].label = "";
+ }
+
+ rc = optionally_enter(nsearch_string, command_line, 0,
+ sizeof(nsearch_string),
+ prompt, ekey, help, &flags);
+ if(rc == 3){
+ help = help == NO_HELP ? h_oe_searchab : NO_HELP;
+ continue;
+ }
+ else if(rc == 10){
+ *warped = 1;
+ warp_to_beginning(); /* go to top of addrbooks */
+ if((nl=first_selectable_line(0L)) != NO_LINE){
+ *new_line = nl;
+ q_status_message(SM_INFO, 0, 2, _("Searched to first entry"));
+ return 0;
+ }
+ else{
+ q_status_message(SM_INFO, 0, 2, _("No entries"));
+ return -1;
+ }
+ }
+ else if(rc == 11){
+ *warped = 1;
+ warp_to_end(); /* go to bottom */
+ if((nl=first_selectable_line(0L)) != NO_LINE){
+ *new_line = nl;
+ q_status_message(SM_INFO, 0, 2, _("Searched to last entry"));
+ return 0;
+ }
+ else{
+ q_status_message(SM_INFO, 0, 2, _("No entries"));
+ return -1;
+ }
+ }
+ else if(rc == 30){
+ if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
+ strncpy(nsearch_string, p, sizeof(nsearch_string));
+ nsearch_string[sizeof(nsearch_string)-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+ else if(rc == 31){
+ if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
+ strncpy(nsearch_string, p, sizeof(nsearch_string));
+ nsearch_string[sizeof(nsearch_string)-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+
+ if(rc != 4){ /* 4 is redraw */
+ save_hist(history, nsearch_string, 0, NULL);
+ break;
+ }
+ }
+
+
+ if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
+ return -2;
+
+ if(nsearch_string[0] != '\0'){
+ strncpy(search_string, nsearch_string, sizeof(search_string)-1);
+ search_string[sizeof(search_string)-1] = '\0';
+ }
+
+ find_result = find_in_book(cur_line, search_string, new_line, wrapped);
+
+ if(*wrapped == 1)
+ *warped = 1;
+
+ if(find_result){
+ int also = 0, notdisplayed = 0;
+
+ pab = &as.adrbks[adrbk_num_from_lineno(*new_line)];
+ if(find_result & MATCH_NICK){
+ if(pab->nick_is_displayed)
+ also++;
+ else
+ notdisplayed++;
+ }
+
+ if(find_result & MATCH_FULL){
+ if(pab->full_is_displayed)
+ also++;
+ else
+ notdisplayed++;
+ }
+
+ if(find_result & MATCH_ADDR){
+ if(pab->addr_is_displayed)
+ also++;
+ else
+ notdisplayed++;
+ }
+
+ if(find_result & MATCH_FCC){
+ if(pab->fcc_is_displayed)
+ also++;
+ else
+ notdisplayed++;
+ }
+
+ if(find_result & MATCH_COMMENT){
+ if(pab->comment_is_displayed)
+ also++;
+ else
+ notdisplayed++;
+ }
+
+ if(find_result & MATCH_LISTMEM){
+ AddrScrn_Disp *dl;
+
+ dl = dlist(*new_line);
+ if(F_OFF(F_EXPANDED_DISTLISTS,ps_global)
+ && !exp_is_expanded(pab->address_book->exp, (a_c_arg_t)dl->elnum))
+ notdisplayed++;
+ }
+
+ if(notdisplayed > 1 && *wrapped == 0){
+ if(also)
+ /* TRANSLATORS: These "matched" messages are advisory messages explaining
+ how a search command matched an entry */
+ q_status_message1(SM_ORDER,0,4, _("Also matched string in %s other fields"),
+ comatose(notdisplayed));
+ else
+ q_status_message1(SM_ORDER,0,4, _("Matched string in %s fields"),
+ comatose(notdisplayed));
+ }
+ else if(notdisplayed == 1 && *wrapped == 0){
+ if(also){
+ if(find_result & MATCH_NICK && !pab->nick_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Also matched string in Nickname field"));
+ else if(find_result & MATCH_FULL && !pab->full_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Also matched string in Fullname field"));
+ else if(find_result & MATCH_ADDR && !pab->addr_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Also matched string in Address field"));
+ else if(find_result & MATCH_FCC && !pab->fcc_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Also matched string in Fcc field"));
+ else if(find_result & MATCH_COMMENT && !pab->comment_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Also matched string in Comment field"));
+ else if(find_result & MATCH_LISTMEM)
+ q_status_message(SM_ORDER,0,4, _("Also matched string in list member address"));
+ else
+ q_status_message(SM_ORDER,0,4, _("Also matched string in ?"));
+ }
+ else{
+ if(find_result & MATCH_NICK && !pab->nick_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Matched string in Nickname field"));
+ else if(find_result & MATCH_FULL && !pab->full_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Matched string in Fullname field"));
+ else if(find_result & MATCH_ADDR && !pab->addr_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Matched string in Address field"));
+ else if(find_result & MATCH_FCC && !pab->fcc_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Matched string in Fcc field"));
+ else if(find_result & MATCH_COMMENT && !pab->comment_is_displayed)
+ q_status_message(SM_ORDER,0,4, _("Matched string in Comment field"));
+ else if(find_result & MATCH_LISTMEM)
+ q_status_message(SM_ORDER,0,4, _("Matched string in list member address"));
+ else
+ q_status_message(SM_ORDER,0,4, _("Matched string in ?"));
+ }
+ }
+
+
+ /* be sure to be on a selectable field */
+ if(!line_is_selectable(*new_line))
+ if((nl=first_selectable_line(*new_line+1)) != NO_LINE)
+ *new_line = nl;
+ }
+
+ return(find_result ? 0 : -1);
+}
+
+
+/*
+ * Search the display list for the given string.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * string -- String to search for
+ * new_line -- Return value: new line position
+ * wrapped -- Wrapped to beginning of display during search
+ *
+ * Result: The new line number is set if the search is successful.
+ * Returns 0 -- string not found
+ * Otherwise, a bitmask of which fields the string was found in.
+ */
+int
+find_in_book(long int cur_line, char *string, long int *new_line, int *wrapped)
+{
+ register AddrScrn_Disp *dl;
+ long nl, nl_save;
+ int fields;
+ AdrBk_Entry *abe;
+ char *listaddr = NULL;
+ DL_CACHE_S *dlc,
+ dlc_save; /* a local copy */
+
+
+ dprint((9, "- find_in_book -\n"));
+
+ /*
+ * Save info to allow us to get back to where we were if we can't find
+ * the string. Also used to stop our search if we wrap back to the
+ * start and search forward.
+ */
+
+ nl_save = cur_line;
+ dlc = get_dlc(nl_save);
+ dlc_save = *dlc;
+
+ *wrapped = 0;
+ nl = cur_line + 1L;
+
+ /* start with next line and search to the end of the disp_list */
+ dl = dlist(nl);
+ while(dl->type != End){
+ if(dl->type == Simple ||
+ dl->type == ListHead ||
+ dl->type == ListEnt ||
+ dl->type == ListClickHere){
+ abe = ae(nl);
+ if(dl->type == ListEnt)
+ listaddr = listmem(nl);
+ }
+ else
+ abe = (AdrBk_Entry *)NULL;
+
+ if((fields=search_in_one_line(dl, abe, listaddr, string)) != 0)
+ goto found;
+
+ dl = dlist(++nl);
+ }
+
+
+ /*
+ * Wrap back to the start of the addressbook and search forward
+ * from there.
+ */
+ warp_to_beginning(); /* go to top of addrbooks */
+ nl = 0L; /* line number is always 0 after warp_to_beginning */
+ *wrapped = 1;
+
+ dlc = get_dlc(nl);
+ while(!matching_dlcs(&dlc_save, dlc) && dlc->type != DlcEnd){
+
+ fill_in_dl_field(dlc);
+ dl = &dlc->dl;
+
+ if(dl->type == Simple ||
+ dl->type == ListHead ||
+ dl->type == ListEnt ||
+ dl->type == ListClickHere){
+ abe = ae(nl);
+ if(dl->type == ListEnt)
+ listaddr = listmem(nl);
+ }
+ else
+ abe = (AdrBk_Entry *)NULL;
+
+ if((fields=search_in_one_line(dl, abe, listaddr, string)) != 0)
+ goto found;
+
+ dlc = get_dlc(++nl);
+ }
+
+ /* see if it is in the current line */
+ fill_in_dl_field(dlc);
+ dl = &dlc->dl;
+
+ if(dl->type == Simple ||
+ dl->type == ListHead ||
+ dl->type == ListEnt ||
+ dl->type == ListClickHere){
+ abe = ae(nl);
+ if(dl->type == ListEnt)
+ listaddr = listmem(nl);
+ }
+ else
+ abe = (AdrBk_Entry *)NULL;
+
+ fields = search_in_one_line(dl, abe, listaddr, string);
+ if(dl->usst &&
+ (dl->type == Text || dl->type == Title || dl->type == TitleCmb))
+ fs_give((void **)&dl->usst);
+
+ /* jump cache back to where we started */
+ *wrapped = 0;
+ warp_to_dlc(&dlc_save, nl_save);
+ if(fields){
+ *new_line = nl_save; /* because it was in current line */
+ *wrapped = 2;
+ }
+
+ nl = *new_line;
+
+found:
+ *new_line = nl;
+ return(fields);
+}
+
+
+/*
+ * Look in line dl for string.
+ *
+ * Args: dl -- the display list for this line
+ * abe -- AdrBk_Entry if it is an address type
+ * listaddr -- list member if it is of type ListEnt
+ * string -- look for this string
+ *
+ * Result: 0 -- string not found
+ * Otherwise, a bitmask of which fields the string was found in.
+ * MATCH_NICK 0x1
+ * MATCH_FULL 0x2
+ * MATCH_ADDR 0x4
+ * MATCH_FCC 0x8
+ * MATCH_COMMENT 0x10
+ * MATCH_BIGFIELD 0x20
+ * MATCH_LISTMEM 0x40
+ */
+int
+search_in_one_line(AddrScrn_Disp *dl, AdrBk_Entry *abe, char *listaddr, char *string)
+{
+ register int c;
+ int ret_val = 0;
+ char **lm;
+
+ for(c = 0; c < 5; c++){
+ switch(c){
+ case 0:
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ if(srchstr(abe->nickname, string))
+ ret_val |= MATCH_NICK;
+
+ break;
+
+ case Text:
+ case Title:
+ case TitleCmb:
+ case AskServer:
+ if(srchstr(dl->usst, string))
+ ret_val |= MATCH_BIGFIELD;
+
+ default:
+ break;
+ }
+ break;
+
+ case 1:
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ if(abe && srchstr(
+ (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fullname),
+ string))
+ ret_val |= MATCH_FULL;
+
+ default:
+ break;
+ }
+ break;
+
+ case 2:
+ switch(dl->type){
+ case Simple:
+ if(srchstr((abe && abe->tag == Single) ?
+ abe->addr.addr : NULL, string))
+ ret_val |= MATCH_ADDR;
+
+ break;
+
+ case ListEnt:
+ if(srchstr(
+ (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, listaddr), string))
+ ret_val |= MATCH_LISTMEM;
+
+ break;
+
+ case ListClickHere:
+ if(abe)
+ for(lm = abe->addr.list;
+ !(ret_val & MATCH_LISTMEM) && *lm; lm++)
+ if(srchstr(*lm, string))
+ ret_val |= MATCH_LISTMEM;
+
+ break;
+
+ case Empty:
+ case ListEmpty:
+ if(srchstr(EMPTY, string))
+ ret_val |= MATCH_BIGFIELD;
+
+ break;
+
+ case AddFirstPers:
+ if(srchstr(ADD_PERSONAL, string))
+ ret_val |= MATCH_BIGFIELD;
+
+ break;
+
+ case AddFirstGlob:
+ if(srchstr(ADD_GLOBAL, string))
+ ret_val |= MATCH_BIGFIELD;
+
+ break;
+
+ case NoAbooks:
+ if(srchstr(NOABOOKS, string))
+ ret_val |= MATCH_BIGFIELD;
+
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case 3: /* fcc */
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ if(abe && srchstr(abe->fcc, string))
+ ret_val |= MATCH_FCC;
+
+ default:
+ break;
+ }
+ break;
+
+ case 4: /* comment */
+ switch(dl->type){
+ case Simple:
+ case ListHead:
+ if(abe){
+ size_t n, len;
+ unsigned char *p, *tmp = NULL;
+
+ if((n = 4*strlen(abe->extra)) > SIZEOF_20KBUF-1){
+ len = n+1;
+ p = tmp = (unsigned char *)fs_get(len * sizeof(char));
+ }
+ else{
+ len = SIZEOF_20KBUF;
+ p = (unsigned char *)tmp_20k_buf;
+ }
+
+ if(srchstr((char *)rfc1522_decode_to_utf8(p, len, abe->extra),
+ string))
+ ret_val |= MATCH_COMMENT;
+
+ if(tmp)
+ fs_give((void **)&tmp);
+
+ }
+
+ default:
+ break;
+ }
+ break;
+ }
+ }
+
+ return(ret_val);
+}
+
+
+/*
+ * These chars in nicknames will mess up parsing.
+ *
+ * Returns 0 if ok, 1 if not.
+ * Returns an allocated error message on error.
+ */
+int
+nickname_check(char *nickname, char **error)
+{
+ register char *t;
+ char buf[100];
+
+ if((t = strindex(nickname, SPACE)) ||
+ (t = strindex(nickname, ',')) ||
+ (t = strindex(nickname, '"')) ||
+ (t = strindex(nickname, ';')) ||
+ (t = strindex(nickname, ':')) ||
+ (t = strindex(nickname, '@')) ||
+ (t = strindex(nickname, '(')) ||
+ (t = strindex(nickname, ')')) ||
+ (t = strindex(nickname, '\\')) ||
+ (t = strindex(nickname, '[')) ||
+ (t = strindex(nickname, ']')) ||
+ (t = strindex(nickname, '<')) ||
+ (t = strindex(nickname, '>'))){
+ char s[4];
+ s[0] = '"';
+ s[1] = *t;
+ s[2] = '"';
+ s[3] = '\0';
+ if(error){
+ /*
+ * TRANSLATORS: this is telling the user that one of the characters
+ * they have included in a nickname will not work. It will say something like
+ * Blank spaces not allowed in nicknames or
+ * Commas not...
+ * etc.
+ */
+ snprintf(buf, sizeof(buf), _("%s not allowed in nicknames"),
+ *t == SPACE ?
+ _("Blank spaces") :
+ *t == ',' ?
+ _("Commas") :
+ *t == '"' ?
+ _("Quotes") :
+ s);
+ buf[sizeof(buf)-1] = '\0';
+ *error = cpystr(buf);
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+char *
+abook_select_screen(struct pine *ps)
+{
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ OPT_SCREEN_S screen;
+ int adrbknum;
+ char *helptitle;
+ HelpType help;
+ PerAddrBook *pab;
+ char *abook = NULL;
+
+ helptitle = _("HELP FOR SELECTING AN ADDRESS BOOK");
+ help = h_role_abook_select;
+
+ init_ab_if_needed();
+
+ for(adrbknum = 0; adrbknum < as.n_addrbk; adrbknum++){
+ new_confline(&ctmp);
+ if(!first_line)
+ first_line = ctmp;
+
+ pab = &as.adrbks[adrbknum];
+
+ ctmp->value = cpystr((pab && pab->abnick)
+ ? pab->abnick
+ : (pab && pab->filename)
+ ? pab->filename
+ : "?");
+
+ ctmp->d.b.selected = &abook;
+ ctmp->d.b.abookname = ctmp->value;
+ ctmp->keymenu = &abook_select_km;
+ ctmp->help = help;
+ ctmp->help_title = helptitle;
+ ctmp->tool = abook_select_tool;
+ ctmp->flags = CF_STARTITEM;
+ ctmp->valoffset = 4;
+ }
+
+ if(first_line){
+ memset(&screen, 0, sizeof(screen));
+ (void) conf_scroll_screen(ps, &screen, first_line,
+ /* TRANSLATORS: Print something1 using something2.
+ abooks is something1 */
+ _("SELECT ADDRESS BOOK"), _("abooks"), 0);
+ }
+ else
+ q_status_message(SM_ORDER|SM_DING, 3, 3, _("No address books defined!"));
+
+ ps->mangled_screen = 1;
+ return(abook);
+}
+
+
+int
+abook_select_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int retval;
+
+ switch(cmd){
+ case MC_CHOICE :
+ *((*cl)->d.b.selected) = cpystr((*cl)->d.b.abookname);
+ retval = simple_exit_cmd(flags);
+ break;
+
+ case MC_EXIT :
+ retval = simple_exit_cmd(flags);
+ break;
+
+ default:
+ retval = -1;
+ break;
+ }
+
+ if(retval > 0)
+ ps->mangled_body = 1;
+
+ return(retval);
+}
+
+
+/*
+ * This isn't actually just a nickname completion anymore. The user types
+ * in a prefix of either a nickname, or a full address, or the addr@mailbox
+ * part of an address and we look in all of the address books for matches
+ * like that. We return the longest unambiguous match in answer.
+ *
+ * Args prefix -- The part of the "nickname" that has been typed so far
+ * answer -- The answer is returned here.
+ * tabtab -- If the answer returned from adrbk_list_of_completions is
+ * ambiguous and tabtab is set, then offer up a
+ * selector screen for all the possible answers.
+ * flags -- ANC_AFTERCOMMA -- This means that the passed in
+ * prefix may be a list of comma
+ * separated addresses and we're only
+ * completing the last one.
+ *
+ * Returns 0 -- no matches at all
+ * 1 -- more than one nickname (or address) begins with
+ * the answer being returned
+ * 2 -- the returned answer is a complete answer, either a
+ * nickname or a complete address, and there are
+ * no longer matches for the prefix
+ *
+ * Allocated answer is returned in answer argument.
+ * Caller needs to free the answer.
+ */
+int
+abook_nickname_complete(char *prefix, char **answer, int tabtab, unsigned flags)
+{
+ int ambiguity;
+ COMPLETE_S *completions, *cp;
+ char *saved_beginning = NULL;
+ char *potential_answer = NULL;
+
+ wp_exit = wp_nobail = 0;
+
+ /* there shouldn't be a case where answer is NULL */
+ if(answer)
+ *answer = NULL;
+
+ /*
+ * If we need to handle case where no prefix is passed in,
+ * figure that out when we need it.
+ */
+ if(!(prefix && prefix[0]))
+ return(0);
+
+ if(flags & ANC_AFTERCOMMA){
+ char *lastnick;
+
+ /*
+ * Find last comma, save the part before that, operate
+ * only on the last address.
+ */
+ if((lastnick = strrchr(prefix ? prefix : "", ',')) != NULL){
+ lastnick++;
+ while(!(*lastnick & 0x80) && isspace((unsigned char) (*lastnick)))
+ lastnick++;
+
+ saved_beginning = cpystr(prefix);
+ saved_beginning[lastnick-prefix] = '\0';
+ prefix = lastnick;
+ }
+ }
+
+ if(!(prefix && prefix[0]))
+ return(0);
+
+ completions = adrbk_list_of_completions(prefix,
+ ps_global->cur_uid_stream, ps_global->cur_uid,
+ ALC_INCLUDE_ADDRS | ((strlen(prefix) >= 3) ? ALC_INCLUDE_LDAP : 0));
+
+ if(!completions)
+ ambiguity = 0;
+ else if(completions && completions->next)
+ ambiguity = 1;
+ else
+ ambiguity = 2;
+
+ if(ambiguity == 2){
+ if(completions->full_address && completions->full_address[0])
+ potential_answer = cpystr(completions->full_address);
+ else if(completions->nickname && completions->nickname[0])
+ potential_answer = cpystr(completions->nickname);
+ else if(completions->addr && completions->addr[0])
+ potential_answer = cpystr(completions->addr);
+ else
+ potential_answer = cpystr(prefix);
+ }
+ /* answer is ambiguous and caller wants a choose list in that case */
+ else if(ambiguity == 1 && tabtab){
+ potential_answer = choose_an_address_with_this_prefix(completions);
+
+ if(potential_answer)
+ ambiguity = 2;
+ else{
+ ambiguity = 1;
+ potential_answer = cpystr(prefix);
+ }
+ }
+ else if(ambiguity == 1){
+ int k;
+ char cand1_kth_char, cand2_kth_char;
+ char unambig[1000];
+
+ /* find the longest unambiguous prefix */
+ strncpy(unambig, prefix, sizeof(unambig));
+ unambig[sizeof(unambig)-1] = '\0';
+ k = strlen(unambig);
+
+ /*
+ * First verify that they all match through prefix. LDAP sometimes gives
+ * weird, inexplicable answers that don't seem to match at all.
+ */
+ for(cp = completions; cp; cp = cp->next)
+ if(!( /* not a match */
+/* no NICK bit OR nickname matches prefix */
+ (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && !struncmp(unambig, cp->nickname, k)))
+/* AND no ADDR bit OR addr matches prefix */
+ && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && !struncmp(unambig, cp->addr, k)))
+/* AND neither FULL bit is set OR one of the two fulls matches prefix */
+ && (!(cp->matches_bitmap & (ALC_FULL | ALC_REVFULL)) || ((cp->matches_bitmap & ALC_FULL && cp->full_address && strlen(cp->full_address) >= k && !struncmp(unambig, cp->full_address, k)) || (cp->matches_bitmap & ALC_REVFULL && cp->rev_fullname && strlen(cp->rev_fullname) >= k && !struncmp(unambig, cp->rev_fullname, k))))
+ ))
+ break;
+
+ /* if cp that means there was not a universal match up through prefix, stop */
+ if(!cp)
+ do{
+ cand1_kth_char = cand2_kth_char = '\0';
+ if(completions->matches_bitmap & ALC_NICK && completions->nickname && strlen(completions->nickname) >= k)
+ cand1_kth_char = completions->nickname[k];
+ else if(completions->matches_bitmap & ALC_ADDR && completions->addr && strlen(completions->addr) >= k)
+ cand1_kth_char = completions->addr[k];
+ else{
+ if(completions->matches_bitmap & ALC_FULL && completions->full_address && strlen(completions->full_address) >= k)
+ cand1_kth_char = completions->full_address[k];
+
+ if(completions->matches_bitmap & ALC_REVFULL && completions->rev_fullname && strlen(completions->rev_fullname) >= k)
+ cand2_kth_char = completions->rev_fullname[k];
+ }
+
+ /*
+ * You'll want a wide screen to read this. There are two possible
+ * candidate chars for the next position. One or the other of them
+ * has to match in all of the possible completions. We consider it
+ * a match if either of the fullname completions for this entry is
+ * a match. That may not match what the user expects but it may.
+ */
+ for(cp = completions; cp; cp = cp->next){
+ if(!( /* candidate 1 is not a match */
+ /* candidate 1 is defined */
+ cand1_kth_char && cand1_kth_char != ','
+ /* AND no NICK bit OR nickname char is a match */
+ && (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && cp->nickname[k] == cand1_kth_char))
+ /* AND no ADDR bit OR addr char is a match */
+ && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && cp->addr[k] == cand1_kth_char))
+ /* AND neither FULL bit is set OR one of the two full chars is a match */
+ && (!(cp->matches_bitmap & (ALC_FULL | ALC_REVFULL)) || ((cp->matches_bitmap & ALC_FULL && cp->full_address && strlen(cp->full_address) >= k && cp->full_address[k] == cand1_kth_char) || (cp->matches_bitmap & ALC_REVFULL && cp->rev_fullname && strlen(cp->rev_fullname) >= k && cp->rev_fullname[k] == cand1_kth_char)))
+ ))
+ cand1_kth_char = '\0'; /* mark that it isn't a match */
+
+ if(!cand1_kth_char && !( /* cand1 is not a match AND cand2 is not a match */
+ /* candidate 2 is defined */
+ cand2_kth_char && cand2_kth_char != ','
+ /* AND no NICK bit OR nickname char is a match */
+ && (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && cp->nickname[k] == cand2_kth_char))
+ /* AND no ADDR bit OR addr char is a match */
+ && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && cp->addr[k] == cand2_kth_char))
+ /* AND neither FULL bit is set OR one of the two full chars is a match */
+ && (!(cp->matches_bitmap & (ALC_FULL | ALC_REVFULL)) || ((cp->matches_bitmap & ALC_FULL && cp->full_address && strlen(cp->full_address) >= k && cp->full_address[k] == cand2_kth_char) || (cp->matches_bitmap & ALC_REVFULL && cp->rev_fullname && strlen(cp->rev_fullname) >= k && cp->rev_fullname[k] == cand2_kth_char)))
+ ))
+ cand2_kth_char = '\0'; /* mark that it isn't a match */
+
+ if(!cand1_kth_char && !cand2_kth_char)
+ break; /* no match so break */
+ }
+
+ if(!cp) /* they all matched */
+ unambig[k++] = cand1_kth_char ? cand1_kth_char : cand2_kth_char;
+
+ }while(!cp && k < sizeof(unambig)-1);
+
+ unambig[k] = '\0';
+ unambig[sizeof(unambig)-1] = '\0';
+
+ /* don't return answer with trailing space */
+ while(--k >= 0 && isspace((unsigned char) unambig[k]))
+ unambig[k] = '\0';
+
+ potential_answer = cpystr(unambig);
+ }
+
+ if(completions)
+ free_complete_s(&completions);
+
+ if(answer && ambiguity != 0){
+ if(potential_answer){
+ if(saved_beginning){
+ size_t l1, l2;
+
+ l1 = strlen(saved_beginning);
+ l2 = strlen(potential_answer);
+ *answer = (char *) fs_get((l1+l2+1) * sizeof(char));
+ strncpy(*answer, saved_beginning, l1+l2);
+ strncpy(*answer+l1, potential_answer, l2);
+ (*answer)[l1+l2] = '\0';
+ }
+ else{
+ *answer = potential_answer;
+ potential_answer = NULL;
+ }
+ }
+ else{
+ /* this can't happen */
+ ambiguity = 0;
+ }
+ }
+
+ if(saved_beginning)
+ fs_give((void **) &saved_beginning);
+
+ if(potential_answer)
+ fs_give((void **) &potential_answer);
+
+ return(ambiguity);
+}
+
+
+/*
+ * Returns an allocated nickname choice from user that begins with
+ * prefix.
+ */
+char *
+choose_an_address_with_this_prefix(COMPLETE_S *completions)
+{
+ char buf[1000];
+ char *chosen_address = NULL;
+ char **lp, **da, **possible_addrs = NULL, **display_addrs = NULL;
+ COMPLETE_S *cp;
+ size_t cnt = 0;
+ int show_nick, show_revfull;
+
+ /*
+ * Count how many and allocate an array for choose_item_from_list().
+ */
+ for(cnt = 0, cp = completions; cp; cp = cp->next)
+ cnt++;
+
+ /*
+ * Copy completions into an array.
+ */
+ if(cnt > 0){
+ lp = possible_addrs = (char **) fs_get((cnt+1) * sizeof(*possible_addrs));
+ memset(possible_addrs, 0, (cnt+1) * sizeof(*possible_addrs));
+ da = display_addrs = (char **) fs_get((cnt+1) * sizeof(*display_addrs));
+ memset(display_addrs, 0, (cnt+1) * sizeof(*display_addrs));
+ for(cp = completions; cp; cp = cp->next){
+ show_nick = (cp->matches_bitmap & ALC_NICK) && cp->nickname && cp->nickname[0];
+ show_revfull = 0;
+ if(!show_nick && !(cp->matches_bitmap & (ALC_NICK | ALC_ADDR | ALC_FULL))
+ && (cp->matches_bitmap & ALC_REVFULL)
+ && cp->rev_fullname && cp->rev_fullname[0])
+ show_revfull = 1;
+
+ snprintf(buf, sizeof(buf), "%s%s%s%s%s",
+ cp->full_address ? cp->full_address : "?",
+ (show_nick || show_revfull) ? " (" : "",
+ show_nick ? cp->nickname : "",
+ show_revfull ? cp->rev_fullname : "",
+ (show_nick || show_revfull) ? ")" : "");
+ *da++ = cpystr(buf);
+ *lp++ = cpystr(cp->full_address ? cp->full_address : "?");
+ }
+ }
+
+ if(possible_addrs){
+ chosen_address = choose_item_from_list(possible_addrs, display_addrs,
+ _("SELECT AN ADDRESS"),
+ _("addresses"),
+ h_select_address_screen,
+ _("HELP FOR SELECTING AN ADDRESS"),
+ NULL);
+ free_list_array(&possible_addrs);
+ }
+
+ if(display_addrs)
+ free_list_array(&display_addrs);
+
+ return(chosen_address);
+}
+
+
+#ifdef _WINDOWS
+/*
+ * addr_scroll_up - adjust the global screen state struct such that pine's
+ * window on the data is shifted DOWN (i.e., the data's
+ * scrolled up).
+ */
+int
+addr_scroll_up(count)
+ long count;
+{
+ int next;
+
+ if(count < 0)
+ return(addr_scroll_down(-count));
+ else if(count){
+ long i;
+
+ i=count;
+ as.cur_row += as.top_ent;
+ while(i && as.top_ent + 1 < as.last_ent){
+ if(line_is_selectable(as.top_ent)){
+ if(next_selectable_line(as.top_ent,&next)){
+ as.cur_row = next;
+ i--;
+ as.top_ent++;
+ }
+ else i = 0;
+ }
+ else {
+ i--;
+ as.top_ent++;
+ }
+ }
+ as.cur_row = as.cur_row - as.top_ent; /* must always be positive */
+
+ as.old_cur_row = as.cur_row;
+ }
+
+ return(1);
+}
+
+
+/*
+ * addr_scroll_down - adjust the global screen state struct such that pine's
+ * window on the data is shifted UP (i.e., the data's
+ * scrolled down).
+ */
+int
+addr_scroll_down(count)
+ long count;
+{
+ if(count < 0)
+ return(addr_scroll_up(-count));
+ else if(count){
+ long i;
+
+ for(i = count; i && as.top_ent; i--, as.top_ent--)
+ as.cur_row++;
+
+ while (as.cur_row >= as.l_p_page){
+ prev_selectable_line(as.cur_row+as.top_ent, &as.cur_row);
+ as.cur_row = as.cur_row - as.top_ent;
+ }
+
+ as.old_cur_row = as.cur_row;
+ }
+
+ return(1);
+}
+
+
+/*
+ * addr_scroll_to_pos - scroll the address book data in pine's window such
+ * tthat the given "line" is at the top of the page.
+ */
+int
+addr_scroll_to_pos(line)
+ long line;
+{
+ return(addr_scroll_up(line - as.top_ent));
+}
+
+
+/*----------------------------------------------------------------------
+ MSWin scroll callback. Called during scroll message processing.
+
+
+
+ Args: cmd - what type of scroll operation.
+ scroll_pos - parameter for operation.
+ used as position for SCROLL_TO operation.
+
+ Returns: TRUE - did the scroll operation.
+ FALSE - was not able to do the scroll operation.
+ ----*/
+int
+addr_scroll_callback (cmd, scroll_pos)
+ int cmd;
+ long scroll_pos;
+{
+ int paint = TRUE;
+
+ switch (cmd) {
+ case MSWIN_KEY_SCROLLUPLINE:
+ paint = addr_scroll_down (scroll_pos);
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNLINE:
+ paint = addr_scroll_up (scroll_pos);
+ break;
+
+ case MSWIN_KEY_SCROLLUPPAGE:
+ paint = addr_scroll_down (as.l_p_page);
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNPAGE:
+ paint = addr_scroll_up (as.l_p_page);
+ break;
+
+ case MSWIN_KEY_SCROLLTO:
+ paint = addr_scroll_to_pos (scroll_pos);
+ break;
+ }
+
+ if(paint)
+ display_book(0, as.cur_row, -1, 1, (Pos *)NULL);
+
+ return(paint);
+}
+
+
+char *
+pcpine_help_addrbook(title)
+ char *title;
+{
+ /*
+ * Title is size 256. Fix this to pass the titlelen.
+ */
+ if(title)
+ strncpy(title, (as.config)
+ ? _("Alpine CONFIGURING ADDRESS BOOKS Help")
+ : _("Alpine ADDRESS_BOOK Help"), 256);
+
+ return(pcpine_help(gAbookHelp));
+}
+#endif /* _WINDOWS */
diff --git a/alpine/addrbook.h b/alpine/addrbook.h
new file mode 100644
index 00000000..d93ef12d
--- /dev/null
+++ b/alpine/addrbook.h
@@ -0,0 +1,69 @@
+/*
+ * $Id: addrbook.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_ADDRBOOK_INCLUDED
+#define PINE_ADDRBOOK_INCLUDED
+
+
+#include "../pith/adrbklib.h"
+#include "../pith/state.h"
+
+
+/*
+ * Flags to abook_nickname_complete().
+ * ANC_AFTERCOMMA means the passed in prefix
+ * looks like "stuff, stuff, prefix" and
+ * we are to peel off the stuff before the prefix,
+ * look for matches, then put the stuff back before
+ * returning the answer.
+ */
+#define ANC_AFTERCOMMA 0x1
+
+
+/* exported protoypes */
+int cur_is_open(void);
+void init_disp_form(PerAddrBook *, char **, int);
+void init_abook_screen(void);
+void save_and_restore(int, SAVE_STATE_S *);
+void save_state(SAVE_STATE_S *);
+void restore_state(SAVE_STATE_S *);
+AdrBk_Entry *ae(long);
+char *get_abook_display_line(long, int, char **, char **, int *, char *, size_t);
+void addr_book_screen(struct pine *);
+void addr_book_config(struct pine *, int);
+char *addr_book_oneaddr(void);
+char *addr_book_multaddr_nf(void);
+char *addr_book_oneaddr_nf(void);
+char *addr_book_compose(char **);
+char *addr_book_compose_lcc(char **);
+char *addr_book_change_list(char **);
+char *addr_book_bounce(void);
+char *addr_book_takeaddr(void);
+char *addr_book_nick_for_edit(char **);
+char *addr_book_selnick(void);
+char *abook_select_screen(struct pine *);
+int abook_nickname_complete(char *, char **, int, unsigned);
+int is_addr(long);
+long first_line(long);
+void ab_resize(void);
+int cur_addr_book(void);
+int resync_screen(PerAddrBook *, AddrBookArg, int);
+int nickname_check(char *, char **);
+int calculate_field_widths(void);
+void erase_selections(void);
+
+
+#endif /* PINE_ADDRBOOK_INCLUDED */
diff --git a/alpine/adrbkcmd.c b/alpine/adrbkcmd.c
new file mode 100644
index 00000000..9320160d
--- /dev/null
+++ b/alpine/adrbkcmd.c
@@ -0,0 +1,7694 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: adrbkcmd.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ adrbkcmds.c
+ Commands called from the addrbook screens.
+ ====*/
+
+
+#include "headers.h"
+#include "adrbkcmd.h"
+#include "addrbook.h"
+#include "takeaddr.h"
+#include "status.h"
+#include "keymenu.h"
+#include "mailview.h"
+#include "mailcmd.h"
+#include "mailindx.h"
+#include "radio.h"
+#include "folder.h"
+#include "reply.h"
+#include "help.h"
+#include "titlebar.h"
+#include "signal.h"
+#include "roleconf.h"
+#include "send.h"
+#include "../pith/adrbklib.h"
+#include "../pith/addrbook.h"
+#include "../pith/abdlc.h"
+#include "../pith/ablookup.h"
+#include "../pith/bldaddr.h"
+#include "../pith/ldap.h"
+#include "../pith/state.h"
+#include "../pith/filter.h"
+#include "../pith/conf.h"
+#include "../pith/msgno.h"
+#include "../pith/addrstring.h"
+#include "../pith/remote.h"
+#include "../pith/url.h"
+#include "../pith/util.h"
+#include "../pith/detoken.h"
+#include "../pith/stream.h"
+#include "../pith/send.h"
+#include "../pith/list.h"
+#include "../pith/busy.h"
+#include "../pith/icache.h"
+#include "../pith/osdep/color.h"
+
+
+/* internal prototypes */
+int url_hilite_abook(long, char *, LT_INS_S **, void *);
+int process_abook_view_cmd(int, MSGNO_S *, SCROLL_S *);
+int expand_addrs_for_pico(struct headerentry *, char ***);
+char *view_message_for_pico(char **);
+int verify_nick(char *, char **, char **, BUILDER_ARG *, int *);
+int verify_addr(char *, char **, char **, BUILDER_ARG *, int *);
+int pico_sendexit_for_adrbk(struct headerentry *, void(*)(void), int, char **);
+char *pico_cancelexit_for_adrbk(char *, void (*)(void));
+char *pico_cancel_for_adrbk_take(void (*)(void));
+char *pico_cancel_for_adrbk_edit(void (*)(void));
+int ab_modify_abook_list(int, int, int, char *, char *, char *);
+int convert_abook_to_remote(struct pine *, PerAddrBook *, char *, size_t, int);
+int any_rule_files_to_warn_about(struct pine *);
+int verify_folder_name(char *,char **,char **,BUILDER_ARG *, int *);
+int verify_server_name(char *,char **,char **,BUILDER_ARG *, int *);
+int verify_abook_nick(char *, char **,char **,BUILDER_ARG *, int *);
+int do_the_shuffle(int *, int, int, char **);
+void ab_compose_internal(BuildTo, int);
+int ab_export(struct pine *, long, int, int);
+VCARD_INFO_S *prepare_abe_for_vcard(struct pine *, AdrBk_Entry *, int);
+void write_single_tab_entry(gf_io_t, VCARD_INFO_S *);
+int percent_done_copying(void);
+int cmp_action_list(const qsort_t *, const qsort_t *);
+void set_act_list_member(ACTION_LIST_S *, a_c_arg_t, PerAddrBook *, PerAddrBook *, char *);
+void convert_pinerc_to_remote(struct pine *, char *);
+
+#ifdef ENABLE_LDAP
+typedef struct _saved_query {
+ char *qq,
+ *cn,
+ *sn,
+ *gn,
+ *mail,
+ *org,
+ *unit,
+ *country,
+ *state,
+ *locality,
+ *custom;
+} SAVED_QUERY_S;
+
+int process_ldap_cmd(int, MSGNO_S *, SCROLL_S *);
+int pico_simpleexit(struct headerentry *, void (*)(void), int, char **);
+char *pico_simplecancel(void (*)(void));
+void save_query_parameters(SAVED_QUERY_S *);
+SAVED_QUERY_S *copy_query_parameters(SAVED_QUERY_S *);
+void free_query_parameters(SAVED_QUERY_S **);
+int restore_query_parameters(struct headerentry *, char ***);
+
+static char *expander_address;
+#endif /* ENABLE_LDAP */
+
+static char *fakedomain = "@";
+
+
+#define VIEW_ABOOK_NONE 0
+#define VIEW_ABOOK_EDITED 1
+#define VIEW_ABOOK_WARPED 2
+
+/*
+ * View an addrbook entry.
+ * Call scrolltool to do the work.
+ */
+void
+view_abook_entry(struct pine *ps, long int cur_line)
+{
+ AdrBk_Entry *abe;
+ STORE_S *in_store, *out_store;
+ char *string, *errstr;
+ SCROLL_S sargs;
+ HANDLE_S *handles = NULL;
+ URL_HILITE_S uh;
+ gf_io_t pc, gc;
+ int cmd, abook_indent;
+ long offset = 0L;
+ char b[500];
+
+ dprint((5, "- view_abook_entry -\n"));
+
+ if(is_addr(cur_line)){
+ abe = ae(cur_line);
+ if(!abe){
+ q_status_message(SM_ORDER, 0, 3, _("Error reading entry"));
+ return;
+ }
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 3, _("Nothing to view"));
+ return;
+ }
+
+ if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
+ return;
+ }
+
+ abook_indent = utf8_width(_("Nickname")) + 2;
+
+ /* TRANSLATORS: Nickname is a shorthand name for something */
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
+ so_puts(in_store, b);
+ if(abe->nickname)
+ so_puts(in_store, abe->nickname);
+
+ so_puts(in_store, "\015\012");
+
+ /* TRANSLATORS: Full name is the name that goes with an email address.
+ For example, in
+ Fred Flintstone <fred@bedrock.org>
+ Fred Flintstone is the Full Name. */
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
+ so_puts(in_store, b);
+ if(abe->fullname)
+ so_puts(in_store, abe->fullname);
+
+ so_puts(in_store, "\015\012");
+
+ /* TRANSLATORS: Fcc is an abbreviation for File carbon copy. It is like
+ a cc which is a copy of a message that goes to somebody other than the
+ main recipient, only this is a copy of a message which is put into a
+ file on the user's computer. */
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
+ so_puts(in_store, b);
+ if(abe->fcc)
+ so_puts(in_store, abe->fcc);
+
+ so_puts(in_store, "\015\012");
+
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, AB_COMMENT_STR);
+ so_puts(in_store, b);
+ if(abe->extra)
+ so_puts(in_store, abe->extra);
+
+ so_puts(in_store, "\015\012");
+
+ /* TRANSLATORS: Addresses refers to email Addresses */
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ so_puts(in_store, b);
+ if(abe->tag == Single){
+ char *tmp = abe->addr.addr ? abe->addr.addr : "";
+#ifdef ENABLE_LDAP
+ if(!strncmp(tmp, QRUN_LDAP, LEN_QRL))
+ string = LDAP_DISP;
+ else
+#endif
+ string = tmp;
+
+ so_puts(in_store, string);
+ }
+ else{
+ char **ll;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ if(ll != abe->addr.list){
+ so_puts(in_store, "\015\012");
+ so_puts(in_store, repeat_char(abook_indent+2, SPACE));
+ }
+
+#ifdef ENABLE_LDAP
+ if(!strncmp(*ll, QRUN_LDAP, LEN_QRL))
+ string = LDAP_DISP;
+ else
+#endif
+ string = *ll;
+
+ so_puts(in_store, string);
+
+ if(*(ll+1)) /* not the last one */
+ so_puts(in_store, ",");
+ }
+ }
+
+ so_puts(in_store, "\015\012");
+
+ do{
+ if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ so_give(&in_store);
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space."));
+ return;
+ }
+
+ so_seek(in_store, 0L, 0);
+
+ init_handles(&handles);
+ gf_filter_init();
+
+ if(F_ON(F_VIEW_SEL_URL, ps_global)
+ || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
+ || F_ON(F_SCAN_ADDR, ps_global))
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(url_hilite_abook,
+ gf_url_hilite_opt(&uh,&handles,0)));
+
+ gf_link_filter(gf_wrap, gf_wrap_filter_opt(ps->ttyo->screen_cols - 4,
+ ps->ttyo->screen_cols,
+ NULL, abook_indent+2,
+ GFW_HANDLES));
+ gf_link_filter(gf_nvtnl_local, NULL);
+
+ gf_set_so_readc(&gc, in_store);
+ gf_set_so_writec(&pc, out_store);
+
+ if((errstr = gf_pipe(gc, pc)) != NULL){
+ so_give(&in_store);
+ so_give(&out_store);
+ free_handles(&handles);
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ /* TRANSLATORS: %s is the error message */
+ _("Can't format entry: %s"), errstr);
+ return;
+ }
+
+ gf_clear_so_writec(out_store);
+ gf_clear_so_readc(in_store);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(out_store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("expanded entry");
+ sargs.text.handles = handles;
+
+ if(offset){ /* resize? preserve paging! */
+ sargs.start.on = Offset;
+ sargs.start.loc.offset = offset;
+ offset = 0L;
+ }
+
+ /* TRANSLATORS: a screen title. We are viewing an address book */
+ sargs.bar.title = _("ADDRESS BOOK (View)");
+ sargs.bar.style = TextPercent;
+ sargs.proc.tool = process_abook_view_cmd;
+ sargs.proc.data.i = VIEW_ABOOK_NONE;
+ sargs.resize_exit = 1;
+ sargs.help.text = h_abook_view;
+ /* TRANSLATORS: help screen title */
+ sargs.help.title = _("HELP FOR ADDRESS BOOK VIEW");
+ sargs.keys.menu = &abook_view_keymenu;
+ setbitmap(sargs.keys.bitmap);
+
+ if(handles)
+ sargs.keys.menu->how_many = 2;
+ else{
+ sargs.keys.menu->how_many = 1;
+ clrbitn(OTHER_KEY, sargs.keys.bitmap);
+ }
+
+ if((cmd = scrolltool(&sargs)) == MC_RESIZE)
+ offset = sargs.start.loc.offset;
+
+ so_give(&out_store);
+ free_handles(&handles);
+ }
+ while(cmd == MC_RESIZE);
+
+ so_give(&in_store);
+
+ if(sargs.proc.data.i != VIEW_ABOOK_NONE){
+ long old_l_p_p, old_top_ent, old_cur_row;
+
+ if(sargs.proc.data.i == VIEW_ABOOK_WARPED){
+ /*
+ * Warped means we got plopped down somewhere in the display
+ * list so that we don't know where we are relative to where
+ * we were before we warped. The current line number will
+ * be zero, since that is what the warp would have set.
+ */
+ as.top_ent = first_line(0L - as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+ else if(sargs.proc.data.i == VIEW_ABOOK_EDITED){
+ old_l_p_p = as.l_p_page;
+ old_top_ent = as.top_ent;
+ old_cur_row = as.cur_row;
+ }
+
+ /* Window size may have changed while in pico. */
+ ab_resize();
+
+ /* fix up what ab_resize messed up */
+ if(sargs.proc.data.i != VIEW_ABOOK_WARPED && old_l_p_p == as.l_p_page){
+ as.top_ent = old_top_ent;
+ as.cur_row = old_cur_row;
+ as.old_cur_row = old_cur_row;
+ }
+
+ cur_line = as.top_ent+as.cur_row;
+ }
+
+ ps->mangled_screen = 1;
+}
+
+
+int
+url_hilite_abook(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ register char *lp;
+
+ if((lp = strchr(line, ':')) &&
+ !strncmp(line, AB_COMMENT_STR, strlen(AB_COMMENT_STR)))
+ (void) url_hilite(linenum, lp + 1, ins, local);
+
+ return(0);
+}
+
+
+int
+process_abook_view_cmd(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 1, i;
+ PerAddrBook *pab;
+ AddrScrn_Disp *dl;
+ static ESCKEY_S text_or_vcard[] = {
+ /* TRANSLATORS: Text refers to plain old text, probably the text of
+ an email message */
+ {'t', 't', "T", N_("Text")},
+ /* TRANSLATORS: A VCard is kind of like an electronic business card. It is not
+ something specific to alpine, it is universal. */
+ {'v', 'v', "V", N_("VCard")},
+ {-1, 0, NULL, NULL}};
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ switch(cmd){
+ case MC_EDIT :
+ /*
+ * MC_EDIT works a little differently from the other cmds here.
+ * The others return 0 to scrolltool so that we are still in
+ * the view screen. This one is different because we may have
+ * changed what we're viewing. We handle that by returning 1
+ * to scrolltool and setting the sparms opt union's gint
+ * to the value below.
+ *
+ * (Late breaking news. Now we're going to return 1 from all these
+ * commands and then in the caller we're going to bounce back out
+ * to the index view instead of the view of the individual entry.
+ * So there is some dead code around for now.)
+ *
+ * Then, in the view_abook_entry function we check the value
+ * of this field on scrolltool's return and if it is one of
+ * the two special values below view_abook_entry resets the
+ * current line if necessary, flushes the display cache (in
+ * ab_resize) and loops back and starts over, effectively
+ * putting us back in the view screen but with updated
+ * contents. A side effect is that the screen above that (the
+ * abook index) will also have been flushed and corrected by
+ * the ab_resize.
+ */
+ pab = &as.adrbks[cur_addr_book()];
+ if(pab && pab->access == ReadOnly){
+ /* TRANSLATORS: Address book can be viewed but not changed */
+ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
+ rv = 0;
+ break;
+ }
+
+ if(adrbk_check_all_validity_now()){
+ if(resync_screen(pab, AddrBookScreen, 0)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: The address book was changed by some other
+ process. The user is being told that their change (their
+ Update) has been canceled and they should try again. */
+ _("Address book changed. Update cancelled. Try again."));
+ ps_global->mangled_screen = 1;
+ break;
+ }
+ }
+
+ if(pab && pab->access == ReadOnly){
+ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
+ rv = 0;
+ break;
+ }
+
+ /*
+ * Arguments all come from globals, not from the arguments to
+ * process_abook_view_cmd. It would be a little cleaner if the
+ * information was contained in att, I suppose.
+ */
+ if(is_addr(as.cur_row+as.top_ent)){
+ AdrBk_Entry *abe, *abe_copy;
+ a_c_arg_t entry;
+ int warped = 0;
+
+ dl = dlist(as.top_ent+as.cur_row);
+ entry = dl->elnum;
+ abe = adrbk_get_ae(pab->address_book, entry);
+ abe_copy = copy_ae(abe);
+ dprint((9,"Calling edit_entry to edit from view\n"));
+ /* TRANSLATORS: update as in update an address book entry */
+ edit_entry(pab->address_book, abe_copy, entry,
+ abe->tag, 0, &warped, _("update"));
+ /*
+ * The ABOOK_EDITED case doesn't mean that we necessarily
+ * changed something, just that we might have but we know
+ * we didn't change the sort order (causing the warp).
+ */
+ sparms->proc.data.i = warped
+ ? VIEW_ABOOK_WARPED : VIEW_ABOOK_EDITED;
+
+ free_ae(&abe_copy);
+ rv = 1; /* causes scrolltool to return */
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 4,
+ "Something wrong, entry not updateable");
+ rv = 0;
+ break;
+ }
+
+ break;
+
+ case MC_SAVE :
+ pab = &as.adrbks[cur_addr_book()];
+ /*
+ * Arguments all come from globals, not from the arguments to
+ * process_abook_view_cmd. It would be a little cleaner if the
+ * information was contained in att, I suppose.
+ */
+ (void)ab_save(ps_global, pab ? pab->address_book : NULL,
+ as.top_ent+as.cur_row, -FOOTER_ROWS(ps_global), 0);
+ rv = 1;
+ break;
+
+ case MC_COMPOSE :
+ (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 0);
+ rv = 1;
+ break;
+
+ case MC_ROLE :
+ (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 1);
+ rv = 1;
+ break;
+
+ case MC_FORWARD :
+ rv = 1;
+ /* TRANSLATORS: A question with two choices for the answer. Forward
+ as text means to include the text of the message being forwarded
+ in the new message. Forward as a Vcard attachment means to
+ attach it to the message in a special format so it is recognizable
+ as a Vcard. */
+ i = radio_buttons(_("Forward as text or forward as Vcard attachment ? "),
+ -FOOTER_ROWS(ps_global), text_or_vcard, 't', 'x',
+ h_ab_text_or_vcard, RB_NORM);
+ switch(i){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book forward cancelled"));
+ rv = 0;
+ break;
+
+ case 't':
+ forward_text(ps_global, sparms->text.text, sparms->text.src);
+ break;
+
+ case 'v':
+ (void)ab_forward(ps_global, as.top_ent+as.cur_row, 0);
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3,
+ "can't happen in process_abook_view_cmd");
+ break;
+ }
+
+ break;
+
+ default:
+ panic("Unexpected command in process_abook_view_cmd");
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Give expanded view of this address entry.
+ * Call scrolltool to do the work.
+ *
+ * Args: headents -- The headerentry array from pico.
+ * s -- Unused here.
+ *
+ * Returns -- Always 0.
+ */
+int
+expand_addrs_for_pico(struct headerentry *headents, char ***s)
+{
+ BuildTo bldto;
+ STORE_S *store;
+ char *error = NULL, *addr = NULL, *fullname = NULL, *address = NULL;
+ SAVE_STATE_S state;
+ ADDRESS *adrlist = NULL, *a;
+ int j, address_index = -1, fullname_index = -1, no_a_fld = 0;
+ void (*redraw)(void) = ps_global->redrawer;
+
+ char *tmp, *tmp2, *tmp3;
+ SCROLL_S sargs;
+ AdrBk_Entry abe;
+ char fakeaddrpmt[500];
+ int abook_indent;
+
+ dprint((5, "- expand_addrs_for_pico -\n"));
+
+ abook_indent = utf8_width(_("Nickname")) + 2;
+ utf8_snprintf(fakeaddrpmt, sizeof(fakeaddrpmt), "%-*.*w: ", abook_indent, abook_indent, _("Address"));
+
+ if(s)
+ *s = NULL;
+
+ ps_global->redrawer = NULL;
+ fix_windsize(ps_global);
+
+ ab_nesting_level++;
+ save_state(&state);
+
+ for(j=0;
+ headents[j].name != NULL && (address_index < 0 || fullname_index < 0);
+ j++){
+ if(!strncmp(headents[j].name, "Address", 7) || !strncmp(headents[j].name, _("Address"), strlen(_("Address"))))
+ address_index = j;
+ else if(!strncmp(headents[j].name, "Fullname", 8) || !strncmp(headents[j].name, _("Fullname"), strlen(_("Fullname"))))
+ fullname_index = j;
+ }
+
+ if(address_index >= 0)
+ address = *headents[address_index].realaddr;
+ else{
+ address_index = 1000; /* a big number */
+ no_a_fld++;
+ }
+
+ if(fullname_index >= 0)
+ fullname = adrbk_formatname(*headents[fullname_index].realaddr,
+ NULL, NULL);
+
+ memset(&abe, 0, sizeof(abe));
+ if(fullname)
+ abe.fullname = cpystr(fullname);
+
+ if(address){
+ char *tmp_a_string;
+
+ tmp_a_string = cpystr(address);
+ rfc822_parse_adrlist(&adrlist, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+ if(adrlist && adrlist->next){
+ int cnt = 0;
+
+ abe.tag = List;
+ for(a = adrlist; a; a = a->next)
+ cnt++;
+
+ abe.addr.list = (char **)fs_get((cnt+1) * sizeof(char *));
+ cnt = 0;
+ for(a = adrlist; a; a = a->next){
+ if(a->host && a->host[0] == '@')
+ abe.addr.list[cnt++] = cpystr(a->mailbox);
+ else if(a->host && a->host[0] && a->mailbox && a->mailbox[0])
+ abe.addr.list[cnt++] =
+ cpystr(simple_addr_string(a, tmp_20k_buf,
+ SIZEOF_20KBUF));
+ }
+
+ abe.addr.list[cnt] = '\0';
+ }
+ else{
+ abe.tag = Single;
+ abe.addr.addr = address;
+ }
+
+ if(adrlist)
+ mail_free_address(&adrlist);
+
+ bldto.type = Abe;
+ bldto.arg.abe = &abe;
+ our_build_address(bldto, &addr, &error, NULL, NULL);
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ if(addr){
+ tmp_a_string = cpystr(addr);
+ rfc822_parse_adrlist(&adrlist, tmp_a_string, ps_global->maildomain);
+ fs_give((void **)&tmp_a_string);
+ }
+ }
+#ifdef ENABLE_LDAP
+ else if(no_a_fld && expander_address){
+ WP_ERR_S wp_err;
+
+ memset(&wp_err, 0, sizeof(wp_err));
+ adrlist = wp_lookups(expander_address, &wp_err, 0);
+ if(fullname && *fullname){
+ if(adrlist){
+ if(adrlist->personal)
+ fs_give((void **)&adrlist->personal);
+
+ adrlist->personal = cpystr(fullname);
+ }
+ }
+
+ if(wp_err.error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", wp_err.error);
+ fs_give((void **)&wp_err.error);
+ }
+ }
+#endif
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
+ restore_state(&state);
+ return(0);
+ }
+
+ for(j = 0; j < address_index && headents[j].name != NULL; j++){
+ if((tmp = fold(*headents[j].realaddr,
+ ps_global->ttyo->screen_cols,
+ ps_global->ttyo->screen_cols,
+ headents[j].prompt,
+ repeat_char(headents[j].prwid, SPACE), FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+
+ /*
+ * There is an assumption that Addresses is the last field.
+ */
+ tmp3 = cpystr(repeat_char(headents[0].prwid + 2, SPACE));
+ if(adrlist){
+ for(a = adrlist; a; a = a->next){
+ ADDRESS *next_addr;
+ char *bufp;
+ size_t len;
+
+ next_addr = a->next;
+ a->next = NULL;
+ len = est_size(a);
+ bufp = (char *) fs_get(len * sizeof(char));
+ (void) addr_string(a, bufp, len);
+ a->next = next_addr;
+
+ /*
+ * Another assumption, all the prwids are the same.
+ */
+ if((tmp = fold(bufp,
+ ps_global->ttyo->screen_cols,
+ ps_global->ttyo->screen_cols,
+ (a == adrlist) ? (no_a_fld
+ ? fakeaddrpmt
+ : headents[address_index].prompt)
+ : tmp3+2,
+ tmp3, FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **) &tmp);
+ }
+
+ fs_give((void **) &bufp);
+ }
+ }
+ else{
+ tmp2 = NULL;
+ if((tmp = fold(tmp2=cpystr("<none>"),
+ ps_global->ttyo->screen_cols,
+ ps_global->ttyo->screen_cols,
+ no_a_fld ? fakeaddrpmt
+ : headents[address_index].prompt,
+ tmp3, FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **) &tmp);
+ }
+
+ if(tmp2)
+ fs_give((void **)&tmp2);
+ }
+
+ if(tmp3)
+ fs_give((void **)&tmp3);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("expanded entry");
+ /* TRANSLATORS: screen title for viewing an address book entry in Rich
+ view mode. Rich just means it is expanded to include everything. */
+ sargs.bar.title = _("ADDRESS BOOK (Rich View)");
+ sargs.bar.style = TextPercent;
+ sargs.keys.menu = &abook_text_km;
+ setbitmap(sargs.keys.bitmap);
+
+ scrolltool(&sargs);
+ so_give(&store);
+
+ restore_state(&state);
+ ab_nesting_level--;
+
+ if(addr)
+ fs_give((void **)&addr);
+
+ if(adrlist)
+ mail_free_address(&adrlist);
+
+ if(fullname)
+ fs_give((void **)&fullname);
+
+ if(abe.fullname)
+ fs_give((void **)&abe.fullname);
+
+ if(abe.tag == List && abe.addr.list)
+ free_list_array(&(abe.addr.list));
+
+ ps_global->redrawer = redraw;
+
+ return(0);
+}
+
+
+/*
+ * Callback from TakeAddr editing screen to see message that was being
+ * viewed. Call scrolltool to do the work.
+ */
+char *
+view_message_for_pico(char **error)
+{
+ STORE_S *store;
+ gf_io_t pc;
+ void (*redraw)(void) = ps_global->redrawer;
+ SourceType src = CharStar;
+ SCROLL_S sargs;
+
+ dprint((5, "- view_message_for_pico -\n"));
+
+ ps_global->redrawer = NULL;
+ fix_windsize(ps_global);
+
+#ifdef DOS
+ src = TmpFileStar;
+#endif
+
+ if(!(store = so_get(src, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
+ return(NULL);
+ }
+
+ gf_set_so_writec(&pc, store);
+
+ format_message(msgno_for_pico_callback, env_for_pico_callback,
+ body_for_pico_callback, NULL, FM_NEW_MESS | FM_DISPLAY, pc);
+
+ gf_clear_so_writec(store);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = src;
+ sargs.text.desc = _("expanded entry");
+ /* TRANSLATORS: this is a screen title */
+ sargs.bar.title = _("MESSAGE TEXT");
+ sargs.bar.style = TextPercent;
+ sargs.keys.menu = &abook_text_km;
+ setbitmap(sargs.keys.bitmap);
+
+ scrolltool(&sargs);
+
+ so_give(&store);
+
+ ps_global->redrawer = redraw;
+
+ return(NULL);
+}
+
+
+/*
+prompt::name::help::prwid::maxlen::realaddr::
+builder::affected_entry::next_affected::selector::key_label::fileedit::nickcmpl
+display_it::break_on_comma::is_attach::rich_header::only_file_chars::
+single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
+*/
+static struct headerentry headents_for_edit[]={
+ {"Nickname : ", N_("Nickname"), h_composer_abook_nick, 12, 0, NULL,
+ /* TRANSLATORS: To AddrBk is a command that takes the user to
+ the address book screen to select an entry from there. */
+ verify_nick, NULL, NULL, addr_book_nick_for_edit, N_("To AddrBk"), NULL, abook_nickname_complete,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Fullname : ", N_("Fullname"), h_composer_abook_full, 12, 0, NULL,
+ NULL, NULL, NULL, view_message_for_pico, N_("To Message"), NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ /* TRANSLATORS: a File Copy is a copy of a sent message saved in a regular
+ file on the computer's disk */
+ {"Fcc : ", N_("FileCopy"), h_composer_abook_fcc, 12, 0, NULL,
+ /* TRANSLATORS: To Folders */
+ NULL, NULL, NULL, folders_for_fcc, N_("To Fldrs"), NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Comment : ", N_("Comment"), h_composer_abook_comment, 12, 0, NULL,
+ NULL, NULL, NULL, view_message_for_pico, N_("To Message"), NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Addresses : ", N_("Addresses"), h_composer_abook_addrs, 12, 0, NULL,
+ verify_addr, NULL, NULL, addr_book_change_list, N_("To AddrBk"), NULL, abook_nickname_complete,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
+};
+#define NNN_NICK 0
+#define NNN_FULL 1
+#define NNN_FCC 2
+#define NNN_COMMENT 3
+#define NNN_ADDR 4
+#define NNN_END 5
+
+static char *nick_saved_for_pico_check;
+static AdrBk *abook_saved_for_pico_check;
+
+/*
+ * Args: abook -- Address book handle
+ * abe -- AdrBk_Entry of old entry to work on. If NULL, this will
+ * be a new entry. This has to be a pointer to a copy of
+ * an abe that won't go away until we finish this function.
+ * In other words, don't pass in a pointer to an abe in
+ * the cache, copy it first. The tag on this abe is only
+ * used to decide whether to read abe->addr.list or
+ * abe->addr.addr, not to determine what the final result
+ * will be. That's determined solely by how many addresses
+ * there are after the user edits.
+ * entry -- The entry number of the old entry that we will be changing.
+ * old_tag -- If we're changing an old entry, then this is the tag of
+ * that old entry.
+ * readonly -- Call pico with readonly flag
+ * warped -- We warped to a new part of the addrbook
+ * (We also overload warped in a couple places and use it's
+ * being set as an indicator of whether we are Taking or
+ * not. It will be NULL if we are Taking.)
+ */
+void
+edit_entry(AdrBk *abook, AdrBk_Entry *abe, a_c_arg_t entry, Tag old_tag, int readonly, int *warped, char *cmd)
+{
+ AdrBk_Entry local_abe;
+ struct headerentry *he;
+ PICO pbf;
+ STORE_S *msgso;
+ adrbk_cntr_t old_entry_num, new_entry_num = NO_NEXT;
+ int rc = 0, resort_happened = 0, list_changed = 0, which_addrbook;
+ int editor_result, i = 0, add, n_end, ldap = 0;
+ char *nick, *full, *fcc, *comment, *fname, *pp;
+ char **orig_addrarray = NULL;
+ char **new_addrarray = NULL;
+ char *comma_sep_addr = NULL;
+ char **p, **q;
+ Tag new_tag;
+ char titlebar[40];
+ char nickpmt[100], fullpmt[100], fccpmt[100], cmtpmt[100], addrpmt[100];
+ int abook_indent;
+ long length;
+ SAVE_STATE_S state; /* For saving state of addrbooks temporarily */
+
+ dprint((2, "- edit_entry -\n"));
+
+ old_entry_num = (adrbk_cntr_t) entry;
+ save_state(&state);
+ abook_saved_for_pico_check = abook;
+
+ add = (abe == NULL); /* doing add or change? */
+ if(add){
+ local_abe.nickname = "";
+ local_abe.fullname = "";
+ local_abe.fcc = "";
+ local_abe.extra = "";
+ local_abe.addr.addr = "";
+ local_abe.tag = NotSet;
+ abe = &local_abe;
+ old_entry_num = NO_NEXT;
+ }
+
+ new_tag = abe->tag;
+
+#ifdef ENABLE_LDAP
+ expander_address = NULL;
+ if(abe->tag == Single &&
+ abe->addr.addr &&
+ !strncmp(abe->addr.addr,QRUN_LDAP,LEN_QRL)){
+ ldap = 1;
+ expander_address = cpystr(abe->addr.addr);
+ removing_double_quotes(expander_address);
+ }
+ else if(abe->tag == List &&
+ abe->addr.list &&
+ abe->addr.list[0] &&
+ !abe->addr.list[1] &&
+ !strncmp(abe->addr.list[0],QRUN_LDAP,LEN_QRL)){
+ ldap = 2;
+ expander_address = cpystr(abe->addr.list[0]);
+ removing_double_quotes(expander_address);
+ }
+#endif
+
+ standard_picobuf_setup(&pbf);
+ pbf.exittest = pico_sendexit_for_adrbk;
+ pbf.canceltest = warped ? pico_cancel_for_adrbk_edit
+ : pico_cancel_for_adrbk_take;
+ pbf.expander = expand_addrs_for_pico;
+ pbf.ctrlr_label = _("RichView");
+ /* xgettext: c-format */
+ if(readonly)
+ /* TRANSLATORS: screen titles */
+ snprintf(titlebar, sizeof(titlebar), _("ADDRESS BOOK (View)"));
+ else
+ snprintf(titlebar, sizeof(titlebar), _("ADDRESS BOOK (%c%s)"),
+ islower((unsigned char)(*cmd))
+ ? toupper((unsigned char)*cmd)
+ : *cmd, (cmd+1));
+
+ pbf.pine_anchor = set_titlebar(titlebar,
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+ pbf.pine_flags |= P_NOBODY;
+ if(readonly)
+ pbf.pine_flags |= P_VIEW;
+
+ /* An informational message */
+ if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ pbf.msgtext = (void *)so_text(msgso);
+ /*
+ * It's nice if we can make it so these lines make sense even if
+ * they don't all make it on the screen, because the user can't
+ * scroll down to see them. So just make each line a whole sentence
+ * that doesn't need the others below it to make sense.
+ */
+ if(add){
+ so_puts(msgso,
+/*
+ * TRANSLATORS: The following lines go together to form a screen of
+ * explanation about how to edit an address book entry.
+ */
+_("\n Fill in the fields. It is ok to leave fields blank."));
+ so_puts(msgso,
+_("\n To form a list, just enter multiple comma-separated addresses."));
+ }
+ else{
+ so_puts(msgso,
+/* TRANSLATORS: Same here, but a different version of the screen. */
+_("\n Change any of the fields. It is ok to leave fields blank."));
+ if(ldap)
+ so_puts(msgso,
+_("\n Since this entry does a directory lookup you may not edit the address field."));
+ else
+ so_puts(msgso,
+_("\n Additional comma-separated addresses may be entered in the address field."));
+ }
+
+ so_puts(msgso,
+_("\n Press \"^X\" to save the entry, \"^C\" to cancel, \"^G\" for help."));
+ so_puts(msgso,
+_("\n If you want to use quotation marks inside the Fullname field, it is best"));
+ so_puts(msgso,
+_("\n to use single quotation marks; for example: George 'Husky' Washington."));
+ }
+
+ he = (struct headerentry *) fs_get((NNN_END+1) * sizeof(struct headerentry));
+ memset((void *)he, 0, (NNN_END+1) * sizeof(struct headerentry));
+ pbf.headents = he;
+
+ abook_indent = utf8_width(_("Nickname")) + 2;
+
+ /* make a copy of each field */
+ nick = cpystr(abe->nickname ? abe->nickname : "");
+ removing_leading_and_trailing_white_space(nick);
+ nick_saved_for_pico_check = cpystr(nick);
+ he[NNN_NICK] = headents_for_edit[NNN_NICK];
+ he[NNN_NICK].realaddr = &nick;
+ utf8_snprintf(nickpmt, sizeof(nickpmt), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
+ he[NNN_NICK].prompt = nickpmt;
+ he[NNN_NICK].prwid = abook_indent+2;
+ if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global))
+ he[NNN_NICK].nickcmpl = NULL;
+
+ full = cpystr(abe->fullname ? abe->fullname : "");
+ removing_leading_and_trailing_white_space(full);
+ he[NNN_FULL] = headents_for_edit[NNN_FULL];
+ he[NNN_FULL].realaddr = &full;
+ utf8_snprintf(fullpmt, sizeof(fullpmt), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
+ he[NNN_FULL].prompt = fullpmt;
+ he[NNN_FULL].prwid = abook_indent+2;
+
+ fcc = cpystr(abe->fcc ? abe->fcc : "");
+ removing_leading_and_trailing_white_space(fcc);
+ he[NNN_FCC] = headents_for_edit[NNN_FCC];
+ he[NNN_FCC].realaddr = &fcc;
+ utf8_snprintf(fccpmt, sizeof(fccpmt), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
+ he[NNN_FCC].prompt = fccpmt;
+ he[NNN_FCC].prwid = abook_indent+2;
+
+ comment = cpystr(abe->extra ? abe->extra : "");
+ removing_leading_and_trailing_white_space(comment);
+ he[NNN_COMMENT] = headents_for_edit[NNN_COMMENT];
+ he[NNN_COMMENT].realaddr = &comment;
+ utf8_snprintf(cmtpmt, sizeof(cmtpmt), "%-*.*w: ", abook_indent, abook_indent, _("Comment"));
+ he[NNN_COMMENT].prompt = cmtpmt;
+ he[NNN_COMMENT].prwid = abook_indent+2;
+
+ n_end = NNN_END;
+ if(ldap)
+ n_end--;
+
+ if(!ldap){
+ he[NNN_ADDR] = headents_for_edit[NNN_ADDR];
+ he[NNN_ADDR].realaddr = &comma_sep_addr;
+ utf8_snprintf(addrpmt, sizeof(addrpmt), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ he[NNN_ADDR].prompt = addrpmt;
+ he[NNN_ADDR].prwid = abook_indent+2;
+ if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global))
+ he[NNN_NICK].nickcmpl = NULL;
+
+ if(abe->tag == Single){
+ if(abe->addr.addr){
+ orig_addrarray = (char **) fs_get(2 * sizeof(char *));
+ orig_addrarray[0] = cpystr(abe->addr.addr);
+ orig_addrarray[1] = NULL;
+ }
+ }
+ else if(abe->tag == List){
+ if(listmem_count_from_abe(abe) > 0){
+ orig_addrarray = (char **) fs_get(
+ (size_t)(listmem_count_from_abe(abe) + 1)
+ * sizeof(char *));
+ for(q = orig_addrarray, p = abe->addr.list; p && *p; p++, q++)
+ *q = cpystr(*p);
+
+ *q = NULL;
+ }
+ }
+
+ /* figure out how large a string we need to allocate */
+ length = 0L;
+ for(p = orig_addrarray; p && *p; p++)
+ length += (strlen(*p) + 2);
+
+ if(length)
+ length -= 2L;
+
+ pp = comma_sep_addr = (char *) fs_get((size_t)(length+1L) * sizeof(char));
+ *pp = '\0';
+ for(p = orig_addrarray; p && *p; p++){
+ sstrncpy(&pp, *p, length-(pp-comma_sep_addr));
+ if(*(p+1))
+ sstrncpy(&pp, ", ", length-(pp-comma_sep_addr));
+ }
+
+ comma_sep_addr[length] = '\0';
+
+ if(verify_addr(comma_sep_addr, NULL, NULL, NULL, NULL) < 0)
+ he[NNN_ADDR].start_here = 1;
+ }
+
+ he[n_end] = headents_for_edit[NNN_END];
+ for(i = 0; i < n_end; i++){
+ /* no callbacks in some cases */
+ if(readonly || ((i == NNN_FULL || i == NNN_COMMENT) && !env_for_pico_callback)){
+ he[i].selector = NULL;
+ he[i].key_label = NULL;
+ }
+
+ /* no builders for readonly */
+ if(readonly)
+ he[i].builder = NULL;
+ }
+
+ /* pass to pico and let user change them */
+ editor_result = pico(&pbf);
+ ps_global->mangled_screen = 1;
+ standard_picobuf_teardown(&pbf);
+
+ if(editor_result & COMP_GOTHUP)
+ hup_signal();
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+
+ if(editor_result & COMP_CANCEL){
+ if(!readonly)
+ /* TRANSLATOR: Something like
+ Address book save cancelled */
+ q_status_message1(SM_INFO, 0, 2, _("Address book %s cancelled"), cmd);
+ }
+ else if(editor_result & COMP_EXIT){
+ if(pico_usingcolor())
+ clear_index_cache(ps_global->mail_stream, 0);
+ removing_leading_and_trailing_white_space(nick);
+ removing_leading_and_trailing_white_space(full);
+ removing_leading_and_trailing_white_space(fcc);
+ removing_leading_and_trailing_white_space(comment);
+ removing_leading_and_trailing_white_space(comma_sep_addr);
+
+ /* not needed if pico is returning UTF-8 */
+ convert_possibly_encoded_str_to_utf8(&nick);
+ convert_possibly_encoded_str_to_utf8(&full);
+ convert_possibly_encoded_str_to_utf8(&fcc);
+ convert_possibly_encoded_str_to_utf8(&comment);
+ convert_possibly_encoded_str_to_utf8(&comma_sep_addr);
+
+ /* don't allow adding null entry */
+ if(add && !*nick && !*full && !*fcc && !*comment && !*comma_sep_addr)
+ goto outtahere;
+
+ /*
+ * comma_sep_addr is now the string which has been edited
+ */
+ if(comma_sep_addr)
+ new_addrarray = parse_addrlist(comma_sep_addr);
+
+ if(!ldap && (!new_addrarray || !new_addrarray[0]))
+ q_status_message(SM_ORDER, 3, 5, _("Warning: entry has no addresses"));
+
+ if(!new_addrarray || !new_addrarray[0] || !new_addrarray[1])
+ new_tag = Single; /* one or zero addresses means its a Single */
+ else
+ new_tag = List; /* more than one addresses means its a List */
+
+ if(new_tag == List && old_tag == List){
+ /*
+ * If Taking, make sure we write it even if user didn't edit
+ * it any further.
+ */
+ if(!warped)
+ list_changed++;
+ else if(he[NNN_ADDR].dirty)
+ for(q = orig_addrarray, p = new_addrarray; p && *p && q && *q; p++, q++)
+ if(strcmp(*p, *q) != 0){
+ list_changed++;
+ break;
+ }
+
+ if(!list_changed && he[NNN_ADDR].dirty
+ && ((!(p && *p) && (q && *q)) || ((p && *p) && !(q && *q))))
+ list_changed++;
+
+ if(list_changed){
+ /*
+ * need to delete old list members and add new members below
+ */
+ rc = adrbk_listdel_all(abook, (a_c_arg_t) old_entry_num);
+ }
+ else{
+ /* don't need new_addrarray */
+ free_list_array(&new_addrarray);
+ }
+
+ if(comma_sep_addr)
+ fs_give((void **) &comma_sep_addr);
+ }
+ else if((new_tag == List && old_tag == Single)
+ || (new_tag == Single && old_tag == List)){
+ /* delete old entry */
+ rc = adrbk_delete(abook, (a_c_arg_t) old_entry_num, 0, 0, 0, 0);
+ old_entry_num = NO_NEXT;
+ if(comma_sep_addr && new_tag == List)
+ fs_give((void **) &comma_sep_addr);
+ }
+
+ /*
+ * This will be an edit in the cases where the tag didn't change
+ * and an add in the cases where it did.
+ */
+ if(rc == 0)
+ rc = adrbk_add(abook,
+ (a_c_arg_t)old_entry_num,
+ nick,
+ he[NNN_FULL].dirty ? full : abe->fullname,
+ new_tag == Single ? (ldap == 1 ? abe->addr.addr :
+ ldap == 2 ? abe->addr.list[0] :
+ comma_sep_addr)
+ : NULL,
+ fcc,
+ he[NNN_COMMENT].dirty ? comment : abe->extra,
+ new_tag,
+ &new_entry_num,
+ &resort_happened,
+ 1,
+ 0,
+ (new_tag != List || !new_addrarray));
+ }
+
+ if(rc == 0 && new_tag == List && new_addrarray)
+ rc = adrbk_nlistadd(abook, (a_c_arg_t) new_entry_num, &new_entry_num,
+ &resort_happened, new_addrarray, 1, 0, 1);
+
+ restore_state(&state);
+
+ if(rc == -2 || rc == -3){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Error updating address book: %s"),
+ rc == -2 ? error_description(errno) : "Alpine bug");
+ }
+ else if(rc == 0
+ && strucmp(nick, nick_saved_for_pico_check) != 0
+ && (editor_result & COMP_EXIT)){
+ int added_to;
+
+ for(added_to = 0; added_to < as.n_addrbk; added_to++)
+ if(abook_saved_for_pico_check == as.adrbks[added_to].address_book)
+ break;
+
+ if(added_to >= as.n_addrbk)
+ added_to = -1;
+
+ fname = addr_lookup(nick, &which_addrbook, added_to);
+ if(fname){
+ q_status_message4(SM_ORDER, 5, 9,
+ /* TRANSLATORS: The first %s is the nickname the user is
+ trying to use that exists in another address book.
+ The second %s is the name of the other address book.
+ The third %s is " as " because it will say something
+ like also exists in <name> as <description>. */
+ _("Warning! Nickname %s also exists in \"%s\"%s%s"),
+ nick, as.adrbks[which_addrbook].abnick,
+ (fname && *fname) ? _(" as ") : "",
+ (fname && *fname) ? fname : "");
+ fs_give((void **)&fname);
+ }
+ }
+
+ if(resort_happened || list_changed){
+ DL_CACHE_S dlc_restart;
+
+ dlc_restart.adrbk_num = as.cur;
+ dlc_restart.dlcelnum = new_entry_num;
+ switch(new_tag){
+ case Single:
+ dlc_restart.type = DlcSimple;
+ break;
+
+ case List:
+ dlc_restart.type = DlcListHead;
+ break;
+
+ default:
+ break;
+ }
+
+ warp_to_dlc(&dlc_restart, 0L);
+ if(warped)
+ *warped = 1;
+ }
+
+outtahere:
+ if(he)
+ free_headents(&he);
+
+ if(msgso)
+ so_give(&msgso);
+
+ if(nick)
+ fs_give((void **)&nick);
+ if(full)
+ fs_give((void **)&full);
+ if(fcc)
+ fs_give((void **)&fcc);
+ if(comment)
+ fs_give((void **)&comment);
+
+ if(comma_sep_addr)
+ fs_give((void **)&comma_sep_addr);
+ if(nick_saved_for_pico_check)
+ fs_give((void **)&nick_saved_for_pico_check);
+
+ free_list_array(&orig_addrarray);
+ free_list_array(&new_addrarray);
+#ifdef ENABLE_LDAP
+ if(expander_address)
+ fs_give((void **) &expander_address);
+#endif
+}
+
+
+/*ARGSUSED*/
+int
+verify_nick(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ char *tmp;
+
+ dprint((7, "- verify_nick - (%s)\n", given ? given : "nul"));
+
+ tmp = cpystr(given);
+ removing_leading_and_trailing_white_space(tmp);
+
+ if(nickname_check(tmp, error)){
+ fs_give((void **)&tmp);
+ if(mangled){
+ if(ps_global->mangled_screen)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ else if(ps_global->mangled_footer)
+ *mangled |= BUILDER_FOOTER_MANGLED;
+ }
+
+ return -2;
+ }
+
+ if(ps_global->remote_abook_validity > 0 &&
+ adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+
+ ab_nesting_level++;
+ if(strucmp(tmp, nick_saved_for_pico_check) != 0
+ && adrbk_lookup_by_nick(abook_saved_for_pico_check,
+ tmp, (adrbk_cntr_t *)NULL)){
+ if(error){
+ char buf[MAX_NICKNAME + 80];
+
+ /* TRANSLATORS: The %s is the nickname of an entry that is already
+ in the address book */
+ snprintf(buf, sizeof(buf), _("\"%s\" already in address book."), tmp);
+ buf[sizeof(buf)-1] = '\0';
+ *error = cpystr(buf);
+ }
+
+ ab_nesting_level--;
+ fs_give((void **)&tmp);
+ if(mangled){
+ if(ps_global->mangled_screen)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ else if(ps_global->mangled_footer)
+ *mangled |= BUILDER_FOOTER_MANGLED;
+ }
+
+ return -2;
+ }
+
+ ab_nesting_level--;
+ if(expanded)
+ *expanded = tmp;
+ else
+ fs_give((void **)&tmp);
+
+ /* This is so pico will erase any old message */
+ if(error)
+ *error = cpystr("");
+
+ if(mangled){
+ if(ps_global->mangled_screen)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ else if(ps_global->mangled_footer)
+ *mangled |= BUILDER_FOOTER_MANGLED;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Args: to -- the passed in line to parse
+ * full_to -- Address of a pointer to return the full address in.
+ * This will be allocated here and freed by the caller.
+ * However, this function is just going to copy "to".
+ * (special case for the route-addr-hack in build_address_int)
+ * We're just looking for the error messages.
+ * error -- Address of a pointer to return an error message in.
+ * This will be allocated here and freed by the caller.
+ * fcc -- This should be passed in NULL.
+ * This builder doesn't support affected_entry's.
+ *
+ * Result: 0 is returned if address was OK,
+ * -2 if address wasn't OK.
+ *
+ * Side effect: Can flush addrbook entry cache entries so they need to be
+ * re-fetched afterwords.
+ */
+int
+verify_addr(char *to, char **full_to, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ register char *p;
+ int ret_val;
+ BuildTo bldto;
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((7, "- verify_addr - (%s)\n", to ? to : "nul"));
+
+ /* check to see if to string is empty to avoid work */
+ for(p = to; p && *p && isspace((unsigned char)(*p)); p++)
+ ;/* do nothing */
+
+ if(!p || !*p){
+ if(full_to)
+ *full_to = cpystr(to ? to : ""); /* because pico does a strcmp() */
+
+ return 0;
+ }
+
+ if(full_to != NULL)
+ *full_to = (char *)NULL;
+
+ if(error != NULL)
+ *error = (char *)NULL;
+
+ /*
+ * If we end up jumping back here because somebody else changed one of
+ * our addrbooks out from underneath us, we may well leak some memory.
+ * That's probably ok since this will be very rare.
+ */
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ save_nesting_level = cpyint(ab_nesting_level);
+ if(setjmp(addrbook_changed_unexpectedly)){
+ if(full_to && *full_to)
+ fs_give((void **)full_to);
+
+ /* TRANSLATORS: This is sort of an error, something unexpected has
+ happened and alpine is re-initializing the address book. */
+ q_status_message(SM_ORDER, 3, 5, _("Resetting address book..."));
+ dprint((1,
+ "RESETTING address book... verify_addr(%s)!\n", to ? to : "?"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = to;
+
+ if(ps_global->remote_abook_validity > 0 &&
+ adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+
+ ab_nesting_level++;
+
+ ret_val = build_address_internal(bldto, full_to, error, NULL, NULL, NULL,
+ save_and_restore, 1, mangled);
+
+ ab_nesting_level--;
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ if(full_to && *full_to && ret_val >= 0)
+ removing_leading_and_trailing_white_space(*full_to);
+
+ /* This is so pico will erase the old message */
+ if(error != NULL && *error == NULL)
+ *error = cpystr("");
+
+ if(ret_val < 0)
+ ret_val = -2; /* cause pico to stay on same header line */
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ return(ret_val);
+}
+
+
+/*
+ * Call back for pico to prompt the user for exit confirmation
+ *
+ * Returns: either NULL if the user accepts exit, or string containing
+ * reason why the user declined.
+ */
+int
+pico_sendexit_for_adrbk(struct headerentry *he, void (*redraw_pico)(void),
+ int allow_flowed, char **result)
+{
+ char *rstr = NULL;
+ void (*redraw)(void) = ps_global->redrawer;
+
+ ps_global->redrawer = redraw_pico;
+ fix_windsize(ps_global);
+
+ /* TRANSLATORS: A question */
+ switch(want_to(_("Exit and save changes "), 'y', 0, NO_HELP, WT_NORM)){
+ case 'y':
+ break;
+
+ case 'n':
+ rstr = _("Use ^C to abandon changes you've made");
+ break;
+ }
+
+ if(result)
+ *result = rstr;
+
+ ps_global->redrawer = redraw;
+ return((rstr == NULL) ? 0 : 1);
+}
+
+
+/*
+ * Call back for pico to prompt the user for exit confirmation
+ *
+ * Returns: either NULL if the user accepts exit, or string containing
+ * reason why the user declined.
+ */
+char *
+pico_cancelexit_for_adrbk(char *word, void (*redraw_pico)(void))
+{
+ char prompt[90];
+ char *rstr = NULL;
+ void (*redraw)(void) = ps_global->redrawer;
+
+ /* TRANSLATORS: A question. The %s is a noun describing what is being cancelled. */
+ snprintf(prompt, sizeof(prompt), _("Cancel %s (answering \"Yes\" will abandon any changes made) "), word);
+ ps_global->redrawer = redraw_pico;
+ fix_windsize(ps_global);
+
+ switch(want_to(prompt, 'y', 'x', NO_HELP, WT_NORM)){
+ case 'y':
+ rstr = "";
+ break;
+
+ case 'n':
+ case 'x':
+ break;
+ }
+
+ ps_global->redrawer = redraw;
+ return(rstr);
+}
+
+
+char *
+pico_cancel_for_adrbk_take(void (*redraw_pico)(void))
+{
+ return(pico_cancelexit_for_adrbk(_("take"), redraw_pico));
+}
+
+
+char *
+pico_cancel_for_adrbk_edit(void (*redraw_pico)(void))
+{
+ return(pico_cancelexit_for_adrbk(_("changes"), redraw_pico));
+}
+
+
+/*
+prompt::name::help::prwid::maxlen::realaddr::
+builder::affected_entry::next_affected::selector::key_label::fileedit::
+display_it::break_on_comma::is_attach::rich_header::only_file_chars::
+single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
+*/
+static struct headerentry headents_for_add[]={
+ {"Server Name : ", N_("Server"), h_composer_abook_add_server, 14, 0, NULL,
+ verify_server_name, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Folder Name : ", N_("Folder"), h_composer_abook_add_folder, 14, 0, NULL,
+ verify_folder_name, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"NickName : ", N_("Nickname"), h_composer_abook_add_nick, 14, 0, NULL,
+ verify_abook_nick, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
+};
+#define NN_SERVER 0
+#define NN_FOLDER 1
+#define NN_NICK 2
+#define NN_END 3
+
+/*
+ * Args: global -- Add a global address book, not personal.
+ * add_after_this -- This is the addrbook number which should come
+ * right before the new addrbook we're adding, if
+ * that makes sense. If this is -1, append to end
+ * of the list.
+ *
+ * Returns: addrbook number of new addrbook, or
+ * -1, no addrbook added
+ */
+int
+ab_add_abook(int global, int add_after_this)
+{
+ int ret;
+
+ dprint((2, "- ab_add_abook -\n"));
+
+ ret = ab_modify_abook_list(0, global, add_after_this, NULL, NULL, NULL);
+
+ if(ret >= 0)
+ q_status_message(SM_ORDER, 0, 3,
+ _("New address book added. Use \"$\" to adjust order"));
+
+ return(ret);
+}
+
+
+/*
+ * Args: global -- Add a global address book, not personal.
+ * abook_num -- Abook num of the entry we are editing.
+ * serv -- Default server.
+ * folder -- Default folder.
+ * nick -- Default nickname.
+ *
+ * Returns: abook_num if successful,
+ * -1, if not
+ */
+int
+ab_edit_abook(int global, int abook_num, char *serv, char *folder, char *nick)
+{
+ dprint((2, "- ab_edit_abook -\n"));
+
+ return(ab_modify_abook_list(1, global, abook_num, serv, folder, nick));
+}
+
+static int the_one_were_editing;
+
+
+/*
+ * Args: edit -- Edit existing entry
+ * global -- Add a global address book, not personal.
+ * abook_num -- This is the addrbook number which should come
+ * right before the new addrbook we're adding, if
+ * that makes sense. If this is -1, append to end
+ * of the list.
+ * If we are editing instead of adding, this is
+ * the abook number of the entry we are editing.
+ * def_serv -- Default server.
+ * def_fold -- Default folder.
+ * def_nick -- Default nickname.
+ *
+ * Returns: addrbook number of new addrbook, or
+ * -1, no addrbook added
+ */
+int
+ab_modify_abook_list(int edit, int global, int abook_num, char *def_serv, char *def_fold, char *def_nick)
+{
+ struct headerentry *he;
+ PICO pbf;
+ STORE_S *msgso;
+ int editor_result, i, how_many_in_list, new_abook_num, num_in_list;
+ int ret = 0;
+ char *server, *folder, *nickname;
+ char *new_item = NULL;
+ EditWhich ew;
+ AccessType remember_access_result;
+ PerAddrBook *pab;
+ char titlebar[100];
+ char **list, **new_list = NULL;
+ char tmp[1000+MAXFOLDER];
+ int abook_indent;
+ char servpmt[100], foldpmt[100], nickpmt[100];
+ struct variable *vars = ps_global->vars;
+
+ dprint((2, "- ab_modify_abook_list -\n"));
+
+ if(ps_global->readonly_pinerc){
+ if(edit)
+ /* TRANSLATORS: Change was the name of the command the user was
+ trying to perform. It is what was cancelled. */
+ q_status_message(SM_ORDER, 0, 3, _("Change cancelled: config file not changeable"));
+ else
+ /* TRANSLATORS: Add was the command that is being cancelled. */
+ q_status_message(SM_ORDER, 0, 3, _("Add cancelled: config file not changeable"));
+
+ return -1;
+ }
+
+ ew = Main;
+
+ if((global && vars[V_GLOB_ADDRBOOK].is_fixed) ||
+ (!global && vars[V_ADDRESSBOOK].is_fixed)){
+ if(global)
+ /* TRANSLATORS: Operation was cancelled because the system management
+ does not allow the changing of global address books */
+ q_status_message(SM_ORDER, 0, 3, _("Cancelled: Sys. Mgmt. does not allow changing global address books"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Cancelled: Sys. Mgmt. does not allow changing address books"));
+
+ return -1;
+ }
+
+ init_ab_if_needed();
+
+ if(edit){
+ if((!global &&
+ (abook_num < 0 || abook_num >= as.how_many_personals)) ||
+ (global &&
+ (abook_num < as.how_many_personals ||
+ abook_num >= as.n_addrbk))){
+ dprint((1, "Programming botch in ab_modify_abook_list: global=%d abook_num=%d n_addrbk=%d\n", global, abook_num, as.n_addrbk));
+ q_status_message(SM_ORDER, 0, 3, "Programming botch, bad abook_num");
+ return -1;
+ }
+
+ the_one_were_editing = abook_num;
+ }
+ else
+ the_one_were_editing = -1;
+
+ standard_picobuf_setup(&pbf);
+ pbf.exittest = pico_sendexit_for_adrbk;
+ pbf.canceltest = pico_cancel_for_adrbk_edit;
+ if(edit)
+ /* TRANSLATORS: screen title */
+ strncpy(titlebar, _("CHANGE ADDRESS BOOK"), sizeof(titlebar));
+ else
+ /* TRANSLATORS: screen title */
+ strncpy(titlebar, _("ADD ADDRESS BOOK"), sizeof(titlebar));
+
+ titlebar[sizeof(titlebar)-1] = '\0';
+ pbf.pine_anchor = set_titlebar(titlebar,
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+ pbf.pine_flags |= P_NOBODY;
+
+ /* An informational message */
+ if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ int lines_avail;
+char *t1 =
+/* TRANSLATORS: The next few lines go together to explain how to add
+ the address book entry the user is working on. */
+_(" To add a local address book that will be accessed *only* by Alpine running\n on this machine, leave the server field blank.");
+char *t2 =
+_(" To add an address book that will be accessed by IMAP, fill in the\n server name.");
+char *t3 =
+_(" (NOTE: An address book cannot be accessed by IMAP from\n one Alpine and as a local file from another. It is either always accessed\n by IMAP or it is a local address book which is never accessed by IMAP.)");
+char *t4 =
+_(" In the Folder field, type the remote folder name or local file name.");
+char *t5 =
+_(" In the Nickname field, give the address book a nickname or leave it blank.");
+char *t6 =
+_(" To get help specific to an item, press ^G.");
+char *t7 =
+_(" To exit and save the configuration, press ^X. To cancel, press ^C.");
+
+ pbf.msgtext = (void *)so_text(msgso);
+ /*
+ * It's nice if we can make it so these lines make sense even if
+ * they don't all make it on the screen, because the user can't
+ * scroll down to see them.
+ *
+ * The 3 is the number of fields to be defined, the 1 is for a
+ * single blank line after the field definitions.
+ */
+ lines_avail = ps_global->ttyo->screen_rows - HEADER_ROWS(ps_global) -
+ FOOTER_ROWS(ps_global) - 3 - 1;
+
+ if(lines_avail >= 15){ /* extra blank line */
+ so_puts(msgso, "\n");
+ lines_avail--;
+ }
+
+ if(lines_avail >= 2){
+ so_puts(msgso, t1);
+ lines_avail -= 2;
+ }
+
+ if(lines_avail >= 5){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t2);
+ so_puts(msgso, t3);
+ lines_avail -= 5;
+ }
+ else if(lines_avail >= 3){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t2);
+ lines_avail -= 3;
+ }
+
+ if(lines_avail >= 2){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t4);
+ lines_avail -= 2;
+ }
+
+ if(lines_avail >= 2){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t5);
+ lines_avail -= 2;
+ }
+
+ if(lines_avail >= 3){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t6);
+ so_puts(msgso, "\n");
+ so_puts(msgso, t7);
+ }
+ else if(lines_avail >= 2){
+ so_puts(msgso, "\n\n");
+ so_puts(msgso, t7);
+ }
+ }
+
+ he = (struct headerentry *)fs_get((NN_END+1) * sizeof(struct headerentry));
+ memset((void *)he, 0, (NN_END+1) * sizeof(struct headerentry));
+ pbf.headents = he;
+
+ abook_indent = utf8_width(_("Server Name")) + 2;
+
+ /* make a copy of each field */
+ server = cpystr(def_serv ? def_serv : "");
+ he[NN_SERVER] = headents_for_add[NN_SERVER];
+ he[NN_SERVER].realaddr = &server;
+ utf8_snprintf(servpmt, sizeof(servpmt), "%-*.*w: ", abook_indent, abook_indent, _("Server Name"));
+ he[NN_SERVER].prompt = servpmt;
+ he[NN_SERVER].prwid = abook_indent+2;
+
+ folder = cpystr(def_fold ? def_fold : "");
+ he[NN_FOLDER] = headents_for_add[NN_FOLDER];
+ he[NN_FOLDER].realaddr = &folder;
+ utf8_snprintf(foldpmt, sizeof(foldpmt), "%-*.*w: ", abook_indent, abook_indent, _("Folder Name"));
+ he[NN_FOLDER].prompt = foldpmt;
+ he[NN_FOLDER].prwid = abook_indent+2;
+
+ nickname = cpystr(def_nick ? def_nick : "");
+ he[NN_NICK] = headents_for_add[NN_NICK];
+ he[NN_NICK].realaddr = &nickname;
+ utf8_snprintf(nickpmt, sizeof(nickpmt), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
+ he[NN_NICK].prompt = nickpmt;
+ he[NN_NICK].prwid = abook_indent+2;
+
+ he[NN_END] = headents_for_add[NN_END];
+
+ /* pass to pico and let user change them */
+ editor_result = pico(&pbf);
+ standard_picobuf_teardown(&pbf);
+
+ if(editor_result & COMP_GOTHUP){
+ ret = -1;
+ hup_signal();
+ }
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+
+ if(editor_result & COMP_CANCEL){
+ ret = -1;
+ if(edit)
+ q_status_message(SM_ORDER, 0, 3, _("Address book change is cancelled"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Address book add is cancelled"));
+ }
+ else if(editor_result & COMP_EXIT){
+ if(edit &&
+ !strcmp(server, def_serv ? def_serv : "") &&
+ !strcmp(folder, def_fold ? def_fold : "") &&
+ !strcmp(nickname, def_nick ? def_nick : "")){
+ ret = -1;
+ if(edit)
+ q_status_message(SM_ORDER, 0, 3, _("No change: Address book change is cancelled"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("No change: Address book add is cancelled"));
+ }
+ else{
+ if(global){
+ list = VAR_GLOB_ADDRBOOK;
+ how_many_in_list = as.n_addrbk - as.how_many_personals;
+ if(edit)
+ new_abook_num = abook_num;
+ else if(abook_num < 0)
+ new_abook_num = as.n_addrbk;
+ else
+ new_abook_num = MAX(MIN(abook_num + 1, as.n_addrbk),
+ as.how_many_personals);
+
+ num_in_list = new_abook_num - as.how_many_personals;
+ }
+ else{
+ list = VAR_ADDRESSBOOK;
+ how_many_in_list = as.how_many_personals;
+ new_abook_num = abook_num;
+ if(edit)
+ new_abook_num = abook_num;
+ else if(abook_num < 0)
+ new_abook_num = as.how_many_personals;
+ else
+ new_abook_num = MIN(abook_num + 1, as.how_many_personals);
+
+ num_in_list = new_abook_num;
+ }
+
+ if(!edit)
+ how_many_in_list++; /* for new abook */
+
+ removing_leading_and_trailing_white_space(server);
+ removing_leading_and_trailing_white_space(folder);
+ removing_leading_and_trailing_white_space(nickname);
+
+ /* convert nickname to UTF-8 */
+ if(nickname){
+ char *conv;
+
+ conv = convert_to_utf8(nickname, NULL, 0);
+ if(conv){
+ fs_give((void **) &nickname);
+ nickname = conv;
+ }
+ }
+
+ /* eliminate surrounding brackets */
+ if(server[0] == '{' && server[strlen(server)-1] == '}'){
+ char *p;
+
+ server[strlen(server)-1] = '\0';
+ for(p = server; *p; p++)
+ *p = *(p+1);
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s%s%s%.*s",
+ *server ? "{" : "",
+ *server ? server : "",
+ *server ? "}" : "",
+ MAXFOLDER, folder);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ new_item = put_pair(nickname, tmp);
+
+ if(!new_item || *new_item == '\0'){
+ if(edit)
+ q_status_message(SM_ORDER, 0, 3, _("Address book change is cancelled"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Address book add is cancelled"));
+
+ ret = -1;
+ goto get_out;
+ }
+
+ /* allocate for new list */
+ new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));
+
+ /* copy old list up to where we will insert new entry */
+ for(i = 0; i < num_in_list; i++)
+ new_list[i] = cpystr(list[i]);
+
+ /* insert the new entry */
+ new_list[i++] = cpystr(new_item);
+
+ /* copy rest of old list, skip current if editing */
+ for(; i < how_many_in_list; i++)
+ new_list[i] = cpystr(list[edit ? i : (i-1)]);
+
+ new_list[i] = NULL;
+
+ /* this frees old variable contents for us */
+ if(set_variable_list(global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK,
+ new_list, TRUE, ew)){
+ if(edit)
+ q_status_message(SM_ORDER, 0, 3, _("Change cancelled: couldn't save configuration file"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Add cancelled: couldn't save configuration file"));
+
+ set_current_val(&vars[global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK],
+ TRUE, FALSE);
+ ret = -1;
+ goto get_out;
+ }
+
+ ret = new_abook_num;
+ set_current_val(&vars[global ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK],
+ TRUE, FALSE);
+
+ addrbook_reset();
+ init_ab_if_needed();
+
+ /*
+ * Test to see if this definition is going to work.
+ * Error messages are a good side effect.
+ */
+ pab = &as.adrbks[num_in_list];
+ init_abook(pab, NoDisplay);
+ remember_access_result = pab->access;
+ addrbook_reset();
+ init_ab_if_needed();
+ /* if we had trouble, give a clue to user (other than error msg) */
+ if(remember_access_result == NoAccess){
+ pab = &as.adrbks[num_in_list];
+ pab->access = remember_access_result;
+ }
+ }
+ }
+
+get_out:
+
+ if(he)
+ free_headents(&he);
+
+ if(new_list)
+ free_list_array(&new_list);
+
+ if(new_item)
+ fs_give((void **)&new_item);
+
+ if(msgso)
+ so_give(&msgso);
+
+ if(server)
+ fs_give((void **)&server);
+ if(folder)
+ fs_give((void **)&folder);
+ if(nickname)
+ fs_give((void **)&nickname);
+
+ return(ret);
+}
+
+
+int
+any_addrbooks_to_convert(struct pine *ps)
+{
+ PerAddrBook *pab;
+ int i, count = 0;
+
+ init_ab_if_needed();
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
+ count++;
+ }
+
+ return(count);
+}
+
+
+int
+convert_addrbooks_to_remote(struct pine *ps, char *rem_folder_prefix, size_t len)
+{
+ PerAddrBook *pab;
+ int i, count = 0, ret = 0;
+
+ init_ab_if_needed();
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
+ count++;
+ }
+
+ for(i = 0; ret != -1 && i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(pab && !(pab->type & REMOTE_VIA_IMAP) && !(pab->type & GLOBAL))
+ ret = convert_abook_to_remote(ps, pab, rem_folder_prefix, len, count);
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Returns -1 if cancelled, -2 on error, 0 otherwise.
+ */
+int
+convert_abook_to_remote(struct pine *ps, PerAddrBook *pab, char *rem_folder_prefix, size_t len, int count)
+{
+#define DEF_ABOOK_NAME "remote_addrbook"
+ char local_file[MAILTMPLEN];
+ char rem_abook[MAILTMPLEN+3], prompt[MAILTMPLEN], old_nick[MAILTMPLEN];
+ char *p = NULL, *err_msg = NULL, *q;
+ char *serv = NULL, *nick = NULL, *file = NULL, *folder = NULL;
+ int ans, rc, offset, i, abook_num = -1, flags = OE_APPEND_CURRENT;
+ HelpType help;
+
+ snprintf(old_nick, sizeof(old_nick), "%s%s%s",
+ count > 1 ? " \"" : "",
+ count > 1 ? pab->abnick : "",
+ count > 1 ? "\"" : "");
+ old_nick[sizeof(old_nick)-1] = '\0';
+
+ snprintf(prompt, sizeof(prompt), _("Convert addressbook%s to a remote addrbook "), old_nick);
+ prompt[sizeof(prompt)-1] = '\0';
+ if((ans=want_to(prompt, 'y', 'x', h_convert_abook, WT_NORM)) != 'y')
+ return(ans == 'n' ? 0 : -1);
+
+ /* make sure the addrbook has been opened before, so that the file exists */
+ if(pab->ostatus == Closed || pab->ostatus == HalfOpen){
+ (void)init_addrbooks(NoDisplay, 0, 0, 0);
+ (void)init_addrbooks(Closed, 0, 0, 0);
+ }
+
+ if(pab->filename){
+ strncpy(local_file, pab->filename, sizeof(local_file)-1);
+ local_file[sizeof(local_file)-1] = '\0';
+#if defined(DOS)
+ p = strrindex(pab->filename, '\\');
+#else
+ p = strrindex(pab->filename, '/');
+#endif
+ }
+
+ strncpy(rem_abook, rem_folder_prefix, sizeof(rem_abook)-3);
+ if(!*rem_abook){
+ /* TRANSLATORS: The user is defining an address book which will be
+ stored on another server. This is called a remote addrbook and
+ this is a question asking for the name of the server. */
+ snprintf(prompt, sizeof(prompt), _("Name of server to contain remote addrbook : "));
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ while(1){
+ rc = optionally_enter(rem_abook, -FOOTER_ROWS(ps), 0,
+ sizeof(rem_abook), prompt, NULL,
+ help, &flags);
+ removing_leading_and_trailing_white_space(rem_abook);
+ if(rc == 3){
+ help = help == NO_HELP ? h_convert_pinerc_server : NO_HELP;
+ }
+ else if(rc == 1){
+ cmd_cancelled(NULL);
+ return(-1);
+ }
+ else if(rc == 0){
+ if(*rem_abook){
+ /* add brackets */
+ offset = strlen(rem_abook);
+ for(i = offset; i >= 0; i--)
+ rem_abook[i+1] = rem_abook[i];
+
+ rem_abook[0] = '{';
+ rem_abook[++offset] = '}';
+ rem_abook[++offset] = '\0';
+ break;
+ }
+ }
+ }
+ }
+
+ if(*rem_abook){
+ if(p && count > 1)
+ strncat(rem_abook, p+1,
+ sizeof(rem_abook)-1-strlen(rem_abook));
+ else
+ strncat(rem_abook, DEF_ABOOK_NAME,
+ sizeof(rem_abook)-1-strlen(rem_abook));
+ }
+
+ if(*rem_abook){
+ file = cpystr(rem_abook);
+ if(pab->abnick){
+ nick = (char *)fs_get((MAX(strlen(pab->abnick),strlen("Address Book"))+8) * sizeof(char));
+ snprintf(nick, sizeof(nick), "Remote %s",
+ (pab->abnick && !strcmp(pab->abnick, DF_ADDRESSBOOK))
+ ? "Address Book" : pab->abnick);
+ nick[sizeof(nick)-1] = '\0';
+ }
+ else
+ nick = cpystr("Remote Address Book");
+
+ if(file && *file == '{'){
+ q = file + 1;
+ if((p = strindex(file, '}'))){
+ *p = '\0';
+ serv = q;
+ folder = p+1;
+ }
+ else if(file)
+ fs_give((void **)&file);
+ }
+ else
+ folder = file;
+ }
+
+ q_status_message(SM_ORDER, 3, 5,
+ _("You now have a chance to change the name of the remote addrbook..."));
+ abook_num = ab_modify_abook_list(0, 0, -1, serv, folder, nick);
+
+ /* extract folder name of new abook so we can copy to it */
+ if(abook_num >= 0){
+ char **lval;
+ EditWhich ew = Main;
+
+ lval = LVAL(&ps->vars[V_ADDRESSBOOK], ew);
+ get_pair(lval[abook_num], &nick, &file, 0, 0);
+ if(nick)
+ fs_give((void **)&nick);
+
+ if(file){
+ strncpy(rem_abook, file, sizeof(rem_abook)-1);
+ rem_abook[sizeof(rem_abook)-1] = '\0';
+ fs_give((void **)&file);
+ }
+ }
+
+ /* copy the abook */
+ if(abook_num >= 0 && copy_abook(local_file, rem_abook, &err_msg)){
+ if(err_msg){
+ q_status_message(SM_ORDER | SM_DING, 7, 10, err_msg);
+ fs_give((void **)&err_msg);
+ }
+
+ return(-2);
+ }
+ else if(abook_num >= 0){ /* give user some info */
+ STORE_S *store;
+ SCROLL_S sargs;
+ char *beg, *end;
+
+ /*
+ * Save the hostname in rem_folder_prefix so we can use it again
+ * for other conversions if needed.
+ */
+ if((beg = rem_abook)
+ && (*beg == '{' || (*beg == '*' && *++beg == '{'))
+ && (end = strindex(rem_abook, '}'))){
+ rem_folder_prefix[0] = '{';
+ strncpy(rem_folder_prefix+1, beg+1, MIN(end-beg,len-2));
+ rem_folder_prefix[MIN(end-beg,len-2)] = '}';
+ rem_folder_prefix[MIN(end-beg+1,len-1)] = '\0';
+ }
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return(-2);
+ }
+
+ /* TRANSLATORS: Several lines in a row here that go together. */
+ snprintf(prompt, sizeof(prompt), _("\nYour addressbook%s has been copied to the"), old_nick);
+ prompt[sizeof(prompt)-1] = '\0';
+ so_puts(store, prompt);
+ so_puts(store, _("\nremote folder \""));
+ so_puts(store, rem_abook);
+ so_puts(store, "\".");
+ so_puts(store, _("\nA definition for this remote address book has been added to your list"));
+ so_puts(store, _("\nof address books. The definition for the address book it was copied"));
+ so_puts(store, _("\nfrom is also still there. You may want to remove that after you"));
+ so_puts(store, _("\nare confident that the new address book is complete and working."));
+ so_puts(store, _("\nUse the Setup/AddressBooks command to do that.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Remote Address Book Information");
+ /* TRANSLATORS: a screen title */
+ sargs.bar.title = _("ABOUT REMOTE ABOOK");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store); /* free resources associated with store */
+ ps->mangled_screen = 1;
+ }
+
+ return(0);
+}
+
+
+int
+any_sigs_to_convert(struct pine *ps)
+{
+ char *sigfile, *litsig;
+ long rflags;
+ PAT_STATE pstate;
+ PAT_S *pat;
+ PAT_LINE_S *patline;
+
+ /* first check main signature file */
+ sigfile = ps->VAR_SIGNATURE_FILE;
+ litsig = ps->VAR_LITERAL_SIG;
+
+ if(sigfile && *sigfile && !litsig && sigfile[strlen(sigfile)-1] != '|' &&
+ !IS_REMOTE(sigfile))
+ return(1);
+
+ rflags = (ROLE_DO_ROLES | PAT_USE_MAIN);
+ if(any_patterns(rflags, &pstate)){
+ set_pathandle(rflags);
+ for(patline = *cur_pat_h ? (*cur_pat_h)->patlinehead : NULL;
+ patline; patline = patline->next){
+ for(pat = patline->first; pat; pat = pat->next){
+
+ /*
+ * See detoken() for when a sig file is used with a role.
+ */
+ sigfile = pat->action ? pat->action->sig : NULL;
+ litsig = pat->action ? pat->action->litsig : NULL;
+
+ if(sigfile && *sigfile && !litsig &&
+ sigfile[strlen(sigfile)-1] != '|' &&
+ !IS_REMOTE(sigfile))
+ return(1);
+ }
+ }
+ }
+
+ return(0);
+}
+
+
+int
+any_rule_files_to_warn_about(struct pine *ps)
+{
+ long rflags;
+ PAT_STATE pstate;
+ PAT_S *pat;
+
+ rflags = (ROLE_DO_ROLES | ROLE_DO_INCOLS | ROLE_DO_SCORES |
+ ROLE_DO_FILTER | ROLE_DO_OTHER | ROLE_DO_SRCH | PAT_USE_MAIN);
+ if(any_patterns(rflags, &pstate)){
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->patline && pat->patline->type == File)
+ break;
+ }
+
+ if(pat)
+ return(1);
+ }
+
+ return(0);
+}
+
+
+int
+convert_sigs_to_literal(struct pine *ps, int interactive)
+{
+ EditWhich ew = Main;
+ char *sigfile, *litsig, *cstring_version, *nick, *src = NULL;
+ char prompt[MAILTMPLEN];
+ STORE_S *store;
+ SCROLL_S sargs;
+ long rflags;
+ int ans;
+ PAT_STATE pstate;
+ PAT_S *pat;
+ PAT_LINE_S *patline;
+
+ /* first check main signature file */
+ sigfile = ps->VAR_SIGNATURE_FILE;
+ litsig = ps->VAR_LITERAL_SIG;
+
+ if(sigfile && *sigfile && !litsig && sigfile[strlen(sigfile)-1] != '|' &&
+ !IS_REMOTE(sigfile)){
+ if(interactive){
+ snprintf(prompt,sizeof(prompt),
+ /* TRANSLATORS: A literal sig is a way to store a signature
+ in alpine. It isn't a very descriptive name. Instead of
+ storing it in its own file it is stored in the configuration
+ file. */
+ _("Convert signature file \"%s\" to a literal sig "),
+ sigfile);
+ prompt[sizeof(prompt)-1] = '\0';
+ ClearBody();
+ ps->mangled_body = 1;
+ if((ans=want_to(prompt, 'y', 'x', h_convert_sig, WT_NORM)) == 'x'){
+ cmd_cancelled(NULL);
+ return(-1);
+ }
+ }
+ else
+ ans = 'y';
+
+ if(ans == 'y' && (src = get_signature_file(sigfile, 0, 0, 0)) != NULL){
+ cstring_version = string_to_cstring(src);
+ set_variable(V_LITERAL_SIG, cstring_version, 0, 0, ew);
+
+ if(cstring_version)
+ fs_give((void **)&cstring_version);
+
+ fs_give((void **)&src);
+
+ if(interactive){
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return(-1);
+ }
+
+ snprintf(prompt, sizeof(prompt),
+ /* TRANSLATORS: following lines go together */
+ _("\nYour signature file \"%s\" has been converted"), sigfile);
+ prompt[sizeof(prompt)-1] = '\0';
+ so_puts(store, prompt);
+ so_puts(store,
+ _("\nto a literal signature, which means it is contained in your"));
+ so_puts(store,
+ _("\nAlpine configuration instead of being in a file of its own."));
+ so_puts(store,
+ _("\nIf that configuration is copied to a remote folder then the"));
+ so_puts(store,
+ _("\nsignature will be available remotely also."));
+ so_puts(store,
+ _("\nChanges to the signature file itself will no longer have any"));
+ so_puts(store,
+ _("\neffect on Alpine but you may still edit the signature with the"));
+ so_puts(store,
+ _("\nSetup/Signature command.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Literal Signature Information");
+ /* TRANSLATORS: screen title */
+ sargs.bar.title = _("ABOUT LITERAL SIG");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store);
+ ps->mangled_screen = 1;
+ }
+ }
+ }
+
+ rflags = (ROLE_DO_ROLES | PAT_USE_MAIN);
+ if(any_patterns(rflags, &pstate)){
+ set_pathandle(rflags);
+ for(patline = *cur_pat_h ? (*cur_pat_h)->patlinehead : NULL;
+ patline; patline = patline->next){
+ for(pat = patline->first; pat; pat = pat->next){
+
+ /*
+ * See detoken() for when a sig file is used with a role.
+ */
+ sigfile = pat->action ? pat->action->sig : NULL;
+ litsig = pat->action ? pat->action->litsig : NULL;
+ nick = (pat->action && pat->action->nick && pat->action->nick[0]) ? pat->action->nick : NULL;
+
+ if(sigfile && *sigfile && !litsig &&
+ sigfile[strlen(sigfile)-1] != '|' &&
+ !IS_REMOTE(sigfile)){
+ if(interactive){
+ snprintf(prompt,sizeof(prompt),
+ /* TRANSLATORS: asking whether a signature file should be converted to what
+ we call a literal signature, which is one contained in the regular
+ configuration file. Think of the set of 4 %s arguments as a
+ single argument which is the name of the signature file. */
+ _("Convert signature file \"%s\"%s%s%s to a literal sig "),
+ sigfile,
+ nick ? " in role \"" : "",
+ nick ? nick : "",
+ nick ? "\"" : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ ClearBody();
+ ps->mangled_body = 1;
+ if((ans=want_to(prompt, 'y', 'x',
+ h_convert_sig, WT_NORM)) == 'x'){
+ cmd_cancelled(NULL);
+ return(-1);
+ }
+ }
+ else
+ ans = 'y';
+
+ if(ans == 'y' &&
+ (src = get_signature_file(sigfile,0,0,0)) != NULL){
+
+ cstring_version = string_to_cstring(src);
+
+ if(pat->action->litsig)
+ fs_give((void **)&pat->action->litsig);
+
+ pat->action->litsig = cstring_version;
+ fs_give((void **)&src);
+
+ set_pathandle(rflags);
+ if(patline->type == Literal)
+ (*cur_pat_h)->dirtypinerc = 1;
+ else
+ patline->dirty = 1;
+
+ if(write_patterns(rflags) == 0){
+ if(interactive){
+ /*
+ * Flush out current_vals of anything we've
+ * possibly changed.
+ */
+ close_patterns(ROLE_DO_ROLES | PAT_USE_CURRENT);
+
+ if(!(store=so_get(CharStar,NULL,EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return(-1);
+ }
+
+ snprintf(prompt, sizeof(prompt),
+ /* TRANSLATORS: Keep the %s's together, they are sort of
+ the name of the file. */
+ _("Your signature file \"%s\"%s%s%s has been converted"),
+ sigfile,
+ nick ? " in role \"" : "",
+ nick ? nick : "",
+ nick ? "\"" : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ so_puts(store, prompt);
+ so_puts(store,
+ /* TRANSLATORS: several lines that go together */
+ _("\nto a literal signature, which means it is contained in your"));
+ so_puts(store,
+ _("\nAlpine configuration instead of being in a file of its own."));
+ so_puts(store,
+ _("\nIf that configuration is copied to a remote folder then the"));
+ so_puts(store,
+ _("\nsignature will be available remotely also."));
+ so_puts(store,
+ _("\nChanges to the signature file itself will no longer have any"));
+ so_puts(store,
+ _("\neffect on Alpine. You may edit the signature with the"));
+ so_puts(store,
+ _("\nSetup/Rules/Roles command.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc =
+ _("Literal Signature Information");
+ /* TRANSLATORS: a screen title */
+ sargs.bar.title = _("ABOUT LITERAL SIG");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store);
+ ps->mangled_screen = 1;
+ }
+ }
+ else if(interactive){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ /* TRANSLATORS: config is an abbreviation for configuration */
+ _("Error writing rules config."));
+ }
+ else{
+ /* TRANSLATORS: sig is signature */
+ fprintf(stderr, _("Error converting role sig\n"));
+ return(-1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return(0);
+}
+
+
+void
+warn_about_rule_files(struct pine *ps)
+{
+ STORE_S *store;
+ SCROLL_S sargs;
+
+ if(any_rule_files_to_warn_about(ps)){
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return;
+ }
+
+ /* TRANSLATORS: several lines that go together */
+ so_puts(store, _("\nSome of your Rules are contained in Rule files instead of being directly"));
+ so_puts(store, _("\ncontained in your Alpine configuration file. To make those rules"));
+ so_puts(store, _("\navailable remotely you will need to move them out of the files."));
+ so_puts(store, _("\nThat can be done using the Shuffle command in the appropriate"));
+ so_puts(store, _("\nSetup/Rules subcommands.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Rule Files Information");
+ /* TRANSLATORS: a screen title */
+ sargs.bar.title = _("ABOUT RULE FILES");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store);
+ ps->mangled_screen = 1;
+ }
+}
+
+
+void
+convert_to_remote_config(struct pine *ps, int edit_exceptions)
+{
+ char rem_pinerc_prefix[MAILTMPLEN];
+ char *beg, *end;
+ CONTEXT_S *context;
+ int abooks, sigs;
+
+ if(edit_exceptions){
+ /* TRANSLATORS: The exceptions command (X) was typed but it doesn't make sense */
+ q_status_message(SM_ORDER, 3, 5,
+ _("eXceptions does not make sense with this command"));
+ return;
+ }
+
+ if(!ps->prc)
+ panic("NULL prc in convert_to_remote_config");
+
+ dprint((2, "convert_to_remote_config\n"));
+
+ if(ps->prc->type == RemImap){ /* pinerc is already remote */
+ char prompt[MAILTMPLEN];
+
+ /*
+ * Check to see if there is anything at all to do. If there are
+ * address books to convert, sigfiles to convert, or rule files
+ * to comment on, we have something to do. Otherwise, just bail.
+ */
+ abooks = any_addrbooks_to_convert(ps);
+ sigs = any_sigs_to_convert(ps);
+
+ if(abooks || sigs){
+ if(abooks && sigs)
+ /* TRANSLATORS: AddressBooks is Address Books */
+ snprintf(prompt, sizeof(prompt), _("Config is already remote, convert AddressBooks and signature files "));
+ else if(abooks)
+ snprintf(prompt, sizeof(prompt), _("Config is already remote, convert AddressBooks "));
+ else
+ snprintf(prompt, sizeof(prompt), _("Config is already remote, convert signature files "));
+
+ prompt[sizeof(prompt)-1] = '\0';
+ if(want_to(prompt, 'y', 'x',
+ (abooks && sigs) ? h_convert_abooks_and_sigs :
+ abooks ? h_convert_abooks :
+ sigs ? h_convert_sigs : NO_HELP,
+ WT_NORM) != 'y'){
+ cmd_cancelled(NULL);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Figure out a good default for where to put the remote config.
+ * If the default collection is remote we'll take the hostname and
+ * and modifiers from there. If not, we'll try to get the hostname from
+ * the inbox-path. In either case, we use the home directory on the
+ * server, not the directory where the folder collection is (if different).
+ * If we don't have a clue, we'll ask user.
+ */
+ if((context = default_save_context(ps->context_list)) != NULL &&
+ IS_REMOTE(context_apply(rem_pinerc_prefix, context, "",
+ sizeof(rem_pinerc_prefix)))){
+ /* just use the host from the default collection, not the whole path */
+ if((end = strrindex(rem_pinerc_prefix, '}')) != NULL)
+ *(end + 1) = '\0';
+ }
+ else{
+ /* use host from inbox path */
+ rem_pinerc_prefix[0] = '\0';
+ if((beg = ps->VAR_INBOX_PATH)
+ && (*beg == '{' || (*beg == '*' && *++beg == '{'))
+ && (end = strindex(ps->VAR_INBOX_PATH, '}'))){
+ rem_pinerc_prefix[0] = '{';
+ strncpy(rem_pinerc_prefix+1, beg+1,
+ MIN(end-beg, sizeof(rem_pinerc_prefix)-2));
+ rem_pinerc_prefix[MIN(end-beg, sizeof(rem_pinerc_prefix)-2)] = '}';
+ rem_pinerc_prefix[MIN(end-beg+1,sizeof(rem_pinerc_prefix)-1)]='\0';
+ }
+ }
+
+ /* ask about converting addrbooks to remote abooks */
+ if(ps->prc->type != RemImap || abooks)
+ if(convert_addrbooks_to_remote(ps, rem_pinerc_prefix,
+ sizeof(rem_pinerc_prefix)) == -1){
+ cmd_cancelled(NULL);
+ return;
+ }
+
+ /* ask about converting sigfiles to literal sigs */
+ if(ps->prc->type != RemImap || sigs)
+ if(convert_sigs_to_literal(ps, 1) == -1){
+ cmd_cancelled(NULL);
+ return;
+ }
+
+ warn_about_rule_files(ps);
+
+ /* finally, copy the config file */
+ if(ps->prc->type == Loc)
+ convert_pinerc_to_remote(ps, rem_pinerc_prefix);
+ else if(!(abooks || sigs))
+ q_status_message(SM_ORDER, 3, 5,
+ _("Cannot copy config file since it is already remote."));
+}
+
+
+void
+convert_pinerc_to_remote(struct pine *ps, char *rem_pinerc_prefix)
+{
+#define DEF_FOLDER_NAME "remote_pinerc"
+ char prompt[MAILTMPLEN], rem_pinerc[MAILTMPLEN];
+ char *err_msg = NULL;
+ int i, rc, offset;
+ HelpType help;
+ int flags = OE_APPEND_CURRENT;
+
+ ClearBody();
+ ps->mangled_body = 1;
+ strncpy(rem_pinerc, rem_pinerc_prefix, sizeof(rem_pinerc)-1);
+ rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
+
+ if(*rem_pinerc == '\0'){
+ snprintf(prompt, sizeof(prompt), _("Name of server to contain remote Alpine config : "));
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ while(1){
+ rc = optionally_enter(rem_pinerc, -FOOTER_ROWS(ps), 0,
+ sizeof(rem_pinerc), prompt, NULL,
+ help, &flags);
+ removing_leading_and_trailing_white_space(rem_pinerc);
+ if(rc == 3){
+ help = help == NO_HELP ? h_convert_pinerc_server : NO_HELP;
+ }
+ else if(rc == 1){
+ cmd_cancelled(NULL);
+ return;
+ }
+ else if(rc == 0){
+ if(*rem_pinerc){
+ /* add brackets */
+ offset = strlen(rem_pinerc);
+ for(i = offset; i >= 0; i--)
+ if(i+1 < sizeof(rem_pinerc))
+ rem_pinerc[i+1] = rem_pinerc[i];
+
+ rem_pinerc[0] = '{';
+ if(offset+2 < sizeof(rem_pinerc)){
+ rem_pinerc[++offset] = '}';
+ rem_pinerc[++offset] = '\0';
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
+
+ /*
+ * Add a default folder name.
+ */
+ if(*rem_pinerc){
+ /*
+ * Add /user= to modify hostname so that user won't be asked who they
+ * are each time they login.
+ */
+ if(!strstr(rem_pinerc, "/user=") && ps->VAR_USER_ID &&
+ ps->VAR_USER_ID[0]){
+ char *p;
+
+ p = rem_pinerc + strlen(rem_pinerc) - 1;
+ if(*p == '}') /* this should be the case */
+ snprintf(p, sizeof(rem_pinerc)-(p-rem_pinerc), "/user=\"%s\"}", ps->VAR_USER_ID);
+
+ rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
+ }
+
+ strncat(rem_pinerc, DEF_FOLDER_NAME,
+ sizeof(rem_pinerc) - strlen(rem_pinerc) - 1);
+ rem_pinerc[sizeof(rem_pinerc)-1] = '\0';
+ }
+
+ /* ask user about folder name for remote config */
+ snprintf(prompt, sizeof(prompt), _("Folder to contain remote config : "));
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ while(1){
+ rc = optionally_enter(rem_pinerc, -FOOTER_ROWS(ps), 0,
+ sizeof(rem_pinerc), prompt, NULL, help, &flags);
+ removing_leading_and_trailing_white_space(rem_pinerc);
+ if(rc == 0 && *rem_pinerc){
+ break;
+ }
+
+ if(rc == 3){
+ help = (help == NO_HELP) ? h_convert_pinerc_folder : NO_HELP;
+ }
+ else if(rc == 1 || rem_pinerc[0] == '\0'){
+ cmd_cancelled(NULL);
+ return;
+ }
+ }
+
+#ifndef _WINDOWS
+ /*
+ * If we are on a Unix system, writing to a remote config, we want the
+ * remote config to work smoothly from a PC, too. If we don't have a
+ * user-id on the PC then we will be asked for our password.
+ * So add user-id to the pinerc before we copy it.
+ */
+ if(!ps->vars[V_USER_ID].main_user_val.p && ps->VAR_USER_ID)
+ ps->vars[V_USER_ID].main_user_val.p = cpystr(ps->VAR_USER_ID);
+
+ ps->vars[V_USER_ID].is_used = 1; /* so it will write to pinerc */
+ ps->prc->outstanding_pinerc_changes = 1;
+#endif
+
+ if(ps->prc->outstanding_pinerc_changes)
+ write_pinerc(ps, Main, WRP_NONE);
+
+#ifndef _WINDOWS
+ ps->vars[V_USER_ID].is_used = 0;
+#endif
+
+ /* copy the pinerc */
+ if(copy_pinerc(ps->prc->name, rem_pinerc, &err_msg)){
+ if(err_msg){
+ q_status_message(SM_ORDER | SM_DING, 7, 10, err_msg);
+ fs_give((void **)&err_msg);
+ }
+
+ return;
+ }
+
+ /* tell user about command line flags */
+ if(ps->prc->type != RemImap){
+ STORE_S *store;
+ SCROLL_S sargs;
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for message."));
+ return;
+ }
+
+ /* TRANSLATORS: several lines that go together */
+ so_puts(store, _("\nYou may want to save a copy of this information!"));
+ so_puts(store, _("\n\nYour Alpine configuration data has been copied to"));
+ so_puts(store, "\n\n ");
+ so_puts(store, rem_pinerc);
+ so_puts(store, "\n");
+ so_puts(store, _("\nTo use that remote configuration from this computer you will"));
+ so_puts(store, _("\nhave to change the way you start Alpine by using the command line option"));
+ so_puts(store, _("\n\"alpine -p <remote_folder>\". The command should probably be"));
+#ifdef _WINDOWS
+ so_puts(store, "\n\n ");
+ so_puts(store, "alpine -p ");
+ so_puts(store, rem_pinerc);
+ so_puts(store, "\n");
+ so_puts(store, _("\nWith PC-Alpine, you may want to create a shortcut which"));
+ so_puts(store, _("\nhas the required arguments."));
+#else
+ so_puts(store, "\n\n ");
+ so_puts(store, "alpine -p \"");
+ so_puts(store, rem_pinerc);
+ so_puts(store, "\"\n");
+ so_puts(store, _("\nThe quotes are there around the last argument to protect the special"));
+ so_puts(store, _("\ncharacters in the folder name (like braces) from the command shell"));
+ so_puts(store, _("\nyou use. If you are not running Alpine from a command shell which knows"));
+ so_puts(store, _("\nabout quoting, it is possible you will have to remove those quotes"));
+ so_puts(store, _("\nfrom the command. For example, if you also use PC-Alpine you will probably"));
+ so_puts(store, _("\nwant to create a shortcut, and you would not need the quotes there."));
+ so_puts(store, _("\nWithout the quotes, the command might look like"));
+ so_puts(store, "\n\n ");
+ so_puts(store, "alpine -p ");
+ so_puts(store, rem_pinerc);
+ so_puts(store, "\n");
+ so_puts(store, _("\nConsider creating an alias or shell script to execute this command to make"));
+ so_puts(store, _("\nit more convenient."));
+#endif
+ so_puts(store, _("\n\nIf you want to use your new remote configuration for this session, quit"));
+ so_puts(store, _("\nAlpine now and restart with the changed command line options mentioned above.\n"));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Remote Config Information");
+ /* TRANSLATORS: a screen title */
+ sargs.bar.title = _("ABOUT REMOTE CONFIG");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+
+ scrolltool(&sargs);
+
+ so_give(&store); /* free resources associated with store */
+ ps->mangled_screen = 1;
+ }
+}
+
+
+int
+verify_folder_name(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ char *tmp;
+
+ tmp = cpystr(given ? given : "");
+ removing_leading_and_trailing_white_space(tmp);
+
+ if(expanded)
+ *expanded = tmp;
+ else
+ fs_give((void **)&tmp);
+
+ if(error)
+ *error = cpystr("");
+
+ return 0;
+}
+
+
+int
+verify_server_name(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ char *tmp;
+
+ tmp = cpystr(given ? given : "");
+ removing_leading_and_trailing_white_space(tmp);
+
+ if(*tmp){
+ /*
+ * could try to verify the hostname here
+ */
+ }
+
+ if(expanded)
+ *expanded = tmp;
+ else
+ fs_give((void **)&tmp);
+
+ if(error)
+ *error = cpystr("");
+
+ return 0;
+}
+
+
+int
+verify_abook_nick(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ int i;
+ char *tmp;
+
+ tmp = cpystr(given ? given : "");
+ removing_leading_and_trailing_white_space(tmp);
+
+ if(strindex(tmp, '"')){
+ fs_give((void **)&tmp);
+ if(error)
+ /* TRANSLATORS: Double quote refers to the " character */
+ *error = cpystr(_("Double quote not allowed in nickname"));
+
+ return -2;
+ }
+
+ for(i = 0; i < as.n_addrbk; i++)
+ if(i != the_one_were_editing && !strcmp(tmp, as.adrbks[i].abnick))
+ break;
+
+ if(i < as.n_addrbk){
+ fs_give((void **)&tmp);
+
+ if(error)
+ *error = cpystr(_("Nickname is already being used"));
+
+ return -2;
+ }
+
+ if(expanded)
+ *expanded = tmp;
+ else
+ fs_give((void **)&tmp);
+
+ if(error)
+ *error = cpystr("");
+
+ return 0;
+}
+
+
+/*
+ * Delete an addressbook.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * command_line -- The screen line on which to prompt
+ * err -- Points to error message
+ *
+ * Returns -- 0, deleted addrbook
+ * -1, addrbook not deleted
+ */
+int
+ab_del_abook(long int cur_line, int command_line, char **err)
+{
+ int abook_num, varnum, delete_data = 0,
+ num_in_list, how_many_in_list, i, cnt, warn_about_revert = 0;
+ char **list, **new_list, **t, **lval;
+ char tmp[200];
+ PerAddrBook *pab;
+ struct variable *vars = ps_global->vars;
+ EditWhich ew;
+ enum {NotSet,
+ Modify,
+ RevertToDefault,
+ OverRideDefault,
+ DontChange} modify_config;
+
+ /* restrict address book config to normal config file */
+ ew = Main;
+
+ if(ps_global->readonly_pinerc){
+ if(err)
+ *err = _("Delete cancelled: config file not changeable");
+
+ return -1;
+ }
+
+ abook_num = adrbk_num_from_lineno(cur_line);
+
+ pab = &as.adrbks[abook_num];
+
+ dprint((2, "- ab_del_abook(%s) -\n",
+ pab->abnick ? pab->abnick : "?"));
+
+ varnum = (pab->type & GLOBAL) ? V_GLOB_ADDRBOOK : V_ADDRESSBOOK;
+
+ if(vars[varnum].is_fixed){
+ if(err){
+ if(pab->type & GLOBAL)
+ *err =
+ _("Cancelled: Sys. Mgmt. does not allow changing global address book config");
+ else
+ *err =
+ _("Cancelled: Sys. Mgmt. does not allow changing address book config");
+ }
+
+ return -1;
+ }
+
+ /*
+ * Deal with reverting to default values of the address book
+ * variables, or with user deleting a default value.
+ */
+ modify_config = NotSet;
+
+ /* First count how many address books are in the user's config. */
+ cnt = 0;
+ lval = LVAL(&vars[varnum], ew);
+ if(lval && lval[0])
+ for(t = lval; *t != NULL; t++)
+ cnt++;
+
+ /*
+ * Easy case, we can just delete one from the user's list.
+ */
+ if(cnt > 1){
+ modify_config = Modify;
+ }
+ /*
+ * Also easy. We'll revert to the default if it exists, and warn
+ * the user about that.
+ */
+ else if(cnt == 1){
+ modify_config = RevertToDefault;
+ /* see if there's a default to revert to */
+ cnt = 0;
+ if(vars[varnum].global_val.l && vars[varnum].global_val.l[0])
+ for(t = vars[varnum].global_val.l; *t != NULL; t++)
+ cnt++;
+
+ warn_about_revert = cnt;
+ }
+ /*
+ * User is already using the default. Split it into two cases. If there
+ * is one address book in default ask user if they want to delete that
+ * default from their config. If there is more than one, ask them if
+ * they want to ignore all the defaults or just delete this one.
+ */
+ else{
+ /* count how many in default */
+ cnt = 0;
+ if(vars[varnum].global_val.l && vars[varnum].global_val.l[0])
+ for(t = vars[varnum].global_val.l; *t != NULL; t++)
+ cnt++;
+
+ if(cnt > 1){
+ static ESCKEY_S opts[] = {
+ /* TRANSLATORS: Ignore All means ignore all of the default values,
+ and Remove One means just remove this one default value. */
+ {'i', 'i', "I", N_("Ignore All")},
+ {'r', 'r', "R", N_("Remove One")},
+ {-1, 0, NULL, NULL}};
+
+ snprintf(tmp, sizeof(tmp),
+ /* TRANSLATORS: %s is an adjective modifying address books */
+ _("Ignore all default %s address books or just remove this one ? "),
+ /* TRANSLATORS: global or personal address books */
+ pab->type & GLOBAL ? _("global") : _("personal"));
+ tmp[sizeof(tmp)-1] = '\0';
+ switch(radio_buttons(tmp, command_line, opts, 'i', 'x',
+ h_ab_del_ignore, RB_NORM)){
+ case 'i':
+ modify_config = OverRideDefault;
+ break;
+
+ case 'r':
+ modify_config = Modify;
+ break;
+
+ case 'x':
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+ }
+ }
+ else{
+ /* TRANSLATORS: a question */
+ switch(want_to(_("Delete this default address book from config "),
+ 'n', 'x', h_ab_del_default, WT_NORM)){
+ case 'n':
+ case 'x':
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+
+ case 'y':
+ modify_config = OverRideDefault;
+ break;
+ }
+ }
+ }
+
+ /*
+ * ReadWrite means it exists and MaybeRorW means it is remote and we
+ * haven't selected it yet to know our access permissions. The remote
+ * folder should have been created, though, unless we didn't even have
+ * permissions for that, in which case we got some error messages earlier.
+ */
+ if(pab->access == ReadWrite || pab->access == MaybeRorW){
+ static ESCKEY_S o[] = {
+ /* TRANSLATORS: user is asked whether to remove just data files, just configuration,
+ or both for an address book. */
+ {'d', 'd', "D", N_("Data")},
+ {'c', 'c', "C", N_("Config")},
+ {'b', 'b', "B", N_("Both")},
+ {-1, 0, NULL, NULL}};
+
+ switch(radio_buttons(_("Delete data, config, or both ? "),
+ command_line, o, 'c', 'x',
+ (modify_config == RevertToDefault)
+ ? h_ab_del_data_revert
+ : h_ab_del_data_modify,
+ RB_NORM)){
+ case 'b': /* Delete Both */
+ delete_data = 1;
+ break;
+
+ case 'd': /* Delete only Data */
+ modify_config = DontChange;
+ delete_data = 1;
+ break;
+
+ case 'c': /* Delete only Config */
+ break;
+
+ case 'x': /* Cancel */
+ default:
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+ }
+ }
+ else{
+ /*
+ * Deleting config for address book which doesn't yet exist (hasn't
+ * ever been opened).
+ */
+ /* TRANSLATORS: a question */
+ switch(want_to(_("Delete configuration for highlighted addressbook "),
+ 'n', 'x',
+ (modify_config == RevertToDefault)
+ ? h_ab_del_config_revert
+ : h_ab_del_config_modify,
+ WT_NORM)){
+ case 'n':
+ case 'x':
+ default:
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+
+ case 'y':
+ break;
+ }
+ }
+
+ if(delete_data){
+ char warning[800];
+
+ dprint((5, "deleting addrbook data\n"));
+ warning[0] = '\0';
+
+ /*
+ * In order to delete the address book it is easiest if we open
+ * it first. That fills in the filenames we want to delete.
+ */
+ if(pab->address_book == NULL){
+ warning[300] = '\0';
+ pab->address_book = adrbk_open(pab, ps_global->home_dir,
+ &warning[300], sizeof(warning)-300,
+ AB_SORT_RULE_NONE);
+ /*
+ * Couldn't get it open.
+ */
+ if(pab->address_book == NULL){
+ if(warning[300])
+ /* TRANSLATORS: %s is an error message */
+ snprintf(warning, 300, _("Can't delete data: %s"), &warning[300]);
+ else
+ strncpy(warning, _("Can't delete address book data"), 100);
+ }
+ }
+
+ /*
+ * If we have it open, set the delete bits and close to get the
+ * local copies. Delete the remote folder by hand.
+ */
+ if(pab->address_book){
+ char *file, *origfile = NULL;
+ int f=0, o=0;
+
+ /*
+ * We're about to destroy addrbook data, better ask again.
+ */
+ if(pab->address_book->count > 0){
+ char prompt[100];
+
+ /* TRANSLATORS: a question */
+ snprintf(prompt, sizeof(prompt),
+ _("About to delete the contents of address book (%ld entries), really delete "), (long) adrbk_count(pab->address_book));
+ prompt[sizeof(prompt)-1] = '\0';
+
+ switch(want_to(prompt, 'n', 'n', h_ab_really_delete, WT_NORM)){
+ case 'y':
+ break;
+
+ case 'n':
+ default:
+ if(err)
+ *err = _("Delete cancelled");
+
+ return -1;
+ }
+ }
+
+ pab->address_book->flags |= DEL_FILE;
+ file = cpystr(pab->address_book->filename);
+ if(pab->type & REMOTE_VIA_IMAP)
+ origfile = cpystr(pab->address_book->orig_filename);
+
+ /*
+ * In order to avoid locking problems when we delete the
+ * remote folder, we need to actually close the remote stream
+ * instead of just putting it back in the stream pool.
+ * So we will remove this stream from the re-usable portion
+ * of the stream pool by clearing the SP_USEPOOL flag.
+ * Init_abook(pab, TotallyClosed) via rd_close_remdata is
+ * going to pine_mail_close it.
+ */
+ if(pab->type && REMOTE_VIA_IMAP
+ && pab->address_book
+ && pab->address_book->type == Imap
+ && pab->address_book->rd
+ && rd_stream_exists(pab->address_book->rd)){
+
+ sp_unflag(pab->address_book->rd->t.i.stream, SP_USEPOOL);
+ }
+
+ /* This deletes the files because of DEL_ bits we set above. */
+ init_abook(pab, TotallyClosed);
+
+ /*
+ * Delete the remote folder.
+ */
+ if(pab->type & REMOTE_VIA_IMAP){
+ REMDATA_S *rd;
+ int exists;
+
+ ps_global->c_client_error[0] = '\0';
+ if(!pine_mail_delete(NULL, origfile) &&
+ ps_global->c_client_error[0] != '\0'){
+ dprint((1, "%s: %s\n", origfile ? origfile : "?",
+ ps_global->c_client_error));
+ }
+
+ /* delete line from metadata */
+ rd = rd_new_remdata(RemImap, origfile, NULL);
+ rd_write_metadata(rd, 1);
+ rd_close_remdata(&rd);
+
+ /* Check to see if it's still there */
+ if((exists=folder_exists(NULL, origfile)) &&
+ (exists != FEX_ERROR)){
+ o++;
+ dprint((1, "Trouble deleting %s\n",
+ origfile ? origfile : "?"));
+ }
+ }
+
+ if(can_access(file, ACCESS_EXISTS) == 0){
+ f++;
+ dprint((1, "Trouble deleting %s\n",
+ file ? file : "?"));
+ }
+
+ if(f || o){
+ snprintf(warning, sizeof(warning), _("Trouble deleting data %s%s%s%s"),
+ f ? file : "",
+ (f && o) ? (o ? ", " : " and ") : "",
+ o ? " and " : "",
+ o ? origfile : "");
+ warning[sizeof(warning)-1] = '\0';
+ }
+
+ fs_give((void **) &file);
+ if(origfile)
+ fs_give((void **) &origfile);
+ }
+
+ if(*warning){
+ q_status_message(SM_ORDER, 3, 3, warning);
+ dprint((1, "%s\n", warning));
+ display_message(NO_OP_COMMAND);
+ }
+ else if(modify_config == DontChange)
+ q_status_message(SM_ORDER, 0, 1, _("Addressbook data deleted"));
+ }
+
+ if(modify_config == DontChange){
+ /*
+ * We return -1 to indicate that the addrbook wasn't deleted (as far
+ * as we're concerned) but we don't fill in err so that no error
+ * message will be printed.
+ * Since the addrbook is still an addrbook we need to reinitialize it.
+ */
+ pab->access = adrbk_access(pab);
+ if(pab->type & GLOBAL && pab->access != NoAccess)
+ pab->access = ReadOnly;
+
+ init_abook(pab, HalfOpen);
+ return -1;
+ }
+ else if(modify_config == Modify){
+ list = vars[varnum].current_val.l;
+ if(pab->type & GLOBAL){
+ how_many_in_list = as.n_addrbk - as.how_many_personals - 1;
+ num_in_list = abook_num - as.how_many_personals;
+ }
+ else{
+ how_many_in_list = as.how_many_personals - 1;
+ num_in_list = abook_num;
+ }
+ }
+ else if(modify_config == OverRideDefault)
+ how_many_in_list = 1;
+ else if(modify_config == RevertToDefault)
+ how_many_in_list = 0;
+ else
+ q_status_message(SM_ORDER, 3, 3, "can't happen in ab_del_abook");
+
+ /* allocate for new list */
+ if(how_many_in_list)
+ new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));
+ else
+ new_list = NULL;
+
+ /*
+ * This case is both for modifying the users user_val and for the
+ * case where the user wants to modify the global_val default and
+ * use the modified version for his or her new user_val. We just
+ * copy from the existing global_val, deleting the one addrbook
+ * and put the result in user_val.
+ */
+ if(modify_config == Modify){
+ /* copy old list up to where we will delete entry */
+ for(i = 0; i < num_in_list; i++)
+ new_list[i] = cpystr(list[i]);
+
+ /* copy rest of old list */
+ for(; i < how_many_in_list; i++)
+ new_list[i] = cpystr(list[i+1]);
+
+ new_list[i] = NULL;
+ }
+ else if(modify_config == OverRideDefault){
+ new_list[0] = cpystr("");
+ new_list[1] = NULL;
+ }
+
+ /* this also frees old variable contents for us */
+ if(set_variable_list(varnum, new_list, TRUE, ew)){
+ if(err)
+ *err = _("Delete cancelled: couldn't save pine configuration file");
+
+ set_current_val(&vars[varnum], TRUE, FALSE);
+ free_list_array(&new_list);
+
+ return -1;
+ }
+
+ set_current_val(&vars[varnum], TRUE, FALSE);
+
+ if(warn_about_revert){
+ /* TRANSLATORS: the %s may be "global " or nothing */
+ snprintf(tmp, sizeof(tmp), _("Reverting to default %saddress books"),
+ pab->type & GLOBAL ? _("global ") : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ q_status_message(SM_ORDER, 3, 4, tmp);
+ }
+
+ free_list_array(&new_list);
+
+ return 0;
+}
+
+
+/*
+ * Shuffle addrbooks.
+ *
+ * Args: pab -- Pab from current addrbook.
+ * slide -- return value, tells how far to slide the cursor. If slide
+ * is negative, slide it up, if positive, slide it down.
+ * command_line -- The screen line on which to prompt
+ * msg -- Points to returned message, if any. Should be freed by
+ * caller.
+ *
+ * Result: Two address books are swapped in the display order. If the shuffle
+ * crosses the Personal/Global boundary, then instead of swapping
+ * two address books the highlighted abook is moved from one section
+ * to the other.
+ * >= 0 on success.
+ * < 0 on failure, no changes.
+ * > 0 If the return value is greater than zero it means that we've
+ * reverted one of the variables to its default value. That
+ * means we've added at least one new addrbook, so the caller
+ * should reset. The value returned is the number of the
+ * moved addrbook + 1 (+1 so it won't be confused with zero).
+ * = 0 If the return value is zero we've just moved addrbooks around.
+ * No reset need be done.
+ */
+int
+ab_shuffle(PerAddrBook *pab, int *slide, int command_line, char **msg)
+{
+ ESCKEY_S opts[3];
+ char tmp[200];
+ int i, deefault, rv, target = 0;
+ int up_into_empty = 0, down_into_empty = 0;
+ HelpType help;
+ struct variable *vars = ps_global->vars;
+
+ dprint((2, "- ab_shuffle() -\n"));
+
+ *slide = 0;
+
+ if(ps_global->readonly_pinerc){
+ if(msg)
+ *msg = cpystr(_("Shuffle cancelled: config file not changeable"));
+
+ return -1;
+ }
+
+ /* Move it up or down? */
+ i = 0;
+ opts[i].ch = 'u';
+ opts[i].rval = 'u';
+ opts[i].name = "U";
+ /* TRANSLATORS: shuffle something Up or Down in a list */
+ opts[i++].label = N_("Up");
+
+ opts[i].ch = 'd';
+ opts[i].rval = 'd';
+ opts[i].name = "D";
+ opts[i++].label = N_("Down");
+
+ opts[i].ch = -1;
+ deefault = 'u';
+
+ if(pab->type & GLOBAL){
+ if(vars[V_GLOB_ADDRBOOK].is_fixed){
+ if(msg)
+ *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing global address book config"));
+
+ return -1;
+ }
+
+ if(as.cur == 0){
+ if(as.config)
+ up_into_empty++;
+ else{ /* no up */
+ opts[0].ch = -2;
+ deefault = 'd';
+ }
+ }
+
+ if(as.cur == as.n_addrbk - 1) /* no down */
+ opts[1].ch = -2;
+ }
+ else{
+ if(vars[V_ADDRESSBOOK].is_fixed){
+ if(msg)
+ *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing address book config"));
+
+ return -1;
+ }
+
+ if(as.cur == 0){ /* no up */
+ opts[0].ch = -2;
+ deefault = 'd';
+ }
+
+ if(as.cur == as.n_addrbk - 1){
+ if(as.config)
+ down_into_empty++;
+ else
+ opts[1].ch = -2; /* no down */
+ }
+ }
+
+ snprintf(tmp, sizeof(tmp), _("Shuffle \"%s\" %s%s%s ? "),
+ pab->abnick,
+ (opts[0].ch != -2) ? _("UP") : "",
+ (opts[0].ch != -2 && opts[1].ch != -2) ? " or " : "",
+ (opts[1].ch != -2) ? _("DOWN") : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ help = (opts[0].ch == -2) ? h_ab_shuf_down
+ : (opts[1].ch == -2) ? h_ab_shuf_up
+ : h_ab_shuf;
+
+ rv = radio_buttons(tmp, command_line, opts, deefault, 'x',
+ help, RB_NORM);
+
+ ps_global->mangled_footer = 1;
+
+ if((rv == 'u' && up_into_empty) || (rv == 'd' && down_into_empty))
+ target = -1;
+ else
+ target = as.cur + (rv == 'u' ? -1 : 1);
+
+ if(rv == 'x'){
+ if(msg)
+ *msg = cpystr(_("Shuffle cancelled"));
+
+ return -1;
+ }
+ else
+ return(do_the_shuffle(slide, as.cur, target, msg));
+}
+
+
+/*
+ * Actually shuffle the config variables and address books structures around.
+ *
+ * Args: anum1, anum2 -- The numbers of the address books
+ * msg -- Points to returned message, if any.
+ *
+ * Returns: >= 0 on success.
+ * < 0 on failure, no changes.
+ * > 0 If the return value is greater than zero it means that we've
+ * reverted one of the variables to its default value. That
+ * means we've added at least one new addrbook, so the caller
+ * should reset. The value returned is the number of the
+ * moved addrbook + 1 (+1 so it won't be confused with zero).
+ * = 0 If the return value is zero we've just moved addrbooks around.
+ * No reset need be done.
+ *
+ * Anum1 is the one that we want to move, anum2 is the one that it will be
+ * swapped with. When anum1 and anum2 are on the opposite sides of the
+ * Personal/Global boundary then instead of swapping we just move anum1 to
+ * the other side of the boundary.
+ *
+ * Anum2 of -1 means it is a swap into the other type of address book, which
+ * is currently empty.
+ */
+int
+do_the_shuffle(int *slide, int anum1, int anum2, char **msg)
+{
+ PerAddrBook *pab;
+ enum {NotSet, Pers, Glob, Empty} type1, type2;
+ int i, j, retval = -1;
+ struct variable *vars = ps_global->vars;
+ char **lval;
+ EditWhich ew;
+ char *cancel_msg = _("Shuffle cancelled: couldn't save configuration file");
+
+ dprint((5, "- do_the_shuffle(%d, %d) -\n", anum1, anum2));
+
+ /* restrict address book config to normal config file */
+ ew = Main;
+
+ if(anum1 == -1)
+ type1 = Empty;
+ else{
+ pab = &as.adrbks[anum1];
+ type1 = (pab->type & GLOBAL) ? Glob : Pers;
+ }
+
+ if(type1 == Empty){
+ if(msg)
+ *msg =
+ cpystr(_("Shuffle cancelled: highlight entry you wish to shuffle"));
+
+ return(retval);
+ }
+
+ if(anum2 == -1)
+ type2 = Empty;
+ else{
+ pab = &as.adrbks[anum2];
+ type2 = (pab->type & GLOBAL) ? Glob : Pers;
+ }
+
+ if(type2 == Empty)
+ type2 = (type1 == Pers) ? Glob : Pers;
+
+ if((type1 == Pers || type2 == Pers) && vars[V_ADDRESSBOOK].is_fixed){
+ if(msg)
+ *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing address book configuration"));
+
+ return(retval);
+ }
+
+ if((type1 == Glob || type2 == Glob) && vars[V_GLOB_ADDRBOOK].is_fixed){
+ if(msg)
+ *msg = cpystr(_("Cancelled: Sys. Mgmt. does not allow changing global address book config"));
+
+ return(retval);
+ }
+
+ /*
+ * There are two cases. If the shuffle is two address books within the
+ * same variable, then they just swap places. If it is a shuffle of an
+ * addrbook from one side of the boundary to the other, just that one
+ * is moved.
+ */
+ if((type1 == Glob && type2 == Glob) ||
+ (type1 == Pers && type2 == Pers)){
+ int how_many_in_list, varnum;
+ int anum1_rel, anum2_rel; /* position in specific list */
+ char **list, **new_list;
+ PerAddrBook tmppab;
+
+ *slide = (anum1 < anum2) ? LINES_PER_ABOOK : -1 * LINES_PER_ABOOK;
+
+ if(type1 == Pers){
+ how_many_in_list = as.how_many_personals;
+ list = VAR_ADDRESSBOOK;
+ varnum = V_ADDRESSBOOK;
+ anum1_rel = anum1;
+ anum2_rel = anum2;
+ }
+ else{
+ how_many_in_list = as.n_addrbk - as.how_many_personals;
+ list = VAR_GLOB_ADDRBOOK;
+ varnum = V_GLOB_ADDRBOOK;
+ anum1_rel = anum1 - as.how_many_personals;
+ anum2_rel = anum2 - as.how_many_personals;
+ }
+
+ /* allocate for new list, same size as old list */
+ new_list = (char **)fs_get((how_many_in_list + 1) * sizeof(char *));
+
+ /* fill in new_list */
+ for(i = 0; i < how_many_in_list; i++){
+ /* swap anum1 and anum2 */
+ if(i == anum1_rel)
+ j = anum2_rel;
+ else if(i == anum2_rel)
+ j = anum1_rel;
+ else
+ j = i;
+
+ new_list[i] = cpystr(list[j]);
+ }
+
+ new_list[i] = NULL;
+
+ if(set_variable_list(varnum, new_list, TRUE, ew)){
+ if(msg)
+ *msg = cpystr(cancel_msg);
+
+ /* restore old values */
+ set_current_val(&vars[varnum], TRUE, FALSE);
+ free_list_array(&new_list);
+ return(retval);
+ }
+
+ retval = 0;
+ set_current_val(&vars[varnum], TRUE, FALSE);
+ free_list_array(&new_list);
+
+ /* Swap PerAddrBook structs */
+ tmppab = as.adrbks[anum1];
+ as.adrbks[anum1] = as.adrbks[anum2];
+ as.adrbks[anum2] = tmppab;
+ }
+ else if((type1 == Pers && type2 == Glob) ||
+ (type1 == Glob && type2 == Pers)){
+ int how_many_in_srclist, how_many_in_dstlist;
+ int srcvarnum, dstvarnum, srcanum;
+ int cnt, warn_about_revert = 0;
+ char **t;
+ char **new_src, **new_dst, **srclist, **dstlist;
+ char tmp[200];
+ enum {NotSet, Modify, RevertToDefault, OverRideDefault} modify_config;
+
+ /*
+ * how_many_in_srclist = # in orig src list (Pers or Glob list).
+ * how_many_in_dstlist = # in orig dst list
+ * srcanum = # of highlighted addrbook that is being shuffled
+ */
+ if(type1 == Pers){
+ how_many_in_srclist = as.how_many_personals;
+ how_many_in_dstlist = as.n_addrbk - as.how_many_personals;
+ srclist = VAR_ADDRESSBOOK;
+ dstlist = VAR_GLOB_ADDRBOOK;
+ srcvarnum = V_ADDRESSBOOK;
+ dstvarnum = V_GLOB_ADDRBOOK;
+ srcanum = as.how_many_personals - 1;
+ *slide = (how_many_in_srclist == 1)
+ ? (LINES_PER_ADD_LINE + XTRA_LINES_BETWEEN)
+ : XTRA_LINES_BETWEEN;
+ }
+ else{
+ how_many_in_srclist = as.n_addrbk - as.how_many_personals;
+ how_many_in_dstlist = as.how_many_personals;
+ srclist = VAR_GLOB_ADDRBOOK;
+ dstlist = VAR_ADDRESSBOOK;
+ srcvarnum = V_GLOB_ADDRBOOK;
+ dstvarnum = V_ADDRESSBOOK;
+ srcanum = as.how_many_personals;
+ *slide = (how_many_in_dstlist == 0)
+ ? (LINES_PER_ADD_LINE + XTRA_LINES_BETWEEN)
+ : XTRA_LINES_BETWEEN;
+ *slide = -1 * (*slide);
+ }
+
+
+ modify_config = Modify;
+ if(how_many_in_srclist == 1){
+ /*
+ * Deal with reverting to default values of the address book
+ * variables, or with user deleting a default value.
+ */
+ modify_config = NotSet;
+
+ /*
+ * Count how many address books are in the user's config.
+ * This has to be one or zero, because how_many_in_srclist == 1.
+ */
+ cnt = 0;
+ lval = LVAL(&vars[srcvarnum], ew);
+ if(lval && lval[0])
+ for(t = lval; *t != NULL; t++)
+ cnt++;
+
+ /*
+ * We'll revert to the default if it exists, and warn
+ * the user about that.
+ */
+ if(cnt == 1){
+ modify_config = RevertToDefault;
+ /* see if there's a default to revert to */
+ cnt = 0;
+ if(vars[srcvarnum].global_val.l &&
+ vars[srcvarnum].global_val.l[0])
+ for(t = vars[srcvarnum].global_val.l; *t != NULL; t++)
+ cnt++;
+
+ warn_about_revert = cnt;
+ if(warn_about_revert > 1 && type1 == Pers)
+ *slide = LINES_PER_ABOOK * warn_about_revert +
+ XTRA_LINES_BETWEEN;
+ }
+ /*
+ * User is already using the default.
+ */
+ else if(cnt == 0){
+ modify_config = OverRideDefault;
+ }
+ }
+
+ /*
+ * We're adding one to the dstlist, so need how_many + 1 + 1.
+ */
+ new_dst = (char **)fs_get((how_many_in_dstlist + 2) * sizeof(char *));
+ j = 0;
+
+ /*
+ * Because the Personal list comes before the Global list, when
+ * we move to Global we're inserting a new first element into
+ * the global list (the dstlist).
+ *
+ * When we move from Global to Personal, we're appending a new
+ * last element onto the personal list (the dstlist).
+ */
+ if(type2 == Glob)
+ new_dst[j++] = cpystr(srclist[how_many_in_srclist-1]);
+
+ for(i = 0; i < how_many_in_dstlist; i++)
+ new_dst[j++] = cpystr(dstlist[i]);
+
+ if(type2 == Pers)
+ new_dst[j++] = cpystr(srclist[0]);
+
+ new_dst[j] = NULL;
+
+ /*
+ * The srclist is complicated by the reverting to default
+ * behaviors.
+ */
+ if(modify_config == Modify){
+ /*
+ * In this case we're just removing one from the srclist
+ * so the new_src is of size how_many -1 +1.
+ */
+ new_src = (char **)fs_get((how_many_in_srclist) * sizeof(char *));
+ j = 0;
+
+ for(i = 0; i < how_many_in_srclist-1; i++)
+ new_src[j++] = cpystr(srclist[i + ((type1 == Glob) ? 1 : 0)]);
+
+ new_src[j] = NULL;
+ }
+ else if(modify_config == OverRideDefault){
+ /*
+ * We were using default and will now revert to nothing.
+ */
+ new_src = (char **)fs_get(2 * sizeof(char *));
+ new_src[0] = cpystr("");
+ new_src[1] = NULL;
+ }
+ else if(modify_config == RevertToDefault){
+ /*
+ * We are moving our last user variable out and reverting
+ * to the default value for this variable.
+ */
+ new_src = NULL;
+ }
+
+ if(set_variable_list(dstvarnum, new_dst, TRUE, ew) ||
+ set_variable_list(srcvarnum, new_src, TRUE, ew)){
+ if(msg)
+ *msg = cpystr(cancel_msg);
+
+ /* restore old values */
+ set_current_val(&vars[dstvarnum], TRUE, FALSE);
+ set_current_val(&vars[srcvarnum], TRUE, FALSE);
+ free_list_array(&new_dst);
+ free_list_array(&new_src);
+ return(retval);
+ }
+
+ set_current_val(&vars[dstvarnum], TRUE, FALSE);
+ set_current_val(&vars[srcvarnum], TRUE, FALSE);
+ free_list_array(&new_dst);
+ free_list_array(&new_src);
+
+ retval = (type1 == Pers && warn_about_revert)
+ ? (warn_about_revert + 1) : (srcanum + 1);
+
+ /*
+ * This is a tough case. We're adding one or more new address books
+ * in this case so we need to reset the addrbooks and start over.
+ * We return the number of the address book we just moved after the
+ * reset so that the caller can focus attention on the moved one.
+ * Actually, we return 1+the number so that we can tell it apart
+ * from a return of zero, which just means everything is ok.
+ */
+ if(warn_about_revert){
+ snprintf(tmp, sizeof(tmp),
+ "This address book now %s, reverting to default %s address %s",
+ (type1 == Glob) ? "Personal" : "Global",
+ (type1 == Glob) ? "Global" : "Personal",
+ warn_about_revert > 1 ? "books" : "book");
+ tmp[sizeof(tmp)-1] = '\0';
+ if(msg)
+ *msg = cpystr(tmp);
+ }
+ else{
+ /*
+ * Modify PerAddrBook struct and adjust boundary.
+ * In this case we aren't swapping two addrbooks, but just modifying
+ * one from being global to personal or the reverse. It will
+ * still be the same element in the as.adrbks array.
+ */
+ pab = &as.adrbks[srcanum];
+ if(type2 == Glob){
+ as.how_many_personals--;
+ pab->type |= GLOBAL;
+ if(pab->access != NoAccess)
+ pab->access = ReadOnly;
+ }
+ else{
+ as.how_many_personals++;
+ pab->type &= ~GLOBAL;
+ if(pab->access != NoAccess && pab->access != MaybeRorW)
+ pab->access = ReadWrite;
+ }
+
+ snprintf(tmp, sizeof(tmp),
+ "This address book now %s",
+ (type1 == Glob) ? "Personal" : "Global");
+ tmp[sizeof(tmp)-1] = '\0';
+ if(msg)
+ *msg = cpystr(tmp);
+ }
+ }
+
+ return(retval);
+}
+
+
+int
+ab_compose_to_addr(long int cur_line, int agg, int allow_role)
+{
+ AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ SAVE_STATE_S state;
+ BuildTo bldto;
+
+ dprint((2, "- ab_compose_to_addr -\n"));
+
+ save_state(&state);
+
+ bldto.type = Str;
+ bldto.arg.str = NULL;
+
+ if(agg){
+ int i;
+ size_t incr = 100, avail, alloced;
+ char *to = NULL;
+
+ to = (char *)fs_get(incr);
+ *to = '\0';
+ avail = incr;
+ alloced = incr;
+
+ /*
+ * Run through all of the selected entries
+ * in all of the address books.
+ * Put the nicknames together into one long
+ * string with comma separators.
+ */
+ for(i = 0; i < as.n_addrbk; i++){
+ adrbk_cntr_t num;
+ PerAddrBook *pab;
+ EXPANDED_S *next_one;
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT){
+ char *a_string;
+ AddrScrn_Disp fake_dl;
+
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
+
+ /*
+ * Since we're picking up address book entries
+ * directly from the address books and have
+ * no knowledge of the display lines they came
+ * from, we don't know the dl's that go with
+ * them. We need to pass a dl to abe_to_nick
+ * but it really is only going to use the
+ * type in this case.
+ */
+ dl = &fake_dl;
+ dl->type = (abe->tag == Single) ? Simple : ListHead;
+ a_string = abe_to_nick_or_addr_string(abe, dl, i);
+
+ while(abe && avail < (size_t)strlen(a_string)+1){
+ alloced += incr;
+ avail += incr;
+ fs_resize((void **)&to, alloced);
+ }
+
+ if(!*to){
+ strncpy(to, a_string, alloced);
+ to[alloced-1] = '\0';
+ }
+ else{
+ strncat(to, ",", alloced-strlen(to)-1);
+ to[alloced-1] = '\0';
+ strncat(to, a_string, alloced-strlen(to)-1);
+ to[alloced-1] = '\0';
+ }
+
+ avail -= (strlen(a_string) + 1);
+ fs_give((void **)&a_string);
+ }
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = to;
+ }
+ else{
+ if(is_addr(cur_line)){
+
+ dl = dlist(cur_line);
+ abe = ae(cur_line);
+
+ if(dl->type == ListEnt){
+ bldto.type = Str;
+ bldto.arg.str = cpystr(listmem(cur_line));
+ }
+ else{
+ bldto.type = Abe;
+ bldto.arg.abe = abe;
+ }
+ }
+ }
+
+ if(bldto.type == Str && bldto.arg.str == NULL)
+ bldto.arg.str = cpystr("");
+
+ ab_compose_internal(bldto, allow_role);
+
+ restore_state(&state);
+
+ if(bldto.type == Str && bldto.arg.str)
+ fs_give((void **)&bldto.arg.str);
+
+ /*
+ * Window size may have changed in composer.
+ * Pine_send will have reset the window size correctly,
+ * but we still have to reset our address book data structures.
+ */
+ ab_resize();
+ ps_global->mangled_screen = 1;
+ return(1);
+}
+
+
+/*
+ * Used by the two compose routines.
+ */
+void
+ab_compose_internal(BuildTo bldto, int allow_role)
+{
+ int good_addr;
+ char *addr, *fcc, *error = NULL;
+ ACTION_S *role = NULL;
+ void (*prev_screen)(struct pine *) = ps_global->prev_screen,
+ (*redraw)(void) = ps_global->redrawer;
+
+ if(allow_role)
+ ps_global->redrawer = NULL;
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ fcc = NULL;
+ addr = NULL;
+
+ good_addr = (our_build_address(bldto, &addr, &error, &fcc, NULL) >= 0);
+
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ if(!good_addr && addr && *addr)
+ fs_give((void **)&addr); /* relying on fs_give setting addr to NULL */
+
+ if(allow_role){
+ /* Setup role */
+ if(role_select_screen(ps_global, &role, MC_COMPOSE) < 0){
+ cmd_cancelled("Composition");
+ ps_global->next_screen = prev_screen;
+ ps_global->redrawer = redraw;
+ return;
+ }
+
+ /*
+ * If default role was selected (NULL) we need to make up a role which
+ * won't do anything, but will cause compose_mail to think there's
+ * already a role so that it won't try to confirm the default.
+ */
+ if(role)
+ role = copy_action(role);
+ else{
+ role = (ACTION_S *)fs_get(sizeof(*role));
+ memset((void *)role, 0, sizeof(*role));
+ role->nick = cpystr("Default Role");
+ }
+ }
+
+ compose_mail(addr, fcc, role, NULL, NULL);
+
+ if(addr)
+ fs_give((void **)&addr);
+
+ if(fcc)
+ fs_give((void **)&fcc);
+}
+
+
+/*
+ * Export addresses into a file.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * command_line -- The screen line on which to prompt
+ *
+ * Returns -- 1 if the export is done
+ * 0 if not
+ */
+int
+ab_export(struct pine *ps, long int cur_line, int command_line, int agg)
+{
+ int ret = 0, i, retflags = GER_NONE;
+ int r, orig_errno, failure = 0;
+ struct variable *vars = ps->vars;
+ char filename[MAXPATH+1], full_filename[MAXPATH+1];
+ STORE_S *store;
+ gf_io_t pc;
+ long start_of_append;
+ char *addr = NULL, *error = NULL;
+ BuildTo bldto;
+ char *p;
+ int good_addr, plur, vcard = 0, tab = 0;
+ static HISTORY_S *history = NULL;
+ AdrBk_Entry *abe;
+ VCARD_INFO_S *vinfo;
+ static ESCKEY_S ab_export_opts[] = {
+ {ctrl('T'), 10, "^T", N_("To Files")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}};
+ static ESCKEY_S vcard_or_addresses[] = {
+ {'a', 'a', "A", N_("Address List")},
+ {'v', 'v', "V", N_("VCard")},
+ /* TRANSLATORS: TabSep is a command key label meaning Tab Separated List */
+ {'t', 't', "T", N_("TabSep")},
+ {-1, 0, NULL, NULL}};
+
+
+ dprint((2, "- ab_export -\n"));
+
+ if(ps->restricted){
+ q_status_message(SM_ORDER, 0, 3,
+ "Alpine demo can't export addresses to files");
+ return(ret);
+ }
+
+ while(1){
+ i = radio_buttons(_("Export list of addresses, vCard format, or Tab Separated ? "),
+ command_line, vcard_or_addresses, 'a', 'x',
+ NO_HELP, RB_NORM|RB_RET_HELP);
+ if(i == 3){
+ /* TRANSLATORS: a screen title */
+ helper(h_ab_export_vcard, _("HELP FOR EXPORT FORMAT"),
+ HLPD_SIMPLE);
+ ps_global->mangled_screen = 1;
+ }
+ else
+ break;
+ }
+
+ switch(i){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book export cancelled"));
+ return(ret);
+
+ case 'a':
+ break;
+
+ case 'v':
+ vcard++;
+ break;
+
+ case 't':
+ tab++;
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3, "can't happen in ab_export");
+ return(ret);
+ }
+
+ if(agg)
+ plur = 1;
+ else{
+ abe = ae(cur_line);
+ plur = (abe && abe->tag == List);
+ }
+
+ filename[0] = '\0';
+ r = 0;
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ ab_export_opts[++r].ch = ctrl('I');
+ ab_export_opts[r].rval = 11;
+ ab_export_opts[r].name = "TAB";
+ ab_export_opts[r].label = N_("Complete");
+ }
+
+ ab_export_opts[++r].ch = -1;
+
+ r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
+ plur ? _("addresses") : _("address"),
+ _("EXPORT"), ab_export_opts,
+ &retflags, command_line, GE_IS_EXPORT, &history);
+
+ if(r < 0){
+ switch(r){
+ case -1:
+ q_status_message(SM_INFO, 0, 2, _("Address book export cancelled"));
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't export to file outside of %s"), VAR_OPER_DIR);
+ break;
+ }
+
+ goto fini;
+ }
+
+ dprint((5, "Opening file \"%s\" for export\n",
+ full_filename ? full_filename : "?"));
+
+ if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error opening file \"%s\" for address export: %s"),
+ full_filename, error_description(errno));
+ goto fini;
+ }
+
+ /*
+ * The write_single_vcard_entry function wants a pc.
+ */
+ if(vcard || tab)
+ gf_set_so_writec(&pc, store);
+
+ start_of_append = so_tell(store);
+
+ if(agg){
+ for(i = 0; !failure && i < as.n_addrbk; i++){
+ adrbk_cntr_t num;
+ PerAddrBook *pab;
+ EXPANDED_S *next_one;
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while(!failure && (num = entry_get_next(&next_one)) != NO_NEXT){
+
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
+ if((vcard || tab) && abe){
+ /*
+ * There is no place to store the charset information
+ * so we don't ask for it.
+ */
+ if(!(vinfo=prepare_abe_for_vcard(ps, abe, 1)))
+ failure++;
+ else{
+ if(vcard)
+ write_single_vcard_entry(ps, pc, vinfo);
+ else
+ write_single_tab_entry(pc, vinfo);
+
+ free_vcard_info(&vinfo);
+ }
+ }
+ else if(abe){
+ bldto.type = Abe;
+ bldto.arg.abe = abe;
+ error = NULL;
+ addr = NULL;
+ good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
+
+ if(error){
+ q_status_message1(SM_ORDER, 0, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ /* rfc1522_decode the addr */
+ if(addr){
+ size_t len;
+
+ len = 4*strlen(addr)+1;
+ p = (char *)fs_get(len * sizeof(char));
+ if(rfc1522_decode_to_utf8((unsigned char *)p,len,addr) == (unsigned char *)p){
+ fs_give((void **)&addr);
+ addr = p;
+ }
+ else
+ fs_give((void **)&p);
+ }
+
+ if(good_addr){
+ int quoted = 0;
+
+ /*
+ * Change the unquoted commas into newlines.
+ * Not worth it to do complicated quoting,
+ * just consider double quotes.
+ */
+ for(p = addr; *p; p++){
+ if(*p == '"')
+ quoted = !quoted;
+ else if(!quoted && *p == ','){
+ *p++ = '\n';
+ removing_leading_white_space(p);
+ p--;
+ }
+ }
+
+ if(!so_puts(store, addr) || !so_puts(store, NEWLINE)){
+ orig_errno = errno;
+ failure = 1;
+ }
+ }
+
+ if(addr)
+ fs_give((void **)&addr);
+ }
+ }
+ }
+ }
+ else{
+ AddrScrn_Disp *dl;
+
+ dl = dlist(cur_line);
+ abe = ae(cur_line);
+ if((vcard || tab) && abe){
+ if(!(vinfo=prepare_abe_for_vcard(ps, abe, 1)))
+ failure++;
+ else{
+ if(vcard)
+ write_single_vcard_entry(ps, pc, vinfo);
+ else
+ write_single_tab_entry(pc, vinfo);
+
+ free_vcard_info(&vinfo);
+ }
+ }
+ else{
+
+ if(dl->type == ListHead && listmem_count_from_abe(abe) == 0){
+ error = _("List is empty, nothing to export!");
+ good_addr = 0;
+ }
+ else if(dl->type == ListEnt){
+ bldto.type = Str;
+ bldto.arg.str = listmem(cur_line);
+ good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
+ }
+ else{
+ bldto.type = Abe;
+ bldto.arg.abe = abe;
+ good_addr = (our_build_address(bldto,&addr,&error,NULL,NULL) >= 0);
+ }
+
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ }
+
+ /* Have to rfc1522_decode the addr */
+ if(addr){
+ size_t len;
+ len = 4*strlen(addr)+1;
+ p = (char *)fs_get(len * sizeof(char));
+ if(rfc1522_decode_to_utf8((unsigned char *)p,len,addr) == (unsigned char *)p){
+ fs_give((void **)&addr);
+ addr = p;
+ }
+ else
+ fs_give((void **)&p);
+ }
+
+ if(good_addr){
+ int quoted = 0;
+
+ /*
+ * Change the unquoted commas into newlines.
+ * Not worth it to do complicated quoting,
+ * just consider double quotes.
+ */
+ for(p = addr; *p; p++){
+ if(*p == '"')
+ quoted = !quoted;
+ else if(!quoted && *p == ','){
+ *p++ = '\n';
+ removing_leading_white_space(p);
+ p--;
+ }
+ }
+
+ if(!so_puts(store, addr) || !so_puts(store, NEWLINE)){
+ orig_errno = errno;
+ failure = 1;
+ }
+ }
+
+ if(addr)
+ fs_give((void **)&addr);
+ }
+ }
+
+ if(vcard || tab)
+ gf_clear_so_writec(store);
+
+ if(so_give(&store)) /* release storage */
+ failure++;
+
+ if(failure){
+ our_truncate(full_filename, (off_t)start_of_append);
+ dprint((1, "FAILED Export: file \"%s\" : %s\n",
+ full_filename ? full_filename : "?",
+ error_description(orig_errno)));
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error exporting to \"%s\" : %s"),
+ filename, error_description(orig_errno));
+ }
+ else{
+ ret = 1;
+ q_status_message3(SM_ORDER,0,3,
+ "%s %s to file \"%s\"",
+ (vcard || tab) ? (agg ? "Entries" : "Entry")
+ : (plur ? "Addresses" : "Address"),
+ retflags & GER_OVER
+ ? "overwritten"
+ : retflags & GER_APPEND ? "appended" : "exported",
+ filename);
+ }
+
+fini:
+ ps->mangled_footer = 1;
+ return(ret);
+}
+
+
+/*
+ * Forward an address book entry or entries via email attachment.
+ *
+ * We use the vCard standard to send entries. We group multiple entries
+ * using the BEGIN/END construct of vCard, not with multiple MIME parts.
+ * A limitation of vCard is that there can be only one charset for the
+ * whole group we send, so we might lose that information.
+ *
+ * Args: cur_line -- The current line position (in global display list)
+ * of cursor
+ * command_line -- The screen line on which to prompt
+ */
+int
+ab_forward(struct pine *ps, long int cur_line, int agg)
+{
+ AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ ENVELOPE *outgoing = NULL;
+ BODY *pb, *body = NULL;
+ PART **pp;
+ char *sig;
+ gf_io_t pc;
+ int i, ret = 0;
+ VCARD_INFO_S *vinfo;
+ ACTION_S *role = NULL;
+
+ dprint((2, "- ab_forward -\n"));
+
+ if(!agg){
+ dl = dlist(cur_line);
+ if(dl->type != ListHead && dl->type != Simple)
+ return(ret);
+
+ abe = ae(cur_line);
+ if(!abe){
+ q_status_message(SM_ORDER, 3, 3, _("Trouble accessing current entry"));
+ return(ret);
+ }
+ }
+
+ outgoing = mail_newenvelope();
+ outgoing->message_id = generate_message_id();
+ if(agg && as.selections > 1)
+ outgoing->subject = cpystr("Forwarded address book entries from Alpine");
+ else
+ outgoing->subject = cpystr("Forwarded address book entry from Alpine");
+
+ body = mail_newbody();
+ body->type = TYPEMULTIPART;
+ /*---- The TEXT part/body ----*/
+ body->nested.part = mail_newbody_part();
+ body->nested.part->body.type = TYPETEXT;
+ /*--- Allocate an object for the body ---*/
+ if((body->nested.part->body.contents.text.data =
+ (void *)so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ int did_sig = 0;
+ long rflags = ROLE_COMPOSE;
+ PAT_STATE dummy;
+
+ pp = &(body->nested.part->next);
+
+ if(nonempty_patterns(rflags, &dummy)){
+ /*
+ * This is really more like Compose, even though it
+ * is called Forward.
+ */
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel */
+ role = NULL;
+ cmd_cancelled("Composition");
+ goto bomb;
+ }
+ }
+
+ if(role)
+ q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""),
+ role->nick);
+
+ if((sig = detoken(role, NULL, 2, 0, 1, NULL, NULL)) != NULL){
+ if(*sig){
+ so_puts((STORE_S *)body->nested.part->body.contents.text.data,
+ sig);
+ did_sig++;
+ }
+
+ fs_give((void **)&sig);
+ }
+
+ /* so we don't have an empty part */
+ if(!did_sig)
+ so_puts((STORE_S *)body->nested.part->body.contents.text.data, "\n");
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Problem creating space for message text"));
+ goto bomb;
+ }
+
+
+ /*---- create the attachment, and write abook entry into it ----*/
+ *pp = mail_newbody_part();
+ pb = &((*pp)->body);
+ pb->type = TYPETEXT;
+ pb->encoding = ENCOTHER; /* let data decide */
+ pb->id = generate_message_id();
+ pb->subtype = cpystr("DIRECTORY");
+ if(agg && as.selections > 1)
+ pb->description = cpystr("Alpine addressbook entries");
+ else
+ pb->description = cpystr("Alpine addressbook entry");
+
+ pb->parameter = NULL;
+ set_parameter(&pb->parameter, "profile", "vCard");
+
+ if((pb->contents.text.data = (void *)so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ int are_some_unqualified = 0, expand_nicks = 0;
+ adrbk_cntr_t num;
+ PerAddrBook *pab;
+ EXPANDED_S *next_one;
+
+ gf_set_so_writec(&pc, (STORE_S *) pb->contents.text.data);
+
+ if(agg){
+ for(i = 0; i < as.n_addrbk && !are_some_unqualified; i++){
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT &&
+ !are_some_unqualified){
+
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
+ if(abe->tag == Single){
+ if(abe->addr.addr && abe->addr.addr[0]
+ && !strindex(abe->addr.addr, '@'))
+ are_some_unqualified++;
+ }
+ else{
+ char **ll;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ if(!strindex(*ll, '@')){
+ are_some_unqualified++;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ else{
+ /*
+ * Search through the addresses to see if there are any
+ * that are unqualified, and so would be different if
+ * expanded.
+ */
+ if(abe->tag == Single){
+ if(abe->addr.addr && abe->addr.addr[0]
+ && !strindex(abe->addr.addr, '@'))
+ are_some_unqualified++;
+ }
+ else{
+ char **ll;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ if(!strindex(*ll, '@')){
+ are_some_unqualified++;
+ break;
+ }
+ }
+ }
+ }
+
+ if(are_some_unqualified){
+ switch(want_to(_("Expand nicknames"), 'y', 'x', h_ab_forward,WT_NORM)){
+ case 'x':
+ gf_clear_so_writec((STORE_S *) pb->contents.text.data);
+ q_status_message(SM_INFO, 0, 2, _("Address book forward cancelled"));
+ goto bomb;
+
+ case 'y':
+ expand_nicks = 1;
+ break;
+
+ case 'n':
+ expand_nicks = 0;
+ break;
+ }
+
+ ps->mangled_footer = 1;
+ }
+
+ if(agg){
+ for(i = 0; i < as.n_addrbk; i++){
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT){
+
+ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num);
+ if(!(vinfo=prepare_abe_for_vcard(ps, abe, expand_nicks))){
+ gf_clear_so_writec((STORE_S *) pb->contents.text.data);
+ goto bomb;
+ }
+ else{
+ write_single_vcard_entry(ps, pc, vinfo);
+ free_vcard_info(&vinfo);
+ }
+ }
+ }
+ }
+ else{
+ if(!(vinfo=prepare_abe_for_vcard(ps, abe, expand_nicks))){
+ gf_clear_so_writec((STORE_S *) pb->contents.text.data);
+ goto bomb;
+ }
+ else{
+ write_single_vcard_entry(ps, pc, vinfo);
+ free_vcard_info(&vinfo);
+ }
+ }
+
+ /* This sets parameter charset, if necessary, and encoding */
+ set_mime_type_by_grope(pb);
+ set_charset_possibly_to_ascii(pb, "UTF-8");
+ pb->size.bytes =
+ strlen((char *)so_text((STORE_S *)pb->contents.text.data));
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Problem creating space for message text"));
+ goto bomb;
+ }
+
+ gf_clear_so_writec((STORE_S *) pb->contents.text.data);
+
+ pine_send(outgoing, &body, _("FORWARDING ADDRESS BOOK ENTRY"), role, NULL,
+ NULL, NULL, NULL, NULL, 0);
+
+ ps->mangled_screen = 1;
+ ret = 1;
+
+bomb:
+ if(outgoing)
+ mail_free_envelope(&outgoing);
+
+ if(body)
+ pine_free_body(&body);
+
+ free_action(&role);
+ return(ret);
+}
+
+
+/*
+ * Given an Adrbk_Entry fill in some of the fields in a VCARD_INFO_S
+ * for use by write_single_vcard_entry. The returned structure is freed
+ * by the caller.
+ */
+VCARD_INFO_S *
+prepare_abe_for_vcard(struct pine *ps, AdrBk_Entry *abe, int expand_nicks)
+{
+ VCARD_INFO_S *vinfo = NULL;
+ char *init_addr = NULL, *addr = NULL, *astring;
+ int cnt;
+ ADDRESS *adrlist = NULL;
+
+ if(!abe)
+ return(vinfo);
+
+ vinfo = (VCARD_INFO_S *) fs_get(sizeof(*vinfo));
+ memset((void *) vinfo, 0, sizeof(*vinfo));
+
+ if(abe->nickname && abe->nickname[0]){
+ vinfo->nickname = (char **) fs_get((1+1) * sizeof(char *));
+ vinfo->nickname[0] = cpystr(abe->nickname);
+ vinfo->nickname[1] = NULL;
+ }
+
+ if(abe->fcc && abe->fcc[0]){
+ vinfo->fcc = (char **) fs_get((1+1) * sizeof(char *));
+ vinfo->fcc[0] = cpystr(abe->fcc);
+ vinfo->fcc[1] = NULL;
+ }
+
+ if(abe->extra && abe->extra[0]){
+ vinfo->note = (char **) fs_get((1+1) * sizeof(char *));
+ vinfo->note[0] = cpystr(abe->extra);
+ vinfo->note[1] = NULL;
+ }
+
+ if(abe->fullname && abe->fullname[0]){
+ char *fn, *last = NULL, *middle = NULL, *first = NULL;
+
+ fn = adrbk_formatname(abe->fullname, &first, &last);
+ if(fn){
+ if(*fn){
+ vinfo->fullname = (char **)fs_get((1+1) * sizeof(char *));
+ vinfo->fullname[0] = fn;
+ vinfo->fullname[1] = NULL;
+ }
+ else
+ fs_give((void **)&fn);
+ }
+
+ if(last && *last){
+ if(first && (middle=strindex(first, ' '))){
+ *middle++ = '\0';
+ middle = skip_white_space(middle);
+ }
+
+ vinfo->last = last;
+ vinfo->first = first;
+ vinfo->middle = middle ? cpystr(middle) : NULL;
+ first = NULL;
+ last = NULL;
+ }
+
+ if(last)
+ fs_give((void **)&last);
+ if(first)
+ fs_give((void **)&first);
+ }
+
+ /* expand nicknames and fully-qualify unqualified names */
+ if(expand_nicks){
+ char *error = NULL;
+ BuildTo bldto;
+ ADDRESS *a;
+
+ if(abe->tag == Single)
+ init_addr = cpystr(abe->addr.addr);
+ else{
+ char **ll;
+ char *p;
+ long length;
+
+ /* figure out how large a string we need to allocate */
+ length = 0L;
+ for(ll = abe->addr.list; ll && *ll; ll++)
+ length += (strlen(*ll) + 2);
+
+ if(length)
+ length -= 2L;
+
+ init_addr = (char *)fs_get((size_t)(length+1L) * sizeof(char));
+ p = init_addr;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ sstrncpy(&p, *ll, length-(p-init_addr));
+ if(*(ll+1))
+ sstrncpy(&p, ", ", length-(p-init_addr));
+ }
+
+ init_addr[length] = '\0';
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = init_addr;
+ our_build_address(bldto, &addr, &error, NULL, NULL);
+ if(error){
+ q_status_message1(SM_ORDER, 3, 4, "%s", error);
+ fs_give((void **)&error);
+ free_vcard_info(&vinfo);
+ return(NULL);
+ }
+
+ if(addr)
+ rfc822_parse_adrlist(&adrlist, addr, ps->maildomain);
+
+ for(cnt = 0, a = adrlist; a; a = a->next)
+ cnt++;
+
+ vinfo->email = (char **)fs_get((cnt+1) * sizeof(char *));
+
+ for(cnt = 0, a = adrlist; a; a = a->next){
+ char *bufp;
+ ADDRESS *next_addr;
+ size_t len;
+
+ next_addr = a->next;
+ a->next = NULL;
+ len = est_size(a);
+ bufp = (char *) fs_get(len * sizeof(char));
+ astring = addr_string(a, bufp, len);
+ a->next = next_addr;
+ vinfo->email[cnt++] = cpystr(astring ? astring : "");
+ fs_give((void **)&bufp);
+ }
+
+ vinfo->email[cnt] = '\0';
+ }
+ else{ /* don't expand or qualify */
+ if(abe->tag == Single){
+ astring =
+ (abe->addr.addr && abe->addr.addr[0]) ? abe->addr.addr : "";
+ vinfo->email = (char **)fs_get((1+1) * sizeof(char *));
+ vinfo->email[0] = cpystr(astring);
+ vinfo->email[1] = '\0';
+ }
+ else{
+ char **ll;
+
+ for(cnt = 0, ll = abe->addr.list; ll && *ll; ll++)
+ cnt++;
+
+ vinfo->email = (char **)fs_get((cnt+1) * sizeof(char *));
+ for(cnt = 0, ll = abe->addr.list; ll && *ll; ll++)
+ vinfo->email[cnt++] = cpystr(*ll);
+
+ vinfo->email[cnt] = '\0';
+ }
+ }
+
+ return(vinfo);
+}
+
+
+void
+free_vcard_info(VCARD_INFO_S **vinfo)
+{
+ if(vinfo && *vinfo){
+ if((*vinfo)->nickname)
+ free_list_array(&(*vinfo)->nickname);
+ if((*vinfo)->fullname)
+ free_list_array(&(*vinfo)->fullname);
+ if((*vinfo)->fcc)
+ free_list_array(&(*vinfo)->fcc);
+ if((*vinfo)->note)
+ free_list_array(&(*vinfo)->note);
+ if((*vinfo)->title)
+ free_list_array(&(*vinfo)->title);
+ if((*vinfo)->tel)
+ free_list_array(&(*vinfo)->tel);
+ if((*vinfo)->email)
+ free_list_array(&(*vinfo)->email);
+
+ if((*vinfo)->first)
+ fs_give((void **)&(*vinfo)->first);
+ if((*vinfo)->middle)
+ fs_give((void **)&(*vinfo)->middle);
+ if((*vinfo)->last)
+ fs_give((void **)&(*vinfo)->last);
+
+ fs_give((void **)vinfo);
+ }
+}
+
+
+/*
+ *
+ */
+void
+write_single_vcard_entry(struct pine *ps, gf_io_t pc, VCARD_INFO_S *vinfo)
+{
+ char *decoded, *tmp2, *tmp = NULL, *hdr;
+ char **ll;
+ int i, did_fn = 0, did_n = 0;
+ int cr;
+ char eol[3];
+#define FOLD_BY 75
+
+ if(!vinfo)
+ return;
+
+#if defined(DOS) || defined(OS2)
+ cr = 1;
+#else
+ cr = 0;
+#endif
+
+ if(cr)
+ strncpy(eol, "\r\n", sizeof(eol));
+ else
+ strncpy(eol, "\n", sizeof(eol));
+
+ eol[sizeof(eol)-1] = '\0';
+
+ gf_puts("BEGIN:VCARD", pc);
+ gf_puts(eol, pc);
+ gf_puts("VERSION:3.0", pc);
+ gf_puts(eol, pc);
+
+ for(i = 0; i < 7; i++){
+ switch(i){
+ case 0:
+ ll = vinfo->nickname;
+ hdr = "NICKNAME:";
+ break;
+
+ case 1:
+ ll = vinfo->fullname;
+ hdr = "FN:";
+ break;
+
+ case 2:
+ ll = vinfo->email;
+ hdr = "EMAIL:";
+ break;
+
+ case 3:
+ ll = vinfo->title;
+ hdr = "TITLE:";
+ break;
+
+ case 4:
+ ll = vinfo->note;
+ hdr = "NOTE:";
+ break;
+
+ case 5:
+ ll = vinfo->fcc;
+ hdr = "X-FCC:";
+ break;
+
+ case 6:
+ ll = vinfo->tel;
+ hdr = "TEL:";
+ break;
+
+ default:
+ panic("can't happen in write_single_vcard_entry");
+ }
+
+ for(; ll && *ll; ll++){
+ decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf), SIZEOF_20KBUF, *ll);
+
+ tmp = vcard_escape(decoded);
+ if(tmp){
+ if((tmp2 = fold(tmp, FOLD_BY, FOLD_BY, hdr, " ", FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
+ gf_puts(tmp2, pc);
+ fs_give((void **)&tmp2);
+ if(i == 1)
+ did_fn++;
+ }
+
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+
+ if(vinfo->last && vinfo->last[0]){
+ char *pl, *pf, *pm;
+
+ pl = vcard_escape(vinfo->last);
+ pf = (vinfo->first && *vinfo->first) ? vcard_escape(vinfo->first)
+ : NULL;
+ pm = (vinfo->middle && *vinfo->middle) ? vcard_escape(vinfo->middle)
+ : NULL;
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s%s%s",
+ (pl && *pl) ? pl : "",
+ ((pf && *pf) || (pm && *pm)) ? ";" : "",
+ (pf && *pf) ? pf : "",
+ (pm && *pm) ? ";" : "",
+ (pm && *pm) ? pm : "");
+
+ if((tmp2 = fold(tmp_20k_buf, FOLD_BY, FOLD_BY, "N:", " ",
+ FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
+ gf_puts(tmp2, pc);
+ fs_give((void **)&tmp2);
+ did_n++;
+ }
+
+ if(pl)
+ fs_give((void **)&pl);
+ if(pf)
+ fs_give((void **)&pf);
+ if(pm)
+ fs_give((void **)&pm);
+ }
+
+ /*
+ * These two types are required in draft-ietf-asid-mime-vcard-06, which
+ * is April 98 and is in last call.
+ */
+ if(!did_fn || !did_n){
+ if(did_n){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s%s%s",
+ (vinfo->first && *vinfo->first) ? vinfo->first : "",
+ (vinfo->first && *vinfo->first &&
+ vinfo->middle && *vinfo->middle) ? " " : "",
+ (vinfo->middle && *vinfo->middle) ? vinfo->middle : "",
+ (((vinfo->first && *vinfo->first) ||
+ (vinfo->middle && *vinfo->middle)) &&
+ vinfo->last && *vinfo->last) ? " " : "",
+ (vinfo->last && *vinfo->last) ? vinfo->last : "");
+
+ tmp = vcard_escape(tmp_20k_buf);
+ if(tmp){
+ if((tmp2 = fold(tmp, FOLD_BY, FOLD_BY, "FN:", " ",
+ FLD_PWS | (cr ? FLD_CRLF : 0))) != NULL){
+ gf_puts(tmp2, pc);
+ fs_give((void **)&tmp2);
+ did_n++;
+ }
+
+ fs_give((void **)&tmp);
+ }
+ }
+ else{
+ if(!did_fn){
+ gf_puts("FN:<Unknown>", pc);
+ gf_puts(eol, pc);
+ }
+
+ gf_puts("N:<Unknown>", pc);
+ gf_puts(eol, pc);
+ }
+ }
+
+ gf_puts("END:VCARD", pc);
+ gf_puts(eol, pc);
+}
+
+
+/*
+ *
+ */
+void
+write_single_tab_entry(gf_io_t pc, VCARD_INFO_S *vinfo)
+{
+ char *decoded, *tmp = NULL;
+ char **ll;
+ int i, first;
+ char *eol;
+
+ if(!vinfo)
+ return;
+
+#if defined(DOS) || defined(OS2)
+ eol = "\r\n";
+#else
+ eol = "\n";
+#endif
+
+ for(i = 0; i < 4; i++){
+ switch(i){
+ case 0:
+ ll = vinfo->nickname;
+ break;
+
+ case 1:
+ ll = vinfo->fullname;
+ break;
+
+ case 2:
+ ll = vinfo->email;
+ break;
+
+ case 3:
+ ll = vinfo->note;
+ break;
+
+ default:
+ panic("can't happen in write_single_tab_entry");
+ }
+
+ if(i)
+ gf_puts("\t", pc);
+
+ for(first = 1; ll && *ll; ll++){
+
+ decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf), SIZEOF_20KBUF, *ll);
+ tmp = vcard_escape(decoded);
+ if(tmp){
+ if(i == 2 && !first)
+ gf_puts(",", pc);
+ else
+ first = 0;
+
+ gf_puts(tmp, pc);
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+
+ gf_puts(eol, pc);
+}
+
+
+/*
+ * for ab_save percent done
+ */
+static int total_to_copy;
+static int copied_so_far;
+int
+percent_done_copying(void)
+{
+ return((copied_so_far * 100) / total_to_copy);
+}
+
+int
+cmp_action_list(const qsort_t *a1, const qsort_t *a2)
+{
+ ACTION_LIST_S *x = (ACTION_LIST_S *)a1;
+ ACTION_LIST_S *y = (ACTION_LIST_S *)a2;
+
+ if(x->pab != y->pab)
+ return((x->pab > y->pab) ? 1 : -1); /* order doesn't matter */
+
+ /*
+ * The only one that matters is when both x and y have dup lit.
+ * For the others, just need to be consistent so sort will terminate.
+ */
+ if(x->dup){
+ if(y->dup)
+ return((x->num_in_dst > y->num_in_dst) ? -1
+ : (x->num_in_dst == y->num_in_dst) ? 0 : 1);
+ else
+ return(-1);
+ }
+ else if(y->dup)
+ return(1);
+ else
+ return((x->num > y->num) ? -1 : (x->num == y->num) ? 0 : 1);
+}
+
+
+/*
+ * Copy a bunch of address book entries to a particular address book.
+ *
+ * Args abook -- the current addrbook handle
+ * cur_line -- the current line the cursor is on
+ * command_line -- the line to prompt on
+ * agg -- 1 if this is an aggregate copy
+ *
+ * Returns 1 if successful, 0 if not
+ */
+int
+ab_save(struct pine *ps, AdrBk *abook, long int cur_line, int command_line, int agg)
+{
+ PerAddrBook *pab_dst, *pab;
+ SAVE_STATE_S state; /* For saving state of addrbooks temporarily */
+ int rc, i;
+ int how_many_dups = 0, how_many_to_copy = 0, skip_dups = 0;
+ int how_many_no_action = 0, ret = 1;
+ int err = 0, need_write = 0, we_cancel = 0;
+ int act_list_size, special_case = 0;
+ adrbk_cntr_t num, new_entry_num;
+ char warn[2][MAX_NICKNAME+1];
+ char warning[MAX_NICKNAME+1];
+ char tmp[MAX(200,2*MAX_NICKNAME+80)];
+ ACTION_LIST_S *action_list = NULL, *al;
+ static ESCKEY_S save_or_export[] = {
+ {'s', 's', "S", N_("Save")},
+ {'e', 'e', "E", N_("Export")},
+ {-1, 0, NULL, NULL}};
+
+ if(!agg)
+ snprintf(tmp, sizeof(tmp), _("Save highlighted entry to address book or Export to filesystem ? "));
+ else if(as.selections > 1)
+ snprintf(tmp, sizeof(tmp), _("Save selected entries to address book or Export to filesystem ? "));
+ else if(as.selections == 1)
+ snprintf(tmp, sizeof(tmp), _("Save selected entry to address book or Export to filesystem ? "));
+ else
+ snprintf(tmp, sizeof(tmp), _("Save to address book or Export to filesystem ? "));
+
+ i = radio_buttons(tmp, -FOOTER_ROWS(ps), save_or_export, 's', 'x',
+ h_ab_save_exp, RB_NORM);
+ switch(i){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
+ return(0);
+
+ case 'e':
+ return(ab_export(ps, cur_line, command_line, agg));
+
+ case 's':
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3, "can't happen in ab_save");
+ return(0);
+ }
+
+ pab_dst = setup_for_addrbook_add(&state, command_line, _("Save"));
+ if(!pab_dst)
+ goto get_out;
+
+ pab = &as.adrbks[as.cur];
+
+ dprint((2, "- ab_save: %s -> %s (agg=%d)-\n",
+ pab->abnick ? pab->abnick : "?",
+ pab_dst->abnick ? pab_dst->abnick : "?", agg));
+
+ if(agg)
+ act_list_size = as.selections;
+ else
+ act_list_size = 1;
+
+ action_list = (ACTION_LIST_S *)fs_get((act_list_size+1) *
+ sizeof(ACTION_LIST_S));
+ memset((void *)action_list, 0, (act_list_size+1) * sizeof(ACTION_LIST_S));
+ al = action_list;
+
+ if(agg){
+
+ for(i = 0; i < as.n_addrbk; i++){
+ EXPANDED_S *next_one;
+
+ pab = &as.adrbks[i];
+ if(pab->address_book)
+ next_one = pab->address_book->selects;
+ else
+ continue;
+
+ while((num = entry_get_next(&next_one)) != NO_NEXT){
+ if(pab != pab_dst &&
+ pab->ostatus != Open &&
+ pab->ostatus != NoDisplay)
+ init_abook(pab, NoDisplay);
+
+ if(pab->ostatus != Open && pab->ostatus != NoDisplay){
+ q_status_message1(SM_ORDER, 0, 4,
+ _("Can't re-open address book %s to save from"),
+ pab->abnick);
+ err++;
+ goto get_out;
+ }
+
+ set_act_list_member(al, (a_c_arg_t)num, pab_dst, pab, warning);
+ if(al->skip)
+ how_many_no_action++;
+ else{
+ if(al->dup){
+ if(how_many_dups < 2 && warning[0]){
+ strncpy(warn[how_many_dups], warning, MAX_NICKNAME);
+ warn[how_many_dups][MAX_NICKNAME] = '\0';
+ }
+
+ how_many_dups++;
+ }
+
+ how_many_to_copy++;
+ }
+
+ al++;
+ }
+ }
+ }
+ else{
+ if(is_addr(cur_line)){
+ AddrScrn_Disp *dl;
+
+ dl = dlist(cur_line);
+
+ if(dl->type == ListEnt)
+ special_case++;
+
+ if(pab && dl){
+ num = dl->elnum;
+ set_act_list_member(al, (a_c_arg_t)num, pab_dst, pab, warning);
+ }
+ else
+ al->skip = 1;
+
+ if(al->skip)
+ how_many_no_action++;
+ else{
+ if(al->dup){
+ if(how_many_dups < 2 && warning[0]){
+ strncpy(warn[how_many_dups], warning, MAX_NICKNAME);
+ warn[how_many_dups][MAX_NICKNAME] = '\0';
+ }
+
+ how_many_dups++;
+ }
+
+ how_many_to_copy++;
+ }
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 4, _("No current entry to save"));
+ goto get_out;
+ }
+ }
+
+ if(how_many_to_copy == 0 && how_many_no_action == 1 && act_list_size == 1)
+ special_case++;
+
+ if(special_case){
+ TA_STATE_S tas, *tasp;
+
+ /* Not going to use the action_list now */
+ if(action_list)
+ fs_give((void **)&action_list);
+
+ tasp = &tas;
+ tas.state = state;
+ tas.pab = pab_dst;
+ take_this_one_entry(ps, &tasp, abook, cur_line);
+
+ /*
+ * If take_this_one_entry or its children didn't do this for
+ * us, we do it here.
+ */
+ if(tas.pab)
+ restore_state(&(tas.state));
+
+ /*
+ * We don't have enough information to know what to return.
+ */
+ return(0);
+ }
+
+ /* nothing to do (except for special Take case below) */
+ if(how_many_to_copy == 0){
+ if(how_many_no_action == 0){
+ err++;
+ goto get_out;
+ }
+ else{
+ restore_state(&state);
+
+ if(how_many_no_action > 1)
+ snprintf(tmp, sizeof(tmp), _("Saved %d entries to %s"), how_many_no_action, pab_dst->abnick);
+ else
+ snprintf(tmp, sizeof(tmp), _("Saved %d entry to %s"), how_many_no_action, pab_dst->abnick);
+
+ tmp[sizeof(tmp)-1] = '\0';
+ q_status_message(SM_ORDER, 0, 4, tmp);
+ if(action_list)
+ fs_give((void **)&action_list);
+
+ return(ret);
+ }
+ }
+
+ /*
+ * If there are some nicknames which already exist in the selected
+ * abook, ask user what to do.
+ */
+ if(how_many_dups > 0){
+ if(how_many_dups == 1)
+ snprintf(tmp, sizeof(tmp), _("Entry with nickname \"%.*s\" already exists, replace "),
+ MAX_NICKNAME, warn[0]);
+ else if(how_many_dups == 2)
+ snprintf(tmp, sizeof(tmp),
+ _("Nicknames \"%.*s\" and \"%.*s\" already exist, replace "),
+ MAX_NICKNAME, warn[0], MAX_NICKNAME, warn[1]);
+ else
+ snprintf(tmp, sizeof(tmp), _("%d of the nicknames already exist, replace "),
+ how_many_dups);
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ switch(want_to(tmp, 'n', 'x', h_ab_copy_dups, WT_NORM)){
+ case 'n':
+ skip_dups++;
+ if(how_many_to_copy == how_many_dups){
+ restore_state(&state);
+ if(action_list)
+ fs_give((void **)&action_list);
+
+ q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
+ return(ret);
+ }
+
+ break;
+
+ case 'y':
+ break;
+
+ case 'x':
+ err++;
+ goto get_out;
+ }
+ }
+
+ /*
+ * Because the deletes happen immediately we have to delete from high
+ * entry number towards lower entry numbers so that we are deleting
+ * the correct entries. In order to do that we'll sort the action_list
+ * to give us a safe order.
+ */
+ if(!skip_dups && how_many_dups > 1)
+ qsort((qsort_t *)action_list, (size_t)as.selections, sizeof(*action_list),
+ cmp_action_list);
+
+ /*
+ * Set up the busy alarm percent counters.
+ */
+ total_to_copy = how_many_to_copy - (skip_dups ? how_many_dups : 0);
+ copied_so_far = 0;
+ we_cancel = busy_cue(_("Saving entries"),
+ (total_to_copy > 4) ? percent_done_copying : NULL, 0);
+
+ /*
+ * Add the list of entries to the destination abook.
+ */
+ for(al = action_list; al && al->pab; al++){
+ AdrBk_Entry *abe;
+
+ if(al->skip || (skip_dups && al->dup))
+ continue;
+
+ if(!(abe = adrbk_get_ae(al->pab->address_book, (a_c_arg_t) al->num))){
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error saving entry: %s"),
+ error_description(errno));
+ err++;
+ goto get_out;
+ }
+
+ /*
+ * Delete existing dups and replace them.
+ */
+ if(al->dup){
+
+ /* delete the existing entry */
+ rc = 0;
+ if(adrbk_delete(pab_dst->address_book,
+ (a_c_arg_t)al->num_in_dst, 1, 0, 0, 0) == 0){
+ need_write++;
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error replacing entry in %s: %s"),
+ pab_dst->abnick,
+ error_description(errno));
+ err++;
+ goto get_out;
+ }
+ }
+
+ /*
+ * Now we have a clean slate to work with.
+ * Add (sorted in correctly) or append abe to the destination
+ * address book.
+ */
+ if(total_to_copy <= 1)
+ rc = adrbk_add(pab_dst->address_book,
+ NO_NEXT,
+ abe->nickname,
+ abe->fullname,
+ abe->tag == Single ? abe->addr.addr : NULL,
+ abe->fcc,
+ abe->extra,
+ abe->tag,
+ &new_entry_num,
+ (int *)NULL,
+ 0,
+ 0,
+ 0);
+ else
+ rc = adrbk_append(pab_dst->address_book,
+ abe->nickname,
+ abe->fullname,
+ abe->tag == Single ? abe->addr.addr : NULL,
+ abe->fcc,
+ abe->extra,
+ abe->tag,
+ &new_entry_num);
+
+ if(rc == 0)
+ need_write++;
+
+ /*
+ * If the entry we copied is a list, we also have to add
+ * the list members to the copy.
+ */
+ if(rc == 0 && abe->tag == List){
+ int save_sort_rule;
+
+ /*
+ * We want it to copy the list in the exact order
+ * without sorting it.
+ */
+ save_sort_rule = pab_dst->address_book->sort_rule;
+ pab_dst->address_book->sort_rule = AB_SORT_RULE_NONE;
+
+ rc = adrbk_nlistadd(pab_dst->address_book,
+ (a_c_arg_t)new_entry_num, NULL, NULL,
+ abe->addr.list,
+ 0, 0, 0);
+
+ pab_dst->address_book->sort_rule = save_sort_rule;
+ }
+
+ if(rc != 0){
+ if(abe && abe->nickname)
+ q_status_message2(SM_ORDER | SM_DING, 3, 5, _("Error saving %s: %s"), abe->nickname, error_description(errno));
+ else
+ q_status_message1(SM_ORDER | SM_DING, 3, 5, _("Error saving entry: %s"), error_description(errno));
+ err++;
+ goto get_out;
+ }
+
+ copied_so_far++;
+ }
+
+ if(need_write){
+ int sort_happened = 0;
+
+ if(adrbk_write(pab_dst->address_book, 0, NULL, &sort_happened, 0, 1)){
+ err++;
+ goto get_out;
+ }
+
+ if(sort_happened)
+ ps_global->mangled_screen = 1;
+ }
+
+get_out:
+ if(we_cancel)
+ cancel_busy_cue(1);
+
+ restore_state(&state);
+ if(action_list)
+ fs_give((void **)&action_list);
+
+ ps_global->mangled_footer = 1;
+
+ if(err){
+ ret = 0;
+ if(need_write)
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Save only partially completed"));
+ else
+ q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
+ }
+ else if (how_many_to_copy + how_many_no_action -
+ (skip_dups ? how_many_dups : 0) > 0){
+
+ ret = 1;
+ snprintf(tmp, sizeof(tmp), "Saved %d %s to %s",
+ how_many_to_copy + how_many_no_action -
+ (skip_dups ? how_many_dups : 0),
+ ((how_many_to_copy + how_many_no_action -
+ (skip_dups ? how_many_dups : 0)) > 1) ? "entries" : "entry",
+ pab_dst->abnick);
+ tmp[sizeof(tmp)-1] = '\0';
+ q_status_message(SM_ORDER, 0, 4, tmp);
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Warn should point to an array of size MAX_NICKNAME+1.
+ */
+void
+set_act_list_member(ACTION_LIST_S *al, a_c_arg_t numarg, PerAddrBook *pab_dst, PerAddrBook *pab, char *warn)
+{
+ AdrBk_Entry *abe1, *abe2;
+ adrbk_cntr_t num;
+
+ num = (adrbk_cntr_t)numarg;
+
+ al->pab = pab;
+ al->num = num;
+
+ /* skip if they're copying from and to same addrbook */
+ if(pab == pab_dst)
+ al->skip = 1;
+ else{
+ abe1 = adrbk_get_ae(pab->address_book, numarg);
+ if(abe1 && abe1->nickname && abe1->nickname[0]){
+ adrbk_cntr_t dst_enum;
+
+ abe2 = adrbk_lookup_by_nick(pab_dst->address_book,
+ abe1->nickname, &dst_enum);
+ /*
+ * This nickname already exists in the destn address book.
+ */
+ if(abe2){
+ /* If it isn't different, no problem. Check it out. */
+ if(abes_are_equal(abe1, abe2))
+ al->skip = 1;
+ else{
+ strncpy(warn, abe1->nickname, MAX_NICKNAME);
+ warn[MAX_NICKNAME] = '\0';
+ al->dup = 1;
+ al->num_in_dst = dst_enum;
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * Print out the display list.
+ */
+int
+ab_print(int agg)
+{
+ int do_entry = 0, curopen;
+ char *prompt;
+
+ dprint((2, "- ab_print -\n"));
+
+ curopen = cur_is_open();
+ if(!agg && curopen){
+ static ESCKEY_S prt[] = {
+ {'a', 'a', "A", N_("AddressBook")},
+ {'e', 'e', "E", N_("Entry")},
+ {-1, 0, NULL, NULL}};
+
+ prompt = _("Print Address Book or just this Entry? ");
+ switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global), prt, 'a', 'x',
+ NO_HELP, RB_NORM)){
+ case 'x' :
+ q_status_message(SM_INFO, 0, 2, _("Address book print cancelled"));
+ ps_global->mangled_footer = 1;
+ return 0;
+
+ case 'e':
+ do_entry = 1;
+ break;
+
+ default:
+ case 'a':
+ break;
+ }
+ }
+
+ /* TRANSLATORS: This is input for
+ Print something1 using something2. The thing we're
+ defining here is something1. */
+ if(agg)
+ prompt = _("selected entries");
+ else{
+ if(!curopen)
+ prompt = _("address book list");
+ else if(do_entry)
+ prompt = _("entry");
+ else
+ prompt = _("address book");
+ }
+
+ if(open_printer(prompt) == 0){
+ DL_CACHE_S dlc_buf, *match_dlc;
+ AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ long save_line;
+ char *addr;
+ char spaces[100];
+ char more_spaces[100];
+ char b[500];
+ int abook_indent;
+
+ save_line = as.top_ent + as.cur_row;
+ match_dlc = get_dlc(save_line);
+ dlc_buf = *match_dlc;
+ match_dlc = &dlc_buf;
+
+ if(do_entry){ /* print an individual addrbook entry */
+
+ abook_indent = utf8_width(_("Nickname")) + 2;
+
+ snprintf(spaces, sizeof(spaces), "%*.*s", abook_indent+2, abook_indent+2, "");
+ snprintf(more_spaces, sizeof(more_spaces), "%*.*s",
+ abook_indent+4, abook_indent+4, "");
+
+ dl = dlist(save_line);
+ abe = ae(save_line);
+
+ if(abe){
+ int are_some_unqualified = 0, expand_nicks = 0;
+ char *string, *tmp;
+ ADDRESS *adrlist = NULL;
+
+ /*
+ * Search through the addresses to see if there are any
+ * that are unqualified, and so would be different if
+ * expanded.
+ */
+ if(abe->tag == Single){
+ if(abe->addr.addr && abe->addr.addr[0]
+ && !strindex(abe->addr.addr, '@'))
+ are_some_unqualified++;
+ }
+ else{
+ char **ll;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ if(!strindex(*ll, '@')){
+ are_some_unqualified++;
+ break;
+ }
+ }
+ }
+
+ if(are_some_unqualified){
+ switch(want_to("Expand nicknames", 'y', 'x', h_ab_forward,
+ WT_NORM)){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book print cancelled"));
+ ps_global->mangled_footer = 1;
+ return 0;
+
+ case 'y':
+ expand_nicks = 1;
+ break;
+
+ case 'n':
+ expand_nicks = 0;
+ break;
+ }
+ }
+
+ /* expand nicknames and fully-qualify unqualified names */
+ if(expand_nicks){
+ char *error = NULL;
+ BuildTo bldto;
+ char *init_addr = NULL;
+
+ if(abe->tag == Single)
+ init_addr = cpystr(abe->addr.addr);
+ else{
+ char **ll;
+ char *p;
+ long length;
+
+ /* figure out how large a string we need to allocate */
+ length = 0L;
+ for(ll = abe->addr.list; ll && *ll; ll++)
+ length += (strlen(*ll) + 2);
+
+ if(length)
+ length -= 2L;
+
+ init_addr = (char *)fs_get((size_t)(length+1L) *
+ sizeof(char));
+ p = init_addr;
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ sstrncpy(&p, *ll, length-(p-init_addr));
+ if(*(ll+1))
+ sstrncpy(&p, ", ", length-(p-init_addr));
+ }
+
+ init_addr[length] = '\0';
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = init_addr;
+ our_build_address(bldto, &addr, &error, NULL, NULL);
+ if(init_addr)
+ fs_give((void **)&init_addr);
+
+ if(error){
+ q_status_message1(SM_ORDER, 0, 4, "%s", error);
+ fs_give((void **)&error);
+
+ ps_global->mangled_footer = 1;
+ return 0;
+ }
+
+ if(addr){
+ rfc822_parse_adrlist(&adrlist, addr,
+ ps_global->maildomain);
+ fs_give((void **)&addr);
+ }
+
+ /* Will use adrlist to do the printing below */
+ }
+
+ tmp = abe->nickname ? abe->nickname : "";
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Nickname"));
+ if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ tmp = abe->fullname ? abe->fullname : "";
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fullname"));
+ if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ tmp = abe->fcc ? abe->fcc : "";
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Fcc"));
+ if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ tmp = abe->extra ? abe->extra : "";
+ {unsigned char *p, *bb = NULL;
+ size_t n, len;
+ if((n = 4*strlen(tmp)) > SIZEOF_20KBUF-1){
+ len = n+1;
+ p = bb = (unsigned char *)fs_get(len * sizeof(char));
+ }
+ else{
+ len = SIZEOF_20KBUF;
+ p = (unsigned char *)tmp_20k_buf;
+ }
+
+ string = (char *)rfc1522_decode_to_utf8(p, len, tmp);
+ utf8_snprintf(b, len, "%-*.*w: ", abook_indent, abook_indent, _("Comment"));
+ if((tmp = fold(string, 80, 80, b, spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ if(bb)
+ fs_give((void **)&bb);
+ }
+
+ /*
+ * Print addresses
+ */
+
+ if(expand_nicks){
+ ADDRESS *a;
+
+ for(a = adrlist; a; a = a->next){
+ char *bufp;
+ ADDRESS *next_addr;
+ size_t len;
+
+ next_addr = a->next;
+ a->next = NULL;
+ len = est_size(a);
+ bufp = (char *) fs_get(len * sizeof(char));
+ tmp = addr_string(a, bufp, len);
+ a->next = next_addr;
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf+10000,
+ SIZEOF_20KBUF-10000, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ if((tmp = fold(string, 80, 80,
+ (a == adrlist) ? b : spaces,
+ more_spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+
+ fs_give((void **)&bufp);
+ }
+
+ if(adrlist)
+ mail_free_address(&adrlist);
+ else{
+ utf8_snprintf(b, sizeof(b), "%-*.*w:\n", abook_indent, abook_indent, _("Addresses"));
+ print_text(b);
+ }
+ }
+ else{ /* don't expand or qualify */
+ if(abe->tag == Single){
+ tmp = abe->addr.addr ? abe->addr.addr : "";
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *) (tmp_20k_buf+10000),
+ SIZEOF_20KBUF-10000, tmp);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ if((tmp = fold(string, 80, 80, b,
+ more_spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+ else{
+ char **ll;
+
+ if(!abe->addr.list || !abe->addr.list[0]){
+ utf8_snprintf(b, sizeof(b), "%-*.*w:\n", abook_indent, abook_indent, _("Addresses"));
+ print_text(b);
+ }
+
+ for(ll = abe->addr.list; ll && *ll; ll++){
+ string = (char *)rfc1522_decode_to_utf8((unsigned char *) (tmp_20k_buf+10000),
+ SIZEOF_20KBUF-10000, *ll);
+ utf8_snprintf(b, sizeof(b), "%-*.*w: ", abook_indent, abook_indent, _("Addresses"));
+ if((tmp = fold(string, 80, 80,
+ (ll == abe->addr.list)
+ ? b : spaces,
+ more_spaces, FLD_NONE)) != NULL){
+ print_text(tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+ }
+ }
+ }
+ else{
+ long lineno;
+ char lbuf[6*MAX_SCREEN_COLS + 1];
+ char *p;
+ int i, savecur, savezoomed;
+ OpenStatus savestatus;
+ PerAddrBook *pab;
+
+ if(agg){ /* print all selected entries */
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
+ savezoomed = as.zoomed;
+ /*
+ * Fool display code into thinking display is zoomed, so
+ * we'll skip the unselected entries. Since this feature
+ * causes all abooks to be displayed we don't have to
+ * step through each addrbook.
+ */
+ as.zoomed = 1;
+ warp_to_beginning();
+ lineno = 0L;
+ for(dl = dlist(lineno);
+ dl->type != End;
+ dl = dlist(++lineno)){
+
+ switch(dl->type){
+ case Beginning:
+ case ListClickHere:
+ case ListEmpty:
+ case ClickHereCmb:
+ case Text:
+ case TitleCmb:
+ case Empty:
+ case ZoomEmpty:
+ case AskServer:
+ continue;
+ default:
+ break;
+ }
+
+ p = get_abook_display_line(lineno, 0, NULL, NULL,
+ NULL, lbuf, sizeof(lbuf));
+ print_text1("%s\n", p);
+ }
+
+ as.zoomed = savezoomed;
+ }
+ else{ /* print all selected entries */
+ savecur = as.cur;
+ savezoomed = as.zoomed;
+ as.zoomed = 1;
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(!(pab->address_book &&
+ any_selected(pab->address_book->selects)))
+ continue;
+
+ /*
+ * Print selected entries from addrbook i.
+ * We have to put addrbook i into Open state so
+ * that the display code will work right.
+ */
+ as.cur = i;
+ savestatus = pab->ostatus;
+ init_abook(pab, Open);
+ init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
+ (void)calculate_field_widths();
+ warp_to_beginning();
+ lineno = 0L;
+
+ for(dl = dlist(lineno);
+ dl->type != End;
+ dl = dlist(++lineno)){
+
+ switch(dl->type){
+ case Beginning:
+ case ListClickHere:
+ case ClickHereCmb:
+ continue;
+ default:
+ break;
+ }
+
+ p = get_abook_display_line(lineno, 0, NULL, NULL,
+ NULL, lbuf,sizeof(lbuf));
+ print_text1("%s\n", p);
+ }
+
+ init_abook(pab, savestatus);
+ }
+
+ as.cur = savecur;
+ as.zoomed = savezoomed;
+ /* restore the display for the current addrbook */
+ init_disp_form(&as.adrbks[as.cur],
+ ps_global->VAR_ABOOK_FORMATS, as.cur);
+ }
+ }
+ else{ /* print either the abook list or a single abook */
+ int anum;
+ DL_CACHE_S *dlc;
+
+ savezoomed = as.zoomed;
+ as.zoomed = 0;
+
+ if(curopen){ /* print a single address book */
+ anum = adrbk_num_from_lineno(as.top_ent+as.cur_row);
+ warp_to_top_of_abook(anum);
+ if(F_ON(F_CMBND_ABOOK_DISP,ps_global))
+ lineno = 0L - XTRA_TITLE_LINES_IN_OLD_ABOOK_DISP;
+ else{
+ print_text(" ");
+ print_text(_("ADDRESS BOOK"));
+ print_text1(" %s\n\n", as.adrbks[as.cur].abnick);
+ lineno = 0L;
+ }
+ }
+ else{ /* print the list of address books */
+ warp_to_beginning();
+ lineno = 0L;
+ }
+
+ for(dl = dlist(lineno);
+ dl->type != End && (!curopen ||
+ (anum==adrbk_num_from_lineno(lineno) &&
+ (as.n_serv == 0 ||
+ ((dlc=get_dlc(lineno)) &&
+ dlc->type != DlcDirDelim1))));
+ dl = dlist(++lineno)){
+
+ switch(dl->type){
+ case Beginning:
+ case ListClickHere:
+ case ClickHereCmb:
+ continue;
+ default:
+ break;
+ }
+
+ p = get_abook_display_line(lineno, 0, NULL, NULL, NULL,
+ lbuf, sizeof(lbuf));
+ print_text1("%s\n", p);
+ }
+
+ as.zoomed = savezoomed;
+ }
+ }
+
+ close_printer();
+
+ /*
+ * jump cache back to where we started so that the next
+ * request won't cause us to page through the whole thing
+ */
+ if(!do_entry)
+ warp_to_dlc(match_dlc, save_line);
+
+ ps_global->mangled_screen = 1;
+ }
+
+ ps_global->mangled_footer = 1;
+ return 1;
+}
+
+
+/*
+ * Delete address book entries.
+ */
+int
+ab_agg_delete(struct pine *ps, int agg)
+{
+ int ret = 0, i, ch, rc = 0;
+ PerAddrBook *pab;
+ adrbk_cntr_t num, ab_count;
+ char prompt[80];
+
+ dprint((2, "- ab_agg_delete -\n"));
+
+ if(agg){
+ snprintf(prompt, sizeof(prompt), _("Really delete %d selected entries"), as.selections);
+ prompt[sizeof(prompt)-1] = '\0';
+ ch = want_to(prompt, 'n', 'n', NO_HELP, WT_NORM);
+ if(ch == 'y'){
+ adrbk_cntr_t newelnum, flushelnum = NO_NEXT;
+ DL_CACHE_S dlc_save, dlc_restart, *dlc;
+ int we_cancel = 0;
+ int top_level_display;
+
+ /*
+ * We want to try to put the cursor in a reasonable position
+ * on the screen when we're done. If we are in the top-level
+ * display, then we can leave it the same. If we have an
+ * addrbook opened, then we want to see if we can get back to
+ * the same entry we are currently on.
+ */
+ if(!(top_level_display = !any_ab_open())){
+ dlc = get_dlc(as.top_ent+as.cur_row);
+ dlc_save = *dlc;
+ newelnum = dlc_save.dlcelnum;
+ }
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ for(i = 0; i < as.n_addrbk && rc != -5; i++){
+ int orig_selected, selected;
+
+ pab = &as.adrbks[i];
+ if(!pab->address_book)
+ continue;
+
+ ab_count = adrbk_count(pab->address_book);
+ rc = 0;
+ selected = howmany_selected(pab->address_book->selects);
+ orig_selected = selected;
+ /*
+ * Because deleting an entry causes the addrbook to be
+ * immediately updated, we need to delete from higher entry
+ * numbers to lower numbers. That way, entry number n is still
+ * entry number n in the updated address book because we've
+ * only deleted entries higher than n.
+ */
+ for(num = ab_count-1; selected > 0 && rc == 0; num--){
+ if(entry_is_selected(pab->address_book->selects,
+ (a_c_arg_t)num)){
+ rc = adrbk_delete(pab->address_book, (a_c_arg_t)num,
+ 1, 0, 0, 0);
+
+ selected--;
+
+ /*
+ * This is just here to help us reposition the cursor.
+ */
+ if(!top_level_display && as.cur == i && rc == 0){
+ if(num >= newelnum)
+ flushelnum = num;
+ else
+ newelnum--;
+ }
+ }
+ }
+
+ if(rc == 0 && orig_selected > 0){
+ int sort_happened = 0;
+
+ rc = adrbk_write(pab->address_book, 0, NULL, &sort_happened, 1, 0);
+ if(sort_happened)
+ ps_global->mangled_screen = 1;
+ }
+
+ if(rc && rc != -5){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ "Error updating %s: %s",
+ (as.n_addrbk > 1) ? pab->abnick
+ : "address book",
+ error_description(errno));
+ dprint((1, "Error updating %s: %s\n",
+ pab->filename ? pab->filename : "?",
+ error_description(errno)));
+ }
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(rc == 0){
+ q_status_message(SM_ORDER, 0, 2, _("Deletions completed"));
+ ret = 1;
+ erase_selections();
+ }
+
+ if(!top_level_display){
+ int lost = 0;
+ long new_ent;
+
+ if(flushelnum != dlc_save.dlcelnum){
+ /*
+ * We didn't delete current so restart there. The elnum
+ * may have changed if we deleted entries above it.
+ */
+ dlc_restart = dlc_save;
+ dlc_restart.dlcelnum = newelnum;
+ }
+ else{
+ /*
+ * Current was deleted.
+ */
+ dlc_restart.adrbk_num = as.cur;
+ pab = &as.adrbks[as.cur];
+ ab_count = adrbk_count(pab->address_book);
+ if(ab_count == 0)
+ dlc_restart.type = DlcEmpty;
+ else{
+ AdrBk_Entry *abe;
+
+ dlc_restart.dlcelnum = MIN(newelnum, ab_count-1);
+ abe = adrbk_get_ae(pab->address_book,
+ (a_c_arg_t) dlc_restart.dlcelnum);
+ if(abe && abe->tag == Single)
+ dlc_restart.type = DlcSimple;
+ else if(abe && abe->tag == List)
+ dlc_restart.type = DlcListHead;
+ else
+ lost++;
+ }
+ }
+
+ if(lost){
+ warp_to_top_of_abook(as.cur);
+ as.top_ent = 0L;
+ new_ent = first_selectable_line(0L);
+ if(new_ent == NO_LINE)
+ as.cur_row = 0L;
+ else
+ as.cur_row = new_ent;
+
+ /* if it is off screen */
+ if(as.cur_row >= as.l_p_page){
+ as.top_ent += (as.cur_row - as.l_p_page + 1);
+ as.cur_row = (as.l_p_page - 1);
+ }
+ }
+ else if(dlc_restart.type != DlcEmpty &&
+ dlc_restart.dlcelnum == dlc_save.dlcelnum &&
+ (F_OFF(F_CMBND_ABOOK_DISP,ps_global) || as.cur == 0)){
+ /*
+ * Didn't delete any before this line.
+ * Leave screen about the same. (May have deleted current.)
+ */
+ warp_to_dlc(&dlc_restart, as.cur_row+as.top_ent);
+ }
+ else{
+ warp_to_dlc(&dlc_restart, 0L);
+ /* put in middle of screen */
+ as.top_ent = first_line(0L - (long)as.l_p_page/2L);
+ as.cur_row = 0L - as.top_ent;
+ }
+
+ ps->mangled_body = 1;
+ }
+ }
+ else
+ cmd_cancelled("Apply Delete command");
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Delete an entry from the address book
+ *
+ * Args: abook -- The addrbook handle into access library
+ * command_line -- The screen line on which to prompt
+ * cur_line -- The entry number in the display list
+ * warped -- We warped to a new part of the addrbook
+ *
+ * Result: returns 1 if an entry was deleted, 0 if not.
+ *
+ * The main routine above knows what to repaint because it's always the
+ * current entry that's deleted. Here confirmation is asked of the user
+ * and the appropriate adrbklib functions are called.
+ */
+int
+single_entry_delete(AdrBk *abook, long int cur_line, int *warped)
+{
+ char ch, *cmd, *dname;
+ char prompt[200];
+ int rc;
+ register AddrScrn_Disp *dl;
+ AdrBk_Entry *abe;
+ DL_CACHE_S *dlc_to_flush;
+
+ dprint((2, "- single_entry_delete -\n"));
+
+ if(warped)
+ *warped = 0;
+
+ dl = dlist(cur_line);
+ abe = adrbk_get_ae(abook, (a_c_arg_t) dl->elnum);
+
+ switch(dl->type){
+ case Simple:
+ dname = (abe->fullname && abe->fullname[0])
+ ? (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fullname)
+ : abe->nickname ? abe->nickname : "";
+ cmd = _("Really delete \"%s\"");
+ break;
+
+ case ListHead:
+ dname = (abe->fullname && abe->fullname[0])
+ ? (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fullname)
+ : abe->nickname ? abe->nickname : "";
+ cmd = _("Really delete ENTIRE list \"%s\"");
+ break;
+
+ case ListEnt:
+ dname = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, listmem_from_dl(abook, dl));
+ cmd = _("Really delete \"%s\" from list");
+ break;
+
+ default:
+ break;
+ }
+
+ dname = dname ? dname : "";
+ cmd = cmd ? cmd : "";
+
+ snprintf(prompt, sizeof(prompt), cmd, dname);
+ prompt[sizeof(prompt)-1] = '\0';
+ ch = want_to(prompt, 'n', 'n', NO_HELP, WT_NORM);
+ if(ch == 'y'){
+ dlc_to_flush = get_dlc(cur_line);
+ if(dl->type == Simple || dl->type == ListHead){
+ /*--- Kill a single entry or an entire list ---*/
+ rc = adrbk_delete(abook, (a_c_arg_t)dl->elnum, 1, 1, 0, 1);
+ }
+ else if(listmem_count_from_abe(abe) > 2){
+ /*---- Kill an entry out of a list ----*/
+ rc = adrbk_listdel(abook, (a_c_arg_t)dl->elnum,
+ listmem_from_dl(abook, dl));
+ }
+ else{
+ char *nick, *full, *addr, *fcc, *comment;
+ adrbk_cntr_t new_entry_num = NO_NEXT;
+
+ /*---- Convert a List to a Single entry ----*/
+
+ /* Save old info to be transferred */
+ nick = cpystr(abe->nickname);
+ full = cpystr(abe->fullname);
+ fcc = cpystr(abe->fcc);
+ comment = cpystr(abe->extra);
+ if(listmem_count_from_abe(abe) == 2)
+ addr = cpystr(abe->addr.list[1 - dl->l_offset]);
+ else
+ addr = cpystr("");
+
+ rc = adrbk_delete(abook, (a_c_arg_t)dl->elnum, 0, 1, 0, 0);
+ if(rc == 0)
+ adrbk_add(abook,
+ NO_NEXT,
+ nick,
+ full,
+ addr,
+ fcc,
+ comment,
+ Single,
+ &new_entry_num,
+ (int *)NULL,
+ 1,
+ 0,
+ 1);
+
+ fs_give((void **)&nick);
+ fs_give((void **)&full);
+ fs_give((void **)&fcc);
+ fs_give((void **)&comment);
+ fs_give((void **)&addr);
+
+ if(rc == 0){
+ DL_CACHE_S dlc_restart;
+
+ dlc_restart.adrbk_num = as.cur;
+ dlc_restart.dlcelnum = new_entry_num;
+ dlc_restart.type = DlcSimple;
+ warp_to_dlc(&dlc_restart, 0L);
+ *warped = 1;
+ return 1;
+ }
+ }
+
+ if(rc == 0){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Entry deleted, address book updated"));
+ dprint((5, "abook: Entry %s\n",
+ (dl->type == Simple || dl->type == ListHead) ? "deleted"
+ : "modified"));
+ /*
+ * Remove deleted line and everything after it from
+ * the dlc cache. Next time we try to access those lines they
+ * will get filled in with the right info.
+ */
+ flush_dlc_from_cache(dlc_to_flush);
+ return 1;
+ }
+ else{
+ PerAddrBook *pab;
+
+ if(rc != -5)
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error updating address book: %s"),
+ error_description(errno));
+ pab = &as.adrbks[as.cur];
+ dprint((1, "Error deleting entry from %s (%s): %s\n",
+ pab->abnick ? pab->abnick : "?",
+ pab->filename ? pab->filename : "?",
+ error_description(errno)));
+ }
+
+ return 0;
+ }
+ else{
+ q_status_message(SM_INFO, 0, 2, _("Entry not deleted"));
+ return 0;
+ }
+}
+
+
+void
+free_headents(struct headerentry **head)
+{
+ struct headerentry *he;
+ PrivateTop *pt;
+
+ if(head && *head){
+ for(he = *head; he->name; he++)
+ if(he->bldr_private){
+ pt = (PrivateTop *)he->bldr_private;
+ free_privatetop(&pt);
+ }
+
+ fs_give((void **)head);
+ }
+}
+
+
+#ifdef ENABLE_LDAP
+/*
+prompt::name::help::prwid::maxlen::realaddr::
+builder::affected_entry::next_affected::selector::key_label::fileedit::
+display_it::break_on_comma::is_attach::rich_header::only_file_chars::
+single_space::sticky::dirty::start_here::blank::KS_ODATAVAR
+*/
+static struct headerentry headents_for_query[]={
+ {"Normal Search : ", "NormalSearch", h_composer_qserv_qq, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Name : ", "Name", h_composer_qserv_cn, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Surname : ", "SurName", h_composer_qserv_sn, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Given Name : ", "GivenName", h_composer_qserv_gn, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Email Address : ", "EmailAddress", h_composer_qserv_mail, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Organization : ", "Organization", h_composer_qserv_org, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Org Unit : ", "OrganizationalUnit", h_composer_qserv_unit, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Country : ", "Country", h_composer_qserv_country, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"State : ", "State", h_composer_qserv_state, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Locality : ", "Locality", h_composer_qserv_locality, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {" ", "BlankLine", NO_HELP, 1, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, KS_NONE},
+ {"Custom Filter : ", "CustomFilter", h_composer_qserv_custom, 16, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
+};
+#define QQ_QQ 0
+#define QQ_CN 1
+#define QQ_SN 2
+#define QQ_GN 3
+#define QQ_MAIL 4
+#define QQ_ORG 5
+#define QQ_UNIT 6
+#define QQ_COUNTRY 7
+#define QQ_STATE 8
+#define QQ_LOCALITY 9
+#define QQ_BLANK 10
+#define QQ_CUSTOM 11
+#define QQ_END 12
+
+static SAVED_QUERY_S *saved_params;
+
+
+/*
+ * Formulate a query for LDAP servers and send it and view it.
+ * This is called from the Address Book screen, either from ^T from the
+ * composer (selecting) or just when browsing in the address book screen
+ * (selecting == 0).
+ *
+ * Args ps -- Pine struct
+ * selecting -- This is set if we're just selecting an address, as opposed
+ * to browsing on an LDAP server
+ * who -- Tells us which server to query
+ * error -- An error message allocated here and freed by caller.
+ *
+ * Returns -- Null if not selecting, possibly an address if selecting.
+ * The address is 1522 decoded and should be freed by the caller.
+ */
+char *
+query_server(struct pine *ps, int selecting, int *exit, int who, char **error)
+{
+ struct headerentry *he = NULL;
+ PICO pbf;
+ STORE_S *msgso = NULL;
+ int i, lret, editor_result;
+ int r = 4, flags;
+ HelpType help = NO_HELP;
+#define FILTSIZE 1000
+ char fbuf[FILTSIZE+1];
+ char *ret = NULL;
+ LDAP_CHOOSE_S *winning_e = NULL;
+ LDAP_SERV_RES_S *free_when_done = NULL;
+ SAVED_QUERY_S *sq = NULL;
+ static ESCKEY_S ekey[] = {
+ /* TRANSLATORS: go to more complex search screen */
+ {ctrl('T'), 10, "^T", N_("To complex search")},
+ {-1, 0, NULL, NULL}
+ };
+
+ dprint((2, "- query_server(%s) -\n", selecting?"Selecting":""));
+
+ if(!(ps->VAR_LDAP_SERVERS && ps->VAR_LDAP_SERVERS[0] &&
+ ps->VAR_LDAP_SERVERS[0][0])){
+ if(error)
+ *error = cpystr(_("No LDAP server available for lookup"));
+
+ return(ret);
+ }
+
+ fbuf[0] = '\0';
+ flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
+ while(r == 4 || r == 3){
+ /* TRANSLATORS: we're asking for a character string to search for */
+ r = optionally_enter(fbuf, -FOOTER_ROWS(ps), 0, sizeof(fbuf),
+ _("String to search for : "),
+ ekey, help, &flags);
+ if(r == 3)
+ help = help == NO_HELP ? h_dir_comp_search : NO_HELP;
+ }
+
+ /* strip quotes that user typed by mistake */
+ (void)removing_double_quotes(fbuf);
+
+ if(r == 1 || (r != 10 && fbuf[0] == '\0')){
+ ps->mangled_footer = 1;
+ if(error)
+ *error = cpystr(_("Cancelled"));
+
+ return(ret);
+ }
+
+ editor_result = COMP_EXIT; /* just to get right logic below */
+
+ memset((void *)&pbf, 0, sizeof(pbf));
+
+ if(r == 10){
+ standard_picobuf_setup(&pbf);
+ pbf.exittest = pico_simpleexit;
+ pbf.exit_label = _("Search");
+ pbf.canceltest = pico_simplecancel;
+ if(saved_params){
+ pbf.expander = restore_query_parameters;
+ pbf.ctrlr_label = _("Restore");
+ }
+
+ pbf.pine_anchor = set_titlebar(_("SEARCH DIRECTORY SERVER"),
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,
+ ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+ pbf.pine_flags |= P_NOBODY;
+
+ /* An informational message */
+ if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ pbf.msgtext = (void *)so_text(msgso);
+ /*
+ * It's nice if we can make it so these lines make sense even if
+ * they don't all make it on the screen, because the user can't
+ * scroll down to see them. So just make each line a whole sentence
+ * that doesn't need the others below it to make sense.
+ */
+ so_puts(msgso,
+_("\n Fill in some of the fields above to create a query."));
+ so_puts(msgso,
+_("\n The match will be for the exact string unless you include wildcards (*)."));
+ so_puts(msgso,
+_("\n All filled-in fields must match in order to be counted as a match."));
+ so_puts(msgso,
+_("\n Press \"^R\" to restore previous query values (if you've queried previously)."));
+ so_puts(msgso,
+_("\n \"^G\" for help specific to each item. \"^X\" to make the query, or \"^C\" to cancel."));
+ }
+
+ he = (struct headerentry *)fs_get((QQ_END+1) *
+ sizeof(struct headerentry));
+ memset((void *)he, 0, (QQ_END+1) * sizeof(struct headerentry));
+ for(i = QQ_QQ; i <= QQ_END; i++)
+ he[i] = headents_for_query[i];
+
+ pbf.headents = he;
+
+ sq = copy_query_parameters(NULL);
+ he[QQ_QQ].realaddr = &sq->qq;
+ he[QQ_CN].realaddr = &sq->cn;
+ he[QQ_SN].realaddr = &sq->sn;
+ he[QQ_GN].realaddr = &sq->gn;
+ he[QQ_MAIL].realaddr = &sq->mail;
+ he[QQ_ORG].realaddr = &sq->org;
+ he[QQ_UNIT].realaddr = &sq->unit;
+ he[QQ_COUNTRY].realaddr = &sq->country;
+ he[QQ_STATE].realaddr = &sq->state;
+ he[QQ_LOCALITY].realaddr = &sq->locality;
+ he[QQ_CUSTOM].realaddr = &sq->custom;
+
+ /* pass to pico and let user set them */
+ editor_result = pico(&pbf);
+ ps->mangled_screen = 1;
+ standard_picobuf_teardown(&pbf);
+
+ if(editor_result & COMP_GOTHUP)
+ hup_signal();
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+ }
+
+ if(editor_result & COMP_EXIT &&
+ ((r == 0 && *fbuf) ||
+ (r == 10 && sq &&
+ (*sq->qq || *sq->cn || *sq->sn || *sq->gn || *sq->mail ||
+ *sq->org || *sq->unit || *sq->country || *sq->state ||
+ *sq->locality || *sq->custom)))){
+ LDAPLookupStyle style;
+ WP_ERR_S wp_err;
+ int need_and, mangled;
+ char *string;
+ CUSTOM_FILT_S *filter;
+ SAVED_QUERY_S *s;
+
+ s = copy_query_parameters(sq);
+ save_query_parameters(s);
+
+ if(r == 0){
+ string = fbuf;
+ filter = NULL;
+ }
+ else{
+ int categories = 0;
+
+ categories = ((*sq->cn != '\0') ? 1 : 0) +
+ ((*sq->sn != '\0') ? 1 : 0) +
+ ((*sq->gn != '\0') ? 1 : 0) +
+ ((*sq->mail != '\0') ? 1 : 0) +
+ ((*sq->org != '\0') ? 1 : 0) +
+ ((*sq->unit != '\0') ? 1 : 0) +
+ ((*sq->country != '\0') ? 1 : 0) +
+ ((*sq->state != '\0') ? 1 : 0) +
+ ((*sq->locality != '\0') ? 1 : 0);
+ need_and = (categories > 1);
+
+ if((sq->cn ? strlen(sq->cn) : 0 +
+ sq->sn ? strlen(sq->sn) : 0 +
+ sq->gn ? strlen(sq->gn) : 0 +
+ sq->mail ? strlen(sq->mail) : 0 +
+ sq->org ? strlen(sq->org) : 0 +
+ sq->unit ? strlen(sq->unit) : 0 +
+ sq->country ? strlen(sq->country) : 0 +
+ sq->state ? strlen(sq->state) : 0 +
+ sq->locality ? strlen(sq->locality) : 0) > FILTSIZE - 100){
+ if(error)
+ *error = cpystr(_("Search strings too long"));
+
+ goto all_done;
+ }
+
+ if(categories > 0){
+
+ snprintf(fbuf, sizeof(fbuf),
+ "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ need_and ? "(&" : "",
+ *sq->cn ? "(cn=" : "",
+ *sq->cn ? sq->cn : "",
+ *sq->cn ? ")" : "",
+ *sq->sn ? "(sn=" : "",
+ *sq->sn ? sq->sn : "",
+ *sq->sn ? ")" : "",
+ *sq->gn ? "(givenname=" : "",
+ *sq->gn ? sq->gn : "",
+ *sq->gn ? ")" : "",
+ *sq->mail ? "(mail=" : "",
+ *sq->mail ? sq->mail : "",
+ *sq->mail ? ")" : "",
+ *sq->org ? "(o=" : "",
+ *sq->org ? sq->org : "",
+ *sq->org ? ")" : "",
+ *sq->unit ? "(ou=" : "",
+ *sq->unit ? sq->unit : "",
+ *sq->unit ? ")" : "",
+ *sq->country ? "(c=" : "",
+ *sq->country ? sq->country : "",
+ *sq->country ? ")" : "",
+ *sq->state ? "(st=" : "",
+ *sq->state ? sq->state : "",
+ *sq->state ? ")" : "",
+ *sq->locality ? "(l=" : "",
+ *sq->locality ? sq->locality : "",
+ *sq->locality ? ")" : "",
+ need_and ? ")" : "");
+ fbuf[sizeof(fbuf)-1] = '\0';
+ }
+
+ if(categories > 0 || *sq->custom)
+ filter = (CUSTOM_FILT_S *)fs_get(sizeof(CUSTOM_FILT_S));
+
+ /* combine the configed filters with this filter */
+ if(*sq->custom){
+ string = "";
+ filter->filt = sq->custom;
+ filter->combine = 0;
+ }
+ else if(*sq->qq && categories > 0){
+ string = sq->qq;
+ filter->filt = fbuf;
+ filter->combine = 1;
+ }
+ else if(categories > 0){
+ string = "";
+ filter->filt = fbuf;
+ filter->combine = 0;
+ }
+ else{
+ string = sq->qq;
+ filter = NULL;
+ }
+ }
+
+ mangled = 0;
+ memset(&wp_err, 0, sizeof(wp_err));
+ wp_err.mangled = &mangled;
+ style = selecting ? AlwaysDisplayAndMailRequired : AlwaysDisplay;
+
+ /* maybe coming from composer */
+ fix_windsize(ps_global);
+ init_sigwinch();
+ clear_cursor_pos();
+
+ lret = ldap_lookup_all(string, who, 0, style, filter, &winning_e,
+ &wp_err, &free_when_done);
+
+ if(filter)
+ fs_give((void **)&filter);
+
+ if(wp_err.mangled)
+ ps->mangled_screen = 1;
+
+ if(wp_err.error){
+ if(status_message_remaining() && error)
+ *error = wp_err.error;
+ else
+ fs_give((void **)&wp_err.error);
+ }
+
+ if(lret == 0 && winning_e && selecting){
+ ADDRESS *addr;
+
+ addr = address_from_ldap(winning_e);
+ if(addr){
+ if(!addr->host){
+ addr->host = cpystr("missing-hostname");
+ if(error){
+ if(*error)
+ fs_give((void **)error);
+
+ *error = cpystr(_("Missing hostname in LDAP address"));
+ }
+ }
+
+ ret = addr_list_string(addr, NULL, 1);
+ if(!ret || !ret[0]){
+ if(ret)
+ fs_give((void **)&ret);
+
+ if(exit)
+ *exit = 1;
+
+ if(error && !*error){
+ char buf[200];
+
+ snprintf(buf, sizeof(buf), _("No email address available for \"%s\""),
+ (addr->personal && *addr->personal)
+ ? addr->personal
+ : "selected entry");
+ buf[sizeof(buf)-1] = '\0';
+ *error = cpystr(buf);
+ }
+ }
+
+ mail_free_address(&addr);
+ }
+ }
+ else if(lret == -1 && exit)
+ *exit = 1;
+ }
+
+all_done:
+ if(he)
+ free_headents(&he);
+
+ if(free_when_done)
+ free_ldap_result_list(&free_when_done);
+
+ if(winning_e)
+ fs_give((void **)&winning_e);
+
+ if(msgso)
+ so_give(&msgso);
+
+ if(sq)
+ free_query_parameters(&sq);
+
+ return(ret);
+}
+
+
+/*
+ * View all fields of an LDAP entry while browsing.
+ *
+ * Args ps -- Pine struct
+ * winning_e -- The struct containing the information about the entry
+ * to be viewed.
+ */
+void
+view_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *winning_e)
+{
+ STORE_S *srcstore = NULL;
+ SourceType srctype = CharStar;
+ SCROLL_S sargs;
+ HANDLE_S *handles = NULL;
+
+ dprint((9, "- view_ldap_entry -\n"));
+
+ if((srcstore=prep_ldap_for_viewing(ps,winning_e,srctype,&handles)) != NULL){
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(srcstore);
+ sargs.text.src = srctype;
+ sargs.text.desc = _("expanded entry");
+ sargs.text.handles= handles;
+ sargs.bar.title = _("DIRECTORY ENTRY");
+ sargs.proc.tool = process_ldap_cmd;
+ sargs.proc.data.p = (void *) winning_e;
+ sargs.help.text = h_ldap_view;
+ sargs.help.title = _("HELP FOR DIRECTORY VIEW");
+ sargs.keys.menu = &ldap_view_keymenu;
+ setbitmap(sargs.keys.bitmap);
+
+ if(handles)
+ sargs.keys.menu->how_many = 2;
+ else{
+ sargs.keys.menu->how_many = 1;
+ clrbitn(OTHER_KEY, sargs.keys.bitmap);
+ }
+
+ scrolltool(&sargs);
+
+ ps->mangled_screen = 1;
+ so_give(&srcstore);
+ free_handles(&handles);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
+}
+
+
+/*
+ * Compose a message to the email addresses contained in an LDAP entry.
+ *
+ * Args ps -- Pine struct
+ * winning_e -- The struct containing the information about the entry
+ * to be viewed.
+ */
+void
+compose_to_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *e, int allow_role)
+{
+ char **elecmail = NULL,
+ **mail = NULL,
+ **cn = NULL,
+ **sn = NULL,
+ **givenname = NULL,
+ **ll;
+ size_t len = 0;
+
+ dprint((9, "- compose_to_ldap_entry -\n"));
+
+ if(e){
+ char *a;
+ BerElement *ber;
+
+ for(a = ldap_first_attribute(e->ld, e->selected_entry, &ber);
+ a != NULL;
+ a = ldap_next_attribute(e->ld, e->selected_entry, ber)){
+
+ if(strcmp(a, e->info_used->mailattr) == 0){
+ if(!mail)
+ mail = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, "electronicmail") == 0){
+ if(!elecmail)
+ elecmail = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->cnattr) == 0){
+ if(!cn)
+ cn = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->gnattr) == 0){
+ if(!givenname)
+ givenname = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->snattr) == 0){
+ if(!sn)
+ sn = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+
+ our_ldap_memfree(a);
+ }
+ }
+
+ if(elecmail){
+ if(elecmail[0] && elecmail[0][0] && !mail)
+ mail = elecmail;
+ else
+ ldap_value_free(elecmail);
+
+ elecmail = NULL;
+ }
+
+ for(ll = mail; ll && *ll; ll++)
+ len += strlen(*ll) + 1;
+
+ if(len){
+ char *p, *address, *fn = NULL;
+ BuildTo bldto;
+ SAVE_STATE_S state;
+
+ address = (char *)fs_get(len * sizeof(char));
+ p = address;
+ ll = mail;
+ while(*ll){
+ sstrncpy(&p, *ll, len-(p-address));
+ ll++;
+ if(*ll)
+ sstrncpy(&p, ",", len-(p-address));
+ }
+
+ address[len-1] = '\0';
+
+ /*
+ * If we have a fullname and there is only a single address and
+ * the address doesn't seem to have a fullname with it, add it.
+ */
+ if(mail && mail[0] && mail[0][0] && !mail[1]){
+ if(cn && cn[0] && cn[0][0])
+ fn = cpystr(cn[0]);
+ else if(sn && sn[0] && sn[0][0] &&
+ givenname && givenname[0] && givenname[0][0]){
+ size_t l;
+
+ l = strlen(givenname[0]) + strlen(sn[0]) + 1;
+ fn = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(fn, l+1, "%s %s", givenname[0], sn[0]);
+ fn[l] = '\0';
+ }
+ }
+
+ if(mail && mail[0] && mail[0][0] && !mail[1] && fn){
+ ADDRESS *adrlist = NULL;
+ char *tmp_a_string;
+
+ tmp_a_string = cpystr(address);
+ rfc822_parse_adrlist(&adrlist, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+ if(adrlist && !adrlist->next && !adrlist->personal){
+ char *new_address;
+ size_t len;
+ RFC822BUFFER rbuf;
+
+ adrlist->personal = cpystr(fn);
+ len = est_size(adrlist);
+ new_address = (char *) fs_get(len * sizeof(char));
+ new_address[0] ='\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = new_address;
+ rbuf.cur = new_address;
+ rbuf.end = new_address+len-1;
+ /* this will quote it if it needs quoting */
+ rfc822_output_address_list(&rbuf, adrlist, 0L, NULL);
+ *rbuf.cur = '\0';
+ fs_give((void **)&address);
+ address = new_address;
+ }
+
+ if(adrlist)
+ mail_free_address(&adrlist);
+ }
+
+ bldto.type = Str;
+ bldto.arg.str = address;
+
+ save_state(&state);
+ ab_compose_internal(bldto, allow_role);
+ restore_state(&state);
+ fs_give((void **)&address);
+ if(fn)
+ fs_give((void **)&fn);
+
+ /*
+ * Window size may have changed in composer.
+ * Pine_send will have reset the window size correctly,
+ * but we still have to reset our address book data structures.
+ */
+ if(as.initialized)
+ ab_resize();
+
+ ps_global->mangled_screen = 1;
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4, _("No address to compose to"));
+
+ if(mail)
+ ldap_value_free(mail);
+ if(cn)
+ ldap_value_free(cn);
+ if(sn)
+ ldap_value_free(sn);
+ if(givenname)
+ ldap_value_free(givenname);
+}
+
+
+/*
+ * Forward the text of an LDAP entry via email (not a vcard attachment)
+ *
+ * Args ps -- Pine struct
+ * winning_e -- The struct containing the information about the entry
+ * to be viewed.
+ */
+void
+forward_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *winning_e)
+{
+ STORE_S *srcstore = NULL;
+ SourceType srctype = CharStar;
+
+ dprint((9, "- forward_ldap_entry -\n"));
+
+ if((srcstore = prep_ldap_for_viewing(ps,winning_e,srctype,NULL)) != NULL){
+ forward_text(ps, so_text(srcstore), srctype);
+ ps->mangled_screen = 1;
+ so_give(&srcstore);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
+}
+
+
+STORE_S *
+prep_ldap_for_viewing(struct pine *ps, LDAP_CHOOSE_S *winning_e, SourceType srctype, HANDLE_S **handlesp)
+{
+ STORE_S *store = NULL;
+ char *a, *tmp;
+ BerElement *ber;
+ int i, width;
+#define W (1000)
+#define INDENTHERE (22)
+ char obuf[W+10];
+ char hdr[6*INDENTHERE+1], hdr2[6*INDENTHERE+1];
+ char **cn = NULL;
+ int indent = INDENTHERE;
+
+ if(!(store = so_get(srctype, NULL, EDIT_ACCESS)))
+ return(store);
+
+ /* for mailto handles so user can select individual email addrs */
+ if(handlesp)
+ init_handles(handlesp);
+
+ width = MAX(ps->ttyo->screen_cols, 25);
+
+ snprintf(hdr2, sizeof(hdr2), "%-*.*s: ", indent-2,indent-2, "");
+ hdr2[sizeof(hdr2)-1] = '\0';
+
+ if(sizeof(obuf) > ps->ttyo->screen_cols+1){
+ memset((void *)obuf, '-', ps->ttyo->screen_cols * sizeof(char));
+ obuf[ps->ttyo->screen_cols] = '\n';
+ obuf[ps->ttyo->screen_cols+1] = '\0';
+ }
+
+ a = ldap_get_dn(winning_e->ld, winning_e->selected_entry);
+
+ so_puts(store, obuf);
+ if((tmp = fold(a, width, width, "", " ", FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **)&tmp);
+ }
+
+ so_puts(store, obuf);
+ so_puts(store, "\n");
+
+ our_ldap_dn_memfree(a);
+
+ for(a = ldap_first_attribute(winning_e->ld, winning_e->selected_entry, &ber);
+ a != NULL;
+ a = ldap_next_attribute(winning_e->ld, winning_e->selected_entry, ber)){
+
+ if(a && *a){
+ char **vals;
+ char *fn = NULL;
+
+ vals = ldap_get_values(winning_e->ld, winning_e->selected_entry, a);
+
+ /* save this for mailto */
+ if(handlesp && !cn && !strcmp(a, winning_e->info_used->cnattr))
+ cn = ldap_get_values(winning_e->ld, winning_e->selected_entry, a);
+
+ if(vals){
+ int do_mailto;
+
+ do_mailto = (handlesp &&
+ !strcmp(a, winning_e->info_used->mailattr));
+
+ utf8_snprintf(hdr, sizeof(hdr), "%-*.*w: ", indent-2,indent-2,
+ ldap_translate(a, winning_e->info_used));
+ hdr[sizeof(hdr)-1] = '\0';
+ for(i = 0; vals[i] != NULL; i++){
+ if(do_mailto){
+ ADDRESS *ad = NULL;
+ HANDLE_S *h;
+ char buf[20];
+ char *tmp_a_string;
+ char *addr, *new_addr, *enc_addr;
+ char *path = NULL;
+
+ addr = cpystr(vals[i]);
+ if(cn && cn[0] && cn[0][0])
+ fn = cpystr(cn[0]);
+
+ if(fn){
+ tmp_a_string = cpystr(addr);
+ rfc822_parse_adrlist(&ad, tmp_a_string, "@");
+ fs_give((void **)&tmp_a_string);
+ if(ad && !ad->next && !ad->personal){
+ RFC822BUFFER rbuf;
+ size_t len;
+
+ ad->personal = cpystr(fn);
+ len = est_size(ad);
+ new_addr = (char *) fs_get(len * sizeof(char));
+ new_addr[0] = '\0';
+ /* this will quote it if it needs quoting */
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = new_addr;
+ rbuf.cur = new_addr;
+ rbuf.end = new_addr+len-1;
+ rfc822_output_address_list(&rbuf, ad, 0L, NULL);
+ *rbuf.cur = '\0';
+ fs_give((void **) &addr);
+ addr = new_addr;
+ }
+
+ if(ad)
+ mail_free_address(&ad);
+
+ fs_give((void **)&fn);
+ }
+
+ if((enc_addr = rfc1738_encode_mailto(addr)) != NULL){
+ size_t l;
+
+ l = strlen(enc_addr) + 7;
+ path = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(path, l+1, "mailto:%s", enc_addr);
+ path[l] = '\0';
+ fs_give((void **)&enc_addr);
+ }
+
+ fs_give((void **)&addr);
+
+ if(path){
+ h = new_handle(handlesp);
+ h->type = URL;
+ h->h.url.path = path;
+ snprintf(buf, sizeof(buf), "%d", h->key);
+ buf[sizeof(buf)-1] = '\0';
+
+ /*
+ * Don't try to fold this address. Just put it on
+ * one line and let scrolltool worry about
+ * cutting it off before it goes past the
+ * right hand edge. Otherwise, we have to figure
+ * out how to handle the wrapped handle.
+ */
+ snprintf(obuf, sizeof(obuf), "%c%c%c%s%s%c%c%c%c",
+ TAG_EMBED, TAG_HANDLE,
+ strlen(buf), buf, vals[i],
+ TAG_EMBED, TAG_BOLDOFF,
+ TAG_EMBED, TAG_INVOFF);
+ obuf[sizeof(obuf)-1] = '\0';
+
+ so_puts(store, (i==0) ? hdr : hdr2);
+ so_puts(store, obuf);
+ so_puts(store, "\n");
+ }
+ else{
+ snprintf(obuf, sizeof(obuf), "%s", vals[i]);
+ obuf[sizeof(obuf)-1] = '\0';
+
+ if((tmp = fold(obuf, width, width,
+ (i==0) ? hdr : hdr2,
+ repeat_char(indent+2, SPACE),
+ FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+ else{
+ snprintf(obuf, sizeof(obuf), "%s", vals[i]);
+ obuf[sizeof(obuf)-1] = '\0';
+
+ if((tmp = fold(obuf, width, width,
+ (i==0) ? hdr : hdr2,
+ repeat_char(indent+2, SPACE),
+ FLD_NONE)) != NULL){
+ so_puts(store, tmp);
+ fs_give((void **)&tmp);
+ }
+ }
+ }
+
+ ldap_value_free(vals);
+ }
+ else{
+ utf8_snprintf(obuf, sizeof(obuf), "%-*.*w\n", indent-1,indent-1,
+ ldap_translate(a, winning_e->info_used));
+ obuf[sizeof(obuf)-1] = '\0';
+ so_puts(store, obuf);
+ }
+ }
+
+ our_ldap_memfree(a);
+ }
+
+ if(cn)
+ ldap_value_free(cn);
+
+ return(store);
+}
+
+
+int
+process_ldap_cmd(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 1;
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ switch(cmd){
+ case MC_SAVE :
+ save_ldap_entry(ps_global, sparms->proc.data.p, 0);
+ rv = 0;
+ break;
+
+ case MC_COMPOSE :
+ compose_to_ldap_entry(ps_global, sparms->proc.data.p, 0);
+ rv = 0;
+ break;
+
+ case MC_ROLE :
+ compose_to_ldap_entry(ps_global, sparms->proc.data.p, 1);
+ rv = 0;
+ break;
+
+ default:
+ panic("Unexpected command in process_ldap_cmd");
+ break;
+ }
+
+ return(rv);
+}
+
+
+int
+pico_simpleexit(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
+ char **result)
+{
+ if(result)
+ *result = NULL;
+
+ return(0);
+}
+
+char *
+pico_simplecancel(void (*redraw_pico)(void))
+{
+ return("Cancelled");
+}
+
+
+/*
+ * Store query parameters so that they can be recalled by user later with ^R.
+ */
+void
+save_query_parameters(SAVED_QUERY_S *params)
+{
+ free_saved_query_parameters();
+ saved_params = params;
+}
+
+
+SAVED_QUERY_S *
+copy_query_parameters(SAVED_QUERY_S *params)
+{
+ SAVED_QUERY_S *sq;
+
+ sq = (SAVED_QUERY_S *)fs_get(sizeof(SAVED_QUERY_S));
+ memset((void *)sq, 0, sizeof(SAVED_QUERY_S));
+
+ if(params && params->qq)
+ sq->qq = cpystr(params->qq);
+ else
+ sq->qq = cpystr("");
+
+ if(params && params->cn)
+ sq->cn = cpystr(params->cn);
+ else
+ sq->cn = cpystr("");
+
+ if(params && params->sn)
+ sq->sn = cpystr(params->sn);
+ else
+ sq->sn = cpystr("");
+
+ if(params && params->gn)
+ sq->gn = cpystr(params->gn);
+ else
+ sq->gn = cpystr("");
+
+ if(params && params->mail)
+ sq->mail = cpystr(params->mail);
+ else
+ sq->mail = cpystr("");
+
+ if(params && params->org)
+ sq->org = cpystr(params->org);
+ else
+ sq->org = cpystr("");
+
+ if(params && params->unit)
+ sq->unit = cpystr(params->unit);
+ else
+ sq->unit = cpystr("");
+
+ if(params && params->country)
+ sq->country = cpystr(params->country);
+ else
+ sq->country = cpystr("");
+
+ if(params && params->state)
+ sq->state = cpystr(params->state);
+ else
+ sq->state = cpystr("");
+
+ if(params && params->locality)
+ sq->locality = cpystr(params->locality);
+ else
+ sq->locality = cpystr("");
+
+ if(params && params->custom)
+ sq->custom = cpystr(params->custom);
+ else
+ sq->custom = cpystr("");
+
+ return(sq);
+}
+
+
+void
+free_saved_query_parameters(void)
+{
+ if(saved_params)
+ free_query_parameters(&saved_params);
+}
+
+
+void
+free_query_parameters(SAVED_QUERY_S **parm)
+{
+ if(parm){
+ if(*parm){
+ if((*parm)->qq)
+ fs_give((void **)&(*parm)->qq);
+ if((*parm)->cn)
+ fs_give((void **)&(*parm)->cn);
+ if((*parm)->sn)
+ fs_give((void **)&(*parm)->sn);
+ if((*parm)->gn)
+ fs_give((void **)&(*parm)->gn);
+ if((*parm)->mail)
+ fs_give((void **)&(*parm)->mail);
+ if((*parm)->org)
+ fs_give((void **)&(*parm)->org);
+ if((*parm)->unit)
+ fs_give((void **)&(*parm)->unit);
+ if((*parm)->country)
+ fs_give((void **)&(*parm)->country);
+ if((*parm)->state)
+ fs_give((void **)&(*parm)->state);
+ if((*parm)->locality)
+ fs_give((void **)&(*parm)->locality);
+ if((*parm)->custom)
+ fs_give((void **)&(*parm)->custom);
+
+ fs_give((void **)parm);
+ }
+ }
+}
+
+
+/*
+ * A callback from pico to restore the saved query parameters.
+ *
+ * Args he -- Unused.
+ * s -- The place to return the allocated array of values.
+ *
+ * Returns -- 1 if there are parameters to return, 0 otherwise.
+ */
+int
+restore_query_parameters(struct headerentry *he, char ***s)
+{
+ int retval = 0, i = 0;
+
+ if(s)
+ *s = NULL;
+
+ if(saved_params && s){
+ *s = (char **)fs_get((QQ_END + 1) * sizeof(char *));
+ (*s)[i++] = cpystr(saved_params->qq ? saved_params->qq : "");
+ (*s)[i++] = cpystr(saved_params->cn ? saved_params->cn : "");
+ (*s)[i++] = cpystr(saved_params->sn ? saved_params->sn : "");
+ (*s)[i++] = cpystr(saved_params->gn ? saved_params->gn : "");
+ (*s)[i++] = cpystr(saved_params->mail ? saved_params->mail : "");
+ (*s)[i++] = cpystr(saved_params->org ? saved_params->org : "");
+ (*s)[i++] = cpystr(saved_params->unit ? saved_params->unit : "");
+ (*s)[i++] = cpystr(saved_params->country ? saved_params->country : "");
+ (*s)[i++] = cpystr(saved_params->state ? saved_params->state : "");
+ (*s)[i++] = cpystr(saved_params->locality ? saved_params->locality :"");
+ (*s)[i++] = cpystr(saved_params->custom ? saved_params->custom :"");
+ (*s)[i] = 0;
+ retval = 1;
+ }
+
+ return(retval);
+}
+
+
+/*
+ * Internal handler for viewing an LDAP url.
+ */
+int
+url_local_ldap(char *url)
+{
+ LDAP *ld;
+ struct timeval t;
+ int ld_err, mangled = 0, we_cancel, retval = 0, proto = 3;
+ int we_turned_on = 0;
+ char ebuf[300];
+ LDAPMessage *result;
+ LDAP_SERV_S *info;
+ LDAP_SERV_RES_S *serv_res = NULL;
+ LDAPURLDesc *ldapurl = NULL;
+ WP_ERR_S wp_err;
+
+ dprint((2, "url_local_ldap(%s)\n", url ? url : "?"));
+
+ ld_err = ldap_url_parse(url, &ldapurl);
+ if(ld_err || !ldapurl){
+ snprintf(ebuf, sizeof(ebuf), "URL parse failed for %s", url);
+ ebuf[sizeof(ebuf)-1] = '\0';
+ q_status_message(SM_ORDER, 3, 5, ebuf);
+ return(retval);
+ }
+
+ if(!ldapurl->lud_host){
+ /* TRNASLATORS: No host in <url> */
+ snprintf(ebuf, sizeof(ebuf), _("No host in %s"), url);
+ ebuf[sizeof(ebuf)-1] = '\0';
+ q_status_message(SM_ORDER, 3, 5, ebuf);
+ ldap_free_urldesc(ldapurl);
+ return(retval);
+ }
+
+ we_turned_on = intr_handling_on();
+ we_cancel = busy_cue(_("Searching for LDAP url"), NULL, 0);
+ ps_global->mangled_footer = 1;
+
+#if (LDAPAPI >= 11)
+ if((ld = ldap_init(ldapurl->lud_host, ldapurl->lud_port)) == NULL)
+#else
+ if((ld = ldap_open(ldapurl->lud_host, ldapurl->lud_port)) == NULL)
+#endif
+ {
+ if(we_cancel){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+ }
+
+ q_status_message(SM_ORDER,3,5, _("LDAP search failed: can't initialize"));
+ }
+ else if(!ps_global->intr_pending){
+ if(ldap_v3_is_supported(ld) &&
+ our_ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &proto) == 0){
+ dprint((5, "ldap: using version 3 protocol\n"));
+ }
+
+ /*
+ * If we don't set RESTART then the select() waiting for the answer
+ * in libldap will be interrupted and stopped by our busy_cue.
+ */
+ our_ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
+
+ t.tv_sec = 30; t.tv_usec = 0;
+ ld_err = ldap_search_st(ld, ldapurl->lud_dn, ldapurl->lud_scope,
+ ldapurl->lud_filter, ldapurl->lud_attrs,
+ 0, &t, &result);
+ if(ld_err != LDAP_SUCCESS){
+ if(we_cancel){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+ }
+
+ snprintf(ebuf, sizeof(ebuf), _("LDAP search failed: %s"), ldap_err2string(ld_err));
+ ebuf[sizeof(ebuf)-1] = '\0';
+ q_status_message(SM_ORDER, 3, 5, ebuf);
+ ldap_unbind(ld);
+ }
+ else if(!ps_global->intr_pending){
+ if(we_cancel){
+ cancel_busy_cue(-1);
+ we_cancel = 0;
+ }
+
+ if(we_turned_on){
+ intr_handling_off();
+ we_turned_on = 0;
+ }
+
+ if(ldap_count_entries(ld, result) == 0){
+ q_status_message(SM_ORDER, 3, 5, _("No matches found for url"));
+ ldap_unbind(ld);
+ if(result)
+ ldap_msgfree(result);
+ }
+ else{
+ serv_res = (LDAP_SERV_RES_S *)fs_get(sizeof(LDAP_SERV_RES_S));
+ memset((void *)serv_res, 0, sizeof(*serv_res));
+ serv_res->ld = ld;
+ serv_res->res = result;
+ info = (LDAP_SERV_S *)fs_get(sizeof(LDAP_SERV_S));
+ memset((void *)info, 0, sizeof(*info));
+ info->mailattr = cpystr(DEF_LDAP_MAILATTR);
+ info->snattr = cpystr(DEF_LDAP_SNATTR);
+ info->gnattr = cpystr(DEF_LDAP_GNATTR);
+ info->cnattr = cpystr(DEF_LDAP_CNATTR);
+ serv_res->info_used = info;
+ memset(&wp_err, 0, sizeof(wp_err));
+ wp_err.mangled = &mangled;
+
+ ask_user_which_entry(serv_res, NULL, NULL, &wp_err, DisplayForURL);
+ if(wp_err.error){
+ q_status_message(SM_ORDER, 3, 5, wp_err.error);
+ fs_give((void **)&wp_err.error);
+ }
+
+ if(mangled)
+ ps_global->mangled_screen = 1;
+
+ free_ldap_result_list(&serv_res);
+ retval = 1;
+ }
+ }
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(we_turned_on)
+ intr_handling_off();
+
+ if(ldapurl)
+ ldap_free_urldesc(ldapurl);
+
+ return(retval);
+}
+#endif /* ENABLE_LDAP */
diff --git a/alpine/adrbkcmd.h b/alpine/adrbkcmd.h
new file mode 100644
index 00000000..3a0b1752
--- /dev/null
+++ b/alpine/adrbkcmd.h
@@ -0,0 +1,64 @@
+/*
+ * $Id: adrbkcmd.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_ADRBKCMD_INCLUDED
+#define PITH_ADRBKCMD_INCLUDED
+
+
+#include "../pith/adrbklib.h"
+#include "../pith/state.h"
+#include "../pith/ldap.h"
+#include "../pith/handle.h"
+#include "../pith/store.h"
+
+
+#define AB_COMMENT_STR _("Comment")
+
+
+/* exported protoypes */
+void view_abook_entry(struct pine *, long);
+void edit_entry(AdrBk *, AdrBk_Entry *, a_c_arg_t, Tag, int, int *, char *);
+int ab_add_abook(int, int);
+int ab_edit_abook(int, int, char *, char *, char *);
+int any_addrbooks_to_convert(struct pine *);
+int convert_addrbooks_to_remote(struct pine *, char *, size_t);
+int any_sigs_to_convert(struct pine *);
+int convert_sigs_to_literal(struct pine *, int);
+void warn_about_rule_files(struct pine *);
+void convert_to_remote_config(struct pine *, int);
+int ab_del_abook(long, int, char **);
+int ab_shuffle(PerAddrBook *, int *, int, char **);
+int ab_compose_to_addr(long, int, int);
+int ab_forward(struct pine *, long, int);
+int ab_save(struct pine *, AdrBk *, long, int, int);
+int ab_print(int);
+int ab_agg_delete(struct pine *, int);
+int single_entry_delete(AdrBk *, long, int *);
+char *query_server(struct pine *, int, int *, int, char **);
+void free_headents(struct headerentry **);
+void write_single_vcard_entry(struct pine *, gf_io_t, VCARD_INFO_S *);
+void free_vcard_info(VCARD_INFO_S **);
+#ifdef ENABLE_LDAP
+void view_ldap_entry(struct pine *, LDAP_CHOOSE_S *);
+void compose_to_ldap_entry(struct pine *, LDAP_CHOOSE_S *,int);
+void forward_ldap_entry(struct pine *, LDAP_CHOOSE_S *);
+STORE_S *prep_ldap_for_viewing(struct pine *, LDAP_CHOOSE_S *, SourceType, HANDLE_S **);
+void free_saved_query_parameters(void);
+int url_local_ldap(char *);
+#endif
+
+
+#endif /* PITH_ADRBKCMD_INCLUDED */
diff --git a/alpine/after.c b/alpine/after.c
new file mode 100644
index 00000000..c2e2af71
--- /dev/null
+++ b/alpine/after.c
@@ -0,0 +1,276 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: after.c 138 2006-09-22 22:12:03Z mikes@u.washington.edu $";
+#endif
+
+/* ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ Implement asynchronous start_after() call
+ ====*/
+
+
+#include <system.h>
+#include <general.h>
+
+#include "../pith/debug.h"
+#include "../pith/osdep/err_desc.h"
+
+#include "../pico/utf8stub.h"
+
+#include "after.h"
+
+
+/* internal state */
+int after_active;
+
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+static pthread_t after_thread;
+static pthread_mutex_t status_message_mutex;
+#endif
+
+
+/* internal prototypes */
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+void *do_after(void *);
+#else
+void *cleanup_data;
+#endif
+
+void cleanup_after(void *);
+
+
+/*
+ * start_after - pause and/or loop calling passed function
+ * without getting in the way of main thread
+ *
+ */
+void
+start_after(AFTER_S *a)
+{
+ if(a){
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+ pthread_attr_t attr;
+ int rc;
+ size_t stack;
+
+ if(after_active)
+ stop_after(1);
+
+ /* Initialize and set thread detached attribute */
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+#if defined(PTHREAD_STACK_MIN)
+ stack = PTHREAD_STACK_MIN + 0x10000;
+ pthread_attr_setstacksize(&attr, stack);
+#endif
+
+ if((rc = pthread_create(&after_thread, &attr, do_after, (void *) a)) != 0){
+ after_active = 0;
+ dprint((1, "start_after: pthread_create failed %d (%d)", rc, errno));
+ }
+ else
+ after_active = 1;
+
+ pthread_attr_destroy(&attr);
+ dprint((9, "start_after() created %x: done", after_thread));
+#else /* !(defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)) */
+ /*
+ * Just call the first function
+ */
+ if(!a->delay)
+ (void) (*a->f)(a->data); /* do the thing */
+
+ cleanup_data = (void *) a;
+ after_active = 1;
+#endif
+ }
+}
+
+
+/*
+ * stop_after - stop the thread
+ */
+void
+stop_after(int join)
+{
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+ int rv;
+
+ dprint((9, "stop_after(join=%d) tid=%x", join, pthread_self()));
+
+ if(after_active){
+ if((rv = pthread_cancel(after_thread)) != 0){ /* tell thread to end */
+ dprint((1, "pthread_cancel: %d (%s)\n", rv, error_description(errno)));
+ }
+
+ if(join){
+ if((rv = pthread_join(after_thread, NULL)) != 0){ /* wait for it to end */
+ dprint((1, "pthread_join: %d (%s)\n", rv, error_description(errno)));
+ }
+ }
+ else if((rv = pthread_detach(after_thread)) != 0){ /* mark thread for deletion */
+ dprint((1, "pthread_detach: %d (%s)\n", rv, error_description(errno)));
+ }
+ }
+
+ /* not literally true uless "join" set */
+ after_active = 0;
+
+#else /* !(defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)) */
+
+ cleanup_after((void *) cleanup_data);
+ cleanup_data = NULL;
+ after_active = 0;
+
+#endif
+}
+
+
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+/*
+ * do_after - loop thru list of pause/loop functions
+ */
+void *
+do_after(void *data)
+{
+ AFTER_S *a;
+ struct timespec ts;
+ int loop;
+ sigset_t sigs;
+
+#if defined(SIGCHLD) || defined(SIGWINCH)
+ sigemptyset(&sigs);
+#if defined(SIGCHLD)
+ /* make sure we don't end up with SIGCHLD */
+ sigaddset(&sigs, SIGCHLD);
+#endif /* SIGCHLD */
+#if defined(SIGCHLD)
+ /* or with SIGWINCH */
+ sigaddset(&sigs, SIGWINCH);
+#endif /* SIGWINCH */
+ pthread_sigmask(SIG_BLOCK, &sigs, NULL);
+#endif
+
+ /* prepare for the finish */
+ pthread_cleanup_push(cleanup_after, data);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
+
+ /* and jump in */
+ for(a = (AFTER_S *) data; a != NULL && a->f != NULL; a = a->next){
+ if(a->delay){
+ ts.tv_sec = a->delay / 100; /* seconds */
+ ts.tv_nsec = (a->delay % 100) * 10000000;
+
+ if(nanosleep(&ts, NULL))
+ pthread_exit(NULL); /* interrupted */
+ }
+
+ while(1){
+ /* after waking, make sure we're still wanted */
+ pthread_testcancel();
+
+ loop = (*a->f)(a->data); /* do the thing */
+
+ if(loop > 0){
+ ts.tv_sec = loop / 100;
+ ts.tv_nsec = (loop % 100) * 10000000;
+
+ if(nanosleep(&ts, NULL))
+ pthread_exit(NULL); /* interrupted */
+ }
+ else
+ break;
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ pthread_exit(NULL);
+}
+
+#endif /* defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP) */
+
+
+/*
+ * cleanup_after - give start_after caller opportunity to clean up
+ * their data, then free up AFTER_S list
+ */
+void
+cleanup_after(void *data)
+{
+ AFTER_S *a, *an;
+
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+ dprint((9, "cleanup_after() tid=%x", pthread_self()));
+#endif
+
+ /* free linked list of AFTER_S's */
+ a = (AFTER_S *) data;
+ while(a != NULL){
+ an = a->next;
+
+ if(a->cf)
+ (*a->cf)(a->data);
+
+ free((void *) a);
+
+ a = an;
+ }
+}
+
+
+AFTER_S *
+new_afterstruct(void)
+{
+ AFTER_S *a;
+
+ if((a = (AFTER_S *)malloc(sizeof(AFTER_S))) == NULL){
+ fatal("Out of memory");
+ }
+
+ memset((void *) a, 0, sizeof(AFTER_S));
+
+ return(a);
+}
+
+
+void
+status_message_lock_init(void)
+{
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+ pthread_mutex_init(&status_message_mutex, NULL);
+#endif
+}
+
+
+int
+status_message_lock(void)
+{
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+ return(pthread_mutex_lock(&status_message_mutex));
+#else
+ return(0);
+#endif
+}
+
+
+int
+status_message_unlock(void)
+{
+#if defined(HAVE_PTHREAD) && defined(HAVE_NANOSLEEP)
+ return(pthread_mutex_unlock(&status_message_mutex));
+#else
+ return(0);
+#endif
+}
diff --git a/alpine/after.h b/alpine/after.h
new file mode 100644
index 00000000..0e27be06
--- /dev/null
+++ b/alpine/after.h
@@ -0,0 +1,44 @@
+/*
+ * $Id: after.h 137 2006-09-22 21:34:06Z mikes@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_AFTER_INCLUDED
+#define PINE_AFTER_INCLUDED
+
+/*
+ * start_after() arguments
+ */
+typedef struct _after_s {
+ int delay; /* call "f" after "delay" 1/100's of sec */
+ int (*f)(void *); /* 0 if done, else repeat in ret'd 1/100's */
+ void (*cf)(void *); /* called when done to clean up "data" etc */
+ void *data; /* hook to pass args and such to "f" and "cf" */
+ struct _after_s *next; /* next function to pause or repeat */
+} AFTER_S;
+
+
+extern int after_active;
+
+
+/* exported prototypes */
+void start_after(AFTER_S *);
+void stop_after(int);
+AFTER_S *new_afterstruct(void);
+void status_message_lock_init(void);
+int status_message_lock(void);
+int status_message_unlock(void);
+
+
+#endif /* PINE_AFTER_INCLUDED */
diff --git a/alpine/alpine.c b/alpine/alpine.c
new file mode 100644
index 00000000..76f66950
--- /dev/null
+++ b/alpine/alpine.c
@@ -0,0 +1,3587 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: alpine.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+
+#include "../pith/newmail.h"
+#include "../pith/init.h"
+#include "../pith/sort.h"
+#include "../pith/options.h"
+#include "../pith/list.h"
+#include "../pith/conf.h"
+
+#include "osdep/debuging.h"
+#include "osdep/termout.gen.h"
+#include "osdep/chnge_pw.h"
+
+#include "alpine.h"
+#include "mailindx.h"
+#include "mailcmd.h"
+#include "addrbook.h"
+#include "reply.h"
+#include "arg.h"
+#include "keymenu.h"
+#include "status.h"
+#include "context.h"
+#include "mailview.h"
+#include "imap.h"
+#include "radio.h"
+#include "folder.h"
+#include "send.h"
+#include "help.h"
+#include "titlebar.h"
+#include "takeaddr.h"
+#include "dispfilt.h"
+#include "init.h"
+#include "remote.h"
+#include "pattern.h"
+#include "newuser.h"
+#include "setup.h"
+#include "adrbkcmd.h"
+#include "signal.h"
+#include "kblock.h"
+#include "ldapconf.h"
+#include "roleconf.h"
+#include "colorconf.h"
+#include "print.h"
+#include "after.h"
+#include "smime.h"
+#include "newmail.h"
+#ifndef _WINDOWS
+#include "../pico/osdep/raw.h" /* for STD*_FD */
+#endif
+
+
+#define PIPED_FD 5 /* Some innocuous desc */
+
+
+/* look for my_timer_period in pico directory for an explanation */
+int my_timer_period = ((IDLE_TIMEOUT + 1)*1000);
+
+/* byte count used by our gets routine to keep track */
+static unsigned long gets_bytes;
+
+
+/*
+ * Internal prototypes
+ */
+void convert_args_to_utf8(struct pine *, ARGDATA_S *);
+void preopen_stayopen_folders(void);
+int read_stdin_char(char *);
+void main_redrawer(void);
+void show_main_screen(struct pine *, int, OtherMenu, struct key_menu *, int, Pos *);
+void do_menu(int, Pos *, struct key_menu *);
+int choose_setup_cmd(int, MSGNO_S *, SCROLL_S *);
+int setup_menu(struct pine *);
+void do_setup_task(int);
+void queue_init_errors(struct pine *);
+void process_init_cmds(struct pine *, char **);
+void goodnight_gracey(struct pine *, int);
+void pine_read_progress(GETS_DATA *, unsigned long);
+int remote_pinerc_failure(void);
+void dump_supported_options(void);
+int prune_folders_ok(void);
+#ifdef WIN32
+char *pine_user_callback(void);
+#endif
+#ifdef _WINDOWS
+int fkey_mode_callback(int, long);
+void imap_telemetry_on(void);
+void imap_telemetry_off(void);
+char *pcpine_help_main(char *);
+int pcpine_main_cursor(int, long);
+#define main app_main
+#endif
+
+
+typedef struct setup_return_val {
+ int cmd;
+ int exc;
+}SRV_S;
+
+
+/*
+ * strlen of longest label from keymenu, of labels corresponding to
+ * commands in the middle of the screen. 9 is length of ListFldrs
+ */
+#define LONGEST_LABEL 9 /* length of longest label from keymenu */
+
+#define EDIT_EXCEPTION (0x100)
+
+
+static int in_panic = 0;
+
+
+/*----------------------------------------------------------------------
+ main routine -- entry point
+
+ Args: argv, argc -- The command line arguments
+
+
+ Initialize pine, parse arguments and so on
+
+ If there is a user address on the command line go into send mode and exit,
+ otherwise loop executing the various screens in Alpine.
+
+ NOTE: The Windows port def's this to "app_main"
+ ----*/
+
+int
+main(int argc, char **argv)
+{
+ ARGDATA_S args;
+ int rv;
+ long rvl;
+ struct pine *pine_state;
+ gf_io_t stdin_getc = NULL;
+ char *args_for_debug = NULL, *init_pinerc_debugging = NULL;
+
+ /*----------------------------------------------------------------------
+ Set up buffering and some data structures
+ ----------------------------------------------------------------------*/
+
+ pine_state = new_pine_struct();
+ ps_global = pine_state;
+
+ /*
+ * fill in optional pith-offered behavior hooks
+ */
+ pith_opt_read_msg_prompt = read_msg_prompt;
+ pith_opt_paint_index_hline = paint_index_hline;
+ pith_opt_rfc2369_editorial = rfc2369_editorial;
+ pith_opt_condense_thread_cue = condensed_thread_cue;
+ pith_opt_truncate_sfstr = truncate_subj_and_from_strings;
+ pith_opt_save_and_restore = save_and_restore;
+ pith_opt_newmail_announce = newmail_status_message;
+ pith_opt_newmail_check_cue = newmail_check_cue;
+ pith_opt_checkpoint_cue = newmail_check_point_cue;
+ pith_opt_icon_text = icon_text;
+ pith_opt_rd_metadata_name = rd_metadata_name;
+ pith_opt_remote_pinerc_failure = remote_pinerc_failure;
+ pith_opt_reopen_folder = ask_mailbox_reopen;
+ pith_opt_expunge_prompt = expunge_prompt;
+ pith_opt_begin_closing = expunge_and_close_begins;
+ pith_opt_replyto_prompt = reply_using_replyto_query;
+ pith_opt_reply_to_all_prompt = reply_to_all_query;
+ pith_opt_save_create_prompt = create_for_save_prompt;
+ pith_opt_daemon_confirm = confirm_daemon_send;
+ pith_opt_save_size_changed_prompt = save_size_changed_prompt;
+ pith_opt_save_index_state = setup_index_state;
+ pith_opt_filter_pattern_cmd = pattern_filter_command;
+ pith_opt_get_signature_file = get_signature_file;
+ pith_opt_pretty_var_name = pretty_var_name;
+ pith_opt_pretty_feature_name = pretty_feature_name;
+ pith_opt_closing_stream = titlebar_stream_closing;
+ pith_opt_current_expunged = mm_expunged_current;
+#ifdef SMIME
+ pith_opt_smime_get_passphrase = smime_get_passphrase;
+#endif
+#ifdef ENABLE_LDAP
+ pith_opt_save_ldap_entry = save_ldap_entry;
+#endif
+
+ status_message_lock_init();
+
+#if HAVE_SRANDOM
+ /*
+ * Seed the random number generator with the date & pid. Random
+ * numbers are used for new mail notification and bug report id's
+ */
+ srandom(getpid() + time(0));
+#endif
+
+ /* need home directory early */
+ get_user_info(&ps_global->ui);
+
+ if(!(pine_state->home_dir = our_getenv("HOME")))
+ pine_state->home_dir = cpystr(ps_global->ui.homedir);
+
+#ifdef _WINDOWS
+ {
+ char *p;
+
+ /* normalize path delimiters */
+ for(p = pine_state->home_dir; p = strchr(p, '/'); p++)
+ *p='\\';
+ }
+#endif /* _WINDOWS */
+
+#ifdef DEBUG
+ { size_t len = 0;
+ int i;
+ char *p;
+ char *no_args = " <no args>";
+
+ for(i = 0; i < argc; i++)
+ len += (strlen(argv[i] ? argv[i] : "")+3);
+
+ if(argc == 1)
+ len += strlen(no_args);
+
+ p = args_for_debug = (char *)fs_get((len+2) * sizeof(char));
+ *p++ = '\n';
+ *p = '\0';
+
+ for(i = 0; i < argc; i++){
+ snprintf(p, len+2-(p-args_for_debug), "%s\"%s\"", i ? " " : "", argv[i] ? argv[i] : "");
+ args_for_debug[len+2-1] = '\0';
+ p += strlen(p);
+ }
+
+ if(argc == 1){
+ strncat(args_for_debug, no_args, len+2-strlen(args_for_debug)-1);
+ args_for_debug[len+2-1] = '\0';
+ }
+ }
+#endif
+
+ /*----------------------------------------------------------------------
+ Parse arguments and initialize debugging
+ ----------------------------------------------------------------------*/
+ pine_args(pine_state, argc, argv, &args);
+
+#ifndef _WINDOWS
+ if(!isatty(0)){
+ /*
+ * monkey with descriptors so our normal tty i/o routines don't
+ * choke...
+ */
+ dup2(STDIN_FD, PIPED_FD); /* redirected stdin to new desc */
+ dup2(STDERR_FD, STDIN_FD); /* rebind stdin to the tty */
+ stdin_getc = read_stdin_char;
+ if(stdin_getc && args.action == aaURL){
+ display_args_err(
+ "Cannot read stdin when using -url\nFor mailto URLs, use \'body=\' instead",
+ NULL, 1);
+ args_help();
+ exit(-1);
+ }
+ }
+
+#else /* _WINDOWS */
+ /*
+ * We now have enough information to do some of the basic registry settings.
+ */
+ if(ps_global->update_registry != UREG_NEVER_SET){
+ mswin_reg(MSWR_OP_SET
+ | ((ps_global->update_registry == UREG_ALWAYS_SET)
+ ? MSWR_OP_FORCE : 0),
+ MSWR_PINE_DIR, ps_global->pine_dir, (size_t)NULL);
+ mswin_reg(MSWR_OP_SET
+ | ((ps_global->update_registry == UREG_ALWAYS_SET)
+ ? MSWR_OP_FORCE : 0),
+ MSWR_PINE_EXE, ps_global->pine_name, (size_t)NULL);
+ }
+
+#endif /* _WINDOWS */
+
+ if(ps_global->convert_sigs &&
+ (!ps_global->pinerc || !ps_global->pinerc[0])){
+ fprintf(stderr, "Use -p <pinerc> with -convert_sigs\n");
+ exit(-1);
+ }
+
+ mail_parameters(NULL, SET_QUOTA, (void *) pine_parse_quota);
+ /* set some default timeouts in case pinerc is remote */
+ mail_parameters(NULL, SET_OPENTIMEOUT, (void *)(long)30);
+ mail_parameters(NULL, SET_READTIMEOUT, (void *)(long)15);
+ mail_parameters(NULL, SET_TIMEOUT, (void *) pine_tcptimeout);
+ /* could be TO_BAIL_THRESHOLD, 15 seems more appropriate for now */
+ pine_state->tcp_query_timeout = 15;
+
+ mail_parameters(NULL, SET_SENDCOMMAND, (void *) pine_imap_cmd_happened);
+ mail_parameters(NULL, SET_FREESTREAMSPAREP, (void *) sp_free_callback);
+ mail_parameters(NULL, SET_FREEELTSPAREP, (void *) free_pine_elt);
+#ifdef SMIME
+ mail_parameters(NULL, SET_FREEBODYSPAREP, (void *) free_smime_body_sparep);
+#endif
+
+ init_pinerc(pine_state, &init_pinerc_debugging);
+
+#ifdef DEBUG
+ /* Since this is specific debugging we don't mind if the
+ ifdef is the type of system.
+ */
+#if defined(HAVE_SMALLOC) || defined(NXT)
+ if(ps_global->debug_malloc)
+ malloc_debug(ps_global->debug_malloc);
+#endif
+#ifdef CSRIMALLOC
+ if(ps_global->debug_malloc)
+ mal_debug(ps_global->debug_malloc);
+#endif
+
+ if(!ps_global->convert_sigs
+#ifdef _WINDOWS
+ && !ps_global->install_flag
+#endif /* _WINDOWS */
+ )
+ init_debug();
+
+ if(args_for_debug){
+ dprint((0, " %s (PID=%ld)\n\n", args_for_debug,
+ (long) getpid()));
+ fs_give((void **)&args_for_debug);
+ }
+
+ {
+ char *env_to_free;
+ if((env_to_free = our_getenv("HOME")) != NULL){
+ dprint((2, "Setting home dir from $HOME: \"%s\"\n",
+ env_to_free));
+ fs_give((void **)&env_to_free);
+ }
+ else{
+ dprint((2, "Setting home dir: \"%s\"\n",
+ pine_state->home_dir ? pine_state->home_dir : "<?>"));
+ }
+ }
+
+ /* Watch out. Sensitive information in debug file. */
+ if(ps_global->debug_imap > 4)
+ mail_parameters(NULL, SET_DEBUGSENSITIVE, (void *) TRUE);
+
+#ifndef DEBUGJOURNAL
+ if(ps_global->debug_tcp)
+#endif
+ mail_parameters(NULL, SET_TCPDEBUG, (void *) TRUE);
+
+#ifdef _WINDOWS
+ mswin_setdebug(debug, debugfile);
+ mswin_setdebugoncallback (imap_telemetry_on);
+ mswin_setdebugoffcallback (imap_telemetry_off);
+ mswin_enableimaptelemetry(ps_global->debug_imap != 0);
+#endif
+#endif /* DEBUG */
+
+#ifdef _WINDOWS
+ mswin_setsortcallback(index_sort_callback);
+ mswin_setflagcallback(flag_callback);
+ mswin_sethdrmodecallback(header_mode_callback);
+ mswin_setselectedcallback(any_selected_callback);
+ mswin_setzoomodecallback(zoom_mode_callback);
+ mswin_setfkeymodecallback(fkey_mode_callback);
+#endif
+
+ /*------- Set up c-client drivers -------*/
+#include "../c-client/linkage.c"
+
+ /*------- ... then tune the drivers just installed -------*/
+#ifdef _WINDOWS
+ if(_tgetenv(TEXT("HOME")))
+ mail_parameters(NULL, SET_HOMEDIR, (void *) pine_state->home_dir);
+
+ mail_parameters(NULL, SET_USERPROMPT, (void *) pine_user_callback);
+
+ /*
+ * Sniff the environment for timezone offset. We need to do this
+ * here since Windows needs help figuring out UTC, and will adjust
+ * what time() returns based on TZ. THIS WILL SCREW US because
+ * we use time() differences to manage status messages. So, if
+ * rfc822_date, which calls localtime() and thus needs tzset(),
+ * is called while a status message is displayed, it's possible
+ * for time() to return a time *before* what we remember as the
+ * time we put the status message on the display. Sheesh.
+ */
+ tzset();
+#else /* !_WINDOWS */
+ /*
+ * We used to let c-client do this for us automatically, but it declines
+ * to do so for root. This forces c-client to establish an environment,
+ * even if the uid is 0.
+ */
+ env_init(ps_global->ui.login, ps_global->ui.homedir);
+
+ /*
+ * Install callback to let us know the progress of network reads...
+ */
+ (void) mail_parameters(NULL, SET_READPROGRESS, (void *)pine_read_progress);
+#endif /* !_WINDOWS */
+
+ /*
+ * Install callback to handle certificate validation failures,
+ * allowing the user to continue if they wish.
+ */
+ mail_parameters(NULL, SET_SSLCERTIFICATEQUERY, (void *) pine_sslcertquery);
+ mail_parameters(NULL, SET_SSLFAILURE, (void *) pine_sslfailure);
+
+ if(init_pinerc_debugging){
+ dprint((2, init_pinerc_debugging));
+ fs_give((void **)&init_pinerc_debugging);
+ }
+
+ /*
+ * Initial allocation of array of stream pool pointers.
+ * We do this before init_vars so that we can re-use streams used for
+ * remote config files. These sizes may get changed later.
+ */
+ ps_global->s_pool.max_remstream = 2;
+ dprint((9,
+ "Setting initial max_remstream to %d for remote config re-use\n",
+ ps_global->s_pool.max_remstream));
+
+ init_vars(pine_state, process_init_cmds);
+
+#ifdef SMIME
+ if(F_ON(F_DONT_DO_SMIME, ps_global))
+ smime_deinit();
+#endif /* SMIME */
+
+#ifdef ENABLE_NLS
+ /*
+ * LC_CTYPE is already set from the set_collation call above.
+ *
+ * We can't use gettext calls before we do this stuff so it doesn't
+ * help to translate strings that come before this in the program.
+ * Maybe we could rearrange things to accomodate that.
+ */
+ setlocale(LC_MESSAGES, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset(PACKAGE, "UTF-8");
+ textdomain(PACKAGE);
+#endif /* ENABLE_NLS */
+
+ convert_args_to_utf8(pine_state, &args);
+
+ if(args.action == aaFolder){
+ pine_state->beginning_of_month = first_run_of_month();
+ pine_state->beginning_of_year = first_run_of_year();
+ }
+
+ /* Set up optional for user-defined display filtering */
+ pine_state->tools.display_filter = dfilter;
+ pine_state->tools.display_filter_trigger = dfilter_trigger;
+
+#ifdef _WINDOWS
+ if(ps_global->install_flag){
+ init_install_get_vars();
+
+ if(ps_global->prc)
+ free_pinerc_s(&ps_global->prc);
+
+ exit(0);
+ }
+#endif
+
+ if(ps_global->convert_sigs){
+ if(convert_sigs_to_literal(ps_global, 0) == -1){
+ /* TRANSLATORS: sigs refers to signatures, which the user was trying to convert */
+ fprintf(stderr, _("trouble converting sigs\n"));
+ exit(-1);
+ }
+
+ if(ps_global->prc){
+ if(ps_global->prc->outstanding_pinerc_changes)
+ write_pinerc(ps_global, Main, WRP_NONE);
+
+ free_pinerc_s(&pine_state->prc);
+ }
+
+ exit(0);
+ }
+
+ /*
+ * Set up a c-client read timeout and timeout handler. In general,
+ * it shouldn't happen, but a server crash or dead link can cause
+ * pine to appear wedged if we don't set this up...
+ */
+ rv = 30;
+ if(pine_state->VAR_TCPOPENTIMEO)
+ (void)SVAR_TCP_OPEN(pine_state, rv, tmp_20k_buf, SIZEOF_20KBUF);
+ mail_parameters(NULL, SET_OPENTIMEOUT, (void *)(long)rv);
+
+ rv = 15;
+ if(pine_state->VAR_TCPREADWARNTIMEO)
+ (void)SVAR_TCP_READWARN(pine_state, rv, tmp_20k_buf, SIZEOF_20KBUF);
+ mail_parameters(NULL, SET_READTIMEOUT, (void *)(long)rv);
+
+ rv = 0;
+ if(pine_state->VAR_TCPWRITEWARNTIMEO){
+ if(!SVAR_TCP_WRITEWARN(pine_state, rv, tmp_20k_buf, SIZEOF_20KBUF))
+ if(rv == 0 || rv > 4) /* making sure */
+ mail_parameters(NULL, SET_WRITETIMEOUT, (void *)(long)rv);
+ }
+
+ mail_parameters(NULL, SET_TIMEOUT, (void *) pine_tcptimeout);
+
+ rv = 15;
+ if(pine_state->VAR_RSHOPENTIMEO){
+ if(!SVAR_RSH_OPEN(pine_state, rv, tmp_20k_buf, SIZEOF_20KBUF))
+ if(rv == 0 || rv > 4) /* making sure */
+ mail_parameters(NULL, SET_RSHTIMEOUT, (void *)(long)rv);
+ }
+
+ rv = 15;
+ if(pine_state->VAR_SSHOPENTIMEO){
+ if(!SVAR_SSH_OPEN(pine_state, rv, tmp_20k_buf, SIZEOF_20KBUF))
+ if(rv == 0 || rv > 4) /* making sure */
+ mail_parameters(NULL, SET_SSHTIMEOUT, (void *)(long)rv);
+ }
+
+ rvl = 60L;
+ if(pine_state->VAR_MAILDROPCHECK){
+ if(!SVAR_MAILDCHK(pine_state, rvl, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(rvl == 0L)
+ rvl = (60L * 60L * 24L * 100L); /* 100 days */
+
+ if(rvl >= 60L) /* making sure */
+ mail_parameters(NULL, SET_SNARFINTERVAL, (void *) rvl);
+ }
+ }
+
+ /*
+ * Lookups of long login names which don't exist are very slow in aix.
+ * This would normally get set in system-wide config if not needed.
+ */
+ if(F_ON(F_DISABLE_SHARED_NAMESPACES, ps_global))
+ mail_parameters(NULL, SET_DISABLEAUTOSHAREDNS, (void *) TRUE);
+
+ if(F_ON(F_HIDE_NNTP_PATH, ps_global))
+ mail_parameters(NULL, SET_NNTPHIDEPATH, (void *) TRUE);
+
+ if(F_ON(F_MAILDROPS_PRESERVE_STATE, ps_global))
+ mail_parameters(NULL, SET_SNARFPRESERVE, (void *) TRUE);
+
+ rvl = 0L;
+ if(pine_state->VAR_NNTPRANGE){
+ if(!SVAR_NNTPRANGE(pine_state, rvl, tmp_20k_buf, SIZEOF_20KBUF))
+ if(rvl > 0L)
+ mail_parameters(NULL, SET_NNTPRANGE, (void *) rvl);
+ }
+
+ /*
+ * Tell c-client not to be so aggressive about uid mappings
+ */
+ mail_parameters(NULL, SET_UIDLOOKAHEAD, (void *) 20);
+
+ /*
+ * Setup referral handling
+ */
+ mail_parameters(NULL, SET_IMAPREFERRAL, (void *) imap_referral);
+ mail_parameters(NULL, SET_MAILPROXYCOPY, (void *) imap_proxycopy);
+
+ /*
+ * Setup multiple newsrc transition
+ */
+ mail_parameters(NULL, SET_NEWSRCQUERY, (void *) pine_newsrcquery);
+
+ /*
+ * Disable some drivers if requested.
+ */
+ if(ps_global->VAR_DISABLE_DRIVERS &&
+ ps_global->VAR_DISABLE_DRIVERS[0] &&
+ ps_global->VAR_DISABLE_DRIVERS[0][0]){
+ char **t;
+
+ for(t = ps_global->VAR_DISABLE_DRIVERS; t[0] && t[0][0]; t++)
+ if(mail_parameters(NULL, DISABLE_DRIVER, (void *)(*t))){
+ dprint((2, "Disabled mail driver \"%s\"\n", *t));
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ _("Failed to disable mail driver \"%s\": name not found"),
+ *t);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ init_error(ps_global, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ }
+ }
+
+ /*
+ * Disable some authenticators if requested.
+ */
+ if(ps_global->VAR_DISABLE_AUTHS &&
+ ps_global->VAR_DISABLE_AUTHS[0] &&
+ ps_global->VAR_DISABLE_AUTHS[0][0]){
+ char **t;
+
+ for(t = ps_global->VAR_DISABLE_AUTHS; t[0] && t[0][0]; t++)
+ if(mail_parameters(NULL, DISABLE_AUTHENTICATOR, (void *)(*t))){
+ dprint((2,"Disabled SASL authenticator \"%s\"\n", *t));
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ _("Failed to disable SASL authenticator \"%s\": name not found"),
+ *t);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ init_error(ps_global, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ }
+ }
+
+ /*
+ * setup alternative authentication driver preference for IMAP opens
+ */
+ if(F_ON(F_PREFER_ALT_AUTH, ps_global))
+ mail_parameters(NULL, SET_IMAPTRYALT, (void *) TRUE);
+
+ /*
+ * Install handler to let us know about potential delays
+ */
+ (void) mail_parameters(NULL, SET_BLOCKNOTIFY, (void *) pine_block_notify);
+
+ if(ps_global->dump_supported_options){
+ dump_supported_options();
+ exit(0);
+ }
+
+ /*
+ * Install extra headers to fetch along with all the other stuff
+ * mail_fetch_structure and mail_fetch_overview requests.
+ */
+ calc_extra_hdrs();
+ if(get_extra_hdrs())
+ (void) mail_parameters(NULL, SET_IMAPEXTRAHEADERS,
+ (void *) get_extra_hdrs());
+
+ if(init_username(pine_state) < 0){
+ fprintf(stderr, _("Who are you? (Unable to look up login name)\n"));
+ exit(-1);
+ }
+
+ if(init_userdir(pine_state) < 0)
+ exit(-1);
+
+ if(init_hostname(pine_state) < 0)
+ exit(-1);
+
+ /*
+ * Verify mail dir if we're not in send only mode...
+ */
+ if(args.action == aaFolder && init_mail_dir(pine_state) < 0)
+ exit(-1);
+
+ init_signals();
+
+ /*--- input side ---*/
+ if(init_tty_driver(pine_state)){
+#ifndef _WINDOWS /* always succeeds under _WINDOWS */
+ fprintf(stderr, _("Can't access terminal or input is not a terminal. Redirection of\nstandard input is not allowed. For example \"pine < file\" doesn't work.\n%c"), BELL);
+ exit(-1);
+#endif /* !_WINDOWS */
+ }
+
+
+ /*--- output side ---*/
+ rv = config_screen(&(pine_state->ttyo));
+#ifndef _WINDOWS /* always succeeds under _WINDOWS */
+ if(rv){
+ switch(rv){
+ case -1:
+ printf(_("Terminal type (environment variable TERM) not set.\n"));
+ break;
+ case -2:
+ printf(_("Terminal type \"%s\" is unknown.\n"), getenv("TERM"));
+ break;
+ case -3:
+ printf(_("Can't open terminal capabilities database.\n"));
+ break;
+ case -4:
+ printf(_("Your terminal, of type \"%s\", is lacking functions needed to run alpine.\n"), getenv("TERM"));
+ break;
+ }
+
+ printf("\r");
+ end_tty_driver(pine_state);
+ exit(-1);
+ }
+#endif /* !_WINDOWS */
+
+ if(F_ON(F_BLANK_KEYMENU,ps_global))
+ FOOTER_ROWS(ps_global) = 1;
+
+ init_screen();
+ init_keyboard(pine_state->orig_use_fkeys);
+ strncpy(pine_state->inbox_name, INBOX_NAME,
+ sizeof(pine_state->inbox_name)-1);
+ init_folders(pine_state); /* digest folder spec's */
+
+ pine_state->in_init_seq = 0; /* so output (& ClearScreen) show up */
+ pine_state->dont_use_init_cmds = 1; /* don't use up initial_commands yet */
+ ClearScreen();
+
+ /* initialize titlebar in case we use it */
+ set_titlebar("", NULL, NULL, NULL, NULL, 0, FolderName, 0, 0, NULL);
+
+ /*
+ * Prep storage object driver for PicoText
+ */
+ so_register_external_driver(pine_pico_get, pine_pico_give, pine_pico_writec, pine_pico_readc,
+ pine_pico_puts, pine_pico_seek, NULL, NULL);
+
+#ifdef DEBUG
+ if(ps_global->debug_imap > 4 || debug > 9){
+ q_status_message(SM_ORDER | SM_DING, 5, 9,
+ _("Warning: sensitive authentication data included in debug file"));
+ flush_status_messages(0);
+ }
+#endif
+
+ if(args.action == aaPrcCopy || args.action == aaAbookCopy){
+ int exit_val = -1;
+ char *err_msg = NULL;
+
+ /*
+ * Don't translate these into UTF-8 because we'll be using them
+ * before we translate next time. User should use ascii.
+ */
+ if(args.data.copy.local && args.data.copy.remote){
+ switch(args.action){
+ case aaPrcCopy:
+ exit_val = copy_pinerc(args.data.copy.local,
+ args.data.copy.remote, &err_msg);
+ break;
+
+ case aaAbookCopy:
+ exit_val = copy_abook(args.data.copy.local,
+ args.data.copy.remote, &err_msg);
+ break;
+
+ default:
+ break;
+ }
+ }
+ if(err_msg){
+ q_status_message(SM_ORDER | SM_DING, 3, 4, err_msg);
+ fs_give((void **)&err_msg);
+ }
+ goodnight_gracey(pine_state, exit_val);
+ }
+
+ if(args.action == aaFolder
+ && (pine_state->first_time_user || pine_state->show_new_version)){
+ pine_state->mangled_header = 1;
+ show_main_screen(pine_state, 0, FirstMenu, &main_keymenu, 0,
+ (Pos *) NULL);
+ new_user_or_version(pine_state);
+ ClearScreen();
+ }
+
+ /* put back in case we need to suppress output */
+ pine_state->in_init_seq = pine_state->save_in_init_seq;
+
+ /* queue any init errors so they get displayed in a screen below */
+ queue_init_errors(ps_global);
+
+ /* "Page" the given file? */
+ if(args.action == aaMore){
+ int dice = 1, redir = 0;
+
+ if(pine_state->in_init_seq){
+ pine_state->in_init_seq = pine_state->save_in_init_seq = 0;
+ clear_cursor_pos();
+ if(pine_state->free_initial_cmds)
+ fs_give((void **)&(pine_state->free_initial_cmds));
+
+ pine_state->initial_cmds = NULL;
+ }
+
+ /*======= Requested that we simply page the given file =======*/
+ if(args.data.file){ /* Open the requested file... */
+ SourceType src;
+ STORE_S *store = NULL;
+ char *decode_error = NULL;
+ char filename[MAILTMPLEN];
+
+ if(args.data.file[0] == '\0'){
+ HelpType help = NO_HELP;
+
+ pine_state->mangled_footer = 1;
+ filename[0] = '\0';
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ rv = optionally_enter(filename, -FOOTER_ROWS(pine_state),
+ 0, sizeof(filename),
+ /* TRANSLATORS: file is computer data */
+ _("File to open : "),
+ NULL, help, &flags);
+ if(rv == 3){
+ help = (help == NO_HELP) ? h_no_F_arg : NO_HELP;
+ continue;
+ }
+
+ if(rv != 4)
+ break;
+ }
+
+ if(rv == 1){
+ q_status_message(SM_ORDER, 0, 2, _("Cancelled"));
+ goodnight_gracey(pine_state, -1);
+ }
+
+ if(*filename){
+ removing_trailing_white_space(filename);
+ removing_leading_white_space(filename);
+ if(is_absolute_path(filename))
+ fnexpand(filename, sizeof(filename));
+
+ args.data.file = filename;
+ }
+
+ if(!*filename){
+ /* TRANSLATORS: file is computer data */
+ q_status_message(SM_ORDER, 0, 2 ,_("No file to open"));
+ goodnight_gracey(pine_state, -1);
+ }
+ }
+
+ if(stdin_getc){
+ redir++;
+ src = CharStar;
+ if(isatty(0) && (store = so_get(src, NULL, EDIT_ACCESS))){
+ gf_io_t pc;
+
+ gf_set_so_writec(&pc, store);
+ gf_filter_init();
+ if((decode_error = gf_pipe(stdin_getc, pc)) != NULL){
+ dice = 0;
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Problem reading standard input: %s"),
+ decode_error);
+ }
+
+ gf_clear_so_writec(store);
+ }
+ else
+ dice = 0;
+ }
+ else{
+ src = FileStar;
+ strncpy(ps_global->cur_folder, args.data.file,
+ sizeof(ps_global->cur_folder)-1);
+ ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
+ if(!(store = so_get(src, args.data.file, READ_ACCESS|READ_FROM_LOCALE)))
+ dice = 0;
+ }
+
+ if(dice){
+ SCROLL_S sargs;
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = src;
+ /* TRANSLATORS: file is computer file being read by user */
+ sargs.text.desc = _("file");
+ /* TRANSLATORS: this is in the title bar at top of screen */
+ sargs.bar.title = _("FILE VIEW");
+ sargs.bar.style = FileTextPercent;
+ sargs.keys.menu = &simple_file_keymenu;
+ setbitmap(sargs.keys.bitmap);
+ scrolltool(&sargs);
+
+ printf("\n\n");
+ so_give(&store);
+ }
+ }
+
+ if(!dice){
+ q_status_message2(SM_ORDER, 3, 4,
+ _("Can't display \"%s\": %s"),
+ (redir) ? _("Standard Input")
+ : args.data.file ? args.data.file : "NULL",
+ error_description(errno));
+ }
+
+ goodnight_gracey(pine_state, 0);
+ }
+ else if(args.action == aaMail || (stdin_getc && (args.action != aaURL))){
+ /*======= address on command line/send one message mode ============*/
+ char *to = NULL, *error = NULL, *addr = NULL;
+ int len, good_addr = 1;
+ int exit_val = 0;
+ BUILDER_ARG fcc;
+
+ if(pine_state->in_init_seq){
+ pine_state->in_init_seq = pine_state->save_in_init_seq = 0;
+ clear_cursor_pos();
+ if(pine_state->free_initial_cmds)
+ fs_give((void **) &(pine_state->free_initial_cmds));
+
+ pine_state->initial_cmds = NULL;
+ }
+
+ /*----- Format the To: line with commas for the composer ---*/
+ if(args.data.mail.addrlist){
+ STRLIST_S *p;
+
+ for(p = args.data.mail.addrlist, len = 0; p; p = p->next)
+ len += strlen(p->name) + 2;
+
+ to = (char *) fs_get((len + 5) * sizeof(char));
+ for(p = args.data.mail.addrlist, *to = '\0'; p; p = p->next){
+ if(*to){
+ strncat(to, ", ", len+5-strlen(to)-1);
+ to[len+5-1] = '\0';
+ }
+
+ strncat(to, p->name, len+5-strlen(to)-1);
+ to[len+5-1] = '\0';
+ }
+
+ memset((void *)&fcc, 0, sizeof(BUILDER_ARG));
+ dprint((2, "building addr: -->%s<--\n", to ? to : "?"));
+ good_addr = (build_address(to, &addr, &error, &fcc, NULL) >= 0);
+ dprint((2, "mailing to: -->%s<--\n", addr ? addr : "?"));
+ free_strlist(&args.data.mail.addrlist);
+ }
+ else
+ memset(&fcc, 0, sizeof(fcc));
+
+ if(good_addr){
+ compose_mail(addr, fcc.tptr, NULL,
+ args.data.mail.attachlist, stdin_getc);
+ }
+ else{
+ /* TRANSLATORS: refers to bad email address */
+ q_status_message1(SM_ORDER, 3, 4, _("Bad address: %s"), error);
+ exit_val = -1;
+ }
+
+ if(addr)
+ fs_give((void **) &addr);
+
+ if(fcc.tptr)
+ fs_give((void **) &fcc.tptr);
+
+ if(args.data.mail.attachlist)
+ free_attachment_list(&args.data.mail.attachlist);
+
+ if(to)
+ fs_give((void **) &to);
+
+ if(error)
+ fs_give((void **) &error);
+
+ goodnight_gracey(pine_state, exit_val);
+ }
+ else{
+ char int_mail[MAXPATH+1];
+ struct key_menu *km = &main_keymenu;
+
+ /*========== Normal pine mail reading mode ==========*/
+
+ pine_state->mail_stream = NULL;
+ pine_state->mangled_screen = 1;
+
+ if(args.action == aaURL){
+ url_tool_t f;
+
+ if(pine_state->in_init_seq){
+ pine_state->in_init_seq = pine_state->save_in_init_seq = 0;
+ clear_cursor_pos();
+ if(pine_state->free_initial_cmds)
+ fs_give((void **) &(pine_state->free_initial_cmds));
+ pine_state->initial_cmds = NULL;
+ }
+ if((f = url_local_handler(args.url)) != NULL){
+ if(args.data.mail.attachlist){
+ if(f == url_local_mailto){
+ if(!(url_local_mailto_and_atts(args.url,
+ args.data.mail.attachlist)
+ && pine_state->next_screen))
+ free_attachment_list(&args.data.mail.attachlist);
+ goodnight_gracey(pine_state, 0);
+ }
+ else {
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Only mailto URLs are allowed with file attachments"));
+ goodnight_gracey(pine_state, -1); /* no return */
+ }
+ }
+ else if(!((*f)(args.url) && pine_state->next_screen))
+ goodnight_gracey(pine_state, 0); /* no return */
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Unrecognized URL \"%s\""), args.url);
+ goodnight_gracey(pine_state, -1); /* no return */
+ }
+ }
+ else if(!pine_state->start_in_index){
+ /* flash message about executing initial commands */
+ if(pine_state->in_init_seq){
+ pine_state->in_init_seq = 0;
+ clear_cursor_pos();
+ pine_state->mangled_header = 1;
+ pine_state->mangled_footer = 1;
+ pine_state->mangled_screen = 0;
+ /* show that this is Alpine */
+ show_main_screen(pine_state, 0, FirstMenu, km, 0, (Pos *)NULL);
+ pine_state->mangled_screen = 1;
+ pine_state->painted_footer_on_startup = 1;
+ if(MIN(4, pine_state->ttyo->screen_rows - 4) > 1){
+ char buf1[6*MAX_SCREEN_COLS+1];
+ char buf2[6*MAX_SCREEN_COLS+1];
+ int wid;
+
+ /* TRANSLATORS: Initial Keystroke List is the literal name of an option */
+ strncpy(buf1, _("Executing Initial Keystroke List......"), sizeof(buf1));
+ buf1[sizeof(buf1)-1] = '\0';
+ wid = utf8_width(buf1);
+ if(wid > ps_global->ttyo->screen_cols){
+ utf8_pad_to_width(buf2, buf1, sizeof(buf2), ps_global->ttyo->screen_cols, 1);
+ PutLine0(MIN(4, pine_state->ttyo->screen_rows - 4), 0, buf2);
+ }
+ else{
+ PutLine0(MIN(4, pine_state->ttyo->screen_rows - 4),
+ MAX(MIN(11, pine_state->ttyo->screen_cols - wid), 0), buf1);
+ }
+ }
+
+ pine_state->in_init_seq = 1;
+ }
+ else{
+ show_main_screen(pine_state, 0, FirstMenu, km, 0, (Pos *)NULL);
+ pine_state->painted_body_on_startup = 1;
+ pine_state->painted_footer_on_startup = 1;
+ }
+ }
+ else{
+ /* cancel any initial commands, overridden by cmd line */
+ if(pine_state->in_init_seq){
+ pine_state->in_init_seq = 0;
+ pine_state->save_in_init_seq = 0;
+ clear_cursor_pos();
+ if(pine_state->initial_cmds){
+ if(pine_state->free_initial_cmds)
+ fs_give((void **)&(pine_state->free_initial_cmds));
+
+ pine_state->initial_cmds = NULL;
+ }
+
+ F_SET(F_USE_FK,pine_state, pine_state->orig_use_fkeys);
+ }
+
+ (void) do_index_border(pine_state->context_current,
+ pine_state->cur_folder,
+ pine_state->mail_stream,
+ pine_state->msgmap, MsgIndex, NULL,
+ INDX_CLEAR|INDX_HEADER|INDX_FOOTER);
+ pine_state->painted_footer_on_startup = 1;
+ if(MIN(4, pine_state->ttyo->screen_rows - 4) > 1){
+ char buf1[6*MAX_SCREEN_COLS+1];
+ char buf2[6*MAX_SCREEN_COLS+1];
+ int wid;
+
+ strncpy(buf1, _("Please wait, opening mail folder......"), sizeof(buf1));
+ buf1[sizeof(buf1)-1] = '\0';
+ wid = utf8_width(buf1);
+ if(wid > ps_global->ttyo->screen_cols){
+ utf8_pad_to_width(buf2, buf1, sizeof(buf2), ps_global->ttyo->screen_cols, 1);
+ PutLine0(MIN(4, pine_state->ttyo->screen_rows - 4), 0, buf2);
+ }
+ else{
+ PutLine0(MIN(4, pine_state->ttyo->screen_rows - 4),
+ MAX(MIN(11, pine_state->ttyo->screen_cols - wid), 0), buf1);
+ }
+ }
+ }
+
+ fflush(stdout);
+
+#if !defined(_WINDOWS) && !defined(LEAVEOUTFIFO)
+ if(ps_global->VAR_FIFOPATH && ps_global->VAR_FIFOPATH[0])
+ init_newmailfifo(ps_global->VAR_FIFOPATH);
+#endif
+
+ if(pine_state->in_init_seq){
+ pine_state->in_init_seq = 0;
+ clear_cursor_pos();
+ }
+
+ if(args.action == aaFolder && args.data.folder){
+ CONTEXT_S *cntxt = NULL, *tc = NULL;
+ char foldername[MAILTMPLEN];
+ int notrealinbox = 0;
+
+ if(args.data.folder[0] == '\0'){
+ char *fldr;
+ unsigned save_def_goto_rule;
+
+ foldername[0] = '\0';
+ save_def_goto_rule = pine_state->goto_default_rule;
+ pine_state->goto_default_rule = GOTO_FIRST_CLCTN;
+ tc = default_save_context(pine_state->context_list);
+ fldr = broach_folder(-FOOTER_ROWS(pine_state), 1, &notrealinbox, &tc);
+ pine_state->goto_default_rule = save_def_goto_rule;
+ if(fldr){
+ strncpy(foldername, fldr, sizeof(foldername)-1);
+ foldername[sizeof(foldername)-1] = '\0';
+ }
+
+ if(*foldername){
+ removing_trailing_white_space(foldername);
+ removing_leading_white_space(foldername);
+ args.data.folder = cpystr(foldername);
+ }
+
+ if(!*foldername){
+ q_status_message(SM_ORDER, 0, 2 ,_("No folder to open"));
+ goodnight_gracey(pine_state, -1);
+ }
+ }
+
+ if(tc)
+ cntxt = tc;
+ else if((rv = pine_state->init_context) < 0)
+ /*
+ * As with almost all the folder vars in the pinerc,
+ * we subvert the collection "breakout" here if the
+ * folder name given looks like an asolute path on
+ * this system...
+ */
+ cntxt = (is_absolute_path(args.data.folder))
+ ? NULL : pine_state->context_current;
+ else if(rv == 0)
+ cntxt = NULL;
+ else
+ for(cntxt = pine_state->context_list;
+ rv > 1 && cntxt->next;
+ rv--, cntxt = cntxt->next)
+ ;
+
+ if(pine_state && pine_state->ttyo){
+ blank_keymenu(pine_state->ttyo->screen_rows - 2, 0);
+ pine_state->painted_footer_on_startup = 0;
+ pine_state->mangled_footer = 1;
+ }
+
+ if(do_broach_folder(args.data.folder, cntxt, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT) <= 0){
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Unable to open folder \"%s\""), args.data.folder);
+
+ goodnight_gracey(pine_state, -1);
+ }
+ }
+ else if(args.action == aaFolder){
+#ifdef _WINDOWS
+ /*
+ * need to ask for the inbox name if no default under DOS
+ * since there is no "inbox"
+ */
+
+ if(!pine_state->VAR_INBOX_PATH || !pine_state->VAR_INBOX_PATH[0]
+ || strucmp(pine_state->VAR_INBOX_PATH, "inbox") == 0){
+ HelpType help = NO_HELP;
+ static ESCKEY_S ekey[] = {{ctrl(T), 2, "^T", "To Fldrs"},
+ {-1, 0, NULL, NULL}};
+
+ pine_state->mangled_footer = 1;
+ int_mail[0] = '\0';
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ rv = optionally_enter(int_mail, -FOOTER_ROWS(pine_state),
+ 0, sizeof(int_mail),
+ _("No inbox! Folder to open as inbox : "),
+ /* ekey */ NULL, help, &flags);
+ if(rv == 3){
+ help = (help == NO_HELP) ? h_sticky_inbox : NO_HELP;
+ continue;
+ }
+
+ if(rv != 4)
+ break;
+ }
+
+ if(rv == 1){
+ q_status_message(SM_ORDER, 0, 2 ,_("Folder open cancelled"));
+ rv = 0; /* reset rv */
+ }
+ else if(rv == 2){
+ show_main_screen(pine_state,0,FirstMenu,km,0,(Pos *)NULL);
+ }
+
+ if(*int_mail){
+ removing_trailing_white_space(int_mail);
+ removing_leading_white_space(int_mail);
+ if((!pine_state->VAR_INBOX_PATH
+ || strucmp(pine_state->VAR_INBOX_PATH, "inbox") == 0)
+ /* TRANSLATORS: Inbox-Path and PINERC are literal, not to be translated */
+ && want_to(_("Preserve folder as \"Inbox-Path\" in PINERC"),
+ 'y', 'n', NO_HELP, WT_NORM) == 'y'){
+ set_variable(V_INBOX_PATH, int_mail, 1, 1, Main);
+ }
+ else{
+ if(pine_state->VAR_INBOX_PATH)
+ fs_give((void **)&pine_state->VAR_INBOX_PATH);
+
+ pine_state->VAR_INBOX_PATH = cpystr(int_mail);
+ }
+
+ if(pine_state && pine_state->ttyo){
+ blank_keymenu(pine_state->ttyo->screen_rows - 2, 0);
+ pine_state->painted_footer_on_startup = 0;
+ pine_state->mangled_footer = 1;
+ }
+
+ do_broach_folder(pine_state->inbox_name,
+ pine_state->context_list, NULL, DB_INBOXWOCNTXT);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 2 ,_("No folder opened"));
+
+ }
+ else
+
+#endif /* _WINDOWS */
+ if(F_ON(F_PREOPEN_STAYOPENS, ps_global))
+ preopen_stayopen_folders();
+
+ if(pine_state && pine_state->ttyo){
+ blank_keymenu(pine_state->ttyo->screen_rows - 2, 0);
+ pine_state->painted_footer_on_startup = 0;
+ pine_state->mangled_footer = 1;
+ }
+
+ /* open inbox */
+ do_broach_folder(pine_state->inbox_name,
+ pine_state->context_list, NULL, DB_INBOXWOCNTXT);
+ }
+
+ if(pine_state->mangled_footer)
+ pine_state->painted_footer_on_startup = 0;
+
+ if(args.action == aaFolder
+ && pine_state->mail_stream
+ && expire_sent_mail())
+ pine_state->painted_footer_on_startup = 0;
+
+ /*
+ * Initialize the defaults. Initializing here means that
+ * if they're remote, the user isn't prompted for an imap login
+ * before the display's drawn, AND there's the chance that
+ * we can climb onto the already opened folder's stream...
+ */
+ if(ps_global->first_time_user)
+ init_save_defaults(); /* initialize default save folders */
+
+ build_path(int_mail,
+ ps_global->VAR_OPER_DIR ? ps_global->VAR_OPER_DIR
+ : pine_state->home_dir,
+ INTERRUPTED_MAIL, sizeof(int_mail));
+ if(args.action == aaFolder
+ && (folder_exists(NULL, int_mail) & FEX_ISFILE))
+ q_status_message(SM_ORDER | SM_DING, 4, 5,
+ _("Use Compose command to continue interrupted message."));
+
+#if defined(USE_QUOTAS)
+ {
+ long q;
+ int over;
+ q = disk_quota(pine_state->home_dir, &over);
+ if(q > 0 && over){
+ q_status_message2(SM_ASYNC | SM_DING, 4, 5,
+ _("WARNING! Over your disk quota by %s bytes (%s)"),
+ comatose(q),byte_string(q));
+ }
+ }
+#endif
+
+ pine_state->in_init_seq = pine_state->save_in_init_seq;
+ pine_state->dont_use_init_cmds = 0;
+ clear_cursor_pos();
+
+ if(pine_state->give_fixed_warning)
+ q_status_message(SM_ASYNC, 0, 10,
+/* TRANSLATORS: config is an abbreviation for configuration */
+_("Note: some of your config options conflict with site policy and are ignored"));
+
+ if(!prune_folders_ok())
+ q_status_message(SM_ASYNC, 0, 10,
+ /* TRANSLATORS: Pruned-Folders is literal */
+ _("Note: ignoring Pruned-Folders outside of default collection for saves"));
+
+ if(get_input_timeout() == 0 &&
+ ps_global->VAR_INBOX_PATH &&
+ ps_global->VAR_INBOX_PATH[0] == '{')
+ q_status_message(SM_ASYNC, 0, 10,
+_("Note: Mail-Check-Interval=0 may cause IMAP server connection to time out"));
+
+#ifdef _WINDOWS
+ mswin_setnewmailwidth(ps_global->nmw_width);
+#endif
+
+
+ /*-------------------------------------------------------------------
+ Loop executing the commands
+
+ This is done like this so that one command screen can cause
+ another one to execute it with out going through the main menu.
+ ------------------------------------------------------------------*/
+ if(!pine_state->next_screen)
+ pine_state->next_screen = pine_state->start_in_index
+ ? mail_index_screen : main_menu_screen;
+ while(1){
+ if(pine_state->next_screen == SCREEN_FUN_NULL)
+ pine_state->next_screen = main_menu_screen;
+
+ (*(pine_state->next_screen))(pine_state);
+ }
+ }
+
+ exit(0);
+}
+
+
+/*
+ * The arguments need to be converted to UTF-8 for our internal use.
+ * Not all arguments are converted because some are used before we
+ * are able to do the conversion, like the pinerc name.
+ */
+void
+convert_args_to_utf8(struct pine *ps, ARGDATA_S *args)
+{
+ char *fromcharset = NULL;
+ char *conv;
+
+ if(args){
+ if(ps->keyboard_charmap && strucmp(ps->keyboard_charmap, "UTF-8")
+ && strucmp(ps->keyboard_charmap, "US-ASCII"))
+ fromcharset = ps->keyboard_charmap;
+ else if(ps->display_charmap && strucmp(ps->display_charmap, "UTF-8")
+ && strucmp(ps->display_charmap, "US-ASCII"))
+ fromcharset = ps->display_charmap;
+#ifndef _WINDOWS
+ else if(ps->VAR_OLD_CHAR_SET && strucmp(ps->VAR_OLD_CHAR_SET, "UTF-8")
+ && strucmp(ps->VAR_OLD_CHAR_SET, "US-ASCII"))
+ fromcharset = ps->VAR_OLD_CHAR_SET;
+#endif /* ! _WINDOWS */
+
+ if(args->action == aaURL && args->url){
+ conv = convert_to_utf8(args->url, fromcharset, 0);
+ if(conv){
+ fs_give((void **) &args->url);
+ args->url = conv;
+ }
+ }
+
+ if(args->action == aaFolder && args->data.folder){
+ conv = convert_to_utf8(args->data.folder, fromcharset, 0);
+ if(conv){
+ fs_give((void **) &args->data.folder);
+ args->data.folder = conv;
+ }
+ }
+
+ if(args->action == aaMore && args->data.file){
+ conv = convert_to_utf8(args->data.file, fromcharset, 0);
+ if(conv){
+ fs_give((void **) &args->data.file);
+ args->data.file = conv;
+ }
+ }
+
+ if(args->action == aaURL || args->action == aaMail){
+ if(args->data.mail.addrlist){
+ STRLIST_S *p;
+
+ for(p = args->data.mail.addrlist; p; p=p->next){
+ if(p->name){
+ conv = convert_to_utf8(p->name, fromcharset, 0);
+ if(conv){
+ fs_give((void **) &p->name);
+ p->name = conv;
+ }
+ }
+ }
+ }
+
+ if(args->data.mail.attachlist){
+ PATMT *p;
+
+ for(p = args->data.mail.attachlist; p; p=p->next){
+ if(p->filename){
+ conv = convert_to_utf8(p->filename, fromcharset, 0);
+ if(conv){
+ fs_give((void **) &p->filename);
+ p->filename = conv;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void
+preopen_stayopen_folders(void)
+{
+ char **open_these;
+
+ for(open_these = ps_global->VAR_PERMLOCKED;
+ open_these && *open_these; open_these++)
+ (void) do_broach_folder(*open_these, ps_global->context_list,
+ NULL, DB_NOVISIT);
+}
+
+
+/*
+ * read_stdin_char - simple function to return a character from
+ * redirected stdin
+ */
+int
+read_stdin_char(char *c)
+{
+ int rv;
+
+ /* it'd probably be a good idea to fix this to pre-read blocks */
+ while(1){
+ rv = read(PIPED_FD, c, 1);
+ if(rv < 0){
+ if(errno == EINTR){
+ dprint((2, "read_stdin_char: read interrupted, restarting\n"));
+ continue;
+ }
+ else
+ dprint((1, "read_stdin_char: read FAILED: %s\n",
+ error_description(errno)));
+ }
+ break;
+ }
+ return(rv);
+}
+
+
+/* this default is from the array of structs below */
+#define DEFAULT_MENU_ITEM ((unsigned) 3) /* LIST FOLDERS */
+#define ABOOK_MENU_ITEM ((unsigned) 4) /* ADDRESS BOOK */
+#define MAX_MENU_ITEM ((unsigned) 6)
+/*
+ * Skip this many spaces between rows of main menu screen.
+ * We have MAX_MENU_ITEM+1 = # of commands in menu
+ * 1 = copyright line
+ * MAX_MENU_ITEM = rows between commands
+ * 1 = extra row above commands
+ * 1 = row between commands and copyright
+ *
+ * To make it simple, if there is enough room for all of that include all the
+ * extra space, if not, cut it all out.
+ */
+#define MNSKIP(X) (((HEADER_ROWS(X)+FOOTER_ROWS(X)+(MAX_MENU_ITEM+1)+1+MAX_MENU_ITEM+1+1) <= (X)->ttyo->screen_rows) ? 1 : 0)
+
+static unsigned menu_index = DEFAULT_MENU_ITEM;
+
+/*
+ * One of these for each line that gets printed in the middle of the
+ * screen in the main menu.
+ */
+static struct menu_key {
+ char *key_and_name,
+ *news_addition;
+ int key_index; /* index into keymenu array for this cmd */
+} mkeys[] = {
+ /*
+ * TRANSLATORS: These next few are headings on the Main alpine menu.
+ * It's nice if the dashes can be made to line up vertically.
+ */
+ {N_(" %s HELP - Get help using Alpine"),
+ NULL, MAIN_HELP_KEY},
+ {N_(" %s COMPOSE MESSAGE - Compose and send%s a message"),
+ /* TRANSLATORS: We think of sending an email message or posting a news message.
+ The message is shown as Compose and send/post a message */
+ N_("/post"), MAIN_COMPOSE_KEY},
+ {N_(" %s MESSAGE INDEX - View messages in current folder"),
+ NULL, MAIN_INDEX_KEY},
+ {N_(" %s FOLDER LIST - Select a folder%s to view"),
+ /* TRANSLATORS: When news is supported the message above becomes
+ Select a folder OR news group to view */
+ N_(" OR news group"), MAIN_FOLDER_KEY},
+ {N_(" %s ADDRESS BOOK - Update address book"),
+ NULL, MAIN_ADDRESS_KEY},
+ {N_(" %s SETUP - Configure Alpine Options"),
+ NULL, MAIN_SETUP_KEY},
+ /* TRANSLATORS: final Main menu line */
+ {N_(" %s QUIT - Leave the Alpine program"),
+ NULL, MAIN_QUIT_KEY}
+};
+
+
+
+/*----------------------------------------------------------------------
+ display main menu and execute main menu commands
+
+ Args: The usual pine structure
+
+ Result: main menu commands are executed
+
+
+ M A I N M E N U S C R E E N
+
+ Paint the main menu on the screen, get the commands and either execute
+the function or pass back the name of the function to execute for the menu
+selection. Only simple functions that always return here can be executed
+here.
+
+This functions handling of new mail, redrawing, errors and such can
+serve as a template for the other screen that do much the same thing.
+
+There is a loop that fetchs and executes commands until a command to leave
+this screen is given. Then the name of the next screen to display is
+stored in next_screen member of the structure and this function is exited
+with a return.
+
+First a check for new mail is performed. This might involve reading the new
+mail into the inbox which might then cause the screen to be repainted.
+
+Then the general screen painting is done. This is usually controlled
+by a few flags and some other position variables. If they change they
+tell this part of the code what to repaint. This will include cursor
+motion and so on.
+ ----*/
+void
+main_menu_screen(struct pine *pine_state)
+{
+ UCS ch;
+ int cmd, just_a_navigate_cmd, setup_command, km_popped;
+ int notrealinbox;
+ char *new_folder, *utf8str;
+ CONTEXT_S *tc;
+ struct key_menu *km;
+ OtherMenu what;
+ Pos curs_pos;
+
+ ps_global = pine_state;
+ just_a_navigate_cmd = 0;
+ km_popped = 0;
+ menu_index = DEFAULT_MENU_ITEM;
+ what = FirstMenu; /* which keymenu to display */
+ ch = 'x'; /* For display_message 1st time through */
+ pine_state->next_screen = SCREEN_FUN_NULL;
+ pine_state->prev_screen = main_menu_screen;
+ curs_pos.row = pine_state->ttyo->screen_rows-FOOTER_ROWS(pine_state);
+ curs_pos.col = 0;
+ km = &main_keymenu;
+
+ mailcap_free(); /* free resources we won't be using for a while */
+
+ if(!pine_state->painted_body_on_startup
+ && !pine_state->painted_footer_on_startup){
+ pine_state->mangled_screen = 1;
+ }
+
+ dprint((1, "\n\n ---- MAIN_MENU_SCREEN ----\n"));
+
+ while(1){
+ if(km_popped){
+ km_popped--;
+ if(km_popped == 0){
+ clearfooter(pine_state);
+ pine_state->mangled_body = 1;
+ }
+ }
+
+ /*
+ * fix up redrawer just in case some submenu caused it to get
+ * reassigned...
+ */
+ pine_state->redrawer = main_redrawer;
+
+ /*----------- Check for new mail -----------*/
+ if(new_mail(0, NM_TIMING(ch), NM_STATUS_MSG | NM_DEFER_SORT) >= 0)
+ pine_state->mangled_header = 1;
+
+ if(streams_died())
+ pine_state->mangled_header = 1;
+
+ show_main_screen(pine_state, just_a_navigate_cmd, what, km,
+ km_popped, &curs_pos);
+ just_a_navigate_cmd = 0;
+ what = SameMenu;
+
+ /*---- This displays new mail notification, or errors ---*/
+ if(km_popped){
+ FOOTER_ROWS(pine_state) = 3;
+ mark_status_dirty();
+ }
+
+ display_message(ch);
+ if(km_popped){
+ FOOTER_ROWS(pine_state) = 1;
+ mark_status_dirty();
+ }
+
+ if(F_OFF(F_SHOW_CURSOR, ps_global)){
+ curs_pos.row =pine_state->ttyo->screen_rows-FOOTER_ROWS(pine_state);
+ curs_pos.col =0;
+ }
+
+ MoveCursor(curs_pos.row, curs_pos.col);
+
+ /*------ Read the command from the keyboard ----*/
+#ifdef MOUSE
+ mouse_in_content(KEY_MOUSE, -1, -1, 0, 0);
+ register_mfunc(mouse_in_content, HEADER_ROWS(pine_state), 0,
+ pine_state->ttyo->screen_rows-(FOOTER_ROWS(pine_state)+1),
+ pine_state->ttyo->screen_cols);
+#endif
+#if defined(DOS) || defined(OS2)
+ /*
+ * AND pre-build header lines. This works just fine under
+ * DOS since we wait for characters in a loop. Something will
+ * will have to change under UNIX if we want to do the same.
+ */
+ /* while_waiting = build_header_cache; */
+#ifdef _WINDOWS
+ mswin_sethelptextcallback(pcpine_help_main);
+ mswin_mousetrackcallback(pcpine_main_cursor);
+#endif
+#endif
+ ch = READ_COMMAND(&utf8str);
+#ifdef MOUSE
+ clear_mfunc(mouse_in_content);
+#endif
+#if defined(DOS) || defined(OS2)
+/* while_waiting = NULL; */
+#ifdef _WINDOWS
+ mswin_sethelptextcallback(NULL);
+ mswin_mousetrackcallback(NULL);
+#endif
+#endif
+
+ /* No matter what, Quit here always works */
+ if(ch == 'q' || ch == 'Q'){
+ cmd = MC_QUIT;
+ }
+#ifdef DEBUG
+ else if(debug && ch && ch < 0x80 && strchr("123456789", ch)){
+ int olddebug;
+
+ olddebug = debug;
+ debug = ch - '0';
+ if(debug > 7)
+ ps_global->debug_timestamp = 1;
+ else
+ ps_global->debug_timestamp = 0;
+
+ if(debug > 7)
+ ps_global->debug_imap = 4;
+ else if(debug > 6)
+ ps_global->debug_imap = 3;
+ else if(debug > 4)
+ ps_global->debug_imap = 2;
+ else if(debug > 2)
+ ps_global->debug_imap = 1;
+ else
+ ps_global->debug_imap = 0;
+
+ if(ps_global->mail_stream){
+ if(ps_global->debug_imap > 0){
+ mail_debug(ps_global->mail_stream);
+#ifdef _WINDOWS
+ mswin_enableimaptelemetry(TRUE);
+#endif
+ }
+ else{
+ mail_nodebug(ps_global->mail_stream);
+#ifdef _WINDOWS
+ mswin_enableimaptelemetry(FALSE);
+#endif
+ }
+ }
+
+ if(debug > 7 && olddebug <= 7)
+ mail_parameters(NULL, SET_TCPDEBUG, (void *) TRUE);
+ else if(debug <= 7 && olddebug > 7 && !ps_global->debugmem)
+ mail_parameters(NULL, SET_TCPDEBUG, (void *) FALSE);
+
+ dprint((1, "*** Debug level set to %d ***\n", debug));
+ if(debugfile)
+ fflush(debugfile);
+
+ q_status_message1(SM_ORDER, 0, 1, _("Debug level set to %s"),
+ int2string(debug));
+ continue;
+ }
+#endif /* DEBUG */
+ else{
+ cmd = menu_command(ch, km);
+
+ if(km_popped)
+ switch(cmd){
+ case MC_NONE :
+ case MC_OTHER :
+ case MC_RESIZE :
+ case MC_REPAINT :
+ km_popped++;
+ break;
+
+ default:
+ clearfooter(pine_state);
+ break;
+ }
+ }
+
+ /*------ Execute the command ------*/
+ switch (cmd){
+help_case :
+ /*------ HELP ------*/
+ case MC_HELP :
+
+ if(FOOTER_ROWS(pine_state) == 1 && km_popped == 0){
+ km_popped = 2;
+ pine_state->mangled_footer = 1;
+ }
+ else{
+ /* TRANSLATORS: This is a screen title */
+ helper(main_menu_tx, _("HELP FOR MAIN MENU"), 0);
+ pine_state->mangled_screen = 1;
+ }
+
+ break;
+
+
+ /*---------- display other key bindings ------*/
+ case MC_OTHER :
+ if(ch == 'o')
+ warn_other_cmds();
+
+ what = NextMenu;
+ pine_state->mangled_footer = 1;
+ break;
+
+
+ /*---------- Previous item in menu ----------*/
+ case MC_PREVITEM :
+ if(menu_index > 0) {
+ menu_index--;
+ pine_state->mangled_body = 1;
+ if(km->which == 0)
+ pine_state->mangled_footer = 1;
+
+ just_a_navigate_cmd++;
+ }
+ else
+ /* TRANSLATORS: list refers to list of commands in main menu */
+ q_status_message(SM_ORDER, 0, 2, _("Already at top of list"));
+
+ break;
+
+
+ /*---------- Next item in menu ----------*/
+ case MC_NEXTITEM :
+ if(menu_index < MAX_MENU_ITEM){
+ menu_index++;
+ pine_state->mangled_body = 1;
+ if(km->which == 0)
+ pine_state->mangled_footer = 1;
+
+ just_a_navigate_cmd++;
+ }
+ else
+ q_status_message(SM_ORDER, 0, 2, _("Already at bottom of list"));
+
+ break;
+
+
+ /*---------- Release Notes ----------*/
+ case MC_RELNOTES :
+ /* TRANSLATORS: This is a screen title */
+ helper(h_news, _("ALPINE RELEASE NOTES"), 0);
+ pine_state->mangled_screen = 1;
+ break;
+
+
+#ifdef KEYBOARD_LOCK
+ /*---------- Keyboard lock ----------*/
+ case MC_KBLOCK :
+ (void) lock_keyboard();
+ pine_state->mangled_screen = 1;
+ break;
+#endif /* KEYBOARD_LOCK */
+
+
+ /*---------- Quit pine ----------*/
+ case MC_QUIT :
+ pine_state->next_screen = quit_screen;
+ return;
+
+
+ /*---------- Go to composer ----------*/
+ case MC_COMPOSE :
+ pine_state->next_screen = compose_screen;
+ return;
+
+
+ /*---- Go to alternate composer ------*/
+ case MC_ROLE :
+ pine_state->next_screen = alt_compose_screen;
+ return;
+
+
+ /*---------- Top of Folder list ----------*/
+ case MC_COLLECTIONS :
+ pine_state->next_screen = folder_screen;
+ return;
+
+
+ /*---------- Goto new folder ----------*/
+ case MC_GOTO :
+ tc = ps_global->context_current;
+ new_folder = broach_folder(-FOOTER_ROWS(pine_state), 1, &notrealinbox, &tc);
+ if(new_folder)
+ visit_folder(ps_global, new_folder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
+
+ return;
+
+
+ /*---------- Go to index ----------*/
+ case MC_INDEX :
+ if(THREADING()
+ && sp_viewing_a_thread(pine_state->mail_stream)
+ && unview_thread(pine_state, pine_state->mail_stream,
+ pine_state->msgmap)){
+ pine_state->view_skipped_index = 0;
+ pine_state->mangled_screen = 1;
+ }
+
+ pine_state->next_screen = mail_index_screen;
+ return;
+
+
+ /*---------- Review Status Messages ----------*/
+ case MC_JOURNAL :
+ review_messages();
+ pine_state->mangled_screen = 1;
+ break;
+
+
+ /*---------- Setup mini menu ----------*/
+ case MC_SETUP :
+setup_case :
+ setup_command = setup_menu(pine_state);
+ pine_state->mangled_footer = 1;
+ do_setup_task(setup_command);
+ if(ps_global->next_screen != main_menu_screen)
+ return;
+
+ break;
+
+
+ /*---------- Go to address book ----------*/
+ case MC_ADDRBOOK :
+ pine_state->next_screen = addr_book_screen;
+ return;
+
+
+ /*------ Repaint the works -------*/
+ case MC_RESIZE :
+ case MC_REPAINT :
+ ClearScreen();
+ pine_state->mangled_screen = 1;
+ break;
+
+
+#ifdef MOUSE
+ /*------- Mouse event ------*/
+ case MC_MOUSE :
+ {
+ MOUSEPRESS mp;
+ unsigned ndmi;
+ struct pine *ps = pine_state;
+
+ mouse_get_last (NULL, &mp);
+
+#ifdef _WINDOWS
+ if(mp.button == M_BUTTON_RIGHT){
+ if(!mp.doubleclick){
+ static MPopup main_popup[] = {
+ {tQueue, {"Folder List", lNormal}, {'L'}},
+ {tQueue, {"Message Index", lNormal}, {'I'}},
+ {tSeparator},
+ {tQueue, {"Address Book", lNormal}, {'A'}},
+ {tQueue, {"Setup Options", lNormal}, {'S'}},
+ {tTail}
+ };
+
+ mswin_popup(main_popup);
+ }
+ }
+ else {
+#endif
+ if (mp.row >= (HEADER_ROWS(ps) + MNSKIP(ps)))
+ ndmi = (mp.row+1 - HEADER_ROWS(ps) - (MNSKIP(ps)+1))/(MNSKIP(ps)+1);
+
+ if (mp.row >= (HEADER_ROWS(ps) + MNSKIP(ps))
+ && !(MNSKIP(ps) && (mp.row+1) & 0x01)
+ && ndmi <= MAX_MENU_ITEM
+ && FOOTER_ROWS(ps) + (ndmi+1)*(MNSKIP(ps)+1)
+ + MNSKIP(ps) + FOOTER_ROWS(ps) <= ps->ttyo->screen_rows){
+ if(mp.doubleclick){
+ switch(ndmi){ /* fake main_screen request */
+ case 0 :
+ goto help_case;
+
+ case 1 :
+ pine_state->next_screen = compose_screen;
+ return;
+
+ case 2 :
+ pine_state->next_screen = mail_index_screen;
+ return;
+
+ case 3 :
+ pine_state->next_screen = folder_screen;
+ return;
+
+ case 4 :
+ pine_state->next_screen = addr_book_screen;
+ return;
+
+ case 5 :
+ goto setup_case;
+
+ case 6 :
+ pine_state->next_screen = quit_screen;
+ return;
+
+ default: /* no op */
+ break;
+ }
+ }
+ else{
+ menu_index = ndmi;
+ pine_state->mangled_body = 1;
+ if(km->which == 0)
+ pine_state->mangled_footer = 1;
+
+ just_a_navigate_cmd++;
+ }
+ }
+#ifdef _WINDOWS
+ }
+#endif
+ }
+
+ break;
+#endif
+
+
+ /*------ Input timeout ------*/
+ case MC_NONE :
+ break; /* noop for timeout loop mail check */
+
+
+ /*------ Bogus Input ------*/
+ case MC_UNKNOWN :
+ if(ch == 'm' || ch == 'M'){
+ q_status_message(SM_ORDER, 0, 1, "Already in Main Menu");
+ break;
+ }
+
+ default:
+ bogus_command(ch, F_ON(F_USE_FK,pine_state) ? "F1" : "?");
+ break;
+
+ case MC_UTF8:
+ bogus_utf8_command(utf8str, F_ON(F_USE_FK, pine_state) ? "F1" : "?");
+ break;
+ } /* the switch */
+ } /* the BIG while loop! */
+}
+
+
+/*----------------------------------------------------------------------
+ Re-Draw the main menu
+
+ Args: none.
+
+ Result: main menu is re-displayed
+ ----*/
+void
+main_redrawer(void)
+{
+ struct key_menu *km = &main_keymenu;
+
+ ps_global->mangled_screen = 1;
+ show_main_screen(ps_global, 0, FirstMenu, km, 0, (Pos *)NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Draw the main menu
+
+ Args: pine_state - the usual struct
+ quick_draw - tells do_menu() it can skip some drawing
+ what - tells which section of keymenu to draw
+ km - the keymenu
+ cursor_pos - returns a good position for the cursor to be located
+
+ Result: main menu is displayed
+ ----*/
+void
+show_main_screen(struct pine *ps, int quick_draw, OtherMenu what,
+ struct key_menu *km, int km_popped, Pos *cursor_pos)
+{
+ if(ps->painted_body_on_startup || ps->painted_footer_on_startup){
+ ps->mangled_screen = 0; /* only worry about it here */
+ ps->mangled_header = 1; /* we have to redo header */
+ if(!ps->painted_body_on_startup)
+ ps->mangled_body = 1; /* make sure to paint body*/
+
+ if(!ps->painted_footer_on_startup)
+ ps->mangled_footer = 1; /* make sure to paint footer*/
+
+ ps->painted_body_on_startup = 0;
+ ps->painted_footer_on_startup = 0;
+ }
+
+ if(ps->mangled_screen){
+ ps->mangled_header = 1;
+ ps->mangled_body = 1;
+ ps->mangled_footer = 1;
+ ps->mangled_screen = 0;
+ }
+
+#ifdef _WINDOWS
+ /* Reset the scroll range. Main screen never scrolls. */
+ scroll_setrange (0L, 0L);
+ mswin_beginupdate();
+#endif
+
+ /* paint the titlebar if needed */
+ if(ps->mangled_header){
+ /* TRANSLATORS: screen title */
+ set_titlebar(_("MAIN MENU"), ps->mail_stream, ps->context_current,
+ ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0, NULL);
+ ps->mangled_header = 0;
+ }
+
+ /* paint the body if needed */
+ if(ps->mangled_body){
+ if(!quick_draw)
+ ClearBody();
+
+ do_menu(quick_draw, cursor_pos, km);
+ ps->mangled_body = 0;
+ }
+
+ /* paint the keymenu if needed */
+ if(km && ps->mangled_footer){
+ static char label[LONGEST_LABEL + 2 + 1], /* label + brackets + \0 */
+ name[8];
+ bitmap_t bitmap;
+
+ setbitmap(bitmap);
+
+#ifdef KEYBOARD_LOCK
+ if(ps_global->restricted || F_ON(F_DISABLE_KBLOCK_CMD,ps_global))
+#endif
+ clrbitn(MAIN_KBLOCK_KEY, bitmap);
+
+ menu_clear_binding(km, '>');
+ menu_clear_binding(km, '.');
+ menu_clear_binding(km, KEY_RIGHT);
+ menu_clear_binding(km, ctrl('M'));
+ menu_clear_binding(km, ctrl('J'));
+ km->keys[MAIN_DEFAULT_KEY].bind
+ = km->keys[mkeys[menu_index].key_index].bind;
+ km->keys[MAIN_DEFAULT_KEY].label
+ = km->keys[mkeys[menu_index].key_index].label;
+
+ /* put brackets around the default action */
+ snprintf(label, sizeof(label), "[%s]", km->keys[mkeys[menu_index].key_index].label);
+ label[sizeof(label)-1] = '\0';
+ strncpy(name, ">", sizeof(name));
+ name[sizeof(name)-1] = '\0';
+ km->keys[MAIN_DEFAULT_KEY].label = label;
+ km->keys[MAIN_DEFAULT_KEY].name = name;
+ menu_add_binding(km, '>', km->keys[MAIN_DEFAULT_KEY].bind.cmd);
+ menu_add_binding(km, '.', km->keys[MAIN_DEFAULT_KEY].bind.cmd);
+ menu_add_binding(km, ctrl('M'), km->keys[MAIN_DEFAULT_KEY].bind.cmd);
+ menu_add_binding(km, ctrl('J'), km->keys[MAIN_DEFAULT_KEY].bind.cmd);
+
+ if(F_ON(F_ARROW_NAV,ps_global))
+ menu_add_binding(km, KEY_RIGHT, km->keys[MAIN_DEFAULT_KEY].bind.cmd);
+
+ if(km_popped){
+ FOOTER_ROWS(ps) = 3;
+ clearfooter(ps);
+ }
+
+ draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps_global), 0, what);
+ ps->mangled_footer = 0;
+ if(km_popped){
+ FOOTER_ROWS(ps) = 1;
+ mark_keymenu_dirty();
+ }
+ }
+
+#ifdef _WINDOWS
+ mswin_endupdate();
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Actually display the main menu
+
+ Args: quick_draw - just a next or prev command was typed so we only have
+ to redraw the highlighting
+ cursor_pos - a place to return a good value for cursor location
+
+ Result: Main menu is displayed
+ ---*/
+void
+do_menu(int quick_draw, Pos *cursor_pos, struct key_menu *km)
+{
+ struct pine *ps = ps_global;
+ int dline, indent, longest = 0, cmd;
+ char buf[4*MAX_SCREEN_COLS+1];
+ char buf2[4*MAX_SCREEN_COLS+1];
+ static int last_inverse = -1;
+ Pos pos;
+
+ /* find the longest command */
+ for(cmd = 0; cmd < sizeof(mkeys)/(sizeof(mkeys[1])); cmd++){
+ memset((void *) buf, ' ', sizeof(buf));
+ snprintf(buf, sizeof(buf), mkeys[cmd].key_and_name[0] ? _(mkeys[cmd].key_and_name) : "",
+ (F_OFF(F_USE_FK,ps)
+ && km->keys[mkeys[cmd].key_index].name)
+ ? km->keys[mkeys[cmd].key_index].name : "",
+ (ps->VAR_NEWS_SPEC && mkeys[cmd].news_addition && mkeys[cmd].news_addition[0])
+ ? _(mkeys[cmd].news_addition) : "");
+ buf[sizeof(buf)-1] = '\0';
+
+ if(longest < (indent = utf8_width(buf)))
+ longest = indent;
+ }
+
+ indent = MAX(((ps->ttyo->screen_cols - longest)/2) - 1, 0);
+
+ dline = HEADER_ROWS(ps) + MNSKIP(ps);
+ for(cmd = 0; cmd < sizeof(mkeys)/(sizeof(mkeys[1])); cmd++){
+ /* leave room for copyright and footer */
+ if(dline + MNSKIP(ps) + 1 + FOOTER_ROWS(ps) >= ps->ttyo->screen_rows)
+ break;
+
+ if(quick_draw && !(cmd == last_inverse || cmd == menu_index)){
+ dline += (1 + MNSKIP(ps));
+ continue;
+ }
+
+ if(cmd == menu_index)
+ StartInverse();
+
+ memset((void *) buf, ' ', sizeof(buf));
+ snprintf(buf, sizeof(buf), mkeys[cmd].key_and_name[0] ? _(mkeys[cmd].key_and_name) : "",
+ (F_OFF(F_USE_FK,ps)
+ && km->keys[mkeys[cmd].key_index].name)
+ ? km->keys[mkeys[cmd].key_index].name : "",
+ (ps->VAR_NEWS_SPEC && mkeys[cmd].news_addition && mkeys[cmd].news_addition[0])
+ ? _(mkeys[cmd].news_addition) : "");
+ buf[sizeof(buf)-1] = '\0';
+
+ utf8_pad_to_width(buf2, buf, sizeof(buf2),
+ MIN(ps->ttyo->screen_cols-indent,longest+1), 1);
+ pos.row = dline++;
+ pos.col = indent;
+ PutLine0(pos.row, pos.col, buf2);
+
+ if(MNSKIP(ps))
+ dline++;
+
+ if(cmd == menu_index){
+ if(cursor_pos){
+ cursor_pos->row = pos.row;
+ /* 6 is 1 for the letter plus 5 spaces */
+ cursor_pos->col = pos.col + 6;
+ if(F_OFF(F_USE_FK,ps))
+ cursor_pos->col++;
+
+ cursor_pos->col = MIN(cursor_pos->col, ps->ttyo->screen_cols);
+ }
+
+ EndInverse();
+ }
+ }
+
+
+ last_inverse = menu_index;
+
+ if(!quick_draw && FOOTER_ROWS(ps)+1 < ps->ttyo->screen_rows){
+ utf8_to_width(buf2, LEGAL_NOTICE, sizeof(buf2),
+ ps->ttyo->screen_cols-3, NULL);
+ PutLine0(ps->ttyo->screen_rows - (FOOTER_ROWS(ps)+1),
+ MAX(0, ((ps->ttyo->screen_cols-utf8_width(buf2))/2)),
+ buf2);
+ }
+
+ fflush(stdout);
+}
+
+
+int
+choose_setup_cmd(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 1;
+ SRV_S *srv;
+
+ if(!(srv = (SRV_S *)sparms->proc.data.p)){
+ sparms->proc.data.p = (SRV_S *)fs_get(sizeof(*srv));
+ srv = (SRV_S *)sparms->proc.data.p;
+ memset(srv, 0, sizeof(*srv));
+ }
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ switch(cmd){
+ case MC_PRINTER :
+ srv->cmd = 'p';
+ break;
+
+ case MC_PASSWD :
+ srv->cmd = 'n';
+ break;
+
+ case MC_CONFIG :
+ srv->cmd = 'c';
+ break;
+
+ case MC_SIG :
+ srv->cmd = 's';
+ break;
+
+ case MC_ABOOKS :
+ srv->cmd = 'a';
+ break;
+
+ case MC_CLISTS :
+ srv->cmd = 'l';
+ break;
+
+ case MC_RULES :
+ srv->cmd = 'r';
+ break;
+
+ case MC_DIRECTORY :
+ srv->cmd = 'd';
+ break;
+
+ case MC_KOLOR :
+ srv->cmd = 'k';
+ break;
+
+ case MC_REMOTE :
+ srv->cmd = 'z';
+ break;
+
+ case MC_SECURITY : /* S/MIME setup screen */
+ srv->cmd = 'm';
+ break;
+
+ case MC_EXCEPT :
+ srv->exc = !srv->exc;
+ menu_clear_binding(sparms->keys.menu, 'x');
+ if(srv->exc){
+ if(sparms->bar.title) fs_give((void **)&sparms->bar.title);
+ /* TRANSLATORS: screen title */
+ sparms->bar.title = cpystr(_("SETUP EXCEPTIONS"));
+ ps_global->mangled_header = 1;
+ /* TRANSLATORS: The reason the X is upper case in eXceptions
+ is because the command key is X. It isn't necessary, just
+ nice if it works. */
+ menu_init_binding(sparms->keys.menu, 'x', MC_EXCEPT, "X",
+ N_("not eXceptions"), SETUP_EXCEPT);
+ }
+ else{
+ if(sparms->bar.title) fs_give((void **)&sparms->bar.title);
+ /* TRANSLATORS: screen title */
+ sparms->bar.title = cpystr(_("SETUP"));
+ ps_global->mangled_header = 1;
+ menu_init_binding(sparms->keys.menu, 'x', MC_EXCEPT, "X",
+ N_("eXceptions"), SETUP_EXCEPT);
+ }
+
+ if(sparms->keys.menu->which == 1)
+ ps_global->mangled_footer = 1;
+
+ rv = 0;
+ break;
+
+ case MC_NO_EXCEPT :
+#if defined(DOS) || defined(OS2)
+ q_status_message(SM_ORDER, 0, 2, _("Need argument \"-x <except_config>\" or \"PINERCEX\" file to use eXceptions"));
+#else
+ q_status_message(SM_ORDER, 0, 2, _("Need argument \"-x <except_config>\" or \".pinercex\" file to use eXceptions"));
+#endif
+ rv = 0;
+ break;
+
+ default:
+ panic("Unexpected command in choose_setup_cmd");
+ break;
+ }
+
+ return(rv);
+}
+
+
+int
+setup_menu(struct pine *ps)
+{
+ int ret = 0, exceptions = 0;
+ int printer = 0, passwd = 0, config = 0, sig = 0, dir = 0, smime = 0, exc = 0;
+ SCROLL_S sargs;
+ SRV_S *srv;
+ STORE_S *store;
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error allocating space."));
+ return(ret);
+ }
+
+#if !defined(DOS)
+ if(!ps_global->vars[V_PRINTER].is_fixed) /* printer can be changed */
+ printer++;
+#endif
+
+#ifdef PASSWD_PROG
+ if(F_OFF(F_DISABLE_PASSWORD_CMD,ps_global)) /* password is allowed */
+ passwd++;
+#endif
+
+ if(F_OFF(F_DISABLE_CONFIG_SCREEN,ps_global)) /* config allowed */
+ config++;
+
+ if(F_OFF(F_DISABLE_SIGEDIT_CMD,ps_global)) /* .sig editing is allowed */
+ sig++;
+
+#ifdef ENABLE_LDAP
+ dir++;
+#endif
+
+#ifdef SMIME
+ smime++;
+#endif
+
+ if(ps_global->post_prc)
+ exc++;
+
+ /* TRANSLATORS: starting here we have a whole screen of help text */
+ so_puts(store, _("This is the Setup screen for Alpine. Choose from the following commands:\n"));
+
+ so_puts(store, "\n");
+ so_puts(store, _("(E) Exit Setup:\n"));
+ so_puts(store, _(" This puts you back at the Main Menu.\n"));
+
+ if(exc){
+ so_puts(store, "\n");
+ so_puts(store, _("(X) eXceptions:\n"));
+ so_puts(store, _(" This command is different from the rest. It is not actually a command\n"));
+ so_puts(store, _(" itself. Instead, it is a toggle which modifies the behavior of the\n"));
+ so_puts(store, _(" other commands. You toggle Exceptions editing on and off with this\n"));
+ so_puts(store, _(" command. When it is off you will be editing (changing) your regular\n"));
+ so_puts(store, _(" configuration file. When it is on you will be editing your exceptions\n"));
+ so_puts(store, _(" configuration file. For example, you might want to type the command \n"));
+ so_puts(store, _(" \"eXceptions\" followed by \"Kolor\" to setup different screen colors\n"));
+ so_puts(store, _(" on a particular platform.\n"));
+ so_puts(store, _(" (Note: this command does not show up on the keymenu at the bottom of\n"));
+ so_puts(store, _(" the screen unless you press \"O\" for \"Other Commands\" --but you don't\n"));
+ so_puts(store, _(" need to press the \"O\" in order to invoke the command.)\n"));
+ }
+
+ if(printer){
+ so_puts(store, "\n");
+ so_puts(store, _("(P) Printer:\n"));
+ so_puts(store, _(" Allows you to set a default printer and to define custom\n"));
+ so_puts(store, _(" print commands.\n"));
+ }
+
+ if(passwd){
+ so_puts(store, "\n");
+ so_puts(store, _("(N) Newpassword:\n"));
+ so_puts(store, _(" Change your password.\n"));
+ }
+
+ if(config){
+ so_puts(store, "\n");
+ so_puts(store, _("(C) Config:\n"));
+ so_puts(store, _(" Allows you to set or unset many features of Alpine.\n"));
+ so_puts(store, _(" You may also set the values of many options with this command.\n"));
+ }
+
+ if(sig){
+ so_puts(store, "\n");
+ so_puts(store, _("(S) Signature:\n"));
+ so_puts(store, _(" Enter or edit a custom signature which will\n"));
+ so_puts(store, _(" be included with each new message you send.\n"));
+ }
+
+ so_puts(store, "\n");
+ so_puts(store, _("(A) AddressBooks:\n"));
+ so_puts(store, _(" Define a non-default address book.\n"));
+
+ so_puts(store, "\n");
+ so_puts(store, _("(L) collectionLists:\n"));
+ so_puts(store, _(" You may define groups of folders to help you better organize your mail.\n"));
+
+ so_puts(store, "\n");
+ so_puts(store, _("(R) Rules:\n"));
+ so_puts(store, _(" This has up to six sub-categories: Roles, Index Colors, Filters,\n"));
+ so_puts(store, _(" SetScores, Search, and Other. If the Index Colors option is\n"));
+ so_puts(store, _(" missing you may turn it on (if possible) with Setup/Kolor.\n"));
+ so_puts(store, _(" If Roles is missing it has probably been administratively disabled.\n"));
+
+ if(dir){
+ so_puts(store, "\n");
+ so_puts(store, _("(D) Directory:\n"));
+ so_puts(store, _(" Define an LDAP Directory server for Alpine's use. A directory server is\n"));
+ so_puts(store, _(" similar to an address book, but it is usually maintained by an\n"));
+ so_puts(store, _(" organization. It is similar to a telephone directory.\n"));
+ }
+
+ so_puts(store, "\n");
+ so_puts(store, _("(K) Kolor:\n"));
+ so_puts(store, _(" Set custom colors for various parts of the Alpine screens. For example, the\n"));
+ so_puts(store, _(" command key labels, the titlebar at the top of each page, and quoted\n"));
+ so_puts(store, _(" sections of messages you are viewing.\n"));
+
+ if(smime){
+ so_puts(store, "\n");
+ so_puts(store, _("(M) S/MIME:\n"));
+ so_puts(store, _(" Setup for using S/MIME to verify signed messages, decrypt\n"));
+ so_puts(store, _(" encrypted messages, and to sign or encrypt outgoing messages.\n"));
+ }
+
+ so_puts(store, "\n");
+ so_puts(store, _("(Z) RemoteConfigSetup:\n"));
+ so_puts(store, _(" This is a command you will probably only want to use once, if at all.\n"));
+ so_puts(store, _(" It helps you transfer your Alpine configuration data to an IMAP server,\n"));
+ so_puts(store, _(" where it will be accessible from any of the computers you read mail\n"));
+ so_puts(store, _(" from (using Alpine). The idea behind a remote configuration is that you\n"));
+ so_puts(store, _(" can change your configuration in one place and have that change show\n"));
+ so_puts(store, _(" up on all of the computers you use.\n"));
+ so_puts(store, _(" (Note: this command does not show up on the keymenu at the bottom of\n"));
+ so_puts(store, _(" the screen unless you press \"O\" for \"Other Commands\" --but you don't\n"));
+ so_puts(store, _(" need to press the \"O\" in order to invoke the command.)\n"));
+
+ /* put this down here for people who don't have exceptions */
+ if(!exc){
+ so_puts(store, "\n");
+ so_puts(store, _("(X) eXceptions:\n"));
+ so_puts(store, _(" This command is different from the rest. It is not actually a command\n"));
+ so_puts(store, _(" itself. Instead, it is a toggle which modifies the behavior of the\n"));
+ so_puts(store, _(" other commands. You toggle Exceptions editing on and off with this\n"));
+ so_puts(store, _(" command. When it is off you will be editing (changing) your regular\n"));
+ so_puts(store, _(" configuration file. When it is on you will be editing your exceptions\n"));
+ so_puts(store, _(" configuration file. For example, you might want to type the command \n"));
+ so_puts(store, _(" \"eXceptions\" followed by \"Kolor\" to setup different screen colors\n"));
+ so_puts(store, _(" on a particular platform.\n"));
+ so_puts(store, _(" (Note: this command does not do anything unless you have a configuration\n"));
+ so_puts(store, _(" with exceptions enabled (you don't have that). Common ways to enable an\n"));
+ so_puts(store, _(" exceptions config are the command line argument \"-x <exception_config>\";\n"));
+ so_puts(store, _(" or the existence of the file \".pinercex\" for Unix Alpine, or \"PINERCEX\")\n"));
+ so_puts(store, _(" for PC-Alpine.)\n"));
+ so_puts(store, _(" (Another note: this command does not show up on the keymenu at the bottom\n"));
+ so_puts(store, _(" of the screen unless you press \"O\" for \"Other Commands\" --but you\n"));
+ so_puts(store, _(" don't need to press the \"O\" in order to invoke the command.)\n"));
+ }
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Information About Setup Command");
+ sargs.bar.title = cpystr(_("SETUP"));
+ sargs.proc.tool = choose_setup_cmd;
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+ sargs.keys.menu = &choose_setup_keymenu;
+ sargs.keys.menu->how_many = 2;
+
+ setbitmap(sargs.keys.bitmap);
+ if(!printer)
+ clrbitn(SETUP_PRINTER, sargs.keys.bitmap);
+
+ if(!passwd)
+ clrbitn(SETUP_PASSWD, sargs.keys.bitmap);
+
+ if(!config)
+ clrbitn(SETUP_CONFIG, sargs.keys.bitmap);
+
+ if(!sig)
+ clrbitn(SETUP_SIG, sargs.keys.bitmap);
+
+ if(!dir)
+ clrbitn(SETUP_DIRECTORY, sargs.keys.bitmap);
+
+ if(!smime)
+ clrbitn(SETUP_SMIME, sargs.keys.bitmap);
+
+ if(exc)
+ menu_init_binding(sargs.keys.menu, 'x', MC_EXCEPT, "X",
+ N_("eXceptions"), SETUP_EXCEPT);
+ else
+ menu_init_binding(sargs.keys.menu, 'x', MC_NO_EXCEPT, "X",
+ N_("eXceptions"), SETUP_EXCEPT);
+
+
+ scrolltool(&sargs);
+
+ ps->mangled_screen = 1;
+
+ srv = (SRV_S *)sargs.proc.data.p;
+
+ exceptions = srv ? srv->exc : 0;
+
+ so_give(&store);
+
+ if(sargs.bar.title) fs_give((void**)&sargs.bar.title);
+ if(srv){
+ ret = srv->cmd;
+ fs_give((void **)&sargs.proc.data.p);
+ }
+ else
+ ret = 'e';
+
+ return(ret | (exceptions ? EDIT_EXCEPTION : 0));
+}
+
+
+/*----------------------------------------------------------------------
+
+Args: command -- command char to perform
+
+ ----*/
+void
+do_setup_task(int command)
+{
+ char *err = NULL;
+ int rtype;
+ int edit_exceptions = 0;
+ int do_lit_sig = 0;
+
+ if(command & EDIT_EXCEPTION){
+ edit_exceptions = 1;
+ command &= ~EDIT_EXCEPTION;
+ }
+
+ switch(command) {
+ /*----- EDIT SIGNATURE -----*/
+ case 's':
+ if(ps_global->VAR_LITERAL_SIG)
+ do_lit_sig = 1;
+ else {
+ char sig_path[MAXPATH+1];
+
+ if(!signature_path(ps_global->VAR_SIGNATURE_FILE, sig_path, MAXPATH))
+ do_lit_sig = 1;
+ else if((!IS_REMOTE(ps_global->VAR_SIGNATURE_FILE)
+ && can_access(sig_path, READ_ACCESS) == 0)
+ ||(IS_REMOTE(ps_global->VAR_SIGNATURE_FILE)
+ && (folder_exists(NULL, sig_path) & FEX_ISFILE)))
+ do_lit_sig = 0;
+ else if(!ps_global->vars[V_SIGNATURE_FILE].main_user_val.p
+ && !ps_global->vars[V_SIGNATURE_FILE].cmdline_val.p
+ && !ps_global->vars[V_SIGNATURE_FILE].fixed_val.p)
+ do_lit_sig = 1;
+ else
+ do_lit_sig = 0;
+ }
+
+ if(do_lit_sig){
+ char *result = NULL;
+ char **apval;
+ EditWhich ew;
+ int readonly = 0;
+
+ ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;
+
+ if(ps_global->restricted)
+ readonly = 1;
+ else switch(ew){
+ case Main:
+ readonly = ps_global->prc->readonly;
+ break;
+ case Post:
+ readonly = ps_global->post_prc->readonly;
+ break;
+ default:
+ break;
+ }
+
+ if(readonly)
+ err = cpystr(ps_global->restricted
+ ? "Alpine demo can't change config file"
+ : _("Config file not changeable"));
+
+ if(!err){
+ apval = APVAL(&ps_global->vars[V_LITERAL_SIG], ew);
+ if(!apval)
+ err = cpystr(_("Problem accessing configuration"));
+ else{
+ char *input;
+
+ input = (char *)fs_get((strlen(*apval ? *apval : "")+1) *
+ sizeof(char));
+ input[0] = '\0';
+ cstring_to_string(*apval, input);
+ err = signature_edit_lit(input, &result,
+ _("SIGNATURE EDITOR"),
+ h_composer_sigedit);
+ fs_give((void **)&input);
+ }
+ }
+
+ if(!err){
+ char *cstring_version;
+
+ cstring_version = string_to_cstring(result);
+
+ set_variable(V_LITERAL_SIG, cstring_version, 0, 0, ew);
+
+ if(cstring_version)
+ fs_give((void **)&cstring_version);
+ }
+
+ if(result)
+ fs_give((void **)&result);
+ }
+ else
+ err = signature_edit(ps_global->VAR_SIGNATURE_FILE,
+ _("SIGNATURE EDITOR"));
+
+ if(err){
+ q_status_message(SM_ORDER, 3, 4, err);
+ fs_give((void **)&err);
+ }
+
+ ps_global->mangled_screen = 1;
+ break;
+
+ /*----- ADD ADDRESSBOOK ----*/
+ case 'a':
+ addr_book_config(ps_global, edit_exceptions);
+ menu_index = ABOOK_MENU_ITEM;
+ ps_global->mangled_screen = 1;
+ break;
+
+#ifdef ENABLE_LDAP
+ /*--- ADD DIRECTORY SERVER --*/
+ case 'd':
+ directory_config(ps_global, edit_exceptions);
+ ps_global->mangled_screen = 1;
+ break;
+#endif
+
+#ifdef SMIME
+ /*--- S/MIME --*/
+ case 'm':
+ smime_config_screen(ps_global, edit_exceptions);
+ ps_global->mangled_screen = 1;
+ break;
+#endif
+
+ /*----- CONFIGURE OPTIONS -----*/
+ case 'c':
+ option_screen(ps_global, edit_exceptions);
+ ps_global->mangled_screen = 1;
+ break;
+
+ /*----- COLLECTION LIST -----*/
+ case 'l':
+ folder_config_screen(ps_global, edit_exceptions);
+ ps_global->mangled_screen = 1;
+ break;
+
+ /*----- RULES -----*/
+ case 'r':
+ rtype = rule_setup_type(ps_global, RS_RULES | RS_INCFILTNOW,
+ _("Type of rule setup : "));
+ switch(rtype){
+ case 'r':
+ case 's':
+ case 'i':
+ case 'f':
+ case 'o':
+ case 'c':
+ role_config_screen(ps_global, (rtype == 'r') ? ROLE_DO_ROLES :
+ (rtype == 's') ? ROLE_DO_SCORES :
+ (rtype == 'o') ? ROLE_DO_OTHER :
+ (rtype == 'f') ? ROLE_DO_FILTER :
+ (rtype == 'c') ? ROLE_DO_SRCH :
+ ROLE_DO_INCOLS,
+ edit_exceptions);
+ break;
+
+ case 'Z':
+ q_status_message(SM_ORDER | SM_DING, 3, 5,
+ _("Try turning on color with the Setup/Kolor command."));
+ break;
+
+ case 'n':
+ role_process_filters();
+ break;
+
+ default:
+ cmd_cancelled(NULL);
+ break;
+ }
+
+ ps_global->mangled_screen = 1;
+ break;
+
+ /*----- COLOR -----*/
+ case 'k':
+ color_config_screen(ps_global, edit_exceptions);
+ ps_global->mangled_screen = 1;
+ break;
+
+ case 'z':
+ convert_to_remote_config(ps_global, edit_exceptions);
+ ps_global->mangled_screen = 1;
+ break;
+
+ /*----- EXIT -----*/
+ case 'e':
+ break;
+
+ /*----- NEW PASSWORD -----*/
+ case 'n':
+#ifdef PASSWD_PROG
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER, 3, 5,
+ "Password change unavailable in restricted demo version of Alpine.");
+ }else {
+ change_passwd();
+ ClearScreen();
+ ps_global->mangled_screen = 1;
+ }
+#else
+ q_status_message(SM_ORDER, 0, 5,
+ _("Password changing not configured for this version of Alpine."));
+ display_message('x');
+#endif /* DOS */
+ break;
+
+#if !defined(DOS)
+ /*----- CHOOSE PRINTER ------*/
+ case 'p':
+ select_printer(ps_global, edit_exceptions);
+ ps_global->mangled_screen = 1;
+ break;
+#endif
+ }
+}
+
+
+int
+rule_setup_type(struct pine *ps, int flags, char *prompt)
+{
+ ESCKEY_S opts[9];
+ int ekey_num = 0, deefault = 0;
+
+ if(flags & RS_INCADDR){
+ deefault = 'a';
+ opts[ekey_num].ch = 'a';
+ opts[ekey_num].rval = 'a';
+ opts[ekey_num].name = "A";
+ opts[ekey_num++].label = "Addrbook";
+ }
+
+ if(flags & RS_RULES){
+
+ if(F_OFF(F_DISABLE_ROLES_SETUP,ps)){ /* roles are allowed */
+ if(deefault != 'a')
+ deefault = 'r';
+
+ opts[ekey_num].ch = 'r';
+ opts[ekey_num].rval = 'r';
+ opts[ekey_num].name = "R";
+ opts[ekey_num++].label = "Roles";
+ }
+ else if(deefault != 'a')
+ deefault = 's';
+
+ opts[ekey_num].ch = 's';
+ opts[ekey_num].rval = 's';
+ opts[ekey_num].name = "S";
+ opts[ekey_num++].label = "SetScores";
+
+#ifndef _WINDOWS
+ if(ps->color_style != COL_NONE && pico_hascolor()){
+#endif
+ if(deefault != 'a')
+ deefault = 'i';
+
+ opts[ekey_num].ch = 'i';
+ opts[ekey_num].rval = 'i';
+ opts[ekey_num].name = "I";
+ opts[ekey_num++].label = "Indexcolor";
+#ifndef _WINDOWS
+ }
+ else{
+ opts[ekey_num].ch = 'i';
+ opts[ekey_num].rval = 'Z'; /* notice this rval! */
+ opts[ekey_num].name = "I";
+ opts[ekey_num++].label = "Indexcolor";
+ }
+#endif
+
+ opts[ekey_num].ch = 'f';
+ opts[ekey_num].rval = 'f';
+ opts[ekey_num].name = "F";
+ opts[ekey_num++].label = "Filters";
+
+ opts[ekey_num].ch = 'o';
+ opts[ekey_num].rval = 'o';
+ opts[ekey_num].name = "O";
+ opts[ekey_num++].label = "Other";
+
+ opts[ekey_num].ch = 'c';
+ opts[ekey_num].rval = 'c';
+ opts[ekey_num].name = "C";
+ opts[ekey_num++].label = "searCh";
+
+ }
+
+ if(flags & RS_INCEXP){
+ opts[ekey_num].ch = 'e';
+ opts[ekey_num].rval = 'e';
+ opts[ekey_num].name = "E";
+ opts[ekey_num++].label = "Export";
+ }
+
+ if(flags & RS_INCFILTNOW){
+ opts[ekey_num].ch = 'n';
+ opts[ekey_num].rval = 'n';
+ opts[ekey_num].name = "N";
+ opts[ekey_num++].label = "filterNow";
+ }
+
+ opts[ekey_num].ch = -1;
+
+ return(radio_buttons(prompt, -FOOTER_ROWS(ps), opts,
+ deefault, 'x', NO_HELP, RB_NORM));
+}
+
+
+
+/*
+ * Process the command list, changing function key notation into
+ * lexical equivalents.
+ */
+void
+process_init_cmds(struct pine *ps, char **list)
+{
+ char **p;
+ int i = 0;
+ int j;
+ int lpm1;
+#define MAX_INIT_CMDS 500
+ /* this is just a temporary stack array, the real one is allocated below */
+ int i_cmds[MAX_INIT_CMDS];
+ int fkeys = 0;
+ int not_fkeys = 0;
+
+ if(list){
+ for(p = list; *p; p++){
+ if(i >= MAX_INIT_CMDS){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Initial keystroke list too long at \"%s\"", *p);
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ break;
+ }
+
+
+ /* regular character commands */
+ if(strlen(*p) == 1){
+ i_cmds[i++] = **p;
+ not_fkeys++;
+ }
+
+ /* special commands */
+ else if(strucmp(*p, "SPACE") == 0)
+ i_cmds[i++] = ' ';
+ else if(strucmp(*p, "CR") == 0)
+ i_cmds[i++] = '\n';
+ else if(strucmp(*p, "TAB") == 0)
+ i_cmds[i++] = '\t';
+ else if(strucmp(*p, "UP") == 0)
+ i_cmds[i++] = KEY_UP;
+ else if(strucmp(*p, "DOWN") == 0)
+ i_cmds[i++] = KEY_DOWN;
+ else if(strucmp(*p, "LEFT") == 0)
+ i_cmds[i++] = KEY_LEFT;
+ else if(strucmp(*p, "RIGHT") == 0)
+ i_cmds[i++] = KEY_RIGHT;
+
+ /* control chars */
+ else if(strlen(*p) == 2 && **p == '^')
+ i_cmds[i++] = ctrl(*((*p)+1));
+
+ /* function keys */
+ else if(**p == 'F' || **p == 'f'){
+ int v;
+
+ fkeys++;
+ v = atoi((*p)+1);
+ if(v >= 1 && v <= 12)
+ i_cmds[i++] = PF1 + v - 1;
+ else
+ i_cmds[i++] = KEY_JUNK;
+ }
+
+ /* literal string */
+ else if(**p == '"' && (*p)[lpm1 = strlen(*p) - 1] == '"'){
+ if(lpm1 + i - 1 > MAX_INIT_CMDS){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Initial keystroke list too long, truncated at %s\n", *p);
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ break; /* Bail out of this loop! */
+ } else
+ for(j = 1; j < lpm1; j++)
+ i_cmds[i++] = (*p)[j];
+ }
+ else {
+ snprintf(tmp_20k_buf,SIZEOF_20KBUF,
+ "Bad initial keystroke \"%.500s\" (missing comma?)", *p);
+ init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ break;
+ }
+ }
+ }
+
+ /*
+ * We don't handle the case where function keys are used to specify the
+ * commands but some non-function key input is also required. For example,
+ * you might want to jump to a specific message number and view it
+ * on start up. To do that, you need to use character commands instead
+ * of function key commands in the initial-keystroke-list.
+ */
+ if(fkeys && not_fkeys){
+ init_error(ps, SM_ORDER | SM_DING, 3, 5,
+"Mixed characters and function keys in \"initial-keystroke-list\", skipping.");
+ i = 0;
+ }
+
+ if(fkeys && !not_fkeys)
+ F_TURN_ON(F_USE_FK,ps);
+ if(!fkeys && not_fkeys)
+ F_TURN_OFF(F_USE_FK,ps);
+
+ if(i > 0){
+ ps->initial_cmds = (int *)fs_get((i+1) * sizeof(int));
+ ps->free_initial_cmds = ps->initial_cmds;
+ for(j = 0; j < i; j++)
+ ps->initial_cmds[j] = i_cmds[j];
+
+ ps->initial_cmds[i] = 0;
+ ps->in_init_seq = ps->save_in_init_seq = 1;
+ }
+}
+
+
+UCS *
+user_wordseps(char **list)
+ {
+ char **p;
+ int i = 0;
+ int j;
+#define MAX_SEPARATORS 500
+ /*
+ * This is just a temporary stack array, the real one is allocated below.
+ * This is supposed to be way large enough.
+ */
+ UCS seps[MAX_SEPARATORS+1];
+ UCS *u;
+ UCS *return_array = NULL;
+ size_t l;
+
+ seps[0] = '\0';
+
+ if(list){
+ for(p = list; *p; p++){
+ if(i >= MAX_SEPARATORS){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Warning: composer-word-separators list is too long");
+ break;
+ }
+
+ u = utf8_to_ucs4_cpystr(*p);
+
+ if(u){
+ if(ucs4_strlen(u) == 1)
+ seps[i++] = *u;
+ else if(*u == '"' && u[l = ucs4_strlen(u) - 1] == '"'){
+ if(l + i - 1 > MAX_SEPARATORS){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Warning: composer-word-separators list is too long");
+ break; /* Bail out of this loop! */
+ }
+ else{
+ for(j = 1; j < l; j++)
+ seps[i++] = u[j];
+ }
+ }
+ else{
+ l = ucs4_strlen(u);
+ if(l + i > MAX_SEPARATORS){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Warning: composer-word-separators list is too long");
+ break; /* Bail out of this loop! */
+ }
+ else{
+ for(j = 0; j < l; j++)
+ seps[i++] = u[j];
+ }
+ }
+
+ fs_give((void **) &u);
+ }
+ }
+ }
+
+ seps[i] = '\0';
+
+ if(i > 0)
+ return_array = ucs4_cpystr(seps);
+
+ return(return_array);
+}
+
+
+/*
+ * Make sure any errors during initialization get queued for display
+ */
+void
+queue_init_errors(struct pine *ps)
+{
+ int i;
+
+ if(ps->init_errs){
+ for(i = 0; (ps->init_errs)[i].message; i++){
+ q_status_message((ps->init_errs)[i].flags,
+ (ps->init_errs)[i].min_time,
+ (ps->init_errs)[i].max_time,
+ (ps->init_errs)[i].message);
+ fs_give((void **)&(ps->init_errs)[i].message);
+ }
+
+ fs_give((void **)&ps->init_errs);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Quit pine if the user wants to
+
+ Args: The usual pine structure
+
+ Result: User is asked if she wants to quit, if yes then execute quit.
+
+ Q U I T S C R E E N
+
+Not really a full screen. Just count up deletions and ask if we really
+want to quit.
+ ----*/
+void
+quit_screen(struct pine *pine_state)
+{
+ int quit = 0;
+
+ dprint((1, "\n\n ---- QUIT SCREEN ----\n"));
+
+ if(F_ON(F_CHECK_MAIL_ONQUIT,ps_global)
+ && new_mail(1, VeryBadTime, NM_STATUS_MSG | NM_DEFER_SORT) > 0
+ && (quit = want_to(_("Quit even though new mail just arrived"), 'y', 0,
+ NO_HELP, WT_NORM)) != 'y'){
+ refresh_sort(pine_state->mail_stream, pine_state->msgmap, SRT_VRB);
+ pine_state->next_screen = pine_state->prev_screen;
+ return;
+ }
+
+ if(quit != 'y'
+ && F_OFF(F_QUIT_WO_CONFIRM,pine_state)
+ && want_to(_("Really quit Alpine"), 'y', 0, NO_HELP, WT_NORM) != 'y'){
+ pine_state->next_screen = pine_state->prev_screen;
+ return;
+ }
+
+ goodnight_gracey(pine_state, 0);
+}
+
+
+/*----------------------------------------------------------------------
+ The nuts and bolts of actually cleaning up and exitting pine
+
+ Args: ps -- the usual pine structure,
+ exit_val -- what to tell our parent
+
+ Result: This never returns
+
+ ----*/
+void
+goodnight_gracey(struct pine *pine_state, int exit_val)
+{
+ int i, cnt_user_streams = 0;
+ char *final_msg = NULL;
+ char msg[MAX_SCREEN_COLS+1];
+ char *pf = _("Alpine finished");
+ MAILSTREAM *m;
+ extern KBESC_T *kbesc;
+
+ dprint((2, "goodnight_gracey:\n"));
+
+ /* We want to do this here before we close up the streams */
+ trim_remote_adrbks();
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR))
+ cnt_user_streams++;
+ }
+
+ /* clean up open streams */
+
+ if(pine_state->mail_stream
+ && sp_flagged(pine_state->mail_stream, SP_LOCKED)
+ && sp_flagged(pine_state->mail_stream, SP_USERFLDR)){
+ dprint((5, "goodnight_gracey: close current stream\n"));
+ expunge_and_close(pine_state->mail_stream,
+ (cnt_user_streams <= 1) ? &final_msg : NULL, EC_NONE);
+ cnt_user_streams--;
+ }
+
+ pine_state->mail_stream = NULL;
+ pine_state->redrawer = (void (*)(void))NULL;
+
+ dprint((5,
+ "goodnight_gracey: close other stream pool streams\n"));
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ /*
+ * fix global for functions that depend(ed) on it sort_folder.
+ * Hopefully those will get phased out.
+ */
+ ps_global->mail_stream = m;
+ if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)
+ && !sp_flagged(m, SP_INBOX)){
+ sp_set_expunge_count(m, 0L);
+ expunge_and_close(m, (cnt_user_streams <= 1) ? &final_msg : NULL,
+ EC_NONE);
+ cnt_user_streams--;
+ }
+ }
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ /*
+ * fix global for functions that depend(ed) on it (sort_folder).
+ * Hopefully those will get phased out.
+ */
+ ps_global->mail_stream = m;
+ if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)
+ && sp_flagged(m, SP_INBOX)){
+ dprint((5,
+ "goodnight_gracey: close inbox stream stream\n"));
+ sp_set_expunge_count(m, 0L);
+ expunge_and_close(m, (cnt_user_streams <= 1) ? &final_msg : NULL,
+ EC_NONE);
+ cnt_user_streams--;
+ }
+ }
+
+#ifdef _WINDOWS
+ if(ps_global->ttyo)
+ (void)get_windsize(ps_global->ttyo);
+#endif
+
+ dprint((7, "goodnight_gracey: close config files\n"));
+
+#ifdef SMIME
+ smime_deinit();
+#endif
+
+ free_pinerc_strings(&pine_state);
+
+ strncpy(msg, pf, sizeof(msg));
+ msg[sizeof(msg)-1] = '\0';
+ if(final_msg){
+ strncat(msg, " -- ", sizeof(msg)-strlen(msg)-1);
+ msg[sizeof(msg)-1] = '\0';
+ strncat(msg, final_msg, sizeof(msg)-strlen(msg)-1);
+ msg[sizeof(msg)-1] = '\0';
+ fs_give((void **)&final_msg);
+ }
+
+ dprint((7, "goodnight_gracey: sp_end\n"));
+ ps_global->noshow_error = 1;
+ sp_end();
+
+ /* after sp_end, which might call a filter */
+ completely_done_with_adrbks();
+
+ dprint((7, "goodnight_gracey: end_screen\n"));
+ end_screen(msg, exit_val);
+ dprint((7, "goodnight_gracey: end_titlebar\n"));
+ end_titlebar();
+ dprint((7, "goodnight_gracey: end_keymenu\n"));
+ end_keymenu();
+
+ dprint((7, "goodnight_gracey: end_keyboard\n"));
+ end_keyboard(F_ON(F_USE_FK,pine_state));
+ dprint((7, "goodnight_gracey: end_ttydriver\n"));
+ end_tty_driver(pine_state);
+#if !defined(DOS) && !defined(OS2)
+ kbdestroy(kbesc);
+#if !defined(LEAVEOUTFIFO)
+ close_newmailfifo();
+#endif
+#endif
+ end_signals(0);
+ if(filter_data_file(0))
+ our_unlink(filter_data_file(0));
+
+ imap_flush_passwd_cache(TRUE);
+ free_newsgrp_cache();
+ mailcap_free();
+ close_every_pattern();
+ free_extra_hdrs();
+ free_contexts(&ps_global->context_list);
+ free_charsetchecker();
+ dprint((7, "goodnight_gracey: free more memory\n"));
+#ifdef ENABLE_LDAP
+ free_saved_query_parameters();
+#endif
+
+ free_pine_struct(&pine_state);
+
+ free_histlist();
+
+#ifdef DEBUG
+ if(debugfile){
+ if(debug >= 2)
+ fputs("goodnight_gracey finished\n", debugfile);
+
+ fclose(debugfile);
+ }
+#endif
+
+ exit(exit_val);
+}
+
+
+/*----------------------------------------------------------------------
+ Call back for c-client to feed us back the progress of network reads
+
+ Input:
+
+ Result:
+ ----*/
+void
+pine_read_progress(GETS_DATA *md, long unsigned int count)
+{
+ gets_bytes += count; /* update counter */
+}
+
+
+/*----------------------------------------------------------------------
+ Function to fish the current byte count from a c-client fetch.
+
+ Input: reset -- flag telling us to reset the count
+
+ Result: Returns the number of bytes read by the c-client so far
+ ----*/
+unsigned long
+pine_gets_bytes(int reset)
+{
+ if(reset)
+ gets_bytes = 0L;
+
+ return(gets_bytes);
+}
+
+
+/*----------------------------------------------------------------------
+ Panic pine - call on detected programmatic errors to exit pine
+
+ Args: message -- message to record in debug file and to be printed for user
+
+ Result: The various tty modes are restored
+ If debugging is active a core dump will be generated
+ Exits Alpine
+
+ This is also called from imap routines and fs_get and fs_resize.
+ ----*/
+void
+panic(char *message)
+{
+ char buf[256];
+
+ /* global variable in .../pico/edef.h */
+ in_panic = 1;
+
+ if(ps_global->ttyo){
+ end_screen(NULL, -1);
+ end_keyboard(ps_global != NULL ? F_ON(F_USE_FK,ps_global) : 0);
+ end_tty_driver(ps_global);
+ end_signals(1);
+ }
+ if(filter_data_file(0))
+ our_unlink(filter_data_file(0));
+
+ dprint((1, "\n===========================================\n\n"));
+ dprint((1, " Alpine Panic: %s\n\n", message ? message : "?"));
+ dprint((1, "===========================================\n\n"));
+
+ /* intercept c-client "free storage" errors */
+ if(strstr(message, "free storage"))
+ snprintf(buf, sizeof(buf), _("No more available memory.\nAlpine Exiting"));
+ else
+ snprintf(buf, sizeof(buf), _("Problem detected: \"%s\".\nAlpine Exiting."), message);
+
+ buf[sizeof(buf)-1] = '\0';
+
+#ifdef _WINDOWS
+ /* Put up a message box. */
+ mswin_messagebox (buf, 1);
+#else
+ fprintf(stderr, "\n\n%s\n", buf);
+#endif
+
+#ifdef DEBUG
+ if(debugfile){
+ save_debug_on_crash(debugfile, recent_keystroke);
+ }
+
+ coredump(); /*--- If we're debugging get a core dump --*/
+#endif
+
+ exit(-1);
+ fatal("ffo"); /* BUG -- hack to get fatal out of library in right order*/
+}
+
+
+/*
+ * panicking - function to test whether or not we're exiting under stress.
+ *
+ */
+int
+panicking(void)
+{
+ return(in_panic);
+}
+
+
+/*----------------------------------------------------------------------
+ exceptional_exit - called to exit under unusual conditions (with no core)
+
+ Args: message -- message to record in debug file and to be printed for user
+ ev -- exit value
+
+ ----*/
+void
+exceptional_exit(char *message, int ev)
+{
+ fprintf(stderr, "%s\n", message);
+ exit(ev);
+}
+
+
+/*
+ * PicoText Storage Object Support Routines
+ */
+
+STORE_S *
+pine_pico_get(void)
+{
+ return((STORE_S *)pico_get());
+}
+
+int
+pine_pico_give(STORE_S **sop)
+{
+ pico_give((void *)sop);
+ return(1);
+}
+
+int
+pine_pico_writec(int c, STORE_S *so)
+{
+ unsigned char ch = (unsigned char) c;
+
+ return(pico_writec(so->txt, ch, PICOREADC_NONE));
+}
+
+int
+pine_pico_writec_noucs(int c, STORE_S *so)
+{
+ unsigned char ch = (unsigned char) c;
+
+ return(pico_writec(so->txt, ch, PICOREADC_NOUCS));
+}
+
+int
+pine_pico_readc(unsigned char *c, STORE_S *so)
+{
+ return(pico_readc(so->txt, c, PICOREADC_NONE));
+}
+
+int
+pine_pico_readc_noucs(unsigned char *c, STORE_S *so)
+{
+ return(pico_readc(so->txt, c, PICOREADC_NOUCS));
+}
+
+int
+pine_pico_puts(STORE_S *so, char *s)
+{
+ return(pico_puts(so->txt, s, PICOREADC_NONE));
+}
+
+int
+pine_pico_puts_noucs(STORE_S *so, char *s)
+{
+ return(pico_puts(so->txt, s, PICOREADC_NOUCS));
+}
+
+int
+pine_pico_seek(STORE_S *so, long pos, int orig)
+{
+ return(pico_seek((void *)so, pos, orig));
+}
+
+
+int
+remote_pinerc_failure(void)
+{
+#ifdef _WINDOWS
+ if(ps_global->install_flag) /* just exit silently */
+ exit(0);
+#endif /* _WINDOWS */
+
+ if(ps_global->exit_if_no_pinerc){
+ exceptional_exit("Exiting because -bail option is set and config file not readable.", -1);
+ }
+
+ if(want_to("Trouble reading remote configuration! Continue anyway ",
+ 'n', 'n', NO_HELP, WT_FLUSH_IN) != 'y'){
+ return(0);
+ }
+
+ return(1);
+}
+
+
+void
+dump_supported_options(void)
+{
+ char **config;
+
+ config = get_supported_options();
+ if(config){
+ display_args_err(NULL, config, 0);
+ free_list_array(&config);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Check pruned-folders for validity, making sure they are in the
+ same context as sent-mail.
+
+ ----*/
+int
+prune_folders_ok(void)
+{
+ char **p;
+
+ for(p = ps_global->VAR_PRUNED_FOLDERS; p && *p && **p; p++)
+ if(!context_isambig(*p))
+ return(0);
+
+ return(1);
+}
+
+
+#ifdef WIN32
+char *
+pine_user_callback()
+{
+ if(ps_global->VAR_USER_ID && ps_global->VAR_USER_ID[0]){
+ return(ps_global->VAR_USER_ID);
+ }
+ else{
+ /* SHOULD PROMPT HERE! */
+ return(NULL);
+ }
+}
+#endif
+
+#ifdef _WINDOWS
+/*
+ * windows callback to get/set function keys mode state
+ */
+int
+fkey_mode_callback(set, args)
+ int set;
+ long args;
+{
+ return(F_ON(F_USE_FK, ps_global) != 0);
+}
+
+
+void
+imap_telemetry_on()
+{
+ if(ps_global->mail_stream)
+ mail_debug(ps_global->mail_stream);
+}
+
+
+void
+imap_telemetry_off()
+{
+ if(ps_global->mail_stream)
+ mail_nodebug(ps_global->mail_stream);
+}
+
+
+char *
+pcpine_help_main(title)
+ char *title;
+{
+ if(title)
+ strncpy(title, _("PC-Alpine MAIN MENU Help"), 256);
+
+ return(pcpine_help(main_menu_tx));
+}
+
+
+int
+pcpine_main_cursor(col, row)
+ int col;
+ long row;
+{
+ unsigned ndmi;
+
+ if (row >= (HEADER_ROWS(ps_global) + MNSKIP(ps_global)))
+ ndmi = (row+1 - HEADER_ROWS(ps_global) - (MNSKIP(ps_global)+1))/(MNSKIP(ps_global)+1);
+
+ if (row >= (HEADER_ROWS(ps_global) + MNSKIP(ps_global))
+ && !(MNSKIP(ps_global) && (row+1) & 0x01)
+ && ndmi <= MAX_MENU_ITEM
+ && FOOTER_ROWS(ps_global) + (ndmi+1)*(MNSKIP(ps_global)+1)
+ + MNSKIP(ps_global) + FOOTER_ROWS(ps_global) <= ps_global->ttyo->screen_rows)
+ return(MSWIN_CURSOR_HAND);
+ else
+ return(MSWIN_CURSOR_ARROW);
+}
+#endif /* _WINDOWS */
diff --git a/alpine/alpine.h b/alpine/alpine.h
new file mode 100644
index 00000000..cef14db9
--- /dev/null
+++ b/alpine/alpine.h
@@ -0,0 +1,41 @@
+/*
+ * $Id: alpine.h 767 2007-10-24 00:03:59Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef ALPINE_ALPINE_INCLUDED
+#define ALPINE_ALPINE_INCLUDED
+
+
+#include "../pith/state.h"
+
+
+/* exported protoypes */
+void main_menu_screen(struct pine *);
+unsigned long pine_gets_bytes(int);
+void quit_screen(struct pine *);
+int panicking(void);
+int rule_setup_type(struct pine *ps, int flags, char *prompt);
+UCS *user_wordseps(char **);
+STORE_S *pine_pico_get(void);
+int pine_pico_give(STORE_S **);
+int pine_pico_writec(int, STORE_S *);
+int pine_pico_writec_noucs(int, STORE_S *);
+int pine_pico_readc(unsigned char *, STORE_S *);
+int pine_pico_readc_noucs(unsigned char *, STORE_S *);
+int pine_pico_puts(STORE_S *, char *);
+int pine_pico_puts_noucs(STORE_S *, char *);
+int pine_pico_seek(STORE_S *, long, int);
+
+
+#endif /* ALPINE_ALPINE_INCLUDED */
diff --git a/alpine/arg.c b/alpine/arg.c
new file mode 100644
index 00000000..e23853c4
--- /dev/null
+++ b/alpine/arg.c
@@ -0,0 +1,1325 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: arg.c 900 2008-01-05 01:13:26Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ Command line argument parsing functions
+
+ ====*/
+
+#include "headers.h"
+
+#include "../pith/state.h"
+#include "../pith/init.h"
+#include "../pith/conf.h"
+#include "../pith/list.h"
+#include "../pith/util.h"
+#include "../pith/help.h"
+
+#include "imap.h"
+
+#include "arg.h"
+
+
+int process_debug_str(char *);
+void args_add_attach(PATMT **, char *, int);
+int pinerc_cmdline_opt(char *);
+
+
+/*
+ * Name started as to invoke function key mode
+ */
+#define ALPINE_FKEY_NAME "alpinef"
+
+/*
+ * Various error and informational strings..
+ */
+/* TRANSLATORS: This is a list of errors printed when something goes wrong
+ early on with the argument list. Be careful not to change literal
+ option names mentioned in the strings. */
+static char args_err_missing_pinerc[] = N_("missing argument for option \"-pinerc\" (use - for standard out)");
+#if defined(DOS) || defined(OS2)
+static char args_err_missing_aux[] = N_("missing argument for option \"-aux\"");
+#endif
+#ifdef PASSFILE
+static char args_err_missing_passfile[] = N_("missing argument for option \"-passfile\"");
+static char args_err_non_abs_passfile[] = N_("argument to \"-passfile\" should be fully-qualified");
+#endif
+static char args_err_missing_sort[] = N_("missing argument for option \"-sort\"");
+static char args_err_missing_flag_arg[] = N_("missing argument for flag \"%c\"");
+static char args_err_missing_flag_num[] = N_("Non numeric argument for flag \"%c\"");
+static char args_err_missing_debug_num[] = N_("Non numeric argument for \"%s\"");
+static char args_err_missing_url[] = N_("missing URL for \"-url\"");
+static char args_err_missing_attachment[] = N_("missing attachment for \"%s\"");
+static char args_err_conflict[] = N_("conflicting action: \"%s\"");
+static char args_err_unknown[] = N_("unknown flag \"%c\"");
+static char args_err_I_error[] = N_("-I argument \"%s\": %s");
+static char args_err_d_error[] = N_("-d argument \"%s\": %s");
+static char args_err_internal[] = "%s";
+static char args_err_missing_copyprc[] = N_("missing argument for option \"-copy_pinerc\"\nUsage: pine -copy_pinerc <local_pinerc> <remote_pinerc>");
+static char args_err_missing_copyabook[] = N_("missing argument for option \"-copy_abook\"\nUsage: pine -copy_abook <local_abook> <remote_abook>");
+
+
+static char *args_pine_args[] = {
+N_("Possible Starting Arguments for Alpine program:"),
+"",
+N_(" Argument\tMeaning"),
+N_(" <addrs>...\tGo directly into composer sending to given address"),
+N_("\t\tList multiple addresses with a single space between them."),
+N_("\t\tStandard input redirection is allowed with addresses."),
+N_("\t\tNote: Places addresses in the \"To\" field only."),
+N_(" -attach <file>\tGo directly into composer with given file"),
+N_(" -attachlist <file-list>"),
+N_(" -attach_and_delete <file>"),
+N_("\t\tGo to composer, attach file, delete when finished"),
+N_("\t\tNote: Attach options can't be used if -f, -F"),
+N_("\t\tadded to Attachment list. Attachlist must be the last"),
+N_("\t\toption on the command line"),
+N_(" -bail\t\tExit if pinerc file doesn't already exist"),
+#ifdef DEBUG
+N_(" -d n\t\tDebug - set debug level to 'n', or use the following:"),
+N_(" -d keywords...\tflush,timestamp,imap=0..4,tcp,numfiles=0..31,verbose=0..9"),
+#endif
+N_(" -f <folder>\tFolder - give folder name to open"),
+N_(" -c <number>\tContext - which context to apply to -f arg"),
+N_(" -F <file>\tFile - give file name to open and page through and"),
+N_("\t\tforward as email."),
+N_(" -h \t\tHelp - give this list of options"),
+N_(" -k \t\tKeys - Force use of function keys"),
+N_(" -z \t\tSuspend - allow use of ^Z suspension"),
+N_(" -r \t\tRestricted - can only send mail to oneself"),
+N_(" -sort <sort>\tSort - Specify sort order of folder:"),
+N_("\t\t\tarrival, subject, threaded, orderedsubject, date,"),
+N_("\t\t\tfrom, size, score, to, cc, /reverse"),
+N_(" -i\t\tIndex - Go directly to index, bypassing main menu"),
+N_(" -I <keystroke_list> Initial keystrokes to be executed"),
+N_(" -n <number>\tEntry in index to begin on"),
+N_(" -o \t\tReadOnly - Open first folder read-only"),
+N_(" -conf\t\tConfiguration - Print out fresh global configuration. The"),
+N_("\t\tvalues of your global configuration affect all Alpine users"),
+N_("\t\ton your system unless they have overridden the values in their"),
+N_("\t\tpinerc files."),
+N_(" -pinerc <file>\tConfiguration - Put fresh pinerc configuration in <file>"),
+N_(" -p <pinerc>\tUse alternate .pinerc file"),
+#if !defined(DOS) && !defined(OS2)
+N_(" -P <pine.conf>\tUse alternate pine.conf file"),
+#else
+N_(" -aux <aux_files_dir>\tUse this with remote pinerc"),
+N_(" -P <pine.conf>\tUse pine.conf file for default settings"),
+N_(" -nosplash \tDisable the PC-Alpine splash screen"),
+#endif
+
+#if defined(APPLEKEYCHAIN) || (WINCRED > 0)
+N_(" -erase_stored_passwords\tEliminate any stored passwords"),
+#endif
+
+#ifdef PASSFILE
+N_(" -passfile <fully_qualified_filename>\tSet the password file to something other"),
+N_("\t\tthan the default"),
+#endif /* PASSFILE */
+
+#ifdef LOCAL_PASSWD_CACHE
+N_(" -nowrite_password_cache\tRead from a password cache if there is one, but"),
+N_("\t\t\t\tnever offer to write a password to the cache"),
+#endif /* LOCAL_PASSWD_CACHE */
+
+N_(" -x <config>\tUse configuration exceptions in <config>."),
+N_("\t\tExceptions are used to override your default pinerc"),
+N_("\t\tsettings for a particular platform, can be a local file or"),
+N_("\t\ta remote folder."),
+N_(" -v \t\tVersion - show version information"),
+N_(" -version\tVersion - show version information"),
+N_(" -supported\tList supported options"),
+N_(" -url <url>\tOpen the given URL"),
+N_("\t\tNote: Can't be used if -f, -F"),
+N_("\t\tStandard input redirection is not allowed with URLs."),
+N_("\t\tFor mailto URLs, 'body='text should be used in place of"),
+N_("\t\tinput redirection."),
+N_(" -copy_pinerc <local_pinerc> <remote_pinerc> copy local pinerc to remote"),
+N_(" -copy_abook <local_abook> <remote_abook> copy local addressbook to remote"),
+N_(" -convert_sigs -p <pinerc> convert signatures to literal signatures"),
+#if defined(_WINDOWS)
+N_(" -install \tPrompt for some basic setup information"),
+N_(" -uninstall \tRemove traces of Alpine from Windows system settings"),
+N_(" -registry <cmd>\tWhere cmd is set,noset,clear,clearsilent,dump"),
+#endif
+" -<option>=<value> Assign <value> to the pinerc option <option>",
+"\t\t e.g. -signature-file=sig1",
+"\t\t e.g. -color-style=no-color",
+"\t\t e.g. -feature-list=enable-sigdashes",
+"\t\t Note: feature-list is additive.",
+"\t\t You may leave off the \"feature-list=\" part of that,",
+"\t\t e.g. -enable-sigdashes",
+NULL
+};
+
+
+
+/*
+ * Parse the command line args.
+ *
+ * Args: pine_state -- The pine_state structure to put results in
+ * argc, argv -- The obvious
+ * addrs -- Pointer to address list that we set for caller
+ *
+ * Result: command arguments parsed
+ * possible printing of help for command line
+ * various flags in pine_state set
+ * returns the string name of the first folder to open
+ * addrs is set
+ */
+void
+pine_args(struct pine *pine_state, int argc, char **argv, ARGDATA_S *args)
+{
+ register int ac;
+ register char **av;
+ int c;
+ char *str;
+ char *cmd_list = NULL;
+ char *debug_str = NULL;
+ char *sort = NULL;
+ char *pinerc_file = NULL;
+ char *lc = NULL;
+ int do_help = 0;
+ int do_conf = 0;
+ int usage = 0;
+ int do_use_fk = 0;
+ int do_can_suspend = 0;
+ int do_version = 0;
+ struct variable *vars = pine_state->vars;
+
+ ac = argc;
+ av = argv;
+ memset(args, 0, sizeof(ARGDATA_S));
+ args->action = aaFolder;
+
+ pine_state->pine_name = (lc = last_cmpnt(argv[0])) ? lc : (lc = argv[0]);
+#ifdef DOS
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%.*s", pine_state->pine_name - argv[0], argv[0]);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ pine_state->pine_dir = cpystr(tmp_20k_buf);
+#endif
+
+ /* while more arguments with leading - */
+Loop: while(--ac > 0)
+ if(**++av == '-'){
+ /* while more chars in this argument */
+ while(*++*av){
+ /* check for pinerc options */
+ if(pinerc_cmdline_opt(*av)){
+ goto Loop; /* done with this arg, go to next */
+ }
+ /* then other multi-char options */
+ else if(strcmp(*av, "conf") == 0){
+ do_conf = 1;
+ goto Loop; /* done with this arg, go to next */
+ }
+ else if(strcmp(*av, "pinerc") == 0){
+ if(--ac)
+ pinerc_file = *++av;
+ else{
+ display_args_err(_(args_err_missing_pinerc), NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+#if defined(DOS) || defined(OS2)
+ else if(strcmp(*av, "aux") == 0){
+ if(--ac){
+ if((str = *++av) != NULL)
+ pine_state->aux_files_dir = cpystr(str);
+ }
+ else{
+ display_args_err(_(args_err_missing_aux), NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+ else if(strcmp(*av, "nosplash") == 0)
+ goto Loop; /* already taken care of in WinMain */
+#endif
+
+#if defined(APPLEKEYCHAIN) || (WINCRED > 0)
+ else if(strcmp(*av, "erase_stored_passwords") == 0){
+#if (WINCRED > 0)
+ erase_windows_credentials();
+#else
+ macos_erase_keychain();
+#endif
+ goto Loop;
+ }
+#endif /* defined(APPLEKEYCHAIN) || (WINCRED > 0) */
+
+#ifdef PASSFILE
+ else if(strcmp(*av, "passfile") == 0){
+ if(--ac){
+ if((str = *++av) != NULL){
+ if(!is_absolute_path(str)){
+ display_args_err(_(args_err_non_abs_passfile),
+ NULL, 1);
+ ++usage;
+ }
+ else{
+ if(pine_state->passfile)
+ fs_give((void **)&pine_state->passfile);
+
+ pine_state->passfile = cpystr(str);
+ }
+ }
+ }
+ else{
+ display_args_err(_(args_err_missing_passfile), NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+#endif /* PASSFILE */
+
+#ifdef LOCAL_PASSWD_CACHE
+ else if(strcmp(*av, "nowrite_password_cache") == 0){
+ ps_global->nowrite_password_cache = 1;
+ goto Loop;
+ }
+#endif /* LOCAL_PASSWD_CACHE */
+
+ else if(strcmp(*av, "convert_sigs") == 0){
+ ps_global->convert_sigs = 1;
+ goto Loop;
+ }
+ else if(strcmp(*av, "supported") == 0){
+ ps_global->dump_supported_options = 1;
+ goto Loop;
+ }
+ else if(strcmp(*av, "copy_pinerc") == 0){
+ if(args->action == aaFolder && !args->data.folder){
+ args->action = aaPrcCopy;
+ if(ac > 2){
+ ac -= 2;
+ args->data.copy.local = *++av;
+ args->data.copy.remote = *++av;
+ }
+ else{
+ display_args_err(_(args_err_missing_copyprc), NULL, 1);
+ ++usage;
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), "-copy_pinerc");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+ else if(strcmp(*av, "copy_abook") == 0){
+ if(args->action == aaFolder && !args->data.folder){
+ args->action = aaAbookCopy;
+ if(ac > 2){
+ ac -= 2;
+ args->data.copy.local = *++av;
+ args->data.copy.remote = *++av;
+ }
+ else{
+ display_args_err(_(args_err_missing_copyabook), NULL, 1);
+ ++usage;
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), "-copy_abook");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+ else if(strcmp(*av, "sort") == 0){
+ if(--ac){
+ sort = *++av;
+ COM_SORT_KEY = cpystr(sort);
+ }
+ else{
+ display_args_err(_(args_err_missing_sort), NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+ else if(strcmp(*av, "url") == 0){
+ if(args->action == aaFolder && !args->data.folder){
+ args->action = aaURL;
+ if(--ac){
+ args->url = cpystr(*++av);
+ }
+ else{
+ display_args_err(_(args_err_missing_url), NULL, 1);
+ ++usage;
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), "-url");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+ else if(strcmp(*av, "attach") == 0){
+ if((args->action == aaFolder && !args->data.folder)
+ || args->action == aaMail
+ || args->action == aaURL){
+ if(args->action != aaURL)
+ args->action = aaMail;
+ if(--ac){
+ args_add_attach(&args->data.mail.attachlist,
+ *++av, FALSE);
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_attachment), "-attach");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), "-attach");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+ else if(strcmp(*av, "attachlist") == 0){
+ if((args->action == aaFolder && !args->data.folder)
+ || args->action == aaMail
+ || args->action == aaURL){
+ if(args->action != aaURL)
+ args->action = aaMail;
+ if(ac - 1){
+ do{
+ if(can_access(*(av+1), READ_ACCESS) == 0){
+ ac--;
+ args_add_attach(&args->data.mail.attachlist,
+ *++av, FALSE);
+ }
+ else
+ break;
+ }
+ while(ac);
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_attachment), "-attachList");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), "-attachList");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+ else if(strcmp(*av, "attach_and_delete") == 0){
+ if((args->action == aaFolder && !args->data.folder)
+ || args->action == aaMail
+ || args->action == aaURL){
+ if(args->action != aaURL)
+ args->action = aaMail;
+ if(--ac){
+ args_add_attach(&args->data.mail.attachlist,
+ *++av, TRUE);
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_attachment), "-attach_and_delete");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), "-attach_and_delete");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+ else if(strcmp(*av, "bail") == 0){
+ pine_state->exit_if_no_pinerc = 1;
+ goto Loop;
+ }
+ else if(strcmp(*av, "version") == 0){
+ do_version = 1;
+ goto Loop;
+ }
+#ifdef _WINDOWS
+ else if(strcmp(*av, "install") == 0){
+ pine_state->install_flag = 1;
+ pine_state->update_registry = UREG_ALWAYS_SET;
+ goto Loop;
+ }
+ else if(strcmp(*av, "uninstall") == 0){
+ /*
+ * Blast password cache, clear registry settings
+ */
+#if (WINCRED > 0)
+ erase_windows_credentials();
+#endif
+ mswin_reg(MSWR_OP_BLAST, MSWR_NULL, NULL, 0);
+ exit(0);
+ }
+ else if(strcmp(*av, "registry") == 0){
+ if(--ac){
+ if(!strucmp(*++av, "set")){
+ pine_state->update_registry = UREG_ALWAYS_SET;
+ }
+ else if(!strucmp(*av, "noset")){
+ pine_state->update_registry = UREG_NEVER_SET;
+ }
+ else if(!strucmp(*av, "clear")){
+ if(!mswin_reg(MSWR_OP_BLAST, MSWR_NULL, NULL, 0))
+ display_args_err(
+ _("Alpine related Registry values removed."),
+ NULL, 0);
+ else
+ display_args_err(
+ _("Not all Alpine related Registry values could be removed"),
+ NULL, 0);
+ exit(0);
+ }
+ else if(!strucmp(*av, "clearsilent")){
+ mswin_reg(MSWR_OP_BLAST, MSWR_NULL, NULL, 0);
+ exit(0);
+ }
+ else if(!strucmp(*av, "dump")){
+ char **pRegistry = mswin_reg_dump();
+
+ if(pRegistry){
+ display_args_err(NULL, pRegistry, 0);
+ free_list_array(&pRegistry);
+ }
+ exit(0);
+ }
+ else{
+ display_args_err(_("unknown registry command"),
+ NULL, 1);
+ ++usage;
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_flag_arg), c);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ }
+
+ goto Loop;
+ }
+#endif
+ /* single char flags */
+ else{
+ switch(c = **av){
+ case 'h':
+ do_help = 1;
+ break; /* break back to inner-while */
+ case 'k':
+ do_use_fk = 1;
+ break;
+ case 'z':
+ do_can_suspend = 1;
+ break;
+ case 'r':
+ pine_state->restricted = 1;
+ break;
+ case 'o':
+ pine_state->open_readonly_on_startup = 1;
+ break;
+ case 'i':
+ pine_state->start_in_index = 1;
+ break;
+ case 'v':
+ do_version = 1;
+ break; /* break back to inner-while */
+ /* these take arguments */
+ case 'f': case 'F': case 'p': case 'I':
+ case 'c': case 'd': case 'P': case 'x': /* string args */
+ case 'n': /* integer args */
+ if(*++*av)
+ str = *av;
+ else if(--ac)
+ str = *++av;
+ else if(c == 'f' || c == 'F')
+ str = "";
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_flag_arg), c);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ goto Loop;
+ }
+
+ switch(c){
+ case 'f':
+ if(args->action == aaFolder && !args->data.folder){
+ args->data.folder = cpystr(str);
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), "-f");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage++;
+ }
+
+ break;
+ case 'F':
+ if(args->action == aaFolder && !args->data.folder){
+ args->action = aaMore;
+ args->data.file = cpystr(str);
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), "-F");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage++;
+ }
+
+ break;
+ case 'd':
+ debug_str = str;
+ break;
+ case 'I':
+ cmd_list = str;
+ break;
+ case 'p':
+ if(str){
+ char path[MAXPATH], dir[MAXPATH];
+
+ if(IS_REMOTE(str) || is_absolute_path(str)){
+ strncpy(path, str, sizeof(path)-1);
+ path[sizeof(path)-1] = '\0';
+ }
+ else{
+ getcwd(dir, sizeof(path));
+ build_path(path, dir, str, sizeof(path));
+ }
+
+ /*
+ * Pinerc used to be the name of the pinerc
+ * file. Now, since the pinerc can be remote,
+ * we've replaced the variable pinerc with the
+ * structure prc. Unfortunately, other parts of
+ * pine rely on the fact that pinerc is the
+ * name of the pinerc _file_, and use the
+ * directory that the pinerc file is located
+ * in for their own purposes. We keep that so
+ * things will keep working.
+ */
+
+ if(!IS_REMOTE(path)){
+ if(pine_state->pinerc)
+ fs_give((void **)&pine_state->pinerc);
+
+ pine_state->pinerc = cpystr(path);
+ }
+
+ /*
+ * Last one wins. This would be the place where
+ * we put multiple pinercs in a list if we
+ * were to allow that.
+ */
+ if(pine_state->prc)
+ free_pinerc_s(&pine_state->prc);
+
+ pine_state->prc = new_pinerc_s(path);
+ }
+
+ break;
+ case 'P':
+ if(str){
+ char path[MAXPATH], dir[MAXPATH];
+
+ if(IS_REMOTE(str) || is_absolute_path(str)){
+ strncpy(path, str, sizeof(path)-1);
+ path[sizeof(path)-1] = '\0';
+ }
+ else{
+ getcwd(dir, sizeof(path));
+ build_path(path, dir, str, sizeof(path));
+ }
+
+ if(pine_state->pconf)
+ free_pinerc_s(&pine_state->pconf);
+
+ pine_state->pconf = new_pinerc_s(path);
+ }
+
+ break;
+ case 'x':
+ if(str)
+ pine_state->exceptions = cpystr(str);
+
+ break;
+ case 'c':
+ if(!isdigit((unsigned char)str[0])){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ _(args_err_missing_flag_num), c);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ break;
+ }
+
+ pine_state->init_context = (short) atoi(str);
+ break;
+
+ case 'n':
+ if(!isdigit((unsigned char)str[0])){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ _(args_err_missing_flag_num), c);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ break;
+ }
+
+ pine_state->start_entry = atoi(str);
+ if(pine_state->start_entry < 1)
+ pine_state->start_entry = 1;
+
+ break;
+ }
+
+ goto Loop;
+
+ default:
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_unknown), c);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ ++usage;
+ break;
+ }
+ }
+ }
+ }
+ else if(args->action == aaMail
+ || (args->action == aaFolder && !args->data.folder)){
+ STRLIST_S *stp, **slp;
+
+ args->action = aaMail;
+
+ stp = new_strlist(*av);
+
+ for(slp = &args->data.mail.addrlist; *slp; slp = &(*slp)->next)
+ ;
+
+ *slp = stp;
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_conflict), *av);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage++;
+ }
+
+ if(cmd_list){
+ int commas = 0;
+ char *p = cmd_list;
+ char *error = NULL;
+
+ while(*p++)
+ if(*p == ',')
+ ++commas;
+
+ COM_INIT_CMD_LIST = parse_list(cmd_list, commas+1, 0, &error);
+ if(error){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_I_error), cmd_list, error);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ exit(-1);
+ }
+ }
+
+#ifdef DEBUG
+ pine_state->debug_nfiles = NUMDEBUGFILES;
+#endif
+ if(debug_str && process_debug_str(debug_str))
+ usage++;
+
+ if(lc && strncmp(lc, ALPINE_FKEY_NAME, sizeof(ALPINE_FKEY_NAME) - 1) == 0)
+ do_use_fk = 1;
+
+ if(do_use_fk || do_can_suspend){
+ char list[500];
+ int commas = 0;
+ char *p = list;
+ char *error = NULL;
+
+ list[0] = '\0';
+
+ if(do_use_fk){
+ if(list[0]){
+ strncat(list, ",", sizeof(list)-strlen(list)-1);
+ list[sizeof(list)-1] = '\0';
+ }
+
+ strncat(list, "use-function-keys", sizeof(list)-strlen(list)-1);
+ list[sizeof(list)-1] = '\0';
+ }
+
+ if(do_can_suspend){
+ if(list[0]){
+ strncat(list, ",", sizeof(list)-strlen(list)-1);
+ list[sizeof(list)-1] = '\0';
+ }
+
+ strncat(list, "enable-suspend", sizeof(list)-strlen(list)-1);
+ list[sizeof(list)-1] = '\0';
+ }
+
+ while(*p++)
+ if(*p == ',')
+ ++commas;
+
+ pine_state->feat_list_back_compat = parse_list(list,commas+1,0,&error);
+ if(error){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, args_err_internal, error);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ exit(-1);
+ }
+ }
+
+ if(((do_conf ? 1 : 0)+(pinerc_file ? 1 : 0)) > 1){
+ display_args_err(_("May only have one of -conf and -pinerc"),
+ NULL, 1);
+ exit(-1);
+ }
+
+ if(do_help || usage)
+ args_help();
+
+ if(usage)
+ exit(-1);
+
+ if(do_version){
+ extern char datestamp[], hoststamp[];
+ char rev[128];
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Alpine %s (%s %s) built %s on %s",
+ ALPINE_VERSION,
+ SYSTYPE ? SYSTYPE : "?",
+ get_alpine_revision_string(rev, sizeof(rev)),
+ datestamp, hoststamp);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 0);
+ exit(0);
+ }
+
+ if(do_conf)
+ dump_global_conf();
+
+ if(pinerc_file)
+ dump_new_pinerc(pinerc_file);
+
+ /*
+ * Don't NULL out argv[0] or we might crash in unexpected ways. In OS X, we were
+ * crashing when opening attachments because of this.
+ */
+ if(ac <= 0 && av != argv)
+ *av = NULL;
+}
+
+
+/*
+ * Returns 0 if ok, -1 if error.
+ */
+int
+process_debug_str(char *debug_str)
+{
+ int i, usage = 0;
+ int commas = 0;
+ int new_style_debug_arg = 0;
+ char *q = debug_str;
+ char *error = NULL;
+ char **list, **p;
+
+#ifdef DEBUG
+ if(debug_str){
+ if(!isdigit((unsigned char)debug_str[0]))
+ new_style_debug_arg++;
+
+ if(new_style_debug_arg){
+ while(*q++)
+ if(*q == ',')
+ ++commas;
+
+ list = parse_list(debug_str, commas+1, 0, &error);
+ if(error){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_d_error), debug_str, error);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ return(-1);
+ }
+
+ if(list){
+ for(p = list; *p; p++){
+ if(struncmp(*p, "timestamp", 9) == 0){
+ ps_global->debug_timestamp = 1;
+ }
+ else if(struncmp(*p, "imap", 4) == 0){
+ q = *p + 4;
+ if(!*q || !*(q+1) || *q != '=' ||
+ !isdigit((unsigned char)*(q+1))){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_debug_num), *p);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage = -1;
+ }
+ else{
+ i = atoi(q+1);
+ ps_global->debug_imap = MIN(5,MAX(0,i));
+ }
+ }
+ else if(struncmp(*p, "flush", 5) == 0){
+ ps_global->debug_flush = 1;
+ }
+ else if(struncmp(*p, "tcp", 3) == 0){
+ ps_global->debug_tcp = 1;
+ }
+ else if(struncmp(*p, "verbose", 7) == 0){
+ q = *p + 7;
+ if(!*q || !*(q+1) || *q != '=' ||
+ !isdigit((unsigned char)*(q+1))){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_debug_num), *p);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage = -1;
+ }
+ else
+ debug = atoi(q+1);
+ }
+ else if(struncmp(*p, "numfiles", 8) == 0){
+ q = *p + 8;
+ if(!*q || !*(q+1) || *q != '=' ||
+ !isdigit((unsigned char)*(q+1))){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_debug_num), *p);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage = -1;
+ }
+ else{
+ i = atoi(q+1);
+ ps_global->debug_nfiles = MIN(31,MAX(0,i));
+ }
+ }
+ else if(struncmp(*p, "malloc", 6) == 0){
+ q = *p + 6;
+ if(!*q || !*(q+1) || *q != '=' ||
+ !isdigit((unsigned char)*(q+1))){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_debug_num), *p);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage = -1;
+ }
+ else{
+ i = atoi(q+1);
+ ps_global->debug_malloc = MIN(63,MAX(0,i));
+ }
+ }
+#if defined(ENABLE_LDAP) && defined(LDAP_DEBUG)
+ else if(struncmp(*p, "ldap", 4) == 0){
+ q = *p + 4;
+ if(!*q || !*(q+1) || *q != '=' ||
+ !isdigit((unsigned char)*(q+1))){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _(args_err_missing_debug_num), *p);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage = -1;
+ }
+ else{
+ i = atoi(q+1);
+ ldap_debug = i;
+ }
+ }
+#endif /* LDAP */
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("unknown debug keyword \"%s\""), *p);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage = -1;
+ }
+ }
+
+ free_list_array(&list);
+ }
+ }
+ else{
+ debug = atoi(debug_str);
+ if(debug > 9)
+ ps_global->debug_imap = 5;
+ else if(debug > 7)
+ ps_global->debug_imap = 4;
+ else if(debug > 6)
+ ps_global->debug_imap = 3;
+ else if(debug > 4)
+ ps_global->debug_imap = 2;
+ else if(debug > 2)
+ ps_global->debug_imap = 1;
+
+ if(debug > 7)
+ ps_global->debug_timestamp = 1;
+
+ if(debug > 8)
+ ps_global->debug_flush = 1;
+ }
+ }
+
+ if(!new_style_debug_arg){
+#ifdef CSRIMALLOC
+ ps_global->debug_malloc =
+ (debug <= DEFAULT_DEBUG) ? 1 : (debug < 9) ? 2 : 3;
+#else /* !CSRIMALLOC */
+#ifdef HAVE_SMALLOC
+ if(debug > 8)
+ ps_global->debug_malloc = 2;
+#else /* !HAVE_SMALLOC */
+#ifdef NXT
+ if(debug > 8)
+ ps_global->debug_malloc = 32;
+#endif /* NXT */
+#endif /* HAVE_SMALLOC */
+#endif /* CSRIMALLOC */
+ }
+
+#else /* !DEBUG */
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("unknown flag \"d\", debugging not compiled in"));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ display_args_err(tmp_20k_buf, NULL, 1);
+ usage = -1;
+
+#endif /* !DEBUG */
+
+ return(usage);
+}
+
+
+void
+args_add_attach(PATMT **alpp, char *s, int deleted)
+{
+ PATMT *atmp, **atmpp;
+
+ atmp = (PATMT *) fs_get(sizeof(PATMT));
+ memset(atmp, 0, sizeof(PATMT));
+ atmp->filename = cpystr(s);
+
+#if defined(DOS) || defined(OS2)
+ (void) removing_quotes(atmp->filename);
+#endif
+
+ if(deleted)
+ atmp->flags |= A_TMP;
+
+ for(atmpp = alpp; *atmpp; atmpp = &(*atmpp)->next)
+ ;
+
+ *atmpp = atmp;
+}
+
+
+/*----------------------------------------------------------------------
+ print a few lines of help for command line arguments
+
+ Args: none
+
+ Result: prints help messages
+ ----------------------------------------------------------------------*/
+void
+args_help(void)
+{
+ char *pp[2];
+#ifndef _WINDOWS
+ char **a;
+#endif
+
+ pp[1] = NULL;
+
+ /** print out possible starting arguments... **/
+
+ /*
+ * display_args_err expects already translated input
+ * so we need to translate a line at a time. Since
+ * the 1st and 3rd args are zero it is ok to call it
+ * a line at a time.
+ *
+ * Windows expects the full block of text so we'll pass
+ * it as such.
+ */
+#ifndef _WINDOWS
+ for(a=args_pine_args; a && *a; a++){
+ pp[0] = _(*a);
+ display_args_err(NULL, pp, 0);
+ }
+#else
+ display_args_err(NULL, args_pine_args, 0);
+#endif
+
+ exit(1);
+}
+
+
+/*----------------------------------------------------------------------
+ write argument error to the display...
+
+ Args: none
+
+ Result: prints help messages
+ ----------------------------------------------------------------------*/
+void
+display_args_err(char *s, char **a, int err)
+{
+ char errstr[256], *errp;
+ FILE *fp = err ? stderr : stdout;
+
+
+ if(err && s){
+ snprintf(errp = errstr, sizeof(errstr), "%s: %s", _("Argument Error"), s);
+ errstr[sizeof(errstr)-1] = '\0';
+ }
+ else
+ errp = s;
+
+#ifdef _WINDOWS
+ if(errp)
+ mswin_messagebox(errp, err);
+
+ if(a && *a){
+ os_argsdialog(a);
+ }
+#else
+ if(errp)
+ fprintf(fp, "%s\n", errp);
+
+ while(a && *a)
+ fprintf(fp, "%s\n", *a++);
+#endif
+}
+
+
+/*
+ * The argument is an argument from the command line. We check to see
+ * if it is specifying an alternate value for one of the options that is
+ * normally set in pinerc. If so, we return 1 and set the appropriate
+ * values in the variables array.
+ * The arg can be
+ * varname=value
+ * varname=value1,value2,value3
+ * feature-list=featurename these are just a special
+ * feature-list=featurename1,featurename2 case of above
+ * featurename This is equivalent to above
+ * no-featurename
+ */
+int
+pinerc_cmdline_opt(char *arg)
+{
+ struct variable *v;
+ char *p1 = NULL,
+ *value,
+ **oldlvalue = NULL,
+ **lvalue;
+ int i, count;
+
+ if(!arg || !arg[0])
+ return 0;
+
+ for(v = ps_global->vars; v->name != NULL; v++){
+ if(v->is_used && struncmp(v->name, arg, strlen(v->name)) == 0){
+ p1 = arg + strlen(v->name);
+
+ /*----- Skip to '=' -----*/
+ while(*p1 && (*p1 == '\t' || *p1 == ' '))
+ p1++;
+
+ if(*p1 != '='){
+ char buf[MAILTMPLEN];
+
+ snprintf(buf, sizeof(buf), _("Missing \"=\" after -%s\n"), v->name);
+ buf[sizeof(buf)-1] = '\0';
+ exceptional_exit(buf, -1);
+ }
+
+ p1++;
+ break;
+ }
+ }
+
+ /* no match, check for a feature name used directly */
+ if(v->name == NULL){
+ FEATURE_S *feat;
+ char *featname;
+
+ if(struncmp(arg, "no-", 3) == 0)
+ featname = arg+3;
+ else
+ featname = arg;
+
+ for(i = 0; (feat = feature_list(i)) != NULL; i++){
+ if(strucmp(featname, feat->name) == 0){
+ v = &(ps_global->vars)[V_FEATURE_LIST];
+ p1 = arg;
+ break;
+ }
+ }
+ }
+
+ if(!p1)
+ return 0;
+
+ if(v->is_obsolete || !v->is_user){
+ char buf[MAILTMPLEN];
+
+ if(v->is_obsolete)
+ snprintf(buf, sizeof(buf), _("Option \"%s\" is obsolete\n"), v->name);
+ else
+ snprintf(buf, sizeof(buf), _("Option \"%s\" is not user settable\n"), v->name);
+
+ exceptional_exit(buf, -1);
+ }
+
+ /* free mem */
+ if(v->is_list){
+ oldlvalue = v->cmdline_val.l;
+ v->cmdline_val.l = NULL;
+ }
+ else if(v->cmdline_val.p)
+ fs_give((void **) &(v->cmdline_val.p));
+
+ /*----- Matched a variable, get its value ----*/
+ while(*p1 == ' ' || *p1 == '\t')
+ p1++;
+ value = p1;
+
+ if(*value == '\0'){
+ if(v->is_list){
+ v->cmdline_val.l = (char **)fs_get(2 * sizeof(char *));
+ /*
+ * we let people leave off the quotes on command line so that
+ * they don't have to mess with shell quoting
+ */
+ v->cmdline_val.l[0] = cpystr("");
+ v->cmdline_val.l[1] = NULL;
+ if(oldlvalue)
+ free_list_array(&oldlvalue);
+
+ }else{
+ v->cmdline_val.p = cpystr("");
+ }
+ return 1;
+ }
+
+ /*--value is non-empty--*/
+ if(*value == '"' && !v->is_list){
+ value++;
+ for(p1 = value; *p1 && *p1 != '"'; p1++);
+ if(*p1 == '"')
+ *p1 = '\0';
+ else
+ removing_trailing_white_space(value);
+ }else{
+ removing_trailing_white_space(value);
+ }
+
+ if(v->is_list){
+ int was_quoted = 0;
+ char *error = NULL;
+
+ count = 1;
+ for(p1=value; *p1; p1++){ /* generous count of list elements */
+ if(*p1 == '"') /* ignore ',' if quoted */
+ was_quoted = (was_quoted) ? 0 : 1;
+
+ if(*p1 == ',' && !was_quoted)
+ count++;
+ }
+
+ lvalue = parse_list(value, count, 0, &error);
+ if(error){
+ char buf[MAILTMPLEN];
+
+ snprintf(buf, sizeof(buf), "%s in %s = \"%s\"\n", error, v->name, value);
+ buf[sizeof(buf)-1] = '\0';
+ exceptional_exit(buf, -1);
+ }
+ /*
+ * Special case: turn "" strings into empty strings.
+ * This allows users to turn off default lists. For example,
+ * if smtp-server is set then a user could override smtp-server
+ * with smtp-server="".
+ */
+ for(i = 0; lvalue[i]; i++)
+ if(lvalue[i][0] == '"' &&
+ lvalue[i][1] == '"' &&
+ lvalue[i][2] == '\0')
+ lvalue[i][0] = '\0';
+ }
+
+ if(v->is_list){
+ if(oldlvalue){
+ char **combinedlvalue;
+ int j;
+
+ /* combine old and new cmdline lists */
+ for(count = 0, i = 0; oldlvalue[i]; i++, count++)
+ ;
+
+ for(i = 0; lvalue && lvalue[i]; i++, count++)
+ ;
+
+ combinedlvalue = (char **) fs_get((count+1) * sizeof(char *));
+ memset(combinedlvalue, 0, (count+1) * sizeof(*combinedlvalue));
+
+ for(i = 0, j = 0; oldlvalue[i]; i++, j++)
+ combinedlvalue[j] = cpystr(oldlvalue[i]);
+
+ for(i = 0; lvalue && lvalue[i]; i++, j++)
+ combinedlvalue[j] = cpystr(lvalue[i]);
+
+ v->cmdline_val.l = combinedlvalue;
+
+ fs_give((void **) &oldlvalue);
+ if(lvalue)
+ fs_give((void **) &lvalue);
+ }
+ else
+ v->cmdline_val.l = lvalue;
+ }
+ else
+ v->cmdline_val.p = cpystr(value);
+
+ return 1;
+}
diff --git a/alpine/arg.h b/alpine/arg.h
new file mode 100644
index 00000000..339e793b
--- /dev/null
+++ b/alpine/arg.h
@@ -0,0 +1,52 @@
+/*
+ * $Id: arg.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_ARG_INCLUDED
+#define PINE_ARG_INCLUDED
+
+
+#include "../pith/state.h"
+#include "../pith/string.h"
+
+
+/*
+ * Used by pine_args to tell caller what was found;
+ */
+typedef struct argdata {
+ enum {aaFolder = 0, aaMore, aaURL, aaMail,
+ aaPrcCopy, aaAbookCopy} action;
+ union {
+ char *folder;
+ char *file;
+ struct {
+ STRLIST_S *addrlist;
+ PATMT *attachlist;
+ } mail;
+ struct {
+ char *local;
+ char *remote;
+ } copy;
+ } data;
+ char *url;
+} ARGDATA_S;
+
+
+/* exported protoypes */
+void pine_args(struct pine *, int, char **, ARGDATA_S *);
+void display_args_err(char *, char **, int);
+void args_help(void);
+
+
+#endif /* PINE_ARG_INCLUDED */
diff --git a/alpine/brk2pine.sh b/alpine/brk2pine.sh
new file mode 100755
index 00000000..5b4bff09
--- /dev/null
+++ b/alpine/brk2pine.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# $Id: brk2pine.sh 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+#
+# T H E P I N E M A I L S Y S T E M
+#
+# Laurence Lundblade and Mike Seibel
+# Networks and Distributed Computing
+# Computing and Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, Washington, 98195, USA
+# Internet: lgl@CAC.Washington.EDU
+# mikes@CAC.Washington.EDU
+#
+# Please address all bugs and comments to "pine-bugs@cac.washington.edu"
+#
+#
+# Pine and Pico are registered trademarks of the University of Washington.
+# No commercial use of these trademarks may be made without prior written
+# permission of the University of Washington.
+#
+# Pine, Pico, and Pilot software and its included text are Copyright
+# 1989-1996 by the University of Washington.
+#
+# The full text of our legal notices is contained in the file called
+# CPYRIGHT, included with this distribution.
+#
+#
+# Pine is in part based on The Elm Mail System:
+# ***********************************************************************
+# * The Elm Mail System - Revision: 2.13 *
+# * *
+# * Copyright (c) 1986, 1987 Dave Taylor *
+# * Copyright (c) 1988, 1989 USENET Community Trust *
+# ***********************************************************************
+#
+#
+
+
+#
+# A filter to convert personal mail aliases in a .mailrc file into
+# pine address book format.
+#
+# Usage: program [.mailrc] >> .addressbook
+#
+# Corey Satten, corey@cac.washington.edu, 9/25/91
+#
+sed -n '
+# first fold continued lines (ending in \) into a single long line
+ /\\[ ]*$/ {
+ : more
+ s/\\//g
+ N
+ s/\n/ /
+ /\\/b more
+ }
+# next convert all sequences of whitespace into single space
+ s/[ ][ ]*/ /g
+# finally, reformat and print lines containing alias as the first word
+ /^ *alias / {
+ s/^ *alias \([!-~][!-~]*\) \(.*\)$/\1 \1 (\2)/
+ s/ /,/g
+ s/(\([^,]*\))/\1/
+ p
+ }
+' ${*-$HOME/.mailrc}
diff --git a/alpine/busy.c b/alpine/busy.c
new file mode 100644
index 00000000..a0c71ab0
--- /dev/null
+++ b/alpine/busy.c
@@ -0,0 +1,473 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: signal.c 138 2006-09-22 22:12:03Z mikes@u.washington.edu $";
+#endif
+
+/* ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ Implement busy_cue spinner
+ ====*/
+
+
+#include <system.h>
+#include <general.h>
+
+#include "../c-client/c-client.h"
+
+#include "../pith/conf.h"
+#include "../pith/state.h"
+#include "../pith/status.h"
+#include "../pith/busy.h"
+#include "../pith/debug.h"
+#include "../pith/help.h"
+
+#include "../pith/charconv/utf8.h"
+
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+#include "status.h"
+#include "after.h"
+#include "busy.h"
+
+
+static int dotcount;
+static char busy_message[MAX_BM + 1];
+static int busy_cue_pause;
+static int busy_width;
+static int final_message;
+static int final_message_pri;
+
+static percent_done_t percent_done_ptr;
+
+#define MAX_SPINNER_WIDTH 32
+#define MAX_SPINNER_ELEMENTS 64
+
+static int spinner = 0;
+static struct _spinner {
+ int width,
+ elements;
+ char *bars[MAX_SPINNER_ELEMENTS];
+ unsigned used_this_round:1;
+} spinners[] = {
+ {4, 4, {"<|> ", "</> ", "<-> ", "<\\> "}},
+ {8, 34, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
+ "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
+ "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
+ "~~\\___/~", "~~~\\___/", "~~~\\___/", "^~~~\\___", "^~~~\\___",
+ "~^~~~\\__", "~^~~~\\__", "~~^~~~\\_", "~~^~~~\\_", "~~~^~~~\\",
+ "~~~^~~~\\", "~~~~^~~~", "~~~~^~~~", "~~~~~^~~", "~~~~~^~~",
+ "~~~~~~^~", "~~~~~~^~", "~~~~~~~^", "~~~~~~~^"}},
+ {9, 14 , {"| |", "| |", "-| |-", "-| |-",
+ "--| |--", "--| |--", "---||---", "---||---",
+ "--|WA|--", "--|WA|--", "-|WAIT|-", "-|WAIT|-",
+ "| WAIT |", "| WAIT |"}},
+ {9, 24 , {"o | ", "o | ", " o | ", " o | ",
+ " o | ", " o | ", " o| ", " o| ",
+ " \\ ", " \\ ", " -o ", " -o ",
+ " / o ", " / o ", " | o ", " | o ",
+ " \\ o", " \\ o", " - ", " - ",
+ " / ", " / ", " | ", " | "}},
+ {8, 38, {"~~~~~~~~", "~~~~~~~~", "/~~~~~~~", "/~~~~~~~", "_/~~~~~~",
+ "_/~~~~~~", "__/~~~~~", "__/~~~~~", "___/~~~~", "___/~~~~",
+ "\\___/~~~", "\\___/~~~", "~\\___/~~", "~\\___/~~", "~~\\___/~",
+ "~~\\___/~", "~~~\\___/", "~~~\\___/", "~~~~\\___", "~~~~\\___",
+ "\\~~~~\\__", "\\~~~~\\__", "/\\~~~~\\_", "/\\~~~~\\_", "~/\\~~~~\\",
+ "~/\\~~~~\\", "~~/\\~~~~", "~~/\\~~~~", "~~~/\\~~~", "~~~/\\~~~",
+ "~~~~/\\~~", "~~~~/\\~~", "~~~~~/\\~", "~~~~~/\\~", "~~~~~~/\\",
+ "~~~~~~/\\", "~~~~~~~/", "~~~~~~~/"}},
+ {6, 10, {"> ", "<> ", "><> ", " ><> ", " ><> ",
+ " ><> ", " ><> ", " ><>", " ><", " >"}},
+ {6, 10, {" <", " <>", " <><", " <>< ", " <>< ",
+ " <>< ", " <>< ", "<>< ", ">< ", "< "}},
+ {6, 10, {"> <", "<> <>", "><> <><", " ><><>< ", " ><>< ",
+ " <><> ", " <><><> ", "<>< ><>", ">< ><", "< >"}},
+ {11, 4, {"--|-(o)-|--", "--/-(o)-\\--", "----(o)----", "--\\-(o)-/--"}},
+ {6, 7, {"\\____/", "_\\__/_", "__\\/__", "__/\\__",
+ "_/__\\_", "/____\\", "|____|"}},
+ {4, 4, {"<|> ", "<\\> ", "<-> ", "</> "}},
+ {4, 10,{"| ", " / ", " _ ", " \\ ", " | ", " | ", " \\ ",
+ " _ ", " / ", "| "}},
+ {4, 8, {"_ _ ", "\\ \\ ", " | |", " / /", " _ _", " / /", " | |", "\\ \\ "}},
+ {4, 8, {"_ ", "\\ ", " | ", " / ", " _ ", " \\ ", " | ", "/ "}},
+ {4, 8, {"_ ", "\\ ", " | ", " / ", " _ ", " / ", " | ", "\\ "}},
+ {4, 4, {" . ", " o ", " O ", " o "}},
+ {4, 5, {"....", " ...", ". ..", ".. .", "... "}},
+ {4, 5, {" ", ". ", " . ", " . ", " ."}},
+ {4, 4, {".oOo", "oOo.", "Oo.o", "o.oO"}},
+ {4, 11,{"____", "\\___", "|\\__", "||\\_", "|||\\", "||||", "/|||",
+ "_/||", "__/|", "___/", "____"}},
+ {7, 9, {". .", " . . ", " . . ",
+ " . ", " + ", " * ", " X ",
+ " # ", " "}},
+ {4, 4, {". O ", "o o ", "O . ", "o o "}},
+ {4, 26,{"| ", "/ ", "_ ", "\\ ", " | ", " / ", " _ ", " \\ ",
+ " | ", " / ", " _ ", " \\ ", " |", " |", " \\ ", " _ ", " / ",
+ " | ", " \\ ", " _ ", " / ", " | ", "\\ ", "_ ", "/ ", "| "}},
+ {4, 8, {"* ", "-* ", "--* ", " --*", " --", " -", " ", " "}},
+ {4, 2, {"\\/\\/", "/\\/\\"}},
+ {4, 4, {"\\|/|", "|\\|/", "/|\\|", "|/|\\"}}
+};
+
+
+
+/*
+ * various pauses in 100th's of second
+ */
+#define BUSY_PERIOD_PERCENT 25 /* percent done update */
+#define BUSY_MSG_DONE 0 /* no more updates */
+#define BUSY_MSG_RETRY 25 /* message line conflict */
+#define BUSY_DELAY_PERCENT 33 /* pause before showing % */
+#define BUSY_DELAY_SPINNER 100 /* second pause before spinner */
+
+
+/* internal prototypes */
+int do_busy_cue(void *);
+void done_busy_cue(void *);
+
+
+/*
+ * Turn on a busy cue.
+ *
+ * msg -- the busy message to print in status line
+ * pc_f -- if non-null, call this function to get the percent done,
+ * (an integer between 0 and 100). If null, append dots.
+ * delay -- seconds to delay output of delay notification
+ *
+ * Returns: 0 If busy cue was already set up before we got here
+ * 1 If busy cue was not already set up.
+ *
+ * NOTE: busy_cue and cancel_busy_cue MUST be called symetrically in the
+ * same lexical scope.
+ */
+int
+busy_cue(char *msg, percent_done_t pc_f, int delay)
+{
+ AFTER_S *a = NULL, **ap;
+
+ dprint((9, "busy_cue(%s, %p, %d)\n", msg ? msg : "Busy", pc_f, delay));
+
+ if(!(ps_global && ps_global->ttyo)){
+ dprint((9, "busy_cue returns No (ttyo)"));
+ return(0);
+ }
+
+ /*
+ * If we're already busy'ing, but a cue is invoked that
+ * supplies more useful info, use it...
+ */
+ if(after_active){
+ if(msg || pc_f)
+ stop_after(1); /* uninstall old handler */
+ else
+ return(0); /* nothing to add, return */
+ }
+
+ /* get ready to set up list of AFTER_S's */
+ ap = &a;
+
+ dotcount = 0;
+ percent_done_ptr = pc_f;
+
+ if(msg){
+ strncpy(busy_message, msg, sizeof(busy_message));
+ final_message = 1;
+ }
+ else{
+ strncpy(busy_message, "Busy", sizeof(busy_message));
+ final_message = 0;
+ }
+
+ busy_message[sizeof(busy_message)-1] = '\0';
+ busy_width = utf8_width(busy_message);
+
+ if(!delay){
+ char progress[MAX_SCREEN_COLS+1];
+ int space_left, slots_used;
+
+ final_message = 1;
+ space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols
+ : 80) - busy_width - 2; /* 2 is for [] */
+ slots_used = MAX(0, MIN(space_left-3, 10));
+
+ if(percent_done_ptr && slots_used >= 4){
+ snprintf(progress, sizeof(progress), "%s |%*s|", busy_message, slots_used, "");
+ progress[sizeof(progress)-1] = '\0';
+ }
+ else{
+ dotcount++;
+ snprintf(progress, sizeof(progress), "%s%*s", busy_message,
+ spinners[spinner].width + 1, "");
+ progress[sizeof(progress)-1] = '\0';
+ }
+
+
+ if(status_message_remaining()){
+ char buf[sizeof(progress) + 30];
+ char *append = " [not actually shown]";
+
+ strncpy(buf, progress, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+
+ strncat(buf, append, sizeof(buf) - strlen(buf) - 1);
+ buf[sizeof(buf)-1] = '\0';
+
+ add_review_message(buf, -1);
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 1, progress);
+
+ /*
+ * We use display_message so that the initial message will
+ * be forced out only if there is not a previous message
+ * currently being displayed that hasn't been displayed for
+ * its min display time yet. In that case, we don't want
+ * to force out the initial message.
+ */
+ display_message('x');
+ }
+
+ fflush(stdout);
+ }
+
+ /*
+ * Randomly select one of the animations, even taking care
+ * to run through all of them before starting over!
+ * The user won't actually see them all because most of them
+ * will never show up before they are canceled, but it
+ * should still help.
+ */
+ spinner = -1;
+ if(F_OFF(F_USE_BORING_SPINNER,ps_global) && ps_global->active_status_interval > 0){
+ int arrsize, eligible, pick_this_one, i, j;
+
+ arrsize = sizeof(spinners)/sizeof(struct _spinner);
+
+ /* how many of them are eligible to be used this round? */
+ for(eligible = i = 0; i < arrsize; i++)
+ if(!spinners[i].used_this_round)
+ eligible++;
+
+ if(eligible == 0) /* reset */
+ for(eligible = i = 0; i < arrsize; i++){
+ spinners[i].used_this_round = 0;
+ eligible++;
+ }
+
+ if(eligible > 0){ /* it will be */
+ pick_this_one = random() % eligible;
+ for(j = i = 0; i < arrsize && spinner < 0; i++)
+ if(!spinners[i].used_this_round){
+ if(j == pick_this_one)
+ spinner = i;
+ else
+ j++;
+ }
+ }
+ }
+
+ if(spinner < 0 || spinner > sizeof(spinners)/sizeof(struct _spinner) -1)
+ spinner = 0;
+
+ *ap = new_afterstruct();
+ (*ap)->delay = (pc_f) ? BUSY_DELAY_PERCENT : BUSY_DELAY_SPINNER;
+ (*ap)->f = do_busy_cue;
+ (*ap)->cf = done_busy_cue;
+ ap = &(*ap)->next;
+
+ start_after(a); /* launch cue handler */
+
+#ifdef _WINDOWS
+ mswin_setcursor(MSWIN_CURSOR_BUSY);
+#endif
+
+ return(1);
+}
+
+
+/*
+ * If final_message was set when busy_cue was called:
+ * and message_pri = -1 -- no final message queued
+ * else final message queued with min equal to message_pri
+ */
+void
+cancel_busy_cue(int message_pri)
+{
+ dprint((9, "cancel_busy_cue(%d)\n", message_pri));
+
+ final_message_pri = message_pri;
+
+ stop_after(0);
+}
+
+/*
+ * suspend_busy_cue - continue previously installed busy_cue.
+ */
+void
+suspend_busy_cue(void)
+{
+ dprint((9, "suspend_busy_cue\n"));
+
+ if(after_active)
+ busy_cue_pause = 1;
+}
+
+
+/*
+ * resume_busy_cue - continue previously installed busy_cue.
+ */
+void
+resume_busy_cue(unsigned int pause)
+{
+ dprint((9, "resume_busy_cue\n"));
+
+ if(after_active)
+ busy_cue_pause = 0;
+}
+
+
+/*
+ * do_busy_cue - paint the busy cue and return how long caller
+ * should pause before calling us again
+ */
+int
+do_busy_cue(void *data)
+{
+ int space_left, slots_used, period;
+ char dbuf[MAX_SCREEN_COLS+1];
+
+ /* Don't wipe out any displayed status message prematurely */
+ if(status_message_remaining() || busy_cue_pause)
+ return(BUSY_MSG_RETRY);
+
+ space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) -
+ busy_width - 2; /* 2 is for [] */
+ slots_used = MAX(0, MIN(space_left-3, 10));
+
+ if(percent_done_ptr && slots_used >= 4){
+ int completed, pd;
+ char *s;
+
+ pd = (*percent_done_ptr)();
+ pd = MIN(MAX(0, pd), 100);
+
+ completed = (pd * slots_used) / 100;
+ snprintf(dbuf, sizeof(dbuf), "%s |%s%s%*s|", busy_message,
+ completed > 1 ? repeat_char(completed-1, pd==100 ? ' ' : '-') : "",
+ (completed > 0 && pd != 100) ? ">" : "",
+ slots_used - completed, "");
+ dbuf[sizeof(dbuf)-1] = '\0';
+
+ if(slots_used == 10){
+ s = dbuf + strlen(dbuf) - 8;
+ if(pd < 10){
+ s++; s++;
+ *s++ = '0' + pd;
+ }
+ else if(pd < 100){
+ s++;
+ *s++ = '0' + pd / 10;
+ *s++ = '0' + pd % 10;
+ }
+ else{
+ *s++ = '1';
+ *s++ = '0';
+ *s++ = '0';
+ }
+
+ *s = '%';
+ }
+
+ period = BUSY_PERIOD_PERCENT;
+ }
+ else{
+ char b[MAX_SPINNER_WIDTH + 2];
+ int ind;
+
+ ind = (dotcount % spinners[spinner].elements);
+
+ spinners[spinner].used_this_round = 1;
+ if(space_left >= spinners[spinner].width + 1){
+ b[0] = SPACE;
+ strncpy(b+1,
+ (ps_global->active_status_interval > 0)
+ ? spinners[spinner].bars[ind] : "... ", sizeof(b)-1);
+ b[sizeof(b)-1] = '\0';
+ }
+ else if(space_left >= 2 && space_left < sizeof(b)){
+ b[0] = '.';
+ b[1] = '.';
+ b[2] = '.';
+ b[space_left] = '\0';
+ }
+ else
+ b[0] = '\0';
+
+ snprintf(dbuf, sizeof(dbuf), "%s%s", busy_message, b);
+ dbuf[sizeof(dbuf)-1] = '\0';
+
+ /* convert interval to delay in 100ths of second */
+ period = (ps_global->active_status_interval > 0)
+ ? (100 / MIN(10, ps_global->active_status_interval)) : BUSY_MSG_DONE;
+ }
+
+ status_message_write(dbuf, 1);
+ dotcount++;
+ fflush(stdout);
+
+ return(period);
+}
+
+
+void
+done_busy_cue(void *data)
+{
+ int space_left, slots_used;
+
+ if(final_message && final_message_pri >= 0){
+ char progress[MAX_SCREEN_COLS+1];
+
+ /* 2 is for [] */
+ space_left = (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - busy_width - 2;
+ slots_used = MAX(0, MIN(space_left-3, 10));
+
+ if(percent_done_ptr && slots_used >= 4){
+ int left, right;
+
+ right = (slots_used - 4)/2;
+ left = slots_used - 4 - right;
+ snprintf(progress, sizeof(progress), "%s |%*s100%%%*s|",
+ busy_message, left, "", right, "");
+ progress[sizeof(progress)-1] = '\0';
+ q_status_message(SM_ORDER,
+ final_message_pri>=2 ? MAX(final_message_pri,3) : 0,
+ final_message_pri+2, progress);
+ }
+ else{
+ int padding;
+
+ padding = MAX(0, MIN(space_left-5, spinners[spinner].width-4));
+
+ snprintf(progress, sizeof(progress), "%s %*sDONE", busy_message,
+ padding, "");
+ progress[sizeof(progress)-1] = '\0';
+ q_status_message(SM_ORDER,
+ final_message_pri>=2 ? MAX(final_message_pri,3) : 0,
+ final_message_pri+2, progress);
+ }
+ }
+
+ mark_status_dirty();
+}
diff --git a/alpine/busy.h b/alpine/busy.h
new file mode 100644
index 00000000..63602867
--- /dev/null
+++ b/alpine/busy.h
@@ -0,0 +1,30 @@
+/*
+ * $Id: busy.h 137 2006-09-22 21:34:06Z mikes@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_BUSY_INCLUDED
+#define PINE_BUSY_INCLUDED
+
+
+#define MAX_BM 150 /* max length of busy message */
+
+
+/* exported prototypes */
+
+void suspend_busy_cue(void);
+void resume_busy_cue(unsigned);
+
+
+#endif /* PINE_BUSY_INCLUDED */
diff --git a/alpine/colorconf.c b/alpine/colorconf.c
new file mode 100644
index 00000000..e0aee596
--- /dev/null
+++ b/alpine/colorconf.c
@@ -0,0 +1,2788 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: colorconf.c 934 2008-02-23 00:44:29Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+#include "headers.h"
+#include "colorconf.h"
+#include "keymenu.h"
+#include "status.h"
+#include "confscroll.h"
+#include "radio.h"
+#include "mailview.h"
+#include "../pith/state.h"
+#include "../pith/util.h"
+#include "../pith/color.h"
+#include "../pith/icache.h"
+#include "../pith/mailcmd.h"
+#include "../pith/list.h"
+
+
+/*
+ * Internal prototypes
+ */
+char *color_setting_text_line(struct pine *, struct variable *);
+void revert_to_saved_color_config(struct pine *, SAVED_CONFIG_S *);
+SAVED_CONFIG_S *save_color_config_vars(struct pine *);
+void free_saved_color_config(struct pine *, SAVED_CONFIG_S **);
+void color_config_init_display(struct pine *, CONF_S **, CONF_S **);
+void add_header_color_line(struct pine *, CONF_S **, char *, int);
+int is_rgb_color(char *);
+char *new_color_line(char *, int, int, int);
+int color_text_tool(struct pine *, int, CONF_S **, unsigned);
+int offer_normal_color_for_var(struct pine *, struct variable *);
+int offer_none_color_for_var(struct pine *, struct variable *);
+void color_update_selected(struct pine *, CONF_S *, char *, char *, int);
+int color_edit_screen(struct pine *, CONF_S **);
+
+
+int treat_color_vars_as_text;
+
+
+void
+color_config_screen(struct pine *ps, int edit_exceptions)
+{
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ SAVED_CONFIG_S *vsave;
+ OPT_SCREEN_S screen;
+ int readonly_warning = 0;
+
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ mailcap_free(); /* free resources we won't be using for a while */
+
+ if(ps->fix_fixed_warning)
+ offer_to_fix_pinerc(ps);
+
+ ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;
+
+ if(ps->restricted)
+ readonly_warning = 1;
+ else{
+ PINERC_S *prc = NULL;
+
+ switch(ew){
+ case Main:
+ prc = ps->prc;
+ break;
+ case Post:
+ prc = ps->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ readonly_warning = prc ? prc->readonly : 1;
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return;
+ }
+ }
+
+ color_config_init_display(ps, &ctmp, &first_line);
+
+ vsave = save_color_config_vars(ps);
+
+ memset(&screen, 0, sizeof(screen));
+ screen.deferred_ro_warning = readonly_warning;
+ switch(conf_scroll_screen(ps, &screen, first_line,
+ edit_exceptions ? _("SETUP COLOR EXCEPTIONS")
+ : _("SETUP COLOR"),
+ /* TRANSLATORS: Print something1 using something2.
+ configuration is something1 */
+ _("configuration"), 0)){
+ case 0:
+ break;
+
+ case 1:
+ write_pinerc(ps, ew, WRP_NONE);
+ break;
+
+ case 10:
+ revert_to_saved_color_config(ps, vsave);
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 7, 10,
+ _("conf_scroll_screen bad ret in color_config"));
+ break;
+ }
+
+ free_saved_color_config(ps, &vsave);
+
+#ifdef _WINDOWS
+ mswin_set_quit_confirm (F_OFF(F_QUIT_WO_CONFIRM, ps_global));
+#endif
+}
+
+
+char *
+sample_text(struct pine *ps, struct variable *v)
+{
+ char *ret = SAMP2;
+ char *pvalfg, *pvalbg;
+
+ pvalfg = PVAL(v, ew);
+ pvalbg = PVAL(v+1, ew);
+
+ if((v && v->name &&
+ srchstr(v->name, "-foreground-color") &&
+ pvalfg && pvalfg[0] && pvalbg && pvalbg[0]) ||
+ (v == &ps->vars[V_VIEW_HDR_COLORS] || v == &ps->vars[V_KW_COLORS]))
+ ret = SAMP1;
+
+ return(ret);
+}
+
+
+char *
+sampleexc_text(struct pine *ps, struct variable *v)
+{
+ char *ret = "";
+ char *pvalfg, *pvalbg;
+
+ pvalfg = PVAL(v, Post);
+ pvalbg = PVAL(v+1, Post);
+ if(v && color_holding_var(ps, v) &&
+ srchstr(v->name, "-foreground-color")){
+ if(ew == Main && pvalfg && pvalfg[0] && pvalbg && pvalbg[0])
+ ret = SAMPEXC;
+ }
+
+ return(ret);
+}
+
+
+char *
+color_setting_text_line(struct pine *ps, struct variable *v)
+{
+ char *p;
+ char tmp[1200];
+
+ p = sampleexc_text(ps, v);
+
+ /*
+ * Don't need to use utf8_snprintf if we're not trying to
+ * control the widths of fields.
+ */
+ snprintf(tmp, sizeof(tmp), "%s %s%*s%s%s", SAMPLE_LEADER,
+ sample_text(ps,v), *p ? SBS : 0, "", p,
+ color_parenthetical(v));
+ return(cpystr(tmp));
+}
+
+
+/*
+ * Compare saved user_val with current user_val to see if it changed.
+ * If any have changed, change it back and take the appropriate action.
+ */
+void
+revert_to_saved_color_config(struct pine *ps, SAVED_CONFIG_S *vsave)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *v;
+ int i, n;
+ int changed = 0;
+ char *pval, **apval, **lval, ***alval;
+
+ v = vsave;
+ for(vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!(color_related_var(ps, vreal) || vreal==&ps->vars[V_FEATURE_LIST]))
+ continue;
+
+ if(vreal->is_list){
+ lval = LVAL(vreal, ew);
+ alval = ALVAL(vreal, ew);
+
+ if((v->saved_user_val.l && !lval)
+ || (!v->saved_user_val.l && lval))
+ changed++;
+ else if(!v->saved_user_val.l && !lval)
+ ;/* no change, nothing to do */
+ else
+ for(i = 0; v->saved_user_val.l[i] || lval[i]; i++)
+ if((v->saved_user_val.l[i]
+ && (!lval[i]
+ || strcmp(v->saved_user_val.l[i], lval[i])))
+ ||
+ (!v->saved_user_val.l[i] && lval[i])){
+ changed++;
+ break;
+ }
+
+ if(changed){
+ char **list;
+
+ if(alval){
+ if(*alval)
+ free_list_array(alval);
+
+ /* copy back the original one */
+ if(v->saved_user_val.l){
+ list = v->saved_user_val.l;
+ n = 0;
+ /* count how many */
+ while(list[n])
+ n++;
+
+ *alval = (char **)fs_get((n+1) * sizeof(char *));
+
+ for(i = 0; i < n; i++)
+ (*alval)[i] = cpystr(v->saved_user_val.l[i]);
+
+ (*alval)[n] = NULL;
+ }
+ }
+ }
+ }
+ else{
+ pval = PVAL(vreal, ew);
+ apval = APVAL(vreal, ew);
+
+ if((v->saved_user_val.p &&
+ (!pval || strcmp(v->saved_user_val.p, pval))) ||
+ (!v->saved_user_val.p && pval)){
+ /* It changed, fix it */
+ changed++;
+ if(apval){
+ /* free the changed value */
+ if(*apval)
+ fs_give((void **)apval);
+
+ if(v->saved_user_val.p)
+ *apval = cpystr(v->saved_user_val.p);
+ }
+ }
+ }
+
+ if(changed){
+ if(vreal == &ps->vars[V_FEATURE_LIST])
+ set_feature_list_current_val(vreal);
+ else
+ set_current_val(vreal, TRUE, FALSE);
+
+ fix_side_effects(ps, vreal, 1);
+ }
+ }
+
+ if(changed){
+ set_current_color_vals(ps);
+ ClearScreen();
+ ps->mangled_screen = 1;
+ }
+}
+
+
+SAVED_CONFIG_S *
+save_color_config_vars(struct pine *ps)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *vsave, *v;
+
+ vsave = (SAVED_CONFIG_S *)fs_get((V_LAST_VAR+1)*sizeof(SAVED_CONFIG_S));
+ memset((void *)vsave, 0, (V_LAST_VAR+1)*sizeof(SAVED_CONFIG_S));
+ v = vsave;
+ for(vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!(color_related_var(ps, vreal) || vreal==&ps->vars[V_FEATURE_LIST]))
+ continue;
+
+ if(vreal->is_list){
+ int n, i;
+ char **list;
+
+ if(LVAL(vreal, ew)){
+ /* count how many */
+ n = 0;
+ list = LVAL(vreal, ew);
+ while(list[n])
+ n++;
+
+ v->saved_user_val.l = (char **)fs_get((n+1) * sizeof(char *));
+ memset((void *)v->saved_user_val.l, 0, (n+1)*sizeof(char *));
+ for(i = 0; i < n; i++)
+ v->saved_user_val.l[i] = cpystr(list[i]);
+
+ v->saved_user_val.l[n] = NULL;
+ }
+ }
+ else{
+ if(PVAL(vreal, ew))
+ v->saved_user_val.p = cpystr(PVAL(vreal, ew));
+ }
+ }
+
+ return(vsave);
+}
+
+
+void
+free_saved_color_config(struct pine *ps, SAVED_CONFIG_S **vsavep)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *v;
+
+ if(vsavep && *vsavep){
+ for(v = *vsavep, vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!(color_related_var(ps, vreal) ||
+ (vreal == &ps->vars[V_FEATURE_LIST])))
+ continue;
+
+ if(vreal->is_list){ /* free saved_user_val.l */
+ if(v && v->saved_user_val.l)
+ free_list_array(&v->saved_user_val.l);
+ }
+ else if(v && v->saved_user_val.p)
+ fs_give((void **)&v->saved_user_val.p);
+ }
+
+ fs_give((void **)vsavep);
+ }
+}
+
+
+void
+color_config_init_display(struct pine *ps, CONF_S **ctmp, CONF_S **first_line)
+{
+ char **lval;
+ int i, saw_first_index = 0;
+ struct variable *vtmp;
+ char *dashes = "--------------";
+
+#ifndef _WINDOWS
+ vtmp = &ps->vars[V_COLOR_STYLE];
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= (CF_NOSELECT | CF_STARTITEM);
+ (*ctmp)->keymenu = &config_radiobutton_keymenu;
+ (*ctmp)->tool = NULL;
+ (*ctmp)->varname = cpystr("Color Style");
+ (*ctmp)->varnamep = *ctmp;
+
+ standard_radio_setup(ps, ctmp, vtmp, first_line);
+
+ new_confline(ctmp);
+ /* Blank line */
+ (*ctmp)->flags |= (CF_NOSELECT | CF_B_LINE);
+
+ if(!pico_usingcolor()){
+ /* add a line explaining that color is not turned on */
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(COLORNOSET);
+
+ new_confline(ctmp);
+ /* Blank line */
+ (*ctmp)->flags |= (CF_NOSELECT | CF_B_LINE);
+ }
+
+#endif
+
+ vtmp = &ps->vars[V_INDEX_COLOR_STYLE];
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= (CF_NOSELECT | CF_STARTITEM);
+ (*ctmp)->keymenu = &config_radiobutton_keymenu;
+ (*ctmp)->tool = NULL;
+ (*ctmp)->varname = cpystr(_("Current Indexline Style"));
+ (*ctmp)->varnamep = *ctmp;
+
+ standard_radio_setup(ps, ctmp, vtmp, NULL);
+
+ new_confline(ctmp);
+ /* Blank line */
+ (*ctmp)->flags |= (CF_NOSELECT | CF_B_LINE);
+
+ vtmp = &ps->vars[V_TITLEBAR_COLOR_STYLE];
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= (CF_NOSELECT | CF_STARTITEM);
+ (*ctmp)->keymenu = &config_radiobutton_keymenu;
+ (*ctmp)->tool = NULL;
+ (*ctmp)->varname = cpystr(_("Titlebar Color Style"));
+ (*ctmp)->varnamep = *ctmp;
+
+ standard_radio_setup(ps, ctmp, vtmp, NULL);
+
+ new_confline(ctmp);
+ /* Blank line */
+ (*ctmp)->flags |= (CF_NOSELECT | CF_B_LINE);
+
+ new_confline(ctmp);
+ /* title before general colors */
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(dashes);
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(_("GENERAL COLORS"));
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(dashes);
+
+ for(vtmp = ps->vars; vtmp->name; vtmp++){
+ if(!color_holding_var(ps, vtmp))
+ continue;
+
+ /* If not foreground, skip it */
+ if(!srchstr(vtmp->name, "-foreground-color"))
+ continue;
+
+ /* skip this for now and include it with HEADER COLORS */
+ if(vtmp == &ps->vars[V_HEADER_GENERAL_FORE_COLOR])
+ continue;
+
+ if(!saw_first_index && !struncmp(vtmp->name, "index-", 6)){
+ saw_first_index++;
+ new_confline(ctmp); /* Blank line */
+ (*ctmp)->flags |= (CF_NOSELECT | CF_B_LINE);
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(dashes);
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(_("INDEX COLORS"));
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(dashes);
+ }
+
+ new_confline(ctmp);
+ /* Blank line */
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(ctmp)->var = vtmp;
+ if(first_line && !*first_line)
+ *first_line = *ctmp;
+
+ (*ctmp)->varnamep = *ctmp;
+ (*ctmp)->keymenu = &color_setting_keymenu;
+ (*ctmp)->help = config_help(vtmp - ps->vars, 0);
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->flags |= (CF_STARTITEM | CF_COLORSAMPLE | CF_POT_SLCTBL);
+ if(!pico_usingcolor())
+ (*ctmp)->flags |= CF_NOSELECT;
+
+ (*ctmp)->value = pretty_value(ps, *ctmp);
+ (*ctmp)->valoffset = COLOR_INDENT;
+ }
+
+ /*
+ * custom header colors
+ */
+ new_confline(ctmp); /* Blank line */
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(dashes);
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(_("HEADER COLORS"));
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(dashes);
+
+ vtmp = &ps->vars[V_HEADER_GENERAL_FORE_COLOR];
+ new_confline(ctmp);
+ /* Blank line */
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(ctmp)->var = vtmp;
+
+ (*ctmp)->varnamep = *ctmp;
+ (*ctmp)->keymenu = &color_setting_keymenu;
+ (*ctmp)->help = config_help(vtmp - ps->vars, 0);
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->flags |= (CF_STARTITEM | CF_COLORSAMPLE | CF_POT_SLCTBL);
+ if(!pico_usingcolor())
+ (*ctmp)->flags |= CF_NOSELECT;
+
+ (*ctmp)->value = pretty_value(ps, *ctmp);
+ (*ctmp)->valoffset = COLOR_INDENT;
+
+ vtmp = &ps->vars[V_VIEW_HDR_COLORS];
+ lval = LVAL(vtmp, ew);
+
+ if(lval && lval[0] && lval[0][0]){
+ for(i = 0; lval && lval[i]; i++)
+ add_header_color_line(ps, ctmp, lval[i], i);
+ }
+ else{
+ new_confline(ctmp); /* Blank line */
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(_(ADDHEADER_COMMENT));
+ (*ctmp)->valoffset = COLOR_INDENT;
+ }
+
+
+ /*
+ * custom keyword colors
+ */
+ new_confline(ctmp); /* Blank line */
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(dashes);
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(KW_COLORS_HDR);
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(dashes);
+
+
+ if(ps->keywords){
+ KEYWORD_S *kw;
+ char *name, *comment, *word;
+ int j, lv = 0, lc = 0, ltot = 0, eq_col = EQ_COL;
+
+ vtmp = &ps->vars[V_KW_COLORS];
+
+ /* first figure out widths for display */
+ for(kw = ps->keywords; kw; kw = kw->next){
+ word = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
+ i = utf8_width(word);
+ if(lv < i)
+ lv = i;
+
+ j = 0;
+ if(kw->nick && kw->kw && kw->kw[0]){
+ word = kw->kw;
+ j = utf8_width(word) + 2;
+ if(lc < j)
+ lc = j;
+ }
+
+ if(ltot < (i + (j > 2 ? j : 0)))
+ ltot = (i + (j > 2 ? j : 0));
+ }
+
+ lv = MIN(lv, 100);
+ lc = MIN(lc, 100);
+ ltot = MIN(ltot, 100);
+
+ /*
+ * SC is width of " Color"
+ * SS is space between nickname and keyword value
+ * SW is space between words and Sample
+ */
+#define SC 6
+#define SS 1
+#define SW 3
+ if(COLOR_INDENT + SC + ltot + (lc>0?SS:0) > eq_col - SW){
+ eq_col = MIN(MAX(ps->ttyo->screen_cols - 10, 20),
+ COLOR_INDENT + SC + ltot + (lc>0?SS:0) + SW);
+ if(COLOR_INDENT + SC + ltot + (lc>0?SS:0) > eq_col - SW){
+ eq_col = MIN(MAX(ps->ttyo->screen_cols - 10, 20),
+ COLOR_INDENT + SC + lv + (lc>0?SS:0) + lc + SW);
+ if(COLOR_INDENT + SC + lv + (lc>0?SS:0) + lc > eq_col - SW){
+ lc = MIN(lc, MAX(eq_col - SW - COLOR_INDENT - SC - lv - SS, 7));
+ if(COLOR_INDENT + SC + lv + (lc>0?SS:0) + lc > eq_col - SW){
+ lc = 0;
+ if(COLOR_INDENT + SC + lv > eq_col - SW){
+ lv = MAX(eq_col - SW - COLOR_INDENT - SC, 7);
+ }
+ }
+ }
+ }
+ }
+
+ lval = LVAL(vtmp, ew);
+ if(lval && lval[0] && !strcmp(lval[0], INHERIT)){
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->varnamep = *ctmp;
+ (*ctmp)->keymenu = &kw_color_setting_keymenu;
+ (*ctmp)->help = config_help(vtmp - ps->vars, 0);
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->varmem = CFC_SET_COLOR(i, 0);
+ (*ctmp)->valoffset = COLOR_INDENT;
+
+ (*ctmp)->flags = (CF_NOSELECT | CF_INHERIT);
+ }
+
+ /* now create the config lines */
+ for(kw = ps->keywords, i = 0; kw; kw = kw->next, i++){
+ char tmp[2000];
+
+ /* Blank line */
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->varnamep = *ctmp;
+ (*ctmp)->keymenu = &kw_color_setting_keymenu;
+ (*ctmp)->help = config_help(vtmp - ps->vars, 0);
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->flags |= (CF_STARTITEM | CF_COLORSAMPLE | CF_POT_SLCTBL);
+ if(!pico_usingcolor())
+ (*ctmp)->flags |= CF_NOSELECT;
+
+ /*
+ * Not actually a color in this case, it is an index into
+ * the keywords list.
+ */
+ (*ctmp)->varmem = CFC_SET_COLOR(i, 0);
+
+ name = short_str(kw->nick ? kw->nick : kw->kw ? kw->kw : "",
+ tmp_20k_buf+10000, 1000, lv, EndDots);
+ if(lc > 0 && kw->nick && kw->kw && kw->kw[0])
+ comment = short_str(kw->kw, tmp_20k_buf+11000, SIZEOF_20KBUF - 11000, lc, EndDots);
+ else
+ comment = NULL;
+
+ utf8_snprintf(tmp, sizeof(tmp), "%.*w%*s%s%.*w%s Color%*s %s%s",
+ lv, name,
+ (lc > 0 && comment) ? SS : 0, "",
+ (lc > 0 && comment) ? "(" : "",
+ (lc > 0 && comment) ? lc : 0, (lc > 0 && comment) ? comment : "",
+ (lc > 0 && comment) ? ")" : "",
+ MAX(MIN(eq_col - COLOR_INDENT - MIN(lv,utf8_width(name))
+ - SC - 1
+ - ((lc > 0 && comment)
+ ? (SS+2+MIN(lc,utf8_width(comment))) : 0), 100), 0), "",
+ sample_text(ps,vtmp),
+ color_parenthetical(vtmp));
+
+ (*ctmp)->value = cpystr(tmp);
+ (*ctmp)->valoffset = COLOR_INDENT;
+ }
+ }
+ else{
+ new_confline(ctmp); /* Blank line */
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+ new_confline(ctmp);
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(_("[ Use Setup/Config command to add Keywords ]"));
+ (*ctmp)->valoffset = COLOR_INDENT;
+ }
+}
+
+
+char *
+color_parenthetical(struct variable *var)
+{
+ int norm, exc, exc_inh;
+ char **lval, *ret = "";
+
+ if(var == &ps_global->vars[V_VIEW_HDR_COLORS]
+ || var == &ps_global->vars[V_KW_COLORS]){
+ norm = (LVAL(var, Main) != NULL);
+ exc = (LVAL(var, ps_global->ew_for_except_vars) != NULL);
+ exc_inh = ((lval=LVAL(var, ps_global->ew_for_except_vars)) &&
+ lval[0] && !strcmp(INHERIT, lval[0]));
+
+ /* editing normal but there is an exception config */
+ if((ps_global->ew_for_except_vars != Main) && (ew == Main)){
+ if((exc && !exc_inh))
+ ret = _(" (overridden by exceptions)");
+ else if(exc && exc_inh)
+ ret = _(" (more in exceptions)");
+ }
+ /* editing exception config */
+ else if((ps_global->ew_for_except_vars != Main) &&
+ (ew == ps_global->ew_for_except_vars)){
+ if(exc && exc_inh && norm)
+ ret = _(" (more in main config)");
+ }
+ }
+
+ return(ret);
+}
+
+
+void
+add_header_color_line(struct pine *ps, CONF_S **ctmp, char *val, int which)
+{
+ struct variable *vtmp;
+ SPEC_COLOR_S *hc;
+ char tmp[100+1];
+ int l;
+
+ vtmp = &ps->vars[V_VIEW_HDR_COLORS];
+ l = strlen(HEADER_WORD);
+
+ /* Blank line */
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->varnamep = *ctmp;
+ (*ctmp)->keymenu = &custom_color_setting_keymenu;
+ (*ctmp)->help = config_help(vtmp - ps->vars, 0);
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->flags |= (CF_STARTITEM | CF_COLORSAMPLE | CF_POT_SLCTBL);
+ if(!pico_usingcolor())
+ (*ctmp)->flags |= CF_NOSELECT;
+
+ /* which is an index into the variable list */
+ (*ctmp)->varmem = CFC_SET_COLOR(which, 0);
+
+ hc = spec_color_from_var(val, 0);
+ if(hc && hc->inherit)
+ (*ctmp)->flags = (CF_NOSELECT | CF_INHERIT);
+ else{
+ /*
+ * This isn't quite right because of the assumption that the
+ * first character of spec fits in one octet. I haven't tried
+ * to figure out what we're really trying to accomplish
+ * with all this. It probably doesn't happen in real life.
+ */
+ utf8_snprintf(tmp, sizeof(tmp), "%s%c%.*w Color%*w %s%s",
+ HEADER_WORD,
+ (hc && hc->spec) ? (islower((unsigned char)hc->spec[0])
+ ? toupper((unsigned char)hc->spec[0])
+ : hc->spec[0]) : '?',
+ MIN(utf8_width((hc && hc->spec && hc->spec[0]) ? hc->spec+1 : ""),30-l),
+ (hc && hc->spec && hc->spec[0]) ? hc->spec+1 : "",
+ MAX(EQ_COL - COLOR_INDENT -1 -
+ MIN(utf8_width((hc && hc->spec && hc->spec[0]) ? hc->spec+1 : ""),30-l)
+ - l - 6 - 1, 0), "",
+ sample_text(ps,vtmp),
+ color_parenthetical(vtmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ (*ctmp)->value = cpystr(tmp);
+ }
+
+ (*ctmp)->valoffset = COLOR_INDENT;
+
+ if(hc)
+ free_spec_colors(&hc);
+}
+
+
+/*
+ * Set up the standard color setting display for one color.
+ *
+ * Args fg -- the current foreground color
+ * bg -- the current background color
+ * def -- default box should be checked
+ */
+void
+add_color_setting_disp(struct pine *ps, CONF_S **ctmp, struct variable *var,
+ CONF_S *varnamep, struct key_menu *km,
+ struct key_menu *cb_km, HelpType help, int indent,
+ int which, char *fg, char *bg, int def)
+{
+ int i, lv, count, trans_is_on, transparent;
+ char tmp[100+1];
+ char *title = _("HELP FOR SETTING UP COLOR");
+ int fg_is_custom = 1, bg_is_custom = 1;
+#ifdef _WINDOWS
+ CONF_S *cl_custom = NULL;
+#else
+ char *pvalfg, *pvalbg;
+#endif
+
+
+ /* find longest value's name */
+ count = pico_count_in_color_table();
+ lv = COLOR_BLOB_LEN;
+
+ trans_is_on = pico_trans_is_on();
+
+ /* put a title before list */
+ new_confline(ctmp);
+ (*ctmp)->varnamep = varnamep;
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->valoffset = indent;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->varmem = 0;
+
+ /*
+ * The width of Foreground plus the spaces before Background should
+ * be about lv + 5 + SPACE_BETWEEN_DOUBLEVARS.
+ */
+ (*ctmp)->value = cpystr("Foreground Background");
+
+ new_confline(ctmp)->var = var;
+ (*ctmp)->varnamep = varnamep;
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->valoffset = indent;
+ (*ctmp)->flags |= (CF_COLORSAMPLE | CF_NOSELECT);
+ (*ctmp)->varmem = CFC_SET_COLOR(which, 0);
+ (*ctmp)->value = color_setting_text_line(ps, var);
+
+ for(i = 0; i < count; i++){
+ new_confline(ctmp)->var = var;
+ (*ctmp)->varnamep = varnamep;
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = help;
+ (*ctmp)->help_title = title;
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->valoffset = indent;
+ /* 5 is length of "( ) " */
+ (*ctmp)->val2offset = indent + lv + 5 + SPACE_BETWEEN_DOUBLEVARS;
+ (*ctmp)->flags |= CF_DOUBLEVAR;
+ (*ctmp)->varmem = CFC_SET_COLOR(which, i);
+
+ transparent = (trans_is_on && i == count-1);
+ if(transparent)
+ (*ctmp)->help = h_config_usetransparent_color;
+
+ if(fg_is_custom && fg && !strucmp(color_to_canonical_name(fg), colorx(i)))
+ fg_is_custom = 0;
+
+ if(bg_is_custom && bg && !strucmp(color_to_canonical_name(bg), colorx(i)))
+ bg_is_custom = 0;
+
+ (*ctmp)->value = new_color_line(transparent ? COLOR_BLOB_TRAN
+ : COLOR_BLOB,
+ fg &&
+ !strucmp(color_to_canonical_name(fg), colorx(i)),
+ bg &&
+ !strucmp(color_to_canonical_name(bg), colorx(i)),
+ lv);
+ }
+
+#ifdef _WINDOWS
+ new_confline(ctmp)->var = var;
+ (*ctmp)->varnamep = varnamep;
+ (*ctmp)->keymenu = (km == &custom_color_changing_keymenu)
+ ? &custom_rgb_keymenu :
+ (km == &kw_color_changing_keymenu)
+ ? &kw_rgb_keymenu : &color_rgb_keymenu;
+ (*ctmp)->help = help;
+ (*ctmp)->help_title = title;
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->valoffset = indent;
+ /* 5 is length of "( ) " */
+ (*ctmp)->val2offset = indent + lv + 5 + SPACE_BETWEEN_DOUBLEVARS;
+ (*ctmp)->flags |= CF_DOUBLEVAR;
+ (*ctmp)->varmem = CFC_SET_COLOR(which, i);
+ cl_custom = (*ctmp);
+#endif
+
+ if(offer_normal_color_for_var(ps, var)){
+ new_confline(ctmp)->var = var;
+ (*ctmp)->varnamep = varnamep;
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = h_config_usenormal_color;
+ (*ctmp)->help_title = title;
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->valoffset = indent;
+ /* 5 is length of "( ) " */
+ (*ctmp)->val2offset = indent + lv + 5 + SPACE_BETWEEN_DOUBLEVARS;
+ (*ctmp)->flags |= CF_DOUBLEVAR;
+ (*ctmp)->varmem = CFC_SET_COLOR(which, CFC_ICOLOR_NORM);
+ if(fg_is_custom && fg && !struncmp(fg, MATCH_NORM_COLOR, RGBLEN))
+ fg_is_custom = 0;
+
+ if(bg_is_custom && bg && !struncmp(bg, MATCH_NORM_COLOR, RGBLEN))
+ bg_is_custom = 0;
+
+ (*ctmp)->value = new_color_line(COLOR_BLOB_NORM,
+ fg && !struncmp(fg, MATCH_NORM_COLOR, RGBLEN),
+ bg && !struncmp(bg, MATCH_NORM_COLOR, RGBLEN),
+ lv);
+ }
+
+ if(offer_none_color_for_var(ps, var)){
+ new_confline(ctmp)->var = var;
+ (*ctmp)->varnamep = varnamep;
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = h_config_usenone_color;
+ (*ctmp)->help_title = title;
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->valoffset = indent;
+ /* 5 is length of "( ) " */
+ (*ctmp)->val2offset = indent + lv + 5 + SPACE_BETWEEN_DOUBLEVARS;
+ (*ctmp)->flags |= CF_DOUBLEVAR;
+ (*ctmp)->varmem = CFC_SET_COLOR(which, CFC_ICOLOR_NONE);
+ if(fg_is_custom && fg && !struncmp(fg, MATCH_NONE_COLOR, RGBLEN))
+ fg_is_custom = 0;
+
+ if(bg_is_custom && bg && !struncmp(bg, MATCH_NONE_COLOR, RGBLEN))
+ bg_is_custom = 0;
+
+ (*ctmp)->value = new_color_line(COLOR_BLOB_NONE,
+ fg && !struncmp(fg, MATCH_NONE_COLOR, RGBLEN),
+ bg && !struncmp(bg, MATCH_NONE_COLOR, RGBLEN),
+ lv);
+ }
+
+#ifdef _WINDOWS
+ if(cl_custom)
+ cl_custom->value = new_color_line("Custom", fg_is_custom, bg_is_custom, lv);
+#endif
+
+ new_confline(ctmp)->var = var;
+ (*ctmp)->varnamep = varnamep;
+ (*ctmp)->keymenu = cb_km;
+ (*ctmp)->help = h_config_dflt_color;
+ (*ctmp)->help_title = title;
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->valoffset = indent;
+ (*ctmp)->varmem = CFC_SET_COLOR(which, 0);
+#ifdef _WINDOWS
+ snprintf(tmp, sizeof(tmp), "[%c] %s", def ? 'X' : ' ', "Default");
+ tmp[sizeof(tmp)-1] = '\0';
+#else
+ pvalfg = PVAL(var,Main);
+ pvalbg = PVAL(var+1,Main);
+ if(!var->is_list &&
+ ((var == &ps->vars[V_NORM_FORE_COLOR]) ||
+ (ew == Post && pvalfg && pvalfg[0] && pvalbg && pvalbg[0]) ||
+ (var->global_val.p && var->global_val.p[0] &&
+ (var+1)->global_val.p && (var+1)->global_val.p[0])))
+ snprintf(tmp, sizeof(tmp), "[%c] %s", def ? 'X' : ' ', "Default");
+ else if(var == &ps->vars[V_REV_FORE_COLOR])
+ snprintf(tmp, sizeof(tmp), "[%c] %s", def ? 'X' : ' ',
+ "Default (terminal's standout mode, usually reverse video)");
+ else if(var == &ps->vars[V_SLCTBL_FORE_COLOR])
+ snprintf(tmp, sizeof(tmp), "[%c] %s", def ? 'X' : ' ',
+ "Default (Bold Normal Color)");
+ else if(var == &ps->vars[V_TITLECLOSED_FORE_COLOR])
+ snprintf(tmp, sizeof(tmp), "[%c] %s", def ? 'X' : ' ',
+ "Default (same as Title Color)");
+ else if(var_defaults_to_rev(var))
+ snprintf(tmp, sizeof(tmp), "[%c] %s", def ? 'X' : ' ',
+ "Default (same as Reverse Color)");
+ else if(km == &kw_color_changing_keymenu)
+ snprintf(tmp, sizeof(tmp), "[%c] %s", def ? 'X' : ' ',
+ "Default (same as Indexline Color)");
+ else
+ snprintf(tmp, sizeof(tmp), "[%c] %s", def ? 'X' : ' ',
+ "Default (same as Normal Color)");
+
+ tmp[sizeof(tmp)-1] = '\0';
+#endif
+ (*ctmp)->value = cpystr(tmp);
+
+ /*
+ * Add a checkbox to turn bold on or off for selectable-item color.
+ */
+ if(var == &ps->vars[V_SLCTBL_FORE_COLOR]){
+ char **lval;
+
+ new_confline(ctmp)->var = var;
+ (*ctmp)->varnamep = varnamep;
+ (*ctmp)->keymenu = &selectable_bold_checkbox_keymenu;
+ (*ctmp)->help = h_config_bold_slctbl;
+ (*ctmp)->help_title = title;
+ (*ctmp)->tool = color_setting_tool;
+ (*ctmp)->valoffset = indent;
+ (*ctmp)->varmem = feature_list_index(F_SLCTBL_ITEM_NOBOLD);
+
+ lval = LVAL(&ps->vars[V_FEATURE_LIST], ew);
+ /*
+ * We don't use checkbox_pretty_value here because we just want
+ * the word Bold instead of the name of the variable and because
+ * we are actually using the negative of the feature. That is,
+ * the feature is really NOBOLD and we are using Bold.
+ */
+ snprintf(tmp, sizeof(tmp), "[%c] %s",
+ test_feature(lval, feature_list_name(F_SLCTBL_ITEM_NOBOLD), 0)
+ ? ' ' : 'X', "Bold");
+ tmp[sizeof(tmp)-1] = '\0';
+
+ (*ctmp)->value = cpystr(tmp);
+ }
+}
+
+
+int
+is_rgb_color(char *color)
+{
+ int i, j;
+
+ for(i = 0; i < 3; i++){
+ if(i && *color++ != ',')
+ return(FALSE);
+
+ for(j = 0; j < 3; j++, color++)
+ if(!isdigit((unsigned char) *color))
+ return(FALSE);
+ }
+
+ return(TRUE);
+}
+
+
+char *
+new_color_line(char *color, int fg, int bg, int len)
+{
+ char tmp[256];
+
+ snprintf(tmp, sizeof(tmp), "(%c) %-*.*s%*s(%c) %-*.*s",
+ fg ? R_SELD : ' ', len, len, color, SPACE_BETWEEN_DOUBLEVARS, "",
+ bg ? R_SELD : ' ', len, len, color);
+ tmp[sizeof(tmp)-1] = '\0';
+ return(cpystr(tmp));
+}
+
+
+int
+color_text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0, i;
+ struct variable v, *save_var;
+ SPEC_COLOR_S *hc, *hcolors;
+ char *starting_val, *val, tmp[100], ***alval, **apval;
+
+ if(cmd == MC_EXIT)
+ return(simple_exit_cmd(flags));
+
+ alval = ALVAL((*cl)->var, ew);
+ if(!alval || !*alval)
+ return(rv);
+
+ hcolors = spec_colors_from_varlist(*alval, 0);
+
+ for(hc = hcolors, i=0; hc; hc = hc->next, i++)
+ if(CFC_ICUST(*cl) == i)
+ break;
+
+ starting_val = (hc && hc->val) ? pattern_to_string(hc->val) : NULL;
+
+ memset(&v, 0, sizeof(v));
+ v.is_used = 1;
+ v.is_user = 1;
+ snprintf(tmp, sizeof(tmp), "\"%c%s Pattern\"",
+ islower((unsigned char)hc->spec[0])
+ ? toupper((unsigned char)hc->spec[0])
+ : hc->spec[0],
+ hc->spec[1] ? hc->spec + 1 : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ v.name = tmp;
+ /* have to use right part of v so text_tool will work right */
+ apval = APVAL(&v, ew);
+ *apval = starting_val ? cpystr(starting_val) : NULL;
+ set_current_val(&v, FALSE, FALSE);
+
+ save_var = (*cl)->var;
+ (*cl)->var = &v;
+ rv = text_tool(ps, cmd, cl, flags);
+
+ if(rv == 1){
+ val = *apval;
+ *apval = NULL;
+ if(val)
+ removing_leading_and_trailing_white_space(val);
+
+ if(hc->val)
+ fs_give((void **)&hc->val);
+
+ hc->val = string_to_pattern(val);
+
+ (*cl)->var = save_var;
+
+ if((*alval)[CFC_ICUST(*cl)])
+ fs_give((void **)&(*alval)[CFC_ICUST(*cl)]);
+
+ (*alval)[CFC_ICUST(*cl)] = var_from_spec_color(hc);
+ set_current_color_vals(ps);
+ ps->mangled_screen = 1;
+ }
+ else
+ (*cl)->var = save_var;
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+ if(*apval)
+ fs_give((void **)apval);
+ if(v.current_val.p)
+ fs_give((void **)&v.current_val.p);
+ if(starting_val)
+ fs_give((void **)&starting_val);
+
+ return(rv);
+}
+
+
+/*
+ * Test whether or not a var is one of the vars which might have a
+ * color value stored in it.
+ *
+ * returns: 1 if it is a color var, 0 otherwise
+ */
+int
+color_holding_var(struct pine *ps, struct variable *var)
+{
+ if(treat_color_vars_as_text)
+ return(0);
+ else
+ return(var && var->name &&
+ (srchstr(var->name, "-foreground-color") ||
+ srchstr(var->name, "-background-color") ||
+ var == &ps->vars[V_VIEW_HDR_COLORS] ||
+ var == &ps->vars[V_KW_COLORS]));
+}
+
+
+/*
+ * test whether or not a var is one of the vars having to do with color
+ *
+ * returns: 1 if it is a color var, 0 otherwise
+ */
+int
+color_related_var(struct pine *ps, struct variable *var)
+{
+ return(
+#ifndef _WINDOWS
+ var == &ps->vars[V_COLOR_STYLE] ||
+#endif
+ var == &ps->vars[V_INDEX_COLOR_STYLE] ||
+ var == &ps->vars[V_TITLEBAR_COLOR_STYLE] ||
+ color_holding_var(ps, var));
+}
+
+
+int
+offer_normal_color_for_var(struct pine *ps, struct variable *var)
+{
+ return(color_holding_var(ps, var)
+ && var != &ps->vars[V_NORM_FORE_COLOR]
+ && var != &ps->vars[V_NORM_BACK_COLOR]
+ && var != &ps->vars[V_REV_FORE_COLOR]
+ && var != &ps->vars[V_REV_BACK_COLOR]
+ && var != &ps->vars[V_SLCTBL_FORE_COLOR]
+ && var != &ps->vars[V_SLCTBL_BACK_COLOR]);
+}
+
+
+int
+offer_none_color_for_var(struct pine *ps, struct variable *var)
+{
+ return(color_holding_var(ps, var)
+ && (!struncmp(var->name, "index-", 6)
+ || var == &ps->vars[V_KW_COLORS]));
+}
+
+
+int
+color_setting_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0, i, cancel = 0, deefault;
+ int curcolor, prevcolor, nextcolor, another;
+ CONF_S *ctmp, *first_line, *beg = NULL, *end = NULL,
+ *cur_beg, *cur_end, *prev_beg, *prev_end,
+ *next_beg, *next_end;
+ struct variable *v, *fgv, *bgv, *setv = NULL, *otherv;
+ SPEC_COLOR_S *hc = NULL, *new_hcolor;
+ SPEC_COLOR_S *hcolors = NULL;
+ KEYWORD_S *kw;
+ char *old_val, *confline = NULL;
+ char prompt[100], sval[MAXPATH+1];
+ char **lval, **apval, ***alval, **t;
+ char **apval1, **apval2;
+ HelpType help;
+ ESCKEY_S opts[3];
+#ifdef _WINDOWS
+ char *pval;
+#endif
+
+ sval[0] = '\0';
+
+ switch(cmd){
+ case MC_CHOICE : /* set a color */
+
+ if(((*cl)->flags & CF_VAR2 && fixed_var((*cl)->var+1, NULL, NULL)) ||
+ (!((*cl)->flags & CF_VAR2) && fixed_var((*cl)->var, NULL, NULL))){
+ if(((*cl)->var->post_user_val.p ||
+ ((*cl)->var+1)->post_user_val.p ||
+ (*cl)->var->main_user_val.p ||
+ ((*cl)->var+1)->main_user_val.p)
+ && want_to(_("Delete old unused personal option setting"),
+ 'y', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ delete_user_vals((*cl)->var);
+ delete_user_vals((*cl)->var+1);
+ q_status_message(SM_ORDER, 0, 3, _("Deleted"));
+ rv = 1;
+ }
+
+ return(rv);
+ }
+
+ fgv = (*cl)->var; /* foreground color */
+ bgv = (*cl)->var+1; /* background color */
+ v = ((*cl)->flags & CF_VAR2) ? bgv : fgv; /* var being changed */
+
+ apval = APVAL(v, ew);
+ old_val = apval ? *apval : NULL;
+
+ if(apval){
+ if(CFC_ICOLOR(*cl) < pico_count_in_color_table())
+ *apval = cpystr(colorx(CFC_ICOLOR(*cl)));
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NORM)
+ *apval = cpystr(MATCH_NORM_COLOR);
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NONE)
+ *apval = cpystr(MATCH_NONE_COLOR);
+ else if(old_val)
+ *apval = cpystr(is_rgb_color(old_val)
+ ? old_val : color_to_asciirgb(old_val));
+ else if(v->current_val.p)
+ *apval = cpystr(is_rgb_color(v->current_val.p)
+ ? v->current_val.p
+ : color_to_asciirgb(v->current_val.p));
+ else if(v == fgv)
+ *apval = cpystr(color_to_asciirgb(colorx(COL_BLACK)));
+ else
+ *apval = cpystr(color_to_asciirgb(colorx(COL_WHITE)));
+ }
+
+ if(old_val)
+ fs_give((void **)&old_val);
+
+ set_current_val(v, TRUE, FALSE);
+
+ /*
+ * If the user sets one of foreground/background and the
+ * other is not yet set, set the other.
+ */
+ if(PVAL(v, ew)){
+ if(v == fgv && !PVAL(bgv, ew)){
+ setv = bgv;
+ otherv = fgv;
+ }
+ else if(v == bgv && !PVAL(fgv, ew)){
+ setv = fgv;
+ otherv = bgv;
+ }
+ }
+
+ if(setv){
+ if((apval = APVAL(setv, ew)) != NULL){
+ if(setv->current_val.p)
+ *apval = cpystr(setv->current_val.p);
+ else if (setv == fgv && ps_global->VAR_NORM_FORE_COLOR)
+ *apval = cpystr(ps_global->VAR_NORM_FORE_COLOR);
+ else if (setv == bgv && ps_global->VAR_NORM_BACK_COLOR)
+ *apval = cpystr(ps_global->VAR_NORM_BACK_COLOR);
+ else if(!strucmp(color_to_canonical_name(otherv->current_val.p),
+ colorx(COL_WHITE)))
+ *apval = cpystr(colorx(COL_BLACK));
+ else
+ *apval = cpystr(colorx(COL_WHITE));
+ }
+
+ set_current_val(setv, TRUE, FALSE);
+ }
+
+ fix_side_effects(ps, v, 0);
+ set_current_color_vals(ps);
+
+ /*
+ * Turn on selected *'s for default selections, if any, and
+ * for ones we forced on.
+ */
+ color_update_selected(ps, *cl, PVAL(fgv, ew), PVAL(bgv, ew), TRUE);
+
+ ClearScreen();
+ rv = ps->mangled_screen = 1;
+ break;
+
+ case MC_CHOICEB : /* set a custom hdr color */
+ /*
+ * Find the SPEC_COLOR_S for header.
+ */
+ lval = LVAL((*cl)->var, ew);
+ hcolors = spec_colors_from_varlist(lval, 0);
+ for(hc = hcolors, i=0; hc; hc = hc->next, i++)
+ if(CFC_ICUST(*cl) == i)
+ break;
+
+ if(hc){
+ if((*cl)->flags & CF_VAR2){
+ old_val = hc->bg;
+ if(CFC_ICOLOR(*cl) < pico_count_in_color_table())
+ hc->bg = cpystr(colorx(CFC_ICOLOR(*cl)));
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NORM)
+ hc->bg = cpystr(MATCH_NORM_COLOR);
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NONE)
+ hc->bg = cpystr(MATCH_NONE_COLOR);
+ else if(old_val)
+ hc->bg = cpystr(is_rgb_color(old_val)
+ ? old_val
+ : color_to_asciirgb(old_val));
+ else
+ hc->bg = cpystr(color_to_asciirgb(colorx(COL_WHITE)));
+
+ if(old_val)
+ fs_give((void **) &old_val);
+
+ /*
+ * If the user sets one of foreground/background and the
+ * other is not yet set, set it.
+ */
+ if(!(hc->fg && hc->fg[0])){
+ if(hc->fg)
+ fs_give((void **)&hc->fg);
+
+ hc->fg = cpystr(ps->VAR_NORM_FORE_COLOR);
+ }
+ }
+ else{
+ old_val = hc->fg;
+
+ if(CFC_ICOLOR(*cl) < pico_count_in_color_table())
+ hc->fg = cpystr(colorx(CFC_ICOLOR(*cl)));
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NORM)
+ hc->fg = cpystr(MATCH_NORM_COLOR);
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NONE)
+ hc->fg = cpystr(MATCH_NONE_COLOR);
+ else if(old_val)
+ hc->fg = cpystr(is_rgb_color(old_val)
+ ? old_val
+ : color_to_asciirgb(old_val));
+ else
+ hc->fg = cpystr(color_to_asciirgb(colorx(COL_BLACK)));
+
+ if(old_val)
+ fs_give((void **) &old_val);
+
+ if(!(hc->bg && hc->bg[0])){
+ if(hc->bg)
+ fs_give((void **)&hc->bg);
+
+ hc->bg = cpystr(ps->VAR_NORM_BACK_COLOR);
+ }
+ }
+ }
+
+ /*
+ * Turn on selected *'s for default selections, if any, and
+ * for ones we forced on.
+ */
+ color_update_selected(ps, *cl,
+ (hc && hc->fg && hc->fg[0]
+ && hc->bg && hc->bg[0])
+ ? hc->fg : ps->VAR_NORM_FORE_COLOR,
+ (hc && hc->fg && hc->fg[0]
+ && hc->bg && hc->bg[0])
+ ? hc->bg : ps->VAR_NORM_BACK_COLOR,
+ TRUE);
+
+ if(hc && lval && lval[i]){
+ fs_give((void **)&lval[i]);
+ lval[i] = var_from_spec_color(hc);
+ }
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+
+ set_current_color_vals(ps);
+ ClearScreen();
+ rv = ps->mangled_screen = 1;
+ break;
+
+
+ case MC_CHOICEC : /* set a custom keyword color */
+ /* find keyword associated with color we are editing */
+ for(kw=ps->keywords, i=0; kw; kw=kw->next, i++)
+ if(CFC_ICUST(*cl) == i)
+ break;
+
+ if(!kw){ /* can't happen */
+ dprint((1,
+ "This can't happen, kw not set when setting keyword color\n"));
+ break;
+ }
+
+ hcolors = spec_colors_from_varlist(LVAL((*cl)->var, ew), 0);
+
+ /*
+ * Look through hcolors, derived from lval, to find this keyword
+ * and its current color.
+ */
+ for(hc = hcolors; hc; hc = hc->next)
+ if(hc->spec && ((kw->nick && !strucmp(kw->nick, hc->spec))
+ || (kw->kw && !strucmp(kw->kw, hc->spec))))
+ break;
+
+ if(!hc){ /* this keyword didn't have a color set, add to list */
+ SPEC_COLOR_S *new;
+
+ new = (SPEC_COLOR_S *) fs_get(sizeof(*hc));
+ memset((void *) new, 0, sizeof(*new));
+ new->spec = cpystr(kw->kw);
+ new->fg = cpystr(ps->VAR_NORM_FORE_COLOR);
+ new->bg = cpystr(ps->VAR_NORM_BACK_COLOR);
+
+ if(hcolors){
+ for(hc = hcolors; hc->next; hc = hc->next)
+ ;
+
+ hc->next = new;
+ }
+ else
+ hcolors = new;
+
+ hc = new;
+ }
+
+ if(hc){
+ if((*cl)->flags & CF_VAR2){
+ old_val = hc->bg;
+ if(CFC_ICOLOR(*cl) < pico_count_in_color_table())
+ hc->bg = cpystr(colorx(CFC_ICOLOR(*cl)));
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NORM)
+ hc->bg = cpystr(MATCH_NORM_COLOR);
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NONE)
+ hc->bg = cpystr(MATCH_NONE_COLOR);
+ else if(old_val)
+ hc->bg = cpystr(is_rgb_color(old_val)
+ ? old_val
+ : color_to_asciirgb(old_val));
+ else
+ hc->bg = cpystr(color_to_asciirgb(colorx(COL_WHITE)));
+
+ if(old_val)
+ fs_give((void **) &old_val);
+
+ /*
+ * If the user sets one of foreground/background and the
+ * other is not yet set, set it.
+ */
+ if(!(hc->fg && hc->fg[0])){
+ if(hc->fg)
+ fs_give((void **)&hc->fg);
+
+ hc->fg = cpystr(ps->VAR_NORM_FORE_COLOR);
+ }
+ }
+ else{
+ old_val = hc->fg;
+
+ if(CFC_ICOLOR(*cl) < pico_count_in_color_table())
+ hc->fg = cpystr(colorx(CFC_ICOLOR(*cl)));
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NORM)
+ hc->fg = cpystr(MATCH_NORM_COLOR);
+ else if(CFC_ICOLOR(*cl) == CFC_ICOLOR_NONE)
+ hc->fg = cpystr(MATCH_NONE_COLOR);
+ else if(old_val)
+ hc->fg = cpystr(is_rgb_color(old_val)
+ ? old_val
+ : color_to_asciirgb(old_val));
+ else
+ hc->fg = cpystr(color_to_asciirgb(colorx(COL_BLACK)));
+
+ if(old_val)
+ fs_give((void **) &old_val);
+
+ if(!(hc->bg && hc->bg[0])){
+ if(hc->bg)
+ fs_give((void **)&hc->bg);
+
+ hc->bg = cpystr(ps->VAR_NORM_BACK_COLOR);
+ }
+ }
+ }
+
+ /*
+ * Turn on selected *'s for default selections, if any, and
+ * for ones we forced on.
+ */
+ color_update_selected(ps, *cl,
+ (hc && hc->fg && hc->fg[0]
+ && hc->bg && hc->bg[0])
+ ? hc->fg : ps->VAR_NORM_FORE_COLOR,
+ (hc && hc->fg && hc->fg[0]
+ && hc->bg && hc->bg[0])
+ ? hc->bg : ps->VAR_NORM_BACK_COLOR,
+ TRUE);
+
+ alval = ALVAL((*cl)->var, ew);
+ free_list_array(alval);
+ *alval = varlist_from_spec_colors(hcolors);
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+
+ fix_side_effects(ps, (*cl)->var, 0);
+ set_current_color_vals(ps);
+ ClearScreen();
+ rv = ps->mangled_screen = 1;
+ break;
+
+ case MC_TOGGLE : /* toggle default on or off */
+ fgv = (*cl)->var; /* foreground color */
+ bgv = (*cl)->var+1; /* background color */
+
+ if((*cl)->value[1] == 'X'){ /* turning default off */
+ (*cl)->value[1] = ' ';
+ /*
+ * Take whatever color is the current_val and suck it
+ * into the user_val. Same colors remain checked.
+ */
+ apval1 = APVAL(fgv, ew);
+ if(apval1){
+ if(*apval1)
+ fs_give((void **)apval1);
+ }
+
+ apval2 = APVAL(bgv, ew);
+ if(apval2){
+ if(*apval2)
+ fs_give((void **)apval2);
+ }
+
+ /* editing normal but there is an exception config */
+ if((ps->ew_for_except_vars != Main) && (ew == Main)){
+ COLOR_PAIR *newc;
+
+ /* use global_val if it is set */
+ if(fgv && fgv->global_val.p && fgv->global_val.p[0] &&
+ bgv && bgv->global_val.p && bgv->global_val.p[0]){
+ *apval1 = cpystr(fgv->global_val.p);
+ *apval2 = cpystr(bgv->global_val.p);
+ }
+ else if(var_defaults_to_rev(fgv) &&
+ (newc = pico_get_rev_color())){
+ *apval1 = cpystr(newc->fg);
+ *apval2 = cpystr(newc->bg);
+ }
+ else{
+ *apval1 = cpystr(ps->VAR_NORM_FORE_COLOR);
+ *apval2 = cpystr(ps->VAR_NORM_BACK_COLOR);
+ }
+ }
+ else{ /* editing outermost config */
+ /* just use current_vals */
+ if(fgv->current_val.p)
+ *apval1 = cpystr(fgv->current_val.p);
+ if(bgv->current_val.p)
+ *apval2 = cpystr(bgv->current_val.p);
+ }
+ }
+ else{ /* turning default on */
+ char *starred_fg = NULL, *starred_bg = NULL;
+
+ (*cl)->value[1] = 'X';
+ apval = APVAL(fgv, ew);
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ apval = APVAL(bgv, ew);
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(fgv->cmdline_val.p)
+ fs_give((void **)&fgv->cmdline_val.p);
+
+ if(bgv->cmdline_val.p)
+ fs_give((void **)&bgv->cmdline_val.p);
+
+ set_current_color_vals(ps);
+
+ if(fgv == &ps->vars[V_SLCTBL_FORE_COLOR]){
+ F_TURN_OFF(F_SLCTBL_ITEM_NOBOLD, ps);
+ (*cl)->next->value[1] = 'X';
+ }
+
+ /* editing normal but there is an exception config */
+ if((ps->ew_for_except_vars != Main) && (ew == Main)){
+ COLOR_PAIR *newc;
+
+ /* use global_val if it is set */
+ if(fgv && fgv->global_val.p && fgv->global_val.p[0] &&
+ bgv && bgv->global_val.p && bgv->global_val.p[0]){
+ starred_fg = fgv->global_val.p;
+ starred_bg = bgv->global_val.p;
+ }
+ else if(var_defaults_to_rev(fgv) &&
+ (newc = pico_get_rev_color())){
+ starred_fg = newc->fg;
+ starred_bg = newc->bg;
+ }
+ else{
+ starred_fg = ps->VAR_NORM_FORE_COLOR;
+ starred_bg = ps->VAR_NORM_BACK_COLOR;
+ }
+ }
+ else{ /* editing outermost config */
+ starred_fg = fgv->current_val.p;
+ starred_bg = bgv->current_val.p;
+ }
+
+ /*
+ * Turn on selected *'s for default selections.
+ */
+ color_update_selected(ps, prev_confline(*cl),
+ starred_fg, starred_bg, FALSE);
+
+ ps->mangled_body = 1;
+ }
+
+ fix_side_effects(ps, fgv, 0);
+ rv = 1;
+ break;
+
+ case MC_TOGGLEB : /* toggle default on or off, hdr color */
+ /*
+ * Find the SPEC_COLOR_S for header.
+ */
+ rv = 1;
+ lval = LVAL((*cl)->var, ew);
+ hcolors = spec_colors_from_varlist(lval, 0);
+ for(hc = hcolors, i=0; hc; hc = hc->next, i++)
+ if(CFC_ICUST(*cl) == i)
+ break;
+
+ if((*cl)->value[1] == 'X'){ /* turning default off */
+ (*cl)->value[1] = ' ';
+ /*
+ * Take whatever color is the default value and suck it
+ * into the hc structure.
+ */
+ if(hc){
+ if(hc->bg)
+ fs_give((void **)&hc->bg);
+ if(hc->fg)
+ fs_give((void **)&hc->fg);
+
+ if(ps->VAR_NORM_FORE_COLOR &&
+ ps->VAR_NORM_FORE_COLOR[0] &&
+ ps->VAR_NORM_BACK_COLOR &&
+ ps->VAR_NORM_BACK_COLOR[0]){
+ hc->fg = cpystr(ps->VAR_NORM_FORE_COLOR);
+ hc->bg = cpystr(ps->VAR_NORM_BACK_COLOR);
+ }
+
+ if(lval && lval[i]){
+ fs_give((void **)&lval[i]);
+ lval[i] = var_from_spec_color(hc);
+ }
+ }
+ }
+ else{ /* turning default on */
+ (*cl)->value[1] = 'X';
+ /* Remove current colors, leaving val */
+ if(hc){
+ if(hc->bg)
+ fs_give((void **)&hc->bg);
+ if(hc->fg)
+ fs_give((void **)&hc->fg);
+
+ if(lval && lval[i]){
+ fs_give((void **)&lval[i]);
+ lval[i] = var_from_spec_color(hc);
+ }
+ }
+
+ set_current_color_vals(ps);
+ ClearScreen();
+ ps->mangled_screen = 1;
+
+ }
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+
+ /*
+ * Turn on selected *'s for default selections.
+ */
+ color_update_selected(ps, prev_confline(*cl),
+ ps->VAR_NORM_FORE_COLOR,
+ ps->VAR_NORM_BACK_COLOR,
+ FALSE);
+
+ break;
+
+ case MC_TOGGLEC : /* toggle default on or off, keyword color */
+ for(kw=ps->keywords, i=0; kw; kw=kw->next, i++)
+ if(CFC_ICUST(*cl) == i)
+ break;
+
+ if(!kw){ /* can't happen */
+ dprint((1,
+ "This can't happen, kw not set when togglec keyword color\n"));
+ break;
+ }
+
+ hcolors = spec_colors_from_varlist(LVAL((*cl)->var, ew), 0);
+
+ /*
+ * Look through hcolors, derived from lval, to find this keyword
+ * and its current color.
+ */
+ for(hc = hcolors; hc; hc = hc->next)
+ if(hc->spec && ((kw->nick && !strucmp(kw->nick, hc->spec))
+ || (kw->kw && !strucmp(kw->kw, hc->spec))))
+ break;
+
+ /* Remove this color from list */
+ if(hc){
+ SPEC_COLOR_S *tmp;
+
+ if(hc == hcolors){
+ hcolors = hc->next;
+ hc->next = NULL;
+ free_spec_colors(&hc);
+ }
+ else{
+ for(tmp = hcolors; tmp->next; tmp = tmp->next)
+ if(tmp->next == hc)
+ break;
+
+ if(tmp->next){
+ tmp->next = hc->next;
+ hc->next = NULL;
+ free_spec_colors(&hc);
+ }
+ }
+ }
+
+ if((*cl)->value[1] == 'X')
+ (*cl)->value[1] = ' ';
+ else
+ (*cl)->value[1] = 'X';
+
+ /*
+ * Turn on selected *'s for default selections, if any, and
+ * for ones we forced on.
+ */
+ color_update_selected(ps, prev_confline(*cl),
+ ps->VAR_NORM_FORE_COLOR,
+ ps->VAR_NORM_BACK_COLOR,
+ FALSE);
+
+ alval = ALVAL((*cl)->var, ew);
+ free_list_array(alval);
+ *alval = varlist_from_spec_colors(hcolors);
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+
+ fix_side_effects(ps, (*cl)->var, 0);
+ set_current_color_vals(ps);
+ ClearScreen();
+ rv = ps->mangled_screen = 1;
+ break;
+
+ case MC_TOGGLED : /* toggle selectable item bold on or off */
+ toggle_feature_bit(ps, feature_list_index(F_SLCTBL_ITEM_NOBOLD),
+ &ps->vars[V_FEATURE_LIST], *cl, 1);
+ ps->mangled_body = 1; /* to fix Sample Text */
+ rv = 1;
+ break;
+
+ case MC_DEFAULT : /* restore default values */
+
+ /* First, confirm that user wants to restore all default colors */
+ if(want_to(_("Really restore all colors to default values"),
+ 'y', 'n', NO_HELP, WT_NORM) != 'y'){
+ cmd_cancelled("RestoreDefs");
+ return(rv);
+ }
+
+ /* get rid of all user set colors */
+ for(v = ps->vars; v->name; v++){
+ if(!color_holding_var(ps, v)
+ || v == &ps->vars[V_VIEW_HDR_COLORS]
+ || v == &ps->vars[V_KW_COLORS])
+ continue;
+
+ apval = APVAL(v, ew);
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(v->cmdline_val.p)
+ fs_give((void **)&v->cmdline_val.p);
+ }
+
+ /*
+ * For custom header colors, we want to remove the color values
+ * but leave the spec value so that it is easy to reset.
+ */
+ alval = ALVAL(&ps->vars[V_VIEW_HDR_COLORS], ew);
+ if(alval && *alval){
+ SPEC_COLOR_S *global_hcolors = NULL, *hcg;
+
+ v = &ps->vars[V_VIEW_HDR_COLORS];
+ if(v->global_val.l && v->global_val.l[0])
+ global_hcolors = spec_colors_from_varlist(v->global_val.l, 0);
+
+ hcolors = spec_colors_from_varlist(*alval, 0);
+ for(hc = hcolors; hc; hc = hc->next){
+ if(hc->fg)
+ fs_give((void **)&hc->fg);
+ if(hc->bg)
+ fs_give((void **)&hc->bg);
+
+ for(hcg = global_hcolors; hcg; hcg = hcg->next){
+ if(hc->spec && hcg->spec && !strucmp(hc->spec, hcg->spec)){
+ hc->fg = hcg->fg;
+ hcg->fg = NULL;
+ hc->bg = hcg->bg;
+ hcg->bg = NULL;
+ if(hc->val && !hcg->val)
+ fs_give((void **) &hc->val);
+ }
+ }
+
+ if(global_hcolors)
+ free_spec_colors(&global_hcolors);
+ }
+
+ free_list_array(alval);
+ *alval = varlist_from_spec_colors(hcolors);
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+ }
+
+ /*
+ * Same for keyword colors.
+ */
+ alval = ALVAL(&ps->vars[V_KW_COLORS], ew);
+ if(alval && *alval){
+ hcolors = spec_colors_from_varlist(*alval, 0);
+ for(hc = hcolors; hc; hc = hc->next){
+ if(hc->fg)
+ fs_give((void **)&hc->fg);
+ if(hc->bg)
+ fs_give((void **)&hc->bg);
+ }
+
+ free_list_array(alval);
+ *alval = varlist_from_spec_colors(hcolors);
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+ }
+
+ /* set bold for selectable items */
+ F_TURN_OFF(F_SLCTBL_ITEM_NOBOLD, ps);
+ lval = LVAL(&ps->vars[V_FEATURE_LIST], ew);
+ if(test_feature(lval, feature_list_name(F_SLCTBL_ITEM_NOBOLD), 0))
+ toggle_feature_bit(ps, feature_list_index(F_SLCTBL_ITEM_NOBOLD),
+ &ps->vars[V_FEATURE_LIST], *cl, 1);
+
+ set_current_color_vals(ps);
+ clear_index_cache(ps->mail_stream, 0);
+
+ /* redo config display */
+ *cl = first_confline(*cl);
+ free_conflines(cl);
+ opt_screen->top_line = NULL;
+ first_line = NULL;
+ color_config_init_display(ps, cl, &first_line);
+ *cl = first_line;
+ ClearScreen();
+ ps->mangled_screen = 1;
+ rv = 1;
+ break;
+
+ case MC_ADD : /* add custom header color */
+ /* get header field name */
+ help = NO_HELP;
+ while(1){
+ i = optionally_enter(sval, -FOOTER_ROWS(ps), 0, sizeof(sval),
+ _("Enter the name of the header field to be added: "),
+ NULL, help, NULL);
+ if(i == 0)
+ break;
+ else if(i == 1){
+ cmd_cancelled("Add");
+ cancel = 1;
+ break;
+ }
+ else if(i == 3){
+ help = help == NO_HELP ? h_config_add_custom_color : NO_HELP;
+ continue;
+ }
+ else
+ break;
+ }
+
+ ps->mangled_footer = 1;
+
+ removing_leading_and_trailing_white_space(sval);
+ if(sval[strlen(sval)-1] == ':') /* remove trailing colon */
+ sval[strlen(sval)-1] = '\0';
+
+ removing_trailing_white_space(sval);
+
+ if(cancel || !sval[0])
+ break;
+
+ new_hcolor = (SPEC_COLOR_S *)fs_get(sizeof(*new_hcolor));
+ memset((void *)new_hcolor, 0, sizeof(*new_hcolor));
+ new_hcolor->spec = cpystr(sval);
+ confline = var_from_spec_color(new_hcolor);
+
+ /* add it to end of list */
+ alval = ALVAL(&ps->vars[V_VIEW_HDR_COLORS], ew);
+ if(alval){
+ /* get rid of possible empty value first */
+ if((t = *alval) && t[0] && !t[0][0] && !(t+1)[0])
+ free_list_array(alval);
+
+ for(t = *alval, i=0; t && t[0]; t++)
+ i++;
+
+ if(i)
+ fs_resize((void **)alval, sizeof(char *) * (i+1+1));
+ else
+ *alval = (char **)fs_get(sizeof(char *) * (i+1+1));
+
+ (*alval)[i] = confline;
+ (*alval)[i+1] = NULL;
+ }
+
+ set_current_color_vals(ps);
+
+ /* go to end of display */
+ for(ctmp = *cl; ctmp && ctmp->next; ctmp = next_confline(ctmp))
+ ;
+
+ /* back up to the KEYWORD COLORS title line */
+ for(; ctmp && (!ctmp->value || strcmp(ctmp->value, KW_COLORS_HDR))
+ && ctmp->prev;
+ ctmp = prev_confline(ctmp))
+ ;
+
+ /*
+ * Back up to last header line, or comment line if no header lines.
+ * One of many in a long line of dangerous moves in the config
+ * screens.
+ */
+ ctmp = prev_confline(ctmp); /* ------- line */
+ ctmp = prev_confline(ctmp); /* blank line */
+ ctmp = prev_confline(ctmp); /* the line */
+
+ *cl = ctmp;
+
+ /* delete the comment line if there were no headers before this */
+ if(i == 0){
+ beg = ctmp->prev;
+ end = ctmp;
+
+ *cl = beg ? beg->prev : NULL;
+
+ if(beg && beg->prev) /* this will always be true */
+ beg->prev->next = end ? end->next : NULL;
+
+ if(end && end->next)
+ end->next->prev = beg ? beg->prev : NULL;
+
+ if(end)
+ end->next = NULL;
+
+ if(beg == opt_screen->top_line || end == opt_screen->top_line)
+ opt_screen->top_line = NULL;
+
+ free_conflines(&beg);
+ }
+
+ add_header_color_line(ps, cl, confline, i);
+
+ /* be sure current is on selectable line */
+ for(; *cl && ((*cl)->flags & CF_NOSELECT); *cl = next_confline(*cl))
+ ;
+ for(; *cl && ((*cl)->flags & CF_NOSELECT); *cl = prev_confline(*cl))
+ ;
+
+ rv = ps->mangled_body = 1;
+ break;
+
+ case MC_DELETE : /* delete custom header color */
+ if((*cl)->var != &ps->vars[V_VIEW_HDR_COLORS]){
+ q_status_message(SM_ORDER, 0, 2,
+ _("Can't delete this color setting"));
+ break;
+ }
+
+ if(want_to(_("Really delete header color from config"),
+ 'y', 'n', NO_HELP, WT_NORM) != 'y'){
+ cmd_cancelled("Delete");
+ return(rv);
+ }
+
+ alval = ALVAL((*cl)->var, ew);
+ if(alval){
+ int n, j;
+
+ for(t = *alval, n=0; t && t[0]; t++)
+ n++;
+
+ j = CFC_ICUST(*cl);
+
+ if(n > j){ /* it better be */
+ if((*alval)[j])
+ fs_give((void **)&(*alval)[j]);
+
+ for(i = j; i < n; i++)
+ (*alval)[i] = (*alval)[i+1];
+ }
+ }
+
+ set_current_color_vals(ps);
+
+ /*
+ * Note the conf lines that go with this header. That's the
+ * blank line before and the current line.
+ */
+ beg = (*cl)->prev;
+ end = *cl;
+
+ another = 0;
+ /* reset current line */
+ if(end && end->next && end->next->next &&
+ end->next->next->var == &ps->vars[V_VIEW_HDR_COLORS]){
+ *cl = end->next->next; /* next Header Color */
+ another++;
+ }
+ else if(beg && beg->prev &&
+ beg->prev->var == &ps->vars[V_VIEW_HDR_COLORS]){
+ *cl = beg->prev; /* prev Header Color */
+ another++;
+ }
+
+ /* adjust SPEC_COLOR_S index (varmem) values */
+ for(ctmp = end; ctmp; ctmp = next_confline(ctmp))
+ if(ctmp->var == &ps->vars[V_VIEW_HDR_COLORS])
+ ctmp->varmem = CFC_ICUST_DEC(ctmp);
+
+ /*
+ * If that was the last header color line, add in the comment
+ * line placeholder. If there is another, just delete the
+ * old conf lines.
+ */
+ if(another){
+ if(beg && beg->prev) /* this will always be true */
+ beg->prev->next = end ? end->next : NULL;
+
+ if(end && end->next)
+ end->next->prev = beg ? beg->prev : NULL;
+
+ if(end)
+ end->next = NULL;
+
+ if(beg == opt_screen->top_line || end == opt_screen->top_line)
+ opt_screen->top_line = NULL;
+
+ free_conflines(&beg);
+ }
+ else if(end){
+ if(end->varname)
+ fs_give((void **) &end->varname);
+
+ if(end->value)
+ fs_give((void **) &end->value);
+
+ end->flags = CF_NOSELECT;
+ end->help = NO_HELP;
+ end->value = cpystr(_(ADDHEADER_COMMENT));
+ end->valoffset = COLOR_INDENT;
+ end->varnamep = NULL;
+ end->varmem = 0;
+ end->keymenu = NULL;
+ end->tool = NULL;
+ }
+
+ /* if not selectable, find next selectable line */
+ for(; *cl && ((*cl)->flags & CF_NOSELECT) && next_confline(*cl); *cl = next_confline(*cl))
+ ;
+ /* if no next selectable line, search backwards for one */
+ for(; *cl && ((*cl)->flags & CF_NOSELECT) && prev_confline(*cl); *cl = prev_confline(*cl))
+ ;
+
+ rv = ps->mangled_body = 1;
+ q_status_message(SM_ORDER, 0, 3, _("header color deleted"));
+ break;
+
+ case MC_SHUFFLE : /* shuffle order of custom headers */
+ if((*cl)->var != &ps->vars[V_VIEW_HDR_COLORS]){
+ q_status_message(SM_ORDER, 0, 2,
+ _("Can't shuffle this color setting"));
+ break;
+ }
+
+ alval = ALVAL((*cl)->var, ew);
+ if(!alval)
+ return(rv);
+
+ curcolor = CFC_ICUST(*cl);
+ prevcolor = curcolor-1;
+ nextcolor = curcolor+1;
+ if(!*alval || !(*alval)[nextcolor])
+ nextcolor = -1;
+
+ if((prevcolor < 0 && nextcolor < 0) || !*alval){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Shuffle only makes sense when there is more than one Header Color defined"));
+ return(rv);
+ }
+
+ /* Move it up or down? */
+ i = 0;
+ opts[i].ch = 'u';
+ opts[i].rval = 'u';
+ opts[i].name = "U";
+ opts[i++].label = _("Up");
+
+ opts[i].ch = 'd';
+ opts[i].rval = 'd';
+ opts[i].name = "D";
+ opts[i++].label = _("Down");
+
+ opts[i].ch = -1;
+ deefault = 'u';
+
+ if(prevcolor < 0){ /* no up */
+ opts[0].ch = -2;
+ deefault = 'd';
+ }
+ else if(nextcolor < 0)
+ opts[1].ch = -2; /* no down */
+
+ snprintf(prompt, sizeof(prompt), _("Shuffle %s%s%s ? "),
+ (opts[0].ch != -2) ? _("UP") : "",
+ (opts[0].ch != -2 && opts[1].ch != -2) ? " or " : "",
+ (opts[1].ch != -2) ? _("DOWN") : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ help = (opts[0].ch == -2) ? h_hdrcolor_shuf_down
+ : (opts[1].ch == -2) ? h_hdrcolor_shuf_up
+ : h_hdrcolor_shuf;
+
+ i = radio_buttons(prompt, -FOOTER_ROWS(ps), opts, deefault, 'x',
+ help, RB_NORM);
+
+ switch(i){
+ case 'x':
+ cmd_cancelled("Shuffle");
+ return(rv);
+
+ case 'u':
+ case 'd':
+ break;
+ }
+
+ /* swap order */
+ if(i == 'd'){
+ old_val = (*alval)[curcolor];
+ (*alval)[curcolor] = (*alval)[nextcolor];
+ (*alval)[nextcolor] = old_val;
+ }
+ else if(i == 'u'){
+ old_val = (*alval)[curcolor];
+ (*alval)[curcolor] = (*alval)[prevcolor];
+ (*alval)[prevcolor] = old_val;
+ }
+ else /* can't happen */
+ return(rv);
+
+ set_current_color_vals(ps);
+
+ /*
+ * Swap the conf lines.
+ */
+
+ cur_beg = (*cl)->prev;
+ cur_end = *cl;
+
+ if(i == 'd'){
+ next_beg = cur_end->next;
+ next_end = next_beg ? next_beg->next : NULL;
+
+ if(next_end->next)
+ next_end->next->prev = cur_end;
+ cur_end->next = next_end->next;
+ next_end->next = cur_beg;
+ if(cur_beg->prev)
+ cur_beg->prev->next = next_beg;
+ next_beg->prev = cur_beg->prev;
+ cur_beg->prev = next_end;
+
+ /* adjust SPEC_COLOR_S index values */
+ cur_beg->varmem = CFC_ICUST_INC(cur_beg);
+ cur_beg->next->varmem = CFC_ICUST_INC(cur_beg->next);
+
+ next_beg->varmem = CFC_ICUST_DEC(next_beg);
+ next_beg->next->varmem = CFC_ICUST_DEC(next_beg->next);
+
+ if(opt_screen->top_line == cur_end)
+ opt_screen->top_line = next_end;
+ else if(opt_screen->top_line == cur_beg)
+ opt_screen->top_line = next_beg;
+ }
+ else{
+ prev_end = cur_beg->prev;
+ prev_beg = prev_end ? prev_end->prev : NULL;
+
+ if(prev_beg && prev_beg->prev)
+ prev_beg->prev->next = cur_beg;
+ cur_beg->prev = prev_beg->prev;
+ prev_beg->prev = cur_end;
+ if(cur_end->next)
+ cur_end->next->prev = prev_end;
+ prev_end->next = cur_end->next;
+ cur_end->next = prev_beg;
+
+ /* adjust SPEC_COLOR_S index values */
+ cur_beg->varmem = CFC_ICUST_DEC(cur_beg);
+ cur_beg->next->varmem = CFC_ICUST_DEC(cur_beg->next);
+
+ prev_beg->varmem = CFC_ICUST_INC(prev_beg);
+ prev_beg->next->varmem = CFC_ICUST_INC(prev_beg->next);
+
+ if(opt_screen->top_line == prev_end)
+ opt_screen->top_line = cur_end;
+ else if(opt_screen->top_line == prev_beg)
+ opt_screen->top_line = cur_beg;
+ }
+
+ rv = ps->mangled_body = 1;
+ q_status_message(SM_ORDER, 0, 3, _("Header Colors shuffled"));
+ break;
+
+ case MC_EDIT:
+ rv = color_edit_screen(ps, cl);
+ if((*cl)->value && (*cl)->var &&
+ srchstr((*cl)->var->name, "-foreground-color")){
+ fs_give((void **)&(*cl)->value);
+ (*cl)->value = pretty_value(ps, *cl);
+ }
+
+ break;
+
+ case MC_EXIT: /* exit */
+ if((*cl)->keymenu == &color_changing_keymenu ||
+ (*cl)->keymenu == &kw_color_changing_keymenu ||
+ (*cl)->keymenu == &custom_color_changing_keymenu ||
+ ((*cl)->prev &&
+ ((*cl)->prev->keymenu == &color_changing_keymenu ||
+ (*cl)->prev->keymenu == &kw_color_changing_keymenu ||
+ (*cl)->prev->keymenu == &custom_color_changing_keymenu)) ||
+ ((*cl)->prev->prev &&
+ ((*cl)->prev->prev->keymenu == &color_changing_keymenu ||
+ (*cl)->prev->prev->keymenu == &kw_color_changing_keymenu ||
+ (*cl)->prev->prev->keymenu == &custom_color_changing_keymenu)))
+ rv = simple_exit_cmd(flags);
+ else
+ rv = config_exit_cmd(flags);
+
+ break;
+
+#ifdef _WINDOWS
+ case MC_RGB1 :
+ fgv = (*cl)->var;
+ bgv = (*cl)->var+1;
+ v = (*cl)->var;
+ if((*cl)->flags & CF_VAR2)
+ v += 1;
+
+ pval = PVAL(v, ew);
+ apval = APVAL(v, ew);
+ if(old_val = mswin_rgbchoice(pval ? pval : v->current_val.p)){
+ if(*apval)
+ fs_give((void **)apval);
+
+ *apval = old_val;
+ set_current_val(v, TRUE, FALSE);
+ fix_side_effects(ps, v, 0);
+ set_current_color_vals(ps);
+ color_update_selected(ps, *cl, PVAL(fgv, ew), PVAL(bgv, ew), TRUE);
+ rv = ps->mangled_screen = 1;
+ }
+
+ break;
+
+ case MC_RGB2 :
+ /*
+ * Find the SPEC_COLOR_S for header.
+ */
+ alval = ALVAL((*cl)->var, ew);
+ hcolors = spec_colors_from_varlist(*alval, 0);
+
+ for(hc = hcolors, i = 0; hc; hc = hc->next, i++)
+ if(CFC_ICUST(*cl) == i){
+ char **pc = ((*cl)->flags & CF_VAR2) ? &hc->bg : &hc->fg;
+
+ if(old_val = mswin_rgbchoice(*pc)){
+ fs_give((void **) pc);
+ *pc = old_val;
+ color_update_selected(ps, *cl,
+ (hc->fg && hc->fg[0]
+ && hc->bg && hc->bg[0])
+ ? hc->fg : ps->VAR_NORM_FORE_COLOR,
+ (hc->fg && hc->fg[0]
+ && hc->bg && hc->bg[0])
+ ? hc->bg : ps->VAR_NORM_BACK_COLOR,
+ TRUE);
+
+ if(hc && *alval && (*alval)[i]){
+ fs_give((void **)&(*alval)[i]);
+ (*alval)[i] = var_from_spec_color(hc);
+ }
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+
+ set_current_color_vals(ps);
+ ClearScreen();
+ rv = ps->mangled_screen = 1;
+ }
+
+ break;
+ }
+
+ break;
+
+ case MC_RGB3 :
+ /*
+ * Custom colored keywords.
+ */
+ for(kw=ps->keywords, i=0; kw; kw=kw->next, i++)
+ if(CFC_ICUST(*cl) == i)
+ break;
+
+ if(!kw){ /* can't happen */
+ dprint((1,
+ "This can't happen, kw not set in MC_RGB3\n"));
+ break;
+ }
+
+ hcolors = spec_colors_from_varlist(LVAL((*cl)->var, ew), 0);
+
+ /*
+ * Look through hcolors, derived from lval, to find this keyword
+ * and its current color.
+ */
+ for(hc = hcolors; hc; hc = hc->next)
+ if(hc->spec && ((kw->nick && !strucmp(kw->nick, hc->spec))
+ || (kw->kw && !strucmp(kw->kw, hc->spec))))
+ break;
+
+ if(!hc){ /* this keyword didn't have a color set, add to list */
+ SPEC_COLOR_S *new;
+
+ new = (SPEC_COLOR_S *) fs_get(sizeof(*hc));
+ memset((void *) new, 0, sizeof(*new));
+ new->spec = cpystr(kw->kw);
+ new->fg = cpystr(ps->VAR_NORM_FORE_COLOR);
+ new->bg = cpystr(ps->VAR_NORM_BACK_COLOR);
+
+ if(hcolors){
+ for(hc = hcolors; hc->next; hc = hc->next)
+ ;
+
+ hc->next = new;
+ }
+ else
+ hcolors = new;
+
+ hc = new;
+ }
+
+ if(hc){
+ char **pc = ((*cl)->flags & CF_VAR2) ? &hc->bg : &hc->fg;
+
+ if(old_val = mswin_rgbchoice(*pc)){
+ fs_give((void **) pc);
+ *pc = old_val;
+
+ /*
+ * Turn on selected *'s for default selections, if any, and
+ * for ones we forced on.
+ */
+ color_update_selected(ps, *cl,
+ (hc && hc->fg && hc->fg[0]
+ && hc->bg && hc->bg[0])
+ ? hc->fg : ps->VAR_NORM_FORE_COLOR,
+ (hc && hc->fg && hc->fg[0]
+ && hc->bg && hc->bg[0])
+ ? hc->bg : ps->VAR_NORM_BACK_COLOR,
+ TRUE);
+
+ alval = ALVAL((*cl)->var, ew);
+ free_list_array(alval);
+ *alval = varlist_from_spec_colors(hcolors);
+ fix_side_effects(ps, (*cl)->var, 0);
+ set_current_color_vals(ps);
+ ClearScreen();
+ rv = 1;
+ }
+ }
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+
+ ps->mangled_screen = 1;
+ break;
+#endif
+
+ default :
+ rv = -1;
+ break;
+ }
+
+
+ if(rv == 1)
+ exception_override_warning((*cl)->var);
+
+ return(rv);
+}
+
+
+/*
+ * Turn on selected *'s for default selections, if any, and
+ * for ones we forced on.
+ * Adjust the Sample line right above the color selection lines.
+ */
+void
+color_update_selected(struct pine *ps, CONF_S *cl, char *fg, char *bg, int cleardef)
+{
+ int i, fg_is_custom = 1, bg_is_custom = 1;
+#ifdef _WINDOWS
+ CONF_S *cl_custom = NULL;
+#endif
+
+ /* back up to header line */
+ for(; cl && (cl->flags & CF_DOUBLEVAR); cl = prev_confline(cl))
+ ;
+
+ /* adjust sample line */
+ if(cl && cl->var && cl->flags & CF_COLORSAMPLE){
+ if(cl->value)
+ fs_give((void **)&cl->value);
+
+ cl->value = color_setting_text_line(ps, cl->var);
+ }
+
+ for(i = 0, cl = next_confline(cl);
+ i < pico_count_in_color_table() && cl;
+ i++, cl = next_confline(cl)){
+ if(fg && !strucmp(color_to_canonical_name(fg), colorx(i))){
+ cl->value[1] = R_SELD;
+ fg_is_custom = 0;
+ }
+ else
+ cl->value[1] = ' ';
+
+ if(bg && !strucmp(color_to_canonical_name(bg), colorx(i))){
+ cl->value[cl->val2offset - cl->valoffset + 1] = R_SELD;
+ bg_is_custom = 0;
+ }
+ else
+ cl->value[cl->val2offset - cl->valoffset + 1] = ' ';
+ }
+
+#ifdef _WINDOWS
+ cl_custom = cl;
+ cl = next_confline(cl);
+#endif
+
+ if(cl && cl->var && offer_normal_color_for_var(ps, cl->var)){
+ if(fg && !struncmp(color_to_canonical_name(fg), MATCH_NORM_COLOR, RGBLEN)){
+ cl->value[1] = R_SELD;
+ fg_is_custom = 0;
+ }
+ else
+ cl->value[1] = ' ';
+
+ if(bg && !struncmp(color_to_canonical_name(bg), MATCH_NORM_COLOR, RGBLEN)){
+ cl->value[cl->val2offset - cl->valoffset + 1] = R_SELD;
+ bg_is_custom = 0;
+ }
+ else
+ cl->value[cl->val2offset - cl->valoffset + 1] = ' ';
+
+ cl = next_confline(cl);
+ }
+
+ if(cl && cl->var && offer_none_color_for_var(ps, cl->var)){
+ if(fg && !struncmp(color_to_canonical_name(fg), MATCH_NONE_COLOR, RGBLEN)){
+ cl->value[1] = R_SELD;
+ fg_is_custom = 0;
+ }
+ else
+ cl->value[1] = ' ';
+
+ if(bg && !struncmp(color_to_canonical_name(bg), MATCH_NONE_COLOR, RGBLEN)){
+ cl->value[cl->val2offset - cl->valoffset + 1] = R_SELD;
+ bg_is_custom = 0;
+ }
+ else
+ cl->value[cl->val2offset - cl->valoffset + 1] = ' ';
+
+ cl = next_confline(cl);
+ }
+
+ /* Turn off Default X */
+ if(cleardef)
+ cl->value[1] = ' ';
+
+#ifdef _WINDOWS
+ /* check for a custom setting */
+ if(cl_custom){
+ cl_custom->value[1] = fg_is_custom ? R_SELD : ' ';
+ cl_custom->value[cl_custom->val2offset - cl_custom->valoffset + 1]
+ = bg_is_custom ? R_SELD : ' ';
+ }
+#endif
+}
+
+
+int
+color_edit_screen(struct pine *ps, CONF_S **cl)
+{
+ OPT_SCREEN_S screen, *saved_screen;
+ CONF_S *ctmp = NULL, *first_line = NULL, *ctmpb;
+ int rv, is_index = 0, is_hdrcolor = 0, indent = 12;
+ int is_general = 0, is_keywordcol = 0;
+ char tmp[1200+1], name[1200], *p;
+ struct variable *vtmp, v;
+ int i, def;
+ COLOR_PAIR *color = NULL;
+ SPEC_COLOR_S *hc = NULL, *hcolors = NULL;
+ KEYWORD_S *kw;
+
+ vtmp = (*cl)->var;
+ if(vtmp == &ps->vars[V_VIEW_HDR_COLORS])
+ is_hdrcolor++;
+ else if(vtmp == &ps->vars[V_KW_COLORS])
+ is_keywordcol++;
+ else if(color_holding_var(ps, vtmp)){
+ if(!struncmp(vtmp->name, "index-", 6))
+ is_index++;
+ else
+ is_general++;
+ }
+
+ new_confline(&ctmp);
+ /* Blank line */
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ first_line = ctmp;
+
+ new_confline(&ctmp)->var = vtmp;
+
+ name[0] = '\0';
+ if(is_general){
+ p = srchstr(vtmp->name, "-foreground-color");
+ snprintf(name, sizeof(name), "%.*s", p ? MIN(p - vtmp->name, 30) : 30, vtmp->name);
+ name[sizeof(name)-1] = '\0';
+ if(islower((unsigned char)name[0]))
+ name[0] = toupper((unsigned char)name[0]);
+ }
+ else if(is_index){
+ p = srchstr(vtmp->name, "-foreground-color");
+ snprintf(name, sizeof(name), "%.*s Symbol",
+ p ? MIN(p - vtmp->name, 30) : 30, vtmp->name);
+ name[sizeof(name)-1] = '\0';
+ if(islower((unsigned char)name[0]))
+ name[0] = toupper((unsigned char)name[0]);
+ }
+ else if(is_hdrcolor){
+ char **lval;
+
+ lval = LVAL(vtmp, ew);
+ hcolors = spec_colors_from_varlist(lval, 0);
+
+ for(hc = hcolors, i = 0; hc; hc = hc->next, i++)
+ if(CFC_ICUST(*cl) == i)
+ break;
+
+ if(hc){
+ snprintf(name, sizeof(name), "%s%s", HEADER_WORD, hc->spec);
+ name[sizeof(name)-1] = '\0';
+ i = sizeof(HEADER_WORD) - 1;
+ if(islower((unsigned char) name[i]))
+ name[i] = toupper((unsigned char) name[i]);
+ }
+ }
+ else if(is_keywordcol){
+ char **lval;
+
+ for(kw=ps->keywords, i=0; kw; kw=kw->next, i++)
+ if(CFC_ICUST(*cl) == i)
+ break;
+
+ if(kw){
+ char *nm, *comment = NULL;
+
+ nm = kw->nick ? kw->nick : kw->kw ? kw->kw : "";
+ if(utf8_width(nm) > 60)
+ nm = short_str(nm, tmp_20k_buf, SIZEOF_20KBUF, 60, EndDots);
+
+ if(kw->nick && kw->kw && kw->kw[0])
+ comment = kw->kw;
+
+ if(utf8_width(nm) + (comment ? utf8_width(comment) : 0) < 60)
+ utf8_snprintf(name, sizeof(name), "%.50w%s%.50w%s",
+ nm,
+ comment ? " (" : "",
+ comment ? comment : "",
+ comment ? ")" : "");
+ else
+ snprintf(name, sizeof(name), "%s", nm);
+
+ name[sizeof(name)-1] = '\0';
+
+ lval = LVAL(vtmp, ew);
+ hcolors = spec_colors_from_varlist(lval, 0);
+ if(kw && hcolors)
+ if(!(kw->nick && kw->nick[0]
+ && (color=hdr_color(kw->nick, NULL, hcolors))))
+ if(kw->kw && kw->kw[0])
+ color = hdr_color(kw->kw, NULL, hcolors);
+ }
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s Color =", name[0] ? name : "?");
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_STARTITEM | CF_NOSELECT);
+ ctmp->keymenu = &color_changing_keymenu;
+
+ if(is_hdrcolor){
+ char **apval;
+
+ def = !(hc && hc->fg && hc->fg[0] && hc->bg && hc->bg[0]);
+
+ add_color_setting_disp(ps, &ctmp, vtmp, ctmpb,
+ &custom_color_changing_keymenu,
+ &hdr_color_checkbox_keymenu,
+ config_help(vtmp - ps->vars, 0),
+ indent, CFC_ICUST(*cl),
+ def ? ps_global->VAR_NORM_FORE_COLOR
+ : hc->fg,
+ def ? ps_global->VAR_NORM_BACK_COLOR
+ : hc->bg,
+ def);
+
+ /* optional string to match in header value */
+ new_confline(&ctmp);
+ ctmp->varnamep = ctmpb;
+ ctmp->keymenu = &color_pattern_keymenu;
+ ctmp->help = h_config_customhdr_pattern;
+ ctmp->tool = color_text_tool;
+ ctmp->varoffset = indent-5;
+ ctmp->varname = cpystr(_("Pattern to match ="));
+ ctmp->valoffset = indent-5 + strlen(ctmp->varname) + 1;
+ ctmp->varmem = (*cl)->varmem;
+
+ /*
+ * This is really ugly. This is just to get the value correct.
+ */
+ memset(&v, 0, sizeof(v));
+ v.is_used = 1;
+ v.is_user = 1;
+ apval = APVAL(&v, ew);
+ if(hc && hc->val && apval)
+ *apval = pattern_to_string(hc->val);
+
+ set_current_val(&v, FALSE, FALSE);
+ ctmp->var = &v;
+ ctmp->value = pretty_value(ps, ctmp);
+ ctmp->var = vtmp;
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(v.current_val.p)
+ fs_give((void **)&v.current_val.p);
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+ }
+ else if(is_keywordcol){
+
+ def = !(color && color->fg && color->fg[0]
+ && color->bg && color->bg[0]);
+
+ add_color_setting_disp(ps, &ctmp, vtmp, ctmpb,
+ &kw_color_changing_keymenu,
+ &kw_color_checkbox_keymenu,
+ config_help(vtmp - ps->vars, 0),
+ indent, CFC_ICUST(*cl),
+ def ? ps_global->VAR_NORM_FORE_COLOR
+ : color->fg,
+ def ? ps_global->VAR_NORM_BACK_COLOR
+ : color->bg,
+ def);
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+ }
+ else{
+ char *pvalfg, *pvalbg;
+ int def;
+ COLOR_PAIR *newc;
+
+ pvalfg = PVAL(vtmp, ew);
+ pvalbg = PVAL(vtmp+1, ew);
+ def = !(pvalfg && pvalfg[0] && pvalbg && pvalbg[0]);
+ if(def){
+ /* display default val, if there is one */
+ pvalfg = PVAL(vtmp, Main);
+ pvalbg = PVAL(vtmp+1, Main);
+ if(ew == Post && pvalfg && pvalfg[0] && pvalbg && pvalbg[0]){
+ ;
+ }
+ else if(vtmp && vtmp->global_val.p && vtmp->global_val.p[0] &&
+ (vtmp+1)->global_val.p && (vtmp+1)->global_val.p[0]){
+ pvalfg = vtmp->global_val.p;
+ pvalbg = (vtmp+1)->global_val.p;
+ }
+ else{
+ if(var_defaults_to_rev(vtmp) && (newc = pico_get_rev_color())){
+ pvalfg = newc->fg;
+ pvalbg = newc->bg;
+ }
+ else{
+ pvalfg = NULL;
+ pvalbg = NULL;
+ }
+ }
+ }
+
+ add_color_setting_disp(ps, &ctmp, vtmp, ctmpb,
+ &color_changing_keymenu,
+ &config_checkbox_keymenu,
+ config_help(vtmp - ps->vars, 0),
+ indent, 0, pvalfg, pvalbg, def);
+ }
+
+ first_line = first_sel_confline(first_line);
+
+ saved_screen = opt_screen;
+ memset(&screen, 0, sizeof(screen));
+ screen.ro_warning = saved_screen ? saved_screen->deferred_ro_warning : 0;
+ rv = conf_scroll_screen(ps, &screen, first_line,
+ ew == Post ? _("SETUP COLOR EXCEPTIONS")
+ : _("SETUP COLOR"),
+ _("configuration"), 1);
+
+ opt_screen = saved_screen;
+ ps->mangled_screen = 1;
+ return(rv);
+}
diff --git a/alpine/colorconf.h b/alpine/colorconf.h
new file mode 100644
index 00000000..501e31e5
--- /dev/null
+++ b/alpine/colorconf.h
@@ -0,0 +1,79 @@
+/*
+ * $Id: colorconf.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_COLORCONF_INCLUDED
+#define PINE_COLORCONF_INCLUDED
+
+
+#include "conftype.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+
+
+#define HEADER_WORD "Header "
+#define KW_COLORS_HDR "KEYWORD COLORS"
+#define ADDHEADER_COMMENT N_("[ Use the AddHeader command to add colored headers in MESSAGE TEXT screen ]")
+#define EQ_COL 37
+
+#define SPACE_BETWEEN_DOUBLEVARS 3
+#define SAMPLE_LEADER "---------------------------"
+#define SAMP1 "[Sample ]"
+#define SAMP2 "[Default]"
+#define SAMPEXC "[Exception]"
+#define SBS 1 /* space between samples */
+#define COLOR_BLOB "< >"
+#define COLOR_BLOB_TRAN "<TRAN>"
+#define COLOR_BLOB_NORM "<NORM>"
+#define COLOR_BLOB_NONE "<NONE>"
+#define COLOR_BLOB_LEN 6
+#define COLOR_INDENT 3
+#define COLORNOSET " [ Colors below may not be set until color is turned on above ]"
+
+
+/*
+ * The CONF_S's varmem field serves dual purposes. The low two bytes
+ * are reserved for the pre-defined color index (though only 8 are
+ * defined for the nonce, and the high order bits are for the index
+ * of the particular SPEC_COLOR_S this CONF_S is associated with.
+ * Capiche?
+ */
+#define CFC_ICOLOR(V) ((V)->varmem & 0xffff)
+#define CFC_ICUST(V) ((V)->varmem >> 16)
+#define CFC_SET_COLOR(I, C) (((I) << 16) | (C))
+#define CFC_ICUST_INC(V) CFC_SET_COLOR(CFC_ICUST(V) + 1, CFC_ICOLOR(V))
+#define CFC_ICUST_DEC(V) CFC_SET_COLOR(CFC_ICUST(V) - 1, CFC_ICOLOR(V))
+
+#define CFC_ICOLOR_NORM (1000)
+#define CFC_ICOLOR_NONE (1001)
+
+
+extern int treat_color_vars_as_text;
+
+
+/* exported protoypes */
+void color_config_screen(struct pine *, int);
+int color_setting_tool(struct pine *, int, CONF_S **, unsigned);
+char *sampleexc_text(struct pine *, struct variable *);
+int color_related_var(struct pine *, struct variable *);
+int color_holding_var(struct pine *, struct variable *);
+char *sample_text(struct pine *, struct variable *);
+char *color_parenthetical(struct variable *);
+void add_color_setting_disp(struct pine *, CONF_S **, struct variable *, CONF_S *,
+ struct key_menu *, struct key_menu *, HelpType,
+ int, int, char *, char *, int);
+
+
+#endif /* PINE_COLORCONF_INCLUDED */
diff --git a/alpine/confscroll.c b/alpine/confscroll.c
new file mode 100644
index 00000000..0ada8a79
--- /dev/null
+++ b/alpine/confscroll.c
@@ -0,0 +1,5968 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: confscroll.c 1169 2008-08-27 06:42:06Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "confscroll.h"
+#include "keymenu.h"
+#include "status.h"
+#include "titlebar.h"
+#include "help.h"
+#include "radio.h"
+#include "print.h"
+#include "ldapconf.h"
+#include "roleconf.h"
+#include "colorconf.h"
+#include "mailview.h"
+#include "mailcmd.h"
+#include "mailindx.h"
+#include "talk.h"
+#include "setup.h"
+#include "smime.h"
+#include "../pith/state.h"
+#include "../pith/flag.h"
+#include "../pith/list.h"
+#include "../pith/conf.h"
+#include "../pith/util.h"
+#include "../pith/newmail.h"
+#include "../pith/sort.h"
+#include "../pith/thread.h"
+#include "../pith/color.h"
+#include "../pith/hist.h"
+#include "../pith/icache.h"
+#include "../pith/conf.h"
+#include "../pith/init.h"
+#include "../pith/folder.h"
+#include "../pith/busy.h"
+#include "../pith/tempfile.h"
+#include "../pith/pattern.h"
+#include "../pith/charconv/utf8.h"
+
+
+#define CONFIG_SCREEN_HELP_TITLE _("HELP FOR SETUP CONFIGURATION")
+
+/* TRANSLATORS: Empty Value is what is shown in the configuration
+ screen when the user not only does not set an option but also
+ wants to explicitly not use the default value. Empty value means
+ an option with no value. */
+char *empty_val = N_("Empty Value");
+char *empty_val2 = N_("<Empty Value>");
+/* TRANSLATORS: No Value set is similar to Empty Value, but the
+ user has not explicitly decided to not use the default. It is
+ just an option which the user has left at the default value. */
+char *no_val = N_("No Value Set");
+/* TRANSLATORS: Value is Fixed is what is displayed in the config
+ screen when the system managers have set an option to a specific
+ value and they don't allow the user to change it. The value
+ is fixed to a certain value. This isn't the same word as
+ Repaired, it means Unchanging. */
+char *fixed_val = N_("Value is Fixed");
+char yesstr[] = "Yes";
+char nostr[] = "No";
+
+
+EditWhich ew = Main;
+
+
+OPT_SCREEN_S *opt_screen;
+
+
+/*
+ * This is pretty ugly. Some of the routines operate differently depending
+ * on which variable they are operating on. Sometimes those variables are
+ * global (real alpine.h V_ style variables) and sometimes they are just
+ * local variables (as in role_config_edit_screen). These pointers are here
+ * so that the routines can figure out which variable they are operating
+ * on and do the right thing.
+ */
+struct variable *score_act_global_ptr,
+ *scorei_pat_global_ptr,
+ *age_pat_global_ptr,
+ *size_pat_global_ptr,
+ *cati_global_ptr,
+ *cat_cmd_global_ptr,
+ *cat_lim_global_ptr,
+ *startup_ptr,
+ *role_comment_ptr,
+ *role_forw_ptr,
+ *role_repl_ptr,
+ *role_fldr_ptr,
+ *role_afrom_ptr,
+ *role_filt_ptr,
+ *role_status1_ptr,
+ *role_status2_ptr,
+ *role_status3_ptr,
+ *role_status4_ptr,
+ *role_status5_ptr,
+ *role_status6_ptr,
+ *role_status7_ptr,
+ *role_status8_ptr,
+ *msg_state1_ptr,
+ *msg_state2_ptr,
+ *msg_state3_ptr,
+ *msg_state4_ptr;
+
+
+typedef NAMEVAL_S *(*PTR_TO_RULEFUNC)(int);
+
+
+/*
+ * Internal prototypes
+ */
+PTR_TO_RULEFUNC rulefunc_from_var(struct pine *, struct variable *);
+void set_radio_pretty_vals(struct pine *, CONF_S **);
+int save_include(struct pine *, struct variable *, int);
+void config_scroll_up(long);
+void config_scroll_down(long);
+void config_scroll_to_pos(long);
+CONF_S *config_top_scroll(struct pine *, CONF_S *);
+void update_option_screen(struct pine *, OPT_SCREEN_S *, Pos *);
+void print_option_screen(OPT_SCREEN_S *, char *);
+void option_screen_redrawer(void);
+char *text_pretty_value(struct pine *, CONF_S *);
+char *checkbox_pretty_value(struct pine *, CONF_S *);
+char *yesno_pretty_value(struct pine *, CONF_S *);
+char *radio_pretty_value(struct pine *, CONF_S *);
+char *sigfile_pretty_value(struct pine *, CONF_S *);
+char *color_pretty_value(struct pine *, CONF_S *);
+char *sort_pretty_value(struct pine *, CONF_S *);
+int longest_feature_name(void);
+COLOR_PAIR *sample_color(struct pine *, struct variable *);
+COLOR_PAIR *sampleexc_color(struct pine *, struct variable *);
+void clear_feature(char ***, char *);
+CONF_S *last_confline(CONF_S *);
+#ifdef _WINDOWS
+int config_scroll_callback(int, long);
+#endif
+
+
+/*
+ * We test for this same set of vars in a few places.
+ */
+int
+standard_radio_var(struct pine *ps, struct variable *v)
+{
+ return(v == &ps->vars[V_SAVED_MSG_NAME_RULE] ||
+ v == &ps->vars[V_FCC_RULE] ||
+ v == &ps->vars[V_GOTO_DEFAULT_RULE] ||
+ v == &ps->vars[V_INCOMING_STARTUP] ||
+ v == &ps->vars[V_PRUNING_RULE] ||
+ v == &ps->vars[V_REOPEN_RULE] ||
+ v == &ps->vars[V_THREAD_DISP_STYLE] ||
+ v == &ps->vars[V_THREAD_INDEX_STYLE] ||
+ v == &ps->vars[V_FLD_SORT_RULE] ||
+#ifndef _WINDOWS
+ v == &ps->vars[V_COLOR_STYLE] ||
+#endif
+ v == &ps->vars[V_INDEX_COLOR_STYLE] ||
+ v == &ps->vars[V_TITLEBAR_COLOR_STYLE] ||
+ v == &ps->vars[V_AB_SORT_RULE]);
+}
+
+
+PTR_TO_RULEFUNC
+rulefunc_from_var(struct pine *ps, struct variable *v)
+{
+ PTR_TO_RULEFUNC rulefunc = NULL;
+
+ if(v == &ps->vars[V_SAVED_MSG_NAME_RULE])
+ rulefunc = save_msg_rules;
+ else if(v == &ps->vars[V_FCC_RULE])
+ rulefunc = fcc_rules;
+ else if(v == &ps->vars[V_GOTO_DEFAULT_RULE])
+ rulefunc = goto_rules;
+ else if(v == &ps->vars[V_INCOMING_STARTUP])
+ rulefunc = incoming_startup_rules;
+ else if(v == startup_ptr)
+ rulefunc = startup_rules;
+ else if(v == &ps->vars[V_PRUNING_RULE])
+ rulefunc = pruning_rules;
+ else if(v == &ps->vars[V_REOPEN_RULE])
+ rulefunc = reopen_rules;
+ else if(v == &ps->vars[V_THREAD_DISP_STYLE])
+ rulefunc = thread_disp_styles;
+ else if(v == &ps->vars[V_THREAD_INDEX_STYLE])
+ rulefunc = thread_index_styles;
+ else if(v == &ps->vars[V_FLD_SORT_RULE])
+ rulefunc = fld_sort_rules;
+ else if(v == &ps->vars[V_AB_SORT_RULE])
+ rulefunc = ab_sort_rules;
+ else if(v == &ps->vars[V_INDEX_COLOR_STYLE])
+ rulefunc = index_col_style;
+ else if(v == &ps->vars[V_TITLEBAR_COLOR_STYLE])
+ rulefunc = titlebar_col_style;
+#ifndef _WINDOWS
+ else if(v == &ps->vars[V_COLOR_STYLE])
+ rulefunc = col_style;
+#endif
+
+ return(rulefunc);
+}
+
+
+void
+standard_radio_setup(struct pine *ps, CONF_S **cl, struct variable *v, CONF_S **first_line)
+{
+ int i, rindent = 12;
+ CONF_S *ctmpb;
+ PTR_TO_RULEFUNC rulefunc;
+ NAMEVAL_S *f;
+ char b[100];
+
+ if(!(cl && *cl))
+ return;
+
+ rulefunc = rulefunc_from_var(ps, v);
+ ctmpb = (*cl);
+
+ (*cl)->flags |= CF_NOSELECT;
+ (*cl)->keymenu = &config_radiobutton_keymenu;
+ (*cl)->tool = NULL;
+
+ /* put a nice delimiter before list */
+ new_confline(cl)->var = NULL;
+ (*cl)->varnamep = ctmpb;
+ (*cl)->keymenu = &config_radiobutton_keymenu;
+ (*cl)->help = NO_HELP;
+ (*cl)->tool = radiobutton_tool;
+ (*cl)->valoffset = rindent;
+ (*cl)->flags |= CF_NOSELECT;
+ /* TRANSLATORS: Set and Rule Values are the headings for an option
+ that can take one of several values. Underneath the Set heading
+ will be a column where one possibility is turned on (is Set).
+ The other column will be very short descriptions of what
+ the possibilities are (the Rule Values). */
+ utf8_snprintf(b, sizeof(b), "%-5.5w %s", _("Set"), _("Rule Values"));
+ (*cl)->value = cpystr(b);
+
+ new_confline(cl)->var = NULL;
+ (*cl)->varnamep = ctmpb;
+ (*cl)->keymenu = &config_radiobutton_keymenu;
+ (*cl)->help = NO_HELP;
+ (*cl)->tool = radiobutton_tool;
+ (*cl)->valoffset = rindent;
+ (*cl)->flags |= CF_NOSELECT;
+ (*cl)->value = cpystr("--- ----------------------");
+
+ if(rulefunc)
+ for(i = 0; (f = (*rulefunc)(i)); i++){
+ new_confline(cl)->var = v;
+ if(first_line && !*first_line && !pico_usingcolor())
+ *first_line = (*cl);
+
+ (*cl)->varnamep = ctmpb;
+ (*cl)->keymenu = &config_radiobutton_keymenu;
+ (*cl)->help = (v == startup_ptr)
+ ? h_config_other_startup
+ : config_help(v - ps->vars,0);
+ (*cl)->tool = radiobutton_tool;
+ (*cl)->valoffset = rindent;
+ (*cl)->varmem = i;
+ (*cl)->value = pretty_value(ps, *cl);
+ }
+}
+
+
+/*
+ * Reset the displayed values for all of the lines for this
+ * variable because others besides this line may change.
+ */
+void
+set_radio_pretty_vals(struct pine *ps, CONF_S **cl)
+{
+ CONF_S *ctmp;
+
+ if(!(cl && *cl &&
+ ((*cl)->var == &ps->vars[V_SORT_KEY] ||
+ standard_radio_var(ps, (*cl)->var) ||
+ (*cl)->var == startup_ptr)))
+ return;
+
+ /* hunt backwards */
+ for(ctmp = *cl;
+ ctmp && !(ctmp->flags & CF_NOSELECT) && !ctmp->varname;
+ ctmp = prev_confline(ctmp)){
+ if(ctmp->value)
+ fs_give((void **)&ctmp->value);
+
+ ctmp->value = pretty_value(ps, ctmp);
+ }
+
+ /* hunt forwards */
+ for(ctmp = *cl;
+ ctmp && !ctmp->varname && !(ctmp->flags & CF_NOSELECT);
+ ctmp = next_confline(ctmp)){
+ if(ctmp->value)
+ fs_give((void **)&ctmp->value);
+
+ ctmp->value = pretty_value(ps, ctmp);
+ }
+}
+
+
+/*
+ * test whether or not a var is
+ *
+ * returns: 1 if it should be excluded, 0 otw
+ */
+int
+exclude_config_var(struct pine *ps, struct variable *var, int allow_hard_to_config_remotely)
+{
+ if((ew != Main && (var->is_onlymain)) ||
+ (ew != ps_global->ew_for_except_vars && var->is_outermost))
+ return(1);
+
+ if(allow_hard_to_config_remotely)
+ return(!(var->is_user && var->is_used && !var->is_obsolete));
+
+ switch(var - ps->vars){
+ case V_MAIL_DIRECTORY :
+ case V_INCOMING_FOLDERS :
+ case V_FOLDER_SPEC :
+ case V_NEWS_SPEC :
+ case V_STANDARD_PRINTER :
+ case V_LAST_TIME_PRUNE_QUESTION :
+ case V_LAST_VERS_USED :
+ case V_ADDRESSBOOK :
+ case V_GLOB_ADDRBOOK :
+ case V_DISABLE_DRIVERS :
+ case V_DISABLE_AUTHS :
+ case V_REMOTE_ABOOK_METADATA :
+ case V_REMOTE_ABOOK_HISTORY :
+ case V_REMOTE_ABOOK_VALIDITY :
+ case V_OPER_DIR :
+ case V_USERINPUTTIMEO :
+ case V_TCPOPENTIMEO :
+ case V_TCPREADWARNTIMEO :
+ case V_TCPWRITEWARNTIMEO :
+ case V_TCPQUERYTIMEO :
+ case V_RSHCMD :
+ case V_RSHPATH :
+ case V_RSHOPENTIMEO :
+ case V_SSHCMD :
+ case V_SSHPATH :
+ case V_SSHOPENTIMEO :
+ case V_SENDMAIL_PATH :
+ case V_NEW_VER_QUELL :
+ case V_PATTERNS :
+ case V_PAT_ROLES :
+ case V_PAT_FILTS :
+ case V_PAT_SCORES :
+ case V_PAT_INCOLS :
+ case V_PAT_OTHER :
+ case V_PAT_SRCH :
+ case V_PRINTER :
+ case V_PERSONAL_PRINT_COMMAND :
+ case V_PERSONAL_PRINT_CATEGORY :
+ case V_RSS_NEWS :
+ case V_RSS_WEATHER :
+ case V_WP_INDEXHEIGHT :
+ case V_WP_INDEXLINES :
+ case V_WP_AGGSTATE :
+ case V_WP_STATE :
+ case V_WP_COLUMNS :
+#ifndef _WINDOWS
+ case V_OLD_CHAR_SET :
+#endif /* ! _WINDOWS */
+#if defined(DOS) || defined(OS2)
+ case V_UPLOAD_CMD :
+ case V_UPLOAD_CMD_PREFIX :
+ case V_DOWNLOAD_CMD :
+ case V_DOWNLOAD_CMD_PREFIX :
+#ifdef _WINDOWS
+ case V_FONT_NAME :
+ case V_FONT_SIZE :
+ case V_FONT_STYLE :
+ case V_FONT_CHAR_SET :
+ case V_PRINT_FONT_NAME :
+ case V_PRINT_FONT_SIZE :
+ case V_PRINT_FONT_STYLE :
+ case V_PRINT_FONT_CHAR_SET :
+ case V_WINDOW_POSITION :
+ case V_CURSOR_STYLE :
+#endif /* _WINDOWS */
+#endif /* DOS */
+#ifdef ENABLE_LDAP
+ case V_LDAP_SERVERS :
+#endif /* ENABLE_LDAP */
+ return(1);
+
+ default:
+ break;
+ }
+
+ return(!(var->is_user && var->is_used && !var->is_obsolete &&
+#ifdef SMIME
+ !smime_related_var(ps, var) &&
+#endif /* SMIME */
+ !color_related_var(ps, var)));
+}
+
+
+/*
+ * Test to indicate what should be saved in case user wants to abandon
+ * changes.
+ */
+int
+save_include(struct pine *ps, struct variable *v, int allow_hard_to_config_remotely)
+{
+ return(!exclude_config_var(ps, v, allow_hard_to_config_remotely)
+ || (v->is_user
+ && v->is_used
+ && !v->is_obsolete
+ && (v == &ps->vars[V_PERSONAL_PRINT_COMMAND]
+#ifdef ENABLE_LDAP
+ || v == &ps->vars[V_LDAP_SERVERS]
+#endif
+ )));
+}
+
+
+/*
+ * Handles screen painting and motion. Passes other commands to
+ * custom tools.
+ *
+ * Tool return values: Tools should return the following:
+ * 0 nothing changed
+ * -1 unrecognized command
+ * 1 something changed, conf_scroll_screen should remember that
+ * 2 tells conf_scroll_screen to return with value 1 or 0 depending
+ * on whether or not it has previously gotten a 1 from some tool.
+ * 3 tells conf_scroll_screen to return 1 (like 1 and 2 combined)
+ * ? Other tool-specific values can be used. They will cause
+ * conf_scroll_screen to return that value.
+ *
+ * Return values:
+ * 0 if nothing happened. That is, a tool returned 2 and we hadn't
+ * previously noted a return of 1
+ * 1 if something happened. That is, a tool returned 2 and we had
+ * previously noted a return of 1
+ * ? Tool-returned value different from -1, 0, 1, 2, or 3. This is it.
+ *
+ * Special proviso: If first_line->flags has CF_CHANGES set on entry, then
+ * that will cause things to behave like a change was made since entering
+ * this function.
+ */
+int
+conf_scroll_screen(struct pine *ps, OPT_SCREEN_S *screen, CONF_S *start_line, char *title, char *pdesc, int multicol)
+{
+ char tmp[MAXPATH+1];
+ char *utf8str;
+ UCS ch = 'x';
+ int cmd, i, j, done = 0, changes = 0;
+ int retval = 0;
+ int km_popped = 0, stay_in_col = 0;
+ struct key_menu *km = NULL;
+ CONF_S *ctmpa = NULL, *ctmpb = NULL;
+ Pos cursor_pos;
+ OtherMenu what_keymenu = FirstMenu;
+ void (*prev_redrawer)(void);
+
+ dprint((7, "conf_scroll_screen()\n"));
+
+ if(BODY_LINES(ps) < 1){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Screen too small"));
+ return(0);
+ }
+
+ if(screen && screen->ro_warning)
+ q_status_message1(SM_ORDER, 1, 3,
+ /* TRANSLATORS: "Config file not changeable," is what replaces the %s */
+ _("%s can't change options or settings"),
+ ps_global->restricted ? "Alpine demo"
+ : _("Config file not changeable,"));
+
+ screen->current = start_line;
+ if(start_line && start_line->flags & CF_CHANGES)
+ changes++;
+
+ opt_screen = screen;
+ ps->mangled_screen = 1;
+ ps->redrawer = option_screen_redrawer;
+
+ while(!done){
+ ps->user_says_cancel = 0;
+ if(km_popped){
+ km_popped--;
+ if(km_popped == 0){
+ clearfooter(ps);
+ ps->mangled_body = 1;
+ }
+ }
+
+ if(ps->mangled_screen){
+ ps->mangled_header = 1;
+ ps->mangled_footer = 1;
+ ps->mangled_body = 1;
+ ps->mangled_screen = 0;
+ }
+
+ /*----------- Check for new mail -----------*/
+ if(new_mail(0, NM_TIMING(ch), NM_STATUS_MSG | NM_DEFER_SORT) >= 0)
+ ps->mangled_header = 1;
+
+ if(ps->mangled_header){
+ set_titlebar(title, ps->mail_stream,
+ ps->context_current,
+ ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0, NULL);
+ ps->mangled_header = 0;
+ }
+
+ update_option_screen(ps, screen, &cursor_pos);
+
+ if(F_OFF(F_SHOW_CURSOR, ps)){
+ cursor_pos.row = ps->ttyo->screen_rows - FOOTER_ROWS(ps);
+ cursor_pos.col = 0;
+ }
+
+ /*---- This displays new mail notification, or errors ---*/
+ if(km_popped){
+ FOOTER_ROWS(ps) = 3;
+ mark_status_unknown();
+ }
+
+ display_message(ch);
+ if(km_popped){
+ FOOTER_ROWS(ps) = 1;
+ mark_status_unknown();
+ }
+
+ if(ps->mangled_footer || km != screen->current->keymenu){
+ bitmap_t bitmap;
+
+ setbitmap(bitmap);
+
+ ps->mangled_footer = 0;
+ km = screen->current->keymenu;
+
+ if(multicol &&
+ (F_OFF(F_ARROW_NAV, ps_global) ||
+ F_ON(F_RELAXED_ARROW_NAV, ps_global))){
+ menu_clear_binding(km, KEY_LEFT);
+ menu_clear_binding(km, KEY_RIGHT);
+ menu_clear_binding(km, KEY_UP);
+ menu_clear_binding(km, KEY_DOWN);
+ menu_add_binding(km, KEY_UP, MC_CHARUP);
+ menu_add_binding(km, KEY_DOWN, MC_CHARDOWN);
+ menu_add_binding(km, KEY_LEFT, MC_PREVITEM);
+ menu_add_binding(km, ctrl('B'), MC_PREVITEM);
+ menu_add_binding(km, KEY_RIGHT, MC_NEXTITEM);
+ menu_add_binding(km, ctrl('F'), MC_NEXTITEM);
+ }
+ else{
+ menu_clear_binding(km, KEY_LEFT);
+ menu_clear_binding(km, KEY_RIGHT);
+ menu_clear_binding(km, KEY_UP);
+ menu_clear_binding(km, KEY_DOWN);
+
+ /*
+ * Fix up arrow nav mode if necessary...
+ */
+ if(F_ON(F_ARROW_NAV, ps_global)){
+ int cmd;
+
+ if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
+ menu_add_binding(km, '<', cmd);
+ menu_add_binding(km, KEY_LEFT, cmd);
+ }
+
+ if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
+ menu_add_binding(km, '>', cmd);
+ menu_add_binding(km, KEY_RIGHT, cmd);
+ }
+
+ if((cmd = menu_clear_binding(km, 'p')) != MC_UNKNOWN){
+ menu_add_binding(km, 'p', cmd);
+ menu_add_binding(km, KEY_UP, cmd);
+ }
+
+ if((cmd = menu_clear_binding(km, 'n')) != MC_UNKNOWN){
+ menu_add_binding(km, 'n', cmd);
+ menu_add_binding(km, KEY_DOWN, cmd);
+ }
+ }
+ }
+
+ if(km_popped){
+ FOOTER_ROWS(ps) = 3;
+ clearfooter(ps);
+ }
+
+ draw_keymenu(km, bitmap, ps->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps), 0, what_keymenu);
+ what_keymenu = SameMenu;
+
+ if(km_popped){
+ FOOTER_ROWS(ps) = 1;
+ mark_keymenu_dirty();
+ }
+ }
+
+ MoveCursor(cursor_pos.row, cursor_pos.col);
+#ifdef MOUSE
+ mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); /* prime the handler */
+ register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
+ ps_global->ttyo->screen_rows -(FOOTER_ROWS(ps_global)+1),
+ ps_global->ttyo->screen_cols);
+#endif
+#ifdef _WINDOWS
+ mswin_setscrollcallback(config_scroll_callback);
+#endif
+ /*------ Read the command from the keyboard ----*/
+ ch = READ_COMMAND(&utf8str);
+#ifdef MOUSE
+ clear_mfunc(mouse_in_content);
+#endif
+#ifdef _WINDOWS
+ mswin_setscrollcallback(NULL);
+#endif
+
+ cmd = menu_command(ch, km);
+
+ if(km_popped)
+ switch(cmd){
+ case MC_NONE:
+ case MC_OTHER:
+ case MC_RESIZE:
+ case MC_REPAINT:
+ km_popped++;
+ break;
+
+ default:
+ clearfooter(ps);
+ break;
+ }
+
+ switch(cmd){
+ case MC_OTHER :
+ what_keymenu = NextMenu;
+ ps->mangled_footer = 1;
+ break;
+
+ case MC_HELP: /* help! */
+ if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
+ km_popped = 2;
+ ps->mangled_footer = 1;
+ break;
+ }
+
+ if(screen->current->help != NO_HELP){
+ prev_redrawer = ps_global->redrawer;
+ helper(screen->current->help,
+ (screen->current->help_title)
+ ? screen->current->help_title
+ : CONFIG_SCREEN_HELP_TITLE,
+ HLPD_SIMPLE);
+ ps_global->redrawer = prev_redrawer;
+ ps->mangled_screen = 1;
+ }
+ else
+ q_status_message(SM_ORDER,0,3,_("No help yet."));
+
+ break;
+
+
+ case MC_NEXTITEM: /* next list element */
+ case MC_CHARDOWN:
+ stay_in_col = 0;
+ if(screen->current->flags & CF_DOUBLEVAR){
+ /* if going from col1 to col2, it's simple */
+ if(!(screen->current->flags & CF_VAR2) && cmd == MC_NEXTITEM){
+ screen->current->flags |= CF_VAR2;
+ break;
+ }
+
+ /* otherwise we fall through to normal next */
+ stay_in_col = (screen->current->flags & CF_VAR2 &&
+ cmd == MC_CHARDOWN);
+ screen->current->flags &= ~CF_VAR2;
+ }
+
+ for(ctmpa = next_confline(screen->current), i = 1;
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = next_confline(ctmpa), i++)
+ ;
+
+ if(ctmpa){
+ screen->current = ctmpa;
+ if(screen->current->flags & CF_DOUBLEVAR && stay_in_col)
+ screen->current->flags |= CF_VAR2;
+
+ if(cmd == MC_CHARDOWN){
+ for(ctmpa = screen->top_line,
+ j = BODY_LINES(ps) - 1 - HS_MARGIN(ps);
+ j > 0 && ctmpa && ctmpa != screen->current;
+ ctmpa = next_confline(ctmpa), j--)
+ ;
+
+ if(!j && ctmpa){
+ for(i = 0;
+ ctmpa && ctmpa != screen->current;
+ ctmpa = next_confline(ctmpa), i++)
+ ;
+
+ if(i)
+ config_scroll_up(i);
+ }
+ }
+ }
+ else{
+ /*
+ * Scroll screen a bit so we show the non-selectable
+ * lines at the bottom.
+ */
+
+ /* set ctmpa to the bottom line on the screen */
+ for(ctmpa = screen->top_line, j = BODY_LINES(ps) - 1;
+ j > 0 && ctmpa;
+ ctmpa = next_confline(ctmpa), j--)
+ ;
+
+ i = 0;
+ if(ctmpa){
+ for(ctmpa = next_confline(ctmpa);
+ ctmpa &&
+ (ctmpa->flags & (CF_NOSELECT | CF_B_LINE)) ==
+ CF_NOSELECT;
+ ctmpa = next_confline(ctmpa), i++)
+ ;
+ }
+
+ if(i)
+ config_scroll_up(i);
+ else
+ q_status_message(SM_ORDER,0,1, _("Already at end of screen"));
+ }
+
+ break;
+
+ case MC_PREVITEM: /* prev list element */
+ case MC_CHARUP:
+ stay_in_col = 0;
+ if(screen->current->flags & CF_DOUBLEVAR){
+ if(screen->current->flags & CF_VAR2 && cmd == MC_PREVITEM){
+ screen->current->flags &= ~CF_VAR2;
+ break;
+ }
+
+ /* otherwise we fall through to normal prev */
+ stay_in_col = (!(screen->current->flags & CF_VAR2) &&
+ cmd == MC_CHARUP);
+ screen->current->flags &= ~CF_VAR2;
+ }
+ else if(cmd == MC_CHARUP)
+ stay_in_col = 1;
+
+ ctmpa = screen->current;
+ i = 0;
+ do
+ if(ctmpa == config_top_scroll(ps, screen->top_line))
+ i = 1;
+ else if(i)
+ i++;
+ while((ctmpa = prev_confline(ctmpa))
+ && (ctmpa->flags&CF_NOSELECT));
+
+ if(ctmpa){
+ screen->current = ctmpa;
+ if(screen->current->flags & CF_DOUBLEVAR && !stay_in_col)
+ screen->current->flags |= CF_VAR2;
+
+ if((cmd == MC_CHARUP) && i)
+ config_scroll_down(i);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 1,
+ _("Already at start of screen"));
+
+ break;
+
+ case MC_PAGEDN: /* page forward */
+ screen->current->flags &= ~CF_VAR2;
+ for(ctmpa = screen->top_line, i = BODY_LINES(ps);
+ i > 0 && ctmpa;
+ ctmpb = ctmpa, ctmpa = next_confline(ctmpa), i--)
+ ;
+
+ if(ctmpa){ /* first line off bottom of screen */
+ ctmpb = ctmpa;
+ ps->mangled_body = 1;
+ /* find first selectable line on next page */
+ for(screen->top_line = ctmpa;
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = next_confline(ctmpa))
+ ;
+
+ /*
+ * No selectable lines on next page. Slide up to first
+ * selectable.
+ */
+ if(!ctmpa){
+ for(ctmpa = prev_confline(ctmpb);
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = prev_confline(ctmpa))
+ ;
+
+ if(ctmpa)
+ screen->top_line = ctmpa;
+ }
+ }
+ else{ /* on last screen */
+ /* just move current down to last entry on screen */
+ if(ctmpb){ /* last line of data */
+ for(ctmpa = ctmpb, i = BODY_LINES(ps);
+ i > 0 && ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = prev_confline(ctmpa), i--)
+ ;
+
+ if(ctmpa == screen->current){
+ q_status_message(SM_ORDER,0,1,
+ _("Already at end of screen"));
+ goto no_down;
+ }
+
+ ps->mangled_body = 1;
+ }
+ }
+
+ if(ctmpa)
+ screen->current = ctmpa;
+no_down:
+ break;
+
+ case MC_PAGEUP: /* page backward */
+ ps->mangled_body = 1;
+ screen->current->flags &= ~CF_VAR2;
+ if(!(ctmpa=prev_confline(screen->top_line)))
+ ctmpa = screen->current;
+
+ for(i = BODY_LINES(ps) - 1;
+ i > 0 && prev_confline(ctmpa);
+ i--, ctmpa = prev_confline(ctmpa))
+ ;
+
+ for(screen->top_line = ctmpa;
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = next_confline(ctmpa))
+ ;
+
+ if(ctmpa){
+ if(ctmpa == screen->current){
+ /*
+ * We get to here if there was nothing selectable on
+ * the previous page. There still may be something
+ * selectable further back than the previous page,
+ * so look for that.
+ */
+ for(ctmpa = prev_confline(screen->top_line);
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = prev_confline(ctmpa))
+ ;
+
+ if(!ctmpa){
+ ctmpa = screen->current;
+ q_status_message(SM_ORDER, 0, 1,
+ _("Already at start of screen"));
+ }
+ }
+
+ screen->current = ctmpa;
+ }
+
+ break;
+
+#ifdef MOUSE
+ case MC_MOUSE:
+ {
+ MOUSEPRESS mp;
+
+ mouse_get_last (NULL, &mp);
+ mp.row -= HEADER_ROWS(ps);
+ ctmpa = screen->top_line;
+
+ while (mp.row && ctmpa != NULL) {
+ --mp.row;
+ ctmpa = ctmpa->next;
+ }
+
+ if (ctmpa != NULL && !(ctmpa->flags & CF_NOSELECT)){
+ if(screen->current->flags & CF_DOUBLEVAR)
+ screen->current->flags &= ~CF_VAR2;
+
+ screen->current = ctmpa;
+
+ if(screen->current->flags & CF_DOUBLEVAR &&
+ mp.col >= screen->current->val2offset)
+ screen->current->flags |= CF_VAR2;
+
+ update_option_screen(ps, screen, &cursor_pos);
+
+ if(mp.button == M_BUTTON_LEFT && mp.doubleclick){
+
+ if(screen->current->tool){
+ unsigned flags;
+ int default_cmd;
+
+ flags = screen->current->flags;
+ flags |= (changes ? CF_CHANGES : 0);
+
+ default_cmd = menu_command(ctrl('M'), km);
+ switch(i=(*screen->current->tool)(ps, default_cmd,
+ &screen->current, flags)){
+ case -1:
+ case 0:
+ break;
+
+ case 1:
+ changes = 1;
+ break;
+
+ case 2:
+ retval = changes;
+ done++;
+ break;
+
+ case 3:
+ retval = 1;
+ done++;
+ break;
+
+ default:
+ retval = i;
+ done++;
+ break;
+ }
+ }
+ }
+#ifdef _WINDOWS
+ else if(mp.button == M_BUTTON_RIGHT) {
+ MPopup other_popup[20];
+ int n = -1, cmd, i;
+ struct key_menu *sckm = screen->current->keymenu; /* only for popup */
+
+ if((cmd = menu_command(ctrl('M'), sckm)) != MC_UNKNOWN){
+ i = menu_binding_index(sckm, cmd);
+ other_popup[++n].type = tQueue;
+ other_popup[n].label.style = lNormal;
+ other_popup[n].label.string = sckm->keys[i].label;
+ other_popup[n].data.val = ctrl('M');
+ }
+ else if((cmd = menu_command('>', sckm)) != MC_UNKNOWN){
+ i = menu_binding_index(sckm, cmd);
+ other_popup[++n].type = tQueue;
+ other_popup[n].label.style = lNormal;
+ other_popup[n].label.string = sckm->keys[i].label;
+ other_popup[n].data.val = '>';
+ }
+
+ if(((i = menu_binding_index(sckm, MC_RGB1)) >= 0) ||
+ ((i = menu_binding_index(sckm, MC_RGB2)) >= 0)){
+ other_popup[++n].type = tQueue;
+ other_popup[n].label.style = lNormal;
+ other_popup[n].label.string = sckm->keys[i].label;
+ other_popup[n].data.val =
+ sckm->keys[i].bind.ch[0];
+ }
+
+ if((cmd = menu_command('<', sckm)) != MC_UNKNOWN){
+ i = menu_binding_index(sckm, cmd);
+ other_popup[++n].type = tQueue;
+ other_popup[n].label.style = lNormal;
+ other_popup[n].label.string = sckm->keys[i].label;
+ other_popup[n].data.val = '<';
+ }
+ else if((i = menu_binding_index(sckm, MC_EXIT)) >= 0){
+ other_popup[++n].type = tQueue;
+ other_popup[n].label.style = lNormal;
+ other_popup[n].label.string = sckm->keys[i].label;
+ other_popup[n].data.val =
+ sckm->keys[i].bind.ch[0];
+ }
+
+ if((i = menu_binding_index(sckm, MC_HELP)) >= 0){
+ if(n > 0)
+ other_popup[++n].type = tSeparator;
+
+ other_popup[++n].type = tQueue;
+ other_popup[n].label.style = lNormal;
+ other_popup[n].label.string = sckm->keys[i].label;
+ other_popup[n].data.val = sckm->keys[i].bind.ch[0];
+ }
+
+ if(n > 0){
+ other_popup[++n].type = tTail;
+ mswin_popup(other_popup);
+ }
+ }
+ }
+ else if(mp.button == M_BUTTON_RIGHT) {
+ MPopup other_popup[20];
+ int n = -1, cmd, i;
+ struct key_menu *sckm = screen->current->keymenu; /* only for popup */
+
+ if((cmd = menu_command('<', sckm)) != MC_UNKNOWN){
+ i = menu_binding_index(sckm, cmd);
+ other_popup[++n].type = tQueue;
+ other_popup[n].label.style = lNormal;
+ other_popup[n].label.string = sckm->keys[i].label;
+ other_popup[n].data.val = '<';
+ }
+ else if((i = menu_binding_index(sckm, MC_EXIT)) >= 0){
+ other_popup[++n].type = tQueue;
+ other_popup[n].label.style = lNormal;
+ other_popup[n].label.string = sckm->keys[i].label;
+ other_popup[n].data.val = sckm->keys[i].bind.ch[0];
+ }
+
+ other_popup[++n].type = tTail;
+
+ if(n > 0)
+ mswin_popup(other_popup);
+#endif
+ }
+ }
+ break;
+#endif
+
+ case MC_PRINTTXT: /* print screen */
+ print_option_screen(screen, pdesc ? pdesc : "");
+ break;
+
+ case MC_WHEREIS: /* whereis */
+ /*--- get string ---*/
+ {int rc, found = 0;
+#define FOUND_IT 0x01
+#define FOUND_CURRENT 0x02
+#define FOUND_WRAPPED 0x04
+#define FOUND_NOSELECT 0x08
+#define FOUND_ABOVE 0x10
+ char *result = NULL, buf[64];
+ char *p, last[64];
+ static HISTORY_S *history = NULL;
+ HelpType help;
+ static ESCKEY_S ekey[] = {
+ {0, 0, "", ""},
+ /* TRANSLATORS: go to Top of screen */
+ {ctrl('Y'), 10, "^Y", N_("Top")},
+ {ctrl('V'), 11, "^V", N_("Bottom")},
+ {KEY_UP, 30, "", ""},
+ {KEY_DOWN, 31, "", ""},
+ {-1, 0, NULL, NULL}};
+#define KU_WI (3) /* index of KEY_UP */
+
+ init_hist(&history, HISTSIZE);
+ last[0] = '\0';
+ if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
+ strncpy(last, p, sizeof(last));
+ last[sizeof(last)-1] = '\0';
+ }
+
+ ps->mangled_footer = 1;
+ buf[0] = '\0';
+ snprintf(tmp, sizeof(tmp), "Word to find %s%s%s: ",
+ (last[0]) ? "[" : "",
+ (last[0]) ? last : "",
+ (last[0]) ? "]" : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ help = NO_HELP;
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ /*
+ * 2 is really 1 because there will be one real entry and
+ * one entry of "" because of the get_prev_hist above.
+ */
+ if(items_in_hist(history) > 2){
+ ekey[KU_WI].name = HISTORY_UP_KEYNAME;
+ ekey[KU_WI].label = HISTORY_KEYLABEL;
+ ekey[KU_WI+1].name = HISTORY_DOWN_KEYNAME;
+ ekey[KU_WI+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ ekey[KU_WI].name = "";
+ ekey[KU_WI].label = "";
+ ekey[KU_WI+1].name = "";
+ ekey[KU_WI+1].label = "";
+ }
+
+ rc = optionally_enter(buf,-FOOTER_ROWS(ps),0,sizeof(buf),
+ tmp,ekey,help,&flags);
+ if(rc == 3)
+ help = help == NO_HELP ? h_config_whereis : NO_HELP;
+ else if(rc == 30){
+ if((p = get_prev_hist(history, buf, 0, NULL)) != NULL){
+ strncpy(buf, p, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+ else if(rc == 31){
+ if((p = get_next_hist(history, buf, 0, NULL)) != NULL){
+ strncpy(buf, p, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+ else if(rc == 0 || rc == 1 || rc == 10 || rc == 11 || !buf[0]){
+ if(rc == 0 && !buf[0] && last[0])
+ strncpy(buf, last, 64);
+
+ break;
+ }
+ }
+
+ screen->current->flags &= ~CF_VAR2;
+ if(rc == 0 && buf[0]){
+ CONF_S *started_here;
+
+ save_hist(history, buf, 0, NULL);
+
+ ch = KEY_DOWN;
+ ctmpa = screen->current;
+ /*
+ * Skip over the unselectable lines of this "item"
+ * before starting search so that we don't find the
+ * same one again.
+ */
+ while((ctmpb = next_confline(ctmpa)) &&
+ (ctmpb->flags & CF_NOSELECT) &&
+ !(ctmpb->flags & CF_STARTITEM))
+ ctmpa = ctmpb;
+
+ started_here = next_confline(ctmpa);
+ while((ctmpa = next_confline(ctmpa)) != NULL)
+ if(srchstr(ctmpa->varname, buf)
+ || srchstr(ctmpa->value, buf)){
+
+ found = FOUND_IT;
+ /*
+ * If this line is not selectable, back up to the
+ * previous selectable line, but not past the
+ * start of this "entry".
+ */
+ if(ctmpa->flags & CF_NOSELECT)
+ found |= FOUND_NOSELECT;
+
+ while((ctmpa->flags & CF_NOSELECT) &&
+ !(ctmpa->flags & CF_STARTITEM) &&
+ (ctmpb = prev_confline(ctmpa)))
+ ctmpa = ctmpb;
+
+ /*
+ * If that isn't selectable, better search forward
+ * for something that is.
+ */
+ while((ctmpa->flags & CF_NOSELECT) &&
+ (ctmpb = next_confline(ctmpa))){
+ ctmpa = ctmpb;
+ found |= FOUND_ABOVE;
+ }
+
+ /*
+ * If that still isn't selectable, better search
+ * backwards for something that is.
+ */
+ while((ctmpa->flags & CF_NOSELECT) &&
+ (ctmpb = prev_confline(ctmpa))){
+ ctmpa = ctmpb;
+ found &= ~FOUND_ABOVE;
+ }
+
+ break;
+ }
+
+ if(!found){
+ found = FOUND_WRAPPED;
+ ctmpa = first_confline(screen->current);
+
+ while(ctmpa != started_here)
+ if(srchstr(ctmpa->varname, buf)
+ || srchstr(ctmpa->value, buf)){
+
+ found |= FOUND_IT;
+ if(ctmpa->flags & CF_NOSELECT)
+ found |= FOUND_NOSELECT;
+
+ while((ctmpa->flags & CF_NOSELECT) &&
+ !(ctmpa->flags & CF_STARTITEM) &&
+ (ctmpb = prev_confline(ctmpa)))
+ ctmpa = ctmpb;
+
+ while((ctmpa->flags & CF_NOSELECT) &&
+ (ctmpb = next_confline(ctmpa))){
+ ctmpa = ctmpb;
+ found |= FOUND_ABOVE;
+ }
+
+ if(ctmpa == screen->current)
+ found |= FOUND_CURRENT;
+
+ break;
+ }
+ else
+ ctmpa = next_confline(ctmpa);
+ }
+ }
+ else if(rc == 10){
+ screen->current = first_confline(screen->current);
+ if(screen->current && screen->current->flags & CF_NOSELECT){
+ for(ctmpa = next_confline(screen->current);
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = next_confline(ctmpa))
+ ;
+
+ if(ctmpa)
+ screen->current = ctmpa;
+ }
+
+ /* TRANSLATORS: Searched to ... is the result of the search, searched
+ to top means the search went past the bottom of the screen and
+ wrapped back around to the top. */
+ result = _("Searched to top");
+ }
+ else if(rc == 11){
+ screen->current = last_confline(screen->current);
+ if(screen->current && screen->current->flags & CF_NOSELECT){
+ for(ctmpa = prev_confline(screen->current);
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = prev_confline(ctmpa))
+ ;
+
+ if(ctmpa)
+ screen->current = ctmpa;
+ }
+
+ result = _("Searched to bottom");
+ }
+ else
+ result = _("WhereIs cancelled");
+
+ if((found & FOUND_IT) && ctmpa){
+ strncpy(last, buf, 64);
+ result =
+ (found & FOUND_CURRENT && found & FOUND_WRAPPED && found & FOUND_NOSELECT)
+ ? _("Current item contains the only match")
+ : (found & FOUND_CURRENT && found & FOUND_WRAPPED)
+ ? _("Current line contains the only match")
+ : (found & FOUND_NOSELECT && found & FOUND_WRAPPED)
+ ? ((found & FOUND_ABOVE)
+ ? _("Search wrapped: word found in text above current line")
+ : _("Search wrapped: word found in text below current line"))
+ : (found & FOUND_WRAPPED)
+ ? _("Search wrapped to beginning: word found")
+ : (found & FOUND_NOSELECT)
+ ? ((found & FOUND_ABOVE)
+ ? _("Word found in text above current line")
+ : _("Word found in text below current line"))
+ : _("Word found");
+ screen->current = ctmpa;
+ }
+
+ q_status_message(SM_ORDER,0,3,result ? result : _("Word not found"));
+ }
+
+ break;
+
+ case MC_HOMEKEY:
+ screen->current = first_confline(screen->current);
+ if(screen->current && screen->current->flags & CF_NOSELECT){
+ for(ctmpa = next_confline(screen->current);
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = next_confline(ctmpa))
+ ;
+
+ if(ctmpa)
+ screen->current = ctmpa;
+ }
+
+ q_status_message(SM_ORDER,0,3, _("Moved to top"));
+ break;
+
+ case MC_ENDKEY:
+ screen->current = last_confline(screen->current);
+ if(screen->current && screen->current->flags & CF_NOSELECT){
+ for(ctmpa = prev_confline(screen->current);
+ ctmpa && (ctmpa->flags & CF_NOSELECT);
+ ctmpa = prev_confline(ctmpa))
+ ;
+
+ if(ctmpa)
+ screen->current = ctmpa;
+ }
+
+ q_status_message(SM_ORDER,0,3, _("Moved to bottom"));
+ break;
+
+ case MC_REPAINT: /* redraw the display */
+ case MC_RESIZE:
+ ClearScreen();
+ ps->mangled_screen = 1;
+ break;
+
+ default:
+ if(screen && screen->ro_warning){
+ if(cmd == MC_EXIT){
+ retval = 0;
+ done++;
+ }
+ else
+ q_status_message1(SM_ORDER|SM_DING, 1, 3,
+ _("%s can't change options or settings"),
+ ps_global->restricted ? "Alpine demo"
+ : _("Config file not changeable,"));
+ }
+ else if(screen->current->tool){
+ unsigned flags;
+
+ flags = screen->current->flags;
+ flags |= (changes ? CF_CHANGES : 0);
+
+ switch(i=(*screen->current->tool)(ps, cmd,
+ &screen->current, flags)){
+ case -1:
+ q_status_message2(SM_ORDER, 0, 2,
+ /* TRANSLATORS: Command <command letter> not defined here.
+ Leave the trailing %s which might be a parenthetical
+ remark. */
+ _("Command \"%s\" not defined here.%s"),
+ pretty_command(ch),
+ F_ON(F_BLANK_KEYMENU,ps) ? "" : " See key menu below.");
+ break;
+
+ case 0:
+ break;
+
+ case 1:
+ changes = 1;
+ break;
+
+ case 2:
+ retval = changes;
+ done++;
+ break;
+
+ case 3:
+ retval = 1;
+ done++;
+ break;
+
+ default:
+ retval = i;
+ done++;
+ break;
+ }
+ }
+
+ break;
+
+ case MC_UTF8:
+ bogus_utf8_command(utf8str, "?");
+ break;
+
+ case MC_NONE: /* simple timeout */
+ break;
+ }
+ }
+
+ screen->current = first_confline(screen->current);
+ free_conflines(&screen->current);
+ return(retval);
+}
+
+
+/*
+ *
+ */
+void
+config_scroll_up(long int n)
+{
+ CONF_S *ctmp = opt_screen->top_line;
+ int cur_found = 0;
+
+ if(n < 0)
+ config_scroll_down(-n);
+ else if(n){
+ for(; n>0 && ctmp->next; n--){
+ ctmp = next_confline(ctmp);
+ if(prev_confline(ctmp) == opt_screen->current)
+ cur_found++;
+ }
+
+ opt_screen->top_line = ctmp;
+ ps_global->mangled_body = 1;
+ if(cur_found){
+ for(ctmp = opt_screen->top_line;
+ ctmp && (ctmp->flags & CF_NOSELECT);
+ ctmp = next_confline(ctmp))
+ ;
+
+ if(ctmp)
+ opt_screen->current = opt_screen->prev = ctmp;
+ else {
+ while(opt_screen->top_line->flags & CF_NOSELECT)
+ opt_screen->top_line = prev_confline(opt_screen->top_line);
+ opt_screen->current = opt_screen->prev = opt_screen->top_line;
+ }
+ }
+ }
+}
+
+
+/*
+ * config_scroll_down -
+ */
+void
+config_scroll_down(long int n)
+{
+ CONF_S *ctmp = opt_screen->top_line, *last_sel = NULL;
+ int i;
+
+ if(n < 0)
+ config_scroll_up(-n);
+ else if(n){
+ for(; n>0 && ctmp->prev; n--)
+ ctmp = prev_confline(ctmp);
+
+ opt_screen->top_line = ctmp;
+ ps_global->mangled_body = 1;
+ for(ctmp = opt_screen->top_line, i = BODY_LINES(ps_global);
+ i > 0 && ctmp && ctmp != opt_screen->current;
+ ctmp = next_confline(ctmp), i--)
+ if(!(ctmp->flags & CF_NOSELECT))
+ last_sel = ctmp;
+
+ if(!i && last_sel)
+ opt_screen->current = opt_screen->prev = last_sel;
+ }
+}
+
+
+/*
+ * config_scroll_to_pos -
+ */
+void
+config_scroll_to_pos(long int n)
+{
+ CONF_S *ctmp;
+
+ for(ctmp = first_confline(opt_screen->current);
+ n && ctmp && ctmp != opt_screen->top_line;
+ ctmp = next_confline(ctmp), n--)
+ ;
+
+ if(n == 0)
+ while(ctmp && ctmp != opt_screen->top_line)
+ if((ctmp = next_confline(ctmp)) != NULL)
+ n--;
+
+ config_scroll_up(n);
+}
+
+
+/*
+ * config_top_scroll - return pointer to the
+ */
+CONF_S *
+config_top_scroll(struct pine *ps, CONF_S *topline)
+{
+ int i;
+ CONF_S *ctmp;
+
+ for(ctmp = topline, i = HS_MARGIN(ps);
+ ctmp && i;
+ ctmp = next_confline(ctmp), i--)
+ ;
+
+ return(ctmp ? ctmp : topline);
+}
+
+
+int
+text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ return(text_toolit(ps, cmd, cl, flags, 0));
+}
+
+
+/*
+ * simple text variable handler
+ *
+ * note, things get a little involved due to the
+ * screen struct <--> variable mapping. (but, once its
+ * running it shouldn't need changing ;).
+ *
+ * look_for_backslash == 1 means that backslash is an escape character.
+ * In particular, \, can be used to put a literal comma
+ * into a value. The value will still have the backslash
+ * in it, but the comma after the backslash won't be treated
+ * as an item separator.
+ *
+ * returns: -1 on unrecognized cmd, 0 if no change, 1 if change
+ * returns what conf_exit_cmd returns for exit command.
+ */
+int
+text_toolit(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags, int look_for_backslash)
+{
+ char prompt[81], *sval, *tmp, *swap_val, **newval = NULL;
+ char *pval, **apval, **lval, ***alval;
+ char *olddefval = NULL;
+ int rv = 0, skip_to_next = 0, after = 0, i = 4, j, k;
+ int lowrange, hirange, incr, oeflags, oebufsize;
+ int numval, repeat_key = 0;
+ int curindex, previndex, nextindex, deefault;
+ HelpType help;
+ ESCKEY_S ekey[6];
+
+ if((*cl)->var->is_list){
+ lval = LVAL((*cl)->var, ew);
+ alval = ALVAL((*cl)->var, ew);
+ }
+ else{
+ pval = PVAL((*cl)->var, ew);
+ apval = APVAL((*cl)->var, ew);
+ }
+
+ oebufsize = 6*MAXPATH;
+ sval = (char *) fs_get(oebufsize*sizeof(char));
+ sval[0] = '\0';
+
+ if(flags&CF_NUMBER){ /* only happens if !is_list */
+ incr = 1;
+ if((*cl)->var == &ps->vars[V_FILLCOL]){
+ lowrange = 1;
+ hirange = MAX_FILLCOL;
+ }
+ else if((*cl)->var == &ps->vars[V_OVERLAP]
+ || (*cl)->var == &ps->vars[V_MARGIN]){
+ lowrange = 0;
+ hirange = 20;
+ }
+ else if((*cl)->var == &ps->vars[V_QUOTE_SUPPRESSION]){
+ lowrange = -(Q_SUPP_LIMIT-1);
+ hirange = 1000;
+ }
+ else if((*cl)->var == &ps->vars[V_MAXREMSTREAM]){
+ lowrange = 0;
+ hirange = 15;
+ }
+ else if((*cl)->var == &ps->vars[V_STATUS_MSG_DELAY]){
+ lowrange = -10;
+ hirange = 30;
+ }
+ else if((*cl)->var == &ps->vars[V_ACTIVE_MSG_INTERVAL]){
+ lowrange = 0;
+ hirange = 20;
+ }
+ else if((*cl)->var == &ps->vars[V_MAILCHECK] ||
+ (*cl)->var == &ps->vars[V_INCCHECKINTERVAL] ||
+ (*cl)->var == &ps->vars[V_INC2NDCHECKINTERVAL] ||
+ (*cl)->var == &ps->vars[V_MAILCHECKNONCURR]){
+ lowrange = 0;
+ hirange = 25000;
+ incr = 15;
+ }
+ else if((*cl)->var == &ps->vars[V_DEADLETS]){
+ lowrange = 0;
+ hirange = 9;
+ }
+ else if((*cl)->var == &ps->vars[V_NMW_WIDTH]){
+ lowrange = 20;
+ hirange = MAX_SCREEN_COLS;
+ }
+ else if((*cl)->var == score_act_global_ptr){
+ lowrange = -100;
+ hirange = 100;
+ }
+ else if((*cl)->var == &ps->vars[V_TCPOPENTIMEO] ||
+ (*cl)->var == &ps->vars[V_TCPREADWARNTIMEO] ||
+ (*cl)->var == &ps->vars[V_TCPQUERYTIMEO]){
+ lowrange = 5;
+ hirange = 1000;
+ }
+ else if((*cl)->var == &ps->vars[V_TCPWRITEWARNTIMEO] ||
+ (*cl)->var == &ps->vars[V_RSHOPENTIMEO] ||
+ (*cl)->var == &ps->vars[V_SSHOPENTIMEO] ||
+ (*cl)->var == &ps->vars[V_USERINPUTTIMEO]){
+ lowrange = 0;
+ hirange = 1000;
+ }
+ else if((*cl)->var == &ps->vars[V_INCCHECKTIMEO]){
+ lowrange = 1;
+ hirange = 1000;
+ }
+ else if((*cl)->var == &ps->vars[V_MAILDROPCHECK]){
+ lowrange = 0;
+ hirange = 1000000;
+ incr = 60;
+ }
+ else if((*cl)->var == &ps->vars[V_NNTPRANGE]){
+ lowrange = 0;
+ hirange = 1000000;
+ incr = 100;
+ }
+ else if((*cl)->var == &ps->vars[V_REMOTE_ABOOK_VALIDITY]){
+ lowrange = -1;
+ hirange = 25000;
+ }
+ else if((*cl)->var == &ps->vars[V_REMOTE_ABOOK_HISTORY]){
+ lowrange = 0;
+ hirange = 100;
+ }
+ else if((*cl)->var == cat_lim_global_ptr){
+ lowrange = -1;
+ hirange = 10000000;
+ }
+ else{
+ lowrange = 0;
+ hirange = 25000;
+ }
+
+ ekey[0].ch = -2;
+ ekey[0].rval = 'x';
+ ekey[0].name = "";
+ ekey[0].label = "";
+ ekey[1].ch = ctrl('P');
+ ekey[1].rval = ctrl('P');
+ ekey[1].name = "^P";
+ ekey[1].label = N_("Decrease");
+ ekey[2].ch = ctrl('N');
+ ekey[2].rval = ctrl('N');
+ ekey[2].name = "^N";
+ ekey[2].label = N_("Increase");
+ ekey[3].ch = KEY_DOWN;
+ ekey[3].rval = ctrl('P');
+ ekey[3].name = "";
+ ekey[3].label = "";
+ ekey[4].ch = KEY_UP;
+ ekey[4].rval = ctrl('N');
+ ekey[4].name = "";
+ ekey[4].label = "";
+ ekey[5].ch = -1;
+ }
+
+ switch(cmd){
+ case MC_ADD: /* add to list */
+ if(fixed_var((*cl)->var, "add to", NULL)){
+ break;
+ }
+ else if(!(*cl)->var->is_list && pval){
+ q_status_message(SM_ORDER, 3, 3,
+ _("Only single value allowed. Use \"Change\"."));
+ }
+ else{
+ int maxwidth;
+ char *p;
+
+ if((*cl)->var->is_list
+ && lval && lval[0] && lval[0][0]
+ && (*cl)->value){
+ char tmpval[101];
+ /* regular add to an existing list */
+
+ strncpy(tmpval, (*cl)->value, sizeof(tmpval));
+ tmpval[sizeof(tmpval)-1] = '\0';
+ removing_trailing_white_space(tmpval);
+
+ /* 33 is the number of chars other than the value */
+ maxwidth = MIN(80, ps->ttyo->screen_cols) - 15;
+ k = MIN(18, MAX(maxwidth-33,0));
+ if(utf8_width(tmpval) > k && k >= 3){
+ (void) utf8_truncate(tmpval, k-3);
+ strncat(tmpval, "...", sizeof(tmpval)-strlen(tmpval)-1);
+ tmpval[sizeof(tmpval)-1] = '\0';
+ }
+
+ utf8_snprintf(prompt, sizeof(prompt),
+ _("Enter text to insert before \"%.*w\": "), k, tmpval);
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+ else if((*cl)->var->is_list
+ && !lval
+ && (*cl)->var->current_val.l){
+ /* Add to list which doesn't exist, but default does exist */
+ ekey[0].ch = 'r';
+ ekey[0].rval = 'r';
+ ekey[0].name = "R";
+ ekey[0].label = N_("Replace");
+ ekey[1].ch = 'a';
+ ekey[1].rval = 'a';
+ ekey[1].name = "A";
+ ekey[1].label = N_("Add To");
+ ekey[2].ch = -1;
+ strncpy(prompt, _("Replace or Add To default value ? "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ switch(radio_buttons(prompt, -FOOTER_ROWS(ps), ekey, 'a', 'x',
+ h_config_replace_add, RB_NORM)){
+ case 'a':
+ p = sval;
+ for(j = 0; (*cl)->var->current_val.l[j]; j++){
+ sstrncpy(&p, (*cl)->var->current_val.l[j], oebufsize-(p-sval));
+ if(oebufsize-(p-sval) > 2){
+ *p++ = ',';
+ *p++ = ' ';
+ }
+
+ if(oebufsize-(p-sval) > 0)
+ *p = '\0';
+ }
+
+ sval[oebufsize-1] = '\0';
+
+add_text:
+ if(flags & CF_NUMBER)
+ snprintf(prompt, sizeof(prompt), _("Enter the numeric text to be added : "));
+ else
+ snprintf(prompt, sizeof(prompt), _("Enter the text to be added : "));
+
+ break;
+
+ case 'r':
+replace_text:
+ if(olddefval){
+ strncpy(sval, olddefval, oebufsize);
+ sval[oebufsize-1] = '\0';
+ }
+
+ if(flags & CF_NUMBER)
+ snprintf(prompt, sizeof(prompt), _("Enter the numeric replacement text : "));
+ else
+ snprintf(prompt, sizeof(prompt), _("Enter the replacement text : "));
+
+ break;
+
+ case 'x':
+ i = 1;
+ cmd_cancelled("Add");
+ break;
+ }
+ }
+ else{
+ if(flags & CF_NUMBER)
+ snprintf(prompt, sizeof(prompt), _("Enter the numeric text to be added : "));
+ else
+ snprintf(prompt, sizeof(prompt), _("Enter the text to be added : "));
+ }
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ ps->mangled_footer = 1;
+
+ if(i == 1)
+ break;
+
+ help = NO_HELP;
+ while(1){
+ if((*cl)->var->is_list
+ && lval && lval[0] && lval[0][0]
+ && (*cl)->value){
+ ekey[0].ch = ctrl('W');
+ ekey[0].rval = 5;
+ ekey[0].name = "^W";
+ /* TRANSLATORS: Insert new item before current item */
+ ekey[0].label = after ? N_("InsertBefore") : N_("InsertAfter");
+ ekey[1].ch = -1;
+ }
+ else if(!(flags&CF_NUMBER))
+ ekey[0].ch = -1;
+
+ oeflags = OE_APPEND_CURRENT;
+ i = optionally_enter(sval, -FOOTER_ROWS(ps), 0, oebufsize,
+ prompt,
+ (ekey[0].ch != -1) ? ekey : NULL,
+ help, &oeflags);
+ if(i == 0){
+ rv = 1;
+ if((*cl)->var->is_list)
+ ps->mangled_body = 1;
+ else
+ ps->mangled_footer = 1;
+
+ removing_leading_and_trailing_white_space(sval);
+ /*
+ * Coerce "" and <Empty Value> to empty string input.
+ * Catch <No Value Set> as a substitute for deleting.
+ */
+ if((*sval == '\"' && *(sval+1) == '\"' && *(sval+2) == '\0')
+ || !struncmp(sval, _(empty_val), strlen(_(empty_val)))
+ || (*sval == '<'
+ && !struncmp(sval+1, _(empty_val), strlen(_(empty_val)))))
+ *sval = '\0';
+ else if(!struncmp(sval, _(no_val), strlen(_(no_val)))
+ || (*sval == '<'
+ && !struncmp(sval+1, _(no_val), strlen(_(no_val)))))
+ goto delete;
+
+ if((*cl)->var->is_list){
+ if(*sval || !lval){
+ char **ltmp;
+ int i;
+
+ i = 0;
+ for(tmp = sval; *tmp; tmp++)
+ if(*tmp == ',')
+ i++; /* conservative count of ,'s */
+
+ if(!i){
+ ltmp = (char **)fs_get(2 * sizeof(char *));
+ ltmp[0] = cpystr(sval);
+ ltmp[1] = NULL;
+ }
+ else
+ ltmp = parse_list(sval, i + 1,
+ look_for_backslash
+ ? PL_COMMAQUOTE : 0,
+ NULL);
+
+ if(ltmp[0]){
+ config_add_list(ps, cl, ltmp, &newval, after);
+ if(after)
+ skip_to_next = 1;
+ }
+ else{
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Can't add %s to list"), _(empty_val));
+ rv = ps->mangled_body = 0;
+ }
+
+ fs_give((void **)&ltmp);
+ }
+ else{
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Can't add %s to list"), _(empty_val));
+ }
+ }
+ else{
+ if(flags&CF_NUMBER && sval[0]
+ && !(isdigit((unsigned char)sval[0])
+ || sval[0] == '-' || sval[0] == '+')){
+ q_status_message(SM_ORDER,3,3,
+ _("Entry must be numeric"));
+ i = 3; /* to keep loop going */
+ continue;
+ }
+
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(!(olddefval && !strcmp(sval, olddefval))
+ || ((*cl)->var == &ps->vars[V_POST_CHAR_SET])
+ || want_to(_("Leave unset and use default "),
+ 'y', 'y', NO_HELP, WT_FLUSH_IN) == 'n')
+ *apval = cpystr(sval);
+
+ newval = &(*cl)->value;
+ }
+ }
+ else if(i == 1){
+ cmd_cancelled("Add");
+ }
+ else if(i == 3){
+ help = help == NO_HELP ? h_config_add : NO_HELP;
+ continue;
+ }
+ else if(i == 4){ /* no redraw, yet */
+ continue;
+ }
+ else if(i == 5){ /* change from/to prepend to/from append */
+ char tmpval[101];
+
+ after = after ? 0 : 1;
+ strncpy(tmpval, (*cl)->value, sizeof(tmpval));
+ tmpval[sizeof(tmpval)-1] = '\0';
+ removing_trailing_white_space(tmpval);
+ /* 33 is the number of chars other than the value */
+ maxwidth = MIN(80, ps->ttyo->screen_cols) - 15;
+ k = MIN(18, MAX(maxwidth-33,0));
+ if(utf8_width(tmpval) > k && k >= 3){
+ (void) utf8_truncate(tmpval, k-3);
+ strncat(tmpval, "...", sizeof(tmpval)-strlen(tmpval)-1);
+ tmpval[sizeof(tmpval)-1] = '\0';
+ }
+
+ if(after)
+ snprintf(prompt, sizeof(prompt), _("Enter text to insert after \"%.*s\": "), k, tmpval);
+ else
+ snprintf(prompt, sizeof(prompt), _("Enter text to insert before \"%.*s\": "), k, tmpval);
+
+ continue;
+ }
+ else if(i == ctrl('P')){
+ if(sval[0])
+ numval = atoi(sval);
+ else{
+ if(pval)
+ numval = atoi(pval);
+ else
+ numval = lowrange + 1;
+ }
+
+ if(numval == lowrange){
+ /*
+ * Protect user from repeating arrow key that
+ * causes message to appear over and over.
+ */
+ if(++repeat_key > 0){
+ q_status_message1(SM_ORDER,3,3,
+ _("Minimum value is %s"), comatose(lowrange));
+ repeat_key = -5;
+ }
+ }
+ else
+ repeat_key = 0;
+
+ numval = MAX(numval - incr, lowrange);
+ snprintf(sval, oebufsize, "%d", numval);
+ sval[oebufsize-1] = '\0';
+ continue;
+ }
+ else if(i == ctrl('N')){
+ if(sval[0])
+ numval = atoi(sval);
+ else{
+ if(pval)
+ numval = atoi(pval);
+ else
+ numval = lowrange + 1;
+ }
+
+ if(numval == hirange){
+ if(++repeat_key > 0){
+ q_status_message1(SM_ORDER,3,3,
+ _("Maximum value is %s"), comatose(hirange));
+ repeat_key = -5;
+ }
+ }
+ else
+ repeat_key = 0;
+
+ numval = MIN(numval + incr, hirange);
+ snprintf(sval, oebufsize, "%d", numval);
+ sval[oebufsize-1] = '\0';
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ break;
+
+ case MC_DELETE: /* delete */
+delete:
+ if(!(*cl)->var->is_list
+ && apval && !*apval
+ && (*cl)->var->current_val.p){
+ char pmt[80];
+
+ snprintf(pmt, sizeof(pmt), _("Override default with %s"), _(empty_val2));
+ pmt[sizeof(pmt)-1] = '\0';
+ if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ sval[0] = '\0';
+ *apval = cpystr(sval);
+ newval = &(*cl)->value;
+ rv = ps->mangled_footer = 1;
+ }
+ }
+ else if((*cl)->var->is_list
+ && alval && !lval
+ && (*cl)->var->current_val.l){
+ char pmt[80];
+
+ snprintf(pmt, sizeof(pmt), _("Override default with %s"), _(empty_val2));
+ pmt[sizeof(pmt)-1] = '\0';
+ if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ char **ltmp;
+
+ sval[0] = '\0';
+ ltmp = (char **)fs_get(2 * sizeof(char *));
+ ltmp[0] = cpystr(sval);
+ ltmp[1] = NULL;
+ config_add_list(ps, cl, ltmp, &newval, 0);
+ fs_give((void **)&ltmp);
+ rv = ps->mangled_body = 1;
+ }
+ }
+ else if(((*cl)->var->is_list && !lval)
+ || (!(*cl)->var->is_list && !pval)){
+ q_status_message(SM_ORDER, 0, 3, _("No set value to delete"));
+ }
+ else{
+ if((*cl)->var->is_fixed)
+ snprintf(prompt, sizeof(prompt), _("Delete (unused) %s from %s "),
+ (*cl)->var->is_list
+ ? (!*lval[(*cl)->varmem])
+ ? _(empty_val2)
+ : lval[(*cl)->varmem]
+ : (pval)
+ ? (!*pval)
+ ? _(empty_val2)
+ : pval
+ : "<NULL VALUE>",
+ (*cl)->var->name);
+ else
+ snprintf(prompt, sizeof(prompt), _("Really delete %s%s from %s "),
+ (*cl)->var->is_list ? "item " : "",
+ (*cl)->var->is_list
+ ? int2string((*cl)->varmem + 1)
+ : (pval)
+ ? (!*pval)
+ ? _(empty_val2)
+ : pval
+ : "<NULL VALUE>",
+ (*cl)->var->name);
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+
+ ps->mangled_footer = 1;
+ if(want_to(prompt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ rv = 1;
+ if((*cl)->var->is_list)
+ ps->mangled_body = 1;
+ else
+ ps->mangled_footer = 1;
+
+ if((*cl)->var->is_list){
+ if(lval[(*cl)->varmem])
+ fs_give((void **)&lval[(*cl)->varmem]);
+
+ config_del_list_item(cl, &newval);
+ }
+ else{
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ newval = &(*cl)->value;
+ }
+ }
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Value not deleted"));
+ }
+
+ break;
+
+ case MC_EDIT: /* edit/change list option */
+ if(fixed_var((*cl)->var, NULL, NULL)){
+ break;
+ }
+ else if(((*cl)->var->is_list
+ && !lval
+ && (*cl)->var->current_val.l)
+ ||
+ (!(*cl)->var->is_list
+ && !pval
+ && (*cl)->var->current_val.p)){
+
+ /*
+ * In non-list case, offer default value for editing.
+ */
+ if(!(*cl)->var->is_list
+ && (*cl)->var != &ps->vars[V_REPLY_INTRO]
+ && (*cl)->var->current_val.p[0]
+ && strcmp(VSTRING,(*cl)->var->current_val.p)){
+ int quote_it;
+ size_t len;
+
+ olddefval = (char *) fs_get(strlen((*cl)->var->current_val.p)+3);
+
+ if(!strncmp((*cl)->var->current_val.p,
+ DSTRING,
+ (len=strlen(DSTRING)))){
+ /* strip DSTRING and trailing paren */
+ strncpy(olddefval, (*cl)->var->current_val.p+len,
+ strlen((*cl)->var->current_val.p)-len-1);
+ olddefval[strlen((*cl)->var->current_val.p)-len-1] = '\0';
+ }
+ else{
+ /* quote it if there are trailing spaces */
+ quote_it = ((*cl)->var->current_val.p[strlen((*cl)->var->current_val.p)-1] == SPACE);
+ snprintf(olddefval, strlen((*cl)->var->current_val.p)+3, "%s%s%s", quote_it ? "\"" : "", (*cl)->var->current_val.p, quote_it ? "\"" : "");
+ }
+
+ olddefval[strlen((*cl)->var->current_val.p)+3-1] = '\0';
+ }
+
+ goto replace_text;
+ }
+ else if(((*cl)->var->is_list
+ && !lval
+ && !(*cl)->var->current_val.l)
+ ||
+ (!(*cl)->var->is_list
+ && !pval
+ && !(*cl)->var->current_val.p)){
+ goto add_text;
+ }
+ else{
+ HelpType help;
+ char *clptr;
+
+ if(sval)
+ fs_give((void **)&sval);
+ if((*cl)->var->is_list){
+ snprintf(prompt, sizeof(prompt), _("Change field %s list entry : "),
+ (*cl)->var->name);
+ prompt[sizeof(prompt)-1] = '\0';
+ clptr = lval[(*cl)->varmem] ? lval[(*cl)->varmem] : NULL;
+ }
+ else{
+ if(flags & CF_NUMBER)
+ snprintf(prompt, sizeof(prompt), _("Change numeric field %s value : "), (*cl)->var->name);
+ else
+ snprintf(prompt, sizeof(prompt), _("Change field %s value : "), (*cl)->var->name);
+
+ clptr = pval ? pval : NULL;
+ }
+
+ oebufsize = clptr ? (int) MAX(MAXPATH, 50+strlen(clptr)) : MAXPATH;
+ sval = (char *) fs_get(oebufsize * sizeof(char));
+ snprintf(sval, oebufsize, "%s", clptr ? clptr : "");
+ sval[oebufsize-1] = '\0';
+
+ ps->mangled_footer = 1;
+ help = NO_HELP;
+ while(1){
+ if(!(flags&CF_NUMBER))
+ ekey[0].ch = -1;
+
+ oeflags = OE_APPEND_CURRENT;
+ i = optionally_enter(sval, -FOOTER_ROWS(ps), 0, oebufsize,
+ prompt,
+ (ekey[0].ch != -1) ? ekey : NULL,
+ help, &oeflags);
+ if(i == 0){
+ removing_leading_and_trailing_white_space(sval);
+ /*
+ * Coerce "" and <Empty Value> to empty string input.
+ * Catch <No Value Set> as a substitute for deleting.
+ */
+ if((*sval == '\"' && *(sval+1) == '\"' && *(sval+2) == '\0')
+ || !struncmp(sval, _(empty_val), strlen(_(empty_val)))
+ || (*sval == '<'
+ && !struncmp(sval+1, _(empty_val), strlen(_(empty_val)))))
+ *sval = '\0';
+ else if(!struncmp(sval, _(no_val), strlen(_(no_val)))
+ || (*sval == '<'
+ && !struncmp(sval+1, _(no_val), strlen(_(no_val)))))
+ goto delete;
+
+ rv = 1;
+ if((*cl)->var->is_list)
+ ps->mangled_body = 1;
+ else
+ ps->mangled_footer = 1;
+
+ if((*cl)->var->is_list){
+ char **ltmp = NULL;
+ int i;
+
+ if(lval[(*cl)->varmem])
+ fs_give((void **)&lval[(*cl)->varmem]);
+
+ i = 0;
+ for(tmp = sval; *tmp; tmp++)
+ if(*tmp == ',')
+ i++; /* conservative count of ,'s */
+
+ if(i)
+ ltmp = parse_list(sval, i + 1,
+ look_for_backslash
+ ? PL_COMMAQUOTE : 0,
+ NULL);
+
+ if(ltmp && !ltmp[0]) /* only commas */
+ goto delete;
+ else if(!i || (ltmp && !ltmp[1])){ /* only one item */
+ lval[(*cl)->varmem] = cpystr(sval);
+ newval = &(*cl)->value;
+
+ if(ltmp && ltmp[0])
+ fs_give((void **)&ltmp[0]);
+ }
+ else if(ltmp){
+ /*
+ * Looks like the value was changed to a
+ * list, so delete old value, and insert
+ * new list...
+ *
+ * If more than one item in existing list and
+ * current is end of existing list, then we
+ * have to delete and append instead of
+ * deleting and prepending.
+ */
+ if(((*cl)->varmem > 0 || lval[1])
+ && !(lval[(*cl)->varmem+1])){
+ after = 1;
+ skip_to_next = 1;
+ }
+
+ config_del_list_item(cl, &newval);
+ config_add_list(ps, cl, ltmp, &newval, after);
+ }
+
+ if(ltmp)
+ fs_give((void **)&ltmp);
+ }
+ else{
+ if(flags&CF_NUMBER && sval[0]
+ && !(isdigit((unsigned char)sval[0])
+ || sval[0] == '-' || sval[0] == '+')){
+ q_status_message(SM_ORDER,3,3,
+ _("Entry must be numeric"));
+ continue;
+ }
+
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(sval[0] && apval)
+ *apval = cpystr(sval);
+
+ newval = &(*cl)->value;
+ }
+ }
+ else if(i == 1){
+ cmd_cancelled("Change");
+ }
+ else if(i == 3){
+ help = help == NO_HELP ? h_config_change : NO_HELP;
+ continue;
+ }
+ else if(i == 4){ /* no redraw, yet */
+ continue;
+ }
+ else if(i == ctrl('P')){
+ numval = atoi(sval);
+ if(numval == lowrange){
+ /*
+ * Protect user from repeating arrow key that
+ * causes message to appear over and over.
+ */
+ if(++repeat_key > 0){
+ q_status_message1(SM_ORDER,3,3,
+ _("Minimum value is %s"), comatose(lowrange));
+ repeat_key = -5;
+ }
+ }
+ else
+ repeat_key = 0;
+
+ numval = MAX(numval - incr, lowrange);
+ snprintf(sval, oebufsize, "%d", numval);
+ sval[oebufsize-1] = '\0';
+ continue;
+ }
+ else if(i == ctrl('N')){
+ numval = atoi(sval);
+ if(numval == hirange){
+ if(++repeat_key > 0){
+ q_status_message1(SM_ORDER,3,3,
+ _("Maximum value is %s"), comatose(hirange));
+ repeat_key = -5;
+ }
+ }
+ else
+ repeat_key = 0;
+
+ numval = MIN(numval + incr, hirange);
+ snprintf(sval, oebufsize, "%d", numval);
+ sval[oebufsize-1] = '\0';
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ break;
+
+ case MC_SHUFFLE:
+ if(!((*cl)->var && (*cl)->var->is_list)){
+ q_status_message(SM_ORDER, 0, 2,
+ _("Can't shuffle single-valued setting"));
+ break;
+ }
+
+ if(!alval)
+ break;
+
+ curindex = (*cl)->varmem;
+ previndex = curindex-1;
+ nextindex = curindex+1;
+ if(!*alval || !(*alval)[nextindex])
+ nextindex = -1;
+
+ if((previndex < 0 && nextindex < 0) || !*alval){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Shuffle only makes sense when there is more than one value defined"));
+ break;
+ }
+
+ /* Move it up or down? */
+ i = 0;
+ ekey[i].ch = 'u';
+ ekey[i].rval = 'u';
+ ekey[i].name = "U";
+ ekey[i++].label = N_("Up");
+
+ ekey[i].ch = 'd';
+ ekey[i].rval = 'd';
+ ekey[i].name = "D";
+ ekey[i++].label = N_("Down");
+
+ ekey[i].ch = -1;
+ deefault = 'u';
+
+ if(previndex < 0){ /* no up */
+ ekey[0].ch = -2;
+ deefault = 'd';
+ }
+ else if(nextindex < 0)
+ ekey[1].ch = -2; /* no down */
+
+ snprintf(prompt, sizeof(prompt), "Shuffle %s%s%s ? ",
+ (ekey[0].ch != -2) ? "UP" : "",
+ (ekey[0].ch != -2 && ekey[1].ch != -2) ? " or " : "",
+ (ekey[1].ch != -2) ? "DOWN" : "");
+ help = (ekey[0].ch == -2) ? h_hdrcolor_shuf_down
+ : (ekey[1].ch == -2) ? h_hdrcolor_shuf_up
+ : h_hdrcolor_shuf;
+ prompt[sizeof(prompt)-1] = '\0';
+
+ i = radio_buttons(prompt, -FOOTER_ROWS(ps), ekey, deefault, 'x',
+ help, RB_NORM);
+
+ switch(i){
+ case 'x':
+ cmd_cancelled("Shuffle");
+ return(rv);
+
+ case 'u':
+ case 'd':
+ break;
+ }
+
+ /* swap order */
+ if(i == 'd'){
+ swap_val = (*alval)[curindex];
+ (*alval)[curindex] = (*alval)[nextindex];
+ (*alval)[nextindex] = swap_val;
+ }
+ else if(i == 'u'){
+ swap_val = (*alval)[curindex];
+ (*alval)[curindex] = (*alval)[previndex];
+ (*alval)[previndex] = swap_val;
+ }
+ else /* can't happen */
+ break;
+
+ /*
+ * Fix the conf line values.
+ */
+
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ (*cl)->value = pretty_value(ps, *cl);
+
+ if(i == 'd'){
+ if((*cl)->next->value)
+ fs_give((void **)&(*cl)->next->value);
+
+ (*cl)->next->value = pretty_value(ps, (*cl)->next);
+ *cl = next_confline(*cl);
+ }
+ else{
+ if((*cl)->prev->value)
+ fs_give((void **)&(*cl)->prev->value);
+
+ (*cl)->prev->value = pretty_value(ps, (*cl)->prev);
+ *cl = prev_confline(*cl);
+ }
+
+ rv = ps->mangled_body = 1;
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = config_exit_cmd(flags);
+ break;
+
+ default:
+ rv = -1;
+ break;
+ }
+
+ if(skip_to_next)
+ *cl = next_confline(*cl);
+
+ /*
+ * At this point, if changes occurred, var->user_val.X is set.
+ * So, fix the current_val, and handle special cases...
+ *
+ * NOTE: we don't worry about the "fixed variable" case here, because
+ * editing such vars should have been prevented above...
+ */
+ if(rv == 1){
+ /*
+ * Now go and set the current_val based on user_val changes
+ * above. Turn off command line settings...
+ */
+ set_current_val((*cl)->var, TRUE, FALSE);
+ fix_side_effects(ps, (*cl)->var, 0);
+
+ /*
+ * Delay setting the displayed value until "var.current_val" is set
+ * in case current val get's changed due to a special case above.
+ */
+ if(newval){
+ if(*newval)
+ fs_give((void **) newval);
+
+ *newval = pretty_value(ps, *cl);
+ }
+
+ exception_override_warning((*cl)->var);
+ }
+
+ if(sval)
+ fs_give((void **) &sval);
+
+ if(olddefval)
+ fs_give((void **) &olddefval);
+
+ return(rv);
+}
+
+
+int
+config_exit_cmd(unsigned int flags)
+{
+ return(screen_exit_cmd(flags, "Configuration"));
+}
+
+
+int
+simple_exit_cmd(unsigned int flags)
+{
+ return(2);
+}
+
+
+/*
+ * screen_exit_cmd - basic config/flag screen exit logic
+ */
+int
+screen_exit_cmd(unsigned int flags, char *cmd)
+{
+ if(flags & CF_CHANGES){
+ switch(want_to(EXIT_PMT, 'y', 'x', h_config_undo, WT_FLUSH_IN)){
+ case 'y':
+ q_status_message1(SM_ORDER,0,3,"%s changes saved", cmd);
+ return(2);
+
+ case 'n':
+ q_status_message1(SM_ORDER,3,5,"No %s changes saved", cmd);
+ return(10);
+
+ case 'x': /* ^C */
+ default :
+ q_status_message(SM_ORDER,3,5,"Changes not yet saved");
+ return(0);
+ }
+ }
+ else
+ return(2);
+}
+
+
+/*
+ *
+ */
+void
+config_add_list(struct pine *ps, CONF_S **cl, char **ltmp, char ***newval, int after)
+{
+ int items, i;
+ char *tmp, ***alval;
+ CONF_S *ctmp;
+
+ for(items = 0, i = 0; ltmp[i]; i++) /* count list items */
+ items++;
+
+ alval = ALVAL((*cl)->var, ew);
+
+ if(alval && (*alval)){
+ if((*alval)[0] && (*alval)[0][0]){
+ /*
+ * Since we were already a list, make room
+ * for the new member[s] and fall thru to
+ * actually fill them in below...
+ */
+ for(i = 0; (*alval)[i]; i++)
+ ;
+
+ fs_resize((void **)alval, (i + items + 1) * sizeof(char *));
+
+ /*
+ * move the ones that will be bumped down to the bottom of the list
+ */
+ for(; i >= (*cl)->varmem + (after?1:0); i--)
+ (*alval)[i+items] = (*alval)[i];
+
+ i = 0;
+ }
+ else if(alval){
+ (*cl)->varmem = 0;
+ if(*alval)
+ free_list_array(alval);
+
+ *alval = (char **)fs_get((items+1)*sizeof(char *));
+ memset((void *)(*alval), 0, (items+1)*sizeof(char *));
+ (*alval)[0] = ltmp[0];
+ if(newval)
+ *newval = &(*cl)->value;
+
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ i = 1;
+ }
+ }
+ else if(alval){
+ /*
+ * since we were previously empty, we want
+ * to replace the first CONF_S's value with
+ * the first new value, and fill the other
+ * in below if there's a list...
+ *
+ * first, make sure we're at the beginning of this config
+ * section and dump the config lines for the default list,
+ * except for the first one, which we will over-write.
+ */
+ *cl = (*cl)->varnamep;
+ while((*cl)->next && (*cl)->next->varnamep == (*cl)->varnamep)
+ snip_confline(&(*cl)->next);
+
+ /*
+ * now allocate the new user_val array and fill in the first entry.
+ */
+ *alval = (char **)fs_get((items+1)*sizeof(char *));
+ memset((void *)(*alval), 0, (items+1) * sizeof(char *));
+ (*alval)[(*cl)->varmem=0] = ltmp[0];
+ if(newval)
+ *newval = &(*cl)->value;
+
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ i = 1;
+ }
+
+ /*
+ * Make new cl's to fit in the new space. Move the value from the current
+ * line if inserting before it, else leave it where it is.
+ */
+ for(; i < items ; i++){
+ (*alval)[i+(*cl)->varmem + (after?1:0)] = ltmp[i];
+ tmp = (*cl)->value;
+ new_confline(cl);
+ if(after)
+ (*cl)->value = NULL;
+ else
+ (*cl)->value = tmp;
+
+ (*cl)->var = (*cl)->prev->var;
+ (*cl)->valoffset = (*cl)->prev->valoffset;
+ (*cl)->varoffset = (*cl)->prev->varoffset;
+ (*cl)->headingp = (*cl)->prev->headingp;
+ (*cl)->keymenu = (*cl)->prev->keymenu;
+ (*cl)->help = (*cl)->prev->help;
+ (*cl)->tool = (*cl)->prev->tool;
+ (*cl)->varnamep = (*cl)->prev->varnamep;
+ *cl = (*cl)->prev;
+ if(!after)
+ (*cl)->value = NULL;
+
+ if(newval){
+ if(after)
+ *newval = &(*cl)->next->value;
+ else
+ *newval = &(*cl)->value;
+ }
+ }
+
+ /*
+ * now fix up varmem values and fill in new values that have been
+ * left NULL
+ */
+ for(ctmp = (*cl)->varnamep, i = 0;
+ (*alval)[i];
+ ctmp = ctmp->next, i++){
+ ctmp->varmem = i;
+ if(!ctmp->value){
+ /* BUG: We should be able to do this without the temp
+ * copy...
+ */
+ char *ptmp = pretty_value(ps, ctmp);
+ ctmp->value = (ctmp->varnamep->flags & CF_PRINTER) ? printer_name(ptmp) : cpystr(ptmp);
+ fs_give((void **)&ptmp);
+ }
+ }
+}
+
+
+/*
+ *
+ */
+void
+config_del_list_item(CONF_S **cl, char ***newval)
+{
+ char **bufp, ***alval;
+ int i;
+ CONF_S *ctmp;
+
+ alval = ALVAL((*cl)->var, ew);
+
+ if((*alval)[(*cl)->varmem + 1]){
+ for(bufp = &(*alval)[(*cl)->varmem];
+ (*bufp = *(bufp+1)) != NULL; bufp++)
+ ;
+
+ if(*cl == (*cl)->varnamep){ /* leading value */
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ ctmp = (*cl)->next;
+ (*cl)->value = ctmp->value;
+ ctmp->value = NULL;
+ }
+ else{
+ ctmp = *cl; /* blast the confline */
+ *cl = (*cl)->next;
+ if(ctmp == opt_screen->top_line)
+ opt_screen->top_line = *cl;
+ }
+
+ snip_confline(&ctmp);
+
+ for(ctmp = (*cl)->varnamep, i = 0; /* now fix up varmem values */
+ (*alval)[i];
+ ctmp = ctmp->next, i++)
+ ctmp->varmem = i;
+ }
+ else if((*cl)->varmem){ /* blasted last in list */
+ ctmp = *cl;
+ *cl = (*cl)->prev;
+ if(ctmp == opt_screen->top_line)
+ opt_screen->top_line = *cl;
+
+ snip_confline(&ctmp);
+ }
+ else{ /* blasted last remaining */
+ if(alval && *alval)
+ fs_give((void **)alval);
+
+ *newval = &(*cl)->value;
+ }
+}
+
+
+/*
+ * feature list manipulation tool
+ *
+ *
+ * returns: -1 on unrecognized cmd, 0 if no change, 1 if change
+ */
+int
+checkbox_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+
+ switch(cmd){
+ case MC_TOGGLE: /* mark/unmark feature */
+ if((*cl)->var == &ps->vars[V_FEATURE_LIST]){
+ rv = 1;
+ toggle_feature_bit(ps, (*cl)->varmem, (*cl)->var, *cl, 0);
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 6,
+ "Programmer botch! Unknown checkbox type.");
+
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = config_exit_cmd(flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * simple radio-button style variable handler
+ */
+int
+radiobutton_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ char **apval;
+ int rv = 0;
+ NAMEVAL_S *rule = NULL;
+#ifndef _WINDOWS
+ int old_uc, old_cs;
+ CONF_S *ctmp;
+#endif
+
+ apval = APVAL((*cl)->var, ew);
+
+ switch(cmd){
+ case MC_CHOICE : /* set/unset feature */
+
+ if(fixed_var((*cl)->var, NULL, NULL)){
+ if(((*cl)->var->post_user_val.p || (*cl)->var->main_user_val.p)
+ && want_to(_("Delete old unused personal option setting"),
+ 'y', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ delete_user_vals((*cl)->var);
+ q_status_message(SM_ORDER, 0, 3, _("Deleted"));
+ rv = 1;
+ }
+
+ return(rv);
+ }
+
+ if(standard_radio_var(ps, (*cl)->var) || (*cl)->var == startup_ptr){
+ PTR_TO_RULEFUNC rulefunc;
+
+#ifndef _WINDOWS
+ if((*cl)->var == &ps->vars[V_COLOR_STYLE]){
+ old_uc = pico_usingcolor();
+ old_cs = ps->color_style;
+ }
+#endif
+
+ if((*cl)->var->cmdline_val.p)
+ fs_give((void **)&(*cl)->var->cmdline_val.p);
+
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ rulefunc = rulefunc_from_var(ps, (*cl)->var);
+ if(rulefunc)
+ rule = (*rulefunc)((*cl)->varmem);
+
+ if(apval && rule)
+ *apval = cpystr(S_OR_L(rule));
+
+ cur_rule_value((*cl)->var, TRUE, TRUE);
+ set_radio_pretty_vals(ps, cl);
+
+ if((*cl)->var == &ps->vars[V_AB_SORT_RULE])
+ addrbook_redo_sorts();
+ else if((*cl)->var == &ps->vars[V_THREAD_DISP_STYLE]){
+ clear_index_cache(ps->mail_stream, 0);
+ }
+ else if((*cl)->var == &ps->vars[V_THREAD_INDEX_STYLE]){
+ MAILSTREAM *m;
+ int i;
+
+ clear_index_cache(ps->mail_stream, 0);
+ /* clear all hidden and collapsed flags */
+ set_lflags(ps->mail_stream, ps->msgmap, MN_COLL | MN_CHID, 0);
+
+ if(SEP_THRDINDX()
+ && SORT_IS_THREADED(ps->msgmap)
+ && unview_thread(ps, ps->mail_stream, ps->msgmap)){
+ ps->next_screen = mail_index_screen;
+ ps->view_skipped_index = 0;
+ ps->mangled_screen = 1;
+ }
+
+ if(SORT_IS_THREADED(ps->msgmap)
+ && (SEP_THRDINDX() || COLL_THRDS()))
+ collapse_threads(ps->mail_stream, ps->msgmap, NULL);
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m)
+ sp_set_viewing_a_thread(m, 0);
+ }
+
+ adjust_cur_to_visible(ps->mail_stream, ps->msgmap);
+ }
+#ifndef _WINDOWS
+ else if((*cl)->var == &ps->vars[V_COLOR_STYLE]){
+ if(old_cs != ps->color_style){
+ pico_toggle_color(0);
+ switch(ps->color_style){
+ case COL_NONE:
+ case COL_TERMDEF:
+ pico_set_color_options(pico_trans_color() ? COLOR_TRANS_OPT : 0);
+ break;
+ case COL_ANSI8:
+ pico_set_color_options(COLOR_ANSI8_OPT|COLOR_TRANS_OPT);
+ break;
+ case COL_ANSI16:
+ pico_set_color_options(COLOR_ANSI16_OPT|COLOR_TRANS_OPT);
+ break;
+ case COL_ANSI256:
+ pico_set_color_options(COLOR_ANSI256_OPT|COLOR_TRANS_OPT);
+ break;
+ }
+
+ if(ps->color_style != COL_NONE)
+ pico_toggle_color(1);
+ }
+
+ if(pico_usingcolor())
+ pico_set_normal_color();
+
+ if(!old_uc && pico_usingcolor()){
+
+ /*
+ * remove the explanatory warning line and a blank line
+ */
+
+ /* first find the first blank line */
+ for(ctmp = *cl; ctmp; ctmp = next_confline(ctmp))
+ if(ctmp->flags & CF_NOSELECT)
+ break;
+
+ if(ctmp && ctmp->flags & CF_NOSELECT &&
+ ctmp->prev && !(ctmp->prev->flags & CF_NOSELECT) &&
+ ctmp->next && ctmp->next->flags & CF_NOSELECT &&
+ ctmp->next->next &&
+ ctmp->next->next->flags & CF_NOSELECT){
+ ctmp->prev->next = ctmp->next->next;
+ ctmp->next->next->prev = ctmp->prev;
+ ctmp->next->next = NULL;
+ free_conflines(&ctmp);
+ }
+
+ /* make all the colors selectable */
+ for(ctmp = *cl; ctmp; ctmp = next_confline(ctmp))
+ if(ctmp->flags & CF_POT_SLCTBL)
+ ctmp->flags &= ~CF_NOSELECT;
+ }
+ else if(old_uc && !pico_usingcolor()){
+
+ /*
+ * add the explanatory warning line and a blank line
+ */
+
+ /* first find the existing blank line */
+ for(ctmp = *cl; ctmp; ctmp = next_confline(ctmp))
+ if(ctmp->flags & CF_NOSELECT)
+ break;
+
+ /* add the explanatory warning line */
+ new_confline(&ctmp);
+ ctmp->help = NO_HELP;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(COLORNOSET);
+
+ /* and add another blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= (CF_NOSELECT | CF_B_LINE);
+
+ /* make all the colors non-selectable */
+ for(ctmp = *cl; ctmp; ctmp = next_confline(ctmp))
+ if(ctmp->flags & CF_POT_SLCTBL)
+ ctmp->flags |= CF_NOSELECT;
+ }
+
+ clear_index_cache(ps->mail_stream, 0);
+ ClearScreen();
+ ps->mangled_screen = 1;
+ }
+#endif
+
+ ps->mangled_body = 1; /* BUG: redraw it all for now? */
+ rv = 1;
+ }
+ else if((*cl)->var == &ps->vars[V_SORT_KEY]){
+ SortOrder def_sort;
+ int def_sort_rev;
+
+ def_sort_rev = (*cl)->varmem >= (short) EndofList;
+ def_sort = (SortOrder) ((*cl)->varmem - (def_sort_rev
+ * EndofList));
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s", sort_name(def_sort),
+ (def_sort_rev) ? "/Reverse" : "");
+
+ if((*cl)->var->cmdline_val.p)
+ fs_give((void **)&(*cl)->var->cmdline_val.p);
+
+ if(apval){
+ if(*apval)
+ fs_give((void **)apval);
+
+ *apval = cpystr(tmp_20k_buf);
+ }
+
+ set_current_val((*cl)->var, TRUE, TRUE);
+ if(decode_sort(ps->VAR_SORT_KEY, &def_sort, &def_sort_rev) != -1){
+ ps->def_sort = def_sort;
+ ps->def_sort_rev = def_sort_rev;
+ }
+
+ set_radio_pretty_vals(ps, cl);
+ ps->mangled_body = 1; /* BUG: redraw it all for now? */
+ rv = 1;
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 6,
+ "Programmer botch! Unknown radiobutton type.");
+
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = config_exit_cmd(flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ if(rv == 1)
+ exception_override_warning((*cl)->var);
+
+ return(rv);
+}
+
+
+/*
+ * simple yes/no style variable handler
+ */
+int
+yesno_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0, yes = 0;
+ char *pval, **apval;
+
+ pval = PVAL((*cl)->var, ew);
+ apval = APVAL((*cl)->var, ew);
+
+ switch(cmd){
+ case MC_TOGGLE: /* toggle yes to no and back */
+ if(fixed_var((*cl)->var, NULL, NULL)){
+ if(((*cl)->var->post_user_val.p || (*cl)->var->main_user_val.p)
+ && want_to(_("Delete old unused personal option setting"),
+ 'y', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ delete_user_vals((*cl)->var);
+ q_status_message(SM_ORDER, 0, 3, _("Deleted"));
+ rv = 1;
+ }
+
+ return(rv);
+ }
+
+ rv = 1;
+ yes = ((pval && !strucmp(pval, yesstr)) ||
+ (!pval && (*cl)->var->current_val.p &&
+ !strucmp((*cl)->var->current_val.p, yesstr)));
+ fs_give((void **)&(*cl)->value);
+
+ if(apval){
+ if(*apval)
+ fs_give((void **)apval);
+
+ if(yes)
+ *apval = cpystr(nostr);
+ else
+ *apval = cpystr(yesstr);
+ }
+
+ set_current_val((*cl)->var, FALSE, FALSE);
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ (*cl)->value = pretty_value(ps, *cl);
+ fix_side_effects(ps, (*cl)->var, 0);
+
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = config_exit_cmd(flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Manage display of the config/options menu body.
+ */
+void
+update_option_screen(struct pine *ps, OPT_SCREEN_S *screen, Pos *cursor_pos)
+{
+ int dline, w, save = '\0';
+ CONF_S *top_line, *ctmp;
+ char *value;
+ unsigned got_width;
+ int want_width, first_width;
+ char *saveptr = NULL;
+
+#ifdef _WINDOWS
+ int last_selectable;
+ mswin_beginupdate();
+#endif
+ if(screen == NULL)
+ return;
+
+ if(cursor_pos){
+ cursor_pos->col = 0;
+ cursor_pos->row = -1; /* to tell us if we've set it yet */
+ }
+
+ /*
+ * calculate top line of display for reframing if the current field
+ * is off the display defined by screen->top_line...
+ */
+ if((ctmp = screen->top_line) != NULL)
+ for(dline = BODY_LINES(ps);
+ dline && ctmp && ctmp != screen->current;
+ ctmp = next_confline(ctmp), dline--)
+ ;
+
+ if(!ctmp || !dline){ /* force reframing */
+ dline = 0;
+ ctmp = top_line = first_confline(screen->current);
+ do
+ if(((dline++)%BODY_LINES(ps)) == 0)
+ top_line = ctmp;
+ while(ctmp != screen->current && (ctmp = next_confline(ctmp)));
+ }
+ else
+ top_line = screen->top_line;
+
+#ifdef _WINDOWS
+ /*
+ * Figure out how far down the top line is from the top and how many
+ * total lines there are. Dumb to loop every time thru, but
+ * there aren't that many lines, and it's cheaper than rewriting things
+ * to maintain a line count in each structure...
+ */
+ for(dline = 0, ctmp = prev_confline(top_line); ctmp; ctmp = prev_confline(ctmp))
+ dline++;
+
+ scroll_setpos(dline);
+ last_selectable = dline;
+ for(ctmp = next_confline(top_line); ctmp ; ctmp = next_confline(ctmp)){
+ dline++;
+ if (!(ctmp->flags & CF_NOSELECT))
+ last_selectable = dline;
+ }
+ dline = last_selectable;
+ scroll_setrange(BODY_LINES(ps), dline);
+#endif
+
+ /* mangled body or new page, force redraw */
+ if(ps->mangled_body || screen->top_line != top_line)
+ screen->prev = NULL;
+
+ /* loop thru painting what's needed */
+ for(dline = 0, ctmp = top_line;
+ dline < BODY_LINES(ps);
+ dline++, ctmp = next_confline(ctmp)){
+
+ /*
+ * only fall thru painting if something needs painting...
+ */
+ if(!(!screen->prev || ctmp == screen->prev || ctmp == screen->current
+ || ctmp == screen->prev->varnamep
+ || ctmp == screen->current->varnamep
+ || ctmp == screen->prev->headingp
+ || ctmp == screen->current->headingp))
+ continue;
+
+ ClearLine(dline + HEADER_ROWS(ps));
+
+ if(ctmp){
+ if(ctmp->flags & CF_B_LINE)
+ continue;
+
+ if(ctmp->varname && !(ctmp->flags & CF_INVISIBLEVAR)){
+ if(ctmp == screen->current && cursor_pos)
+ cursor_pos->row = dline + HEADER_ROWS(ps);
+
+ if((ctmp == screen->current
+ || ctmp == screen->current->varnamep
+ || ctmp == screen->current->headingp)
+ && !(ctmp->flags & CF_NOHILITE))
+ StartInverse();
+
+ if(ctmp->flags & CF_H_LINE){
+ MoveCursor(dline + HEADER_ROWS(ps), 0);
+ Write_to_screen(repeat_char(ps->ttyo->screen_cols, '-'));
+ }
+
+ if(ctmp->flags & CF_CENTERED){
+ int offset = ps->ttyo->screen_cols/2
+ - (utf8_width(ctmp->varname)/2);
+ MoveCursor(dline + HEADER_ROWS(ps),
+ (offset > 0) ? offset : 0);
+ }
+ else if(ctmp->varoffset)
+ MoveCursor(dline+HEADER_ROWS(ps), ctmp->varoffset);
+
+ Write_to_screen(ctmp->varname);
+ if((ctmp == screen->current
+ || ctmp == screen->current->varnamep
+ || ctmp == screen->current->headingp)
+ && !(ctmp->flags & CF_NOHILITE))
+ EndInverse();
+ }
+
+ value = (ctmp->flags & CF_INHERIT) ? INHERIT : ctmp->value;
+ if(value){
+ char *p;
+ int i, j;
+
+ memset(tmp_20k_buf, '\0',
+ (6*ps->ttyo->screen_cols + 1) * sizeof(char));
+ if(ctmp == screen->current){
+ if(!(ctmp->flags & CF_DOUBLEVAR && ctmp->flags & CF_VAR2))
+ StartInverse();
+
+ if(cursor_pos)
+ cursor_pos->row = dline + HEADER_ROWS(ps);
+ }
+
+ if(ctmp->flags & CF_H_LINE)
+ memset(tmp_20k_buf, '-',
+ ps->ttyo->screen_cols * sizeof(char));
+
+ if(ctmp->flags & CF_CENTERED){
+ int offset = ps->ttyo->screen_cols/2
+ - (utf8_width(value)/2);
+ /* BUG: tabs screw us figuring length above */
+ if(offset > 0){
+ char *q;
+
+ p = tmp_20k_buf + offset;
+ if(!*(q = tmp_20k_buf))
+ while(q < p)
+ *q++ = ' ';
+ }
+ }
+ else
+ p = tmp_20k_buf;
+
+ /*
+ * Copy the value to a temp buffer expanding tabs, and
+ * making sure not to write beyond screen right...
+ */
+ for(i = 0, j = ctmp->valoffset; value[i]; i++){
+ if(value[i] == ctrl('I')){
+ do
+ *p++ = ' ';
+ while((++j) & 0x07);
+ }
+ else{
+ *p++ = value[i];
+ j++;
+ }
+ }
+
+ if(ctmp == screen->current && cursor_pos){
+ if(ctmp->flags & CF_DOUBLEVAR && ctmp->flags & CF_VAR2)
+ cursor_pos->col = ctmp->val2offset;
+ else
+ cursor_pos->col = ctmp->valoffset;
+
+ if(ctmp->tool == radiobutton_tool
+#ifdef ENABLE_LDAP
+ || ctmp->tool==ldap_radiobutton_tool
+#endif
+ || ctmp->tool==role_radiobutton_tool
+ || ctmp->tool==checkbox_tool
+ || (ctmp->tool==color_setting_tool &&
+ ctmp->valoffset != COLOR_INDENT))
+ cursor_pos->col++;
+ }
+
+ if(ctmp->flags & CF_DOUBLEVAR){
+ long l;
+
+ p = tmp_20k_buf;
+ first_width = ctmp->val2offset - ctmp->valoffset - SPACE_BETWEEN_DOUBLEVARS;
+ if((l=utf8_width(p)) > first_width && first_width >= 0){
+ saveptr = utf8_count_forw_width(p, first_width, &got_width);
+ /*
+ * got_width != first_width indicates there's a problem
+ * that should not happen. Ignore it.
+ */
+ if(saveptr){
+ save = *saveptr;
+ *saveptr = '\0';
+ }
+ }
+ else
+ save = '\0';
+
+ /*
+ * If this is a COLOR_BLOB line we do special coloring.
+ * The current object inverse hilite is only on the
+ * checkbox part, the exact format comes from the
+ * new_color_line function. If we change that we'll have
+ * to change this to get the coloring right.
+ */
+ if(p[0] == '(' && p[2] == ')' &&
+ p[3] == ' ' && p[4] == ' ' &&
+ (!strncmp(p+5, COLOR_BLOB, COLOR_BLOB_LEN)
+ || !strncmp(p+5, COLOR_BLOB_TRAN, COLOR_BLOB_LEN)
+ || !strncmp(p+5, COLOR_BLOB_NORM, COLOR_BLOB_LEN))){
+ COLOR_PAIR *lastc = NULL, *newc = NULL;
+
+ MoveCursor(dline+HEADER_ROWS(ps), ctmp->valoffset);
+ Write_to_screen_n(p, 3);
+ if(!(ctmp->flags & CF_VAR2) && ctmp == screen->current)
+ EndInverse();
+
+ Write_to_screen_n(p+3, 3);
+ newc = new_color_pair(colorx(CFC_ICOLOR(ctmp)),
+ colorx(CFC_ICOLOR(ctmp)));
+ if(newc){
+ lastc = pico_get_cur_color();
+ (void)pico_set_colorp(newc, PSC_NONE);
+ free_color_pair(&newc);
+ }
+
+ Write_to_screen_n(p+6, COLOR_BLOB_LEN-2);
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ Write_to_screen(p+6+COLOR_BLOB_LEN-2);
+ }
+ else{
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset, p);
+ if(!(ctmp->flags & CF_VAR2) && ctmp == screen->current)
+ EndInverse();
+ }
+
+ if(saveptr)
+ *saveptr = save;
+
+ PutLine0(dline+HEADER_ROWS(ps),
+ ctmp->val2offset - SPACE_BETWEEN_DOUBLEVARS,
+ repeat_char(SPACE_BETWEEN_DOUBLEVARS, SPACE));
+
+ if(l > ctmp->val2offset - ctmp->valoffset && ctmp->val2offset - ctmp->valoffset >= 0)
+ p = saveptr + SPACE_BETWEEN_DOUBLEVARS;
+
+ if(p > tmp_20k_buf){
+ if(ctmp->flags & CF_VAR2 && ctmp == screen->current)
+ StartInverse();
+
+ if(p[0] == '(' && p[2] == ')' &&
+ p[3] == ' ' && p[4] == ' ' &&
+ (!strncmp(p+5, COLOR_BLOB, COLOR_BLOB_LEN)
+ || !strncmp(p+5, COLOR_BLOB_TRAN, COLOR_BLOB_LEN)
+ || !strncmp(p+5, COLOR_BLOB_NORM, COLOR_BLOB_LEN))){
+ COLOR_PAIR *lastc = NULL, *newc = NULL;
+
+ MoveCursor(dline+HEADER_ROWS(ps), ctmp->val2offset);
+ Write_to_screen_n(p, 3);
+ if(ctmp->flags & CF_VAR2 && ctmp == screen->current)
+ EndInverse();
+
+ Write_to_screen_n(p+3, 3);
+ newc = new_color_pair(colorx(CFC_ICOLOR(ctmp)),
+ colorx(CFC_ICOLOR(ctmp)));
+ if(newc){
+ lastc = pico_get_cur_color();
+ (void)pico_set_colorp(newc, PSC_NONE);
+ free_color_pair(&newc);
+ }
+
+ Write_to_screen_n(p+6, COLOR_BLOB_LEN-2);
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ Write_to_screen(p+6+COLOR_BLOB_LEN-2);
+ }
+ else{
+ PutLine0(dline+HEADER_ROWS(ps),ctmp->val2offset,p);
+ if(ctmp->flags & CF_VAR2 && ctmp == screen->current)
+ EndInverse();
+ }
+ }
+ }
+ else{
+ char *q, *first_space, *sample, *ptr;
+ COLOR_PAIR *lastc, *newc;
+ int invert;
+
+
+ if(ctmp->flags & CF_COLORSAMPLE &&
+ pico_usingcolor() &&
+ ((q = strstr(tmp_20k_buf, SAMPLE_LEADER)) ||
+ (q = strstr(tmp_20k_buf, "Color"))) &&
+ (first_space = strindex(q, SPACE)) &&
+ (strstr(value, SAMP1) ||
+ strstr(value, SAMP2))){
+
+ ptr = tmp_20k_buf;
+
+ /* write out first part */
+ *first_space = '\0';
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset,
+ ptr);
+ *first_space = SPACE;
+ ptr = first_space;
+
+ if(ctmp == screen->current)
+ EndInverse();
+
+ sample = skip_white_space(ptr);
+ /* if there's enough room to put some sample up */
+ save = *sample;
+ *sample = '\0';
+ w = utf8_width(tmp_20k_buf);
+ *sample = save;
+ if(ctmp->valoffset + w < ps->ttyo->screen_cols){
+
+ sample++; /* for `[' at edge of sample */
+
+ save = *ptr;
+ *ptr = '\0';
+ w = utf8_width(tmp_20k_buf);
+ *ptr = save;
+
+ save = *sample;
+ *sample = '\0';
+ /* spaces and bracket before sample1 */
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset+w, ptr);
+ *sample = save;
+
+ ptr = sample;
+
+ /* then the color sample */
+ if(ctmp->var == &ps->vars[V_VIEW_HDR_COLORS]){
+ SPEC_COLOR_S *hc, *hcolors;
+
+ lastc = newc = NULL;
+
+ hcolors =
+ spec_colors_from_varlist(LVAL(ctmp->var, ew),
+ 0);
+ for(hc = hcolors, i=0; hc; hc = hc->next, i++)
+ if(CFC_ICUST(ctmp) == i)
+ break;
+
+ if(hc && hc->fg && hc->fg[0] && hc->bg &&
+ hc->bg[0])
+ newc = new_color_pair(hc->fg, hc->bg);
+
+ if(newc){
+ lastc = pico_get_cur_color();
+ (void)pico_set_colorp(newc, PSC_NONE);
+ free_color_pair(&newc);
+ }
+
+ if(hcolors)
+ free_spec_colors(&hcolors);
+
+
+ /* print out sample1 */
+
+ save = *ptr;
+ *ptr = '\0';
+ w = utf8_width(tmp_20k_buf);
+ *ptr = save;
+
+ want_width = MIN(utf8_width(SAMP1)-2, ps->ttyo->screen_cols - w - ctmp->valoffset);
+ saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
+ if(saveptr){
+ save = *saveptr;
+ *saveptr = '\0';
+ }
+
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
+ if(saveptr)
+ *saveptr = save;
+
+ ptr = strindex(ptr, ']');
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+ }
+ else if(ctmp->var == &ps->vars[V_KW_COLORS]){
+ KEYWORD_S *kw;
+ SPEC_COLOR_S *kw_col = NULL;
+
+ lastc = newc = NULL;
+
+ /* find keyword associated with this line */
+ for(kw=ps->keywords, i=0; kw; kw=kw->next, i++)
+ if(CFC_ICUST(ctmp) == i)
+ break;
+
+ if(kw)
+ kw_col =
+ spec_colors_from_varlist(LVAL(ctmp->var,ew),
+ 0);
+
+ /* color for this keyword */
+ if(kw && kw_col
+ && ((kw->nick && kw->nick[0]
+ && (newc=hdr_color(kw->nick, NULL,
+ kw_col)))
+ ||
+ (kw->kw && kw->kw[0]
+ && (newc=hdr_color(kw->kw, NULL,
+ kw_col))))){
+ lastc = pico_get_cur_color();
+ (void)pico_set_colorp(newc, PSC_NONE);
+ free_color_pair(&newc);
+ }
+
+ if(kw_col)
+ free_spec_colors(&kw_col);
+
+ /* print out sample1 */
+
+ save = *ptr;
+ *ptr = '\0';
+ w = utf8_width(tmp_20k_buf);
+ *ptr = save;
+
+ want_width = MIN(utf8_width(SAMP1)-2, ps->ttyo->screen_cols - w - ctmp->valoffset);
+ saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
+ if(saveptr){
+ save = *saveptr;
+ *saveptr = '\0';
+ }
+
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
+ if(saveptr)
+ *saveptr = save;
+
+ ptr = strindex(ptr, ']');
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+ }
+ else{
+ lastc = NULL;
+ invert = 0;
+ newc = sample_color(ps, ctmp->var);
+ if(newc){
+ if((lastc = pico_get_cur_color()) != NULL)
+ (void)pico_set_colorp(newc, PSC_NONE);
+
+ free_color_pair(&newc);
+ }
+ else if(var_defaults_to_rev(ctmp->var)){
+ if((newc = pico_get_rev_color()) != NULL){
+ /*
+ * Note, don't have to free newc.
+ */
+ if((lastc = pico_get_cur_color()) != NULL)
+ (void)pico_set_colorp(newc, PSC_NONE);
+ }
+ else{
+ StartInverse();
+ invert = 1;
+ }
+ }
+
+ if(ctmp->var==&ps->vars[V_SLCTBL_FORE_COLOR] &&
+
+ (F_OFF(F_SLCTBL_ITEM_NOBOLD, ps) ||
+ !(PVAL(ctmp->var,ew) &&
+ PVAL(ctmp->var+1,ew))))
+ StartBold();
+
+ /* print out sample1 */
+
+ save = *ptr;
+ *ptr = '\0';
+ w = utf8_width(tmp_20k_buf);
+ *ptr = save;
+
+ want_width = MIN(utf8_width(SAMP1)-2, ps->ttyo->screen_cols - w - ctmp->valoffset);
+ saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
+ if(saveptr){
+ save = *saveptr;
+ *saveptr = '\0';
+ }
+
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
+ if(saveptr)
+ *saveptr = save;
+
+ ptr = strindex(ptr, ']');
+
+ if(ctmp->var==&ps->vars[V_SLCTBL_FORE_COLOR] &&
+
+ (F_OFF(F_SLCTBL_ITEM_NOBOLD, ps) ||
+ !(PVAL(ctmp->var,ew) &&
+ PVAL(ctmp->var+1,ew))))
+ EndBold();
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+ else if(invert)
+ EndInverse();
+ }
+
+ /*
+ * Finish sample1 with the right bracket.
+ */
+ save = *ptr;
+ *ptr = '\0';
+ w = utf8_width(tmp_20k_buf);
+ *ptr = save;
+ if(ctmp->valoffset + w < ps->ttyo->screen_cols){
+ save = *(ptr+1);
+ *(ptr+1) = '\0';
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
+ *(ptr+1) = save;
+ ptr++;
+ w++;
+ }
+
+ /*
+ * Now check for an exception sample and paint it.
+ */
+ if(ctmp->valoffset + w + SBS + 1 < ps->ttyo->screen_cols && (q = strstr(ptr, SAMPEXC))){
+ /* spaces + `[' */
+ save = ptr[SBS+1];
+ ptr[SBS+1] = '\0';
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
+ ptr[SBS+1] = save;
+ ptr += (SBS+1);
+
+ /*
+ * Figure out what color to paint it.
+ * This only happens with normal variables,
+ * not with V_VIEW_HDR_COLORS.
+ */
+ lastc = NULL;
+ invert = 0;
+ newc = sampleexc_color(ps, ctmp->var);
+ if(newc){
+ if((lastc = pico_get_cur_color()) != NULL)
+ (void)pico_set_colorp(newc, PSC_NONE);
+
+ free_color_pair(&newc);
+ }
+ else if(var_defaults_to_rev(ctmp->var)){
+ if((newc = pico_get_rev_color()) != NULL){
+ /*
+ * Note, don't have to free newc.
+ */
+ if((lastc = pico_get_cur_color()) != NULL)
+ (void)pico_set_colorp(newc, PSC_NONE);
+ }
+ else{
+ StartInverse();
+ invert = 1;
+ }
+ }
+
+ if(ctmp->var==&ps->vars[V_SLCTBL_FORE_COLOR] &&
+
+ (F_OFF(F_SLCTBL_ITEM_NOBOLD, ps) ||
+ !(PVAL(ctmp->var,Post) &&
+ PVAL(ctmp->var+1,Post))))
+ StartBold();
+
+ /* sample2 */
+ save = *ptr;
+ *ptr = '\0';
+ w = utf8_width(tmp_20k_buf);
+ *ptr = save;
+
+ want_width = MIN(utf8_width(SAMPEXC)-2, ps->ttyo->screen_cols - w - ctmp->valoffset);
+ saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
+ if(saveptr){
+ save = *saveptr;
+ *saveptr = '\0';
+ }
+
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
+ if(saveptr)
+ *saveptr = save;
+
+ ptr = strindex(ptr, ']');
+
+ /* turn off bold and color */
+ if(ctmp->var==&ps->vars[V_SLCTBL_FORE_COLOR] &&
+
+ (F_OFF(F_SLCTBL_ITEM_NOBOLD, ps) ||
+ !(PVAL(ctmp->var,Post) &&
+ PVAL(ctmp->var+1,Post))))
+ EndBold();
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+ else if(invert)
+ EndInverse();
+
+ /*
+ * Finish sample2 with the right bracket.
+ */
+ save = *ptr;
+ *ptr = '\0';
+ w = utf8_width(tmp_20k_buf);
+ *ptr = save;
+ if(ctmp->valoffset + w < ps->ttyo->screen_cols){
+ save = *(ptr+1);
+ *(ptr+1) = '\0';
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
+ *(ptr+1) = save;
+ ptr++;
+ w++;
+ }
+ }
+
+ /* paint rest of the line if there is any left */
+ if(ctmp->valoffset + w < ps->ttyo->screen_cols && *ptr){
+ want_width = ps->ttyo->screen_cols - w - ctmp->valoffset;
+ saveptr = utf8_count_forw_width(ptr, want_width, &got_width);
+ if(saveptr){
+ save = *saveptr;
+ *saveptr = '\0';
+ }
+
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset + w, ptr);
+ if(saveptr)
+ *saveptr = save;
+ }
+ }
+ }
+ else{
+ w = utf8_width(tmp_20k_buf);
+ want_width = ps->ttyo->screen_cols - ctmp->valoffset;
+ if(w > want_width){
+ saveptr = utf8_count_forw_width(tmp_20k_buf, want_width, &got_width);
+ if(saveptr)
+ *saveptr = '\0';
+ }
+
+ PutLine0(dline+HEADER_ROWS(ps), ctmp->valoffset, tmp_20k_buf);
+ if(ctmp == screen->current)
+ EndInverse();
+ }
+ }
+ }
+ }
+ }
+
+ ps->mangled_body = 0;
+ screen->top_line = top_line;
+ screen->prev = screen->current;
+#ifdef _WINDOWS
+ mswin_endupdate();
+#endif
+}
+
+
+
+/*
+ *
+ */
+void
+print_option_screen(OPT_SCREEN_S *screen, char *prompt)
+{
+ CONF_S *ctmp;
+ int so_far;
+ char line[500];
+
+ if(open_printer(prompt) == 0){
+ for(ctmp = first_confline(screen->current);
+ ctmp;
+ ctmp = next_confline(ctmp)){
+
+ so_far = 0;
+ if(ctmp->varname && !(ctmp->flags & CF_INVISIBLEVAR)){
+
+ snprintf(line, sizeof(line), "%*s%s", ctmp->varoffset, "",
+ ctmp->varname);
+ line[sizeof(line)-1] = '\0';
+ print_text(line);
+ so_far = ctmp->varoffset + utf8_width(ctmp->varname);
+ }
+
+ if(ctmp && ctmp->value){
+ char *p = tmp_20k_buf;
+ int i, j, spaces;
+
+ /* Copy the value to a temp buffer expanding tabs. */
+ for(i = 0, j = ctmp->valoffset; ctmp->value[i]; i++){
+ if(ctmp->value[i] == ctrl('I')){
+ do
+ *p++ = ' ';
+ while((++j) & 0x07);
+
+ }
+ else{
+ *p++ = ctmp->value[i];
+ j++;
+ }
+ }
+
+ *p = '\0';
+ removing_trailing_white_space(tmp_20k_buf);
+
+ spaces = MAX(ctmp->valoffset - so_far, 0);
+ snprintf(line, sizeof(line), "%*s%s\n", spaces, "", tmp_20k_buf);
+ line[sizeof(line)-1] = '\0';
+ print_text(line);
+ }
+ }
+
+ close_printer();
+ }
+}
+
+
+
+/*
+ *
+ */
+void
+option_screen_redrawer(void)
+{
+ ps_global->mangled_body = 1;
+ update_option_screen(ps_global, opt_screen, (Pos *)NULL);
+}
+
+
+
+/*
+ * pretty_value - given the line, return an
+ * alloc'd string for line's value...
+ */
+char *
+pretty_value(struct pine *ps, CONF_S *cl)
+{
+ struct variable *v;
+
+ v = cl->var;
+
+ if(v == &ps->vars[V_FEATURE_LIST])
+ return(checkbox_pretty_value(ps, cl));
+ else if(standard_radio_var(ps, v) || v == startup_ptr)
+ return(radio_pretty_value(ps, cl));
+ else if(v == &ps->vars[V_SORT_KEY])
+ return(sort_pretty_value(ps, cl));
+ else if(v == &ps->vars[V_SIGNATURE_FILE])
+ return(sigfile_pretty_value(ps, cl));
+ else if(v == &ps->vars[V_USE_ONLY_DOMAIN_NAME])
+ return(yesno_pretty_value(ps, cl));
+ else if(color_holding_var(ps, v))
+ return(color_pretty_value(ps, cl));
+ else
+ return(text_pretty_value(ps, cl));
+}
+
+
+char *
+text_pretty_value(struct pine *ps, CONF_S *cl)
+{
+ char tmp[6*MAX_SCREEN_COLS+20], *pvalnorm, **lvalnorm, *pvalexc, **lvalexc;
+ char *p, *pval, **lval, lastchar = '\0';
+ int editing_except, fixed, uvalset, uvalposlen;
+ unsigned got_width;
+ int comments, except_set, avail_width;
+ int norm_with_except = 0, norm_with_except_inherit = 0;
+ int inherit_line = 0;
+
+ editing_except = (ew == ps_global->ew_for_except_vars);
+ fixed = cl->var->is_fixed;
+ if((ps_global->ew_for_except_vars != Main) && (ew == Main))
+ norm_with_except++; /* editing normal and except config exists */
+
+ if(cl->var->is_list){
+ lvalnorm = LVAL(cl->var, Main);
+ lvalexc = LVAL(cl->var, ps_global->ew_for_except_vars);
+ if(editing_except){
+ uvalset = lvalexc != NULL;
+ uvalposlen = uvalset && lvalexc[0] && lvalexc[0][0];
+ lval = lvalexc;
+ }
+ else{
+ uvalset = lvalnorm != NULL;
+ uvalposlen = uvalset && lvalnorm[0] && lvalnorm[0][0];
+ lval = lvalnorm;
+ }
+
+ except_set = lvalexc != NULL;
+ comments = cl->var->current_val.l != NULL;
+ if(norm_with_except && except_set && lvalexc[0] &&
+ !strcmp(lvalexc[0],INHERIT))
+ norm_with_except_inherit++;
+
+ if(uvalset && !strcmp(lval[0], INHERIT)){
+ if(cl->varmem == 0){
+ inherit_line++;
+ comments = 0;
+ }
+ }
+
+ /* only add extra comments on last member of list */
+ if(uvalset && !inherit_line && lval && lval[cl->varmem] &&
+ lval[cl->varmem + 1])
+ comments = 0;
+ }
+ else{
+ pvalnorm = PVAL(cl->var, Main);
+ pvalexc = PVAL(cl->var, ps_global->ew_for_except_vars);
+ if(editing_except){
+ uvalset = pvalexc != NULL;
+ uvalposlen = uvalset && *pvalexc;
+ pval = pvalexc;
+ }
+ else{
+ uvalset = pvalnorm != NULL;
+ uvalposlen = uvalset && *pvalnorm;
+ pval = pvalnorm;
+ }
+
+ except_set = pvalexc != NULL;
+ comments = cl->var->current_val.p != NULL;
+ }
+
+ memset(tmp, 0, sizeof(tmp));
+ p = tmp;
+ *p = '\0';
+
+ avail_width = ps->ttyo->screen_cols - cl->valoffset;
+
+ if(fixed || !uvalset || !uvalposlen){
+ p += utf8_to_width(p, "<", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+
+ if(fixed){
+ p += utf8_to_width(p, _(fixed_val), sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else if(!uvalset){
+ p += utf8_to_width(p, _(no_val), sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else if(!uvalposlen){
+ p += utf8_to_width(p, _(empty_val), sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else if(inherit_line){
+ p += utf8_to_width(p, INHERIT, sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else{
+ if(cl->var->is_list){
+ p += utf8_to_width(p, lval[cl->varmem], sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else{
+ p += utf8_to_width(p, pval, sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ }
+
+ if(comments && (fixed || !uvalset || (norm_with_except && except_set))){
+ if(fixed || !uvalset){
+ p += utf8_to_width(p, ": using ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+
+ if(norm_with_except && except_set){
+ if(!uvalset){
+ p += utf8_to_width(p, "exception ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else if(!fixed){
+ if(!uvalposlen){
+ p += utf8_to_width(p, ": ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else{
+ p += utf8_to_width(p, " (", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+
+ if(norm_with_except_inherit){
+ p += utf8_to_width(p, "added to by exception ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else{
+ p += utf8_to_width(p, "overridden by exception ", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ }
+ }
+
+ if(avail_width >= 7){
+ if(cl->var == &ps_global->vars[V_POST_CHAR_SET]){
+ p += utf8_to_width(p, "most specific (see help)", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ else{
+ sstrncpy(&p, "\"", sizeof(tmp)-(p-tmp));
+ avail_width--;
+ if(cl->var->is_list){
+ char **the_list;
+
+ the_list = cl->var->current_val.l;
+
+ if(norm_with_except && except_set)
+ the_list = lvalexc;
+
+ if(the_list && the_list[0] && !strcmp(the_list[0], INHERIT))
+ the_list++;
+
+ for(lval = the_list; avail_width-(p-tmp) > 0 && *lval; lval++){
+ if(lval != the_list){
+ p += utf8_to_width(p, ",", sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+
+ p += utf8_to_width(p, *lval, sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+ }
+ else{
+ p += utf8_to_width(p, cl->var->current_val.p, sizeof(tmp)-(p-tmp), avail_width, &got_width);
+ avail_width -= got_width;
+ }
+
+ if(p-tmp+2 < sizeof(tmp)){
+ *p++ = '\"';
+ *p = '\0';
+ }
+ }
+ }
+ else if(*(p-1) == SPACE)
+ *--p = '\0';
+ }
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ if(fixed || !uvalset || !uvalposlen)
+ lastchar = '>';
+ else if(comments && norm_with_except && except_set)
+ lastchar = ')';
+
+ if(lastchar){
+ if(p-tmp+2 < sizeof(tmp)){
+ *p++ = lastchar;
+ *p = '\0';
+ }
+ }
+
+ tmp[sizeof(tmp)-1] = '\0';
+ avail_width = ps->ttyo->screen_cols - cl->valoffset;
+
+ if(utf8_width(tmp) < avail_width)
+ snprintf(tmp+strlen(tmp), sizeof(tmp)-strlen(tmp), "%*s", avail_width-utf8_width(tmp), "");
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ return(cpystr(tmp));
+}
+
+
+char *
+checkbox_pretty_value(struct pine *ps, CONF_S *cl)
+{
+ char tmp[6*MAXPATH];
+ char *comment = NULL;
+ int indent, x, screen_width, need;
+ int longest_featname, longest_comment;
+ int nwidcomm; /* name width with comment */
+ int nwidnocomm; /* and without comment */
+ FEATURE_S *feature;
+
+ screen_width = (ps && ps->ttyo) ? ps->ttyo->screen_cols : 80;
+ tmp[0] = '\0';
+
+ longest_featname = longest_feature_name();
+ longest_comment = longest_feature_comment(ps, ew);
+ indent = feature_indent();
+
+ nwidcomm = longest_featname;
+ nwidnocomm = longest_featname + 2 + longest_comment;
+
+ if((need = (indent + 5 + longest_featname + 2 + longest_comment) - screen_width) > 0){
+ if(need < 10){
+ nwidcomm -= need;
+ nwidnocomm -= need;
+ }
+ else{
+ longest_comment = 0;
+ nwidnocomm = longest_featname;
+ }
+ }
+
+ feature = feature_list(cl->varmem);
+
+ x = feature_gets_an_x(ps, cl->var, feature, &comment, ew);
+
+ if(longest_comment && comment && *comment){
+ utf8_snprintf(tmp, sizeof(tmp), "[%c] %-*.*w %-*.*w", x ? 'X' : ' ',
+ nwidcomm, nwidcomm,
+ pretty_feature_name(feature->name, nwidcomm),
+ longest_comment, longest_comment, comment ? comment : "");
+ }
+ else{
+ utf8_snprintf(tmp, sizeof(tmp), "[%c] %-*.*w", x ? 'X' : ' ',
+ nwidnocomm, nwidnocomm,
+ pretty_feature_name(feature->name, nwidnocomm));
+ }
+
+ return(cpystr(tmp));
+}
+
+
+int
+longest_feature_name(void)
+{
+ static int lv = -1;
+ int i, j;
+ FEATURE_S *feature;
+
+ if(lv < 0){
+ for(lv = 0, i = 0; (feature = feature_list(i)); i++)
+ if(feature_list_section(feature)
+ && lv < (j = utf8_width(pretty_feature_name(feature->name, -1))))
+ lv = j;
+
+ lv = MIN(lv, 100);
+ }
+
+ return(lv);
+}
+
+
+int
+feature_indent(void)
+{
+ return(6);
+}
+
+
+char *
+yesno_pretty_value(struct pine *ps, CONF_S *cl)
+{
+ char tmp[6*MAXPATH], *pvalnorm, *pvalexc;
+ char *p, *pval, lastchar = '\0';
+ int editing_except, fixed, norm_with_except, uvalset;
+ int curval, except_set;
+
+ editing_except = (ew == ps_global->ew_for_except_vars);
+ fixed = cl->var->is_fixed;
+ if((ps_global->ew_for_except_vars == Main) ||
+ (ew == ps_global->ew_for_except_vars))
+ norm_with_except = 0;
+ else
+ norm_with_except = 1; /* editing normal and except config exists */
+
+ pvalnorm = PVAL(cl->var, Main);
+ pvalexc = PVAL(cl->var, ps_global->ew_for_except_vars);
+ if(editing_except){
+ uvalset = (pvalexc != NULL &&
+ (!strucmp(pvalexc,yesstr) || !strucmp(pvalexc,nostr)));
+ pval = pvalexc;
+ }
+ else{
+ uvalset = (pvalnorm != NULL &&
+ (!strucmp(pvalnorm,yesstr) || !strucmp(pvalnorm,nostr)));
+ pval = pvalnorm;
+ }
+
+ except_set = (pvalexc != NULL &&
+ (!strucmp(pvalexc,yesstr) || !strucmp(pvalexc,nostr)));
+ curval = (cl->var->current_val.p != NULL &&
+ (!strucmp(cl->var->current_val.p,yesstr) ||
+ !strucmp(cl->var->current_val.p,nostr)));
+
+ p = tmp;
+ *p = '\0';
+
+ if(fixed || !uvalset)
+ sstrncpy(&p, "<", sizeof(tmp)-(p-tmp));
+
+ if(fixed)
+ sstrncpy(&p, _(fixed_val), sizeof(tmp)-(p-tmp));
+ else if(!uvalset)
+ sstrncpy(&p, _(no_val), sizeof(tmp)-(p-tmp));
+ else if(!strucmp(pval, yesstr))
+ sstrncpy(&p, yesstr, sizeof(tmp)-(p-tmp));
+ else
+ sstrncpy(&p, nostr, sizeof(tmp)-(p-tmp));
+
+ if(curval && (fixed || !uvalset || (norm_with_except && except_set))){
+ if(fixed || !uvalset)
+ sstrncpy(&p, ": using ", sizeof(tmp)-(p-tmp));
+
+ if(norm_with_except && except_set){
+ if(!uvalset)
+ sstrncpy(&p, "exception ", sizeof(tmp)-(p-tmp));
+ else if(!fixed){
+ sstrncpy(&p, " (", sizeof(tmp)-(p-tmp));
+ sstrncpy(&p, "overridden by exception ", sizeof(tmp)-(p-tmp));
+ }
+ }
+
+ sstrncpy(&p, "\"", sizeof(tmp)-(p-tmp));
+ sstrncpy(&p, !strucmp(cl->var->current_val.p,yesstr) ? yesstr : nostr, sizeof(tmp)-(p-tmp));
+ sstrncpy(&p, "\"", sizeof(tmp)-(p-tmp));
+ }
+
+ if(fixed || !uvalset)
+ lastchar = '>';
+ else if(curval && norm_with_except && except_set)
+ lastchar = ')';
+
+ if(lastchar && sizeof(tmp)-(p-tmp) > 1){
+ *p++ = lastchar;
+ *p = '\0';
+ }
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ if(utf8_width(tmp) < ps->ttyo->screen_cols - cl->valoffset)
+ snprintf(tmp+strlen(tmp), sizeof(tmp)-strlen(tmp),
+ "%*s", ps->ttyo->screen_cols - cl->valoffset - utf8_width(tmp), "");
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ return(cpystr(tmp));
+}
+
+
+char *
+radio_pretty_value(struct pine *ps, CONF_S *cl)
+{
+ char tmp[6*MAXPATH];
+ char *pvalnorm, *pvalexc, *pval;
+ int editing_except_which_isnt_normal, editing_normal_which_isnt_except;
+ int fixed, is_set_for_this_level = 0, is_the_one, the_exc_one;
+ int i, j, lv = 0;
+ NAMEVAL_S *rule = NULL, *f;
+ PTR_TO_RULEFUNC rulefunc;
+ struct variable *v;
+
+ tmp[0] = '\0';
+ v = cl->var;
+
+ editing_except_which_isnt_normal = (ew == ps_global->ew_for_except_vars &&
+ ew != Main);
+ editing_normal_which_isnt_except = (ew == Main &&
+ ew != ps_global->ew_for_except_vars);
+ fixed = cl->var->is_fixed;
+ pvalnorm = PVAL(v, Main);
+ pvalexc = PVAL(v, ps_global->ew_for_except_vars);
+
+ rulefunc = rulefunc_from_var(ps, v);
+ rule = rulefunc ? (*rulefunc)(cl->varmem) : NULL;
+
+ /* find longest name */
+ if(rulefunc)
+ for(lv = 0, i = 0; (f = (*rulefunc)(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ if(editing_except_which_isnt_normal)
+ pval = pvalexc;
+ else
+ pval = pvalnorm;
+
+ if(pval)
+ is_set_for_this_level++;
+
+ if(fixed){
+ pval = v->fixed_val.p;
+ is_the_one = (pval && !strucmp(pval, S_OR_L(rule)));
+
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w%s",
+ is_the_one ? R_SELD : ' ',
+ lv, lv, rule->name, is_the_one ? " (value is fixed)" : "");
+ }
+ else if(is_set_for_this_level){
+ is_the_one = (pval && !strucmp(pval, S_OR_L(rule)));
+ the_exc_one = (editing_normal_which_isnt_except && pvalexc &&
+ !strucmp(pvalexc, S_OR_L(rule)));
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w%s",
+ is_the_one ? R_SELD : ' ',
+ lv, lv, rule->name,
+ (!is_the_one && the_exc_one) ? " (value set in exceptions)" :
+ (is_the_one && the_exc_one) ? " (also set in exceptions)" :
+ (is_the_one &&
+ editing_normal_which_isnt_except &&
+ pvalexc &&
+ !the_exc_one) ? " (overridden by exceptions)" :
+ "");
+ }
+ else{
+ if(pvalexc){
+ is_the_one = !strucmp(pvalexc, S_OR_L(rule));
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w%s",
+ is_the_one ? R_SELD : ' ',
+ lv, lv, rule->name,
+ is_the_one ? " (value set in exceptions)" : "");
+ }
+ else{
+ pval = v->current_val.p;
+ is_the_one = (pval && !strucmp(pval, S_OR_L(rule)));
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w%s",
+ is_the_one ? R_SELD : ' ',
+ lv, lv, rule->name,
+ is_the_one ? ((editing_except_which_isnt_normal && pvalnorm) ? " (default from regular config)" : " (default)") : "");
+ }
+ }
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ return(cpystr(tmp));
+}
+
+
+char *
+sigfile_pretty_value(struct pine *ps, CONF_S *cl)
+{
+ if(cl && cl->var == &ps->vars[V_SIGNATURE_FILE] &&
+ cl->prev && cl->prev->var == &ps->vars[V_LITERAL_SIG]){
+ if(cl->prev->var->current_val.p){
+ cl->flags |= CF_NOSELECT; /* side effect */
+ return(cpystr(_("<Ignored: using Literal-Signature instead>")));
+ }
+ else{
+ cl->flags &= ~CF_NOSELECT;
+ return(text_pretty_value(ps, cl));
+ }
+ }
+ else
+ return(cpystr(""));
+}
+
+
+char *
+color_pretty_value(struct pine *ps, CONF_S *cl)
+{
+ char tmp[6*MAXPATH];
+ char *p, *q;
+ struct variable *v;
+ int is_index;
+
+ tmp[0] = '\0';
+ v = cl->var;
+
+ if(v && color_holding_var(ps, v) &&
+ (p=srchstr(v->name, "-foreground-color"))){
+
+ is_index = !struncmp(v->name, "index-", 6);
+
+ q = sampleexc_text(ps, v);
+ utf8_snprintf(tmp, sizeof(tmp), "%c%.*s %sColor%*.50s %.20w%*s%.20w%.20w",
+ islower((unsigned char)v->name[0])
+ ? toupper((unsigned char)v->name[0])
+ : v->name[0],
+ MIN(p-v->name-1,30), v->name+1,
+ is_index ? "Symbol " : "",
+ MAX(EQ_COL - COLOR_INDENT -1 - MIN(p-v->name-1,30)
+ - 6 - (is_index ? 7 : 0) - 1,0), "",
+ sample_text(ps,v), *q ? SBS : 0, "", q,
+ color_parenthetical(v));
+ }
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ return(cpystr(tmp));
+}
+
+
+char *
+sort_pretty_value(struct pine *ps, CONF_S *cl)
+{
+ return(generalized_sort_pretty_value(ps, cl, 1));
+}
+
+
+char *
+generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok)
+{
+ char tmp[6*MAXPATH];
+ char *pvalnorm, *pvalexc, *pval;
+ int editing_except_which_isnt_normal, editing_normal_which_isnt_except;
+ int fixed, is_set_for_this_level = 0, is_the_one, the_exc_one;
+ int i, j, lv = 0;
+ struct variable *v;
+ SortOrder line_sort, var_sort, exc_sort;
+ int line_sort_rev, var_sort_rev, exc_sort_rev;
+
+ tmp[0] = '\0';
+ v = cl->var;
+
+ editing_except_which_isnt_normal = (ew == ps_global->ew_for_except_vars &&
+ ew != Main);
+ editing_normal_which_isnt_except = (ew == Main &&
+ ew != ps_global->ew_for_except_vars);
+ fixed = cl->var->is_fixed;
+ pvalnorm = PVAL(v, Main);
+ pvalexc = PVAL(v, ps_global->ew_for_except_vars);
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; ps->sort_types[i] != EndofList; i++)
+ if(lv < (j = utf8_width(sort_name(ps->sort_types[i]))))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ if(editing_except_which_isnt_normal)
+ pval = pvalexc;
+ else
+ pval = pvalnorm;
+
+ if(pval)
+ is_set_for_this_level++;
+
+ /* the config line we're talking about */
+ if(cl->varmem >= 0){
+ line_sort_rev = cl->varmem >= (short)EndofList;
+ line_sort = (SortOrder)(cl->varmem - (line_sort_rev * EndofList));
+ }
+
+ if(cl->varmem < 0){
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*w",
+ (pval == NULL) ? R_SELD : ' ',
+ lv, "Default");
+ }
+ else if(fixed){
+ pval = v->fixed_val.p;
+ decode_sort(pval, &var_sort, &var_sort_rev);
+ is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort);
+
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s",
+ is_the_one ? R_SELD : ' ',
+ line_sort_rev ? "Reverse " : "",
+ lv, sort_name(line_sort),
+ line_sort_rev ? 0 : 8, "",
+ is_the_one ? " (value is fixed)" : "");
+ }
+ else if(is_set_for_this_level){
+ decode_sort(pval, &var_sort, &var_sort_rev);
+ is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort);
+ decode_sort(pvalexc, &exc_sort, &exc_sort_rev);
+ the_exc_one = (editing_normal_which_isnt_except && pvalexc &&
+ exc_sort_rev == line_sort_rev && exc_sort == line_sort);
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s",
+ is_the_one ? R_SELD : ' ',
+ line_sort_rev ? "Reverse " : "",
+ lv, sort_name(line_sort),
+ line_sort_rev ? 0 : 8, "",
+ (!is_the_one && the_exc_one) ? " (value set in exceptions)" :
+ (is_the_one && the_exc_one) ? " (also set in exceptions)" :
+ (is_the_one &&
+ editing_normal_which_isnt_except &&
+ pvalexc &&
+ !the_exc_one) ? " (overridden by exceptions)" :
+ "");
+ }
+ else{
+ if(pvalexc){
+ decode_sort(pvalexc, &exc_sort, &exc_sort_rev);
+ is_the_one = (exc_sort_rev == line_sort_rev &&
+ exc_sort == line_sort);
+ utf8_snprintf(tmp, sizeof(tmp), "( ) %s%-*w%*s%s",
+ line_sort_rev ? "Reverse " : "",
+ lv, sort_name(line_sort),
+ line_sort_rev ? 0 : 8, "",
+ is_the_one ? " (value set in exceptions)" : "");
+ }
+ else{
+ pval = v->current_val.p;
+ decode_sort(pval, &var_sort, &var_sort_rev);
+ is_the_one = ((pval || default_ok) &&
+ var_sort_rev == line_sort_rev &&
+ var_sort == line_sort);
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s",
+ is_the_one ? R_SELD : ' ',
+ line_sort_rev ? "Reverse " : "",
+ lv, sort_name(line_sort),
+ line_sort_rev ? 0 : 8, "",
+ is_the_one ? ((editing_except_which_isnt_normal && pvalnorm) ? " (default from regular config)" : " (default)") : "");
+ }
+ }
+
+ return(cpystr(tmp));
+}
+
+
+COLOR_PAIR *
+sample_color(struct pine *ps, struct variable *v)
+{
+ COLOR_PAIR *cp = NULL;
+ char *pvalefg, *pvalebg;
+ char *pvalmfg, *pvalmbg;
+
+ pvalefg = PVAL(v, ew);
+ pvalebg = PVAL(v+1, ew);
+ pvalmfg = PVAL(v, Main);
+ pvalmbg = PVAL(v+1, Main);
+ if(v && color_holding_var(ps, v) &&
+ srchstr(v->name, "-foreground-color")){
+ if(pvalefg && pvalefg[0] && pvalebg && pvalebg[0])
+ cp = new_color_pair(pvalefg, pvalebg);
+ else if(ew == Post && pvalmfg && pvalmfg[0] && pvalmbg && pvalmbg[0])
+ cp = new_color_pair(pvalmfg, pvalmbg);
+ else if(v->global_val.p && v->global_val.p[0] &&
+ (v+1)->global_val.p && (v+1)->global_val.p[0])
+ cp = new_color_pair(v->global_val.p, (v+1)->global_val.p);
+ }
+
+ return(cp);
+}
+
+
+COLOR_PAIR *
+sampleexc_color(struct pine *ps, struct variable *v)
+{
+ COLOR_PAIR *cp = NULL;
+ char *pvalfg, *pvalbg;
+
+ pvalfg = PVAL(v, Post);
+ pvalbg = PVAL(v+1, Post);
+ if(v && color_holding_var(ps, v) &&
+ srchstr(v->name, "-foreground-color") &&
+ pvalfg && pvalfg[0] && pvalbg && pvalbg[0])
+ cp = new_color_pair(pvalfg, pvalbg);
+
+ return(cp);
+}
+
+
+void
+clear_feature(char ***l, char *f)
+{
+ char **list = l ? *l : NULL;
+ int count = 0;
+
+ for(; list && *list; list++, count++){
+ if(f && !strucmp(((!struncmp(*list,"no-",3)) ? *list + 3 : *list), f)){
+ fs_give((void **)list);
+ f = NULL;
+ }
+
+ if(!f) /* shift */
+ *list = *(list + 1);
+ }
+
+ /*
+ * this is helpful to keep the array from growing if a feature
+ * get's set and unset repeatedly
+ */
+ if(!f)
+ fs_resize((void **)l, count * sizeof(char *));
+}
+
+
+/*
+ *
+ */
+void
+toggle_feature_bit(struct pine *ps, int index, struct variable *var, CONF_S *cl, int just_flip_value)
+{
+ FEATURE_S *f;
+ int og, on_before;
+ char *p, **vp;
+
+ f = feature_list(index);
+
+ og = test_old_growth_bits(ps, f->id);
+
+ /*
+ * if this feature is in the fixed set, or old-growth is in the fixed
+ * set and this feature is in the old-growth set, don't alter it...
+ */
+ for(vp = var->fixed_val.l; vp && *vp; vp++){
+ p = (struncmp(*vp, "no-", 3)) ? *vp : *vp + 3;
+ if(!strucmp(p, f->name) || (og && !strucmp(p, "old-growth"))){
+ q_status_message(SM_ORDER, 3, 3,
+ _("Can't change value fixed by sys-admin."));
+ return;
+ }
+ }
+
+ on_before = F_ON(f->id, ps);
+
+ toggle_feature(ps, var, f, just_flip_value, ew);
+
+ /*
+ * Handle any alpine-specific features that need attention here. Features
+ * that aren't alpine-specific should be handled in toggle_feature instead.
+ */
+ if(on_before != F_ON(f->id, ps))
+ switch(f->id){
+ case F_CMBND_ABOOK_DISP :
+ addrbook_reset();
+ break;
+
+ case F_PRESERVE_START_STOP :
+ /* toggle raw mode settings to make tty driver aware of new setting */
+ PineRaw(0);
+ PineRaw(1);
+ break;
+
+ case F_USE_FK :
+ ps->orig_use_fkeys = F_ON(F_USE_FK, ps);
+ ps->mangled_footer = 1;
+ mark_keymenu_dirty();
+ break;
+
+ case F_SHOW_SORT :
+ ps->mangled_header = 1;
+ break;
+
+ case F_BLANK_KEYMENU :
+ if(F_ON(f->id, ps)){
+ FOOTER_ROWS(ps) = 1;
+ ps->mangled_body = 1;
+ }
+ else{
+ FOOTER_ROWS(ps) = 3;
+ ps->mangled_footer = 1;
+ }
+
+ clearfooter(ps);
+ break;
+
+ case F_ENABLE_INCOMING :
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Folder List changes will take effect your next Alpine session.");
+ break;
+
+#ifdef _WINDOWS
+ case F_SHOW_CURSOR :
+ mswin_showcaret(F_ON(f->id,ps));
+ break;
+
+ case F_ENABLE_TRAYICON :
+ mswin_trayicon(F_ON(f->id,ps));
+ break;
+#endif
+
+#if !defined(DOS) && !defined(OS2)
+ case F_ALLOW_TALK :
+ if(F_ON(f->id, ps))
+ allow_talk(ps);
+ else
+ disallow_talk(ps);
+
+ break;
+#endif
+
+ case F_PASS_CONTROL_CHARS :
+ ps->pass_ctrl_chars = F_ON(F_PASS_CONTROL_CHARS,ps_global) ? 1 : 0;
+ break;
+
+ case F_PASS_C1_CONTROL_CHARS :
+ ps->pass_c1_ctrl_chars = F_ON(F_PASS_C1_CONTROL_CHARS,ps_global) ? 1 : 0;
+ break;
+
+#ifdef MOUSE
+ case F_ENABLE_MOUSE :
+ if(F_ON(f->id, ps)){
+ init_mouse();
+ if(!mouseexist())
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Mouse tracking still off ($DISPLAY variable set?)");
+ }
+ else
+ end_mouse();
+
+ break;
+#endif
+ }
+
+ if(just_flip_value){
+ if(cl->value && cl->value[0])
+ cl->value[1] = (cl->value[1] == ' ') ? 'X' : ' ';
+ }
+ else{
+ /*
+ * This fork is only called from the checkbox_tool, which has
+ * varmem set to index correctly and cl->var set correctly.
+ */
+ if(cl->value)
+ fs_give((void **)&cl->value);
+
+ cl->value = pretty_value(ps, cl);
+ }
+}
+
+
+/*
+ * new_confline - create new CONF_S zero it out, and insert it after current.
+ * NOTE current gets set to the new CONF_S too!
+ */
+CONF_S *
+new_confline(CONF_S **current)
+{
+ CONF_S *p;
+
+ p = (CONF_S *)fs_get(sizeof(CONF_S));
+ memset((void *)p, 0, sizeof(CONF_S));
+ if(current){
+ if(*current){
+ p->next = (*current)->next;
+ (*current)->next = p;
+ p->prev = *current;
+ if(p->next)
+ p->next->prev = p;
+ }
+
+ *current = p;
+ }
+
+ return(p);
+}
+
+
+/*
+ *
+ */
+void
+snip_confline(CONF_S **p)
+{
+ CONF_S *q;
+
+ /*
+ * Be careful. We need this line because the
+ * q->prev->next = ...
+ * may change q itself if &q == &q->prev->next.
+ * Then the use of q in the next line is wrong.
+ * That's what happens if we pass in the address of
+ * some ->next and use *p directly instead of q.
+ */
+ q = *p;
+
+ if(q){
+ /* Yank it from the linked list */
+ if(q->prev)
+ q->prev->next = q->next;
+
+ if(q->next)
+ q->next->prev = q->prev;
+
+ /* Then free up it's memory */
+ q->prev = q->next = NULL;
+ free_conflines(&q);
+ }
+}
+
+
+/*
+ *
+ */
+void
+free_conflines(CONF_S **p)
+{
+ if(*p){
+ free_conflines(&(*p)->next);
+
+ if((*p)->varname)
+ fs_give((void **) &(*p)->varname);
+
+ if((*p)->value)
+ fs_give((void **) &(*p)->value);
+
+ fs_give((void **) p);
+ }
+}
+
+
+/*
+ *
+ */
+CONF_S *
+first_confline(CONF_S *p)
+{
+ while(p && p->prev)
+ p = p->prev;
+
+ return(p);
+}
+
+
+/*
+ * First selectable confline.
+ */
+CONF_S *
+first_sel_confline(CONF_S *p)
+{
+ for(p = first_confline(p); p && (p->flags&CF_NOSELECT); p=next_confline(p))
+ ;/* do nothing */
+
+ return(p);
+}
+
+
+/*
+ *
+ */
+CONF_S *
+last_confline(CONF_S *p)
+{
+ while(p && p->next)
+ p = p->next;
+
+ return(p);
+}
+
+
+/*
+ *
+ */
+int
+fixed_var(struct variable *v, char *action, char *name)
+{
+ char **lval;
+
+ if(v && v->is_fixed
+ && (!v->is_list
+ || ((lval=v->fixed_val.l) && lval[0]
+ && strcmp(INHERIT, lval[0]) != 0))){
+ q_status_message2(SM_ORDER, 3, 3,
+ "Can't %s sys-admin defined %s.",
+ action ? action : "change", name ? name : "value");
+ return(1);
+ }
+
+ return(0);
+}
+
+
+void
+exception_override_warning(struct variable *v)
+{
+ char **lval;
+
+ /* if exceptions config file exists and we're not editing it */
+ if(v && (ps_global->ew_for_except_vars != Main) && (ew == Main)){
+ if((!v->is_list && PVAL(v, ps_global->ew_for_except_vars)) ||
+ (v->is_list && (lval=LVAL(v, ps_global->ew_for_except_vars)) &&
+ lval[0] && strcmp(INHERIT, lval[0]) != 0))
+ q_status_message1(SM_ORDER, 3, 3,
+ _("Warning: \"%s\" is overridden in your exceptions configuration"),
+ v->name);
+ }
+}
+
+
+void
+offer_to_fix_pinerc(struct pine *ps)
+{
+ struct variable *v;
+ char prompt[300];
+ char *p, *q;
+ char **list;
+ char **list_fixed;
+ int rv = 0, write_main = 0, write_post = 0;
+ int i, k, j, need, exc;
+ char *clear = ": delete it";
+ char ***plist;
+
+ dprint((4, "offer_to_fix_pinerc()\n"));
+
+ ps->fix_fixed_warning = 0; /* so we only ask first time */
+
+ if(ps->readonly_pinerc)
+ return;
+
+ set_titlebar(_("FIXING PINERC"), ps->mail_stream,
+ ps->context_current,
+ ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0, NULL);
+
+ if(want_to(_("Some of your options conflict with site policy. Investigate"),
+ 'y', 'n', NO_HELP, WT_FLUSH_IN) != 'y')
+ return;
+
+/* space want_to requires in addition to the string you pass in */
+#define WANTTO_SPACE 6
+ need = WANTTO_SPACE + utf8_width(clear);
+
+ for(v = ps->vars; v->name; v++){
+ if(!v->is_fixed ||
+ !v->is_user ||
+ v->is_obsolete ||
+ v == &ps->vars[V_FEATURE_LIST]) /* handle feature-list below */
+ continue;
+
+ prompt[0] = '\0';
+
+ if(v->is_list &&
+ (v->post_user_val.l || v->main_user_val.l)){
+ char **active_list;
+
+ active_list = v->post_user_val.l ? v->post_user_val.l
+ : v->main_user_val.l;
+ if(*active_list){
+ snprintf(prompt, sizeof(prompt), _("Your setting for %s is "), v->name);
+ prompt[sizeof(prompt)-1] = '\0';
+ p = prompt + strlen(prompt);
+ for(i = 0; active_list[i]; i++){
+ if(utf8_width(prompt) > ps->ttyo->screen_cols - need)
+ break;
+ if(i && sizeof(prompt)-(p-prompt) > 0)
+ *p++ = ',';
+
+ sstrncpy(&p, active_list[i], sizeof(prompt)-(p-prompt));
+ if(sizeof(prompt)-(p-prompt) > 0)
+ *p = '\0';
+
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+ if(sizeof(prompt)-(p-prompt) > 0)
+ *p = '\0';
+ }
+ else
+ snprintf(prompt, sizeof(prompt), _("Your setting for %s is %s"), v->name, _(empty_val2));
+ }
+ else{
+ if(v->post_user_val.p || v->main_user_val.p){
+ char *active_var;
+
+ active_var = v->post_user_val.p ? v->post_user_val.p
+ : v->main_user_val.p;
+ if(*active_var){
+ snprintf(prompt, sizeof(prompt), _("Your setting for %s is %s"),
+ v->name, active_var);
+ }
+ else{
+ snprintf(prompt, sizeof(prompt), _("Your setting for %s is %s"),
+ v->name, _(empty_val2));
+ }
+ }
+ }
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ if(*prompt){
+ if(utf8_width(prompt) > ps->ttyo->screen_cols - need)
+ (void) utf8_truncate(prompt, ps->ttyo->screen_cols - need);
+
+ (void) strncat(prompt, clear, sizeof(prompt)-strlen(prompt)-1);
+ prompt[sizeof(prompt)-1] = '\0';
+ if(want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){
+ if(v->is_list){
+ if(v->main_user_val.l)
+ write_main++;
+ if(v->post_user_val.l)
+ write_post++;
+ }
+ else{
+ if(v->main_user_val.p)
+ write_main++;
+ if(v->post_user_val.p)
+ write_post++;
+ }
+
+ if(delete_user_vals(v))
+ rv++;
+ }
+ }
+ }
+
+
+ /*
+ * As always, feature-list has to be handled separately.
+ */
+ exc = (ps->ew_for_except_vars != Main);
+ v = &ps->vars[V_FEATURE_LIST];
+ list_fixed = v->fixed_val.l;
+
+ for(j = 0; j < 2; j++){
+ plist = (j==0) ? &v->main_user_val.l : &v->post_user_val.l;
+ list = *plist;
+ if(list){
+ for(i = 0; list[i]; i++){
+ p = list[i];
+ if(!struncmp(p, "no-", 3))
+ p += 3;
+ for(k = 0; list_fixed && list_fixed[k]; k++){
+ q = list_fixed[k];
+ if(!struncmp(q, "no-", 3))
+ q += 3;
+ if(!strucmp(q, p) && strucmp(list[i], list_fixed[k])){
+ snprintf(prompt, sizeof(prompt), "Your %s is %s%s, fixed value is %s",
+ p, p == list[i] ? _("ON") : _("OFF"),
+ exc ? ((plist == &v->main_user_val.l) ? ""
+ : " in postload-config")
+ : "",
+ q == list_fixed[k] ? _("ON") : _("OFF"));
+
+ prompt[sizeof(prompt)-1] = '\0';
+ if(utf8_width(prompt) > ps->ttyo->screen_cols - need)
+ (void) utf8_truncate(prompt, ps->ttyo->screen_cols - need);
+
+ (void) strncat(prompt, clear, sizeof(prompt)-strlen(prompt)-1);
+ prompt[sizeof(prompt)-1] = '\0';
+ if(want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){
+ rv++;
+
+ if(plist == &v->main_user_val.l)
+ write_main++;
+ else
+ write_post++;
+
+ /*
+ * Clear the feature from the user's pinerc
+ * so that we'll stop bothering them when they
+ * start up Pine.
+ */
+ clear_feature(plist, p);
+
+ /*
+ * clear_feature scoots the list up, so if list[i] was
+ * the last one going in, now it is the end marker. We
+ * just decrement i so that it will get incremented and
+ * then test == 0 in the for loop. We could just goto
+ * outta_here to accomplish the same thing.
+ */
+ if(!list[i])
+ i--;
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ if(write_main)
+ write_pinerc(ps, Main, WRP_NONE);
+ if(write_post)
+ write_pinerc(ps, Post, WRP_NONE);
+
+ return;
+}
+
+
+/*
+ * Adjust side effects that happen because variable changes values.
+ *
+ * Var->user_val should be set to the new value before calling this.
+ */
+void
+fix_side_effects(struct pine *ps, struct variable *var, int revert)
+{
+ int val = 0;
+ char **v, *q, **apval;
+ struct variable *vars = ps->vars;
+
+ /* move this up here so we get the Using default message */
+ if(var == &ps->vars[V_PERSONAL_NAME]){
+ if(!(var->main_user_val.p ||
+ var->post_user_val.p) && ps->ui.fullname){
+ if(var->current_val.p)
+ fs_give((void **)&var->current_val.p);
+
+ var->current_val.p = cpystr(ps->ui.fullname);
+ }
+ }
+
+ if(!revert
+ && ((!var->is_fixed
+ && !var->is_list
+ && !(var->main_user_val.p ||
+ var->post_user_val.p)
+ && var->current_val.p)
+ ||
+ (!var->is_fixed
+ && var->is_list
+ && !(var->main_user_val.l ||
+ var->post_user_val.l)
+ && var->current_val.l)))
+ q_status_message(SM_ORDER,0,3,_("Using default value"));
+
+ if(var == &ps->vars[V_USER_DOMAIN]){
+ char *p, *q;
+
+ if(ps->VAR_USER_DOMAIN
+ && ps->VAR_USER_DOMAIN[0]
+ && (p = strrindex(ps->VAR_USER_DOMAIN, '@'))){
+ if(*(++p)){
+ if(!revert)
+ q_status_message2(SM_ORDER, 3, 5,
+ _("User-Domain (%s) cannot contain \"@\"; using %s"),
+ ps->VAR_USER_DOMAIN, p);
+ q = ps->VAR_USER_DOMAIN;
+ while((*q++ = *p++) != '\0')
+ ;/* do nothing */
+ }
+ else{
+ if(!revert)
+ q_status_message1(SM_ORDER, 3, 5,
+ _("User-domain (%s) cannot contain \"@\"; deleting"),
+ ps->VAR_USER_DOMAIN);
+
+ if(ps->vars[V_USER_DOMAIN].post_user_val.p){
+ fs_give((void **)&ps->vars[V_USER_DOMAIN].post_user_val.p);
+ set_current_val(&vars[V_USER_DOMAIN], TRUE, TRUE);
+ }
+
+ if(ps->VAR_USER_DOMAIN
+ && ps->VAR_USER_DOMAIN[0]
+ && (p = strrindex(ps->VAR_USER_DOMAIN, '@'))){
+ if(ps->vars[V_USER_DOMAIN].main_user_val.p){
+ fs_give((void **)&ps->vars[V_USER_DOMAIN].main_user_val.p);
+ set_current_val(&vars[V_USER_DOMAIN], TRUE, TRUE);
+ }
+ }
+ }
+ }
+
+ /*
+ * Reset various pointers pertaining to domain name and such...
+ */
+ init_hostname(ps);
+ }
+ else if(var == &ps->vars[V_INBOX_PATH]){
+ /*
+ * fixup the inbox path based on global/default values...
+ */
+ init_inbox_mapping(ps->VAR_INBOX_PATH, ps->context_list);
+
+ if(!strucmp(ps->cur_folder, ps->inbox_name) && ps->mail_stream
+ && strcmp(ps->VAR_INBOX_PATH, ps->mail_stream->mailbox)){
+ /*
+ * If we currently have "inbox" open and the mailbox name
+ * doesn't match, reset the current folder's name and
+ * remove the SP_INBOX flag.
+ */
+ strncpy(ps->cur_folder, ps->mail_stream->mailbox,
+ sizeof(ps->cur_folder)-1);
+ ps->cur_folder[sizeof(ps->cur_folder)-1] = '\0';
+ sp_set_fldr(ps->mail_stream, ps->cur_folder);
+ sp_unflag(ps->mail_stream, SP_INBOX);
+ ps->mangled_header = 1;
+ }
+ else if(sp_inbox_stream()
+ && strcmp(ps->VAR_INBOX_PATH, sp_inbox_stream()->original_mailbox)){
+ MAILSTREAM *m = sp_inbox_stream();
+
+ /*
+ * if we don't have inbox directly open, but have it
+ * open for new mail notification, close the stream like
+ * any other ordinary folder, and clean up...
+ */
+ if(m){
+ sp_unflag(m, SP_PERMLOCKED | SP_INBOX);
+ sp_set_fldr(m, m->mailbox);
+ expunge_and_close(m, NULL, EC_NONE);
+ }
+ }
+ }
+ else if(var == &ps->vars[V_INCCHECKTIMEO]){
+ int old_value = ps->inc_check_timeout;
+
+ if(SVAR_INC_CHECK_TIMEO(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else
+ ps->inc_check_timeout = old_value;
+
+ if(!revert && (F_OFF(F_ENABLE_INCOMING, ps) || F_OFF(F_ENABLE_INCOMING_CHECKING, ps)))
+ q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
+ }
+ else if(var == &ps->vars[V_INCCHECKINTERVAL]){
+ int old_value = ps->inc_check_interval;
+
+ if(SVAR_INC_CHECK_INTERV(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else
+ ps->inc_check_interval = old_value;
+
+ if(!revert && (F_OFF(F_ENABLE_INCOMING, ps) || F_OFF(F_ENABLE_INCOMING_CHECKING, ps)))
+ q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
+ }
+ else if(var == &ps->vars[V_INC2NDCHECKINTERVAL]){
+ int old_value = ps->inc_second_check_interval;
+
+ if(SVAR_INC_2NDCHECK_INTERV(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else
+ ps->inc_second_check_interval = old_value;
+
+ if(!revert && (F_OFF(F_ENABLE_INCOMING, ps) || F_OFF(F_ENABLE_INCOMING_CHECKING, ps)))
+ q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
+ }
+ else if(var == &ps->vars[V_INCCHECKLIST]){
+ if(ps->context_list && ps->context_list->use & CNTXT_INCMNG)
+ reinit_incoming_folder_list(ps, ps->context_list);
+
+ if(!revert && (F_OFF(F_ENABLE_INCOMING, ps) || F_OFF(F_ENABLE_INCOMING_CHECKING, ps)))
+ q_status_message(SM_ORDER, 0, 3, _("This option has no effect without Enable-Incoming-Folders-Checking"));
+ }
+ else if(var == &ps->vars[V_ADDRESSBOOK] ||
+ var == &ps->vars[V_GLOB_ADDRBOOK] ||
+#ifdef ENABLE_LDAP
+ var == &ps->vars[V_LDAP_SERVERS] ||
+#endif
+ var == &ps->vars[V_ABOOK_FORMATS]){
+ addrbook_reset();
+ }
+ else if(var == &ps->vars[V_INDEX_FORMAT]){
+ reset_index_format();
+ clear_index_cache(ps->mail_stream, 0);
+ }
+ else if(var == &ps->vars[V_DEFAULT_FCC] ||
+ var == &ps->vars[V_DEFAULT_SAVE_FOLDER]){
+ init_save_defaults();
+ }
+ else if(var == &ps->vars[V_KW_BRACES] ||
+ var == &ps->vars[V_OPENING_SEP] ||
+ var == &ps->vars[V_ALT_ADDRS]){
+ clear_index_cache(ps->mail_stream, 0);
+ }
+ else if(var == &ps->vars[V_KEYWORDS]){
+ if(ps_global->keywords)
+ free_keyword_list(&ps_global->keywords);
+
+ if(var->current_val.l && var->current_val.l[0])
+ ps_global->keywords = init_keyword_list(var->current_val.l);
+
+ clear_index_cache(ps->mail_stream, 0);
+ }
+ else if(var == &ps->vars[V_INIT_CMD_LIST]){
+ if(!revert)
+ q_status_message(SM_ASYNC, 0, 3,
+ _("Initial command changes will affect your next Alpine session."));
+ }
+ else if(var == &ps->vars[V_VIEW_HEADERS]){
+ ps->view_all_except = 0;
+ if(ps->VAR_VIEW_HEADERS)
+ for(v = ps->VAR_VIEW_HEADERS; (q = *v) != NULL; v++)
+ if(q[0]){
+ char *p;
+
+ removing_leading_white_space(q);
+ /* look for colon or space or end */
+ for(p = q; *p && !isspace((unsigned char)*p) && *p != ':'; p++)
+ ;/* do nothing */
+
+ *p = '\0';
+ if(strucmp(q, ALL_EXCEPT) == 0)
+ ps->view_all_except = 1;
+ }
+ }
+ else if(var == &ps->vars[V_OVERLAP]){
+ int old_value = ps->viewer_overlap;
+
+ if(SVAR_OVERLAP(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else
+ ps->viewer_overlap = old_value;
+ }
+#ifdef SMIME
+ else if(smime_related_var(ps, var)){
+ smime_deinit();
+ }
+#endif /* SMIME */
+ else if(var == &ps->vars[V_MAXREMSTREAM]){
+ int old_value = ps->s_pool.max_remstream;
+
+ if(SVAR_MAXREMSTREAM(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert )
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else
+ ps->s_pool.max_remstream = old_value;
+
+ dprint((9, "max_remstream goes to %d\n",
+ ps->s_pool.max_remstream));
+ }
+#ifndef _WINDOWS
+ else if(var == &ps->vars[V_CHAR_SET]){
+ char *err = NULL;
+
+ if(F_ON(F_USE_SYSTEM_TRANS, ps)){
+ if(!revert)
+ q_status_message(SM_ORDER, 5, 5, _("This change has no effect because feature Use-System-Translation is on"));
+ }
+ else{
+ if(reset_character_set_stuff(&err) == -1)
+ panic(err ? err : "trouble with Character-Set");
+ else if(err){
+ q_status_message(SM_ORDER | SM_DING, 3, 5, err);
+ fs_give((void **) &err);
+ }
+ }
+ }
+ else if(var == &ps->vars[V_KEY_CHAR_SET]){
+ char *err = NULL;
+
+ if(F_ON(F_USE_SYSTEM_TRANS, ps)){
+ if(!revert)
+ q_status_message(SM_ORDER, 5, 5, _("This change has no effect because feature Use-System-Translation is on"));
+ }
+ else{
+ if(reset_character_set_stuff(&err) == -1)
+ panic(err ? err : "trouble with Character-Set");
+ else if(err){
+ q_status_message(SM_ORDER | SM_DING, 3, 5, err);
+ fs_give((void **) &err);
+ }
+ }
+ }
+#endif /* ! _WINDOWS */
+ else if(var == &ps->vars[V_POST_CHAR_SET]){
+ update_posting_charset(ps, revert);
+ }
+ else if(var == &ps->vars[V_MARGIN]){
+ int old_value = ps->scroll_margin;
+
+ if(SVAR_MARGIN(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else
+ ps->scroll_margin = old_value;
+ }
+ else if(var == &ps->vars[V_DEADLETS]){
+ int old_value = ps->deadlets;
+
+ if(SVAR_DEADLETS(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else
+ ps->deadlets = old_value;
+ }
+ else if(var == &ps->vars[V_FILLCOL]){
+ if(SVAR_FILLCOL(ps, ps->composer_fillcol, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ }
+ else if(var == &ps->vars[V_QUOTE_SUPPRESSION]){
+ val = ps->quote_suppression_threshold;
+ if(val < Q_SUPP_LIMIT && val > 0)
+ val = -val;
+
+ if(ps->VAR_QUOTE_SUPPRESSION
+ && SVAR_QUOTE_SUPPRESSION(ps, val, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else{
+ if(val > 0 && val < Q_SUPP_LIMIT){
+ if(!revert){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Ignoring Quote-Suppression-Threshold value of %s, see help"), ps->VAR_QUOTE_SUPPRESSION);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ }
+ else{
+ if(val < 0 && val != Q_DEL_ALL)
+ ps->quote_suppression_threshold = -val;
+ else
+ ps->quote_suppression_threshold = val;
+ }
+ }
+ }
+ else if(var == &ps->vars[V_STATUS_MSG_DELAY]){
+ if(SVAR_MSGDLAY(ps, ps->status_msg_delay, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ }
+ else if(var == &ps->vars[V_ACTIVE_MSG_INTERVAL]){
+ if(SVAR_ACTIVEINTERVAL(ps, ps->active_status_interval, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else{
+ busy_cue(_("Active Example"), NULL, 0);
+ sleep(5);
+ cancel_busy_cue(-1);
+ }
+ }
+#if !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
+ else if(var == &ps->vars[V_FIFOPATH]){
+ init_newmailfifo(ps->VAR_FIFOPATH);
+ }
+#endif
+ else if(var == &ps->vars[V_NMW_WIDTH]){
+ int old_value = ps->nmw_width;
+
+ if(SVAR_NMW_WIDTH(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert )
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else{
+#ifdef _WINDOWS
+ if(old_value != ps->nmw_width)
+ mswin_setnewmailwidth(old_value); /* actually the new value */
+#endif
+ ps->nmw_width = old_value;
+ }
+ }
+ else if(var == &ps->vars[V_TCPOPENTIMEO]){
+ val = 30;
+ if(!revert)
+ if(ps->VAR_TCPOPENTIMEO && SVAR_TCP_OPEN(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else if(var == &ps->vars[V_TCPREADWARNTIMEO]){
+ val = 15;
+ if(!revert)
+ if(ps->VAR_TCPREADWARNTIMEO && SVAR_TCP_READWARN(ps,val,tmp_20k_buf, SIZEOF_20KBUF))
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else if(var == &ps->vars[V_TCPWRITEWARNTIMEO]){
+ val = 0;
+ if(!revert)
+ if(ps->VAR_TCPWRITEWARNTIMEO && SVAR_TCP_WRITEWARN(ps,val,tmp_20k_buf, SIZEOF_20KBUF))
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else if(var == &ps->vars[V_TCPQUERYTIMEO]){
+ val = 60;
+ if(!revert)
+ if(ps->VAR_TCPQUERYTIMEO && SVAR_TCP_QUERY(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else if(var == &ps->vars[V_RSHOPENTIMEO]){
+ val = 15;
+ if(!revert)
+ if(ps->VAR_RSHOPENTIMEO && SVAR_RSH_OPEN(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else if(var == &ps->vars[V_SSHOPENTIMEO]){
+ val = 15;
+ if(!revert)
+ if(ps->VAR_SSHOPENTIMEO && SVAR_SSH_OPEN(ps, val, tmp_20k_buf, SIZEOF_20KBUF))
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else if(var == &ps->vars[V_SIGNATURE_FILE]){
+ if(ps->VAR_OPER_DIR && ps->VAR_SIGNATURE_FILE &&
+ is_absolute_path(ps->VAR_SIGNATURE_FILE) &&
+ !in_dir(ps->VAR_OPER_DIR, ps->VAR_SIGNATURE_FILE)){
+ char *e;
+ size_t l;
+
+ l = strlen(ps->VAR_OPER_DIR) + 100;
+ e = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(e, l+1, _("Warning: Sig file can't be outside of %s"),
+ ps->VAR_OPER_DIR);
+ e[l] = '\0';
+ q_status_message(SM_ORDER, 3, 6, e);
+ fs_give((void **)&e);
+ }
+ }
+ else if(var == &ps->vars[V_OPER_DIR]){
+ if(ps->VAR_OPER_DIR && !ps->VAR_OPER_DIR[0]){
+ q_status_message(SM_ORDER, 3, 5, "Operating-dir is turned off.");
+ fs_give((void **)&ps->vars[V_OPER_DIR].current_val.p);
+ if(ps->vars[V_OPER_DIR].fixed_val.p)
+ fs_give((void **)&ps->vars[V_OPER_DIR].fixed_val.p);
+ if(ps->vars[V_OPER_DIR].global_val.p)
+ fs_give((void **)&ps->vars[V_OPER_DIR].global_val.p);
+ if(ps->vars[V_OPER_DIR].cmdline_val.p)
+ fs_give((void **)&ps->vars[V_OPER_DIR].cmdline_val.p);
+ if(ps->vars[V_OPER_DIR].post_user_val.p)
+ fs_give((void **)&ps->vars[V_OPER_DIR].post_user_val.p);
+ if(ps->vars[V_OPER_DIR].main_user_val.p)
+ fs_give((void **)&ps->vars[V_OPER_DIR].main_user_val.p);
+ }
+ }
+ else if(var == &ps->vars[V_MAILCHECK]){
+ int timeo = 15;
+ if(SVAR_MAILCHK(ps, timeo, tmp_20k_buf, SIZEOF_20KBUF)){
+ set_input_timeout(15);
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else{
+ set_input_timeout(timeo);
+ if(get_input_timeout() == 0 && !revert){
+ q_status_message(SM_ORDER, 4, 6,
+ _("Warning: automatic new mail checking and mailbox checkpointing is disabled"));
+ if(ps->VAR_INBOX_PATH && ps->VAR_INBOX_PATH[0] == '{')
+ q_status_message(SM_ASYNC, 3, 6,
+ _("Warning: Mail-Check-Interval=0 may cause IMAP server connection to time out"));
+ }
+ }
+ }
+ else if(var == &ps->vars[V_MAILCHECKNONCURR]){
+ val = (int) ps->check_interval_for_noncurr;
+ if(ps->VAR_MAILCHECKNONCURR
+ && SVAR_MAILCHKNONCURR(ps, val, tmp_20k_buf, SIZEOF_20KBUF)){
+ if(!revert)
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else
+ ps->check_interval_for_noncurr = (time_t) val;
+ }
+ else if(var == &ps->vars[V_MAILDROPCHECK]){
+ long rvl;
+
+ rvl = 60L;
+ if(ps->VAR_MAILDROPCHECK && SVAR_MAILDCHK(ps, rvl, tmp_20k_buf, SIZEOF_20KBUF))
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ else{
+ if(rvl == 0L)
+ rvl = (60L * 60L * 24L * 100L); /* 100 days */
+
+ if(rvl >= 60L)
+ mail_parameters(NULL, SET_SNARFINTERVAL, (void *) rvl);
+ }
+ }
+ else if(var == &ps->vars[V_NNTPRANGE]){
+ long rvl;
+
+ rvl = 0L;
+ if(ps->VAR_NNTPRANGE && SVAR_NNTPRANGE(ps, rvl, tmp_20k_buf, SIZEOF_20KBUF))
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ else{
+ if(rvl >= 0L)
+ mail_parameters(NULL, SET_NNTPRANGE, (void *) rvl);
+ }
+ }
+ else if(var == &ps->vars[V_CUSTOM_HDRS] || var == &ps->vars[V_COMP_HDRS]){
+ /* this will give warnings about headers that can't be changed */
+ if(!revert && var->current_val.l && var->current_val.l[0])
+ customized_hdr_setup(NULL, var->current_val.l, UseAsDef);
+ }
+#if defined(DOS) || defined(OS2)
+ else if(var == &ps->vars[V_FOLDER_EXTENSION]){
+ mail_parameters(NULL, SET_EXTENSION,
+ (void *)var->current_val.p);
+ }
+ else if(var == &ps->vars[V_NEWSRC_PATH]){
+ if(var->current_val.p && var->current_val.p[0])
+ mail_parameters(NULL, SET_NEWSRC,
+ (void *)var->current_val.p);
+ }
+#endif
+ else if(revert && standard_radio_var(ps, var)){
+
+ cur_rule_value(var, TRUE, FALSE);
+ if(var == &ps_global->vars[V_AB_SORT_RULE])
+ addrbook_redo_sorts();
+ else if(var == &ps_global->vars[V_THREAD_INDEX_STYLE]){
+ clear_index_cache(ps_global->mail_stream, 0);
+ set_lflags(ps_global->mail_stream, ps_global->msgmap,
+ MN_COLL | MN_CHID, 0);
+ if(SORT_IS_THREADED(ps_global->msgmap)
+ && (SEP_THRDINDX() || COLL_THRDS()))
+ collapse_threads(ps_global->mail_stream, ps_global->msgmap, NULL);
+
+ adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
+ }
+#ifndef _WINDOWS
+ else if(var == &ps->vars[V_COLOR_STYLE]){
+ pico_toggle_color(0);
+ switch(ps->color_style){
+ case COL_NONE:
+ case COL_TERMDEF:
+ pico_set_color_options(pico_trans_color() ? COLOR_TRANS_OPT : 0);
+ break;
+ case COL_ANSI8:
+ pico_set_color_options(COLOR_ANSI8_OPT|COLOR_TRANS_OPT);
+ break;
+ case COL_ANSI16:
+ pico_set_color_options(COLOR_ANSI16_OPT|COLOR_TRANS_OPT);
+ break;
+ case COL_ANSI256:
+ pico_set_color_options(COLOR_ANSI256_OPT|COLOR_TRANS_OPT);
+ break;
+ }
+
+ if(ps->color_style != COL_NONE)
+ pico_toggle_color(1);
+
+ if(pico_usingcolor())
+ pico_set_normal_color();
+
+ clear_index_cache(ps_global->mail_stream, 0);
+ ClearScreen();
+ ps->mangled_screen = 1;
+ }
+#endif
+ }
+ else if(revert && var == &ps->vars[V_SORT_KEY]){
+ int def_sort_rev;
+
+ decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev);
+ ps->def_sort_rev = def_sort_rev;
+ }
+ else if(var == &ps->vars[V_THREAD_MORE_CHAR] ||
+ var == &ps->vars[V_THREAD_EXP_CHAR] ||
+ var == &ps->vars[V_THREAD_LASTREPLY_CHAR]){
+
+ if(var == &ps->vars[V_THREAD_LASTREPLY_CHAR] &&
+ !(var->current_val.p && var->current_val.p[0])){
+ if(var->current_val.p)
+ fs_give((void **) &var->current_val.p);
+
+ q_status_message1(SM_ORDER, 3, 5,
+ _("\"%s\" can't be Empty, using default"), var->name);
+
+ apval = APVAL(var, ew);
+ if(*apval)
+ fs_give((void **)apval);
+
+ set_current_val(var, FALSE, FALSE);
+
+ if(!(var->current_val.p && var->current_val.p[0]
+ && !var->current_val.p[1])){
+ if(var->current_val.p)
+ fs_give((void **) &var->current_val.p);
+
+ var->current_val.p = cpystr(DF_THREAD_LASTREPLY_CHAR);
+ }
+ }
+
+ if(var == &ps->vars[V_THREAD_MORE_CHAR] ||
+ var == &ps->vars[V_THREAD_EXP_CHAR] ||
+ var == &ps->vars[V_THREAD_LASTREPLY_CHAR]){
+ if(var->current_val.p && var->current_val.p[0] &&
+ var->current_val.p[1]){
+ q_status_message1(SM_ORDER, 3, 5,
+ "Only first character of \"%s\" is used",
+ var->name);
+ var->current_val.p[1] = '\0';
+ }
+
+ if(var->main_user_val.p && var->main_user_val.p[0] &&
+ var->main_user_val.p[1])
+ var->main_user_val.p[1] = '\0';
+
+ if(var->post_user_val.p && var->post_user_val.p[0] &&
+ var->post_user_val.p[1])
+ var->post_user_val.p[1] = '\0';
+ }
+
+ clear_index_cache(ps_global->mail_stream, 0);
+ set_need_format_setup(ps_global->mail_stream);
+ }
+ else if(var == &ps->vars[V_NNTP_SERVER]){
+ free_contexts(&ps_global->context_list);
+ init_folders(ps_global);
+ }
+ else if(var == &ps->vars[V_USE_ONLY_DOMAIN_NAME]){
+ init_hostname(ps);
+ }
+ else if(var == &ps->vars[V_PRINTER]){
+ if(!revert && ps->vars[V_PERSONAL_PRINT_COMMAND].is_fixed){
+ if(printer_value_check_and_adjust())
+ q_status_message1(SM_ORDER, 3, 5,
+ _("Can't set \"%s\" to that value, see Setup/Printer"),
+ pretty_var_name(var->name));
+ }
+ }
+ else if(var == &ps->vars[V_KW_COLORS] ||
+ var == &ps->vars[V_IND_PLUS_FORE_COLOR] ||
+ var == &ps->vars[V_IND_IMP_FORE_COLOR] ||
+ var == &ps->vars[V_IND_DEL_FORE_COLOR] ||
+ var == &ps->vars[V_IND_ANS_FORE_COLOR] ||
+ var == &ps->vars[V_IND_NEW_FORE_COLOR] ||
+ var == &ps->vars[V_IND_UNS_FORE_COLOR] ||
+ var == &ps->vars[V_IND_HIPRI_FORE_COLOR]||
+ var == &ps->vars[V_IND_LOPRI_FORE_COLOR]||
+ var == &ps->vars[V_IND_ARR_FORE_COLOR] ||
+ var == &ps->vars[V_IND_REC_FORE_COLOR] ||
+ var == &ps->vars[V_IND_FWD_FORE_COLOR] ||
+ var == &ps->vars[V_IND_OP_FORE_COLOR] ||
+ var == &ps->vars[V_IND_FROM_FORE_COLOR] ||
+ var == &ps->vars[V_IND_SUBJ_FORE_COLOR] ||
+ var == &ps->vars[V_IND_PLUS_BACK_COLOR] ||
+ var == &ps->vars[V_IND_IMP_BACK_COLOR] ||
+ var == &ps->vars[V_IND_DEL_BACK_COLOR] ||
+ var == &ps->vars[V_IND_ANS_BACK_COLOR] ||
+ var == &ps->vars[V_IND_NEW_BACK_COLOR] ||
+ var == &ps->vars[V_IND_UNS_BACK_COLOR] ||
+ var == &ps->vars[V_IND_ARR_BACK_COLOR] ||
+ var == &ps->vars[V_IND_REC_BACK_COLOR] ||
+ var == &ps->vars[V_IND_FWD_BACK_COLOR] ||
+ var == &ps->vars[V_IND_OP_BACK_COLOR] ||
+ var == &ps->vars[V_IND_FROM_BACK_COLOR] ||
+ var == &ps->vars[V_IND_SUBJ_BACK_COLOR]){
+ clear_index_cache(ps_global->mail_stream, 0);
+ }
+ else if(var == score_act_global_ptr){
+ int score;
+
+ score = atoi(var->current_val.p);
+ if(score < SCORE_MIN || score > SCORE_MAX){
+ q_status_message2(SM_ORDER, 3, 5,
+ _("Score Value must be in range %s to %s"),
+ comatose(SCORE_MIN), comatose(SCORE_MAX));
+ apval = APVAL(var, ew);
+ if(*apval)
+ fs_give((void **)apval);
+
+ set_current_val(var, FALSE, FALSE);
+ }
+ }
+ else if(var == scorei_pat_global_ptr || var == age_pat_global_ptr
+ || var == size_pat_global_ptr || var == cati_global_ptr){
+ apval = APVAL(var, ew);
+ if(*apval){
+ INTVL_S *iv;
+ iv = parse_intvl(*apval);
+ if(iv){
+ fs_give((void **) apval);
+ *apval = stringform_of_intvl(iv);
+ free_intvl(&iv);
+ }
+ else
+ fs_give((void **) apval);
+ }
+
+ set_current_val(var, FALSE, FALSE);
+ }
+ else if(var == &ps->vars[V_FEATURE_LIST]){
+ process_feature_list(ps, var->current_val.l, 0, 0, 0);
+ }
+ else if(!revert && (var == &ps->vars[V_LAST_TIME_PRUNE_QUESTION] ||
+ var == &ps->vars[V_REMOTE_ABOOK_HISTORY] ||
+ var == &ps->vars[V_REMOTE_ABOOK_VALIDITY] ||
+ var == &ps->vars[V_USERINPUTTIMEO] ||
+ var == &ps->vars[V_NEWS_ACTIVE_PATH] ||
+ var == &ps->vars[V_NEWS_SPOOL_DIR] ||
+ var == &ps->vars[V_INCOMING_FOLDERS] ||
+ var == &ps->vars[V_FOLDER_SPEC] ||
+ var == &ps->vars[V_NEWS_SPEC] ||
+ var == &ps->vars[V_DISABLE_DRIVERS] ||
+ var == &ps->vars[V_DISABLE_AUTHS] ||
+ var == &ps->vars[V_RSHPATH] ||
+ var == &ps->vars[V_RSHCMD] ||
+ var == &ps->vars[V_SSHCMD] ||
+ var == &ps->vars[V_SSHPATH])){
+ q_status_message2(SM_ASYNC, 0, 3,
+ _("Changes%s%s will affect your next Alpine session."),
+ var->name ? " to " : "", var->name ? var->name : "");
+ }
+
+ if(!revert && (var == &ps->vars[V_TCPOPENTIMEO] ||
+ var == &ps->vars[V_TCPREADWARNTIMEO] ||
+ var == &ps->vars[V_TCPWRITEWARNTIMEO] ||
+ var == &ps->vars[V_TCPQUERYTIMEO] ||
+ var == &ps->vars[V_RSHOPENTIMEO] ||
+ var == &ps->vars[V_SSHOPENTIMEO]))
+ q_status_message(SM_ASYNC, 0, 3,
+ _("Timeout changes will affect your next Alpine session."));
+}
+
+
+/*
+ * Compare saved user_val with current user_val to see if it changed.
+ * If any have changed, change it back and take the appropriate action.
+ */
+void
+revert_to_saved_config(struct pine *ps, SAVED_CONFIG_S *vsave, int allow_hard_to_config_remotely)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *v;
+ int i, n;
+ int changed = 0;
+ char *pval, **apval, **lval, ***alval;
+
+ v = vsave;
+ for(vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!save_include(ps, vreal, allow_hard_to_config_remotely))
+ continue;
+
+ changed = 0;
+ if(vreal->is_list){
+ lval = LVAL(vreal, ew);
+ alval = ALVAL(vreal, ew);
+
+ if((v->saved_user_val.l && !lval)
+ || (!v->saved_user_val.l && lval))
+ changed++;
+ else if(!v->saved_user_val.l && !lval)
+ ;/* no change, nothing to do */
+ else
+ for(i = 0; v->saved_user_val.l[i] || lval[i]; i++)
+ if((v->saved_user_val.l[i]
+ && (!lval[i]
+ || strcmp(v->saved_user_val.l[i], lval[i])))
+ ||
+ (!v->saved_user_val.l[i] && lval[i])){
+ changed++;
+ break;
+ }
+
+ if(changed){
+ char **list;
+
+ if(alval){
+ if(*alval)
+ free_list_array(alval);
+
+ /* copy back the original one */
+ if(v->saved_user_val.l){
+ list = v->saved_user_val.l;
+ n = 0;
+ /* count how many */
+ while(list[n])
+ n++;
+
+ *alval = (char **)fs_get((n+1) * sizeof(char *));
+
+ for(i = 0; i < n; i++)
+ (*alval)[i] = cpystr(v->saved_user_val.l[i]);
+
+ (*alval)[n] = NULL;
+ }
+ }
+ }
+ }
+ else{
+ pval = PVAL(vreal, ew);
+ apval = APVAL(vreal, ew);
+
+ if((v->saved_user_val.p &&
+ (!pval || strcmp(v->saved_user_val.p, pval))) ||
+ (!v->saved_user_val.p && pval)){
+ /* It changed, fix it */
+ changed++;
+ if(apval){
+ /* free the changed value */
+ if(*apval)
+ fs_give((void **)apval);
+
+ if(v->saved_user_val.p)
+ *apval = cpystr(v->saved_user_val.p);
+ }
+ }
+ }
+
+ if(changed){
+ if(vreal == &ps->vars[V_FEATURE_LIST])
+ set_feature_list_current_val(vreal);
+ else
+ set_current_val(vreal, TRUE, FALSE);
+
+ fix_side_effects(ps, vreal, 1);
+ }
+ }
+}
+
+
+SAVED_CONFIG_S *
+save_config_vars(struct pine *ps, int allow_hard_to_config_remotely)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *vsave, *v;
+
+ vsave = (SAVED_CONFIG_S *)fs_get((V_LAST_VAR+1)*sizeof(SAVED_CONFIG_S));
+ memset((void *)vsave, 0, (V_LAST_VAR+1)*sizeof(SAVED_CONFIG_S));
+ v = vsave;
+ for(vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!save_include(ps, vreal, allow_hard_to_config_remotely))
+ continue;
+
+ if(vreal->is_list){
+ int n, i;
+ char **list;
+
+ if(LVAL(vreal, ew)){
+ /* count how many */
+ n = 0;
+ list = LVAL(vreal, ew);
+ while(list[n])
+ n++;
+
+ v->saved_user_val.l = (char **)fs_get((n+1) * sizeof(char *));
+ memset((void *)v->saved_user_val.l, 0, (n+1)*sizeof(char *));
+ for(i = 0; i < n; i++)
+ v->saved_user_val.l[i] = cpystr(list[i]);
+
+ v->saved_user_val.l[n] = NULL;
+ }
+ }
+ else{
+ if(PVAL(vreal, ew))
+ v->saved_user_val.p = cpystr(PVAL(vreal, ew));
+ }
+ }
+
+ return(vsave);
+}
+
+
+void
+free_saved_config(struct pine *ps, SAVED_CONFIG_S **vsavep, int allow_hard_to_config_remotely)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *v;
+
+ if(vsavep && *vsavep){
+ for(v = *vsavep, vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!save_include(ps, vreal, allow_hard_to_config_remotely))
+ continue;
+
+ if(vreal->is_list){ /* free saved_user_val.l */
+ if(v && v->saved_user_val.l)
+ free_list_array(&v->saved_user_val.l);
+ }
+ else if(v && v->saved_user_val.p)
+ fs_give((void **)&v->saved_user_val.p);
+ }
+
+ fs_give((void **)vsavep);
+ }
+}
+
+
+/*
+ * Returns positive if any thing was actually deleted.
+ */
+int
+delete_user_vals(struct variable *v)
+{
+ int rv = 0;
+
+ if(v){
+ if(v->is_list){
+ if(v->post_user_val.l){
+ rv++;
+ free_list_array(&v->post_user_val.l);
+ }
+ if(v->main_user_val.l){
+ rv++;
+ free_list_array(&v->main_user_val.l);
+ }
+ }
+ else{
+ if(v->post_user_val.p){
+ rv++;
+ fs_give((void **)&v->post_user_val.p);
+ }
+ if(v->main_user_val.p){
+ rv++;
+ fs_give((void **)&v->main_user_val.p);
+ }
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * ../pith/conf.c required function
+ */
+int
+unexpected_pinerc_change(void)
+{
+ Writechar(BELL, 0);
+ if(want_to("Unexpected pinerc change! Overwrite with current config",
+ 'n', 0, NO_HELP, WT_FLUSH_IN) == 'n'){
+ return(-1); /* abort pinerc write */
+ }
+
+ return(0); /* overwrite */
+}
+
+
+#ifdef _WINDOWS
+
+/*----------------------------------------------------------------------
+ MSWin scroll callback. Called during scroll message processing.
+
+
+
+ Args: cmd - what type of scroll operation.
+ scroll_pos - paramter for operation.
+ used as position for SCROLL_TO operation.
+
+ Returns: TRUE - did the scroll operation.
+ FALSE - was not able to do the scroll operation.
+ ----*/
+int
+config_scroll_callback (cmd, scroll_pos)
+int cmd;
+long scroll_pos;
+{
+ switch (cmd) {
+ case MSWIN_KEY_SCROLLUPLINE:
+ config_scroll_down (scroll_pos);
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNLINE:
+ config_scroll_up (scroll_pos);
+ break;
+
+ case MSWIN_KEY_SCROLLUPPAGE:
+ config_scroll_down (BODY_LINES(ps_global));
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNPAGE:
+ config_scroll_up (BODY_LINES(ps_global));
+ break;
+
+ case MSWIN_KEY_SCROLLTO:
+ config_scroll_to_pos (scroll_pos);
+ break;
+ }
+
+ option_screen_redrawer();
+ fflush(stdout);
+
+ return(TRUE);
+}
+
+#endif /* _WINDOWS */
diff --git a/alpine/confscroll.h b/alpine/confscroll.h
new file mode 100644
index 00000000..4315c8e1
--- /dev/null
+++ b/alpine/confscroll.h
@@ -0,0 +1,114 @@
+/*
+ * $Id: confscroll.h 812 2007-11-10 01:00:15Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_CONFSCROLL_INCLUDED
+#define PINE_CONFSCROLL_INCLUDED
+
+
+#include "conftype.h"
+#include "../pith/conf.h"
+#include "../pith/state.h"
+
+
+#define BODY_LINES(X) ((X)->ttyo->screen_rows -HEADER_ROWS(X)-FOOTER_ROWS(X))
+
+#define R_SELD '*'
+
+#define EXIT_PMT "Commit changes (\"Yes\" replaces settings, \"No\" abandons changes)"
+
+
+/* another in a long line of hacks in this stuff */
+#define DSTRING "default ("
+#define VSTRING "value from fcc-name-rule"
+
+
+#define next_confline(p) ((p) ? (p)->next : NULL)
+#define prev_confline(p) ((p) ? (p)->prev : NULL)
+
+
+extern char *empty_val;
+extern char *empty_val2;
+
+
+extern struct variable *score_act_global_ptr,
+ *scorei_pat_global_ptr,
+ *age_pat_global_ptr,
+ *size_pat_global_ptr,
+ *cati_global_ptr,
+ *cat_cmd_global_ptr,
+ *cat_lim_global_ptr,
+ *startup_ptr,
+ *role_comment_ptr,
+ *role_forw_ptr,
+ *role_repl_ptr,
+ *role_fldr_ptr,
+ *role_afrom_ptr,
+ *role_filt_ptr,
+ *role_status1_ptr,
+ *role_status2_ptr,
+ *role_status3_ptr,
+ *role_status4_ptr,
+ *role_status5_ptr,
+ *role_status6_ptr,
+ *role_status7_ptr,
+ *role_status8_ptr,
+ *msg_state1_ptr,
+ *msg_state2_ptr,
+ *msg_state3_ptr,
+ *msg_state4_ptr;
+
+
+extern OPT_SCREEN_S *opt_screen;
+
+
+extern EditWhich ew;
+
+
+/* exported protoypes */
+int conf_scroll_screen(struct pine *, OPT_SCREEN_S *, CONF_S *, char *, char *, int);
+void standard_radio_setup(struct pine *, CONF_S **, struct variable *, CONF_S **);
+int standard_radio_var(struct pine *, struct variable *);
+int delete_user_vals(struct variable *);
+CONF_S *new_confline(CONF_S **);
+void free_conflines(CONF_S **);
+CONF_S *first_confline(CONF_S *);
+CONF_S *first_sel_confline(CONF_S *);
+void snip_confline(CONF_S **);
+char *pretty_value(struct pine *, CONF_S *);
+int feature_indent(void);
+SAVED_CONFIG_S *save_config_vars(struct pine *, int);
+int text_tool(struct pine *, int, CONF_S **, unsigned);
+int checkbox_tool(struct pine *, int, CONF_S **, unsigned);
+int radiobutton_tool(struct pine *, int, CONF_S **, unsigned);
+int yesno_tool(struct pine *, int, CONF_S **, unsigned);
+int text_toolit(struct pine *, int, CONF_S **, unsigned, int);
+char *generalized_sort_pretty_value(struct pine *, CONF_S *, int);
+int exclude_config_var(struct pine *, struct variable *, int);
+int config_exit_cmd(unsigned);
+int simple_exit_cmd(unsigned);
+int screen_exit_cmd(unsigned, char *);
+void config_add_list(struct pine *, CONF_S **, char **, char ***, int);
+void config_del_list_item(CONF_S **, char ***);
+void toggle_feature_bit(struct pine *, int, struct variable *, CONF_S *, int);
+int fixed_var(struct variable *, char *, char *);
+void exception_override_warning(struct variable *);
+void offer_to_fix_pinerc(struct pine *);
+void fix_side_effects(struct pine *, struct variable *, int);
+void revert_to_saved_config(struct pine *, SAVED_CONFIG_S *, int);
+void free_saved_config(struct pine *, SAVED_CONFIG_S **, int);
+
+
+#endif /* PINE_CONFSCROLL_INCLUDED */
diff --git a/alpine/conftype.h b/alpine/conftype.h
new file mode 100644
index 00000000..976df5c2
--- /dev/null
+++ b/alpine/conftype.h
@@ -0,0 +1,141 @@
+/*
+ * $Id: conftype.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_CONFTYPE_INCLUDED
+#define PINE_CONFTYPE_INCLUDED
+
+
+#include "keymenu.h"
+#include "help.h"
+#include "flagmaint.h"
+#include "context.h"
+#include "listsel.h"
+#include "../pith/pattern.h"
+#include "../pith/conf.h"
+#include "../pith/ldap.h"
+
+
+typedef enum {ListMode, SingleMode} ScreenMode;
+
+
+typedef struct edit_arb {
+ struct variable *v;
+ ARBHDR_S *a;
+ struct edit_arb *next;
+} EARB_S;
+
+
+typedef struct conf_line {
+ char *varname, /* alloc'd var name string */
+ *value; /* alloc'd var value string */
+ short varoffset; /* offset from screen left */
+ short valoffset; /* offset from screen left */
+ short val2offset; /* offset from screen left */
+ struct variable *var; /* pointer to pinerc var */
+ long varmem; /* value's index, if list */
+ /* tool to manipulate values */
+ int (*tool)(struct pine *, int, struct conf_line **, unsigned);
+ struct key_menu *keymenu; /* tool-specific keymenu */
+ HelpType help; /* variable's help text */
+ char *help_title;
+ unsigned flags;
+ struct conf_line *varnamep; /* pointer to varname */
+ struct conf_line *headingp; /* pointer to heading */
+ struct conf_line *next, *prev;
+ union flag_or_context_data {
+ struct flag_conf {
+ struct flag_table **ftbl; /* address of start of table */
+ struct flag_table *fp; /* pointer into table for each row */
+ } f;
+ struct context_and_screen {
+ CONTEXT_S *ct;
+ CONT_SCR_S *cs;
+ } c;
+ struct role_conf {
+ PAT_LINE_S *patline;
+ PAT_S *pat;
+ PAT_S **selected;
+ int *change_def;
+ } r;
+ struct abook_conf {
+ char **selected;
+ char *abookname;
+ } b;
+ EARB_S **earb;
+ struct list_select {
+ LIST_SEL_S *lsel;
+ ScreenMode *listmode;
+ } l;
+#ifdef ENABLE_LDAP
+ struct entry_and_screen {
+ LDAP *ld;
+ LDAPMessage *entry;
+ LDAP_SERV_S *info_used;
+ char *serv;
+ ADDR_CHOOSE_S *ac;
+ } a;
+#endif
+ struct take_export_val {
+ int selected;
+ char *exportval;
+ ScreenMode *listmode;
+ } t;
+ } d;
+} CONF_S;
+
+
+/*
+ * Valid for flags argument of config screen tools or flags field in CONF_S
+ */
+#define CF_CHANGES 0x0001 /* Have been earlier changes */
+#define CF_NOSELECT 0x0002 /* This line is unselectable */
+#define CF_NOHILITE 0x0004 /* Don't highlight varname */
+#define CF_NUMBER 0x0008 /* Input should be numeric */
+#define CF_INVISIBLEVAR 0x0010 /* Don't show the varname */
+#define CF_PRINTER 0x0020 /* Printer config line */
+#define CF_H_LINE 0x0040 /* Horizontal line */
+#define CF_B_LINE 0x0080 /* Blank line */
+#define CF_CENTERED 0x0100 /* Centered text */
+#define CF_STARTITEM 0x0200 /* Start of an "item" */
+#define CF_PRIVATE 0x0400 /* Private flag for tool */
+#define CF_DOUBLEVAR 0x0800 /* Line has 2 settable vars */
+#define CF_VAR2 0x1000 /* Cursor on 2nd of 2 vars */
+#define CF_COLORSAMPLE 0x2000 /* Show color sample here */
+#define CF_POT_SLCTBL 0x4000 /* Potentially selectable */
+#define CF_INHERIT 0x8000 /* Inherit Defaults line */
+
+
+typedef struct save_config {
+ union {
+ char *p;
+ char **l;
+ } saved_user_val;
+} SAVED_CONFIG_S;
+
+
+typedef struct conf_screen {
+ CONF_S *current,
+ *prev,
+ *top_line;
+ int ro_warning,
+ deferred_ro_warning;
+} OPT_SCREEN_S;
+
+
+/* exported protoypes */
+
+
+#endif /* PINE_CONFTYPE_INCLUDED */
diff --git a/alpine/context.c b/alpine/context.c
new file mode 100644
index 00000000..c3d4dc9e
--- /dev/null
+++ b/alpine/context.c
@@ -0,0 +1,1019 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: context.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "context.h"
+#include "confscroll.h"
+#include "status.h"
+#include "folder.h"
+#include "radio.h"
+#include "alpine.h"
+#include "mailindx.h"
+#include "mailcmd.h"
+#include "send.h"
+#include "../pith/list.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/thread.h"
+
+
+/*
+ * Internal prototypes
+ */
+int context_config_tool(struct pine *, int, CONF_S **, unsigned);
+int context_config_add(struct pine *, CONF_S **);
+int context_config_shuffle(struct pine *, CONF_S **);
+int context_config_edit(struct pine *, CONF_S **);
+int context_config_delete(struct pine *, CONF_S **);
+int ccs_var_delete(struct pine *, CONTEXT_S *);
+int ccs_var_insert(struct pine *, char *, struct variable *, char **, int);
+int context_select_tool(struct pine *, int, CONF_S **, unsigned);
+
+
+/*
+ * Setup CollectionLists. Build a context list on the fly from the config
+ * variable and edit it. This won't include Incoming-Folders because that
+ * is a pseudo collection, but that's ok since we can't do the operations
+ * on it, anyway. Reset real config list at the end.
+ */
+void
+context_config_screen(struct pine *ps, CONT_SCR_S *cs, int edit_exceptions)
+{
+ CONTEXT_S *top, **clist, *cp;
+ CONF_S *ctmpa, *first_line, *heading;
+ OPT_SCREEN_S screen;
+ int i, readonly_warning, some_defined, ret;
+ int reinit_contexts = 0, prime;
+ char **lval, **lval1, **lval2, ***alval;
+ struct variable fake_fspec_var, fake_nspec_var;
+ struct variable *fake_fspec, *fake_nspec;
+
+ ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;
+
+ if(ps->restricted)
+ readonly_warning = 1;
+ else{
+ PINERC_S *prc = NULL;
+
+ switch(ew){
+ case Main:
+ prc = ps->prc;
+ break;
+ case Post:
+ prc = ps->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ readonly_warning = prc ? prc->readonly : 1;
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return;
+ }
+ }
+
+go_again:
+ top = NULL; ctmpa = NULL; first_line = NULL;
+ some_defined = 0, prime = 0;
+ fake_fspec = &fake_fspec_var;
+ fake_nspec = &fake_nspec_var;
+ memset((void *)fake_fspec, 0, sizeof(*fake_fspec));
+ memset((void *)fake_nspec, 0, sizeof(*fake_nspec));
+
+ /* so fixed_var() will work right */
+ fake_fspec->is_list = 1;
+ fake_nspec->is_list = 1;
+ if((ps->vars[V_FOLDER_SPEC]).is_fixed){
+ fake_fspec->is_fixed = 1;
+ if((ps->vars[V_FOLDER_SPEC]).fixed_val.l
+ && (ps->vars[V_FOLDER_SPEC]).fixed_val.l[0]){
+ fake_fspec->fixed_val.l = (char **) fs_get(2 * sizeof(char *));
+ fake_fspec->fixed_val.l[0]
+ = cpystr((ps->vars[V_FOLDER_SPEC]).fixed_val.l[0]);
+ fake_fspec->fixed_val.l[1] = NULL;
+ }
+ }
+
+ if((ps->vars[V_NEWS_SPEC]).is_fixed){
+ fake_nspec->is_fixed = 1;
+ if((ps->vars[V_NEWS_SPEC]).fixed_val.l
+ && (ps->vars[V_NEWS_SPEC]).fixed_val.l[0]){
+ fake_nspec->fixed_val.l = (char **) fs_get(2 * sizeof(char *));
+ fake_nspec->fixed_val.l[0] = cpystr(INHERIT);
+ fake_nspec->fixed_val.l[0]
+ = cpystr((ps->vars[V_NEWS_SPEC]).fixed_val.l[0]);
+ fake_nspec->fixed_val.l[1] = NULL;
+ }
+ }
+
+ clist = &top;
+ lval1 = LVAL(&ps->vars[V_FOLDER_SPEC], ew);
+ lval2 = LVAL(&ps->vars[V_NEWS_SPEC], ew);
+
+ alval = ALVAL(fake_fspec, ew);
+ if(lval1)
+ *alval = copy_list_array(lval1);
+ else if(!edit_exceptions && ps->VAR_FOLDER_SPEC && ps->VAR_FOLDER_SPEC[0] &&
+ ps->VAR_FOLDER_SPEC[0][0])
+ *alval = copy_list_array(ps->VAR_FOLDER_SPEC);
+ else
+ fake_fspec = NULL;
+
+ if(fake_fspec){
+ lval = LVAL(fake_fspec, ew);
+ for(i = 0; lval && lval[i]; i++){
+ cp = NULL;
+ if(i == 0 && !strcmp(lval[i], INHERIT)){
+ cp = (CONTEXT_S *)fs_get(sizeof(*cp));
+ memset((void *)cp, 0, sizeof(*cp));
+ cp->use = CNTXT_INHERIT;
+ cp->label = cpystr("Default collections are inherited");
+ }
+ else if((cp = new_context(lval[i], &prime)) != NULL){
+ cp->var.v = fake_fspec;
+ cp->var.i = i;
+ }
+
+ if(cp){
+ *clist = cp; /* add it to list */
+ clist = &cp->next; /* prepare for next */
+ }
+ }
+
+ set_current_val(fake_fspec, FALSE, FALSE);
+ }
+
+ alval = ALVAL(fake_nspec, ew);
+ if(lval2)
+ *alval = copy_list_array(lval2);
+ else if(!edit_exceptions && ps->VAR_NEWS_SPEC && ps->VAR_NEWS_SPEC[0] &&
+ ps->VAR_NEWS_SPEC[0][0])
+ *alval = copy_list_array(ps->VAR_NEWS_SPEC);
+ else
+ fake_nspec = NULL;
+
+ if(fake_nspec){
+ lval = LVAL(fake_nspec, ew);
+ for(i = 0; lval && lval[i]; i++){
+ cp = NULL;
+ if(i == 0 && !strcmp(lval[i], INHERIT)){
+ cp = (CONTEXT_S *)fs_get(sizeof(*cp));
+ memset((void *)cp, 0, sizeof(*cp));
+ cp->use = CNTXT_INHERIT;
+ cp->label = cpystr("Default collections are inherited");
+ }
+ else if((cp = new_context(lval[i], &prime)) != NULL){
+ cp->var.v = fake_nspec;
+ cp->var.i = i;
+ }
+
+ if(cp){
+ *clist = cp; /* add it to list */
+ clist = &cp->next; /* prepare for next */
+ }
+ }
+
+ set_current_val(fake_nspec, FALSE, FALSE);
+ }
+
+ for(cp = top; cp; cp = cp->next)
+ if(!(cp->use & CNTXT_INHERIT)){
+ some_defined++;
+ break;
+ }
+
+ if(edit_exceptions && !some_defined){
+ q_status_message(SM_ORDER, 3, 7,
+ _("No exceptions to edit. First collection exception must be set by editing file"));
+ free_contexts(&top);
+ if(reinit_contexts){
+ free_contexts(&ps_global->context_list);
+ init_folders(ps_global);
+ }
+
+ return;
+ }
+
+
+ /* fix up prev pointers */
+ for(cp = top; cp; cp = cp->next)
+ if(cp->next)
+ cp->next->prev = cp;
+
+ new_confline(&ctmpa); /* blank line */
+ ctmpa->keymenu = cs->keymenu;
+ ctmpa->help = cs->help.text;
+ ctmpa->help_title = cs->help.title;
+ ctmpa->tool = context_config_tool;
+ ctmpa->flags |= (CF_NOSELECT | CF_B_LINE);
+
+ for(cp = top; cp; cp = cp->next){
+ new_confline(&ctmpa);
+ heading = ctmpa;
+ if(!(cp->use & CNTXT_INHERIT))
+ ctmpa->value = cpystr(cp->nickname ? cp->nickname : cp->context);
+
+ ctmpa->var = cp->var.v;
+ ctmpa->keymenu = cs->keymenu;
+ ctmpa->help = cs->help.text;
+ ctmpa->help_title = cs->help.title;
+ ctmpa->tool = context_config_tool;
+ ctmpa->flags |= CF_STARTITEM;
+ ctmpa->valoffset = 4;
+ ctmpa->d.c.ct = cp;
+ ctmpa->d.c.cs = cs;
+ if(cp->use & CNTXT_INHERIT)
+ ctmpa->flags |= CF_INHERIT | CF_NOSELECT;
+
+ if((!first_line && !(cp->use & CNTXT_INHERIT)) ||
+ (!(cp->use & CNTXT_INHERIT) &&
+ cp->var.v &&
+ (cs->starting_var == cp->var.v) &&
+ (cs->starting_varmem == cp->var.i)))
+ first_line = ctmpa;
+
+ /* Add explanatory text */
+ new_confline(&ctmpa);
+ ctmpa->value = cpystr(cp->label ? cp->label : "* * *");
+ ctmpa->keymenu = cs->keymenu;
+ ctmpa->help = cs->help.text;
+ ctmpa->help_title = cs->help.title;
+ ctmpa->tool = context_config_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = 8;
+
+ /* Always add blank line, make's shuffling a little easier */
+ new_confline(&ctmpa);
+ heading->headingp = ctmpa; /* use headingp to mark end */
+ ctmpa->keymenu = cs->keymenu;
+ ctmpa->help = cs->help.text;
+ ctmpa->help_title = cs->help.title;
+ ctmpa->tool = context_config_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+ ctmpa->valoffset = 0;
+ }
+
+ cs->starting_var = NULL;
+
+
+ memset(&screen, 0, sizeof(screen));
+ screen.ro_warning = readonly_warning;
+ ret = conf_scroll_screen(ps, &screen, first_line, cs->title,
+ cs->print_string, 0);
+
+ free_contexts(&top);
+
+ if(ret)
+ reinit_contexts++;
+
+ /*
+ * 15 means the tool wants us to reset and go again. The config var
+ * has been changed. The contexts will be built again from the
+ * config variable and the real contexts will be rebuilt below.
+ * This is easier and safer than having the tools rebuild the context
+ * list and the display correct. It's difficult to do because of all
+ * the inheriting and defaulting going on.
+ */
+ if(ret == 15){
+ if(edit_exceptions && !LVAL(fake_fspec, ew) && !LVAL(fake_nspec, ew)){
+ if(want_to(_("Really delete last exceptional collection"),
+ 'n', 'n', h_config_context_del_except,
+ WT_FLUSH_IN) != 'y'){
+ free_variable_values(fake_fspec);
+ free_variable_values(fake_nspec);
+ goto go_again;
+ }
+ }
+
+ /* resolve variable changes */
+ if((lval1 && !equal_list_arrays(lval1, LVAL(fake_fspec, ew))) ||
+ (fake_fspec && !equal_list_arrays(ps->VAR_FOLDER_SPEC,
+ LVAL(fake_fspec, ew)))){
+ i = set_variable_list(V_FOLDER_SPEC,
+ LVAL(fake_fspec, ew), TRUE, ew);
+ set_current_val(&ps->vars[V_FOLDER_SPEC], TRUE, FALSE);
+
+ if(i)
+ q_status_message(SM_ORDER, 3, 3,
+ _("Trouble saving change, cancelled"));
+ else if(!edit_exceptions && lval1 && !LVAL(fake_fspec, ew)){
+ cs->starting_var = fake_fspec;
+ cs->starting_varmem = 0;
+ q_status_message(SM_ORDER, 3, 3,
+ _("Deleted last Folder-Collection, reverting to default"));
+ }
+ else if(!edit_exceptions && !lval1 && !LVAL(fake_fspec, ew)){
+ cs->starting_var = fake_fspec;
+ cs->starting_varmem = 0;
+ q_status_message(SM_ORDER, 3, 3,
+ _("Deleted default Folder-Collection, reverting back to default"));
+ }
+ }
+
+ if((lval2 && !equal_list_arrays(lval2, LVAL(fake_nspec, ew))) ||
+ (fake_nspec && !equal_list_arrays(ps->VAR_NEWS_SPEC,
+ LVAL(fake_nspec, ew)))){
+ i = set_variable_list(V_NEWS_SPEC,
+ LVAL(fake_nspec, ew), TRUE, ew);
+ set_news_spec_current_val(TRUE, FALSE);
+
+ if(i)
+ q_status_message(SM_ORDER, 3, 3,
+ _("Trouble saving change, cancelled"));
+ else if(!edit_exceptions && lval2 && !LVAL(fake_nspec, ew) &&
+ ps->VAR_NEWS_SPEC && ps->VAR_NEWS_SPEC[0] &&
+ ps->VAR_NEWS_SPEC[0][0]){
+ cs->starting_var = fake_nspec;
+ cs->starting_varmem = 0;
+ q_status_message(SM_ORDER, 3, 3,
+ _("Deleted last News-Collection, reverting to default"));
+ }
+ else if(!edit_exceptions && !lval2 && !LVAL(fake_nspec, ew) &&
+ ps->VAR_NEWS_SPEC && ps->VAR_NEWS_SPEC[0] &&
+ ps->VAR_NEWS_SPEC[0][0]){
+ cs->starting_var = fake_nspec;
+ cs->starting_varmem = 0;
+ q_status_message(SM_ORDER, 3, 3,
+ _("Deleted default News-Collection, reverting back to default"));
+ }
+ }
+
+ free_variable_values(fake_fspec);
+ free_variable_values(fake_nspec);
+ goto go_again;
+ }
+
+ ps->mangled_screen = 1;
+
+ /* make the real context list match the changed config variables */
+ if(reinit_contexts){
+ free_contexts(&ps_global->context_list);
+ init_folders(ps_global);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Function to display/manage collections
+
+ ----*/
+CONTEXT_S *
+context_select_screen(struct pine *ps, CONT_SCR_S *cs, int ro_warn)
+{
+ CONTEXT_S *cp;
+ CONF_S *ctmpa = NULL, *first_line = NULL, *heading;
+ OPT_SCREEN_S screen, *saved_screen;
+ int readonly_warning = 0;
+
+ /* restrict to normal config */
+ ew = Main;
+
+ if(!cs->edit)
+ readonly_warning = 0;
+ else if(ps->restricted)
+ readonly_warning = 1;
+ else{
+ PINERC_S *prc = NULL;
+
+ switch(ew){
+ case Main:
+ prc = ps->prc;
+ break;
+ case Post:
+ prc = ps->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ readonly_warning = prc ? prc->readonly : 1;
+ if(ro_warn && prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return(NULL);
+ }
+ }
+
+ readonly_warning *= ro_warn;
+
+ /*
+ * Loop thru available contexts, setting up for display
+ * (note: if no "cp" we're hosed. should never happen ;)
+ */
+ for(cp = *cs->contexts; cp->prev; cp = cp->prev)
+ ;
+
+ /* delimiter for Mail Collections */
+ new_confline(&ctmpa); /* blank line */
+ ctmpa->keymenu = cs->keymenu;
+ ctmpa->help = cs->help.text;
+ ctmpa->help_title = cs->help.title;
+ ctmpa->tool = context_select_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+
+ do{
+ new_confline(&ctmpa);
+ heading = ctmpa;
+ ctmpa->value = cpystr(cp->nickname ? cp->nickname : cp->context);
+ ctmpa->var = cp->var.v;
+ ctmpa->keymenu = cs->keymenu;
+ ctmpa->help = cs->help.text;
+ ctmpa->help_title = cs->help.title;
+ ctmpa->tool = context_select_tool;
+ ctmpa->flags |= CF_STARTITEM;
+ ctmpa->valoffset = 4;
+ ctmpa->d.c.ct = cp;
+ ctmpa->d.c.cs = cs;
+
+ if(!first_line || cp == cs->start)
+ first_line = ctmpa;
+
+ /* Add explanatory text */
+ new_confline(&ctmpa);
+ ctmpa->value = cpystr(cp->label ? cp->label : "* * *");
+ ctmpa->keymenu = cs->keymenu;
+ ctmpa->help = cs->help.text;
+ ctmpa->help_title = cs->help.title;
+ ctmpa->tool = context_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = 8;
+
+ /* Always add blank line, make's shuffling a little easier */
+ new_confline(&ctmpa);
+ heading->headingp = ctmpa;
+ ctmpa->keymenu = cs->keymenu;
+ ctmpa->help = cs->help.text;
+ ctmpa->help_title = cs->help.title;
+ ctmpa->tool = context_select_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+ ctmpa->valoffset = 0;
+ }
+ while((cp = cp->next) != NULL);
+
+
+ saved_screen = opt_screen;
+ memset(&screen, 0, sizeof(screen));
+ screen.ro_warning = readonly_warning;
+ (void) conf_scroll_screen(ps, &screen, first_line, cs->title,
+ cs->print_string, 0);
+ opt_screen = saved_screen;
+ ps->mangled_screen = 1;
+ return(cs->selected);
+}
+
+
+int
+context_config_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int retval = 0;
+
+ switch(cmd){
+ case MC_DELETE :
+ if(!fixed_var((*cl)->var, "delete", "collection"))
+ retval = context_config_delete(ps, cl);
+
+ break;
+
+ case MC_EDIT :
+ if(!fixed_var((*cl)->var, "change", "collection"))
+ retval = context_config_edit(ps, cl);
+
+ break;
+
+ case MC_ADD :
+ if(!fixed_var((*cl)->var, "add to", "collection"))
+ retval = context_config_add(ps, cl);
+
+ break;
+
+ case MC_SHUFFLE :
+ if(!fixed_var((*cl)->var, "shuffle", "collection"))
+ retval = context_config_shuffle(ps, cl);
+
+ break;
+
+ case MC_EXIT :
+ retval = simple_exit_cmd(flags);
+ break;
+
+ default:
+ retval = -1;
+ break;
+ }
+
+ if(retval > 0)
+ ps->mangled_body = 1;
+
+ return(retval);
+}
+
+
+int
+context_config_add(struct pine *ps, CONF_S **cl)
+{
+ char *raw_ctxt;
+ struct variable *var;
+ char **lval;
+ int count;
+
+ if((raw_ctxt = context_edit_screen(ps, "ADD", NULL, NULL, NULL, NULL)) != NULL){
+
+ /*
+ * If var is non-NULL we add to the end of that var.
+ * If it is NULL, that means we're adding to the current_val, so
+ * we'll have to soak up the default values from there into our
+ * own variable.
+ */
+ if((*cl)->var){
+ var = (*cl)->var;
+ lval = LVAL((*cl)->var, ew);
+ }
+ else{
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ "Programmer botch in context_config_add");
+ return(0);
+ }
+
+ for(count = 0; lval && lval[count]; count++)
+ ;
+
+ if(!ccs_var_insert(ps, raw_ctxt, var, lval, count)){
+ fs_give((void **)&raw_ctxt);
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ _("Error adding new collection"));
+ return(0);
+ }
+
+ fs_give((void **)&raw_ctxt);
+
+ (*cl)->d.c.cs->starting_var = var;
+ (*cl)->d.c.cs->starting_varmem = count;
+ q_status_message(SM_ORDER, 0, 3,
+ _("New collection added. Use \"$\" to adjust order."));
+ return(15);
+ }
+
+ ps->mangled_screen = 1;
+ return(0);
+}
+
+
+int
+context_config_shuffle(struct pine *ps, CONF_S **cl)
+{
+ char prompt[256];
+ int n = 0, cmd, i1, i2, count = 0, insert_num, starting_varmem;
+ int news_problem = 0, deefault = 0;
+ ESCKEY_S ekey[3];
+ CONTEXT_S *cur_ctxt, *other_ctxt;
+ char *tmp, **lval, **lval1, **lval2;
+ struct variable *cur_var, *other_var;
+
+ if(!((*cl)->d.c.ct && (*cl)->var))
+ return(0);
+
+ /* enable UP? */
+ if((*cl)->d.c.ct->prev && !((*cl)->d.c.ct->prev->use & CNTXT_INHERIT)){
+ /*
+ * Don't allow shuffling news collection up to become
+ * the primary collection. That would happen if prev is primary
+ * and this one is news.
+ */
+ if((*cl)->d.c.ct->prev->use & CNTXT_SAVEDFLT &&
+ (*cl)->d.c.ct->use & CNTXT_NEWS)
+ news_problem++;
+ else{
+ ekey[n].ch = 'u';
+ ekey[n].rval = 'u';
+ ekey[n].name = "U";
+ ekey[n++].label = N_("Up");
+ deefault = 'u';
+ }
+ }
+
+ /* enable DOWN? */
+ if((*cl)->d.c.ct->next && !((*cl)->d.c.ct->next->use & CNTXT_INHERIT)){
+ /*
+ * Don't allow shuffling down past news collection if this
+ * is primary collection.
+ */
+ if((*cl)->d.c.ct->use & CNTXT_SAVEDFLT &&
+ (*cl)->d.c.ct->next->use & CNTXT_NEWS)
+ news_problem++;
+ else{
+ ekey[n].ch = 'd';
+ ekey[n].rval = 'd';
+ ekey[n].name = "D";
+ ekey[n++].label = N_("Down");
+ if(!deefault)
+ deefault = 'd';
+ }
+ }
+
+ if(n){
+ ekey[n].ch = -1;
+ snprintf(prompt, sizeof(prompt), _("Shuffle selected context %s%s%s? "),
+ (ekey[0].ch == 'u') ? _("UP") : "",
+ (n > 1) ? " or " : "",
+ (ekey[0].ch == 'd' || n > 1) ? _("DOWN") : "");
+ prompt[sizeof(prompt)-1] = '\0';
+
+ cmd = radio_buttons(prompt, -FOOTER_ROWS(ps), ekey,
+ deefault, 'x', NO_HELP, RB_NORM);
+ switch(cmd){
+ case 'x':
+ default:
+ cmd_cancelled("Shuffle");
+ return(0);
+
+ case 'u':
+ case 'd':
+ break;
+ }
+
+ /*
+ * This is complicated by the fact that there are two
+ * vars involved, the folder-collections and the news-collections.
+ * We may have to shuffle across collections.
+ */
+ cur_ctxt = (*cl)->d.c.ct;
+ if(cmd == 'd')
+ other_ctxt = (*cl)->d.c.ct->next;
+ else if(cmd == 'u')
+ other_ctxt = (*cl)->d.c.ct->prev;
+
+ cur_var = cur_ctxt->var.v;
+ other_var = other_ctxt->var.v;
+
+ /* swap elements of config var */
+ if(cur_var == other_var){
+ i1 = cur_ctxt->var.i;
+ i2 = other_ctxt->var.i;
+ lval = LVAL(cur_var, ew);
+ if(lval){
+ tmp = lval[i1];
+ lval[i1] = lval[i2];
+ lval[i2] = tmp;
+ }
+
+ starting_varmem = i2;
+ }
+ else{
+ /* swap into the other_var */
+ i1 = cur_ctxt->var.i;
+ i2 = other_ctxt->var.i;
+ lval1 = LVAL(cur_var, ew);
+ lval2 = LVAL(other_var, ew);
+ /* count */
+ for(count = 0; lval2 && lval2[count]; count++)
+ ;
+ if(cmd == 'd')
+ insert_num = count ? 1 : 0;
+ else{
+ insert_num = count ? count - 1 : count;
+ }
+
+ starting_varmem = insert_num;
+ if(ccs_var_insert(ps,lval1[i1],other_var,lval2,insert_num)){
+ if(!ccs_var_delete(ps, cur_ctxt)){
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ _("Error deleting shuffled context"));
+ return(0);
+ }
+ }
+ else{
+ q_status_message(SM_ORDER, 3, 3,
+ _("Trouble shuffling, cancelled"));
+ return(0);
+ }
+ }
+
+ (*cl)->d.c.cs->starting_var = other_var;
+ (*cl)->d.c.cs->starting_varmem = starting_varmem;
+
+ q_status_message(SM_ORDER, 0, 3, _("Collections shuffled"));
+ return(15);
+ }
+
+ if(news_problem)
+ /* TRANSLATORS: Sorry, can't move news group collections above email collections */
+ q_status_message(SM_ORDER, 0, 3, _("Sorry, cannot Shuffle news to top"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Sorry, nothing to Shuffle"));
+
+ return(0);
+}
+
+
+int
+context_config_edit(struct pine *ps, CONF_S **cl)
+{
+ char *raw_ctxt, tpath[MAILTMPLEN], *p, **lval;
+ struct variable *var;
+ int i;
+
+ if(!(*cl)->d.c.ct)
+ return(0);
+
+ /* Undigest the context */
+ strncpy(tpath, ((*cl)->d.c.ct->context[0] == '{'
+ && (p = strchr((*cl)->d.c.ct->context, '}')))
+ ? ++p
+ : (*cl)->d.c.ct->context, sizeof(tpath)-1);
+ tpath[sizeof(tpath)-1] = '\0';
+
+ if((p = strstr(tpath, "%s")) != NULL)
+ *p = '\0';
+
+ if((raw_ctxt = context_edit_screen(ps, "EDIT", (*cl)->d.c.ct->nickname,
+ (*cl)->d.c.ct->server, tpath,
+ (*cl)->d.c.ct->dir ?
+ (*cl)->d.c.ct->dir->view.user
+ : NULL)) != NULL){
+
+ if((*cl)->var){
+ var = (*cl)->var;
+ lval = LVAL(var, ew);
+ i = (*cl)->d.c.ct->var.i;
+
+ if(lval && lval[i] && !strcmp(lval[i], raw_ctxt))
+ q_status_message(SM_ORDER, 0, 3, _("No change"));
+ else if(lval){
+ if(lval[i])
+ fs_give((void **) &lval[i]);
+
+ lval[i] = raw_ctxt;
+ raw_ctxt = NULL;
+
+ q_status_message(SM_ORDER, 0, 3, _("Collection list entry updated"));
+ }
+
+ (*cl)->d.c.cs->starting_var = var;
+ (*cl)->d.c.cs->starting_varmem = i;
+
+ if(raw_ctxt)
+ fs_give((void **) &raw_ctxt);
+ }
+ else{
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ "Programmer botch in context_config_edit");
+ return(0);
+ }
+
+ return(15);
+ }
+
+ ps->mangled_screen = 1;
+ return(0);
+}
+
+
+int
+context_config_delete(struct pine *ps, CONF_S **cl)
+{
+ char tmp[MAILTMPLEN];
+
+ if(!(*cl)->var){
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ "Programmer botch in context_config_delete");
+ return(0);
+ }
+
+ if((*cl)->d.c.ct->use & CNTXT_SAVEDFLT &&
+ (*cl)->d.c.ct->next &&
+ (*cl)->d.c.ct->next->use & CNTXT_NEWS &&
+ (*cl)->d.c.ct->var.v == (*cl)->d.c.ct->next->var.v){
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ _("Sorry, cannot Delete causing news to move to top"));
+ return(0);
+ }
+
+ snprintf(tmp, sizeof(tmp), _("Delete the collection definition for \"%s\""),
+ (*cl)->value);
+ tmp[sizeof(tmp)-1] = '\0';
+ if(want_to(tmp, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+
+ (*cl)->d.c.cs->starting_var = (*cl)->var;
+ (*cl)->d.c.cs->starting_varmem = (*cl)->d.c.ct->var.i;
+ if((*cl)->d.c.ct->next){
+ if((*cl)->d.c.ct->next->var.v != (*cl)->var){
+ (*cl)->d.c.cs->starting_var = (*cl)->d.c.ct->next->var.v;
+ (*cl)->d.c.cs->starting_varmem = 0;
+ }
+ else{
+ (*cl)->d.c.cs->starting_var = (*cl)->var;
+ (*cl)->d.c.cs->starting_varmem = (*cl)->d.c.ct->var.i;
+ }
+ }
+ else{
+ if((*cl)->d.c.ct->var.i > 0){
+ (*cl)->d.c.cs->starting_var = (*cl)->var;
+ (*cl)->d.c.cs->starting_varmem = (*cl)->d.c.ct->var.i - 1;
+ }
+ else{
+ if((*cl)->d.c.ct->prev){
+ (*cl)->d.c.cs->starting_var = (*cl)->d.c.ct->prev->var.v;
+ (*cl)->d.c.cs->starting_varmem = (*cl)->d.c.ct->prev->var.i;
+ }
+ }
+ }
+
+ /* Remove from var list */
+ if(!ccs_var_delete(ps, (*cl)->d.c.ct)){
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ _("Error deleting renamed context"));
+ return(0);
+ }
+
+ q_status_message(SM_ORDER, 0, 3, _("Collection deleted"));
+
+ return(15);
+ }
+
+ q_status_message(SM_ORDER, 0, 3, _("No collections deleted"));
+ return(0);
+}
+
+
+int
+ccs_var_delete(struct pine *ps, CONTEXT_S *ctxt)
+{
+ int count, i;
+ char **newl = NULL, **lval, **lp, ***alval;
+
+ if(ctxt)
+ lval = LVAL(ctxt->var.v, ew);
+ else
+ lval = NULL;
+
+ for(count = 0; lval && lval[count]; count++)
+ ; /* sum the list */
+
+ if(count > 1){
+ newl = (char **) fs_get(count * sizeof(char *));
+ for(i = 0, lp = newl; lval[i]; i++)
+ if(i != ctxt->var.i)
+ *lp++ = cpystr(lval[i]);
+
+ *lp = NULL;
+ }
+
+ alval = ALVAL(ctxt->var.v, ew);
+ if(alval){
+ free_list_array(alval);
+ if(newl){
+ for(i = 0; newl[i] ; i++) /* count elements */
+ ;
+
+ *alval = (char **) fs_get((i+1) * sizeof(char *));
+
+ for(i = 0; newl[i] ; i++)
+ (*alval)[i] = cpystr(newl[i]);
+
+ (*alval)[i] = NULL;
+ }
+ }
+
+ free_list_array(&newl);
+ return(1);
+}
+
+
+/*
+ * Insert into "var", which currently has values "oldvarval", the "newline"
+ * at position "insert".
+ */
+int
+ccs_var_insert(struct pine *ps, char *newline, struct variable *var, char **oldvarval, int insert)
+{
+ int count, i, offset;
+ char **newl, ***alval;
+
+ for(count = 0; oldvarval && oldvarval[count]; count++)
+ ;
+
+ if(insert < 0 || insert > count){
+ q_status_message(SM_ORDER,3,5, "unexpected problem inserting folder");
+ return(0);
+ }
+
+ newl = (char **)fs_get((count + 2) * sizeof(char *));
+ newl[insert] = cpystr(newline);
+ newl[count + 1] = NULL;
+ for(i = offset = 0; oldvarval && oldvarval[i]; i++){
+ if(i == insert)
+ offset = 1;
+
+ newl[i + offset] = cpystr(oldvarval[i]);
+ }
+
+ alval = ALVAL(var, ew);
+ if(alval){
+ free_list_array(alval);
+ if(newl){
+ for(i = 0; newl[i] ; i++) /* count elements */
+ ;
+
+ *alval = (char **) fs_get((i+1) * sizeof(char *));
+
+ for(i = 0; newl[i] ; i++)
+ (*alval)[i] = cpystr(newl[i]);
+
+ (*alval)[i] = NULL;
+ }
+ }
+
+ free_list_array(&newl);
+ return(1);
+}
+
+
+int
+context_select_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int retval = 0;
+
+ switch(cmd){
+ case MC_CHOICE :
+ (*cl)->d.c.cs->selected = (*cl)->d.c.ct;
+ retval = simple_exit_cmd(flags);
+ break;
+
+ case MC_EXIT :
+ retval = simple_exit_cmd(flags);
+ break;
+
+ case MC_MAIN :
+ retval = simple_exit_cmd(flags);
+ ps_global->next_screen = main_menu_screen;
+ break;
+
+ case MC_INDEX :
+ if(THREADING()
+ && sp_viewing_a_thread(ps_global->mail_stream)
+ && unview_thread(ps_global, ps_global->mail_stream, ps_global->msgmap)){
+ ps_global->next_screen = mail_index_screen;
+ ps_global->view_skipped_index = 0;
+ ps_global->mangled_screen = 1;
+ }
+
+ retval = simple_exit_cmd(flags);
+ ps_global->next_screen = mail_index_screen;
+ break;
+
+ case MC_COMPOSE :
+ retval = simple_exit_cmd(flags);
+ ps_global->next_screen = compose_screen;
+ break;
+
+ case MC_ROLE :
+ retval = simple_exit_cmd(flags);
+ ps_global->next_screen = alt_compose_screen;
+ break;
+
+ case MC_GOTO :
+ {
+ int notrealinbox;
+ CONTEXT_S *c = (*cl)->d.c.ct;
+ char *new_fold = broach_folder(-FOOTER_ROWS(ps), 0, &notrealinbox, &c);
+
+ if(new_fold && do_broach_folder(new_fold, c, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT) > 0){
+ ps_global->next_screen = mail_index_screen;
+ retval = simple_exit_cmd(flags);
+ }
+ else
+ ps->mangled_footer = 1;
+ }
+
+ break;
+
+ case MC_QUIT :
+ retval = simple_exit_cmd(flags);
+ ps_global->next_screen = quit_screen;
+ break;
+
+ default:
+ retval = -1;
+ break;
+ }
+
+ if(retval > 0)
+ ps->mangled_body = 1;
+
+ return(retval);
+}
diff --git a/alpine/context.h b/alpine/context.h
new file mode 100644
index 00000000..a7c8d641
--- /dev/null
+++ b/alpine/context.h
@@ -0,0 +1,51 @@
+/*
+ * $Id: context.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_CONTEXT_INCLUDED
+#define PINE_CONTEXT_INCLUDED
+
+
+#include "../pith/context.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "help.h"
+#include "keymenu.h"
+
+
+/*
+ * Structures to control the collection list screen
+ */
+typedef struct context_screen {
+ unsigned edit:1;
+ char *title, *print_string;
+ CONTEXT_S *start, /* for context_select_screen */
+ *selected,
+ **contexts;
+ struct variable *starting_var; /* another type of start for config */
+ int starting_varmem;
+ struct {
+ HelpType text;
+ char *title;
+ } help;
+ struct key_menu *keymenu;
+} CONT_SCR_S;
+
+
+/* exported protoypes */
+void context_config_screen(struct pine *, CONT_SCR_S *, int);
+CONTEXT_S *context_select_screen(struct pine *, CONT_SCR_S *, int);
+
+
+#endif /* PINE_CONTEXT_INCLUDED */
diff --git a/alpine/dispfilt.c b/alpine/dispfilt.c
new file mode 100644
index 00000000..be46fe60
--- /dev/null
+++ b/alpine/dispfilt.c
@@ -0,0 +1,454 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: dispfilt.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ dispfilt.c
+ Display filter technology ;)
+
+ ====*/
+
+#include "headers.h"
+
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/filter.h"
+#include "../pith/store.h"
+#include "../pith/addrstring.h"
+#include "../pith/mimedesc.h"
+#include "../pith/list.h"
+#include "../pith/detach.h"
+
+#include "mailview.h"
+#include "signal.h"
+#include "busy.h"
+#include "dispfilt.h"
+
+
+/* internal prototypes */
+int df_valid_test(BODY *, char *);
+
+
+
+/*
+ * dfilter - pipe the data from the given storage object thru the
+ * global display filter and into whatever the putchar's
+ * function points to.
+ *
+ * Input is assumed to be UTF-8.
+ * That's converted to user's locale and passed to rawcmd.
+ * That's converted back to UTF-8 and passed through aux_filters.
+ */
+char *
+dfilter(char *rawcmd, STORE_S *input_so, gf_io_t output_pc, FILTLIST_S *aux_filters)
+{
+ char *status = NULL, *cmd, *resultf = NULL, *tmpfile = NULL;
+ int key = 0;
+
+ if((cmd = expand_filter_tokens(rawcmd,NULL,&tmpfile,&resultf,NULL,&key,NULL)) != NULL){
+ suspend_busy_cue();
+#ifndef _WINDOWS
+ ps_global->mangled_screen = 1;
+ ClearScreen();
+ fflush(stdout);
+#endif
+
+ /*
+ * If it was requested that the interaction take place via
+ * a tmpfile, fill it with text from our input_so, and let
+ * system_pipe handle the rest. Session key and tmp file
+ * mode support additions based loosely on a patch
+ * supplied by Thomas Stroesslin <thomas.stroesslin@epfl.ch>
+ */
+ if(tmpfile){
+ PIPE_S *filter_pipe;
+ FILE *fp;
+ gf_io_t gc, pc;
+ STORE_S *tmpf_so;
+
+ /* write the tmp file */
+ so_seek(input_so, 0L, 0);
+ if((tmpf_so = so_get(FileStar, tmpfile, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
+ if(key){
+ so_puts(tmpf_so, filter_session_key());
+ so_puts(tmpf_so, NEWLINE);
+ }
+ /* copy input to tmp file */
+ gf_set_so_readc(&gc, input_so);
+ gf_set_so_writec(&pc, tmpf_so);
+ gf_filter_init();
+ status = gf_pipe(gc, pc);
+ gf_clear_so_readc(input_so);
+ gf_clear_so_writec(tmpf_so);
+ if(so_give(&tmpf_so) != 0 && status == NULL)
+ status = error_description(errno);
+
+ /* prepare the terminal in case the filter uses it */
+ if(status == NULL){
+ if((filter_pipe = open_system_pipe(cmd, NULL, NULL,
+ PIPE_USER | PIPE_RESET,
+ 0, pipe_callback, NULL)) != NULL){
+ if(close_system_pipe(&filter_pipe, NULL, pipe_callback) == 0){
+ /* pull result out of tmp file */
+ if((fp = our_fopen(tmpfile, "rb")) != NULL){
+ gf_set_readc(&gc, fp, 0L, FileStar, READ_FROM_LOCALE);
+ gf_filter_init();
+ if(aux_filters)
+ for( ; aux_filters->filter; aux_filters++)
+ gf_link_filter(aux_filters->filter,
+ aux_filters->data);
+
+ status = gf_pipe(gc, output_pc);
+ fclose(fp);
+ }
+ else
+ status = "Can't read result of display filter";
+ }
+ else
+ status = "Filter command returned error.";
+ }
+ else
+ status = "Can't open pipe for display filter";
+ }
+
+ our_unlink(tmpfile);
+ }
+ else
+ status = "Can't open display filter tmp file";
+ }
+ else if((status = gf_filter(cmd, key ? filter_session_key() : NULL,
+ input_so, output_pc, aux_filters,
+ F_ON(F_DISABLE_TERM_RESET_DISP, ps_global),
+ pipe_callback)) != NULL){
+ unsigned long ch;
+
+ fprintf(stdout,"\r\n%s Hit return to continue.", status);
+ fflush(stdout);
+ while((ch = read_char(300)) != ctrl('M') && ch != NO_OP_IDLE)
+ putchar(BELL);
+ }
+
+ if(resultf){
+ if(name_file_size(resultf) > 0L)
+ display_output_file(resultf, "Filter", NULL, DOF_BRIEF);
+
+ fs_give((void **)&resultf);
+ }
+
+ resume_busy_cue(0);
+#ifndef _WINDOWS
+ ClearScreen();
+#endif
+ fs_give((void **)&cmd);
+ }
+
+ return(status);
+}
+
+
+/*
+ * expand_filter_tokens - return an alloc'd string with any special tokens
+ * in the given filter expanded, NULL otherwise.
+ */
+char *
+expand_filter_tokens(char *filter, ENVELOPE *env, char **tmpf, char **resultf,
+ char **mtypef, int *key, int *hdrs)
+{
+ char **array, **q;
+ char *bp, *cmd = NULL, *p = NULL,
+ *tfn = NULL, *rfn = NULL, *dfn = NULL, *mfn = NULL,
+ *freeme_tfn = NULL, *freeme_rfn = NULL, *freeme_mfn = NULL;
+ int n = 0;
+ size_t len;
+
+ /*
+ * break filter into words delimited by whitespace so that we can
+ * look for tokens. First we count how many words.
+ */
+ if((bp = cpystr(filter)) != NULL)
+ p = strtok(bp, " \t");
+
+ if(p){
+ n++;
+ while(strtok(NULL, " \t") != NULL)
+ n++;
+ }
+
+ if(!n){
+ dprint((1, "Unexpected failure creating sending_filter\n"));
+ if(bp)
+ fs_give((void **)&bp);
+
+ return(cmd);
+ }
+
+ q = array = (char **) fs_get((n+1) * sizeof(*array));
+ memset(array, 0, (n+1) * sizeof(*array));
+ /* restore bp and form the array */
+ strncpy(bp, filter, strlen(filter)+1);
+ if((p = strtok(bp, " \t")) != NULL){
+ if(q-array < n+1)
+ *q++ = cpystr(p);
+
+ while((p = strtok(NULL, " \t")) != NULL && (q-array < n+1))
+ *q++ = cpystr(p);
+ }
+
+ if(bp)
+ fs_give((void **)&bp);
+
+ for(q = array; *q != NULL; q++){
+ if(!strcmp(*q, "_RECIPIENTS_")){
+ char *rl = NULL;
+
+ if(env){
+ size_t l;
+
+ char *to_l = addr_list_string(env->to,
+ simple_addr_string, 0),
+ *cc_l = addr_list_string(env->cc,
+ simple_addr_string, 0),
+ *bcc_l = addr_list_string(env->bcc,
+ simple_addr_string, 0);
+
+ l = strlen(to_l) + strlen(cc_l) + strlen(bcc_l) + 2;
+ rl = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(rl, l+1, "%s %s %s", to_l, cc_l, bcc_l);
+ fs_give((void **)&to_l);
+ fs_give((void **)&cc_l);
+ fs_give((void **)&bcc_l);
+ for(to_l = rl; *to_l; to_l++) /* to_l overloaded! */
+ if(*to_l == ',') /* space delim'd list */
+ *to_l = ' ';
+ }
+
+ fs_give((void **)q);
+ *q = rl ? rl : cpystr("");
+ }
+ else if(!strcmp(*q, "_TMPFILE_")){
+ if(!tfn){
+ tfn = temp_nam(NULL, "sf"); /* send filter file */
+ if(!tfn)
+ dprint((1, "FAILED creat of _TMPFILE_\n"));
+ }
+
+ if(tmpf)
+ *tmpf = tfn;
+ else
+ freeme_tfn = tfn;
+
+ fs_give((void **)q);
+ *q = cpystr(tfn ? tfn : "");
+ }
+ else if(!strcmp(*q, "_RESULTFILE_")){
+ if(!rfn){
+ rfn = temp_nam(NULL, "rf");
+ /*
+ * We don't create the result file, the user does.
+ * That means we have to remove it after temp_nam creates it.
+ */
+ if(rfn)
+ our_unlink(rfn);
+ else
+ dprint((1, "FAILED creat of _RESULTFILE_\n"));
+ }
+
+ if(resultf)
+ *resultf = rfn;
+ else
+ freeme_rfn = rfn;
+
+ fs_give((void **)q);
+ *q = cpystr(rfn ? rfn : "");
+ }
+ else if(!strcmp(*q, "_MIMETYPE_")){
+ if(!mfn){
+ mfn = temp_nam(NULL, "mt");
+ /*
+ * We don't create the mimetype file, the user does.
+ * That means we have to remove it after temp_nam creates it.
+ */
+ if(mfn)
+ our_unlink(mfn);
+ else
+ dprint((1, "FAILED creat of _MIMETYPE_\n"));
+ }
+
+ if(mtypef)
+ *mtypef = mfn;
+ else
+ freeme_mfn = mfn;
+
+ fs_give((void **)q);
+ *q = cpystr(mfn ? mfn : "");
+ }
+ else if(!strcmp(*q, "_DATAFILE_")){
+ if((dfn = filter_data_file(1)) == NULL) /* filter data file */
+ dprint((1, "FAILED creat of _DATAFILE_\n"));
+
+ fs_give((void **)q);
+ *q = cpystr(dfn ? dfn : "");
+ }
+ else if(!strcmp(*q, "_PREPENDKEY_")){
+ (*q)[0] = '\0';
+ if(key)
+ *key = 1;
+ }
+ else if(!strcmp(*q, "_INCLUDEALLHDRS_")){
+ (*q)[0] = '\0';
+ if(hdrs)
+ *hdrs = 1;
+ }
+ }
+
+ /* count up required length */
+ for(len = 0, q = array; *q != NULL; q++)
+ len += (strlen(*q)+1);
+
+ cmd = fs_get((len+1) * sizeof(char));
+ cmd[len] = '\0';
+
+ /* cat together all the args */
+ p = cmd;
+ for(q = array; *q != NULL; q++){
+ sstrncpy(&p, *q, len+1-(p-cmd));
+ sstrncpy(&p, " ", len+1-(p-cmd));
+ }
+
+ cmd[len] = '\0';
+
+ if(freeme_rfn)
+ fs_give((void **) &freeme_rfn);
+
+ if(freeme_tfn){ /* this shouldn't happen */
+ our_unlink(freeme_tfn);
+ fs_give((void **) &freeme_tfn);
+ }
+
+ if(freeme_mfn)
+ fs_give((void **) &freeme_mfn);
+
+ free_list_array(&array);
+
+ return(cmd);
+}
+
+
+/*
+ * filter_session_key - function to return randomly generated number
+ * representing a key for this session. The idea is
+ * the display/sending filter could use it to muddle
+ * up any pass phrase or such stored in the
+ * "_DATAFILE_".
+ */
+char *
+filter_session_key(void)
+{
+ static char *key = NULL;
+
+ if(!key){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%ld", random());
+ key = cpystr(tmp_20k_buf);
+ }
+
+ return(key);
+}
+
+
+
+
+/*
+ * filter_data_file - function to return filename of scratch file for
+ * display and sending filters. This file is created
+ * the first time it's needed, and persists until pine
+ * exits.
+ */
+char *
+filter_data_file(int create_it)
+{
+ static char *fn = NULL;
+
+ if(!fn && create_it)
+ fn = temp_nam(NULL, "df");
+
+ return(fn);
+}
+
+
+/*
+ * df_static_trigger - look thru the display filter list and set the
+ * filter to any triggers that don't require scanning
+ * the message segment.
+ */
+char *
+dfilter_trigger(struct mail_bodystruct *body, char *cmdbuf, size_t cmdbuflen)
+{
+ int passed = 0;
+ char **l, *test, *cmd;
+
+ for(l = ps_global->VAR_DISPLAY_FILTERS ; l && *l && !passed; l++){
+
+ get_pair(*l, &test, &cmd, 1, 1);
+
+ dprint((5, "static trigger: \"%s\" --> \"%s\" and \"%s\"",
+ (l && *l) ? *l : "?",
+ test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
+
+ if((passed = (df_valid_test(body, test) && valid_filter_command(&cmd))) != 0){
+ strncpy(cmdbuf, cmd, cmdbuflen);
+ cmdbuf[cmdbuflen-1] = '\0';
+ }
+
+ fs_give((void **) &test);
+ fs_give((void **) &cmd);
+ }
+
+ return(passed ? cmdbuf : NULL);
+}
+
+
+
+int
+df_valid_test(struct mail_bodystruct *body, char *test)
+{
+ int passed = 0;
+
+ if(!(passed = !test)){ /* NO test always wins */
+ if(!*test){
+ passed++; /* NULL test also wins! */
+ }
+ else if(body && !struncmp(test, "_CHARSET(", 9)){
+ char *p = strrindex(test, ')');
+
+ if(p){
+ *p = '\0'; /* tie off user charset */
+ if((p = parameter_val(body->parameter,"charset")) != NULL){
+ passed = !strucmp(test + 9, p);
+ fs_give((void **) &p);
+ }
+ else
+ passed = !strucmp(test + 9, "us-ascii");
+ }
+ else
+ dprint((1,
+ "filter trigger: malformed test: %s\n",
+ test ? test : "?"));
+ }
+ }
+
+ return(passed);
+}
diff --git a/alpine/dispfilt.h b/alpine/dispfilt.h
new file mode 100644
index 00000000..bac20821
--- /dev/null
+++ b/alpine/dispfilt.h
@@ -0,0 +1,30 @@
+/*
+ * $Id: dispfilt.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_DISPFILT_INCLUDED
+#define PINE_DISPFILT_INCLUDED
+
+
+
+/* exported protoypes */
+char *dfilter(char *, STORE_S *, gf_io_t, FILTLIST_S *);
+char *dfilter_trigger(BODY *, char *, size_t);
+char *expand_filter_tokens(char *, ENVELOPE *, char **, char **, char **, int *, int *);
+char *filter_session_key(void);
+char *filter_data_file(int);
+
+
+
+#endif /* PINE_DISPFILT_INCLUDED */
diff --git a/alpine/flagmaint.c b/alpine/flagmaint.c
new file mode 100644
index 00000000..c2385692
--- /dev/null
+++ b/alpine/flagmaint.c
@@ -0,0 +1,332 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: flagmaint.c 807 2007-11-09 01:21:33Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "flagmaint.h"
+#include "confscroll.h"
+#include "../pith/state.h"
+#include "../pith/flag.h"
+#include "../pith/status.h"
+#include "../pith/mailcmd.h"
+#include "../pith/icache.h"
+
+
+#define FLAG_ADD_RETURN 15
+
+
+/*
+ * Internal prototypes
+ */
+int flag_checkbox_tool(struct pine *, int, CONF_S **, unsigned);
+
+
+/*----------------------------------------------------------------------
+ Function to control flag set/clearing
+
+ Basically, turn the flags into a fake list of features...
+
+ Returns 0 unless user has added a keyword, then 1.
+
+ ----*/
+int
+flag_maintenance_screen(struct pine *ps, struct flag_screen *flags)
+{
+ int i, lv, lc, maxwidth, offset, need, rv = 0;
+ char tmp[1200], **p, *spacer;
+ CONF_S *ctmpa, *first_line;
+ struct flag_table *fp;
+ OPT_SCREEN_S screen;
+
+try_again:
+ maxwidth = MAX(MIN((ps->ttyo ? ps->ttyo->screen_cols : 80), 150), 30);
+ first_line = NULL;
+ ctmpa = NULL;
+
+ for(p = flags->explanation; p && *p; p++){
+ new_confline(&ctmpa);
+ ctmpa->keymenu = &flag_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = flag_checkbox_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = 0;
+ ctmpa->value = cpystr(_(*p));
+ }
+
+ /* Now wire flags checkboxes together */
+ for(lv = 0, lc = 0, fp = (flags->flag_table ? *flags->flag_table : NULL);
+ fp && fp->name; fp++){ /* longest name */
+ if(fp->flag != F_COMMENT){
+ if(lv < (i = utf8_width(_(fp->name))))
+ lv = i;
+ if(fp->comment && lc < (i = utf8_width(fp->comment)))
+ lc = i;
+ }
+ }
+
+ lv = MIN(lv,100);
+ lc = MIN(lc,100);
+ if(lc > 0)
+ spacer = " ";
+ else
+ spacer = "";
+
+ offset = 6;
+ if((need = offset + 5 + lv + strlen(spacer) + lc) > maxwidth){
+ offset -= (need - maxwidth);
+ offset = MAX(0,offset);
+ if((need = offset + 5 + lv + strlen(spacer) + lc) > maxwidth){
+ spacer = " ";
+ if((need = offset + 5 + lv + strlen(spacer) + lc) > maxwidth){
+ lc -= (need - maxwidth);
+ lc = MAX(0,lc);
+ if(lc == 0)
+ spacer = "";
+ }
+ }
+ }
+
+ new_confline(&ctmpa);
+ ctmpa->keymenu = &flag_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = flag_checkbox_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = 0;
+ ctmpa->value = cpystr("");
+
+ new_confline(&ctmpa);
+ ctmpa->keymenu = &flag_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = flag_checkbox_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = 0;
+ utf8_snprintf(tmp, sizeof(tmp), "%*.*w %s", offset+3, offset+3, _("Set"), _("Flag/Keyword Name"));
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmpa->value = cpystr(tmp);
+
+ new_confline(&ctmpa);
+ ctmpa->keymenu = &flag_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = flag_checkbox_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = 0;
+ snprintf(tmp, sizeof(tmp), "%*.*s--- %.*s",
+ offset, offset, "",
+ lv+lc+strlen(spacer), repeat_char(lv+lc+strlen(spacer), '-'));
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmpa->value = cpystr(tmp);
+
+ for(fp = (flags->flag_table ? *flags->flag_table : NULL);
+ fp && fp->name; fp++){ /* build the list */
+ new_confline(&ctmpa);
+ if(!first_line && (fp->flag != F_COMMENT))
+ first_line = ctmpa;
+
+ ctmpa->keymenu = &flag_keymenu;
+ ctmpa->tool = flag_checkbox_tool;
+ ctmpa->valoffset = offset;
+
+ if(fp->flag == F_COMMENT){
+ ctmpa->help = NO_HELP;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr(fp->name);
+ }
+ else{
+ ctmpa->help = fp->help;
+ ctmpa->d.f.ftbl = flags->flag_table;
+ ctmpa->d.f.fp = fp;
+
+ utf8_snprintf(tmp, sizeof(tmp), "[%c] %-*.*w%s%-*.*w",
+ (fp->set == 0) ? ' ' : (fp->set == 1) ? 'X' : '?',
+ lv, lv, _(fp->name),
+ spacer, lc, lc, fp->comment ? fp->comment : "");
+ ctmpa->value = cpystr(tmp);
+ }
+ }
+
+ memset(&screen, 0, sizeof(screen));
+ /*
+ * TRANSLATORS: FLAG MAINTENANCE is a screen title.
+ * Print something1 using something2. configuration is something1
+ */
+ if(conf_scroll_screen(ps, &screen, first_line,
+ _("FLAG MAINTENANCE"),
+ _("configuration"), 0) == FLAG_ADD_RETURN){
+ int flags, r;
+ char keyword[500];
+ char nickname[500];
+ char prompt[500];
+ char *error = NULL;
+ KEYWORD_S *kw;
+ HelpType help;
+
+ /*
+ * User is asking to add a new keyword. We will add it to the
+ * mailbox if necessary and add it to the keywords list from
+ * Setup/Config. Then we will modify the flag_table and present
+ * the flag modification screen again.
+ */
+
+ ps->mangled_screen = 1;
+ keyword[0] = '\0';
+ flags = OE_APPEND_CURRENT;
+ help = NO_HELP;
+
+ do{
+ if(error){
+ q_status_message(SM_ORDER, 3, 4, error);
+ fs_give((void **) &error);
+ }
+
+ strncpy(prompt, _("Keyword to be added : "), sizeof(prompt)-1);
+ prompt[sizeof(prompt)-1] = '\0';
+ r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
+ sizeof(keyword), prompt, NULL, help, &flags);
+
+ if(r == 3)
+ help = help == NO_HELP ? h_type_keyword : NO_HELP;
+ else if(r == 1){
+ cmd_cancelled("Add Keyword");
+ goto try_again;
+ }
+
+ removing_leading_and_trailing_white_space(keyword);
+
+ }while(r == 3 || keyword_check(keyword, &error));
+
+ for(kw = ps->keywords; kw; kw = kw->next){
+ if(kw->kw && !strucmp(kw->kw, keyword)){
+ q_status_message(SM_ORDER, 3, 4, _("Keyword already configured, changing nickname"));
+ break;
+ }
+ }
+
+ snprintf(prompt, sizeof(prompt), _("Optional nickname for \"%s\" : "), keyword);
+
+ nickname[0] = '\0';
+ help = NO_HELP;
+
+ do{
+ r = optionally_enter(nickname, -FOOTER_ROWS(ps_global), 0,
+ sizeof(nickname), prompt, NULL, help, &flags);
+
+ if(r == 3)
+ help = help == NO_HELP ? h_type_keyword_nickname : NO_HELP;
+ else if(r == 1){
+ cmd_cancelled("Add Keyword");
+ goto try_again;
+ }
+
+ removing_leading_and_trailing_white_space(nickname);
+
+ }while(r == 3);
+
+ if(keyword[0]){
+ char ***alval;
+ int offset = -1;
+ struct variable *var;
+
+ var = &ps_global->vars[V_KEYWORDS];
+ alval = ALVAL(var, Main);
+
+ for(kw = ps->keywords; kw; kw = kw->next){
+ offset++;
+ if(kw->kw && !strucmp(kw->kw, keyword)){
+ /* looks like it should already exist at offset */
+ break;
+ }
+ }
+
+ if(!kw)
+ offset = -1;
+
+ if(offset >= 0 && (*alval) && (*alval)[offset]){
+ fs_give((void **) &(*alval)[offset]);
+ (*alval)[offset] = put_pair(nickname, keyword);
+ }
+ else if(!*alval){
+ offset = 0;
+ *alval = (char **) fs_get(2*sizeof(char *));
+ (*alval)[offset] = put_pair(nickname, keyword);
+ (*alval)[offset+1] = NULL;
+ }
+ else{
+ for(offset=0; (*alval)[offset]; offset++);
+ ;
+
+ fs_resize((void **) alval, (offset + 2) * sizeof(char *));
+ (*alval)[offset] = put_pair(nickname, keyword);
+ (*alval)[offset+1] = NULL;
+ }
+
+ set_current_val(var, TRUE, FALSE);
+ if(ps_global->prc)
+ ps_global->prc->outstanding_pinerc_changes = 1;
+
+ if(ps_global->keywords)
+ free_keyword_list(&ps_global->keywords);
+
+ if(var->current_val.l && var->current_val.l[0])
+ ps_global->keywords = init_keyword_list(var->current_val.l);
+
+ clear_index_cache(ps_global->mail_stream, 0);
+
+ rv = 1;
+ }
+ }
+
+ ps->mangled_screen = 1;
+
+ return(rv);
+}
+
+
+/*
+ * Message flag manipulation tool
+ *
+ *
+ * returns: -1 on unrecognized cmd, 0 if no change, 1 if change
+ */
+int
+flag_checkbox_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0, state;
+
+ switch(cmd){
+ case MC_TOGGLE: /* mark/unmark feature */
+ state = (*cl)->d.f.fp->set;
+ state = (state == 1) ? 0 : (!state && ((*cl)->d.f.fp->ukn)) ? 2 : 1;
+ (*cl)->value[1] = (state == 0) ? ' ' : ((state == 1) ? 'X': '?');
+ (*cl)->d.f.fp->set = state;
+ rv = 1;
+ break;
+
+ case MC_ADD:
+ rv = FLAG_ADD_RETURN;
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = simple_exit_cmd(flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
diff --git a/alpine/flagmaint.h b/alpine/flagmaint.h
new file mode 100644
index 00000000..dbc9dfbf
--- /dev/null
+++ b/alpine/flagmaint.h
@@ -0,0 +1,55 @@
+/*
+ * $Id: flagmaint.h 807 2007-11-09 01:21:33Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_FLAGMAINT_INCLUDED
+#define PINE_FLAGMAINT_INCLUDED
+
+
+#include "../pith/state.h"
+
+
+/*
+ * Structures to control flag maintenance screen
+ */
+struct flag_table {
+ char *name; /* flag's name or keyword's nickname */
+ HelpType help; /* help text */
+ long flag; /* flag tag (i.e., F_DEL ) */
+ unsigned set:2; /* its state (set, unset, unknown) */
+ unsigned ukn:1; /* allow unknown state */
+ char *keyword; /* only for user-defined, could be same as name */
+ char *comment; /* comment about the name, keyword name if there is a nick */
+};
+
+
+struct flag_screen {
+ char **explanation;
+ struct flag_table **flag_table;
+};
+
+/*
+ * Some defs to help keep flag setting straight...
+ */
+#define CMD_FLAG_CLEAR FALSE
+#define CMD_FLAG_SET TRUE
+#define CMD_FLAG_UNKN 2
+
+
+/* exported protoypes */
+int flag_maintenance_screen(struct pine *, struct flag_screen *);
+
+
+#endif /* PINE_FLAGMAINT_INCLUDED */
diff --git a/alpine/folder.c b/alpine/folder.c
new file mode 100644
index 00000000..02a73b71
--- /dev/null
+++ b/alpine/folder.c
@@ -0,0 +1,6922 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: folder.c 1144 2008-08-14 16:53:34Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ folder.c
+
+ Screen to display and manage all the users folders
+
+This puts up a list of all the folders in the users mail directory on
+the screen spacing it nicely. The arrow keys move from one to another
+and the user can delete the folder or select it to change to or copy a
+message to. The dispay lets the user scroll up or down a screen full,
+or search for a folder name.
+ ====*/
+
+
+#include "headers.h"
+#include "folder.h"
+#include "keymenu.h"
+#include "status.h"
+#include "context.h"
+#include "mailview.h"
+#include "mailindx.h"
+#include "mailcmd.h"
+#include "titlebar.h"
+#include "alpine.h"
+#include "send.h"
+#include "help.h"
+#include "imap.h"
+#include "signal.h"
+#include "reply.h"
+#include "setup.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/folder.h"
+#include "../pith/flag.h"
+#include "../pith/filter.h"
+#include "../pith/msgno.h"
+#include "../pith/thread.h"
+#include "../pith/util.h"
+#include "../pith/stream.h"
+#include "../pith/save.h"
+#include "../pith/busy.h"
+#include "../pith/list.h"
+
+
+#define SUBSCRIBE_PMT \
+ _("Enter newsgroup name (or partial name to get a list): ")
+#define LISTMODE_GRIPE _("Use \"X\" to mark selections in list mode")
+#define SEL_ALTER_PMT _("ALTER folder selection : ")
+#define SEL_TEXT_PMT _("Select by folder Name or Contents ? ")
+#define SEL_PROP_PMT _("Select by which folder property ? ")
+#define DIR_FOLD_PMT \
+ _("Folder and directory of the same name will be deleted. Continue")
+
+
+/*
+ * folder_list_write
+ */
+#define FLW_NONE 0x00
+#define FLW_LUNK 0x01 /* Using handles */
+#define FLW_SLCT 0x02 /* Some folder is selected, may need X to show it */
+#define FLW_LIST 0x04 /* Allow for ListMode for subscribing */
+#define FLW_UNSEEN 0x08 /* Add (unseen) */
+
+
+
+/*----------------------------------------------------------------------
+ The data needed to redraw the folders screen, including the case where the
+screen changes size in which case it may recalculate the folder_display.
+ ----*/
+
+
+/*
+ * Struct managing folder_lister arguments and holding state
+ * for various internal methods
+ */
+typedef struct _folder_screen {
+ CONTEXT_S *context; /* current collection */
+ CONTEXT_S *list_cntxt; /* list mode collection */
+ MAILSTREAM **cache_streamp; /* cached mailstream */
+ char first_folder[MAXFOLDER];
+ unsigned first_dir:1; /* first_folder is a dir */
+ unsigned combined_view:1; /* display flat folder list */
+ unsigned no_dirs:1; /* no dirs in this screen */
+ unsigned no_empty_dirs:1; /* no empty dirs on this screen */
+ unsigned relative_path:1; /* return fully-qual'd specs */
+ unsigned save_sel:1;
+ unsigned force_intro:1;
+ unsigned agg_ops:1;
+ unsigned include_unseen_cnt:1;
+ struct key_menu *km; /* key label/command bindings */
+ struct _func_dispatch {
+ int (*valid)(FOLDER_S *, struct _folder_screen *);
+ struct {
+ HelpType text;
+ char *title;
+ } help;
+ struct {
+ char *bar;
+ TitleBarType style;
+ } title;
+ } f;
+} FSTATE_S;
+
+
+/*
+ * Struct mananging folder_lister metadata as it get's passed
+ * in and back up thru scrolltool
+ */
+typedef struct _folder_proc {
+ FSTATE_S *fs;
+ STRLIST_S *rv;
+ unsigned done:1; /* done listing folders?... */
+ unsigned all_done:1; /* ...and will list no more forever */
+} FPROC_S;
+
+#define FPROC(X) ((FPROC_S *)(X)->proc.data.p)
+
+
+typedef struct _scanarg {
+ MAILSTREAM *stream;
+ int newstream;
+ CONTEXT_S *context;
+ char *pattern;
+ char type;
+} SCANARG_S;
+
+
+typedef struct _statarg {
+ MAILSTREAM *stream;
+ int newstream;
+ CONTEXT_S *context;
+ long flags;
+ long nmsgs;
+ int cmp;
+} STATARG_S;
+
+
+/*
+ * Internal prototypes
+ */
+STRLIST_S *folders_for_subscribe(struct pine *, CONTEXT_S *, char *);
+int folders_for_post(struct pine *, CONTEXT_S **, char *);
+int folder_selector(struct pine *, FSTATE_S *, char *, CONTEXT_S **);
+void folder_sublist_context(char *, CONTEXT_S *, CONTEXT_S *, FDIR_S **, int);
+CONTEXT_S *context_screen(CONTEXT_S *, struct key_menu *, int);
+int exit_collection_add(struct headerentry *, void (*)(void), int, char **);
+char *cancel_collection_add(void (*)(void));
+char *cancel_collection_edit(void (*)(void));
+char *cancel_collection_editing(char *, void (*)(void));
+int build_namespace(char *, char **, char **, BUILDER_ARG *, int *);
+int fl_val_gen(FOLDER_S *, FSTATE_S *);
+int fl_val_writable(FOLDER_S *, FSTATE_S *);
+int fl_val_subscribe(FOLDER_S *, FSTATE_S *);
+STRLIST_S *folder_lister(struct pine *, FSTATE_S *);
+int folder_list_text(struct pine *, FPROC_S *, gf_io_t, HANDLE_S **, int);
+int folder_list_write(gf_io_t, HANDLE_S **, CONTEXT_S *, int, char *, int);
+int folder_list_write_prefix(FOLDER_S *, int, gf_io_t);
+int folder_list_write_middle(FOLDER_S *fp, CONTEXT_S *ctxt, gf_io_t pc, HANDLE_S *);
+int folder_list_write_suffix(FOLDER_S *, int, gf_io_t);
+int color_monitored_unseen(FOLDER_S *, int);
+int folder_list_ith(int, CONTEXT_S *);
+char *folder_list_center_space(char *, int);
+HANDLE_S *folder_list_handle(FSTATE_S *, HANDLE_S *);
+int folder_processor(int, MSGNO_S *, SCROLL_S *);
+int folder_lister_clickclick(SCROLL_S *);
+int folder_lister_choice(SCROLL_S *);
+int folder_lister_finish(SCROLL_S *, CONTEXT_S *, int);
+int folder_lister_addmanually(SCROLL_S *);
+void folder_lister_km_manager(SCROLL_S *, int);
+void folder_lister_km_sel_manager(SCROLL_S *, int);
+void folder_lister_km_sub_manager(SCROLL_S *, int);
+int folder_select(struct pine *, CONTEXT_S *, int);
+int folder_lister_select(FSTATE_S *, CONTEXT_S *, int, int);
+int folder_lister_parent(FSTATE_S *, CONTEXT_S *, int, int);
+char *folder_lister_fullname(FSTATE_S *, char *);
+void folder_export(SCROLL_S *);
+int folder_import(SCROLL_S *, char *, size_t);
+int folder_select_toggle(CONTEXT_S *, int, int (*)(CONTEXT_S *, int));
+char *end_bracket_no_nest(char *);
+int group_subscription(char *, size_t, CONTEXT_S *);
+int rename_folder(CONTEXT_S *, int, char *, size_t, MAILSTREAM *);
+int delete_folder(CONTEXT_S *, int, char *, size_t, MAILSTREAM **);
+void print_folders(FPROC_S *);
+int scan_get_pattern(char *, char *, int);
+int folder_select_text(struct pine *, CONTEXT_S *, int);
+int foreach_do_scan(FOLDER_S *, void *);
+int scan_scan_folder(MAILSTREAM *, CONTEXT_S *, FOLDER_S *, char *);
+int folder_select_props(struct pine *, CONTEXT_S *, int);
+int folder_select_count(long *, int *);
+int foreach_do_stat(FOLDER_S *, void *);
+int foreach_folder(CONTEXT_S *, int, int (*)(FOLDER_S *, void *), void *);
+int folder_delimiter(char *);
+int shuffle_incoming_folders(CONTEXT_S *, int);
+int swap_incoming_folders(int, int, FLIST *);
+int search_folder_list(void *, char *);
+char *get_post_list(char **);
+char *quote_brackets_if_needed(char *);
+#ifdef _WINDOWS
+int folder_list_popup(SCROLL_S *, int);
+int folder_list_select_popup(SCROLL_S *, int);
+void folder_popup_config(FSTATE_S *, struct key_menu *,MPopup *);
+#endif
+
+
+/*----------------------------------------------------------------------
+ Front end to folder lister when it's called from the main menu
+
+ Args: ps -- The general pine_state data structure
+
+ Result: runs context and folder listers
+
+ ----*/
+void
+folder_screen(struct pine *ps)
+{
+ int n = 1;
+ CONTEXT_S *cntxt = ps->context_current;
+ STRLIST_S *folders;
+ FSTATE_S fs;
+ MAILSTREAM *cache_stream = NULL;
+
+ dprint((1, "=== folder_screen called ====\n"));
+ mailcap_free(); /* free resources we won't be using for a while */
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ /* Initialize folder state and dispatches */
+ memset(&fs, 0, sizeof(FSTATE_S));
+ fs.context = cntxt;
+ fs.cache_streamp = &cache_stream;
+ fs.combined_view = F_ON(F_CMBND_FOLDER_DISP, ps_global) != 0;
+ fs.agg_ops = F_ON(F_ENABLE_AGG_OPS, ps_global) != 0;
+ fs.relative_path = 1;
+ fs.include_unseen_cnt = 1;
+ fs.f.valid = fl_val_gen;
+ /* TRANSLATORS: The all upper case things are screen titles */
+ fs.f.title.bar = _("FOLDER LIST");
+ fs.f.title.style = FolderName;
+ fs.f.help.text = h_folder_maint;
+ fs.f.help.title = _("HELP FOR FOLDERS");
+ fs.km = &folder_km;
+
+ if(context_isambig(ps->cur_folder)
+ && (IS_REMOTE(ps->cur_folder) || !is_absolute_path(ps->cur_folder)
+ || (cntxt && cntxt->context && cntxt->context[0] == '{'))){
+ if(strlen(ps_global->cur_folder) < MAXFOLDER - 1){
+ strncpy(fs.first_folder, ps_global->cur_folder, MAXFOLDER);
+ fs.first_folder[MAXFOLDER-1] = '\0';
+ }
+
+ /*
+ * If we're asked to start in the folder list of the current
+ * folder and it looks like the current folder is part of the
+ * current context, try to start in the list of folders in the
+ * current context.
+ */
+ if(ps->start_in_context || fs.combined_view){
+ char tmp[MAILTMPLEN], *p, *q;
+ FDIR_S *fp;
+
+ ps->start_in_context = 0;
+ n = 0;
+
+ if(!(NEWS_TEST(cntxt) || (cntxt->use & CNTXT_INCMNG))
+ && cntxt->dir->delim
+ && strchr(ps->cur_folder, cntxt->dir->delim)){
+ for(p = strchr(q = ps->cur_folder, cntxt->dir->delim);
+ p;
+ p = strchr(q = ++p, cntxt->dir->delim)){
+ strncpy(tmp, q, MIN(p - q, sizeof(tmp)-1));
+ tmp[MIN(p - q, sizeof(tmp)-1)] = '\0';
+
+ fp = next_folder_dir(cntxt, tmp, FALSE, fs.cache_streamp);
+
+ fp->desc = folder_lister_desc(cntxt, fp);
+
+ /* Insert new directory into list */
+ fp->delim = cntxt->dir->delim;
+ fp->prev = cntxt->dir;
+ fp->status |= CNTXT_SUBDIR;
+ cntxt->dir = fp;
+ }
+ }
+ }
+ }
+
+ while(ps->next_screen == SCREEN_FUN_NULL
+ && ((n++) ? (cntxt = context_screen(cntxt,&c_mgr_km,1)) != NULL :1)){
+
+ fs.context = cntxt;
+ if(F_ON(F_ENABLE_INCOMING_CHECKING, ps) && ps->VAR_INCOMING_FOLDERS && ps->VAR_INCOMING_FOLDERS[0])
+ ps->in_folder_screen = 1;
+
+ if((folders = folder_lister(ps, &fs)) != NULL){
+
+ ps->in_folder_screen = 0;
+
+ if(ps && ps->ttyo){
+ blank_keymenu(ps->ttyo->screen_rows - 2, 0);
+ ps->mangled_footer = 1;
+ }
+
+ if(do_broach_folder((char *) folders->name,
+ fs.context, fs.cache_streamp
+ && *fs.cache_streamp ? fs.cache_streamp
+ : NULL, 0L) == 1){
+ reset_context_folders(ps->context_list);
+ ps->next_screen = mail_index_screen;
+ }
+
+ if(fs.cache_streamp)
+ *fs.cache_streamp = NULL;
+ free_strlist(&folders);
+ }
+
+ ps->in_folder_screen = 0;
+ }
+
+ if(fs.cache_streamp && *fs.cache_streamp)
+ pine_mail_close(*fs.cache_streamp);
+
+ ps->prev_screen = folder_screen;
+}
+
+
+/*----------------------------------------------------------------------
+ Front end to folder lister when it's called from the main menu
+
+ Args: ps -- The general pine_state data structure
+
+ Result: runs context and folder listers
+
+ ----*/
+void
+folder_config_screen(struct pine *ps, int edit_exceptions)
+{
+ CONT_SCR_S css;
+ char title[50], htitle[50];
+
+ dprint((1, "=== folder_config_screen called ====\n"));
+ mailcap_free(); /* free resources we won't be using for a while */
+
+ if(edit_exceptions){
+ snprintf(title, sizeof(title), _("SETUP EXCEPTIONS COLLECTION LIST"));
+ snprintf(htitle, sizeof(htitle), _("HELP FOR SETUP EXCEPTIONS COLLECTIONS"));
+ }
+ else{
+ snprintf(title, sizeof(title), _("SETUP COLLECTION LIST"));
+ snprintf(htitle, sizeof(htitle), _("HELP FOR SETUP COLLECTIONS"));
+ }
+
+ memset(&css, 0, sizeof(CONT_SCR_S));
+ css.title = title;
+ /* TRANSLATORS: Print something1 using something2.
+ contexts is something1 */
+ css.print_string = _("contexts");
+ css.contexts = &ps_global->context_list;
+ css.help.text = h_collection_maint;
+ css.help.title = htitle;
+ css.keymenu = &c_cfg_km;
+ css.edit = 1;
+
+ /*
+ * Use conf_scroll_screen to manage display/selection
+ * of contexts
+ */
+ context_config_screen(ps_global, &css, edit_exceptions);
+}
+
+
+/*----------------------------------------------------------------------
+ Browse folders for ^T selection from the Goto Prompt
+
+ Args: ps --
+ cntxtp -- pointer to addr of context to start in, list, and return
+ folder -- pointer to buffer inwhich to return selected folder
+
+ Returns: 1 if we have something valid in cntxtp and folder
+ 0 if problem or user cancelled
+
+ ----*/
+int
+folders_for_goto(struct pine *ps, CONTEXT_S **cntxtp, char *folder, int sublist)
+{
+ int rv;
+ CONTEXT_S fake_context;
+ FDIR_S *fake_dir = NULL;
+ FSTATE_S fs;
+
+ dprint((1, "=== folders_for_goto called ====\n"));
+
+ /* Initialize folder state and dispatches */
+ memset(&fs, 0, sizeof(FSTATE_S));
+ fs.context = *cntxtp;
+ fs.combined_view = !sublist && F_ON(F_CMBND_FOLDER_DISP, ps_global) != 0;
+ fs.f.valid = fl_val_gen;
+ fs.f.title.bar = _("GOTO: SELECT FOLDER");
+ fs.f.title.style = FolderName;
+ fs.f.help.text = h_folder_open;
+ fs.f.help.title = _("HELP FOR OPENING FOLDERS");
+ fs.km = &folder_sel_km;
+
+ /* If we were provided a string,
+ * dummy up a context for a substring match
+ */
+ if(sublist && *folder && context_isambig(folder)){
+ if((*cntxtp)->use & CNTXT_INCMNG){
+ q_status_message(SM_ORDER, 0, 3,
+ _("All folders displayed for Incoming Collection"));
+ }
+ else{
+ folder_sublist_context(folder, *cntxtp, &fake_context,
+ &fake_dir, sublist);
+ fs.context = &fake_context;
+ fs.relative_path = 1;
+ fs.force_intro = 1;
+ cntxtp = &fs.context;
+ }
+ }
+
+ rv = folder_selector(ps, &fs, folder, cntxtp);
+
+ if(fake_dir)
+ free_fdir(&fake_dir, TRUE);
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Browse folders for ^T selection from the Save Prompt
+
+ Args: ps --
+ cntxtp -- pointer to addr of context to start in, list, and return
+ folder -- pointer to buffer inwhich to return selected folder
+
+ Returns: 1 if we have something valid in cntxtp and folder
+ 0 if problem or user cancelled
+
+ ----*/
+int
+folders_for_save(struct pine *ps, CONTEXT_S **cntxtp, char *folder, int sublist)
+{
+ int rv;
+ CONTEXT_S fake_context;
+ FDIR_S *fake_dir = NULL;
+ FSTATE_S fs;
+
+ dprint((1, "=== folders_for_save called ====\n"));
+
+ /* Initialize folder state and dispatches */
+ memset(&fs, 0, sizeof(FSTATE_S));
+ fs.context = *cntxtp;
+ fs.combined_view = F_ON(F_CMBND_FOLDER_DISP, ps_global) != 0;
+ fs.f.valid = fl_val_gen;
+ fs.f.title.bar = _("SAVE: SELECT FOLDER");
+ fs.f.title.style = MessageNumber;
+ fs.f.help.text = h_folder_save;
+ fs.f.help.title = _("HELP FOR SAVING MESSAGES TO FOLDERS");
+ fs.km = &folder_sela_km;
+
+ /* If we were provided a string,
+ * dummy up a context for a substring match
+ */
+ if(sublist && *folder && context_isambig(folder)){
+ if((*cntxtp)->use & CNTXT_INCMNG){
+ q_status_message(SM_ORDER, 0, 3,
+ _("All folders displayed for Incoming Collection"));
+ }
+ else{
+ folder_sublist_context(folder, *cntxtp, &fake_context,
+ &fake_dir, sublist);
+ fs.context = &fake_context;
+ fs.relative_path = 1;
+ fs.force_intro = 1;
+ cntxtp = &fs.context;
+ }
+ }
+
+ rv = folder_selector(ps, &fs, folder, cntxtp);
+
+ if(fake_dir)
+ free_fdir(&fake_dir, TRUE);
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Browse folders for ^T selection from the Subscribe Prompt
+
+ Args: ps --
+ cntxtp -- pointer to addr of context to start in, list, and return
+ folder -- pointer to buffer inwhich to return selected folder
+
+ Returns: 1 if we have something valid in cntxtp and folder
+ 0 if problem or user cancelled
+
+ ----*/
+STRLIST_S *
+folders_for_subscribe(struct pine *ps, CONTEXT_S *cntxt, char *folder)
+{
+ STRLIST_S *folders = NULL;
+ FSTATE_S fs;
+ void (*redraw)(void);
+
+ dprint((1, "=== folders_for_sub called ====\n"));
+
+ /* Initialize folder state and dispatches */
+ memset(&fs, 0, sizeof(FSTATE_S));
+ fs.context = cntxt;
+ fs.f.valid = fl_val_subscribe;
+ fs.f.title.bar = _("SUBSCRIBE: SELECT FOLDER");
+ fs.f.title.style = FolderName;
+ fs.f.help.text = h_folder_subscribe;
+ fs.f.help.title = _("HELP SELECTING NEWSGROUP TO SUBSCRIBE TO");
+ fs.km = &folder_sub_km;
+ fs.force_intro = 1;
+
+ fs.context = cntxt;
+ redraw = ps_global->redrawer;
+ folders = folder_lister(ps, &fs);
+ ps_global->redrawer = redraw;
+ if(ps_global->redrawer)
+ (*ps_global->redrawer)();
+
+ return(folders);
+}
+
+
+/*----------------------------------------------------------------------
+ Browse folders for ^T selection for posting
+
+ Args: ps --
+ cntxtp -- pointer to addr of context to start in, list, and return
+ folder -- pointer to buffer inwhich to return selected folder
+
+ Returns: 1 if we have something valid in cntxtp and folder
+ 0 if problem or user cancelled
+
+ ----*/
+int
+folders_for_post(struct pine *ps, CONTEXT_S **cntxtp, char *folder)
+{
+ FSTATE_S fs;
+
+ dprint((1, "=== folders_for_post called ====\n"));
+
+ /* Initialize folder state and dispatches */
+ memset(&fs, 0, sizeof(FSTATE_S));
+ fs.context = *cntxtp;
+ fs.f.valid = fl_val_subscribe;
+ fs.f.title.bar = _("NEWS: SELECT GROUP");
+ fs.f.title.style = FolderName;
+ fs.f.help.text = h_folder_postnews;
+ fs.f.help.title = _("HELP FOR SELECTING NEWSGROUP TO POST TO");
+ fs.km = &folder_post_km;
+
+ return(folder_selector(ps, &fs, folder, cntxtp));
+}
+
+
+int
+folder_selector(struct pine *ps, FSTATE_S *fs, char *folder, CONTEXT_S **cntxtp)
+{
+ int rv = 0;
+ STRLIST_S *folders;
+
+ do{
+ fs->context = *cntxtp;
+ if((folders = folder_lister(ps, fs)) != NULL){
+ strncpy(folder, (char *) folders->name, MAILTMPLEN-1);
+ folder[MAILTMPLEN-1] = '\0';
+ free_strlist(&folders);
+ *cntxtp = fs->context;
+ rv++;
+ break;
+ }
+ else if(!(fs->context
+ && (fs->context->next || fs->context->prev))
+ || fs->combined_view)
+ break;
+ }
+ while((*cntxtp = context_screen(*cntxtp, &c_sel_km, 0)) != NULL);
+
+ return(rv);
+}
+
+
+void
+folder_sublist_context(char *folder, CONTEXT_S *cntxt, CONTEXT_S *new_cntxt, FDIR_S **new_dir, int lev)
+{
+ char *p, *q, *ref, *wildcard;
+
+ *new_cntxt = *cntxt;
+ new_cntxt->next = new_cntxt->prev = NULL;
+ new_cntxt->dir = *new_dir = (FDIR_S *) fs_get(sizeof(FDIR_S));
+ memset(*new_dir, 0, sizeof(FDIR_S));
+ (*new_dir)->status |= CNTXT_NOFIND;
+ (*new_dir)->folders = init_folder_entries();
+ if(!((*new_dir)->delim = cntxt->dir->delim)){
+ /* simple LIST to acquire delimiter, doh */
+ build_folder_list(NULL, new_cntxt, "", NULL,
+ NEWS_TEST(new_cntxt) ? BFL_LSUB : BFL_NONE);
+ new_cntxt->dir->status = CNTXT_NOFIND;
+ }
+
+ wildcard = NEWS_TEST(new_cntxt) ? "*" : "%";
+
+ if((p = strrindex(folder, (*new_dir)->delim)) != NULL){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s", p + 1, wildcard);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ (*new_dir)->view.internal = cpystr(tmp_20k_buf);
+ for(ref = tmp_20k_buf, q = new_cntxt->context;
+ (*ref = *q) != '\0' && !(*q == '%' && *(q+1) == 's');
+ ref++, q++)
+ ;
+
+ for(q = folder; q <= p; q++, ref++)
+ *ref = *q;
+
+ *ref = '\0';
+ (*new_dir)->ref = cpystr(tmp_20k_buf);
+
+ (*new_dir)->status |= CNTXT_SUBDIR;
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s",
+ (lev > 1) ? wildcard : "", folder, wildcard);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ (*new_dir)->view.internal = cpystr(tmp_20k_buf);
+ /* leave (*new_dir)->ref == NULL */
+ }
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("List of folders matching \"%s*\""), folder);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ (*new_dir)->desc = cpystr(tmp_20k_buf);
+}
+
+
+/*----------------------------------------------------------------------
+ Browse folders for ^T selection from the composer
+
+ Args: error_mess -- pointer to place to return an error message
+
+ Returns: result if folder selected, NULL if not
+ Composer expects the result to be alloc'd here
+
+ ----*/
+char *
+folders_for_fcc(char **errmsg)
+{
+ char *rs = NULL;
+ STRLIST_S *folders;
+ FSTATE_S fs;
+
+ dprint((1, "=== folders_for_fcc called ====\n"));
+
+ /* Coming back from composer */
+ fix_windsize(ps_global);
+ init_sigwinch();
+
+ /* Initialize folder state and dispatches */
+ memset(&fs, 0, sizeof(FSTATE_S));
+ fs.combined_view = F_ON(F_CMBND_FOLDER_DISP, ps_global) != 0;
+ fs.f.valid = fl_val_gen;
+ fs.f.title.bar = _("FCC: SELECT FOLDER");
+ fs.f.title.style = FolderName;
+ fs.f.help.text = h_folder_fcc;
+ fs.f.help.title = _("HELP FOR SELECTING THE FCC");
+ fs.km = &folder_sela_km;
+
+ /* start in the default save context */
+ fs.context = default_save_context(ps_global->context_list);
+
+ do{
+ if((folders = folder_lister(ps_global, &fs)) != NULL){
+ char *name;
+
+ /* replace nickname with full name */
+ if(!(name = folder_is_nick((char *) folders->name,
+ FOLDERS(fs.context), 0)))
+ name = (char *) folders->name;
+
+ if(context_isambig(name) && !((fs.context->use) & CNTXT_SAVEDFLT)){
+ char path_in_context[MAILTMPLEN];
+
+ context_apply(path_in_context, fs.context, name,
+ sizeof(path_in_context));
+ if(!(IS_REMOTE(path_in_context)
+ || is_absolute_path(path_in_context))){
+ /*
+ * Name is relative to the home directory,
+ * so have to add that. Otherwise, the sender will
+ * assume it is in the primary collection since it
+ * will still be ambiguous.
+ */
+ build_path(tmp_20k_buf, ps_global->ui.homedir,
+ path_in_context, SIZEOF_20KBUF);
+ rs = cpystr(tmp_20k_buf);
+ }
+ else
+ rs = cpystr(path_in_context);
+ }
+ else
+ rs = cpystr(name);
+
+ free_strlist(&folders);
+ break;
+ }
+ else if(!(fs.context && (fs.context->next || fs.context->prev))
+ || fs.combined_view)
+ break;
+ }
+ while((fs.context = context_screen(fs.context, &c_fcc_km, 0)) != NULL);
+
+ return(rs);
+}
+
+
+/*----------------------------------------------------------------------
+ Browse folders for ^T selection from the role editor
+
+ Returns: result if folder selected, NULL if not
+ Tesult is alloc'd here
+
+ ----*/
+char *
+folder_for_config(int flags)
+{
+ char *rs = NULL;
+ STRLIST_S *folders;
+ FSTATE_S fs;
+
+ dprint((1, "=== folder_for_config called ====\n"));
+
+ /* Initialize folder state and dispatches */
+ memset(&fs, 0, sizeof(FSTATE_S));
+ fs.combined_view = F_ON(F_CMBND_FOLDER_DISP, ps_global) != 0;
+ fs.f.valid = fl_val_gen;
+ fs.f.title.bar = _("SELECT FOLDER");
+ fs.f.title.style = FolderName;
+ fs.km = &folder_sela_km;
+ if(flags & FOR_PATTERN){
+ fs.f.help.text = h_folder_pattern_roles;
+ fs.f.help.title = _("HELP FOR SELECTING CURRENT FOLDER");
+ }
+ else if(flags & FOR_OPTIONSCREEN){
+ fs.f.help.text = h_folder_stayopen_folders;
+ fs.f.help.title = _("HELP FOR SELECTING FOLDER");
+ }
+ else{
+ fs.f.help.text = h_folder_action_roles;
+ fs.f.help.title = _("HELP FOR SELECTING FOLDER");
+ }
+
+ /* start in the current context */
+ fs.context = ps_global->context_current;
+
+ do{
+ if((folders = folder_lister(ps_global, &fs)) != NULL){
+ char *name = NULL;
+
+ /* replace nickname with full name */
+ if(!(flags & (FOR_PATTERN | FOR_OPTIONSCREEN)))
+ name = folder_is_nick((char *) folders->name,
+ FOLDERS(fs.context), 0);
+
+ if(!name)
+ name = (char *) folders->name;
+
+ if(context_isambig(name) &&
+ !(flags & (FOR_PATTERN | FOR_OPTIONSCREEN) &&
+ folder_is_nick(name, FOLDERS(fs.context), 0))){
+ char path_in_context[MAILTMPLEN];
+
+ context_apply(path_in_context, fs.context, name,
+ sizeof(path_in_context));
+
+ /*
+ * We may still have a non-fully-qualified name. In the
+ * action case, that will be interpreted in the primary
+ * collection instead of as a local name.
+ * Qualify that name the same way we
+ * qualify it in match_pattern_folder_specific.
+ */
+ if(!(IS_REMOTE(path_in_context) ||
+ path_in_context[0] == '#')){
+ if(strlen(path_in_context) < (MAILTMPLEN/2)){
+ char tmp[MAX(MAILTMPLEN,NETMAXMBX)];
+ char *t;
+
+ t = mailboxfile(tmp, path_in_context);
+ rs = cpystr(t);
+ }
+ else{ /* the else part should never happen */
+ build_path(tmp_20k_buf, ps_global->ui.homedir,
+ path_in_context, SIZEOF_20KBUF);
+ rs = cpystr(tmp_20k_buf);
+ }
+ }
+ else
+ rs = cpystr(path_in_context);
+ }
+ else
+ rs = cpystr(name);
+
+ free_strlist(&folders);
+ break;
+ }
+ else if(!(fs.context && (fs.context->next || fs.context->prev))
+ || fs.combined_view)
+ break;
+ }
+ while((fs.context = context_screen(fs.context, &c_fcc_km, 0)) != NULL);
+
+ return(rs);
+}
+
+
+/*
+ * offer screen with list of contexts to select and some sort
+ * of descriptions
+ */
+CONTEXT_S *
+context_screen(CONTEXT_S *start, struct key_menu *km, int edit_config)
+{
+ /* If a list, let the user tell us what to do */
+ if(F_OFF(F_CMBND_FOLDER_DISP, ps_global)
+ && ps_global->context_list
+ && ps_global->context_list->next){
+ CONT_SCR_S css;
+
+ memset(&css, 0, sizeof(CONT_SCR_S));
+ css.title = _("COLLECTION LIST");
+ css.print_string = _("contexts");
+ css.start = start;
+ css.contexts = &ps_global->context_list;
+ css.help.text = h_collection_screen;
+ css.help.title = _("HELP FOR COLLECTION LIST");
+ css.keymenu = km;
+ css.edit = edit_config;
+
+ /*
+ * Use conf_scroll_screen to manage display/selection
+ * of contexts
+ */
+ return(context_select_screen(ps_global, &css, 0));
+ }
+
+ return(ps_global->context_list);
+}
+
+
+static struct headerentry headents_templ[]={
+ /* TRANSLATORS: these are the headings for setting up a collection of
+ folders, PATH is a filesystem path, VIEW is sort of a technical
+ term that can be used to restrict the View to fewer folders */
+ {"Nickname : ", N_("Nickname"), h_composer_cntxt_nick, 12, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Server : ", N_("Server"), h_composer_cntxt_server, 12, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Path : ", N_("Path"), h_composer_cntxt_path, 12, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"View : ", N_("View"), h_composer_cntxt_view, 12, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {NULL, NULL, NO_HELP, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
+};
+#define AC_NICK 0
+#define AC_SERV 1
+#define AC_PATH 2
+#define AC_VIEW 3
+
+
+char *
+context_edit_screen(struct pine *ps, char *func, char *def_nick,
+ char *def_serv, char *def_path, char *def_view)
+{
+ int editor_result, i, j;
+ char nickpart[MAILTMPLEN], servpart[MAILTMPLEN], new_cntxt[MAILTMPLEN];
+ char pathpart[MAILTMPLEN], allbutnick[MAILTMPLEN];
+ char tmp[MAILTMPLEN], *nick, *serv, *path, *view,
+ *return_cntxt = NULL, *val, *p, new[MAILTMPLEN];
+ char nickpmt[100], servpmt[100], pathpmt[100], viewpmt[100];
+ int indent;
+ PICO pbf;
+ STORE_S *msgso;
+ NETMBX mb;
+
+ standard_picobuf_setup(&pbf);
+ pbf.pine_flags |= P_NOBODY;
+ pbf.exittest = exit_collection_add;
+ pbf.canceltest = (func && !strucmp(func, "EDIT")) ? cancel_collection_edit
+ : cancel_collection_add;
+ snprintf(tmp, sizeof(tmp), _("FOLDER COLLECTION %s"), func);
+ tmp[sizeof(tmp)-1] = '\0';
+ pbf.pine_anchor = set_titlebar(tmp, ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+
+ /* An informational message */
+ if((msgso = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ pbf.msgtext = (void *) so_text(msgso);
+ so_puts(msgso,
+ _("\n Fill in the fields above to add a Folder Collection to your"));
+ so_puts(msgso,
+ _("\n COLLECTION LIST screen."));
+ so_puts(msgso,
+ _("\n Use the \"^G\" command to get help specific to each item, and"));
+ so_puts(msgso,
+ _("\n use \"^X\" when finished."));
+ }
+
+
+ pbf.headents = (struct headerentry *)fs_get((sizeof(headents_templ)
+ /sizeof(struct headerentry))
+ * sizeof(struct headerentry));
+ memset((void *) pbf.headents, 0,
+ (sizeof(headents_templ)/sizeof(struct headerentry))
+ * sizeof(struct headerentry));
+
+ for(i = 0; headents_templ[i].prompt; i++)
+ pbf.headents[i] = headents_templ[i];
+
+ indent = utf8_width(_("Nickname")) + 2;
+
+ nick = cpystr(def_nick ? def_nick : "");
+ pbf.headents[AC_NICK].realaddr = &nick;
+ pbf.headents[AC_NICK].maxlen = strlen(nick);
+ utf8_snprintf(nickpmt, sizeof(nickpmt), "%-*.*w: ", indent, indent, _("Nickname"));
+ pbf.headents[AC_NICK].prompt = nickpmt;
+ pbf.headents[AC_NICK].prwid = indent+2;
+
+ serv = cpystr(def_serv ? def_serv : "");
+ pbf.headents[AC_SERV].realaddr = &serv;
+ pbf.headents[AC_SERV].maxlen = strlen(serv);
+ utf8_snprintf(servpmt, sizeof(servpmt), "%-*.*w: ", indent, indent, _("Server"));
+ pbf.headents[AC_SERV].prompt = servpmt;
+ pbf.headents[AC_SERV].prwid = indent+2;
+
+ path = cpystr(def_path ? def_path : "");
+ pbf.headents[AC_PATH].realaddr = &path;
+ pbf.headents[AC_PATH].maxlen = strlen(path);
+ pbf.headents[AC_PATH].bldr_private = (void *) 0;
+ utf8_snprintf(pathpmt, sizeof(pathpmt), "%-*.*w: ", indent, indent, _("Path"));
+ pbf.headents[AC_PATH].prompt = pathpmt;
+ pbf.headents[AC_PATH].prwid = indent+2;
+
+ view = cpystr(def_view ? def_view : "");
+ pbf.headents[AC_VIEW].realaddr = &view;
+ pbf.headents[AC_VIEW].maxlen = strlen(view);
+ utf8_snprintf(viewpmt, sizeof(viewpmt), "%-*.*w: ", indent, indent, _("View"));
+ pbf.headents[AC_VIEW].prompt = viewpmt;
+ pbf.headents[AC_VIEW].prwid = indent+2;
+
+ /*
+ * If this is new context, setup to query IMAP server
+ * for location of personal namespace.
+ */
+ if(!(def_nick || def_serv || def_path || def_view)){
+ pbf.headents[AC_SERV].builder = build_namespace;
+ pbf.headents[AC_SERV].affected_entry = &pbf.headents[AC_PATH];
+ pbf.headents[AC_SERV].bldr_private = (void *) 0;
+ }
+
+ /* pass to pico and let user change them */
+ editor_result = pico(&pbf);
+
+ if(editor_result & COMP_GOTHUP){
+ hup_signal();
+ }
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+
+ if(editor_result & COMP_CANCEL){
+ cmd_cancelled(func);
+ }
+ else if(editor_result & COMP_EXIT){
+ servpart[0] = pathpart[0] = new_cntxt[0] = allbutnick[0] = '\0';
+ if(serv && *serv){
+ if(serv[0] == '{' && serv[strlen(serv)-1] == '}'){
+ strncpy(servpart, serv, sizeof(servpart)-1);
+ servpart[sizeof(servpart)-1] = '\0';
+ }
+ else
+ snprintf(servpart, sizeof(servpart), "{%s}", serv);
+
+ if(mail_valid_net_parse(servpart, &mb)){
+ if(!struncmp(mb.service, "nntp", 4)
+ && (!path || strncmp(path, "#news.", 6)))
+ strncat(servpart, "#news.", sizeof(servpart)-1-strlen(servpart));
+ }
+ else
+ panic("Unexpected invalid server");
+ }
+ else
+ servpart[0] = '\0';
+
+ servpart[sizeof(servpart)-1] = '\0';
+
+ new_cntxt[0] = '\0';
+ if(nick && *nick){
+ val = quote_if_needed(nick);
+ if(val){
+ strncpy(new_cntxt, val, sizeof(new_cntxt)-2);
+ new_cntxt[sizeof(new_cntxt)-2] = '\0';
+ if(val != nick)
+ fs_give((void **)&val);
+
+ strncat(new_cntxt, " ", sizeof(new_cntxt)-strlen(new_cntxt)-1);
+ new_cntxt[sizeof(new_cntxt)-1] = '\0';
+ }
+ }
+
+ p = allbutnick;
+ sstrncpy(&p, servpart, sizeof(allbutnick)-1-(p-allbutnick));
+ allbutnick[sizeof(allbutnick)-1] = '\0';
+
+ if(path){
+ val = quote_brackets_if_needed(path);
+ if(val){
+ strncpy(pathpart, val, sizeof(pathpart)-1);
+ pathpart[sizeof(pathpart)-1] = '\0';
+ if(val != path)
+ fs_give((void **)&val);
+ }
+
+ if(pbf.headents[AC_PATH].bldr_private != (void *) 0){
+ strncat(pathpart, (char *) pbf.headents[AC_PATH].bldr_private,
+ sizeof(pathpart)-strlen(pathpart)-1);
+ pathpart[sizeof(pathpart)-1] = '\0';
+ }
+ }
+
+ sstrncpy(&p, pathpart, sizeof(allbutnick)-1-(p-allbutnick));
+ allbutnick[sizeof(allbutnick)-1] = '\0';
+
+ if(view[0] != '[' && sizeof(allbutnick)-1-(p-allbutnick) > 0){
+ *p++ = '[';
+ *p = '\0';
+ }
+
+ sstrncpy(&p, view, sizeof(allbutnick)-1-(p-allbutnick));
+ allbutnick[sizeof(allbutnick)-1] = '\0';
+ if((j=strlen(view)) < 2 || (view[j-1] != ']' &&
+ sizeof(allbutnick)-1-(p-allbutnick) > 0)){
+ *p++ = ']';
+ *p = '\0';
+ }
+
+ val = quote_if_needed(allbutnick);
+ if(val){
+ strncat(new_cntxt, val, sizeof(new_cntxt)-1-strlen(new_cntxt));
+ new_cntxt[sizeof(new_cntxt)-1] = '\0';
+
+ if(val != allbutnick)
+ fs_give((void **)&val);
+ }
+
+ return_cntxt = cpystr(new_cntxt);
+ }
+
+ for(i = 0; headents_templ[i].prompt; i++)
+ fs_give((void **) pbf.headents[i].realaddr);
+
+ if(pbf.headents[AC_PATH].bldr_private != (void *) 0)
+ fs_give(&pbf.headents[AC_PATH].bldr_private);
+
+ fs_give((void **) &pbf.headents);
+
+ standard_picobuf_teardown(&pbf);
+
+ if(msgso)
+ so_give(&msgso);
+
+ return(return_cntxt);
+}
+
+
+/*
+ * Doubles up '[' and ']' characters and returns either
+ * an allocated string or a pointer to the source string
+ * if no quoting is needed.
+ */
+char *
+quote_brackets_if_needed(char *src)
+{
+ char *step1 = NULL, *step2 = NULL, *ret;
+
+ ret = src;
+
+ if((strpbrk(src, "[]") != NULL)
+ && ((step1 = add_escapes(src, "[", '[', "", "")) != NULL)
+ && ((step2 = add_escapes(step1, "]", ']', "", ""))))
+ ret = step2;
+
+ if(step1)
+ fs_give((void **) &step1);
+
+ return(ret);
+}
+
+
+/*
+ * Call back for pico to prompt the user for exit confirmation
+ *
+ * Returns: either NULL if the user accepts exit, or string containing
+ * reason why the user declined.
+ */
+int
+exit_collection_add(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
+ char **result)
+{
+ char prompt[256], tmp[MAILTMPLEN], tmpnodel[MAILTMPLEN], *server, *path,
+ delim = '\0', *rstr = NULL, *p;
+ int exists = 0, i;
+ void (*redraw)(void) = ps_global->redrawer;
+ NETMBX mb;
+
+ ps_global->redrawer = redraw_pico;
+ fix_windsize(ps_global);
+
+ server = *he[AC_SERV].realaddr;
+ removing_trailing_white_space(server);
+ removing_leading_white_space(server);
+
+ path = *he[AC_PATH].realaddr;
+
+ if(*server){
+ /* No brackets? */
+ if(server[0] == '{' && server[strlen(server)-1] == '}'){
+ strncpy(tmp, server, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+ else /* add them */
+ snprintf(tmp, sizeof(tmp), "{%.*s}", sizeof(tmp)-3, server);
+
+ if(mail_valid_net_parse(tmp, &mb)){ /* news? verify namespace */
+ if(!struncmp(mb.service, "nntp", 4) && strncmp(path, "#news.", 6))
+ strncat(tmp, "#news.", sizeof(tmp)-1-strlen(tmp));
+ }
+ else
+ rstr = "Invalid Server entry";
+ }
+ else
+ tmp[0] = '\0';
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ if(!rstr){
+ /*
+ * Delimiter acquisition below serves dual purposes. One's to
+ * get the delimiter so we can make sure it's hanging off the end
+ * of the path so we can test for the directory existence. The
+ * second's to make sure the server (and any requested service)
+ * name we were given exists. It should be handled by the folder
+ * existence test futher below, but it doesn't work with news...
+ *
+ * Update. Now we are stripping the delimiter in the tmpnodel version
+ * so that we can pass that to folder_exists. Cyrus does not answer
+ * that the folder exists if we leave the trailing delimiter.
+ * Hubert 2004-12-17
+ */
+ strncat(tmp, path, sizeof(tmp)-1-strlen(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ strncpy(tmpnodel, tmp, sizeof(tmpnodel)-1);
+ tmpnodel[sizeof(tmpnodel)-1] = '\0';
+
+ if(he[AC_PATH].bldr_private != (void *) 0)
+ fs_give(&he[AC_PATH].bldr_private);
+
+ ps_global->mm_log_error = 0;
+ if((delim = folder_delimiter(tmp)) != '\0'){
+ if(*path){
+ if(tmp[(i = strlen(tmp)) - 1] == delim)
+ tmpnodel[i-1] = '\0';
+ else{
+ tmp[i] = delim;
+ tmp[i+1] = '\0';
+ he[AC_PATH].bldr_private = (void *) cpystr(&tmp[i]);
+ }
+ }
+ }
+ else if(ps_global->mm_log_error && ps_global->last_error)
+ /* We used to bail, but this was changed with 4.10
+ * as some users wanted to add clctn before the server
+ * was actually around and built.
+ */
+ flush_status_messages(0); /* mail_create gripes */
+ else
+ dprint((1, "exit_col_test: No Server Hierarchy!\n"));
+ }
+
+ if(!rstr){
+ if(!*tmp
+ || !delim
+ || ((*(p = tmp) == '#'
+ || (*tmp == '{' && (p = strchr(tmp, '}')) && *++p))
+ && !struncmp(p, "#news.", 6))
+ || (*tmp == '{' && (p = strchr(tmp, '}')) && !*++p)){
+ exists = 1;
+ }
+ else if((i = folder_exists(NULL, tmpnodel)) & FEX_ERROR){
+ if(!(rstr = ps_global->last_error))
+ rstr = _("Problem testing for directory existence");
+ }
+ else
+ exists = (i & FEX_ISDIR);
+
+ if(exists)
+ snprintf(prompt, sizeof(prompt), _("Exit and save changes"));
+ else
+ snprintf(prompt, sizeof(prompt), _("Exit, saving changes and creating Path"));
+
+ if(want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
+ if(!exists && !pine_mail_create(NULL, tmp)){
+ flush_status_messages(1); /* mail_create gripes */
+ if(!(rstr = ps_global->last_error))
+ rstr = "";
+ }
+ }
+ else
+ rstr = _("Use ^C to abandon changes you've made");
+ }
+
+ if(result)
+ *result = rstr;
+
+ ps_global->redrawer = redraw;
+ return((rstr == NULL) ? 0 : 1);
+}
+
+
+char *
+cancel_collection_add(void (*redraw_pico)(void))
+{
+ return(cancel_collection_editing(_("Add"), redraw_pico));
+}
+
+
+char *
+cancel_collection_edit(void (*redraw_pico)(void))
+{
+ return(cancel_collection_editing(_("Edit"), redraw_pico));
+}
+
+
+char *
+cancel_collection_editing(char *func, void (*redraw_pico)(void))
+{
+ char *rstr = NULL;
+ void (*redraw)(void) = ps_global->redrawer;
+ static char rbuf[20];
+ char prompt[256];
+#define CCA_PROMPT \
+ _("Cancel Add (answering \"Yes\" will abandon any changes made) ")
+
+ snprintf(prompt, sizeof(prompt), _("Cancel %s (answering \"Yes\" will abandon any changes made) "), func ? func : "Add");
+ snprintf(rbuf, sizeof(rbuf), _("%s Cancelled) "), func ? func : "Add");
+
+ ps_global->redrawer = redraw_pico;
+ fix_windsize(ps_global);
+
+ switch(want_to(prompt, 'y', 'x', NO_HELP, WT_NORM)){
+ case 'y':
+ rstr = rbuf;
+ break;
+
+ case 'n':
+ case 'x':
+ break;
+ }
+
+ ps_global->redrawer = redraw;
+ return(rstr);
+}
+
+
+int
+build_namespace(char *server, char **server_too, char **error, BUILDER_ARG *barg, int *mangled)
+{
+ char *p, *name;
+ int we_cancel = 0;
+ MAILSTREAM *stream;
+ NAMESPACE ***namespace;
+ size_t len;
+
+ dprint((5, "- build_namespace - (%s)\n",
+ server ? server : "nul"));
+
+ if(*barg->me){ /* only call this once! */
+ if(server_too)
+ *server_too = cpystr(server ? server : "");
+
+ return(0);
+ }
+ else
+ *barg->me = (void *) 1;
+
+ if((p = server) != NULL) /* empty string? */
+ while(*p && isspace((unsigned char) *p))
+ p++;
+
+ if(p && *p){
+ if(server_too)
+ *server_too = cpystr(p);
+
+ len = strlen(p) + 2;
+ name = (char *) fs_get((len + 1) * sizeof(char));
+ snprintf(name, len+1, "{%s}", p);
+ }
+ else{
+ if(server_too)
+ *server_too = cpystr("");
+
+ return(0);
+ }
+
+ *mangled |= BUILDER_SCREEN_MANGLED;
+ fix_windsize(ps_global);
+ init_sigwinch();
+ clear_cursor_pos();
+
+ we_cancel = busy_cue(_("Fetching default directory"), NULL, 1);
+
+ if((stream = pine_mail_open(NULL, name,
+ OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
+ NULL)) != NULL){
+ if((namespace = mail_parameters(stream, GET_NAMESPACE, NULL))
+ && *namespace && (*namespace)[0]
+ && (*namespace)[0]->name && (*namespace)[0]->name[0]){
+ if(barg->tptr)
+ fs_give((void **)&barg->tptr);
+
+ barg->tptr = cpystr((*namespace)[0]->name);
+ }
+
+ pine_mail_close(stream);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ fs_give((void **) &name);
+
+ return(1);
+}
+
+
+int
+fl_val_gen (FOLDER_S *f, FSTATE_S *fs)
+{
+ return(f && FLDR_NAME(f));
+}
+
+
+int
+fl_val_writable (FOLDER_S *f, FSTATE_S *fs)
+{
+ return(1);
+}
+
+
+int
+fl_val_subscribe (FOLDER_S *f, FSTATE_S *fs)
+{
+ if(f->subscribed){
+ q_status_message1(SM_ORDER, 0, 4, _("Already subscribed to \"%s\""),
+ FLDR_NAME(f));
+ return(0);
+ }
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Business end of displaying and operating on a collection of folders
+
+ Args: ps -- The pine_state data structure
+ fs -- Folder screen state structure
+
+ Result: A string list containing folders selected,
+ NULL on Cancel, Error or other problem
+
+ ----*/
+STRLIST_S *
+folder_lister(struct pine *ps, FSTATE_S *fs)
+{
+ int fltrv, we_cancel = 0;
+ int first_time_through = 1;
+ HANDLE_S *handles = NULL;
+ STORE_S *screen_text = NULL;
+ FPROC_S folder_proc_data;
+ gf_io_t pc;
+
+ dprint((1, "\n\n ---- FOLDER LISTER ----\n"));
+
+ memset(&folder_proc_data, 0, sizeof(FPROC_S));
+ folder_proc_data.fs = fs;
+
+ while(!folder_proc_data.done){
+ if((screen_text = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ gf_set_so_writec(&pc, screen_text);
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Formatting Error: Can't create space for list");
+ return(NULL);
+ }
+
+ we_cancel = busy_cue(_("Fetching folder data"), NULL, 1);
+ fltrv = folder_list_text(ps, &folder_proc_data, pc, &handles,
+ ps->ttyo->screen_cols);
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ if(fltrv){
+
+ SCROLL_S sargs;
+ struct key_menu km;
+ struct key keys[36];
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(screen_text);
+ sargs.text.src = CharStar;
+ sargs.text.desc = "folder list";
+ if((sargs.text.handles = folder_list_handle(fs, handles)) != NULL)
+ sargs.start.on = Handle;
+
+ sargs.bar.title = fs->f.title.bar;
+ sargs.bar.style = fs->f.title.style;
+
+ sargs.proc.tool = folder_processor;
+ sargs.proc.data.p = (void *) &folder_proc_data;
+
+ sargs.quell_first_view = first_time_through ? 0 : 1;
+ first_time_through = 0;
+
+ sargs.resize_exit = 1;
+ sargs.vert_handle = 1;
+ sargs.srch_handle = 1;
+
+ sargs.help.text = fs->f.help.text;
+ sargs.help.title = fs->f.help.title;
+
+ sargs.keys.menu = &km;
+ km = *fs->km;
+ km.keys = keys;
+ memcpy(&keys[0], fs->km->keys,
+ (km.how_many * 12) * sizeof(struct key));
+ setbitmap(sargs.keys.bitmap);
+
+ if(fs->km == &folder_km){
+ sargs.keys.each_cmd = folder_lister_km_manager;
+#ifdef _WINDOWS
+ sargs.mouse.popup = folder_list_popup;
+#endif
+ }
+ else{
+#ifdef _WINDOWS
+ sargs.mouse.popup = folder_list_select_popup;
+#endif
+ if(fs->km == &folder_sel_km || fs->km == &folder_sela_km)
+ sargs.keys.each_cmd = folder_lister_km_sel_manager;
+ else if(fs->km == &folder_sub_km)
+ sargs.keys.each_cmd = folder_lister_km_sub_manager;
+ }
+
+ sargs.mouse.clickclick = folder_lister_clickclick;
+
+ switch(scrolltool(&sargs)){
+ case MC_MAIN : /* just leave */
+ folder_proc_data.done = 1;
+ break;
+
+ case MC_RESIZE : /* loop around rebuilding screen */
+ if(sargs.text.handles){
+ FOLDER_S *fp;
+
+ fp = folder_entry(sargs.text.handles->h.f.index,
+ FOLDERS(sargs.text.handles->h.f.context));
+ if(fp && strlen(FLDR_NAME(fp)) < MAXFOLDER -1){
+ strncpy(fs->first_folder, FLDR_NAME(fp), MAXFOLDER);
+ fs->first_folder[MAXFOLDER-1] = '\0';
+ }
+
+ fs->context = sargs.text.handles->h.f.context;
+ }
+
+ break;
+
+ /*--------- EXIT menu -----------*/
+ case MC_EXIT :
+ case MC_EXITQUERY :
+ fs->list_cntxt = NULL;
+ folder_proc_data.done = folder_proc_data.all_done = 1;
+ break;
+
+ default :
+ break;
+ }
+
+ ps->noticed_change_in_unseen = 0;
+
+
+ if(F_ON(F_BLANK_KEYMENU,ps))
+ FOOTER_ROWS(ps) = 1;
+
+ gf_clear_so_writec(screen_text);
+ so_give(&screen_text);
+ free_handles(&handles);
+ }
+ else
+ folder_proc_data.done = 1;
+ }
+
+ reset_context_folders(fs->context);
+
+ if(folder_proc_data.all_done)
+ fs->context = NULL;
+
+ if(fs->cache_streamp && *fs->cache_streamp){
+ int i;
+
+ /*
+ * check stream pool to see if currently cached
+ * stream went away
+ */
+ for(i = 0; i < ps->s_pool.nstream; i++)
+ if(ps->s_pool.streams[i] == *fs->cache_streamp)
+ break;
+ if(i == ps->s_pool.nstream)
+ *fs->cache_streamp = NULL;
+ }
+
+ return(folder_proc_data.rv);
+}
+
+
+/*
+ * folder_list_text - format collection's contents for display
+ */
+int
+folder_list_text(struct pine *ps, FPROC_S *fp, gf_io_t pc, HANDLE_S **handlesp, int cols)
+{
+ int rv = 1, i, j, ftotal, fcount, slot_width, slot_rows,
+ slot_cols, index, findex, width, shown, selected;
+ CONTEXT_S *c_list;
+ char lbuf[6*MAX_SCREEN_COLS+1];
+
+ /* disarm this trigger that gets us out of scrolltool */
+ ps->noticed_change_in_unseen = 0;
+
+ if(handlesp)
+ init_handles(handlesp);
+
+ c_list = fp->fs->context;
+ if(fp->fs->combined_view
+ && (F_ON(F_CMBND_SUBDIR_DISP, ps_global) || !c_list->dir->prev))
+ while(c_list->prev) /* rewind to start */
+ c_list = c_list->prev;
+
+ do{
+ ps->user_says_cancel = 0;
+
+ /* If we're displaying folders, fetch the list */
+ if((shown = (c_list == fp->fs->context
+ || (c_list->dir->status & CNTXT_NOFIND) == 0
+ || F_ON(F_EXPANDED_FOLDERS, ps_global))) != 0){
+ /*
+ * if select is allowed, flag context so any that are
+ * are remembered even after the list is destroyed
+ */
+ if(fp->fs->agg_ops)
+ c_list->use |= CNTXT_PRESRV;
+
+ /* Make sure folder list filled in */
+ refresh_folder_list(c_list, fp->fs->no_dirs, FALSE, fp->fs->cache_streamp);
+ }
+
+ /* Insert any introductory text here */
+ if(c_list->next
+ || c_list->prev
+ || c_list->dir->prev
+ || fp->fs->force_intro){
+
+ /* Leading horizontal line? */
+ if(fp->fs->combined_view
+ && (F_ON(F_CMBND_SUBDIR_DISP,ps_global)
+ || !c_list->dir->prev)){
+ if(c_list->prev)
+ gf_puts("\n", pc); /* blank line */
+
+ gf_puts(repeat_char(cols, '-'), pc);
+ gf_puts("\n", pc);
+ }
+
+ /* nickname or description */
+ if(F_ON(F_CMBND_FOLDER_DISP, ps_global)
+ && (!c_list->dir->prev
+ || F_ON(F_CMBND_SUBDIR_DISP, ps_global))){
+ char buf[6*MAX_SCREEN_COLS + 1];
+
+ if(cols < 40){
+ snprintf(buf, sizeof(buf), "%.*s", cols,
+ strsquish(tmp_20k_buf, SIZEOF_20KBUF,
+ (c_list->nickname)
+ ? c_list->nickname
+ : (c_list->label ? c_list->label : ""),
+ cols));
+ gf_puts(folder_list_center_space(buf, cols), pc);
+ }
+ else{
+ int wid;
+
+ snprintf(buf, sizeof(buf), "%s-Collection <",
+ NEWS_TEST(c_list) ? "News" : "Folder");
+ wid = utf8_width(buf)+1;
+ wid = MAX(0, cols-wid);
+ snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%.*s>", wid,
+ strsquish(tmp_20k_buf, SIZEOF_20KBUF,
+ (c_list->nickname)
+ ? c_list->nickname
+ : (c_list->label ? c_list->label : ""),
+ wid));
+ }
+
+ gf_puts(buf, pc);
+ gf_puts("\n", pc);
+ }
+ else if(c_list->label){
+ if(utf8_width(c_list->label) > ps_global->ttyo->screen_cols)
+ utf8_pad_to_width(lbuf, c_list->label, sizeof(lbuf), ps_global->ttyo->screen_cols, 1);
+ else
+ strncpy(lbuf, c_list->label, sizeof(lbuf));
+
+ lbuf[sizeof(lbuf)-1] = '\0';
+
+ gf_puts(folder_list_center_space(lbuf, cols), pc);
+ gf_puts(lbuf, pc);
+ gf_puts("\n", pc);
+ }
+
+ if(c_list->comment){
+ if(utf8_width(c_list->comment) > ps_global->ttyo->screen_cols)
+ utf8_pad_to_width(lbuf, c_list->comment, sizeof(lbuf), ps_global->ttyo->screen_cols, 1);
+ else
+ strncpy(lbuf, c_list->comment, sizeof(lbuf));
+
+ lbuf[sizeof(lbuf)-1] = '\0';
+
+ gf_puts(folder_list_center_space(lbuf, cols), pc);
+ gf_puts(lbuf, pc);
+ gf_puts("\n", pc);
+ }
+
+ if(c_list->dir->desc){
+ char buf[6*MAX_SCREEN_COLS + 1];
+
+ strncpy(buf, strsquish(tmp_20k_buf,SIZEOF_20KBUF,c_list->dir->desc,cols),
+ sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ gf_puts(folder_list_center_space(buf, cols), pc);
+ gf_puts(buf, pc);
+ gf_puts("\n", pc);
+ }
+
+ if(c_list->use & CNTXT_ZOOM){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "[ ZOOMED on %d (of %d) %ss ]",
+ selected_folders(c_list),
+ folder_total(FOLDERS(c_list)),
+ (c_list->use & CNTXT_NEWS) ? "Newsgroup" : "Folder");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ if(utf8_width(tmp_20k_buf) > ps_global->ttyo->screen_cols)
+ utf8_pad_to_width(lbuf, tmp_20k_buf, sizeof(lbuf), ps_global->ttyo->screen_cols, 1);
+ else
+ strncpy(lbuf, tmp_20k_buf, sizeof(lbuf));
+
+ lbuf[sizeof(lbuf)-1] = '\0';
+
+ gf_puts(folder_list_center_space(lbuf, cols), pc);
+ gf_puts(lbuf, pc);
+ gf_puts("\n", pc);
+ }
+
+ gf_puts(repeat_char(cols, '-'), pc);
+ gf_puts("\n\n", pc);
+ }
+
+ if(shown){
+ /* Run thru list formatting as necessary */
+ if((ftotal = folder_total(FOLDERS(c_list))) != 0){
+ /* If previously selected, mark members of new list */
+ selected = selected_folders(c_list);
+
+ /* How many screen cells per cell for each folder name? */
+ slot_width = 1;
+ for(fcount = i = 0; i < ftotal; i++){
+ FOLDER_S *f = folder_entry(i, FOLDERS(c_list));
+ unsigned char *fname;
+
+ ps->user_says_cancel = 0;
+
+ if((c_list->use & CNTXT_ZOOM) && !f->selected)
+ continue;
+
+ fcount++;
+
+ fname = folder_name_decoded((unsigned char *)FLDR_NAME(f));
+
+ width = utf8_width(fname ? (char *)fname : FLDR_NAME(f));
+
+ if(fname)
+ fs_give((void **)&fname);
+
+ if(f->isdir)
+ width += (f->isfolder) ? 3 : 1;
+
+ if(NEWS_TEST(c_list) && (c_list->use & CNTXT_FINDALL))
+ /* assume listmode so we don't have to reformat */
+ /* if listmode is actually entered */
+ width += 4;
+
+ if(selected){
+ if(F_OFF(F_SELECTED_SHOWN_BOLD, ps_global))
+ width += 4; /* " X " */
+ }
+ else if(c_list == fp->fs->list_cntxt)
+ width += 4; /* "[X] " */
+
+ if(fp->fs->include_unseen_cnt
+ && c_list->use & CNTXT_INCMNG
+ && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
+ && ps_global->VAR_INCOMING_FOLDERS
+ && ps_global->VAR_INCOMING_FOLDERS[0]){
+
+ update_folder_unseen(f, c_list, UFU_ANNOUNCE, NULL);
+ if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
+ && f->unseen_valid
+ && (f->new > 0L
+ || F_ON(F_INCOMING_CHECKING_TOTAL, ps_global))){
+ width += (strlen(tose(f->new)) + 3);
+ if(F_ON(F_INCOMING_CHECKING_TOTAL, ps_global))
+ width += (strlen(tose(f->total)) + 1);
+ }
+ else if(F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
+ && f->unseen_valid
+ && (f->unseen > 0L
+ || F_ON(F_INCOMING_CHECKING_TOTAL, ps_global))){
+ width += (strlen(tose(f->unseen)) + 3);
+ if(F_ON(F_INCOMING_CHECKING_TOTAL, ps_global))
+ width += (strlen(tose(f->total)) + 1);
+ }
+ else if(!f->unseen_valid && f->last_unseen_update != LUU_NEVERCHK)
+ width += 4; /* " (?)" */
+ }
+
+ if(slot_width < width)
+ slot_width = width;
+ }
+
+ if(F_ON(F_SINGLE_FOLDER_LIST, ps_global)){
+ slot_cols = 1;
+ slot_rows = fcount;
+ }
+ else{
+ /* fit as many columns as possible */
+ slot_cols = 1;
+ while(((slot_cols+1) * slot_width) + slot_cols <= cols)
+ slot_cols++;
+
+ switch(slot_cols){
+ case 0 :
+ slot_cols = 1;
+ /* fall through */
+
+ case 1 :
+ slot_rows = fcount;
+ break;
+
+ default :
+ /*
+ * Make the slot_width as large as possible.
+ * Slot_width is the width of the column, not counting
+ * the space between columns.
+ */
+ while((slot_cols * (slot_width+1)) + slot_cols-1 <= cols)
+ slot_width++;
+
+ slot_rows = (fcount / slot_cols)
+ + ((fcount % slot_cols) ? 1 : 0);
+ break;
+ }
+ }
+
+ for(i = index = 0; i < slot_rows; i++){
+ if(i)
+ gf_puts("\n", pc);
+
+ for(j = width = 0; j < slot_cols; j++, index++){
+ if(width){
+ gf_puts(repeat_char(slot_width + 1 - width, ' '), pc);
+ width = 0;
+ }
+
+ if(F_ON(F_VERTICAL_FOLDER_LIST, ps_global))
+ index = i + (j * slot_rows);
+
+ findex = index;
+
+ if(c_list->use & CNTXT_ZOOM)
+ findex = folder_list_ith(index, c_list);
+
+ if(findex < ftotal){
+ int flags = (handlesp) ? FLW_LUNK : FLW_NONE;
+
+ if(c_list == fp->fs->list_cntxt)
+ flags |= FLW_LIST;
+ else if(selected)
+ flags |= FLW_SLCT;
+ else if(F_ON(F_SINGLE_FOLDER_LIST, ps_global)
+ || ((c_list->use & CNTXT_FINDALL)
+ && NEWS_TEST(c_list)))
+ gf_puts(" ", pc);
+
+ if(fp->fs->include_unseen_cnt
+ && c_list->use & CNTXT_INCMNG
+ && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
+ && ps_global->VAR_INCOMING_FOLDERS
+ && ps_global->VAR_INCOMING_FOLDERS[0])
+ flags |= FLW_UNSEEN;
+
+ width = folder_list_write(pc, handlesp, c_list,
+ findex, NULL, flags);
+ }
+ }
+ }
+ }
+ else if(fp->fs->combined_view
+ && (F_ON(F_CMBND_SUBDIR_DISP, ps_global)
+ || !c_list->dir->prev)){
+ char *emptiness = N_("[No Folders in Collection]");
+
+ if(utf8_width(_(emptiness)) > ps_global->ttyo->screen_cols)
+ utf8_pad_to_width(lbuf, _(emptiness), sizeof(lbuf), ps_global->ttyo->screen_cols, 1);
+ else
+ strncpy(lbuf, _(emptiness), sizeof(lbuf));
+
+ lbuf[sizeof(lbuf)-1] = '\0';
+
+ gf_puts(folder_list_center_space(lbuf, cols), pc);
+ (void) folder_list_write(pc, handlesp, c_list, -1, lbuf,
+ (handlesp) ? FLW_LUNK : FLW_NONE);
+ }
+ }
+ else if(fp->fs->combined_view
+ && (F_ON(F_CMBND_SUBDIR_DISP, ps_global)
+ || !c_list->dir->prev)){
+ char *unexpanded = N_("[Select Here to See Expanded List]");
+
+ if(utf8_width(_(unexpanded)) > ps_global->ttyo->screen_cols)
+ utf8_pad_to_width(lbuf, _(unexpanded), sizeof(lbuf), ps_global->ttyo->screen_cols, 1);
+ else
+ strncpy(lbuf, _(unexpanded), sizeof(lbuf));
+
+ lbuf[sizeof(lbuf)-1] = '\0';
+
+ gf_puts(folder_list_center_space(lbuf, cols), pc);
+ (void) folder_list_write(pc, handlesp, c_list, -1, lbuf,
+ (handlesp) ? FLW_LUNK : FLW_NONE);
+ }
+
+ gf_puts("\n", pc); /* blank line */
+
+ }
+ while(fp->fs->combined_view
+ && (F_ON(F_CMBND_SUBDIR_DISP, ps_global) || !c_list->dir->prev)
+ && (c_list = c_list->next));
+
+ return(rv);
+}
+
+
+int
+folder_list_write(gf_io_t pc, HANDLE_S **handlesp, CONTEXT_S *ctxt, int fnum, char *alt_name, int flags)
+{
+ char buf[256];
+ int width = 0, lprefix = 0, lmiddle = 0, lsuffix = 0;
+ FOLDER_S *fp;
+ HANDLE_S *h1 = NULL, *h2 = NULL;
+
+ if(flags & FLW_LUNK){
+ h1 = new_handle(handlesp);
+ h1->type = Folder;
+ h1->h.f.index = fnum;
+ h1->h.f.context = ctxt;
+ h1->force_display = 1;
+
+ snprintf(buf, sizeof(buf), "%d", h1->key);
+ buf[sizeof(buf)-1] = '\0';
+ }
+
+ fp = (fnum < 0) ? NULL : folder_entry(fnum, FOLDERS(ctxt));
+
+ if(flags & FLW_LUNK && h1 && fp && fp->isdir && fp->isfolder){
+ h2 = new_handle(handlesp);
+ h2->type = Folder;
+ h2->h.f.index = fnum;
+ h2->h.f.context = ctxt;
+ h2->force_display = 1;
+
+ h1->is_dual_do_open = 1;
+ }
+
+ if(h1){
+ /* color unseen? */
+ if(color_monitored_unseen(fp, flags)){
+ h1->color_unseen = 1;
+ if(h2)
+ h2->color_unseen = 1;
+ }
+ }
+
+ /* embed handle pointer */
+ if(((h1 && h1->color_unseen) ?
+ gf_puts(color_embed(ps_global->VAR_INCUNSEEN_FORE_COLOR,
+ ps_global->VAR_INCUNSEEN_BACK_COLOR), pc) : 1)
+ && (h1 ? ((*pc)(TAG_EMBED) && (*pc)(TAG_HANDLE)
+ && (*pc)(strlen(buf)) && gf_puts(buf, pc)) : 1)
+ && (fp ? ((lprefix = folder_list_write_prefix(fp, flags, pc)) >= 0
+ && (lmiddle = folder_list_write_middle(fp, ctxt, pc, h2)) >= 0
+ && ((lsuffix = folder_list_write_suffix(fp, flags, pc)) >= 0))
+ : (alt_name ? gf_puts(alt_name, pc) : 0))
+ && (h1 ? ((*pc)(TAG_EMBED) && (*pc)(TAG_BOLDOFF)
+ && (*pc)(TAG_EMBED) && (*pc)(TAG_INVOFF)) : 1)
+ && ((h1 && h1->color_unseen) ?
+ gf_puts(color_embed(ps_global->VAR_NORM_FORE_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR), pc) : 1)){
+ if(fp)
+ width = lprefix + lmiddle + lsuffix;
+ else if(alt_name)
+ width = utf8_width(alt_name);
+ }
+
+ return(width);
+}
+
+
+int
+folder_list_write_prefix(FOLDER_S *f, int flags, gf_io_t pc)
+{
+ int rv = 0;
+
+ if(flags & FLW_SLCT){
+ if(F_OFF(F_SELECTED_SHOWN_BOLD, ps_global) || !(flags & FLW_LUNK)){
+ rv = 4;
+ if(f->selected){
+ gf_puts(" X ", pc);
+ }
+ else{
+ gf_puts(" ", pc);
+ }
+ }
+ else
+ rv = ((*pc)(TAG_EMBED)
+ && (*pc)((f->selected) ? TAG_BOLDON : TAG_BOLDOFF)) ? 0 : -1;
+ }
+ else if(flags & FLW_LIST){
+ rv = 4;
+ /* screen width of "SUB " is 4 */
+ gf_puts(f->subscribed ? "SUB " : (f->selected ? "[X] " : "[ ] "), pc);
+ }
+
+ return(rv);
+}
+
+
+int
+folder_list_write_middle(FOLDER_S *fp, CONTEXT_S *ctxt, gf_io_t pc, HANDLE_S *h2)
+{
+ int rv = -1;
+ char buf[256];
+ unsigned char *fname;
+
+ if(h2){
+ snprintf(buf, sizeof(buf), "%d", h2->key);
+ buf[sizeof(buf)-1] = '\0';
+ }
+
+ if(!fp)
+ return(rv);
+
+ fname = folder_name_decoded((unsigned char *)FLDR_NAME(fp));
+
+ if(gf_puts(fname ? (char *)fname : FLDR_NAME(fp), pc)
+ && (h2 ? ((*pc)(TAG_EMBED) && (*pc)(TAG_BOLDOFF) /* tie off handle 1 */
+ && (*pc)(TAG_EMBED) && (*pc)(TAG_INVOFF)) : 1)
+ && (h2 ? ((*pc)(TAG_EMBED) && (*pc)(TAG_HANDLE) /* start handle 2 */
+ && (*pc)(strlen(buf)) && gf_puts(buf, pc)) : 1)
+ && ((fp->isdir && fp->isfolder) ? (*pc)('[') : 1)
+ && ((fp->isdir) ? (*pc)(ctxt->dir->delim) : 1)
+ && ((fp->isdir && fp->isfolder) ? (*pc)(']') : 1)){
+ rv = utf8_width(fname ? (char *)fname : FLDR_NAME(fp));
+ if(fp->isdir)
+ rv += (fp->isfolder) ? 3 : 1;
+ }
+
+ if(fname)
+ fs_give((void **)&fname);
+
+ return(rv);
+}
+
+
+int
+folder_list_write_suffix(FOLDER_S *f, int flags, gf_io_t pc)
+{
+ int rv = 0;
+
+ if(flags & FLW_UNSEEN){
+ char buf[100];
+
+ buf[0] = '\0';
+ if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global)
+ && f->unseen_valid
+ && (f->new > 0L
+ || (F_ON(F_INCOMING_CHECKING_TOTAL, ps_global)
+ && f->total > 0L))){
+ snprintf(buf, sizeof(buf), " (%s%s%s)",
+ tose(f->new),
+ F_ON(F_INCOMING_CHECKING_TOTAL, ps_global) ? "/" : "",
+ F_ON(F_INCOMING_CHECKING_TOTAL, ps_global) ? tose(f->total) : "");
+ }
+ else if(F_OFF(F_INCOMING_CHECKING_RECENT, ps_global)
+ && f->unseen_valid
+ && (f->unseen > 0L
+ || (F_ON(F_INCOMING_CHECKING_TOTAL, ps_global)
+ && f->total > 0L))){
+ snprintf(buf, sizeof(buf), " (%s%s%s)",
+ tose(f->unseen),
+ F_ON(F_INCOMING_CHECKING_TOTAL, ps_global) ? "/" : "",
+ F_ON(F_INCOMING_CHECKING_TOTAL, ps_global) ? tose(f->total) : "");
+ }
+ else if(!f->unseen_valid && f->last_unseen_update != LUU_NEVERCHK){
+ snprintf(buf, sizeof(buf), " (?)");
+ }
+
+ rv = strlen(buf);
+ if(rv)
+ gf_puts(buf, pc);
+ }
+
+ return(rv);
+}
+
+
+int
+color_monitored_unseen(FOLDER_S *f, int flags)
+{
+ return((flags & FLW_UNSEEN) && f && f->unseen_valid
+ && ((F_ON(F_INCOMING_CHECKING_RECENT, ps_global) && f->new > 0L)
+ || (F_OFF(F_INCOMING_CHECKING_RECENT, ps_global) && f->unseen > 0L))
+ && pico_usingcolor()
+ && pico_is_good_color(ps_global->VAR_INCUNSEEN_FORE_COLOR)
+ && pico_is_good_color(ps_global->VAR_INCUNSEEN_BACK_COLOR)
+ && (colorcmp(ps_global->VAR_INCUNSEEN_FORE_COLOR,
+ ps_global->VAR_NORM_FORE_COLOR)
+ || colorcmp(ps_global->VAR_INCUNSEEN_BACK_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR)));
+}
+
+
+int
+folder_list_ith(int n, CONTEXT_S *cntxt)
+{
+ int index, ftotal;
+ FOLDER_S *f;
+
+ for(index = 0, ftotal = folder_total(FOLDERS(cntxt));
+ index < ftotal
+ && (f = folder_entry(index, FOLDERS(cntxt)))
+ && !(f->selected && !n--);
+ index++)
+ ;
+
+ return(index);
+}
+
+
+char *
+folder_list_center_space(char *s, int width)
+{
+ int l;
+
+ return(((l = utf8_width(s)) < width) ? repeat_char((width - l)/2, ' ') : "");
+}
+
+
+/*
+ * folder_list_handle - return pointer in handle list
+ * corresponding to "start"
+ */
+HANDLE_S *
+folder_list_handle(FSTATE_S *fs, HANDLE_S *handles)
+{
+ char *p, *name = NULL;
+ HANDLE_S *h, *h_found = NULL;
+ FOLDER_S *fp;
+
+ if(handles && fs->context){
+ if(!(NEWS_TEST(fs->context) || (fs->context->use & CNTXT_INCMNG))
+ && fs->context->dir->delim)
+ for(p = strchr(fs->first_folder, fs->context->dir->delim);
+ p;
+ p = strchr(p, fs->context->dir->delim))
+ name = ++p;
+
+ for(h = handles; h; h = h->next)
+ if(h->h.f.context == fs->context){
+ if(!h_found) /* match at least given context */
+ h_found = h;
+
+ if(!fs->first_folder[0]
+ || ((fp = folder_entry(h->h.f.index, FOLDERS(h->h.f.context)))
+ && ((fs->first_dir && fp->isdir)
+ || (!fs->first_dir && fp->isfolder))
+ && ((fp->nickname && !strcmp(name ? name : fs->first_folder, fp->nickname))
+ || (fp->name && !strcmp(name ? name : fs->first_folder, fp->name))))){
+ h_found = h;
+ break;
+ }
+ }
+
+ fs->first_folder[0] = '\0';
+ }
+
+ return(h_found ? h_found : handles);
+}
+
+
+int
+folder_processor(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 0;
+ char tmp_output[MAILTMPLEN];
+
+ switch(cmd){
+ case MC_FINISH :
+ FPROC(sparms)->done = rv = 1;;
+ break;
+
+ /*---------- Select or enter a View ----------*/
+ case MC_CHOICE :
+ rv = folder_lister_choice(sparms);
+ break;
+
+ /*--------- Hidden "To Fldrs" command -----------*/
+ case MC_LISTMODE :
+ if(sparms->text.handles
+ && folder_total(FOLDERS(sparms->text.handles->h.f.context))){
+ if(!FPROC(sparms)->fs->list_cntxt){
+ FPROC(sparms)->fs->list_cntxt
+ = sparms->text.handles->h.f.context;
+ rv = scroll_add_listmode(sparms->text.handles->h.f.context,
+ folder_total(FOLDERS(sparms->text.handles->h.f.context)));
+ if(!rv){
+ /* need to set the subscribe key ourselves */
+ sparms->keys.menu->keys[SB_SUB_KEY].name = "S";
+ sparms->keys.menu->keys[SB_SUB_KEY].label = N_("Subscribe");
+ sparms->keys.menu->keys[SB_SUB_KEY].bind.cmd = MC_CHOICE;
+ sparms->keys.menu->keys[SB_SUB_KEY].bind.ch[0] = 's';
+ setbitn(SB_SUB_KEY, sparms->keys.bitmap);
+ ps_global->mangled_screen = 1;
+
+ sparms->keys.menu->keys[SB_EXIT_KEY].bind.cmd = MC_EXITQUERY;
+ }
+ q_status_message(SM_ORDER, 0, 1, LISTMODE_GRIPE);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4, _("Already in List Mode"));
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4,
+ _("No Folders! Can't enter List Mode"));
+
+ break;
+
+
+ /*--------- Visit parent directory -----------*/
+ case MC_PARENT :
+ if(folder_lister_parent(FPROC(sparms)->fs,
+ (sparms->text.handles)
+ ? sparms->text.handles->h.f.context
+ : FPROC(sparms)->fs->context,
+ (sparms->text.handles)
+ ? sparms->text.handles->h.f.index : -1, 0))
+ rv = 1; /* leave scrolltool to rebuild screen */
+
+ break;
+
+
+ /*--------- Open the selected folder -----------*/
+ case MC_OPENFLDR :
+ if(sparms->text.handles
+ && folder_total(FOLDERS(sparms->text.handles->h.f.context)))
+ rv = folder_lister_finish(sparms, sparms->text.handles->h.f.context,
+ sparms->text.handles->h.f.index);
+ else
+ q_status_message(SM_ORDER, 0, 4,
+ _("No Folders! Nothing to View"));
+
+ break;
+
+
+ /*--------- Export the selected folder -----------*/
+ case MC_EXPORT :
+ folder_export(sparms);
+ break;
+
+
+ /*--------- Import the selected folder -----------*/
+ case MC_IMPORT :
+ {
+ CONTEXT_S *cntxt = (sparms->text.handles)
+ ? sparms->text.handles->h.f.context
+ : FPROC(sparms)->fs->context;
+ char new_file[2*MAXFOLDER+10];
+ int r;
+
+ new_file[0] = '\0';
+
+ r = folder_import(sparms, new_file, sizeof(new_file));
+
+ if(r && (cntxt->use & CNTXT_INCMNG || context_isambig(new_file))){
+ rv = 1; /* rebuild display! */
+ FPROC(sparms)->fs->context = cntxt;
+ if(strlen(new_file) < MAXFOLDER - 1){
+ strncpy(FPROC(sparms)->fs->first_folder, new_file, MAXFOLDER);
+ FPROC(sparms)->fs->first_folder[MAXFOLDER-1] = '\0';
+ }
+ }
+ else
+ ps_global->mangled_footer++;
+ }
+
+ break;
+
+
+ /*--------- Return to the Collections Screen -----------*/
+ case MC_COLLECTIONS :
+ FPROC(sparms)->done = rv = 1;
+ break;
+
+
+ /*--------- QUIT pine -----------*/
+ case MC_QUIT :
+ ps_global->next_screen = quit_screen;
+ FPROC(sparms)->done = rv = 1;
+ break;
+
+
+ /*--------- Compose -----------*/
+ case MC_COMPOSE :
+ ps_global->next_screen = compose_screen;
+ FPROC(sparms)->done = rv = 1;
+ break;
+
+
+ /*--------- Alt Compose -----------*/
+ case MC_ROLE :
+ ps_global->next_screen = alt_compose_screen;
+ FPROC(sparms)->done = rv = 1;
+ break;
+
+
+ /*--------- Message Index -----------*/
+ case MC_INDEX :
+ if(THREADING()
+ && sp_viewing_a_thread(ps_global->mail_stream)
+ && unview_thread(ps_global, ps_global->mail_stream, ps_global->msgmap)){
+ ps_global->next_screen = mail_index_screen;
+ ps_global->view_skipped_index = 0;
+ ps_global->mangled_screen = 1;
+ }
+
+ ps_global->next_screen = mail_index_screen;
+ FPROC(sparms)->done = rv = 1;
+ break;
+
+
+ /*----------------- Add a new folder name -----------*/
+ case MC_ADDFLDR :
+ {
+ CONTEXT_S *cntxt = (sparms->text.handles)
+ ? sparms->text.handles->h.f.context
+ : FPROC(sparms)->fs->context;
+ char new_file[2*MAXFOLDER+10];
+ int r;
+
+ if(NEWS_TEST(cntxt))
+ r = group_subscription(new_file, sizeof(new_file), cntxt);
+ else{
+ r = add_new_folder(cntxt, Main, V_INCOMING_FOLDERS, new_file,
+ sizeof(new_file),
+ FPROC(sparms)->fs->cache_streamp
+ ? *FPROC(sparms)->fs->cache_streamp : NULL,
+ NULL);
+ if(ps_global->prc && ps_global->prc->outstanding_pinerc_changes)
+ write_pinerc(ps_global, Main, WRP_NONE);
+ }
+
+ if(r && (cntxt->use & CNTXT_INCMNG || context_isambig(new_file))){
+ rv = 1; /* rebuild display! */
+ FPROC(sparms)->fs->context = cntxt;
+ if(strlen(new_file) < MAXFOLDER - 1){
+ strncpy(FPROC(sparms)->fs->first_folder, new_file, MAXFOLDER);
+ FPROC(sparms)->fs->first_folder[MAXFOLDER-1] = '\0';
+ }
+ }
+ else
+ ps_global->mangled_footer++;
+ }
+
+ break;
+
+
+ /*------ Type in new folder name, e.g., for save ----*/
+ case MC_ADD :
+ rv = folder_lister_addmanually(sparms);
+ break;
+
+
+ /*--------------- Rename folder ----------------*/
+ case MC_RENAMEFLDR :
+ if(sparms->text.handles
+ && folder_total(FOLDERS(sparms->text.handles->h.f.context))){
+ char new_file[MAXFOLDER+1];
+ int r;
+
+ r = rename_folder(sparms->text.handles->h.f.context,
+ sparms->text.handles->h.f.index, new_file,
+ sizeof(new_file),
+ FPROC(sparms)->fs->cache_streamp
+ ? *FPROC(sparms)->fs->cache_streamp : NULL);
+
+ if(r){
+ /* repaint, placing cursor on new folder! */
+ rv = 1;
+ if(context_isambig(new_file)){
+ FPROC(sparms)->fs->context
+ = sparms->text.handles->h.f.context;
+ if(strlen(new_file) < MAXFOLDER - 1){
+ strncpy(FPROC(sparms)->fs->first_folder, new_file, MAXFOLDER);
+ FPROC(sparms)->fs->first_folder[MAXFOLDER-1] = '\0';
+ }
+ }
+ }
+
+ ps_global->mangled_footer++;
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ _("Empty folder collection. No folder to rename!"));
+
+ break;
+
+
+ /*-------------- Delete --------------------*/
+ case MC_DELETE :
+ if(!(sparms->text.handles
+ && folder_total(FOLDERS(sparms->text.handles->h.f.context)))){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ _("Empty folder collection. No folder to delete!"));
+ }
+ else{
+ char next_folder[MAILTMPLEN+1];
+
+ next_folder[0] = '\0';
+ if(delete_folder(sparms->text.handles->h.f.context,
+ sparms->text.handles->h.f.index,
+ next_folder, sizeof(next_folder),
+ FPROC(sparms)->fs->cache_streamp
+ && *FPROC(sparms)->fs->cache_streamp
+ ? FPROC(sparms)->fs->cache_streamp : NULL)){
+
+ /* repaint, placing cursor on adjacent folder! */
+ rv = 1;
+ if(next_folder[0] && strlen(next_folder) < MAXFOLDER - 1){
+ strncpy(FPROC(sparms)->fs->first_folder, next_folder, MAXFOLDER);
+ FPROC(sparms)->fs->first_folder[MAXFOLDER-1] = '\0';
+ }
+ else
+ sparms->text.handles->h.f.context->use &= ~CNTXT_ZOOM;
+ }
+ else
+ ps_global->mangled_footer++;
+ }
+
+ break;
+
+
+ /*----------------- Shuffle incoming folder list -----------*/
+ case MC_SHUFFLE :
+ {
+ CONTEXT_S *cntxt = (sparms->text.handles)
+ ? sparms->text.handles->h.f.context : NULL;
+
+ if(!(cntxt && cntxt->use & CNTXT_INCMNG))
+ q_status_message(SM_ORDER, 0, 4,
+ _("May only shuffle Incoming-Folders."));
+ else if(folder_total(FOLDERS(cntxt)) == 0)
+ q_status_message(SM_ORDER, 0, 4,
+ _("No folders to shuffle."));
+ else if(folder_total(FOLDERS(cntxt)) < 2)
+ q_status_message(SM_ORDER, 0, 4,
+ _("Shuffle only makes sense with more than one folder."));
+ else{
+ if(FPROC(sparms) && FPROC(sparms)->fs &&
+ FPROC(sparms)->fs && sparms->text.handles &&
+ sparms->text.handles->h.f.index >= 0 &&
+ sparms->text.handles->h.f.index <
+ folder_total(FOLDERS(cntxt))){
+ strncpy(FPROC(sparms)->fs->first_folder,
+ FLDR_NAME(folder_entry(sparms->text.handles->h.f.index,
+ FOLDERS(cntxt))), MAXFOLDER);
+ FPROC(sparms)->fs->first_folder[MAXFOLDER-1] = '\0';
+ }
+
+ rv = shuffle_incoming_folders(cntxt,
+ sparms->text.handles->h.f.index);
+ }
+ }
+
+ break;
+
+
+ /*-------------- Goto Folder Prompt --------------------*/
+ case MC_GOTO :
+ {
+ int notrealinbox;
+ CONTEXT_S *c = (sparms->text.handles)
+ ? sparms->text.handles->h.f.context
+ : FPROC(sparms)->fs->context;
+ char *new_fold = broach_folder(-FOOTER_ROWS(ps_global), 0, &notrealinbox, &c);
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ if(new_fold && do_broach_folder(new_fold, c, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT) > 0){
+ ps_global->next_screen = mail_index_screen;
+ FPROC(sparms)->done = rv = 1;
+ }
+ else
+ ps_global->mangled_footer = 1;
+
+ if((c = ((sparms->text.handles)
+ ? sparms->text.handles->h.f.context
+ : FPROC(sparms)->fs->context))->dir->status & CNTXT_NOFIND)
+ refresh_folder_list(c, FPROC(sparms)->fs->no_dirs, TRUE, FPROC(sparms)->fs->cache_streamp);
+ }
+
+ break;
+
+
+ /*------------- Print list of folders ---------*/
+ case MC_PRINTFLDR :
+ print_folders(FPROC(sparms));
+ ps_global->mangled_footer++;
+ break;
+
+
+ /*----- Select the current folder, or toggle checkbox -----*/
+ case MC_SELCUR :
+ /*---------- Select set of folders ----------*/
+ case MC_SELECT :
+ if(sparms->text.handles
+ && folder_total(FOLDERS(sparms->text.handles->h.f.context))){
+ if(cmd == MC_SELCUR){
+ rv = folder_select_toggle(sparms->text.handles->h.f.context,
+ sparms->text.handles->h.f.index,
+ (NEWS_TEST(sparms->text.handles->h.f.context)
+ && FPROC(sparms)->fs->list_cntxt)
+ ? ng_scroll_edit : folder_select_update);
+ if(!rv) ps_global->mangled_screen = 1;
+ }
+ else
+ switch(folder_select(ps_global,
+ sparms->text.handles->h.f.context,
+ sparms->text.handles->h.f.index)){
+ case 1 :
+ rv = 1; /* rebuild screen */
+
+ case 0 :
+ default :
+ ps_global->mangled_screen++;
+ break;
+ }
+
+ if((sparms->text.handles->h.f.context->use & CNTXT_ZOOM)
+ && !selected_folders(sparms->text.handles->h.f.context)){
+ sparms->text.handles->h.f.context->use &= ~CNTXT_ZOOM;
+ rv = 1; /* make sure to redraw */
+ }
+
+ if(rv){ /* remember where to start */
+ FOLDER_S *fp;
+
+ FPROC(sparms)->fs->context = sparms->text.handles->h.f.context;
+ if((fp = folder_entry(sparms->text.handles->h.f.index,
+ FOLDERS(sparms->text.handles->h.f.context))) != NULL){
+ if(strlen(FLDR_NAME(fp)) < MAXFOLDER - 1){
+ strncpy(FPROC(sparms)->fs->first_folder, FLDR_NAME(fp), MAXFOLDER);
+ FPROC(sparms)->fs->first_folder[MAXFOLDER-1] = '\0';
+ }
+ }
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ _("Empty folder collection. No folder to select!"));
+
+ break;
+
+
+ /*---------- Display folders ----------*/
+ case MC_ZOOM :
+ if(sparms->text.handles
+ && folder_total(FOLDERS(sparms->text.handles->h.f.context))){
+ FOLDER_S *fp;
+ int n;
+
+ if((n = selected_folders(sparms->text.handles->h.f.context)) != 0){
+ if(sparms->text.handles->h.f.context->use & CNTXT_ZOOM){
+ sparms->text.handles->h.f.context->use &= ~CNTXT_ZOOM;
+ q_status_message(SM_ORDER, 0, 3,
+ _("Folder List Zoom mode is now off"));
+ }
+ else{
+ q_status_message1(SM_ORDER, 0, 3,
+ _("In Zoomed list of %s folders. Use \"Z\" to restore regular list"),
+ int2string(n));
+ sparms->text.handles->h.f.context->use |= CNTXT_ZOOM;
+ }
+
+ /* exit scrolltool to rebuild screen */
+ rv = 1;
+
+ /* Set where to start after it's rebuilt */
+ FPROC(sparms)->fs->context = sparms->text.handles->h.f.context;
+ FPROC(sparms)->fs->first_folder[0] = '\0';
+ if((fp = folder_entry(sparms->text.handles->h.f.index,
+ FOLDERS(sparms->text.handles->h.f.context)))
+ && !((sparms->text.handles->h.f.context->use & CNTXT_ZOOM)
+ && !fp->selected)
+ && strlen(FLDR_NAME(fp)) < MAXFOLDER - 1){
+ strncpy(FPROC(sparms)->fs->first_folder, FLDR_NAME(fp), MAXFOLDER);
+ FPROC(sparms)->fs->first_folder[MAXFOLDER-1] = '\0';
+ }
+ }
+ else
+ q_status_message(SM_ORDER, 0, 3,
+ _("No selected folders to Zoom on"));
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4, _("No Folders to Zoom on!"));
+
+ break;
+
+ /*----- Ask user to abandon selection before exiting -----*/
+ case MC_EXITQUERY :
+ if(sparms->text.handles
+ && FOLDERS(sparms->text.handles->h.f.context)){
+ int i, folder_n;
+ FOLDER_S *fp;
+
+ folder_n = folder_total(FOLDERS(sparms->text.handles->h.f.context));
+ /* any selected? */
+ for(i = 0; i < folder_n; i++){
+ fp = folder_entry(i, FOLDERS(sparms->text.handles->h.f.context));
+ if(fp->selected)
+ break;
+ }
+
+ if(i < folder_n /* some selections have been made */
+ && want_to(_("Really abandon your selections "),
+ 'y', 'x', NO_HELP, WT_NORM) != 'y'){
+ break;
+ }
+ }
+ rv = 1;
+ break;
+
+ /*------------New Msg command --------------*/
+ case MC_CHK_RECENT:
+ /*
+ * Derived from code provided by
+ * Rostislav Neplokh neplokh@andrew.cmu.edu and
+ * Robert Siemborski (rjs3@andrew).
+ */
+ if(sparms->text.handles
+ && folder_total(FOLDERS(sparms->text.handles->h.f.context))){
+ FOLDER_S *folder;
+
+ folder = folder_entry(sparms->text.handles->h.f.index,
+ FOLDERS(sparms->text.handles->h.f.context));
+
+ if(!folder){
+ strncpy(tmp_output, _("Invalid Folder Name"), sizeof(tmp_output)-1);
+ tmp_output[sizeof(tmp_output)-1] = '\0';
+ }
+ else if(folder->isdir && !folder->isfolder){
+ snprintf(tmp_output, sizeof(tmp_output), _("\"%s\" is a directory"), folder->name);
+ }
+ else{
+ char mailbox_name[MAXPATH+1];
+ unsigned long tot, rec;
+ int we_cancel;
+
+ context_apply(mailbox_name,
+ sparms->text.handles->h.f.context,
+ folder->name, MAXPATH+1);
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ if(get_recent_in_folder(mailbox_name, &rec, NULL, &tot, NULL))
+ snprintf(tmp_output, sizeof(tmp_output),
+ _("%lu total message%s, %lu of them recent"),
+ tot, plural(tot), rec);
+ else
+ snprintf(tmp_output, sizeof(tmp_output),
+ _("%s: Trouble checking for recent mail"), folder->name);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+ }
+ }
+ else{
+ strncpy(tmp_output, _("No folder to check! Can't get recent info"),
+ sizeof(tmp_output)-1);
+ tmp_output[sizeof(tmp_output)-1] = '\0';
+ }
+
+ q_status_message(SM_ORDER, 0, 3, tmp_output);
+ break;
+
+
+ /*--------------- Invalid Command --------------*/
+ default:
+ q_status_message1(SM_ORDER, 0, 2, "fix this: cmd = %s", comatose(cmd));
+ break;
+ }
+
+ return(rv);
+}
+
+
+int
+folder_lister_clickclick(SCROLL_S *sparms)
+{
+ if(!FPROC(sparms)->fs->list_cntxt)
+ return(folder_lister_choice(sparms));
+ else
+ return(folder_processor(MC_SELCUR, ps_global->msgmap, sparms));
+}
+
+int
+folder_lister_choice(SCROLL_S *sparms)
+{
+ int rv = 0, empty = 0;
+ int index = (sparms->text.handles)
+ ? sparms->text.handles->h.f.index : 0;
+ CONTEXT_S *cntxt = (sparms->text.handles)
+ ? sparms->text.handles->h.f.context : NULL;
+
+ if(cntxt){
+
+ FPROC(sparms)->fs->context = cntxt;
+
+ if(cntxt->dir->status & CNTXT_NOFIND){
+ rv = 1; /* leave scrolltool to rebuild screen */
+ FPROC(sparms)->fs->context = cntxt;
+ FPROC(sparms)->fs->first_folder[0] = '\0';
+ }
+ else if(folder_total(FOLDERS(cntxt))){
+ if(folder_lister_select(FPROC(sparms)->fs, cntxt, index,
+ sparms->text.handles ?
+ sparms->text.handles->is_dual_do_open : 0)){
+ rv = 1; /* leave scrolltool to rebuild screen */
+ }
+ else if(FPROC(sparms)->fs->list_cntxt == cntxt){
+ int n = 0, i, folder_n;
+ FOLDER_S *fp;
+ STRLIST_S *sl = NULL, **slp;
+
+ /* Scan folder list for selected items */
+ folder_n = folder_total(FOLDERS(cntxt));
+ slp = &sl;
+ for(i = 0; i < folder_n; i++){
+ fp = folder_entry(i, FOLDERS(cntxt));
+ if(fp->selected){
+ n++;
+ if((*FPROC(sparms)->fs->f.valid)(fp,
+ FPROC(sparms)->fs)){
+ *slp = new_strlist(NULL);
+ (*slp)->name = folder_lister_fullname(
+ FPROC(sparms)->fs, FLDR_NAME(fp));
+
+ slp = &(*slp)->next;
+ }
+ else{
+ free_strlist(&sl);
+ break;
+ }
+ }
+ }
+
+ if((FPROC(sparms)->rv = sl) != NULL)
+ FPROC(sparms)->done = rv = 1;
+ else if(!n)
+ q_status_message(SM_ORDER, 0, 1, LISTMODE_GRIPE);
+ }
+ else
+ rv = folder_lister_finish(sparms, cntxt, index);
+ }
+ else
+ empty++;
+ }
+ else
+ empty++;
+
+ if(empty)
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Empty folder list!"));
+
+ return(rv);
+}
+
+
+int
+folder_lister_finish(SCROLL_S *sparms, CONTEXT_S *cntxt, int index)
+{
+ FOLDER_S *f = folder_entry(index, FOLDERS(cntxt));
+ int rv = 0;
+
+ if((*FPROC(sparms)->fs->f.valid)(f, FPROC(sparms)->fs)){
+ /*
+ * Package up the selected folder names and return...
+ */
+ FPROC(sparms)->fs->context = cntxt;
+ FPROC(sparms)->rv = new_strlist(NULL);
+ FPROC(sparms)->rv->name = folder_lister_fullname(FPROC(sparms)->fs,
+ FLDR_NAME(f));
+ FPROC(sparms)->done = rv = 1;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * This is so that when you Save and use ^T to go to the folder list, and
+ * you're in a directory with no folders, you have a way to add a new
+ * folder there. The add actually gets done by the caller. This is just a
+ * way to let the user type in the name.
+ */
+int
+folder_lister_addmanually(SCROLL_S *sparms)
+{
+ int rc, flags = OE_APPEND_CURRENT, cnt = 0, rv = 0;
+ char addname[MAXFOLDER+1];
+ HelpType help;
+ CONTEXT_S *cntxt = (sparms->text.handles)
+ ? sparms->text.handles->h.f.context
+ : FPROC(sparms)->fs->context;
+
+ /*
+ * Get the foldername from the user.
+ */
+ addname[0] = '\0';
+ help = NO_HELP;
+ while(1){
+ rc = optionally_enter(addname, -FOOTER_ROWS(ps_global), 0,
+ sizeof(addname), _("Name of new folder : "),
+ NULL, help, &flags);
+ removing_leading_and_trailing_white_space(addname);
+
+ if(rc == 3)
+ help = (help == NO_HELP) ? h_save_addman : NO_HELP;
+ else if(rc == 1)
+ return(rv);
+ else if(rc == 0){
+ if(F_OFF(F_ENABLE_DOT_FOLDERS,ps_global) && *addname == '.'){
+ if(cnt++ <= 0)
+ q_status_message(SM_ORDER,3,3,
+ _("Folder name can't begin with dot"));
+ else
+ q_status_message1(SM_ORDER,3,3,
+ _("Config feature \"%s\" enables names beginning with dot"),
+ pretty_feature_name(feature_list_name(F_ENABLE_DOT_FOLDERS), -1));
+
+ display_message(NO_OP_COMMAND);
+ continue;
+ }
+ else if(!strucmp(addname, ps_global->inbox_name)){
+ q_status_message1(SM_ORDER, 3, 3,
+ _("Can't add folder named %s"),
+ ps_global->inbox_name);
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if(*addname){
+ FPROC(sparms)->fs->context = cntxt;
+ FPROC(sparms)->rv = new_strlist(NULL);
+ FPROC(sparms)->rv->name = folder_lister_fullname(FPROC(sparms)->fs,
+ addname);
+ FPROC(sparms)->done = rv = 1;
+ }
+
+ return(rv);
+}
+
+
+void
+folder_lister_km_manager(SCROLL_S *sparms, int handle_hidden)
+{
+ FOLDER_S *fp;
+
+ /* if we're "in" a sub-directory, offer way out */
+ if((sparms->text.handles)
+ ? sparms->text.handles->h.f.context->dir->prev
+ : FPROC(sparms)->fs->context->dir->prev){
+
+ /*
+ * Leave the command characters alone and just change
+ * the labels and the bind.cmd for KM_COL_KEY.
+ * Also, leave KM_MAIN_KEY alone instead of trying to
+ * turn it off in the else clause when it is redundant.
+ */
+ sparms->keys.menu->keys[KM_COL_KEY].label = N_("ParentDir");
+ sparms->keys.menu->keys[KM_COL_KEY].bind.cmd = MC_PARENT;
+ }
+ else if((FPROC(sparms)->fs->context->next
+ || FPROC(sparms)->fs->context->prev)
+ && !FPROC(sparms)->fs->combined_view){
+ sparms->keys.menu->keys[KM_COL_KEY].label = N_("ClctnList");
+ sparms->keys.menu->keys[KM_COL_KEY].bind.cmd = MC_EXIT;
+ }
+ else{
+ sparms->keys.menu->keys[KM_COL_KEY].label = N_("Main Menu");
+ sparms->keys.menu->keys[KM_COL_KEY].bind.cmd = MC_MAIN;
+ }
+
+ if(F_OFF(F_ENABLE_AGG_OPS,ps_global)){
+ clrbitn(KM_ZOOM_KEY, sparms->keys.bitmap);
+ clrbitn(KM_SELECT_KEY, sparms->keys.bitmap);
+ clrbitn(KM_SELCUR_KEY, sparms->keys.bitmap);
+ }
+
+ if(sparms->text.handles
+ && (fp = folder_entry(sparms->text.handles->h.f.index,
+ FOLDERS(sparms->text.handles->h.f.context)))){
+ if(fp->isdir && !sparms->text.handles->is_dual_do_open){
+ sparms->keys.menu->keys[KM_SEL_KEY].label = "[" N_("View Dir") "]";
+ menu_add_binding(sparms->keys.menu, 'v', MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('M'), MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('J'), MC_CHOICE);
+ setbitn(KM_SEL_KEY, sparms->keys.bitmap);
+ clrbitn(KM_ALTVIEW_KEY, sparms->keys.bitmap);
+ clrbitn(KM_EXPORT_KEY, sparms->keys.bitmap);
+ clrbitn(KM_IMPORT_KEY, sparms->keys.bitmap);
+ }
+ else{
+ sparms->keys.menu->keys[KM_SEL_KEY].label = "[" N_("View Fldr") "]";
+ menu_add_binding(sparms->keys.menu, 'v', MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('M'), MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('J'), MC_CHOICE);
+ setbitn(KM_SEL_KEY, sparms->keys.bitmap);
+ clrbitn(KM_ALTVIEW_KEY, sparms->keys.bitmap);
+ setbitn(KM_EXPORT_KEY, sparms->keys.bitmap);
+ setbitn(KM_IMPORT_KEY, sparms->keys.bitmap);
+ }
+ }
+ else if(FPROC(sparms)->fs->combined_view
+ && sparms->text.handles && sparms->text.handles->h.f.context
+ && !sparms->text.handles->h.f.context->dir->prev){
+ sparms->keys.menu->keys[KM_SEL_KEY].label = "[" N_("View Cltn") "]";
+ menu_add_binding(sparms->keys.menu, 'v', MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('M'), MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('J'), MC_CHOICE);
+ setbitn(KM_SEL_KEY, sparms->keys.bitmap);
+ clrbitn(KM_ALTVIEW_KEY, sparms->keys.bitmap);
+ clrbitn(KM_EXPORT_KEY, sparms->keys.bitmap);
+ clrbitn(KM_IMPORT_KEY, sparms->keys.bitmap);
+ }
+ else{
+ clrbitn(KM_SEL_KEY, sparms->keys.bitmap);
+ clrbitn(KM_ALTVIEW_KEY, sparms->keys.bitmap);
+ clrbitn(KM_EXPORT_KEY, sparms->keys.bitmap);
+ clrbitn(KM_IMPORT_KEY, sparms->keys.bitmap);
+ }
+
+ if((sparms->text.handles &&
+ sparms->text.handles->h.f.context &&
+ sparms->text.handles->h.f.context->use & CNTXT_INCMNG) ||
+ (FPROC(sparms) && FPROC(sparms)->fs &&
+ FPROC(sparms)->fs->context &&
+ FPROC(sparms)->fs->context->use & CNTXT_INCMNG))
+ setbitn(KM_SHUFFLE_KEY, sparms->keys.bitmap);
+ else
+ clrbitn(KM_SHUFFLE_KEY, sparms->keys.bitmap);
+
+ if(F_ON(F_TAB_CHK_RECENT, ps_global)){
+ menu_clear_binding(sparms->keys.menu, TAB);
+ menu_init_binding(sparms->keys.menu, TAB, MC_CHK_RECENT, "Tab",
+ /* TRANSLATORS: New Messages */
+ N_("NewMsgs"), KM_RECENT_KEY);
+ setbitn(KM_RECENT_KEY, sparms->keys.bitmap);
+ }
+ else{
+ menu_clear_binding(sparms->keys.menu, TAB);
+ menu_add_binding(sparms->keys.menu, TAB, MC_NEXT_HANDLE);
+ clrbitn(KM_RECENT_KEY, sparms->keys.bitmap);
+ }
+
+ /* May have to "undo" what scrolltool "did" */
+ if(F_ON(F_ARROW_NAV, ps_global)){
+ if(F_ON(F_RELAXED_ARROW_NAV, ps_global)){
+ menu_clear_binding(sparms->keys.menu, KEY_LEFT);
+ menu_add_binding(sparms->keys.menu, KEY_LEFT, MC_PREV_HANDLE);
+ menu_clear_binding(sparms->keys.menu, KEY_RIGHT);
+ menu_add_binding(sparms->keys.menu, KEY_RIGHT, MC_NEXT_HANDLE);
+ }
+ else{
+ menu_clear_binding(sparms->keys.menu, KEY_UP);
+ menu_add_binding(sparms->keys.menu, KEY_UP, MC_PREV_HANDLE);
+ menu_clear_binding(sparms->keys.menu, KEY_DOWN);
+ menu_add_binding(sparms->keys.menu, KEY_DOWN, MC_NEXT_HANDLE);
+ }
+ }
+}
+
+
+void
+folder_lister_km_sel_manager(SCROLL_S *sparms, int handle_hidden)
+{
+ FOLDER_S *fp;
+
+ /* if we're "in" a sub-directory, offer way out */
+ if((sparms->text.handles)
+ ? sparms->text.handles->h.f.context->dir->prev
+ : FPROC(sparms)->fs->context->dir->prev){
+ sparms->keys.menu->keys[FC_COL_KEY].name = "<";
+ /* TRANSLATORS: go to parent directory one level up */
+ sparms->keys.menu->keys[FC_COL_KEY].label = N_("ParentDir");
+ sparms->keys.menu->keys[FC_COL_KEY].bind.cmd = MC_PARENT;
+ sparms->keys.menu->keys[FC_COL_KEY].bind.ch[0] = '<';
+ sparms->keys.menu->keys[FC_COL_KEY].bind.ch[1] = ',';
+ if(F_ON(F_ARROW_NAV,ps_global)){
+ sparms->keys.menu->keys[FC_COL_KEY].bind.nch = 3;
+ sparms->keys.menu->keys[FC_COL_KEY].bind.ch[2] = KEY_LEFT;
+ }
+ else
+ sparms->keys.menu->keys[FC_COL_KEY].bind.nch = 2;
+
+ /* ExitSelect in position 1 */
+ setbitn(FC_EXIT_KEY, sparms->keys.bitmap);
+ }
+ else if((FPROC(sparms)->fs->context->next
+ || FPROC(sparms)->fs->context->prev)
+ && !FPROC(sparms)->fs->combined_view){
+ sparms->keys.menu->keys[FC_COL_KEY].name = "<";
+ /* TRANSLATORS: go to Collection List */
+ sparms->keys.menu->keys[FC_COL_KEY].label = N_("ClctnList");
+ sparms->keys.menu->keys[FC_COL_KEY].bind.cmd = MC_COLLECTIONS;
+ sparms->keys.menu->keys[FC_COL_KEY].bind.ch[0] = '<';
+ sparms->keys.menu->keys[FC_COL_KEY].bind.ch[1] = ',';
+ if(F_ON(F_ARROW_NAV,ps_global)){
+ sparms->keys.menu->keys[FC_COL_KEY].bind.nch = 3;
+ sparms->keys.menu->keys[FC_COL_KEY].bind.ch[2] = KEY_LEFT;
+ }
+ else
+ sparms->keys.menu->keys[FC_COL_KEY].bind.nch = 2;
+
+ /* ExitSelect in position 1 */
+ setbitn(FC_EXIT_KEY, sparms->keys.bitmap);
+ }
+ else if(FPROC(sparms)->fs->combined_view){
+ /*
+ * This can't be a menu_init_binding() because we don't want
+ * to remove the ExitSelect command in position FC_EXIT_KEY.
+ * We just turn it off until we need it again.
+ */
+ sparms->keys.menu->keys[FC_COL_KEY].name = "E";
+ sparms->keys.menu->keys[FC_COL_KEY].label = N_("ExitSelect");
+ sparms->keys.menu->keys[FC_COL_KEY].bind.cmd = MC_EXIT;
+ sparms->keys.menu->keys[FC_COL_KEY].bind.nch = 1;
+ sparms->keys.menu->keys[FC_COL_KEY].bind.ch[0] = 'e';
+
+ /* turn off ExitSelect in position 1, it's in 2 now */
+ clrbitn(FC_EXIT_KEY, sparms->keys.bitmap);
+ }
+
+ /* clean up per-entry bindings */
+ clrbitn(FC_SEL_KEY, sparms->keys.bitmap);
+ clrbitn(FC_ALTSEL_KEY, sparms->keys.bitmap);
+ menu_clear_binding(sparms->keys.menu, ctrl('M'));
+ menu_clear_binding(sparms->keys.menu, ctrl('J'));
+ menu_clear_binding(sparms->keys.menu, '>');
+ menu_clear_binding(sparms->keys.menu, '.');
+ menu_clear_binding(sparms->keys.menu, 's');
+ if(F_ON(F_ARROW_NAV,ps_global))
+ menu_clear_binding(sparms->keys.menu, KEY_RIGHT);
+
+ /* and then re-assign them as needed */
+ if(sparms->text.handles
+ && (fp = folder_entry(sparms->text.handles->h.f.index,
+ FOLDERS(sparms->text.handles->h.f.context)))){
+ setbitn(FC_SEL_KEY, sparms->keys.bitmap);
+ if(fp->isdir){
+ sparms->keys.menu->keys[FC_SEL_KEY].name = ">";
+ menu_add_binding(sparms->keys.menu, '>', MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, '.', MC_CHOICE);
+ if(F_ON(F_ARROW_NAV,ps_global))
+ menu_add_binding(sparms->keys.menu, KEY_RIGHT, MC_CHOICE);
+
+ if(fp->isfolder){
+ sparms->keys.menu->keys[FC_SEL_KEY].label = N_("View Dir");
+ setbitn(FC_ALTSEL_KEY, sparms->keys.bitmap);
+ menu_add_binding(sparms->keys.menu, 's', MC_OPENFLDR);
+ menu_add_binding(sparms->keys.menu, ctrl('M'), MC_OPENFLDR);
+ menu_add_binding(sparms->keys.menu, ctrl('J'), MC_OPENFLDR);
+ }
+ else{
+ sparms->keys.menu->keys[FC_SEL_KEY].label = "[" N_("View Dir") "]";
+ menu_add_binding(sparms->keys.menu, ctrl('M'), MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('J'), MC_CHOICE);
+ }
+ }
+ else{
+ sparms->keys.menu->keys[FC_SEL_KEY].name = "S";
+ sparms->keys.menu->keys[FC_SEL_KEY].label = "[" N_("Select") "]";
+
+ menu_add_binding(sparms->keys.menu, 's', MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('M'), MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('J'), MC_CHOICE);
+ }
+
+ if(F_ON(F_ARROW_NAV,ps_global))
+ menu_add_binding(sparms->keys.menu, KEY_RIGHT, MC_CHOICE);
+ }
+ else if(FPROC(sparms)->fs->combined_view
+ && sparms->text.handles && sparms->text.handles->h.f.context
+ && !sparms->text.handles->h.f.context->dir->prev){
+ setbitn(FC_SEL_KEY, sparms->keys.bitmap);
+ sparms->keys.menu->keys[FC_SEL_KEY].name = ">";
+ sparms->keys.menu->keys[FC_SEL_KEY].label = "[" N_("View Cltn") "]";
+ menu_add_binding(sparms->keys.menu, '>', MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, '.', MC_CHOICE);
+ if(F_ON(F_ARROW_NAV,ps_global))
+ menu_add_binding(sparms->keys.menu, KEY_RIGHT, MC_CHOICE);
+
+ menu_add_binding(sparms->keys.menu, ctrl('M'), MC_CHOICE);
+ menu_add_binding(sparms->keys.menu, ctrl('J'), MC_CHOICE);
+ }
+
+ /* May have to "undo" what scrolltool "did" */
+ if(F_ON(F_ARROW_NAV, ps_global)){
+ if(F_ON(F_RELAXED_ARROW_NAV, ps_global)){
+ menu_clear_binding(sparms->keys.menu, KEY_LEFT);
+ menu_add_binding(sparms->keys.menu, KEY_LEFT, MC_PREV_HANDLE);
+ menu_clear_binding(sparms->keys.menu, KEY_RIGHT);
+ menu_add_binding(sparms->keys.menu, KEY_RIGHT, MC_NEXT_HANDLE);
+ }
+ else{
+ menu_clear_binding(sparms->keys.menu, KEY_UP);
+ menu_add_binding(sparms->keys.menu, KEY_UP, MC_PREV_HANDLE);
+ menu_clear_binding(sparms->keys.menu, KEY_DOWN);
+ menu_add_binding(sparms->keys.menu, KEY_DOWN, MC_NEXT_HANDLE);
+ }
+ }
+}
+
+
+void
+folder_lister_km_sub_manager(SCROLL_S *sparms, int handle_hidden)
+{
+ /*
+ * Folder_processor() also modifies the keymenu.
+ */
+ if(FPROC(sparms)->fs->list_cntxt){
+ clrbitn(SB_LIST_KEY, sparms->keys.bitmap);
+ sparms->keys.menu->keys[SB_SEL_KEY].name = "X";
+ sparms->keys.menu->keys[SB_SEL_KEY].label = "[" N_("Set/Unset") "]";
+ sparms->keys.menu->keys[SB_SEL_KEY].bind.cmd = MC_SELCUR;
+ sparms->keys.menu->keys[SB_SEL_KEY].bind.ch[0] = 'x';
+ }
+ else{
+ clrbitn(SB_SUB_KEY, sparms->keys.bitmap);
+ sparms->keys.menu->keys[SB_SEL_KEY].name = "S";
+ sparms->keys.menu->keys[SB_SEL_KEY].label = "[" N_("Subscribe") "]";
+ sparms->keys.menu->keys[SB_SEL_KEY].bind.cmd = MC_CHOICE;
+ sparms->keys.menu->keys[SB_SEL_KEY].bind.ch[0] = 's';
+ }
+
+ /* May have to "undo" what scrolltool "did" */
+ if(F_ON(F_ARROW_NAV, ps_global)){
+ if(F_ON(F_RELAXED_ARROW_NAV, ps_global)){
+ menu_clear_binding(sparms->keys.menu, KEY_LEFT);
+ menu_add_binding(sparms->keys.menu, KEY_LEFT, MC_PREV_HANDLE);
+ menu_clear_binding(sparms->keys.menu, KEY_RIGHT);
+ menu_add_binding(sparms->keys.menu, KEY_RIGHT, MC_NEXT_HANDLE);
+ }
+ else{
+ menu_clear_binding(sparms->keys.menu, KEY_UP);
+ menu_add_binding(sparms->keys.menu, KEY_UP, MC_PREV_HANDLE);
+ menu_clear_binding(sparms->keys.menu, KEY_DOWN);
+ menu_add_binding(sparms->keys.menu, KEY_DOWN, MC_NEXT_HANDLE);
+ }
+ }
+}
+
+
+
+int
+folder_select(struct pine *ps, CONTEXT_S *context, int cur_index)
+{
+ int i, j, n, total, old_tot, diff,
+ q = 0, rv = 0, narrow = 0;
+ HelpType help = NO_HELP;
+ ESCKEY_S *sel_opts;
+ FOLDER_S *f;
+ static ESCKEY_S self_opts2[] = {
+ /* TRANSLATORS: keymenu descriptions, select all folders, current folder, select
+ based on folder properties, or select based on text contents in folders */
+ {'a', 'a', "A", N_("select All")},
+ {'c', 'c', "C", N_("select Cur")},
+ {'p', 'p', "P", N_("Properties")},
+ {'t', 't', "T", N_("Text")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}
+ };
+ extern ESCKEY_S sel_opts1[];
+ extern char *sel_pmt2;
+#define N_RECENT 4
+
+ f = folder_entry(cur_index, FOLDERS(context));
+
+ sel_opts = self_opts2;
+ if((old_tot = selected_folders(context)) != 0){
+ sel_opts1[1].label = f->selected ? N_("unselect Cur") : N_("select Cur");
+ sel_opts += 2; /* disable extra options */
+ switch(q = radio_buttons(SEL_ALTER_PMT, -FOOTER_ROWS(ps_global),
+ sel_opts1, 'c', 'x', help, RB_NORM)){
+ case 'f' : /* flip selection */
+ n = folder_total(FOLDERS(context));
+ for(total = i = 0; i < n; i++)
+ if((f = folder_entry(i, FOLDERS(context))) != NULL)
+ f->selected = !f->selected;
+
+ return(1); /* repaint */
+
+ case 'n' : /* narrow selection */
+ narrow++;
+ case 'b' : /* broaden selection */
+ q = 0; /* but don't offer criteria prompt */
+ if(context->use & CNTXT_INCMNG){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Select \"%s\" not supported in Incoming Folders"),
+ narrow ? "Narrow" : "Broaden");
+ return(0);
+ }
+
+ break;
+
+ case 'c' : /* Un/Select Current */
+ case 'a' : /* Unselect All */
+ case 'x' : /* cancel */
+ break;
+
+ default :
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Unsupported Select option"));
+ return(0);
+ }
+ }
+
+ if(context->use & CNTXT_INCMNG && F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)){
+ if(F_ON(F_INCOMING_CHECKING_RECENT, ps_global)){
+ self_opts2[N_RECENT].ch = 'r';
+ self_opts2[N_RECENT].rval = 'r';
+ self_opts2[N_RECENT].name = "R";
+ self_opts2[N_RECENT].label = N_("Recent");
+ }
+ else{
+ self_opts2[N_RECENT].ch = 'u';
+ self_opts2[N_RECENT].rval = 'u';
+ self_opts2[N_RECENT].name = "U";
+ self_opts2[N_RECENT].label = N_("Unseen");
+ }
+ }
+ else{
+ self_opts2[N_RECENT].ch = -1;
+ self_opts2[N_RECENT].rval = 0;
+ self_opts2[N_RECENT].name = NULL;
+ self_opts2[N_RECENT].label = NULL;
+ }
+
+ if(!q)
+ q = radio_buttons(sel_pmt2, -FOOTER_ROWS(ps_global),
+ sel_opts, 'c', 'x', help, RB_NORM);
+
+ /*
+ * NOTE: See note about MESSAGECACHE "searched" bits above!
+ */
+ switch(q){
+ case 'x': /* cancel */
+ cmd_cancelled("Select command");
+ return(0);
+
+ case 'c' : /* toggle current's selected state */
+ return(folder_select_toggle(context, cur_index, folder_select_update));
+
+ case 'a' : /* select/unselect all */
+ n = folder_total(FOLDERS(context));
+ for(total = i = 0; i < n; i++)
+ folder_entry(i, FOLDERS(context))->selected = old_tot == 0;
+
+ q_status_message4(SM_ORDER, 0, 2,
+ "%s%s folder%s %sselected",
+ old_tot ? "" : "All ",
+ comatose(old_tot ? old_tot : n),
+ plural(old_tot ? old_tot : n), old_tot ? "UN" : "");
+ return(1);
+
+ case 't' : /* Text */
+ if(!folder_select_text(ps, context, narrow))
+ rv++;
+
+ break;
+
+ case 'p' : /* Properties */
+ if(!folder_select_props(ps, context, narrow))
+ rv++;
+
+ break;
+
+ case 'r' :
+ n = folder_total(FOLDERS(context));
+ for(i = 0; i < n; i++){
+ f = folder_entry(i, FOLDERS(context));
+ if(f->unseen_valid && f->new > 0L)
+ f->selected = 1;
+ }
+
+ break;
+
+ case 'u' :
+ n = folder_total(FOLDERS(context));
+ for(i = 0; i < n; i++){
+ f = folder_entry(i, FOLDERS(context));
+ if(f->unseen_valid && f->unseen > 0L)
+ f->selected = 1;
+ }
+
+ break;
+
+ default :
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Unsupported Select option"));
+ return(0);
+ }
+
+ if(rv)
+ return(0);
+
+ /* rectify the scanned vs. selected folders */
+ n = folder_total(FOLDERS(context));
+ for(total = i = j = 0; i < n; i++)
+ if(folder_entry(i, FOLDERS(context))->scanned)
+ break;
+
+ /*
+ * Any matches at all? If not, the selected set remains the same.
+ * Note that when Narrowing, only matches in the intersection will
+ * show up as scanned. We need to reset i to 0 to erase any already
+ * selected messages which aren't in the intersection.
+ */
+ if(i < n)
+ for(i = 0; i < n; i++)
+ if((f = folder_entry(i, FOLDERS(context))) != NULL){
+ if(narrow){
+ if(f->selected){
+ f->selected = f->scanned;
+ j++;
+ }
+ }
+ else if(f->scanned)
+ f->selected = 1;
+ }
+
+ if(!(diff = (total = selected_folders(context)) - old_tot)){
+ if(narrow)
+ q_status_message4(SM_ORDER, 0, 2,
+ "%s. %s folder%s remain%s selected.",
+ j ? _("No change resulted")
+ : _("No messages in intersection"),
+ comatose(old_tot), plural(old_tot),
+ (old_tot == 1L) ? "s" : "");
+ else if(old_tot && j)
+ q_status_message(SM_ORDER, 0, 2,
+ _("No change resulted. Matching folders already selected."));
+ else
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ "Select failed! No %sfolders selected.",
+ old_tot ? "additional " : "");
+ }
+ else if(old_tot){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Select matched %d folder%s. %s %sfolder%s %sselected.",
+ (diff > 0) ? diff : old_tot + diff,
+ plural((diff > 0) ? diff : old_tot + diff),
+ comatose((diff > 0) ? total : -diff),
+ (diff > 0) ? "total " : "",
+ plural((diff > 0) ? total : -diff),
+ (diff > 0) ? "" : "UN");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ q_status_message(SM_ORDER, 0, 2, tmp_20k_buf);
+ }
+ else
+ q_status_message2(SM_ORDER, 0, 2, "Select matched %s folder%s.",
+ comatose(diff), plural(diff));
+
+ return(1);
+}
+
+
+
+
+int
+folder_lister_select(FSTATE_S *fs, CONTEXT_S *context, int index, int is_dual_do_open)
+{
+ int rv = 0;
+ FDIR_S *fp;
+ FOLDER_S *f = folder_entry(index, FOLDERS(context));
+
+ /*--- Entering a directory? ---*/
+ if(f->isdir && !is_dual_do_open){
+ fp = next_folder_dir(context, f->name, TRUE, fs->cache_streamp);
+
+ /* Provide context in new collection header */
+ fp->desc = folder_lister_desc(context, fp);
+
+ /* Insert new directory into list */
+ free_folder_list(context);
+ fp->prev = context->dir;
+ fp->status |= CNTXT_SUBDIR;
+ context->dir = fp;
+ q_status_message2(SM_ORDER, 0, 3, "Now in %sdirectory: %s",
+ folder_total(FOLDERS(context))
+ ? "" : "EMPTY ", fp->ref);
+ rv++;
+ }
+ else
+ rv = folder_lister_parent(fs, context, index, 1);
+
+ return(rv);
+}
+
+
+int
+folder_lister_parent(FSTATE_S *fs, CONTEXT_S *context, int index, int force_parent)
+{
+ int rv = 0;
+ FDIR_S *fp;
+
+ if(!force_parent && (fp = context->dir->prev)){
+ char *s, oldir[MAILTMPLEN];
+
+ folder_select_preserve(context);
+ oldir[0] = '\0';
+ if((s = strrindex(context->dir->ref, context->dir->delim)) != NULL){
+ *s = '\0';
+ if((s = strrindex(context->dir->ref, context->dir->delim)) != NULL){
+ strncpy(oldir, s+1, sizeof(oldir)-1);
+ oldir[sizeof(oldir)-1] = '\0';
+ }
+ }
+
+ if(*oldir){
+ /* remember current directory for hiliting in new list */
+ fs->context = context;
+ if(strlen(oldir) < MAXFOLDER - 1){
+ strncpy(fs->first_folder, oldir, MAXFOLDER);
+ fs->first_folder[MAXFOLDER-1] = '\0';
+ fs->first_dir = 1;
+ }
+ }
+
+ free_fdir(&context->dir, 0);
+ fp->status |= CNTXT_NOFIND;
+
+ context->dir = fp;
+
+ if(fp->status & CNTXT_SUBDIR)
+ q_status_message1(SM_ORDER, 0, 3, _("Now in directory: %s"),
+ strsquish(tmp_20k_buf + 500, SIZEOF_20KBUF-500, fp->ref,
+ ps_global->ttyo->screen_cols - 22));
+ else
+ q_status_message(SM_ORDER, 0, 3,
+ _("Returned to collection's top directory"));
+
+ rv++;
+ }
+
+ return(rv);
+}
+
+
+char *
+folder_lister_fullname(FSTATE_S *fs, char *name)
+{
+ if(fs->context->dir->status & CNTXT_SUBDIR){
+ char tmp[2*MAILTMPLEN], tmp2[2*MAILTMPLEN], *p;
+
+ if(fs->context->dir->ref){
+ snprintf(tmp, sizeof(tmp), "%.*s%.*s",
+ sizeof(tmp)/2,
+ ((fs->relative_path || (fs->context->use & CNTXT_SAVEDFLT))
+ && (p = strstr(fs->context->context, "%s")) && !*(p+2)
+ && !strncmp(fs->context->dir->ref, fs->context->context,
+ p - fs->context->context))
+ ? fs->context->dir->ref + (p - fs->context->context)
+ : fs->context->dir->ref,
+ sizeof(tmp)/2, name);
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+
+ /*
+ * If the applied name is still ambiguous (defined
+ * that way by the user (i.e., "mail/[]"), then
+ * we better fix it up...
+ */
+ if(context_isambig(tmp)
+ && !fs->relative_path
+ && !(fs->context->use & CNTXT_SAVEDFLT)){
+ /* if it's in the primary collection, the names relative */
+ if(fs->context->dir->ref){
+ if(IS_REMOTE(fs->context->context)
+ && (p = strrindex(fs->context->context, '}'))){
+ snprintf(tmp2, sizeof(tmp2), "%.*s%.*s",
+ MIN(p - fs->context->context + 1, sizeof(tmp2)/2),
+ fs->context->context,
+ sizeof(tmp2)/2, tmp);
+ tmp2[sizeof(tmp2)-1] = '\0';
+ }
+ else
+ build_path(tmp2, ps_global->ui.homedir, tmp, sizeof(tmp2));
+ }
+ else
+ (void) context_apply(tmp2, fs->context, tmp, sizeof(tmp2));
+
+ return(cpystr(tmp2));
+ }
+
+ return(cpystr(tmp));
+ }
+
+ return(cpystr(name));
+}
+
+
+/*
+ * Export a folder from pine space to user's space. It will still be a regular
+ * mail folder but it will be in the user's home directory or something like
+ * that.
+ */
+void
+folder_export(SCROLL_S *sparms)
+{
+ FOLDER_S *f;
+ MAILSTREAM *stream, *ourstream = NULL;
+ char expanded_file[MAILTMPLEN], *p,
+ tmp[MAILTMPLEN], *fname, *fullname = NULL,
+ filename[MAXPATH+1], full_filename[MAXPATH+1],
+ deefault[MAXPATH+1];
+ int open_inbox = 0, we_cancel = 0, width,
+ index = (sparms && sparms->text.handles)
+ ? sparms->text.handles->h.f.index : 0;
+ CONTEXT_S *savecntxt,
+ *cntxt = (sparms && sparms->text.handles)
+ ? sparms->text.handles->h.f.context : NULL;
+ static HISTORY_S *history = NULL;
+
+ dprint((4, "\n - folder export -\n"));
+
+ if(cntxt){
+ if(folder_total(FOLDERS(cntxt))){
+ f = folder_entry(index, FOLDERS(cntxt));
+ if((*FPROC(sparms)->fs->f.valid)(f, FPROC(sparms)->fs)){
+ savecntxt = FPROC(sparms)->fs->context; /* necessary? */
+ FPROC(sparms)->fs->context = cntxt;
+ strncpy(deefault, FLDR_NAME(f), sizeof(deefault)-1);
+ deefault[sizeof(deefault)-1] = '\0';
+ fname = folder_lister_fullname(FPROC(sparms)->fs, FLDR_NAME(f));
+ FPROC(sparms)->fs->context = savecntxt;
+
+ /*
+ * We have to allow for INBOX and nicknames in
+ * the incoming collection. Mimic what happens in
+ * do_broach_folder.
+ */
+ strncpy(expanded_file, fname, sizeof(expanded_file));
+ expanded_file[sizeof(expanded_file)-1] = '\0';
+
+ if(strucmp(fname, ps_global->inbox_name) == 0
+ || strucmp(fname, ps_global->VAR_INBOX_PATH) == 0)
+ open_inbox++;
+
+ if(!open_inbox && cntxt && context_isambig(fname)){
+ if((p=folder_is_nick(fname, FOLDERS(cntxt), 0)) != NULL){
+ strncpy(expanded_file, p, sizeof(expanded_file));
+ expanded_file[sizeof(expanded_file)-1] = '\0';
+ }
+ else if ((cntxt->use & CNTXT_INCMNG)
+ && (folder_index(fname, cntxt, FI_FOLDER) < 0)
+ && !is_absolute_path(fname)){
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Can't find Incoming Folder %s."), fname);
+ return;
+ }
+ }
+
+ if(open_inbox)
+ fullname = ps_global->VAR_INBOX_PATH;
+ else{
+ /*
+ * We don't want to interpret this name in the context
+ * of the reference string, that was already done
+ * above in folder_lister_fullname(), just in the
+ * regular context. We also don't want to lose the
+ * reference string because we will still be in the
+ * subdirectory after this operation completes. So
+ * temporarily zero out the reference.
+ */
+ FDIR_S *tmpdir;
+
+ tmpdir = (cntxt ? cntxt->dir : NULL);
+ cntxt->dir = NULL;
+ fullname = context_apply(tmp, cntxt, expanded_file,
+ sizeof(tmp));
+ cntxt->dir = tmpdir;
+ }
+
+ width = MAX(20,
+ ps_global->ttyo ? ps_global->ttyo->screen_cols : 80);
+ stream = sp_stream_get(fullname, SP_MATCH | SP_RO_OK);
+ if(!stream && fullname){
+ /*
+ * Just using filename and full_filename as convenient
+ * temporary buffers here.
+ */
+ snprintf(filename, sizeof(filename), "Opening \"%s\"",
+ short_str(fullname, full_filename, sizeof(full_filename),
+ MAX(10, width-17),
+ MidDots));
+ filename[sizeof(filename)-1] = '\0';
+ we_cancel = busy_cue(filename, NULL, 0);
+ stream = pine_mail_open(NULL, fullname,
+ OP_READONLY|SP_USEPOOL|SP_TEMPUSE,
+ NULL);
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ ourstream = stream;
+ }
+
+ /*
+ * We have a stream for the folder we want to
+ * export.
+ */
+ if(stream && stream->nmsgs > 0L){
+ int r = 1;
+ static ESCKEY_S eopts[] = {
+ {ctrl('T'), 10, "^T", N_("To Files")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}};
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ eopts[r].ch = ctrl('I');
+ eopts[r].rval = 11;
+ eopts[r].name = "TAB";
+ /* TRANSLATORS: Complete is a verb, complete the name of a folder */
+ eopts[r].label = _("Complete");
+ }
+
+ eopts[++r].ch = -1;
+
+ filename[0] = '\0';
+ full_filename[0] = '\0';
+
+ r = get_export_filename(ps_global, filename, deefault,
+ full_filename,
+ sizeof(filename)-20, fname, NULL,
+ eopts, NULL,
+ -FOOTER_ROWS(ps_global),
+ GE_IS_EXPORT | GE_NO_APPEND, &history);
+ if(r < 0){
+ switch(r){
+ default:
+ case -1:
+ cmd_cancelled("Export folder");
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't export to file outside of %s"),
+ ps_global->VAR_OPER_DIR);
+ break;
+ }
+ }
+ else{
+ int ok;
+ char *ff;
+
+ ps_global->mm_log_error = 0;
+
+ /*
+ * Do the creation in standard unix format, so it
+ * is readable by all.
+ */
+ rplstr(full_filename, sizeof(full_filename), 0, "#driver.unix/");
+ ok = pine_mail_create(NULL, full_filename) != 0L;
+ /*
+ * ff points to the original filename, without the
+ * driver prefix. Only mail_create knows how to
+ * handle driver prefixes.
+ */
+ ff = full_filename + strlen("#driver.unix/");
+
+ if(!ok){
+ if(!ps_global->mm_log_error)
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error creating file"));
+ }
+ else{
+ long l, snmsgs;
+ MSGNO_S *tmpmap = NULL;
+
+ snmsgs = stream->nmsgs;
+ mn_init(&tmpmap, snmsgs);
+ for(l = 1L; l <= snmsgs; l++)
+ if(l == 1L)
+ mn_set_cur(tmpmap, l);
+ else
+ mn_add_cur(tmpmap, l);
+
+ blank_keymenu(ps_global->ttyo->screen_rows-2, 0);
+ we_cancel = busy_cue(_("Copying folder"), NULL, 0);
+ l = save(ps_global, stream, NULL, ff, tmpmap, 0);
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ mn_give(&tmpmap);
+
+ if(l == snmsgs)
+ q_status_message2(SM_ORDER, 0, 3,
+ "Folder %s exported to %s",
+ fname, filename);
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error exporting to %s"),
+ filename);
+ dprint((2,
+ "Error exporting to %s: expected %ld msgs, saved %ld\n",
+ filename, snmsgs, l));
+ }
+ }
+ }
+ }
+ else if(stream)
+ q_status_message1(SM_ORDER|SM_DING, 3, 3,
+ _("No messages in %s to export"), fname);
+ else
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ _("Can't open folder for exporting"));
+
+ if(fname)
+ fs_give((void **) &fname);
+
+ if(ourstream)
+ pine_mail_close(ourstream);
+ }
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Empty folder list!"));
+}
+
+
+/*
+ * Import a folder from user's space back to pine space.
+ * We're just importing a regular mail folder, and saving all the messages
+ * to another folder. It may seem more magical to the user but it isn't.
+ * The import folder is a local folder, the new one can be remote or whatever.
+ * Args sparms
+ * add_folder -- return new folder name here
+ * len -- length of add_folder
+ *
+ * Returns 1 if we may have to redraw screen, 0 otherwise
+ */
+int
+folder_import(SCROLL_S *sparms, char *add_folder, size_t len)
+{
+ MAILSTREAM *istream = NULL;
+ int r = 1, rv = 0;
+ char filename[MAXPATH+1], full_filename[MAXPATH+1];
+ static HISTORY_S *history = NULL;
+ static ESCKEY_S eopts[] = {
+ {ctrl('T'), 10, "^T", N_("To Files")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}};
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ eopts[r].ch = ctrl('I');
+ eopts[r].rval = 11;
+ eopts[r].name = "TAB";
+ eopts[r].label = N_("Complete");
+ }
+
+ eopts[++r].ch = -1;
+
+ filename[0] = '\0';
+ full_filename[0] = '\0';
+
+ /* get a folder to import */
+ r = get_export_filename(ps_global, filename, NULL, full_filename,
+ sizeof(filename)-20, "messages", "IMPORT",
+ eopts, NULL,
+ -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
+ if(r < 0){
+ switch(r){
+ default:
+ case -1:
+ cmd_cancelled("Import folder");
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't import file outside of %s"),
+ ps_global->VAR_OPER_DIR);
+ break;
+ }
+ }
+ else{
+ ps_global->mm_log_error = 0;
+ if(full_filename && full_filename[0])
+ istream = pine_mail_open(NULL, full_filename,
+ OP_READONLY | SP_TEMPUSE, NULL);
+
+ if(istream && istream->nmsgs > 0L){
+ long l;
+ int we_cancel = 0;
+ char newfolder[MAILTMPLEN], nmsgs[32];
+ MSGNO_S *tmpmap = NULL;
+ CONTEXT_S *cntxt, *ourcntxt;
+
+ cntxt = (sparms && sparms->text.handles)
+ ? sparms->text.handles->h.f.context : NULL;
+ ourcntxt = cntxt;
+ newfolder[0] = '\0';
+ snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(istream->nmsgs));
+ nmsgs[sizeof(nmsgs)-1] = '\0';
+
+ /*
+ * Select a folder to save the messages to.
+ */
+ if(save_prompt(ps_global, &cntxt, newfolder, sizeof(newfolder),
+ nmsgs, NULL, 0L, NULL, NULL, NULL)){
+
+ if((cntxt == ourcntxt) && newfolder[0]){
+ rv = 1;
+ strncpy(add_folder, newfolder, len-1);
+ add_folder[len-1] = '\0';
+ free_folder_list(cntxt);
+ }
+
+ mn_init(&tmpmap, istream->nmsgs);
+ for(l = 1; l <= istream->nmsgs; l++)
+ if(l == 1L)
+ mn_set_cur(tmpmap, l);
+ else
+ mn_add_cur(tmpmap, l);
+
+ blank_keymenu(ps_global->ttyo->screen_rows-2, 0);
+ we_cancel = busy_cue("Importing messages", NULL, 0);
+ l = save(ps_global, istream, cntxt, newfolder, tmpmap,
+ SV_INBOXWOCNTXT);
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ mn_give(&tmpmap);
+
+ if(l == istream->nmsgs)
+ q_status_message2(SM_ORDER, 0, 3,
+ "Folder %s imported to %s",
+ full_filename, newfolder);
+ else
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error importing to %s"),
+ newfolder);
+ }
+ }
+ else if(istream)
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("No messages in file %s"),
+ full_filename);
+ else if(!ps_global->mm_log_error)
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Can't open file %s for import"), full_filename);
+ }
+
+ if(istream)
+ pine_mail_close(istream);
+
+ return(rv);
+}
+
+
+int
+folder_select_toggle(CONTEXT_S *context, int index, int (*func) (CONTEXT_S *, int))
+{
+ FOLDER_S *f;
+
+ if((f = folder_entry(index, FOLDERS(context))) != NULL){
+ f->selected = !f->selected;
+ return((*func)(context, index));
+ }
+ return 1;
+}
+
+
+/*
+ * Find the next '}' that isn't part of a "${"
+ * that appear for environment variables. We need
+ * this for configuration, because we want to edit
+ * the original pinerc entry, and not the digested
+ * value.
+ */
+char *
+end_bracket_no_nest(char *str)
+{
+ char *p;
+
+ for(p = str; *p; p++){
+ if(*p == '$' && *(p+1) == '{'){
+ while(*p && *p != '}')
+ p++;
+ if(!*p)
+ return NULL;
+ }
+ else if(*p == '}')
+ return p;
+ }
+ /* if we get here then there was no trailing '}' */
+ return NULL;
+}
+
+
+/*----------------------------------------------------------------------
+ Create a new folder
+
+ Args: context -- context we're adding in
+ which -- which config file to operate on
+ varnum -- which varnum to operate on
+ add_folder -- return new folder here
+ add_folderlen -- length of add_folder
+ possible_stream -- possible stream for re-use
+ def -- default value to start out with for add_folder
+ (for now, only for inbox-path editing)
+
+ Result: returns nonzero on successful add, 0 otherwise
+ ----*/
+int
+add_new_folder(CONTEXT_S *context, EditWhich which, int varnum, char *add_folder,
+ size_t add_folderlen, MAILSTREAM *possible_stream, char *def)
+{
+ char tmp[MAX(MAXFOLDER,6*MAX_SCREEN_COLS)+1], nickname[32],
+ *p = NULL, *return_val = NULL, buf[MAILTMPLEN],
+ buf2[MAILTMPLEN], def_in_prompt[MAILTMPLEN];
+ HelpType help;
+ PINERC_S *prc = NULL;
+ int i, rc, offset, exists, cnt = 0, isdir = 0;
+ int maildrop = 0, flags = 0, inbox = 0, require_a_subfolder = 0;
+ char *maildropfolder = NULL, *maildroplongname = NULL;
+ char *default_mail_drop_host = NULL,
+ *default_mail_drop_folder = NULL,
+ *default_dstn_host = NULL,
+ *default_dstn_folder = NULL,
+ *copydef = NULL,
+ *dstnmbox = NULL;
+ char mdmbox[MAILTMPLEN], ctmp[MAILTMPLEN];
+ MAILSTREAM *create_stream = NULL;
+ FOLDER_S *f;
+ static ESCKEY_S add_key[] = {{ctrl('X'),12,"^X", NULL},
+ {-1, 0, NULL, NULL}};
+
+ dprint((4, "\n - add_new_folder - \n"));
+
+ add_folder[0] = '\0';
+ nickname[0] = '\0';
+ inbox = (varnum == V_INBOX_PATH);
+
+ if(inbox || context->use & CNTXT_INCMNG){
+ char inbox_host[MAXPATH], *beg, *end = NULL;
+ int readonly = 0;
+ static ESCKEY_S host_key[4];
+
+ if(ps_global->restricted)
+ readonly = 1;
+ else{
+ switch(which){
+ case Main:
+ prc = ps_global->prc;
+ break;
+ case Post:
+ prc = ps_global->post_prc;
+ break;
+ case None:
+ break;
+ }
+
+ readonly = prc ? prc->readonly : 1;
+ }
+
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return(FALSE);
+ }
+
+ if(readonly){
+ q_status_message(SM_ORDER,3,5,
+ _("Cancelled: config file not editable"));
+ return(FALSE);
+ }
+
+ /*
+ * When adding an Incoming folder we can't supply the inbox host
+ * as a default, because then the user has no way to type just
+ * a plain RETURN to mean "no host, I want a local folder".
+ * So we supply it as a ^X command instead. We could supply it
+ * as the initial value of the string...
+ *
+ * When editing inbox-path we will supply the default value as an
+ * initial value of the string which can be edited. They can edit it
+ * or erase it entirely to mean no host.
+ */
+
+ if(inbox && def){
+
+ copydef = cpystr(def);
+ (void) removing_double_quotes(copydef);
+
+ if(check_for_move_mbox(copydef, mdmbox, sizeof(mdmbox), &dstnmbox)){
+
+ /*
+ * Current inbox is using a mail drop. Get the default
+ * host value for the maildrop.
+ */
+
+ if(mdmbox
+ && (mdmbox[0] == '{'
+ || (mdmbox[0] == '*' && mdmbox[1] == '{'))
+ && (end = end_bracket_no_nest(mdmbox+1))
+ && end-mdmbox < add_folderlen){
+ *end = '\0';
+ if(mdmbox[0] == '{')
+ default_mail_drop_host = cpystr(mdmbox+1);
+ else
+ default_mail_drop_host = cpystr(mdmbox+2);
+ }
+
+ if(!default_mail_drop_host)
+ default_mail_drop_folder = cpystr(mdmbox);
+ else if(end && *(end+1))
+ default_mail_drop_folder = cpystr(end+1);
+
+ end = NULL;
+ if(dstnmbox
+ && (*dstnmbox == '{'
+ || (*dstnmbox == '*' && *++dstnmbox == '{'))
+ && (end = end_bracket_no_nest(dstnmbox+1))
+ && end-dstnmbox < add_folderlen){
+ *end = '\0';
+ default_dstn_host = cpystr(dstnmbox+1);
+ }
+
+ if(!default_dstn_host)
+ default_dstn_folder = cpystr(dstnmbox);
+ else if(end && *(end+1))
+ default_dstn_folder = cpystr(end+1);
+
+ maildrop++;
+ }
+ else{
+ end = NULL;
+ dstnmbox = copydef;
+ if(dstnmbox
+ && (*dstnmbox == '{'
+ || (*dstnmbox == '*' && *++dstnmbox == '{'))
+ && (end = end_bracket_no_nest(dstnmbox+1))
+ && end-dstnmbox < add_folderlen){
+ *end = '\0';
+ default_dstn_host = cpystr(dstnmbox+1);
+ }
+
+ if(!default_dstn_host)
+ default_dstn_folder = cpystr(dstnmbox);
+ else if(end && *(end+1))
+ default_dstn_folder = cpystr(end+1);
+ }
+
+ if(copydef)
+ fs_give((void **) &copydef);
+ }
+
+get_folder_name:
+
+ i = 0;
+ host_key[i].ch = 0;
+ host_key[i].rval = 0;
+ host_key[i].name = "";
+ host_key[i++].label = "";
+
+ inbox_host[0] = '\0';
+ if(!inbox && (beg = ps_global->VAR_INBOX_PATH)
+ && (*beg == '{' || (*beg == '*' && *++beg == '{'))
+ && (end = strindex(ps_global->VAR_INBOX_PATH, '}'))){
+ strncpy(inbox_host, beg+1, end - beg);
+ inbox_host[end - beg - 1] = '\0';
+ host_key[i].ch = ctrl('X');
+ host_key[i].rval = 12;
+ host_key[i].name = "^X";
+ host_key[i++].label = N_("Use Inbox Host");
+ }
+ else{
+ host_key[i].ch = 0;
+ host_key[i].rval = 0;
+ host_key[i].name = "";
+ host_key[i++].label = "";
+ }
+
+ if(!maildrop && !maildropfolder){
+ host_key[i].ch = ctrl('W');
+ host_key[i].rval = 13;
+ host_key[i].name = "^W";
+ /* TRANSLATORS: a mail drop is a place where mail is copied to so you
+ can read it. */
+ host_key[i++].label = N_("Use a Mail Drop");
+ }
+ else if(maildrop){
+ host_key[i].ch = ctrl('W');
+ host_key[i].rval = 13;
+ host_key[i].name = "^W";
+ host_key[i++].label = N_("Do Not use a Mail Drop");
+ }
+
+ host_key[i].ch = -1;
+ host_key[i].rval = 0;
+ host_key[i].name = NULL;
+ host_key[i].label = NULL;
+
+ if(maildrop)
+ snprintf(tmp, sizeof(tmp), _("Name of Mail Drop server : "));
+ else if(maildropfolder)
+ snprintf(tmp, sizeof(tmp), _("Name of server to contain destination folder : "));
+ else if(inbox)
+ snprintf(tmp, sizeof(tmp), _("Name of Inbox server : "));
+ else
+ snprintf(tmp, sizeof(tmp), _("Name of server to contain added folder : "));
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ help = NO_HELP;
+
+ /* set up defaults */
+ if(inbox && def){
+ flags = OE_APPEND_CURRENT;
+ if(maildrop && default_mail_drop_host){
+ strncpy(add_folder, default_mail_drop_host, add_folderlen);
+ add_folder[add_folderlen-1] = '\0';
+ }
+ else if(!maildrop && default_dstn_host){
+ strncpy(add_folder, default_dstn_host, add_folderlen);
+ add_folder[add_folderlen-1] = '\0';
+ }
+ else
+ add_folder[0] = '\0';
+ }
+ else{
+ flags = 0;
+ add_folder[0] = '\0';
+ }
+
+ while(1){
+ rc = optionally_enter(add_folder, -FOOTER_ROWS(ps_global), 0,
+ add_folderlen, tmp, host_key, help, &flags);
+ removing_leading_and_trailing_white_space(add_folder);
+
+ /*
+ * User went for the whole enchilada and entered a maildrop
+ * completely without going through the steps.
+ * Split it up as if they did and then skip over
+ * some of the code.
+ */
+ if(check_for_move_mbox(add_folder, mdmbox, sizeof(mdmbox),
+ &dstnmbox)){
+ maildrop = 1;
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+
+ maildropfolder = cpystr(mdmbox);
+
+ strncpy(add_folder, dstnmbox, add_folderlen);
+ add_folder[add_folderlen-1] = '\0';
+ offset = 0;
+ goto skip_over_folder_input;
+ }
+
+ /*
+ * Now check to see if they entered a whole c-client
+ * spec that isn't a mail drop.
+ */
+ if(add_folder[0] == '{'
+ && add_folder[1] != '\0'
+ && (p = srchstr(add_folder, "}"))
+ && *(p+1) != '\0'){
+ offset = p+1 - add_folder;
+ goto skip_over_folder_input;
+ }
+
+ /*
+ * And check to see if they entered "INBOX".
+ */
+ if(!strucmp(add_folder, ps_global->inbox_name)){
+ offset = 0;
+ goto skip_over_folder_input;
+ }
+
+ /* remove surrounding braces */
+ if(add_folder[0] == '{' && add_folder[1] != '\0'){
+ char *q;
+
+ q = add_folder + strlen(add_folder) - 1;
+ if(*q == '}'){
+ *q = '\0';
+ for(q = add_folder; *q; q++)
+ *q = *(q+1);
+ }
+ }
+
+ if(rc == 3){
+ if(maildropfolder && inbox)
+ helper(h_inbox_add_maildrop_destn,
+ _("HELP FOR DESTINATION SERVER "), HLPD_SIMPLE);
+ else if(maildropfolder && !inbox)
+ helper(h_incoming_add_maildrop_destn,
+ _("HELP FOR DESTINATION SERVER "), HLPD_SIMPLE);
+ else if(maildrop && inbox)
+ helper(h_inbox_add_maildrop, _("HELP FOR MAILDROP NAME "),
+ HLPD_SIMPLE);
+ else if(maildrop && !inbox)
+ helper(h_incoming_add_maildrop, _("HELP FOR MAILDROP NAME "),
+ HLPD_SIMPLE);
+ else if(inbox)
+ helper(h_incoming_add_inbox, _("HELP FOR INBOX SERVER "),
+ HLPD_SIMPLE);
+ else
+ helper(h_incoming_add_folder_host, _("HELP FOR SERVER NAME "),
+ HLPD_SIMPLE);
+
+ ps_global->mangled_screen = 1;
+ }
+ else if(rc == 12){
+ strncpy(add_folder, inbox_host, add_folderlen);
+ flags |= OE_APPEND_CURRENT;
+ }
+ else if(rc == 13){
+ if(maildrop){
+ maildrop = 0;
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+ }
+ else{
+ maildrop++;
+ if(inbox && def){
+ default_mail_drop_host = default_dstn_host;
+ default_dstn_host = NULL;
+ default_mail_drop_folder = default_dstn_folder;
+ default_dstn_folder = NULL;
+ }
+ }
+
+ goto get_folder_name;
+ }
+ else if(rc == 1){
+ q_status_message(SM_ORDER,0,2,
+ inbox ? _("INBOX change cancelled")
+ : _("Addition of new folder cancelled"));
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+
+ if(inbox && def){
+ if(default_mail_drop_host)
+ fs_give((void **) &default_mail_drop_host);
+
+ if(default_mail_drop_folder)
+ fs_give((void **) &default_mail_drop_folder);
+
+ if(default_dstn_host)
+ fs_give((void **) &default_dstn_host);
+
+ if(default_dstn_folder)
+ fs_give((void **) &default_dstn_folder);
+ }
+
+ return(FALSE);
+ }
+ else if(rc == 0)
+ break;
+ }
+ }
+
+ /* set up default folder, if any */
+ def_in_prompt[0] = '\0';
+ if(inbox && def){
+ if(maildrop && default_mail_drop_folder){
+ strncpy(def_in_prompt, default_mail_drop_folder,
+ sizeof(def_in_prompt));
+ def_in_prompt[sizeof(def_in_prompt)-1] = '\0';
+ }
+ else if(!maildrop && default_dstn_folder){
+ strncpy(def_in_prompt, default_dstn_folder,
+ sizeof(def_in_prompt));
+ def_in_prompt[sizeof(def_in_prompt)-1] = '\0';
+ }
+ }
+
+ if((offset = strlen(add_folder)) != 0){ /* must be host for incoming */
+ int i;
+ if(maildrop)
+ snprintf(tmp, sizeof(tmp),
+ "Maildrop folder on \"%s\" to copy mail from%s%s%s : ",
+ short_str(add_folder, buf, sizeof(buf), 15, EndDots),
+ def_in_prompt[0] ? " [" : "",
+ short_str(def_in_prompt, buf2, sizeof(buf2), 20, MidDots),
+ def_in_prompt[0] ? "]" : "");
+ else if(maildropfolder)
+ snprintf(tmp, sizeof(tmp),
+ "Folder on \"%s\" to copy mail to%s%s%s : ",
+ short_str(add_folder, buf, sizeof(buf), 20, EndDots),
+ def_in_prompt[0] ? " [" : "",
+ short_str(def_in_prompt, buf2, sizeof(buf2), 20, MidDots),
+ def_in_prompt[0] ? "]" : "");
+ else if(inbox)
+ snprintf(tmp, sizeof(tmp),
+ "Folder on \"%s\" to use for INBOX%s%s%s : ",
+ short_str(add_folder, buf, sizeof(buf), 20, EndDots),
+ def_in_prompt[0] ? " [" : "",
+ short_str(def_in_prompt, buf2, sizeof(buf2), 20, MidDots),
+ def_in_prompt[0] ? "]" : "");
+ else
+ snprintf(tmp, sizeof(tmp),
+ "Folder on \"%s\" to add%s%s%s : ",
+ short_str(add_folder, buf, sizeof(buf), 25, EndDots),
+ def_in_prompt[0] ? " [" : "",
+ short_str(def_in_prompt, buf2, sizeof(buf2), 20, MidDots),
+ def_in_prompt[0] ? "]" : "");
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ for(i = offset;i >= 0; i--)
+ add_folder[i+1] = add_folder[i];
+
+ add_folder[0] = '{';
+ add_folder[++offset] = '}';
+ add_folder[++offset] = '\0'; /* +2, total */
+ }
+ else{
+ if(maildrop)
+ snprintf(tmp, sizeof(tmp),
+ "Folder to copy mail from%s%s%s : ",
+ def_in_prompt[0] ? " [" : "",
+ short_str(def_in_prompt, buf2, sizeof(buf2), 20, MidDots),
+ def_in_prompt[0] ? "]" : "");
+ else if(maildropfolder)
+ snprintf(tmp, sizeof(tmp),
+ "Folder to copy mail to%s%s%s : ",
+ def_in_prompt[0] ? " [" : "",
+ short_str(def_in_prompt, buf2, sizeof(buf2), 20, MidDots),
+ def_in_prompt[0] ? "]" : "");
+ else if(inbox)
+ snprintf(tmp, sizeof(tmp),
+ "Folder name to use for INBOX%s%s%s : ",
+ def_in_prompt[0] ? " [" : "",
+ short_str(def_in_prompt, buf2, sizeof(buf2), 20, MidDots),
+ def_in_prompt[0] ? "]" : "");
+ else
+ snprintf(tmp, sizeof(tmp),
+ "Folder name to add%s%s%s : ",
+ def_in_prompt[0] ? " [" : "",
+ short_str(def_in_prompt, buf2, sizeof(buf2), 20, MidDots),
+ def_in_prompt[0] ? "]" : "");
+
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+
+ help = NO_HELP;
+ while(1){
+
+ p = NULL;
+ if(isdir){
+ add_key[0].label = N_("Create Folder");
+ if(tmp[0] == 'F')
+ rplstr(tmp, sizeof(tmp), 6, N_("Directory"));
+ }
+ else{
+ add_key[0].label = N_("Create Directory");
+ if(tmp[0] == 'D')
+ rplstr(tmp, sizeof(tmp), 9, N_("Folder"));
+ }
+
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(&add_folder[offset], -FOOTER_ROWS(ps_global), 0,
+ add_folderlen - offset, tmp,
+ ((context->dir->delim) && !maildrop)
+ ? add_key : NULL,
+ help, &flags);
+ /* use default */
+ if(rc == 0 && def_in_prompt[0] && !add_folder[offset]){
+ strncpy(&add_folder[offset], def_in_prompt, add_folderlen-offset);
+ add_folder[add_folderlen-1] = '\0';
+ }
+
+ removing_leading_and_trailing_white_space(&add_folder[offset]);
+
+ if(rc == 0 && !(inbox || context->use & CNTXT_INCMNG)
+ && check_for_move_mbox(add_folder, NULL, 0L, NULL)){
+ q_status_message(SM_ORDER, 6, 6,
+ _("#move folders may only be the INBOX or in the Incoming Collection"));
+ display_message(NO_OP_COMMAND);
+ continue;
+ }
+
+ if(rc == 0 && add_folder[offset]){
+ if(F_OFF(F_ENABLE_DOT_FOLDERS,ps_global)
+ && add_folder[offset] == '.'){
+ if(cnt++ <= 0)
+ q_status_message(SM_ORDER,3,3,
+ _("Folder name can't begin with dot"));
+ else
+ q_status_message1(SM_ORDER,3,3,
+ _("Config feature \"%s\" enables names beginning with dot"),
+ pretty_feature_name(feature_list_name(F_ENABLE_DOT_FOLDERS), -1));
+
+ display_message(NO_OP_COMMAND);
+ continue;
+ }
+
+ /* if last char is delim, blast from new folder */
+ for(p = &add_folder[offset]; *p && *(p + 1); p++)
+ ; /* find last char in folder */
+
+ if(isdir){
+ if(*p && *p != context->dir->delim){
+ *++p = context->dir->delim;
+ *(p+1) = '\0';
+ }
+
+ if(F_ON(F_QUELL_EMPTY_DIRS, ps_global))
+ require_a_subfolder++;
+ }
+ else if(*p == context->dir->delim){
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ _("Can't have trailing directory delimiters!"));
+ display_message('X');
+ continue;
+ }
+
+ break;
+ }
+
+ if(rc == 12){
+ isdir = !isdir; /* toggle directory creation */
+ }
+ else if(rc == 3){
+ helper(h_incoming_add_folder_name, _("HELP FOR FOLDER NAME "),
+ HLPD_SIMPLE);
+ }
+ else if(rc == 1 || add_folder[0] == '\0') {
+ q_status_message(SM_ORDER,0,2,
+ inbox ? _("INBOX change cancelled")
+ : _("Addition of new folder cancelled"));
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+
+ if(inbox && def){
+ if(default_mail_drop_host)
+ fs_give((void **) &default_mail_drop_host);
+
+ if(default_mail_drop_folder)
+ fs_give((void **) &default_mail_drop_folder);
+
+ if(default_dstn_host)
+ fs_give((void **) &default_dstn_host);
+
+ if(default_dstn_folder)
+ fs_give((void **) &default_dstn_folder);
+ }
+
+ return(FALSE);
+ }
+ }
+
+ if(maildrop && !maildropfolder){
+ maildropfolder = cpystr(add_folder);
+ maildrop = 0;
+ goto get_folder_name;
+ }
+
+skip_over_folder_input:
+
+ if(require_a_subfolder){
+ /* add subfolder name to directory name */
+ offset = strlen(add_folder);
+ tmp[0] = '\0';
+
+ if(offset > 0){ /* it had better be */
+ char save_delim;
+
+ save_delim = add_folder[offset-1];
+ add_folder[offset-1] = '\0';
+
+ snprintf(tmp, sizeof(tmp),
+ "Name of subfolder to add in \"%s\" : ",
+ short_str(add_folder, buf, sizeof(buf), 15, FrontDots));
+
+ tmp[sizeof(tmp)-1] = '\0';
+ add_folder[offset-1] = save_delim;
+ }
+
+ while(1){
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(&add_folder[offset], -FOOTER_ROWS(ps_global), 0,
+ add_folderlen - offset, tmp,
+ NULL, NO_HELP, &flags);
+
+ removing_leading_and_trailing_white_space(&add_folder[offset]);
+
+ /* use default */
+ if(rc == 0 && !add_folder[offset]){
+ q_status_message(SM_ORDER, 4, 4,
+ _("A subfolder name is required, there is no default subfolder name"));
+ continue;
+ }
+
+ if(rc == 0 && add_folder[offset]){
+ break;
+ }
+
+ if(rc == 3){
+ helper(h_emptydir_subfolder_name, _("HELP FOR SUBFOLDER NAME "),
+ HLPD_SIMPLE);
+ }
+ else if(rc == 1 || add_folder[0] == '\0') {
+ q_status_message(SM_ORDER,0,2, _("Addition of new folder cancelled"));
+ return(FALSE);
+ }
+ }
+
+ /* the directory is implicit now */
+ isdir = 0;
+ }
+
+ if(context == ps_global->context_list
+ && !(context->dir && context->dir->ref)
+ && !strucmp(ps_global->inbox_name, add_folder)){
+ q_status_message1(SM_ORDER,3,3,
+ _("Cannot add folder %s in current context"),
+ add_folder);
+ return(FALSE);
+ }
+
+ create_stream = sp_stream_get(context_apply(ctmp, context, add_folder,
+ sizeof(ctmp)),
+ SP_SAME);
+
+ if(!create_stream && possible_stream)
+ create_stream = context_same_stream(context, add_folder, possible_stream);
+
+ help = NO_HELP;
+ if(!inbox && context->use & CNTXT_INCMNG){
+ snprintf(tmp, sizeof(tmp), _("Nickname for folder \"%s\" : "), &add_folder[offset]);
+ tmp[sizeof(tmp)-1] = '\0';
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ rc = optionally_enter(nickname, -FOOTER_ROWS(ps_global), 0,
+ sizeof(nickname), tmp, NULL, help, &flags);
+ removing_leading_and_trailing_white_space(nickname);
+ if(rc == 0){
+ if(strucmp(ps_global->inbox_name, nickname))
+ break;
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Nickname cannot be \"%s\""), nickname);
+ }
+ }
+
+ if(rc == 3){
+ help = help == NO_HELP
+ ? h_incoming_add_folder_nickname : NO_HELP;
+ }
+ else if(rc == 1 || (rc != 3 && !*nickname)){
+ q_status_message(SM_ORDER,0,2,
+ inbox ? _("INBOX change cancelled")
+ : _("Addition of new folder cancelled"));
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+
+ if(inbox && def){
+ if(default_mail_drop_host)
+ fs_give((void **) &default_mail_drop_host);
+
+ if(default_mail_drop_folder)
+ fs_give((void **) &default_mail_drop_folder);
+
+ if(default_dstn_host)
+ fs_give((void **) &default_dstn_host);
+
+ if(default_dstn_folder)
+ fs_give((void **) &default_dstn_folder);
+ }
+
+ return(FALSE);
+ }
+ }
+
+ /*
+ * Already exist? First, make sure this name won't collide with
+ * anything else in the list. Next, quickly test to see if it
+ * the actual mailbox exists so we know any errors from
+ * context_create() are really bad...
+ */
+ for(offset = 0; offset < folder_total(FOLDERS(context)); offset++){
+ f = folder_entry(offset, FOLDERS(context));
+ if(!strucmp(FLDR_NAME(f), nickname[0] ? nickname : add_folder)){
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ _("Incoming folder \"%s\" already exists"),
+ nickname[0] ? nickname : add_folder);
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+
+ if(inbox && def){
+ if(default_mail_drop_host)
+ fs_give((void **) &default_mail_drop_host);
+
+ if(default_mail_drop_folder)
+ fs_give((void **) &default_mail_drop_folder);
+
+ if(default_dstn_host)
+ fs_give((void **) &default_dstn_host);
+
+ if(default_dstn_folder)
+ fs_give((void **) &default_dstn_folder);
+ }
+
+ return(FALSE);
+ }
+ }
+
+ ps_global->c_client_error[0] = '\0';
+ exists = folder_exists(context, add_folder);
+ if(exists == FEX_ERROR){
+ if(ps_global->c_client_error[0] != '\0')
+ q_status_message1(SM_ORDER, 3, 3, "%s",
+ ps_global->c_client_error);
+ else
+ q_status_message1(SM_ORDER, 3, 3, _("Error checking for %s"), add_folder);
+ }
+ }
+ else if(!inbox)
+ exists = FEX_NOENT;
+ else{
+ exists = FEX_ISFILE;
+ /*
+ * If inbox is a maildropfolder, try to create the destination
+ * folder. But it shouldn't cause a fatal error.
+ */
+ if(maildropfolder && (folder_exists(NULL, add_folder) == FEX_NOENT))
+ context_create(NULL, NULL, add_folder);
+ }
+
+ if(exists == FEX_ERROR
+ || (exists == FEX_NOENT
+ && !context_create(context, create_stream, add_folder)
+ && !((context->use & CNTXT_INCMNG)
+ && !context_isambig(add_folder)))){
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+
+ if(inbox && def){
+ if(default_mail_drop_host)
+ fs_give((void **) &default_mail_drop_host);
+
+ if(default_mail_drop_folder)
+ fs_give((void **) &default_mail_drop_folder);
+
+ if(default_dstn_host)
+ fs_give((void **) &default_dstn_host);
+
+ if(default_dstn_folder)
+ fs_give((void **) &default_dstn_folder);
+ }
+
+ return(FALSE); /* c-client should've reported error */
+ }
+
+ if(isdir && p) /* whack off trailing delim */
+ *p = '\0';
+
+ if(inbox || context->use & CNTXT_INCMNG){
+ char **apval;
+ char ***alval;
+
+ if(inbox){
+ apval = APVAL(&ps_global->vars[varnum], which);
+ if(apval && *apval)
+ fs_give((void **) apval);
+ }
+ else{
+ alval = ALVAL(&ps_global->vars[varnum], which);
+ if(!*alval){
+ offset = 0;
+ *alval = (char **) fs_get(2*sizeof(char *));
+ }
+ else{
+ for(offset=0; (*alval)[offset]; offset++)
+ ;
+
+ fs_resize((void **) alval, (offset + 2) * sizeof(char *));
+ }
+ }
+
+ /*
+ * If we're using a Mail Drop we have to assemble the correct
+ * c-client string to do that. Mail drop syntax looks like
+ *
+ * #move <DELIM> <FromMailbox> <DELIM> <ToMailbox>
+ *
+ * DELIM is any character which does not appear in either of
+ * the mailbox names.
+ *
+ * And then the nickname is still in front of that mess.
+ */
+ if(maildropfolder){
+ char *delims = " +-_:!|ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ char *c;
+ size_t len;
+
+ len = 5 + 2 + strlen(maildropfolder) + strlen(add_folder);
+ maildroplongname = (char *) fs_get((len+1) * sizeof(char));
+
+ for(c = delims; *c; c++){
+ if(!strindex(maildropfolder, *c) &&
+ !strindex(add_folder, *c))
+ break;
+ }
+
+ if(*c){
+ snprintf(maildroplongname, len+1, "#move%c%s%c%s",
+ *c, maildropfolder, *c, add_folder);
+ if(strlen(maildroplongname) < add_folderlen){
+ strncpy(add_folder, maildroplongname, add_folderlen);
+ add_folder[add_folderlen-1] = '\0';
+ }
+ }
+ else{
+ q_status_message2(SM_ORDER,0,2,
+ "Can't find delimiter for \"#move %s %s\"",
+ maildropfolder, add_folder);
+ dprint((2,
+ "Can't find delimiter for \"#move %s %s\"",
+ maildropfolder ? maildropfolder : "?",
+ add_folder ? add_folder : "?"));
+
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+
+ if(inbox && def){
+ if(default_mail_drop_host)
+ fs_give((void **) &default_mail_drop_host);
+
+ if(default_mail_drop_folder)
+ fs_give((void **) &default_mail_drop_folder);
+
+ if(default_dstn_host)
+ fs_give((void **) &default_dstn_host);
+
+ if(default_dstn_folder)
+ fs_give((void **) &default_dstn_folder);
+ }
+
+ return(FALSE);
+ }
+
+ if(maildroplongname)
+ fs_give((void **) &maildroplongname);
+ }
+
+ if(inbox)
+ *apval = cpystr(add_folder);
+ else{
+ (*alval)[offset] = put_pair(nickname, add_folder);
+ (*alval)[offset+1] = NULL;
+ }
+
+ set_current_val(&ps_global->vars[varnum], TRUE, FALSE);
+ if(prc)
+ prc->outstanding_pinerc_changes = 1;
+
+ if(context->use & CNTXT_INCMNG){
+ if(!inbox && add_folder && add_folder[0] && alval && *alval && (*alval)[offset]){
+ /*
+ * Instead of re-initing we try to insert the
+ * added folder so that we preserve the last_unseen_update
+ * information.
+ */
+ f = new_folder(add_folder, line_hash((*alval)[offset]));
+ f->isfolder = 1;
+ if(nickname && nickname[0]){
+ f->nickname = cpystr(nickname);
+ f->name_len = strlen(f->nickname);
+ }
+
+ if(F_ON(F_ENABLE_INCOMING_CHECKING, ps_global)
+ && !ps_global->VAR_INCCHECKLIST)
+ f->last_unseen_update = LUU_INIT;
+ else
+ f->last_unseen_update = LUU_NEVERCHK;
+
+ folder_insert(folder_total(FOLDERS(context)), f, FOLDERS(context));
+ }
+ else
+ /* re-init to make sure we got it right */
+ reinit_incoming_folder_list(ps_global, context);
+ }
+
+ if(nickname[0]){
+ strncpy(add_folder, nickname, add_folderlen-1); /* known by new name */
+ add_folder[add_folderlen-1] = '\0';
+ }
+
+ if(!inbox)
+ q_status_message1(SM_ORDER, 0, 3, "Folder \"%s\" created",
+ add_folder);
+ return_val = add_folder;
+ }
+ else if(context_isambig(add_folder)){
+ free_folder_list(context);
+ q_status_message2(SM_ORDER, 0, 3, "%s \"%s\" created",
+ isdir ? "Directory" : "Folder", add_folder);
+ return_val = add_folder;
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 3,
+ "Folder \"%s\" created outside current collection",
+ add_folder);
+
+ if(maildropfolder)
+ fs_give((void **) &maildropfolder);
+
+ if(inbox && def){
+ if(default_mail_drop_host)
+ fs_give((void **) &default_mail_drop_host);
+
+ if(default_mail_drop_folder)
+ fs_give((void **) &default_mail_drop_folder);
+
+ if(default_dstn_host)
+ fs_give((void **) &default_dstn_host);
+
+ if(default_dstn_folder)
+ fs_give((void **) &default_dstn_folder);
+ }
+
+ return(return_val != NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Subscribe to a news group
+
+ Args: folder -- last folder added
+ cntxt -- The context the subscription is for
+
+ Result: returns the name of the folder subscribed too
+
+
+This builds a complete context for the entire list of possible news groups.
+It also build a context to find the newly created news groups as
+determined by data kept in .pinerc. When the find of these new groups is
+done the subscribed context is searched and the items marked as new.
+A list of new board is never actually created.
+
+ ----*/
+int
+group_subscription(char *folder, size_t len, CONTEXT_S *cntxt)
+{
+ STRLIST_S *folders = NULL;
+ int rc = 0, last_rc, i, n, flags,
+ last_find_partial = 0, we_cancel = 0;
+ CONTEXT_S subscribe_cntxt;
+ HelpType help;
+ ESCKEY_S subscribe_keys[3];
+
+ subscribe_keys[i = 0].ch = ctrl('T');
+ subscribe_keys[i].rval = 12;
+ subscribe_keys[i].name = "^T";
+ subscribe_keys[i++].label = N_("To All Grps");
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ subscribe_keys[i].ch = ctrl('I');
+ subscribe_keys[i].rval = 11;
+ subscribe_keys[i].name = "TAB";
+ subscribe_keys[i++].label = N_("Complete");
+ }
+
+ subscribe_keys[i].ch = -1;
+
+ /*---- Build a context to find all news groups -----*/
+
+ subscribe_cntxt = *cntxt;
+ subscribe_cntxt.use |= CNTXT_FINDALL;
+ subscribe_cntxt.use &= ~(CNTXT_PSEUDO | CNTXT_ZOOM | CNTXT_PRESRV);
+ subscribe_cntxt.next = NULL;
+ subscribe_cntxt.prev = NULL;
+ subscribe_cntxt.dir = new_fdir(cntxt->dir->ref,
+ cntxt->dir->view.internal, '*');
+ FOLDERS(&subscribe_cntxt) = init_folder_entries();
+ /*
+ * Prompt for group name.
+ */
+ folder[0] = '\0';
+ help = NO_HELP;
+ while(1){
+ flags = OE_APPEND_CURRENT;
+ last_rc = rc;
+ rc = optionally_enter(folder, -FOOTER_ROWS(ps_global), 0, len,
+ SUBSCRIBE_PMT, subscribe_keys, help, &flags);
+ removing_trailing_white_space(folder);
+ removing_leading_white_space(folder);
+ if((rc == 0 && folder[0]) || rc == 11 || rc == 12){
+ we_cancel = busy_cue(_("Fetching newsgroup list"), NULL, 1);
+
+ if(last_find_partial){
+ /* clean up any previous find results */
+ free_folder_list(&subscribe_cntxt);
+ last_find_partial = 0;
+ }
+
+ if(rc == 11){ /* Tab completion! */
+ if(folder_complete_internal(&subscribe_cntxt,
+ folder, len, &n, FC_FORCE_LIST)){
+ continue;
+ }
+ else{
+ if(!(n && last_rc == 11 && !(flags & OE_USER_MODIFIED))){
+ Writechar(BELL, 0);
+ continue;
+ }
+ }
+ }
+
+ if(rc == 12){ /* list the whole enchilada */
+ build_folder_list(NULL, &subscribe_cntxt, NULL, NULL,BFL_NONE);
+ }
+ else if(strlen(folder)){
+ char tmp[MAILTMPLEN];
+
+ snprintf(tmp, sizeof(tmp), "%s%.*s*", (rc == 11) ? "" : "*",
+ sizeof(tmp)-3, folder);
+ tmp[sizeof(tmp)-1] = '\0';
+ build_folder_list(NULL, &subscribe_cntxt, tmp, NULL, BFL_NONE);
+ subscribe_cntxt.dir->status &= ~(CNTXT_PARTFIND|CNTXT_NOFIND);
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 2,
+ _("No group substring to match! Use ^T to list all news groups."));
+ continue;
+ }
+
+ /*
+ * If we did a partial find on matches, then we faked a full
+ * find which will cause this to just return.
+ */
+ if((i = folder_total(FOLDERS(&subscribe_cntxt))) != 0){
+ char *f;
+
+ /*
+ * fake that we've found everything there is to find...
+ */
+ subscribe_cntxt.use &= ~(CNTXT_NOFIND|CNTXT_PARTFIND);
+ last_find_partial = 1;
+
+ if(i == 1){
+ f = folder_entry(0, FOLDERS(&subscribe_cntxt))->name;
+ if(!strcmp(f, folder)){
+ rc = 1; /* success! */
+ break;
+ }
+ else{ /* else complete the group */
+ strncpy(folder, f, len-1);
+ folder[len-1] = '\0';
+ continue;
+ }
+ }
+ else if(!(flags & OE_USER_MODIFIED)){
+ /*
+ * See if there wasn't an exact match in the lot.
+ */
+ while(i-- > 0){
+ f = folder_entry(i,FOLDERS(&subscribe_cntxt))->name;
+ if(!strcmp(f, folder))
+ break;
+ else
+ f = NULL;
+ }
+
+ /* if so, then the user picked it from the list the
+ * last time and didn't change it at the prompt.
+ * Must mean they're accepting it...
+ */
+ if(f){
+ rc = 1; /* success! */
+ break;
+ }
+ }
+ }
+ else{
+ if(rc == 12)
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("No groups to select from!"));
+ else
+ q_status_message1(SM_ORDER, 3, 3,
+ _("News group \"%s\" didn't match any existing groups"),
+ folder);
+ free_folder_list(&subscribe_cntxt);
+
+ continue;
+ }
+
+ /*----- Mark groups that are currently subscribed too ------*/
+ /* but first make sure they're found */
+ build_folder_list(NULL, cntxt, "*", NULL, BFL_LSUB);
+ for(i = 0 ; i < folder_total(FOLDERS(&subscribe_cntxt)); i++) {
+ FOLDER_S *f = folder_entry(i, FOLDERS(&subscribe_cntxt));
+
+ f->subscribed = search_folder_list(FOLDERS(cntxt),
+ f->name) != 0;
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ /*----- Call the folder lister to do all the work -----*/
+ folders = folders_for_subscribe(ps_global,
+ &subscribe_cntxt, folder);
+
+ if(folders){
+ /* Multiple newsgroups OR Auto-select */
+ if(folders->next || F_ON(F_SELECT_WO_CONFIRM,ps_global))
+ break;
+
+ strncpy(folder, (char *) folders->name, len-1);
+ folder[len-1] = '\0';
+ free_strlist(&folders);
+ }
+ }
+ else if(rc == 3){
+ help = help == NO_HELP ? h_news_subscribe : NO_HELP;
+ }
+ else if(rc == 1 || folder[0] == '\0'){
+ rc = -1;
+ break;
+ }
+ }
+
+ if(rc < 0){
+ folder[0] = '\0'; /* make sure not to return partials */
+ if(rc == -1)
+ q_status_message(SM_ORDER, 0, 3, _("Subscribe cancelled"));
+ }
+ else{
+ MAILSTREAM *sub_stream;
+ int sclose = 0, errors = 0;
+
+ if(folders){ /*------ Actually do the subscription -----*/
+ STRLIST_S *flp;
+ int n = 0;
+
+ /* subscribe one at a time */
+ folder[0] = '\0';
+ /*
+ * Open stream before subscribing so c-client knows what newsrc
+ * to use, along with other side-effects.
+ */
+ if((sub_stream = mail_cmd_stream(&subscribe_cntxt, &sclose)) != NULL){
+ for(flp = folders; flp; flp = flp->next){
+ (void) context_apply(tmp_20k_buf, &subscribe_cntxt,
+ (char *) flp->name, SIZEOF_20KBUF);
+ if(mail_subscribe(sub_stream, tmp_20k_buf) == 0L){
+ /*
+ * This message may not make it to the screen,
+ * because a c-client message about the failure
+ * will be there. Probably best not to string
+ * together a whole bunch of errors if there is
+ * something wrong.
+ */
+ q_status_message1(errors ?SM_INFO : SM_ORDER,
+ errors ? 0 : 3, 3,
+ _("Error subscribing to \"%s\""),
+ (char *) flp->name);
+ errors++;
+ }
+ else{
+ n++;
+ if(!folder[0]){
+ strncpy(folder, (char *) flp->name, len-1);
+ folder[len-1] = '\0';
+ }
+
+ /*---- Update the screen display data structures -----*/
+ if(ALL_FOUND(cntxt)){
+ if(cntxt->use & CNTXT_PSEUDO){
+ folder_delete(0, FOLDERS(cntxt));
+ cntxt->use &= ~CNTXT_PSEUDO;
+ }
+
+ folder_insert(-1, new_folder((char *) flp->name, 0),
+ FOLDERS(cntxt));
+ }
+ }
+ }
+ if(sclose)
+ pine_mail_close(sub_stream);
+ }
+ else
+ errors++;
+
+ if(n == 0)
+ q_status_message(SM_ORDER | SM_DING, 3, 5,
+ _("Subscriptions failed, subscribed to no new groups"));
+ else
+ q_status_message3(SM_ORDER | (errors ? SM_DING : 0),
+ errors ? 3 : 0,3,
+ "Subscribed to %s new groups%s%s",
+ comatose((long)n),
+ errors ? ", failed on " : "",
+ errors ? comatose((long)errors) : "");
+
+ free_strlist(&folders);
+ }
+ else{
+ if((sub_stream = mail_cmd_stream(&subscribe_cntxt, &sclose)) != NULL){
+ (void) context_apply(tmp_20k_buf, &subscribe_cntxt, folder,
+ SIZEOF_20KBUF);
+ if(mail_subscribe(sub_stream, tmp_20k_buf) == 0L){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error subscribing to \"%s\""), folder);
+ errors++;
+ }
+ else if(ALL_FOUND(cntxt)){
+ /*---- Update the screen display data structures -----*/
+ if(cntxt->use & CNTXT_PSEUDO){
+ folder_delete(0, FOLDERS(cntxt));
+ cntxt->use &= ~CNTXT_PSEUDO;
+ }
+
+ folder_insert(-1, new_folder(folder, 0), FOLDERS(cntxt));
+ }
+ if(sclose)
+ pine_mail_close(sub_stream);
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error subscribing to \"%s\""), folder);
+ errors++;
+ }
+ }
+
+ if(!errors && folder[0])
+ q_status_message1(SM_ORDER, 0, 3, _("Subscribed to \"%s\""), folder);
+ }
+
+ free_fdir(&subscribe_cntxt.dir, 1);
+ return(folder[0]);
+}
+
+
+/*----------------------------------------------------------------------
+ Rename folder
+
+ Args: new_name -- buffer to contain new file name
+ len -- length of new_name buffer
+ index -- index of folder in folder list to rename
+ context -- collection of folders making up folder list
+ possible_stream -- may be able to use this stream
+
+ Result: returns the new name of the folder, or NULL if nothing happened.
+
+ When either the sent-mail or saved-message folders are renamed, immediately
+create a new one in their place so they always exist. The main loop above also
+detects this and makes the rename look like an add of the sent-mail or
+saved-messages folder. (This behavior may not be optimal, but it keeps things
+consistent.
+
+ ----*/
+int
+rename_folder(CONTEXT_S *context, int index, char *new_name, size_t len, MAILSTREAM *possible_stream)
+{
+ char *folder, prompt[64], *name_p = NULL;
+ HelpType help;
+ FOLDER_S *new_f;
+ PINERC_S *prc = NULL;
+ int rc, ren_cur, cnt = 0, isdir = 0, readonly = 0;
+ EditWhich ew;
+ MAILSTREAM *strm;
+
+ dprint((4, "\n - rename folder -\n"));
+
+ if(NEWS_TEST(context)){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Can't rename news groups!"));
+ return(0);
+ }
+ else if(!folder_total(FOLDERS(context))){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ _("Empty folder collection. No folder to rename!"));
+ return(0);
+ }
+ else if((new_f = folder_entry(index, FOLDERS(context)))
+ && context == ps_global->context_list
+ && !(context->dir && context->dir->ref)
+ && !strucmp(FLDR_NAME(new_f), ps_global->inbox_name)) {
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Can't change special folder name \"%s\""),
+ ps_global->inbox_name);
+ return(0);
+ }
+
+ ew = config_containing_inc_fldr(new_f);
+ if(ps_global->restricted)
+ readonly = 1;
+ else{
+ switch(ew){
+ case Main:
+ prc = ps_global->prc;
+ break;
+ case Post:
+ prc = ps_global->post_prc;
+ break;
+ case None:
+ break;
+ }
+
+ readonly = prc ? prc->readonly : 1;
+ }
+
+ if(prc && prc->quit_to_edit && (context->use & CNTXT_INCMNG)){
+ quit_to_edit_msg(prc);
+ return(0);
+ }
+
+ if(readonly && (context->use & CNTXT_INCMNG)){
+ q_status_message(SM_ORDER,3,5,
+ _("Rename cancelled: folder not in editable config file"));
+ return(0);
+ }
+
+ if(context->use & CNTXT_INCMNG){
+ if(!(folder = new_f->nickname))
+ folder = ""; /* blank nickname */
+ }
+ else
+ folder = FLDR_NAME(new_f);
+
+ ren_cur = strcmp(folder, ps_global->cur_folder) == 0;
+
+ snprintf(prompt, sizeof(prompt), "%s %s to : ", _("Rename"),
+ (context->use & CNTXT_INCMNG)
+ ? _("nickname")
+ : (isdir = new_f->isdir)
+ ? _("directory") : _("folder"));
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ strncpy(new_name, folder, len-1);
+ new_name[len-1] = '\0';
+ while(1) {
+ int flags = OE_APPEND_CURRENT;
+
+ rc = optionally_enter(new_name, -FOOTER_ROWS(ps_global), 0,
+ len, prompt, NULL, help, &flags);
+ if(rc == 3) {
+ help = help == NO_HELP ? h_oe_foldrename : NO_HELP;
+ continue;
+ }
+
+ removing_leading_and_trailing_white_space(new_name);
+
+ if(rc == 0 && (*new_name || (context->use & CNTXT_INCMNG))) {
+ /* verify characters */
+ if(F_OFF(F_ENABLE_DOT_FOLDERS,ps_global) && *new_name == '.'){
+ if(cnt++ <= 0)
+ q_status_message(SM_ORDER,3,3,
+ _("Folder name can't begin with dot"));
+ else
+ q_status_message1(SM_ORDER,3,3,
+ _("Config feature \"s\" enables names beginning with dot"),
+ pretty_feature_name(feature_list_name(F_ENABLE_DOT_FOLDERS), -1));
+
+ display_message(NO_OP_COMMAND);
+ continue;
+ }
+
+ if(folder_index(new_name, context, FI_ANY|FI_RENAME) >= 0){
+ q_status_message1(SM_ORDER, 3, 3,
+ _("Folder \"%s\" already exists"),
+ pretty_fn(new_name));
+ display_message(NO_OP_COMMAND);
+ continue;
+ }
+ else if(context == ps_global->context_list
+ && !(context->dir && context->dir->ref)
+ && !strucmp(new_name, ps_global->inbox_name)){
+ if(context->use & CNTXT_INCMNG)
+ q_status_message1(SM_ORDER, 3, 3, _("Can't rename incoming folder to %s"),
+ new_name);
+ else
+ q_status_message1(SM_ORDER, 3, 3, _("Can't rename folder to %s"),
+ new_name);
+
+ display_message(NO_OP_COMMAND);
+ continue;
+ }
+ }
+
+ if(rc != 4) /* redraw */
+ break; /* no redraw */
+
+ }
+
+ if(rc != 1 && isdir){ /* add trailing delim? */
+ for(name_p = new_name; *name_p && *(name_p+1) ; name_p++)
+ ;
+
+ if(*name_p == context->dir->delim) /* lop off delim */
+ *name_p = '\0';
+ }
+
+ if(rc == 1
+ || !(*new_name || (context->use & CNTXT_INCMNG))
+ || !strcmp(new_name, folder)){
+ q_status_message(SM_ORDER, 0, 2, _("Folder rename cancelled"));
+ return(0);
+ }
+
+ if(context->use & CNTXT_INCMNG){
+ char **new_list, **lp, ***alval;
+ int i;
+
+ alval = ALVAL(&ps_global->vars[V_INCOMING_FOLDERS], ew);
+ for(i = 0; (*alval)[i]; i++)
+ ;
+
+ new_list = (char **) fs_get((i + 1) * sizeof(char *));
+
+ for(lp = new_list, i = 0; (*alval)[i]; i++){
+ /* figure out if this is the one we're renaming */
+ expand_variables(tmp_20k_buf, SIZEOF_20KBUF, (*alval)[i], 0);
+ if(new_f->varhash == line_hash(tmp_20k_buf)){
+ char *folder_string = NULL, *nickname = NULL;
+
+ if(new_f->nickname)
+ fs_give((void **) &new_f->nickname);
+
+ if(*new_name)
+ new_f->nickname = cpystr(new_name);
+
+ /*
+ * Parse folder line for nickname and folder name.
+ * No nickname on line is OK.
+ */
+ get_pair(tmp_20k_buf, &nickname, &folder_string, 0, 0);
+
+ if(nickname)
+ fs_give((void **)&nickname);
+
+ *lp = put_pair(new_name, folder_string);
+
+ new_f->varhash = line_hash(*lp++);
+ }
+ else
+ *lp++ = cpystr((*alval)[i]);
+ }
+
+ *lp = NULL;
+
+ set_variable_list(V_INCOMING_FOLDERS, new_list, TRUE, ew);
+ free_list_array(&new_list);
+
+ return(1);
+ }
+
+ /* Can't rename open streams */
+ if((strm = context_already_open_stream(context, folder, AOS_NONE))
+ || (ren_cur && (strm=ps_global->mail_stream))){
+ if(possible_stream == strm)
+ possible_stream = NULL;
+
+ pine_mail_actually_close(strm);
+ }
+
+ if(possible_stream
+ && !context_same_stream(context, new_name, possible_stream))
+ possible_stream = NULL;
+
+ if((rc = context_rename(context, possible_stream, folder, new_name)) != 0){
+ if(name_p && *name_p == context->dir->delim)
+ *name_p = '\0'; /* blat trailing delim */
+
+ /* insert new name? */
+ if(!strindex(new_name, context->dir->delim)){
+ new_f = new_folder(new_name, 0);
+ new_f->isdir = isdir;
+ folder_insert(-1, new_f, FOLDERS(context));
+ }
+
+ if(strcmp(ps_global->VAR_DEFAULT_FCC, folder) == 0
+ || strcmp(ps_global->VAR_DEFAULT_SAVE_FOLDER, folder) == 0) {
+ /* renaming sent-mail or saved-messages */
+ if(context_create(context, NULL, folder)){
+ q_status_message3(SM_ORDER,0,3,
+ "Folder \"%s\" renamed to \"%s\". New \"%s\" created",
+ folder, new_name,
+ pretty_fn(
+ (strcmp(ps_global->VAR_DEFAULT_SAVE_FOLDER,
+ folder) == 0)
+ ? ps_global->VAR_DEFAULT_SAVE_FOLDER
+ : ps_global->VAR_DEFAULT_FCC));
+
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "Error creating new \"%s\"", folder);
+
+ dprint((1, "Error creating \"%s\" in %s context\n",
+ folder ? folder : "?",
+ context->context ? context->context : "?"));
+ }
+ }
+ else
+ q_status_message2(SM_ORDER, 0, 3,
+ "Folder \"%s\" renamed to \"%s\"",
+ pretty_fn(folder), pretty_fn(new_name));
+
+ free_folder_list(context);
+ }
+
+ if(ren_cur) {
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ /* No reopen the folder we just had open */
+ do_broach_folder(new_name, context, NULL, 0L);
+ }
+
+ return(rc);
+}
+
+
+/*----------------------------------------------------------------------
+ Confirm and delete a folder
+
+ Args: fs -- folder screen state
+
+ Result: return 0 if not delete, 1 if deleted.
+ ----*/
+int
+delete_folder(CONTEXT_S *context, int index, char *next_folder, size_t len, MAILSTREAM **possible_streamp)
+{
+ char *folder, ques_buf[MAX_SCREEN_COLS+1], *target = NULL,
+ *fnamep, buf[1000];
+ MAILSTREAM *del_stream = NULL, *sub_stream, *strm = NULL;
+ FOLDER_S *fp;
+ EditWhich ew;
+ PINERC_S *prc = NULL;
+ int ret, unsub_opened = 0, close_opened = 0, blast_folder = 1,
+ readonly;
+
+ if(!context){
+ cmd_cancelled("Missing context in Delete");
+ return(0);
+ }
+
+ if(NEWS_TEST(context)){
+ folder = folder_entry(index, FOLDERS(context))->name;
+ snprintf(ques_buf, sizeof(ques_buf), _("Really unsubscribe from \"%s\""), folder);
+ ques_buf[sizeof(ques_buf)-1] = '\0';
+
+ ret = want_to(ques_buf, 'n', 'x', NO_HELP, WT_NORM);
+ switch(ret) {
+ /* ^C */
+ case 'x':
+ Writechar(BELL, 0);
+ /* fall through */
+ case 'n':
+ return(0);
+ }
+
+ dprint((2, "deleting folder \"%s\" in context \"%s\"\n",
+ folder ? folder : "?",
+ context->context ? context->context : "?"));
+
+ if((sub_stream = mail_cmd_stream(context, &unsub_opened)) != NULL){
+ (void) context_apply(tmp_20k_buf, context, folder, SIZEOF_20KBUF);
+ if(!mail_unsubscribe(sub_stream, tmp_20k_buf)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error unsubscribing from \"%s\""),folder);
+ if(unsub_opened)
+ pine_mail_close(sub_stream);
+ return(0);
+ }
+ if(unsub_opened)
+ pine_mail_close(sub_stream);
+ }
+
+ /*
+ * Fix up the displayed list
+ */
+ folder_delete(index, FOLDERS(context));
+ return(1);
+ }
+
+ fp = folder_entry(index, FOLDERS(context));
+ if(!fp){
+ cmd_cancelled("Can't find folder to Delete!");
+ return(0);
+ }
+
+ if(!((context->use & CNTXT_INCMNG) && fp->name
+ && check_for_move_mbox(fp->name, NULL, 0, &target))){
+ target = NULL;
+ }
+
+ folder = FLDR_NAME(fp);
+ dprint((4, "=== delete_folder(%s) ===\n",
+ folder ? folder : "?"));
+
+ ew = config_containing_inc_fldr(fp);
+ if(ps_global->restricted)
+ readonly = 1;
+ else{
+ switch(ew){
+ case Main:
+ prc = ps_global->prc;
+ break;
+ case Post:
+ prc = ps_global->post_prc;
+ break;
+ case None:
+ break;
+ }
+
+ readonly = prc ? prc->readonly : 1;
+ }
+
+ if(prc && prc->quit_to_edit && (context->use & CNTXT_INCMNG)){
+ quit_to_edit_msg(prc);
+ return(0);
+ }
+
+ if(context == ps_global->context_list
+ && !(context->dir && context->dir->ref)
+ && strucmp(folder, ps_global->inbox_name) == 0){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Can't delete special folder \"%s\"."),
+ ps_global->inbox_name);
+ return(0);
+ }
+ else if(readonly && (context->use & CNTXT_INCMNG)){
+ q_status_message(SM_ORDER,3,5,
+ _("Deletion cancelled: folder not in editable config file"));
+ return(0);
+ }
+ else if((fp->name
+ && (strm=context_already_open_stream(context,fp->name,AOS_NONE)))
+ ||
+ (target
+ && (strm=context_already_open_stream(NULL,target,AOS_NONE)))){
+ if(strm == ps_global->mail_stream)
+ close_opened++;
+ }
+ else if(fp->isdir || fp->isdual){ /* NO DELETE if directory isn't EMPTY */
+ FDIR_S *fdirp = next_folder_dir(context,folder,TRUE,possible_streamp);
+
+ if(fp->haschildren)
+ ret = 1;
+ else if(fp->hasnochildren)
+ ret = 0;
+ else{
+ ret = folder_total(fdirp->folders) > 0;
+ free_fdir(&fdirp, 1);
+ }
+
+ if(ret){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Can't delete non-empty directory \"%s\"%s."),
+ folder, (fp->isfolder && fp->isdual) ? " (or folder of same name)" : "");
+ return(0);
+ }
+
+ /*
+ * Folder by the same name exist? If so, server's probably going
+ * to delete it as well. Punt?
+ */
+ if(fp->isdual
+ && (ret = want_to(DIR_FOLD_PMT,'n','x',NO_HELP,WT_NORM)) != 'y'){
+ q_status_message(SM_ORDER,0,3, (ret == 'x') ? _("Delete cancelled")
+ : _("No folder deleted"));
+ return(0);
+ }
+ }
+
+ if(context->use & CNTXT_INCMNG){
+ static ESCKEY_S delf_opts[] = {
+ {'n', 'n', "N", N_("Nickname only")},
+ {'b', 'b', "B", N_("Both Folder and Nickname")},
+ {-1, 0, NULL, NULL}
+ };
+#define DELF_PROMPT _("DELETE only Nickname or Both nickname and folder? ")
+
+ switch(radio_buttons(DELF_PROMPT, -FOOTER_ROWS(ps_global),
+ delf_opts,'n','x',NO_HELP,RB_NORM)){
+ case 'n' :
+ blast_folder = 0;
+ break;
+
+ case 'x' :
+ cmd_cancelled("Delete");
+ return(0);
+
+ default :
+ break;
+ }
+ }
+ else{
+ unsigned char *fname = folder_name_decoded((unsigned char *)folder);
+ snprintf(ques_buf, sizeof(ques_buf), "DELETE \"%s\"%s", fname ? (char *) fname : folder,
+ close_opened ? " (the currently open folder)" :
+ (fp->isdir && !(fp->isdual || fp->isfolder
+ || (folder_index(folder, context, FI_FOLDER) >= 0)))
+ ? " (a directory)" : "");
+ if(fname) fs_give((void **)&fname);
+ ques_buf[sizeof(ques_buf)-1] = '\0';
+
+ if((ret = want_to(ques_buf, 'n', 'x', NO_HELP, WT_NORM)) != 'y'){
+ q_status_message(SM_ORDER,0,3, (ret == 'x') ? _("Delete cancelled")
+ : _("Nothing deleted"));
+ return(0);
+ }
+ }
+
+ if(blast_folder){
+ dprint((2,"deleting \"%s\" (%s) in context \"%s\"\n",
+ fp->name ? fp->name : "?",
+ fp->nickname ? fp->nickname : "",
+ context->context ? context->context : "?"));
+ if(strm){
+ /*
+ * Close it, NULL the pointer, and let do_broach_folder fixup
+ * the rest...
+ */
+ pine_mail_actually_close(strm);
+ if(close_opened){
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ ps_global->mangled_header = 1;
+ do_broach_folder(ps_global->inbox_name,
+ ps_global->context_list, NULL, DB_INBOXWOCNTXT);
+ }
+ }
+
+ /*
+ * Use fp->name since "folder" may be a nickname...
+ */
+ if(possible_streamp && *possible_streamp
+ && context_same_stream(context, fp->name, *possible_streamp))
+ del_stream = *possible_streamp;
+
+ fnamep = fp->name;
+
+ if(!context_delete(context, del_stream, fnamep)){
+/*
+ * BUG: what if sent-mail or saved-messages????
+ */
+ q_status_message1(SM_ORDER,3,3,"Delete of \"%s\" Failed!", folder);
+ return(0);
+ }
+ }
+
+ snprintf(buf, sizeof(buf), "%s\"%s\" deleted.",
+ !blast_folder ? "Nickname " :
+ fp->isdual ? "Folder/Directory " :
+ (fp->isdir && fp->isfolder) ? "Folder " :
+ fp->isdir ? "Directory " :
+ "Folder ",
+ folder);
+ buf[sizeof(buf)-1] = '\0';
+
+ q_status_message(SM_ORDER, 0, 3, buf);
+
+ if(context->use & CNTXT_INCMNG){
+ char **new_list, **lp, ***alval;
+ int i;
+
+ alval = ALVAL(&ps_global->vars[V_INCOMING_FOLDERS], ew);
+ for(i = 0; (*alval)[i]; i++)
+ ;
+
+ /*
+ * Make it one too big in case we don't find the match for
+ * some unknown reason.
+ */
+ new_list = (char **) fs_get((i + 1) * sizeof(char *));
+
+ /*
+ * Copy while figuring out which one to skip.
+ */
+ for(lp = new_list, i = 0; (*alval)[i]; i++){
+ expand_variables(tmp_20k_buf, SIZEOF_20KBUF, (*alval)[i], 0);
+ if(fp->varhash != line_hash(tmp_20k_buf))
+ *lp++ = cpystr((*alval)[i]);
+ }
+
+ *lp = NULL;
+
+ set_variable_list(V_INCOMING_FOLDERS, new_list, TRUE, ew);
+ free_list_array(&new_list);
+ }
+
+ /*
+ * Fix up the displayed list.
+ */
+ folder_delete(index, FOLDERS(context));
+
+ /*
+ * Take a guess at what should get hilited next.
+ */
+ if(index < (ret = folder_total(FOLDERS(context)))
+ || ((index = ret - 1) >= 0)){
+ if((fp = folder_entry(index, FOLDERS(context)))
+ && strlen(FLDR_NAME(fp)) < len - 1)
+ strncpy(next_folder, FLDR_NAME(fp), len-1);
+ next_folder[len-1] = '\0';
+ }
+
+ if(!(context->use & CNTXT_INCMNG)){
+ /*
+ * Then cause the list to get rebuild 'cause we may've blasted
+ * some folder that's also a directory or vice versa...
+ */
+ free_folder_list(context);
+ }
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Print the list of folders on paper
+
+ Args: list -- The current list of folders
+ lens -- The list of lengths of the current folders
+ display -- The current folder display structure
+
+ Result: list printed on paper
+
+If the display list was created for 80 columns it is used, otherwise
+a new list is created for 80 columns
+
+ ----*/
+void
+print_folders(FPROC_S *fp)
+{
+ if(open_printer(_("folder list")) == 0){
+ (void) folder_list_text(ps_global, fp, print_char, NULL, 80);
+
+ close_printer();
+ }
+}
+
+
+int
+scan_get_pattern(char *kind, char *pat, int len)
+{
+ char prompt[256];
+ int flags;
+
+ pat[0] = '\0';
+ snprintf(prompt, sizeof(prompt), _("String in folder %s to match : "), kind);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ while(1){
+ flags = OE_APPEND_CURRENT | OE_DISALLOW_HELP;
+ switch(optionally_enter(pat, -FOOTER_ROWS(ps_global), 0, len,
+ prompt, NULL, NO_HELP, &flags)){
+
+ case 4 :
+ if(ps_global->redrawer)
+ (*ps_global->redrawer)();
+
+ break;
+
+ case 0 :
+ if(*pat)
+ return(1);
+
+ case 1 :
+ cmd_cancelled("Select");
+
+ default :
+ return(0);
+ }
+ }
+}
+
+
+int
+folder_select_text(struct pine *ps, CONTEXT_S *context, int selected)
+{
+ char pattern[MAILTMPLEN], type = '\0';
+ static ESCKEY_S scan_opts[] = {
+ {'n', 'n', "N", N_("Name Select")},
+ {'c', 'c', "C", N_("Content Select")},
+ {-1, 0, NULL, NULL}
+ };
+
+ if(context->use & CNTXT_INCMNG){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Select \"Text\" not supported in Incoming Folders"));
+ return(0);
+ }
+
+ switch(radio_buttons(SEL_TEXT_PMT, -FOOTER_ROWS(ps_global),
+ scan_opts, 'n', 'x', NO_HELP, RB_NORM)){
+ case 'n' : /* Name search */
+ if(scan_get_pattern("NAME", pattern, sizeof(pattern)))
+ type = 'n';
+
+ break;
+
+ case 'c' : /* content search */
+ if(scan_get_pattern("CONTENTS", pattern, sizeof(pattern)))
+ type = 'c';
+
+ break;
+
+ case 'x' :
+ default :
+ break;
+ }
+
+ if(type){
+ int rv;
+ char tmp[MAILTMPLEN];
+ SCANARG_S args;
+
+ memset(&args, 0, sizeof(SCANARG_S));
+ args.pattern = pattern;
+ args.type = type;
+ args.context = context;
+
+ if(type == 'c'){
+ args.stream = sp_stream_get(context_apply(tmp, context,
+ "xxx", sizeof(tmp)),
+ SP_SAME);
+ if(!args.stream){
+ args.stream = pine_mail_open(NULL, tmp,
+ OP_SILENT|OP_HALFOPEN|SP_USEPOOL|SP_TEMPUSE,
+ NULL);
+ args.newstream = (args.stream != NULL);
+ }
+ }
+
+ rv = foreach_folder(context, selected,
+ foreach_do_scan, (void *) &args);
+
+ if(args.newstream)
+ pine_mail_close(args.stream);
+
+ if(rv)
+ return(1);
+ }
+
+ cmd_cancelled("Select");
+ return(0);
+}
+
+
+int
+foreach_do_scan(FOLDER_S *f, void *d)
+{
+ SCANARG_S *sa = (SCANARG_S *) d;
+
+ return((sa->type == 'n' && srchstr(FLDR_NAME(f), sa->pattern))
+ || (sa->type == 'c'
+ && scan_scan_folder(sa->stream, sa->context, f, sa->pattern)));
+}
+
+
+int
+scan_scan_folder(MAILSTREAM *stream, CONTEXT_S *context, FOLDER_S *f, char *pattern)
+{
+ MM_LIST_S ldata;
+ LISTRES_S response;
+ int we_cancel = 0;
+ char *folder, *ref = NULL, tmp[MAILTMPLEN];
+
+ folder = f->name;
+ snprintf(tmp, sizeof(tmp), "Scanning \"%s\"", FLDR_NAME(f));
+ tmp[sizeof(tmp)-1] = '\0';
+ we_cancel = busy_cue(tmp, NULL, 1);
+
+ mm_list_info = &ldata; /* tie down global reference */
+ memset(&ldata, 0, sizeof(MM_LIST_S));
+ ldata.filter = mail_list_response;
+ memset(ldata.data = &response, 0, sizeof(LISTRES_S));
+
+ /*
+ * If no preset reference string, must be at top of context
+ */
+ if(context && context_isambig(folder) && !(ref = context->dir->ref)){
+ char *p;
+
+ if((p = strstr(context->context, "%s")) != NULL){
+ if(!*(p+2)){
+ snprintf(tmp, sizeof(tmp), "%.*s", MIN(p - context->context, sizeof(tmp)-1),
+ context->context);
+ tmp[sizeof(tmp)-1] = '\0';
+ ref = tmp;
+ }
+ else{
+ snprintf(tmp, sizeof(tmp), context->context, folder);
+ tmp[sizeof(tmp)-1] = '\0';
+ folder = tmp;
+ ref = "";
+ }
+ }
+ else
+ ref = context->context;
+ }
+
+ mail_scan(stream, ref, folder, pattern);
+
+ if(context && context->dir && response.delim)
+ context->dir->delim = response.delim;
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return(((response.isfile) ? FEX_ISFILE : 0)
+ | ((response.isdir) ? FEX_ISDIR : 0));
+}
+
+
+int
+folder_select_props(struct pine *ps, CONTEXT_S *context, int selected)
+{
+ int cmp = 0;
+ long flags = 0L, count;
+ static ESCKEY_S prop_opts[] = {
+ {'u', 'u', "U", N_("Unseen msgs")},
+ {'n', 'n', "N", N_("New msgs")},
+ {'c', 'c', "C", N_("msg Count")},
+ {-1, 0, NULL, NULL}
+ };
+
+ if(context->use & CNTXT_INCMNG){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Select \"Properties\" not supported in Incoming Folders"));
+ return(0);
+ }
+
+ switch(radio_buttons(SEL_PROP_PMT, -FOOTER_ROWS(ps_global),
+ prop_opts, 'n', 'x', h_folder_prop, RB_NORM)){
+ case 'c' : /* message count */
+ if(folder_select_count(&count, &cmp))
+ flags = SA_MESSAGES;
+
+ break;
+
+ case 'n' : /* folders with new */
+ flags = SA_RECENT;
+ break;
+
+ case 'u' : /* folders with unseen */
+ flags = SA_UNSEEN;
+ break;
+
+ case 'x' :
+ default :
+ break;
+ }
+
+ if(flags){
+ int rv;
+ char tmp[MAILTMPLEN];
+ STATARG_S args;
+
+ memset(&args, 0, sizeof(STATARG_S));
+ args.flags = flags;
+ args.context = context;
+ args.nmsgs = count;
+ args.cmp = cmp;
+
+ args.stream = sp_stream_get(context_apply(tmp, context,
+ "xxx", sizeof(tmp)),
+ SP_SAME);
+ if(!args.stream){
+ args.stream = pine_mail_open(NULL, tmp,
+ OP_SILENT|OP_HALFOPEN|SP_USEPOOL|SP_TEMPUSE,
+ NULL);
+ args.newstream = (args.stream != NULL);
+ }
+
+ rv = foreach_folder(context, selected,
+ foreach_do_stat, (void *) &args);
+
+ if(args.newstream)
+ pine_mail_close(args.stream);
+
+ if(rv)
+ return(1);
+ }
+
+ cmd_cancelled("Select");
+ return(0);
+}
+
+
+int
+folder_select_count(long int *count, int *cmp)
+{
+ int r, flags;
+ char number[32], prompt[128];
+ static char *tense[] = {"EQUAL TO", "LESS THAN", "GREATER THAN"};
+ static ESCKEY_S sel_num_opt[] = {
+ {ctrl('W'), 14, "^W", N_("Toggle Comparison")},
+ {-1, 0, NULL, NULL}
+ };
+
+ *count = 0L;
+ while(1){
+ flags = OE_APPEND_CURRENT | OE_DISALLOW_HELP;
+ snprintf(number, sizeof(number), "%ld", *count);
+ number[sizeof(number)-1] = '\0';
+ snprintf(prompt, sizeof(prompt), "Select folders with messages %s : ", tense[*cmp]);
+ prompt[sizeof(prompt)-1] = '\0';
+ r = optionally_enter(number, -FOOTER_ROWS(ps_global), 0, sizeof(number),
+ prompt, sel_num_opt, NO_HELP, &flags);
+ switch (r){
+ case 0 :
+ if(!*number)
+ break;
+ else if((*count = atol(number)) < 0L)
+ q_status_message(SM_ORDER, 3, 3,
+ "Can't have NEGATIVE message count!");
+ else
+ return(1); /* success */
+
+ case 3 : /* help */
+ case 4 : /* redraw */
+ continue;
+
+ case 14 : /* toggle comparison */
+ *cmp = ++(*cmp) % 3;
+ continue;
+
+ case -1 : /* cancel */
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ return(0); /* return failure */
+}
+
+
+int
+foreach_do_stat(FOLDER_S *f, void *d)
+{
+ STATARG_S *sa = (STATARG_S *) d;
+ int rv = 0;
+
+ if(ps_global->context_current == sa->context
+ && !strcmp(ps_global->cur_folder, FLDR_NAME(f))){
+ switch(sa->flags){
+ case SA_MESSAGES :
+ switch(sa->cmp){
+ case 0 : /* equals */
+ if(ps_global->mail_stream->nmsgs == sa->nmsgs)
+ rv = 1;
+
+ break;
+
+ case 1 : /* less than */
+ if(ps_global->mail_stream->nmsgs < sa->nmsgs)
+ rv = 1;
+
+ break;
+
+ case 2 :
+ if(ps_global->mail_stream->nmsgs > sa->nmsgs)
+ rv = 1;
+
+ break;
+
+ default :
+ break;
+ }
+
+ break;
+
+ case SA_RECENT :
+ if(count_flagged(ps_global->mail_stream, F_RECENT))
+ rv = 1;
+
+ break;
+
+ case SA_UNSEEN :
+ if(count_flagged(ps_global->mail_stream, F_UNSEEN))
+ rv = 1;
+
+ break;
+
+ default :
+ break;
+ }
+ }
+ else{
+ int we_cancel = 0;
+ char msg_buf[MAX_BM+1];
+ extern MAILSTATUS mm_status_result;
+
+ snprintf(msg_buf, sizeof(msg_buf), "Checking %s for %s", FLDR_NAME(f),
+ (sa->flags == SA_UNSEEN)
+ ? "unseen messages"
+ : (sa->flags == SA_MESSAGES) ? "message count"
+ : "recent messages");
+ msg_buf[sizeof(msg_buf)-1] = '\0';
+ we_cancel = busy_cue(msg_buf, NULL, 0);
+
+ if(!context_status(sa->context, sa->stream, f->name, sa->flags))
+ mm_status_result.flags = 0L;
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(sa->flags & mm_status_result.flags)
+ switch(sa->flags){
+ case SA_MESSAGES :
+ switch(sa->cmp){
+ case 0 : /* equals */
+ if(mm_status_result.messages == sa->nmsgs)
+ rv = 1;
+
+ break;
+
+ case 1 : /* less than */
+ if(mm_status_result.messages < sa->nmsgs)
+ rv = 1;
+
+ break;
+
+ case 2 :
+ if(mm_status_result.messages > sa->nmsgs)
+ rv = 1;
+
+ break;
+
+ default :
+ break;
+ }
+
+ break;
+
+ case SA_RECENT :
+ if(mm_status_result.recent)
+ rv = 1;
+
+ break;
+
+ case SA_UNSEEN :
+ if(mm_status_result.unseen)
+ rv = 1;
+
+ break;
+
+ default :
+ break;
+ }
+ }
+
+ return(rv);
+}
+
+
+int
+foreach_folder(CONTEXT_S *context, int selected, int (*test) (FOLDER_S *, void *), void *args)
+{
+ int i, n, rv = 1;
+ int we_turned_on = 0;
+ FOLDER_S *fp;
+
+ we_turned_on = intr_handling_on();
+
+ for(i = 0, n = folder_total(FOLDERS(context)); i < n; i++){
+ if(ps_global->intr_pending){
+ for(; i >= 0; i--)
+ folder_entry(i, FOLDERS(context))->scanned = 0;
+
+ cmd_cancelled("Select");
+ rv = 0;
+ break;
+ }
+
+ fp = folder_entry(i, FOLDERS(context));
+ fp->scanned = 0;
+ if((!selected || fp->selected) && fp->isfolder && (*test)(fp, args))
+ fp->scanned = 1;
+ }
+
+ if(we_turned_on)
+ intr_handling_off();
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Return the path delimiter for the given folder on the given server
+
+ Args: folder -- folder type for delimiter
+
+ ----*/
+int
+folder_delimiter(char *folder)
+{
+ int rv, we_cancel = 0;
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ rv = get_folder_delimiter(folder);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ return(rv);
+}
+
+
+/*
+ * next_folder - given a current folder in a context, return the next in
+ * the list, or NULL if no more or there's a problem.
+ *
+ * Args streamp -- If set, try to re-use this stream for checking.
+ * next -- Put return value here, return points to this
+ * nextlen -- Length of next
+ * current -- Current folder, so we know where to start looking
+ * cntxt --
+ * find_recent -- Returns the number of recent here. The presence of
+ * this arg also indicates that we should do the calls
+ * to figure out whether there is a next interesting folder
+ * or not.
+ * did_cancel -- Tell caller if user canceled. Only used if find_recent
+ * is also set. Also, user will only be given the
+ * opportunity to cancel if this is set. If it isn't
+ * set, we just plow ahead when there is an error or
+ * when the folder does not exist.
+ */
+char *
+next_folder(MAILSTREAM **streamp, char *next, size_t nextlen, char *current, CONTEXT_S *cntxt, long int *find_recent, int *did_cancel)
+{
+ int index, recent = 0, failed_status = 0, try_fast;
+ char prompt[128];
+ FOLDER_S *f = NULL;
+ char tmp[MAILTMPLEN];
+
+
+ /* note: find_folders may assign "stream" */
+ build_folder_list(streamp, cntxt, NULL, NULL,
+ NEWS_TEST(cntxt) ? BFL_LSUB : BFL_NONE);
+
+ try_fast = (F_ON(F_ENABLE_FAST_RECENT, ps_global) &&
+ F_OFF(F_TAB_USES_UNSEEN, ps_global));
+ if(find_recent)
+ *find_recent = 0L;
+
+ for(index = folder_index(current, cntxt, FI_FOLDER) + 1;
+ index > 0
+ && index < folder_total(FOLDERS(cntxt))
+ && (f = folder_entry(index, FOLDERS(cntxt)))
+ && !f->isdir;
+ index++)
+ if(find_recent){
+ MAILSTREAM *stream = NULL;
+ int rv, we_cancel = 0, match;
+ char msg_buf[MAX_BM+1];
+
+ /* must be a folder and it can't be the current one */
+ if(ps_global->context_current == ps_global->context_list
+ && !strcmp(ps_global->cur_folder, FLDR_NAME(f)))
+ continue;
+
+ /*
+ * If we already have the folder open, short circuit all this
+ * stuff.
+ */
+ match = 0;
+ if((stream = sp_stream_get(context_apply(tmp, cntxt, f->name,
+ sizeof(tmp)),
+ SP_MATCH)) != NULL
+ || (!IS_REMOTE(tmp) && (stream = already_open_stream(tmp, AOS_NONE)) != NULL)){
+ (void) pine_mail_ping(stream);
+
+ if(F_ON(F_TAB_USES_UNSEEN, ps_global)){
+ /*
+ * Just fall through and let the status call below handle
+ * the already open stream. If we were doing this the
+ * same as the else case, we would figure out how many
+ * unseen are in this open stream by doing a search.
+ * Instead of repeating that code that is already in
+ * pine_mail_status_full, fall through and note the
+ * special case by lighting the match variable.
+ */
+ match++;
+ }
+ else{
+ *find_recent = sp_recent_since_visited(stream);
+ if(*find_recent){
+ recent++;
+ break;
+ }
+
+ continue;
+ }
+ }
+
+ snprintf(msg_buf, sizeof(msg_buf), "Checking %s for %s messages",
+ FLDR_NAME(f), F_ON(F_TAB_USES_UNSEEN, ps_global) ? "unseen" : "recent");
+ msg_buf[sizeof(msg_buf)-1] = '\0';
+ we_cancel = busy_cue(msg_buf, NULL, 0);
+
+ /* First, get a stream for the test */
+ if(!stream && streamp && *streamp){
+ if(context_same_stream(cntxt, f->name, *streamp)){
+ stream = *streamp;
+ }
+ else{
+ pine_mail_close(*streamp);
+ *streamp = NULL;
+ }
+ }
+
+ if(!stream){
+ stream = sp_stream_get(context_apply(tmp, cntxt, f->name,
+ sizeof(tmp)),
+ SP_SAME);
+ }
+
+ /*
+ * If interestingness is indeterminate or we're
+ * told to explicitly, look harder...
+ */
+
+ /*
+ * We could make this more efficient in the cases where we're
+ * opening a new stream or using streamp by having folder_exists
+ * cache the stream. The change would require a folder_exists()
+ * that caches streams, but most of the time folder_exists just
+ * uses the inbox stream or ps->mail_stream.
+ *
+ * Another thing to consider is that maybe there should be an
+ * option to try to LIST a folder before doing a STATUS (or SELECT).
+ * This isn't done by default for the case where a folder is
+ * SELECTable but not LISTable, but on some servers doing an
+ * RLIST first tells the server that we support mailbox referrals.
+ */
+ if(!try_fast
+ || !((rv = folder_exists(cntxt,f->name))
+ & (FEX_ISMARKED | FEX_UNMARKED))){
+ extern MAILSTATUS mm_status_result;
+
+ if(try_fast && (rv == 0 || rv & FEX_ERROR)){
+ failed_status = 1;
+ mm_status_result.flags = 0L;
+ }
+ else{
+ if(stream){
+ if(!context_status_full(cntxt, match ? NULL : stream,
+ f->name,
+ F_ON(F_TAB_USES_UNSEEN, ps_global)
+ ? SA_UNSEEN : SA_RECENT,
+ &f->uidvalidity,
+ &f->uidnext)){
+ failed_status = 1;
+ mm_status_result.flags = 0L;
+ }
+ }
+ else{
+ /* so we can re-use the stream */
+ if(!context_status_streamp_full(cntxt, streamp, f->name,
+ F_ON(F_TAB_USES_UNSEEN, ps_global)
+ ? SA_UNSEEN : SA_RECENT,
+ &f->uidvalidity,
+ &f->uidnext)){
+ failed_status = 1;
+ mm_status_result.flags = 0L;
+ }
+ }
+ }
+
+ if(F_ON(F_TAB_USES_UNSEEN, ps_global)){
+ rv = ((mm_status_result.flags & SA_UNSEEN)
+ && (*find_recent = mm_status_result.unseen))
+ ? FEX_ISMARKED : 0;
+ }
+ else{
+ rv = ((mm_status_result.flags & SA_RECENT)
+ && (*find_recent = mm_status_result.recent))
+ ? FEX_ISMARKED : 0;
+ }
+
+ /* we don't know how many in this case */
+ if(try_fast)
+ *find_recent = 0L; /* consistency, boy! */
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(failed_status && did_cancel){
+ char buf1[6*MAX_SCREEN_COLS+1];
+ int wid1, wid2;
+
+ snprintf(prompt, sizeof(prompt), _("Check of folder %s failed. Continue "), FLDR_NAME(f));
+ if(utf8_width(prompt) > MAXPROMPT){
+ snprintf(prompt, sizeof(prompt), _("Check of %s failed. Continue "), FLDR_NAME(f));
+ if((wid1=utf8_width(prompt)) > MAXPROMPT){
+ if((wid2=utf8_width(FLDR_NAME(f))) > wid1-MAXPROMPT)
+ snprintf(prompt, sizeof(prompt), _("Check of %s failed. Continue "), strsquish(buf1, sizeof(buf1), FLDR_NAME(f), wid2-(wid1-MAXPROMPT)));
+ else
+ snprintf(prompt, sizeof(prompt), _("Check failed. Continue "));
+ }
+ }
+
+ if(want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'n'){
+ *did_cancel = 1;
+ break;
+ }
+ else
+ /* have to reset this lower-level cancel marker */
+ ps_global->user_says_cancel = 0;
+ }
+
+ failed_status = 0;
+
+ if(rv & FEX_ISMARKED){
+ recent++;
+ break;
+ }
+ }
+
+ if(f && (!find_recent || recent)){
+ strncpy(next, FLDR_NAME(f), nextlen);
+ next[nextlen-1] = '\0';
+ }
+ else if(nextlen > 0)
+ *next = '\0';
+
+ /* BUG: how can this be made smarter so we cache the list? */
+ free_folder_list(cntxt);
+ return((*next) ? next : NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Shuffle order of incoming folders
+ ----*/
+int
+shuffle_incoming_folders(CONTEXT_S *context, int index)
+{
+ int tot, i, deefault, rv, inheriting = 0;
+ int new_index, index_within_var, new_index_within_var;
+ int readonly = 0;
+ char tmp[200];
+ HelpType help;
+ ESCKEY_S opts[3];
+ char ***alval;
+ EditWhich ew;
+ FOLDER_S *fp;
+ PINERC_S *prc = NULL;
+
+ dprint((4, "shuffle_incoming_folders\n"));
+
+ if(!(context->use & CNTXT_INCMNG) ||
+ (tot = folder_total(FOLDERS(context))) < 2 ||
+ index < 0 || index >= tot)
+ return(0);
+
+ if(index == 0){
+ q_status_message(SM_ORDER,0,3, _("Cannot shuffle INBOX"));
+ return(0);
+ }
+
+ fp = folder_entry(index, FOLDERS(context));
+ ew = config_containing_inc_fldr(fp);
+
+ if(ps_global->restricted)
+ readonly = 1;
+ else{
+ switch(ew){
+ case Main:
+ prc = ps_global->prc;
+ break;
+ case Post:
+ prc = ps_global->post_prc;
+ break;
+ case None:
+ break;
+ }
+
+ readonly = prc ? prc->readonly : 1;
+ }
+
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return(0);
+ }
+
+ if(readonly){
+ q_status_message(SM_ORDER,3,5,
+ _("Shuffle cancelled: config file not editable"));
+ return(0);
+ }
+
+ alval = ALVAL(&ps_global->vars[V_INCOMING_FOLDERS], ew);
+
+ if(!(alval && *alval))
+ return(0);
+
+ i = 0;
+ opts[i].ch = 'b';
+ opts[i].rval = 'b';
+ opts[i].name = "B";
+ opts[i++].label = N_("Back");
+
+ opts[i].ch = 'f';
+ opts[i].rval = 'f';
+ opts[i].name = "F";
+ opts[i++].label = N_("Forward");
+
+ opts[i].ch = -1;
+ deefault = 'b';
+
+ /* find where this entry is in the particular config list */
+ index_within_var = -1;
+ for(i = 0; (*alval)[i]; i++){
+ expand_variables(tmp_20k_buf, SIZEOF_20KBUF, (*alval)[i], 0);
+ if(i == 0 && !strcmp(tmp_20k_buf, INHERIT))
+ inheriting = 1;
+ else if(fp->varhash == line_hash(tmp_20k_buf)){
+ index_within_var = i;
+ break;
+ }
+ }
+
+ if(index_within_var == -1){ /* didn't find it */
+ q_status_message(SM_ORDER,3,5,
+ _("Shuffle cancelled: unexpected trouble shuffling"));
+ return(0);
+ }
+
+ if(index_within_var == 0 || (inheriting && index_within_var == 1)){
+ opts[0].ch = -2; /* no back */
+ deefault = 'f';
+ }
+
+ if(!(*alval)[i+1]) /* no forward */
+ opts[1].ch = -2;
+
+ if(opts[0].ch == -2 && opts[1].ch == -2){
+ q_status_message(SM_ORDER, 0, 4,
+ _("Cannot shuffle from one config file to another."));
+ return(0);
+ }
+
+ snprintf(tmp, sizeof(tmp), "Shuffle \"%s\" %s%s%s ? ",
+ FLDR_NAME(folder_entry(index, FOLDERS(context))),
+ (opts[0].ch != -2) ? "BACK" : "",
+ (opts[0].ch != -2 && opts[1].ch != -2) ? " or " : "",
+ (opts[1].ch != -2) ? "FORWARD" : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ help = (opts[0].ch == -2) ? h_incoming_shuf_down
+ : (opts[1].ch == -2) ? h_incoming_shuf_up
+ : h_incoming_shuf;
+
+ rv = radio_buttons(tmp, -FOOTER_ROWS(ps_global), opts, deefault, 'x',
+ help, RB_NORM);
+
+ new_index = index;
+ new_index_within_var = index_within_var;
+
+ switch(rv){
+ case 'x':
+ cmd_cancelled("Shuffle");
+ return(0);
+
+ case 'b':
+ new_index_within_var--;
+ new_index--;
+ break;
+
+ case 'f':
+ new_index_within_var++;
+ new_index++;
+ break;
+ }
+
+ if(swap_incoming_folders(index, new_index, FOLDERS(context))){
+ char *stmp;
+
+ /* swap them in the config variable, too */
+ stmp = (*alval)[index_within_var];
+ (*alval)[index_within_var] = (*alval)[new_index_within_var];
+ (*alval)[new_index_within_var] = stmp;
+
+ set_current_val(&ps_global->vars[V_INCOMING_FOLDERS], TRUE, FALSE);
+ write_pinerc(ps_global, ew, WRP_NONE);
+
+ return(1);
+ }
+ else
+ return(0);
+}
+
+
+int
+swap_incoming_folders(int index1, int index2, FLIST *flist)
+{
+ FOLDER_S *ftmp;
+
+ if(!flist)
+ return(0);
+
+ if(index1 == index2)
+ return(1);
+
+ if(index1 < 0 || index1 >= flist->used){
+ dprint((1, "Error in swap_incoming_folders: index1=%d, used=%d\n", index1, flist->used));
+ return(0);
+ }
+
+ if(index2 < 0 || index2 >= flist->used){
+ dprint((1, "Error in swap_incoming_folders: index2=%d, used=%d\n", index2, flist->used));
+ return(0);
+ }
+
+ ftmp = flist->folders[index1];
+ flist->folders[index1] = flist->folders[index2];
+ flist->folders[index2] = ftmp;
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Find an entry in the folder list by matching names
+ ----*/
+int
+search_folder_list(void *list, char *name)
+{
+ int i;
+ char *n;
+
+ for(i = 0; i < folder_total(list); i++) {
+ n = folder_entry(i, list)->name;
+ if(strucmp(name, n) == 0)
+ return(1); /* Found it */
+ }
+ return(0);
+}
+
+
+static CONTEXT_S *post_cntxt = NULL;
+
+/*----------------------------------------------------------------------
+ Browse list of newsgroups available for posting
+
+ Called from composer when ^T is typed in newsgroups field
+
+Args: none
+
+Returns: pointer to selected newsgroup, or NULL.
+ Selector call in composer expects this to be alloc'd here.
+
+ ----*/
+char *
+news_group_selector(char **error_mess)
+{
+ CONTEXT_S *tc;
+ char *post_folder;
+ int rc;
+ char *em;
+
+ /* Coming back from composer */
+ fix_windsize(ps_global);
+ init_sigwinch();
+
+ post_folder = fs_get((size_t)MAILTMPLEN);
+
+ /*--- build the post_cntxt -----*/
+ em = get_post_list(ps_global->VAR_NNTP_SERVER);
+ if(em != NULL){
+ if(error_mess != NULL)
+ *error_mess = cpystr(em);
+
+ cancel_busy_cue(-1);
+ return(NULL);
+ }
+
+ /*----- Call the browser -------*/
+ tc = post_cntxt;
+ if((rc = folders_for_post(ps_global, &tc, post_folder)) != 0)
+ post_cntxt = tc;
+
+ cancel_busy_cue(-1);
+
+ if(rc <= 0)
+ return(NULL);
+
+ return(post_folder);
+}
+
+
+/*----------------------------------------------------------------------
+ Get the list of news groups that are possible for posting
+
+Args: post_host -- host name for posting
+
+Returns NULL if list is retrieved, pointer to error message if failed
+
+This is kept in a standards "CONTEXT" for a acouple of reasons. First
+it makes it very easy to use the folder browser to display the
+newsgroup for selection on ^T from the composer. Second it will allow
+the same mechanism to be used for all folder lists on memory tight
+systems like DOS. The list is kept for the life of the session because
+fetching it is a expensive.
+
+ ----*/
+char *
+get_post_list(char **post_host)
+{
+ char *post_context_string;
+
+ if(!post_host || !post_host[0]) {
+ /* BUG should assume inews and get this from active file */
+ return(_("Can't post messages, NNTP server needs to be configured"));
+ }
+
+ if(!post_cntxt){
+ int we_cancel;
+ size_t l;
+
+ we_cancel = busy_cue(_("Getting full list of groups for posting"), NULL, 1);
+
+ l = strlen(post_host[0]) + 20;
+ post_context_string = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(post_context_string, l+1, "{%s/nntp}#news.[]", post_host[0]);
+ post_context_string[l] = '\0';
+
+ post_cntxt = new_context(post_context_string, NULL);
+ post_cntxt->use |= CNTXT_FINDALL;
+ post_cntxt->dir->status |= CNTXT_NOFIND;
+ post_cntxt->next = NULL;
+
+ build_folder_list(NULL, post_cntxt, NULL, NULL,
+ NEWS_TEST(post_cntxt) ? BFL_LSUB : BFL_NONE);
+ if(we_cancel)
+ cancel_busy_cue(-1);
+ }
+ return(NULL);
+}
+
+
+#ifdef _WINDOWS
+int
+folder_list_popup(sparms, in_handle)
+ SCROLL_S *sparms;
+ int in_handle;
+{
+ MPopup fldr_popup[20];
+
+ memset(fldr_popup, 0, 20 * sizeof(MPopup));
+ fldr_popup[0].type = tTail;
+ if(in_handle){
+ int i, n = 0;
+ HANDLE_S *h = get_handle(sparms->text.handles, in_handle);
+ FOLDER_S *fp = (h) ? folder_entry(h->h.f.index,
+ FOLDERS(h->h.f.context))
+ : NULL;
+
+ if((i = menu_binding_index(sparms->keys.menu, MC_CHOICE)) >= 0){
+ fldr_popup[n].type = tQueue;
+ fldr_popup[n].data.val = sparms->keys.menu->keys[i].bind.ch[0];
+ fldr_popup[n].label.style = lNormal;
+ fldr_popup[n++].label.string = (fp && fp->isdir)
+ ? "&View Directory"
+ : "&View Folder";
+ }
+
+ if((i = menu_binding_index(sparms->keys.menu, MC_SELCUR)) >= 0
+ && bitnset(i, sparms->keys.bitmap)){
+ fldr_popup[n].type = tQueue;
+ fldr_popup[n].data.val = sparms->keys.menu->keys[i].bind.ch[0];
+ fldr_popup[n].label.style = lNormal;
+ fldr_popup[n++].label.string = (fp && fp->isdir)
+ ? "&Select Directory"
+ : "&Select Folder";
+ }
+
+ if((i = menu_binding_index(sparms->keys.menu, MC_DELETE)) >= 0
+ && bitnset(i, sparms->keys.bitmap)){
+ fldr_popup[n].type = tQueue;
+ fldr_popup[n].data.val = sparms->keys.menu->keys[i].bind.ch[0];
+ fldr_popup[n].label.style = lNormal;
+ fldr_popup[n++].label.string = "Delete Folder";
+ }
+
+ if((i = menu_binding_index(sparms->keys.menu, MC_EXPORT)) >= 0
+ && bitnset(i, sparms->keys.bitmap)){
+ fldr_popup[n].type = tQueue;
+ fldr_popup[n].data.val = sparms->keys.menu->keys[i].bind.ch[0];
+ fldr_popup[n].label.style = lNormal;
+ fldr_popup[n++].label.string = "Export Folder";
+ }
+
+ if((i = menu_binding_index(sparms->keys.menu, MC_CHK_RECENT)) >= 0
+ && bitnset(i, sparms->keys.bitmap)){
+ fldr_popup[n].type = tQueue;
+ fldr_popup[n].data.val = sparms->keys.menu->keys[i].bind.ch[0];
+ fldr_popup[n].label.style = lNormal;
+ fldr_popup[n++].label.string = "Check New Messages";
+ }
+
+ if(n)
+ fldr_popup[n++].type = tSeparator;
+
+ folder_popup_config(((FPROC_S *)sparms->proc.data.p)->fs,
+ sparms->keys.menu, &fldr_popup[n]);
+ }
+ else
+ folder_popup_config(((FPROC_S *)sparms->proc.data.p)->fs,
+ sparms->keys.menu, fldr_popup);
+
+ if(fldr_popup[0].type != tTail)
+ mswin_popup(fldr_popup);
+
+ return(0);
+}
+
+
+int
+folder_list_select_popup(sparms, in_handle)
+ SCROLL_S *sparms;
+ int in_handle;
+{
+ MPopup fldr_popup[20];
+
+ memset(fldr_popup, 0, 20 * sizeof(MPopup));
+ fldr_popup[0].type = tTail;
+ if(in_handle){
+ int i, n = 0;
+ HANDLE_S *h = get_handle(sparms->text.handles, in_handle);
+ FOLDER_S *fp = (h) ? folder_entry(h->h.f.index,FOLDERS(h->h.f.context))
+ : NULL;
+
+ if((i = menu_binding_index(sparms->keys.menu, MC_CHOICE)) >= 0){
+ fldr_popup[n].type = tQueue;
+ fldr_popup[n].data.val = sparms->keys.menu->keys[i].bind.ch[0];
+ fldr_popup[n].label.style = lNormal;
+ fldr_popup[n++].label.string = (fp && fp->isdir)
+ ? "&View Directory"
+ : "&Select";
+
+ fldr_popup[n++].type = tSeparator;
+ }
+
+ folder_popup_config(((FPROC_S *)sparms->proc.data.p)->fs,
+ sparms->keys.menu, &fldr_popup[n]);
+ }
+ else
+ folder_popup_config(((FPROC_S *)sparms->proc.data.p)->fs,
+ sparms->keys.menu, fldr_popup);
+
+ if(fldr_popup[0].type != tTail)
+ mswin_popup(fldr_popup);
+
+ return(0);
+}
+
+
+/*
+ * Just a little something to simplify assignments
+ */
+#define FLDRPOPUP(p, c, s) { \
+ (p)->type = tQueue; \
+ (p)->data.val = c; \
+ (p)->label.style = lNormal; \
+ (p)->label.string = s; \
+ }
+
+
+/*----------------------------------------------------------------------
+ Popup Menu configurator
+
+ ----*/
+void
+folder_popup_config(fs, km, popup)
+ FSTATE_S *fs;
+ struct key_menu *km;
+ MPopup *popup;
+{
+ int i;
+
+ if((i = menu_binding_index(km, MC_PARENT)) >= 0){
+ FLDRPOPUP(popup, km->keys[i].bind.ch[0], "Parent Directory");
+ popup++;
+ }
+
+ if(fs->km == &folder_km){
+ if((fs->context->next || fs->context->prev) && !fs->combined_view){
+ FLDRPOPUP(popup, '<', "Collection List");
+ popup++;
+ }
+ }
+ else if((i = menu_binding_index(km, MC_COLLECTIONS)) >= 0){
+ FLDRPOPUP(popup, km->keys[i].bind.ch[0], "Collection List");
+ popup++;
+ }
+
+ if((i = menu_binding_index(km, MC_INDEX)) >= 0){
+ FLDRPOPUP(popup, km->keys[i].bind.ch[0], "Current Folder Index");
+ popup++;
+ }
+
+ if((i = menu_binding_index(km, MC_MAIN)) >= 0){
+ FLDRPOPUP(popup, km->keys[i].bind.ch[0], "Main Menu");
+ popup++;
+ }
+
+ popup->type = tTail; /* tie off the array */
+}
+#endif /* _WINDOWS */
diff --git a/alpine/folder.h b/alpine/folder.h
new file mode 100644
index 00000000..6e9fbee5
--- /dev/null
+++ b/alpine/folder.h
@@ -0,0 +1,39 @@
+/*
+ * $Id: folder.h 767 2007-10-24 00:03:59Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_FOLDER_INCLUDED
+#define PINE_FOLDER_INCLUDED
+
+
+#include "context.h"
+#include "../pith/folder.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+
+
+/* exported protoypes */
+void folder_screen(struct pine *);
+void folder_config_screen(struct pine *, int);
+int folders_for_goto(struct pine *, CONTEXT_S **, char *, int);
+int folders_for_save(struct pine *, CONTEXT_S **, char *, int);
+char *folders_for_fcc(char **);
+char *folder_for_config(int);
+char *context_edit_screen(struct pine *, char *, char *, char *, char *, char *);
+int add_new_folder(CONTEXT_S *, EditWhich, int, char *, size_t, MAILSTREAM *, char *);
+char *next_folder(MAILSTREAM **, char *, size_t, char *, CONTEXT_S *, long *, int *);
+char *news_group_selector(char **);
+
+
+#endif /* PINE_FOLDER_INCLUDED */
diff --git a/alpine/headers.h b/alpine/headers.h
new file mode 100644
index 00000000..759b7879
--- /dev/null
+++ b/alpine/headers.h
@@ -0,0 +1,68 @@
+/*
+ * $Id: headers.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef ALPINE_HEADERS_INCLUDED
+#define ALPINE_HEADERS_INCLUDED
+
+
+/*----------------------------------------------------------------------
+ Include files
+ ----*/
+
+#include "../pith/headers.h"
+
+#include "../pico/headers.h"
+
+
+/*
+ * Redefinition to help pico storage object use more clear
+ */
+#define PicoText ExternalText
+
+
+#if !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
+ /*
+ * If LEAVEOUTFIFO is set in os.h, then we leave it out.
+ * If it isn't set, we still might leave it out. We'll decide
+ * based on whether or not O_NONBLOCK is defined or not.
+ * It's just a guess. Safer would be to change the polarity of the
+ * test and define something like INCLUDEFIFO instead of LEAVEOUTFIFO
+ * and only define it where we know. Since we don't really know
+ * we'd rather run the risk of being wrong and finding out that
+ * way instead of just having people not know about it.
+ */
+#if !defined(O_NONBLOCK)
+#define LEAVEOUTFIFO 1
+#endif
+#endif
+
+/* include osdep protos and def'ns */
+#include "osdep/debuging.h"
+#include "osdep/execview.h"
+#include "osdep/fltrname.h"
+#include "osdep/jobcntrl.h"
+#include "osdep/print.h"
+#include "osdep/termin.gen.h"
+#include "osdep/termout.gen.h"
+#ifndef _WINDOWS
+#include "osdep/termin.unx.h"
+#include "osdep/termout.unx.h"
+#else /* _WINDOWS */
+#include "osdep/termin.wnt.h"
+#include "osdep/termout.wnt.h"
+#endif /* _WINDOWS */
+
+
+#endif /* ALPINE_HEADERS_INCLUDED */
diff --git a/alpine/help.c b/alpine/help.c
new file mode 100644
index 00000000..b291defd
--- /dev/null
+++ b/alpine/help.c
@@ -0,0 +1,1408 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: help.c 1032 2008-04-11 00:30:04Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "help.h"
+#include "keymenu.h"
+#include "status.h"
+#include "mailview.h"
+#include "mailindx.h"
+#include "mailcmd.h"
+#include "reply.h"
+#include "signal.h"
+#include "radio.h"
+#include "send.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/filter.h"
+#include "../pith/msgno.h"
+#include "../pith/pipe.h"
+#include "../pith/util.h"
+#include "../pith/detoken.h"
+#include "../pith/list.h"
+#include "../pith/margin.h"
+
+
+typedef struct _help_scroll {
+ unsigned keys_formatted:1; /* Has full keymenu been formatted? */
+ char **help_source; /* Source of displayed help text */
+} HELP_SCROLL_S;
+
+
+static struct {
+ unsigned crlf:1;
+ char **line,
+ *offset;
+} g_h_text;
+
+
+typedef struct _help_print_state {
+ int page;
+ char *title;
+ int title_len;
+} HPRT_S;
+
+static HPRT_S *g_hprt;
+
+
+static char att_cur_msg[] = "\
+ Reporting a bug...\n\
+\n\
+ If you think that the \"current\" message may be related to the bug you\n\
+ are reporting you may include it as an attachment. If you want to\n\
+ include a message but you aren't sure if it is the current message,\n\
+ cancel this bug report, go to the folder index, place the cursor on\n\
+ the message you wish to include, then return to the main menu and run\n\
+ the bug report command again. Answer \"Y\" when asked the question\n\
+ \"Attach current message to report?\"\n\
+\n\
+ This bug report will also automatically include your pine\n\
+ configuration file, which is helpful when investigating the problem.";
+
+
+#define GRIPE_OPT_CONF 0x01
+#define GRIPE_OPT_MSG 0x02
+#define GRIPE_OPT_LOCAL 0x04
+#define GRIPE_OPT_KEYS 0x08
+
+
+/*
+ * Internal prototypes
+ */
+int helper_internal(HelpType, char *, char *, int);
+int help_processor(int, MSGNO_S *, SCROLL_S *);
+void help_keymenu_tweek(SCROLL_S *, int);
+void print_all_help(void);
+void print_help_page_title(char *, size_t, HPRT_S *);
+int print_help_page_break(long, char *, LT_INS_S **, void *);
+int help_bogus_input(UCS);
+int gripe_newbody(struct pine *, BODY **, long, int);
+ADDRESS *gripe_token_addr(char *);
+char *gripe_id(char *);
+void att_cur_drawer(void);
+int journal_processor(int, MSGNO_S *, SCROLL_S *);
+int help_popup(SCROLL_S *, int);
+#ifdef _WINDOWS
+int help_subsection_popup(SCROLL_S *, int);
+#endif
+
+
+
+/*----------------------------------------------------------------------
+ Get the help text in the proper format and call scroller
+
+ Args: text -- The help text to display (from pine.help --> helptext.c)
+ title -- The title of the help text
+
+ Result: format text and call scroller
+
+ The pages array contains the line number of the start of the pages in
+the text. Page 1 is in the 0th element of the array.
+The list is ended with a page line number of -1. Line number 0 is also
+the first line in the text.
+ -----*/
+int
+helper_internal(HelpType text, char *frag, char *title, int flags)
+{
+ char **shown_text;
+ int cmd = MC_NONE;
+ long offset = 0L;
+ char *error = NULL, tmp_title[MAX_SCREEN_COLS + 1];
+ STORE_S *store;
+ HANDLE_S *handles = NULL, *htmp;
+ HELP_SCROLL_S hscroll;
+ gf_io_t pc;
+
+ dprint((1, "\n\n ---- HELPER ----\n"));
+
+ /* assumption here is that HelpType is char ** */
+ shown_text = text;
+
+ if(F_ON(F_BLANK_KEYMENU,ps_global)){
+ FOOTER_ROWS(ps_global) = 3;
+ clearfooter(ps_global);
+ ps_global->mangled_screen = 1;
+ }
+
+ if(flags & HLPD_NEWWIN){
+ fix_windsize(ps_global);
+ init_sigwinch();
+ }
+
+ /*
+ * At this point, shown_text is a charstarstar with html
+ * Turn it into a charstar with digested html
+ */
+ do{
+ init_helper_getc(shown_text);
+ init_handles(&handles);
+
+ memset(&hscroll, 0, sizeof(HELP_SCROLL_S));
+ hscroll.help_source = shown_text;
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ gf_set_so_writec(&pc, store);
+ gf_filter_init();
+
+ if(!struncmp(shown_text[0], "<html>", 6))
+ gf_link_filter(gf_html2plain,
+ gf_html2plain_opt("x-alpine-help:",
+ ps_global->ttyo->screen_cols,
+ non_messageview_margin(),
+ &handles, NULL, GFHP_LOCAL_HANDLES));
+ else
+ gf_link_filter(gf_wrap, gf_wrap_filter_opt(
+ ps_global->ttyo->screen_cols,
+ ps_global->ttyo->screen_cols,
+ NULL, 0, GFW_HANDLES | GFW_SOFTHYPHEN));
+
+ error = gf_pipe(helper_getc, pc);
+
+ gf_clear_so_writec(store);
+
+ if(!error){
+ SCROLL_S sargs;
+ struct key_menu km;
+ struct key keys[24];
+
+ for(htmp = handles; htmp; htmp = htmp->next)
+ if(htmp->type == URL
+ && htmp->h.url.path
+ && (htmp->h.url.path[0] == 'x'
+ || htmp->h.url.path[0] == '#'))
+ htmp->force_display = 1;
+
+ /* This is mostly here to get the curses variables
+ * for line and column in sync with where the
+ * cursor is on the screen. This gets warped when
+ * the composer is called because it does it's own
+ * stuff
+ */
+ ClearScreen();
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("help text");
+ if((sargs.text.handles = handles) != NULL)
+ while(sargs.text.handles->type == URL
+ && !sargs.text.handles->h.url.path
+ && sargs.text.handles->next)
+ sargs.text.handles = sargs.text.handles->next;
+
+ if(!(sargs.bar.title = title)){
+ if(!struncmp(shown_text[0], "<html>", 6)){
+ char *p;
+ int i;
+
+ /* if we're looking at html, look for a <title>
+ * in the <head>... */
+ for(i = 1;
+ shown_text[i]
+ && struncmp(shown_text[i], "</head>", 7);
+ i++)
+ if(!struncmp(shown_text[i], "<title>", 7)){
+ strncpy(tmp_20k_buf, &shown_text[i][7], SIZEOF_20KBUF);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ if((p = strchr(tmp_20k_buf, '<')) != NULL)
+ *p = '\0';
+
+ snprintf(sargs.bar.title = tmp_title, sizeof(tmp_title),
+ "%s -- %.*s", _("HELP"),
+ ps_global->ttyo->screen_cols-10,
+ strsquish(tmp_20k_buf + 500, SIZEOF_20KBUF,
+ _(tmp_20k_buf),
+ ps_global->ttyo->screen_cols / 3));
+ tmp_title[sizeof(tmp_title)-1] = '\0';
+ break;
+ }
+ }
+
+ if(!sargs.bar.title)
+ sargs.bar.title = _("HELP TEXT");
+ }
+
+ sargs.bar.style = TextPercent;
+ sargs.proc.tool = help_processor;
+ sargs.proc.data.p = (void *) &hscroll;
+ sargs.resize_exit = 1;
+ sargs.help.text = h_special_help_nav;
+ sargs.help.title = _("HELP FOR HELP TEXT");
+ sargs.keys.menu = &km;
+ km = help_keymenu;
+ km.keys = keys;
+ memcpy(&keys[0], help_keymenu.keys,
+ (help_keymenu.how_many * 12) * sizeof(struct key));
+ setbitmap(sargs.keys.bitmap);
+ if(flags & HLPD_FROMHELP){
+ km.keys[HLP_EXIT_KEY].name = "P";
+ km.keys[HLP_EXIT_KEY].label = _("Prev Help");
+ km.keys[HLP_EXIT_KEY].bind.cmd = MC_FINISH;
+ km.keys[HLP_EXIT_KEY].bind.ch[0] = 'p';
+
+ km.keys[HLP_SUBEXIT_KEY].name = "E";
+ km.keys[HLP_SUBEXIT_KEY].label = _("Exit Help");
+ km.keys[HLP_SUBEXIT_KEY].bind.cmd = MC_EXIT;
+ km.keys[HLP_SUBEXIT_KEY].bind.ch[0] = 'e';
+ }
+ else if(text == h_special_help_nav){
+ km.keys[HLP_EXIT_KEY].name = "P";
+ km.keys[HLP_EXIT_KEY].label = _("Prev Help");
+ km.keys[HLP_EXIT_KEY].bind.cmd = MC_FINISH;
+ km.keys[HLP_EXIT_KEY].bind.ch[0] = 'p';
+
+ clrbitn(HLP_MAIN_KEY, sargs.keys.bitmap);
+ clrbitn(HLP_SUBEXIT_KEY, sargs.keys.bitmap);
+ }
+ else{
+ km.keys[HLP_EXIT_KEY].name = "E";
+ km.keys[HLP_EXIT_KEY].label = _("Exit Help");
+ km.keys[HLP_EXIT_KEY].bind.cmd = MC_EXIT;
+ km.keys[HLP_EXIT_KEY].bind.ch[0] = 'e';
+
+ km.keys[HLP_SUBEXIT_KEY].name = "?";
+ /* TRANSLATORS: this is the label of a command where
+ the user is asking for Help about the Help command */
+ km.keys[HLP_SUBEXIT_KEY].label = _("Help Help");
+ km.keys[HLP_SUBEXIT_KEY].bind.cmd = MC_HELP;
+ km.keys[HLP_SUBEXIT_KEY].bind.ch[0] = '?';
+ }
+
+ if(flags & HLPD_SIMPLE){
+ clrbitn(HLP_MAIN_KEY, sargs.keys.bitmap);
+ }
+ else
+ sargs.bogus_input = help_bogus_input;
+
+ if(handles){
+ sargs.keys.each_cmd = help_keymenu_tweek;
+ hscroll.keys_formatted = 0;
+ }
+ else{
+ clrbitn(HLP_VIEW_HANDLE, sargs.keys.bitmap);
+ clrbitn(HLP_PREV_HANDLE, sargs.keys.bitmap);
+ clrbitn(HLP_NEXT_HANDLE, sargs.keys.bitmap);
+ }
+
+ if(text != main_menu_tx
+ && text != h_mainhelp_pinehelp)
+ clrbitn(HLP_ALL_KEY, sargs.keys.bitmap);
+
+ if(frag){
+ sargs.start.on = Fragment;
+ sargs.start.loc.frag = frag;
+ frag = NULL; /* ignore next time */
+ }
+ else if(offset){
+ sargs.start.on = Offset;
+ sargs.start.loc.offset = offset;
+ }
+ else
+ sargs.start.on = FirstPage;
+
+#ifdef _WINDOWS
+ sargs.mouse.popup = (flags & HLPD_FROMHELP)
+ ? help_subsection_popup : help_popup;
+#endif
+
+ cmd = scrolltool(&sargs);
+
+ offset = sargs.start.loc.offset;
+
+ if(F_ON(F_BLANK_KEYMENU,ps_global))
+ FOOTER_ROWS(ps_global) = 1;
+
+ ClearScreen();
+ }
+
+ so_give(&store);
+ }
+
+ free_handles(&handles);
+ }
+ while(cmd == MC_RESIZE);
+
+ return(cmd);
+}
+
+
+/*
+ * helper -- compatibility function around newer helper_internal
+ */
+int
+helper(HelpType text, char *title, int flags)
+{
+ return(helper_internal(text, NULL, title, flags));
+}
+
+
+void
+init_helper_getc(char **help_txt)
+{
+ g_h_text.crlf = 0;
+ g_h_text.line = help_txt;
+ g_h_text.offset = *g_h_text.line;
+ if(g_h_text.offset && g_h_text.offset[0])
+ g_h_text.offset = _(g_h_text.offset);
+}
+
+
+int
+helper_getc(char *c)
+{
+ if(g_h_text.crlf){
+ *c = '\012';
+ g_h_text.crlf = 0;
+ return(1);
+ }
+ else if(g_h_text.offset && *g_h_text.line){
+ if(!(*c = *g_h_text.offset++)){
+ g_h_text.offset = *++g_h_text.line;
+ if(g_h_text.offset && g_h_text.offset[0])
+ g_h_text.offset = _(g_h_text.offset);
+
+ *c = '\015';
+ g_h_text.crlf = 1;
+ }
+
+ return(1);
+ }
+
+ return(0);
+}
+
+
+int
+help_processor(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 0;
+ char message[64];
+ struct help_texts *t;
+
+ switch(cmd){
+ /*----------- Print all the help ------------*/
+ case MC_PRINTALL :
+ print_all_help();
+ break;
+
+ case MC_PRINTMSG :
+ snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
+ message[sizeof(message)-1] = '\0';
+ if(open_printer(message) == 0){
+ print_help(((HELP_SCROLL_S *)sparms->proc.data.p)->help_source);
+ close_printer();
+ }
+
+ break;
+
+ case MC_EXPORT: /* reuse old definition, so as not to patch pine.h */
+ {char help_name[40];
+ help_name[0] = '\0';
+ for(t = h_texts; t->help_text != NO_HELP; t++)
+ if(t->help_text == ((HELP_SCROLL_S *)sparms->proc.data.p)->help_source){
+ strcpy(help_name,t->tag);
+ break;
+ }
+ if(help_name[0])
+ q_status_message1(SM_ORDER, 0, 2,
+ "Internal Name: x-alpine-help:%s", help_name);
+ else
+ q_status_message(SM_ORDER|SM_DING, 1, 2,
+ "Can not find link for text help");
+ }
+ break;
+
+ case MC_FINISH :
+ rv = 1;
+ break;
+
+ default :
+ panic("Unhandled case");
+ }
+
+ return(rv);
+}
+
+
+void
+help_keymenu_tweek(SCROLL_S *sparms, int handle_hidden)
+{
+ if(handle_hidden){
+ sparms->keys.menu->keys[HLP_VIEW_HANDLE].name = "";
+ sparms->keys.menu->keys[HLP_VIEW_HANDLE].label = "";
+ }
+ else{
+ if(!((HELP_SCROLL_S *)sparms->proc.data.p)->keys_formatted){
+ /* If label's always been blank, force reformatting */
+ mark_keymenu_dirty();
+ sparms->keys.menu->width = 0;
+ ((HELP_SCROLL_S *)sparms->proc.data.p)->keys_formatted = 1;
+ }
+
+ sparms->keys.menu->keys[HLP_VIEW_HANDLE].name = "V";
+ sparms->keys.menu->keys[HLP_VIEW_HANDLE].label = "[" N_("View Link") "]";
+ }
+}
+
+
+/*
+ * print_help - send the raw array of lines to printer
+ */
+void
+print_help(char **text)
+{
+ char *error, buf[256];
+ HPRT_S help_data;
+
+ init_helper_getc(text);
+
+ memset(g_hprt = &help_data, 0, sizeof(HPRT_S));
+
+ help_data.page = 1;
+
+ gf_filter_init();
+
+ if(!struncmp(text[0], "<html>", 6)){
+ int i;
+ char *p;
+
+ gf_link_filter(gf_html2plain,
+ gf_html2plain_opt(NULL,80,non_messageview_margin(),
+ NULL,NULL,GFHP_STRIPPED));
+ for(i = 1; i <= 5 && text[i]; i++)
+ if(!struncmp(text[i], "<title>", 7)
+ && (p = srchstr(text[i] + 7, "</title>"))
+ && p - text[i] > 7){
+ help_data.title = text[i] + 7;
+ help_data.title_len = p - help_data.title;
+ break;
+ }
+ }
+ else
+ gf_link_filter(gf_wrap, gf_wrap_filter_opt(80, 80, NULL, 0, GFW_NONE));
+
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(print_help_page_break, NULL));
+ gf_link_filter(gf_nvtnl_local, NULL);
+
+ print_help_page_title(buf, sizeof(buf), &help_data);
+ print_text(buf);
+ print_text(NEWLINE); /* terminate it */
+ print_text(NEWLINE); /* and write two blank links */
+ print_text(NEWLINE);
+
+ if((error = gf_pipe(helper_getc, print_char)) != NULL)
+ q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Printing Error: %s"), error);
+
+ print_char(ctrl('L')); /* new page. */
+}
+
+
+void
+print_all_help(void)
+{
+ struct help_texts *t;
+ char **h;
+ int we_turned_on = 0;
+
+ if(open_printer(_("all 150+ pages of help text")) == 0) {
+ we_turned_on = intr_handling_on();
+ for(t = h_texts; (h = t->help_text) != NO_HELP; t++) {
+ if(ps_global->intr_pending){
+ q_status_message(SM_ORDER, 3, 3,
+ _("Print of all help cancelled"));
+ break;
+ }
+
+ print_help(h);
+ }
+
+ if(we_turned_on)
+ intr_handling_off();
+
+ close_printer();
+ }
+}
+
+
+/*
+ * print_help_page_title --
+ */
+void
+print_help_page_title(char *buf, size_t buflen, HPRT_S *hprt)
+{
+ snprintf(buf, buflen, " Alpine Help%s%.*s%*s%d",
+ hprt->title_len ? ": " : " Text",
+ MIN(55, hprt->title_len), hprt->title_len ? hprt->title : "",
+ 59 - (hprt->title_len ? MIN(55, hprt->title_len) : 5),
+ "Page ", hprt->page);
+ buf[buflen-1] = '\0';
+}
+
+
+/*
+ * print_help_page_break -- insert page breaks and such for printed
+ * help text
+ */
+int
+print_help_page_break(long int linenum, char *line, LT_INS_S **ins, void *local)
+{
+ char buf[256];
+
+ if(((linenum + (g_hprt->page * 3)) % 62) == 0){
+ g_hprt->page++; /* start on new page */
+ buf[0] = ctrl('L');
+ print_help_page_title(buf + 1, sizeof(buf)-1, g_hprt);
+ strncat(buf, "\015\012\015\012\015\012", sizeof(buf)-strlen(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ ins = gf_line_test_new_ins(ins, line, buf, strlen(buf));
+ }
+
+ return(0);
+}
+
+
+/*
+ * help_bogus_input - used by scrolltool to complain about
+ * invalid user input.
+ */
+int
+help_bogus_input(UCS ch)
+{
+ bogus_command(ch, NULL);
+ return(0);
+}
+
+
+int
+url_local_helper(char *url)
+{
+ if((!struncmp(url, "x-alpine-help:", 14) && *(url += 14))
+ || (!struncmp(url, "x-pine-help:", 12) && *(url += 12))){
+ char *frag;
+ HelpType newhelp;
+
+ /* internal fragment reference? */
+ if((frag = strchr(url, '#')) != NULL){
+ size_t len;
+
+ if((len = frag - url) != 0){
+ newhelp = help_name2section(url, len);
+ }
+ else{
+ url_local_fragment(url);
+ return(1);
+ }
+ }
+ else
+ newhelp = help_name2section(url, strlen(url));
+
+
+ if(newhelp != NO_HELP){
+ int rv;
+
+ rv = helper_internal(newhelp, frag, _("HELP SUB-SECTION"),
+ HLPD_NEWWIN | HLPD_SIMPLE | HLPD_FROMHELP);
+ ps_global->mangled_screen = 1;
+ return((rv == MC_EXIT) ? 2 : 1);
+ }
+ }
+
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ _("Unrecognized Internal help: \"%s\""), url);
+ return(0);
+}
+
+
+int
+url_local_config(char *url)
+{
+ if(!struncmp(url, "x-alpine-config:", 16)){
+ char **config;
+ int rv;
+
+ config = get_supported_options();
+ if(config){
+ /* TRANSLATORS: Help for configuration */
+ rv = helper_internal(config, NULL, _("HELP CONFIG"),
+ HLPD_NEWWIN | HLPD_SIMPLE | HLPD_FROMHELP);
+ free_list_array(&config);
+ }
+
+ ps_global->mangled_screen = 1;
+ return((rv == MC_EXIT) ? 2 : 1);
+ }
+
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ _("Unrecognized Internal help: \"%s\""), url);
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Review latest status messages
+ -----*/
+void
+review_messages(void)
+{
+ SCROLL_S sargs;
+ STORE_S *in_store = NULL, *out_store = NULL;
+ gf_io_t gc, pc;
+ int jo, lo, hi, donejo, donelo, donehi;
+ RMCat rmcat;
+ int cmd, timestamps=0, show_level=-1;
+ char debugkeylabel[20];
+ /* TRANSLATORS: command label asking pine to include time stamps in output */
+ char timestampkeylabel[] = N_("Timestamps");
+ /* TRANSLATORS: do not include time stamps in output */
+ char *notimestampkeylabel = N_("NoTimestamps");
+
+ if((rmjofirst < 0 && rmlofirst < 0 && rmhifirst < 0)
+ || rmjofirst >= RMJLEN || rmjolast >= RMJLEN
+ || rmlofirst >= RMLLEN || rmlolast >= RMLLEN
+ || rmhifirst >= RMHLEN || rmhilast >= RMHLEN
+ || (rmjofirst >= 0 && rmjolast < 0)
+ || (rmlofirst >= 0 && rmlolast < 0)
+ || (rmhifirst >= 0 && rmhilast < 0))
+ return;
+
+ do{
+
+ if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
+ !(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ if(in_store)
+ so_give(&in_store);
+
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Failed allocating memory"));
+ return;
+ }
+
+ add_review_message(_("Turning off new messages while reviewing"), 0);
+ rm_not_right_now = 1;
+
+ donejo = donehi = donelo = 0;
+ jo = rmjofirst;
+ if(jo < 0)
+ donejo = 1;
+
+ lo = rmlofirst;
+ if(lo < 0)
+ donelo = 1;
+
+ hi = rmhifirst;
+ if(hi < 0)
+ donehi = 1;
+
+ while(!(donejo && donelo && donehi)){
+ REV_MSG_S *pjo, *plo, *phi, *p;
+
+ if(!donejo)
+ pjo = &rmjoarray[jo];
+ else
+ pjo = NULL;
+
+ if(!donelo)
+ plo = &rmloarray[lo];
+ else
+ plo = NULL;
+
+ if(!donehi)
+ phi = &rmhiarray[hi];
+ else
+ phi = NULL;
+
+ if(pjo && (!plo || pjo->seq <= plo->seq)
+ && (!phi || pjo->seq <= phi->seq))
+ rmcat = Jo;
+ else if(plo && (!phi || plo->seq <= phi->seq))
+ rmcat = Lo;
+ else if(phi)
+ rmcat = Hi;
+ else
+ rmcat = No;
+
+ switch(rmcat){
+ case Jo:
+ p = pjo;
+ if(jo == rmjofirst && (((rmjolast + 1) % RMJLEN) == rmjofirst))
+ so_puts(in_store,
+ _("**** Journal entries prior to this point have been trimmed. ****\n"));
+ break;
+
+ case Lo:
+ p = plo;
+ if(show_level >= 0 &&
+ lo == rmlofirst && (((rmlolast + 1) % RMLLEN) == rmlofirst))
+ so_puts(in_store,
+ _("**** Debug 0-4 entries prior to this point have been trimmed. ****\n"));
+ break;
+
+ case Hi:
+ p = phi;
+ if(show_level >= 5 &&
+ hi == rmhifirst && (((rmhilast + 1) % RMHLEN) == rmhifirst))
+ so_puts(in_store,
+ _("**** Debug 5-9 entries prior to this point have been trimmed. ****\n"));
+ break;
+
+ default:
+ p = NULL;
+ break;
+ }
+
+ if(p){
+ if(p->level <= show_level){
+ if(timestamps && p->timestamp && p->timestamp[0]){
+ so_puts(in_store, p->timestamp);
+ so_puts(in_store, ": ");
+ }
+
+ if(p->message && p->message[0]){
+ if(p->continuation)
+ so_puts(in_store, ">");
+
+ so_puts(in_store, p->message);
+ so_puts(in_store, "\n");
+ }
+ }
+ }
+
+ switch(rmcat){
+ case Jo:
+ if(jo == rmjolast)
+ donejo++;
+ else
+ jo = (jo + 1) % RMJLEN;
+
+ break;
+
+ case Lo:
+ if(lo == rmlolast)
+ donelo++;
+ else
+ lo = (lo + 1) % RMLLEN;
+
+ break;
+
+ case Hi:
+ if(hi == rmhilast)
+ donehi++;
+ else
+ hi = (hi + 1) % RMHLEN;
+
+ break;
+
+ default:
+ donejo++;
+ donelo++;
+ donehi++;
+ break;
+ }
+ }
+
+
+ so_seek(in_store, 0L, 0);
+ gf_filter_init();
+ gf_link_filter(gf_wrap,
+ gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
+ ps_global->ttyo->screen_cols,
+ NULL, show_level < 0 ? 2 : 0, GFW_NONE));
+ gf_set_so_readc(&gc, in_store);
+ gf_set_so_writec(&pc, out_store);
+ gf_pipe(gc, pc);
+ gf_clear_so_writec(out_store);
+ gf_clear_so_readc(in_store);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(out_store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("journal");
+ sargs.keys.menu = &rev_msg_keymenu;
+ sargs.proc.tool = journal_processor;
+ sargs.start.on = LastPage;
+ sargs.resize_exit = 1;
+ sargs.proc.data.p = (void *)&show_level;
+ setbitmap(sargs.keys.bitmap);
+
+#ifdef DEBUG
+#ifdef DEBUGJOURNAL
+ sargs.jump_is_debug = 1;
+ /* TRANSLATORS: these are some screen titles */
+ sargs.help.title = _("HELP FOR DEBUG JOURNAL");
+ sargs.help.text = h_debugjournal;
+ sargs.bar.title = _("REVIEW DEBUGGING");
+#else /* !DEBUGJOURNAL */
+ clrbitn(DEBUG_KEY, sargs.keys.bitmap);
+ sargs.help.title = _("HELP FOR JOURNAL");
+ sargs.help.text = h_journal;
+ sargs.bar.title = _("REVIEW RECENT MESSAGES");
+#endif /* !DEBUGJOURNAL */
+#else /* !DEBUG */
+ clrbitn(DEBUG_KEY, sargs.keys.bitmap);
+ clrbitn(TIMESTAMP_KEY, sargs.keys.bitmap);
+ sargs.help.title = _("HELP FOR JOURNAL");
+ sargs.help.text = h_journal;
+ sargs.bar.title = _("REVIEW RECENT MESSAGES");
+#endif /* !DEBUG */
+
+ if(timestamps)
+ rev_msg_keys[TIMESTAMP_KEY].label = notimestampkeylabel;
+ else
+ rev_msg_keys[TIMESTAMP_KEY].label = timestampkeylabel;
+
+ if(show_level >= 0)
+ /* TRANSLATORS: shows what numeric level Debug output is displayed at */
+ snprintf(debugkeylabel, sizeof(debugkeylabel), _("Debug (%d)"), show_level);
+ else
+ /* TRANSLATORS: include debug output in journal */
+ strncpy(debugkeylabel, _("DebugView"), sizeof(debugkeylabel));
+
+ debugkeylabel[sizeof(debugkeylabel)-1] = '\0';
+
+ rev_msg_keys[DEBUG_KEY].label = debugkeylabel;
+ KS_OSDATASET(&rev_msg_keys[DEBUG_KEY], KS_NONE);
+
+ if((cmd = scrolltool(&sargs)) == MC_TOGGLE)
+ timestamps = !timestamps;
+
+ so_give(&in_store);
+ so_give(&out_store);
+
+ }while(cmd != MC_EXIT);
+
+ rm_not_right_now = 0;
+ add_review_message("Done reviewing", 0);
+}
+
+
+int
+journal_processor(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ switch(cmd){
+ case MC_TOGGLE: /* turn timestamps on or off */
+ break;
+
+ default:
+ panic("Unexpected command in journal_processor");
+ break;
+ }
+
+ return(1);
+}
+
+
+/*
+ * standard type of storage object used for body parts...
+ */
+#ifdef DOS
+#define PART_SO_TYPE TmpFileStar
+#else
+#define PART_SO_TYPE CharStar
+#endif
+
+
+int
+gripe_gripe_to(url)
+ char *url;
+{
+ char *composer_title, *url_copy, *optstr, *p;
+ int opts = 0;
+ BODY *body = NULL;
+ ENVELOPE *outgoing = NULL;
+ REPLY_S fake_reply;
+ PINEFIELD *pf = NULL;
+ long msgno = mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap));
+
+ url_copy = cpystr(url + strlen("x-alpine-gripe:"));
+ if((optstr = strchr(url_copy, '?')) != NULL)
+ *optstr++ = '\0';
+
+ outgoing = mail_newenvelope();
+ outgoing->message_id = generate_message_id();
+
+ if((outgoing->to = gripe_token_addr(url_copy)) != NULL){
+ composer_title = _("COMPOSE TO LOCAL SUPPORT");
+ dprint((1,
+ "\n\n -- Send to local support(%s@%s) --\n",
+ outgoing->to->mailbox ? outgoing->to->mailbox : "NULL",
+ outgoing->to->host ? outgoing->to->host : "NULL"));
+ }
+ else{ /* must be global */
+ composer_title = _("REQUEST FOR ASSISTANCE");
+ rfc822_parse_adrlist(&outgoing->to, url_copy, ps_global->maildomain);
+ }
+
+ /*
+ * Sniff thru options
+ */
+ while(optstr){
+ if((p = strchr(optstr, '?')) != NULL) /* tie off list item */
+ *p++ = '\0';
+
+ if(!strucmp(optstr, "config"))
+ opts |= GRIPE_OPT_CONF;
+ else if(!strucmp(optstr, "curmsg"))
+ opts |= GRIPE_OPT_MSG;
+ else if(!strucmp(optstr, "local"))
+ opts |= GRIPE_OPT_LOCAL;
+ else if(!strucmp(optstr, "keys"))
+ opts |= GRIPE_OPT_KEYS;
+
+ optstr = p;
+ }
+
+ /* build body and hand off to composer... */
+ if(gripe_newbody(ps_global, &body, msgno, opts) == 0){
+ pf = (PINEFIELD *) fs_get(sizeof(PINEFIELD));
+ memset(pf, 0, sizeof(PINEFIELD));
+ pf->name = cpystr("X-Generated-Via");
+ pf->type = FreeText;
+ pf->textbuf = gripe_id("Alpine Bug Report screen");
+ memset((void *)&fake_reply, 0, sizeof(fake_reply));
+ fake_reply.pseudo = 1;
+ fake_reply.data.pico_flags = P_HEADEND;
+ pine_send(outgoing, &body, composer_title, NULL, NULL,
+ &fake_reply, NULL, NULL, pf, PS_STICKY_TO);
+ }
+
+ ps_global->mangled_screen = 1;
+ mail_free_envelope(&outgoing);
+
+ if(body)
+ pine_free_body(&body);
+
+ fs_give((void **) &url_copy);
+
+ return(10);
+}
+
+
+int
+gripe_newbody(ps, body, msgno, flags)
+ struct pine *ps;
+ BODY **body;
+ long msgno;
+ int flags;
+{
+ BODY *pb;
+ PART **pp;
+ STORE_S *store;
+ gf_io_t pc;
+ static char *err = "Problem creating space for message text.";
+ int i;
+ char tmp[MAILTMPLEN], *p;
+
+ if((store = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ *body = mail_newbody();
+
+ if((p = detoken(NULL, NULL, 2, 0, 1, NULL, NULL)) != NULL){
+ if(*p)
+ so_puts(store, p);
+
+ fs_give((void **) &p);
+ }
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4, err);
+ return(-1);
+ }
+
+ if(flags){
+ /*---- Might have multiple parts ----*/
+ (*body)->type = TYPEMULTIPART;
+ /*---- The TEXT part/body ----*/
+ (*body)->nested.part = mail_newbody_part();
+ (*body)->nested.part->body.type = TYPETEXT;
+ (*body)->nested.part->body.contents.text.data = (void *) store;
+
+ /*---- create object, and write current config into it ----*/
+ pp = &((*body)->nested.part->next);
+
+ if(flags & GRIPE_OPT_CONF){
+ *pp = mail_newbody_part();
+ pb = &((*pp)->body);
+ pp = &((*pp)->next);
+ pb->type = TYPETEXT;
+ pb->id = generate_message_id();
+ pb->description = cpystr("Alpine Configuration Data");
+ pb->parameter = mail_newbody_parameter();
+ pb->parameter->attribute = cpystr("name");
+ pb->parameter->value = cpystr("config.txt");
+
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ extern char datestamp[], hoststamp[];
+
+ pb->contents.text.data = (void *) store;
+ gf_set_so_writec(&pc, store);
+ gf_puts("Alpine built ", pc);
+ gf_puts(datestamp, pc);
+ gf_puts(" on host: ", pc);
+ gf_puts(hoststamp, pc);
+ gf_puts("\n", pc);
+
+#ifdef DEBUG
+ dump_pine_struct(ps, pc);
+ dump_config(ps, pc, 0);
+#endif /* DEBUG */
+
+ pb->size.bytes = strlen((char *) so_text(store));
+ gf_clear_so_writec(store);
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4, err);
+ return(-1);
+ }
+ }
+
+ if(flags & GRIPE_OPT_KEYS){
+ *pp = mail_newbody_part();
+ pb = &((*pp)->body);
+ pp = &((*pp)->next);
+ pb->type = TYPETEXT;
+ pb->id = generate_message_id();
+ pb->description = cpystr("Recent User Input");
+ pb->parameter = mail_newbody_parameter();
+ pb->parameter->attribute = cpystr("name");
+ pb->parameter->value = cpystr("uinput.txt");
+
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ pb->contents.text.data = (void *) store;
+
+ so_puts(store, "User's most recent input:\n");
+
+ /* dump last n keystrokes */
+ so_puts(store, "========== Latest keystrokes ==========\n");
+ while((i = key_playback(0)) != -1){
+ snprintf(tmp, sizeof(tmp), "\t%s\t(0x%x)\n", pretty_command(i), i);
+ tmp[sizeof(tmp)-1] = '\0';
+ so_puts(store, tmp);
+ }
+
+ pb->size.bytes = strlen((char *) so_text(store));
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4, err);
+ return(-1);
+ }
+ }
+
+ /* check for local debugging info? */
+ if((flags & GRIPE_OPT_LOCAL)
+ && ps_global->VAR_BUGS_EXTRAS
+ && can_access(ps_global->VAR_BUGS_EXTRAS, EXECUTE_ACCESS) == 0){
+ char *error = NULL;
+
+ *pp = mail_newbody_part();
+ pb = &((*pp)->body);
+ pp = &((*pp)->next);
+ pb->type = TYPETEXT;
+ pb->id = generate_message_id();
+ pb->description = cpystr("Local Configuration Data");
+ pb->parameter = mail_newbody_parameter();
+ pb->parameter->attribute = cpystr("name");
+ pb->parameter->value = cpystr("lconfig.txt");
+
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ PIPE_S *syspipe;
+ gf_io_t gc;
+
+ pb->contents.text.data = (void *) store;
+ gf_set_so_writec(&pc, store);
+ if((syspipe = open_system_pipe(ps_global->VAR_BUGS_EXTRAS,
+ NULL, NULL,
+ PIPE_READ | PIPE_STDERR | PIPE_USER,
+ 0, pipe_callback, pipe_report_error)) != NULL){
+ gf_set_readc(&gc, (void *)syspipe, 0, PipeStar, 0);
+ gf_filter_init();
+ error = gf_pipe(gc, pc);
+ (void) close_system_pipe(&syspipe, NULL, pipe_callback);
+ }
+ else
+ error = "executing config collector";
+
+ gf_clear_so_writec(store);
+ }
+
+ if(error){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "Problem %s", error);
+ return(-1);
+ }
+ else /* fixup attachment's size */
+ pb->size.bytes = strlen((char *) so_text(store));
+ }
+
+ if((flags & GRIPE_OPT_MSG) && mn_get_total(ps->msgmap) > 0L){
+ int ch = 0;
+
+ ps->redrawer = att_cur_drawer;
+ att_cur_drawer();
+
+ if((ch = one_try_want_to("Attach current message to report",
+ 'y','x',NO_HELP,
+ WT_FLUSH_IN|WT_SEQ_SENSITIVE)) == 'y'){
+ *pp = mail_newbody_part();
+ pb = &((*pp)->body);
+ pp = &((*pp)->next);
+ pb->type = TYPEMESSAGE;
+ pb->id = generate_message_id();
+ snprintf(tmp, sizeof(tmp), "Problem Message (%ld of %ld)",
+ mn_get_cur(ps->msgmap), mn_get_total(ps->msgmap));
+ tmp[sizeof(tmp)-1] = '\0';
+ pb->description = cpystr(tmp);
+
+ /*---- Package each message in a storage object ----*/
+ if((store = so_get(PART_SO_TYPE, NULL, EDIT_ACCESS)) != NULL){
+ pb->contents.text.data = (void *) store;
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4, err);
+ return(-1);
+ }
+
+ /* write the header */
+ if((p = mail_fetch_header(ps->mail_stream, msgno, NIL, NIL,
+ NIL, FT_PEEK)) && *p)
+ so_puts(store, p);
+ else
+ return(-1);
+
+ pb->size.bytes = strlen(p);
+ so_puts(store, "\015\012");
+
+ if((p = pine_mail_fetch_text(ps->mail_stream,
+ msgno, NULL, NULL, NIL))
+ && *p)
+ so_puts(store, p);
+ else
+ return(-1);
+
+ pb->size.bytes += strlen(p);
+ }
+ else if(ch == 'x'){
+ q_status_message(SM_ORDER, 0, 3, "Bug report cancelled.");
+ return(-1);
+ }
+ }
+ }
+ else{
+ /*---- Only one part! ----*/
+ (*body)->type = TYPETEXT;
+ (*body)->contents.text.data = (void *) store;
+ }
+
+ return(0);
+}
+
+
+ADDRESS *
+gripe_token_addr(token)
+ char *token;
+{
+ char *p;
+ ADDRESS *a = NULL;
+
+ if(token && *token++ == '_'){
+ if(!strcmp(token, "LOCAL_ADDRESS_")){
+ p = (ps_global->VAR_LOCAL_ADDRESS
+ && ps_global->VAR_LOCAL_ADDRESS[0])
+ ? ps_global->VAR_LOCAL_ADDRESS
+ : "postmaster";
+ a = rfc822_parse_mailbox(&p, ps_global->maildomain);
+ a->personal = cpystr((ps_global->VAR_LOCAL_FULLNAME
+ && ps_global->VAR_LOCAL_FULLNAME[0])
+ ? ps_global->VAR_LOCAL_FULLNAME
+ : "Place to report Alpine Bugs");
+ }
+ else if(!strcmp(token, "BUGS_ADDRESS_")){
+ p = (ps_global->VAR_BUGS_ADDRESS
+ && ps_global->VAR_BUGS_ADDRESS[0])
+ ? ps_global->VAR_BUGS_ADDRESS : "postmaster";
+ a = rfc822_parse_mailbox(&p, ps_global->maildomain);
+ a->personal = cpystr((ps_global->VAR_BUGS_FULLNAME
+ && ps_global->VAR_BUGS_FULLNAME[0])
+ ? ps_global->VAR_BUGS_FULLNAME
+ : "Place to report Alpine Bugs");
+ }
+ }
+
+ return(a);
+}
+
+
+char *
+gripe_id(key)
+ char *key;
+{
+ int i,j,k,l;
+
+ /*
+ * Build our contribution to the subject; part constant string
+ * and random 4 character alpha numeric string.
+ */
+ i = (int)(random() % 36L);
+ j = (int)(random() % 36L);
+ k = (int)(random() % 36L);
+ l = (int)(random() % 36L);
+ tmp_20k_buf[0] = '\0';
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s (ID %c%c%d%c%c)", key,
+ (i < 10) ? '0' + i : 'A' + (i - 10),
+ (j < 10) ? '0' + j : 'A' + (j - 10),
+ (int)(random() % 10L),
+ (k < 10) ? '0' + k : 'A' + (k - 10),
+ (l < 10) ? '0' + l : 'A' + (l - 10));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ return(cpystr(tmp_20k_buf));
+}
+
+
+/*
+ * Used by gripe_tool.
+ */
+void
+att_cur_drawer(void)
+{
+ int i, dline, j;
+ char buf[256+1];
+
+ /* blat helpful message to screen */
+ ClearBody();
+ j = 0;
+ for(dline = 2;
+ dline < ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global);
+ dline++){
+ for(i = 0; i < 256 && att_cur_msg[j] && att_cur_msg[j] != '\n'; i++)
+ buf[i] = att_cur_msg[j++];
+
+ buf[i] = '\0';
+ if(att_cur_msg[j])
+ j++;
+ else if(!i)
+ break;
+
+ PutLine0(dline, 1, buf);
+ }
+}
+
+
+#ifdef _WINDOWS
+
+/*
+ *
+ */
+char *
+pcpine_help(HelpType section)
+{
+ char **help_lines, *help_text = NULL;
+ STORE_S *store;
+ gf_io_t pc;
+
+ /* assumption here is that HelpType is char ** */
+ help_lines = section;
+ if (help_lines != NULL){
+ init_helper_getc(help_lines);
+ if(store = so_get(CharStar, NULL, EDIT_ACCESS)){
+ gf_set_so_writec(&pc, store);
+ gf_filter_init();
+
+ gf_link_filter(gf_local_nvtnl, NULL);
+
+ gf_link_filter(gf_html2plain,
+ gf_html2plain_opt(NULL,
+ ps_global->ttyo->screen_cols,
+ non_messageview_margin(), NULL, NULL, GFHP_STRIPPED));
+
+ if(!gf_pipe(helper_getc, pc)){
+ help_text = (char *) store->txt;
+ store->txt = (void *) NULL;
+ }
+
+ gf_clear_so_writec(store);
+ so_give(&store);
+ }
+ }
+
+ return(help_text);
+}
+
+
+/*
+ *
+ */
+int
+help_popup(SCROLL_S *sparms, int in_handle)
+{
+ MPopup hp_menu[10];
+ int i = -1;
+
+ if(in_handle){
+ hp_menu[++i].type = tQueue;
+ hp_menu[i].label.style = lNormal;
+ hp_menu[i].label.string = "View Help Section";
+ hp_menu[i].data.val = 'V';
+ }
+
+ hp_menu[++i].type = tQueue;
+ hp_menu[i].label.style = lNormal;
+ hp_menu[i].label.string = "Exit Help";
+ hp_menu[i].data.val = 'E';
+
+ hp_menu[++i].type = tTail;
+
+ mswin_popup(hp_menu);
+
+ return(0);
+}
+
+
+/*
+ *
+ */
+int
+help_subsection_popup(SCROLL_S *sparms, int in_handle)
+{
+ MPopup hp_menu[10];
+ int i = -1;
+
+ if(in_handle){
+ hp_menu[++i].type = tQueue;
+ hp_menu[i].label.style = lNormal;
+ hp_menu[i].label.string = "View Help Section";
+ hp_menu[i].data.val = 'V';
+ }
+
+ hp_menu[++i].type = tQueue;
+ hp_menu[i].label.style = lNormal;
+ hp_menu[i].label.string = "Previous Help Section";
+ hp_menu[i].data.val = 'P';
+
+ hp_menu[++i].type = tQueue;
+ hp_menu[i].label.style = lNormal;
+ hp_menu[i].label.string = "Exit Help";
+ hp_menu[i].data.val = 'E';
+
+ hp_menu[++i].type = tTail;
+
+ if(mswin_popup(hp_menu) == (in_handle ? 1 : 0))
+ /*(void) helper_internal()*/;
+
+ return(0);
+}
+
+#endif /* _WINDOWS */
diff --git a/alpine/help.h b/alpine/help.h
new file mode 100644
index 00000000..51cd5d0f
--- /dev/null
+++ b/alpine/help.h
@@ -0,0 +1,51 @@
+/*
+ * $Id: help.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_HELP_INCLUDED
+#define PINE_HELP_INCLUDED
+
+
+#include "../pith/help.h"
+
+
+/*
+ * Flags to help manage help display
+ */
+#define HLPD_NONE 0
+#define HLPD_NEWWIN 0x01
+#define HLPD_SECONDWIN 0x02
+#define HLPD_SIMPLE 0x04
+#define HLPD_FROMHELP 0x08
+
+
+/* exported protoypes */
+int url_local_helper(char *);
+int url_local_config(char *);
+void init_helper_getc(char **);
+int helper_getc(char *);
+int helper(HelpType, char *, int);
+void review_messages(void);
+void print_help(char **);
+#ifdef _WINDOWS
+char *pcpine_help(HelpType);
+#endif
+#ifdef DEBUG
+void dump_config(struct pine *, gf_io_t, int);
+void dump_pine_struct(struct pine *, gf_io_t);
+#endif
+
+
+#endif /* PINE_HELP_INCLUDED */
diff --git a/alpine/imap.c b/alpine/imap.c
new file mode 100644
index 00000000..074b9f6d
--- /dev/null
+++ b/alpine/imap.c
@@ -0,0 +1,3011 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ imap.c
+ The call back routines for the c-client/imap
+ - handles error messages and other notification
+ - handles prelimirary notification of new mail and expunged mail
+ - prompting for imap server login and password
+
+ ====*/
+
+#include "headers.h"
+#include "alpine.h"
+#include "imap.h"
+#include "status.h"
+#include "mailview.h"
+#include "mailcmd.h"
+#include "radio.h"
+#include "keymenu.h"
+#include "signal.h"
+#include "mailpart.h"
+#include "mailindx.h"
+#include "arg.h"
+#include "busy.h"
+#include "titlebar.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/msgno.h"
+#include "../pith/filter.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/list.h"
+#include "../pith/margin.h"
+#ifdef SMIME
+#include "../pith/smime.h"
+#endif /* SMIME */
+
+#if (WINCRED > 0)
+#include <wincred.h>
+#define TNAME "UWash_Alpine_"
+#define TNAMESTAR "UWash_Alpine_*"
+
+/*
+ * WinCred Function prototypes
+ */
+typedef BOOL (WINAPI CREDWRITEW) ( __in PCREDENTIALW Credential, __in DWORD Flags );
+typedef BOOL (WINAPI CREDENUMERATEW) ( __in LPCWSTR Filter, __reserved DWORD Flags,
+ __out DWORD *Count, __deref_out_ecount(*Count) PCREDENTIALW **Credential );
+typedef BOOL (WINAPI CREDDELETEW) ( __in LPCWSTR TargetName, __in DWORD Type,
+ __reserved DWORD Flags );
+typedef VOID (WINAPI CREDFREE) ( __in PVOID Buffer );
+
+/*
+ * WinCred functions
+ */
+int g_CredInited = 0; /* 1 for loaded successfully,
+ * -1 for not available.
+ * 0 for not initialized yet.
+ */
+CREDWRITEW *g_CredWriteW;
+CREDENUMERATEW *g_CredEnumerateW;
+CREDDELETEW *g_CredDeleteW;
+CREDFREE *g_CredFree;
+
+#endif /* WINCRED */
+
+#ifdef APPLEKEYCHAIN
+#include <Security/SecKeychain.h>
+#include <Security/SecKeychainItem.h>
+#include <Security/SecKeychainSearch.h>
+#define TNAME "UWash_Alpine"
+#define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
+
+int macos_store_pass_prompt(void);
+void macos_set_store_pass_prompt(int);
+
+static int storepassprompt = -1;
+#endif /* APPLEKEYCHAIN */
+
+
+/*
+ * Internal prototypes
+ */
+void mm_login_alt_cue(NETMBX *);
+long pine_tcptimeout_noscreen(long, long, char *);
+int answer_cert_failure(int, MSGNO_S *, SCROLL_S *);
+
+#ifdef LOCAL_PASSWD_CACHE
+int read_passfile(char *, MMLOGIN_S **);
+void write_passfile(char *, MMLOGIN_S *);
+int preserve_prompt(void);
+void update_passfile_hostlist(char *, char *, STRLIST_S *, int);
+
+static MMLOGIN_S *passfile_cache = NULL;
+static int using_passfile = -1;
+int save_password = 1;
+#endif /* LOCAL_PASSWD_CACHE */
+
+#ifdef PASSFILE
+char xlate_in(int);
+char xlate_out(char);
+char *passfile_name(char *, char *, size_t);
+int line_get(char *, size_t, char **);
+#endif /* PASSFILE */
+
+#if (WINCRED > 0)
+void ask_erase_credentials(void);
+int init_wincred_funcs(void);
+#endif /* WINCRED */
+
+
+static char *details_cert, *details_host, *details_reason;
+
+
+/*----------------------------------------------------------------------
+ recieve notification from IMAP
+
+ Args: stream -- Mail stream message is relavant to
+ string -- The message text
+ errflg -- Set if it is a serious error
+
+ Result: message displayed in status line
+
+ The facility is for general notices, such as connection to server;
+ server shutting down etc... It is used infrequently.
+ ----------------------------------------------------------------------*/
+void
+mm_notify(MAILSTREAM *stream, char *string, long int errflg)
+{
+ time_t now;
+ struct tm *tm_now;
+
+ now = time((time_t *)0);
+ tm_now = localtime(&now);
+
+ /* be sure to log the message... */
+#ifdef DEBUG
+ if(ps_global->debug_imap || ps_global->debugmem)
+ dprint((errflg == TCPDEBUG ? 7 : 2,
+ "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
+ tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
+ tm_now->tm_mon+1, tm_now->tm_mday,
+ (!errflg) ? "babble" :
+ (errflg == ERROR) ? "error" :
+ (errflg == WARN) ? "warning" :
+ (errflg == PARSE) ? "parse" :
+ (errflg == TCPDEBUG) ? "tcp" :
+ (errflg == BYE) ? "bye" : "unknown",
+ (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
+ string ? string : "?"));
+#endif
+
+ snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
+ (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
+ MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
+ string);
+ ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
+ : sizeof(ps_global->last_error)-1] = '\0';
+
+ /*
+ * Then either set special bits in the pine struct or
+ * display the message if it's tagged as an "ALERT" or
+ * its errflg > NIL (i.e., WARN, or ERROR)
+ */
+ if(errflg == BYE)
+ /*
+ * We'd like to sp_mark_stream_dead() here but we can't do that because
+ * that might call mail_close and we are already in a c-client callback.
+ * So just set the dead bit and clean it up later.
+ */
+ sp_set_dead_stream(stream, 1);
+ else if(!strncmp(string, "[TRYCREATE]", 11))
+ ps_global->try_to_create = 1;
+ else if(!strncmp(string, "[REFERRAL ", 10))
+ ; /* handled in the imap_referral() callback */
+ else if(!strncmp(string, "[ALERT]", 7))
+ q_status_message2(SM_MODAL, 3, 3,
+ _("Alert received while accessing \"%s\": %s"),
+ (stream && stream->mailbox)
+ ? stream->mailbox : "-no folder-",
+ rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
+ SIZEOF_20KBUF-10000, string));
+ else if(!strncmp(string, "[UNSEEN ", 8)){
+ char *p;
+ long n = 0;
+
+ for(p = string + 8; isdigit(*p); p++)
+ n = (n * 10) + (*p - '0');
+
+ sp_set_first_unseen(stream, n);
+ }
+ else if(!strncmp(string, "[READ-ONLY]", 11)
+ && !(stream && stream->mailbox && IS_NEWS(stream)))
+ q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
+ (stream && stream->mailbox)
+ ? stream->mailbox : "-no folder-",
+ string + 11);
+ else if((errflg && errflg != BYE && errflg != PARSE)
+ && !ps_global->noshow_error
+ && !(errflg == WARN
+ && (ps_global->noshow_warn || (stream && stream->unhealthy))))
+ q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
+ 3, 6, ps_global->last_error);
+}
+
+
+/*----------------------------------------------------------------------
+ Queue imap log message for display in the message line
+
+ Args: string -- The message
+ errflg -- flag set to 1 if pertains to an error
+
+ Result: Message queued for display
+
+ The c-client/imap reports most of it's status and errors here
+ ---*/
+void
+mm_log(char *string, long int errflg)
+{
+ char message[sizeof(ps_global->c_client_error)];
+ char *occurence;
+ int was_capitalized;
+ static char saw_kerberos_init_warning;
+ time_t now;
+ struct tm *tm_now;
+
+ now = time((time_t *)0);
+ tm_now = localtime(&now);
+
+ dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 :
+ (errflg == TCPDEBUG) ? 10 : 2,
+ "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
+ tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
+ tm_now->tm_mon+1, tm_now->tm_mday,
+ (!errflg) ? "babble" :
+ (errflg == ERROR) ? "error" :
+ (errflg == WARN) ? "warning" :
+ (errflg == PARSE) ? "parse" :
+ (errflg == TCPDEBUG) ? "tcp" :
+ (errflg == BYE) ? "bye" : "unknown",
+ string ? string : "?"));
+
+ if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
+ ps_global->try_to_create = 1;
+ return;
+ }
+ else if(ps_global->try_to_create
+ || !strncmp(string, "[CLOSED]", 8)
+ || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
+ /*
+ * Don't display if creating new folder OR
+ * warning about a dead stream ...
+ */
+ return;
+
+ strncpy(message, string, sizeof(message));
+ message[sizeof(message) - 1] = '\0';
+
+ if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
+ if(saw_kerberos_init_warning)
+ return;
+
+ saw_kerberos_init_warning = 1;
+ }
+
+ /*---- replace all "mailbox" with "folder" ------*/
+ occurence = srchstr(message, "mailbox");
+ while(occurence) {
+ if(!*(occurence+7)
+ || isspace((unsigned char) *(occurence+7))
+ || *(occurence+7) == ':'){
+ was_capitalized = isupper((unsigned char) *occurence);
+ rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
+ if(was_capitalized)
+ *occurence = (errflg == PARSE ? 'A' : 'F');
+ }
+ else
+ occurence += 7;
+
+ occurence = srchstr(occurence, "mailbox");
+ }
+
+ /*---- replace all "GSSAPI" with "Kerberos" ------*/
+ occurence = srchstr(message, "GSSAPI");
+ while(occurence) {
+ if(!*(occurence+6)
+ || isspace((unsigned char) *(occurence+6))
+ || *(occurence+6) == ':')
+ rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
+ else
+ occurence += 6;
+
+ occurence = srchstr(occurence, "GSSAPI");
+ }
+
+ if(errflg == ERROR)
+ ps_global->mm_log_error = 1;
+
+ if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
+ strncpy(ps_global->c_client_error, message,
+ sizeof(ps_global->c_client_error));
+
+ if(ps_global->noshow_error
+ || (ps_global->noshow_warn && errflg == WARN)
+ || !(errflg == ERROR || errflg == WARN))
+ return; /* Only care about errors; don't print when asked not to */
+
+ /*---- Display the message ------*/
+ q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
+ 3, 5, message);
+ strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
+ ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
+}
+
+
+void
+mm_login_work(NETMBX *mb, char *user, char *pwd, long int trial,
+ char *usethisprompt, char *altuserforcache)
+{
+ char prompt[1000], *last;
+ char port[20], non_def_port[20], insecure[20];
+ char defuser[NETMAXUSER];
+ char hostleadin[80], hostname[200], defubuf[200];
+ char logleadin[80], pwleadin[50];
+ char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
+ /* TRANSLATORS: when logging in, this text is added to the prompt to show
+ that the password will be sent unencrypted over the network. This is
+ just a warning message that gets added parenthetically when the user
+ is asked for a password. */
+ char *insec = _(" (INSECURE)");
+ /* TRANSLATORS: Retrying is shown when the user is being asked for a password
+ after having already failed at least once. */
+ char *retry = _("Retrying - ");
+ /* TRANSLATORS: A label for the hostname that the user is logging in on */
+ char *hostlabel = _("HOST");
+ /* TRANSLATORS: user is logging in as a particular user (a particular
+ login name), this is just labelling that user name. */
+ char *userlabel = _("USER");
+ STRLIST_S hostlist[2];
+ HelpType help ;
+ int len, rc, q_line, flags;
+ int oespace, avail, need, save_dont_use;
+ int save_in_init;
+ struct servent *sv;
+#if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
+ int preserve_password = -1;
+#endif
+
+ dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
+ trial, mb->user ? mb->user : "(null)",
+ mb->service ? mb->service : "(null)",
+ mb->port ? " port=" : "",
+ mb->port ? comatose(mb->port) : "",
+ altuserforcache ? " altuserforcache =" : "",
+ altuserforcache ? altuserforcache : ""));
+ q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
+
+ save_in_init = ps_global->in_init_seq;
+ ps_global->in_init_seq = 0;
+ ps_global->no_newmail_check_from_optionally_enter = 1;
+
+ /* make sure errors are seen */
+ if(ps_global->ttyo)
+ flush_status_messages(0);
+
+ /*
+ * Add port number to hostname if going through a tunnel or something
+ */
+ non_def_port[0] = '\0';
+ if(mb->port && mb->service &&
+ (sv = getservbyname(mb->service, "tcp")) &&
+ (mb->port != ntohs(sv->s_port))){
+ snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
+ non_def_port[sizeof(non_def_port)-1] = '\0';
+ dprint((9, "mm_login: using non-default port=%s\n",
+ non_def_port ? non_def_port : "?"));
+ }
+
+ /*
+ * set up host list for sybil servers...
+ */
+ if(*non_def_port){
+ strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
+ hostlist0[sizeof(hostlist0)-1] = '\0';
+ strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
+ hostlist0[sizeof(hostlist0)-1] = '\0';
+ hostlist[0].name = hostlist0;
+ if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
+ strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
+ hostlist1[sizeof(hostlist1)-1] = '\0';
+ strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
+ hostlist1[sizeof(hostlist1)-1] = '\0';
+ hostlist[0].next = &hostlist[1];
+ hostlist[1].name = hostlist1;
+ hostlist[1].next = NULL;
+ }
+ else
+ hostlist[0].next = NULL;
+ }
+ else{
+ hostlist[0].name = mb->host;
+ if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
+ hostlist[0].next = &hostlist[1];
+ hostlist[1].name = mb->orighost;
+ hostlist[1].next = NULL;
+ }
+ else
+ hostlist[0].next = NULL;
+ }
+
+ if(hostlist[0].name){
+ dprint((9, "mm_login: host=%s\n",
+ hostlist[0].name ? hostlist[0].name : "?"));
+ if(hostlist[0].next && hostlist[1].name){
+ dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
+ }
+ }
+
+ /*
+ * Initialize user name with either
+ * 1) /user= value in the stream being logged into,
+ * or 2) the user name we're running under.
+ *
+ * Note that VAR_USER_ID is not yet initialized if this login is
+ * the one to access the remote config file. In that case, the user
+ * can supply the username in the config file name with /user=.
+ */
+ if(trial == 0L && !altuserforcache){
+ strncpy(user, (*mb->user) ? mb->user :
+ ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
+ NETMAXUSER);
+ user[NETMAXUSER-1] = '\0';
+
+ /* try last working password associated with this host. */
+ if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
+ (mb->sslflag||mb->tlsflag))){
+ dprint((9, "mm_login: found a password to try\n"));
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+
+#ifdef LOCAL_PASSWD_CACHE
+ /* check to see if there's a password left over from last session */
+ if(get_passfile_passwd(ps_global->pinerc, pwd,
+ user, hostlist, (mb->sslflag||mb->tlsflag))){
+ imap_set_passwd(&mm_login_list, pwd, user,
+ hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
+ update_passfile_hostlist(ps_global->pinerc, user, hostlist,
+ (mb->sslflag||mb->tlsflag));
+ dprint((9, "mm_login: found a password in passfile to try\n"));
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+#endif /* LOCAL_PASSWD_CACHE */
+
+ /*
+ * If no explicit user name supplied and we've not logged in
+ * with our local user name, see if we've visited this
+ * host before as someone else.
+ */
+ if(!*mb->user &&
+ ((last = imap_get_user(mm_login_list, hostlist))
+#ifdef LOCAL_PASSWD_CACHE
+ ||
+ (last = get_passfile_user(ps_global->pinerc, hostlist))
+#endif /* LOCAL_PASSWD_CACHE */
+ )){
+ strncpy(user, last, NETMAXUSER);
+ user[NETMAXUSER-1] = '\0';
+ dprint((9, "mm_login: found user=%s\n",
+ user ? user : "?"));
+
+ /* try last working password associated with this host/user. */
+ if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
+ (mb->sslflag||mb->tlsflag))){
+ dprint((9,
+ "mm_login: found a password for user=%s to try\n",
+ user ? user : "?"));
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+
+#ifdef LOCAL_PASSWD_CACHE
+ /* check to see if there's a password left over from last session */
+ if(get_passfile_passwd(ps_global->pinerc, pwd,
+ user, hostlist, (mb->sslflag||mb->tlsflag))){
+ imap_set_passwd(&mm_login_list, pwd, user,
+ hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
+ update_passfile_hostlist(ps_global->pinerc, user, hostlist,
+ (mb->sslflag||mb->tlsflag));
+ dprint((9,
+ "mm_login: found a password for user=%s in passfile to try\n",
+ user ? user : "?"));
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+#endif /* LOCAL_PASSWD_CACHE */
+ }
+
+#if !defined(DOS) && !defined(OS2)
+ if(!*mb->user && !*user &&
+ (last = (ps_global->ui.login && ps_global->ui.login[0])
+ ? ps_global->ui.login : NULL)
+ ){
+ strncpy(user, last, NETMAXUSER);
+ user[NETMAXUSER-1] = '\0';
+ dprint((9, "mm_login: found user=%s\n",
+ user ? user : "?"));
+
+ /* try last working password associated with this host. */
+ if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
+ (mb->sslflag||mb->tlsflag))){
+ dprint((9, "mm_login:ui: found a password to try\n"));
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+
+#ifdef LOCAL_PASSWD_CACHE
+ /* check to see if there's a password left over from last session */
+ if(get_passfile_passwd(ps_global->pinerc, pwd,
+ user, hostlist, (mb->sslflag||mb->tlsflag))){
+ imap_set_passwd(&mm_login_list, pwd, user,
+ hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
+ update_passfile_hostlist(ps_global->pinerc, user, hostlist,
+ (mb->sslflag||mb->tlsflag));
+ dprint((9, "mm_login:ui: found a password in passfile to try\n"));
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+#endif /* LOCAL_PASSWD_CACHE */
+ }
+#endif
+ }
+
+ user[NETMAXUSER-1] = '\0';
+
+ if(trial == 0)
+ retry = "";
+
+ /*
+ * Even if we have a user now, user gets a chance to change it.
+ */
+ ps_global->mangled_footer = 1;
+ if(!*mb->user && !altuserforcache){
+
+ help = NO_HELP;
+
+ /*
+ * Instead of offering user with a value that the user can edit,
+ * we offer [user] as a default so that the user can type CR to
+ * use it. Otherwise, the user has to type in whole name.
+ */
+ strncpy(defuser, user, sizeof(defuser)-1);
+ defuser[sizeof(defuser)-1] = '\0';
+ user[0] = '\0';
+
+ /*
+ * Need space for "Retrying - "
+ * "+ HOST: "
+ * hostname
+ * " (INSECURE)"
+ * ENTER LOGIN NAME
+ * " [defuser] : "
+ * about 15 chars for input
+ */
+
+ snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
+ (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
+ hostleadin[sizeof(hostleadin)-1] = '\0';
+
+ strncpy(hostname, mb->host, sizeof(hostname)-1);
+ hostname[sizeof(hostname)-1] = '\0';
+
+ /*
+ * Add port number to hostname if going through a tunnel or something
+ */
+ if(*non_def_port)
+ strncpy(port, non_def_port, sizeof(port));
+ else
+ port[0] = '\0';
+
+ insecure[0] = '\0';
+ /* if not encrypted and SSL/TLS is supported */
+ if(!(mb->sslflag||mb->tlsflag) &&
+ mail_parameters(NIL, GET_SSLDRIVER, NIL))
+ strncpy(insecure, insec, sizeof(insecure));
+
+ /* TRANSLATORS: user is being asked to type in their login name */
+ snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
+
+ snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
+ (*defuser) ? defuser : "",
+ (*defuser) ? "]" : "");
+ defubuf[sizeof(defubuf)-1] = '\0';
+ /* space reserved after prompt */
+ oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
+
+ avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
+ need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
+ utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
+
+ /* If we're retrying cut the hostname back to the first word. */
+ if(avail < need && trial > 0){
+ char *p;
+
+ len = strlen(hostname);
+ if((p = strchr(hostname, '.')) != NULL){
+ *p = '\0';
+ need -= (len - strlen(hostname));
+ }
+ }
+
+ if(avail < need){
+ need -= utf8_width(retry);
+ retry = "";
+
+ if(avail < need){
+
+ /* reduce length of logleadin */
+ len = utf8_width(logleadin);
+ /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
+ longer version doesn't fit on screen */
+ snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
+ need -= (len - utf8_width(logleadin));
+
+ if(avail < need){
+ /* get two spaces from hostleadin */
+ len = utf8_width(hostleadin);
+ snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
+ (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
+ hostleadin[sizeof(hostleadin)-1] = '\0';
+ need -= (len - utf8_width(hostleadin));
+
+ /* get rid of port */
+ if(avail < need && strlen(port) > 0){
+ need -= strlen(port);
+ port[0] = '\0';
+ }
+
+ if(avail < need){
+ int reduce_to;
+
+ /*
+ * Reduce space for hostname. Best we can do is 6 chars
+ * with hos...
+ */
+ reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
+ len = strlen(hostname);
+ strncpy(hostname+reduce_to-3, "...", 4);
+ need -= (len - strlen(hostname));
+
+ if(avail < need && strlen(insecure) > 0){
+ if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
+ need -= 3;
+ insecure[strlen(insecure)-4] = ')';
+ insecure[strlen(insecure)-3] = '\0';
+ }
+ else{
+ need -= utf8_width(insecure);
+ insecure[0] = '\0';
+ }
+ }
+
+ if(avail < need){
+ if(strlen(defubuf) > 3){
+ len = strlen(defubuf);
+ strncpy(defubuf, " [..] :", 9);
+ need -= (len - strlen(defubuf));
+ }
+
+ if(avail < need)
+ strncpy(defubuf, ":", 2);
+
+ /*
+ * If it still doesn't fit, optionally_enter gets
+ * to worry about it.
+ */
+ }
+ }
+ }
+ }
+ }
+
+ snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
+ retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ while(1) {
+ if(ps_global->ttyo)
+ mm_login_alt_cue(mb);
+
+ flags = OE_APPEND_CURRENT;
+ save_dont_use = ps_global->dont_use_init_cmds;
+ ps_global->dont_use_init_cmds = 1;
+#ifdef _WINDOWS
+ if(!*user && *defuser){
+ strncpy(user, defuser, NETMAXUSER);
+ user[NETMAXUSER-1] = '\0';
+ }
+
+ rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
+#ifdef LOCAL_PASSWD_CACHE
+ is_using_passfile() ? 1 :
+#endif /* LOCAL_PASSWD_CACHE */
+ 0, 0, &preserve_password);
+ ps_global->dont_use_init_cmds = save_dont_use;
+ if(rc == 0 && *user && *pwd)
+ goto nopwpmt;
+#else /* !_WINDOWS */
+ rc = optionally_enter(user, q_line, 0, NETMAXUSER,
+ prompt, NULL, help, &flags);
+#endif /* !_WINDOWS */
+ ps_global->dont_use_init_cmds = save_dont_use;
+
+ if(rc == 3) {
+ help = help == NO_HELP ? h_oe_login : NO_HELP;
+ continue;
+ }
+
+ /* default */
+ if(rc == 0 && !*user){
+ strncpy(user, defuser, NETMAXUSER);
+ user[NETMAXUSER-1] = '\0';
+ }
+
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 1 || !user[0]) {
+ ps_global->user_says_cancel = (rc == 1);
+ user[0] = '\0';
+ pwd[0] = '\0';
+ }
+ }
+ else{
+ strncpy(user, mb->user, NETMAXUSER);
+ user[NETMAXUSER-1] = '\0';
+ }
+
+ user[NETMAXUSER-1] = '\0';
+ pwd[NETMAXPASSWD-1] = '\0';
+
+ if(!(user[0] || altuserforcache)){
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+
+ /*
+ * Now that we have a user, we can check in the cache again to see
+ * if there is a password there. Try last working password associated
+ * with this host and user.
+ */
+ if(trial == 0L && !*mb->user && !altuserforcache){
+ if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
+ (mb->sslflag||mb->tlsflag))){
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+
+#ifdef LOCAL_PASSWD_CACHE
+ if(get_passfile_passwd(ps_global->pinerc, pwd,
+ user, hostlist, (mb->sslflag||mb->tlsflag))){
+ imap_set_passwd(&mm_login_list, pwd, user,
+ hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+#endif /* LOCAL_PASSWD_CACHE */
+ }
+ else if(trial == 0 && altuserforcache){
+ if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
+ (mb->sslflag||mb->tlsflag))){
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+
+#ifdef LOCAL_PASSWD_CACHE
+ if(get_passfile_passwd(ps_global->pinerc, pwd,
+ altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
+ imap_set_passwd(&mm_login_list, pwd, altuserforcache,
+ hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+#endif /* LOCAL_PASSWD_CACHE */
+ }
+
+ /*
+ * Didn't find password in cache or this isn't the first try. Ask user.
+ */
+ help = NO_HELP;
+
+ /*
+ * Need space for "Retrying - "
+ * "+ HOST: "
+ * hostname
+ * " (INSECURE) "
+ * " USER: "
+ * user
+ * " ENTER PASSWORD: "
+ * about 15 chars for input
+ */
+
+ snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
+ (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
+
+ strncpy(hostname, mb->host, sizeof(hostname)-1);
+ hostname[sizeof(hostname)-1] = '\0';
+
+ /*
+ * Add port number to hostname if going through a tunnel or something
+ */
+ if(*non_def_port)
+ strncpy(port, non_def_port, sizeof(port));
+ else
+ port[0] = '\0';
+
+ insecure[0] = '\0';
+
+ /* if not encrypted and SSL/TLS is supported */
+ if(!(mb->sslflag||mb->tlsflag) &&
+ mail_parameters(NIL, GET_SSLDRIVER, NIL))
+ strncpy(insecure, insec, sizeof(insecure));
+
+ if(usethisprompt){
+ strncpy(logleadin, usethisprompt, sizeof(logleadin));
+ logleadin[sizeof(logleadin)-1] = '\0';
+ defubuf[0] = '\0';
+ user[0] = '\0';
+ }
+ else{
+ snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
+
+ strncpy(defubuf, user, sizeof(defubuf)-1);
+ defubuf[sizeof(defubuf)-1] = '\0';
+ }
+
+ /* TRANSLATORS: user is being asked to type in their password */
+ snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
+
+ /* space reserved after prompt */
+ oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
+
+ avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
+ need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
+ utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
+ utf8_width(pwleadin) + oespace;
+
+ if(avail < need && trial > 0){
+ char *p;
+
+ len = strlen(hostname);
+ if((p = strchr(hostname, '.')) != NULL){
+ *p = '\0';
+ need -= (len - strlen(hostname));
+ }
+ }
+
+ if(avail < need){
+ need -= utf8_width(retry);
+ retry = "";
+
+ if(avail < need){
+
+ if(!usethisprompt){
+ snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
+ need--;
+ }
+
+ rplstr(pwleadin, sizeof(pwleadin), 1, "");
+ need--;
+
+ if(avail < need){
+ /* get two spaces from hostleadin */
+ len = utf8_width(hostleadin);
+ snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
+ (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
+ hostleadin[sizeof(hostleadin)-1] = '\0';
+ need -= (len - utf8_width(hostleadin));
+
+ /* get rid of port */
+ if(avail < need && strlen(port) > 0){
+ need -= strlen(port);
+ port[0] = '\0';
+ }
+
+ if(avail < need){
+ len = utf8_width(pwleadin);
+ /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
+ snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
+ need -= (len - utf8_width(pwleadin));
+ }
+ }
+
+ if(avail < need){
+ int reduce_to;
+
+ /*
+ * Reduce space for hostname. Best we can do is 6 chars
+ * with hos...
+ */
+ reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
+ len = strlen(hostname);
+ strncpy(hostname+reduce_to-3, "...", 4);
+ need -= (len - strlen(hostname));
+
+ if(avail < need && strlen(insecure) > 0){
+ if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
+ need -= 3;
+ insecure[strlen(insecure)-4] = ')';
+ insecure[strlen(insecure)-3] = '\0';
+ }
+ else{
+ need -= utf8_width(insecure);
+ insecure[0] = '\0';
+ }
+ }
+
+ if(avail < need){
+ len = utf8_width(logleadin);
+ strncpy(logleadin, " ", sizeof(logleadin));
+ logleadin[sizeof(logleadin)-1] = '\0';
+ need -= (len - utf8_width(logleadin));
+
+ if(avail < need){
+ reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
+ if(reduce_to)
+ strncpy(defubuf+reduce_to-3, "...", 4);
+ else
+ defubuf[0] = '\0';
+ }
+ }
+ }
+ }
+ }
+
+ snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
+ retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ *pwd = '\0';
+ while(1) {
+ if(ps_global->ttyo)
+ mm_login_alt_cue(mb);
+
+ save_dont_use = ps_global->dont_use_init_cmds;
+ ps_global->dont_use_init_cmds = 1;
+ flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
+#ifdef _WINDOWS
+ rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD, 0, 1,
+ &preserve_password);
+#else /* !_WINDOWS */
+ rc = optionally_enter(pwd, q_line, 0, NETMAXPASSWD,
+ prompt, NULL, help, &flags);
+#endif /* !_WINDOWS */
+ ps_global->dont_use_init_cmds = save_dont_use;
+
+ if(rc == 3) {
+ help = help == NO_HELP ? h_oe_passwd : NO_HELP;
+ }
+ else if(rc == 4){
+ }
+ else
+ break;
+ }
+
+ if(rc == 1 || !pwd[0]) {
+ ps_global->user_says_cancel = (rc == 1);
+ user[0] = pwd[0] = '\0';
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+ ps_global->in_init_seq = save_in_init;
+ return;
+ }
+
+#ifdef _WINDOWS
+ nopwpmt:
+#endif
+ /* remember the password for next time */
+ if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
+ imap_set_passwd(&mm_login_list, pwd,
+ altuserforcache ? altuserforcache : user, hostlist,
+ (mb->sslflag||mb->tlsflag), 0, 0);
+#ifdef LOCAL_PASSWD_CACHE
+ /* if requested, remember it on disk for next session */
+ if(save_password)
+ set_passfile_passwd(ps_global->pinerc, pwd,
+ altuserforcache ? altuserforcache : user, hostlist,
+ (mb->sslflag||mb->tlsflag),
+ (preserve_password == -1 ? 0
+ : (preserve_password == 0 ? 2 :1)));
+#endif /* LOCAL_PASSWD_CACHE */
+
+ ps_global->no_newmail_check_from_optionally_enter = 0;
+}
+
+
+void
+mm_login_alt_cue(NETMBX *mb)
+{
+ if(ps_global->ttyo){
+ COLOR_PAIR *lastc;
+
+ lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
+ ps_global->VAR_TITLE_BACK_COLOR,
+ PSC_REV | PSC_RET);
+
+ mark_titlebar_dirty();
+ PutLine0(0, ps_global->ttyo->screen_cols - 1,
+ (mb->sslflag||mb->tlsflag) ? "+" : " ");
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ fflush(stdout);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Receive notification of an error writing to disk
+
+ Args: stream -- The stream the error occured on
+ errcode -- The system error code (errno)
+ serious -- Flag indicating error is serious (mail may be lost)
+
+Result: If error is non serious, the stream is marked as having an error
+ and deletes are disallowed until error clears
+ If error is serious this goes modal, allowing the user to retry
+ or get a shell escape to fix the condition. When the condition is
+ serious it means that mail existing in the mailbox will be lost
+ if Pine exits without writing, so we try to induce the user to
+ fix the error, go get someone that can fix the error, or whatever
+ and don't provide an easy way out.
+ ----*/
+long
+mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
+{
+ int i, j;
+ char *p, *q, *s;
+ static ESCKEY_S de_opts[] = {
+ {'r', 'r', "R", "Retry"},
+ {'f', 'f', "F", "FileBrowser"},
+ {'s', 's', "S", "ShellPrompt"},
+ {-1, 0, NULL, NULL}
+ };
+#define DE_COLS (ps_global->ttyo->screen_cols)
+#define DE_LINE (ps_global->ttyo->screen_rows - 3)
+
+#define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
+#define DE_PMT \
+ "Disk error! Choose Retry, or the File browser or Shell to clean up: "
+#define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
+#define DE_STR2 \
+ "The reported error number is %s. The last reported mail error was:"
+ static char *de_msg[] = {
+ "Please try to correct the error preventing Alpine from saving your",
+ "mail folder. For example if the disk is out of space try removing",
+ "unneeded files. You might also contact your system administrator.",
+ "",
+ "Both Alpine's File Browser and an option to enter the system's",
+ "command prompt are offered to aid in fixing the problem. When",
+ "you believe the problem is resolved, choose the \"Retry\" option.",
+ "Be aware that messages may be lost or this folder left in an",
+ "inaccessible condition if you exit or kill Alpine before the problem",
+ "is resolved.",
+ NULL};
+ static char *de_shell_msg[] = {
+ "\n\nPlease attempt to correct the error preventing saving of the",
+ "mail folder. If you do not know how to correct the problem, contact",
+ "your system administrator. To return to Alpine, type \"exit\".",
+ NULL};
+
+ dprint((0,
+ "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
+ DE_FOLDER(stream), errcode, serious ? "" : "not "));
+ dprint((0, "***** message: \"%s\"\n\n",
+ ps_global->last_error ? ps_global->last_error : "?"));
+
+ if(!serious) {
+ sp_set_io_error_on_stream(stream, 1);
+ return (1) ;
+ }
+
+ while(1){
+ /* replace pine's body display with screen full of explanatory text */
+ ClearLine(2);
+ PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
+ - strlen(DE_FOLDER(stream)))/2, 0),
+ DE_STR1, DE_FOLDER(stream));
+ ClearLine(3);
+ PutLine1(3, 4, DE_STR2, long2string(errcode));
+
+ PutLine0(4, 0, " \"");
+ removing_leading_white_space(ps_global->last_error);
+ for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
+ for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
+ if(isspace((unsigned char)*q))
+ s = q;
+
+ if(*q && s)
+ q = s;
+
+ while(p < q)
+ Writechar(*p++, 0);
+
+ if(*(p = q)){
+ ClearLine(++i);
+ PutLine0(i, 0, " ");
+ while(*p && isspace((unsigned char)*p))
+ p++;
+ }
+ else{
+ Writechar('\"', 0);
+ CleartoEOLN();
+ break;
+ }
+ }
+
+ ClearLine(++i);
+ for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
+ ClearLine(i);
+ PutLine0(i, 0, " ");
+ Write_to_screen(de_msg[i-j]);
+ }
+
+ while(i < DE_LINE)
+ ClearLine(i++);
+
+ switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
+ 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
+ case 'r' : /* Retry! */
+ ps_global->mangled_screen = 1;
+ return(0L);
+
+ case 'f' : /* File Browser */
+ {
+ char full_filename[MAXPATH+1], filename[MAXPATH+1];
+
+ filename[0] = '\0';
+ build_path(full_filename, ps_global->home_dir, filename,
+ sizeof(full_filename));
+ file_lister("DISK ERROR", full_filename, sizeof(full_filename),
+ filename, sizeof(filename), FALSE, FB_SAVE);
+ }
+
+ break;
+
+ case 's' :
+ EndInverse();
+ end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
+ end_tty_driver(ps_global);
+ for(i = 0; de_shell_msg[i]; i++)
+ puts(de_shell_msg[i]);
+
+ /*
+ * Don't use our piping mechanism to spawn a subshell here
+ * since it will the server (thus reentering c-client).
+ * Bad thing to do.
+ */
+#ifdef _WINDOWS
+#else
+ system("csh");
+#endif
+ init_tty_driver(ps_global);
+ init_keyboard(F_ON(F_USE_FK,ps_global));
+ break;
+ }
+
+ if(ps_global->redrawer)
+ (*ps_global->redrawer)();
+ }
+}
+
+
+long
+pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
+{
+ long rv = 1L;
+ char pmt[128];
+
+#ifdef _WINDOWS
+ mswin_killsplash();
+#endif
+
+ if(elapsed >= (long)ps_global->tcp_query_timeout){
+ snprintf(pmt, sizeof(pmt),
+ _("No reply in %s seconds from server %s. Break connection"),
+ long2string(elapsed), host);
+ pmt[sizeof(pmt)-1] = '\0';
+ if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ ps_global->user_says_cancel = 1;
+ return(0L);
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * -------------------------------------------------------------
+ * These are declared in pith/imap.h as mandatory to implement.
+ * -------------------------------------------------------------
+ */
+
+
+/*
+ * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
+ */
+long
+pine_tcptimeout(long int elapsed, long int sincelast, char *host)
+{
+ long rv = 1L; /* keep trying by default */
+ unsigned long ch;
+
+#ifdef DEBUG
+ dprint((1, "tcptimeout: waited %s seconds\n",
+ long2string(elapsed)));
+ if(debugfile)
+ fflush(debugfile);
+#endif
+
+#ifdef _WINDOWS
+ mswin_killsplash();
+#endif
+
+ if(ps_global->noshow_timeout)
+ return(rv);
+
+ if(!ps_global->ttyo)
+ return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
+
+ suspend_busy_cue();
+
+ /*
+ * Prompt after a minute (since by then things are probably really bad)
+ * A prompt timeout means "keep trying"...
+ */
+ if(elapsed >= (long)ps_global->tcp_query_timeout){
+ int clear_inverse;
+
+ ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
+ if((clear_inverse = !InverseState()) != 0)
+ StartInverse();
+
+ Writechar(BELL, 0);
+
+ PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
+ _("No reply in %s seconds from server %s. Break connection?"),
+ long2string(elapsed), host);
+ CleartoEOLN();
+ fflush(stdout);
+ flush_input();
+ ch = read_char(7);
+ if(ch == 'y' || ch == 'Y'){
+ ps_global->user_says_cancel = 1;
+ rv = 0L;
+ }
+
+ if(clear_inverse)
+ EndInverse();
+
+ ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
+ }
+
+ if(rv == 1L){ /* just warn 'em something's up */
+ q_status_message2(SM_ORDER, 0, 0,
+ _("No reply in %s seconds from server %s. Still Waiting..."),
+ long2string(elapsed), host);
+ flush_status_messages(0); /* make sure it's seen */
+ }
+
+ mark_status_dirty(); /* make sure it get's cleared */
+
+ resume_busy_cue((rv == 1) ? 3 : 0);
+
+ return(rv);
+}
+
+QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
+{
+ QUOTALIST *cquota = NULL;
+
+ if(pquota){
+ cquota = mail_newquotalist();
+ if (pquota->name && *pquota->name)
+ cquota->name = cpystr(pquota->name);
+ cquota->usage = pquota->usage;
+ cquota->limit = pquota->limit;
+ if (pquota->next)
+ cquota->next = pine_quotalist_copy(pquota->next);
+ }
+ return cquota;
+}
+
+
+/* c-client callback to handle quota */
+
+void
+pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
+{
+ ps_global->quota = pine_quotalist_copy (pquota);
+}
+
+/*
+ * C-client callback to handle SSL/TLS certificate validation failures
+ *
+ * Returning 0 means error becomes fatal
+ * Non-zero means certificate problem is ignored and SSL session is
+ * established
+ *
+ * We remember the answer and won't re-ask for subsequent open attempts to
+ * the same hostname.
+ */
+long
+pine_sslcertquery(char *reason, char *host, char *cert)
+{
+ char tmp[500];
+ char *unknown = "<unknown>";
+ long rv = 0L;
+ STRLIST_S hostlist;
+ int ok_novalidate = 0, warned = 0;
+
+ dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
+ host ? host : "?", reason ? reason : "?",
+ cert ? cert : "?"));
+
+ hostlist.name = host ? host : "";
+ hostlist.next = NULL;
+
+ /*
+ * See if we've been asked about this host before.
+ */
+ if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
+ /* we were asked before, did we say Yes? */
+ if(ok_novalidate)
+ rv++;
+
+ if(rv){
+ dprint((5,
+ "sslcertificatequery: approved automatically\n"));
+ return(rv);
+ }
+
+ dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
+ }
+
+ if(ps_global->ttyo){
+ SCROLL_S sargs;
+ STORE_S *in_store, *out_store;
+ gf_io_t pc, gc;
+ HANDLE_S *handles = NULL;
+ int the_answer = 'n';
+
+ if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
+ !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
+ goto try_wantto;
+
+ so_puts(in_store, "<HTML><P>");
+ so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
+
+ so_puts(in_store, "<P><CENTER>");
+ so_puts(in_store, host ? host : unknown);
+ so_puts(in_store, "</CENTER>");
+
+ so_puts(in_store, "<P>");
+ so_puts(in_store, _("The reason for the failure was"));
+
+ /* squirrel away details */
+ if(details_host)
+ fs_give((void **)&details_host);
+ if(details_reason)
+ fs_give((void **)&details_reason);
+ if(details_cert)
+ fs_give((void **)&details_cert);
+
+ details_host = cpystr(host ? host : unknown);
+ details_reason = cpystr(reason ? reason : unknown);
+ details_cert = cpystr(cert ? cert : unknown);
+
+ so_puts(in_store, "<P><CENTER>");
+ snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
+ reason ? reason : unknown);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ so_puts(in_store, tmp);
+ so_puts(in_store, "</CENTER>");
+
+ so_puts(in_store, "<P>");
+ so_puts(in_store, _("We have not verified the identity of your server. If you ignore this certificate validation problem and continue, you could end up connecting to an imposter server."));
+
+ so_puts(in_store, "<P>");
+ so_puts(in_store, _("If the certificate validation failure was expected and permanent you may avoid seeing this warning message in the future by adding the option"));
+
+ so_puts(in_store, "<P><CENTER>");
+ so_puts(in_store, "/novalidate-cert");
+ so_puts(in_store, "</CENTER>");
+
+ so_puts(in_store, "<P>");
+ so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
+
+ so_puts(in_store, "<P><CENTER>");
+ so_puts(in_store, host ? host : unknown);
+ so_puts(in_store, "</CENTER>");
+
+ so_puts(in_store, "<P>");
+ so_puts(in_store, _("in your configuration, replace those characters with"));
+
+ so_puts(in_store, "<P><CENTER>");
+ so_puts(in_store, host ? host : unknown);
+ so_puts(in_store, "/novalidate-cert");
+ so_puts(in_store, "</CENTER>");
+
+ so_puts(in_store, "<P>");
+ so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
+
+ so_seek(in_store, 0L, 0);
+ init_handles(&handles);
+ gf_filter_init();
+ gf_link_filter(gf_html2plain,
+ gf_html2plain_opt(NULL,
+ ps_global->ttyo->screen_cols, non_messageview_margin(),
+ &handles, NULL, GFHP_LOCAL_HANDLES));
+ gf_set_so_readc(&gc, in_store);
+ gf_set_so_writec(&pc, out_store);
+ gf_pipe(gc, pc);
+ gf_clear_so_writec(out_store);
+ gf_clear_so_readc(in_store);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.handles = handles;
+ sargs.text.text = so_text(out_store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("help text");
+ sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
+ sargs.proc.tool = answer_cert_failure;
+ sargs.proc.data.p = (void *)&the_answer;
+ sargs.keys.menu = &ans_certquery_keymenu;
+ /* don't want to re-enter c-client */
+ sargs.quell_newmail = 1;
+ setbitmap(sargs.keys.bitmap);
+ sargs.help.text = h_tls_validation_failure;
+ sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
+
+ scrolltool(&sargs);
+
+ if(the_answer == 'y')
+ rv++;
+
+ ps_global->mangled_screen = 1;
+ ps_global->painted_body_on_startup = 0;
+ ps_global->painted_footer_on_startup = 0;
+ so_give(&in_store);
+ so_give(&out_store);
+ free_handles(&handles);
+ if(details_host)
+ fs_give((void **)&details_host);
+ if(details_reason)
+ fs_give((void **)&details_reason);
+ if(details_cert)
+ fs_give((void **)&details_cert);
+ }
+ else{
+ /*
+ * If screen hasn't been initialized yet, use want_to.
+ */
+try_wantto:
+ memset((void *)tmp, 0, sizeof(tmp));
+ strncpy(tmp,
+ reason ? reason : _("SSL/TLS certificate validation failure"),
+ sizeof(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
+
+ if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
+ rv++;
+ }
+
+ if(rv == 0)
+ q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
+ host ? host : unknown);
+
+ imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
+
+ dprint((5, "sslcertificatequery: %s\n",
+ rv ? "approved" : "rejected"));
+
+ return(rv);
+}
+
+
+char *
+pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
+{
+ char buf[MAILTMPLEN];
+
+ if((can_access(mulname, ACCESS_EXISTS) == 0)
+ || !(can_access(name, ACCESS_EXISTS) == 0))
+ return(mulname);
+
+ snprintf(buf, sizeof(buf),
+ _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
+ last_cmpnt(name),
+ strlen(last_cmpnt(name)) > 15 ? "..." : "");
+ buf[sizeof(buf)-1] = '\0';
+ if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
+ rename_file(name, mulname);
+ return(mulname);
+}
+
+
+int
+url_local_certdetails(char *url)
+{
+ if(!struncmp(url, "x-alpine-cert:", 14)){
+ STORE_S *store;
+ SCROLL_S sargs;
+ char *folded;
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 7, 10,
+ _("Error allocating space for details."));
+ return(0);
+ }
+
+ so_puts(store, _("Host given by user:\n\n "));
+ so_puts(store, details_host);
+ so_puts(store, _("\n\nReason for failure:\n\n "));
+ so_puts(store, details_reason);
+ so_puts(store, _("\n\nCertificate being verified:\n\n"));
+ folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ so_puts(store, "\n");
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Details");
+ sargs.bar.title = _("CERT VALIDATION DETAILS");
+ sargs.help.text = NO_HELP;
+ sargs.help.title = NULL;
+ sargs.quell_newmail = 1;
+ sargs.help.text = h_tls_failure_details;
+ sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
+
+ scrolltool(&sargs);
+
+ so_give(&store); /* free resources associated with store */
+ ps_global->mangled_screen = 1;
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * C-client callback to handle SSL/TLS certificate validation failures
+ */
+void
+pine_sslfailure(char *host, char *reason, long unsigned int flags)
+{
+ SCROLL_S sargs;
+ STORE_S *store;
+ int the_answer = 'n', indent, len, cols;
+ char buf[500], buf2[500];
+ char *folded;
+ char *hst = host ? host : "<unknown>";
+ char *rsn = reason ? reason : "<unknown>";
+ char *notls = "/notls";
+ STRLIST_S hostlist;
+ int ok_novalidate = 0, warned = 0;
+
+
+ dprint((1, "sslfailure: host=%s reason=%s\n",
+ hst ? hst : "?",
+ rsn ? rsn : "?"));
+
+ if(flags & NET_SILENT)
+ return;
+
+ hostlist.name = host ? host : "";
+ hostlist.next = NULL;
+
+ /*
+ * See if we've been told about this host before.
+ */
+ if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
+ /* we were told already */
+ if(warned){
+ snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
+ buf[sizeof(buf)-1] = '\0';
+ mm_log(buf, ERROR);
+ return;
+ }
+ }
+
+ cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
+ cols--;
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
+ return;
+
+ strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ so_puts(store, "\n");
+
+ if((len=strlen(hst)) <= cols){
+ if((indent=((cols-len)/2)) > 0)
+ so_puts(store, repeat_char(indent, SPACE));
+
+ so_puts(store, hst);
+ so_puts(store, "\n");
+ }
+ else{
+ strncpy(buf, hst, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ }
+
+ so_puts(store, "\n");
+
+ strncpy(buf, _("The reason for the failure was"), sizeof(buf));
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ so_puts(store, "\n");
+
+ if((len=strlen(rsn)) <= cols){
+ if((indent=((cols-len)/2)) > 0)
+ so_puts(store, repeat_char(indent, SPACE));
+
+ so_puts(store, rsn);
+ so_puts(store, "\n");
+ }
+ else{
+ strncpy(buf, rsn, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ }
+
+ so_puts(store, "\n");
+
+ strncpy(buf, _("This is just an informational message. With the current setup, SSL/TLS will not work. If this error re-occurs every time you run Alpine, your current setup is not compatible with the configuration of your mail server. You may want to add the option"), sizeof(buf));
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ so_puts(store, "\n");
+
+ if((len=strlen(notls)) <= cols){
+ if((indent=((cols-len)/2)) > 0)
+ so_puts(store, repeat_char(indent, SPACE));
+
+ so_puts(store, notls);
+ so_puts(store, "\n");
+ }
+ else{
+ strncpy(buf, notls, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ }
+
+ so_puts(store, "\n");
+
+ strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
+ sizeof(buf));
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ so_puts(store, "\n");
+
+ if((len=strlen(hst)) <= cols){
+ if((indent=((cols-len)/2)) > 0)
+ so_puts(store, repeat_char(indent, SPACE));
+
+ so_puts(store, hst);
+ so_puts(store, "\n");
+ }
+ else{
+ strncpy(buf, hst, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ }
+
+ so_puts(store, "\n");
+
+ strncpy(buf, _("in your configuration, replace those characters with"),
+ sizeof(buf));
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ so_puts(store, "\n");
+
+ snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
+ buf2[sizeof(buf2)-1] = '\0';
+ if((len=strlen(buf2)) <= cols){
+ if((indent=((cols-len)/2)) > 0)
+ so_puts(store, repeat_char(indent, SPACE));
+
+ so_puts(store, buf2);
+ so_puts(store, "\n");
+ }
+ else{
+ strncpy(buf, buf2, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ }
+
+ so_puts(store, "\n");
+
+ if(ps_global->ttyo){
+ strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
+ folded = fold(buf, cols, cols, "", "", FLD_NONE);
+ so_puts(store, folded);
+ fs_give((void **)&folded);
+ }
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("help text");
+ sargs.bar.title = _("SSL/TLS FAILURE");
+ sargs.proc.tool = answer_cert_failure;
+ sargs.proc.data.p = (void *)&the_answer;
+ sargs.keys.menu = &ans_certfail_keymenu;
+ setbitmap(sargs.keys.bitmap);
+ /* don't want to re-enter c-client */
+ sargs.quell_newmail = 1;
+ sargs.help.text = h_tls_failure;
+ sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
+
+ if(ps_global->ttyo)
+ scrolltool(&sargs);
+ else{
+ char **q, **qp;
+ char *p;
+ unsigned char c;
+ int cnt = 0;
+
+ /*
+ * The screen isn't initialized yet, which should mean that this
+ * is the result of a -p argument. Display_args_err knows how to deal
+ * with the uninitialized screen, so we mess with the data to get it
+ * in shape for display_args_err. This is pretty hacky.
+ */
+
+ so_seek(store, 0L, 0); /* rewind */
+ /* count the lines */
+ while(so_readc(&c, store))
+ if(c == '\n')
+ cnt++;
+
+ qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
+ memset(q, 0, (cnt+1) * sizeof(char *));
+
+ so_seek(store, 0L, 0); /* rewind */
+ p = buf;
+ while(so_readc(&c, store)){
+ if(c == '\n'){
+ *p = '\0';
+ *qp++ = cpystr(buf);
+ p = buf;
+ }
+ else
+ *p++ = c;
+ }
+
+ display_args_err(NULL, q, 0);
+ free_list_array(&q);
+ }
+
+ ps_global->mangled_screen = 1;
+ ps_global->painted_body_on_startup = 0;
+ ps_global->painted_footer_on_startup = 0;
+ so_give(&store);
+
+ imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
+}
+
+
+int
+answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 1;
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ switch(cmd){
+ case MC_YES :
+ *(int *)(sparms->proc.data.p) = 'y';
+ break;
+
+ case MC_NO :
+ *(int *)(sparms->proc.data.p) = 'n';
+ break;
+
+ default:
+ panic("Unexpected command in answer_cert_failure");
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ This can be used to prevent the flickering of the check_cue char
+ caused by numerous (5000+) fetches by c-client. Right now, the only
+ practical use found is newsgroup subsciption.
+
+ check_cue_display will check if this global is set, and won't clear
+ the check_cue_char if set.
+ ----*/
+void
+set_read_predicted(int i)
+{
+ ps_global->read_predicted = i==1;
+#ifdef _WINDOWS
+ if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
+ check_cue_display(" ");
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Exported method to retrieve logged in user name associated with stream
+
+ Args: host -- host to find associated login name with.
+
+ Result:
+ ----*/
+void *
+pine_block_notify(int reason, void *data)
+{
+ switch(reason){
+ case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
+ break;
+
+ case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
+ break;
+
+ case BLOCK_TCPWRITE: /* blocked on TCP write */
+ case BLOCK_FILELOCK: /* blocked on file locking */
+#ifdef _WINDOWS
+ if(F_ON(F_SHOW_DELAY_CUE, ps_global))
+ check_cue_display(">");
+
+ mswin_setcursor(MSWIN_CURSOR_BUSY);
+#endif
+ break;
+
+ case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
+ case BLOCK_TCPOPEN: /* blocked on TCP open */
+ case BLOCK_TCPREAD: /* blocked on TCP read */
+ case BLOCK_TCPCLOSE: /* blocked on TCP close */
+#ifdef _WINDOWS
+ if(F_ON(F_SHOW_DELAY_CUE, ps_global))
+ check_cue_display("<");
+
+ mswin_setcursor(MSWIN_CURSOR_BUSY);
+#endif
+ break;
+
+ default :
+ case BLOCK_NONE: /* not blocked */
+#ifdef _WINDOWS
+ if(F_ON(F_SHOW_DELAY_CUE, ps_global))
+ check_cue_display(" ");
+#endif
+ break;
+
+ }
+
+ return(NULL);
+}
+
+
+void
+mm_expunged_current(long unsigned int rawno)
+{
+ /* expunged something we're viewing? */
+ if(!ps_global->expunge_in_progress
+ && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
+ && (ps_global->prev_screen == mail_view_screen
+ || ps_global->prev_screen == attachment_screen))){
+ ps_global->next_screen = mail_index_screen;
+ q_status_message(SM_ORDER | SM_DING , 3, 3,
+ "Message you were viewing is gone!");
+ }
+}
+
+
+#ifdef PASSFILE
+
+/*
+ * Specific functions to support caching username/passwd/host
+ * triples on disk for use from one session to the next...
+ */
+
+#define FIRSTCH 0x20
+#define LASTCH 0x7e
+#define TABSZ (LASTCH - FIRSTCH + 1)
+
+static int xlate_key;
+
+
+/*
+ * xlate_in() - xlate_in the given character
+ */
+char
+xlate_in(int c)
+{
+ register int eti;
+
+ eti = xlate_key;
+ if((c >= FIRSTCH) && (c <= LASTCH)){
+ eti += (c - FIRSTCH);
+ eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
+ return((xlate_key = eti) + FIRSTCH);
+ }
+ else
+ return(c);
+}
+
+
+/*
+ * xlate_out() - xlate_out the given character
+ */
+char
+xlate_out(char c)
+{
+ register int dti;
+ register int xch;
+
+ if((c >= FIRSTCH) && (c <= LASTCH)){
+ xch = c - (dti = xlate_key);
+ xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
+ dti = (xch - FIRSTCH) + dti;
+ dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
+ xlate_key = dti;
+ return(xch);
+ }
+ else
+ return(c);
+}
+
+
+char *
+passfile_name(char *pinerc, char *path, size_t len)
+{
+ struct stat sbuf;
+ char *p = NULL;
+ int i, j;
+
+ if(!path || !((pinerc && pinerc[0]) || ps_global->passfile))
+ return(NULL);
+
+ if(ps_global->passfile)
+ strncpy(path, ps_global->passfile, len-1);
+ else{
+ if((p = last_cmpnt(pinerc)) && *(p-1) && *(p-1) != PASSFILE[0])
+ for(i = 0; pinerc < p && i < len; pinerc++, i++)
+ path[i] = *pinerc;
+ else
+ i = 0;
+
+ for(j = 0; (i < len) && (path[i] = PASSFILE[j]); i++, j++)
+ ;
+
+ }
+
+ path[len-1] = '\0';
+
+ dprint((9, "Looking for passfile \"%s\"\n",
+ path ? path : "?"));
+
+#if defined(DOS) || defined(OS2)
+ return((our_stat(path, &sbuf) == 0
+ && ((sbuf.st_mode & S_IFMT) == S_IFREG))
+ ? path : NULL);
+#else
+ /* First, make sure it's ours and not sym-linked */
+ if(our_lstat(path, &sbuf) == 0
+ && ((sbuf.st_mode & S_IFMT) == S_IFREG)
+ && sbuf.st_uid == getuid()){
+ /* if too liberal permissions, fix them */
+ if((sbuf.st_mode & 077) != 0)
+ if(our_chmod(path, sbuf.st_mode & ~077) != 0)
+ return(NULL);
+
+ return(path);
+ }
+ else
+ return(NULL);
+#endif
+}
+
+#endif /* PASSFILE */
+
+
+#ifdef LOCAL_PASSWD_CACHE
+
+
+int
+line_get(char *tmp, size_t len, char **textp)
+{
+ char *s, c;
+
+ tmp[0] = '\0';
+ if (*textp == NULL)
+ return 0;
+ s = strchr(*textp, '\n');
+ if(s != NULL){
+ *s = '\0';
+ if(*(s-1) == '\r')
+ *(s-1) = '\0';
+ if(strlen(*textp) < len - 1)
+ strcpy(tmp, *textp);
+ else
+ return 0;
+ strcat(tmp, "\n");
+ *textp = s+1;
+ }
+ else
+ return 0;
+
+ return 1;
+}
+/*
+ * For UNIX:
+ * Passfile lines are
+ *
+ * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
+ *
+ * In pine4.40 and before there was no orig_hostname, and there still isn't
+ * if it is the same as hostname.
+ *
+ * else for WINDOWS:
+ * Use Windows credentials. The TargetName of the credential is
+ * UWash_Alpine_<hostname:port>\tuser\taltflag
+ * and the blob consists of
+ * passwd\torighost (if different from host)
+ *
+ * We don't use anything fancy we just copy out all the credentials which
+ * begin with TNAME and put them into our cache, so we don't lookup based
+ * on the TargetName or anything like that. That was so we could re-use
+ * the existing code and so that orighost data could be easily used.
+ */
+int
+read_passfile(pinerc, l)
+ char *pinerc;
+ MMLOGIN_S **l;
+{
+#ifdef WINCRED
+# if (WINCRED > 0)
+ LPCTSTR lfilter = TEXT(TNAMESTAR);
+ DWORD count, k;
+ PCREDENTIAL *pcred;
+ char *tmp, *blob, *target = NULL;
+ char *host, *user, *sflags, *passwd, *orighost;
+ char *ui[5];
+ int i, j;
+
+ if(using_passfile == 0)
+ return(using_passfile);
+
+ if(!g_CredInited){
+ if(init_wincred_funcs() != 1){
+ using_passfile = 0;
+ return(using_passfile);
+ }
+ }
+
+ dprint((9, "read_passfile\n"));
+
+ using_passfile = 1;
+
+ if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
+ if(pcred){
+ for(k = 0; k < count; k++){
+
+ host = user = sflags = passwd = orighost = NULL;
+ ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
+
+ target = lptstr_to_utf8(pcred[k]->TargetName);
+ tmp = srchstr(target, TNAME);
+
+ if(tmp){
+ tmp += strlen(TNAME);
+ for(i = 0, j = 0; tmp[i] && j < 3; j++){
+ for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
+ ; /* find end of data */
+
+ if(tmp[i])
+ tmp[i++] = '\0'; /* tie off data */
+ }
+
+ host = ui[0];
+ user = ui[1];
+ sflags = ui[2];
+ }
+
+ blob = (char *) pcred[k]->CredentialBlob;
+ if(blob){
+ for(i = 0, j = 3; blob[i] && j < 5; j++){
+ for(ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
+ ; /* find end of data */
+
+ if(blob[i])
+ blob[i++] = '\0'; /* tie off data */
+ }
+
+ passwd = ui[3];
+ orighost = ui[4];
+ }
+
+ if(passwd && host && user){ /* valid field? */
+ STRLIST_S hostlist[2];
+ int flags = sflags ? atoi(sflags) : 0;
+
+ hostlist[0].name = host;
+ if(orighost){
+ hostlist[0].next = &hostlist[1];
+ hostlist[1].name = orighost;
+ hostlist[1].next = NULL;
+ }
+ else{
+ hostlist[0].next = NULL;
+ }
+
+ imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
+ }
+
+ if(target)
+ fs_give((void **) &target);
+ }
+
+ g_CredFree((PVOID) pcred);
+ }
+ }
+
+ return(1);
+
+# else /* old windows */
+ using_passfile = 0;
+ return(0);
+# endif
+
+#elif APPLEKEYCHAIN
+
+ char target[MAILTMPLEN];
+ char *tmp, *host, *user, *sflags, *passwd, *orighost;
+ char *ui[5];
+ int i, j, k, rc;
+ SecKeychainAttributeList attrList;
+ SecKeychainSearchRef searchRef = NULL;
+ SecKeychainAttribute attrs[] = {
+ { kSecAccountItemAttr, strlen(TNAME), TNAME }
+ };
+
+ if(using_passfile == 0)
+ return(using_passfile);
+
+ dprint((9, "read_passfile\n"));
+
+
+ /* search for only our items in the keychain */
+ attrList.count = 1;
+ attrList.attr = attrs;
+
+ using_passfile = 1;
+ if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
+ kSecGenericPasswordItemClass,
+ &attrList,
+ &searchRef))){
+ dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
+ if(searchRef){
+ SecKeychainItemRef itemRef = NULL;
+ SecKeychainAttributeInfo info;
+ SecKeychainAttributeList *attrList = NULL;
+ UInt32 blength = 0;
+ char *blob = NULL;
+ char *blobcopy = NULL; /* NULL terminated copy */
+
+ UInt32 tags[] = {kSecAccountItemAttr,
+ kSecServiceItemAttr};
+ UInt32 formats[] = {0,0};
+
+ dprint((10, "read_passfile: searchRef not NULL\n"));
+ info.count = 2;
+ info.tag = tags;
+ info.format = formats;
+
+ /*
+ * Go through each item we found and put it
+ * into our list.
+ */
+ while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
+ dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
+ rc = SecKeychainItemCopyAttributesAndData(itemRef,
+ &info, NULL,
+ &attrList,
+ &blength,
+ (void **) &blob);
+ if(rc == 0 && attrList){
+ dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
+
+ blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
+ strncpy(blobcopy, (char *) blob, blength);
+ blobcopy[blength] = '\0';
+
+ /*
+ * I'm not real clear on how this works. It seems to be
+ * necessary to combine the attributes from two passes
+ * (attrList->count == 2) in order to get the full set
+ * of attributes we inserted into the keychain in the
+ * first place. So, we reset host...orighost outside of
+ * the following for loop, not inside.
+ */
+ host = user = sflags = passwd = orighost = NULL;
+ ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
+
+ for(k = 0; k < attrList->count; k++){
+
+ if(attrList->attr[k].length){
+ strncpy(target,
+ (char *) attrList->attr[k].data,
+ MIN(attrList->attr[k].length,sizeof(target)));
+ target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
+ }
+
+ tmp = target;
+ for(i = 0, j = 0; tmp[i] && j < 3; j++){
+ for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
+ ; /* find end of data */
+
+ if(tmp[i])
+ tmp[i++] = '\0'; /* tie off data */
+ }
+
+ if(ui[0])
+ host = ui[0];
+
+ if(ui[1])
+ user = ui[1];
+
+ if(ui[2])
+ sflags = ui[2];
+
+ for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
+ for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
+ ; /* find end of data */
+
+ if(blobcopy[i])
+ blobcopy[i++] = '\0'; /* tie off data */
+ }
+
+ if(ui[3])
+ passwd = ui[3];
+
+ if(ui[4])
+ orighost = ui[4];
+
+ dprint((10, "read_passfile: host=%s user=%s sflags=%s passwd=%s orighost=%s\n", host?host:"", user?user:"", sflags?sflags:"", passwd?passwd:"", orighost?orighost:""));
+ }
+
+ if(passwd && host && user){ /* valid field? */
+ STRLIST_S hostlist[2];
+ int flags = sflags ? atoi(sflags) : 0;
+
+ hostlist[0].name = host;
+ if(orighost){
+ hostlist[0].next = &hostlist[1];
+ hostlist[1].name = orighost;
+ hostlist[1].next = NULL;
+ }
+ else{
+ hostlist[0].next = NULL;
+ }
+
+ imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
+ }
+
+ if(blobcopy)
+ fs_give((void **) & blobcopy);
+
+ SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
+ }
+ else{
+ using_passfile = 0;
+ dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
+ }
+
+ CFRelease(itemRef);
+ itemRef = NULL;
+ }
+
+ CFRelease(searchRef);
+ }
+ else{
+ using_passfile = 0;
+ dprint((10, "read_passfile: searchRef NULL\n"));
+ }
+ }
+ else{
+ using_passfile = 0;
+ dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
+ }
+
+ return(using_passfile);
+
+#else /* PASSFILE */
+
+ char tmp[MAILTMPLEN], *ui[5];
+ int i, j, n;
+#ifdef SMIME
+ char tmp2[MAILTMPLEN];
+ char *text = NULL, *text2 = NULL;
+ int encrypted = 0;
+#endif /* SMIME */
+ FILE *fp;
+
+ if(using_passfile == 0)
+ return(using_passfile);
+
+ dprint((9, "read_passfile\n"));
+
+ /* if there's no password to read, bag it!! */
+ if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
+ using_passfile = 0;
+ return(using_passfile);
+ };
+
+#ifdef SMIME
+ tmp2[0] = '\0';
+ fgets(tmp2, sizeof(tmp2), fp);
+ if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
+ fclose(fp);
+ if(encrypt_file(tmp, NULL))
+ encrypted++;
+ }
+ else{
+ fclose(fp);
+ encrypted++;
+ }
+
+ /*
+ * if password file is encrypted we attemtp to decrypt. We ask the
+ * user for the password to unlock the password file. If the user
+ * enters the password and it unlocks the file, use it and keep saving
+ * passwords in it. If the user enters the wrong passwords and does
+ * not unlock it, we will not see that here, but in decrypt_file, so
+ * the only other possibility is that the user cancels. In that case
+ * we will see i == -1. In that case, we will let the user attempt
+ * manual login to the server they want to login, but passwords will
+ * not be saved so that the password file will not be saved
+ * unencrypted and rewritten again.
+ */
+ if(encrypted){
+ text = text2 = decrypt_file(tmp, &i);
+ switch(i){
+ case 1 : save_password = 1;
+ break;
+
+ case -1: save_password = 0;
+ break;
+
+ default: break;
+ }
+ }
+ else
+ fp = our_fopen(tmp, "rb"); /* reopen to read data */
+#endif /* SMIME */
+
+ using_passfile = 1;
+#ifdef SMIME
+ for(n = 0; encrypted ? line_get(tmp, sizeof(tmp), &text2)
+ : (fgets(tmp, sizeof(tmp), fp) != NULL); n++){
+#else /* SMIME */
+ for(n = 0; fgets(tmp, sizeof(tmp), fp); n++){
+#endif /* SMIME */
+ /*** do any necessary DEcryption here ***/
+ xlate_key = n;
+ for(i = 0; tmp[i]; i++)
+ tmp[i] = xlate_out(tmp[i]);
+
+ if(i && tmp[i-1] == '\n')
+ tmp[i-1] = '\0'; /* blast '\n' */
+
+ dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
+ ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
+ for(i = 0, j = 0; tmp[i] && j < 5; j++){
+ for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
+ ; /* find end of data */
+
+ if(tmp[i])
+ tmp[i++] = '\0'; /* tie off data */
+ }
+
+ dprint((10, "read_passfile: calling imap_set_passwd\n"));
+ if(ui[0] && ui[1] && ui[2]){ /* valid field? */
+ STRLIST_S hostlist[2];
+ int flags = ui[3] ? atoi(ui[3]) : 0;
+
+ hostlist[0].name = ui[2];
+ if(ui[4]){
+ hostlist[0].next = &hostlist[1];
+ hostlist[1].name = ui[4];
+ hostlist[1].next = NULL;
+ }
+ else{
+ hostlist[0].next = NULL;
+ }
+
+ imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
+ }
+ }
+
+#ifdef SMIME
+ if (text) fs_give((void **)&text);
+#else /* SMIME */
+ fclose(fp);
+#endif /* SMIME */
+ return(1);
+#endif /* PASSFILE */
+}
+
+
+
+void
+write_passfile(pinerc, l)
+ char *pinerc;
+ MMLOGIN_S *l;
+{
+#ifdef WINCRED
+# if (WINCRED > 0)
+ char target[MAILTMPLEN];
+ char blob[MAILTMPLEN];
+ CREDENTIAL cred;
+ LPTSTR ltarget = 0;
+
+ if(using_passfile == 0)
+ return;
+
+ dprint((9, "write_passfile\n"));
+
+ for(; l; l = l->next){
+ snprintf(target, sizeof(target), "%s%s\t%s\t%d",
+ TNAME,
+ (l->hosts && l->hosts->name) ? l->hosts->name : "",
+ l->user ? l->user : "",
+ l->altflag);
+ ltarget = utf8_to_lptstr((LPSTR) target);
+
+ if(ltarget){
+ snprintf(blob, sizeof(blob), "%s%s%s",
+ l->passwd ? l->passwd : "",
+ (l->hosts && l->hosts->next && l->hosts->next->name)
+ ? "\t" : "",
+ (l->hosts && l->hosts->next && l->hosts->next->name)
+ ? l->hosts->next->name : "");
+ memset((void *) &cred, 0, sizeof(cred));
+ cred.Flags = 0;
+ cred.Type = CRED_TYPE_GENERIC;
+ cred.TargetName = ltarget;
+ cred.CredentialBlobSize = strlen(blob)+1;
+ cred.CredentialBlob = (LPBYTE) &blob;
+ cred.Persist = CRED_PERSIST_ENTERPRISE;
+ g_CredWriteW(&cred, 0);
+
+ fs_give((void **) &ltarget);
+ }
+ }
+ #endif /* WINCRED > 0 */
+
+#elif APPLEKEYCHAIN
+
+ int rc;
+ char target[MAILTMPLEN];
+ char blob[MAILTMPLEN];
+ SecKeychainItemRef itemRef = NULL;
+
+ if(using_passfile == 0)
+ return;
+
+ dprint((9, "write_passfile\n"));
+
+ for(; l; l = l->next){
+ snprintf(target, sizeof(target), "%s\t%s\t%d",
+ (l->hosts && l->hosts->name) ? l->hosts->name : "",
+ l->user ? l->user : "",
+ l->altflag);
+
+ snprintf(blob, sizeof(blob), "%s%s%s",
+ l->passwd ? l->passwd : "",
+ (l->hosts && l->hosts->next && l->hosts->next->name)
+ ? "\t" : "",
+ (l->hosts && l->hosts->next && l->hosts->next->name)
+ ? l->hosts->next->name : "");
+
+ dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
+
+ rc = SecKeychainAddGenericPassword(NULL,
+ strlen(target), target,
+ strlen(TNAME), TNAME,
+ strlen(blob), blob,
+ NULL);
+ if(rc==0){
+ dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
+ }
+ else{
+ dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
+ }
+
+ if(rc == errSecDuplicateItem){
+ /* fix existing entry */
+ dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
+ itemRef = NULL;
+ if(!(rc=SecKeychainFindGenericPassword(NULL,
+ strlen(target), target,
+ strlen(TNAME), TNAME,
+ NULL, NULL,
+ &itemRef)) && itemRef){
+
+ rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
+ if(!rc){
+ dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
+ }
+ }
+ else{
+ dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
+ }
+ }
+ }
+
+#else /* PASSFILE */
+
+ char tmp[MAILTMPLEN];
+ int i, n;
+ FILE *fp;
+#ifdef SMIME
+ char *text = NULL, tmp2[MAILTMPLEN];
+ int len = 0;
+#endif
+
+ if(using_passfile == 0)
+ return;
+
+ dprint((9, "write_passfile\n"));
+
+ /* if there's no passfile to read, bag it!! */
+ if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
+ using_passfile = 0;
+ return;
+ }
+
+#ifdef SMIME
+ strcpy(tmp2, tmp);
+#endif /* SMIME */
+
+ for(n = 0; l; l = l->next, n++){
+ /*** do any necessary ENcryption here ***/
+ snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%d%s%s\n", l->passwd, l->user,
+ l->hosts->name, l->altflag,
+ (l->hosts->next && l->hosts->next->name) ? "\t" : "",
+ (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
+ : "");
+ dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
+ xlate_key = n;
+ for(i = 0; tmp[i]; i++)
+ tmp[i] = xlate_in(tmp[i]);
+
+#ifdef SMIME
+ if(len == 0){
+ len = strlen(tmp) + 1;
+ text = fs_get(len*sizeof(char));
+ *text = '\0';
+ }
+ if(strlen(text) + strlen(tmp) > len){
+ len = strlen(text) + strlen(tmp) + 1;
+ fs_resize((void **)&text, len*sizeof(char));
+ }
+ strcat(text, tmp);
+#else /* SMIME */
+ fputs(tmp, fp);
+#endif /* SMIME */
+ }
+
+ fclose(fp);
+#ifdef SMIME
+ if(encrypt_file(tmp2, text) == 0){
+ if((fp = our_fopen(tmp2, "wb")) != NULL){
+ fputs(text, fp);
+ fclose(fp);
+ }
+ }
+ fs_give((void **)&text);
+#endif /* SMIME */
+#endif /* PASSFILE */
+}
+
+#endif /* LOCAL_PASSWD_CACHE */
+
+
+#if (WINCRED > 0)
+void
+erase_windows_credentials(void)
+{
+ LPCTSTR lfilter = TEXT(TNAMESTAR);
+ DWORD count, k;
+ PCREDENTIAL *pcred;
+
+ if(!g_CredInited){
+ if(init_wincred_funcs() != 1)
+ return;
+ }
+
+ if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
+ if(pcred){
+ for(k = 0; k < count; k++)
+ g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
+
+ g_CredFree((PVOID) pcred);
+ }
+ }
+}
+
+void
+ask_erase_credentials(void)
+{
+ if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
+ erase_windows_credentials();
+ q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
+ }
+ else
+ q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
+}
+
+#endif /* WINCRED */
+
+
+#ifdef LOCAL_PASSWD_CACHE
+
+/*
+ * get_passfile_passwd - return the password contained in the special passord
+ * cache. The file is assumed to be in the same directory
+ * as the pinerc with the name defined above.
+ */
+int
+get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
+ char *pinerc, *passwd, *user;
+ STRLIST_S *hostlist;
+ int altflag;
+{
+ dprint((10, "get_passfile_passwd\n"));
+ return((passfile_cache || read_passfile(pinerc, &passfile_cache))
+ ? imap_get_passwd(passfile_cache, passwd,
+ user, hostlist, altflag)
+ : 0);
+}
+
+int
+is_using_passfile()
+{
+ return(using_passfile == 1);
+}
+
+/*
+ * Just trying to guess the username the user might want to use on this
+ * host, the user will confirm.
+ */
+char *
+get_passfile_user(pinerc, hostlist)
+ char *pinerc;
+ STRLIST_S *hostlist;
+{
+ return((passfile_cache || read_passfile(pinerc, &passfile_cache))
+ ? imap_get_user(passfile_cache, hostlist)
+ : NULL);
+}
+
+
+int
+preserve_prompt(void)
+{
+#ifdef WINCRED
+# if (WINCRED > 0)
+ /*
+ * This prompt was going to be able to be turned on and off via a registry
+ * setting controlled from the config menu. We decided to always use the
+ * dialog for login, and there the prompt is unobtrusive enough to always
+ * be in there. As a result, windows should never reach this, but now
+ * OS X somewhat uses the behavior just described.
+ */
+ if(mswin_store_pass_prompt()
+ && (want_to(_("Preserve password for next login"),
+ 'y', 'x', NO_HELP, WT_NORM)
+ == 'y'))
+ return(1);
+ else
+ return(0);
+# else
+ return(0);
+# endif
+
+#elif APPLEKEYCHAIN
+
+ int rc;
+ if(rc = macos_store_pass_prompt()){
+ if(want_to(_("Preserve password for next login"),
+ 'y', 'x', NO_HELP, WT_NORM)
+ == 'y'){
+ if(rc == -1){
+ macos_set_store_pass_prompt(1);
+ q_status_message(SM_ORDER, 4, 4,
+ _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
+ }
+ return(1);
+ }
+ else if(rc == -1){
+ macos_set_store_pass_prompt(0);
+ q_status_message(SM_ORDER, 4, 4,
+ _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
+ }
+ return(0);
+ }
+ return(0);
+#else /* PASSFILE */
+ return(want_to(_("Preserve password on DISK for next login"),
+ 'y', 'x', NO_HELP, WT_NORM)
+ == 'y');
+#endif /* PASSFILE */
+}
+
+#endif /* LOCAL_PASSWD_CACHE */
+
+
+#ifdef APPLEKEYCHAIN
+
+/*
+ * Returns:
+ * 1 if store pass prompt is set in the "registry" to on
+ * 0 if set to off
+ * -1 if not set to anything
+ */
+int
+macos_store_pass_prompt(void)
+{
+ char *data = NULL;
+ UInt32 len = 0;
+ int rc = -1;
+ int val;
+
+ if(storepassprompt == -1){
+ if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
+ strlen(TNAMEPROMPT),
+ TNAMEPROMPT, &len,
+ (void **) &data, NULL))){
+ val = (len == 1 && data && data[0] == '1');
+ }
+ }
+
+ if(storepassprompt == -1 && !rc){
+ if(val)
+ storepassprompt = 1;
+ else
+ storepassprompt = 0;
+ }
+
+ return(storepassprompt);
+}
+
+
+void
+macos_set_store_pass_prompt(int val)
+{
+ storepassprompt = val ? 1 : 0;
+
+ SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
+ TNAMEPROMPT, 1, val ? "1" : "0", NULL);
+}
+
+
+void
+macos_erase_keychain(void)
+{
+ SecKeychainAttributeList attrList;
+ SecKeychainSearchRef searchRef = NULL;
+ SecKeychainAttribute attrs1[] = {
+ { kSecAccountItemAttr, strlen(TNAME), TNAME }
+ };
+ SecKeychainAttribute attrs2[] = {
+ { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
+ };
+
+ dprint((9, "macos_erase_keychain\n"));
+
+ /*
+ * Seems like we ought to be able to combine attrs1 and attrs2
+ * into a single array, but I couldn't get it to work.
+ */
+
+ /* search for only our items in the keychain */
+ attrList.count = 1;
+ attrList.attr = attrs1;
+
+ if(!SecKeychainSearchCreateFromAttributes(NULL,
+ kSecGenericPasswordItemClass,
+ &attrList,
+ &searchRef)){
+ if(searchRef){
+ SecKeychainItemRef itemRef = NULL;
+
+ /*
+ * Go through each item we found and put it
+ * into our list.
+ */
+ while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
+ dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
+ SecKeychainItemDelete(itemRef);
+ CFRelease(itemRef);
+ }
+
+ CFRelease(searchRef);
+ }
+ }
+
+ attrList.count = 1;
+ attrList.attr = attrs2;
+
+ if(!SecKeychainSearchCreateFromAttributes(NULL,
+ kSecGenericPasswordItemClass,
+ &attrList,
+ &searchRef)){
+ if(searchRef){
+ SecKeychainItemRef itemRef = NULL;
+
+ /*
+ * Go through each item we found and put it
+ * into our list.
+ */
+ while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
+ SecKeychainItemDelete(itemRef);
+ CFRelease(itemRef);
+ }
+
+ CFRelease(searchRef);
+ }
+ }
+}
+
+#endif /* APPLEKEYCHAIN */
+
+#ifdef LOCAL_PASSWD_CACHE
+
+/*
+ * set_passfile_passwd - set the password file entry associated with
+ * cache. The file is assumed to be in the same directory
+ * as the pinerc with the name defined above.
+ * already_prompted: 0 not prompted
+ * 1 prompted, answered yes
+ * 2 prompted, answered no
+ */
+void
+set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
+ char *pinerc, *passwd, *user;
+ STRLIST_S *hostlist;
+ int altflag, already_prompted;
+{
+ dprint((10, "set_passfile_passwd\n"));
+ if((passfile_cache || read_passfile(pinerc, &passfile_cache))
+ && !ps_global->nowrite_password_cache
+ && ((already_prompted == 0 && preserve_prompt())
+ || already_prompted == 1)){
+ imap_set_passwd(&passfile_cache, passwd, user, hostlist, altflag, 0, 0);
+ write_passfile(pinerc, passfile_cache);
+ }
+}
+
+
+/*
+ * Passfile lines are
+ *
+ * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
+ *
+ * In pine4.40 and before there was no orig_hostname.
+ * This routine attempts to repair that.
+ */
+void
+update_passfile_hostlist(pinerc, user, hostlist, altflag)
+ char *pinerc;
+ char *user;
+ STRLIST_S *hostlist;
+ int altflag;
+{
+#ifdef WINCRED
+ return;
+#else /* !WINCRED */
+ MMLOGIN_S *l;
+ STRLIST_S *h1, *h2;
+
+ for(l = passfile_cache; l; l = l->next)
+ if(imap_same_host(l->hosts, hostlist)
+ && *user
+ && !strcmp(user, l->user)
+ && l->altflag == altflag){
+ break;
+ }
+
+ if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
+ && hostlist->next->name
+ && !ps_global->nowrite_password_cache){
+ l->hosts->next = new_strlist(hostlist->next->name);
+ write_passfile(pinerc, passfile_cache);
+ }
+#endif /* !WINCRED */
+}
+
+#endif /* LOCAL_PASSWD_CACHE */
+
+
+#if (WINCRED > 0)
+/*
+ * Load and init the WinCred structure.
+ * This gives us a way to skip the WinCred code
+ * if the dll doesn't exist.
+ */
+int
+init_wincred_funcs(void)
+{
+ if(!g_CredInited)
+ {
+ HMODULE hmod;
+
+ /* Assume the worst. */
+ g_CredInited = -1;
+
+ hmod = LoadLibrary(TEXT("advapi32.dll"));
+ if(hmod)
+ {
+ FARPROC fpCredWriteW;
+ FARPROC fpCredEnumerateW;
+ FARPROC fpCredDeleteW;
+ FARPROC fpCredFree;
+
+ fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
+ fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
+ fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
+ fpCredFree = GetProcAddress(hmod, "CredFree");
+
+ if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
+ {
+ g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
+ g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
+ g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
+ g_CredFree = (CREDFREE *)fpCredFree;
+
+ g_CredInited = 1;
+ }
+ }
+
+ mswin_set_erasecreds_callback(ask_erase_credentials);
+ }
+
+ return g_CredInited;
+}
+
+#endif /* WINCRED */
diff --git a/alpine/imap.h b/alpine/imap.h
new file mode 100644
index 00000000..b254fb53
--- /dev/null
+++ b/alpine/imap.h
@@ -0,0 +1,51 @@
+/*
+ * $Id: imap.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_IMAP_INCLUDED
+#define PINE_IMAP_INCLUDED
+
+
+#include "../pith/imap.h"
+
+
+/* exported protoypes */
+void pine_parse_quota (MAILSTREAM *, unsigned char *, QUOTALIST *);
+QUOTALIST *pine_quotalist_copy (QUOTALIST *);
+void *pine_block_notify(int, void *);
+long pine_tcptimeout(long, long, char *);
+long pine_sslcertquery(char *, char *, char *);
+char *pine_newsrcquery(MAILSTREAM *, char *, char *);
+int url_local_certdetails(char *);
+void pine_sslfailure(char *, char *, unsigned long);
+void mm_expunged_current(long unsigned int);
+
+#ifdef LOCAL_PASSWD_CACHE
+int get_passfile_passwd(char *, char *, char *, STRLIST_S *, int);
+int is_using_passfile();
+void set_passfile_passwd(char *, char *, char *, STRLIST_S *, int, int);
+char *get_passfile_user(char *, STRLIST_S *);
+#endif /* LOCAL_PASSWD_CACHE */
+
+#if (WINCRED > 0)
+void erase_windows_credentials(void);
+#endif
+
+#ifdef APPLEKEYCHAIN
+void macos_erase_keychain(void);
+#endif
+
+
+#endif /* PINE_IMAP_INCLUDED */
diff --git a/alpine/init.c b/alpine/init.c
new file mode 100644
index 00000000..27c58cbf
--- /dev/null
+++ b/alpine/init.c
@@ -0,0 +1,396 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: init.c 101 2006-08-10 22:53:04Z mikes@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ init.c
+
+ Session initiation support routines
+
+ ====*/
+
+
+#include "headers.h"
+
+#include "../pith/init.h"
+#include "../pith/conf.h"
+#include "../pith/status.h"
+#include "../pith/folder.h"
+#include "../pith/context.h"
+
+#include "init.h"
+
+
+/* these are used to report folder directory creation problems */
+CONF_TXT_T init_md_exists[] = "The \"%s\" subdirectory already exists, but it is not writable by Alpine so Alpine cannot run. Please correct the permissions and restart Alpine.";
+
+CONF_TXT_T init_md_file[] = "Alpine requires a directory called \"%s\" and usualy creates it. You already have a regular file by that name which means Alpine cannot create the directory. Please move or remove it and start Alpine again.";
+
+CONF_TXT_T init_md_create[] = "Creating subdirectory \"%s\" where Alpine will store its mail folders.";
+
+
+/*
+ * Internal prototypes
+ */
+void display_init_err(char *, int);
+char *context_string(char *);
+int prune_folders(CONTEXT_S *, char *, int, char *, unsigned);
+int prune_move_folder(char *, char *, CONTEXT_S *);
+void delete_old_mail(struct sm_folder *, CONTEXT_S *, char *);
+
+
+
+/*----------------------------------------------------------------------
+ Make sure the alpine folders directory exists, with proper folders
+
+ Args: ps -- alpine structure to get mail directory and contexts from
+
+ Result: returns 0 if it exists or it is created and all is well
+ 1 if it is missing and can't be created.
+ ----*/
+int
+init_mail_dir(struct pine *ps)
+{
+
+/* MAIL_LIST: can we use imap4 CREATE? */
+ /*
+ * We don't really care if mail_dir exists if it isn't
+ * part of the first folder collection specified. If this
+ * is the case, it must have been created external to us, so
+ * just move one...
+ */
+ if(ps->VAR_FOLDER_SPEC && ps->VAR_FOLDER_SPEC[0]){
+ char *p = context_string(ps->VAR_FOLDER_SPEC[0]);
+ int rv = strncmp(p, ps->VAR_MAIL_DIRECTORY,
+ strlen(ps->VAR_MAIL_DIRECTORY));
+ fs_give((void **)&p);
+ if(rv)
+ return(0);
+ }
+
+ switch(is_writable_dir(ps->folders_dir)){
+ case 0:
+ /* --- all is well --- */
+ return(0);
+
+ case 1:
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, init_md_exists, ps->folders_dir);
+ display_init_err(tmp_20k_buf, 1);
+ return(-1);
+
+ case 2:
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, init_md_file, ps->folders_dir);
+ display_init_err(tmp_20k_buf, 1);
+ return(-1);
+
+ case 3:
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, init_md_create, ps->folders_dir);
+ display_init_err(tmp_20k_buf, 0);
+#ifndef _WINDOWS
+ sleep(4);
+#endif
+ if(create_mail_dir(ps->folders_dir) < 0){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Error creating subdirectory \"%s\" : %s",
+ ps->folders_dir, error_description(errno));
+ display_init_err(tmp_20k_buf, 1);
+ return(-1);
+ }
+ }
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Make sure the default save folders exist in the default
+ save context.
+ ----*/
+void
+display_init_err(char *s, int err)
+{
+#ifdef _WINDOWS
+ mswin_messagebox(s, err);
+#else
+ int n = 0;
+
+ if(err)
+ fputc(BELL, stdout);
+
+ for(; *s; s++)
+ if(++n > 60 && isspace((unsigned char)*s)){
+ n = 0;
+ fputc('\n', stdout);
+ while(*(s+1) && isspace((unsigned char)*(s+1)))
+ s++;
+ }
+ else
+ fputc(*s, stdout);
+
+ fputc('\n', stdout);
+#endif
+}
+
+
+/*
+ * Return malloc'd string containing only the context specifier given
+ * a string containing the raw pinerc entry
+ */
+char *
+context_string(char *s)
+{
+ CONTEXT_S *t = new_context(s, NULL);
+ char *rv = NULL;
+
+ if(t){
+ /*
+ * Take what we want from context, then free the rest...
+ */
+ rv = t->context;
+ t->context = NULL; /* don't free it! */
+ free_context(&t);
+ }
+
+ return(rv ? rv : cpystr(""));
+}
+
+
+
+/*----------------------------------------------------------------------
+ Rename the current sent-mail folder to sent-mail for last month
+
+ open up sent-mail and get date of very first message
+ if date is last month rename and...
+ if files from 3 months ago exist ask if they should be deleted and...
+ if files from previous months and yes ask about them, too.
+ ----------------------------------------------------------------------*/
+int
+expire_sent_mail(void)
+{
+ int cur_month, ok = 1;
+ time_t now;
+ char tmp[50], **p;
+ struct tm *tm_now;
+ CONTEXT_S *prune_cntxt;
+
+ dprint((5, "==== expire_mail called ====\n"));
+
+ if(!check_prune_time(&now, &tm_now))
+ return 0;
+
+ cur_month = (1900 + tm_now->tm_year) * 12 + tm_now->tm_mon;
+ dprint((5, "Current month %d\n", cur_month));
+
+ /*
+ * locate the default save context...
+ */
+ if(!(prune_cntxt = default_save_context(ps_global->context_list)))
+ prune_cntxt = ps_global->context_list;
+
+ /*
+ * Since fcc's and read-mail can be an IMAP mailbox, be sure to only
+ * try expiring a list if it's an ambiguous name associated with some
+ * collection...
+ *
+ * If sentmail set outside a context, then pruning is up to the
+ * user...
+ */
+ if(prune_cntxt){
+ if(ps_global->VAR_DEFAULT_FCC && *ps_global->VAR_DEFAULT_FCC
+ && context_isambig(ps_global->VAR_DEFAULT_FCC))
+ ok = prune_folders(prune_cntxt, ps_global->VAR_DEFAULT_FCC,
+ cur_month, " SENT",
+ ps_global->pruning_rule);
+
+ if(ok && ps_global->VAR_READ_MESSAGE_FOLDER
+ && *ps_global->VAR_READ_MESSAGE_FOLDER
+ && context_isambig(ps_global->VAR_READ_MESSAGE_FOLDER))
+ ok = prune_folders(prune_cntxt, ps_global->VAR_READ_MESSAGE_FOLDER,
+ cur_month, " READ",
+ ps_global->pruning_rule);
+ }
+
+ /*
+ * Within the default prune context,
+ * prune back the folders with the given name
+ */
+ if(ok && prune_cntxt && (p = ps_global->VAR_PRUNED_FOLDERS))
+ for(; ok && *p; p++)
+ if(**p && context_isambig(*p))
+ ok = prune_folders(prune_cntxt, *p, cur_month, "",
+ ps_global->pruning_rule);
+
+ /*
+ * Mark that we're done for this month...
+ */
+ if(ok){
+ ps_global->last_expire_year = tm_now->tm_year;
+ ps_global->last_expire_month = tm_now->tm_mon;
+ snprintf(tmp, sizeof(tmp), "%d.%d", ps_global->last_expire_year,
+ ps_global->last_expire_month + 1);
+ set_variable(V_LAST_TIME_PRUNE_QUESTION, tmp, 1, 1, Main);
+ }
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Offer to delete old sent-mail folders
+
+ Args: sml -- The list of sent-mail folders
+
+ ----*/
+int
+prune_folders(CONTEXT_S *prune_cntxt, char *folder_base, int cur_month,
+ char *type, unsigned int pr)
+{
+ char path2[MAXPATH+1], prompt[128], tmp[21];
+ int month_to_use, exists;
+ struct sm_folder *mail_list, *sm;
+
+ mail_list = get_mail_list(prune_cntxt, folder_base);
+ free_folder_list(prune_cntxt);
+
+#ifdef DEBUG
+ for(sm = mail_list; sm != NULL && sm->name != NULL; sm++)
+ dprint((5,"Old sent-mail: %5d %s\n", sm->month_num,
+ sm->name[0] ? sm->name : "?"));
+#endif
+
+ for(sm = mail_list; sm != NULL && sm->name != NULL; sm++)
+ if(sm->month_num == cur_month - 1)
+ break; /* matched a month */
+
+ month_to_use = (sm == NULL || sm->name == NULL) ? cur_month - 1 : 0;
+
+ dprint((5, "Month_to_use : %d\n", month_to_use));
+
+ if(month_to_use == 0 || pr == PRUNE_NO_AND_ASK || pr == PRUNE_NO_AND_NO)
+ goto delete_old;
+
+ strncpy(path2, folder_base, sizeof(path2)-1);
+ path2[sizeof(path2)-1] = '\0';
+
+ if(F_ON(F_PRUNE_USES_ISO,ps_global)){ /* sent-mail-yyyy-mm */
+ snprintf(path2 + strlen(path2), sizeof(path2)-strlen(path2), "-%4.4d-%2.2d", month_to_use/12,
+ month_to_use % 12 + 1);
+ }
+ else{
+ strncpy(tmp, month_abbrev((month_to_use % 12)+1), 20);
+ tmp[sizeof(tmp)-1] = '\0';
+ lcase((unsigned char *) tmp);
+#ifdef DOS
+ if(*prune_cntxt->context != '{'){
+ int i;
+
+ i = strlen(path2);
+ snprintf(path2 + (size_t)((i > 4) ? 4 : i),
+ sizeof(path2)- ((i > 4) ? 4 : i),
+ "%2.2d%2.2d", (month_to_use % 12) + 1,
+ ((month_to_use / 12) - 1900) % 100);
+ }
+ else
+#endif
+ snprintf(path2 + strlen(path2), sizeof(path2)-strlen(path2), "-%.20s-%d", tmp, month_to_use/12);
+ }
+
+ Writechar(BELL, 0);
+ snprintf(prompt, sizeof(prompt), "Move current \"%.50s\" to \"%.50s\"", folder_base, path2);
+ if((exists = folder_exists(prune_cntxt, folder_base)) == FEX_ERROR){
+ dprint((5, "prune_folders: Error testing existence\n"));
+ return(0);
+ }
+ else if(exists == FEX_NOENT){ /* doesn't exist */
+ dprint((5, "prune_folders: nothing to prune <%s %s>\n",
+ prune_cntxt->context ? prune_cntxt->context : "?",
+ folder_base ? folder_base : "?"));
+ goto delete_old;
+ }
+ else if(!(exists & FEX_ISFILE)
+ || pr == PRUNE_NO_AND_ASK || pr == PRUNE_NO_AND_NO
+ || ((pr == PRUNE_ASK_AND_ASK || pr == PRUNE_ASK_AND_NO) &&
+ want_to(prompt, 'n', 0, h_wt_expire, WT_FLUSH_IN) == 'n')){
+ dprint((5, "User declines renaming %s\n",
+ ps_global->VAR_DEFAULT_FCC ? ps_global->VAR_DEFAULT_FCC : "?"));
+ goto delete_old;
+ }
+
+ prune_move_folder(folder_base, path2, prune_cntxt);
+
+ delete_old:
+ if(pr == PRUNE_ASK_AND_ASK || pr == PRUNE_YES_AND_ASK
+ || pr == PRUNE_NO_AND_ASK)
+ delete_old_mail(mail_list, prune_cntxt, type);
+
+ if((sm = mail_list) != NULL){
+ while(sm->name){
+ fs_give((void **)&(sm->name));
+ sm++;
+ }
+
+ fs_give((void **)&mail_list);
+ }
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Offer to delete old sent-mail folders
+
+ Args: sml -- The list of sent-mail folders
+ fcc_cntxt -- context to delete list of folders in
+ type -- label indicating type of folders being deleted
+
+ ----*/
+void
+delete_old_mail(struct sm_folder *sml, CONTEXT_S *fcc_cntxt, char *type)
+{
+ char prompt[150];
+ struct sm_folder *sm;
+
+ for(sm = sml; sm != NULL && sm->name != NULL; sm++){
+ if(sm->name[0] == '\0') /* can't happen */
+ continue;
+
+ snprintf(prompt, sizeof(prompt),
+ "To save disk space, delete old%.10s mail folder \"%.30s\" ",
+ type, sm->name);
+ if(want_to(prompt, 'n', 0, h_wt_delete_old, WT_FLUSH_IN) == 'y'){
+
+ if(!context_delete(fcc_cntxt, NULL, sm->name)){
+ q_status_message1(SM_ORDER,
+ 3, 3, "Error deleting \"%s\".", sm->name);
+ dprint((1, "Error context_deleting %s in %s\n", sm->name,
+ (fcc_cntxt && fcc_cntxt->context)
+ ? fcc_cntxt->context : "<null>"));
+ }
+ else{
+ int index;
+
+ if((index = folder_index(sm->name, fcc_cntxt, FI_FOLDER)) >= 0)
+ folder_delete(index, FOLDERS(fcc_cntxt));
+ }
+ }else{
+ /* break;*/ /* skip out of the whole thing when he says no */
+ /* Decided to keep asking anyway */
+ }
+ }
+}
+
+
+
diff --git a/alpine/init.h b/alpine/init.h
new file mode 100644
index 00000000..e9c18053
--- /dev/null
+++ b/alpine/init.h
@@ -0,0 +1,27 @@
+/*
+ * $Id: init.h 82 2006-07-12 23:36:59Z mikes@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_INIT_INCLUDED
+#define PINE_INIT_INCLUDED
+
+
+/* exported protoypes */
+int init_mail_dir(struct pine *);
+int expire_sent_mail(void);
+
+
+#endif /* PINE_INIT_INCLUDED */
+
+
diff --git a/alpine/kblock.c b/alpine/kblock.c
new file mode 100644
index 00000000..774f31ad
--- /dev/null
+++ b/alpine/kblock.c
@@ -0,0 +1,196 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: kblock.c 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "kblock.h"
+#include "status.h"
+#include "titlebar.h"
+#include "radio.h"
+#include "../pith/conf.h"
+#include "../pith/state.h"
+
+#ifdef KEYBOARD_LOCK
+
+
+/*
+ * Internal prototypes
+ */
+void redraw_kl_body(void);
+void redraw_klocked_body(void);
+void draw_klocked_body(char *, char *);
+
+
+static char *klockin, *klockame;
+
+
+void
+redraw_kl_body(void)
+{
+ ClearScreen();
+
+ set_titlebar(_("KEYBOARD LOCK"), ps_global->mail_stream,
+ ps_global->context_current, ps_global->cur_folder, NULL,
+ 1, FolderName, 0, 0, NULL);
+
+ PutLine0(6,3 ,
+ _("You may lock this keyboard so that no one else can access your mail"));
+ PutLine0(8, 3 ,
+ _("while you are away. The screen will be locked after entering the "));
+ PutLine0(10, 3 ,
+ _("password to be used for unlocking the keyboard when you return."));
+ fflush(stdout);
+}
+
+
+void
+redraw_klocked_body(void)
+{
+ ClearScreen();
+
+ set_titlebar(_("KEYBOARD LOCK"), ps_global->mail_stream,
+ ps_global->context_current, ps_global->cur_folder, NULL,
+ 1, FolderName, 0, 0, NULL);
+
+ PutLine2(6, 3, _("This keyboard is locked by %s <%s>."),klockame, klockin);
+ PutLine0(8, 3, _("To unlock, enter password used to lock the keyboard."));
+ fflush(stdout);
+}
+
+
+void
+draw_klocked_body(char *login, char *username)
+{
+ klockin = login;
+ klockame = username;
+ redraw_klocked_body();
+}
+
+
+/*----------------------------------------------------------------------
+ Execute the lock keyboard command
+
+ Args: None
+
+ Result: keyboard is locked until user gives password
+ ---*/
+
+int
+lock_keyboard(void)
+{
+ struct pine *ps = ps_global;
+ char inpasswd[80], passwd[80], pw[80];
+ HelpType help = NO_HELP;
+ int i, times, old_suspend, flags;
+
+ passwd[0] = '\0';
+ redraw_kl_body();
+ ps->redrawer = redraw_kl_body;
+
+ times = atoi(ps->VAR_KBLOCK_PASSWD_COUNT);
+ if(times < 1 || times > 5){
+ dprint((2,
+ "Kblock-passwd-count var out of range (1 to 5) [%d]\n", times));
+ times = 1;
+ }
+
+ inpasswd[0] = '\0';
+
+ for(i = 0; i < times; i++){
+ pw[0] = '\0';
+ while(1){ /* input pasword to use for locking */
+ int rc;
+ char prompt[50];
+
+ if(i > 1)
+ snprintf(prompt, sizeof(prompt), _("Retype password to LOCK keyboard (Yes, again) : "));
+ else if(i)
+ snprintf(prompt, sizeof(prompt), _("Retype password to LOCK keyboard : "));
+ else
+ snprintf(prompt, sizeof(prompt), _("Enter password to LOCK keyboard : "));
+
+ flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
+ rc = optionally_enter(pw, -FOOTER_ROWS(ps), 0, sizeof(pw),
+ prompt, NULL, help, &flags);
+
+ if(rc == 3)
+ help = help == NO_HELP ? h_kb_lock : NO_HELP;
+ else if(rc == 1 || pw[0] == '\0'){
+ q_status_message(SM_ORDER, 0, 2, _("Keyboard lock cancelled"));
+ return(-1);
+ }
+ else if(rc != 4)
+ break;
+ }
+
+ if(!inpasswd[0]){
+ strncpy(inpasswd, pw, sizeof(inpasswd));
+ inpasswd[sizeof(inpasswd)-1] = '\0';
+ }
+ else if(strcmp(inpasswd, pw)){
+ q_status_message(SM_ORDER, 0, 2,
+ _("Mismatch with initial password: keyboard lock cancelled"));
+ return(-1);
+ }
+ }
+
+ if(want_to(_("Really lock keyboard with entered password"), 'y', 'n',
+ NO_HELP, WT_NORM) != 'y'){
+ q_status_message(SM_ORDER, 0, 2, _("Keyboard lock cancelled"));
+ return(-1);
+ }
+
+ draw_klocked_body(ps->VAR_USER_ID ? ps->VAR_USER_ID : "<no-user>",
+ ps->VAR_PERSONAL_NAME ? ps->VAR_PERSONAL_NAME : "<no-name>");
+
+ ps->redrawer = redraw_klocked_body;
+ if((old_suspend = F_ON(F_CAN_SUSPEND, ps_global)) != 0)
+ F_TURN_OFF(F_CAN_SUSPEND, ps_global);
+
+ while(strcmp(inpasswd, passwd)){
+ if(passwd[0])
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Password to UNLOCK doesn't match password used to LOCK"));
+
+ help = NO_HELP;
+ while(1){
+ int rc;
+
+ flags = OE_DISALLOW_CANCEL
+ | (F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD);
+ rc = optionally_enter(passwd, -FOOTER_ROWS(ps), 0, sizeof(passwd),
+ _("Enter password to UNLOCK keyboard : "),NULL,
+ help, &flags);
+ if(rc == 3) {
+ help = help == NO_HELP ? h_oe_keylock : NO_HELP;
+ continue;
+ }
+
+ if(rc != 4)
+ break;
+ }
+ }
+
+ if(old_suspend)
+ F_TURN_ON(F_CAN_SUSPEND, ps_global);
+
+ q_status_message(SM_ORDER, 0, 3, _("Keyboard Unlocked"));
+ return(0);
+}
+
+
+#endif /* KEYBOARD_LOCK */
diff --git a/alpine/kblock.h b/alpine/kblock.h
new file mode 100644
index 00000000..0b6984c0
--- /dev/null
+++ b/alpine/kblock.h
@@ -0,0 +1,27 @@
+/*
+ * $Id: kblock.h 870 2007-12-14 06:45:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_KBLOCK_INCLUDED
+#define PINE_KBLOCK_INCLUDED
+
+
+#ifdef KEYBOARD_LOCK
+
+/* exported protoypes */
+int lock_keyboard(void);
+
+#endif /* KEYBOARD_LOCK */
+
+#endif /* PINE_KBLOCK_INCLUDED */
diff --git a/alpine/keymenu.c b/alpine/keymenu.c
new file mode 100644
index 00000000..929e6ada
--- /dev/null
+++ b/alpine/keymenu.c
@@ -0,0 +1,3961 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: keymenu.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "keymenu.h"
+#include "mailcmd.h"
+#include "signal.h"
+#include "status.h"
+#include "../pith/bitmap.h"
+#include "../pith/conf.h"
+#include "../pith/state.h"
+
+
+/*
+ * We put all of the keymenu definitions here so that it is easy to
+ * share them. The names are in keymenu.h, as well. Some of these
+ * aren't easily shareable because we modify them dynamically but
+ * here they are anyway. It's not altogether clear that this is a good idea.
+ * Perhaps it would be better to just define the keymenus multiple times
+ * in different source files even though they are the same, with static
+ * declarations.
+ *
+ * The key numbers are sometimes used symbolically, like OTHER_KEY. Those
+ * are in keymenu.h, too, and are hard to use. If you change a menu here
+ * be sure to check those key numbers that go with it in keymenu.h.
+ */
+
+#ifdef _WINDOWS
+void configure_menu_items (struct key_menu *, bitmap_t);
+#endif
+
+
+/*
+ * Macro to simplify instantiation of key_menu structs from key structs
+ */
+#define INST_KEY_MENU(X, Y) struct key_menu X = \
+ {sizeof(Y)/(sizeof(Y[0])*12), 0, 0, Y}
+
+struct key cancel_keys[] =
+ {HELP_MENU,
+ {"^C",N_("Cancel"),{MC_NONE},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(cancel_keymenu, cancel_keys);
+
+
+/*
+ * A bunch of these are NULL_MENU because we want to change them dynamically.
+ */
+struct key ab_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to Previous Entry */
+ {"P", N_("PrevEntry"), {MC_PREVITEM,1,{'p'}}, KS_NONE},
+ /* TRANSLATORS: go to Next Entry */
+ {"N", N_("NextEntry"), {MC_NEXTITEM,1,{'n'}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ QUIT_MENU,
+ NULL_MENU,
+ LISTFLD_MENU,
+ GOTO_MENU,
+ INDEX_MENU,
+ RCOMPOSE_MENU,
+ PRYNTTXT_MENU,
+ NULL_MENU,
+ SAVE_MENU,
+ FORWARD_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ /* TRANSLATORS: Select this Entry */
+ {";",N_("Select"),{MC_SELECT,1,{';'}},KS_NONE},
+ /* TRANSLATORS: Apply a command to several objects at once */
+ {"A",N_("Apply"),{MC_APPLY,1,{'a'}},KS_APPLY},
+ /* TRANSLATORS: Select Current entry */
+ {":",N_("SelectCur"),{MC_SELCUR,1,{':'}},KS_SELECTCUR},
+ /* TRANSLATORS: Zoom refers to zooming in on a smaller set of
+ items, like with a camera zoom lense */
+ {"Z",N_("ZoomMode"),{MC_ZOOM,1,{'z'}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(ab_keymenu, ab_keys);
+
+
+struct key abook_select_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E", N_("Exit"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE},
+ /* TRANSLATORS: go to Previous entry */
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(abook_select_km, abook_select_keys);
+
+
+struct key abook_view_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ /* TRANSLATORS: Abook is an abbreviation for Address Book */
+ {"<",N_("Abook"),{MC_EXIT,2,{'<',','}},KS_NONE},
+ {"U",N_("Update"),{MC_EDIT,1,{'u'}},KS_NONE},
+ /* TRANSLATORS: ComposeTo means to start editing a new message to
+ this address book entry */
+ {"C",N_("ComposeTo"),{MC_COMPOSE,1,{'c'}},KS_COMPOSER},
+ RCOMPOSE_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ /* TRANSLATORS: abbreviation for Forward as Email */
+ {"F", N_("Fwd Email"), {MC_FORWARD, 1, {'f'}}, KS_FORWARD},
+ SAVE_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ /* TRANSLATORS: View the highlighted link, for example, a URL */
+ {"V",N_("ViewLink"),{MC_VIEW_HANDLE,3,{'v',ctrl('m'),ctrl('j')}},KS_NONE},
+ NULL_MENU,
+ /* TRANSLATORS: go to the previous link, for example, the previous URL */
+ {"^B",N_("PrevLink"),{MC_PREV_HANDLE,1,{ctrl('B')}},KS_NONE},
+ {"^F",N_("NextLink"),{MC_NEXT_HANDLE,1,{ctrl('F')}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(abook_view_keymenu, abook_view_keys);
+
+
+struct key abook_text_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E",N_("Exit Viewer"),{MC_EXIT,1,{'e'}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ FWDEMAIL_MENU,
+ {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE}};
+INST_KEY_MENU(abook_text_km, abook_text_keys);
+
+
+struct key ldap_view_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ /* TRANSLATORS: go back to the index of results instead of
+ viewing this particular entry */
+ {"<",N_("Results Index"),{MC_EXIT,2,{'<',','}},KS_NONE},
+ NULL_MENU,
+ {"C", N_("ComposeTo"), {MC_COMPOSE,1,{'c'}}, KS_COMPOSER},
+ RCOMPOSE_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ FWDEMAIL_MENU,
+ SAVE_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ {"V",N_("ViewLink"),{MC_VIEW_HANDLE,3,{'v',ctrl('m'),ctrl('j')}},KS_NONE},
+ NULL_MENU,
+ {"^B",N_("PrevLink"),{MC_PREV_HANDLE,1,{ctrl('B')}},KS_NONE},
+ {"^F",N_("NextLink"),{MC_NEXT_HANDLE,1,{ctrl('F')}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(ldap_view_keymenu, ldap_view_keys);
+
+
+struct key context_mgr_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"<", N_("Main Menu"), {MC_MAIN,3,{'m','<',','}}, KS_EXITMODE},
+ /* TRANSLATORS: View this Collection of folders */
+ {">", "[" N_("View Cltn") "]",
+ {MC_CHOICE,5,{'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREVC_MENU,
+ NEXTC_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ QUIT_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ GOTO_MENU,
+ CIND_MENU,
+ COMPOSE_MENU,
+ PRYNTTXT_MENU,
+ RCOMPOSE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(c_mgr_km, context_mgr_keys);
+
+
+struct key context_cfg_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E", N_("Exit Setup"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"C", "[" N_("Change") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREVC_MENU,
+ NEXTC_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ /* TRANSLATORS; Add a Collection of folders */
+ {"A", N_("Add Cltn"), {MC_ADD,1,{'a'}}, KS_NONE},
+ DELC_MENU,
+ /* TRANSLATORS; Change the order of items in a list */
+ {"$", N_("Shuffle"), {MC_SHUFFLE,1,{'$'}},KS_NONE},
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ PRYNTTXT_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(c_cfg_km, context_cfg_keys);
+
+
+struct key context_select_keys[] =
+ {HELP_MENU,
+ {"E", N_("ExitSelect"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ NULL_MENU,
+ /* TRANSLATORS: View this collection */
+ {">", "[" N_("View Cltn") "]",
+ {MC_CHOICE, 5, {'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREVC_MENU,
+ NEXTC_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(c_sel_km, context_select_keys);
+
+
+struct key context_fcc_keys[] =
+ {HELP_MENU,
+ {"E", N_("ExitSelect"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ NULL_MENU,
+ {">", "[" N_("View Cltn") "]",
+ {MC_CHOICE, 5, {'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREVC_MENU,
+ NEXTC_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(c_fcc_km, context_fcc_keys);
+
+static struct key quota_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E","Exit",{MC_EXIT,3,{'e','i',ctrl('C')}},KS_EXITMODE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(pine_quota_keymenu, quota_keys);
+
+struct key folder_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"<", NULL, {MC_EXIT,2,{'<',','}}, KS_NONE},
+ {">", NULL, {MC_CHOICE,2,{'>','.'}}, KS_NONE},
+ PREVF_MENU,
+ NEXTF_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ /* TRANSLATORS: make an addition, for example add a new folder
+ or a new entry in an address book */
+ {"A",N_("Add"),{MC_ADDFLDR,1,{'a'}},KS_NONE},
+ DELETE_MENU,
+ /* TRANSLATORS: change the name of something */
+ {"R",N_("Rename"),{MC_RENAMEFLDR,1,{'r'}}, KS_NONE},
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ QUIT_MENU,
+ MAIN_MENU,
+ {"V", "[" N_("View Fldr") "]", {MC_OPENFLDR}, KS_NONE},
+ GOTO_MENU,
+ CIND_MENU,
+ COMPOSE_MENU,
+ {"%", N_("Print"), {MC_PRINTFLDR,1,{'%'}}, KS_PRINT},
+ {"Z", N_("ZoomMode"), {MC_ZOOM,1,{'z'}}, KS_NONE},
+ {";",N_("Select"),{MC_SELECT,1,{';'}},KS_SELECT},
+ /* TRANSLATORS: Select current item */
+ {":",N_("SelectCur"),{MC_SELCUR,1,{':'}},KS_SELECT},
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"$", N_("Shuffle"), {MC_SHUFFLE,1,{'$'}},KS_NONE},
+ RCOMPOSE_MENU,
+ EXPORT_MENU,
+ /* TRANSLATORS: Import refers to bringing something in from
+ outside of alpine's normal world */
+ {"U", N_("Import"), {MC_IMPORT,1,{'u'}},KS_NONE},
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(folder_km, folder_keys);
+
+
+struct key folder_sel_keys[] =
+ {HELP_MENU,
+ {"E", N_("ExitSelect"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ NULL_MENU,
+ {NULL, NULL, {MC_CHOICE,0}, KS_NONE},
+ PREVF_MENU,
+ NEXTF_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"S", N_("Select"), {MC_OPENFLDR,1,{'s'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(folder_sel_km, folder_sel_keys);
+
+
+struct key folder_sela_keys[] =
+ {HELP_MENU,
+ {"E", N_("ExitSelect"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ NULL_MENU,
+ {NULL, NULL, {MC_CHOICE,0}, KS_NONE},
+ PREVF_MENU,
+ NEXTF_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"S", N_("Select"), {MC_OPENFLDR,1,{'s'}}, KS_NONE},
+ NULL_MENU,
+ {"A", N_("AddNew"), {MC_ADD,1,{'a'}}, KS_NONE},
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(folder_sela_km, folder_sela_keys);
+
+
+struct key folder_sub_keys[] =
+ {HELP_MENU,
+ /* TRANSLATORS: Subscribe to a news group */
+ {"S", N_("Subscribe"), {MC_CHOICE,1,{'s'}}, KS_NONE},
+ /* TRANSLATORS: Exit Subscribe screen */
+ {"E", N_("ExitSubscb"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {NULL, "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREVF_MENU,
+ NEXTF_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ /* TRANSLATORS: List Mode in alpine is where you can select not just
+ one of something but you can select a whole list of something, for
+ example a whole list of addresses to send to. */
+ {"L", N_("List Mode"), {MC_LISTMODE, 1, {'l'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(folder_sub_km, folder_sub_keys);
+
+
+struct key folder_post_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E", N_("ExitSelect"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_CHOICE, 3, {'s',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREVF_MENU,
+ NEXTF_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(folder_post_km, folder_post_keys);
+
+
+struct key help_keys[] =
+ {MAIN_MENU,
+ {NULL,NULL,{MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {NULL,NULL,{MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {NULL,NULL,{MC_VIEW_HANDLE,3,{'v',ctrl('m'),ctrl('j')}},KS_NONE},
+ {"^B",N_("PrevLink"),{MC_PREV_HANDLE,1,{ctrl('B')}},KS_NONE},
+ {"^F",N_("NextLink"),{MC_NEXT_HANDLE,1,{ctrl('F')}},KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTMSG_MENU,
+ {"Z",N_("Print All"),{MC_PRINTALL,1,{'z'}},KS_NONE},
+ {"N",N_("Name"),{MC_EXPORT,1,{'n'}},KS_NONE},
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(help_keymenu, help_keys);
+
+
+struct key rev_msg_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E",N_("Exit Viewer"),{MC_EXIT,1,{'e'}},KS_EXITMODE},
+ NULL_MENU,
+ {"T",NULL,{MC_TOGGLE,1,{'t'}},KS_NONE},
+ {"D",NULL,{MC_JUMP,1,{'d'}},KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ FWDEMAIL_MENU,
+ {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE},
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(rev_msg_keymenu, rev_msg_keys);
+
+
+struct key ans_certfail_keys[] =
+ {NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: Continue means to keep going. The user is paused to read
+ something and has to tell us to continue when they are finished. */
+ {"C","[" N_("Continue") "]",{MC_YES,3,{'c',ctrl('J'),ctrl('M')}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(ans_certfail_keymenu, ans_certfail_keys);
+
+
+struct key ans_certquery_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"Y",N_("Yes, continue"),{MC_YES,1,{'y'}},KS_NONE},
+ {"D","[" N_("Details") "]",{MC_VIEW_HANDLE,3,{'d',ctrl('M'),ctrl('J')}},KS_NONE},
+ {"N",N_("No"),{MC_NO,1,{'n'}},KS_NONE},
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ FWDEMAIL_MENU,
+ {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE}};
+INST_KEY_MENU(ans_certquery_keymenu, ans_certquery_keys);
+
+
+struct key forge_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"Y",N_("Yes, continue"),{MC_YES,1,{'y'}},KS_NONE},
+ {"N",N_("No"),{MC_NO,1,{'n'}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ FWDEMAIL_MENU,
+ {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE}};
+INST_KEY_MENU(forge_keymenu, forge_keys);
+
+
+struct key listmgr_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: Exit Command List */
+ {"E",N_("Exit CmdList"),{MC_EXIT,1,{'e'}},KS_EXITMODE},
+ {"Ret","[" N_("Try Command") "]",{MC_VIEW_HANDLE,3,
+ {ctrl('m'),ctrl('j'),KEY_RIGHT}},KS_NONE},
+ /* TRANSLATORS: go to Previous Command in list */
+ {"^B",N_("Prev Cmd"),{MC_PREV_HANDLE,1,{ctrl('B')}},KS_NONE},
+ /* TRANSLATORS: go to Next Command in list */
+ {"^F",N_("Next Cmd"),{MC_NEXT_HANDLE,1,{ctrl('F')}},KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(listmgr_keymenu, listmgr_keys);
+
+
+struct key index_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"<", NULL, {MC_FOLDERS,2,{'<',','}}, KS_NONE},
+ VIEWMSG_MENU,
+ PREVMSG_MENU,
+ NEXTMSG_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ DELETE_MENU,
+ UNDELETE_MENU,
+ REPLY_MENU,
+ FORWARD_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ MAIN_MENU,
+ QUIT_MENU,
+ COMPOSE_MENU,
+ GOTO_MENU,
+ TAB_MENU,
+ WHEREIS_MENU,
+ PRYNTMSG_MENU,
+ TAKE_MENU,
+ SAVE_MENU,
+ EXPORT_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ {"X",NULL,{MC_EXPUNGE,1,{'x'}},KS_NONE},
+ /* TRANSLATORS: this stands for unexclude which is the opposite
+ of the exclude command. Exclude eliminates some messages from
+ the view and unexclude gets them back. */
+ {"&",N_("unXclude"),{MC_UNEXCLUDE,1,{'&'}},KS_NONE},
+ {";",N_("Select"),{MC_SELECT,1,{';'}},KS_SELECT},
+ {"A",N_("Apply"),{MC_APPLY,1,{'a'}},KS_APPLY},
+ FLDRSORT_MENU,
+ JUMP_MENU,
+ HDRMODE_MENU,
+ BOUNCE_MENU,
+ FLAG_MENU,
+ PIPE_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ {":",N_("SelectCur"),{MC_SELCUR,1,{':'}},KS_SELECTCUR},
+ {"Z",N_("ZoomMode"),{MC_ZOOM,1,{'z'}},KS_ZOOM},
+ LISTFLD_MENU,
+ RCOMPOSE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: toggles a collapsed view or an expanded view
+ of a message thread on and off */
+ {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE},
+ {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE},
+ NULL_MENU};
+INST_KEY_MENU(index_keymenu, index_keys);
+
+
+struct key simple_index_keys[] =
+ {HELP_MENU,
+ {"E",N_("ExitSelect"),{MC_EXIT,1,{'e'}},KS_EXITMODE},
+ NULL_MENU,
+ {"S","[" N_("Select") "]",{MC_SELECT,3,{'s',ctrl('M'),ctrl('J')}},KS_SELECT},
+ PREVMSG_MENU,
+ NEXTMSG_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ DELETE_MENU,
+ UNDELETE_MENU,
+ WHEREIS_MENU,
+ NULL_MENU};
+INST_KEY_MENU(simple_index_keymenu, simple_index_keys);
+
+
+struct key thread_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ /* TRANSLATORS: go to the Folder List */
+ {"<", N_("FldrList"), {MC_FOLDERS,2,{'<',','}}, KS_NONE},
+ /* TRANSLATORS: View a Thread of messages */
+ {">", "[" N_("ViewThd") "]", {MC_VIEW_ENTRY,5,{'v','.','>',ctrl('M'),ctrl('J')}},
+ KS_VIEW},
+ /* TRANSLATORS: go to the Previous Thread */
+ {"P", N_("PrevThd"), {MC_PREVITEM, 1, {'p'}}, KS_PREVMSG},
+ /* TRANSLATORS: go to the Next Thread */
+ {"N", N_("NextThd"), {MC_NEXTITEM, 1, {'n'}}, KS_NEXTMSG},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ DELETE_MENU,
+ UNDELETE_MENU,
+ REPLY_MENU,
+ FORWARD_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ MAIN_MENU,
+ QUIT_MENU,
+ COMPOSE_MENU,
+ GOTO_MENU,
+ TAB_MENU,
+ WHEREIS_MENU,
+ PRYNTMSG_MENU,
+ TAKE_MENU,
+ SAVE_MENU,
+ EXPORT_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ {"X",NULL,{MC_EXPUNGE,1,{'x'}},KS_NONE},
+ {"&",N_("unXclude"),{MC_UNEXCLUDE,1,{'&'}},KS_NONE},
+ {";",N_("Select"),{MC_SELECT,1,{';'}},KS_SELECT},
+ {"A",N_("Apply"),{MC_APPLY,1,{'a'}},KS_APPLY},
+ FLDRSORT_MENU,
+ JUMP_MENU,
+ HDRMODE_MENU,
+ BOUNCE_MENU,
+ FLAG_MENU,
+ PIPE_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ {":",N_("SelectCur"),{MC_SELCUR,1,{':'}},KS_SELECTCUR},
+ {"Z",N_("ZoomMode"),{MC_ZOOM,1,{'z'}},KS_ZOOM},
+ LISTFLD_MENU,
+ RCOMPOSE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE},
+ {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE},
+ NULL_MENU};
+INST_KEY_MENU(thread_keymenu, thread_keys);
+
+
+struct key att_index_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"<",NULL,{MC_EXIT,2,{'<',','}},KS_EXITMODE},
+ {">","[" N_("View") "]",{MC_VIEW_ATCH,5,{'v','>','.',ctrl('M'),ctrl('J')}},
+ KS_VIEW},
+ /* TRANSLATORS: go to Previous Attachment */
+ {"P", N_("PrevAttch"),{MC_PREVITEM,4,{'p',ctrl('B'),ctrl('P'),KEY_UP}},
+ KS_PREVMSG},
+ /* TRANSLATORS: go to Next Attachment */
+ {"N", N_("NextAtch"),
+ {MC_NEXTITEM, 5, {'n','\t',ctrl('F'),ctrl('N'), KEY_DOWN}},
+ KS_NEXTMSG},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ DELETE_MENU,
+ UNDELETE_MENU,
+ {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE},
+ {NULL, NULL, {MC_EXPORT, 1, {'e'}}, KS_EXPORT},
+
+ HELP_MENU,
+ OTHER_MENU,
+ MAIN_MENU,
+ QUIT_MENU,
+ PIPE_MENU,
+ BOUNCE_MENU,
+ /* TRANSLATORS: About Attachment, a short description of the attachment */
+ {"A",N_("AboutAttch"),{MC_ABOUTATCH,1,{'a'}},KS_NONE},
+ WHEREIS_MENU,
+ {"%", N_("Print"), {MC_PRINTMSG,1,{'%'}}, KS_PRINT},
+ INDEX_MENU,
+ REPLY_MENU,
+ FORWARD_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ HDRMODE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(att_index_keymenu, att_index_keys);
+
+
+struct key att_view_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"<",NULL,{MC_EXIT,2,{'<',','}},KS_EXITMODE},
+ /* TRANSLATORS: View highlighted URL */
+ {"Ret","[" N_("View Hilite") "]",{MC_VIEW_HANDLE,3,
+ {ctrl('m'),ctrl('j'),KEY_RIGHT}},KS_NONE},
+ /* TRANSLATORS: go to Previous URL */
+ {"^B",N_("Prev URL"),{MC_PREV_HANDLE,1,{ctrl('B')}},KS_NONE},
+ /* TRANSLATORS: go to Next URL */
+ {"^F",N_("Next URL"),{MC_NEXT_HANDLE,1,{ctrl('F')}},KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ DELETE_MENU,
+ UNDELETE_MENU,
+ SAVE_MENU,
+ EXPORT_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ MAIN_MENU,
+ QUIT_MENU,
+ PIPE_MENU,
+ BOUNCE_MENU,
+ HDRMODE_MENU,
+ WHEREIS_MENU,
+ {"%", N_("Print"), {MC_PRINTMSG,1,{'%'}}, KS_PRINT},
+ NULL_MENU,
+ REPLY_MENU,
+ FORWARD_MENU};
+INST_KEY_MENU(att_view_keymenu, att_view_keys);
+
+
+struct key view_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ /* TRANSLATORS: go to Message Index */
+ {"<",N_("MsgIndex"),{MC_INDEX,3,{'i','<',','}},KS_FLDRINDEX},
+ /* TRANSLATORS: View the Attachment */
+ {">",N_("ViewAttch"),{MC_VIEW_ATCH,3,{'v','>','.'}},KS_NONE},
+ PREVMSG_MENU,
+ NEXTMSG_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ DELETE_MENU,
+ UNDELETE_MENU,
+ REPLY_MENU,
+ FORWARD_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ MAIN_MENU,
+ QUIT_MENU,
+ LISTFLD_MENU,
+ GOTO_MENU,
+ COMPOSE_MENU,
+ WHEREIS_MENU,
+ PRYNTMSG_MENU,
+ TAKE_MENU,
+ SAVE_MENU,
+ EXPORT_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ /* TRANSLATORS: View the highlighted URL */
+ {"Ret","[" N_("View Hilite") "]",{MC_VIEW_HANDLE,3,
+ {ctrl('m'),ctrl('j'),KEY_RIGHT}},KS_NONE},
+ /* TRANSLATORS: Select the current item */
+ {":",N_("SelectCur"),{MC_SELCUR,1,{':'}},KS_SELECTCUR},
+ /* TRANSLATORS: go to previous URL */
+ {"^B",N_("Prev URL"),{MC_PREV_HANDLE,1,{ctrl('B')}},KS_NONE},
+ /* TRANSLATORS: go to next URL */
+ {"^F",N_("Next URL"),{MC_NEXT_HANDLE,1,{ctrl('F')}},KS_NONE},
+ JUMP_MENU,
+ TAB_MENU,
+ HDRMODE_MENU,
+ BOUNCE_MENU,
+ FLAG_MENU,
+ PIPE_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ RCOMPOSE_MENU,
+ {"A",N_("TogglePreferPlain"),{MC_TOGGLE,1,{'a'}},KS_NONE},
+#ifdef SMIME
+ {"^D","Decrypt", {MC_DECRYPT,1,{ctrl('d')},KS_NONE}},
+ {"^E","Security", {MC_SECURITY,1,{ctrl('e')},KS_NONE}},
+#else
+ NULL_MENU,
+ NULL_MENU,
+#endif
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(view_keymenu, view_keys);
+
+
+struct key simple_text_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E",N_("Exit Viewer"),{MC_EXIT,1,{'e'}},KS_EXITMODE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ FWDEMAIL_MENU,
+ {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE},
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(simple_text_keymenu, simple_text_keys);
+
+
+struct key oe_keys[] =
+ {{"^G",N_("Help"),{MC_NONE},KS_SCREENHELP},
+ {"^C",N_("Cancel"),{MC_NONE},KS_NONE},
+ {"^T","xxx",{MC_NONE},KS_NONE},
+ /* TRANSLATORS: The user is entering characters, for example, the
+ name of a folder. Accept means the user is done and wants to
+ accept what is currently displayed. */
+ {"Ret",N_("Accept"),{MC_NONE},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(oe_keymenu, oe_keys);
+
+
+struct key choose_setup_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E",N_("Exit Setup"),{MC_EXIT,2,{'e',ctrl('C')}},KS_EXITMODE},
+ {"P",N_("Printer"),{MC_PRINTER,1,{'p'}},KS_NONE},
+ /* TRANSLATORS: Change password */
+ {"N",N_("Newpassword"),{MC_PASSWD,1,{'n'}},KS_NONE},
+ /* TRANSLATORS: Configure Alpine */
+ {"C",N_("Config"),{MC_CONFIG,1,{'c'}},KS_NONE},
+ /* TRANSLATORS: Edit signature block */
+ {"S",N_("Signature"),{MC_SIG,1,{'s'}},KS_NONE},
+ /* TRANSLATORS: configure address books */
+ {"A",N_("AddressBooks"),{MC_ABOOKS,1,{'a'}},KS_NONE},
+ /* TRANSLATORS: configure collection lists */
+ {"L",N_("collectionList"),{MC_CLISTS,1,{'l'}},KS_NONE},
+ /* TRANSLATORS: configure rules, an alpine concept */
+ {"R",N_("Rules"),{MC_RULES,1,{'r'}},KS_NONE},
+ /* TRANSLATORS: configure directory servers */
+ {"D",N_("Directory"),{MC_DIRECTORY,1,{'d'}},KS_NONE},
+ /* TRANSLATORS: configure color */
+ {"K",N_("Kolor"),{MC_KOLOR,1,{'k'}},KS_NONE},
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: remote configuration setup */
+ {"Z",N_("RemoteConfigSetup"),{MC_REMOTE,1,{'z'}},KS_NONE},
+ /* TRANSLATORS: configure S/MIME */
+ {"M",N_("S/Mime"),{MC_SECURITY,1,{'m'}},KS_NONE},
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(choose_setup_keymenu, choose_setup_keys);
+
+
+struct key main_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to Previous Command in list */
+ {"P",N_("PrevCmd"),{MC_PREVITEM,3,{'p',ctrl('P'),KEY_UP}},KS_NONE},
+ {"N",N_("NextCmd"),{MC_NEXTITEM,3,{'n',ctrl('N'),KEY_DOWN}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: show release notes */
+ {"R",N_("RelNotes"),{MC_RELNOTES,1,{'r'}},KS_NONE},
+ /* TRANSLATORS: lock keyboard */
+ {"K",N_("KBLock"),{MC_KBLOCK,1,{'k'}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ QUIT_MENU,
+ COMPOSE_MENU,
+ LISTFLD_MENU,
+ GOTO_MENU,
+ {"I",N_("Index"),{MC_INDEX,1,{'i'}},KS_FLDRINDEX},
+ /* TRANSLATORS: go to the Journal. The Journal shows past
+ messages that alpine has shown the user. */
+ {"J",N_("Journal"),{MC_JOURNAL,1,{'j'}},KS_REVIEW},
+ /* TRANSLATORS: go to the Setup screen */
+ {"S",N_("Setup"),{MC_SETUP,1,{'s'}},KS_NONE},
+ /* TRANSLATORS: go to the address book screen */
+ {"A",N_("AddrBook"),{MC_ADDRBOOK,1,{'a'}},KS_ADDRBOOK},
+ RCOMPOSE_MENU,
+ NULL_MENU};
+INST_KEY_MENU(main_keymenu, main_keys);
+
+
+struct key simple_file_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"Q",N_("Quit Viewer"),{MC_EXIT,1,{'q'}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+ FWDEMAIL_MENU,
+ {"S", N_("Save"), {MC_SAVETEXT,1,{'s'}}, KS_SAVE},
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(simple_file_keymenu, simple_file_keys);
+
+
+struct key nuov_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E",NULL,{MC_EXIT,1,{'e',ctrl('M'),ctrl('J')}},KS_NONE},
+ /* TRANSLATORS: Alpine asks the user to be counted when they
+ first start using alpine. */
+ {"Ret","[" N_("Be Counted!") "]",{MC_VIEW_HANDLE,2,{ctrl('M'),ctrl('J')}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ PRYNTMSG_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: show release notes */
+ {"R",N_("RelNotes"),{MC_RELNOTES,1,{'r'}},KS_NONE},
+ NULL_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(nuov_keymenu, nuov_keys);
+
+
+struct key modal_message_keys[] =
+ {NULL_MENU,
+ NULL_MENU,
+ {"Ret",N_("Finished"),{MC_EXIT,2,{ctrl('m'),ctrl('j')}},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(modal_message_keymenu, modal_message_keys);
+
+
+struct key ta_keys_lm[] =
+ {HELP_MENU,
+ WHEREIS_MENU,
+ TA_EXIT_MENU,
+ /* TRANSLATORS: Take this address into the address book */
+ {"T", N_("Take"), {MC_TAKE,1,{'t'}}, KS_NONE},
+ TA_PREV_MENU,
+ TA_NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"X","[" N_("Set/Unset") "]", {MC_CHOICE,3,{'x',ctrl('M'),ctrl('J')}}, KS_NONE},
+ {"A", N_("SetAll"),{MC_SELALL,1,{'a'}},KS_NONE},
+ {"U",N_("UnSetAll"),{MC_UNSELALL,1,{'u'}},KS_NONE},
+ /* TRANSLATORS: The Take Address screen has a Single mode and a
+ List mode. This command causes us to go into Single mode. */
+ {"S",N_("SinglMode"),{MC_LISTMODE,1,{'s'}},KS_NONE}};
+INST_KEY_MENU(ta_keymenu_lm, ta_keys_lm);
+
+
+struct key ta_keys_sm[] =
+ {HELP_MENU,
+ WHEREIS_MENU,
+ TA_EXIT_MENU,
+ {"T","[" N_("Take") "]",{MC_TAKE,3,{'t',ctrl('M'),ctrl('J')}}, KS_NONE},
+ TA_PREV_MENU,
+ TA_NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: The Take Address screen has a Single mode and a
+ List mode. This command causes us to go into List mode. */
+ {"L",N_("ListMode"),{MC_LISTMODE,1,{'l'}},KS_NONE}};
+INST_KEY_MENU(ta_keymenu_sm, ta_keys_sm);
+
+
+struct key pipe_cancel_keys[] =
+ {NULL_MENU,
+ {"^C",N_("Stop Waiting"),{MC_NONE},KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(pipe_cancel_keymenu, pipe_cancel_keys);
+
+
+struct key color_pattern_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ /* TRANSLATORS: Change Value */
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: Delete Value */
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(color_pattern_keymenu, color_pattern_keys);
+
+
+struct key hdr_color_checkbox_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ EXIT_SETUP_MENU,
+ TOGGLEB_MENU,
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(hdr_color_checkbox_keymenu, hdr_color_checkbox_keys);
+
+
+struct key kw_color_checkbox_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ EXIT_SETUP_MENU,
+ TOGGLEC_MENU,
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(kw_color_checkbox_keymenu, kw_color_checkbox_keys);
+
+
+struct key selectable_bold_checkbox_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ EXIT_SETUP_MENU,
+ TOGGLED_MENU,
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(selectable_bold_checkbox_keymenu, selectable_bold_checkbox_keys);
+
+
+struct key flag_keys[] =
+ {HELP_MENU,
+ {"A", N_("Add KW"), {MC_ADD,1,{'a'}}, KS_NONE},
+ /* TRANSLATORS: Exit from the Flags screen */
+ {"E", N_("Exit Flags"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ TOGGLE_MENU,
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(flag_keymenu, flag_keys);
+
+
+struct key addr_select_keys[] =
+ {HELP_MENU,
+ {"E", N_("ExitSelect"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ NULL_MENU,
+ {"S", "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(addr_s_km, addr_select_keys);
+
+
+struct key addr_select_with_goback_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to address book list */
+ {"<", N_("AddressBkList"), {MC_ADDRBOOK,2,{'<',','}}, KS_NONE},
+ {"S", "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ {"E", N_("ExitSelect"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ WHEREIS_MENU};
+INST_KEY_MENU(addr_s_km_with_goback, addr_select_with_goback_keys);
+
+static struct key addr_select_with_view_keys[] =
+ {HELP_MENU,
+ RCOMPOSE_MENU,
+ {"<", N_("AddressBkList"), {MC_ADDRBOOK,2,{'<',','}}, KS_NONE},
+ {">", "[" N_("View") "]",
+ {MC_VIEW_TEXT,5,{'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ /* TRANSLATORS: compose a message to the current address */
+ {"C", N_("ComposeTo"), {MC_COMPOSE,1,{'c'}}, KS_COMPOSER},
+ FWDEMAIL_MENU,
+ SAVE_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(addr_s_km_with_view, addr_select_with_view_keys);
+
+
+struct key addr_select_for_url_keys[] =
+ {HELP_MENU,
+ RCOMPOSE_MENU,
+ {"<", N_("Exit Viewer"), {MC_ADDRBOOK,3,{'<',',','e'}}, KS_NONE},
+ {">", "[" N_("View") "]",
+ {MC_VIEW_TEXT,5,{'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"C", N_("ComposeTo"), {MC_COMPOSE,1,{'c'}}, KS_COMPOSER},
+ FWDEMAIL_MENU,
+ SAVE_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(addr_s_km_for_url, addr_select_for_url_keys);
+
+
+struct key addr_select_exit_keys[] =
+ {NULL_MENU,
+ NULL_MENU,
+ {"E", "[" N_("Exit") "]", {MC_EXIT,3,{'e',ctrl('M'),ctrl('J')}},
+ KS_EXITMODE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(addr_s_km_exit, addr_select_exit_keys);
+
+
+struct key addr_select_goback_keys[] =
+ {NULL_MENU,
+ NULL_MENU,
+ {"E", "[" N_("Exit") "]", {MC_ADDRBOOK,3,{'e',ctrl('M'),ctrl('J')}},
+ KS_EXITMODE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(addr_s_km_goback, addr_select_goback_keys);
+
+
+struct key config_text_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_text_keymenu, config_text_keys);
+
+
+struct key config_text_to_charsets_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to list of keywords */
+ {"T", N_("ToCharsets"), {MC_CHOICE, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_text_to_charsets_keymenu, config_text_to_charsets_keys);
+
+
+struct key direct_config_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E", N_("Exit Setup"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"C", "[" N_("Change") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ /* TRANSLATORS: go to previous LDAP directory server in the list */
+ {"P", N_("PrevDir"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("NextDir"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ /* TRANSLATORS: add a directory server to configuration */
+ {"A", N_("Add Dir"), {MC_ADD,1,{'a'}}, KS_NONE},
+ /* TRANSLATORS: delete a directory */
+ {"D", N_("Del Dir"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ {"$", N_("Shuffle"), {MC_SHUFFLE,1,{'$'}}, KS_NONE},
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(dir_conf_km, direct_config_keys);
+
+
+struct key sel_from_list_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E", N_("Exit"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_SELECT,3,{'s',ctrl('J'),ctrl('M')}}, KS_NONE},
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(sel_from_list, sel_from_list_keys);
+
+
+struct key sel_from_list_keys_ctrlc[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"^C", N_("exit"), {MC_EXIT,1,{ctrl('C')}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_SELECT,3,{'s',ctrl('J'),ctrl('M')}}, KS_NONE},
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(sel_from_list_ctrlc, sel_from_list_keys_ctrlc);
+
+
+struct key sel_from_list_keys_sm[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E", N_("Exit"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_SELECT,3,{'s',ctrl('J'),ctrl('M')}}, KS_NONE},
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ {"L",N_("ListMode"),{MC_LISTMODE,1,{'l'}},KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(sel_from_list_sm, sel_from_list_keys_sm);
+
+
+struct key sel_from_list_keys_sm_ctrlc[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"^C", N_("exit"), {MC_EXIT,1,{ctrl('C')}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_SELECT,3,{'s',ctrl('J'),ctrl('M')}}, KS_NONE},
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ {"L",N_("ListMode"),{MC_LISTMODE,1,{'l'}},KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(sel_from_list_sm_ctrlc, sel_from_list_keys_sm_ctrlc);
+
+
+struct key sel_from_list_keys_lm[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E", N_("Exit"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_SELECT,3,{'s',ctrl('J'),ctrl('M')}}, KS_NONE},
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"X", N_("Set/Unset"), {MC_TOGGLE,1,{'x'}}, KS_NONE},
+ {"1",N_("SinglMode"),{MC_LISTMODE,1,{'1'}},KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(sel_from_list_lm, sel_from_list_keys_lm);
+
+
+struct key sel_from_list_keys_lm_ctrlc[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"^C", N_("exit"), {MC_EXIT,1,{ctrl('C')}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_SELECT,3,{'s',ctrl('J'),ctrl('M')}}, KS_NONE},
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"X", N_("Set/Unset"), {MC_TOGGLE,1,{'x'}}, KS_NONE},
+ {"1",N_("SinglMode"),{MC_LISTMODE,1,{'1'}},KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(sel_from_list_lm_ctrlc, sel_from_list_keys_lm_ctrlc);
+
+
+struct key sel_from_list_keys_olm[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E", N_("Exit"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_SELECT,3,{'s',ctrl('J'),ctrl('M')}}, KS_NONE},
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"X", N_("Set/Unset"), {MC_TOGGLE,1,{'x'}}, KS_NONE},
+ NULL_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(sel_from_list_olm, sel_from_list_keys_olm);
+
+
+struct key sel_from_list_keys_olm_ctrlc[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"^C", N_("exit"), {MC_EXIT,1,{ctrl('C')}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_SELECT,3,{'s',ctrl('J'),ctrl('M')}}, KS_NONE},
+ {"P", N_("Prev"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("Next"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"X", N_("Set/Unset"), {MC_TOGGLE,1,{'x'}}, KS_NONE},
+ NULL_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(sel_from_list_olm_ctrlc, sel_from_list_keys_olm_ctrlc);
+
+
+#ifndef DOS
+
+struct key printer_edit_keys[] =
+ {HELP_MENU,
+ PRYNTTXT_MENU,
+ EXIT_SETUP_MENU,
+ {"S", "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ /* TRANSLATORS: add a printer to configuration */
+ {"A", N_("Add Printer"), {MC_ADD,1,{'a'}}, KS_NONE},
+ /* TRANSLATORS: delete a printer from configuration */
+ {"D", N_("DeletePrint"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ {"C", N_("Change"), {MC_EDIT,1,{'c'}}, KS_NONE},
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(printer_edit_keymenu, printer_edit_keys);
+
+
+struct key printer_select_keys[] =
+ {HELP_MENU,
+ PRYNTTXT_MENU,
+ EXIT_SETUP_MENU,
+ {"S", "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(printer_select_keymenu, printer_select_keys);
+
+#endif /* !DOS */
+
+
+struct key role_select_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E", N_("Exit"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ NULL_MENU,
+ /* TRANSLATORS: go to previous Role in list */
+ {"P", N_("PrevRole"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("NextRole"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ {"D", "", {MC_TOGGLE, 1, {'d'}}, KS_NONE},
+ WHEREIS_MENU};
+INST_KEY_MENU(role_select_km, role_select_keys);
+
+
+struct key role_config_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E", N_("Exit Setup"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"C", "[" N_("Change") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ /* TRANSLATORS: go to previous Rule in list */
+ {"P", N_("PrevRule"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("NextRule"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ {"$", N_("Shuffle"), {MC_SHUFFLE,1,{'$'}}, KS_NONE},
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: Include a File from filesystem */
+ {"I", N_("IncludeFile"), {MC_ADDFILE,1,{'i'}}, KS_NONE},
+ {"X", N_("eXcludeFile"), {MC_DELFILE,1,{'x'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ {"R", N_("Replicate"), {MC_COPY,1,{'r'}}, KS_NONE},
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(role_conf_km, role_config_keys);
+
+
+struct key config_text_wshuf_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"$", N_("Shuffle"), {MC_SHUFFLE,1,{'$'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_text_wshuf_keymenu, config_text_wshuf_keys);
+
+
+struct key config_text_wshufandfldr_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"$", N_("Shuffle"), {MC_SHUFFLE,1,{'$'}}, KS_NONE},
+ {"T", N_("ToFldrs"), {MC_CHOICE,2,{'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_text_wshufandfldr_keymenu, config_text_wshufandfldr_keys);
+
+
+struct key config_role_file_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to list of Files */
+ {"T", N_("ToFiles"), {MC_CHOICE, 2, {'t', ctrl('T')}}, KS_NONE},
+ /* TRANSLATORS: edit a file */
+ {"F", N_("editFile"), {MC_EDITFILE, 1, {'f'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_file_keymenu, config_role_file_keys);
+
+
+struct key config_role_file_res_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"T", N_("ToFiles"), {MC_CHOICE, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_file_res_keymenu, config_role_file_res_keys);
+
+
+struct key config_role_keyword_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to list of keywords */
+ {"T", N_("ToKeywords"), {MC_CHOICE, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_keyword_keymenu, config_role_keyword_keys);
+
+
+struct key config_role_keyword_keys_not[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"T", N_("ToKeywords"), {MC_CHOICE, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ /* TRANSLATORS: toggle between NOT and not NOT, turn NOT on or off */
+ {"!", N_("toggle NOT"), {MC_NOT,1,{'!'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_keyword_keymenu_not, config_role_keyword_keys_not);
+
+
+struct key config_role_charset_keys_not[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to list of character sets */
+ {"T", N_("ToCharSets"), {MC_CHOICE, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ {"!", N_("toggle NOT"), {MC_NOT,1,{'!'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_charset_keymenu_not, config_role_charset_keys_not);
+
+
+struct key config_role_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_keymenu, config_role_keys);
+
+
+struct key config_role_keys_not[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: add extra headers to list */
+ {"X", N_("eXtraHdr"), {MC_ADDHDR, 1, {'x'}}, KS_NONE},
+ {"!", N_("toggle NOT"), {MC_NOT,1,{'!'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_keymenu_not, config_role_keys_not);
+
+
+struct key config_role_keys_extra[] =
+ {HELP_MENU,
+ NULL_MENU,
+ EXIT_SETUP_MENU,
+ {"X", "[" N_("eXtraHdr") "]", {MC_ADDHDR, 3,{'x',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(config_role_keymenu_extra, config_role_keys_extra);
+
+
+struct key config_role_addr_pat_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to address book to get address */
+ {"T", N_("ToAddrBk"), {MC_CHOICEB, 2, {'t', ctrl('T')}}, KS_NONE},
+ {"X", N_("eXtraHdr"), {MC_ADDHDR, 1, {'x'}}, KS_NONE},
+ {"!", N_("toggle NOT"), {MC_NOT,1,{'!'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_addr_pat_keymenu, config_role_addr_pat_keys);
+
+
+struct key config_role_xtrahdr_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"X", N_("eXtraHdr"), {MC_ADDHDR, 1, {'x'}}, KS_NONE},
+ {"!", N_("toggle NOT"), {MC_NOT,1,{'!'}}, KS_NONE},
+ NULL_MENU,
+ /* TRANSLATORS: remove a header we previously added */
+ {"R", N_("RemoveHdr"), {MC_DELHDR, 1, {'r'}}, KS_NONE},
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_xtrahdr_keymenu, config_role_xtrahdr_keys);
+
+
+struct key config_role_addr_act_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"T", N_("ToAddrBk"), {MC_CHOICEC, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_addr_act_keymenu, config_role_addr_act_keys);
+
+
+struct key config_role_patfolder_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to list of folders */
+ {"T", N_("ToFldrs"), {MC_CHOICED, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_patfolder_keymenu, config_role_patfolder_keys);
+
+
+struct key config_role_actionfolder_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"T", N_("ToFldrs"), {MC_CHOICEE, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_actionfolder_keymenu, config_role_actionfolder_keys);
+
+
+struct key config_role_inick_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to list of nicknames */
+ {"T", N_("ToNicks"), {MC_CHOICE, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_inick_keymenu, config_role_inick_keys);
+
+
+struct key config_role_afrom_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to list of address books */
+ {"T", N_("ToAbookList"), {MC_CHOICE, 2, {'t', ctrl('T')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_role_afrom_keymenu, config_role_afrom_keys);
+
+
+struct key config_checkbox_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ EXIT_SETUP_MENU,
+ TOGGLE_MENU,
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(config_checkbox_keymenu, config_checkbox_keys);
+
+
+struct key config_radiobutton_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ EXIT_SETUP_MENU,
+ {"*", "[" N_("Select") "]", {MC_CHOICE,3,{'*',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(config_radiobutton_keymenu, config_radiobutton_keys);
+
+
+struct key config_yesno_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change") "]", {MC_TOGGLE,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(config_yesno_keymenu, config_yesno_keys);
+
+
+struct key color_changing_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E", N_("To Colors"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"*", "[" N_("Select") "]", {MC_CHOICE,3,{'*',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(color_changing_keymenu, color_changing_keys);
+
+
+struct key custom_color_changing_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ /* TRANSLATORS: go to color configuration screen */
+ {"E", N_("To Colors"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"*", "[" N_("Select") "]", {MC_CHOICEB,3,{'*',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(custom_color_changing_keymenu, custom_color_changing_keys);
+
+
+struct key kw_color_changing_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E", N_("To Colors"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"*", "[" N_("Select") "]", {MC_CHOICEC,3,{'*',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(kw_color_changing_keymenu, kw_color_changing_keys);
+
+
+#ifdef _WINDOWS
+
+struct key color_rgb_changing_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E", N_("To Colors"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"*", "[" N_("Select") "]", {MC_CHOICE,3,{'*',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"C", N_("Customize"), {MC_RGB1,1,{'c'}},KS_NONE},
+ NULL_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(color_rgb_keymenu, color_rgb_changing_keys);
+
+
+struct key custom_rgb_changing_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E", N_("To Colors"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"*", "[" N_("Select") "]", {MC_CHOICEB,3,{'*',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"C", N_("Customize"), {MC_RGB2,1,{'c'}},KS_NONE},
+ NULL_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(custom_rgb_keymenu, custom_rgb_changing_keys);
+
+
+struct key kw_rgb_changing_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"E", N_("To Colors"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"*", "[" N_("Select") "]", {MC_CHOICEC,3,{'*',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"C", N_("Customize"), {MC_RGB3,1,{'c'}},KS_NONE},
+ NULL_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(kw_rgb_keymenu, kw_rgb_changing_keys);
+
+#endif
+
+
+struct key color_setting_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("AddHeader"), {MC_ADD,1,{'a'}}, KS_NONE},
+ /* TRANSLATORS: restore defaults */
+ {"R", N_("RestoreDefs"), {MC_DEFAULT,1,{'r'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(color_setting_keymenu, color_setting_keys);
+
+
+struct key custom_color_setting_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("AddHeader"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"R", N_("RestoreDefs"), {MC_DEFAULT,1,{'r'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"D", N_("DeleteHdr"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ /* TRANSLATORS: shuffle headers (change the order of headers) */
+ {"$", N_("ShuffleHdr"), {MC_SHUFFLE,1,{'$'}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(custom_color_setting_keymenu, custom_color_setting_keys);
+
+
+struct key role_color_setting_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ EXIT_SETUP_MENU,
+ {"*", "[" N_("Select") "]", {MC_CHOICE,3,{'*',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ PRYNTTXT_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(role_color_setting_keymenu, role_color_setting_keys);
+
+
+struct key kw_color_setting_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("AddHeader"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"R", N_("RestoreDefs"), {MC_DEFAULT,1,{'r'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(kw_color_setting_keymenu, kw_color_setting_keys);
+
+
+struct key take_export_keys_sm[] =
+ {HELP_MENU,
+ WHEREIS_MENU,
+ /* TRANSLATORS: exit the Take Address screen */
+ {"<",N_("ExitTake"), {MC_EXIT,4,{'e',ctrl('C'),'<',','}}, KS_EXITMODE},
+ {"T","[" N_("Take") "]",{MC_TAKE,3,{'t',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ {"L",N_("ListMode"),{MC_LISTMODE,1,{'l'}},KS_NONE}};
+INST_KEY_MENU(take_export_keymenu_sm, take_export_keys_sm);
+
+
+struct key take_export_keys_lm[] =
+ {HELP_MENU,
+ WHEREIS_MENU,
+ {"<",N_("ExitTake"), {MC_EXIT,4,{'e',ctrl('C'),'<',','}}, KS_EXITMODE},
+ {"T",N_("Take"), {MC_TAKE,1,{'t'}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"X","[" N_("Set/Unset") "]", {MC_CHOICE,3,{'x',ctrl('M'),ctrl('J')}}, KS_NONE},
+ {"A", N_("SetAll"),{MC_SELALL,1,{'a'}},KS_NONE},
+ {"U",N_("UnSetAll"),{MC_UNSELALL,1,{'u'}},KS_NONE},
+ {"S",N_("SinglMode"),{MC_LISTMODE,1,{'s'}},KS_NONE}};
+INST_KEY_MENU(take_export_keymenu_lm, take_export_keys_lm);
+
+
+struct key smime_info_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ {"<","Back",{MC_VIEW_TEXT,2,{'<',','}},KS_EXITMODE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ MAIN_MENU,
+ QUIT_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ INDEX_MENU,
+ NULL_MENU,
+ NULL_MENU};
+INST_KEY_MENU(smime_info_keymenu, smime_info_keys);
+
+
+struct key config_smime_helper_keys[] =
+ {HELP_MENU,
+ WHEREIS_MENU,
+ EXIT_SETUP_MENU,
+ {"T","[" N_("Transfer") "]", {MC_CHOICE,3,{'t',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_smime_helper_keymenu, config_smime_helper_keys);
+
+
+/*
+ * Internal prototypes
+ */
+void output_keymenu(struct key_menu *, bitmap_t, int, int);
+void format_keymenu(struct key_menu *, bitmap_t, int);
+void menu_clear_cmd_binding(struct key_menu *, int);
+#ifdef MOUSE
+void print_inverted_label(int, MENUITEM *);
+#endif
+
+
+/* Saved key menu drawing state */
+static struct {
+ struct key_menu *km;
+ int row,
+ column,
+ blanked;
+ bitmap_t bitmap;
+} km_state;
+
+
+/*
+ * Longest label that can be displayed in keymenu
+ */
+#define MAX_LABEL 40
+#define MAX_KEYNAME 3
+static struct key last_time_buf[12];
+static int keymenu_is_dirty = 1;
+
+void
+mark_keymenu_dirty(void)
+{
+ keymenu_is_dirty = 1;
+}
+
+
+/*
+ * Write an already formatted key_menu to the screen
+ *
+ * Args: km -- key_menu structure
+ * bm -- bitmap, 0's mean don't draw this key
+ * row -- the row on the screen to begin on, negative values
+ * are counted from the bottom of the screen up
+ * column -- column on the screen to begin on
+ *
+ * The bits in the bitmap are used from least significant to most significant,
+ * not left to right. So, if you write out the bitmap in the normal way, for
+ * example,
+ * bm[0] = 0x5, bm[1] = 0x8, bm[2] = 0x21, bm[3] = bm[4] = bm[5] = 0
+ * 0000 0101 0000 1000 0010 0001 ...
+ * means that menu item 0 (first row, first column) is set, item 1 (2nd row,
+ * first column) is not set, item 2 is set, items 3-10 are not set, item 11
+ * (2nd row, 6th and last column) is set. In the second menu (the second set
+ * of 12 bits) items 0-3 are unset, 4 is set, 5-8 unset, 9 set, 10-11 unset.
+ * That uses up bm[0] - bm[2].
+ * Just to make sure, here it is drawn out for the first set of 12 items in
+ * the first keymenu (0-11)
+ * bm[0] x x x x x x x x bm[1] x x x x x x x x
+ * 7 6 5 4 3 2 1 0 1110 9 8
+ */
+void
+output_keymenu(struct key_menu *km, unsigned char *bm, int row, int column)
+{
+#ifdef __CYGWIN__
+ extern char term_name[];
+#endif
+ register struct key *k;
+ struct key *last_time;
+ int i, j,
+ ufk, /* using function keys */
+ real_row,
+ max_column, /* number of columns on screen */
+ off; /* offset into keymap */
+ struct variable *vars = ps_global->vars;
+ COLOR_PAIR *lastc=NULL, *label_color=NULL, *name_color=NULL;
+#ifdef MOUSE
+ /* 6's are for UTF-8 */
+ char keystr[6*MAX_KEYNAME + 6*MAX_LABEL + 2];
+#endif
+
+ off = km->which * 12;
+ max_column = ps_global->ttyo->screen_cols;
+
+ if((ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)) < 0
+ || max_column <= 0){
+ keymenu_is_dirty = 1;
+ return;
+ }
+
+
+ real_row = row > 0 ? row : ps_global->ttyo->screen_rows + row;
+
+ if(pico_usingcolor()){
+ lastc = pico_get_cur_color();
+ if(lastc && VAR_KEYLABEL_FORE_COLOR && VAR_KEYLABEL_BACK_COLOR &&
+ pico_is_good_color(VAR_KEYLABEL_FORE_COLOR) &&
+ pico_is_good_color(VAR_KEYLABEL_BACK_COLOR)){
+ label_color = new_color_pair(VAR_KEYLABEL_FORE_COLOR,
+ VAR_KEYLABEL_BACK_COLOR);
+ if(label_color)
+ (void)pico_set_colorp(label_color, PSC_NONE);
+ }
+
+ if(label_color && VAR_KEYNAME_FORE_COLOR && VAR_KEYNAME_BACK_COLOR &&
+ pico_is_good_color(VAR_KEYNAME_FORE_COLOR) &&
+ pico_is_good_color(VAR_KEYNAME_BACK_COLOR)){
+ name_color = new_color_pair(VAR_KEYNAME_FORE_COLOR,
+ VAR_KEYNAME_BACK_COLOR);
+ }
+ }
+
+ if(keymenu_is_dirty){
+ ClearLines(real_row, real_row+1);
+ keymenu_is_dirty = 0;
+ /* first time through, set up storage */
+ if(!last_time_buf[0].name){
+ for(i = 0; i < 12; i++){
+ last_time = &last_time_buf[i];
+ last_time->name = (char *) fs_get(6*MAX_KEYNAME + 1);
+ last_time->label = (char *) fs_get(6*MAX_LABEL + 1);
+ }
+ }
+
+ for(i = 0; i < 12; i++)
+ last_time_buf[i].column = -1;
+ }
+
+ for(i = 0; i < 12; i++){
+ int e;
+
+ e = off + i;
+ dprint((9, "%2d %-7.7s %-10.10s %d\n", i,
+ km == NULL ? "(no km)"
+ : km->keys[e].name == NULL ? "(null)"
+ : km->keys[e].name,
+ km == NULL ? "(no km)"
+ : km->keys[e].label == NULL ? "(null)"
+ : km->keys[e].label, km ? km->keys[e].column : 0));
+#ifdef MOUSE
+ register_key(i, NO_OP_COMMAND, "", NULL, 0, 0, 0, NULL, NULL);
+#endif
+ }
+
+ ufk = F_ON(F_USE_FK, ps_global);
+ dprint((9, "row: %d, real_row: %d, column: %d\n", row,
+ real_row, column));
+
+ for(i = 0; i < 2; i++){
+ int c, el, empty, fkey, last_in_row, fix_start;
+ short next_col;
+ char temp[6*MAX_SCREEN_COLS+1];
+ char temp2[6*MAX_SCREEN_COLS+1];
+ char this_label[6*MAX_LABEL+1];
+
+ j = 6*i - 1;
+#ifndef __CYGWIN__
+ if(i == 1 && !label_color)
+#else
+ if(i == 1 && (!label_color || !struncmp(term_name,"cygwin", 6)))
+#endif
+ max_column--; /* Some terminals scroll if you write in the
+ lower right hand corner. If user has a
+ label_color set we'll take our chances.
+ Otherwise, we'd get one cell of Normal. */
+
+ /*
+ * k is the key struct we're working on
+ * c is the column number
+ * el is an index into the whole keys array
+ * Last_time_buf is ordered strangely. It goes row by row instead
+ * of down each column like km does. J is an index into it.
+ */
+ for(c = 0, el = off+i, k = &km->keys[el];
+ k < &km->keys[off+12] && c < max_column;
+ k += 2, el += 2){
+
+ if(k->column > max_column)
+ break;
+
+ j++;
+ if(ufk)
+ fkey = 1 + k - &km->keys[off];
+
+ empty = (!bitnset(el,bm) || !(k->name && *k->name));
+ last_time = &last_time_buf[j];
+ if(k+2 < &km->keys[off+12]){
+ last_in_row = 0;
+ next_col = last_time_buf[j+1].column;
+ fix_start = (k == &km->keys[off] ||
+ k == &km->keys[off+1]) ? k->column : 0;
+ }
+ else{
+ last_in_row = 1;
+ fix_start = 0;
+ }
+
+ /*
+ * Make sure there is a space between this label and
+ * the next name. That is, we prefer a space to the
+ * extra character of the label because the space
+ * separates the commands and looks nicer.
+ */
+ if(k->label){
+ size_t l;
+ char tmp_label[6*MAX_LABEL+1];
+
+ if(k->label[0] == '[' && k->label[(l=strlen(k->label))-1] == ']' && l > 2){
+ /*
+ * Can't write in k->label, which might be a constant array.
+ */
+ strncpy(tmp_label, &k->label[1], MIN(sizeof(tmp_label),l-2));
+ tmp_label[MIN(sizeof(tmp_label)-1,l-2)] = '\0';
+
+ snprintf(this_label, sizeof(this_label), "[%s]", _(tmp_label));
+ }
+ else
+ strncpy(this_label, _(k->label), sizeof(this_label));
+
+ this_label[sizeof(this_label)-1] = '\0';
+ if(!last_in_row){
+ int trunc;
+
+ trunc = (k+2)->column - k->column
+ - ((k->name ? utf8_width(k->name) : 0) + 1);
+ /*
+ * trunc columns available for label but we don't want the label
+ * to go all the way to the edge
+ */
+ if(utf8_width(this_label) >= trunc){
+ if(trunc > 1){
+ strncpy(tmp_label, this_label, sizeof(tmp_label));
+ tmp_label[sizeof(tmp_label)-1] = '\0';
+ l = utf8_pad_to_width(this_label, tmp_label, sizeof(this_label)-2, trunc-1, 1);
+ this_label[l++] = SPACE;
+ this_label[l] = '\0';;
+ }
+ else if(trunc == 1)
+ this_label[0] = SPACE;
+ else
+ this_label[0] = '\0';
+
+ this_label[sizeof(this_label)-1] = '\0';
+ }
+ }
+ }
+ else
+ this_label[0] = '\0';
+
+ if(!(k->column == last_time->column
+ && (last_in_row || (k+2)->column <= next_col)
+ && ((empty && !*last_time->label && !*last_time->name)
+ || (!empty
+ && this_label && !strcmp(this_label,last_time->label)
+ && ((k->name && !strcmp(k->name,last_time->name))
+ || ufk))))){
+ if(empty){
+ /* blank out key with spaces */
+ strncpy(temp, repeat_char(
+ ((last_in_row || (k+2)->column > max_column)
+ ? max_column
+ : (k+2)->column) -
+ (fix_start
+ ? 0
+ : k->column),
+ SPACE), sizeof(temp));
+ temp[sizeof(temp)-1] = '\0';
+ last_time->column = k->column;
+ *last_time->name = '\0';
+ *last_time->label = '\0';
+ MoveCursor(real_row + i, column + (fix_start ? 0 : k->column));
+ Write_to_screen(temp);
+ c = (fix_start ? 0 : k->column) + strlen(temp);
+ }
+ else{
+ /* make sure extra space before key name is there */
+ if(fix_start){
+ strncpy(temp, repeat_char(k->column, SPACE), sizeof(temp));
+ temp[sizeof(temp)-1] = '\0';
+ MoveCursor(real_row + i, column + 0);
+ Write_to_screen(temp);
+ }
+
+ /* short name of the key */
+ if(ufk)
+ snprintf(temp, sizeof(temp), "F%d", fkey);
+ else
+ strncpy(temp, k->name, sizeof(temp));
+
+ temp[sizeof(temp)-1] = '\0';
+ last_time->column = k->column;
+ strncpy(last_time->name, temp, 6*MAX_KEYNAME);
+ last_time->name[6*MAX_KEYNAME] = '\0';
+ /* make sure name not too long */
+#ifdef MOUSE
+ strncpy(keystr, temp, sizeof(keystr));
+ keystr[sizeof(keystr)-1] = '\0';
+#endif
+ MoveCursor(real_row + i, column + k->column);
+ if(!empty){
+ if(name_color)
+ (void)pico_set_colorp(name_color, PSC_NONE);
+ else
+ StartInverse();
+ }
+
+ Write_to_screen(temp);
+ c = k->column + utf8_width(temp);
+ if(!empty){
+ if(!name_color)
+ EndInverse();
+ }
+
+ /* now the space after the name and the label */
+ temp[0] = '\0';
+ if(c < max_column){
+ temp[0] = SPACE;
+ temp[1] = '\0';
+ strncat(temp, this_label, sizeof(temp)-strlen(temp)-1);
+
+ /* Don't run over the right hand edge */
+ if(utf8_width(temp) > max_column - c){
+ size_t l;
+
+ l = utf8_pad_to_width(temp2, temp, sizeof(temp2)-1, max_column-c, 1);
+ temp2[l] = '\0';
+ strncpy(temp, temp2, sizeof(temp));
+ temp[sizeof(temp)-1] = '\0';
+ }
+
+ c += utf8_width(temp);
+ }
+
+#ifdef MOUSE
+ strncat(keystr, temp, sizeof(keystr)-strlen(keystr)-1);
+ keystr[sizeof(keystr)-1] = '\0';
+#endif
+ /* fill out rest of this key with spaces */
+ if(c < max_column){
+ if(last_in_row){
+ strncat(temp, repeat_char(max_column - c, SPACE), sizeof(temp)-strlen(temp)-1);
+ c = max_column;
+ }
+ else{
+ if(c < (k+2)->column){
+ strncat(temp,
+ repeat_char((k+2)->column - c, SPACE), sizeof(temp)-strlen(temp)-1);
+ c = (k+2)->column;
+ }
+ }
+
+ temp[sizeof(temp)-1] = '\0';
+ }
+
+ strncpy(last_time->label, this_label, 6*MAX_LABEL);
+ last_time->label[6*MAX_LABEL] = '\0';
+ if(label_color)
+ (void)pico_set_colorp(label_color, PSC_NONE);
+
+ Write_to_screen(temp);
+ }
+ }
+#ifdef MOUSE
+ else if(!empty)
+ /* fill in what register_key needs from cached data */
+ snprintf(keystr, sizeof(keystr), "%s %s", last_time->name, last_time->label);
+
+ if(!empty){
+ int len;
+
+ /*
+ * If label ends in space,
+ * don't register the space part of label.
+ */
+ len = strlen(keystr);
+ while(keystr[len-1] == SPACE)
+ len--;
+ len--;
+
+ register_key(j, ufk ? PF1 + fkey - 1
+ : (k->name[0] == '^')
+ ? ctrl(k->name[1])
+ : (!strucmp(k->name, "ret"))
+ ? ctrl('M')
+ : (!strucmp(k->name, "tab"))
+ ? '\t'
+ : (!strucmp(k->name, "spc"))
+ ? SPACE
+ : (!strucmp(k->name, HISTORY_UP_KEYNAME))
+ ? KEY_UP
+ : (!strucmp(k->name, HISTORY_DOWN_KEYNAME))
+ ? KEY_DOWN
+ : (k->bind.nch)
+ ? ((isascii((int) k->bind.ch[0]) && islower((int) k->bind.ch[0]))
+ ? toupper((unsigned char) k->bind.ch[0])
+ : k->bind.ch[0])
+ : k->name[0],
+ keystr, print_inverted_label,
+ real_row+i, k->column, len,
+ name_color, label_color);
+ }
+#endif
+
+ }
+
+ while(++j < 6*(i+1))
+ last_time_buf[j].column = -1;
+ }
+
+ fflush(stdout);
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ if(label_color)
+ free_color_pair(&label_color);
+ if(name_color)
+ free_color_pair(&name_color);
+ }
+}
+
+
+/*
+ * Clear the key menu lines.
+ */
+void
+blank_keymenu(int row, int column)
+{
+ struct variable *vars = ps_global->vars;
+ COLOR_PAIR *lastc;
+
+ if(FOOTER_ROWS(ps_global) > 1){
+ km_state.blanked = 1;
+ km_state.row = row;
+ km_state.column = column;
+ MoveCursor(row, column);
+ lastc = pico_set_colors(VAR_KEYLABEL_FORE_COLOR,
+ VAR_KEYLABEL_BACK_COLOR, PSC_NORM|PSC_RET);
+
+ CleartoEOLN();
+ MoveCursor(row+1, column);
+ CleartoEOLN();
+ fflush(stdout);
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+ }
+}
+
+
+void
+draw_cancel_keymenu(void)
+{
+ bitmap_t bitmap;
+
+ setbitmap(bitmap);
+ draw_keymenu(&cancel_keymenu, bitmap, ps_global->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps_global), 0, FirstMenu);
+}
+
+
+void
+clearfooter(struct pine *ps)
+{
+ ClearLines(ps->ttyo->screen_rows - 3, ps->ttyo->screen_rows - 1);
+ mark_keymenu_dirty();
+ mark_status_unknown();
+}
+
+
+/*
+ * Calculate formatting for key menu at bottom of screen
+ *
+ * Args: km -- The key_menu structure to format
+ * bm -- Bitmap indicating which menu items should be displayed. If
+ * an item is NULL, that also means it shouldn't be displayed.
+ * Sometimes the bitmap will be turned on in that case and just
+ * rely on the NULL entry.
+ * width -- the screen width to format it at
+ *
+ * If already formatted for this particular screen width and the requested
+ * bitmap and formatted bitmap agree, return.
+ *
+ * The formatting results in the column field in the key_menu being
+ * filled in. The column field is the column to start the label at, the
+ * name of the key; after that is the label for the key. The basic idea
+ * is to line up the end of the names and beginning of the labels. If
+ * the name is too long and shifting it left would run into previous
+ * label, then shift the whole menu right, or at least that entry if
+ * things following are short enough to fit back into the regular
+ * spacing. This has to be calculated and not fixed so it can cope with
+ * screen resize.
+ */
+void
+format_keymenu(struct key_menu *km, unsigned char *bm, int width)
+{
+ int spacing[7], w[6], min_w[6], tw[6], extra[6], ufk, i, set;
+
+ /* already formatted? */
+ if(!km || (width == km->width &&
+ km->how_many <= km->formatted_hm &&
+ !memcmp(km->bitmap, bm, BM_SIZE)))
+ return;
+
+ /*
+ * If we're in the initial command sequence we may be using function
+ * keys instead of alphas, or vice versa, so we want to recalculate
+ * the formatting next time through.
+ */
+ if((F_ON(F_USE_FK,ps_global) && ps_global->orig_use_fkeys) ||
+ (F_OFF(F_USE_FK,ps_global) && !ps_global->orig_use_fkeys)){
+ km->width = width;
+ km->formatted_hm = km->how_many;
+ memcpy(km->bitmap, bm, BM_SIZE);
+ }
+
+ ufk = F_ON(F_USE_FK,ps_global); /* ufk = "Using Function Keys" */
+
+ /* set up "ideal" columns to start in, plus fake 7th column start */
+ for(i = 0; i < 7; i++)
+ spacing[i] = (i * width) / 6;
+
+ /* Loop thru each set of 12 menus */
+ for(set = 0; set < km->how_many; set++){
+ int k_top, k_bot, top_name_width, bot_name_width,
+ top_label_width, bot_label_width, done, offset, next_one;
+ struct key *keytop, *keybot;
+
+ offset = set * 12; /* offset into keymenu */
+
+ /*
+ * Find the required widths for each box.
+ */
+ for(i = 0; i < 6; i++){
+ k_top = offset + i*2;
+ k_bot = k_top + 1;
+ keytop = &km->keys[k_top];
+ keybot = &km->keys[k_bot];
+
+ /*
+ * The width of a box is the max width of top or bottom name,
+ * plus 1 space, plus the max width of top or bottom label.
+ *
+ * ? HelpInfo
+ * ^C Cancel
+ * ||||||||||| = 2 + 1 + 8 = 11
+ *
+ * Then we adjust that by adding one space after the box to
+ * separate it from the next box. The last box doesn't need that
+ * but we may need an extra space for last box to avoid putting
+ * a character in the lower right hand cell of display.
+ * We also have a minimum label width (if screen is really narrow)
+ * of 3, so at least "Hel" and "Can" shows and the rest gets
+ * truncated off right hand side.
+ */
+
+ top_name_width = (keytop->name && bitnset(k_top,bm))
+ ? (ufk ? (i >= 5 ? 3 : 2)
+ : utf8_width(keytop->name)) : 0;
+ bot_name_width = (keybot->name && bitnset(k_bot,bm))
+ ? (ufk ? (i >= 4 ? 3 : 2)
+ : utf8_width(keybot->name)) : 0;
+ /*
+ * Labels are complicated by the fact that we want to look
+ * up their translation, but also by the fact that we surround
+ * the word with brackets like [ViewMsg] when the command is
+ * the default. We want to look up the translation of the
+ * part inside the brackets, not the whole thing.
+ */
+ if(keytop->label && bitnset(k_top,bm)){
+ char tmp_label[6*MAX_LABEL+1];
+ size_t l;
+
+ if(keytop->label[0] == '[' && keytop->label[(l=strlen(keytop->label))-1] == ']' && l > 2){
+ /*
+ * Can't write in k->label, which might be a constant array.
+ */
+ strncpy(tmp_label, &keytop->label[1], MIN(sizeof(tmp_label),l-2));
+ tmp_label[MIN(sizeof(tmp_label)-1,l-2)] = '\0';
+
+ top_label_width = 2 + utf8_width(_(tmp_label));
+ }
+ else
+ top_label_width = utf8_width(_(keytop->label));
+ }
+ else
+ top_label_width = 0;
+
+ if(keybot->label && bitnset(k_bot,bm)){
+ char tmp_label[6*MAX_LABEL+1];
+ size_t l;
+
+ if(keybot->label[0] == '[' && keybot->label[(l=strlen(keybot->label))-1] == ']' && l > 2){
+ strncpy(tmp_label, &keybot->label[1], MIN(sizeof(tmp_label),l-2));
+ tmp_label[MIN(sizeof(tmp_label)-1,l-2)] = '\0';
+
+ bot_label_width = 2 + utf8_width(_(tmp_label));
+ }
+ else
+ bot_label_width = utf8_width(_(keybot->label));
+ }
+ else
+ bot_label_width = 0;
+
+ /*
+ * The 1 for i < 5 is the space between adjacent boxes.
+ * The last 1 or 0 when i == 5 is so that we won't try to put
+ * a character in the lower right cell of the display, since that
+ * causes a linefeed on some terminals.
+ */
+ w[i] = MAX(top_name_width, bot_name_width) + 1 +
+ MAX(top_label_width, bot_label_width) +
+ ((i < 5) ? 1
+ : ((bot_label_width >= top_label_width) ? 1 : 0));
+
+ /*
+ * The smallest we'll squeeze a column.
+ *
+ * X ABCDEF we'll squeeze to X ABC
+ * YZ GHIJ YZ GHI
+ */
+ min_w[i] = MAX(top_name_width, bot_name_width) + 1 +
+ MIN(MAX(top_label_width, bot_label_width), 3) +
+ ((i < 5) ? 1
+ : ((bot_label_width >= top_label_width) ? 1 : 0));
+
+ /* init trial width */
+ tw[i] = spacing[i+1] - spacing[i];
+ extra[i] = tw[i] - w[i]; /* negative if it doesn't fit */
+ }
+
+ /*
+ * See if we can fit everything on the screen.
+ */
+ done = 0;
+ while(!done){
+ int smallest_extra, how_small;
+
+ /* Find smallest extra */
+ smallest_extra = -1;
+ how_small = 100;
+ for(i = 0; i < 6; i++){
+ if(extra[i] < how_small){
+ smallest_extra = i;
+ how_small = extra[i];
+ }
+ }
+
+ if(how_small >= 0) /* everything fits */
+ done++;
+ else{
+ int take_from, how_close;
+
+ /*
+ * Find the one that is closest to the ideal width
+ * that has some extra to spare.
+ */
+ take_from = -1;
+ how_close = 100;
+ for(i = 0; i < 6; i++){
+ if(extra[i] > 0 &&
+ ((spacing[i+1]-spacing[i]) - tw[i]) < how_close){
+ take_from = i;
+ how_close = (spacing[i+1]-spacing[i]) - tw[i];
+ }
+ }
+
+ if(take_from >= 0){
+ /*
+ * Found one. Take one from take_from and add it
+ * to the smallest_extra.
+ */
+ tw[smallest_extra]++;
+ extra[smallest_extra]++;
+ tw[take_from]--;
+ extra[take_from]--;
+ }
+ else{
+ int used_width;
+
+ /*
+ * Oops. Not enough space to fit everything in.
+ * Some of the labels are truncated. Some may even be
+ * truncated past the minimum. We make sure that each
+ * field is at least its minimum size, and then we cut
+ * back those over the minimum until we can fit all the
+ * minimal names on the screen (if possible).
+ */
+ for(i = 0; i < 6; i++)
+ tw[i] = MAX(tw[i], min_w[i]);
+
+ used_width = 0;
+ for(i = 0; i < 6; i++)
+ used_width += tw[i];
+
+ while(used_width > width && !done){
+ int candidate, excess;
+
+ /*
+ * Find the one with the most width over it's
+ * minimum width.
+ */
+ candidate = -1;
+ excess = -100;
+ for(i = 0; i < 6; i++){
+ if(tw[i] - min_w[i] > excess){
+ candidate = i;
+ excess = tw[i] - min_w[i];
+ }
+ }
+
+ if(excess > 0){
+ tw[candidate]--;
+ used_width--;
+ }
+ else
+ done++;
+ }
+
+ done++;
+ }
+ }
+ }
+
+ /*
+ * Assign the format we came up with to the keymenu.
+ */
+ next_one = 0;
+ for(i = 0; i < 6; i++){
+ k_top = offset + i*2;
+ k_bot = k_top + 1;
+ keytop = &km->keys[k_top];
+ keybot = &km->keys[k_bot];
+ top_name_width = (keytop->name && bitnset(k_top,bm))
+ ? (ufk ? (i >= 5 ? 3 : 2)
+ : utf8_width(keytop->name)) : 0;
+ bot_name_width = (keybot->name && bitnset(k_bot,bm))
+ ? (ufk ? (i >= 4 ? 3 : 2)
+ : utf8_width(keybot->name)) : 0;
+
+ if(top_name_width >= bot_name_width){
+ keytop->column = next_one;
+ keybot->column = next_one + (top_name_width - bot_name_width);
+ }
+ else{
+ keytop->column = next_one + (bot_name_width - top_name_width);
+ keybot->column = next_one;
+ }
+
+ next_one += tw[i];
+ }
+ }
+}
+
+
+/*
+ * Draw the key menu at bottom of screen
+ *
+ * Args: km -- key_menu structure
+ * bitmap -- which fields are active
+ * width -- the screen width to format it at
+ * row -- where to put it
+ * column -- where to put it
+ * what -- this is an enum telling us whether to display the
+ * first menu (first set of 12 keys), or to display the same
+ * one we displayed last time, or to display a particular
+ * one (which), or to display the next one.
+ *
+ * Fields are inactive if *either* the corresponding bitmap entry is 0 *or*
+ * the actual entry in the key_menu is NULL. Therefore, it is sometimes
+ * useful to just turn on all the bits in a bitmap and let the NULLs take
+ * care of it. On the other hand, the bitmap gives a convenient method
+ * for turning some keys on or off dynamically or due to options.
+ * Both methods are used about equally.
+ *
+ * Also saves the state for a possible redraw later.
+ *
+ * Row should usually be a negative number. If row is 0, the menu is not
+ * drawn.
+ */
+void
+draw_keymenu(struct key_menu *km, unsigned char *bitmap, int width, int row,
+ int column, OtherMenu what)
+{
+#ifdef _WINDOWS
+ configure_menu_items (km, bitmap);
+#endif
+ format_keymenu(km, bitmap, width);
+
+ /*--- save state for a possible redraw ---*/
+ km_state.km = km;
+ km_state.row = row;
+ km_state.column = column;
+ memcpy(km_state.bitmap, bitmap, BM_SIZE);
+
+ if(row == 0)
+ return;
+
+ if(km_state.blanked)
+ keymenu_is_dirty = 1;
+
+ if(what == FirstMenu || what == SecondMenu || what == ThirdMenu ||
+ what == FourthMenu || what == MenuNotSet){
+ if(what == FirstMenu || what == MenuNotSet)
+ km->which = 0;
+ else if(what == SecondMenu)
+ km->which = 1;
+ else if(what == ThirdMenu)
+ km->which = 2;
+ else if(what == FourthMenu)
+ km->which = 3;
+
+ if(km->which >= km->how_many)
+ km->which = 0;
+ }
+ else if(what == NextMenu)
+ km->which = (km->which + 1) % km->how_many;
+ /* else what must be SameMenu */
+
+ output_keymenu(km, bitmap, row, column);
+
+ km_state.blanked = 0;
+}
+
+
+void
+redraw_keymenu(void)
+{
+ if(km_state.blanked)
+ blank_keymenu(km_state.row, km_state.column);
+ else
+ draw_keymenu(km_state.km, km_state.bitmap, ps_global->ttyo->screen_cols,
+ km_state.row, km_state.column, SameMenu);
+}
+
+
+/*
+ * end_keymenu - free resources associated with keymenu display cache
+ */
+void
+end_keymenu(void)
+{
+ int i;
+
+ for(i = 0; i < 12; i++){
+ if(last_time_buf[i].name)
+ fs_give((void **) &last_time_buf[i].name);
+
+ if(last_time_buf[i].label)
+ fs_give((void **) &last_time_buf[i].label);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Reveal Keymenu to Pine Command mappings
+
+ Args:
+
+ ----*/
+int
+menu_command(UCS keystroke, struct key_menu *menu)
+{
+ int i, n;
+
+ if(keystroke == KEY_UTF8 || keystroke == KEY_UNKNOWN)
+ return(MC_UTF8);
+
+ if(!menu)
+ return(MC_UNKNOWN);
+
+ if(F_ON(F_USE_FK,ps_global)){
+ /* No alpha commands permitted in function key mode */
+ if(keystroke < 0x80 && isalpha((unsigned char) keystroke))
+ return(MC_UNKNOWN);
+
+ /* Tres simple: compute offset, and test */
+ if(keystroke >= F1 && keystroke <= F12){
+ n = (menu->which * 12) + (keystroke - F1);
+ if(bitnset(n, menu->bitmap))
+ return(menu->keys[n].bind.cmd);
+ }
+ }
+ else if(keystroke >= F1 && keystroke <= F12)
+ return(MC_UNKNOWN);
+
+ /* if ascii, coerce lower case */
+ if(keystroke < 0x80 && isupper((unsigned char) keystroke))
+ keystroke = tolower((unsigned char) keystroke);
+
+ /* keep this here for Windows port */
+ if((keystroke = validatekeys(keystroke)) == KEY_JUNK)
+ return(MC_UNKNOWN);
+
+ /* Scan the list for any keystroke/command binding */
+ if(keystroke != NO_OP_COMMAND)
+ for(i = (menu->how_many * 12) - 1; i >= 0; i--)
+ if(bitnset(i, menu->bitmap))
+ for(n = menu->keys[i].bind.nch - 1; n >= 0; n--)
+ if(keystroke == menu->keys[i].bind.ch[n])
+ return(menu->keys[i].bind.cmd);
+
+ /*
+ * If explicit mapping failed, check feature mappings and
+ * hardwired defaults...
+ */
+ if(F_ON(F_ENABLE_PRYNT,ps_global)
+ && (keystroke == 'y' || keystroke == 'Y')){
+ /* SPECIAL CASE: Scan the list for print bindings */
+ for(i = (menu->how_many * 12) - 1; i >= 0; i--)
+ if(bitnset(i, menu->bitmap))
+ if(menu->keys[i].bind.cmd == MC_PRINTMSG
+ || menu->keys[i].bind.cmd == MC_PRINTTXT)
+ return(menu->keys[i].bind.cmd);
+ }
+
+ if(F_ON(F_ENABLE_LESSTHAN_EXIT,ps_global)
+ && (keystroke == '<' || keystroke == ','
+ || (F_ON(F_ARROW_NAV,ps_global) && keystroke == KEY_LEFT))){
+ /* SPECIAL CASE: Scan the list for MC_EXIT bindings */
+ for(i = (menu->how_many * 12) - 1; i >= 0; i--)
+ if(bitnset(i, menu->bitmap))
+ if(menu->keys[i].bind.cmd == MC_EXIT)
+ return(MC_EXIT);
+ }
+
+ /*
+ * If no match after scanning bindings, try universally
+ * bound keystrokes...
+ */
+ switch(keystroke){
+ case KEY_MOUSE :
+ return(MC_MOUSE);
+
+ case ctrl('P') :
+ case KEY_UP :
+ return(MC_CHARUP);
+
+ case ctrl('N') :
+ case KEY_DOWN :
+ return(MC_CHARDOWN);
+
+ case ctrl('F') :
+ case KEY_RIGHT :
+ return(MC_CHARRIGHT);
+
+ case ctrl('B') :
+ case KEY_LEFT :
+ return(MC_CHARLEFT);
+
+ case ctrl('A') :
+ return(MC_GOTOBOL);
+
+ case ctrl('E') :
+ return(MC_GOTOEOL);
+
+ case ctrl('L') :
+ return(MC_REPAINT);
+
+ case KEY_RESIZE :
+ return(MC_RESIZE);
+
+ case NO_OP_IDLE:
+ case NO_OP_COMMAND:
+ if(USER_INPUT_TIMEOUT(ps_global))
+ user_input_timeout_exit(ps_global->hours_to_timeout); /* no return */
+
+ return(MC_NONE);
+
+ default :
+ break;
+ }
+
+ return(MC_UNKNOWN); /* utter failure */
+}
+
+
+
+/*----------------------------------------------------------------------
+ Set up a binding for cmd, with one key bound to it.
+ Use menu_add_binding to add more keys to this binding.
+
+ Args: menu -- the keymenu
+ key -- the initial key to bind to
+ cmd -- the command to initialize to
+ name -- a pointer to the string to point name to
+ label -- a pointer to the string to point label to
+ keynum -- which key in the keys array to initialize
+
+ For translation purposes, the label in the calling routine
+ should be wrapped in an N_() macro.
+
+ ----*/
+void
+menu_init_binding(struct key_menu *menu, UCS key, int cmd, char *name, char *label, int keynum)
+{
+ /* if ascii, coerce to lower case */
+ if(key < 0x80 && isupper((unsigned char)key))
+ key = tolower((unsigned char)key);
+
+ /* remove binding from any other key */
+ menu_clear_cmd_binding(menu, cmd);
+
+ menu->keys[keynum].name = name;
+ menu->keys[keynum].label = label;
+ menu->keys[keynum].bind.cmd = cmd;
+ menu->keys[keynum].bind.nch = 0;
+ menu->keys[keynum].bind.ch[menu->keys[keynum].bind.nch++] = key;
+}
+
+
+/*----------------------------------------------------------------------
+ Add a key/command binding to the given keymenu structure
+
+ Args:
+
+ ----*/
+void
+menu_add_binding(struct key_menu *menu, UCS key, int cmd)
+{
+ int i, n;
+
+ /* NOTE: cmd *MUST* already have had a binding */
+ for(i = (menu->how_many * 12) - 1; i >= 0; i--)
+ if(menu->keys[i].bind.cmd == cmd){
+ for(n = menu->keys[i].bind.nch - 1;
+ n >= 0 && key != menu->keys[i].bind.ch[n];
+ n--)
+ ;
+
+ /* if ascii, coerce to lower case */
+ if(key < 0x80 && isupper((unsigned char)key))
+ key = tolower((unsigned char)key);
+
+ if(n < 0) /* not already bound, bind it */
+ menu->keys[i].bind.ch[menu->keys[i].bind.nch++] = key;
+
+ break;
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ REMOVE a key/command binding from the given keymenu structure
+
+ Args:
+
+ ----*/
+int
+menu_clear_binding(struct key_menu *menu, UCS key)
+{
+ int i, n;
+
+ /* if ascii, coerce to lower case */
+ if(key < 0x80 && isupper((unsigned char)key))
+ key = tolower((unsigned char)key);
+
+ for(i = (menu->how_many * 12) - 1; i >= 0; i--)
+ for(n = menu->keys[i].bind.nch - 1; n >= 0; n--)
+ if(key == menu->keys[i].bind.ch[n]){
+ int cmd = menu->keys[i].bind.cmd;
+
+ for(--menu->keys[i].bind.nch; n < menu->keys[i].bind.nch; n++)
+ menu->keys[i].bind.ch[n] = menu->keys[i].bind.ch[n+1];
+
+ return(cmd);
+ }
+
+ return(MC_UNKNOWN);
+}
+
+
+void
+menu_clear_cmd_binding(struct key_menu *menu, int cmd)
+{
+ int i;
+
+ for(i = (menu->how_many * 12) - 1; i >= 0; i--){
+ if(cmd == menu->keys[i].bind.cmd){
+ menu->keys[i].name = NULL;
+ menu->keys[i].label = NULL;
+ menu->keys[i].bind.cmd = 0;
+ menu->keys[i].bind.nch = 0;
+ menu->keys[i].bind.ch[0] = 0;
+ }
+ }
+}
+
+
+int
+menu_binding_index(struct key_menu *menu, int cmd)
+{
+ int i;
+
+ for(i = 0; i < menu->how_many * 12; i++)
+ if(cmd == menu->keys[i].bind.cmd)
+ return(i);
+
+ return(-1);
+}
+
+
+#ifdef MOUSE
+/*
+ * print_inverted_label - highlight the label of the given menu item.
+ * (callback from pico mouse routines)
+ *
+ * So far, this is only
+ * ever called with the top left row equal to the bottom right row.
+ * If you change that you probably need to fix it.
+ */
+void
+print_inverted_label(int state, MENUITEM *m)
+{
+ unsigned i, j, k;
+ int col_offsetwid, col_offsetchars, do_color = 0, skipwid = 0, skipchars = 0, len, c;
+ char prename[100];
+ char namepart[100];
+ char labelpart[100];
+ char *lp, *label;
+ COLOR_PAIR *name_color = NULL, *label_color = NULL, *lastc = NULL;
+ struct variable *vars = ps_global->vars;
+
+ if(m->label && (lp=strchr(m->label, ' '))){
+ char save;
+
+ save = *lp;
+ *lp = '\0';
+ col_offsetwid = utf8_width(m->label);
+ col_offsetchars = lp - m->label;
+ *lp = save;
+ }
+ else
+ col_offsetwid = col_offsetchars = 0;
+
+ if(pico_usingcolor() && ((VAR_KEYLABEL_FORE_COLOR &&
+ VAR_KEYLABEL_BACK_COLOR) ||
+ (VAR_KEYNAME_FORE_COLOR &&
+ VAR_KEYNAME_BACK_COLOR))){
+ lastc = pico_get_cur_color();
+
+ if(VAR_KEYNAME_FORE_COLOR && VAR_KEYNAME_BACK_COLOR){
+ name_color = state ? new_color_pair(VAR_KEYNAME_BACK_COLOR,
+ VAR_KEYNAME_FORE_COLOR)
+ : new_color_pair(VAR_KEYNAME_FORE_COLOR,
+ VAR_KEYNAME_BACK_COLOR);
+ }
+ else if(VAR_REV_FORE_COLOR && VAR_REV_BACK_COLOR)
+ name_color = new_color_pair(VAR_REV_FORE_COLOR, VAR_REV_BACK_COLOR);
+
+ if(VAR_KEYLABEL_FORE_COLOR && VAR_KEYLABEL_BACK_COLOR){
+ label_color = state ? new_color_pair(VAR_KEYLABEL_BACK_COLOR,
+ VAR_KEYLABEL_FORE_COLOR)
+ : new_color_pair(VAR_KEYLABEL_FORE_COLOR,
+ VAR_KEYLABEL_BACK_COLOR);
+ }
+ else if(VAR_REV_FORE_COLOR && VAR_REV_BACK_COLOR){
+ label_color = state ? new_color_pair(VAR_REV_FORE_COLOR,
+ VAR_REV_BACK_COLOR)
+ : new_color_pair(VAR_NORM_FORE_COLOR,
+ VAR_NORM_BACK_COLOR);
+ }
+
+ /*
+ * See if we can grok all these colors. If not, we're going to
+ * punt and pretend there are no colors at all.
+ */
+ if(!pico_is_good_colorpair(name_color) ||
+ !pico_is_good_colorpair(label_color)){
+ if(name_color)
+ free_color_pair(&name_color);
+ if(label_color)
+ free_color_pair(&label_color);
+ if(lastc)
+ free_color_pair(&lastc);
+ }
+ else{
+ do_color++;
+ (void)pico_set_colorp(label_color, PSC_NONE);
+ if(!(VAR_KEYLABEL_FORE_COLOR && VAR_KEYLABEL_BACK_COLOR)){
+ if(state)
+ StartInverse();
+ else
+ EndInverse();
+ }
+ }
+ }
+
+ if(!do_color){
+ /*
+ * Command name's already inverted, leave it.
+ */
+ skipwid = state ? 0 : col_offsetwid;
+ skipchars = state ? 0 : col_offsetchars;
+ if(state)
+ StartInverse();
+ else
+ EndInverse();
+ }
+
+ MoveCursor((int)(m->tl.r), (int)(m->tl.c) + skipwid);
+
+ label = m->label ? m->label : "";
+ len = strlen(label);
+
+ /*
+ * this is a bit complicated by the fact that we have to keep track of
+ * the screenwidth as we print the label, because the screenwidth might
+ * not be the same as the number of characters.
+* UNTESTED SINCE switching to UTF-8 *
+ */
+ for(i = m->tl.r; i <= m->br.r; i++){
+ /* collect part before name */
+ for(k=0, j = m->tl.c + skipchars; j < MIN(m->lbl.c,m->br.c); j++){
+ if(k < sizeof(prename))
+ prename[k++] = ' ';
+ }
+
+ if(k < sizeof(prename))
+ prename[k] = '\0';
+
+ /* collect name part */
+ for(k=0; j < MIN(m->lbl.c+col_offsetchars,m->br.c); j++){
+ c = (i == m->lbl.r &&
+ j - m->lbl.c < len) ? label[j - m->lbl.c] : ' ';
+ if(k < sizeof(namepart))
+ namepart[k++] = c;
+ }
+
+ if(k < sizeof(namepart))
+ namepart[k] = '\0';
+
+ /* collect label part */
+ for(k=0; j <= m->br.c; j++){
+ c = (i == m->lbl.r &&
+ j - m->lbl.c < len) ? label[j - m->lbl.c] : ' ';
+ if(k < sizeof(labelpart))
+ labelpart[k++] = c;
+ }
+
+ if(k < sizeof(labelpart))
+ labelpart[k] = '\0';
+ }
+
+ if(prename)
+ Write_to_screen(prename);
+
+ if(namepart){
+ if(name_color && col_offsetchars)
+ (void) pico_set_colorp(name_color, PSC_NONE);
+
+ Write_to_screen(namepart);
+ }
+
+ if(labelpart){
+ if(name_color && col_offsetchars){
+ if(label_color)
+ (void) pico_set_colorp(label_color, PSC_NONE);
+ else{
+ if(state)
+ StartInverse();
+ else
+ EndInverse();
+ }
+ }
+
+ Write_to_screen(labelpart);
+ }
+
+ if(do_color){
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+ else if(state)
+ EndInverse();
+ else
+ pico_set_normal_color();
+
+ if(name_color)
+ free_color_pair(&name_color);
+ if(label_color)
+ free_color_pair(&label_color);
+ }
+ else{
+ if(state)
+ EndInverse();
+ }
+}
+#endif /* MOUSE */
+
+
+#ifdef _WINDOWS
+/*
+ * This function scans the key menu and calls mswin.c functions
+ * to build a corresponding windows menu.
+ */
+void
+configure_menu_items (struct key_menu *km, bitmap_t bitmap)
+{
+ int i;
+ struct key *k;
+ UCS key;
+
+ mswin_menuitemclear ();
+
+ if(!km)
+ return;
+
+ for (i = 0, k = km->keys ; i < km->how_many * 12; i++, k++) {
+ if (k->name != NULL && k->label != NULL && bitnset (i, bitmap) &&
+ k->menuitem != KS_NONE) {
+
+ if (k->name[0] == '^')
+ key = ctrl(k->name[1]);
+ else if (strcmp(k->name, "Ret") == 0)
+ key = '\r';
+ else if (strcmp(k->name, "Spc") == 0)
+ key = ' ';
+ else
+ key = k->name[0];
+
+ mswin_menuitemadd (key, k->label, k->menuitem, 0);
+ }
+ }
+}
+#endif
diff --git a/alpine/keymenu.h b/alpine/keymenu.h
new file mode 100644
index 00000000..0802c20d
--- /dev/null
+++ b/alpine/keymenu.h
@@ -0,0 +1,670 @@
+/*
+ * $Id: keymenu.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_KEYMENU_INCLUDED
+#define PINE_KEYMENU_INCLUDED
+
+
+#include <general.h>
+#include "../pith/state.h"
+#include "../pith/bitmap.h"
+
+
+/*------
+ A key menu has two ways to turn on and off individual items in the menu.
+ If there is a null entry in the key_menu structure for that key, then
+ it is off. Also, if the passed bitmap has a zero in the position for
+ that key, then it is off. This means you can usually set all of the
+ bitmaps and only turn them off if you want to kill a key that is normally
+ there otherwise.
+ Each key_menu is an array of keys with a multiple of 12 number of keys.
+ ------*/
+/*------
+ Argument to draw_keymenu(). These are to identify which of the possibly
+ multiple sets of twelve keys should be shown in the keymenu. That is,
+ a keymenu may have 24 or 36 keys, so that there are 2 or 3 different
+ screens of key menus for that keymenu. FirstMenu means to use the
+ first twelve, NextTwelve uses the one after the previous one, SameTwelve
+ uses the same one.
+ ------*/
+typedef enum {MenuNotSet = 0, FirstMenu, NextMenu, SameMenu,
+ SecondMenu, ThirdMenu, FourthMenu} OtherMenu;
+
+
+struct key {
+ char *name; /* the short name */
+ char *label; /* the descriptive label */
+ struct { /* */
+ short cmd; /* the resulting command */
+ short nch; /* how many of ch are active */
+ UCS ch[11]; /* which keystrokes trigger cmd */
+ } bind; /* */
+ KS_OSDATAVAR /* slot for port-specific data */
+ short column; /* menu col after formatting */
+};
+
+
+struct key_menu {
+ unsigned int how_many:4; /* how many separate sets of 12 */
+ unsigned int which:4; /* which of the sets are we using */
+ unsigned int width:8; /* screen width when formatting done */
+ struct key *keys; /* array of how_many*12 size structs */
+ unsigned int formatted_hm:4; /* how_many when formatting done */
+ bitmap_t bitmap;
+};
+
+
+/*
+ * Definitions for the various Menu Commands...
+ */
+#define MC_NONE 0 /* NO command defined */
+#define MC_UNKNOWN 200
+#define MC_UTF8 201
+
+/* Cursor/page Motion */
+#define MC_CHARUP 100
+#define MC_CHARDOWN 101
+#define MC_CHARRIGHT 102
+#define MC_CHARLEFT 103
+#define MC_GOTOBOL 104
+#define MC_GOTOEOL 105
+#define MC_GOTOSOP 106
+#define MC_GOTOEOP 107
+#define MC_PAGEUP 108
+#define MC_PAGEDN 109
+#define MC_MOUSE 110
+#define MC_HOMEKEY 111
+#define MC_ENDKEY 112
+
+/* New Screen Commands */
+#define MC_HELP 500
+#define MC_QUIT 501
+#define MC_OTHER 502
+#define MC_MAIN 503
+#define MC_INDEX 504
+#define MC_VIEW_TEXT 505
+#define MC_VIEW_ATCH 506
+#define MC_FOLDERS 507
+#define MC_ADDRBOOK 508
+#define MC_RELNOTES 509
+#define MC_KBLOCK 510
+#define MC_JOURNAL 511
+#define MC_SETUP 512
+#define MC_COLLECTIONS 513
+#define MC_PARENT 514
+#define MC_ROLE 515
+#define MC_LISTMGR 516
+#define MC_THRDINDX 517
+#define MC_SECURITY 518
+
+/* Commands within Screens */
+#define MC_NEXTITEM 700
+#define MC_PREVITEM 701
+#define MC_DELETE 702
+#define MC_UNDELETE 703
+#define MC_COMPOSE 704
+#define MC_REPLY 705
+#define MC_FORWARD 706
+#define MC_GOTO 707
+#define MC_TAB 708
+#define MC_WHEREIS 709
+#define MC_ZOOM 710
+#define MC_PRINTMSG 711
+#define MC_PRINTTXT 712
+#define MC_TAKE 713
+#define MC_SAVE 714
+#define MC_EXPORT 715
+#define MC_IMPORT 716
+#define MC_EXPUNGE 717
+#define MC_UNEXCLUDE 718
+#define MC_CHOICE 719
+#define MC_SELECT 720
+#define MC_SELCUR 721
+#define MC_SELALL 722
+#define MC_UNSELALL 723
+#define MC_APPLY 724
+#define MC_SORT 725
+#define MC_FULLHDR 726
+#define MC_BOUNCE 727
+#define MC_FLAG 728
+#define MC_PIPE 729
+#define MC_EXIT 730
+#define MC_PRINTALL 731
+#define MC_REPAINT 732
+#define MC_JUMP 733
+#define MC_RESIZE 734
+#define MC_FWDTEXT 735
+#define MC_SAVETEXT 736
+#define MC_ABOUTATCH 737
+#define MC_LISTMODE 738
+#define MC_RENAMEFLDR 739
+#define MC_ADDFLDR 740
+#define MC_SUBSCRIBE 741
+#define MC_UNSUBSCRIBE 742
+#define MC_ADD 743
+#define MC_TOGGLE 744
+#define MC_EDIT 745
+#define MC_ADDABOOK 746
+#define MC_DELABOOK 747
+#define MC_VIEW_ENTRY 748
+#define MC_EDITABOOK 750
+#define MC_OPENABOOK 751
+#define MC_POPUP 752
+#define MC_EXPAND 753
+#define MC_UNEXPAND 754
+#define MC_COPY 755
+#define MC_SHUFFLE 756
+#define MC_VIEW_HANDLE 757
+#define MC_NEXT_HANDLE 758
+#define MC_PREV_HANDLE 759
+#define MC_QUERY_SERV 760
+#define MC_GRIPE_LOCAL 761
+#define MC_GRIPE_PIC 762
+#define MC_GRIPE_READ 763
+#define MC_GRIPE_POST 764
+#define MC_FINISH 765
+#define MC_PRINTFLDR 766
+#define MC_OPENFLDR 767
+#define MC_EDITFILE 768
+#define MC_ADDFILE 769
+#define MC_DELFILE 770
+#define MC_CHOICEB 771
+#define MC_CHOICEC 772
+#define MC_CHOICED 773
+#define MC_CHOICEE 774
+#define MC_DEFAULT 775
+#define MC_TOGGLEB 776
+#define MC_TOGGLEC 777
+#define MC_TOGGLED 778
+#define MC_RGB1 779
+#define MC_RGB2 780
+#define MC_RGB3 781
+#define MC_EXITQUERY 782
+#define MC_ADDHDR 783
+#define MC_DELHDR 784
+#define MC_PRINTER 785
+#define MC_PASSWD 786
+#define MC_CONFIG 787
+#define MC_SIG 788
+#define MC_ABOOKS 789
+#define MC_CLISTS 790
+#define MC_RULES 791
+#define MC_DIRECTORY 792
+#define MC_KOLOR 793
+#define MC_EXCEPT 794
+#define MC_REMOTE 795
+#define MC_NO_EXCEPT 796
+#define MC_YES 797
+#define MC_NO 798
+#define MC_NOT 799
+#define MC_COLLAPSE 800
+#define MC_CHK_RECENT 801
+#define MC_DECRYPT 802
+#define MC_QUOTA 803
+
+/*
+ * Some standard Key/Command Bindings
+ */
+#define NULL_MENU {NULL, NULL, {MC_NONE}, KS_NONE}
+/*
+ * TRANSLATORS: Alpine has a key menu at the bottom of each screen which
+ * lists all of the available commands. Each command is a single character
+ * which is followed with a short descriptive word which describes what
+ * the command does. Commands are almost always to be thought of as verbs
+ * so the descriptive words describe what the command does.
+ * These descriptive words have to fit across the
+ * screen so they would normally only be 8-10 characters long, though
+ * they will be truncated if there isn't enough space. The command
+ * descriptions are usually chosen so that they will be a mnemonic
+ * for the command. For example, the command S is Save,
+ * E is Export, Q is Quit, and ? is Help. You get to translate the
+ * label (the Save part) but the actual command (the S) will stay
+ * the same, so it will be very difficult to come up with mnemonic
+ * labels. The mnemonic isn't necessary, just nice. You can see that
+ * we have stretched to the edge of the usefullness of mnemonics with
+ * cases like K Kolor (instead of Color) and X eXceptions (because E
+ * already meant something else).
+ */
+#define HELP_MENU {"?", N_("Help"), \
+ {MC_HELP, 2, {'?',ctrl('G')}}, \
+ KS_SCREENHELP}
+/* TRANSLATORS: Show Other commands */
+#define OTHER_MENU {"O", N_("OTHER CMDS"), \
+ {MC_OTHER, 1, {'o'}}, \
+ KS_NONE}
+/* TRANSLATORS: WhereIs command searches for text */
+#define WHEREIS_MENU {"W", N_("WhereIs"), \
+ {MC_WHEREIS, 2, {'w',ctrl('W')}}, \
+ KS_WHEREIS}
+/* TRANSLATORS: the Main Menu is the menu of commands you get when
+ you first start alpine. */
+#define MAIN_MENU {"M", N_("Main Menu"), \
+ {MC_MAIN, 1, {'m'}}, \
+ KS_MAINMENU}
+#define QUIT_MENU {"Q", N_("Quit Alpine"), \
+ {MC_QUIT, 1, {'q'}}, \
+ KS_EXIT}
+/* TRANSLATORS: go to Previous Message */
+#define PREVMSG_MENU {"P", N_("PrevMsg"), \
+ {MC_PREVITEM, 1, {'p'}}, \
+ KS_PREVMSG}
+/* TRANSLATORS: go to Next Message */
+#define NEXTMSG_MENU {"N", N_("NextMsg"), \
+ {MC_NEXTITEM, 1, {'n'}}, \
+ KS_NEXTMSG}
+#define HOMEKEY_MENU {"Hme", N_("FirstPage"), \
+ {MC_HOMEKEY, 1, {KEY_HOME}}, \
+ KS_NONE}
+#define ENDKEY_MENU {"End", N_("LastPage"), \
+ {MC_ENDKEY, 1, {KEY_END}}, \
+ KS_NONE}
+/* TRANSLATORS: go to Previous Page */
+#define PREVPAGE_MENU {"-", N_("PrevPage"), \
+ {MC_PAGEUP, 3, {'-',ctrl('Y'),KEY_PGUP}}, \
+ KS_PREVPAGE}
+#define NEXTPAGE_MENU {"Spc", N_("NextPage"), \
+ {MC_PAGEDN, 4, {'+',' ',ctrl('V'),KEY_PGDN}}, \
+ KS_NEXTPAGE}
+/* TRANSLATORS: Jump to a different message in the index */
+#define JUMP_MENU {"J", N_("Jump"), \
+ {MC_JUMP, 1, {'j'}}, \
+ KS_JUMPTOMSG}
+/* TRANSLATORS: Forward Email */
+#define FWDEMAIL_MENU {"F", N_("Fwd Email"), \
+ {MC_FWDTEXT,1,{'f'}}, \
+ KS_FORWARD}
+#define PRYNTMSG_MENU {"%", N_("Print"), \
+ {MC_PRINTMSG,1,{'%'}}, \
+ KS_PRINT}
+#define PRYNTTXT_MENU {"%", N_("Print"), \
+ {MC_PRINTTXT,1,{'%'}}, \
+ KS_PRINT}
+/* TRANSLATORS: In alpine, Save means to save something internally
+ within alpine or within the email system. */
+#define SAVE_MENU {"S", N_("Save"), \
+ {MC_SAVE,1,{'s'}}, \
+ KS_SAVE}
+/* TRANSLATORS: In alpine, Export means to save something externally
+ to alpine, to a file in the user's home directory, for example. */
+#define EXPORT_MENU {"E", N_("Export"), \
+ {MC_EXPORT, 1, {'e'}}, \
+ KS_EXPORT}
+/* TRANSLATORS: Edit a new message to be sent as email */
+#define COMPOSE_MENU {"C", N_("Compose"), \
+ {MC_COMPOSE,1,{'c'}}, \
+ KS_COMPOSER}
+/* TRANSLATORS: Edit a new message while acting in one of
+ your roles */
+#define RCOMPOSE_MENU {"#", N_("Role"), \
+ {MC_ROLE,1,{'#'}}, \
+ KS_NONE}
+#define DELETE_MENU {"D", N_("Delete"), \
+ {MC_DELETE,2,{'d',KEY_DEL}}, \
+ KS_DELETE}
+#define UNDELETE_MENU {"U", N_("Undelete"), \
+ {MC_UNDELETE,1,{'u'}}, \
+ KS_UNDELETE}
+/* TRANSLATORS: Reply to an email message */
+#define REPLY_MENU {"R", N_("Reply"), \
+ {MC_REPLY,1,{'r'}}, \
+ KS_REPLY}
+/* TRANSLATORS: Forward an email message to someone else */
+#define FORWARD_MENU {"F", N_("Forward"), \
+ {MC_FORWARD,1,{'f'}}, \
+ KS_FORWARD}
+/* TRANSLATORS: go to List of Folders */
+#define LISTFLD_MENU {"L", N_("ListFldrs"), \
+ {MC_COLLECTIONS,1,{'l'}}, \
+ KS_FLDRLIST}
+/* TRANSLATORS: the index is a list of email messages, go there */
+#define INDEX_MENU {"I", N_("Index"), \
+ {MC_INDEX,1,{'i'}}, \
+ KS_FLDRINDEX}
+/* TRANSLATORS: Go To Folder, a command to go view another mail folder */
+#define GOTO_MENU {"G", N_("GotoFldr"), \
+ {MC_GOTO,1,{'g'}}, \
+ KS_GOTOFLDR}
+/* TRANSLATORS: Take Address is a command to copy addresses from a
+ message to the user's address book */
+#define TAKE_MENU {"T", N_("TakeAddr"), \
+ {MC_TAKE,1,{'t'}}, \
+ KS_TAKEADDR}
+/* TRANSLATORS: To Flag a message means to mark it in some way,
+ for example, flag it as important */
+#define FLAG_MENU {"*", N_("Flag"), \
+ {MC_FLAG,1,{'*'}}, \
+ KS_FLAG}
+/* TRANSLATORS: Pipe refers to the Unix pipe command (the vertical bar). */
+#define PIPE_MENU {"|", N_("Pipe"), \
+ {MC_PIPE,1,{'|'}}, \
+ KS_NONE}
+/* TRANSLATORS: Bounce is sometimes called re-sending a message.
+ A user would use this command if a message had been incorrectly
+ sent to him or her. */
+#define BOUNCE_MENU {"B", N_("Bounce"), \
+ {MC_BOUNCE,1,{'b'}}, \
+ KS_BOUNCE}
+/* TRANSLATORS: Header Mode, refers to showing more or fewer message
+ headers when viewing a message. This command toggles between more
+ and fewer each time it is typed. */
+#define HDRMODE_MENU {"H", N_("HdrMode"), \
+ {MC_FULLHDR,1,{'h'}}, \
+ KS_HDRMODE}
+/* TRANSLATORS: The Tab key goes to the Next New message */
+#define TAB_MENU {"Tab", N_("NextNew"), \
+ {MC_TAB,1,{TAB}}, \
+ KS_NONE}
+/* TRANSLATORS: go to the Previous item */
+#define PREV_MENU {"P", N_("Prev"), \
+ {MC_PREVITEM,1,{'p'}}, \
+ KS_NONE}
+#define NEXT_MENU {"N", N_("Next"), \
+ {MC_NEXTITEM,2,{'n','\t'}}, \
+ KS_NONE}
+#define EXIT_SETUP_MENU {"E", N_("Exit Setup"), \
+ {MC_EXIT,1,{'e'}}, \
+ KS_EXITMODE}
+#define TOGGLE_MENU {"X", "[" N_("Set/Unset") "]", \
+ {MC_TOGGLE,3,{'x',ctrl('M'),ctrl('J')}}, \
+ KS_NONE}
+#define TOGGLEB_MENU {"X", "[" N_("Set/Unset") "]", \
+ {MC_TOGGLEB,3,{'x',ctrl('M'),ctrl('J')}}, \
+ KS_NONE}
+#define TOGGLEC_MENU {"X", "[" N_("Set/Unset") "]", \
+ {MC_TOGGLEC,3,{'x',ctrl('M'),ctrl('J')}}, \
+ KS_NONE}
+#define TOGGLED_MENU {"X", "[" N_("Set/Unset") "]", \
+ {MC_TOGGLED,3,{'x',ctrl('M'),ctrl('J')}}, \
+ KS_NONE}
+/* TRANSLATORS: go to the Previous Collection. A Collection in Alpine refers
+ to a collection of mail folders. */
+#define PREVC_MENU {"P", N_("PrevCltn"), \
+ {MC_PREVITEM,1,{'p'}}, \
+ KS_NONE}
+/* TRANSLATORS: Next Collection. */
+#define NEXTC_MENU {"N", N_("NextCltn"), \
+ {MC_NEXTITEM,2,{'n','\t'}}, \
+ KS_NONE}
+/* TRANSLATORS: Delete Collection. */
+#define DELC_MENU {"D", N_("Del Cltn"), \
+ {MC_DELETE,2,{'d',KEY_DEL}}, \
+ KS_NONE}
+/* TRANSLATORS: go to the Previous Folder (in a list of folders). */
+#define PREVF_MENU {"P", N_("PrevFldr"), \
+ {MC_PREV_HANDLE,3,{'p',ctrl('B'),KEY_LEFT}}, \
+ KS_NONE}
+/* TRANSLATORS: Next Folder (in a list of folders). */
+#define NEXTF_MENU {"N", N_("NextFldr"), \
+ {MC_NEXT_HANDLE,4,{'n',ctrl('F'),TAB,KEY_RIGHT}}, \
+ KS_NONE}
+/* TRANSLATORS: Current Index of messages (go to the current index) */
+#define CIND_MENU {"I", N_("CurIndex"), \
+ {MC_INDEX,1,{'i'}}, \
+ KS_FLDRINDEX}
+/* TRANSLATORS: View this Message */
+#define VIEWMSG_MENU {">", "[" N_("ViewMsg") "]", \
+ {MC_VIEW_TEXT, 5,{'v','.','>',ctrl('M'),ctrl('J')}}, \
+ KS_VIEW}
+/* TRANSLATORS: Sort the index of messages */
+#define FLDRSORT_MENU {"$", N_("SortIndex"), \
+ {MC_SORT,1,{'$'}}, \
+ KS_SORT}
+/* TRANSLATORS: Exit the Take Address screen */
+#define TA_EXIT_MENU {"<",N_("ExitTake"), \
+ {MC_EXIT,4,{'e',ctrl('C'),'<',','}}, \
+ KS_EXITMODE}
+#define TA_NEXT_MENU {"N",N_("Next"), \
+ {MC_CHARDOWN,4,{'n','\t',ctrl('N'),KEY_DOWN}}, \
+ KS_NONE}
+/* TRANSLATORS: abbreviation for Previous */
+#define TA_PREV_MENU {"P",N_("Prev"), \
+ {MC_CHARUP, 3, {'p',ctrl('P'),KEY_UP}}, \
+ KS_NONE}
+
+
+/*
+ * It's bogus that these are defined here. They go with the structures
+ * defined in keymenu.c and have to stay in sync with them.
+ */
+#define OTHER_KEY 1
+#define TWO_KEY 2
+#define THREE_KEY 3
+#define ADD_KEY 8
+#define DELETE_KEY 9
+#define SENDTO_KEY 10
+#define SECONDARY_MAIN_KEY 15
+#define RCOMPOSE_KEY 19
+#define TAKE_KEY 21
+#define SAVE_KEY 22
+#define FORW_KEY 23
+#define KM_COL_KEY 2
+#define KM_SEL_KEY 3
+#define KM_MAIN_KEY 15
+#define KM_ALTVIEW_KEY 16
+#define KM_ZOOM_KEY 21
+#define KM_SELECT_KEY 22
+#define KM_SELCUR_KEY 23
+#define KM_RECENT_KEY 28
+#define KM_SHUFFLE_KEY 30
+#define KM_EXPORT_KEY 32
+#define KM_IMPORT_KEY 33
+#define FC_EXIT_KEY 1
+#define FC_COL_KEY 2
+#define FC_SEL_KEY 3
+#define FC_ALTSEL_KEY 8
+#define SB_SUB_KEY 1
+#define SB_EXIT_KEY 2
+#define SB_SEL_KEY 3
+#define SB_LIST_KEY 8
+#define HLP_MAIN_KEY 0
+#define HLP_SUBEXIT_KEY 1
+#define HLP_EXIT_KEY 2
+#define HLP_VIEW_HANDLE 3
+#define HLP_PREV_HANDLE 4
+#define HLP_NEXT_HANDLE 5
+#define HLP_ALL_KEY 9
+#define TIMESTAMP_KEY 4
+#define DEBUG_KEY 5
+#define LM_TRY_KEY 3
+#define LM_PREV_KEY 4
+#define LM_NEXT_KEY 5
+#define BACK_KEY 2
+#define PREVM_KEY 4
+#define NEXTM_KEY 5
+#define EXCLUDE_KEY 26
+#define UNEXCLUDE_KEY 27
+#define SELECT_KEY 28
+#define APPLY_KEY 29
+#define VIEW_FULL_HEADERS_KEY 32
+#define BOUNCE_KEY 33
+#define FLAG_KEY 34
+#define VIEW_PIPE_KEY 35
+#define SELCUR_KEY 38
+#define ZOOM_KEY 39
+#define COLLAPSE_KEY 45
+#define ATT_PARENT_KEY 2
+#define ATT_EXPORT_KEY 11
+#define ATT_PIPE_KEY 16
+#define ATT_BOUNCE_KEY 17
+#define ATT_PRINT_KEY 20
+#define ATT_REPLY_KEY 22
+#define ATT_FORWARD_KEY 23
+#define ATV_BACK_KEY 2
+#define ATV_VIEW_HILITE 3
+#define ATV_PREV_URL 4
+#define ATV_NEXT_URL 5
+#define ATV_EXPORT_KEY 11
+#define ATV_PIPE_KEY 16
+#define ATV_BOUNCE_KEY 17
+#define ATV_PRINT_KEY 20
+#define ATV_REPLY_KEY 22
+#define ATV_FORWARD_KEY 23
+#define VIEW_ATT_KEY 3
+#define VIEW_FULL_HEADERS_KEY 32
+#define VIEW_VIEW_HANDLE 26
+#define VIEW_SELECT_KEY 27
+#define VIEW_PREV_HANDLE 28
+#define VIEW_NEXT_HANDLE 29
+#define OE_HELP_KEY 0
+#define OE_CANCEL_KEY 1
+#define OE_CTRL_T_KEY 2
+#define OE_ENTER_KEY 3
+#define SETUP_PRINTER 3
+#define SETUP_PASSWD 4
+#define SETUP_CONFIG 5
+#define SETUP_SIG 6
+#define SETUP_DIRECTORY 10
+#define SETUP_EXCEPT 14
+#define SETUP_SMIME 16
+#define MAIN_HELP_KEY 0
+#define MAIN_DEFAULT_KEY 3
+#define MAIN_KBLOCK_KEY 9
+#define MAIN_QUIT_KEY 14
+#define MAIN_COMPOSE_KEY 15
+#define MAIN_FOLDER_KEY 16
+#define MAIN_INDEX_KEY 18
+#define MAIN_SETUP_KEY 20
+#define MAIN_ADDRESS_KEY 21
+#define NUOV_EXIT 2
+#define NUOV_VIEW 3
+#define NUOV_NEXT_PG 6
+#define NUOV_PREV_PG 7
+#define NUOV_RELNOTES 10
+#define DEFAULT_KEY 3
+#define CHANGEDEF_KEY 10
+#define SMIME_PARENT_KEY 2
+#define DECRYPT_KEY (VIEW_PIPE_KEY + 7)
+#define SECURITY_KEY (DECRYPT_KEY + 1)
+
+
+extern struct key_menu cancel_keymenu,
+ ab_keymenu,
+ abook_select_km,
+ abook_view_keymenu,
+ abook_text_km,
+ ldap_view_keymenu,
+ c_mgr_km,
+ c_cfg_km,
+ c_sel_km,
+ c_fcc_km,
+ pine_quota_keymenu,
+ folder_km,
+ folder_sel_km,
+ folder_sela_km,
+ folder_sub_km,
+ folder_post_km,
+ help_keymenu,
+ rev_msg_keymenu,
+ ans_certfail_keymenu,
+ ans_certquery_keymenu,
+ forge_keymenu,
+ listmgr_keymenu,
+ index_keymenu,
+ simple_index_keymenu,
+ thread_keymenu,
+ att_index_keymenu,
+ att_view_keymenu,
+ view_keymenu,
+ simple_text_keymenu,
+ oe_keymenu,
+ choose_setup_keymenu,
+ main_keymenu,
+ simple_file_keymenu,
+ nuov_keymenu,
+ modal_message_keymenu,
+ ta_keymenu_lm,
+ ta_keymenu_sm,
+ pipe_cancel_keymenu,
+ color_pattern_keymenu,
+ hdr_color_checkbox_keymenu,
+ kw_color_checkbox_keymenu,
+ selectable_bold_checkbox_keymenu,
+ flag_keymenu,
+ addr_s_km,
+ addr_s_km_with_goback,
+ addr_s_km_with_view,
+ addr_s_km_for_url,
+ addr_s_km_exit,
+ addr_s_km_goback,
+ dir_conf_km,
+ sel_from_list,
+ sel_from_list_ctrlc,
+ sel_from_list_sm,
+ sel_from_list_sm_ctrlc,
+ sel_from_list_lm,
+ sel_from_list_lm_ctrlc,
+ sel_from_list_olm,
+ sel_from_list_olm_ctrlc,
+ printer_edit_keymenu,
+ printer_select_keymenu,
+ role_select_km,
+ role_conf_km,
+ config_text_wshuf_keymenu,
+ config_text_wshufandfldr_keymenu,
+ config_role_file_keymenu,
+ config_role_file_res_keymenu,
+ config_role_keyword_keymenu,
+ config_role_keyword_keymenu_not,
+ config_role_charset_keymenu_not,
+ config_role_keymenu,
+ config_role_keymenu_not,
+ config_role_keymenu_extra,
+ config_role_addr_pat_keymenu,
+ config_role_xtrahdr_keymenu,
+ config_role_addr_act_keymenu,
+ config_role_patfolder_keymenu,
+ config_role_actionfolder_keymenu,
+ config_role_inick_keymenu,
+ config_role_afrom_keymenu,
+ config_checkbox_keymenu,
+ config_text_keymenu,
+ config_text_to_charsets_keymenu,
+ config_radiobutton_keymenu,
+ config_yesno_keymenu,
+ color_changing_keymenu,
+ custom_color_changing_keymenu,
+ kw_color_changing_keymenu,
+ color_rgb_keymenu,
+ custom_rgb_keymenu,
+ kw_rgb_keymenu,
+ color_setting_keymenu,
+ custom_color_setting_keymenu,
+ role_color_setting_keymenu,
+ kw_color_setting_keymenu,
+ take_export_keymenu_sm,
+ take_export_keymenu_lm,
+ config_smime_helper_keymenu,
+ smime_info_keymenu;
+
+extern struct key rev_msg_keys[];
+
+
+/* exported protoypes */
+void draw_cancel_keymenu(void);
+void end_keymenu(void);
+int menu_command(UCS, struct key_menu *);
+void menu_init_binding(struct key_menu *, UCS, int, char *, char *, int);
+void menu_add_binding(struct key_menu *, UCS, int);
+int menu_clear_binding(struct key_menu *, UCS);
+int menu_binding_index(struct key_menu *, int);
+void mark_keymenu_dirty(void);
+void blank_keymenu(int, int);
+void draw_keymenu(struct key_menu *, bitmap_t, int, int, int, OtherMenu);
+void redraw_keymenu(void);
+void clearfooter(struct pine *);
+
+
+#endif /* PINE_KEYMENU_INCLUDED */
diff --git a/alpine/ldap32.dll b/alpine/ldap32.dll
new file mode 100755
index 00000000..2284f9f8
--- /dev/null
+++ b/alpine/ldap32.dll
Binary files differ
diff --git a/alpine/ldapconf.c b/alpine/ldapconf.c
new file mode 100644
index 00000000..714bf0cd
--- /dev/null
+++ b/alpine/ldapconf.c
@@ -0,0 +1,2432 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: ldapconf.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "ldapconf.h"
+#include "keymenu.h"
+#include "radio.h"
+#include "status.h"
+#include "confscroll.h"
+#include "adrbkcmd.h"
+#include "titlebar.h"
+#include "takeaddr.h"
+#include "../pith/ldap.h"
+#include "../pith/state.h"
+#include "../pith/bitmap.h"
+#include "../pith/mailcmd.h"
+#include "../pith/list.h"
+
+
+/*
+ * Internal prototypes
+ */
+#ifdef ENABLE_LDAP
+int addr_select_tool(struct pine *, int, CONF_S **, unsigned);
+void dir_init_display(struct pine *, CONF_S **, char **, struct variable *, CONF_S **);
+int dir_config_tool(struct pine *, int, CONF_S **, unsigned);
+void dir_config_add(struct pine *, CONF_S **);
+void dir_config_shuffle(struct pine *, CONF_S **);
+void dir_config_edit(struct pine *, CONF_S **);
+int dir_edit_screen(struct pine *, LDAP_SERV_S *, char *, char **);
+int dir_edit_tool(struct pine *, int, CONF_S **, unsigned);
+void dir_config_del(struct pine *, CONF_S **);
+void add_ldap_fake_first_server(struct pine *, CONF_S **, struct variable *,
+ struct key_menu *, HelpType,
+ int (*)(struct pine *, int, CONF_S **, unsigned));
+void add_ldap_server_to_display(struct pine *, CONF_S **, char *, char *,
+ struct variable *, int, struct key_menu *, HelpType,
+ int (*)(struct pine *, int, CONF_S **, unsigned),
+ int, CONF_S **);
+int ldap_checkbox_tool(struct pine *, int, CONF_S **, unsigned);
+void toggle_ldap_option_bit(struct pine *, int, struct variable *, char *);
+NAMEVAL_S *ldap_feature_list(int);
+
+
+static char *srch_res_help_title = N_("HELP FOR SEARCH RESULTS INDEX");
+static char *set_choose = "--- ----------------------";
+#define ADD_FIRST_LDAP_SERVER _("Use Add to add a directory server")
+#define ADDR_SELECT_EXIT_VAL 5
+#define ADDR_SELECT_GOBACK_VAL 6
+#define ADDR_SELECT_FORCED_EXIT_VAL 7
+
+
+static int some_selectable;
+static char *dserv = N_("Directory Server on ");
+
+/*
+ * Let user choose an ldap entry (or return an entry if user doesn't need
+ * to be consulted).
+ *
+ * Returns 0 if ok,
+ * -1 if Exit was chosen
+ * -2 if none were selectable
+ * -3 if no entries existed at all
+ * -4 go back to Abook List was chosen
+ * -5 caller shouldn't free ac->res_head
+ *
+ * When 0 is returned the winner is pointed to by result.
+ * Result is an allocated LDAP_SEARCH_WINNER_S which has pointers
+ * to the ld and entry that were chosen. Those are pointers into
+ * the initial data, not copies. The two-pointer structure is
+ * allocated here and freed by the caller.
+ */
+int
+ldap_addr_select(struct pine *ps, ADDR_CHOOSE_S *ac, LDAP_CHOOSE_S **result,
+ LDAPLookupStyle style, WP_ERR_S *wp_err, char *srchstr)
+{
+ LDAPMessage *e;
+ LDAP_SERV_RES_S *res_list;
+ CONF_S *ctmpa = NULL, *first_line = NULL, *alt_first_line = NULL;
+ int i, retval = 0, got_n_mail = 0, got_n_entries = 0;
+ int need_mail;
+ OPT_SCREEN_S screen;
+ struct key_menu *km;
+ char ee[200];
+ HelpType help;
+ void (*prev_redrawer) (void);
+
+ dprint((4, "ldap_addr_select()\n"));
+
+ need_mail = (style == AlwaysDisplay || style == DisplayForURL) ? 0 : 1;
+ if(style == AlwaysDisplay){
+ km = &addr_s_km_with_view;
+ help = h_address_display;
+ }
+ else if(style == AlwaysDisplayAndMailRequired){
+ km = &addr_s_km_with_goback;
+ help = h_address_select;
+ }
+ else if(style == DisplayForURL){
+ km = &addr_s_km_for_url;
+ help = h_address_display;
+ }
+ else{
+ km = &addr_s_km;
+ help = h_address_select;
+ }
+
+ if(result)
+ *result = NULL;
+
+ some_selectable = 0;
+
+ for(res_list = ac->res_head; res_list; res_list = res_list->next){
+ for(e = ldap_first_entry(res_list->ld, res_list->res);
+ e != NULL;
+ e = ldap_next_entry(res_list->ld, e)){
+ char *dn, *a;
+ char **cn, **org, **unit, **title, **mail, **sn;
+ BerElement *ber;
+ int indent, have_mail;
+
+ dn = NULL;
+ cn = org = title = unit = mail = sn = NULL;
+ for(a = ldap_first_attribute(res_list->ld, e, &ber);
+ a != NULL;
+ a = ldap_next_attribute(res_list->ld, e, ber)){
+
+ dprint((9, " %s", a ? a : "?"));
+ if(strcmp(a, res_list->info_used->cnattr) == 0){
+ if(!cn)
+ cn = ldap_get_values(res_list->ld, e, a);
+
+ if(cn && !(cn[0] && cn[0][0])){
+ ldap_value_free(cn);
+ cn = NULL;
+ }
+ }
+ else if(strcmp(a, res_list->info_used->mailattr) == 0){
+ if(!mail)
+ mail = ldap_get_values(res_list->ld, e, a);
+ }
+ else if(strcmp(a, "o") == 0){
+ if(!org)
+ org = ldap_get_values(res_list->ld, e, a);
+ }
+ else if(strcmp(a, "ou") == 0){
+ if(!unit)
+ unit = ldap_get_values(res_list->ld, e, a);
+ }
+ else if(strcmp(a, "title") == 0){
+ if(!title)
+ title = ldap_get_values(res_list->ld, e, a);
+ }
+
+ our_ldap_memfree(a);
+ }
+
+ dprint((9, "\n"));
+
+ if(!cn){
+ for(a = ldap_first_attribute(res_list->ld, e, &ber);
+ a != NULL;
+ a = ldap_next_attribute(res_list->ld, e, ber)){
+
+ if(strcmp(a, res_list->info_used->snattr) == 0){
+ if(!sn)
+ sn = ldap_get_values(res_list->ld, e, a);
+
+ if(sn && !(sn[0] && sn[0][0])){
+ ldap_value_free(sn);
+ sn = NULL;
+ }
+ }
+
+ our_ldap_memfree(a);
+ }
+ }
+
+ if(mail && mail[0] && mail[0][0])
+ have_mail = 1;
+ else
+ have_mail = 0;
+
+ got_n_mail += have_mail;
+ got_n_entries++;
+ indent = 2;
+
+ /*
+ * First line is either cn, sn, or dn.
+ */
+ if(cn){
+ new_confline(&ctmpa);
+ if(!alt_first_line)
+ alt_first_line = ctmpa;
+
+ ctmpa->flags |= CF_STARTITEM;
+ if(need_mail && !have_mail)
+ ctmpa->flags |= CF_PRIVATE;
+
+ ctmpa->value = cpystr(cn[0]);
+ ldap_value_free(cn);
+ ctmpa->valoffset = indent;
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ if(!first_line && (have_mail || !need_mail))
+ first_line = ctmpa;
+ }
+
+ /* only happens if no cn */
+ if(sn){
+ new_confline(&ctmpa);
+ if(!alt_first_line)
+ alt_first_line = ctmpa;
+
+ ctmpa->flags |= CF_STARTITEM;
+ if(need_mail && !have_mail)
+ ctmpa->flags |= CF_PRIVATE;
+
+ ctmpa->value = cpystr(sn[0]);
+ ldap_value_free(sn);
+ ctmpa->valoffset = indent;
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ if(!first_line && (have_mail || !need_mail))
+ first_line = ctmpa;
+ }
+
+ if(!sn && !cn){
+ new_confline(&ctmpa);
+ if(!alt_first_line)
+ alt_first_line = ctmpa;
+
+ ctmpa->flags |= CF_STARTITEM;
+ if(need_mail && !have_mail)
+ ctmpa->flags |= CF_PRIVATE;
+
+ dn = ldap_get_dn(res_list->ld, e);
+
+ if(dn && !dn[0]){
+ our_ldap_dn_memfree(dn);
+ dn = NULL;
+ }
+
+ ctmpa->value = cpystr(dn ? dn : "?");
+ if(dn)
+ our_ldap_dn_memfree(dn);
+
+ ctmpa->valoffset = indent;
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ if(!first_line && (have_mail || !need_mail))
+ first_line = ctmpa;
+ }
+
+ if(title){
+ for(i = 0; title[i] && title[i][0]; i++){
+ new_confline(&ctmpa);
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = indent + 2;
+ ctmpa->value = cpystr(title[i]);
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ }
+
+ ldap_value_free(title);
+ }
+
+ if(unit){
+ for(i = 0; unit[i] && unit[i][0]; i++){
+ new_confline(&ctmpa);
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = indent + 2;
+ ctmpa->value = cpystr(unit[i]);
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ }
+
+ ldap_value_free(unit);
+ }
+
+ if(org){
+ for(i = 0; org[i] && org[i][0]; i++){
+ new_confline(&ctmpa);
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = indent + 2;
+ ctmpa->value = cpystr(org[i]);
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ }
+
+ ldap_value_free(org);
+ }
+
+ if(have_mail){
+ /* Don't show long list of email addresses. */
+ if(!(mail[0] && mail[0][0]) ||
+ !(mail[1] && mail[1][0]) ||
+ !(mail[2] && mail[2][0]) ||
+ !(mail[3] && mail[3][0])){
+ for(i = 0; mail[i] && mail[i][0]; i++){
+ new_confline(&ctmpa);
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = indent + 2;
+ ctmpa->value = cpystr(mail[i]);
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ }
+ }
+ else{
+ char tmp[200];
+
+ for(i = 4; mail[i] && mail[i][0]; i++)
+ ;
+
+ new_confline(&ctmpa);
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = indent + 2;
+ snprintf(tmp, sizeof(tmp), _("(%d email addresses)"), i);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmpa->value = cpystr(tmp);
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ }
+ }
+ else{
+ new_confline(&ctmpa);
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->valoffset = indent + 2;
+ ctmpa->value = cpystr(_("<No Email Address Available>"));
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->d.a.ld = res_list->ld;
+ ctmpa->d.a.entry = e;
+ ctmpa->d.a.info_used = res_list->info_used;
+ ctmpa->d.a.serv = res_list->serv;
+ ctmpa->d.a.ac = ac;
+ }
+
+ if(mail)
+ ldap_value_free(mail);
+
+ new_confline(&ctmpa); /* blank line */
+ ctmpa->keymenu = km;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+ }
+ }
+
+ if(first_line)
+ some_selectable++;
+ else if(alt_first_line)
+ first_line = alt_first_line;
+ else{
+ new_confline(&ctmpa); /* blank line */
+ ctmpa->keymenu = need_mail ? &addr_s_km_exit : &addr_s_km_goback;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmpa);
+ first_line = ctmpa;
+ strncpy(ee, "[ ", sizeof(ee));
+ ee[sizeof(ee)-1] = '\0';
+ if(wp_err && wp_err->ldap_errno)
+ /* TRANSLATORS: No matches returned for an LDAP search */
+ snprintf(ee+2, sizeof(ee)-2, _("%s, No Matches Returned"),
+ ldap_err2string(wp_err->ldap_errno));
+ else
+ strncpy(ee+2, _("No Matches"), sizeof(ee)-2);
+
+ ee[sizeof(ee)-1] = '\0';
+
+ /* TRANSLATORS: a request for user to choose Exit after they read text */
+ strncat(ee, _(" -- Choose Exit ]"), sizeof(ee)-strlen(ee)-1);
+ ee[sizeof(ee)-1] = '\0';
+ ctmpa->value = cpystr(ee);
+ ctmpa->valoffset = 10;
+ ctmpa->keymenu = need_mail ? &addr_s_km_exit : &addr_s_km_goback;
+ ctmpa->help = help;
+ ctmpa->help_title = _(srch_res_help_title);
+ ctmpa->tool = addr_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ }
+
+ if(style == AlwaysDisplay || style == DisplayForURL ||
+ style == AlwaysDisplayAndMailRequired ||
+ (style == DisplayIfOne && got_n_mail >= 1) ||
+ (style == DisplayIfTwo && got_n_mail >= 1 && got_n_entries >= 2)){
+ if(wp_err && wp_err->mangled)
+ *wp_err->mangled = 1;
+
+ prev_redrawer = ps_global->redrawer;
+ push_titlebar_state();
+
+ memset(&screen, 0, sizeof(screen));
+ /* TRANSLATORS: Print something1 using something2.
+ "this" is something1 */
+ switch(conf_scroll_screen(ps,&screen,first_line,ac->title,_("this"),0)){
+ case ADDR_SELECT_EXIT_VAL:
+ retval = -1;
+ break;
+
+ case ADDR_SELECT_GOBACK_VAL:
+ retval = -4;
+ break;
+
+ case ADDR_SELECT_FORCED_EXIT_VAL:
+ if(alt_first_line) /* some entries, but none suitable */
+ retval = -2;
+ else
+ retval = -3;
+
+ break;
+
+ default:
+ retval = 0;
+ break;
+ }
+
+ ClearScreen();
+ pop_titlebar_state();
+ redraw_titlebar();
+ if((ps_global->redrawer = prev_redrawer) != NULL)
+ (*ps_global->redrawer)();
+
+ if(result && retval == 0 && ac->selected_ld && ac->selected_entry){
+ (*result) = (LDAP_CHOOSE_S *)fs_get(sizeof(LDAP_CHOOSE_S));
+ (*result)->ld = ac->selected_ld;
+ (*result)->selected_entry = ac->selected_entry;
+ (*result)->info_used = ac->info_used;
+ (*result)->serv = ac->selected_serv;
+ }
+ }
+ else if(style == DisplayIfOne && got_n_mail < 1){
+ if(alt_first_line) /* some entries, but none suitable */
+ retval = -2;
+ else
+ retval = -3;
+
+ first_line = first_confline(ctmpa);
+ free_conflines(&first_line);
+ }
+ else if(style == DisplayIfTwo && (got_n_mail < 1 || got_n_entries < 2)){
+ if(got_n_mail < 1){
+ if(alt_first_line) /* some entries, but none suitable */
+ retval = -2;
+ else
+ retval = -3;
+ }
+ else{
+ retval = 0;
+ if(result){
+ (*result) = (LDAP_CHOOSE_S *)fs_get(sizeof(LDAP_CHOOSE_S));
+ (*result)->ld = first_line->d.a.ld;
+ (*result)->selected_entry = first_line->d.a.entry;
+ (*result)->info_used = first_line->d.a.info_used;
+ (*result)->serv = first_line->d.a.serv;
+ }
+ }
+
+ first_line = first_confline(ctmpa);
+ free_conflines(&first_line);
+ }
+
+ return(retval);
+}
+
+
+int
+addr_select_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int retval = 0;
+
+ switch(cmd){
+ case MC_CHOICE :
+ if(flags & CF_PRIVATE){
+ q_status_message(SM_ORDER | SM_DING, 0, 3,
+ _("No email address available for this entry; choose another or ExitSelect"));
+ }
+ else if(some_selectable){
+ (*cl)->d.a.ac->selected_ld = (*cl)->d.a.ld;
+ (*cl)->d.a.ac->selected_entry = (*cl)->d.a.entry;
+ (*cl)->d.a.ac->info_used = (*cl)->d.a.info_used;
+ (*cl)->d.a.ac->selected_serv = (*cl)->d.a.serv;
+ retval = simple_exit_cmd(flags);
+ }
+ else
+ retval = ADDR_SELECT_FORCED_EXIT_VAL;
+
+ break;
+
+ case MC_VIEW_TEXT :
+ case MC_SAVE :
+ case MC_FWDTEXT :
+ case MC_COMPOSE :
+ case MC_ROLE :
+ {LDAP_CHOOSE_S *e;
+
+ if((*cl)->d.a.ld && (*cl)->d.a.entry){
+ e = (LDAP_CHOOSE_S *)fs_get(sizeof(LDAP_CHOOSE_S));
+ e->ld = (*cl)->d.a.ld;
+ e->selected_entry = (*cl)->d.a.entry;
+ e->info_used = (*cl)->d.a.info_used;
+ e->serv = (*cl)->d.a.serv;
+ if(cmd == MC_VIEW_TEXT)
+ view_ldap_entry(ps, e);
+ else if(cmd == MC_SAVE)
+ save_ldap_entry(ps, e, 0);
+ else if(cmd == MC_COMPOSE)
+ compose_to_ldap_entry(ps, e, 0);
+ else if(cmd == MC_ROLE)
+ compose_to_ldap_entry(ps, e, 1);
+ else
+ forward_ldap_entry(ps, e);
+
+ fs_give((void **)&e);
+ }
+ }
+
+ break;
+
+ case MC_ADDRBOOK :
+ retval = ADDR_SELECT_GOBACK_VAL;
+ break;
+
+ case MC_EXIT :
+ retval = ADDR_SELECT_EXIT_VAL;
+ break;
+
+ default:
+ retval = -1;
+ break;
+ }
+
+ if(retval > 0)
+ ps->mangled_body = 1;
+
+ return(retval);
+}
+
+
+void
+dir_init_display(struct pine *ps, CONF_S **ctmp, char **servers,
+ struct variable *var, CONF_S **first_line)
+{
+ int i;
+ char *serv;
+ char *subtitle;
+ LDAP_SERV_S *info;
+
+ if(first_line)
+ *first_line = NULL;
+
+ if(servers && servers[0] && servers[0][0]){
+ for(i = 0; servers[i]; i++){
+ info = break_up_ldap_server(servers[i]);
+ serv = (info && info->nick && *info->nick) ? cpystr(info->nick) :
+ (info && info->serv && *info->serv) ? cpystr(info->serv) :
+ cpystr(_("Bad Server Config, Delete this"));
+ subtitle = (char *)fs_get((((info && info->serv && *info->serv)
+ ? strlen(info->serv)
+ : 3) +
+ strlen(_(dserv)) + 15) *
+ sizeof(char));
+ if(info && info->port >= 0)
+ snprintf(subtitle, sizeof(subtitle), "%s%s:%d",
+ _(dserv),
+ (info && info->serv && *info->serv) ? info->serv : "<?>",
+ info->port);
+ else
+ snprintf(subtitle, sizeof(subtitle), "%s%s",
+ _(dserv),
+ (info && info->serv && *info->serv) ? info->serv : "<?>");
+
+ subtitle[sizeof(subtitle)-1] = '\0';
+
+ add_ldap_server_to_display(ps, ctmp, serv, subtitle, var,
+ i, &dir_conf_km, h_direct_config,
+ dir_config_tool, 0,
+ (first_line && *first_line == NULL)
+ ? first_line
+ : NULL);
+
+ free_ldap_server_info(&info);
+ }
+ }
+ else{
+ add_ldap_fake_first_server(ps, ctmp, var,
+ &dir_conf_km, h_direct_config,
+ dir_config_tool);
+ if(first_line)
+ *first_line = *ctmp;
+ }
+}
+
+
+void
+directory_config(struct pine *ps, int edit_exceptions)
+{
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ OPT_SCREEN_S screen;
+ int no_ex, readonly_warning = 0;
+
+ if(edit_exceptions){
+ q_status_message(SM_ORDER, 3, 7,
+ _("Exception Setup not implemented for directory"));
+ return;
+ }
+
+ ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;
+
+ no_ex = (ps_global->ew_for_except_vars == Main);
+
+ if(ps->restricted)
+ readonly_warning = 1;
+ else{
+ PINERC_S *prc = NULL;
+
+ switch(ew){
+ case Main:
+ prc = ps->prc;
+ break;
+ case Post:
+ prc = ps->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ readonly_warning = prc ? prc->readonly : 1;
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return;
+ }
+ }
+
+ if(ps->fix_fixed_warning)
+ offer_to_fix_pinerc(ps);
+
+ dir_init_display(ps, &ctmp, no_ex ? ps->VAR_LDAP_SERVERS
+ : LVAL(&ps->vars[V_LDAP_SERVERS], ew),
+ &ps->vars[V_LDAP_SERVERS], &first_line);
+
+ memset(&screen, 0, sizeof(screen));
+ screen.deferred_ro_warning = readonly_warning;
+ /* TRANSLATORS: Print something1 using something2.
+ "servers" is something1 */
+ (void)conf_scroll_screen(ps, &screen, first_line,
+ _("SETUP DIRECTORY SERVERS"), _("servers"), 0);
+ ps->mangled_screen = 1;
+}
+
+
+int
+dir_config_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int first_one, rv = 0;
+
+ first_one = (*cl)->value &&
+ (strcmp((*cl)->value, ADD_FIRST_LDAP_SERVER) == 0);
+ switch(cmd){
+ case MC_DELETE :
+ if(first_one)
+ q_status_message(SM_ORDER|SM_DING, 0, 3,
+ _("Nothing to Delete, use Add"));
+ else
+ dir_config_del(ps, cl);
+
+ break;
+
+ case MC_ADD :
+ if(!fixed_var((*cl)->var, NULL, "directory list"))
+ dir_config_add(ps, cl);
+
+ break;
+
+ case MC_EDIT :
+ if(!fixed_var((*cl)->var, NULL, "directory list")){
+ if(first_one)
+ dir_config_add(ps, cl);
+ else
+ dir_config_edit(ps, cl);
+ }
+
+ break;
+
+ case MC_SHUFFLE :
+ if(!fixed_var((*cl)->var, NULL, "directory list")){
+ if(first_one)
+ q_status_message(SM_ORDER|SM_DING, 0, 3,
+ _("Nothing to Shuffle, use Add"));
+ else
+ dir_config_shuffle(ps, cl);
+ }
+
+ break;
+
+ case MC_EXIT :
+ rv = 2;
+ break;
+
+ default:
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Add LDAP directory entry
+ */
+void
+dir_config_add(struct pine *ps, CONF_S **cl)
+{
+ char *raw_server = NULL;
+ LDAP_SERV_S *info = NULL;
+ char **lval;
+ int no_ex;
+
+ no_ex = (ps_global->ew_for_except_vars == Main);
+
+ if(dir_edit_screen(ps, NULL, "ADD A", &raw_server) == 1){
+
+ info = break_up_ldap_server(raw_server);
+
+ if(info && info->serv && *info->serv){
+ char *subtitle;
+ int i, cnt = 0;
+ char **new_list;
+ CONF_S *cp;
+
+ lval = no_ex ? (*cl)->var->current_val.l : LVAL((*cl)->var, ew);
+ if(lval)
+ while(lval[cnt])
+ cnt++;
+
+ /* catch the special "" case */
+ if(cnt == 0 ||
+ (cnt == 1 && lval[0][0] == '\0')){
+ new_list = (char **)fs_get((1 + 1) * sizeof(char *));
+ new_list[0] = raw_server;
+ new_list[1] = NULL;
+ }
+ else{
+ /* add one for new value */
+ cnt++;
+ new_list = (char **)fs_get((cnt + 1) * sizeof(char *));
+
+ for(i = 0; i < (*cl)->varmem; i++)
+ new_list[i] = cpystr(lval[i]);
+
+ new_list[(*cl)->varmem] = raw_server;
+
+ for(i = (*cl)->varmem; i < cnt; i++)
+ new_list[i+1] = cpystr(lval[i]);
+ }
+
+ raw_server = NULL;
+ set_variable_list(V_LDAP_SERVERS, new_list, FALSE, ew);
+ free_list_array(&new_list);
+ set_current_val((*cl)->var, TRUE, FALSE);
+ subtitle = (char *)fs_get((((info && info->serv && *info->serv)
+ ? strlen(info->serv)
+ : 3) +
+ strlen(_(dserv)) + 15) *
+ sizeof(char));
+ if(info && info->port >= 0)
+ snprintf(subtitle, sizeof(subtitle), "%s%s:%d",
+ _(dserv),
+ (info && info->serv && *info->serv) ? info->serv : "<?>",
+ info->port);
+ else
+ snprintf(subtitle, sizeof(subtitle), "%s%s",
+ _(dserv),
+ (info && info->serv && *info->serv) ? info->serv : "<?>");
+
+ subtitle[sizeof(subtitle)-1] = '\0';
+
+ if(cnt < 2){ /* first one */
+ struct variable *var;
+ struct key_menu *keymenu;
+ HelpType help;
+ int (*tool)(struct pine *, int, CONF_S **, unsigned);
+
+ var = (*cl)->var;
+ keymenu = (*cl)->keymenu;
+ help = (*cl)->help;
+ tool = (*cl)->tool;
+ *cl = first_confline(*cl);
+ free_conflines(cl);
+ add_ldap_server_to_display(ps, cl,
+ (info && info->nick && *info->nick)
+ ? cpystr(info->nick)
+ : cpystr(info->serv),
+ subtitle, var, 0, keymenu, help,
+ tool, 0, NULL);
+
+ opt_screen->top_line = NULL;
+ }
+ else{
+ /*
+ * Insert new server.
+ */
+ add_ldap_server_to_display(ps, cl,
+ (info && info->nick && *info->nick)
+ ? cpystr(info->nick)
+ : cpystr(info->serv),
+ subtitle,
+ (*cl)->var,
+ (*cl)->varmem,
+ (*cl)->keymenu,
+ (*cl)->help,
+ (*cl)->tool,
+ 1,
+ NULL);
+ /* adjust the rest of the varmems */
+ for(cp = (*cl)->next; cp; cp = cp->next)
+ cp->varmem++;
+ }
+
+ /* because add_ldap advanced cl to its third line */
+ (*cl) = (*cl)->prev->prev;
+
+ fix_side_effects(ps, (*cl)->var, 0);
+ write_pinerc(ps, ew, WRP_NONE);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Add cancelled, no server name"));
+ }
+
+ free_ldap_server_info(&info);
+ if(raw_server)
+ fs_give((void **)&raw_server);
+}
+
+
+/*
+ * Shuffle order of LDAP directory entries
+ */
+void
+dir_config_shuffle(struct pine *ps, CONF_S **cl)
+{
+ int cnt, rv, current_num, new_num, i, j, deefault;
+ char **new_list, **lval;
+ char tmp[200];
+ HelpType help;
+ ESCKEY_S opts[3];
+ CONF_S *a, *b;
+ int no_ex;
+
+ no_ex = (ps_global->ew_for_except_vars == Main);
+
+ /* how many are in our current list? */
+ lval = no_ex ? (*cl)->var->current_val.l : LVAL((*cl)->var, ew);
+ for(cnt = 0; lval && lval[cnt]; cnt++)
+ ;
+
+ if(cnt < 2){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Shuffle only makes sense when there is more than one server in list"));
+ return;
+ }
+
+ current_num = (*cl)->varmem; /* variable number of highlighted directory */
+
+ /* Move it up or down? */
+ i = 0;
+ opts[i].ch = 'u';
+ opts[i].rval = 'u';
+ opts[i].name = "U";
+ opts[i++].label = _("Up");
+
+ opts[i].ch = 'd';
+ opts[i].rval = 'd';
+ opts[i].name = "D";
+ opts[i++].label = _("Down");
+
+ opts[i].ch = -1;
+ deefault = 'u';
+
+ if(current_num == 0){ /* no up */
+ opts[0].ch = -2;
+ deefault = 'd';
+ }
+ else if(current_num == cnt - 1) /* no down */
+ opts[1].ch = -2;
+
+ snprintf(tmp, sizeof(tmp), "Shuffle \"%s\" %s%s%s ? ",
+ (*cl)->value,
+ (opts[0].ch != -2) ? _("UP") : "",
+ (opts[0].ch != -2 && opts[1].ch != -2) ? " or " : "",
+ (opts[1].ch != -2) ? _("DOWN") : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ help = (opts[0].ch == -2) ? h_dir_shuf_down
+ : (opts[1].ch == -2) ? h_dir_shuf_up
+ : h_dir_shuf;
+
+ rv = radio_buttons(tmp, -FOOTER_ROWS(ps), opts, deefault, 'x',
+ help, RB_NORM);
+
+ switch(rv){
+ case 'x':
+ cmd_cancelled("Shuffle");
+ return;
+
+ case 'u':
+ new_num = current_num - 1;
+ a = (*cl)->prev->prev->prev;
+ b = *cl;
+ break;
+
+ case 'd':
+ new_num = current_num + 1;
+ a = *cl;
+ b = (*cl)->next->next->next;
+ break;
+ }
+
+ /* allocate space for new list */
+ new_list = (char **)fs_get((cnt + 1) * sizeof(char *));
+
+ /* fill in new_list */
+ for(i = 0; i < cnt; i++){
+ if(i == current_num)
+ j = new_num;
+ else if (i == new_num)
+ j = current_num;
+ else
+ j = i;
+
+ /* notice this works even if we were using default */
+ new_list[i] = cpystr(lval[j]);
+ }
+
+ new_list[i] = NULL;
+
+ j = set_variable_list((*cl)->var - ps->vars, new_list, TRUE, ew);
+ free_list_array(&new_list);
+ if(j){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Shuffle cancelled: couldn't save configuration file"));
+ set_current_val((*cl)->var, TRUE, FALSE);
+ return;
+ }
+
+ set_current_val((*cl)->var, TRUE, FALSE);
+
+ if(a == opt_screen->top_line)
+ opt_screen->top_line = b;
+
+ j = a->varmem;
+ a->varmem = b->varmem;
+ b->varmem = j;
+
+ /*
+ * Swap display lines. To start with, a is lower in list, b is higher.
+ * The fact that there are 3 lines per entry is totally entangled in
+ * the code.
+ */
+ a->next->next->next = b->next->next->next;
+ if(b->next->next->next)
+ b->next->next->next->prev = a->next->next;
+ b->prev = a->prev;
+ if(a->prev)
+ a->prev->next = b;
+ b->next->next->next = a;
+ a->prev = b->next->next;
+
+ ps->mangled_body = 1;
+ write_pinerc(ps, ew, WRP_NONE);
+}
+
+
+/*
+ * Edit LDAP directory entry
+ */
+void
+dir_config_edit(struct pine *ps, CONF_S **cl)
+{
+ char *raw_server = NULL, **lval;
+ LDAP_SERV_S *info;
+ int no_ex;
+
+ no_ex = (ps_global->ew_for_except_vars == Main);
+
+ lval = no_ex ? (*cl)->var->current_val.l : LVAL((*cl)->var, ew);
+ info = break_up_ldap_server((lval && lval[(*cl)->varmem])
+ ? lval[(*cl)->varmem] : NULL);
+
+ if(dir_edit_screen(ps, info, "CHANGE THIS", &raw_server) == 1){
+
+ free_ldap_server_info(&info);
+ info = break_up_ldap_server(raw_server);
+
+ if(lval && lval[(*cl)->varmem] &&
+ strcmp(lval[(*cl)->varmem], raw_server) == 0)
+ q_status_message(SM_ORDER, 0, 3, _("No change, cancelled"));
+ else if(!(info && info->serv && *info->serv))
+ q_status_message(SM_ORDER, 0, 3,
+ _("Change cancelled, use Delete if you want to remove this server"));
+ else{
+ char *subtitle;
+ int i, cnt;
+ char **new_list;
+
+ for(cnt = 0; lval && lval[cnt]; cnt++)
+ ;
+
+ new_list = (char **)fs_get((cnt + 1) * sizeof(char *));
+
+ for(i = 0; i < (*cl)->varmem; i++)
+ new_list[i] = cpystr(lval[i]);
+
+ new_list[(*cl)->varmem] = raw_server;
+ raw_server = NULL;
+
+ for(i = (*cl)->varmem + 1; i < cnt; i++)
+ new_list[i] = cpystr(lval[i]);
+
+ new_list[cnt] = NULL;
+ set_variable_list(V_LDAP_SERVERS, new_list, FALSE, ew);
+ free_list_array(&new_list);
+ set_current_val((*cl)->var, TRUE, FALSE);
+
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ (*cl)->value = cpystr((info->nick && *info->nick) ? info->nick
+ : info->serv);
+
+ if((*cl)->next->value)
+ fs_give((void **)&(*cl)->next->value);
+
+ subtitle = (char *)fs_get((((info && info->serv && *info->serv)
+ ? strlen(info->serv)
+ : 3) +
+ strlen(_(dserv)) + 15) *
+ sizeof(char));
+ if(info && info->port >= 0)
+ snprintf(subtitle, sizeof(subtitle), "%s%s:%d",
+ _(dserv),
+ (info && info->serv && *info->serv) ? info->serv : "<?>",
+ info->port);
+ else
+ snprintf(subtitle, sizeof(subtitle), "%s%s",
+ _(dserv),
+ (info && info->serv && *info->serv) ? info->serv : "<?>");
+
+ subtitle[sizeof(subtitle)-1] = '\0';
+
+ (*cl)->next->value = subtitle;
+
+ fix_side_effects(ps, (*cl)->var, 0);
+ write_pinerc(ps, ew, WRP_NONE);
+ }
+ }
+
+ free_ldap_server_info(&info);
+ if(raw_server)
+ fs_give((void **)&raw_server);
+}
+
+
+#define LDAP_F_IMPL 0
+#define LDAP_F_RHS 1
+#define LDAP_F_REF 2
+#define LDAP_F_NOSUB 3
+#define LDAP_F_TLS 4
+#define LDAP_F_TLSMUST 5
+bitmap_t ldap_option_list;
+struct variable *ldap_srch_rule_ptr;
+
+/*
+ * Gives user screen to edit config values for ldap server.
+ *
+ * Args ps -- pine struct
+ * def -- default values to start with
+ * title -- part of title at top of screen
+ * raw_server -- This is the returned item, allocated here and freed by caller.
+ *
+ * Returns: 0 if no change
+ * 1 if user requested a change
+ * (change is stored in raw_server and hasn't been acted upon yet)
+ * 10 user says abort
+ */
+int
+dir_edit_screen(struct pine *ps, LDAP_SERV_S *def, char *title, char **raw_server)
+{
+ OPT_SCREEN_S screen, *saved_screen;
+ CONF_S *ctmp = NULL, *ctmpb, *first_line = NULL;
+ char tmp[MAXPATH+1], custom_scope[MAXPATH], **apval;
+ int rv, i, j, lv, indent, rindent;
+ NAMEVAL_S *f;
+ struct variable server_var, base_var, binddn_var, port_var, nick_var,
+ srch_type_var, srch_rule_var, time_var,
+ size_var, mailattr_var, cnattr_var,
+ snattr_var, gnattr_var, cust_var,
+ opt_var, *v, *varlist[21];
+ char *server = NULL, *base = NULL, *port = NULL, *nick = NULL,
+ *srch_type = NULL, *srch_rule = NULL, *ttime = NULL,
+ *c_s_f = "custom-search-filter", *binddn = NULL,
+ *ssize = NULL, *mailattr = NULL, *cnattr = NULL,
+ *snattr = NULL, *gnattr = NULL, *cust = NULL;
+
+ /*
+ * We edit by making a nested call to conf_scroll_screen.
+ * We use some fake struct variables to get back the results in, and
+ * so we can use the existing tools from the config screen.
+ */
+
+ custom_scope[0] = '\0';
+
+ varlist[j = 0] = &server_var;
+ varlist[++j] = &base_var;
+ varlist[++j] = &port_var;
+ varlist[++j] = &binddn_var;
+ varlist[++j] = &nick_var;
+ varlist[++j] = &srch_type_var;
+ varlist[++j] = &srch_rule_var;
+ varlist[++j] = &time_var;
+ varlist[++j] = &size_var;
+ varlist[++j] = &mailattr_var;
+ varlist[++j] = &cnattr_var;
+ varlist[++j] = &snattr_var;
+ varlist[++j] = &gnattr_var;
+ varlist[++j] = &cust_var;
+ varlist[++j] = &opt_var;
+ varlist[++j] = NULL;
+ for(j = 0; varlist[j]; j++)
+ memset(varlist[j], 0, sizeof(struct variable));
+
+ server_var.name = cpystr("ldap-server");
+ server_var.is_used = 1;
+ server_var.is_user = 1;
+ apval = APVAL(&server_var, ew);
+ *apval = (def && def->serv && def->serv[0]) ? cpystr(def->serv) : NULL;
+ set_current_val(&server_var, FALSE, FALSE);
+
+ base_var.name = cpystr("search-base");
+ base_var.is_used = 1;
+ base_var.is_user = 1;
+ apval = APVAL(&base_var, ew);
+ *apval = (def && def->base && def->base[0]) ? cpystr(def->base) : NULL;
+ set_current_val(&base_var, FALSE, FALSE);
+
+ port_var.name = cpystr("port");
+ port_var.is_used = 1;
+ port_var.is_user = 1;
+ if(def && def->port >= 0){
+ apval = APVAL(&port_var, ew);
+ *apval = cpystr(int2string(def->port));
+ }
+
+ port_var.global_val.p = cpystr(int2string(LDAP_PORT));
+ set_current_val(&port_var, FALSE, FALSE);
+
+ binddn_var.name = cpystr("bind-dn");
+ binddn_var.is_used = 1;
+ binddn_var.is_user = 1;
+ apval = APVAL(&binddn_var, ew);
+ *apval = (def && def->binddn && def->binddn[0]) ? cpystr(def->binddn) : NULL;
+ set_current_val(&binddn_var, FALSE, FALSE);
+
+ nick_var.name = cpystr("nickname");
+ nick_var.is_used = 1;
+ nick_var.is_user = 1;
+ apval = APVAL(&nick_var, ew);
+ *apval = (def && def->nick && def->nick[0]) ? cpystr(def->nick) : NULL;
+ set_current_val(&nick_var, FALSE, FALSE);
+
+ srch_type_var.name = cpystr("search-type");
+ srch_type_var.is_used = 1;
+ srch_type_var.is_user = 1;
+ apval = APVAL(&srch_type_var, ew);
+ *apval = (f=ldap_search_types(def ? def->type : -1))
+ ? cpystr(f->name) : NULL;
+ srch_type_var.global_val.p =
+ (f=ldap_search_types(DEF_LDAP_TYPE)) ? cpystr(f->name) : NULL;
+ set_current_val(&srch_type_var, FALSE, FALSE);
+
+ ldap_srch_rule_ptr = &srch_rule_var; /* so radiobuttons can tell */
+ srch_rule_var.name = cpystr("search-rule");
+ srch_rule_var.is_used = 1;
+ srch_rule_var.is_user = 1;
+ apval = APVAL(&srch_rule_var, ew);
+ *apval = (f=ldap_search_rules(def ? def->srch : -1))
+ ? cpystr(f->name) : NULL;
+ srch_rule_var.global_val.p =
+ (f=ldap_search_rules(DEF_LDAP_SRCH)) ? cpystr(f->name) : NULL;
+ set_current_val(&srch_rule_var, FALSE, FALSE);
+
+ time_var.name = cpystr("timelimit");
+ time_var.is_used = 1;
+ time_var.is_user = 1;
+ if(def && def->time >= 0){
+ apval = APVAL(&time_var, ew);
+ *apval = cpystr(int2string(def->time));
+ }
+
+ time_var.global_val.p = cpystr(int2string(DEF_LDAP_TIME));
+ set_current_val(&time_var, FALSE, FALSE);
+
+ size_var.name = cpystr("sizelimit");
+ size_var.is_used = 1;
+ size_var.is_user = 1;
+ if(def && def->size >= 0){
+ apval = APVAL(&size_var, ew);
+ *apval = cpystr(int2string(def->size));
+ }
+
+ size_var.global_val.p = cpystr(int2string(DEF_LDAP_SIZE));
+ set_current_val(&size_var, FALSE, FALSE);
+
+ mailattr_var.name = cpystr("email-attribute");
+ mailattr_var.is_used = 1;
+ mailattr_var.is_user = 1;
+ apval = APVAL(&mailattr_var, ew);
+ *apval = (def && def->mailattr && def->mailattr[0])
+ ? cpystr(def->mailattr) : NULL;
+ mailattr_var.global_val.p = cpystr(DEF_LDAP_MAILATTR);
+ set_current_val(&mailattr_var, FALSE, FALSE);
+
+ cnattr_var.name = cpystr("name-attribute");
+ cnattr_var.is_used = 1;
+ cnattr_var.is_user = 1;
+ apval = APVAL(&cnattr_var, ew);
+ *apval = (def && def->cnattr && def->cnattr[0])
+ ? cpystr(def->cnattr) : NULL;
+ cnattr_var.global_val.p = cpystr(DEF_LDAP_CNATTR);
+ set_current_val(&cnattr_var, FALSE, FALSE);
+
+ snattr_var.name = cpystr("surname-attribute");
+ snattr_var.is_used = 1;
+ snattr_var.is_user = 1;
+ apval = APVAL(&snattr_var, ew);
+ *apval = (def && def->snattr && def->snattr[0])
+ ? cpystr(def->snattr) : NULL;
+ snattr_var.global_val.p = cpystr(DEF_LDAP_SNATTR);
+ set_current_val(&snattr_var, FALSE, FALSE);
+
+ gnattr_var.name = cpystr("givenname-attribute");
+ gnattr_var.is_used = 1;
+ gnattr_var.is_user = 1;
+ apval = APVAL(&gnattr_var, ew);
+ *apval = (def && def->gnattr && def->gnattr[0])
+ ? cpystr(def->gnattr) : NULL;
+ gnattr_var.global_val.p = cpystr(DEF_LDAP_GNATTR);
+ set_current_val(&gnattr_var, FALSE, FALSE);
+
+ cust_var.name = cpystr(c_s_f);
+ cust_var.is_used = 1;
+ cust_var.is_user = 1;
+ apval = APVAL(&cust_var, ew);
+ *apval = (def && def->cust && def->cust[0]) ? cpystr(def->cust) : NULL;
+ set_current_val(&cust_var, FALSE, FALSE);
+
+ /* TRANSLATORS: Features is a section title in the LDAP configuration screen. Following
+ this are a list of features or options that can be turned on or off. */
+ opt_var.name = cpystr(_("Features"));
+ opt_var.is_used = 1;
+ opt_var.is_user = 1;
+ opt_var.is_list = 1;
+ clrbitmap(ldap_option_list);
+ if(def && def->impl)
+ setbitn(LDAP_F_IMPL, ldap_option_list);
+ if(def && def->rhs)
+ setbitn(LDAP_F_RHS, ldap_option_list);
+ if(def && def->ref)
+ setbitn(LDAP_F_REF, ldap_option_list);
+ if(def && def->nosub)
+ setbitn(LDAP_F_NOSUB, ldap_option_list);
+ if(def && def->tls)
+ setbitn(LDAP_F_TLS, ldap_option_list);
+ if(def && def->tlsmust)
+ setbitn(LDAP_F_TLSMUST, ldap_option_list);
+
+ /* save the old opt_screen before calling scroll screen again */
+ saved_screen = opt_screen;
+
+ indent = utf8_width(c_s_f) + 3;
+ rindent = 12;
+
+ /* Server */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR LDAP SERVER");
+ ctmp->var = &server_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_server;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,server_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ first_line = ctmp;
+
+ /* Search Base */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SERVER SEARCH BASE");
+ ctmp->var = &base_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_base;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,base_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Port */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR PORT NUMBER");
+ ctmp->var = &port_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_port;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,port_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ ctmp->flags |= CF_NUMBER;
+
+ /* Bind DN (DN to bind to if needed) */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SERVER BIND DN");
+ ctmp->var = &binddn_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_binddn;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,binddn_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Nickname */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SERVER NICKNAME");
+ ctmp->var = &nick_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_nick;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,nick_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Options */
+ new_confline(&ctmp);
+ ctmp->var = &opt_var;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", opt_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = ldap_checkbox_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Feature Name");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = ldap_checkbox_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose);
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = ldap_feature_list(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = ldap_feature_list(i)); i++){
+ new_confline(&ctmp);
+ ctmp->var = &opt_var;
+ ctmp->help_title= _("HELP FOR LDAP FEATURES");
+ ctmp->varnamep = ctmpb;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ switch(i){
+ case LDAP_F_IMPL:
+ ctmp->help = h_config_ldap_opts_impl;
+ break;
+ case LDAP_F_RHS:
+ ctmp->help = h_config_ldap_opts_rhs;
+ break;
+ case LDAP_F_REF:
+ ctmp->help = h_config_ldap_opts_ref;
+ break;
+ case LDAP_F_NOSUB:
+ ctmp->help = h_config_ldap_opts_nosub;
+ break;
+ case LDAP_F_TLS:
+ ctmp->help = h_config_ldap_opts_tls;
+ break;
+ case LDAP_F_TLSMUST:
+ ctmp->help = h_config_ldap_opts_tlsmust;
+ break;
+ }
+
+ ctmp->tool = ldap_checkbox_tool;
+ ctmp->valoffset = rindent;
+ ctmp->varmem = i;
+ utf8_snprintf(tmp, sizeof(tmp), "[%c] %-*.*w",
+ bitnset(f->value, ldap_option_list) ? 'X' : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Search Type */
+ new_confline(&ctmp);
+ ctmp->var = &srch_type_var;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", srch_type_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Rule Values");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = ldap_radiobutton_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose);
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = ldap_search_types(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = ldap_search_types(i)); i++){
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SEARCH TYPE");
+ ctmp->var = &srch_type_var;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_ldap_searchtypes;
+ ctmp->varmem = i;
+ ctmp->tool = ldap_radiobutton_tool;
+ ctmp->varnamep = ctmpb;
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w", (((!def || def->type == -1) &&
+ f->value == DEF_LDAP_TYPE) ||
+ (def && f->value == def->type))
+ ? R_SELD : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+ ctmp->varname = cpystr("");
+
+ /* Search Rule */
+ new_confline(&ctmp);
+ ctmp->var = &srch_rule_var;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", srch_rule_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ /* Search Rule */
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Rule Values");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = ldap_radiobutton_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose);
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = ldap_search_rules(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = ldap_search_rules(i)); i++){
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SEARCH RULE");
+ ctmp->var = &srch_rule_var;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_ldap_searchrules;
+ ctmp->varmem = i;
+ ctmp->tool = ldap_radiobutton_tool;
+ ctmp->varnamep = ctmpb;
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w", (((!def || def->srch == -1) &&
+ f->value == DEF_LDAP_SRCH) ||
+ (def && f->value == def->srch))
+ ? R_SELD : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+ ctmp->varname = cpystr("");
+
+ /* Email attribute name */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR EMAIL ATTRIBUTE NAME");
+ ctmp->var = &mailattr_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_email_attr;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,mailattr_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Name attribute name */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR NAME ATTRIBUTE NAME");
+ ctmp->var = &cnattr_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_cn_attr;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,cnattr_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Surname attribute name */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SURNAME ATTRIBUTE NAME");
+ ctmp->var = &snattr_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_sn_attr;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,snattr_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Givenname attribute name */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR GIVEN NAME ATTRIBUTE NAME");
+ ctmp->var = &gnattr_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_gn_attr;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,gnattr_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+ ctmp->varname = cpystr("");
+
+ /* Time limit */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SERVER TIMELIMIT");
+ ctmp->var = &time_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_time;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,time_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ ctmp->flags |= CF_NUMBER;
+
+ /* Size limit */
+ new_confline(&ctmp);
+ ctmp->var = &size_var;
+ ctmp->help_title= _("HELP FOR SERVER SIZELIMIT");
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_size;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,size_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ ctmp->flags |= CF_NUMBER;
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Custom Search Filter */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR CUSTOM SEARCH FILTER");
+ ctmp->var = &cust_var;
+ ctmp->valoffset = indent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_ldap_cust;
+ ctmp->tool = dir_edit_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3,indent-3,cust_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+
+ snprintf(tmp, sizeof(tmp), "%s DIRECTORY SERVER", title);
+ tmp[sizeof(tmp)-1] = '\0';
+ memset(&screen, 0, sizeof(screen));
+ screen.ro_warning = saved_screen ? saved_screen->deferred_ro_warning : 0;
+ /* TRANSLATORS: Print something1 using something2.
+ servers is something1 */
+ rv = conf_scroll_screen(ps, &screen, first_line, tmp, _("servers"), 0);
+
+ /*
+ * Now look at the fake variables and extract the information we
+ * want from them.
+ */
+
+ if(rv == 1 && raw_server){
+ char dir_tmp[2200], *p;
+ int portval = -1, timeval = -1, sizeval = -1;
+
+ apval = APVAL(&server_var, ew);
+ server = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&base_var, ew);
+ base = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&port_var, ew);
+ port = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&binddn_var, ew);
+ binddn = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&nick_var, ew);
+ nick = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&srch_type_var, ew);
+ srch_type = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&srch_rule_var, ew);
+ srch_rule = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&time_var, ew);
+ ttime = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&size_var, ew);
+ ssize = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&cust_var, ew);
+ cust = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&mailattr_var, ew);
+ mailattr = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&snattr_var, ew);
+ snattr = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&gnattr_var, ew);
+ gnattr = *apval;
+ *apval = NULL;
+
+ apval = APVAL(&cnattr_var, ew);
+ cnattr = *apval;
+ *apval = NULL;
+
+ if(server)
+ removing_leading_and_trailing_white_space(server);
+
+ if(base){
+ removing_leading_and_trailing_white_space(base);
+ (void)removing_double_quotes(base);
+ p = add_backslash_escapes(base);
+ fs_give((void **)&base);
+ base = p;
+ }
+
+ if(port){
+ removing_leading_and_trailing_white_space(port);
+ if(*port)
+ portval = atoi(port);
+ }
+
+ if(binddn){
+ removing_leading_and_trailing_white_space(binddn);
+ (void)removing_double_quotes(binddn);
+ p = add_backslash_escapes(binddn);
+ fs_give((void **)&binddn);
+ binddn = p;
+ }
+
+ if(nick){
+ removing_leading_and_trailing_white_space(nick);
+ (void)removing_double_quotes(nick);
+ p = add_backslash_escapes(nick);
+ fs_give((void **)&nick);
+ nick = p;
+ }
+
+ if(ttime){
+ removing_leading_and_trailing_white_space(ttime);
+ if(*ttime)
+ timeval = atoi(ttime);
+ }
+
+ if(ssize){
+ removing_leading_and_trailing_white_space(ssize);
+ if(*ssize)
+ sizeval = atoi(ssize);
+ }
+
+ if(cust){
+ removing_leading_and_trailing_white_space(cust);
+ p = add_backslash_escapes(cust);
+ fs_give((void **)&cust);
+ cust = p;
+ }
+
+ if(mailattr){
+ removing_leading_and_trailing_white_space(mailattr);
+ p = add_backslash_escapes(mailattr);
+ fs_give((void **)&mailattr);
+ mailattr = p;
+ }
+
+ if(snattr){
+ removing_leading_and_trailing_white_space(snattr);
+ p = add_backslash_escapes(snattr);
+ fs_give((void **)&snattr);
+ snattr = p;
+ }
+
+ if(gnattr){
+ removing_leading_and_trailing_white_space(gnattr);
+ p = add_backslash_escapes(gnattr);
+ fs_give((void **)&gnattr);
+ gnattr = p;
+ }
+
+ if(cnattr){
+ removing_leading_and_trailing_white_space(cnattr);
+ p = add_backslash_escapes(cnattr);
+ fs_give((void **)&cnattr);
+ cnattr = p;
+ }
+
+ /*
+ * Don't allow user to edit scope but if one is present then we
+ * leave it (so they could edit it by hand).
+ */
+ if(def && def->scope != -1 && def->scope != DEF_LDAP_SCOPE){
+ NAMEVAL_S *v;
+
+ v = ldap_search_scope(def->scope);
+ if(v){
+ snprintf(custom_scope, sizeof(custom_scope), "/scope=%s", v->name);
+ custom_scope[sizeof(custom_scope)-1] = '\0';
+ }
+ }
+
+ snprintf(dir_tmp, sizeof(dir_tmp), "%s%s%s \"/base=%s/binddn=%s/impl=%d/rhs=%d/ref=%d/nosub=%d/tls=%d/tlsm=%d/type=%s/srch=%s%s/time=%s/size=%s/cust=%s/nick=%s/matr=%s/catr=%s/satr=%s/gatr=%s\"",
+ server ? server : "",
+ (portval >= 0 && port && *port) ? ":" : "",
+ (portval >= 0 && port && *port) ? port : "",
+ base ? base : "",
+ binddn ? binddn : "",
+ bitnset(LDAP_F_IMPL, ldap_option_list) ? 1 : 0,
+ bitnset(LDAP_F_RHS, ldap_option_list) ? 1 : 0,
+ bitnset(LDAP_F_REF, ldap_option_list) ? 1 : 0,
+ bitnset(LDAP_F_NOSUB, ldap_option_list) ? 1 : 0,
+ bitnset(LDAP_F_TLS, ldap_option_list) ? 1 : 0,
+ bitnset(LDAP_F_TLSMUST, ldap_option_list) ? 1 : 0,
+ srch_type ? srch_type : "",
+ srch_rule ? srch_rule : "",
+ custom_scope,
+ (timeval >= 0 && ttime && *ttime) ? ttime : "",
+ (sizeval >= 0 && ssize && *ssize) ? ssize : "",
+ cust ? cust : "",
+ nick ? nick : "",
+ mailattr ? mailattr : "",
+ cnattr ? cnattr : "",
+ snattr ? snattr : "",
+ gnattr ? gnattr : "");
+ dir_tmp[sizeof(dir_tmp)-1] = '\0';
+
+ *raw_server = cpystr(dir_tmp);
+ }
+
+ for(j = 0; varlist[j]; j++){
+ v = varlist[j];
+ if(v->current_val.p)
+ fs_give((void **)&v->current_val.p);
+ if(v->global_val.p)
+ fs_give((void **)&v->global_val.p);
+ if(v->main_user_val.p)
+ fs_give((void **)&v->main_user_val.p);
+ if(v->post_user_val.p)
+ fs_give((void **)&v->post_user_val.p);
+ if(v->name)
+ fs_give((void **)&v->name);
+ }
+
+ if(server)
+ fs_give((void **)&server);
+ if(base)
+ fs_give((void **)&base);
+ if(port)
+ fs_give((void **)&port);
+ if(binddn)
+ fs_give((void **)&binddn);
+ if(nick)
+ fs_give((void **)&nick);
+ if(srch_type)
+ fs_give((void **)&srch_type);
+ if(srch_rule)
+ fs_give((void **)&srch_rule);
+ if(ttime)
+ fs_give((void **)&ttime);
+ if(ssize)
+ fs_give((void **)&ssize);
+ if(mailattr)
+ fs_give((void **)&mailattr);
+ if(cnattr)
+ fs_give((void **)&cnattr);
+ if(snattr)
+ fs_give((void **)&snattr);
+ if(gnattr)
+ fs_give((void **)&gnattr);
+ if(cust)
+ fs_give((void **)&cust);
+
+ opt_screen = saved_screen;
+ ps->mangled_screen = 1;
+ return(rv);
+}
+
+
+/*
+ * Just calls text_tool except for intercepting MC_EXIT.
+ */
+int
+dir_edit_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ if(cmd == MC_EXIT)
+ return(config_exit_cmd(flags));
+ else
+ return(text_tool(ps, cmd, cl, flags));
+}
+
+
+/*
+ * Delete LDAP directory entry
+ */
+void
+dir_config_del(struct pine *ps, CONF_S **cl)
+{
+ char prompt[81];
+ int rv = 0, i;
+
+ if(fixed_var((*cl)->var, NULL, NULL)){
+ if((*cl)->var->post_user_val.l || (*cl)->var->main_user_val.l){
+ if(want_to(_("Delete (unused) directory servers "),
+ 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ rv = 1;
+ delete_user_vals((*cl)->var);
+ }
+ }
+ else
+ q_status_message(SM_ORDER, 3, 3,
+ _("Can't delete sys-admin defined value"));
+ }
+ else{
+ int cnt, ans = 0, no_ex;
+ char **new_list, **lval, **nelval;
+
+ no_ex = (ps_global->ew_for_except_vars == Main);
+
+ /* This can't happen, intercepted at caller by first_one case */
+ nelval = no_ex ? (*cl)->var->current_val.l : LVAL((*cl)->var, ew);
+ lval = LVAL((*cl)->var, ew);
+ if(lval && lval[0] && lval[0][0] == '\0')
+ ans = 'r';
+
+ /* how many servers defined? */
+ for(cnt = 0; nelval[cnt]; cnt++)
+ ;
+
+ /*
+ * If using default and there is more than one in list, ask if user
+ * wants to ignore them all or delete just this one. If just this
+ * one, copy rest to user_val. If ignore all, copy "" to user_val
+ * to override.
+ */
+ if(!lval && cnt > 1){
+ static ESCKEY_S opts[] = {
+ {'i', 'i', "I", N_("Ignore All")},
+ {'r', 'r', "R", N_("Remove One")},
+ {-1, 0, NULL, NULL}};
+ ans = radio_buttons(
+ _("Ignore all default directory servers or just remove this one ? "),
+ -FOOTER_ROWS(ps), opts, 'i', 'x',
+ h_ab_del_dir_ignore, RB_NORM);
+ }
+
+ if(ans == 0){
+ snprintf(prompt, sizeof(prompt), _("Really delete %s \"%s\" from directory servers "),
+ ((*cl)->value && *(*cl)->value)
+ ? "server"
+ : "item",
+ ((*cl)->value && *(*cl)->value)
+ ? (*cl)->value
+ : int2string((*cl)->varmem + 1));
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+
+ ps->mangled_footer = 1;
+ if(ans == 'i'){
+ rv = ps->mangled_body = 1;
+
+ /*
+ * Ignore all of default by adding an empty string. Make it
+ * look just like there are no servers defined.
+ */
+
+ new_list = (char **)fs_get((1 + 1) * sizeof(char *));
+ new_list[0] = cpystr("");
+ new_list[1] = NULL;
+ set_variable_list(V_LDAP_SERVERS, new_list, FALSE, ew);
+ free_list_array(&new_list);
+ *cl = first_confline(*cl);
+ free_conflines(cl);
+ opt_screen->top_line = NULL;
+
+ add_ldap_fake_first_server(ps, cl, &ps->vars[V_LDAP_SERVERS],
+ &dir_conf_km, h_direct_config,
+ dir_config_tool);
+ }
+ else if(ans == 'r' ||
+ (ans != 'x' &&
+ want_to(prompt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y')){
+ CONF_S *cp;
+ char **servers;
+ int move_top = 0, this_one, revert_to_default,
+ default_there_to_revert_to;
+
+ /*
+ * Remove one from current list.
+ */
+
+ rv = ps->mangled_body = 1;
+
+ this_one = (*cl)->varmem;
+
+ /* might have to re-adjust screen to see new current */
+ move_top = (this_one > 0) &&
+ (this_one == cnt - 1) &&
+ (((*cl) == opt_screen->top_line) ||
+ ((*cl)->prev == opt_screen->top_line) ||
+ ((*cl)->prev->prev == opt_screen->top_line));
+
+ /*
+ * If this is last one and there is a default available, revert
+ * to it.
+ */
+ revert_to_default = ((cnt == 1) && lval);
+ if(cnt > 1){
+ new_list = (char **)fs_get((cnt + 1) * sizeof(char *));
+ for(i = 0; i < this_one; i++)
+ new_list[i] = cpystr(nelval[i]);
+
+ for(i = this_one; i < cnt; i++)
+ new_list[i] = cpystr(nelval[i+1]);
+
+ set_variable_list(V_LDAP_SERVERS, new_list, FALSE, ew);
+ free_list_array(&new_list);
+ }
+ else if(revert_to_default){
+ char ***alval;
+
+ alval = ALVAL((*cl)->var, ew);
+ if(alval && *alval)
+ free_list_array(alval);
+ }
+ else{
+ /* cnt is one and we want to hide default */
+ new_list = (char **)fs_get((1 + 1) * sizeof(char *));
+ new_list[0] = cpystr("");
+ new_list[1] = NULL;
+ set_variable_list(V_LDAP_SERVERS, new_list, FALSE, ew);
+ free_list_array(&new_list);
+ }
+
+ if(cnt == 1){ /* delete display line for this_one */
+ if(revert_to_default){
+ servers = (*cl)->var->global_val.l;
+ default_there_to_revert_to = (servers != NULL);
+ }
+
+ *cl = first_confline(*cl);
+ free_conflines(cl);
+ opt_screen->top_line = NULL;
+ if(revert_to_default && default_there_to_revert_to){
+ CONF_S *first_line = NULL;
+
+ q_status_message(SM_ORDER, 0, 3,
+ _("Reverting to default directory server"));
+ dir_init_display(ps, cl, servers,
+ &ps->vars[V_LDAP_SERVERS], &first_line);
+ *cl = first_line;
+ }
+ else{
+ add_ldap_fake_first_server(ps, cl,
+ &ps->vars[V_LDAP_SERVERS],
+ &dir_conf_km, h_direct_config,
+ dir_config_tool);
+ }
+ }
+ else if(this_one == cnt - 1){ /* deleted last one */
+ /* back up and delete it */
+ *cl = (*cl)->prev;
+ free_conflines(&(*cl)->next);
+ /* now back up to first line of this server */
+ *cl = (*cl)->prev->prev;
+ if(move_top)
+ opt_screen->top_line = *cl;
+ }
+ else{ /* deleted one out of the middle */
+ if(*cl == opt_screen->top_line)
+ opt_screen->top_line = (*cl)->next->next->next;
+
+ cp = *cl;
+ *cl = (*cl)->next; /* move to next line, then */
+ snip_confline(&cp); /* snip 1st deleted line */
+ cp = *cl;
+ *cl = (*cl)->next; /* move to next line, then */
+ snip_confline(&cp); /* snip 2nd deleted line */
+ cp = *cl;
+ *cl = (*cl)->next; /* move to next line, then */
+ snip_confline(&cp); /* snip 3rd deleted line */
+ /* adjust varmems */
+ for(cp = *cl; cp; cp = cp->next)
+ cp->varmem--;
+ }
+ }
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Server not deleted"));
+ }
+
+ if(rv == 1){
+ set_current_val((*cl)->var, TRUE, FALSE);
+ fix_side_effects(ps, (*cl)->var, 0);
+ write_pinerc(ps, ew, WRP_NONE);
+ }
+}
+
+
+/*
+ * Utility routine to help set up display
+ */
+void
+add_ldap_fake_first_server(struct pine *ps, CONF_S **ctmp, struct variable *var,
+ struct key_menu *km, HelpType help,
+ int (*tool)(struct pine *, int, CONF_S **, unsigned))
+{
+ new_confline(ctmp);
+ (*ctmp)->help_title= _("HELP FOR DIRECTORY SERVER CONFIGURATION");
+ (*ctmp)->value = cpystr(ADD_FIRST_LDAP_SERVER);
+ (*ctmp)->var = var;
+ (*ctmp)->varmem = 0;
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = help;
+ (*ctmp)->tool = tool;
+ (*ctmp)->valoffset = 2;
+}
+
+
+/*
+ * Add an ldap server to the display list.
+ *
+ * Args before -- Insert it before current, else append it after.
+ */
+void
+add_ldap_server_to_display(struct pine *ps, CONF_S **ctmp, char *serv, char *subtitle,
+ struct variable *var, int member, struct key_menu *km,
+ HelpType help,
+ int (*tool)(struct pine *, int, CONF_S **, unsigned),
+ int before, CONF_S **first_line)
+{
+ new_confline(ctmp);
+ if(first_line)
+ *first_line = *ctmp;
+
+ if(before){
+ /*
+ * New_confline appends ctmp after old current instead of inserting
+ * it, so we have to adjust. We have
+ * <- a <-> b <-> p <-> c -> and want <- a <-> p <-> b <-> c ->
+ */
+
+ CONF_S *a, *b, *c, *p;
+
+ p = *ctmp;
+ b = (*ctmp)->prev;
+ c = (*ctmp)->next;
+ a = b ? b->prev : NULL;
+ if(a)
+ a->next = p;
+
+ if(b){
+ b->prev = p;
+ b->next = c;
+ }
+
+ if(c)
+ c->prev = b;
+
+ p->prev = a;
+ p->next = b;
+ }
+
+ (*ctmp)->help_title= _("HELP FOR DIRECTORY SERVER CONFIGURATION");
+ (*ctmp)->value = serv;
+ (*ctmp)->var = var;
+ (*ctmp)->varmem = member;
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = help;
+ (*ctmp)->tool = tool;
+ (*ctmp)->flags |= CF_STARTITEM;
+ (*ctmp)->valoffset = 4;
+
+ new_confline(ctmp);
+ (*ctmp)->value = subtitle;
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = help;
+ (*ctmp)->tool = tool;
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->valoffset = 8;
+
+ new_confline(ctmp);
+ (*ctmp)->keymenu = km;
+ (*ctmp)->help = help;
+ (*ctmp)->tool = tool;
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+ (*ctmp)->valoffset = 0;
+}
+
+
+/*
+ * ldap option list manipulation tool
+ *
+ *
+ * returns: -1 on unrecognized cmd, 0 if no change, 1 if change
+ */
+int
+ldap_checkbox_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+
+ switch(cmd){
+ case MC_TOGGLE: /* mark/unmark option */
+ rv = 1;
+ toggle_ldap_option_bit(ps, (*cl)->varmem, (*cl)->var, (*cl)->value);
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = config_exit_cmd(flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+void
+toggle_ldap_option_bit(struct pine *ps, int index, struct variable *var, char *value)
+{
+ NAMEVAL_S *f;
+
+ f = ldap_feature_list(index);
+
+ /* flip the bit */
+ if(bitnset(f->value, ldap_option_list))
+ clrbitn(f->value, ldap_option_list);
+ else
+ setbitn(f->value, ldap_option_list);
+
+ if(value)
+ value[1] = bitnset(f->value, ldap_option_list) ? 'X' : ' ';
+}
+
+
+NAMEVAL_S *
+ldap_feature_list(int index)
+{
+ static NAMEVAL_S ldap_feat_list[] = {
+ {"use-implicitly-from-composer", NULL, LDAP_F_IMPL},
+ {"lookup-addrbook-contents", NULL, LDAP_F_RHS},
+ {"save-search-criteria-not-result", NULL, LDAP_F_REF},
+ {"disable-ad-hoc-space-substitution", NULL, LDAP_F_NOSUB},
+ {"attempt-tls-on-connection", NULL, LDAP_F_TLS},
+ {"require-tls-on-connection", NULL, LDAP_F_TLSMUST}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(ldap_feat_list)/sizeof(ldap_feat_list[0])))
+ ? &ldap_feat_list[index] : NULL);
+}
+
+
+/*
+ * simple radio-button style variable handler
+ */
+int
+ldap_radiobutton_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+ CONF_S *ctmp;
+ NAMEVAL_S *rule;
+ char **apval;
+
+ switch(cmd){
+ case MC_CHOICE : /* set/unset feature */
+
+ /* hunt backwards, turning off old values */
+ for(ctmp = *cl; ctmp && !(ctmp->flags & CF_NOSELECT) && !ctmp->varname;
+ ctmp = prev_confline(ctmp))
+ ctmp->value[1] = ' ';
+
+ /* hunt forwards, turning off old values */
+ for(ctmp = *cl; ctmp && !(ctmp->flags & CF_NOSELECT) && !ctmp->varname;
+ ctmp = next_confline(ctmp))
+ ctmp->value[1] = ' ';
+
+ /* turn on current value */
+ (*cl)->value[1] = R_SELD;
+
+ if((*cl)->var == ldap_srch_rule_ptr)
+ rule = ldap_search_rules((*cl)->varmem);
+ else
+ rule = ldap_search_types((*cl)->varmem);
+
+ apval = APVAL((*cl)->var, ew);
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(apval)
+ *apval = cpystr(rule->name);
+
+ ps->mangled_body = 1; /* BUG: redraw it all for now? */
+ rv = 1;
+
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = config_exit_cmd(flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+#endif /* ENABLE_LDAP */
diff --git a/alpine/ldapconf.h b/alpine/ldapconf.h
new file mode 100644
index 00000000..9dbe8e4d
--- /dev/null
+++ b/alpine/ldapconf.h
@@ -0,0 +1,34 @@
+/*
+ * $Id: ldapconf.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_LDAPCONF_INCLUDED
+#define PINE_LDAPCONF_INCLUDED
+
+
+#include "conftype.h"
+#include "../pith/ldap.h"
+#include "../pith/state.h"
+#include "../pith/bldaddr.h"
+
+
+/* exported protoypes */
+#ifdef ENABLE_LDAP
+void directory_config(struct pine *, int);
+int ldap_radiobutton_tool(struct pine *, int, CONF_S **, unsigned);
+#endif
+
+
+#endif /* PINE_LDAPCONF_INCLUDED */
diff --git a/alpine/listsel.c b/alpine/listsel.c
new file mode 100644
index 00000000..cb9e8812
--- /dev/null
+++ b/alpine/listsel.c
@@ -0,0 +1,332 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: listsel.c 918 2008-01-23 19:39:38Z hubert@u.washington.edu $";
+#endif
+/* ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+#include "headers.h"
+#include "listsel.h"
+#include "status.h"
+#include "confscroll.h"
+#include "../pith/state.h"
+
+
+/*
+ * Internal prototypes
+ */
+int select_from_list_tool(struct pine *, int, CONF_S **, unsigned);
+int select_from_list_tool_allow_noselections(struct pine *, int, CONF_S **, unsigned);
+
+
+/*
+ * This is intended to be a generic tool to select strings from a list
+ * of strings.
+ *
+ * Args lsel -- the items as well as the answer are contained in this list
+ * flags -- There is some inconsistent flags usage. Notice that the
+ * flag SFL_ALLOW_LISTMODE is a flag passed in the flags
+ * argument whereas the flag SFL_NOSELECT is a per item
+ * (that is, per LIST_SEL_S) flag.
+ * title -- passed to conf_scroll_screen
+ * pdesc -- passed to conf_scroll_screen
+ * help -- passed to conf_scroll_screen
+ * helptitle -- passed to conf_scroll_screen
+ *
+ * You have screen width - 4 columns to work with. If you want to overflow to
+ * a second (or third or fourth) line for an item just send another item
+ * in the list but with the SFL_NOSELECT flag set. Only the selectable lines
+ * will be highlighted, which is kind of a crock, but it looked like a lot
+ * of work to fix that.
+ *
+ * Returns 0 on successful choice
+ * -1 if cancelled
+ */
+int
+select_from_list_screen(LIST_SEL_S *lsel, long unsigned int flags, char *title,
+ char *pdesc, HelpType help, char *htitle,
+ LIST_SEL_S *starting_val)
+{
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ OPT_SCREEN_S screen;
+ int j, lv, ret = -1;
+ LIST_SEL_S *p;
+ char *display;
+ size_t l;
+ ScreenMode listmode = SingleMode;
+ int (*tool)(struct pine *, int, CONF_S **, unsigned);
+
+ if(!lsel)
+ return(ret);
+
+ /* find longest value's length */
+ for(lv = 0, p = lsel; p; p = p->next){
+ if(!(p->flags & SFL_NOSELECT)){
+ display = p->display_item ? p->display_item :
+ p->item ? p->item : "";
+ if(lv < (j = utf8_width(display)))
+ lv = j;
+ }
+ }
+
+ lv = MIN(lv, ps_global->ttyo->screen_cols - 4);
+
+ tool = (flags & SFL_CTRLC) ? select_from_list_tool_allow_noselections
+ : select_from_list_tool;
+
+ /*
+ * Convert the passed in list to conf_scroll lines.
+ */
+
+ if(flags & SFL_ALLOW_LISTMODE){
+
+ if(flags & SFL_ONLY_LISTMODE) {assert(flags & SFL_STARTIN_LISTMODE);}
+
+ for(p = lsel; p; p = p->next){
+
+ display = p->display_item ? p->display_item :
+ p->item ? p->item : "";
+ new_confline(&ctmp);
+ if(!first_line && !(p->flags & SFL_NOSELECT))
+ first_line = ctmp;
+ if(!first_line && !(p->flags & SFL_NOSELECT))
+ if(!starting_val || (starting_val == p))
+ first_line = ctmp;
+
+ /* generous allocation */
+ l = lv + 4 + strlen(display);
+ ctmp->value = (char *) fs_get((l + 1) * sizeof(char));
+ utf8_snprintf(ctmp->value, l+1, " %-*.*w", lv, lv, display);
+ ctmp->value[l] = '\0';
+
+ ctmp->d.l.lsel = p;
+ ctmp->d.l.listmode = &listmode;
+ if(flags & SFL_ONLY_LISTMODE){
+ if(flags & SFL_CTRLC)
+ ctmp->keymenu = &sel_from_list_olm_ctrlc;
+ else
+ ctmp->keymenu = &sel_from_list_olm;
+ }
+ else{
+ if(flags & SFL_CTRLC)
+ ctmp->keymenu = &sel_from_list_sm_ctrlc;
+ else
+ ctmp->keymenu = &sel_from_list_sm;
+ }
+
+ ctmp->help = help;
+ ctmp->help_title = htitle;
+ ctmp->tool = tool;
+ ctmp->flags = CF_STARTITEM |
+ ((p->flags & SFL_NOSELECT) ? CF_NOSELECT : 0);
+ }
+ }
+ else{
+
+ assert(!(flags & SFL_ONLY_LISTMODE));
+ assert(!(flags & SFL_STARTIN_LISTMODE));
+
+ for(p = lsel; p; p = p->next){
+
+ display = p->display_item ? p->display_item :
+ p->item ? p->item : "";
+ new_confline(&ctmp);
+ if(!first_line && !(p->flags & SFL_NOSELECT))
+ if(!starting_val || (starting_val == p))
+ first_line = ctmp;
+
+ l = lv + strlen(display);
+ ctmp->value = (char *) fs_get((l + 1) * sizeof(char));
+ utf8_snprintf(ctmp->value, l+1, "%-*.*w", lv, lv, display);
+ ctmp->value[l] = '\0';
+
+ ctmp->d.l.lsel = p;
+ ctmp->d.l.listmode = &listmode;
+ if(flags & SFL_CTRLC)
+ ctmp->keymenu = &sel_from_list_ctrlc;
+ else
+ ctmp->keymenu = &sel_from_list;
+
+ ctmp->help = help;
+ ctmp->help_title = htitle;
+ ctmp->tool = tool;
+ ctmp->flags = CF_STARTITEM |
+ ((p->flags & SFL_NOSELECT) ? CF_NOSELECT : 0);
+ ctmp->valoffset = 4;
+ }
+ }
+
+ /* just convert to start in listmode after the fact, easier that way */
+ if(flags & SFL_STARTIN_LISTMODE){
+ listmode = ListMode;
+
+ for(ctmp = first_line; ctmp; ctmp = next_confline(ctmp))
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->value){
+ ctmp->value[0] = '[';
+ ctmp->value[1] = ctmp->d.l.lsel->selected ? 'X' : SPACE;
+ ctmp->value[2] = ']';
+ if(flags & SFL_ONLY_LISTMODE){
+ if(flags & SFL_CTRLC)
+ ctmp->keymenu = &sel_from_list_olm_ctrlc;
+ else
+ ctmp->keymenu = &sel_from_list_olm;
+ }
+ else{
+ if(flags & SFL_CTRLC)
+ ctmp->keymenu = &sel_from_list_lm_ctrlc;
+ else
+ ctmp->keymenu = &sel_from_list_lm;
+ }
+ }
+ }
+
+ memset(&screen, 0, sizeof(screen));
+ switch(conf_scroll_screen(ps_global, &screen, first_line, title, pdesc, 0)){
+ case 1:
+ ret = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ ps_global->mangled_screen = 1;
+ return(ret);
+}
+
+
+int
+select_from_list_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ CONF_S *ctmp;
+ int retval = 0;
+
+ switch(cmd){
+ case MC_SELECT :
+ if(*(*cl)->d.l.listmode == SingleMode){
+ (*cl)->d.l.lsel->selected = 1;
+ retval = 3;
+ }
+ else{
+ /* check if anything is selected */
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ for(; ctmp; ctmp = next_confline(ctmp))
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->d.l.lsel->selected){
+ retval = 3;
+ break;
+ }
+
+ if(retval == 0){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Nothing selected, use Exit to exit without a selection."));
+ }
+ }
+
+ break;
+
+ case MC_LISTMODE :
+ if(*(*cl)->d.l.listmode == SingleMode){
+ /*
+ * UnHide the checkboxes
+ */
+
+ *(*cl)->d.l.listmode = ListMode;
+
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ for(; ctmp; ctmp = next_confline(ctmp))
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->value){
+ ctmp->value[0] = '[';
+ ctmp->value[1] = ctmp->d.l.lsel->selected ? 'X' : SPACE;
+ ctmp->value[2] = ']';
+ ctmp->keymenu = &sel_from_list_lm;
+ }
+ }
+ else{
+ /*
+ * Hide the checkboxes
+ */
+
+ *(*cl)->d.l.listmode = SingleMode;
+
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ for(; ctmp; ctmp = next_confline(ctmp))
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->value){
+ ctmp->value[0] = ctmp->value[1] = ctmp->value[2] = SPACE;
+ ctmp->keymenu = &sel_from_list_sm;
+ }
+ }
+
+ ps->mangled_body = ps->mangled_footer = 1;
+ break;
+
+ case MC_TOGGLE :
+ if((*cl)->value[1] == 'X'){
+ (*cl)->d.l.lsel->selected = 0;
+ (*cl)->value[1] = SPACE;
+ }
+ else{
+ (*cl)->d.l.lsel->selected = 1;
+ (*cl)->value[1] = 'X';
+ }
+
+ ps->mangled_body = 1;
+ break;
+
+ case MC_EXIT :
+ retval = simple_exit_cmd(flags);
+ break;
+
+ default:
+ retval = -1;
+ break;
+ }
+
+ if(retval > 0)
+ ps->mangled_body = 1;
+
+ return(retval);
+}
+
+
+int
+select_from_list_tool_allow_noselections(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int retval = 0;
+
+ switch(cmd){
+ case MC_SELECT :
+ retval = 3;
+ if(*(*cl)->d.l.listmode == SingleMode)
+ (*cl)->d.l.lsel->selected = 1;
+
+ break;
+
+ default:
+ retval = select_from_list_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ if(retval > 0)
+ ps->mangled_body = 1;
+
+ return(retval);
+}
diff --git a/alpine/listsel.h b/alpine/listsel.h
new file mode 100644
index 00000000..cab8da58
--- /dev/null
+++ b/alpine/listsel.h
@@ -0,0 +1,47 @@
+/*
+ * $Id: listsel.h 918 2008-01-23 19:39:38Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_LISTSEL_INCLUDED
+#define PINE_LISTSEL_INCLUDED
+
+
+#include "help.h"
+
+
+/* for select_from_list_screen */
+#define SFL_NONE 0x000
+#define SFL_ALLOW_LISTMODE 0x001
+#define SFL_STARTIN_LISTMODE 0x002 /* should also light ALLOW_LISTMODE */
+#define SFL_ONLY_LISTMODE 0x004 /* don't allow switching back out of ListMode */
+#define SFL_NOSELECT 0x008 /* per item flag, line not selectable */
+#define SFL_CTRLC 0x010 /* use ^C instead of Exit for Exit and
+ allow zero selections */
+
+
+typedef struct list_selection {
+ char *display_item; /* use item if this is NULL */
+ char *item; /* selected value for item */
+ int selected; /* is item selected or not */
+ int flags;
+ struct list_selection *next;
+} LIST_SEL_S;
+
+
+/* exported protoypes */
+int select_from_list_screen(LIST_SEL_S *, unsigned long, char *, char *, HelpType, char *, LIST_SEL_S *);
+
+
+#endif /* PINE_LISTSEL_INCLUDED */
diff --git a/alpine/mailcmd.c b/alpine/mailcmd.c
new file mode 100644
index 00000000..fa781556
--- /dev/null
+++ b/alpine/mailcmd.c
@@ -0,0 +1,9472 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ mailcmd.c
+ The meat and pototoes of mail processing here:
+ - initial command processing and dispatch
+ - save message
+ - capture address off incoming mail
+ - jump to specific numbered message
+ - open (broach) a new folder
+ - search message headers (where is) command
+ ====*/
+
+
+#include "headers.h"
+#include "mailcmd.h"
+#include "status.h"
+#include "mailview.h"
+#include "flagmaint.h"
+#include "listsel.h"
+#include "keymenu.h"
+#include "alpine.h"
+#include "mailpart.h"
+#include "mailindx.h"
+#include "folder.h"
+#include "reply.h"
+#include "help.h"
+#include "titlebar.h"
+#include "signal.h"
+#include "radio.h"
+#include "pipe.h"
+#include "send.h"
+#include "takeaddr.h"
+#include "roleconf.h"
+#include "smime.h"
+#include "../pith/state.h"
+#include "../pith/msgno.h"
+#include "../pith/store.h"
+#include "../pith/thread.h"
+#include "../pith/flag.h"
+#include "../pith/sort.h"
+#include "../pith/maillist.h"
+#include "../pith/save.h"
+#include "../pith/pipe.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/sequence.h"
+#include "../pith/keyword.h"
+#include "../pith/stream.h"
+#include "../pith/mailcmd.h"
+#include "../pith/hist.h"
+#include "../pith/list.h"
+#include "../pith/icache.h"
+#include "../pith/busy.h"
+#include "../pith/mimedesc.h"
+#include "../pith/pattern.h"
+#include "../pith/tempfile.h"
+#include "../pith/search.h"
+#include "../pith/margin.h"
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+/*
+ * Internal Prototypes
+ */
+int cmd_flag(struct pine *, MSGNO_S *, int);
+int cmd_flag_prompt(struct pine *, struct flag_screen *, int);
+void free_flag_table(struct flag_table **);
+int cmd_reply(struct pine *, MSGNO_S *, int);
+int cmd_forward(struct pine *, MSGNO_S *, int);
+int cmd_bounce(struct pine *, MSGNO_S *, int);
+int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere);
+void role_compose(struct pine *);
+void cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *);
+int cmd_export(struct pine *, MSGNO_S *, int, int);
+char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere);
+char *cmd_delete_view(struct pine *, MSGNO_S *);
+char *cmd_delete_index(struct pine *, MSGNO_S *);
+long get_level(int, UCS, SCROLL_S *);
+long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t);
+int update_folder_spec(char *, size_t, char *);
+int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere);
+int cmd_pipe(struct pine *, MSGNO_S *, int);
+STORE_S *list_mgmt_text(RFC2369_S *, long);
+void list_mgmt_screen(STORE_S *);
+int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere);
+int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
+int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
+int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
+int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
+int select_by_size(MAILSTREAM *, SEARCHSET **);
+SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
+int select_by_status(MAILSTREAM *, SEARCHSET **);
+int select_by_rule(MAILSTREAM *, SEARCHSET **);
+int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
+char *choose_a_rule(int);
+int select_by_keyword(MAILSTREAM *, SEARCHSET **);
+char *choose_a_keyword(void);
+int select_sort(struct pine *, int, SortOrder *, int *);
+int print_index(struct pine *, MSGNO_S *, int);
+
+
+
+/*
+ * List of Select options used by apply_* functions...
+ */
+static char *sel_pmt1 = N_("ALTER message selection : ");
+ESCKEY_S sel_opts1[] = {
+ /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
+ we will add more messages to the selection, Narrow selection means we will
+ remove some selections (like a logical AND instead of logical OR), and Flip
+ Selected means that all the messages that are currently selected become unselected,
+ and all the unselected messages become selected. */
+ {'a', 'a', "A", N_("unselect All")},
+ {'c', 'c', "C", NULL},
+ {'b', 'b', "B", N_("Broaden selctn")},
+ {'n', 'n', "N", N_("Narrow selctn")},
+ {'f', 'f', "F", N_("Flip selected")},
+ {-1, 0, NULL, NULL}
+};
+
+
+#define SEL_OPTS_THREAD 9 /* index number of "tHread" */
+#define SEL_OPTS_THREAD_CH 'h'
+
+char *sel_pmt2 = "SELECT criteria : ";
+static ESCKEY_S sel_opts2[] = {
+ /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
+ means select the currently highlighted message; select by Number is by message
+ number; Status is by status of the message, for example the message might be
+ New or it might be Unseen or marked Important; Size has the Z upper case because
+ it is a Z command; Keyword is an alpine keyword that has been set by the user;
+ and Rule is an alpine rule */
+ {'a', 'a', "A", N_("select All")},
+ {'c', 'c', "C", N_("select Cur")},
+ {'n', 'n', "N", N_("Number")},
+ {'d', 'd', "D", N_("Date")},
+ {'t', 't', "T", N_("Text")},
+ {'s', 's', "S", N_("Status")},
+ {'z', 'z', "Z", N_("siZe")},
+ {'k', 'k', "K", N_("Keyword")},
+ {'r', 'r', "R", N_("Rule")},
+ {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
+ {-1, 0, NULL, NULL}
+};
+
+
+static ESCKEY_S sel_opts3[] = {
+ /* TRANSLATORS: these are operations we can do on a set of selected messages.
+ Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
+ the address book; Save means to save the messages into another alpine folder;
+ Export means to copy the messages to a file outside of alpine, external to
+ alpine's world. */
+ {'d', 'd', "D", N_("Del")},
+ {'u', 'u', "U", N_("Undel")},
+ {'r', 'r', "R", N_("Reply")},
+ {'f', 'f', "F", N_("Forward")},
+ {'%', '%', "%", N_("Print")},
+ {'t', 't', "T", N_("TakeAddr")},
+ {'s', 's', "S", N_("Save")},
+ {'e', 'e', "E", N_("Export")},
+ { -1, 0, NULL, NULL},
+ { -1, 0, NULL, NULL},
+ { -1, 0, NULL, NULL},
+ { -1, 0, NULL, NULL},
+ { -1, 0, NULL, NULL},
+ { -1, 0, NULL, NULL},
+ { -1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}
+};
+
+static ESCKEY_S sel_opts4[] = {
+ {'a', 'a', "A", N_("select All")},
+ /* TRANSLATORS: select currrently highlighted message Thread */
+ {'c', 'c', "C", N_("select Curthrd")},
+ {'n', 'n', "N", N_("Number")},
+ {'d', 'd', "D", N_("Date")},
+ {'t', 't', "T", N_("Text")},
+ {'s', 's', "S", N_("Status")},
+ {'z', 'z', "Z", N_("siZe")},
+ {'k', 'k', "K", N_("Keyword")},
+ {'r', 'r', "R", N_("Rule")},
+ {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
+ {-1, 0, NULL, NULL}
+};
+
+
+static char *sel_flag =
+ N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
+static char *sel_flag_not =
+ N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
+static ESCKEY_S sel_flag_opt[] = {
+ /* TRANSLATORS: When selecting messages by message Status these are the
+ different types of Status you can select on. Is the message New, Recent,
+ and so on. Not means flip the meaning of the selection to the opposite
+ thing, so message is not New or not Important. */
+ {'n', 'n', "N", N_("New")},
+ {'*', '*', "*", N_("Important")},
+ {'d', 'd', "D", N_("Deleted")},
+ {'a', 'a', "A", N_("Answered")},
+ {'f', 'f', "F", N_("Forwarded")},
+ {-2, 0, NULL, NULL},
+ {'!', '!', "!", N_("Not")},
+ {-2, 0, NULL, NULL},
+ {'r', 'r', "R", N_("Recent")},
+ {'u', 'u', "U", N_("Unseen")},
+ {-1, 0, NULL, NULL}
+};
+
+
+static ESCKEY_S sel_date_opt[] = {
+ {0, 0, NULL, NULL},
+ /* TRANSLATORS: options when selecting messages by Date */
+ {ctrl('P'), 12, "^P", N_("Prev Day")},
+ {ctrl('N'), 13, "^N", N_("Next Day")},
+ {ctrl('X'), 11, "^X", N_("Cur Msg")},
+ {ctrl('W'), 14, "^W", N_("Toggle When")},
+ {KEY_UP, 12, "", ""},
+ {KEY_DOWN, 13, "", ""},
+ {-1, 0, NULL, NULL}
+};
+
+
+static char *sel_text =
+ N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
+static char *sel_text_not =
+ N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
+static ESCKEY_S sel_text_opt[] = {
+ /* TRANSLATORS: Select messages based on the text contained in the From line, or
+ the Subject line, and so on. */
+ {'f', 'f', "F", N_("From")},
+ {'s', 's', "S", N_("Subject")},
+ {'t', 't', "T", N_("To")},
+ {'a', 'a', "A", N_("All Text")},
+ {'c', 'c', "C", N_("Cc")},
+ {'!', '!', "!", N_("Not")},
+ {'r', 'r', "R", N_("Recipient")},
+ {'p', 'p', "P", N_("Participant")},
+ {'b', 'b', "B", N_("Body")},
+ {'h', 'h', "H", N_("Header")},
+ {-1, 0, NULL, NULL}
+};
+
+static ESCKEY_S choose_action[] = {
+ {'c', 'c', "C", N_("Compose")},
+ {'r', 'r', "R", N_("Reply")},
+ {'f', 'f', "F", N_("Forward")},
+ {'b', 'b', "B", N_("Bounce")},
+ {-1, 0, NULL, NULL}
+};
+
+static char *select_num =
+ N_("Enter comma-delimited list of numbers (dash between ranges): ");
+
+static char *select_size_larger_msg =
+ N_("Select messages with size larger than: ");
+
+static char *select_size_smaller_msg =
+ N_("Select messages with size smaller than: ");
+
+static char *sel_size_larger = N_("Larger");
+static char *sel_size_smaller = N_("Smaller");
+static ESCKEY_S sel_size_opt[] = {
+ {0, 0, NULL, NULL},
+ {ctrl('W'), 14, "^W", NULL},
+ {-1, 0, NULL, NULL}
+};
+
+static ESCKEY_S sel_key_opt[] = {
+ {0, 0, NULL, NULL},
+ {ctrl('T'), 14, "^T", N_("To List")},
+ {0, 0, NULL, NULL},
+ {'!', '!', "!", N_("Not")},
+ {-1, 0, NULL, NULL}
+};
+
+static ESCKEY_S flag_text_opt[] = {
+ /* TRANSLATORS: these are types of flags (markers) that the user can
+ set. For example, they can flag the message as an important message. */
+ {'n', 'n', "N", N_("New")},
+ {'*', '*', "*", N_("Important")},
+ {'d', 'd', "D", N_("Deleted")},
+ {'a', 'a', "A", N_("Answered")},
+ {'f', 'f', "F", N_("Forwarded")},
+ {'!', '!', "!", N_("Not")},
+ {ctrl('T'), 10, "^T", N_("To Flag Details")},
+ {-1, 0, NULL, NULL}
+};
+
+
+/*----------------------------------------------------------------------
+ The giant switch on the commands for index and viewing
+
+ Input: command -- The command char/code
+ in_index -- flag indicating command is from index
+ orig_command -- The original command typed before pre-processing
+ Output: force_mailchk -- Set to tell caller to force call to new_mail().
+
+ Result: Manifold
+
+ Returns 1 if the message number or attachment to show changed
+ ---*/
+int
+process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
+ int command, CmdWhere in_index, int *force_mailchk)
+{
+ int question_line, a_changed, flags = 0, ret, j;
+ int notrealinbox;
+ long new_msgno, del_count, old_msgno, i;
+ long start;
+ char *newfolder, prompt[MAX_SCREEN_COLS+1];
+ CONTEXT_S *tc;
+ MESSAGECACHE *mc;
+#if defined(DOS) && !defined(_WINDOWS)
+ extern long coreleft();
+#endif
+
+ dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
+
+ question_line = -FOOTER_ROWS(state);
+ state->mangled_screen = 0;
+ state->mangled_footer = 0;
+ state->mangled_header = 0;
+ state->next_screen = SCREEN_FUN_NULL;
+ old_msgno = mn_get_cur(msgmap);
+ a_changed = FALSE;
+ *force_mailchk = 0;
+
+ switch (command) {
+ /*------------- Help --------*/
+ case MC_HELP :
+ /*
+ * We're not using the h_mail_view portion of this right now because
+ * that call is being handled in scrolltool() before it gets
+ * here. Leave it in case we change how it works.
+ */
+ helper((in_index == MsgIndx)
+ ? h_mail_index
+ : (in_index == View)
+ ? h_mail_view
+ : h_mail_thread_index,
+ (in_index == MsgIndx)
+ ? _("HELP FOR MESSAGE INDEX")
+ : (in_index == View)
+ ? _("HELP FOR MESSAGE TEXT")
+ : _("HELP FOR THREAD INDEX"),
+ HLPD_NONE);
+ dprint((4,"MAIL_CMD: did help command\n"));
+ state->mangled_screen = 1;
+ break;
+
+
+ /*--------- Return to main menu ------------*/
+ case MC_MAIN :
+ state->next_screen = main_menu_screen;
+ dprint((2,"MAIL_CMD: going back to main menu\n"));
+ break;
+
+
+ /*------- View message text --------*/
+ case MC_VIEW_TEXT :
+view_text:
+ if(any_messages(msgmap, NULL, "to View")){
+ state->next_screen = mail_view_screen;
+ }
+
+ break;
+
+
+ /*------- View attachment --------*/
+ case MC_VIEW_ATCH :
+ state->next_screen = attachment_screen;
+ dprint((2,"MAIL_CMD: going to attachment screen\n"));
+ break;
+
+
+ /*---------- Previous message ----------*/
+ case MC_PREVITEM :
+ if(any_messages(msgmap, NULL, NULL)){
+ if((i = mn_get_cur(msgmap)) > 1L){
+ mn_dec_cur(stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ if(i == mn_get_cur(msgmap)){
+ PINETHRD_S *thrd, *topthrd;
+
+ if(THRD_INDX_ENABLED()){
+ mn_dec_cur(stream, msgmap, MH_ANYTHD);
+ if(i == mn_get_cur(msgmap))
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Already on first %s in Zoomed Index"),
+ THRD_INDX() ? _("thread") : _("message"));
+ else{
+ if(in_index == View
+ || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
+ ret = 'y';
+ else
+ ret = want_to(_("View previous thread"), 'y', 'x',
+ NO_HELP, WT_NORM);
+
+ if(ret == 'y'){
+ q_status_message(SM_ORDER, 0, 2,
+ _("Viewing previous thread"));
+ new_msgno = mn_get_cur(msgmap);
+ mn_set_cur(msgmap, i);
+ if(unview_thread(state, stream, msgmap)){
+ state->next_screen = mail_index_screen;
+ state->view_skipped_index = 0;
+ state->mangled_screen = 1;
+ }
+
+ mn_set_cur(msgmap, new_msgno);
+ if(THRD_AUTO_VIEW() && in_index == View){
+
+ thrd = fetch_thread(stream,
+ mn_m2raw(msgmap,
+ new_msgno));
+ if(count_lflags_in_thread(stream, thrd,
+ msgmap,
+ MN_NONE) == 1){
+ if(view_thread(state, stream, msgmap, 1)){
+ if(current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+
+ state->view_skipped_index = 1;
+ command = MC_VIEW_TEXT;
+ goto view_text;
+ }
+ }
+ }
+
+ j = 0;
+ if(THRD_AUTO_VIEW() && in_index != View){
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
+ if(thrd && thrd->top)
+ topthrd = fetch_thread(stream, thrd->top);
+
+ if(topthrd)
+ j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
+ }
+
+ if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
+ if(view_thread(state, stream, msgmap, 1)
+ && current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+
+ }
+
+ state->next_screen = SCREEN_FUN_NULL;
+ }
+ else
+ mn_set_cur(msgmap, i); /* put it back */
+ }
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Already on first %s in Zoomed Index"),
+ THRD_INDX() ? _("thread") : _("message"));
+ }
+ }
+ else{
+ time_t now;
+
+ if(!IS_NEWS(stream)
+ && ((now = time(0)) > state->last_nextitem_forcechk)){
+ *force_mailchk = 1;
+ /* check at most once a second */
+ state->last_nextitem_forcechk = now;
+ }
+
+ q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
+ THRD_INDX() ? _("thread") : _("message"));
+ }
+ }
+
+ break;
+
+
+ /*---------- Next Message ----------*/
+ case MC_NEXTITEM :
+ if(mn_get_total(msgmap) > 0L
+ && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
+ mn_inc_cur(stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ if(i == mn_get_cur(msgmap)){
+ PINETHRD_S *thrd, *topthrd;
+
+ if(THRD_INDX_ENABLED()){
+ if(!THRD_INDX())
+ mn_inc_cur(stream, msgmap, MH_ANYTHD);
+
+ if(i == mn_get_cur(msgmap)){
+ if(any_lflagged(msgmap, MN_HIDE))
+ any_messages(NULL, "more", "in Zoomed Index");
+ else
+ goto nfolder;
+ }
+ else{
+ if(in_index == View
+ || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
+ ret = 'y';
+ else
+ ret = want_to(_("View next thread"), 'y', 'x',
+ NO_HELP, WT_NORM);
+
+ if(ret == 'y'){
+ q_status_message(SM_ORDER, 0, 2,
+ _("Viewing next thread"));
+ new_msgno = mn_get_cur(msgmap);
+ mn_set_cur(msgmap, i);
+ if(unview_thread(state, stream, msgmap)){
+ state->next_screen = mail_index_screen;
+ state->view_skipped_index = 0;
+ state->mangled_screen = 1;
+ }
+
+ mn_set_cur(msgmap, new_msgno);
+ if(THRD_AUTO_VIEW() && in_index == View){
+
+ thrd = fetch_thread(stream,
+ mn_m2raw(msgmap,
+ new_msgno));
+ if(count_lflags_in_thread(stream, thrd,
+ msgmap,
+ MN_NONE) == 1){
+ if(view_thread(state, stream, msgmap, 1)){
+ if(current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+
+ state->view_skipped_index = 1;
+ command = MC_VIEW_TEXT;
+ goto view_text;
+ }
+ }
+ }
+
+ j = 0;
+ if(THRD_AUTO_VIEW() && in_index != View){
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
+ if(thrd && thrd->top)
+ topthrd = fetch_thread(stream, thrd->top);
+
+ if(topthrd)
+ j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
+ }
+
+ if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
+ if(view_thread(state, stream, msgmap, 1)
+ && current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+
+ }
+
+ state->next_screen = SCREEN_FUN_NULL;
+ }
+ else
+ mn_set_cur(msgmap, i); /* put it back */
+ }
+ }
+ else if(THREADING()
+ && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
+ && thrd->next
+ && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
+ q_status_message(SM_ORDER, 0, 2,
+ _("Expand collapsed thread to see more messages"));
+ }
+ else
+ any_messages(NULL, "more", "in Zoomed Index");
+ }
+ }
+ else{
+ time_t now;
+nfolder:
+ prompt[0] = '\0';
+ if(IS_NEWS(stream)
+ || (state->context_current->use & CNTXT_INCMNG)){
+ char nextfolder[MAXPATH];
+
+ strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
+ nextfolder[sizeof(nextfolder)-1] = '\0';
+ if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
+ state->context_current, NULL, NULL))
+ strncpy(prompt, _(". Press TAB for next folder."),
+ sizeof(prompt));
+ else
+ strncpy(prompt, _(". No more folders to TAB to."),
+ sizeof(prompt));
+
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+ any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
+ prompt[0] ? prompt : NULL);
+
+ if(!IS_NEWS(stream)
+ && ((now = time(0)) > state->last_nextitem_forcechk)){
+ *force_mailchk = 1;
+ /* check at most once a second */
+ state->last_nextitem_forcechk = now;
+ }
+ }
+
+ break;
+
+
+ /*---------- Delete message ----------*/
+ case MC_DELETE :
+ (void) cmd_delete(state, msgmap, MCMD_NONE,
+ (in_index == View) ? cmd_delete_view : cmd_delete_index);
+ break;
+
+
+ /*---------- Undelete message ----------*/
+ case MC_UNDELETE :
+ (void) cmd_undelete(state, msgmap, MCMD_NONE);
+ update_titlebar_status();
+ break;
+
+
+ /*---------- Reply to message ----------*/
+ case MC_REPLY :
+ (void) cmd_reply(state, msgmap, MCMD_NONE);
+ break;
+
+
+ /*---------- Forward message ----------*/
+ case MC_FORWARD :
+ (void) cmd_forward(state, msgmap, MCMD_NONE);
+ break;
+
+
+ /*---------- Quit pine ------------*/
+ case MC_QUIT :
+ state->next_screen = quit_screen;
+ dprint((1,"MAIL_CMD: quit\n"));
+ break;
+
+
+ /*---------- Compose message ----------*/
+ case MC_COMPOSE :
+ state->prev_screen = (in_index == View) ? mail_view_screen
+ : mail_index_screen;
+ compose_screen(state);
+ state->mangled_screen = 1;
+ if (state->next_screen)
+ a_changed = TRUE;
+ break;
+
+
+ /*---------- Alt Compose message ----------*/
+ case MC_ROLE :
+ state->prev_screen = (in_index == View) ? mail_view_screen
+ : mail_index_screen;
+ role_compose(state);
+ if(state->next_screen)
+ a_changed = TRUE;
+
+ break;
+
+
+ /*--------- Folders menu ------------*/
+ case MC_FOLDERS :
+ state->start_in_context = 1;
+
+ /*--------- Top of Folders list menu ------------*/
+ case MC_COLLECTIONS :
+ state->next_screen = folder_screen;
+ dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
+ break;
+
+
+ /*---------- Open specific new folder ----------*/
+ case MC_GOTO :
+ tc = (state->context_last && !NEWS_TEST(state->context_current))
+ ? state->context_last : state->context_current;
+
+ newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
+ if(newfolder){
+ visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
+ a_changed = TRUE;
+ }
+
+ break;
+
+
+ /*------- Go to Index Screen ----------*/
+ case MC_INDEX :
+ state->next_screen = mail_index_screen;
+ break;
+
+ /*------- Skip to next interesting message -----------*/
+ case MC_TAB :
+ if(THRD_INDX()){
+ PINETHRD_S *thrd;
+
+ /*
+ * If we're in the thread index, start looking after this
+ * thread. We don't want to match something in the current
+ * thread.
+ */
+ start = mn_get_cur(msgmap);
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
+ if(mn_get_revsort(msgmap)){
+ /* if reversed, top of thread is last one before next thread */
+ if(thrd && thrd->top)
+ start = mn_raw2m(msgmap, thrd->top);
+ }
+ else{
+ /* last msg of thread is at the ends of the branches/nexts */
+ while(thrd){
+ start = mn_raw2m(msgmap, thrd->rawno);
+ if(thrd->branch)
+ thrd = fetch_thread(stream, thrd->branch);
+ else if(thrd->next)
+ thrd = fetch_thread(stream, thrd->next);
+ else
+ thrd = NULL;
+ }
+ }
+
+ /*
+ * Flags is 0 in this case because we want to not skip
+ * messages inside of threads so that we can find threads
+ * which have some unseen messages even though the top-level
+ * of the thread is already seen.
+ * If new_msgno ends up being a message which is not visible
+ * because it isn't at the top-level, the current message #
+ * will be adjusted below in adjust_cur.
+ */
+ flags = 0;
+ new_msgno = next_sorted_flagged((F_UNDEL
+ | F_UNSEEN
+ | ((F_ON(F_TAB_TO_NEW,state))
+ ? 0 : F_OR_FLAG)),
+ stream, start, &flags);
+ }
+ else if(THREADING() && sp_viewing_a_thread(stream)){
+ PINETHRD_S *thrd, *topthrd = NULL;
+
+ start = mn_get_cur(msgmap);
+
+ /*
+ * Things are especially complicated when we're viewing_a_thread
+ * from the thread index. First we have to check within the
+ * current thread for a new message. If none is found, then
+ * we search in the next threads and offer to continue in
+ * them. Then we offer to go to the next folder.
+ */
+ flags = NSF_SKIP_CHID;
+ new_msgno = next_sorted_flagged((F_UNDEL
+ | F_UNSEEN
+ | ((F_ON(F_TAB_TO_NEW,state))
+ ? 0 : F_OR_FLAG)),
+ stream, start, &flags);
+ /*
+ * If we found a match then we are done, that is another message
+ * in the current thread index. Otherwise, we have to look
+ * further.
+ */
+ if(!(flags & NSF_FLAG_MATCH)){
+ ret = 'n';
+ while(1){
+
+ flags = 0;
+ new_msgno = next_sorted_flagged((F_UNDEL
+ | F_UNSEEN
+ | ((F_ON(F_TAB_TO_NEW,
+ state))
+ ? 0 : F_OR_FLAG)),
+ stream, start, &flags);
+ /*
+ * If we got a match, new_msgno is a message in
+ * a different thread from the one we are viewing.
+ */
+ if(flags & NSF_FLAG_MATCH){
+ thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
+ if(thrd && thrd->top)
+ topthrd = fetch_thread(stream, thrd->top);
+
+ if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
+ static ESCKEY_S next_opt[] = {
+ {'y', 'y', "Y", N_("Yes")},
+ {'n', 'n', "N", N_("No")},
+ {TAB, 'n', "Tab", N_("NextNew")},
+ {-1, 0, NULL, NULL}
+ };
+
+ if(in_index)
+ snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
+ topthrd ? comatose(topthrd->thrdno) : "?");
+ else
+ snprintf(prompt, sizeof(prompt),
+ _("View message in thread number %s? "),
+ topthrd ? comatose(topthrd->thrdno) : "?");
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ ret = radio_buttons(prompt, -FOOTER_ROWS(state),
+ next_opt, 'y', 'x', NO_HELP,
+ RB_NORM);
+ if(ret == 'x'){
+ cmd_cancelled(NULL);
+ goto get_out;
+ }
+ }
+ else
+ ret = 'y';
+
+ if(ret == 'y'){
+ if(unview_thread(state, stream, msgmap)){
+ state->next_screen = mail_index_screen;
+ state->view_skipped_index = 0;
+ state->mangled_screen = 1;
+ }
+
+ mn_set_cur(msgmap, new_msgno);
+ if(THRD_AUTO_VIEW()){
+
+ if(count_lflags_in_thread(stream, topthrd,
+ msgmap, MN_NONE) == 1){
+ if(view_thread(state, stream, msgmap, 1)){
+ if(current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+
+ state->view_skipped_index = 1;
+ command = MC_VIEW_TEXT;
+ goto view_text;
+ }
+ }
+ }
+
+ if(view_thread(state, stream, msgmap, 1) && current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+
+ state->next_screen = SCREEN_FUN_NULL;
+ break;
+ }
+ else if(ret == 'n' && topthrd){
+ /*
+ * skip to end of this thread and look starting
+ * in the next thread.
+ */
+ if(mn_get_revsort(msgmap)){
+ /*
+ * if reversed, top of thread is last one
+ * before next thread
+ */
+ start = mn_raw2m(msgmap, topthrd->rawno);
+ }
+ else{
+ /*
+ * last msg of thread is at the ends of
+ * the branches/nexts
+ */
+ thrd = topthrd;
+ while(thrd){
+ start = mn_raw2m(msgmap, thrd->rawno);
+ if(thrd->branch)
+ thrd = fetch_thread(stream, thrd->branch);
+ else if(thrd->next)
+ thrd = fetch_thread(stream, thrd->next);
+ else
+ thrd = NULL;
+ }
+ }
+ }
+ else if(ret == 'n')
+ break;
+ }
+ else
+ break;
+ }
+ }
+ }
+ else{
+
+ start = mn_get_cur(msgmap);
+
+ /*
+ * If we are on a collapsed thread, start looking after the
+ * collapsed part, unless we are viewing the message.
+ */
+ if(THREADING() && in_index != View){
+ PINETHRD_S *thrd;
+ long rawno;
+ int collapsed;
+
+ rawno = mn_m2raw(msgmap, start);
+ thrd = fetch_thread(stream, rawno);
+ collapsed = thrd && thrd->next
+ && get_lflag(stream, NULL, rawno, MN_COLL);
+
+ if(collapsed){
+ if(mn_get_revsort(msgmap)){
+ if(thrd && thrd->top)
+ start = mn_raw2m(msgmap, thrd->top);
+ }
+ else{
+ while(thrd){
+ start = mn_raw2m(msgmap, thrd->rawno);
+ if(thrd->branch)
+ thrd = fetch_thread(stream, thrd->branch);
+ else if(thrd->next)
+ thrd = fetch_thread(stream, thrd->next);
+ else
+ thrd = NULL;
+ }
+ }
+
+ }
+ }
+
+ new_msgno = next_sorted_flagged((F_UNDEL
+ | F_UNSEEN
+ | ((F_ON(F_TAB_TO_NEW,state))
+ ? 0 : F_OR_FLAG)),
+ stream, start, &flags);
+ }
+
+ /*
+ * If there weren't any unread messages left, OR there
+ * aren't any messages at all, we may want to offer to
+ * go on to the next folder...
+ */
+ if(flags & NSF_FLAG_MATCH){
+ mn_set_cur(msgmap, new_msgno);
+ if(in_index != View)
+ adjust_cur_to_visible(stream, msgmap);
+ }
+ else{
+ int in_inbox = sp_flagged(stream, SP_INBOX);
+
+ if(state->context_current
+ && ((NEWS_TEST(state->context_current)
+ && context_isambig(state->cur_folder))
+ || ((state->context_current->use & CNTXT_INCMNG)
+ && (in_inbox
+ || folder_index(state->cur_folder,
+ state->context_current,
+ FI_FOLDER) >= 0)))){
+ char nextfolder[MAXPATH];
+ MAILSTREAM *nextstream = NULL;
+ long recent_cnt;
+ int did_cancel = 0;
+
+ strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
+ nextfolder[sizeof(nextfolder)-1] = '\0';
+ while(1){
+ if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
+ state->context_current, &recent_cnt,
+ F_ON(F_TAB_NO_CONFIRM,state)
+ ? NULL : &did_cancel))){
+ if(!in_inbox){
+ static ESCKEY_S inbox_opt[] = {
+ {'y', 'y', "Y", N_("Yes")},
+ {'n', 'n', "N", N_("No")},
+ {TAB, 'z', "Tab", N_("To Inbox")},
+ {-1, 0, NULL, NULL}
+ };
+
+ if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
+ ret = 'y';
+ else{
+ /* TRANSLATORS: this is a question, with some information followed
+ by Return to INBOX? */
+ if(state->context_current->use&CNTXT_INCMNG)
+ snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
+ else
+ snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
+
+ ret = radio_buttons(prompt, -FOOTER_ROWS(state),
+ inbox_opt, 'y', 'x',
+ NO_HELP, RB_NORM);
+ }
+
+ /*
+ * 'z' is a synonym for 'y'. It is not 'y'
+ * so that it isn't displayed as a default
+ * action with square-brackets around it
+ * in the keymenu...
+ */
+ if(ret == 'y' || ret == 'z'){
+ visit_folder(state, state->inbox_name,
+ state->context_current,
+ NULL, DB_INBOXWOCNTXT);
+ a_changed = TRUE;
+ }
+ }
+ else if (did_cancel)
+ cmd_cancelled(NULL);
+ else{
+ if(state->context_current->use&CNTXT_INCMNG)
+ q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
+ else
+ q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
+ }
+
+ break;
+ }
+
+ {char *front, type[80], cnt[80], fbuf[MAX_SCREEN_COLS/2+1];
+ int rbspace, avail, need, take_back;
+
+ /*
+ * View_next_
+ * Incoming_folder_ or news_group_ or folder_ or group_
+ * "foldername"
+ * _(13 recent) or _(some recent) or nothing
+ * ?_
+ */
+ front = "View next";
+ strncpy(type,
+ (state->context_current->use & CNTXT_INCMNG)
+ ? "Incoming folder" : "news group",
+ sizeof(type));
+ type[sizeof(type)-1] = '\0';
+ snprintf(cnt, sizeof(cnt), " (%.*s %s)", sizeof(cnt)-20,
+ recent_cnt ? long2string(recent_cnt) : "some",
+ F_ON(F_TAB_USES_UNSEEN, ps_global)
+ ? "unseen" : "recent");
+ cnt[sizeof(cnt)-1] = '\0';
+
+ /*
+ * Space reserved for radio_buttons call.
+ * If we make this 3 then radio_buttons won't mess
+ * with the prompt. If we make it 2, then we get
+ * one more character to use but radio_buttons will
+ * cut off the last character of our prompt, which is
+ * ok because it is a space.
+ */
+ rbspace = 2;
+ avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
+ : 80;
+ need = strlen(front)+1 + strlen(type)+1 +
+ + strlen(nextfolder)+2 + strlen(cnt) +
+ 2 + rbspace;
+ if(avail < need){
+ take_back = strlen(type);
+ strncpy(type,
+ (state->context_current->use & CNTXT_INCMNG)
+ ? "folder" : "group", sizeof(type));
+ take_back -= strlen(type);
+ need -= take_back;
+ if(avail < need){
+ need -= strlen(cnt);
+ cnt[0] = '\0';
+ }
+ }
+
+ snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
+ sizeof(prompt)/8, front,
+ sizeof(prompt)/8, type,
+ sizeof(prompt)/2,
+ short_str(nextfolder, fbuf, sizeof(fbuf),
+ strlen(nextfolder) -
+ ((need>avail) ? (need-avail) : 0),
+ MidDots),
+ sizeof(prompt)/8, cnt);
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+ /*
+ * When help gets added, this'll have to become
+ * a loop like the rest...
+ */
+ if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
+ static ESCKEY_S next_opt[] = {
+ {'y', 'y', "Y", N_("Yes")},
+ {'n', 'n', "N", N_("No")},
+ {TAB, 'n', "Tab", N_("NextNew")},
+ {-1, 0, NULL, NULL}
+ };
+
+ ret = radio_buttons(prompt, -FOOTER_ROWS(state),
+ next_opt, 'y', 'x', NO_HELP,
+ RB_NORM);
+ if(ret == 'x'){
+ cmd_cancelled(NULL);
+ break;
+ }
+ }
+ else
+ ret = 'y';
+
+ if(ret == 'y'){
+ if(nextstream && sp_dead_stream(nextstream))
+ nextstream = NULL;
+
+ visit_folder(state, nextfolder,
+ state->context_current, nextstream,
+ DB_FROMTAB);
+ /* visit_folder takes care of nextstream */
+ nextstream = NULL;
+ a_changed = TRUE;
+ break;
+ }
+ }
+
+ if(nextstream)
+ pine_mail_close(nextstream);
+ }
+ else
+ any_messages(NULL,
+ (mn_get_total(msgmap) > 0L)
+ ? IS_NEWS(stream) ? "more undeleted" : "more new"
+ : NULL,
+ NULL);
+ }
+
+get_out:
+
+ break;
+
+
+ /*------- Zoom -----------*/
+ case MC_ZOOM :
+ /*
+ * Right now the way zoom is implemented is sort of silly.
+ * There are two per-message flags where just one and a
+ * global "zoom mode" flag to suppress messags from the index
+ * should suffice.
+ */
+ if(any_messages(msgmap, NULL, "to Zoom on")){
+ if(unzoom_index(state, stream, msgmap)){
+ dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
+ q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
+ }
+ else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
+ if(any_lflagged(msgmap, MN_HIDE)){
+ dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
+ q_status_message4(SM_ORDER, 0, 2,
+ _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
+ THRD_INDX() ? "" : comatose(i),
+ THRD_INDX() ? "" : " ",
+ THRD_INDX() ? _("threads") : _("message"),
+ THRD_INDX() ? "" : plural(i));
+ }
+ else
+ q_status_message(SM_ORDER, 0, 2,
+ _("All messages selected, so not entering Index Zoom Mode"));
+ }
+ else
+ any_messages(NULL, "selected", "to Zoom on");
+ }
+
+ break;
+
+
+ /*---------- print message on paper ----------*/
+ case MC_PRINTMSG :
+ if(any_messages(msgmap, NULL, "to print"))
+ (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
+
+ break;
+
+
+ /*---------- Take Address ----------*/
+ case MC_TAKE :
+ if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
+ any_messages(msgmap, NULL, "to Take address from"))
+ (void) cmd_take_addr(state, msgmap, MCMD_NONE);
+
+ break;
+
+
+ /*---------- Save Message ----------*/
+ case MC_SAVE :
+ if(any_messages(msgmap, NULL, "to Save"))
+ (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
+
+ break;
+
+
+ /*---------- Export message ----------*/
+ case MC_EXPORT :
+ if(any_messages(msgmap, NULL, "to Export")){
+ (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
+ state->mangled_footer = 1;
+ }
+
+ break;
+
+
+ /*---------- Expunge ----------*/
+ case MC_EXPUNGE :
+ cmd_expunge(state, stream, msgmap);
+ break;
+
+
+ /*------- Unexclude -----------*/
+ case MC_UNEXCLUDE :
+ if(!(IS_NEWS(stream) && stream->rdonly)){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Unexclude not available for mail folders"));
+ }
+ else if(any_lflagged(msgmap, MN_EXLD)){
+ SEARCHPGM *pgm;
+ long i;
+ int exbits;
+
+ /*
+ * Since excluded means "hidden deleted" and "killed",
+ * the count should reflect the former.
+ */
+ pgm = mail_newsearchpgm();
+ pgm->deleted = 1;
+ pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
+ for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) && mc->searched
+ && get_lflag(stream, NULL, i, MN_EXLD)
+ && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
+ && (exbits & MSG_EX_FILTERED)))
+ del_count++;
+
+ if(del_count > 0L){
+ state->mangled_footer = 1;
+ snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
+ plural(del_count), sizeof(prompt)-40,
+ pretty_fn(state->cur_folder));
+ prompt[sizeof(prompt)-1] = '\0';
+ if(F_ON(F_FULL_AUTO_EXPUNGE, state)
+ || (F_ON(F_AUTO_EXPUNGE, state)
+ && (state->context_current
+ && (state->context_current->use & CNTXT_INCMNG))
+ && context_isambig(state->cur_folder))
+ || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
+ long save_cur_rawno;
+ int were_viewing_a_thread;
+
+ save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ were_viewing_a_thread = (THREADING()
+ && sp_viewing_a_thread(stream));
+
+ if(msgno_include(stream, msgmap, MI_NONE)){
+ clear_index_cache(stream, 0);
+
+ if(stream && stream->spare)
+ erase_threading_info(stream, msgmap);
+
+ refresh_sort(stream, msgmap, SRT_NON);
+ }
+
+ if(were_viewing_a_thread){
+ if(save_cur_rawno > 0L)
+ mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
+
+ if(view_thread(state, stream, msgmap, 1) && current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+ }
+
+ if(save_cur_rawno > 0L)
+ mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
+
+ state->mangled_screen = 1;
+ q_status_message2(SM_ORDER, 0, 4,
+ "%s message%s UNexcluded",
+ long2string(del_count),
+ plural(del_count));
+
+ if(in_index != View)
+ adjust_cur_to_visible(stream, msgmap);
+ }
+ else
+ any_messages(NULL, NULL, "UNexcluded");
+ }
+ else
+ any_messages(NULL, "excluded", "to UNexclude");
+ }
+ else
+ any_messages(NULL, "excluded", "to UNexclude");
+
+ break;
+
+
+ /*------- Make Selection -----------*/
+ case MC_SELECT :
+ if(any_messages(msgmap, NULL, "to Select")){
+ if(aggregate_select(state, msgmap, question_line, in_index) == 0
+ && (in_index == MsgIndx || in_index == ThrdIndx)
+ && F_ON(F_AUTO_ZOOM, state)
+ && any_lflagged(msgmap, MN_SLCT) > 0L
+ && !any_lflagged(msgmap, MN_HIDE))
+ (void) zoom_index(state, stream, msgmap, MN_SLCT);
+ }
+
+ break;
+
+
+ /*------- Toggle Current Message Selection State -----------*/
+ case MC_SELCUR :
+ if(any_messages(msgmap, NULL, NULL)){
+ if((select_by_current(state, msgmap, in_index)
+ || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
+ && !any_lflagged(msgmap, MN_HIDE)))
+ && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
+ /* advance current */
+ mn_inc_cur(stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ }
+ }
+
+ break;
+
+
+ /*------- Apply command -----------*/
+ case MC_APPLY :
+ if(any_messages(msgmap, NULL, NULL)){
+ if(any_lflagged(msgmap, MN_SLCT) > 0L){
+ if(apply_command(state, stream, msgmap, 0,
+ AC_NONE, question_line)){
+ if(F_ON(F_AUTO_UNSELECT, state)){
+ agg_select_all(stream, msgmap, NULL, 0);
+ unzoom_index(state, stream, msgmap);
+ }
+ else if(F_ON(F_AUTO_UNZOOM, state))
+ unzoom_index(state, stream, msgmap);
+ }
+ }
+ else
+ any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
+ }
+
+ break;
+
+
+ /*-------- Sort command -------*/
+ case MC_SORT :
+ {
+ int were_threading = THREADING();
+ SortOrder sort = mn_get_sort(msgmap);
+ int rev = mn_get_revsort(msgmap);
+
+ dprint((1,"MAIL_CMD: sort\n"));
+ if(select_sort(state, question_line, &sort, &rev)){
+ /* $ command reinitializes threading collapsed/expanded info */
+ if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
+ erase_threading_info(stream, msgmap);
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
+ }
+
+ state->mangled_footer = 1;
+
+ /*
+ * We've changed whether we are threading or not so we need to
+ * exit the index and come back in so that we switch between the
+ * thread index and the regular index. Sort_folder will have
+ * reset viewing_a_thread if necessary.
+ */
+ if(SEP_THRDINDX()
+ && ((!were_threading && THREADING())
+ || (were_threading && !THREADING()))){
+ state->next_screen = mail_index_screen;
+ state->mangled_screen = 1;
+ }
+ }
+
+ break;
+
+
+ /*------- Toggle Full Headers -----------*/
+ case MC_FULLHDR :
+ state->full_header++;
+ if(state->full_header == 1){
+ if(!(state->quote_suppression_threshold
+ && (state->some_quoting_was_suppressed || in_index != View)))
+ state->full_header++;
+ }
+ else if(state->full_header > 2)
+ state->full_header = 0;
+
+ switch(state->full_header){
+ case 0:
+ q_status_message(SM_ORDER, 0, 3,
+ _("Display of full headers is now off."));
+ break;
+
+ case 1:
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Quotes displayed, use %s to see full headers"),
+ F_ON(F_USE_FK, state) ? "F9" : "H");
+ break;
+
+ case 2:
+ q_status_message(SM_ORDER, 0, 3,
+ _("Display of full headers is now on."));
+ break;
+
+ }
+
+ a_changed = TRUE;
+ break;
+
+
+ case MC_TOGGLE :
+ a_changed = TRUE;
+ break;
+
+
+#ifdef SMIME
+ /*------- Try to decrypt message -----------*/
+ case MC_DECRYPT:
+ if(state->smime && state->smime->need_passphrase)
+ smime_get_passphrase();
+
+ a_changed = TRUE;
+ break;
+
+ case MC_SECURITY:
+ state->next_screen = smime_info_screen;
+ break;
+#endif
+
+
+ /*------- Bounce -----------*/
+ case MC_BOUNCE :
+ (void) cmd_bounce(state, msgmap, MCMD_NONE);
+ break;
+
+
+ /*------- Flag -----------*/
+ case MC_FLAG :
+ dprint((4, "\n - flag message -\n"));
+ (void) cmd_flag(state, msgmap, MCMD_NONE);
+ break;
+
+
+ /*------- Pipe message -----------*/
+ case MC_PIPE :
+ (void) cmd_pipe(state, msgmap, MCMD_NONE);
+ break;
+
+
+ /*--------- Default, unknown command ----------*/
+ default:
+ panic("Unexpected command case");
+ break;
+ }
+
+ return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Map some of the special characters into sensible strings for human
+ consumption.
+ c is a UCS-4 character!
+ ----*/
+char *
+pretty_command(UCS c)
+{
+ static char buf[10];
+ char *s;
+
+ buf[0] = '\0';
+ s = buf;
+
+ switch(c){
+ case ' ' : s = "SPACE"; break;
+ case '\033' : s = "ESC"; break;
+ case '\177' : s = "DEL"; break;
+ case ctrl('I') : s = "TAB"; break;
+ case ctrl('J') : s = "LINEFEED"; break;
+ case ctrl('M') : s = "RETURN"; break;
+ case ctrl('Q') : s = "XON"; break;
+ case ctrl('S') : s = "XOFF"; break;
+ case KEY_UP : s = "Up Arrow"; break;
+ case KEY_DOWN : s = "Down Arrow"; break;
+ case KEY_RIGHT : s = "Right Arrow"; break;
+ case KEY_LEFT : s = "Left Arrow"; break;
+ case KEY_PGUP : s = "Prev Page"; break;
+ case KEY_PGDN : s = "Next Page"; break;
+ case KEY_HOME : s = "Home"; break;
+ case KEY_END : s = "End"; break;
+ case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
+ case KEY_JUNK : s = "Junk!"; break;
+ case BADESC : s = "Bad Esc"; break;
+ case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
+ case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
+ case KEY_RESIZE : s = "KEY_RESIZE"; break;
+ case KEY_UTF8 : s = "KEY_UTF8"; break;
+ case KEY_MOUSE : s = "KEY_MOUSE"; break;
+ case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
+ case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
+ case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
+ case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
+ case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
+ case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
+ case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
+ case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
+ case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
+ case PF1 :
+ case PF2 :
+ case PF3 :
+ case PF4 :
+ case PF5 :
+ case PF6 :
+ case PF7 :
+ case PF8 :
+ case PF9 :
+ case PF10 :
+ case PF11 :
+ case PF12 :
+ snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
+ break;
+
+ default:
+ if(c < ' ' || (c >= 0x80 && c < 0xA0)){
+ char d;
+ int c1;
+
+ c1 = (c >= 0x80);
+ d = (c & 0x1f) + 'A' - 1;
+ snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
+ }
+ else{
+ memset(buf, 0, sizeof(buf));
+ utf8_put((unsigned char *) buf, (unsigned long) c);
+ }
+
+ break;
+ }
+
+ return(s);
+}
+
+
+/*----------------------------------------------------------------------
+ Complain about bogus input
+
+ Args: ch -- input command to complain about
+ help -- string indicating where to get help
+
+ ----*/
+void
+bogus_command(UCS cmd, char *help)
+{
+ if(cmd == ctrl('Q') || cmd == ctrl('S'))
+ q_status_message1(SM_ASYNC, 0, 2,
+ "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
+ pretty_command(cmd));
+ else if(cmd == KEY_JUNK)
+ q_status_message3(SM_ORDER, 0, 2,
+ "Invalid key pressed.%s%s%s",
+ (help) ? " Use " : "",
+ (help) ? help : "",
+ (help) ? " for help" : "");
+ else
+ q_status_message4(SM_ORDER, 0, 2,
+ "Command \"%s\" not defined for this screen.%s%s%s",
+ pretty_command(cmd),
+ (help) ? " Use " : "",
+ (help) ? help : "",
+ (help) ? " for help" : "");
+}
+
+
+void
+bogus_utf8_command(char *cmd, char *help)
+{
+ q_status_message4(SM_ORDER, 0, 2,
+ "Command \"%s\" not defined for this screen.%s%s%s",
+ cmd ? cmd : "?",
+ (help) ? " Use " : "",
+ (help) ? help : "",
+ (help) ? " for help" : "");
+}
+
+
+/*----------------------------------------------------------------------
+ Execute FLAG message command
+
+ Args: state -- Various satate info
+ msgmap -- map of c-client to local message numbers
+
+ Result: with side effect of "current" message FLAG flag set or UNset
+
+ ----*/
+int
+cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
+{
+ char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
+ char *keyword_array[2];
+ int user_defined_flags = 0, mailbox_flags = 0;
+ int directly_to_maint_screen = 0;
+ long unflagged, flagged, flags, rawno;
+ MESSAGECACHE *mc = NULL;
+ KEYWORD_S *kw;
+ int i, cnt, is_set, trouble = 0, rv = 0;
+ size_t len;
+ struct flag_table *fp, *ftbl = NULL;
+ struct flag_screen flag_screen;
+ static char *flag_screen_text1[] = {
+ N_(" Set desired flags for current message below. An 'X' means set"),
+ N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
+ NULL
+ };
+
+ static char *flag_screen_text2[] = {
+ N_(" Set desired flags below for selected messages. A '?' means to"),
+ N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
+ N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
+ N_(" \"Exit\" when finished."),
+ NULL
+ };
+
+ static struct flag_table default_ftbl[] = {
+ {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
+ {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
+ {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
+ {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
+ {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
+ {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
+ };
+
+ /* Only check for dead stream for now. Should check permanent flags
+ * eventually
+ */
+ if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
+ return rv;
+
+ if(sp_io_error_on_stream(state->mail_stream)){
+ sp_set_io_error_on_stream(state->mail_stream, 0);
+ pine_mail_check(state->mail_stream); /* forces write */
+ return rv;
+ }
+
+go_again:
+ answer = NULL;
+ user_defined_flags = 0;
+ mailbox_flags = 0;
+ mc = NULL;
+ trouble = 0;
+ ftbl = NULL;
+
+ /* count how large ftbl will be */
+ for(cnt = 0; default_ftbl[cnt].name; cnt++)
+ ;
+
+ /* add user flags */
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
+ user_defined_flags++;
+ cnt++;
+ }
+ }
+
+ /*
+ * Add mailbox flags that aren't user-defined flags.
+ * Don't consider it if it matches either one of our defined
+ * keywords or one of our defined nicknames for a keyword.
+ */
+ for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
+ char *q;
+
+ q = stream_to_user_flag_name(state->mail_stream, i);
+ if(q && q[0]){
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
+ break;
+ }
+ }
+
+ if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
+ mailbox_flags++;
+ cnt++;
+ }
+ }
+
+ cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
+
+ /* set up ftbl, first the system flags */
+ ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
+ memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
+ for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
+ fp->name = cpystr(default_ftbl[i].name);
+ fp->help = default_ftbl[i].help;
+ fp->flag = default_ftbl[i].flag;
+ fp->set = default_ftbl[i].set;
+ fp->ukn = default_ftbl[i].ukn;
+ }
+
+ if(user_defined_flags){
+ fp->flag = F_COMMENT;
+ fp->name = cpystr("");
+ fp++;
+ fp->flag = F_COMMENT;
+ len = strlen(_("User-defined Keywords from Setup/Config"));
+ fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
+ snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
+ fp++;
+ }
+
+ /* then the user-defined keywords */
+ if(user_defined_flags)
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
+ || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
+ fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
+ fp->keyword = cpystr(kw->kw ? kw->kw : "");
+ if(kw->nick && kw->kw){
+ size_t l;
+
+ l = strlen(kw->kw)+2;
+ fp->comment = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(fp->comment, l+1, "(%.*s)", strlen(kw->kw), kw->kw);
+ fp->comment[l] = '\0';
+ }
+
+ fp->help = h_flag_user_flag;
+ fp->flag = F_KEYWORD;
+ fp->set = 0;
+ fp->ukn = 0;
+ fp++;
+ }
+ }
+
+ if(mailbox_flags){
+ fp->flag = F_COMMENT;
+ fp->name = cpystr("");
+ fp++;
+ fp->flag = F_COMMENT;
+ len = strlen(_("Other keywords in the mailbox that are not user-defined"));
+ fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
+ snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
+ fp++;
+ }
+
+ /* then the extra mailbox-defined keywords */
+ if(mailbox_flags)
+ for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
+ char *q;
+
+ q = stream_to_user_flag_name(state->mail_stream, i);
+ if(q && q[0]){
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+ if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
+ break;
+ }
+ }
+
+ if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
+ fp->name = cpystr(q);
+ fp->keyword = cpystr(q);
+ fp->help = h_flag_user_flag;
+ fp->flag = F_KEYWORD;
+ fp->set = 0;
+ fp->ukn = 0;
+ fp++;
+ }
+ }
+
+ flag_screen.flag_table = &ftbl;
+ flag_screen.explanation = screen_text;
+
+ if(MCMD_ISAGG(aopt)){
+ if(!pseudo_selected(ps_global->mail_stream, msgmap)){
+ free_flag_table(&ftbl);
+ return rv;
+ }
+
+ exp = flag_screen_text2;
+ for(fp = ftbl; fp->name; fp++){
+ fp->set = CMD_FLAG_UNKN; /* set to unknown */
+ fp->ukn = TRUE;
+ }
+ }
+ else if(state->mail_stream
+ && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
+ && rawno <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, rawno))){
+ exp = flag_screen_text1;
+ for(fp = &ftbl[0]; fp->name; fp++){
+ fp->ukn = 0;
+ if(fp->flag == F_KEYWORD){
+ /* see if this keyword is defined for this message */
+ fp->set = CMD_FLAG_CLEAR;
+ if(user_flag_is_set(state->mail_stream,
+ rawno, fp->keyword))
+ fp->set = CMD_FLAG_SET;
+ }
+ else if(fp->flag == F_FWD){
+ /* see if forwarded keyword is defined for this message */
+ fp->set = CMD_FLAG_CLEAR;
+ if(user_flag_is_set(state->mail_stream,
+ rawno, FORWARDED_FLAG))
+ fp->set = CMD_FLAG_SET;
+ }
+ else if(fp->flag != F_COMMENT)
+ fp->set = ((fp->flag == F_SEEN && !mc->seen)
+ || (fp->flag == F_DEL && mc->deleted)
+ || (fp->flag == F_FLAG && mc->flagged)
+ || (fp->flag == F_ANS && mc->answered))
+ ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
+ }
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error accessing message data"));
+ free_flag_table(&ftbl);
+ return rv;
+ }
+
+ if(directly_to_maint_screen)
+ goto the_maint_screen;
+
+#ifdef _WINDOWS
+ if (mswin_usedialog ()) {
+ if (!os_flagmsgdialog (&ftbl[0])){
+ free_flag_table(&ftbl);
+ return rv;
+ }
+ }
+ else
+#endif
+ {
+ int use_maint_screen;
+ int keyword_shortcut = 0;
+
+ use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
+
+ if(!use_maint_screen){
+ /*
+ * We're going to call cmd_flag_prompt(). We need
+ * to decide whether or not to offer the keyword setting
+ * shortcut. We'll offer it if the user has the feature
+ * enabled AND there are some possible keywords that could
+ * be set.
+ */
+ if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
+ for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
+ if(fp->flag == F_KEYWORD){
+ int first_char;
+ ESCKEY_S *tp;
+
+ first_char = (fp->name && fp->name[0])
+ ? fp->name[0] : -2;
+ if(isascii(first_char) && isupper(first_char))
+ first_char = tolower((unsigned char) first_char);
+
+ for(tp=flag_text_opt; tp->ch != -1; tp++)
+ if(tp->ch == first_char)
+ break;
+
+ if(tp->ch == -1)
+ keyword_shortcut++;
+ }
+ }
+ }
+
+ use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
+ keyword_shortcut);
+ }
+
+the_maint_screen:
+ if(use_maint_screen){
+ for(p = &screen_text[0]; *exp; p++, exp++)
+ *p = *exp;
+
+ *p = NULL;
+
+ directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
+ }
+ }
+
+ /* reaquire the elt pointer */
+ mc = (state->mail_stream
+ && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
+ && rawno <= state->mail_stream->nmsgs)
+ ? mail_elt(state->mail_stream, rawno) : NULL;
+
+ for(fp = ftbl; mc && fp->name; fp++){
+ flags = -1;
+ switch(fp->flag){
+ case F_SEEN:
+ if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
+ || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
+ flagit = "\\SEEN";
+ if(fp->set){
+ flags = 0L;
+ unflagged = F_SEEN;
+ }
+ else{
+ flags = ST_SET;
+ unflagged = F_UNSEEN;
+ }
+ }
+
+ break;
+
+ case F_ANS:
+ if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
+ || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
+ flagit = "\\ANSWERED";
+ if(fp->set){
+ flags = ST_SET;
+ unflagged = F_UNANS;
+ }
+ else{
+ flags = 0L;
+ unflagged = F_ANS;
+ }
+ }
+
+ break;
+
+ case F_DEL:
+ if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
+ || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
+ flagit = "\\DELETED";
+ if(fp->set){
+ flags = ST_SET;
+ unflagged = F_UNDEL;
+ }
+ else{
+ flags = 0L;
+ unflagged = F_DEL;
+ }
+ }
+
+ break;
+
+ case F_FLAG:
+ if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
+ || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
+ flagit = "\\FLAGGED";
+ if(fp->set){
+ flags = ST_SET;
+ unflagged = F_UNFLAG;
+ }
+ else{
+ flags = 0L;
+ unflagged = F_FLAG;
+ }
+ }
+
+ break;
+
+ case F_FWD :
+ if(!MCMD_ISAGG(aopt)){
+ /* see if forwarded is defined for this message */
+ is_set = CMD_FLAG_CLEAR;
+ if(user_flag_is_set(state->mail_stream,
+ mn_m2raw(msgmap, mn_get_cur(msgmap)),
+ FORWARDED_FLAG))
+ is_set = CMD_FLAG_SET;
+ }
+
+ if((!MCMD_ISAGG(aopt) && fp->set != is_set)
+ || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
+ flagit = FORWARDED_FLAG;
+ if(fp->set){
+ flags = ST_SET;
+ unflagged = F_UNFWD;
+ }
+ else{
+ flags = 0L;
+ unflagged = F_FWD;
+ }
+ }
+
+ break;
+
+ case F_KEYWORD:
+ if(!MCMD_ISAGG(aopt)){
+ /* see if this keyword is defined for this message */
+ is_set = CMD_FLAG_CLEAR;
+ if(user_flag_is_set(state->mail_stream,
+ mn_m2raw(msgmap, mn_get_cur(msgmap)),
+ fp->keyword))
+ is_set = CMD_FLAG_SET;
+ }
+
+ if((!MCMD_ISAGG(aopt) && fp->set != is_set)
+ || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
+ flagit = fp->keyword;
+ keyword_array[0] = fp->keyword;
+ keyword_array[1] = NULL;
+ if(fp->set){
+ flags = ST_SET;
+ unflagged = F_UNKEYWORD;
+ }
+ else{
+ flags = 0L;
+ unflagged = F_KEYWORD;
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ flagged = 0L;
+ if(flags >= 0L
+ && (seq = currentf_sequence(state->mail_stream, msgmap,
+ unflagged, &flagged, unflagged & F_DEL,
+ (fp->flag == F_KEYWORD
+ && unflagged == F_KEYWORD)
+ ? keyword_array : NULL,
+ (fp->flag == F_KEYWORD
+ && unflagged == F_UNKEYWORD)
+ ? keyword_array : NULL))){
+ /*
+ * For user keywords, we may have to create the flag in
+ * the folder if it doesn't already exist and we are setting
+ * it (as opposed to clearing it). Mail_flag will
+ * do that for us, but it's failure isn't very friendly
+ * error-wise. So we try to make it a little smoother.
+ */
+ if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
+ || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
+ && i < NUSERFLAGS))
+ mail_flag(state->mail_stream, seq, flagit, flags);
+ else{
+ /* trouble, see if we can add the user flag */
+ if(state->mail_stream->kwd_create)
+ mail_flag(state->mail_stream, seq, flagit, flags);
+ else{
+ trouble++;
+
+ if(some_user_flags_defined(state->mail_stream))
+ q_status_message(SM_ORDER, 3, 4,
+ _("No more keywords allowed in this folder!"));
+ else if(fp->flag == F_FWD)
+ q_status_message(SM_ORDER, 3, 4,
+ _("Cannot add keywords for this folder so cannot set Forwarded flag"));
+ else
+ q_status_message(SM_ORDER, 3, 4,
+ _("Cannot add keywords for this folder"));
+ }
+ }
+
+ fs_give((void **) &seq);
+ if(flagged && !trouble){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
+ (fp->set) ? "F" : "Unf",
+ MCMD_ISAGG(aopt) ? " " : "",
+ MCMD_ISAGG(aopt) ? long2string(flagged) : "",
+ (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
+ ? " (of " : "",
+ (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
+ ? comatose(mn_total_cur(msgmap)) : "",
+ (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
+ ? ")" : "",
+ MCMD_ISAGG(aopt) ? plural(flagged) : " ",
+ MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
+ fp->name);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
+ rv++;
+ }
+ }
+ }
+
+ free_flag_table(&ftbl);
+
+ if(directly_to_maint_screen)
+ goto go_again;
+
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ if(!answer)
+ q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
+
+ return rv;
+}
+
+
+/*----------------------------------------------------------------------
+ Offer concise status line flag prompt
+
+ Args: state -- Various satate info
+ flags -- flags to offer setting
+
+ Result: TRUE if flag to set specified in flags struct or FALSE otw
+
+ ----*/
+int
+cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
+{
+ int r, setflag = 1, first_char;
+ struct flag_table *fp;
+ ESCKEY_S *ek;
+ char *ftext, *ftext_not;
+ static char *flag_text =
+ N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
+ static char *flag_text_ak =
+ N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
+ static char *flag_text_not =
+ N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
+ static char *flag_text_ak_not =
+ N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
+
+ if(allow_keyword_shortcuts){
+ int cnt = 0;
+ ESCKEY_S *dp, *sp, *tp;
+
+ for(sp=flag_text_opt; sp->ch != -1; sp++)
+ cnt++;
+
+ for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
+ if(fp->flag == F_KEYWORD)
+ cnt++;
+
+ /* set up an ESCKEY_S list which includes invisible keys for keywords */
+ ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
+ memset(ek, 0, (cnt+1) * sizeof(*ek));
+ for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
+ *dp = *sp;
+
+ for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
+ if(fp->flag == F_KEYWORD){
+ first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
+ if(isascii(first_char) && isupper(first_char))
+ first_char = tolower((unsigned char) first_char);
+
+ /*
+ * Check to see if an earlier keyword in the list, or one of
+ * the builtin system letters already uses this character.
+ * If so, the first one wins.
+ */
+ for(tp=ek; tp->ch != 0; tp++)
+ if(tp->ch == first_char)
+ break;
+
+ if(tp->ch != 0)
+ continue; /* skip it, already used that char */
+
+ dp->ch = first_char;
+ dp->rval = first_char;
+ dp->name = "";
+ dp->label = "";
+ dp++;
+ }
+ }
+
+ dp->ch = -1;
+ ftext = _(flag_text_ak);
+ ftext_not = _(flag_text_ak_not);
+ }
+ else{
+ ek = flag_text_opt;
+ ftext = _(flag_text);
+ ftext_not = _(flag_text_not);
+ }
+
+ while(1){
+ r = radio_buttons(setflag ? ftext : ftext_not,
+ -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
+ NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
+ /*
+ * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
+ * being used otherwise. The keywords use up all the possible
+ * letters, so a negative number is good, but it has to be different
+ * from other negative return values.
+ */
+ if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
+ return(TRUE);
+ else if(r == 10) /* return and goto flag screen */
+ return(FALSE);
+ else if(r == '!') /* flip intention */
+ setflag = !setflag;
+ else
+ break;
+ }
+
+ for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
+ if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
+ if((r == 'n' && fp->flag == F_SEEN)
+ || (r == '*' && fp->flag == F_FLAG)
+ || (r == 'd' && fp->flag == F_DEL)
+ || (r == 'f' && fp->flag == F_FWD)
+ || (r == 'a' && fp->flag == F_ANS)){
+ fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
+ break;
+ }
+ }
+ else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
+ first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
+ if(isascii(first_char) && isupper(first_char))
+ first_char = tolower((unsigned char) first_char);
+
+ if(r == first_char){
+ fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
+ break;
+ }
+ }
+ }
+
+ if(ek != flag_text_opt)
+ fs_give((void **) &ek);
+
+ return(TRUE);
+}
+
+
+/*
+ * (*ft) is an array of flag_table entries.
+ */
+void
+free_flag_table(struct flag_table **ft)
+{
+ struct flag_table *fp;
+
+ if(ft && *ft){
+ for(fp = (*ft); fp->name; fp++){
+ if(fp->name)
+ fs_give((void **) &fp->name);
+
+ if(fp->keyword)
+ fs_give((void **) &fp->keyword);
+
+ if(fp->comment)
+ fs_give((void **) &fp->comment);
+ }
+
+ fs_give((void **) ft);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Execute REPLY message command
+
+ Args: state -- Various satate info
+ msgmap -- map of c-client to local message numbers
+
+ Result: reply sent or not
+
+ ----*/
+int
+cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt)
+{
+ int rv = 0;
+
+ if(any_messages(msgmap, NULL, "to Reply to")){
+ if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
+ return rv;
+
+ rv = reply(state, NULL);
+
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ state->mangled_screen = 1;
+ }
+
+ return rv;
+}
+
+
+/*----------------------------------------------------------------------
+ Execute FORWARD message command
+
+ Args: state -- Various satate info
+ msgmap -- map of c-client to local message numbers
+
+ Result: selected message[s] forwarded or not
+
+ ----*/
+int
+cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt)
+{
+ int rv = 0;
+
+ if(any_messages(msgmap, NULL, "to Forward")){
+ if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
+ return rv;
+
+ rv = forward(state, NULL);
+
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ state->mangled_screen = 1;
+ }
+
+ return rv;
+}
+
+
+/*----------------------------------------------------------------------
+ Execute BOUNCE message command
+
+ Args: state -- Various satate info
+ msgmap -- map of c-client to local message numbers
+ aopt -- aggregate options
+
+ Result: selected message[s] bounced or not
+
+ ----*/
+int
+cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt)
+{
+ int rv = 0;
+
+ if(any_messages(msgmap, NULL, "to Bounce")){
+ if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
+ return rv;
+
+ rv = bounce(state, NULL);
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ state->mangled_footer = 1;
+ }
+
+ return rv;
+}
+
+
+/*----------------------------------------------------------------------
+ Execute save message command: prompt for folder and call function to save
+
+ Args: screen_line -- Line on the screen to prompt on
+ message -- The MESSAGECACHE entry of message to save
+
+ Result: The folder lister can be called to make selection; mangled screen set
+
+ This does the prompting for the folder name to save to, possibly calling
+ up the folder display for selection of folder by user.
+ ----*/
+int
+cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
+{
+ char newfolder[MAILTMPLEN], nmsgs[32], *nick;
+ int we_cancel = 0, rv = 0, save_flags;
+ long i, raw;
+ CONTEXT_S *cntxt = NULL;
+ ENVELOPE *e = NULL;
+ SaveDel del = DontAsk;
+ SavePreserveOrder pre = DontAskPreserve;
+
+ dprint((4, "\n - saving message -\n"));
+
+ state->ugly_consider_advancing_bit = 0;
+ if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
+ && msgno_any_deletedparts(stream, msgmap)
+ && want_to(_("Saved copy will NOT include entire message! Continue"),
+ 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
+ cmd_cancelled("Save message");
+ return rv;
+ }
+
+ if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
+ return rv;
+
+ raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
+
+ if(mn_total_cur(msgmap) <= 1L){
+ snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
+ nmsgs[sizeof(nmsgs)-1] = '\0';
+ e = pine_mail_fetchstructure(stream, raw, NULL);
+ if(!e) {
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Can't save message. Error accessing folder"));
+ restore_selected(msgmap);
+ return rv;
+ }
+ }
+ else{
+ snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
+ nmsgs[sizeof(nmsgs)-1] = '\0';
+
+ /* e is just used to get a default save folder from the first msg */
+ e = pine_mail_fetchstructure(stream,
+ mn_m2raw(msgmap, mn_first_cur(msgmap)),
+ NULL);
+ }
+
+ del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
+ ? Del : NoDel;
+ if(mn_total_cur(msgmap) > 1L)
+ pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
+ else
+ pre = DontAskPreserve;
+
+ if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
+ raw, NULL, &del, &pre)){
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ save_flags = SV_FIX_DELS;
+ if(pre == RetPreserve)
+ save_flags |= SV_PRESERVE;
+ if(del == RetDel)
+ save_flags |= SV_DELETE;
+ if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
+ save_flags |= SV_INBOXWOCNTXT;
+
+ we_cancel = busy_cue(_("Saving"), NULL, 1);
+ i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(i == mn_total_cur(msgmap)){
+ rv++;
+ if(mn_total_cur(msgmap) <= 1L){
+ int need, avail = ps_global->ttyo->screen_cols - 2;
+ int lennick, lenfldr;
+
+ if(cntxt
+ && ps_global->context_list->next
+ && context_isambig(newfolder)){
+ lennick = MIN(strlen(cntxt->nickname), 500);
+ lenfldr = MIN(strlen(newfolder), 500);
+ need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
+ lenfldr + lennick;
+ if(need > avail){
+ if(lennick > 10){
+ need -= MIN(lennick-10, need-avail);
+ lennick -= MIN(lennick-10, need-avail);
+ }
+
+ if(need > avail && lenfldr > 10)
+ lenfldr -= MIN(lenfldr-10, need-avail);
+ }
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Message %s copied to \"%s\" in <%s>",
+ long2string(mn_get_cur(msgmap)),
+ short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
+ lenfldr, MidDots),
+ short_str(cntxt->nickname,
+ (char *)(tmp_20k_buf+2000), 1000,
+ lennick, EndDots));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+ else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Message %s copied to \"%s\"",
+ long2string(mn_get_cur(msgmap)),
+ nick);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+ else{
+ char *f = " folder";
+
+ lenfldr = MIN(strlen(newfolder), 500);
+ need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
+ lenfldr;
+ if(need > avail){
+ need -= strlen(f);
+ f = "";
+ if(need > avail && lenfldr > 10)
+ lenfldr -= MIN(lenfldr-10, need-avail);
+ }
+
+ snprintf(tmp_20k_buf,SIZEOF_20KBUF,
+ "Message %s copied to%s \"%s\"",
+ long2string(mn_get_cur(msgmap)), f,
+ short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
+ lenfldr, MidDots));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
+ comatose(mn_total_cur(msgmap)));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+
+ if(del == RetDel){
+ strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+
+ q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
+
+ if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
+ if(sp_new_mail_count(stream))
+ process_filter_patterns(stream, msgmap,
+ sp_new_mail_count(stream));
+
+ mn_inc_cur(stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ }
+
+ state->ugly_consider_advancing_bit = 1;
+ }
+ }
+
+ if(MCMD_ISAGG(aopt)) /* straighten out fakes */
+ restore_selected(msgmap);
+
+ if(del == RetDel)
+ update_titlebar_status(); /* make sure they see change */
+
+ return rv;
+}
+
+
+void
+role_compose(struct pine *state)
+{
+ int action;
+
+ if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
+ PAT_STATE pstate;
+
+ if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
+ action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
+ -FOOTER_ROWS(state), choose_action,
+ 'c', 'x', h_role_compose, RB_NORM);
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 3,
+ _("No roles available. Use Setup/Rules to add roles."));
+ return;
+ }
+ }
+ else
+ action = 'c';
+
+ if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
+ ACTION_S *role = NULL;
+ void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
+
+ redraw = state->redrawer;
+ state->redrawer = NULL;
+ prev_screen = state->prev_screen;
+ role = NULL;
+ state->next_screen = SCREEN_FUN_NULL;
+
+ /* Setup role */
+ if(role_select_screen(state, &role,
+ action == 'f' ? MC_FORWARD :
+ action == 'r' ? MC_REPLY :
+ action == 'b' ? MC_BOUNCE :
+ action == 'c' ? MC_COMPOSE : 0) < 0){
+ cmd_cancelled(action == 'f' ? _("Forward") :
+ action == 'r' ? _("Reply") :
+ action == 'c' ? _("Composition") : _("Bounce"));
+ state->next_screen = prev_screen;
+ state->redrawer = redraw;
+ state->mangled_screen = 1;
+ }
+ else{
+ /*
+ * If default role was selected (NULL) we need to make
+ * up a role which won't do anything, but will cause
+ * compose_mail to think there's already a role so that
+ * it won't try to confirm the default.
+ */
+ if(role)
+ role = combine_inherited_role(role);
+ else{
+ role = (ACTION_S *) fs_get(sizeof(*role));
+ memset((void *) role, 0, sizeof(*role));
+ role->nick = cpystr("Default Role");
+ }
+
+ state->redrawer = NULL;
+ switch(action){
+ case 'c':
+ compose_mail(NULL, NULL, role, NULL, NULL);
+ break;
+
+ case 'r':
+ (void) reply(state, role);
+ break;
+
+ case 'f':
+ (void) forward(state, role);
+ break;
+
+ case 'b':
+ (void) bounce(state, role);
+ break;
+ }
+
+ if(role)
+ free_action(&role);
+
+ state->next_screen = prev_screen;
+ state->redrawer = redraw;
+ state->mangled_screen = 1;
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Do the dirty work of prompting the user for a folder name
+
+ Args:
+ nfldr should be a buffer at least MAILTMPLEN long
+ dela -- a pointer to a SaveDel. If it is
+ DontAsk on input, don't offer Delete prompt
+ Del on input, offer Delete command with default of Delete
+ NoDel NoDelete
+ RetDel and RetNoDel are return values
+
+
+ Result:
+
+ ----*/
+int
+save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
+ char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
+ SaveDel *dela, SavePreserveOrder *prea)
+{
+ int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
+ int delindex, preindex, r;
+ char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
+ char *buf = tmp_20k_buf;
+ char shortbuf[200];
+ char *folder;
+ HelpType help;
+ SaveDel del = DontAsk;
+ SavePreserveOrder pre = DontAskPreserve;
+ char *deltext = NULL;
+ static HISTORY_S *history = NULL;
+ CONTEXT_S *tc;
+ ESCKEY_S ekey[10];
+
+ if(!cntxt)
+ panic("no context ptr in save_prompt");
+
+ init_hist(&history, HISTSIZE);
+
+ if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
+ return(0); /* message expunged! */
+
+ /* how many context's can be saved to... */
+ for(tc = state->context_list; tc; tc = tc->next)
+ if(!NEWS_TEST(tc))
+ saveable_count++;
+
+ /* set up extra command option keys */
+ rc = 0;
+ ekey[rc].ch = ctrl('T');
+ ekey[rc].rval = 2;
+ ekey[rc].name = "^T";
+ /* TRANSLATORS: command means go to Folders list */
+ ekey[rc++].label = N_("To Fldrs");
+
+ if(saveable_count > 1){
+ ekey[rc].ch = ctrl('P');
+ ekey[rc].rval = 10;
+ ekey[rc].name = "^P";
+ ekey[rc++].label = N_("Prev Collection");
+
+ ekey[rc].ch = ctrl('N');
+ ekey[rc].rval = 11;
+ ekey[rc].name = "^N";
+ ekey[rc++].label = N_("Next Collection");
+ }
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
+ ekey[rc].ch = TAB;
+ ekey[rc].rval = 12;
+ ekey[rc].name = "TAB";
+ /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
+ ekey[rc++].label = N_("Complete");
+ }
+
+ if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
+ ekey[rc].ch = ctrl('X');
+ ekey[rc].rval = 14;
+ ekey[rc].name = "^X";
+ /* TRANSLATORS: list all the matches */
+ ekey[rc++].label = N_("ListMatches");
+ }
+
+ if(dela && (*dela == NoDel || *dela == Del)){
+ ekey[rc].ch = ctrl('R');
+ ekey[rc].rval = 15;
+ ekey[rc].name = "^R";
+ delindex = rc++;
+ del = *dela;
+ }
+
+ if(prea && (*prea == NoPreserve || *prea == Preserve)){
+ ekey[rc].ch = ctrl('W');
+ ekey[rc].rval = 16;
+ ekey[rc].name = "^W";
+ preindex = rc++;
+ pre = *prea;
+ }
+
+ if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
+ ekey[rc].ch = KEY_UP;
+ ekey[rc].rval = 10;
+ ekey[rc].name = "";
+ ekey[rc++].label = "";
+
+ ekey[rc].ch = KEY_DOWN;
+ ekey[rc].rval = 11;
+ ekey[rc].name = "";
+ ekey[rc++].label = "";
+ }
+ else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
+ ekey[rc].ch = KEY_UP;
+ ekey[rc].rval = 30;
+ ekey[rc].name = "";
+ ku = rc;
+ ekey[rc++].label = "";
+
+ ekey[rc].ch = KEY_DOWN;
+ ekey[rc].rval = 31;
+ ekey[rc].name = "";
+ ekey[rc++].label = "";
+ }
+
+ ekey[rc].ch = -1;
+
+ *nfldr = '\0';
+ help = NO_HELP;
+ while(!done){
+ /* only show collection number if more than one available */
+ if(ps_global->context_list->next)
+ snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
+ deltext ? deltext : "",
+ nmsgs,
+ short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
+ strsquish(buf, SIZEOF_20KBUF, folder, 25));
+ else
+ snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
+ deltext ? deltext : "",
+ nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ /*
+ * If the prompt won't fit, try removing deltext.
+ */
+ if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
+ if(ps_global->context_list->next)
+ snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
+ nmsgs,
+ short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
+ strsquish(buf, SIZEOF_20KBUF, folder, 25));
+ else
+ snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
+ nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
+
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+ /*
+ * If the prompt still won't fit, remove the extra info contained
+ * in nmsgs.
+ */
+ if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
+ if(ps_global->context_list->next)
+ snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
+ short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
+ strsquish(buf, SIZEOF_20KBUF, folder, 25));
+ else
+ snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
+ strsquish(buf, SIZEOF_20KBUF, folder, 25));
+
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+ if(del != DontAsk)
+ ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
+
+ if(pre != DontAskPreserve)
+ ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
+
+ if(ku >= 0){
+ if(items_in_hist(history) > 1){
+ ekey[ku].name = HISTORY_UP_KEYNAME;
+ ekey[ku].label = HISTORY_KEYLABEL;
+ ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
+ ekey[ku+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ ekey[ku].name = "";
+ ekey[ku].label = "";
+ ekey[ku+1].name = "";
+ ekey[ku+1].label = "";
+ }
+ }
+
+ flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
+ rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
+ prompt, ekey, help, &flags);
+
+ switch(rc){
+ case -1 :
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error reading folder name"));
+ done--;
+ break;
+
+ case 0 :
+ removing_trailing_white_space(nfldr);
+ removing_leading_white_space(nfldr);
+
+ if(*nfldr || *folder){
+ char *p, *name, *fullname = NULL;
+ int exists, breakout = FALSE;
+
+ if(!*nfldr){
+ strncpy(nfldr, folder, len_nfldr-1);
+ nfldr[len_nfldr-1] = '\0';
+ }
+
+ save_hist(history, nfldr, 0, (void *) *cntxt);
+
+ if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
+ name = nfldr;
+
+ if(update_folder_spec(expanded, sizeof(expanded), name)){
+ strncpy(name = nfldr, expanded, len_nfldr-1);
+ nfldr[len_nfldr-1] = '\0';
+ }
+
+ exists = folder_name_exists(*cntxt, name, &fullname);
+
+ if(exists == FEX_ERROR){
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Problem accessing folder \"%s\""),
+ nfldr);
+ done--;
+ }
+ else{
+ if(fullname){
+ strncpy(name = nfldr, fullname, len_nfldr-1);
+ nfldr[len_nfldr-1] = '\0';
+ fs_give((void **) &fullname);
+ breakout = TRUE;
+ }
+
+ if(exists & FEX_ISFILE){
+ done++;
+ }
+ else if((exists & FEX_ISDIR)){
+ char tmp[MAILTMPLEN];
+
+ tc = *cntxt;
+ if(breakout){
+ CONTEXT_S *fake_context;
+ size_t l;
+
+ strncpy(tmp, name, sizeof(tmp));
+ tmp[sizeof(tmp)-2-1] = '\0';
+ if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
+ if(l < sizeof(tmp)){
+ tmp[l] = tc->dir->delim;
+ strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
+ }
+ }
+ else
+ strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ fake_context = new_context(tmp, 0);
+ nfldr[0] = '\0';
+ done = display_folder_list(&fake_context, nfldr,
+ 1, folders_for_save);
+ free_context(&fake_context);
+ }
+ else if(tc->dir->delim
+ && (p = strrindex(name, tc->dir->delim))
+ && *(p+1) == '\0')
+ done = display_folder_list(cntxt, nfldr,
+ 1, folders_for_save);
+ else{
+ q_status_message1(SM_ORDER, 3, 3,
+ _("\"%s\" is a directory"), name);
+ if(tc->dir->delim
+ && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
+ strncpy(tmp, name, sizeof(tmp));
+ tmp[sizeof(tmp)-1] = '\0';
+ snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
+ }
+ }
+ }
+ else{ /* Doesn't exist, create! */
+ if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
+ strncpy(name = nfldr, fullname, len_nfldr-1);
+ nfldr[len_nfldr-1] = '\0';
+ fs_give((void **) &fullname);
+ }
+
+ switch(create_for_save(*cntxt, name)){
+ case 1 : /* success */
+ done++;
+ break;
+ case 0 : /* error */
+ case -1 : /* declined */
+ done--;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ /* else fall thru like they cancelled */
+
+ case 1 :
+ cmd_cancelled("Save message");
+ done--;
+ break;
+
+ case 2 :
+ r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
+
+ if(r)
+ done++;
+
+ break;
+
+ case 3 :
+ helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
+ ps_global->mangled_screen = 1;
+ break;
+
+ case 4 : /* redraw */
+ break;
+
+ case 10 : /* previous collection */
+ for(tc = (*cntxt)->prev; tc; tc = tc->prev)
+ if(!NEWS_TEST(tc))
+ break;
+
+ if(!tc){
+ CONTEXT_S *tc2;
+
+ for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
+ if(!NEWS_TEST(tc2))
+ tc = tc2;
+ }
+
+ *cntxt = tc;
+ break;
+
+ case 11 : /* next collection */
+ tc = (*cntxt);
+
+ do
+ if(((*cntxt) = (*cntxt)->next) == NULL)
+ (*cntxt) = ps_global->context_list;
+ while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
+ break;
+
+ case 12 : /* file name completion */
+ if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
+ if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
+ r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
+ if(r)
+ done++; /* bingo! */
+ else
+ rc = 0; /* burn last_rc */
+ }
+ else
+ Writechar(BELL, 0);
+ }
+
+ break;
+
+ case 14 : /* file name completion */
+ r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
+ if(r)
+ done++; /* bingo! */
+ else
+ rc = 0; /* burn last_rc */
+
+ break;
+
+ case 15 : /* Delete / No Delete */
+ del = (del == NoDel) ? Del : NoDel;
+ deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
+ break;
+
+ case 16 : /* Preserve Order or not */
+ pre = (pre == NoPreserve) ? Preserve : NoPreserve;
+ break;
+
+ case 30 :
+ if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
+ strncpy(nfldr, p, len_nfldr);
+ nfldr[len_nfldr-1] = '\0';
+ if(history->hist[history->curindex])
+ *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+ case 31 :
+ if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
+ strncpy(nfldr, p, len_nfldr);
+ nfldr[len_nfldr-1] = '\0';
+ if(history->hist[history->curindex])
+ *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+ default :
+ panic("Unhandled case");
+ break;
+ }
+
+ last_rc = rc;
+ }
+
+ ps_global->mangled_footer = 1;
+
+ if(done < 0)
+ return(0);
+
+ if(*nfldr){
+ strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
+ ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
+ if(*cntxt)
+ ps_global->last_save_context = *cntxt;
+ }
+ else{
+ strncpy(nfldr, folder, len_nfldr-1);
+ nfldr[len_nfldr-1] = '\0';
+ }
+
+ /* nickname? Copy real name to nfldr */
+ if(*cntxt
+ && context_isambig(nfldr)
+ && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
+ strncpy(nfldr, p, len_nfldr-1);
+ nfldr[len_nfldr-1] = '\0';
+ }
+
+ if(dela && (*dela == NoDel || *dela == Del))
+ *dela = (del == NoDel) ? RetNoDel : RetDel;
+
+ if(prea && (*prea == NoPreserve || *prea == Preserve))
+ *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Prompt user before implicitly creating a folder for saving
+
+ Args: context - context to create folder in
+ folder - folder name to create
+
+ Result: 1 on proceed, -1 on decline, 0 on error
+
+ ----*/
+int
+create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
+{
+ if(context && ps_global->context_list->next && context_isambig(folder)){
+ if(context->use & CNTXT_INCMNG){
+ snprintf(tmp_20k_buf,SIZEOF_20KBUF,
+ _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
+ folder, (strlen(folder) > 15) ? "..." : "");
+ q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
+ return(0); /* error */
+ }
+
+ snprintf(tmp_20k_buf,SIZEOF_20KBUF,
+ _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
+ folder, (strlen(folder) > 15) ? "..." : "",
+ context->nickname,
+ (strlen(context->nickname) > 15) ? "..." : "");
+ }
+ else
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ _("Folder \"%.40s%s\" doesn't exist. Create"),
+ folder, strlen(folder) > 40 ? "..." : "");
+
+ if(want_to(tmp_20k_buf, 'y', 'n',
+ NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
+ cmd_cancelled("Save message");
+ return(-1);
+ }
+
+ return(1);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Expunge messages from current folder
+
+ Args: state -- pointer to struct holding a bunch of pine state
+ msgmap -- table mapping msg nums to c-client sequence nums
+ qline -- screen line to ask questions on
+ agg -- boolean indicating we're to operate on aggregate set
+
+ Result:
+ ----*/
+void
+cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ long del_count, prefilter_del_count;
+ int we_cancel = 0;
+ char prompt[MAX_SCREEN_COLS+1];
+ COLOR_PAIR *lastc = NULL;
+
+ dprint((2, "\n - expunge -\n"));
+
+ if(IS_NEWS(stream) && stream->rdonly){
+ if((del_count = count_flagged(stream, F_DEL)) > 0L){
+ state->mangled_footer = 1;
+ snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
+ plural(del_count), sizeof(prompt)-40,
+ pretty_fn(state->cur_folder));
+ prompt[sizeof(prompt)-1] = '\0';
+ if(F_ON(F_FULL_AUTO_EXPUNGE, state)
+ || (F_ON(F_AUTO_EXPUNGE, state)
+ && (state->context_current
+ && (state->context_current->use & CNTXT_INCMNG))
+ && context_isambig(state->cur_folder))
+ || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
+
+ if(F_ON(F_NEWS_CROSS_DELETE, state))
+ cross_delete_crossposts(stream);
+
+ msgno_exclude_deleted(stream, msgmap);
+ clear_index_cache(stream, 0);
+
+ /*
+ * This is kind of surprising at first. For most sort
+ * orders, if the whole set is sorted, then any subset
+ * is also sorted. Not so for threaded sorts.
+ */
+ if(SORT_IS_THREADED(msgmap))
+ refresh_sort(stream, msgmap, SRT_NON);
+
+ state->mangled_body = 1;
+ state->mangled_header = 1;
+ q_status_message2(SM_ORDER, 0, 4,
+ "%s message%s excluded",
+ long2string(del_count),
+ plural(del_count));
+ }
+ else
+ any_messages(NULL, NULL, "Excluded");
+ }
+ else
+ any_messages(NULL, "deleted", "to Exclude");
+
+ return;
+ }
+ else if(READONLY_FOLDER(stream)){
+ q_status_message(SM_ORDER, 0, 4,
+ _("Can't expunge. Folder is read-only"));
+ return;
+ }
+
+ prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
+
+ mail_expunge_prefilter(stream, MI_NONE);
+
+ if((del_count = count_flagged(stream, F_DEL|F_NOFILT)) != 0){
+ int ret;
+
+ snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
+ plural(del_count), sizeof(prompt)-40,
+ pretty_fn(state->cur_folder));
+ prompt[sizeof(prompt)-1] = '\0';
+ state->mangled_footer = 1;
+
+ if(F_ON(F_FULL_AUTO_EXPUNGE, state)
+ || (F_ON(F_AUTO_EXPUNGE, state)
+ && ((!strucmp(state->cur_folder,state->inbox_name))
+ || (state->context_current->use & CNTXT_INCMNG))
+ && context_isambig(state->cur_folder))
+ || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
+ ret = 'y';
+
+ if(ret == 'x')
+ cmd_cancelled("Expunge");
+
+ if(ret != 'y')
+ return;
+ }
+
+ dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
+ mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
+
+ lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
+ state->VAR_TITLE_BACK_COLOR,
+ PSC_REV|PSC_RET);
+
+ PutLine0(0, 0, "**"); /* indicate delay */
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
+ fflush(stdout);
+
+ we_cancel = busy_cue(_("Expunging"), NULL, 1);
+
+ if(cmd_expunge_work(stream, msgmap))
+ state->mangled_body = 1;
+
+ if(we_cancel)
+ cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
+
+ lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
+ state->VAR_TITLE_BACK_COLOR,
+ PSC_REV|PSC_RET);
+ PutLine0(0, 0, " "); /* indicate delay's over */
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ fflush(stdout);
+
+ if(sp_expunge_count(stream) > 0){
+ /*
+ * This is kind of surprising at first. For most sort
+ * orders, if the whole set is sorted, then any subset
+ * is also sorted. Not so for threaded sorts.
+ */
+ if(SORT_IS_THREADED(msgmap))
+ refresh_sort(stream, msgmap, SRT_NON);
+ }
+ else{
+ if(del_count)
+ q_status_message1(SM_ORDER, 0, 3,
+ _("No messages expunged from folder \"%s\""),
+ pretty_fn(state->cur_folder));
+ else if(!prefilter_del_count)
+ q_status_message(SM_ORDER, 0, 3,
+ _("No messages marked deleted. No messages expunged."));
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Expunge_and_close callback to prompt user for confirmation
+
+ Args: stream -- folder's stream
+ folder -- name of folder containing folders
+ deleted -- number of del'd msgs
+
+ Result: 'y' to continue with expunge
+ ----*/
+int
+expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
+{
+ long max_folder;
+ int charcnt = 0;
+ char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
+ char *short_folder_name;
+
+ if(deleted == 1)
+ charcnt = 1;
+ else{
+ snprintf(temp, sizeof(temp), "%ld", deleted);
+ charcnt = strlen(temp)+1;
+ }
+
+ max_folder = MAX(1,MAXPROMPT - (36+charcnt));
+ strncpy(temp, folder, sizeof(temp));
+ temp[sizeof(temp)-1] = '\0';
+ short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
+
+ if(IS_NEWS(stream))
+ snprintf(prompt_b, sizeof(prompt_b),
+ "Delete %s%ld message%s from \"%s\"",
+ (deleted > 1L) ? "all " : "", deleted,
+ plural(deleted), short_folder_name);
+ else
+ snprintf(prompt_b, sizeof(prompt_b),
+ "Expunge the %ld deleted message%s from \"%s\"",
+ deleted, deleted == 1 ? "" : "s",
+ short_folder_name);
+
+ return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
+}
+
+
+/*
+ * This is used with multiple append saves. Call it once before
+ * the series of appends with SSCP_INIT and once after all are
+ * done with SSCP_END. In between, it is called automatically
+ * from save_fetch_append or save_fetch_append_cb when we need
+ * to ask the user if he or she wants to continue even though
+ * announced message size doesn't match the actual message size.
+ * As of 2008-02-29 the gmail IMAP server has these size mismatches
+ * on a regular basis even though the data is ok.
+ */
+int
+save_size_changed_prompt(long msgno, int flags)
+{
+ int ret;
+ char prompt[100];
+ static int remember_the_yes = 0;
+ static int possible_corruption = 0;
+ static ESCKEY_S save_size_opts[] = {
+ {'y', 'y', "Y", "Yes"},
+ {'n', 'n', "N", "No"},
+ {'a', 'a', "A", "yes to All"},
+ {-1, 0, NULL, NULL}
+ };
+
+ if(flags & SSCP_INIT || flags & SSCP_END){
+ if(flags & SSCP_END && possible_corruption)
+ q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
+
+ remember_the_yes = 0;
+ possible_corruption = 0;
+ ps_global->noshow_error = 0;
+ ps_global->noshow_warn = 0;
+ return(0);
+ }
+
+ if(remember_the_yes){
+ snprintf(prompt, sizeof(prompt),
+ "Message to save shrank! (msg # %ld): Continuing", msgno);
+ q_status_message(SM_ORDER, 0, 3, prompt);
+ display_message('x');
+ return(remember_the_yes);
+ }
+
+ snprintf(prompt, sizeof(prompt),
+ "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
+ ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
+ 'n', 0, h_save_size_changed, RB_NORM);
+
+ switch(ret){
+ case 'a':
+ remember_the_yes = 'y';
+ possible_corruption++;
+ return(remember_the_yes);
+
+ case 'y':
+ possible_corruption++;
+ return('y');
+
+ default:
+ possible_corruption = 0;
+ ps_global->noshow_error = 1;
+ ps_global->noshow_warn = 1;
+ break;
+ }
+
+ return('n');
+}
+
+
+/*----------------------------------------------------------------------
+ Expunge_and_close callback that happens once the decision to expunge
+ and close has been made and before expunging and closing begins
+
+
+ Args: stream -- folder's stream
+ folder -- name of folder containing folders
+ deleted -- number of del'd msgs
+
+ Result: 'y' to continue with expunge
+ ----*/
+void
+expunge_and_close_begins(int flags, char *folder)
+{
+ if(!(flags & EC_NO_CLOSE)){
+ q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", folder);
+ flush_status_messages(1);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Export a message to a plain file in users home directory
+
+ Args: state -- pointer to struct holding a bunch of pine state
+ msgmap -- table mapping msg nums to c-client sequence nums
+ qline -- screen line to ask questions on
+ agg -- boolean indicating we're to operate on aggregate set
+
+ Result:
+ ----*/
+int
+cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
+{
+ char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
+ char nmsgs[80];
+ int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
+ int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
+ ENVELOPE *env;
+ MESSAGECACHE *mc;
+ BODY *b;
+ long i, count = 0L, start_of_append, rawno;
+ gf_io_t pc;
+ STORE_S *store;
+ struct variable *vars = ps_global->vars;
+ ESCKEY_S export_opts[5];
+ static HISTORY_S *history = NULL;
+
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER, 0, 3,
+ "Alpine demo can't export messages to files");
+ return rv;
+ }
+
+ if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
+ return rv;
+
+ export_opts[i = 0].ch = ctrl('T');
+ export_opts[i].rval = 10;
+ export_opts[i].name = "^T";
+ export_opts[i++].label = N_("To Files");
+
+#if !defined(DOS) && !defined(MAC) && !defined(OS2)
+ if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
+ export_opts[i].ch = ctrl('V');
+ export_opts[i].rval = 12;
+ export_opts[i].name = "^V";
+ /* TRANSLATORS: this is an abbreviation for Download Messages */
+ export_opts[i++].label = N_("Downld Msg");
+ }
+#endif /* !(DOS || MAC) */
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ export_opts[i].ch = ctrl('I');
+ export_opts[i].rval = 11;
+ export_opts[i].name = "TAB";
+ export_opts[i++].label = N_("Complete");
+ }
+
+#if 0
+ /* Commented out since it's not yet support! */
+ if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
+ export_opts[i].ch = ctrl('X');
+ export_opts[i].rval = 14;
+ export_opts[i].name = "^X";
+ export_opts[i++].label = N_("ListMatches");
+ }
+#endif
+
+ /*
+ * If message has attachments, add a toggle that will allow the user
+ * to save all of the attachments to a single directory, using the
+ * names provided with the attachments or part names. What we'll do is
+ * export the message as usual, and then export the attachments into
+ * a subdirectory that did not exist before. The subdir will be named
+ * something based on the name of the file being saved to, but a
+ * unique, new name.
+ */
+ if(!MCMD_ISAGG(aopt)
+ && state->mail_stream
+ && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
+ && rawno <= state->mail_stream->nmsgs
+ && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
+ && b
+ && b->type == TYPEMULTIPART
+ && b->subtype
+ && strucmp(b->subtype, "ALTERNATIVE") != 0){
+ PART *part;
+
+ part = b->nested.part; /* 1st part */
+ if(part && part->next)
+ flags |= GE_ALLPARTS;
+ }
+
+ export_opts[i].ch = -1;
+ filename[0] = '\0';
+
+ if(mn_total_cur(msgmap) <= 1L){
+ snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
+ nmsgs[sizeof(nmsgs)-1] = '\0';
+ }
+ else{
+ snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
+ nmsgs[sizeof(nmsgs)-1] = '\0';
+ }
+
+ r = get_export_filename(state, filename, NULL, full_filename,
+ sizeof(filename), nmsgs, "EXPORT",
+ export_opts, &rflags, qline, flags, &history);
+
+ if(r < 0){
+ switch(r){
+ case -1:
+ cmd_cancelled("Export message");
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't export to file outside of %s"),
+ VAR_OPER_DIR);
+ break;
+ }
+
+ goto fini;
+ }
+#if !defined(DOS) && !defined(MAC) && !defined(OS2)
+ else if(r == 12){ /* Download */
+ char cmd[MAXPATH], *tfp = NULL;
+ int next = 0;
+ PIPE_S *syspipe;
+ STORE_S *so;
+ gf_io_t pc;
+
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Download disallowed in restricted mode");
+ goto fini;
+ }
+
+ err = NULL;
+ tfp = temp_nam(NULL, "pd");
+ build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
+ ps_global->VAR_DOWNLOAD_CMD, tfp);
+ dprint((1, "Download cmd called: \"%s\"\n", cmd));
+ if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
+ gf_set_so_writec(&pc, so);
+
+ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
+ if(!(state->mail_stream
+ && (rawno = mn_m2raw(msgmap, i)) > 0L
+ && rawno <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, rawno))
+ && mc->valid))
+ mc = NULL;
+
+ if(!(env = pine_mail_fetchstructure(state->mail_stream,
+ mn_m2raw(msgmap, i), &b))
+ || !bezerk_delimiter(env, mc, pc, next++)
+ || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
+ env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ err = "Error writing tempfile for download");
+ break;
+ }
+ }
+
+ gf_clear_so_writec(so);
+ if(so_give(&so)){ /* close file */
+ if(!err)
+ err = "Error writing tempfile for download";
+ }
+
+ if(!err){
+ if((syspipe = open_system_pipe(cmd, NULL, NULL,
+ PIPE_USER | PIPE_RESET,
+ 0, pipe_callback, pipe_report_error)) != NULL)
+ (void) close_system_pipe(&syspipe, NULL, pipe_callback);
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ err = _("Error running download command"));
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ err = "Error building temp file for download");
+
+ if(tfp){
+ our_unlink(tfp);
+ fs_give((void **)&tfp);
+ }
+
+ if(!err)
+ q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
+
+ goto fini;
+ }
+#endif /* !(DOS || MAC) */
+
+
+ if(rflags & GER_APPEND)
+ leading_nl = 1;
+ else
+ leading_nl = 0;
+
+ dprint((5, "Opening file \"%s\" for export\n",
+ full_filename ? full_filename : "?"));
+
+ if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
+ _("Error opening file \"%s\" to export message: %s"),
+ full_filename, error_description(errno));
+ goto fini;
+ }
+ else
+ gf_set_so_writec(&pc, store);
+
+ err = NULL;
+ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
+ env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
+ &b);
+ if(!env) {
+ err = _("Can't export message. Error accessing mail folder");
+ failure = 1;
+ break;
+ }
+
+ if(!(state->mail_stream
+ && (rawno = mn_m2raw(msgmap, i)) > 0L
+ && rawno <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, rawno))
+ && mc->valid))
+ mc = NULL;
+
+ start_of_append = so_tell(store);
+ if(!bezerk_delimiter(env, mc, pc, leading_nl)
+ || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
+ FM_NEW_MESS | FM_NOWRAP, pc)){
+ orig_errno = errno; /* save incase things are really bad */
+ failure = 1; /* pop out of here */
+ break;
+ }
+
+ leading_nl = 1;
+ }
+
+ gf_clear_so_writec(store);
+ if(so_give(&store)) /* release storage */
+ failure++;
+
+ if(failure){
+ our_truncate(full_filename, (off_t)start_of_append);
+ if(err){
+ dprint((1, "FAILED Export: fetch(%ld): %s\n",
+ i, err ? err : "?"));
+ q_status_message(SM_ORDER | SM_DING, 3, 4, err);
+ }
+ else{
+ dprint((1, "FAILED Export: file \"%s\" : %s\n",
+ full_filename ? full_filename : "?",
+ error_description(orig_errno)));
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: Error exporting to <filename>: <error text> */
+ _("Error exporting to \"%s\" : %s"),
+ filename, error_description(orig_errno));
+ }
+ }
+ else{
+ if(rflags & GER_ALLPARTS && full_filename[0]){
+ char dir[MAXPATH+1];
+ char lfile[MAXPATH+1];
+ int ok = 0, tries = 0, saved = 0, errs = 0;
+ ATTACH_S *a;
+
+ /*
+ * Now we want to save all of the attachments to a subdirectory.
+ * To make it easier for us and probably easier for the user, and
+ * to prevent the user from shooting himself in the foot, we
+ * make a new subdirectory so that we can't possibly step on
+ * any existing files, and we don't need any interaction with the
+ * user while saving.
+ *
+ * We'll just use the directory name full_filename.d or if that
+ * already exists and isn't empty, we'll try adding a suffix to
+ * that until we get something to use.
+ */
+
+ if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Can't save attachments, filename too long: %s"),
+ full_filename);
+ goto fini;
+ }
+
+ ok = 0;
+ snprintf(dir, sizeof(dir), "%s.d", full_filename);
+ dir[sizeof(dir)-1] = '\0';
+
+ do {
+ tries++;
+ switch(r = is_writable_dir(dir)){
+ case 0: /* exists and is a writable dir */
+ /*
+ * We could figure out if it is empty and use it in
+ * that case, but that sounds like a lot of work, so
+ * just fall through to default.
+ */
+
+ default:
+ if(strlen(full_filename) + strlen(".d") + 1 +
+ 1 + strlen(long2string((long) tries)) > sizeof(dir)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Problem saving attachments");
+ goto fini;
+ }
+
+ snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
+ long2string((long) tries));
+ dir[sizeof(dir)-1] = '\0';
+ break;
+
+ case 3: /* doesn't exist, that's good! */
+ /* make new directory */
+ ok++;
+ break;
+ }
+ } while(!ok && tries < 1000);
+
+ if(tries >= 1000){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Problem saving attachments"));
+ goto fini;
+ }
+
+ /* create the new directory */
+ if(our_mkdir(dir, 0700)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Problem saving attachments: %s: %s"), dir,
+ error_description(errno));
+ goto fini;
+ }
+
+ if(!(state->mail_stream
+ && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
+ && rawno <= state->mail_stream->nmsgs
+ && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
+ && b)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Problem reading message"));
+ goto fini;
+ }
+
+ zero_atmts(state->atmts);
+ describe_mime(b, "", 1, 1, 0, 0);
+
+ a = state->atmts;
+ if(a && a->description) /* skip main body part */
+ a++;
+
+ for(; a->description != NULL; a++){
+ /* skip over these parts of the message */
+ if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
+ continue;
+
+ lfile[0] = '\0';
+ (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
+
+ if(lfile[0] == '\0'){
+ snprintf(lfile, sizeof(lfile), "part_%.*s", sizeof(lfile)-6,
+ a->number ? a->number : "?");
+ lfile[sizeof(lfile)-1] = '\0';
+ }
+
+ if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
+ > sizeof(filename)){
+ dprint((2,
+ "FAILED Att Export: name too long: %s\n",
+ dir, S_FILESEP, lfile));
+ errs++;
+ continue;
+ }
+
+ snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
+ filename[sizeof(filename)-1] = '\0';
+
+ if(write_attachment_to_file(state->mail_stream, rawno,
+ a, GER_NONE, filename) == 1)
+ saved++;
+ else
+ errs++;
+ }
+
+ if(errs){
+ if(saved)
+ q_status_message1(SM_ORDER, 3, 3,
+ "Errors saving some attachments, %s attachments saved",
+ long2string((long) saved));
+ else
+ q_status_message(SM_ORDER, 3, 3,
+ _("Problems saving attachments"));
+ }
+ else{
+ if(saved)
+ q_status_message2(SM_ORDER, 0, 3,
+ /* TRANSLATORS: Saved <how many> attachements to <directory name> */
+ _("Saved %s attachments to %s"),
+ long2string((long) saved), dir);
+ else
+ q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
+ }
+ }
+ else if(mn_total_cur(msgmap) > 1L)
+ q_status_message4(SM_ORDER,0,3,
+ "%s message%s %s to file \"%s\"",
+ long2string(count), plural(count),
+ rflags & GER_OVER
+ ? "overwritten"
+ : rflags & GER_APPEND ? "appended" : "exported",
+ filename);
+ else
+ q_status_message3(SM_ORDER,0,3,
+ "Message %s %s to file \"%s\"",
+ long2string(mn_get_cur(msgmap)),
+ rflags & GER_OVER
+ ? "overwritten"
+ : rflags & GER_APPEND ? "appended" : "exported",
+ filename);
+ rv++;
+ }
+
+ fini:
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ return rv;
+}
+
+
+/*
+ * Ask user what file to export to. Export from srcstore to that file.
+ *
+ * Args ps -- pine struct
+ * srctext -- pointer to source text
+ * srctype -- type of that source text
+ * prompt_msg -- see get_export_filename
+ * lister_msg -- "
+ *
+ * Returns: != 0 : error
+ * 0 : ok
+ */
+int
+simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
+{
+ int r = 1, rflags = GER_NONE;
+ char filename[MAXPATH+1], full_filename[MAXPATH+1];
+ STORE_S *store = NULL;
+ struct variable *vars = ps->vars;
+ static HISTORY_S *history = NULL;
+ static ESCKEY_S simple_export_opts[] = {
+ {ctrl('T'), 10, "^T", N_("To Files")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}};
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
+ simple_export_opts[r].ch = ctrl('I');
+ simple_export_opts[r].rval = 11;
+ simple_export_opts[r].name = "TAB";
+ simple_export_opts[r].label = N_("Complete");
+ }
+
+ if(!srctext){
+ q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
+ r = -3;
+ goto fini;
+ }
+
+ simple_export_opts[++r].ch = -1;
+ filename[0] = '\0';
+ full_filename[0] = '\0';
+
+ r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
+ prompt_msg, lister_msg, simple_export_opts, &rflags,
+ -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
+
+ if(r < 0)
+ goto fini;
+ else if(!full_filename[0]){
+ r = -1;
+ goto fini;
+ }
+
+ dprint((5, "Opening file \"%s\" for export\n",
+ full_filename ? full_filename : "?"));
+
+ if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
+ char *pipe_err;
+ gf_io_t pc, gc;
+
+ gf_set_so_writec(&pc, store);
+ gf_set_readc(&gc, srctext, (srctype == CharStar)
+ ? strlen((char *)srctext)
+ : 0L,
+ srctype, 0);
+ gf_filter_init();
+ if((pipe_err = gf_pipe(gc, pc)) != NULL){
+ q_status_message2(SM_ORDER | SM_DING, 3, 3,
+ /* TRANSLATORS: Problem saving to <filename>: <error text> */
+ _("Problem saving to \"%s\": %s"),
+ filename, pipe_err);
+ r = -3;
+ }
+ else
+ r = 0;
+
+ gf_clear_so_writec(store);
+ if(so_give(&store)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 3,
+ _("Problem saving to \"%s\": %s"),
+ filename, error_description(errno));
+ r = -3;
+ }
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error opening file \"%s\" for export: %s"),
+ full_filename, error_description(errno));
+ r = -3;
+ }
+
+fini:
+ switch(r){
+ case 0:
+ /* overloading full_filename */
+ snprintf(full_filename, sizeof(full_filename), "%c%s",
+ (prompt_msg && prompt_msg[0])
+ ? (islower((unsigned char)prompt_msg[0])
+ ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
+ : 'T',
+ (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
+ full_filename[sizeof(full_filename)-1] = '\0';
+ q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
+ full_filename,
+ rflags & GER_OVER
+ ? "overwritten"
+ : rflags & GER_APPEND ? "appended" : "exported",
+ filename);
+ break;
+
+ case -1:
+ cmd_cancelled("Export");
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't export to file outside of %s"), VAR_OPER_DIR);
+ break;
+ }
+
+ ps->mangled_footer = 1;
+ return(r);
+}
+
+
+/*
+ * Ask user what file to export to.
+ *
+ * filename -- On input, this is the filename to start with. On exit,
+ * this is the filename chosen. (but this isn't used)
+ * deefault -- This is the default value if user hits return. The
+ * prompt will have [deefault] added to it automatically.
+ * full_filename -- This is the full filename on exit.
+ * len -- Minimum length of _both_ filename and full_filename.
+ * prompt_msg -- Message to insert in prompt.
+ * lister_msg -- Message to insert in file_lister.
+ * opts -- Key options.
+ * There is a tangled relationship between the callers
+ * and this routine as far as opts are concerned. Some
+ * of the opts are handled here. In particular, r == 3,
+ * r == 10, r == 11, and r == 13 are all handled here.
+ * Don't use those values unless you want what happens
+ * here. r == 12 and others are handled by the caller.
+ * rflags -- Return flags
+ * GER_OVER - overwrite of existing file
+ * GER_APPEND - append of existing file
+ * else file did not exist before
+ *
+ * GER_ALLPARTS - AllParts toggle was turned on
+ *
+ * qline -- Command line to prompt on.
+ * flags -- Logically OR'd flags
+ * GE_IS_EXPORT - The command was an Export command
+ * so the prompt should include
+ * EXPORT:.
+ * GE_SEQ_SENSITIVE - The command that got us here is
+ * sensitive to sequence number changes
+ * caused by unsolicited expunges.
+ * GE_NO_APPEND - We will not allow append to an
+ * existing file, only removal of the
+ * file if it exists.
+ * GE_IS_IMPORT - We are selecting for reading.
+ * No overwriting or checking for
+ * existence at all. Don't use this
+ * together with GE_NO_APPEND.
+ * GE_ALLPARTS - Turn on AllParts toggle.
+ *
+ * Returns: -1 cancelled
+ * -2 prohibited by VAR_OPER_DIR
+ * -3 other error, already reported here
+ * 0 ok
+ * 12 user chose 12 command from opts
+ */
+int
+get_export_filename(struct pine *ps, char *filename, char *deefault,
+ char *full_filename, size_t len, char *prompt_msg,
+ char *lister_msg, ESCKEY_S *optsarg, int *rflags,
+ int qline, int flags, HISTORY_S **history)
+{
+ char dir[MAXPATH+1], dir2[MAXPATH+1];
+ char precolon[MAXPATH+1], postcolon[MAXPATH+1];
+ char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
+ int l, i, ku = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
+ int allparts = 0;
+ char prompt_buf[400];
+ char def[500];
+ ESCKEY_S *opts = NULL;
+ struct variable *vars = ps->vars;
+
+ if(flags & GE_ALLPARTS || history){
+ /*
+ * Copy the opts and add one to the end of the list.
+ */
+ for(i = 0; optsarg[i].ch != -1; i++)
+ ;
+
+ if(history)
+ i += 2;
+
+ if(flags & GE_ALLPARTS)
+ i++;
+
+ opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
+ memset(opts, 0, (i+1) * sizeof(*opts));
+
+ for(i = 0; optsarg[i].ch != -1; i++){
+ opts[i].ch = optsarg[i].ch;
+ opts[i].rval = optsarg[i].rval;
+ opts[i].name = optsarg[i].name; /* no need to make a copy */
+ opts[i].label = optsarg[i].label; /* " */
+ }
+
+ if(flags & GE_ALLPARTS){
+ allparts = i;
+ opts[i].ch = ctrl('P');
+ opts[i].rval = 13;
+ opts[i].name = "^P";
+ /* TRANSLATORS: Export all attachment parts */
+ opts[i++].label = N_("AllParts");
+ }
+
+ if(history){
+ opts[i].ch = KEY_UP;
+ opts[i].rval = 30;
+ opts[i].name = "";
+ ku = i;
+ opts[i++].label = "";
+
+ opts[i].ch = KEY_DOWN;
+ opts[i].rval = 31;
+ opts[i].name = "";
+ opts[i++].label = "";
+ }
+
+ opts[i].ch = -1;
+
+ if(history)
+ init_hist(history, HISTSIZE);
+ }
+ else
+ opts = optsarg;
+
+ if(rflags)
+ *rflags = GER_NONE;
+
+ if(F_ON(F_USE_CURRENT_DIR, ps))
+ dir[0] = '\0';
+ else if(VAR_OPER_DIR){
+ strncpy(dir, VAR_OPER_DIR, sizeof(dir));
+ dir[sizeof(dir)-1] = '\0';
+ }
+#if defined(DOS) || defined(OS2)
+ else if(VAR_FILE_DIR){
+ strncpy(dir, VAR_FILE_DIR, sizeof(dir));
+ dir[sizeof(dir)-1] = '\0';
+ }
+#endif
+ else{
+ dir[0] = '~';
+ dir[1] = '\0';
+ homedir=1;
+ }
+
+ postcolon[0] = '\0';
+ strncpy(precolon, dir, sizeof(precolon));
+ precolon[sizeof(precolon)-1] = '\0';
+ if(deefault){
+ strncpy(def, deefault, sizeof(def)-1);
+ def[sizeof(def)-1] = '\0';
+ removing_leading_and_trailing_white_space(def);
+ }
+ else
+ def[0] = '\0';
+
+ avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
+
+ /*---------- Prompt the user for the file name -------------*/
+ while(1){
+ int oeflags;
+ char dirb[50], fileb[50];
+ int l1, l2, l3, l4, l5, needed;
+ char *p, p1[100], p2[100], *p3, p4[100], p5[100];
+
+ snprintf(p1, sizeof(p1), "%sCopy ",
+ (flags & GE_IS_EXPORT) ? "EXPORT: " :
+ (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
+ p1[sizeof(p1)-1] = '\0';
+ l1 = strlen(p1);
+
+ strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
+ p2[sizeof(p2)-1] = '\0';
+ l2 = strlen(p2);
+
+ if(rflags && *rflags & GER_ALLPARTS)
+ p3 = " (and atts)";
+ else
+ p3 = "";
+
+ l3 = strlen(p3);
+
+ snprintf(p4, sizeof(p4), " %s file%s%s",
+ (flags & GE_IS_IMPORT) ? "from" : "to",
+ is_absolute_path(filename) ? "" : " in ",
+ is_absolute_path(filename) ? "" :
+ (!dir[0] ? "current directory"
+ : (dir[0] == '~' && !dir[1]) ? "home directory"
+ : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
+ p4[sizeof(p4)-1] = '\0';
+ l4 = strlen(p4);
+
+ snprintf(p5, sizeof(p5), "%s%s%s: ",
+ *def ? " [" : "",
+ *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
+ *def ? "]" : "");
+ p5[sizeof(p5)-1] = '\0';
+ l5 = strlen(p5);
+
+ if((needed = l1+l2+l3+l4+l5-avail) > 0){
+ snprintf(p4, sizeof(p4), " %s file%s%s",
+ (flags & GE_IS_IMPORT) ? "from" : "to",
+ is_absolute_path(filename) ? "" : " in ",
+ is_absolute_path(filename) ? "" :
+ (!dir[0] ? "current dir"
+ : (dir[0] == '~' && !dir[1]) ? "home dir"
+ : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
+ p4[sizeof(p4)-1] = '\0';
+ l4 = strlen(p4);
+ }
+
+ if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
+ snprintf(p5, sizeof(p5), "%s%s%s: ",
+ *def ? " [" : "",
+ *def ? short_str(def,fileb,sizeof(fileb),
+ MAX(15,l5-5-needed),EndDots) : "",
+ *def ? "]" : "");
+ p5[sizeof(p5)-1] = '\0';
+ l5 = strlen(p5);
+ }
+
+ if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
+
+ /*
+ * 14 is about the shortest we can make this, because there are
+ * fixed length strings of length 14 coming in here.
+ */
+ p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
+ if(p != p2){
+ strncpy(p2, p, sizeof(p2)-1);
+ p2[sizeof(p2)-1] = '\0';
+ }
+
+ l2 = strlen(p2);
+ }
+
+ if((needed = l1+l2+l3+l4+l5-avail) > 0){
+ strncpy(p1, "Copy ", sizeof(p1)-1);
+ p1[sizeof(p1)-1] = '\0';
+ l1 = strlen(p1);
+ }
+
+ if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
+ snprintf(p5, sizeof(p5), "%s%s%s: ",
+ *def ? " [" : "",
+ *def ? short_str(def,fileb, sizeof(fileb),
+ MAX(10,l5-5-needed),EndDots) : "",
+ *def ? "]" : "");
+ p5[sizeof(p5)-1] = '\0';
+ l5 = strlen(p5);
+ }
+
+ if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
+ if(needed <= l3 - strlen(" (+ atts)"))
+ p3 = " (+ atts)";
+ else if(needed <= l3 - strlen(" (atts)"))
+ p3 = " (atts)";
+ else if(needed <= l3 - strlen(" (+)"))
+ p3 = " (+)";
+ else if(needed <= l3 - strlen("+"))
+ p3 = "+";
+ else
+ p3 = "";
+
+ l3 = strlen(p3);
+ }
+
+ snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
+ prompt_buf[sizeof(prompt_buf)-1] = '\0';
+
+ if(ku >= 0){
+ if(items_in_hist(*history) > 0){
+ opts[ku].name = HISTORY_UP_KEYNAME;
+ opts[ku].label = HISTORY_KEYLABEL;
+ opts[ku+1].name = HISTORY_DOWN_KEYNAME;
+ opts[ku+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ opts[ku].name = "";
+ opts[ku].label = "";
+ opts[ku+1].name = "";
+ opts[ku+1].label = "";
+ }
+ }
+
+ oeflags = OE_APPEND_CURRENT |
+ ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
+ r = optionally_enter(filename, qline, 0, len, prompt_buf,
+ opts, NO_HELP, &oeflags);
+
+ /*--- Help ----*/
+ if(r == 3){
+ /*
+ * Helps may not be right if you add another caller or change
+ * things. Check it out.
+ */
+ if(flags & GE_IS_IMPORT)
+ helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
+ else if(flags & GE_ALLPARTS)
+ helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
+ else
+ helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
+
+ ps->mangled_screen = 1;
+
+ continue;
+ }
+ else if(r == 10 || r == 11){ /* Browser or File Completion */
+ if(filename[0]=='~'){
+ if(filename[1] == C_FILESEP && filename[2]!='\0'){
+ precolon[0] = '~';
+ precolon[1] = '\0';
+ for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
+ filename[i] = filename[i+2];
+ filename[i] = '\0';
+ strncpy(dir, precolon, sizeof(dir)-1);
+ dir[sizeof(dir)-1] = '\0';
+ }
+ else if(filename[1]=='\0' ||
+ (filename[1] == C_FILESEP && filename[2] == '\0')){
+ precolon[0] = '~';
+ precolon[1] = '\0';
+ filename[0] = '\0';
+ strncpy(dir, precolon, sizeof(dir)-1);
+ dir[sizeof(dir)-1] = '\0';
+ }
+ }
+ else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
+ if(homedir){
+ precolon[0] = '~';
+ precolon[1] = '\0';
+ strncpy(dir, precolon, sizeof(dir)-1);
+ dir[sizeof(dir)-1] = '\0';
+ }
+ else{
+ precolon[0] = '\0';
+ dir[0] = '\0';
+ }
+ }
+ l = MAXPATH;
+ dir2[0] = '\0';
+ strncpy(tmp, filename, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ if(*tmp && is_absolute_path(tmp))
+ fnexpand(tmp, sizeof(tmp));
+ if(strncmp(tmp,postcolon, strlen(postcolon)))
+ postcolon[0] = '\0';
+
+ if(*tmp && (fn = last_cmpnt(tmp))){
+ l -= fn - tmp;
+ strncpy(filename2, fn, sizeof(filename2)-1);
+ filename2[sizeof(filename2)-1] = '\0';
+ if(is_absolute_path(tmp)){
+ strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
+ dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
+#ifdef _WINDOWS
+ if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
+ dir2[2] = '\\';
+ dir2[3] = '\0';
+ }
+#endif
+ strncpy(postcolon, dir2, sizeof(postcolon)-1);
+ postcolon[sizeof(postcolon)-1] = '\0';
+ precolon[0] = '\0';
+ }
+ else{
+ char *p = NULL;
+ /*
+ * Just building the directory name in dir2,
+ * full_filename is overloaded.
+ */
+ snprintf(full_filename, len, "%.*s", MIN(fn-tmp,len-1), tmp);
+ full_filename[len-1] = '\0';
+ strncpy(postcolon, full_filename, sizeof(postcolon)-1);
+ postcolon[sizeof(postcolon)-1] = '\0';
+ build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
+ : (dir[0] == '~' && !dir[1])
+ ? ps->home_dir
+ : dir,
+ full_filename, sizeof(dir2));
+ if(p)
+ free(p);
+ }
+ }
+ else{
+ if(is_absolute_path(tmp)){
+ strncpy(dir2, tmp, sizeof(dir2)-1);
+ dir2[sizeof(dir2)-1] = '\0';
+#ifdef _WINDOWS
+ if(dir2[2]=='\0' && dir2[1]==':'){
+ dir2[2]='\\';
+ dir2[3]='\0';
+ strncpy(postcolon,dir2,sizeof(postcolon)-1);
+ postcolon[sizeof(postcolon)-1] = '\0';
+ }
+#endif
+ filename2[0] = '\0';
+ precolon[0] = '\0';
+ }
+ else{
+ strncpy(filename2, tmp, sizeof(filename2)-1);
+ filename2[sizeof(filename2)-1] = '\0';
+ if(!dir[0])
+ (void)getcwd(dir2, sizeof(dir2));
+ else if(dir[0] == '~' && !dir[1]){
+ strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
+ dir2[sizeof(dir2)-1] = '\0';
+ }
+ else{
+ strncpy(dir2, dir, sizeof(dir2)-1);
+ dir2[sizeof(dir2)-1] = '\0';
+ }
+
+ postcolon[0] = '\0';
+ }
+ }
+
+ build_path(full_filename, dir2, filename2, len);
+ if(!strcmp(full_filename, dir2))
+ filename2[0] = '\0';
+ if(full_filename[strlen(full_filename)-1] == C_FILESEP
+ && isdir(full_filename,NULL,NULL)){
+ if(strlen(full_filename) == 1)
+ strncpy(postcolon, full_filename, sizeof(postcolon)-1);
+ else if(filename2[0])
+ strncpy(postcolon, filename2, sizeof(postcolon)-1);
+ postcolon[sizeof(postcolon)-1] = '\0';
+ strncpy(dir2, full_filename, sizeof(dir2)-1);
+ dir2[sizeof(dir2)-1] = '\0';
+ filename2[0] = '\0';
+ }
+#ifdef _WINDOWS /* use full_filename even if not a valid directory */
+ else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
+ strncpy(postcolon, filename2, sizeof(postcolon)-1);
+ postcolon[sizeof(postcolon)-1] = '\0';
+ strncpy(dir2, full_filename, sizeof(dir2)-1);
+ dir2[sizeof(dir2)-1] = '\0';
+ filename2[0] = '\0';
+ }
+#endif
+ if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
+ && strcmp(dir2+1, ":\\"))
+ /* last condition to prevent stripping of '\\'
+ in windows partition */
+ dir2[strlen(dir2)-1] = '\0';
+
+ if(r == 10){ /* File Browser */
+ r = file_lister(lister_msg ? lister_msg : "EXPORT",
+ dir2, sizeof(dir2), filename2, sizeof(filename2),
+ TRUE,
+ (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
+#ifdef _WINDOWS
+/* Windows has a special "feature" in which entering the file browser will
+ change the working directory if the directory is changed at all (even
+ clicking "Cancel" will change the working directory).
+*/
+ if(F_ON(F_USE_CURRENT_DIR, ps))
+ (void)getcwd(dir2,sizeof(dir2));
+#endif
+ if(isdir(dir2,NULL,NULL)){
+ strncpy(precolon, dir2, sizeof(precolon)-1);
+ precolon[sizeof(precolon)-1] = '\0';
+ }
+ strncpy(postcolon, filename2, sizeof(postcolon)-1);
+ postcolon[sizeof(postcolon)-1] = '\0';
+ if(r == 1){
+ build_path(full_filename, dir2, filename2, len);
+ if(isdir(full_filename, NULL, NULL)){
+ strncpy(dir, full_filename, sizeof(dir)-1);
+ dir[sizeof(dir)-1] = '\0';
+ filename[0] = '\0';
+ }
+ else{
+ fn = last_cmpnt(full_filename);
+ strncpy(dir, full_filename,
+ MIN(fn - full_filename, sizeof(dir)-1));
+ dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
+ if(fn - full_filename > 1)
+ dir[fn - full_filename - 1] = '\0';
+ }
+
+ if(!strcmp(dir, ps->home_dir)){
+ dir[0] = '~';
+ dir[1] = '\0';
+ }
+
+ strncpy(filename, fn, len-1);
+ filename[len-1] = '\0';
+ }
+ }
+ else{ /* File Completion */
+ if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
+ Writechar(BELL, 0);
+ strncat(postcolon, filename2,
+ sizeof(postcolon)-1-strlen(postcolon));
+ postcolon[sizeof(postcolon)-1] = '\0';
+
+ was_abs_path = is_absolute_path(filename);
+
+ if(!strcmp(dir, ps->home_dir)){
+ dir[0] = '~';
+ dir[1] = '\0';
+ }
+ }
+ strncpy(filename, postcolon, len-1);
+ filename[len-1] = '\0';
+ strncpy(dir, precolon, sizeof(dir)-1);
+ dir[sizeof(dir)-1] = '\0';
+
+ if(filename[0] == '~' && !filename[1]){
+ dir[0] = '~';
+ dir[1] = '\0';
+ filename[0] = '\0';
+ }
+
+ continue;
+ }
+ else if(r == 12){ /* Download, caller handles it */
+ ret = r;
+ goto done;
+ }
+ else if(r == 13){ /* toggle AllParts bit */
+ if(rflags){
+ if(*rflags & GER_ALLPARTS){
+ *rflags &= ~GER_ALLPARTS;
+ opts[allparts].label = N_("AllParts");
+ }
+ else{
+ *rflags |= GER_ALLPARTS;
+ /* opposite of All Parts, No All Parts */
+ opts[allparts].label = N_("NoAllParts");
+ }
+ }
+
+ continue;
+ }
+#if 0
+ else if(r == 14){ /* List file names matching partial? */
+ continue;
+ }
+#endif
+ else if(r == 1){ /* Cancel */
+ ret = -1;
+ goto done;
+ }
+ else if(r == 4){
+ continue;
+ }
+ else if(r == 30 || r == 31){
+ char *p = NULL;
+
+ if(history){
+ if(r == 30)
+ p = get_prev_hist(*history, filename, 0, NULL);
+ else
+ p = get_next_hist(*history, filename, 0, NULL);
+ }
+
+ if(p != NULL){
+ fn = last_cmpnt(p);
+ strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
+ dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
+ if(fn - p > 1)
+ dir[fn - p - 1] = '\0';
+
+ if(!strcmp(dir, ps->home_dir)){
+ dir[0] = '~';
+ dir[1] = '\0';
+ }
+
+ strncpy(filename, fn, len-1);
+ filename[len-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+ else if(r != 0){
+ Writechar(BELL, 0);
+ continue;
+ }
+
+ removing_leading_and_trailing_white_space(filename);
+
+ if(!*filename){
+ if(!*def){ /* Cancel */
+ ret = -1;
+ goto done;
+ }
+
+ strncpy(filename, def, len-1);
+ filename[len-1] = '\0';
+ }
+
+#if defined(DOS) || defined(OS2)
+ if(is_absolute_path(filename)){
+ fixpath(filename, len);
+ }
+#else
+ if(filename[0] == '~'){
+ if(fnexpand(filename, len) == NULL){
+ char *p = strindex(filename, '/');
+ if(p != NULL)
+ *p = '\0';
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error expanding file name: \"%s\" unknown user"),
+ filename);
+ continue;
+ }
+ }
+#endif
+
+ if(is_absolute_path(filename)){
+ strncpy(full_filename, filename, len-1);
+ full_filename[len-1] = '\0';
+ }
+ else{
+ if(!dir[0])
+ build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
+ filename, len);
+ else if(dir[0] == '~' && !dir[1])
+ build_path(full_filename, ps->home_dir, filename, len);
+ else
+ build_path(full_filename, dir, filename, len);
+ }
+
+ if((ill = filter_filename(full_filename, &fatal,
+ ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
+ if(fatal){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
+ continue;
+ }
+ else{
+/* BUG: we should beep when the key's pressed rather than bitch later */
+ /* Warn and ask for confirmation. */
+ snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
+ ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
+ prompt_buf[sizeof(prompt_buf)-1] = '\0';
+ if(want_to(prompt_buf, 'n', 0, NO_HELP,
+ ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
+ continue;
+ }
+ }
+
+ break; /* Must have got an OK file name */
+ }
+
+ if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
+ ret = -2;
+ goto done;
+ }
+
+ if(!can_access(full_filename, ACCESS_EXISTS)){
+ int rbflags;
+ static ESCKEY_S access_opts[] = {
+ /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
+ a file or append to the end of the file */
+ {'o', 'o', "O", N_("Overwrite")},
+ {'a', 'a', "A", N_("Append")},
+ {-1, 0, NULL, NULL}};
+
+ rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
+
+ if(flags & GE_NO_APPEND){
+ r = strlen(filename);
+ snprintf(prompt_buf, sizeof(prompt_buf),
+ /* TRANSLATORS: asking user whether to overwrite a file or not,
+ File <filename> already exists. Overwrite it ? */
+ _("File \"%s%s\" already exists. Overwrite it "),
+ (r > 20) ? "..." : "",
+ filename + ((r > 20) ? r - 20 : 0));
+ prompt_buf[sizeof(prompt_buf)-1] = '\0';
+ if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
+ if(rflags)
+ *rflags |= GER_OVER;
+
+ if(our_unlink(full_filename) < 0){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ /* TRANSLATORS: Cannot remove old <filename>: <error text> */
+ _("Cannot remove old %s: %s"),
+ full_filename, error_description(errno));
+ }
+ }
+ else{
+ ret = -1;
+ goto done;
+ }
+ }
+ else if(!(flags & GE_IS_IMPORT)){
+ r = strlen(filename);
+ snprintf(prompt_buf, sizeof(prompt_buf),
+ /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
+ _("File \"%s%s\" already exists. Overwrite or append to it ? "),
+ (r > 20) ? "..." : "",
+ filename + ((r > 20) ? r - 20 : 0));
+ prompt_buf[sizeof(prompt_buf)-1] = '\0';
+ switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
+ access_opts, 'a', 'x', NO_HELP, rbflags)){
+ case 'o' :
+ if(rflags)
+ *rflags |= GER_OVER;
+
+ if(our_truncate(full_filename, (off_t)0) < 0)
+ /* trouble truncating, but we'll give it a try anyway */
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
+ _("Warning: Cannot truncate old %s: %s"),
+ full_filename, error_description(errno));
+ break;
+
+ case 'a' :
+ if(rflags)
+ *rflags |= GER_APPEND;
+
+ break;
+
+ case 'x' :
+ default :
+ ret = -1;
+ goto done;
+ }
+ }
+ }
+
+done:
+ if(history && ret == 0)
+ save_hist(*history, full_filename, 0, NULL);
+
+ if(opts && opts != optsarg)
+ fs_give((void **) &opts);
+
+ return(ret);
+}
+
+
+/*----------------------------------------------------------------------
+ parse the config'd upload/download command
+
+ Args: cmd -- buffer to return command fit for shellin'
+ prefix --
+ cfg_str --
+ fname -- file name to build into the command
+
+ Returns: pointer to cmd_str buffer or NULL on real bad error
+
+ NOTE: One SIDE EFFECT is that any defined "prefix" string in the
+ cfg_str is written to standard out right before a successful
+ return of this function. The call immediately following this
+ function darn well better be the shell exec...
+ ----*/
+char *
+build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
+{
+ char *p;
+ int fname_found = 0;
+
+ if(prefix && *prefix){
+ /* loop thru replacing all occurances of _FILE_ */
+ p = strncpy(cmd, prefix, cmdlen);
+ cmd[cmdlen-1] = '\0';
+ while((p = strstr(p, "_FILE_")))
+ rplstr(p, cmdlen-(p-cmd), 6, fname);
+
+ fputs(cmd, stdout);
+ }
+
+ /* loop thru replacing all occurances of _FILE_ */
+ p = strncpy(cmd, cfg_str, cmdlen);
+ cmd[cmdlen-1] = '\0';
+ while((p = strstr(p, "_FILE_"))){
+ rplstr(p, cmdlen-(p-cmd), 6, fname);
+ fname_found = 1;
+ }
+
+ if(!fname_found)
+ snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
+
+ cmd[cmdlen-1] = '\0';
+
+ dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
+ cmd ? cmd : "?"));
+ return(cmd);
+}
+
+
+/*----------------------------------------------------------------------
+ Write a berzerk format message delimiter using the given putc function
+
+ Args: e -- envelope of message to write
+ pc -- function to use
+
+ Returns: TRUE if we could write it, FALSE if there was a problem
+
+ NOTE: follows delimiter with OS-dependent newline
+ ----*/
+int
+bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
+{
+ MESSAGECACHE telt;
+ time_t when;
+ char *p;
+
+ /* write "[\n]From mailbox[@host] " */
+ if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
+ && gf_puts("From ", pc)
+ && gf_puts((env && env->from) ? env->from->mailbox
+ : "the-concourse-on-high", pc)
+ && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
+ && gf_puts((env && env->from && env->from->host) ? env->from->host
+ : "", pc)
+ && (*pc)(' ')))
+ return(0);
+
+ if(mc && mc->valid)
+ when = mail_longdate(mc);
+ else if(env && env->date && env->date[0]
+ && mail_parse_date(&telt,env->date))
+ when = mail_longdate(&telt);
+ else
+ when = time(0);
+
+ p = ctime(&when);
+
+ while(p && *p && *p != '\n') /* write date */
+ if(!(*pc)(*p++))
+ return(0);
+
+ if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
+ return(0);
+
+ return(1);
+}
+
+
+/*----------------------------------------------------------------------
+ Execute command to jump to a given message number
+
+ Args: qline -- Line to ask question on
+
+ Result: returns true if the use selected a new message, false otherwise
+
+ ----*/
+long
+jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
+{
+ char jump_num_string[80], *j, prompt[70];
+ HelpType help;
+ int rc;
+ static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
+ /* TRANSLATORS: go to First Message */
+ {ctrl('Y'), 10, "^Y", N_("First Msg")},
+ {ctrl('V'), 11, "^V", N_("Last Msg")},
+ {-1, 0, NULL, NULL} };
+
+ dprint((4, "\n - jump_to -\n"));
+
+#ifdef DEBUG
+ if(sparms && sparms->jump_is_debug)
+ return(get_level(qline, first_num, sparms));
+#endif
+
+ if(!any_messages(msgmap, NULL, "to Jump to"))
+ return(0L);
+
+ if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
+ jump_num_string[0] = first_num;
+ jump_num_string[1] = '\0';
+ }
+ else
+ jump_num_string[0] = '\0';
+
+ if(mn_total_cur(msgmap) > 1L){
+ snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
+ comatose(mn_total_cur(msgmap)));
+ prompt[sizeof(prompt)-1] = '\0';
+ if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
+ return(0L);
+ }
+
+ snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
+ ? "Thread"
+ : "Message");
+ prompt[sizeof(prompt)-1] = '\0';
+
+ help = NO_HELP;
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ rc = optionally_enter(jump_num_string, qline, 0,
+ sizeof(jump_num_string), prompt,
+ jump_to_key, help, &flags);
+ if(rc == 3){
+ help = help == NO_HELP
+ ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
+ : NO_HELP;
+ continue;
+ }
+ else if(rc == 10 || rc == 11){
+ char warning[100];
+ long closest;
+
+ closest = closest_jump_target(rc == 10 ? 1L
+ : ((in_index == ThrdIndx)
+ ? msgmap->max_thrdno
+ : mn_get_total(msgmap)),
+ ps_global->mail_stream,
+ msgmap, 0,
+ in_index, warning, sizeof(warning));
+ /* ignore warning */
+ return(closest);
+ }
+
+ /*
+ * If we take out the *jump_num_string nonempty test in this if
+ * then the closest_jump_target routine will offer a jump to the
+ * last message. However, it is slow because you have to wait for
+ * the status message and it is annoying for people who hit J command
+ * by mistake and just want to hit return to do nothing, like has
+ * always worked. So the test is there for now. Hubert 2002-08-19
+ *
+ * Jumping to first/last message is now possible through ^Y/^V
+ * commands above. jpf 2002-08-21
+ * (and through "end" hubert 2006-07-07)
+ */
+ if(rc == 0 && *jump_num_string != '\0'){
+ removing_leading_and_trailing_white_space(jump_num_string);
+ for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
+ ;
+
+ if(*j != '\0'){
+ if(!strucmp("end", j))
+ return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
+
+ q_status_message(SM_ORDER | SM_DING, 2, 2,
+ _("Invalid number entered. Use only digits 0-9"));
+ jump_num_string[0] = '\0';
+ }
+ else{
+ char warning[100];
+ long closest, jump_num;
+
+ if(*jump_num_string)
+ jump_num = atol(jump_num_string);
+ else
+ jump_num = -1L;
+
+ warning[0] = '\0';
+ closest = closest_jump_target(jump_num, ps_global->mail_stream,
+ msgmap,
+ *jump_num_string ? 0 : 1,
+ in_index, warning, sizeof(warning));
+ if(warning[0])
+ q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
+
+ if(closest == jump_num)
+ return(jump_num);
+
+ if(closest == 0L)
+ jump_num_string[0] = '\0';
+ else
+ strncpy(jump_num_string, long2string(closest),
+ sizeof(jump_num_string));
+ }
+
+ continue;
+ }
+
+ if(rc != 4)
+ break;
+ }
+
+ return(0L);
+}
+
+
+/*
+ * cmd_delete_action - handle msgno advance and such after single message deletion
+ */
+char *
+cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
+{
+ int opts;
+ long msgno;
+ char *rv = NULL;
+
+ msgno = mn_get_cur(msgmap);
+ advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
+
+ if(IS_NEWS(state->mail_stream)
+ || ((state->context_current->use & CNTXT_INCMNG)
+ && context_isambig(state->cur_folder))){
+
+ opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
+ if(in_index == View)
+ opts &= ~NSF_SKIP_CHID;
+
+ (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
+ if(!(opts & NSF_FLAG_MATCH)){
+ char nextfolder[MAXPATH];
+
+ strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
+ nextfolder[sizeof(nextfolder)-1] = '\0';
+ rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
+ state->context_current, NULL, NULL)
+ ? ". Press TAB for next folder."
+ : ". No more folders to TAB to.";
+ }
+ }
+
+ return(rv);
+}
+
+
+/*
+ * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
+ */
+char *
+cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
+{
+ return(cmd_delete_action(state, msgmap,MsgIndx));
+}
+
+/*
+ * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
+ */
+char *
+cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
+{
+ return(cmd_delete_action(state, msgmap, View));
+}
+
+
+void
+advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
+{
+ long new_msgno, msgno;
+ int opts;
+
+ new_msgno = msgno = mn_get_cur(msgmap);
+ opts = NSF_TRUST_FLAGS;
+
+ if(F_ON(F_DEL_SKIPS_DEL, state)){
+
+ if(THREADING() && sp_viewing_a_thread(stream))
+ opts |= NSF_SKIP_CHID;
+
+ new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
+ }
+ else{
+ mn_inc_cur(stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ new_msgno = mn_get_cur(msgmap);
+ if(new_msgno != msgno)
+ opts |= NSF_FLAG_MATCH;
+ }
+
+ /*
+ * Viewing_a_thread is the complicated case because we want to ignore
+ * other threads at first and then look in other threads if we have to.
+ * By ignoring other threads we also ignore collapsed partial threads
+ * in our own thread.
+ */
+ if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
+ long rawno, orig_thrdno;
+ PINETHRD_S *thrd, *topthrd = NULL;
+
+ rawno = mn_m2raw(msgmap, msgno);
+ thrd = fetch_thread(stream, rawno);
+ if(thrd && thrd->top)
+ topthrd = fetch_thread(stream, thrd->top);
+
+ orig_thrdno = topthrd ? topthrd->thrdno : -1L;
+
+ opts = NSF_TRUST_FLAGS;
+ new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
+
+ /*
+ * If we got a match, new_msgno may be a message in
+ * a different thread from the one we are viewing, or it could be
+ * in a collapsed part of this thread.
+ */
+ if(opts & NSF_FLAG_MATCH){
+ int ret;
+ char pmt[128];
+
+ topthrd = NULL;
+ thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
+ if(thrd && thrd->top)
+ topthrd = fetch_thread(stream, thrd->top);
+
+ /*
+ * If this match is in the same thread we're already in
+ * then we're done, else we have to ask the user and maybe
+ * switch threads.
+ */
+ if(!(orig_thrdno > 0L && topthrd
+ && topthrd->thrdno == orig_thrdno)){
+
+ if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
+ if(in_index == View)
+ snprintf(pmt, sizeof(pmt),
+ "View message in thread number %.10s",
+ topthrd ? comatose(topthrd->thrdno) : "?");
+ else
+ snprintf(pmt, sizeof(pmt), "View thread number %.10s",
+ topthrd ? comatose(topthrd->thrdno) : "?");
+
+ ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
+ }
+ else
+ ret = 'y';
+
+ if(ret == 'y'){
+ unview_thread(state, stream, msgmap);
+ mn_set_cur(msgmap, new_msgno);
+ if(THRD_AUTO_VIEW()
+ && (count_lflags_in_thread(stream, topthrd, msgmap,
+ MN_NONE) == 1)
+ && view_thread(state, stream, msgmap, 1)){
+ if(current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+
+ state->view_skipped_index = 1;
+ state->next_screen = mail_view_screen;
+ }
+ else{
+ view_thread(state, stream, msgmap, 1);
+ if(current_index_state)
+ msgmap->top_after_thrd = current_index_state->msg_at_top;
+
+ state->next_screen = SCREEN_FUN_NULL;
+ }
+ }
+ else
+ new_msgno = msgno; /* stick with original */
+ }
+ }
+ }
+
+ mn_set_cur(msgmap, new_msgno);
+ if(in_index != View)
+ adjust_cur_to_visible(stream, msgmap);
+}
+
+
+#ifdef DEBUG
+long
+get_level(int qline, UCS first_num, SCROLL_S *sparms)
+{
+ char debug_num_string[80], *j, prompt[70];
+ HelpType help;
+ int rc;
+ long debug_num;
+
+ if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
+ debug_num_string[0] = first_num;
+ debug_num_string[1] = '\0';
+ debug_num = atol(debug_num_string);
+ *(int *)(sparms->proc.data.p) = debug_num;
+ q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
+ comatose(debug_num));
+ return(1L);
+ }
+ else
+ debug_num_string[0] = '\0';
+
+ snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
+ prompt[sizeof(prompt)-1] = '\0';
+
+ help = NO_HELP;
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ rc = optionally_enter(debug_num_string, qline, 0,
+ sizeof(debug_num_string), prompt,
+ NULL, help, &flags);
+ if(rc == 3){
+ help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
+ continue;
+ }
+
+ if(rc == 0){
+ removing_leading_and_trailing_white_space(debug_num_string);
+ for(j=debug_num_string; isdigit((unsigned char)*j); j++)
+ ;
+
+ if(*j != '\0'){
+ q_status_message(SM_ORDER | SM_DING, 2, 2,
+ _("Invalid number entered. Use only digits 0-9"));
+ debug_num_string[0] = '\0';
+ }
+ else{
+ debug_num = atol(debug_num_string);
+ if(debug_num < 0)
+ q_status_message(SM_ORDER | SM_DING, 2, 2,
+ _("Number should be >= 0"));
+ else if(debug_num > MAX(debug,9))
+ q_status_message1(SM_ORDER | SM_DING, 2, 2,
+ _("Maximum is %s"), comatose(MAX(debug,9)));
+ else{
+ *(int *)(sparms->proc.data.p) = debug_num;
+ q_status_message1(SM_ORDER, 0, 3,
+ "Show debug <= level %s",
+ comatose(debug_num));
+ return(1L);
+ }
+ }
+
+ continue;
+ }
+
+ if(rc != 4)
+ break;
+ }
+
+ return(0L);
+}
+#endif /* DEBUG */
+
+
+/*
+ * Returns the message number closest to target that isn't hidden.
+ * Make warning at least 100 chars.
+ * A return of 0 means there is no message to jump to.
+ */
+long
+closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
+{
+ long i, start, closest = 0L;
+ char buf[80];
+ long maxnum;
+
+ warning[0] = '\0';
+ maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
+
+ if(no_target){
+ target = maxnum;
+ start = 1L;
+ snprintf(warning, warninglen, "No %s number entered, jump to end? ",
+ (in_index == ThrdIndx) ? "thread" : "message");
+ warning[warninglen-1] = '\0';
+ }
+ else if(target < 1L)
+ start = 1L - target;
+ else if(target > maxnum)
+ start = target - maxnum;
+ else
+ start = 1L;
+
+ if(target > 0L && target <= maxnum)
+ if(in_index == ThrdIndx
+ || !msgline_hidden(stream, msgmap, target, 0))
+ return(target);
+
+ for(i = start; target+i <= maxnum || target-i > 0L; i++){
+
+ if(target+i > 0L && target+i <= maxnum &&
+ (in_index == ThrdIndx
+ || !msgline_hidden(stream, msgmap, target+i, 0))){
+ closest = target+i;
+ break;
+ }
+
+ if(target-i > 0L && target-i <= maxnum &&
+ (in_index == ThrdIndx
+ || !msgline_hidden(stream, msgmap, target-i, 0))){
+ closest = target-i;
+ break;
+ }
+ }
+
+ strncpy(buf, long2string(closest), sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+
+ if(closest == 0L)
+ strncpy(warning, "Nothing to jump to", warninglen);
+ else if(target < 1L)
+ snprintf(warning, warninglen, "%s number (%s) must be at least %s",
+ (in_index == ThrdIndx) ? "Thread" : "Message",
+ long2string(target), buf);
+ else if(target > maxnum)
+ snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
+ (in_index == ThrdIndx) ? "Thread" : "Message",
+ long2string(target), buf);
+ else if(!no_target)
+ snprintf(warning, warninglen,
+ "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
+ long2string(target), buf);
+
+ warning[warninglen-1] = '\0';
+
+ return(closest);
+}
+
+
+/*----------------------------------------------------------------------
+ Prompt for folder name to open, expand the name and return it
+
+ Args: qline -- Screen line to prompt on
+ allow_list -- if 1, allow ^T to bring up collection lister
+
+ Result: returns the folder name or NULL
+ pine structure mangled_footer flag is set
+ may call the collection lister in which case mangled screen will be set
+
+ This prompts the user for the folder to open, possibly calling up
+the collection lister if the user types ^T.
+----------------------------------------------------------------------*/
+char *
+broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
+{
+ HelpType help;
+ static char newfolder[MAILTMPLEN];
+ char expanded[MAXPATH+1],
+ prompt[MAX_SCREEN_COLS+1],
+ *last_folder, *p;
+ static HISTORY_S *history = NULL;
+ CONTEXT_S *tc, *tc2;
+ ESCKEY_S ekey[9];
+ int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
+
+ /*
+ * the idea is to provide a clue for the context the file name
+ * will be saved in (if a non-imap names is typed), and to
+ * only show the previous if it was also in the same context
+ */
+ help = NO_HELP;
+ *expanded = '\0';
+ *newfolder = '\0';
+ last_folder = NULL;
+ if(notrealinbox)
+ (*notrealinbox) = 1;
+
+ init_hist(&history, HISTSIZE);
+
+ tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
+
+ /* set up extra command option keys */
+ rc = 0;
+ ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
+ ekey[rc].rval = (allow_list) ? 2 : 0;
+ ekey[rc].name = (allow_list) ? "^T" : "";
+ ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
+
+ if(ps_global->context_list->next){
+ ekey[rc].ch = ctrl('P');
+ ekey[rc].rval = 10;
+ ekey[rc].name = "^P";
+ ekey[rc++].label = N_("Prev Collection");
+
+ ekey[rc].ch = ctrl('N');
+ ekey[rc].rval = 11;
+ ekey[rc].name = "^N";
+ ekey[rc++].label = N_("Next Collection");
+ }
+
+ ekey[rc].ch = ctrl('W');
+ ekey[rc].rval = 17;
+ ekey[rc].name = "^W";
+ ekey[rc++].label = N_("INBOX");
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ ekey[rc].ch = TAB;
+ ekey[rc].rval = 12;
+ ekey[rc].name = "TAB";
+ ekey[rc++].label = N_("Complete");
+ }
+
+ if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
+ ekey[rc].ch = ctrl('X');
+ ekey[rc].rval = 14;
+ ekey[rc].name = "^X";
+ ekey[rc++].label = N_("ListMatches");
+ }
+
+ if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
+ ekey[rc].ch = KEY_UP;
+ ekey[rc].rval = 10;
+ ekey[rc].name = "";
+ ekey[rc++].label = "";
+
+ ekey[rc].ch = KEY_DOWN;
+ ekey[rc].rval = 11;
+ ekey[rc].name = "";
+ ekey[rc++].label = "";
+ }
+ else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
+ ekey[rc].ch = KEY_UP;
+ ekey[rc].rval = 30;
+ ekey[rc].name = "";
+ ku = rc;
+ ekey[rc++].label = "";
+
+ ekey[rc].ch = KEY_DOWN;
+ ekey[rc].rval = 31;
+ ekey[rc].name = "";
+ ekey[rc++].label = "";
+ }
+
+ ekey[rc].ch = -1;
+
+ while(!done) {
+ /*
+ * Figure out next default value for this context. The idea
+ * is that in each context the last folder opened is cached.
+ * It's up to pick it out and display it. This is fine
+ * and dandy if we've currently got the inbox open, BUT
+ * if not, make the inbox the default the first time thru.
+ */
+ if(!inbox){
+ last_folder = ps_global->inbox_name;
+ inbox = 1; /* pretend we're in inbox from here on out */
+ }
+ else
+ last_folder = (ps_global->last_unambig_folder[0])
+ ? ps_global->last_unambig_folder
+ : ((tc->last_folder[0]) ? tc->last_folder : NULL);
+
+ if(last_folder){
+ unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
+ snprintf(expanded, sizeof(expanded), " [%.*s]", sizeof(expanded)-5,
+ fname ? (char *) fname : last_folder);
+ if(fname) fs_give((void **)&fname);
+ }
+ else
+ *expanded = '\0';
+
+ expanded[sizeof(expanded)-1] = '\0';
+
+ /* only show collection number if more than one available */
+ if(ps_global->context_list->next)
+ snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
+ NEWS_TEST(tc) ? "news group" : "folder",
+ tc->nickname, sizeof(prompt)-50, expanded,
+ *expanded ? " " : "");
+ else
+ snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", sizeof(prompt)-20, expanded,
+ *expanded ? " " : "");
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ if(utf8_width(prompt) > MAXPROMPT){
+ if(ps_global->context_list->next)
+ snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
+ tc->nickname, sizeof(prompt)-50, expanded,
+ *expanded ? " " : "");
+ else
+ snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", sizeof(prompt)-20, expanded,
+ *expanded ? " " : "");
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ if(utf8_width(prompt) > MAXPROMPT){
+ if(ps_global->context_list->next)
+ snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
+ tc->nickname, sizeof(prompt)-50, expanded,
+ *expanded ? " " : "");
+ else
+ snprintf(prompt, sizeof(prompt), "%.*s%s: ", sizeof(prompt)-20, expanded,
+ *expanded ? " " : "");
+
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+ }
+
+ if(ku >= 0){
+ if(items_in_hist(history) > 1){
+ ekey[ku].name = HISTORY_UP_KEYNAME;
+ ekey[ku].label = HISTORY_KEYLABEL;
+ ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
+ ekey[ku+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ ekey[ku].name = "";
+ ekey[ku].label = "";
+ ekey[ku+1].name = "";
+ ekey[ku+1].label = "";
+ }
+ }
+
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(newfolder, qline, 0, sizeof(newfolder),
+ prompt, ekey, help, &flags);
+
+ ps_global->mangled_footer = 1;
+
+ switch(rc){
+ case -1 : /* o_e says error! */
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error reading folder name"));
+ return(NULL);
+
+ case 0 : /* o_e says normal entry */
+ removing_trailing_white_space(newfolder);
+ removing_leading_white_space(newfolder);
+
+ if(*newfolder){
+ char *name, *fullname = NULL;
+ int exists, breakout = 0;
+
+ save_hist(history, newfolder, 0, tc);
+
+ if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
+ FN_WHOLE_NAME)))
+ name = newfolder;
+
+ if(update_folder_spec(expanded, sizeof(expanded), name)){
+ strncpy(name = newfolder, expanded, sizeof(newfolder));
+ newfolder[sizeof(newfolder)-1] = '\0';
+ }
+
+ exists = folder_name_exists(tc, name, &fullname);
+
+ if(fullname){
+ strncpy(name = newfolder, fullname, sizeof(newfolder));
+ newfolder[sizeof(newfolder)-1] = '\0';
+ fs_give((void **) &fullname);
+ breakout = TRUE;
+ }
+
+ /*
+ * if we know the things a folder, open it.
+ * else if we know its a directory, visit it.
+ * else we're not sure (it either doesn't really
+ * exist or its unLISTable) so try opening it anyway
+ */
+ if(exists & FEX_ISFILE){
+ done++;
+ break;
+ }
+ else if((exists & FEX_ISDIR)){
+ if(breakout){
+ CONTEXT_S *fake_context;
+ char tmp[MAILTMPLEN];
+ size_t l;
+
+ strncpy(tmp, name, sizeof(tmp));
+ tmp[sizeof(tmp)-2-1] = '\0';
+ if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
+ if(l < sizeof(tmp)){
+ tmp[l] = tc->dir->delim;
+ strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
+ }
+ }
+ else
+ strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
+
+ tmp[sizeof(tmp)-1] = '\0';
+
+ fake_context = new_context(tmp, 0);
+ newfolder[0] = '\0';
+ done = display_folder_list(&fake_context, newfolder,
+ 1, folders_for_goto);
+ free_context(&fake_context);
+ break;
+ }
+ else if(!(tc->use & CNTXT_INCMNG)){
+ done = display_folder_list(&tc, newfolder,
+ 1, folders_for_goto);
+ break;
+ }
+ }
+ else if((exists & FEX_ERROR)){
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Problem accessing folder \"%s\""),
+ newfolder);
+ return(NULL);
+ }
+ else{
+ done++;
+ break;
+ }
+
+ if(exists == FEX_ERROR)
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Problem accessing folder \"%s\""),
+ newfolder);
+ else if(tc->use & CNTXT_INCMNG)
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Can't find Incoming Folder: %s"),
+ newfolder);
+ else if(context_isambig(newfolder))
+ q_status_message2(SM_ORDER, 0, 3,
+ _("Can't find folder \"%s\" in %s"),
+ newfolder, (void *) tc->nickname);
+ else
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Can't find folder \"%s\""),
+ newfolder);
+
+ return(NULL);
+ }
+ else if(last_folder){
+ if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
+ && !strucmp(last_folder, ps_global->inbox_name)
+ && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
+ ? ps_global->context_list->next : ps_global->context_list)){
+ if(notrealinbox)
+ (*notrealinbox) = 0;
+
+ tc = ps_global->context_list;
+ }
+
+ strncpy(newfolder, last_folder, sizeof(newfolder));
+ newfolder[sizeof(newfolder)-1] = '\0';
+ save_hist(history, newfolder, 0, tc);
+ done++;
+ break;
+ }
+ /* fall thru like they cancelled */
+
+ case 1 : /* o_e says user cancel */
+ cmd_cancelled("Open folder");
+ return(NULL);
+
+ case 2 : /* o_e says user wants list */
+ r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
+ if(r)
+ done++;
+
+ break;
+
+ case 3 : /* o_e says user wants help */
+ help = help == NO_HELP ? h_oe_broach : NO_HELP;
+ break;
+
+ case 4 : /* redraw */
+ break;
+
+ case 10 : /* Previous collection */
+ tc2 = ps_global->context_list;
+ while(tc2->next && tc2->next != tc)
+ tc2 = tc2->next;
+
+ tc = tc2;
+ break;
+
+ case 11 : /* Next collection */
+ tc = (tc->next) ? tc->next : ps_global->context_list;
+ break;
+
+ case 12 : /* file name completion */
+ if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
+ if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
+ r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
+ if(r)
+ done++; /* bingo! */
+ else
+ rc = 0; /* burn last_rc */
+ }
+ else
+ Writechar(BELL, 0);
+ }
+
+ break;
+
+ case 14 : /* file name completion */
+ r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
+ if(r)
+ done++; /* bingo! */
+ else
+ rc = 0; /* burn last_rc */
+
+ break;
+
+ case 17 : /* GoTo INBOX */
+ done++;
+ strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
+ newfolder[sizeof(newfolder)-1] = '\0';
+ if(notrealinbox)
+ (*notrealinbox) = 0;
+
+ tc = ps_global->context_list;
+ save_hist(history, newfolder, 0, tc);
+
+ break;
+
+ case 30 :
+ if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
+ strncpy(newfolder, p, sizeof(newfolder));
+ newfolder[sizeof(newfolder)-1] = '\0';
+ if(history->hist[history->curindex])
+ tc = history->hist[history->curindex]->cntxt;
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+ case 31 :
+ if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
+ strncpy(newfolder, p, sizeof(newfolder));
+ newfolder[sizeof(newfolder)-1] = '\0';
+ if(history->hist[history->curindex])
+ tc = history->hist[history->curindex]->cntxt;
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+ default :
+ panic("Unhandled case");
+ break;
+ }
+
+ last_rc = rc;
+ }
+
+ dprint((2, "broach folder, name entered \"%s\"\n",
+ newfolder ? newfolder : "?"));
+
+ /*-- Just check that we can expand this. It gets done for real later --*/
+ strncpy(expanded, newfolder, sizeof(expanded));
+ expanded[sizeof(expanded)-1] = '\0';
+
+ if(!expand_foldername(expanded, sizeof(expanded))) {
+ dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
+ expanded ? expanded : "?"));
+ return(NULL);
+ }
+
+ *context = tc;
+ return(newfolder);
+}
+
+
+/*----------------------------------------------------------------------
+ Check to see if user wants to reopen dead stream.
+
+ Args: ps --
+ reopenp --
+
+ Result: 1 if the folder was successfully updatedn
+ 0 if not necessary
+
+ ----*/
+int
+ask_mailbox_reopen(struct pine *ps, int *reopenp)
+{
+ if(((ps->mail_stream->dtb
+ && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
+ || (ps->mail_stream->rdonly
+ && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
+ && (ps->reopen_rule == REOPEN_ASK_ASK_Y
+ || ps->reopen_rule == REOPEN_ASK_ASK_N
+ || ps->reopen_rule == REOPEN_ASK_NO_Y
+ || ps->reopen_rule == REOPEN_ASK_NO_N))
+ || ((ps->mail_stream->dtb
+ && ps->mail_stream->rdonly
+ && !(ps->mail_stream->dtb->flags & DR_LOCAL))
+ && (ps->reopen_rule == REOPEN_YES_ASK_Y
+ || ps->reopen_rule == REOPEN_YES_ASK_N
+ || ps->reopen_rule == REOPEN_ASK_ASK_Y
+ || ps->reopen_rule == REOPEN_ASK_ASK_N))){
+ int deefault;
+
+ switch(ps->reopen_rule){
+ case REOPEN_YES_ASK_Y:
+ case REOPEN_ASK_ASK_Y:
+ case REOPEN_ASK_NO_Y:
+ deefault = 'y';
+ break;
+
+ default:
+ deefault = 'n';
+ break;
+ }
+
+ switch(want_to("Re-open folder to check for new messages", deefault,
+ 'x', h_reopen_folder, WT_NORM)){
+ case 'y':
+ (*reopenp)++;
+ break;
+
+ case 'x':
+ return(-1);
+ }
+ }
+
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Check to see if user input is in form of old c-client mailbox speck
+
+ Args: old --
+ new --
+
+ Result: 1 if the folder was successfully updatedn
+ 0 if not necessary
+
+ ----*/
+int
+update_folder_spec(char *new, size_t newlen, char *old)
+{
+ char *p, *orignew;
+ int nntp = 0;
+
+ orignew = new;
+ if(*(p = old) == '*') /* old form? */
+ old++;
+
+ if(*old == '{') /* copy host spec */
+ do
+ switch(*new = *old++){
+ case '\0' :
+ return(FALSE);
+
+ case '/' :
+ if(!struncmp(old, "nntp", 4))
+ nntp++;
+
+ break;
+
+ default :
+ break;
+ }
+ while(*new++ != '}' && (new-orignew) < newlen-1);
+
+ if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
+ /*
+ * OK, some heuristics here. If it looks like a newsgroup
+ * then we plunk it into the #news namespace else we
+ * assume that they're trying to get at a #public folder...
+ */
+ for(p = old;
+ *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
+ p++)
+ ;
+
+ sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
+ strncpy(new, old, newlen-(new-orignew));
+ return(TRUE);
+ }
+
+ orignew[newlen-1] = '\0';
+
+ return(FALSE);
+}
+
+
+/*----------------------------------------------------------------------
+ Open the requested folder in the requested context
+
+ Args: state -- usual pine state struct
+ newfolder -- folder to open
+ new_context -- folder context might live in
+ stream -- candidate for recycling
+
+ Result: New folder open or not (if error), and we're set to
+ enter the index screen.
+ ----*/
+void
+visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
+ MAILSTREAM *stream, long unsigned int flags)
+{
+ dprint((9, "visit_folder(%s, %s)\n",
+ newfolder ? newfolder : "?",
+ (new_context && new_context->context)
+ ? new_context->context : "(NULL)"));
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
+ flags) >= 0
+ || !sp_flagged(state->mail_stream, SP_LOCKED))
+ state->next_screen = mail_index_screen;
+ else
+ state->next_screen = folder_screen;
+}
+
+
+/*----------------------------------------------------------------------
+ Move read messages from folder if listed in archive
+
+ Args:
+
+ ----*/
+int
+read_msg_prompt(long int n, char *f)
+{
+ char buf[MAX_SCREEN_COLS+1];
+
+ snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
+ buf[sizeof(buf)-1] = '\0';
+ return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
+}
+
+
+/*----------------------------------------------------------------------
+ Print current message[s] or folder index
+
+ Args: state -- pointer to struct holding a bunch of pine state
+ msgmap -- table mapping msg nums to c-client sequence nums
+ aopt -- aggregate options
+ in_index -- boolean indicating we're called from Index Screen
+
+ Filters the original header and sends stuff to printer
+ ---*/
+int
+cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
+{
+ char prompt[250];
+ long i, msgs, rawno;
+ int next = 0, do_index = 0, rv = 0;
+ ENVELOPE *e;
+ BODY *b;
+ MESSAGECACHE *mc;
+
+ if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
+ return rv;
+
+ msgs = mn_total_cur(msgmap);
+
+ if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
+ char m[10];
+ int ans;
+ static ESCKEY_S prt_opts[] = {
+ {'i', 'i', "I", N_("Index")},
+ {'m', 'm', "M", NULL},
+ {-1, 0, NULL, NULL}};
+
+ if(in_index == ThrdIndx){
+ /* TRANSLATORS: This is a question, Print Index ? */
+ if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
+ ans = 'i';
+ else
+ ans = 'x';
+ }
+ else{
+ snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
+ m[sizeof(m)-1] = '\0';
+ prt_opts[1].label = m;
+ snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
+ (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
+ (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
+ NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
+ }
+
+ switch(ans){
+ case 'x' :
+ cmd_cancelled("Print");
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ return rv;
+
+ case 'i':
+ do_index = 1;
+ break;
+
+ default :
+ case 'm':
+ break;
+ }
+ }
+
+ if(do_index)
+ snprintf(prompt, sizeof(prompt), "%sFolder Index",
+ (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
+ else if(msgs > 1L)
+ snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
+ else
+ snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ if(open_printer(prompt) < 0){
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ return rv;
+ }
+
+ if(do_index){
+ TITLE_S *tc;
+
+ tc = format_titlebar();
+
+ /* Print titlebar... */
+ print_text1("%s\n\n", tc ? tc->titlebar_line : "");
+ /* then all the index members... */
+ if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error printing folder index"));
+ else
+ rv++;
+ }
+ else{
+ rv++;
+ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
+ if(next && F_ON(F_AGG_PRINT_FF, state))
+ if(!print_char(FORMFEED)){
+ rv = 0;
+ break;
+ }
+
+ if(!(state->mail_stream
+ && (rawno = mn_m2raw(msgmap, i)) > 0L
+ && rawno <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, rawno))
+ && mc->valid))
+ mc = NULL;
+
+ if(!(e=pine_mail_fetchstructure(state->mail_stream,
+ mn_m2raw(msgmap,i),
+ &b))
+ || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
+ && !bezerk_delimiter(e, mc, print_char, next))
+ || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
+ e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
+ print_char)){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error printing message"));
+ rv = 0;
+ break;
+ }
+ }
+ }
+
+ close_printer();
+
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ return rv;
+}
+
+
+/*----------------------------------------------------------------------
+ Pipe message text
+
+ Args: state -- various pine state bits
+ msgmap -- Message number mapping table
+ aopt -- option flags
+
+ Filters the original header and sends stuff to specified command
+ ---*/
+int
+cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
+{
+ ENVELOPE *e;
+ MESSAGECACHE *mc;
+ BODY *b;
+ PIPE_S *syspipe;
+ char *resultfilename = NULL, prompt[80], *p;
+ int done = 0, rv = 0;
+ gf_io_t pc;
+ int fourlabel = -1, j = 0, next = 0, ku;
+ int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
+ long i, rawno;
+ unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
+ static HISTORY_S *history = NULL;
+ int capture = 1, raw = 0, delimit = 0, newpipe = 0;
+ char pipe_command[MAXPATH];
+ ESCKEY_S pipe_opt[8];
+
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ "Alpine demo can't pipe messages");
+ return rv;
+ }
+ else if(!any_messages(msgmap, NULL, "to Pipe"))
+ return rv;
+
+ pipe_command[0] = '\0';
+ init_hist(&history, HISTSIZE);
+ flagsforhist = (raw ? 0x8 : 0) +
+ (delimit ? 0x4 : 0) +
+ (newpipe ? 0x2 : 0) +
+ (capture ? 0x1 : 0);
+ if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
+ strncpy(pipe_command, p, sizeof(pipe_command));
+ pipe_command[sizeof(pipe_command)-1] = '\0';
+ if(history->hist[history->curindex]){
+ flagsforhist = history->hist[history->curindex]->flags;
+ raw = (flagsforhist & 0x8) ? 1 : 0;
+ delimit = (flagsforhist & 0x4) ? 1 : 0;
+ newpipe = (flagsforhist & 0x2) ? 1 : 0;
+ capture = (flagsforhist & 0x1) ? 1 : 0;
+ }
+ }
+
+ pipe_opt[j].ch = 0;
+ pipe_opt[j].rval = 0;
+ pipe_opt[j].name = "";
+ pipe_opt[j++].label = "";
+
+ pipe_opt[j].ch = ctrl('W');
+ pipe_opt[j].rval = 10;
+ pipe_opt[j].name = "^W";
+ pipe_opt[j++].label = NULL;
+
+ pipe_opt[j].ch = ctrl('Y');
+ pipe_opt[j].rval = 11;
+ pipe_opt[j].name = "^Y";
+ pipe_opt[j++].label = NULL;
+
+ pipe_opt[j].ch = ctrl('R');
+ pipe_opt[j].rval = 12;
+ pipe_opt[j].name = "^R";
+ pipe_opt[j++].label = NULL;
+
+ if(MCMD_ISAGG(aopt)){
+ if(!pseudo_selected(state->mail_stream, msgmap))
+ return rv;
+ else{
+ fourlabel = j;
+ pipe_opt[j].ch = ctrl('T');
+ pipe_opt[j].rval = 13;
+ pipe_opt[j].name = "^T";
+ pipe_opt[j++].label = NULL;
+ }
+ }
+
+ pipe_opt[j].ch = KEY_UP;
+ pipe_opt[j].rval = 30;
+ pipe_opt[j].name = "";
+ ku = j;
+ pipe_opt[j++].label = "";
+
+ pipe_opt[j].ch = KEY_DOWN;
+ pipe_opt[j].rval = 31;
+ pipe_opt[j].name = "";
+ pipe_opt[j++].label = "";
+
+ pipe_opt[j].ch = -1;
+
+ while (!done) {
+ int flags;
+
+ snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
+ raw ? "RAW " : "",
+ MCMD_ISAGG(aopt) ? "s" : " ",
+ MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
+ (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
+ capture ? "" : "uncaptured",
+ (!capture && delimit) ? "," : "",
+ delimit ? "delimited" : "",
+ ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
+ (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
+ (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
+ pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
+ pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
+ if(fourlabel > 0)
+ pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
+
+
+ /*
+ * 2 is really 1 because there will be one real entry and
+ * one entry of "" because of the get_prev_hist above.
+ */
+ if(items_in_hist(history) > 2){
+ pipe_opt[ku].name = HISTORY_UP_KEYNAME;
+ pipe_opt[ku].label = HISTORY_KEYLABEL;
+ pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
+ pipe_opt[ku+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ pipe_opt[ku].name = "";
+ pipe_opt[ku].label = "";
+ pipe_opt[ku+1].name = "";
+ pipe_opt[ku+1].label = "";
+ }
+
+ flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
+ switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
+ sizeof(pipe_command), prompt,
+ pipe_opt, NO_HELP, &flags)){
+ case -1 :
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Internal problem encountered"));
+ done++;
+ break;
+
+ case 10 : /* flip raw bit */
+ raw = !raw;
+ break;
+
+ case 11 : /* flip capture bit */
+ capture = !capture;
+ break;
+
+ case 12 : /* flip delimit bit */
+ delimit = !delimit;
+ break;
+
+ case 13 : /* flip newpipe bit */
+ newpipe = !newpipe;
+ break;
+
+ case 30 :
+ flagsforhist = (raw ? 0x8 : 0) +
+ (delimit ? 0x4 : 0) +
+ (newpipe ? 0x2 : 0) +
+ (capture ? 0x1 : 0);
+ if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
+ strncpy(pipe_command, p, sizeof(pipe_command));
+ pipe_command[sizeof(pipe_command)-1] = '\0';
+ if(history->hist[history->curindex]){
+ flagsforhist = history->hist[history->curindex]->flags;
+ raw = (flagsforhist & 0x8) ? 1 : 0;
+ delimit = (flagsforhist & 0x4) ? 1 : 0;
+ newpipe = (flagsforhist & 0x2) ? 1 : 0;
+ capture = (flagsforhist & 0x1) ? 1 : 0;
+ }
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+ case 31 :
+ flagsforhist = (raw ? 0x8 : 0) +
+ (delimit ? 0x4 : 0) +
+ (newpipe ? 0x2 : 0) +
+ (capture ? 0x1 : 0);
+ if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
+ strncpy(pipe_command, p, sizeof(pipe_command));
+ pipe_command[sizeof(pipe_command)-1] = '\0';
+ if(history->hist[history->curindex]){
+ flagsforhist = history->hist[history->curindex]->flags;
+ raw = (flagsforhist & 0x8) ? 1 : 0;
+ delimit = (flagsforhist & 0x4) ? 1 : 0;
+ newpipe = (flagsforhist & 0x2) ? 1 : 0;
+ capture = (flagsforhist & 0x1) ? 1 : 0;
+ }
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+ case 0 :
+ if(pipe_command[0]){
+
+ flagsforhist = (raw ? 0x8 : 0) +
+ (delimit ? 0x4 : 0) +
+ (newpipe ? 0x2 : 0) +
+ (capture ? 0x1 : 0);
+ save_hist(history, pipe_command, flagsforhist, NULL);
+
+ flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
+ flags |= (raw ? PIPE_RAW : 0);
+ if(!capture){
+#ifndef _WINDOWS
+ ClearScreen();
+ fflush(stdout);
+ clear_cursor_pos();
+ ps_global->mangled_screen = 1;
+ ps_global->in_init_seq = 1;
+#endif
+ flags |= PIPE_RESET;
+ }
+
+ if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
+ (flags & PIPE_RESET)
+ ? NULL
+ : &resultfilename,
+ flags, &pc)))
+ done++;
+
+ for(i = mn_first_cur(msgmap);
+ i > 0L && !done;
+ i = mn_next_cur(msgmap)){
+ e = pine_mail_fetchstructure(ps_global->mail_stream,
+ mn_m2raw(msgmap, i), &b);
+ if(!(state->mail_stream
+ && (rawno = mn_m2raw(msgmap, i)) > 0L
+ && rawno <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, rawno))
+ && mc->valid))
+ mc = NULL;
+
+ if((newpipe
+ && !(syspipe = cmd_pipe_open(pipe_command,
+ (flags & PIPE_RESET)
+ ? NULL
+ : &resultfilename,
+ flags, &pc)))
+ || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
+ done++;
+
+ if(!done){
+ if(raw){
+ char *pipe_err;
+
+ prime_raw_pipe_getc(ps_global->mail_stream,
+ mn_m2raw(msgmap, i), -1L, 0L);
+ gf_filter_init();
+ gf_link_filter(gf_nvtnl_local, NULL);
+ if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
+ q_status_message1(SM_ORDER|SM_DING,
+ 3, 3,
+ _("Internal Error: %s"),
+ pipe_err);
+ done++;
+ }
+ }
+ else if(!format_message(mn_m2raw(msgmap, i), e, b,
+ NULL, FM_NEW_MESS | FM_NOWRAP, pc))
+ done++;
+ }
+
+ if(newpipe)
+ if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
+ done++;
+ }
+
+ if(!capture)
+ ps_global->in_init_seq = 0;
+
+ if(!newpipe)
+ if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
+ done++;
+ if(done) /* say we had a problem */
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error piping message"));
+ else if(resultfilename){
+ rv++;
+ /* only display if no error */
+ display_output_file(resultfilename, "PIPE MESSAGE",
+ NULL, DOF_EMPTY);
+ fs_give((void **)&resultfilename);
+ }
+ else{
+ rv++;
+ q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
+ }
+
+ done++;
+ break;
+ }
+ /* else fall thru as if cancelled */
+
+ case 1 :
+ cmd_cancelled("Pipe command");
+ done++;
+ break;
+
+ case 3 :
+ helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
+ ps_global->mangled_screen = 1;
+ break;
+
+ case 2 : /* no place to escape to */
+ case 4 : /* can't suspend */
+ default :
+ break;
+ }
+ }
+
+ ps_global->mangled_footer = 1;
+ if(MCMD_ISAGG(aopt))
+ restore_selected(msgmap);
+
+ return rv;
+}
+
+
+/*----------------------------------------------------------------------
+ Screen to offer list management commands contained in message
+
+ Args: state -- pointer to struct holding a bunch of pine state
+ msgmap -- table mapping msg nums to c-client sequence nums
+ aopt -- aggregate options
+
+ Result:
+
+ NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
+ ----*/
+void
+rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
+{
+ int winner = 0;
+ char *h, *hdrs[MLCMD_COUNT + 1];
+ long index_no = mn_raw2m(msgmap, msgno);
+ RFC2369_S data[MLCMD_COUNT];
+
+ /* for each header field */
+ if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
+ memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
+ if(rfc2369_parse_fields(h, &data[0])){
+ STORE_S *explain;
+
+ if((explain = list_mgmt_text(data, index_no)) != NULL){
+ list_mgmt_screen(explain);
+ ps_global->mangled_screen = 1;
+ so_give(&explain);
+ winner++;
+ }
+ }
+
+ fs_give((void **) &h);
+ }
+
+ if(!winner)
+ q_status_message1(SM_ORDER, 0, 3,
+ "Message %s contains no list management information",
+ comatose(index_no));
+}
+
+
+STORE_S *
+list_mgmt_text(RFC2369_S *data, long int msgno)
+{
+ STORE_S *store;
+ int i, j, n, fields = 0;
+ static char *rfc2369_intro1 =
+ "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
+ static char *rfc2369_intro2[] = {
+ N_(" has information associated with it "),
+ N_("that explains how to participate in an email list. An "),
+ N_("email list is represented by a single email address that "),
+ N_("users sharing a common interest can send messages to (known "),
+ N_("as posting) which are then redistributed to all members "),
+ N_("of the list (sometimes after review by a moderator)."),
+ N_("<P>List participation commands in this message include:"),
+ NULL
+ };
+
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+
+ /* Insert introductory text */
+ so_puts(store, rfc2369_intro1);
+
+ so_puts(store, comatose(msgno));
+
+ for(i = 0; rfc2369_intro2[i]; i++)
+ so_puts(store, _(rfc2369_intro2[i]));
+
+ so_puts(store, "<P>");
+ for(i = 0; i < MLCMD_COUNT; i++)
+ if(data[i].data[0].value
+ || data[i].data[0].comment
+ || data[i].data[0].error){
+ if(!fields++)
+ so_puts(store, "<UL>");
+
+ so_puts(store, "<LI>");
+ so_puts(store,
+ (n = (data[i].data[1].value || data[i].data[1].comment))
+ ? "Methods to "
+ : "A method to ");
+
+ so_puts(store, data[i].field.description);
+ so_puts(store, ". ");
+
+ if(n)
+ so_puts(store, "<OL>");
+
+ for(j = 0;
+ j < MLCMD_MAXDATA
+ && (data[i].data[j].comment
+ || data[i].data[j].value
+ || data[i].data[j].error);
+ j++){
+
+ so_puts(store, n ? "<P><LI>" : "<P>");
+
+ if(data[i].data[j].comment){
+ so_puts(store,
+ _("With the provided comment:<P><BLOCKQUOTE>"));
+ so_puts(store, data[i].data[j].comment);
+ so_puts(store, "</BLOCKQUOTE><P>");
+ }
+
+ if(data[i].data[j].value){
+ if(i == MLCMD_POST
+ && !strucmp(data[i].data[j].value, "NO")){
+ so_puts(store,
+ _("Posting is <EM>not</EM> allowed on this list"));
+ }
+ else{
+ so_puts(store, "Select <A HREF=\"");
+ so_puts(store, data[i].data[j].value);
+ so_puts(store, "\">HERE</A> to ");
+ so_puts(store, (data[i].field.action)
+ ? data[i].field.action
+ : "try it");
+ }
+
+ so_puts(store, ".");
+ }
+
+ if(data[i].data[j].error){
+ so_puts(store, "<P>Unfortunately, Alpine can not offer");
+ so_puts(store, " to take direct action based upon it");
+ so_puts(store, " because it was improperly formatted.");
+ so_puts(store, " The unrecognized data associated with");
+ so_puts(store, " the \"");
+ so_puts(store, data[i].field.name);
+ so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
+ so_puts(store, data[i].data[j].error);
+ so_puts(store, "</BLOCKQUOTE>");
+ }
+
+ so_puts(store, "<P>");
+ }
+
+ if(n)
+ so_puts(store, "</OL>");
+ }
+
+ if(fields)
+ so_puts(store, "</UL>");
+
+ so_puts(store, "</BODY></HTML>");
+ }
+
+ return(store);
+}
+
+
+void
+list_mgmt_screen(STORE_S *html)
+{
+ int cmd = MC_NONE;
+ long offset = 0L;
+ char *error = NULL;
+ STORE_S *store;
+ HANDLE_S *handles = NULL;
+ gf_io_t gc, pc;
+
+ do{
+ so_seek(html, 0L, 0);
+ gf_set_so_readc(&gc, html);
+
+ init_handles(&handles);
+
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ gf_set_so_writec(&pc, store);
+ gf_filter_init();
+
+ gf_link_filter(gf_html2plain,
+ gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
+ non_messageview_margin(), &handles, NULL, 0));
+
+ error = gf_pipe(gc, pc);
+
+ gf_clear_so_writec(store);
+
+ if(!error){
+ SCROLL_S sargs;
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = "list commands";
+ sargs.text.handles = handles;
+ if(offset){
+ sargs.start.on = Offset;
+ sargs.start.loc.offset = offset;
+ }
+
+ sargs.bar.title = _("MAIL LIST COMMANDS");
+ sargs.bar.style = MessageNumber;
+ sargs.resize_exit = 1;
+ sargs.help.text = h_special_list_commands;
+ sargs.help.title = _("HELP FOR LIST COMMANDS");
+ sargs.keys.menu = &listmgr_keymenu;
+ setbitmap(sargs.keys.bitmap);
+ if(!handles){
+ clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
+ clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
+ clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
+ }
+
+ cmd = scrolltool(&sargs);
+ offset = sargs.start.loc.offset;
+ }
+
+ so_give(&store);
+ }
+
+ free_handles(&handles);
+ gf_clear_so_readc(html);
+ }
+ while(cmd == MC_RESIZE);
+}
+
+
+/*----------------------------------------------------------------------
+ Prompt the user for the type of select desired
+
+ NOTE: any and all functions that successfully exit the second
+ switch() statement below (currently "select_*() functions"),
+ *MUST* update the folder's MESSAGECACHE element's "searched"
+ bits to reflect the search result. Functions using
+ mail_search() get this for free, the others must update 'em
+ by hand.
+
+ Returns -1 if canceled without changing selection
+ 0 if selection may have changed
+ ----*/
+int
+aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
+{
+ long i, diff, old_tot, msgno, raw;
+ int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
+ ESCKEY_S *sel_opts;
+ MESSAGECACHE *mc;
+ SEARCHSET *limitsrch = NULL;
+ PINETHRD_S *thrd;
+ extern MAILSTREAM *mm_search_stream;
+ extern long mm_search_count;
+
+ hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
+ mm_search_stream = state->mail_stream;
+ mm_search_count = 0L;
+
+ sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
+ if(THREADING()){
+ sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
+ }
+ else{
+ sel_opts[SEL_OPTS_THREAD].ch = -1;
+ }
+
+ if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
+ if(THRD_INDX()){
+ i = 0;
+ thrd = fetch_thread(state->mail_stream,
+ mn_m2raw(msgmap, mn_get_cur(msgmap)));
+ /* check if whole thread is selected or not */
+ if(thrd &&
+ count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
+ ==
+ count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
+ i = 1;
+
+ sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
+ }
+ else{
+ i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
+ MN_SLCT);
+ sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
+ }
+
+ sel_opts += 2; /* disable extra options */
+ switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
+ RB_NORM)){
+ case 'f' : /* flip selection */
+ msgno = 0L;
+ for(i = 1L; i <= mn_get_total(msgmap); i++){
+ ret = 0;
+ q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
+ set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
+ if(hidden){
+ set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
+ if(!msgno && q)
+ mn_reset_cur(msgmap, msgno = i);
+ }
+ }
+
+ return(ret);
+
+ case 'n' : /* narrow selection */
+ narrow++;
+ case 'b' : /* broaden selection */
+ q = 0; /* offer criteria prompt */
+ break;
+
+ case 'c' : /* Un/Select Current */
+ case 'a' : /* Unselect All */
+ case 'x' : /* cancel */
+ break;
+
+ default :
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Unsupported Select option");
+ return(ret);
+ }
+ }
+
+ if(!q){
+ while(1){
+ q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x',
+ NO_HELP, RB_NORM|RB_RET_HELP);
+
+ if(q == 3){
+ helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
+ ps_global->mangled_screen = 1;
+ }
+ else
+ break;
+ }
+ }
+
+ /*
+ * The purpose of this is to add the appropriate searchset to the
+ * search so that the search can be limited to only looking at what
+ * it needs to look at. That is, if we are narrowing then we only need
+ * to look at messages which are already selected, and if we are
+ * broadening, then we only need to look at messages which are not
+ * yet selected. This routine will work whether or not
+ * limiting_searchset properly limits the search set. In particular,
+ * the searchset returned by limiting_searchset may include messages
+ * which really shouldn't be included. We do that because a too-large
+ * searchset will break some IMAP servers. It is even possible that it
+ * becomes inefficient to send the whole set. If the select function
+ * frees limitsrch, it should be sure to set it to NULL so we won't
+ * try freeing it again here.
+ */
+ limitsrch = limiting_searchset(state->mail_stream, narrow);
+
+ /*
+ * NOTE: See note about MESSAGECACHE "searched" bits above!
+ */
+ switch(q){
+ case 'x': /* cancel */
+ cmd_cancelled("Select command");
+ return(ret);
+
+ case 'c' : /* select/unselect current */
+ (void) select_by_current(state, msgmap, in_index);
+ ret = 0;
+ return(ret);
+
+ case 'a' : /* select/unselect all */
+ msgno = any_lflagged(msgmap, MN_SLCT);
+ diff = (!msgno) ? mn_get_total(msgmap) : 0L;
+ ret = 0;
+ agg_select_all(state->mail_stream, msgmap, &diff,
+ any_lflagged(msgmap, MN_SLCT) <= 0L);
+ q_status_message4(SM_ORDER,0,2,
+ "%s%s message%s %sselected",
+ msgno ? "" : "All ", comatose(diff),
+ plural(diff), msgno ? "UN" : "");
+ return(ret);
+
+ case 'n' : /* Select by Number */
+ ret = 0;
+ if(THRD_INDX())
+ rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
+ else
+ rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
+
+ break;
+
+ case 'd' : /* Select by Date */
+ ret = 0;
+ rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
+ &limitsrch);
+ break;
+
+ case 't' : /* Text */
+ ret = 0;
+ rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
+ &limitsrch);
+ break;
+
+ case 'z' : /* Size */
+ ret = 0;
+ rv = select_by_size(state->mail_stream, &limitsrch);
+ break;
+
+ case 's' : /* Status */
+ ret = 0;
+ rv = select_by_status(state->mail_stream, &limitsrch);
+ break;
+
+ case 'k' : /* Keyword */
+ ret = 0;
+ rv = select_by_keyword(state->mail_stream, &limitsrch);
+ break;
+
+ case 'r' : /* Rule */
+ ret = 0;
+ rv = select_by_rule(state->mail_stream, &limitsrch);
+ break;
+
+ case 'h' : /* Thread */
+ ret = 0;
+ rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
+ break;
+
+ default :
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Unsupported Select option");
+ return(ret);
+ }
+
+ if(limitsrch)
+ mail_free_searchset(&limitsrch);
+
+ if(rv) /* bad return value.. */
+ return(ret); /* error already displayed */
+
+ if(narrow) /* make sure something was selected */
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
+ && raw <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
+ if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
+ break;
+ else
+ mm_search_count--;
+ }
+
+ diff = 0L;
+ if(mm_search_count){
+ /*
+ * loop thru all the messages, adjusting local flag bits
+ * based on their "searched" bit...
+ */
+ for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
+ if(narrow){
+ /* turning OFF selectedness if the "searched" bit isn't lit. */
+ if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
+ if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
+ && raw <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, raw))
+ && !mc->searched){
+ diff--;
+ set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
+ if(hidden)
+ set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
+ }
+ /* adjust current message in case we unselect and hide it */
+ else if(msgno < mn_get_cur(msgmap)
+ && (!THRD_INDX()
+ || !get_lflag(state->mail_stream, msgmap,
+ i, MN_CHID)))
+ msgno = i;
+ }
+ }
+ else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
+ && raw <= state->mail_stream->nmsgs
+ && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
+ /* turn ON selectedness if "searched" bit is lit. */
+ if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
+ diff++;
+ set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
+ if(hidden)
+ set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
+ }
+ }
+
+ /* if we're zoomed and the current message was unselected */
+ if(narrow && msgno
+ && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
+ mn_reset_cur(msgmap, msgno);
+ }
+
+ if(!diff){
+ if(narrow)
+ q_status_message4(SM_ORDER, 3, 3,
+ "%s. %s message%s remain%s selected.",
+ mm_search_count
+ ? "No change resulted"
+ : "No messages in intersection",
+ comatose(old_tot), plural(old_tot),
+ (old_tot == 1L) ? "s" : "");
+ else if(old_tot)
+ q_status_message(SM_ORDER, 3, 3,
+ _("No change resulted. Matching messages already selected."));
+ else
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Select failed. No %smessages selected."),
+ old_tot ? _("additional ") : "");
+ }
+ else if(old_tot){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Select matched %ld message%s. %s %smessage%s %sselected.",
+ (diff > 0) ? diff : old_tot + diff,
+ plural((diff > 0) ? diff : old_tot + diff),
+ comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
+ (diff > 0) ? "total " : "",
+ plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
+ (diff > 0) ? "" : "UN");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
+ }
+ else
+ q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
+ comatose(diff), plural(diff));
+
+ return(ret);
+}
+
+
+/*----------------------------------------------------------------------
+ Toggle the state of the current message
+
+ Args: state -- pointer pine's state variables
+ msgmap -- message collection to operate on
+ in_index -- in the message index view
+ Returns: TRUE if current marked selected, FALSE otw
+ ----*/
+int
+select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
+{
+ long cur;
+ int all_selected = 0;
+ unsigned long was, tot, rawno;
+ PINETHRD_S *thrd;
+
+ cur = mn_get_cur(msgmap);
+
+ if(THRD_INDX()){
+ thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
+ if(!thrd)
+ return 0;
+
+ was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
+ tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
+ if(was == tot)
+ all_selected++;
+
+ if(all_selected){
+ set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
+ if(any_lflagged(msgmap, MN_HIDE) > 0L){
+ set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
+ /*
+ * See if there's anything left to zoom on. If so,
+ * pick an adjacent one for highlighting, else make
+ * sure nothing is left hidden...
+ */
+ if(any_lflagged(msgmap, MN_SLCT)){
+ mn_inc_cur(state->mail_stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(state->mail_stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ if(mn_get_cur(msgmap) == cur)
+ mn_dec_cur(state->mail_stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(state->mail_stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ }
+ else /* clear all hidden flags */
+ (void) unzoom_index(state, state->mail_stream, msgmap);
+ }
+ }
+ else
+ set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
+
+ q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
+ comatose(all_selected ? was : tot-was),
+ plural(all_selected ? was : tot-was),
+ all_selected ? "UN" : "");
+ }
+ /* collapsed thread */
+ else if(THREADING()
+ && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
+ && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
+ && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
+ /*
+ * This doesn't work quite the same as the colon command works, but
+ * it is arguably doing the correct thing. The difference is
+ * that aggregate_select will zoom after selecting back where it
+ * was called from, but selecting a thread with colon won't zoom.
+ * Maybe it makes sense to zoom after a select but not after a colon
+ * command even though they are very similar.
+ */
+ thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
+ }
+ else{
+ if((all_selected =
+ get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
+ set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
+ if(any_lflagged(msgmap, MN_HIDE) > 0L){
+ set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
+ /*
+ * See if there's anything left to zoom on. If so,
+ * pick an adjacent one for highlighting, else make
+ * sure nothing is left hidden...
+ */
+ if(any_lflagged(msgmap, MN_SLCT)){
+ mn_inc_cur(state->mail_stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(state->mail_stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ if(mn_get_cur(msgmap) == cur)
+ mn_dec_cur(state->mail_stream, msgmap,
+ (in_index == View && THREADING()
+ && sp_viewing_a_thread(state->mail_stream))
+ ? MH_THISTHD
+ : (in_index == View)
+ ? MH_ANYTHD : MH_NONE);
+ }
+ else /* clear all hidden flags */
+ (void) unzoom_index(state, state->mail_stream, msgmap);
+ }
+ }
+ else
+ set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
+
+ q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
+ long2string(cur), all_selected ? "UN" : "");
+ }
+
+
+ return(!all_selected);
+}
+
+
+/*----------------------------------------------------------------------
+ Prompt the user for the command to perform on selected messages
+
+ Args: state -- pointer pine's state variables
+ msgmap -- message collection to operate on
+ q_line -- line on display to write prompts
+ Returns: 1 if the selected messages are suitably commanded,
+ 0 if the choice to pick the command was declined
+
+ ----*/
+int
+apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
+ UCS preloadkeystroke, int flags, int q_line)
+{
+ int i = 8, /* number of static entries in sel_opts3 */
+ rv = 0,
+ cmd,
+ we_cancel = 0,
+ agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
+ char prompt[80];
+
+ /*
+ * To do this "right", we really ought to have access to the keymenu
+ * here and change the typed command into a real command by running
+ * it through menu_command. Then the switch below would be against
+ * results from menu_command. If we did that we'd also pass the
+ * results of menu_command in as preloadkeystroke instead of passing
+ * the keystroke itself. But we don't have the keymenu handy,
+ * so we have to fake it. The only complication that we run into
+ * is that KEY_DEL is an escape sequence so we change a typed
+ * KEY_DEL esc seq into the letter D.
+ */
+
+ if(!preloadkeystroke){
+ if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
+ sel_opts3[i].ch = '*';
+ sel_opts3[i].rval = '*';
+ sel_opts3[i].name = "*";
+ sel_opts3[i++].label = N_("Flag");
+ }
+
+ if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
+ sel_opts3[i].ch = '|';
+ sel_opts3[i].rval = '|';
+ sel_opts3[i].name = "|";
+ sel_opts3[i++].label = N_("Pipe");
+ }
+
+ if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
+ sel_opts3[i].ch = 'b';
+ sel_opts3[i].rval = 'b';
+ sel_opts3[i].name = "B";
+ sel_opts3[i++].label = N_("Bounce");
+ }
+
+ if(flags & AC_FROM_THREAD){
+ if(flags & (AC_COLL | AC_EXPN)){
+ sel_opts3[i].ch = '/';
+ sel_opts3[i].rval = '/';
+ sel_opts3[i].name = "/";
+ sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
+ : N_("Expand");
+ }
+
+ sel_opts3[i].ch = ';';
+ sel_opts3[i].rval = ';';
+ sel_opts3[i].name = ";";
+ if(flags & AC_UNSEL)
+ sel_opts3[i++].label = N_("UnSelect");
+ else
+ sel_opts3[i++].label = N_("Select");
+ }
+
+ if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
+ sel_opts3[i].ch = 'y';
+ sel_opts3[i].rval = '%';
+ sel_opts3[i].name = "";
+ sel_opts3[i++].label = "";
+ }
+
+ sel_opts3[i].ch = KEY_DEL; /* also invisible */
+ sel_opts3[i].rval = 'd';
+ sel_opts3[i].name = "";
+ sel_opts3[i++].label = "";
+
+ sel_opts3[i].ch = -1;
+
+ snprintf(prompt, sizeof(prompt), "%s command : ",
+ (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
+ prompt[sizeof(prompt)-1] = '\0';
+ cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'x', NO_HELP,
+ RB_SEQ_SENSITIVE);
+ if(isupper(cmd))
+ cmd = tolower(cmd);
+ }
+ else{
+ if(preloadkeystroke == KEY_DEL)
+ cmd = 'd';
+ else{
+ if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
+ cmd = tolower((int) preloadkeystroke);
+ else
+ cmd = (int) preloadkeystroke; /* shouldn't happen */
+ }
+ }
+
+ switch(cmd){
+ case 'd' : /* delete */
+ we_cancel = busy_cue(NULL, NULL, 1);
+ rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
+ if(we_cancel)
+ cancel_busy_cue(0);
+ break;
+
+ case 'u' : /* undelete */
+ we_cancel = busy_cue(NULL, NULL, 1);
+ rv = cmd_undelete(state, msgmap, agg);
+ if(we_cancel)
+ cancel_busy_cue(0);
+ break;
+
+ case 'r' : /* reply */
+ rv = cmd_reply(state, msgmap, agg);
+ break;
+
+ case 'f' : /* Forward */
+ rv = cmd_forward(state, msgmap, agg);
+ break;
+
+ case '%' : /* print */
+ rv = cmd_print(state, msgmap, agg, MsgIndx);
+ break;
+
+ case 't' : /* take address */
+ rv = cmd_take_addr(state, msgmap, agg);
+ break;
+
+ case 's' : /* save */
+ rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
+ break;
+
+ case 'e' : /* export */
+ rv = cmd_export(state, msgmap, q_line, agg);
+ break;
+
+ case '|' : /* pipe */
+ rv = cmd_pipe(state, msgmap, agg);
+ break;
+
+ case '*' : /* flag */
+ we_cancel = busy_cue(NULL, NULL, 1);
+ rv = cmd_flag(state, msgmap, agg);
+ if(we_cancel)
+ cancel_busy_cue(0);
+ break;
+
+ case 'b' : /* bounce */
+ rv = cmd_bounce(state, msgmap, agg);
+ break;
+
+ case '/' :
+ collapse_or_expand(state, stream, msgmap,
+ F_ON(F_SLASH_COLL_ENTIRE, ps_global)
+ ? 0L
+ : mn_get_cur(msgmap));
+ break;
+
+ case ':' :
+ select_thread_stmp(state, stream, msgmap);
+ break;
+
+ case 'x' : /* cancel */
+ cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
+ : "Apply command");
+ break;
+
+ case 'z' : /* default */
+ q_status_message(SM_INFO, 0, 2,
+ "Cancelled, there is no default command");
+ break;
+
+ default:
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Select by message number ranges.
+ * Sets searched bits in mail_elts
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
+{
+ int r, end;
+ long n1, n2, raw;
+ char number1[16], number2[16], numbers[80], *p, *t;
+ HelpType help;
+ MESSAGECACHE *mc;
+
+ numbers[0] = '\0';
+ ps_global->mangled_footer = 1;
+ help = NO_HELP;
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
+ sizeof(numbers), _(select_num), NULL, help, &flags);
+ if(r == 4)
+ continue;
+
+ if(r == 3){
+ help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
+ continue;
+ }
+
+ for(t = p = numbers; *p ; p++) /* strip whitespace */
+ if(!isspace((unsigned char)*p))
+ *t++ = *p;
+
+ *t = '\0';
+
+ if(r == 1 || numbers[0] == '\0'){
+ cmd_cancelled("Selection by number");
+ return(1);
+ }
+ else
+ break;
+ }
+
+ for(n1 = 1; n1 <= stream->nmsgs; n1++)
+ if((mc = mail_elt(stream, n1)) != NULL)
+ mc->searched = 0; /* clear searched bits */
+
+ for(p = numbers; *p ; p++){
+ t = number1;
+ while(*p && isdigit((unsigned char)*p))
+ *t++ = *p++;
+
+ *t = '\0';
+
+ end = 0;
+ if(number1[0] == '\0'){
+ if(*p == '-'){
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid number range, missing number before \"-\": %s"),
+ numbers);
+ return(1);
+ }
+ else if(!strucmp("end", p)){
+ end = 1;
+ p += strlen("end");
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid message number: %s"), numbers);
+ return(1);
+ }
+ }
+
+ if(end)
+ n1 = mn_get_total(msgmap);
+ else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("\"%s\" out of message number range"),
+ long2string(n1));
+ return(1);
+ }
+
+ t = number2;
+ if(*p == '-'){
+ while(*++p && isdigit((unsigned char)*p))
+ *t++ = *p;
+
+ *t = '\0';
+
+ end = 0;
+ if(number2[0] == '\0'){
+ if(!strucmp("end", p)){
+ end = 1;
+ p += strlen("end");
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid number range, missing number after \"-\": %s"),
+ numbers);
+ return(1);
+ }
+ }
+
+ if(end)
+ n2 = mn_get_total(msgmap);
+ else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("\"%s\" out of message number range"),
+ long2string(n2));
+ return(1);
+ }
+
+ if(n2 <= n1){
+ char t[20];
+
+ strncpy(t, long2string(n1), sizeof(t));
+ t[sizeof(t)-1] = '\0';
+ q_status_message2(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid reverse message number range: %s-%s"),
+ t, long2string(n2));
+ return(1);
+ }
+
+ for(;n1 <= n2; n1++){
+ raw = mn_m2raw(msgmap, n1);
+ if(raw > 0L
+ && (!(limitsrch && *limitsrch)
+ || in_searchset(*limitsrch, (unsigned long) raw)))
+ mm_searched(stream, raw);
+ }
+ }
+ else{
+ raw = mn_m2raw(msgmap, n1);
+ if(raw > 0L
+ && (!(limitsrch && *limitsrch)
+ || in_searchset(*limitsrch, (unsigned long) raw)))
+ mm_searched(stream, raw);
+ }
+
+ if(*p == '\0')
+ break;
+ }
+
+ return(0);
+}
+
+
+/*
+ * Select by thread number ranges.
+ * Sets searched bits in mail_elts
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
+{
+ int r, end;
+ long n1, n2;
+ char number1[16], number2[16], numbers[80], *p, *t;
+ HelpType help;
+ PINETHRD_S *thrd = NULL;
+ MESSAGECACHE *mc;
+
+ numbers[0] = '\0';
+ ps_global->mangled_footer = 1;
+ help = NO_HELP;
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
+ sizeof(numbers), _(select_num), NULL, help, &flags);
+ if(r == 4)
+ continue;
+
+ if(r == 3){
+ help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
+ continue;
+ }
+
+ for(t = p = numbers; *p ; p++) /* strip whitespace */
+ if(!isspace((unsigned char)*p))
+ *t++ = *p;
+
+ *t = '\0';
+
+ if(r == 1 || numbers[0] == '\0'){
+ cmd_cancelled("Selection by number");
+ return(1);
+ }
+ else
+ break;
+ }
+
+ for(n1 = 1; n1 <= stream->nmsgs; n1++)
+ if((mc = mail_elt(stream, n1)) != NULL)
+ mc->searched = 0; /* clear searched bits */
+
+ for(p = numbers; *p ; p++){
+ t = number1;
+ while(*p && isdigit((unsigned char)*p))
+ *t++ = *p++;
+
+ *t = '\0';
+
+ end = 0;
+ if(number1[0] == '\0'){
+ if(*p == '-'){
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid number range, missing number before \"-\": %s"),
+ numbers);
+ return(1);
+ }
+ else if(!strucmp("end", p)){
+ end = 1;
+ p += strlen("end");
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid thread number: %s"), numbers);
+ return(1);
+ }
+ }
+
+ if(end)
+ n1 = msgmap->max_thrdno;
+ else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("\"%s\" out of thread number range"),
+ long2string(n1));
+ return(1);
+ }
+
+ t = number2;
+ if(*p == '-'){
+
+ while(*++p && isdigit((unsigned char)*p))
+ *t++ = *p;
+
+ *t = '\0';
+
+ end = 0;
+ if(number2[0] == '\0'){
+ if(!strucmp("end", p)){
+ end = 1;
+ p += strlen("end");
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid number range, missing number after \"-\": %s"),
+ numbers);
+ return(1);
+ }
+ }
+
+ if(end)
+ n2 = msgmap->max_thrdno;
+ else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("\"%s\" out of thread number range"),
+ long2string(n2));
+ return(1);
+ }
+
+ if(n2 <= n1){
+ char t[20];
+
+ strncpy(t, long2string(n1), sizeof(t));
+ t[sizeof(t)-1] = '\0';
+ q_status_message2(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid reverse message number range: %s-%s"),
+ t, long2string(n2));
+ return(1);
+ }
+
+ for(;n1 <= n2; n1++){
+ thrd = find_thread_by_number(stream, msgmap, n1, thrd);
+
+ if(thrd)
+ set_search_bit_for_thread(stream, thrd, msgset);
+ }
+ }
+ else{
+ thrd = find_thread_by_number(stream, msgmap, n1, NULL);
+
+ if(thrd)
+ set_search_bit_for_thread(stream, thrd, msgset);
+ }
+
+ if(*p == '\0')
+ break;
+ }
+
+ return(0);
+}
+
+
+/*
+ * Select by message dates.
+ * Sets searched bits in mail_elts
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
+{
+ int r, we_cancel = 0, when = 0;
+ char date[100], defdate[100], prompt[128];
+ time_t seldate = time(0);
+ struct tm *seldate_tm;
+ SEARCHPGM *pgm;
+ HelpType help;
+ static struct _tense {
+ char *preamble,
+ *range,
+ *scope;
+ } tense[] = {
+ {"were ", "SENT SINCE", " (inclusive)"},
+ {"were ", "SENT BEFORE", " (exclusive)"},
+ {"were ", "SENT ON", "" },
+ {"", "ARRIVED SINCE", " (inclusive)"},
+ {"", "ARRIVED BEFORE", " (exclusive)"},
+ {"", "ARRIVED ON", "" }
+ };
+
+ date[0] = '\0';
+ ps_global->mangled_footer = 1;
+ help = NO_HELP;
+
+ /*
+ * If talking to an old server, default to SINCE instead of
+ * SENTSINCE, which was added later.
+ */
+ if(is_imap_stream(stream) && !modern_imap_stream(stream))
+ when = 3;
+
+ while(1){
+ int flags = OE_APPEND_CURRENT;
+
+ seldate_tm = localtime(&seldate);
+ snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
+ month_abbrev(seldate_tm->tm_mon + 1),
+ seldate_tm->tm_year + 1900);
+ defdate[sizeof(defdate)-1] = '\0';
+ snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
+ tense[when].preamble, tense[when].range,
+ tense[when].scope, defdate);
+ prompt[sizeof(prompt)-1] = '\0';
+ r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
+ prompt, sel_date_opt, help, &flags);
+ switch (r){
+ case 1 :
+ cmd_cancelled("Selection by date");
+ return(1);
+
+ case 3 :
+ help = (help == NO_HELP) ? h_select_date : NO_HELP;
+ continue;
+
+ case 4 :
+ continue;
+
+ case 11 :
+ {
+ MESSAGECACHE *mc;
+ long rawno;
+
+ if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
+ && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno))){
+
+ /* cache not filled in yet? */
+ if(mc->day == 0){
+ char seq[20];
+
+ if(stream->dtb && stream->dtb->flags & DR_NEWS){
+ strncpy(seq,
+ ulong2string(mail_uid(stream, rawno)),
+ sizeof(seq));
+ seq[sizeof(seq)-1] = '\0';
+ mail_fetch_overview(stream, seq, NULL);
+ }
+ else{
+ strncpy(seq, long2string(rawno),
+ sizeof(seq));
+ seq[sizeof(seq)-1] = '\0';
+ mail_fetch_fast(stream, seq, 0L);
+ }
+ }
+
+ /* mail_date returns fixed field width date */
+ mail_date(date, mc);
+ date[11] = '\0';
+ }
+ }
+
+ continue;
+
+ case 12 : /* set default to PREVIOUS day */
+ seldate -= 86400;
+ continue;
+
+ case 13 : /* set default to NEXT day */
+ seldate += 86400;
+ continue;
+
+ case 14 :
+ when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
+ continue;
+
+ default:
+ break;
+ }
+
+ removing_leading_white_space(date);
+ removing_trailing_white_space(date);
+ if(!*date){
+ strncpy(date, defdate, sizeof(date));
+ date[sizeof(date)-1] = '\0';
+ }
+
+ break;
+ }
+
+ if((pgm = mail_newsearchpgm()) != NULL){
+ MESSAGECACHE elt;
+ short converted_date;
+
+ if(mail_parse_date(&elt, (unsigned char *) date)){
+ converted_date = mail_shortdate(elt.year, elt.month, elt.day);
+
+ switch(when){
+ case 0:
+ pgm->sentsince = converted_date;
+ break;
+ case 1:
+ pgm->sentbefore = converted_date;
+ break;
+ case 2:
+ pgm->senton = converted_date;
+ break;
+ case 3:
+ pgm->since = converted_date;
+ break;
+ case 4:
+ pgm->before = converted_date;
+ break;
+ case 5:
+ pgm->on = converted_date;
+ break;
+ }
+
+ pgm->msgno = (limitsrch ? *limitsrch : NULL);
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ we_cancel = busy_cue(_("Selecting"), NULL, 1);
+
+ pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ /* we know this was freed in mail_search, let caller know */
+ if(limitsrch)
+ *limitsrch = NULL;
+ }
+ else{
+ mail_free_searchpgm(&pgm);
+ q_status_message1(SM_ORDER, 3, 3,
+ _("Invalid date entered: %s"), date);
+ return(1);
+ }
+ }
+
+ return(0);
+}
+
+
+/*
+ * Select by searching in message headers or body.
+ * Sets searched bits in mail_elts
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
+{
+ int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
+ int not = 0, me = 0;
+ char sstring[80], savedsstring[80], tmp[128];
+ char *p, *sval = NULL;
+ char buftmp[MAILTMPLEN], namehdr[80];
+ ESCKEY_S ekey[8];
+ ENVELOPE *env = NULL;
+ HelpType help;
+ unsigned flagsforhist = 0;
+ static HISTORY_S *history = NULL;
+ static char *recip = "RECIPIENTS";
+ static char *partic = "PARTICIPANTS";
+ static char *match_me = N_("[Match_My_Addresses]");
+ static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
+
+ ps_global->mangled_footer = 1;
+ savedsstring[0] = '\0';
+ ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
+
+ while(1){
+ type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
+ -FOOTER_ROWS(ps_global), sel_text_opt,
+ 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
+
+ if(type == '!')
+ not = !not;
+ else if(type == 3){
+ helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
+ HLPD_SIMPLE);
+ ps_global->mangled_screen = 1;
+ }
+ else
+ break;
+ }
+
+ /*
+ * prepare some friendly defaults...
+ */
+ switch(type){
+ case 't' : /* address fields, offer To or From */
+ case 'f' :
+ case 'c' :
+ case 'r' :
+ case 'p' :
+ sval = (type == 't') ? "TO" :
+ (type == 'f') ? "FROM" :
+ (type == 'c') ? "CC" :
+ (type == 'r') ? recip : partic;
+ ekey[ekeyi].ch = ctrl('T');
+ ekey[ekeyi].name = "^T";
+ ekey[ekeyi].rval = 10;
+ /* TRANSLATORS: use Current To Address */
+ ekey[ekeyi++].label = N_("Cur To");
+ ekey[ekeyi].ch = ctrl('R');
+ ekey[ekeyi].name = "^R";
+ ekey[ekeyi].rval = 11;
+ /* TRANSLATORS: use Current From Address */
+ ekey[ekeyi++].label = N_("Cur From");
+ ekey[ekeyi].ch = ctrl('W');
+ ekey[ekeyi].name = "^W";
+ ekey[ekeyi].rval = 12;
+ /* TRANSLATORS: use Current Cc Address */
+ ekey[ekeyi++].label = N_("Cur Cc");
+ ekey[ekeyi].ch = ctrl('Y');
+ ekey[ekeyi].name = "^Y";
+ ekey[ekeyi].rval = 13;
+ /* TRANSLATORS: Match Me means match my address */
+ ekey[ekeyi++].label = N_("Match Me");
+ ekey[ekeyi].ch = 0;
+ ekey[ekeyi].name = "";
+ ekey[ekeyi].rval = 0;
+ ekey[ekeyi++].label = "";
+ break;
+
+ case 's' :
+ sval = "SUBJECT";
+ ekey[ekeyi].ch = ctrl('X');
+ ekey[ekeyi].name = "^X";
+ ekey[ekeyi].rval = 14;
+ /* TRANSLATORS: use Current Subject */
+ ekey[ekeyi++].label = N_("Cur Subject");
+ break;
+
+ case 'a' :
+ sval = "TEXT";
+ break;
+
+ case 'b' :
+ sval = "BODYTEXT";
+ break;
+
+ case 'h' :
+ strcpy(tmp, "Name of HEADER to match : ");
+ flags = OE_APPEND_CURRENT;
+ namehdr[0] = '\0';
+ r = 'x';
+ while (r == 'x'){
+ int done = 0;
+
+ r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
+ sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
+ if (r == 1){
+ cmd_cancelled("Selection by text");
+ return(1);
+ }
+ removing_leading_white_space(namehdr);
+ while(!done){
+ while ((namehdr[0] != '\0') && /* remove trailing ":" */
+ (namehdr[strlen(namehdr) - 1] == ':'))
+ namehdr[strlen(namehdr) - 1] = '\0';
+ if ((namehdr[0] != '\0')
+ && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
+ removing_trailing_white_space(namehdr);
+ else
+ done++;
+ }
+ if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
+ strchr(namehdr,':'))
+ namehdr[0] = '\0';
+ if (namehdr[0] == '\0')
+ r = 'x';
+ }
+ sval = namehdr;
+ break;
+
+ case 'x':
+ break;
+
+ default:
+ dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
+ return(1);
+ }
+
+ ekey[ekeyi].ch = KEY_UP;
+ ekey[ekeyi].rval = 30;
+ ekey[ekeyi].name = "";
+ ku = ekeyi;
+ ekey[ekeyi++].label = "";
+
+ ekey[ekeyi].ch = KEY_DOWN;
+ ekey[ekeyi].rval = 31;
+ ekey[ekeyi].name = "";
+ ekey[ekeyi++].label = "";
+
+ ekey[ekeyi].ch = -1;
+
+ if(type != 'x'){
+
+ init_hist(&history, HISTSIZE);
+
+ if(ekey[0].ch > -1 && msgno > 0L
+ && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
+ NULL)))
+ ekey[0].ch = -1;
+
+ sstring[0] = '\0';
+ help = NO_HELP;
+ r = type;
+ while(r != 'x'){
+ if(not)
+ /* TRANSLATORS: character String in message <message number> to NOT match : " */
+ snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
+ else
+ snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
+
+ if(items_in_hist(history) > 0){
+ ekey[ku].name = HISTORY_UP_KEYNAME;
+ ekey[ku].label = HISTORY_KEYLABEL;
+ ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
+ ekey[ku+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ ekey[ku].name = "";
+ ekey[ku].label = "";
+ ekey[ku+1].name = "";
+ ekey[ku+1].label = "";
+ }
+
+ flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
+ r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
+ 79, tmp, ekey, help, &flags);
+
+ if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
+ me = 0;
+
+ switch(r){
+ case 3 :
+ help = (help == NO_HELP)
+ ? (not
+ ? ((type == 'f') ? h_select_txt_not_from
+ : (type == 't') ? h_select_txt_not_to
+ : (type == 'c') ? h_select_txt_not_cc
+ : (type == 's') ? h_select_txt_not_subj
+ : (type == 'a') ? h_select_txt_not_all
+ : (type == 'r') ? h_select_txt_not_recip
+ : (type == 'p') ? h_select_txt_not_partic
+ : (type == 'b') ? h_select_txt_not_body
+ : NO_HELP)
+ : ((type == 'f') ? h_select_txt_from
+ : (type == 't') ? h_select_txt_to
+ : (type == 'c') ? h_select_txt_cc
+ : (type == 's') ? h_select_txt_subj
+ : (type == 'a') ? h_select_txt_all
+ : (type == 'r') ? h_select_txt_recip
+ : (type == 'p') ? h_select_txt_partic
+ : (type == 'b') ? h_select_txt_body
+ : NO_HELP))
+ : NO_HELP;
+
+ case 4 :
+ continue;
+
+ case 10 : /* To: default */
+ if(env && env->to && env->to->mailbox){
+ snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
+ env->to->host ? "@" : "",
+ env->to->host ? env->to->host : "");
+ sstring[sizeof(sstring)-1] = '\0';
+ }
+ continue;
+
+ case 11 : /* From: default */
+ if(env && env->from && env->from->mailbox){
+ snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
+ env->from->host ? "@" : "",
+ env->from->host ? env->from->host : "");
+ sstring[sizeof(sstring)-1] = '\0';
+ }
+ continue;
+
+ case 12 : /* Cc: default */
+ if(env && env->cc && env->cc->mailbox){
+ snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
+ env->cc->host ? "@" : "",
+ env->cc->host ? env->cc->host : "");
+ sstring[sizeof(sstring)-1] = '\0';
+ }
+ continue;
+
+ case 13 : /* Match my addresses */
+ me++;
+ snprintf(sstring, sizeof(sstring), not ? _(dont_match_me) : _(match_me));
+ continue;
+
+ case 14 : /* Subject: default */
+ if(env && env->subject && env->subject[0]){
+ char *q = NULL;
+
+ snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
+ buftmp[sizeof(buftmp)-1] = '\0';
+ q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp);
+ if(q != env->subject){
+ snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
+ savedsstring[sizeof(savedsstring)-1] = '\0';
+ }
+
+ snprintf(sstring, sizeof(sstring), "%s", q);
+ sstring[sizeof(sstring)-1] = '\0';
+ }
+
+ continue;
+
+ case 30 :
+ flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
+ if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
+ strncpy(sstring, p, sizeof(sstring));
+ sstring[sizeof(sstring)-1] = '\0';
+ if(history->hist[history->curindex]){
+ flagsforhist = history->hist[history->curindex]->flags;
+ not = (flagsforhist & 0x1) ? 1 : 0;
+ me = (flagsforhist & 0x2) ? 1 : 0;
+ }
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+
+ case 31 :
+ flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
+ if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
+ strncpy(sstring, p, sizeof(sstring));
+ sstring[sizeof(sstring)-1] = '\0';
+ if(history->hist[history->curindex]){
+ flagsforhist = history->hist[history->curindex]->flags;
+ not = (flagsforhist & 0x1) ? 1 : 0;
+ me = (flagsforhist & 0x2) ? 1 : 0;
+ }
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+
+ default :
+ break;
+ }
+
+ if(r == 1 || sstring[0] == '\0')
+ r = 'x';
+
+ break;
+ }
+ }
+
+ if(type == 'x' || r == 'x'){
+ cmd_cancelled("Selection by text");
+ return(1);
+ }
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ we_cancel = busy_cue(_("Selecting"), NULL, 1);
+
+ flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
+ save_hist(history, sstring, flagsforhist, NULL);
+
+ rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ return(rv);
+}
+
+
+/*
+ * Select by message size.
+ * Sets searched bits in mail_elts
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
+{
+ int r, large = 1, we_cancel = 0;
+ unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
+ char size[16], numbers[80], *p, *t;
+ HelpType help;
+ SEARCHPGM *pgm;
+ long flags = (SE_NOPREFETCH | SE_FREE);
+
+ numbers[0] = '\0';
+ ps_global->mangled_footer = 1;
+
+ help = NO_HELP;
+ while(1){
+ int flgs = OE_APPEND_CURRENT;
+
+ sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
+
+ r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
+ sizeof(numbers), large ? _(select_size_larger_msg)
+ : _(select_size_smaller_msg),
+ sel_size_opt, help, &flgs);
+ if(r == 4)
+ continue;
+
+ if(r == 14){
+ large = 1 - large;
+ continue;
+ }
+
+ if(r == 3){
+ help = (help == NO_HELP) ? (large ? h_select_by_larger_size
+ : h_select_by_smaller_size)
+ : NO_HELP;
+ continue;
+ }
+
+ for(t = p = numbers; *p ; p++) /* strip whitespace */
+ if(!isspace((unsigned char)*p))
+ *t++ = *p;
+
+ *t = '\0';
+
+ if(r == 1 || numbers[0] == '\0'){
+ cmd_cancelled("Selection by size");
+ return(1);
+ }
+ else
+ break;
+ }
+
+ if(numbers[0] == '-'){
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid size entered: %s"), numbers);
+ return(1);
+ }
+
+ t = size;
+ p = numbers;
+
+ while(*p && isdigit((unsigned char)*p))
+ *t++ = *p++;
+
+ *t = '\0';
+
+ if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
+ size[0] = '0';
+ size[1] = '\0';
+ }
+
+ if(size[0] == '\0'){
+ q_status_message1(SM_ORDER | SM_DING, 0, 2,
+ _("Invalid size entered: %s"), numbers);
+ return(1);
+ }
+
+ n = strtoul(size, (char **)NULL, 10);
+
+ size[0] = '\0';
+ if(*p == '.'){
+ /*
+ * We probably ought to just use atof() to convert 1.1 into a
+ * double, but since we haven't used atof() anywhere else I'm
+ * reluctant to use it because of portability concerns.
+ */
+ p++;
+ t = size;
+ while(*p && isdigit((unsigned char)*p)){
+ *t++ = *p++;
+ divisor *= 10;
+ }
+
+ *t = '\0';
+
+ if(size[0])
+ numerator = strtoul(size, (char **)NULL, 10);
+ }
+
+ switch(*p){
+ case 'g':
+ case 'G':
+ mult *= 1000;
+ /* fall through */
+
+ case 'm':
+ case 'M':
+ mult *= 1000;
+ /* fall through */
+
+ case 'k':
+ case 'K':
+ mult *= 1000;
+ break;
+ }
+
+ n = n * mult + (numerator * mult) / divisor;
+
+ pgm = mail_newsearchpgm();
+ if(large)
+ pgm->larger = n;
+ else
+ pgm->smaller = n;
+
+ if(is_imap_stream(stream) && !modern_imap_stream(stream))
+ flags |= SE_NOSERVER;
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ we_cancel = busy_cue(_("Selecting"), NULL, 1);
+
+ pgm->msgno = (limitsrch ? *limitsrch : NULL);
+ pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
+ /* we know this was freed in mail_search, let caller know */
+ if(limitsrch)
+ *limitsrch = NULL;
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ return(0);
+}
+
+
+/*
+ * visible_searchset -- return c-client search set unEXLDed
+ * sequence numbers
+ */
+SEARCHSET *
+visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ long n, run;
+ SEARCHSET *full_set = NULL, **set;
+
+ /*
+ * If we're talking to anything other than a server older than
+ * imap 4rev1, build a searchset otherwise it'll choke.
+ */
+ if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
+ if(any_lflagged(msgmap, MN_EXLD)){
+ for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
+ if(get_lflag(stream, NULL, n, MN_EXLD)){
+ if(run){ /* previous NOT excluded? */
+ if(run > 1L)
+ (*set)->last = n - 1L;
+
+ set = &(*set)->next;
+ run = 0L;
+ }
+ }
+ else if(run++){ /* next in run */
+ (*set)->last = n;
+ }
+ else{ /* start of run */
+ *set = mail_newsearchset();
+ (*set)->first = n;
+ }
+ }
+ else{
+ full_set = mail_newsearchset();
+ full_set->first = 1L;
+ full_set->last = stream->nmsgs;
+ }
+ }
+
+ return(full_set);
+}
+
+
+/*
+ * Select by message status bits.
+ * Sets searched bits in mail_elts
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
+{
+ int s, not = 0, we_cancel = 0, rv;
+
+ while(1){
+ s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
+ -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
+ NO_HELP, RB_NORM|RB_RET_HELP);
+
+ if(s == 'x'){
+ cmd_cancelled("Selection by status");
+ return(1);
+ }
+ else if(s == 3){
+ helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
+ HLPD_SIMPLE);
+ ps_global->mangled_screen = 1;
+ }
+ else if(s == '!')
+ not = !not;
+ else
+ break;
+ }
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ we_cancel = busy_cue(_("Selecting"), NULL, 1);
+ rv = agg_flag_select(stream, not, s, limitsrch);
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ return(rv);
+}
+
+
+/*
+ * Select by rule. Usually srch, indexcolor, and roles would be most useful.
+ * Sets searched bits in mail_elts
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
+{
+ char rulenick[1000], *nick;
+ PATGRP_S *patgrp;
+ int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
+ | ROLE_DO_INCOLS
+ | ROLE_DO_ROLES
+ | ROLE_DO_SCORES
+ | ROLE_DO_OTHER
+ | ROLE_DO_FILTER;
+
+ rulenick[0] = '\0';
+ ps_global->mangled_footer = 1;
+
+ do{
+ int oe_flags;
+
+ oe_flags = OE_APPEND_CURRENT;
+ r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
+ sizeof(rulenick),
+ not ? _("Rule to NOT match: ")
+ : _("Rule to match: "),
+ sel_key_opt, NO_HELP, &oe_flags);
+
+ if(r == 14){
+ /* select rulenick from a list */
+ if((nick=choose_a_rule(rflags)) != NULL){
+ strncpy(rulenick, nick, sizeof(rulenick)-1);
+ rulenick[sizeof(rulenick)-1] = '\0';
+ fs_give((void **) &nick);
+ }
+ else
+ r = 4;
+ }
+ else if(r == '!')
+ not = !not;
+
+ if(r == 3){
+ helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
+ ps_global->mangled_screen = 1;
+ }
+ else if(r == 1){
+ cmd_cancelled("Selection by Rule");
+ return(1);
+ }
+
+ removing_leading_and_trailing_white_space(rulenick);
+
+ }while(r == 3 || r == 4 || r == '!');
+
+
+ /*
+ * The approach of requiring a nickname instead of just allowing the
+ * user to select from the list of rules has the drawback that a rule
+ * may not have a nickname, or there may be more than one rule with
+ * the same nickname. However, it has the benefit of allowing the user
+ * to type in the nickname and, most importantly, allows us to set
+ * up the ! (not). We could incorporate the ! into the selection
+ * screen, but this is easier and also allows the typing of nicks.
+ * User can just set up nicknames if they want to use this feature.
+ */
+ patgrp = nick_to_patgrp(rulenick, rflags);
+
+ if(patgrp){
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ we_cancel = busy_cue(_("Selecting"), NULL, 1);
+ match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
+ get_msg_score,
+ (not ? MP_NOT : 0) | SE_NOPREFETCH);
+ free_patgrp(&patgrp);
+ if(we_cancel)
+ cancel_busy_cue(0);
+ }
+
+ if(limitsrch && *limitsrch){
+ mail_free_searchset(limitsrch);
+ *limitsrch = NULL;
+ }
+
+ return(0);
+}
+
+
+/*
+ * Allow user to choose a rule from their list of rules.
+ *
+ * Returns an allocated rule nickname on success, NULL otherwise.
+ */
+char *
+choose_a_rule(int rflags)
+{
+ char *choice = NULL;
+ char **rule_list, **lp;
+ int cnt = 0;
+ PAT_S *pat;
+ PAT_STATE pstate;
+
+ if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
+ q_status_message(SM_ORDER, 3, 3,
+ _("No rules available. Use Setup/Rules to add some."));
+ return(choice);
+ }
+
+ /*
+ * Build a list of rules to choose from.
+ */
+
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
+ cnt++;
+
+ if(cnt <= 0){
+ q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
+ return(choice);
+ }
+
+ lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
+ memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
+
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
+ *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
+ ? pat->patgrp->nick : "?");
+
+ /* TRANSLATORS: SELECT A RULE is a screen title
+ TRANSLATORS: Print something1 using something2.
+ "rules" is something1 */
+ choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
+ _("rules"), h_select_rule_screen,
+ _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
+
+ if(!choice)
+ q_status_message(SM_ORDER, 1, 4, "No choice");
+
+ free_list_array(&rule_list);
+
+ return(choice);
+}
+
+
+/*
+ * Select by current thread.
+ * Sets searched bits in mail_elts for this entire thread
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
+{
+ long n;
+ PINETHRD_S *thrd = NULL;
+ int ret = 1;
+ MESSAGECACHE *mc;
+
+ if(!stream)
+ return(ret);
+
+ for(n = 1L; n <= stream->nmsgs; n++)
+ if((mc = mail_elt(stream, n)) != NULL)
+ mc->searched = 0; /* clear searched bits */
+
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
+ if(thrd && thrd->top && thrd->top != thrd->rawno)
+ thrd = fetch_thread(stream, thrd->top);
+
+ /*
+ * This doesn't unselect if the thread is already selected
+ * (like select current does), it always selects.
+ * There is no way to select ! this thread.
+ */
+ if(thrd){
+ set_search_bit_for_thread(stream, thrd, limitsrch);
+ ret = 0;
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Select by message keywords.
+ * Sets searched bits in mail_elts
+ *
+ * Args limitsrch -- limit search to this searchset
+ *
+ * Returns 0 on success.
+ */
+int
+select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
+{
+ int r, not = 0, we_cancel = 0;
+ char keyword[MAXUSERFLAG+1], *kword;
+ char *error = NULL, *p, *prompt;
+ HelpType help;
+ SEARCHPGM *pgm;
+
+ keyword[0] = '\0';
+ ps_global->mangled_footer = 1;
+
+ help = NO_HELP;
+ do{
+ int oe_flags;
+
+ if(error){
+ q_status_message(SM_ORDER, 3, 4, error);
+ fs_give((void **) &error);
+ }
+
+ if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
+ if(not)
+ prompt = _("Keyword (or keyword initial) to NOT match: ");
+ else
+ prompt = _("Keyword (or keyword initial) to match: ");
+ }
+ else{
+ if(not)
+ prompt = _("Keyword to NOT match: ");
+ else
+ prompt = _("Keyword to match: ");
+ }
+
+ oe_flags = OE_APPEND_CURRENT;
+ r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
+ sizeof(keyword),
+ prompt, sel_key_opt, help, &oe_flags);
+
+ if(r == 14){
+ /* select keyword from a list */
+ if((kword=choose_a_keyword()) != NULL){
+ strncpy(keyword, kword, sizeof(keyword)-1);
+ keyword[sizeof(keyword)-1] = '\0';
+ fs_give((void **) &kword);
+ }
+ else
+ r = 4;
+ }
+ else if(r == '!')
+ not = !not;
+
+ if(r == 3)
+ help = help == NO_HELP ? h_select_keyword : NO_HELP;
+ else if(r == 1){
+ cmd_cancelled("Selection by keyword");
+ return(1);
+ }
+
+ removing_leading_and_trailing_white_space(keyword);
+
+ }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
+
+
+ if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
+ p = initial_to_keyword(keyword);
+ if(p != keyword){
+ strncpy(keyword, p, sizeof(keyword)-1);
+ keyword[sizeof(keyword)-1] = '\0';
+ }
+ }
+
+ /*
+ * We want to check the keyword, not the nickname of the keyword,
+ * so convert it to the keyword if necessary.
+ */
+ p = nick_to_keyword(keyword);
+ if(p != keyword){
+ strncpy(keyword, p, sizeof(keyword)-1);
+ keyword[sizeof(keyword)-1] = '\0';
+ }
+
+ pgm = mail_newsearchpgm();
+ if(not){
+ pgm->unkeyword = mail_newstringlist();
+ pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
+ pgm->unkeyword->text.size = strlen(keyword);
+ }
+ else{
+ pgm->keyword = mail_newstringlist();
+ pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
+ pgm->keyword->text.size = strlen(keyword);
+ }
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ we_cancel = busy_cue(_("Selecting"), NULL, 1);
+
+ pgm->msgno = (limitsrch ? *limitsrch : NULL);
+ pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
+ /* we know this was freed in mail_search, let caller know */
+ if(limitsrch)
+ *limitsrch = NULL;
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ return(0);
+}
+
+
+/*
+ * Allow user to choose a keyword from their list of keywords.
+ *
+ * Returns an allocated keyword on success, NULL otherwise.
+ */
+char *
+choose_a_keyword(void)
+{
+ char *choice = NULL;
+ char **keyword_list, **lp;
+ int cnt;
+ KEYWORD_S *kw;
+
+ /*
+ * Build a list of keywords to choose from.
+ */
+
+ for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
+ cnt++;
+
+ if(cnt <= 0){
+ q_status_message(SM_ORDER, 3, 4,
+ _("No keywords defined, use \"keywords\" option in Setup/Config"));
+ return(choice);
+ }
+
+ lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
+ memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
+
+ for(kw = ps_global->keywords; kw; kw = kw->next)
+ *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
+
+ /* TRANSLATORS: SELECT A KEYWORD is a screen title
+ TRANSLATORS: Print something1 using something2.
+ "keywords" is something1 */
+ choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
+ _("keywords"), h_select_keyword_screen,
+ _("HELP FOR SELECTING A KEYWORD"), NULL);
+
+ if(!choice)
+ q_status_message(SM_ORDER, 1, 4, "No choice");
+
+ free_list_array(&keyword_list);
+
+ return(choice);
+}
+
+
+/*
+ * Allow user to choose a list of keywords from their list of keywords.
+ *
+ * Returns allocated list.
+ */
+char **
+choose_list_of_keywords(void)
+{
+ LIST_SEL_S *listhead, *ls, *p;
+ char **ret = NULL;
+ int cnt, i;
+ KEYWORD_S *kw;
+
+ /*
+ * Build a list of keywords to choose from.
+ */
+
+ p = listhead = NULL;
+ for(kw = ps_global->keywords; kw; kw = kw->next){
+
+ ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(ls, 0, sizeof(*ls));
+ ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
+
+ if(p){
+ p->next = ls;
+ p = p->next;
+ }
+ else
+ listhead = p = ls;
+ }
+
+ if(!listhead)
+ return(ret);
+
+ /* TRANSLATORS: SELECT KEYWORDS is a screen title
+ Print something1 using something2.
+ "keywords" is something1 */
+ if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
+ _("SELECT KEYWORDS"), _("keywords"),
+ h_select_multkeyword_screen,
+ _("HELP FOR SELECTING KEYWORDS"), NULL)){
+ for(cnt = 0, p = listhead; p; p = p->next)
+ if(p->selected)
+ cnt++;
+
+ ret = (char **) fs_get((cnt+1) * sizeof(*ret));
+ memset(ret, 0, (cnt+1) * sizeof(*ret));
+ for(i = 0, p = listhead; p; p = p->next)
+ if(p->selected)
+ ret[i++] = cpystr(p->item ? p->item : "");
+ }
+
+ free_list_sel(&listhead);
+
+ return(ret);
+}
+
+
+/*
+ * Allow user to choose a charset
+ *
+ * Returns an allocated charset on success, NULL otherwise.
+ */
+char *
+choose_a_charset(int which_charsets)
+{
+ char *choice = NULL;
+ char **charset_list, **lp;
+ const CHARSET *cs;
+ int cnt;
+
+ /*
+ * Build a list of charsets to choose from.
+ */
+
+ for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
+ if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
+ && ((which_charsets & CAC_ALL)
+ || (which_charsets & CAC_POSTING
+ && cs->flags & CF_POSTING)
+ || (which_charsets & CAC_DISPLAY
+ && cs->type != CT_2022
+ && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
+ cnt++;
+ }
+
+ if(cnt <= 0){
+ q_status_message(SM_ORDER, 3, 4,
+ _("No charsets found? Enter charset manually."));
+ return(choice);
+ }
+
+ lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
+ memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
+
+ for(cs = utf8_charset(NIL); cs && cs->name; cs++){
+ if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
+ && ((which_charsets & CAC_ALL)
+ || (which_charsets & CAC_POSTING
+ && cs->flags & CF_POSTING)
+ || (which_charsets & CAC_DISPLAY
+ && cs->type != CT_2022
+ && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
+ *lp++ = cpystr(cs->name);
+ }
+
+ /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
+ TRANSLATORS: Print something1 using something2.
+ "character sets" is something1 */
+ choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
+ _("character sets"), h_select_charset_screen,
+ _("HELP FOR SELECTING A CHARACTER SET"), NULL);
+
+ if(!choice)
+ q_status_message(SM_ORDER, 1, 4, "No choice");
+
+ free_list_array(&charset_list);
+
+ return(choice);
+}
+
+
+/*
+ * Allow user to choose a list of character sets and/or scripts
+ *
+ * Returns allocated list.
+ */
+char **
+choose_list_of_charsets(void)
+{
+ LIST_SEL_S *listhead, *ls, *p;
+ char **ret = NULL;
+ int cnt, i, got_one;
+ const CHARSET *cs;
+ SCRIPT *s;
+ char *q, *t;
+ long width, limit;
+ char buf[1024], *folded;
+
+ /*
+ * Build a list of charsets to choose from.
+ */
+
+ p = listhead = NULL;
+
+ /* this width is determined by select_from_list_screen() */
+ width = ps_global->ttyo->screen_cols - 4;
+
+ /* first comes a list of scripts (sets of character sets) */
+ for(s = utf8_script(NIL); s && s->name; s++){
+
+ limit = sizeof(buf)-1;
+ q = buf;
+ memset(q, 0, limit+1);
+
+ if(s->name)
+ sstrncpy(&q, s->name, limit);
+
+ if(s->description){
+ sstrncpy(&q, " (", limit-(q-buf));
+ sstrncpy(&q, s->description, limit-(q-buf));
+ sstrncpy(&q, ")", limit-(q-buf));
+ }
+
+ /* add the list of charsets that are in this script */
+ got_one = 0;
+ for(cs = utf8_charset(NIL);
+ cs && cs->name && (q-buf) < limit; cs++){
+ if(cs->script & s->script){
+ /*
+ * Filter out some un-useful members of the list.
+ * UTF-7 and UTF-8 weren't actually in the list at the
+ * time this was written. Just making sure.
+ */
+ if(!strucmp(cs->name, "ISO-2022-JP-2")
+ || !strucmp(cs->name, "UTF-7")
+ || !strucmp(cs->name, "UTF-8"))
+ continue;
+
+ if(got_one)
+ sstrncpy(&q, " ", limit-(q-buf));
+ else{
+ got_one = 1;
+ sstrncpy(&q, " {", limit-(q-buf));
+ }
+
+ sstrncpy(&q, cs->name, limit-(q-buf));
+ }
+ }
+
+ if(got_one)
+ sstrncpy(&q, "}", limit-(q-buf));
+
+ /* fold this line so that it can all be seen on the screen */
+ folded = fold(buf, width, width, "", " ", FLD_NONE);
+ if(folded){
+ t = folded;
+ while(t && *t && (q = strindex(t, '\n')) != NULL){
+ *q = '\0';
+
+ ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(ls, 0, sizeof(*ls));
+ if(t == folded)
+ ls->item = cpystr(s->name);
+ else
+ ls->flags = SFL_NOSELECT;
+
+ ls->display_item = cpystr(t);
+
+ t = q+1;
+
+ if(p){
+ p->next = ls;
+ p = p->next;
+ }
+ else{
+ /* add a heading */
+ listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(listhead, 0, sizeof(*listhead));
+ listhead->flags = SFL_NOSELECT;
+ listhead->display_item =
+ cpystr(_("Scripts representing groups of related character sets"));
+ listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(listhead->next, 0, sizeof(*listhead));
+ listhead->next->flags = SFL_NOSELECT;
+ listhead->next->display_item =
+ cpystr(repeat_char(width, '-'));
+
+ listhead->next->next = ls;
+ p = ls;
+ }
+ }
+
+ fs_give((void **) &folded);
+ }
+ }
+
+ ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(ls, 0, sizeof(*ls));
+ ls->flags = SFL_NOSELECT;
+ if(p){
+ p->next = ls;
+ p = p->next;
+ }
+ else
+ listhead = p = ls;
+
+ ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(ls, 0, sizeof(*ls));
+ ls->flags = SFL_NOSELECT;
+ ls->display_item =
+ cpystr(_("Individual character sets, may be mixed with scripts"));
+ p->next = ls;
+ p = p->next;
+
+ ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(ls, 0, sizeof(*ls));
+ ls->flags = SFL_NOSELECT;
+ ls->display_item =
+ cpystr(repeat_char(width, '-'));
+ p->next = ls;
+ p = p->next;
+
+ /* then comes a list of individual character sets */
+ for(cs = utf8_charset(NIL); cs && cs->name; cs++){
+ ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(ls, 0, sizeof(*ls));
+ ls->item = cpystr(cs->name);
+
+ if(p){
+ p->next = ls;
+ p = p->next;
+ }
+ else
+ listhead = p = ls;
+ }
+
+ if(!listhead)
+ return(ret);
+
+ /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
+ Print something1 using something2.
+ "character sets" is something1 */
+ if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
+ _("SELECT CHARACTER SETS"), _("character sets"),
+ h_select_multcharsets_screen,
+ _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
+ for(cnt = 0, p = listhead; p; p = p->next)
+ if(p->selected)
+ cnt++;
+
+ ret = (char **) fs_get((cnt+1) * sizeof(*ret));
+ memset(ret, 0, (cnt+1) * sizeof(*ret));
+ for(i = 0, p = listhead; p; p = p->next)
+ if(p->selected)
+ ret[i++] = cpystr(p->item ? p->item : "");
+ }
+
+ free_list_sel(&listhead);
+
+ return(ret);
+}
+
+/* Report quota summary resources in an IMAP server */
+
+void cmd_quota (struct pine *state)
+{
+ QUOTALIST *imapquota;
+ NETMBX mb;
+ STORE_S *store;
+ SCROLL_S sargs;
+
+ if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
+ q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
+ return;
+ }
+
+ if (state->mail_stream
+ && !sp_dead_stream(state->mail_stream)
+ && state->mail_stream->mailbox
+ && *state->mail_stream->mailbox
+ && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
+ imap_getquotaroot(state->mail_stream, mb.mailbox);
+
+ if(!state->quota) /* failed ? */
+ return; /* go back... */
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
+ return;
+ }
+
+ so_puts(store, "Quota Report for ");
+ so_puts(store, state->mail_stream->original_mailbox);
+ so_puts(store, "\n\n");
+
+ for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
+
+ so_puts(store, _("Resource : "));
+ so_puts(store, imapquota->name);
+ so_writec('\n', store);
+
+ so_puts(store, _("Usage : "));
+ so_puts(store, long2string(imapquota->usage));
+ if(!strucmp(imapquota->name,"STORAGE"))
+ so_puts(store, " KiB ");
+ if(!strucmp(imapquota->name,"MESSAGE")){
+ so_puts(store, _(" message"));
+ if(imapquota->usage != 1)
+ so_puts(store, _("s ")); /* plural */
+ else
+ so_puts(store, _(" "));
+ }
+ so_writec('(', store);
+ so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
+ so_puts(store, "%)\n");
+
+ so_puts(store, _("Limit : "));
+ so_puts(store, long2string(imapquota->limit));
+ if(!strucmp(imapquota->name,"STORAGE"))
+ so_puts(store, " KiB\n\n");
+ if(!strucmp(imapquota->name,"MESSAGE")){
+ so_puts(store, _(" message"));
+ if(imapquota->usage != 1)
+ so_puts(store, _("s\n\n")); /* plural */
+ else
+ so_puts(store, _("\n\n"));
+ }
+ }
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = _("Quota Resources Summary");
+ sargs.bar.title = _("QUOTA SUMMARY");
+ sargs.proc.tool = NULL;
+ sargs.help.text = h_quota_command;
+ sargs.help.title = NULL;
+ sargs.keys.menu = &pine_quota_keymenu;
+ setbitmap(sargs.keys.bitmap);
+
+ scrolltool(&sargs);
+ so_give(&store);
+
+ if (state->quota)
+ mail_free_quotalist(&(state->quota));
+}
+
+/*----------------------------------------------------------------------
+ Prompt the user for the type of sort he desires
+
+Args: state -- pine state pointer
+ q1 -- Line to prompt on
+
+ Returns 0 if it was cancelled, 1 otherwise.
+ ----*/
+int
+select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
+{
+ char prompt[200], tmp[3], *p;
+ int s, i;
+ int deefault = 'a', retval = 1;
+ HelpType help;
+ ESCKEY_S sorts[14];
+
+#ifdef _WINDOWS
+ DLG_SORTPARAM sortsel;
+
+ if (mswin_usedialog ()) {
+
+ sortsel.reverse = mn_get_revsort (state->msgmap);
+ sortsel.cursort = mn_get_sort (state->msgmap);
+ /* assumption here that HelpType is char ** */
+ sortsel.helptext = h_select_sort;
+ sortsel.rval = 0;
+
+ if ((retval = os_sortdialog (&sortsel))) {
+ *sort = sortsel.cursort;
+ *rev = sortsel.reverse;
+ }
+
+ return (retval);
+ }
+#endif
+
+ /*----- String together the prompt ------*/
+ tmp[1] = '\0';
+ if(F_ON(F_USE_FK,ps_global))
+ strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
+ else
+ strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
+ sizeof(prompt));
+
+ for(i = 0; state->sort_types[i] != EndofList; i++) {
+ sorts[i].rval = i;
+ p = sorts[i].label = sort_name(state->sort_types[i]);
+ while(*(p+1) && islower((unsigned char)*p))
+ p++;
+
+ sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
+ sorts[i].name = cpystr(tmp);
+
+ if(mn_get_sort(state->msgmap) == state->sort_types[i])
+ deefault = sorts[i].rval;
+ }
+
+ sorts[i].ch = 'r';
+ sorts[i].rval = 'r';
+ sorts[i].name = cpystr("R");
+ if(F_ON(F_USE_FK,ps_global))
+ sorts[i].label = N_("Reverse");
+ else
+ sorts[i].label = "";
+
+ sorts[++i].ch = -1;
+ help = h_select_sort;
+
+ if((F_ON(F_USE_FK,ps_global)
+ && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
+ help,RB_NORM)) != 'x'))
+ ||
+ (F_OFF(F_USE_FK,ps_global)
+ && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
+ help,RB_NORM)) != 'x'))){
+ state->mangled_body = 1; /* signal screen's changed */
+ if(s == 'r')
+ *rev = !mn_get_revsort(state->msgmap);
+ else
+ *sort = state->sort_types[s];
+
+ if(F_ON(F_SHOW_SORT, ps_global))
+ ps_global->mangled_header = 1;
+ }
+ else{
+ retval = 0;
+ cmd_cancelled("Sort");
+ }
+
+ while(--i >= 0)
+ fs_give((void **)&sorts[i].name);
+
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ return(retval);
+}
+
+
+/*---------------------------------------------------------------------
+ Build list of folders in the given context for user selection
+
+ Args: c -- pointer to pointer to folder's context context
+ f -- folder prefix to display
+ sublist -- whether or not to use 'f's contents as prefix
+ lister -- function used to do the actual display
+
+ Returns: malloc'd string containing sequence, else NULL if
+ no messages in msgmap with local "selected" flag.
+ ----*/
+int
+display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
+{
+ int rc;
+ CONTEXT_S *tc;
+ void (*redraw)(void) = ps_global->redrawer;
+
+ push_titlebar_state();
+ tc = *c;
+ if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
+ *c = tc;
+
+ ClearScreen();
+ pop_titlebar_state();
+ redraw_titlebar();
+ if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
+ (*ps_global->redrawer)();
+
+ if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
+ return(1);
+
+ return(0);
+}
+
+
+/*
+ * Allow user to choose a single item from a list of strings.
+ *
+ * Args list -- Array of strings to choose from, NULL terminated.
+ * displist -- Array of strings to display instead of displaying list.
+ * Indices correspond to the list array. Display the displist
+ * but return the item from list if displist non-NULL.
+ * title -- For conf_scroll_screen
+ * pdesc -- For conf_scroll_screen
+ * help -- For conf_scroll_screen
+ * htitle -- For conf_scroll_screen
+ *
+ * Returns an allocated copy of the chosen item or NULL.
+ */
+char *
+choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
+ char *htitle, char *cursor_location)
+{
+ LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
+ char **t, **dl;
+ char *ret = NULL, *choice = NULL;
+
+ /* build the LIST_SEL_S list */
+ p = listhead = NULL;
+ for(t = list, dl = displist; *t; t++, dl++){
+ ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(ls, 0, sizeof(*ls));
+ ls->item = cpystr(*t);
+ if(displist)
+ ls->display_item = cpystr(*dl);
+
+ if(cursor_location && (cursor_location == (*t)))
+ starting_val = ls;
+
+ if(p){
+ p->next = ls;
+ p = p->next;
+ }
+ else
+ listhead = p = ls;
+ }
+
+ if(!listhead)
+ return(ret);
+
+ if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
+ help, htitle, starting_val))
+ for(p = listhead; !choice && p; p = p->next)
+ if(p->selected)
+ choice = p->item;
+
+ if(choice)
+ ret = cpystr(choice);
+
+ free_list_sel(&listhead);
+
+ return(ret);
+}
+
+
+void
+free_list_sel(LIST_SEL_S **lsel)
+{
+ if(lsel && *lsel){
+ free_list_sel(&(*lsel)->next);
+ if((*lsel)->item)
+ fs_give((void **) &(*lsel)->item);
+
+ if((*lsel)->display_item)
+ fs_give((void **) &(*lsel)->display_item);
+
+ fs_give((void **) lsel);
+ }
+}
+
+
+/*
+ * file_lister - call pico library's file lister
+ */
+int
+file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
+{
+ PICO pbf;
+ int rv;
+ void (*redraw)(void) = ps_global->redrawer;
+
+ standard_picobuf_setup(&pbf);
+ push_titlebar_state();
+ if(!newmail)
+ pbf.newmail = NULL;
+
+/* BUG: what about help command and text? */
+ pbf.pine_anchor = title;
+
+ rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
+ standard_picobuf_teardown(&pbf);
+ fix_windsize(ps_global);
+ init_signals(); /* has it's own signal stuff */
+
+ /* Restore display's titlebar and body */
+ pop_titlebar_state();
+ redraw_titlebar();
+ if((ps_global->redrawer = redraw) != NULL)
+ (*ps_global->redrawer)();
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Print current folder index
+
+ ---*/
+int
+print_index(struct pine *state, MSGNO_S *msgmap, int agg)
+{
+ long i;
+ ICE_S *ice;
+ char buf[MAX_SCREEN_COLS+1];
+
+ for(i = 1L; i <= mn_get_total(msgmap); i++){
+ if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
+ continue;
+
+ if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
+ continue;
+
+ ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
+
+ if(ice){
+ /*
+ * I don't understand why we'd want to mark the current message
+ * instead of printing out the first character of the status
+ * so I'm taking it out and including the first character of the
+ * line instead. Hubert 2006-02-09
+ *
+ if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
+ return(0);
+ */
+
+ if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
+ print_char)
+ || !gf_puts(NEWLINE, print_char))
+ return(0);
+ }
+ }
+
+ return(1);
+}
+
+
+#ifdef _WINDOWS
+
+/*
+ * windows callback to get/set header mode state
+ */
+int
+header_mode_callback(set, args)
+ int set;
+ long args;
+{
+ return(ps_global->full_header);
+}
+
+
+/*
+ * windows callback to get/set zoom mode state
+ */
+int
+zoom_mode_callback(set, args)
+ int set;
+ long args;
+{
+ return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
+}
+
+
+/*
+ * windows callback to get/set zoom mode state
+ */
+int
+any_selected_callback(set, args)
+ int set;
+ long args;
+{
+ return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
+}
+
+
+/*
+ *
+ */
+int
+flag_callback(set, flags)
+ int set;
+ long flags;
+{
+ MESSAGECACHE *mc;
+ int newflags = 0;
+ long msgno;
+ int permflag = 0;
+
+ switch (set) {
+ case 1: /* Important */
+ permflag = ps_global->mail_stream->perm_flagged;
+ break;
+
+ case 2: /* New */
+ permflag = ps_global->mail_stream->perm_seen;
+ break;
+
+ case 3: /* Answered */
+ permflag = ps_global->mail_stream->perm_answered;
+ break;
+
+ case 4: /* Deleted */
+ permflag = ps_global->mail_stream->perm_deleted;
+ break;
+
+ }
+
+ if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
+ && can_set_flag(ps_global, "flag", permflag)))
+ return(0);
+
+ if(sp_io_error_on_stream(ps_global->mail_stream)){
+ sp_set_io_error_on_stream(ps_global->mail_stream, 0);
+ pine_mail_check(ps_global->mail_stream); /* forces write */
+ return(0);
+ }
+
+ msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
+ if(msgno > 0L && ps_global->mail_stream
+ && msgno <= ps_global->mail_stream->nmsgs
+ && (mc = mail_elt(ps_global->mail_stream, msgno))
+ && mc->valid){
+ /*
+ * NOTE: code below is *VERY* sensitive to the order of
+ * the messages defined in resource.h for flag handling.
+ * Don't change it unless you know what you're doing.
+ */
+ if(set){
+ char *flagstr;
+ long mflag;
+
+ switch(set){
+ case 1 : /* Important */
+ flagstr = "\\FLAGGED";
+ mflag = (mc->flagged) ? 0L : ST_SET;
+ break;
+
+ case 2 : /* New */
+ flagstr = "\\SEEN";
+ mflag = (mc->seen) ? 0L : ST_SET;
+ break;
+
+ case 3 : /* Answered */
+ flagstr = "\\ANSWERED";
+ mflag = (mc->answered) ? 0L : ST_SET;
+ break;
+
+ case 4 : /* Deleted */
+ flagstr = "\\DELETED";
+ mflag = (mc->deleted) ? 0L : ST_SET;
+ break;
+
+ default : /* bogus */
+ return(0);
+ }
+
+ mail_flag(ps_global->mail_stream, long2string(msgno),
+ flagstr, mflag);
+
+ if(ps_global->redrawer)
+ (*ps_global->redrawer)();
+ }
+ else{
+ /* Important */
+ if(mc->flagged)
+ newflags |= 0x0001;
+
+ /* New */
+ if(!mc->seen)
+ newflags |= 0x0002;
+
+ /* Answered */
+ if(mc->answered)
+ newflags |= 0x0004;
+
+ /* Deleted */
+ if(mc->deleted)
+ newflags |= 0x0008;
+ }
+ }
+
+ return(newflags);
+}
+
+
+
+/*
+ * BUG: Should teach this about keywords
+ */
+MPopup *
+flag_submenu(mc)
+ MESSAGECACHE *mc;
+{
+ static MPopup flag_submenu[] = {
+ {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
+ {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
+ {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
+ {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
+ {tTail}
+ };
+
+ /* Important */
+ flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
+
+ /* New */
+ flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
+
+ /* Answered */
+ flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
+
+ /* Deleted */
+ flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
+
+ return(flag_submenu);
+}
+
+#endif /* _WINDOWS */
diff --git a/alpine/mailcmd.h b/alpine/mailcmd.h
new file mode 100644
index 00000000..c86ef7cd
--- /dev/null
+++ b/alpine/mailcmd.h
@@ -0,0 +1,107 @@
+/*
+ * $Id: mailcmd.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_MAILCMD_INCLUDED
+#define PINE_MAILCMD_INCLUDED
+
+
+#include <general.h>
+#include "context.h"
+#include "mailview.h"
+#include "radio.h"
+#include "listsel.h"
+#include "../pith/mailcmd.h"
+#include "../pith/mailindx.h"
+#include "../pith/state.h"
+#include "../pith/msgno.h"
+#include "../pith/store.h"
+#include "../pith/filter.h"
+#include "../pith/string.h"
+#include "../pith/hist.h"
+
+
+#define USER_INPUT_TIMEOUT(ps) ((ps->hours_to_timeout > 0) && \
+ ((time(0) - time_of_last_input()) > 60*60*(ps->hours_to_timeout)))
+
+
+#define GE_NONE 0x00 /* get_export_filename flags */
+#define GE_IS_EXPORT 0x01 /* include EXPORT: in prompt */
+#define GE_SEQ_SENSITIVE 0x02 /* Sensitive to seq # changes */
+#define GE_NO_APPEND 0x04 /* No appending to file allowed */
+#define GE_IS_IMPORT 0x08 /* No writing of file */
+#define GE_ALLPARTS 0x10 /* Add AllParts toggle to options */
+
+#define GER_NONE 0x00 /* get_export_filename return flags */
+#define GER_OVER 0x01 /* overwrite of existing file */
+#define GER_APPEND 0x02 /* append of existing file */
+#define GER_ALLPARTS 0x04 /* AllParts toggle is on */
+
+
+#define CAC_NONE 0x00 /* flags for choose_a_charset */
+#define CAC_ALL 0x01 /* choose from entire list */
+#define CAC_POSTING 0x02 /* choose from charsets useful for posting */
+#define CAC_DISPLAY 0x04 /* choose from charsets useful for display */
+
+
+typedef enum {DontAsk, NoDel, Del, RetNoDel, RetDel} SaveDel;
+typedef enum {DontAskPreserve, NoPreserve, Preserve, RetNoPreserve, RetPreserve} SavePreserveOrder;
+
+typedef enum {View, MsgIndx, ThrdIndx} CmdWhere;
+
+
+/* exported protoypes */
+int process_cmd(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere, int *);
+char *pretty_command(UCS);
+void bogus_command(UCS, char *);
+void bogus_utf8_command(char *, char *);
+int save_prompt(struct pine *, CONTEXT_S **, char *, size_t,
+ char *, ENVELOPE *, long, char *, SaveDel *,
+ SavePreserveOrder *);
+int create_for_save_prompt(CONTEXT_S *, char *, int);
+int expunge_prompt(MAILSTREAM *, char *, long);
+int save_size_changed_prompt(long, int);
+void expunge_and_close_begins(int, char *);
+int simple_export(struct pine *, void *, SourceType, char *, char *);
+int get_export_filename(struct pine *, char *, char *, char *, size_t, char *,
+ char *, ESCKEY_S *, int *, int, int, HISTORY_S **);
+char *build_updown_cmd(char *, size_t, char *, char *, char*);
+int bezerk_delimiter(ENVELOPE *, MESSAGECACHE *, gf_io_t, int);
+long jump_to(MSGNO_S *, int, UCS, SCROLL_S *, CmdWhere);
+char *broach_folder(int, int, int *, CONTEXT_S **);
+int ask_mailbox_reopen(struct pine *, int *);
+void visit_folder(struct pine *, char *, CONTEXT_S *, MAILSTREAM *, unsigned long);
+int select_by_current(struct pine *, MSGNO_S *, CmdWhere);
+int apply_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int);
+char **choose_list_of_keywords(void);
+char *choose_a_charset(int);
+char **choose_list_of_charsets(void);
+char *choose_item_from_list(char **, char **, char *, char *, HelpType, char *, char *);
+int display_folder_list(CONTEXT_S **, char *, int,
+ int (*)(struct pine *, CONTEXT_S **, char *, int));
+int file_lister(char *, char *, size_t, char *, size_t, int, int);
+int read_msg_prompt(long, char *);
+void advance_cur_after_delete(struct pine *, MAILSTREAM *, MSGNO_S *, CmdWhere);
+void free_list_sel(LIST_SEL_S **);
+#ifdef _WINDOWS
+int header_mode_callback(int, long);
+int zoom_mode_callback(int, long);
+int any_selected_callback(int, long);
+int flag_callback(int, long);
+MPopup *flag_submenu(MESSAGECACHE *);
+#endif
+
+
+#endif /* PINE_MAILCMD_INCLUDED */
diff --git a/alpine/mailindx.c b/alpine/mailindx.c
new file mode 100644
index 00000000..01e01558
--- /dev/null
+++ b/alpine/mailindx.c
@@ -0,0 +1,3644 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailindx.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "mailindx.h"
+#include "mailcmd.h"
+#include "status.h"
+#include "context.h"
+#include "keymenu.h"
+#include "alpine.h"
+#include "help.h"
+#include "radio.h"
+#include "titlebar.h"
+#include "../pith/flag.h"
+#include "../pith/newmail.h"
+#include "../pith/thread.h"
+#include "../pith/conf.h"
+#include "../pith/msgno.h"
+#include "../pith/icache.h"
+#include "../pith/state.h"
+#include "../pith/bitmap.h"
+#include "../pith/news.h"
+#include "../pith/strlst.h"
+#include "../pith/sequence.h"
+#include "../pith/sort.h"
+#include "../pith/hist.h"
+#include "../pith/busy.h"
+#include "../pith/signal.h"
+
+
+struct save_thrdinfo {
+ ICE_S *(*format_index_line)(INDEXDATA_S *);
+ void (*setup_header_widths)(MAILSTREAM *);
+ unsigned viewing_a_thread:1;
+};
+
+
+static OtherMenu what_keymenu = FirstMenu;
+
+struct index_state *current_index_state = NULL;
+
+
+/*
+ * Internal prototypes
+ */
+void index_index_screen(struct pine *);
+void thread_index_screen(struct pine *);
+int update_index(struct pine *, struct index_state *);
+int index_scroll_up(long);
+int index_scroll_down(long);
+int index_scroll_to_pos(long);
+long top_ent_calc(MAILSTREAM *, MSGNO_S *, long, long);
+void reset_index_border(void);
+void redraw_index_body(void);
+int paint_index_line(ICE_S *, int, long, IndexColType, IndexColType, IndexColType,
+ struct entry_state *, int, int);
+void pine_imap_envelope(MAILSTREAM *, unsigned long, ENVELOPE *);
+void index_search(struct pine *, MAILSTREAM *, int, MSGNO_S *);
+#ifdef _WINDOWS
+int index_scroll_callback(int,long);
+int index_gettext_callback(char *, size_t, void **, long *, int *);
+void index_popup(IndexType style, MAILSTREAM *, MSGNO_S *, int);
+char *pcpine_help_index(char *);
+char *pcpine_help_index_simple(char *);
+#endif
+
+
+
+/*----------------------------------------------------------------------
+
+
+ ----*/
+struct key_menu *
+do_index_border(CONTEXT_S *cntxt, char *folder, MAILSTREAM *stream, MSGNO_S *msgmap,
+ IndexType style, int *which_keys, int flags)
+{
+ struct key_menu *km = (style == ThreadIndex)
+ ? &thread_keymenu
+ : (ps_global->mail_stream != stream)
+ ? &simple_index_keymenu
+ : &index_keymenu;
+
+ if(flags & INDX_CLEAR)
+ ClearScreen();
+
+ if(flags & INDX_HEADER)
+ set_titlebar((style == ThreadIndex)
+ /* TRANSLATORS: these are some screen titles */
+ ? _("THREAD INDEX")
+ : (stream == ps_global->mail_stream)
+ ? (style == MsgIndex || style == MultiMsgIndex)
+ ? _("MESSAGE INDEX")
+ : _("ZOOMED MESSAGE INDEX")
+ : (!strcmp(folder, INTERRUPTED_MAIL))
+ ? _("COMPOSE: SELECT INTERRUPTED")
+ : (ps_global->VAR_FORM_FOLDER
+ && !strcmp(ps_global->VAR_FORM_FOLDER, folder))
+ ? _("COMPOSE: SELECT FORM LETTER")
+ : _("COMPOSE: SELECT POSTPONED"),
+ stream, cntxt, folder, msgmap, 1,
+ (style == ThreadIndex) ? ThrdIndex
+ : (THREADING()
+ && sp_viewing_a_thread(stream))
+ ? ThrdMsgNum
+ : MessageNumber,
+ 0, 0, NULL);
+
+ if(flags & INDX_FOOTER) {
+ bitmap_t bitmap;
+ int cmd;
+
+ setbitmap(bitmap);
+
+ if(km == &index_keymenu){
+ if(THREADING() && sp_viewing_a_thread(stream)){
+ menu_init_binding(km, '<', MC_THRDINDX, "<",
+ N_("ThrdIndex"), BACK_KEY);
+ menu_add_binding(km, ',', MC_THRDINDX);
+ }
+ else{
+ menu_init_binding(km, '<', MC_FOLDERS, "<",
+ N_("FldrList"), BACK_KEY);
+ menu_add_binding(km, ',', MC_FOLDERS);
+ }
+ if(F_OFF(F_ENABLE_PIPE,ps_global))
+ clrbitn(VIEW_PIPE_KEY, bitmap); /* always clear for DOS */
+ if(F_OFF(F_ENABLE_FULL_HDR,ps_global))
+ clrbitn(VIEW_FULL_HEADERS_KEY, bitmap);
+ if(F_OFF(F_ENABLE_BOUNCE,ps_global))
+ clrbitn(BOUNCE_KEY, bitmap);
+ if(F_OFF(F_ENABLE_FLAG,ps_global))
+ clrbitn(FLAG_KEY, bitmap);
+ if(F_OFF(F_ENABLE_AGG_OPS,ps_global)){
+ clrbitn(SELECT_KEY, bitmap);
+ clrbitn(APPLY_KEY, bitmap);
+ clrbitn(SELCUR_KEY, bitmap);
+ if(style != ZoomIndex)
+ clrbitn(ZOOM_KEY, bitmap);
+
+ }
+
+ if(style == MultiMsgIndex){
+ clrbitn(PREVM_KEY, bitmap);
+ clrbitn(NEXTM_KEY, bitmap);
+ }
+ }
+
+ if(km == &index_keymenu || km == &thread_keymenu){
+ if(IS_NEWS(stream)){
+ km->keys[EXCLUDE_KEY].label = N_("eXclude");
+ KS_OSDATASET(&km->keys[EXCLUDE_KEY], KS_NONE);
+ }
+ else {
+ clrbitn(UNEXCLUDE_KEY, bitmap);
+ km->keys[EXCLUDE_KEY].label = N_("eXpunge");
+ KS_OSDATASET(&km->keys[EXCLUDE_KEY], KS_EXPUNGE);
+ }
+ }
+
+ if(km != &simple_index_keymenu && !THRD_COLLAPSE_ENABLE())
+ clrbitn(COLLAPSE_KEY, bitmap);
+
+ menu_clear_binding(km, KEY_LEFT);
+ menu_clear_binding(km, KEY_RIGHT);
+ if(F_ON(F_ARROW_NAV, ps_global)){
+ if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
+ menu_add_binding(km, '<', cmd);
+ menu_add_binding(km, KEY_LEFT, cmd);
+ }
+
+ if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
+ menu_add_binding(km, '>', cmd);
+ menu_add_binding(km, KEY_RIGHT, cmd);
+ }
+ }
+
+ if(menu_binding_index(km, MC_JUMP) >= 0){
+ for(cmd = 0; cmd < 10; cmd++)
+ if(F_ON(F_ENABLE_JUMP, ps_global))
+ (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
+ else
+ (void) menu_clear_binding(km, '0' + cmd);
+ }
+
+ draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps_global), 0, what_keymenu);
+ what_keymenu = SameMenu;
+ if(which_keys)
+ *which_keys = km->which; /* pass back to caller */
+ }
+
+ return(km);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Main loop executing commands for the mail index screen
+
+ Args: state -- the pine_state structure for next/prev screen pointers
+ and to pass to the index manager...
+ ----*/
+
+void
+mail_index_screen(struct pine *state)
+{
+ if(!state->mail_stream) {
+ q_status_message(SM_ORDER, 0, 3, _("No folder is currently open"));
+ state->prev_screen = mail_index_screen;
+ state->next_screen = main_menu_screen;
+ return;
+ }
+
+ state->prev_screen = mail_index_screen;
+ state->next_screen = SCREEN_FUN_NULL;
+
+ if(THRD_AUTO_VIEW()
+ && sp_viewing_a_thread(state->mail_stream)
+ && state->view_skipped_index
+ && unview_thread(state, state->mail_stream, state->msgmap)){
+ state->next_screen = mail_index_screen;
+ state->view_skipped_index = 0;
+ state->mangled_screen = 1;
+ }
+
+ adjust_cur_to_visible(state->mail_stream, state->msgmap);
+
+ if(THRD_INDX())
+ thread_index_screen(state);
+ else
+ index_index_screen(state);
+}
+
+
+void
+index_index_screen(struct pine *state)
+{
+ dprint((1, "\n\n ---- MAIL INDEX ----\n"));
+
+ setup_for_index_index_screen();
+
+ index_lister(state, state->context_current, state->cur_folder,
+ state->mail_stream, state->msgmap);
+}
+
+
+void
+thread_index_screen(struct pine *state)
+{
+ dprint((1, "\n\n ---- THREAD INDEX ----\n"));
+
+ setup_for_thread_index_screen();
+
+ index_lister(state, state->context_current, state->cur_folder,
+ state->mail_stream, state->msgmap);
+}
+
+
+void *
+stop_threading_temporarily(void)
+{
+ struct save_thrdinfo *ti;
+
+ ps_global->turn_off_threading_temporarily = 1;
+
+ ti = (struct save_thrdinfo *) fs_get(sizeof(*ti));
+ ti->format_index_line = format_index_line;
+ ti->setup_header_widths = setup_header_widths;
+ ti->viewing_a_thread = sp_viewing_a_thread(ps_global->mail_stream);
+
+ setup_for_index_index_screen();
+
+ return((void *) ti);
+}
+
+
+void
+restore_threading(void **p)
+{
+ struct save_thrdinfo *ti;
+
+ ps_global->turn_off_threading_temporarily = 0;
+
+ if(p && *p){
+ ti = (struct save_thrdinfo *) (*p);
+ format_index_line = ti->format_index_line;
+ setup_header_widths = ti->setup_header_widths;
+ sp_set_viewing_a_thread(ps_global->mail_stream, ti->viewing_a_thread);
+
+ fs_give(p);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Main loop executing commands for the mail index screen
+
+ Args: state -- pine_state structure for display flags and such
+ msgmap -- c-client/pine message number mapping struct
+ ----*/
+
+int
+index_lister(struct pine *state, CONTEXT_S *cntxt, char *folder, MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ UCS ch;
+ int cmd, which_keys, force,
+ cur_row, cur_col, km_popped, paint_status;
+ static int old_day = -1;
+ long i, j, k, old_max_msgno;
+ char *utf8str;
+ IndexType style, old_style = MsgIndex;
+ struct index_state id;
+ struct key_menu *km = NULL;
+
+
+ dprint((1, "\n\n ---- INDEX MANAGER ----\n"));
+
+ ch = 'x'; /* For displaying msg 1st time thru */
+ force = 0;
+ km_popped = 0;
+ state->mangled_screen = 1;
+ what_keymenu = FirstMenu;
+ old_max_msgno = mn_get_total(msgmap);
+ memset((void *)&id, 0, sizeof(struct index_state));
+ current_index_state = &id;
+ id.msgmap = msgmap;
+ if(msgmap->top != 0L)
+ id.msg_at_top = msgmap->top;
+
+ id.stream = stream;
+ set_need_format_setup(stream);
+
+ while (1) {
+ ps_global->user_says_cancel = 0;
+
+ if(km_popped){
+ km_popped--;
+ if(km_popped == 0){
+ clearfooter(state);
+ if(!state->mangled_body
+ && id.entry_state
+ && id.lines_per_page > 1){
+ id.entry_state[id.lines_per_page-2].id = 0;
+ id.entry_state[id.lines_per_page-1].id = 0;
+ }
+ else
+ state->mangled_body = 1;
+ }
+ }
+
+ /*------- Check for new mail -------*/
+ new_mail(force, NM_TIMING(ch), NM_STATUS_MSG);
+ force = 0; /* may not need to next time around */
+
+ /*
+ * If the width of the message number field in the display changes
+ * we need to flush the cache and redraw. When the cache is cleared
+ * the widths are recalculated, taking into account the max msgno.
+ */
+
+ if(format_includes_msgno(stream) &&
+ ((old_max_msgno < 1000L && mn_get_total(msgmap) >= 1000L)
+ || (old_max_msgno < 10000L && mn_get_total(msgmap) >= 10000L)
+ || (old_max_msgno < 100000L && mn_get_total(msgmap) >= 100000L))){
+ clear_index_cache(stream, IC_CLEAR_WIDTHS_DONE);
+ state->mangled_body = 1;
+ }
+
+ old_max_msgno = mn_get_total(msgmap);
+
+ /*
+ * If the display includes the SMARTDATE ("Today", "Yesterday", ...)
+ * then when the day changes the date column will change. All of the
+ * Today's will become Yesterday's at midnight. So we have to
+ * clear the cache at midnight.
+ */
+ if(format_includes_smartdate(stream)){
+ char db[200];
+ struct date nnow;
+
+ rfc822_date(db);
+ parse_date(db, &nnow);
+ if(old_day != -1 && nnow.day != old_day){
+ clear_index_cache(stream, 0);
+ state->mangled_body = 1;
+ }
+
+ old_day = nnow.day;
+ }
+
+ if(streams_died())
+ state->mangled_header = 1;
+
+ if(state->mangled_screen){
+ state->mangled_header = 1;
+ state->mangled_body = 1;
+ state->mangled_footer = 1;
+ state->mangled_screen = 0;
+ }
+
+ /*
+ * events may have occured that require us to shift from
+ * mode to another...
+ */
+ style = THRD_INDX()
+ ? ThreadIndex
+ : (any_lflagged(msgmap, MN_HIDE))
+ ? ZoomIndex
+ : (mn_total_cur(msgmap) > 1L) ? MultiMsgIndex : MsgIndex;
+ if(style != old_style){
+ state->mangled_header = 1;
+ state->mangled_footer = 1;
+ old_style = style;
+ if(!(style == ThreadIndex || old_style == ThreadIndex))
+ id.msg_at_top = 0L;
+ }
+
+ /*------------ Update the title bar -----------*/
+ if(state->mangled_header) {
+ km = do_index_border(cntxt, folder, stream, msgmap,
+ style, NULL, INDX_HEADER);
+ state->mangled_header = 0;
+ paint_status = 0;
+ }
+ else if(mn_get_total(msgmap) > 0) {
+ update_titlebar_message();
+ /*
+ * If flags aren't available to update the status,
+ * defer it until after all the fetches associated
+ * with building index lines are done (no extra rtts!)...
+ */
+ paint_status = !update_titlebar_status();
+ }
+
+ current_index_state = &id;
+
+ /*------------ draw the index body ---------------*/
+ cur_row = update_index(state, &id);
+ if(F_OFF(F_SHOW_CURSOR, state)){
+ cur_row = state->ttyo->screen_rows - FOOTER_ROWS(state);
+ cur_col = 0;
+ }
+ else if(id.status_col >= 0)
+ cur_col = MIN(id.status_col, state->ttyo->screen_cols-1);
+
+ ps_global->redrawer = redraw_index_body;
+
+ if(paint_status)
+ (void) update_titlebar_status();
+
+ /*------------ draw the footer/key menus ---------------*/
+ if(state->mangled_footer) {
+ if(!state->painted_footer_on_startup){
+ if(km_popped){
+ FOOTER_ROWS(state) = 3;
+ clearfooter(state);
+ }
+
+ km = do_index_border(cntxt, folder, stream, msgmap, style,
+ &which_keys, INDX_FOOTER);
+ if(km_popped){
+ FOOTER_ROWS(state) = 1;
+ mark_keymenu_dirty();
+ }
+ }
+
+ state->mangled_footer = 0;
+ }
+
+ state->painted_body_on_startup = 0;
+ state->painted_footer_on_startup = 0;
+
+ /*-- Display any queued message (eg, new mail, command result --*/
+ if(km_popped){
+ FOOTER_ROWS(state) = 3;
+ mark_status_unknown();
+ }
+
+ display_message(ch);
+ if(km_popped){
+ FOOTER_ROWS(state) = 1;
+ mark_status_unknown();
+ }
+
+ if(F_ON(F_SHOW_CURSOR, state) && cur_row < 0){
+ cur_row = state->ttyo->screen_rows - FOOTER_ROWS(state);
+ }
+
+ cur_row = MIN(MAX(cur_row, 0), state->ttyo->screen_rows-1);
+ MoveCursor(cur_row, cur_col);
+
+ /* Let read_command do the fflush(stdout) */
+
+ /*---------- Read command and validate it ----------------*/
+#ifdef MOUSE
+ mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
+ register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
+ state->ttyo->screen_rows-(FOOTER_ROWS(ps_global)+1),
+ state->ttyo->screen_cols);
+#endif
+#ifdef _WINDOWS
+ mswin_setscrollcallback (index_scroll_callback);
+ mswin_sethelptextcallback((stream == state->mail_stream)
+ ? pcpine_help_index
+ : pcpine_help_index_simple);
+ mswin_setviewinwindcallback(view_in_new_window);
+#endif
+ ch = READ_COMMAND(&utf8str);
+#ifdef MOUSE
+ clear_mfunc(mouse_in_content);
+#endif
+#ifdef _WINDOWS
+ mswin_setscrollcallback(NULL);
+ mswin_sethelptextcallback(NULL);
+ mswin_setviewinwindcallback(NULL);
+#endif
+
+ cmd = menu_command(ch, km);
+
+ if(km_popped)
+ switch(cmd){
+ case MC_NONE :
+ case MC_OTHER :
+ case MC_RESIZE :
+ case MC_REPAINT :
+ km_popped++;
+ break;
+
+ default:
+ clearfooter(state);
+ break;
+ }
+
+ /*----------- Execute the command ------------------*/
+ switch(cmd){
+
+ /*---------- Roll keymenu ----------*/
+ case MC_OTHER :
+ if(F_OFF(F_USE_FK, ps_global))
+ warn_other_cmds();
+
+ what_keymenu = NextMenu;
+ state->mangled_footer = 1;
+ break;
+
+
+ /*---------- Scroll line up ----------*/
+ case MC_CHARUP :
+ (void) process_cmd(state, stream, msgmap, MC_PREVITEM,
+ (style == MsgIndex
+ || style == MultiMsgIndex
+ || style == ZoomIndex)
+ ? MsgIndx
+ : (style == ThreadIndex)
+ ? ThrdIndx
+ : View,
+ &force);
+ if(mn_get_cur(msgmap) < (id.msg_at_top + HS_MARGIN(state)))
+ index_scroll_up(1L);
+
+ break;
+
+
+ /*---------- Scroll line down ----------*/
+ case MC_CHARDOWN :
+ /*
+ * Special Page framing handling here. If we
+ * did something that should scroll-by-a-line, frame
+ * the page by hand here rather than leave it to the
+ * page-by-page framing in update_index()...
+ */
+ (void) process_cmd(state, stream, msgmap, MC_NEXTITEM,
+ (style == MsgIndex
+ || style == MultiMsgIndex
+ || style == ZoomIndex)
+ ? MsgIndx
+ : (style == ThreadIndex)
+ ? ThrdIndx
+ : View,
+ &force);
+ for(j = 0L, k = i = id.msg_at_top; ; i++){
+ if(!msgline_hidden(stream, msgmap, i, 0)){
+ k = i;
+ if(j++ >= id.lines_per_page)
+ break;
+ }
+
+ if(i >= mn_get_total(msgmap)){
+ k = 0L; /* don't scroll */
+ break;
+ }
+ }
+
+ if(k && (mn_get_cur(msgmap) + HS_MARGIN(state)) >= k)
+ index_scroll_down(1L);
+
+ break;
+
+
+ /*---------- Scroll page up ----------*/
+ case MC_PAGEUP :
+ j = -1L;
+ for(k = i = id.msg_at_top; ; i--){
+ if(!msgline_hidden(stream, msgmap, i, 0)){
+ k = i;
+ if(++j >= id.lines_per_page){
+ if((id.msg_at_top = i) == 1L)
+ q_status_message(SM_ORDER, 0, 1, _("First Index page"));
+
+ break;
+ }
+ }
+
+ if(i <= 1L){
+ if((!THREADING() && mn_get_cur(msgmap) == 1L)
+ || (THREADING()
+ && mn_get_cur(msgmap) == first_sorted_flagged(F_NONE,
+ stream,
+ 0L,
+ FSF_SKIP_CHID)))
+ q_status_message(SM_ORDER, 0, 1,
+ _("Already at start of Index"));
+
+ break;
+ }
+ }
+
+ if(mn_get_total(msgmap) > 0L && mn_total_cur(msgmap) == 1L)
+ mn_set_cur(msgmap, k);
+
+ break;
+
+
+ /*---------- Scroll page forward ----------*/
+ case MC_PAGEDN :
+ j = -1L;
+ for(k = i = id.msg_at_top; ; i++){
+ if(!msgline_hidden(stream, msgmap, i, 0)){
+ k = i;
+ if(++j >= id.lines_per_page){
+ if(i+id.lines_per_page > mn_get_total(msgmap))
+ q_status_message(SM_ORDER, 0, 1, _("Last Index page"));
+
+ id.msg_at_top = i;
+ break;
+ }
+ }
+
+ if(i >= mn_get_total(msgmap)){
+ if(mn_get_cur(msgmap) == k)
+ q_status_message(SM_ORDER,0,1,_("Already at end of Index"));
+
+ break;
+ }
+ }
+
+ if(mn_get_total(msgmap) > 0L && mn_total_cur(msgmap) == 1L)
+ mn_set_cur(msgmap, k);
+
+ break;
+
+
+ /*---------- Scroll to first page ----------*/
+ case MC_HOMEKEY :
+ if((mn_get_total(msgmap) > 0L)
+ && (mn_total_cur(msgmap) <= 1L)){
+ long cur_msg = mn_get_cur(msgmap), selected;
+
+ if(any_lflagged(msgmap, MN_HIDE | MN_CHID)){
+ do {
+ selected = cur_msg;
+ mn_dec_cur(stream, msgmap, MH_NONE);
+ cur_msg = mn_get_cur(msgmap);
+ }
+ while(selected != cur_msg);
+ }
+ else
+ cur_msg = (mn_get_total(msgmap) > 0L) ? 1L : 0L;
+ mn_set_cur(msgmap, cur_msg);
+ q_status_message(SM_ORDER, 0, 3, _("First Index Page"));
+ }
+ break;
+
+ /*---------- Scroll to last page ----------*/
+ case MC_ENDKEY :
+ if((mn_get_total(msgmap) > 0L)
+ && (mn_total_cur(msgmap) <= 1L)){
+ long cur_msg = mn_get_cur(msgmap), selected;
+
+ if(any_lflagged(msgmap, MN_HIDE | MN_CHID)){
+ do {
+ selected = cur_msg;
+ mn_inc_cur(stream, msgmap, MH_NONE);
+ cur_msg = mn_get_cur(msgmap);
+ }
+ while(selected != cur_msg);
+ }
+ else
+ cur_msg = mn_get_total(msgmap);
+ mn_set_cur(msgmap, cur_msg);
+ q_status_message(SM_ORDER, 0, 3, _("Last Index Page"));
+ }
+ break;
+
+ /*---------- Search (where is command) ----------*/
+ case MC_WHEREIS :
+ index_search(state, stream, -FOOTER_ROWS(ps_global), msgmap);
+ state->mangled_footer = 1;
+ break;
+
+
+ /*-------------- jump command -------------*/
+ /* NOTE: preempt the process_cmd() version because
+ * we need to get at the number..
+ */
+ case MC_JUMP :
+ j = jump_to(msgmap, -FOOTER_ROWS(ps_global), ch, NULL,
+ (style == ThreadIndex) ? ThrdIndx : MsgIndx);
+ if(j > 0L){
+ if(style == ThreadIndex){
+ PINETHRD_S *thrd;
+
+ thrd = find_thread_by_number(stream, msgmap, j, NULL);
+
+ if(thrd && thrd->rawno)
+ mn_set_cur(msgmap, mn_raw2m(msgmap, thrd->rawno));
+ }
+ else{
+ /* jump to message */
+ if(mn_total_cur(msgmap) > 1L){
+ mn_reset_cur(msgmap, j);
+ }
+ else{
+ mn_set_cur(msgmap, j);
+ }
+ }
+
+ id.msg_at_top = 0L;
+ }
+
+ state->mangled_footer = 1;
+ break;
+
+
+ case MC_VIEW_ENTRY : /* only happens in thread index */
+
+ /*
+ * If the feature F_THRD_AUTO_VIEW is turned on and there
+ * is only one message in the thread, then we skip the index
+ * view of the thread and go straight to the message view.
+ */
+view_a_thread:
+ if(THRD_AUTO_VIEW() && style == ThreadIndex){
+ PINETHRD_S *thrd;
+
+ thrd = fetch_thread(stream,
+ mn_m2raw(msgmap, mn_get_cur(msgmap)));
+ if(thrd
+ && (count_lflags_in_thread(stream, thrd,
+ msgmap, MN_NONE) == 1)){
+ if(view_thread(state, stream, msgmap, 1)){
+ state->view_skipped_index = 1;
+ cmd = MC_VIEW_TEXT;
+ goto do_the_default;
+ }
+ }
+ }
+
+ if(view_thread(state, stream, msgmap, 1)){
+ ps_global->next_screen = mail_index_screen;
+ ps_global->redrawer = NULL;
+ current_index_state = NULL;
+ if(id.entry_state)
+ fs_give((void **)&(id.entry_state));
+
+ return(0);
+ }
+
+ break;
+
+
+ case MC_THRDINDX :
+ msgmap->top = msgmap->top_after_thrd;
+ if(unview_thread(state, stream, msgmap)){
+ state->next_screen = mail_index_screen;
+ state->view_skipped_index = 0;
+ state->mangled_screen = 1;
+ ps_global->redrawer = NULL;
+ current_index_state = NULL;
+ if(id.entry_state)
+ fs_give((void **)&(id.entry_state));
+
+ return(0);
+ }
+
+ break;
+
+
+#ifdef MOUSE
+ case MC_MOUSE:
+ {
+ MOUSEPRESS mp;
+ int new_cur;
+
+ mouse_get_last (NULL, &mp);
+ mp.row -= 2;
+
+ for(i = id.msg_at_top;
+ mp.row >= 0 && i <= mn_get_total(msgmap);
+ i++)
+ if(!msgline_hidden(stream, msgmap, i, 0)){
+ mp.row--;
+ new_cur = i;
+ }
+
+ if(mn_get_total(msgmap) && mp.row < 0){
+ switch(mp.button){
+ case M_BUTTON_LEFT :
+ if(mn_total_cur(msgmap) == 1L)
+ mn_set_cur(msgmap, new_cur);
+
+ if(mp.flags & M_KEY_CONTROL){
+ if(F_ON(F_ENABLE_AGG_OPS, ps_global)){
+ (void) select_by_current(state, msgmap, MsgIndx);
+ }
+ }
+ else if(!(mp.flags & M_KEY_SHIFT)){
+ if (THREADING()
+ && mp.col >= 0
+ && mp.col == id.plus_col
+ && style != ThreadIndex){
+ collapse_or_expand(state, stream, msgmap,
+ mn_get_cur(msgmap));
+ }
+ else if (mp.doubleclick){
+ if(mp.button == M_BUTTON_LEFT){
+ if(stream == state->mail_stream){
+ if(THRD_INDX()){
+ cmd = MC_VIEW_ENTRY;
+ goto view_a_thread;
+ }
+ else{
+ cmd = MC_VIEW_TEXT;
+ goto do_the_default;
+ }
+ }
+
+ ps_global->redrawer = NULL;
+ current_index_state = NULL;
+ if(id.entry_state)
+ fs_give((void **)&(id.entry_state));
+
+ return(0);
+ }
+ }
+ }
+
+ break;
+
+ case M_BUTTON_MIDDLE:
+ break;
+
+ case M_BUTTON_RIGHT :
+#ifdef _WINDOWS
+ if (!mp.doubleclick){
+ if(mn_total_cur(msgmap) == 1L)
+ mn_set_cur(msgmap, new_cur);
+
+ cur_row = update_index(state, &id);
+
+ index_popup(style, stream, msgmap, TRUE);
+ }
+#endif
+ break;
+ }
+ }
+ else{
+ switch(mp.button){
+ case M_BUTTON_LEFT :
+ break;
+
+ case M_BUTTON_MIDDLE :
+ break;
+
+ case M_BUTTON_RIGHT :
+#ifdef _WINDOWS
+ index_popup(style, stream, msgmap, FALSE);
+#endif
+ break;
+ }
+ }
+ }
+
+ break;
+#endif /* MOUSE */
+
+ /*---------- Resize ----------*/
+ case MC_RESIZE:
+ /*
+ * If we were smarter we could do the
+ * IC_CLEAR_WIDTHS_DONE trick here. The problem is
+ * that entire columns of the format can go away or
+ * appear because the width gets smaller or larger,
+ * so in that case we need to re-do. If we could tell
+ * when that happened or not we could set the flag
+ * selectively.
+ */
+ clear_index_cache(stream, 0);
+ reset_index_border();
+ break;
+
+ case MC_QUOTA:
+ cmd_quota(state);
+
+ /*---------- Redraw ----------*/
+ case MC_REPAINT :
+ force = 1; /* check for new mail! */
+ reset_index_border();
+ break;
+
+
+ /*---------- No op command ----------*/
+ case MC_NONE :
+ break; /* no op check for new mail */
+
+
+ /*--------- keystroke not bound to command --------*/
+ case MC_CHARRIGHT :
+ case MC_CHARLEFT :
+ case MC_GOTOBOL :
+ case MC_GOTOEOL :
+ case MC_UNKNOWN :
+ if(cmd == MC_UNKNOWN && (ch == 'i' || ch == 'I'))
+ q_status_message(SM_ORDER, 0, 1, "Already in Index");
+ else
+ bogus_command(ch, F_ON(F_USE_FK,state) ? "F1" : "?");
+
+ break;
+
+
+ case MC_COLLAPSE :
+ thread_command(state, stream, msgmap, ch, -FOOTER_ROWS(state));
+ break;
+
+ case MC_DELETE :
+ case MC_UNDELETE :
+ case MC_REPLY :
+ case MC_FORWARD :
+ case MC_TAKE :
+ case MC_SAVE :
+ case MC_EXPORT :
+ case MC_BOUNCE :
+ case MC_PIPE :
+ case MC_FLAG :
+ case MC_SELCUR :
+ { int collapsed = 0;
+ unsigned long rawno;
+ PINETHRD_S *thrd = NULL;
+
+ if(THREADING()){
+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+
+ collapsed = thrd && thrd->next
+ && get_lflag(stream, NULL, rawno, MN_COLL);
+ }
+
+ if(collapsed){
+ thread_command(state, stream, msgmap,
+ ch, -FOOTER_ROWS(state));
+ /* increment current */
+ if(cmd == MC_DELETE){
+ advance_cur_after_delete(state, stream, msgmap,
+ (style == MsgIndex
+ || style == MultiMsgIndex
+ || style == ZoomIndex)
+ ? MsgIndx
+ : (style == ThreadIndex)
+ ? ThrdIndx
+ : View);
+ }
+ else if((cmd == MC_SELCUR
+ && (state->ugly_consider_advancing_bit
+ || F_OFF(F_UNSELECT_WONT_ADVANCE, state)))
+ || (state->ugly_consider_advancing_bit
+ && cmd == MC_SAVE
+ && F_ON(F_SAVE_ADVANCES, state))){
+ mn_inc_cur(stream, msgmap, MH_NONE);
+ }
+ }
+ else
+ goto do_the_default;
+ }
+
+ break;
+
+
+ case MC_UTF8:
+ bogus_utf8_command(utf8str, NULL);
+ break;
+
+
+ /*---------- First HELP command with menu hidden ----------*/
+ case MC_HELP :
+ if(FOOTER_ROWS(state) == 1 && km_popped == 0){
+ km_popped = 2;
+ mark_status_unknown();
+ mark_keymenu_dirty();
+ state->mangled_footer = 1;
+ break;
+ }
+ /* else fall thru to normal default */
+
+
+ /*---------- Default -- all other command ----------*/
+ default:
+ do_the_default:
+ if(stream == state->mail_stream){
+ msgmap->top = id.msg_at_top;
+ process_cmd(state, stream, msgmap, cmd,
+ (style == MsgIndex
+ || style == MultiMsgIndex
+ || style == ZoomIndex)
+ ? MsgIndx
+ : (style == ThreadIndex)
+ ? ThrdIndx
+ : View,
+ &force);
+ if(state->next_screen != SCREEN_FUN_NULL){
+ ps_global->redrawer = NULL;
+ current_index_state = NULL;
+ if(id.entry_state)
+ fs_give((void **)&(id.entry_state));
+
+ return(0);
+ }
+ else{
+ if(stream != state->mail_stream){
+ /*
+ * Must have had an failed open. repair our
+ * pointers...
+ */
+ id.stream = stream = state->mail_stream;
+ id.msgmap = msgmap = state->msgmap;
+ }
+
+ current_index_state = &id;
+
+ if(cmd == MC_ZOOM && THRD_INDX())
+ id.msg_at_top = 0L;
+ }
+ }
+ else{ /* special processing */
+ switch(cmd){
+ case MC_HELP :
+ helper(h_simple_index,
+ (!strcmp(folder, INTERRUPTED_MAIL))
+ ? _("HELP FOR SELECTING INTERRUPTED MSG")
+ : _("HELP FOR SELECTING POSTPONED MSG"),
+ HLPD_SIMPLE);
+ state->mangled_screen = 1;
+ break;
+
+ case MC_DELETE : /* delete */
+ dprint((3, "Special delete: msg %s\n",
+ long2string(mn_get_cur(msgmap))));
+ {
+ long raw, t;
+ int del = 0;
+ MESSAGECACHE *mc;
+
+ raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ if(raw > 0L && stream
+ && raw <= stream->nmsgs
+ && (mc = mail_elt(stream, raw))
+ && !mc->deleted){
+ if((t = mn_get_cur(msgmap)) > 0L)
+ clear_index_cache_ent(stream, t, 0);
+
+ mail_setflag(stream,long2string(raw),"\\DELETED");
+ update_titlebar_status();
+ del++;
+ }
+
+ q_status_message1(SM_ORDER, 0, 1,
+ del ? _("Message %s deleted") : _("Message %s already deleted"),
+ long2string(mn_get_cur(msgmap)));
+ }
+
+ break;
+
+ case MC_UNDELETE : /* UNdelete */
+ dprint((3, "Special UNdelete: msg %s\n",
+ long2string(mn_get_cur(msgmap))));
+ {
+ long raw, t;
+ int del = 0;
+ MESSAGECACHE *mc;
+
+ raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ if(raw > 0L && stream
+ && raw <= stream->nmsgs
+ && (mc = mail_elt(stream, raw))
+ && mc->deleted){
+ if((t = mn_get_cur(msgmap)) > 0L)
+ clear_index_cache_ent(stream, t, 0);
+
+ mail_clearflag(stream, long2string(raw),
+ "\\DELETED");
+ update_titlebar_status();
+ del++;
+ }
+
+ q_status_message1(SM_ORDER, 0, 1,
+ del ? _("Message %s UNdeleted") : _("Message %s NOT deleted"),
+ long2string(mn_get_cur(msgmap)));
+ }
+
+ break;
+
+ case MC_EXIT : /* exit */
+ ps_global->redrawer = NULL;
+ current_index_state = NULL;
+ if(id.entry_state)
+ fs_give((void **)&(id.entry_state));
+
+ return(1);
+
+ case MC_SELECT : /* select */
+ ps_global->redrawer = NULL;
+ current_index_state = NULL;
+ if(id.entry_state)
+ fs_give((void **)&(id.entry_state));
+
+ return(0);
+
+ case MC_PREVITEM : /* previous */
+ mn_dec_cur(stream, msgmap, MH_NONE);
+ break;
+
+ case MC_NEXTITEM : /* next */
+ mn_inc_cur(stream, msgmap, MH_NONE);
+ break;
+
+ default :
+ bogus_command(ch, NULL);
+ break;
+ }
+ }
+ } /* The big switch */
+ } /* the BIG while loop! */
+}
+
+
+
+/*----------------------------------------------------------------------
+ Manage index body painting
+
+ Args: state - pine struct containing selected message data
+ index_state - struct describing what's currently displayed
+
+ Returns: screen row number of first highlighted message
+
+ The idea is pretty simple. Maintain an array of index line id's that
+ are displayed and their hilited state. Decide what's to be displayed
+ and update the screen appropriately. All index screen painting
+ is done here. Pretty simple, huh?
+ ----*/
+int
+update_index(struct pine *state, struct index_state *screen)
+{
+ int i, retval = -1, row, already_fetched = 0;
+ long n, visible;
+ PINETHRD_S *thrd = NULL;
+ int we_cancel = 0;
+
+ dprint((7, "--update_index--\n"));
+
+ if(!screen)
+ return(-1);
+
+#ifdef _WINDOWS
+ mswin_beginupdate();
+#endif
+
+ /*---- reset the works if necessary ----*/
+ if(state->mangled_body){
+ ClearBody();
+ if(screen->entry_state){
+ fs_give((void **)&(screen->entry_state));
+ screen->lines_per_page = 0;
+ }
+ }
+
+ state->mangled_body = 0;
+
+ /*---- make sure we have a place to write state ----*/
+ if(screen->lines_per_page
+ != MAX(0, state->ttyo->screen_rows - FOOTER_ROWS(state)
+ - HEADER_ROWS(state))){
+ i = screen->lines_per_page;
+ screen->lines_per_page
+ = MAX(0, state->ttyo->screen_rows - FOOTER_ROWS(state)
+ - HEADER_ROWS(state));
+ if(!i){
+ size_t len = screen->lines_per_page * sizeof(struct entry_state);
+ screen->entry_state = (struct entry_state *) fs_get(len);
+ }
+ else
+ fs_resize((void **)&(screen->entry_state),
+ (size_t)screen->lines_per_page);
+
+ for(; i < screen->lines_per_page; i++) /* init new entries */
+ memset(&screen->entry_state[i], 0, sizeof(struct entry_state));
+ }
+
+ /*---- figure out the first message on the display ----*/
+ if(screen->msg_at_top < 1L
+ || msgline_hidden(screen->stream, screen->msgmap, screen->msg_at_top,0)){
+ screen->msg_at_top = top_ent_calc(screen->stream, screen->msgmap,
+ screen->msg_at_top,
+ screen->lines_per_page);
+ }
+ else if(mn_get_cur(screen->msgmap) < screen->msg_at_top){
+ long i, j, k;
+
+ /* scroll back a page at a time until current is displayed */
+ while(mn_get_cur(screen->msgmap) < screen->msg_at_top){
+ for(i = screen->lines_per_page, j = screen->msg_at_top-1L, k = 0L;
+ i > 0L && j > 0L;
+ j--)
+ if(!msgline_hidden(screen->stream, screen->msgmap, j, 0)){
+ k = j;
+ i--;
+ }
+
+ if(i == screen->lines_per_page)
+ break; /* can't scroll back ? */
+ else
+ screen->msg_at_top = k;
+ }
+ }
+ else if(mn_get_cur(screen->msgmap) >= screen->msg_at_top
+ + screen->lines_per_page){
+ long i, j, k;
+
+ while(1){
+ for(i = screen->lines_per_page, j = k = screen->msg_at_top;
+ j <= mn_get_total(screen->msgmap) && i > 0L;
+ j++)
+ if(!msgline_hidden(screen->stream, screen->msgmap, j, 0)){
+ k = j;
+ i--;
+ if(mn_get_cur(screen->msgmap) <= k)
+ break;
+ }
+
+ if(mn_get_cur(screen->msgmap) <= k)
+ break;
+ else{
+ /* set msg_at_top to next displayed message */
+ for(i = k + 1L; i <= mn_get_total(screen->msgmap); i++)
+ if(!msgline_hidden(screen->stream, screen->msgmap, i, 0)){
+ k = i;
+ break;
+ }
+
+ screen->msg_at_top = k;
+ }
+ }
+ }
+
+#ifdef _WINDOWS
+ /*
+ * Set scroll range and position. Note that message numbers start at 1
+ * while scroll position starts at 0.
+ */
+
+ if(THREADING() && sp_viewing_a_thread(screen->stream)
+ && mn_get_total(screen->msgmap) > 1L){
+ long x = 0L, range = 0L, lowest_numbered_msg;
+
+ /*
+ * We know that all visible messages in the thread are marked
+ * with MN_CHID2.
+ */
+ thrd = fetch_thread(screen->stream,
+ mn_m2raw(screen->msgmap,
+ mn_get_cur(screen->msgmap)));
+ if(thrd && thrd->top && thrd->top != thrd->rawno)
+ thrd = fetch_thread(screen->stream, thrd->top);
+
+ if(thrd){
+ if(mn_get_revsort(screen->msgmap)){
+ n = mn_raw2m(screen->msgmap, thrd->rawno);
+ while(n > 1L && get_lflag(screen->stream, screen->msgmap,
+ n-1L, MN_CHID2))
+ n--;
+
+ lowest_numbered_msg = n;
+ }
+ else
+ lowest_numbered_msg = mn_raw2m(screen->msgmap, thrd->rawno);
+ }
+
+ if(thrd){
+ n = lowest_numbered_msg;
+ for(; n <= mn_get_total(screen->msgmap); n++){
+
+ if(!get_lflag(screen->stream, screen->msgmap, n, MN_CHID2))
+ break;
+
+ if(!msgline_hidden(screen->stream, screen->msgmap, n, 0)){
+ range++;
+ if(n < screen->msg_at_top)
+ x++;
+ }
+ }
+ }
+
+ scroll_setrange(screen->lines_per_page, range-1L);
+ scroll_setpos(x);
+ }
+ else if(THRD_INDX()){
+ if(any_lflagged(screen->msgmap, MN_HIDE)){
+ long x = 0L, range;
+
+ range = screen->msgmap->visible_threads - 1L;
+ scroll_setrange(screen->lines_per_page, range);
+ if(range >= screen->lines_per_page){ /* else not needed */
+ PINETHRD_S *topthrd;
+ int thrddir;
+ long xdir;
+
+ /* find top of currently displayed top line */
+ topthrd = fetch_thread(screen->stream,
+ mn_m2raw(screen->msgmap,
+ screen->msg_at_top));
+ if(topthrd && topthrd->top != topthrd->rawno)
+ topthrd = fetch_thread(screen->stream, topthrd->top);
+
+ if(topthrd){
+ /*
+ * Split into two halves to speed up finding scroll pos.
+ * It's tricky because the thread list always goes from
+ * past to future but the thrdno's will be reversed if
+ * the sort is reversed and of course the order on the
+ * screen will be reversed.
+ */
+ if((!mn_get_revsort(screen->msgmap)
+ && topthrd->thrdno <= screen->msgmap->max_thrdno/2)
+ ||
+ (mn_get_revsort(screen->msgmap)
+ && topthrd->thrdno > screen->msgmap->max_thrdno/2)){
+
+ /* start with head of thread list */
+ if(topthrd && topthrd->head)
+ thrd = fetch_thread(screen->stream, topthrd->head);
+ else
+ thrd = NULL;
+
+ thrddir = 1;
+ }
+ else{
+ long tailrawno;
+
+ /*
+ * Start with tail thread and work back.
+ */
+ if(mn_get_revsort(screen->msgmap))
+ tailrawno = mn_m2raw(screen->msgmap, 1L);
+ else
+ tailrawno = mn_m2raw(screen->msgmap,
+ mn_get_total(screen->msgmap));
+
+ thrd = fetch_thread(screen->stream, tailrawno);
+ if(thrd && thrd->top && thrd->top != thrd->rawno)
+ thrd = fetch_thread(screen->stream, thrd->top);
+
+ thrddir = -1;
+ }
+
+ /*
+ * x is the scroll position. We try to use the fewest
+ * number of steps to find it, so we start with either
+ * the beginning or the end.
+ */
+ if(topthrd->thrdno <= screen->msgmap->max_thrdno/2){
+ x = 0L;
+ xdir = 1L;
+ }
+ else{
+ x = range;
+ xdir = -1L;
+ }
+
+ while(thrd && thrd != topthrd){
+ if(!msgline_hidden(screen->stream, screen->msgmap,
+ mn_raw2m(screen->msgmap,thrd->rawno),
+ 0))
+ x += xdir;
+
+ if(thrddir > 0 && thrd->nextthd)
+ thrd = fetch_thread(screen->stream, thrd->nextthd);
+ else if(thrddir < 0 && thrd->prevthd)
+ thrd = fetch_thread(screen->stream, thrd->prevthd);
+ else
+ thrd = NULL;
+ }
+ }
+
+ scroll_setpos(x);
+ }
+ }
+ else{
+ /*
+ * This works for forward or reverse sort because the thrdno's
+ * will have been reversed.
+ */
+ thrd = fetch_thread(screen->stream,
+ mn_m2raw(screen->msgmap, screen->msg_at_top));
+ if(thrd){
+ scroll_setrange(screen->lines_per_page,
+ screen->msgmap->max_thrdno - 1L);
+ scroll_setpos(thrd->thrdno - 1L);
+ }
+ }
+ }
+ else if(n = any_lflagged(screen->msgmap, MN_HIDE | MN_CHID)){
+ long x, range;
+
+ range = mn_get_total(screen->msgmap) - n - 1L;
+ scroll_setrange(screen->lines_per_page, range);
+
+ if(range >= screen->lines_per_page){ /* else not needed */
+ if(screen->msg_at_top < mn_get_total(screen->msgmap) / 2){
+ for(n = 1, x = 0; n != screen->msg_at_top; n++)
+ if(!msgline_hidden(screen->stream, screen->msgmap, n, 0))
+ x++;
+ }
+ else{
+ for(n = mn_get_total(screen->msgmap), x = range;
+ n != screen->msg_at_top; n--)
+ if(!msgline_hidden(screen->stream, screen->msgmap, n, 0))
+ x--;
+ }
+
+ scroll_setpos(x);
+ }
+ }
+ else{
+ scroll_setrange(screen->lines_per_page,
+ mn_get_total(screen->msgmap) - 1L);
+ scroll_setpos(screen->msg_at_top - 1L);
+ }
+#endif
+
+ /*
+ * Set up c-client call back to tell us about IMAP envelope arrivals
+ * Can't do it (easily) if single lines on the screen need information
+ * about more than a single message before they can be drawn.
+ */
+ if(F_OFF(F_QUELL_IMAP_ENV_CB, ps_global) && !THRD_INDX()
+ && !(THREADING() && (sp_viewing_a_thread(screen->stream)
+ || ps_global->thread_disp_style == THREAD_MUTTLIKE
+ || any_lflagged(screen->msgmap, MN_COLL))))
+ mail_parameters(NULL, SET_IMAPENVELOPE, (void *) pine_imap_envelope);
+
+ if(THRD_INDX())
+ visible = screen->msgmap->visible_threads;
+ else if(THREADING() && sp_viewing_a_thread(screen->stream)){
+ /*
+ * We know that all visible messages in the thread are marked
+ * with MN_CHID2.
+ */
+ for(visible = 0L, n = screen->msg_at_top;
+ visible < (int) screen->lines_per_page
+ && n <= mn_get_total(screen->msgmap); n++){
+
+ if(!get_lflag(screen->stream, screen->msgmap, n, MN_CHID2))
+ break;
+
+ if(!msgline_hidden(screen->stream, screen->msgmap, n, 0))
+ visible++;
+ }
+ }
+ else
+ visible = mn_get_total(screen->msgmap)
+ - any_lflagged(screen->msgmap, MN_HIDE|MN_CHID);
+
+ /*---- march thru display lines, painting whatever is needed ----*/
+ for(i = 0, n = screen->msg_at_top; i < (int) screen->lines_per_page; i++){
+ if(visible == 0L || n < 1 || n > mn_get_total(screen->msgmap)){
+ if(screen->entry_state[i].id != LINE_HASH_N){
+ screen->entry_state[i].hilite = 0;
+ screen->entry_state[i].bolded = 0;
+ screen->entry_state[i].msgno = 0L;
+ screen->entry_state[i].id = LINE_HASH_N;
+ ClearLine(HEADER_ROWS(state) + i);
+ }
+ }
+ else{
+ ICE_S *ice;
+
+ /*
+ * This changes status_col as a side effect so it has to be
+ * executed before next line.
+ */
+ ice = build_header_line(state, screen->stream, screen->msgmap,
+ n, &already_fetched);
+ if(visible > 0L)
+ visible--;
+
+ if(THRD_INDX()){
+ unsigned long rawno;
+
+ rawno = mn_m2raw(screen->msgmap, n);
+ if(rawno)
+ thrd = fetch_thread(screen->stream, rawno);
+ }
+
+ row = paint_index_line(ice, i,
+ (THRD_INDX() && thrd) ? thrd->thrdno : n,
+ screen->status_fld, screen->plus_fld,
+ screen->arrow_fld, &screen->entry_state[i],
+ mn_is_cur(screen->msgmap, n),
+ THRD_INDX()
+ ? (count_lflags_in_thread(screen->stream,
+ thrd,
+ screen->msgmap,
+ MN_SLCT) > 0)
+ : get_lflag(screen->stream, screen->msgmap,
+ n, MN_SLCT));
+ fflush(stdout);
+ if(row && retval < 0)
+ retval = row;
+ }
+
+ /*--- increment n ---*/
+ while((visible == -1L || visible > 0L)
+ && ++n <= mn_get_total(screen->msgmap)
+ && msgline_hidden(screen->stream, screen->msgmap, n, 0))
+ ;
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ mail_parameters(NULL, SET_IMAPENVELOPE, (void *) NULL);
+
+#ifdef _WINDOWS
+ mswin_endupdate();
+#endif
+ fflush(stdout);
+ dprint((7, "--update_index done\n"));
+ return(retval);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Create a string summarizing the message header for index on screen
+
+ Args: stream -- mail stream to fetch envelope info from
+ msgmap -- message number to pine sort mapping
+ msgno -- Message number to create line for
+
+ Result: returns a malloced string
+ saves string in a cache for next call for same header
+ ----*/
+ICE_S *
+build_header_line(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, int *already_fetched)
+{
+ return(build_header_work(state, stream, msgmap, msgno,
+ current_index_state->msg_at_top,
+ current_index_state->lines_per_page,
+ already_fetched));
+}
+
+
+/*----------------------------------------------------------------------
+ Paint the given index line
+
+
+ Args: ice -- index cache entry
+ line -- index line number on screen, starting at 0 for first
+ visible line, 1, 2, ...
+ msgno -- for painting the message number field
+ sfld -- field type of the status field, which is
+ where we'll put the X for selected if necessary
+ pfld -- field type where the thread indicator symbol goes
+ afld -- field type of column which corresponds to the
+ index-format ARROW token
+ entry -- cache used to help us decide whether or not we need to
+ redraw the index line or if we can just leave it alone because
+ we know it is already correct
+ cur -- is this the current message?
+ sel -- is this message in the selected set?
+
+ Returns: screen row number if this is current message, else 0
+ ----*/
+int
+paint_index_line(ICE_S *argice, int line, long int msgno, IndexColType sfld,
+ IndexColType pfld, IndexColType afld, struct entry_state *entry,
+ int cur, int sel)
+{
+ COLOR_PAIR *lastc = NULL, *base_color = NULL;
+ ICE_S *ice;
+ IFIELD_S *ifield, *previfield = NULL;
+ IELEM_S *ielem;
+ int save_schar1 = -1, save_schar2 = -1, save_pchar = -1, i;
+ int draw_whole_line = 0, draw_partial_line = 0;
+ int n = MAX_SCREEN_COLS*6;
+ char draw[MAX_SCREEN_COLS*6+1], *p;
+
+ ice = (THRD_INDX() && argice) ? argice->tice : argice;
+
+ /* This better not happen! */
+ if(!ice){
+ q_status_message3(SM_ORDER | SM_DING, 5, 5,
+ "NULL ice in paint_index_line: %s, msgno=%s line=%s",
+ THRD_INDX() ? "THRD_INDX" : "reg index",
+ comatose(msgno), comatose(line));
+ dprint((1, "NULL ice in paint_index_line: %s, msgno=%ld line=%d\n",
+ THRD_INDX() ? "THRD_INDX" : "reg index",
+ msgno, line));
+ return 0;
+ }
+
+ if(entry->msgno != msgno || ice->id == 0 || entry->id != ice->id){
+ entry->id = 0L;
+ entry->msgno = 0L;
+ draw_whole_line = 1;
+ }
+ else if((cur != entry->hilite) || (sel != entry->bolded)
+ || (ice->plus != entry->plus)){
+ draw_partial_line = 1;
+ }
+
+ if(draw_whole_line || draw_partial_line){
+
+ if(F_ON(F_FORCE_LOW_SPEED,ps_global) || ps_global->low_speed){
+
+ memset(draw, 0, sizeof(draw));
+ p = draw;
+
+ for(ifield = ice->ifield; ifield && p-draw < n; ifield = ifield->next){
+
+ /* space between fields */
+ if(ifield != ice->ifield && !(previfield && previfield->ctype == iText))
+ *p++ = ' ';
+
+ /* message number string is generated on the fly */
+ if(ifield->ctype == iMessNo){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen >= ifield->width){
+ snprintf(ielem->data, ielem->datalen+1, "%*.ld", ifield->width, msgno);
+ ielem->data[ifield->width] = '\0';
+ ielem->data[ielem->datalen] = '\0';
+ }
+ }
+
+ if(ifield->ctype == sfld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen > 0){
+ if(draw_partial_line)
+ MoveCursor(HEADER_ROWS(ps_global) + line, utf8_width(draw));
+
+ if(ielem->datalen == 1){
+ save_schar1 = ielem->data[0];
+ ielem->data[0] = (sel) ? 'X' : (cur && save_schar1 == ' ') ? '-' : save_schar1;
+ if(draw_partial_line)
+ Writechar(ielem->data[0], 0);
+
+ if(ielem->next && ielem->next->datalen){
+ save_schar2 = ielem->next->data[0];
+ if(cur)
+ ielem->next->data[0] = '>';
+
+ if(draw_partial_line)
+ Writechar(ielem->next->data[0], 0);
+ }
+ }
+ else if(ielem->datalen > 1){
+ save_schar1 = ielem->data[0];
+ ielem->data[0] = (sel) ? 'X' : (cur && save_schar1 == ' ') ? '-' : save_schar1;
+ if(draw_partial_line)
+ Writechar(ielem->data[0], 0);
+
+ save_schar2 = ielem->data[1];
+ if(cur){
+ ielem->data[1] = '>';
+ if(draw_partial_line)
+ Writechar(ielem->data[1], 0);
+ }
+ }
+ }
+ }
+ else if(ifield->ctype == afld){
+
+ if(draw_partial_line){
+ MoveCursor(HEADER_ROWS(ps_global) + line, utf8_width(draw));
+ for(i = 0; i < ifield->width-1; i++)
+ Writechar(cur ? '-' : ' ', 0);
+
+ Writechar(cur ? '>' : ' ', 0);
+ }
+
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen >= ifield->width){
+ for(i = 0; i < ifield->width-1; i++)
+ ielem->data[i] = cur ? '-' : ' ';
+
+ ielem->data[i] = cur ? '>' : ' ';
+ }
+ }
+ else if(ifield->ctype == pfld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen > 0){
+ save_pchar = ielem->data[0];
+ ielem->data[0] = ice->plus;
+
+ if(draw_partial_line){
+ MoveCursor(HEADER_ROWS(ps_global) + line, utf8_width(draw));
+ Writechar(ielem->data[0], 0);
+ Writechar(' ', 0);
+ }
+ }
+ }
+
+ for(ielem = ifield->ielem;
+ ielem && ielem->print_format && p-draw < n;
+ ielem = ielem->next){
+ char *src;
+ size_t bytes_added;
+
+ src = ielem->data;
+ bytes_added = utf8_pad_to_width(p, src,
+ ((n+1)-(p-draw)) * sizeof(char),
+ ielem->wid, ifield->leftadj);
+ p += bytes_added;
+ }
+
+ draw[n] = '\0';
+
+ if(ifield->ctype == sfld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen > 0){
+ if(ielem->datalen == 1){
+ ielem->data[0] = save_schar1;
+ if(ielem->next && ielem->next->datalen)
+ ielem->next->data[0] = save_schar2;
+ }
+ else if(ielem->datalen > 1){
+ ielem->data[0] = save_schar1;
+ ielem->data[1] = save_schar2;
+ }
+ }
+ }
+ else if(ifield->ctype == afld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen >= ifield->width)
+ for(i = 0; i < ifield->width; i++)
+ ielem->data[i] = ' ';
+ }
+ else if(ifield->ctype == pfld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen > 0)
+ ielem->data[0] = save_pchar;
+ }
+
+ previfield = ifield;
+ }
+
+ *p = '\0';
+
+ if(draw_whole_line)
+ PutLine0(HEADER_ROWS(ps_global) + line, 0, draw);
+ }
+ else{
+ int uc, ac, do_arrow;
+ int i, drew_X = 0;
+ int inverse_hack = 0, need_inverse_hack = 0;
+ int doing_bold = 0;
+
+ /* so we can restore current color at the end */
+ if((uc=pico_usingcolor()) != 0)
+ lastc = pico_get_cur_color();
+
+ /*
+ * There are two possible "arrow" cursors. One is the one that
+ * you get when you are at a slow speed or you turn that slow
+ * speed one on. It is drawn as part of the status column.
+ * That one is the one associated with the variable "ac".
+ * It is always the base_color or the inverse of the base_color.
+ *
+ * The other "arrow" cursor is the one you get by including the
+ * ARROW token in the index-format. It may be configured to
+ * be colored.
+ *
+ * The arrow cursors have two special properties that make
+ * them different from other sections or fields.
+ * First, the arrow cursors only show up on the current line.
+ * Second, the arrow cursors are drawn with generated data, not
+ * data that is present in the passed in data.
+ */
+
+ /* ac is for the old integrated arrow cursor */
+ ac = F_ON(F_FORCE_ARROW,ps_global);
+
+ /* do_arrow is for the ARROW token in index-format */
+ do_arrow = (afld != iNothing);
+
+ MoveCursor(HEADER_ROWS(ps_global) + line, 0);
+
+ /* find the base color for the whole line */
+ if(cur && !ac && !do_arrow){
+ /*
+ * This stanza handles the current line marking in the
+ * regular, non-arrow-cursor case.
+ */
+
+ /*
+ * If the current line has a linecolor, apply the
+ * appropriate reverse transformation to show it is current.
+ */
+ if(uc && ice->linecolor && ice->linecolor->fg[0]
+ && ice->linecolor->bg[0] && pico_is_good_colorpair(ice->linecolor)){
+ base_color = apply_rev_color(ice->linecolor,
+ ps_global->index_color_style);
+
+ (void)pico_set_colorp(base_color, PSC_NONE);
+ }
+ else{
+ inverse_hack++;
+ if(uc){
+ COLOR_PAIR *rev;
+
+ if((rev = pico_get_rev_color()) != NULL){
+ base_color = new_color_pair(rev->fg, rev->bg);
+ (void)pico_set_colorp(base_color, PSC_NONE);
+ }
+ else
+ base_color = lastc;
+ }
+ }
+ }
+ else if(uc && ice->linecolor && ice->linecolor->fg[0]
+ && ice->linecolor->bg[0]
+ && pico_is_good_colorpair(ice->linecolor)){
+ (void)pico_set_colorp(ice->linecolor, PSC_NONE);
+ base_color = ice->linecolor;
+ }
+ else
+ base_color = lastc;
+
+ memset(draw, 0, sizeof(draw));
+ p = draw;
+
+ doing_bold = (sel && F_ON(F_SELECTED_SHOWN_BOLD, ps_global) && StartBold());
+
+ /* draw each field */
+ for(ifield = ice->ifield; ifield && p-draw < n; ifield = ifield->next){
+
+ drew_X = 0;
+
+ /*
+ * Fix up the data for some special cases.
+ */
+
+ /* message number string is generated on the fly */
+ if(ifield->ctype == iMessNo){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen >= ifield->width){
+ snprintf(ielem->data, ielem->datalen+1, "%*.ld", ifield->width, msgno);
+ ielem->data[ifield->width] = '\0';
+ ielem->data[ielem->datalen] = '\0';
+ }
+ }
+
+ if(ifield->ctype == sfld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen > 0){
+ if(ielem->datalen == 1){
+ save_schar1 = ielem->data[0];
+ if(sel && !doing_bold){
+ ielem->data[0] = 'X';
+ drew_X++;
+ }
+ else if(ac && cur && ielem->data[0] == ' ')
+ ielem->data[0] = '-';
+
+ if(ielem->next && ielem->next->datalen){
+ save_schar2 = ielem->next->data[0];
+ if(ac && cur && ielem->next->data[0] != '\0')
+ ielem->next->data[0] = '>';
+ }
+ }
+ else if(ielem->datalen > 1){
+ if(sel && !doing_bold){
+ ielem->data[0] = 'X';
+ drew_X++;
+ }
+ else if(ac && cur && ielem->data[0] == ' ')
+ ielem->data[0] = '-';
+
+ save_schar2 = ielem->data[1];
+ if(ac && cur && ielem->data[1] != '\0')
+ ielem->data[1] = '>';
+ }
+ }
+ }
+ else if(ifield->ctype == afld && do_arrow && cur){
+
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen >= ifield->width){
+ for(i = 0; i < ifield->width-1; i++)
+ ielem->data[i] = cur ? '-' : ' ';
+
+ ielem->data[i] = '>';
+ }
+ }
+ else if(ifield->ctype == pfld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen > 0){
+ save_pchar = ielem->data[0];
+ ielem->data[0] = ice->plus;
+ }
+ }
+
+ /* space between fields */
+ if(ifield != ice->ifield && !(previfield && previfield->ctype == iText)){
+ if(inverse_hack)
+ StartInverse();
+
+ Write_to_screen(" ");
+ if(inverse_hack)
+ EndInverse();
+ }
+
+ for(ielem = ifield->ielem; ielem; ielem = ielem->next){
+ char *src;
+ size_t bytes_added;
+
+ src = ielem->data;
+ bytes_added = utf8_pad_to_width(draw, src,
+ (n+1) * sizeof(char),
+ ielem->wid, ifield->leftadj);
+ draw[n] = '\0';
+
+ /*
+ * Switch to color for ielem.
+ * But don't switch if we drew an X in this column,
+ * because that overwrites the colored thing, and don't
+ * switch if this is the ARROW field and this is not
+ * the current message. ARROW field is only colored for
+ * the current message.
+ * And don't switch if current line and type eTypeCol.
+ */
+ if(ielem->color && pico_is_good_colorpair(ielem->color)
+ && !(do_arrow && ifield->ctype == afld && !cur)
+ && (!drew_X || ielem != ifield->ielem)
+ && !(cur && ielem->type == eTypeCol)){
+ need_inverse_hack = 0;
+ (void) pico_set_colorp(ielem->color, PSC_NORM);
+ }
+ else
+ need_inverse_hack = 1;
+
+ if(need_inverse_hack && inverse_hack)
+ StartInverse();
+
+ Write_to_screen(draw);
+ if(need_inverse_hack && inverse_hack)
+ EndInverse();
+
+ (void) pico_set_colorp(base_color, PSC_NORM);
+ }
+
+ /*
+ * Restore the data for the special cases.
+ */
+
+ if(ifield->ctype == sfld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen > 0){
+ if(ielem->datalen == 1){
+ ielem->data[0] = save_schar1;
+ if(ielem->next && ielem->next->datalen)
+ ielem->next->data[0] = save_schar2;
+ }
+ else if(ielem->datalen > 1){
+ ielem->data[0] = save_schar1;
+ ielem->data[1] = save_schar2;
+ }
+ }
+ }
+ else if(ifield->ctype == afld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen >= ifield->width)
+ for(i = 0; i < ifield->width; i++)
+ ielem->data[i] = ' ';
+ }
+ else if(ifield->ctype == pfld){
+ ielem = ifield->ielem;
+ if(ielem && ielem->datalen > 0)
+ ielem->data[0] = save_pchar;
+ }
+
+ previfield = ifield;
+ }
+
+ if(doing_bold)
+ EndBold();
+
+ if(base_color && base_color != lastc && base_color != ice->linecolor)
+ free_color_pair(&base_color);
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NORM);
+ free_color_pair(&lastc);
+ }
+ }
+ }
+
+ entry->hilite = cur;
+ entry->bolded = sel;
+ entry->msgno = msgno;
+ entry->plus = ice->plus;
+ entry->id = ice->id;
+
+ if(!ice->color_lookup_done && pico_usingcolor())
+ entry->id = 0;
+
+ return(cur ? (line + HEADER_ROWS(ps_global)) : 0);
+}
+
+/*
+ * setup_index_state - hooked onto pith_opt_save_index_state to setup
+ * current_index_state after setup_{index,thread}_header_widths
+ */
+void
+setup_index_state(int threaded)
+{
+ if(current_index_state){
+ if(threaded){
+ current_index_state->status_col = 0;
+ current_index_state->status_fld = iStatus;
+ current_index_state->plus_fld = iNothing;
+ current_index_state->arrow_fld = iNothing;
+ } else {
+ INDEX_COL_S *cdesc, *prevcdesc = NULL;
+ IndexColType sfld, altfld, plusfld, arrowfld;
+ int width, fld, col, pluscol, scol, altcol;
+
+ col = 0;
+ scol = -1;
+ sfld = iNothing;
+ altcol = -1;
+ altfld = iNothing;
+ /* figure out which field is status field */
+ for(cdesc = ps_global->index_disp_format, fld = 0;
+ cdesc->ctype != iNothing;
+ cdesc++){
+ width = cdesc->width;
+ if(width == 0)
+ continue;
+
+ /* space between columns */
+ if(col > 0 && !(prevcdesc && prevcdesc->ctype == iText))
+ col++;
+
+ if(cdesc->ctype == iStatus){
+ scol = col;
+ sfld = cdesc->ctype;
+ break;
+ }
+
+ if(cdesc->ctype == iFStatus || cdesc->ctype == iIStatus){
+ scol = col;
+ sfld = cdesc->ctype;
+ break;
+ }
+
+ if(cdesc->ctype == iMessNo){
+ altcol = col;
+ altfld = cdesc->ctype;
+ }
+
+ col += width;
+ fld++;
+ prevcdesc = cdesc;
+ }
+
+ if(sfld == iNothing){
+ if(altcol == -1){
+ scol = 0;
+ }
+ else{
+ scol = altcol;
+ sfld = altfld;
+ }
+ }
+
+
+ current_index_state->status_col = scol;
+ current_index_state->status_fld = sfld;
+
+ col = 0;
+ plusfld = iNothing;
+ pluscol = -1;
+ prevcdesc = NULL;
+ /* figure out which column to use for threading '+' */
+ if(THREADING()
+ && ps_global->thread_disp_style != THREAD_NONE
+ && ps_global->VAR_THREAD_MORE_CHAR[0]
+ && ps_global->VAR_THREAD_EXP_CHAR[0])
+ for(cdesc = ps_global->index_disp_format, fld = 0;
+ cdesc->ctype != iNothing;
+ cdesc++){
+ width = cdesc->width;
+ if(width == 0)
+ continue;
+
+ /* space between columns */
+ if(col > 0 && !(prevcdesc && prevcdesc->ctype == iText))
+ col++;
+
+ if((cdesc->ctype == iSubject
+ || cdesc->ctype == iSubjectText
+ || cdesc->ctype == iSubjKey
+ || cdesc->ctype == iSubjKeyText
+ || cdesc->ctype == iSubjKeyInit
+ || cdesc->ctype == iSubjKeyInitText)
+ && (ps_global->thread_disp_style == THREAD_STRUCT
+ || ps_global->thread_disp_style == THREAD_MUTTLIKE
+ || ps_global->thread_disp_style == THREAD_INDENT_SUBJ1
+ || ps_global->thread_disp_style == THREAD_INDENT_SUBJ2)){
+ plusfld = cdesc->ctype;
+ pluscol = col;
+ break;
+ }
+
+ if((cdesc->ctype == iFrom
+ || cdesc->ctype == iFromToNotNews
+ || cdesc->ctype == iFromTo
+ || cdesc->ctype == iAddress
+ || cdesc->ctype == iMailbox)
+ && (ps_global->thread_disp_style == THREAD_INDENT_FROM1
+ || ps_global->thread_disp_style == THREAD_INDENT_FROM2
+ || ps_global->thread_disp_style == THREAD_STRUCT_FROM)){
+ plusfld = cdesc->ctype;
+ pluscol = col;
+ break;
+ }
+
+ col += width;
+ fld++;
+ prevcdesc = cdesc;
+ }
+
+ current_index_state->plus_fld = plusfld;
+ current_index_state->plus_col = pluscol;
+
+ arrowfld = iNothing;
+ /* figure out which field is arrow field, if any */
+ for(cdesc = ps_global->index_disp_format, fld = 0;
+ cdesc->ctype != iNothing;
+ cdesc++){
+ width = cdesc->width;
+ if(width == 0)
+ continue;
+
+ if(cdesc->ctype == iArrow){
+ arrowfld = cdesc->ctype;
+ break;
+ }
+
+ fld++;
+ }
+
+ current_index_state->arrow_fld = arrowfld;
+ }
+ }
+}
+
+
+/*
+ * insert_condensed_thread_cue - used on pith hook to add decoration to
+ * subject or from text to show condensed thread info
+ */
+int
+condensed_thread_cue(PINETHRD_S *thd, ICE_S *ice,
+ char **fieldstr, size_t *strsize, int width, int collapsed)
+{
+ if(current_index_state->plus_fld != iNothing && !THRD_INDX() && fieldstr && *fieldstr){
+ /*
+ * WARNING!
+ * There is an unwarranted assumption here that VAR_THREAD_MORE_CHAR[0]
+ * and VAR_THREAD_EXP_CHAR[0] are ascii.
+ * Could do something similar to the conversions done with keyword
+ * initials in key_str.
+ */
+ if(ice)
+ ice->plus = collapsed ? ps_global->VAR_THREAD_MORE_CHAR[0]
+ : (thd && thd->next)
+ ? ps_global->VAR_THREAD_EXP_CHAR[0] : ' ';
+
+ if(strsize && *strsize > 0 && width != 0){
+ *(*fieldstr)++ = ' ';
+ (*strsize)--;
+ if(width > 0)
+ width--;
+ }
+
+ if(strsize && *strsize > 0 && width != 0){
+ *(*fieldstr)++ = ' ';
+ (*strsize)--;
+ if(width > 0)
+ width--;
+ }
+ }
+
+ return(width);
+}
+
+
+int
+truncate_subj_and_from_strings(void)
+{
+ return 1;
+}
+
+
+/*
+ * paint_index_hline - paint index line given what we got
+ */
+void
+paint_index_hline(MAILSTREAM *stream, long int msgno, ICE_S *ice)
+{
+ PINETHRD_S *thrd;
+
+ /*
+ * Trust only what we get back that isn't bogus since
+ * we were prevented from doing any fetches and such...
+ */
+ if((ps_global->redrawer == redraw_index_body
+ || ps_global->prev_screen == mail_index_screen)
+ && current_index_state
+ && current_index_state->stream == stream
+ && !ps_global->msgmap->hilited){
+ int line;
+
+ /*
+ * This test isn't right if there are hidden lines. The line will
+ * fail the test because it seems like it is past the end of the
+ * screen but since the hidden lines don't take up space the line
+ * might actually be on the screen. Don't know that it is worth
+ * it to fix this, though, since you may have to file through
+ * many hidden lines before finding the visible ones. I'm not sure
+ * if the logic inside the if is correct when we do pass the
+ * top-level test. Leave it for now. Hubert - 2002-06-28
+ */
+ if((line = (int)(msgno - current_index_state->msg_at_top)) >= 0
+ && line < current_index_state->lines_per_page){
+ if(any_lflagged(ps_global->msgmap, MN_HIDE | MN_CHID)){
+ long n;
+ long zoomhide, collapsehide;
+
+ zoomhide = any_lflagged(ps_global->msgmap, MN_HIDE);
+ collapsehide = any_lflagged(ps_global->msgmap, MN_CHID);
+
+ /*
+ * Line is visible if it is selected and not hidden due to
+ * thread collapse, or if there is no zooming happening and
+ * it is not hidden due to thread collapse.
+ */
+ for(line = 0, n = current_index_state->msg_at_top;
+ n != msgno;
+ n++)
+ if((zoomhide
+ && get_lflag(stream, current_index_state->msgmap,
+ n, MN_SLCT)
+ && (!collapsehide
+ || !get_lflag(stream, current_index_state->msgmap, n,
+ MN_CHID)))
+ ||
+ (!zoomhide
+ && !get_lflag(stream, current_index_state->msgmap,
+ n, MN_CHID)))
+ line++;
+ }
+
+ thrd = NULL;
+ if(THRD_INDX()){
+ unsigned long rawno;
+
+ rawno = mn_m2raw(current_index_state->msgmap, msgno);
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+ }
+
+ paint_index_line(ice, line,
+ (THRD_INDX() && thrd) ? thrd->thrdno : msgno,
+ current_index_state->status_fld,
+ current_index_state->plus_fld,
+ current_index_state->arrow_fld,
+ &current_index_state->entry_state[line],
+ mn_is_cur(current_index_state->msgmap, msgno),
+ THRD_INDX()
+ ? (count_lflags_in_thread(stream, thrd,
+ current_index_state->msgmap,
+ MN_SLCT) > 0)
+ : get_lflag(stream, current_index_state->msgmap,
+ msgno, MN_SLCT));
+ fflush(stdout);
+ }
+ }
+}
+
+
+
+
+/*
+ * pine_imap_env -- C-client's telling us an envelope just arrived
+ * from the server. Use it if we can...
+ */
+void
+pine_imap_envelope(MAILSTREAM *stream, long unsigned int rawno, ENVELOPE *env)
+{
+ MESSAGECACHE *mc;
+
+ dprint((7, "imap_env(%ld)\n", rawno));
+ if(stream && !sp_mail_box_changed(stream)
+ && stream == ps_global->mail_stream
+ && rawno > 0L && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream,rawno))
+ && mc->valid
+ && mc->rfc822_size
+ && !get_lflag(stream, NULL, rawno, MN_HIDE | MN_CHID | MN_EXLD)){
+ INDEXDATA_S idata;
+ ICE_S *ice;
+
+ memset(&idata, 0, sizeof(INDEXDATA_S));
+ idata.no_fetch = 1;
+ idata.size = mc->rfc822_size;
+ idata.rawno = rawno;
+ idata.msgno = mn_raw2m(sp_msgmap(stream), rawno);
+ idata.stream = stream;
+
+ index_data_env(&idata, env);
+
+ /*
+ * Look for resent-to already in MAILCACHE data
+ */
+ if(mc->private.msg.header.text.data){
+ STRINGLIST *lines;
+ SIZEDTEXT szt;
+ static char *linelist[] = {"resent-to" , NULL};
+
+ if(mail_match_lines(lines = new_strlst(linelist),
+ mc->private.msg.lines, 0L)){
+ idata.valid_resent_to = 1;
+ memset(&szt, 0, sizeof(SIZEDTEXT));
+ textcpy(&szt, &mc->private.msg.header.text);
+ mail_filter((char *) szt.data, szt.size, lines, 0L);
+ idata.resent_to_us = parsed_resent_to_us((char *) szt.data);
+ if(szt.data)
+ fs_give((void **) &szt.data);
+ }
+
+ free_strlst(&lines);
+ }
+
+ ice = (*format_index_line)(&idata);
+ if(idata.bogus)
+ clear_ice(&ice);
+ else
+ paint_index_hline(stream, idata.msgno, ice);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Scroll to specified postion.
+
+
+ Args: pos - position to scroll to.
+
+ Returns: TRUE - did the scroll operation.
+ FALSE - was not able to do the scroll operation.
+ ----*/
+int
+index_scroll_to_pos (long int pos)
+{
+ static short bad_timing = 0;
+ long i, j, k;
+
+ if(bad_timing)
+ return (FALSE);
+
+ /*
+ * Put the requested line at the top of the screen...
+ */
+
+ /*
+ * Starting at msg 'pos' find next visible message.
+ */
+ for(i=pos; i <= mn_get_total(current_index_state->msgmap); i++) {
+ if(!msgline_hidden(current_index_state->stream,
+ current_index_state->msgmap, i, 0)){
+ current_index_state->msg_at_top = i;
+ break;
+ }
+ }
+
+ /*
+ * If single selection, move selected message to be on the screen.
+ */
+ if (mn_total_cur(current_index_state->msgmap) == 1L) {
+ if (current_index_state->msg_at_top >
+ mn_get_cur (current_index_state->msgmap)) {
+ /* Selection was above screen, move to top of screen. */
+ mn_set_cur(current_index_state->msgmap,current_index_state->msg_at_top);
+ }
+ else {
+ /* Scan through the screen. If selection found, leave where is.
+ * Otherwise, move to end of screen */
+ for( i = current_index_state->msg_at_top,
+ j = current_index_state->lines_per_page;
+ i != mn_get_cur(current_index_state->msgmap) &&
+ i <= mn_get_total(current_index_state->msgmap) &&
+ j > 0L;
+ i++) {
+ if(!msgline_hidden(current_index_state->stream,
+ current_index_state->msgmap, i, 0)){
+ j--;
+ k = i;
+ }
+ }
+ if(j <= 0L)
+ /* Move to end of screen. */
+ mn_set_cur(current_index_state->msgmap, k);
+ }
+ }
+
+ bad_timing = 0;
+ return (TRUE);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Adjust the index display state down a line
+
+ Args: scroll_count -- number of lines to scroll
+
+ Returns: TRUE - did the scroll operation.
+ FALSE - was not able to do the scroll operation.
+ ----*/
+int
+index_scroll_down(long int scroll_count)
+{
+ static short bad_timing = 0;
+ long i, j, k;
+ long cur, total;
+
+ if(bad_timing)
+ return (FALSE);
+
+ bad_timing = 1;
+
+
+ j = -1L;
+ total = mn_get_total (current_index_state->msgmap);
+ for(k = i = current_index_state->msg_at_top; ; i++){
+
+ /* Only examine non-hidden messages. */
+ if(!msgline_hidden(current_index_state->stream,
+ current_index_state->msgmap, i, 0)){
+ /* Remember this message */
+ k = i;
+ /* Increment count of lines. */
+ if (++j >= scroll_count) {
+ /* Counted enough lines, stop. */
+ current_index_state->msg_at_top = k;
+ break;
+ }
+ }
+
+ /* If at last message, stop. */
+ if (i >= total){
+ current_index_state->msg_at_top = k;
+ break;
+ }
+ }
+
+ /*
+ * If not multiple selection, see if selected message visable. if not
+ * set it to last visable message.
+ */
+ if(mn_total_cur(current_index_state->msgmap) == 1L) {
+ j = 0L;
+ cur = mn_get_cur (current_index_state->msgmap);
+ for (i = current_index_state->msg_at_top; i <= total; ++i) {
+ if(!msgline_hidden(current_index_state->stream,
+ current_index_state->msgmap, i, 0)){
+ if (++j >= current_index_state->lines_per_page) {
+ break;
+ }
+ if (i == cur)
+ break;
+ }
+ }
+ if (i != cur)
+ mn_set_cur(current_index_state->msgmap,
+ current_index_state->msg_at_top);
+ }
+
+ bad_timing = 0;
+ return (TRUE);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Adjust the index display state up a line
+
+ Args: scroll_count -- number of lines to scroll
+
+ Returns: TRUE - did the scroll operation.
+ FALSE - was not able to do the scroll operation.
+
+ ----*/
+int
+index_scroll_up(long int scroll_count)
+{
+ static short bad_timing = 0;
+ long i, j, k;
+ long cur;
+
+ if(bad_timing)
+ return(FALSE);
+
+ bad_timing = 1;
+
+ j = -1L;
+ for(k = i = current_index_state->msg_at_top; ; i--){
+
+ /* Only examine non-hidden messages. */
+ if(!msgline_hidden(current_index_state->stream,
+ current_index_state->msgmap, i, 0)){
+ /* Remember this message */
+ k = i;
+ /* Increment count of lines. */
+ if (++j >= scroll_count) {
+ /* Counted enough lines, stop. */
+ current_index_state->msg_at_top = k;
+ break;
+ }
+ }
+
+ /* If at first message, stop */
+ if (i <= 1L){
+ current_index_state->msg_at_top = k;
+ break;
+ }
+ }
+
+
+ /*
+ * If not multiple selection, see if selected message visable. if not
+ * set it to last visable message.
+ */
+ if(mn_total_cur(current_index_state->msgmap) == 1L) {
+ j = 0L;
+ cur = mn_get_cur (current_index_state->msgmap);
+ for ( i = current_index_state->msg_at_top;
+ i <= mn_get_total(current_index_state->msgmap);
+ ++i) {
+ if(!msgline_hidden(current_index_state->stream,
+ current_index_state->msgmap, i, 0)){
+ if (++j >= current_index_state->lines_per_page) {
+ k = i;
+ break;
+ }
+ if (i == cur)
+ break;
+ }
+ }
+ if (i != cur)
+ mn_set_cur(current_index_state->msgmap, k);
+ }
+
+
+ bad_timing = 0;
+ return (TRUE);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Calculate the message number that should be at the top of the display
+
+ Args: current - the current message number
+ lines_per_page - the number of lines for the body of the index only
+
+ Returns: -1 if the current message is -1
+ the message entry for the first message at the top of the screen.
+
+When paging in the index it is always on even page boundies, and the
+current message is always on the page thus the top of the page is
+completely determined by the current message and the number of lines
+on the page.
+ ----*/
+long
+top_ent_calc(MAILSTREAM *stream, MSGNO_S *msgs, long int at_top, long int lines_per_page)
+{
+ long current, hidden, lastn;
+ long n, m = 0L, t = 1L;
+
+ current = (mn_total_cur(msgs) <= 1L) ? mn_get_cur(msgs) : at_top;
+
+ if(current < 0L)
+ return(-1);
+
+ if(lines_per_page == 0L)
+ return(current);
+
+ if(THRD_INDX_ENABLED()){
+ long rawno;
+ PINETHRD_S *thrd = NULL;
+
+ rawno = mn_m2raw(msgs, mn_get_cur(msgs));
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+
+ if(THRD_INDX()){
+
+ if(any_lflagged(msgs, MN_HIDE)){
+ PINETHRD_S *is_current_thrd;
+
+ is_current_thrd = thrd;
+ if(is_current_thrd){
+ if(mn_get_revsort(msgs)){
+ /* start with top of tail of thread list */
+ thrd = fetch_thread(stream, mn_m2raw(msgs, 1L));
+ if(thrd && thrd->top && thrd->top != thrd->rawno)
+ thrd = fetch_thread(stream, thrd->top);
+ }
+ else{
+ /* start with head of thread list */
+ thrd = fetch_head_thread(stream);
+ }
+
+ t = 1L;
+ m = 0L;
+ if(thrd)
+ n = mn_raw2m(msgs, thrd->rawno);
+
+ while(thrd){
+ if(!msgline_hidden(stream, msgs, n, 0)
+ && (++m % lines_per_page) == 1L)
+ t = n;
+
+ if(thrd == is_current_thrd)
+ break;
+
+ if(mn_get_revsort(msgs) && thrd->prevthd)
+ thrd = fetch_thread(stream, thrd->prevthd);
+ else if(!mn_get_revsort(msgs) && thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else
+ thrd = NULL;
+
+ if(thrd)
+ n = mn_raw2m(msgs, thrd->rawno);
+ }
+ }
+ }
+ else{
+ if(thrd){
+ n = thrd->thrdno;
+ m = lines_per_page * ((n - 1L)/ lines_per_page) + 1L;
+ n = thrd->rawno;
+ /*
+ * We want to find the m'th thread and the
+ * message number that goes with that. We just have
+ * to back up from where we are to get there.
+ * If we have a reverse sort backing up is going
+ * forward through the thread.
+ */
+ while(thrd && m < thrd->thrdno){
+ n = thrd->rawno;
+ if(mn_get_revsort(msgs) && thrd->nextthd)
+ thrd = fetch_thread(stream, thrd->nextthd);
+ else if(!mn_get_revsort(msgs) && thrd->prevthd)
+ thrd = fetch_thread(stream, thrd->prevthd);
+ else
+ thrd = NULL;
+ }
+
+ if(thrd)
+ n = thrd->rawno;
+
+ t = mn_raw2m(msgs, n);
+ }
+ }
+ }
+ else{ /* viewing a thread */
+
+ lastn = mn_get_total(msgs);
+ t = 1L;
+
+ /* get top of thread */
+ if(thrd && thrd->top && thrd->top != thrd->rawno)
+ thrd = fetch_thread(stream, thrd->top);
+
+ if(thrd){
+ if(mn_get_revsort(msgs))
+ lastn = mn_raw2m(msgs, thrd->rawno);
+ else
+ t = mn_raw2m(msgs, thrd->rawno);
+ }
+
+ n = 0L;
+
+ /* n is the end of this thread */
+ while(thrd){
+ n = mn_raw2m(msgs, thrd->rawno);
+ if(thrd->branch)
+ thrd = fetch_thread(stream, thrd->branch);
+ else if(thrd->next)
+ thrd = fetch_thread(stream, thrd->next);
+ else
+ thrd = NULL;
+ }
+
+ if(n){
+ if(mn_get_revsort(msgs))
+ t = n;
+ else
+ lastn = n;
+ }
+
+ for(m = 0L, n = t; n <= MIN(current, lastn); n++)
+ if(!msgline_hidden(stream, msgs, n, 0)
+ && (++m % lines_per_page) == 1L)
+ t = n;
+ }
+
+ return(t);
+ }
+ else if((hidden = any_lflagged(msgs, MN_HIDE | MN_CHID)) != 0){
+
+ if(current < mn_get_total(msgs) / 2){
+ t = 1L;
+ m = 0L;
+ for(n = 1L; n <= MIN(current, mn_get_total(msgs)); n++)
+ if(!msgline_hidden(stream, msgs, n, 0)
+ && (++m % lines_per_page) == 1L)
+ t = n;
+ }
+ else{
+ t = current+1L;
+ m = mn_get_total(msgs)-hidden+1L;
+ for(n = mn_get_total(msgs); n >= 1L && t > current; n--)
+ if(!msgline_hidden(stream, msgs, n, 0)
+ && (--m % lines_per_page) == 1L)
+ t = n;
+
+ if(t > current)
+ t = 1L;
+ }
+
+ return(t);
+ }
+ else
+ return(lines_per_page * ((current - 1L)/ lines_per_page) + 1L);
+}
+
+
+/*----------------------------------------------------------------------
+ Clear various bits that make up a healthy display
+
+ ----*/
+void
+reset_index_border(void)
+{
+ mark_status_dirty();
+ mark_keymenu_dirty();
+ mark_titlebar_dirty();
+ ps_global->mangled_screen = 1; /* signal FULL repaint */
+}
+
+
+/*----------------------------------------------------------------------
+ This redraws the body of the index screen, taking into
+account any change in the size of the screen. All the state needed to
+repaint is in the static variables so this can be called from
+anywhere.
+ ----*/
+void
+redraw_index_body(void)
+{
+ int agg;
+
+ if((agg = (mn_total_cur(current_index_state->msgmap) > 1L)) != 0)
+ restore_selected(current_index_state->msgmap);
+
+ ps_global->mangled_body = 1;
+
+ (void) update_index(ps_global, current_index_state);
+ if(agg)
+ pseudo_selected(current_index_state->stream, current_index_state->msgmap);
+}
+
+
+/*----------------------------------------------------------------------
+ Give hint about Other command being optional. Some people get the idea
+ that it is required to use the commands on the 2nd and 3rd keymenus.
+
+ Args: none
+
+ Result: message may be printed to status line
+ ----*/
+void
+warn_other_cmds(void)
+{
+ static int other_cmds = 0;
+
+ other_cmds++;
+ if(((ps_global->first_time_user || ps_global->show_new_version) &&
+ other_cmds % 3 == 0 && other_cmds < 10) || other_cmds % 20 == 0)
+ q_status_message(SM_ASYNC, 0, 9,
+ _("Remember the \"O\" command is always optional"));
+}
+
+
+void
+thread_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
+ UCS preloadkeystroke, int q_line)
+{
+ PINETHRD_S *thrd = NULL;
+ unsigned long rawno, save_branch;
+ int we_cancel = 0;
+ int flags = AC_FROM_THREAD;
+
+ if(!(stream && msgmap))
+ return;
+
+ rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+ if(rawno)
+ thrd = fetch_thread(stream, rawno);
+
+ if(!thrd)
+ return;
+
+ save_branch = thrd->branch;
+ thrd->branch = 0L; /* branch is a sibling, not part of thread */
+
+ if(!preloadkeystroke){
+ if(!THRD_INDX()){
+ if(get_lflag(stream, NULL, rawno, MN_COLL) && thrd->next)
+ flags |= AC_EXPN;
+ else
+ flags |= AC_COLL;
+ }
+
+ if(count_lflags_in_thread(stream, thrd, msgmap, MN_SLCT)
+ == count_lflags_in_thread(stream, thrd, msgmap, MN_NONE))
+ flags |= AC_UNSEL;
+ }
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ /* save the SLCT flags in STMP for restoring at the bottom */
+ copy_lflags(stream, msgmap, MN_SLCT, MN_STMP);
+
+ /* clear the values from the SLCT flags */
+ set_lflags(stream, msgmap, MN_SLCT, 0);
+
+ /* set SLCT for thrd on down */
+ set_flags_for_thread(stream, msgmap, MN_SLCT, thrd, 1);
+ thrd->branch = save_branch;
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ (void ) apply_command(state, stream, msgmap, preloadkeystroke, flags,
+ q_line);
+
+ /* restore the original flags */
+ copy_lflags(stream, msgmap, MN_STMP, MN_SLCT);
+
+ if(any_lflagged(msgmap, MN_HIDE) > 0L){
+ /* if nothing left selected, unhide all */
+ if(any_lflagged(msgmap, MN_SLCT) == 0L){
+ (void) unzoom_index(ps_global, stream, msgmap);
+ dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
+ q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
+ }
+
+ /* if current is hidden, adjust */
+ adjust_cur_to_visible(stream, msgmap);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Search the message headers as displayed in index
+
+ Args: command_line -- The screen line to prompt on
+ msg -- The current message number to start searching at
+ max_msg -- The largest message number in the current folder
+
+ The headers are searched exactly as they are displayed on the screen. The
+search will wrap around to the beginning if not string is not found right
+away.
+ ----*/
+void
+index_search(struct pine *state, MAILSTREAM *stream, int command_line, MSGNO_S *msgmap)
+{
+ int rc, select_all = 0, flags, prefetch, we_turned_on = 0;
+ long i, sorted_msg, selected = 0L;
+ char prompt[MAX_SEARCH+50], new_string[MAX_SEARCH+1];
+ char buf[MAX_SCREEN_COLS+1], *p;
+ HelpType help;
+ char search_string[MAX_SEARCH+1];
+ ICE_S *ice, *ic;
+ static HISTORY_S *history = NULL;
+ static ESCKEY_S header_search_key[] = { {0, 0, NULL, NULL },
+ {ctrl('Y'), 10, "^Y", N_("First Msg")},
+ {ctrl('V'), 11, "^V", N_("Last Msg")},
+ {KEY_UP, 30, "", ""},
+ {KEY_DOWN, 31, "", ""},
+ {-1, 0, NULL, NULL} };
+#define KU_IS (3) /* index of KEY_UP */
+#define PREFETCH_THIS_MANY_LINES (50)
+
+ init_hist(&history, HISTSIZE);
+ search_string[0] = '\0';
+ if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
+ strncpy(search_string, p, sizeof(search_string));
+ search_string[sizeof(search_string)-1] = '\0';
+ }
+
+ dprint((4, "\n - search headers - \n"));
+
+ if(!any_messages(msgmap, NULL, "to search")){
+ return;
+ }
+ else if(mn_total_cur(msgmap) > 1L){
+ q_status_message1(SM_ORDER, 0, 2, "%s msgs selected; Can't search",
+ comatose(mn_total_cur(msgmap)));
+ return;
+ }
+ else
+ sorted_msg = mn_get_cur(msgmap);
+
+ help = NO_HELP;
+ new_string[0] = '\0';
+
+ while(1) {
+ snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
+
+ if(F_ON(F_ENABLE_AGG_OPS, ps_global)){
+ header_search_key[0].ch = ctrl('X');
+ header_search_key[0].rval = 12;
+ header_search_key[0].name = "^X";
+ header_search_key[0].label = N_("Select Matches");
+ }
+ else{
+ header_search_key[0].ch = header_search_key[0].rval = 0;
+ header_search_key[0].name = header_search_key[0].label = NULL;
+ }
+
+ /*
+ * 2 is really 1 because there will be one real entry and
+ * one entry of "" because of the get_prev_hist above.
+ */
+ if(items_in_hist(history) > 2){
+ header_search_key[KU_IS].name = HISTORY_UP_KEYNAME;
+ header_search_key[KU_IS].label = HISTORY_KEYLABEL;
+ header_search_key[KU_IS+1].name = HISTORY_DOWN_KEYNAME;
+ header_search_key[KU_IS+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ header_search_key[KU_IS].name = "";
+ header_search_key[KU_IS].label = "";
+ header_search_key[KU_IS+1].name = "";
+ header_search_key[KU_IS+1].label = "";
+ }
+
+ flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
+
+ rc = optionally_enter(new_string, command_line, 0, sizeof(new_string),
+ prompt, header_search_key, help, &flags);
+
+ if(rc == 3) {
+ help = (help != NO_HELP) ? NO_HELP :
+ F_ON(F_ENABLE_AGG_OPS, ps_global) ? h_os_index_whereis_agg
+ : h_os_index_whereis;
+ continue;
+ }
+ else if(rc == 10){
+ q_status_message(SM_ORDER, 0, 3, _("Searched to First Message."));
+ if(any_lflagged(msgmap, MN_HIDE | MN_CHID)){
+ do{
+ selected = sorted_msg;
+ mn_dec_cur(stream, msgmap, MH_NONE);
+ sorted_msg = mn_get_cur(msgmap);
+ }
+ while(selected != sorted_msg);
+ }
+ else
+ sorted_msg = (mn_get_total(msgmap) > 0L) ? 1L : 0L;
+
+ mn_set_cur(msgmap, sorted_msg);
+ return;
+ }
+ else if(rc == 11){
+ q_status_message(SM_ORDER, 0, 3, _("Searched to Last Message."));
+ if(any_lflagged(msgmap, MN_HIDE | MN_CHID)){
+ do{
+ selected = sorted_msg;
+ mn_inc_cur(stream, msgmap, MH_NONE);
+ sorted_msg = mn_get_cur(msgmap);
+ }
+ while(selected != sorted_msg);
+ }
+ else
+ sorted_msg = mn_get_total(msgmap);
+
+ mn_set_cur(msgmap, sorted_msg);
+ return;
+ }
+ else if(rc == 12){
+ select_all = 1;
+ break;
+ }
+ else if(rc == 30){
+ if((p = get_prev_hist(history, new_string, 0, NULL)) != NULL){
+ strncpy(new_string, p, sizeof(new_string));
+ new_string[sizeof(new_string)-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+ else if(rc == 31){
+ if((p = get_next_hist(history, new_string, 0, NULL)) != NULL){
+ strncpy(new_string, p, sizeof(new_string));
+ new_string[sizeof(new_string)-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+
+ if(rc != 4){ /* 4 is redraw */
+ save_hist(history, new_string, 0, NULL);
+ break;
+ }
+ }
+
+ if(rc == 1 || (new_string[0] == '\0' && search_string[0] == '\0')) {
+ cmd_cancelled(_("Search"));
+ return;
+ }
+
+ if(new_string[0] == '\0'){
+ strncpy(new_string, search_string, sizeof(new_string));
+ new_string[sizeof(new_string)-1] = '\0';
+ }
+
+ strncpy(search_string, new_string, sizeof(search_string));
+ search_string[sizeof(search_string)-1] = '\0';
+
+ we_turned_on = intr_handling_on();
+
+ prefetch = 0;
+ for(i = sorted_msg + ((select_all)?0:1);
+ i <= mn_get_total(msgmap) && !ps_global->intr_pending;
+ i++){
+ if(msgline_hidden(stream, msgmap, i, 0))
+ continue;
+
+ if(prefetch <= 0)
+ prefetch = PREFETCH_THIS_MANY_LINES;
+
+ ic = build_header_work(state, stream, msgmap, i, i, prefetch--, NULL);
+
+ ice = (ic && THRD_INDX() && ic->tice) ? ic->tice : ic;
+
+ if(srchstr(simple_index_line(buf, sizeof(buf), ice, i),
+ search_string)){
+ selected++;
+ if(select_all)
+ set_lflag(stream, msgmap, i, MN_SLCT, 1);
+ else
+ break;
+ }
+ }
+
+ prefetch = 0;
+ if(i > mn_get_total(msgmap)){
+ for(i = 1; i < sorted_msg && !ps_global->intr_pending; i++){
+ if(msgline_hidden(stream, msgmap, i, 0))
+ continue;
+
+ if(prefetch <= 0)
+ prefetch = PREFETCH_THIS_MANY_LINES;
+
+ ic = build_header_work(state, stream, msgmap, i, i, prefetch--, NULL);
+
+ ice = (ic && THRD_INDX() && ic->tice) ? ic->tice : ic;
+
+ if(srchstr(simple_index_line(buf, sizeof(buf), ice, i),
+ search_string)){
+ selected++;
+ if(select_all)
+ set_lflag(stream, msgmap, i, MN_SLCT, 1);
+ else
+ break;
+ }
+ }
+ }
+
+ /* search current line */
+ if(!select_all && !selected){
+ i = sorted_msg;
+ if(!msgline_hidden(stream, msgmap, i, 0)){
+
+ ic = build_header_work(state, stream, msgmap, i, i, 1, NULL);
+
+ ice = (ic && THRD_INDX() && ic->tice) ? ic->tice : ic;
+
+ if(srchstr(simple_index_line(buf, sizeof(buf), ice, i),
+ search_string)){
+ selected++;
+ }
+ }
+ }
+
+ if(ps_global->intr_pending){
+ q_status_message1(SM_ORDER, 0, 3, _("Search cancelled.%s"),
+ select_all ? _(" Selected set may be incomplete."):"");
+ }
+ else if(select_all){
+ if(selected
+ && any_lflagged(msgmap, MN_SLCT) > 0L
+ && !any_lflagged(msgmap, MN_HIDE)
+ && F_ON(F_AUTO_ZOOM, state))
+ (void) zoom_index(state, stream, msgmap, MN_SLCT);
+
+ q_status_message1(SM_ORDER, 0, 3, _("%s messages found matching word"),
+ long2string(selected));
+ }
+ else if(selected){
+ q_status_message1(SM_ORDER, 0, 3, _("Word found%s"),
+ (i < sorted_msg) ? _(". Search wrapped to beginning") :
+ (i == sorted_msg) ? _(". Current line contains only match") : "");
+ mn_set_cur(msgmap, i);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Word not found"));
+
+ if(we_turned_on)
+ intr_handling_off();
+}
+
+
+/*
+ * Original idea from Stephen Casner <casner@acm.org>.
+ *
+ * Apply the appropriate reverse color transformation to the given
+ * color pair and return a new color pair. The caller should free the
+ * color pair.
+ *
+ */
+COLOR_PAIR *
+apply_rev_color(COLOR_PAIR *cp, int style)
+{
+ COLOR_PAIR *rc = pico_get_rev_color();
+
+ if(rc){
+ if(style == IND_COL_REV){
+ /* just use Reverse color regardless */
+ return(new_color_pair(rc->fg, rc->bg));
+ }
+ else if(style == IND_COL_FG){
+ /*
+ * If changing to Rev fg is readable and different
+ * from what it already is, do it.
+ */
+ if(strcmp(rc->fg, cp->bg) && strcmp(rc->fg, cp->fg))
+ return(new_color_pair(rc->fg, cp->bg));
+ }
+ else if(style == IND_COL_BG){
+ /*
+ * If changing to Rev bg is readable and different
+ * from what it already is, do it.
+ */
+ if(strcmp(rc->bg, cp->fg) && strcmp(rc->bg, cp->bg))
+ return(new_color_pair(cp->fg, rc->bg));
+ }
+ else if(style == IND_COL_FG_NOAMBIG){
+ /*
+ * If changing to Rev fg is readable, different
+ * from what it already is, and not the same as
+ * the Rev color, do it.
+ */
+ if(strcmp(rc->fg, cp->bg) && strcmp(rc->fg, cp->fg) &&
+ strcmp(rc->bg, cp->bg))
+ return(new_color_pair(rc->fg, cp->bg));
+ }
+ else if(style == IND_COL_BG_NOAMBIG){
+ /*
+ * If changing to Rev bg is readable, different
+ * from what it already is, and not the same as
+ * the Rev color, do it.
+ */
+ if(strcmp(rc->bg, cp->fg) && strcmp(rc->bg, cp->bg) &&
+ strcmp(rc->fg, cp->fg))
+ return(new_color_pair(cp->fg, rc->bg));
+ }
+ }
+
+ /* come here for IND_COL_FLIP and for the cases which fail the tests */
+ return(new_color_pair(cp->bg, cp->fg)); /* flip the colors */
+}
+
+
+
+#ifdef _WINDOWS
+
+/*----------------------------------------------------------------------
+ Callback to get the text of the current message. Used to display
+ a message in an alternate window.
+
+ Args: cmd - what type of scroll operation.
+ text - filled with pointer to text.
+ l - length of text.
+ style - Returns style of text. Can be:
+ GETTEXT_TEXT - Is a pointer to text with CRLF deliminated
+ lines
+ GETTEXT_LINES - Is a pointer to NULL terminated array of
+ char *. Each entry points to a line of
+ text.
+
+ this implementation always returns GETTEXT_TEXT.
+
+ Returns: TRUE - did the scroll operation.
+ FALSE - was not able to do the scroll operation.
+ ----*/
+int
+index_scroll_callback (cmd, scroll_pos)
+int cmd;
+long scroll_pos;
+{
+ int paint = TRUE;
+
+ switch (cmd) {
+ case MSWIN_KEY_SCROLLUPLINE:
+ paint = index_scroll_up (scroll_pos);
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNLINE:
+ paint = index_scroll_down (scroll_pos);
+ break;
+
+ case MSWIN_KEY_SCROLLUPPAGE:
+ paint = index_scroll_up (current_index_state->lines_per_page);
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNPAGE:
+ paint = index_scroll_down (current_index_state->lines_per_page);
+ break;
+
+ case MSWIN_KEY_SCROLLTO:
+ /* Normalize msgno in zoomed case */
+ if(any_lflagged(ps_global->msgmap, MN_HIDE | MN_CHID)){
+ long n, x;
+
+ for(n = 1L, x = 0;
+ x < scroll_pos && n < mn_get_total(ps_global->msgmap);
+ n++)
+ if(!msgline_hidden(ps_global->mail_stream, ps_global->msgmap,
+ n, 0))
+ x++;
+
+ scroll_pos = n - 1; /* list-position --> message number */
+ }
+
+ paint = index_scroll_to_pos (scroll_pos + 1);
+ break;
+ }
+
+ if(paint){
+ mswin_beginupdate();
+ update_titlebar_message();
+ update_titlebar_status();
+ redraw_index_body();
+ mswin_endupdate();
+ }
+
+ return(paint);
+}
+
+
+/*----------------------------------------------------------------------
+ MSWin scroll callback to get the text of the current message
+
+ Args: title - title for new window
+ text -
+ l -
+ style -
+
+ Returns: TRUE - got the requested text
+ FALSE - was not able to get the requested text
+ ----*/
+int
+index_gettext_callback(title, titlelen, text, l, style)
+ char *title;
+ size_t titlelen;
+ void **text;
+ long *l;
+ int *style;
+{
+ int rv = 0;
+ ENVELOPE *env;
+ BODY *body;
+ STORE_S *so;
+ gf_io_t pc;
+
+ if(mn_get_total(ps_global->msgmap) > 0L
+ && (so = so_get(CharStar, NULL, WRITE_ACCESS))){
+ gf_set_so_writec(&pc, so);
+
+ if((env = pine_mail_fetchstructure(ps_global->mail_stream,
+ mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap)),
+ &body))
+ && format_message(mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap)),
+ env, body, NULL, FM_NEW_MESS, pc)){
+ snprintf(title, titlelen, "Folder %s -- Message %ld of %ld",
+ strsquish(tmp_20k_buf + 500, SIZEOF_20KBUF-500, ps_global->cur_folder, 50),
+ mn_get_cur(ps_global->msgmap),
+ mn_get_total(ps_global->msgmap));
+ title[titlelen-1] = '\0';
+ *text = so_text(so);
+ *l = strlen((char *)so_text(so));
+ *style = GETTEXT_TEXT;
+
+ /* free alloc'd so, but preserve the text passed back to caller */
+ so->txt = (void *) NULL;
+ rv = 1;
+ }
+
+ gf_clear_so_writec(so);
+ so_give(&so);
+ }
+
+ return(rv);
+}
+
+
+/*
+ *
+ */
+int
+index_sort_callback(set, order)
+ int set;
+ long order;
+{
+ int i = 0;
+
+ if(set){
+ sort_folder(ps_global->mail_stream, ps_global->msgmap,
+ order & 0x000000ff,
+ (order & 0x00000100) != 0, SRT_VRB);
+ mswin_beginupdate();
+ update_titlebar_message();
+ update_titlebar_status();
+ redraw_index_body();
+ mswin_endupdate();
+ flush_status_messages(1);
+ }
+ else{
+ i = (int) mn_get_sort(ps_global->msgmap);
+ if(mn_get_revsort(ps_global->msgmap))
+ i |= 0x0100;
+ }
+
+ return(i);
+}
+
+
+/*
+ *
+ */
+void
+index_popup(IndexType style, MAILSTREAM *stream, MSGNO_S *msgmap, int full)
+{
+ int n = 0;
+ int view_in_new_wind_index = -1;
+ long rawno;
+ MESSAGECACHE *mc;
+ MPopup view_index_popup[32];
+ struct key_menu *km = (style == ThreadIndex)
+ ? &thread_keymenu
+ : (ps_global->mail_stream != stream)
+ ? &simple_index_keymenu
+ : &index_keymenu;
+
+ /*
+ * Loosely follow the logic in do_index_border to figure
+ * out which commands to show.
+ */
+
+ if(full){
+ if(km != &simple_index_keymenu){
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.string = (km == &thread_keymenu)
+ ? "&View Thread" : "&View";
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n++].data.val = 'V';
+ }
+
+ if(km == &index_keymenu){
+ view_in_new_wind_index = n;
+ view_index_popup[n].type = tIndex;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n++].label.string = "View in New Window";
+ }
+
+ if(km != &simple_index_keymenu)
+ view_index_popup[n++].type = tSeparator;
+
+ if(km == &thread_keymenu){
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.string = "&Delete Thread";
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n++].data.val = 'D';
+
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.string = "&UnDelete Thread";
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n++].data.val = 'U';
+ }
+ else{
+ /* Make "delete/undelete" item sensitive */
+ mc = ((rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
+ && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ if(mc && mc->deleted){
+ view_index_popup[n].label.string = "&Undelete";
+ view_index_popup[n++].data.val = 'U';
+ }
+ else{
+ view_index_popup[n].label.string = "&Delete";
+ view_index_popup[n++].data.val = 'D';
+ }
+ }
+
+ if(km == &index_keymenu && F_ON(F_ENABLE_FLAG, ps_global)){
+ view_index_popup[n].type = tSubMenu;
+ view_index_popup[n].label.string = "Flag";
+ view_index_popup[n++].data.submenu = flag_submenu(mc);
+ }
+
+ if(km == &simple_index_keymenu){
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "&Select";
+ view_index_popup[n++].data.val = 'S';
+ }
+ else{
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = (km == &thread_keymenu)
+ ? "&Save Thread" : "&Save";
+ view_index_popup[n++].data.val = 'S';
+
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = (km == &thread_keymenu)
+ ? "&Export Thread" : "&Export";
+ view_index_popup[n++].data.val = 'E';
+
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "Print";
+ view_index_popup[n++].data.val = '%';
+
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = (km == &thread_keymenu)
+ ? "&Reply To Thread" : "&Reply";
+ view_index_popup[n++].data.val = 'R';
+
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = (km == &thread_keymenu)
+ ? "&Forward Thread" : "&Forward";
+ view_index_popup[n++].data.val = 'F';
+
+ if(F_ON(F_ENABLE_BOUNCE, ps_global)){
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = (km == &thread_keymenu)
+ ? "&Bounce Thread" : "&Bounce";
+ view_index_popup[n++].data.val = 'B';
+ }
+
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "&Take Addresses";
+ view_index_popup[n++].data.val = 'T';
+
+ if(F_ON(F_ENABLE_AGG_OPS, ps_global)){
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "[Un]Select Current";
+ view_index_popup[n++].data.val = ':';
+ }
+ }
+
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "&WhereIs";
+ view_index_popup[n++].data.val = 'W';
+
+ view_index_popup[n++].type = tSeparator;
+ }
+
+ if(km == &simple_index_keymenu){
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "&Exit Select";
+ view_index_popup[n++].data.val = 'E';
+ }
+ else if(km == &index_keymenu && THREADING() && sp_viewing_a_thread(stream)){
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "Thread Index";
+ view_index_popup[n++].data.val = '<';
+ }
+ else{
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "Folder &List";
+ view_index_popup[n++].data.val = '<';
+ }
+
+ view_index_popup[n].type = tQueue;
+ view_index_popup[n].label.style = lNormal;
+ view_index_popup[n].label.string = "&Main Menu";
+ view_index_popup[n++].data.val = 'M';
+
+ view_index_popup[n].type = tTail;
+
+ if(mswin_popup(view_index_popup) == view_in_new_wind_index
+ && view_in_new_wind_index >= 0)
+ view_in_new_window();
+}
+
+
+char *
+pcpine_help_index(title)
+ char *title;
+{
+ /*
+ * Title is size 256 in pico. Put in args.
+ */
+ if(title)
+ strncpy(title, "Alpine MESSAGE INDEX Help", 256);
+
+ return(pcpine_help(h_mail_index));
+}
+
+char *
+pcpine_help_index_simple(title)
+ char *title;
+{
+ /*
+ * Title is size 256 in pico. Put in args.
+ */
+ if(title)
+ strncpy(title, "Alpine SELECT MESSAGE Help", 256);
+
+ return(pcpine_help(h_simple_index));
+}
+
+
+#include "../pico/osdep/mswin_tw.h"
+
+
+void
+view_in_new_window(void)
+{
+ char title[GETTEXT_TITLELEN+1];
+ void *text;
+ long len;
+ int format;
+ MSWIN_TEXTWINDOW *mswin_tw;
+
+ /* Launch text in alt window. */
+ if(index_gettext_callback(title, sizeof (title), &text, &len, &format)){
+ if(format == GETTEXT_TEXT)
+ mswin_tw = mswin_displaytext(title, text, (size_t) len, NULL,
+ NULL, MSWIN_DT_USEALTWINDOW);
+ else if(format == GETTEXT_LINES)
+ mswin_tw = mswin_displaytext(title, NULL, 0, text,
+ NULL, MSWIN_DT_USEALTWINDOW);
+
+ if(mswin_tw)
+ mswin_set_readonly(mswin_tw, FALSE);
+ }
+}
+
+#endif /* _WINDOWS */
diff --git a/alpine/mailindx.h b/alpine/mailindx.h
new file mode 100644
index 00000000..3b4291c2
--- /dev/null
+++ b/alpine/mailindx.h
@@ -0,0 +1,114 @@
+/*
+ * $Id: mailindx.h 770 2007-10-24 00:23:09Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_MAILINDX_INCLUDED
+#define PINE_MAILINDX_INCLUDED
+
+
+#include "../pith/mailindx.h"
+#include "context.h"
+#include "keymenu.h"
+#include "../pith/state.h"
+#include "../pith/msgno.h"
+#include "../pith/indxtype.h"
+#include "../pith/thread.h"
+#include "../pith/color.h"
+
+
+/*-----------
+ Saved state to redraw message index body
+ ----*/
+struct entry_state {
+ unsigned hilite:1;
+ unsigned bolded:1;
+ unsigned already_erased:1;
+ int plus;
+ long msgno; /* last msgno we drew */
+ unsigned long id;
+};
+
+
+struct index_state {
+ long msg_at_top,
+ lines_per_page;
+ struct entry_state *entry_state;
+ MSGNO_S *msgmap;
+ MAILSTREAM *stream;
+ IndexColType status_fld; /* field for select X's */
+ int status_col; /* column for select X's */
+ IndexColType plus_fld; /* field for threading '+' or '>' */
+ int plus_col; /* column for threading '+' or '>' */
+ IndexColType arrow_fld; /* field for cursor arrow */
+};
+
+
+#define AC_NONE 0x00 /* flags modifying apply_command */
+#define AC_FROM_THREAD 0x01 /* called from thread_command */
+#define AC_COLL 0x02 /* offer collapse command */
+#define AC_EXPN 0x04 /* offer expand command */
+#define AC_UNSEL 0x08 /* all selected, offer UnSelect */
+
+
+/*
+ * Macro to reveal horizontal scroll margin. It can be no greater than
+ * half the number of lines on the display...
+ */
+#define HS_MAX_MARGIN(p) (((p)->ttyo->screen_rows-FOOTER_ROWS(p)-3)/2)
+#define HS_MARGIN(p) MIN((p)->scroll_margin, HS_MAX_MARGIN(p))
+
+
+/*
+ * Flags to indicate how much index border to paint
+ */
+#define INDX_CLEAR 0x01
+#define INDX_HEADER 0x02
+#define INDX_FOOTER 0x04
+
+
+typedef enum {MsgIndex, MultiMsgIndex, ZoomIndex, ThreadIndex} IndexType;
+
+
+/*
+ * Macro to simplify clearing body portion of pine's display
+ */
+#define ClearBody() ClearLines(1, ps_global->ttyo->screen_rows \
+ - FOOTER_ROWS(ps_global) - 1)
+
+
+extern struct index_state *current_index_state;
+
+
+/* exported protoypes */
+void mail_index_screen(struct pine *);
+struct key_menu *do_index_border(CONTEXT_S *, char *, MAILSTREAM *, MSGNO_S *, IndexType, int *, int);
+void *stop_threading_temporarily(void);
+void restore_threading(void **);
+int index_lister(struct pine *, CONTEXT_S *, char *, MAILSTREAM *, MSGNO_S *);
+ICE_S *build_header_line(struct pine *, MAILSTREAM *, MSGNO_S *, long, int *);
+int condensed_thread_cue(PINETHRD_S *, ICE_S *, char **, size_t *, int, int);
+int truncate_subj_and_from_strings(void);
+void paint_index_hline(MAILSTREAM *, long, ICE_S *);
+void setup_index_state(int);
+void warn_other_cmds(void);
+void thread_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int);
+COLOR_PAIR *apply_rev_color(COLOR_PAIR *, int);
+#ifdef _WINDOWS
+int index_sort_callback(int, long);
+void view_in_new_window(void);
+#endif
+
+
+#endif /* PINE_MAILINDX_INCLUDED */
diff --git a/alpine/mailpart.c b/alpine/mailpart.c
new file mode 100644
index 00000000..8286d79d
--- /dev/null
+++ b/alpine/mailpart.c
@@ -0,0 +1,3981 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailpart.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ mailpart.c
+ The meat and pototoes of attachment processing here.
+
+ ====*/
+
+#include "headers.h"
+#include "mailpart.h"
+#include "status.h"
+#include "context.h"
+#include "keymenu.h"
+#include "alpine.h"
+#include "reply.h"
+#include "radio.h"
+#include "takeaddr.h"
+#include "mailview.h"
+#include "mailindx.h"
+#include "mailcmd.h"
+#include "help.h"
+#include "titlebar.h"
+#include "signal.h"
+#include "send.h"
+#include "busy.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/store.h"
+#include "../pith/msgno.h"
+#include "../pith/detach.h"
+#include "../pith/handle.h"
+#include "../pith/filter.h"
+#include "../pith/bitmap.h"
+#include "../pith/charset.h"
+#include "../pith/mimedesc.h"
+#include "../pith/mailcap.h"
+#include "../pith/newmail.h"
+#include "../pith/rfc2231.h"
+#include "../pith/flag.h"
+#include "../pith/text.h"
+#include "../pith/editorial.h"
+#include "../pith/save.h"
+#include "../pith/pipe.h"
+#include "../pith/util.h"
+#include "../pith/detoken.h"
+#include "../pith/busy.h"
+#include "../pith/mimetype.h"
+#include "../pith/icache.h"
+#include "../pith/list.h"
+#include "../pith/ablookup.h"
+#include "../pith/options.h"
+#include "../pith/smime.h"
+
+
+/*
+ * Information used to paint and maintain a line on the attachment
+ * screen.
+ */
+typedef struct atdisp_line {
+ char *dstring; /* alloc'd var value string */
+ ATTACH_S *attp; /* actual attachment pointer */
+ struct atdisp_line *next, *prev;
+} ATDISP_S;
+
+
+/*
+ * struct defining attachment screen's current state
+ */
+typedef struct att_screen {
+ ATDISP_S *current,
+ *top_line;
+ COLOR_PAIR *titlecolor;
+} ATT_SCREEN_S;
+
+static ATT_SCREEN_S *att_screen;
+
+
+#define next_attline(p) ((p) ? (p)->next : NULL)
+#define prev_attline(p) ((p) ? (p)->prev : NULL)
+
+
+#ifdef _WINDOWS
+/*
+ * local global pointer to scroll attachment popup menu
+ */
+static ATTACH_S *scrat_attachp;
+#endif
+
+
+#define FETCH_READC g_fr_desc->readc
+
+
+/* used to keep track of detached MIME segments total length */
+static long save_att_length;
+
+/*
+ * Internal Prototypes
+ */
+ATDISP_S *new_attline(ATDISP_S **);
+void free_attline(ATDISP_S **);
+int attachment_screen_updater(struct pine *, ATDISP_S *, ATT_SCREEN_S *);
+void attachment_screen_redrawer(void);
+void update_att_screen_titlebar(void);
+ATDISP_S *first_attline(ATDISP_S *);
+int init_att_progress(char *, MAILSTREAM *, BODY *);
+long save_att_piped(int);
+int save_att_percent(void);
+void save_attachment(int, long, ATTACH_S *);
+void export_attachment(int, long, ATTACH_S *);
+char *write_attached_msg(long, ATTACH_S **, STORE_S *, int);
+void save_msg_att(long, ATTACH_S *);
+void save_digest_att(long, ATTACH_S *);
+int save_msg_att_work(long int, ATTACH_S *, MAILSTREAM *, char *,
+ CONTEXT_S *, char *);
+void export_msg_att(long, ATTACH_S *);
+void export_digest_att(long, ATTACH_S *);
+void print_attachment(int, long, ATTACH_S *);
+int print_msg_att(long, ATTACH_S *, int);
+void print_digest_att(long, ATTACH_S *);
+void run_viewer(char *, BODY *, int);
+STORE_S *format_text_att(long, ATTACH_S *, HANDLE_S **);
+int display_text_att(long, ATTACH_S *, int);
+int display_msg_att(long, ATTACH_S *, int);
+void display_digest_att(long, ATTACH_S *, int);
+int scroll_attachment(char *, STORE_S *, SourceType, HANDLE_S *, ATTACH_S *, int);
+int process_attachment_cmd(int, MSGNO_S *, SCROLL_S *);
+int format_msg_att(long, ATTACH_S **, HANDLE_S **, gf_io_t, int);
+void display_vcard_att(long, ATTACH_S *, int);
+void display_attach_info(long, ATTACH_S *);
+void forward_attachment(MAILSTREAM *, long, ATTACH_S *);
+void forward_msg_att(MAILSTREAM *, long, ATTACH_S *);
+void reply_msg_att(MAILSTREAM *, long, ATTACH_S *);
+void bounce_msg_att(MAILSTREAM *, long, char *, char *);
+void pipe_attachment(long, ATTACH_S *);
+int delete_attachment(long, ATTACH_S *);
+int undelete_attachment(long, ATTACH_S *, int *);
+#ifdef _WINDOWS
+int scroll_att_popup(SCROLL_S *, int);
+void display_text_att_window(ATTACH_S *);
+void display_msg_att_window(ATTACH_S *);
+#endif
+
+
+/*----------------------------------------------------------------------
+ Provide attachments in browser for selected action
+
+ Args: ps -- pointer to pine structure
+ msgmap -- struct containing current message to display
+
+ Result:
+
+ Handle painting and navigation of attachment index. It would be nice
+ to someday handle message/rfc822 segments in a neat way (i.e., enable
+ forwarding, take address, etc.).
+ ----*/
+void
+attachment_screen(struct pine *ps)
+{
+ UCS ch = 'x';
+ int n, cmd, dline,
+ maxnumwid = 0, maxsizewid = 0, old_cols = -1, km_popped = 0, expbits,
+ last_type = TYPEOTHER;
+ long msgno;
+ char *q, *last_subtype = NULL, backtag[64], *utf8str;
+ OtherMenu what;
+ ATTACH_S *a;
+ ATDISP_S *current = NULL, *ctmp = NULL;
+ ATT_SCREEN_S screen;
+
+ ps->prev_screen = attachment_screen;
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ ps->some_quoting_was_suppressed = 0;
+
+ if(ps->ttyo->screen_rows - HEADER_ROWS(ps) - FOOTER_ROWS(ps) < 1){
+ q_status_message(SM_ORDER | SM_DING, 0, 3,
+ _("Screen too small to view attachment"));
+ ps->next_screen = mail_view_screen;
+ return;
+ }
+
+ if(mn_total_cur(ps->msgmap) > 1L){
+ q_status_message(SM_ORDER | SM_DING, 0, 3,
+ _("Can only view one message's attachments at a time."));
+ return;
+ }
+ else if(ps->atmts && ps->atmts->description && !(ps->atmts + 1)->description)
+ q_status_message1(SM_ASYNC, 0, 3,
+ _("Message %s has only one part (the message body), and no attachments."),
+ long2string(mn_get_cur(ps->msgmap)));
+
+ /*----- Figure max display widths -----*/
+ for(a = ps->atmts; a && a->description != NULL; a++){
+ if((n = utf8_width(a->number)) > maxnumwid)
+ maxnumwid = n;
+
+ if((n = utf8_width(a->size)) > maxsizewid)
+ maxsizewid = n;
+ }
+
+ /*
+ * Then, allocate and initialize attachment line list...
+ */
+ for(a = ps->atmts; a && a->description; a++)
+ new_attline(&current)->attp = a;
+
+ memset(&screen, 0, sizeof(screen));
+ msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
+ ps->mangled_screen = 1; /* build display */
+ ps->redrawer = attachment_screen_redrawer;
+ att_screen = &screen;
+ current = first_attline(current);
+ what = FirstMenu;
+
+ /*
+ * Advance to next attachment if it's likely that we've already
+ * shown it to the user...
+ */
+
+ if (current == NULL){
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ _("Malformed message: %s"),
+ ps->c_client_error ? ps->c_client_error : "?");
+ ps->next_screen = mail_view_screen;
+ }
+ else if(current->attp->shown && (ctmp = next_attline(current)))
+ current = ctmp;
+
+ while(ps->next_screen == SCREEN_FUN_NULL){
+ ps->user_says_cancel = 0;
+ if(km_popped){
+ km_popped--;
+ if(km_popped == 0){
+ clearfooter(ps);
+ ps->mangled_body = 1;
+ }
+ }
+
+ if(ps->mangled_screen){
+ /*
+ * build/rebuild display lines
+ */
+ if(old_cols != ps->ttyo->screen_cols){
+ int avail, s1, s2, s4, s5, dwid, sizewid, descwid;
+
+ avail = ps_global->ttyo->screen_cols;
+
+ s1 = MAX(MIN(1, avail), 0);
+ avail -= s1;
+
+ dwid = MAX(MIN(1, avail), 0);
+ avail -= dwid;
+
+ s2 = MAX(MIN(1, avail), 0);
+ avail -= s2;
+
+ /* Only give up a third of the display to part numbers */
+ maxnumwid = MIN(maxnumwid, (ps_global->ttyo->screen_cols/3));
+ maxnumwid = MAX(MIN(maxnumwid, avail), 0);
+ avail -= maxnumwid;
+
+ s4 = MAX(MIN(3, avail), 0);
+ avail -= s4;
+
+ sizewid = MAX(MIN(maxsizewid, avail), 0);
+ avail -= sizewid;
+
+ s5 = MAX(MIN(3, avail), 0);
+ avail -= s5;
+
+ descwid = MAX(0, avail);
+
+ old_cols = ps->ttyo->screen_cols;
+
+ for(ctmp = first_attline(current);
+ ctmp && (a = ctmp->attp) && a->description;
+ ctmp = next_attline(ctmp)){
+ size_t len;
+ char numbuf[50];
+ char description[1000];
+
+ if(ctmp->dstring)
+ fs_give((void **)&ctmp->dstring);
+
+ len = 6 * MAX(80, ps->ttyo->screen_cols) * sizeof(char);
+ ctmp->dstring = (char *)fs_get(len + 1);
+
+ /*
+ * description
+ */
+ q = a->body->description;
+ if(!(q && q[0]) && (MIME_MSG_A(a) && a->body->nested.msg->env))
+ q = a->body->nested.msg->env->subject;
+
+ if(q && q[0]){
+ char buftmp[1000];
+
+ strncpy(buftmp, q, sizeof(buftmp));
+ buftmp[sizeof(buftmp)-1] = '\0';
+
+ q = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp);
+ }
+
+ if(q && !q[0])
+ q = NULL;
+
+ snprintf(description, sizeof(description), "%s%s%s%s", type_desc(a->body->type, a->body->subtype, a->body->parameter, a->body->disposition.type ? a->body->disposition.parameter : NULL, 1), q ? ", \"" : "", q ? q : "", q ? "\"" : "");
+ description[sizeof(description)-1] = '\0';
+
+ utf8_snprintf(ctmp->dstring, len+1,
+ "%*.*s%*.*w%*.*s%-*.*w%*.*s%*.*w%*.*s%-*.*w",
+ s1, s1, "",
+ dwid, dwid,
+ msgno_part_deleted(ps->mail_stream, msgno, a->number) ? "D" : "",
+ s2, s2, "",
+ maxnumwid, maxnumwid,
+ a->number
+ ? short_str(a->number, numbuf, sizeof(numbuf), maxnumwid, FrontDots)
+ : "",
+ s4, s4, "",
+ sizewid, sizewid,
+ a->size ? a->size : "",
+ s5, s5, "",
+ descwid, descwid, description);
+
+ ctmp->dstring[len] = '\0';
+ }
+ }
+
+ ps->mangled_header = 1;
+ ps->mangled_footer = 1;
+ ps->mangled_body = 1;
+ }
+
+ /*----------- Check for new mail -----------*/
+ if(new_mail(0, NM_TIMING(ch), NM_STATUS_MSG | NM_DEFER_SORT) >= 0)
+ ps->mangled_header = 1;
+
+ /*
+ * If an expunge of the current message happened during the
+ * new mail check we want to bail out of here. See mm_expunged.
+ */
+ if(ps->next_screen != SCREEN_FUN_NULL)
+ break;
+
+ if(ps->mangled_header){
+ update_att_screen_titlebar();
+ ps->mangled_header = 0;
+ }
+
+ if(ps->mangled_screen){
+ ClearLine(1);
+ ps->mangled_screen = 0;
+ }
+
+ dline = attachment_screen_updater(ps, current, &screen);
+
+ /*---- This displays new mail notification, or errors ---*/
+ if(km_popped){
+ FOOTER_ROWS(ps) = 3;
+ mark_status_unknown();
+ }
+
+ display_message(ch);
+ if(km_popped){
+ FOOTER_ROWS(ps) = 1;
+ mark_status_unknown();
+ }
+
+ if(ps->mangled_footer
+ || current->attp->body->type != last_type
+ || !(last_subtype && current->attp->body->subtype)
+ || strucmp(last_subtype, current->attp->body->subtype)){
+
+ struct key_menu *km = &att_index_keymenu;
+ bitmap_t bitmap;
+
+ setbitmap(bitmap);
+ ps->mangled_footer = 0;
+ last_type = current->attp->body->type;
+ last_subtype = current->attp->body->subtype;
+
+ snprintf(backtag, sizeof(backtag), "Msg #%ld", mn_get_cur(ps->msgmap));
+ backtag[sizeof(backtag)-1] = '\0';
+ km->keys[ATT_PARENT_KEY].label = backtag;
+
+ if(F_OFF(F_ENABLE_PIPE, ps))
+ clrbitn(ATT_PIPE_KEY, bitmap);
+
+ /*
+ * If message or digest, leave Reply and Save and,
+ * conditionally, Bounce on...
+ */
+ if(MIME_MSG(last_type, last_subtype)){
+ if(F_OFF(F_ENABLE_BOUNCE, ps))
+ clrbitn(ATT_BOUNCE_KEY, bitmap);
+
+ km->keys[ATT_EXPORT_KEY].name = "";
+ km->keys[ATT_EXPORT_KEY].label = "";
+ }
+ else if(MIME_DGST(last_type, last_subtype)){
+ clrbitn(ATT_BOUNCE_KEY, bitmap);
+ clrbitn(ATT_REPLY_KEY, bitmap);
+
+ km->keys[ATT_EXPORT_KEY].name = "";
+ km->keys[ATT_EXPORT_KEY].label = "";
+ }
+ else{
+ clrbitn(ATT_BOUNCE_KEY, bitmap);
+ clrbitn(ATT_REPLY_KEY, bitmap);
+
+ if(last_type != TYPETEXT)
+ clrbitn(ATT_PRINT_KEY, bitmap);
+
+ km->keys[ATT_EXPORT_KEY].name = "E";
+ km->keys[ATT_EXPORT_KEY].label = N_("Export");
+ }
+
+ if(km_popped){
+ FOOTER_ROWS(ps) = 3;
+ clearfooter(ps);
+ }
+
+ if(F_ON(F_ARROW_NAV, ps)){
+ menu_add_binding(km, KEY_LEFT, MC_EXIT);
+ menu_add_binding(km, KEY_RIGHT, MC_VIEW_ATCH);
+ }
+ else{
+ menu_clear_binding(km, KEY_LEFT);
+ menu_clear_binding(km, KEY_RIGHT);
+ }
+
+ draw_keymenu(km, bitmap, ps->ttyo->screen_cols, 1-FOOTER_ROWS(ps),
+ 0, what);
+ what = SameMenu;
+ if(km_popped){
+ FOOTER_ROWS(ps) = 1;
+ mark_keymenu_dirty();
+ }
+ }
+
+ if(F_ON(F_SHOW_CURSOR, ps))
+ MoveCursor(dline, 0);
+ else
+ MoveCursor(MAX(0, ps->ttyo->screen_rows - FOOTER_ROWS(ps)), 0);
+
+ /*------ Prepare to read the command from the keyboard ----*/
+#ifdef MOUSE
+ mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); /* prime the handler */
+ register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
+ ps_global->ttyo->screen_rows -(FOOTER_ROWS(ps_global)+1),
+ ps_global->ttyo->screen_cols);
+#endif
+ ch = READ_COMMAND(&utf8str);
+#ifdef MOUSE
+ clear_mfunc(mouse_in_content);
+#endif
+
+ cmd = menu_command(ch, &att_index_keymenu);
+
+ if(km_popped)
+ switch(cmd){
+ case MC_NONE :
+ case MC_OTHER :
+ case MC_RESIZE :
+ case MC_REPAINT :
+ km_popped++;
+ break;
+
+ default:
+ clearfooter(ps);
+ break;
+ }
+
+ switch(cmd){
+ case MC_HELP :
+ if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
+ km_popped = 2;
+ ps->mangled_footer = 1;
+ break;
+ }
+
+ helper(h_attachment_screen, _("HELP FOR ATTACHMENT INDEX"), 0);
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_OTHER :
+ what = NextMenu;
+ ps->mangled_footer = 1;
+ break;
+
+ case MC_FULLHDR :
+ ps->full_header++;
+ if(ps->full_header == 1){
+ if(!(ps->quote_suppression_threshold
+ && (ps->some_quoting_was_suppressed /* || in_index != View */)))
+ ps->full_header++;
+ }
+ else if(ps->full_header > 2)
+ ps->full_header = 0;
+
+ switch(ps->full_header){
+ case 0:
+ q_status_message(SM_ORDER, 0, 3,
+ _("Display of full headers is now off."));
+ break;
+
+ case 1:
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Quotes displayed, use %s to see full headers"),
+ F_ON(F_USE_FK, ps) ? "F9" : "H");
+ break;
+
+ case 2:
+ q_status_message(SM_ORDER, 0, 3,
+ _("Display of full headers is now on."));
+ break;
+
+ }
+
+ break;
+
+ case MC_EXIT : /* exit attachment screen */
+ ps->next_screen = mail_view_screen;
+ break;
+
+ case MC_QUIT :
+ ps->next_screen = quit_screen;
+ break;
+
+ case MC_MAIN :
+ ps->next_screen = main_menu_screen;
+ break;
+
+ case MC_INDEX :
+ ps->next_screen = mail_index_screen;
+ break;
+
+ case MC_NEXTITEM :
+ if((ctmp = next_attline(current)) != NULL)
+ current = ctmp;
+ else
+ q_status_message(SM_ORDER, 0, 1, _("Already on last attachment"));
+
+ break;
+
+ case MC_PREVITEM :
+ if((ctmp = prev_attline(current)) != NULL)
+ current = ctmp;
+ else
+ q_status_message(SM_ORDER, 0, 1, _("Already on first attachment"));
+
+ break;
+
+ case MC_PAGEDN : /* page forward */
+ if(next_attline(current)){
+ while(dline++ < ps->ttyo->screen_rows - FOOTER_ROWS(ps))
+ if((ctmp = next_attline(current)) != NULL)
+ current = ctmp;
+ else
+ break;
+ }
+ else
+ q_status_message(SM_ORDER, 0, 1,
+ _("Already on last page of attachments"));
+
+ break;
+
+ case MC_PAGEUP : /* page backward */
+ if(prev_attline(current)){
+ while(dline-- > HEADER_ROWS(ps))
+ if((ctmp = prev_attline(current)) != NULL)
+ current = ctmp;
+ else
+ break;
+
+ while(++dline < ps->ttyo->screen_rows - FOOTER_ROWS(ps))
+ if((ctmp = prev_attline(current)) != NULL)
+ current = ctmp;
+ else
+ break;
+ }
+ else
+ q_status_message(SM_ORDER, 0, 1,
+ _("Already on first page of attachments"));
+
+ break;
+
+#ifdef MOUSE
+ case MC_MOUSE:
+ {
+ MOUSEPRESS mp;
+
+ mouse_get_last (NULL, &mp);
+ mp.row -= HEADER_ROWS(ps);
+ ctmp = screen.top_line;
+ while (mp.row && ctmp != NULL) {
+ --mp.row;
+ ctmp = ctmp->next;
+ }
+
+ if (ctmp != NULL){
+ current = ctmp;
+
+ if (mp.doubleclick){
+ if(mp.button == M_BUTTON_LEFT)
+ display_attachment(msgno, current->attp, DA_SAVE);
+ }
+#ifdef _WINDOWS
+ else if(mp.button == M_BUTTON_RIGHT){
+ MPopup atmt_popup[20];
+ int i = -1, exoffer = 0;
+
+ dline = attachment_screen_updater(ps,current,&screen);
+
+ if(dispatch_attachment(current->attp) != MCD_NONE){
+ atmt_popup[++i].type = tQueue;
+ atmt_popup[i].data.val = 'V';
+ atmt_popup[i].label.style = lNormal;
+ atmt_popup[i].label.string = "&View";
+
+ if(!(current->attp->can_display & MCD_EXTERNAL)
+ && (current->attp->body->type == TYPETEXT
+ || MIME_MSG_A(current->attp)
+ || MIME_DGST_A(current->attp))){
+ exoffer++;
+ atmt_popup[++i].type = tIndex;
+ atmt_popup[i].label.style = lNormal;
+ atmt_popup[i].label.string =
+ "View in New Window";
+ }
+ }
+
+ atmt_popup[++i].type = tQueue;
+ atmt_popup[i].data.val = 'S';
+ atmt_popup[i].label.style = lNormal;
+ atmt_popup[i].label.string = "&Save";
+
+ atmt_popup[++i].type = tQueue;
+ atmt_popup[i].label.style = lNormal;
+ if(current->dstring[1] == 'D'){
+ atmt_popup[i].data.val = 'U';
+ atmt_popup[i].label.string = "&Undelete";
+ }
+ else{
+ atmt_popup[i].data.val = 'D';
+ atmt_popup[i].label.string = "&Delete";
+ }
+
+ if(MIME_MSG_A(current->attp)){
+ atmt_popup[++i].type = tQueue;
+ atmt_popup[i].label.style = lNormal;
+ atmt_popup[i].label.string = "&Reply...";
+ atmt_popup[i].data.val = 'R';
+
+ atmt_popup[++i].type = tQueue;
+ atmt_popup[i].label.style = lNormal;
+ atmt_popup[i].label.string = "&Forward...";
+ atmt_popup[i].data.val = 'F';
+ }
+
+ atmt_popup[++i].type = tQueue;
+ atmt_popup[i].label.style = lNormal;
+ atmt_popup[i].label.string = "&About...";
+ atmt_popup[i].data.val = 'A';
+
+ atmt_popup[++i].type = tSeparator;
+
+ atmt_popup[++i].type = tQueue;
+ snprintf(backtag, sizeof(backtag), "View Message #%ld",
+ mn_get_cur(ps->msgmap));
+ backtag[sizeof(backtag)-1] = '\0';
+ atmt_popup[i].label.string = backtag;
+ atmt_popup[i].label.style = lNormal;
+ atmt_popup[i].data.val = '<';
+
+ atmt_popup[++i].type = tTail;
+
+ if(mswin_popup(atmt_popup) == 1 && exoffer)
+ display_att_window(current->attp);
+ }
+ }
+ else if(mp.button == M_BUTTON_RIGHT){
+ MPopup atmt_popup[2];
+
+ atmt_popup[0].type = tQueue;
+ snprintf(backtag, sizeof(backtag), "View Message #%ld",
+ mn_get_cur(ps->msgmap));
+ backtag[sizeof(backtag)-1] = '\0';
+ atmt_popup[0].label.string = backtag;
+ atmt_popup[0].label.style = lNormal;
+ atmt_popup[0].data.val = '<';
+
+ atmt_popup[1].type = tTail;
+
+ mswin_popup(atmt_popup);
+#endif
+ }
+ }
+ break;
+#endif
+
+ case MC_WHEREIS : /* whereis */
+ /*--- get string ---*/
+ {int rc, found = 0;
+ char *result = NULL, buf[64];
+ static char last[64], tmp[64];
+ HelpType help;
+
+ ps->mangled_footer = 1;
+ buf[0] = '\0';
+ snprintf(tmp, sizeof(tmp), "Word to find %s%s%s: ",
+ (last[0]) ? "[" : "",
+ (last[0]) ? last : "",
+ (last[0]) ? "]" : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ help = NO_HELP;
+ while(1){
+ int flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
+
+ rc = optionally_enter(buf,-FOOTER_ROWS(ps),0,sizeof(buf),
+ tmp,NULL,help,&flags);
+ if(rc == 3)
+ help = help == NO_HELP ? h_attach_index_whereis : NO_HELP;
+ else if(rc == 0 || rc == 1 || !buf[0]){
+ if(rc == 0 && !buf[0] && last[0]){
+ strncpy(buf, last, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ }
+
+ break;
+ }
+ }
+
+ if(rc == 0 && buf[0]){
+ ctmp = current;
+ while((ctmp = next_attline(ctmp)) != NULL)
+ if(srchstr(ctmp->dstring, buf)){
+ found++;
+ break;
+ }
+
+ if(!found){
+ ctmp = first_attline(current);
+
+ while(ctmp != current)
+ if(srchstr(ctmp->dstring, buf)){
+ found++;
+ break;
+ }
+ else
+ ctmp = next_attline(ctmp);
+ }
+ }
+ else
+ result = _("WhereIs cancelled");
+
+ if(found && ctmp){
+ strncpy(last, buf, sizeof(last));
+ last[sizeof(last)-1] = '\0';
+ result = "Word found";
+ current = ctmp;
+ }
+
+ q_status_message(SM_ORDER, 0, 3,
+ result ? result : _("Word not found"));
+ }
+
+ break;
+
+ case MC_DELETE :
+ if(delete_attachment(msgno, current->attp)){
+ int l = current ? strlen(current->attp->number) : 0;
+
+ current->dstring[1] = 'D';
+
+ /* Also indicate any children that will be deleted */
+
+ for(ctmp = current; ctmp; ctmp = next_attline(ctmp))
+ if(!strncmp(ctmp->attp->number, current->attp->number, l)
+ && ctmp->attp->number[l] == '.'){
+ ctmp->dstring[1] = 'D';
+ ps->mangled_screen = 1;
+ }
+ }
+
+ break;
+
+ case MC_UNDELETE :
+ if(undelete_attachment(msgno, current->attp, &expbits)){
+ int l = current ? strlen(current->attp->number) : 0;
+
+ current->dstring[1] = ' ';
+
+ /* And unflag anything implicitly undeleted */
+ for(ctmp = current; ctmp; ctmp = next_attline(ctmp))
+ if(!strncmp(ctmp->attp->number, current->attp->number, l)
+ && ctmp->attp->number[l] == '.'
+ && (!msgno_exceptions(ps->mail_stream, msgno,
+ ctmp->attp->number, &expbits, FALSE)
+ || !(expbits & MSG_EX_DELETE))){
+ ctmp->dstring[1] = ' ';
+ ps->mangled_screen = 1;
+ }
+ }
+
+ break;
+
+ case MC_REPLY :
+ reply_msg_att(ps->mail_stream, msgno, current->attp);
+ break;
+
+ case MC_FORWARD :
+ forward_attachment(ps->mail_stream, msgno, current->attp);
+ break;
+
+ case MC_BOUNCE :
+ bounce_msg_att(ps->mail_stream, msgno, current->attp->number,
+ current->attp->body->nested.msg->env->subject);
+ ps->mangled_footer = 1;
+ break;
+
+ case MC_ABOUTATCH :
+ display_attach_info(msgno, current->attp);
+ break;
+
+ case MC_VIEW_ATCH : /* View command */
+ display_attachment(msgno, current->attp, DA_SAVE);
+ old_cols = -1;
+ /* fall thru to repaint */
+
+ case MC_REPAINT : /* redraw */
+ case MC_RESIZE :
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_EXPORT :
+ export_attachment(-FOOTER_ROWS(ps), msgno, current->attp);
+ ps->mangled_footer = 1;
+ break;
+
+ case MC_SAVETEXT : /* Save command */
+ save_attachment(-FOOTER_ROWS(ps), msgno, current->attp);
+ ps->mangled_footer = 1;
+ break;
+
+ case MC_PRINTMSG : /* Save command */
+ print_attachment(-FOOTER_ROWS(ps), msgno, current->attp);
+ ps->mangled_footer = 1;
+ break;
+
+ case MC_PIPE : /* Pipe command */
+ if(F_ON(F_ENABLE_PIPE, ps)){
+ pipe_attachment(msgno, current->attp);
+ ps->mangled_footer = 1;
+ break;
+ } /* fall thru to complain */
+
+ case MC_NONE: /* simple timeout */
+ break;
+
+ case MC_UTF8:
+ bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps) ? "F1" : "?");
+ break;
+
+ case MC_CHARUP :
+ case MC_CHARDOWN :
+ case MC_CHARRIGHT :
+ case MC_CHARLEFT :
+ case MC_GOTOBOL :
+ case MC_GOTOEOL :
+ case MC_UNKNOWN :
+ default:
+ bogus_command(ch, F_ON(F_USE_FK, ps) ? "F1" : "?");
+
+ }
+ }
+
+ for(current = first_attline(current); current;){ /* clean up */
+ ctmp = current->next;
+ free_attline(&current);
+ current = ctmp;
+ }
+
+ if(screen.titlecolor)
+ free_color_pair(&screen.titlecolor);
+}
+
+
+/*----------------------------------------------------------------------
+ allocate and attach a fresh attachment line struct
+
+ Args: current -- display line to attache new struct to
+
+ Returns: newly alloc'd attachment display line
+ ----*/
+ATDISP_S *
+new_attline(ATDISP_S **current)
+{
+ ATDISP_S *p;
+
+ p = (ATDISP_S *)fs_get(sizeof(ATDISP_S));
+ memset((void *)p, 0, sizeof(ATDISP_S));
+ if(current){
+ if(*current){
+ p->next = (*current)->next;
+ (*current)->next = p;
+ p->prev = *current;
+ if(p->next)
+ p->next->prev = p;
+ }
+
+ *current = p;
+ }
+
+ return(p);
+}
+
+
+/*----------------------------------------------------------------------
+ Release system resources associated with attachment display line
+
+ Args: p -- line to free
+
+ Result:
+ ----*/
+void
+free_attline(ATDISP_S **p)
+{
+ if(p){
+ if((*p)->dstring)
+ fs_give((void **)&(*p)->dstring);
+
+ fs_give((void **)p);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Manage display of the attachment screen menu body.
+
+ Args: ps -- pine struct pointer
+ current -- currently selected display line
+ screen -- reference points for display painting
+
+ Result:
+ */
+int
+attachment_screen_updater(struct pine *ps, ATDISP_S *current, ATT_SCREEN_S *screen)
+{
+ int dline, return_line = HEADER_ROWS(ps);
+ ATDISP_S *top_line, *ctmp;
+
+ /* calculate top line of display */
+ dline = 0;
+ ctmp = top_line = first_attline(current);
+ do
+ if(((dline++)%(ps->ttyo->screen_rows-HEADER_ROWS(ps)-FOOTER_ROWS(ps)))==0)
+ top_line = ctmp;
+ while(ctmp != current && (ctmp = next_attline(ctmp)));
+
+#ifdef _WINDOWS
+ /* Don't know how to manage scroll bar for attachment screen yet. */
+ scroll_setrange (0L, 0L);
+#endif
+
+ /* mangled body or new page, force redraw */
+ if(ps->mangled_body || screen->top_line != top_line)
+ screen->current = NULL;
+
+ /* loop thru painting what's needed */
+ for(dline = 0, ctmp = top_line;
+ dline < ps->ttyo->screen_rows - FOOTER_ROWS(ps) - HEADER_ROWS(ps);
+ dline++, ctmp = next_attline(ctmp)){
+
+ /*
+ * only fall thru painting if something needs painting...
+ */
+ if(!(!screen->current || ctmp == screen->current || ctmp == current))
+ continue;
+
+ if(ctmp && ctmp->dstring){
+ char *p = tmp_20k_buf;
+ int i, col, x = 0, totlen;
+
+ if(F_ON(F_FORCE_LOW_SPEED,ps) || ps->low_speed){
+ x = 2;
+ if(ctmp == current){
+ return_line = dline + HEADER_ROWS(ps);
+ PutLine0(dline + HEADER_ROWS(ps), 0, "->");
+ }
+ else
+ PutLine0(dline + HEADER_ROWS(ps), 0, " ");
+
+ /*
+ * Only paint lines if we have to...
+ */
+ if(screen->current)
+ continue;
+ }
+ else if(ctmp == current){
+ return_line = dline + HEADER_ROWS(ps);
+ StartInverse();
+ }
+
+ totlen = strlen(ctmp->dstring);
+
+ /*
+ * Copy the value to a temp buffer expanding tabs.
+ * Assume the caller set the widths so as not to overflow the
+ * right margin.
+ */
+ for(i=0,col=x; ctmp->dstring[i]; i++){
+ if(ctmp->dstring[i] == TAB){
+ if((p-tmp_20k_buf) < SIZEOF_20KBUF-8)
+ do
+ *p++ = ' ';
+ while((++col)&0x07);
+ }
+ else{
+ col += width_at_this_position((unsigned char *) &ctmp->dstring[i], totlen-i);
+ if((p-tmp_20k_buf) < SIZEOF_20KBUF)
+ *p++ = ctmp->dstring[i];
+
+ }
+ }
+
+ if((p-tmp_20k_buf) < SIZEOF_20KBUF)
+ *p = '\0';
+
+ PutLine0(dline + HEADER_ROWS(ps), x, tmp_20k_buf + x);
+
+ if(ctmp == current
+ && !(F_ON(F_FORCE_LOW_SPEED,ps) || ps->low_speed))
+ EndInverse();
+ }
+ else
+ ClearLine(dline + HEADER_ROWS(ps));
+ }
+
+ ps->mangled_body = 0;
+ screen->top_line = top_line;
+ screen->current = current;
+ return(return_line);
+}
+
+
+/*----------------------------------------------------------------------
+ Redraw the attachment screen based on the global "att_screen" struct
+
+ Args: none
+
+ Result:
+ ----*/
+void
+attachment_screen_redrawer(void)
+{
+ bitmap_t bitmap;
+
+ update_att_screen_titlebar();
+ ps_global->mangled_header = 0;
+ ClearLine(1);
+
+ ps_global->mangled_body = 1;
+ (void)attachment_screen_updater(ps_global,att_screen->current,att_screen);
+
+ setbitmap(bitmap);
+ draw_keymenu(&att_index_keymenu, bitmap, ps_global->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps_global), 0, SameMenu);
+}
+
+
+void
+update_att_screen_titlebar(void)
+{
+ long raw_msgno;
+ COLOR_PAIR *returned_color = NULL;
+ COLOR_PAIR *titlecolor = NULL;
+ int colormatch;
+ SEARCHSET *ss = NULL;
+ PAT_STATE *pstate = NULL;
+
+ if(ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
+ raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
+ ss = mail_newsearchset();
+ ss->first = ss->last = (unsigned long) raw_msgno;
+
+ if(ss){
+ colormatch = get_index_line_color(ps_global->mail_stream,
+ ss, &pstate, &returned_color);
+ mail_free_searchset(&ss);
+
+ /*
+ * This is a bit tricky. If there is a colormatch but
+ * returned_color
+ * is NULL, that means that the user explicitly wanted the
+ * Normal color used in this index line, so that is what we
+ * use. If no colormatch then we will use the TITLE color
+ * instead of Normal.
+ */
+ if(colormatch){
+ if(returned_color)
+ titlecolor = returned_color;
+ else
+ titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR);
+ }
+
+ if(titlecolor
+ && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
+ char cbuf[MAXCOLORLEN+1];
+
+ strncpy(cbuf, titlecolor->fg, sizeof(cbuf));
+ cbuf[sizeof(cbuf)-1] = '\0';
+ strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
+ titlecolor->fg[MAXCOLORLEN] = '\0';
+ strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
+ titlecolor->bg[MAXCOLORLEN] = '\0';
+ }
+ }
+
+ /* Did the color change? */
+ if((!titlecolor && att_screen->titlecolor)
+ ||
+ (titlecolor && !att_screen->titlecolor)
+ ||
+ (titlecolor && att_screen->titlecolor
+ && (strcmp(titlecolor->fg, att_screen->titlecolor->fg)
+ || strcmp(titlecolor->bg, att_screen->titlecolor->bg)))){
+
+ if(att_screen->titlecolor)
+ free_color_pair(&att_screen->titlecolor);
+
+ att_screen->titlecolor = titlecolor;
+ titlecolor = NULL;
+ }
+
+ if(titlecolor)
+ free_color_pair(&titlecolor);
+ }
+
+ set_titlebar(_("ATTACHMENT INDEX"), ps_global->mail_stream,
+ ps_global->context_current, ps_global->cur_folder,
+ ps_global->msgmap, 1, MessageNumber, 0, 0,
+ att_screen->titlecolor);
+}
+
+
+/*----------------------------------------------------------------------
+ Seek back from the given display line to the beginning of the list
+
+ Args: p -- display linked list
+
+ Result:
+ ----*/
+ATDISP_S *
+first_attline(ATDISP_S *p)
+{
+ while(p && p->prev)
+ p = p->prev;
+
+ return(p);
+}
+
+
+int
+init_att_progress(char *msg, MAILSTREAM *stream, struct mail_bodystruct *body)
+{
+ if((save_att_length = body->size.bytes) != 0){
+ /* if there are display filters, factor in extra copy */
+ if(body->type == TYPETEXT && ps_global->VAR_DISPLAY_FILTERS)
+ save_att_length += body->size.bytes;
+
+ /* if remote folder and segment not cached, factor in IMAP fetch */
+ if(stream && stream->mailbox && IS_REMOTE(stream->mailbox)
+ && !((body->type == TYPETEXT && body->contents.text.data)
+ || ((body->type == TYPEMESSAGE)
+ && body->nested.msg && body->nested.msg->text.text.data)
+ || body->contents.text.data))
+ save_att_length += body->size.bytes;
+
+ gf_filter_init(); /* reset counters */
+ pine_gets_bytes(1);
+ save_att_piped(1);
+ return(busy_cue(msg, save_att_percent, 0));
+ }
+
+ return(0);
+}
+
+
+long
+save_att_piped(int reset)
+{
+ static long x;
+ long y;
+
+ if(reset){
+ x = y = 0L;
+ }
+ else if((y = gf_bytes_piped()) >= x){
+ x = y;
+ y = 0;
+ }
+
+ return(x + y);
+}
+
+
+int
+save_att_percent(void)
+{
+ int i = (int) (((pine_gets_bytes(0) + save_att_piped(0)) * 100)
+ / save_att_length);
+ return(MIN(i, 100));
+}
+
+
+/*----------------------------------------------------------------------
+ Save the given attachment associated with the given message no
+
+ Args: ps
+
+ Result:
+ ----*/
+void
+save_attachment(int qline, long int msgno, ATTACH_S *a)
+{
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ "Alpine demo can't save attachments");
+ return;
+ }
+
+ if(MIME_MSG_A(a))
+ save_msg_att(msgno, a);
+ else if(MIME_DGST_A(a))
+ save_digest_att(msgno, a);
+ else if(MIME_VCARD_A(a))
+ save_vcard_att(ps_global, qline, msgno, a);
+ else
+ write_attachment(qline, msgno, a, "SAVE");
+}
+
+
+/*----------------------------------------------------------------------
+ Save the given attachment associated with the given message no
+
+ Args: ps
+
+ Result:
+ ----*/
+void
+export_attachment(int qline, long int msgno, ATTACH_S *a)
+{
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ "Alpine demo can't export attachments");
+ return;
+ }
+
+ if(MIME_MSG_A(a))
+ export_msg_att(msgno, a);
+ else if(MIME_DGST_A(a))
+ export_digest_att(msgno, a);
+ else
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Can't Export %s. Use \"Save\" to write file, \"<\" to leave index."),
+ body_type_names(a->body->type));
+}
+
+
+void
+write_attachment(int qline, long int msgno, ATTACH_S *a, char *method)
+{
+ char filename[MAXPATH+1], full_filename[MAXPATH+1],
+ title_buf[64], *err;
+ int r, rflags = GER_NONE, we_cancel = 0;
+ static HISTORY_S *history = NULL;
+ static ESCKEY_S att_save_opts[] = {
+ {ctrl('T'), 10, "^T", N_("To Files")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}};
+
+ /*------- Figure out suggested file name ----*/
+ filename[0] = full_filename[0] = '\0';
+ (void) get_filename_parameter(filename, sizeof(filename), a->body, NULL);
+
+ dprint((9, "export_attachment(name: %s)\n",
+ filename ? filename : "?"));
+
+ r = 0;
+#if !defined(DOS) && !defined(MAC) && !defined(OS2)
+ if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
+ att_save_opts[++r].ch = ctrl('V');
+ att_save_opts[r].rval = 12;
+ att_save_opts[r].name = "^V";
+ att_save_opts[r].label = N_("Downld Msg");
+ }
+#endif /* !(DOS || MAC) */
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ att_save_opts[++r].ch = ctrl('I');
+ att_save_opts[r].rval = 11;
+ att_save_opts[r].name = "TAB";
+ att_save_opts[r].label = N_("Complete");
+ }
+
+ att_save_opts[++r].ch = -1;
+
+ snprintf(title_buf, sizeof(title_buf), "%s ATTACHMENT", method);
+ title_buf[sizeof(title_buf)-1] = '\0';
+
+ r = get_export_filename(ps_global, filename, NULL, full_filename,
+ sizeof(filename), "attachment", title_buf,
+ att_save_opts, &rflags, qline, GE_SEQ_SENSITIVE, &history);
+
+ if(r < 0){
+ switch(r){
+ case -1:
+ cmd_cancelled((char *) lcase((unsigned char *) title_buf + 1) - 1);
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't save to file outside of %s"),
+ ps_global->VAR_OPER_DIR);
+ break;
+ }
+
+ return;
+ }
+#if !defined(DOS) && !defined(MAC) && !defined(OS2)
+ else if(r == 12){ /* Download */
+ char cmd[MAXPATH], *tfp = NULL;
+ PIPE_S *syspipe;
+ gf_io_t pc;
+ long len;
+ STORE_S *store;
+ char prompt_buf[256];
+
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Download disallowed in restricted mode");
+ return;
+ }
+
+ err = NULL;
+ tfp = temp_nam(NULL, "pd");
+ dprint((1, "Download attachment called!\n"));
+ if((store = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
+
+ snprintf(prompt_buf, sizeof(prompt_buf), "Saving to \"%s\"", tfp);
+ prompt_buf[sizeof(prompt_buf)-1] = '\0';
+ we_cancel = init_att_progress(prompt_buf,
+ ps_global->mail_stream,
+ a->body);
+
+ gf_set_so_writec(&pc, store);
+ if((err = detach(ps_global->mail_stream, msgno,
+ a->number, 0L, &len, pc, NULL, 0)) != NULL)
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ "%s: Error writing attachment to \"%s\"",
+ err, tfp);
+
+ /* cancel regardless, so it doesn't get in way of xfer */
+ cancel_busy_cue(0);
+
+ gf_clear_so_writec(store);
+ if(so_give(&store)) /* close file */
+ err = "Error writing tempfile for download";
+
+ if(!err){
+ build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
+ ps_global->VAR_DOWNLOAD_CMD, tfp);
+ if((syspipe = open_system_pipe(cmd, NULL, NULL,
+ PIPE_USER | PIPE_RESET,
+ 0, pipe_callback, pipe_report_error)) != NULL)
+ (void)close_system_pipe(&syspipe, NULL, pipe_callback);
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ err = "Error running download command");
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ err = "Error building temp file for download");
+
+ if(tfp){
+ our_unlink(tfp);
+ fs_give((void **)&tfp);
+ }
+
+ if(!err)
+ q_status_message1(SM_ORDER, 0, 4, "Part %s downloaded",
+ a->number);
+
+ return;
+ }
+#endif /* !(DOS || MAC) */
+
+ (void) write_attachment_to_file(ps_global->mail_stream, msgno, a,
+ rflags, full_filename);
+}
+
+
+/*
+ * Args stream --
+ * msgno -- raw message number
+ * a -- attachment struct
+ * flags -- comes from get_export_filename
+ * GER_OVER -- the file was truncated before we wrote
+ * GER_APPEND -- the file was not truncated prior to our writing,
+ * so we were appending
+ * else -- the file didn't previously exist
+ * file -- the full pathname of the file
+ *
+ * Returns 1 for successful write, 0 for nothing to write, -1 for error
+ */
+int
+write_attachment_to_file(MAILSTREAM *stream, long int msgno, ATTACH_S *a, int flags, char *file)
+{
+ char *l_string, sbuf[256], *err;
+ int is_text, we_cancel = 0;
+ long len, orig_size;
+ gf_io_t pc;
+ STORE_S *store;
+
+ if(!(a && a->body && a->number && a->number[0] && file && file[0]
+ && stream))
+ return 0;
+
+ is_text = (a && a->body && a->body->type == TYPETEXT);
+
+ if(flags & GER_APPEND)
+ orig_size = name_file_size(file);
+
+ store = so_get(FileStar, file, WRITE_ACCESS | (is_text ? WRITE_TO_LOCALE : 0));
+ if(store == NULL){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ /* TRANSLATORS: Error opening destination <filename>: <error text> */
+ _("Error opening destination %s: %s"),
+ file, error_description(errno));
+ return -1;
+ }
+
+ snprintf(sbuf, sizeof(sbuf), "Saving to \"%s\"", file);
+ sbuf[sizeof(sbuf)-1] = '\0';
+ we_cancel = init_att_progress(sbuf, stream, a->body);
+
+ gf_set_so_writec(&pc, store);
+ err = detach(stream, msgno, a->number, 0L, &len, pc, NULL, 0);
+ gf_clear_so_writec(store);
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ if(so_give(&store)) /* close file */
+ err = error_description(errno);
+
+ if(err){
+ if(!(flags & (GER_APPEND | GER_OVER)))
+ our_unlink(file);
+ else
+ our_truncate(file, (flags & GER_APPEND) ? (off_t) orig_size : (off_t) 0);
+
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ /* TRANSLATORS: <error text>: Error writing attachment to <filename> */
+ _("%s: Error writing attachment to \"%s\""),
+ err, file);
+ return -1;
+ }
+ else{
+ l_string = cpystr(byte_string(len));
+ q_status_message8(SM_ORDER, 0, 4,
+ "Part %s, %s%s %s to \"%s\"%s%s%s",
+ a->number,
+ is_text
+ ? comatose(a->body->size.lines) : l_string,
+ is_text ? " lines" : "",
+ flags & GER_OVER
+ ? "overwritten"
+ : flags & GER_APPEND ? "appended" : "written",
+ file,
+ (is_text || len == a->body->size.bytes)
+ ? "" : "(decoded from ",
+ (is_text || len == a->body->size.bytes)
+ ? "" : byte_string(a->body->size.bytes),
+ is_text || len == a->body->size.bytes
+ ? "" : ")");
+ fs_give((void **)&l_string);
+ return 1;
+ }
+}
+
+
+char *
+write_attached_msg(long int msgno, ATTACH_S **ap, STORE_S *store, int newfile)
+{
+ char *err = NULL;
+ long start_of_append;
+ gf_io_t pc;
+ MESSAGECACHE *mc;
+
+ if(ap && *ap && (*ap)->body && (*ap)->body->nested.msg
+ && (*ap)->body->nested.msg->env){
+ start_of_append = so_tell(store);
+
+ gf_set_so_writec(&pc, store);
+ if(!(ps_global->mail_stream && msgno > 0L
+ && msgno <= ps_global->mail_stream->nmsgs
+ && (mc = mail_elt(ps_global->mail_stream, msgno)) && mc->valid))
+ mc = NULL;
+
+ if(!bezerk_delimiter((*ap)->body->nested.msg->env, mc, pc, newfile)
+ || !format_msg_att(msgno, ap, NULL, pc, FM_NOINDENT))
+ err = error_description(errno);
+
+ gf_clear_so_writec(store);
+
+ if(err)
+ ftruncate(fileno((FILE *)store->txt), (off_t) start_of_append);
+ }
+ else
+ err = "Can't export message. Missing Envelope data";
+
+ return(err);
+}
+
+
+/*----------------------------------------------------------------------
+ Save the attachment message/rfc822 to specified folder
+
+ Args:
+
+ Result:
+ ----*/
+void
+save_msg_att(long int msgno, ATTACH_S *a)
+{
+ char newfolder[MAILTMPLEN], *save_folder, *flags = NULL;
+ char date[64], nmsgs[80];
+ CONTEXT_S *cntxt = NULL;
+ int our_stream = 0, rv;
+ MAILSTREAM *save_stream;
+ MESSAGECACHE *mc;
+
+ snprintf(nmsgs, sizeof(nmsgs), _("Attached Msg (part %s) "), a->number);
+ nmsgs[sizeof(nmsgs)-1] = '\0';
+ if(save_prompt(ps_global, &cntxt, newfolder, sizeof(newfolder), nmsgs,
+ a->body->nested.msg->env, msgno, a->number, NULL, NULL)){
+ if(strucmp(newfolder, ps_global->inbox_name) == 0){
+ save_folder = ps_global->VAR_INBOX_PATH;
+ cntxt = NULL;
+ }
+ else
+ save_folder = newfolder;
+
+ save_stream = save_msg_stream(cntxt, save_folder, &our_stream);
+
+ mc = (msgno > 0L && ps_global->mail_stream
+ && msgno <= ps_global->mail_stream->nmsgs)
+ ? mail_elt(ps_global->mail_stream, msgno) : NULL;
+ flags = flag_string(ps_global->mail_stream, msgno, F_ANS|F_FLAG|F_SEEN|F_KEYWORD);
+ if(mc && mc->day)
+ mail_date(date, mc);
+ else
+ *date = '\0';
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
+
+ rv = save_msg_att_work(msgno, a, save_stream, save_folder, cntxt, date);
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
+
+ if(flags)
+ fs_give((void **) &flags);
+
+ if(rv == 1)
+ q_status_message2(SM_ORDER, 0, 4,
+ _("Attached message (part %s) saved to \"%s\""),
+ a->number,
+ save_folder);
+ else if(rv == -1)
+ cmd_cancelled("Attached message Save");
+ /* else whatever broke in save_fetch_append shoulda bitched */
+
+ if(our_stream)
+ mail_close(save_stream);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Save the message/rfc822 in the given digest to the specified folder
+
+ Args:
+
+ Result:
+ ----*/
+void
+save_digest_att(long int msgno, ATTACH_S *a)
+{
+ char newfolder[MAILTMPLEN], *save_folder,
+ date[64], nmsgs[80];
+ CONTEXT_S *cntxt = NULL;
+ int our_stream = 0, rv, cnt = 0;
+ MAILSTREAM *save_stream;
+ ATTACH_S *ap;
+
+ for(ap = a + 1;
+ ap->description
+ && !strncmp(a->number, ap->number, strlen(a->number));
+ ap++)
+ if(MIME_MSG(ap->body->type, ap->body->subtype))
+ cnt++;
+
+ snprintf(nmsgs, sizeof(nmsgs), "%d Msg Digest (part %s) ", cnt, a->number);
+ nmsgs[sizeof(nmsgs)-1] = '\0';
+
+ if(save_prompt(ps_global, &cntxt, newfolder, sizeof(newfolder),
+ nmsgs, NULL, 0, NULL, NULL, NULL)){
+ save_folder = (strucmp(newfolder, ps_global->inbox_name) == 0)
+ ? ps_global->VAR_INBOX_PATH : newfolder;
+
+ save_stream = save_msg_stream(cntxt, save_folder, &our_stream);
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
+
+ for(ap = a + 1;
+ ap->description
+ && !strncmp(a->number, ap->number, strlen(a->number));
+ ap++)
+ if(MIME_MSG(ap->body->type, ap->body->subtype)){
+ *date = '\0';
+ rv = save_msg_att_work(msgno, ap, save_stream, save_folder, cntxt, date);
+ if(rv != 1)
+ break;
+ }
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
+
+ if(rv == 1)
+ q_status_message2(SM_ORDER, 0, 4,
+ _("Attached digest (part %s) saved to \"%s\""),
+ a->number,
+ save_folder);
+ else if(rv == -1)
+ cmd_cancelled("Attached digest Save");
+ /* else whatever broke in save_fetch_append shoulda bitched */
+
+ if(our_stream)
+ mail_close(save_stream);
+ }
+}
+
+
+int
+save_msg_att_work(long int msgno, ATTACH_S *a, MAILSTREAM *save_stream,
+ char *save_folder, CONTEXT_S *cntxt, char *date)
+{
+ STORE_S *so;
+ int rv = 0;
+
+ if(a && a->body && MIME_MSG(a->body->type, a->body->subtype)){
+ if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
+ *date = '\0';
+ rv = save_fetch_append(ps_global->mail_stream, msgno,
+ a->number,
+ save_stream, save_folder, cntxt,
+ a->body->size.bytes,
+ NULL, date, so);
+ }
+ else{
+ dprint((1, "Can't allocate store for save: %s\n",
+ error_description(errno)));
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Problem creating space for message text."));
+ rv = 0;
+ }
+ }
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Export the attachment message/rfc822 to specified file
+
+ Args:
+
+ Result:
+ ----*/
+void
+export_msg_att(long int msgno, ATTACH_S *a)
+{
+ char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
+ int rv, rflags = GER_NONE, i = 1;
+ ATTACH_S *ap = a;
+ STORE_S *store;
+ static HISTORY_S *history = NULL;
+ static ESCKEY_S opts[] = {
+ {ctrl('T'), 10, "^T", N_("To Files")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}};
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ opts[i].ch = ctrl('I');
+ opts[i].rval = 11;
+ opts[i].name = "TAB";
+ opts[i].label = N_("Complete");
+ }
+
+ filename[0] = full_filename[0] = '\0';
+
+ rv = get_export_filename(ps_global, filename, NULL, full_filename,
+ sizeof(filename), "msg attachment",
+ /* TRANSLATORS: Message Attachment (a screen title) */
+ _("MSG ATTACHMENT"), opts,
+ &rflags, -FOOTER_ROWS(ps_global),
+ GE_IS_EXPORT | GE_SEQ_SENSITIVE, &history);
+
+ if(rv < 0){
+ switch(rv){
+ case -1:
+ cmd_cancelled("Export");
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't export to file outside of %s"),
+ ps_global->VAR_OPER_DIR);
+ break;
+ }
+
+ return;
+ }
+
+ /* With name in hand, allocate storage object and save away... */
+ if((store = so_get(FileStar, full_filename, WRITE_ACCESS)) != NULL){
+ if((err = write_attached_msg(msgno, &ap, store, !(rflags & GER_APPEND))) != NULL)
+ q_status_message(SM_ORDER | SM_DING, 3, 4, err);
+ else
+ q_status_message3(SM_ORDER, 0, 4,
+ _("Attached message (part %s) %s to \"%s\""),
+ a->number,
+ rflags & GER_OVER
+ ? _("overwritten")
+ : rflags & GER_APPEND ? _("appended") : _("written"),
+ full_filename);
+
+ if(so_give(&store))
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error writing %s: %s"),
+ full_filename, error_description(errno));
+ }
+ else
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: Error opening file <filename> to export message: <error text> */
+ _("Error opening file \"%s\" to export message: %s"),
+ full_filename, error_description(errno));
+}
+
+
+/*----------------------------------------------------------------------
+ Export the message/rfc822 in the given digest to the specified file
+
+ Args:
+
+ Result:
+ ----*/
+void
+export_digest_att(long int msgno, ATTACH_S *a)
+{
+ char filename[MAXPATH+1], full_filename[MAXPATH+1], *err = NULL;
+ int rv, rflags = GER_NONE, i = 1;
+ long count = 0L;
+ ATTACH_S *ap;
+ static HISTORY_S *history = NULL;
+ STORE_S *store;
+ static ESCKEY_S opts[] = {
+ {ctrl('T'), 10, "^T", N_("To Files")},
+ {-1, 0, NULL, NULL},
+ {-1, 0, NULL, NULL}};
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ opts[i].ch = ctrl('I');
+ opts[i].rval = 11;
+ opts[i].name = "TAB";
+ opts[i].label = N_("Complete");
+ }
+
+ filename[0] = full_filename[0] = '\0';
+
+ rv = get_export_filename(ps_global, filename, NULL, full_filename,
+ sizeof(filename), "digest", _("DIGEST ATTACHMENT"),
+ opts, &rflags, -FOOTER_ROWS(ps_global),
+ GE_IS_EXPORT | GE_SEQ_SENSITIVE, &history);
+
+ if(rv < 0){
+ switch(rv){
+ case -1:
+ cmd_cancelled("Export");
+ break;
+
+ case -2:
+ q_status_message1(SM_ORDER, 0, 2,
+ _("Can't export to file outside of %s"),
+ ps_global->VAR_OPER_DIR);
+ break;
+ }
+
+ return;
+ }
+
+ /* With name in hand, allocate storage object and save away... */
+ if((store = so_get(FileStar, full_filename, WRITE_ACCESS)) != NULL){
+ count = 0;
+
+ for(ap = a + 1;
+ ap->description
+ && !strncmp(a->number, ap->number, strlen(a->number))
+ && !err;
+ ap++){
+ if(MIME_MSG(ap->body->type, ap->body->subtype)){
+ count++;
+ err = write_attached_msg(msgno, &ap, store,
+ !count && !(rflags & GER_APPEND));
+ }
+ }
+
+ if(so_give(&store))
+ err = error_description(errno);
+
+ if(err){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Error exporting: %s"), err);
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("%s messages exported before error occurred"), err);
+ }
+ else
+ q_status_message4(SM_ORDER, 0, 4,
+ "%s messages in digest (part %s) %s to \"%s\"",
+ long2string(count),
+ a->number,
+ rflags & GER_OVER
+ ? "overwritten"
+ : rflags & GER_APPEND ? "appended" : "written",
+ full_filename);
+ }
+ else
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error opening file \"%s\" to export digest: %s"),
+ full_filename, error_description(errno));
+}
+
+
+/*----------------------------------------------------------------------
+ Print the given attachment associated with the given message no
+
+ Args: ps
+
+ Result:
+ ----*/
+void
+print_attachment(int qline, long int msgno, ATTACH_S *a)
+{
+ char prompt[250];
+
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ "Alpine demo can't Print attachments");
+ return;
+ }
+
+ snprintf(prompt, sizeof(prompt), "attach%s %s",
+ (a->body->type == TYPETEXT) ? "ment" : "ed message",
+ MIME_DGST_A(a) ? "digest" : a->number);
+ prompt[sizeof(prompt)-1] = '\0';
+ if(open_printer(prompt) >= 0){
+ if(MIME_MSG_A(a))
+ (void) print_msg_att(msgno, a, 1);
+ else if(MIME_DGST_A(a))
+ print_digest_att(msgno, a);
+ else
+ (void) decode_text(a, msgno, print_char, NULL, QStatus, FM_NOINDENT);
+
+ close_printer();
+ }
+}
+
+
+/*
+ * Print the attachment message/rfc822 to specified file
+ *
+ * Returns 1 on success, 0 on failure.
+ */
+int
+print_msg_att(long int msgno, ATTACH_S *a, int first)
+{
+ ATTACH_S *ap = a;
+ MESSAGECACHE *mc;
+
+ if(!(ps_global->mail_stream && msgno > 0L
+ && msgno <= ps_global->mail_stream->nmsgs
+ && (mc = mail_elt(ps_global->mail_stream, msgno)) && mc->valid))
+ mc = NULL;
+
+ if(((!first && F_ON(F_AGG_PRINT_FF, ps_global)) ? print_char(FORMFEED) : 1)
+ && pine_mail_fetchstructure(ps_global->mail_stream, msgno, NULL)
+ && (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
+ ? bezerk_delimiter(a->body->nested.msg->env, mc, print_char, !first)
+ : 1)
+ && format_msg_att(msgno, &ap, NULL, print_char, FM_NOINDENT))
+ return(1);
+
+
+ q_status_message2(SM_ORDER | SM_DING, 3, 3,
+ _("Error printing message %s, part %s"),
+ long2string(msgno), a->number);
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Print the attachment message/rfc822 to specified file
+
+ Args:
+
+ Result:
+ ----*/
+void
+print_digest_att(long int msgno, ATTACH_S *a)
+{
+ ATTACH_S *ap;
+ int next = 0;
+
+ for(ap = a + 1;
+ ap->description
+ && !strncmp(a->number, ap->number, strlen(a->number));
+ ap++){
+ if(MIME_MSG(ap->body->type, ap->body->subtype)){
+ char *p = part_desc(ap->number, ap->body->nested.msg->body,
+ 0, 80, FM_NOINDENT, print_char);
+ if(p){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Can't print digest: %s"), p);
+ break;
+ }
+ else if(!print_msg_att(msgno, ap, !next))
+ break;
+
+ next++;
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Unpack and display the given attachment associated with given message no.
+
+ Args: msgno -- message no attachment is part of
+ a -- attachment to display
+
+ Returns: 0 on success, non-zero (and error message queued) otherwise
+ ----*/
+int
+display_attachment(long int msgno, ATTACH_S *a, int flags)
+{
+ char *filename = NULL;
+ char sender_filename[1000];
+ char *extp = NULL;
+ STORE_S *store;
+ gf_io_t pc;
+ char *err;
+ int we_cancel = 0, rv;
+ char prefix[70];
+ char ext[32];
+ char mtype[128];
+
+ /*------- Display the attachment -------*/
+ if(dispatch_attachment(a) == MCD_NONE){
+ /*----- Can't display this type ------*/
+ if(a->body->encoding < ENCOTHER)
+ q_status_message4(SM_ORDER | SM_DING, 3, 5,
+ /* TRANSLATORS: Don't know how to display <certain type> attachments. <might say Try Save.> */
+ _("Don't know how to display %s%s%s attachments.%s"),
+ body_type_names(a->body->type),
+ a->body->subtype ? "/" : "",
+ a->body->subtype ? a->body->subtype :"",
+ (flags & DA_SAVE) ? _(" Try Save.") : "");
+ else
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Don't know how to unpack \"%s\" encoding"),
+ body_encodings[(a->body->encoding <= ENCMAX)
+ ? a->body->encoding : ENCOTHER]);
+
+ return(1);
+ }
+ else if(!(a->can_display & MCD_EXTERNAL)){
+ if(a->body->type == TYPEMULTIPART){
+ if(a->body->subtype){
+ if(!strucmp(a->body->subtype, "digest"))
+ display_digest_att(msgno, a, flags);
+ else
+ q_status_message1(SM_ORDER, 3, 5,
+ _("Can't display Multipart/%s"),
+ a->body->subtype);
+ }
+ else
+ q_status_message(SM_ORDER, 3, 5,
+ _("Can't display unknown Multipart Subtype"));
+ }
+ else if(MIME_VCARD_A(a))
+ display_vcard_att(msgno, a, flags);
+ else if(a->body->type == TYPETEXT){
+ do{
+ rv = display_text_att(msgno, a, flags);
+ } while(rv == MC_FULLHDR);
+ }
+ else if(a->body->type == TYPEMESSAGE){
+ do{
+ rv = display_msg_att(msgno, a, flags);
+ } while(rv == MC_FULLHDR);
+ }
+
+ ps_global->mangled_screen = 1;
+ return(0);
+ }
+
+ /* arrive here if MCD_EXTERNAL */
+
+ if(F_OFF(F_QUELL_ATTACH_EXTRA_PROMPT, ps_global)
+ && (!(flags & DA_DIDPROMPT)))
+ if(want_to(_("View selected Attachment"), 'y',
+ 0, NO_HELP, WT_NORM) == 'n'){
+ cmd_cancelled(NULL);
+ return(1);
+ }
+
+ sender_filename[0] = '\0';
+ ext[0] = '\0';
+ prefix[0] = '\0';
+
+ if(F_OFF(F_QUELL_ATTACH_EXT_WARN, ps_global)
+ && (a->can_display & MCD_EXT_PROMPT)){
+ char prompt[256];
+
+ (void) get_filename_parameter(sender_filename, sizeof(sender_filename),
+ a->body, &extp);
+ snprintf(prompt, sizeof(prompt),
+ "Attachment %s%s unrecognized. %s%s%s",
+ a->body->subtype,
+ strlen(a->body->subtype) > 12 ? "..." : "",
+ (extp && extp[0]) ? "Try open by file extension (." : "Try opening anyway",
+ (extp && extp[0]) ? extp : "",
+ (extp && extp[0]) ? ")" : "");
+
+ if(want_to(prompt, 'n', 0, NO_HELP, WT_NORM) == 'n'){
+ cmd_cancelled(NULL);
+ return(1);
+ }
+ }
+
+ /*------ Write the image to a temporary file ------*/
+
+ /* create type/subtype in mtype */
+ strncpy(mtype, body_type_names(a->body->type), sizeof(mtype));
+ mtype[sizeof(mtype)-1] = '\0';
+ if(a->body->subtype){
+ strncat(mtype, "/", sizeof(mtype)-strlen(mtype)-1);
+ mtype[sizeof(mtype)-1] = '\0';
+ strncat(mtype, a->body->subtype, sizeof(mtype)-strlen(mtype)-1);
+ mtype[sizeof(mtype)-1] = '\0';
+ }
+
+ /*
+ * If we haven't already gotten the filename parameter, get it
+ * now. It may be used in the temporary filename and possibly
+ * for its extension.
+ */
+ if(!sender_filename[0])
+ (void) get_filename_parameter(sender_filename, sizeof(sender_filename),
+ a->body, &extp);
+
+ if(!set_mime_extension_by_type(ext, mtype)){ /* extension from type */
+ if(extp && extp[0]){ /* extension from filename */
+ strncpy(ext, extp, sizeof(ext));
+ ext[sizeof(ext)-1] = '\0';
+ }
+ }
+
+ /* create a temp file */
+ if(sender_filename){
+ char *p, *q = NULL;
+
+ /* get rid of any extension */
+ if(mt_get_file_ext(sender_filename, &q) && q && q > sender_filename)
+ *(q-1) = '\0';
+
+ /* be careful about what is allowed in the filename */
+ for(p = sender_filename; *p; p++)
+ if(!(isascii((unsigned char) *p)
+ && (isalnum((unsigned char) *p)
+ || *p == '-' || *p == '_' || *p == '+' || *p == '.' || *p == '=')))
+ break;
+
+ if(!*p) /* filename was ok to use */
+ snprintf(prefix, sizeof(prefix), "img-%s-", sender_filename);
+ }
+
+ /* didn't get it yet */
+ if(!prefix[0]){
+ snprintf(prefix, sizeof(prefix), "img-%s-", (a->body->subtype)
+ ? a->body->subtype : "unk");
+ }
+
+ filename = temp_nam_ext(NULL, prefix, ext);
+
+ if(!filename){
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error \"%s\", Can't create temporary file"),
+ error_description(errno));
+ return(1);
+ }
+
+ if((store = so_get(FileStar, filename, WRITE_ACCESS|OWNER_ONLY)) == NULL){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error \"%s\", Can't write file %s"),
+ error_description(errno), filename);
+ if(filename){
+ our_unlink(filename);
+ fs_give((void **)&filename);
+ }
+
+ return(1);
+ }
+
+
+ if(a->body->size.bytes){
+ char msg_buf[128];
+
+ snprintf(msg_buf, sizeof(msg_buf), "Decoding %s%s%s%s",
+ a->description ? "\"" : "",
+ a->description ? a->description : "attachment number ",
+ a->description ? "" : a->number,
+ a->description ? "\"" : "");
+ msg_buf[sizeof(msg_buf)-1] = '\0';
+ we_cancel = init_att_progress(msg_buf, ps_global->mail_stream,
+ a->body);
+ }
+
+ if(a->body->type == TYPEMULTIPART){
+ char *h = mail_fetch_mime(ps_global->mail_stream, msgno, a->number,
+ NULL, 0L);
+
+ /* Write to store while converting newlines */
+ while(h && *h)
+ if(*h == '\015' && *(h+1) == '\012'){
+ so_puts(store, NEWLINE);
+ h += 2;
+ }
+ else
+ so_writec(*h++, store);
+ }
+
+ gf_set_so_writec(&pc, store);
+
+ err = detach(ps_global->mail_stream, msgno, a->number, 0L, NULL, pc, NULL, 0);
+
+ gf_clear_so_writec(store);
+
+ if(we_cancel)
+ cancel_busy_cue(0);
+
+ so_give(&store);
+
+ if(err){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ "%s: Error saving image to temp file %s",
+ err, filename);
+ if(filename){
+ our_unlink(filename);
+ fs_give((void **)&filename);
+ }
+
+ return(1);
+ }
+
+ /*----- Run the viewer process ----*/
+ run_viewer(filename, a->body, a->can_display & MCD_EXT_PROMPT);
+ if(filename)
+ fs_give((void **)&filename);
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Fish the required command from mailcap and run it
+
+ Args: image_file -- The name of the file to pass viewer
+ body -- body struct containing type/subtype of part
+
+A side effect may be that scrolltool is called as well if
+exec_mailcap_cmd has any substantial output...
+ ----*/
+void
+run_viewer(char *image_file, struct mail_bodystruct *body, int chk_extension)
+{
+ MCAP_CMD_S *mc_cmd = NULL;
+ int needs_terminal = 0, we_cancel = 0;
+
+ we_cancel = busy_cue("Displaying attachment", NULL, 0);
+
+ if((mc_cmd = mailcap_build_command(body->type, body->subtype,
+ body, image_file,
+ &needs_terminal, chk_extension)) != NULL){
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ exec_mailcap_cmd(mc_cmd, image_file, needs_terminal);
+ if(mc_cmd->command)
+ fs_give((void **)&mc_cmd->command);
+ fs_give((void **)&mc_cmd);
+ }
+ else{
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ q_status_message1(SM_ORDER, 3, 4, _("Cannot display %s attachment"),
+ type_desc(body->type, body->subtype,
+ body->parameter, NULL, 1));
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Detach and provide for browsing a text body part
+
+ Args: msgno -- raw message number to get part from
+ a -- attachment struct for the desired part
+
+ Result:
+ ----*/
+STORE_S *
+format_text_att(long int msgno, ATTACH_S *a, HANDLE_S **handlesp)
+{
+ STORE_S *store;
+ gf_io_t pc;
+
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ if(handlesp)
+ init_handles(handlesp);
+
+ gf_set_so_writec(&pc, store);
+ (void) decode_text(a, msgno, pc, handlesp, QStatus, FM_DISPLAY);
+ gf_clear_so_writec(store);
+ }
+
+ return(store);
+}
+
+
+/*----------------------------------------------------------------------
+ Detach and provide for browsing a text body part
+
+ Args: msgno -- raw message number to get part from
+ a -- attachment struct for the desired part
+
+ Result:
+ ----*/
+int
+display_text_att(long int msgno, ATTACH_S *a, int flags)
+{
+ STORE_S *store;
+ HANDLE_S *handles = NULL;
+ int rv = 0;
+
+ if(msgno > 0L)
+ clear_index_cache_ent(ps_global->mail_stream, msgno, 0);
+
+ if((store = format_text_att(msgno, a, &handles)) != NULL){
+ rv = scroll_attachment("ATTACHED TEXT", store, CharStar, handles, a, flags);
+ free_handles(&handles);
+ so_give(&store); /* free resources associated with store */
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space for attachment."));
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Detach and provide for browsing a body part of type "MESSAGE"
+
+ Args: msgno -- message number to get partrom
+ a -- attachment struct for the desired part
+
+ Result:
+ ----*/
+int
+display_msg_att(long int msgno, ATTACH_S *a, int flags)
+{
+ STORE_S *store;
+ gf_io_t pc;
+ ATTACH_S *ap = a;
+ HANDLE_S *handles = NULL;
+ int rv = 0;
+
+ if(msgno > 0L)
+ clear_index_cache_ent(ps_global->mail_stream, msgno, 0);
+
+ /* BUG, should check this return code */
+ (void) pine_mail_fetchstructure(ps_global->mail_stream, msgno, NULL);
+
+ /* initialize a storage object */
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space for message."));
+ return(rv);
+ }
+
+ gf_set_so_writec(&pc, store);
+
+ if(format_msg_att(msgno, &ap, &handles, pc, FM_DISPLAY)){
+ if(ps_global->full_header == 2)
+ q_status_message(SM_INFO, 0, 3,
+ _("Full header mode ON. All header text being included"));
+
+ rv = scroll_attachment((a->body->subtype
+ && !strucmp(a->body->subtype, "delivery-status"))
+ ? "DELIVERY STATUS REPORT" : "ATTACHED MESSAGE",
+ store, CharStar, handles, a, flags);
+ free_handles(&handles);
+ }
+
+ gf_clear_so_writec(store);
+
+ so_give(&store); /* free resources associated with store */
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Detach and provide for browsing a multipart body part of type "DIGEST"
+
+ Args: msgno -- message number to get partrom
+ a -- attachment struct for the desired part
+
+ Result:
+ ----*/
+void
+display_digest_att(long int msgno, ATTACH_S *a, int flags)
+{
+ STORE_S *store;
+ ATTACH_S *ap;
+ HANDLE_S *handles = NULL;
+ gf_io_t pc;
+ SourceType src = CharStar;
+ int bad_news = 0;
+
+ if(msgno > 0L)
+ clear_index_cache_ent(ps_global->mail_stream, msgno, 0);
+
+ /* BUG, should check this return code */
+ (void) pine_mail_fetchstructure(ps_global->mail_stream, msgno, NULL);
+
+ if(!(store = so_get(src, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space for message."));
+ return;
+ }
+
+ gf_set_so_writec(&pc, store);
+
+ /*
+ * While in a subtype of this message
+ */
+ for(ap = a + 1;
+ ap->description
+ && !strncmp(a->number, ap->number, strlen(a->number))
+ && !bad_news;
+ ap++){
+ if(ap->body->type == TYPEMESSAGE){
+ char *errstr;
+
+ if(ap->body->subtype && strucmp(ap->body->subtype, "rfc822")){
+ char *tsub;
+
+ tsub = cpystr(ap->body->subtype);
+ convert_possibly_encoded_str_to_utf8((char **) &tsub);
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Unknown Message subtype: %s", tsub);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ if((errstr = format_editorial(tmp_20k_buf,
+ ps_global->ttyo->screen_cols, 0,
+ NULL, pc)) != NULL){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Can't format digest: %s"), errstr);
+ bad_news++;
+ }
+ else if(!gf_puts(NEWLINE, pc))
+ bad_news++;
+
+ fs_give((void **) &tsub);
+ }
+ else{
+ if((errstr = part_desc(ap->number, ap->body->nested.msg->body,
+ 0, ps_global->ttyo->screen_cols, FM_DISPLAY, pc)) != NULL){
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Can't format digest: %s"), errstr);
+ bad_news++;
+ }
+ else if(!format_msg_att(msgno, &ap, &handles, pc, FM_DISPLAY))
+ bad_news++;
+ }
+ }
+ else if(ap->body->type == TYPETEXT
+ && decode_text(ap, msgno, pc, NULL, QStatus, FM_DISPLAY))
+ bad_news++;
+ else if(!gf_puts("Unknown type in Digest", pc))
+ bad_news++;
+ }
+
+ if(!bad_news){
+ if(ps_global->full_header == 2)
+ q_status_message(SM_INFO, 0, 3,
+ _("Full header mode ON. All header text being included"));
+
+ scroll_attachment(_("ATTACHED MESSAGES"), store, src, handles, a, flags);
+ }
+
+ free_handles(&handles);
+
+ gf_clear_so_writec(store);
+ so_give(&store); /* free resources associated with store */
+}
+
+
+int
+scroll_attachment(char *title, STORE_S *store, SourceType src, HANDLE_S *handles, ATTACH_S *a, int flags)
+{
+ SCROLL_S sargs;
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = src;
+ sargs.text.desc = "attachment";
+ sargs.text.handles = handles;
+ sargs.bar.title = title;
+ sargs.proc.tool = process_attachment_cmd;
+ sargs.proc.data.p = (void *) a;
+ sargs.help.text = h_mail_text_att_view;
+ sargs.help.title = _("HELP FOR ATTACHED TEXT VIEW");
+ sargs.keys.menu = &att_view_keymenu;
+ setbitmap(sargs.keys.bitmap);
+
+ /* First, fix up "back" key */
+ if(flags & DA_FROM_VIEW){
+ att_view_keymenu.keys[ATV_BACK_KEY].label = N_("MsgText");
+ }
+ else{
+ att_view_keymenu.keys[ATV_BACK_KEY].label = N_("AttchIndex");
+ }
+
+ if(!handles){
+ clrbitn(ATV_VIEW_HILITE, sargs.keys.bitmap);
+ clrbitn(ATV_PREV_URL, sargs.keys.bitmap);
+ clrbitn(ATV_NEXT_URL, sargs.keys.bitmap);
+ }
+
+ if(F_OFF(F_ENABLE_PIPE, ps_global))
+ clrbitn(ATV_PIPE_KEY, sargs.keys.bitmap);
+
+ if(!(a->body->type == TYPETEXT || MIME_MSG_A(a) || MIME_DGST_A(a)))
+ clrbitn(ATV_PRINT_KEY, sargs.keys.bitmap);
+
+ /*
+ * If message or digest, leave Reply and Save and,
+ * conditionally, Bounce on...
+ */
+ if(MIME_MSG_A(a)){
+ if(F_OFF(F_ENABLE_BOUNCE, ps_global))
+ clrbitn(ATV_BOUNCE_KEY, sargs.keys.bitmap);
+ }
+ else{
+ clrbitn(ATV_BOUNCE_KEY, sargs.keys.bitmap);
+ clrbitn(ATV_REPLY_KEY, sargs.keys.bitmap);
+ clrbitn(ATV_EXPORT_KEY, sargs.keys.bitmap);
+ }
+
+ sargs.use_indexline_color = 1;
+
+ if(DA_RESIZE & flags)
+ sargs.resize_exit = 1;
+
+#ifdef _WINDOWS
+ scrat_attachp = a;
+ sargs.mouse.popup = scroll_att_popup;
+#endif
+
+ return(scrolltool(&sargs));
+}
+
+
+int
+process_attachment_cmd(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 0, n;
+ long rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
+#define AD(X) ((ATTACH_S *) (X)->proc.data.p)
+
+ switch(cmd){
+ case MC_EXIT :
+ rv = 1;
+ break;
+
+ case MC_QUIT :
+ ps_global->next_screen = quit_screen;
+ break;
+
+ case MC_MAIN :
+ ps_global->next_screen = main_menu_screen;
+ break;
+
+ case MC_REPLY :
+ reply_msg_att(ps_global->mail_stream, rawno, sparms->proc.data.p);
+ break;
+
+ case MC_FORWARD :
+ forward_attachment(ps_global->mail_stream, rawno, sparms->proc.data.p);
+ break;
+
+ case MC_BOUNCE :
+ bounce_msg_att(ps_global->mail_stream, rawno, AD(sparms)->number,
+ AD(sparms)->body->nested.msg->env->subject);
+ ps_global->mangled_footer = 1;
+ break;
+
+ case MC_DELETE :
+ delete_attachment(rawno, sparms->proc.data.p);
+ break;
+
+ case MC_UNDELETE :
+ if(undelete_attachment(rawno, sparms->proc.data.p, &n))
+ q_status_message1(SM_ORDER, 0, 3,
+ "Part %s UNdeleted", AD(sparms)->number);
+
+ break;
+
+ case MC_SAVE :
+ save_attachment(-FOOTER_ROWS(ps_global), rawno, sparms->proc.data.p);
+ ps_global->mangled_footer = 1;
+ break;
+
+ case MC_EXPORT :
+ export_attachment(-FOOTER_ROWS(ps_global), rawno, sparms->proc.data.p);
+ ps_global->mangled_footer = 1;
+ break;
+
+ case MC_PRINTMSG :
+ print_attachment(-FOOTER_ROWS(ps_global), rawno, sparms->proc.data.p);
+ ps_global->mangled_footer = 1;
+ break;
+
+ case MC_PIPE :
+ pipe_attachment(rawno, sparms->proc.data.p);
+ ps_global->mangled_footer = 1;
+ break;
+
+ case MC_FULLHDR :
+ ps_global->full_header++;
+ if(ps_global->full_header == 1){
+ if(!(ps_global->quote_suppression_threshold
+ && (ps_global->some_quoting_was_suppressed /* || in_index != View*/)))
+ ps_global->full_header++;
+ }
+ else if(ps_global->full_header > 2)
+ ps_global->full_header = 0;
+
+ switch(ps_global->full_header){
+ case 0:
+ q_status_message(SM_ORDER, 0, 3,
+ _("Display of full headers is now off."));
+ break;
+
+ case 1:
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Quotes displayed, use %s to see full headers"),
+ F_ON(F_USE_FK, ps_global) ? "F9" : "H");
+ break;
+
+ case 2:
+ q_status_message(SM_ORDER, 0, 3,
+ _("Display of full headers is now on."));
+ break;
+
+ }
+
+ rv = 1;
+ break;
+
+ default:
+ panic("Unexpected command case");
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Returns 1 on success, 0 on error.
+ */
+int
+format_msg_att(long int msgno, ATTACH_S **a, HANDLE_S **handlesp, gf_io_t pc, int flags)
+{
+ int rv = 1;
+
+ if((*a)->body->type != TYPEMESSAGE)
+ return(gf_puts("[ Undisplayed Attachment of Type ", pc)
+ && gf_puts(body_type_names((*a)->body->type), pc)
+ && gf_puts(" ]", pc) && gf_puts(NEWLINE, pc));
+
+ if((*a)->body->subtype && strucmp((*a)->body->subtype, "rfc822") == 0){
+ HEADER_S h;
+
+ HD_INIT(&h, ps_global->VAR_VIEW_HEADERS, ps_global->view_all_except,
+ FE_DEFAULT);
+ switch(format_header(ps_global->mail_stream, msgno, (*a)->number,
+ (*a)->body->nested.msg->env, &h, NULL, NULL,
+ flags, NULL, pc)){
+ case -1 : /* write error */
+ return(0);
+
+ case 1 : /* fetch error */
+ if(!(gf_puts("[ Error fetching header ]", pc)
+ && !gf_puts(NEWLINE, pc)))
+ return(0);
+
+ break;
+ }
+
+ gf_puts(NEWLINE, pc);
+
+ ++(*a);
+
+#ifdef SMIME
+ if((*a)->body && (*a)->body->subtype && (strucmp((*a)->body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)==0)){
+ if((*a)->description){
+ if(!(!format_editorial((*a)->description,
+ ps_global->ttyo->screen_cols,
+ flags, NULL, pc)
+ && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
+ rv = 0;
+ }
+
+ ++(*a);
+ }
+#endif /* SMIME */
+
+ if(((*a))->description
+ && (*a)->body && (*a)->body->type == TYPETEXT){
+ if(decode_text(*a, msgno, pc, NULL, QStatus, flags))
+ rv = 0;
+ }
+ else if(!(gf_puts("[Can't display ", pc)
+ && gf_puts(((*a)->description && (*a)->body)
+ ? "first non-" : "missing ", pc)
+ && gf_puts("text segment]", pc)
+ && gf_puts(NEWLINE, pc)))
+ rv = 0;
+ }
+ else if((*a)->body->subtype
+ && strucmp((*a)->body->subtype, "external-body") == 0) {
+ if(format_editorial("This part is not included and can be fetched as follows:",
+ ps_global->ttyo->screen_cols, flags, NULL, pc)
+ || !gf_puts(NEWLINE, pc)
+ || format_editorial(display_parameters((*a)->body->parameter),
+ ps_global->ttyo->screen_cols, flags, handlesp, pc))
+ rv = 0;
+ }
+ else if(decode_text(*a, msgno, pc, NULL, QStatus, flags))
+ rv = 0;
+
+ return(rv);
+}
+
+
+void
+display_vcard_att(long int msgno, ATTACH_S *a, int flags)
+{
+ STORE_S *in_store, *out_store = NULL;
+ HANDLE_S *handles = NULL;
+ URL_HILITE_S uh;
+ gf_io_t gc, pc;
+ char **lines, **ll, *errstr = NULL, tmp[MAILTMPLEN], *p;
+ int cmd, indent, begins = 0;
+
+ lines = detach_vcard_att(ps_global->mail_stream,
+ msgno, a->body, a->number);
+ if(!lines){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error accessing attachment."));
+ return;
+ }
+
+ if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ free_list_array(&lines);
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space for attachment."));
+ return;
+ }
+
+ for(ll = lines, indent = 0; ll && *ll; ll++)
+ if((p = strindex(*ll, ':')) && p - *ll > indent)
+ indent = p - *ll;
+
+ indent += 5;
+ for(ll = lines; ll && *ll; ll++){
+ if((p = strindex(*ll, ':')) != NULL){
+ if(begins < 2 && struncmp(*ll, "begin:", 6) == 0)
+ begins++;
+
+ snprintf(tmp, sizeof(tmp), " %-*.*s : ", indent - 5,
+ MIN(p - *ll, sizeof(tmp)-5), *ll);
+ tmp[sizeof(tmp)-1] = '\0';
+ so_puts(in_store, tmp);
+ p++;
+ }
+ else{
+ p = *ll;
+ so_puts(in_store, repeat_char(indent, SPACE));
+ }
+
+ snprintf(tmp, sizeof(tmp), "%.200s", p);
+ tmp[sizeof(tmp)-1] = '\0';
+ so_puts(in_store,
+ (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, tmp));
+ so_puts(in_store, "\015\012");
+ }
+
+ free_list_array(&lines);
+
+ so_puts(in_store, "\015\012\015\012");
+
+ do{
+ if((out_store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ so_seek(in_store, 0L, 0);
+
+ init_handles(&handles);
+ gf_filter_init();
+
+ if(F_ON(F_VIEW_SEL_URL, ps_global)
+ || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
+ || F_ON(F_SCAN_ADDR, ps_global))
+ gf_link_filter(gf_line_test,
+ gf_line_test_opt(url_hilite,
+ gf_url_hilite_opt(&uh,&handles,0)));
+
+ gf_link_filter(gf_wrap,
+ gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
+ ps_global->ttyo->screen_cols,
+ NULL, indent, GFW_HANDLES));
+ gf_link_filter(gf_nvtnl_local, NULL);
+
+ gf_set_so_readc(&gc, in_store);
+ gf_set_so_writec(&pc, out_store);
+
+ errstr = gf_pipe(gc, pc);
+
+ gf_clear_so_readc(in_store);
+
+ if(!errstr){
+#define VCARD_TEXT_ONE _("This is a vCard which has been forwarded to you. You may add parts of it to your address book with the Save command. You will have a chance to edit it before committing it to your address book.")
+#define VCARD_TEXT_MORE _("This is a vCard which has been forwarded to you. You may add the entries to your address book with the Save command.")
+ errstr = format_editorial((begins > 1)
+ ? VCARD_TEXT_MORE : VCARD_TEXT_ONE,
+ ps_global->ttyo->screen_cols, 0, NULL, pc);
+ }
+
+ gf_clear_so_writec(out_store);
+
+ if(!errstr)
+ cmd = scroll_attachment(_("ADDRESS BOOK ATTACHMENT"), out_store,
+ CharStar, handles, a, flags | DA_RESIZE);
+
+ free_handles(&handles);
+ so_give(&out_store);
+ }
+ else
+ errstr = _("Error allocating space");
+ }
+ while(!errstr && (cmd == MC_RESIZE || cmd == MC_FULLHDR));
+
+ if(errstr)
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Can't format entry : %s"), errstr);
+
+ so_give(&in_store);
+}
+
+
+/*----------------------------------------------------------------------
+ Display attachment information
+
+ Args: msgno -- message number to get partrom
+ a -- attachment struct for the desired part
+
+ Result: a screen containing information about attachment:
+ ----*/
+void
+display_attach_info(long int msgno, ATTACH_S *a)
+{
+ int i, indent, cols;
+ char buf1[100], *folded;
+ STORE_S *store;
+ PARMLIST_S *plist;
+ SCROLL_S sargs;
+
+ (void) dispatch_attachment(a);
+
+ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space for message."));
+ return;
+ }
+
+ cols = ps_global->ttyo->screen_cols;
+
+ /*
+ * 2 spaces on left
+ * 16 for text (longest is Display Method == 14)
+ * 2 for ": "
+ */
+ indent = 20;
+
+ /* don't try stupid folding */
+ cols = MAX(indent+10, cols);
+
+ so_puts(store, "Details about Attachment #");
+ so_puts(store, a->number);
+ so_puts(store, " :\n\n");
+ utf8_snprintf(buf1, sizeof(buf1), " %-*.*w: ", indent-4, indent-4, "Type");
+ so_puts(store, buf1);
+ so_puts(store, body_type_names(a->body->type));
+ so_puts(store, "\n");
+ utf8_snprintf(buf1, sizeof(buf1), " %-*.*w: ", indent-4, indent-4, "Subtype");
+ so_puts(store, buf1);
+ so_puts(store, a->body->subtype ? a->body->subtype : "Unknown");
+ so_puts(store, "\n");
+ utf8_snprintf(buf1, sizeof(buf1), " %-*.*w: ", indent-4, indent-4, "Encoding");
+ so_puts(store, buf1);
+ so_puts(store, a->body->encoding < ENCMAX
+ ? body_encodings[a->body->encoding]
+ : "Unknown");
+ so_puts(store, "\n");
+ if((plist = rfc2231_newparmlist(a->body->parameter)) != NULL){
+ utf8_snprintf(buf1, sizeof(buf1), " %-*.*w: ", indent-4, indent-4, "Parameters");
+ so_puts(store, buf1);
+ i = 0;
+ while(rfc2231_list_params(plist)){
+ if(i++)
+ so_puts(store, repeat_char(indent, ' '));
+
+ so_puts(store, plist->attrib);
+ so_puts(store, " = ");
+ so_puts(store, plist->value ? plist->value : "");
+ so_puts(store, "\n");
+ }
+
+ rfc2231_free_parmlist(&plist);
+ }
+
+ if(a->body->description && a->body->description[0]){
+ char buftmp[MAILTMPLEN];
+ unsigned char *q;
+
+ utf8_snprintf(buf1, sizeof(buf1), " %-*.*w: ", indent-4, indent-4, "Description");
+
+ snprintf(buftmp, sizeof(buftmp), "%s", a->body->description);
+ buftmp[sizeof(buftmp)-1] = '\0';
+ q = rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, buftmp);
+ folded = fold((char *) q, cols, cols, buf1, repeat_char(indent+1, ' '), FLD_NONE);
+
+ if(folded){
+ so_puts(store, folded);
+ fs_give((void **) &folded);
+ }
+ }
+
+ /* BUG: no a->body->language support */
+
+ if(a->body->disposition.type){
+ utf8_snprintf(buf1, sizeof(buf1), " %-*.*w: ", indent-4, indent-4, "Disposition");
+ so_puts(store, buf1);
+ so_puts(store, a->body->disposition.type);
+ so_puts(store, "\n");
+ if((plist = rfc2231_newparmlist(a->body->disposition.parameter)) != NULL){
+ while(rfc2231_list_params(plist)){
+ so_puts(store, repeat_char(indent, ' '));
+ so_puts(store, plist->attrib);
+ so_puts(store, " = ");
+ so_puts(store, plist->value ? plist->value : "");
+ so_puts(store, "\n");
+ }
+
+ rfc2231_free_parmlist(&plist);
+ }
+ }
+
+ utf8_snprintf(buf1, sizeof(buf1), " %-*.*w: ", indent-4, indent-4, "Approx. Size");
+ so_puts(store, buf1);
+ so_puts(store, comatose((a->body->encoding == ENCBASE64)
+ ? ((a->body->size.bytes * 3)/4)
+ : a->body->size.bytes));
+ so_puts(store, " bytes\n");
+ utf8_snprintf(buf1, sizeof(buf1), " %-*.*w: ", indent-4, indent-4, "Display Method");
+ so_puts(store, buf1);
+ if(a->can_display == MCD_NONE) {
+ so_puts(store, "Can't, ");
+ so_puts(store, (a->body->encoding < ENCOTHER)
+ ? "Unknown Attachment Format"
+ : "Unknown Encoding");
+ }
+ else if(!(a->can_display & MCD_EXTERNAL)){
+ so_puts(store, "Alpine's Internal Pager");
+ }
+ else{
+ int nt, free_pretty_cmd;
+ MCAP_CMD_S *mc_cmd;
+ char *pretty_cmd;
+
+ if((mc_cmd = mailcap_build_command(a->body->type, a->body->subtype,
+ a->body, "<datafile>", &nt,
+ a->can_display & MCD_EXT_PROMPT)) != NULL){
+ so_puts(store, "\"");
+ if((pretty_cmd = execview_pretty_command(mc_cmd, &free_pretty_cmd)) != NULL)
+ so_puts(store, pretty_cmd);
+ so_puts(store, "\"");
+ if(free_pretty_cmd && pretty_cmd)
+ fs_give((void **)&pretty_cmd);
+ if(mc_cmd->command)
+ fs_give((void **)&mc_cmd->command);
+ fs_give((void **)&mc_cmd);
+ }
+ }
+
+ so_puts(store, "\n");
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = "attachment info";
+ sargs.bar.title = _("ABOUT ATTACHMENT");
+ sargs.help.text = h_simple_text_view;
+ sargs.help.title = _("HELP FOR \"ABOUT ATTACHMENT\"");
+
+ sargs.use_indexline_color = 1;
+
+ scrolltool(&sargs);
+
+ so_give(&store); /* free resources associated with store */
+ ps_global->mangled_screen = 1;
+}
+
+
+/*----------------------------------------------------------------------
+
+ ----*/
+void
+forward_attachment(MAILSTREAM *stream, long int msgno, ATTACH_S *a)
+{
+ char *sig;
+ void *msgtext;
+ ENVELOPE *outgoing;
+ BODY *body;
+
+ if(MIME_MSG_A(a)){
+ forward_msg_att(stream, msgno, a);
+ }
+ else{
+ ACTION_S *role = NULL;
+ REDRAFT_POS_S *redraft_pos = NULL;
+ long rflags = ROLE_FORWARD;
+ PAT_STATE dummy;
+
+ outgoing = mail_newenvelope();
+ outgoing->message_id = generate_message_id();
+ outgoing->subject = cpystr("Forwarded attachment...");
+
+ if(nonempty_patterns(rflags, &dummy)){
+ /*
+ * There is no message, but a Current Folder Type might match.
+ *
+ * This has been changed to check against the message
+ * containing the attachment.
+ */
+ role = set_role_from_msg(ps_global, ROLE_FORWARD, msgno, NULL);
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{
+ role = NULL;
+ cmd_cancelled("Forward");
+ mail_free_envelope(&outgoing);
+ return;
+ }
+ }
+
+ if(role)
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Forwarding using role \"%s\""), role->nick);
+
+ /*
+ * as with all text bound for the composer, build it in
+ * a storage object of the type it understands...
+ */
+ if((msgtext = (void *) so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ int impl, template_len = 0;
+
+ if(role && role->template){
+ char *filtered;
+
+ impl = 1;
+ filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
+ if(filtered){
+ if(*filtered){
+ so_puts((STORE_S *)msgtext, filtered);
+ if(impl == 1)
+ template_len = strlen(filtered);
+ }
+
+ fs_give((void **)&filtered);
+ }
+ }
+ else
+ impl = 1;
+
+ if((sig = detoken(role, NULL, 2, 0, 1, &redraft_pos, &impl)) != NULL){
+ if(impl == 2)
+ redraft_pos->offset += template_len;
+
+ so_puts((STORE_S *)msgtext, *sig ? sig : NEWLINE);
+
+ fs_give((void **)&sig);
+ }
+ else
+ so_puts((STORE_S *)msgtext, NEWLINE);
+
+ /*---- New Body to start with ----*/
+ body = mail_newbody();
+ body->type = TYPEMULTIPART;
+
+ /*---- The TEXT part/body ----*/
+ body->nested.part = mail_newbody_part();
+ body->nested.part->body.type = TYPETEXT;
+ body->nested.part->body.contents.text.data = msgtext;
+
+ /*---- The corresponding things we're attaching ----*/
+ body->nested.part->next = mail_newbody_part();
+ body->nested.part->next->body.id = generate_message_id();
+ copy_body(&body->nested.part->next->body, a->body);
+
+ if(fetch_contents(stream, msgno, a->number,
+ &body->nested.part->next->body)){
+ pine_send(outgoing, &body, "FORWARD MESSAGE",
+ role, NULL, NULL, redraft_pos, NULL, NULL, 0);
+
+ ps_global->mangled_screen = 1;
+ pine_free_body(&body);
+ free_redraft_pos(&redraft_pos);
+ }
+ else{
+ mail_free_body(&body);
+ so_give((STORE_S **) &msgtext);
+ free_redraft_pos(&redraft_pos);
+ q_status_message(SM_ORDER | SM_DING, 4, 5,
+ _("Error fetching message contents. Can't forward message."));
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error allocating message text"));
+
+ mail_free_envelope(&outgoing);
+ free_action(&role);
+ }
+}
+
+
+void
+forward_msg_att(MAILSTREAM *stream, long int msgno, ATTACH_S *a)
+{
+ char *p, *sig = NULL;
+ int ret;
+ void *msgtext;
+ ENVELOPE *outgoing;
+ BODY *body;
+ ACTION_S *role = NULL;
+ REPLY_S reply;
+ REDRAFT_POS_S *redraft_pos = NULL;
+
+ outgoing = mail_newenvelope();
+ outgoing->message_id = generate_message_id();
+
+ memset((void *)&reply, 0, sizeof(reply));
+
+ if((outgoing->subject = forward_subject(a->body->nested.msg->env, 0)) != NULL){
+ /*
+ * as with all text bound for the composer, build it in
+ * a storage object of the type it understands...
+ */
+ if((msgtext = (void *) so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ int impl, template_len = 0;
+ long rflags = ROLE_FORWARD;
+ PAT_STATE dummy;
+
+ ret = 'n';
+ if(ps_global->full_header == 2)
+ ret = want_to(_("Forward message as an attachment"), 'n', 0,
+ NO_HELP, WT_SEQ_SENSITIVE);
+ /* Setup possible role */
+ if(nonempty_patterns(rflags, &dummy)){
+ role = set_role_from_msg(ps_global, rflags, msgno, a->number);
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel reply */
+ role = NULL;
+ cmd_cancelled("Forward");
+ mail_free_envelope(&outgoing);
+ so_give((STORE_S **) &msgtext);
+ return;
+ }
+ }
+
+ if(role)
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Forwarding using role \"%s\""), role->nick);
+
+ if(role && role->template){
+ char *filtered;
+
+ impl = 1;
+ filtered = detoken(role, a->body->nested.msg->env,
+ 0, 0, 0, &redraft_pos, &impl);
+ if(filtered){
+ if(*filtered){
+ so_puts((STORE_S *)msgtext, filtered);
+ if(impl == 1)
+ template_len = strlen(filtered);
+ }
+
+ fs_give((void **)&filtered);
+ }
+ }
+ else
+ impl = 1;
+
+ if((sig = detoken(role, a->body->nested.msg->env,
+ 2, 0, 1, &redraft_pos, &impl)) != NULL){
+ if(impl == 2)
+ redraft_pos->offset += template_len;
+
+ so_puts((STORE_S *)msgtext, *sig ? sig : NEWLINE);
+
+ fs_give((void **)&sig);
+ }
+ else
+ so_puts((STORE_S *)msgtext, NEWLINE);
+
+ if(ret == 'y'){
+ /*---- New Body to start with ----*/
+ body = mail_newbody();
+ body->type = TYPEMULTIPART;
+
+ /*---- The TEXT part/body ----*/
+ body->nested.part = mail_newbody_part();
+ body->nested.part->body.type = TYPETEXT;
+ body->nested.part->body.contents.text.data = msgtext;
+
+ if(!forward_mime_msg(stream, msgno,
+ p = body_partno(stream, msgno, a->body),
+ a->body->nested.msg->env,
+ &body->nested.part->next, msgtext))
+ mail_free_body(&body);
+ }
+ else{
+ reply.forw = 1;
+ if(a->body->nested.msg->body){
+ char *charset;
+
+ charset
+ = parameter_val(a->body->nested.msg->body->parameter,
+ "charset");
+
+ if(charset && strucmp(charset, "us-ascii") != 0){
+ CONV_TABLE *ct;
+
+ /*
+ * There is a non-ascii charset,
+ * is there conversion happening?
+ */
+ if(!(ct=conversion_table(charset, ps_global->posting_charmap))
+ || !ct->table){
+ reply.orig_charset = charset;
+ charset = NULL;
+ }
+ }
+
+ if(charset)
+ fs_give((void **) &charset);
+ }
+
+ body = forward_body(stream, a->body->nested.msg->env,
+ a->body->nested.msg->body, msgno,
+ p = body_partno(stream, msgno, a->body),
+ msgtext, FWD_NONE);
+ }
+
+ fs_give((void **) &p);
+
+ if(body){
+ pine_send(outgoing, &body,
+ "FORWARD MESSAGE",
+ role, NULL,
+ reply.forw ? &reply : NULL,
+ redraft_pos, NULL, NULL, 0);
+
+ ps_global->mangled_screen = 1;
+ pine_free_body(&body);
+ free_redraft_pos(&redraft_pos);
+ free_action(&role);
+ }
+ else{
+ so_give((STORE_S **) &msgtext);
+ q_status_message(SM_ORDER | SM_DING, 4, 5,
+ _("Error fetching message contents. Can't forward message."));
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error allocating message text"));
+ }
+ else
+ q_status_message1(SM_ORDER,3,4,
+ _("Error fetching message %s. Can't forward it."),
+ long2string(msgno));
+
+ if(reply.orig_charset)
+ fs_give((void **)&reply.orig_charset);
+
+ mail_free_envelope(&outgoing);
+}
+
+
+void
+reply_msg_att(MAILSTREAM *stream, long int msgno, ATTACH_S *a)
+{
+ ADDRESS *saved_from, *saved_to, *saved_cc, *saved_resent;
+ ENVELOPE *outgoing;
+ BODY *body;
+ void *msgtext;
+ char *tp, *prefix = NULL, *fcc = NULL, *errmsg = NULL;
+ int include_text = 0, flags = RSF_QUERY_REPLY_ALL;
+ int rolemsg = 0, copytomsg = 0;
+ long rflags;
+ PAT_STATE dummy;
+ REDRAFT_POS_S *redraft_pos = NULL;
+ ACTION_S *role = NULL;
+
+ outgoing = mail_newenvelope();
+
+ dprint((4,"\n - attachment reply \n"));
+
+ saved_from = (ADDRESS *) NULL;
+ saved_to = (ADDRESS *) NULL;
+ saved_cc = (ADDRESS *) NULL;
+ saved_resent = (ADDRESS *) NULL;
+ outgoing->subject = NULL;
+
+ prefix = reply_quote_str(a->body->nested.msg->env);
+ /*
+ * For consistency, the first question is always "include text?"
+ */
+ if((include_text = reply_text_query(ps_global, 1, &prefix)) >= 0
+ && reply_news_test(a->body->nested.msg->env, outgoing) > 0
+ && reply_harvest(ps_global, msgno, a->number,
+ a->body->nested.msg->env, &saved_from,
+ &saved_to, &saved_cc, &saved_resent, &flags)){
+ outgoing->subject = reply_subject(a->body->nested.msg->env->subject,
+ NULL, 0);
+ clear_cursor_pos();
+ reply_seed(ps_global, outgoing, a->body->nested.msg->env,
+ saved_from, saved_to, saved_cc, saved_resent,
+ &fcc, flags & RSF_FORCE_REPLY_ALL, &errmsg);
+ if(errmsg){
+ if(*errmsg){
+ q_status_message1(SM_ORDER, 3, 3, "%.200s", errmsg);
+ display_message(NO_OP_COMMAND);
+ }
+
+ fs_give((void **)&errmsg);
+ }
+
+ if(sp_expunge_count(stream)) /* current msg was expunged */
+ goto seeyalater;
+
+ /* Setup possible role */
+ rflags = ROLE_REPLY;
+ if(nonempty_patterns(rflags, &dummy)){
+ role = set_role_from_msg(ps_global, rflags, msgno, a->number);
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel reply */
+ role = NULL;
+ cmd_cancelled("Reply");
+ goto seeyalater;
+ }
+ }
+
+ if(role){
+ rolemsg++;
+
+ /* override fcc gotten in reply_seed */
+ if(role->fcc && fcc)
+ fs_give((void **) &fcc);
+ }
+
+ if(F_ON(F_COPY_TO_TO_FROM, ps_global) && !(role && role->from)){
+ ADDRESS *us_in_to_and_cc, *ap;
+
+ us_in_to_and_cc = (ADDRESS *) NULL;
+ if(a->body->nested.msg->env && a->body->nested.msg->env->to)
+ if((ap=reply_cp_addr(ps_global, 0L, NULL,
+ NULL, us_in_to_and_cc, NULL,
+ a->body->nested.msg->env->to, RCA_ONLY_US)) != NULL)
+ reply_append_addr(&us_in_to_and_cc, ap);
+
+ if(a->body->nested.msg->env && a->body->nested.msg->env->cc)
+ if((ap=reply_cp_addr(ps_global, 0L, NULL,
+ NULL, us_in_to_and_cc, NULL,
+ a->body->nested.msg->env->cc, RCA_ONLY_US)) != NULL)
+ reply_append_addr(&us_in_to_and_cc, ap);
+
+ /*
+ * A list of all of our addresses that appear in the To
+ * and cc fields is in us_in_to_and_cc.
+ * If there is exactly one address in that list then
+ * use it for the outgoing From.
+ */
+ if(us_in_to_and_cc && !us_in_to_and_cc->next){
+ PINEFIELD *custom, *pf;
+ ADDRESS *a = NULL;
+ char *addr = NULL;
+
+ /*
+ * Check to see if this address is different from what
+ * we would have used anyway. If it is, notify the user
+ * with a status message. This is pretty hokey because we're
+ * mimicking how pine_send would set the From address and
+ * there is no coordination between the two.
+ */
+
+ /* in case user has a custom From value */
+ custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
+
+ pf = (PINEFIELD *) fs_get(sizeof(*pf));
+ memset((void *) pf, 0, sizeof(*pf));
+ pf->name = cpystr("From");
+ pf->addr = &a;
+ if(set_default_hdrval(pf, custom) >= UseAsDef
+ && pf->textbuf && pf->textbuf[0]){
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
+ if(addr)
+ fs_give((void **) &addr);
+ }
+
+ if(!*pf->addr)
+ *pf->addr = generate_from();
+
+ if(*pf->addr && !address_is_same(*pf->addr, us_in_to_and_cc)){
+ copytomsg++;
+ if(!role){
+ role = (ACTION_S *) fs_get(sizeof(*role));
+ memset((void *) role, 0, sizeof(*role));
+ role->is_a_role = 1;
+ }
+
+ role->from = us_in_to_and_cc;
+ us_in_to_and_cc = NULL;
+ }
+
+ free_customs(custom);
+ free_customs(pf);
+ }
+
+ if(us_in_to_and_cc)
+ mail_free_address(&us_in_to_and_cc);
+
+ }
+
+ if(role){
+ if(rolemsg && copytomsg)
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Replying using role \"%s\" and To as From"), role->nick);
+ else if(rolemsg)
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Replying using role \"%s\""), role->nick);
+ else if(copytomsg)
+ q_status_message(SM_ORDER, 3, 4,
+ _("Replying using incoming To as outgoing From"));
+ }
+
+ outgoing->in_reply_to = reply_in_reply_to(a->body->nested.msg->env);
+ outgoing->references = reply_build_refs(a->body->nested.msg->env);
+ outgoing->message_id = generate_message_id();
+
+ if(!outgoing->to && !outgoing->cc
+ && !outgoing->bcc && !outgoing->newsgroups)
+ q_status_message(SM_ORDER | SM_DING, 3, 6,
+ _("Warning: no valid addresses to reply to!"));
+
+ /*
+ * Now fix up the body...
+ */
+ if((msgtext = (void *) so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ REPLY_S reply;
+
+ memset((void *)&reply, 0, sizeof(reply));
+ reply.forw = 1;
+ if(a->body->nested.msg->body){
+ char *charset;
+
+ charset
+ = parameter_val(a->body->nested.msg->body->parameter,
+ "charset");
+
+ if(charset && strucmp(charset, "us-ascii") != 0){
+ CONV_TABLE *ct;
+
+ /*
+ * There is a non-ascii charset,
+ * is there conversion happening?
+ */
+ if(!(ct=conversion_table(charset, ps_global->posting_charmap))
+ || !ct->table){
+ reply.orig_charset = charset;
+ charset = NULL;
+ }
+ }
+
+ if(charset)
+ fs_give((void **) &charset);
+ }
+
+ if((body = reply_body(stream, a->body->nested.msg->env,
+ a->body->nested.msg->body, msgno,
+ tp = body_partno(stream, msgno, a->body),
+ msgtext, prefix, include_text, role,
+ 1, &redraft_pos)) != NULL){
+ /* partially formatted outgoing message */
+ pine_send(outgoing, &body, "COMPOSE MESSAGE REPLY",
+ role, fcc, &reply, redraft_pos, NULL, NULL, 0);
+
+ pine_free_body(&body);
+ ps_global->mangled_screen = 1;
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error building message body"));
+
+ fs_give((void **) &tp);
+ if(reply.orig_charset)
+ fs_give((void **)&reply.orig_charset);
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error allocating message text"));
+ }
+
+seeyalater:
+ mail_free_envelope(&outgoing);
+ mail_free_address(&saved_from);
+ mail_free_address(&saved_to);
+ mail_free_address(&saved_cc);
+ mail_free_address(&saved_resent);
+
+ if(prefix)
+ fs_give((void **) &prefix);
+
+ if(fcc)
+ fs_give((void **) &fcc);
+
+ free_redraft_pos(&redraft_pos);
+ free_action(&role);
+}
+
+
+void
+bounce_msg_att(MAILSTREAM *stream, long int msgno, char *part, char *subject)
+{
+ char *errstr;
+
+ if((errstr = bounce_msg(stream, msgno, part, NULL, NULL, subject, NULL, NULL)) != NULL)
+ q_status_message(SM_ORDER | SM_DING, 3, 3, errstr);
+}
+
+
+void
+pipe_attachment(long int msgno, ATTACH_S *a)
+{
+ char *err, *resultfilename = NULL, prompt[80], *p;
+ int rc, capture = 1, raw = 0, we_cancel = 0, j = 0;
+ long ku;
+ PIPE_S *syspipe;
+ HelpType help;
+ char pipe_command[MAXPATH+1];
+ unsigned flagsforhist = 1; /* raw=2 /capture=1 */
+ static HISTORY_S *history = NULL;
+ ESCKEY_S pipe_opt[6];
+
+ if(ps_global->restricted){
+ q_status_message(SM_ORDER | SM_DING, 0, 4,
+ "Alpine demo can't pipe attachments");
+ return;
+ }
+
+ help = NO_HELP;
+ pipe_command[0] = '\0';
+
+ init_hist(&history, HISTSIZE);
+ flagsforhist = (raw ? 0x2 : 0) + (capture ? 0x1 : 0);
+ if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
+ strncpy(pipe_command, p, sizeof(pipe_command));
+ pipe_command[sizeof(pipe_command)-1] = '\0';
+ if(history->hist[history->curindex]){
+ flagsforhist = history->hist[history->curindex]->flags;
+ raw = (flagsforhist & 0x2) ? 1 : 0;
+ capture = (flagsforhist & 0x1) ? 1 : 0;
+ }
+ }
+
+ pipe_opt[j].ch = 0;
+ pipe_opt[j].rval = 0;
+ pipe_opt[j].name = "";
+ pipe_opt[j++].label = "";
+
+ pipe_opt[j].ch = ctrl('W');
+ pipe_opt[j].rval = 10;
+ pipe_opt[j].name = "^W";
+ pipe_opt[j++].label = NULL;
+
+ pipe_opt[j].ch = ctrl('Y');
+ pipe_opt[j].rval = 11;
+ pipe_opt[j].name = "^Y";
+ pipe_opt[j++].label = NULL;
+
+ pipe_opt[j].ch = KEY_UP;
+ pipe_opt[j].rval = 30;
+ pipe_opt[j].name = "";
+ ku = j;
+ pipe_opt[j++].label = "";
+
+ pipe_opt[j].ch = KEY_DOWN;
+ pipe_opt[j].rval = 31;
+ pipe_opt[j].name = "";
+ pipe_opt[j++].label = "";
+
+ pipe_opt[j].ch = -1;
+
+ while(1){
+ int flags;
+
+ snprintf(prompt, sizeof(prompt), "Pipe %sattachment %s to %s: ", raw ? "RAW " : "",
+ a->number, capture ? "" : "(Free Output) ");
+ prompt[sizeof(prompt)-1] = '\0';
+ pipe_opt[1].label = raw ? "DecodedData" : "Raw Data";
+ pipe_opt[2].label = capture ? "Free Output" : "Capture Output";
+
+ /*
+ * 2 is really 1 because there will be one real entry and
+ * one entry of "" because of the get_prev_hist above.
+ */
+ if(items_in_hist(history) > 2){
+ pipe_opt[ku].name = HISTORY_UP_KEYNAME;
+ pipe_opt[ku].label = HISTORY_KEYLABEL;
+ pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
+ pipe_opt[ku+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ pipe_opt[ku].name = "";
+ pipe_opt[ku].label = "";
+ pipe_opt[ku+1].name = "";
+ pipe_opt[ku+1].label = "";
+ }
+
+ flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
+ rc = optionally_enter(pipe_command, -FOOTER_ROWS(ps_global), 0,
+ sizeof(pipe_command), prompt,
+ pipe_opt, help, &flags);
+ if(rc == -1){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Internal problem encountered");
+ break;
+ }
+ else if(rc == 10){
+ raw = !raw; /* flip raw text */
+ }
+ else if(rc == 11){
+ capture = !capture; /* flip capture output */
+ }
+ else if(rc == 30){
+ flagsforhist = (raw ? 0x2 : 0) + (capture ? 0x1 : 0);
+ if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
+ strncpy(pipe_command, p, sizeof(pipe_command));
+ pipe_command[sizeof(pipe_command)-1] = '\0';
+ if(history->hist[history->curindex]){
+ flagsforhist = history->hist[history->curindex]->flags;
+ raw = (flagsforhist & 0x2) ? 1 : 0;
+ capture = (flagsforhist & 0x1) ? 1 : 0;
+ }
+ }
+ else
+ Writechar(BELL, 0);
+ }
+ else if(rc == 31){
+ flagsforhist = (raw ? 0x2 : 0) + (capture ? 0x1 : 0);
+ if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
+ strncpy(pipe_command, p, sizeof(pipe_command));
+ pipe_command[sizeof(pipe_command)-1] = '\0';
+ if(history->hist[history->curindex]){
+ flagsforhist = history->hist[history->curindex]->flags;
+ raw = (flagsforhist & 0x2) ? 1 : 0;
+ capture = (flagsforhist & 0x1) ? 1 : 0;
+ }
+ }
+ else
+ Writechar(BELL, 0);
+ }
+ else if(rc == 0){
+ if(pipe_command[0] == '\0'){
+ cmd_cancelled("Pipe command");
+ break;
+ }
+
+ flagsforhist = (raw ? 0x2 : 0) + (capture ? 0x1 : 0);
+ save_hist(history, pipe_command, flagsforhist, NULL);
+
+ flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
+ flags |= (raw ? PIPE_RAW : 0);
+ if(!capture){
+#ifndef _WINDOWS
+ ClearScreen();
+ fflush(stdout);
+ clear_cursor_pos();
+ ps_global->mangled_screen = 1;
+#endif
+ flags |= PIPE_RESET;
+ }
+
+ if((syspipe = open_system_pipe(pipe_command,
+ (flags&PIPE_RESET) ? NULL : &resultfilename,
+ NULL, flags, 0, pipe_callback, pipe_report_error)) != NULL){
+ int is_text = 0;
+ gf_io_t pc; /* wire up a generic putchar */
+
+ is_text = (a && a->body && a->body->type == TYPETEXT);
+
+ gf_set_writec(&pc, syspipe, 0L, PipeStar,
+ (is_text && !raw) ? WRITE_TO_LOCALE : 0);
+
+ /*------ Write the image to a temporary file ------*/
+ if(raw){ /* pipe raw text */
+ FETCH_READC_S fetch_part;
+
+ err = NULL;
+
+ if(capture)
+ we_cancel = busy_cue(NULL, NULL, 1);
+ else
+ suspend_busy_cue();
+
+ gf_filter_init();
+ fetch_readc_init(&fetch_part, ps_global->mail_stream,
+ msgno, a->number, a->body->size.bytes, 0, 0);
+ gf_link_filter(gf_nvtnl_local, NULL);
+ err = gf_pipe(FETCH_READC, pc);
+
+ if(capture){
+ if(we_cancel)
+ cancel_busy_cue(0);
+ }
+ else
+ resume_busy_cue(0);
+ }
+ else{
+ /* BUG: there's got to be a better way */
+ if(!capture)
+ ps_global->print = (PRINT_S *) 1;
+
+ suspend_busy_cue();
+ err = detach(ps_global->mail_stream, msgno,
+ a->number, 0L, (long *)NULL, pc, NULL, 0);
+ ps_global->print = (PRINT_S *) NULL;
+ resume_busy_cue(0);
+ }
+
+ (void) close_system_pipe(&syspipe, NULL, pipe_callback);
+
+ if(err)
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Error detaching for pipe: %s"), err);
+
+ display_output_file(resultfilename,
+ (err)
+ ? _("PIPE ATTACHMENT (ERROR)")
+ : _("PIPE ATTACHMENT"),
+ NULL, DOF_EMPTY);
+
+ fs_give((void **) &resultfilename);
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error opening pipe"));
+
+ break;
+ }
+ else if(rc == 1){
+ cmd_cancelled("Pipe");
+ break;
+ }
+ else if(rc == 3)
+ help = (help == NO_HELP) ? h_pipe_attach : NO_HELP;
+ }
+}
+
+
+int
+delete_attachment(long int msgno, ATTACH_S *a)
+{
+ int expbits, rv = 0;
+
+ if(!msgno_exceptions(ps_global->mail_stream, msgno,
+ a->number, &expbits, FALSE)
+ || !(expbits & MSG_EX_DELETE)){
+ expbits |= MSG_EX_DELETE;
+ msgno_exceptions(ps_global->mail_stream, msgno,
+ a->number, &expbits, TRUE);
+
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Part %s will be omitted only if message is Saved"),
+ a->number);
+ rv = 1;
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 3, _("Part %s already deleted"),
+ a->number);
+
+ return(rv);
+}
+
+
+int
+undelete_attachment(long int msgno, ATTACH_S *a, int *expbitsp)
+{
+ int rv = 0;
+
+ if(msgno_exceptions(ps_global->mail_stream, msgno,
+ a->number, expbitsp, FALSE)
+ && ((*expbitsp) & MSG_EX_DELETE)){
+ (*expbitsp) ^= MSG_EX_DELETE;
+ msgno_exceptions(ps_global->mail_stream, msgno,
+ a->number, expbitsp, TRUE);
+ rv = 1;
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 3, _("Part %s already UNdeleted"),
+ a->number);
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Resolve any deferred tests for attachment displayability
+
+ Args: attachment structure
+
+ Returns: undefer's attachment's displayability test
+ ----*/
+int
+dispatch_attachment(ATTACH_S *a)
+{
+ if(a->test_deferred){
+ a->test_deferred = 0;
+ a->can_display = mime_can_display(a->body->type, a->body->subtype, a->body);
+ }
+
+ return(a->can_display);
+}
+
+
+#ifdef _WINDOWS
+int
+scroll_att_popup(sparms, in_handle)
+ SCROLL_S *sparms;
+ int in_handle;
+{
+ MPopup scrat_popup[20];
+ int i = -1, n;
+
+ if(in_handle){
+ scrat_popup[++i].type = tIndex;
+ scrat_popup[i].label.style = lNormal;
+ scrat_popup[i].label.string = "View Selectable Item";
+ scrat_popup[i].data.val = ctrl('L');
+ }
+
+ scrat_popup[++i].type = tQueue;
+ scrat_popup[i].label.style = lNormal;
+ scrat_popup[i].label.string = "&Save";
+ scrat_popup[i].data.val = 'S';
+
+ scrat_popup[++i].type = tQueue;
+ scrat_popup[i].label.style = lNormal;
+ if(msgno_exceptions(ps_global->mail_stream,
+ mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap)),
+ scrat_attachp->number, &n, FALSE)
+ && (n & MSG_EX_DELETE)){
+ scrat_popup[i].label.string = "&Undelete";
+ scrat_popup[i].data.val = 'U';
+ }
+ else{
+ scrat_popup[i].label.string = "&Delete";
+ scrat_popup[i].data.val = 'D';
+ }
+
+ if(MIME_MSG_A(scrat_attachp) || MIME_DGST_A(scrat_attachp)){
+ scrat_popup[++i].type = tQueue;
+ scrat_popup[i].label.style = lNormal;
+ scrat_popup[i].label.string = "&Reply";
+ scrat_popup[i].data.val = 'R';
+
+ scrat_popup[++i].type = tQueue;
+ scrat_popup[i].label.style = lNormal;
+ scrat_popup[i].label.string = "&Forward";
+ scrat_popup[i].data.val = 'f';
+ }
+
+ scrat_popup[++i].type = tSeparator;
+
+ scrat_popup[++i].type = tQueue;
+ scrat_popup[i].label.style = lNormal;
+ scrat_popup[i].label.string = "Attachment Index";
+ scrat_popup[i].data.val = '<';
+
+ scrat_popup[++i].type = tTail;
+
+ return(mswin_popup(scrat_popup) == 0 && in_handle);
+}
+
+
+void
+display_att_window(a)
+ ATTACH_S *a;
+{
+#if !defined(DOS) && !defined(OS2)
+ char prefix[8];
+#endif
+
+ if(a->body->type == TYPEMULTIPART){
+ if(a->body->subtype){
+/* if(!strucmp(a->body->subtype, "digest"))
+ display_digest_att(msgno, a, flags);
+ else */
+ q_status_message1(SM_ORDER, 3, 5,
+ "Can't display Multipart/%s",
+ a->body->subtype);
+ }
+ else
+ q_status_message(SM_ORDER, 3, 5,
+ "Can't display unknown Multipart Subtype");
+ }
+/* else if(MIME_VCARD_A(a))
+ display_vcard_att_window(msgno, a, flags);*/
+ else if(a->body->type == TYPETEXT)
+ display_text_att_window(a);
+ else if(a->body->type == TYPEMESSAGE)
+ display_msg_att_window(a);
+}
+
+
+void
+display_text_att_window(a)
+ ATTACH_S *a;
+{
+ STORE_S *store;
+ long msgno;
+
+ msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
+
+ if(store = format_text_att(msgno, a, NULL)){
+ if (mswin_displaytext("ATTACHED TEXT",
+ so_text(store),
+ strlen((char *) so_text(store)),
+ NULL, NULL, 0) >= 0)
+ store->txt = (void *) NULL; /* free'd in mswin_displaytext */
+
+ so_give(&store); /* free resources associated with store */
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Error allocating space for attachment.");
+}
+
+
+void
+display_msg_att_window(a)
+ ATTACH_S *a;
+{
+ STORE_S *store;
+ gf_io_t pc;
+ ATTACH_S *ap = a;
+ long msgno;
+
+ msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
+
+ /* BUG, should check this return code */
+ (void) pine_mail_fetchstructure(ps_global->mail_stream, msgno, NULL);
+
+ /* initialize a storage object */
+ if(store = so_get(CharStar, NULL, EDIT_ACCESS)){
+
+ gf_set_so_writec(&pc, store);
+
+ if(format_msg_att(msgno, &ap, NULL, pc, FM_DISPLAY)
+ && mswin_displaytext("ATTACHED MESSAGE", so_text(store),
+ strlen((char *) so_text(store)),
+ NULL, NULL, 0) >= 0)
+ /* free'd in mswin_displaytext */
+ store->txt = (void *) NULL;
+
+ gf_clear_so_writec(store);
+
+ so_give(&store);
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Error allocating space for message.");
+}
+#endif
diff --git a/alpine/mailpart.h b/alpine/mailpart.h
new file mode 100644
index 00000000..c67cf083
--- /dev/null
+++ b/alpine/mailpart.h
@@ -0,0 +1,43 @@
+/*
+ * $Id: mailpart.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_MAILPART_INCLUDED
+#define PINE_MAILPART_INCLUDED
+
+
+#include "../pith/mailpart.h"
+#include "context.h"
+#include "../pith/state.h"
+
+
+#define DA_SAVE 0x01 /* flags used by display_attachment */
+#define DA_FROM_VIEW 0x02 /* see mailpart.c */
+#define DA_RESIZE 0x04
+#define DA_DIDPROMPT 0x08 /* Already prompted to view att */
+
+
+/* exported protoypes */
+void attachment_screen(struct pine *);
+void write_attachment(int, long, ATTACH_S *, char *);
+int write_attachment_to_file(MAILSTREAM *, long, ATTACH_S *, int, char *);
+int display_attachment(long, ATTACH_S *, int);
+int dispatch_attachment(ATTACH_S *);
+#ifdef _WINDOWS
+void display_att_window(ATTACH_S *);
+#endif
+
+
+#endif /* PINE_MAILPART_INCLUDED */
diff --git a/alpine/mailtrfc.sh b/alpine/mailtrfc.sh
new file mode 100755
index 00000000..456ee817
--- /dev/null
+++ b/alpine/mailtrfc.sh
@@ -0,0 +1,124 @@
+#!/bin/sh
+#
+# $Id: mailtrfc.sh 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+#
+# T H E P I N E M A I L S Y S T E M
+#
+# Laurence Lundblade and Mike Seibel
+# Networks and Distributed Computing
+# Computing and Communications
+# University of Washington
+# Administration Building, AG-44
+# Seattle, Washington, 98195, USA
+# Internet: lgl@CAC.Washington.EDU
+# mikes@CAC.Washington.EDU
+#
+# Please address all bugs and comments to "pine-bugs@cac.washington.edu"
+#
+#
+# Pine and Pico are registered trademarks of the University of Washington.
+# No commercial use of these trademarks may be made without prior written
+# permission of the University of Washington.
+#
+# Pine, Pico, and Pilot software and its included text are Copyright
+# 1989-1996 by the University of Washington.
+#
+# The full text of our legal notices is contained in the file called
+# CPYRIGHT, included with this distribution.
+#
+#
+# Pine is in part based on The Elm Mail System:
+# ***********************************************************************
+# * The Elm Mail System - Revision: 2.13 *
+# * *
+# * Copyright (c) 1986, 1987 Dave Taylor *
+# * Copyright (c) 1988, 1989 USENET Community Trust *
+# ***********************************************************************
+#
+#
+
+
+#
+# mailtrfc.sh -- A shell script to analyze the mail traffic as logged in
+# /usr/spool/mqueue/syslog*. This currently as the University of Washington
+# domains wired in and needs to be made more general. Also, lots more
+# formats of message ID's could be added.
+#
+
+
+
+org=`awk '/^domain/ {print $2}' < /etc/resolv.conf`
+domain=`echo $org | sed -e 's/^[^.]*\.//'`
+host=`hostname`".$org"
+
+echo "Domain: $domain"
+echo "Organization: $org"
+echo "Hostname: $host"
+
+sed -n -e '/message-id/s/^.*</</p' |
+awk 'BEGIN {mailers[0] = "Other";
+ mailers[1] = "Pine";
+ mailers[2] = "MailManager";
+ mailers[3] = "sendmail";
+ mailers[4] = "BITNET";
+ mailers[5] = "? news ?";
+ mailers[6] = "Sprint";
+ mailers[7] = "X.400";
+ mailers[8] = "Mac MS";
+ mailers[9] = "MMDF";
+ mailers[10] = "Andrew";
+ mailers[11] = "Columbia MM";
+ mailers[12] = "Unknown #1";
+ mailers[13] = "EasyMail";
+ mailers[14] = "CompuServe";
+ mailers[15] = "smail";
+ mailers[16] = "MCI Mail";
+ mailers[17] = "VAX MAIL(?)";
+ mailers[18] = "Gator Mail (?)";
+ mailers[19] = "TOTAL";
+ max = 19;}
+ {mailer = 0;}
+ /^<Pine/ {mailer = 1;}
+ /^<MailManager/ {mailer = 2;}
+ /^<[12]?[90]?9[0-9]1?[0-9][1-3]?[0-9]+\.[AaBb][AaBb][0-9]+@/ {mailer = 3;}
+ /^<[0-9A-Z]+@/ {mailer = 4;}
+ /^<199[0-9][A-Za-z]..[0-9]*\./ {mailer = 5;}
+ /@sprint.com/ {mailer = 6;}
+ /\/[A-Z]*=.*\/[A-Z]*=.*/ {mailer = 7;}
+ /^<MacMS\.[0-9]+\.[0-9]+\.[a-z]+@/ {mailer = 8;}
+ /^<MAILQUEUE-[0-9]+\.[0-9]+/ {mailer = 9;}
+ /^<.[d-l][A-Z0-9a-z=_]+00[A-Za-z0-9_=]+@/ {mailer = 10;}
+ /^<CMM\.[0-9]+\.[0-9]+\.[0-9]+/ {mailer = 11 ;}
+ /^<9[0-9][JFMASOND][aepuco][nbrylgptvc][0-9][0-9]?\.[0-9]+[a-z]+\./ {mailer = 12;}
+ /^<EasyMail\.[0-9]+/ {mailer = 13;}
+ /@CompuServe.COM/ {mailer = 14;}
+ /^<m[A-Za-z0-9].....-[0-9A-Za-z].....C@/ {mailer = 15;}
+ /@mcimail.com/ {mailer = 16;}
+ /^<9[0-9][01][0-9][0-3][0-9][0-2][0-9][0-5][0-9][0-5][0-9].[0-9a-z]*@/ {mailer = 17;}
+ /^<0[0-9][0-9]+\.[0-9][0-9][0-9][0-9]+\.[0-9][0-9]+@/ {mailer=18;}
+
+
+
+ '"/$domain>/"' {campus[mailer]++; campus[max]++}
+ '"/$org>/"' {u[mailer]++; u[max]++}
+ '"/$host>/"' {milton[mailer]++; milton[max]++}
+ {total[mailer]++; total[max]++}
+ {if(mailer == 0) printf("-->%s\n",$0)}
+ END {
+ for(m = 0; m <= max; m++) {
+ printf("%-10.10s", mailers[m]);
+ printf(" %11d %11d %11d %11d %11d (%3d%%)\n", milton[m], u[m] - milton[m], campus[m] -u[m], total[m] - campus[m], total[m], (total[m]*100)/total[max]);
+ }
+ printf(" ---- (%3d%%) (%3d%%) (%3d%%) (%3d%%)\n", (milton[max]*100)/total[max], ((u[max] - milton[max])*100)/total[max], ((campus[max] - u[max])*100)/total[max], ((total[max] - campus[max])*100)/total[max], (u[max]*100)/total[max]);
+
+ }' > /tmp/syslogx.$$
+
+
+echo $host $org $domain | \
+ awk '{printf(" %.17s %.11s %.11s Off Campus Total\n", $1, $2, $3)}'
+egrep -v 'TOTAL|----|^-->' /tmp/syslogx.$$ | sort +0.60rn
+egrep 'TOTAL|----' /tmp/syslogx.$$
+grep '^-->' /tmp/syslogx.$$ | sed -e 's/-->//' > other-traffic
+rm -f /tmp/syslogx.$$
+
+
diff --git a/alpine/mailview.c b/alpine/mailview.c
new file mode 100644
index 00000000..35f58339
--- /dev/null
+++ b/alpine/mailview.c
@@ -0,0 +1,5662 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailview.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+
+ mailview.c
+
+ Implements the mailview screen
+ Also includes scrolltool used to display help text
+
+ ====*/
+
+#include "headers.h"
+#include "mailcmd.h"
+#include "mailview.h"
+#include "mailindx.h"
+#include "mailpart.h"
+#include "adrbkcmd.h"
+#include "keymenu.h"
+#include "status.h"
+#include "radio.h"
+#include "help.h"
+#include "imap.h"
+#include "reply.h"
+#include "folder.h"
+#include "alpine.h"
+#include "titlebar.h"
+#include "signal.h"
+#include "send.h"
+#include "dispfilt.h"
+#include "busy.h"
+#include "smime.h"
+#include "../pith/conf.h"
+#include "../pith/filter.h"
+#include "../pith/msgno.h"
+#include "../pith/escapes.h"
+#include "../pith/flag.h"
+#include "../pith/mimedesc.h"
+#include "../pith/url.h"
+#include "../pith/bldaddr.h"
+#include "../pith/mailcmd.h"
+#include "../pith/newmail.h"
+#include "../pith/pipe.h"
+#include "../pith/thread.h"
+#include "../pith/util.h"
+#include "../pith/detoken.h"
+#include "../pith/editorial.h"
+#include "../pith/maillist.h"
+#include "../pith/hist.h"
+#include "../pith/busy.h"
+#include "../pith/list.h"
+#include "../pith/detach.h"
+
+
+/*----------------------------------------------------------------------
+ Saved state for scrolling text
+ ----*/
+typedef struct scroll_text {
+ SCROLL_S *parms; /* Original text (file, char *, char **) */
+ char **text_lines, /* Lines to display */
+ *fname; /* filename of line offsets in "text" */
+ FILE *findex; /* file pointer to line offsets in "text" */
+ short *line_lengths; /* Length of each line in "text_lines" */
+ long top_text_line, /* index in "text_lines" top displayed line */
+ num_lines; /* number of valid pointers in "text_lines" */
+ int lines_allocated; /* size of "text_lines" array */
+ struct {
+ int length, /* count of displayable lines (== PGSIZE) */
+ width, /* width of displayable lines */
+ start_line, /* line number to start painting on */
+ other_lines; /* # of lines not for scroll text */
+ } screen; /* screen parameters */
+} SCRLCTRL_S;
+
+
+typedef struct scroll_file {
+ long offset;
+ int len;
+} SCRLFILE_S;
+
+
+/*
+ * Struct to help write lines do display as they're decoded
+ */
+struct view_write_s {
+ char *line;
+ int index,
+ screen_line,
+ last_screen_line;
+#ifdef _WINDOWS
+ long lines;
+#endif
+ HANDLE_S **handles;
+ STORE_S *store;
+} *g_view_write;
+
+#define LINEBUFSIZ (4096)
+
+#define MAX_FUDGE (1024*1024)
+
+/*
+ * Definitions to help scrolltool
+ */
+#define SCROLL_LINES_ABOVE(X) HEADER_ROWS(X)
+#define SCROLL_LINES_BELOW(X) FOOTER_ROWS(X)
+#define SCROLL_LINES(X) MAX(((X)->ttyo->screen_rows \
+ - SCROLL_LINES_ABOVE(X) - SCROLL_LINES_BELOW(X)), 0)
+#define scroll_text_lines() (scroll_state(SS_CUR)->num_lines)
+
+
+/*
+ * Definitions for various scroll state manager's functions
+ */
+#define SS_NEW 1
+#define SS_CUR 2
+#define SS_FREE 3
+
+
+/*
+ * Handle hints.
+ */
+#define HANDLE_INIT_MSG \
+ _("Selectable items in text -- Use Up/Down Arrows to choose, Return to view")
+#define HANDLE_ABOVE_ERR \
+ _("No selected item displayed -- Use PrevPage to bring choice into view")
+#define HANDLE_BELOW_ERR \
+ _("No selected item displayed -- Use NextPage to bring choice into view")
+
+
+#define PGSIZE(X) (ps_global->ttyo->screen_rows - (X)->screen.other_lines)
+
+#define TYPICAL_BIG_MESSAGE_LINES 200
+
+
+/*
+ * Internal prototypes
+ */
+void view_writec_killbuf(void);
+int view_end_scroll(SCROLL_S *);
+long format_size_guess(BODY *);
+int scroll_handle_prompt(HANDLE_S *, int);
+int scroll_handle_launch(HANDLE_S *, int);
+int scroll_handle_obscured(HANDLE_S *);
+HANDLE_S *scroll_handle_in_frame(long);
+long scroll_handle_reframe(int, int);
+int handle_on_line(long, int);
+int handle_on_page(HANDLE_S *, long, long);
+int scroll_handle_selectable(HANDLE_S *);
+HANDLE_S *scroll_handle_next_sel(HANDLE_S *);
+HANDLE_S *scroll_handle_prev_sel(HANDLE_S *);
+HANDLE_S *scroll_handle_next(HANDLE_S *);
+HANDLE_S *scroll_handle_prev(HANDLE_S *);
+HANDLE_S *scroll_handle_boundary(HANDLE_S *, HANDLE_S *(*)(HANDLE_S *));
+int scroll_handle_column(int, int);
+int scroll_handle_index(int, int);
+void scroll_handle_set_loc(POSLIST_S **, int, int);
+int dot_on_handle(long, int);
+int url_launch(HANDLE_S *);
+int url_launch_too_long(int);
+char *url_external_handler(HANDLE_S *, int);
+void url_mailto_addr(ADDRESS **, char *);
+int url_local_imap(char *);
+int url_local_nntp(char *);
+int url_local_news(char *);
+int url_local_file(char *);
+int url_local_phone_home(char *);
+static int print_to_printer(SCROLL_S *);
+int search_text(int, long, int, char **, Pos *, int *);
+void update_scroll_titlebar(long, int);
+SCRLCTRL_S *scroll_state(int);
+void set_scroll_text(SCROLL_S *, long, SCRLCTRL_S *);
+void redraw_scroll_text(void);
+void zero_scroll_text(void);
+void format_scroll_text(void);
+void ScrollFile(long);
+long make_file_index(void);
+char *scroll_file_line(FILE *, char *, SCRLFILE_S *, int *);
+long scroll_scroll_text(long, HANDLE_S *, int);
+int search_scroll_text(long, int, char *, Pos *, int *);
+char *search_scroll_line(char *, char *, int, int);
+int visible_linelen(int);
+long doubleclick_handle(SCROLL_S *, HANDLE_S *, int *, int *);
+#ifdef _WINDOWS
+int format_message_popup(SCROLL_S *, int);
+int simple_text_popup(SCROLL_S *, int);
+int mswin_readscrollbuf(int);
+int pcpine_do_scroll(int, long);
+char *pcpine_help_scroll(char *);
+int pcpine_view_cursor(int, long);
+#endif
+
+
+
+/*----------------------------------------------------------------------
+ Format a buffer with the text of the current message for browser
+
+ Args: ps - pine state structure
+
+ Result: The scrolltool is called to display the message
+
+ Loop here viewing mail until the folder changed or a command takes
+us to another screen. Inside the loop the message text is fetched and
+formatted into a buffer allocated for it. These are passed to the
+scrolltool(), that displays the message and executes commands. It
+returns when it's time to display a different message, when we
+change folders, when it's time for a different screen, or when
+there are no more messages available.
+ ---*/
+
+void
+mail_view_screen(struct pine *ps)
+{
+ char last_was_full_header = 0;
+ long last_message_viewed = -1L, raw_msgno, offset = 0L;
+ OtherMenu save_what = FirstMenu;
+ int we_cancel = 0, flags, cmd = 0;
+ int force_prefer = 0;
+ MESSAGECACHE *mc;
+ ENVELOPE *env;
+ BODY *body;
+ STORE_S *store;
+ HANDLE_S *handles = NULL;
+ SCROLL_S scrollargs;
+ SourceType src = CharStar;
+
+ dprint((1, "\n\n ----- MAIL VIEW -----\n"));
+
+ ps->prev_screen = mail_view_screen;
+ ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
+
+ if(ps->ttyo->screen_rows - HEADER_ROWS(ps) - FOOTER_ROWS(ps) < 1){
+ q_status_message(SM_ORDER | SM_DING, 0, 3,
+ _("Screen too small to view message"));
+ ps->next_screen = mail_index_screen;
+ return;
+ }
+
+ /*----------------- Loop viewing messages ------------------*/
+ do {
+
+ ps->user_says_cancel = 0;
+ ps->some_quoting_was_suppressed = 0;
+
+ /*
+ * Check total to make sure there's something to view. Check it
+ * inside the loop to make sure everything wasn't expunged while
+ * we were viewing. If so, make sure we don't just come back.
+ */
+ if(mn_get_total(ps->msgmap) <= 0L || !ps->mail_stream){
+ q_status_message(SM_ORDER, 0, 3, _("No messages to read!"));
+ ps->next_screen = mail_index_screen;
+ break;
+ }
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ if(mn_get_cur(ps->msgmap) <= 0L)
+ mn_set_cur(ps->msgmap,
+ THREADING() ? first_sorted_flagged(F_NONE,
+ ps->mail_stream,
+ 0L, FSF_SKIP_CHID)
+ : 1L);
+
+ raw_msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
+ body = NULL;
+ if(raw_msgno == 0L
+ || !(env = pine_mail_fetchstructure(ps->mail_stream,raw_msgno,&body))
+ || !(raw_msgno > 0L && ps->mail_stream
+ && raw_msgno <= ps->mail_stream->nmsgs
+ && (mc = mail_elt(ps->mail_stream, raw_msgno)))){
+ q_status_message1(SM_ORDER, 3, 3,
+ "Error getting message %s data",
+ comatose(mn_get_cur(ps->msgmap)));
+ dprint((1, "!!!! ERROR fetching %s of msg %ld\n",
+ env ? "elt" : "env", mn_get_cur(ps->msgmap)));
+
+ ps->next_screen = mail_index_screen;
+ break;
+ }
+ else
+ ps->unseen_in_view = !mc->seen;
+
+ init_handles(&handles);
+
+ store = so_get(src, NULL, EDIT_ACCESS);
+ so_truncate(store, format_size_guess(body) + 2048);
+
+ view_writec_init(store, &handles, SCROLL_LINES_ABOVE(ps),
+ SCROLL_LINES_ABOVE(ps) +
+ ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps)
+ + SCROLL_LINES_BELOW(ps)));
+
+ flags = FM_DISPLAY;
+ if(last_message_viewed != mn_get_cur(ps->msgmap)
+ || last_was_full_header == 2 || cmd == MC_TOGGLE)
+ flags |= FM_NEW_MESS;
+
+ if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
+ && last_message_viewed != -1L
+ && last_message_viewed != mn_get_cur(ps->msgmap))
+ ps->full_header = 0;
+
+ if(offset) /* no pre-paint during resize */
+ view_writec_killbuf();
+
+#ifdef SMIME
+ /* Attempt to handle S/MIME bodies */
+ if(ps->smime)
+ ps->smime->need_passphrase = 0;
+
+ if(F_OFF(F_DONT_DO_SMIME, ps_global) && fiddle_smime_message(body, raw_msgno))
+ flags |= FM_NEW_MESS; /* body was changed, force a reload */
+#endif
+
+#ifdef _WINDOWS
+ mswin_noscrollupdate(1);
+#endif
+ ps->cur_uid_stream = ps->mail_stream;
+ ps->cur_uid = mail_uid(ps->mail_stream, raw_msgno);
+ (void) format_message(raw_msgno, env, body, &handles, flags | force_prefer,
+ view_writec);
+#ifdef _WINDOWS
+ mswin_noscrollupdate(0);
+#endif
+
+ view_writec_destroy();
+
+ last_message_viewed = mn_get_cur(ps->msgmap);
+ last_was_full_header = ps->full_header;
+
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ memset(&scrollargs, 0, sizeof(SCROLL_S));
+ scrollargs.text.text = so_text(store);
+ scrollargs.text.src = src;
+ scrollargs.text.desc = "message";
+
+ /*
+ * make first selectable handle the default
+ */
+ if(handles){
+ HANDLE_S *hp;
+
+ hp = handles;
+ while(!scroll_handle_selectable(hp) && hp != NULL)
+ hp = hp->next;
+
+ if((scrollargs.text.handles = hp) != NULL)
+ scrollargs.body_valid = (hp == handles);
+ else
+ free_handles(&handles);
+ }
+ else
+ scrollargs.body_valid = 1;
+
+ if(offset){ /* resize? preserve paging! */
+ scrollargs.start.on = Offset;
+ scrollargs.start.loc.offset = offset;
+ scrollargs.body_valid = 0;
+ offset = 0L;
+ }
+
+ scrollargs.use_indexline_color = 1;
+
+ /* TRANSLATORS: a screen title */
+ scrollargs.bar.title = _("MESSAGE TEXT");
+ scrollargs.end_scroll = view_end_scroll;
+ scrollargs.resize_exit = 1;
+ scrollargs.help.text = h_mail_view;
+ scrollargs.help.title = _("HELP FOR MESSAGE TEXT VIEW");
+ scrollargs.keys.menu = &view_keymenu;
+ scrollargs.keys.what = save_what;
+ setbitmap(scrollargs.keys.bitmap);
+ if(F_OFF(F_ENABLE_PIPE, ps_global))
+ clrbitn(VIEW_PIPE_KEY, scrollargs.keys.bitmap);
+
+ /*
+ * turn off attachment viewing for raw msg txt, atts
+ * haven't been set up at this point
+ */
+ if(ps_global->full_header == 2
+ && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
+ clrbitn(VIEW_ATT_KEY, scrollargs.keys.bitmap);
+
+ if(F_OFF(F_ENABLE_BOUNCE, ps_global))
+ clrbitn(BOUNCE_KEY, scrollargs.keys.bitmap);
+
+ if(F_OFF(F_ENABLE_FLAG, ps_global))
+ clrbitn(FLAG_KEY, scrollargs.keys.bitmap);
+
+ if(F_OFF(F_ENABLE_AGG_OPS, ps_global))
+ clrbitn(VIEW_SELECT_KEY, scrollargs.keys.bitmap);
+
+ if(F_OFF(F_ENABLE_FULL_HDR, ps_global))
+ clrbitn(VIEW_FULL_HEADERS_KEY, scrollargs.keys.bitmap);
+
+#ifdef SMIME
+ if(!(ps->smime && ps->smime->need_passphrase))
+ clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
+
+ if(F_ON(F_DONT_DO_SMIME, ps_global)){
+ clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap);
+ clrbitn(SECURITY_KEY, scrollargs.keys.bitmap);
+ }
+#endif
+
+ if(!handles){
+ /*
+ * NOTE: the comment below only really makes sense if we
+ * decide to replace the "Attachment Index" with
+ * a model that lets you highlight the attachments
+ * in the header. In a way its consistent, but
+ * would mean more keymenu monkeybusiness since not
+ * all things would be available all the time. No wait.
+ * Then what would "Save" mean; the attachment, url or
+ * message you're currently viewing? Would Save
+ * of a message then only be possible from the message
+ * index? Clumsy. what about arrow-navigation. isn't
+ * that now inconsistent? Maybe next/prev url shouldn't
+ * be bound to the arrow/^N/^P navigation?
+ */
+ clrbitn(VIEW_VIEW_HANDLE, scrollargs.keys.bitmap);
+ clrbitn(VIEW_PREV_HANDLE, scrollargs.keys.bitmap);
+ clrbitn(VIEW_NEXT_HANDLE, scrollargs.keys.bitmap);
+ }
+
+#ifdef _WINDOWS
+ scrollargs.mouse.popup = format_message_popup;
+#endif
+
+ if(((cmd = scrolltool(&scrollargs)) == MC_RESIZE
+ || (cmd == MC_FULLHDR && ps_global->full_header == 1))
+ && scrollargs.start.on == Offset)
+ offset = scrollargs.start.loc.offset;
+
+ if(cmd == MC_TOGGLE && ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0){
+ if(F_ON(F_PREFER_PLAIN_TEXT, ps_global))
+ ps->force_no_prefer_plain = 1;
+ else
+ ps->force_prefer_plain = 1;
+ }
+ else{
+ ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
+ }
+
+ /*
+ * We could use the values directly but this is the way it
+ * already worked with the flags, so leave it alone.
+ */
+ if(ps->force_prefer_plain == 0 && ps->force_no_prefer_plain == 0)
+ force_prefer = 0;
+ else if(ps->force_prefer_plain)
+ force_prefer = FM_FORCEPREFPLN;
+ else if(ps->force_no_prefer_plain)
+ force_prefer = FM_FORCENOPREFPLN;
+
+ save_what = scrollargs.keys.what;
+ ps_global->unseen_in_view = 0;
+ so_give(&store); /* free resources associated with store */
+ free_handles(&handles);
+#ifdef _WINDOWS
+ mswin_destroyicons();
+#endif
+ }
+ while(ps->next_screen == SCREEN_FUN_NULL);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+
+ ps->force_prefer_plain = ps->force_no_prefer_plain = 0;
+
+ ps->cur_uid_stream = NULL;
+ ps->cur_uid = 0;
+
+ /*
+ * Unless we're going into attachment screen,
+ * start over with full_header.
+ */
+ if(F_OFF(F_QUELL_FULL_HDR_RESET, ps_global)
+ && ps->next_screen != attachment_screen)
+ ps->full_header = 0;
+}
+
+
+
+/*
+ * view_writec_init - function to create and init struct that manages
+ * writing to the display what we can as soon
+ * as we can.
+ */
+void
+view_writec_init(STORE_S *store, HANDLE_S **handlesp, int first_line, int last_line)
+{
+ char tmp[MAILTMPLEN];
+
+ g_view_write = (struct view_write_s *)fs_get(sizeof(struct view_write_s));
+ memset(g_view_write, 0, sizeof(struct view_write_s));
+ g_view_write->store = store;
+ g_view_write->handles = handlesp;
+ g_view_write->screen_line = first_line;
+ g_view_write->last_screen_line = last_line;
+
+ if(!dfilter_trigger(NULL, tmp, sizeof(tmp))){
+ g_view_write->line = (char *) fs_get(LINEBUFSIZ*sizeof(char));
+#ifdef _WINDOWS
+ mswin_beginupdate();
+ scroll_setrange(0L, 0L);
+#endif
+
+ if(ps_global->VAR_DISPLAY_FILTERS)
+ ClearLines(first_line, last_line - 1);
+ }
+}
+
+
+
+void
+view_writec_destroy(void)
+{
+ if(g_view_write){
+ if(g_view_write->line && g_view_write->index)
+ view_writec('\n'); /* flush pending output! */
+
+ while(g_view_write->screen_line < g_view_write->last_screen_line)
+ ClearLine(g_view_write->screen_line++);
+
+ view_writec_killbuf();
+
+ fs_give((void **) &g_view_write);
+ }
+
+#ifdef _WINDOWS
+ mswin_endupdate();
+#endif
+}
+
+
+
+void
+view_writec_killbuf(void)
+{
+ if(g_view_write->line)
+ fs_give((void **) &g_view_write->line);
+}
+
+
+
+/*
+ * view_screen_pc - write chars into the final buffer
+ */
+int
+view_writec(int c)
+{
+ static int in_color = 0;
+
+ if(g_view_write->line){
+ /*
+ * This only works if the 2nd and 3rd parts of the || don't happen.
+ * The only way it breaks is if we get a really, really long line
+ * because there are oodles of tags in it. In that case we will
+ * wrap incorrectly or split a tag across lines (losing color perhaps)
+ * but we won't crash.
+ */
+ if(c == '\n' ||
+ (!in_color &&
+ (char)c != TAG_EMBED &&
+ g_view_write->index >= (LINEBUFSIZ - 20 * (RGBLEN+2))) ||
+ (g_view_write->index >= LINEBUFSIZ - 1)){
+ int rv;
+
+ in_color = 0;
+ suspend_busy_cue();
+ ClearLine(g_view_write->screen_line);
+ if(c != '\n')
+ g_view_write->line[g_view_write->index++] = (char) c;
+
+ PutLine0n8b(g_view_write->screen_line++, 0,
+ g_view_write->line, g_view_write->index,
+ g_view_write->handles ? *g_view_write->handles : NULL);
+
+ resume_busy_cue(0);
+ rv = so_nputs(g_view_write->store,
+ g_view_write->line,
+ g_view_write->index);
+ g_view_write->index = 0;
+
+ if(g_view_write->screen_line >= g_view_write->last_screen_line){
+ fs_give((void **) &g_view_write->line);
+ fflush(stdout);
+ }
+
+ if(!rv)
+ return(0);
+ else if(c != '\n')
+ return(1);
+ }
+ else{
+ /*
+ * Don't split embedded things over multiple lines. Colors are
+ * the longest tags so use their length.
+ */
+ if((char)c == TAG_EMBED)
+ in_color = RGBLEN+1;
+ else if(in_color)
+ in_color--;
+
+ g_view_write->line[g_view_write->index++] = (char) c;
+ return(1);
+ }
+ }
+#ifdef _WINDOWS
+ else if(c == '\n' && g_view_write->lines++ > SCROLL_LINES(ps_global))
+ scroll_setrange(SCROLL_LINES(ps_global),
+ g_view_write->lines + SCROLL_LINES(ps_global));
+#endif
+
+ return(so_writec(c, g_view_write->store));
+}
+
+int
+view_end_scroll(SCROLL_S *sparms)
+{
+ int done = 0, result, force;
+
+ if(F_ON(F_ENABLE_SPACE_AS_TAB, ps_global)){
+
+ if(F_ON(F_ENABLE_TAB_DELETES, ps_global)){
+ long save_msgno;
+
+ /* Let the TAB advance cur msgno for us */
+ save_msgno = mn_get_cur(ps_global->msgmap);
+ (void) cmd_delete(ps_global, ps_global->msgmap, MCMD_NONE, NULL);
+ mn_set_cur(ps_global->msgmap, save_msgno);
+ }
+
+ /* act like the user hit a TAB */
+ result = process_cmd(ps_global, ps_global->mail_stream,
+ ps_global->msgmap, MC_TAB, View, &force);
+
+ if(result == 1)
+ done = 1;
+ }
+
+ return(done);
+}
+
+
+/*
+ * format_size_guess -- Run down the given body summing the text/plain
+ * pieces we're likely to display. It need only
+ * be a guess since this is intended for preallocating
+ * our display buffer...
+ */
+long
+format_size_guess(struct mail_bodystruct *body)
+{
+ long size = 0L;
+ long extra = 0L;
+ char *free_me = NULL;
+
+ if(body){
+ if(body->type == TYPEMULTIPART){
+ PART *part;
+
+ for(part = body->nested.part; part; part = part->next)
+ size += format_size_guess(&part->body);
+ }
+ else if(body->type == TYPEMESSAGE
+ && body->subtype && !strucmp(body->subtype, "rfc822"))
+ size = format_size_guess(body->nested.msg->body);
+ else if((!body->type || body->type == TYPETEXT)
+ && (!body->subtype || !strucmp(body->subtype, "plain"))
+ && ((body->disposition.type
+ && !strucmp(body->disposition.type, "inline"))
+ || !(free_me = parameter_val(body->parameter, "name")))){
+ /*
+ * Handles and colored quotes cause memory overhead. Figure about
+ * 100 bytes per level of quote per line and about 100 bytes per
+ * handle. Make a guess of 1 handle or colored quote per
+ * 10 lines of message. If we guess too low, we'll do a resize in
+ * so_cs_writec. Most of the overhead comes from the colors, so
+ * if we can see we don't do colors or don't have these features
+ * turned on, skip it.
+ */
+ if(pico_usingcolor() &&
+ ((ps_global->VAR_QUOTE1_FORE_COLOR &&
+ ps_global->VAR_QUOTE1_BACK_COLOR) ||
+ F_ON(F_VIEW_SEL_URL, ps_global) ||
+ F_ON(F_VIEW_SEL_URL_HOST, ps_global) ||
+ F_ON(F_SCAN_ADDR, ps_global)))
+ extra = MIN(100/10 * body->size.lines, MAX_FUDGE);
+
+ size = body->size.bytes + extra;
+ }
+
+ if(free_me)
+ fs_give((void **) &free_me);
+ }
+
+ return(size);
+}
+
+
+int
+scroll_handle_prompt(HANDLE_S *handle, int force)
+{
+ char prompt[256], tmp[MAILTMPLEN];
+ int rc, flags, local_h;
+ static ESCKEY_S launch_opts[] = {
+ /* TRANSLATORS: command names, editURL means user gets to edit a URL if they
+ want, editApp is edit application where they edit the application used to
+ view a URL */
+ {'y', 'y', "Y", N_("Yes")},
+ {'n', 'n', "N", N_("No")},
+ {-2, 0, NULL, NULL},
+ {-2, 0, NULL, NULL},
+ {0, 'u', "U", N_("editURL")},
+ {0, 'a', "A", N_("editApp")},
+ {-1, 0, NULL, NULL}};
+
+ if(handle->type == URL){
+ launch_opts[4].ch = 'u';
+
+ if((!(local_h = !struncmp(handle->h.url.path, "x-alpine-", 9))
+ || !(local_h = !struncmp(handle->h.url.path, "x-pine-help", 11)))
+ && (handle->h.url.tool
+ || ((local_h = url_local_handler(handle->h.url.path) != NULL)
+ && (handle->h.url.tool = url_external_handler(handle,1)))
+ || (!local_h
+ && (handle->h.url.tool = url_external_handler(handle,0))))){
+#ifdef _WINDOWS
+ /* if NOT special DDE hack */
+ if(handle->h.url.tool[0] != '*')
+#endif
+ if(ps_global->vars[V_BROWSER].is_fixed)
+ launch_opts[5].ch = -1;
+ else
+ launch_opts[5].ch = 'a';
+ }
+ else{
+ launch_opts[5].ch = -1;
+ if(!local_h){
+ if(ps_global->vars[V_BROWSER].is_fixed){
+ q_status_message(SM_ORDER, 3, 4,
+ _("URL-Viewer is disabled by sys-admin"));
+ return(0);
+ }
+ else{
+ /* TRANSLATORS: a question */
+ if(want_to(_("No URL-Viewer application defined. Define now"),
+ 'y', 0, NO_HELP, WT_SEQ_SENSITIVE) == 'y'){
+ /* Prompt for the displayer? */
+ tmp[0] = '\0';
+ while(1){
+ flags = OE_APPEND_CURRENT |
+ OE_SEQ_SENSITIVE |
+ OE_KEEP_TRAILING_SPACE;
+
+ rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
+ sizeof(tmp),
+ _("Web Browser: "),
+ NULL, NO_HELP, &flags);
+ if(rc == 0){
+ if((flags & OE_USER_MODIFIED) && *tmp){
+ if(can_access(tmp, EXECUTE_ACCESS) == 0){
+ int n;
+ char **l;
+
+ /*
+ * Save it for next time...
+ */
+ for(l = ps_global->VAR_BROWSER, n = 0;
+ l && *l;
+ l++)
+ n++; /* count */
+
+ l = (char **) fs_get((n+2)*sizeof(char *));
+ for(n = 0;
+ ps_global->VAR_BROWSER
+ && ps_global->VAR_BROWSER[n];
+ n++)
+ l[n] = cpystr(ps_global->VAR_BROWSER[n]);
+
+ l[n++] = cpystr(tmp);
+ l[n] = NULL;
+
+ set_variable_list(V_BROWSER, l, TRUE, Main);
+ free_list_array(&l);
+
+ handle->h.url.tool = cpystr(tmp);
+ break;
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 2, 2,
+ _("Browser not found: %s"),
+ error_description(errno));
+ continue;
+ }
+ }
+ else
+ return(0);
+ }
+ else if(rc == 1 || rc == -1){
+ return(0);
+ }
+ else if(rc == 4){
+ if(ps_global->redrawer)
+ (*ps_global->redrawer)();
+ }
+ }
+ }
+ else
+ return(0);
+ }
+ }
+ }
+ }
+ else
+ launch_opts[4].ch = -1;
+
+ if(force
+ || (handle->type == URL
+ && !struncmp(handle->h.url.path, "x-alpine-", 9)
+ || !struncmp(handle->h.url.path, "x-pine-help", 11)))
+ return(1);
+
+ while(1){
+ int sc = ps_global->ttyo->screen_cols;
+
+ /*
+ * Customize the prompt for mailto, all the other schemes make
+ * sense if you just say View selected URL ...
+ */
+ if(handle->type == URL &&
+ !struncmp(handle->h.url.path, "mailto:", 7))
+ snprintf(prompt, sizeof(prompt), "Compose mail to \"%.*s%s\" ? ",
+ MIN(MAX(0,sc - 25), sizeof(prompt)-50), handle->h.url.path+7,
+ (strlen(handle->h.url.path+7) > MAX(0,sc-25)) ? "..." : "");
+ else
+ snprintf(prompt, sizeof(prompt), "View selected %s %s%.*s%s ? ",
+ (handle->type == URL) ? "URL" : "Attachment",
+ (handle->type == URL) ? "\"" : "",
+ MIN(MAX(0,sc-27), sizeof(prompt)-50),
+ (handle->type == URL) ? handle->h.url.path : "",
+ (handle->type == URL)
+ ? ((strlen(handle->h.url.path) > MAX(0,sc-27))
+ ? "...\"" : "\"") : "");
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global),
+ launch_opts, 'y', 'n', NO_HELP, RB_SEQ_SENSITIVE)){
+ case 'y' :
+ return(1);
+
+ case 'u' :
+ strncpy(tmp, handle->h.url.path, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ while(1){
+ flags = OE_APPEND_CURRENT |
+ OE_SEQ_SENSITIVE |
+ OE_KEEP_TRAILING_SPACE;
+
+ rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
+ sizeof(tmp), _("Edit URL: "),
+ NULL, NO_HELP, &flags);
+ if(rc == 0){
+ if(flags & OE_USER_MODIFIED){
+ if(handle->h.url.path)
+ fs_give((void **) &handle->h.url.path);
+
+ handle->h.url.path = cpystr(tmp);
+ }
+
+ break;
+ }
+ else if(rc == 1 || rc == -1){
+ return(0);
+ }
+ else if(rc == 4){
+ if(ps_global->redrawer)
+ (*ps_global->redrawer)();
+ }
+ }
+
+ continue;
+
+ case 'a' :
+ if(handle->h.url.tool){
+ strncpy(tmp, handle->h.url.tool, sizeof(tmp)-1);
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+ else
+ tmp[0] = '\0';
+
+ while(1){
+ flags = OE_APPEND_CURRENT |
+ OE_SEQ_SENSITIVE |
+ OE_KEEP_TRAILING_SPACE |
+ OE_DISALLOW_HELP;
+
+ rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
+ sizeof(tmp), _("Viewer Command: "),
+ NULL, NO_HELP, &flags);
+ if(rc == 0){
+ if(flags & OE_USER_MODIFIED){
+ if(handle->h.url.tool)
+ fs_give((void **) &handle->h.url.tool);
+
+ handle->h.url.tool = cpystr(tmp);
+ }
+
+ break;
+ }
+ else if(rc == 1 || rc == -1){
+ return(0);
+ }
+ else if(rc == 4){
+ if(ps_global->redrawer)
+ (*ps_global->redrawer)();
+ }
+ }
+
+ continue;
+
+ case 'n' :
+ default :
+ return(0);
+ }
+ }
+}
+
+
+
+int
+scroll_handle_launch(HANDLE_S *handle, int force)
+{
+ switch(handle->type){
+ case URL :
+ if(handle->h.url.path){
+ if(scroll_handle_prompt(handle, force)){
+ if(url_launch(handle)
+ || ps_global->next_screen != SCREEN_FUN_NULL)
+ return(1); /* done with this screen */
+ }
+ else
+ return(-1);
+ }
+
+ break;
+
+ case Attach :
+ if(scroll_handle_prompt(handle, force))
+ display_attachment(mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap)),
+ handle->h.attach, DA_FROM_VIEW | DA_DIDPROMPT);
+ else
+ return(-1);
+
+ break;
+
+ case Folder :
+ break;
+
+ case Function :
+ (*handle->h.func.f)(handle->h.func.args.stream,
+ handle->h.func.args.msgmap,
+ handle->h.func.args.msgno);
+ break;
+
+
+ default :
+ panic("Unexpected HANDLE type");
+ }
+
+ return(0);
+}
+
+
+int
+scroll_handle_obscured(HANDLE_S *handle)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+
+ return(handle_on_page(handle, st->top_text_line,
+ st->top_text_line + st->screen.length));
+}
+
+
+
+/*
+ * scroll_handle_in_frame -- return handle pointer to visible handle.
+ */
+HANDLE_S *
+scroll_handle_in_frame(long int top_line)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ HANDLE_S *hp;
+
+ switch(handle_on_page(hp = st->parms->text.handles, top_line,
+ top_line + st->screen.length)){
+ case -1 : /* handle above page */
+ /* Find first handle from top of page */
+ for(hp = st->parms->text.handles->next; hp; hp = hp->next)
+ if(scroll_handle_selectable(hp))
+ switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
+ case 0 : return(hp);
+ case 1 : return(NULL);
+ case -1 : default : break;
+ }
+
+ break;
+
+ case 1 : /* handle below page */
+ /* Find first handle from top of page */
+ for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
+ if(scroll_handle_selectable(hp))
+ switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
+ case 0 : return(hp);
+ case -1 : return(NULL);
+ case 1 : default : break;
+ }
+
+ break;
+
+ case 0 :
+ default :
+ break;
+ }
+
+ return(hp);
+}
+
+/*
+ * scroll_handle_reframe -- adjust display params to display given handle
+ */
+long
+scroll_handle_reframe(int key, int center)
+{
+ long l, offset, dlines, start_line;
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+
+ dlines = PGSIZE(st);
+ offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
+ start_line = st->top_text_line;
+
+ if(key < 0)
+ key = st->parms->text.handles->key;
+
+ /* Searc down from the top line */
+ for(l = start_line; l < st->num_lines; l++) {
+ if(st->parms->text.src == FileStar && l > offset + dlines)
+ ScrollFile(offset += dlines);
+
+ if(handle_on_line(l - offset, key))
+ break;
+ }
+
+ if(l < st->num_lines){
+ if(l >= dlines + start_line) /* bingo! */
+ start_line = l - ((center ? (dlines / 2) : dlines) - 1);
+ }
+ else{
+ if(st->parms->text.src == FileStar) /* wrap offset */
+ ScrollFile(offset = 0);
+
+ for(l = 0; l < start_line; l++) {
+ if(st->parms->text.src == FileStar && l > offset + dlines)
+ ScrollFile(offset += dlines);
+
+ if(handle_on_line(l - offset, key))
+ break;
+ }
+
+ if(l == start_line)
+ panic("Internal Error: no handle found");
+ else
+ start_line = l;
+ }
+
+ return(start_line);
+}
+
+
+int
+handle_on_line(long int line, int goal)
+{
+ int i, n, key;
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+
+ for(i = 0; i < st->line_lengths[line]; i++)
+ if(st->text_lines[line][i] == TAG_EMBED
+ && st->text_lines[line][++i] == TAG_HANDLE){
+ for(key = 0, n = st->text_lines[line][++i]; n; n--)
+ key = (key * 10) + (st->text_lines[line][++i] - '0');
+
+ if(key == goal)
+ return(1);
+ }
+
+ return(0);
+}
+
+
+int
+handle_on_page(HANDLE_S *handle, long int first_line, long int last_line)
+{
+ POSLIST_S *l;
+ int rv = 0;
+
+ if(handle && (l = handle->loc)){
+ for( ; l; l = l->next)
+ if(l->where.row < first_line){
+ if(!rv)
+ rv = -1;
+ }
+ else if(l->where.row >= last_line){
+ if(!rv)
+ rv = 1;
+ }
+ else
+ return(0); /* found! */
+ }
+
+ return(rv);
+}
+
+
+int
+scroll_handle_selectable(HANDLE_S *handle)
+{
+ return(handle && (handle->type != URL
+ || (handle->h.url.path && *handle->h.url.path)));
+}
+
+
+HANDLE_S *
+scroll_handle_next_sel(HANDLE_S *handles)
+{
+ while(handles && !scroll_handle_selectable(handles = handles->next))
+ ;
+
+ return(handles);
+}
+
+
+HANDLE_S *
+scroll_handle_prev_sel(HANDLE_S *handles)
+{
+ while(handles && !scroll_handle_selectable(handles = handles->prev))
+ ;
+
+ return(handles);
+}
+
+
+HANDLE_S *
+scroll_handle_next(HANDLE_S *handles)
+{
+ HANDLE_S *next = NULL;
+
+ if(scroll_handle_obscured(handles) <= 0){
+ next = handles;
+ while((next = scroll_handle_next_sel(next))
+ && scroll_handle_obscured(next))
+ ;
+ }
+
+ return(next);
+}
+
+
+
+HANDLE_S *
+scroll_handle_prev(HANDLE_S *handles)
+{
+ HANDLE_S *prev = NULL;
+
+ if(scroll_handle_obscured(handles) >= 0){
+ prev = handles;
+ while((prev = scroll_handle_prev_sel(prev))
+ && scroll_handle_obscured(prev))
+ ;
+ }
+
+ return(prev);
+}
+
+
+HANDLE_S *
+scroll_handle_boundary(HANDLE_S *handle, HANDLE_S *(*f) (HANDLE_S *))
+{
+ HANDLE_S *hp, *whp = NULL;
+
+ /* Multi-line handle? Punt! */
+ if(handle && (!(hp = handle)->loc->next))
+ while((hp = (*f)(hp))
+ && hp->loc->where.row == handle->loc->where.row)
+ whp = hp;
+
+ return(whp);
+}
+
+
+int
+scroll_handle_column(int line, int offset)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ int i, n, col;
+ int key, limit;
+
+ limit = (offset > -1) ? offset : st->line_lengths[line];
+
+ for(i = 0, col = 0; i < limit;){
+ if(st && st->text_lines && st->text_lines[line])
+ switch(st->text_lines[line][i]){
+ case TAG_EMBED:
+ i++;
+ switch((i < limit) ? st->text_lines[line][i] : 0){
+ case TAG_HANDLE:
+ for(key = 0, n = st->text_lines[line][++i]; n > 0 && i < limit-1; n--)
+ key = (key * 10) + (st->text_lines[line][++i] - '0');
+
+ i++;
+ break;
+
+ case TAG_FGCOLOR :
+ case TAG_BGCOLOR :
+ i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
+ break;
+
+ case TAG_INVON:
+ case TAG_INVOFF:
+ case TAG_BOLDON:
+ case TAG_BOLDOFF:
+ case TAG_ULINEON:
+ case TAG_ULINEOFF:
+ i++;
+ break;
+
+ default: /* literal embed char */
+ break;
+ }
+
+ break;
+
+ case TAB:
+ i++;
+ while(((++col) & 0x07) != 0) /* add tab's spaces */
+ ;
+
+ break;
+
+ default:
+ col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
+ st->line_lengths[line] - i);
+ i++;
+ break;
+ }
+ }
+
+ return(col);
+}
+
+
+int
+scroll_handle_index(int row, int column)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ int index = 0;
+
+ for(index = 0; column > 0;)
+ switch(st->text_lines[row][index++]){
+ case TAG_EMBED :
+ switch(st->text_lines[row][index++]){
+ case TAG_HANDLE:
+ index += st->text_lines[row][index] + 1;
+ break;
+
+ case TAG_FGCOLOR :
+ case TAG_BGCOLOR :
+ index += RGBLEN;
+ break;
+
+ default :
+ break;
+ }
+
+ break;
+
+ case TAB : /* add tab's spaces */
+ while(((--column) & 0x07) != 0)
+ ;
+
+ break;
+
+ default :
+ column--;
+ break;
+ }
+
+ return(index);
+}
+
+
+void
+scroll_handle_set_loc(POSLIST_S **lpp, int line, int column)
+{
+ POSLIST_S *lp;
+
+ lp = (POSLIST_S *) fs_get(sizeof(POSLIST_S));
+ lp->where.row = line;
+ lp->where.col = column;
+ lp->next = NULL;
+ for(; *lpp; lpp = &(*lpp)->next)
+ ;
+
+ *lpp = lp;
+}
+
+
+int
+dot_on_handle(long int line, int goal)
+{
+ int i = 0, n, key = 0, column = -1;
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+
+ do{
+ if(i >= st->line_lengths[line])
+ return(0);
+
+ switch(st->text_lines[line][i++]){
+ case TAG_EMBED :
+ switch(st->text_lines[line][i++]){
+ case TAG_HANDLE :
+ for(key = 0, n = st->text_lines[line][i++]; n; n--)
+ key = (key * 10) + (st->text_lines[line][i++] - '0');
+
+ break;
+
+ case TAG_FGCOLOR :
+ case TAG_BGCOLOR :
+ i += RGBLEN; /* advance past color setting */
+ break;
+
+ case TAG_BOLDOFF :
+ key = 0;
+ break;
+ }
+
+ break;
+
+ case TAB :
+ while(((++column) & 0x07) != 0) /* add tab's spaces */
+ ;
+
+ break;
+
+ default :
+ column += width_at_this_position((unsigned char*) &st->text_lines[line][i-1],
+ st->line_lengths[line] - (i-1));
+ break;
+ }
+ }
+ while(column < goal);
+
+ return(key);
+}
+
+
+/*
+ * url_launch - Sniff the given url, see if we can do anything with
+ * it. If not, hand off to user-defined app.
+ *
+ */
+int
+url_launch(HANDLE_S *handle)
+{
+ int rv = 0;
+ url_tool_t f;
+#define URL_MAX_LAUNCH (2 * MAILTMPLEN)
+
+ if(handle->h.url.tool){
+ char *toolp, *cmdp, *p, *q, cmd[URL_MAX_LAUNCH + 4];
+ char *left_double_quote, *right_double_quote;
+ int mode, quotable = 0, copied = 0, double_quoted = 0;
+ int escape_single_quotes = 0;
+ PIPE_S *syspipe;
+
+ toolp = handle->h.url.tool;
+
+ /*
+ * Figure out if we need to quote the URL. If there are shell
+ * metacharacters in it we want to quote it, because we don't want
+ * the shell to interpret them. However, if the user has already
+ * quoted the URL in the command definition we don't want to quote
+ * again. So, we try to see if there are a pair of unescaped
+ * quotes surrounding _URL_ in the cmd.
+ * If we quote when we shouldn't have, it'll cause it not to work.
+ * If we don't quote when we should have, it's a possible security
+ * problem (and it still won't work).
+ *
+ * In bash and ksh $( executes a command, so we use single quotes
+ * instead of double quotes to do our quoting. If configured command
+ * is double-quoted we change that to single quotes.
+ */
+#ifdef _WINDOWS
+ /*
+ * It should be safe to not quote any of the characters from the
+ * string below. It was quoting with '?' and '&' in a URL, which is
+ * unnecessary. Also the quoting code below only quotes with
+ * ' (single quote), so we'd want it to at least do double quotes
+ * instead, for Windows.
+ */
+ if(toolp)
+ quotable = 0; /* always never quote */
+ else
+#endif
+ /* quote shell specials */
+ if(strpbrk(handle->h.url.path, "&*;<>?[]|~$(){}'\"") != NULL){
+ escape_single_quotes++;
+ if((p = strstr(toolp, "_URL_")) != NULL){ /* explicit arg? */
+ int in_quote = 0;
+
+ /* see whether or not it is already quoted */
+
+ quotable = 1;
+
+ for(q = toolp; q < p; q++)
+ if(*q == '\'' && (q == toolp || q[-1] != '\\'))
+ in_quote = 1 - in_quote;
+
+ if(in_quote){
+ for(q = p+5; *q; q++)
+ if(*q == '\'' && q[-1] != '\\'){
+ /* already single quoted, leave it alone */
+ quotable = 0;
+ break;
+ }
+ }
+
+ if(quotable){
+ in_quote = 0;
+ for(q = toolp; q < p; q++)
+ if(*q == '\"' && (q == toolp || q[-1] != '\\')){
+ in_quote = 1 - in_quote;
+ if(in_quote)
+ left_double_quote = q;
+ }
+
+ if(in_quote){
+ for(q = p+5; *q; q++)
+ if(*q == '\"' && q[-1] != '\\'){
+ /* we'll replace double quotes with singles */
+ double_quoted = 1;
+ right_double_quote = q;
+ break;
+ }
+ }
+ }
+ }
+ else
+ quotable = 1;
+ }
+ else
+ quotable = 0;
+
+ /* Build the command */
+ cmdp = cmd;
+ while(cmdp-cmd < URL_MAX_LAUNCH)
+ if((!*toolp && !copied)
+ || (*toolp == '_' && !strncmp(toolp + 1, "URL_", 4))){
+
+ /* implicit _URL_ at end */
+ if(!*toolp)
+ *cmdp++ = ' ';
+
+ /* add single quotes */
+ if(quotable && !double_quoted && cmdp-cmd < URL_MAX_LAUNCH)
+ *cmdp++ = '\'';
+
+ copied = 1;
+ /*
+ * If the url.path contains a single quote we should escape
+ * that single quote to remove its special meaning.
+ * Since backslash-quote is ignored inside single quotes we
+ * close the quotes, backslash escape the quote, then open
+ * the quotes again. So
+ * 'fred's car'
+ * becomes 'fred'\''s car'
+ */
+ for(p = handle->h.url.path;
+ p && *p && cmdp-cmd < URL_MAX_LAUNCH; p++){
+ if(escape_single_quotes && *p == '\''){
+ *cmdp++ = '\''; /* closing quote */
+ *cmdp++ = '\\';
+ *cmdp++ = '\''; /* opening quote comes from p below */
+ }
+
+ *cmdp++ = *p;
+ }
+
+ if(quotable && !double_quoted && cmdp-cmd < URL_MAX_LAUNCH){
+ *cmdp++ = '\'';
+ *cmdp = '\0';
+ }
+
+ if(*toolp)
+ toolp += 5; /* length of "_URL_" */
+ }
+ else{
+ /* replace double quotes with single quotes */
+ if(double_quoted &&
+ (toolp == left_double_quote || toolp == right_double_quote)){
+ *cmdp++ = '\'';
+ toolp++;
+ }
+ else if(!(*cmdp++ = *toolp++))
+ break;
+ }
+
+
+ if(cmdp-cmd >= URL_MAX_LAUNCH)
+ return(url_launch_too_long(rv));
+
+ mode = PIPE_RESET | PIPE_USER | PIPE_RUNNOW ;
+ if((syspipe = open_system_pipe(cmd, NULL, NULL, mode, 0, pipe_callback, pipe_report_error)) != NULL){
+ close_system_pipe(&syspipe, NULL, pipe_callback);
+ q_status_message(SM_ORDER, 0, 4, _("VIEWER command completed"));
+ }
+ else
+ q_status_message1(SM_ORDER, 3, 4,
+ /* TRANSLATORS: Cannot start command : <command name> */
+ _("Cannot start command : %s"), cmd);
+ }
+ else if((f = url_local_handler(handle->h.url.path)) != NULL){
+ if((*f)(handle->h.url.path) > 1)
+ rv = 1; /* done! */
+ }
+ else
+ q_status_message1(SM_ORDER, 2, 2,
+ _("\"URL-Viewer\" not defined: Can't open %s"),
+ handle->h.url.path);
+
+ return(rv);
+}
+
+
+int
+url_launch_too_long(int return_value)
+{
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "Can't spawn. Command too long.");
+ return(return_value);
+}
+
+
+char *
+url_external_handler(HANDLE_S *handle, int specific)
+{
+ char **l, *test, *cmd, *p, *q, *ep;
+ int i, specific_match;
+
+ for(l = ps_global->VAR_BROWSER ; l && *l; l++){
+ get_pair(*l, &test, &cmd, 0, 1);
+ dprint((5, "TEST: \"%s\" CMD: \"%s\"\n",
+ test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
+ removing_quotes(cmd);
+ if(valid_filter_command(&cmd)){
+ specific_match = 0;
+
+ if((p = test) != NULL){
+ while(*p && cmd)
+ if(*p == '_'){
+ if(!strncmp(p+1, "TEST(", 5)
+ && (ep = strstr(p+6, ")_"))){
+ *ep = '\0';
+
+ if(exec_mailcap_test_cmd(p+6) == 0){
+ p = ep + 2;
+ }
+ else{
+ dprint((5,"failed handler TEST\n"));
+ fs_give((void **) &cmd);
+ }
+ }
+ else if(!strncmp(p+1, "SCHEME(", 7)
+ && (ep = strstr(p+8, ")_"))){
+ *ep = '\0';
+
+ p += 8;
+ do
+ if((q = strchr(p, ',')) != NULL)
+ *q++ = '\0';
+ else
+ q = ep;
+ while(!((i = strlen(p))
+ && ((p[i-1] == ':'
+ && handle->h.url.path[i - 1] == ':')
+ || (p[i-1] != ':'
+ && handle->h.url.path[i] == ':'))
+ && !struncmp(handle->h.url.path, p, i))
+ && *(p = q));
+
+ if(*p){
+ specific_match = 1;
+ p = ep + 2;
+ }
+ else{
+ dprint((5,"failed handler SCHEME\n"));
+ fs_give((void **) &cmd);
+ }
+ }
+ else{
+ dprint((5, "UNKNOWN underscore test\n"));
+ fs_give((void **) &cmd);
+ }
+ }
+ else if(isspace((unsigned char) *p)){
+ p++;
+ }
+ else{
+ dprint((1,"bogus handler test: \"%s\"",
+ test ? test : "?"));
+ fs_give((void **) &cmd);
+ }
+
+ fs_give((void **) &test);
+ }
+
+ if(cmd && (!specific || specific_match))
+ return(cmd);
+ }
+
+ if(test)
+ fs_give((void **) &test);
+
+ if(cmd)
+ fs_give((void **) &cmd);
+ }
+
+ cmd = NULL;
+
+ if(!specific){
+ cmd = url_os_specified_browser(handle->h.url.path);
+ /*
+ * Last chance, anything handling "text/html" in mailcap...
+ */
+ if(!cmd && mailcap_can_display(TYPETEXT, "html", NULL, 0)){
+ MCAP_CMD_S *mc_cmd;
+
+ mc_cmd = mailcap_build_command(TYPETEXT, "html",
+ NULL, "_URL_", NULL, 0);
+ /*
+ * right now URL viewing won't return anything requiring
+ * special handling
+ */
+ if(mc_cmd){
+ cmd = mc_cmd->command;
+ fs_give((void **)&mc_cmd);
+ }
+ }
+ }
+
+ return(cmd);
+}
+
+
+url_tool_t
+url_local_handler(char *s)
+{
+ int i;
+ static struct url_t {
+ char *url;
+ short len;
+ url_tool_t f;
+ } handlers[] = {
+ {"mailto:", 7, url_local_mailto}, /* see url_tool_t def's */
+ {"imap://", 7, url_local_imap}, /* for explanations */
+ {"nntp://", 7, url_local_nntp},
+ {"file://", 7, url_local_file},
+#ifdef ENABLE_LDAP
+ {"ldap://", 7, url_local_ldap},
+#endif
+ {"news:", 5, url_local_news},
+ {"x-alpine-gripe:", 15, gripe_gripe_to},
+ {"x-alpine-help:", 14, url_local_helper},
+ {"x-pine-help:", 12, url_local_helper},
+ {"x-alpine-phone-home:", 20, url_local_phone_home},
+ {"x-alpine-config:", 16, url_local_config},
+ {"x-alpine-cert:", 14, url_local_certdetails},
+ {"#", 1, url_local_fragment},
+ {NULL, 0, NULL}
+ };
+
+ for(i = 0; handlers[i].url ; i++)
+ if(!struncmp(s, handlers[i].url, handlers[i].len))
+ return(handlers[i].f);
+
+ return(NULL);
+}
+
+
+
+/*
+ * mailto URL digester ala draft-hoffman-mailto-url-02.txt
+ */
+int
+url_local_mailto(char *url)
+{
+ return(url_local_mailto_and_atts(url, NULL));
+}
+
+int
+url_local_mailto_and_atts(char *url, PATMT *attachlist)
+{
+ ENVELOPE *outgoing;
+ BODY *body = NULL;
+ REPLY_S fake_reply;
+ char *sig, *urlp, *p, *hname, *hvalue;
+ int rv = 0;
+ long rflags;
+ int was_a_body = 0, impl, template_len = 0;
+ char *fcc = NULL;
+ PAT_STATE dummy;
+ REDRAFT_POS_S *redraft_pos = NULL;
+ ACTION_S *role = NULL;
+
+ outgoing = mail_newenvelope();
+ outgoing->message_id = generate_message_id();
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ if((body->contents.text.data = (void *) so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){
+ /*
+ * URL format is:
+ *
+ * mailtoURL = "mailto:" [ to ] [ headers ]
+ * to = #mailbox
+ * headers = "?" header *( "&" header )
+ * header = hname "=" hvalue
+ * hname = *urlc
+ * hvalue = *urlc
+ *
+ * NOTE2: "from" and "bcc" are intentionally excluded from
+ * the list of understood "header" fields
+ */
+ if((p = strchr(urlp = cpystr(url+7), '?')) != NULL)
+ *p++ = '\0'; /* headers? Tie off mailbox */
+
+ /* grok mailbox as first "to", then roll thru specific headers */
+ if(*urlp)
+ rfc822_parse_adrlist(&outgoing->to,
+ rfc1738_str(urlp),
+ ps_global->maildomain);
+
+ while(p){
+ /* Find next "header" */
+ if((p = strchr(hname = p, '&')) != NULL)
+ *p++ = '\0'; /* tie off "header" */
+
+ if((hvalue = strchr(hname, '=')) != NULL)
+ *hvalue++ = '\0'; /* tie off hname */
+
+ if(!hvalue || !strucmp(hname, "subject")){
+ char *sub = rfc1738_str(hvalue ? hvalue : hname);
+
+ if(outgoing->subject){
+ int len = strlen(outgoing->subject);
+
+ fs_resize((void **) &outgoing->subject,
+ (len + strlen(sub) + 2) * sizeof(char));
+ snprintf(outgoing->subject + len, strlen(sub)+2, " %s", sub);
+ outgoing->subject[len + strlen(sub) + 2 - 1] = '\0';
+ }
+ else
+ outgoing->subject = cpystr(sub);
+ }
+ else if(!strucmp(hname, "to")){
+ url_mailto_addr(&outgoing->to, hvalue);
+ }
+ else if(!strucmp(hname, "cc")){
+ url_mailto_addr(&outgoing->cc, hvalue);
+ }
+ else if(!strucmp(hname, "bcc")){
+ q_status_message(SM_ORDER, 3, 4,
+ "\"Bcc\" header in mailto url ignored");
+ }
+ else if(!strucmp(hname, "from")){
+ q_status_message(SM_ORDER, 3, 4,
+ "\"From\" header in mailto url ignored");
+ }
+ else if(!strucmp(hname, "body")){
+ char *sub = rfc1738_str(hvalue ? hvalue : "");
+
+ so_puts((STORE_S *)body->contents.text.data, sub);
+ so_puts((STORE_S *)body->contents.text.data, NEWLINE);
+ was_a_body++;
+ }
+ }
+
+ fs_give((void **) &urlp);
+
+ rflags = ROLE_COMPOSE;
+ if(nonempty_patterns(rflags, &dummy)){
+ role = set_role_from_msg(ps_global, rflags, -1L, NULL);
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel */
+ role = NULL;
+ cmd_cancelled("Composition");
+ goto outta_here;
+ }
+ }
+
+ if(role)
+ q_status_message1(SM_ORDER, 3, 4, "Composing using role \"%s\"",
+ role->nick);
+
+ if(!was_a_body && role && role->template){
+ char *filtered;
+
+ impl = 1; /* leave cursor in header if not explicit */
+ filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
+ if(filtered){
+ if(*filtered){
+ so_puts((STORE_S *)body->contents.text.data, filtered);
+ if(impl == 1)
+ template_len = strlen(filtered);
+ }
+
+ fs_give((void **)&filtered);
+ }
+ }
+ else
+ impl = 1;
+
+ if(!was_a_body && (sig = detoken(role, NULL, 2, 0, 1, &redraft_pos,
+ &impl))){
+ if(impl == 2)
+ redraft_pos->offset += template_len;
+
+ if(*sig)
+ so_puts((STORE_S *)body->contents.text.data, sig);
+
+ fs_give((void **)&sig);
+ }
+
+ memset((void *)&fake_reply, 0, sizeof(fake_reply));
+ fake_reply.pseudo = 1;
+ fake_reply.data.pico_flags = (outgoing->subject) ? P_BODY : P_HEADEND;
+
+
+ if(!(role && role->fcc))
+ fcc = get_fcc_based_on_to(outgoing->to);
+
+ if(attachlist)
+ create_message_body(&body, attachlist, 0);
+
+ pine_send(outgoing, &body, "\"MAILTO\" COMPOSE",
+ role, fcc, &fake_reply, redraft_pos, NULL, NULL, PS_STICKY_TO);
+ rv++;
+ ps_global->mangled_screen = 1;
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Can't create space for composer"));
+
+outta_here:
+ if(outgoing)
+ mail_free_envelope(&outgoing);
+
+ if(body)
+ pine_free_body(&body);
+
+ if(fcc)
+ fs_give((void **)&fcc);
+
+ free_redraft_pos(&redraft_pos);
+ free_action(&role);
+
+ return(rv);
+}
+
+
+void
+url_mailto_addr(struct mail_address **a, char *a_raw)
+{
+ char *p = cpystr(rfc1738_str(a_raw));
+
+ while(*a) /* append to address list */
+ a = &(*a)->next;
+
+ rfc822_parse_adrlist(a, p, ps_global->maildomain);
+ fs_give((void **) &p);
+}
+
+
+/*
+ * imap URL digester ala RFC 2192
+ */
+int
+url_local_imap(char *url)
+{
+ char *folder, *mailbox = NULL, *errstr = NULL, *search = NULL,
+ newfolder[MAILTMPLEN];
+ int rv;
+ long i;
+ imapuid_t uid = 0L, uid_val = 0L;
+ CONTEXT_S *fake_context;
+ MESSAGECACHE *mc;
+
+ rv = url_imap_folder(url, &folder, &uid, &uid_val, &search, 0);
+ switch(rv & URL_IMAP_MASK){
+ case URL_IMAP_IMAILBOXLIST :
+/* BUG: deal with lsub tag */
+ if((fake_context = new_context(folder, NULL)) != NULL){
+ newfolder[0] = '\0';
+ if(display_folder_list(&fake_context, newfolder,
+ 0, folders_for_goto))
+ if(strlen(newfolder) + 1 < MAILTMPLEN)
+ mailbox = newfolder;
+ }
+ else
+ errstr = "Problem building URL's folder list";
+
+ fs_give((void **) &folder);
+ free_context(&fake_context);
+
+ if(mailbox){
+ return(1);
+ }
+ else if(errstr)
+ q_status_message(SM_ORDER|SM_DING, 3, 3, errstr);
+ else
+ cmd_cancelled("URL Launch");
+
+ break;
+
+ case URL_IMAP_IMESSAGEPART :
+ case URL_IMAP_IMESSAGELIST :
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ rv = do_broach_folder(folder, NULL, NULL, 0L);
+ fs_give((void **) &folder);
+ switch(rv){
+ case -1 : /* utter failure */
+ ps_global->next_screen = main_menu_screen;
+ break;
+
+ case 0 : /* same folder reopened */
+ ps_global->next_screen = mail_index_screen;
+ break;
+
+ case 1 : /* requested folder open */
+ ps_global->next_screen = mail_index_screen;
+
+ if(uid_val && uid_val != ps_global->mail_stream->uid_validity){
+ /* Complain! */
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ "Warning! Referenced folder changed since URL recorded");
+ }
+
+ if(uid){
+ /*
+ * Make specified message the currently selected..
+ */
+ for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
+ if(mail_uid(ps_global->mail_stream, i) == uid){
+ ps_global->next_screen = mail_view_screen;
+ mn_set_cur(ps_global->msgmap, i);
+ break;
+ }
+
+ if(i > mn_get_total(ps_global->msgmap))
+ q_status_message(SM_ORDER, 2, 3,
+ "Couldn't find specified article number");
+ }
+ else if(search){
+ /*
+ * Select the specified messages
+ * and present a zoom'd index...
+ */
+/* BUG: not dealing with CHARSET yet */
+
+/* ANOTHER BUG: mail_criteria is a compatibility routine for IMAP2BIS
+ * so it doesn't know about IMAP4 search criteria, like SENTSINCE.
+ * It also doesn't handle literals. */
+
+ pine_mail_search_full(ps_global->mail_stream, NULL,
+ mail_criteria(search),
+ SE_NOPREFETCH | SE_FREE);
+
+ for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
+ if(ps_global->mail_stream
+ && i <= ps_global->mail_stream->nmsgs
+ && (mc = mail_elt(ps_global->mail_stream, i))
+ && mc->searched)
+ set_lflag(ps_global->mail_stream,
+ ps_global->msgmap, i, MN_SLCT, 1);
+
+ if((i = any_lflagged(ps_global->msgmap, MN_SLCT)) != 0){
+
+ q_status_message2(SM_ORDER, 0, 3,
+ "%s message%s selected",
+ long2string(i), plural(i));
+ /* Zoom the index! */
+ zoom_index(ps_global, ps_global->mail_stream,
+ ps_global->msgmap, MN_SLCT);
+ }
+ }
+ }
+
+ return(1);
+
+ default:
+ case URL_IMAP_ERROR :
+ break;
+ }
+
+ return(0);
+}
+
+
+int
+url_local_nntp(char *url)
+{
+ char folder[2*MAILTMPLEN], *group;
+ int group_len;
+ long i, article_num;
+
+ /* no hostport, no url, end of story */
+ if((group = strchr(url + 7, '/')) != 0){
+ group++;
+ for(group_len = 0; group[group_len] && group[group_len] != '/';
+ group_len++)
+ if(!rfc1738_group(&group[group_len]))
+ /* TRANSLATORS: these are errors in news group URLs */
+ return(url_bogus(url, _("Invalid newsgroup specified")));
+
+ if(group_len){
+ snprintf(folder, sizeof(folder), "{%.*s/nntp}#news.%.*s",
+ MIN((group - 1) - (url + 7), MAILTMPLEN-20), url + 7,
+ MIN(group_len, MAILTMPLEN-20), group);
+ folder[sizeof(folder)-1] = '\0';
+ }
+ else
+ return(url_bogus(url, _("No newsgroup specified")));
+ }
+ else
+ return(url_bogus(url, _("No server specified")));
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ switch(do_broach_folder(rfc1738_str(folder), NULL, NULL, 0L)){
+ case -1 : /* utter failure */
+ ps_global->next_screen = main_menu_screen;
+ break;
+
+ case 0 : /* same folder reopened */
+ ps_global->next_screen = mail_index_screen;
+ break;
+
+ case 1 : /* requested folder open */
+ ps_global->next_screen = mail_index_screen;
+
+ /* grok article number --> c-client UID */
+ if(group[group_len++] == '/'
+ && (article_num = atol(&group[group_len]))){
+ /*
+ * Make the requested article our current message
+ */
+ for(i = 1; i <= mn_get_nmsgs(ps_global->msgmap); i++)
+ if(mail_uid(ps_global->mail_stream, i) == article_num){
+ ps_global->next_screen = mail_view_screen;
+ if((i = mn_raw2m(ps_global->msgmap, i)) != 0)
+ mn_set_cur(ps_global->msgmap, i);
+ break;
+ }
+
+ if(i == 0 || i > mn_get_total(ps_global->msgmap))
+ q_status_message(SM_ORDER, 2, 3,
+ _("Couldn't find specified article number"));
+ }
+
+ break;
+ }
+
+ ps_global->redrawer = (void(*)(void))NULL;
+ return(1);
+}
+
+
+int
+url_local_news(char *url)
+{
+ char folder[MAILTMPLEN], *p;
+ CONTEXT_S *cntxt = NULL;
+
+ /*
+ * NOTE: NO SUPPORT for '*' or message-id
+ */
+ if(*(url+5)){
+ if(*(url+5) == '/' && *(url+6) == '/')
+ return(url_local_nntp(url)); /* really meant "nntp://"? */
+
+ if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0])
+ /*
+ * BUG: Only the first NNTP server is tried.
+ */
+ snprintf(folder, sizeof(folder), "{%s/nntp}#news.", ps_global->VAR_NNTP_SERVER[0]);
+ else
+ strncpy(folder, "#news.", sizeof(folder));
+
+ folder[sizeof(folder)-1] = '\0';
+
+ for(p = strncpy(folder + strlen(folder), url + 5, sizeof(folder)-strlen(folder)-1);
+ *p && rfc1738_group(p);
+ p++)
+ ;
+
+ if(*p)
+ return(url_bogus(url, "Invalid newsgroup specified"));
+ }
+ else{ /* fish first group from newsrc */
+ FOLDER_S *f;
+ int alphaorder;
+
+ folder[0] = '\0';
+
+ /* Find first news context */
+ for(cntxt = ps_global->context_list;
+ cntxt && !(cntxt->use & CNTXT_NEWS);
+ cntxt = cntxt->next)
+ ;
+
+ if(cntxt){
+ if((alphaorder = F_OFF(F_READ_IN_NEWSRC_ORDER, ps_global)) != 0)
+ (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 1);
+
+ build_folder_list(NULL, cntxt, NULL, NULL, BFL_LSUB);
+ if((f = folder_entry(0, FOLDERS(cntxt))) != NULL){
+ strncpy(folder, f->name, sizeof(folder));
+ folder[sizeof(folder)-1] = '\0';
+ }
+
+ free_folder_list(cntxt);
+
+ if(alphaorder)
+ (void) F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 0);
+ }
+
+ if(folder[0] == '\0'){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ "No default newsgroup");
+ return(0);
+ }
+ }
+
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ if(do_broach_folder(rfc1738_str(folder), cntxt, NULL, 0L) < 0)
+ ps_global->next_screen = main_menu_screen;
+ else
+ ps_global->next_screen = mail_index_screen;
+
+ ps_global->redrawer = (void(*)(void))NULL;
+
+ return(1);
+}
+
+
+int
+url_local_file(char *file_url)
+{
+ if(want_to(
+ /* TRANSLATORS: this is a warning that the file URL can cause programs to run which may
+ be a security problem. We are asking the user to confirm that they want to do this. */
+ _("\"file\" URL may cause programs to be run on your system. Run anyway"),
+ 'n', 0, NO_HELP, WT_NORM) == 'y'){
+ HANDLE_S handle;
+
+ /* fake a handle */
+ handle.h.url.path = file_url;
+ if((handle.h.url.tool = url_external_handler(&handle, 1))
+ || (handle.h.url.tool = url_external_handler(&handle, 0))){
+ url_launch(&handle);
+ return 1;
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 4,
+ _("No viewer for \"file\" URL. VIEWER command cancelled"));
+ return 0;
+ }
+ }
+ q_status_message(SM_ORDER, 0, 4, _("VIEWER command cancelled"));
+ return 0;
+}
+
+
+int
+url_local_fragment(char *fragment)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ HANDLE_S *hp;
+
+ /*
+ * find a handle with the fragment's name
+ */
+ for(hp = st->parms->text.handles; hp; hp = hp->next)
+ if(hp->type == URL && hp->h.url.name
+ && !strcmp(hp->h.url.name, fragment + 1))
+ break;
+
+ if(!hp)
+ for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
+ if(hp->type == URL && hp->h.url.name
+ && !strcmp(hp->h.url.name, fragment + 1))
+ break;
+
+ /*
+ * set the top line of the display to contain this line
+ */
+ if(hp && hp->loc){
+ st->top_text_line = hp->loc->where.row;
+ ps_global->mangled_body = 1;
+ }
+ else
+ q_status_message1(SM_ORDER | SM_DING, 0, 3,
+ "Can't find fragment: %s", fragment);
+
+ return(1);
+}
+
+
+int
+url_local_phone_home(char *url)
+{
+ phone_home(url + strlen("x-alpine-phone-home:"));
+ return(2);
+}
+
+
+/*
+ * Format editorial comment referencing screen offering
+ * List-* header supplied commands
+ */
+int
+rfc2369_editorial(long int msgno, HANDLE_S **handlesp, int flags, int width, gf_io_t pc)
+{
+ char *p, *hdrp, *hdrs[MLCMD_COUNT + 1],
+ color[64], buf[2048];
+ int i, n, rv = TRUE;
+ HANDLE_S *h = NULL;
+
+ if((flags & FM_DISPLAY)
+ && (hdrp = pine_fetchheader_lines(ps_global->mail_stream, msgno,
+ NULL, rfc2369_hdrs(hdrs)))){
+ if(*hdrp){
+ snprintf(buf, sizeof(buf), "Note: This message contains ");
+ buf[sizeof(buf)-1] = '\0';
+ p = buf + strlen(buf);
+
+ if(handlesp){
+ h = new_handle(handlesp);
+ h->type = Function;
+ h->h.func.f = rfc2369_display;
+ h->h.func.args.stream = ps_global->mail_stream;
+ h->h.func.args.msgmap = ps_global->msgmap;
+ h->h.func.args.msgno = msgno;
+
+ if(!(flags & FM_NOCOLOR)
+ && handle_start_color(color, sizeof(color), &n, 0)){
+ if((p-buf)+n < sizeof(buf))
+ for(i = 0; i < n; i++)
+ *p++ = color[i];
+ }
+ else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_BOLDON;
+ }
+
+ if((p-buf)+2 < sizeof(buf)){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_HANDLE;
+ }
+
+ snprintf(p + 1, sizeof(buf)-(p+1-buf), "%d", h->key);
+ buf[sizeof(buf)-1] = '\0';
+ *p = strlen(p + 1);
+ p += (*p + 1);
+ }
+
+ sstrncpy(&p, "email list management information", sizeof(buf)-(p-buf));
+ buf[sizeof(buf)-1] = '\0';
+
+ if(h){
+ /* in case it was the current selection */
+ if((p-buf)+2 < sizeof(buf)){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_INVOFF;
+ }
+
+ if(handle_end_color(color, sizeof(color), &n)){
+ if((p-buf)+n < sizeof(buf))
+ for(i = 0; i < n; i++)
+ *p++ = color[i];
+ }
+ else{
+ if((p-buf)+2 < sizeof(buf)){
+ *p++ = TAG_EMBED;
+ *p++ = TAG_BOLDOFF;
+ }
+ }
+
+ if(p-buf < sizeof(buf))
+ *p = '\0';
+ }
+
+ buf[sizeof(buf)-1] = '\0';
+
+ rv = (gf_puts(NEWLINE, pc)
+ && format_editorial(buf, width, flags, handlesp, pc) == NULL
+ && gf_puts(NEWLINE, pc));
+ }
+
+ fs_give((void **) &hdrp);
+ }
+
+ return(rv);
+}
+
+
+
+/*----------------------------------------------------------------------
+ routine for displaying text on the screen.
+
+ Args: sparms -- structure of args controlling what happens outside
+ just the business of managing text scrolling
+
+ This displays in three different kinds of text. One is an array of
+lines passed in in text_array. The other is a simple long string of
+characters passed in in text.
+
+ The style determines what some of the error messages will be, and
+what commands are available as different things are appropriate for
+help text than for message text etc.
+
+ ---*/
+
+int
+scrolltool(SCROLL_S *sparms)
+{
+ register long cur_top_line, num_display_lines;
+ UCS ch;
+ int result, done, cmd, found_on, found_on_index,
+ first_view, force, scroll_lines, km_size,
+ cursor_row, cursor_col, km_popped;
+ char *utf8str;
+ long jn;
+ struct key_menu *km;
+ HANDLE_S *next_handle;
+ bitmap_t bitmap;
+ OtherMenu what;
+ Pos whereis_pos;
+
+ num_display_lines = SCROLL_LINES(ps_global);
+ km_popped = 0;
+ ps_global->mangled_header = 1;
+ ps_global->mangled_footer = 1;
+ ps_global->mangled_body = !sparms->body_valid;
+
+
+ what = sparms->keys.what; /* which key menu to display */
+ cur_top_line = 0;
+ done = 0;
+ found_on = -1;
+ found_on_index = -1;
+ first_view = 1;
+ if(sparms->quell_first_view)
+ first_view = 0;
+
+ force = 0;
+ ch = 'x'; /* for first time through */
+ whereis_pos.row = 0;
+ whereis_pos.col = 0;
+ next_handle = sparms->text.handles;
+
+ set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
+ format_scroll_text();
+
+ if((km = sparms->keys.menu) != NULL){
+ memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
+ }
+ else{
+ setbitmap(bitmap);
+ km = &simple_text_keymenu;
+#ifdef _WINDOWS
+ sparms->mouse.popup = simple_text_popup;
+#endif
+ }
+
+ if(!sparms->bar.title)
+ sparms->bar.title = "Text";
+
+ if(sparms->bar.style == TitleBarNone){
+ if(THREADING() && sp_viewing_a_thread(ps_global->mail_stream))
+ sparms->bar.style = ThrdMsgPercent;
+ else
+ sparms->bar.style = MsgTextPercent;
+ }
+
+ switch(sparms->start.on){
+ case LastPage :
+ cur_top_line = MAX(0, scroll_text_lines() - (num_display_lines-2));
+ if(F_ON(F_SHOW_CURSOR, ps_global)){
+ whereis_pos.row = scroll_text_lines() - cur_top_line;
+ found_on = scroll_text_lines() - 1;
+ }
+
+ break;
+
+ case Fragment :
+ if(sparms->start.loc.frag){
+ (void) url_local_fragment(sparms->start.loc.frag);
+
+ cur_top_line = scroll_state(SS_CUR)->top_text_line;
+
+ if(F_ON(F_SHOW_CURSOR, ps_global)){
+ whereis_pos.row = scroll_text_lines() - cur_top_line;
+ found_on = scroll_text_lines() - 1;
+ }
+ }
+
+ break;
+
+ case Offset :
+ if(sparms->start.loc.offset){
+ for(cur_top_line = 0L;
+ cur_top_line + 1 < scroll_text_lines()
+ && (sparms->start.loc.offset
+ -= scroll_handle_column(cur_top_line,
+ -1)) >= 0;
+ cur_top_line++)
+ ;
+ }
+
+ break;
+
+ case Handle :
+ if(scroll_handle_obscured(sparms->text.handles))
+ cur_top_line = scroll_handle_reframe(-1, TRUE);
+
+ break;
+
+ default : /* no-op */
+ break;
+ }
+
+ /* prepare for calls below to tell us where to go */
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ cancel_busy_cue(-1);
+
+ while(!done) {
+ ps_global->user_says_cancel = 0;
+ if(km_popped){
+ km_popped--;
+ if(km_popped == 0){
+ clearfooter(ps_global);
+ ps_global->mangled_body = 1;
+ }
+ }
+
+ if(ps_global->mangled_screen) {
+ ps_global->mangled_header = 1;
+ ps_global->mangled_footer = 1;
+ ps_global->mangled_body = 1;
+ }
+
+ if(!sparms->quell_newmail && streams_died())
+ ps_global->mangled_header = 1;
+
+ dprint((9, "@@@@ current:%ld\n",
+ mn_get_cur(ps_global->msgmap)));
+
+
+ /*==================== All Screen painting ====================*/
+ /*-------------- The title bar ---------------*/
+ update_scroll_titlebar(cur_top_line, ps_global->mangled_header);
+
+ if(ps_global->mangled_screen){
+ /* this is the only line not cleared by header, body or footer
+ * repaint calls....
+ */
+ ClearLine(1);
+ ps_global->mangled_screen = 0;
+ }
+
+ /*---- Scroll or update the body of the text on the screen -------*/
+ cur_top_line = scroll_scroll_text(cur_top_line, next_handle,
+ ps_global->mangled_body);
+ ps_global->redrawer = redraw_scroll_text;
+ ps_global->mangled_body = 0;
+
+ /*--- Check to see if keymenu might change based on next_handle --*/
+ if(sparms->text.handles != next_handle)
+ ps_global->mangled_footer = 1;
+
+ if(next_handle)
+ sparms->text.handles = next_handle;
+
+ /*------------- The key menu footer --------------------*/
+ if(ps_global->mangled_footer || sparms->keys.each_cmd){
+ if(km_popped){
+ FOOTER_ROWS(ps_global) = 3;
+ clearfooter(ps_global);
+ }
+
+ if(F_ON(F_ARROW_NAV, ps_global)){
+ menu_clear_binding(km, KEY_LEFT);
+ if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
+ menu_add_binding(km, '<', cmd);
+ menu_add_binding(km, KEY_LEFT, cmd);
+ }
+ }
+
+ if(F_ON(F_ARROW_NAV, ps_global)){
+ menu_clear_binding(km, KEY_RIGHT);
+ if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
+ menu_add_binding(km, '>', cmd);
+ menu_add_binding(km, KEY_RIGHT, cmd);
+ }
+ }
+
+ if(sparms->keys.each_cmd){
+ (*sparms->keys.each_cmd)(sparms,
+ scroll_handle_obscured(sparms->text.handles));
+ memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
+ }
+
+ if(menu_binding_index(km, MC_JUMP) >= 0){
+ for(cmd = 0; cmd < 10; cmd++)
+ if(F_ON(F_ENABLE_JUMP, ps_global))
+ (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
+ else
+ (void) menu_clear_binding(km, '0' + cmd);
+ }
+
+ draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps_global), 0, what);
+ what = SameMenu;
+ ps_global->mangled_footer = 0;
+ if(km_popped){
+ FOOTER_ROWS(ps_global) = 1;
+ mark_keymenu_dirty();
+ }
+ }
+
+ if((ps_global->first_time_user || ps_global->show_new_version)
+ && first_view && sparms->text.handles
+ && (sparms->text.handles->next || sparms->text.handles->prev)
+ && !sparms->quell_help)
+ q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);
+
+ /*============ Check for New Mail and CheckPoint ============*/
+ if(!sparms->quell_newmail &&
+ new_mail(force, NM_TIMING(ch), NM_STATUS_MSG) >= 0){
+ update_scroll_titlebar(cur_top_line, 1);
+ if(ps_global->mangled_footer)
+ draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps_global), 0, what);
+
+ ps_global->mangled_footer = 0;
+ }
+
+ /*
+ * If an expunge of the current message happened during the
+ * new mail check we want to bail out of here. See mm_expunged.
+ */
+ if(ps_global->next_screen != SCREEN_FUN_NULL){
+ done = 1;
+ continue;
+ }
+
+ if(ps_global->noticed_change_in_unseen){
+ ps_global->noticed_change_in_unseen = 0; /* redraw only once */
+ cmd = MC_RESIZE; /* causes cursor to be saved in folder_lister */
+ done = 1;
+ continue;
+ }
+
+ if(first_view && num_display_lines >= scroll_text_lines())
+ q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
+
+
+ force = 0; /* may not need to next time around */
+ first_view = 0; /* check_point a priority any more? */
+
+ /*==================== Output the status message ==============*/
+ if(!sparms->no_stat_msg){
+ if(km_popped){
+ FOOTER_ROWS(ps_global) = 3;
+ mark_status_unknown();
+ }
+
+ display_message(ch);
+ if(km_popped){
+ FOOTER_ROWS(ps_global) = 1;
+ mark_status_unknown();
+ }
+ }
+
+ if(F_ON(F_SHOW_CURSOR, ps_global)){
+#ifdef WINDOWS
+ if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
+ whereis_pos.row = 0;
+#endif
+
+ if(whereis_pos.row > 0){
+ cursor_row = SCROLL_LINES_ABOVE(ps_global)
+ + whereis_pos.row - 1;
+ cursor_col = whereis_pos.col;
+ }
+ else{
+ POSLIST_S *lp = NULL;
+
+ if(sparms->text.handles &&
+ !scroll_handle_obscured(sparms->text.handles)){
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+
+ for(lp = sparms->text.handles->loc; lp; lp = lp->next)
+ if(lp->where.row >= st->top_text_line
+ && lp->where.row < st->top_text_line
+ + st->screen.length){
+ cursor_row = lp->where.row - cur_top_line
+ + SCROLL_LINES_ABOVE(ps_global);
+ cursor_col = lp->where.col;
+ break;
+ }
+ }
+
+ if(!lp){
+ cursor_col = 0;
+ /* first new line of text */
+ cursor_row = SCROLL_LINES_ABOVE(ps_global) +
+ ((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
+ }
+ }
+ }
+ else{
+ cursor_col = 0;
+ cursor_row = ps_global->ttyo->screen_rows
+ - SCROLL_LINES_BELOW(ps_global);
+ }
+
+ MoveCursor(cursor_row, cursor_col);
+
+ /*================ Get command and validate =====================*/
+#ifdef MOUSE
+#ifndef WIN32
+ if(sparms->text.handles)
+#endif
+ {
+ mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
+ register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
+ ps_global->ttyo->screen_rows
+ - (FOOTER_ROWS(ps_global) + 1),
+ ps_global->ttyo->screen_cols);
+ }
+#endif
+#ifdef _WINDOWS
+ mswin_allowcopy(mswin_readscrollbuf);
+ mswin_setscrollcallback(pcpine_do_scroll);
+
+ if(sparms->help.text != NO_HELP)
+ mswin_sethelptextcallback(pcpine_help_scroll);
+
+ if(sparms->text.handles
+ && sparms->text.handles->type != Folder)
+ mswin_mousetrackcallback(pcpine_view_cursor);
+
+ if(ps_global->prev_screen == mail_view_screen)
+ mswin_setviewinwindcallback(view_in_new_window);
+#endif
+ ch = (sparms->quell_newmail || read_command_prep()) ? read_command(&utf8str) : NO_OP_COMMAND;
+#ifdef MOUSE
+#ifndef WIN32
+ if(sparms->text.handles)
+#endif
+ clear_mfunc(mouse_in_content);
+#endif
+#ifdef _WINDOWS
+ mswin_allowcopy(NULL);
+ mswin_setscrollcallback(NULL);
+ mswin_sethelptextcallback(NULL);
+ mswin_mousetrackcallback(NULL);
+ mswin_setviewinwindcallback(NULL);
+ cur_top_line = scroll_state(SS_CUR)->top_text_line;
+#endif
+
+ cmd = menu_command(ch, km);
+
+ if(km_popped)
+ switch(cmd){
+ case MC_NONE :
+ case MC_OTHER :
+ case MC_RESIZE:
+ case MC_REPAINT :
+ km_popped++;
+ break;
+
+ default:
+ clearfooter(ps_global);
+ break;
+ }
+
+
+ /*============= Execute command =======================*/
+ switch(cmd){
+
+ /* ------ Help -------*/
+ case MC_HELP :
+ if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
+ km_popped = 2;
+ ps_global->mangled_footer = 1;
+ break;
+ }
+
+ whereis_pos.row = 0;
+ if(sparms->help.text == NO_HELP){
+ q_status_message(SM_ORDER, 0, 5,
+ _("No help text currently available"));
+ break;
+ }
+
+ km_size = FOOTER_ROWS(ps_global);
+
+ helper(sparms->help.text, sparms->help.title, 0);
+
+ if(ps_global->next_screen != main_menu_screen
+ && km_size == FOOTER_ROWS(ps_global)) {
+ /* Have to reset because helper uses scroll_text */
+ num_display_lines = SCROLL_LINES(ps_global);
+ ps_global->mangled_screen = 1;
+ }
+ else
+ done = 1;
+
+ break;
+
+
+ /*---------- Roll keymenu ------*/
+ case MC_OTHER :
+ if(F_OFF(F_USE_FK, ps_global))
+ warn_other_cmds();
+
+ what = NextMenu;
+ ps_global->mangled_footer = 1;
+ break;
+
+
+ /* -------- Scroll back one page -----------*/
+ case MC_PAGEUP :
+ whereis_pos.row = 0;
+ if(cur_top_line) {
+ scroll_lines = MIN(MAX(num_display_lines -
+ ps_global->viewer_overlap, 1), num_display_lines);
+ cur_top_line -= scroll_lines;
+ if(cur_top_line <= 0){
+ cur_top_line = 0;
+ q_status_message1(SM_INFO, 0, 1, "START of %s",
+ STYLE_NAME(sparms));
+ }
+ }
+ else{
+ /* hilite last available handle */
+ next_handle = NULL;
+ if(sparms->text.handles){
+ HANDLE_S *h = sparms->text.handles;
+
+ while((h = scroll_handle_prev_sel(h))
+ && !scroll_handle_obscured(h))
+ next_handle = h;
+ }
+
+ if(!next_handle)
+ q_status_message1(SM_ORDER, 0, 1, _("Already at start of %s"),
+ STYLE_NAME(sparms));
+
+ }
+
+
+ break;
+
+
+ /*---- Scroll down one page -------*/
+ case MC_PAGEDN :
+ if(cur_top_line + num_display_lines < scroll_text_lines()){
+ whereis_pos.row = 0;
+ scroll_lines = MIN(MAX(num_display_lines -
+ ps_global->viewer_overlap, 1), num_display_lines);
+ cur_top_line += scroll_lines;
+
+ if(cur_top_line + num_display_lines >= scroll_text_lines())
+ q_status_message1(SM_INFO, 0, 1, "END of %s",
+ STYLE_NAME(sparms));
+ }
+ else if(!sparms->end_scroll
+ || !(done = (*sparms->end_scroll)(sparms))){
+ q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
+ STYLE_NAME(sparms));
+ /* hilite last available handle */
+ if(sparms->text.handles){
+ HANDLE_S *h = sparms->text.handles;
+
+ while((h = scroll_handle_next_sel(h)) != NULL)
+ next_handle = h;
+ }
+ }
+
+ break;
+
+ /* scroll to the top page */
+ case MC_HOMEKEY:
+ if(cur_top_line){
+ cur_top_line = 0;
+ q_status_message1(SM_INFO, 0, 1, "START of %s",
+ STYLE_NAME(sparms));
+ }
+
+ next_handle = NULL;
+ if(sparms->text.handles){
+ HANDLE_S *h = sparms->text.handles;
+
+ while((h = scroll_handle_prev_sel(h)) != NULL)
+ next_handle = h;
+ }
+ break;
+
+ /* scroll to the bottom page */
+ case MC_ENDKEY:
+ if(cur_top_line + num_display_lines < scroll_text_lines()){
+ cur_top_line = scroll_text_lines() - MIN(5, num_display_lines);
+ q_status_message1(SM_INFO, 0, 1, "END of %s",
+ STYLE_NAME(sparms));
+ }
+
+ if(sparms->text.handles){
+ HANDLE_S *h = sparms->text.handles;
+
+ while((h = scroll_handle_next_sel(h)) != NULL)
+ next_handle = h;
+ }
+ break;
+
+ /*------ Scroll down one line -----*/
+ case MC_CHARDOWN :
+ next_handle = NULL;
+ if(sparms->text.handles){
+ if(sparms->vert_handle){
+ HANDLE_S *h, *h2;
+ int i, j, k;
+
+ h2 = sparms->text.handles;
+ if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
+ h2 = h2->prev;
+
+ i = h2->loc->where.row + 1;
+ j = h2->loc->where.col;
+ for(h = NULL, k = h2->key;
+ h2 && (!h
+ || (h->loc->where.row == h2->loc->where.row));
+ h2 = h2->next)
+ /* must be different key */
+ /* ... below current line */
+ /* ... pref'bly to left */
+ if(h2->key != k
+ && h2->loc->where.row >= i){
+ if(h2->loc->where.col > j){
+ if(!h)
+ h = h2;
+
+ break;
+ }
+ else
+ h = h2;
+ }
+
+ if(h){
+ whereis_pos.row = 0;
+ next_handle = h;
+ if((result = scroll_handle_obscured(next_handle)) != 0){
+ long new_top;
+
+ if(scroll_handle_obscured(sparms->text.handles)
+ && result > 0)
+ next_handle = sparms->text.handles;
+
+ ps_global->mangled_body++;
+ new_top = scroll_handle_reframe(next_handle->key,0);
+ if(new_top >= 0)
+ cur_top_line = new_top;
+ }
+ }
+ }
+ else if(!(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)))
+ next_handle = scroll_handle_next(sparms->text.handles);
+ }
+
+ if(!next_handle){
+ if(cur_top_line + num_display_lines < scroll_text_lines()){
+ whereis_pos.row = 0;
+ cur_top_line++;
+ if(cur_top_line + num_display_lines >= scroll_text_lines())
+ q_status_message1(SM_INFO, 0, 1, "END of %s",
+ STYLE_NAME(sparms));
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 1, _("Already at end of %s"),
+ STYLE_NAME(sparms));
+ }
+
+ break;
+
+
+ /* ------ Scroll back up one line -------*/
+ case MC_CHARUP :
+ next_handle = NULL;
+ if(sparms->text.handles){
+ if(sparms->vert_handle){
+ HANDLE_S *h, *h2;
+ int i, j, k;
+
+ h2 = sparms->text.handles;
+ if(h2->type == Folder && h2->prev && h2->prev->is_dual_do_open)
+ h2 = h2->prev;
+
+ i = h2->loc->where.row - 1;
+ j = h2->loc->where.col;
+
+ for(h = NULL, k = h2->key;
+ h2 && (!h
+ || (h->loc->where.row == h2->loc->where.row));
+ h2 = h2->prev)
+ /* must be new key, above current
+ * line and pref'bly to right
+ */
+ if(h2->key != k
+ && h2->loc->where.row <= i){
+ if(h2->loc->where.col < j){
+ if(!h)
+ h = h2;
+
+ break;
+ }
+ else
+ h = h2;
+ }
+
+ if(h){
+ whereis_pos.row = 0;
+ next_handle = h;
+ if((result = scroll_handle_obscured(next_handle)) != 0){
+ long new_top;
+
+ if(scroll_handle_obscured(sparms->text.handles)
+ && result < 0)
+ next_handle = sparms->text.handles;
+
+ ps_global->mangled_body++;
+ new_top = scroll_handle_reframe(next_handle->key,0);
+ if(new_top >= 0)
+ cur_top_line = new_top;
+ }
+ }
+ }
+ else if(!(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)))
+ next_handle = scroll_handle_prev(sparms->text.handles);
+ }
+
+ if(!next_handle){
+ whereis_pos.row = 0;
+ if(cur_top_line){
+ cur_top_line--;
+ if(cur_top_line == 0)
+ q_status_message1(SM_INFO, 0, 1, "START of %s",
+ STYLE_NAME(sparms));
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 1,
+ _("Already at start of %s"),
+ STYLE_NAME(sparms));
+ }
+
+ break;
+
+
+ case MC_NEXT_HANDLE :
+ if((next_handle = scroll_handle_next_sel(sparms->text.handles)) != NULL){
+ whereis_pos.row = 0;
+ if((result = scroll_handle_obscured(next_handle)) != 0){
+ long new_top;
+
+ if(scroll_handle_obscured(sparms->text.handles)
+ && result > 0)
+ next_handle = sparms->text.handles;
+
+ ps_global->mangled_body++;
+ new_top = scroll_handle_reframe(next_handle->key, 0);
+ if(new_top >= 0)
+ cur_top_line = new_top;
+ }
+ }
+ else{
+ if(scroll_handle_obscured(sparms->text.handles)){
+ long new_top;
+
+ ps_global->mangled_body++;
+ if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
+ whereis_pos.row = 0;
+ cur_top_line = new_top;
+ }
+ }
+
+ q_status_message1(SM_ORDER, 0, 1,
+ _("Already on last item in %s"),
+ STYLE_NAME(sparms));
+ }
+
+ break;
+
+
+ case MC_PREV_HANDLE :
+ if((next_handle = scroll_handle_prev_sel(sparms->text.handles)) != NULL){
+ whereis_pos.row = 0;
+ if((result = scroll_handle_obscured(next_handle)) != 0){
+ long new_top;
+
+ if(scroll_handle_obscured(sparms->text.handles)
+ && result < 0)
+ next_handle = sparms->text.handles;
+
+ ps_global->mangled_body++;
+ new_top = scroll_handle_reframe(next_handle->key, 0);
+ if(new_top >= 0)
+ cur_top_line = new_top;
+ }
+ }
+ else{
+ if(scroll_handle_obscured(sparms->text.handles)){
+ long new_top;
+
+ ps_global->mangled_body++;
+ if((new_top = scroll_handle_reframe(-1, 0)) >= 0){
+ whereis_pos.row = 0;
+ cur_top_line = new_top;
+ }
+ }
+
+ q_status_message1(SM_ORDER, 0, 1,
+ _("Already on first item in %s"),
+ STYLE_NAME(sparms));
+ }
+
+ break;
+
+
+ /*------ View the current handle ------*/
+ case MC_VIEW_HANDLE :
+ switch(scroll_handle_obscured(sparms->text.handles)){
+ default :
+ case 0 :
+ switch(scroll_handle_launch(sparms->text.handles,
+ sparms->text.handles->force_display)){
+ case 1 :
+ cmd = MC_EXIT; /* propagate */
+ done = 1;
+ break;
+
+ case -1 :
+ cmd_cancelled(NULL);
+ break;
+
+ default :
+ break;
+ }
+
+ cur_top_line = scroll_state(SS_CUR)->top_text_line;
+ break;
+
+ case 1 :
+ q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
+ break;
+
+ case -1 :
+ q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
+ break;
+ }
+
+ break;
+
+ /*---------- Search text (where is) ----------*/
+ case MC_WHEREIS :
+ ps_global->mangled_footer = 1;
+ {long start_row;
+ int start_index, key = 0;
+ char *report = NULL;
+
+ start_row = cur_top_line;
+ start_index = 0;
+
+ if(F_ON(F_SHOW_CURSOR,ps_global)){
+ if(found_on < 0
+ || found_on >= scroll_text_lines()
+ || found_on < cur_top_line
+ || found_on >= cur_top_line + num_display_lines){
+ start_row = cur_top_line;
+ start_index = 0;
+ }
+ else{
+ if(found_on_index < 0){
+ start_row = found_on + 1;
+ start_index = 0;
+ }
+ else{
+ start_row = found_on;
+ start_index = found_on_index+1;
+ }
+ }
+ }
+ else if(sparms->srch_handle){
+ HANDLE_S *h;
+
+ if((h = scroll_handle_next_sel(sparms->text.handles)) != NULL){
+ /*
+ * Translate the screen's column into the
+ * line offset to start on...
+ *
+ * This makes it so search_text never returns -3
+ * so we don't know it is the same match. That's
+ * because we start well after the current handle
+ * (at the next handle) and that causes us to
+ * think the one we just matched on is a different
+ * one from before. Can't think of an easy way to
+ * fix it, though, and it isn't a big deal. We still
+ * match, we just don't say current line contains
+ * the only match.
+ */
+ start_row = h->loc->where.row;
+ start_index = scroll_handle_index(start_row, h->loc->where.col);
+ }
+ else{
+ /* last handle, start over at top */
+ start_row = cur_top_line;
+ start_index = 0;
+ }
+ }
+ else{
+ start_row = (found_on < 0
+ || found_on >= scroll_text_lines()
+ || found_on < cur_top_line
+ || found_on >= cur_top_line + num_display_lines)
+ ? cur_top_line : found_on + 1,
+ start_index = 0;
+ }
+
+ found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
+ start_index, &report,
+ &whereis_pos, &found_on_index);
+
+ if(found_on == -4){ /* search to top of text */
+ whereis_pos.row = 0;
+ whereis_pos.col = 0;
+ found_on = 0;
+ if(sparms->text.handles && sparms->srch_handle)
+ key = 1;
+ }
+ else if(found_on == -5){ /* search to bottom of text */
+ HANDLE_S *h;
+
+ whereis_pos.row = MAX(scroll_text_lines() - 1, 0);
+ whereis_pos.col = 0;
+ found_on = whereis_pos.row;
+ if((h = sparms->text.handles) && sparms->srch_handle)
+ do
+ key = h->key;
+ while((h = h->next) != NULL);
+ }
+ else if(found_on == -3){
+ whereis_pos.row = found_on = start_row;
+ found_on_index = start_index - 1;
+ q_status_message(SM_ORDER, 1, 3,
+ _("Current line contains the only match"));
+ }
+
+ if(found_on >= 0){
+ result = found_on < cur_top_line;
+ if(!key)
+ key = (sparms->text.handles)
+ ? dot_on_handle(found_on, whereis_pos.col) : 0;
+
+ if(F_ON(F_FORCE_LOW_SPEED,ps_global)
+ || ps_global->low_speed
+ || F_ON(F_SHOW_CURSOR,ps_global)
+ || key){
+ if((found_on >= cur_top_line + num_display_lines ||
+ found_on < cur_top_line) &&
+ num_display_lines > ps_global->viewer_overlap){
+ cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
+ if(scroll_text_lines()-cur_top_line < 5)
+ cur_top_line = MAX(0,
+ scroll_text_lines()-MIN(5,num_display_lines));
+ }
+ /* else leave cur_top_line alone */
+ }
+ else{
+ cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
+ if(scroll_text_lines()-cur_top_line < 5)
+ cur_top_line = MAX(0,
+ scroll_text_lines()-MIN(5,num_display_lines));
+ }
+
+ whereis_pos.row = whereis_pos.row - cur_top_line + 1;
+ if(report)
+ q_status_message(SM_ORDER, 0, 3, report);
+ else
+ q_status_message2(SM_ORDER, 0, 3,
+ "%sFound on line %s on screen",
+ result ? "Search wrapped to start. " : "",
+ int2string(whereis_pos.row));
+
+ if(key){
+ if(sparms->text.handles->key < key)
+ for(next_handle = sparms->text.handles->next;
+ next_handle->key != key;
+ next_handle = next_handle->next)
+ ;
+ else
+ for(next_handle = sparms->text.handles;
+ next_handle->key != key;
+ next_handle = next_handle->prev)
+ ;
+ }
+ }
+ else if(found_on == -1)
+ cmd_cancelled("Search");
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Word not found"));
+ }
+
+ break;
+
+
+ /*-------------- jump command -------------*/
+ /* NOTE: preempt the process_cmd() version because
+ * we need to get at the number..
+ */
+ case MC_JUMP :
+ jn = jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch,
+ sparms, View);
+ if(sparms && sparms->jump_is_debug)
+ done = 1;
+ else if(jn > 0 && jn != mn_get_cur(ps_global->msgmap)){
+
+ if(mn_total_cur(ps_global->msgmap) > 1L)
+ mn_reset_cur(ps_global->msgmap, jn);
+ else
+ mn_set_cur(ps_global->msgmap, jn);
+
+ done = 1;
+ }
+ else
+ ps_global->mangled_footer = 1;
+
+ break;
+
+
+#ifdef MOUSE
+ /*-------------- Mouse Event -------------*/
+ case MC_MOUSE:
+ {
+ MOUSEPRESS mp;
+ long line;
+ int key;
+
+ mouse_get_last (NULL, &mp);
+ mp.row -= 2;
+
+ /* The clicked line have anything special on it? */
+ if((line = cur_top_line + mp.row) < scroll_text_lines()
+ && (key = dot_on_handle(line, mp.col))){
+ switch(mp.button){
+ case M_BUTTON_RIGHT :
+#ifdef _WINDOWS
+ if(sparms->mouse.popup){
+ if(sparms->text.handles->key < key)
+ for(next_handle = sparms->text.handles->next;
+ next_handle->key != key;
+ next_handle = next_handle->next)
+ ;
+ else
+ for(next_handle = sparms->text.handles;
+ next_handle->key != key;
+ next_handle = next_handle->prev)
+ ;
+
+ if(sparms->mouse.popup){
+ cur_top_line = scroll_scroll_text(cur_top_line,
+ next_handle,
+ ps_global->mangled_body);
+ fflush(stdout);
+ switch((*sparms->mouse.popup)(sparms, key)){
+ case 1 :
+ cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
+ break;
+
+ case 2 :
+ done++;
+ break;
+ }
+ }
+ }
+
+#endif
+ break;
+
+ case M_BUTTON_LEFT :
+ if(sparms->text.handles->key < key)
+ for(next_handle = sparms->text.handles->next;
+ next_handle->key != key;
+ next_handle = next_handle->next)
+ ;
+ else
+ for(next_handle = sparms->text.handles;
+ next_handle->key != key;
+ next_handle = next_handle->prev)
+ ;
+
+ if(mp.doubleclick) /* launch url */
+ cur_top_line = doubleclick_handle(sparms, next_handle, &cmd, &done);
+ else if(sparms->mouse.click)
+ (*sparms->mouse.click)(sparms);
+
+ break;
+
+ case M_BUTTON_MIDDLE : /* NO-OP for now */
+ break;
+
+ default: /* just ignore */
+ break;
+ }
+ }
+#ifdef _WINDOWS
+ else if(mp.button == M_BUTTON_RIGHT){
+ /*
+ * Toss generic popup on to the screen
+ */
+ if(sparms->mouse.popup)
+ if((*sparms->mouse.popup)(sparms, 0) == 2){
+ done++;
+ }
+ }
+#endif
+ }
+
+ break;
+#endif /* MOUSE */
+
+
+ /*-------------- Display Resize -------------*/
+ case MC_RESIZE :
+ if(sparms->resize_exit){
+ long line;
+
+ /*
+ * Figure out char offset of the char in the top left
+ * corner of the display. Pass it back to the
+ * fetcher/formatter and have it pass the offset
+ * back to us...
+ */
+ sparms->start.on = Offset;
+ for(sparms->start.loc.offset = line = 0L;
+ line < cur_top_line;
+ line++)
+ sparms->start.loc.offset += scroll_handle_column(line, -1);
+
+ done = 1;
+ ClearLine(1);
+ break;
+ }
+ /* else no reformatting neccessary, fall thru to repaint */
+
+
+ /*-------------- refresh -------------*/
+ case MC_REPAINT :
+ num_display_lines = SCROLL_LINES(ps_global);
+ mark_status_dirty();
+ mark_keymenu_dirty();
+ mark_titlebar_dirty();
+ ps_global->mangled_screen = 1;
+ force = 1;
+ break;
+
+
+ /*------- no op timeout to check for new mail ------*/
+ case MC_NONE :
+ break;
+
+
+ /*------- Forward displayed text ------*/
+ case MC_FWDTEXT :
+ forward_text(ps_global, sparms->text.text, sparms->text.src);
+ break;
+
+
+ /*----------- Save the displayed text ------------*/
+ case MC_SAVETEXT :
+ (void)simple_export(ps_global, sparms->text.text,
+ sparms->text.src, "text", NULL);
+ break;
+
+
+ /*----------- Exit this screen ------------*/
+ case MC_EXIT :
+ done = 1;
+ break;
+
+
+ /*----------- Pop back to the Main Menu ------------*/
+ case MC_MAIN :
+ ps_global->next_screen = main_menu_screen;
+ done = 1;
+ break;
+
+
+ /*----------- Print ------------*/
+ case MC_PRINTTXT :
+ print_to_printer(sparms);
+ break;
+
+
+ /* ------- First handle on Line ------ */
+ case MC_GOTOBOL :
+ if(sparms->text.handles){
+ next_handle = scroll_handle_boundary(sparms->text.handles,
+ scroll_handle_prev_sel);
+
+ break;
+ }
+ /* fall thru as bogus */
+
+ /* ------- Last handle on Line ------ */
+ case MC_GOTOEOL :
+ if(sparms->text.handles){
+ next_handle = scroll_handle_boundary(sparms->text.handles,
+ scroll_handle_next_sel);
+
+ break;
+ }
+ /* fall thru as bogus */
+
+ /*------- BOGUS INPUT ------*/
+ case MC_CHARRIGHT :
+ case MC_CHARLEFT :
+ case MC_UNKNOWN :
+ if(sparms->bogus_input)
+ done = (*sparms->bogus_input)(ch);
+ else
+ bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");
+
+ break;
+
+
+ case MC_UTF8:
+ bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps_global) ? "F1" : "?");
+ break;
+
+
+ /*------- Standard commands ------*/
+ default:
+ whereis_pos.row = 0;
+ if(sparms->proc.tool)
+ result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
+ else
+ result = process_cmd(ps_global, ps_global->mail_stream,
+ ps_global->msgmap, cmd, View, &force);
+
+ dprint((7, "PROCESS_CMD return: %d\n", result));
+
+ if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
+ done = 1;
+ if(cmd == MC_FULLHDR){
+ if(ps_global->full_header == 1){
+ long line;
+
+ /*
+ * Figure out char offset of the char in the top left
+ * corner of the display. Pass it back to the
+ * fetcher/formatter and have it pass the offset
+ * back to us...
+ */
+ sparms->start.on = Offset;
+ for(sparms->start.loc.offset = line = 0L;
+ line < cur_top_line;
+ line++)
+ sparms->start.loc.offset +=
+ scroll_handle_column(line, -1);
+ }
+ else
+ sparms->start.on = 0;
+
+ switch(km->which){
+ case 0:
+ sparms->keys.what = FirstMenu;
+ break;
+ case 1:
+ sparms->keys.what = SecondMenu;
+ break;
+ case 2:
+ sparms->keys.what = ThirdMenu;
+ break;
+ case 3:
+ sparms->keys.what = FourthMenu;
+ break;
+ }
+ }
+ }
+ else if(!scroll_state(SS_CUR)){
+ num_display_lines = SCROLL_LINES(ps_global);
+ ps_global->mangled_screen = 1;
+ }
+
+ break;
+
+ } /* End of switch() */
+
+ /* Need to frame some handles? */
+ if(sparms->text.handles
+ && ((!next_handle
+ && handle_on_page(sparms->text.handles, cur_top_line,
+ cur_top_line + num_display_lines))
+ || (next_handle
+ && handle_on_page(next_handle, cur_top_line,
+ cur_top_line + num_display_lines))))
+ next_handle = scroll_handle_in_frame(cur_top_line);
+
+ } /* End of while() -- loop executing commands */
+
+ ps_global->redrawer = NULL; /* next statement makes this invalid! */
+ zero_scroll_text(); /* very important to zero out on return!!! */
+ scroll_state(SS_FREE);
+ if(sparms->bar.color)
+ free_color_pair(&sparms->bar.color);
+
+#ifdef _WINDOWS
+ scroll_setrange(0L, 0L);
+#endif
+ return(cmd);
+}
+
+
+/*----------------------------------------------------------------------
+ Print text on paper
+
+ Args: text -- The text to print out
+ source -- What type of source text is
+ message -- Message for open_printer()
+ Handling of error conditions is very poor.
+
+ ----*/
+static int
+print_to_printer(SCROLL_S *sparms)
+{
+ char message[64];
+
+ snprintf(message, sizeof(message), "%s", STYLE_NAME(sparms));
+ message[sizeof(message)-1] = '\0';
+
+ if(open_printer(message) != 0)
+ return(-1);
+
+ switch(sparms->text.src){
+ case CharStar :
+ if(sparms->text.text != (char *)NULL)
+ print_text((char *)sparms->text.text);
+
+ break;
+
+ case CharStarStar :
+ if(sparms->text.text != (char **)NULL){
+ register char **t;
+
+ for(t = sparms->text.text; *t != NULL; t++){
+ print_text(*t);
+ print_text(NEWLINE);
+ }
+ }
+
+ break;
+
+ case FileStar :
+ if(sparms->text.text != (FILE *)NULL) {
+ size_t n;
+ int i;
+
+ fseek((FILE *)sparms->text.text, 0L, 0);
+ n = SIZEOF_20KBUF - 1;
+ while((i = fread((void *)tmp_20k_buf, sizeof(char),
+ n, (FILE *)sparms->text.text)) != 0) {
+ tmp_20k_buf[i] = '\0';
+ print_text(tmp_20k_buf);
+ }
+ }
+
+ default :
+ break;
+ }
+
+ close_printer();
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Search text being viewed (help or message)
+
+ Args: q_line -- The screen line to prompt for search string on
+ start_line -- Line number in text to begin search on
+ start_index -- Where to begin search at in first line of text
+ cursor_pos -- position of cursor is returned to caller here
+ (Actually, this isn't really the position of the
+ cursor because we don't know where we are on the
+ screen. So row is set to the line number and col
+ is set to the right column.)
+ offset_in_line -- Offset where match was found.
+
+ Result: returns line number string was found on
+ -1 for cancel
+ -2 if not found
+ -3 if only match is at start_index - 1
+ -4 if search to first line
+ -5 if search to last line
+ ---*/
+int
+search_text(int q_line, long int start_line, int start_index, char **report,
+ Pos *cursor_pos, int *offset_in_line)
+{
+ char prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], *p;
+ HelpType help;
+ int rc, flags;
+ static HISTORY_S *history = NULL;
+ char search_string[MAX_SEARCH+1];
+ static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
+ {ctrl('Y'), 10, "^Y", N_("First Line")},
+ {ctrl('V'), 11, "^V", N_("Last Line")},
+ {KEY_UP, 30, "", ""},
+ {KEY_DOWN, 31, "", ""},
+ {-1, 0, NULL, NULL}
+ };
+#define KU_ST (3) /* index of KEY_UP */
+
+ init_hist(&history, HISTSIZE);
+
+ /*
+ * Put the last one used in the default search_string,
+ * not in nsearch_string.
+ */
+ search_string[0] = '\0';
+ if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
+ strncpy(search_string, p, sizeof(search_string));
+ search_string[sizeof(search_string)-1] = '\0';
+ }
+
+ snprintf(prompt, sizeof(prompt), _("Word to search for [%s] : "), search_string);
+ help = NO_HELP;
+ nsearch_string[0] = '\0';
+
+ while(1) {
+ flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
+
+ /*
+ * 2 is really 1 because there will be one real entry and
+ * one entry of "" because of the get_prev_hist above.
+ */
+ if(items_in_hist(history) > 2){
+ word_search_key[KU_ST].name = HISTORY_UP_KEYNAME;
+ word_search_key[KU_ST].label = HISTORY_KEYLABEL;
+ word_search_key[KU_ST+1].name = HISTORY_DOWN_KEYNAME;
+ word_search_key[KU_ST+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ word_search_key[KU_ST].name = "";
+ word_search_key[KU_ST].label = "";
+ word_search_key[KU_ST+1].name = "";
+ word_search_key[KU_ST+1].label = "";
+ }
+
+ rc = optionally_enter(nsearch_string, q_line, 0, sizeof(nsearch_string),
+ prompt, word_search_key, help, &flags);
+
+ if(rc == 3) {
+ help = help == NO_HELP ? h_oe_searchview : NO_HELP;
+ continue;
+ }
+ else if(rc == 10){
+ if(report)
+ *report = _("Searched to First Line.");
+
+ return(-4);
+ }
+ else if(rc == 11){
+ if(report)
+ *report = _("Searched to Last Line.");
+
+ return(-5);
+ }
+ else if(rc == 30){
+ if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
+ strncpy(nsearch_string, p, sizeof(nsearch_string));
+ nsearch_string[sizeof(nsearch_string)-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+ else if(rc == 31){
+ if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
+ strncpy(nsearch_string, p, sizeof(nsearch_string));
+ nsearch_string[sizeof(nsearch_string)-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ continue;
+ }
+
+ if(rc != 4){ /* 4 is redraw */
+ save_hist(history, nsearch_string, 0, NULL);
+ break;
+ }
+ }
+
+ if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
+ return(-1);
+
+ if(nsearch_string[0] != '\0'){
+ strncpy(search_string, nsearch_string, sizeof(search_string)-1);
+ search_string[sizeof(search_string)-1] = '\0';
+ }
+
+ rc = search_scroll_text(start_line, start_index, search_string, cursor_pos,
+ offset_in_line);
+ return(rc);
+}
+
+
+/*----------------------------------------------------------------------
+ Update the scroll tool's titlebar
+
+ Args: cur_top_line --
+ redraw -- flag to force updating
+
+ ----*/
+void
+update_scroll_titlebar(long int cur_top_line, int redraw)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ int num_display_lines = SCROLL_LINES(ps_global);
+ long new_line = (cur_top_line + num_display_lines > st->num_lines)
+ ? st->num_lines
+ : cur_top_line + num_display_lines;
+ long raw_msgno;
+ COLOR_PAIR *returned_color = NULL;
+ COLOR_PAIR *titlecolor = NULL;
+ int colormatch;
+ SEARCHSET *ss = NULL;
+
+ if(st->parms->use_indexline_color
+ && ps_global->titlebar_color_style != TBAR_COLOR_DEFAULT){
+ raw_msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
+ if(raw_msgno > 0L && ps_global->mail_stream
+ && raw_msgno <= ps_global->mail_stream->nmsgs){
+ ss = mail_newsearchset();
+ ss->first = ss->last = (unsigned long) raw_msgno;
+ }
+
+ if(ss){
+ PAT_STATE *pstate = NULL;
+
+ colormatch = get_index_line_color(ps_global->mail_stream,
+ ss, &pstate, &returned_color);
+ mail_free_searchset(&ss);
+
+ /*
+ * This is a bit tricky. If there is a colormatch but returned_color
+ * is NULL, that means that the user explicitly wanted the
+ * Normal color used in this index line, so that is what we
+ * use. If no colormatch then we will use the TITLE color
+ * instead of Normal.
+ */
+ if(colormatch){
+ if(returned_color)
+ titlecolor = returned_color;
+ else
+ titlecolor = new_color_pair(ps_global->VAR_NORM_FORE_COLOR,
+ ps_global->VAR_NORM_BACK_COLOR);
+ }
+
+ if(titlecolor
+ && ps_global->titlebar_color_style == TBAR_COLOR_REV_INDEXLINE){
+ char cbuf[MAXCOLORLEN+1];
+
+ strncpy(cbuf, titlecolor->fg, MAXCOLORLEN);
+ strncpy(titlecolor->fg, titlecolor->bg, MAXCOLORLEN);
+ strncpy(titlecolor->bg, cbuf, MAXCOLORLEN);
+ }
+ }
+
+ /* Did the color change? */
+ if((!titlecolor && st->parms->bar.color)
+ ||
+ (titlecolor && !st->parms->bar.color)
+ ||
+ (titlecolor && st->parms->bar.color
+ && (strcmp(titlecolor->fg, st->parms->bar.color->fg)
+ || strcmp(titlecolor->bg, st->parms->bar.color->bg)))){
+
+ redraw++;
+ if(st->parms->bar.color)
+ free_color_pair(&st->parms->bar.color);
+
+ st->parms->bar.color = titlecolor;
+ titlecolor = NULL;
+ }
+
+ if(titlecolor)
+ free_color_pair(&titlecolor);
+ }
+
+
+ if(redraw){
+ set_titlebar(st->parms->bar.title, ps_global->mail_stream,
+ ps_global->context_current, ps_global->cur_folder,
+ ps_global->msgmap, 1, st->parms->bar.style,
+ new_line, st->num_lines, st->parms->bar.color);
+ ps_global->mangled_header = 0;
+ }
+ else if(st->parms->bar.style == TextPercent)
+ update_titlebar_lpercent(new_line);
+ else
+ update_titlebar_percent(new_line);
+}
+
+
+/*----------------------------------------------------------------------
+ manager of global (to this module, anyway) scroll state structures
+
+
+ ----*/
+SCRLCTRL_S *
+scroll_state(int func)
+{
+ struct scrollstack {
+ SCRLCTRL_S s;
+ struct scrollstack *prev;
+ } *s;
+ static struct scrollstack *stack = NULL;
+
+ switch(func){
+ case SS_CUR: /* no op */
+ break;
+ case SS_NEW:
+ s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
+ memset((void *)s, 0, sizeof(struct scrollstack));
+ s->prev = stack;
+ stack = s;
+ break;
+ case SS_FREE:
+ if(stack){
+ s = stack->prev;
+ fs_give((void **)&stack);
+ stack = s;
+ }
+ break;
+ default: /* BUG: should complain */
+ break;
+ }
+
+ return(stack ? &stack->s : NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Save all the data for scrolling text and paint the screen
+
+
+ ----*/
+void
+set_scroll_text(SCROLL_S *sparms, long int current_line, SCRLCTRL_S *st)
+{
+ /* save all the stuff for possible asynchronous redraws */
+ st->parms = sparms;
+ st->top_text_line = current_line;
+ st->screen.start_line = SCROLL_LINES_ABOVE(ps_global);
+ st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
+ + SCROLL_LINES_BELOW(ps_global);
+ st->screen.width = -1; /* Force text formatting calculation */
+}
+
+
+/*----------------------------------------------------------------------
+ Redraw the text on the screen, possibly reformatting if necessary
+
+ Args None
+
+ ----*/
+void
+redraw_scroll_text(void)
+{
+ int i, offset;
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+
+ format_scroll_text();
+
+ offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;
+
+#ifdef _WINDOWS
+ mswin_beginupdate();
+#endif
+ /*---- Actually display the text on the screen ------*/
+ for(i = 0; i < st->screen.length; i++){
+ ClearLine(i + st->screen.start_line);
+ if((offset + i) < st->num_lines)
+ PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
+ st->line_lengths[offset + i], st->parms->text.handles);
+ }
+
+
+ fflush(stdout);
+#ifdef _WINDOWS
+ mswin_endupdate();
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Free memory used as scrolling buffers for text on disk. Also mark
+ text_lines as available
+ ----*/
+void
+zero_scroll_text(void)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ register int i;
+
+ for(i = 0; i < st->lines_allocated; i++)
+ if(st->parms->text.src == FileStar && st->text_lines[i])
+ fs_give((void **)&st->text_lines[i]);
+ else
+ st->text_lines[i] = NULL;
+
+ if(st->parms->text.src == FileStar && st->findex != NULL){
+ fclose(st->findex);
+ st->findex = NULL;
+ if(st->fname){
+ our_unlink(st->fname);
+ fs_give((void **)&st->fname);
+ }
+ }
+
+ if(st->text_lines)
+ fs_give((void **)&st->text_lines);
+
+ if(st->line_lengths)
+ fs_give((void **) &st->line_lengths);
+}
+
+
+/*----------------------------------------------------------------------
+
+Always format at least 20 chars wide. Wrapping lines would be crazy for
+screen widths of 1-20 characters
+ ----*/
+void
+format_scroll_text(void)
+{
+ int i;
+ char *p, **pp;
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ register short *ll;
+ register char **tl, **tl_end;
+
+ if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
+ && st->screen.length == PGSIZE(st)))
+ return;
+
+ st->screen.width = MAX(20, i);
+ st->screen.length = PGSIZE(st);
+
+ if(st->lines_allocated == 0) {
+ st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
+ st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
+ memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
+ st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
+ }
+
+ tl = st->text_lines;
+ ll = st->line_lengths;
+ tl_end = &st->text_lines[st->lines_allocated];
+
+ if(st->parms->text.src == CharStarStar) {
+ /*---- original text is already list of lines -----*/
+ /* The text could be wrapped nicely for narrow screens; for now
+ it will get truncated as it is displayed */
+ for(pp = (char **)st->parms->text.text; *pp != NULL;) {
+ *tl++ = *pp++;
+ *ll++ = st->screen.width;
+ if(tl >= tl_end) {
+ i = tl - st->text_lines;
+ st->lines_allocated *= 2;
+ fs_resize((void **)&st->text_lines,
+ st->lines_allocated * sizeof(char *));
+ fs_resize((void **)&st->line_lengths,
+ st->lines_allocated*sizeof(short));
+ tl = &st->text_lines[i];
+ ll = &st->line_lengths[i];
+ tl_end = &st->text_lines[st->lines_allocated];
+ }
+ }
+
+ st->num_lines = tl - st->text_lines;
+ }
+ else if (st->parms->text.src == CharStar) {
+ /*------ Format the plain text ------*/
+ for(p = (char *)st->parms->text.text; *p; ) {
+ *tl = p;
+
+ for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
+ ;
+
+ *ll = p - *tl;
+ ll++; tl++;
+ if(tl >= tl_end) {
+ i = tl - st->text_lines;
+ st->lines_allocated *= 2;
+ fs_resize((void **)&st->text_lines,
+ st->lines_allocated * sizeof(char *));
+ fs_resize((void **)&st->line_lengths,
+ st->lines_allocated*sizeof(short));
+ tl = &st->text_lines[i];
+ ll = &st->line_lengths[i];
+ tl_end = &st->text_lines[st->lines_allocated];
+ }
+
+ if(*p == '\r' && *(p+1) == '\n')
+ p += 2;
+ else if(*p == '\n' || *p == '\r')
+ p++;
+ }
+
+ st->num_lines = tl - st->text_lines;
+ }
+ else {
+ /*------ Display text is in a file --------*/
+
+ /*
+ * This is pretty much only useful under DOS where we can't fit
+ * all of big messages in core at once. This scheme makes
+ * some simplifying assumptions:
+ * 1. Lines are on disk just the way we'll display them. That
+ * is, line breaks and such are left to the function that
+ * writes the disk file to catch and fix.
+ * 2. We get away with this mainly because the DOS display isn't
+ * going to be resized out from under us.
+ *
+ * The idea is to use the already alloc'd array of char * as a
+ * buffer for sections of what's on disk. We'll set up the first
+ * few lines here, and read new ones in as needed in
+ * scroll_scroll_text().
+ *
+ * but first, make sure there are enough buffer lines allocated
+ * to serve as a place to hold lines from the file.
+ *
+ * Actually, this is also used under windows so the display will
+ * be resized out from under us. So I changed the following
+ * to always
+ * 1. free old text_lines, which may have been allocated
+ * for a narrow screen.
+ * 2. insure we have enough text_lines
+ * 3. reallocate all text_lines that are needed.
+ * (tom unger 10/26/94)
+ */
+
+ /* free old text lines, which may be too short. */
+ for(i = 0; i < st->lines_allocated; i++)
+ if(st->text_lines[i]) /* clear alloc'd lines */
+ fs_give((void **)&st->text_lines[i]);
+
+ /* Insure we have enough text lines. */
+ if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
+ st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */
+
+ fs_resize((void **)&st->text_lines,
+ st->lines_allocated * sizeof(char *));
+ memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
+ fs_resize((void **)&st->line_lengths,
+ st->lines_allocated*sizeof(short));
+ }
+
+ /* reallocate all text lines that are needed. */
+ for(i = 0; i <= PGSIZE(st); i++)
+ if(st->text_lines[i] == NULL)
+ st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
+ * sizeof(char));
+
+ tl = &st->text_lines[i];
+
+ st->num_lines = make_file_index();
+
+ ScrollFile(st->top_text_line); /* then load them up */
+ }
+
+ /*
+ * Efficiency hack. If there are handles, fill in their
+ * line number field for later...
+ */
+ if(st->parms->text.handles){
+ long line;
+ int i, col, n, key;
+ HANDLE_S *h;
+
+ for(line = 0; line < st->num_lines; line++)
+ for(i = 0, col = 0; i < st->line_lengths[line];)
+ switch(st->text_lines[line][i]){
+ case TAG_EMBED:
+ i++;
+ switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
+ : 0){
+ case TAG_HANDLE:
+ for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
+ key = (key * 10) + (st->text_lines[line][++i] - '0');
+
+ i++;
+ for(h = st->parms->text.handles; h; h = h->next)
+ if(h->key == key){
+ scroll_handle_set_loc(&h->loc, line, col);
+ break;
+ }
+
+ if(!h) /* anything behind us? */
+ for(h = st->parms->text.handles->prev; h; h = h->prev)
+ if(h->key == key){
+ scroll_handle_set_loc(&h->loc, line, col);
+ break;
+ }
+
+ break;
+
+ case TAG_FGCOLOR :
+ case TAG_BGCOLOR :
+ i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
+ break;
+
+ case TAG_INVON:
+ case TAG_INVOFF:
+ case TAG_BOLDON:
+ case TAG_BOLDOFF:
+ case TAG_ULINEON:
+ case TAG_ULINEOFF:
+ i++;
+ break;
+
+ default: /* literal embed char */
+ break;
+ }
+
+ break;
+
+ case TAB:
+ i++;
+ while(((++col) & 0x07) != 0) /* add tab's spaces */
+ ;
+
+ break;
+
+ default:
+ col += width_at_this_position((unsigned char*) &st->text_lines[line][i],
+ st->line_lengths[line] - i);
+ i++; /* character count */
+ break;
+ }
+ }
+
+#ifdef _WINDOWS
+ scroll_setrange (st->screen.length, st->num_lines);
+#endif
+
+ *tl = NULL;
+}
+
+
+/*
+ * ScrollFile - scroll text into the st struct file making sure 'line'
+ * of the file is the one first in the text_lines buffer.
+ *
+ * NOTE: talk about massive potential for tuning...
+ * Goes without saying this is still under constuction
+ */
+void
+ScrollFile(long int line)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ SCRLFILE_S sf;
+ register int i;
+
+ if(line <= 0){ /* reset and load first couple of pages */
+ fseek((FILE *) st->parms->text.text, 0L, 0);
+ line = 0L;
+ }
+
+ if(!st->text_lines)
+ return;
+
+ for(i = 0; i < PGSIZE(st); i++){
+ /*** do stuff to get the file pointer into the right place ***/
+ /*
+ * BOGUS: this is painfully crude right now, but I just want to get
+ * it going.
+ *
+ * possibly in the near furture, an array of indexes into the
+ * file that are the offset for the beginning of each line will
+ * speed things up. Of course, this
+ * will have limits, so maybe a disk file that is an array
+ * of indexes is the answer.
+ */
+ if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
+ || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
+ || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
+ || !st->text_lines[i]
+ || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
+ (FILE *) st->parms->text.text)))
+ break;
+
+ st->line_lengths[i] = sf.len;
+ }
+
+ for(; i < PGSIZE(st); i++)
+ if(st->text_lines[i]){ /* blank out any unused lines */
+ *st->text_lines[i] = '\0';
+ st->line_lengths[i] = 0;
+ }
+}
+
+
+/*
+ * make_file_index - do a single pass over the file containing the text
+ * to display, recording line lengths and offsets.
+ * NOTE: This is never really to be used on a real OS with virtual
+ * memory. This is the whole reason st->findex exists. Don't
+ * want to waste precious memory on a stupid array that could
+ * be very large.
+ */
+long
+make_file_index(void)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ SCRLFILE_S sf;
+ long l = 0L;
+ int state = 0;
+
+ if(!st->findex){
+ if(!st->fname)
+ st->fname = temp_nam(NULL, "pi");
+
+ if(!st->fname || (st->findex = our_fopen(st->fname,"w+b")) == NULL){
+ if(st->fname){
+ our_unlink(st->fname);
+ fs_give((void **)&st->fname);
+ }
+
+ return(0);
+ }
+ }
+ else
+ fseek(st->findex, 0L, 0);
+
+ fseek((FILE *)st->parms->text.text, 0L, 0);
+
+ while(1){
+ sf.len = st->screen.width + 1;
+ if(scroll_file_line((FILE *) st->parms->text.text,
+ tmp_20k_buf, &sf, &state)){
+ fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
+ l++;
+ }
+ else
+ break;
+ }
+
+ fseek((FILE *)st->parms->text.text, 0L, 0);
+
+ return(l);
+}
+
+
+/*----------------------------------------------------------------------
+ Get the next line to scroll from the given file
+
+ ----*/
+char *
+scroll_file_line(FILE *fp, char *buf, SCRLFILE_S *sfp, int *wrapt)
+{
+ register char *s = NULL;
+
+ while(1){
+ if(!s){
+ sfp->offset = ftell(fp);
+ if(!(s = fgets(buf, sfp->len, fp)))
+ return(NULL); /* can't grab a line? */
+ }
+
+ if(!*s){
+ *wrapt = 1; /* remember; that we wrapped */
+ break;
+ }
+ else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
+ int empty = (*wrapt && s == buf);
+
+ *wrapt = 0; /* turn off wrapped state */
+ if(empty)
+ s = NULL; /* get a new line */
+ else
+ break; /* done! */
+ }
+ else
+ s++;
+ }
+
+ sfp->len = s - buf;
+ return(buf);
+}
+
+
+/*----------------------------------------------------------------------
+ Scroll the text on the screen
+
+ Args: new_top_line -- The line to be displayed on top of the screen
+ redraw -- Flag to force a redraw even if nothing changed
+
+ Returns: resulting top line
+ Note: the returned line number may be less than new_top_line if
+ reformatting caused the total line count to change.
+
+ ----*/
+long
+scroll_scroll_text(long int new_top_line, HANDLE_S *handle, int redraw)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ int num_display_lines, l, top;
+ POSLIST_S *lp, *lp2;
+
+ /* When this is true, we're still on the same page of the display. */
+ if(st->top_text_line == new_top_line && !redraw){
+ /* handle changed, so hilite the new handle and unhilite the old */
+ if(handle && handle != st->parms->text.handles){
+ top = st->screen.start_line - new_top_line;
+ /* hilite the new one */
+ if(!scroll_handle_obscured(handle))
+ for(lp = handle->loc; lp; lp = lp->next)
+ if((l = lp->where.row) >= st->top_text_line
+ && l < st->top_text_line + st->screen.length){
+ if(st->parms->text.src == FileStar)
+ l -= new_top_line;
+
+ PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
+ st->line_lengths[l], handle);
+ }
+
+ /* unhilite the old one */
+ if(!scroll_handle_obscured(st->parms->text.handles))
+ for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
+ if((l = lp->where.row) >= st->top_text_line
+ && l < st->top_text_line + st->screen.length){
+ for(lp2 = handle->loc; lp2; lp2 = lp2->next)
+ if(l == lp2->where.row)
+ break;
+
+ if(!lp2){
+ if(st->parms->text.src == FileStar)
+ l -= new_top_line;
+
+ PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
+ st->line_lengths[l], handle);
+ }
+ }
+
+ st->parms->text.handles = handle; /* update current */
+ }
+
+ return(new_top_line);
+ }
+
+ num_display_lines = PGSIZE(st);
+
+ format_scroll_text();
+
+ if(st->top_text_line >= st->num_lines) /* don't pop line count */
+ new_top_line = st->top_text_line = MAX(st->num_lines - 1, 0);
+
+ if(st->parms->text.src == FileStar)
+ ScrollFile(new_top_line); /* set up new st->text_lines */
+
+#ifdef _WINDOWS
+ scroll_setrange (st->screen.length, st->num_lines);
+ scroll_setpos (new_top_line);
+#endif
+
+ /* ---
+ Check out the scrolling situation. If we want to scroll, but BeginScroll
+ says we can't then repaint, + 10 is so we repaint most of the time.
+ ----*/
+ if(redraw ||
+ (st->top_text_line - new_top_line + 10 >= num_display_lines ||
+ new_top_line - st->top_text_line + 10 >= num_display_lines) ||
+ BeginScroll(st->screen.start_line,
+ st->screen.start_line + num_display_lines - 1) != 0) {
+ /* Too much text to scroll, or can't scroll -- just repaint */
+
+ if(handle)
+ st->parms->text.handles = handle;
+
+ st->top_text_line = new_top_line;
+ redraw_scroll_text();
+ }
+ else{
+ /*
+ * We're going to scroll the screen, but first we have to make sure
+ * the old hilited handles are unhilited if they are going to remain
+ * on the screen.
+ */
+ top = st->screen.start_line - st->top_text_line;
+ if(handle && handle != st->parms->text.handles
+ && st->parms->text.handles
+ && !scroll_handle_obscured(st->parms->text.handles))
+ for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
+ if((l = lp->where.row) >= MAX(st->top_text_line,new_top_line)
+ && l < MIN(st->top_text_line,new_top_line) + st->screen.length){
+ if(st->parms->text.src == FileStar)
+ l -= new_top_line;
+
+ PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
+ st->line_lengths[l], handle);
+ }
+
+ if(new_top_line > st->top_text_line){
+ /*------ scroll down ------*/
+ while(new_top_line > st->top_text_line) {
+ ScrollRegion(1);
+
+ l = (st->parms->text.src == FileStar)
+ ? num_display_lines - (new_top_line - st->top_text_line)
+ : st->top_text_line + num_display_lines;
+
+ if(l < st->num_lines){
+ PutLine0n8b(st->screen.start_line + num_display_lines - 1,
+ 0, st->text_lines[l], st->line_lengths[l],
+ handle ? handle : st->parms->text.handles);
+ /*
+ * We clear to the end of line in the right background
+ * color. If the line was exactly the width of the screen
+ * then PutLine0n8b will have left _col and _row moved to
+ * the start of the next row. We don't need or want to clear
+ * that next row.
+ */
+ if(pico_usingcolor()
+ && (st->line_lengths[l] < ps_global->ttyo->screen_cols
+ || visible_linelen(l) < ps_global->ttyo->screen_cols))
+ CleartoEOLN();
+ }
+
+ st->top_text_line++;
+ }
+ }
+ else{
+ /*------ scroll up -----*/
+ while(new_top_line < st->top_text_line) {
+ ScrollRegion(-1);
+
+ st->top_text_line--;
+ l = (st->parms->text.src == FileStar)
+ ? st->top_text_line - new_top_line
+ : st->top_text_line;
+ PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
+ st->line_lengths[l],
+ handle ? handle : st->parms->text.handles);
+ /*
+ * We clear to the end of line in the right background
+ * color. If the line was exactly the width of the screen
+ * then PutLine0n8b will have left _col and _row moved to
+ * the start of the next row. We don't need or want to clear
+ * that next row.
+ */
+ if(pico_usingcolor()
+ && (st->line_lengths[l] < ps_global->ttyo->screen_cols
+ || visible_linelen(l) < ps_global->ttyo->screen_cols))
+ CleartoEOLN();
+ }
+ }
+
+ EndScroll();
+
+ if(handle && handle != st->parms->text.handles){
+ POSLIST_S *lp;
+
+ for(lp = handle->loc; lp; lp = lp->next)
+ if(lp->where.row >= st->top_text_line
+ && lp->where.row < st->top_text_line + st->screen.length){
+ PutLine0n8b(st->screen.start_line
+ + (lp->where.row - st->top_text_line),
+ 0, st->text_lines[lp->where.row],
+ st->line_lengths[lp->where.row],
+ handle);
+
+ }
+
+ st->parms->text.handles = handle;
+ }
+
+ fflush(stdout);
+ }
+
+ return(new_top_line);
+}
+
+
+/*---------------------------------------------------------------------
+ Edit individual char in text so that the entire text doesn't need
+ to be completely reformatted.
+
+ Returns 0 if there were no errors, 1 if we would like the entire
+ text to be reformatted.
+----*/
+int
+ng_scroll_edit(CONTEXT_S *context, int index)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ char *ngp, tmp[MAILTMPLEN+10];
+ int len;
+ FOLDER_S *f;
+
+ if (!(f = folder_entry(index, FOLDERS(context))))
+ return 1;
+ if(f->subscribed)
+ return 0; /* nothing in scroll needs to be changed */
+ tmp[0] = TAG_HANDLE;
+ snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
+ tmp[sizeof(tmp)-1] = '\0';
+ tmp[1] = len = strlen(tmp+2);
+ snprintf(tmp+len+2, sizeof(tmp)-(len+2), "%s ", f->selected ? "[ ]" : "[X]");
+ tmp[sizeof(tmp)-1] = '\0';
+ snprintf(tmp+len+6, sizeof(tmp)-(len+6), "%.*s", MAILTMPLEN, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ ngp = *(st->text_lines);
+
+ ngp = strstr(ngp, tmp);
+
+ if(!ngp) return 1;
+ ngp += 3+len;
+
+ /* assumption that text is of form "[ ] xxx.xxx" */
+
+ if(ngp){
+ if(*ngp == 'X'){
+ *ngp = ' ';
+ return 0;
+ }
+ else if (*ngp == ' '){
+ *ngp = 'X';
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/*---------------------------------------------------------------------
+ Similar to ng_scroll_edit, but this is the more general case of
+ selecting a folder, as opposed to selecting a newsgroup for
+ subscription while in listmode.
+
+ Returns 0 if there were no errors, 1 if we would like the entire
+ text to be reformatted.
+----*/
+int
+folder_select_update(CONTEXT_S *context, int index)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ FOLDER_S *f;
+ char *ngp, tmp[MAILTMPLEN+10];
+ int len, total, fnum, num_sel = 0;
+
+ if (!(f = folder_entry(index, FOLDERS(context))))
+ return 1;
+ ngp = *(st->text_lines);
+
+ total = folder_total(FOLDERS(context));
+
+ for (fnum = 0; num_sel < 2 && fnum < total; fnum++)
+ if(folder_entry(fnum, FOLDERS(context))->selected)
+ num_sel++;
+ if(!num_sel || (f->selected && num_sel == 1))
+ return 1; /* need to reformat the whole thing */
+
+ tmp[0] = TAG_HANDLE;
+ snprintf(tmp+2, sizeof(tmp)-2, "%d", st->parms->text.handles->key);
+ tmp[sizeof(tmp)-1] = '\0';
+ tmp[1] = len = strlen(tmp+2);
+
+ ngp = strstr(ngp, tmp);
+ if(!ngp) return 1;
+
+ if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global)){
+ ngp += 2 + len;
+ while(*ngp && ngp[0] != TAG_EMBED
+ && ngp[1] != (f->selected ? TAG_BOLDOFF : TAG_BOLDON)
+ && *ngp != *(f->name))
+ ngp++;
+
+ if (!(*ngp) || (*ngp == *(f->name)))
+ return 1;
+ else {
+ ngp++;
+ *ngp = (f->selected ? TAG_BOLDON : TAG_BOLDOFF);
+ return 0;
+ }
+ }
+ else{
+ while(*ngp != ' ' && *ngp != *(f->name) && *ngp)
+ ngp++;
+ if(!(*ngp) || (*ngp == *(f->name)))
+ return 1;
+ else {
+ ngp++;
+ *ngp = f->selected ? 'X' : ' ';
+ return 0;
+ }
+ }
+}
+
+
+/*---------------------------------------------------------------------
+ We gotta go through all of the formatted text and add "[ ] " in the right
+ place. If we don't do this, we must completely reformat the whole text,
+ which could take a very long time.
+
+ Return 1 if we encountered some sort of error and we want to reformat the
+ whole text, return 0 if everything went as planned.
+
+ ASSUMPTION: for this to work, we assume that there are only total
+ number of handles, numbered 1 through total.
+----*/
+int
+scroll_add_listmode(CONTEXT_S *context, int total)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ long i;
+ char *ngp, *ngname, handle_str[MAILTMPLEN];
+ HANDLE_S *h;
+
+
+ ngp = *(st->text_lines);
+ h = st->parms->text.handles;
+
+ while(h && h->key != 1 && h->prev)
+ h = h->prev;
+ if (!h) return 1;
+ handle_str[0] = TAG_EMBED;
+ handle_str[1] = TAG_HANDLE;
+ for(i = 1; i <= total && h; i++, h = h->next){
+ snprintf(handle_str+3, sizeof(handle_str)-3, "%d", h->key);
+ handle_str[sizeof(handle_str)-1] = '\0';
+ handle_str[2] = strlen(handle_str+3);
+ ngp = strstr(ngp, handle_str);
+ if(!ngp){
+ ngp = *(st->text_lines);
+ if (!ngp)
+ return 1;
+ }
+ ngname = ngp + strlen(handle_str);
+ while (strncmp(ngp, " ", 4) && !(*ngp == '\n')
+ && !(ngp == *(st->text_lines)))
+ ngp--;
+ if (strncmp(ngp, " ", 4))
+ return 1;
+ while(ngp+4 != ngname && *ngp){
+ ngp[0] = ngp[4];
+ ngp++;
+ }
+
+ if(folder_entry(h->h.f.index, FOLDERS(context))->subscribed){
+ ngp[0] = 'S';
+ ngp[1] = 'U';
+ ngp[2] = 'B';
+ }
+ else{
+ ngp[0] = '[';
+ ngp[1] = ' ';
+ ngp[2] = ']';
+ }
+ ngp[3] = ' ';
+ }
+
+ return 0;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Search the set scrolling text
+
+ Args: start_line -- line to start searching on
+ start_index -- column to start searching at in first line
+ word -- string to search for
+ cursor_pos -- position of cursor is returned to caller here
+ (Actually, this isn't really the position of the
+ cursor because we don't know where we are on the
+ screen. So row is set to the line number and col
+ is set to the right column.)
+ offset_in_line -- Offset where match was found.
+
+ Returns: the line the word was found on, or -2 if it wasn't found, or
+ -3 if the only match is at column start_index - 1.
+
+ ----*/
+int
+search_scroll_text(long int start_line, int start_index, char *word,
+ Pos *cursor_pos, int *offset_in_line)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ char *wh;
+ long l, offset, dlines;
+#define SROW(N) ((N) - offset)
+#define SLINE(N) st->text_lines[SROW(N)]
+#define SLEN(N) st->line_lengths[SROW(N)]
+
+ dlines = PGSIZE(st);
+ offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
+
+ if(start_line < st->num_lines){
+ /* search first line starting at position start_index in */
+ if((wh = search_scroll_line(SLINE(start_line) + start_index,
+ word,
+ SLEN(start_line) - start_index,
+ st->parms->text.handles != NULL)) != NULL){
+ cursor_pos->row = start_line;
+ cursor_pos->col = scroll_handle_column(SROW(start_line),
+ *offset_in_line = wh - SLINE(start_line));
+ return(start_line);
+ }
+
+ if(st->parms->text.src == FileStar)
+ offset++;
+
+ for(l = start_line + 1; l < st->num_lines; l++) {
+ if(st->parms->text.src == FileStar && l > offset + dlines)
+ ScrollFile(offset += dlines);
+
+ if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
+ st->parms->text.handles != NULL)) != NULL){
+ cursor_pos->row = l;
+ cursor_pos->col = scroll_handle_column(SROW(l),
+ *offset_in_line = wh - SLINE(l));
+ return(l);
+ }
+ }
+ }
+ else
+ start_line = st->num_lines;
+
+ if(st->parms->text.src == FileStar) /* wrap offset */
+ ScrollFile(offset = 0);
+
+ for(l = 0; l < start_line; l++) {
+ if(st->parms->text.src == FileStar && l > offset + dlines)
+ ScrollFile(offset += dlines);
+
+ if((wh = search_scroll_line(SLINE(l), word, SLEN(l),
+ st->parms->text.handles != NULL)) != NULL){
+ cursor_pos->row = l;
+ cursor_pos->col = scroll_handle_column(SROW(l),
+ *offset_in_line = wh - SLINE(l));
+ return(l);
+ }
+ }
+
+ /* search in current line */
+ if(start_line < st->num_lines
+ && (wh = search_scroll_line(SLINE(start_line), word,
+ start_index + strlen(word) - 2,
+ st->parms->text.handles != NULL)) != NULL){
+ cursor_pos->row = start_line;
+ cursor_pos->col = scroll_handle_column(SROW(start_line),
+ *offset_in_line = wh - SLINE(start_line));
+
+ return(start_line);
+ }
+
+ /* see if the only match is a repeat */
+ if(start_index > 0 && start_line < st->num_lines
+ && (wh = search_scroll_line(
+ SLINE(start_line) + start_index - 1,
+ word, strlen(word),
+ st->parms->text.handles != NULL)) != NULL){
+ cursor_pos->row = start_line;
+ cursor_pos->col = scroll_handle_column(SROW(start_line),
+ *offset_in_line = wh - SLINE(start_line));
+ return(-3);
+ }
+
+ return(-2);
+}
+
+
+/*----------------------------------------------------------------------
+ Search one line of scroll text for given string
+
+ Args: haystack -- The string to search in, the larger string
+ needle -- The string to search for, the smaller string
+ n -- The max number of chars in haystack to search
+
+ Search for first occurrence of needle in the haystack, and return a pointer
+ into the string haystack when it is found. The search is case independent.
+ ----*/
+char *
+search_scroll_line(char *haystack, char *needle, int n, int handles)
+{
+ char *return_ptr = NULL, *found_it = NULL;
+ char *haystack_copy, *p, *free_this = NULL, *end;
+ char buf[1000];
+ int state = 0, i = 0;
+
+ if(n > 0 && haystack){
+ if(n < sizeof(buf))
+ haystack_copy = buf;
+ else
+ haystack_copy = free_this = (char *) fs_get((n+1) * sizeof(char));
+
+ strncpy(haystack_copy, haystack, n);
+ haystack_copy[n] = '\0';
+
+ /*
+ * We don't want to match text inside embedded tags.
+ * Replace embedded octets with nulls and convert
+ * uppercase ascii to lowercase. We should also do
+ * some sort of canonicalization of UTF-8 but that
+ * sounds daunting.
+ */
+ for(i = n, p = haystack_copy; i-- > 0 && *p; p++){
+ if(handles)
+ switch(state){
+ case 0 :
+ if(*p == TAG_EMBED){
+ *p = '\0';
+ state = -1;
+ continue;
+ }
+ else{
+ /* lower case just ascii chars */
+ if(!(*p & 0x80) && isupper(*p))
+ *p = tolower(*p);
+ }
+
+ break;
+
+ case -1 :
+ state = (*p == TAG_HANDLE)
+ ? -2
+ : (*p == TAG_FGCOLOR || *p == TAG_BGCOLOR) ? RGBLEN : 0;
+ *p = '\0';
+ continue;
+
+ case -2 :
+ state = *p; /* length of handle's key */
+ *p = '\0';
+ continue;
+
+ default :
+ state--;
+ *p = '\0';
+ continue;
+ }
+ }
+
+ /*
+ * The haystack_copy string now looks like
+ *
+ * "chars\0\0\0\0\0\0chars...\0\0\0chars... \0"
+ *
+ * with that final \0 at haystack_copy[n].
+ * Search each piece one at a time.
+ */
+
+ end = haystack_copy + n;
+ p = haystack_copy;
+
+ while(p < end && !return_ptr){
+
+ /* skip nulls */
+ while(*p == '\0' && p < end)
+ p++;
+
+ if(*p != '\0')
+ found_it = srchstr(p, needle);
+
+ if(found_it){
+ /* found it, make result relative to haystack */
+ return_ptr = haystack + (found_it - haystack_copy);
+ }
+
+ /* skip to next null */
+ while(*p != '\0' && p < end)
+ p++;
+ }
+
+ if(free_this)
+ fs_give((void **) &free_this);
+ }
+
+ return(return_ptr);
+}
+
+
+/*
+ * Returns the number of columns taken up by the visible part of the line.
+ * That is, account for handles and color changes and so forth.
+ */
+int
+visible_linelen(int line)
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ int i, n, len = 0;
+
+ if(line < 0 || line >= st->num_lines)
+ return(len);
+
+ for(i = 0, len = 0; i < st->line_lengths[line];)
+ switch(st->text_lines[line][i]){
+
+ case TAG_EMBED:
+ i++;
+ switch((i < st->line_lengths[line]) ? st->text_lines[line][i] : 0){
+ case TAG_HANDLE:
+ i++;
+ /* skip the length byte plus <length> more bytes */
+ if(i < st->line_lengths[line]){
+ n = st->text_lines[line][i];
+ i++;
+ }
+
+ if(i < st->line_lengths[line] && n > 0)
+ i += n;
+
+ break;
+
+ case TAG_FGCOLOR :
+ case TAG_BGCOLOR :
+ i += (RGBLEN + 1); /* 1 for TAG, RGBLEN for color */
+ break;
+
+ case TAG_INVON:
+ case TAG_INVOFF:
+ case TAG_BOLDON:
+ case TAG_BOLDOFF:
+ case TAG_ULINEON:
+ case TAG_ULINEOFF:
+ i++;
+ break;
+
+ case TAG_EMBED: /* escaped embed character */
+ i++;
+ len++;
+ break;
+
+ default: /* the embed char was literal */
+ i++;
+ len += 2;
+ break;
+ }
+
+ break;
+
+ case TAB:
+ i++;
+ while(((++len) & 0x07) != 0) /* add tab's spaces */
+ ;
+
+ break;
+
+ default:
+ i++;
+ len++;
+ break;
+ }
+
+ return(len);
+}
+
+
+/*----------------------------------------------------------------------
+ Display the contents of the given file (likely output from some command)
+
+ Args: filename -- name of file containing output
+ title -- title to be used for screen displaying output
+ alt_msg -- if no output, Q this message instead of the default
+ mode -- non-zero to display short files in status line
+ Returns: none
+ ----*/
+void
+display_output_file(char *filename, char *title, char *alt_msg, int mode)
+{
+ STORE_S *in_file = NULL, *out_store = NULL;
+
+ if((in_file = so_get(FileStar, filename, READ_ACCESS|READ_FROM_LOCALE))){
+ if(mode == DOF_BRIEF){
+ int msg_q = 0, i = 0;
+ char buf[512], *msg_p[4];
+#define MAX_SINGLE_MSG_LEN 60
+
+ buf[0] = '\0';
+ msg_p[0] = buf;
+
+ /*
+ * Might need to do something about CRLFs for Windows.
+ */
+ while(so_fgets(in_file, msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf))
+ && msg_q < 3
+ && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
+ msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
+ if (*(msg_p[++msg_q] - 1) == '\n')
+ *(msg_p[msg_q] - 1) = '\0';
+ }
+
+ if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
+ if(*msg_p[0])
+ for(i = 0; i < msg_q; i++)
+ q_status_message2(SM_ORDER, 3, 4,
+ "%s Result: %s", title, msg_p[i]);
+ else
+ q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
+ alt_msg
+ ? alt_msg
+ : " command completed with no output");
+
+ so_give(&in_file);
+ in_file = NULL;
+ }
+ }
+ else if(mode == DOF_EMPTY){
+ unsigned char c;
+
+ if(so_readc(&c, in_file) < 1){
+ q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
+ alt_msg
+ ? alt_msg
+ : " command completed with no output");
+ so_give(&in_file);
+ in_file = NULL;
+ }
+ }
+
+ /*
+ * We need to translate the file contents from the user's locale
+ * charset to UTF-8 for use in scrolltool. We get that translation
+ * from the READ_FROM_LOCALE in the in_file storage object.
+ * It would be nice to skip this step but scrolltool doesn't use
+ * the storage object routines to read from the file, so would
+ * skip the translation step.
+ */
+ if(in_file){
+ char *errstr;
+ gf_io_t gc, pc;
+
+ if(!(out_store = so_get(CharStar, NULL, EDIT_ACCESS))){
+ so_give(&in_file);
+ our_unlink(filename);
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space."));
+ return;
+ }
+
+ so_seek(in_file, 0L, 0);
+
+ gf_filter_init();
+
+ gf_link_filter(gf_wrap,
+ gf_wrap_filter_opt(ps_global->ttyo->screen_cols - 4,
+ ps_global->ttyo->screen_cols,
+ NULL, 0, GFW_NONE));
+
+ gf_set_so_readc(&gc, in_file);
+ gf_set_so_writec(&pc, out_store);
+
+ if((errstr = gf_pipe(gc, pc)) != NULL){
+ so_give(&in_file);
+ so_give(&out_store);
+ our_unlink(filename);
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Error allocating space."));
+ return;
+ }
+
+ gf_clear_so_writec(out_store);
+ gf_clear_so_readc(in_file);
+ so_give(&in_file);
+ }
+
+ if(out_store){
+ SCROLL_S sargs;
+ char title_buf[64];
+
+ snprintf(title_buf, sizeof(title_buf), "HELP FOR %s VIEW", title);
+ title_buf[sizeof(title_buf)-1] = '\0';
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(out_store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = "output";
+ sargs.bar.title = title;
+ sargs.bar.style = TextPercent;
+ sargs.help.text = h_simple_text_view;
+ sargs.help.title = title_buf;
+ scrolltool(&sargs);
+ ps_global->mangled_screen = 1;
+ so_give(&out_store);
+ }
+
+ our_unlink(filename);
+ }
+ else
+ dprint((2, "Error reopening %s to get results: %s\n",
+ filename ? filename : "?", error_description(errno)));
+}
+
+
+/*--------------------------------------------------------------------
+ Call the function that will perform the double click operation
+
+ Returns: the current top line
+--------*/
+long
+doubleclick_handle(SCROLL_S *sparms, HANDLE_S *next_handle, int *cmd, int *done)
+{
+ if(sparms->mouse.clickclick){
+ if((*sparms->mouse.clickclick)(sparms))
+ *done = 1;
+ }
+ else
+ switch(scroll_handle_launch(next_handle, TRUE)){
+ case 1 :
+ *cmd = MC_EXIT; /* propagate */
+ *done = 1;
+ break;
+
+ case -1 :
+ cmd_cancelled("View");
+ break;
+
+ default :
+ break;
+ }
+
+ return(scroll_state(SS_CUR)->top_text_line);
+}
+
+
+
+#ifdef _WINDOWS
+/*
+ * Just a little something to simplify assignments
+ */
+#define VIEWPOPUP(p, c, s) { \
+ (p)->type = tQueue; \
+ (p)->data.val = c; \
+ (p)->label.style = lNormal; \
+ (p)->label.string = s; \
+ }
+
+
+/*
+ *
+ */
+int
+format_message_popup(sparms, in_handle)
+ SCROLL_S *sparms;
+ int in_handle;
+{
+ MPopup fmp_menu[32];
+ HANDLE_S *h = NULL;
+ int i = -1, n;
+ long rawno;
+ MESSAGECACHE *mc;
+
+ /* Reason to offer per message ops? */
+ if(mn_get_total(ps_global->msgmap) > 0L){
+ if(in_handle){
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+
+ switch((h = get_handle(st->parms->text.handles, in_handle))->type){
+ case Attach :
+ fmp_menu[++i].type = tIndex;
+ fmp_menu[i].label.string = "View Attachment";
+ fmp_menu[i].label.style = lNormal;
+ fmp_menu[i].data.val = 'X'; /* for local use */
+
+ if(h->h.attach
+ && dispatch_attachment(h->h.attach) != MCD_NONE
+ && !(h->h.attach->can_display & MCD_EXTERNAL)
+ && h->h.attach->body
+ && (h->h.attach->body->type == TYPETEXT
+ || (h->h.attach->body->type == TYPEMESSAGE
+ && h->h.attach->body->subtype
+ && !strucmp(h->h.attach->body->subtype,"rfc822")))){
+ fmp_menu[++i].type = tIndex;
+ fmp_menu[i].label.string = "View Attachment in New Window";
+ fmp_menu[i].label.style = lNormal;
+ fmp_menu[i].data.val = 'Y'; /* for local use */
+ }
+
+ fmp_menu[++i].type = tIndex;
+ fmp_menu[i].label.style = lNormal;
+ fmp_menu[i].data.val = 'Z'; /* for local use */
+ msgno_exceptions(ps_global->mail_stream,
+ mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap)),
+ h->h.attach->number, &n, FALSE);
+ fmp_menu[i].label.string = (n & MSG_EX_DELETE)
+ ? "Undelete Attachment"
+ : "Delete Attachment";
+ break;
+
+ case URL :
+ default :
+ fmp_menu[++i].type = tIndex;
+ fmp_menu[i].label.string = "View Link";
+ fmp_menu[i].label.style = lNormal;
+ fmp_menu[i].data.val = 'X'; /* for local use */
+
+ fmp_menu[++i].type = tIndex;
+ fmp_menu[i].label.string = "Copy Link";
+ fmp_menu[i].label.style = lNormal;
+ fmp_menu[i].data.val = 'W'; /* for local use */
+ break;
+ }
+
+ fmp_menu[++i].type = tSeparator;
+ }
+
+ /* Delete or Undelete? That is the question. */
+ fmp_menu[++i].type = tQueue;
+ fmp_menu[i].label.style = lNormal;
+ mc = ((rawno = mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap))) > 0L
+ && ps_global->mail_stream
+ && rawno <= ps_global->mail_stream->nmsgs)
+ ? mail_elt(ps_global->mail_stream, rawno) : NULL;
+ if(mc && mc->deleted){
+ fmp_menu[i].data.val = 'U';
+ fmp_menu[i].label.string = in_handle
+ ? "&Undelete Message" : "&Undelete";
+ }
+ else{
+ fmp_menu[i].data.val = 'D';
+ fmp_menu[i].label.string = in_handle
+ ? "&Delete Message" : "&Delete";
+ }
+
+ if(F_ON(F_ENABLE_FLAG, ps_global)){
+ fmp_menu[++i].type = tSubMenu;
+ fmp_menu[i].label.string = "Flag";
+ fmp_menu[i].data.submenu = flag_submenu(mc);
+ }
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'E', in_handle ? "&Export Message" : "&Export");
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'R',
+ in_handle ? "&Reply to Message" : "&Reply");
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'F',
+ in_handle ? "&Forward Message" : "&Forward");
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'B',
+ in_handle ? "&Bounce Message" : "&Bounce");
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'T', "&Take Addresses");
+
+ fmp_menu[++i].type = tSeparator;
+
+ if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");
+ }
+
+ if(mn_get_cur(ps_global->msgmap) > 0){
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'P', "View &Prev Message");
+ }
+
+ if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)
+ || mn_get_cur(ps_global->msgmap) > 0)
+ fmp_menu[++i].type = tSeparator;
+
+ /* Offer the attachment screen? */
+ for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
+ ;
+
+ if(n > 1){
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
+ }
+ }
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");
+
+ i++;
+ VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");
+
+ fmp_menu[++i].type = tTail;
+
+ if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
+ switch(fmp_menu[i].data.val){
+ case 'W' : /* Copy URL to clipboard */
+ mswin_addclipboard(h->h.url.path);
+ break;
+
+ case 'X' :
+ return(1); /* return like the user double-clicked */
+
+ break;
+
+ case 'Y' : /* popup the thing in another window */
+ display_att_window(h->h.attach);
+ break;
+
+ case 'Z' :
+ if(h && h->type == Attach){
+ msgno_exceptions(ps_global->mail_stream,
+ mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap)),
+ h->h.attach->number, &n, FALSE);
+ n ^= MSG_EX_DELETE;
+ msgno_exceptions(ps_global->mail_stream,
+ mn_m2raw(ps_global->msgmap,
+ mn_get_cur(ps_global->msgmap)),
+ h->h.attach->number, &n, TRUE);
+ q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
+ h->h.attach->number,
+ (n & MSG_EX_DELETE) ? "deleted" : "undeleted");
+
+ return(2);
+ }
+
+ break;
+
+ default :
+ break;
+ }
+
+ return(0);
+}
+
+
+
+/*
+ *
+ */
+int
+simple_text_popup(sparms, in_handle)
+ SCROLL_S *sparms;
+ int in_handle;
+{
+ MPopup simple_menu[12];
+ int n = 0;
+
+ VIEWPOPUP(&simple_menu[n], '%', "Print");
+ n++;
+
+ VIEWPOPUP(&simple_menu[n], 'S', "Save");
+ n++;
+
+ VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
+ n++;
+
+ simple_menu[n++].type = tSeparator;
+
+ VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
+ n++;
+
+ simple_menu[n].type = tTail;
+
+ (void) mswin_popup(simple_menu);
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Return characters in scroll tool buffer serially
+
+ Args: n -- index of char to return
+
+ Returns: returns the character at index 'n', or -1 on error or
+ end of buffer.
+
+ ----*/
+int
+mswin_readscrollbuf(n)
+ int n;
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ int c;
+ static char **orig = NULL, **l, *p;
+ static int lastn;
+
+ if(!st)
+ return(-1);
+
+ /*
+ * All of these are mind-numbingly slow at the moment...
+ */
+ switch(st->parms->text.src){
+ case CharStar :
+ return((n >= strlen((char *)st->parms->text.text))
+ ? -1 : ((char *)st->parms->text.text)[n]);
+
+ case CharStarStar :
+ /* BUG? is this test rigorous enough? */
+ if(orig != (char **)st->parms->text.text || n < lastn){
+ lastn = n;
+ if(orig = l = (char **)st->parms->text.text) /* reset l and p */
+ p = *l;
+ }
+ else{ /* use cached l and p */
+ c = n; /* and adjust n */
+ n -= lastn;
+ lastn = c;
+ }
+
+ while(l){ /* look for 'n' on each line */
+ for(; n && *p; n--, p++)
+ ;
+
+ if(n--) /* 'n' found ? */
+ p = *++l;
+ else
+ break;
+ }
+
+ return((l && *l) ? *p ? *p : '\n' : -1);
+
+ case FileStar :
+ return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
+ || (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);
+
+ default:
+ return(-1);
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ MSWin scroll callback. Called during scroll message processing.
+
+
+
+ Args: cmd - what type of scroll operation.
+ scroll_pos - paramter for operation.
+ used as position for SCROLL_TO operation.
+
+ Returns: TRUE - did the scroll operation.
+ FALSE - was not able to do the scroll operation.
+ ----*/
+int
+pcpine_do_scroll (cmd, scroll_pos)
+int cmd;
+long scroll_pos;
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ HANDLE_S *next_handle;
+ int paint = FALSE;
+ int num_display_lines;
+ int scroll_lines;
+ char message[64];
+ long maxscroll;
+
+
+ message[0] = '\0';
+ maxscroll = st->num_lines;
+ switch (cmd) {
+ case MSWIN_KEY_SCROLLUPLINE:
+ if(st->top_text_line > 0) {
+ st->top_text_line -= (int) scroll_pos;
+ paint = TRUE;
+ if (st->top_text_line <= 0){
+ snprintf(message, sizeof(message), "START of %.*s",
+ 32, STYLE_NAME(st->parms));
+ message[sizeof(message)-1] = '\0';
+ st->top_text_line = 0;
+ }
+ }
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNLINE:
+ if(st->top_text_line < maxscroll) {
+ st->top_text_line += (int) scroll_pos;
+ paint = TRUE;
+ if (st->top_text_line >= maxscroll){
+ snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
+ message[sizeof(message)-1] = '\0';
+ st->top_text_line = maxscroll;
+ }
+ }
+ break;
+
+ case MSWIN_KEY_SCROLLUPPAGE:
+ if(st->top_text_line > 0) {
+ num_display_lines = SCROLL_LINES(ps_global);
+ scroll_lines = MIN(MAX(num_display_lines -
+ ps_global->viewer_overlap, 1), num_display_lines);
+ if (st->top_text_line > scroll_lines)
+ st->top_text_line -= scroll_lines;
+ else {
+ st->top_text_line = 0;
+ snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
+ message[sizeof(message)-1] = '\0';
+ }
+ paint = TRUE;
+ }
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNPAGE:
+ num_display_lines = SCROLL_LINES(ps_global);
+ if(st->top_text_line < maxscroll) {
+ scroll_lines = MIN(MAX(num_display_lines -
+ ps_global->viewer_overlap, 1), num_display_lines);
+ st->top_text_line += scroll_lines;
+ if (st->top_text_line >= maxscroll) {
+ st->top_text_line = maxscroll;
+ snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
+ message[sizeof(message)-1] = '\0';
+ }
+ paint = TRUE;
+ }
+ break;
+
+ case MSWIN_KEY_SCROLLTO:
+ if (st->top_text_line != scroll_pos) {
+ st->top_text_line = scroll_pos;
+ if (st->top_text_line == 0)
+ snprintf(message, sizeof(message), "START of %.*s", 32, STYLE_NAME(st->parms));
+ else if(st->top_text_line >= maxscroll)
+ snprintf(message, sizeof(message), "END of %.*s", 32, STYLE_NAME(st->parms));
+
+ message[sizeof(message)-1] = '\0';
+ paint = TRUE;
+ }
+ break;
+ }
+
+ /* Need to frame some handles? */
+ if(st->parms->text.handles
+ && (next_handle = scroll_handle_in_frame(st->top_text_line)))
+ st->parms->text.handles = next_handle;
+
+ if (paint) {
+ mswin_beginupdate();
+ update_scroll_titlebar(st->top_text_line, 0);
+ (void) scroll_scroll_text(st->top_text_line,
+ st->parms->text.handles, 1);
+ if (message[0])
+ q_status_message(SM_INFO, 0, 1, message);
+
+ /* Display is always called so that the "START(END) of message"
+ * message gets removed when no longer at the start(end). */
+ display_message (KEY_PGDN);
+ mswin_endupdate();
+ }
+
+ return (TRUE);
+}
+
+
+char *
+pcpine_help_scroll(title)
+ char *title;
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+
+ if(title)
+ strncpy(title, (st->parms->help.title)
+ ? st->parms->help.title : "Alpine Help", 256);
+
+ return(pcpine_help(st->parms->help.text));
+}
+
+
+int
+pcpine_view_cursor(col, row)
+ int col;
+ long row;
+{
+ SCRLCTRL_S *st = scroll_state(SS_CUR);
+ int key;
+ long line;
+
+ return((row >= HEADER_ROWS(ps_global)
+ && row < HEADER_ROWS(ps_global) + SCROLL_LINES(ps_global)
+ && (line = (row - 2) + st->top_text_line) < st->num_lines
+ && (key = dot_on_handle(line, col))
+ && scroll_handle_selectable(get_handle(st->parms->text.handles,key)))
+ ? MSWIN_CURSOR_HAND
+ : MSWIN_CURSOR_ARROW);
+}
+#endif /* _WINDOWS */
diff --git a/alpine/mailview.h b/alpine/mailview.h
new file mode 100644
index 00000000..1a404a38
--- /dev/null
+++ b/alpine/mailview.h
@@ -0,0 +1,129 @@
+/*
+ * $Id: mailview.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_MAILVIEW_INCLUDED
+#define PINE_MAILVIEW_INCLUDED
+
+
+#include <general.h>
+#include "../pith/mailview.h"
+#include "titlebar.h"
+#include "keymenu.h"
+#include "../pith/handle.h"
+#include "../pith/store.h"
+#include "../pith/bitmap.h"
+
+
+/*
+ * display_output_file mode flags
+ */
+#define DOF_NONE 0
+#define DOF_EMPTY 1
+#define DOF_BRIEF 2
+
+
+#define STYLE_NAME(s) ((s)->text.desc ? (s)->text.desc : "text")
+
+
+/*
+ * Struct defining scrolltool operating parameters.
+ */
+typedef struct scrolltool_s {
+ struct { /* Data and its attributes to scroll */
+ void *text; /* what to scroll */
+ SourceType src; /* it's form (char **,char *,FILE *) */
+ char *desc; /* Description of "type" of data shown */
+ HANDLE_S *handles; /* embedded data descriptions */
+ } text;
+ struct { /* titlebar state */
+ char *title; /* screen's title */
+ TitleBarType style; /* it's type */
+ COLOR_PAIR *color;
+ } bar;
+ struct { /* screen's keymenu/command bindings */
+ struct key_menu *menu;
+ bitmap_t bitmap;
+ OtherMenu what;
+ void (*each_cmd)(struct scrolltool_s *, int);
+ } keys;
+ struct { /* help for this attachment */
+ HelpType text; /* help text */
+ char *title; /* title for help screen */
+ } help;
+ struct {
+ int (*click)(struct scrolltool_s *);
+ int (*clickclick)(struct scrolltool_s *);
+#ifdef _WINDOWS
+ /*
+ * For systems that support it, allow caller to do popup menu
+ */
+ int (*popup)(struct scrolltool_s *, int);
+#endif
+ } mouse;
+ struct { /* where to start paging from */
+ enum {FirstPage = 0, LastPage, Fragment, Offset, Handle} on;
+ union {
+ char *frag; /* fragment in html text to start on */
+ long offset;
+ } loc;
+ } start;
+ struct { /* Non-default Command Processor */
+ int (*tool)(int, MSGNO_S *, struct scrolltool_s *);
+ /* The union below is opaque as far as scrolltool itself is
+ * concerned, but is provided as a container to pass data
+ * between the scrolltool caller and the given "handler"
+ * callback (or any other callback that takes a scrolltool_s *).
+ */
+ union {
+ void *p;
+ int i;
+ } data;
+ } proc;
+ /* End of page processing */
+ int (*end_scroll)(struct scrolltool_s *);
+ /* Handler for invalid command input */
+ int (*bogus_input)(UCS);
+ unsigned resize_exit:1; /* Return from scrolltool if resized */
+ unsigned body_valid:1; /* Screen's body already displayed */
+ unsigned no_stat_msg:1; /* Don't display status messages */
+ unsigned vert_handle:1; /* hunt up and down on arrows/ctrl-[np] */
+ unsigned srch_handle:1; /* search to next handle */
+ unsigned quell_help:1; /* Don't show handle nav help message */
+ unsigned quell_newmail:1; /* Don't check for new mail */
+ unsigned quell_first_view:1; /* Don't act special first time through */
+ unsigned jump_is_debug:1;
+ unsigned use_indexline_color:1;
+} SCROLL_S;
+
+
+/* exported protoypes */
+void mail_view_screen(struct pine *);
+url_tool_t url_local_handler(char *);
+int url_local_mailto(char *);
+int url_local_mailto_and_atts(char *, PATMT *);
+int url_local_fragment(char *);
+int scrolltool(SCROLL_S *);
+int ng_scroll_edit(CONTEXT_S *, int);
+int folder_select_update(CONTEXT_S *, int);
+int scroll_add_listmode(CONTEXT_S *, int);
+void display_output_file(char *, char *, char *, int);
+int rfc2369_editorial(long, HANDLE_S **, int, int, gf_io_t);
+void view_writec_init(STORE_S *, HANDLE_S **, int, int);
+void view_writec_destroy(void);
+int view_writec(int);
+
+
+#endif /* PINE_MAILVIEW_INCLUDED */
diff --git a/alpine/makefile.wnt b/alpine/makefile.wnt
new file mode 100644
index 00000000..db2e4707
--- /dev/null
+++ b/alpine/makefile.wnt
@@ -0,0 +1,89 @@
+# $Id: makefile.wnt 14098 2005-10-03 18:54:13Z jpf@u.washington.edu $
+#
+# ========================================================================
+# Copyright 2006-2007 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+#
+#
+# Makefile for WIN NT version of the libalpine.lib and pine.exe
+#
+#
+CC=cl
+RM=del
+CP=copy
+RC=rc
+
+#includes symbol info for debugging
+CDEBUG= #-Zi -Od
+LDEBUG= /DEBUG /DEBUGTYPE:CV
+
+STDCFLAGS= -I..\include -I..\regex -nologo -MT -DWIN32 -DDOS -D_WINDOWS -DMSC_MALLOC
+
+CFLAGS= $(CDEBUG) $(STDCFLAGS) $(NET) $(EXTRACFLAGS)
+
+LFLAGS= $(LDEBUG) $(EXTRALDFLAGS)
+
+RCFLAGS =
+
+STDLIBS=oldnames.lib kernel32.lib advapi32.lib ws2_32.lib user32.lib gdi32.lib \
+ shell32.lib comdlg32.lib winmm.lib crypt32.lib
+SPELLLIBS=
+
+LIBER=lib
+LIBARGS=/nologo /verbose
+
+HFILES= ../include/system.h ../include/general.h \
+ addrbook.h adrbkcmd.h after.h alpine.h arg.h busy.h colorconf.h confscroll.h \
+ conftype.h context.h dispfilt.h flagmaint.h folder.h headers.h help.h imap.h \
+ init.h kblock.h keymenu.h ldapconf.h listsel.h mailcmd.h mailindx.h mailpart.h \
+ mailview.h newmail.h newuser.h pattern.h pipe.h print.h radio.h remote.h \
+ reply.h roleconf.h \
+ send.h setup.h signal.h status.h takeaddr.h talk.h titlebar.h
+
+OFILES= addrbook.obj adrbkcmd.obj after.obj alpine.obj arg.obj busy.obj colorconf.obj \
+ confscroll.obj context.obj dispfilt.obj flagmaint.obj folder.obj help.obj \
+ imap.obj init.obj kblock.obj keymenu.obj ldapconf.obj listsel.obj mailcmd.obj \
+ mailindx.obj mailpart.obj mailview.obj newmail.obj \
+ newuser.obj pattern.obj pipe.obj \
+ print.obj radio.obj remote.obj reply.obj roleconf.obj \
+ send.obj setup.obj signal.obj status.obj takeaddr.obj titlebar.obj
+
+OURLIBS= ../c-client/cclient.lib osdep/libalpineosd.lib osdep/mswin.res \
+ ../pith/osdep/libpithosd.lib ../pith/charconv/libpithcc.lib ../regex/libregex.lib ../pith/libpith.lib \
+ ../pico/osdep/libpicoosd.lib ../pico/libpico.lib
+
+LIBS= $(STDLIBS) $(LDAPLIBS) $(SPELLLIBS) $(OURLIBS) $(EXTRALIBES)
+
+all: alpine.exe rpload.exe rpdump.exe
+
+.c.obj:
+ $(CC) -c $(CFLAGS) "$(MAKEDIR)"\$*.c
+
+$(OFILES): $(HFILES)
+
+libalpine.lib: $(OFILES)
+ $(RM) libalpine.lib || rem
+ $(LIBER) /out:libalpine.lib $(OFILES)
+
+rpload.exe: rpload.obj ..\c-client\cclient.lib
+ link /subsystem:console /out:rpload.exe $(LFLAGS) rpload.obj bdate.obj $(LIBS)
+
+rpdump.exe: rpdump.obj ..\c-client\cclient.lib
+ link /subsystem:console /out:rpdump.exe $(LFLAGS) rpdump.obj bdate.obj $(LIBS)
+
+alpine.exe: $(OFILES) $(OURLIBS)
+ ..\pico\blddate > bdate.c
+ $(CC) /c $(CFLAGS) bdate.c
+ link /subsystem:windows /entry:wWinMainCRTStartup /out:alpine.exe $(LFLAGS) bdate.obj $(OFILES) $(LIBS)
+
+clean:
+ $(RM) *.lib
+ $(RM) *.obj
diff --git a/alpine/newmail.c b/alpine/newmail.c
new file mode 100644
index 00000000..ba1dbda9
--- /dev/null
+++ b/alpine/newmail.c
@@ -0,0 +1,333 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: newmail.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2009 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/newmail.h"
+#include "../pith/conf.h"
+#include "../pith/flag.h"
+#include "../pith/mailindx.h"
+#include "../pith/msgno.h"
+#include "../pith/bldaddr.h"
+#include "../pith/stream.h"
+#include "../pith/sort.h"
+#include "../pith/status.h"
+#include "../pith/util.h"
+#include "../pith/thread.h"
+#include "../pith/options.h"
+#include "../pith/folder.h"
+#include "../pith/ablookup.h"
+
+#ifdef _WINDOWS
+#include "../pico/osdep/mswin.h"
+#endif
+
+
+/*
+ * Internal prototypes
+ */
+void new_mail_win_mess(MAILSTREAM *, long);
+void new_mail_mess(MAILSTREAM *, long, long, int);
+void newmailfifo(int, char *, char *, char *);
+
+
+/*----------------------------------------------------------------------
+ pith optional function to queue a newmail announcement
+
+
+ ----*/
+void
+newmail_status_message(MAILSTREAM *stream, long n, long t_nm_count)
+{
+#ifdef _WINDOWS
+ if(mswin_newmailwinon())
+ new_mail_win_mess(stream, t_nm_count);
+#elif !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
+ if(ps_global->VAR_FIFOPATH)
+ new_mail_win_mess(stream, t_nm_count);
+#endif
+ if(n){
+ new_mail_mess(stream, sp_mail_since_cmd(stream), n, 0);
+ }
+}
+
+
+/*
+ * alert for each new message individually. new_mail_mess lumps
+ * messages together, we call new_mail_mess with 1 message at a time.
+ * This is currently for PC-Pine new mail window, but could probably
+ * be used more generally.
+ * stream - new mail stream
+ * number - number of new messages to alert for
+ */
+void
+new_mail_win_mess(MAILSTREAM *stream, long int number)
+{
+ int n, i;
+ MESSAGECACHE *mc;
+
+ if(!stream)
+ return;
+
+ /*
+ * spare6, or MN_STMP, should be safe to use for now, we
+ * just want to set which messages to alert about before
+ * going to c-client.
+ */
+ for(n = stream->nmsgs, i = 0; n > 1L && i < number; n--){
+ if(!get_lflag(stream, NULL, n, MN_EXLD)){
+ mc = mail_elt(stream, n);
+ if(mc)
+ mc->spare6 = 1;
+
+ if(++i == number)
+ break;
+ }
+ else{
+ mc = mail_elt(stream, n);
+ if(mc)
+ mc->spare6 = 0;
+ }
+ }
+ /*
+ * Here n is the first new message we want to notify about.
+ * spare6 will tell us which ones to use. We set spare6
+ * in case of new mail or expunge that could happen when
+ * we mail_fetchstructure in new_mail_mess.
+ */
+ for(; n <= stream->nmsgs; n++)
+ if(n > 0L && (mc = mail_elt(stream, n)) && mc->spare6){
+ mc->spare6 = 0;
+ new_mail_mess(stream, 1, n, 1);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Format and queue a "new mail" message
+
+ Args: stream -- mailstream on which a mail has arrived
+ number -- number of new messages since last command
+ max_num -- The number of messages now on stream
+ for_new_mail_win -- for separate new mail window (curr. PC-Pine)
+
+ Not too much worry here about the length of the message because the
+status_message code will fit what it can on the screen and truncation on
+the right is about what we want which is what will happen.
+ ----*/
+void
+new_mail_mess(MAILSTREAM *stream, long int number, long int max_num, int for_new_mail_win)
+{
+ char subject[MAILTMPLEN+1], subjtext[MAILTMPLEN+1], from[MAILTMPLEN+1],
+ *folder = NULL, intro[MAILTMPLEN+1];
+ ENVELOPE *e = NULL;
+
+ if(stream)
+ e = pine_mail_fetchstructure(stream, max_num, NULL);
+
+ if(stream){
+ if(sp_flagged(stream, SP_INBOX))
+ folder = NULL;
+ else{
+ folder = STREAMNAME(stream);
+ if(folder[0] == '?' && folder[1] == '\0')
+ folder = NULL;
+ }
+ }
+
+ format_new_mail_msg(folder, number, e, intro, from, subject, subjtext, sizeof(subject));
+
+ if(!for_new_mail_win)
+ q_status_message5(SM_ASYNC | SM_DING, 0, 60,
+ "%s%s%s%.80s%.80s", intro,
+ from && from[0] ? ((number > 1L) ? " Most recent f" : " F") : "",
+ from && from[0] ? "rom " : "",
+ from && from[0] ? from : "",
+ subjtext);
+#if (!defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)) || defined(_WINDOWS)
+ else {
+ int is_us = 0;
+ ADDRESS *tadr;
+
+ if(e)
+ for(tadr = e->to; tadr; tadr = tadr->next)
+ if(address_is_us(tadr, ps_global)){
+ is_us = 1;
+ break;
+ }
+#ifdef _WINDOWS
+ mswin_newmailwin(is_us, from, subject, folder);
+#else
+ newmailfifo(is_us, from, subject, folder);
+#endif
+ }
+#endif
+
+ if(pith_opt_icon_text){
+ if(F_ON(F_ENABLE_XTERM_NEWMAIL, ps_global)
+ && F_ON(F_ENABLE_NEWMAIL_SHORT_TEXT, ps_global)){
+ long inbox_nm;
+ if(!sp_flagged(stream, SP_INBOX)
+ && (inbox_nm = sp_mail_since_cmd(sp_inbox_stream()))){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "[%ld, %ld] %s",
+ inbox_nm > 1L ? inbox_nm : 1L,
+ number > 1L ? number: 1L,
+ ps_global->pine_name);
+ }
+ else
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "[%ld] %s", number > 1L ? number: 1L,
+ ps_global->pine_name);
+ }
+ else
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s%.80s", intro,
+ from && from[0] ? ((number > 1L) ? " Most recent f" : " F") : "",
+ from && from[0] ? "rom " : "",
+ from && from[0] ? from : "");
+
+ (*pith_opt_icon_text)(tmp_20k_buf, IT_NEWMAIL);
+ }
+}
+
+
+#if !defined(DOS) && !defined(OS2) && !defined(LEAVEOUTFIFO)
+static char *fifoname = NULL;
+static int fifoopenerrmsg = 0;
+static int fifofd = -1;
+static int fifoheader = 0;
+
+void
+init_newmailfifo(char *fname)
+{
+ if(fifoname)
+ close_newmailfifo();
+
+ if(!(fname && *fname))
+ return;
+
+ if(!fifoname){
+ if(mkfifo(fname, 0600) == -1){
+ q_status_message2(SM_ORDER,3,3,
+ "Can't create NewMail FIFO \"%s\": %s",
+ fname, error_description(errno));
+ return;
+ }
+ else
+ q_status_message1(SM_ORDER,0,3, "NewMail FIFO: \"%s\"", fname);
+
+ fifoname = cpystr(fname);
+ }
+}
+
+
+void
+close_newmailfifo(void)
+{
+ if(fifoname){
+ if(fifofd >= 0)
+ (void) close(fifofd);
+
+ if(*fifoname)
+ our_unlink(fifoname);
+
+ fs_give((void **) &fifoname);
+ }
+
+ fifoheader = 0;
+ fifoname = NULL;
+ fifofd = -1;
+ fifoopenerrmsg = 0;
+}
+
+
+void
+newmailfifo(int is_us, char *from, char *subject, char *folder)
+{
+ char buf[MAX_SCREEN_COLS+1], buf2[MAX_SCREEN_COLS+1];
+ char buf3[MAX_SCREEN_COLS+1], buf4[MAX_SCREEN_COLS+1];
+
+ if(!(fifoname && *fifoname)){
+ if(fifoname)
+ close_newmailfifo();
+
+ return;
+ }
+
+ if(fifofd < 0){
+ fifofd = our_open(fifoname, O_WRONLY | O_NONBLOCK | O_BINARY, 0600);
+ if(fifofd < 0){
+ if(!fifoopenerrmsg){
+ if(errno == ENXIO)
+ q_status_message2(SM_ORDER,0,3, "Nothing reading \"%s\": %s",
+ fifoname, error_description(errno));
+ else
+ q_status_message2(SM_ORDER,0,3, "Can't open \"%s\": %s",
+ fifoname, error_description(errno));
+
+ fifoopenerrmsg++;
+ }
+
+ return;
+ }
+ }
+
+ if(fifofd >= 0){
+ int width;
+ int fromlen, subjlen, foldlen;
+
+ width = MIN(MAX(20, ps_global->nmw_width), MAX_SCREEN_COLS);
+
+ foldlen = .18 * width;
+ foldlen = MAX(5, foldlen);
+ fromlen = .28 * width;
+ subjlen = width - 2 - foldlen - fromlen;
+
+ if(!fifoheader){
+ time_t now;
+ char *tmtxt;
+
+ now = time((time_t *) 0);
+ tmtxt = ctime(&now);
+ if(!tmtxt)
+ tmtxt = "";
+
+ snprintf(buf, sizeof(buf), "New Mail window started at %.*s\n",
+ MIN(100, strlen(tmtxt)-1), tmtxt);
+ (void) write(fifofd, buf, strlen(buf));
+
+ snprintf(buf, sizeof(buf), " %-*s%-*s%-*s\n",
+ fromlen, "From:",
+ subjlen, "Subject:",
+ foldlen, "Folder:");
+ (void) write(fifofd, buf, strlen(buf));
+
+ snprintf(buf, sizeof(buf), "%-*.*s\n", width, width, repeat_char(width, '-'));
+ (void) write(fifofd, buf, strlen(buf));
+
+ fifoheader++;
+ }
+
+ snprintf(buf, sizeof(buf), "%s %-*.*s %-*.*s %-*.*s\n", is_us ? "+" : " ",
+ fromlen - 1, fromlen - 1,
+ short_str(from ? from : "", buf2, sizeof(buf2), fromlen-1, EndDots),
+ subjlen - 1, subjlen - 1,
+ short_str(subject ? subject : "(no subject)",
+ buf3, sizeof(buf3), subjlen-1, EndDots),
+ foldlen, foldlen,
+ short_str(folder ? folder : "INBOX", buf4, sizeof(buf4), foldlen, FrontDots));
+ (void) write(fifofd, buf, strlen(buf));
+ }
+}
+#endif
diff --git a/alpine/newmail.h b/alpine/newmail.h
new file mode 100644
index 00000000..9526077f
--- /dev/null
+++ b/alpine/newmail.h
@@ -0,0 +1,22 @@
+/*
+ * $Id: newmail.h 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2009 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef ALPINE_NEWMAIL_INCLUDED
+#define ALPINE_NEWMAIL_INCLUDED
+
+/* exported protoypes */
+void newmail_status_message(MAILSTREAM *, long, long);
+
+#endif /* ALPINE_NEWMAIL_INCLUDED */
diff --git a/alpine/newuser.c b/alpine/newuser.c
new file mode 100644
index 00000000..381f21c6
--- /dev/null
+++ b/alpine/newuser.c
@@ -0,0 +1,204 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: newuser.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+#include "headers.h"
+#include "newuser.h"
+#include "mailview.h"
+#include "help.h"
+#include "keymenu.h"
+#include "osdep/print.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/msgno.h"
+#include "../pith/filter.h"
+#include "../pith/init.h"
+#include "../pith/margin.h"
+
+
+/*
+ * Internal prototypes
+ */
+int nuov_processor(int, MSGNO_S *, SCROLL_S *);
+
+
+/*
+ * Display a new user or new version message.
+ */
+void
+new_user_or_version(struct pine *ps)
+{
+ char **shown_text;
+ int cmd = MC_NONE;
+ int first_time_alpine_user = 0;
+ char *error = NULL;
+ HelpType text;
+ SCROLL_S sargs;
+ STORE_S *store;
+ HANDLE_S *handles = NULL, *htmp;
+ gf_io_t pc;
+ char *vers = ps->vers_internal;
+
+ first_time_alpine_user = (ps->first_time_user
+ || (ps->pine_pre_vers
+ && isdigit((unsigned char) ps->pine_pre_vers[0])
+ && ps->pine_pre_vers[1] == '.'
+ && isdigit((unsigned char) vers[0])
+ && vers[1] == '.'
+ && ps->pine_pre_vers[0] < '5' /* it was Pine */
+ && vers[0] >= '5')); /* Alpine */
+
+ text = ps->first_time_user ? new_user_greeting :
+ first_time_alpine_user ? new_alpine_user_greeting : new_version_greeting;
+
+ shown_text = text;
+
+ /*
+ * Set it if the major revision number
+ * (the first after the dot) has changed.
+ */
+ ps->phone_home = (first_time_alpine_user
+ || (ps->pine_pre_vers
+ && isdigit((unsigned char) ps->pine_pre_vers[0])
+ && ps->pine_pre_vers[1] == '.'
+ && isdigit((unsigned char) ps->pine_pre_vers[2])
+ && isdigit((unsigned char) vers[0])
+ && vers[1] == '.'
+ && isdigit((unsigned char) vers[2])
+ && strncmp(ps->pine_pre_vers, vers, 3) < 0));
+
+ /*
+ * At this point, shown_text is a charstarstar with html
+ * Turn it into a charstar with digested html
+ */
+ do{
+ init_helper_getc(shown_text);
+ init_handles(&handles);
+
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+ gf_set_so_writec(&pc, store);
+ gf_filter_init();
+
+ gf_link_filter(gf_html2plain,
+ gf_html2plain_opt("x-alpine-help:",
+ ps->ttyo->screen_cols, non_messageview_margin(),
+ &handles, NULL, GFHP_LOCAL_HANDLES));
+
+ error = gf_pipe(helper_getc, pc);
+
+ gf_clear_so_writec(store);
+
+ if(!error){
+ struct key_menu km;
+ struct key keys[24];
+
+ for(htmp = handles; htmp; htmp = htmp->next)
+ if(htmp->type == URL
+ && htmp->h.url.path
+ && (htmp->h.url.path[0] == 'x'
+ || htmp->h.url.path[0] == '#'))
+ htmp->force_display = 1;
+
+ /* This is mostly here to get the curses variables
+ * for line and column in sync with where the
+ * cursor is on the screen. This gets warped when
+ * the composer is called because it does it's own
+ * stuff
+ */
+ ClearScreen();
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = so_text(store);
+ sargs.text.src = CharStar;
+ sargs.text.desc = "greeting text";
+ sargs.text.handles = handles;
+ sargs.bar.title = "GREETING TEXT";
+ sargs.bar.style = TextPercent;
+ sargs.proc.tool = nuov_processor;
+ sargs.help.text = main_menu_tx;
+ sargs.help.title = "MAIN PINE HELP";
+ sargs.resize_exit = 1;
+ sargs.keys.menu = &km;
+ km = nuov_keymenu;
+ km.keys = keys;
+ memcpy(&keys[0], nuov_keymenu.keys,
+ (nuov_keymenu.how_many * 12) * sizeof(struct key));
+ setbitmap(sargs.keys.bitmap);
+
+ if(ps->phone_home){
+ km.keys[NUOV_EXIT].label = "Exit this greeting";
+ km.keys[NUOV_EXIT].bind.nch = 1;
+ }
+ else{
+ km.keys[NUOV_EXIT].label = "[Exit this greeting]";
+ km.keys[NUOV_EXIT].bind.nch = 3;
+ clrbitn(NUOV_VIEW, sargs.keys.bitmap);
+ }
+
+ if(ps->first_time_user)
+ clrbitn(NUOV_RELNOTES, sargs.keys.bitmap);
+
+ cmd = scrolltool(&sargs);
+
+ flush_input();
+
+ if(F_ON(F_BLANK_KEYMENU,ps_global))
+ FOOTER_ROWS(ps_global) = 1;
+
+ ClearScreen();
+ }
+
+ so_give(&store);
+ }
+
+ free_handles(&handles);
+ }
+ while(cmd == MC_RESIZE);
+}
+
+
+int
+nuov_processor(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 0;
+
+ switch(cmd){
+ /*----------- Print all the help ------------*/
+ case MC_PRINTMSG :
+ if(open_printer(sparms->text.desc) == 0){
+ print_help(sparms->proc.data.p);
+ close_printer();
+ }
+
+ break;
+
+ case MC_RELNOTES :
+ helper(h_news, "ALPINE RELEASE NOTES", 0);
+ ps_global->mangled_screen = 1;
+ break;
+
+ case MC_EXIT :
+ rv = 1;
+ break;
+
+ default :
+ panic("Unhandled case");
+ }
+
+ return(rv);
+}
diff --git a/alpine/newuser.h b/alpine/newuser.h
new file mode 100644
index 00000000..6cbb2c5d
--- /dev/null
+++ b/alpine/newuser.h
@@ -0,0 +1,28 @@
+/*
+ * $Id: newuser.h 768 2007-10-24 00:10:03Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_NEWUSER_INCLUDED
+#define PINE_NEWUSER_INCLUDED
+
+
+#include "../pith/state.h"
+
+
+/* exported protoypes */
+void new_user_or_version(struct pine *);
+
+
+#endif /* PINE_NEWUSER_INCLUDED */
diff --git a/alpine/osdep/Makefile.am b/alpine/osdep/Makefile.am
new file mode 100644
index 00000000..7c1547d4
--- /dev/null
+++ b/alpine/osdep/Makefile.am
@@ -0,0 +1,22 @@
+## Process this file with automake to produce Makefile.in
+## Use aclocal -I m4; automake
+
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+noinst_LIBRARIES = libpineosd.a
+
+libpineosd_a_SOURCES = chnge_pw.c debuging.c diskquot.non.c execview.c fltrname.c \
+ jobcntrl.c print.c termin.gen.c termin.unx.c termout.gen.c termout.unx.c \
+ debuging.h execview.h fltrname.h jobcntrl.h print.h resource.h termin.gen.h \
+ termin.unx.h termout.gen.h termout.unx.h windlg.h
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include
diff --git a/alpine/osdep/Makefile.in b/alpine/osdep/Makefile.in
new file mode 100644
index 00000000..4d452c9f
--- /dev/null
+++ b/alpine/osdep/Makefile.in
@@ -0,0 +1,544 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 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@
+
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+VPATH = @srcdir@
+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@
+subdir = alpine/osdep
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/acx_pthread.m4 \
+ $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/iconv.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.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/nls.m4 $(top_srcdir)/m4/po.m4 \
+ $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/VERSION \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/include/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+libpineosd_a_AR = $(AR) $(ARFLAGS)
+libpineosd_a_LIBADD =
+am_libpineosd_a_OBJECTS = chnge_pw.$(OBJEXT) debuging.$(OBJEXT) \
+ diskquot.non.$(OBJEXT) execview.$(OBJEXT) fltrname.$(OBJEXT) \
+ jobcntrl.$(OBJEXT) print.$(OBJEXT) termin.gen.$(OBJEXT) \
+ termin.unx.$(OBJEXT) termout.gen.$(OBJEXT) \
+ termout.unx.$(OBJEXT)
+libpineosd_a_OBJECTS = $(am_libpineosd_a_OBJECTS)
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(libpineosd_a_SOURCES)
+DIST_SOURCES = $(libpineosd_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_CFLAGS = @AM_CFLAGS@
+AM_LDFLAGS = @AM_LDFLAGS@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CP = @CP@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+C_CLIENT_CFLAGS = @C_CLIENT_CFLAGS@
+C_CLIENT_GCCOPTLEVEL = @C_CLIENT_GCCOPTLEVEL@
+C_CLIENT_LDFLAGS = @C_CLIENT_LDFLAGS@
+C_CLIENT_SPECIALS = @C_CLIENT_SPECIALS@
+C_CLIENT_TARGET = @C_CLIENT_TARGET@
+C_CLIENT_WITH_IPV6 = @C_CLIENT_WITH_IPV6@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISPELLPROG = @ISPELLPROG@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN = @LN@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKE = @MAKE@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NPA_PROG = @NPA_PROG@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+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@
+POSUB = @POSUB@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+PWPROG = @PWPROG@
+RANLIB = @RANLIB@
+REGEX_BUILD = @REGEX_BUILD@
+RM = @RM@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPELLPROG = @SPELLPROG@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEB_BINDIR = @WEB_BINDIR@
+WEB_BUILD = @WEB_BUILD@
+WEB_PUBCOOKIE_BUILD = @WEB_PUBCOOKIE_BUILD@
+WEB_PUBCOOKIE_LIB = @WEB_PUBCOOKIE_LIB@
+WEB_PUBCOOKIE_LINK = @WEB_PUBCOOKIE_LINK@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+acx_pthread_config = @acx_pthread_config@
+alpine_interactive_spellcheck = @alpine_interactive_spellcheck@
+alpine_simple_spellcheck = @alpine_simple_spellcheck@
+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@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_ECHO = @lt_ECHO@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LIBRARIES = libpineosd.a
+libpineosd_a_SOURCES = chnge_pw.c debuging.c diskquot.non.c execview.c fltrname.c \
+ jobcntrl.c print.c termin.gen.c termin.unx.c termout.gen.c termout.unx.c \
+ debuging.h execview.h fltrname.h jobcntrl.h print.h resource.h termin.gen.h \
+ termin.unx.h termout.gen.h termout.unx.h windlg.h
+
+AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include
+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 alpine/osdep/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign alpine/osdep/Makefile
+.PRECIOUS: 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__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ 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):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libpineosd.a: $(libpineosd_a_OBJECTS) $(libpineosd_a_DEPENDENCIES)
+ -rm -f libpineosd.a
+ $(libpineosd_a_AR) libpineosd.a $(libpineosd_a_OBJECTS) $(libpineosd_a_LIBADD)
+ $(RANLIB) libpineosd.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chnge_pw.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debuging.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/diskquot.non.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/execview.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fltrname.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jobcntrl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/termin.gen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/termin.unx.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/termout.gen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/termout.unx.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ 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
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ 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"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(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 $(LIBRARIES)
+installdirs:
+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:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+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-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -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-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+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 -rf ./$(DEPDIR)
+ -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:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+ clean-libtool clean-noinstLIBRARIES ctags distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am 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-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 uninstall uninstall-am
+
+
+# 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/alpine/osdep/ReadMe b/alpine/osdep/ReadMe
new file mode 100644
index 00000000..787b9b71
--- /dev/null
+++ b/alpine/osdep/ReadMe
@@ -0,0 +1,14 @@
+The os-xxx.h files are created totally by hand. Start with os-gen.h,
+the generic version of the os.h file.
+
+The os-xxx.c files may be created by hand or you may use the simple include
+method that is used here. The os.c files are made by creating an
+os-xxx.ic file which lists all of the files it includes, and then
+running make os-xxx.c. There is an os-gen.ic generic version of the
+.ic file.
+
+You will also need to create a makefile.xxx in the parent directory. There
+are other makefiles to inspect there, including a generic makefile.gen.
+
+The names of all the included files here have to be eight characters or less
+for DOS, hence the stupid names.
diff --git a/alpine/osdep/alpine-splash.bmp b/alpine/osdep/alpine-splash.bmp
new file mode 100644
index 00000000..0bd797be
--- /dev/null
+++ b/alpine/osdep/alpine-splash.bmp
Binary files differ
diff --git a/alpine/osdep/alpine.ico b/alpine/osdep/alpine.ico
new file mode 100644
index 00000000..d6b7af76
--- /dev/null
+++ b/alpine/osdep/alpine.ico
Binary files differ
diff --git a/alpine/osdep/chnge_pw.c b/alpine/osdep/chnge_pw.c
new file mode 100644
index 00000000..e16e5983
--- /dev/null
+++ b/alpine/osdep/chnge_pw.c
@@ -0,0 +1,65 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: chnge_pw.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h> /* os-dep defs/includes */
+#include <general.h> /* generally useful definitions */
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/osdep/color.h"
+#include "../../pith/state.h"
+
+#include "termin.gen.h"
+#include "termout.gen.h"
+#include "chnge_pw.h"
+
+/*----------------------------------------------------------------------
+ Call the system to change the passwd
+
+It would be nice to talk to the passwd program via a pipe or ptty so the
+user interface could be consistent, but we can't count on the the prompts
+and responses from the passwd program to be regular so we just let the user
+type at the passwd program with some screen space, hope he doesn't scroll
+off the top and repaint when he's done.
+ ----*/
+void
+change_passwd(void)
+{
+#ifdef PASSWD_PROG
+ char cmd_buf[100];
+
+ ClearLines(1, ps_global->ttyo->screen_rows - 1);
+
+ MoveCursor(5, 0);
+ fflush(stdout);
+
+ PineRaw(0);
+ strncpy(cmd_buf, PASSWD_PROG, sizeof(cmd_buf));
+ cmd_buf[sizeof(cmd_buf)-1] = '\0';
+ system(cmd_buf);
+ sleep(3);
+ PineRaw(1);
+#endif /* PASSWD_PROG */
+}
+
+
diff --git a/alpine/osdep/chnge_pw.h b/alpine/osdep/chnge_pw.h
new file mode 100644
index 00000000..ce3463bc
--- /dev/null
+++ b/alpine/osdep/chnge_pw.h
@@ -0,0 +1,25 @@
+/*
+ * $Id: chnge_pw.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_CHNGE_PW_INCLUDED
+#define PINE_OSDEP_CHNGE_PW_INCLUDED
+
+
+/* exported prototypes */
+void change_passwd(void);
+
+
+#endif /* PINE_OSDEP_CHNGE_PW_INCLUDED */
diff --git a/alpine/osdep/debuging.c b/alpine/osdep/debuging.c
new file mode 100644
index 00000000..0310f5b5
--- /dev/null
+++ b/alpine/osdep/debuging.c
@@ -0,0 +1,604 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: debuging.c 900 2008-01-05 01:13:26Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#include <system.h> /* os-dep defs/includes */
+#include <general.h> /* generally useful definitions */
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/osdep/canaccess.h"
+
+#include "../../pith/debug.h"
+#include "../../pith/osdep/debugtime.h"
+#include "../../pith/osdep/color.h"
+#include "../../pith/osdep/bldpath.h"
+#include "../../pith/osdep/rename.h"
+#include "../../pith/osdep/filesize.h"
+#include "../../pith/init.h"
+#include "../../pith/status.h"
+#include "../../pith/sort.h"
+#include "../../pith/state.h"
+#include "../../pith/msgno.h"
+#include "../../pith/conf.h"
+#include "../../pith/flag.h"
+#include "../../pith/foldertype.h"
+#include "../../pith/folder.h"
+#include "../../pith/charconv/utf8.h"
+#include "../../pith/charconv/filesys.h"
+#include "../help.h"
+
+#include "debuging.h"
+
+
+#ifdef DEBUG
+
+/*
+ * globally visible
+ */
+FILE *debugfile = NULL;
+
+
+/*----------------------------------------------------------------------
+ Initialize debugging - open the debug log file
+
+ Args: none
+
+ Result: opens the debug logfile for dprints
+
+ Opens the file "~/.pine-debug1. Also maintains .pine-debug[2-4]
+ by renaming them each time so the last 4 sessions are saved.
+ ----*/
+void
+init_debug(void)
+{
+ char nbuf[5];
+ char newfname[MAXPATH+1], filename[MAXPATH+1], *dfile = NULL;
+ int i, fd;
+
+ if(!(debug || ps_global->debug_imap || ps_global->debug_tcp))
+ return;
+
+ for(i = ps_global->debug_nfiles - 1; i > 0; i--){
+ build_path(filename, ps_global->home_dir, DEBUGFILE, sizeof(filename));
+ strncpy(newfname, filename, sizeof(newfname)-1);
+ newfname[sizeof(newfname)-1] = '\0';
+ snprintf(nbuf, sizeof(nbuf), "%d", i);
+ strncat(filename, nbuf, sizeof(filename)-1-strlen(filename));
+ snprintf(nbuf, sizeof(nbuf), "%d", i+1);
+ strncat(newfname, nbuf, sizeof(newfname)-1-strlen(newfname));
+ (void)rename_file(filename, newfname);
+ }
+
+ build_path(filename, ps_global->home_dir, DEBUGFILE, sizeof(filename)-1);
+ strncat(filename, "1", sizeof(filename)-1-strlen(filename));
+ filename[sizeof(filename)-1] = '\0';
+
+ debugfile = NULL;
+ dfile = filename;
+ /*
+ * We were doing our_open so _WINDOWS would open it in widechar mode, but
+ * that means we have to print in widechar mode. For now just do open, since
+ * we it's a debug file and still readable. The alternative is copying all
+ * debug text to a widechar buffer, which would be a drag.
+ */
+ if((fd = open(filename, O_TRUNC|O_RDWR|O_CREAT, 0600)) >= 0)
+ debugfile = fdopen(fd, "w+");
+
+ if(debugfile != NULL){
+ char rev[128];
+ time_t now = time((time_t *)0);
+ if(ps_global->debug_flush)
+ setvbuf(debugfile, (char *)NULL, _IOLBF, BUFSIZ);
+
+ if(ps_global->debug_nfiles == 0){
+ /*
+ * If no debug files are asked for, make filename a tempfile
+ * to be used for a record should pine later crash...
+ */
+ if(debug < 9 && !ps_global->debug_flush && ps_global->debug_imap<4){
+ our_unlink(filename);
+ dfile = NULL;
+ }
+ }
+
+ dprint((0, "Debug output of the Alpine program (debug=%d debug_imap=%d). Version %s (%s %s)\n%s\n",
+ debug, ps_global->debug_imap,
+ ALPINE_VERSION,
+ SYSTYPE ? SYSTYPE : "?",
+ get_alpine_revision_string(rev, sizeof(rev)),
+ ctime(&now)));
+
+ dprint((0, "Starting after the reading_pinerc calls, the data in this file should\nbe encoded as UTF-8. Before that it will be in the user's native charset.\n"));
+ if(dfile && (debug > DEFAULT_DEBUG ||
+ ps_global->debug_imap > 0 ||
+ ps_global->debug_tcp > 0)){
+ snprintf(newfname, sizeof(newfname), "Debug file: %s (level=%d imap=%d)", dfile,
+ debug, ps_global->debug_imap);
+ init_error(ps_global, SM_ORDER, 3, 5, newfname);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Try to save the debug file if we crash in a controlled way
+
+ Args: dfile: pointer to open debug file
+
+ Result: tries to move the appropriate .pine-debugx file to .pine-crash
+
+ Looks through the four .pine-debug files hunting for the one that is
+ associated with this pine, and then renames it.
+ ----*/
+void
+save_debug_on_crash(FILE *dfile, int (*keystrokes) (int *, char *, size_t))
+{
+ char nbuf[5], crashfile[MAXPATH+1], filename[MAXPATH+1];
+ int i;
+ struct stat dbuf, tbuf;
+ time_t now = time((time_t *)0);
+
+ if(!(dfile && fstat(fileno(dfile), &dbuf) == 0))
+ return;
+
+ fprintf(dfile, "save_debug_on_crash: Version %s: debug level %d",
+ ALPINE_VERSION, debug);
+ fprintf(dfile, "\n : %s\n", ctime(&now));
+
+ build_path(crashfile, ps_global->home_dir, ".pine-crash",sizeof(crashfile));
+
+ fprintf(dfile, "Attempting to save debug file to %s\n\n", crashfile);
+ fprintf(stderr,
+ "\n\n Attempting to save debug file to %s\n\n", crashfile);
+
+ /* Blat out last n keystrokes */
+ if(keystrokes){
+ int cval;
+ char cstr[256];
+
+ fputs("========== Latest Keystrokes =========================\n", dfile);
+
+ while((*keystrokes)(&cval, cstr, sizeof(cstr)) != -1){
+ fprintf(dfile, "\t%s\t(0x%4.4x)\n", cstr, cval);
+ }
+ }
+
+ fputs("========== Latest Keystrokes End =====================\n\n", dfile);
+
+#ifdef DEBUGJOURNAL
+ fputs("========== Append DebugJournal =======================\n", dfile);
+#else /* DEBUGJOURNAL */
+ fputs("========== Append Journal =======================\n", dfile);
+#endif /* DEBUGJOURNAL */
+ debugjournal_to_file(dfile);
+#ifdef DEBUGJOURNAL
+ fputs("========== Append DebugJournal End ===================\n", dfile);
+#else /* DEBUGJOURNAL */
+ fputs("========== Append Journal End ===================\n", dfile);
+#endif /* DEBUGJOURNAL */
+
+ /* look for existing debug file */
+ for(i = 1; i <= ps_global->debug_nfiles; i++){
+ build_path(filename, ps_global->home_dir, DEBUGFILE, sizeof(filename));
+ snprintf(nbuf, sizeof(nbuf), "%d", i);
+ strncat(filename, nbuf, sizeof(filename)-1-strlen(filename));
+ if(our_stat(filename, &tbuf) != 0)
+ continue;
+
+ /* This must be the current debug file */
+ if(tbuf.st_dev == dbuf.st_dev && tbuf.st_ino == dbuf.st_ino){
+ rename_file(filename, crashfile);
+ break;
+ }
+ }
+
+ /* if current debug file name not found, write it by hand */
+ if(i > ps_global->debug_nfiles){
+ FILE *cfp;
+ char buf[1025];
+
+ /*
+ * Copy the debug temp file into the
+ */
+ if((cfp = our_fopen(crashfile, "wb")) != NULL){
+ buf[sizeof(buf)-1] = '\0';
+ fseek(dfile, 0L, 0);
+ while(fgets(buf, sizeof(buf)-1, dfile) && fputs(buf, cfp) != EOF)
+ ;
+
+ fclose(cfp);
+ }
+ }
+
+ fclose(dfile);
+}
+
+
+/*
+ * functions required by pith library
+ */
+#define CHECK_EVERY_N_TIMES 100
+#define MAX_DEBUG_FILE_SIZE 200000L
+/*
+ * This is just to catch runaway Pines that are looping spewing out
+ * debugging (and filling up a file system). The stop doesn't have to be
+ * at all precise, just soon enough to hopefully prevent filling the
+ * file system. If the debugging level is high (9 for now), then we're
+ * presumably looking for some problem, so don't truncate.
+ */
+int
+do_debug(FILE *debug_fp)
+{
+ static int counter = CHECK_EVERY_N_TIMES;
+ static int ok = 1;
+ long filesize;
+
+ if(!debugfile)
+ return(0);
+
+ if(debug <= DEFAULT_DEBUG
+ && !ps_global->debug_flush
+ && !ps_global->debug_tcp
+ && !ps_global->debug_timestamp
+ && !ps_global->debug_imap
+ && ok
+ && --counter <= 0){
+ if((filesize = fp_file_size(debug_fp)) != -1L)
+ ok = (unsigned long)filesize < (unsigned long)MAX_DEBUG_FILE_SIZE;
+
+ counter = CHECK_EVERY_N_TIMES;
+ if(!ok){
+ fprintf(debug_fp, "\n\n --- No more debugging ---\n");
+ fprintf(debug_fp,
+ " (debug file growing too large - over %ld bytes)\n\n",
+ MAX_DEBUG_FILE_SIZE);
+ fflush(debug_fp);
+ }
+ }
+
+ if(ok && ps_global->debug_timestamp)
+ fprintf(debug_fp, "\n%s\n", debug_time(0, ps_global->debug_timestamp));
+
+ return(ok);
+}
+
+void
+output_debug_msg(int dlevel, char *fmt, ...)
+{
+ va_list args;
+
+ if(debugfile && debug >= dlevel && do_debug(debugfile)){
+ int l;
+
+ va_start(args, fmt);
+ vfprintf(debugfile, fmt, args);
+ va_end(args);
+
+ if((l = strlen(fmt)) > 2 && fmt[l-1] != '\n')
+ fputc('\n', debugfile);
+
+ if(ps_global->debug_flush)
+ fflush(debugfile);
+ }
+
+ if(panicking())
+ return;
+
+#ifdef DEBUGJOURNAL
+
+ if(dlevel <= 9 || dlevel <= debug){
+ /*
+ * Make this static in order to move it off of
+ * the stack. We were getting "random" crashes
+ * on some systems when this size interacted with
+ * pthread stack size somehow. Taking it off of
+ * the stack ought to fix that without us having to
+ * understand how it all works.
+ */
+ static char b[64000];
+
+ va_start(args, fmt);
+ vsnprintf(b, sizeof(b), fmt, args);
+ va_end(args);
+
+ b[sizeof(b)-1] = '\0';
+ add_review_message(b, dlevel);
+ }
+
+#endif /* DEBUGJOURNAL */
+
+}
+
+
+/*----------------------------------------------------------------------
+ Dump the whole config enchilada using the given function
+
+ Args: ps -- pine struct containing vars to dump
+ pc -- function to use to write the config data
+ brief -- brief dump, only dump user_vals
+ Result: command line, global, user var's written with given function
+
+ ----*/
+void
+dump_configuration(int brief)
+{
+ gf_io_t pc;
+
+ if(!do_debug(debugfile))
+ return;
+
+ gf_set_writec(&pc, debugfile, 0L, FileStar, 0);
+
+ dump_config(ps_global, pc, brief);
+}
+
+
+void
+dump_config(struct pine *ps, gf_io_t pc, int brief)
+{
+ int i;
+ char quotes[3], tmp[MAILTMPLEN];
+ register struct variable *vars;
+ FEATURE_S *feat;
+
+ quotes[0] = '"'; quotes[1] = '"'; quotes[2] = '\0';
+
+ for(i = (brief ? 2 : 0); i < (brief ? 4 : 6); i++){
+ snprintf(tmp, sizeof(tmp), "======= %.20s_val options set",
+ (i == 0) ? "Current" :
+ (i == 1) ? "Command_line" :
+ (i == 2) ? "User" :
+ (i == 3) ? "PostloadUser" :
+ (i == 4) ? "Global"
+ : "Fixed");
+ gf_puts(tmp, pc);
+ if(i > 1){
+ snprintf(tmp, sizeof(tmp), " (%.100s)",
+ (i == 2) ? ((ps->prc) ? ps->prc->name : ".pinerc") :
+ (i == 3) ? ((ps->post_prc) ? ps->post_prc->name : "postload") :
+ (i == 4) ? ((ps->pconf) ? ps->pconf->name
+ : SYSTEM_PINERC) :
+#if defined(DOS) || defined(OS2)
+ "NO FIXED"
+#else
+ ((can_access(SYSTEM_PINERC_FIXED, ACCESS_EXISTS) == 0)
+ ? SYSTEM_PINERC_FIXED : "NO pine.conf.fixed")
+#endif
+ );
+ gf_puts(tmp, pc);
+ }
+
+ gf_puts(" =======\n", pc);
+ for(vars = ps->vars; vars->name; vars++){
+ if(vars->is_list){
+ char **t;
+ t = (i == 0) ? vars->current_val.l :
+ (i == 1) ? vars->cmdline_val.l :
+ (i == 2) ? vars->main_user_val.l :
+ (i == 3) ? vars->post_user_val.l :
+ (i == 4) ? vars->global_val.l
+ : vars->fixed_val.l;
+ if(t && *t){
+ snprintf(tmp, sizeof(tmp), " %20.20s : %.*s\n", vars->name,
+ sizeof(tmp)-26, **t ? *t : quotes);
+ gf_puts(tmp, pc);
+ while(++t && *t){
+ snprintf(tmp, sizeof(tmp)," %20.20s : %.*s\n","",
+ sizeof(tmp)-26, **t ? *t : quotes);
+ gf_puts(tmp, pc);
+ }
+ }
+ }
+ else{
+ char *t;
+ t = (i == 0) ? vars->current_val.p :
+ (i == 1) ? vars->cmdline_val.p :
+ (i == 2) ? vars->main_user_val.p :
+ (i == 3) ? vars->post_user_val.p :
+ (i == 4) ? vars->global_val.p
+ : vars->fixed_val.p;
+ if(t){
+ snprintf(tmp, sizeof(tmp), " %20.20s : %.*s\n", vars->name,
+ sizeof(tmp)-26, *t ? t : quotes);
+ gf_puts(tmp, pc);
+ }
+ }
+ }
+ }
+
+ if(!brief){
+ gf_puts("========== Feature settings ==========\n", pc);
+ for(i = 0; (feat = feature_list(i)); i++)
+ if(feat->id != F_OLD_GROWTH){
+ snprintf(tmp, sizeof(tmp),
+ " %.50s%.100s\n", F_ON(feat->id, ps) ? " " : "no-",
+ feat->name);
+ gf_puts(tmp, pc);
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Dump interesting variables from the given pine struct
+
+ Args: ps -- pine struct to dump
+ pc -- function to use to write the config data
+
+ Result: various important pine struct data written
+
+ ----*/
+void
+dump_pine_struct(struct pine *ps, gf_io_t pc)
+{
+ char *p;
+ extern char term_name[];
+ int i;
+ MAILSTREAM *m;
+ MSGNO_S *msgmap;
+
+ gf_puts("========== struct pine * ==========\n", pc);
+ if(!ps){
+ gf_puts("!No struct!\n", pc);
+ return;
+ }
+
+ gf_puts("ui:\tlogin = ", pc);
+ gf_puts((ps->ui.login) ? ps->ui.login : "NULL", pc);
+ gf_puts(", full = ", pc);
+ gf_puts((ps->ui.fullname) ? ps->ui.fullname : "NULL", pc);
+ gf_puts("\n\thome = ", pc);
+ gf_puts((ps->ui.homedir) ? ps->ui.homedir : "NULL", pc);
+
+ gf_puts("\nhome_dir=\t", pc);
+ gf_puts(ps->home_dir ? ps->home_dir : "NULL", pc);
+ gf_puts("\nhostname=\t", pc);
+ gf_puts(ps->hostname ? ps->hostname : "NULL", pc);
+ gf_puts("\nlocaldom=\t", pc);
+ gf_puts(ps->localdomain ? ps->localdomain : "NULL", pc);
+ gf_puts("\nuserdom=\t", pc);
+ gf_puts(ps->userdomain ? ps->userdomain : "NULL", pc);
+ gf_puts("\nmaildom=\t", pc);
+ gf_puts(ps->maildomain ? ps->maildomain : "NULL", pc);
+
+ gf_puts("\ncur_cntxt=\t", pc);
+ gf_puts((ps->context_current && ps->context_current->context)
+ ? ps->context_current->context : "None", pc);
+ gf_puts("\ncur_fldr=\t", pc);
+ gf_puts(ps->cur_folder, pc);
+
+ gf_puts("\nnstream=\t", pc);
+ gf_puts(long2string((long) ps->s_pool.nstream), pc);
+
+ for(i = 0; i < ps->s_pool.nstream; i++){
+ m = ps->s_pool.streams[i];
+ gf_puts("\nstream slot ", pc);
+ gf_puts(long2string((long) i), pc);
+ if(!m){
+ gf_puts(" empty", pc);
+ continue;
+ }
+
+ if(ps->mail_stream == m)
+ gf_puts("\nThis is the current mailstream", pc);
+ if(sp_flagged(m, SP_INBOX))
+ gf_puts("\nThis is the inbox stream", pc);
+
+ gf_puts("\nactual mbox=\t", pc);
+ gf_puts(m->mailbox ? m->mailbox : "no mailbox!", pc);
+
+ gf_puts("\nflags=", pc);
+ gf_puts(long2string((long) sp_flags(m)), pc);
+ gf_puts("\nnew_mail_count=", pc);
+ gf_puts(long2string((long) sp_new_mail_count(m)), pc);
+ gf_puts("\nmail_since_cmd=", pc);
+ gf_puts(long2string((long) sp_mail_since_cmd(m)), pc);
+ gf_puts("\nmail_box_changed=", pc);
+ gf_puts(long2string((long) sp_mail_box_changed(m)), pc);
+ gf_puts("\nunsorted_newmail=", pc);
+ gf_puts(long2string((long) sp_unsorted_newmail(m)), pc);
+ gf_puts("\nneed_to_rethread=", pc);
+ gf_puts(long2string((long) sp_need_to_rethread(m)), pc);
+ gf_puts("\nviewing_a_thread=", pc);
+ gf_puts(long2string((long) sp_viewing_a_thread(m)), pc);
+ gf_puts("\ndead_stream=", pc);
+ gf_puts(long2string((long) sp_dead_stream(m)), pc);
+ gf_puts("\nnoticed_dead_stream=", pc);
+ gf_puts(long2string((long) sp_noticed_dead_stream(m)), pc);
+ gf_puts("\nio_error_on_stream=", pc);
+ gf_puts(long2string((long) sp_io_error_on_stream(m)), pc);
+
+ msgmap = sp_msgmap(m);
+ if(msgmap){
+ gf_puts("\nmsgmap: tot=", pc);
+ gf_puts(long2string(mn_get_total(msgmap)), pc);
+ gf_puts(", cur=", pc);
+ gf_puts(long2string(mn_get_cur(msgmap)), pc);
+ gf_puts(", del=", pc);
+ gf_puts(long2string(count_flagged(m, F_DEL)),pc);
+ gf_puts(", hid=", pc);
+ gf_puts(long2string(any_lflagged(msgmap, MN_HIDE)), pc);
+ gf_puts(", exld=", pc);
+ gf_puts(long2string(any_lflagged(msgmap, MN_EXLD)), pc);
+ gf_puts(", slct=", pc);
+ gf_puts(long2string(any_lflagged(msgmap, MN_SLCT)), pc);
+ gf_puts(", chid=", pc);
+ gf_puts(long2string(any_lflagged(msgmap, MN_CHID)), pc);
+ gf_puts(", coll=", pc);
+ gf_puts(long2string(any_lflagged(msgmap, MN_COLL)), pc);
+ gf_puts(", usor=", pc);
+ gf_puts(long2string(any_lflagged(msgmap, MN_USOR)), pc);
+ gf_puts(", stmp=", pc);
+ gf_puts(long2string(any_lflagged(msgmap, MN_STMP)), pc);
+ gf_puts(", sort=", pc);
+ if(mn_get_revsort(msgmap))
+ gf_puts("rev-", pc);
+
+ gf_puts(sort_name(mn_get_sort(msgmap)), pc);
+ }
+ else
+ gf_puts("\nNo msgmap", pc);
+ }
+
+
+ gf_puts("\nterm ", pc);
+#if !defined(DOS) && !defined(OS2)
+ gf_puts("type=", pc);
+ gf_puts(term_name, pc);
+ gf_puts(", ttyname=", pc);
+ gf_puts((p = (char *)ttyname(0)) ? p : "NONE", pc);
+#endif
+ gf_puts(", size=", pc);
+ gf_puts(int2string(ps->ttyo->screen_rows), pc);
+ gf_puts("x", pc);
+ gf_puts(int2string(ps->ttyo->screen_cols), pc);
+ gf_puts(", speed=", pc);
+ gf_puts((ps->low_speed) ? "slow" : "normal", pc);
+ gf_puts("\n", pc);
+}
+
+
+void
+dump_contexts(void)
+{
+ int i = 0;
+ CONTEXT_S *c = ps_global->context_list;
+
+ if(!(debugfile && debug > 7 && do_debug(debugfile)))
+ return;
+
+ while(debugfile && c != NULL){
+ fprintf(debugfile, "\n***** context %s\n", c->context);
+ if(c->label)
+ fprintf(debugfile,"LABEL: %s\n", c->label);
+
+ if(c->comment)
+ fprintf(debugfile,"COMMENT: %s\n", c->comment);
+
+ for(i = 0; i < folder_total(FOLDERS(c)); i++)
+ fprintf(debugfile, " %d) %s\n", i + 1, folder_entry(i,FOLDERS(c))->name);
+
+ c = c->next;
+ }
+}
+
+#endif /* DEBUG */
diff --git a/alpine/osdep/debuging.h b/alpine/osdep/debuging.h
new file mode 100644
index 00000000..aee9d721
--- /dev/null
+++ b/alpine/osdep/debuging.h
@@ -0,0 +1,30 @@
+/*
+ * $Id: debuging.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_DEBUGING_INCLUDED
+#define PINE_OSDEP_DEBUGING_INCLUDED
+
+/* stream to stuff debuging output */
+extern FILE *debugfile;
+
+/*
+ * Exported Prototypes
+ */
+#ifdef DEBUG
+void init_debug(void);
+void save_debug_on_crash(FILE *, int (*)(int *, char *, size_t));
+#endif
+
+#endif /* PINE_OSDEP_DEBUGING_INCLUDED */
diff --git a/alpine/osdep/diskquot b/alpine/osdep/diskquot
new file mode 100644
index 00000000..4ff6b261
--- /dev/null
+++ b/alpine/osdep/diskquot
@@ -0,0 +1,78 @@
+#if defined(USE_QUOTAS)
+
+#include <sys/param.h>
+#include <sys/quota.h>
+
+/*----------------------------------------------------------------------
+ Return space left in disk quota on file system which given path is in.
+
+ Args: path - Path name of file or directory on file system of concern
+ over - pointer to flag that is set if the user is over quota
+
+ Returns: If *over = 0, the number of bytes free in disk quota as per
+ the soft limit.
+ If *over = 1, the number of bytes *over* quota.
+ -1 is returned on an error looking up quota
+ 0 is returned if there is no quota
+
+BUG: If there's more than 2.1Gb free this function will break
+ ----*/
+long
+disk_quota(path, over)
+ char *path;
+ int *over;
+{
+ static int no_quota = 0;
+ struct stat statx;
+ struct dqblk quotax;
+ long q;
+
+ if(no_quota)
+ return(0L); /* If no quota the first time, then none the second,
+ Also Ultrix seems to give the wrong answer the second
+ time */
+
+ dprint(5, (debugfile, "quota_check path: %s\n", path ? path : "?"));
+ if(stat(path, &statx) < 0) {
+ return(-1L);
+ }
+
+ *over = 0;
+ errno = 0;
+
+ dprint(7, (debugfile, "Quota check: UID:%d stat: %d %x\n",
+ getuid(), statx.st_dev, statx.st_dev));
+ memset((void *)&quotax, 0, sizeof(struct dqblk));
+ if(quota(Q_GETDLIM, getuid(), statx.st_dev, &quotax) < 0) {
+ dprint(5, (debugfile, "Quota failed : %s\n",
+ error_description(errno)));
+ if(errno == ESRCH){
+ no_quota = 1;
+ return(0L); /* No limit */
+ } else {
+ return(-1L); /* Some thing went wrong */
+ }
+ }
+
+ dprint(5,(debugfile,"Quota: bsoftlimit:%d bhardlimit:%d curblock:%d\n",
+ quotax.dqb_bsoftlimit, quotax.dqb_bhardlimit, quotax.dqb_curblocks));
+
+ /* Some confusion on the type of bsoftlimit. The include file says
+ unsigned, but -1 seems to indicate no quota */
+ if(quotax.dqb_bsoftlimit == 0 || (long)quotax.dqb_bsoftlimit == -1) {
+ no_quota = 1;
+ return(0L);
+ }
+
+ q = (quotax.dqb_bsoftlimit - quotax.dqb_curblocks) * 1024;
+
+ if(q < 0) {
+ q = -q;
+ *over = 1;
+ }
+ dprint(5, (debugfile, "disk_quota returning :%d, over:%d\n", q, *over));
+ return(q);
+}
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/diskquot.a32 b/alpine/osdep/diskquot.a32
new file mode 100644
index 00000000..c4695764
--- /dev/null
+++ b/alpine/osdep/diskquot.a32
@@ -0,0 +1,8 @@
+#if defined(USE_QUOTAS)
+
+#include <jfs/quota.h>
+
+include(sunquota)
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/diskquot.a41 b/alpine/osdep/diskquot.a41
new file mode 100644
index 00000000..564c4201
--- /dev/null
+++ b/alpine/osdep/diskquot.a41
@@ -0,0 +1,6 @@
+#if defined(USE_QUOTAS)
+#include <jfs/quota.h>
+
+include(sunquota)
+#endif /* USE_QUOTAS */
+
diff --git a/alpine/osdep/diskquot.hpp b/alpine/osdep/diskquot.hpp
new file mode 100644
index 00000000..1b328cd6
--- /dev/null
+++ b/alpine/osdep/diskquot.hpp
@@ -0,0 +1,9 @@
+#if defined(USE_QUOTAS)
+
+#include <sys/param.h>
+#include <sys/quota.h>
+
+include(sunquota)
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/diskquot.non.c b/alpine/osdep/diskquot.non.c
new file mode 100644
index 00000000..17465f63
--- /dev/null
+++ b/alpine/osdep/diskquot.non.c
@@ -0,0 +1,46 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: diskquot.non.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#ifdef USE_QUOTAS
+
+/*----------------------------------------------------------------------
+ This system doesn't have disk quotas.
+ Return space left in disk quota on file system which given path is in.
+
+ Args: path - Path name of file or directory on file system of concern
+ over - pointer to flag that is set if the user is over quota
+
+ Returns: If *over = 0, the number of bytes free in disk quota as per
+ the soft limit.
+ If *over = 1, the number of bytes *over* quota.
+ -1 is returned on an error looking up quota
+ 0 is returned if there is no quota
+
+BUG: If there's more than 2.1Gb free this function will break
+ ----*/
+long
+disk_quota(path, over)
+ char *path;
+ int *over;
+{
+ return(0L);
+}
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/diskquot.ptx b/alpine/osdep/diskquot.ptx
new file mode 100644
index 00000000..b636db08
--- /dev/null
+++ b/alpine/osdep/diskquot.ptx
@@ -0,0 +1,9 @@
+#if defined(USE_QUOTAS)
+
+#include <sys/ufsquota.h>
+#define MTABNAME "/etc/mnttab"
+
+include(sunquota)
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/diskquot.sgi b/alpine/osdep/diskquot.sgi
new file mode 100644
index 00000000..3100d06c
--- /dev/null
+++ b/alpine/osdep/diskquot.sgi
@@ -0,0 +1,8 @@
+#if defined(USE_QUOTAS)
+
+#include <sys/quota.h>
+
+include(sunquota)
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/diskquot.so5 b/alpine/osdep/diskquot.so5
new file mode 100644
index 00000000..ffd4ca93
--- /dev/null
+++ b/alpine/osdep/diskquot.so5
@@ -0,0 +1,9 @@
+#if defined(USE_QUOTAS)
+
+#include <sys/fs/ufs_quota.h>
+#define MTABNAME "/etc/mnttab"
+
+include(solquota)
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/diskquot.sun b/alpine/osdep/diskquot.sun
new file mode 100644
index 00000000..e8cb0506
--- /dev/null
+++ b/alpine/osdep/diskquot.sun
@@ -0,0 +1,8 @@
+#if defined(USE_QUOTAS)
+
+#include <ufs/quota.h>
+
+include(sunquota)
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/diskquot.sv4 b/alpine/osdep/diskquot.sv4
new file mode 100644
index 00000000..dc330a30
--- /dev/null
+++ b/alpine/osdep/diskquot.sv4
@@ -0,0 +1,9 @@
+#if defined(USE_QUOTAS)
+
+#include <sys/fs/ufs_quota.h>
+#define MTABNAME "/etc/mnttab"
+
+include(sunquota)
+#endif /* USE_QUOTAS */
+
+
diff --git a/alpine/osdep/execview.c b/alpine/osdep/execview.c
new file mode 100644
index 00000000..da7310df
--- /dev/null
+++ b/alpine/osdep/execview.c
@@ -0,0 +1,555 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: execview.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/debug.h"
+
+#include "../../pith/osdep/temp_nam.h"
+#include "../../pith/osdep/color.h"
+#include "../../pith/osdep/mimedisp.h"
+
+#include "../../pith/charconv/utf8.h"
+#include "../../pith/charconv/filesys.h"
+
+#include "../pith/mailcap.h"
+
+#include "../status.h"
+#include "../radio.h"
+#include "../signal.h"
+#include "../../pico/estruct.h"
+#include "../../pico/pico.h"
+#include "../mailview.h"
+#include "termin.gen.h"
+
+#ifdef _WINDOWS
+#include "../../pico/osdep/mswin.h"
+#endif
+
+/* Useful structures */
+#if OSX_TARGET
+typedef struct _execview_event_data_s {
+ int done;
+ ProcessSerialNumber pid;
+ int set_pid;
+} EXEC_EVENT_DATA_S;
+#endif
+
+
+/* internal prototypes */
+#if OSX_TARGET
+pascal OSStatus osx_launch_app_callback(EventHandlerCallRef,
+ EventRef, void *);
+int install_app_launch_cb(void *);
+void osx_launch_special_handling(MCAP_CMD_S *, char *);
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ Execute the given mailcap command
+
+ Args: cmd -- the command to execute
+ image_file -- the file the data is in
+ needsterminal -- does this command want to take over the terminal?
+ ----*/
+void
+exec_mailcap_cmd(MCAP_CMD_S *mc_cmd, char *image_file, int needsterminal)
+{
+#ifdef _WINDOWS
+ STARTUPINFO start_info;
+ PROCESS_INFORMATION proc_info;
+ WINHAND childProcess;
+ int success = 0;
+ char *cmd;
+ LPTSTR image_file_lpt = NULL;
+ LPTSTR cmd_lpt = NULL;
+
+ /* no special handling yet, but could be used to replace '*' hack */
+ if(mc_cmd)
+ cmd = mc_cmd->command;
+ else
+ return;
+
+ dprint((9, "run_viewer: command=%s\n", cmd ? cmd : "?")) ;
+
+ if(image_file)
+ image_file_lpt = utf8_to_lptstr(image_file);
+
+ /* Set to READONLY so the viewer can't try to edit it and keep it around */
+ if(image_file_lpt)
+ SetFileAttributes(image_file_lpt, FILE_ATTRIBUTE_READONLY);
+
+ if(*cmd == '*' || (*cmd == '\"' && *(cmd+1) == '*')){
+ /*
+ * It has been asked that there be the ability to do an
+ * "Open With..." on attachments like you can from the
+ * Windows file browser. After looking into this, it
+ * seems that the only way to do this would be through
+ * an undocumented hack. Here, we would pass "openas" as
+ * the verb to mswin_shell_exec (also some changes in
+ * mswin_shell_exec). Since this is the delicate world
+ * of attachment handling, it seems right not to rely on
+ * a hack. The interface wouldn't be too clean anyways,
+ * as we would have to download the attachment only to
+ * display the "Open With..." dialog. Go figure, some
+ * things Microsoft just wants to keep to themselves.
+ */
+
+ /*
+ * 2/1/2007. No idea when the above comment was written, but it is
+ * documented now at least. The below two urls describe the "openas" verb:
+ *
+ * http://blogs.msdn.com/oldnewthing/archive/2004/11/26/270710.aspx
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
+ * shellcc/platform/shell/programmersguide/shell_basics/
+ * shell_basics_extending/context.asp
+ */
+ success = mswin_shell_exec(cmd, &childProcess) == 0;
+ }
+ else{
+ memset(&proc_info, 0, sizeof(proc_info));
+ memset(&start_info, 0, sizeof(start_info));
+ start_info.dwFlags = STARTF_FORCEONFEEDBACK;
+ start_info.wShowWindow = SW_SHOWNORMAL;
+
+ if(cmd)
+ cmd_lpt = utf8_to_lptstr(cmd);
+
+ if(CreateProcess(NULL, cmd_lpt, NULL, NULL, FALSE,
+ CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
+ NULL, NULL, &start_info, &proc_info) == TRUE){
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
+ dprint ((3, "CreatProcess(%s) Success.\n",
+ cmd ? cmd : "?"));
+ childProcess = proc_info.hProcess;
+ success = 1;
+ }
+
+ if(cmd_lpt)
+ fs_give((void **) &cmd_lpt);
+ }
+
+ if(!success){
+ int rc = (int) GetLastError();
+ if(image_file_lpt)
+ SetFileAttributes(image_file_lpt, FILE_ATTRIBUTE_NORMAL);
+
+ our_unlink(image_file);
+ q_status_message2(SM_ORDER, 3, 4, "\007Can't start viewer. %s%s.",
+ (rc == 2 || rc == 3) ? "Viewer not found: " :
+ (rc == 8) ? "Not enough memory" : "Windows error ",
+ (rc == 2 || rc == 3) ? cmd :
+ (rc == 8) ? "" : int2string(rc));
+ }
+
+ if(image_file_lpt)
+ fs_give((void **) &image_file_lpt);
+
+#elif OSX_TARGET
+
+ char *command = NULL,
+ *result_file = NULL,
+ *p;
+ char **r_file_h;
+ PIPE_S *syspipe;
+ int mode;
+
+ if(!mc_cmd)
+ return;
+ if(mc_cmd->special_handling){
+ char *rhost;
+
+ if(mime_os_specific_access())
+ osx_launch_special_handling(mc_cmd, image_file);
+ else{
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command cancelled");
+ our_unlink(image_file);
+ }
+ }
+ else {
+ char *cmd = mc_cmd->command;
+ size_t l;
+
+ l = 32 + strlen(cmd) + (2*strlen(image_file));
+ p = command = (char *) fs_get((l+1) * sizeof(char));
+ if(!needsterminal) /* put in background if it doesn't need terminal */
+ *p++ = '(';
+
+ snprintf(p, l+1-(p-command), "%s", cmd);
+ p += strlen(p);
+ if(!needsterminal){
+ if(p-command+2 < l+1){
+ *p++ = ')';
+ *p++ = ' ';
+ *p++ = '&';
+ }
+ }
+
+ if(p-command < l+1)
+ *p++ = '\n';
+
+ if(p-command < l+1)
+ *p = '\0';
+
+ dprint((9, "exec_mailcap_cmd: command=%s\n",
+ command ? command : "?"));
+
+ mode = PIPE_RESET;
+ if(needsterminal == 1)
+ r_file_h = NULL;
+ else{
+ mode |= PIPE_WRITE | PIPE_STDERR;
+ result_file = temp_nam(NULL, "pine_cmd");
+ r_file_h = &result_file;
+ }
+
+ if(syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0, pipe_callback, NULL)){
+ close_system_pipe(&syspipe, NULL, pipe_callback);
+ if(needsterminal == 1)
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
+ else if(needsterminal == 2)
+ display_output_file(result_file, "VIEWER", " command result", 1);
+ else
+ display_output_file(result_file, "VIEWER", " command launched", 1);
+ }
+ else
+ q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd);
+
+ fs_give((void **)&command);
+ if(result_file)
+ fs_give((void **)&result_file);
+ }
+#else
+ char *command = NULL,
+ *result_file = NULL,
+ *p, *cmd;
+ char **r_file_h;
+ PIPE_S *syspipe;
+ int mode;
+ size_t l;
+
+ /* no os-specific command handling */
+ if(mc_cmd)
+ cmd = mc_cmd->command;
+ else
+ return;
+ l = 32 + strlen(cmd) + 2*strlen(image_file);
+ p = command = (char *)fs_get((l+1) * sizeof(char));
+ if(!needsterminal) /* put in background if it doesn't need terminal */
+ *p++ = '(';
+ snprintf(p, l+1-(p-command), "%s ; rm -f %s", cmd, image_file);
+ command[l] = '\0';
+ p += strlen(p);
+ if(!needsterminal && (p-command)+5 < l){
+ *p++ = ')';
+ *p++ = ' ';
+ *p++ = '&';
+ }
+
+ *p++ = '\n';
+ *p = '\0';
+
+ dprint((9, "exec_mailcap_cmd: command=%s\n",
+ command ? command : "?"));
+
+ mode = PIPE_RESET;
+ if(needsterminal == 1)
+ r_file_h = NULL;
+ else{
+ mode |= PIPE_WRITE | PIPE_STDERR;
+ result_file = temp_nam(NULL, "pine_cmd");
+ r_file_h = &result_file;
+ }
+
+ if((syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0, pipe_callback, NULL)) != NULL){
+ close_system_pipe(&syspipe, NULL, pipe_callback);
+ if(needsterminal == 1)
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
+ else if(needsterminal == 2)
+ display_output_file(result_file, "VIEWER", " command result", 1);
+ else
+ display_output_file(result_file, "VIEWER", " command launched", 1);
+ }
+ else
+ q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd);
+
+ fs_give((void **)&command);
+
+ if(result_file)
+ fs_give((void **)&result_file);
+#endif
+}
+
+
+/* ----------------------------------------------------------------------
+ Execute the given mailcap test= cmd
+
+ Args: cmd -- command to execute
+ Returns exit status
+
+ ----*/
+int
+exec_mailcap_test_cmd(char *cmd)
+{
+#ifdef _WINDOWS
+ return((WinExec(cmd, SW_SHOWMINNOACTIVE) < 32) ? 1 : 0);
+#else
+ PIPE_S *syspipe;
+
+ return((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_SILENT, 0,
+ pipe_callback, NULL))
+ ? close_system_pipe(&syspipe, NULL, pipe_callback) : -1);
+#endif
+}
+
+
+char *
+url_os_specified_browser(char *url)
+{
+#ifdef _WINDOWS
+ return(mswin_reg_default_browser(url));
+#elif OSX_TARGET
+ if(mime_os_specific_access()){
+ return(cpystr("open"));
+ }
+#else
+ /* do nothing here */
+ return(NULL);
+#endif
+}
+
+/*
+ * Return a pretty command, on some OS's we might do something
+ * different than just display the command.
+ *
+ * free_ret - whether or not to free the return value
+ */
+char *
+execview_pretty_command(MCAP_CMD_S *mc_cmd, int *free_ret)
+{
+ char *str;
+ int rv_to_free = 0;
+
+ if(free_ret)
+ *free_ret = rv_to_free;
+
+ if(!mc_cmd)
+ return NULL;
+
+ str = mc_cmd->command;
+
+#ifdef _WINDOWS
+ if(*str == '*' || (*str == '\"' && str[1] == '*')){
+ if(!strncmp(str + ((*str == '\"') ? 2 : 1), "DDE*", 4))
+ str = cpystr("via app already running");
+ else if(!strncmp(str + ((*str == '\"') ? 2 : 1),"ShellEx*",8))
+ str = cpystr("via Explorer defined app");
+ else
+ str = cpystr("via Windows-specific method");
+
+ rv_to_free = 1;
+ }
+#elif OSX_TARGET
+ if(mc_cmd->special_handling){
+ CFStringRef str_ref = NULL, kind_str_ref = NULL;
+ CFURLRef url_ref;
+ char buf[256];
+
+ if((str_ref = CFStringCreateWithCString(NULL, mc_cmd->command,
+ kCFStringEncodingASCII)) == NULL)
+ return "";
+
+ if((url_ref = CFURLCreateWithString(NULL, str_ref, NULL)) == NULL)
+ return "";
+
+ if(LSCopyDisplayNameForURL(url_ref, &kind_str_ref) != noErr)
+ return "";
+
+ if(CFStringGetCString(kind_str_ref, buf, (CFIndex)255,
+ kCFStringEncodingASCII) == false)
+ return "";
+
+ buf[255] = '\0';
+ str = cpystr(buf);
+ rv_to_free = 1;
+ if(kind_str_ref)
+ CFRelease(kind_str_ref);
+ }
+#else
+ /* always pretty */
+#endif
+
+ if(free_ret)
+ *free_ret = rv_to_free;
+
+ return(str);
+}
+
+
+#if OSX_TARGET
+void
+osx_launch_special_handling(MCAP_CMD_S *mc_cmd, char *image_file)
+{
+ CFStringRef str_ref = NULL;
+ CFURLRef url_ref = NULL;
+ LSLaunchFSRefSpec launch_spec;
+ FSRef app_ref, file_ref;
+ static EXEC_EVENT_DATA_S event_data;
+
+ install_app_launch_cb((void *)&event_data);
+ if((str_ref = CFStringCreateWithCString(NULL, mc_cmd->command,
+ kCFStringEncodingASCII)) == NULL)
+ return;
+ if((url_ref = CFURLCreateWithString(NULL, str_ref, NULL)) == NULL)
+ return;
+ if(CFURLGetFSRef(url_ref, &app_ref) == false)
+ return;
+ if(FSPathMakeRef((unsigned char *)image_file,
+ &file_ref, NULL) != noErr)
+ return;
+ launch_spec.appRef = &app_ref;
+ launch_spec.numDocs = 1;
+ launch_spec.itemRefs = &file_ref;
+ launch_spec.passThruParams = NULL;
+ launch_spec.launchFlags = kLSLaunchDontAddToRecents | kLSLaunchNoParams
+ | kLSLaunchAsync | kLSLaunchNewInstance;
+ /* would want to use this if we ever did true event handling */
+ launch_spec.asyncRefCon = 0;
+
+ if(LSOpenFromRefSpec( &launch_spec, NULL) == noErr){
+ /*
+ * Here's the strategy: we want to be able to just launch
+ * the app and then just delete the temp file, but that
+ * doesn't work because the called app needs the temp file
+ * at least until it's finished loading. Being that there's
+ * no way to tell when the app has finished loading, we wait
+ * until the program has exited, which is the safest thing to
+ * do and is what we do for windows. Since we haven't totally
+ * embraced event handling at this point, we must do the waiting
+ * synchronously. We allow for a keystroke to stop waiting, and
+ * just delete the temp file.
+ * Ideally, we would launch the app, and keep running, checking
+ * the events until the process terminates, and then delete the
+ * temp file. In this method, we would delete the temp file
+ * at close time if the app was still running.
+ */
+ int ch;
+ OSStatus rne_rv;
+ EventTargetRef target;
+ EventRef out_event;
+ EventTypeSpec event_types[2] = {
+ {kEventClassApplication, kEventAppTerminated},
+ {kEventClassApplication, kEventAppLaunchNotification}};
+
+ q_status_message(SM_ORDER, 0, 4,
+ "Waiting for program to finish, or press a key to stop waiting...");
+ flush_status_messages(1);
+ target = GetEventDispatcherTarget();
+ event_data.done = 0;
+ event_data.set_pid = 0;
+ while(!event_data.done){
+ if((rne_rv = ReceiveNextEvent(2, event_types, 1,
+ true, &out_event)) == noErr){
+ SendEventToEventTarget(out_event, target);
+ ReleaseEvent(out_event);
+ }
+ else if(rne_rv == eventLoopTimedOutErr){
+ ch = read_char(1);
+ if(ch)
+ event_data.done = 1;
+ }
+ else if(rne_rv == eventLoopQuitErr)
+ event_data.done = 1;
+ }
+ our_unlink(image_file);
+ }
+ q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
+}
+
+pascal OSStatus osx_launch_app_callback(EventHandlerCallRef next_h, EventRef event,void *user_data)
+{
+ EXEC_EVENT_DATA_S *ev_datap = (EXEC_EVENT_DATA_S *)user_data;
+ ProcessSerialNumber pid;
+ Boolean res = 0;
+
+ static int dont_do_anything_yet = 0;
+ switch(GetEventClass(event)){
+ case kEventClassKeyboard:
+ ev_datap->done = 1;
+ break;
+ case kEventClassApplication:
+ switch(GetEventKind(event)){
+ case kEventAppTerminated:
+ GetEventParameter(event,
+ kEventParamProcessID,
+ typeProcessSerialNumber, NULL,
+ sizeof(pid), NULL,
+ &pid);
+ SameProcess(&ev_datap->pid, &pid, &res);
+ if(res){
+ ev_datap->done = 1;
+ ev_datap->set_pid = 0;
+ }
+ break;
+ case kEventAppLaunchNotification:
+ /* could check asyncRef too */
+ if(!ev_datap->set_pid){ /* should always be true */
+ GetEventParameter(event,
+ kEventParamProcessID,
+ typeProcessSerialNumber, NULL,
+ sizeof(ev_datap->pid), NULL,
+ &(ev_datap->pid));
+ ev_datap->set_pid = 1;
+ }
+ break;
+ }
+ break;
+ }
+ return(noErr);
+}
+
+
+int
+install_app_launch_cb(void *user_data)
+{
+ static int already_installed = 0;
+
+ if(!already_installed){
+ EventHandlerUPP cb_upp;
+ EventTypeSpec event_types[2] = {
+ {kEventClassApplication, kEventAppTerminated},
+ {kEventClassApplication, kEventAppLaunchNotification}};
+
+ if((cb_upp = NewEventHandlerUPP(osx_launch_app_callback)) == NULL)
+ return 1;
+ InstallApplicationEventHandler(cb_upp, 2, event_types,
+ user_data, NULL);
+ already_installed = 1;
+ }
+ return 0;
+}
+#endif /* OSX_TARGET */
diff --git a/alpine/osdep/execview.h b/alpine/osdep/execview.h
new file mode 100644
index 00000000..b6406bfc
--- /dev/null
+++ b/alpine/osdep/execview.h
@@ -0,0 +1,27 @@
+/*
+ * $Id: execview.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_EXECVIEW_INCLUDED
+#define PINE_OSDEP_EXECVIEW_INCLUDED
+
+#include "../../pith/mailcap.h"
+
+/* exported prototypes */
+void exec_mailcap_cmd(MCAP_CMD_S *, char *, int);
+char *url_os_specified_browser(char *);
+char *execview_pretty_command(MCAP_CMD_S *, int *);
+
+
+#endif /* PINE_OSDEP_EXECVIEW_INCLUDED */
diff --git a/alpine/osdep/fltrname.c b/alpine/osdep/fltrname.c
new file mode 100644
index 00000000..5f2e2185
--- /dev/null
+++ b/alpine/osdep/fltrname.c
@@ -0,0 +1,119 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: fltrname.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../c-client/mail.h" /* for MAILSTREAM */
+#undef ERROR
+
+#include <system.h>
+#include <general.h>
+
+#include "../../pith/osdep/writ_dir.h"
+#include "../../pith/osdep/bldpath.h"
+
+#include "fltrname.h"
+
+
+/*----------------------------------------------------------------------
+ Filter file names for strange characters
+
+ Args: file -- the file name to check
+
+ Result: Returns NULL if file name is OK
+ Returns formatted error message if it is not
+ ----*/
+char *
+filter_filename(char *file, int *fatal, int strict)
+{
+#define ALLOW_WEIRD 1
+#ifdef ALLOW_WEIRD
+ static char illegal[] = {'\177', '\0'};
+#else
+#ifdef _WINDOWS
+ static char illegal[] = {'"', '#', '$', '%', '&', /*'/',*/ '(', ')','*',
+ ',', ';', '<', '=', '>', '?', '[', ']',
+ '^', '|', '\177', '\0'};
+#else /* UNIX */
+ static char illegal[] = {'"', '#', '$', '%', '&', '\'','(', ')','*',
+ ',', ':', ';', '<', '=', '>', '?', '[', ']',
+ '\\', '^', '|', '\177', '\0'};
+#endif /* UNIX */
+#endif
+ static char error[100];
+ char ill_file[MAXPATH+1], *ill_char, *ptr, e2[20];
+ int i;
+
+ for(ptr = file; *ptr == ' '; ptr++) ; /* leading spaces gone */
+
+ while(*ptr && (unsigned char)(*ptr) >= ' ' && strchr(illegal, *ptr) == 0)
+ ptr++;
+
+ *fatal = TRUE;
+ if(*ptr != '\0') {
+ if(*ptr == '\n') {
+ ill_char = "<newline>";
+ } else if(*ptr == '\r') {
+ ill_char = "<carriage return>";
+ } else if(*ptr == '\t') {
+ ill_char = "<tab>";
+ *fatal = FALSE; /* just whitespace */
+ } else if(*ptr < ' ') {
+ snprintf(e2, sizeof(e2), "control-%c", *ptr + '@');
+ ill_char = e2;
+ } else if (*ptr == '\177') {
+ ill_char = "<del>";
+ } else {
+ e2[0] = *ptr;
+ e2[1] = '\0';
+ ill_char = e2;
+ *fatal = FALSE; /* less offensive? */
+ }
+ if(!*fatal){
+ strncpy(error, ill_char, sizeof(error)-1);
+ error[sizeof(error)-1] = '\0';
+ }
+ else if(ptr != file) {
+ strncpy(ill_file, file, MIN(ptr-file,sizeof(ill_file)-1));
+ ill_file[MIN(ptr-file,sizeof(ill_file)-1)] = '\0';
+ snprintf(error, sizeof(error),
+ "Character \"%s\" after \"%.*s\" not allowed in file name",
+ ill_char, sizeof(error)-50, ill_file);
+ } else {
+ snprintf(error, sizeof(error),
+ "First character, \"%s\", not allowed in file name",
+ ill_char);
+ }
+
+ return(error);
+ }
+
+ if((i=is_writable_dir(file)) == 0 || i == 1){
+ snprintf(error, sizeof(error), "\"%.*s\" is a directory", sizeof(error)-50, file);
+ return(error);
+ }
+
+ if(strict){
+ for(ptr = file; *ptr == ' '; ptr++) ; /* leading spaces gone */
+
+ if((ptr[0] == '.' && ptr[1] == '.') || filename_parent_ref(ptr)){
+ snprintf(error, sizeof(error), "\"..\" not allowed in filename");
+ return(error);
+ }
+ }
+
+ return((char *)NULL);
+}
diff --git a/alpine/osdep/fltrname.h b/alpine/osdep/fltrname.h
new file mode 100644
index 00000000..523c641d
--- /dev/null
+++ b/alpine/osdep/fltrname.h
@@ -0,0 +1,26 @@
+/*
+ * $Id: fltrname.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_FLTRNAME_INCLUDED
+#define PINE_OSDEP_FLTRNAME_INCLUDED
+
+
+/*
+ * Exported Prototypes
+ */
+char *filter_filename(char *, int *, int);
+
+
+#endif /* PINE_OSDEP_FLTRNAME_INCLUDED */
diff --git a/alpine/osdep/jobcntrl.c b/alpine/osdep/jobcntrl.c
new file mode 100644
index 00000000..38169822
--- /dev/null
+++ b/alpine/osdep/jobcntrl.c
@@ -0,0 +1,55 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: jobcntrl.c 765 2007-10-23 23:51:37Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+
+#include "jobcntrl.h"
+
+
+/*----------------------------------------------------------------------
+ This routine returns 1 if job control is available. Note, thiis
+ could be some type of fake job control. It doesn't have to be
+ real BSD-style job control.
+ ----*/
+int
+have_job_control(void)
+{
+ return 1;
+}
+
+
+/*----------------------------------------------------------------------
+ If we don't have job control, this routine is never called.
+ ----*/
+void
+stop_process(void)
+{
+#ifndef _WINDOWS
+ RETSIGTYPE (*save_usr2)(int);
+
+ /*
+ * Since we can't respond to KOD while stopped, the process that sent
+ * the KOD is going to go read-only. Therefore, we can safely ignore
+ * any KODs that come in before we are ready to respond...
+ */
+ save_usr2 = signal(SIGUSR2, SIG_IGN);
+ kill(0, SIGSTOP);
+ (void)signal(SIGUSR2, save_usr2);
+#endif /* !_WINDOWS */
+}
+
+
diff --git a/alpine/osdep/jobcntrl.h b/alpine/osdep/jobcntrl.h
new file mode 100644
index 00000000..3908cce7
--- /dev/null
+++ b/alpine/osdep/jobcntrl.h
@@ -0,0 +1,25 @@
+/*
+ * $Id: jobcntrl.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_JOBCNTRL_INCLUDED
+#define PINE_OSDEP_JOBCNTRL_INCLUDED
+
+
+/* exported protoypes */
+int have_job_control(void);
+void stop_process(void);
+
+
+#endif /* PINE_OSDEP_JOBCNTRL_INCLUDED */
diff --git a/alpine/osdep/makefile.wnt b/alpine/osdep/makefile.wnt
new file mode 100644
index 00000000..07afeb63
--- /dev/null
+++ b/alpine/osdep/makefile.wnt
@@ -0,0 +1,66 @@
+# $Id: makefile.wnt 14098 2005-10-03 18:54:13Z jpf@u.washington.edu $
+#
+# ========================================================================
+# Copyright 2006 University of Washington
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# ========================================================================
+
+#
+#
+# Makefile for WIN NT version of the libalpineosd.lib
+#
+#
+CC=cl
+RM=del
+CP=copy
+RC=rc
+
+#includes symbol info for debugging
+CDEBUG= #-Zi -Od
+LDEBUG= /DEBUG /DEBUGTYPE:CV
+
+STDCFLAGS= -I..\..\include -I..\..\regex -nologo -MT -DWIN32 -DDOS -D_WINDOWS -DJOB_CONTROL -DMSC_MALLOC
+
+CFLAGS= $(CDEBUG) $(STDCFLAGS) $(NET) $(EXTRACFLAGS)
+
+LFLAGS= $(LDEBUG) $(EXTRALDFLAGS)
+
+RCFLAGS =
+
+LIBER=lib
+LIBARGS=/nologo /verbose
+
+HFILES= ../../include/system.h ../../include/general.h \
+ debuging.h execview.h fltrname.h jobcntrl.h print.h resource.h termin.gen.h \
+ termin.wnt.h termout.gen.h termout.wnt.h windlg.h
+
+OFILES= chnge_pw.obj debuging.obj diskquot.non.obj execview.obj fltrname.obj \
+ jobcntrl.obj print.obj termin.gen.obj termin.wnt.obj termout.gen.obj \
+ termout.wnt.obj mswinver.obj
+
+all: libalpineosd.lib mswin.res
+
+.c.obj:
+ $(CC) -c $(CFLAGS) "$(MAKEDIR)"\$*.c
+
+$(OFILES): $(HFILES)
+
+libalpineosd.lib: $(OFILES)
+ $(RM) libalpineosd.lib || rem
+ $(LIBER) /out:libalpineosd.lib $(OFILES)
+
+mswin.res: ../../pico/osdep/mswinhnd.cur alpine-splash.bmp resource.h \
+ mswin.rc alpine.ico newmail.ico mclosed.ico
+ $(RC) $(RCFLAGS) /fo mswin.res mswin.rc
+
+
+clean:
+ $(RM) *.lib
+ $(RM) *.obj
+ $(RM) mswin.res
diff --git a/alpine/osdep/mclosed.ico b/alpine/osdep/mclosed.ico
new file mode 100644
index 00000000..0c6306b3
--- /dev/null
+++ b/alpine/osdep/mclosed.ico
Binary files differ
diff --git a/alpine/osdep/mswin.def b/alpine/osdep/mswin.def
new file mode 100644
index 00000000..0b262412
--- /dev/null
+++ b/alpine/osdep/mswin.def
@@ -0,0 +1,17 @@
+NAME PINE
+EXETYPE WINDOWS
+DESCRIPTION 'PC-Pine for Windows (Character)'
+STUB 'winstub.exe'
+
+CODE PRELOAD DISCARDABLE SHARED
+DATA PRELOAD MULTIPLE
+
+HEAPSIZE 100
+
+EXPORTS
+ PWNDPROC @1
+ TWWNDPROC @2
+ ABOUTDLGPROC @3
+
+
+
diff --git a/alpine/osdep/mswin.rc b/alpine/osdep/mswin.rc
new file mode 100644
index 00000000..b4a8533a
--- /dev/null
+++ b/alpine/osdep/mswin.rc
@@ -0,0 +1,605 @@
+//Microsoft Developer Studio generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#define APSTUDIO_HIDDEN_SYMBOLS
+#include "windows.h"
+#undef APSTUDIO_HIDDEN_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+ALPINEICON ICON DISCARDABLE "ALPINE.ICO"
+NEWMAILICON ICON DISCARDABLE "NEWMAIL.ICO"
+MCLOSEDICON ICON DISCARDABLE "MCLOSED.ICO"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+ALPINESPLASH BITMAP MOVEABLE PURE "ALPINE-SPLASH.BMP"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Cursor
+//
+
+PICOHAND CURSOR "..\..\PICO\OSDEP\MSWINHND.CUR"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+ALPINEMENU MENU DISCARDABLE
+BEGIN
+ POPUP "&File"
+ BEGIN
+ MENUITEM "&Print", IDM_MI_PRINT
+ MENUITEM SEPARATOR
+ MENUITEM "&Read File", IDM_MI_READFILE
+ MENUITEM SEPARATOR
+ MENUITEM "E&xit", IDM_MI_EXIT
+ END
+ POPUP "&Edit"
+ BEGIN
+ MENUITEM "Cu&t\tCtrl-Shift-X", IDM_EDIT_CUT
+ MENUITEM "&Copy\tCtrl-Shift-C", IDM_EDIT_COPY
+ MENUITEM "Copy Append\tCtrl-Alt-C", IDM_EDIT_COPY_APPEND
+ MENUITEM "&Paste\tCtrl-Shift-V", IDM_EDIT_PASTE
+ MENUITEM "Cance&l Paste!\tCtrl-Alt-V", IDM_EDIT_CANCEL_PASTE
+ MENUITEM SEPARATOR
+ MENUITEM "&Find\tCtrl-Shift-F", IDM_MI_WHEREIS
+ END
+ POPUP "&Go"
+ BEGIN
+ MENUITEM "&Main Menu", IDM_MI_MAINMENU
+ MENUITEM "&List Folders", IDM_MI_FLDRLIST
+ MENUITEM "&Index", IDM_MI_FLDRINDEX
+ MENUITEM "&Goto Folder", IDM_MI_GOTOFLDR
+ MENUITEM "E&xit Current Mode", IDM_MI_EXITMODE
+ MENUITEM SEPARATOR
+ MENUITEM "&Previous Message", IDM_MI_PREVMSG
+ MENUITEM "&Next Message", IDM_MI_NEXTMSG
+ MENUITEM "&Jump To Message", IDM_MI_JUMPTOMSG
+ MENUITEM "&Where Is", IDM_MI_WHEREIS
+ END
+ POPUP "&Message"
+ BEGIN
+ MENUITEM "&View", IDM_MI_VIEW
+ MENUITEM "ViewInNew&Window", IDM_MI_VIEWINWIND
+ MENUITEM "Pr&int", IDM_MI_PRINT
+ MENUITEM "&Save", IDM_MI_SAVE
+ MENUITEM "&Export", IDM_MI_EXPORT
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete", IDM_MI_DELETE
+ POPUP "&Flag"
+ BEGIN
+ MENUITEM "&Important", IDM_MI_FLAGIMPORTANT
+ MENUITEM "&New", IDM_MI_FLAGNEW
+ MENUITEM "&Answered", IDM_MI_FLAGANSWERED
+ MENUITEM "&Deleted", IDM_MI_FLAGDELETED
+ END
+ MENUITEM "E&xpunge", IDM_MI_EXPUNGE
+ MENUITEM SEPARATOR
+ MENUITEM "&Take Address", IDM_MI_TAKEADDR
+ MENUITEM "&Header Mode", IDM_MI_HDRMODE
+ POPUP "S&ort"
+ BEGIN
+ MENUITEM "Arrival", IDM_MI_SORTARRIVAL
+ MENUITEM "Date", IDM_MI_SORTDATE
+ MENUITEM "From", IDM_MI_SORTFROM
+ MENUITEM "To", IDM_MI_SORTTO
+ MENUITEM "Cc", IDM_MI_SORTCC
+ MENUITEM "Subject", IDM_MI_SORTSUBJECT
+ MENUITEM "OrderedSubject", IDM_MI_SORTORDERSUB
+ MENUITEM "Size", IDM_MI_SORTSIZE
+ MENUITEM "Score", IDM_MI_SORTSCORE
+ MENUITEM "Thread", IDM_MI_SORTTHREAD
+ MENUITEM SEPARATOR
+ MENUITEM "Reverse", IDM_MI_SORTREVERSE
+ END
+ MENUITEM "Se&lect", IDM_MI_SELECT
+ MENUITEM "&Apply", IDM_MI_APPLY
+ MENUITEM "&Zoom", IDM_MI_ZOOM
+ END
+ POPUP "&Send"
+ BEGIN
+ MENUITEM "&Compose Message", IDM_MI_COMPOSER
+ MENUITEM "&Reply", IDM_MI_REPLY
+ MENUITEM "&Forward", IDM_MI_FORWARD
+ MENUITEM "&Bounce", IDM_MI_BOUNCE
+ END
+ POPUP "&Config"
+ BEGIN
+ MENUITEM "Set &Window Font", IDM_OPT_SETFONT
+ MENUITEM "Print Font &Same As Window", IDM_OPT_FONTSAMEAS
+ MENUITEM "Set &Printer Font", IDM_OPT_SETPRINTFONT
+ MENUITEM SEPARATOR
+ MENUITEM "&Toolbar", IDM_OPT_TOOLBAR
+ MENUITEM "Tool&bar on Top", IDM_OPT_TOOLBARPOS
+ POPUP "&Cursor"
+ BEGIN
+ MENUITEM "Block", IDM_OPT_CARETBLOCK
+ MENUITEM "Small Block", IDM_OPT_CARETSMALLBLOCK
+ MENUITEM "Underline", IDM_OPT_CARETHBAR
+ MENUITEM "Vertical Bar", IDM_OPT_CARETVBAR
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Use &Dialog Boxes", IDM_OPT_USEDIALOGS
+ MENUITEM "Erase Preserved &Passwords", IDM_OPT_ERASE_CREDENTIALS
+ MENUITEM SEPARATOR
+ MENUITEM "&IMAP Telemetry", IDM_OPT_IMAPTELEM
+ MENUITEM "&New Mail Window", IDM_OPT_NEWMAILWIN
+ END
+ POPUP "&Help"
+ BEGIN
+ MENUITEM "&Screen Help", IDM_MI_SCREENHELP
+ MENUITEM "&About", IDM_ABOUT
+ END
+END
+
+TEXTWINMENU MENU DISCARDABLE
+BEGIN
+ POPUP "&File"
+ BEGIN
+ MENUITEM "&Print", IDM_FILE_PRINT
+ MENUITEM SEPARATOR
+ MENUITEM "&Close", IDM_FILE_CLOSE
+ END
+ POPUP "&Edit"
+ BEGIN
+ MENUITEM "&Copy\tCtrl-Shift-C", IDM_EDIT_COPY
+ MENUITEM "Select All", IDM_EDIT_SEL_ALL
+ END
+END
+
+COMPOSERMENU MENU DISCARDABLE
+BEGIN
+ POPUP "&File"
+ BEGIN
+ MENUITEM "&Print", IDM_MI_PRINT
+ MENUITEM SEPARATOR
+ MENUITEM "&Read File", 181
+ MENUITEM SEPARATOR
+ MENUITEM "E&xit", IDM_MI_EXIT
+ END
+ POPUP "&Edit"
+ BEGIN
+ MENUITEM "Cu&t\tCtrl-Shift-X", IDM_EDIT_CUT
+ MENUITEM "&Copy\tCtrl-Shift-C", IDM_EDIT_COPY
+ MENUITEM "Copy Append\tCtrl-Alt-C", IDM_EDIT_COPY_APPEND
+ MENUITEM "&Paste\tCtrl-Shift-V", IDM_EDIT_PASTE
+ MENUITEM "Cance&l Paste!\tCtrl-Alt-V", IDM_EDIT_CANCEL_PASTE
+ MENUITEM SEPARATOR
+ MENUITEM "Select &All", IDM_EDIT_SEL_ALL
+ MENUITEM SEPARATOR
+ MENUITEM "&Find\tCtrl-Shift-F", IDM_MI_WHEREIS
+ MENUITEM SEPARATOR
+ MENUITEM "&Spell", IDM_MI_SPELLCHK
+ END
+ POPUP "&Compose"
+ BEGIN
+ MENUITEM "P&ostpone", IDM_MI_POSTPONE
+ MENUITEM "&Send", IDM_MI_SEND
+ MENUITEM "&Cancel", IDM_MI_CANCEL
+ MENUITEM SEPARATOR
+ MENUITEM "&Attach", IDM_MI_ATTACH
+ MENUITEM "Rich &Headers", IDM_MI_RICHHDR
+ MENUITEM "&To Address Book", IDM_MI_TOADDRBOOK
+ MENUITEM SEPARATOR
+ MENUITEM "&Justify Paragraph", IDM_MI_JUSTIFY
+ MENUITEM "Run Alternate &Editor", IDM_MI_ALTEDITOR
+ END
+ POPUP "C&onfig"
+ BEGIN
+ MENUITEM "Set &Window Font", IDM_OPT_SETFONT
+ MENUITEM "Print Font &Same As Window", IDM_OPT_FONTSAMEAS
+ MENUITEM "Set &Printer Font", IDM_OPT_SETPRINTFONT
+ MENUITEM SEPARATOR
+ MENUITEM "&Toolbar", IDM_OPT_TOOLBAR
+ MENUITEM "Tool&bar on Top", IDM_OPT_TOOLBARPOS
+ POPUP "&Cursor"
+ BEGIN
+ MENUITEM "Block", IDM_OPT_CARETBLOCK
+ MENUITEM "Small Block", IDM_OPT_CARETSMALLBLOCK
+ MENUITEM "Underline", IDM_OPT_CARETHBAR
+ MENUITEM "Vertical Bar", IDM_OPT_CARETVBAR
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Use &Dialog Boxes", IDM_OPT_USEDIALOGS
+ END
+ POPUP "&Help"
+ BEGIN
+ MENUITEM "&Screen Help", IDM_MI_SCREENHELP
+ MENUITEM "&About", IDM_ABOUT
+ END
+END
+
+#ifndef _MAC
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,01,0,0
+ PRODUCTVERSION 2,01,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "Comments", "see http://www.washington.edu/alpine\0"
+ VALUE "CompanyName", "University of Washington\0"
+#ifdef _PCP_W2K
+ VALUE "FileDescription", "Alpine with krb5 for Windows\0"
+#else
+ VALUE "FileDescription", "Alpine\0"
+#endif
+ VALUE "FileVersion", "2.10\0"
+ VALUE "InternalName", "alpine\0"
+ VALUE "LegalCopyright", "Copyright 2006-2009\0"
+ VALUE "OriginalFilename", "alpine.exe\0"
+ VALUE "ProductName", "alpine\0"
+ VALUE "ProductVersion", "2.10\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+#endif // !_MAC
+
+
+#include "..\..\pico\osdep\mswin_spell.dlg"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+ABOUTDLGBOX DIALOGEX 10, 25, 217, 80
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Alpine"
+FONT 8, "Helv", 0, 0, 0x1
+BEGIN
+ CTEXT "Alpine for Windows%S\nVersion %d.%d%S\nBuild (%S)",
+ IDD_VERSION,47,12,110,26,SS_NOPREFIX | NOT WS_GROUP,
+ WS_EX_TRANSPARENT
+ LTEXT "",IDD_BYLINE,21,46,172,56,SS_NOPREFIX | NOT WS_GROUP
+ DEFPUSHBUTTON "OK",IDD_OK,159,15,40,14
+ ICON ALPINEICON,IDC_STATIC,25,15,20,20
+END
+
+SPLASHDLGBOX DIALOGEX 0, 0, 520, 316
+STYLE WS_POPUP | DS_SETFOREGROUND | DS_LOCALEDIT
+BEGIN
+END
+
+IDD_TOOLBAR DIALOG DISCARDABLE 0, 0, 311, 12
+STYLE WS_CHILD
+FONT 8, "MS Sans Serif"
+BEGIN
+ PUSHBUTTON "Quit",IDM_MI_EXIT,1,1,25,11,NOT WS_TABSTOP
+ PUSHBUTTON "Main Menu",IDM_MI_MAINMENU,26,1,40,11,NOT WS_TABSTOP
+ PUSHBUTTON "Folders",IDM_MI_FLDRLIST,66,1,29,11,NOT WS_TABSTOP
+ PUSHBUTTON "Index",IDM_MI_FLDRINDEX,95,1,25,11,NOT WS_TABSTOP
+ PUSHBUTTON "View",IDM_MI_VIEW,120,1,24,11,NOT WS_TABSTOP
+ PUSHBUTTON "Compose",IDM_MI_COMPOSER,144,1,37,11,NOT WS_TABSTOP
+ PUSHBUTTON "Reply",IDM_MI_REPLY,181,1,27,11,NOT WS_TABSTOP
+ PUSHBUTTON "Forward",IDM_MI_FORWARD,208,1,34,11,NOT WS_TABSTOP
+END
+
+IDD_COMPOSER_TB DIALOG DISCARDABLE 0, 0, 311, 12
+STYLE WS_CHILD
+FONT 8, "MS Sans Serif"
+BEGIN
+ PUSHBUTTON "Send",IDM_MI_SEND,1,1,25,11
+ PUSHBUTTON "Cancel",IDM_MI_CANCEL,26,1,29,11
+ PUSHBUTTON "Postpone",IDM_MI_POSTPONE,55,1,37,11
+ PUSHBUTTON "Attach",IDM_MI_ATTACH,92,1,29,11
+ PUSHBUTTON "Find",IDM_MI_WHEREIS,121,1,22,11
+ PUSHBUTTON "Spell",IDM_MI_SPELLCHK,143,1,23,11
+END
+
+IDD_OPTIONALYENTER DIALOG DISCARDABLE 30, 30, 255, 86
+STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Alpine"
+FONT 8, "MS Sans Serif"
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,7,52,79,14
+ PUSHBUTTON "Cancel",IDCANCEL,7,68,78,14
+ LTEXT "",IDC_PROMPT,6,9,231,18
+ EDITTEXT IDC_RESPONCE,3,33,237,12,ES_AUTOHSCROLL | ES_NOHIDESEL
+ DEFPUSHBUTTON "Help '^G'",IDC_GETHELP,87,52,79,14
+END
+
+IDD_LOGINDLG DIALOG DISCARDABLE 0, 0, 213, 111
+STYLE DS_MODALFRAME | DS_SETFOREGROUND | DS_CENTER | WS_MINIMIZEBOX |
+ WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Alpine Login"
+FONT 8, "MS Sans Serif"
+BEGIN
+ ICON ALPINEICON,IDC_STATIC,7,10,21,20
+ LTEXT "Authentication required.",IDC_STATIC,44,12,152,8
+ LTEXT "Host :",IDC_PROMPT,44,23,165,8
+ LTEXT "Login: ",IDC_STATIC,20,44,20,8
+ EDITTEXT IDC_RLOGINE,45,42,133,12,ES_AUTOHSCROLL
+ LTEXT "Password: ",IDC_RPWTEXT,7,57,36,8
+ EDITTEXT IDC_RPASSWORD,45,55,133,12,ES_PASSWORD | ES_AUTOHSCROLL
+ CONTROL "Preserve this password for future logins",
+ IDC_PRESPASS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,45,70,
+ 165,8
+ DEFPUSHBUTTON "OK",IDOK,47,90,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,106,90,50,14
+END
+
+IDD_LOGINDLG2 DIALOG DISCARDABLE 0, 0, 281, 222
+STYLE DS_MODALFRAME | DS_SETFOREGROUND | DS_CENTER | WS_MINIMIZEBOX |
+ WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Alpine Login"
+FONT 8, "MS Sans Serif"
+BEGIN
+ ICON ALPINEICON,IDC_STATIC,7,10,21,20
+ LTEXT "Authentication required.\n\nIn order to set up Alpine, please authenticate to the IMAP server where your configuration will be stored.",IDC_STATIC,44,24,180,50
+ LTEXT "Host:",IDC_PROMPT,59,80,165,8
+ LTEXT "Login: ",IDC_STATIC,57,98,20,8
+ EDITTEXT IDC_RLOGINE,82,96,133,12,ES_AUTOHSCROLL
+ LTEXT "Password: ",IDC_RPWTEXT,44,120,36,8
+ EDITTEXT IDC_RPASSWORD,82,118,133,12,ES_PASSWORD | ES_AUTOHSCROLL
+ CONTROL "Preserve this password for future logins",
+ IDC_PRESPASS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,82,138,
+ 165,8
+ DEFPUSHBUTTON "OK",IDOK,65,196,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,131,196,50,14
+END
+
+
+IDD_CONFIGDLG DIALOG DISCARDABLE 0, 0, 281, 222
+STYLE DS_MODALFRAME | DS_SETFOREGROUND | DS_CENTER | WS_MINIMIZEBOX |
+ WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Alpine Configuration Setup"
+FONT 8, "MS Sans Serif"
+BEGIN
+ ICON ALPINEICON,IDC_STATIC,7,10,21,20
+ LTEXT "Alpine was not able to locate your personal configuration file. Please specify where Alpine should look for (or create) it.",
+ IDC_CONFTEXT,45,14,211,17
+ GROUPBOX "",IDC_STATIC,10,34,259,88
+ CONTROL "Use configuration file stored on IMAP server",
+ IDC_CONFRRADIO,"Button",BS_AUTORADIOBUTTON,17,42,154,10
+ LTEXT "IMAP Server :",IDC_CONFSRVRTXT,31,54,45,8
+ EDITTEXT IDC_CONFESERVER,77,52,138,14,ES_AUTOHSCROLL
+ LTEXT "Username :",IDC_CONFUNTXT,115,70,37,8
+ EDITTEXT IDC_CONFEUSERNAME,154,68,62,14,ES_AUTOHSCROLL
+ CONTROL "Use default configuration folder name",IDC_CONFDFLTFLDR,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,31,89,133,10
+ LTEXT "Configuration folder name :",IDC_CONFFLDRTXT,31,104,85,8
+ EDITTEXT IDC_CONFEFLDRNAME,117,102,86,14,ES_AUTOHSCROLL
+ GROUPBOX "",IDC_STATIC,11,126,259,43
+ CONTROL "Use local configuration file",IDC_CONFLRADIO,"Button",
+ BS_AUTORADIOBUTTON,17,135,99,10
+ LTEXT "Configuration file name :",IDC_CONFFNTXT,34,148,76,8
+ EDITTEXT IDC_CONFEFN,113,146,89,14,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse",IDC_CONFBROWSE,207,145,50,14
+ CONTROL "Use this as the default Alpine Configuration",
+ IDC_CONFDFLTSET,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,15,178,
+ 157,10
+ DEFPUSHBUTTON "OK",IDOK,65,196,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,131,196,50,14
+END
+
+IDD_CFVARSDLG DIALOG DISCARDABLE 0, 0, 281, 222
+STYLE DS_MODALFRAME | DS_SETFOREGROUND | DS_CENTER | WS_MINIMIZEBOX |
+ WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Alpine Configuration Setup"
+FONT 8, "MS Sans Serif"
+BEGIN
+ ICON ALPINEICON,IDC_STATIC,7,10,21,20
+ LTEXT "In order to properly set up Alpine, please fill out the following information, which can also be provided later.",
+ IDC_STATIC,45,14,211,17
+ LTEXT "Personal Name :",IDC_STATIC,33,46,53,8
+ EDITTEXT IDC_CFV_PNAME,90,43,157,14,ES_AUTOHSCROLL
+ LTEXT "Email Address :",IDC_STATIC,37,66,49,8
+ EDITTEXT IDC_CFV_EMAILADR,90,63,157,14,ES_AUTOHSCROLL
+ LTEXT "Mail Server :",IDC_STATIC,45,86,41,8
+ EDITTEXT IDC_CFV_MSERVER,90,83,157,14,ES_AUTOHSCROLL
+ CONTROL "This server is an IMAP server",IDC_CFV_IMAP,"Button",
+ BS_AUTORADIOBUTTON,141,100,108,10
+ CONTROL "This server is a POP3 server",IDC_CFV_POP3,"Button",
+ BS_AUTORADIOBUTTON,141,113,105,10
+ LTEXT "Login name (optional) :",IDC_STATIC,120,129,72,8
+ EDITTEXT IDC_CFV_LOGIN,195,126,51,14,ES_AUTOHSCROLL
+ LTEXT "SMTP Server :",IDC_STATIC,39,149,48,8
+ EDITTEXT IDC_CFV_SMTPSERVER,90,146,157,14,ES_AUTOHSCROLL
+ CONTROL "Make Alpine the default E-mail reader",
+ IDC_CFV_DEFMAILER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,
+ 90,165,157,10
+ CONTROL "Make Alpine the default Newsgroup reader",
+ IDC_CFV_DEFNEWSRDR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,
+ 90,177,157,10
+ DEFPUSHBUTTON "OK",IDOK,65,196,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,131,196,50,14
+END
+
+IDD_SELECT DIALOG DISCARDABLE 30, 30, 255, 66
+STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Alpine"
+FONT 8, "MS Sans Serif"
+BEGIN
+ PUSHBUTTON "Help",IDC_GETHELP,7,50,78,14
+ PUSHBUTTON "Cancel",IDCANCEL,7,33,78,14
+ LTEXT "",IDC_PROMPT,7,7,231,22
+ CONTROL "User1",IDC_RESPONCE,"KeyboardCapture",NOT WS_VISIBLE,
+ 208,49,35,12
+END
+
+IDD_SELECTSORT DIALOG DISCARDABLE 30, 30, 170, 102
+STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Select Sort"
+FONT 8, "MS Sans Serif"
+BEGIN
+ CONTROL "Arrival",IDC_SORTARRIVAL,"Button",BS_AUTORADIOBUTTON |
+ WS_GROUP | WS_TABSTOP,11,6,74,9
+ CONTROL "From",IDC_SORTFROM,"Button",BS_AUTORADIOBUTTON |
+ WS_TABSTOP,96,6,74,9
+ CONTROL "Subject",IDC_SORTSUBJECT,"Button",BS_AUTORADIOBUTTON |
+ WS_TABSTOP,11,18,74,9
+ CONTROL "To",IDC_SORTTO,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,
+ 96,18,74,9
+ CONTROL "Ordered Subject",IDC_SORTORDERSUB,"Button",
+ BS_AUTORADIOBUTTON | WS_TABSTOP,11,30,74,9
+ CONTROL "Cc",IDC_SORTCC,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,
+ 96,30,74,9
+ CONTROL "Size",IDC_SORTSIZE,"Button",BS_AUTORADIOBUTTON |
+ WS_TABSTOP,11,42,74,9
+ CONTROL "Date",IDC_SORTDATE,"Button",BS_AUTORADIOBUTTON |
+ WS_TABSTOP,96,42,74,9
+ CONTROL "Thread",IDC_SORTTHREAD,"Button",BS_AUTORADIOBUTTON |
+ WS_TABSTOP,11,54,74,9
+ CONTROL "Score",IDC_SORTSCORE,"Button",BS_AUTORADIOBUTTON |
+ WS_TABSTOP,96,54,74,9
+ CONTROL "Reverse Sort",IDC_SORTREVERSE,"Button",BS_AUTOCHECKBOX |
+ WS_TABSTOP,55,67,58,12
+ DEFPUSHBUTTON "OK",IDOK,5,82,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,60,82,50,14
+ PUSHBUTTON "Help",IDC_GETHELP,115,82,50,14
+END
+
+IDD_SELECTFLAG DIALOG DISCARDABLE 30, 30, 134, 42
+STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Select Flags"
+FONT 8, "MS Sans Serif"
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,11,22,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,72,22,50,14
+ CONTROL "Flag 1",IDC_FLAGCOL1,"Button",BS_AUTOCHECKBOX | NOT
+ WS_VISIBLE | WS_TABSTOP,4,4,59,10
+ CONTROL "Flag 2",IDC_FLAGCOL2,"Button",BS_AUTOCHECKBOX | NOT
+ WS_VISIBLE | WS_TABSTOP,70,4,59,10
+END
+
+IDD_ARGLIST DIALOG DISCARDABLE 0, 0, 302, 223
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Alpine - Command Line Arguments"
+FONT 8, "MS Sans Serif"
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,124,196,50,14,BS_CENTER
+ EDITTEXT IDC_ARGTEXT,7,7,287,182,ES_MULTILINE | ES_AUTOHSCROLL |
+ ES_READONLY | WS_VSCROLL | WS_HSCROLL
+END
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+2 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
+ "#include ""windows.h""\r\n"
+ "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
+ "\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+1 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "resource.h\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO DISCARDABLE
+BEGIN
+ IDD_ARGLIST, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 295
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 414
+ HORZGUIDE, 160
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE DISCARDABLE
+BEGIN
+ IDS_BYLINE "Copyright 2006-2009 University of Washington"
+ IDS_APPNAME "Alpine"
+ IDS_APPIDENT "alpine"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/alpine/osdep/mswinver.c b/alpine/osdep/mswinver.c
new file mode 100644
index 00000000..0c683520
--- /dev/null
+++ b/alpine/osdep/mswinver.c
@@ -0,0 +1,74 @@
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#define VER_MAJOR 2
+#define VER_MINOR 1
+extern char datestamp[];
+
+
+/*
+ * Return major version number...
+ */
+int
+mswin_majorver()
+{
+ return(VER_MAJOR);
+}
+
+
+/*
+ * Return minor version number...
+ */
+int
+mswin_minorver()
+{
+ return(VER_MINOR);
+}
+
+
+/*
+ * Return compilation number...
+ */
+char *
+mswin_compilation_date()
+{
+ return(datestamp);
+}
+
+
+/*
+ * Return special remarks...
+ */
+char *
+mswin_compilation_remarks()
+{
+#ifdef SPCL_REMARKS
+ return(SPCL_REMARKS);
+#else
+ return("");
+#endif
+}
+
+/*
+ * Return specific windows version...
+ */
+char *
+mswin_specific_winver()
+{
+#ifdef SPCFC_WINVER
+ return(SPCFC_WINVER);
+#else
+ return("");
+#endif
+}
diff --git a/alpine/osdep/newmail.ico b/alpine/osdep/newmail.ico
new file mode 100644
index 00000000..47783cb8
--- /dev/null
+++ b/alpine/osdep/newmail.ico
Binary files differ
diff --git a/alpine/osdep/print.c b/alpine/osdep/print.c
new file mode 100644
index 00000000..01720ece
--- /dev/null
+++ b/alpine/osdep/print.c
@@ -0,0 +1,529 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: print.c 942 2008-03-04 18:21:33Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#include <system.h>
+#include <general.h>
+
+#include "../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../c-client/osdep.h"
+#include "../c-client/rfc822.h" /* for soutr_t and such */
+#include "../c-client/misc.h" /* for cpystr proto */
+#include "../c-client/utf8.h" /* for CHARSET and such*/
+#include "../c-client/imap4r1.h"
+
+#include "../../pith/charconv/utf8.h"
+#include "../../pith/charconv/filesys.h"
+
+#include "../../pith/osdep/color.h"
+#include "../../pith/osdep/temp_nam.h"
+#include "../../pith/osdep/err_desc.h"
+#include "../../pith/osdep/collate.h"
+
+#include "../../pith/debug.h"
+#include "../../pith/conf.h"
+#include "../../pith/store.h"
+#include "../../pith/filttype.h"
+
+#include "../../pico/estruct.h" /* for ctrl() */
+#include "../../pico/keydefs.h" /* for KEY_* */
+
+#include "../status.h"
+#include "../signal.h"
+#include "../radio.h"
+#include "../../pico/estruct.h"
+#include "../../pico/pico.h"
+#include "../mailview.h"
+
+#ifdef _WINDOWS
+#include "../../pico/osdep/mswin.h"
+#endif
+
+#include "../../pico/osdep/raw.h"
+
+#include "termin.gen.h"
+
+#include "print.h"
+
+
+/*======================================================================
+ print routines
+
+ Functions having to do with printing on paper and forking of spoolers
+
+ In general one calls open_printer() to start printing. One of
+ the little print functions to send a line or string, and then
+ call print_end() when complete. This takes care of forking off a spooler
+ and piping the stuff down it. No handles or anything here because there's
+ only one printer open at a time.
+
+ ====*/
+
+
+
+#ifndef _WINDOWS
+static char *trailer; /* so both open and close_printer can see it */
+static int ansi_off;
+static CBUF_S cb;
+#endif /* !_WINDOWS */
+
+
+/*----------------------------------------------------------------------
+ Open the printer
+
+ Args: desc -- Description of item to print. Should have one trailing blank.
+
+ Return value: < 0 is a failure.
+ 0 a success.
+
+This does most of the work of popen so we can save the standard output of the
+command we execute and send it back to the user.
+ ----*/
+int
+open_printer(char *desc)
+{
+#ifndef _WINDOWS
+ char command[201], prompt[200];
+ int cmd, rc, just_one;
+ char *p, *init, *nick;
+ char aname[100], wname[100];
+ char *printer;
+ int done = 0, i, lastprinter, cur_printer = 0;
+ HelpType help;
+ char **list;
+ static ESCKEY_S ekey[] = {
+ /* TRANSLATORS: these are command labels for printing screen */
+ {'y', 'y', "Y", N_("Yes")},
+ {'n', 'n', "N", N_("No")},
+ /* TRANSLATORS: go to Previous Printer in list */
+ {ctrl('P'), 10, "^P", N_("Prev Printer")},
+ {ctrl('N'), 11, "^N", N_("Next Printer")},
+ {-2, 0, NULL, NULL},
+ /* TRANSLATORS: use Custom Print command */
+ {'c', 'c', "C", N_("CustomPrint")},
+ {KEY_UP, 10, "", ""},
+ {KEY_DOWN, 11, "", ""},
+ {-1, 0, NULL, NULL}};
+#define PREV_KEY 2
+#define NEXT_KEY 3
+#define CUSTOM_KEY 5
+#define UP_KEY 6
+#define DOWN_KEY 7
+
+ trailer = NULL;
+ init = NULL;
+ nick = NULL;
+ command[sizeof(command)-1] = '\0';
+
+ if(ps_global->VAR_PRINTER == NULL){
+ q_status_message(SM_ORDER | SM_DING, 3, 5,
+ "No printer has been chosen. Use SETUP on main menu to make choice.");
+ return(-1);
+ }
+
+ /* Is there just one print command available? */
+ just_one = (ps_global->printer_category!=3&&ps_global->printer_category!=2)
+ || (ps_global->printer_category == 2
+ && !(ps_global->VAR_STANDARD_PRINTER
+ && ps_global->VAR_STANDARD_PRINTER[0]
+ && ps_global->VAR_STANDARD_PRINTER[1]))
+ || (ps_global->printer_category == 3
+ && !(ps_global->VAR_PERSONAL_PRINT_COMMAND
+ && ps_global->VAR_PERSONAL_PRINT_COMMAND[0]
+ && ps_global->VAR_PERSONAL_PRINT_COMMAND[1]));
+
+ if(F_ON(F_CUSTOM_PRINT, ps_global))
+ ekey[CUSTOM_KEY].ch = 'c'; /* turn this key on */
+ else
+ ekey[CUSTOM_KEY].ch = -2; /* turn this key off */
+
+ if(just_one){
+ ekey[PREV_KEY].ch = -2; /* turn these keys off */
+ ekey[NEXT_KEY].ch = -2;
+ ekey[UP_KEY].ch = -2;
+ ekey[DOWN_KEY].ch = -2;
+ }
+ else{
+ ekey[PREV_KEY].ch = ctrl('P'); /* turn these keys on */
+ ekey[NEXT_KEY].ch = ctrl('N');
+ ekey[UP_KEY].ch = KEY_UP;
+ ekey[DOWN_KEY].ch = KEY_DOWN;
+ /*
+ * count how many printers in list and find the default in the list
+ */
+ if(ps_global->printer_category == 2)
+ list = ps_global->VAR_STANDARD_PRINTER;
+ else
+ list = ps_global->VAR_PERSONAL_PRINT_COMMAND;
+
+ for(i = 0; list[i]; i++)
+ if(strcmp(ps_global->VAR_PRINTER, list[i]) == 0)
+ cur_printer = i;
+
+ lastprinter = i - 1;
+ }
+
+ help = NO_HELP;
+ ps_global->mangled_footer = 1;
+
+ while(!done){
+ if(init)
+ fs_give((void **)&init);
+
+ if(trailer)
+ fs_give((void **)&trailer);
+
+ if(just_one)
+ printer = ps_global->VAR_PRINTER;
+ else
+ printer = list[cur_printer];
+
+ parse_printer(printer, &nick, &p, &init, &trailer, NULL, NULL);
+ strncpy(command, p, sizeof(command)-1);
+ command[sizeof(command)-1] = '\0';
+ fs_give((void **)&p);
+ /* TRANSLATORS: Print something1 using something2.
+ For example, Print configuration using printer three. */
+ snprintf(prompt, sizeof(prompt), _("Print %s using \"%s\" ? "),
+ desc ? desc : "",
+ *nick ? nick : command);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ fs_give((void **)&nick);
+
+ cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global),
+ ekey, 'y', 'x', help, RB_NORM);
+
+ switch(cmd){
+ case 'y':
+ q_status_message1(SM_ORDER, 0, 9,
+ "Printing with command \"%s\"", command);
+ done++;
+ break;
+
+ case 10:
+ cur_printer = (cur_printer>0)
+ ? (cur_printer-1)
+ : lastprinter;
+ break;
+
+ case 11:
+ cur_printer = (cur_printer<lastprinter)
+ ? (cur_printer+1)
+ : 0;
+ break;
+
+ case 'n':
+ case 'x':
+ done++;
+ break;
+
+ case 'c':
+ done++;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if(cmd == 'c'){
+ if(init)
+ fs_give((void **)&init);
+
+ if(trailer)
+ fs_give((void **)&trailer);
+
+ snprintf(prompt, sizeof(prompt), "Enter custom command : ");
+ prompt[sizeof(prompt)-1] = '\0';
+ command[0] = '\0';
+ rc = 1;
+ help = NO_HELP;
+ while(rc){
+ int flags = OE_APPEND_CURRENT;
+
+ rc = optionally_enter(command, -FOOTER_ROWS(ps_global), 0,
+ sizeof(command), prompt, NULL, help, &flags);
+
+ if(rc == 1){
+ cmd = 'x';
+ rc = 0;
+ }
+ else if(rc == 3)
+ help = (help == NO_HELP) ? h_custom_print : NO_HELP;
+ else if(rc == 0){
+ removing_trailing_white_space(command);
+ removing_leading_white_space(command);
+ q_status_message1(SM_ORDER, 0, 9,
+ "Printing with command \"%s\"", command);
+ }
+ }
+ }
+
+ if(cmd == 'x' || cmd == 'n'){
+ q_status_message(SM_ORDER, 0, 2, "Print cancelled");
+ if(init)
+ fs_give((void **)&init);
+
+ if(trailer)
+ fs_give((void **)&trailer);
+
+ return(-1);
+ }
+
+ display_message('x');
+
+ ps_global->print = (PRINT_S *)fs_get(sizeof(PRINT_S));
+ memset(ps_global->print, 0, sizeof(PRINT_S));
+
+ strncpy(aname, ANSI_PRINTER, sizeof(aname)-1);
+ aname[sizeof(aname)-1] = '\0';
+ strncat(aname, "-no-formfeed", sizeof(aname)-strlen(aname)-1);
+ strncpy(wname, WYSE_PRINTER, sizeof(wname)-1);
+ wname[sizeof(wname)-1] = '\0';
+ strncat(wname, "-no-formfeed", sizeof(wname)-strlen(wname)-1);
+ if(strucmp(command, ANSI_PRINTER) == 0
+ || strucmp(command, aname) == 0
+ || strucmp(command, WYSE_PRINTER) == 0
+ || strucmp(command, wname) == 0){
+ /*----------- Attached printer ---------*/
+ q_status_message(SM_ORDER, 0, 9,
+ "Printing to attached desktop printer...");
+ display_message('x');
+ xonxoff_proc(1); /* make sure XON/XOFF used */
+ crlf_proc(1); /* AND LF->CR xlation */
+ if(strucmp(command, ANSI_PRINTER) == 0
+ || strucmp(command, aname) == 0){
+ fputs("\033[5i", stdout);
+ ansi_off = 1;
+ }
+ else{
+ ansi_off = 0;
+ printf("%c", 18); /* aux on for wyse60,
+ Chuck Everett <ceverett@odessa.edu> */
+ }
+
+ ps_global->print->fp = stdout;
+ if(strucmp(command, ANSI_PRINTER) == 0
+ || strucmp(command, WYSE_PRINTER) == 0){
+ /* put formfeed at the end of the trailer string */
+ if(trailer){
+ int len = strlen(trailer);
+
+ fs_resize((void **)&trailer, len+2);
+ trailer[len] = '\f';
+ trailer[len+1] = '\0';
+ }
+ else
+ trailer = cpystr("\f");
+ }
+ }
+ else{
+ /*----------- Print by forking off a UNIX command ------------*/
+ dprint((4, "Printing using command \"%s\"\n",
+ command ? command : "?"));
+ ps_global->print->result = temp_nam(NULL, "pine_prt");
+ if(ps_global->print->result &&
+ (ps_global->print->pipe = open_system_pipe(command,
+ &ps_global->print->result, NULL,
+ PIPE_WRITE | PIPE_STDERR, 0,
+ pipe_callback, NULL))){
+ ps_global->print->fp = ps_global->print->pipe->out.f;
+ }
+ else{
+ if(ps_global->print->result){
+ our_unlink(ps_global->print->result);
+ fs_give((void **)&ps_global->print->result);
+ }
+
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "Error opening printer: %s",
+ error_description(errno));
+ dprint((2, "Error popening printer \"%s\"\n",
+ error_description(errno)));
+ if(init)
+ fs_give((void **)&init);
+
+ if(trailer)
+ fs_give((void **)&trailer);
+
+ return(-1);
+ }
+ }
+
+ ps_global->print->err = 0;
+ if(init){
+ if(*init)
+ fputs(init, ps_global->print->fp);
+
+ fs_give((void **)&init);
+ }
+
+ cb.cbuf[0] = '\0';
+ cb.cbufp = cb.cbuf;
+ cb.cbufend = cb.cbuf;
+#else /* _WINDOWS */
+ int status;
+ LPTSTR desclpt = NULL;
+
+ if(desc)
+ desclpt = utf8_to_lptstr(desc);
+
+ if (status = mswin_print_ready (0, desclpt)) {
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "Error starting print job: %s",
+ mswin_print_error(status));
+ if(desclpt)
+ fs_give((void **) &desclpt);
+
+ return(-1);
+ }
+
+ if(desclpt)
+ fs_give((void **) &desclpt);
+
+ q_status_message(SM_ORDER, 0, 9, "Printing to windows printer...");
+ display_message('x');
+
+ /* init print control structure */
+ ps_global->print = (PRINT_S *)fs_get(sizeof(PRINT_S));
+ memset(ps_global->print, 0, sizeof(PRINT_S));
+
+ ps_global->print->err = 0;
+#endif /* _WINDOWS */
+
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Close printer
+
+ If we're piping to a spooler close down the pipe and wait for the process
+to finish. If we're sending to an attached printer send the escape sequence.
+Also let the user know the result of the print
+ ----*/
+void
+close_printer(void)
+{
+#ifndef _WINDOWS
+ if(trailer){
+ if(*trailer)
+ fputs(trailer, ps_global->print->fp);
+
+ fs_give((void **)&trailer);
+ }
+
+ if(ps_global->print->fp == stdout) {
+ if(ansi_off)
+ fputs("\033[4i", stdout);
+ else
+ printf("%c", 20); /* aux off for wyse60 */
+
+ fflush(stdout);
+ if(F_OFF(F_PRESERVE_START_STOP, ps_global))
+ xonxoff_proc(0); /* turn off XON/XOFF */
+
+ crlf_proc(0); /* turn off CF->LF xlantion */
+ } else {
+ (void) close_system_pipe(&ps_global->print->pipe, NULL, pipe_callback);
+ display_output_file(ps_global->print->result, "PRINT", NULL, 1);
+ if(ps_global->print && ps_global->print->result)
+ fs_give((void **) &ps_global->print->result);
+ }
+#else /* _WINDOWS */
+ mswin_print_done();
+#endif /* _WINDOWS */
+
+ if(ps_global->print)
+ fs_give((void **) &ps_global->print);
+
+ q_status_message(SM_ASYNC, 0, 3, "Print command completed");
+ display_message('x');
+}
+
+
+/*----------------------------------------------------------------------
+ Print a single character, translate from UTF-8 to user's locale charset.
+
+ Args: c -- char to print
+ Returns: 1 on success, 0 on ps_global->print->err
+ ----*/
+int
+print_char(int c)
+{
+#ifndef _WINDOWS
+ int i, outchars;
+ unsigned char obuf[MAX(MB_LEN_MAX,32)];
+
+ if(!ps_global->print->err
+ && (outchars = utf8_to_locale(c, &cb, obuf, sizeof(obuf)))){
+ for(i = 0; i < outchars && !ps_global->print->err; i++)
+ if(putc(obuf[i], ps_global->print->fp) == EOF)
+ ps_global->print->err = 1;
+ }
+#else /* _WINDOWS */
+ if(!ps_global->print->err
+ && (ps_global->print->err = mswin_print_char_utf8(c)))
+ q_status_message1(SM_ORDER, 0, 9, "Print cancelled: %s",
+ mswin_print_error((unsigned short)ps_global->print->err));
+#endif /* _WINDOWS */
+
+ return(!ps_global->print->err);
+}
+
+
+/*----------------------------------------------------------------------
+ Send a line of text to the printer
+
+ Args: line -- Text to print
+
+ ----*/
+void
+print_text(char *line)
+{
+#ifndef _WINDOWS
+ int slen = strlen(line);
+
+ while(!ps_global->print->err && slen--)
+ if(print_char(*line++) == 0)
+ ps_global->print->err = 1;
+#else /* _WINDOWS */
+ if(!ps_global->print->err
+ && (ps_global->print->err = mswin_print_text_utf8(line)))
+ q_status_message1(SM_ORDER, 0, 9, "Print cancelled: %s",
+ mswin_print_error((unsigned short)ps_global->print->err));
+#endif /* _WINDOWS */
+}
+
+
+/*----------------------------------------------------------------------
+ printf style formatting with one arg for printer
+
+ Args: line -- The printf control string
+ a1 -- The 1st argument for printf
+ ----*/
+void
+print_text1(char *line, char *a1)
+{
+ char buf[64000];
+
+ if(!ps_global->print->err && snprintf(buf, sizeof(buf), line, a1) < 0)
+ ps_global->print->err = 1;
+ else
+ print_text(buf);
+}
diff --git a/alpine/osdep/print.h b/alpine/osdep/print.h
new file mode 100644
index 00000000..47e28eec
--- /dev/null
+++ b/alpine/osdep/print.h
@@ -0,0 +1,28 @@
+/*
+ * $Id: print.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_PRINT_INCLUDED
+#define PINE_OSDEP_PRINT_INCLUDED
+
+
+/* exported prototypes */
+int open_printer(char *);
+void close_printer(void);
+int print_char(int);
+void print_text(char *);
+void print_text1(char *, char *);
+
+
+#endif /* PINE_OSDEP_PRINT_INCLUDED */
diff --git a/alpine/osdep/resource.h b/alpine/osdep/resource.h
new file mode 100644
index 00000000..c52b54f9
--- /dev/null
+++ b/alpine/osdep/resource.h
@@ -0,0 +1 @@
+#include "../../pico/osdep/resource.h"
diff --git a/alpine/osdep/solquota b/alpine/osdep/solquota
new file mode 100644
index 00000000..348bad2a
--- /dev/null
+++ b/alpine/osdep/solquota
@@ -0,0 +1,174 @@
+static char *device_name();
+
+#include <fcntl.h>
+#include <sys/fs/ufs_quota.h>
+
+/*
+ * define the "quotactl" function as in Solaris 1, based on ioctl().
+ * By Marc Mazuhelli <mazu@dmi.usherb.ca>
+ * The "special" parameter is any file on the file system,
+ * not the block special device name as in Solaris 1.
+ * Thanks to veronica@solution.maths.unsw.edu.au who provided
+ * the idea and the basis for this function.
+ *
+ * [ Apparently quotactl used to exist in SunOS but no longer exists in ]
+ * [ Solaris. This is an equivalent. If you are running on a system ]
+ * [ which has quotactl, comment this routine out or use sunquota. ]
+ */
+
+int
+quotactl(int cmd, char *special, uid_t uid, struct dqblk * addr)
+{
+ struct quotctl op;
+ int fd = open(special, O_RDONLY);
+
+ if (fd < 0)
+ return -1;
+
+ op.op = cmd;
+ op.uid = uid;
+ op.addr = (caddr_t) addr;
+
+ if (ioctl(fd, Q_QUOTACTL, &op) < 0) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return (0);
+}
+
+
+/*----------------------------------------------------------------------
+ Return space left in disk quota on file system which given path is in.
+
+ Args: path - Path name of file or directory on file system of concern
+ over - pointer to flag that is set if the user is over quota
+
+ Returns: If *over = 0, the number of bytes free in disk quota as per
+ the soft limit.
+ If *over = 1, the number of bytes *over* quota.
+ -1 is returned on an error looking up quota
+ 0 is returned if there is no quota
+
+BUG: If there's more than 2.1Gb free this function will break
+ ----*/
+long
+disk_quota(path, over)
+ char *path;
+ int *over;
+{
+ static int no_quota = 0;
+ struct stat statx;
+ struct dqblk quotax;
+ long q;
+ char *dname;
+
+ if(no_quota)
+ return(0L); /* If no quota the first time, then none the second. */
+
+ dprint(5, (debugfile, "quota_check path: %s\n", path ? path : "?"));
+ if(stat(path, &statx) < 0) {
+ return(-1L);
+ }
+
+ *over = 0;
+ errno = 0;
+
+ dname = device_name(statx.st_dev);
+ if(dname == NULL)
+ return(-1L);
+
+ dprint(7, (debugfile, "Quota check: UID:%d device: %s\n",
+ getuid(), dname ? dname : "?"));
+ if(quotactl(Q_GETQUOTA, dname, getuid(), (char *)&quotax) < 0) {
+ dprint(5, (debugfile, "Quota failed : %s\n",
+ error_description(errno)));
+ return(-1L); /* Something went wrong */
+ }
+
+ dprint(5,(debugfile,"Quota: bsoftlimit:%d bhardlimit:%d curblock:%d\n",
+ quotax.dqb_bsoftlimit, quotax.dqb_bhardlimit, quotax.dqb_curblocks));
+
+ if(quotax.dqb_bsoftlimit == -1)
+ return(-1L);
+
+ q = (quotax.dqb_bsoftlimit - quotax.dqb_curblocks) * 512;
+
+ if(q < 0) {
+ q = -q;
+ *over = 1;
+ }
+ dprint(5, (debugfile, "disk_quota returning :%d, over:%d\n", q, *over));
+ return(q);
+}
+
+
+/*----------------------------------------------------------------------
+ * devNumToName
+ *
+ * This routine is here so that ex can get a device name to check
+ * disk quotas. One might wonder, why not use getmntent(), rather
+ * than read /etc/mtab in this crude way? The problem with getmntent
+ * is that it uses stdio, and ex/vi pointedly doesn't.
+ ----*/
+static char
+*device_name(st_devArg)
+ dev_t st_devArg;
+{
+#ifndef MTABNAME
+#define MTABNAME "/etc/mtab"
+#endif
+ char *mtab;
+ static char devName[48];
+ static char *answer = (char *) 0;
+ struct stat devStat;
+ static dev_t st_dev;
+ int nb, cur, bol;
+ char c;
+ int dname;
+
+ if (st_devArg == st_dev)
+ return answer;
+
+ mtab = read_file(MTABNAME);
+ if(mtab == NULL)
+ return((char *)NULL);
+
+ /* Initialize save data. */
+ st_dev = st_devArg;
+ answer = (char *) 0;
+ nb = strlen(mtab);
+
+ for (cur=bol=0, dname=1; cur < nb; ++cur) {
+
+ if (dname && (mtab[cur] <= ' ')) {
+ /* Space, tab or other such character has been found,
+ presumably marking the end of the device name string. */
+
+ dname = 0;
+ c = mtab[cur]; /* Save current character. */
+ mtab[cur] = 0; /* C zero-terminated string. */
+
+ /* Get device number, via stat(). If it's the right
+ number, copy the string and return its address. */
+ if (stat (&mtab[bol], &devStat) == 0) {
+ if (devStat.st_rdev == st_dev) {
+ if ((cur - bol + 1) < sizeof (devName)) {
+ strncpy (devName, &mtab[bol], sizeof(devName));
+ devName[sizeof(devName)-1] = '\0';
+ answer = &devName[0];
+ return(answer);
+ }
+ }
+ }
+ mtab[cur] = c;
+ }
+ if (mtab[cur] == '\n') {
+ dname = 1;
+ bol = cur + 1;
+ }
+ }
+ answer = NULL;
+
+ return(answer);
+}
diff --git a/alpine/osdep/sunquota b/alpine/osdep/sunquota
new file mode 100644
index 00000000..3528a1bb
--- /dev/null
+++ b/alpine/osdep/sunquota
@@ -0,0 +1,136 @@
+static char *device_name();
+
+/*----------------------------------------------------------------------
+ Return space left in disk quota on file system which given path is in.
+
+ Args: path - Path name of file or directory on file system of concern
+ over - pointer to flag that is set if the user is over quota
+
+ Returns: If *over = 0, the number of bytes free in disk quota as per
+ the soft limit.
+ If *over = 1, the number of bytes *over* quota.
+ -1 is returned on an error looking up quota
+ 0 is returned if there is no quota
+
+BUG: If there's more than 2.1Gb free this function will break
+ ----*/
+long
+disk_quota(path, over)
+ char *path;
+ int *over;
+{
+ static int no_quota = 0;
+ struct stat statx;
+ struct dqblk quotax;
+ long q;
+ char *dname;
+
+ if(no_quota)
+ return(0L); /* If no quota the first time, then none the second. */
+
+ dprint(5, (debugfile, "quota_check path: %s\n", path ? path : "?"));
+ if(stat(path, &statx) < 0) {
+ return(-1L);
+ }
+
+ *over = 0;
+ errno = 0;
+
+ dname = device_name(statx.st_dev);
+ if(dname == NULL)
+ return(-1L);
+
+ dprint(7, (debugfile, "Quota check: UID:%d device: %s\n",
+ getuid(), dname ? dname : "?"));
+ if(quotactl(Q_GETQUOTA, dname, getuid(), (char *)&quotax) < 0) {
+ dprint(5, (debugfile, "Quota failed : %s\n",
+ error_description(errno)));
+ return(-1L); /* Something went wrong */
+ }
+
+ dprint(5,(debugfile,"Quota: bsoftlimit:%d bhardlimit:%d curblock:%d\n",
+ quotax.dqb_bsoftlimit, quotax.dqb_bhardlimit, quotax.dqb_curblocks));
+
+ if(quotax.dqb_bsoftlimit == -1)
+ return(-1L);
+
+ q = (quotax.dqb_bsoftlimit - quotax.dqb_curblocks) * 512;
+
+ if(q < 0) {
+ q = -q;
+ *over = 1;
+ }
+ dprint(5, (debugfile, "disk_quota returning :%d, over:%d\n", q, *over));
+ return(q);
+}
+
+
+/*----------------------------------------------------------------------
+ * devNumToName
+ *
+ * This routine is here so that ex can get a device name to check
+ * disk quotas. One might wonder, why not use getmntent(), rather
+ * than read /etc/mtab in this crude way? The problem with getmntent
+ * is that it uses stdio, and ex/vi pointedly doesn't.
+ ----*/
+static char
+*device_name(st_devArg)
+ dev_t st_devArg;
+{
+#ifndef MTABNAME
+#define MTABNAME "/etc/mtab"
+#endif
+ char *mtab;
+ static char devName[48];
+ static char *answer = (char *) 0;
+ struct stat devStat;
+ static dev_t st_dev;
+ int nb, cur, bol;
+ char c;
+ int dname;
+
+ if (st_devArg == st_dev)
+ return answer;
+
+ mtab = read_file(MTABNAME);
+ if(mtab == NULL)
+ return((char *)NULL);
+
+ /* Initialize save data. */
+ st_dev = st_devArg;
+ answer = (char *) 0;
+ nb = strlen(mtab);
+
+ for (cur=bol=0, dname=1; cur < nb; ++cur) {
+
+ if (dname && (mtab[cur] <= ' ')) {
+ /* Space, tab or other such character has been found,
+ presumably marking the end of the device name string. */
+
+ dname = 0;
+ c = mtab[cur]; /* Save current character. */
+ mtab[cur] = 0; /* C zero-terminated string. */
+
+ /* Get device number, via stat(). If it's the right
+ number, copy the string and return its address. */
+ if (stat (&mtab[bol], &devStat) == 0) {
+ if (devStat.st_rdev == st_dev) {
+ if ((cur - bol + 1) < sizeof (devName)) {
+ strncpy (devName, &mtab[bol], sizeof(devName));
+ devName[sizeof(devName)-1] = '\0';
+ answer = &devName[0];
+ return(answer);
+ }
+ }
+ }
+ mtab[cur] = c;
+ }
+ if (mtab[cur] == '\n') {
+ dname = 1;
+ bol = cur + 1;
+ }
+ }
+ answer = NULL;
+
+ return(answer);
+}
diff --git a/alpine/osdep/termin.gen.c b/alpine/osdep/termin.gen.c
new file mode 100644
index 00000000..fb106be1
--- /dev/null
+++ b/alpine/osdep/termin.gen.c
@@ -0,0 +1,1252 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: termin.gen.c 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/osdep/color.h"
+
+#include "../../pith/charconv/utf8.h"
+
+#include "../../pith/debug.h"
+#include "../../pith/newmail.h"
+#include "../../pith/conf.h"
+#include "../../pith/busy.h"
+
+#include "../../pico/estruct.h"
+#include "../../pico/pico.h"
+#include "../../pico/keydefs.h"
+
+#include "../../pico/osdep/color.h"
+
+#include "../status.h"
+#include "../folder.h"
+#include "../keymenu.h"
+#include "../send.h"
+#include "../radio.h"
+#include "../busy.h"
+
+#ifdef _WINDOWS
+#include "../../pico/osdep/mswin.h"
+#include "termin.wnt.h"
+#include "termout.wnt.h"
+#else
+#include "termout.unx.h"
+#endif
+
+#include "termin.gen.h"
+#include "termout.gen.h"
+
+#include "../mailcmd.h"
+
+
+#ifdef _WINDOWS
+static int g_mc_row, g_mc_col;
+
+int pcpine_oe_cursor(int, long);
+#endif
+
+
+/*
+ * Generic tty input routines
+ */
+
+
+/*----------------------------------------------------------------------
+ Read a character from keyboard with timeout
+ Input: none
+
+ Result: Returns command read via read_char
+ Times out and returns a null command every so often
+
+ Calculates the timeout for the read, and does a few other house keeping
+things. The duration of the timeout is set in pine.c.
+ ----------------------------------------------------------------------*/
+UCS
+read_command(char **utf8str)
+{
+ int tm = 0, more_freq_timeo;
+ UCS ucs;
+ long dtime;
+ static unsigned char utf8buf[7];
+ unsigned char *newdestp;
+
+ /*
+ * timeo is the mail-check-interval. What we want to do (ignoring the
+ * messages_queued part) is timeout more often than timeo but only
+ * check for new mail every timeo or so seconds. The reason we want to
+ * timeout more often is so that we will have a chance to catch the user
+ * in an idle period where we can do a check_point(). Otherwise, with
+ * a default mail-check-interval, we are almost always calling newmail
+ * right after the user presses a key, making it the worst possible
+ * time to do a checkpoint.
+ */
+
+ more_freq_timeo = MIN(get_input_timeout(), IDLE_TIMEOUT);
+ if(more_freq_timeo == 0)
+ more_freq_timeo = IDLE_TIMEOUT;
+
+ cancel_busy_cue(-1);
+ tm = (messages_queued(&dtime) > 1) ? (int)dtime : more_freq_timeo;
+
+ if(utf8str)
+ *utf8str = NULL;
+
+ ucs = read_char(tm);
+ if(ucs != NO_OP_COMMAND && ucs != NO_OP_IDLE && ucs != KEY_RESIZE)
+ zero_new_mail_count();
+
+#ifdef BACKGROUND_POST
+ /*
+ * Any expired children to report on?
+ */
+ if(ps_global->post && ps_global->post->pid == 0){
+ int winner = 0;
+
+ if(ps_global->post->status < 0){
+ q_status_message(SM_ORDER | SM_DING, 3, 3, "Abysmal failure!");
+ }
+ else{
+ (void) pine_send_status(ps_global->post->status,
+ ps_global->post->fcc, tmp_20k_buf, SIZEOF_20KBUF,
+ &winner);
+ q_status_message(SM_ORDER | (winner ? 0 : SM_DING), 3, 3,
+ tmp_20k_buf);
+
+ }
+
+ if(!winner)
+ q_status_message(SM_ORDER, 0, 3,
+ "Re-send via \"Compose\" then \"Yes\" to \"Continue INTERRUPTED?\"");
+
+ if(ps_global->post->fcc)
+ fs_give((void **) &ps_global->post->fcc);
+
+ fs_give((void **) &ps_global->post);
+ }
+#endif
+
+ /*
+ * The character we get from read_char() is a UCS-4 char. Or it could be a special
+ * value like KEY_UP or NO_OP_IDLE or something similar. From here on out
+ * we're going to operate with UTF-8 internally. This is the point where we
+ * convert the UCS-4 input (actually whatever sort of input the user is typing
+ * was converted to UCS-4 first) to UTF-8. It's easy in this read_command
+ * case because if user types a non-ascii character as a command it's going to be
+ * an error. All commands are ascii. In order to present a reasonable error
+ * message we pass back the UTF-8 string to the caller.
+ */
+ if(ucs >= 0x80 && ucs < KEY_BASE){
+ /*
+ * User typed a character that is non-ascii. Convert it to
+ * UTF-8.
+ */
+ memset(utf8buf, 0, sizeof(utf8buf));
+ newdestp = utf8_put(utf8buf, (unsigned long) ucs);
+ if(newdestp - utf8buf > 1){ /* this should happen */
+ if(utf8str)
+ *utf8str = (char *) utf8buf;
+
+ dprint((9, "Read command: looks like user typed non-ascii command 0x%x %s: returning KEY_UTF8\n", ucs, pretty_command(ucs)));
+ ucs = KEY_UTF8;
+ }
+ else{
+ dprint((9, "Read command: looks like user typed unknown, non-ascii command 0x%x %s: returning KEY_UNKNOWN\n", ucs, pretty_command(ucs)));
+ ucs = KEY_UNKNOWN; /* best we can do, shouldn't happen */
+ }
+ }
+ else{
+ dprint((9, "Read command returning: 0x%x %s\n", ucs, pretty_command(ucs)));
+ }
+
+ return(ucs);
+}
+
+
+int
+read_command_prep()
+{
+ int i;
+ char *fname;
+ MAILSTREAM *m;
+
+ /*
+ * Before we sniff at the input queue, make sure no external event's
+ * changed our picture of the message sequence mapping. If so,
+ * recalculate the dang thing and run thru whatever processing loop
+ * we're in again...
+ */
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)
+ && sp_expunge_count(m)){
+ fname = STREAMNAME(m);
+ q_status_message3(SM_ORDER, 3, 3,
+ "%s message%s expunged from folder \"%s\"",
+ long2string(sp_expunge_count(m)),
+ plural(sp_expunge_count(m)),
+ pretty_fn(fname));
+ sp_set_expunge_count(m, 0L);
+ display_message('x');
+ }
+ }
+
+ if(sp_mail_box_changed(ps_global->mail_stream)
+ && sp_new_mail_count(ps_global->mail_stream)){
+ dprint((2, "Noticed %ld new msgs! \n",
+ sp_new_mail_count(ps_global->mail_stream)));
+ return(FALSE); /* cycle thru so caller can update */
+ }
+
+ return(TRUE);
+}
+
+
+/*----------------------------------------------------------------------
+ Prompt user for a string in status line with various options
+
+ Args: utf8string -- the buffer result is returned in, and original string (if
+ any) is passed in.
+ y_base -- y position on screen to start on. 0,0 is upper left
+ negative numbers start from bottom
+ x_base -- column position on screen to start on. 0,0 is upper left
+ utf8string_size -- Length of utf8string buffer
+ utf8prompt -- The string to prompt with
+ escape_list -- pointer to array of ESCKEY_S's. input chars matching
+ those in list return value from list.
+ help -- Array of strings for help text in bottom screen lines
+ flags -- pointer (because some are return values) to flags
+ OE_USER_MODIFIED - Set on return if user modified buffer
+ OE_DISALLOW_CANCEL - No cancel in menu.
+ OE_DISALLOW_HELP - No help in menu.
+ OE_KEEP_TRAILING_SPACE - Allow trailing space.
+ OE_SEQ_SENSITIVE - Caller is sensitive to sequence
+ number changes.
+ OE_APPEND_CURRENT - String should not be truncated
+ before accepting user input.
+ OE_PASSWD - Don't echo on screen.
+
+ Result: editing input string
+ returns -1 unexpected errors
+ returns 0 normal entry typed (editing and return or PF2)
+ returns 1 typed ^C or PF2 (cancel)
+ returns 3 typed ^G or PF1 (help)
+ returns 4 typed ^L for a screen redraw
+
+ WARNING: Care is required with regard to the escape_list processing.
+ The passed array is terminated with an entry that has ch = -1.
+ Function key labels and key strokes need to be setup externally!
+ Traditionally, a return value of 2 is used for ^T escapes.
+
+ Unless in escape_list, tabs are trapped by isprint().
+This allows near full weemacs style editing in the line
+ ^A beginning of line
+ ^E End of line
+ ^R Redraw line
+ ^G Help
+ ^F forward
+ ^B backward
+ ^D delete
+----------------------------------------------------------------------*/
+
+int
+optionally_enter(char *utf8string, int y_base, int x_base, int utf8string_size,
+ char *utf8prompt, ESCKEY_S *escape_list, HelpType help, int *flags)
+{
+ UCS *string = NULL, ucs;
+ size_t string_size;
+ UCS *s2;
+ UCS *saved_original = NULL;
+ char *candidate;
+ UCS *kill_buffer = NULL;
+ UCS *k, *kb;
+ int field_pos; /* offset into array dline.vl */
+ int i, j, return_v, cols, prompt_width, too_thin,
+ real_y_base, km_popped, passwd;
+ char **help_text;
+ long fkey_table[12];
+ struct key_menu *km;
+ bitmap_t bitmap;
+ COLOR_PAIR *lastc = NULL, *promptc = NULL;
+ struct variable *vars = ps_global->vars;
+ struct display_line dline;
+#ifdef _WINDOWS
+ int cursor_shown;
+#endif
+
+ dprint((5, "=== optionally_enter called ===\n"));
+ dprint((9, "utf8string:\"%s\" y:%d x:%d length: %d append: %d\n",
+ utf8string ? utf8string : "",
+ x_base, y_base, utf8string_size,
+ (flags && *flags & OE_APPEND_CURRENT)));
+ dprint((9, "passwd:%d utf8prompt:\"%s\" label:\"%s\"\n",
+ (flags && *flags & OE_PASSWD_NOAST) ? 10 :
+ (flags && *flags & OE_PASSWD) ? 1 : 0,
+ utf8prompt ? utf8prompt : "",
+ (escape_list && escape_list[0].ch != -1 && escape_list[0].label)
+ ? escape_list[0].label: ""));
+
+ if(!ps_global->ttyo)
+ return(pre_screen_config_opt_enter(utf8string, utf8string_size, utf8prompt,
+ escape_list, help, flags));
+
+#ifdef _WINDOWS
+ if (mswin_usedialog ())
+ return(win_dialog_opt_enter(utf8string, utf8string_size, utf8prompt,
+ escape_list, help, flags));
+#endif
+
+
+ /*
+ * Utf8string comes in as UTF-8. We'll convert it to a UCS-4 array and operate on
+ * that array, then convert it back before returning. Utf8string_size is the size
+ * of the utf8string array but that doesn't help us much for the array we need to
+ * operate on here. We'll just allocate a big array and then cut it off when
+ * sending it back.
+ *
+ * This should come before the specialized calls above but those aren't
+ * converted to use UCS-4 yet.
+ */
+ string = utf8_to_ucs4_cpystr(utf8string);
+ dline.vused = ucs4_strlen(string);
+
+ string_size = (2 * MAX(utf8string_size,dline.vused) + 100);
+ fs_resize((void **) &string, string_size * sizeof(UCS));
+
+ suspend_busy_cue();
+ cols = ps_global->ttyo->screen_cols;
+ prompt_width = utf8_width(utf8prompt);
+ too_thin = 0;
+ km_popped = 0;
+ if(y_base > 0)
+ real_y_base = y_base;
+ else{
+ real_y_base = y_base + ps_global->ttyo->screen_rows;
+ real_y_base = MAX(real_y_base, 0);
+ }
+
+ flush_ordered_messages();
+ mark_status_dirty();
+
+ if(flags && *flags & OE_APPEND_CURRENT) /* save a copy in case of cancel */
+ saved_original = ucs4_cpystr(string);
+
+ /*
+ * build the function key mapping table, skipping predefined keys...
+ */
+ memset(fkey_table, NO_OP_COMMAND, 12 * sizeof(long));
+ for(i = 0, j = 0; escape_list && escape_list[i].ch != -1 && i+j < 12; i++){
+ if(i+j == OE_HELP_KEY)
+ j++;
+
+ if(i+j == OE_CANCEL_KEY)
+ j++;
+
+ if(i+j == OE_ENTER_KEY)
+ j++;
+
+ fkey_table[i+j] = escape_list[i].ch;
+ }
+
+ /* assumption that HelpType is char ** */
+ help_text = help;
+ if(help_text){ /*---- Show help text -----*/
+ int width = ps_global->ttyo->screen_cols - x_base;
+
+ if(FOOTER_ROWS(ps_global) == 1){
+ km_popped++;
+ FOOTER_ROWS(ps_global) = 3;
+ clearfooter(ps_global);
+
+ y_base = -3;
+ real_y_base = y_base + ps_global->ttyo->screen_rows;
+ }
+
+ for(j = 0; j < 2 && help_text[j]; j++){
+ MoveCursor(real_y_base + 1 + j, x_base);
+ CleartoEOLN();
+
+ if(width < utf8_width(help_text[j])){
+ char *tmp = cpystr(help_text[j]);
+ (void) utf8_truncate(tmp, width);
+ PutLine0(real_y_base + 1 + j, x_base, tmp);
+ fs_give((void **) &tmp);
+ }
+ else
+ PutLine0(real_y_base + 1 + j, x_base, help_text[j]);
+ }
+ }
+ else{
+ clrbitmap(bitmap);
+ clrbitmap((km = &oe_keymenu)->bitmap); /* force formatting */
+ if(!(flags && (*flags) & OE_DISALLOW_HELP))
+ setbitn(OE_HELP_KEY, bitmap);
+
+ setbitn(OE_ENTER_KEY, bitmap);
+ if(!(flags && (*flags) & OE_DISALLOW_CANCEL))
+ setbitn(OE_CANCEL_KEY, bitmap);
+
+ setbitn(OE_CTRL_T_KEY, bitmap);
+
+ /*---- Show the usual possible keys ----*/
+ for(i=0,j=0; escape_list && escape_list[i].ch != -1 && i+j < 12; i++){
+ if(i+j == OE_HELP_KEY)
+ j++;
+
+ if(i+j == OE_CANCEL_KEY)
+ j++;
+
+ if(i+j == OE_ENTER_KEY)
+ j++;
+
+ oe_keymenu.keys[i+j].label = escape_list[i].label;
+ oe_keymenu.keys[i+j].name = escape_list[i].name;
+ setbitn(i+j, bitmap);
+ }
+
+ for(i = i+j; i < 12; i++)
+ if(!(i == OE_HELP_KEY || i == OE_ENTER_KEY || i == OE_CANCEL_KEY))
+ oe_keymenu.keys[i].name = NULL;
+
+ draw_keymenu(km, bitmap, cols, 1-FOOTER_ROWS(ps_global), 0, FirstMenu);
+ }
+
+ if(pico_usingcolor() && VAR_PROMPT_FORE_COLOR &&
+ VAR_PROMPT_BACK_COLOR &&
+ pico_is_good_color(VAR_PROMPT_FORE_COLOR) &&
+ pico_is_good_color(VAR_PROMPT_BACK_COLOR)){
+ lastc = pico_get_cur_color();
+ if(lastc){
+ promptc = new_color_pair(VAR_PROMPT_FORE_COLOR,
+ VAR_PROMPT_BACK_COLOR);
+ (void)pico_set_colorp(promptc, PSC_NONE);
+ }
+ }
+ else
+ StartInverse();
+
+ /*
+ * if display length isn't wide enough to support input,
+ * shorten up the prompt...
+ */
+ if((dline.dwid = cols - (x_base + prompt_width)) < MIN_OPT_ENT_WIDTH){
+ char *p;
+ unsigned got_width;
+
+ /*
+ * Scoot prompt pointer forward at least (MIN_OPT_ENT_WIDTH - dline.dwid) screencells.
+ */
+ p = utf8_count_forw_width(utf8prompt, MIN_OPT_ENT_WIDTH-dline.dwid, &got_width);
+ if(got_width < MIN_OPT_ENT_WIDTH-dline.dwid)
+ p = utf8_count_forw_width(utf8prompt, MIN_OPT_ENT_WIDTH+1-dline.dwid, &got_width);
+
+ if(p){
+ prompt_width = utf8_width(p);
+ dline.dwid = cols - (x_base + prompt_width);
+ utf8prompt = p;
+ }
+ }
+
+ /*
+ * How many UCS-4 characters will we need to make up the width dwid? It could be
+ * unlimited because of zero-width characters, I suppose, but realistically it
+ * isn't going to be much more than dwid.
+ */
+ dline.dlen = 2 * dline.dwid + 100;
+
+ dline.dl = (UCS *) fs_get(dline.dlen * sizeof(UCS));
+ dline.olddl = (UCS *) fs_get(dline.dlen * sizeof(UCS));
+ memset(dline.dl, 0, dline.dlen * sizeof(UCS));
+ memset(dline.olddl, 0, dline.dlen * sizeof(UCS));
+
+ dline.movecursor = MoveCursor;
+ dline.writechar = Writewchar;
+
+ dline.row = real_y_base;
+ dline.col = x_base + prompt_width;
+
+ dline.vl = string;
+ dline.vlen = --string_size; /* -1 for terminating zero */
+ dline.vbase = field_pos = 0;
+
+#ifdef _WINDOWS
+ cursor_shown = mswin_showcaret(1);
+#endif
+
+ PutLine0(real_y_base, x_base, utf8prompt);
+
+ /*
+ * If appending, position field_pos at end of input.
+ */
+ if(flags && *flags & OE_APPEND_CURRENT)
+ while(string[field_pos])
+ field_pos++;
+
+ passwd = (flags && *flags & OE_PASSWD_NOAST) ? 10 :
+ (flags && *flags & OE_PASSWD) ? 1 : 0;
+ line_paint(field_pos, &dline, &passwd);
+
+ /*----------------------------------------------------------------------
+ The main loop
+ loops until someone sets the return_v.
+ ----------------------------------------------------------------------*/
+ return_v = -10;
+
+ while(return_v == -10) {
+
+#ifdef MOUSE
+ mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
+ register_mfunc(mouse_in_content,
+ real_y_base, x_base + prompt_width,
+ real_y_base, ps_global->ttyo->screen_cols);
+#endif
+#ifdef _WINDOWS
+ mswin_allowpaste(MSWIN_PASTE_LINE);
+ g_mc_row = real_y_base;
+ g_mc_col = x_base + prompt_width;
+ mswin_mousetrackcallback(pcpine_oe_cursor);
+#endif
+
+ /* Timeout 10 min to keep imap mail stream alive */
+ ps_global->conceal_sensitive_debugging = passwd ? 1 : 0;
+ ucs = read_char(600);
+ ps_global->conceal_sensitive_debugging = 0;
+
+#ifdef MOUSE
+ clear_mfunc(mouse_in_content);
+#endif
+#ifdef _WINDOWS
+ mswin_allowpaste(MSWIN_PASTE_DISABLE);
+ mswin_mousetrackcallback(NULL);
+#endif
+
+ /*
+ * Don't want to intercept all characters if typing in passwd.
+ * We select an ad hoc set that we will catch and let the rest
+ * through. We would have caught the set below in the big switch
+ * but we skip the switch instead. Still catch things like ^K,
+ * DELETE, ^C, RETURN.
+ */
+ if(passwd)
+ switch(ucs){
+ case ctrl('F'):
+ case KEY_RIGHT:
+ case ctrl('B'):
+ case KEY_LEFT:
+ case ctrl('U'):
+ case ctrl('A'):
+ case KEY_HOME:
+ case ctrl('E'):
+ case KEY_END:
+ case TAB:
+ goto ok_for_passwd;
+ }
+
+ if(too_thin && ucs != KEY_RESIZE && ucs != ctrl('Z') && ucs != ctrl('C'))
+ goto bleep;
+
+ switch(ucs){
+
+ /*--------------- KEY RIGHT ---------------*/
+ case ctrl('F'):
+ case KEY_RIGHT:
+ if(field_pos >= string_size || string[field_pos] == '\0')
+ goto bleep;
+
+ line_paint(++field_pos, &dline, &passwd);
+ break;
+
+ /*--------------- KEY LEFT ---------------*/
+ case ctrl('B'):
+ case KEY_LEFT:
+ if(field_pos <= 0)
+ goto bleep;
+
+ line_paint(--field_pos, &dline, &passwd);
+ break;
+
+ /*-------------------- WORD SKIP --------------------*/
+ case ctrl('@'):
+ /*
+ * Note: read_char *can* return NO_OP_COMMAND which is
+ * the def'd with the same value as ^@ (NULL), BUT since
+ * read_char has a big timeout (>25 secs) it won't.
+ */
+
+ /* skip thru current word */
+ while(string[field_pos]
+ && isalnum((unsigned char) string[field_pos]))
+ field_pos++;
+
+ /* skip thru current white space to next word */
+ while(string[field_pos]
+ && !isalnum((unsigned char) string[field_pos]))
+ field_pos++;
+
+ line_paint(field_pos, &dline, &passwd);
+ break;
+
+ /*-------------------- RETURN --------------------*/
+ case PF4:
+ if(F_OFF(F_USE_FK,ps_global)) goto bleep;
+ case ctrl('J'):
+ case ctrl('M'):
+ return_v = 0;
+ break;
+
+ /*-------------------- Destructive backspace --------------------*/
+ case '\177': /* DEL */
+ case ctrl('H'):
+ /* Try and do this with by telling the terminal to delete a
+ a character. If that fails, then repaint the rest of the
+ line, acheiving the same much less efficiently
+ */
+ if(field_pos <= 0)
+ goto bleep;
+
+ field_pos--;
+ /* drop thru to pull line back ... */
+
+ /*-------------------- Delete char --------------------*/
+ case ctrl('D'):
+ case KEY_DEL:
+ if(field_pos >= string_size || !string[field_pos])
+ goto bleep;
+
+ dline.vused--;
+ for(s2 = &string[field_pos]; *s2 != 0; s2++)
+ *s2 = s2[1];
+
+ *s2 = 0; /* Copy last NULL */
+ line_paint(field_pos, &dline, &passwd);
+ if(flags) /* record change if requested */
+ *flags |= OE_USER_MODIFIED;
+
+ break;
+
+ /*--------------- Kill line -----------------*/
+ case ctrl('K'):
+ if(kill_buffer != NULL)
+ fs_give((void **) &kill_buffer);
+
+ if(field_pos != 0 || string[0]){
+ if(!passwd && F_ON(F_DEL_FROM_DOT, ps_global))
+ dline.vused -= ucs4_strlen(&string[i = field_pos]);
+ else
+ dline.vused = i = 0;
+
+ kill_buffer = ucs4_cpystr(&string[field_pos = i]);
+ string[field_pos] = '\0';
+ line_paint(field_pos, &dline, &passwd);
+ if(flags) /* record change if requested */
+ *flags |= OE_USER_MODIFIED;
+ }
+
+ break;
+
+ /*------------------- Undelete line --------------------*/
+ case ctrl('U'):
+ if(kill_buffer == NULL)
+ goto bleep;
+
+ /* Make string so it will fit */
+ kb = ucs4_cpystr(kill_buffer);
+ if(ucs4_strlen(kb) + ucs4_strlen(string) > string_size)
+ kb[string_size - ucs4_strlen(string)] = '\0';
+
+ if(string[field_pos] == '\0') {
+ /*--- adding to the end of the string ----*/
+ for(k = kb; *k; k++)
+ string[field_pos++] = *k;
+
+ string[field_pos] = '\0';
+ }
+ else{
+ int shift;
+
+ shift = ucs4_strlen(kb);
+
+ /* shift field_pos ... end to right */
+ for(k = &string[field_pos] + ucs4_strlen(&string[field_pos]);
+ k >= &string[field_pos]; k--)
+ *(k+shift) = *k;
+
+ for(k = kb; *k; k++)
+ string[field_pos++] = *k;
+ }
+
+ if(*kb && flags) /* record change if requested */
+ *flags |= OE_USER_MODIFIED;
+
+ dline.vused = ucs4_strlen(string);
+ fs_give((void **) &kb);
+ line_paint(field_pos, &dline, &passwd);
+ break;
+
+ /*-------------------- Interrupt --------------------*/
+ case ctrl('C'): /* ^C */
+ if(F_ON(F_USE_FK,ps_global) || (flags && ((*flags) & OE_DISALLOW_CANCEL)))
+ goto bleep;
+
+ goto cancel;
+
+ case PF2:
+ if(F_OFF(F_USE_FK,ps_global) || (flags && ((*flags) & OE_DISALLOW_CANCEL)))
+ goto bleep;
+
+ cancel:
+ return_v = 1;
+ if(saved_original){
+ for(i = 0; saved_original[i]; i++)
+ string[i] = saved_original[i];
+
+ string[i] = 0;
+ }
+
+ break;
+
+ case ctrl('A'):
+ case KEY_HOME:
+ /*-------------------- Start of line -------------*/
+ line_paint(field_pos = 0, &dline, &passwd);
+ break;
+
+ case ctrl('E'):
+ case KEY_END:
+ /*-------------------- End of line ---------------*/
+ line_paint(field_pos = dline.vused, &dline, &passwd);
+ break;
+
+ /*-------------------- Help --------------------*/
+ case ctrl('G') :
+ case PF1:
+ if(flags && ((*flags) & OE_DISALLOW_HELP))
+ goto bleep;
+ else if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
+ km_popped++;
+ FOOTER_ROWS(ps_global) = 3;
+ clearfooter(ps_global);
+ if(lastc)
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ else
+ EndInverse();
+
+ draw_keymenu(km, bitmap, cols, 1-FOOTER_ROWS(ps_global),
+ 0, FirstMenu);
+
+ if(promptc)
+ (void)pico_set_colorp(promptc, PSC_NONE);
+ else
+ StartInverse();
+
+ mark_keymenu_dirty();
+ y_base = -3;
+ dline.row = real_y_base = y_base + ps_global->ttyo->screen_rows;
+ PutLine0(real_y_base, x_base, utf8prompt);
+ memset(dline.dl, 0, dline.dlen * sizeof(UCS));
+ memset(dline.olddl, 0, dline.dlen * sizeof(UCS));
+ line_paint(field_pos, &dline, &passwd);
+ break;
+ }
+
+ if(FOOTER_ROWS(ps_global) > 1){
+ mark_keymenu_dirty();
+ return_v = 3;
+ }
+ else
+ goto bleep;
+
+ break;
+
+
+#ifdef MOUSE
+ /* Mouse support untested in pine 5.00 */
+ case KEY_MOUSE :
+ {
+ MOUSEPRESS mp;
+ int w;
+
+ mouse_get_last (NULL, &mp);
+
+ switch(mp.button){
+ case M_BUTTON_LEFT : /* position cursor */
+ mp.col -= dline.col;
+
+ /*
+ * We have to figure out which character is under the cursor.
+ * This is complicated by the fact that characters may
+ * be other than one cell wide.
+ */
+
+ /* the -1 is for the '<' when text is offscreen left */
+ w = (dline.vbase > 0) ? mp.col-1 : mp.col;
+
+ if(mp.col <= 0)
+ field_pos = dline.vbase - 1;
+ else{
+ if(dline.vused <= dline.vbase
+ || ucs4_str_width_a_to_b(dline.vl,dline.vbase,dline.vused-1) <= w)
+ field_pos = dline.vused;
+ else{
+ /*
+ * Find index of 1st character that causes the
+ * width to be > w.
+ */
+ for(i = 0;
+ ucs4_str_width_a_to_b(dline.vl,dline.vbase,dline.vbase+i) <= w;
+ i++)
+ ;
+
+ field_pos = dline.vbase + i;
+ }
+ }
+
+ field_pos = MIN(MAX(field_pos, 0), dline.vused);
+
+ /* just allow line_paint to choose vbase */
+ line_paint(field_pos, &dline, &passwd);
+ break;
+
+ case M_BUTTON_RIGHT :
+#ifdef _WINDOWS
+
+ /*
+ * Same as M_BUTTON_LEFT except we paste in text after
+ * moving the cursor.
+ */
+
+ mp.col -= dline.col;
+
+ /* the -1 is for the '<' when text is offscreen left */
+ w = (dline.vbase > 0) ? mp.col-1 : mp.col;
+
+ if(mp.col <= 0)
+ field_pos = dline.vbase - 1;
+ else{
+ if(dline.vused <= dline.vbase
+ || ucs4_str_width_a_to_b(dline.vl,dline.vbase,dline.vused-1) <= w)
+ field_pos = dline.vused;
+ else{
+ /*
+ * Find index of 1st character that causes the
+ * width to be > w.
+ */
+ for(i = 0;
+ ucs4_str_width_a_to_b(dline.vl,dline.vbase,dline.vbase+i) <= w;
+ i++)
+ ;
+
+ field_pos = dline.vbase + i;
+ }
+ }
+
+ field_pos = MIN(MAX(field_pos, 0), dline.vused);
+
+ line_paint(field_pos, &dline, &passwd);
+
+ mswin_allowpaste(MSWIN_PASTE_LINE);
+ mswin_paste_popup();
+ mswin_allowpaste(MSWIN_PASTE_DISABLE);
+ break;
+#endif
+
+ case M_BUTTON_MIDDLE : /* NO-OP for now */
+ default: /* just ignore */
+ break;
+ }
+ }
+
+ break;
+#endif
+
+
+ case NO_OP_IDLE:
+ /*
+ * Keep mail stream alive by checking for new mail.
+ * If we're asking for a password in a login prompt
+ * we don't want to check for new_mail because the
+ * new mail check might be what got us here in the first
+ * place (because of a filter trying to save a message).
+ * If we need to wait for the user to come back then
+ * the caller will just have to deal with the failure
+ * to login.
+ */
+ i = -1;
+ if(!ps_global->no_newmail_check_from_optionally_enter)
+ i = new_mail(0, 2, NM_DEFER_SORT);
+
+ if(sp_expunge_count(ps_global->mail_stream) &&
+ flags && ((*flags) & OE_SEQ_SENSITIVE))
+ goto cancel;
+
+ if(i < 0){
+ line_paint(field_pos, &dline, &passwd);
+ break; /* no changes, get on with life */
+ }
+ /* Else fall into redraw */
+
+ /*-------------------- Redraw --------------------*/
+ case ctrl('L'):
+ /*---------------- re size ----------------*/
+ case KEY_RESIZE:
+
+ dline.row = real_y_base = y_base > 0 ? y_base :
+ y_base + ps_global->ttyo->screen_rows;
+ if(lastc)
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ else
+ EndInverse();
+
+ ClearScreen();
+ redraw_titlebar();
+ if(ps_global->redrawer != (void (*)(void))NULL)
+ (*ps_global->redrawer)();
+
+ redraw_keymenu();
+ if(promptc)
+ (void)pico_set_colorp(promptc, PSC_NONE);
+ else
+ StartInverse();
+
+ PutLine0(real_y_base, x_base, utf8prompt);
+ cols = ps_global->ttyo->screen_cols;
+ too_thin = 0;
+ if(cols < x_base + prompt_width + 4){
+ Writechar(BELL, 0);
+ PutLine0(real_y_base, 0, "Screen's too thin. Ouch!");
+ too_thin = 1;
+ }
+ else{
+ dline.col = x_base + prompt_width;
+ dline.dwid = cols - (x_base + prompt_width);
+ dline.dlen = 2 * dline.dwid + 100;
+ fs_resize((void **) &dline.dl, (size_t) dline.dlen * sizeof(UCS));
+ fs_resize((void **) &dline.olddl, (size_t) dline.dlen * sizeof(UCS));
+ memset(dline.dl, 0, dline.dlen * sizeof(UCS));
+ memset(dline.olddl, 0, dline.dlen * sizeof(UCS));
+ line_paint(field_pos, &dline, &passwd);
+ }
+
+ fflush(stdout);
+
+ dprint((9,
+ "optionally_enter RESIZE new_cols:%d too_thin: %d\n",
+ cols, too_thin));
+ break;
+
+ case PF3 : /* input to potentially remap */
+ case PF5 :
+ case PF6 :
+ case PF7 :
+ case PF8 :
+ case PF9 :
+ case PF10 :
+ case PF11 :
+ case PF12 :
+ if(F_ON(F_USE_FK,ps_global)
+ && fkey_table[ucs - PF1] != NO_OP_COMMAND)
+ ucs = fkey_table[ucs - PF1]; /* remap function key input */
+
+ default:
+ if(escape_list){ /* in the escape key list? */
+ for(j=0; escape_list[j].ch != -1; j++){
+ if(escape_list[j].ch == ucs){
+ return_v = escape_list[j].rval;
+ break;
+ }
+ }
+
+ if(return_v != -10)
+ break;
+ }
+
+ if(ucs < 0x80 && FILTER_THIS((unsigned char) ucs)){
+ bleep:
+ putc(BELL, stdout);
+ continue;
+ }
+
+ ok_for_passwd:
+ /*--- Insert a character -----*/
+ if(dline.vused >= string_size)
+ goto bleep;
+
+ /*---- extending the length of the string ---*/
+ for(s2 = &string[++dline.vused]; s2 - string > field_pos; s2--)
+ *s2 = *(s2-1);
+
+ string[field_pos++] = ucs;
+ line_paint(field_pos, &dline, &passwd);
+ if(flags) /* record change if requested */
+ *flags |= OE_USER_MODIFIED;
+
+ } /*---- End of switch on char ----*/
+ }
+
+#ifdef _WINDOWS
+ if(!cursor_shown)
+ mswin_showcaret(0);
+#endif
+
+ if(dline.dl)
+ fs_give((void **) &dline.dl);
+
+ if(dline.olddl)
+ fs_give((void **) &dline.olddl);
+
+ if(saved_original)
+ fs_give((void **) &saved_original);
+
+ if(kill_buffer)
+ fs_give((void **) &kill_buffer);
+
+ /*
+ * Change string back into UTF-8.
+ */
+ candidate = ucs4_to_utf8_cpystr(string);
+
+ if(string)
+ fs_give((void **) &string);
+
+ if(candidate){
+ strncpy(utf8string, candidate, utf8string_size);
+ utf8string[utf8string_size-1] = '\0';
+ fs_give((void **) &candidate);
+ }
+
+ if (!(flags && (*flags) & OE_KEEP_TRAILING_SPACE))
+ removing_trailing_white_space(utf8string);
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ if(promptc)
+ free_color_pair(&promptc);
+ }
+ else
+ EndInverse();
+
+ MoveCursor(real_y_base, x_base); /* Move the cursor to show we're done */
+ fflush(stdout);
+ resume_busy_cue(0);
+ if(km_popped){
+ FOOTER_ROWS(ps_global) = 1;
+ clearfooter(ps_global);
+ ps_global->mangled_body = 1;
+ }
+
+ return(return_v);
+}
+
+
+/*----------------------------------------------------------------------
+ Check to see if the given command is reasonably valid
+
+ Args: ch -- the character to check
+
+ Result: A valid command is returned, or a well know bad command is returned.
+
+ ---*/
+UCS
+validatekeys(UCS ch)
+{
+ if(F_ON(F_USE_FK,ps_global)){
+ if(ch >= 'a' && ch <= 'z')
+ return(KEY_JUNK);
+ }
+ else{
+ if(ch >= PF1 && ch <= PF12)
+ return(KEY_JUNK);
+ }
+
+ return(ch);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Prepend config'd commands to keyboard input
+
+ Args: ch -- pointer to storage for returned command
+
+ Returns: TRUE if we're passing back a useful command, FALSE otherwise
+
+ ---*/
+int
+process_config_input(UCS *ch)
+{
+ static char firsttime = (char) 1;
+ int c;
+ unsigned long octets_so_far, remaining_octets, ret = 0;
+ unsigned char *inputp;
+ UCS ucs;
+ unsigned char inputbuf[20];
+
+ /* commands in config file */
+ if(ps_global->initial_cmds && *ps_global->initial_cmds) {
+ /*
+ * There are a few commands that may require keyboard input before
+ * we enter the main command loop. That input should be interactive,
+ * not from our list of initial keystrokes.
+ */
+ if(ps_global->dont_use_init_cmds)
+ return(ret);
+
+ c = *ps_global->initial_cmds++;
+
+ /*
+ * Use enough bytes to make up a character and convert it to UCS-4.
+ */
+ if(c < 0x80 || c > KEY_BASE){
+ *ch = (UCS) c;
+ ret = 1;
+ }
+ else{
+ memset(inputbuf, 0, sizeof(inputbuf));
+ inputbuf[0] = (0xff & c);
+ octets_so_far = 1;
+
+ while(!ret){
+ remaining_octets = octets_so_far;
+ inputp = inputbuf;
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+ switch(ucs){
+ case U8G_ENDSTRG: /* incomplete character, wait */
+ case U8G_ENDSTRI: /* incomplete character, wait */
+ if(!*ps_global->initial_cmds || octets_so_far >= sizeof(inputbuf)){
+ *ch = BADESC;
+ ret = 1;
+ }
+ else
+ inputbuf[octets_so_far++] = (0xff & *ps_global->initial_cmds++);
+
+ break;
+
+ default:
+ if(ucs & U8G_ERROR || ucs == UBOGON)
+ *ch = BADESC;
+ else
+ *ch = ucs;
+
+ ret = 1;
+ break;
+ }
+ }
+ }
+
+ if(!*ps_global->initial_cmds && ps_global->free_initial_cmds){
+ fs_give((void **) &ps_global->free_initial_cmds);
+ ps_global->initial_cmds = NULL;
+ }
+
+ return(ret);
+ }
+
+ if(firsttime) {
+ firsttime = 0;
+ if(ps_global->in_init_seq) {
+ ps_global->in_init_seq = 0;
+ ps_global->save_in_init_seq = 0;
+ clear_cursor_pos();
+ F_SET(F_USE_FK,ps_global,ps_global->orig_use_fkeys);
+ /* draw screen */
+ *ch = (UCS) ctrl('L');
+ return(1);
+ }
+ }
+
+ return(0);
+}
+
+
+#define TAPELEN 256
+static int tape[TAPELEN];
+static long recorded = 0L;
+static short length = 0;
+
+
+/*
+ * record user keystrokes
+ *
+ * Args: ch -- the character to record
+ *
+ * Returns: character recorded
+ */
+int
+key_recorder(int ch)
+{
+ tape[recorded++ % TAPELEN] = ch;
+ if(length < TAPELEN)
+ length++;
+
+ return(ch);
+}
+
+
+/*
+ * playback user keystrokes
+ *
+ * Args: ch -- ignored
+ *
+ * Returns: character played back or -1 to indicate end of tape
+ */
+int
+key_playback(int ch)
+{
+ ch = length ? tape[(recorded + TAPELEN - length--) % TAPELEN] : -1;
+ return(ch);
+}
+
+
+/*
+ * recent_keystroke - verbose version of key_playback
+ */
+int
+recent_keystroke(int *cv, char *cs, size_t cslen)
+{
+ int c;
+
+ if((c = key_playback(0)) != -1){
+ *cv = c;
+ snprintf(cs, cslen, "%.32s", pretty_command(c));
+ return(0);
+ }
+
+ return(-1);
+}
+
+
+#ifdef _WINDOWS
+int
+pcpine_oe_cursor(col, row)
+ int col;
+ long row;
+{
+ return((row == g_mc_row
+ && col >= g_mc_col
+ && col < ps_global->ttyo->screen_cols)
+ ? MSWIN_CURSOR_IBEAM
+ : MSWIN_CURSOR_ARROW);
+}
+#endif
diff --git a/alpine/osdep/termin.gen.h b/alpine/osdep/termin.gen.h
new file mode 100644
index 00000000..18c7c0de
--- /dev/null
+++ b/alpine/osdep/termin.gen.h
@@ -0,0 +1,48 @@
+/*
+ * $Id: termin.gen.h 890 2007-12-21 05:34:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_TERMIN_GEN_INCLUDED
+#define PINE_OSDEP_TERMIN_GEN_INCLUDED
+
+#include <general.h>
+#include "../radio.h"
+
+/* Useful Macros */
+#define READ_COMMAND(U) (read_command_prep() ? read_command(U) : NO_OP_COMMAND)
+
+/* exported prototypes */
+UCS read_command(char **);
+int read_command_prep(void);
+int key_recorder(int);
+int optionally_enter(char *, int, int, int, char *, ESCKEY_S *, HelpType, int *);
+UCS validatekeys(UCS);
+int process_config_input(UCS *);
+int key_recorder(int);
+int key_playback(int);
+int recent_keystroke(int *, char *, size_t);
+int init_tty_driver(struct pine *);
+void end_tty_driver(struct pine *);
+int PineRaw(int);
+UCS read_char(int);
+void init_keyboard(int);
+void end_keyboard(int);
+int pre_screen_config_opt_enter(char *, int, char *,
+ ESCKEY_S *, HelpType, int *);
+#ifdef _WINDOWS
+int pcpine_oe_cursor(int, long);
+#endif
+
+#endif /* PINE_OSDEP_TERMIN_GEN_INCLUDED */
diff --git a/alpine/osdep/termin.unx.c b/alpine/osdep/termin.unx.c
new file mode 100644
index 00000000..451d18c2
--- /dev/null
+++ b/alpine/osdep/termin.unx.c
@@ -0,0 +1,752 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: termin.unx.c 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/charconv/utf8.h"
+#include "../../pith/charconv/filesys.h"
+
+#include "../../pith/osdep/color.h"
+#include "../../pith/osdep/collate.h"
+#include "../../pith/osdep/err_desc.h"
+
+#include "../../pith/debug.h"
+#include "../../pith/state.h"
+#include "../../pith/conf.h"
+#include "../../pith/detach.h"
+#include "../../pith/adrbklib.h"
+#include "../../pith/remote.h"
+#include "../../pith/imap.h"
+#include "../../pith/status.h"
+
+#include "../pico/estruct.h"
+
+#include "../../pico/estruct.h"
+#include "../../pico/pico.h"
+#include "../../pico/osdep/raw.h"
+#include "../../pico/osdep/signals.h"
+#include "../../pico/osdep/mouse.h"
+#include "../../pico/osdep/read.h"
+#include "../../pico/osdep/getkey.h"
+#include "../../pico/osdep/tty.h"
+#include "../../pico/keydefs.h"
+
+#include "../talk.h"
+#include "../radio.h"
+#include "../dispfilt.h"
+#include "../signal.h"
+#include "../mailcmd.h"
+#include "../setup.h"
+
+#include "termin.gen.h"
+#include "termout.gen.h"
+#include "termin.unx.h"
+
+
+
+/*======================================================================
+ Things having to do with reading from the tty driver and keyboard
+ - initialize tty driver and reset tty driver
+ - read a character from terminal with keyboard escape seqence mapping
+ - initialize keyboard (keypad or such) and reset keyboard
+ - prompt user for a line of input
+ - read a command from keyboard with timeouts.
+
+ ====*/
+
+
+/*
+ * Helpful definitions
+ */
+/*
+ * Should really be using pico's TERM's t_getchar to read a character but
+ * we're just calling ttgetc directly for now. Ttgetc is the same as
+ * t_getchar whenever we use it so we're avoiding the trouble of initializing
+ * the TERM struct and calling ttgetc directly.
+ */
+#define READ_A_CHAR() ttgetc(NO_OP_COMMAND, key_rec, read_bail)
+
+
+/*
+ * Internal prototypes
+ */
+int pine_simple_ttgetc(int (*recorder)(int), void (*bail_handler)(void));
+UCS check_for_timeout(int);
+void read_bail(void);
+
+
+/*----------------------------------------------------------------------
+ Initialize the tty driver to do single char I/O and whatever else (UNIX)
+
+ Args: struct pine
+
+ Result: tty driver is put in raw mode so characters can be read one
+ at a time. Returns -1 if unsuccessful, 0 if successful.
+
+Some file descriptor voodoo to allow for pipes across vforks. See
+open_mailer for details.
+ ----------------------------------------------------------------------*/
+int
+init_tty_driver(struct pine *ps)
+{
+#ifdef MOUSE
+ if(F_ON(F_ENABLE_MOUSE, ps_global))
+ init_mouse();
+#endif /* MOUSE */
+
+ /* turn off talk permission by default */
+
+ if(F_ON(F_ALLOW_TALK, ps))
+ allow_talk(ps);
+ else
+ disallow_talk(ps);
+
+ return(PineRaw(1));
+}
+
+
+
+/*----------------------------------------------------------------------
+ Set or clear the specified tty mode
+
+ Args: ps -- struct pine
+ mode -- mode bits to modify
+ clear -- whether or not to clear or set
+
+ Result: tty driver mode change.
+ ----------------------------------------------------------------------*/
+void
+tty_chmod(struct pine *ps, int mode, int func)
+{
+ char *tty_name;
+ int new_mode;
+ struct stat sbuf;
+ static int saved_mode = -1;
+
+ /* if no problem figuring out tty's name & mode? */
+ if((((tty_name = (char *) ttyname(STDIN_FD)) != NULL
+ && fstat(STDIN_FD, &sbuf) == 0)
+ || ((tty_name = (char *) ttyname(STDOUT_FD)) != NULL
+ && fstat(STDOUT_FD, &sbuf) == 0))
+ && !(func == TMD_RESET && saved_mode < 0)){
+ new_mode = (func == TMD_RESET)
+ ? saved_mode
+ : (func == TMD_CLEAR)
+ ? (sbuf.st_mode & ~mode)
+ : (sbuf.st_mode | mode);
+ /* assign tty new mode */
+ if(our_chmod(tty_name, new_mode) == 0){
+ if(func == TMD_RESET) /* forget we knew */
+ saved_mode = -1;
+ else if(saved_mode < 0)
+ saved_mode = sbuf.st_mode; /* remember original */
+ }
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ End use of the tty, put it back into it's normal mode (UNIX)
+
+ Args: ps -- struct pine
+
+ Result: tty driver mode change.
+ ----------------------------------------------------------------------*/
+void
+end_tty_driver(struct pine *ps)
+{
+ ps = ps; /* get rid of unused parameter warning */
+
+#ifdef MOUSE
+ end_mouse();
+#endif /* MOUSE */
+ fflush(stdout);
+ dprint((2, "about to end_tty_driver\n"));
+
+ tty_chmod(ps, 0, TMD_RESET);
+ PineRaw(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Actually set up the tty driver (UNIX)
+
+ Args: state -- which state to put it in. 1 means go into raw, 0 out of
+
+ Result: returns 0 if successful and < 0 if not.
+ ----*/
+int
+PineRaw(int state)
+{
+ int result;
+
+ result = Raw(state);
+
+ if(result == 0 && state == 1){
+ /*
+ * Only go into 8 bit mode if we are doing something other
+ * than plain ASCII. This will save the folks that have
+ * their parity on their serial lines wrong the trouble of
+ * getting it right
+ */
+ if((ps_global->keyboard_charmap && ps_global->keyboard_charmap[0] &&
+ strucmp(ps_global->keyboard_charmap, "us-ascii"))
+ || (ps_global->display_charmap && ps_global->display_charmap[0] &&
+ strucmp(ps_global->display_charmap, "us-ascii")))
+ bit_strip_off();
+
+#ifdef DEBUG
+ if(debug < 9) /* only on if full debugging set */
+#endif
+ quit_char_off();
+ ps_global->low_speed = ttisslow();
+ crlf_proc(0);
+ xonxoff_proc(F_ON(F_PRESERVE_START_STOP, ps_global));
+ }
+
+ return(result);
+}
+
+
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+jmp_buf winch_state;
+int winch_occured = 0;
+int ready_for_winch = 0;
+#endif
+
+/*----------------------------------------------------------------------
+ This checks whether or not a character (UNIX)
+ is ready to be read, or it times out.
+
+ Args: time_out -- number of seconds before it will timeout
+
+ Result: Returns a NO_OP_IDLE or a NO_OP_COMMAND if the timeout expires
+ before input is available, or a KEY_RESIZE if a resize event
+ occurs, or READY_TO_READ if input is available before the timeout.
+ ----*/
+UCS
+check_for_timeout(int time_out)
+{
+ UCS res = NO_OP_COMMAND;
+
+ fflush(stdout);
+
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+ if(!winch_occured){
+ if(setjmp(winch_state) != 0){
+ winch_occured = 1;
+ ready_for_winch = 0;
+
+ /*
+ * Need to unblock signal after longjmp from handler, because
+ * signal is normally unblocked upon routine exit from the handler.
+ */
+ our_sigunblock(SIGWINCH);
+ }
+ else
+ ready_for_winch = 1;
+ }
+
+ if(winch_occured){
+ winch_occured = ready_for_winch = 0;
+ fix_windsize(ps_global);
+ return(KEY_RESIZE);
+ }
+#endif /* SIGWINCH */
+
+ switch(res = input_ready(time_out)){
+ case BAIL_OUT:
+ read_bail(); /* non-tragic exit */
+ /* NO RETURN */
+
+ case PANIC_NOW:
+ panic1("Select error: %s\n", error_description(errno));
+ /* NO RETURN */
+
+ case READ_INTR:
+ res = NO_OP_COMMAND;
+ /* fall through */
+
+ case NO_OP_IDLE:
+ case NO_OP_COMMAND:
+ case READY_TO_READ:
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+ ready_for_winch = 0;
+#endif
+ return(res);
+ }
+
+ /* not reachable */
+ return(res);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Read input characters with lots of processing for arrow keys and such (UNIX)
+
+ Args: time_out -- The timeout to for the reads
+
+ Result: returns the character read. Possible special chars.
+
+ This deals with function and arrow keys as well.
+
+ The idea is that this routine handles all escape codes so it done in
+ only one place. Especially so the back arrow key can work when entering
+ things on a line. Also so all function keys can be disabled and not
+ cause weird things to happen.
+ ---*/
+UCS
+read_char(int time_out)
+{
+ UCS status, cc, ch;
+ int (*key_rec)(int);
+
+ key_rec = key_recorder;
+ if(ps_global->conceal_sensitive_debugging)
+ key_rec = NULL;
+
+ /* Get input from initial-keystrokes */
+ if(process_config_input(&cc)){
+ ch = cc;
+ return(ch);
+ }
+
+ if((ch = check_for_timeout(time_out)) != READY_TO_READ)
+ goto done;
+
+ switch(status = kbseq(pine_simple_ttgetc, key_rec, read_bail,
+ ps_global->input_cs, &ch)){
+ case KEY_DOUBLE_ESC:
+ /*
+ * Special hack to get around comm devices eating control characters.
+ */
+ if(check_for_timeout(5) != READY_TO_READ){
+ dprint((9, "Read char: incomplete double escape timed out...\n"));
+ ch = KEY_JUNK; /* user typed ESC ESC, then stopped */
+ goto done;
+ }
+ else
+ ch = READ_A_CHAR();
+
+ ch &= 0x7f;
+
+ /* We allow a 3-digit number between 001 and 255 */
+ if(isdigit((unsigned char) ch)){
+ int n = 0, i = ch - '0';
+
+ if(i < 0 || i > 2){
+ dprint((9, "Read char: double escape followed by 1st digit not 0, 1, or 2... (%d)\n", i));
+ ch = KEY_JUNK;
+ goto done; /* bogus literal char value */
+ }
+
+ while(n++ < 2){
+ if(check_for_timeout(5) != READY_TO_READ
+ || (!isdigit((unsigned char) (ch = READ_A_CHAR()))
+ || (n == 1 && i == 2 && ch > '5')
+ || (n == 2 && i == 25 && ch > '5'))){
+ dprint((9, "Read char: bad double escape, either timed out or too large 3-digit num...\n"));
+ ch = KEY_JUNK; /* user typed ESC ESC #, stopped */
+ goto done;
+ }
+
+ i = (i * 10) + (ch - '0');
+ }
+
+ ch = i;
+ }
+ else{ /* or, normal case, ESC ESC c means ^c */
+ if(islower((unsigned char) ch)) /* canonicalize if alpha */
+ ch = toupper((unsigned char) ch);
+
+ ch = (isalpha((unsigned char)ch) || ch == '@'
+ || (ch >= '[' && ch <= '_'))
+ ? ctrl(ch) : ((ch == SPACE) ? ctrl('@'): ch);
+ dprint((9, "Read char: this is a successful double escape...\n"));
+ }
+
+ goto done;
+
+#ifdef MOUSE
+ case KEY_XTERM_MOUSE:
+ if(mouseexist()){
+ /*
+ * Special hack to get mouse events from an xterm.
+ * Get the details, then pass it past the keymenu event
+ * handler, and then to the installed handler if there
+ * is one...
+ */
+ static int down = 0;
+ int x, y, button;
+ unsigned long cmd;
+
+ clear_cursor_pos();
+ button = READ_A_CHAR() & 0x03;
+
+ x = READ_A_CHAR() - '!';
+ y = READ_A_CHAR() - '!';
+
+ ch = NO_OP_COMMAND;
+ if(button == 0){ /* xterm button 1 down */
+ down = 1;
+ if(checkmouse(&cmd, 1, x, y))
+ ch = cmd;
+ }
+ else if (down && button == 3){
+ down = 0;
+ if(checkmouse(&cmd, 0, x, y))
+ ch = cmd;
+ }
+
+ goto done;
+ }
+
+ break;
+#endif /* MOUSE */
+
+ case KEY_UP :
+ case KEY_DOWN :
+ case KEY_RIGHT :
+ case KEY_LEFT :
+ case KEY_PGUP :
+ case KEY_PGDN :
+ case KEY_HOME :
+ case KEY_END :
+ case KEY_DEL :
+ case PF1 :
+ case PF2 :
+ case PF3 :
+ case PF4 :
+ case PF5 :
+ case PF6 :
+ case PF7 :
+ case PF8 :
+ case PF9 :
+ case PF10 :
+ case PF11 :
+ case PF12 :
+ dprint((9, "Read char returning: 0x%x %s\n", status, pretty_command(status)));
+ return(status);
+
+ case CTRL_KEY_UP :
+ status = KEY_UP;
+ dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_UP)\n", status, pretty_command(status)));
+ return(status);
+
+ case CTRL_KEY_DOWN :
+ status = KEY_DOWN;
+ dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_DOWN)\n", status, pretty_command(status)));
+ return(status);
+
+ case CTRL_KEY_RIGHT :
+ status = KEY_RIGHT;
+ dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_RIGHT)\n", status, pretty_command(status)));
+ return(status);
+
+ case CTRL_KEY_LEFT :
+ status = KEY_LEFT;
+ dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_LEFT)\n", status, pretty_command(status)));
+ return(status);
+
+ case KEY_SWALLOW_Z:
+ status = KEY_JUNK;
+ case KEY_SWAL_UP:
+ case KEY_SWAL_DOWN:
+ case KEY_SWAL_LEFT:
+ case KEY_SWAL_RIGHT:
+ do
+ if(check_for_timeout(2) != READY_TO_READ){
+ status = KEY_JUNK;
+ break;
+ }
+ while(!strchr("~qz", READ_A_CHAR()));
+ ch = (status == KEY_JUNK) ? status : status - (KEY_SWAL_UP - KEY_UP);
+ goto done;
+
+ case KEY_KERMIT:
+ do{
+ cc = ch;
+ if(check_for_timeout(2) != READY_TO_READ){
+ status = KEY_JUNK;
+ break;
+ }
+ else
+ ch = READ_A_CHAR();
+ }while(cc != '\033' && ch != '\\');
+
+ ch = KEY_JUNK;
+ goto done;
+
+ case BADESC:
+ ch = KEY_JUNK;
+ goto done;
+
+ case 0: /* regular character */
+ default:
+ /*
+ * we used to strip (ch &= 0x7f;), but this seems much cleaner
+ * in the face of line noise and has the benefit of making it
+ * tougher to emit mistakenly labeled MIME...
+ */
+ if((ch & ~0x7f)
+ && ((!ps_global->keyboard_charmap || !strucmp(ps_global->keyboard_charmap, "US-ASCII"))
+ && (!ps_global->display_charmap || !strucmp(ps_global->display_charmap, "US-ASCII")))){
+ dprint((9, "Read char sees ch = 0x%x status=0x%x, returns KEY_JUNK\n", ch, status));
+ return(KEY_JUNK);
+ }
+ else if(ch == ctrl('Z')){
+ dprint((9, "Read char got ^Z, calling do_suspend\n"));
+ ch = do_suspend();
+ dprint((9, "After do_suspend Read char returns 0x%x %s\n", ch, pretty_command(ch)));
+ return(ch);
+ }
+#ifdef MOUSE
+ else if(ch == ctrl('\\')){
+ int e;
+
+ dprint((9, "Read char got ^\\, toggle xterm mouse\n"));
+ if(F_ON(F_ENABLE_MOUSE, ps_global)){
+ (e=mouseexist()) ? end_mouse() : (void) init_mouse();
+ if(e != mouseexist())
+ q_status_message1(SM_ASYNC, 0, 2, "Xterm mouse tracking %s!",
+ mouseexist() ? "on" : "off");
+ else if(!e)
+ q_status_message1(SM_ASYNC, 0, 2, "See help for feature \"%s\" ($DISPLAY variable set?)", pretty_feature_name(feature_list_name(F_ENABLE_MOUSE), -1));
+ }
+ else
+ q_status_message1(SM_ASYNC, 0, 2, "Feature \"%s\" not enabled",
+ pretty_feature_name(feature_list_name(F_ENABLE_MOUSE), -1));
+
+ return(NO_OP_COMMAND);
+ }
+#endif /* MOUSE */
+
+
+ done:
+#ifdef DEBUG
+ if(ps_global->conceal_sensitive_debugging && debug < 10){
+ dprint((9, "Read char returning: <hidden char>\n"));
+ }
+ else{
+ dprint((9, "Read char returning: 0x%x %s\n", ch, pretty_command(ch)));
+ }
+#endif
+
+ return(ch);
+ }
+
+ /* not reachable */
+ return(KEY_JUNK);
+}
+
+
+/*----------------------------------------------------------------------
+ Reading input somehow failed and we need to shutdown now
+
+ Args: none
+
+ Result: pine exits
+
+ ---*/
+void
+read_bail(void)
+{
+ dprint((1, "read_bail: cleaning up\n"));
+ end_signals(1);
+
+ /*
+ * This gets rid of temporary cache files for remote addrbooks.
+ */
+ completely_done_with_adrbks();
+
+ /*
+ * This flushes out deferred changes and gets rid of temporary cache
+ * files for remote config files.
+ */
+ if(ps_global->prc){
+ if(ps_global->prc->outstanding_pinerc_changes)
+ write_pinerc(ps_global, Main, WRP_NOUSER);
+
+ if(ps_global->prc->rd)
+ rd_close_remdata(&ps_global->prc->rd);
+
+ free_pinerc_s(&ps_global->prc);
+ }
+
+ /* as does this */
+ if(ps_global->post_prc){
+ if(ps_global->post_prc->outstanding_pinerc_changes)
+ write_pinerc(ps_global, Post, WRP_NOUSER);
+
+ if(ps_global->post_prc->rd)
+ rd_close_remdata(&ps_global->post_prc->rd);
+
+ free_pinerc_s(&ps_global->post_prc);
+ }
+
+ sp_end();
+
+ dprint((1, "done with read_bail clean up\n"));
+
+ imap_flush_passwd_cache(TRUE);
+ end_keyboard(F_ON(F_USE_FK,ps_global));
+ end_tty_driver(ps_global);
+ if(filter_data_file(0))
+ our_unlink(filter_data_file(0));
+
+ exit(0);
+}
+
+
+int
+pine_simple_ttgetc(int (*fi)(int), void (*fv)(void))
+{
+ int ch;
+
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+ if(!winch_occured){
+ if(setjmp(winch_state) != 0){
+ winch_occured = 1;
+ ready_for_winch = 0;
+
+ /*
+ * Need to unblock signal after longjmp from handler, because
+ * signal is normally unblocked upon routine exit from the handler.
+ */
+ our_sigunblock(SIGWINCH);
+ }
+ else
+ ready_for_winch = 1;
+ }
+
+ if(winch_occured){
+ winch_occured = ready_for_winch = 0;
+ fix_windsize(ps_global);
+ return(KEY_RESIZE);
+ }
+#endif /* SIGWINCH */
+
+ ch = simple_ttgetc(fi, fv);
+
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+ ready_for_winch = 0;
+#endif
+
+ return(ch);
+}
+
+
+
+extern char term_name[];
+/* -------------------------------------------------------------------
+ Set up the keyboard -- usually enable some function keys (UNIX)
+
+ Args: struct pine
+
+So far all we do here is turn on keypad mode for certain terminals
+
+Hack for NCSA telnet on an IBM PC to put the keypad in the right mode.
+This is the same for a vtXXX terminal or [zh][12]9's which we have
+a lot of at UW
+ ----*/
+void
+init_keyboard(int use_fkeys)
+{
+ if(use_fkeys && (!strucmp(term_name,"vt102")
+ || !strucmp(term_name,"vt100")))
+ printf("\033\133\071\071\150");
+}
+
+
+
+/*----------------------------------------------------------------------
+ Clear keyboard, usually disable some function keys (UNIX)
+
+ Args: pine state (terminal type)
+
+ Result: keyboard state reset
+ ----*/
+void
+end_keyboard(int use_fkeys)
+{
+ if(use_fkeys && (!strcmp(term_name, "vt102")
+ || !strcmp(term_name, "vt100"))){
+ printf("\033\133\071\071\154");
+ fflush(stdout);
+ }
+}
+
+
+/*
+ * This is a bare-bones implementation which is missing most of the
+ * features of the real optionally_enter. The initial value of string is
+ * isgnored. The escape_list is ignored. The help is not implemented. The
+ * only flag implemented is OE_PASSWD. We don't go into raw mode so the
+ * only input possible is a line (the EOL is stripped before returning).
+ */
+int
+pre_screen_config_opt_enter(char *string, int string_size, char *prompt,
+ ESCKEY_S *escape_list, HelpType help, int *flags)
+{
+ char *pw;
+ int return_v = -10;
+
+ while(return_v == -10) {
+
+ if(flags && (*flags & (OE_PASSWD | OE_PASSWD_NOAST))){
+ if((pw = getpass(prompt)) != NULL){
+ if(strlen(pw) < string_size){
+ strncpy(string, pw, string_size);
+ string[string_size-1] = '\0';
+ return_v = 0;
+ }
+ else{
+ fputs("Password too long\n", stderr);
+ return_v = -1;
+ }
+ }
+ else
+ return_v = 1; /* cancel? */
+ }
+ else{
+ char *p;
+
+ fputs(prompt, stdout);
+ fgets(string, string_size, stdin);
+ string[string_size-1] = '\0';
+ if((p = strpbrk(string, "\r\n")) != NULL)
+ *p = '\0';
+
+ return_v = 0;
+ }
+ }
+
+ return(return_v);
+}
+
+
diff --git a/alpine/osdep/termin.unx.h b/alpine/osdep/termin.unx.h
new file mode 100644
index 00000000..7ead09f0
--- /dev/null
+++ b/alpine/osdep/termin.unx.h
@@ -0,0 +1,29 @@
+/*
+ * $Id: termin.unx.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_TERMIN_UNX_INCLUDED
+#define PINE_OSDEP_TERMIN_UNX_INCLUDED
+
+
+#include <general.h>
+
+
+/* exported prototypes */
+void tty_chmod(struct pine *, int, int);
+
+
+
+#endif /* PINE_OSDEP_TERMIN_UNX_INCLUDED */
diff --git a/alpine/osdep/termin.wnt.c b/alpine/osdep/termin.wnt.c
new file mode 100644
index 00000000..83dc8284
--- /dev/null
+++ b/alpine/osdep/termin.wnt.c
@@ -0,0 +1,352 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: termin.unx.c 193 2006-10-20 17:09:26Z mikes@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/charconv/utf8.h"
+#include "../../pith/charconv/filesys.h"
+
+#include "../../pith/osdep/color.h"
+
+#include "../../pith/debug.h"
+#include "../../pith/state.h"
+#include "../../pith/conf.h"
+#include "../../pith/detach.h"
+
+#include "../pico/estruct.h"
+#include "../pico/pico.h"
+
+#include "../../pico/osdep/raw.h"
+#include "../../pico/osdep/signals.h"
+#include "../../pico/osdep/mouse.h"
+#include "../../pico/keydefs.h"
+
+#include "../talk.h"
+#include "../radio.h"
+#include "../dispfilt.h"
+#include "../signal.h"
+#include "../status.h"
+#include "../titlebar.h"
+
+#include "../../pico/osdep/mswin.h"
+
+#include "termin.gen.h"
+#include "termin.wnt.h"
+#include "termout.gen.h"
+
+#define RETURN_CH(X) return(key_rec ? ((*key_rec)(X)) : (int)(X))
+
+/* global to tell us if the window was resized. */
+static int DidResize = FALSE;
+
+int pine_window_resize_callback(void);
+
+/*----------------------------------------------------------------------
+ Initialize the tty driver to do single char I/O and whatever else
+
+ Input: struct pine
+
+ Result: tty driver is put in raw mode
+ ----------------------------------------------------------------------*/
+int
+init_tty_driver(struct pine *pine)
+{
+ mswin_showwindow();
+ mswin_setresizecallback (pine_window_resize_callback);
+ init_mouse (); /* always a mouse under windows? */
+ return(PineRaw(1));
+}
+
+
+/*----------------------------------------------------------------------
+ End use of the tty, put it back into it's normal mode
+
+ Input: struct pine
+
+ Result: tty driver mode change
+ ----------------------------------------------------------------------*/
+void
+end_tty_driver(struct pine *pine)
+{
+ dprint((2, "about to end_tty_driver\n"));
+ mswin_clearresizecallback (pine_window_resize_callback);
+}
+
+/*----------------------------------------------------------------------
+ Actually set up the tty driver
+
+ Args: state -- which state to put it in. 1 means go into raw, 0 out of
+
+ Result: returns 0 if successful and -1 if not.
+ ----*/
+
+int
+PineRaw(int state)
+{
+ ps_global->low_speed = 0;
+ return(0);
+}
+
+/*----------------------------------------------------------------------
+ Read input characters with lots of processing for arrow keys and such
+
+ Input: none
+
+ Result: returns the character read. Possible special chars defined h file
+
+
+ This deals with function and arrow keys as well.
+ It returns ^T for up , ^U for down, ^V for forward and ^W for back.
+ These are just sort of arbitrarily picked and might be changed.
+ They are defined in defs.h. Didn't want to use 8 bit chars because
+ the values are signed chars, though it ought to work with negative
+ values.
+
+ The idea is that this routine handles all escape codes so it done in
+ only one place. Especially so the back arrow key can work when entering
+ things on a line. Also so all function keys can be broken and not
+ cause weird things to happen.
+----------------------------------------------------------------------*/
+
+UCS
+read_char(int tm)
+{
+ unsigned ch = 0;
+ time_t timein;
+ int (*key_rec)(int);
+
+ key_rec = key_recorder;
+ if(ps_global->conceal_sensitive_debugging && debug < 10)
+ key_rec = NULL;
+
+ if(process_config_input((int *) &ch))
+ RETURN_CH(ch);
+
+ if (DidResize) {
+ DidResize = FALSE;
+ fix_windsize(ps_global);
+ return(KEY_RESIZE);
+ }
+
+ mswin_setcursor(MSWIN_CURSOR_ARROW);
+
+ if(tm){
+ timein = time(0L);
+ /* mswin_charavail() Yields control to other window apps. */
+ while (!mswin_charavail()) {
+ if(time(0L) >= timein + (time_t) tm){
+ ch = (tm < IDLE_TIMEOUT) ? NO_OP_COMMAND : NO_OP_IDLE;
+ goto gotone;
+ }
+ if (DidResize) {
+ DidResize = FALSE;
+ fix_windsize(ps_global);
+ return(KEY_RESIZE);
+ }
+ if(checkmouse(&ch,0,0,0))
+ goto gotone;
+ }
+ }
+ else
+ while(!mswin_charavail())
+ if(checkmouse(&ch,0,0,0))
+ goto gotone;
+
+ ch = mswin_getc_fast();
+
+ /*
+ * mswin_getc_fast() returns UCS type codes (see keydefs.h). Map
+ * those values to what is expected from read_char(): Ctrl keys being
+ * in the 0..0x1f range, NO_OP_COMMAND, etc.
+ */
+ if(ch == NODATA)
+ ch = NO_OP_COMMAND;
+ else if(ch & CTRL)
+ {
+ ch &= ~CTRL;
+ if(ch >= '@' && ch < '@' + 32) {
+ ch -= '@';
+ }
+ else {
+ /*
+ * This could be a ctrl key this part of Pine doesn't understand.
+ * For example, CTRL|KEY_LEFT. Map these to nops.
+ */
+ ch = NO_OP_COMMAND;
+ }
+ }
+
+gotone:
+ /* More obtuse key mapping. If it is a mouse event, the return
+ * may be KEY_MOUSE, which indicates to the upper layer that it
+ * is a mouse event. Return it here to avoid the code that
+ * follows which would do a (ch & 0xff).
+ */
+ if (ch == KEY_MOUSE)
+ RETURN_CH(ch);
+
+ /*
+ * WARNING: Hack notice.
+ * the mouse interaction complicates this expression a bit as
+ * if function key mode is set, PFn values are setup for return
+ * by the mouse event catcher. For now, just special case them
+ * since they don't conflict with any of the DOS special keys.
+ */
+ if((ch & 0xff) == ctrl('Z'))
+ RETURN_CH(do_suspend());
+
+ RETURN_CH (ch);
+}
+
+void
+flush_input(void)
+{
+ mswin_flush_input();
+}
+
+void
+init_keyboard(int use_fkeys)
+{
+}
+
+void
+end_keyboard(int use_fkeys)
+{
+}
+
+int
+pre_screen_config_opt_enter(char *utf8string, int utf8string_size,
+ char *utf8prompt, ESCKEY_S *escape_list,
+ HelpType help, int *flags)
+{
+ mswin_setwindow(NULL, NULL, NULL, NULL, NULL, NULL);
+ return(win_dialog_opt_enter(utf8string, utf8string_size, utf8prompt, escape_list,
+ help, flags));
+}
+
+int
+win_dialog_opt_enter(char *utf8string, int utf8string_size, char *utf8prompt,
+ ESCKEY_S *escape_list, HelpType help, int *flags)
+{
+ MDlgButton button_list[12];
+ LPTSTR free_names[12];
+ LPTSTR free_labels[12];
+ int i, b, return_v;
+ char **help_text;
+ char *utf8, *saved_string = NULL;
+ UCS *string, *s, *prompt;
+ int n;
+
+ memset(&free_names, 0, sizeof(LPTSTR) * 12);
+ memset(&free_labels, 0, sizeof(LPTSTR) * 12);
+ memset (&button_list, 0, sizeof (MDlgButton) * 12);
+ b = 0;
+ for (i = 0; escape_list && escape_list[i].ch != -1 && i < 11; ++i) {
+ if (escape_list[i].name != NULL
+ && escape_list[i].ch > 0 && escape_list[i].ch < 256) {
+ button_list[b].ch = escape_list[i].ch;
+ button_list[b].rval = escape_list[i].rval;
+ free_names[b] = utf8_to_lptstr(escape_list[i].name);
+ button_list[b].name = free_names[b];
+ free_labels[b] = utf8_to_lptstr(escape_list[i].label);
+ button_list[b].label = free_labels[b];
+ b++;
+ }
+ }
+
+ button_list[b].ch = -1;
+
+ if(utf8string)
+ saved_string = cpystr(utf8string);
+
+ /* assumption here is that HelpType is char ** */
+ help_text = help;
+
+ n = utf8string_size;
+ string = (UCS *) fs_get(n * sizeof(UCS));
+ s = utf8_to_ucs4_cpystr(utf8string);
+ if(s){
+ ucs4_strncpy(string, s, n);
+ string[n-1] = '\0';
+ fs_give((void **) &s);
+ }
+
+ prompt = utf8_to_ucs4_cpystr(utf8prompt ? utf8prompt : "");
+
+ return_v = mswin_dialog(prompt, string, n,
+ (flags && *flags & OE_APPEND_CURRENT),
+ (flags && *flags & OE_PASSWD_NOAST) ? 10 :
+ (flags && *flags & OE_PASSWD) ? 1 : 0,
+ button_list,
+ help_text, flags ? *flags : OE_NONE);
+
+ for(i = 0; i < 12; i++){
+ if(free_names[i])
+ fs_give((void **) &free_names[i]);
+ if(free_labels[i])
+ fs_give((void **) &free_labels[i]);
+ }
+
+ utf8 = ucs4_to_utf8_cpystr(string);
+ if(utf8){
+ strncpy(utf8string, utf8, utf8string_size);
+ utf8string[utf8string_size-1] = '\0';
+ fs_give((void **) &utf8);
+ }
+
+ if(string)
+ fs_give((void **) &string);
+
+ if(prompt)
+ fs_give((void **) &prompt);
+
+ if(flags && (saved_string && !utf8string || !saved_string && utf8string ||
+ (saved_string && string && strcmp(saved_string, utf8string))))
+ *flags |= OE_USER_MODIFIED;
+
+ if(saved_string)
+ fs_give((void **) &saved_string);
+
+ return(return_v);
+}
+
+
+/*----------------------------------------------------------------------
+ Flag the fact the window has resized.
+*/
+int
+pine_window_resize_callback (void)
+{
+ DidResize = TRUE;
+ return(0);
+}
+
+void
+intr_proc(int state)
+{
+ return; /* no op */
+}
+
diff --git a/alpine/osdep/termin.wnt.h b/alpine/osdep/termin.wnt.h
new file mode 100644
index 00000000..68480852
--- /dev/null
+++ b/alpine/osdep/termin.wnt.h
@@ -0,0 +1,32 @@
+/*
+ * $Id: termin.unx.h 136 2006-09-22 20:06:05Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_TERMIN_WNT_INCLUDED
+#define PINE_OSDEP_TERMIN_WNT_INCLUDED
+
+
+#include <general.h>
+#include "../radio.h"
+#include "../../pith/helptext.h"
+
+
+/* exported prototypes */
+void flush_input(void);
+int win_dialog_opt_enter(char *, int, char *, ESCKEY_S *, HelpType, int *);
+void intr_proc(int);
+
+
+#endif /* PINE_OSDEP_TERMIN_WNT_INCLUDED */
diff --git a/alpine/osdep/termout.gen.c b/alpine/osdep/termout.gen.c
new file mode 100644
index 00000000..a006c694
--- /dev/null
+++ b/alpine/osdep/termout.gen.c
@@ -0,0 +1,584 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: termout.gen.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/osdep/color.h"
+#include "../../pith/charconv/filesys.h"
+
+#include "../../pith/debug.h"
+#include "../../pith/newmail.h"
+#include "../../pith/filter.h"
+#include "../../pith/handle.h"
+#include "../../pith/conf.h"
+#include "../../pith/mimedesc.h"
+
+#include "../../pico/estruct.h"
+#include "../../pico/pico.h"
+#include "../../pico/keydefs.h"
+#include "../../pico/osdep/color.h"
+
+#include "../status.h"
+#include "../radio.h"
+#include "../folder.h"
+#include "../keymenu.h"
+#include "../send.h"
+#include "../mailindx.h"
+
+int _line = FARAWAY;
+int _col = FARAWAY;
+
+#include "termout.gen.h"
+
+
+#define PUTLINE_BUFLEN 256
+
+
+
+/*
+ * Generic tty output routines...
+ */
+
+/*----------------------------------------------------------------------
+ Printf style output line to the screen at given position, 0 args
+
+ Args: x -- column position on the screen
+ y -- row position on the screen
+ line -- line of text to output
+
+ Result: text is output
+ cursor position is update
+ ----*/
+void
+PutLine0(int x, int y, register char *line)
+{
+ MoveCursor(x,y);
+ Write_to_screen(line);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Output line of length len to the display observing embedded attributes
+
+ Args: x -- column position on the screen
+ y -- column position on the screen
+ line -- text to be output
+ length -- length of text to be output
+
+ Result: text is output
+ cursor position is updated
+ ----------------------------------------------------------------------*/
+void
+PutLine0n8b(int x, int y, register char *line, int length, HANDLE_S *handles)
+{
+ unsigned char c;
+ int is_inv = 0, is_bold = 0, is_uline = 0, is_fg = 0, is_bg = 0;
+#ifdef _WINDOWS
+ int hkey = 0;
+#endif
+
+ MoveCursor(x,y);
+
+ while(length--){
+
+ c = (unsigned char) *line++;
+
+ if(c == (unsigned char) TAG_EMBED && length){
+
+ length--;
+
+ switch(*line++){
+ case TAG_INVON :
+ StartInverse();
+ is_inv = 1;
+ break;
+
+ case TAG_INVOFF :
+ EndInverse();
+ is_inv = 0;
+ break;
+
+ case TAG_BOLDON :
+ StartBold();
+ is_bold = 1;
+ break;
+
+ case TAG_BOLDOFF :
+ EndBold();
+ is_bold = 0;
+ break;
+
+ case TAG_ITALICON : /* express italic as uline in terminal */
+ case TAG_ULINEON :
+ StartUnderline();
+ is_uline = 1;
+ break;
+
+ case TAG_ITALICOFF : /* express italic as uline in terminal */
+ case TAG_ULINEOFF :
+ EndUnderline();
+ is_uline = 0;
+ break;
+
+ case TAG_HANDLE :
+ length -= *line + 1; /* key length plus length tag */
+ if(handles){
+ int key, n, current_key = 0;
+
+ for(key = 0, n = *line++; n; n--) /* forget Horner? */
+ key = (key * 10) + (*line++ - '0');
+
+#if _WINDOWS
+ hkey = key;
+#endif
+
+ if(handles->using_is_used){
+ HANDLE_S *h;
+
+ for(h = handles; h; h = h->next)
+ if(h->is_used)
+ break;
+
+ if(h)
+ current_key = h->key;
+ }
+ else
+ current_key = handles->key;
+
+ if(key == current_key){
+ COLOR_PAIR *curcolor = NULL;
+ COLOR_PAIR *revcolor = NULL;
+
+ if(handles->color_unseen
+ && (curcolor = pico_get_cur_color())
+ && (colorcmp(curcolor->fg, ps_global->VAR_NORM_FORE_COLOR)
+ || colorcmp(curcolor->bg, ps_global->VAR_NORM_BACK_COLOR))
+ && (revcolor = apply_rev_color(curcolor,
+ ps_global->index_color_style)))
+ (void) pico_set_colorp(revcolor, PSC_NONE);
+ else{
+
+ if(pico_usingcolor() &&
+ ps_global->VAR_SLCTBL_FORE_COLOR &&
+ ps_global->VAR_SLCTBL_BACK_COLOR){
+ pico_set_normal_color();
+ }
+ else
+ EndBold();
+
+ StartInverse();
+ is_inv = 1;
+ }
+
+ if(curcolor)
+ free_color_pair(&curcolor);
+
+ if(revcolor)
+ free_color_pair(&revcolor);
+ }
+ }
+ else{
+ /* BUG: complain? */
+ line += *line + 1;
+ }
+
+ break;
+
+ case TAG_FGCOLOR :
+ if(length < RGBLEN){
+ dprint((9,
+ "FGCOLOR not proper length, ignoring\n"));
+ length = 0;
+ break;
+ }
+
+ (void)pico_set_fg_color(line);
+ is_fg = 1;
+ length -= RGBLEN;
+ line += RGBLEN;
+ break;
+
+ case TAG_BGCOLOR :
+ if(length < RGBLEN){
+ dprint((9,
+ "BGCOLOR not proper length, ignoring\n"));
+ length = 0;
+ break;
+ }
+
+ (void)pico_set_bg_color(line);
+ is_bg = 1;
+ length -= RGBLEN;
+ line += RGBLEN;
+ break;
+
+ case TAG_EMBED: /* literal "embed" char */
+ Writechar(TAG_EMBED, 0);
+ break;
+
+ case TAG_STRIKEON : /* unsupported text markup */
+ case TAG_STRIKEOFF :
+ case TAG_BIGON :
+ case TAG_BIGOFF :
+ case TAG_SMALLON :
+ case TAG_SMALLOFF :
+ default : /* Eat unrecognized tag - TAG_BIGON, etc */
+ break;
+ } /* tag with handle, skip it */
+ }
+ else
+ Writechar(c, 0);
+ }
+
+
+#if _WINDOWS_X
+ if(hkey) {
+ char *tmp_file = NULL, ext[32], mtype[128];
+ HANDLE_S *h;
+ extern HANDLE_S *get_handle (HANDLE_S *, int);
+
+ if((h = get_handle(handles, hkey)) && h->type == Attach){
+ ext[0] = '\0';
+ strncpy(mtype, body_type_names(h->h.attach->body->type), sizeof(mtype));
+ mtype[sizeof(mtype)-1] = '\0';
+ if (h->h.attach->body->subtype) {
+ strncat (mtype, "/", sizeof(mtype)-strlen(mtype)-1);
+ mtype[sizeof(mtype)-1] = '\0';
+ strncat (mtype, h->h.attach->body->subtype, sizeof(mtype)-strlen(mtype)-1);
+ mtype[sizeof(mtype)-1] = '\0';
+ }
+
+ if(!set_mime_extension_by_type(ext, mtype)){
+ char *p, *extp = NULL;
+
+ if((p = get_filename_parameter(NULL, 0, h->h.attach->body, &extp)) != NULL){
+ if(extp){
+ strncpy(ext, extp, sizeof(ext));
+ ext[sizeof(ext)-1] = '\0';
+ }
+
+ fs_give((void **) &p);
+ }
+ }
+
+ if(ext[0] && (tmp_file = temp_nam_ext(NULL, "im", ext))){
+ FILE *f = our_fopen(tmp_file, "w");
+
+ mswin_registericon(x, h->key, tmp_file);
+
+ fclose(f);
+ our_unlink(tmp_file);
+ fs_give((void **)&tmp_file);
+ }
+ }
+ }
+#endif
+ if(is_inv){
+ dprint((9,
+ "INVERSE left on at end of line, turning off now\n"));
+ EndInverse();
+ }
+ if(is_bold){
+ dprint((9,
+ "BOLD left on at end of line, turning off now\n"));
+ EndBold();
+ }
+ if(is_uline){
+ dprint((9,
+ "UNDERLINE left on at end of line, turning off now\n"));
+ EndUnderline();
+ }
+ if(is_fg || is_bg)
+ pico_set_normal_color();
+
+}
+
+
+/*----------------------------------------------------------------------
+ Printf style output line to the screen at given position, 1 arg
+
+ Input: position on the screen
+ line of text to output
+
+ Result: text is output
+ cursor position is update
+ ----------------------------------------------------------------------*/
+void
+/*VARARGS2*/
+PutLine1(int x, int y, char *line, void *arg1)
+{
+ char buffer[PUTLINE_BUFLEN];
+
+ snprintf(buffer, sizeof(buffer), line, arg1);
+ buffer[sizeof(buffer)-1] = '\0';
+ PutLine0(x, y, buffer);
+}
+
+
+/*----------------------------------------------------------------------
+ Printf style output line to the screen at given position, 2 args
+
+ Input: position on the screen
+ line of text to output
+
+ Result: text is output
+ cursor position is update
+ ----------------------------------------------------------------------*/
+void
+/*VARARGS3*/
+PutLine2(int x, int y, char *line, void *arg1, void *arg2)
+{
+ char buffer[PUTLINE_BUFLEN];
+
+ snprintf(buffer, sizeof(buffer), line, arg1, arg2);
+ buffer[sizeof(buffer)-1] = '\0';
+ PutLine0(x, y, buffer);
+}
+
+
+/*----------------------------------------------------------------------
+ Printf style output line to the screen at given position, 3 args
+
+ Input: position on the screen
+ line of text to output
+
+ Result: text is output
+ cursor position is update
+ ----------------------------------------------------------------------*/
+void
+/*VARARGS4*/
+PutLine3(int x, int y, char *line, void *arg1, void *arg2, void *arg3)
+{
+ char buffer[PUTLINE_BUFLEN];
+
+ snprintf(buffer, sizeof(buffer), line, arg1, arg2, arg3);
+ buffer[sizeof(buffer)-1] = '\0';
+ PutLine0(x, y, buffer);
+}
+
+
+/*----------------------------------------------------------------------
+ Printf style output line to the screen at given position, 4 args
+
+ Args: x -- column position on the screen
+ y -- column position on the screen
+ line -- printf style line of text to output
+
+ Result: text is output
+ cursor position is update
+ ----------------------------------------------------------------------*/
+void
+/*VARARGS5*/
+PutLine4(int x, int y, char *line, void *arg1, void *arg2, void *arg3, void *arg4)
+{
+ char buffer[PUTLINE_BUFLEN];
+
+ snprintf(buffer, sizeof(buffer), line, arg1, arg2, arg3, arg4);
+ buffer[sizeof(buffer)-1] = '\0';
+ PutLine0(x, y, buffer);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Printf style output line to the screen at given position, 5 args
+
+ Args: x -- column position on the screen
+ y -- column position on the screen
+ line -- printf style line of text to output
+
+ Result: text is output
+ cursor position is update
+ ----------------------------------------------------------------------*/
+void
+/*VARARGS6*/
+PutLine5(int x, int y, char *line, void *arg1, void *arg2, void *arg3, void *arg4, void *arg5)
+{
+ char buffer[PUTLINE_BUFLEN];
+
+ snprintf(buffer, sizeof(buffer), line, arg1, arg2, arg3, arg4, arg5);
+ buffer[sizeof(buffer)-1] = '\0';
+ PutLine0(x, y, buffer);
+}
+
+
+/*----------------------------------------------------------------------
+ Output a UTF-8 line to the screen, centered
+
+ Input: Line number to print on, string to output
+
+ Result: String is output to screen
+ Returns column number line is output on
+ ----------------------------------------------------------------------*/
+int
+Centerline(int line, char *string)
+{
+ int width, col;
+
+ width = (int) utf8_width(string);
+
+ if (width > ps_global->ttyo->screen_cols)
+ col = 0;
+ else
+ col = (ps_global->ttyo->screen_cols - width) / 2;
+
+ PutLine0(line, col, string);
+
+ return(col);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Clear specified line on the screen
+
+ Result: The line is blanked and the cursor is left at column 0.
+
+ ----*/
+void
+ClearLine(int n)
+{
+ if(ps_global->in_init_seq)
+ return;
+
+ MoveCursor(n, 0);
+ CleartoEOLN();
+}
+
+
+
+/*----------------------------------------------------------------------
+ Clear specified lines on the screen
+
+ Result: The lines starting at 'x' and ending at 'y' are blanked
+ and the cursor is left at row 'x', column 0
+
+ ----*/
+void
+ClearLines(int x, int y)
+{
+ int i;
+
+ for(i = x; i <= y; i++)
+ ClearLine(i);
+
+ MoveCursor(x, 0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Indicate to the screen painting here that the position of the cursor
+ has been disturbed and isn't where these functions might think.
+ ----*/
+void
+clear_cursor_pos(void)
+{
+ _line = FARAWAY;
+ _col = FARAWAY;
+}
+
+
+/*----------------------------------------------------------------------
+ Write a character to the screen, keeping track of cursor position
+
+ Args: ch -- character to output. The stream of characters coming to
+ this function is expected to be UTF-8. State is kept between
+ calls in order to collect up the octets needed for a single
+ Unicode character.
+
+ Result: character output
+ cursor position variables updated
+ ----*/
+void
+Writechar(unsigned int ch, int new_esc_len)
+{
+ static unsigned char cbuf[6];
+ static unsigned char *cbufp = cbuf;
+
+ if(cbufp < cbuf+sizeof(cbuf)){
+ unsigned char *inputp;
+ unsigned long remaining_octets;
+ UCS ucs;
+
+ *cbufp++ = (unsigned char) ch;
+ inputp = cbuf;
+ remaining_octets = (cbufp - cbuf) * sizeof(unsigned char);
+ ucs = (UCS) utf8_get(&inputp, &remaining_octets);
+
+ switch(ucs){
+ case U8G_ENDSTRG: /* incomplete character, wait */
+ case U8G_ENDSTRI: /* incomplete character, wait */
+ break;
+
+ default:
+ if(ucs & U8G_ERROR || ucs == UBOGON){
+ /*
+ * None of these cases is supposed to happen. If it
+ * does happen then the input stream isn't UTF-8
+ * so something is wrong. Treat each character in the
+ * input buffer as a separate error character and
+ * print a '?' for each.
+ */
+ for(inputp = cbuf; inputp < cbufp; inputp++){
+ int width = 0;
+
+ if(_col + width <= ps_global->ttyo->screen_cols){
+ Writewchar('?');
+ width++;
+ }
+ }
+
+ cbufp = cbuf; /* start over */
+ }
+ else{
+
+ /* got a good character */
+ Writewchar(ucs);
+
+ /* update the input buffer */
+ if(inputp >= cbufp) /* this should be the case */
+ cbufp = cbuf;
+ else{ /* extra chars for some reason? */
+ unsigned char *q, *newcbufp;
+
+ newcbufp = (cbufp - inputp) + cbuf;
+ q = cbuf;
+ while(inputp < cbufp)
+ *q++ = *inputp++;
+
+ cbufp = newcbufp;
+ }
+ }
+
+ break;
+ }
+ }
+ else{ /* error */
+ Writewchar('?');
+ cbufp = cbuf; /* start over */
+ }
+}
diff --git a/alpine/osdep/termout.gen.h b/alpine/osdep/termout.gen.h
new file mode 100644
index 00000000..2aff4b37
--- /dev/null
+++ b/alpine/osdep/termout.gen.h
@@ -0,0 +1,54 @@
+/*
+ * $Id: termout.gen.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_TERMOUT_GEN_INCLUDED
+#define PINE_OSDEP_TERMOUT_GEN_INCLUDED
+
+
+extern int _line;
+extern int _col;
+
+
+/* exported prototypes */
+void PutLine0(int, int, register char *);
+void PutLine0n8b(int, int, register char *, int, HANDLE_S *);
+void PutLine1(int, int, char *, void *);
+void PutLine2(int, int, char *, void *, void *);
+void PutLine3(int, int, char *, void *, void *, void *);
+void PutLine4(int, int, char *, void *, void *, void *, void *);
+void PutLine5(int, int, char *, void *, void *, void *, void *, void *);
+int Centerline(int, char *);
+void ClearLine(int);
+void ClearLines(int, int);
+void CleartoEOLN(void);
+void ClearScreen(void);
+void clear_cursor_pos(void);
+void Writechar(unsigned int, int);
+void Write_to_screen(char *);
+void Write_to_screen_n(char *, int);
+void init_screen(void);
+void end_screen(char *, int);
+int config_screen(struct ttyo **);
+void MoveCursor(int, int);
+void Writewchar(UCS);
+void icon_text(char *, int);
+int get_windsize(struct ttyo *);
+int BeginScroll(int, int);
+void EndScroll(void);
+int ScrollRegion(int);
+
+
+#endif /* PINE_OSDEP_TERMOUT_GEN_INCLUDED */
diff --git a/alpine/osdep/termout.unx.c b/alpine/osdep/termout.unx.c
new file mode 100644
index 00000000..faf8c7f9
--- /dev/null
+++ b/alpine/osdep/termout.unx.c
@@ -0,0 +1,1002 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: termout.unx.c 955 2008-03-06 23:52:36Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/osdep/color.h"
+
+#include "../../pith/debug.h"
+#include "../../pith/conf.h"
+#include "../../pith/newmail.h"
+#include "../../pith/charconv/utf8.h"
+
+#include "../../pico/estruct.h"
+#include "../../pico/pico.h"
+#include "../../pico/edef.h"
+#include "../../pico/efunc.h"
+#include "../../pico/osdep/color.h"
+
+#include "../status.h"
+#include "../keymenu.h"
+#include "../titlebar.h"
+
+#include "termout.gen.h"
+#include "termout.unx.h"
+
+
+/*======================================================================
+ Routines for painting the screen
+ - figure out what the terminal type is
+ - deal with screen size changes
+ - save special output sequences
+ - the usual screen clearing, cursor addressing and scrolling
+
+
+ This library gives programs the ability to easily access the
+ termcap information and write screen oriented and raw input
+ programs. The routines can be called as needed, except that
+ to use the cursor / screen routines there must be a call to
+ InitScreen() first. The 'Raw' input routine can be used
+ independently, however. (Elm comment)
+
+ Not sure what the original source of this code was. It got to be
+ here as part of ELM. It has been changed significantly from the
+ ELM version to be more robust in the face of inconsistent terminal
+ autowrap behaviour. Also, the unused functions were removed, it was
+ made to pay attention to the window size, and some code was made nicer
+ (in my opinion anyways). It also outputs the terminal initialization
+ strings and provides for minimal scrolling and detects terminals
+ with out enough capabilities. (Pine comment, 1990)
+
+
+This code used to pay attention to the "am" auto margin and "xn"
+new line glitch fields, but they were so often incorrect because many
+terminals can be configured to do either that we've taken it out. It
+now assumes it dosn't know where the cursor is after outputing in the
+80th column.
+*/
+
+
+static int _lines, _columns;
+
+
+/*
+ * Internal prototypes
+ */
+static int outchar(int);
+static void moveabsolute(int, int);
+static void CursorUp(int);
+static void CursorDown(int);
+static void CursorLeft(int);
+static void CursorRight(int);
+
+
+extern char *_clearscreen, *_moveto, *_up, *_down, *_right, *_left,
+ *_setinverse, *_clearinverse,
+ *_cleartoeoln, *_cleartoeos,
+ *_startinsert, *_endinsert, *_insertchar, *_deletechar,
+ *_deleteline, *_insertline,
+ *_scrollregion, *_scrollup, *_scrolldown,
+ *_termcap_init, *_termcap_end;
+extern char term_name[];
+extern int _tlines, _tcolumns, _bce;
+
+static enum {NoScroll,UseScrollRegion,InsertDelete} _scrollmode;
+
+extern int tputs(char *, int, int (*)(int));
+extern char *tgoto(char *, int, int);
+
+
+
+/*----------------------------------------------------------------------
+ Initialize the screen for output, set terminal type, etc
+
+ Args: tt -- Pointer to variable to store the tty output structure.
+
+ Result: terminal size is discovered and set in pine state
+ termcap entry is fetched and stored
+ make sure terminal has adequate capabilites
+ evaluate scrolling situation
+ returns status of indicating the state of the screen/termcap entry
+
+ Returns:
+ -1 indicating no terminal name associated with this shell,
+ -2..-n No termcap for this terminal type known
+ -3 Can't open termcap file
+ -4 Terminal not powerful enough - missing clear to eoln or screen
+ or cursor motion
+ ----*/
+int
+config_screen(struct ttyo **tt)
+{
+ struct ttyo *ttyo;
+ int err;
+
+ ttyo = (struct ttyo *)fs_get(sizeof (struct ttyo));
+
+ _line = 0; /* where are we right now?? */
+ _col = 0; /* assume zero, zero... */
+
+ /*
+ * This is an ugly hack to let vtterminalinfo know it's being called
+ * from pine.
+ */
+ Pmaster = (PICO *)1;
+ if((err = vtterminalinfo(F_ON(F_TCAP_WINS, ps_global))) != 0)
+ return(err);
+
+ Pmaster = NULL;
+
+ if(_tlines <= 0)
+ _lines = DEFAULT_LINES_ON_TERMINAL;
+ else
+ _lines = _tlines;
+
+ if(_tcolumns <= 0)
+ _columns = DEFAULT_COLUMNS_ON_TERMINAL;
+ else
+ _columns = _tcolumns;
+
+ get_windsize(ttyo);
+
+ ttyo->header_rows = 2;
+ ttyo->footer_rows = 3;
+
+ /*---- Make sure this terminal has the capability.
+ All we need is cursor address, clear line, and
+ reverse video.
+ ---*/
+ if(_moveto == NULL || _cleartoeoln == NULL ||
+ _setinverse == NULL || _clearinverse == NULL) {
+ return(-4);
+ }
+
+ dprint((1, "Terminal type: %s\n", term_name ? term_name : "?"));
+
+ /*------ Figure out scrolling mode -----*/
+ if(_scrollregion != NULL && _scrollregion[0] != '\0' &&
+ _scrollup != NULL && _scrollup[0] != '\0'){
+ _scrollmode = UseScrollRegion;
+ } else if(_insertline != NULL && _insertline[0] != '\0' &&
+ _deleteline != NULL && _deleteline[0] != '\0') {
+ _scrollmode = InsertDelete;
+ } else {
+ _scrollmode = NoScroll;
+ }
+ dprint((7, "Scroll mode: %s\n",
+ _scrollmode==NoScroll ? "No Scroll" :
+ _scrollmode==InsertDelete ? "InsertDelete" : "Scroll Regions"));
+
+ if (!_left) {
+ _left = "\b";
+ }
+
+ *tt = ttyo;
+
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Initialize the screen with the termcap string
+ ----*/
+void
+init_screen(void)
+{
+ if(_termcap_init) /* init using termcap's rule */
+ tputs(_termcap_init, 1, outchar);
+
+ /* and make sure there are no scrolling surprises! */
+ BeginScroll(0, ps_global->ttyo->screen_rows - 1);
+
+ pico_toggle_color(0);
+ switch(ps_global->color_style){
+ case COL_NONE:
+ case COL_TERMDEF:
+ pico_set_color_options(pico_trans_color() ? COLOR_TRANS_OPT : 0);
+ break;
+ case COL_ANSI8:
+ pico_set_color_options(COLOR_ANSI8_OPT|COLOR_TRANS_OPT);
+ break;
+ case COL_ANSI16:
+ pico_set_color_options(COLOR_ANSI16_OPT|COLOR_TRANS_OPT);
+ break;
+ case COL_ANSI256:
+ pico_set_color_options(COLOR_ANSI256_OPT|COLOR_TRANS_OPT);
+ break;
+ }
+
+ if(ps_global->color_style != COL_NONE)
+ pico_toggle_color(1);
+
+ /* set colors */
+ if(pico_usingcolor()){
+ if(ps_global->VAR_NORM_FORE_COLOR)
+ pico_nfcolor(ps_global->VAR_NORM_FORE_COLOR);
+
+ if(ps_global->VAR_NORM_BACK_COLOR)
+ pico_nbcolor(ps_global->VAR_NORM_BACK_COLOR);
+
+ if(ps_global->VAR_REV_FORE_COLOR)
+ pico_rfcolor(ps_global->VAR_REV_FORE_COLOR);
+
+ if(ps_global->VAR_REV_BACK_COLOR)
+ pico_rbcolor(ps_global->VAR_REV_BACK_COLOR);
+
+ pico_set_normal_color();
+ }
+
+ /* and make sure icon text starts out consistent */
+ icon_text(NULL, IT_NEWMAIL);
+ fflush(stdout);
+}
+
+
+
+
+/*----------------------------------------------------------------------
+ Get the current window size
+
+ Args: ttyo -- pointer to structure to store window size in
+
+ NOTE: we don't override the given values unless we know better
+ ----*/
+int
+get_windsize(struct ttyo *ttyo)
+{
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+ struct winsize win;
+
+ /*
+ * Get the window size from the tty driver. If we can't fish it from
+ * stdout (pine's output is directed someplace else), try stdin (which
+ * *must* be associated with the terminal; see init_tty_driver)...
+ */
+ if(ioctl(1, TIOCGWINSZ, &win) >= 0 /* 1 is stdout */
+ || ioctl(0, TIOCGWINSZ, &win) >= 0){ /* 0 is stdin */
+ if(win.ws_row)
+ _lines = MIN(win.ws_row, MAX_SCREEN_ROWS);
+
+ if(win.ws_col)
+ _columns = MIN(win.ws_col, MAX_SCREEN_COLS);
+
+ dprint((2, "new win size -----<%d %d>------\n",
+ _lines, _columns));
+ }
+ else{
+ /* Depending on the OS, the ioctl() may have failed because
+ of a 0 rows, 0 columns setting. That happens on DYNIX/ptx 1.3
+ (with a kernel patch that happens to involve the negotiation
+ of window size in the telnet streams module.) In this case
+ the error is EINVARG. Leave the default settings. */
+ dprint((1, "ioctl(TIOCWINSZ) failed :%s\n",
+ error_description(errno)));
+ }
+#endif
+
+ ttyo->screen_cols = MIN(_columns, MAX_SCREEN_COLS);
+ ttyo->screen_rows = MIN(_lines, MAX_SCREEN_ROWS);
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ End use of the screen.
+ Print status message, if any.
+ Flush status messages.
+ ----*/
+void
+end_screen(char *message, int exit_val)
+{
+ int footer_rows_was_one = 0;
+
+ if(!panicking()){
+
+ dprint((9, "end_screen called\n"));
+
+ if(FOOTER_ROWS(ps_global) == 1){
+ footer_rows_was_one++;
+ FOOTER_ROWS(ps_global) = 3;
+ mark_status_unknown();
+ }
+
+ flush_status_messages(exit_val ? 0 : 1);
+ blank_keymenu(_lines - 2, 0);
+ MoveCursor(_lines - 2, 0);
+ }
+
+ /* unset colors */
+ if(pico_usingcolor())
+ pico_endcolor();
+
+ if(_termcap_end != NULL)
+ tputs(_termcap_end, 1, outchar);
+
+ if(!panicking()){
+
+ if(message){
+ printf("%s\r\n", message);
+ }
+
+ if(F_ON(F_ENABLE_XTERM_NEWMAIL, ps_global) && getenv("DISPLAY"))
+ icon_text("xterm", IT_NEWMAIL);
+
+ fflush(stdout);
+
+ if(footer_rows_was_one){
+ FOOTER_ROWS(ps_global) = 1;
+ mark_status_unknown();
+ }
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Clear the terminal screen
+
+ Result: The screen is cleared
+ internal cursor position set to 0,0
+ ----*/
+void
+ClearScreen(void)
+{
+ _line = 0; /* clear leaves us at top... */
+ _col = 0;
+
+ if(ps_global->in_init_seq)
+ return;
+
+ mark_status_unknown();
+ mark_keymenu_dirty();
+ mark_titlebar_dirty();
+
+ /*
+ * If the terminal doesn't have back color erase, then we have to
+ * erase manually to preserve the background color.
+ */
+ if(pico_usingcolor() && (!_bce || !_clearscreen)){
+ ClearLines(0, _lines-1);
+ MoveCursor(0, 0);
+ }
+ else if(_clearscreen){
+ tputs(_clearscreen, 1, outchar);
+ moveabsolute(0, 0); /* some clearscreens don't move correctly */
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Internal move cursor to absolute position
+
+ Args: col -- column to move cursor to
+ row -- row to move cursor to
+
+ Result: cursor is moved (variables, not updates)
+ ----*/
+
+static void
+moveabsolute(int col, int row)
+{
+
+ char *stuff, *tgoto();
+
+ stuff = tgoto(_moveto, col, row);
+ tputs(stuff, 1, outchar);
+}
+
+
+/*----------------------------------------------------------------------
+ Move the cursor to the row and column number
+ Args: row number
+ column number
+
+ Result: Cursor moves
+ internal position updated
+ ----*/
+void
+MoveCursor(int row, int col)
+{
+ /** move cursor to the specified row column on the screen.
+ 0,0 is the top left! **/
+
+ int scrollafter = 0;
+
+ /* we don't want to change "rows" or we'll mangle scrolling... */
+
+ if(ps_global->in_init_seq)
+ return;
+
+ if (col < 0)
+ col = 0;
+ if (col >= ps_global->ttyo->screen_cols)
+ col = ps_global->ttyo->screen_cols - 1;
+ if (row < 0)
+ row = 0;
+ if (row > ps_global->ttyo->screen_rows) {
+ if (col == 0)
+ scrollafter = row - ps_global->ttyo->screen_rows;
+ row = ps_global->ttyo->screen_rows;
+ }
+
+ if (!_moveto)
+ return;
+
+ if (_col >= ps_global->ttyo->screen_cols)
+ _col = ps_global->ttyo->screen_cols - 1;
+
+ if (row == _line) {
+ if (col == _col)
+ return; /* already there! */
+
+ else if (abs(col - _col) < 5) { /* within 5 spaces... */
+ if (col > _col && _right)
+ CursorRight(col - _col);
+ else if (col < _col && _left)
+ CursorLeft(_col - col);
+ else
+ moveabsolute(col, row);
+ }
+ else /* move along to the new x,y loc */
+ moveabsolute(col, row);
+ }
+ else if (col == _col && abs(row - _line) < 5) {
+ if (row < _line && _up)
+ CursorUp(_line - row);
+ else if (_line > row && _down)
+ CursorDown(row - _line);
+ else
+ moveabsolute(col, row);
+ }
+ else if (_line == row-1 && col == 0) {
+ putchar('\n'); /* that's */
+ putchar('\r'); /* easy! */
+ }
+ else
+ moveabsolute(col, row);
+
+ _line = row; /* to ensure we're really there... */
+ _col = col;
+
+ if (scrollafter) {
+ while (scrollafter--) {
+ putchar('\n');
+ putchar('\r');
+
+ }
+ }
+
+ return;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Newline, move the cursor to the start of next line
+
+ Result: Cursor moves
+ ----*/
+void
+NewLine(void)
+{
+ /** move the cursor to the beginning of the next line **/
+
+ Writechar('\n', 0);
+ Writechar('\r', 0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Move cursor up n lines with terminal escape sequence
+
+ Args: n -- number of lines to go up
+
+ Result: cursor moves,
+ internal position updated
+
+ Only for ttyout use; not outside callers
+ ----*/
+static void
+CursorUp(int n)
+{
+ /** move the cursor up 'n' lines **/
+ /** Calling function must check that _up is not null before calling **/
+
+ _line = (_line-n > 0? _line - n: 0); /* up 'n' lines... */
+
+ while (n-- > 0)
+ tputs(_up, 1, outchar);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Move cursor down n lines with terminal escape sequence
+
+ Arg: n -- number of lines to go down
+
+ Result: cursor moves,
+ internal position updated
+
+ Only for ttyout use; not outside callers
+ ----*/
+static void
+CursorDown(int n)
+{
+ /** move the cursor down 'n' lines **/
+ /** Caller must check that _down is not null before calling **/
+
+ _line = (_line+n < ps_global->ttyo->screen_rows ? _line + n
+ : ps_global->ttyo->screen_rows);
+ /* down 'n' lines... */
+
+ while (n-- > 0)
+ tputs(_down, 1, outchar);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Move cursor left n lines with terminal escape sequence
+
+ Args: n -- number of lines to go left
+
+ Result: cursor moves,
+ internal position updated
+
+ Only for ttyout use; not outside callers
+ ----*/
+static void
+CursorLeft(int n)
+{
+ /** move the cursor 'n' characters to the left **/
+ /** Caller must check that _left is not null before calling **/
+
+ _col = (_col - n> 0? _col - n: 0); /* left 'n' chars... */
+
+ while (n-- > 0)
+ tputs(_left, 1, outchar);
+}
+
+
+/*----------------------------------------------------------------------
+ Move cursor right n lines with terminal escape sequence
+
+ Args: number of lines to go right
+
+ Result: cursor moves,
+ internal position updated
+
+ Only for ttyout use; not outside callers
+ ----*/
+static void
+CursorRight(int n)
+{
+ /** move the cursor 'n' characters to the right (nondestructive) **/
+ /** Caller must check that _right is not null before calling **/
+
+ _col = (_col+n < ps_global->ttyo->screen_cols? _col + n :
+ ps_global->ttyo->screen_cols); /* right 'n' chars... */
+
+ while (n-- > 0)
+ tputs(_right, 1, outchar);
+
+}
+
+
+/*----------------------------------------------------------------------
+ Go into scrolling mode, that is set scrolling region if applicable
+
+ Args: top -- top line of region to scroll
+ bottom -- bottom line of region to scroll
+ (These are zero-origin numbers)
+
+ Result: either set scrolling region or
+ save values for later scrolling
+ returns -1 if we can't scroll
+
+ Unfortunately this seems to leave the cursor in an unpredictable place
+ at least the manuals don't say where, so we force it here.
+-----*/
+static int __t, __b;
+
+int
+BeginScroll(int top, int bottom)
+{
+ char *stuff;
+
+ if(_scrollmode == NoScroll)
+ return(-1);
+
+ __t = top;
+ __b = bottom;
+ if(_scrollmode == UseScrollRegion){
+ stuff = tgoto(_scrollregion, bottom, top);
+ tputs(stuff, 1, outchar);
+ /*-- a location very far away to force a cursor address --*/
+ _line = FARAWAY;
+ _col = FARAWAY;
+ }
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ End scrolling -- clear scrolling regions if necessary
+
+ Result: Clear scrolling region on terminal
+ -----*/
+void
+EndScroll(void)
+{
+ if(_scrollmode == UseScrollRegion && _scrollregion != NULL){
+ /* Use tgoto even though we're not cursor addressing because
+ the format of the capability is the same.
+ */
+ char *stuff = tgoto(_scrollregion, ps_global->ttyo->screen_rows -1, 0);
+ tputs(stuff, 1, outchar);
+ /*-- a location very far away to force a cursor address --*/
+ _line = FARAWAY;
+ _col = FARAWAY;
+ }
+}
+
+
+/* ----------------------------------------------------------------------
+ Scroll the screen using insert/delete or scrolling regions
+
+ Args: lines -- number of lines to scroll, positive forward
+
+ Result: Screen scrolls
+ returns 0 if scroll succesful, -1 if not
+
+ positive lines goes foward (new lines come in at bottom
+ Leaves cursor at the place to insert put new text
+
+ 0,0 is the upper left
+ -----*/
+int
+ScrollRegion(int lines)
+{
+ int l;
+
+ if(lines == 0)
+ return(0);
+
+ if(_scrollmode == UseScrollRegion) {
+ if(lines > 0) {
+ MoveCursor(__b, 0);
+ for(l = lines ; l > 0 ; l--)
+ tputs((_scrolldown == NULL || _scrolldown[0] =='\0') ? "\n" :
+ _scrolldown, 1, outchar);
+ } else {
+ MoveCursor(__t, 0);
+ for(l = -lines; l > 0; l--)
+ tputs(_scrollup, 1, outchar);
+ }
+ } else if(_scrollmode == InsertDelete) {
+ if(lines > 0) {
+ MoveCursor(__t, 0);
+ for(l = lines; l > 0; l--)
+ tputs(_deleteline, 1, outchar);
+ MoveCursor(__b, 0);
+ for(l = lines; l > 0; l--)
+ tputs(_insertline, 1, outchar);
+ } else {
+ for(l = -lines; l > 0; l--) {
+ MoveCursor(__b, 0);
+ tputs(_deleteline, 1, outchar);
+ MoveCursor(__t, 0);
+ tputs(_insertline, 1, outchar);
+ }
+ }
+ } else {
+ return(-1);
+ }
+ fflush(stdout);
+ return(0);
+}
+
+
+void
+Writewchar(UCS ucs)
+{
+ if(ps_global->in_init_seq /* silent */
+ || (F_ON(F_BLANK_KEYMENU, ps_global) /* or bottom, */
+ /* right cell */
+ && _line + 1 == ps_global->ttyo->screen_rows
+ && _col + 1 == ps_global->ttyo->screen_cols))
+ return;
+
+
+ if(ucs == LINE_FEED || ucs == RETURN || ucs == BACKSPACE || ucs == BELL || ucs == TAB){
+ switch(ucs){
+ case LINE_FEED:
+ /*-- Don't have to watch out for auto wrap or newline glitch
+ because we never let it happen. See below
+ ---*/
+ putchar('\n');
+ _line = MIN(_line+1,ps_global->ttyo->screen_rows);
+ break;
+
+ case RETURN : /* move to column 0 */
+ putchar('\r');
+ _col = 0;
+ break;
+
+ case BACKSPACE : /* move back a space if not in column 0 */
+ if(_col != 0) {
+ putchar('\b');
+ _col--;
+ } /* else BACKSPACE does nothing */
+
+ break;
+
+ case BELL : /* ring the bell but don't advance _col */
+ putchar((int) ucs);
+ break;
+
+ case TAB : /* if a tab, output it */
+ do /* BUG? ignores tty driver's spacing */
+ putchar(' ');
+ /* fix from Eduardo Chappa, have to increment _col once more */
+ while(_col < ps_global->ttyo->screen_cols
+ && ((++_col)&0x07) != 0);
+ break;
+ }
+ }
+ else{
+ unsigned char obuf[MAX(MB_LEN_MAX,32)];
+ int i, width = 0, outchars = 0, printable_ascii = 0;
+
+ if(ucs < 0x80 && isprint((unsigned char) ucs)){
+ printable_ascii++; /* efficiency shortcut */
+ width = 1;
+ }
+ else
+ width = wcellwidth(ucs);
+
+ if(width < 0){
+ /*
+ * This happens when we have a Unicode character that
+ * we aren't able to print in our locale. For example,
+ * if the locale is setup with the terminal
+ * expecting ISO-8859-1 characters then there are
+ * lots of Unicode characters that can't be printed.
+ * Print a '?' instead.
+ */
+ width = 1;
+ obuf[outchars++] = '?';
+ }
+ else if(_col + width > ps_global->ttyo->screen_cols){
+ /*
+ * Hopefully this won't happen. The
+ * character overflows the right edge because it
+ * is a double wide and we're in last column.
+ * Fill with spaces instead and toss the
+ * character.
+ *
+ * Put a > in the right column to show we tried to write
+ * past the end.
+ */
+ while(outchars < ps_global->ttyo->screen_cols - _col - 1)
+ obuf[outchars++] = ' ';
+
+ obuf[outchars++] = '>';
+
+ /*
+ * In case last time through wrote in the last column and
+ * caused an autowrap, reposition cursor.
+ */
+ if(_col >= ps_global->ttyo->screen_cols)
+ moveabsolute(ps_global->ttyo->screen_cols-1, _line);
+ }
+ else{
+ /*
+ * Convert the ucs into the multibyte
+ * character that corresponds to the
+ * ucs in the users locale.
+ */
+ if(printable_ascii)
+ obuf[outchars++] = (unsigned char) ucs;
+ else{
+ outchars = wtomb((char *) obuf, ucs);
+ if(outchars < 0){
+ width = 1;
+ obuf[0] = '?';
+ outchars = 1;
+ }
+ }
+ }
+
+ _col += width;
+ for(i = 0; i < outchars; i++)
+ putchar(obuf[i]);
+ }
+
+ /*
+ * We used to wrap by moving to the next line and making _col = 0
+ * when we went past the end. We don't believe that this is useful
+ * anymore. If we wrap it is unintentional. Instead, stay at the
+ * end of the line. We need to be a little careful because we're
+ * moving to a different place than _col is set to, but since _col
+ * is off the right edge, it should be ok.
+ */
+ if(_col >= ps_global->ttyo->screen_cols) {
+ _col = ps_global->ttyo->screen_cols;
+ moveabsolute(ps_global->ttyo->screen_cols-1, _line);
+ }
+}
+
+
+void
+Write_to_screen(char *string) /* UNIX */
+
+{
+ while(*string)
+ Writechar((unsigned char) *string++, 0);
+}
+
+
+/*----------------------------------------------------------------------
+ Write no more than n chars of string to screen at current cursor position
+
+ Args: string -- string to be output
+ n -- number of chars to output
+
+ Result: String written to the screen
+ ----*/
+void
+Write_to_screen_n(char *string, int n) /* UNIX */
+
+
+{
+ while(n-- && *string)
+ Writechar((unsigned char) *string++, 0);
+}
+
+
+/*----------------------------------------------------------------------
+ Clear screen to end of line on current line
+
+ Result: Line is cleared
+ ----*/
+void
+CleartoEOLN(void)
+{
+ int c, starting_col, starting_line;
+ char *last_bg_color;
+
+ /*
+ * If the terminal doesn't have back color erase, then we have to
+ * erase manually to preserve the background color.
+ */
+ if(pico_usingcolor() && (!_bce || !_cleartoeoln)){
+ starting_col = _col;
+ starting_line = _line;
+ last_bg_color = pico_get_last_bg_color();
+ pico_set_nbg_color();
+ for(c = _col; c < _columns; c++)
+ Writechar(' ', 0);
+
+ MoveCursor(starting_line, starting_col);
+ if(last_bg_color){
+ (void)pico_set_bg_color(last_bg_color);
+ fs_give((void **)&last_bg_color);
+ }
+ }
+ else if(_cleartoeoln)
+ tputs(_cleartoeoln, 1, outchar);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Clear screen to end of screen from current point
+
+ Result: screen is cleared
+ ----*/
+void
+CleartoEOS(void)
+{
+ int starting_col, starting_line;
+
+ /*
+ * If the terminal doesn't have back color erase, then we have to
+ * erase manually to preserve the background color.
+ */
+ if(pico_usingcolor() && (!_bce || !_cleartoeos)){
+ starting_col = _col;
+ starting_line = _line;
+ CleartoEOLN();
+ ClearLines(_line+1, _lines-1);
+ MoveCursor(starting_line, starting_col);
+ }
+ else if(_cleartoeos)
+ tputs(_cleartoeos, 1, outchar);
+}
+
+
+
+/*----------------------------------------------------------------------
+ function to output character used by termcap
+
+ Args: c -- character to output
+
+ Result: character output to screen via stdio
+ ----*/
+int
+outchar(int c)
+{
+ /** output the given character. From tputs... **/
+ /** Note: this CANNOT be a macro! **/
+
+ return(putc((unsigned char)c, stdout));
+}
+
+
+
+/*----------------------------------------------------------------------
+ function to output string such that it becomes icon text
+
+ Args: s -- string to write
+
+ Result: string indicated become our "icon" text
+ ----*/
+void
+icon_text(char *s, int type)
+{
+ static enum {ukn, yes, no} xterm;
+ char *str, *converted, *use_this;
+
+ if(xterm == ukn)
+ xterm = (getenv("DISPLAY") != NULL) ? yes : no;
+
+ if(F_ON(F_ENABLE_XTERM_NEWMAIL,ps_global) && xterm == yes){
+ fputs("\033]0;", stdout);
+
+ str = s ? s : ps_global->pine_name;
+ converted = convert_to_locale(str);
+ use_this = converted ? converted : str;
+
+ fputs(use_this, stdout);
+
+ fputs("\007", stdout);
+ fputs("\033]1;", stdout);
+
+ fputs(use_this, stdout);
+
+ fputs("\007", stdout);
+ fflush(stdout);
+
+ if(converted)
+ fs_give((void **) &converted);
+ }
+}
+
+
diff --git a/alpine/osdep/termout.unx.h b/alpine/osdep/termout.unx.h
new file mode 100644
index 00000000..4d9d1b5c
--- /dev/null
+++ b/alpine/osdep/termout.unx.h
@@ -0,0 +1,24 @@
+/*
+ * $Id: termout.unx.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_TERMOUT_UNX_INCLUDED
+#define PINE_OSDEP_TERMOUT_UNX_INCLUDED
+
+
+/* exported prototypes */
+
+
+#endif /* PINE_OSDEP_TERMOUT_UNX_INCLUDED */
diff --git a/alpine/osdep/termout.win b/alpine/osdep/termout.win
new file mode 100644
index 00000000..f3f027ee
--- /dev/null
+++ b/alpine/osdep/termout.win
@@ -0,0 +1,127 @@
+#line 2 "osdep/termout.win"
+
+
+
+
+
+/*----------------------------------------------------------------------
+ End use of the screen.
+ ----------------------------------------------------------------------*/
+
+
+
+
+/*----------------------------------------------------------------------
+ Newline, move the cursor to the start of next line
+
+ Input: none
+
+ Result: Cursor moves
+ ----------------------------------------------------------------------*/
+void
+NewLine()
+{
+ /** move the cursor to the beginning of the next line **/
+
+ MoveCursor(_line+1, 0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Insert character on screen pushing others right
+
+ Input: character to output
+ termcap escape sequences
+
+ Result: charcter is inserted if possible
+ return -1 if it can't be done
+ ----------------------------------------------------------------------*/
+InsertChar(c)
+ int c;
+{
+ mswin_inschar (c);
+ return (0);
+}
+
+
+/*----------------------------------------------------------------------
+ Delete n characters from line, sliding rest of line left
+
+ Input: number of characters to delete
+ termcap escape sequences
+
+ Result: characters deleted on screen
+ returns -1 if it wasn't done
+ ----------------------------------------------------------------------*/
+DeleteChar(n)
+ int n;
+{
+ char c;
+ int oc; /* original column */
+
+ mswin_delchar ();
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Go into scrolling mode, that is set scrolling region if applicable
+
+ Input: top line of region to scroll
+ bottom line of region to scroll
+
+ Result: either set scrolling region or
+ save values for later scrolling
+ returns -1 if we can't scroll
+
+ Unfortunately this seems to leave the cursor in an unpredictable place
+ at least the manuals don't say were, so we force it here.
+----------------------------------------------------------------------*/
+
+/*----------------------------------------------------------------------
+ End scrolling -- clear scrolling regions if necessary
+
+ Input: none
+
+ Result: Clear scrolling region on terminal
+ ----------------------------------------------------------------------*/
+
+
+/* ----------------------------------------------------------------------
+ Scroll the screen using insert/delete or scrolling regions
+
+ Input: number of lines to scroll, positive forward
+
+ Result: Screen scrolls
+ returns 0 if scroll succesful, -1 if not
+
+ positive lines goes foward (new lines come in at bottom
+ Leaves cursor at the place to insert put new text
+
+ 0,0 is the upper left
+ ----------------------------------------------------------------------*/
+
+
+
+
+
+
+
+
+
+/*----------------------------------------------------------------------
+ Clear screen to end of screen from current point
+
+ Input: none
+
+ Result: screen is cleared
+ ----------------------------------------------------------------------*/
+void
+CleartoEOS() /* DOS */
+{
+ mswin_eeop();
+}
+
+
diff --git a/alpine/osdep/termout.wnt.c b/alpine/osdep/termout.wnt.c
new file mode 100644
index 00000000..fae661aa
--- /dev/null
+++ b/alpine/osdep/termout.wnt.c
@@ -0,0 +1,1862 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: termout.unx.c 159 2006-10-02 22:00:13Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#include <system.h>
+#include <general.h>
+
+#include "../../c-client/mail.h" /* for MAILSTREAM and friends */
+#include "../../c-client/osdep.h"
+#include "../../c-client/rfc822.h" /* for soutr_t and such */
+#include "../../c-client/misc.h" /* for cpystr proto */
+#include "../../c-client/utf8.h" /* for CHARSET and such*/
+#include "../../c-client/imap4r1.h"
+
+#include "../../pith/osdep/color.h"
+
+#include "../../pith/charconv/filesys.h"
+
+#include "../../pith/debug.h"
+#include "../../pith/conf.h"
+#include "../../pith/newmail.h"
+
+#include "../../pico/estruct.h"
+#include "../../pico/pico.h"
+#include "../../pico/keydefs.h"
+
+#include "../../pico/osdep/mswin.h"
+#include "../../pico/osdep/color.h"
+
+#include "../flagmaint.h"
+#include "../status.h"
+#include "../keymenu.h"
+#include "../titlebar.h"
+
+#include "termout.gen.h"
+#include "termout.wnt.h"
+
+/*======================================================================
+ Routines for painting the screen
+ - figure out what the terminal type is
+ - deal with screen size changes
+ - save special output sequences
+ - the usual screen clearing, cursor addressing and scrolling
+
+
+ This library gives programs the ability to easily access the
+ termcap information and write screen oriented and raw input
+ programs. The routines can be called as needed, except that
+ to use the cursor / screen routines there must be a call to
+ InitScreen() first. The 'Raw' input routine can be used
+ independently, however. (Elm comment)
+
+ Not sure what the original source of this code was. It got to be
+ here as part of ELM. It has been changed significantly from the
+ ELM version to be more robust in the face of inconsistent terminal
+ autowrap behaviour. Also, the unused functions were removed, it was
+ made to pay attention to the window size, and some code was made nicer
+ (in my opinion anyways). It also outputs the terminal initialization
+ strings and provides for minimal scrolling and detects terminals
+ with out enough capabilities. (Pine comment, 1990)
+
+*/
+
+#define PUTLINE_BUFLEN 256
+
+static int _lines, _columns;
+
+BOOL CALLBACK __export
+args_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+BOOL CALLBACK __export
+login_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+BOOL CALLBACK __export
+flag_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+BOOL CALLBACK __export
+sort_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+BOOL CALLBACK __export
+config_vars_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+BOOL CALLBACK __export
+config_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+void
+init_screen(void)
+{
+ /*
+ * If we want to show it, turn it on. If we don't care, put it back
+ * to the way it was originally.
+ */
+ mswin_showcaret(F_ON(F_SHOW_CURSOR, ps_global));
+ mswin_trayicon(F_ON(F_ENABLE_TRAYICON, ps_global));
+ return; /* NO OP */
+}
+
+void
+end_screen(char *message, int exit_val)
+{
+ int footer_rows_was_one = 0;
+
+ if(panicking())
+ return;
+
+ if(FOOTER_ROWS(ps_global) == 1){
+ footer_rows_was_one++;
+ FOOTER_ROWS(ps_global) = 3;
+ mark_status_unknown();
+ }
+
+ flush_status_messages(exit_val ? 0 : 1);
+
+ blank_keymenu(_lines - 2, 0);
+
+ if(message){
+ StartInverse();
+ PutLine0(_lines - 2, 0, message);
+ }
+
+ EndInverse();
+
+ MoveCursor(_lines - 1, 0);
+
+ mswin_showcaret(F_ON(F_SHOW_CURSOR, ps_global));
+
+ if(footer_rows_was_one){
+ FOOTER_ROWS(ps_global) = 1;
+ mark_status_unknown();
+ }
+}
+
+/*----------------------------------------------------------------------
+ Initialize the screen for output, set terminal type, etc
+
+ Args: tt -- Pointer to variable to store the tty output structure.
+
+ Result: terminal size is discovered and set pine state
+ termcap entry is fetched and stored in local variables
+ make sure terminal has adequate capabilites
+ evaluate scrolling situation
+ returns status of indicating the state of the screen/termcap entry
+
+ Returns:
+ -1 indicating no terminal name associated with this shell,
+ -2..-n No termcap for this terminal type known
+ -3 Can't open termcap file
+ -4 Terminal not powerful enough - missing clear to eoln or screen
+ or cursor motion
+
+ ----*/
+int
+config_screen(struct ttyo **tt)
+{
+ struct ttyo *ttyo;
+
+ _line = 0; /* where are we right now?? */
+ _col = 0; /* assume zero, zero... */
+
+ mswin_getscreensize(&_lines, &_columns);
+ if (_lines > MAX_SCREEN_ROWS)
+ _lines = MAX_SCREEN_ROWS;
+ if (_columns > MAX_SCREEN_COLS)
+ _columns = MAX_SCREEN_COLS;
+
+ ttyo = (struct ttyo *)fs_get(sizeof(struct ttyo));
+ ttyo->screen_cols = _columns;
+ ttyo->screen_rows = _lines ;
+ ttyo->header_rows = 2;
+ ttyo->footer_rows = 3;
+
+ *tt = ttyo;
+
+ return(0);
+}
+
+/*----------------------------------------------------------------------
+ Get the current window size
+
+ Args: ttyo -- pointer to structure to store window size in
+
+ NOTE: we don't override the given values unless we know better
+ ----*/
+int
+get_windsize(struct ttyo *ttyo)
+{
+ char fontName[LF_FACESIZE+1];
+ char fontSize[12];
+ char fontStyle[64];
+ char fontCharSet[256];
+ char windowPosition[32], windowPositionReg[32];
+ char foreColor[64], backColor[64];
+ int newRows, newCols;
+ char cursorStyle[32];
+
+ cursorStyle[0] = '\0';
+
+ /* Get the new window parameters and update the 'pinerc' variables. */
+ mswin_getwindow(fontName, sizeof(fontName), fontSize, sizeof(fontSize),
+ fontStyle, sizeof(fontStyle),
+ windowPosition, sizeof(windowPosition),
+ foreColor, sizeof(foreColor),
+ backColor, sizeof(backColor),
+ cursorStyle, sizeof(cursorStyle),
+ fontCharSet, sizeof(fontCharSet));
+
+ if(!ps_global->VAR_FONT_NAME
+ || strucmp(ps_global->VAR_FONT_NAME, fontName))
+ set_variable(V_FONT_NAME, fontName, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(!ps_global->VAR_FONT_SIZE
+ || strucmp(ps_global->VAR_FONT_SIZE, fontSize))
+ set_variable(V_FONT_SIZE, fontSize, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(!ps_global->VAR_FONT_STYLE
+ || strucmp(ps_global->VAR_FONT_STYLE, fontStyle))
+ set_variable(V_FONT_STYLE, fontStyle, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(!ps_global->VAR_FONT_CHAR_SET
+ || strucmp(ps_global->VAR_FONT_CHAR_SET, fontCharSet))
+ set_variable(V_FONT_CHAR_SET, fontCharSet, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(strnicmp(windowPosition, "MIN0", 4) && !strchr(windowPosition, '!')){
+ if(F_ON(F_STORE_WINPOS_IN_CONFIG, ps_global)){
+ if(!ps_global->VAR_WINDOW_POSITION
+ || strucmp(ps_global->VAR_WINDOW_POSITION, windowPosition))
+ set_variable(V_WINDOW_POSITION, windowPosition, 1, 0,
+ ps_global->ew_for_except_vars);
+ }
+ else{
+ /*
+ * Get the window position stored in the registry.
+ *
+ * If current window position is not in the registry or is
+ * different from what is there, save it.
+ */
+ if((!mswin_reg(MSWR_OP_GET, MSWR_PINE_POS, windowPositionReg,
+ sizeof(windowPositionReg)) ||
+ strucmp(windowPositionReg, windowPosition))
+ && (ps_global->update_registry != UREG_NEVER_SET))
+ mswin_reg(MSWR_OP_SET | MSWR_OP_FORCE, MSWR_PINE_POS, windowPosition,
+ (size_t)NULL);
+ }
+ }
+
+ mswin_getprintfont(fontName, sizeof(fontName),
+ fontSize, sizeof(fontSize),
+ fontStyle, sizeof(fontStyle),
+ fontCharSet, sizeof(fontCharSet));
+ if(!ps_global->VAR_PRINT_FONT_NAME
+ || strucmp(ps_global->VAR_PRINT_FONT_NAME, fontName))
+ set_variable(V_PRINT_FONT_NAME, fontName, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(!ps_global->VAR_PRINT_FONT_SIZE
+ || strucmp(ps_global->VAR_PRINT_FONT_SIZE, fontSize))
+ set_variable(V_PRINT_FONT_SIZE, fontSize, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(!ps_global->VAR_PRINT_FONT_STYLE
+ || strucmp(ps_global->VAR_PRINT_FONT_STYLE, fontStyle))
+ set_variable(V_PRINT_FONT_STYLE, fontStyle, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(!ps_global->VAR_PRINT_FONT_CHAR_SET
+ || strucmp(ps_global->VAR_PRINT_FONT_CHAR_SET, fontCharSet))
+ set_variable(V_PRINT_FONT_CHAR_SET, fontCharSet, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(!ps_global->VAR_NORM_FORE_COLOR
+ || strucmp(ps_global->VAR_NORM_FORE_COLOR, foreColor))
+ set_variable(V_NORM_FORE_COLOR, foreColor, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(!ps_global->VAR_NORM_BACK_COLOR
+ || strucmp(ps_global->VAR_NORM_BACK_COLOR, backColor))
+ set_variable(V_NORM_BACK_COLOR, backColor, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ if(cursorStyle[0] && !ps_global->VAR_CURSOR_STYLE
+ || strucmp(ps_global->VAR_CURSOR_STYLE, cursorStyle))
+ set_variable(V_CURSOR_STYLE, cursorStyle, 1, 0,
+ ps_global->ew_for_except_vars);
+
+ /* Get new window size. Compare to old. The window may have just
+ * moved, in which case we don't bother updating the size. */
+ mswin_getscreensize(&newRows, &newCols);
+ if (newRows == ttyo->screen_rows && newCols == ttyo->screen_cols)
+ return (NO_OP_COMMAND);
+
+ /* True resize. */
+ ttyo->screen_rows = newRows;
+ ttyo->screen_cols = newCols;
+
+ if (ttyo->screen_rows > MAX_SCREEN_ROWS)
+ ttyo->screen_rows = MAX_SCREEN_ROWS;
+ if (ttyo->screen_cols > MAX_SCREEN_COLS)
+ ttyo->screen_cols = MAX_SCREEN_COLS;
+
+ return(KEY_RESIZE);
+}
+
+/*----------------------------------------------------------------------
+ Move the cursor to the row and column number
+ Input: row number
+ column number
+
+ Result: Cursor moves
+ internal position updated
+ ----------------------------------------------------------------------*/
+void
+MoveCursor(int row, int col)
+{
+ /** move cursor to the specified row column on the screen.
+ 0,0 is the top left! **/
+
+ if(ps_global->in_init_seq)
+ return;
+
+ /*
+ * This little hack is here for screen readers, for example, a screen
+ * reader associated with a braille display. If we flash the cursor off
+ * and on in the same position, the reader will be drawn to it. Since we
+ * are calling MoveCursor every time through the message viewing loops we
+ * need to suppress the calls if they aren't really doing anything.
+ * However, mswin_move actually does more than just move the cursor.
+ * It also call FlushAccum(), which is necessary in some cases. For
+ * example, in optionally_enter the user is typing characters in and
+ * the FlushAccum() allows them to show up on the screen. The putblock
+ * call is a quick hack to get the Flush_Accum() call, which is all
+ * the putblock does when the arg is NULL.
+ */
+ if(row == _line && col == _col){
+ mswin_putblock(NULL, 0);
+ return;
+ }
+
+ mswin_move(row, col);
+ _line = row;
+ _col = col;
+}
+
+
+/*----------------------------------------------------------------------
+ Write a character to the screen, keeping track of cursor position
+
+ Input: charater to write
+
+ Result: character output
+ cursor position variables updated
+ ----------------------------------------------------------------------*/
+void
+Writewchar(UCS ucs)
+{
+ int width;
+
+ if(ps_global->in_init_seq)
+ return;
+
+ switch(ucs){
+ case LINE_FEED :
+ _line = min(_line+1,ps_global->ttyo->screen_rows);
+ _col =0;
+ mswin_move(_line, _col);
+ break;
+
+ case RETURN : /* move to column 0 */
+ _col = 0;
+ mswin_move(_line, _col);
+
+ case BACKSPACE : /* move back a space if not in column 0 */
+ if(_col > 0)
+ mswin_move(_line, --_col);
+
+ break;
+
+ case BELL : /* ring the bell but don't advance _col */
+ mswin_beep(); /* libpico call */
+ break;
+
+ case TAB: /* if a tab, output it */
+ do
+ mswin_putc(' ');
+ while(((++_col)&0x07) != 0);
+ break;
+
+ default:
+ /* pass_ctrl_chars is always 1 for Windows */
+ width = wcellwidth(ucs);
+ if(width < 0){
+ mswin_putc('?');
+ _col++;
+ }
+ else if(_col + width > ps_global->ttyo->screen_cols){
+ int i;
+
+ i = ps_global->ttyo->screen_cols - _col - 1;
+ while(i-- > 0)
+ mswin_putc(' ');
+
+ _col = ps_global->ttyo->screen_cols - 1;
+ mswin_move(_line, _col);
+ mswin_putc('>');
+ _col++;
+ }
+ else{
+ mswin_putc(ucs);
+ _col += width;
+ }
+ }
+
+ if(_col >= ps_global->ttyo->screen_cols){
+ _col = ps_global->ttyo->screen_cols;
+ mswin_move(_line, _col);
+ }
+
+ return;
+}
+
+
+/*----------------------------------------------------------------------
+ Printf style write directly to the terminal at current position
+
+ Input: printf style control string
+ number of printf style arguments
+ up to three printf style arguments
+
+ Result: Line written to the screen
+ ----------------------------------------------------------------------*/
+void
+Write_to_screen(char *string)
+{
+ if(ps_global->in_init_seq)
+ return;
+
+ mswin_puts (string);
+
+ /*
+ * mswin_puts does not keep track of where we are. Simple fix is to
+ * reset _line and _col to unknown so that the next MoveCursor will
+ * do a real move. We could check for a simple string and actually
+ * keep track. Is that useful? I don't think so.
+ *
+ * There are other places where we lose track, but by comparing with
+ * the termout.unx code where we also lose track it would seem we don't
+ * really need to keep track in those other cases.
+ */
+ _line = FARAWAY;
+ _col = FARAWAY;
+}
+
+void
+Write_to_screen_n(char *string, int n)
+{
+ if(ps_global->in_init_seq)
+ return;
+
+ mswin_puts_n (string, n);
+ _line = FARAWAY;
+ _col = FARAWAY;
+}
+
+
+/*----------------------------------------------------------------------
+ Clear the terminal screen
+ ----------------------------------------------------------------------*/
+void
+ClearScreen(void)
+{
+ _line = 0; /* clear leaves us at top... */
+ _col = 0;
+
+ if(ps_global->in_init_seq)
+ return;
+
+ mark_status_unknown();
+ mark_keymenu_dirty();
+ mark_titlebar_dirty();
+
+ mswin_move(0, 0);
+ mswin_eeop();
+}
+
+/*----------------------------------------------------------------------
+ Clear screen to end of line on current line
+ ----------------------------------------------------------------------*/
+void
+CleartoEOLN(void)
+{
+ mswin_eeol();
+}
+
+
+/*----------------------------------------------------------------------
+ function to output string such that it becomes icon text
+
+ Args: s -- string to write
+
+ Result: string indicated become our "icon" text
+ ----*/
+void
+icon_text(char *s, int type)
+{
+ if(type == IT_MCLOSED)
+ mswin_mclosedtext (s);
+ else /* IT_NEWMAIL */
+ mswin_newmailtext (s);
+}
+
+/* Scroll stuff */
+
+/*
+ * Set the current position of the scroll bar.
+ */
+void
+scroll_setpos(long newpos)
+{
+ mswin_setscrollpos (newpos);
+}
+
+/*
+ * Set the range of the scroll bar.
+ * zero disables the scroll bar.
+ */
+void
+scroll_setrange (long pagelen, long rangeend)
+{
+ if(rangeend < pagelen)
+ mswin_setscrollrange (0L, 0L);
+ else
+ mswin_setscrollrange (pagelen, rangeend + pagelen - 1L);
+}
+
+void
+EndScroll(void)
+{
+}
+
+int
+ScrollRegion(int lines)
+{
+ return(-1);
+}
+
+int
+BeginScroll(int top, int bottom)
+{
+ static int __t, __b;
+
+ __t = top;
+ __b = bottom;
+ return(-1);
+}
+
+
+/* Dialog stuff */
+#define WINDOW_USER_DATA GWL_USERDATA
+
+/*===========================================================================
+ *
+ * Dialog Data
+ *
+ * The following structures hold the state data for dialogs.
+ */
+typedef struct DLG_TYPEMAP {
+ int pineid;
+ int rsrcid;
+} DLG_TYPEMAP;
+
+typedef struct DLG_SORTDATA {
+ DLG_SORTPARAM *sortsel; /* parameter structure. */
+ int sortcount; /* Number of different sorts. */
+ DLG_TYPEMAP types[20]; /* Map pine sort types to ctrl ids. */
+} DLG_SORTDATA;
+
+typedef struct DLG_LOGINDATA {
+ NETMBX *mb;
+ LPTSTR user;
+ int userlen;
+ LPTSTR pwd;
+ int pwdlen;
+ int pwc; /* set if we're using passfiles and we don't know user yet */
+ int fixuser;
+ int prespass; /* if user wants to preserve the password */
+ int rv;
+} DLG_LOGINDATA;
+
+typedef struct DLG_CONFIGDATA {
+ char *confpath; /* UTF-8 path */
+ int confpathlen;
+ int setreg;
+ int nopinerc;
+ int rv;
+} DLG_CONFIGDATA;
+
+typedef struct installvars {
+ char *pname;
+ char *userid;
+ char *domain;
+ char *inboxpath;
+ char *smtpserver;
+ unsigned defmailclient:1;
+ unsigned defnewsclient:1;
+} INSTALLVARS_S;
+
+typedef struct DLG_CONGIGVARSDATA {
+ INSTALLVARS_S *ivars;
+ int rv;
+} DLG_CONFIGVARSDATA;
+
+typedef struct DLG_FLAGDATA {
+ struct flag_table *ftbl;
+ int flagcount;
+ HINSTANCE hInstance;
+ HWND hTextWnd;
+} DLG_FLAGDATA;
+
+int os_config_vars_dialog(INSTALLVARS_S *);
+static void GetBtnPos (HWND, HWND, RECT *);
+
+int
+init_install_get_vars(void)
+{
+ INSTALLVARS_S ivars;
+ struct pine *ps = ps_global;
+ char *p;
+
+ memset((void *)&ivars, 0, sizeof(INSTALLVARS_S));
+ if(ps->vars[V_PERSONAL_NAME].main_user_val.p)
+ ivars.pname = cpystr(ps->vars[V_PERSONAL_NAME].main_user_val.p);
+ if(ps->vars[V_USER_ID].main_user_val.p)
+ ivars.userid = cpystr(ps->vars[V_USER_ID].main_user_val.p);
+ if(ps->vars[V_USER_DOMAIN].main_user_val.p)
+ ivars.domain = cpystr(ps->vars[V_USER_DOMAIN].main_user_val.p);
+ if(ps->vars[V_INBOX_PATH].main_user_val.p)
+ ivars.inboxpath = cpystr(ps->vars[V_INBOX_PATH].main_user_val.p);
+ else if(ps->prc && ps->prc->name && (*ps->prc->name == '{')
+ && (p = strindex(ps->prc->name, '}'))){
+ ivars.inboxpath = cpystr(ps->prc->name);
+ if(p = strindex(ivars.inboxpath, '}'))
+ *(p+1) = '\0';
+ }
+ if(ps->vars[V_SMTP_SERVER].main_user_val.l
+ && *ps->vars[V_SMTP_SERVER].main_user_val.l)
+ ivars.smtpserver = cpystr(ps->vars[V_SMTP_SERVER].main_user_val.l[0]);
+ ivars.defmailclient = mswin_is_def_client(MSWR_SDC_MAIL);
+ ivars.defnewsclient = mswin_is_def_client(MSWR_SDC_NEWS);
+
+ if(os_config_vars_dialog(&ivars) == 0){
+ set_variable(V_PERSONAL_NAME, ivars.pname, 0, 0, Main);
+ set_variable(V_USER_ID, ivars.userid, 0, 0, Main);
+ set_variable(V_USER_DOMAIN, ivars.domain, 0, 0, Main);
+ set_variable(V_INBOX_PATH, ivars.inboxpath, 0, 0, Main);
+ if(ivars.smtpserver){
+ if(ps->vars[V_SMTP_SERVER].main_user_val.l){
+ fs_give((void **)&ps->vars[V_SMTP_SERVER].main_user_val.l[0]);
+ ps->vars[V_SMTP_SERVER].main_user_val.l[0]
+ = cpystr(ivars.smtpserver);
+ set_current_val(&ps->vars[V_SMTP_SERVER], TRUE, FALSE);
+ }
+ else {
+ char *tstrlist[2];
+
+ tstrlist[0] = ivars.smtpserver;
+ tstrlist[1] = NULL;
+ set_variable_list(V_SMTP_SERVER, tstrlist, 0, Main);
+ }
+ }
+ write_pinerc(ps_global, Main, WRP_NONE);
+ if(ivars.defmailclient)
+ mswin_set_def_client(MSWR_SDC_MAIL);
+ if(ivars.defnewsclient)
+ mswin_set_def_client(MSWR_SDC_NEWS);
+
+ /* Tell Windows that stuff has changed */
+ if(ivars.defmailclient || ivars.defnewsclient)
+ SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE,
+ (WPARAM)NULL, (LPARAM)NULL,
+ SMTO_NORMAL, 1000, NULL);
+ }
+
+ if(ivars.pname)
+ fs_give((void **)&ivars.pname);
+ if(ivars.userid)
+ fs_give((void **)&ivars.userid);
+ if(ivars.domain)
+ fs_give((void **)&ivars.domain);
+ if(ivars.inboxpath)
+ fs_give((void **)&ivars.inboxpath);
+ if(ivars.smtpserver)
+ fs_give((void **)&ivars.smtpserver);
+ return 0;
+}
+
+/*
+ * Show args dialog
+ */
+int
+os_argsdialog (char **arg_text)
+{
+ DLGPROC dlgprc;
+ HINSTANCE hInst;
+ HWND hWnd;
+
+ hInst = (HINSTANCE) mswin_gethinstance ();
+ hWnd = (HWND) mswin_gethwnd ();
+
+ mswin_killsplash();
+
+ dlgprc = args_dialog_proc;
+
+ DialogBoxParam (hInst, MAKEINTRESOURCE (IDD_ARGLIST), hWnd,
+ dlgprc, (LPARAM) arg_text);
+
+ return(1);
+}
+
+/*
+ * Prompt for username and password
+ */
+int
+os_login_dialog (NETMBX *mb, char *user_utf8, int userlen,
+ char *pwd_utf8, int pwdlen, int pwc, int fixuser, int *prespass)
+{
+ DLGPROC dlgprc;
+ HINSTANCE hInst;
+ HWND hWnd;
+ DLG_LOGINDATA dlgpw;
+ LPTSTR user_lptstr, pwd_lptstr;
+ char *tuser_utf8, *tpwd_utf8;
+
+ mswin_killsplash();
+ hInst = (HINSTANCE) mswin_gethinstance ();
+ hWnd = (HWND) mswin_gethwnd ();
+
+ dlgpw.mb = mb;
+
+ dlgpw.user = (LPTSTR)fs_get(userlen*sizeof(TCHAR));
+ user_lptstr = utf8_to_lptstr(user_utf8);
+ _tcsncpy(dlgpw.user, user_lptstr, userlen - 1);
+ dlgpw.user[userlen - 1] = '\0';
+ fs_give((void **) &user_lptstr);
+ dlgpw.userlen = userlen;
+
+ dlgpw.pwd = (LPTSTR)fs_get(pwdlen*sizeof(TCHAR));
+ pwd_lptstr = utf8_to_lptstr(pwd_utf8);
+ _tcsncpy(dlgpw.pwd, pwd_lptstr, pwdlen - 1);
+ dlgpw.pwd[pwdlen - 1] = '\0';
+ fs_give((void **) &pwd_lptstr);
+ dlgpw.pwdlen = pwdlen;
+
+ dlgpw.fixuser = fixuser;
+ dlgpw.pwc = pwc;
+ dlgpw.rv = 0;
+
+ dlgprc = login_dialog_proc;
+
+ DialogBoxParam (hInst, MAKEINTRESOURCE (ps_global->install_flag
+ ? IDD_LOGINDLG2 : IDD_LOGINDLG),
+ NULL, dlgprc, (LPARAM)&dlgpw);
+
+ if(dlgpw.rv == 0){
+ tuser_utf8 = lptstr_to_utf8(dlgpw.user);
+ if(tuser_utf8){
+ strncpy(user_utf8, tuser_utf8, userlen - 1);
+ user_utf8[userlen - 1] = '\0';
+ fs_give((void **) &tuser_utf8);
+ }
+
+ tpwd_utf8 = lptstr_to_utf8(dlgpw.pwd);
+ if(tpwd_utf8){
+ strncpy(pwd_utf8, tpwd_utf8, pwdlen - 1);
+ pwd_utf8[pwdlen - 1] = '\0';
+ fs_give((void **) &tpwd_utf8);
+ }
+ if(prespass)
+ (*prespass) = dlgpw.prespass;
+ }
+
+ return(dlgpw.rv);
+}
+
+/*
+ * Select message flags.
+ */
+int
+os_flagmsgdialog (struct flag_table *ftbl)
+{
+ DLGPROC dlgprc;
+ HINSTANCE hInst;
+ HWND hWnd;
+ DLG_FLAGDATA dlgflag;
+ int rval;
+
+ hInst = (HINSTANCE) mswin_gethinstance ();
+ hWnd = (HWND) mswin_gethwnd ();
+
+ dlgflag.ftbl = ftbl;
+ dlgflag.hInstance = hInst;
+ dlgflag.hTextWnd = hWnd;
+
+ dlgprc = flag_dialog_proc;
+
+ rval = DialogBoxParam (hInst, MAKEINTRESOURCE (IDD_SELECTFLAG), hWnd,
+ dlgprc, (LPARAM)&dlgflag);
+
+ return (rval);
+}
+
+/*
+ * Select a sort type.
+ */
+
+int
+os_sortdialog (DLG_SORTPARAM *sortsel)
+{
+ DLGPROC dlgprc;
+ HINSTANCE hInst;
+ HWND hWnd;
+ int i;
+ DLG_SORTDATA dlgsort;
+
+ hInst = (HINSTANCE) mswin_gethinstance ();
+ hWnd = (HWND) mswin_gethwnd ();
+
+
+ /* Build a map of pine sort types to the resource types. */
+ i = 0;
+ dlgsort.types[i].pineid = SortArrival;
+ dlgsort.types[i++].rsrcid = IDC_SORTARRIVAL;
+ dlgsort.types[i].pineid = SortDate;
+ dlgsort.types[i++].rsrcid = IDC_SORTDATE;
+ dlgsort.types[i].pineid = SortFrom;
+ dlgsort.types[i++].rsrcid = IDC_SORTFROM;
+ dlgsort.types[i].pineid = SortSubject;
+ dlgsort.types[i++].rsrcid = IDC_SORTSUBJECT;
+ dlgsort.types[i].pineid = SortSubject2;
+ dlgsort.types[i++].rsrcid = IDC_SORTORDERSUB;
+ dlgsort.types[i].pineid = SortTo;
+ dlgsort.types[i++].rsrcid = IDC_SORTTO;
+ dlgsort.types[i].pineid = SortCc;
+ dlgsort.types[i++].rsrcid = IDC_SORTCC;
+ dlgsort.types[i].pineid = SortSize;
+ dlgsort.types[i++].rsrcid = IDC_SORTSIZE;
+ dlgsort.types[i].pineid = SortThread;
+ dlgsort.types[i++].rsrcid = IDC_SORTTHREAD;
+ dlgsort.types[i].pineid = SortScore;
+ dlgsort.types[i++].rsrcid = IDC_SORTSCORE;
+ dlgsort.sortcount = i;
+ dlgsort.sortsel = sortsel;
+
+ dlgprc = sort_dialog_proc;
+
+ DialogBoxParam (hInst, MAKEINTRESOURCE (IDD_SELECTSORT), hWnd,
+ dlgprc, (LPARAM)&dlgsort);
+
+ return(1);
+}
+
+
+/*
+ * Dialog proc to handle index sort selection.
+ *
+ * Configures the dialog box on init and retrieves the settings on exit.
+ */
+BOOL CALLBACK __export
+args_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ BOOL ret = FALSE;
+ long i, j, block_size;
+ char **args_text;
+ LPTSTR args_text_lptstr, args_block_lptstr;
+
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+
+ args_text = (char **)lParam;
+
+ /*
+ * First convert char *'s over to one big block of
+ * Unicode
+ */
+ i = 0;
+ while(args_text && *args_text){
+ i += strlen(*args_text++);
+ i += 2;
+ }
+ block_size = i;
+
+ args_block_lptstr = (LPTSTR)fs_get((block_size+1)*sizeof(TCHAR));
+
+ args_text = (char **)lParam;
+ i = 0;
+ j = 0;
+ while(args_text && *args_text){
+ args_text_lptstr = utf8_to_lptstr(*args_text++);
+ while(args_text_lptstr[i] && j < block_size){
+ args_block_lptstr[j++] = args_text_lptstr[i++];
+ }
+ args_block_lptstr[j++] = '\r';
+ args_block_lptstr[j++] = '\n';
+ fs_give((void **) &args_text_lptstr);
+ i = 0;
+ }
+ args_block_lptstr[j] = '\0';
+
+ /* and replace everything selected with args_text */
+ SendDlgItemMessage(hDlg, IDC_ARGTEXT, WM_SETTEXT, (WPARAM) 0,
+ (LPARAM) args_block_lptstr);
+ fs_give((void **)&args_block_lptstr);
+
+ return (1);
+
+ case WM_CLOSE :
+ ret = TRUE;
+ EndDialog (hDlg, TRUE);
+ break;
+
+
+ case WM_COMMAND:
+ switch (wParam) {
+ case IDOK:
+ ret = TRUE;
+ EndDialog (hDlg, TRUE);
+ break;
+ }
+ break;
+
+
+ }
+ return (ret);
+}
+
+/*
+ * Dialog proc to handle login
+ *
+ * Configures the dialog box on init and retrieves the settings on exit.
+ */
+BOOL CALLBACK __export
+login_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DLG_LOGINDATA *dlglogin;
+ BOOL ret = FALSE;
+ TCHAR tcbuf[1024];
+ NETMBX *mb;
+ LPTSTR user, pwd;
+ LPTSTR host_lptstr;
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+
+ dlglogin = (DLG_LOGINDATA *)lParam;
+ mb = dlglogin->mb;
+ user = dlglogin->user;
+ pwd = dlglogin->pwd;
+ SetWindowLong (hDlg, WINDOW_USER_DATA, (LONG) dlglogin);
+
+ host_lptstr = utf8_to_lptstr(mb->host);
+ _sntprintf(tcbuf, sizeof(tcbuf), TEXT("Host: %.100s%s"), host_lptstr,
+ !mb->sslflag && !mb->tlsflag ? TEXT(" (INSECURE)") : TEXT(""));
+ fs_give((void **) &host_lptstr);
+
+ if(mb->sslflag || mb->tlsflag)
+ SetWindowText(hDlg, TEXT("Alpine Login +"));
+ else
+ SetWindowText(hDlg, TEXT("Alpine Insecure Login"));
+
+ if(dlglogin->pwc){
+ EnableWindow(GetDlgItem(hDlg, IDC_RPASSWORD),0);
+ EnableWindow(GetDlgItem(hDlg, IDC_RPWTEXT),0);
+ EnableWindow(GetDlgItem(hDlg, IDC_PRESPASS),0);
+ }
+ if(mb->user && *mb->user){
+ LPTSTR user_lptstr;
+
+ user_lptstr = utf8_to_lptstr(mb->user);
+ SetDlgItemText(hDlg, IDC_RLOGINE, user_lptstr);
+ if((mb->user && *mb->user) || dlglogin->fixuser)
+ EnableWindow(GetDlgItem(hDlg, IDC_RLOGINE), 0);
+ fs_give((void **) &user_lptstr);
+ }
+ else if(user){
+ SetDlgItemText(hDlg, IDC_RLOGINE, user);
+ if(dlglogin->fixuser)
+ EnableWindow(GetDlgItem(hDlg, IDC_RLOGINE), 0);
+ }
+ SetDlgItemText(hDlg, IDC_PROMPT, tcbuf);
+ return (1);
+
+
+ case WM_COMMAND:
+ dlglogin = (DLG_LOGINDATA *) GetWindowLong (hDlg, WINDOW_USER_DATA);
+ switch (wParam) {
+ case IDOK:
+ /* Retrieve the new username/passwd. */
+ user = dlglogin->user;
+ pwd = dlglogin->pwd;
+ GetDlgItemText(hDlg, IDC_RLOGINE, user, dlglogin->userlen - 1);
+ GetDlgItemText(hDlg, IDC_RPASSWORD, pwd, dlglogin->pwdlen - 1);
+ user[dlglogin->userlen - 1] = '\0';
+ pwd[dlglogin->pwdlen - 1] = '\0';
+ dlglogin->prespass = (IsDlgButtonChecked(hDlg, IDC_PRESPASS) == BST_CHECKED);
+
+ EndDialog (hDlg, LOWORD(wParam));
+ dlglogin->rv = 0;
+ ret = TRUE;
+ break;
+
+ case IDCANCEL:
+ dlglogin->rv = 1;
+ ret = TRUE;
+ EndDialog (hDlg, LOWORD(wParam));
+ break;
+ }
+ break;
+ }
+ return (ret);
+}
+
+/*
+ * Dialog proc to handle flag selection.
+ *
+ * Configures the dialog box on init, adding buttons as needed for
+ * an unknown number of flags.
+ * Retrieves the settings on exit.
+ */
+BOOL CALLBACK __export
+flag_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DLG_FLAGDATA *dlgflag;
+ BOOL ret = FALSE;
+ int i;
+ struct flag_table *fp;
+ HWND hRB[2], hBtn;
+ RECT rb[2];
+ UINT bheight, bwidth, bvertSpace;
+ UINT btnOKHeight;
+ int base, line;
+ int bstate;
+ HFONT btnFont;
+
+
+
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ dlgflag = (DLG_FLAGDATA *)lParam;
+ SetWindowLong (hDlg, WINDOW_USER_DATA, (LONG) dlgflag);
+
+ /* Count buttons */
+ dlgflag->flagcount = 0;
+ for (fp = dlgflag->ftbl; fp && fp->name; ++fp)
+ ++dlgflag->flagcount;
+
+ /* Get the positions of the current buttons. */
+ for (i = 0; i < 2; ++i) {
+ hRB[i] = GetDlgItem (hDlg, IDC_FLAGCOL1 + i);
+ GetBtnPos (hDlg, hRB[i], &rb[i]);
+ }
+ bheight = rb[0].bottom - rb[0].top;
+ bwidth = rb[0].right - rb[0].left;
+ bvertSpace = bheight + 5;
+ btnFont = (HFONT) SendMessage (hRB[0], WM_GETFONT, 0, 0);
+
+ for (i = 0; i < dlgflag->flagcount; ++i) {
+ LPTSTR fp_name_lptstr;
+
+ fp = &dlgflag->ftbl[i];
+
+ fp_name_lptstr = utf8_to_lptstr(fp->name);
+ if (i < 2) {
+ hBtn = hRB[i];
+ SetWindowText (hBtn, fp_name_lptstr);
+ }
+ else {
+ base = i % 2;
+ line = i / 2;
+ hBtn = CreateWindow (TEXT("BUTTON"), fp_name_lptstr,
+ WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
+ rb[base].left, rb[base].top + bvertSpace * line,
+ bwidth, bheight,
+ hDlg, (HMENU)NULL, dlgflag->hInstance, NULL);
+ SetWindowLong (hBtn, GWL_ID, IDC_FLAGCOL1 + i);
+ SendMessage (hBtn, WM_SETFONT, (WPARAM)btnFont,
+ MAKELPARAM (0, 0));
+ }
+
+ fs_give((void **) &fp_name_lptstr);
+ if (fp->ukn)
+ SendMessage (hBtn, BM_SETSTYLE,
+ (WPARAM)(BS_CHECKBOX | BS_AUTO3STATE), 0);
+ SendMessage (hBtn, BM_SETCHECK,
+ (WPARAM) fp->set == CMD_FLAG_UNKN ? 2 : fp->set ? 1 : 0,
+ 0);
+ ShowWindow (hBtn, SW_SHOW);
+ EnableWindow (hBtn, TRUE);
+ }
+
+ /* Position the OK and Cancel buttons. */
+ line = (dlgflag->flagcount + 1) / 2;
+ for (i = 0; i < 2; ++i) {
+ hRB[1] = GetDlgItem (hDlg, i == 0 ? IDOK : IDCANCEL);
+ GetBtnPos (hDlg, hRB[1], &rb[1]);
+ MoveWindow (hRB[1], rb[1].left, rb[0].top + bvertSpace * line,
+ rb[1].right - rb[1].left, rb[1].bottom - rb[1].top,
+ FALSE);
+ btnOKHeight = rb[1].bottom - rb[1].top;
+ }
+
+
+ /* Resize whole dialog window. */
+ GetWindowRect (hDlg, &rb[1]);
+ rb[1].right -= rb[1].left;
+ rb[1].bottom = rb[0].top + bvertSpace * line + btnOKHeight + 10 +
+ GetSystemMetrics (SM_CYCAPTION);
+ MoveWindow (hDlg, rb[1].left, rb[1].top, rb[1].right,
+ rb[1].bottom, TRUE);
+ return (1);
+
+
+ case WM_COMMAND:
+ dlgflag = (DLG_FLAGDATA *) GetWindowLong (hDlg, WINDOW_USER_DATA);
+ switch (wParam) {
+
+ case IDOK:
+ /* Retrieve the button states. */
+ for (i = 0; i < dlgflag->flagcount; ++i) {
+ fp = &dlgflag->ftbl[i];
+ bstate = SendMessage (GetDlgItem (hDlg, IDC_FLAGCOL1 + i),
+ BM_GETCHECK, 0, 0);
+ switch (bstate) {
+ case 2: fp->set = CMD_FLAG_UNKN; break;
+ case 1: fp->set = CMD_FLAG_SET; break;
+ case 0: fp->set = CMD_FLAG_CLEAR; break;
+ }
+ }
+ EndDialog (hDlg, TRUE);
+ ret = TRUE;
+ break;
+
+ case IDCANCEL:
+ EndDialog (hDlg, FALSE);
+ ret = TRUE;
+ break;
+ }
+ break;
+
+
+ }
+ return (ret);
+}
+
+/*
+ * Dialog proc to handle index sort selection.
+ *
+ * Configures the dialog box on init and retrieves the settings on exit.
+ */
+BOOL CALLBACK __export
+sort_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DLG_SORTDATA *dlgsort;
+ BOOL ret = FALSE;
+ int cursort;
+ int i;
+
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ dlgsort = (DLG_SORTDATA *)lParam;
+ SetWindowLong (hDlg, WINDOW_USER_DATA, (LONG) dlgsort);
+
+ /* Set the reversed button state. */
+ CheckDlgButton (hDlg, IDC_SORTREVERSE, dlgsort->sortsel->reverse);
+
+ /* Set the current sort type radio button.*/
+ cursort = IDC_SORTARRIVAL;
+ for (i = 0; i < dlgsort->sortcount; ++i) {
+ if (dlgsort->types[i].pineid == dlgsort->sortsel->cursort) {
+ cursort = dlgsort->types[i].rsrcid;
+ break;
+ }
+ }
+ CheckRadioButton (hDlg, IDC_SORTFIRSTBUTTON, IDC_SORTLASTBUTTON,
+ cursort);
+
+ EnableWindow (GetDlgItem (hDlg, IDC_GETHELP),
+ (dlgsort->sortsel->helptext != NULL));
+ return (1);
+
+
+ case WM_COMMAND:
+ dlgsort = (DLG_SORTDATA *) GetWindowLong (hDlg, WINDOW_USER_DATA);
+ switch (wParam) {
+ case IDC_GETHELP:
+ if (dlgsort->sortsel->helptext)
+ mswin_showhelpmsg ((WINHAND)hDlg, dlgsort->sortsel->helptext);
+ ret = TRUE;
+ break;
+
+ case IDOK:
+ dlgsort->sortsel->rval = 1;
+
+ /* Retrieve the reverse sort state. */
+ dlgsort->sortsel->reverse = (IsDlgButtonChecked (hDlg, IDC_SORTREVERSE) == 1);
+
+ /* Retrieve the new sort type. */
+ for (i = 0; i < dlgsort->sortcount; ++i) {
+ if (IsDlgButtonChecked (hDlg, dlgsort->types[i].rsrcid)) {
+ dlgsort->sortsel->cursort = dlgsort->types[i].pineid;
+ break;
+ }
+ }
+ EndDialog (hDlg, dlgsort->sortsel->rval);
+ ret = TRUE;
+ break;
+
+ case IDCANCEL:
+ dlgsort->sortsel->rval = 0;
+ ret = TRUE;
+ EndDialog (hDlg, dlgsort->sortsel->rval);
+ break;
+ }
+ break;
+
+
+ }
+ return (ret);
+}
+
+int
+os_config_vars_dialog (INSTALLVARS_S *ivars)
+{
+ DLGPROC dlgprc;
+ HINSTANCE hInst;
+ HWND hWnd;
+ DLG_CONFIGVARSDATA dlgcv;
+
+ mswin_killsplash();
+ hInst = (HINSTANCE) mswin_gethinstance ();
+ hWnd = (HWND) mswin_gethwnd ();
+
+ dlgcv.ivars = ivars;
+ dlgcv.rv = 0;
+
+ dlgprc = config_vars_dialog_proc;
+
+ DialogBoxParam (hInst, MAKEINTRESOURCE (IDD_CFVARSDLG), NULL,
+ dlgprc, (LPARAM)&dlgcv);
+
+ return(dlgcv.rv);
+}
+
+/*
+ * Dialog proc to handle login
+ *
+ * Configures the dialog box on init and retrieves the settings on exit.
+ */
+BOOL CALLBACK __export
+config_vars_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DLG_CONFIGVARSDATA *dlgcv;
+ BOOL ret = FALSE;
+ int usepop = 0;
+ char buf[2*MAXPATH+1], *p;
+ int buflen = 2*MAXPATH;
+#define TCBUFLEN (2*MAXPATH)
+ TCHAR tcbuf[TCBUFLEN+1];
+ LPTSTR tmp_lptstr;
+ char *u;
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ dlgcv = (DLG_CONFIGVARSDATA *)lParam;
+ SetWindowLong (hDlg, WINDOW_USER_DATA, (LONG) dlgcv);
+ if(dlgcv->ivars->pname){
+ tmp_lptstr = utf8_to_lptstr(dlgcv->ivars->pname);
+ if(tmp_lptstr){
+ SetDlgItemText(hDlg, IDC_CFV_PNAME, tmp_lptstr);
+ fs_give((void **) &tmp_lptstr);
+ }
+ }
+
+ if(dlgcv->ivars->userid && dlgcv->ivars->domain){
+ snprintf(buf, sizeof(buf), "%.*s@%.*s", MAXPATH, dlgcv->ivars->userid,
+ MAXPATH, dlgcv->ivars->domain);
+ buf[buflen-1] = '\0';
+ tmp_lptstr = utf8_to_lptstr(buf);
+ if(tmp_lptstr){
+ SetDlgItemText(hDlg, IDC_CFV_EMAILADR, tmp_lptstr);
+ fs_give((void **) &tmp_lptstr);
+ }
+ }
+
+ if(dlgcv->ivars->inboxpath){
+ char *mbx;
+
+ mbx = dlgcv->ivars->inboxpath;
+ if(*mbx == '{' && (p = strindex(mbx, '}'))
+ && ((*(p+1) == '\0') || !strucmp(p+1, "inbox"))){
+ char srvbuf[MAXPATH+1], tuser[MAXPATH+1];
+ int i, j, k;
+
+ srvbuf[0] = '\0';
+ tuser[0] = '\0';
+ strncpy(buf, mbx+1, min(buflen, (int)(p - (mbx+1))));
+ buf[min(buflen-1, (int)(p - (mbx+1)))] = '\0';
+ for(i = 0, j = 0; buf[i] && j < MAXPATH; i++){
+ if(buf[i] == '/'){
+ if(!struncmp("/user=", buf+i, 6)){
+ i += 6;
+ for(k = 0; k < MAXPATH && buf[i]
+ && buf[i] != '/'; i++)
+ tuser[k++] = buf[i];
+ tuser[k] = '\0';
+ i--;
+ }
+ else if(!struncmp("/pop3", buf+i, 5)
+ && (!*(buf+5) || (*(buf+5) == '/'))){
+ usepop = 1;
+ i += 4;
+ }
+ else
+ srvbuf[j++] = buf[i];
+ }
+ else
+ srvbuf[j++] = buf[i];
+ }
+ srvbuf[j] = '\0';
+ if(*srvbuf){
+ tmp_lptstr = utf8_to_lptstr(srvbuf);
+ if(tmp_lptstr){
+ SetDlgItemText(hDlg, IDC_CFV_MSERVER, tmp_lptstr);
+ fs_give((void **) &tmp_lptstr);
+ }
+ }
+
+ if(*tuser){
+ tmp_lptstr = utf8_to_lptstr(tuser);
+ if(tmp_lptstr){
+ SetDlgItemText(hDlg, IDC_CFV_LOGIN, tmp_lptstr);
+ fs_give((void **) &tmp_lptstr);
+ }
+ }
+ }
+ else {
+ tmp_lptstr = utf8_to_lptstr(mbx);
+ if(tmp_lptstr){
+ SetDlgItemText(hDlg, IDC_CFV_MSERVER, tmp_lptstr);
+ fs_give((void **) &tmp_lptstr);
+ }
+
+ if(*mbx != '{' && *mbx != '#')
+ EnableWindow(GetDlgItem(hDlg, IDC_CFV_MSERVER), 0);
+ }
+ }
+ CheckRadioButton (hDlg, IDC_CFV_IMAP, IDC_CFV_POP3,
+ usepop ? IDC_CFV_POP3 : IDC_CFV_IMAP);
+ if(dlgcv->ivars->smtpserver){
+ tmp_lptstr = utf8_to_lptstr(dlgcv->ivars->smtpserver);
+ if(tmp_lptstr){
+ SetDlgItemText(hDlg, IDC_CFV_SMTPSERVER, tmp_lptstr);
+ fs_give((void **) &tmp_lptstr);
+ }
+ }
+
+ if(dlgcv->ivars->defmailclient)
+ CheckDlgButton(hDlg, IDC_CFV_DEFMAILER, BST_CHECKED);
+ if(dlgcv->ivars->defnewsclient)
+ CheckDlgButton(hDlg, IDC_CFV_DEFNEWSRDR, BST_CHECKED);
+
+ return (1);
+ break;
+
+ case WM_COMMAND:
+ dlgcv = (DLG_CONFIGVARSDATA *) GetWindowLong (hDlg, WINDOW_USER_DATA);
+ switch (wParam) {
+ case IDOK:
+ /* personal name */
+ GetDlgItemText(hDlg, IDC_CFV_PNAME, tcbuf, TCBUFLEN);
+ tcbuf[TCBUFLEN] = '\0';
+ u = lptstr_to_utf8(tcbuf);
+ if(u){
+ removing_leading_and_trailing_white_space(u);
+ if(dlgcv->ivars->pname)
+ fs_give((void **)&dlgcv->ivars->pname);
+
+ if(*u){
+ dlgcv->ivars->pname = u;
+ u = NULL;
+ }
+
+ if(u)
+ fs_give((void **) &u);
+ }
+
+ /* user-id and domain */
+ GetDlgItemText(hDlg, IDC_CFV_EMAILADR, tcbuf, TCBUFLEN);
+ tcbuf[TCBUFLEN] = '\0';
+ u = lptstr_to_utf8(tcbuf);
+ if(u){
+ removing_leading_and_trailing_white_space(u);
+ if(p = strindex(u, '@')){
+ *(p++) = '\0';
+ if(dlgcv->ivars->userid)
+ fs_give((void **)&dlgcv->ivars->userid);
+
+ if(dlgcv->ivars->domain)
+ fs_give((void **)&dlgcv->ivars->domain);
+
+ if(*u){
+ dlgcv->ivars->userid = u;
+ u = NULL;
+ }
+
+ if(*p)
+ dlgcv->ivars->domain = cpystr(p);
+ }
+ else{
+ MessageBox(hDlg, TEXT("Invalid email address. Should be username@domain"),
+ TEXT("Alpine"), MB_ICONWARNING | MB_OK);
+ return(TRUE);
+ }
+
+ if(u)
+ fs_give((void **) &u);
+ }
+
+ /* inbox-path */
+ GetDlgItemText(hDlg, IDC_CFV_MSERVER, tcbuf, TCBUFLEN);
+ tcbuf[TCBUFLEN] = '\0';
+ u = lptstr_to_utf8(tcbuf);
+ if(u){
+ removing_leading_and_trailing_white_space(u);
+ if(*u == '{' || *u == '#'
+ || !IsWindowEnabled(GetDlgItem(hDlg, IDC_CFV_MSERVER))){
+ if(dlgcv->ivars->inboxpath)
+ fs_give((void **)&dlgcv->ivars->inboxpath);
+
+ dlgcv->ivars->inboxpath = u;
+ u = NULL;
+ }
+ else if(*u){
+ char tsrvr[4*MAXPATH+1];
+ int tsrvrlen = 4*MAXPATH;
+
+ if(dlgcv->ivars->inboxpath)
+ fs_give((void **)&dlgcv->ivars->inboxpath);
+
+ snprintf(tsrvr, sizeof(tsrvr), "{%s%s", u,
+ IsDlgButtonChecked(hDlg, IDC_CFV_POP3)
+ == BST_CHECKED ? "/pop3" : "");
+
+ if(u)
+ fs_give((void **) &u);
+
+ GetDlgItemText(hDlg, IDC_CFV_LOGIN, tcbuf, TCBUFLEN);
+ tcbuf[TCBUFLEN] = '\0';
+ u = lptstr_to_utf8(tcbuf);
+ if(u){
+ removing_leading_and_trailing_white_space(u);
+ if(*u){
+ strncat(tsrvr, "/user=", sizeof(tsrvr)-strlen(tsrvr)-1);
+ strncat(tsrvr, u, sizeof(tsrvr)-strlen(tsrvr)-1);
+ }
+
+ strncat(tsrvr, "}inbox", sizeof(tsrvr)-strlen(tsrvr)-1);
+ tsrvr[sizeof(tsrvr)-1] = '\0';
+ dlgcv->ivars->inboxpath = cpystr(tsrvr);
+ }
+ }
+
+ if(u)
+ fs_give((void **) &u);
+ }
+
+ /* smtp-server */
+ GetDlgItemText(hDlg, IDC_CFV_SMTPSERVER, tcbuf, TCBUFLEN);
+ tcbuf[TCBUFLEN] = '\0';
+ u = lptstr_to_utf8(tcbuf);
+ if(u){
+ removing_leading_and_trailing_white_space(u);
+ if(dlgcv->ivars->smtpserver)
+ fs_give((void **)&dlgcv->ivars->smtpserver);
+
+ if(*u){
+ dlgcv->ivars->smtpserver = u;
+ u = NULL;
+ }
+
+ if(u)
+ fs_give((void **) &u);
+ }
+
+ dlgcv->ivars->defmailclient =
+ (IsDlgButtonChecked(hDlg, IDC_CFV_DEFMAILER)
+ == BST_CHECKED);
+ dlgcv->ivars->defnewsclient =
+ (IsDlgButtonChecked(hDlg, IDC_CFV_DEFNEWSRDR)
+ == BST_CHECKED);
+
+ EndDialog (hDlg, LOWORD(wParam));
+ dlgcv->rv = 0;
+ ret = TRUE;
+ break;
+
+ case IDCANCEL:
+ dlgcv->rv = 1;
+ ret = TRUE;
+ EndDialog (hDlg, LOWORD(wParam));
+ break;
+ }
+ break;
+
+
+ }
+ return (ret);
+}
+
+/*
+ * Prompt for config location
+ */
+
+int
+os_config_dialog (char *utf8_buf, int utf8_buflen,
+ int *regset, int nopinerc)
+{
+ DLGPROC dlgprc;
+ HINSTANCE hInst;
+ HWND hWnd;
+ DLG_CONFIGDATA dlgcfg;
+
+ mswin_killsplash();
+ hInst = (HINSTANCE) mswin_gethinstance ();
+ hWnd = (HWND) mswin_gethwnd ();
+
+ dlgcfg.confpath = utf8_buf;
+ dlgcfg.confpathlen = utf8_buflen;
+ dlgcfg.setreg = 0;
+ dlgcfg.nopinerc = nopinerc;
+ dlgcfg.rv = 0;
+
+ dlgprc = config_dialog_proc;
+
+ DialogBoxParam (hInst, MAKEINTRESOURCE (IDD_CONFIGDLG), NULL,
+ dlgprc, (LPARAM)&dlgcfg);
+
+ *regset = dlgcfg.setreg;
+ return(dlgcfg.rv);
+}
+
+
+/*
+ * Dialog proc to handle login
+ *
+ * Configures the dialog box on init and retrieves the settings on exit.
+ */
+BOOL CALLBACK __export
+config_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DLG_CONFIGDATA *dlgcfg;
+ BOOL ret = FALSE;
+ int checked, def_rem_fldr = 1, def_diff_fldr = 0;
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+
+ dlgcfg = (DLG_CONFIGDATA *)lParam;
+ SetWindowLong (hDlg, WINDOW_USER_DATA, (LONG) dlgcfg);
+ if(ps_global->install_flag)
+ SetDlgItemText(hDlg, IDC_CONFTEXT, TEXT("Please specify where Alpine should create (or look for) your personal configuration file."));
+ SetDlgItemText(hDlg, IDC_CONFEFLDRNAME, TEXT("remote_pinerc"));
+ if(*dlgcfg->confpath){
+ if(dlgcfg->confpath[0] == '{'){
+ LPTSTR tfldr, p, tfldr2, p2, user;
+
+ tfldr = utf8_to_lptstr((LPSTR)(dlgcfg->confpath+1));
+ if(p = _tcschr(tfldr, '}')){
+ *p++ = '\0';
+ if(_tcscmp(TEXT("remote_pinerc"), p)){
+ SetDlgItemText(hDlg, IDC_CONFEFLDRNAME, p);
+ def_diff_fldr = 1;
+ }
+
+ tfldr2 = _tcsdup(tfldr);
+ for(p = tfldr, p2 = tfldr2; *p; p++)
+ if(*p == '/' && !_tcsnicmp(p, TEXT("/user="), 6)){
+ p += 6;
+ for(user = p; *p && *p != '/'; p++);
+ if(*p){
+ *p2++ = *p;
+ *p = '\0';
+ }
+ else
+ p--;
+ SetDlgItemText(hDlg, IDC_CONFEUSERNAME, user);
+ }
+ else
+ *p2++ = *p;
+ *p2 = '\0';
+ SetDlgItemText(hDlg, IDC_CONFESERVER, tfldr2);
+ if(tfldr2)
+ MemFree((void *)tfldr2);
+ }
+
+ if(tfldr)
+ fs_give((void **) &tfldr);
+ }
+ else{
+ LPTSTR local_file;
+
+ local_file = utf8_to_lptstr((LPSTR)dlgcfg->confpath);
+ SetDlgItemText(hDlg, IDC_CONFEFN, local_file);
+ if(!dlgcfg->nopinerc)
+ def_rem_fldr = 0;
+
+ fs_give((void **) &local_file);
+ }
+ }
+ CheckRadioButton (hDlg, IDC_CONFRRADIO, IDC_CONFLRADIO,
+ def_rem_fldr ? IDC_CONFRRADIO : IDC_CONFLRADIO);
+ if(def_rem_fldr){
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFFNTXT), 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEFN), 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFBROWSE), 0);
+ if(!def_diff_fldr){
+ CheckDlgButton(hDlg, IDC_CONFDFLTFLDR, BST_CHECKED);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEFLDRNAME), 0);
+ }
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFFLDRTXT), 0);
+ }
+ else {
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFSRVRTXT), 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFESERVER), 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFUNTXT), 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEUSERNAME), 0);
+ CheckDlgButton(hDlg, IDC_CONFDFLTFLDR, BST_CHECKED);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFDFLTFLDR), 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFFLDRTXT), 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEFLDRNAME), 0);
+ }
+ if(ps_global->install_flag)
+ CheckDlgButton(hDlg, IDC_CONFDFLTSET, BST_CHECKED);
+
+ return (1);
+
+
+ case WM_COMMAND:
+ dlgcfg = (DLG_CONFIGDATA *) GetWindowLong (hDlg, WINDOW_USER_DATA);
+ switch (wParam) {
+ case IDC_CONFDFLTFLDR:
+ checked = (IsDlgButtonChecked(hDlg, IDC_CONFDFLTFLDR) == BST_CHECKED);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFFLDRTXT), checked ? 0 : 1);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEFLDRNAME), checked ? 0 : 1);
+ if(checked)
+ SetDlgItemText(hDlg, IDC_CONFEFLDRNAME, TEXT("remote_pinerc"));
+ break;
+ case IDC_CONFRRADIO:
+ case IDC_CONFLRADIO:
+ CheckRadioButton (hDlg, IDC_CONFRRADIO, IDC_CONFLRADIO, wParam);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFFNTXT), wParam == IDC_CONFRRADIO ? 0 : 1);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEFN), wParam == IDC_CONFRRADIO ? 0 : 1);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFBROWSE), wParam == IDC_CONFRRADIO ? 0 : 1);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFSRVRTXT), wParam == IDC_CONFRRADIO ? 1 : 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFESERVER), wParam == IDC_CONFRRADIO ? 1 : 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFUNTXT), wParam == IDC_CONFRRADIO ? 1 : 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEUSERNAME), wParam == IDC_CONFRRADIO ? 1 : 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFDFLTFLDR), wParam == IDC_CONFRRADIO ? 1 : 0);
+ if(wParam == IDC_CONFRRADIO && IsDlgButtonChecked(hDlg, IDC_CONFDFLTFLDR) == BST_UNCHECKED){
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFFLDRTXT), 1);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEFLDRNAME), 1);
+ }
+ else if(wParam == IDC_CONFLRADIO){
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFFLDRTXT), 0);
+ EnableWindow(GetDlgItem(hDlg, IDC_CONFEFLDRNAME), 0);
+ }
+ break;
+ case IDC_CONFBROWSE:
+ if(1){
+ TCHAR fn[MAXPATH+1];
+ OPENFILENAME ofn;
+
+ fn[0] = '\0';
+
+ /* Set up the BIG STRUCTURE. see mswin.c */
+ memset (&ofn, 0, sizeof(ofn));
+ ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+ ofn.hwndOwner = hDlg;
+ ofn.lpstrFilter = NULL;
+ ofn.lpstrCustomFilter = NULL;
+ ofn.nFilterIndex = 0;
+ ofn.lpstrFile = fn;
+ ofn.nMaxFile = MAXPATH;
+ ofn.lpstrFileTitle = NULL;
+ ofn.nMaxFileTitle = 0;
+ ofn.lpstrInitialDir = NULL;
+ ofn.lpstrTitle = TEXT("Select File");
+ ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
+ ofn.lpstrDefExt = NULL;
+
+ if (GetOpenFileName (&ofn)) {
+ SetDlgItemText(hDlg, IDC_CONFEFN, fn);
+ }
+ }
+ break;
+ case IDOK:
+ /* Retrieve the new username/passwd. */
+ if(IsDlgButtonChecked(hDlg, IDC_CONFDFLTSET) == BST_CHECKED)
+ dlgcfg->setreg = 1;
+ else
+ dlgcfg->setreg = 0;
+ if(IsDlgButtonChecked(hDlg, IDC_CONFRRADIO) == BST_CHECKED){
+ TCHAR lptstr_buf[MAXPATH+1];
+ char *utf8_srvr, *utf8_username, *utf8_fldrname;
+
+ GetDlgItemText(hDlg, IDC_CONFESERVER, lptstr_buf, MAXPATH);
+ lptstr_buf[MAXPATH] = '\0';
+ utf8_srvr = lptstr_to_utf8(lptstr_buf);
+ removing_leading_and_trailing_white_space(utf8_srvr);
+ if(!*utf8_srvr){
+ MessageBox(hDlg, TEXT("IMAP Server field empty"),
+ TEXT("Alpine"), MB_ICONWARNING | MB_OK);
+ if(utf8_srvr)
+ fs_give((void **) &utf8_srvr);
+
+ return(TRUE);
+ }
+
+ GetDlgItemText(hDlg, IDC_CONFEUSERNAME, lptstr_buf, MAXPATH);
+ lptstr_buf[MAXPATH] = '\0';
+ utf8_username = lptstr_to_utf8(lptstr_buf);
+ removing_leading_and_trailing_white_space(utf8_username);
+ if(IsDlgButtonChecked(hDlg, IDC_CONFDFLTFLDR) == BST_CHECKED){
+ utf8_fldrname = cpystr("remote_pinerc");
+ }
+ else{
+ GetDlgItemText(hDlg, IDC_CONFEFLDRNAME, lptstr_buf, MAXPATH);
+ lptstr_buf[MAXPATH] = '\0';
+ utf8_fldrname = lptstr_to_utf8(lptstr_buf);
+ removing_leading_and_trailing_white_space(utf8_fldrname);
+ if(!*utf8_fldrname){
+ MessageBox(hDlg, TEXT("Configuration Folder Name field empty"),
+ TEXT("Alpine"), MB_ICONWARNING | MB_OK);
+ if(utf8_srvr)
+ fs_give((void **) &utf8_srvr);
+
+ if(utf8_username)
+ fs_give((void **) &utf8_username);
+
+ if(utf8_fldrname)
+ fs_give((void **) &utf8_fldrname);
+
+ return(TRUE);
+ }
+ }
+ if((strlen(utf8_srvr)
+ + strlen(utf8_username)
+ + strlen(utf8_fldrname)
+ + 11) > dlgcfg->confpathlen){
+ MessageBox(hDlg, TEXT("Config path too long"),
+ TEXT("Alpine"), MB_ICONWARNING | MB_OK);
+ if(utf8_srvr)
+ fs_give((void **) &utf8_srvr);
+
+ if(utf8_username)
+ fs_give((void **) &utf8_username);
+
+ if(utf8_fldrname)
+ fs_give((void **) &utf8_fldrname);
+
+ return(TRUE);
+ }
+
+ snprintf(dlgcfg->confpath, dlgcfg->confpathlen, "{%s%s%s}%s", utf8_srvr,
+ *utf8_username ? "/user=" : "",
+ utf8_username, utf8_fldrname);
+ if(utf8_srvr)
+ fs_give((void **) &utf8_srvr);
+
+ if(utf8_username)
+ fs_give((void **) &utf8_username);
+
+ if(utf8_fldrname)
+ fs_give((void **) &utf8_fldrname);
+ }
+ else{
+ TCHAR lptstr_fn[MAXPATH+1];
+ char *utf8_fn;
+
+ GetDlgItemText(hDlg, IDC_CONFEFN, lptstr_fn, MAXPATH);
+ lptstr_fn[MAXPATH] = '\0';
+ utf8_fn = lptstr_to_utf8(lptstr_fn);
+ removing_leading_and_trailing_white_space(utf8_fn);
+ if(!*utf8_fn){
+ MessageBox(hDlg, TEXT("Configuration File Name field empty"),
+ TEXT("Alpine"), MB_ICONWARNING | MB_OK);
+ if(utf8_fn)
+ fs_give((void **) &utf8_fn);
+
+ return(TRUE);
+ }
+
+ if(strlen(utf8_fn) >= dlgcfg->confpathlen){
+ MessageBox(hDlg, TEXT("Config path too long"),
+ TEXT("Alpine"), MB_ICONWARNING | MB_OK);
+ if(utf8_fn)
+ fs_give((void **) &utf8_fn);
+
+ return(TRUE);
+ }
+
+ strncpy(dlgcfg->confpath, utf8_fn, dlgcfg->confpathlen);
+ dlgcfg->confpath[dlgcfg->confpathlen-1] = '\0';
+ if(utf8_fn)
+ fs_give((void **) &utf8_fn);
+ }
+
+ EndDialog (hDlg, LOWORD(wParam));
+ dlgcfg->rv = 0;
+ ret = TRUE;
+ break;
+
+ case IDCANCEL:
+ dlgcfg->rv = 1;
+ ret = TRUE;
+ EndDialog (hDlg, LOWORD(wParam));
+ break;
+ }
+ break;
+
+
+ }
+ return (ret);
+}
+
+/*
+ * Get a button position in the parent's coordinate space.
+ */
+static void
+GetBtnPos (HWND hPrnt, HWND hWnd, RECT *r)
+{
+ GetWindowRect (hWnd, r);
+ ScreenToClient (hPrnt, (POINT *) r);
+ ScreenToClient (hPrnt, (POINT *) &r->right);
+}
diff --git a/alpine/osdep/termout.wnt.h b/alpine/osdep/termout.wnt.h
new file mode 100644
index 00000000..35ccbb77
--- /dev/null
+++ b/alpine/osdep/termout.wnt.h
@@ -0,0 +1,47 @@
+/*
+ * $Id: termout.unx.h 159 2006-10-02 22:00:13Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_OSDEP_TERMOUT_WNT_INCLUDED
+#define PINE_OSDEP_TERMOUT_WNT_INCLUDED
+
+#include "resource.h"
+
+typedef struct DLG_SORTPARAM {
+ int rval; /* Return value. */
+ int reverse; /* Indicates a reversed sort. */
+ int cursort; /* Current sort (pineid). */
+ char **helptext; /* Pointer to help text. */
+ int sortcount; /* Number of different sorts. */
+ struct sorttypemap *types; /* Pointer to array of conversion between
+ * the pine sort types and the radio button
+ * ids. */
+} DLG_SORTPARAM;
+
+
+/* exported prototypes */
+
+void scroll_setpos(long);
+void scroll_setrange(long, long);
+
+/* dialog stuff */
+int init_install_get_vars(void);
+int os_argsdialog(char **);
+int os_login_dialog(NETMBX *, char *, int, char *, int, int, int, int *);
+int os_flagmsgdialog(struct flag_table *);
+int os_sortdialog(DLG_SORTPARAM *);
+int os_config_dialog(char *, int, int *, int);
+
+#endif /* PINE_OSDEP_TERMOUT_WNT_INCLUDED */
diff --git a/alpine/osdep/windlg.h b/alpine/osdep/windlg.h
new file mode 100644
index 00000000..7730ae88
--- /dev/null
+++ b/alpine/osdep/windlg.h
@@ -0,0 +1,10 @@
+
+
+/*----------------------------------------------------------------------------
+
+ Common includes for custom dialogs.
+
+ ( This file included by os-win.h and os-wnt.h)
+
+*/
+
diff --git a/alpine/pattern.c b/alpine/pattern.c
new file mode 100644
index 00000000..1e497746
--- /dev/null
+++ b/alpine/pattern.c
@@ -0,0 +1,223 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: pattern.c 169 2006-10-04 23:26:48Z hubert@u.washington.edu $";
+#endif
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <system.h>
+#include <general.h>
+
+#include "../c-client/c-client.h"
+
+#include "../pith/osdep/canaccess.h"
+#include "../pith/osdep/color.h"
+#include "../pith/osdep/pipe.h"
+
+#include "../pith/charconv/utf8.h"
+#include "../pith/charconv/filesys.h"
+
+#include "../pith/status.h"
+#include "../pith/pipe.h"
+#include "../pith/debug.h"
+#include "../pith/detach.h"
+
+#include "pipe.h"
+#include "pattern.h"
+#include "signal.h"
+
+
+/* Internal prototypes */
+int test_message_with_cmd(MAILSTREAM *, long, char *, long, int *);
+
+
+/*
+ * pattern_filter_command - filter given message
+ */
+void
+pattern_filter_command(char **cat_cmds, SEARCHSET *srchset, MAILSTREAM *stream, long cat_lim, INTVL_S *cat)
+{
+ char **l;
+ int exitval, i;
+ SEARCHSET *s;
+ MESSAGECACHE *mc;
+ char *cmd = NULL;
+ char *just_arg0 = NULL;
+ char *cmd_start, *cmd_end;
+ int just_one;
+
+ if(!(cat_cmds && cat_cmds[0]))
+ return;
+
+ just_one = !(cat_cmds[1]);
+
+ /* find the first command that exists on this host */
+ for(l = cat_cmds; l && *l; l++){
+ cmd = cpystr(*l);
+ removing_quotes(cmd);
+ if(cmd){
+ for(cmd_start = cmd;
+ *cmd_start && isspace(*cmd_start); cmd_start++)
+ ;
+
+ for(cmd_end = cmd_start+1;
+ *cmd_end && !isspace(*cmd_end); cmd_end++)
+ ;
+
+ just_arg0 = (char *) fs_get((cmd_end-cmd_start+1)
+ * sizeof(char));
+ strncpy(just_arg0, cmd_start, cmd_end - cmd_start);
+ just_arg0[cmd_end - cmd_start] = '\0';
+ }
+
+ if(valid_filter_command(&just_arg0))
+ break;
+ else{
+ if(just_one){
+ if(can_access(just_arg0, ACCESS_EXISTS) != 0)
+ q_status_message1(SM_ORDER, 0, 3,
+ "\"%s\" does not exist",
+ just_arg0);
+ else
+ q_status_message1(SM_ORDER, 0, 3,
+ "\"%s\" is not executable",
+ just_arg0);
+ }
+
+ if(just_arg0)
+ fs_give((void **) &just_arg0);
+ if(cmd)
+ fs_give((void **) &cmd);
+ }
+ }
+
+ if(!just_arg0 && !just_one)
+ q_status_message(SM_ORDER, 0, 3,
+ "None of the category cmds exists and is executable");
+
+ /*
+ * If category_cmd isn't executable, it isn't a match.
+ */
+ if(!just_arg0 || !cmd){
+ /*
+ * If we couldn't run the pipe command,
+ * we declare no match
+ */
+ for(s = srchset; s; s = s->next)
+ for(i = s->first; i <= s->last; i++)
+ if(i > 0L && stream && i <= stream->nmsgs
+ && (mc=mail_elt(stream, i)) && mc->searched)
+ mc->searched = NIL;
+ }
+ else
+ for(s = srchset; s; s = s->next)
+ for(i = s->first; i <= s->last; i++)
+ if(i > 0L && stream && i <= stream->nmsgs
+ && (mc=mail_elt(stream, i)) && mc->searched){
+
+ /*
+ * If there was an error, or the exitval is out of
+ * range, then it is not a match.
+ * Default range is (0,0),
+ * which is right for matching
+ * bogofilter spam.
+ */
+ if(test_message_with_cmd(stream, i, cmd,
+ cat_lim, &exitval) != 0)
+ mc->searched = NIL;
+
+ /* test exitval */
+ if(mc->searched){
+ INTVL_S *iv;
+
+ if(cat){
+ for(iv = cat; iv; iv = iv->next)
+ if((long) exitval >= iv->imin
+ && (long) exitval <= iv->imax)
+ break;
+
+ if(!iv)
+ mc->searched = NIL; /* not in any interval */
+ }
+ else{
+ /* default to interval containing only zero */
+ if(exitval != 0)
+ mc->searched = NIL;
+ }
+ }
+ }
+
+ if(just_arg0)
+ fs_give((void **) &just_arg0);
+
+ if(cmd)
+ fs_give((void **) &cmd);
+}
+
+
+
+/*
+ * Returns 0 if ok, -1 if not ok.
+ * If ok then exitval contains the exit value of the cmd.
+ */
+int
+test_message_with_cmd(MAILSTREAM *stream, long int msgno, char *cmd,
+ long char_limit, /* limit testing to this many chars from body */
+ int *exitval)
+{
+ PIPE_S *tpipe;
+ gf_io_t pc;
+ int status = 0, flags, err = 0;
+ char *resultfile = NULL, *pipe_err;
+
+ if(cmd && cmd[0]){
+
+ flags = PIPE_WRITE | PIPE_NOSHELL | PIPE_STDERR | PIPE_NONEWMAIL;
+
+ dprint((7, "test_message_with_cmd(msgno=%ld cmd=%s)\n",
+ msgno, cmd));
+
+ if((tpipe = cmd_pipe_open(cmd, &resultfile, flags, &pc))){
+
+ prime_raw_pipe_getc(stream, msgno, char_limit, FT_PEEK);
+ gf_filter_init();
+ gf_link_filter(gf_nvtnl_local, NULL);
+ if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
+ q_status_message1(SM_ORDER|SM_DING, 3, 3,
+ "Internal Error: %.200s", pipe_err);
+ err++;
+ }
+
+ /*
+ * Don't call new_mail in close_system_pipe because we're probably
+ * already here from new_mail and we don't want to get loopy.
+ */
+ status = close_system_pipe(&tpipe, exitval, pipe_callback);
+
+ /*
+ * This is a place where the command can put its output, which we
+ * are not interested in.
+ */
+ if(resultfile){
+ our_unlink(resultfile);
+ fs_give((void **) &resultfile);
+ }
+
+ return((err || status) ? -1 : 0);
+ }
+ }
+
+ return(-1);
+}
+
+
diff --git a/alpine/pattern.h b/alpine/pattern.h
new file mode 100644
index 00000000..5c71747f
--- /dev/null
+++ b/alpine/pattern.h
@@ -0,0 +1,25 @@
+/*
+ * $Id: pattern.h 137 2006-09-22 21:34:06Z mikes@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef ALPINE_PATTERN_INCLUDED
+#define ALPINE_PATTERN_INCLUDED
+
+#include "../pith/pattern.h"
+
+/* exported protoypes */
+void pattern_filter_command(char **, SEARCHSET *, MAILSTREAM *, long, INTVL_S *);
+
+
+#endif /* ALPINE_PATTERN_INCLUDED */
diff --git a/alpine/pine-use.c b/alpine/pine-use.c
new file mode 100644
index 00000000..dd7fc8de
--- /dev/null
+++ b/alpine/pine-use.c
@@ -0,0 +1,180 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: pine-use.c 761 2007-10-23 22:35:18Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include <stdio.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef MAILSPOOLPCTS
+#define MAILSPOOLPCTS "/usr/spool/mail/%s"
+/* #define MAILSPOOLPCTS "/usr/mail/%s" */
+#endif
+
+#define DAYSEC (60*60*24)
+
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ struct passwd *pw;
+ char filename[100], buf[100], *p;
+ struct stat statb;
+ long now, inbox_mess, inboxes, inbox_mess_max;
+ int core_files, c, core_id, count, sig_files;
+ int user_count[6], so_far;
+ FILE *f, *core;
+
+ user_count[0] = 0; /* Last week */
+ user_count[1] = 0; /* Last 2 weeks */
+ user_count[2] = 0; /* Last month */
+ user_count[3] = 0; /* Last year */
+ user_count[4] = 0; /* Ever */
+ sig_files = 0;
+ core_files = 0;
+ inboxes = 0;
+ inbox_mess = 0;
+ inbox_mess_max = 0;
+
+ now = time(0);
+ core = NULL;
+
+ if(argc > 1) {
+ core_id = atoi(argv[1]);
+ if(core_id == 0){
+ fprintf(stderr, "Bogus core starting number\n");
+ exit(-1);
+ } else {
+ printf("Core collect starting at %d\n", core_id);
+ core = fopen("pine-core-collect.sh", "w");
+ }
+ }
+
+ so_far = 0;
+ while((pw = getpwent()) != NULL) {
+ so_far++;
+ if((so_far % 200) == 0) {
+ printf("%5d users processed so far\n", so_far);
+ }
+
+ if(strcmp(pw->pw_dir, "/") == 0)
+ continue;
+
+ sprintf(filename, "%s/.pinerc", pw->pw_dir);
+ if(stat(filename, &statb) < 0)
+ continue;
+ if(statb.st_mtime + 7 * DAYSEC > now)
+ user_count[0]++;
+ else if(statb.st_mtime + 14 * DAYSEC > now)
+ user_count[1]++;
+ else if(statb.st_mtime + 30 * DAYSEC > now)
+ user_count[2]++;
+ else if(statb.st_mtime + 365 * DAYSEC > now)
+ user_count[3]++;
+ else
+ user_count[4]++;
+
+
+ if(statb.st_mtime + 30 * DAYSEC >= now) {
+ count = mail_file_size(pw->pw_name);
+ if(count >= 0){
+ inboxes++;
+ inbox_mess += count;
+ inbox_mess_max = inbox_mess_max > count ? inbox_mess_max:count;
+ }
+ }
+
+ sprintf(filename, "%s/.signature", pw->pw_dir);
+ if(access(filename, 0) == 0)
+ sig_files++;
+
+ sprintf(filename, "%s/core", pw->pw_dir);
+ if((f = fopen(filename, "r")) != NULL) {
+ fflush(stdout);
+ while((c = getc(f)) != EOF) {
+ if(c == 'P'){
+ p = buf;
+ *p++ = c;
+ while((c = getc(f)) != EOF) {
+ *p++ = c;
+ if(p > &buf[50]) {
+ break;
+ }
+ if(c == ')') {
+ break;
+ }
+ }
+ *p = '\0';
+ if(c == EOF)
+ break;
+ if(strcmp(&buf[strlen(buf) - 13], "(olivebranch)") == 0) {
+ printf("%s\t%s\n", filename, buf + 14);
+ core_files++;
+ if(core != NULL) {
+ fprintf(core, "mv %s core%d.%s\n", filename,
+ core_id++,pw->pw_name);
+ }
+ break;
+ }
+ }
+ }
+ fclose(f);
+ } else {
+/* printf("%s\n", pw->pw_name); */
+ }
+ }
+
+
+ printf("%5d: last week\n", user_count[0]);
+ printf("%5d: last two weeks (+%d)\n", user_count[1] + user_count[0],
+ user_count[1]);
+ printf("%5d: last month (+%d)\n", user_count[2] + user_count[1] + user_count[0], user_count[2]);
+ printf("%5d: last year\n", user_count[3]);
+ printf("%5d: more than a year\n", user_count[4]);
+ printf("%5d: core files\n", core_files);
+ printf("%5d: Average messages in inbox (%ld/%d)\n",
+ inbox_mess/inboxes, inbox_mess, inboxes);
+ printf("%5d: Largest inbox in messages\n", inbox_mess_max);
+ printf("%5d: Total users checked\n", so_far);
+ printf("%5d: signature files\n", sig_files);
+}
+
+
+mail_file_size(user)
+ char *user;
+{
+ int count = 0;
+ FILE *f;
+ char buf[20480];
+
+ sprintf(buf, MAILSPOOLPCTS, user);
+
+ f = fopen(buf, "r");
+ if(f == NULL)
+ return(-1);
+
+ while(fgets(buf, sizeof(buf), f) != NULL) {
+ if(strncmp(buf, "From ", 5) == 0)
+ count++;
+ }
+ fclose(f);
+/* printf("%s %d\n", user, count); */
+ return(count);
+}
+
+
+
diff --git a/alpine/pine-win.lnk b/alpine/pine-win.lnk
new file mode 100644
index 00000000..387aef1f
--- /dev/null
+++ b/alpine/pine-win.lnk
@@ -0,0 +1,11 @@
+mswinver+addrbook+adrbklib+args+filter+folder+
+context+help+imap+init+mailcmd+mailindx+mailpart+
+mailview+newmail+os+other+pine+reply+screen+
+send+signals+status+strings+ttyin+ttyout+mailcap+
+bdate
+pine
+pine
+..\c-client\cclient+..\pico\libpico+
+winsock+libw+shell+toolhelp+llibcew+oldnames+commdlg+
+..\spell\spell
+osdep\mswin.def
diff --git a/alpine/pipe.c b/alpine/pipe.c
new file mode 100644
index 00000000..033d67b9
--- /dev/null
+++ b/alpine/pipe.c
@@ -0,0 +1,150 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: pipe.c 155 2006-09-29 23:28:46Z hubert@u.washington.edu $";
+#endif
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+#include "../pith/headers.h"
+#include "../pith/pipe.h"
+#include "../pith/stream.h"
+#include "../pith/status.h"
+
+#include "signal.h"
+#include "pipe.h"
+
+
+/*
+ * Support structure and functions to support piping raw message texts...
+ */
+static struct raw_pipe_data {
+ MAILSTREAM *stream;
+ unsigned long msgno, len;
+ long char_limit, flags;
+ char *cur, *body;
+} raw_pipe;
+
+
+int
+raw_pipe_getc(unsigned char *c)
+{
+ static char *free_this = NULL;
+
+ /*
+ * What is this if doing?
+ *
+ * If((just_starting && unsuccessful_fetch_header)
+ * || (no_chars_available && haven't_fetched_body
+ * && (not_supposed_to_fetch
+ * || (supposed_to_fetch_all && unsuccessful_fetch_text)
+ * || (supposed_to_partial_fetch && unsuccessful_partial_fetch))
+ * || (no_chars_available))
+ * return(0);
+ *
+ * otherwise, fall through and return next character
+ */
+ if((!raw_pipe.cur
+ && !(raw_pipe.cur = mail_fetch_header(raw_pipe.stream, raw_pipe.msgno,
+ NULL, NULL,
+ &raw_pipe.len,
+ raw_pipe.flags)))
+ || ((raw_pipe.len <= 0L) && !raw_pipe.body
+ && (raw_pipe.char_limit == 0L
+ || (raw_pipe.char_limit < 0L
+ && !(raw_pipe.cur = raw_pipe.body =
+ pine_mail_fetch_text(raw_pipe.stream,
+ raw_pipe.msgno,
+ NULL,
+ &raw_pipe.len,
+ raw_pipe.flags)))
+ || (raw_pipe.char_limit > 0L
+ && !(raw_pipe.cur = raw_pipe.body =
+ pine_mail_partial_fetch_wrapper(raw_pipe.stream,
+ raw_pipe.msgno,
+ NULL,
+ &raw_pipe.len,
+ raw_pipe.flags,
+ (unsigned long) raw_pipe.char_limit,
+ &free_this, 1)))))
+ || (raw_pipe.len <= 0L)){
+
+ if(free_this)
+ fs_give((void **) &free_this);
+
+ return(0);
+ }
+
+ if(raw_pipe.char_limit > 0L
+ && raw_pipe.body
+ && raw_pipe.len > raw_pipe.char_limit)
+ raw_pipe.len = raw_pipe.char_limit;
+
+ if(raw_pipe.len > 0L){
+ *c = (unsigned char) *raw_pipe.cur++;
+ raw_pipe.len--;
+ return(1);
+ }
+ else
+ return(0);
+
+}
+
+
+/*
+ * Set up for using raw_pipe_getc
+ *
+ * Args: stream
+ * msgno
+ * char_limit Set to -1 means whole thing
+ * 0 means headers only
+ * > 0 means headers plus char_limit body chars
+ * flags -- passed to fetch functions
+ */
+void
+prime_raw_pipe_getc(MAILSTREAM *stream, long int msgno, long int char_limit, long int flags)
+{
+ raw_pipe.stream = stream;
+ raw_pipe.msgno = (unsigned long) msgno;
+ raw_pipe.char_limit = char_limit;
+ raw_pipe.len = 0L;
+ raw_pipe.flags = flags;
+ raw_pipe.cur = NULL;
+ raw_pipe.body = NULL;
+}
+
+
+/*----------------------------------------------------------------------
+ Actually open the pipe used to write piped data down
+
+ Args:
+ Returns: TRUE if success, otherwise FALSE
+
+ ----*/
+PIPE_S *
+cmd_pipe_open(char *cmd, char **result, int flags, gf_io_t *pc)
+{
+ char err[200];
+ PIPE_S *pipe;
+
+ if((pipe = open_system_pipe(cmd, result, NULL, flags, 0,
+ pipe_callback, pipe_report_error)) != NULL)
+ gf_set_writec(pc, pipe, 0L, PipeStar, (flags & PIPE_RAW) ? 0 : WRITE_TO_LOCALE);
+ else{
+ /* TRANSLATORS: The argument is the command name being piped to. */
+ snprintf(err, sizeof(err), _("Error opening pipe: %s"), cmd ? cmd : "?");
+ q_status_message(SM_ORDER | SM_DING, 3, 3, err) ;
+ }
+
+ return(pipe);
+}
diff --git a/alpine/pipe.h b/alpine/pipe.h
new file mode 100644
index 00000000..8c242109
--- /dev/null
+++ b/alpine/pipe.h
@@ -0,0 +1,29 @@
+/*
+ * $Id: pipe.h 136 2006-09-22 20:06:05Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef ALPINE_PIPE_INCLUDED
+#define ALPINE_PIPE_INCLUDED
+
+
+#include "../pith/filter.h"
+#include "../pith/osdep/pipe.h"
+
+
+/* exported protoypes */
+int raw_pipe_getc(unsigned char *);
+void prime_raw_pipe_getc(MAILSTREAM *, long, long, long);
+PIPE_S *cmd_pipe_open(char *, char **, int, gf_io_t *);
+
+#endif /* ALPINE_PIPE_INCLUDED */
diff --git a/alpine/print.c b/alpine/print.c
new file mode 100644
index 00000000..4fa7edc3
--- /dev/null
+++ b/alpine/print.c
@@ -0,0 +1,1288 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: print.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "print.h"
+#include "confscroll.h"
+#include "keymenu.h"
+#include "radio.h"
+#include "status.h"
+#include "../pith/state.h"
+#include "../pith/mailcmd.h"
+
+
+/*
+ * Internal prototypes
+ */
+void set_def_printer_value(char *);
+int print_select_tool(struct pine *, int, CONF_S **, unsigned);
+int print_edit_tool(struct pine *, int, CONF_S **, unsigned);
+
+
+static char **def_printer_line;
+static char no_ff[] = "-no-formfeed";
+
+
+#ifndef DOS
+
+/*
+ * Information used to paint and maintain a line on the configuration screen
+ */
+/*----------------------------------------------------------------------
+ The printer selection screen
+
+ Draws the screen and prompts for the printer number and the custom
+ command if so selected.
+
+ ----*/
+void
+select_printer(struct pine *ps, int edit_exceptions)
+{
+ struct variable *vtmp;
+ CONF_S *ctmpa = NULL, *ctmpb = NULL, *heading = NULL,
+ *start_line = NULL;
+ PINERC_S *prc = NULL;
+ int i, saved_printer_cat, readonly_warning = 0, no_ex;
+ SAVED_CONFIG_S *vsave;
+ char *saved_printer, **lval;
+ OPT_SCREEN_S screen;
+ size_t l;
+
+ if(edit_exceptions){
+ q_status_message(SM_ORDER, 3, 7,
+ _("Exception Setup not implemented for printer"));
+ return;
+ }
+
+ if(fixed_var(&ps_global->vars[V_PRINTER], "change", "printer"))
+ return;
+
+ ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;
+
+ no_ex = (ps_global->ew_for_except_vars == Main);
+
+ if(ps->restricted)
+ readonly_warning = 1;
+ else{
+ switch(ew){
+ case Main:
+ prc = ps->prc;
+ break;
+ case Post:
+ prc = ps->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ readonly_warning = prc ? prc->readonly : 1;
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return;
+ }
+ }
+
+ saved_printer = cpystr(ps->VAR_PRINTER);
+ saved_printer_cat = ps->printer_category;
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 2;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("\"Select\" a port or |pipe-command as your default printer.");
+#else
+ = cpystr(_("You may \"Select\" a print command as your default printer."));
+#endif
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 2;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("You may also add alternative ports or !pipes to the list in the");
+#else
+ = cpystr(_("You may also add custom print commands to the list in the"));
+#endif
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 2;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("\"Personally selected port or |pipe\" section below.");
+#else
+ = cpystr(_("\"Personally selected print command\" section below."));
+#endif
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 2;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 4;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ def_printer_line = &ctmpa->value;
+ set_def_printer_value(ps->VAR_PRINTER);
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 2;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+
+#ifndef OS2
+ new_confline(&ctmpa);
+ heading = ctmpa;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->varname
+ = cpystr(_(" Printer attached to IBM PC or compatible, Macintosh"));
+ ctmpa->flags |= (CF_NOSELECT | CF_STARTITEM);
+ ctmpa->value = cpystr("");
+ ctmpa->headingp = heading;
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+ = cpystr(_("This may not work with all attached printers, and will depend on the"));
+ ctmpa->headingp = heading;
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+ = cpystr(_("terminal emulation/communications software in use. It is known to work"));
+ ctmpa->headingp = heading;
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+ = cpystr("with Kermit and the latest UW version of NCSA telnet on Macs and PCs,");
+ ctmpa->headingp = heading;
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+ = cpystr("Versaterm Pro on Macs, and WRQ Reflections on PCs.");
+ ctmpa->headingp = heading;
+
+ new_confline(&ctmpa);
+ start_line = ctmpb = ctmpa; /* default start line */
+ ctmpa->valoffset = 17;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = h_config_set_att_ansi;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOHILITE;
+ ctmpa->varoffset = 8;
+ ctmpa->varname = cpystr("Printer:");
+ ctmpa->value = cpystr(ANSI_PRINTER);
+ ctmpa->varnamep = ctmpb;
+ ctmpa->headingp = heading;
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 17;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = h_config_set_att_ansi2;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOHILITE;
+ ctmpa->varoffset = 8;
+ l = strlen(ANSI_PRINTER)+strlen(no_ff);
+ ctmpa->value = (char *)fs_get((l+1) * sizeof(char));
+ ctmpa->varnamep = ctmpb;
+ ctmpa->headingp = heading;
+ snprintf(ctmpa->value, l+1, "%s%s", ANSI_PRINTER, no_ff);
+ ctmpa->value[l] = '\0';
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 17;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = h_config_set_att_wyse;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOHILITE;
+ ctmpa->varoffset = 8;
+ ctmpa->value = cpystr(WYSE_PRINTER);
+ ctmpa->varnamep = ctmpb;
+ ctmpa->headingp = heading;
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 17;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = h_config_set_att_wyse2;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOHILITE;
+ ctmpa->varoffset = 8;
+ l = strlen(WYSE_PRINTER)+strlen(no_ff);
+ ctmpa->value = (char *)fs_get((l+1) * sizeof(char));
+ ctmpa->varnamep = ctmpb;
+ ctmpa->headingp = heading;
+ snprintf(ctmpa->value, l+1, "%s%s", WYSE_PRINTER, no_ff);
+ ctmpa->value[l] = '\0';
+#endif
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 0;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+ ctmpa->var = &ps->vars[V_STANDARD_PRINTER];
+
+
+ new_confline(&ctmpa);
+ heading = ctmpa;
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->varname
+#ifdef OS2
+ = cpystr(" Standard OS/2 printer port");
+#else
+ = cpystr(_(" Standard UNIX print command"));
+#endif
+ ctmpa->value = cpystr("");
+ ctmpa->flags |= (CF_NOSELECT | CF_STARTITEM);
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_STANDARD_PRINTER];
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("Using this option may require you to use the OS/2 \"MODE\" command to");
+#else
+ = cpystr(_("Using this option may require setting your \"PRINTER\" or \"LPDEST\""));
+#endif
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_STANDARD_PRINTER];
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("direct printer output to the correct port.");
+#else
+ = cpystr(_("environment variable using the standard UNIX utilities."));
+#endif
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_STANDARD_PRINTER];
+
+ vtmp = &ps->vars[V_STANDARD_PRINTER];
+ for(i = 0; vtmp->current_val.l[i]; i++){
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 22;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->help = h_config_set_stand_print;
+ ctmpa->tool = print_select_tool;
+ if(i == 0){
+ ctmpa->varoffset = 8;
+ ctmpa->varname = cpystr(_("Printer List:"));
+ ctmpa->flags |= CF_NOHILITE|CF_PRINTER;
+#ifdef OS2
+ start_line = ctmpb = ctmpa; /* default start line */
+#else
+ ctmpb = ctmpa;
+#endif
+ }
+
+ ctmpa->varnamep = ctmpb;
+ ctmpa->headingp = heading;
+ ctmpa->varmem = i;
+ ctmpa->var = vtmp;
+ ctmpa->value = printer_name(vtmp->current_val.l[i]);
+ }
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 0;
+ ctmpa->keymenu = &printer_select_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_select_tool;
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmpa);
+ heading = ctmpa;
+ ctmpa->valoffset = 0;
+ ctmpa->keymenu = &printer_edit_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_edit_tool;
+ ctmpa->varname
+#ifdef OS2
+ = cpystr(" Personally selected port or |command");
+#else
+ = cpystr(_(" Personally selected print command"));
+#endif
+ ctmpa->flags |= (CF_NOSELECT | CF_STARTITEM);
+ ctmpa->value = cpystr("");
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_PERSONAL_PRINT_COMMAND];
+
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_edit_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_edit_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("The text to be printed will be sent to the printer or command given here.");
+#else
+ = cpystr(_("The text to be printed will be piped into the command given here. The"));
+#endif
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_PERSONAL_PRINT_COMMAND];
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_edit_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_edit_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("The printer port or |pipe is in the 2nd column, the printer name is in");
+#else
+ = cpystr(_("command is in the 2nd column, the printer name is in the first column. Some"));
+#endif
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_PERSONAL_PRINT_COMMAND];
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_edit_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_edit_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("the first column. Examples: \"LPT1\", \"COM2\", \"|enscript\". A command may");
+#else
+ = cpystr(_("examples are: \"prt\", \"lpr\", \"lp\", or \"enscript\". The command may be given"));
+#endif
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_PERSONAL_PRINT_COMMAND];
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_edit_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_edit_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("be given options, for example \"|ascii2ps -p LPT1\" or \"|txt2hplc -2\". Use");
+#else
+ = cpystr(_("with options, for example \"enscript -2 -r\" or \"lpr -Plpacc170\". The"));
+#endif
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_PERSONAL_PRINT_COMMAND];
+
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 6;
+ ctmpa->keymenu = &printer_edit_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = print_edit_tool;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value
+#ifdef OS2
+ = cpystr("the |command method for printers that require conversion from ASCII.");
+#else
+ = cpystr(_("commands and options on your system may be different from these examples."));
+#endif
+ ctmpa->headingp = heading;
+ ctmpa->var = &ps->vars[V_PERSONAL_PRINT_COMMAND];
+
+ vtmp = &ps->vars[V_PERSONAL_PRINT_COMMAND];
+ lval = no_ex ? vtmp->current_val.l : LVAL(vtmp, ew);
+ if(lval){
+ for(i = 0; lval[i]; i++){
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 22;
+ ctmpa->keymenu = &printer_edit_keymenu;
+ ctmpa->help = h_config_set_custom_print;
+ ctmpa->tool = print_edit_tool;
+ if(i == 0){
+ ctmpa->varoffset = 8;
+ ctmpa->varname = cpystr(_("Printer List:"));
+ ctmpa->flags |= CF_NOHILITE|CF_PRINTER;
+ ctmpb = ctmpa;
+ }
+
+ ctmpa->varnamep = ctmpb;
+ ctmpa->headingp = heading;
+ ctmpa->varmem = i;
+ ctmpa->var = vtmp;
+ ctmpa->value = printer_name(lval[i]);
+ }
+ }
+ else{
+ new_confline(&ctmpa);
+ ctmpa->valoffset = 22;
+ ctmpa->keymenu = &printer_edit_keymenu;
+ ctmpa->help = h_config_set_custom_print;
+ ctmpa->tool = print_edit_tool;
+ ctmpa->flags |= CF_NOHILITE;
+ ctmpa->varoffset = 8;
+ ctmpa->varname = cpystr(_("Printer List:"));
+ ctmpa->varnamep = ctmpa;
+ ctmpa->headingp = heading;
+ ctmpa->varmem = 0;
+ ctmpa->var = vtmp;
+ ctmpa->value = cpystr("");
+ }
+
+ memset(&screen, 0, sizeof(screen));
+ screen.ro_warning = readonly_warning;
+ vsave = save_config_vars(ps, 0);
+ /* TRANSLATORS: SETUP... is a screen title
+ Print something1 using something2.
+ "printer config" is something1 */
+ switch(conf_scroll_screen(ps, &screen, start_line,
+ edit_exceptions ? _("SETUP PRINTER EXCEPTIONS")
+ : _("SETUP PRINTER"),
+ _("printer config"), 0)){
+ case 0:
+ break;
+
+ case 1:
+ write_pinerc(ps, ew, WRP_NONE);
+ break;
+
+ case 10:
+ revert_to_saved_config(ps, vsave, 0);
+ ps->printer_category = saved_printer_cat;
+ set_variable(V_PRINTER, saved_printer, 1, 0, ew);
+ set_variable(V_PERSONAL_PRINT_CATEGORY, comatose(ps->printer_category),
+ 1, 0, ew);
+ if(prc)
+ prc->outstanding_pinerc_changes = 0;
+
+ break;
+ }
+
+ def_printer_line = NULL;
+ free_saved_config(ps, &vsave, 0);
+ fs_give((void **)&saved_printer);
+}
+
+#endif /* !DOS */
+
+
+void
+set_def_printer_value(char *printer)
+{
+ char *p, *nick, *cmd;
+ int set, editing_norm_except_exists;
+ size_t l;
+
+ if(!def_printer_line)
+ return;
+
+ editing_norm_except_exists = ((ps_global->ew_for_except_vars != Main) &&
+ (ew == Main));
+
+ parse_printer(printer, &nick, &cmd, NULL, NULL, NULL, NULL);
+ p = *nick ? nick : cmd;
+ set = *p;
+ if(*def_printer_line)
+ fs_give((void **)def_printer_line);
+
+ l = strlen(p) + 60;
+ *def_printer_line = fs_get((l+1) * sizeof(char));
+ snprintf(*def_printer_line, l+1, "Default printer %s%s%s%s%s",
+ set ? "set to \"" : "unset", set ? p : "", set ? "\"" : "",
+ (set && editing_norm_except_exists) ? " (in exception config)" : "",
+ set ? "." : "");
+ (*def_printer_line)[l] = '\0';
+
+ fs_give((void **)&nick);
+ fs_give((void **)&cmd);
+}
+
+
+int
+print_select_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rc, retval, no_ex, printer_msg = 0;
+ char *p, **lval, *printer_was;
+ struct variable *vtmp;
+
+ no_ex = (ps_global->ew_for_except_vars == Main);
+
+ printer_was = ps->VAR_PRINTER ? cpystr(ps->VAR_PRINTER) : NULL;
+
+ switch(cmd){
+ case MC_EXIT:
+ retval = config_exit_cmd(flags);
+ break;
+
+ case MC_CHOICE :
+ if(cl && *cl){
+ char aname[100], wname[100];
+
+ strncpy(aname, ANSI_PRINTER, sizeof(aname)-1);
+ aname[sizeof(aname)-1] = '\0';
+ strncat(aname, no_ff, sizeof(aname)-strlen(aname)-1);
+ strncpy(wname, WYSE_PRINTER, sizeof(wname)-1);
+ wname[sizeof(wname)-1] = '\0';
+ strncat(wname, no_ff, sizeof(wname)-strlen(wname)-1);
+ if((*cl)->var){
+ vtmp = (*cl)->var;
+ lval = (no_ex || !vtmp->is_user) ? vtmp->current_val.l
+ : LVAL(vtmp, ew);
+ rc = set_variable(V_PRINTER, lval ? lval[(*cl)->varmem] : NULL,
+ 1, 0, ew);
+ if(rc == 0){
+ if(vtmp == &ps->vars[V_STANDARD_PRINTER])
+ ps->printer_category = 2;
+ else if(vtmp == &ps->vars[V_PERSONAL_PRINT_COMMAND])
+ ps->printer_category = 3;
+
+ set_variable(V_PERSONAL_PRINT_CATEGORY,
+ comatose(ps->printer_category), 1, 0, ew);
+
+ printer_msg++;
+ }
+ else
+ q_status_message(SM_ORDER,3,5, _("Trouble setting default printer"));
+
+ retval = 1;
+ }
+ else if(!strcmp((*cl)->value,ANSI_PRINTER)){
+ rc = set_variable(V_PRINTER, ANSI_PRINTER, 1, 0, ew);
+ if(rc == 0){
+ ps->printer_category = 1;
+ set_variable(V_PERSONAL_PRINT_CATEGORY,
+ comatose(ps->printer_category), 1, 0, ew);
+ printer_msg++;
+ }
+ else
+ q_status_message(SM_ORDER,3,5, _("Trouble setting default printer"));
+
+ retval = 1;
+ }
+ else if(!strcmp((*cl)->value,aname)){
+ rc = set_variable(V_PRINTER, aname, 1, 0, ew);
+ if(rc == 0){
+ ps->printer_category = 1;
+ set_variable(V_PERSONAL_PRINT_CATEGORY,
+ comatose(ps->printer_category), 1, 0, ew);
+ printer_msg++;
+ }
+ else
+ q_status_message(SM_ORDER,3,5, _("Trouble setting default printer"));
+
+ retval = 1;
+ }
+ else if(!strcmp((*cl)->value,WYSE_PRINTER)){
+ rc = set_variable(V_PRINTER, WYSE_PRINTER, 1, 0, ew);
+ if(rc == 0){
+ ps->printer_category = 1;
+ set_variable(V_PERSONAL_PRINT_CATEGORY,
+ comatose(ps->printer_category), 1, 0, ew);
+ printer_msg++;
+ }
+ else
+ q_status_message(SM_ORDER,3,5, _("Trouble setting default printer"));
+
+ retval = 1;
+ }
+ else if(!strcmp((*cl)->value,wname)){
+ rc = set_variable(V_PRINTER, wname, 1, 0, ew);
+ if(rc == 0){
+ ps->printer_category = 1;
+ set_variable(V_PERSONAL_PRINT_CATEGORY,
+ comatose(ps->printer_category), 1, 0, ew);
+ printer_msg++;
+ }
+ else
+ q_status_message(SM_ORDER,3,5, _("Trouble setting default printer"));
+
+ retval = 1;
+ }
+ else
+ retval = 0;
+ }
+ else
+ retval = 0;
+
+ if(retval){
+ ps->mangled_body = 1; /* BUG: redraw it all for now? */
+ set_def_printer_value(ps->VAR_PRINTER);
+ }
+
+ break;
+
+ default:
+ retval = -1;
+ break;
+ }
+
+ if(printer_msg){
+ p = NULL;
+ if(ps->VAR_PRINTER){
+ char *nick, *q;
+
+ parse_printer(ps->VAR_PRINTER, &nick, &q,
+ NULL, NULL, NULL, NULL);
+ p = cpystr(*nick ? nick : q);
+ fs_give((void **)&nick);
+ fs_give((void **)&q);
+ }
+
+ q_status_message4(SM_ORDER, 0, 3,
+ "Default printer%s %s%s%s",
+ ((!printer_was && !ps->VAR_PRINTER) ||
+ (printer_was && ps->VAR_PRINTER &&
+ !strcmp(printer_was,ps->VAR_PRINTER)))
+ ? " still" : "",
+ p ? "set to \"" : "unset",
+ p ? p : "", p ? "\"" : "");
+
+ if(p)
+ fs_give((void **)&p);
+ }
+
+ if(printer_was)
+ fs_give((void **)&printer_was);
+
+ return(retval);
+}
+
+
+int
+print_edit_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ char prompt[81], sval[MAXPATH+1], name[MAXPATH+1];
+ char *nick, *p, *tmp, **newval = NULL, **ltmp = NULL;
+ char **lval, **nelval;
+ int rv = 0, skip_to_next = 0, after = 0, i = 4, j, k = 0;
+ int oeflags, changing_selected = 0, no_ex;
+ HelpType help;
+ ESCKEY_S ekey[6];
+
+ /* need this to preserve old behavior when no exception config file */
+ no_ex = (ps_global->ew_for_except_vars == Main);
+
+ if(cmd == MC_CHOICE)
+ return(print_select_tool(ps, cmd, cl, flags));
+
+ if(!(cl && *cl && (*cl)->var))
+ return(0);
+
+ nelval = no_ex ? (*cl)->var->current_val.l : LVAL((*cl)->var, ew);
+ lval = LVAL((*cl)->var, ew);
+
+ switch(cmd){
+ case MC_ADD: /* add to list */
+ sval[0] = '\0';
+ if(!fixed_var((*cl)->var, "add to", NULL)){
+
+ if(lval && (*cl)->value){
+ strncpy(prompt, _("Enter printer name : "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+ else if(!lval && nelval){
+ /* Add to list which doesn't exist, but default does exist */
+ ekey[0].ch = 'r';
+ ekey[0].rval = 'r';
+ ekey[0].name = "R";
+ ekey[0].label = N_("Replace");
+ ekey[1].ch = 'a';
+ ekey[1].rval = 'a';
+ ekey[1].name = "A";
+ ekey[1].label = N_("Add To");
+ ekey[2].ch = -1;
+ strncpy(prompt, _("Replace or Add To default value ? "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ switch(i = radio_buttons(prompt, -FOOTER_ROWS(ps), ekey, 'a',
+ 'x', h_config_replace_add, RB_NORM)){
+ case 'a':
+ /* Make a list of the default commands, leaving room for
+ the command we are about to add below. */
+ for(k = 0; nelval[k]; k++)
+ ;
+
+ ltmp = (char **)fs_get((k+2) * sizeof(char *));
+
+ for(j = 0; j < k; j++)
+ ltmp[j] = cpystr(nelval[j]);
+
+ ltmp[k + 1] = ltmp[k] = NULL;
+
+add_text:
+ strncpy(prompt, _("Enter name of printer to be added : "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ break;
+
+ case 'r':
+replace_text:
+ strncpy(prompt, _("Enter the name for replacement printer : "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ break;
+
+ case 'x':
+ cmd_cancelled("Add");
+ break;
+ }
+
+ if(i == 'x')
+ break;
+ }
+ else{
+ strncpy(prompt, _("Enter name of printer to be added : "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+ ps->mangled_footer = 1;
+ help = NO_HELP;
+
+ name[0] = '\0';
+ i = 2;
+ while(i != 0 && i != 1){
+ if(lval && (*cl)->value){
+ ekey[0].ch = ctrl('W');
+ ekey[0].rval = 5;
+ ekey[0].name = "^W";
+ ekey[0].label = after ? N_("InsertBefore") : N_("InsertAfter");
+ ekey[1].ch = -1;
+ }
+ else
+ ekey[0].ch = -1;
+
+ oeflags = OE_APPEND_CURRENT;
+ i = optionally_enter(name, -FOOTER_ROWS(ps), 0, sizeof(name),
+ prompt,
+ (ekey[0].ch != -1) ? ekey : NULL,
+ help, &oeflags);
+ if(i == 0){
+ rv = ps->mangled_body = 1;
+ removing_leading_and_trailing_white_space(name);
+ }
+ else if(i == 1){
+ cmd_cancelled("Add");
+ }
+ else if(i == 3){
+ help = (help == NO_HELP) ? h_config_insert_after : NO_HELP;
+ }
+ else if(i == 4){ /* no redraw, yet */
+ }
+ else if(i == 5){ /* change from/to prepend to/from append */
+ after = after ? 0 : 1;
+ }
+ }
+
+ if(i == 0)
+ i = 2;
+
+#ifdef OS2
+ strncpy(prompt, "Enter port or |command : ", sizeof(prompt));
+#else
+ strncpy(prompt, _("Enter command for printer : "), sizeof(prompt));
+#endif
+ prompt[sizeof(prompt)-1] = '\0';
+ while(i != 0 && i != 1){
+ oeflags = OE_APPEND_CURRENT;
+ i = optionally_enter(sval, -FOOTER_ROWS(ps), 0, sizeof(sval),
+ prompt,
+ (ekey[0].ch != -1) ? ekey : NULL,
+ help, &oeflags);
+ if(i == 0){
+ rv = ps->mangled_body = 1;
+ removing_leading_and_trailing_white_space(sval);
+ if(*sval || !lval){
+
+ for(tmp = sval; *tmp; tmp++)
+ if(*tmp == ',')
+ i++; /* conservative count of ,'s */
+
+ if(!i){ /* only one item */
+ if (!ltmp){
+ ltmp = (char **)fs_get(2 * sizeof(char *));
+ ltmp[1] = NULL;
+ k = 0;
+ }
+ if(*name){
+ size_t l;
+
+ l = strlen(name) + 4 + strlen(sval);
+ ltmp[k] = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(ltmp[k], l+1, "%s [] %s", name, sval);
+ ltmp[k][l] = '\0';
+ }
+ else
+ ltmp[k] = cpystr(sval);
+ }
+ else{
+ /*
+ * Don't allow input of multiple entries at once.
+ */
+ q_status_message(SM_ORDER,3,5,
+ _("No commas allowed in command"));
+ i = 2;
+ continue;
+ }
+
+ config_add_list(ps, cl, ltmp, &newval, after);
+
+ if(after)
+ skip_to_next = 1;
+
+ fs_give((void **)&ltmp);
+ k = 0;
+ }
+ else
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Can't add %s to list"), empty_val);
+ }
+ else if(i == 1){
+ cmd_cancelled("Add");
+ }
+ else if(i == 3){
+ help = help == NO_HELP ? h_config_print_cmd : NO_HELP;
+ }
+ else if(i == 4){ /* no redraw, yet */
+ }
+ else if(i == 5){ /* change from/to prepend to/from append */
+ after = after ? 0 : 1;
+ }
+ }
+ }
+
+ break;
+
+ case MC_DELETE: /* delete */
+ if((*cl)->var->current_val.l
+ && (*cl)->var->current_val.l[(*cl)->varmem]
+ && !strucmp(ps->VAR_PRINTER,(*cl)->var->current_val.l[(*cl)->varmem]))
+ changing_selected = 1;
+
+ if(!lval && nelval){
+ char pmt[80];
+
+ snprintf(pmt, sizeof(pmt), _("Override default with %s"), empty_val2);
+ pmt[sizeof(pmt)-1] = '\0';
+ if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
+ char **ltmp;
+
+ sval[0] = '\0';
+ ltmp = (char **)fs_get(2 * sizeof(char *));
+ ltmp[0] = cpystr(sval);
+ ltmp[1] = NULL;
+ config_add_list(ps, cl, ltmp, &newval, 0);
+ fs_give((void **)&ltmp);
+ rv = ps->mangled_body = 1;
+ }
+ }
+ else if(!lval){
+ q_status_message(SM_ORDER, 0, 3, _("No set value to delete"));
+ }
+ else{
+ if((*cl)->var->is_fixed){
+ parse_printer(lval[(*cl)->varmem],
+ &nick, &p, NULL, NULL, NULL, NULL);
+ snprintf(prompt, sizeof(prompt), _("Delete (unused) printer %s "),
+ *nick ? nick : (!*p) ? empty_val2 : p);
+ fs_give((void **)&nick);
+ fs_give((void **)&p);
+ }
+ else
+ snprintf(prompt, sizeof(prompt), _("Really delete item %s from printer list "),
+ int2string((*cl)->varmem + 1));
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ ps->mangled_footer = 1;
+ if(want_to(prompt,'n','n',h_config_print_del, WT_FLUSH_IN) == 'y'){
+ rv = ps->mangled_body = 1;
+ fs_give((void **)&lval[(*cl)->varmem]);
+ config_del_list_item(cl, &newval);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Printer not deleted"));
+ }
+
+ break;
+
+ case MC_EDIT: /* edit/change list option */
+ if((*cl)->var->current_val.l
+ && (*cl)->var->current_val.l[(*cl)->varmem]
+ && !strucmp(ps->VAR_PRINTER,(*cl)->var->current_val.l[(*cl)->varmem]))
+ changing_selected = 1;
+
+ if(fixed_var((*cl)->var, NULL, "printer"))
+ break;
+ else if(!lval && nelval)
+ goto replace_text;
+ else if(!lval && !nelval)
+ goto add_text;
+ else{
+ HelpType help;
+
+ ekey[0].ch = 'n';
+ ekey[0].rval = 'n';
+ ekey[0].name = "N";
+ ekey[0].label = N_("Name");
+ ekey[1].ch = 'c';
+ ekey[1].rval = 'c';
+ ekey[1].name = "C";
+ ekey[1].label = N_("Command");
+ ekey[2].ch = 'o';
+ ekey[2].rval = 'o';
+ ekey[2].name = "O";
+ ekey[2].label = N_("Options");
+ ekey[3].ch = -1;
+ /* TRANSLATORS: this is a question with three choices */
+ strncpy(prompt, _("Change Name or Command or Options ? "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ i = radio_buttons(prompt, -FOOTER_ROWS(ps), ekey, 'c', 'x',
+ h_config_print_name_cmd, RB_NORM);
+
+ if(i == 'x'){
+ cmd_cancelled("Change");
+ break;
+ }
+ else if(i == 'c'){
+ char *all_but_cmd;
+
+ parse_printer(lval[(*cl)->varmem],
+ NULL, &p, NULL, NULL, NULL, &all_but_cmd);
+
+ strncpy(prompt, _("Change command : "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ strncpy(sval, p ? p : "", sizeof(sval)-1);
+ sval[sizeof(sval)-1] = '\0';
+ fs_give((void **)&p);
+
+ ps->mangled_footer = 1;
+ help = NO_HELP;
+ while(1){
+ oeflags = OE_APPEND_CURRENT;
+ i = optionally_enter(sval, -FOOTER_ROWS(ps), 0,
+ sizeof(sval), prompt, NULL,
+ help, &oeflags);
+ if(i == 0){
+ removing_leading_and_trailing_white_space(sval);
+ rv = ps->mangled_body = 1;
+ if(lval[(*cl)->varmem])
+ fs_give((void **)&lval[(*cl)->varmem]);
+
+ i = 0;
+ for(tmp = sval; *tmp; tmp++)
+ if(*tmp == ',')
+ i++; /* count of ,'s */
+
+ if(!i){ /* only one item */
+ size_t l;
+
+ l = strlen(all_but_cmd) + strlen(sval);
+ lval[(*cl)->varmem] = (char *)fs_get((l+1) * sizeof(char));
+ snprintf(lval[(*cl)->varmem], l+1, "%s%s", all_but_cmd, sval);
+ lval[(*cl)->varmem][l] = '\0';
+
+ newval = &(*cl)->value;
+ }
+ else{
+ /*
+ * Don't allow input of multiple entries at once.
+ */
+ q_status_message(SM_ORDER,3,5,
+ _("No commas allowed in command"));
+ continue;
+ }
+ }
+ else if(i == 1){
+ cmd_cancelled("Change");
+ }
+ else if(i == 3){
+ help = help == NO_HELP ? h_config_change : NO_HELP;
+ continue;
+ }
+ else if(i == 4){ /* no redraw, yet */
+ continue;
+ }
+
+ break;
+ }
+ }
+ else if(i == 'n'){
+ char *all_but_nick;
+
+ parse_printer(lval[(*cl)->varmem],
+ &p, NULL, NULL, NULL, &all_but_nick, NULL);
+
+ strncpy(prompt, _("Change name : "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ strncpy(name, p ? p : "", sizeof(name));
+ name[sizeof(name)-1] = '\0';
+
+ fs_give((void **)&p);
+
+ ps->mangled_footer = 1;
+ help = NO_HELP;
+ while(1){
+ oeflags = OE_APPEND_CURRENT;
+ i = optionally_enter(name, -FOOTER_ROWS(ps), 0,
+ sizeof(name), prompt, NULL,
+ help, &oeflags);
+ if(i == 0){
+ size_t l;
+
+ rv = ps->mangled_body = 1;
+ removing_leading_and_trailing_white_space(name);
+ if(lval[(*cl)->varmem])
+ fs_give((void **)&lval[(*cl)->varmem]);
+
+ l = strlen(name) + 1 + ((*all_but_nick == '[') ? 0 : 3) + strlen(all_but_nick);
+ lval[(*cl)->varmem] = (char *)fs_get((l+1) * sizeof(char));
+ snprintf(lval[(*cl)->varmem], l+1,
+ "%s %s%s", name,
+ (*all_but_nick == '[') ? "" : "[] ",
+ all_but_nick);
+ lval[(*cl)->varmem][l] = '\0';
+
+ newval = &(*cl)->value;
+ }
+ else if(i == 1){
+ cmd_cancelled("Change");
+ }
+ else if(i == 3){
+ help = help == NO_HELP ? h_config_change : NO_HELP;
+ continue;
+ }
+ else if(i == 4){ /* no redraw, yet */
+ continue;
+ }
+
+ break;
+ }
+
+ fs_give((void **)&all_but_nick);
+ }
+ else if(i == 'o'){
+ HelpType help;
+
+ ekey[0].ch = 'i';
+ ekey[0].rval = 'i';
+ ekey[0].name = "I";
+ ekey[0].label = N_("Init");
+ ekey[1].ch = 't';
+ ekey[1].rval = 't';
+ ekey[1].name = "T";
+ ekey[1].label = N_("Trailer");
+ ekey[2].ch = -1;
+ strncpy(prompt, _("Change Init string or Trailer string ? "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+ j = radio_buttons(prompt, -FOOTER_ROWS(ps), ekey, 'i', 'x',
+ h_config_print_opt_choice, RB_NORM);
+
+ if(j == 'x'){
+ cmd_cancelled("Change");
+ break;
+ }
+ else{
+ char *init, *trailer;
+
+ parse_printer(lval[(*cl)->varmem],
+ &nick, &p, &init, &trailer, NULL, NULL);
+
+ if(j == i)
+ snprintf(prompt, sizeof(prompt), _("Change INIT string : "));
+ else
+ snprintf(prompt, sizeof(prompt), _("Change TRAILER string : "));
+
+ strncpy(sval, (j == 'i') ? init : trailer, sizeof(sval)-1);
+ sval[sizeof(sval)-1] = '\0';
+
+ tmp = string_to_cstring(sval);
+ strncpy(sval, tmp, sizeof(sval)-1);
+ sval[sizeof(sval)-1] = '\0';
+ fs_give((void **)&tmp);
+
+ ps->mangled_footer = 1;
+ help = NO_HELP;
+ while(1){
+ oeflags = OE_APPEND_CURRENT;
+ i = optionally_enter(sval, -FOOTER_ROWS(ps), 0,
+ sizeof(sval), prompt, NULL, help, &oeflags);
+ if(i == 0){
+ size_t l;
+
+ removing_leading_and_trailing_white_space(sval);
+ rv = 1;
+ if(lval[(*cl)->varmem])
+ fs_give((void **)&lval[(*cl)->varmem]);
+ if(j == 'i'){
+ init = cstring_to_hexstring(sval);
+ tmp = cstring_to_hexstring(trailer);
+ fs_give((void **)&trailer);
+ trailer = tmp;
+ }
+ else{
+ trailer = cstring_to_hexstring(sval);
+ tmp = cstring_to_hexstring(init);
+ fs_give((void **)&init);
+ init = tmp;
+ }
+
+ l = strlen(nick) + 1 + 2 + strlen("INIT=") + strlen(init) + 1 + strlen("TRAILER=") + strlen(trailer)+ 1 + strlen(p);
+ lval[(*cl)->varmem] = (char *)fs_get((l+1) * sizeof(char));
+ snprintf(lval[(*cl)->varmem], l+1,
+ "%s%s%s%s%s%s%s%s%s%s%s",
+ /* nick */ nick,
+ /* space */ *nick ? " " : "",
+ /* [ */ (*nick || *init || *trailer) ? "[" : "",
+ /* INIT= */ *init ? "INIT=" : "",
+ /* init */ init,
+ /* space */ (*init && *trailer) ? " " : "",
+ /* TRAILER= */ *trailer ? "TRAILER=" : "",
+ /* trailer */ trailer,
+ /* ] */ (*nick || *init || *trailer) ? "]" : "",
+ /* space */ (*nick || *init || *trailer) ? " " : "",
+ /* command */ p);
+ lval[(*cl)->varmem][l] = '\0';
+
+ newval = &(*cl)->value;
+ }
+ else if(i == 1){
+ cmd_cancelled("Change");
+ }
+ else if(i == 3){
+ help=(help == NO_HELP)?h_config_print_init:NO_HELP;
+ continue;
+ }
+ else if(i == 4){ /* no redraw, yet */
+ continue;
+ }
+
+ break;
+ }
+
+ fs_give((void **)&nick);
+ fs_give((void **)&p);
+ fs_give((void **)&init);
+ fs_give((void **)&trailer);
+ }
+ }
+ }
+
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = config_exit_cmd(flags);
+ break;
+
+ default:
+ rv = -1;
+ break;
+ }
+
+ if(skip_to_next)
+ *cl = next_confline(*cl);
+
+ /*
+ * At this point, if changes occurred, var->user_val.X is set.
+ * So, fix the current_val, and handle special cases...
+ */
+ if(rv == 1){
+ set_current_val((*cl)->var, TRUE, FALSE);
+ fix_side_effects(ps, (*cl)->var, 0);
+
+ if(newval){
+ if(*newval)
+ fs_give((void **)newval);
+
+ if((*cl)->var->current_val.l)
+ *newval = printer_name((*cl)->var->current_val.l[(*cl)->varmem]);
+ else
+ *newval = cpystr("");
+ }
+
+ if(changing_selected)
+ print_select_tool(ps, MC_CHOICE, cl, flags);
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Given a single printer string from the config file, returns an allocated
+ * copy of the friendly printer name, which is
+ * "Nickname" command
+ */
+char *
+printer_name(char *input)
+{
+ char *nick, *cmd;
+ char *ret;
+
+ parse_printer(input, &nick, &cmd, NULL, NULL, NULL, NULL);
+ ret = (char *)fs_get((2+6*22+1+strlen(cmd)) * sizeof(char));
+ utf8_snprintf(ret, 2+6*22+1+strlen(cmd), "\"%.21w\"%*s%s",
+ *nick ? nick : "",
+ 22 - MIN(utf8_width(nick), 21),
+ "",
+ cmd);
+ fs_give((void **) &nick);
+ fs_give((void **) &cmd);
+
+ return(ret);
+}
diff --git a/alpine/print.h b/alpine/print.h
new file mode 100644
index 00000000..73e228b4
--- /dev/null
+++ b/alpine/print.h
@@ -0,0 +1,30 @@
+/*
+ * $Id: print.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_PRINT_INCLUDED
+#define PINE_PRINT_INCLUDED
+
+
+#include "../pith/state.h"
+
+
+/* exported protoypes */
+char *printer_name(char *);
+#ifndef DOS
+void select_printer(struct pine *, int);
+#endif
+
+
+#endif /* PINE_PRINT_INCLUDED */
diff --git a/alpine/radio.c b/alpine/radio.c
new file mode 100644
index 00000000..fc5c9ab9
--- /dev/null
+++ b/alpine/radio.c
@@ -0,0 +1,918 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: radio.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "radio.h"
+#include "keymenu.h"
+#include "busy.h"
+#include "status.h"
+#include "mailcmd.h"
+#include "titlebar.h"
+#include "roleconf.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/newmail.h"
+#include "../pith/util.h"
+
+
+/*
+ * Internal prototypes
+ */
+int pre_screen_config_want_to(char *, int, int);
+ESCKEY_S *construct_combined_esclist(ESCKEY_S *, ESCKEY_S *);
+void radio_help(int, int, HelpType);
+void draw_radio_prompt(int, unsigned, unsigned, char *);
+
+
+#define RAD_BUT_COL 0
+#define WANT_TO_BUF 2500
+
+
+/*
+ * want_to's array passed to radio_buttions...
+ */
+static ESCKEY_S yorn[] = {
+ {'y', 'y', "Y", N_("Yes")},
+ {'n', 'n', "N", N_("No")},
+ {-1, 0, NULL, NULL}
+};
+
+int
+pre_screen_config_want_to(char *question, int dflt, int on_ctrl_C)
+{
+ int ret = 0;
+ char rep[WANT_TO_BUF], *p;
+#ifdef _WINDOWS
+ rep[0] = '\0';
+ mswin_flush();
+ if(strlen(question) + 3 < WANT_TO_BUF){
+ snprintf(rep, sizeof(rep), "%s ?", question);
+ rep[sizeof(rep)-1] = '\0';
+ }
+
+ switch (mswin_yesno_utf8 (*rep ? rep : question)) {
+ default:
+ case 0: return (on_ctrl_C);
+ case 1: return ('y');
+ case 2: return ('n');
+ }
+#endif
+ while(!ret){
+ fprintf(stdout, "%s? [%c]:", question, dflt);
+ fgets(rep, WANT_TO_BUF, stdin);
+ if((p = strpbrk(rep, "\r\n")) != NULL)
+ *p = '\0';
+ switch(*rep){
+ case 'Y':
+ case 'y':
+ ret = (int)'y';
+ break;
+ case 'N':
+ case 'n':
+ ret = (int)'n';
+ break;
+ case '\0':
+ ret = dflt;
+ break;
+ default:
+ break;
+ }
+ }
+ return ret;
+}
+
+
+/*----------------------------------------------------------------------
+ Ask a yes/no question in the status line
+
+ Args: question -- string to prompt user with
+ dflt -- The default answer to the question (should probably
+ be y or n)
+ on_ctrl_C -- Answer returned on ^C
+ help -- Two line help text
+ flags -- Flags to modify behavior
+ WT_FLUSH_IN - Discard pending input.
+ WT_SEQ_SENSITIVE - Caller is sensitive to sequence
+ number changes caused by
+ unsolicited expunges while we're
+ viewing a message.
+
+ Result: Messes up the status line,
+ returns y, n, dflt, on_ctrl_C, or SEQ_EXCEPTION
+ ---*/
+int
+want_to(char *question, int dflt, int on_ctrl_C, HelpType help, int flags)
+{
+ char *free_this = NULL, *free_this2 = NULL, *prompt;
+ int rv, width;
+ size_t len;
+
+ if(!ps_global->ttyo)
+ return(pre_screen_config_want_to(question, dflt, on_ctrl_C));
+#ifdef _WINDOWS
+ if (mswin_usedialog ()) {
+ mswin_flush ();
+ switch (mswin_yesno_utf8 (question)) {
+ default:
+ case 0: return (on_ctrl_C);
+ case 1: return ('y');
+ case 2: return ('n');
+ }
+ }
+#endif
+
+ /*----
+ One problem with adding the (y/n) here is that shrinking the
+ screen while in radio_buttons() will cause it to get chopped
+ off. It would be better to truncate the question passed in
+ here and leave the full "(y/n) [x] : " on.
+ ----*/
+
+ len = strlen(question) + 4;
+ free_this = (char *) fs_get(len);
+ width = utf8_width(question);
+
+ if(width + 2 < ps_global->ttyo->screen_cols){
+ snprintf(free_this, len, "%s? ", question);
+ free_this[len-1] = '\0';
+ prompt = free_this;
+ }
+ else if(width + 1 < ps_global->ttyo->screen_cols){
+ snprintf(free_this, len, "%s?", question);
+ free_this[len-1] = '\0';
+ prompt = free_this;
+ }
+ else if(width < ps_global->ttyo->screen_cols){
+ snprintf(free_this, len, "%s", question);
+ free_this[len-1] = '\0';
+ prompt = free_this;
+ }
+ else{
+ free_this2 = (char *) fs_get(len);
+ snprintf(free_this2, len, "%s? ", question);
+ prompt = short_str(free_this2, free_this, len, ps_global->ttyo->screen_cols-1, MidDots);
+ }
+
+ if(on_ctrl_C == 'n') /* don't ever let cancel == 'n' */
+ on_ctrl_C = 0;
+
+ rv = radio_buttons(prompt,
+ (ps_global->ttyo->screen_rows > 4) ? - FOOTER_ROWS(ps_global) : -1,
+ yorn, dflt, on_ctrl_C, help, flags);
+
+ if(free_this)
+ fs_give((void **) &free_this);
+
+ if(free_this2)
+ fs_give((void **) &free_this2);
+
+ return(rv);
+}
+
+
+int
+one_try_want_to(char *question, int dflt, int on_ctrl_C, HelpType help, int flags)
+{
+ char *q2;
+ int rv;
+ size_t l;
+
+ l = strlen(question) + 5;
+ q2 = fs_get((l+1) * sizeof(char));
+ strncpy(q2, question, l);
+ q2[l] = '\0';
+ (void) utf8_truncate(q2, ps_global->ttyo->screen_cols - 6);
+ strncat(q2, "? ", l+1 - strlen(q2) - 1);
+ q2[l] = '\0';
+ rv = radio_buttons(q2,
+ (ps_global->ttyo->screen_rows > 4) ? - FOOTER_ROWS(ps_global) : -1,
+ yorn, dflt, on_ctrl_C, help, flags | RB_ONE_TRY);
+ fs_give((void **) &q2);
+
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Prompt user for a choice among alternatives
+
+Args -- utf8prompt: The prompt for the question/selection
+ line: The line to prompt on, if negative then relative to bottom
+ esc_list: ESC_KEY_S list of keys
+ dflt: The selection when the <CR> is pressed (should probably
+ be one of the chars in esc_list)
+ on_ctrl_C: The selection when ^C is pressed
+ help_text: Text to be displayed on bottom two lines
+ flags: Logically OR'd flags modifying our behavior to:
+ RB_FLUSH_IN - Discard any pending input chars.
+ RB_ONE_TRY - Only give one chance to answer. Returns
+ on_ctrl_C value if not answered acceptably
+ on first try.
+ RB_NO_NEWMAIL - Quell the usual newmail check.
+ RB_SEQ_SENSITIVE - The caller is sensitive to sequence number
+ changes so return on_ctrl_C if an
+ unsolicited expunge happens while we're
+ viewing a message.
+ RB_RET_HELP - Instead of the regular internal handling
+ way of handling help_text, this just causes
+ radio_buttons to return 3 when help is
+ asked for, so that the caller handles it
+ instead.
+
+ Note: If there are enough keys in the esc_list to need a second
+ screen, and there is no help, then the 13th key will be
+ put in the help position.
+
+Result -- Returns the letter pressed. Will be one of the characters in the
+ esc_list argument, or dflt, or on_ctrl_C, or SEQ_EXCEPTION.
+
+This will pause for any new status message to be seen and then prompt the user.
+The prompt will be truncated to fit on the screen. Redraw and resize are
+handled along with ^Z suspension. Typing ^G will toggle the help text on and
+off. Character types that are not buttons will result in a beep (unless one_try
+is set).
+ ----*/
+int
+radio_buttons(char *utf8prompt, int line, ESCKEY_S *esc_list, int dflt,
+ int on_ctrl_C, HelpType help_text, int flags)
+{
+ UCS ucs;
+ register int ch, real_line;
+ char *q, *ds = NULL;
+ unsigned maxcol;
+ int max_label, i, start, fkey_table[12];
+ int km_popped = 0;
+ struct key rb_keys[12];
+ struct key_menu rb_keymenu;
+ bitmap_t bitmap;
+ struct variable *vars = ps_global->vars;
+ COLOR_PAIR *lastc = NULL, *promptc = NULL;
+
+#ifdef _WINDOWS
+ int cursor_shown;
+
+ if (mswin_usedialog()){
+ MDlgButton button_list[25];
+ LPTSTR free_names[25];
+ LPTSTR free_labels[25];
+ int b, i, ret;
+ char **help;
+
+ memset(&free_names, 0, sizeof(LPTSTR) * 25);
+ memset(&free_labels, 0, sizeof(LPTSTR) * 25);
+ memset(&button_list, 0, sizeof (MDlgButton) * 25);
+ b = 0;
+
+ if(flags & RB_RET_HELP){
+ if(help_text != NO_HELP)
+ panic("RET_HELP and help in radio_buttons!");
+
+ button_list[b].ch = '?';
+ button_list[b].rval = 3;
+ button_list[b].name = TEXT("?");
+ free_labels[b] = utf8_to_lptstr(N_("Help"));
+ button_list[b].label = free_labels[b];
+ ++b;
+ }
+
+ for(i = 0; esc_list && esc_list[i].ch != -1 && i < 23; ++i){
+ if(esc_list[i].ch != -2){
+ button_list[b].ch = esc_list[i].ch;
+ button_list[b].rval = esc_list[i].rval;
+ free_names[b] = utf8_to_lptstr(esc_list[i].name);
+ button_list[b].name = free_names[b];
+ free_labels[b] = utf8_to_lptstr(esc_list[i].label);
+ button_list[b].label = free_labels[b];
+ ++b;
+ }
+ }
+
+ button_list[b].ch = -1;
+
+ /* assumption here is that HelpType is char ** */
+ help = help_text;
+
+ ret = mswin_select(utf8prompt, button_list, dflt, on_ctrl_C, help, flags);
+ for(i = 0; i < 25; i++){
+ if(free_names[i])
+ fs_give((void **) &free_names[i]);
+ if(free_labels[i])
+ fs_give((void **) &free_labels[i]);
+ }
+
+ return (ret);
+ }
+
+#endif /* _WINDOWS */
+
+ suspend_busy_cue();
+ flush_ordered_messages(); /* show user previous status msgs */
+ mark_status_dirty(); /* clear message next display call */
+ real_line = line > 0 ? line : ps_global->ttyo->screen_rows + line;
+ MoveCursor(real_line, RAD_BUT_COL);
+ CleartoEOLN();
+
+ /*---- Find widest label ----*/
+ max_label = 0;
+ for(i = 0; esc_list && esc_list[i].ch != -1 && i < 11; i++){
+ if(esc_list[i].ch == -2) /* -2 means to skip this key and leave blank */
+ continue;
+ if(esc_list[i].name)
+ max_label = MAX(max_label, utf8_width(esc_list[i].name));
+ }
+
+ if(ps_global->ttyo->screen_cols - max_label - 1 > 0)
+ maxcol = ps_global->ttyo->screen_cols - max_label - 1;
+ else
+ maxcol = 0;
+
+ /*
+ * We need to be able to truncate q, so copy it in case it is
+ * a readonly string.
+ */
+ q = cpystr(utf8prompt);
+
+ /*---- Init structs for keymenu ----*/
+ for(i = 0; i < 12; i++)
+ memset((void *)&rb_keys[i], 0, sizeof(struct key));
+
+ memset((void *)&rb_keymenu, 0, sizeof(struct key_menu));
+ rb_keymenu.how_many = 1;
+ rb_keymenu.keys = rb_keys;
+
+ /*---- Setup key menu ----*/
+ start = 0;
+ clrbitmap(bitmap);
+ memset(fkey_table, NO_OP_COMMAND, 12 * sizeof(int));
+ if(flags & RB_RET_HELP && help_text != NO_HELP)
+ panic("RET_HELP and help in radio_buttons!");
+
+ /* if shown, always at position 0 */
+ if(help_text != NO_HELP || flags & RB_RET_HELP){
+ rb_keymenu.keys[0].name = "?";
+ rb_keymenu.keys[0].label = N_("Help");
+ setbitn(0, bitmap);
+ fkey_table[0] = ctrl('G');
+ start++;
+ }
+
+ if(on_ctrl_C){
+ rb_keymenu.keys[1].name = "^C";
+ rb_keymenu.keys[1].label = N_("Cancel");
+ setbitn(1, bitmap);
+ fkey_table[1] = ctrl('C');
+ start++;
+ }
+
+ start = start ? 2 : 0;
+ /*---- Show the usual possible keys ----*/
+ for(i=start; esc_list && esc_list[i-start].ch != -1; i++){
+ /*
+ * If we have an esc_list item we'd like to put in the non-existent
+ * 13th slot, and there is no help, we put it in the help slot
+ * instead. We're hacking now...!
+ *
+ * We may also have invisible esc_list items that don't show up
+ * on the screen. We use this when we have two different keys
+ * which are synonyms, like ^P and KEY_UP. If all the slots are
+ * already full we can still fit invisible keys off the screen to
+ * the right. A key is invisible if it's label is "".
+ */
+ if(i >= 12){
+ if(esc_list[i-start].label
+ && esc_list[i-start].label[0] != '\0'){ /* visible */
+ if(i == 12){ /* special case where we put it in help slot */
+ if(help_text != NO_HELP)
+ panic("Programming botch in radio_buttons(): too many keys");
+
+ if(esc_list[i-start].ch != -2)
+ setbitn(0, bitmap); /* the help slot */
+
+ fkey_table[0] = esc_list[i-start].ch;
+ rb_keymenu.keys[0].name = esc_list[i-start].name;
+ if(esc_list[i-start].ch != -2
+ && esc_list[i-start].rval == dflt
+ && esc_list[i-start].label){
+ size_t l;
+
+ l = strlen(esc_list[i-start].label) + 2;
+ ds = (char *)fs_get((l+1) * sizeof(char));
+ snprintf(ds, l+1, "[%s]", esc_list[i-start].label);
+ ds[l] = '\0';
+ rb_keymenu.keys[0].label = ds;
+ }
+ else
+ rb_keymenu.keys[0].label = esc_list[i-start].label;
+ }
+ else
+ panic("Botch in radio_buttons(): too many keys");
+ }
+ }
+ else{
+ if(esc_list[i-start].ch != -2)
+ setbitn(i, bitmap);
+
+ fkey_table[i] = esc_list[i-start].ch;
+ rb_keymenu.keys[i].name = esc_list[i-start].name;
+ if(esc_list[i-start].ch != -2
+ && esc_list[i-start].rval == dflt
+ && esc_list[i-start].label){
+ size_t l;
+
+ l = strlen(esc_list[i-start].label) + 2;
+ ds = (char *)fs_get((l+1) * sizeof(char));
+ snprintf(ds, l+1, "[%s]", esc_list[i-start].label);
+ ds[l] = '\0';
+ rb_keymenu.keys[i].label = ds;
+ }
+ else
+ rb_keymenu.keys[i].label = esc_list[i-start].label;
+ }
+ }
+
+ for(; i < 12; i++)
+ rb_keymenu.keys[i].name = NULL;
+
+ ps_global->mangled_footer = 1;
+
+#ifdef _WINDOWS
+ cursor_shown = mswin_showcaret(1);
+#endif
+
+ if(pico_usingcolor() && VAR_PROMPT_FORE_COLOR &&
+ VAR_PROMPT_BACK_COLOR &&
+ pico_is_good_color(VAR_PROMPT_FORE_COLOR) &&
+ pico_is_good_color(VAR_PROMPT_BACK_COLOR)){
+ lastc = pico_get_cur_color();
+ if(lastc){
+ promptc = new_color_pair(VAR_PROMPT_FORE_COLOR,
+ VAR_PROMPT_BACK_COLOR);
+ (void)pico_set_colorp(promptc, PSC_NONE);
+ }
+ }
+ else
+ StartInverse();
+
+ draw_radio_prompt(real_line, RAD_BUT_COL, maxcol, q);
+
+ while(1){
+ fflush(stdout);
+
+ /*---- Paint the keymenu ----*/
+ if(lastc)
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ else
+ EndInverse();
+
+ draw_keymenu(&rb_keymenu, bitmap, ps_global->ttyo->screen_cols,
+ 1 - FOOTER_ROWS(ps_global), 0, FirstMenu);
+ if(promptc)
+ (void)pico_set_colorp(promptc, PSC_NONE);
+ else
+ StartInverse();
+
+ MoveCursor(real_line, MIN(RAD_BUT_COL+utf8_width(q), maxcol+1));
+
+ if(flags & RB_FLUSH_IN)
+ flush_input();
+
+ newcmd:
+ /* Timeout 5 min to keep imap mail stream alive */
+ ucs = read_char(600);
+ dprint((2,
+ "Want_to read: %s (0x%x)\n", pretty_command(ucs), ucs));
+ if((ucs < 0x80) && isupper((unsigned char) ucs))
+ ucs = tolower((unsigned char) ucs);
+
+ if(F_ON(F_USE_FK,ps_global)
+ && (((ucs < 0x80) && isalpha((unsigned char) ucs) && !strchr("YyNn",(int) ucs))
+ || ((ucs >= PF1 && ucs <= PF12)
+ && (ucs = fkey_table[ucs - PF1]) == NO_OP_COMMAND))){
+ /*
+ * The funky test above does two things. It maps
+ * esc_list character commands to function keys, *and* prevents
+ * character commands from input while in function key mode.
+ * NOTE: this breaks if we ever need more than the first
+ * twelve function keys...
+ */
+ if(flags & RB_ONE_TRY){
+ ch = ucs = on_ctrl_C;
+ goto out_of_loop;
+ }
+
+ Writechar(BELL, 0);
+ continue;
+ }
+
+ switch(ucs){
+
+ default:
+ for(i = 0; esc_list && esc_list[i].ch != -1; i++)
+ if(ucs == esc_list[i].ch){
+ int len, n;
+
+ MoveCursor(real_line,len=MIN(RAD_BUT_COL+utf8_width(q),maxcol+1));
+ for(n = 0, len = ps_global->ttyo->screen_cols - len;
+ esc_list[i].label && esc_list[i].label[n] && len > 0;
+ n++, len--)
+ Writechar(esc_list[i].label[n], 0);
+
+ ch = esc_list[i].rval;
+ goto out_of_loop;
+ }
+
+ if(flags & RB_ONE_TRY){
+ ch = on_ctrl_C;
+ goto out_of_loop;
+ }
+
+ Writechar(BELL, 0);
+ break;
+
+ case ctrl('M'):
+ case ctrl('J'):
+ ch = dflt;
+ for(i = 0; esc_list && esc_list[i].ch != -1; i++)
+ if(ch == esc_list[i].rval){
+ int len, n;
+
+ MoveCursor(real_line,len=MIN(RAD_BUT_COL+utf8_width(q),maxcol+1));
+ for(n = 0, len = ps_global->ttyo->screen_cols - len;
+ esc_list[i].label && esc_list[i].label[n] && len > 0;
+ n++, len--)
+ Writechar(esc_list[i].label[n], 0);
+ break;
+ }
+
+ goto out_of_loop;
+
+ case ctrl('C'):
+ if(on_ctrl_C || (flags & RB_ONE_TRY)){
+ ch = on_ctrl_C;
+ goto out_of_loop;
+ }
+
+ Writechar(BELL, 0);
+ break;
+
+
+ case '?':
+ case ctrl('G'):
+ if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
+ km_popped++;
+ FOOTER_ROWS(ps_global) = 3;
+ line = -3;
+ real_line = ps_global->ttyo->screen_rows + line;
+ if(lastc)
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ else
+ EndInverse();
+
+ clearfooter(ps_global);
+ if(promptc)
+ (void)pico_set_colorp(promptc, PSC_NONE);
+ else
+ StartInverse();
+
+ draw_radio_prompt(real_line, RAD_BUT_COL, maxcol, q);
+ break;
+ }
+
+ if(flags & RB_RET_HELP){
+ ch = 3;
+ goto out_of_loop;
+ }
+ else if(help_text != NO_HELP && FOOTER_ROWS(ps_global) > 1){
+ mark_keymenu_dirty();
+ if(lastc)
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ else
+ EndInverse();
+
+ MoveCursor(real_line + 1, RAD_BUT_COL);
+ CleartoEOLN();
+ MoveCursor(real_line + 2, RAD_BUT_COL);
+ CleartoEOLN();
+ radio_help(real_line, RAD_BUT_COL, help_text);
+ sleep(5);
+ MoveCursor(real_line, MIN(RAD_BUT_COL+utf8_width(q), maxcol+1));
+ if(promptc)
+ (void)pico_set_colorp(promptc, PSC_NONE);
+ else
+ StartInverse();
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+
+ case NO_OP_COMMAND:
+ goto newcmd; /* misunderstood escape? */
+
+ case NO_OP_IDLE: /* UNODIR, keep the stream alive */
+ if(flags & RB_NO_NEWMAIL)
+ goto newcmd;
+
+ i = new_mail(0, VeryBadTime, NM_DEFER_SORT);
+ if(sp_expunge_count(ps_global->mail_stream)
+ && flags & RB_SEQ_SENSITIVE){
+ if(on_ctrl_C)
+ ch = on_ctrl_C;
+ else
+ ch = SEQ_EXCEPTION;
+
+ goto out_of_loop;
+ }
+
+ if(i < 0)
+ break; /* no changes, get on with life */
+ /* Else fall into redraw to adjust displayed numbers and such */
+
+
+ case KEY_RESIZE:
+ case ctrl('L'):
+ real_line = line > 0 ? line : ps_global->ttyo->screen_rows + line;
+ if(lastc)
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ else
+ EndInverse();
+
+ ClearScreen();
+ redraw_titlebar();
+ if(ps_global->redrawer != NULL)
+ (*ps_global->redrawer)();
+ if(FOOTER_ROWS(ps_global) == 3 || km_popped)
+ redraw_keymenu();
+
+ if(ps_global->ttyo->screen_cols - max_label - 1 > 0)
+ maxcol = ps_global->ttyo->screen_cols - max_label - 1;
+ else
+ maxcol = 0;
+
+ if(promptc)
+ (void)pico_set_colorp(promptc, PSC_NONE);
+ else
+ StartInverse();
+
+ draw_radio_prompt(real_line, RAD_BUT_COL, maxcol, q);
+ break;
+
+ } /* switch */
+ }
+
+ out_of_loop:
+
+#ifdef _WINDOWS
+ if(!cursor_shown)
+ mswin_showcaret(0);
+#endif
+
+ fs_give((void **) &q);
+ if(ds)
+ fs_give((void **) &ds);
+
+ if(lastc){
+ (void) pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ if(promptc)
+ free_color_pair(&promptc);
+ }
+ else
+ EndInverse();
+
+ fflush(stdout);
+ resume_busy_cue(0);
+ if(km_popped){
+ FOOTER_ROWS(ps_global) = 1;
+ clearfooter(ps_global);
+ ps_global->mangled_body = 1;
+ }
+
+ return(ch);
+}
+
+
+#define OTHER_RETURN_VAL 1300
+#define KEYS_PER_LIST 8
+
+/*
+ * This should really be part of radio_buttons itself, I suppose. It was
+ * easier to do it this way. This is for when there are more than 12
+ * possible commands. We could have all the radio_buttons calls call this
+ * instead of radio_buttons, or rename this to radio_buttons.
+ *
+ * Radio_buttons is limited to 10 visible commands unless there is no Help,
+ * in which case it is 11 visible commands.
+ * Double_radio_buttons is limited to 16 visible commands because it uses
+ * slots 3 and 4 for readability and the OTHER CMD.
+ */
+int
+double_radio_buttons(char *prompt, int line, ESCKEY_S *esc_list, int dflt, int on_ctrl_C, HelpType help_text, int flags)
+{
+ ESCKEY_S *list = NULL, *list1 = NULL, *list2 = NULL;
+ int i = 0, j;
+ int v = OTHER_RETURN_VAL, listnum = 0;
+
+#ifdef _WINDOWS
+ if(mswin_usedialog())
+ return(radio_buttons(prompt, line, esc_list, dflt, on_ctrl_C,
+ help_text, flags));
+#endif
+
+ /* check to see if it will all fit in one */
+ while(esc_list && esc_list[i].ch != -1)
+ i++;
+
+ i++; /* for ^C */
+ if(help_text != NO_HELP)
+ i++;
+
+ if(i <= 12)
+ return(radio_buttons(prompt, line, esc_list, dflt, on_ctrl_C,
+ help_text, flags));
+
+ /*
+ * Won't fit, split it into two lists.
+ *
+ * We can fit at most 8 items in the visible list. The rest of
+ * the commands have to be invisible. Each of list1 and list2 should
+ * have no more than 8 visible (name != "" || label != "") items.
+ */
+ list1 = (ESCKEY_S *)fs_get((KEYS_PER_LIST+1) * sizeof(*list1));
+ memset(list1, 0, (KEYS_PER_LIST+1) * sizeof(*list1));
+ list2 = (ESCKEY_S *)fs_get((KEYS_PER_LIST+1) * sizeof(*list2));
+ memset(list2, 0, (KEYS_PER_LIST+1) * sizeof(*list2));
+
+ for(j=0,i=0; esc_list[i].ch != -1 && j < KEYS_PER_LIST; j++,i++)
+ list1[j] = esc_list[i];
+
+ list1[j].ch = -1;
+
+ for(j=0; esc_list[i].ch != -1 && j < KEYS_PER_LIST; j++,i++)
+ list2[j] = esc_list[i];
+
+ list2[j].ch = -1;
+
+ list = construct_combined_esclist(list1, list2);
+
+ while(v == OTHER_RETURN_VAL){
+ v = radio_buttons(prompt,line,list,dflt,on_ctrl_C,help_text,flags);
+ if(v == OTHER_RETURN_VAL){
+ fs_give((void **) &list);
+ listnum = 1 - listnum;
+ list = construct_combined_esclist(listnum ? list2 : list1,
+ listnum ? list1 : list2);
+ }
+ }
+
+ if(list)
+ fs_give((void **) &list);
+ if(list1)
+ fs_give((void **) &list1);
+ if(list2)
+ fs_give((void **) &list2);
+
+ return(v);
+}
+
+
+ESCKEY_S *
+construct_combined_esclist(ESCKEY_S *list1, ESCKEY_S *list2)
+{
+ ESCKEY_S *list;
+ int i, j=0, count;
+
+ count = 2; /* for blank key and for OTHER key */
+ for(i=0; list1 && list1[i].ch != -1; i++)
+ count++;
+ for(i=0; list2 && list2[i].ch != -1; i++)
+ count++;
+
+ list = (ESCKEY_S *) fs_get((count + 1) * sizeof(*list));
+ memset(list, 0, (count + 1) * sizeof(*list));
+
+ list[j].ch = -2; /* leave blank */
+ list[j].rval = 0;
+ list[j].name = NULL;
+ list[j++].label = NULL;
+
+ list[j].ch = 'o';
+ list[j].rval = OTHER_RETURN_VAL;
+ list[j].name = "O";
+ list[j].label = N_("OTHER CMDS");
+
+ /*
+ * Make sure that O for OTHER CMD or the return val for OTHER CMD
+ * isn't used for something else.
+ */
+ for(i=0; list1 && list1[i].ch != -1; i++){
+ if(list1[i].rval == list[j].rval)
+ panic("1bad rval in d_r");
+ if(F_OFF(F_USE_FK,ps_global) && list1[i].ch == list[j].ch)
+ panic("1bad ch in ccl");
+ }
+
+ for(i=0; list2 && list2[i].ch != -1; i++){
+ if(list2[i].rval == list[j].rval)
+ panic("2bad rval in d_r");
+ if(F_OFF(F_USE_FK,ps_global) && list2[i].ch == list[j].ch)
+ panic("2bad ch in ccl");
+ }
+
+ j++;
+
+ /* the visible set */
+ for(i=0; list1 && list1[i].ch != -1; i++){
+ if(i >= KEYS_PER_LIST && list1[i].label[0] != '\0')
+ panic("too many visible keys in ccl");
+
+ list[j++] = list1[i];
+ }
+
+ /* the rest are invisible */
+ for(i=0; list2 && list2[i].ch != -1; i++){
+ list[j] = list2[i];
+ list[j].label = "";
+ list[j++].name = "";
+ }
+
+ list[j].ch = -1;
+
+ return(list);
+}
+
+
+/*----------------------------------------------------------------------
+
+ ----*/
+void
+radio_help(int line, int column, HelpType help)
+{
+ char **text;
+
+ /* assumption here is that HelpType is char ** */
+ text = help;
+ if(text == NULL)
+ return;
+
+ MoveCursor(line + 1, column);
+ CleartoEOLN();
+ if(text[0])
+ PutLine0(line + 1, column, text[0]);
+
+ MoveCursor(line + 2, column);
+ CleartoEOLN();
+ if(text[1])
+ PutLine0(line + 2, column, text[1]);
+
+ fflush(stdout);
+}
+
+
+/*----------------------------------------------------------------------
+ Paint the screen with the radio buttons prompt
+ ----*/
+void
+draw_radio_prompt(int line, unsigned start_c, unsigned max_c, char *q)
+{
+ size_t len;
+ unsigned x, width, got_width;
+ char *tmpq = NULL, *useq;
+
+ width = utf8_width(q);
+ if(width > max_c - start_c + 1){
+ tmpq = (char *) fs_get((len=(strlen(q)+1)) * sizeof(char));
+ (void) utf8_to_width(tmpq, q, len, max_c - start_c + 1, &got_width);
+ useq = tmpq;
+ width = got_width;
+ }
+ else
+ useq = q;
+
+ PutLine0(line, start_c, useq);
+ x = start_c + width;
+ MoveCursor(line, x);
+ while(x++ < ps_global->ttyo->screen_cols)
+ Writechar(' ', 0);
+
+ MoveCursor(line, start_c + width);
+ fflush(stdout);
+ if(tmpq)
+ fs_give((void **) &tmpq);
+}
diff --git a/alpine/radio.h b/alpine/radio.h
new file mode 100644
index 00000000..3b29bd4d
--- /dev/null
+++ b/alpine/radio.h
@@ -0,0 +1,81 @@
+/*
+ * $Id: radio.h 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_RADIO_INCLUDED
+#define PINE_RADIO_INCLUDED
+
+#include "../pith/helptext.h"
+
+#include "help.h"
+
+
+/*
+ * For optionally_enter and radio_buttons, special meanings for keys.
+ */
+typedef struct esckey {
+ long ch; /* holds all UCS values plus -1 and -2 */
+ int rval; /* return value */
+ char *name; /* short name */
+ char *label; /* long descriptive key name */
+} ESCKEY_S;
+
+
+/*
+ * Minimum space needed after the prompt for optionally_enter() to work well.
+ * If the available width after the prompt is smaller than this then the prompt will
+ * have the leading edge cut off by optionally_enter.
+ * (note: used to be 5)
+ */
+#define MIN_OPT_ENT_WIDTH (7)
+
+/* max length prompt for optionally_enter and want_to */
+#define MAXPROMPT (ps_global->ttyo->screen_cols - MIN_OPT_ENT_WIDTH)
+
+
+#define SEQ_EXCEPTION (-3) /* returned when seq # change and no
+ on_ctrl_C available. */
+#define RB_NORM 0x00 /* flags modifying radio_buttons */
+#define RB_ONE_TRY 0x01 /* one shot answer, else default */
+#define RB_FLUSH_IN 0x02 /* discard pending input */
+#define RB_NO_NEWMAIL 0x04 /* Quell new mail check */
+#define RB_SEQ_SENSITIVE 0x08 /* Sensitive to seq # changes */
+#define RB_RET_HELP 0x10 /* Return when help key pressed */
+
+
+#define OE_NONE 0x00 /* optionally_enter flags */
+#define OE_DISALLOW_CANCEL 0x01 /* Turn off Cancel menu item */
+#define OE_DISALLOW_HELP 0x02 /* Turn off Help menu item */
+#define OE_USER_MODIFIED 0x08 /* set on return if user input */
+#define OE_KEEP_TRAILING_SPACE 0x10 /* Allow trailing white-space */
+#define OE_SEQ_SENSITIVE 0x20 /* Sensitive to seq # changes */
+#define OE_APPEND_CURRENT 0x40 /* append, don't truncate */
+#define OE_PASSWD 0x80 /* Use asterisks to echo input */
+#define OE_PASSWD_NOAST 0x100 /* Don't echo user input at all */
+
+#define WT_NORM 0x00 /* flags modifying want_to */
+#define WT_FLUSH_IN 0x01 /* discard pending input */
+#define WT_SEQ_SENSITIVE 0x02 /* Sensitive to seq # changes */
+
+
+/* exported protoypes */
+int want_to(char *, int, int, HelpType, int);
+int one_try_want_to(char *, int, int, HelpType, int);
+int radio_buttons(char *, int, ESCKEY_S *, int, int, HelpType, int);
+int double_radio_buttons(char *, int, ESCKEY_S *, int, int, HelpType, int);
+
+
+
+#endif /* PINE_RADIO_INCLUDED */
diff --git a/alpine/remote.c b/alpine/remote.c
new file mode 100644
index 00000000..b4891833
--- /dev/null
+++ b/alpine/remote.c
@@ -0,0 +1,347 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: remote.c 1032 2008-04-11 00:30:04Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "remote.h"
+#include "keymenu.h"
+#include "mailview.h"
+#include "status.h"
+#include "radio.h"
+#include "../pith/msgno.h"
+#include "../pith/filter.h"
+#include "../pith/util.h"
+#include "../pith/conf.h"
+#include "../pith/tempfile.h"
+#include "../pith/margin.h"
+
+
+/*
+ * Internal prototypes
+ */
+int rd_answer_forge_warning(int, MSGNO_S *, SCROLL_S *);
+
+
+int
+rd_prompt_about_forged_remote_data(int reason, REMDATA_S *rd, char *extra)
+{
+ char tmp[2000];
+ char *unknown = "<unknown>";
+ int rv = -1;
+ char *foldertype, *foldername, *special;
+
+ foldertype = (rd && rd->t.i.special_hdr && !strucmp(rd->t.i.special_hdr, REMOTE_ABOOK_SUBTYPE)) ? "address book" : (rd && rd->t.i.special_hdr && !strucmp(rd->t.i.special_hdr, REMOTE_PINERC_SUBTYPE)) ? "configuration" : "data";
+ foldername = (rd && rd->rn) ? rd->rn : unknown;
+ special = (rd && rd->t.i.special_hdr) ? rd->t.i.special_hdr : unknown;
+
+ dprint((1, "rd_check_out_forged_remote_data:\n"));
+ dprint((1, " reason=%d\n", reason));
+ dprint((1, " folder_type=%s\n", foldertype ? foldertype : "?"));
+ dprint((1, " remotename=%s\n\n", foldername ? foldername : "?"));
+
+ if(rd && rd->flags & USER_SAID_NO)
+ return rv;
+
+ if(reason == -2){
+ dprint((1, "The special header \"%s\" is missing from the last message in the folder.\nThis indicates that something is wrong.\nYou should probably answer \"No\"\nso that you don't use the corrupt data.\nThen you should investigate further.\n", special ? special : "?"));
+ }
+ else if(reason == -1){
+ dprint((1, "The last message in the folder contains \"Received\" headers.\nThis usually indicates that the message was put there by the mail\ndelivery system. Alpine does not add those Received headers.\nYou should probably answer \"No\" so that you don't use the corrupt data.\nThen you should investigate further.\n"));
+ }
+ else if(reason == 0){
+ dprint((1, "The special header \"%s\" in the last message\nin the folder has an unexpected value (%s)\nafter it. This could indicate that something is wrong.\nThis value should not normally be put there by Alpine.\nHowever, since there are no Received lines in the message we choose to\nbelieve that everything is ok and we will proceed.\n",
+ special ? special : "?", (extra && *extra) ? extra : "?"));
+ }
+ else if(reason == 1){
+ dprint((1, "The special header \"%s\" in the last message\nin the folder has an unexpected value (1)\nafter it. It appears that it may have been put there by an Pine\nwith a version number less than 4.50.\nSince there are no Received lines in the message we choose to believe that\nthe header was added by an old Pine and we will proceed.\n",
+ special ? special : "?"));
+ }
+ else if(reason > 1){
+ dprint((1, "The special header \"%s\" in the last message\nin the folder has an unexpected value (%s)\nafter it. This is the right sort of value that Alpine would normally put there,\nbut it doesn't match the value from the first message in the folder.\nThis may indicate that something is wrong.\nHowever, since there are no Received lines in the message we choose to\nbelieve that everything is ok and we will proceed.\n",
+ special ? special : "?", (extra && *extra) ? extra : "?"));
+ }
+
+ if(reason >= 0){
+ /*
+ * This check should not really be here. We have a cookie that
+ * has the wrong value so something is possibly wrong.
+ * But we are worried that old pines will put the bad value in
+ * there (which they will) and then the questions will bother
+ * users and mystify them. So we're just going to pretend the user
+ * said Yes in this case, and we'll try to fix the cookie.
+ * We still catch Received lines and use that or the complete absence
+ * of a special header as indicators of trouble.
+ */
+ rd->flags |= USER_SAID_YES;
+ return(1);
+ }
+
+ if(ps_global->ttyo){
+ SCROLL_S sargs;
+ STORE_S *in_store, *out_store;
+ gf_io_t pc, gc;
+ HANDLE_S *handles = NULL;
+ int the_answer = 'n';
+
+ if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
+ !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
+ goto try_wantto;
+
+ /* TRANSLATORS: the first %s is the folder type, it may be address book or
+ configuration. The second %s is the folder name. Of course, the HTML
+ tags should be left as is. */
+ snprintf(tmp, sizeof(tmp), _("<HTML><P>The data in the remote %s folder<P><CENTER>%s</CENTER><P>looks suspicious. The reason for the suspicion is<P><CENTER>"),
+ foldertype, foldername);
+ tmp[sizeof(tmp)-1] = '\0';
+ so_puts(in_store, tmp);
+
+ if(reason == -2){
+ snprintf(tmp, sizeof(tmp), _("header \"%s\" is missing</CENTER><P>The special header \"%s\" is missing from the last message in the folder. This indicates that something is wrong. You should probably answer \"No\" so that you don't use the corrupt data. Then you should investigate further."),
+ special, special);
+ tmp[sizeof(tmp)-1] = '\0';
+ so_puts(in_store, tmp);
+ }
+ else if(reason == -1){
+ so_puts(in_store, _("\"Received\" headers detected</CENTER><P>The last message in the folder contains \"Received\" headers. This usually indicates that the message was put there by the mail delivery system. Alpine does not add those Received headers. You should probably answer \"No\" so that you don't use the corrupt data. Then you should investigate further."));
+ }
+ else if(reason == 0){
+ snprintf(tmp, sizeof(tmp), _("Unexpected value for header \"%s\"</CENTER><P>The special header \"%s\" in the last message in the folder has an unexpected value (%s) after it. This probably indicates that something is wrong. This value would not normally be put there by Alpine. You should probably answer \"No\" so that you don't use the corrupt data. Then you should investigate further."),
+ special, special, (extra && *extra) ? extra : "?");
+ tmp[sizeof(tmp)-1] = '\0';
+ so_puts(in_store, tmp);
+ }
+ else if(reason == 1){
+ snprintf(tmp, sizeof(tmp), _("Unexpected value for header \"%s\"</CENTER><P>The special header \"%s\" in the last message in the folder has an unexpected value (1) after it. It appears that it may have been put there by a Pine with a version number less than 4.50. If you believe that you have changed this data with an older Pine more recently than you've changed it with this version of Alpine, then you can probably safely answer \"Yes\". If you do not understand why this has happened, you should probably answer \"No\" so that you don't use the corrupt data. Then you should investigate further."),
+ special, special);
+ tmp[sizeof(tmp)-1] = '\0';
+ so_puts(in_store, tmp);
+ }
+ else if(reason > 1){
+ snprintf(tmp, sizeof(tmp), _("Unexpected value for header \"%s\"</CENTER><P>The special header \"%s\" in the last message in the folder has an unexpected value (%s) after it. This is the right sort of value that Alpine would normally put there, but it doesn't match the value from the first message in the folder. This may indicate that something is wrong. Unless you understand why this has happened, you should probably answer \"No\" so that you don't use the corrupt data. Then you should investigate further."),
+ special, special, (extra && *extra) ? extra : "?");
+ tmp[sizeof(tmp)-1] = '\0';
+ so_puts(in_store, tmp);
+ }
+
+ so_seek(in_store, 0L, 0);
+ init_handles(&handles);
+ gf_filter_init();
+ gf_link_filter(gf_html2plain,
+ gf_html2plain_opt(NULL,
+ ps_global->ttyo->screen_cols, non_messageview_margin(),
+ &handles, NULL, GFHP_LOCAL_HANDLES));
+ gf_set_so_readc(&gc, in_store);
+ gf_set_so_writec(&pc, out_store);
+ gf_pipe(gc, pc);
+ gf_clear_so_writec(out_store);
+ gf_clear_so_readc(in_store);
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.handles = handles;
+ sargs.text.text = so_text(out_store);
+ sargs.text.src = CharStar;
+ sargs.bar.title = _("REMOTE DATA FORGERY WARNING");
+ sargs.proc.tool = rd_answer_forge_warning;
+ sargs.proc.data.p = (void *)&the_answer;
+ sargs.keys.menu = &forge_keymenu;
+ setbitmap(sargs.keys.bitmap);
+
+ scrolltool(&sargs);
+
+ if(the_answer == 'y'){
+ rv = 1;
+ rd->flags |= USER_SAID_YES;
+ }
+ else if(rd)
+ rd->flags |= USER_SAID_NO;
+
+ ps_global->mangled_screen = 1;
+ ps_global->painted_body_on_startup = 0;
+ ps_global->painted_footer_on_startup = 0;
+ so_give(&in_store);
+ so_give(&out_store);
+ free_handles(&handles);
+ }
+ else{
+ char *p = tmp;
+
+ snprintf(p, sizeof(tmp), _("\nThe data in the remote %s folder\n\n %s\n\nlooks suspicious. The reason for the suspicion is\n\n "),
+ foldertype, foldername);
+ tmp[sizeof(tmp)-1] = '\0';
+ p += strlen(p);
+
+ if(reason == -2){
+ snprintf(p, sizeof(tmp)-(p-tmp), _("header \"%s\" is missing\n\nThe special header \"%s\" is missing from the last message\nin the folder. This indicates that something is wrong.\nYou should probably answer \"No\" so that you don't use the corrupt data.\nThen you should investigate further.\n\n"),
+ special, special);
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+ else if(reason == -1){
+ snprintf(p, sizeof(tmp)-(p-tmp), _("\"Received\" headers detected\n\nThe last message in the folder contains \"Received\" headers.\nThis usually indicates that the message was put there by the\nmail delivery system. Alpine does not add those Received headers.\nYou should probably answer \"No\" so that you don't use the corrupt data.\nThen you should investigate further.\n\n"));
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+ else if(reason == 0){
+ snprintf(p, sizeof(tmp)-(p-tmp), _("Unexpected value for header \"%s\"\n\nThe special header \"%s\" in the last message in the folder\nhas an unexpected value (%s) after it. This probably\nindicates that something is wrong. This value would not normally be put\nthere by Alpine. You should probably answer \"No\" so that you don't use\nthe corrupt data. Then you should investigate further.\n\n"),
+ special, special, (extra && *extra) ? extra : "?");
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+ else if(reason == 1){
+ snprintf(p, sizeof(tmp)-(p-tmp), _("Unexpected value for header \"%s\"\n\nThe special header \"%s\" in the last message in the folder\nhas an unexpected value (1) after it. It appears that it may have been\nput there by a Pine with a version number less than 4.50.\nIf you believe that you have changed this data with an older Pine more\nrecently than you've changed it with this version of Alpine, then you can\nprobably safely answer \"Yes\". If you do not understand why this has\nhappened, you should probably answer \"No\" so that you don't use the\ncorrupt data. Then you should investigate further.\n\n"),
+ special, special);
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+ else if(reason > 1){
+ snprintf(p, sizeof(tmp)-(p-tmp), _("Unexpected value for header \"%s\"\n\nThe special header \"%s\" in the last message in the folder\nhas an unexpected\nvalue (%s) after it. This is\nthe right sort of value that Alpine would normally put there, but it\ndoesn't match the value from the first message in the folder. This may\nindicate that something is wrong. Unless you understand why this has happened,\nyou should probably answer \"No\" so that you don't use the\ncorrupt data. Then you should investigate further.\n\n"),
+ special, special, (extra && *extra) ? extra : "?");
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+
+try_wantto:
+ p += strlen(p);
+ snprintf(p, sizeof(tmp)-(p-tmp), _("Suspicious data in \"%s\": Continue anyway "),
+ (rd && rd->t.i.special_hdr) ? rd->t.i.special_hdr
+ : unknown);
+ tmp[sizeof(tmp)-1] = '\0';
+ if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
+ rv = 1;
+ rd->flags |= USER_SAID_YES;
+ }
+ else if(rd)
+ rd->flags |= USER_SAID_NO;
+ }
+
+ if(rv < 0)
+ q_status_message1(SM_ORDER, 1, 3, _("Can't open remote %s"),
+ (rd && rd->rn) ? rd->rn : "<noname>");
+
+ return(rv);
+}
+
+
+int
+rd_answer_forge_warning(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
+{
+ int rv = 1;
+
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ switch(cmd){
+ case MC_YES :
+ *(int *)(sparms->proc.data.p) = 'y';
+ break;
+
+ case MC_NO :
+ *(int *)(sparms->proc.data.p) = 'n';
+ break;
+
+ default:
+ panic("Unexpected command in rd_answer_forge_warning");
+ break;
+ }
+
+ return(rv);
+}
+
+
+
+char *
+rd_metadata_name(void)
+{
+ char *p, *q, *metafile;
+ char path[MAXPATH], pinerc_dir[MAXPATH];
+ struct variable *vars = ps_global->vars;
+
+ dprint((9, "rd_metadata_name\n"));
+
+ pinerc_dir[0] = '\0';
+ if(ps_global->pinerc){
+ char *prcn = ps_global->pinerc;
+ char *lc;
+
+ if((lc = last_cmpnt(prcn)) != NULL){
+ int to_copy;
+
+ to_copy = (lc - prcn > 1) ? (lc - prcn - 1) : 1;
+ strncpy(pinerc_dir, prcn, MIN(to_copy, sizeof(pinerc_dir)-1));
+ pinerc_dir[MIN(to_copy, sizeof(pinerc_dir)-1)] = '\0';
+ }
+ else{
+ pinerc_dir[0] = '.';
+ pinerc_dir[1] = '\0';
+ }
+ }
+
+ /*
+ * If there is no metadata file specified in the pinerc, create a filename.
+ */
+ if(!(VAR_REMOTE_ABOOK_METADATA && VAR_REMOTE_ABOOK_METADATA[0])){
+ if(pinerc_dir[0] && (p = tempfile_in_same_dir(ps_global->pinerc,
+ meta_prefix, NULL))){
+ /* fill in the pinerc variable */
+ q = p + strlen(pinerc_dir) + 1;
+ set_variable(V_REMOTE_ABOOK_METADATA, q, 1, 0, Main);
+ dprint((2, "creating name for metadata file: %s\n",
+ q ? q : "?"));
+
+ /* something's broken, return NULL rab */
+ if(!VAR_REMOTE_ABOOK_METADATA || !VAR_REMOTE_ABOOK_METADATA[0]){
+ our_unlink(p);
+ fs_give((void **)&p);
+ return(NULL);
+ }
+
+ fs_give((void **)&p);
+ }
+ else{
+ q_status_message(SM_ORDER, 3, 5,
+ "can't create metadata file in pinerc directory, continuing");
+ return(NULL);
+ }
+ }
+
+ build_path(path, pinerc_dir ? pinerc_dir : NULL,
+ VAR_REMOTE_ABOOK_METADATA, sizeof(path));
+ metafile = path;
+
+ /*
+ * If the metadata file doesn't exist, create it.
+ */
+ if(can_access(metafile, ACCESS_EXISTS) != 0){
+ int fd;
+
+ if((fd = our_open(metafile, O_CREAT|O_EXCL|O_WRONLY|O_BINARY, 0600)) < 0){
+
+ set_variable(V_REMOTE_ABOOK_METADATA, NULL, 1, 0, Main);
+
+ q_status_message2(SM_ORDER, 3, 5,
+ "can't create cache file %.200s, continuing (%.200s)",
+ metafile, error_description(errno));
+
+ dprint((2, "can't create metafile %s: %s\n",
+ metafile ? metafile : "?", error_description(errno)));
+
+ return(NULL);
+ }
+
+ dprint((2, "created metadata file: %s\n",
+ metafile ? metafile : "?"));
+
+ (void)close(fd);
+ }
+
+ return(cpystr(metafile));;
+}
+
diff --git a/alpine/remote.h b/alpine/remote.h
new file mode 100644
index 00000000..2509c79f
--- /dev/null
+++ b/alpine/remote.h
@@ -0,0 +1,27 @@
+/*
+ * $Id: remote.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_REMOTE_INCLUDED
+#define PINE_REMOTE_INCLUDED
+
+
+#include "../pith/remote.h"
+
+
+/* exported protoypes */
+char *rd_metadata_name(void);
+
+
+#endif /* PINE_REMOTE_INCLUDED */
diff --git a/alpine/reply.c b/alpine/reply.c
new file mode 100644
index 00000000..ee90ebd0
--- /dev/null
+++ b/alpine/reply.c
@@ -0,0 +1,2549 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ Code here for forward and reply to mail
+ A few support routines as well
+
+ This code will forward and reply to MIME messages. The Alpine composer
+at this time will only support non-text segments at the end of a
+message so, things don't always come out as one would like. If you
+always forward a message in MIME format, all will be correct. Forwarding
+of nested MULTIPART messages will work. There's still a problem with
+MULTIPART/ALTERNATIVE as the "first text part" rule doesn't allow modifying
+the equivalent parts. Ideally, we should probably such segments as a
+single attachment when forwarding/replying. It would also be real nice to
+flatten out the nesting in the composer so pieces inside can get snipped.
+
+The evolution continues...
+ =====*/
+
+
+#include "headers.h"
+#include "reply.h"
+#include "status.h"
+#include "radio.h"
+#include "send.h"
+#include "titlebar.h"
+#include "mailindx.h"
+#include "help.h"
+#include "signal.h"
+#include "mailcmd.h"
+#include "alpine.h"
+#include "roleconf.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/init.h"
+#include "../pith/filter.h"
+#include "../pith/pattern.h"
+#include "../pith/charset.h"
+#include "../pith/mimedesc.h"
+#include "../pith/remote.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/detoken.h"
+#include "../pith/newmail.h"
+#include "../pith/readfile.h"
+#include "../pith/tempfile.h"
+#include "../pith/busy.h"
+#include "../pith/ablookup.h"
+
+
+/*
+ * Internal Prototypes
+ */
+int reply_poster_followup(ENVELOPE *);
+int sigedit_exit_for_pico(struct headerentry *, void (*)(void), int, char **);
+long new_mail_for_pico(int, int);
+void cmd_input_for_pico(void);
+int display_message_for_pico(int);
+char *checkpoint_dir_for_pico(char *, size_t);
+void resize_for_pico(void);
+PCOLORS *colors_for_pico(void);
+void free_pcolors(PCOLORS **);
+
+
+/*----------------------------------------------------------------------
+ Fill in an outgoing message for reply and pass off to send
+
+ Args: pine_state -- The usual pine structure
+
+ Result: Reply is formatted and passed off to composer/mailer
+
+Reply
+
+ - put senders address in To field
+ - search to and cc fields to see if we aren't the only recipients
+ - if other than us, ask if we should reply to all.
+ - if answer yes, fill out the To and Cc fields
+ - fill in the fcc argument
+ - fill in the subject field
+ - fill out the body and the attachments
+ - pass off to pine_send()
+ ---*/
+int
+reply(struct pine *pine_state, ACTION_S *role_arg)
+{
+ ADDRESS *saved_from, *saved_to, *saved_cc, *saved_resent;
+ ADDRESS *us_in_to_and_cc, *ap;
+ ENVELOPE *env, *outgoing;
+ BODY *body, *orig_body = NULL;
+ REPLY_S reply;
+ void *msgtext = NULL;
+ char *tmpfix = NULL, *prefix = NULL, *fcc = NULL, *errmsg = NULL;
+ long msgno, j, totalm, rflags, *seq = NULL;
+ int i, include_text = 0, times = -1, warned = 0, rv = 0,
+ flags = RSF_QUERY_REPLY_ALL, reply_raw_body = 0;
+ int rolemsg = 0, copytomsg = 0;
+ gf_io_t pc;
+ PAT_STATE dummy;
+ REDRAFT_POS_S *redraft_pos = NULL;
+ ACTION_S *role = NULL, *nrole;
+#if defined(DOS) && !defined(_WINDOWS)
+ char *reserve;
+#endif
+
+ outgoing = mail_newenvelope();
+ totalm = mn_total_cur(pine_state->msgmap);
+ seq = (long *)fs_get(((size_t)totalm + 1) * sizeof(long));
+
+ dprint((4,"\n - reply (%s msgs) -\n", comatose(totalm)));
+
+ saved_from = (ADDRESS *) NULL;
+ saved_to = (ADDRESS *) NULL;
+ saved_cc = (ADDRESS *) NULL;
+ saved_resent = (ADDRESS *) NULL;
+
+ us_in_to_and_cc = (ADDRESS *) NULL;
+
+ outgoing->subject = NULL;
+
+ memset((void *)&reply, 0, sizeof(reply));
+
+ if(ps_global->full_header == 2
+ && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
+ reply_raw_body = 1;
+
+ /*
+ * We may have to loop through first to figure out what default
+ * reply-indent-string to offer...
+ */
+ if(mn_total_cur(pine_state->msgmap) > 1 &&
+ F_ON(F_ENABLE_EDIT_REPLY_INDENT, pine_state) &&
+ reply_quote_str_contains_tokens()){
+ for(msgno = mn_first_cur(pine_state->msgmap);
+ msgno > 0L && !tmpfix;
+ msgno = mn_next_cur(pine_state->msgmap)){
+
+ env = pine_mail_fetchstructure(pine_state->mail_stream,
+ mn_m2raw(pine_state->msgmap, msgno),
+ NULL);
+ if(!env) {
+ q_status_message1(SM_ORDER,3,4,
+ _("Error fetching message %s. Can't reply to it."),
+ long2string(msgno));
+ goto done_early;
+ }
+
+ if(!tmpfix){ /* look for prefix? */
+ tmpfix = reply_quote_str(env);
+ if(prefix){
+ i = strcmp(tmpfix, prefix);
+ fs_give((void **) &tmpfix);
+ if(i){ /* don't check back if dissimilar */
+ fs_give((void **) &prefix);
+ /*
+ * We free prefix, not tmpfix. We set tmpfix to prefix
+ * so that we won't come check again.
+ */
+ tmpfix = prefix = cpystr("> ");
+ }
+ }
+ else{
+ prefix = tmpfix;
+ tmpfix = NULL; /* check back later? */
+ }
+ }
+ }
+
+ tmpfix = prefix;
+ }
+
+ /*
+ * Loop thru the selected messages building the
+ * outgoing envelope's destinations...
+ */
+ for(msgno = mn_first_cur(pine_state->msgmap);
+ msgno > 0L;
+ msgno = mn_next_cur(pine_state->msgmap)){
+
+ /*--- Grab current envelope ---*/
+ env = pine_mail_fetchstructure(pine_state->mail_stream,
+ seq[++times] = mn_m2raw(pine_state->msgmap, msgno),
+ NULL);
+ if(!env) {
+ q_status_message1(SM_ORDER,3,4,
+ _("Error fetching message %s. Can't reply to it."),
+ long2string(msgno));
+ goto done_early;
+ }
+
+ /*
+ * We check for the prefix here if we didn't do it in the first
+ * loop above. This is just to save having to go through the loop
+ * twice in the cases where we don't need to.
+ */
+ if(!tmpfix){
+ tmpfix = reply_quote_str(env);
+ if(prefix){
+ i = strcmp(tmpfix, prefix);
+ fs_give((void **) &tmpfix);
+ if(i){ /* don't check back if dissimilar */
+ fs_give((void **) &prefix);
+ tmpfix = prefix = cpystr("> ");
+ }
+ }
+ else{
+ prefix = tmpfix;
+ tmpfix = NULL; /* check back later? */
+ }
+ }
+
+ /*
+ * For consistency, the first question is always "include text?"
+ */
+ if(!times){ /* only first time */
+ char *p = cpystr(prefix);
+
+ if((include_text=reply_text_query(pine_state,totalm,&prefix)) < 0)
+ goto done_early;
+
+ /* edited prefix? */
+ if(strcmp(p, prefix))
+ tmpfix = prefix; /* stop looking */
+
+ fs_give((void **)&p);
+ }
+
+ /*
+ * If we're agg-replying or there's a newsgroup and the user want's
+ * to post to news *and* via email, add relevant addresses to the
+ * outgoing envelope...
+ *
+ * The single message case gets us around the aggregate reply
+ * to messages in a mixed mail-news archive where some might
+ * have newsgroups and others not or whatever.
+ */
+ if(totalm > 1L || ((i = reply_news_test(env, outgoing)) & 1)){
+ if(totalm > 1)
+ flags |= RSF_FORCE_REPLY_TO;
+
+ if(!reply_harvest(pine_state, seq[times], NULL, env,
+ &saved_from, &saved_to, &saved_cc,
+ &saved_resent, &flags))
+ goto done_early;
+ }
+ else if(i == 0)
+ goto done_early;
+
+ /* collect a list of addresses that are us in to and cc fields */
+ if(env->to)
+ if((ap=reply_cp_addr(pine_state, 0L, NULL,
+ NULL, us_in_to_and_cc, NULL,
+ env->to, RCA_ONLY_US)) != NULL)
+ reply_append_addr(&us_in_to_and_cc, ap);
+
+ if(env->cc)
+ if((ap=reply_cp_addr(pine_state, 0L, NULL,
+ NULL, us_in_to_and_cc, NULL,
+ env->cc, RCA_ONLY_US)) != NULL)
+ reply_append_addr(&us_in_to_and_cc, ap);
+
+ /*------------ Format the subject line ---------------*/
+ if(outgoing->subject){
+ /*
+ * if reply to more than one message, and all subjects
+ * match, so be it. otherwise set it to something generic...
+ */
+ if(strucmp(outgoing->subject,
+ reply_subject(env->subject,tmp_20k_buf,SIZEOF_20KBUF))){
+ fs_give((void **)&outgoing->subject);
+ outgoing->subject = cpystr("Re: several messages");
+ }
+ }
+ else
+ outgoing->subject = reply_subject(env->subject, NULL, 0);
+ }
+
+ /* fill reply header */
+ reply_seed(pine_state, outgoing, env, saved_from,
+ saved_to, saved_cc, saved_resent,
+ &fcc, flags & RSF_FORCE_REPLY_ALL, &errmsg);
+ if(errmsg){
+ if(*errmsg){
+ q_status_message1(SM_ORDER, 3, 3, "%.200s", errmsg);
+ display_message(NO_OP_COMMAND);
+ }
+
+ fs_give((void **)&errmsg);
+ }
+
+ if(sp_expunge_count(pine_state->mail_stream)) /* cur msg expunged */
+ goto done_early;
+
+ /* Setup possible role */
+ if(role_arg)
+ role = copy_action(role_arg);
+
+ if(!role){
+ rflags = ROLE_REPLY;
+ if(nonempty_patterns(rflags, &dummy)){
+ /* setup default role */
+ nrole = NULL;
+ j = mn_first_cur(pine_state->msgmap);
+ do {
+ role = nrole;
+ nrole = set_role_from_msg(pine_state, rflags,
+ mn_m2raw(pine_state->msgmap, j),
+ NULL);
+ } while(nrole && (!role || nrole == role)
+ && (j=mn_next_cur(pine_state->msgmap)) > 0L);
+
+ if(!role || nrole == role)
+ role = nrole;
+ else
+ role = NULL;
+
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel reply */
+ role = NULL;
+ cmd_cancelled("Reply");
+ goto done_early;
+ }
+ }
+ }
+
+ /*
+ * Reply_seed may call c-client in get_fcc_based_on_to, so env may
+ * no longer be valid. Get it again.
+ * Similarly for set_role_from_message.
+ */
+ env = pine_mail_fetchstructure(pine_state->mail_stream, seq[times], NULL);
+
+ if(role){
+ rolemsg++;
+ /* override fcc gotten in reply_seed */
+ if(role->fcc && fcc)
+ fs_give((void **) &fcc);
+ }
+
+ if(F_ON(F_COPY_TO_TO_FROM, pine_state) && !(role && role->from)){
+ /*
+ * A list of all of our addresses that appear in the To
+ * and cc fields is in us_in_to_and_cc.
+ * If there is exactly one address in that list then
+ * use it for the outgoing From.
+ */
+ if(us_in_to_and_cc && !us_in_to_and_cc->next){
+ PINEFIELD *custom, *pf;
+ ADDRESS *a = NULL;
+ char *addr = NULL;
+
+ /*
+ * Check to see if this address is different from what
+ * we would have used anyway. If it is, notify the user
+ * with a status message. This is pretty hokey because we're
+ * mimicking how pine_send would set the From address and
+ * there is no coordination between the two.
+ */
+
+ /* in case user has a custom From value */
+ custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
+
+ pf = (PINEFIELD *) fs_get(sizeof(*pf));
+ memset((void *) pf, 0, sizeof(*pf));
+ pf->name = cpystr("From");
+ pf->addr = &a;
+ if(set_default_hdrval(pf, custom) >= UseAsDef
+ && pf->textbuf && pf->textbuf[0]){
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
+ if(addr)
+ fs_give((void **) &addr);
+ }
+
+ if(!*pf->addr)
+ *pf->addr = generate_from();
+
+ if(*pf->addr && !address_is_same(*pf->addr, us_in_to_and_cc)){
+ copytomsg++;
+ if(!role){
+ role = (ACTION_S *) fs_get(sizeof(*role));
+ memset((void *) role, 0, sizeof(*role));
+ role->is_a_role = 1;
+ }
+
+ role->from = us_in_to_and_cc;
+ us_in_to_and_cc = NULL;
+ }
+
+ free_customs(custom);
+ free_customs(pf);
+ }
+ }
+
+ if(role){
+ if(rolemsg && copytomsg)
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Replying using role \"%s\" and To as From"), role->nick);
+ else if(rolemsg)
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Replying using role \"%s\""), role->nick);
+ else if(copytomsg)
+ q_status_message(SM_ORDER, 3, 4,
+ _("Replying using incoming To as outgoing From"));
+ }
+
+ if(us_in_to_and_cc)
+ mail_free_address(&us_in_to_and_cc);
+
+
+ seq[++times] = -1L; /* mark end of sequence list */
+
+ /*========== Other miscelaneous fields ===================*/
+ outgoing->in_reply_to = reply_in_reply_to(env);
+ outgoing->references = reply_build_refs(env);
+ outgoing->message_id = generate_message_id();
+
+ if(!outgoing->to &&
+ !outgoing->cc &&
+ !outgoing->bcc &&
+ !outgoing->newsgroups)
+ q_status_message(SM_ORDER | SM_DING, 3, 6,
+ _("Warning: no valid addresses to reply to!"));
+
+
+ /*==================== Now fix up the message body ====================*/
+
+ /*
+ * create storage object to be used for message text
+ */
+ if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error allocating message text"));
+ goto done_early;
+ }
+
+ gf_set_so_writec(&pc, (STORE_S *) msgtext);
+
+ /*---- Include the original text if requested ----*/
+ if(include_text && totalm > 1L){
+ char *sig;
+ int impl, template_len = 0, leave_cursor_at_top = 0;
+
+
+ env = NULL;
+ if(role && role->template){
+ char *filtered;
+
+ impl = 0;
+ filtered = detoken(role, env, 0,
+ F_ON(F_SIG_AT_BOTTOM, ps_global) ? 1 : 0,
+ 0, &redraft_pos, &impl);
+ if(filtered){
+ if(*filtered){
+ so_puts((STORE_S *)msgtext, filtered);
+ if(impl == 1)
+ template_len = strlen(filtered);
+ else if(impl == 2)
+ leave_cursor_at_top++;
+ }
+
+ fs_give((void **)&filtered);
+ }
+ else
+ impl = 1;
+ }
+ else
+ impl = 1;
+
+ if((sig = reply_signature(role, env, &redraft_pos, &impl)) &&
+ F_OFF(F_SIG_AT_BOTTOM, ps_global)){
+
+ /*
+ * If CURSORPOS was set explicitly in sig_file, and there was a
+ * template file before that, we need to adjust the offset by the
+ * length of the template file. However, if the template had
+ * a set CURSORPOS in it then impl was 2 before getting to the
+ * signature, so offset wouldn't have been reset by the signature
+ * CURSORPOS and offset would already be correct. That case will
+ * be ok here because template_len will be 0 and adding it does
+ * nothing. If template
+ * didn't have CURSORPOS in it, then impl was 1 and got set to 2
+ * by the CURSORPOS in the sig. In that case we have to adjust the
+ * offset. That's what the next line does. It adjusts it if
+ * template_len is nonzero and if CURSORPOS was set in sig_file.
+ */
+ if(impl == 2)
+ redraft_pos->offset += template_len;
+
+ if(*sig)
+ so_puts((STORE_S *)msgtext, sig);
+
+ /*
+ * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
+ * is set, we won't have used it yet and want it to be non-NULL.
+ */
+ fs_give((void **)&sig);
+ }
+
+ /*
+ * Only put cursor in sig if there is a cursorpos there but not
+ * one in the template, and sig-at-bottom.
+ */
+ if(!(sig && impl == 2 && !leave_cursor_at_top))
+ leave_cursor_at_top++;
+
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ body->contents.text.data = msgtext;
+
+ for(msgno = mn_first_cur(pine_state->msgmap);
+ msgno > 0L;
+ msgno = mn_next_cur(pine_state->msgmap)){
+
+ if(env){ /* put 2 between messages */
+ gf_puts(NEWLINE, pc);
+ gf_puts(NEWLINE, pc);
+ }
+
+ /*--- Grab current envelope ---*/
+ env = pine_mail_fetchstructure(pine_state->mail_stream,
+ mn_m2raw(pine_state->msgmap, msgno),
+ &orig_body);
+ if(!env){
+ q_status_message1(SM_ORDER,3,4,
+ _("Error fetching message %s. Can't reply to it."),
+ long2string(mn_get_cur(pine_state->msgmap)));
+ goto done_early;
+ }
+
+ if(orig_body == NULL || orig_body->type == TYPETEXT || reply_raw_body) {
+ reply_delimiter(env, role, pc);
+ if(F_ON(F_INCLUDE_HEADER, pine_state))
+ reply_forward_header(pine_state->mail_stream,
+ mn_m2raw(pine_state->msgmap,msgno),
+ NULL, env, pc, prefix);
+
+ get_body_part_text(pine_state->mail_stream, reply_raw_body ? NULL : orig_body,
+ mn_m2raw(pine_state->msgmap, msgno),
+ reply_raw_body ? NULL : "1", 0L, pc, prefix,
+ NULL, GBPT_NONE);
+ }
+ else if(orig_body->type == TYPEMULTIPART) {
+ if(!warned++)
+ q_status_message(SM_ORDER,3,7,
+ _("WARNING! Attachments not included in multiple reply."));
+
+ if(orig_body->nested.part
+ && orig_body->nested.part->body.type == TYPETEXT) {
+ /*---- First part of the message is text -----*/
+ reply_delimiter(env, role, pc);
+ if(F_ON(F_INCLUDE_HEADER, pine_state))
+ reply_forward_header(pine_state->mail_stream,
+ mn_m2raw(pine_state->msgmap,
+ msgno),
+ NULL, env, pc, prefix);
+
+ get_body_part_text(pine_state->mail_stream,
+ &orig_body->nested.part->body,
+ mn_m2raw(pine_state->msgmap, msgno),
+ "1", 0L, pc, prefix, NULL, GBPT_NONE);
+ }
+ else{
+ q_status_message(SM_ORDER,0,3,
+ _("Multipart with no leading text part."));
+ }
+ }
+ else{
+ /*---- Single non-text message of some sort ----*/
+ q_status_message(SM_ORDER,3,3,
+ _("Non-text message not included."));
+ }
+ }
+
+ if(!leave_cursor_at_top){
+ long cnt = 0L;
+ unsigned char c;
+
+ /* rewind and count chars to start of sig file */
+ so_seek((STORE_S *)msgtext, 0L, 0);
+ while(so_readc(&c, (STORE_S *)msgtext))
+ cnt++;
+
+ if(!redraft_pos){
+ redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(*redraft_pos));
+ memset((void *)redraft_pos, 0,sizeof(*redraft_pos));
+ redraft_pos->hdrname = cpystr(":");
+ }
+
+ /*
+ * If explicit cursor positioning in sig file,
+ * add offset to start of sig file plus offset into sig file.
+ * Else, just offset to start of sig file.
+ */
+ redraft_pos->offset += cnt;
+ }
+
+ if(sig){
+ if(*sig)
+ so_puts((STORE_S *)msgtext, sig);
+
+ fs_give((void **)&sig);
+ }
+ }
+ else{
+ msgno = mn_m2raw(pine_state->msgmap,
+ mn_get_cur(pine_state->msgmap));
+
+ /*--- Grab current envelope ---*/
+ env = pine_mail_fetchstructure(pine_state->mail_stream, msgno,
+ &orig_body);
+
+ /*
+ * If the charset of the body part is different from ascii and
+ * charset conversion is _not_ happening, then preserve the original
+ * charset from the message so that if we don't enter any new
+ * chars with the hibit set we can use the original charset.
+ * If not all those things, then don't try to preserve it.
+ */
+ if(orig_body){
+ char *charset;
+
+ charset = parameter_val(orig_body->parameter, "charset");
+ if(charset && strucmp(charset, "us-ascii") != 0){
+ CONV_TABLE *ct;
+
+ /*
+ * There is a non-ascii charset, is there conversion happening?
+ */
+ if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
+ reply.orig_charset = charset;
+ charset = NULL;
+ }
+ }
+
+ if(charset)
+ fs_give((void **) &charset);
+ }
+
+ if(env) {
+ if(!(body = reply_body(pine_state->mail_stream, env, orig_body,
+ msgno, NULL, msgtext, prefix,
+ include_text, role, 1, &redraft_pos)))
+ goto done_early;
+ }
+ else{
+ q_status_message1(SM_ORDER,3,4,
+ _("Error fetching message %s. Can't reply to it."),
+ long2string(mn_get_cur(pine_state->msgmap)));
+ goto done_early;
+ }
+ }
+
+ /* fill in reply structure */
+ reply.prefix = prefix;
+ reply.mailbox = cpystr(pine_state->mail_stream->mailbox);
+ reply.origmbox = cpystr(pine_state->mail_stream->original_mailbox
+ ? pine_state->mail_stream->original_mailbox
+ : pine_state->mail_stream->mailbox);
+ reply.data.uid.msgs = (imapuid_t *) fs_get((times + 1) * sizeof(imapuid_t));
+ if((reply.data.uid.validity = pine_state->mail_stream->uid_validity) != 0){
+ reply.uid = 1;
+ for(i = 0; i < times ; i++)
+ reply.data.uid.msgs[i] = mail_uid(pine_state->mail_stream, seq[i]);
+ }
+ else{
+ reply.msgno = 1;
+ for(i = 0; i < times ; i++)
+ reply.data.uid.msgs[i] = seq[i];
+ }
+
+ reply.data.uid.msgs[i] = 0; /* tie off list */
+
+#if defined(DOS) && !defined(_WINDOWS)
+ free((void *)reserve);
+#endif
+
+ /* partially formatted outgoing message */
+ pine_send(outgoing, &body, _("COMPOSE MESSAGE REPLY"),
+ role, fcc, &reply, redraft_pos, NULL, NULL, 0);
+
+ rv++;
+ pine_free_body(&body);
+ if(reply.mailbox)
+ fs_give((void **) &reply.mailbox);
+ if(reply.origmbox)
+ fs_give((void **) &reply.origmbox);
+ if(reply.orig_charset)
+ fs_give((void **) &reply.orig_charset);
+ fs_give((void **) &reply.data.uid.msgs);
+ done_early:
+ if((STORE_S *) msgtext)
+ gf_clear_so_writec((STORE_S *) msgtext);
+
+ mail_free_envelope(&outgoing);
+ mail_free_address(&saved_from);
+ mail_free_address(&saved_to);
+ mail_free_address(&saved_cc);
+ mail_free_address(&saved_resent);
+
+ fs_give((void **)&seq);
+
+ if(prefix)
+ fs_give((void **)&prefix);
+
+ if(fcc)
+ fs_give((void **) &fcc);
+
+ free_redraft_pos(&redraft_pos);
+ free_action(&role);
+ return rv;
+}
+
+
+/*
+ * Ask user to confirm role choice, or choose another role.
+ *
+ * Args role -- A pointer into the pattern_h space at the default
+ * role to use. This can't be a copy, the comparison
+ * relies on it pointing at the actual role.
+ * This arg is also used to return a pointer to the
+ * chosen role.
+ *
+ * Returns 1 -- Yes, use role which is now in *role. This may not be
+ * the same as the role passed in and it may even be NULL.
+ * 0 -- Cancel reply.
+ */
+int
+confirm_role(long int rflags, ACTION_S **role)
+{
+ ACTION_S *role_p = NULL;
+ ACTION_S *default_role = NULL;
+ char prompt[80], *prompt_fodder;
+ int cmd, done, ret = 1;
+ void (*prev_screen)(struct pine *) = ps_global->prev_screen,
+ (*redraw)(void) = ps_global->redrawer;
+ PAT_S *curpat, *pat;
+ PAT_STATE pstate;
+ HelpType help;
+ ESCKEY_S ekey[4];
+
+ if(!nonempty_patterns(ROLE_DO_ROLES, &pstate) || !role)
+ return(ret);
+
+ /*
+ * If this is a reply or forward and the role doesn't require confirmation,
+ * then we just return with what was passed in.
+ */
+ if(((rflags & ROLE_REPLY) &&
+ *role && (*role)->repl_type == ROLE_REPL_NOCONF) ||
+ ((rflags & ROLE_FORWARD) &&
+ *role && (*role)->forw_type == ROLE_FORW_NOCONF) ||
+ ((rflags & ROLE_COMPOSE) &&
+ *role && (*role)->comp_type == ROLE_COMP_NOCONF) ||
+ (!*role && F_OFF(F_ROLE_CONFIRM_DEFAULT, ps_global)
+ && !ps_global->default_role))
+ return(ret);
+
+ /*
+ * Check that there is at least one role available. This is among all
+ * roles, not just the reply roles or just the forward roles. That's
+ * because we have ^T take us to all the roles, not the category-specific
+ * roles.
+ */
+ if(!(pat = last_pattern(&pstate)))
+ return(ret);
+
+ ekey[0].ch = 'y';
+ ekey[0].rval = 'y';
+
+ ekey[1].ch = 'n';
+ ekey[1].rval = 'n';
+
+ ekey[2].ch = ctrl('T');
+ ekey[2].rval = 2;
+ ekey[2].name = "^T";
+
+ ekey[3].ch = -1;
+
+ /* check for more than one role available (or no role set) */
+ if(pat == first_pattern(&pstate) && *role) /* no ^T */
+ ekey[2].ch = -1;
+
+ /*
+ * Setup default role
+ * Go through the loop just in case default_role doesn't point
+ * to a real current role.
+ */
+ if(ps_global->default_role){
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->action == ps_global->default_role){
+ default_role = ps_global->default_role;
+ break;
+ }
+ }
+ }
+
+ curpat = NULL;
+
+ /* override default */
+ if(*role){
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->action == *role){
+ curpat = pat;
+ break;
+ }
+ }
+ }
+
+ if(rflags & ROLE_REPLY)
+ prompt_fodder = _("Reply");
+ else if(rflags & ROLE_FORWARD)
+ prompt_fodder = _("Forward");
+ else
+ prompt_fodder = _("Compose");
+
+ done = 0;
+ while(!done){
+ if(curpat){
+ char buf[100];
+
+ help = h_role_confirm;
+ ekey[0].name = "Y";
+ ekey[0].label = N_("Yes");
+ ekey[1].name = "N";
+ if(default_role)
+ ekey[1].label = N_("No, use default role");
+ else
+ ekey[1].label = N_("No, use default settings");
+
+ ekey[2].label = N_("To Select Alternate Role");
+
+ if(curpat->patgrp && curpat->patgrp->nick)
+ /* TRANSLATORS: This is something like Use role <nickname of role> for Reply? */
+ snprintf(prompt, sizeof(prompt), _("Use role \"%s\" for %s? "),
+ short_str(curpat->patgrp->nick, buf, sizeof(buf), 50, MidDots),
+ prompt_fodder);
+ else
+ snprintf(prompt, sizeof(prompt),
+ _("Use role \"<a role without a nickname>\" for %s? "),
+ prompt_fodder);
+ }
+ else{
+ help = h_norole_confirm;
+ ekey[0].name = "Ret";
+ ekey[0].label = prompt_fodder;
+ ekey[1].name = "";
+ ekey[1].label = "";
+ ekey[2].label = N_("To Select Role");
+ snprintf(prompt, sizeof(prompt),
+ _("Press Return to %s using %s role, or ^T to select a role "),
+ prompt_fodder, default_role ? _("default") : _("no"));
+ }
+
+ prompt[sizeof(prompt)-1] = '\0';
+
+ cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
+ 'y', 'x', help, RB_NORM);
+
+ switch(cmd){
+ case 'y': /* Accept */
+ done++;
+ *role = curpat ? curpat->action : default_role;
+ break;
+
+ case 'x': /* Cancel */
+ ret = 0;
+ /* fall through */
+
+ case 'n': /* NoRole */
+ done++;
+ *role = default_role;
+ break;
+
+ case 2: /* ^T */
+ if(role_select_screen(ps_global, &role_p, 0) >= 0){
+ if(role_p){
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->action == role_p){
+ curpat = pat;
+ break;
+ }
+ }
+ }
+ else
+ curpat = NULL;
+ }
+
+ ClearBody();
+ ps_global->mangled_body = 1;
+ ps_global->prev_screen = prev_screen;
+ ps_global->redrawer = redraw;
+ break;
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * reply_to_all_query - Ask user about replying to all recipients
+ *
+ * Returns: -1 if cancel, 0 otherwise
+ * by reference: flagp
+ */
+int
+reply_to_all_query(int *flagp)
+{
+ switch(want_to("Reply to all recipients",
+ 'n', 'x', NO_HELP, WT_SEQ_SENSITIVE)){
+ case 'x' :
+ return(-1);
+
+ case 'y' : /* set reply-all bit */
+ (*flagp) |= RSF_FORCE_REPLY_ALL;
+ break;
+
+ case 'n' : /* clear reply-all bit */
+ (*flagp) &= ~RSF_FORCE_REPLY_ALL;
+ break;
+ }
+
+ return(0);
+}
+
+
+/*
+ * reply_using_replyto_query - Ask user about replying with reply-to value
+ *
+ * Returns: 'y' if yes
+ * 'x' if cancel
+ */
+int
+reply_using_replyto_query(void)
+{
+ return(want_to("Use \"Reply-To:\" address instead of \"From:\" address",
+ 'y', 'x', NO_HELP,WT_SEQ_SENSITIVE));
+}
+
+
+/*
+ * reply_text_query - Ask user about replying with text...
+ *
+ * Returns: 1 if include the text
+ * 0 if we're NOT to include the text
+ * -1 on cancel or error
+ */
+int
+reply_text_query(struct pine *ps, long int many, char **prefix)
+{
+ int ret, edited = 0;
+ static ESCKEY_S rtq_opts[] = {
+ {'y', 'y', "Y", N_("Yes")},
+ {'n', 'n', "N", N_("No")},
+ {-1, 0, NULL, NULL}, /* may be overridden below */
+ {-1, 0, NULL, NULL}
+ };
+
+ if(F_ON(F_AUTO_INCLUDE_IN_REPLY, ps)
+ && F_OFF(F_ENABLE_EDIT_REPLY_INDENT, ps))
+ return(1);
+
+ while(1){
+ if(many > 1L)
+ /* TRANSLATORS: The final three %s's can probably be safely ignored */
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s original messages in Reply%s%s%s? "),
+ comatose(many),
+ F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "",
+ F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "",
+ F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : "");
+ else
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include original message in Reply%s%s%s? "),
+ F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "",
+ F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "",
+ F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : "");
+
+ if(F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps)){
+ rtq_opts[2].ch = ctrl('R');
+ rtq_opts[2].rval = 'r';
+ rtq_opts[2].name = "^R";
+ rtq_opts[2].label = N_("Edit Indent String");
+ }
+ else
+ rtq_opts[2].ch = -1;
+
+ switch(ret = radio_buttons(tmp_20k_buf,
+ ps->ttyo->screen_rows > 4
+ ? -FOOTER_ROWS(ps_global) : -1,
+ rtq_opts,
+ (edited || F_ON(F_AUTO_INCLUDE_IN_REPLY, ps))
+ ? 'y' : 'n',
+ 'x', NO_HELP, RB_SEQ_SENSITIVE)){
+ case 'x':
+ cmd_cancelled("Reply");
+ return(-1);
+
+ case 'r':
+ if(prefix && *prefix){
+ int done = 0;
+ char buf[64];
+ int flags;
+
+ while(!done){
+ strncpy(buf, *prefix, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+
+ flags = OE_APPEND_CURRENT |
+ OE_KEEP_TRAILING_SPACE |
+ OE_DISALLOW_HELP |
+ OE_SEQ_SENSITIVE;
+
+ switch(optionally_enter(buf, ps->ttyo->screen_rows > 4
+ ? -FOOTER_ROWS(ps_global) : -1,
+ 0, sizeof(buf), "Reply prefix : ",
+ NULL, NO_HELP, &flags)){
+ case 0: /* entry successful, continue */
+ if(flags & OE_USER_MODIFIED){
+ fs_give((void **)prefix);
+ *prefix = removing_quotes(cpystr(buf));
+ edited = 1;
+ }
+
+ done++;
+ break;
+
+ case 1:
+ cmd_cancelled("Reply");
+
+ case -1:
+ return(-1);
+
+ case 4:
+ EndInverse();
+ ClearScreen();
+ redraw_titlebar();
+ if(ps_global->redrawer != NULL)
+ (*ps_global->redrawer)();
+
+ redraw_keymenu();
+ break;
+
+ case 3:
+ break;
+
+ case 2:
+ default:
+ q_status_message(SM_ORDER, 3, 4,
+ "Programmer botch in reply_text_query()");
+ return(-1);
+ }
+ }
+ }
+
+ break;
+
+ case 'y':
+ return(1);
+
+ case 'n':
+ return(0);
+
+ default:
+ q_status_message1(SM_ORDER, 3, 4,
+ "Invalid rval \'%s\'", pretty_command(ret));
+ return(-1);
+ }
+ }
+}
+
+
+/*
+ * reply_poster_followup - return TRUE if "followup-to" set to "poster"
+ *
+ * NOTE: queues status message indicating such
+ */
+int
+reply_poster_followup(ENVELOPE *e)
+{
+ if(e && e->followup_to && !strucmp(e->followup_to, "poster")){
+ q_status_message(SM_ORDER, 2, 3,
+ _("Replying to Poster as specified in \"Followup-To\""));
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*
+ * reply_news_test - Test given envelope for newsgroup data and copy
+ * it at the users request
+ * RETURNS:
+ * 0 if error or cancel
+ * 1 reply via email
+ * 2 follow-up via news
+ * 3 do both
+ */
+int
+reply_news_test(ENVELOPE *env, ENVELOPE *outgoing)
+{
+ int ret = 1;
+ static ESCKEY_S news_opt[] = { {'f', 'f', "F", N_("Follow-up")},
+ {'r', 'r', "R", N_("Reply")},
+ {'b', 'b', "B", N_("Both")},
+ {-1, 0, NULL, NULL} };
+
+ if(env->newsgroups && *env->newsgroups && !reply_poster_followup(env))
+ /*
+ * Now that we know a newsgroups field is present,
+ * ask if the user is posting a follow-up article...
+ */
+ switch(radio_buttons(
+ _("Follow-up to news group(s), Reply via email to author or Both? "),
+ -FOOTER_ROWS(ps_global), news_opt, 'r', 'x',
+ NO_HELP, RB_NORM)){
+ case 'r' : /* Reply */
+ ret = 1;
+ break;
+
+ case 'f' : /* Follow-Up via news ONLY! */
+ ret = 2;
+ break;
+
+ case 'b' : /* BOTH */
+ ret = 3;
+ break;
+
+ case 'x' : /* cancel or unknown response */
+ default :
+ cmd_cancelled("Reply");
+ ret = 0;
+ break;
+ }
+
+ if(ret > 1){
+ if(env->followup_to){
+ q_status_message(SM_ORDER, 2, 3,
+ _("Posting to specified Followup-To groups"));
+ outgoing->newsgroups = cpystr(env->followup_to);
+ }
+ else if(!outgoing->newsgroups)
+ outgoing->newsgroups = cpystr(env->newsgroups);
+ }
+
+ return(ret);
+}
+
+
+/*----------------------------------------------------------------------
+ Acquire the pinerc defined signature file
+ It is allocated here and freed by the caller.
+
+ file -- use this file
+ prenewlines -- prefix the file contents with this many newlines
+ postnewlines -- postfix the file contents with this many newlines
+ is_sig -- this is a signature (not a template)
+ ----*/
+char *
+get_signature_file(char *file, int prenewlines, int postnewlines, int is_sig)
+{
+ char *sig, *tmp_sig = NULL, sig_path[MAXPATH+1];
+ int len, do_the_pipe_thang = 0;
+ long sigsize = 0L, cntdown;
+
+ sig_path[0] = '\0';
+ if(!signature_path(file, sig_path, MAXPATH))
+ return(NULL);
+
+ dprint((5, "get_signature(%s)\n", sig_path));
+
+ if(sig_path[(len=strlen(sig_path))-1] == '|'){
+ if(is_sig && F_ON(F_DISABLE_PIPES_IN_SIGS, ps_global)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Pipes for signatures are administratively disabled"));
+ return(NULL);
+ }
+ else if(!is_sig && F_ON(F_DISABLE_PIPES_IN_TEMPLATES, ps_global)){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Pipes for templates are administratively disabled"));
+ return(NULL);
+ }
+
+ sig_path[len-1] = '\0';
+ removing_trailing_white_space(sig_path);
+ do_the_pipe_thang++;
+ }
+
+ if(!IS_REMOTE(sig_path) && ps_global->VAR_OPER_DIR &&
+ !in_dir(ps_global->VAR_OPER_DIR, sig_path)){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: First arg is the directory name, second is
+ the file user wants to read but can't. */
+ _("Can't read file outside %s: %s"),
+ ps_global->VAR_OPER_DIR, file);
+
+ return(NULL);
+ }
+
+ if(IS_REMOTE(sig_path) || can_access(sig_path, ACCESS_EXISTS) == 0){
+ if(do_the_pipe_thang){
+ if(can_access(sig_path, EXECUTE_ACCESS) == 0){
+ STORE_S *store;
+ int flags;
+ PIPE_S *syspipe;
+ gf_io_t pc, gc;
+ long start;
+
+ if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
+
+ flags = PIPE_READ | PIPE_STDERR | PIPE_NOSHELL;
+
+ start = time(0);
+
+ if((syspipe = open_system_pipe(sig_path, NULL, NULL, flags, 5,
+ pipe_callback, pipe_report_error)) != NULL){
+ unsigned char c;
+ char *error, *q;
+
+ gf_set_so_writec(&pc, store);
+ gf_set_readc(&gc, (void *)syspipe, 0, PipeStar, READ_FROM_LOCALE);
+ gf_filter_init();
+
+ if((error = gf_pipe(gc, pc)) != NULL){
+ (void)close_system_pipe(&syspipe, NULL, pipe_callback);
+ gf_clear_so_writec(store);
+ so_give(&store);
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Can't get file: %s"), error);
+ return(NULL);
+ }
+
+ if(close_system_pipe(&syspipe, NULL, pipe_callback)){
+ long now;
+
+ now = time(0);
+ q_status_message2(SM_ORDER, 3, 4,
+ _("Error running program \"%s\"%s"),
+ file,
+ (now - start > 4) ? ": timed out" : "");
+ }
+
+ gf_clear_so_writec(store);
+
+ /* rewind and count chars */
+ so_seek(store, 0L, 0);
+ while(so_readc(&c, store) && sigsize < 100000L)
+ sigsize++;
+
+ /* allocate space */
+ tmp_sig = fs_get((sigsize + 1) * sizeof(char));
+ tmp_sig[0] = '\0';
+ q = tmp_sig;
+
+ /* rewind and copy chars, no prenewlines... */
+ so_seek(store, 0L, 0);
+ cntdown = sigsize;
+ while(so_readc(&c, store) && cntdown-- > 0L)
+ *q++ = c;
+
+ *q = '\0';
+ so_give(&store);
+ }
+ else{
+ so_give(&store);
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Error running program \"%s\""),
+ file);
+ }
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Error allocating space for sig or template program");
+ }
+ else
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: Arg is a program name */
+ _("Can't execute \"%s\": Permission denied"),
+ sig_path);
+ }
+ else if((IS_REMOTE(sig_path) &&
+ (tmp_sig = simple_read_remote_file(sig_path, REMOTE_SIG_SUBTYPE))) ||
+ (tmp_sig = read_file(sig_path, READ_FROM_LOCALE)))
+ sigsize = strlen(tmp_sig);
+ else
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ /* TRANSLATORS: First arg is error description, 2nd is
+ filename */
+ _("Error \"%s\" reading file \"%s\""),
+ error_description(errno), sig_path);
+ }
+
+ sig = get_signature_lit(tmp_sig, prenewlines, postnewlines, is_sig, 0);
+ if(tmp_sig)
+ fs_give((void **)&tmp_sig);
+
+ return(sig);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Partially set up message to forward and pass off to composer/mailer
+
+ Args: pine_state -- The usual pine structure
+
+ Result: outgoing envelope and body created and passed off to composer/mailer
+
+ Create the outgoing envelope for the mail being forwarded, which is
+not much more than filling in the subject, and create the message body
+of the outgoing message which requires formatting the header from the
+envelope of the original messasge.
+ ----------------------------------------------------------------------*/
+int
+forward(struct pine *ps, ACTION_S *role_arg)
+{
+ char *sig;
+ int ret, forward_raw_body = 0, rv = 0, i;
+ long msgno, j, totalmsgs, rflags;
+ ENVELOPE *env, *outgoing;
+ BODY *orig_body, *body = NULL;
+ REPLY_S reply;
+ void *msgtext = NULL;
+ gf_io_t pc;
+ int impl, template_len = 0;
+ PAT_STATE dummy;
+ REDRAFT_POS_S *redraft_pos = NULL;
+ ACTION_S *role = NULL, *nrole;
+#if defined(DOS) && !defined(_WINDOWS)
+ char *reserve;
+#endif
+
+ dprint((4, "\n - forward -\n"));
+
+ memset((void *)&reply, 0, sizeof(reply));
+ outgoing = mail_newenvelope();
+ outgoing->message_id = generate_message_id();
+
+ if(ps_global->full_header == 2
+ && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
+ forward_raw_body = 1;
+
+ if((totalmsgs = mn_total_cur(ps->msgmap)) > 1L){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s forwarded messages...", comatose(totalmsgs));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ outgoing->subject = cpystr(tmp_20k_buf);
+ }
+ else{
+ /*---------- Get the envelope of message we're forwarding ------*/
+ msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
+ if(!((env = pine_mail_fetchstructure(ps->mail_stream, msgno, NULL))
+ && (outgoing->subject = forward_subject(env, 0)))){
+ q_status_message1(SM_ORDER,3,4,
+ _("Error fetching message %s. Can't forward it."),
+ long2string(msgno));
+ goto clean;
+ }
+ }
+
+ /*
+ * as with all text bound for the composer, build it in
+ * a storage object of the type it understands...
+ */
+ if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error allocating message text"));
+ goto clean;
+ }
+
+ ret = (F_ON(F_FORWARD_AS_ATTACHMENT, ps_global))
+ ? 'y'
+ : (totalmsgs > 1L)
+ ? want_to(_("Forward messages as a MIME digest"), 'y', 'x', NO_HELP, WT_SEQ_SENSITIVE)
+ : (ps->full_header == 2)
+ ? want_to(_("Forward message as an attachment"), 'n', 'x', NO_HELP, WT_SEQ_SENSITIVE)
+ : 0;
+
+ if(ret == 'x'){
+ cmd_cancelled("Forward");
+ so_give((STORE_S **)&msgtext);
+ goto clean;
+ }
+
+ /* Setup possible role */
+ if(role_arg)
+ role = copy_action(role_arg);
+
+ if(!role){
+ rflags = ROLE_FORWARD;
+ if(nonempty_patterns(rflags, &dummy)){
+ /* setup default role */
+ nrole = NULL;
+ j = mn_first_cur(ps->msgmap);
+ do {
+ role = nrole;
+ nrole = set_role_from_msg(ps, rflags,
+ mn_m2raw(ps->msgmap, j), NULL);
+ } while(nrole && (!role || nrole == role)
+ && (j=mn_next_cur(ps->msgmap)) > 0L);
+
+ if(!role || nrole == role)
+ role = nrole;
+ else
+ role = NULL;
+
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel reply */
+ role = NULL;
+ cmd_cancelled("Forward");
+ so_give((STORE_S **)&msgtext);
+ goto clean;
+ }
+ }
+ }
+
+ if(role)
+ q_status_message1(SM_ORDER, 3, 4,
+ _("Forwarding using role \"%s\""), role->nick);
+
+ if(role && role->template){
+ char *filtered;
+
+ impl = 1;
+ filtered = detoken(role, (totalmsgs == 1L) ? env : NULL,
+ 0, 0, 0, &redraft_pos, &impl);
+ if(filtered){
+ if(*filtered){
+ so_puts((STORE_S *)msgtext, filtered);
+ if(impl == 1)
+ template_len = strlen(filtered);
+ }
+
+ fs_give((void **)&filtered);
+ }
+ }
+ else
+ impl = 1;
+
+ if((sig = detoken(role, NULL, 2, 0, 1, &redraft_pos, &impl)) != NULL){
+ if(impl == 2)
+ redraft_pos->offset += template_len;
+
+ so_puts((STORE_S *)msgtext, *sig ? sig : NEWLINE);
+
+ fs_give((void **)&sig);
+ }
+ else
+ so_puts((STORE_S *)msgtext, NEWLINE);
+
+ gf_set_so_writec(&pc, (STORE_S *)msgtext);
+
+#if defined(DOS) && !defined(_WINDOWS)
+#if defined(LWP) || defined(PCTCP) || defined(PCNFS)
+#define IN_RESERVE 8192
+#else
+#define IN_RESERVE 16384
+#endif
+ if((reserve=(char *)malloc(IN_RESERVE)) == NULL){
+ gf_clear_so_writec((STORE_S *) msgtext);
+ so_give((STORE_S **)&msgtext);
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Insufficient memory for message text"));
+ goto clean;
+ }
+#endif
+
+ /*
+ * If we're forwarding multiple messages *or* the forward-as-mime
+ * is turned on and the users wants it done that way, package things
+ * up...
+ */
+ if(ret == 'y'){ /* attach message[s]!!! */
+ PART **pp;
+ long totalsize = 0L;
+
+ /*---- New Body to start with ----*/
+ body = mail_newbody();
+ body->type = TYPEMULTIPART;
+
+ /*---- The TEXT part/body ----*/
+ body->nested.part = mail_newbody_part();
+ body->nested.part->body.type = TYPETEXT;
+ body->nested.part->body.contents.text.data = msgtext;
+
+ if(totalmsgs > 1L){
+ /*---- The MULTIPART/DIGEST part ----*/
+ body->nested.part->next = mail_newbody_part();
+ body->nested.part->next->body.type = TYPEMULTIPART;
+ body->nested.part->next->body.subtype = cpystr("Digest");
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Digest of %s messages", comatose(totalmsgs));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ body->nested.part->next->body.description = cpystr(tmp_20k_buf);
+ pp = &(body->nested.part->next->body.nested.part);
+ }
+ else
+ pp = &(body->nested.part->next);
+
+ /*---- The Message body subparts ----*/
+ for(msgno = mn_first_cur(ps->msgmap);
+ msgno > 0L;
+ msgno = mn_next_cur(ps->msgmap)){
+
+ msgno = mn_m2raw(ps->msgmap, msgno);
+ env = pine_mail_fetchstructure(ps->mail_stream, msgno, NULL);
+
+ if(forward_mime_msg(ps->mail_stream,msgno,NULL,env,pp,msgtext)){
+ totalsize += (*pp)->body.size.bytes;
+ pp = &((*pp)->next);
+ }
+ else
+ goto bomb;
+ }
+
+ if(totalmsgs > 1L)
+ body->nested.part->next->body.size.bytes = totalsize;
+ }
+ else if(totalmsgs > 1L){
+ int warned = 0;
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ body->contents.text.data = msgtext;
+ env = NULL;
+
+ for(msgno = mn_first_cur(ps->msgmap);
+ msgno > 0L;
+ msgno = mn_next_cur(ps->msgmap)){
+
+ if(env){ /* put 2 between messages */
+ gf_puts(NEWLINE, pc);
+ gf_puts(NEWLINE, pc);
+ }
+
+ /*--- Grab current envelope ---*/
+ env = pine_mail_fetchstructure(ps->mail_stream,
+ mn_m2raw(ps->msgmap, msgno),
+ &orig_body);
+ if(!env || !orig_body){
+ q_status_message1(SM_ORDER,3,4,
+ _("Error fetching message %s. Can't forward it."),
+ long2string(msgno));
+ goto bomb;
+ }
+
+ if(orig_body == NULL || orig_body->type == TYPETEXT || forward_raw_body) {
+ forward_delimiter(pc);
+ reply_forward_header(ps->mail_stream,
+ mn_m2raw(ps->msgmap, msgno),
+ NULL, env, pc, "");
+
+ if(!get_body_part_text(ps->mail_stream, forward_raw_body ? NULL : orig_body,
+ mn_m2raw(ps->msgmap, msgno),
+ forward_raw_body ? NULL : "1", 0L, pc,
+ NULL, NULL, GBPT_NONE))
+ goto bomb;
+ } else if(orig_body->type == TYPEMULTIPART) {
+ if(!warned++)
+ q_status_message(SM_ORDER,3,7,
+ _("WARNING! Attachments not included in multiple forward."));
+
+ if(orig_body->nested.part &&
+ orig_body->nested.part->body.type == TYPETEXT) {
+ /*---- First part of the message is text -----*/
+ forward_delimiter(pc);
+ reply_forward_header(ps->mail_stream,
+ mn_m2raw(ps->msgmap,msgno),
+ NULL, env, pc, "");
+
+ if(!get_body_part_text(ps->mail_stream,
+ &orig_body->nested.part->body,
+ mn_m2raw(ps->msgmap, msgno),
+ "1", 0L, pc,
+ NULL, NULL, GBPT_NONE))
+ goto bomb;
+ } else {
+ q_status_message(SM_ORDER,0,3,
+ _("Multipart with no leading text part!"));
+ }
+ } else {
+ /*---- Single non-text message of some sort ----*/
+ q_status_message(SM_ORDER,0,3,
+ _("Non-text message not included!"));
+ }
+ }
+ }
+ else if(!((env = pine_mail_fetchstructure(ps->mail_stream, msgno,
+ &orig_body))
+ && (body = forward_body(ps->mail_stream, env, orig_body, msgno,
+ NULL, msgtext,
+ FWD_NONE)))){
+ q_status_message1(SM_ORDER,3,4,
+ _("Error fetching message %s. Can't forward it."),
+ long2string(msgno));
+ goto clean;
+ }
+
+ if(ret != 'y' && totalmsgs == 1L && orig_body){
+ char *charset;
+
+ charset = parameter_val(orig_body->parameter, "charset");
+ if(charset && strucmp(charset, "us-ascii") != 0){
+ CONV_TABLE *ct;
+
+ /*
+ * There is a non-ascii charset, is there conversion happening?
+ */
+ if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
+ reply.orig_charset = charset;
+ charset = NULL;
+ }
+ }
+
+ if(charset)
+ fs_give((void **) &charset);
+
+ /*
+ * I don't think orig_charset is ever used except possibly
+ * right here. Hubert 2008-01-15.
+ */
+ if(reply.orig_charset)
+ reply.forw = 1;
+ }
+
+ /* fill in reply structure */
+ reply.forwarded = 1;
+ reply.mailbox = cpystr(ps->mail_stream->mailbox);
+ reply.origmbox = cpystr(ps->mail_stream->original_mailbox
+ ? ps->mail_stream->original_mailbox
+ : ps->mail_stream->mailbox);
+ reply.data.uid.msgs = (imapuid_t *) fs_get((totalmsgs + 1) * sizeof(imapuid_t));
+ if((reply.data.uid.validity = ps->mail_stream->uid_validity) != 0){
+ reply.uid = 1;
+ for(msgno = mn_first_cur(ps->msgmap), i = 0;
+ msgno > 0L;
+ msgno = mn_next_cur(ps->msgmap), i++)
+ reply.data.uid.msgs[i] = mail_uid(ps->mail_stream, mn_m2raw(ps->msgmap, msgno));
+ }
+ else{
+ reply.msgno = 1;
+ for(msgno = mn_first_cur(ps->msgmap), i = 0;
+ msgno > 0L;
+ msgno = mn_next_cur(ps->msgmap), i++)
+ reply.data.uid.msgs[i] = mn_m2raw(ps->msgmap, msgno);
+ }
+
+ reply.data.uid.msgs[i] = 0; /* tie off list */
+
+#if defined(DOS) && !defined(_WINDOWS)
+ free((void *)reserve);
+#endif
+ pine_send(outgoing, &body, "FORWARD MESSAGE",
+ role, NULL, &reply, redraft_pos,
+ NULL, NULL, 0);
+ rv++;
+
+ clean:
+ if(body)
+ pine_free_body(&body);
+
+ if((STORE_S *) msgtext)
+ gf_clear_so_writec((STORE_S *) msgtext);
+
+ mail_free_envelope(&outgoing);
+ free_redraft_pos(&redraft_pos);
+ free_action(&role);
+
+ if(reply.orig_charset)
+ fs_give((void **)&reply.orig_charset);
+
+ return rv;
+
+ bomb:
+ q_status_message(SM_ORDER | SM_DING, 4, 5,
+ _("Error fetching message contents. Can't forward message."));
+ goto clean;
+}
+
+
+/*----------------------------------------------------------------------
+ Partially set up message to forward and pass off to composer/mailer
+
+ Args: pine_state -- The usual pine structure
+ message -- The MESSAGECACHE of entry to reply to
+
+ Result: outgoing envelope and body created and passed off to composer/mailer
+
+ Create the outgoing envelope for the mail being forwarded, which is
+not much more than filling in the subject, and create the message body
+of the outgoing message which requires formatting the header from the
+envelope of the original messasge.
+ ----------------------------------------------------------------------*/
+void
+forward_text(struct pine *pine_state, void *text, SourceType source)
+{
+ ENVELOPE *env;
+ BODY *body;
+ gf_io_t pc, gc;
+ STORE_S *msgtext;
+ char *enc_error, *sig;
+ ACTION_S *role = NULL;
+ PAT_STATE dummy;
+ long rflags = ROLE_COMPOSE;
+
+ if((msgtext = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ env = mail_newenvelope();
+ env->message_id = generate_message_id();
+ body = mail_newbody();
+ body->type = TYPETEXT;
+ body->contents.text.data = (void *) msgtext;
+
+ if(nonempty_patterns(rflags, &dummy)){
+ /*
+ * This is really more like Compose, even though it
+ * is called Forward.
+ */
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel */
+ cmd_cancelled("Composition");
+ display_message('x');
+ mail_free_envelope(&env);
+ pine_free_body(&body);
+ return;
+ }
+ }
+
+ if(role)
+ q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""),
+ role->nick);
+
+ sig = detoken(role, NULL, 2, 0, 1, NULL, NULL);
+ so_puts(msgtext, (sig && *sig) ? sig : NEWLINE);
+ so_puts(msgtext, NEWLINE);
+ so_puts(msgtext, "----- Included text -----");
+ so_puts(msgtext, NEWLINE);
+ if(sig)
+ fs_give((void **)&sig);
+
+ gf_filter_init();
+ gf_set_so_writec(&pc, msgtext);
+ gf_set_readc(&gc,text,(source == CharStar) ? strlen((char *)text) : 0L,
+ source, 0);
+
+ if((enc_error = gf_pipe(gc, pc)) == NULL){
+ pine_send(env, &body, "SEND MESSAGE", role, NULL, NULL, NULL,
+ NULL, NULL, 0);
+ pine_state->mangled_screen = 1;
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error reading text \"%s\""),enc_error);
+ display_message('x');
+ }
+
+ gf_clear_so_writec(msgtext);
+ mail_free_envelope(&env);
+ pine_free_body(&body);
+ }
+ else {
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Error allocating message text"));
+ display_message('x');
+ }
+
+ free_action(&role);
+}
+
+
+/*----------------------------------------------------------------------
+ Partially set up message to resend and pass off to mailer
+
+ Args: pine_state -- The usual pine structure
+
+ Result: outgoing envelope and body created and passed off to mailer
+
+ Create the outgoing envelope for the mail being resent, which is
+not much more than filling in the subject, and create the message body
+of the outgoing message which requires formatting the header from the
+envelope of the original messasge.
+ ----------------------------------------------------------------------*/
+int
+bounce(struct pine *pine_state, ACTION_S *role)
+{
+ ENVELOPE *env;
+ long msgno, rawno;
+ char *save_to = NULL, **save_toptr = NULL, *errstr = NULL,
+ *prmpt_who = NULL, *prmpt_cnf = NULL;
+
+ dprint((4, "\n - bounce -\n"));
+
+ if(mn_total_cur(pine_state->msgmap) > 1L){
+ save_toptr = &save_to;
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages to : "),
+ mn_total_cur(pine_state->msgmap));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ prmpt_who = cpystr(tmp_20k_buf);
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Send %ld messages "),
+ mn_total_cur(pine_state->msgmap));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ prmpt_cnf = cpystr(tmp_20k_buf);
+ }
+
+ for(msgno = mn_first_cur(pine_state->msgmap);
+ msgno > 0L;
+ msgno = mn_next_cur(pine_state->msgmap)){
+
+ rawno = mn_m2raw(pine_state->msgmap, msgno);
+ if((env = pine_mail_fetchstructure(pine_state->mail_stream, rawno, NULL)) != NULL)
+ errstr = bounce_msg(pine_state->mail_stream, rawno, NULL, role,
+ save_toptr, env->subject, prmpt_who, prmpt_cnf);
+ else
+ errstr = _("Can't fetch Subject for Bounce");
+
+
+ if(errstr){
+ if(*errstr)
+ q_status_message(SM_ORDER | SM_DING, 4, 7, errstr);
+
+ break;
+ }
+ }
+
+ if(save_to)
+ fs_give((void **)&save_to);
+
+ if(prmpt_who)
+ fs_give((void **) &prmpt_who);
+
+ if(prmpt_cnf)
+ fs_give((void **) &prmpt_cnf);
+
+ return(errstr ? 0 : 1);
+}
+
+
+
+char *
+bounce_msg(MAILSTREAM *stream,
+ long int rawno,
+ char *part,
+ ACTION_S *role,
+ char **to,
+ char *subject,
+ char *pmt_who,
+ char *pmt_cnf)
+{
+ char *errstr = NULL;
+ int was_seen = -1;
+ ENVELOPE *outgoing;
+ BODY *body = NULL;
+ MESSAGECACHE *mc;
+
+ if((errstr = bounce_msg_body(stream, rawno, part, to, subject, &outgoing, &body, &was_seen)) == NULL){
+ if(pine_simple_send(outgoing, &body, role, pmt_who, pmt_cnf, to,
+ !(to && *to) ? SS_PROMPTFORTO : 0) < 0){
+ errstr = ""; /* p_s_s() better have explained! */
+ /* clear seen flag */
+ if(was_seen == 0 && rawno > 0L
+ && stream && rawno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawno)) && mc->seen)
+ mail_flag(stream, long2string(rawno), "\\SEEN", 0);
+ }
+ }
+
+ /* Just for good measure... */
+ mail_free_envelope(&outgoing);
+ pine_free_body(&body);
+
+ return(errstr); /* no problem-o */
+}
+
+
+/*----------------------------------------------------------------------
+ Serve up the current signature within pico for editing
+
+ Args: file to edit
+
+ Result: signature changed or not.
+ ---*/
+char *
+signature_edit(char *sigfile, char *title)
+{
+ int editor_result;
+ char sig_path[MAXPATH+1], errbuf[2000], *errstr = NULL;
+ char *ret = NULL;
+ STORE_S *msgso, *tmpso = NULL;
+ gf_io_t gc, pc;
+ PICO pbf;
+ struct variable *vars = ps_global->vars;
+ REMDATA_S *rd = NULL;
+
+ if(!signature_path(sigfile, sig_path, MAXPATH))
+ return(cpystr(_("No signature file defined.")));
+
+ if(IS_REMOTE(sigfile)){
+ rd = rd_create_remote(RemImap, sig_path, REMOTE_SIG_SUBTYPE,
+ NULL, "Error: ",
+ _("Can't access remote configuration."));
+ if(!rd)
+ return(cpystr(_("Error attempting to edit remote configuration")));
+
+ (void)rd_read_metadata(rd);
+
+ if(rd->access == MaybeRorW){
+ if(rd->read_status == 'R')
+ rd->access = ReadOnly;
+ else
+ rd->access = ReadWrite;
+ }
+
+ if(rd->access != NoExists){
+
+ rd_check_remvalid(rd, 1L);
+
+ /*
+ * If the cached info says it is readonly but
+ * it looks like it's been fixed now, change it to readwrite.
+ */
+ if(rd->read_status == 'R'){
+ rd_check_readonly_access(rd);
+ if(rd->read_status == 'W'){
+ rd->access = ReadWrite;
+ rd->flags |= REM_OUTOFDATE;
+ }
+ else
+ rd->access = ReadOnly;
+ }
+ }
+
+ if(rd->flags & REM_OUTOFDATE){
+ if(rd_update_local(rd) != 0){
+
+ dprint((1,
+ "signature_edit: rd_update_local failed\n"));
+ rd_close_remdata(&rd);
+ return(cpystr(_("Can't access remote sig")));
+ }
+ }
+ else
+ rd_open_remote(rd);
+
+ if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
+ rd_close_remdata(&rd);
+ return(cpystr(_("Can't get write permission for remote sig")));
+ }
+
+ rd->flags |= DO_REMTRIM;
+
+ strncpy(sig_path, rd->lf, sizeof(sig_path)-1);
+ sig_path[sizeof(sig_path)-1] = '\0';
+ }
+
+ standard_picobuf_setup(&pbf);
+ pbf.tty_fix = PineRaw;
+ pbf.composer_help = h_composer_sigedit;
+ pbf.exittest = sigedit_exit_for_pico;
+ pbf.upload = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
+ ? upload_msg_to_pico : NULL;
+ pbf.alt_ed = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
+ ? VAR_EDITOR : NULL;
+ pbf.alt_spell = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
+ pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
+ pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
+ pbf.allow_flowed_text = 0;
+
+ pbf.pine_anchor = set_titlebar(title,
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,
+ ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+
+ /* NOTE: at this point, alot of pico struct fields are null'd out
+ * thanks to the leading memset; in particular "headents" which tells
+ * pico to behave like a normal editor (though modified slightly to
+ * let the caller dictate the file to edit and such)...
+ */
+
+ if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, sig_path)){
+ size_t l;
+
+ l = strlen(VAR_OPER_DIR) + 100;
+ ret = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(ret, l+1, _("Can't edit file outside of %s"), VAR_OPER_DIR);
+ ret[l] = '\0';
+ return(ret);
+ }
+
+ /*
+ * Now alloc and init the text to pass pico
+ */
+ if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
+ ret = cpystr(_("Error allocating space for file"));
+ dprint((1, "Can't alloc space for signature_edit"));
+ return(ret);
+ }
+ else
+ pbf.msgtext = so_text(msgso);
+
+ if(can_access(sig_path, READ_ACCESS) == 0
+ && !(tmpso = so_get(FileStar, sig_path, READ_ACCESS|READ_FROM_LOCALE))){
+ char *problem = error_description(errno);
+
+ snprintf(errbuf, sizeof(errbuf), _("Error editing \"%s\": %s"),
+ sig_path, problem ? problem : "<NULL>");
+ errbuf[sizeof(errbuf)-1] = '\0';
+ ret = cpystr(errbuf);
+
+ dprint((1, "signature_edit: can't open %s: %s", sig_path,
+ problem ? problem : "<NULL>"));
+ return(ret);
+ }
+ else if(tmpso){ /* else, fill pico's edit buffer */
+ gf_set_so_readc(&gc, tmpso); /* read from file, write pico buf */
+ gf_set_so_writec(&pc, msgso);
+ gf_filter_init(); /* no filters needed */
+ if((errstr = gf_pipe(gc, pc)) != NULL){
+ snprintf(errbuf, sizeof(errbuf), _("Error reading file: \"%s\""), errstr);
+ errbuf[sizeof(errbuf)-1] = '\0';
+ ret = cpystr(errbuf);
+ }
+
+ gf_clear_so_readc(tmpso);
+ gf_clear_so_writec(msgso);
+ so_give(&tmpso);
+ }
+
+ if(!errstr){
+#ifdef _WINDOWS
+ mswin_setwindowmenu (MENU_COMPOSER);
+#endif
+
+ /*------ OK, Go edit the signature ------*/
+ editor_result = pico(&pbf);
+
+#ifdef _WINDOWS
+ mswin_setwindowmenu (MENU_DEFAULT);
+#endif
+ if(editor_result & COMP_GOTHUP){
+ hup_signal(); /* do what's normal for a hup */
+ }
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+
+ if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
+ }
+ else{
+ /*------ Must have an edited buffer, write it to .sig -----*/
+ our_unlink(sig_path); /* blast old copy */
+ if((tmpso = so_get(FileStar, sig_path, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
+ so_seek(msgso, 0L, 0);
+ gf_set_so_readc(&gc, msgso); /* read from pico buf */
+ gf_set_so_writec(&pc, tmpso); /* write sig file */
+ gf_filter_init(); /* no filters needed */
+ if((errstr = gf_pipe(gc, pc)) != NULL){
+ snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
+ errstr);
+ errbuf[sizeof(errbuf)-1] = '\0';
+ ret = cpystr(errbuf);
+ }
+
+ gf_clear_so_readc(msgso);
+ gf_clear_so_writec(tmpso);
+ if(so_give(&tmpso)){
+ errstr = error_description(errno);
+ snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
+ errstr);
+ errbuf[sizeof(errbuf)-1] = '\0';
+ ret = cpystr(errbuf);
+ }
+
+ if(IS_REMOTE(sigfile)){
+ int e, we_cancel;
+ char datebuf[200];
+
+ datebuf[0] = '\0';
+
+ we_cancel = busy_cue("Copying to remote sig", NULL, 1);
+ if((e = rd_update_remote(rd, datebuf)) != 0){
+ if(e == -1){
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error opening temporary sig file %s: %s"),
+ rd->lf, error_description(errno));
+ dprint((1,
+ "write_remote_sig: error opening temp file %s\n",
+ rd->lf ? rd->lf : "?"));
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ _("Error copying to %s: %s"),
+ rd->rn, error_description(errno));
+ dprint((1,
+ "write_remote_sig: error copying from %s to %s\n",
+ rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
+ }
+
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+ _("Copy of sig to remote folder failed, changes NOT saved remotely"));
+ }
+ else{
+ rd_update_metadata(rd, datebuf);
+ rd->read_status = 'W';
+ }
+
+ rd_close_remdata(&rd);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+ }
+ }
+ else{
+ snprintf(errbuf, sizeof(errbuf), _("Error writing \"%s\""), sig_path);
+ errbuf[sizeof(errbuf)-1] = '\0';
+ ret = cpystr(errbuf);
+ dprint((1, "signature_edit: can't write %s",
+ sig_path));
+ }
+ }
+ }
+
+ standard_picobuf_teardown(&pbf);
+ so_give(&msgso);
+ return(ret);
+}
+
+
+/*----------------------------------------------------------------------
+ Serve up the current signature within pico for editing
+
+ Args: literal signature to edit
+
+ Result: raw edited signature is returned in result arg
+ ---*/
+char *
+signature_edit_lit(char *litsig, char **result, char *title, HelpType composer_help)
+{
+ int editor_result;
+ char *errstr = NULL;
+ char *ret = NULL;
+ STORE_S *msgso;
+ PICO pbf;
+ struct variable *vars = ps_global->vars;
+
+ standard_picobuf_setup(&pbf);
+ pbf.tty_fix = PineRaw;
+ pbf.search_help = h_sigedit_search;
+ pbf.composer_help = composer_help;
+ pbf.exittest = sigedit_exit_for_pico;
+ pbf.upload = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
+ ? upload_msg_to_pico : NULL;
+ pbf.alt_ed = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
+ ? VAR_EDITOR : NULL;
+ pbf.alt_spell = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
+ pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
+ pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
+ pbf.allow_flowed_text = 0;
+
+ pbf.pine_anchor = set_titlebar(title,
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,
+ ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL);
+
+ /* NOTE: at this point, alot of pico struct fields are null'd out
+ * thanks to the leading memset; in particular "headents" which tells
+ * pico to behave like a normal editor (though modified slightly to
+ * let the caller dictate the file to edit and such)...
+ */
+
+ /*
+ * Now alloc and init the text to pass pico
+ */
+ if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
+ ret = cpystr(_("Error allocating space"));
+ dprint((1, "Can't alloc space for signature_edit_lit"));
+ return(ret);
+ }
+ else
+ pbf.msgtext = so_text(msgso);
+
+ so_puts(msgso, litsig ? litsig : "");
+
+
+ if(!errstr){
+#ifdef _WINDOWS
+ mswin_setwindowmenu (MENU_COMPOSER);
+#endif
+
+ /*------ OK, Go edit the signature ------*/
+ editor_result = pico(&pbf);
+
+#ifdef _WINDOWS
+ mswin_setwindowmenu (MENU_DEFAULT);
+#endif
+ if(editor_result & COMP_GOTHUP){
+ hup_signal(); /* do what's normal for a hup */
+ }
+ else{
+ fix_windsize(ps_global);
+ init_signals();
+ }
+
+ if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
+ ret = cpystr(_("Edit Cancelled"));
+ }
+ else{
+ /*------ Must have an edited buffer, write it to .sig -----*/
+ unsigned char c;
+ int cnt = 0;
+ char *p;
+
+ so_seek(msgso, 0L, 0);
+ while(so_readc(&c, msgso))
+ cnt++;
+
+ *result = (char *)fs_get((cnt+1) * sizeof(char));
+ p = *result;
+ so_seek(msgso, 0L, 0);
+ while(so_readc(&c, msgso))
+ *p++ = c;
+
+ *p = '\0';
+ }
+ }
+
+ standard_picobuf_teardown(&pbf);
+ so_give(&msgso);
+ return(ret);
+}
+
+
+/*
+ * Returns 0 for Save Changes and exit
+ * 1 for Cancel Exit
+ * -1 exit but Dont Save Changes
+ */
+int
+sigedit_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
+ char **result)
+{
+ int rv;
+ char *rstr = NULL;
+ void (*redraw)(void) = ps_global->redrawer;
+ static ESCKEY_S opts[] = {
+ {'s', 's', "S", N_("Save changes")},
+ {'d', 'd', "D", N_("Don't save changes")},
+ {-1, 0, NULL, NULL}
+ };
+
+ ps_global->redrawer = redraw_pico;
+ fix_windsize(ps_global);
+
+ while(1){
+ rv = radio_buttons(_("Exit editor? "),
+ -FOOTER_ROWS(ps_global), opts,
+ 's', 'x', h_exit_editor, RB_NORM);
+ if(rv == 's'){ /* user ACCEPTS! */
+ break;
+ }
+ else if(rv == 'd'){ /* Declined! */
+ rstr = _("No Changes Saved");
+ break;
+ }
+ else if(rv == 'x'){ /* Cancelled! */
+ rstr = _("Exit Cancelled");
+ break;
+ }
+ }
+
+ if(result)
+ *result = rstr;
+
+ ps_global->redrawer = redraw;
+ return((rv == 's') ? 0 : (rv == 'd') ? -1 : 1);
+}
+
+
+/*
+ * Common stuff we almost always want to set when calling pico.
+ */
+void
+standard_picobuf_setup(PICO *pbf)
+{
+ memset(pbf, 0, sizeof(*pbf));
+
+ pbf->pine_version = ALPINE_VERSION;
+ pbf->fillcolumn = ps_global->composer_fillcol;
+ pbf->menu_rows = FOOTER_ROWS(ps_global) - 1;
+ pbf->colors = colors_for_pico();
+ pbf->wordseps = user_wordseps(ps_global->VAR_WORDSEPS);
+ pbf->helper = helper;
+ pbf->showmsg = display_message_for_pico;
+ pbf->suspend = do_suspend;
+ pbf->keybinput = cmd_input_for_pico;
+ pbf->tty_fix = ttyfix; /* watch out for this one */
+ pbf->newmail = new_mail_for_pico;
+ pbf->ckptdir = checkpoint_dir_for_pico;
+ pbf->resize = resize_for_pico;
+ pbf->input_cs = ps_global->input_cs;
+ pbf->winch_cleanup = winch_cleanup;
+ pbf->search_help = h_composer_search;
+ pbf->ins_help = h_composer_ins;
+ pbf->ins_m_help = h_composer_ins_m;
+ pbf->composer_help = h_composer;
+ pbf->browse_help = h_composer_browse;
+ pbf->attach_help = h_composer_ctrl_j;
+
+ pbf->pine_flags =
+ ( (F_ON(F_CAN_SUSPEND,ps_global) ? P_SUSPEND : 0L)
+ | (F_ON(F_USE_FK,ps_global) ? P_FKEYS : 0L)
+ | (ps_global->restricted ? P_SECURE : 0L)
+ | (F_ON(F_ALT_ED_NOW,ps_global) ? P_ALTNOW : 0L)
+ | (F_ON(F_USE_CURRENT_DIR,ps_global) ? P_CURDIR : 0L)
+ | (F_ON(F_SUSPEND_SPAWNS,ps_global) ? P_SUBSHELL : 0L)
+ | (F_ON(F_COMPOSE_MAPS_DEL,ps_global) ? P_DELRUBS : 0L)
+ | (F_ON(F_ENABLE_TAB_COMPLETE,ps_global) ? P_COMPLETE : 0L)
+ | (F_ON(F_SHOW_CURSOR,ps_global) ? P_SHOCUR : 0L)
+ | (F_ON(F_DEL_FROM_DOT,ps_global) ? P_DOTKILL : 0L)
+ | (F_ON(F_ENABLE_DOT_FILES,ps_global) ? P_DOTFILES : 0L)
+ | (F_ON(F_ALLOW_GOTO,ps_global) ? P_ALLOW_GOTO : 0L)
+ | (F_ON(F_ENABLE_SEARCH_AND_REPL,ps_global) ? P_REPLACE : 0L)
+ | (!ps_global->pass_ctrl_chars
+ && !ps_global->pass_c1_ctrl_chars ? P_HICTRL : 0L)
+ | ((F_ON(F_ENABLE_ALT_ED,ps_global)
+ || F_ON(F_ALT_ED_NOW,ps_global)
+ || (ps_global->VAR_EDITOR
+ && ps_global->VAR_EDITOR[0]
+ && ps_global->VAR_EDITOR[0][0]))
+ ? P_ADVANCED : 0L)
+ | ((!ps_global->keyboard_charmap
+ || !strucmp(ps_global->keyboard_charmap, "US-ASCII"))
+ ? P_HIBITIGN : 0L));
+
+ if(ps_global->VAR_OPER_DIR){
+ pbf->oper_dir = ps_global->VAR_OPER_DIR;
+ pbf->pine_flags |= P_TREE;
+ }
+
+ pbf->home_dir = ps_global->home_dir;
+}
+
+
+void
+standard_picobuf_teardown(PICO *pbf)
+{
+ if(pbf){
+ if(pbf->colors)
+ free_pcolors(&pbf->colors);
+
+ if(pbf->wordseps)
+ fs_give((void **) &pbf->wordseps);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Call back for pico to use to check for new mail.
+
+Args: cursor -- pointer to in to tell caller if cursor location changed
+ if NULL, turn off cursor positioning.
+ timing -- whether or not it's a good time to check
+
+
+Returns: returns 1 on success, zero on error.
+----*/
+long
+new_mail_for_pico(int timing, int status)
+{
+ /*
+ * If we're not interested in the status, don't display the busy
+ * cue either...
+ */
+ /* don't know where the cursor's been, reset it */
+ clear_cursor_pos();
+ return(new_mail(0, timing,
+ (status ? NM_STATUS_MSG : NM_NONE) | NM_DEFER_SORT
+ | NM_FROM_COMPOSER));
+}
+
+
+void
+cmd_input_for_pico(void)
+{
+ zero_new_mail_count();
+}
+
+
+/*----------------------------------------------------------------------
+ Call back for pico to get newmail status messages displayed
+
+Args: x -- char processed
+
+Returns:
+----*/
+int
+display_message_for_pico(int x)
+{
+ int rv;
+
+ clear_cursor_pos(); /* can't know where cursor is */
+ mark_status_dirty(); /* don't count on cached text */
+ fix_windsize(ps_global);
+ init_sigwinch();
+ display_message(x);
+ rv = ps_global->mangled_screen;
+ ps_global->mangled_screen = 0;
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Call back for pico to get desired directory for its check point file
+
+ Args: s -- buffer to write directory name
+ n -- length of that buffer
+
+ Returns: pointer to static buffer
+----*/
+char *
+checkpoint_dir_for_pico(char *s, size_t n)
+{
+#if defined(DOS) || defined(OS2)
+ /*
+ * we can't assume anything about root or home dirs, so
+ * just plunk it down in the same place as the pinerc
+ */
+ if(!getenv("HOME")){
+ char *lc = last_cmpnt(ps_global->pinerc);
+
+ if(lc != NULL){
+ strncpy(s, ps_global->pinerc, MIN(n-1,lc-ps_global->pinerc));
+ s[MIN(n-1,lc-ps_global->pinerc)] = '\0';
+ }
+ else{
+ strncpy(s, ".\\", n-1);
+ s[n-1] = '\0';
+ }
+ }
+ else
+#endif
+ strncpy(s, ps_global->home_dir, n-1);
+ s[n-1] = '\0';
+
+ return(s);
+}
+
+
+/*----------------------------------------------------------------------
+ Call back for pico to tell us the window size's changed
+
+ Args: none
+
+ Returns: none (but pine's ttyo structure may have been updated)
+----*/
+void
+resize_for_pico(void)
+{
+ fix_windsize(ps_global);
+}
+
+
+PCOLORS *
+colors_for_pico(void)
+{
+ PCOLORS *colors = NULL;
+ struct variable *vars = ps_global->vars;
+
+ if (pico_usingcolor()){
+ colors = (PCOLORS *)fs_get(sizeof(PCOLORS));
+
+ colors->tbcp = current_titlebar_color();
+
+ if (VAR_KEYLABEL_FORE_COLOR && VAR_KEYLABEL_BACK_COLOR){
+ colors->klcp = new_color_pair(VAR_KEYLABEL_FORE_COLOR,
+ VAR_KEYLABEL_BACK_COLOR);
+ if (!pico_is_good_colorpair(colors->klcp))
+ free_color_pair(&colors->klcp);
+ }
+ else colors->klcp = NULL;
+
+ if (colors->klcp && VAR_KEYNAME_FORE_COLOR && VAR_KEYNAME_BACK_COLOR){
+ colors->kncp = new_color_pair(VAR_KEYNAME_FORE_COLOR,
+ VAR_KEYNAME_BACK_COLOR);
+ }
+ else colors->kncp = NULL;
+
+ if (VAR_STATUS_FORE_COLOR && VAR_STATUS_BACK_COLOR){
+ colors->stcp = new_color_pair(VAR_STATUS_FORE_COLOR,
+ VAR_STATUS_BACK_COLOR);
+ }
+ else colors->stcp = NULL;
+
+ if (VAR_PROMPT_FORE_COLOR && VAR_PROMPT_BACK_COLOR){
+ colors->prcp = new_color_pair(VAR_PROMPT_FORE_COLOR,
+ VAR_PROMPT_BACK_COLOR);
+ }
+ else colors->prcp = NULL;
+ }
+
+ return colors;
+}
+
+
+void
+free_pcolors(PCOLORS **colors)
+{
+ if (*colors){
+ if ((*colors)->tbcp)
+ free_color_pair(&(*colors)->tbcp);
+ if ((*colors)->kncp)
+ free_color_pair(&(*colors)->kncp);
+ if ((*colors)->klcp)
+ free_color_pair(&(*colors)->klcp);
+ if ((*colors)->stcp)
+ free_color_pair(&(*colors)->stcp);
+ if ((*colors)->prcp)
+ free_color_pair(&(*colors)->prcp);
+ fs_give((void **)colors);
+ fs_give((void **)colors);
+ *colors = NULL;
+ }
+}
diff --git a/alpine/reply.h b/alpine/reply.h
new file mode 100644
index 00000000..2c239071
--- /dev/null
+++ b/alpine/reply.h
@@ -0,0 +1,44 @@
+/*
+ * $Id: reply.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_REPLY_INCLUDED
+#define PINE_REPLY_INCLUDED
+
+
+#include "../pith/reply.h"
+#include "../pith/state.h"
+#include "../pith/pattern.h"
+#include "../pith/store.h"
+
+
+/* exported protoypes */
+int reply(struct pine *, ACTION_S *);
+int confirm_role(long, ACTION_S **);
+int reply_to_all_query(int *);
+int reply_using_replyto_query(void);
+int reply_text_query(struct pine *, long, char **);
+int reply_news_test(ENVELOPE *, ENVELOPE *);
+char *get_signature_file(char *, int, int, int);
+int forward(struct pine *, ACTION_S *);
+void forward_text(struct pine *, void *, SourceType);
+int bounce(struct pine *, ACTION_S *);
+char *bounce_msg(MAILSTREAM *, long, char *, ACTION_S *, char **, char *, char *, char *);
+char *signature_edit(char *, char *);
+char *signature_edit_lit(char *, char **, char *, HelpType);
+void standard_picobuf_setup(PICO *);
+void standard_picobuf_teardown(PICO *);
+
+
+#endif /* PINE_REPLY_INCLUDED */
diff --git a/alpine/roleconf.c b/alpine/roleconf.c
new file mode 100644
index 00000000..fad1e661
--- /dev/null
+++ b/alpine/roleconf.c
@@ -0,0 +1,8187 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: roleconf.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "roleconf.h"
+#include "colorconf.h"
+#include "conftype.h"
+#include "confscroll.h"
+#include "keymenu.h"
+#include "status.h"
+#include "radio.h"
+#include "reply.h"
+#include "folder.h"
+#include "addrbook.h"
+#include "mailcmd.h"
+#include "setup.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/msgno.h"
+#include "../pith/bitmap.h"
+#include "../pith/sort.h"
+#include "../pith/addrstring.h"
+#include "../pith/list.h"
+#include "../pith/flag.h"
+#include "../pith/bldaddr.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/detoken.h"
+#include "../pith/icache.h"
+#include "../pith/ablookup.h"
+#include "../pith/pattern.h"
+#include "../pith/tempfile.h"
+
+
+#define NOT "! "
+#define NOTLEN 2
+
+
+#define ARB_HELP _("HELP FOR ARBITRARY HEADER PATTERNS")
+#define ADDXHDRS _("Add Extra Headers")
+
+
+/*
+ * Internal prototypes
+ */
+int role_select_tool(struct pine *, int, CONF_S **, unsigned);
+PATTERN_S *addrlst_to_pattern(ADDRESS *);
+void role_config_init_disp(struct pine *, CONF_S **, long, PAT_STATE *);
+void add_patline_to_display(struct pine *, CONF_S **, int, CONF_S **, CONF_S **, PAT_LINE_S *, long);
+void add_role_to_display(CONF_S **, PAT_LINE_S *, PAT_S *, int, CONF_S **, int, long);
+void add_fake_first_role(CONF_S **, int, long);
+int role_config_tool(struct pine *, int, CONF_S **, unsigned);
+int role_config_add(struct pine *, CONF_S **, long);
+int role_config_replicate(struct pine *, CONF_S **, long);
+int role_config_edit(struct pine *, CONF_S **, long);
+int role_config_del(struct pine *, CONF_S **, long);
+void delete_a_role(CONF_S **, long);
+int role_config_shuffle(struct pine *, CONF_S **);
+int role_config_addfile(struct pine *, CONF_S **, long);
+int role_config_delfile(struct pine *, CONF_S **, long);
+void swap_literal_roles(CONF_S *, CONF_S *);
+void swap_file_roles(CONF_S *, CONF_S *);
+void move_role_into_file(CONF_S **, int);
+void move_role_outof_file(CONF_S **, int);
+void move_role_around_file(CONF_S **, int);
+int role_config_edit_screen(struct pine *, PAT_S *, char *, long, PAT_S **);
+void setup_dummy_pattern_var(struct variable *, char *, PATTERN_S *);
+void setup_role_pat(struct pine *, CONF_S **, struct variable *, HelpType, char *,
+ struct key_menu *,
+ int (*tool)(struct pine *, int, CONF_S **, unsigned),
+ EARB_S **, int);
+void setup_role_pat_alt(struct pine *, CONF_S **, struct variable *, HelpType, char *,
+ struct key_menu *,
+ int (*tool)(struct pine *, int, CONF_S **, unsigned),
+ int, int);
+void free_earb(EARB_S **);
+void calculate_inick_stuff(struct pine *);
+int check_role_folders(char **, unsigned);
+void maybe_add_to_incoming(CONTEXT_S *, char *);
+int role_filt_exitcheck(CONF_S **, unsigned);
+int role_filt_text_tool(struct pine *, int, CONF_S **, unsigned);
+int role_filt_addhdr_tool(struct pine *, int, CONF_S **, unsigned);
+int role_addhdr_tool(struct pine *, int, CONF_S **, unsigned);
+int role_filt_radiobutton_tool(struct pine *, int, CONF_S **, unsigned);
+int role_sort_tool(struct pine *, int, CONF_S **, unsigned);
+char **get_role_specific_folder(CONF_S **);
+int role_litsig_text_tool(struct pine *, int, CONF_S **, unsigned);
+int role_cstm_text_tool(struct pine *, int, CONF_S **, unsigned);
+int role_text_tool(struct pine *, int, CONF_S **, unsigned);
+int role_text_tool_inick(struct pine *, int, CONF_S **, unsigned);
+int role_text_tool_kword(struct pine *, int, CONF_S **, unsigned);
+int role_text_tool_charset(struct pine *, int, CONF_S **, unsigned);
+int role_text_tool_afrom(struct pine *, int, CONF_S **, unsigned);
+char *role_type_print(char *, size_t, char *, long);
+int feat_checkbox_tool(struct pine *, int, CONF_S **, unsigned);
+void toggle_feat_option_bit(struct pine *, int, struct variable *, char *);
+NAMEVAL_S *feat_feature_list(int);
+int inabook_checkbox_tool(struct pine *, int, CONF_S **, unsigned);
+void toggle_inabook_type_bit(struct pine *, int, struct variable *, char *);
+NAMEVAL_S *inabook_feature_list(int);
+
+
+static char *set_choose = "--- --------------------";
+static long role_global_flags;
+static PAT_STATE *role_global_pstate;
+
+
+int
+role_select_screen(struct pine *ps, ACTION_S **role, int alt_compose)
+{
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ OPT_SCREEN_S screen;
+ PAT_S *pat, *sel_pat = NULL;
+ int ret = -1;
+ int change_default = 0;
+ long rflags = ROLE_DO_ROLES;
+ char *helptitle;
+ HelpType help;
+ PAT_STATE pstate;
+
+ if(!role)
+ return(ret);
+
+ *role = NULL;
+
+ if(!(nonempty_patterns(rflags, &pstate) &&
+ first_pattern(&pstate))){
+ q_status_message(SM_ORDER, 3, 3,
+ _("No roles available. Use Setup/Rules to add roles."));
+ return(ret);
+ }
+
+
+ if(alt_compose){
+ menu_init_binding(&role_select_km,
+ alt_compose == MC_FORWARD ? 'F' :
+ alt_compose == MC_REPLY ? 'R' :
+ alt_compose == MC_COMPOSE ? 'C' : 'B',
+ MC_CHOICE,
+ alt_compose == MC_FORWARD ? "F" :
+ alt_compose == MC_REPLY ? "R" :
+ alt_compose == MC_COMPOSE ? "C" : "B",
+ alt_compose == MC_FORWARD ? "[" N_("ForwardAs") "]" :
+ alt_compose == MC_REPLY ? "[" N_("ReplyAs") "]" :
+ alt_compose == MC_COMPOSE ? "[" N_("ComposeAs") "]" : "[" N_("BounceAs") "]",
+ DEFAULT_KEY);
+ menu_add_binding(&role_select_km, ctrl('J'), MC_CHOICE);
+ menu_add_binding(&role_select_km, ctrl('M'), MC_CHOICE);
+ }
+ else{
+ menu_init_binding(&role_select_km, 'S', MC_CHOICE, "S", "[" N_("Select") "]",
+ DEFAULT_KEY);
+ menu_add_binding(&role_select_km, ctrl('J'), MC_CHOICE);
+ menu_add_binding(&role_select_km, ctrl('M'), MC_CHOICE);
+ }
+
+ help = h_role_select;
+ if(alt_compose == MC_BOUNCE)
+ helptitle = _("HELP FOR SELECTING A ROLE TO BOUNCE AS");
+ else if(alt_compose)
+ helptitle = _("HELP FOR SELECTING A ROLE TO COMPOSE AS");
+ else
+ helptitle = _("HELP FOR SELECTING A ROLE");
+
+ menu_init_binding(&role_select_km, 'D', MC_TOGGLE, "D", "changeDef", CHANGEDEF_KEY);
+
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ new_confline(&ctmp);
+ if(!first_line)
+ first_line = ctmp;
+
+ ctmp->value = cpystr((pat->patgrp && pat->patgrp->nick)
+ ? pat->patgrp->nick : "?");
+ ctmp->d.r.selected = &sel_pat;
+ ctmp->d.r.pat = pat;
+ ctmp->d.r.change_def = &change_default;
+ ctmp->keymenu = &role_select_km;
+ ctmp->help = help;
+ ctmp->help_title = helptitle;
+ ctmp->tool = role_select_tool;
+ ctmp->flags = CF_STARTITEM;
+ ctmp->valoffset = 4;
+ }
+
+ memset(&screen, 0, sizeof(screen));
+ /* TRANSLATORS: Print something1 using something2.
+ "roles" is something1 */
+ (void)conf_scroll_screen(ps, &screen, first_line, _("SELECT ROLE"),
+ _("roles"), 0);
+
+ if(sel_pat){
+ *role = sel_pat->action;
+ if(change_default == 1)
+ ps_global->default_role = *role;
+ else if(change_default == 2)
+ ps_global->default_role = NULL;
+
+ ret = 0;
+ }
+
+ ps->mangled_screen = 1;
+ return(ret);
+}
+
+
+int
+role_select_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int retval = 0, newval;
+
+ switch(cmd){
+ case MC_CHOICE :
+ *((*cl)->d.r.selected) = (*cl)->d.r.pat;
+ retval = simple_exit_cmd(flags);
+ break;
+
+ case MC_TOGGLE :
+ newval = (*((*cl)->d.r.change_def) + 1) % 3;
+ *((*cl)->d.r.change_def) = newval;
+ menu_init_binding((*cl)->keymenu, 'D', MC_TOGGLE, "D",
+ (newval == 0) ? "changeDef" : (newval == 1) ? "removeDef" : "leaveDef",
+ CHANGEDEF_KEY);
+ if(newval == 1){
+ if(ps_global->default_role)
+ q_status_message(SM_ORDER, 0, 3,
+ _("Default role will be changed to the role you Select"));
+ else
+ q_status_message(SM_ORDER, 0, 3,
+ _("Default role will be set to the role you Select"));
+ }
+ else if(newval == 2){
+ q_status_message(SM_ORDER, 0, 3, _("Default role will be unset"));
+ }
+ else{ /* newval == 0 */
+ if(ps_global->default_role)
+ q_status_message(SM_ORDER, 0, 3, _("Default role will remain unchanged"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Default role will remain unset"));
+ }
+
+ ps->mangled_footer = 1;
+ retval = 0;
+ break;
+
+ case MC_EXIT :
+ retval = simple_exit_cmd(flags);
+ break;
+
+ default:
+ retval = -1;
+ break;
+ }
+
+ if(retval > 0)
+ ps->mangled_body = 1;
+
+ return(retval);
+}
+
+
+void
+role_config_screen(struct pine *ps, long int rflags, int edit_exceptions)
+{
+ CONF_S *first_line;
+ OPT_SCREEN_S screen;
+ char title[100];
+ int readonly_warning = 0;
+ PAT_STATE pstate;
+ struct variable *v = NULL;
+
+ dprint((4, "role_config_screen()\n"));
+
+ if(ps->fix_fixed_warning)
+ offer_to_fix_pinerc(ps);
+
+ ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;
+
+ if(ps->restricted)
+ readonly_warning = 1;
+ else{
+ PINERC_S *prc = NULL;
+
+ switch(ew){
+ case Main:
+ prc = ps->prc;
+ rflags |= PAT_USE_MAIN;
+ break;
+ case Post:
+ prc = ps->post_prc;
+ rflags |= PAT_USE_POST;
+ break;
+ default:
+ break;
+ }
+
+ readonly_warning = prc ? prc->readonly : 1;
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return;
+ }
+ }
+
+ if(!any_patterns(rflags, &pstate))
+ return;
+
+ if(rflags & ROLE_DO_ROLES)
+ v = &ps_global->vars[V_PAT_ROLES];
+ else if(rflags & ROLE_DO_INCOLS)
+ v = &ps_global->vars[V_PAT_INCOLS];
+ else if(rflags & ROLE_DO_OTHER)
+ v = &ps_global->vars[V_PAT_OTHER];
+ else if(rflags & ROLE_DO_SCORES)
+ v = &ps_global->vars[V_PAT_SCORES];
+ else if(rflags & ROLE_DO_FILTER)
+ v = &ps_global->vars[V_PAT_FILTS];
+ else if(rflags & ROLE_DO_SRCH)
+ v = &ps_global->vars[V_PAT_SRCH];
+
+ if((ps_global->ew_for_except_vars != Main) && (ew == Main)){
+ char **lval;
+
+ if((lval=LVAL(v, ps_global->ew_for_except_vars)) &&
+ lval[0] && strcmp(INHERIT, lval[0]) != 0){
+ role_type_print(title, sizeof(title), _("Warning: \"%sRules\" are overridden in your exceptions configuration"), rflags);
+ q_status_message(SM_ORDER, 7, 7, title);
+ }
+ }
+
+ role_type_print(title, sizeof(title), "%sRules", rflags);
+ if(fixed_var(v, "change", title))
+ return;
+
+uh_oh:
+ first_line = NULL;
+
+ snprintf(title, sizeof(title), "SETUP%s ", edit_exceptions ? " EXCEPTIONAL" : "");
+ title[sizeof(title)-1] = '\0';
+ role_type_print(title+strlen(title), sizeof(title)-strlen(title), "%sRULES", rflags);
+ role_global_flags = rflags;
+ role_global_pstate = &pstate;
+ role_config_init_disp(ps, &first_line, rflags, &pstate);
+
+ if(!first_line){
+ role_global_flags = 0;
+ ps->mangled_screen = 1;
+ q_status_message(SM_ORDER,5,5,
+ _("Unexpected problem: config file modified externally?"));
+ q_status_message1(SM_ORDER,5,5,
+ _("Perhaps a newer version of pine was used to set variable \"%s\"?"),
+ v ? v->name : "?");
+ dprint((1, "Unexpected problem: config file modified externally?\nPerhaps by a newer pine? Variable \"%s\" has unexpected contents.\n",
+ (v && v->name) ? v->name : "?"));
+ return;
+ }
+
+ memset(&screen, 0, sizeof(screen));
+ screen.deferred_ro_warning = readonly_warning;
+ /* TRANSLATORS: Print something1 using something2.
+ "rules" is something1 */
+ switch(conf_scroll_screen(ps, &screen, first_line, title, _("rules"), 0)){
+ case 0:
+ break;
+
+ case 10:
+ /* flush changes and re-read orig */
+ close_patterns(rflags);
+ break;
+
+ case 1:
+ if(write_patterns(rflags))
+ goto uh_oh;
+
+ /*
+ * Flush out current_vals of anything we've possibly changed.
+ */
+
+ if(ps_global->default_role){
+ q_status_message(SM_ORDER,0,3, "Default role is unset");
+ ps_global->default_role = NULL;
+ }
+
+ close_patterns((rflags & ROLE_MASK) | PAT_USE_CURRENT);
+
+ /* scores may have changed */
+ if(rflags & ROLE_DO_SCORES){
+ int i;
+ MAILSTREAM *m;
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m){
+ clear_folder_scores(m);
+ clear_index_cache(m, 0);
+ }
+ }
+
+ if(mn_get_sort(sp_msgmap(ps_global->mail_stream)) == SortScore)
+ refresh_sort(ps_global->mail_stream,
+ sp_msgmap(ps_global->mail_stream), SRT_VRB);
+ }
+
+ /* recalculate need for scores */
+ scores_are_used(SCOREUSE_INVALID);
+
+ /* we may want to fetch more or fewer headers each fetch */
+ calc_extra_hdrs();
+ if(get_extra_hdrs())
+ (void) mail_parameters(NULL, SET_IMAPEXTRAHEADERS,
+ (void *) get_extra_hdrs());
+
+ if(rflags & ROLE_DO_INCOLS && pico_usingcolor())
+ clear_index_cache(ps_global->mail_stream, 0);
+
+ if(rflags & ROLE_DO_FILTER)
+ role_process_filters();
+
+ /*
+ * ROLE_DO_OTHER is made up of a bunch of different variables
+ * that may have changed. Assume they all changed and fix them.
+ */
+ if(rflags & ROLE_DO_OTHER){
+ reset_index_format();
+ clear_index_cache(ps_global->mail_stream, 0);
+ if(!mn_get_mansort(ps_global->msgmap))
+ reset_sort_order(SRT_VRB);
+ }
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER,7,10, "conf_scroll_screen unexpected ret");
+ break;
+ }
+
+ role_global_flags = 0;
+ ps->mangled_screen = 1;
+}
+
+
+/*
+ * This is called from process_cmd to add a new pattern to the end of the
+ * list of patterns. The pattern is seeded with values from the current
+ * message.
+ */
+void
+role_take(struct pine *ps, MSGNO_S *msgmap, int rtype)
+{
+ PAT_S *defpat, *newpat = NULL;
+ PAT_LINE_S *new_patline, *patline;
+ ENVELOPE *env = NULL;
+ long rflags;
+ char *s, title[100], specific_fldr[MAXPATH+1];
+ PAT_STATE pstate;
+ EditWhich ew;
+
+ dprint((4, "role_take()\n"));
+
+ if(mn_get_cur(msgmap) > 0){
+ env = pine_mail_fetchstructure(ps->mail_stream,
+ mn_m2raw(msgmap, mn_get_cur(msgmap)),
+ NULL);
+
+ if(!env){
+ q_status_message(SM_ORDER, 3, 7,
+ _("problem getting addresses from message"));
+ return;
+ }
+ }
+
+ switch(rtype){
+ case 'r':
+ rflags = ROLE_DO_ROLES;
+ ew = ps_global->ew_for_role_take;
+ break;
+ case 's':
+ rflags = ROLE_DO_SCORES;
+ ew = ps_global->ew_for_score_take;
+ break;
+ case 'i':
+ rflags = ROLE_DO_INCOLS;
+ ew = ps_global->ew_for_incol_take;
+ break;
+ case 'f':
+ rflags = ROLE_DO_FILTER;
+ ew = ps_global->ew_for_filter_take;
+ break;
+ case 'o':
+ rflags = ROLE_DO_OTHER;
+ ew = ps_global->ew_for_other_take;
+ break;
+ case 'c':
+ rflags = ROLE_DO_SRCH;
+ ew = ps_global->ew_for_srch_take;
+ break;
+
+ default:
+ cmd_cancelled(NULL);
+ return;
+ }
+
+ switch(ew){
+ case Main:
+ rflags |= PAT_USE_MAIN;
+ break;
+ case Post:
+ rflags |= PAT_USE_POST;
+ break;
+ default:
+ break;
+ }
+
+ if(!any_patterns(rflags, &pstate)){
+ q_status_message(SM_ORDER, 3, 7, _("problem accessing rules"));
+ return;
+ }
+
+ /* set this so that even if we don't edit at all, we'll be asked */
+ rflags |= ROLE_CHANGES;
+
+ /*
+ * Make a pattern out of the information in the envelope and
+ * use that as the default pattern we give to the role editor.
+ * It will have a pattern but no actions set.
+ */
+ defpat = (PAT_S *)fs_get(sizeof(*defpat));
+ memset((void *)defpat, 0, sizeof(*defpat));
+
+ defpat->patgrp = (PATGRP_S *)fs_get(sizeof(*defpat->patgrp));
+ memset((void *)defpat->patgrp, 0, sizeof(*defpat->patgrp));
+
+ if(env){
+ if(env->to)
+ defpat->patgrp->to = addrlst_to_pattern(env->to);
+
+ if(env->from)
+ defpat->patgrp->from = addrlst_to_pattern(env->from);
+
+ if(env->cc)
+ defpat->patgrp->cc = addrlst_to_pattern(env->cc);
+
+ if(env->sender &&
+ (!env->from || !address_is_same(env->sender, env->from)))
+ defpat->patgrp->sender = addrlst_to_pattern(env->sender);
+
+ /*
+ * Env->newsgroups is already comma-separated and there shouldn't be
+ * any commas or backslashes in newsgroup names, so we don't add the
+ * roletake escapes.
+ */
+ if(env->newsgroups)
+ defpat->patgrp->news = string_to_pattern(env->newsgroups);
+
+ /*
+ * Subject may have commas or backslashes, so we add escapes.
+ */
+ if(env->subject){
+ char *q, *t = NULL;
+
+ /*
+ * Mail_strip_subject not only strips the Re's and Fwd's but
+ * it also canonicalizes to UTF-8.
+ */
+ mail_strip_subject(env->subject, &q);
+ if(q != NULL){
+ t = add_roletake_escapes(q);
+ fs_give((void **)&q);
+ }
+
+ if(t){
+ defpat->patgrp->subj = string_to_pattern(t);
+ fs_give((void **)&t);
+ }
+ }
+ }
+
+ if(IS_NEWS(ps->mail_stream))
+ defpat->patgrp->fldr_type = FLDR_NEWS;
+ else
+ defpat->patgrp->fldr_type = FLDR_EMAIL;
+
+ specific_fldr[0] = specific_fldr[sizeof(specific_fldr)-1] = '\0';
+ if(sp_flagged(ps->mail_stream, SP_INBOX))
+ strncpy(specific_fldr, ps_global->inbox_name, sizeof(specific_fldr)-1);
+ else if(ps->context_current
+ && ps->context_current->use & CNTXT_INCMNG &&
+ folder_is_nick(ps->cur_folder, FOLDERS(ps->context_current), 0))
+ strncpy(specific_fldr, ps->cur_folder, sizeof(specific_fldr)-1);
+ else
+ context_apply(specific_fldr, ps->context_current, ps->cur_folder,
+ sizeof(specific_fldr));
+
+ if(specific_fldr[0]){
+ s = add_comma_escapes(specific_fldr);
+ if(s){
+ if(rtype == 'f')
+ defpat->patgrp->fldr_type = FLDR_SPECIFIC;
+
+ defpat->patgrp->folder = string_to_pattern(s);
+ fs_give((void **)&s);
+ }
+ }
+
+ role_type_print(title, sizeof(title), "ADD NEW %sRULE", rflags);
+
+ /*
+ * Role_config_edit_screen is sometimes called as a tool or a sub
+ * routine called from a tool within conf_scroll_screen, but here it
+ * is going to be at the top-level (we're not inside conf_scroll_screen
+ * right now). It uses opt_screen to set the ro_warning bit. We need
+ * to let it know that we're at the top, which we do by setting
+ * opt_screen to NULL. Otherwise, the thing that opt_screen is pointing
+ * to is just random stack stuff from some previous conf_scroll_screen
+ * call which has already exited.
+ */
+ opt_screen = NULL;
+
+ if(role_config_edit_screen(ps, defpat, title, rflags,
+ &newpat) == 1 && newpat){
+
+ if(ps->never_allow_changing_from && newpat->action &&
+ newpat->action->from)
+ q_status_message(SM_ORDER|SM_DING, 3, 7,
+ _("Site policy doesn't allow changing From address so From is ignored"));
+
+ if(rflags & ROLE_DO_ROLES && newpat->patgrp && newpat->patgrp->nick){
+ PAT_S *pat;
+
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->patgrp && pat->patgrp->nick &&
+ !strucmp(pat->patgrp->nick, newpat->patgrp->nick)){
+ q_status_message(SM_ORDER|SM_DING, 3, 7, _("Warning: The nickname of the new role is already in use."));
+ break;
+ }
+ }
+ }
+
+
+ set_pathandle(rflags);
+
+ /* need a new patline */
+ new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline));
+ memset((void *)new_patline, 0, sizeof(*new_patline));
+ new_patline->type = Literal;
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ /* tie together with new pattern */
+ new_patline->first = new_patline->last = newpat;
+ newpat->patline = new_patline;
+
+ /* find last current patline */
+ for(patline = (*cur_pat_h)->patlinehead;
+ patline && patline->next;
+ patline = patline->next)
+ ;
+
+ /* add new patline to end of list */
+ if(patline){
+ patline->next = new_patline;
+ new_patline->prev = patline;
+ }
+ else
+ (*cur_pat_h)->patlinehead = new_patline;
+
+ if(write_patterns(rflags) == 0){
+ char msg[60];
+
+ /*
+ * Flush out current_vals of anything we've possibly changed.
+ */
+
+ if(rflags & ROLE_DO_ROLES && ps_global->default_role){
+ q_status_message(SM_ORDER,0,3, "Default role is unset");
+ ps_global->default_role = NULL;
+ }
+
+ close_patterns(rflags | PAT_USE_CURRENT);
+
+ role_type_print(msg, sizeof(msg), "New %srule saved", rflags);
+ q_status_message(SM_ORDER, 0, 3, msg);
+
+ /* scores may have changed */
+ if(rflags & ROLE_DO_SCORES){
+ int i;
+ MAILSTREAM *m;
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m){
+ clear_folder_scores(m);
+ clear_index_cache(m, 0);
+ }
+ }
+
+ /* We've already bound msgmap to global mail_stream
+ * at the start of this function, but if we wanted to
+ * we could clean this up.
+ */
+ if(mn_get_sort(msgmap) == SortScore)
+ refresh_sort(ps_global->mail_stream, msgmap, SRT_VRB);
+ }
+
+ if(rflags & ROLE_DO_FILTER)
+ role_process_filters();
+
+ /* recalculate need for scores */
+ scores_are_used(SCOREUSE_INVALID);
+
+ /* we may want to fetch more or fewer headers each fetch */
+ calc_extra_hdrs();
+ if(get_extra_hdrs())
+ (void) mail_parameters(NULL, SET_IMAPEXTRAHEADERS,
+ (void *) get_extra_hdrs());
+
+ if(rflags & ROLE_DO_INCOLS && pico_usingcolor())
+ clear_index_cache(ps_global->mail_stream, 0);
+
+ /*
+ * ROLE_DO_OTHER is made up of a bunch of different variables
+ * that may have changed. Assume they all changed and fix them.
+ */
+ if(rflags & ROLE_DO_OTHER){
+ reset_index_format();
+ clear_index_cache(ps_global->mail_stream, 0);
+ if(!mn_get_mansort(msgmap))
+ reset_sort_order(SRT_VRB);
+ }
+ }
+ }
+ else
+ cmd_cancelled(NULL);
+
+ free_pat(&defpat);
+ ps->mangled_screen = 1;
+}
+
+
+PATTERN_S *
+addrlst_to_pattern(struct mail_address *addr)
+{
+ char *s, *t, *u, *v;
+ PATTERN_S *p = NULL;
+ size_t l;
+
+ if(addr){
+ l = est_size(addr);
+ t = s = (char *) fs_get((l+1) * sizeof(char));
+ s[0] = '\0';
+ while(addr){
+ u = simple_addr_string(addr, tmp_20k_buf, SIZEOF_20KBUF);
+ v = add_roletake_escapes(u);
+ if(v){
+ if(*v && t != s)
+ sstrncpy(&t, ",", l-(t-s));
+
+ sstrncpy(&t, v, l-(t-s));
+ fs_give((void **)&v);
+ }
+
+ addr = addr->next;
+ }
+
+ s[l] = '\0';
+
+ if(*s)
+ p = string_to_pattern(s);
+
+ fs_give((void **) &s);
+ }
+
+ return(p);
+}
+
+
+void
+role_config_init_disp(struct pine *ps, CONF_S **first_line, long int rflags, PAT_STATE *pstate)
+{
+ PAT_LINE_S *patline;
+ CONF_S *ctmp = NULL;
+ int inherit = 0, added_fake = 0;
+
+ if(first_line)
+ *first_line = NULL;
+
+ /*
+ * Set cur_pat_h and manipulate directly.
+ */
+ set_pathandle(rflags);
+ patline = *cur_pat_h ? (*cur_pat_h)->patlinehead : NULL;
+ if(patline && patline->type == Inherit){
+ add_patline_to_display(ps, &ctmp, 0, first_line, NULL, patline, rflags);
+ patline = patline->next;
+ }
+
+ if(!patline){
+ add_fake_first_role(&ctmp, 0, rflags);
+ added_fake++;
+ if(first_line && !*first_line)
+ (*first_line) = ctmp;
+ }
+
+ for(; patline; patline = patline->next)
+ add_patline_to_display(ps, &ctmp, 0, first_line, NULL, patline, rflags);
+
+ /*
+ * If there are no actual patterns so far, we need to have an Add line
+ * for the cursor to be on. This would happen if all of the patlines
+ * were File includes and none of the files contained patterns.
+ */
+ if(!first_pattern(role_global_pstate) ||
+ ((inherit=first_pattern(role_global_pstate)->inherit) &&
+ !next_pattern(role_global_pstate))){
+
+ /*
+ * Find the start and prepend the fake first role.
+ */
+ while(ctmp && ctmp->prev)
+ ctmp = ctmp->prev;
+
+ if(!added_fake){
+ add_fake_first_role(&ctmp, inherit ? 0 : 1, rflags);
+ if(first_line && !*first_line)
+ (*first_line) = ctmp;
+ }
+ }
+}
+
+
+void
+add_patline_to_display(struct pine *ps, CONF_S **ctmp, int before, CONF_S **first_line, CONF_S **top_line, PAT_LINE_S *patline, long int rflags)
+{
+ PAT_S *pat;
+ int len, firstitem, wid;
+ char *q;
+ char buf[6*MAX_SCREEN_COLS+1];
+
+ /* put dashed line around file contents */
+ if(patline->type == File){
+
+ new_confline(ctmp);
+ if(before){
+ /*
+ * New_confline appends ctmp after old current instead of inserting
+ * it, so we have to adjust. We have
+ * <- a <-> b <-> p <-> c -> and want <- a <-> p <-> b <-> c ->
+ */
+
+ CONF_S *a, *b, *c, *p;
+
+ p = *ctmp;
+ b = (*ctmp)->prev;
+ c = (*ctmp)->next;
+ a = b ? b->prev : NULL;
+ if(a)
+ a->next = p;
+
+ if(b){
+ b->prev = p;
+ b->next = c;
+ }
+
+ if(c)
+ c->prev = b;
+
+ p->prev = a;
+ p->next = b;
+ }
+
+ if(top_line && *top_line == NULL)
+ *top_line = (*ctmp);
+
+ len = strlen(patline->filename) + 100;
+
+ q = (char *) fs_get((len + 1) * sizeof(char));
+ snprintf(q, len+1, "From file %s%s", patline->filename,
+ patline->readonly ? " (ReadOnly)" : "");
+ q[len-1] = '\0';
+
+ if((wid=utf8_width(q)) > ps->ttyo->screen_cols -2)
+ utf8_snprintf(buf, sizeof(buf), "--%.*w", ps->ttyo->screen_cols -2, q);
+ else
+ snprintf(buf, sizeof(buf), "--%s%s", q, repeat_char(ps->ttyo->screen_cols -2-wid, '-'));
+
+ (*ctmp)->value = cpystr(buf);
+
+ fs_give((void **)&q);
+ (*ctmp)->flags |= (CF_NOSELECT | CF_STARTITEM);
+ (*ctmp)->d.r.patline = patline;
+ firstitem = 0;
+ }
+ else
+ firstitem = 1;
+
+ for(pat = patline->first; pat; pat = pat->next){
+
+ /* Check that pattern has a role and is of right type */
+ if(pat->inherit ||
+ (pat->action &&
+ (((rflags & ROLE_DO_ROLES) && pat->action->is_a_role) ||
+ ((rflags & ROLE_DO_INCOLS) && pat->action->is_a_incol) ||
+ ((rflags & ROLE_DO_SRCH) && pat->action->is_a_srch) ||
+ ((rflags & ROLE_DO_OTHER) && pat->action->is_a_other) ||
+ ((rflags & ROLE_DO_SCORES) && pat->action->is_a_score) ||
+ ((rflags & ROLE_DO_FILTER) && pat->action->is_a_filter)))){
+ add_role_to_display(ctmp, patline, pat, 0,
+ (first_line && *first_line == NULL)
+ ? first_line :
+ (top_line && *top_line == NULL)
+ ? top_line : NULL,
+ firstitem, rflags);
+ firstitem = 1;
+ if(top_line && *top_line == NULL && first_line)
+ *top_line = *first_line;
+ }
+
+ }
+
+ if(patline->type == File){
+ new_confline(ctmp);
+ len = strlen(patline->filename) + 100;
+
+ q = (char *) fs_get((len + 1) * sizeof(char));
+ snprintf(q, len+1, "End of Rules from %s", patline->filename);
+ q[len-1] = '\0';
+
+ if((wid=utf8_width(q)) > ps->ttyo->screen_cols -2)
+ utf8_snprintf(buf, sizeof(buf), "--%.*w", ps->ttyo->screen_cols -2, q);
+ else
+ snprintf(buf, sizeof(buf), "--%s%s", q, repeat_char(ps->ttyo->screen_cols -2-wid, '-'));
+
+ (*ctmp)->value = cpystr(buf);
+
+ fs_give((void **)&q);
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->d.r.patline = patline;
+ }
+}
+
+
+void
+add_role_to_display(CONF_S **ctmp, PAT_LINE_S *patline, PAT_S *pat, int before, CONF_S **first_line, int firstitem, long int rflags)
+{
+ char title[80];
+
+ if(!(pat && (pat->action || pat->inherit)))
+ return;
+
+ new_confline(ctmp);
+ if(first_line && !pat->inherit)
+ *first_line = *ctmp;
+
+ if(before){
+ /*
+ * New_confline appends ctmp after old current instead of inserting
+ * it, so we have to adjust. We have
+ * <- a <-> b <-> p <-> c -> and want <- a <-> p <-> b <-> c ->
+ */
+
+ CONF_S *a, *b, *c, *p;
+
+ p = *ctmp;
+ b = (*ctmp)->prev;
+ c = (*ctmp)->next;
+ a = b ? b->prev : NULL;
+ if(a)
+ a->next = p;
+
+ if(b){
+ b->prev = p;
+ b->next = c;
+ }
+
+ if(c)
+ c->prev = b;
+
+ p->prev = a;
+ p->next = b;
+ }
+
+ role_type_print(title, sizeof(title), _("HELP FOR %sRULE CONFIGURATION"), rflags);
+
+ if(pat->inherit){
+ (*ctmp)->flags |= ((firstitem ? CF_STARTITEM : 0) |
+ CF_NOSELECT | CF_INHERIT);
+ }
+ else{
+ (*ctmp)->flags |= (firstitem ? CF_STARTITEM : 0);
+ (*ctmp)->value = cpystr((pat && pat->patgrp && pat->patgrp->nick)
+ ? pat->patgrp->nick : "?");
+ }
+
+ (*ctmp)->d.r.patline = patline;
+ (*ctmp)->d.r.pat = pat;
+ (*ctmp)->keymenu = &role_conf_km;
+ (*ctmp)->help = (rflags & ROLE_DO_INCOLS) ? h_rules_incols :
+ (rflags & ROLE_DO_OTHER) ? h_rules_other :
+ (rflags & ROLE_DO_FILTER) ? h_rules_filter :
+ (rflags & ROLE_DO_SCORES) ? h_rules_score :
+ (rflags & ROLE_DO_ROLES) ? h_rules_roles :
+ (rflags & ROLE_DO_SRCH) ? h_rules_srch :
+ NO_HELP;
+ (*ctmp)->help_title = title;
+ (*ctmp)->tool = role_config_tool;
+ (*ctmp)->valoffset = 4;
+}
+
+
+void
+add_fake_first_role(CONF_S **ctmp, int before, long int rflags)
+{
+ char title[80];
+ char add[80];
+
+ new_confline(ctmp);
+
+ if(before){
+ /*
+ * New_confline appends ctmp after old current instead of inserting
+ * it, so we have to adjust. We have
+ * <- a <-> b <-> p <-> c -> and want <- a <-> p <-> b <-> c ->
+ */
+
+ CONF_S *a, *b, *c, *p;
+
+ p = *ctmp;
+ b = (*ctmp)->prev;
+ c = (*ctmp)->next;
+ a = b ? b->prev : NULL;
+ if(a)
+ a->next = p;
+
+ if(b){
+ b->prev = p;
+ b->next = c;
+ }
+
+ if(c)
+ c->prev = b;
+
+ p->prev = a;
+ p->next = b;
+ }
+
+ role_type_print(title, sizeof(title), _("HELP FOR %sRULE CONFIGURATION"), rflags);
+ role_type_print(add, sizeof(add), _("Use Add to add a %sRule"), rflags);
+
+ (*ctmp)->value = cpystr(add);
+ (*ctmp)->keymenu = &role_conf_km;
+ (*ctmp)->help = (rflags & ROLE_DO_INCOLS) ? h_rules_incols :
+ (rflags & ROLE_DO_OTHER) ? h_rules_other :
+ (rflags & ROLE_DO_FILTER) ? h_rules_filter :
+ (rflags & ROLE_DO_SCORES) ? h_rules_score :
+ (rflags & ROLE_DO_ROLES) ? h_rules_roles :
+ (rflags & ROLE_DO_SRCH) ? h_rules_srch :
+ NO_HELP;
+ (*ctmp)->help_title = title;
+ (*ctmp)->tool = role_config_tool;
+ (*ctmp)->flags |= CF_STARTITEM;
+ (*ctmp)->valoffset = 4;
+}
+
+
+int
+role_config_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int first_one = 0, rv = 0;
+ char exitpmt[80];
+ PAT_S *pat;
+
+ if(!(pat = first_pattern(role_global_pstate)) ||
+ (pat->inherit && !next_pattern(role_global_pstate)))
+ first_one++;
+
+ switch(cmd){
+ case MC_DELETE :
+ if(first_one)
+ q_status_message(SM_ORDER|SM_DING, 0, 3,
+ _("Nothing to Delete, use Add"));
+ else
+ rv = role_config_del(ps, cl, role_global_flags);
+
+ break;
+
+ case MC_ADD :
+ rv = role_config_add(ps, cl, role_global_flags);
+ break;
+
+ case MC_EDIT :
+ if(first_one)
+ rv = role_config_add(ps, cl, role_global_flags);
+ else
+ rv = role_config_edit(ps, cl, role_global_flags);
+
+ break;
+
+ case MC_SHUFFLE :
+ if(first_one)
+ q_status_message(SM_ORDER|SM_DING, 0, 3,
+ _("Nothing to Shuffle, use Add"));
+ else
+ rv = role_config_shuffle(ps, cl);
+
+ break;
+
+ case MC_EXIT :
+ role_type_print(exitpmt, sizeof(exitpmt), "%sRule Setup", role_global_flags);
+ rv = screen_exit_cmd(flags, exitpmt);
+ break;
+
+ case MC_ADDFILE :
+ rv = role_config_addfile(ps, cl, role_global_flags);
+ break;
+
+ case MC_DELFILE :
+ rv = role_config_delfile(ps, cl, role_global_flags);
+ break;
+
+ case MC_COPY :
+ if(first_one)
+ q_status_message(SM_ORDER|SM_DING, 0, 3,
+ _("Nothing to Replicate, use Add"));
+ else
+ rv = role_config_replicate(ps, cl, role_global_flags);
+
+ break;
+
+ default:
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Add a new role.
+ *
+ * Returns 1 -- There were changes
+ * 0 -- No changes
+ */
+int
+role_config_add(struct pine *ps, CONF_S **cl, long int rflags)
+{
+ int rv = 0, first_pat = 0;
+ PAT_S *new_pat = NULL, *cur_pat;
+ PAT_LINE_S *new_patline, *cur_patline;
+ PAT_STATE pstate;
+ char title[80];
+
+ if((*cl)->d.r.patline &&
+ (*cl)->d.r.patline->readonly
+ && (*cl)->d.r.patline->type == File){
+ q_status_message(SM_ORDER, 0, 3, _("Can't add rule to ReadOnly file"));
+ return(rv);
+ }
+
+ role_type_print(title, sizeof(title), "ADD A %sRULE", rflags);
+
+ if(role_config_edit_screen(ps, NULL, title, rflags,
+ &new_pat) == 1 && new_pat){
+ if(ps->never_allow_changing_from &&
+ new_pat->action &&
+ new_pat->action->from)
+ q_status_message(SM_ORDER|SM_DING, 0, 3,
+ _("Site policy doesn't allow changing From address so From is ignored"));
+
+ if(rflags & ROLE_DO_ROLES &&
+ new_pat->patgrp &&
+ new_pat->patgrp->nick &&
+ nonempty_patterns(ROLE_DO_ROLES, &pstate)){
+ PAT_S *pat;
+
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->patgrp && pat->patgrp->nick &&
+ !strucmp(pat->patgrp->nick, new_pat->patgrp->nick)){
+ q_status_message(SM_ORDER|SM_DING, 3, 7, _("Warning: The nickname of the new role is already in use."));
+ break;
+ }
+ }
+ }
+
+ rv = 1;
+ cur_pat = (*cl)->d.r.pat;
+ if(!cur_pat)
+ first_pat++;
+
+ set_pathandle(rflags);
+ cur_patline = first_pat ? (*cur_pat_h)->patlinehead : cur_pat->patline;
+
+ /* need a new pat_line */
+ if(first_pat || (cur_patline && cur_patline->type == Literal)){
+ new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline));
+ memset((void *)new_patline, 0, sizeof(*new_patline));
+ new_patline->type = Literal;
+ (*cur_pat_h)->dirtypinerc = 1;
+ }
+
+ if(cur_patline){
+ if(first_pat || cur_patline->type == Literal){
+ new_patline->prev = cur_patline;
+ new_patline->next = cur_patline->next;
+ if(cur_patline->next)
+ cur_patline->next->prev = new_patline;
+
+ cur_patline->next = new_patline;
+
+ /* tie new patline and new pat together */
+ new_pat->patline = new_patline;
+ new_patline->first = new_patline->last = new_pat;
+ }
+ else if(cur_patline->type == File){ /* don't need a new pat_line */
+ /* tie together */
+ new_pat->patline = cur_patline;
+ cur_patline->dirty = 1;
+
+ /* Splice new_pat after cur_pat */
+ new_pat->prev = cur_pat;
+ new_pat->next = cur_pat->next;
+ if(cur_pat->next)
+ cur_pat->next->prev = new_pat;
+ else
+ cur_patline->last = new_pat;
+
+ cur_pat->next = new_pat;
+ }
+ }
+ else{
+ /* tie new first patline and pat together */
+ new_pat->patline = new_patline;
+ new_patline->first = new_patline->last = new_pat;
+
+ /* set head of list */
+ (*cur_pat_h)->patlinehead = new_patline;
+ }
+
+ /*
+ * If this is the first role, we replace the "Use Add" fake role
+ * with this real one.
+ */
+ if(first_pat){
+ /* Adjust conf_scroll_screen variables */
+ (*cl)->d.r.pat = new_pat;
+ (*cl)->d.r.patline = new_pat->patline;
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ (*cl)->value = cpystr((new_pat && new_pat->patgrp &&
+ new_pat->patgrp->nick)
+ ? new_pat->patgrp->nick : "?");
+ }
+ /* Else we are inserting a new role after the cur role */
+ else
+ add_role_to_display(cl, new_pat->patline, new_pat, 0, NULL,
+ 1, rflags);
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Replicate a role.
+ *
+ * Returns 1 -- There were changes
+ * 0 -- No changes
+ */
+int
+role_config_replicate(struct pine *ps, CONF_S **cl, long int rflags)
+{
+ int rv = 0, first_pat = 0;
+ PAT_S *new_pat = NULL, *cur_pat, *defpat = NULL;
+ PAT_LINE_S *new_patline, *cur_patline;
+ PAT_STATE pstate;
+ char title[80];
+
+ if((*cl)->d.r.patline &&
+ (*cl)->d.r.patline->readonly
+ && (*cl)->d.r.patline->type == File){
+ q_status_message(SM_ORDER, 0, 3, _("Can't add rule to ReadOnly file"));
+ return(rv);
+ }
+
+ if((*cl)->d.r.pat && (defpat = copy_pat((*cl)->d.r.pat))){
+ /* change nickname */
+ if(defpat->patgrp && defpat->patgrp->nick){
+#define CLONEWORD " Copy"
+ char *oldnick = defpat->patgrp->nick;
+ size_t len;
+
+ len = strlen(oldnick)+strlen(CLONEWORD);
+ defpat->patgrp->nick = (char *)fs_get((len+1) * sizeof(char));
+ strncpy(defpat->patgrp->nick, oldnick, len);
+ defpat->patgrp->nick[len] = '\0';
+ strncat(defpat->patgrp->nick, CLONEWORD,
+ len+1-1-strlen(defpat->patgrp->nick));
+ fs_give((void **)&oldnick);
+ if(defpat->action){
+ if(defpat->action->nick)
+ fs_give((void **)&defpat->action->nick);
+
+ defpat->action->nick = cpystr(defpat->patgrp->nick);
+ }
+ }
+
+ /* set this so that even if we don't edit at all, we'll be asked */
+ rflags |= ROLE_CHANGES;
+
+ role_type_print(title, sizeof(title), "CHANGE THIS %sRULE", rflags);
+
+ if(role_config_edit_screen(ps, defpat, title, rflags,
+ &new_pat) == 1 && new_pat){
+
+ if(ps->never_allow_changing_from &&
+ new_pat->action &&
+ new_pat->action->from)
+ q_status_message(SM_ORDER|SM_DING, 0, 3,
+ _("Site policy doesn't allow changing From address so From is ignored"));
+
+ if(rflags & ROLE_DO_ROLES &&
+ new_pat->patgrp &&
+ new_pat->patgrp->nick &&
+ nonempty_patterns(ROLE_DO_ROLES, &pstate)){
+ PAT_S *pat;
+
+ for(pat = first_pattern(&pstate);
+ pat;
+ pat = next_pattern(&pstate)){
+ if(pat->patgrp && pat->patgrp->nick &&
+ !strucmp(pat->patgrp->nick, new_pat->patgrp->nick)){
+ q_status_message(SM_ORDER|SM_DING, 3, 7, _("Warning: The nickname of the new role is already in use."));
+ break;
+ }
+ }
+ }
+
+ rv = 1;
+ cur_pat = (*cl)->d.r.pat;
+ if(!cur_pat)
+ first_pat++;
+
+ set_pathandle(rflags);
+ cur_patline = first_pat ? (*cur_pat_h)->patlinehead : cur_pat->patline;
+
+ /* need a new pat_line */
+ if(first_pat || (cur_patline && cur_patline->type == Literal)){
+ new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline));
+ memset((void *)new_patline, 0, sizeof(*new_patline));
+ new_patline->type = Literal;
+ (*cur_pat_h)->dirtypinerc = 1;
+ }
+
+ if(cur_patline){
+ if(first_pat || cur_patline->type == Literal){
+ new_patline->prev = cur_patline;
+ new_patline->next = cur_patline->next;
+ if(cur_patline->next)
+ cur_patline->next->prev = new_patline;
+
+ cur_patline->next = new_patline;
+
+ /* tie new patline and new pat together */
+ new_pat->patline = new_patline;
+ new_patline->first = new_patline->last = new_pat;
+ }
+ else if(cur_patline->type == File){ /* don't need a new pat_line */
+ /* tie together */
+ new_pat->patline = cur_patline;
+ cur_patline->dirty = 1;
+
+ /* Splice new_pat after cur_pat */
+ new_pat->prev = cur_pat;
+ new_pat->next = cur_pat->next;
+ if(cur_pat->next)
+ cur_pat->next->prev = new_pat;
+ else
+ cur_patline->last = new_pat;
+
+ cur_pat->next = new_pat;
+ }
+ }
+ else{
+ /* tie new first patline and pat together */
+ new_pat->patline = new_patline;
+ new_patline->first = new_patline->last = new_pat;
+
+ /* set head of list */
+ (*cur_pat_h)->patlinehead = new_patline;
+ }
+
+ /*
+ * If this is the first role, we replace the "Use Add" fake role
+ * with this real one.
+ */
+ if(first_pat){
+ /* Adjust conf_scroll_screen variables */
+ (*cl)->d.r.pat = new_pat;
+ (*cl)->d.r.patline = new_pat->patline;
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ (*cl)->value = cpystr((new_pat && new_pat->patgrp &&
+ new_pat->patgrp->nick)
+ ? new_pat->patgrp->nick : "?");
+ }
+ /* Else we are inserting a new role after the cur role */
+ else
+ add_role_to_display(cl, new_pat->patline, new_pat, 0, NULL,
+ 1, rflags);
+ }
+ }
+
+ if(defpat)
+ free_pat(&defpat);
+
+ return(rv);
+}
+
+
+/*
+ * Change the current role.
+ *
+ * Returns 1 -- There were changes
+ * 0 -- No changes
+ */
+int
+role_config_edit(struct pine *ps, CONF_S **cl, long int rflags)
+{
+ int rv = 0;
+ PAT_S *new_pat = NULL, *cur_pat;
+ char title[80];
+
+ if((*cl)->d.r.patline->readonly){
+ q_status_message(SM_ORDER, 0, 3, _("Can't change ReadOnly rule"));
+ return(rv);
+ }
+
+ cur_pat = (*cl)->d.r.pat;
+
+ role_type_print(title, sizeof(title), "CHANGE THIS %sRULE", rflags);
+
+ if(role_config_edit_screen(ps, cur_pat, title,
+ rflags, &new_pat) == 1 && new_pat){
+
+ if(ps->never_allow_changing_from &&
+ new_pat->action &&
+ new_pat->action->from)
+ q_status_message(SM_ORDER|SM_DING, 0, 3,
+ _("Site policy doesn't allow changing From address so From is ignored"));
+
+ if(rflags & ROLE_DO_ROLES && new_pat->patgrp && new_pat->patgrp->nick){
+ PAT_S *pat;
+
+ for(pat = first_pattern(role_global_pstate);
+ pat;
+ pat = next_pattern(role_global_pstate)){
+ if(pat->patgrp && pat->patgrp->nick && pat != cur_pat &&
+ !strucmp(pat->patgrp->nick, new_pat->patgrp->nick)){
+ q_status_message(SM_ORDER|SM_DING, 3, 7, _("Warning: The nickname of this role is also used for another role."));
+ break;
+ }
+ }
+ }
+
+ rv = 1;
+
+ /*
+ * Splice in new_pat in place of cur_pat
+ */
+
+ if(cur_pat->prev)
+ cur_pat->prev->next = new_pat;
+
+ if(cur_pat->next)
+ cur_pat->next->prev = new_pat;
+
+ new_pat->prev = cur_pat->prev;
+ new_pat->next = cur_pat->next;
+
+ /* tie together patline and pat (new_pat gets patline in editor) */
+ if(new_pat->patline->first == cur_pat)
+ new_pat->patline->first = new_pat;
+
+ if(new_pat->patline->last == cur_pat)
+ new_pat->patline->last = new_pat;
+
+ if(new_pat->patline->type == Literal){
+ set_pathandle(rflags);
+ if(*cur_pat_h)
+ (*cur_pat_h)->dirtypinerc = 1;
+ }
+ else
+ new_pat->patline->dirty = 1;
+
+ cur_pat->next = NULL;
+ free_pat(&cur_pat);
+
+ /* Adjust conf_scroll_screen variables */
+ (*cl)->d.r.pat = new_pat;
+ (*cl)->d.r.patline = new_pat->patline;
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ (*cl)->value = cpystr((new_pat->patgrp && new_pat->patgrp->nick)
+ ? new_pat->patgrp->nick : "?");
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Delete a role.
+ *
+ * Returns 1 -- There were changes
+ * 0 -- No changes
+ */
+int
+role_config_del(struct pine *ps, CONF_S **cl, long int rflags)
+{
+ int rv = 0;
+ char msg[80];
+ char prompt[100];
+
+ if((*cl)->d.r.patline->readonly){
+ q_status_message(SM_ORDER, 0, 3, _("Can't delete ReadOnly rule"));
+ return(rv);
+ }
+
+ role_type_print(msg, sizeof(msg), _("Really delete %srule"), rflags);
+ snprintf(prompt, sizeof(prompt), "%s \"%s\" ", msg, (*cl)->value);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ ps->mangled_footer = 1;
+ if(want_to(prompt,'n','n',h_config_role_del, WT_FLUSH_IN) == 'y'){
+ rv = ps->mangled_body = 1;
+ delete_a_role(cl, rflags);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Rule not deleted"));
+
+ return(rv);
+}
+
+
+void
+delete_a_role(CONF_S **cl, long int rflags)
+{
+ PAT_S *cur_pat;
+ CONF_S *cp, *cq;
+ PAT_LINE_S *cur_patline;
+ int inherit = 0;
+
+ cur_pat = (*cl)->d.r.pat;
+ cur_patline = (*cl)->d.r.patline;
+
+ if(cur_patline->type == Literal){ /* delete patline */
+ set_pathandle(rflags);
+ if(cur_patline->prev)
+ cur_patline->prev->next = cur_patline->next;
+ else{
+ if(*cur_pat_h) /* this has to be true */
+ (*cur_pat_h)->patlinehead = cur_patline->next;
+ }
+
+ if(cur_patline->next)
+ cur_patline->next->prev = cur_patline->prev;
+
+ if(*cur_pat_h) /* this has to be true */
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ cur_patline->next = NULL;
+ free_patline(&cur_patline);
+ }
+ else if(cur_patline->type == File){ /* or delete pat */
+ if(cur_pat->prev)
+ cur_pat->prev->next = cur_pat->next;
+ else
+ cur_patline->first = cur_pat->next;
+
+ if(cur_pat->next)
+ cur_pat->next->prev = cur_pat->prev;
+ else
+ cur_patline->last = cur_pat->prev;
+
+ cur_patline->dirty = 1;
+
+ cur_pat->next = NULL;
+ free_pat(&cur_pat);
+ }
+
+ /* delete the conf line */
+
+ /* deleting last real rule */
+ if(!first_pattern(role_global_pstate) ||
+ ((inherit=first_pattern(role_global_pstate)->inherit) &&
+ !next_pattern(role_global_pstate))){
+
+ cq = *cl;
+
+ /*
+ * Find the start and prepend the fake first role.
+ */
+ while(*cl && (*cl)->prev)
+ *cl = (*cl)->prev;
+
+ add_fake_first_role(cl, inherit ? 0 : 1, rflags);
+ snip_confline(&cq);
+ opt_screen->top_line = (*cl);
+ opt_screen->current = (*cl);
+ }
+ else{
+ /* find next selectable line */
+ for(cp = (*cl)->next;
+ cp && (cp->flags & CF_NOSELECT);
+ cp = cp->next)
+ ;
+
+ if(!cp){ /* no next selectable, find previous selectable */
+ if(*cl == opt_screen->top_line)
+ opt_screen->top_line = (*cl)->prev;
+
+ for(cp = (*cl)->prev;
+ cp && (cp->flags & CF_NOSELECT);
+ cp = cp->prev)
+ ;
+ }
+ else if(*cl == opt_screen->top_line)
+ opt_screen->top_line = (*cl)->next;
+
+ cq = *cl;
+ *cl = cp;
+ snip_confline(&cq);
+ }
+}
+
+
+/*
+ * Shuffle the current role up or down.
+ *
+ * Returns 1 -- There were changes
+ * 0 -- No changes
+ */
+int
+role_config_shuffle(struct pine *ps, CONF_S **cl)
+{
+ int rv = 0, deefault, i;
+ int readonlyabove = 0, readonlybelow = 0;
+ ESCKEY_S opts[5];
+ HelpType help;
+ char tmp[200];
+ CONF_S *a, *b;
+ PAT_TYPE curtype, prevtype, nexttype;
+
+ if(!((*cl)->prev || (*cl)->next)){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Shuffle only makes sense when there is more than one rule defined"));
+ return(rv);
+ }
+
+ /* Move it up or down? */
+ i = 0;
+ opts[i].ch = 'u';
+ opts[i].rval = 'u';
+ opts[i].name = "U";
+ opts[i++].label = N_("Up");
+
+ opts[i].ch = 'd';
+ opts[i].rval = 'd';
+ opts[i].name = "D";
+ opts[i++].label = N_("Down");
+
+ opts[i].ch = 'b';
+ opts[i].rval = 'b';
+ opts[i].name = "B";
+ opts[i++].label = N_("Before File");
+
+ opts[i].ch = 'a';
+ opts[i].rval = 'a';
+ opts[i].name = "A";
+ opts[i++].label = N_("After File");
+
+ opts[i].ch = -1;
+ deefault = 'u';
+
+ curtype = ((*cl)->d.r.patline) ? (*cl)->d.r.patline->type : TypeNotSet;
+
+ prevtype = ((*cl)->prev && (*cl)->prev->d.r.patline)
+ ? (*cl)->prev->d.r.patline->type : TypeNotSet;
+ if(curtype == File && prevtype == File && (*cl)->prev->d.r.pat == NULL)
+ prevtype = TypeNotSet;
+
+ nexttype = ((*cl)->next && (*cl)->next->d.r.patline)
+ ? (*cl)->next->d.r.patline->type : TypeNotSet;
+ if(curtype == File && nexttype == File && (*cl)->next->d.r.pat == NULL)
+ nexttype = TypeNotSet;
+
+
+ if(curtype == Literal){
+ if(prevtype == TypeNotSet ||
+ prevtype == Inherit){ /* no up, at top */
+ opts[0].ch = -2;
+ opts[2].ch = -2;
+ deefault = 'd';
+ }
+ else if(prevtype == Literal){ /* regular up */
+ opts[2].ch = -2;
+ }
+ else if(prevtype == File){ /* file above us */
+ if((*cl)->prev->d.r.patline->readonly)
+ readonlyabove++;
+ }
+
+ if(nexttype == TypeNotSet){ /* no down, at bottom */
+ opts[1].ch = -2;
+ opts[3].ch = -2;
+ }
+ else if(nexttype == Literal){ /* regular down */
+ opts[3].ch = -2;
+ }
+ else if(nexttype == File){ /* file below us */
+ if((*cl)->next->d.r.patline->readonly)
+ readonlybelow++;
+ }
+ }
+ else if(curtype == File){
+ if((*cl)->d.r.patline && (*cl)->d.r.patline->readonly){
+ q_status_message(SM_ORDER, 0, 3, _("Can't change ReadOnly file"));
+ return(0);
+ }
+
+ opts[2].ch = -2;
+ opts[3].ch = -2;
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 3,
+ "Programming Error: unknown line type in role_shuffle");
+ return(rv);
+ }
+
+ snprintf(tmp, sizeof(tmp), "Shuffle \"%s\" %s%s%s%s%s%s%s ? ",
+ (*cl)->value,
+ (opts[0].ch != -2) ? N_("UP") : "",
+ (opts[0].ch != -2 && opts[1].ch != -2) ? " or " : "",
+ (opts[1].ch != -2) ? N_("DOWN") : "",
+ ((opts[0].ch != -2 ||
+ opts[1].ch != -2) && opts[2].ch != -2) ? " or " : "",
+ (opts[2].ch != -2) ? N_("BEFORE") : "",
+ ((opts[0].ch != -2 ||
+ opts[1].ch != -2 ||
+ opts[2].ch != -2) && opts[3].ch != -2) ? " or " : "",
+ (opts[3].ch != -2) ? N_("AFTER") : "");
+ tmp[sizeof(tmp)-1] = '\0';
+
+ help = (opts[0].ch == -2) ? h_role_shuf_down
+ : (opts[1].ch == -2) ? h_role_shuf_up
+ : h_role_shuf;
+
+ rv = radio_buttons(tmp, -FOOTER_ROWS(ps), opts, deefault, 'x',
+ help, RB_NORM);
+
+ if(rv == 'x'){
+ cmd_cancelled("Shuffle");
+ return(0);
+ }
+
+ if((readonlyabove && rv == 'u' && curtype != prevtype) ||
+ (readonlybelow && rv == 'd' && curtype != nexttype)){
+ q_status_message(SM_ORDER, 0, 3, _("Can't shuffle into ReadOnly file"));
+ return(0);
+ }
+
+ if(rv == 'u' && curtype == Literal && prevtype == Literal){
+ rv = 1;
+ a = (*cl)->prev;
+ b = (*cl);
+ if(a == opt_screen->top_line)
+ opt_screen->top_line = b;
+
+ swap_literal_roles(a, b);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'd' && curtype == Literal && nexttype == Literal){
+ rv = 1;
+ a = (*cl);
+ b = (*cl)->next;
+ if(a == opt_screen->top_line)
+ opt_screen->top_line = b;
+
+ swap_literal_roles(a, b);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'u' && curtype == File && prevtype == File){
+ rv = 1;
+ a = (*cl)->prev;
+ b = (*cl);
+ if(a == opt_screen->top_line)
+ opt_screen->top_line = b;
+
+ swap_file_roles(a, b);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'u' && curtype == File){
+ rv = 1;
+ move_role_outof_file(cl, 1);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'd' && curtype == File && nexttype == File){
+ rv = 1;
+ a = (*cl);
+ b = (*cl)->next;
+ if(a == opt_screen->top_line)
+ opt_screen->top_line = b;
+
+ swap_file_roles(a, b);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'd' && curtype == File){
+ rv = 1;
+ if(*cl == opt_screen->top_line)
+ opt_screen->top_line = (*cl)->next;
+
+ move_role_outof_file(cl, 0);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'u' && curtype == Literal && prevtype == File){
+ rv = 1;
+ move_role_into_file(cl, 1);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'd' && curtype == Literal && nexttype == File){
+ rv = 1;
+ if(*cl == opt_screen->top_line)
+ opt_screen->top_line = (*cl)->next;
+
+ move_role_into_file(cl, 0);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'b'){
+ rv = 1;
+ move_role_around_file(cl, 1);
+ ps->mangled_body = 1;
+ }
+ else if(rv == 'a'){
+ rv = 1;
+ if(*cl == opt_screen->top_line)
+ opt_screen->top_line = (*cl)->next;
+
+ move_role_around_file(cl, 0);
+ ps->mangled_body = 1;
+ }
+
+ return(rv);
+}
+
+
+int
+role_config_addfile(struct pine *ps, CONF_S **cl, long int rflags)
+{
+ char filename[MAXPATH+1], full_filename[MAXPATH+1];
+ char dir2[MAXPATH+1], pdir[MAXPATH+1];
+ char *lc, *newfile = NULL;
+ PAT_LINE_S *file_patline;
+ int rv = 0, len;
+ int r = 1, flags;
+ HelpType help = NO_HELP;
+ PAT_TYPE curtype;
+ CONF_S *first_line = NULL, *add_line, *save_current;
+ struct variable *vars = ps->vars;
+
+ if(ps->restricted){
+ q_status_message(SM_ORDER, 0, 3, "Alpine demo can't read files");
+ return(rv);
+ }
+
+ curtype = ((*cl)->d.r.patline && (*cl)->d.r.patline)
+ ? (*cl)->d.r.patline->type : TypeNotSet;
+
+ if(curtype == File){
+ q_status_message(SM_ORDER, 0, 3, _("Current rule is already part of a file. Move outside any files first."));
+ return(rv);
+ }
+
+ /*
+ * Parse_pattern_file uses signature_path to figure out where to look
+ * for the file. In signature_path we read signature files relative
+ * to the pinerc dir, so if user selects one that is in there we'll
+ * use relative instead of absolute, so it looks nicer.
+ */
+ pdir[0] = '\0';
+ if(VAR_OPER_DIR){
+ strncpy(pdir, VAR_OPER_DIR, sizeof(pdir)-1);
+ pdir[sizeof(pdir)-1] = '\0';
+ len = strlen(pdir) + 1;
+ }
+ else if((lc = last_cmpnt(ps->pinerc)) != NULL){
+ strncpy(pdir, ps->pinerc, MIN(sizeof(pdir)-1,lc-ps->pinerc));
+ pdir[MIN(sizeof(pdir)-1, lc-ps->pinerc)] = '\0';
+ len = strlen(pdir);
+ }
+
+ strncpy(dir2, pdir, sizeof(dir2)-1);
+ dir2[sizeof(dir2)-1] = '\0';
+ filename[0] = '\0';
+ full_filename[0] = '\0';
+ ps->mangled_footer = 1;
+
+ while(1){
+ flags = OE_APPEND_CURRENT;
+ r = optionally_enter(filename, -FOOTER_ROWS(ps), 0, sizeof(filename),
+ "Name of file to be added to rules: ",
+ NULL, help, &flags);
+
+ if(r == 3){
+ help = (help == NO_HELP) ? h_config_role_addfile : NO_HELP;
+ continue;
+ }
+ else if(r == 10 || r == 11){ /* Browser or File Completion */
+ continue;
+ }
+ else if(r == 1 || (r == 0 && filename[0] == '\0')){
+ cmd_cancelled("IncludeFile");
+ return(rv);
+ }
+ else if(r == 4){
+ continue;
+ }
+ else if(r != 0){
+ Writechar(BELL, 0);
+ continue;
+ }
+
+ removing_leading_and_trailing_white_space(filename);
+ if(is_absolute_path(filename))
+ newfile = cpystr(filename);
+ else{
+ build_path(full_filename, dir2, filename, sizeof(full_filename));
+ removing_leading_and_trailing_white_space(full_filename);
+ if(!strncmp(full_filename, pdir, strlen(pdir)))
+ newfile = cpystr(full_filename + len);
+ else
+ newfile = cpystr(full_filename);
+ }
+
+ if(newfile && *newfile)
+ break;
+ }
+
+ if(!newfile)
+ return(rv);
+
+ set_pathandle(rflags);
+
+ if((file_patline = parse_pat_file(newfile)) != NULL){
+ /*
+ * Insert the file after the current line.
+ */
+ PAT_LINE_S *cur_patline;
+ int first_pat;
+
+ rv = ps->mangled_screen = 1;
+ first_pat = !(*cl)->d.r.pat;
+ cur_patline = (*cl)->d.r.patline ? (*cl)->d.r.patline :
+ (*cur_pat_h) ? (*cur_pat_h)->patlinehead : NULL;
+
+ if(*cur_pat_h)
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ file_patline->dirty = 1;
+
+ if(cur_patline){
+ file_patline->prev = cur_patline;
+ file_patline->next = cur_patline->next;
+ if(cur_patline->next)
+ cur_patline->next->prev = file_patline;
+
+ cur_patline->next = file_patline;
+ }
+ else{
+ /* set head of list */
+ if(*cur_pat_h)
+ (*cur_pat_h)->patlinehead = file_patline;
+ }
+
+ if(first_pat){
+ if(file_patline->first){
+ /* get rid of Fake Add line */
+ add_line = *cl;
+ opt_screen->top_line = NULL;
+ add_patline_to_display(ps, cl, 0, &first_line,
+ &opt_screen->top_line, file_patline,
+ rflags);
+ opt_screen->current = first_line;
+ snip_confline(&add_line);
+ }
+ else{
+ /* we're _appending_ to the Fake Add line */
+ save_current = opt_screen->current;
+ add_patline_to_display(ps, cl, 0, NULL, NULL, file_patline,
+ rflags);
+ opt_screen->current = save_current;
+ }
+ }
+ else{
+ opt_screen->top_line = NULL;
+ save_current = opt_screen->current;
+ add_patline_to_display(ps, cl, 0, &first_line,
+ &opt_screen->top_line, file_patline,
+ rflags);
+ if(first_line)
+ opt_screen->current = first_line;
+ else
+ opt_screen->current = save_current;
+ }
+ }
+
+ if(newfile)
+ fs_give((void **)&newfile);
+
+ return(rv);
+}
+
+
+int
+role_config_delfile(struct pine *ps, CONF_S **cl, long int rflags)
+{
+ int rv = 0;
+ PAT_LINE_S *cur_patline;
+ char prompt[100];
+
+ if(!(cur_patline = (*cl)->d.r.patline)){
+ q_status_message(SM_ORDER, 0, 3,
+ "Unknown problem in role_config_delfile");
+ return(rv);
+ }
+
+ if(cur_patline->type != File){
+ q_status_message(SM_ORDER, 0, 3, _("Current rule is not part of a file. Use Delete to remove current rule"));
+ return(rv);
+ }
+
+ snprintf(prompt, sizeof(prompt), _("Really remove rule file \"%s\" from rules config "),
+ cur_patline->filename);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ ps->mangled_footer = 1;
+ if(want_to(prompt,'n','n',h_config_role_delfile, WT_FLUSH_IN) == 'y'){
+ CONF_S *ctmp, *cp;
+
+ set_pathandle(rflags);
+ rv = ps->mangled_screen = 1;
+ if(*cur_pat_h)
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ if(cur_patline->prev)
+ cur_patline->prev->next = cur_patline->next;
+ else{
+ if(*cur_pat_h)
+ (*cur_pat_h)->patlinehead = cur_patline->next;
+ }
+
+ if(cur_patline->next)
+ cur_patline->next->prev = cur_patline->prev;
+
+ /* delete the conf lines */
+
+ /* find the first one associated with this file */
+ for(ctmp = *cl;
+ ctmp && ctmp->prev && ctmp->prev->d.r.patline == cur_patline;
+ ctmp = ctmp->prev)
+ ;
+
+ if(ctmp->prev) /* this file wasn't the first thing in config */
+ *cl = ctmp->prev;
+ else{ /* this file was first in config */
+ for(cp = ctmp; cp && cp->next; cp = cp->next)
+ ;
+
+ if(cp->d.r.patline == cur_patline)
+ *cl = NULL;
+ else
+ *cl = cp;
+ }
+
+ /* delete lines from the file */
+ while(ctmp && ctmp->d.r.patline == cur_patline){
+ cp = ctmp;
+ ctmp = ctmp->next;
+ snip_confline(&cp);
+ }
+
+ /* deleting last real rule */
+ if(!first_pattern(role_global_pstate)){
+ /*
+ * Find the start and prepend the fake first role
+ * in there.
+ */
+ while(*cl && (*cl)->prev)
+ *cl = (*cl)->prev;
+
+ add_fake_first_role(cl, 1, rflags);
+ }
+ else if(first_pattern(role_global_pstate)->inherit &&
+ !next_pattern(role_global_pstate)){
+ while(*cl && (*cl)->prev)
+ *cl = (*cl)->prev;
+
+ /* append fake first after inherit */
+ add_fake_first_role(cl, 0, rflags);
+ }
+
+ opt_screen->top_line = first_confline(*cl);
+ opt_screen->current = first_sel_confline(opt_screen->top_line);
+
+ cur_patline->next = NULL;
+ free_patline(&cur_patline);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Rule file not removed"));
+
+ return(rv);
+}
+
+
+/*
+ * Swap from a, b to b, a.
+ */
+void
+swap_literal_roles(CONF_S *a, CONF_S *b)
+{
+ PAT_LINE_S *patline_a, *patline_b;
+
+ patline_a = a->d.r.patline;
+ patline_b = b->d.r.patline;
+
+ set_pathandle(role_global_flags);
+ if(*cur_pat_h)
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ /* first swap the patlines */
+ if(patline_a->next == patline_b){
+ patline_b->prev = patline_a->prev;
+ if(patline_a->prev)
+ patline_a->prev->next = patline_b;
+
+ patline_a->next = patline_b->next;
+ if(patline_b->next)
+ patline_b->next->prev = patline_a;
+
+ patline_b->next = patline_a;
+ patline_a->prev = patline_b;
+ }
+ else{
+ PAT_LINE_S *new_a_prev, *new_a_next;
+
+ new_a_prev = patline_b->prev;
+ new_a_next = patline_b->next;
+
+ patline_b->prev = patline_a->prev;
+ patline_b->next = patline_a->next;
+ if(patline_b->prev)
+ patline_b->prev->next = patline_b;
+ if(patline_b->next)
+ patline_b->next->prev = patline_b;
+
+ patline_a->prev = new_a_prev;
+ patline_a->next = new_a_next;
+ if(patline_a->prev)
+ patline_a->prev->next = patline_a;
+ if(patline_a->next)
+ patline_a->next->prev = patline_a;
+ }
+
+ /*
+ * If patline_b is now the first one in the list, we need to fix the
+ * head of the list to point to this new role.
+ */
+ if(patline_b->prev == NULL && *cur_pat_h)
+ (*cur_pat_h)->patlinehead = patline_b;
+
+
+ /* and then swap the conf lines */
+
+ b->prev = a->prev;
+ if(a->prev)
+ a->prev->next = b;
+
+ a->next = b->next;
+ if(b->next)
+ b->next->prev = a;
+
+ b->next = a;
+ a->prev = b;
+}
+
+
+/*
+ * Swap from a, b to b, a.
+ */
+void
+swap_file_roles(CONF_S *a, CONF_S *b)
+{
+ PAT_S *pat_a, *pat_b;
+ PAT_LINE_S *patline;
+
+ pat_a = a->d.r.pat;
+ pat_b = b->d.r.pat;
+ patline = pat_a->patline;
+
+ patline->dirty = 1;
+
+ /* first swap the pats */
+ if(pat_a->next == pat_b){
+ pat_b->prev = pat_a->prev;
+ if(pat_a->prev)
+ pat_a->prev->next = pat_b;
+
+ pat_a->next = pat_b->next;
+ if(pat_b->next)
+ pat_b->next->prev = pat_a;
+
+ pat_b->next = pat_a;
+ pat_a->prev = pat_b;
+ }
+ else{
+ PAT_S *new_a_prev, *new_a_next;
+
+ new_a_prev = pat_b->prev;
+ new_a_next = pat_b->next;
+
+ pat_b->prev = pat_a->prev;
+ pat_b->next = pat_a->next;
+ if(pat_b->prev)
+ pat_b->prev->next = pat_b;
+ if(pat_b->next)
+ pat_b->next->prev = pat_b;
+
+ pat_a->prev = new_a_prev;
+ pat_a->next = new_a_next;
+ if(pat_a->prev)
+ pat_a->prev->next = pat_a;
+ if(pat_a->next)
+ pat_a->next->prev = pat_a;
+ }
+
+ /*
+ * Fix the first and last pointers.
+ */
+ if(patline->first == pat_a)
+ patline->first = pat_b;
+ if(patline->last == pat_b)
+ patline->last = pat_a;
+
+ /* and then swap the conf lines */
+
+ b->prev = a->prev;
+ if(a->prev)
+ a->prev->next = b;
+
+ a->next = b->next;
+ if(b->next)
+ b->next->prev = a;
+
+ b->next = a;
+ a->prev = b;
+}
+
+
+/*
+ */
+void
+move_role_into_file(CONF_S **cl, int up)
+{
+ PAT_LINE_S *cur_patline, *file_patline;
+ PAT_S *pat;
+ CONF_S *a, *b;
+
+ cur_patline = (*cl)->d.r.patline;
+
+ if(up){
+ file_patline = (*cl)->prev->d.r.patline;
+ a = (*cl)->prev;
+ b = (*cl);
+ b->d.r.patline = file_patline;
+ }
+ else{
+ file_patline = (*cl)->next->d.r.patline;
+ a = (*cl);
+ b = (*cl)->next;
+ a->d.r.patline = file_patline;
+ }
+
+ set_pathandle(role_global_flags);
+ if(*cur_pat_h)
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ file_patline->dirty = 1;
+
+ pat = cur_patline->first;
+
+ if(!up && *cur_pat_h && cur_patline == (*cur_pat_h)->patlinehead)
+ (*cur_pat_h)->patlinehead = (*cur_pat_h)->patlinehead->next;
+
+ if(file_patline->first){
+ if(up){
+ file_patline->last->next = pat;
+ pat->prev = file_patline->last;
+ file_patline->last = pat;
+ }
+ else{
+ file_patline->first->prev = pat;
+ pat->next = file_patline->first;
+ file_patline->first = pat;
+ }
+ }
+ else /* will be only role in file */
+ file_patline->first = file_patline->last = pat;
+
+ pat->patline = file_patline;
+
+ /* delete the now unused cur_patline */
+ cur_patline->first = cur_patline->last = NULL;
+ if(cur_patline->prev)
+ cur_patline->prev->next = cur_patline->next;
+ if(cur_patline->next)
+ cur_patline->next->prev = cur_patline->prev;
+
+ cur_patline->next = NULL;
+ free_patline(&cur_patline);
+
+ /* and then swap the conf lines */
+
+ b->prev = a->prev;
+ if(a->prev)
+ a->prev->next = b;
+
+ a->next = b->next;
+ if(b->next)
+ b->next->prev = a;
+
+ b->next = a;
+ a->prev = b;
+}
+
+
+/*
+ */
+void
+move_role_outof_file(CONF_S **cl, int up)
+{
+ PAT_LINE_S *file_patline, *new_patline;
+ PAT_S *pat;
+ CONF_S *a, *b;
+
+ new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline));
+ memset((void *)new_patline, 0, sizeof(*new_patline));
+ new_patline->type = Literal;
+
+ file_patline = (*cl)->d.r.patline;
+ pat = (*cl)->d.r.pat;
+
+ if(up){
+ a = (*cl)->prev;
+ b = (*cl);
+
+ if(pat->prev)
+ pat->prev->next = pat->next;
+ else
+ file_patline->first = pat->next;
+
+ if(pat->next)
+ pat->next->prev = pat->prev;
+ else
+ file_patline->last = pat->prev;
+
+ if(file_patline->first)
+ file_patline->first->prev = NULL;
+
+ if(file_patline->last)
+ file_patline->last->next = NULL;
+
+ if(file_patline->prev)
+ file_patline->prev->next = new_patline;
+
+ new_patline->prev = file_patline->prev;
+ new_patline->next = file_patline;
+ file_patline->prev = new_patline;
+ b->d.r.patline = new_patline;
+ }
+ else{
+ a = (*cl);
+ b = (*cl)->next;
+
+ if(pat->prev)
+ pat->prev->next = pat->next;
+ else
+ file_patline->first = pat->next;
+
+ if(pat->next)
+ pat->next->prev = pat->prev;
+ else
+ file_patline->last = pat->prev;
+
+ if(file_patline->first)
+ file_patline->first->prev = NULL;
+
+ if(file_patline->last)
+ file_patline->last->next = NULL;
+
+ if(file_patline->next)
+ file_patline->next->prev = new_patline;
+
+ new_patline->next = file_patline->next;
+ new_patline->prev = file_patline;
+ file_patline->next = new_patline;
+ a->d.r.patline = new_patline;
+ }
+
+ set_pathandle(role_global_flags);
+ if(*cur_pat_h)
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ file_patline->dirty = 1;
+
+ new_patline->first = new_patline->last = pat;
+ pat->patline = new_patline;
+ pat->prev = pat->next = NULL;
+
+ if(up && *cur_pat_h && file_patline == (*cur_pat_h)->patlinehead)
+ (*cur_pat_h)->patlinehead = new_patline;
+
+ /* and then swap the conf lines */
+
+ b->prev = a->prev;
+ if(a->prev)
+ a->prev->next = b;
+
+ a->next = b->next;
+ if(b->next)
+ b->next->prev = a;
+
+ b->next = a;
+ a->prev = b;
+}
+
+
+/*
+ * This is a move of a literal role from before a file to after a file,
+ * or vice versa.
+ */
+void
+move_role_around_file(CONF_S **cl, int up)
+{
+ PAT_LINE_S *file_patline, *lit_patline;
+ CONF_S *cp;
+
+ set_pathandle(role_global_flags);
+ lit_patline = (*cl)->d.r.patline;
+ if(up)
+ file_patline = (*cl)->prev->d.r.patline;
+ else{
+ if(*cur_pat_h && lit_patline == (*cur_pat_h)->patlinehead)
+ (*cur_pat_h)->patlinehead = (*cur_pat_h)->patlinehead->next;
+
+ file_patline = (*cl)->next->d.r.patline;
+ }
+
+ if(*cur_pat_h)
+ (*cur_pat_h)->dirtypinerc = 1;
+
+ /* remove the lit_patline from the list */
+ if(lit_patline->prev)
+ lit_patline->prev->next = lit_patline->next;
+ if(lit_patline->next)
+ lit_patline->next->prev = lit_patline->prev;
+
+ /* and reinsert it on the other side of the file */
+ if(up){
+ if(*cur_pat_h && file_patline == (*cur_pat_h)->patlinehead)
+ (*cur_pat_h)->patlinehead = lit_patline;
+
+ lit_patline->prev = file_patline->prev;
+ lit_patline->next = file_patline;
+
+ if(file_patline->prev)
+ file_patline->prev->next = lit_patline;
+
+ file_patline->prev = lit_patline;
+ }
+ else{
+ lit_patline->next = file_patline->next;
+ lit_patline->prev = file_patline;
+
+ if(file_patline->next)
+ file_patline->next->prev = lit_patline;
+
+ file_patline->next = lit_patline;
+ }
+
+ /*
+ * And then move the conf line around the file conf lines.
+ */
+
+ /* find it's new home */
+ if(up)
+ for(cp = (*cl);
+ cp && cp->prev && cp->prev->d.r.patline == file_patline;
+ cp = prev_confline(cp))
+ ;
+ else
+ for(cp = (*cl);
+ cp && cp->next && cp->next->d.r.patline == file_patline;
+ cp = next_confline(cp))
+ ;
+
+ /* remove it from where it is */
+ if((*cl)->prev)
+ (*cl)->prev->next = (*cl)->next;
+ if((*cl)->next)
+ (*cl)->next->prev = (*cl)->prev;
+
+ /* cp points to top or bottom of the file lines */
+ if(up){
+ (*cl)->prev = cp->prev;
+ if(cp->prev)
+ cp->prev->next = (*cl);
+
+ cp->prev = (*cl);
+ (*cl)->next = cp;
+ }
+ else{
+ (*cl)->next = cp->next;
+ if(cp->next)
+ cp->next->prev = (*cl);
+
+ cp->next = (*cl);
+ (*cl)->prev = cp;
+ }
+}
+
+
+#define SETUP_PAT_STATUS(ctmp,svar,val,htitle,hval) \
+ {char tmp[MAXPATH+1]; \
+ int i, j, lv; \
+ NAMEVAL_S *f; \
+ \
+ /* Blank line */ \
+ new_confline(&ctmp); \
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE; \
+ \
+ new_confline(&ctmp); \
+ ctmp->var = &svar; \
+ ctmp->keymenu = &config_radiobutton_keymenu; \
+ ctmp->help = NO_HELP; \
+ ctmp->tool = NULL; \
+ snprintf(tmp, sizeof(tmp), "%-s =", svar.name); \
+ tmp[sizeof(tmp)-1] = '\0'; \
+ ctmp->varname = cpystr(tmp); \
+ ctmp->varnamep = ctmpb = ctmp; \
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM); \
+ \
+ new_confline(&ctmp); \
+ ctmp->var = NULL; \
+ ctmp->valoffset = rindent; \
+ ctmp->keymenu = &config_radiobutton_keymenu; \
+ ctmp->help = NO_HELP; \
+ ctmp->tool = NULL; \
+ ctmp->varnamep = ctmpb; \
+ ctmp->flags |= CF_NOSELECT; \
+ ctmp->value = cpystr("Set Choose One"); \
+ \
+ new_confline(&ctmp); \
+ ctmp->var = NULL; \
+ ctmp->valoffset = rindent; \
+ ctmp->keymenu = &config_radiobutton_keymenu; \
+ ctmp->help = NO_HELP; \
+ ctmp->tool = radio_tool; \
+ ctmp->varnamep = ctmpb; \
+ ctmp->flags |= CF_NOSELECT; \
+ ctmp->value = cpystr(set_choose); \
+ \
+ /* find longest value's name */ \
+ for(lv = 0, i = 0; (f = role_status_types(i)); i++) \
+ if(lv < (j = utf8_width(f->name))) \
+ lv = j; \
+ \
+ lv = MIN(lv, 100); \
+ \
+ for(i = 0; (f = role_status_types(i)); i++){ \
+ new_confline(&ctmp); \
+ ctmp->help_title= htitle; \
+ ctmp->var = &svar; \
+ ctmp->valoffset = rindent; \
+ ctmp->keymenu = &config_radiobutton_keymenu; \
+ ctmp->help = hval; \
+ ctmp->varmem = i; \
+ ctmp->tool = radio_tool; \
+ ctmp->varnamep = ctmpb; \
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w", (((!(def && def->patgrp) || \
+ val == -1) && \
+ f->value == PAT_STAT_EITHER) || \
+ (def && def->patgrp && \
+ f->value == val)) \
+ ? R_SELD : ' ', \
+ lv, lv, f->name); \
+ tmp[sizeof(tmp)-1] = '\0'; \
+ ctmp->value = cpystr(tmp); \
+ } \
+ }
+
+#define SETUP_MSG_STATE(ctmp,svar,val,htitle,hval) \
+ {char tmp[MAXPATH+1]; \
+ int i, j, lv; \
+ NAMEVAL_S *f; \
+ \
+ /* Blank line */ \
+ new_confline(&ctmp); \
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE; \
+ \
+ new_confline(&ctmp); \
+ ctmp->var = &svar; \
+ ctmp->keymenu = &config_radiobutton_keymenu; \
+ ctmp->help = NO_HELP; \
+ ctmp->tool = NULL; \
+ snprintf(tmp, sizeof(tmp), "%-s =", svar.name); \
+ tmp[sizeof(tmp)-1] = '\0'; \
+ ctmp->varname = cpystr(tmp); \
+ ctmp->varnamep = ctmpb = ctmp; \
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM); \
+ \
+ new_confline(&ctmp); \
+ ctmp->var = NULL; \
+ ctmp->valoffset = rindent; \
+ ctmp->keymenu = &config_radiobutton_keymenu; \
+ ctmp->help = NO_HELP; \
+ ctmp->tool = NULL; \
+ ctmp->varnamep = ctmpb; \
+ ctmp->flags |= CF_NOSELECT; \
+ ctmp->value = cpystr("Set Choose One"); \
+ \
+ new_confline(&ctmp); \
+ ctmp->var = NULL; \
+ ctmp->valoffset = rindent; \
+ ctmp->keymenu = &config_radiobutton_keymenu; \
+ ctmp->help = NO_HELP; \
+ ctmp->tool = radio_tool; \
+ ctmp->varnamep = ctmpb; \
+ ctmp->flags |= CF_NOSELECT; \
+ ctmp->value = cpystr(set_choose); \
+ \
+ /* find longest value's name */ \
+ for(lv = 0, i = 0; (f = msg_state_types(i)); i++) \
+ if(lv < (j = utf8_width(f->name))) \
+ lv = j; \
+ \
+ lv = MIN(lv, 100); \
+ \
+ for(i = 0; (f = msg_state_types(i)); i++){ \
+ new_confline(&ctmp); \
+ ctmp->help_title= htitle; \
+ ctmp->var = &svar; \
+ ctmp->valoffset = rindent; \
+ ctmp->keymenu = &config_radiobutton_keymenu; \
+ ctmp->help = hval; \
+ ctmp->varmem = i; \
+ ctmp->tool = radio_tool; \
+ ctmp->varnamep = ctmpb; \
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w", (f->value == val) \
+ ? R_SELD : ' ', \
+ lv, lv, f->name); \
+ tmp[sizeof(tmp)-1] = '\0'; \
+ ctmp->value = cpystr(tmp); \
+ } \
+ }
+
+
+#define FEAT_SENTDATE 0
+#define FEAT_IFNOTDEL 1
+#define FEAT_NONTERM 2
+bitmap_t feat_option_list;
+
+#define INABOOK_FROM 0
+#define INABOOK_REPLYTO 1
+#define INABOOK_SENDER 2
+#define INABOOK_TO 3
+#define INABOOK_CC 4
+bitmap_t inabook_type_list;
+
+
+#define INICK_INICK_CONF 0
+#define INICK_FROM_CONF 1
+#define INICK_REPLYTO_CONF 2
+#define INICK_FCC_CONF 3
+#define INICK_LITSIG_CONF 4 /* this needs to come before SIG_CONF */
+#define INICK_SIG_CONF 5
+#define INICK_TEMPL_CONF 6
+#define INICK_CSTM_CONF 7
+#define INICK_SMTP_CONF 8
+#define INICK_NNTP_CONF 9
+CONF_S *inick_confs[INICK_NNTP_CONF+1];
+
+
+/*
+ * Screen for editing configuration of a role.
+ *
+ * Args ps -- pine struct
+ * def -- default role values to start with
+ * title -- part of title at top of screen
+ * rflags -- which parts of role to edit
+ * result -- This is the returned PAT_S, freed by caller.
+ *
+ * Returns: 0 if no change
+ * 1 if user requested a change
+ * (change is stored in raw_server and hasn't been acted upon yet)
+ * 10 user says abort
+ */
+int
+role_config_edit_screen(struct pine *ps, PAT_S *def, char *title, long int rflags, PAT_S **result)
+{
+ OPT_SCREEN_S screen, *saved_screen;
+ CONF_S *ctmp = NULL, *ctmpb, *first_line = NULL;
+ struct variable nick_var, to_pat_var, from_pat_var,
+ comment_var,
+ sender_pat_var, cc_pat_var, recip_pat_var, news_pat_var,
+ subj_pat_var, inick_var, fldr_type_var, folder_pat_var,
+ abook_type_var, abook_pat_var,
+ alltext_pat_var, scorei_pat_var, partic_pat_var,
+ bodytext_pat_var, age_pat_var, size_pat_var,
+ keyword_pat_var, charset_pat_var,
+ stat_new_var, stat_del_var, stat_imp_var, stat_ans_var,
+ stat_rec_var, stat_8bit_var,
+ stat_bom_var, stat_boy_var,
+ cat_cmd_var, cati_var, cat_lim_var,
+ from_act_var, replyto_act_var, fcc_act_var,
+ sig_act_var, litsig_act_var, templ_act_var,
+ cstm_act_var, smtp_act_var, nntp_act_var,
+ sort_act_var, iform_act_var, startup_var,
+ repl_type_var, forw_type_var, comp_type_var, score_act_var,
+ hdrtok_act_var,
+ rolecolor_vars[2], filter_type_var, folder_act_var,
+ keyword_set_var, keyword_clr_var,
+ filt_new_var, filt_del_var, filt_imp_var, filt_ans_var;
+ struct variable *v, *varlist[65], opt_var, inabook_type_var;
+ char *nick = NULL, *inick = NULL, *fldr_type_pat = NULL,
+ *comment = NULL,
+ *scorei_pat = NULL, *age_pat = NULL, *size_pat = NULL,
+ *abook_type_pat = NULL,
+ *stat_new = NULL, *stat_del = NULL, *stat_imp = NULL,
+ *stat_rec = NULL, *stat_ans = NULL, *stat_8bit = NULL,
+ *stat_bom = NULL, *stat_boy = NULL,
+ *filt_new = NULL, *filt_del = NULL, *filt_imp = NULL,
+ *filt_ans = NULL, *cati = NULL, *cat_lim = NULL,
+ *from_act = NULL, *replyto_act = NULL, *fcc_act = NULL,
+ *sig_act = NULL, *litsig_act = NULL, *sort_act = NULL,
+ *templ_act = NULL, *repl_type = NULL, *forw_type = NULL,
+ *comp_type = NULL, *rc_fg = NULL, *rc_bg = NULL,
+ *score_act = NULL, *filter_type = NULL,
+ *hdrtok_act = NULL,
+ *iform_act = NULL, *startup_act = NULL,
+ *old_fg = NULL, *old_bg = NULL;
+ char **to_pat = NULL, **from_pat = NULL, **sender_pat = NULL,
+ **cc_pat = NULL, **news_pat = NULL, **recip_pat = NULL,
+ **partic_pat = NULL, **subj_pat = NULL,
+ **alltext_pat = NULL, **bodytext_pat = NULL,
+ **keyword_pat = NULL, **folder_pat = NULL,
+ **charset_pat = NULL,
+ **abook_pat = NULL, **folder_act = NULL,
+ **keyword_set = NULL, **keyword_clr = NULL,
+ **cat_cmd = NULL, **cstm_act = NULL, **smtp_act = NULL,
+ **nntp_act = NULL, **spat;
+ char tmp[MAXPATH+1], **apval, **lval, ***alval, *p;
+ /* TRANSLATORS: These next 4 are subheading for sections of a configuration screen. */
+ char *fstr = _(" CURRENT FOLDER CONDITIONS BEGIN HERE ");
+ char mstr[50];
+ char *astr = _(" ACTIONS BEGIN HERE ");
+ char *ustr = _(" USES BEGIN HERE ");
+ char *ostr = _(" OPTIONS BEGIN HERE ");
+ char *s_p_v_n = _("Subject pattern"); /* longest one of these */
+ char *u_s_s = _("Use SMTP Server"); /* ditto */
+ char *c_v_n = _("Exit Status Interval"); /* ditto */
+ SortOrder def_sort;
+ int def_sort_rev;
+ ARBHDR_S *aa, *a;
+ EARB_S *earb = NULL, *ea;
+ int rv, i, j, lv, pindent, maxpindent, rindent,
+ scoreval = 0, edit_role, wid,
+ edit_incol, edit_score, edit_filter, edit_other, edit_srch,
+ dval, ival, nval, aval, fval,
+ per_folder_only, need_uses, need_options;
+ int (*radio_tool)(struct pine *, int, CONF_S **, unsigned);
+ int (*addhdr_tool)(struct pine *, int, CONF_S **, unsigned);
+ int (*t_tool)(struct pine *, int, CONF_S **, unsigned);
+ NAMEVAL_S *f;
+
+ dprint((4, "role_config_edit_screen()\n"));
+ edit_role = rflags & ROLE_DO_ROLES;
+ edit_incol = rflags & ROLE_DO_INCOLS;
+ edit_score = rflags & ROLE_DO_SCORES;
+ edit_filter = rflags & ROLE_DO_FILTER;
+ edit_other = rflags & ROLE_DO_OTHER;
+ edit_srch = rflags & ROLE_DO_SRCH;
+
+ per_folder_only = (edit_other &&
+ !(edit_role || edit_incol || edit_score || edit_filter || edit_srch));
+ need_uses = edit_role;
+ need_options = !per_folder_only;
+
+ radio_tool = edit_filter ? role_filt_radiobutton_tool
+ : role_radiobutton_tool;
+ t_tool = edit_filter ? role_filt_text_tool : role_text_tool;
+ addhdr_tool = edit_filter ? role_filt_addhdr_tool : role_addhdr_tool;
+
+ rindent = 12; /* radio indent */
+
+ /*
+ * We edit by making a nested call to conf_scroll_screen.
+ * We use some fake struct variables to get back the results in, and
+ * so we can use the existing tools from the config screen.
+ */
+ varlist[j = 0] = &nick_var;
+ varlist[++j] = &comment_var;
+ varlist[++j] = &to_pat_var;
+ varlist[++j] = &from_pat_var;
+ varlist[++j] = &sender_pat_var;
+ varlist[++j] = &cc_pat_var;
+ varlist[++j] = &recip_pat_var;
+ varlist[++j] = &partic_pat_var;
+ varlist[++j] = &news_pat_var;
+ varlist[++j] = &subj_pat_var;
+ varlist[++j] = &alltext_pat_var;
+ varlist[++j] = &bodytext_pat_var;
+ varlist[++j] = &keyword_pat_var;
+ varlist[++j] = &charset_pat_var;
+ varlist[++j] = &age_pat_var;
+ varlist[++j] = &size_pat_var;
+ varlist[++j] = &scorei_pat_var;
+ varlist[++j] = &stat_new_var;
+ varlist[++j] = &stat_rec_var;
+ varlist[++j] = &stat_del_var;
+ varlist[++j] = &stat_imp_var;
+ varlist[++j] = &stat_ans_var;
+ varlist[++j] = &stat_8bit_var;
+ varlist[++j] = &stat_bom_var;
+ varlist[++j] = &stat_boy_var;
+ varlist[++j] = &cat_cmd_var;
+ varlist[++j] = &cati_var;
+ varlist[++j] = &cat_lim_var;
+ varlist[++j] = &inick_var;
+ varlist[++j] = &fldr_type_var;
+ varlist[++j] = &folder_pat_var;
+ varlist[++j] = &abook_type_var;
+ varlist[++j] = &abook_pat_var;
+ varlist[++j] = &from_act_var;
+ varlist[++j] = &replyto_act_var;
+ varlist[++j] = &fcc_act_var;
+ varlist[++j] = &sig_act_var;
+ varlist[++j] = &litsig_act_var;
+ varlist[++j] = &sort_act_var;
+ varlist[++j] = &iform_act_var;
+ varlist[++j] = &startup_var;
+ varlist[++j] = &templ_act_var;
+ varlist[++j] = &cstm_act_var;
+ varlist[++j] = &smtp_act_var;
+ varlist[++j] = &nntp_act_var;
+ varlist[++j] = &score_act_var;
+ varlist[++j] = &hdrtok_act_var;
+ varlist[++j] = &repl_type_var;
+ varlist[++j] = &forw_type_var;
+ varlist[++j] = &comp_type_var;
+ varlist[++j] = &rolecolor_vars[0];
+ varlist[++j] = &rolecolor_vars[1];
+ varlist[++j] = &filter_type_var;
+ varlist[++j] = &folder_act_var;
+ varlist[++j] = &keyword_set_var;
+ varlist[++j] = &keyword_clr_var;
+ varlist[++j] = &filt_new_var;
+ varlist[++j] = &filt_del_var;
+ varlist[++j] = &filt_imp_var;
+ varlist[++j] = &filt_ans_var;
+ varlist[++j] = &opt_var;
+ varlist[++j] = &inabook_type_var;
+ varlist[++j] = NULL;
+ for(j = 0; varlist[j]; j++)
+ memset(varlist[j], 0, sizeof(struct variable));
+
+ if(def && ((def->patgrp && def->patgrp->bogus) || (def->action && def->action->bogus))){
+ char msg[MAX_SCREEN_COLS+1];
+
+ snprintf(msg, sizeof(msg),
+ _("Rule contains unknown %s element, possibly from newer Alpine"),
+ (def->patgrp && def->patgrp->bogus) ? "pattern" : "action");
+ msg[sizeof(msg)-1] = '\0';
+ q_status_message(SM_ORDER | SM_DING, 7, 7, msg);
+ q_status_message(SM_ORDER | SM_DING, 7, 7,
+ _("Editing with this version of Alpine will destroy information"));
+ flush_status_messages(0);
+ }
+
+ role_forw_ptr = role_repl_ptr = role_fldr_ptr = role_filt_ptr = NULL;
+ role_status1_ptr = role_status2_ptr = role_status3_ptr = NULL;
+ role_status4_ptr = role_status5_ptr = role_status6_ptr = NULL;
+ role_status7_ptr = NULL; role_status8_ptr = NULL;
+ msg_state1_ptr = msg_state2_ptr = NULL;
+ msg_state3_ptr = msg_state4_ptr = NULL;
+ role_afrom_ptr = startup_ptr = NULL;
+ role_comment_ptr = NULL;
+
+ nick_var.name = cpystr(_("Nickname"));
+ nick_var.is_used = 1;
+ nick_var.is_user = 1;
+ apval = APVAL(&nick_var, ew);
+ *apval = (def && def->patgrp && def->patgrp->nick)
+ ? cpystr(def->patgrp->nick) : NULL;
+
+ nick_var.global_val.p = cpystr(edit_role
+ ? "Alternate Role"
+ : (edit_other
+ ? "Other Rule"
+ : (edit_incol
+ ? "Index Color Rule"
+ : (edit_score
+ ? "Score Rule"
+ : "Filter Rule"))));
+ set_current_val(&nick_var, FALSE, FALSE);
+
+ role_comment_ptr = &comment_var; /* so radiobuttons can tell */
+ comment_var.name = cpystr(_("Comment"));
+ comment_var.is_used = 1;
+ comment_var.is_user = 1;
+ apval = APVAL(&comment_var, ew);
+ *apval = (def && def->patgrp && def->patgrp->comment)
+ ? cpystr(def->patgrp->comment) : NULL;
+ set_current_val(&comment_var, FALSE, FALSE);
+
+ /* TRANSLATORS: Quite a few of the translations to follow are from the
+ rules editing screens. These are mostly headings of individual categories
+ of criteria which can be set in a rule. */
+ setup_dummy_pattern_var(&to_pat_var, _("To pattern"),
+ (def && def->patgrp) ? def->patgrp->to : NULL);
+ setup_dummy_pattern_var(&from_pat_var, _("From pattern"),
+ (def && def->patgrp) ? def->patgrp->from : NULL);
+ setup_dummy_pattern_var(&sender_pat_var, _("Sender pattern"),
+ (def && def->patgrp) ? def->patgrp->sender : NULL);
+ setup_dummy_pattern_var(&cc_pat_var, _("Cc pattern"),
+ (def && def->patgrp) ? def->patgrp->cc : NULL);
+ setup_dummy_pattern_var(&news_pat_var, _("News pattern"),
+ (def && def->patgrp) ? def->patgrp->news : NULL);
+ setup_dummy_pattern_var(&subj_pat_var, s_p_v_n,
+ (def && def->patgrp) ? def->patgrp->subj : NULL);
+ /* TRANSLATORS: Recip is an abbreviation for Recipients which stands for
+ all of the recipients of a message. */
+ setup_dummy_pattern_var(&recip_pat_var, _("Recip pattern"),
+ (def && def->patgrp) ? def->patgrp->recip : NULL);
+ /* TRANSLATORS: Partic is an abbreviation for Participants which stands for
+ all of the recipients plus the sender of a message. */
+ setup_dummy_pattern_var(&partic_pat_var, _("Partic pattern"),
+ (def && def->patgrp) ? def->patgrp->partic : NULL);
+ /* TRANSLATORS: AllText means all of the text of a message */
+ setup_dummy_pattern_var(&alltext_pat_var, _("AllText pattern"),
+ (def && def->patgrp) ? def->patgrp->alltext : NULL);
+ /* TRANSLATORS: BdyText means the text of a message but not the text in the headers */
+ setup_dummy_pattern_var(&bodytext_pat_var, _("BdyText pattern"),
+ (def && def->patgrp) ? def->patgrp->bodytext : NULL);
+
+ setup_dummy_pattern_var(&keyword_pat_var, _("Keyword pattern"),
+ (def && def->patgrp) ? def->patgrp->keyword : NULL);
+ setup_dummy_pattern_var(&charset_pat_var, _("Charset pattern"),
+ (def && def->patgrp) ? def->patgrp->charsets : NULL);
+
+ age_pat_global_ptr = &age_pat_var;
+ /* TRANSLATORS: Age interval is a setting for how old the message is. */
+ age_pat_var.name = cpystr(_("Age interval"));
+ age_pat_var.is_used = 1;
+ age_pat_var.is_user = 1;
+ if(def && def->patgrp && def->patgrp->do_age){
+ apval = APVAL(&age_pat_var, ew);
+ *apval = stringform_of_intvl(def->patgrp->age);
+ }
+
+ set_current_val(&age_pat_var, FALSE, FALSE);
+
+ size_pat_global_ptr = &size_pat_var;
+ size_pat_var.name = cpystr(_("Size interval"));
+ size_pat_var.is_used = 1;
+ size_pat_var.is_user = 1;
+ if(def && def->patgrp && def->patgrp->do_size){
+ apval = APVAL(&size_pat_var, ew);
+ *apval = stringform_of_intvl(def->patgrp->size);
+ }
+
+ set_current_val(&size_pat_var, FALSE, FALSE);
+
+ scorei_pat_global_ptr = &scorei_pat_var;
+ /* TRANSLATORS: Score is an alpine concept where the score can be kept for a
+ message to see if it is a message you want to look at. */
+ scorei_pat_var.name = cpystr(_("Score interval"));
+ scorei_pat_var.is_used = 1;
+ scorei_pat_var.is_user = 1;
+ if(def && def->patgrp && def->patgrp->do_score){
+ apval = APVAL(&scorei_pat_var, ew);
+ *apval = stringform_of_intvl(def->patgrp->score);
+ }
+
+ set_current_val(&scorei_pat_var, FALSE, FALSE);
+
+ role_status1_ptr = &stat_del_var; /* so radiobuttons can tell */
+ stat_del_var.name = cpystr(_("Message is Deleted?"));
+ stat_del_var.is_used = 1;
+ stat_del_var.is_user = 1;
+ apval = APVAL(&stat_del_var, ew);
+ *apval = (f=role_status_types((def && def->patgrp) ? def->patgrp->stat_del : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&stat_del_var, FALSE, FALSE);
+
+ role_status2_ptr = &stat_new_var; /* so radiobuttons can tell */
+ stat_new_var.name = cpystr(_("Message is New (Unseen)?"));
+ stat_new_var.is_used = 1;
+ stat_new_var.is_user = 1;
+ apval = APVAL(&stat_new_var, ew);
+ *apval = (f=role_status_types((def && def->patgrp) ? def->patgrp->stat_new : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&stat_new_var, FALSE, FALSE);
+
+ role_status3_ptr = &stat_imp_var; /* so radiobuttons can tell */
+ stat_imp_var.name = cpystr(_("Message is Important?"));
+ stat_imp_var.is_used = 1;
+ stat_imp_var.is_user = 1;
+ apval = APVAL(&stat_imp_var, ew);
+ *apval = (f=role_status_types((def && def->patgrp) ? def->patgrp->stat_imp : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&stat_imp_var, FALSE, FALSE);
+
+ role_status4_ptr = &stat_ans_var; /* so radiobuttons can tell */
+ stat_ans_var.name = cpystr(_("Message is Answered?"));
+ stat_ans_var.is_used = 1;
+ stat_ans_var.is_user = 1;
+ apval = APVAL(&stat_ans_var, ew);
+ *apval = (f=role_status_types((def && def->patgrp) ? def->patgrp->stat_ans : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&stat_ans_var, FALSE, FALSE);
+
+ role_status5_ptr = &stat_8bit_var; /* so radiobuttons can tell */
+ stat_8bit_var.name = cpystr(_("Subject contains raw 8-bit?"));
+ stat_8bit_var.is_used = 1;
+ stat_8bit_var.is_user = 1;
+ apval = APVAL(&stat_8bit_var, ew);
+ *apval = (f=role_status_types((def && def->patgrp) ? def->patgrp->stat_8bitsubj : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&stat_8bit_var, FALSE, FALSE);
+
+ role_status6_ptr = &stat_rec_var; /* so radiobuttons can tell */
+ stat_rec_var.name = cpystr(_("Message is Recent?"));
+ stat_rec_var.is_used = 1;
+ stat_rec_var.is_user = 1;
+ apval = APVAL(&stat_rec_var, ew);
+ *apval = (f=role_status_types((def && def->patgrp) ? def->patgrp->stat_rec : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&stat_rec_var, FALSE, FALSE);
+
+ role_status7_ptr = &stat_bom_var; /* so radiobuttons can tell */
+ stat_bom_var.name = cpystr(_("Beginning of Month?"));
+ stat_bom_var.is_used = 1;
+ stat_bom_var.is_user = 1;
+ apval = APVAL(&stat_bom_var, ew);
+ *apval = (f=role_status_types((def && def->patgrp) ? def->patgrp->stat_bom : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&stat_bom_var, FALSE, FALSE);
+
+ role_status8_ptr = &stat_boy_var; /* so radiobuttons can tell */
+ stat_boy_var.name = cpystr(_("Beginning of Year?"));
+ stat_boy_var.is_used = 1;
+ stat_boy_var.is_user = 1;
+ apval = APVAL(&stat_boy_var, ew);
+ *apval = (f=role_status_types((def && def->patgrp) ? def->patgrp->stat_boy : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&stat_boy_var, FALSE, FALSE);
+
+
+
+ convert_statebits_to_vals((def && def->action) ? def->action->state_setting_bits : 0L, &dval, &aval, &ival, &nval);
+ msg_state1_ptr = &filt_del_var; /* so radiobuttons can tell */
+ /* TRANSLATORS: these are actions that might be taken by the rule */
+ filt_del_var.name = cpystr(_("Set Deleted Status"));
+ filt_del_var.is_used = 1;
+ filt_del_var.is_user = 1;
+ apval = APVAL(&filt_del_var, ew);
+ *apval = (f=msg_state_types(dval)) ? cpystr(f->name) : NULL;
+ set_current_val(&filt_del_var, FALSE, FALSE);
+
+ msg_state2_ptr = &filt_new_var; /* so radiobuttons can tell */
+ filt_new_var.name = cpystr(_("Set New Status"));
+ filt_new_var.is_used = 1;
+ filt_new_var.is_user = 1;
+ apval = APVAL(&filt_new_var, ew);
+ *apval = (f=msg_state_types(nval)) ? cpystr(f->name) : NULL;
+ set_current_val(&filt_new_var, FALSE, FALSE);
+
+ msg_state3_ptr = &filt_imp_var; /* so radiobuttons can tell */
+ filt_imp_var.name = cpystr(_("Set Important Status"));
+ filt_imp_var.is_used = 1;
+ filt_imp_var.is_user = 1;
+ apval = APVAL(&filt_imp_var, ew);
+ *apval = (f=msg_state_types(ival)) ? cpystr(f->name) : NULL;
+ set_current_val(&filt_imp_var, FALSE, FALSE);
+
+ msg_state4_ptr = &filt_ans_var; /* so radiobuttons can tell */
+ filt_ans_var.name = cpystr(_("Set Answered Status"));
+ filt_ans_var.is_used = 1;
+ filt_ans_var.is_user = 1;
+ apval = APVAL(&filt_ans_var, ew);
+ *apval = (f=msg_state_types(aval)) ? cpystr(f->name) : NULL;
+ set_current_val(&filt_ans_var, FALSE, FALSE);
+
+ inick_var.name = cpystr(_("Initialize settings using role"));
+ inick_var.is_used = 1;
+ inick_var.is_user = 1;
+ apval = APVAL(&inick_var, ew);
+ *apval = (def && def->action && def->action->inherit_nick &&
+ def->action->inherit_nick[0])
+ ? cpystr(def->action->inherit_nick) : NULL;
+
+ role_fldr_ptr = &fldr_type_var; /* so radiobuttons can tell */
+ fldr_type_var.name = cpystr(_("Current Folder Type"));
+ fldr_type_var.is_used = 1;
+ fldr_type_var.is_user = 1;
+ apval = APVAL(&fldr_type_var, ew);
+ *apval = (f=pat_fldr_types((def && def->patgrp) ? def->patgrp->fldr_type : (!def && edit_filter) ? FLDR_SPECIFIC : FLDR_DEFL)) ? cpystr(f->name) : NULL;
+ set_current_val(&fldr_type_var, FALSE, FALSE);
+
+ setup_dummy_pattern_var(&folder_pat_var, _("Folder List"),
+ (def && def->patgrp) ? def->patgrp->folder : NULL);
+ /* special default for folder_pat */
+ alval = ALVAL(&folder_pat_var, ew);
+ if(alval && !*alval && !def && edit_filter){
+ char **ltmp;
+
+ ltmp = (char **) fs_get(2 * sizeof(*ltmp));
+ ltmp[0] = cpystr(ps_global->inbox_name);
+ ltmp[1] = NULL;
+ *alval = ltmp;
+ set_current_val(&folder_pat_var, FALSE, FALSE);
+ }
+
+ role_afrom_ptr = &abook_type_var; /* so radiobuttons can tell */
+ abook_type_var.name = cpystr(_("Address in address book?"));
+ abook_type_var.is_used = 1;
+ abook_type_var.is_user = 1;
+ apval = APVAL(&abook_type_var, ew);
+ *apval = (f=inabook_fldr_types((def && def->patgrp) ? def->patgrp->inabook : IAB_EITHER)) ? cpystr(f->name) : NULL;
+ set_current_val(&abook_type_var, FALSE, FALSE);
+
+ /* TRANSLATORS: Abook is an abbreviation for Address Book */
+ setup_dummy_pattern_var(&abook_pat_var, _("Abook List"),
+ (def && def->patgrp) ? def->patgrp->abooks : NULL);
+
+ /*
+ * This is a little different from some of the other patterns. Tt is
+ * actually a char ** in the struct instead of a PATTERN_S.
+ */
+ cat_cmd_global_ptr = &cat_cmd_var;
+ cat_cmd_var.name = cpystr(_("External Categorizer Commands"));
+ cat_cmd_var.is_used = 1;
+ cat_cmd_var.is_user = 1;
+ cat_cmd_var.is_list = 1;
+ alval = ALVAL(&cat_cmd_var, ew);
+ *alval = (def && def->patgrp && def->patgrp->category_cmd &&
+ def->patgrp->category_cmd[0])
+ ? copy_list_array(def->patgrp->category_cmd) : NULL;
+ set_current_val(&cat_cmd_var, FALSE, FALSE);
+
+ cati_global_ptr = &cati_var;
+ cati_var.name = cpystr(c_v_n);
+ cati_var.is_used = 1;
+ cati_var.is_user = 1;
+ if(def && def->patgrp && def->patgrp->do_cat && def->patgrp->category_cmd &&
+ def->patgrp->category_cmd[0]){
+ apval = APVAL(&cati_var, ew);
+ *apval = stringform_of_intvl(def->patgrp->cat);
+ }
+
+ set_current_val(&cati_var, FALSE, FALSE);
+
+ cat_lim_global_ptr = &cat_lim_var;
+ cat_lim_var.name = cpystr(_("Character Limit"));
+ cat_lim_var.is_used = 1;
+ cat_lim_var.is_user = 1;
+ cat_lim_var.global_val.p = cpystr("-1");
+ apval = APVAL(&cat_lim_var, ew);
+ if(def && def->patgrp && def->patgrp->category_cmd &&
+ def->patgrp->category_cmd[0] && def->patgrp->cat_lim != -1){
+ *apval = (char *) fs_get(20 * sizeof(char));
+ snprintf(*apval, 20, "%ld", def->patgrp->cat_lim);
+ (*apval)[20-1] = '\0';
+ }
+
+ set_current_val(&cat_lim_var, FALSE, FALSE);
+
+ from_act_var.name = cpystr(_("Set From"));
+ from_act_var.is_used = 1;
+ from_act_var.is_user = 1;
+ if(def && def->action && def->action->from){
+ char *bufp;
+ size_t len;
+
+ len = est_size(def->action->from);
+ bufp = (char *) fs_get(len * sizeof(char));
+ apval = APVAL(&from_act_var, ew);
+ *apval = addr_string_mult(def->action->from, bufp, len);
+ }
+ else{
+ apval = APVAL(&from_act_var, ew);
+ *apval = NULL;
+ }
+
+ replyto_act_var.name = cpystr(_("Set Reply-To"));
+ replyto_act_var.is_used = 1;
+ replyto_act_var.is_user = 1;
+ if(def && def->action && def->action->replyto){
+ char *bufp;
+ size_t len;
+
+ len = est_size(def->action->replyto);
+ bufp = (char *) fs_get(len * sizeof(char));
+ apval = APVAL(&replyto_act_var, ew);
+ *apval = addr_string_mult(def->action->replyto, bufp, len);
+ }
+ else{
+ apval = APVAL(&replyto_act_var, ew);
+ *apval = NULL;
+ }
+
+ fcc_act_var.name = cpystr(_("Set Fcc"));
+ fcc_act_var.is_used = 1;
+ fcc_act_var.is_user = 1;
+ apval = APVAL(&fcc_act_var, ew);
+ *apval = (def && def->action && def->action->fcc)
+ ? cpystr(def->action->fcc) : NULL;
+
+ sort_act_var.name = cpystr(_("Set Sort Order"));
+ sort_act_var.is_used = 1;
+ sort_act_var.is_user = 1;
+ apval = APVAL(&sort_act_var, ew);
+ if(def && def->action && def->action->is_a_other &&
+ def->action->sort_is_set){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s", sort_name(def->action->sortorder),
+ (def->action->revsort) ? "/Reverse" : "");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ *apval = cpystr(tmp_20k_buf);
+ }
+ else
+ *apval = NULL;
+
+ iform_act_var.name = cpystr(_("Set Index Format"));
+ iform_act_var.is_used = 1;
+ iform_act_var.is_user = 1;
+ apval = APVAL(&iform_act_var, ew);
+ *apval = (def && def->action && def->action->is_a_other &&
+ def->action->index_format)
+ ? cpystr(def->action->index_format) : NULL;
+ if(ps_global->VAR_INDEX_FORMAT){
+ iform_act_var.global_val.p = cpystr(ps_global->VAR_INDEX_FORMAT);
+ set_current_val(&iform_act_var, FALSE, FALSE);
+ }
+
+ startup_ptr = &startup_var;
+ startup_var.name = cpystr(_("Set Startup Rule"));
+ startup_var.is_used = 1;
+ startup_var.is_user = 1;
+ apval = APVAL(&startup_var, ew);
+ *apval = NULL;
+ if(def && def->action && def->action->is_a_other){
+ *apval = (f=startup_rules(def->action->startup_rule))
+ ? cpystr(f->name) : NULL;
+ set_current_val(&startup_var, FALSE, FALSE);
+ }
+ if(!*apval){
+ *apval = (f=startup_rules(IS_NOTSET)) ? cpystr(f->name) : NULL;
+ set_current_val(&startup_var, FALSE, FALSE);
+ }
+
+ /* TRANSLATORS: LiteralSig is a way to keep the signature in the configuration
+ file instead of in a separate Signature file. */
+ litsig_act_var.name = cpystr(_("Set LiteralSig"));
+ litsig_act_var.is_used = 1;
+ litsig_act_var.is_user = 1;
+ apval = APVAL(&litsig_act_var, ew);
+ *apval = (def && def->action && def->action->litsig)
+ ? cpystr(def->action->litsig) : NULL;
+
+ sig_act_var.name = cpystr(_("Set Signature"));
+ sig_act_var.is_used = 1;
+ sig_act_var.is_user = 1;
+ apval = APVAL(&sig_act_var, ew);
+ *apval = (def && def->action && def->action->sig)
+ ? cpystr(def->action->sig) : NULL;
+
+ /* TRANSLATORS: A template is a skeleton of a message to be used
+ for composing a new message */
+ templ_act_var.name = cpystr(_("Set Template"));
+ templ_act_var.is_used = 1;
+ templ_act_var.is_user = 1;
+ apval = APVAL(&templ_act_var, ew);
+ *apval = (def && def->action && def->action->template)
+ ? cpystr(def->action->template) : NULL;
+
+ /* TRANSLATORS: Hdrs is an abbreviation for Headers */
+ cstm_act_var.name = cpystr(_("Set Other Hdrs"));
+ cstm_act_var.is_used = 1;
+ cstm_act_var.is_user = 1;
+ cstm_act_var.is_list = 1;
+ alval = ALVAL(&cstm_act_var, ew);
+ *alval = (def && def->action && def->action->cstm)
+ ? copy_list_array(def->action->cstm) : NULL;
+
+ smtp_act_var.name = cpystr(u_s_s);
+ smtp_act_var.is_used = 1;
+ smtp_act_var.is_user = 1;
+ smtp_act_var.is_list = 1;
+ alval = ALVAL(&smtp_act_var, ew);
+ *alval = (def && def->action && def->action->smtp)
+ ? copy_list_array(def->action->smtp) : NULL;
+
+ nntp_act_var.name = cpystr(_("Use NNTP Server"));
+ nntp_act_var.is_used = 1;
+ nntp_act_var.is_user = 1;
+ nntp_act_var.is_list = 1;
+ alval = ALVAL(&nntp_act_var, ew);
+ *alval = (def && def->action && def->action->nntp)
+ ? copy_list_array(def->action->nntp) : NULL;
+
+ score_act_global_ptr = &score_act_var;
+ score_act_var.name = cpystr(_("Score Value"));
+ score_act_var.is_used = 1;
+ score_act_var.is_user = 1;
+ if(def && def->action && def->action->scoreval >= SCORE_MIN &&
+ def->action->scoreval <= SCORE_MAX)
+ scoreval = def->action->scoreval;
+
+ score_act_var.global_val.p = cpystr("0");
+ if(scoreval != 0){
+ apval = APVAL(&score_act_var, ew);
+ *apval = (char *)fs_get(20 * sizeof(char));
+ snprintf(*apval, 20, "%d", scoreval);
+ (*apval)[20-1] = '\0';
+ }
+
+ set_current_val(&score_act_var, FALSE, FALSE);
+
+ hdrtok_act_var.name = cpystr(_("Score From Header"));
+ hdrtok_act_var.is_used = 1;
+ hdrtok_act_var.is_user = 1;
+ if(def && def->action && def->action->scorevalhdrtok){
+ apval = APVAL(&hdrtok_act_var, ew);
+ *apval = hdrtok_to_stringform(def->action->scorevalhdrtok);
+ }
+
+ set_current_val(&hdrtok_act_var, FALSE, FALSE);
+
+ role_repl_ptr = &repl_type_var; /* so radiobuttons can tell */
+ /* TRANSLATORS: For these, Use is a now. This part of the rule describes how
+ it will be used when Replying so it is the Reply Use */
+ repl_type_var.name = cpystr(_("Reply Use"));
+ repl_type_var.is_used = 1;
+ repl_type_var.is_user = 1;
+ apval = APVAL(&repl_type_var, ew);
+ *apval = (f=role_repl_types((def && def->action) ? def->action->repl_type : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&repl_type_var, FALSE, FALSE);
+
+ role_forw_ptr = &forw_type_var; /* so radiobuttons can tell */
+ forw_type_var.name = cpystr(_("Forward Use"));
+ forw_type_var.is_used = 1;
+ forw_type_var.is_user = 1;
+ apval = APVAL(&forw_type_var, ew);
+ *apval = (f=role_forw_types((def && def->action) ? def->action->forw_type : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&forw_type_var, FALSE, FALSE);
+
+ comp_type_var.name = cpystr(_("Compose Use"));
+ comp_type_var.is_used = 1;
+ comp_type_var.is_user = 1;
+ apval = APVAL(&comp_type_var, ew);
+ *apval = (f=role_comp_types((def && def->action) ? def->action->comp_type : -1)) ? cpystr(f->name) : NULL;
+ set_current_val(&comp_type_var, FALSE, FALSE);
+
+ rolecolor_vars[0].is_used = 1;
+ rolecolor_vars[0].is_user = 1;
+ apval = APVAL(&rolecolor_vars[0], ew);
+ *apval = (def && def->action && def->action->incol &&
+ def->action->incol->fg[0])
+ ? cpystr(def->action->incol->fg) : NULL;
+ rolecolor_vars[1].is_used = 1;
+ rolecolor_vars[1].is_user = 1;
+ rolecolor_vars[0].name = cpystr("ic-foreground-color");
+ rolecolor_vars[1].name = cpystr(rolecolor_vars[0].name);
+ strncpy(rolecolor_vars[1].name + 3, "back", 4);
+ apval = APVAL(&rolecolor_vars[1], ew);
+ *apval = (def && def->action && def->action->incol &&
+ def->action->incol->bg[0])
+ ? cpystr(def->action->incol->bg) : NULL;
+ set_current_val(&rolecolor_vars[0], FALSE, FALSE);
+ set_current_val(&rolecolor_vars[1], FALSE, FALSE);
+ old_fg = PVAL(&rolecolor_vars[0], ew) ? cpystr(PVAL(&rolecolor_vars[0], ew))
+ : NULL;
+ old_bg = PVAL(&rolecolor_vars[1], ew) ? cpystr(PVAL(&rolecolor_vars[1], ew))
+ : NULL;
+
+
+ /* save the old opt_screen before calling scroll screen again */
+ saved_screen = opt_screen;
+
+ pindent = utf8_width(s_p_v_n); /* the longest one */
+ maxpindent = pindent + 6;
+ for(a = (def && def->patgrp) ? def->patgrp->arbhdr : NULL; a; a = a->next)
+ if((lv=utf8_width(a->field ? a->field : "")+utf8_width(" pattern")) > pindent)
+ pindent = lv;
+
+ pindent = MIN(pindent, maxpindent);
+
+ pindent += NOTLEN; /* width of `! ' */
+
+ pindent += 3; /* width of ` = ' */
+
+ /* Nickname */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR NICKNAME");
+ ctmp->var = &nick_var;
+ ctmp->valoffset = pindent;
+ ctmp->keymenu = &config_role_keymenu;
+ ctmp->help = edit_role ? h_config_role_nick :
+ edit_incol ? h_config_incol_nick :
+ edit_score ? h_config_score_nick :
+ edit_other ? h_config_other_nick
+ : h_config_filt_nick;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", pindent-3, pindent-3, nick_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ ctmp->varmem = -1;
+
+ first_line = ctmp;
+ if(rflags & ROLE_CHANGES)
+ first_line->flags |= CF_CHANGES;
+
+ /* Comment */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR COMMENT");
+ ctmp->var = &comment_var;
+ ctmp->valoffset = pindent;
+ ctmp->keymenu = &config_role_keymenu;
+ ctmp->help = h_config_role_comment;
+ ctmp->tool = role_litsig_text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", pindent-3, pindent-3, comment_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ ctmp->varmem = -1;
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ if(ps->ttyo->screen_cols >= (wid=utf8_width(fstr)) + 4){
+ int dashes;
+
+ dashes = (ps->ttyo->screen_cols - wid)/2;
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s", repeat_char(dashes, '='),
+ fstr, repeat_char(ps->ttyo->screen_cols-wid-dashes, '='));
+ ctmp->value = cpystr(tmp_20k_buf);
+ }
+ else
+ ctmp->value = cpystr(repeat_char(ps->ttyo->screen_cols, '='));
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Folder Type */
+ new_confline(&ctmp);
+ ctmp->var = &fldr_type_var;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", fldr_type_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Choose One");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose); \
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = pat_fldr_types(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ fval = -1;
+ for(i = 0; (f = pat_fldr_types(i)); i++){
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR CURRENT FOLDER TYPE");
+ ctmp->var = &fldr_type_var;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = edit_role ? h_config_role_fldr_type :
+ edit_incol ? h_config_incol_fldr_type :
+ edit_score ? h_config_score_fldr_type :
+ edit_other ? h_config_other_fldr_type
+ : h_config_filt_fldr_type;
+ ctmp->varmem = i;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+
+ if((PVAL(&fldr_type_var, ew) &&
+ !strucmp(PVAL(&fldr_type_var, ew), f->name))
+ || (!PVAL(&fldr_type_var, ew) && f->value == FLDR_DEFL))
+ fval = f->value;
+
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w",
+ (fval == f->value) ? R_SELD : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Folder */
+ /* 5 is the width of `(*) ' */
+ setup_role_pat_alt(ps, &ctmp, &folder_pat_var,
+ edit_role ? h_config_role_fldr_type :
+ edit_incol ? h_config_incol_fldr_type :
+ edit_score ? h_config_score_fldr_type :
+ edit_other ? h_config_other_fldr_type
+ : h_config_filt_fldr_type,
+ _("HELP FOR FOLDER LIST"),
+ &config_role_patfolder_keymenu, t_tool, rindent+5,
+ !(fval == FLDR_SPECIFIC));
+
+ if(!per_folder_only){ /* sorry about that indent */
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+
+ /* TRANSLATORS: The %s is replaced with one of the 4 or 5 words below, CURRENT,
+ SCORED, and so on. */
+ snprintf(mstr, sizeof(mstr), _(" %s MESSAGE CONDITIONS BEGIN HERE "),
+ edit_role ? _("CURRENT") :
+ edit_score ? _("SCORED") :
+ edit_incol ? _("COLORED") :
+ edit_filter ? _("FILTERED") : _("CURRENT"));
+ mstr[sizeof(mstr)-1] = '\0';
+
+ if(ps->ttyo->screen_cols >= (wid=utf8_width(mstr)) + 4){
+ int dashes;
+
+ dashes = (ps->ttyo->screen_cols - wid)/2;
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s", repeat_char(dashes, '='),
+ mstr, repeat_char(ps->ttyo->screen_cols-wid-dashes, '='));
+ ctmp->value = cpystr(tmp_20k_buf);
+ }
+ else
+ ctmp->value = cpystr(repeat_char(ps->ttyo->screen_cols, '='));
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ setup_role_pat(ps, &ctmp, &to_pat_var,
+ edit_role ? h_config_role_topat :
+ edit_incol ? h_config_incol_topat :
+ edit_score ? h_config_score_topat :
+ edit_other ? h_config_other_topat
+ : h_config_filt_topat,
+ _("HELP FOR TO PATTERN"),
+ &config_role_addr_pat_keymenu, t_tool, &earb, pindent);
+
+ setup_role_pat(ps, &ctmp, &from_pat_var, h_config_role_frompat,
+ _("HELP FOR FROM PATTERN"),
+ &config_role_addr_pat_keymenu, t_tool, &earb, pindent);
+
+ setup_role_pat(ps, &ctmp, &sender_pat_var, h_config_role_senderpat,
+ _("HELP FOR SENDER PATTERN"),
+ &config_role_addr_pat_keymenu, t_tool, &earb, pindent);
+
+ setup_role_pat(ps, &ctmp, &cc_pat_var, h_config_role_ccpat,
+ _("HELP FOR CC PATTERN"),
+ &config_role_addr_pat_keymenu, t_tool, &earb, pindent);
+
+ setup_role_pat(ps, &ctmp, &news_pat_var, h_config_role_newspat,
+ _("HELP FOR NEWS PATTERN"),
+ &config_role_keymenu_not, t_tool, &earb, pindent);
+
+ setup_role_pat(ps, &ctmp, &subj_pat_var, h_config_role_subjpat,
+ _("HELP FOR SUBJECT PATTERN"),
+ &config_role_keymenu_not, t_tool, &earb, pindent);
+
+ setup_role_pat(ps, &ctmp, &recip_pat_var, h_config_role_recippat,
+ _("HELP FOR RECIPIENT PATTERN"),
+ &config_role_addr_pat_keymenu, t_tool, &earb, pindent);
+
+ setup_role_pat(ps, &ctmp, &partic_pat_var, h_config_role_particpat,
+ _("HELP FOR PARTICIPANT PATTERN"),
+ &config_role_addr_pat_keymenu, t_tool, &earb, pindent);
+
+ /* Arbitrary Patterns */
+ ea = NULL;
+ for(j = 0, a = (def && def->patgrp) ? def->patgrp->arbhdr : NULL;
+ a;
+ j++, a = a->next){
+ char *fn = (a->field) ? a->field : "";
+
+ if(ea){
+ ea->next = (EARB_S *)fs_get(sizeof(*ea));
+ ea = ea->next;
+ }
+ else{
+ earb = (EARB_S *)fs_get(sizeof(*ea));
+ ea = earb;
+ }
+
+ memset((void *)ea, 0, sizeof(*ea));
+ ea->v = (struct variable *)fs_get(sizeof(struct variable));
+ memset((void *)ea->v, 0, sizeof(struct variable));
+ ea->a = (ARBHDR_S *)fs_get(sizeof(ARBHDR_S));
+ memset((void *)ea->a, 0, sizeof(ARBHDR_S));
+
+ ea->a->field = cpystr(fn);
+
+ p = (char *) fs_get(strlen(fn) + strlen(" pattern") + 1);
+ snprintf(p, strlen(fn) + strlen(" pattern") + 1, "%s pattern", fn);
+ p[strlen(fn) + strlen(" pattern") + 1 - 1] = '\0';
+ setup_dummy_pattern_var(ea->v, p, a->p);
+ fs_give((void **) &p);
+ setup_role_pat(ps, &ctmp, ea->v, h_config_role_arbpat,
+ ARB_HELP, &config_role_xtrahdr_keymenu,
+ t_tool, &earb, pindent);
+ }
+
+ new_confline(&ctmp);
+ ctmp->help_title = _("HELP FOR EXTRA HEADERS");
+ ctmp->value = cpystr(ADDXHDRS);
+ ctmp->keymenu = &config_role_keymenu_extra;
+ ctmp->help = h_config_role_arbpat;
+ ctmp->tool = addhdr_tool;
+ ctmp->d.earb = &earb;
+ ctmp->varmem = -1;
+
+ setup_role_pat(ps, &ctmp, &alltext_pat_var, h_config_role_alltextpat,
+ _("HELP FOR ALL TEXT PATTERN"),
+ &config_role_keymenu_not, t_tool, &earb, pindent);
+
+ setup_role_pat(ps, &ctmp, &bodytext_pat_var, h_config_role_bodytextpat,
+ _("HELP FOR BODY TEXT PATTERN"),
+ &config_role_keymenu_not, t_tool, &earb, pindent);
+
+ /* Age Interval */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR AGE INTERVAL");
+ ctmp->var = &age_pat_var;
+ ctmp->valoffset = pindent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_role_age;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", pindent-3, pindent-3, age_pat_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Size Interval */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SIZE INTERVAL");
+ ctmp->var = &size_pat_var;
+ ctmp->valoffset = pindent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_role_size;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", pindent-3, pindent-3, size_pat_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ if(!edit_score){
+ /* Score Interval */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SCORE INTERVAL");
+ ctmp->var = &scorei_pat_var;
+ ctmp->valoffset = pindent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_role_scorei;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", pindent-3, pindent-3, scorei_pat_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ }
+
+ /* Keyword Pattern */
+ setup_role_pat(ps, &ctmp, &keyword_pat_var, h_config_role_keywordpat,
+ _("HELP FOR KEYWORD PATTERN"),
+ &config_role_keyword_keymenu_not, role_text_tool_kword,
+ NULL, pindent);
+
+ /* Charset Pattern */
+ setup_role_pat(ps, &ctmp, &charset_pat_var, h_config_role_charsetpat,
+ _("HELP FOR CHARACTER SET PATTERN"),
+ &config_role_charset_keymenu_not, role_text_tool_charset,
+ NULL, pindent);
+
+ /* Important Status */
+ SETUP_PAT_STATUS(ctmp, stat_imp_var, def->patgrp->stat_imp,
+ _("HELP FOR IMPORTANT STATUS"), h_config_role_stat_imp);
+ /* New Status */
+ SETUP_PAT_STATUS(ctmp, stat_new_var, def->patgrp->stat_new,
+ _("HELP FOR NEW STATUS"), h_config_role_stat_new);
+ /* Recent Status */
+ SETUP_PAT_STATUS(ctmp, stat_rec_var, def->patgrp->stat_rec,
+ _("HELP FOR RECENT STATUS"), h_config_role_stat_recent);
+ /* Deleted Status */
+ SETUP_PAT_STATUS(ctmp, stat_del_var, def->patgrp->stat_del,
+ _("HELP FOR DELETED STATUS"), h_config_role_stat_del);
+ /* Answered Status */
+ SETUP_PAT_STATUS(ctmp, stat_ans_var, def->patgrp->stat_ans,
+ _("HELP FOR ANSWERED STATUS"), h_config_role_stat_ans);
+ /* 8-bit Subject */
+ SETUP_PAT_STATUS(ctmp, stat_8bit_var, def->patgrp->stat_8bitsubj,
+ _("HELP FOR 8-BIT SUBJECT"), h_config_role_stat_8bitsubj);
+ /* Beginning of month */
+ SETUP_PAT_STATUS(ctmp, stat_bom_var, def->patgrp->stat_bom,
+ _("HELP FOR BEGINNING OF MONTH"), h_config_role_bom);
+ /* Beginning of year */
+ SETUP_PAT_STATUS(ctmp, stat_boy_var, def->patgrp->stat_boy,
+ _("HELP FOR BEGINNING OF YEAR"), h_config_role_boy);
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* From in Addrbook */
+ new_confline(&ctmp);
+ ctmp->var = &abook_type_var;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", abook_type_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Choose One");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose); \
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = inabook_fldr_types(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ fval = -1;
+ for(i = 0; (f = inabook_fldr_types(i)); i++){
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR ADDRESS IN ADDRESS BOOK");
+ ctmp->var = &abook_type_var;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_role_abookfrom;
+ ctmp->varmem = i;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+
+ if((PVAL(&abook_type_var, ew) &&
+ !strucmp(PVAL(&abook_type_var, ew), f->name))
+ || (!PVAL(&abook_type_var, ew) && f->value == IAB_DEFL))
+ fval = f->value;
+
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w",
+ (fval == f->value) ? R_SELD : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Specific list of abooks */
+ /* 5 is the width of `(*) ' */
+ setup_role_pat_alt(ps, &ctmp, &abook_pat_var, h_config_role_abookfrom,
+ _("HELP FOR ABOOK LIST"),
+ &config_role_afrom_keymenu, role_text_tool_afrom, rindent+5,
+ !(fval == IAB_SPEC_YES || fval == IAB_SPEC_NO));
+
+ /* Which addresses to check for */
+ inabook_type_var.name = cpystr(_("Types of addresses to check for in address book"));
+ inabook_type_var.is_used = 1;
+ inabook_type_var.is_user = 1;
+ inabook_type_var.is_list = 1;
+ clrbitmap(inabook_type_list);
+ if(def && def->patgrp && def->patgrp->inabook & IAB_FROM)
+ setbitn(INABOOK_FROM, inabook_type_list);
+ if(def && def->patgrp && def->patgrp->inabook & IAB_REPLYTO)
+ setbitn(INABOOK_REPLYTO, inabook_type_list);
+ if(def && def->patgrp && def->patgrp->inabook & IAB_SENDER)
+ setbitn(INABOOK_SENDER, inabook_type_list);
+ if(def && def->patgrp && def->patgrp->inabook & IAB_TO)
+ setbitn(INABOOK_TO, inabook_type_list);
+ if(def && def->patgrp && def->patgrp->inabook & IAB_CC)
+ setbitn(INABOOK_CC, inabook_type_list);
+
+ /* default setting */
+ if(!(bitnset(INABOOK_FROM, inabook_type_list)
+ || bitnset(INABOOK_REPLYTO, inabook_type_list)
+ || bitnset(INABOOK_SENDER, inabook_type_list)
+ || bitnset(INABOOK_TO, inabook_type_list)
+ || bitnset(INABOOK_CC, inabook_type_list))){
+ setbitn(INABOOK_FROM, inabook_type_list);
+ setbitn(INABOOK_REPLYTO, inabook_type_list);
+ }
+
+ new_confline(&ctmp);
+ ctmp->var = &inabook_type_var;
+ ctmp->varoffset = 4;
+ ctmp->valoffset = 23;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%-20s =", inabook_type_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = inabook_checkbox_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Address types");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = inabook_checkbox_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose);
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = inabook_feature_list(i)); i++){
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+ }
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = inabook_feature_list(i)); i++){
+ new_confline(&ctmp);
+ ctmp->var = &opt_var;
+ ctmp->help_title= _("HELP FOR ADDRESS TYPES");
+ ctmp->varnamep = ctmpb;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ switch(i){
+ case INABOOK_FROM:
+ ctmp->help = h_config_inabook_from;
+ break;
+ case INABOOK_REPLYTO:
+ ctmp->help = h_config_inabook_replyto;
+ break;
+ case INABOOK_SENDER:
+ ctmp->help = h_config_inabook_sender;
+ break;
+ case INABOOK_TO:
+ ctmp->help = h_config_inabook_to;
+ break;
+ case INABOOK_CC:
+ ctmp->help = h_config_inabook_cc;
+ break;
+ }
+
+ ctmp->tool = inabook_checkbox_tool;
+ ctmp->valoffset = rindent;
+ ctmp->varmem = i;
+ utf8_snprintf(tmp, sizeof(tmp), "[%c] %-*.*w",
+ bitnset(f->value, inabook_type_list) ? 'X' : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* 4 is indent of "Command", c_v_n is longest of these three, 3 is ` = ' */
+ i = 4 + utf8_width(c_v_n) + 3;
+
+ /* External Command Categorizer */
+ new_confline(&ctmp);
+ ctmp->var = &cat_cmd_var;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", cat_cmd_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ /* Commands */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR CATEGORIZER COMMAND");
+ ctmp->var = &cat_cmd_var;
+ ctmp->varoffset = 4;
+ ctmp->valoffset = i;
+ ctmp->keymenu = &config_text_wshuf_keymenu;
+ ctmp->help = h_config_role_cat_cmd;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", i-4-3, i-4-3, "Command");
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags = CF_STARTITEM;
+
+ if((lval = LVAL(&cat_cmd_var, ew)) != NULL && lval[0]){
+ for(j = 0; lval[j]; j++){
+ if(j)
+ (void) new_confline(&ctmp);
+
+ ctmp->var = &cat_cmd_var;
+ ctmp->varmem = j;
+ ctmp->valoffset = i;
+ ctmp->keymenu = &config_text_wshuf_keymenu;
+ ctmp->help = h_config_role_cat_cmd;
+ ctmp->tool = t_tool;
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ }
+ }
+ else
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Exit status interval */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR CATEGORIZER EXIT STATUS");
+ ctmp->var = &cati_var;
+ ctmp->varoffset = 4;
+ ctmp->valoffset = i;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_role_cat_status;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", i-4-3, i-4-3, cati_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Character Limit */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR CHARACTER LIMIT");
+ ctmp->var = &cat_lim_var;
+ ctmp->varoffset = 4;
+ ctmp->valoffset = i;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_role_cat_limit;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", i-4-3, i-4-3, cat_lim_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ ctmp->flags |= CF_NUMBER;
+ }
+
+ if(!edit_srch){ /* sorry about that indent */
+ /* Actions */
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ if(ps->ttyo->screen_cols >= (wid=utf8_width(astr)) + 4){
+ int dashes;
+
+ dashes = (ps->ttyo->screen_cols - wid)/2;
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s", repeat_char(dashes, '='),
+ astr, repeat_char(ps->ttyo->screen_cols-wid-dashes, '='));
+ ctmp->value = cpystr(tmp_20k_buf);
+ }
+ else
+ ctmp->value = cpystr(repeat_char(ps->ttyo->screen_cols, '='));
+
+ if(edit_role){
+ int roindent;
+
+ roindent = utf8_width(u_s_s); /* the longest one */
+ roindent += 3; /* width of ` = ' */
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Inherit Nickname */
+ new_confline(&ctmp);
+ inick_confs[INICK_INICK_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR INITIAL SET NICKNAME");
+ ctmp->var = &inick_var;
+ ctmp->keymenu = &config_role_inick_keymenu;
+ ctmp->help = h_config_role_inick;
+ ctmp->tool = role_text_tool_inick;
+ snprintf(tmp, sizeof(tmp), "%s :", inick_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->valoffset = utf8_width(tmp)+1;
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+
+ /* From Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_FROM_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR SET FROM ACTION");
+ ctmp->var = &from_act_var;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_role_addr_act_keymenu;
+ ctmp->help = h_config_role_setfrom;
+ ctmp->tool = role_text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, from_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+
+ /* Reply-To Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_REPLYTO_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR SET REPLY-TO ACTION");
+ ctmp->var = &replyto_act_var;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_role_addr_act_keymenu;
+ ctmp->help = h_config_role_setreplyto;
+ ctmp->tool = role_text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, replyto_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+
+ /* Fcc Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_FCC_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR SET FCC ACTION");
+ ctmp->var = &fcc_act_var;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_role_actionfolder_keymenu;
+ ctmp->help = h_config_role_setfcc;
+ ctmp->tool = role_text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, fcc_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+
+ /* LitSig Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_LITSIG_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR SET LITERAL SIGNATURE ACTION");
+ ctmp->var = &litsig_act_var;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_role_setlitsig;
+ ctmp->tool = role_litsig_text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, litsig_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+
+ /* Sig Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_SIG_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR SET SIGNATURE ACTION");
+ ctmp->var = &sig_act_var;
+ ctmp->valoffset = roindent;
+ if(F_ON(F_DISABLE_ROLES_SIGEDIT, ps_global))
+ ctmp->keymenu = &config_role_file_res_keymenu;
+ else
+ ctmp->keymenu = &config_role_file_keymenu;
+
+ ctmp->help = h_config_role_setsig;
+ ctmp->tool = role_text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, sig_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+
+ /* Template Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_TEMPL_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR SET TEMPLATE ACTION");
+ ctmp->var = &templ_act_var;
+ ctmp->valoffset = roindent;
+ if(F_ON(F_DISABLE_ROLES_TEMPLEDIT, ps_global))
+ ctmp->keymenu = &config_role_file_res_keymenu;
+ else
+ ctmp->keymenu = &config_role_file_keymenu;
+
+ ctmp->help = h_config_role_settempl;
+ ctmp->tool = role_text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, templ_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+
+ /* Other Headers Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_CSTM_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR SET OTHER HEADERS ACTION");
+ ctmp->var = &cstm_act_var;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_text_wshuf_keymenu;
+ ctmp->help = h_config_role_setotherhdr;
+ ctmp->tool = role_cstm_text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, cstm_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags = CF_STARTITEM;
+
+ if((lval = LVAL(&cstm_act_var, ew)) != NULL){
+ for(i = 0; lval[i]; i++){
+ if(i)
+ (void)new_confline(&ctmp);
+
+ ctmp->var = &cstm_act_var;
+ ctmp->varmem = i;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_text_wshuf_keymenu;
+ ctmp->help = h_config_role_setotherhdr;
+ ctmp->tool = role_cstm_text_tool;
+ ctmp->varnamep = ctmpb;
+ }
+ }
+ else
+ ctmp->varmem = 0;
+
+ /* SMTP Server Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_SMTP_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR SMTP SERVER ACTION");
+ ctmp->var = &smtp_act_var;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_text_wshuf_keymenu;
+ ctmp->help = h_config_role_usesmtp;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, smtp_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags = CF_STARTITEM;
+
+ if((lval = LVAL(&smtp_act_var, ew)) != NULL){
+ for(i = 0; lval[i]; i++){
+ if(i)
+ (void)new_confline(&ctmp);
+
+ ctmp->var = &smtp_act_var;
+ ctmp->varmem = i;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_text_wshuf_keymenu;
+ ctmp->help = h_config_role_usesmtp;
+ ctmp->tool = t_tool;
+ ctmp->varnamep = ctmpb;
+ }
+ }
+ else
+ ctmp->varmem = 0;
+
+ /* NNTP Server Action */
+ new_confline(&ctmp);
+ inick_confs[INICK_NNTP_CONF] = ctmp;
+ ctmp->help_title= _("HELP FOR NNTP SERVER ACTION");
+ ctmp->var = &nntp_act_var;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_text_wshuf_keymenu;
+ ctmp->help = h_config_role_usenntp;
+ ctmp->tool = t_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", roindent-3, roindent-3, nntp_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags = CF_STARTITEM;
+
+ if((lval = LVAL(&nntp_act_var, ew)) != NULL){
+ for(i = 0; lval[i]; i++){
+ if(i)
+ (void)new_confline(&ctmp);
+
+ ctmp->var = &nntp_act_var;
+ ctmp->varmem = i;
+ ctmp->valoffset = roindent;
+ ctmp->keymenu = &config_text_wshuf_keymenu;
+ ctmp->help = h_config_role_usenntp;
+ ctmp->tool = t_tool;
+ ctmp->varnamep = ctmpb;
+ }
+ }
+ else
+ ctmp->varmem = 0;
+
+ calculate_inick_stuff(ps);
+ }
+ else
+ inick_confs[INICK_INICK_CONF] = NULL;
+
+ if(edit_score){
+ int sindent;
+
+ sindent = MAX(utf8_width(score_act_var.name),utf8_width(hdrtok_act_var.name)) + 3;
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Score Value -- This doesn't inherit from inick */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SCORE VALUE ACTION");
+ ctmp->var = &score_act_var;
+ ctmp->valoffset = sindent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_role_scoreval;
+ ctmp->tool = text_tool;
+ ctmp->flags |= CF_NUMBER;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", sindent-3, sindent-3, score_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(" OR");
+
+ /* Score From Header */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SCORE VALUE FROM HEADER ACTION");
+ ctmp->var = &hdrtok_act_var;
+ ctmp->valoffset = sindent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_role_scorehdrtok;
+ ctmp->tool = text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", sindent-3, sindent-3, hdrtok_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+ }
+
+ if(edit_filter){
+ /*
+ * Filtering got added in stages, so instead of simply having a
+ * variable in action which is set to one of the three possible
+ * values (FILTER_KILL, FILTER_STATE, FILTER_FOLDER) we infer
+ * the value from other variables. (Perhaps it would still make
+ * sense to change this.)
+ * Action->kill is set iff the user checks Delete.
+ * If the user checks the box that says Just Set State, then kill
+ * is not set and action->folder is not set (and vice versa).
+ * And finally, FILTER_FOLDER is set if !kill and action->folder is set.
+ * (And it is set here as the default if there is no default
+ * action and the user is required to fill in the Folder.)
+ */
+ if(def && def->action && def->action->kill)
+ fval = FILTER_KILL;
+ else if(def && def->action && !def->action->kill &&
+ !def->action->folder)
+ fval = FILTER_STATE;
+ else
+ fval = FILTER_FOLDER;
+
+ role_filt_ptr = &filter_type_var; /* so radiobuttons can tell */
+ filter_type_var.name = cpystr(_("Filter Action"));
+ filter_type_var.is_used = 1;
+ filter_type_var.is_user = 1;
+ apval = APVAL(&filter_type_var, ew);
+ *apval = (f=filter_types(fval)) ? cpystr(f->name) : NULL;
+ set_current_val(&filter_type_var, FALSE, FALSE);
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Filter Type */
+ new_confline(&ctmp);
+ ctmp->var = &filter_type_var;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", filter_type_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Choose One");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose); \
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = filter_types(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = filter_types(i)); i++){
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR FILTER ACTION");
+ ctmp->var = &filter_type_var;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_filt_rule_type;
+ ctmp->varmem = i;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w", (f->value == fval) ? R_SELD : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Specific list of folders to copy to */
+ setup_dummy_pattern_var(&folder_act_var, _("Folder List"),
+ (def && def->action)
+ ? def->action->folder : NULL);
+
+ /* 5 is the width of `(*) ' */
+ setup_role_pat_alt(ps, &ctmp, &folder_act_var, h_config_filter_folder,
+ _("HELP FOR FILTER FOLDER NAME"),
+ &config_role_actionfolder_keymenu, t_tool, rindent+5,
+ !(fval == FILTER_FOLDER));
+
+ SETUP_MSG_STATE(ctmp, filt_imp_var, ival,
+ _("HELP FOR SET IMPORTANT STATUS"), h_config_filt_stat_imp);
+ SETUP_MSG_STATE(ctmp, filt_new_var, nval,
+ _("HELP FOR SET NEW STATUS"), h_config_filt_stat_new);
+ SETUP_MSG_STATE(ctmp, filt_del_var, dval,
+ _("HELP FOR SET DELETED STATUS"), h_config_filt_stat_del);
+ SETUP_MSG_STATE(ctmp, filt_ans_var, aval,
+ _("HELP FOR SET ANSWERED STATUS"), h_config_filt_stat_ans);
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Keywords to be Set */
+ setup_dummy_pattern_var(&keyword_set_var, _("Set These Keywords"),
+ (def && def->action)
+ ? def->action->keyword_set : NULL);
+ setup_role_pat(ps, &ctmp, &keyword_set_var, h_config_filter_kw_set,
+ _("HELP FOR KEYWORDS TO BE SET"),
+ &config_role_keyword_keymenu, role_text_tool_kword,
+ NULL, 23);
+
+ /* Keywords to be Cleared */
+ setup_dummy_pattern_var(&keyword_clr_var, _("Clear These Keywords"),
+ (def && def->action)
+ ? def->action->keyword_clr : NULL);
+ setup_role_pat(ps, &ctmp, &keyword_clr_var, h_config_filter_kw_clr,
+ _("HELP FOR KEYWORDS TO BE CLEARED"),
+ &config_role_keyword_keymenu, role_text_tool_kword,
+ NULL, 23);
+ }
+
+ if(edit_other){
+ char *pval;
+ int oindent;
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmp)->var = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", sort_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = role_sort_tool;
+ ctmp->valoffset = rindent;
+ ctmp->flags |= CF_NOSELECT;
+
+ new_confline(&ctmp)->var = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = role_sort_tool;
+ ctmp->valoffset = rindent;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Sort Options");
+
+ new_confline(&ctmp)->var = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = role_sort_tool;
+ ctmp->valoffset = rindent;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose); \
+
+ pval = PVAL(&sort_act_var, ew);
+ if(pval)
+ decode_sort(pval, &def_sort, &def_sort_rev);
+
+ /* allow user to set their default sort order */
+ new_confline(&ctmp)->var = &sort_act_var;
+ ctmp->varnamep = ctmpb;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_perfolder_sort;
+ ctmp->tool = role_sort_tool;
+ ctmp->valoffset = rindent;
+ ctmp->varmem = -1;
+ ctmp->value = generalized_sort_pretty_value(ps, ctmp, 0);
+
+ for(j = 0; j < 2; j++){
+ for(i = 0; ps->sort_types[i] != EndofList; i++){
+ new_confline(&ctmp)->var = &sort_act_var;
+ ctmp->varnamep = ctmpb;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_perfolder_sort;
+ ctmp->tool = role_sort_tool;
+ ctmp->valoffset = rindent;
+ ctmp->varmem = i + (j * EndofList);
+ ctmp->value = generalized_sort_pretty_value(ps, ctmp,
+ 0);
+ }
+ }
+
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ oindent = utf8_width(iform_act_var.name) + 3;
+
+ /* Index Format Action */
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR SET INDEX FORMAT ACTION");
+ ctmp->var = &iform_act_var;
+ ctmp->valoffset = oindent;
+ ctmp->keymenu = &config_text_keymenu;
+ ctmp->help = h_config_set_index_format;
+ ctmp->tool = text_tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", oindent-3, oindent-3, iform_act_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmp;
+ ctmp->value = pretty_value(ps, ctmp);
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_STARTITEM;
+ utf8_snprintf(tmp, sizeof(tmp), "%s =", startup_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ standard_radio_setup(ps, &ctmp, &startup_var, NULL);
+ }
+
+ if(edit_incol && pico_usingcolor()){
+ char *pval0, *pval1;
+ int def;
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmp);
+ ctmp->var = &rolecolor_vars[0]; /* foreground */
+ ctmp->varname = cpystr(_("Index Line Color ="));
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_STARTITEM | CF_NOSELECT);
+ ctmp->keymenu = &role_color_setting_keymenu;
+
+ pval0 = PVAL(&rolecolor_vars[0], ew);
+ pval1 = PVAL(&rolecolor_vars[1], ew);
+ if(pval0 && pval1)
+ def = !(pval0[0] && pval1[1]);
+ else
+ def = 1;
+
+ add_color_setting_disp(ps, &ctmp, &rolecolor_vars[0], ctmpb,
+ &role_color_setting_keymenu,
+ &config_checkbox_keymenu,
+ h_config_incol,
+ rindent, 0,
+ def ? ps->VAR_NORM_FORE_COLOR
+ : PVAL(&rolecolor_vars[0], ew),
+ def ? ps->VAR_NORM_BACK_COLOR
+ : PVAL(&rolecolor_vars[1], ew),
+ def);
+ }
+ }
+
+ if(need_options){
+ /* Options */
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ if(ps->ttyo->screen_cols >= (wid=utf8_width(ostr)) + 4){
+ int dashes;
+
+ dashes = (ps->ttyo->screen_cols - wid)/2;
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s", repeat_char(dashes, '='),
+ ostr, repeat_char(ps->ttyo->screen_cols-wid-dashes, '='));
+ ctmp->value = cpystr(tmp_20k_buf);
+ }
+ else
+ ctmp->value = cpystr(repeat_char(ps->ttyo->screen_cols, '='));
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ opt_var.name = cpystr(_("Features"));
+ opt_var.is_used = 1;
+ opt_var.is_user = 1;
+ opt_var.is_list = 1;
+ clrbitmap(feat_option_list);
+ if(def && def->patgrp && def->patgrp->age_uses_sentdate)
+ setbitn(FEAT_SENTDATE, feat_option_list);
+
+ if(edit_filter){
+ if(def && def->action && def->action->move_only_if_not_deleted)
+ setbitn(FEAT_IFNOTDEL, feat_option_list);
+ if(def && def->action && def->action->non_terminating)
+ setbitn(FEAT_NONTERM, feat_option_list);
+ }
+
+ /* Options */
+ new_confline(&ctmp);
+ ctmp->var = &opt_var;
+ ctmp->valoffset = 23;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%-20s =", opt_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = feat_checkbox_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Feature Name");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = feat_checkbox_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose); \
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = feat_feature_list(i)); i++){
+ if(!edit_filter && (i == FEAT_IFNOTDEL || i == FEAT_NONTERM))
+ continue;
+
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+ }
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = feat_feature_list(i)); i++){
+ if(!edit_filter && (i == FEAT_IFNOTDEL || i == FEAT_NONTERM))
+ continue;
+
+ new_confline(&ctmp);
+ ctmp->var = &opt_var;
+ ctmp->help_title= _("HELP FOR FILTER FEATURES");
+ ctmp->varnamep = ctmpb;
+ ctmp->keymenu = &config_checkbox_keymenu;
+ switch(i){
+ case FEAT_SENTDATE:
+ ctmp->help = h_config_filt_opts_sentdate;
+ break;
+ case FEAT_IFNOTDEL:
+ ctmp->help = h_config_filt_opts_notdel;
+ break;
+ case FEAT_NONTERM:
+ ctmp->help = h_config_filt_opts_nonterm;
+ break;
+ }
+
+ ctmp->tool = feat_checkbox_tool;
+ ctmp->valoffset = rindent;
+ ctmp->varmem = i;
+ utf8_snprintf(tmp, sizeof(tmp), "[%c] %-*.*w",
+ bitnset(f->value, feat_option_list) ? 'X' : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+ }
+
+ if(need_uses){
+ /* Uses */
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ if(ps->ttyo->screen_cols >= (wid=utf8_width(ustr)) + 4){
+ int dashes;
+
+ dashes = (ps->ttyo->screen_cols - wid)/2;
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%s", repeat_char(dashes, '='),
+ ustr, repeat_char(ps->ttyo->screen_cols-wid-dashes, '='));
+ ctmp->value = cpystr(tmp_20k_buf);
+ }
+ else
+ ctmp->value = cpystr(repeat_char(ps->ttyo->screen_cols, '='));
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Reply Type */
+ new_confline(&ctmp);
+ ctmp->var = &repl_type_var;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", repl_type_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Choose One");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose); \
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = role_repl_types(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = role_repl_types(i)); i++){
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR ROLE REPLY USE");
+ ctmp->var = &repl_type_var;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_role_replyuse;
+ ctmp->varmem = i;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w", (((!(def && def->action) ||
+ def->action->repl_type == -1) &&
+ f->value == ROLE_REPL_DEFL) ||
+ (def && def->action &&
+ f->value == def->action->repl_type))
+ ? R_SELD : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Forward Type */
+ new_confline(&ctmp);
+ ctmp->var = &forw_type_var;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", forw_type_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Choose One");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose); \
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = role_forw_types(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = role_forw_types(i)); i++){
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR ROLE FORWARD USE");
+ ctmp->var = &forw_type_var;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_role_forwarduse;
+ ctmp->varmem = i;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w", (((!(def && def->action) ||
+ def->action->forw_type == -1) &&
+ f->value == ROLE_FORW_DEFL) ||
+ (def && def->action &&
+ f->value == def->action->forw_type))
+ ? R_SELD : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+
+ /* Blank line */
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* Compose Type */
+ new_confline(&ctmp);
+ ctmp->var = &comp_type_var;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ snprintf(tmp, sizeof(tmp), "%s =", comp_type_var.name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->varname = cpystr(tmp);
+ ctmp->varnamep = ctmpb = ctmp;
+ ctmp->flags |= (CF_NOSELECT | CF_STARTITEM);
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = NULL;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr("Set Choose One");
+
+ new_confline(&ctmp);
+ ctmp->var = NULL;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = NO_HELP;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(set_choose); \
+
+ /* find longest value's name */
+ for(lv = 0, i = 0; (f = role_comp_types(i)); i++)
+ if(lv < (j = utf8_width(f->name)))
+ lv = j;
+
+ lv = MIN(lv, 100);
+
+ for(i = 0; (f = role_comp_types(i)); i++){
+ new_confline(&ctmp);
+ ctmp->help_title= _("HELP FOR ROLE COMPOSE USE");
+ ctmp->var = &comp_type_var;
+ ctmp->valoffset = rindent;
+ ctmp->keymenu = &config_radiobutton_keymenu;
+ ctmp->help = h_config_role_composeuse;
+ ctmp->varmem = i;
+ ctmp->tool = radio_tool;
+ ctmp->varnamep = ctmpb;
+ utf8_snprintf(tmp, sizeof(tmp), "(%c) %-*.*w", (((!(def && def->action) ||
+ def->action->comp_type == -1) &&
+ f->value == ROLE_COMP_DEFL) ||
+ (def && def->action &&
+ f->value == def->action->comp_type))
+ ? R_SELD : ' ',
+ lv, lv, f->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmp->value = cpystr(tmp);
+ }
+ }
+
+ memset(&screen, 0, sizeof(screen));
+ screen.ro_warning = saved_screen ? saved_screen->deferred_ro_warning : 0;
+ /* TRANSLATORS: Print something1 using something2.
+ "roles" is something1 */
+ rv = conf_scroll_screen(ps, &screen, first_line, title, _("roles"),
+ (edit_incol && pico_usingcolor()) ? 1 : 0);
+
+ /*
+ * Now look at the fake variables and extract the information we
+ * want from them.
+ */
+
+ if(rv == 1 && result){
+ /*
+ * We know these variables exist, so we don't have to check that
+ * apval is nonnull before evaluating *apval.
+ */
+ apval = APVAL(&nick_var, ew);
+ nick = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(nick);
+
+ apval = APVAL(&comment_var, ew);
+ comment = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(comment);
+
+ alval = ALVAL(&to_pat_var, ew);
+ to_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&from_pat_var, ew);
+ from_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&sender_pat_var, ew);
+ sender_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&cc_pat_var, ew);
+ cc_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&recip_pat_var, ew);
+ recip_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&partic_pat_var, ew);
+ partic_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&news_pat_var, ew);
+ news_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&subj_pat_var, ew);
+ subj_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&alltext_pat_var, ew);
+ alltext_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&bodytext_pat_var, ew);
+ bodytext_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&keyword_pat_var, ew);
+ keyword_pat = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&charset_pat_var, ew);
+ charset_pat = *alval;
+ *alval = NULL;
+
+ apval = APVAL(&age_pat_var, ew);
+ age_pat = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(age_pat);
+
+ apval = APVAL(&size_pat_var, ew);
+ size_pat = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(size_pat);
+
+ apval = APVAL(&scorei_pat_var, ew);
+ scorei_pat = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(scorei_pat);
+
+ apval = APVAL(&stat_del_var, ew);
+ stat_del = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(stat_del);
+
+ apval = APVAL(&stat_new_var, ew);
+ stat_new = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(stat_new);
+
+ apval = APVAL(&stat_rec_var, ew);
+ stat_rec = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(stat_rec);
+
+ apval = APVAL(&stat_imp_var, ew);
+ stat_imp = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(stat_imp);
+
+ apval = APVAL(&stat_ans_var, ew);
+ stat_ans = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(stat_ans);
+
+ apval = APVAL(&stat_8bit_var, ew);
+ stat_8bit = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(stat_8bit);
+
+ apval = APVAL(&stat_bom_var, ew);
+ stat_bom = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(stat_bom);
+
+ apval = APVAL(&stat_boy_var, ew);
+ stat_boy = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(stat_boy);
+
+ apval = APVAL(&fldr_type_var, ew);
+ fldr_type_pat = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(fldr_type_pat);
+
+ alval = ALVAL(&folder_pat_var, ew);
+ folder_pat = *alval;
+ *alval = NULL;
+
+ apval = APVAL(&abook_type_var, ew);
+ abook_type_pat = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(abook_type_pat);
+
+ alval = ALVAL(&abook_pat_var, ew);
+ abook_pat = *alval;
+ *alval = NULL;
+
+ apval = APVAL(&cati_var, ew);
+ cati = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(cati);
+
+ apval = APVAL(&cat_lim_var, ew);
+ cat_lim = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(cat_lim);
+
+ apval = APVAL(&inick_var, ew);
+ inick = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(inick);
+
+ apval = APVAL(&from_act_var, ew);
+ from_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(from_act);
+
+ apval = APVAL(&replyto_act_var, ew);
+ replyto_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(replyto_act);
+
+ apval = APVAL(&fcc_act_var, ew);
+ fcc_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(fcc_act);
+
+ apval = APVAL(&litsig_act_var, ew);
+ litsig_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(litsig_act);
+
+ apval = APVAL(&sort_act_var, ew);
+ sort_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(sort_act);
+
+ apval = APVAL(&iform_act_var, ew);
+ iform_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(iform_act);
+
+ apval = APVAL(&startup_var, ew);
+ startup_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(startup_act);
+
+ apval = APVAL(&sig_act_var, ew);
+ sig_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(sig_act);
+
+ apval = APVAL(&templ_act_var, ew);
+ templ_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(templ_act);
+
+ apval = APVAL(&score_act_var, ew);
+ score_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(score_act);
+
+ apval = APVAL(&hdrtok_act_var, ew);
+ hdrtok_act = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(hdrtok_act);
+
+ apval = APVAL(&repl_type_var, ew);
+ repl_type = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(repl_type);
+
+ apval = APVAL(&forw_type_var, ew);
+ forw_type = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(forw_type);
+
+ apval = APVAL(&comp_type_var, ew);
+ comp_type = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(comp_type);
+
+ apval = APVAL(&rolecolor_vars[0], ew);
+ rc_fg = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(rc_fg);
+
+ apval = APVAL(&rolecolor_vars[1], ew);
+ rc_bg = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(rc_bg);
+
+ apval = APVAL(&filter_type_var, ew);
+ filter_type = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(filter_type);
+
+ alval = ALVAL(&folder_act_var, ew);
+ folder_act = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&keyword_set_var, ew);
+ keyword_set = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&keyword_clr_var, ew);
+ keyword_clr = *alval;
+ *alval = NULL;
+
+ apval = APVAL(&filt_imp_var, ew);
+ filt_imp = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(filt_imp);
+
+ apval = APVAL(&filt_del_var, ew);
+ filt_del = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(filt_del);
+
+ apval = APVAL(&filt_new_var, ew);
+ filt_new = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(filt_new);
+
+ apval = APVAL(&filt_ans_var, ew);
+ filt_ans = *apval;
+ *apval = NULL;
+ removing_leading_and_trailing_white_space(filt_ans);
+
+
+ alval = ALVAL(&cat_cmd_var, ew);
+ cat_cmd = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&cstm_act_var, ew);
+ cstm_act = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&smtp_act_var, ew);
+ smtp_act = *alval;
+ *alval = NULL;
+
+ alval = ALVAL(&nntp_act_var, ew);
+ nntp_act = *alval;
+ *alval = NULL;
+
+ if(ps->VAR_OPER_DIR && sig_act &&
+ is_absolute_path(sig_act) &&
+ !in_dir(ps->VAR_OPER_DIR, sig_act)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Warning: Sig file can't be outside of %s"),
+ ps->VAR_OPER_DIR);
+ }
+
+ if(ps->VAR_OPER_DIR && templ_act &&
+ is_absolute_path(templ_act) &&
+ !in_dir(ps->VAR_OPER_DIR, templ_act)){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Warning: Template file can't be outside of %s"),
+ ps->VAR_OPER_DIR);
+ }
+
+ if(ps->VAR_OPER_DIR && folder_act)
+ for(i = 0; folder_act[i]; i++){
+ if(folder_act[i][0] && is_absolute_path(folder_act[i]) &&
+ !in_dir(ps->VAR_OPER_DIR, folder_act[i])){
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Warning: Folder can't be outside of %s"),
+ ps->VAR_OPER_DIR);
+ }
+ }
+
+ *result = (PAT_S *)fs_get(sizeof(**result));
+ memset((void *)(*result), 0, sizeof(**result));
+
+ (*result)->patgrp = (PATGRP_S *)fs_get(sizeof(*(*result)->patgrp));
+ memset((void *)(*result)->patgrp, 0, sizeof(*(*result)->patgrp));
+
+ (*result)->action = (ACTION_S *)fs_get(sizeof(*(*result)->action));
+ memset((void *)(*result)->action, 0, sizeof(*(*result)->action));
+
+ (*result)->patline = def ? def->patline : NULL;
+
+ if(nick && *nick){
+ (*result)->patgrp->nick = nick;
+ nick = NULL;
+ }
+ else
+ (*result)->patgrp->nick = cpystr(nick_var.global_val.p);
+
+ if(comment && *comment){
+ (*result)->patgrp->comment = comment;
+ comment = NULL;
+ }
+
+ (*result)->action->nick = cpystr((*result)->patgrp->nick);
+
+ (*result)->action->is_a_role = edit_role ? 1 : 0;
+ (*result)->action->is_a_incol = edit_incol ? 1 : 0;
+ (*result)->action->is_a_score = edit_score ? 1 : 0;
+ (*result)->action->is_a_filter = edit_filter ? 1 : 0;
+ (*result)->action->is_a_other = edit_other ? 1 : 0;
+ (*result)->action->is_a_srch = edit_srch ? 1 : 0;
+
+ (*result)->patgrp->to = editlist_to_pattern(to_pat);
+ if((*result)->patgrp->to && !strncmp(to_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->to->not = 1;
+
+ (*result)->patgrp->from = editlist_to_pattern(from_pat);
+ if((*result)->patgrp->from && !strncmp(from_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->from->not = 1;
+
+ (*result)->patgrp->sender = editlist_to_pattern(sender_pat);
+ if((*result)->patgrp->sender &&
+ !strncmp(sender_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->sender->not = 1;
+
+ (*result)->patgrp->cc = editlist_to_pattern(cc_pat);
+ if((*result)->patgrp->cc && !strncmp(cc_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->cc->not = 1;
+
+ (*result)->patgrp->recip = editlist_to_pattern(recip_pat);
+ if((*result)->patgrp->recip &&
+ !strncmp(recip_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->recip->not = 1;
+
+ (*result)->patgrp->partic = editlist_to_pattern(partic_pat);
+ if((*result)->patgrp->partic &&
+ !strncmp(partic_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->partic->not = 1;
+
+ (*result)->patgrp->news = editlist_to_pattern(news_pat);
+ if((*result)->patgrp->news && !strncmp(news_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->news->not = 1;
+
+ (*result)->patgrp->subj = editlist_to_pattern(subj_pat);
+ if((*result)->patgrp->subj && !strncmp(subj_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->subj->not = 1;
+
+ (*result)->patgrp->alltext = editlist_to_pattern(alltext_pat);
+ if((*result)->patgrp->alltext &&
+ !strncmp(alltext_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->alltext->not = 1;
+
+ (*result)->patgrp->bodytext = editlist_to_pattern(bodytext_pat);
+ if((*result)->patgrp->bodytext &&
+ !strncmp(bodytext_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->bodytext->not = 1;
+
+ (*result)->patgrp->keyword = editlist_to_pattern(keyword_pat);
+ if((*result)->patgrp->keyword &&
+ !strncmp(keyword_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->keyword->not = 1;
+
+ (*result)->patgrp->charsets = editlist_to_pattern(charset_pat);
+ if((*result)->patgrp->charsets &&
+ !strncmp(charset_pat_var.name, NOT, NOTLEN))
+ (*result)->patgrp->charsets->not = 1;
+
+ (*result)->patgrp->age_uses_sentdate =
+ bitnset(FEAT_SENTDATE, feat_option_list) ? 1 : 0;
+
+ if(age_pat){
+ if(((*result)->patgrp->age = parse_intvl(age_pat)) != NULL)
+ (*result)->patgrp->do_age = 1;
+ }
+
+ if(size_pat){
+ if(((*result)->patgrp->size = parse_intvl(size_pat)) != NULL)
+ (*result)->patgrp->do_size = 1;
+ }
+
+ if(scorei_pat){
+ if(((*result)->patgrp->score = parse_intvl(scorei_pat)) != NULL)
+ (*result)->patgrp->do_score = 1;
+ }
+
+ (*result)->patgrp->cat_lim = -1L; /* default */
+ if(cat_cmd){
+ if(!cat_cmd[0])
+ fs_give((void **) &cat_cmd);
+
+ /* quick check for absolute paths */
+ if(cat_cmd)
+ for(j = 0; cat_cmd[j]; j++)
+ if(!is_absolute_path(cat_cmd[j]))
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Warning: command must be absolute path: \"%s\""),
+ cat_cmd[j]);
+
+ (*result)->patgrp->category_cmd = cat_cmd;
+ cat_cmd = NULL;
+
+ if(cati){
+ if(((*result)->patgrp->cat = parse_intvl(cati)) != NULL)
+ (*result)->patgrp->do_cat = 1;
+ }
+ if(cat_lim && *cat_lim)
+ (*result)->patgrp->cat_lim = atol(cat_lim);
+ }
+
+ if(stat_del && *stat_del){
+ for(j = 0; (f = role_status_types(j)); j++)
+ if(!strucmp(stat_del, f->name)){
+ (*result)->patgrp->stat_del = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->patgrp->stat_del = PAT_STAT_EITHER;
+
+ if(stat_new && *stat_new){
+ for(j = 0; (f = role_status_types(j)); j++)
+ if(!strucmp(stat_new, f->name)){
+ (*result)->patgrp->stat_new = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->patgrp->stat_new = PAT_STAT_EITHER;
+
+ if(stat_rec && *stat_rec){
+ for(j = 0; (f = role_status_types(j)); j++)
+ if(!strucmp(stat_rec, f->name)){
+ (*result)->patgrp->stat_rec = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->patgrp->stat_rec = PAT_STAT_EITHER;
+
+ if(stat_imp && *stat_imp){
+ for(j = 0; (f = role_status_types(j)); j++)
+ if(!strucmp(stat_imp, f->name)){
+ (*result)->patgrp->stat_imp = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->patgrp->stat_imp = PAT_STAT_EITHER;
+
+ if(stat_ans && *stat_ans){
+ for(j = 0; (f = role_status_types(j)); j++)
+ if(!strucmp(stat_ans, f->name)){
+ (*result)->patgrp->stat_ans = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->patgrp->stat_ans = PAT_STAT_EITHER;
+
+ if(stat_8bit && *stat_8bit){
+ for(j = 0; (f = role_status_types(j)); j++)
+ if(!strucmp(stat_8bit, f->name)){
+ (*result)->patgrp->stat_8bitsubj = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->patgrp->stat_8bitsubj = PAT_STAT_EITHER;
+
+ if(stat_bom && *stat_bom){
+ for(j = 0; (f = role_status_types(j)); j++)
+ if(!strucmp(stat_bom, f->name)){
+ (*result)->patgrp->stat_bom = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->patgrp->stat_bom = PAT_STAT_EITHER;
+
+ if(stat_boy && *stat_boy){
+ for(j = 0; (f = role_status_types(j)); j++)
+ if(!strucmp(stat_boy, f->name)){
+ (*result)->patgrp->stat_boy = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->patgrp->stat_boy = PAT_STAT_EITHER;
+
+ if(sort_act){
+ decode_sort(sort_act, &def_sort, &def_sort_rev);
+ (*result)->action->sort_is_set = 1;
+ (*result)->action->sortorder = def_sort;
+ (*result)->action->revsort = (def_sort_rev ? 1 : 0);
+ /*
+ * Don't try to re-sort until next open of folder. If user
+ * $-sorted then it probably shouldn't change anyway. Why
+ * bother keeping track of that?
+ */
+ }
+
+ (*result)->action->index_format = iform_act;
+ iform_act = NULL;
+
+ if(startup_act && *startup_act){
+ for(j = 0; (f = startup_rules(j)); j++)
+ if(!strucmp(startup_act, f->name)){
+ (*result)->action->startup_rule = f->value;
+ break;
+ }
+ }
+ else
+ (*result)->action->startup_rule = IS_NOTSET;
+
+ aa = NULL;
+ for(ea = earb; ea; ea = ea->next){
+ char *xyz;
+
+ if(aa){
+ aa->next = (ARBHDR_S *)fs_get(sizeof(*aa));
+ aa = aa->next;
+ }
+ else{
+ (*result)->patgrp->arbhdr =
+ (ARBHDR_S *)fs_get(sizeof(ARBHDR_S));
+ aa = (*result)->patgrp->arbhdr;
+ }
+
+ memset(aa, 0, sizeof(*aa));
+
+ aa->field = cpystr((ea->a && ea->a->field) ? ea->a->field : "");
+
+ alval = ALVAL(ea->v, ew);
+ spat = *alval;
+ *alval = NULL;
+ aa->p = editlist_to_pattern(spat);
+ if(aa->p && ea->v && ea->v->name &&
+ !strncmp(ea->v->name, NOT, NOTLEN))
+ aa->p->not = 1;
+
+ if((xyz = pattern_to_string(aa->p)) != NULL){
+ if(!*xyz)
+ aa->isemptyval = 1;
+
+ fs_give((void **)&xyz);
+ }
+ }
+
+ if(fldr_type_pat && *fldr_type_pat){
+ for(j = 0; (f = pat_fldr_types(j)); j++)
+ if(!strucmp(fldr_type_pat, f->name)){
+ (*result)->patgrp->fldr_type = f->value;
+ break;
+ }
+ }
+ else{
+ f = pat_fldr_types(FLDR_DEFL);
+ if(f)
+ (*result)->patgrp->fldr_type = f->value;
+ }
+
+ (*result)->patgrp->folder = editlist_to_pattern(folder_pat);
+
+ if(abook_type_pat && *abook_type_pat){
+ for(j = 0; (f = inabook_fldr_types(j)); j++)
+ if(!strucmp(abook_type_pat, f->name)){
+ (*result)->patgrp->inabook = f->value;
+ break;
+ }
+
+ if(bitnset(INABOOK_FROM, inabook_type_list))
+ (*result)->patgrp->inabook |= IAB_FROM;
+ if(bitnset(INABOOK_REPLYTO, inabook_type_list))
+ (*result)->patgrp->inabook |= IAB_REPLYTO;
+ if(bitnset(INABOOK_SENDER, inabook_type_list))
+ (*result)->patgrp->inabook |= IAB_SENDER;
+ if(bitnset(INABOOK_TO, inabook_type_list))
+ (*result)->patgrp->inabook |= IAB_TO;
+ if(bitnset(INABOOK_CC, inabook_type_list))
+ (*result)->patgrp->inabook |= IAB_CC;
+
+ if(!((*result)->patgrp->inabook & IAB_TYPE_MASK))
+ (*result)->patgrp->inabook |= (IAB_FROM | IAB_REPLYTO);
+ }
+ else{
+ f = inabook_fldr_types(IAB_DEFL);
+ if(f)
+ (*result)->patgrp->inabook = f->value;
+ }
+
+ (*result)->patgrp->abooks = editlist_to_pattern(abook_pat);
+
+
+ (*result)->action->inherit_nick = inick;
+ inick = NULL;
+ (*result)->action->fcc = fcc_act;
+ fcc_act = NULL;
+ (*result)->action->litsig = litsig_act;
+ litsig_act = NULL;
+ (*result)->action->sig = sig_act;
+ sig_act = NULL;
+ (*result)->action->template = templ_act;
+ templ_act = NULL;
+
+ if(cstm_act){
+ /*
+ * Check for From or Reply-To and eliminate them.
+ */
+ for(i = 0; cstm_act[i]; i++){
+ char *free_this;
+
+ if((!struncmp(cstm_act[i],"from",4) &&
+ (cstm_act[i][4] == ':' ||
+ cstm_act[i][4] == '\0')) ||
+ (!struncmp(cstm_act[i],"reply-to",8) &&
+ (cstm_act[i][8] == ':' ||
+ cstm_act[i][8] == '\0'))){
+ free_this = cstm_act[i];
+ /* slide the rest up */
+ for(j = i; cstm_act[j]; j++)
+ cstm_act[j] = cstm_act[j+1];
+
+ fs_give((void **)&free_this);
+ i--; /* recheck now that we've slid them up */
+ }
+ }
+
+ /* nothing left */
+ if(!cstm_act[0])
+ fs_give((void **)&cstm_act);
+
+ (*result)->action->cstm = cstm_act;
+ cstm_act = NULL;
+ }
+
+ if(smtp_act){
+ if(!smtp_act[0])
+ fs_give((void **)&smtp_act);
+
+ (*result)->action->smtp = smtp_act;
+ smtp_act = NULL;
+ }
+
+ if(nntp_act){
+ if(!nntp_act[0])
+ fs_give((void **)&nntp_act);
+
+ (*result)->action->nntp = nntp_act;
+ nntp_act = NULL;
+ }
+
+ if(filter_type && *filter_type){
+ (*result)->action->non_terminating =
+ bitnset(FEAT_NONTERM, feat_option_list) ? 1 : 0;
+ for(i = 0; (f = filter_types(i)); i++){
+ if(!strucmp(filter_type, f->name)){
+ if(f->value == FILTER_FOLDER){
+ (*result)->action->folder = editlist_to_pattern(folder_act);
+ (*result)->action->move_only_if_not_deleted =
+ bitnset(FEAT_IFNOTDEL, feat_option_list) ? 1 : 0;
+ }
+ else if(f->value == FILTER_STATE){
+ (*result)->action->kill = 0;
+ }
+ else if(f->value == FILTER_KILL){
+ (*result)->action->kill = 1;
+ }
+
+ /*
+ * This is indented an extra indent because we used to condition
+ * this on !kill. We changed it so that you can set state bits
+ * even if you're killing. This is marginally helpful if you
+ * have another client running that doesn't know about this
+ * filter, but you want to, for example, have the messages show
+ * up now as deleted instead of having that deferred until we
+ * exit. It is controlled by the user by setting the status
+ * action bits along with the Delete.
+ */
+ if(filt_imp && *filt_imp){
+ for(j = 0; (f = msg_state_types(j)); j++){
+ if(!strucmp(filt_imp, f->name)){
+ switch(f->value){
+ case ACT_STAT_LEAVE:
+ break;
+ case ACT_STAT_SET:
+ (*result)->action->state_setting_bits |= F_FLAG;
+ break;
+ case ACT_STAT_CLEAR:
+ (*result)->action->state_setting_bits |= F_UNFLAG;
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ if(filt_del && *filt_del){
+ for(j = 0; (f = msg_state_types(j)); j++){
+ if(!strucmp(filt_del, f->name)){
+ switch(f->value){
+ case ACT_STAT_LEAVE:
+ break;
+ case ACT_STAT_SET:
+ (*result)->action->state_setting_bits |= F_DEL;
+ break;
+ case ACT_STAT_CLEAR:
+ (*result)->action->state_setting_bits |= F_UNDEL;
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ if(filt_ans && *filt_ans){
+ for(j = 0; (f = msg_state_types(j)); j++){
+ if(!strucmp(filt_ans, f->name)){
+ switch(f->value){
+ case ACT_STAT_LEAVE:
+ break;
+ case ACT_STAT_SET:
+ (*result)->action->state_setting_bits |= F_ANS;
+ break;
+ case ACT_STAT_CLEAR:
+ (*result)->action->state_setting_bits |= F_UNANS;
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ if(filt_new && *filt_new){
+ for(j = 0; (f = msg_state_types(j)); j++){
+ if(!strucmp(filt_new, f->name)){
+ switch(f->value){
+ case ACT_STAT_LEAVE:
+ break;
+ case ACT_STAT_SET:
+ (*result)->action->state_setting_bits |= F_UNSEEN;
+ break;
+ case ACT_STAT_CLEAR:
+ (*result)->action->state_setting_bits |= F_SEEN;
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ (*result)->action->keyword_set =
+ editlist_to_pattern(keyword_set);
+ (*result)->action->keyword_clr =
+ editlist_to_pattern(keyword_clr);
+
+ break;
+ }
+ }
+ }
+
+ if(from_act && *from_act)
+ rfc822_parse_adrlist(&(*result)->action->from, from_act,
+ ps->maildomain);
+
+ if(replyto_act && *replyto_act)
+ rfc822_parse_adrlist(&(*result)->action->replyto, replyto_act,
+ ps->maildomain);
+
+ if(score_act && (j = atoi(score_act)) >= SCORE_MIN && j <= SCORE_MAX)
+ (*result)->action->scoreval = (long) j;
+
+ if(hdrtok_act)
+ (*result)->action->scorevalhdrtok = stringform_to_hdrtok(hdrtok_act);
+
+ if(repl_type && *repl_type){
+ for(j = 0; (f = role_repl_types(j)); j++)
+ if(!strucmp(repl_type, f->name)){
+ (*result)->action->repl_type = f->value;
+ break;
+ }
+ }
+ else{
+ f = role_repl_types(ROLE_REPL_DEFL);
+ if(f)
+ (*result)->action->repl_type = f->value;
+ }
+
+ if(forw_type && *forw_type){
+ for(j = 0; (f = role_forw_types(j)); j++)
+ if(!strucmp(forw_type, f->name)){
+ (*result)->action->forw_type = f->value;
+ break;
+ }
+ }
+ else{
+ f = role_forw_types(ROLE_FORW_DEFL);
+ if(f)
+ (*result)->action->forw_type = f->value;
+ }
+
+ if(comp_type && *comp_type){
+ for(j = 0; (f = role_comp_types(j)); j++)
+ if(!strucmp(comp_type, f->name)){
+ (*result)->action->comp_type = f->value;
+ break;
+ }
+ }
+ else{
+ f = role_comp_types(ROLE_COMP_DEFL);
+ if(f)
+ (*result)->action->comp_type = f->value;
+ }
+
+ if(rc_fg && *rc_fg && rc_bg && *rc_bg){
+ if(!old_fg || !old_bg || strucmp(old_fg, rc_fg) ||
+ strucmp(old_bg, rc_bg))
+ clear_index_cache(ps_global->mail_stream, 0);
+
+ /*
+ * If same as normal color, don't set it. This may or may
+ * not surprise the user when they change the normal color.
+ * This color will track the normal color instead of staying
+ * the same as the old normal color, which is probably
+ * what they want.
+ */
+ if(!ps_global->VAR_NORM_FORE_COLOR ||
+ !ps_global->VAR_NORM_BACK_COLOR ||
+ strucmp(ps_global->VAR_NORM_FORE_COLOR, rc_fg) ||
+ strucmp(ps_global->VAR_NORM_BACK_COLOR, rc_bg))
+ (*result)->action->incol = new_color_pair(rc_fg, rc_bg);
+ }
+ }
+
+ for(j = 0; varlist[j]; j++){
+ v = varlist[j];
+ free_variable_values(v);
+ if(v->name)
+ fs_give((void **)&v->name);
+ }
+
+ if(earb)
+ free_earb(&earb);
+ if(nick)
+ fs_give((void **)&nick);
+ if(comment)
+ fs_give((void **)&comment);
+ if(to_pat)
+ free_list_array(&to_pat);
+ if(from_pat)
+ free_list_array(&from_pat);
+ if(sender_pat)
+ free_list_array(&sender_pat);
+ if(cc_pat)
+ free_list_array(&cc_pat);
+ if(recip_pat)
+ free_list_array(&recip_pat);
+ if(partic_pat)
+ free_list_array(&partic_pat);
+ if(news_pat)
+ free_list_array(&news_pat);
+ if(subj_pat)
+ free_list_array(&subj_pat);
+ if(alltext_pat)
+ free_list_array(&alltext_pat);
+ if(bodytext_pat)
+ free_list_array(&bodytext_pat);
+ if(keyword_pat)
+ free_list_array(&keyword_pat);
+ if(charset_pat)
+ free_list_array(&charset_pat);
+ if(age_pat)
+ fs_give((void **)&age_pat);
+ if(size_pat)
+ fs_give((void **)&size_pat);
+ if(scorei_pat)
+ fs_give((void **)&scorei_pat);
+ if(cati)
+ fs_give((void **)&cati);
+ if(cat_lim)
+ fs_give((void **)&cat_lim);
+ if(stat_del)
+ fs_give((void **)&stat_del);
+ if(stat_new)
+ fs_give((void **)&stat_new);
+ if(stat_rec)
+ fs_give((void **)&stat_rec);
+ if(stat_imp)
+ fs_give((void **)&stat_imp);
+ if(stat_ans)
+ fs_give((void **)&stat_ans);
+ if(stat_8bit)
+ fs_give((void **)&stat_8bit);
+ if(stat_bom)
+ fs_give((void **)&stat_bom);
+ if(stat_boy)
+ fs_give((void **)&stat_boy);
+ if(fldr_type_pat)
+ fs_give((void **)&fldr_type_pat);
+ if(folder_pat)
+ free_list_array(&folder_pat);
+ if(abook_type_pat)
+ fs_give((void **)&abook_type_pat);
+ if(abook_pat)
+ free_list_array(&abook_pat);
+ if(inick)
+ fs_give((void **)&inick);
+ if(from_act)
+ fs_give((void **)&from_act);
+ if(replyto_act)
+ fs_give((void **)&replyto_act);
+ if(fcc_act)
+ fs_give((void **)&fcc_act);
+ if(litsig_act)
+ fs_give((void **)&litsig_act);
+ if(sort_act)
+ fs_give((void **)&sort_act);
+ if(iform_act)
+ fs_give((void **)&iform_act);
+ if(keyword_set)
+ free_list_array(&keyword_set);
+ if(keyword_clr)
+ free_list_array(&keyword_clr);
+ if(startup_act)
+ fs_give((void **)&startup_act);
+ if(sig_act)
+ fs_give((void **)&sig_act);
+ if(templ_act)
+ fs_give((void **)&templ_act);
+ if(score_act)
+ fs_give((void **)&score_act);
+ if(hdrtok_act)
+ fs_give((void **)&hdrtok_act);
+ if(repl_type)
+ fs_give((void **)&repl_type);
+ if(forw_type)
+ fs_give((void **)&forw_type);
+ if(comp_type)
+ fs_give((void **)&comp_type);
+ if(rc_fg)
+ fs_give((void **)&rc_fg);
+ if(rc_bg)
+ fs_give((void **)&rc_bg);
+ if(old_fg)
+ fs_give((void **)&old_fg);
+ if(old_bg)
+ fs_give((void **)&old_bg);
+ if(filt_del)
+ fs_give((void **)&filt_del);
+ if(filt_new)
+ fs_give((void **)&filt_new);
+ if(filt_ans)
+ fs_give((void **)&filt_ans);
+ if(filt_imp)
+ fs_give((void **)&filt_imp);
+ if(folder_act)
+ free_list_array(&folder_act);
+ if(filter_type)
+ fs_give((void **)&filter_type);
+
+ if(cat_cmd)
+ free_list_array(&cat_cmd);
+
+ if(cstm_act)
+ free_list_array(&cstm_act);
+
+ if(smtp_act)
+ free_list_array(&smtp_act);
+
+ if(nntp_act)
+ free_list_array(&nntp_act);
+
+ opt_screen = saved_screen;
+ ps->mangled_screen = 1;
+ return(rv);
+}
+
+
+void
+setup_dummy_pattern_var(struct variable *v, char *name, PATTERN_S *defpat)
+{
+ char ***alval;
+
+ if(!(v && name))
+ panic("setup_dummy_pattern_var");
+
+ v->name = (char *) fs_get(strlen(name)+NOTLEN+1);
+ snprintf(v->name, strlen(name)+NOTLEN+1, "%s%s", (defpat && defpat->not) ? NOT : "", name);
+ v->name[ strlen(name)+NOTLEN+1-1] = '\0';
+ v->is_used = 1;
+ v->is_user = 1;
+ v->is_list = 1;
+ alval = ALVAL(v, ew);
+ *alval = pattern_to_editlist(defpat);
+ set_current_val(v, FALSE, FALSE);
+}
+
+
+void
+setup_role_pat(struct pine *ps, CONF_S **ctmp, struct variable *v, HelpType help,
+ char *help_title, struct key_menu *keymenu,
+ int (*tool)(struct pine *, int, CONF_S **, unsigned),
+ EARB_S **earb, int indent)
+{
+ char tmp[MAXPATH+1];
+ char **lval;
+ int i;
+ CONF_S *ctmpb;
+
+ new_confline(ctmp);
+ (*ctmp)->help_title = help_title;
+ (*ctmp)->var = v;
+ (*ctmp)->valoffset = indent;
+ (*ctmp)->keymenu = keymenu;
+ (*ctmp)->help = help;
+ (*ctmp)->tool = tool;
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.*w =", indent-3, indent-3, (v && v->name) ? v->name : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ (*ctmp)->varname = cpystr(tmp);
+ (*ctmp)->varnamep = *ctmp;
+ (*ctmp)->value = pretty_value(ps, *ctmp);
+ (*ctmp)->d.earb = earb;
+ (*ctmp)->varmem = 0;
+ (*ctmp)->flags = CF_STARTITEM;
+
+ ctmpb = (*ctmp);
+
+ lval = LVAL(v, ew);
+ if(lval){
+ for(i = 0; lval[i]; i++){
+ if(i)
+ new_confline(ctmp);
+
+ (*ctmp)->var = v;
+ (*ctmp)->varmem = i;
+ (*ctmp)->valoffset = indent;
+ (*ctmp)->value = pretty_value(ps, *ctmp);
+ (*ctmp)->d.earb = earb;
+ (*ctmp)->keymenu = keymenu;
+ (*ctmp)->help = help;
+ (*ctmp)->tool = tool;
+ (*ctmp)->varnamep = ctmpb;
+ }
+ }
+}
+
+
+/*
+ * Watch out for polarity of nosel flag. Setting it means to turn on
+ * the NOSELECT flag, which means to make that line unselectable.
+ */
+void
+setup_role_pat_alt(struct pine *ps, CONF_S **ctmp, struct variable *v, HelpType help,
+ char *help_title, struct key_menu *keymenu,
+ int (*tool)(struct pine *, int, CONF_S **, unsigned),
+ int voff, int nosel)
+{
+ char tmp[MAXPATH+1];
+ char **lval;
+ int i, j, k;
+ CONF_S *ctmpb;
+
+ new_confline(ctmp);
+ (*ctmp)->help_title = help_title;
+ (*ctmp)->var = v;
+
+ (*ctmp)->varoffset = voff;
+ k = utf8_width(v->name);
+ j = voff+k+3;
+ (*ctmp)->valoffset = j;
+
+ (*ctmp)->keymenu = keymenu;
+ (*ctmp)->help = help;
+ (*ctmp)->tool = tool;
+
+ utf8_snprintf(tmp, sizeof(tmp), "%*.*w =", k, k, v->name);
+ tmp[sizeof(tmp)-1] = '\0';
+ (*ctmp)->varname = cpystr(tmp);
+
+ (*ctmp)->varnamep = *ctmp;
+ (*ctmp)->value = pretty_value(ps, *ctmp);
+ (*ctmp)->varmem = 0;
+
+ (*ctmp)->flags = (nosel ? CF_NOSELECT : 0);
+
+ ctmpb = (*ctmp);
+
+ lval = LVAL(v, ew);
+ if(lval){
+ for(i = 0; lval[i]; i++){
+ if(i)
+ new_confline(ctmp);
+
+ (*ctmp)->var = v;
+ (*ctmp)->varmem = i;
+ (*ctmp)->varoffset = voff;
+ (*ctmp)->valoffset = j;
+ (*ctmp)->value = pretty_value(ps, *ctmp);
+ (*ctmp)->keymenu = keymenu;
+ (*ctmp)->help = help;
+ (*ctmp)->tool = tool;
+ (*ctmp)->varnamep = ctmpb;
+ (*ctmp)->flags = (nosel ? CF_NOSELECT : 0);
+ }
+ }
+}
+
+
+void
+free_earb(EARB_S **ea)
+{
+ if(ea && *ea){
+ free_earb(&(*ea)->next);
+ if((*ea)->v){
+ free_variable_values((*ea)->v);
+ if((*ea)->v->name)
+ fs_give((void **) &(*ea)->v->name);
+
+ fs_give((void **) &(*ea)->v);;
+ }
+
+ free_arbhdr(&(*ea)->a);
+ fs_give((void **) ea);
+ }
+}
+
+
+void
+calculate_inick_stuff(struct pine *ps)
+{
+ ACTION_S *role, *irole;
+ CONF_S *ctmp, *ctmpa;
+ struct variable *v;
+ int i;
+ char *nick;
+
+ if(inick_confs[INICK_INICK_CONF] == NULL)
+ return;
+
+ for(i = INICK_FROM_CONF; i <= INICK_NNTP_CONF; i++){
+ v = inick_confs[i] ? inick_confs[i]->var : NULL;
+ if(v){
+ if(v->is_list){
+ if(v->global_val.l)
+ free_list_array(&v->global_val.l);
+ }
+ else{
+ if(v->global_val.p)
+ fs_give((void **)&v->global_val.p);
+ }
+ }
+ }
+
+ nick = PVAL(inick_confs[INICK_INICK_CONF]->var, ew);
+
+ if(nick){
+ /*
+ * Use an empty role with inherit_nick set to nick and then use the
+ * combine function to find the action values.
+ */
+ role = (ACTION_S *)fs_get(sizeof(*role));
+ memset((void *)role, 0, sizeof(*role));
+ role->is_a_role = 1;
+ role->inherit_nick = cpystr(nick);
+ irole = combine_inherited_role(role);
+
+ ctmp = inick_confs[INICK_FROM_CONF];
+ v = ctmp ? ctmp->var : NULL;
+
+ if(irole && irole->from){
+ char *bufp;
+ size_t len;
+
+ len = est_size(irole->from);
+ bufp = (char *) fs_get(len * sizeof(char));
+ v->global_val.p = addr_string_mult(irole->from, bufp, len);
+ }
+
+ ctmp = inick_confs[INICK_REPLYTO_CONF];
+ v = ctmp ? ctmp->var : NULL;
+
+ if(irole && irole->replyto){
+ char *bufp;
+ size_t len;
+
+ len = est_size(irole->replyto);
+ bufp = (char *) fs_get(len * sizeof(char));
+ v->global_val.p = addr_string_mult(irole->replyto, bufp, len);
+ }
+
+ ctmp = inick_confs[INICK_FCC_CONF];
+ v = ctmp ? ctmp->var : NULL;
+ v->global_val.p = (irole && irole->fcc) ? cpystr(irole->fcc) : NULL;
+
+ ctmp = inick_confs[INICK_LITSIG_CONF];
+ v = ctmp ? ctmp->var : NULL;
+ v->global_val.p = (irole && irole->litsig) ? cpystr(irole->litsig)
+ : NULL;
+
+ ctmp = inick_confs[INICK_SIG_CONF];
+ v = ctmp ? ctmp->var : NULL;
+ v->global_val.p = (irole && irole->sig) ? cpystr(irole->sig) : NULL;
+
+ ctmp = inick_confs[INICK_TEMPL_CONF];
+ v = ctmp ? ctmp->var : NULL;
+ v->global_val.p = (irole && irole->template)
+ ? cpystr(irole->template) : NULL;
+
+ ctmp = inick_confs[INICK_CSTM_CONF];
+ v = ctmp ? ctmp->var : NULL;
+ v->global_val.l = (irole && irole->cstm) ? copy_list_array(irole->cstm)
+ : NULL;
+
+ ctmp = inick_confs[INICK_SMTP_CONF];
+ v = ctmp ? ctmp->var : NULL;
+ v->global_val.l = (irole && irole->smtp) ? copy_list_array(irole->smtp)
+ : NULL;
+
+ ctmp = inick_confs[INICK_NNTP_CONF];
+ v = ctmp ? ctmp->var : NULL;
+ v->global_val.l = (irole && irole->nntp) ? copy_list_array(irole->nntp)
+ : NULL;
+
+ free_action(&role);
+ free_action(&irole);
+ }
+
+ for(i = INICK_INICK_CONF; i <= INICK_NNTP_CONF; i++){
+ ctmp = inick_confs[i];
+ v = ctmp ? ctmp->var : NULL;
+ /*
+ * If we didn't set a global_val using the nick above, then
+ * set one here for each variable that uses one.
+ */
+ if(v && !v->global_val.p){
+ char *str, *astr, *lc, pdir[MAXPATH+1];
+ ADDRESS *addr;
+ int len;
+
+ switch(i){
+ case INICK_FROM_CONF:
+ addr = generate_from();
+ astr = addr_list_string(addr, NULL, 1);
+ str = (astr && astr[0]) ? astr : "?";
+ v->global_val.p = (char *)fs_get((strlen(str) + 20) *
+ sizeof(char));
+ snprintf(v->global_val.p, strlen(str) + 20, "%s%s)", DSTRING, str);
+ v->global_val.p[strlen(str) + 20 - 1] = '\0';
+ if(astr)
+ fs_give((void **)&astr);
+
+ if(addr)
+ mail_free_address(&addr);
+
+ break;
+
+ case INICK_FCC_CONF:
+ v->global_val.p = cpystr(VSTRING);
+ break;
+
+ case INICK_LITSIG_CONF:
+ /*
+ * This default works this way because of the ordering
+ * of the choices in the detoken routine.
+ */
+ if(ps->VAR_LITERAL_SIG){
+ str = ps->VAR_LITERAL_SIG;
+ v->global_val.p = (char *)fs_get((strlen(str) + 20) *
+ sizeof(char));
+ snprintf(v->global_val.p, strlen(str) + 20,
+ "%s%s)", DSTRING, str);
+ v->global_val.p[strlen(str) + 20 - 1] = '\0';
+ }
+
+ break;
+
+ case INICK_SIG_CONF:
+ pdir[0] = '\0';
+ if(ps_global->VAR_OPER_DIR){
+ strncpy(pdir, ps_global->VAR_OPER_DIR, MAXPATH);
+ pdir[MAXPATH] = '\0';
+ len = strlen(pdir) + 1;
+ }
+ else if((lc = last_cmpnt(ps_global->pinerc)) != NULL){
+ strncpy(pdir, ps_global->pinerc,
+ MIN(MAXPATH,lc-ps_global->pinerc));
+ pdir[MIN(MAXPATH, lc-ps_global->pinerc)] = '\0';
+ len = strlen(pdir);
+ }
+
+ if(pdir[0] && ps->VAR_SIGNATURE_FILE &&
+ ps->VAR_SIGNATURE_FILE[0] &&
+ is_absolute_path(ps->VAR_SIGNATURE_FILE) &&
+ !strncmp(ps->VAR_SIGNATURE_FILE, pdir, len)){
+ str = ps->VAR_SIGNATURE_FILE + len;
+ }
+ else
+ str = (ps->VAR_SIGNATURE_FILE && ps->VAR_SIGNATURE_FILE[0])
+ ? ps->VAR_SIGNATURE_FILE : NULL;
+ if(str){
+ v->global_val.p = (char *)fs_get((strlen(str) + 20) *
+ sizeof(char));
+ snprintf(v->global_val.p, strlen(str) + 20, "%s%s)", DSTRING, str);
+ v->global_val.p[strlen(str) + 20 - 1] = '\0';
+ }
+
+ break;
+
+ case INICK_INICK_CONF:
+ case INICK_REPLYTO_CONF:
+ case INICK_TEMPL_CONF:
+ case INICK_CSTM_CONF:
+ case INICK_SMTP_CONF:
+ case INICK_NNTP_CONF:
+ break;
+ }
+ }
+
+ if(v)
+ set_current_val(v, FALSE, FALSE);
+
+ if(ctmp){
+ CONF_S *ctmpsig = NULL;
+ struct variable *vlsig;
+
+ for(ctmpa = ctmp;
+ ctmpa && ctmpa->varnamep == ctmp;
+ ctmpa = ctmpa->next){
+ if(ctmpa->value)
+ fs_give((void **)&ctmpa->value);
+
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+
+ if(i == INICK_SIG_CONF){
+ /*
+ * Turn off NOSELECT, but possibly turn it on again
+ * in next line.
+ */
+ if((ctmpsig = inick_confs[INICK_SIG_CONF]) != NULL)
+ ctmpsig->flags &= ~CF_NOSELECT;
+
+ if(inick_confs[INICK_LITSIG_CONF] &&
+ (vlsig = inick_confs[INICK_LITSIG_CONF]->var) &&
+ vlsig->current_val.p &&
+ vlsig->current_val.p[0]){
+ if(ctmp->value)
+ fs_give((void **)&ctmp->value);
+
+ ctmp->value =
+ cpystr("<Ignored: using LiteralSig instead>");
+
+ ctmp->flags |= CF_NOSELECT;
+ }
+ }
+ }
+ }
+}
+
+
+/* Arguments:
+ * lst: a list of folders
+ * action: a 1 or 0 value which basically says that str is associated with
+ * the filter action if true or the Current Folder type if false.
+ * Return:
+ * Returns 2 on success (user wants to exit) and 0 on failure.
+ *
+ * This function cycles through a list of folders and checks whether or not each
+ * folder exists. If they exist, return 2, if they don't exist, notify the user
+ * or offer to create depending on whether or not it is a filter action or not.
+ * With each of these prompts, the user can abort their desire to exit.
+ */
+int
+check_role_folders(char **lst, unsigned int action)
+{
+ char *cur_fn, wt_res, prompt[MAX_SCREEN_COLS];
+ int i, rv = 2;
+ CONTEXT_S *cntxt = NULL;
+ char nbuf1[MAX_SCREEN_COLS], nbuf2[MAX_SCREEN_COLS];
+ int space, w1, w2, exists;
+
+ if(!(lst && *lst)){
+ if(action)
+ q_status_message(SM_ORDER, 3, 5,
+ _("Set a valid Filter Action before Exiting"));
+ else
+ q_status_message(SM_ORDER, 3, 5,
+ _("Set a valid Specific Folder before Exiting"));
+ rv = 0;
+ return rv;
+ }
+
+ for(i = 0; lst[i]; i++){
+ if(action)
+ cur_fn = detoken_src(lst[i], FOR_FILT, NULL, NULL, NULL, NULL);
+ else
+ cur_fn = lst[i];
+
+ removing_leading_and_trailing_white_space(cur_fn);
+ if(*cur_fn != '\0'){
+ space = MAXPROMPT;
+ if(is_absolute_path(cur_fn) || !context_isambig(cur_fn))
+ cntxt = NULL;
+ else
+ cntxt = default_save_context(ps_global->context_list);
+
+ if(!(exists=folder_exists(cntxt, cur_fn))
+ && (action
+ || (ps_global->context_list->use & CNTXT_INCMNG
+ && !folder_is_nick(cur_fn,FOLDERS(ps_global->context_list), 0)))){
+ if(cntxt && (action == 1)){
+ space -= 37; /* for fixed part of prompt below */
+ w1 = MAX(1,MIN(strlen(cur_fn),space/2));
+ w2 = MIN(MAX(1,space-w1),strlen(cntxt->nickname));
+ w1 += MAX(0,space-w1-w2);
+ snprintf(prompt, sizeof(prompt),
+ _("Folder \"%s\" in <%s> doesn't exist. Create"),
+ short_str(cur_fn,nbuf1,sizeof(nbuf1),w1,MidDots),
+ short_str(cntxt->nickname,nbuf2,sizeof(nbuf2),w2,MidDots));
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+ else if(cntxt && (action == 0)){
+ space -= 51; /* for fixed part of prompt below */
+ w1 = MAX(1,MIN(strlen(cur_fn),space/2));
+ w2 = MIN(MAX(1,space-w1),strlen(cntxt->nickname));
+ w1 += MAX(0,space-w1-w2);
+ snprintf(prompt, sizeof(prompt),
+ _("Folder \"%s\" in <%s> doesn't exist. Exit and save anyway"),
+ short_str(cur_fn,nbuf1,sizeof(nbuf1),w1,MidDots),
+ short_str(cntxt->nickname,nbuf2,sizeof(nbuf2),w2,MidDots));
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+ else if(!cntxt && (action == 1)){
+ space -= 31; /* for fixed part of prompt below */
+ w1 = MAX(1,space);
+ snprintf(prompt, sizeof(prompt),
+ _("Folder \"%s\" doesn't exist. Create"),
+ short_str(cur_fn,nbuf1,sizeof(nbuf1),w1,MidDots));
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+ else{ /*!cntxt && (action == 0) */
+ space -= 45; /* for fixed part of prompt below */
+ w1 = MAX(1,space);
+ snprintf(prompt, sizeof(prompt),
+ _("Folder \"%s\" doesn't exist. Exit and save anyway"),
+ short_str(cur_fn,nbuf1,sizeof(nbuf1),w1,MidDots));
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+ wt_res = want_to(prompt, 'n', 'x', NO_HELP, WT_NORM);
+ if(wt_res == 'y'){
+ if(action){
+ if(context_create(cntxt, NULL, cur_fn)){
+ q_status_message(SM_ORDER,3,5,_("Folder created"));
+ maybe_add_to_incoming(cntxt, cur_fn);
+ }
+ }
+ /* No message to notify of changes being saved, we can't */
+ /* assume that the role screen isn't exited yet */
+ rv = 2;
+ }
+ else if(wt_res == 'n' && action){
+ rv = 2;
+ q_status_message(SM_ORDER,3,5,_("Folder not created"));
+ }
+ else{
+ q_status_message(SM_ORDER,3,5,_("Exit cancelled"));
+ rv = 0;
+ break;
+ }
+ }
+ else{
+ if(exists & FEX_ERROR){
+ if(ps_global->mm_log_error && ps_global->c_client_error)
+ q_status_message(SM_ORDER,3,5,ps_global->c_client_error);
+ else
+ q_status_message1(SM_ORDER,3,5,"\"%s\": Trouble checking for folder existence", cur_fn);
+ }
+
+ rv = 2;
+ }
+ }
+ else{ /* blank item in list of folders */
+ if(action && lst[i+1] == '\0')
+ q_status_message(SM_ORDER,3,5,_("Set a valid Filter Action before Exiting"));
+ else /* !action && lst[i+1] == '\0' */
+ q_status_message(SM_ORDER,3,5,_("Set a valid Specific Folder before Exiting"));
+ rv = 0;
+ break;
+ }
+
+ if(cur_fn && cur_fn != lst[i])
+ fs_give((void **) &cur_fn);
+ }
+
+ return(rv);
+}
+
+
+void
+maybe_add_to_incoming(CONTEXT_S *cntxt, char *cur_fn)
+{
+ char name[MAILTMPLEN], nname[32];
+ char nbuf1[MAX_SCREEN_COLS], nbuf2[MAX_SCREEN_COLS];
+ char prompt[MAX_SCREEN_COLS];
+ char ***alval;
+ int i, found, space, w1, w2;
+ FOLDER_S *f;
+
+ if(ps_global->context_list->use & CNTXT_INCMNG &&
+ ((alval = ALVAL(&ps_global->vars[V_INCOMING_FOLDERS], Main)) != NULL)){
+ (void)context_apply(name, cntxt, cur_fn, sizeof(name));
+ /*
+ * Since the folder didn't exist it is very unlikely that it is
+ * in the incoming-folders list already, but we're just checking
+ * to be sure. We should really be canonicalizing both names
+ * before comparing, but...
+ */
+ for(found = 0, i = 0; *alval && (*alval)[i] && !found; i++){
+ char *nickname, *folder;
+
+ get_pair((*alval)[i], &nickname, &folder, 0, 0);
+ if(folder && !strucmp((*alval)[i], folder))
+ found++;
+
+ if(nickname)
+ fs_give((void **)&nickname);
+ if(folder)
+ fs_give((void **)&folder);
+ }
+
+ if(found)
+ return;
+
+ space = MAXPROMPT;
+ space -= 15; /* for fixed part of prompt below */
+ w2 = MAX(1,
+ MIN(space/2,MIN(strlen(ps_global->context_list->nickname),20)));
+ w1 = MAX(1,space - w2);
+ snprintf(prompt, sizeof(prompt),
+ "Add \"%s\" to %s list",
+ short_str(name,nbuf1,sizeof(nbuf1),w1,MidDots),
+ short_str(ps_global->context_list->nickname,nbuf2,sizeof(nbuf2),w2,MidDots));
+ prompt[sizeof(prompt)-1] = '\0';
+ if(want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
+ char *pp;
+
+ nname[0] = '\0';
+ space = MAXPROMPT;
+ space -= 25;
+ w1 = MAX(1, space);
+ snprintf(prompt, sizeof(prompt), "Nickname for folder \"%s\" : ",
+ short_str(name,nbuf1,sizeof(nbuf1),w1,MidDots));
+ prompt[sizeof(prompt)-1] = '\0';
+ while(1){
+ int rc;
+ int flags = OE_APPEND_CURRENT;
+
+ rc = optionally_enter(nname, -FOOTER_ROWS(ps_global), 0,
+ sizeof(nname), prompt, NULL,
+ NO_HELP, &flags);
+ removing_leading_and_trailing_white_space(nname);
+ if(rc == 0 && *nname){
+ /* see if nickname already exists */
+ found = 0;
+ if(!strucmp(ps_global->inbox_name, nname))
+ found++;
+
+ for(i = 0;
+ !found &&
+ i < folder_total(FOLDERS(ps_global->context_list));
+ i++){
+ FOLDER_S *f;
+
+ f = folder_entry(i, FOLDERS(ps_global->context_list));
+ if(!strucmp(FLDR_NAME(f), nname))
+ found++;
+ }
+
+ if(found){
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Nickname \"%s\" is already in use"),
+ nname);
+ continue;
+ }
+
+ break;
+ }
+ else if(rc == 3)
+ q_status_message(SM_ORDER, 0, 3, _("No help yet."));
+ else if(rc == 1){
+ q_status_message1(SM_ORDER, 0, 3,
+ _("Not adding nickname to %s list"),
+ ps_global->context_list->nickname);
+ return;
+ }
+ }
+
+ pp = put_pair(nname, name);
+ f = new_folder(name, line_hash(pp));
+ f->nickname = cpystr(nname);
+ f->name_len = strlen(nname);
+ folder_insert(folder_total(FOLDERS(ps_global->context_list)), f,
+ FOLDERS(ps_global->context_list));
+
+ if(!*alval){
+ i = 0;
+ *alval = (char **)fs_get(2 * sizeof(char *));
+ }
+ else{
+ for(i = 0; (*alval)[i]; i++)
+ ;
+
+ fs_resize((void **)alval, (i + 2) * sizeof(char *));
+ }
+
+ (*alval)[i] = pp;
+ (*alval)[i+1] = NULL;
+ set_current_val(&ps_global->vars[V_INCOMING_FOLDERS], TRUE, TRUE);
+ write_pinerc(ps_global, ew, WRP_NONE);
+ }
+ }
+}
+
+
+int
+role_filt_exitcheck(CONF_S **cl, unsigned int flags)
+{
+ int rv, j, action;
+ char **to_folder = NULL, **spec_fldr = NULL;
+ CONF_S *ctmp;
+ NAMEVAL_S *f;
+#define ACT_UNKNOWN 0
+#define ACT_KILL 1
+#define ACT_MOVE 2
+#define ACT_MOVE_NOFOLDER 3
+#define ACT_STATE 4
+
+ /*
+ * We have to locate the lines which define the Filter Action and
+ * then check to see that it is set to something before allowing
+ * user to Exit.
+ */
+ action = ACT_UNKNOWN;
+ if(flags & CF_CHANGES && role_filt_ptr && PVAL(role_filt_ptr,ew)){
+ for(j = 0; (f = filter_types(j)); j++)
+ if(!strucmp(PVAL(role_filt_ptr,ew), f->name))
+ break;
+
+ switch(f ? f->value : -1){
+ case FILTER_KILL:
+ action = ACT_KILL;
+ break;
+
+ case FILTER_STATE:
+ action = ACT_STATE;
+ break;
+
+ case FILTER_FOLDER:
+ /*
+ * Check that the folder is set to something.
+ */
+
+ action = ACT_MOVE_NOFOLDER;
+ /* go to end of screen */
+ for(ctmp = (*cl);
+ ctmp && ctmp->next;
+ ctmp = next_confline(ctmp))
+ ;
+
+ /* back up to start of Filter Action */
+ for(;
+ ctmp &&
+ !(ctmp->flags & CF_STARTITEM && ctmp->var == role_filt_ptr);
+ ctmp = prev_confline(ctmp))
+ ;
+
+ /* skip back past NOSELECTs */
+ for(;
+ ctmp && (ctmp->flags & CF_NOSELECT);
+ ctmp = next_confline(ctmp))
+ ;
+
+ /* find line with new var (the Folder line) */
+ for(;
+ ctmp && (ctmp->var == role_filt_ptr);
+ ctmp = next_confline(ctmp))
+ ;
+
+ /* ok, we're finally at the Folder line */
+ if(ctmp && ctmp->var && LVAL(ctmp->var,ew)){
+ to_folder = copy_list_array(LVAL(ctmp->var,ew));
+ if(to_folder && to_folder[0])
+ action = ACT_MOVE;
+ }
+
+ break;
+
+ default:
+ dprint((1,
+ "Can't happen, role_filt_ptr set to %s\n",
+ PVAL(role_filt_ptr,ew) ? PVAL(role_filt_ptr,ew) : "?"));
+ break;
+ }
+ }
+
+ if(flags & CF_CHANGES){
+ switch(want_to((action == ACT_KILL)
+ ? _("Commit changes (\"Yes\" means matching messages will be deleted)")
+ : EXIT_PMT, 'y', 'x', h_config_undo, WT_FLUSH_IN)){
+ case 'y':
+ switch(action){
+ case ACT_KILL:
+ if((spec_fldr = get_role_specific_folder(cl)) != NULL){
+ rv = check_role_folders(spec_fldr, 0);
+ free_list_array(&spec_fldr);
+ if(rv == 2)
+ q_status_message(SM_ORDER,0,3,_("Ok, messages matching that Pattern will be deleted"));
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 3,
+ _("Ok, messages matching that Pattern will be deleted"));
+ rv = 2;
+ }
+ break;
+
+ case ACT_MOVE:
+ if((spec_fldr = get_role_specific_folder(cl)) != NULL){
+ rv = check_role_folders(spec_fldr, 0);
+ free_list_array(&spec_fldr);
+ if(to_folder && rv == 2)
+ rv = check_role_folders(to_folder, 1);
+ }
+ else
+ rv = check_role_folders(to_folder, 1);
+
+ break;
+
+ case ACT_MOVE_NOFOLDER:
+ rv = 0;
+ q_status_message(SM_ORDER, 3, 5,
+ _("Set a valid Filter Action before Exiting"));
+ break;
+
+ case ACT_STATE:
+ if((spec_fldr = get_role_specific_folder(cl)) != NULL){
+ rv = check_role_folders(spec_fldr, 0);
+ free_list_array(&spec_fldr);
+ }
+ else
+ rv = 2;
+
+ break;
+
+ default:
+ rv = 2;
+ dprint((1,
+ "This can't happen, role_filt_ptr or to_folder not set\n"));
+ break;
+ }
+
+ break;
+
+ case 'n':
+ q_status_message(SM_ORDER,3,5,_("No filter changes saved"));
+ rv = 10;
+ break;
+
+ case 'x': /* ^C */
+ default :
+ q_status_message(SM_ORDER,3,5,_("Changes not yet saved"));
+ rv = 0;
+ break;
+ }
+ }
+ else
+ rv = 2;
+
+ if(to_folder)
+ free_list_array(&to_folder);
+
+ return(rv);
+}
+
+
+/*
+ * Don't allow exit unless user has set the action to something.
+ */
+int
+role_filt_text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv;
+
+ switch(cmd){
+ case MC_EXIT:
+ rv = role_filt_exitcheck(cl, flags);
+ break;
+
+ default:
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ return(rv);
+}
+
+
+int
+role_filt_addhdr_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv;
+
+ switch(cmd){
+ case MC_EXIT:
+ rv = role_filt_exitcheck(cl, flags);
+ break;
+
+ default:
+ rv = role_addhdr_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ return rv;
+}
+
+int
+role_addhdr_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv;
+
+ switch(cmd){
+ case MC_ADDHDR:
+ case MC_EXIT:
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+
+ default:
+ rv = -1;
+ break;
+ }
+
+ return rv;
+}
+
+/*
+ * Don't allow exit unless user has set the action to something.
+ */
+int
+role_filt_radiobutton_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv;
+
+ switch(cmd){
+ case MC_EXIT:
+ rv = role_filt_exitcheck(cl, flags);
+ break;
+
+ default:
+ rv = role_radiobutton_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * simple radio-button style variable handler
+ */
+int
+role_radiobutton_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0, i;
+ CONF_S *ctmp, *spec_ctmp = NULL;
+ NAMEVAL_S *rule;
+ char **apval;
+
+ switch(cmd){
+ case MC_CHOICE : /* set/unset feature */
+
+ /* back up to first line */
+ for(ctmp = (*cl);
+ ctmp && ctmp->prev && !(ctmp->prev->flags & CF_NOSELECT);
+ ctmp = prev_confline(ctmp))
+ ;
+
+ for(i = 0; ctmp && (!(ctmp->flags & CF_NOSELECT)
+ || (*cl)->var == role_fldr_ptr
+ || (*cl)->var == role_afrom_ptr
+ || (*cl)->var == role_filt_ptr);
+ ctmp = next_confline(ctmp), i++){
+ if(((*cl)->var == role_fldr_ptr) ||
+ ((*cl)->var == role_afrom_ptr) ||
+ ((*cl)->var == role_filt_ptr)){
+ if((((*cl)->var == role_fldr_ptr) && !pat_fldr_types(i))
+ || (((*cl)->var == role_afrom_ptr)
+ && !inabook_fldr_types(i))
+ || (((*cl)->var == role_filt_ptr) && !filter_types(i))){
+ spec_ctmp = ctmp;
+ break;
+ }
+ }
+
+ ctmp->value[1] = ' ';
+ }
+
+ /* turn on current value */
+ (*cl)->value[1] = R_SELD;
+
+ if((*cl)->var == role_fldr_ptr){
+ for(ctmp = spec_ctmp;
+ ctmp && ctmp->varnamep == spec_ctmp;
+ ctmp = next_confline(ctmp))
+ if((*cl)->varmem == FLDR_SPECIFIC)
+ ctmp->flags &= ~CF_NOSELECT;
+ else
+ ctmp->flags |= CF_NOSELECT;
+
+ rule = pat_fldr_types((*cl)->varmem);
+ }
+ else if((*cl)->var == role_afrom_ptr){
+ for(ctmp = spec_ctmp;
+ ctmp && ctmp->varnamep == spec_ctmp;
+ ctmp = next_confline(ctmp))
+ if(((*cl)->varmem == IAB_SPEC_YES)
+ || ((*cl)->varmem == IAB_SPEC_NO))
+ ctmp->flags &= ~CF_NOSELECT;
+ else
+ ctmp->flags |= CF_NOSELECT;
+
+ rule = inabook_fldr_types((*cl)->varmem);
+ }
+ else if((*cl)->var == role_filt_ptr){
+ for(ctmp = spec_ctmp;
+ ctmp && ctmp->varnamep == spec_ctmp;
+ ctmp = next_confline(ctmp))
+ if((*cl)->varmem == FILTER_FOLDER)
+ ctmp->flags &= ~CF_NOSELECT;
+ else
+ ctmp->flags |= CF_NOSELECT;
+
+ rule = filter_types((*cl)->varmem);
+ }
+ else if((*cl)->var == role_forw_ptr)
+ rule = role_forw_types((*cl)->varmem);
+ else if((*cl)->var == role_repl_ptr)
+ rule = role_repl_types((*cl)->varmem);
+ else if((*cl)->var == role_status1_ptr ||
+ (*cl)->var == role_status2_ptr ||
+ (*cl)->var == role_status3_ptr ||
+ (*cl)->var == role_status4_ptr ||
+ (*cl)->var == role_status5_ptr ||
+ (*cl)->var == role_status6_ptr ||
+ (*cl)->var == role_status7_ptr ||
+ (*cl)->var == role_status8_ptr)
+ rule = role_status_types((*cl)->varmem);
+ else if((*cl)->var == msg_state1_ptr ||
+ (*cl)->var == msg_state2_ptr ||
+ (*cl)->var == msg_state3_ptr ||
+ (*cl)->var == msg_state4_ptr)
+ rule = msg_state_types((*cl)->varmem);
+ else
+ rule = role_comp_types((*cl)->varmem);
+
+ apval = APVAL((*cl)->var, ew);
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(apval)
+ *apval = cpystr(rule->name);
+
+ ps->mangled_body = 1; /* BUG: redraw it all for now? */
+ rv = 1;
+
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+int
+role_sort_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0, i;
+ CONF_S *ctmp;
+ char **apval;
+ SortOrder def_sort;
+ int def_sort_rev;
+
+ apval = APVAL((*cl)->var, ew);
+
+ switch(cmd){
+ case MC_CHOICE : /* set/unset feature */
+
+ if((*cl)->varmem >= 0){
+ def_sort_rev = (*cl)->varmem >= (short) EndofList;
+ def_sort = (SortOrder)((*cl)->varmem - (def_sort_rev * EndofList));
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s", sort_name(def_sort),
+ (def_sort_rev) ? "/Reverse" : "");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ }
+
+ if(apval){
+ if(*apval)
+ fs_give((void **)apval);
+
+ if((*cl)->varmem >= 0)
+ *apval = cpystr(tmp_20k_buf);
+ }
+
+ /* back up to first line */
+ for(ctmp = (*cl);
+ ctmp && ctmp->prev && !(ctmp->prev->flags & CF_NOSELECT);
+ ctmp = prev_confline(ctmp))
+ ;
+
+ /* turn off all values */
+ for(i = 0;
+ ctmp && !(ctmp->flags & CF_NOSELECT);
+ ctmp = next_confline(ctmp), i++)
+ ctmp->value[1] = ' ';
+
+ /* turn on current value */
+ (*cl)->value[1] = R_SELD;
+
+ ps->mangled_body = 1; /* BUG: redraw it all for now? */
+ rv = 1;
+
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+/*
+ * Return an allocated list of the Specific Folder list for
+ * roles, or NULL if Current Folder type is not set to
+ * to Specific Folder
+ *
+ * WARNING, the method used in obtaining the specific folder is
+ * VERY dependent on the order in which it is presented on the
+ * screen. If the Current Folder radio buttons were changed,
+ * this function would probably need to be fixed accordingly.
+ */
+char **
+get_role_specific_folder(CONF_S **cl)
+{
+ CONF_S *ctmp;
+
+ /* go to the first line */
+ for(ctmp = *cl;
+ ctmp && ctmp->prev;
+ ctmp = prev_confline(ctmp))
+ ;
+
+ /* go to the current folder radio button list */
+ while(ctmp && ctmp->var != role_fldr_ptr)
+ ctmp = next_confline(ctmp);
+
+ /* go to the specific folder button (caution) */
+ while(ctmp && ctmp->varmem != FLDR_SPECIFIC)
+ ctmp = next_confline(ctmp);
+
+ /* check if selected (assumption of format "(*)" */
+ if(ctmp && ctmp->value[1] == R_SELD){
+ /* go to next line, the start of the list */
+ ctmp = next_confline(ctmp);
+ if(LVAL(ctmp->var, ew))
+ return copy_list_array(LVAL(ctmp->var, ew));
+ else{
+ char **ltmp;
+
+ /*
+ * Need to allocate empty string so as not to confuse it
+ * with the possibility that Specific Folder is not selected.
+ */
+ ltmp = (char **) fs_get(2 * sizeof(char *));
+ ltmp[0] = cpystr("");
+ ltmp[1] = NULL;
+ return(ltmp);
+ }
+ }
+ else
+ return NULL;
+}
+
+
+/*
+ */
+int
+role_litsig_text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv;
+
+ switch(cmd){
+ case MC_ADD :
+ case MC_EDIT :
+ rv = litsig_text_tool(ps, cmd, cl, flags);
+ if(rv)
+ calculate_inick_stuff(ps);
+
+ break;
+
+ default :
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ */
+int
+role_cstm_text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv;
+
+ switch(cmd){
+ case MC_EXIT :
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+
+ default :
+ rv = text_tool(ps, cmd, cl, flags);
+ if(rv == 1 && (*cl)->var){
+ char **lval;
+
+ lval = LVAL((*cl)->var, ew);
+ if(lval && lval[(*cl)->varmem] &&
+ ((!struncmp(lval[(*cl)->varmem],"from",4) &&
+ (lval[(*cl)->varmem][4] == ':' ||
+ lval[(*cl)->varmem][4] == '\0')) ||
+ (!struncmp(lval[(*cl)->varmem],"reply-to",8) &&
+ (lval[(*cl)->varmem][8] == ':' ||
+ lval[(*cl)->varmem][8] == '\0'))))
+ q_status_message1(SM_ORDER|SM_DING, 5, 7,
+ "Use \"Set %s\" instead, Change ignored",
+ !struncmp(lval[(*cl)->varmem],"from",4)
+ ? "From" : "Reply-To");
+ }
+
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ */
+int
+role_text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ OPT_SCREEN_S *saved_screen;
+ int rv = -1, oeflags, len, sig, r, i, cancel = 0;
+ char *file, *err, title[20], *newfile, *lc, *addr, *fldr = NULL, *tmpfldr;
+ char dir2[MAXPATH+1], pdir[MAXPATH+1], *p;
+ char full_filename[MAXPATH+1], filename[MAXPATH+1];
+ char tmp[MAXPATH+1], **spec_fldr, **apval;
+ EARB_S *earb, *ea, *eaprev;
+ CONF_S *ctmp, *ctmpb, *newcp, *ctend;
+ HelpType help;
+
+ switch(cmd){
+ case MC_EXIT :
+ if(flags & CF_CHANGES){
+ switch(want_to(EXIT_PMT, 'y', 'x', h_config_role_undo, WT_FLUSH_IN)){
+ case 'y':
+ if((spec_fldr = get_role_specific_folder(cl)) != NULL){
+ rv = check_role_folders(spec_fldr, 0);
+ free_list_array(&spec_fldr);
+ }
+ else
+ rv = 2;
+ break;
+
+ case 'n':
+ q_status_message(SM_ORDER,3,5,_("No changes saved"));
+ rv = 10;
+ break;
+
+ case 'x': /* ^C */
+ q_status_message(SM_ORDER,3,5,_("Changes not yet saved"));
+ rv = 0;
+ break;
+ }
+ }
+ else
+ rv = 2;
+
+ break;
+
+ case MC_NOT : /* toggle between !matching and matching */
+ ctmp = (*cl)->varnamep;
+ if(ctmp->varname && ctmp->var && ctmp->var->name){
+ if(!strncmp(ctmp->varname, NOT, NOTLEN) &&
+ !strncmp(ctmp->var->name, NOT, NOTLEN)){
+ rplstr(ctmp->var->name, strlen(ctmp->var->name)+1, NOTLEN, "");
+ rplstr(ctmp->varname, strlen(ctmp->varname)+1, NOTLEN, "");
+ strncpy(ctmp->varname+strlen(ctmp->varname)-1,
+ repeat_char(NOTLEN, ' '), NOTLEN+1);
+ strncat(ctmp->varname, "=", NOTLEN);
+ }
+ else{
+ rplstr(ctmp->var->name, strlen(ctmp->var->name)+NOTLEN+1, 0, NOT);
+ strncpy(ctmp->varname+strlen(ctmp->varname)-1-NOTLEN, "=", NOTLEN);
+ rplstr(ctmp->varname, strlen(ctmp->varname)+NOTLEN+1, 0, NOT);
+ }
+
+ rv = 1;
+ }
+
+ break;
+
+ case MC_CHOICE : /* Choose a file */
+ /*
+ * In signature_path we read signature files relative to the pinerc
+ * dir, so if user selects one that is in there we'll make it
+ * relative instead of absolute, so it looks nicer.
+ */
+ pdir[0] = '\0';
+ if(ps_global->VAR_OPER_DIR){
+ strncpy(pdir, ps_global->VAR_OPER_DIR, MAXPATH);
+ pdir[MAXPATH] = '\0';
+ len = strlen(pdir) + 1;
+ }
+ else if((lc = last_cmpnt(ps_global->pinerc)) != NULL){
+ strncpy(pdir, ps_global->pinerc, MIN(MAXPATH,lc-ps_global->pinerc));
+ pdir[MIN(MAXPATH, lc-ps_global->pinerc)] = '\0';
+ len = strlen(pdir);
+ }
+
+ strncpy(title, "CHOOSE A", 15);
+ strncpy(dir2, pdir, MAXPATH);
+
+ filename[0] = '\0';
+ build_path(full_filename, dir2, filename, sizeof(full_filename));
+
+ r = file_lister(title, dir2, sizeof(dir2), filename, sizeof(filename), TRUE, FB_READ);
+ ps->mangled_screen = 1;
+
+ if(r == 1){
+ build_path(full_filename, dir2, filename, sizeof(full_filename));
+ removing_leading_and_trailing_white_space(full_filename);
+ if(!strncmp(full_filename, pdir, strlen(pdir)))
+ newfile = cpystr(full_filename + len);
+ else
+ newfile = cpystr(full_filename);
+
+ apval = APVAL((*cl)->var, ew);
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(apval)
+ *apval = newfile;
+
+ if((*cl)->value)
+ fs_give((void **)&((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else
+ rv = 0;
+
+ break;
+
+ case MC_CHOICEB : /* Choose Addresses, no full names */
+ addr = addr_book_multaddr_nf();
+ ps->mangled_screen = 1;
+ if(addr && (*cl)->var && (*cl)->var->is_list){
+ char **ltmp, *tmp;
+ int i;
+
+ i = 0;
+ for(tmp = addr; *tmp; tmp++)
+ if(*tmp == ',')
+ i++; /* conservative count of ,'s */
+
+ ltmp = parse_list(addr, i + 1, PL_COMMAQUOTE, NULL);
+ fs_give((void **) &addr);
+
+ if(ltmp && ltmp[0])
+ config_add_list(ps, cl, ltmp, NULL, 0);
+
+ if(ltmp)
+ fs_give((void **) &ltmp);
+
+ if((*cl)->value)
+ fs_give((void **)&((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else
+ rv = 0;
+
+ break;
+
+ case MC_CHOICEC : /* Choose an Address, no full name */
+ addr = addr_book_oneaddr();
+ ps->mangled_screen = 1;
+ if(addr){
+ apval = APVAL((*cl)->var, ew);
+ if(apval && *apval) /* replace current value */
+ fs_give((void **)apval);
+
+ if(apval)
+ *apval = addr;
+
+ if((*cl)->value)
+ fs_give((void **)&((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else
+ rv = 0;
+
+ break;
+
+ case MC_CHOICED : /* Choose a Folder */
+ case MC_CHOICEE :
+ saved_screen = opt_screen;
+ if(cmd == MC_CHOICED)
+ tmpfldr = folder_for_config(FOR_PATTERN);
+ else
+ tmpfldr = folder_for_config(0);
+
+ if(tmpfldr){
+ fldr = add_comma_escapes(tmpfldr);
+ fs_give((void**) &tmpfldr);
+ }
+
+ opt_screen = saved_screen;
+
+ ps->mangled_screen = 1;
+ if(fldr && *fldr && (*cl)->var && (*cl)->var->is_list){
+ char **ltmp;
+
+ ltmp = (char **) fs_get(2 * sizeof(char *));
+ ltmp[0] = fldr;
+ ltmp[1] = NULL;
+ fldr = NULL;
+
+ if(ltmp && ltmp[0])
+ config_add_list(ps, cl, ltmp, NULL, 0);
+
+ if(ltmp)
+ fs_give((void **) &ltmp);
+
+ if((*cl)->value)
+ fs_give((void **) &((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else if(fldr && *fldr && (*cl)->var && !(*cl)->var->is_list){
+ apval = APVAL((*cl)->var, ew);
+ if(apval && *apval) /* replace current value */
+ fs_give((void **)apval);
+
+ if(apval){
+ *apval = fldr;
+ fldr = NULL;
+ }
+
+ if((*cl)->value)
+ fs_give((void **) &((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else
+ rv = 0;
+
+ if(fldr)
+ fs_give((void **) &fldr);
+
+ break;
+
+ case MC_EDITFILE :
+ file = ((*cl)->var && PVAL((*cl)->var, ew))
+ ? cpystr(PVAL((*cl)->var, ew)) : NULL;
+ if(file)
+ removing_leading_and_trailing_white_space(file);
+
+ sig = (srchstr((*cl)->varname, "signature") != NULL);
+ if(!file || !*file){
+ err = (char *)fs_get(100 * sizeof(char));
+ snprintf(err, 100, "No %s file defined. First define a file name.",
+ sig ? "signature" : "template");
+ err[100-1] = '\0';
+ }
+ else{
+ if(file[len=(strlen(file)-1)] == '|')
+ file[len] = '\0';
+
+ snprintf(title, sizeof(title), "%s EDITOR", sig ? "SIGNATURE" : "TEMPLATE");
+ title[sizeof(title)-1] = '\0';
+ err = signature_edit(file, title);
+ }
+
+ fs_give((void **)&file);
+ if(err){
+ q_status_message1(SM_ORDER, 3, 5, "%s", err);
+ fs_give((void **)&err);
+ }
+
+ rv = 0;
+ ps->mangled_screen = 1;
+ break;
+
+ /* Add an arbitrary header to this role */
+ case MC_ADDHDR :
+ rv = 0;
+ /* make earb point to last one */
+ for(earb = *(*cl)->d.earb; earb && earb->next; earb = earb->next)
+ ;
+
+ /* Add new one to end of list */
+ ea = (EARB_S *)fs_get(sizeof(*ea));
+ memset((void *)ea, 0, sizeof(*ea));
+ ea->v = (struct variable *)fs_get(sizeof(struct variable));
+ memset((void *)ea->v, 0, sizeof(struct variable));
+ ea->a = (ARBHDR_S *)fs_get(sizeof(ARBHDR_S));
+ memset((void *)ea->a, 0, sizeof(ARBHDR_S));
+
+ /* get new header field name */
+ help = NO_HELP;
+ tmp[0] = '\0';
+ while(1){
+ i = optionally_enter(tmp, -FOOTER_ROWS(ps), 0, sizeof(tmp),
+ _("Enter the name of the header field to be added: "),
+ NULL, help, NULL);
+ if(i == 0)
+ break;
+ else if(i == 1){
+ cmd_cancelled("eXtraHdr");
+ cancel = 1;
+ break;
+ }
+ else if(i == 3){
+ help = help == NO_HELP ? h_config_add_pat_hdr : NO_HELP;
+ continue;
+ }
+ else
+ break;
+ }
+
+ ps->mangled_footer = 1;
+
+ removing_leading_and_trailing_white_space(tmp);
+ if(tmp[strlen(tmp)-1] == ':') /* remove trailing colon */
+ tmp[strlen(tmp)-1] = '\0';
+
+ removing_trailing_white_space(tmp);
+
+ if(cancel || !tmp[0])
+ break;
+
+ tmp[0] = islower((unsigned char)tmp[0]) ? toupper((unsigned char)tmp[0])
+ : tmp[0];
+ ea->a->field = cpystr(tmp);
+
+ if(earb)
+ earb->next = ea;
+ else
+ *((*cl)->d.earb) = ea;
+
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ /*
+ * Go to the Add Extra Headers line. We will put the
+ * new header before this line.
+ */
+ for(; ctmp; ctmp = next_confline(ctmp))
+ if(ctmp->value && !strcmp(ctmp->value, ADDXHDRS))
+ break;
+
+ /* move back one */
+ if(ctmp)
+ ctmp = prev_confline(ctmp);
+
+ /*
+ * Add a new line after this point, which is after the last
+ * extra header (if any) or after the Participant pattern, and
+ * before the Add Extra Headers placeholder line.
+ */
+ p = (char *) fs_get(strlen(tmp) + strlen(" pattern") + 1);
+ snprintf(p, strlen(tmp) + strlen(" pattern") + 1, "%s pattern", tmp);
+ p[strlen(tmp) + strlen(" pattern") + 1 - 1] = '\0';
+ setup_dummy_pattern_var(ea->v, p, NULL);
+ fs_give((void **) &p);
+
+ /* find what indent should be */
+ if(ctmp && ctmp->varnamep && ctmp->varnamep->varname)
+ i = MIN(MAX(utf8_width(ctmp->varnamep->varname) + 1, 3), 200);
+ else
+ i = 20;
+
+ setup_role_pat(ps, &ctmp, ea->v, h_config_role_arbpat,
+ ARB_HELP, &config_role_xtrahdr_keymenu,
+ ctmp->prev->tool, ctmp->prev->d.earb, i);
+
+ /*
+ * move current line to new line
+ */
+
+ newcp = ctmp;
+
+ /* check if new line comes before the top of the screen */
+ ctmpb = (opt_screen && opt_screen->top_line)
+ ? opt_screen->top_line->prev : NULL;
+ for(; ctmpb; ctmpb = prev_confline(ctmpb))
+ if(ctmpb == newcp)
+ break;
+ /*
+ * Keep the right lines visible.
+ * The if triggers if the new line is off the top of the screen, and
+ * it makes the new line be the top line.
+ * The else counts how many lines down the screen the new line is.
+ */
+
+ i = 0;
+ if(ctmpb == newcp)
+ opt_screen->top_line = newcp;
+ else{
+ for(ctmp = opt_screen->top_line; ctmp && ctmp != newcp;
+ i++, ctmp = next_confline(ctmp))
+ ;
+ }
+
+ if(i >= BODY_LINES(ps)){ /* new line is off screen */
+ /* move top line down this far */
+ i = i + 1 - BODY_LINES(ps);
+ for(ctmp = opt_screen->top_line;
+ i > 0;
+ i--, ctmp = next_confline(ctmp))
+ ;
+
+ opt_screen->top_line = ctmp;
+ }
+
+ *cl = newcp;
+
+ ps->mangled_screen = 1;
+ rv = 1;
+ break;
+
+ /* Delete an arbitrary header from this role */
+ case MC_DELHDR :
+ /*
+ * Find this one in earb list. We don't have a good way to locate
+ * it so we match the ea->v->name with ctmp->varname.
+ */
+ rv = 0;
+ eaprev = NULL;
+ for(ea = *(*cl)->d.earb; ea; ea = ea->next){
+ if((*cl)->varnamep && (*cl)->varnamep->varname
+ && ea->v && ea->v->name
+ && !strncmp((*cl)->varnamep->varname,
+ ea->v->name, strlen(ea->v->name)))
+ break;
+
+ eaprev = ea;
+ }
+
+ snprintf(tmp, sizeof(tmp), _("Really remove \"%s\" pattern from this rule"),
+ (ea && ea->a && ea->a->field) ? ea->a->field : "this");
+ tmp[sizeof(tmp)-1] = '\0';
+ if(want_to(tmp, 'y', 'n', NO_HELP, WT_NORM) != 'y'){
+ cmd_cancelled("RemoveHdr");
+ return(rv);
+ }
+
+ /* delete the earb element from the list */
+ if(ea){
+ if(eaprev)
+ eaprev->next = ea->next;
+ else
+ *(*cl)->d.earb = ea->next;
+
+ ea->next = NULL;
+ free_earb(&ea);
+ }
+
+ /* remember start of deleted header */
+ ctmp = (*cl && (*cl)->varnamep) ? (*cl)->varnamep : NULL;
+
+ /* and end of deleted header */
+ for(ctend = *cl; ctend; ctend = next_confline(ctend))
+ if(!ctend->next || ctend->next->varnamep != ctmp)
+ break;
+
+ /* check if top line is one we're deleting */
+ for(ctmpb = ctmp; ctmpb; ctmpb = next_confline(ctmpb)){
+ if(ctmpb == opt_screen->top_line)
+ break;
+
+ if(ctmpb == (*cl))
+ break;
+ }
+
+ if(ctmpb == opt_screen->top_line)
+ opt_screen->top_line = ctend ? ctend->next : NULL;
+
+ /* move current line after this header */
+ *cl = ctend ? ctend->next : NULL;
+
+ /* remove deleted header lines */
+ if(ctmp && ctend){
+ /* remove from linked list */
+ if(ctmp->prev)
+ ctmp->prev->next = ctend->next;
+
+ if(ctend->next)
+ ctend->next->prev = ctmp->prev;
+
+ /* free memory */
+ ctmp->prev = ctend->next = NULL;
+ free_conflines(&ctmp);
+ }
+
+ ps->mangled_body = 1;
+ rv = 1;
+ break;
+
+ default :
+ if(((*cl)->var == scorei_pat_global_ptr
+ || (*cl)->var == age_pat_global_ptr
+ || (*cl)->var == size_pat_global_ptr
+ || (*cl)->var == cati_global_ptr)
+ &&
+ (cmd == MC_EDIT || (cmd == MC_ADD && !PVAL((*cl)->var, ew)))){
+ char prompt[60];
+
+ rv = 0;
+ snprintf(prompt, sizeof(prompt), "%s the interval : ",
+ PVAL((*cl)->var, ew) ? "Change" : "Enter");
+ prompt[sizeof(prompt)-1] = '\0';
+
+ ps->mangled_footer = 1;
+ help = NO_HELP;
+ tmp[0] = '\0';
+ snprintf(tmp, sizeof(tmp),
+ "%s", PVAL((*cl)->var, ew) ? PVAL((*cl)->var, ew) : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ while(1){
+ oeflags = OE_APPEND_CURRENT;
+ i = optionally_enter(tmp, -FOOTER_ROWS(ps), 0, sizeof(tmp),
+ prompt, NULL, help, &oeflags);
+ if(i == 0){
+ rv = ps->mangled_body = 1;
+ apval = APVAL((*cl)->var, ew);
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(apval && tmp[0])
+ *apval = cpystr(tmp);
+
+ fix_side_effects(ps, (*cl)->var, 0);
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ (*cl)->value = pretty_value(ps, *cl);
+ }
+ else if(i == 1)
+ cmd_cancelled(cmd == MC_ADD ? "Add" : "Change");
+ else if(i == 3){
+ help = help == NO_HELP ? h_config_edit_scorei : NO_HELP;
+ continue;
+ }
+ else if(i == 4)
+ continue;
+
+ break;
+ }
+ }
+ else{
+ if(cmd == MC_ADD && (*cl)->var && !(*cl)->var->is_list)
+ cmd = MC_EDIT;
+
+ rv = text_toolit(ps, cmd, cl, flags, 1);
+
+ /* make sure the earb pointers are set */
+ for(ctmp = (*cl)->varnamep;
+ ctmp->next && ctmp->next->var == ctmp->var;
+ ctmp = next_confline(ctmp))
+ ctmp->next->d.earb = ctmp->d.earb;
+ }
+
+ break;
+ }
+
+ /*
+ * If the inherit nickname changed, we have to re-calculate the
+ * global_vals and values for the action variables.
+ * We may have to do the same if literal sig changed, too.
+ */
+ if(rv)
+ calculate_inick_stuff(ps);
+
+ return(rv);
+}
+
+
+/*
+ */
+int
+role_text_tool_inick(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = -1;
+ char **apval;
+
+ switch(cmd){
+ case MC_EXIT :
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+
+ case MC_CHOICE : /* Choose a role nickname */
+ {void (*prev_screen)(struct pine *) = ps->prev_screen,
+ (*redraw)(void) = ps->redrawer;
+ OPT_SCREEN_S *saved_screen;
+ ACTION_S *role;
+
+ ps->redrawer = NULL;
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ saved_screen = opt_screen;
+ if(role_select_screen(ps, &role, 0) == 0){
+ apval = APVAL((*cl)->var, ew);
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(apval)
+ *apval = (role && role->nick) ? cpystr(role->nick) : NULL;
+
+ if((*cl)->value)
+ fs_give((void **)&((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else{
+ ps->next_screen = prev_screen;
+ ps->redrawer = redraw;
+ rv = 0;
+ }
+
+ opt_screen = saved_screen;
+ }
+
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_EDIT :
+ case MC_ADD :
+ case MC_DELETE :
+ rv = text_tool(ps, cmd, cl, flags);
+ ps->mangled_screen = 1;
+ break;
+
+ default :
+ rv = text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ /*
+ * If the inherit nickname changed, we have to re-calculate the
+ * global_vals and values for the action variables.
+ * We may have to do the same if literal sig changed, too.
+ */
+ if(rv)
+ calculate_inick_stuff(ps);
+
+ return(rv);
+}
+
+
+/*
+ */
+int
+role_text_tool_kword(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int i, j, rv = -1;
+ char **lval;
+
+ switch(cmd){
+ case MC_CHOICE : /* Choose keywords from list and add them */
+ {void (*prev_screen)(struct pine *) = ps->prev_screen,
+ (*redraw)(void) = ps->redrawer;
+ OPT_SCREEN_S *saved_screen;
+ char *esc;
+ char **kw;
+
+ ps->redrawer = NULL;
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ saved_screen = opt_screen;
+
+ if((kw=choose_list_of_keywords()) != NULL){
+ for(i = 0; kw[i]; i++){
+ esc = add_roletake_escapes(kw[i]);
+ fs_give((void **) &kw[i]);
+ kw[i] = esc;
+ }
+
+ /* eliminate duplicates before the add */
+ lval = LVAL((*cl)->var, ew);
+ if(lval && *lval){
+ for(i = 0; kw[i]; ){
+ /* if kw[i] is a dup, eliminate it */
+ for(j = 0; lval[j]; j++)
+ if(!strcmp(kw[i], lval[j]))
+ break;
+
+ if(lval[j]){ /* it is a dup */
+ for(j = i; kw[j]; j++)
+ kw[j] = kw[j+1];
+ }
+ else
+ i++;
+ }
+ }
+
+ if(kw[0])
+ config_add_list(ps, cl, kw, NULL, 0);
+
+ fs_give((void **) &kw);
+
+ if((*cl)->value)
+ fs_give((void **) &((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else{
+ ps->next_screen = prev_screen;
+ ps->redrawer = redraw;
+ rv = 0;
+ }
+
+ opt_screen = saved_screen;
+ }
+
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_EDIT :
+ case MC_ADD :
+ case MC_DELETE :
+ case MC_NOT :
+ rv = role_text_tool(ps, cmd, cl, flags);
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_EXIT :
+ default :
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ */
+int
+role_text_tool_charset(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int i, j, rv = -1;
+ char **lval;
+
+ switch(cmd){
+ case MC_CHOICE : /* Choose charsets from list and add them */
+ {void (*prev_screen)(struct pine *) = ps->prev_screen,
+ (*redraw)(void) = ps->redrawer;
+ OPT_SCREEN_S *saved_screen;
+ char *esc;
+ char **kw;
+
+ ps->redrawer = NULL;
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ saved_screen = opt_screen;
+
+ if((kw=choose_list_of_charsets()) != NULL){
+ for(i = 0; kw[i]; i++){
+ esc = add_roletake_escapes(kw[i]);
+ fs_give((void **) &kw[i]);
+ kw[i] = esc;
+ }
+
+ /* eliminate duplicates before the add */
+ lval = LVAL((*cl)->var, ew);
+ if(lval && *lval){
+ for(i = 0; kw[i]; ){
+ /* if kw[i] is a dup, eliminate it */
+ for(j = 0; lval[j]; j++)
+ if(!strcmp(kw[i], lval[j]))
+ break;
+
+ if(lval[j]){ /* it is a dup */
+ for(j = i; kw[j]; j++)
+ kw[j] = kw[j+1];
+ }
+ else
+ i++;
+ }
+ }
+
+ if(kw[0])
+ config_add_list(ps, cl, kw, NULL, 0);
+
+ fs_give((void **) &kw);
+
+ if((*cl)->value)
+ fs_give((void **) &((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else{
+ ps->next_screen = prev_screen;
+ ps->redrawer = redraw;
+ rv = 0;
+ }
+
+ opt_screen = saved_screen;
+ }
+
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_EDIT :
+ case MC_ADD :
+ case MC_DELETE :
+ case MC_NOT :
+ rv = role_text_tool(ps, cmd, cl, flags);
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_EXIT :
+ default :
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Choose an address book nickname
+ */
+int
+role_text_tool_afrom(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = -1;
+
+ switch(cmd){
+ case MC_EXIT :
+ rv = role_text_tool(ps, cmd, cl, flags);
+ break;
+
+ case MC_CHOICE : /* Choose an addressbook */
+ {OPT_SCREEN_S *saved_screen;
+ char *abook = NULL, *abookesc = NULL;
+
+ ps->redrawer = NULL;
+ ps->next_screen = SCREEN_FUN_NULL;
+ saved_screen = opt_screen;
+
+ abook = abook_select_screen(ps);
+ if(abook){
+ abookesc = add_comma_escapes(abook);
+ fs_give((void**) &abook);
+ }
+
+ ps->mangled_screen = 1;
+ if(abookesc && *abookesc && (*cl)->var && (*cl)->var->is_list){
+ char **ltmp;
+
+ ltmp = (char **) fs_get(2 * sizeof(char *));
+ ltmp[0] = abookesc;
+ ltmp[1] = NULL;
+ abookesc = NULL;
+
+ if(ltmp && ltmp[0])
+ config_add_list(ps, cl, ltmp, NULL, 0);
+
+ if(ltmp)
+ fs_give((void **) &ltmp);
+
+ if((*cl)->value)
+ fs_give((void **) &((*cl)->value));
+
+ (*cl)->value = pretty_value(ps, *cl);
+ rv = 1;
+ }
+ else
+ rv = 0;
+
+ if(abookesc)
+ fs_give((void **) &abookesc);
+
+ opt_screen = saved_screen;
+ }
+
+ ps->mangled_screen = 1;
+ break;
+
+ default :
+ rv = text_tool(ps, cmd, cl, flags);
+ ps->mangled_screen = 1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Args fmt -- a printf style fmt string with a single %s
+ * buf -- place to put result, assumed large enough (strlen(fmt)+11)
+ * rflags -- controls what goes in buf
+ *
+ * Returns -- pointer to buf
+ */
+char *
+role_type_print(char *buf, size_t buflen, char *fmt, long int rflags)
+{
+#define CASE_MIXED 1
+#define CASE_UPPER 2
+#define CASE_LOWER 3
+ int cas = CASE_UPPER;
+ int prev_word_is_a = 0;
+ char *q, *p;
+
+ /* find %sRule to see what case */
+ if((p = srchstr(fmt, "%srule")) != NULL){
+ if(p[2] == 'R'){
+ if(p[3] == 'U')
+ cas = CASE_UPPER;
+ else
+ cas = CASE_MIXED;
+ }
+ else
+ cas = CASE_LOWER;
+
+ if(p-3 >= fmt &&
+ p[-1] == SPACE &&
+ (p[-2] == 'a' || p[-2] == 'A')
+ && p[-3] == SPACE)
+ prev_word_is_a++;
+ }
+
+ if(cas == CASE_UPPER)
+ q = (rflags & ROLE_DO_INCOLS) ? "INDEX COLOR " :
+ (rflags & ROLE_DO_FILTER) ? "FILTERING " :
+ (rflags & ROLE_DO_SCORES) ? "SCORING " :
+ (rflags & ROLE_DO_OTHER) ? "OTHER " :
+ (rflags & ROLE_DO_SRCH) ? "SEARCH " :
+ (rflags & ROLE_DO_ROLES) ? "ROLE " : "";
+ else if(cas == CASE_LOWER)
+ q = (rflags & ROLE_DO_INCOLS) ? "index color " :
+ (rflags & ROLE_DO_FILTER) ? "filtering " :
+ (rflags & ROLE_DO_SCORES) ? "scoring " :
+ (rflags & ROLE_DO_OTHER) ? "other " :
+ (rflags & ROLE_DO_OTHER) ? "search " :
+ (rflags & ROLE_DO_ROLES) ? "role " : "";
+ else
+ q = (rflags & ROLE_DO_INCOLS) ? "Index Color " :
+ (rflags & ROLE_DO_FILTER) ? "Filtering " :
+ (rflags & ROLE_DO_SCORES) ? "Scoring " :
+ (rflags & ROLE_DO_OTHER) ? "Other " :
+ (rflags & ROLE_DO_OTHER) ? "Search " :
+ (rflags & ROLE_DO_ROLES) ? "Role " : "";
+
+ /* it ain't right to say "a index" */
+ if(prev_word_is_a && !struncmp(q, "index", 5))
+ q += 6;
+
+ snprintf(buf, buflen, fmt, q);
+ buf[buflen-1] = '\0';
+ return(buf);
+}
+
+
+/*
+ * filter option list manipulation tool
+ *
+ *
+ * returns: -1 on unrecognized cmd, 0 if no change, 1 if change
+ */
+int
+feat_checkbox_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+
+ switch(cmd){
+ case MC_TOGGLE: /* mark/unmark option */
+ rv = 1;
+ toggle_feat_option_bit(ps, (*cl)->varmem, (*cl)->var, (*cl)->value);
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = role_filt_exitcheck(cl, flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+void
+toggle_feat_option_bit(struct pine *ps, int index, struct variable *var, char *value)
+{
+ NAMEVAL_S *f;
+
+ f = feat_feature_list(index);
+
+ /* flip the bit */
+ if(bitnset(f->value, feat_option_list))
+ clrbitn(f->value, feat_option_list);
+ else
+ setbitn(f->value, feat_option_list);
+
+ if(value)
+ value[1] = bitnset(f->value, feat_option_list) ? 'X' : ' ';
+}
+
+
+NAMEVAL_S *
+feat_feature_list(int index)
+{
+ static NAMEVAL_S opt_feat_list[] = {
+ {"use-date-header-for-age", NULL, FEAT_SENTDATE},
+ {"move-only-if-not-deleted", NULL, FEAT_IFNOTDEL},
+ {"dont-stop-even-if-rule-matches", NULL, FEAT_NONTERM}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(opt_feat_list)/sizeof(opt_feat_list[0])))
+ ? &opt_feat_list[index] : NULL);
+}
+
+
+/*
+ * address type list manipulation tool
+ *
+ *
+ * returns: -1 on unrecognized cmd, 0 if no change, 1 if change
+ */
+int
+inabook_checkbox_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+
+ switch(cmd){
+ case MC_TOGGLE: /* mark/unmark option */
+ rv = 1;
+ toggle_inabook_type_bit(ps, (*cl)->varmem, (*cl)->var, (*cl)->value);
+ break;
+
+ case MC_EXIT: /* exit */
+ rv = role_filt_exitcheck(cl, flags);
+ break;
+
+ default :
+ rv = -1;
+ break;
+ }
+
+ return(rv);
+}
+
+
+void
+toggle_inabook_type_bit(struct pine *ps, int index, struct variable *var, char *value)
+{
+ NAMEVAL_S *f;
+
+ f = inabook_feature_list(index);
+
+ /* flip the bit */
+ if(bitnset(f->value, inabook_type_list))
+ clrbitn(f->value, inabook_type_list);
+ else
+ setbitn(f->value, inabook_type_list);
+
+ if(value)
+ value[1] = bitnset(f->value, inabook_type_list) ? 'X' : ' ';
+}
+
+
+NAMEVAL_S *
+inabook_feature_list(int index)
+{
+ static NAMEVAL_S inabook_feat_list[] = {
+ {"From", NULL, INABOOK_FROM},
+ {"Reply-To", NULL, INABOOK_REPLYTO},
+ {"Sender", NULL, INABOOK_SENDER},
+ {"To", NULL, INABOOK_TO},
+ {"Cc", NULL, INABOOK_CC}
+ };
+
+ return((index >= 0 &&
+ index < (sizeof(inabook_feat_list)/sizeof(inabook_feat_list[0])))
+ ? &inabook_feat_list[index] : NULL);
+}
diff --git a/alpine/roleconf.h b/alpine/roleconf.h
new file mode 100644
index 00000000..8e0bb7b1
--- /dev/null
+++ b/alpine/roleconf.h
@@ -0,0 +1,33 @@
+/*
+ * $Id: roleconf.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_ROLECONF_INCLUDED
+#define PINE_ROLECONF_INCLUDED
+
+
+#include "conftype.h"
+#include "../pith/state.h"
+#include "../pith/pattern.h"
+#include "../pith/msgno.h"
+
+
+/* exported protoypes */
+int role_select_screen(struct pine *, ACTION_S **, int);
+void role_config_screen(struct pine *, long, int);
+void role_take(struct pine *, MSGNO_S *, int);
+int role_radiobutton_tool(struct pine *, int, CONF_S **, unsigned);
+
+
+#endif /* PINE_ROLECONF_INCLUDED */
diff --git a/alpine/rpdump.c b/alpine/rpdump.c
new file mode 100644
index 00000000..d6b10f54
--- /dev/null
+++ b/alpine/rpdump.c
@@ -0,0 +1,818 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: rpdump.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "radio.h" /* OE_PASSWD */
+#include "../pith/util.h" /* IS_REMOTE() */
+#include "../pith/remote.h" /* REMOTE_ABOOK_SUBTYPE... */
+
+
+typedef enum {Pinerc, Abook, Sig, Smime, NotSet} RemoteType;
+
+
+int parse_args(int, char **, int *, char **, char **);
+RemoteType check_for_header_msg(MAILSTREAM *);
+char *ptype(RemoteType);
+char *spechdr(RemoteType);
+char *err_desc(int);
+int opt_enter(char *, int, char *, int *);
+char *last_cmpnt(char *);
+int wantto(char *, int, int);
+
+
+char *ustr = "usage: %s [-f] -l Local_file -r Remote_folder\n";
+int noshow_error = 0;
+
+/* look for my_timer_period in pico directory for an explanation */
+int my_timer_period = ((IDLE_TIMEOUT + 1)*1000);
+
+
+#ifdef _WINDOWS
+
+#undef main
+
+app_main (argc, argv)
+ int argc;
+ char argv[];
+{
+}
+
+#endif /* _WINDOWS */
+
+
+/*
+ * rpdump [-f] -l Local_file -r Remote_folder
+ *
+ * -f (skip check for special header)
+ *
+ * Note: We're not worrying about memory leaks.
+ */
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ MAILSTREAM *stream = NULL;
+ FILE *fp;
+ int usage = 0;
+ char buf[10000];
+ char *local = NULL, *remote = NULL;
+ int force = 0;
+ BODY *body = NULL;
+ char *data, *p;
+ RemoteType rtype;
+ unsigned long i;
+ struct stat sbuf;
+
+#include "../c-client/linkage.c"
+
+ if(parse_args(argc, argv, &force, &local, &remote)){
+ fprintf(stderr, ustr, argv[0]);
+ exit(-1);
+ }
+
+ if(!local || !*local){
+ fprintf(stderr, "No local file specified\n");
+ usage++;
+ }
+
+ if(!remote || !*remote){
+ fprintf(stderr, "No remote folder specified\n");
+ usage++;
+ }
+
+ if(usage){
+ fprintf(stderr, ustr, argv[0]);
+ exit(-1);
+ }
+
+ if(!IS_REMOTE(remote)){
+ fprintf(stderr,
+ "Remote folder name \"%s\" %s\n", remote,
+ (*remote != '{') ? "must begin with \"{\"" : "not valid");
+ exit(-1);
+ }
+
+ if(IS_REMOTE(local)){
+ fprintf(stderr, "Argument to -l (%s) must be a local filename", local);
+ exit(-1);
+ }
+
+#ifdef _WINDOWS
+ if(stat(local, &sbuf))
+#else
+ if(lstat(local, &sbuf))
+#endif
+ {
+ if(errno == ENOENT){ /* File did not exist */
+ int fd;
+
+ /* create it */
+ if(((fd = open(local, O_CREAT|O_EXCL|O_WRONLY,0600)) < 0)
+ || (close(fd) != 0)){
+ fprintf(stderr, "%s: %s\n", local, err_desc(errno));
+ exit(-1);
+ }
+
+ /* now it exists! */
+ }
+ else{ /* unknown error */
+ fprintf(stderr, "%s: %s\n", local, err_desc(errno));
+ exit(-1);
+ }
+ }
+ else{ /* file exists */
+
+ /* is it a regular file? */
+#ifdef S_ISREG
+ if(!S_ISREG(sbuf.st_mode))
+#else
+ if(!(S_IFREG & sbuf.st_mode))
+#endif
+ {
+ fprintf(stderr, "Only allowed to write to regular local files. Try another filename.\n");
+ exit(-1);
+ }
+
+ if(access(local, WRITE_ACCESS) == 0){
+
+ snprintf(buf, sizeof(buf), "Local file \"%s\" exists, overwrite it",
+ (p = last_cmpnt(local)) ? p : local);
+ if(wantto(buf, 'n', 'n') != 'y'){
+ fprintf(stderr, "Dump cancelled\n");
+ exit(-1);
+ }
+ }
+ else{
+ fprintf(stderr, "Local file \"%s\" is not writable\n", local);
+ exit(-1);
+ }
+ }
+
+ /*
+ * Try opening the local file.
+ */
+ if((fp = fopen(local, "w")) == NULL){
+ fprintf(stderr, "Can't open \"%s\": %s\n", local, err_desc(errno));
+ mail_close(stream);
+ exit(-1);
+ }
+
+ /*
+ * Try opening the remote folder.
+ */
+ stream = mail_open(NULL, remote, OP_READONLY);
+ if(!stream || stream->halfopen){
+ fprintf(stderr, "Remote folder \"%s\" is not readable\n", remote);
+ if(stream)
+ mail_close(stream);
+
+ exit(-1);
+ }
+
+ if(stream->nmsgs >= 2){
+ /*
+ * There is a first message already. Check to see if it is one of
+ * our special header messages.
+ */
+ rtype = check_for_header_msg(stream);
+ if(!force && rtype == NotSet){
+ fprintf(stderr, "Folder \"%s\"\ndoes not appear to be an Alpine remote data folder.\nUse -f to force.\n", remote);
+ mail_close(stream);
+ exit(-1);
+ }
+ }
+ else if(stream->nmsgs == 1){
+ fprintf(stderr, "No data in remote folder to copy (only 1 message)\n");
+ mail_close(stream);
+ exit(-1);
+ }
+ else{
+ fprintf(stderr, "No data in remote folder to copy\n");
+ mail_close(stream);
+ exit(-1);
+ }
+
+ if(!mail_fetchstructure(stream, stream->nmsgs, &body)){
+ fprintf(stderr, "Can't access remote IMAP data\n");
+ mail_close(stream);
+ exit(-1);
+ }
+
+ if(!body ||
+ body->type != REMOTE_DATA_TYPE ||
+ !body->subtype ||
+ (!force && strucmp(body->subtype, spechdr(rtype)))){
+ fprintf(stderr, "Remote IMAP folder has wrong contents\n");
+ mail_close(stream);
+ exit(-1);
+ }
+
+ if(!mail_fetchenvelope(stream, stream->nmsgs)){
+ fprintf(stderr, "Can't access envelope in remote data\n");
+ mail_close(stream);
+ exit(-1);
+ }
+
+ data = mail_fetch_body(stream, stream->nmsgs, "1", &i, FT_PEEK);
+
+ p = data;
+ for(p = data; p < data+i; p++){
+
+ /* convert to unix newlines */
+ if(*p == '\r' && *(p+1) == '\n')
+ p++;
+
+ if(putc(*p, fp) == EOF){
+ fprintf(stderr,
+ "Error writing \"%s\": %s\n", local, err_desc(errno));
+ fclose(fp);
+ mail_close(stream);
+ exit(-1);
+ }
+ }
+
+ mail_close(stream);
+ fclose(fp);
+
+ fprintf(stderr,
+ "Remote folder is of type \"%s\", contents saved to \"%s\"\n",
+ ptype(rtype) ? ptype(rtype) : "unknown", local);
+ exit(0);
+}
+
+
+RemoteType
+check_for_header_msg(stream)
+ MAILSTREAM *stream;
+{
+ STRINGLIST *sl;
+ int ret = NotSet;
+ char *h, *try;
+ size_t len;
+ char *pinerc, *abook, *sig, *smime;
+
+ pinerc = spechdr(Pinerc);
+ abook = spechdr(Abook);
+ sig = spechdr(Sig);
+ smime = spechdr(Smime);
+
+ len = MAX(MAX(strlen(pinerc), strlen(abook)), MAX(strlen(sig), strlen(smime)));
+
+ sl = mail_newstringlist();
+ sl->text.data = (unsigned char *)fs_get((len+1) * sizeof(unsigned char));
+ try = pinerc;
+ strncpy((char *) sl->text.data, try, len);
+ sl->text.data[len] = '\0';
+ sl->text.size = strlen((char *) sl->text.data);
+
+ if(stream && (h=mail_fetch_header(stream,1L,NULL,sl,NULL,FT_PEEK))){
+
+ if(strlen(h) >= sl->text.size && !struncmp(h, try, sl->text.size))
+ ret = Pinerc;
+ }
+
+ if(ret == NotSet){
+ try = abook;
+ strncpy((char *)sl->text.data, try, len);
+ sl->text.data[len] = '\0';
+ sl->text.size = strlen((char *) sl->text.data);
+ if(stream && (h=mail_fetch_header(stream,1L,NULL,sl,NULL,FT_PEEK))){
+
+ if(strlen(h) >= sl->text.size && !struncmp(h, try, sl->text.size))
+ ret = Abook;
+ }
+ }
+
+ if(ret == NotSet){
+ try = sig;
+ strncpy((char *) sl->text.data, try, len);
+ sl->text.data[len] = '\0';
+ sl->text.size = strlen((char *) sl->text.data);
+ if(stream && (h=mail_fetch_header(stream,1L,NULL,sl,NULL,FT_PEEK))){
+
+ if(strlen(h) >= sl->text.size && !struncmp(h, try, sl->text.size))
+ ret = Sig;
+ }
+ }
+
+ if(sl)
+ mail_free_stringlist(&sl);
+
+ if(pinerc)
+ fs_give((void **)&pinerc);
+ if(abook)
+ fs_give((void **)&abook);
+ if(sig)
+ fs_give((void **)&sig);
+ if(smime)
+ fs_give((void **)&smime);
+
+ return(ret);
+}
+
+
+char *
+ptype(rtype)
+ RemoteType rtype;
+{
+ char *ret = NULL;
+
+ switch(rtype){
+ case Pinerc:
+ ret = cpystr("pinerc");
+ break;
+ case Abook:
+ ret = cpystr("abook");
+ break;
+ case Sig:
+ ret = cpystr("sig");
+ break;
+ case Smime:
+ ret = cpystr("smime");
+ break;
+ default:
+ break;
+ }
+
+ return(ret);
+}
+
+
+char *
+spechdr(rtype)
+ RemoteType rtype;
+{
+ char *ret = NULL;
+
+ switch(rtype){
+ case Pinerc:
+ ret = cpystr(REMOTE_PINERC_SUBTYPE);
+ break;
+ case Abook:
+ ret = cpystr(REMOTE_ABOOK_SUBTYPE);
+ break;
+ case Sig:
+ ret = cpystr(REMOTE_SIG_SUBTYPE);
+ break;
+ case Smime:
+ ret = cpystr(REMOTE_SMIME_SUBTYPE);
+ break;
+ default:
+ break;
+ }
+
+ return(ret);
+}
+
+
+int
+parse_args(argc, argv, force, local, remote)
+ int argc;
+ char **argv;
+ int *force;
+ char **local, **remote;
+{
+ int ac;
+ char **av;
+ int c;
+ char *str;
+ int usage = 0;
+
+ ac = argc;
+ av = argv;
+
+ /* while more arguments with leading - */
+Loop: while(--ac > 0 && **++av == '-'){
+ /* while more chars in this argument */
+ while(*++*av){
+ switch(c = **av){
+ case 'h':
+ usage++;
+ break;
+ case 'f':
+ (*force)++;
+ break;
+
+ case 'r': case 'l': /* string args */
+ if(*++*av)
+ str = *av;
+ else if(--ac)
+ str = *++av;
+ else{
+ fprintf(stderr, "missing argument for flag \"%c\"\n", c);
+ ++usage;
+ goto Loop;
+ }
+
+ switch(c){
+ case 'l':
+ if(str)
+ *local = str;
+
+ break;
+ case 'r':
+ if(str)
+ *remote = str;
+
+ break;
+ }
+
+ goto Loop;
+
+ default:
+ fprintf(stderr, "unknown flag \"%c\"\n", c);
+ ++usage;
+ break;
+ }
+ }
+ }
+
+ if(ac != 0)
+ usage++;
+
+ return(usage);
+}
+
+
+char *
+err_desc(err)
+ int err;
+{
+ return((char *) strerror(err));
+}
+
+
+void mm_exists(stream, number)
+ MAILSTREAM *stream;
+ unsigned long number;
+{
+}
+
+
+void mm_expunged(stream, number)
+ MAILSTREAM *stream;
+ unsigned long number;
+{
+}
+
+
+void mm_flags(stream, number)
+ MAILSTREAM *stream;
+ unsigned long number;
+{
+}
+
+
+void mm_list(stream, delim, name, attrib)
+ MAILSTREAM *stream;
+ int delim;
+ char *name;
+ long attrib;
+{
+}
+
+
+void mm_lsub(stream, delimiter, name, attributes)
+ MAILSTREAM *stream;
+ int delimiter;
+ char *name;
+ long attributes;
+{
+}
+
+
+void mm_notify(stream, string, errflg)
+ MAILSTREAM *stream;
+ char *string;
+ long errflg;
+{
+ mm_log(string, errflg);
+}
+
+
+void mm_log(string, errflg)
+ char *string;
+ long errflg;
+{
+ if(noshow_error)
+ return;
+
+ switch(errflg){
+ case BYE:
+ case NIL:
+ break;
+
+ case PARSE:
+ fprintf(stderr, "PARSE: %s\n", string);
+ break;
+
+ case WARN:
+ fprintf(stderr, "WARN: %s\n", string);
+ break;
+
+ case ERROR:
+ fprintf(stderr, "ERROR: %s\n", string);
+ break;
+
+ default:
+ fprintf(stderr, "%s\n", string);
+ break;
+ }
+}
+
+
+void mm_login(mb, user, pwd, trial)
+ NETMBX *mb;
+ char *user;
+ char *pwd;
+ long trial;
+{
+ char prompt[100], *last;
+ int i, j, goal, ugoal, len, rc, flags = 0;
+#define NETMAXPASSWD 100
+
+ user[NETMAXUSER-1] = '\0';
+
+ if(trial == 0L){
+ if(mb->user && *mb->user){
+ strncpy(user, mb->user, NETMAXUSER);
+ user[NETMAXUSER-1] = '\0';
+ }
+ }
+
+ if(!*mb->user){
+ /* Dress up long hostnames */
+ snprintf(prompt, sizeof(prompt), "%sHOST: ",
+ (mb->sslflag||mb->tlsflag) ? "+ " : "");
+ len = strlen(prompt);
+ /* leave space for "HOST", "ENTER NAME", and 15 chars for input name */
+ goal = 80 - (len + 20 + MIN(15, 80/5));
+ last = " ENTER LOGIN NAME: ";
+ if(goal < 9){
+ last = " LOGIN: ";
+ if((goal += 13) < 9){
+ last += 1;
+ goal = 0;
+ }
+ }
+
+ if(goal){
+ for(i = len, j = 0;
+ i < sizeof(prompt) && (prompt[i] = mb->host[j]); i++, j++)
+ if(i == goal && mb->host[goal+1] && i < sizeof(prompt)){
+ strncpy(&prompt[i-3], "...", sizeof(prompt)-(i-3));
+ prompt[sizeof(prompt)-1] = '\0';
+ break;
+ }
+ }
+ else
+ i = 0;
+
+ strncpy(&prompt[i], last, sizeof(prompt)-i);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ while(1) {
+ rc = opt_enter(user, NETMAXUSER, prompt, &flags);
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 1 || !user[0]) {
+ user[0] = '\0';
+ pwd[0] = '\0';
+ }
+ }
+ else
+ strncpy(user, mb->user, NETMAXUSER);
+
+ user[NETMAXUSER-1] = '\0';
+ pwd[NETMAXPASSWD-1] = '\0';
+
+ if(!user[0])
+ return;
+
+
+ /* Dress up long host/user names */
+ /* leave space for "HOST", "USER" "ENTER PWD", 12 for user 6 for pwd */
+ snprintf(prompt, sizeof(prompt), "%sHOST: ", (mb->sslflag||mb->tlsflag) ? "+ " : "");
+ len = strlen(prompt);
+ goal = strlen(mb->host);
+ ugoal = strlen(user);
+ if((i = 80 - (len + 8 + 18 + 6)) < 14){
+ goal = 0; /* no host! */
+ if((i = 80 - (6 + 18 + 6)) <= 6){
+ ugoal = 0; /* no user! */
+ if((i = 80 - (18 + 6)) <= 0)
+ i = 0;
+ }
+ else{
+ ugoal = i; /* whatever's left */
+ i = 0;
+ }
+ }
+ else
+ while(goal + ugoal > i)
+ if(goal > ugoal)
+ goal--;
+ else
+ ugoal--;
+
+ if(goal){
+ snprintf(prompt, sizeof(prompt), "%sHOST: ",
+ (mb->sslflag||mb->tlsflag) ? "+ " : "");
+ for(i = len, j = 0;
+ i < sizeof(prompt) && (prompt[i] = mb->host[j]); i++, j++)
+ if(i == goal && mb->host[goal+1] && i < sizeof(prompt)){
+ strncpy(&prompt[i-3], "...", sizeof(prompt)-(i-3));
+ prompt[sizeof(prompt)-1] = '\0';
+ break;
+ }
+ }
+ else
+ i = 0;
+
+ if(ugoal){
+ strncpy(&prompt[i], &" USER: "[i ? 0 : 2], sizeof(prompt)-i);
+ prompt[sizeof(prompt)-1] = '\0';
+ for(i += strlen(&prompt[i]), j = 0;
+ i < sizeof(prompt) && (prompt[i] = user[j]); i++, j++)
+ if(j == ugoal && user[ugoal+1] && i < sizeof(prompt)){
+ strncpy(&prompt[i-3], "...", sizeof(prompt)-(i-3));
+ prompt[sizeof(prompt)-1] = '\0';
+ break;
+ }
+ }
+
+ strncpy(&prompt[i], &" ENTER PASSWORD: "[i ? 0 : 8], sizeof(prompt)-i);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ *pwd = '\0';
+ while(1) {
+ flags = OE_PASSWD;
+ rc = opt_enter(pwd, NETMAXPASSWD, prompt, &flags);
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 1 || !pwd[0]) {
+ user[0] = pwd[0] = '\0';
+ return;
+ }
+}
+
+
+void mm_critical(stream)
+ MAILSTREAM *stream;
+{
+}
+
+
+void mm_nocritical(stream)
+ MAILSTREAM *stream;
+{
+}
+
+
+long mm_diskerror(stream, errcode, serious)
+ MAILSTREAM *stream;
+ long errcode;
+ long serious;
+{
+ return T;
+}
+
+
+void mm_fatal(string)
+ char *string;
+{
+ fprintf(stderr, "%s\n", string);
+}
+
+
+void mm_searched(stream, msgno)
+ MAILSTREAM *stream;
+ unsigned long msgno;
+{
+}
+
+
+void mm_status(stream, mailbox, status)
+ MAILSTREAM *stream;
+ char *mailbox;
+ MAILSTATUS *status;
+{
+}
+
+void mm_dlog(string)
+ char *string;
+{
+ fprintf(stderr, "%s\n", string);
+}
+
+
+int
+opt_enter(string, field_len, prompt, flags)
+ char *string, *prompt;
+ int field_len;
+ int *flags;
+{
+ char *pw;
+ int return_v = -10;
+
+ while(return_v == -10){
+
+ if(flags && *flags & OE_PASSWD){
+ if((pw = getpass(prompt)) != NULL){
+ if(strlen(pw) < field_len){
+ strncpy(string, pw, field_len);
+ string[field_len-1] = '\0';
+ return_v = 0;
+ }
+ else{
+ fputs("Password too long\n", stderr);
+ return_v = -1;
+ }
+ }
+ else
+ return_v = 1; /* cancel? */
+ }
+ else{
+ char *p;
+
+ fputs(prompt, stdout);
+ fgets(string, field_len, stdin);
+ string[field_len-1] = '\0';
+ if((p = strpbrk(string, "\r\n")) != NULL)
+ *p = '\0';
+
+ return_v = 0;
+ }
+ }
+
+ return(return_v);
+}
+
+char *
+last_cmpnt(filename)
+ char *filename;
+{
+ register char *p = NULL, *q = filename;
+
+ if(!q)
+ return(q);
+
+ while((q = strchr(q, '/')) != NULL)
+ if(*++q)
+ p = q;
+
+ return(p);
+}
+
+int
+wantto(question, dflt, on_ctrl_C)
+ char *question;
+ int dflt, on_ctrl_C;
+{
+ int ret = 0;
+ char rep[1000], *p;
+
+ while(!ret){
+ fprintf(stdout, "%s? [%c]:", question, dflt);
+ fgets(rep, sizeof(rep), stdin);
+ if((p = strpbrk(rep, "\r\n")) != NULL)
+ *p = '\0';
+ switch(*rep){
+ case 'Y':
+ case 'y':
+ ret = (int)'y';
+ break;
+ case 'N':
+ case 'n':
+ ret = (int)'n';
+ break;
+ case '\0':
+ ret = dflt;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return ret;
+}
diff --git a/alpine/rpload.c b/alpine/rpload.c
new file mode 100644
index 00000000..f5606611
--- /dev/null
+++ b/alpine/rpload.c
@@ -0,0 +1,1008 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: rpload.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "radio.h" /* OE_PASSWD */
+#include "../pith/util.h" /* IS_REMOTE() */
+#include "../pith/remote.h" /* REMOTE_ABOOK_SUBTYPE... */
+
+
+typedef enum {Pinerc, Abook, Sig, Smime, NotSet} RemoteType;
+
+
+int parse_args(int, char **, int *, int *, char **, char **, RemoteType *);
+RemoteType check_for_header_msg(MAILSTREAM *);
+char *ptype(RemoteType);
+char *spechdr(RemoteType);
+int add_initial_msg(MAILSTREAM *, char *, char *);
+int append_data(MAILSTREAM *, char *, char *, FILE *);
+void trim_data(MAILSTREAM *, int);
+void write_fake_headers(RFC822BUFFER *, char *, char *, char *);
+char *err_desc(int);
+int opt_enter(char *, int, char *, int *);
+
+
+int noshow_error = 0;
+char *ustr = "usage: %s [-s trimSize] [-f] -t Type -l Local_file -r Remote_folder\n";
+
+
+/* look for my_timer_period in pico directory for an explanation */
+int my_timer_period = ((IDLE_TIMEOUT + 1)*1000);
+
+
+#ifdef _WINDOWS
+
+#undef main
+
+app_main (argc, argv)
+ int argc;
+ char argv[];
+{
+}
+
+#endif /* _WINDOWS */
+
+
+/*
+ * rpload [-s trimSize] [-f] -t Type -l Local_file -r Remote_folder
+ *
+ * Type is one of abook
+ * pinerc
+ * smime
+ * sig (this is mostly obsolete, literal sigs
+ * should be used instead)
+ * -f means force the folder to be written even if it
+ * doesn't look like it is of the right format
+ *
+ * Note: We're not worrying about memory leaks.
+ */
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ MAILSTREAM *stream = NULL;
+ FILE *fp;
+ int delete_existing = 0, usage = 0;
+ int force = 0, trimsize = 0;
+ char *local = NULL, *remote = NULL, *special_hdr = NULL;
+ RemoteType rt, type = NotSet;
+
+#include "../c-client/linkage.c"
+
+ if(parse_args(argc, argv, &force, &trimsize, &local, &remote, &type)){
+ fprintf(stderr, ustr, argv[0]);
+ exit(-1);
+ }
+
+ if(!local || !*local){
+ fprintf(stderr, "No local file specified\n");
+ usage++;
+ }
+
+ if(!remote || !*remote){
+ fprintf(stderr, "No remote folder specified\n");
+ usage++;
+ }
+
+ if(type == NotSet){
+ fprintf(stderr, "Type must be set to one of:\n");
+ for(rt = Pinerc; rt != NotSet; rt++)
+ fprintf(stderr, " %s\n", ptype(rt));
+
+ usage++;
+ }
+
+ if(usage){
+ fprintf(stderr, ustr, argv[0]);
+ exit(-1);
+ }
+
+ if(!IS_REMOTE(remote)){
+ fprintf(stderr,
+ "Remote folder name \"%s\" %s\n", remote,
+ (*remote != '{') ? "must begin with \"{\"" : "not valid");
+ exit(-1);
+ }
+
+ if(IS_REMOTE(local)){
+ fprintf(stderr, "Argument to -l (%s) must be a local filename", local);
+ exit(-1);
+ }
+
+ if(access(local, ACCESS_EXISTS) != 0){
+ fprintf(stderr, "Local file \"%s\" does not exist\n", local);
+ exit(-1);
+ }
+
+ if(access(local, READ_ACCESS) != 0){
+ fprintf(stderr,
+ "Can't read local file \"%s\": %s\n",
+ local, err_desc(errno));
+ exit(-1);
+ }
+
+ /*
+ * Try opening the local file.
+ */
+ if((fp = fopen(local, "r")) == NULL){
+ fprintf(stderr, "Can't open \"%s\": %s\n", local, err_desc(errno));
+ exit(-1);
+ }
+
+ /*
+ * Try opening the remote folder. If it doesn't exist, create it.
+ */
+
+ /* failure would be normal here, so don't show it */
+ noshow_error = 1;
+ stream = mail_open(NULL, remote, 0L);
+ if(!stream || stream->halfopen){
+ if(stream && stream->halfopen){
+ noshow_error = 0;
+ if(!mail_create(stream, remote))
+ exit(-1);
+
+ stream = mail_open(stream, remote, 0L);
+ if(!stream || stream->halfopen)
+ exit(-1);
+ }
+ else{
+ fprintf(stderr, "Trouble opening remote folder \"%s\"\n", remote);
+ exit(-1);
+ }
+ }
+
+ noshow_error = 0;
+
+ if(stream->rdonly){
+ fprintf(stderr, "Remote folder \"%s\" is not writable\n", remote);
+ exit(-1);
+ }
+
+ if(stream->nmsgs > 0){
+ /*
+ * There is a first message already. Check to see if it is one of
+ * our special header messages.
+ */
+ rt = check_for_header_msg(stream);
+ if(rt == NotSet){
+ if(force)
+ delete_existing++;
+ else{
+ fprintf(stderr, "Folder \"%s\"\ndoes not appear to be an Alpine remote \"%s\" folder.\nUse -f to force.\n", remote, ptype(type));
+ fprintf(stderr, "-f will cause %ld messages to be deleted\n",
+ stream->nmsgs);
+ exit(-1);
+ }
+ }
+ else if(rt != type){
+ if(force)
+ delete_existing++;
+ else{
+ fprintf(stderr, "Folder \"%s\" is type \"%s\"\nUse -f to force switch.\n", remote, ptype(rt));
+ fprintf(stderr, "-f will cause %ld messages to be deleted\n",
+ stream->nmsgs);
+ exit(-1);
+ }
+ }
+ }
+
+ if(delete_existing){
+ char sequence[20];
+
+ mail_ping(stream);
+ snprintf(sequence, sizeof(sequence), "1:%ld", stream->nmsgs);
+ mail_flag(stream, sequence, "\\DELETED", ST_SET);
+ mail_expunge(stream);
+ mail_ping(stream);
+ }
+
+ special_hdr = spechdr(type);
+
+ /*
+ * Add the explanatory header message if needed.
+ */
+ if(stream->nmsgs == 0){
+ if(add_initial_msg(stream, remote, special_hdr) != 0){
+ mail_close(stream);
+ exit(-1);
+ }
+ }
+
+ /*
+ * Add the actual data in a message.
+ */
+ if(append_data(stream, remote, special_hdr, fp) != 0){
+ mail_close(stream);
+ exit(-1);
+ }
+
+ /*
+ * Trim the size of the remote folder.
+ */
+ if(trimsize)
+ trim_data(stream, trimsize);
+
+ mail_close(stream);
+ exit(0);
+}
+
+
+RemoteType
+check_for_header_msg(stream)
+ MAILSTREAM *stream;
+{
+ STRINGLIST *sl;
+ int ret = NotSet;
+ char *h, *try;
+ size_t len;
+ char *pinerc, *abook, *sig, *smime;
+
+ pinerc = spechdr(Pinerc);
+ abook = spechdr(Abook);
+ sig = spechdr(Sig);
+ smime = spechdr(Smime);
+
+ len = MAX(MAX(strlen(pinerc), strlen(abook)), MAX(strlen(sig), strlen(smime)));
+
+ sl = mail_newstringlist();
+ sl->text.data = (unsigned char *)fs_get((len+1) * sizeof(unsigned char));
+ try = pinerc;
+ strncpy((char *)sl->text.data, try, len);
+ sl->text.data[len] = '\0';
+ sl->text.size = strlen((char *) sl->text.data);
+
+ if(stream && (h=mail_fetch_header(stream,1L,NULL,sl,NULL,FT_PEEK))){
+
+ if(strlen(h) >= sl->text.size && !struncmp(h, try, sl->text.size))
+ ret = Pinerc;
+ }
+
+ if(ret == NotSet){
+ try = abook;
+ strncpy((char *)sl->text.data, try, len);
+ sl->text.data[len] = '\0';
+ sl->text.size = strlen((char *) sl->text.data);
+ if(stream && (h=mail_fetch_header(stream,1L,NULL,sl,NULL,FT_PEEK))){
+
+ if(strlen(h) >= sl->text.size && !struncmp(h, try, sl->text.size))
+ ret = Abook;
+ }
+ }
+
+ if(ret == NotSet){
+ try = sig;
+ strncpy((char *)sl->text.data, try, len);
+ sl->text.data[len] = '\0';
+ sl->text.size = strlen((char *) sl->text.data);
+ if(stream && (h=mail_fetch_header(stream,1L,NULL,sl,NULL,FT_PEEK))){
+
+ if(strlen(h) >= sl->text.size && !struncmp(h, try, sl->text.size))
+ ret = Sig;
+ }
+ }
+
+ if(sl)
+ mail_free_stringlist(&sl);
+
+ if(pinerc)
+ fs_give((void **)&pinerc);
+ if(abook)
+ fs_give((void **)&abook);
+ if(sig)
+ fs_give((void **)&sig);
+ if(smime)
+ fs_give((void **)&smime);
+
+ return(ret);
+}
+
+
+char *
+ptype(rtype)
+ RemoteType rtype;
+{
+ char *ret = NULL;
+
+ switch(rtype){
+ case Pinerc:
+ ret = cpystr("pinerc");
+ break;
+ case Abook:
+ ret = cpystr("abook");
+ break;
+ case Sig:
+ ret = cpystr("sig");
+ break;
+ case Smime:
+ ret = cpystr("smime");
+ break;
+ default:
+ break;
+ }
+
+ return(ret);
+}
+
+
+char *
+spechdr(rtype)
+ RemoteType rtype;
+{
+ char *ret = NULL;
+
+ switch(rtype){
+ case Pinerc:
+ ret = cpystr(REMOTE_PINERC_SUBTYPE);
+ break;
+ case Abook:
+ ret = cpystr(REMOTE_ABOOK_SUBTYPE);
+ break;
+ case Sig:
+ ret = cpystr(REMOTE_SIG_SUBTYPE);
+ break;
+ case Smime:
+ ret = cpystr(REMOTE_SMIME_SUBTYPE);
+ break;
+ default:
+ break;
+ }
+
+ return(ret);
+}
+
+
+int
+parse_args(argc, argv, force, trimsize, local, remote, type)
+ int argc;
+ char **argv;
+ int *force, *trimsize;
+ char **local, **remote;
+ RemoteType *type;
+{
+ int ac;
+ char **av;
+ int c;
+ char *str;
+ int usage = 0;
+ RemoteType rt;
+
+ ac = argc;
+ av = argv;
+
+ /* while more arguments with leading - */
+Loop: while(--ac > 0 && **++av == '-'){
+ /* while more chars in this argument */
+ while(*++*av){
+ switch(c = **av){
+ case 'h':
+ usage++;
+ break;
+ case 'f':
+ (*force)++;
+ break;
+
+ case 't': case 'l': /* string args */
+ case 'r':
+ case 's': /* integer args */
+ if(*++*av)
+ str = *av;
+ else if(--ac)
+ str = *++av;
+ else{
+ fprintf(stderr, "missing argument for flag \"%c\"\n", c);
+ ++usage;
+ goto Loop;
+ }
+
+ switch(c){
+ case 'l':
+ if(str)
+ *local = str;
+
+ break;
+ case 'r':
+ if(str)
+ *remote = str;
+
+ break;
+ case 't':
+ for(rt = Pinerc; rt != NotSet; rt++){
+ if(!strucmp(str, ptype(rt)))
+ break;
+ }
+
+ *type = rt;
+ break;
+ case 's':
+ if(!isdigit((unsigned char)str[0])){
+ fprintf(stderr,
+ "non-numeric argument for flag \"%c\"\n", c);
+ ++usage;
+ break;
+ }
+
+ *trimsize = atoi(str);
+ if(*trimsize < 1){
+ fprintf(stderr, "trimsize of %d is too small, have to leave at least one copy\n", *trimsize);
+ ++usage;
+ }
+ else if(*trimsize > 100){
+ fprintf(stderr,
+ "trimsize of %d is too large, 5 or 10 is sufficient\n",
+ *trimsize);
+ ++usage;
+ }
+
+ break;
+ }
+
+ goto Loop;
+
+ default:
+ fprintf(stderr, "unknown flag \"%c\"\n", c);
+ ++usage;
+ break;
+ }
+ }
+ }
+
+ if(ac != 0)
+ usage++;
+
+ return(usage);
+}
+
+
+long
+dummy_soutr(stream, string)
+ void *stream;
+ char *string;
+{
+ return LONGT;
+}
+
+
+/*
+ * Add an explanatory first message advising user that this is a
+ * special sort of folder.
+ */
+int
+add_initial_msg(stream, mailbox, special_hdr)
+ MAILSTREAM *stream;
+ char *mailbox;
+ char *special_hdr;
+{
+ STRING msg;
+ char buf[20000];
+ RFC822BUFFER rbuf;
+
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = buf;
+ rbuf.cur = buf;
+ rbuf.end = buf+sizeof(buf)-1;
+ write_fake_headers(&rbuf, "Header Message for Remote Data", "plain", special_hdr);
+ *rbuf.cur = '\0';
+
+ buf[sizeof(buf)-1] = '\0';
+
+ if(!strucmp(special_hdr, REMOTE_ABOOK_SUBTYPE)){
+ strncat(buf, "This folder contains a single Alpine addressbook.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "This message is just an explanatory message.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "The last message in the folder is the live addressbook data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "The rest of the messages contain previous revisions of the addressbook data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "which come after it.\015\012", sizeof(buf)-strlen(buf)-1);
+ }
+ else if(!strucmp(special_hdr, REMOTE_PINERC_SUBTYPE)){
+ strncat(buf, "This folder contains an Alpine config file.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "This message is just an explanatory message.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "The last message in the folder is the live config data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "which come after it.\015\012", sizeof(buf)-strlen(buf)-1);
+ }
+ else if(!strucmp(special_hdr, REMOTE_SMIME_SUBTYPE)){
+ strncat(buf, "This folder contains Alpine S/MIME config information.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "This message is just an explanatory message.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "The last message in the folder is the live data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "which come after it.\015\012", sizeof(buf)-strlen(buf)-1);
+ }
+ else{
+ strncat(buf, "This folder contains remote Alpine data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "This message is just an explanatory message.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "The last message in the folder is the live data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf)-strlen(buf)-1);
+ strncat(buf, "which come after it.\015\012", sizeof(buf)-strlen(buf)-1);
+ }
+
+ INIT(&msg, mail_string, (void *)buf, strlen(buf));
+ if(!mail_append(stream, mailbox, &msg))
+ return(-1);
+
+ return(0);
+}
+
+
+/*
+ * Add a message to the folder with the contents of the local data
+ * in it.
+ */
+int
+append_data(stream, mailbox, special_hdr, fp)
+ MAILSTREAM *stream;
+ char *mailbox;
+ char *special_hdr;
+ FILE *fp;
+{
+ STRING msg;
+ char buf[20000], *sto, *p;
+ struct stat sbuf;
+ long filelen, len;
+ int c, nextc;
+ RFC822BUFFER rbuf;
+
+ if(fstat(fileno(fp), &sbuf) != 0){
+ fprintf(stderr, "fstat of local file failed\n");
+ return(-1);
+ }
+
+ filelen = (long) sbuf.st_size;
+
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = buf;
+ rbuf.cur = buf;
+ rbuf.end = buf+sizeof(buf)-1;
+ write_fake_headers(&rbuf, "Pine Remote Data Container", special_hdr,
+ special_hdr);
+ *rbuf.cur = '\0';
+
+ buf[sizeof(buf)-1] = '\0';
+
+ /* very conservative estimate of space needed */
+ len = filelen + filelen + strlen(buf) + 10;
+ sto = fs_get((len+1) * sizeof(char));
+
+ strncpy(sto, buf, len);
+ sto[len] = '\0';
+ p = sto + strlen(sto);
+ /* Write the contents */
+ while((c = getc(fp)) != EOF){
+ /*
+ * c-client expects CRLF-terminated lines. These lines
+ * can be either CRLF- or LF-terminated. We have to convert them
+ * when we copy into c-client.
+ */
+ if(c == '\r' || c == '\n'){
+ if(c == '\r' && ((nextc = getc(fp)) != '\n') && nextc != EOF)
+ ungetc(nextc, fp);
+
+ /* write the CRFL */
+ if(p - sto < len)
+ *p++ = '\r';
+
+ if(p - sto < len)
+ *p++ = '\n';
+ }
+ else if(p - sto < len)
+ *p++ = c;
+ }
+
+ fclose(fp);
+ if(p - sto < len)
+ *p = '\0';
+
+ sto[len] = '\0';
+
+ INIT(&msg, mail_string, (void *)sto, strlen(sto));
+ if(!mail_append(stream, mailbox, &msg)){
+ fprintf(stderr, "Copy failed\n");
+ return(-1);
+ }
+
+ fs_give((void **)&sto);
+
+ return(0);
+}
+
+
+/*
+ * Trim the number of saved copies of the remote data history in case
+ * this is the only way this folder is ever updated. We leave
+ * the first message there because it is supposed to be an explanatory
+ * message, but we don't actually check to see whether or not it is
+ * such a message or not.
+ */
+void
+trim_data(stream, trimsize)
+ MAILSTREAM *stream;
+ int trimsize;
+{
+ if(stream->nmsgs > trimsize + 1){
+ char sequence[20];
+
+ mail_ping(stream);
+ snprintf(sequence, sizeof(sequence), "2:%ld", stream->nmsgs - trimsize);
+ mail_flag(stream, sequence, "\\DELETED", ST_SET);
+ mail_expunge(stream);
+ }
+}
+
+
+void
+write_fake_headers(where, subject, subtype, special_hdr)
+ RFC822BUFFER *where;
+ char *subject;
+ char *subtype;
+ char *special_hdr;
+{
+ ENVELOPE *fake_env;
+ BODY *fake_body;
+ ADDRESS *fake_from;
+ char date[200], vers[10];
+
+ fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE));
+ memset(fake_env, 0, sizeof(ENVELOPE));
+ fake_body = (BODY *)fs_get(sizeof(BODY));
+ memset(fake_body, 0, sizeof(BODY));
+ fake_from = (ADDRESS *)fs_get(sizeof(ADDRESS));
+ memset(fake_from, 0, sizeof(ADDRESS));
+ rfc822_date(date);
+
+ fake_env->subject = cpystr(subject);
+ fake_env->date = (unsigned char *) cpystr(date);
+ fake_from->personal = cpystr("Pine Remote Data");
+ fake_from->mailbox = cpystr("nobody");
+ fake_from->host = cpystr("nowhere");
+ fake_env->from = fake_from;
+ fake_body->type = REMOTE_DATA_TYPE;
+ fake_body->subtype = cpystr(subtype);
+
+ snprintf(vers, sizeof(vers), "%d", REMOTE_DATA_VERS_NUM);
+
+ /* re-use subtype for special header name, too */
+ rfc822_output_header_line(where, special_hdr, 0L, vers);
+ rfc822_output_header(where, fake_env, fake_body, NULL, 0L);
+ mail_free_envelope(&fake_env);
+ mail_free_body(&fake_body);
+}
+
+
+char *
+err_desc(err)
+ int err;
+{
+ return((char *) strerror(err));
+}
+
+
+void mm_exists(stream, number)
+ MAILSTREAM *stream;
+ unsigned long number;
+{
+}
+
+
+void mm_expunged(stream, number)
+ MAILSTREAM *stream;
+ unsigned long number;
+{
+}
+
+
+void mm_flags(stream, number)
+ MAILSTREAM *stream;
+ unsigned long number;
+{
+}
+
+
+void mm_list(stream, delim, name, attrib)
+ MAILSTREAM *stream;
+ int delim;
+ char *name;
+ long attrib;
+{
+}
+
+
+void mm_lsub(stream, delimiter, name, attributes)
+ MAILSTREAM *stream;
+ int delimiter;
+ char *name;
+ long attributes;
+{
+}
+
+
+void mm_notify(stream, string, errflg)
+ MAILSTREAM *stream;
+ char *string;
+ long errflg;
+{
+ mm_log(string, errflg);
+}
+
+
+void mm_log(string, errflg)
+ char *string;
+ long errflg;
+{
+ if(noshow_error)
+ return;
+
+ switch(errflg){
+ case BYE:
+ case NIL:
+ break;
+
+ case PARSE:
+ fprintf(stderr, "PARSE: %s\n", string);
+ break;
+
+ case WARN:
+ fprintf(stderr, "WARN: %s\n", string);
+ break;
+
+ case ERROR:
+ fprintf(stderr, "ERROR: %s\n", string);
+ break;
+
+ default:
+ fprintf(stderr, "%s\n", string);
+ break;
+ }
+}
+
+
+void mm_login(mb, user, pwd, trial)
+ NETMBX *mb;
+ char *user;
+ char *pwd;
+ long trial;
+{
+ char prompt[100], *last;
+ int i, j, goal, ugoal, len, rc, flags = 0;
+#define NETMAXPASSWD 100
+
+ user[NETMAXUSER-1] = '\0';
+
+ if(trial == 0L){
+ if(mb->user && *mb->user){
+ strncpy(user, mb->user, NETMAXUSER);
+ user[NETMAXUSER-1] = '\0';
+ }
+ }
+
+ if(!*mb->user){
+ /* Dress up long hostnames */
+ snprintf(prompt, sizeof(prompt), "%sHOST: ",
+ (mb->sslflag||mb->tlsflag) ? "+ " : "");
+ len = strlen(prompt);
+ /* leave space for "HOST", "ENTER NAME", and 15 chars for input name */
+ goal = 80 - (len + 20 + MIN(15, 80/5));
+ last = " ENTER LOGIN NAME: ";
+ if(goal < 9){
+ last = " LOGIN: ";
+ if((goal += 13) < 9){
+ last += 1;
+ goal = 0;
+ }
+ }
+
+ if(goal){
+ for(i = len, j = 0;
+ i < sizeof(prompt) && (prompt[i] = mb->host[j]); i++, j++)
+ if(i == goal && mb->host[goal+1] && i < sizeof(prompt)){
+ strncpy(&prompt[i-3], "...", sizeof(prompt)-(i-3));
+ prompt[sizeof(prompt)-1] = '\0';
+ break;
+ }
+ }
+ else
+ i = 0;
+
+ strncpy(&prompt[i], last, sizeof(prompt)-i);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ while(1) {
+ rc = opt_enter(user, NETMAXUSER, prompt, &flags);
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 1 || !user[0]) {
+ user[0] = '\0';
+ pwd[0] = '\0';
+ }
+ }
+ else
+ strncpy(user, mb->user, NETMAXUSER);
+
+ user[NETMAXUSER-1] = '\0';
+ pwd[NETMAXPASSWD-1] = '\0';
+
+ if(!user[0])
+ return;
+
+
+ /* Dress up long host/user names */
+ /* leave space for "HOST", "USER" "ENTER PWD", 12 for user 6 for pwd */
+ snprintf(prompt, sizeof(prompt), "%sHOST: ", (mb->sslflag||mb->tlsflag) ? "+ " : "");
+ len = strlen(prompt);
+ goal = strlen(mb->host);
+ ugoal = strlen(user);
+ if((i = 80 - (len + 8 + 18 + 6)) < 14){
+ goal = 0; /* no host! */
+ if((i = 80 - (6 + 18 + 6)) <= 6){
+ ugoal = 0; /* no user! */
+ if((i = 80 - (18 + 6)) <= 0)
+ i = 0;
+ }
+ else{
+ ugoal = i; /* whatever's left */
+ i = 0;
+ }
+ }
+ else
+ while(goal + ugoal > i)
+ if(goal > ugoal)
+ goal--;
+ else
+ ugoal--;
+
+ if(goal){
+ snprintf(prompt, sizeof(prompt), "%sHOST: ",
+ (mb->sslflag||mb->tlsflag) ? "+ " : "");
+ for(i = len, j = 0;
+ i < sizeof(prompt) && (prompt[i] = mb->host[j]); i++, j++)
+ if(i == goal && mb->host[goal+1] && i < sizeof(prompt)){
+ strncpy(&prompt[i-3], "...", sizeof(prompt)-(i-3));
+ prompt[sizeof(prompt)-1] = '\0';
+ break;
+ }
+ }
+ else
+ i = 0;
+
+ if(ugoal){
+ strncpy(&prompt[i], &" USER: "[i ? 0 : 2], sizeof(prompt)-i);
+ prompt[sizeof(prompt)-1] = '\0';
+ for(i += strlen(&prompt[i]), j = 0;
+ i < sizeof(prompt) && (prompt[i] = user[j]); i++, j++)
+ if(j == ugoal && user[ugoal+1] && i < sizeof(prompt)){
+ strncpy(&prompt[i-3], "...", sizeof(prompt)-(i-3));
+ prompt[sizeof(prompt)-1] = '\0';
+ break;
+ }
+ }
+
+ strncpy(&prompt[i], &" ENTER PASSWORD: "[i ? 0 : 8], sizeof(prompt)-i);
+ prompt[sizeof(prompt)-1] = '\0';
+
+ *pwd = '\0';
+ while(1) {
+ flags = OE_PASSWD;
+ rc = opt_enter(pwd, NETMAXPASSWD, prompt, &flags);
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 1 || !pwd[0]) {
+ user[0] = pwd[0] = '\0';
+ return;
+ }
+}
+
+
+void mm_critical(stream)
+ MAILSTREAM *stream;
+{
+}
+
+
+void mm_nocritical(stream)
+ MAILSTREAM *stream;
+{
+}
+
+
+long mm_diskerror(stream, errcode, serious)
+ MAILSTREAM *stream;
+ long errcode;
+ long serious;
+{
+ return T;
+}
+
+
+void mm_fatal(string)
+ char *string;
+{
+ fprintf(stderr, "%s\n", string);
+}
+
+
+void mm_searched(stream, msgno)
+ MAILSTREAM *stream;
+ unsigned long msgno;
+{
+}
+
+
+void mm_status(stream, mailbox, status)
+ MAILSTREAM *stream;
+ char *mailbox;
+ MAILSTATUS *status;
+{
+}
+
+void mm_dlog(string)
+ char *string;
+{
+ fprintf(stderr, "%s\n", string);
+}
+
+
+int
+opt_enter(string, field_len, prompt, flags)
+ char *string, *prompt;
+ int field_len;
+ int *flags;
+{
+ char *pw;
+ int return_v = -10;
+
+ while(return_v == -10){
+
+ if(flags && *flags & OE_PASSWD){
+ if((pw = getpass(prompt)) != NULL){
+ if(strlen(pw) < field_len){
+ strncpy(string, pw, field_len);
+ string[field_len-1] = '\0';
+ return_v = 0;
+ }
+ else{
+ fputs("Password too long\n", stderr);
+ return_v = -1;
+ }
+ }
+ else
+ return_v = 1; /* cancel? */
+ }
+ else{
+ char *p;
+
+ fputs(prompt, stdout);
+ fgets(string, field_len, stdin);
+ string[field_len-1] = '\0';
+ if((p = strpbrk(string, "\r\n")) != NULL)
+ *p = '\0';
+
+ return_v = 0;
+ }
+ }
+
+ return(return_v);
+}
diff --git a/alpine/send.c b/alpine/send.c
new file mode 100644
index 00000000..3d670f02
--- /dev/null
+++ b/alpine/send.c
@@ -0,0 +1,7155 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: send.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ Functions for composing and sending mail
+
+ ====*/
+
+
+#include "headers.h"
+#include "send.h"
+#include "status.h"
+#include "mailview.h"
+#include "mailindx.h"
+#include "dispfilt.h"
+#include "keymenu.h"
+#include "folder.h"
+#include "radio.h"
+#include "addrbook.h"
+#include "reply.h"
+#include "titlebar.h"
+#include "signal.h"
+#include "mailcmd.h"
+#include "roleconf.h"
+#include "adrbkcmd.h"
+#include "busy.h"
+#include "../pith/debug.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/flag.h"
+#include "../pith/bldaddr.h"
+#include "../pith/copyaddr.h"
+#include "../pith/detach.h"
+#include "../pith/mimedesc.h"
+#include "../pith/pipe.h"
+#include "../pith/addrstring.h"
+#include "../pith/news.h"
+#include "../pith/detoken.h"
+#include "../pith/util.h"
+#include "../pith/init.h"
+#include "../pith/mailcmd.h"
+#include "../pith/ablookup.h"
+#include "../pith/reply.h"
+#include "../pith/hist.h"
+#include "../pith/list.h"
+#include "../pith/icache.h"
+#include "../pith/busy.h"
+#include "../pith/mimetype.h"
+#include "../pith/send.h"
+#include "../pith/smime.h"
+
+
+typedef struct body_particulars {
+ unsigned short type, encoding, had_csp;
+ char *subtype, *charset;
+ PARAMETER *parameter;
+} BODY_PARTICULARS_S;
+
+#define PHONE_HOME_VERSION "-count"
+#define PHONE_HOME_HOST "docserver.cac.washington.edu"
+
+/*
+ * macro to bind pico's headerentry pointer to PINEFIELD "extdata" hook
+ */
+#define HE(PF) ((struct headerentry *)((PF)->extdata))
+
+
+/*
+ * Internal Prototypes
+ */
+int redraft(MAILSTREAM **, ENVELOPE **, BODY **, char **, char **, REPLY_S **,
+ REDRAFT_POS_S **, PINEFIELD **, ACTION_S **, int);
+int redraft_prompt(char *, char *, int);
+int check_for_subject(METAENV *);
+int check_for_fcc(char *);
+void free_prompts(PINEFIELD *);
+int postpone_prompt(void);
+METAENV *pine_simple_send_header(ENVELOPE *, char **, char ***);
+void call_mailer_file_result(char *, int);
+void mark_address_failure_for_pico(METAENV *);
+BODY_PARTICULARS_S
+ *save_body_particulars(BODY *);
+void reset_body_particulars(BODY_PARTICULARS_S *, BODY *);
+void free_body_particulars(BODY_PARTICULARS_S *);
+long message_format_for_pico(long, int (*)(int));
+int send_exit_for_pico(struct headerentry *, void (*)(void), int, char **);
+void new_thread_on_blank_subject(void);
+char *choose_a_priority(char *);
+int dont_flow_this_time(void);
+int mime_type_for_pico(char *);
+char *cancel_for_pico(void (*)(void));
+int filter_message_text(char *, ENVELOPE *, BODY *, STORE_S **, METAENV *);
+void pine_send_newsgroup_name(char *, char*, size_t);
+void outgoing2strings(METAENV *, BODY *, void **, PATMT **, int);
+void strings2outgoing(METAENV *, BODY **, PATMT *, int);
+void create_message_body_text(BODY *, int);
+void set_body_size(BODY *);
+int view_as_rich(char *, int);
+int background_posting(int);
+int valid_subject(char *, char **, char **,BUILDER_ARG *,int *);
+int build_addr_lcc(char *, char **, char **, BUILDER_ARG *, int *);
+int news_build(char *, char **, char **, BUILDER_ARG *, int *);
+void news_build_busy(void);
+#if defined(DOS) || defined(OS2)
+int dos_valid_from(void);
+#endif /* defined(DOS) || defined(OS2) */
+
+
+/*
+ * Pointer to buffer to hold pointers into pine data that's needed by pico.
+ */
+static PICO *pbf;
+
+
+static char *g_rolenick = NULL;
+
+
+static char *sending_filter_requested;
+static char background_requested, flowing_requested;
+static unsigned call_mailer_flags;
+static char *priority_requested;
+
+/* local global to save busy_cue state */
+static int news_busy_cue = 0;
+
+
+/*
+ * Various useful strings
+ */
+#define INTRPT_PMT \
+ _("Continue INTERRUPTED composition (answering \"n\" won't erase it)")
+#define PSTPND_PMT \
+ _("Continue postponed composition (answering \"No\" won't erase it)")
+#define FORM_PMT \
+ _("Start composition from Form Letter Folder")
+#define PSTPN_FORM_PMT \
+ _("Save to Postponed or Form letter folder? ")
+#define POST_PMT \
+ _("Posted message may go to thousands of readers. Really post")
+#define INTR_DEL_PMT \
+ _("Deleted messages will be removed from folder after use. Proceed")
+
+
+/*
+ * Macros to help sort out posting results
+ */
+#define P_MAIL_WIN 0x01
+#define P_MAIL_LOSE 0x02
+#define P_MAIL_BITS 0x03
+#define P_NEWS_WIN 0x04
+#define P_NEWS_LOSE 0x08
+#define P_NEWS_BITS 0x0C
+#define P_FCC_WIN 0x10
+#define P_FCC_LOSE 0x20
+#define P_FCC_BITS 0x30
+
+
+#define COMPOSE_MAIL_TITLE "COMPOSE MESSAGE"
+
+
+/*
+ * For check_for_subject and check_for_fcc
+ */
+#define CF_OK 0x1
+#define CF_MISSING 0x2
+
+
+/*----------------------------------------------------------------------
+ Compose screen (not forward or reply). Set up envelope, call composer
+
+ Args: pine_state -- The usual pine structure
+
+ Little front end for the compose screen
+ ---*/
+void
+compose_screen(struct pine *pine_state)
+{
+ void (*prev_screen)(struct pine *) = pine_state->prev_screen,
+ (*redraw)(void) = pine_state->redrawer;
+
+ pine_state->redrawer = NULL;
+ ps_global->next_screen = SCREEN_FUN_NULL;
+ mailcap_free(); /* free resources we won't be using for a while */
+ compose_mail(NULL, NULL, NULL, NULL, NULL);
+ pine_state->next_screen = prev_screen;
+ pine_state->redrawer = redraw;
+}
+
+
+/*----------------------------------------------------------------------
+ Alternate compose screen. Set up role and call regular compose.
+
+ Args: pine_state -- The usual pine structure
+ ---*/
+void
+alt_compose_screen(struct pine *pine_state)
+{
+ ACTION_S *role = NULL;
+ void (*prev_screen)(struct pine *) = pine_state->prev_screen,
+ (*redraw)(void) = pine_state->redrawer;
+
+ pine_state->redrawer = NULL;
+ ps_global->next_screen = SCREEN_FUN_NULL;
+ mailcap_free(); /* free resources we won't be using for a while */
+
+ /* Setup role */
+ if(role_select_screen(pine_state, &role, MC_COMPOSE) < 0){
+ cmd_cancelled("Composition");
+ pine_state->next_screen = prev_screen;
+ pine_state->redrawer = redraw;
+ return;
+ }
+
+ /*
+ * If default role was selected (NULL) we need to make up a role which
+ * won't do anything, but will cause compose_mail to think there's
+ * already a role so that it won't try to confirm the default.
+ */
+ if(role)
+ role = combine_inherited_role(role);
+ else{
+ role = (ACTION_S *)fs_get(sizeof(*role));
+ memset((void *)role, 0, sizeof(*role));
+ role->nick = cpystr("Default Role");
+ }
+
+ pine_state->redrawer = NULL;
+ compose_mail(NULL, NULL, role, NULL, NULL);
+ free_action(&role);
+ pine_state->next_screen = prev_screen;
+ pine_state->redrawer = redraw;
+}
+
+
+/*----------------------------------------------------------------------
+ Format envelope for outgoing message and call editor
+
+ Args: given_to -- An address to send mail to (usually from command line
+ invocation)
+ fcc_arg -- The fcc that goes with this address.
+
+ If a "To" line is given format that into the envelope and get ready to call
+ the composer
+ If there's a message postponed, offer to continue it, and set it up,
+ otherwise just fill in the outgoing envelope as blank.
+
+ NOTE: we ignore postponed and interrupted messages in nr mode
+ ----*/
+void
+compose_mail(char *given_to, char *fcc_arg, ACTION_S *role_arg,
+ PATMT *attach, gf_io_t inc_text_getc)
+{
+ BODY *body = NULL;
+ ENVELOPE *outgoing = NULL;
+ PINEFIELD *custom = NULL;
+ REPLY_S *reply = NULL;
+ REDRAFT_POS_S *redraft_pos = NULL;
+ ACTION_S *role = NULL;
+ MAILSTREAM *stream;
+ char *fcc_to_free,
+ *fcc = NULL,
+ *lcc = NULL,
+ *sig = NULL;
+ int fcc_is_sticky = 0,
+ to_is_sticky = 0,
+ intrptd = 0,
+ postponed = 0,
+ form = 0;
+
+ dprint((1,
+ "\n\n ---- COMPOSE SCREEN (not in pico yet) ----\n"));
+
+ /*-- Check for INTERRUPTED mail --*/
+ if(!role_arg && !(given_to || attach)){
+ char file_path[MAXPATH+1];
+
+ /* build filename and see if it exists. build_path creates
+ * an explicit local path name, so all c-client access is thru
+ * local drivers.
+ */
+ file_path[0] = '\0';
+ build_path(file_path,
+ ps_global->VAR_OPER_DIR ? ps_global->VAR_OPER_DIR
+ : ps_global->home_dir,
+ INTERRUPTED_MAIL, sizeof(file_path));
+
+ /* check to see if the folder exists, the user wants to continue
+ * and that we can actually read something in...
+ */
+ if(folder_exists(NULL, file_path) & FEX_ISFILE)
+ intrptd = 1;
+ }
+
+ /*-- Check for postponed mail --*/
+ if(!role_arg
+ && !outgoing /* not replying/forwarding */
+ && !(given_to || attach) /* not command line send */
+ && ps_global->VAR_POSTPONED_FOLDER /* folder to look in */
+ && ps_global->VAR_POSTPONED_FOLDER[0])
+ postponed = 1;
+
+ /*-- Check for form letter folder --*/
+ if(!role_arg
+ && !outgoing /* not replying/forwarding */
+ && !(given_to || attach) /* not command line send */
+ && ps_global->VAR_FORM_FOLDER /* folder to look in */
+ && ps_global->VAR_FORM_FOLDER[0])
+ form = 1;
+
+ if(!outgoing && !(given_to || attach)
+ && !role_arg && F_ON(F_ALT_COMPOSE_MENU, ps_global)){
+ char prompt[80];
+ char letters[30];
+ char chosen_task;
+ char *new = "New";
+ char *intrpt = "Interrupted";
+ char *postpnd = "Postponed";
+ char *formltr = "FormLetter";
+ char *roles = "setRole";
+ HelpType help = h_composer_browse;
+ ESCKEY_S compose_style[6];
+ unsigned which_help;
+ int ekey_num;
+
+ ekey_num = 0;
+ compose_style[ekey_num].ch = 'n';
+ compose_style[ekey_num].rval = 'n';
+ compose_style[ekey_num].name = "N";
+ compose_style[ekey_num++].label = new;
+
+ if(intrptd){
+ compose_style[ekey_num].ch = 'i';
+ compose_style[ekey_num].rval = 'i';
+ compose_style[ekey_num].name = "I";
+ compose_style[ekey_num++].label = intrpt;
+ }
+
+ if(postponed){
+ compose_style[ekey_num].ch = 'p';
+ compose_style[ekey_num].rval = 'p';
+ compose_style[ekey_num].name = "P";
+ compose_style[ekey_num++].label = postpnd;
+ }
+
+ if(form){
+ compose_style[ekey_num].ch = 'f';
+ compose_style[ekey_num].rval = 'f';
+ compose_style[ekey_num].name = "F";
+ compose_style[ekey_num++].label = formltr;
+ }
+
+ compose_style[ekey_num].ch = 'r';
+ compose_style[ekey_num].rval = 'r';
+ compose_style[ekey_num].name = "R";
+ compose_style[ekey_num++].label = roles;
+
+ compose_style[ekey_num].ch = -1;
+
+ if(F_ON(F_BLANK_KEYMENU,ps_global)){
+ char *p;
+
+ p = letters;
+ *p = '\0';
+ for(ekey_num = 0; compose_style[ekey_num].ch != -1; ekey_num++){
+ if(p - letters < sizeof(letters))
+ *p++ = (char) compose_style[ekey_num].ch;
+
+ if(compose_style[ekey_num + 1].ch != -1 && p - letters < sizeof(letters))
+ *p++ = ',';
+ }
+
+ if(p - letters < sizeof(letters))
+ *p = '\0';
+ }
+
+ which_help = intrptd + 2 * postponed + 4 * form;
+ switch(which_help){
+ case 1:
+ help = h_compose_intrptd;
+ break;
+ case 2:
+ help = h_compose_postponed;
+ break;
+ case 3:
+ help = h_compose_intrptd_postponed;
+ break;
+ case 4:
+ help = h_compose_form;
+ break;
+ case 5:
+ help = h_compose_intrptd_form;
+ break;
+ case 6:
+ help = h_compose_postponed_form;
+ break;
+ case 7:
+ help = h_compose_intrptd_postponed_form;
+ break;
+ default:
+ help = h_compose_default;
+ break;
+ }
+
+ snprintf(prompt, sizeof(prompt),
+ "Choose a compose method from %s : ",
+ F_ON(F_BLANK_KEYMENU,ps_global) ? letters : "the menu below");
+ prompt[sizeof(prompt)-1] = '\0';
+
+ chosen_task = radio_buttons(prompt, -FOOTER_ROWS(ps_global),
+ compose_style, 'n', 'x', help, RB_NORM);
+ intrptd = postponed = form = 0;
+
+ switch(chosen_task){
+ case 'i':
+ intrptd = 1;
+ break;
+ case 'p':
+ postponed = 1;
+ break;
+ case 'r':
+ {
+ void (*prev_screen)(struct pine *) = ps_global->prev_screen,
+ (*redraw)(void) = ps_global->redrawer;
+
+ ps_global->redrawer = NULL;
+ ps_global->next_screen = SCREEN_FUN_NULL;
+ if(role_select_screen(ps_global, &role, MC_COMPOSE) < 0){
+ cmd_cancelled("Composition");
+ ps_global->next_screen = prev_screen;
+ ps_global->redrawer = redraw;
+ return;
+ }
+
+ ps_global->next_screen = prev_screen;
+ ps_global->redrawer = redraw;
+ if(role)
+ role = combine_inherited_role(role);
+ }
+ break;
+
+ case 'f':
+ form = 1;
+ break;
+
+ case 'x':
+ q_status_message(SM_ORDER, 0, 3,
+ "Composition cancelled");
+ return;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if(intrptd && !outgoing){
+ char file_path[MAXPATH+1];
+ int ret = 'n';
+
+ file_path[0] = '\0';
+ build_path(file_path,
+ ps_global->VAR_OPER_DIR ? ps_global->VAR_OPER_DIR
+ : ps_global->home_dir,
+ INTERRUPTED_MAIL, sizeof(file_path));
+ if(folder_exists(NULL, file_path) & FEX_ISFILE){
+ if((stream = pine_mail_open(NULL, file_path,
+ SP_USEPOOL|SP_TEMPUSE, NULL))
+ && !stream->halfopen){
+
+ if(F_ON(F_ALT_COMPOSE_MENU, ps_global) ||
+ (ret = redraft_prompt("Interrupted",INTRPT_PMT,'n')) =='y'){
+ if(!redraft(&stream, &outgoing, &body, &fcc, &lcc, &reply,
+ &redraft_pos, &custom, &role, REDRAFT_DEL)){
+ if(stream)
+ pine_mail_close(stream);
+
+ return;
+ }
+
+ to_is_sticky++;
+
+ /* redraft() may or may not have closed stream */
+ if(stream)
+ pine_mail_close(stream);
+
+ postponed = form = 0;
+ }
+ else{
+ pine_mail_close(stream);
+ if(ret == 'x'){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Composition cancelled"));
+ return;
+ }
+ }
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ _("Can't open Interrupted mailbox: %s"),
+ file_path);
+ if(stream)
+ pine_mail_close(stream);
+ }
+ }
+ }
+
+ if(postponed && !outgoing){
+ int ret = 'n', done = 0;
+ int exists;
+
+ if((exists=postponed_stream(&stream,
+ ps_global->VAR_POSTPONED_FOLDER,
+ "Postponed", 0)) & FEX_ISFILE){
+ if(F_ON(F_ALT_COMPOSE_MENU, ps_global) ||
+ (ret = redraft_prompt("Postponed",PSTPND_PMT,'n')) == 'y'){
+ if(!redraft(&stream, &outgoing, &body, &fcc, &lcc, &reply,
+ &redraft_pos, &custom, &role,
+ REDRAFT_DEL | REDRAFT_PPND))
+ done++;
+
+ /* stream may or may not be closed in redraft() */
+ if(stream && (stream != ps_global->mail_stream))
+ pine_mail_close(stream);
+
+ to_is_sticky++;
+ intrptd = form = 0;
+ }
+ else{
+ if(stream != ps_global->mail_stream)
+ pine_mail_close(stream);
+
+ if(ret == 'x'){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Composition cancelled"));
+ done++;
+ }
+ }
+ }
+ else if(F_ON(F_ALT_COMPOSE_MENU, ps_global))
+ done++;
+
+ if(done)
+ return;
+ }
+
+ if(form && !outgoing){
+ int ret = 'n', done = 0;
+ int exists;
+
+ if((exists=postponed_stream(&stream,
+ ps_global->VAR_FORM_FOLDER,
+ "Form letter", 1)) & FEX_ISFILE){
+ if(F_ON(F_ALT_COMPOSE_MENU, ps_global) ||
+ (ret = want_to(FORM_PMT,'y','x',NO_HELP,WT_NORM))=='y'){
+ if(!redraft(&stream, &outgoing, &body, &fcc, &lcc, &reply,
+ &redraft_pos, &custom, NULL, REDRAFT_NONE))
+ done++;
+
+ /* stream may or may not be closed in redraft() */
+ if(stream && (stream != ps_global->mail_stream))
+ pine_mail_close(stream);
+
+ to_is_sticky++;
+ intrptd = postponed = 0;
+ }
+ else{
+ if(stream != ps_global->mail_stream)
+ pine_mail_close(stream);
+
+ if(ret == 'x'){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Composition cancelled"));
+ done++;
+ }
+ }
+ }
+ else{
+ if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Form letter folder doesn't exist!"));
+ return;
+ }
+ }
+
+ if(done)
+ return;
+ }
+
+ /*-- normal composition --*/
+ if(!outgoing){
+ int impl, template_len = 0;
+ long rflags = ROLE_COMPOSE;
+ PAT_STATE dummy;
+
+ /*================= Compose new message ===============*/
+ body = mail_newbody();
+ outgoing = mail_newenvelope();
+
+ if(given_to)
+ rfc822_parse_adrlist(&outgoing->to, given_to, ps_global->maildomain);
+
+ outgoing->message_id = generate_message_id();
+
+ /*
+ * Setup possible role
+ */
+ if(role_arg)
+ role = copy_action(role_arg);
+
+ if(!role){
+ /* Setup possible compose role */
+ if(nonempty_patterns(rflags, &dummy)){
+ /*
+ * setup default role
+ * Msgno = -1 means there is no msg.
+ * This will match roles which have the Compose Use turned
+ * on, and have no patterns set, and match the Current
+ * Folder Type.
+ */
+ role = set_role_from_msg(ps_global, rflags, -1L, NULL);
+
+ if(confirm_role(rflags, &role))
+ role = combine_inherited_role(role);
+ else{ /* cancel reply */
+ role = NULL;
+ cmd_cancelled("Composition");
+ return;
+ }
+ }
+ }
+
+ if(role)
+ q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""),
+ role->nick);
+
+ /*
+ * The type of storage object allocated below is vitally
+ * important. See SIMPLIFYING ASSUMPTION #37
+ */
+ if((body->contents.text.data = (void *) so_get(PicoText,
+ NULL, EDIT_ACCESS)) != NULL){
+ char ch;
+
+ if(inc_text_getc){
+ while((*inc_text_getc)(&ch))
+ if(!so_writec(ch, (STORE_S *)body->contents.text.data)){
+ break;
+ }
+ }
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ _("Problem creating space for message text."));
+ return;
+ }
+
+ if(role && role->template){
+ char *filtered;
+
+ impl = 1; /* leave cursor in header if not explicit */
+ filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl);
+ if(filtered){
+ if(*filtered){
+ so_puts((STORE_S *)body->contents.text.data, filtered);
+ if(impl == 1)
+ template_len = strlen(filtered);
+ }
+
+ fs_give((void **)&filtered);
+ }
+ }
+ else
+ impl = 1;
+
+ if((sig = detoken(role, NULL, 2, 0, 1, &redraft_pos, &impl)) != NULL){
+ if(impl == 2)
+ redraft_pos->offset += template_len;
+
+ if(*sig)
+ so_puts((STORE_S *)body->contents.text.data, sig);
+
+ fs_give((void **)&sig);
+ }
+
+ body->type = TYPETEXT;
+
+ if(attach)
+ create_message_body(&body, attach, 0);
+ }
+
+ ps_global->prev_screen = compose_screen;
+ if(!(fcc_to_free = fcc) && !(role && role->fcc))
+ fcc = fcc_arg; /* Didn't pick up fcc, use given */
+
+ /*
+ * check whether a build_address-produced fcc is different from
+ * fcc. If same, do nothing, if different, set sticky bit in pine_send.
+ */
+ if(fcc){
+ char *tmp_fcc = NULL;
+
+ if(outgoing->to){
+ tmp_fcc = get_fcc_based_on_to(outgoing->to);
+ if(strcmp(fcc, tmp_fcc ? tmp_fcc : ""))
+ fcc_is_sticky++; /* cause sticky bit to get set */
+
+ }
+ else if((tmp_fcc = get_fcc(NULL)) != NULL &&
+ !strcmp(fcc, tmp_fcc)){
+ /* not sticky */
+ }
+ else
+ fcc_is_sticky++;
+
+ if(tmp_fcc)
+ fs_give((void **)&tmp_fcc);
+ }
+
+ pine_send(outgoing, &body, COMPOSE_MAIL_TITLE, role, fcc,
+ reply, redraft_pos, lcc, custom,
+ (fcc_is_sticky ? PS_STICKY_FCC : 0) | (to_is_sticky ? PS_STICKY_TO : 0));
+
+ if(reply){
+ if(reply->mailbox)
+ fs_give((void **) &reply->mailbox);
+ if(reply->origmbox)
+ fs_give((void **) &reply->origmbox);
+ if(reply->prefix)
+ fs_give((void **) &reply->prefix);
+ if(reply->data.uid.msgs)
+ fs_give((void **) &reply->data.uid.msgs);
+ fs_give((void **) &reply);
+ }
+
+ if(fcc_to_free)
+ fs_give((void **)&fcc_to_free);
+
+ if(lcc)
+ fs_give((void **)&lcc);
+
+ mail_free_envelope(&outgoing);
+ pine_free_body(&body);
+ free_redraft_pos(&redraft_pos);
+ free_action(&role);
+}
+
+
+/*----------------------------------------------------------------------
+ Args: stream -- This is where we get the postponed messages from
+ We'll expunge and close it here unless it is mail_stream.
+
+ These are all return values:
+ ================
+ outgoing --
+ body --
+ fcc --
+ lcc --
+ reply --
+ redraft_pos --
+ custom --
+ role --
+ ================
+
+ flags --
+
+ ----*/
+int
+redraft(MAILSTREAM **streamp, ENVELOPE **outgoing, struct mail_bodystruct **body,
+ char **fcc, char **lcc, REPLY_S **reply, REDRAFT_POS_S **redraft_pos,
+ PINEFIELD **custom, ACTION_S **role, int flags)
+{
+ MAILSTREAM *stream;
+ long cont_msg = 1L;
+ STORE_S *so;
+
+ if(!(streamp && *streamp))
+ return(0);
+
+ stream = *streamp;
+
+ /*
+ * If we're manipulating the current folder, don't bother
+ * with index
+ */
+ if(!stream->nmsgs){
+ if(REDRAFT_PPND&flags)
+ q_status_message(SM_ORDER | SM_DING, 3, 5, _("Empty folder! No messages really postponed!"));
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 5, _("Empty folder! No messages really interrupted!"));
+
+ return(redraft_cleanup(streamp, FALSE, flags));
+ }
+ else if(stream == ps_global->mail_stream
+ && ps_global->prev_screen == mail_index_screen){
+ /*
+ * Since the user's got this folder already opened and they're
+ * on a selected message, pick that one rather than rebuild
+ * another index screen...
+ */
+ cont_msg = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
+ }
+ else if(stream->nmsgs > 1L){ /* offer browser ? */
+ int rv;
+
+ if(REDRAFT_PPND&flags){ /* set to last message postponed */
+ mn_set_cur(sp_msgmap(stream),
+ mn_get_revsort(sp_msgmap(stream))
+ ? 1L : mn_get_total(sp_msgmap(stream)));
+ }
+ else{ /* set to top form letter */
+ mn_set_cur(sp_msgmap(stream), 1L);
+ }
+
+ clear_index_cache(stream, 0);
+ while(1){
+ void *ti;
+
+ ti = stop_threading_temporarily();
+ rv = index_lister(ps_global, NULL, stream->mailbox,
+ stream, sp_msgmap(stream));
+ restore_threading(&ti);
+
+ cont_msg = mn_m2raw(sp_msgmap(stream), mn_get_cur(sp_msgmap(stream)));
+ if(count_flagged(stream, F_DEL)
+ && want_to(INTR_DEL_PMT, 'n', 0, NO_HELP, WT_NORM) == 'n'){
+ if(REDRAFT_PPND&flags)
+ q_status_message(SM_ORDER, 3, 3, _("Undelete messages to remain postponed, and then continue message"));
+ else
+ q_status_message(SM_ORDER, 3, 3, _("Undelete form letters you want to keep, and then continue message"));
+
+ continue;
+ }
+
+ break;
+ }
+
+ clear_index_cache(stream, 0);
+
+ if(rv){
+ q_status_message(SM_ORDER, 0, 3, _("Composition cancelled"));
+ (void) redraft_cleanup(streamp, FALSE, flags);
+
+ if(!*streamp && !ps_global->mail_stream){
+ q_status_message2(SM_ORDER, 3, 7,
+ "No more %.200s, returning to \"%.200s\"",
+ (REDRAFT_PPND&flags) ? "postponed messages"
+ : "form letters",
+ ps_global->inbox_name);
+ if(ps_global && ps_global->ttyo){
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ ps_global->mangled_footer = 1;
+ }
+
+ do_broach_folder(ps_global->inbox_name,
+ ps_global->context_list, NULL, DB_INBOXWOCNTXT);
+
+ ps_global->next_screen = mail_index_screen;
+ }
+
+ return(0); /* special case */
+ }
+ }
+
+ if((so = (void *) so_get(PicoText, NULL, EDIT_ACCESS)) != NULL)
+ return(redraft_work(streamp, cont_msg, outgoing, body,
+ fcc, lcc, reply, redraft_pos, custom,
+ role, flags, so));
+ else
+ return(0);
+}
+
+
+int
+redraft_prompt(char *type, char *prompt, int failure)
+{
+ if(background_posting(FALSE)){
+ q_status_message1(SM_ORDER, 0, 3,
+ _("%s folder unavailable while background posting"),
+ type);
+ return(failure);
+ }
+
+ return(want_to(prompt, 'y', 'x', NO_HELP, WT_NORM));
+}
+
+
+/* this is for initializing the fixed header elements in pine_send() */
+/*
+prompt::name::help::prwid::maxlen::realaddr::
+builder::affected_entry::next_affected::selector::key_label::fileedit::
+display_it::break_on_comma::is_attach::rich_header::only_file_chars::
+single_space::sticky::dirty::start_here::blank::sticky_special::KS_ODATAVAR
+*/
+static struct headerentry he_template[]={
+ {"", "X-Auth-Received", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"From : ", "From", h_composer_from, 10, 0, NULL,
+ build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete,
+ 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK},
+ {"Reply-To: ", "Reply To", h_composer_reply_to, 10, 0, NULL,
+ build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete,
+ 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK},
+ {"To : ", "To", h_composer_to, 10, 0, NULL,
+ build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, KS_TOADDRBOOK},
+ {"Cc : ", "Cc", h_composer_cc, 10, 0, NULL,
+ build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK},
+ {"Bcc : ", "Bcc", h_composer_bcc, 10, 0, NULL,
+ build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete,
+ 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK},
+ {"Newsgrps: ", "Newsgroups", h_composer_news, 10, 0, NULL,
+ news_build, NULL, NULL, news_group_selector, "To NwsGrps", NULL, NULL,
+ 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Fcc : ", "Fcc", h_composer_fcc, 10, 0, NULL,
+ NULL, NULL, NULL, folders_for_fcc, "To Fldrs", NULL, NULL,
+ 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Lcc : ", "Lcc", h_composer_lcc, 10, 0, NULL,
+ build_addr_lcc, NULL, NULL, addr_book_compose_lcc,"To AddrBk", NULL, abook_nickname_complete,
+ 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"Attchmnt: ", "Attchmnt", h_composer_attachment, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, "To Files", NULL, NULL,
+ 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, KS_NONE},
+ {"Subject : ", "Subject", h_composer_subject, 10, 0, NULL,
+ valid_subject, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "References", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "Date", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "In-Reply-To", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "Message-ID", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "X-Priority", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "User-Agent", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "To", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "X-Post-Error",NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "X-Reply-UID", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "X-Reply-Mbox", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "X-SMTP-Server", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "X-Cursor-Pos", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "X-Our-ReplyTo", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", OUR_HDRS_LIST, NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+ {"", "X-Auth-Received", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE},
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+ {"", "Sender", NO_HELP, 10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}
+#endif
+};
+
+
+static struct headerentry he_custom_addr_templ={
+ NULL, NULL, h_composer_custom_addr,10, 0, NULL,
+ build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete,
+ 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK};
+
+static struct headerentry he_custom_free_templ={
+ NULL, NULL, h_composer_custom_free,10, 0, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_NONE};
+
+
+/*----------------------------------------------------------------------
+ Get addressee for message, then post message
+
+ Args: outgoing -- Partially formatted outgoing ENVELOPE
+ body -- Body of outgoing message
+ prmpt_who -- Optional prompt for optionally_enter call
+ prmpt_cnf -- Optional prompt for confirmation call
+ used_tobufval -- The string that the to was eventually set equal to.
+ This gets passed back if non-NULL on entry.
+ flagsarg -- SS_PROMPTFORTO - Allow user to change recipient
+ SS_NULLRP - Use null return-path so we'll send an
+ SMTP MAIL FROM: <>
+
+ Result: message "To: " field is provided and message is sent or cancelled.
+
+ Fields:
+ remail -
+ return_path -
+ date added here
+ from added here
+ sender -
+ reply_to -
+ subject passed in, NOT edited but maybe canonized here
+ to possibly passed in, edited and canonized here
+ cc -
+ bcc -
+ in_reply_to -
+ message_id -
+
+Can only deal with message bodies that are either TYPETEXT or TYPEMULTIPART
+with the first part TYPETEXT! All newlines in the text here also end with
+CRLF.
+
+Returns 0 on success, -1 on failure.
+ ----*/
+int
+pine_simple_send(ENVELOPE *outgoing, /* envelope for outgoing message */
+ struct mail_bodystruct **body,
+ ACTION_S *role,
+ char *prmpt_who,
+ char *prmpt_cnf,
+ char **used_tobufval,
+ int flagsarg)
+{
+ char **tobufp, *p;
+ void *messagebuf;
+ int done = 0, retval = 0, x;
+ int lastrc, rc = 0, ku, i, resize_len, result, fcc_result;
+ int og2s_done = 0;
+ HelpType help;
+ static HISTORY_S *history = NULL;
+ ESCKEY_S ekey[5];
+ BUILDER_ARG ba_fcc;
+ METAENV *header;
+
+ dprint((1,"\n === simple send called === \n"));
+
+ memset(&ba_fcc, 0, sizeof(BUILDER_ARG));
+
+ init_hist(&history, HISTSIZE);
+
+ header = pine_simple_send_header(outgoing, &ba_fcc.tptr, &tobufp);
+
+ /*----- Fill in a few general parts of the envelope ----*/
+ if(!outgoing->date){
+ if(F_ON(F_QUELL_TIMEZONE, ps_global))
+ mail_parameters(NULL, SET_DISABLE822TZTEXT, (void *) TRUE);
+
+ rfc822_date(tmp_20k_buf); /* format and copy new date */
+ if(F_ON(F_QUELL_TIMEZONE, ps_global))
+ mail_parameters(NULL, SET_DISABLE822TZTEXT, (void *) FALSE);
+
+ outgoing->date = (unsigned char *) cpystr(tmp_20k_buf);
+ }
+
+ if(!outgoing->from){
+ if(role && role->from){
+ if(ps_global->never_allow_changing_from)
+ q_status_message(SM_ORDER, 3, 3, _("Site policy doesn't allow changing From address so role's From has no effect"));
+ else
+ outgoing->from = copyaddrlist(role->from);
+ }
+ else
+ outgoing->from = generate_from();
+ }
+
+ if(!(flagsarg & SS_NULLRP))
+ outgoing->return_path = rfc822_cpy_adr(outgoing->from);
+
+ ekey[i = 0].ch = ctrl('T');
+ ekey[i].rval = 2;
+ ekey[i].name = "^T";
+ ekey[i++].label = N_("To AddrBk");
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ ekey[i].ch = ctrl('I');
+ ekey[i].rval = 11;
+ ekey[i].name = "TAB";
+ ekey[i++].label = N_("Complete");
+ }
+
+ ekey[i].ch = KEY_UP;
+ ekey[i].rval = 30;
+ ekey[i].name = "";
+ ku = i;
+ ekey[i++].label = "";
+
+ ekey[i].ch = KEY_DOWN;
+ ekey[i].rval = 31;
+ ekey[i].name = "";
+ ekey[i++].label = "";
+
+ ekey[i].ch = -1;
+
+ /*----------------------------------------------------------------------
+ Loop editing the "To: " field until everything goes well
+ ----*/
+ help = NO_HELP;
+
+ while(!done){
+ int flags;
+
+ if(!og2s_done){
+ og2s_done++;
+ outgoing2strings(header, *body, &messagebuf, NULL, 1);
+ }
+
+ lastrc = rc;
+ if(flagsarg & SS_PROMPTFORTO){
+ if(!*tobufp)
+ *tobufp = cpystr("");
+
+ resize_len = MAX(MAXPATH, strlen(*tobufp));
+ fs_resize((void **) tobufp, resize_len+1);
+
+ if(items_in_hist(history) > 0){
+ ekey[ku].name = HISTORY_UP_KEYNAME;
+ ekey[ku].label = HISTORY_KEYLABEL;
+ ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
+ ekey[ku+1].label = HISTORY_KEYLABEL;
+ }
+ else{
+ ekey[ku].name = "";
+ ekey[ku].label = "";
+ ekey[ku+1].name = "";
+ ekey[ku+1].label = "";
+ }
+
+ flags = OE_APPEND_CURRENT;
+
+ rc = optionally_enter(*tobufp, -FOOTER_ROWS(ps_global),
+ 0, resize_len,
+ prmpt_who
+ ? prmpt_who
+ : outgoing->remail == NULL
+ ? _("FORWARD (as e-mail) to : ")
+ : _("BOUNCE (redirect) message to : "),
+ ekey, help, &flags);
+ }
+ else
+ rc = 0;
+
+ switch(rc){
+ case -1:
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Internal problem encountered");
+ retval = -1;
+ done++;
+ break;
+
+ case 30 :
+ if((p = get_prev_hist(history, *tobufp, 0, NULL)) != NULL){
+ strncpy(*tobufp, p, resize_len);
+ (*tobufp)[resize_len-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+ case 31 :
+ if((p = get_next_hist(history, *tobufp, 0, NULL)) != NULL){
+ strncpy(*tobufp, p, resize_len);
+ (*tobufp)[resize_len-1] = '\0';
+ }
+ else
+ Writechar(BELL, 0);
+
+ break;
+
+ case 2: /* ^T */
+ case 0:
+ {void (*redraw) (void) = ps_global->redrawer;
+ char *returned_addr = NULL;
+ int len, l;
+
+ if(rc == 2){
+ int got_something = 0;
+
+ push_titlebar_state();
+ returned_addr = addr_book_bounce();
+
+ /*
+ * Just make it look like user typed this list in.
+ */
+ if(returned_addr){
+ got_something++;
+ if((l=resize_len) < (len = strlen(returned_addr)) + 1){
+ l = len;
+ fs_resize((void **) tobufp, (size_t) (l+1));
+ }
+
+ strncpy(*tobufp, returned_addr, l);
+ (*tobufp)[l] = '\0';
+ fs_give((void **)&returned_addr);
+ }
+
+ ClearScreen();
+ pop_titlebar_state();
+ redraw_titlebar();
+ if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
+ (*ps_global->redrawer)();
+
+ if(!got_something)
+ continue;
+ }
+
+ if(*tobufp && **tobufp != '\0'){
+ char *errbuf, *addr;
+ int tolen;
+
+ save_hist(history, *tobufp, 0, NULL);
+
+ errbuf = NULL;
+
+ /*
+ * If role has an fcc, use it instead of what build_address
+ * tells us.
+ */
+ if(role && role->fcc){
+ if(ba_fcc.tptr)
+ fs_give((void **) &ba_fcc.tptr);
+
+ ba_fcc.tptr = cpystr(role->fcc);
+ }
+
+ if(build_address(*tobufp, &addr, &errbuf,
+ (role && role->fcc) ? NULL : &ba_fcc, NULL) >= 0){
+ int sendit = 0;
+
+ if(errbuf)
+ fs_give((void **)&errbuf);
+
+ if((l=strlen(*tobufp)) < (tolen = strlen(addr)) + 1){
+ l = tolen;
+ fs_resize((void **) tobufp, (size_t) (l+1));
+ }
+
+ strncpy(*tobufp, addr, l);
+ (*tobufp)[l] = '\0';
+ if(used_tobufval)
+ *used_tobufval = cpystr(addr);
+
+ /* confirm address */
+ if(flagsarg & SS_PROMPTFORTO){
+ char dsn_string[30];
+ int dsn_label = 0, dsn_show, i;
+ int verbose_label = 0;
+ ESCKEY_S opts[13];
+
+ strings2outgoing(header, body, NULL, 0);
+
+ if((flagsarg & SS_PROMPTFORTO)
+ && ((x = check_addresses(header)) == CA_BAD
+ || (x == CA_EMPTY && F_OFF(F_FCC_ON_BOUNCE,
+ ps_global))))
+ /*--- Addresses didn't check out---*/
+ continue;
+
+ i = 0;
+ opts[i].ch = 'y';
+ opts[i].rval = 'y';
+ opts[i].name = "Y";
+ opts[i++].label = N_("Yes");
+
+ opts[i].ch = 'n';
+ opts[i].rval = 'n';
+ opts[i].name = "N";
+ opts[i++].label = N_("No");
+
+ call_mailer_flags &= ~CM_VERBOSE; /* clear verbose */
+ if(F_ON(F_VERBOSE_POST, ps_global)){
+ /* setup keymenu slot to toggle verbose mode */
+ opts[i].ch = ctrl('W');
+ opts[i].rval = 12;
+ opts[i].name = "^W";
+ verbose_label = i++;
+ if(F_ON(F_DSN, ps_global)){
+ opts[i].ch = 0;
+ opts[i].rval = 0;
+ opts[i].name = "";
+ opts[i++].label = "";
+ }
+ }
+
+ /* clear DSN flags */
+ call_mailer_flags &= ~(CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL);
+ if(F_ON(F_DSN, ps_global)){
+ /* setup keymenu slots to toggle dsn bits */
+ opts[i].ch = 'd';
+ opts[i].rval = 'd';
+ opts[i].name = "D";
+ opts[i].label = "DSNOpts";
+ dsn_label = i++;
+ opts[i].ch = -2;
+ opts[i].rval = 's';
+ opts[i].name = "S";
+ opts[i++].label = "";
+ opts[i].ch = -2;
+ opts[i].rval = 'x';
+ opts[i].name = "X";
+ opts[i++].label = "";
+ opts[i].ch = -2;
+ opts[i].rval = 'h';
+ opts[i].name = "H";
+ opts[i++].label = "";
+ }
+
+ opts[i].ch = -1;
+
+ while(1){
+ int rv;
+
+ dsn_show = (call_mailer_flags & CM_DSN_SHOW);
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "%s%s%s%s%s%sto \"%s\" ? ",
+ prmpt_cnf ? prmpt_cnf : "Send message ",
+ ((call_mailer_flags & CM_VERBOSE)
+ || (dsn_show))
+ ? "(" : "",
+ (call_mailer_flags & CM_VERBOSE)
+ ? "in verbose mode" : "",
+ (dsn_show && (call_mailer_flags & CM_VERBOSE))
+ ? ", " : "",
+ (dsn_show) ? dsn_string : "",
+ ((call_mailer_flags & CM_VERBOSE) || dsn_show)
+ ? ") " : "",
+ (addr && *addr)
+ ? addr
+ : (F_ON(F_FCC_ON_BOUNCE, ps_global)
+ && ba_fcc.tptr && ba_fcc.tptr[0])
+ ? ba_fcc.tptr
+ : "");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ if((strlen(tmp_20k_buf) >
+ ps_global->ttyo->screen_cols - 2) &&
+ ps_global->ttyo->screen_cols >= 7)
+ strncpy(tmp_20k_buf+ps_global->ttyo->screen_cols-7,
+ "...? ", SIZEOF_20KBUF-ps_global->ttyo->screen_cols-7);
+
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ if(verbose_label)
+ opts[verbose_label].label =
+ /* TRANSLATORS: several possible key labels follow */
+ (call_mailer_flags & CM_VERBOSE) ? N_("Normal") : N_("Verbose");
+
+ if(F_ON(F_DSN, ps_global)){
+ if(call_mailer_flags & CM_DSN_SHOW){
+ opts[dsn_label].label =
+ (call_mailer_flags & CM_DSN_DELAY)
+ ? N_("NoDelay") : N_("Delay");
+ opts[dsn_label+1].ch = 's';
+ opts[dsn_label+1].label =
+ (call_mailer_flags & CM_DSN_SUCCESS)
+ ? N_("NoSuccess") : N_("Success");
+ opts[dsn_label+2].ch = 'x';
+ opts[dsn_label+2].label =
+ (call_mailer_flags & CM_DSN_NEVER)
+ ? N_("ErrRets") : N_("NoErrRets");
+ opts[dsn_label+3].ch = 'h';
+ opts[dsn_label+3].label =
+ (call_mailer_flags & CM_DSN_FULL)
+ ? N_("RetHdrs") : N_("RetFull");
+ }
+ }
+
+ rv = radio_buttons(tmp_20k_buf,
+ -FOOTER_ROWS(ps_global), opts,
+ 'y', 'z', NO_HELP, RB_NORM);
+ if(rv == 'y'){ /* user ACCEPTS! */
+ sendit = 1;
+ break;
+ }
+ else if(rv == 'n'){ /* Declined! */
+ break;
+ }
+ else if(rv == 'z'){ /* Cancelled! */
+ break;
+ }
+ else if(rv == 12){ /* flip verbose bit */
+ if(call_mailer_flags & CM_VERBOSE)
+ call_mailer_flags &= ~CM_VERBOSE;
+ else
+ call_mailer_flags |= CM_VERBOSE;
+ }
+ else if(call_mailer_flags & CM_DSN_SHOW){
+ if(rv == 's'){ /* flip success bit */
+ call_mailer_flags ^= CM_DSN_SUCCESS;
+ /* turn off related bits */
+ if(call_mailer_flags & CM_DSN_SUCCESS)
+ call_mailer_flags &= ~(CM_DSN_NEVER);
+ }
+ else if(rv == 'd'){ /* flip delay bit */
+ call_mailer_flags ^= CM_DSN_DELAY;
+ /* turn off related bits */
+ if(call_mailer_flags & CM_DSN_DELAY)
+ call_mailer_flags &= ~(CM_DSN_NEVER);
+ }
+ else if(rv == 'x'){ /* flip never bit */
+ call_mailer_flags ^= CM_DSN_NEVER;
+ /* turn off related bits */
+ if(call_mailer_flags & CM_DSN_NEVER)
+ call_mailer_flags &= ~(CM_DSN_SUCCESS | CM_DSN_DELAY);
+ }
+ else if(rv == 'h'){ /* flip full bit */
+ call_mailer_flags ^= CM_DSN_FULL;
+ }
+ }
+ else if(rv == 'd'){ /* show dsn options */
+ call_mailer_flags |= (CM_DSN_SHOW | CM_DSN_SUCCESS | CM_DSN_DELAY | CM_DSN_FULL);
+ }
+
+ snprintf(dsn_string, sizeof(dsn_string), _("DSN requested[%s%s%s%s]"),
+ (call_mailer_flags & CM_DSN_NEVER)
+ ? _("Never") : "F",
+ (call_mailer_flags & CM_DSN_DELAY)
+ ? "D" : "",
+ (call_mailer_flags & CM_DSN_SUCCESS)
+ ? "S" : "",
+ (call_mailer_flags & CM_DSN_NEVER)
+ ? ""
+ : (call_mailer_flags & CM_DSN_FULL) ? "-Full"
+ : "-Hdrs");
+ dsn_string[sizeof(dsn_string)-1] = '\0';
+ }
+ }
+
+ if(addr)
+ fs_give((void **)&addr);
+
+ if(!(flagsarg & SS_PROMPTFORTO) || sendit){
+ char *fcc = NULL;
+ CONTEXT_S *fcc_cntxt = NULL;
+
+ if(F_ON(F_FCC_ON_BOUNCE, ps_global)){
+ if(ba_fcc.tptr)
+ fcc = cpystr(ba_fcc.tptr);
+
+ set_last_fcc(fcc);
+
+ /*
+ * If special name "inbox" then replace it with the
+ * real inbox path.
+ */
+ if(ps_global->VAR_INBOX_PATH
+ && strucmp(fcc, ps_global->inbox_name) == 0){
+ char *replace_fcc;
+
+ replace_fcc = cpystr(ps_global->VAR_INBOX_PATH);
+ fs_give((void **) &fcc);
+ fcc = replace_fcc;
+ }
+ }
+
+ /*---- Check out fcc -----*/
+ if(fcc && *fcc){
+ (void) commence_fcc(fcc, &fcc_cntxt, FALSE);
+ if(!lmc.so){
+ dprint((4,"can't open fcc, cont\n"));
+ if(!(flagsarg & SS_PROMPTFORTO)){
+ retval = -1;
+ fs_give((void **)&fcc);
+ fcc = NULL;
+ goto finish;
+ }
+ else
+ continue;
+ }
+ else
+ so_truncate(lmc.so, fcc_size_guess(*body) + 2048);
+ }
+ else
+ lmc.so = NULL;
+
+ if(!(outgoing->to || outgoing->cc || outgoing->bcc
+ || lmc.so)){
+ q_status_message(SM_ORDER, 3, 5, _("No recipients specified!"));
+ continue;
+ }
+
+ if(outgoing->to || outgoing->cc || outgoing->bcc){
+ char **alt_smtp = NULL;
+
+ if(role && role->smtp){
+ if(ps_global->FIX_SMTP_SERVER
+ && ps_global->FIX_SMTP_SERVER[0])
+ q_status_message(SM_ORDER | SM_DING, 5, 5, _("Use of a role-defined smtp-server is administratively prohibited"));
+ else
+ alt_smtp = role->smtp;
+ }
+
+ result = call_mailer(header, *body, alt_smtp,
+ call_mailer_flags,
+ call_mailer_file_result,
+ pipe_callback);
+ mark_address_failure_for_pico(header);
+ }
+ else
+ result = 0;
+
+ if(result == 1 && !lmc.so)
+ q_status_message(SM_ORDER, 0, 3, _("Message sent"));
+
+ /*----- Was there an fcc involved? -----*/
+ if(lmc.so){
+ if(result == 1
+ || (result == 0
+ && pine_rfc822_output(header, *body, NULL, NULL))){
+ char label[50];
+
+ strncpy(label, "Fcc", sizeof(label));
+ label[sizeof(label)-1] = '\0';
+ if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
+ snprintf(label + 3, sizeof(label)-3, " to %s", fcc);
+ label[sizeof(label)-1] = '\0';
+ }
+
+ /* Now actually copy to fcc folder and close */
+ fcc_result =
+ write_fcc(fcc, fcc_cntxt, lmc.so, NULL,
+ label,
+ F_ON(F_MARK_FCC_SEEN, ps_global)
+ ? "\\SEEN" : NULL);
+ }
+ else if(result == 0){
+ q_status_message(SM_ORDER,3,5,
+ _("Fcc Failed!. No message saved."));
+ retval = -1;
+ dprint((1, "explicit fcc write failed!\n"));
+ }
+
+ so_give(&lmc.so);
+ }
+
+ if(result < 0){
+ dprint((1, "Bounce failed\n"));
+ if(!(flagsarg & SS_PROMPTFORTO))
+ retval = -1;
+ else
+ continue;
+ }
+ else if(result == 1){
+ if(!fcc)
+ q_status_message(SM_ORDER, 0, 3,
+ _("Message sent"));
+ else{
+ int avail = ps_global->ttyo->screen_cols-2;
+ int need, fcclen;
+ char *part1 = "Message sent and ";
+ char *part2 = fcc_result ? "" : "NOT ";
+ char *part3 = "copied to ";
+ fcclen = strlen(fcc);
+
+ need = 2 + strlen(part1) + strlen(part2) +
+ strlen(part3) + fcclen;
+
+ if(need > avail && fcclen > 6)
+ fcclen -= MIN(fcclen-6, need-avail);
+
+ q_status_message4(SM_ORDER, 0, 3,
+ "%s%s%s\"%s\"",
+ part1, part2, part3,
+ short_str(fcc,
+ (char *)tmp_20k_buf,
+ SIZEOF_20KBUF,
+ fcclen, FrontDots));
+ }
+ }
+
+ if(fcc)
+ fs_give((void **)&fcc);
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 3, _("Send cancelled"));
+ retval = -1;
+ }
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ _("Error in address: %s"), errbuf);
+ if(errbuf)
+ fs_give((void **)&errbuf);
+
+ if(!(flagsarg & SS_PROMPTFORTO))
+ retval = -1;
+ else
+ continue;
+ }
+
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 5,
+ _("No addressee! No e-mail sent."));
+ retval = -1;
+ }
+ }
+
+ done++;
+ break;
+
+ case 1:
+ q_status_message(SM_ORDER, 0, 3, _("Send cancelled"));
+ done++;
+ retval = -1;
+ break;
+
+ case 3:
+ help = (help == NO_HELP)
+ ? (outgoing->remail == NULL
+ ? h_anon_forward
+ : h_bounce)
+ : NO_HELP;
+ break;
+
+ case 11:
+ if(**tobufp){
+ char *new_nickname = NULL;
+ int l;
+ int ambiguity;
+
+ ambiguity = abook_nickname_complete(*tobufp, &new_nickname,
+ (lastrc==rc && !(flags & OE_USER_MODIFIED)), ANC_AFTERCOMMA);
+ if(new_nickname){
+ if(*new_nickname){
+ if((l=strlen(new_nickname)) > resize_len){
+ resize_len = l;
+ fs_resize((void **) tobufp, resize_len+1);
+ }
+
+ strncpy(*tobufp, new_nickname, l);
+ (*tobufp)[l] = '\0';
+ }
+
+ fs_give((void **) &new_nickname);
+ }
+
+ if(ambiguity != 2)
+ Writechar(BELL, 0);
+ }
+
+ break;
+
+ case 4: /* can't suspend */
+ default:
+ break;
+ }
+ }
+
+finish:
+ if(ba_fcc.tptr)
+ fs_give((void **)&ba_fcc.tptr);
+
+ pine_free_env(&header);
+
+ return(retval);
+}
+
+
+/*
+ * pine_simple_send_header - generate header suitable for simple_sending
+ */
+METAENV *
+pine_simple_send_header(ENVELOPE *outgoing, char **fccp, char ***tobufpp)
+{
+ METAENV *header;
+ PINEFIELD *pf;
+ static struct headerentry he_dummy;
+
+ header = pine_new_env(outgoing, fccp, tobufpp, NULL);
+
+ /* assign he_dummy to "To:" field "he" for strings2outgoing */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && !strucmp(pf->name, "to")){
+ memset((void *) &he_dummy, 0, sizeof(he_dummy));
+ pf->extdata = (void *) &he_dummy;
+ HE(pf)->dirty = 1;
+ break;
+ }
+
+ return(header);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Prepare data structures for pico, call pico, then post message
+
+ Args: outgoing -- Partially formatted outgoing ENVELOPE
+ body -- Body of outgoing message
+ editor_title -- Title for anchor line in composer
+ fcc_arg -- The file carbon copy field
+ reply -- Struct describing set of msgs being replied-to
+ lcc_arg --
+ custom -- custom header list.
+ sticky_fcc --
+
+ Result: message is edited, then postponed, cancelled or sent.
+
+ Fields:
+ remail -
+ return_path -
+ date added here
+ from added here
+ sender -
+ reply_to -
+ subject passed in, edited and cannonized here
+ to possibly passed in, edited and cannonized here
+ cc possibly passed in, edited and cannonized here
+ bcc edited and cannonized here
+ in_reply_to generated in reply() and passed in
+ message_id -
+
+ Storage for these fields comes from anywhere outside. It is remalloced
+ here so the composer can realloc them if needed. The copies here are also
+ freed here.
+
+Can only deal with message bodies that are either TYPETEXT or TYPEMULTIPART
+with the first part TYPETEXT! All newlines in the text here also end with
+CRLF.
+
+There's a further assumption that the text in the TYPETEXT part is
+stored in a storage object (see filter.c).
+ ----*/
+void
+pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body,
+ char *editor_title, ACTION_S *role, char *fcc_arg,
+ REPLY_S *reply, REDRAFT_POS_S *redraft_pos, char *lcc_arg,
+ PINEFIELD *custom, int flags)
+{
+ int i, fixed_cnt, total_cnt, index,
+ editor_result = 0, body_start = 0, use_news_order = 0;
+ char *p, *addr, *fcc, *fcc_to_free = NULL;
+ char *start_here_name = NULL;
+ char *suggested_nntp_server = NULL;
+ char *title = NULL;
+ struct headerentry *he, *headents, *he_to, *he_fcc, *he_news = NULL, *he_lcc = NULL,
+ *he_from = NULL;
+ PINEFIELD *pfields, *pf, *pf_nobody = NULL, *pf_to = NULL,
+ *pf_smtp_server, *pf_nntp_server,
+ *pf_fcc = NULL, *pf_err, *pf_uid, *pf_mbox, *pf_curpos,
+ *pf_ourrep, *pf_ourhdrs, **sending_order;
+ METAENV header;
+ ADDRESS *lcc_addr = NULL;
+ ADDRESS *nobody_addr = NULL;
+ BODY_PARTICULARS_S *bp;
+ STORE_S *orig_so = NULL;
+ PICO pbuf1, *save_previous_pbuf;
+ CustomType ct;
+ REDRAFT_POS_S *local_redraft_pos = NULL;
+
+ dprint((1,"\n=== send called ===\n"));
+
+ save_previous_pbuf = pbf;
+ pbf = &pbuf1;
+ standard_picobuf_setup(pbf);
+
+ /*
+ * Cancel any pending initial commands since pico uses a different
+ * input routine. If we didn't cancel them, they would happen after
+ * we returned from the editor, which would be confusing.
+ */
+ if(ps_global->in_init_seq){
+ ps_global->in_init_seq = 0;
+ ps_global->save_in_init_seq = 0;
+ clear_cursor_pos();
+ if(ps_global->initial_cmds){
+ if(ps_global->free_initial_cmds)
+ fs_give((void **)&(ps_global->free_initial_cmds));
+
+ ps_global->initial_cmds = 0;
+ }
+
+ F_SET(F_USE_FK,ps_global,ps_global->orig_use_fkeys);
+ }
+
+#if defined(DOS) || defined(OS2)
+ if(!dos_valid_from()){
+ pbf = save_previous_pbuf;
+ return;
+ }
+
+ pbf->upload = NULL;
+#else
+ pbf->upload = (ps_global->VAR_UPLOAD_CMD
+ && ps_global->VAR_UPLOAD_CMD[0])
+ ? upload_msg_to_pico : NULL;
+#endif
+
+ pbf->msgntext = message_format_for_pico;
+ pbf->mimetype = mime_type_for_pico;
+ pbf->exittest = send_exit_for_pico;
+ pbf->user_says_noflow = dont_flow_this_time;
+ pbf->newthread = new_thread_on_blank_subject;
+ ps_global->newthread = 0; /* reset this value */
+ if(F_OFF(F_CANCEL_CONFIRM, ps_global))
+ pbf->canceltest = cancel_for_pico;
+
+ pbf->alt_ed = (ps_global->VAR_EDITOR && ps_global->VAR_EDITOR[0] &&
+ ps_global->VAR_EDITOR[0][0])
+ ? ps_global->VAR_EDITOR : NULL;
+ pbf->alt_spell = (ps_global->VAR_SPELLER && ps_global->VAR_SPELLER[0])
+ ? ps_global->VAR_SPELLER : NULL;
+ pbf->always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
+ pbf->quote_str = reply && reply->prefix ? reply->prefix : "> ";
+ /* We actually want to set this only if message we're sending is flowed */
+ pbf->strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
+ pbf->allow_flowed_text = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global)
+ && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global)
+ && (strcmp(pbf->quote_str, "> ") == 0
+ || strcmp(pbf->quote_str, ">") == 0));
+ pbf->edit_offset = 0;
+ title = cpystr(set_titlebar(editor_title,
+ ps_global->mail_stream,
+ ps_global->context_current,
+ ps_global->cur_folder,ps_global->msgmap,
+ 0, FolderName, 0, 0, NULL));
+ pbf->pine_anchor = title;
+
+#if defined(DOS) || defined(OS2)
+ if(!pbf->oper_dir && ps_global->VAR_FILE_DIR){
+ pbf->oper_dir = ps_global->VAR_FILE_DIR;
+ }
+#endif
+
+ if(redraft_pos && editor_title && !strcmp(editor_title, COMPOSE_MAIL_TITLE))
+ pbf->pine_flags |= P_CHKPTNOW;
+
+ /* NOTE: initial cursor position set below */
+
+ dprint((9, "flags: %x\n", pbf->pine_flags));
+
+ /*
+ * When user runs compose and the current folder is a newsgroup,
+ * offer to post to the current newsgroup.
+ */
+ if(!(outgoing->to || (outgoing->newsgroups && *outgoing->newsgroups))
+ && IS_NEWS(ps_global->mail_stream)){
+ char prompt[200], news_group[MAILTMPLEN];
+
+ pine_send_newsgroup_name(ps_global->mail_stream->mailbox, news_group,
+ sizeof(news_group));
+
+ /*
+ * Replies don't get this far because To or Newsgroups will already
+ * be filled in. So must be either ordinary compose or forward.
+ * Forward sets subject, so use that to tell the difference.
+ */
+ if(news_group[0] && !outgoing->subject){
+ int ch = 'y';
+ int ret_val;
+ char *errmsg = NULL;
+ BUILDER_ARG *fcc_build = NULL;
+
+ if(F_OFF(F_COMPOSE_TO_NEWSGRP,ps_global)){
+ snprintf(prompt, sizeof(prompt),
+ _("Post to current newsgroup (%s)"), news_group);
+ prompt[sizeof(prompt)-1] = '\0';
+ ch = want_to(prompt, 'y', 'x', NO_HELP, WT_NORM);
+ }
+
+ switch(ch){
+ case 'y':
+ if(outgoing->newsgroups)
+ fs_give((void **)&outgoing->newsgroups);
+
+ if(!fcc_arg && !(role && role->fcc)){
+ fcc_build = (BUILDER_ARG *)fs_get(sizeof(BUILDER_ARG));
+ memset((void *)fcc_build, 0, sizeof(BUILDER_ARG));
+ fcc_build->tptr = fcc_to_free;
+ }
+
+ ret_val = news_build(news_group, &outgoing->newsgroups,
+ &errmsg, fcc_build, NULL);
+
+ if(ret_val == -1){
+ if(outgoing->newsgroups)
+ fs_give((void **)&outgoing->newsgroups);
+
+ outgoing->newsgroups = cpystr(news_group);
+ }
+
+ if(!fcc_arg && !(role && role->fcc)){
+ fcc_arg = fcc_to_free = fcc_build->tptr;
+ fs_give((void **)&fcc_build);
+ }
+
+ if(errmsg){
+ if(*errmsg){
+ q_status_message(SM_ORDER, 3, 3, errmsg);
+ display_message(NO_OP_COMMAND);
+ }
+
+ fs_give((void **)&errmsg);
+ }
+
+ break;
+
+ case 'x': /* ^C */
+ q_status_message(SM_ORDER, 0, 3, _("Message cancelled"));
+ dprint((4, "=== send: cancelled\n"));
+ pbf = save_previous_pbuf;
+ return;
+
+ case 'n':
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ if(F_ON(F_PREDICT_NNTP_SERVER, ps_global)
+ && outgoing->newsgroups && *outgoing->newsgroups
+ && IS_NEWS(ps_global->mail_stream)){
+ NETMBX news_mb;
+
+ if(mail_valid_net_parse(ps_global->mail_stream->original_mailbox,
+ &news_mb))
+ if(!strucmp(news_mb.service, "nntp")){
+ if(*ps_global->mail_stream->original_mailbox == '{'){
+ char *svcp = NULL, *psvcp;
+
+ suggested_nntp_server =
+ cpystr(ps_global->mail_stream->original_mailbox + 1);
+ if((p = strindex(suggested_nntp_server, '}')) != NULL)
+ *p = '\0';
+ for(p = strindex(suggested_nntp_server, '/'); p && *p;
+ p = strindex(p, '/')){
+ /* take out /nntp, which gets added in nntp_open */
+ if(!struncmp(p, "/nntp", 5))
+ svcp = p + 5;
+ else if(!struncmp(p, "/service=nntp", 13))
+ svcp = p + 13;
+ else if(!struncmp(p, "/service=\"nntp\"", 15))
+ svcp = p + 15;
+ else
+ p++;
+ if(svcp){
+ if(*svcp == '\0')
+ *p = '\0';
+ else if(*svcp == '/' || *svcp == ':'){
+ for(psvcp = p; *svcp; svcp++, psvcp++)
+ *psvcp = *svcp;
+ *psvcp = '\0';
+ }
+ svcp = NULL;
+ }
+ }
+ }
+ else
+ suggested_nntp_server = cpystr(news_mb.orighost);
+ }
+ }
+
+ /*
+ * If we don't already have custom headers set and the role has custom
+ * headers, then incorporate those custom headers into "custom".
+ */
+ if(!custom){
+ PINEFIELD *dflthdrs = NULL, *rolehdrs = NULL;
+
+ dflthdrs = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
+/*
+ * If we allow the Combine argument here, we're saying that we want to
+ * combine the values from the envelope and the role for the fields To,
+ * Cc, Bcc, and Newsgroups. For example, if we are replying to a message
+ * we'll have a To in the envelope because we're replying. If our role also
+ * has a To action, then Combine would combine those two and offer both
+ * to the user. We've decided against doing this. Instead, we always use
+ * Replace, and the role's header value replaces the value from the
+ * envelope. It might also make sense in some cases to do the opposite,
+ * which would be treating the role headers as defaults, just like
+ * customized-hdrs.
+ */
+#ifdef WANT_TO_COMBINE_ADDRESSES
+ if(role && role->cstm)
+ rolehdrs = parse_custom_hdrs(role->cstm, Combine);
+#else
+ if(role && role->cstm)
+ rolehdrs = parse_custom_hdrs(role->cstm, Replace);
+#endif
+
+ if(rolehdrs){
+ custom = combine_custom_headers(dflthdrs, rolehdrs);
+ if(dflthdrs){
+ free_prompts(dflthdrs);
+ free_customs(dflthdrs);
+ }
+
+ if(rolehdrs){
+ free_prompts(rolehdrs);
+ free_customs(rolehdrs);
+ }
+ }
+ else
+ custom = dflthdrs;
+ }
+
+ g_rolenick = role ? role->nick : NULL;
+
+ /* how many fixed fields are there? */
+ for(fixed_cnt = 0; pf_template && pf_template[fixed_cnt].name; fixed_cnt++)
+ ;
+
+ total_cnt = fixed_cnt + count_custom_hdrs_pf(custom,1);
+
+ /* the fixed part of the PINEFIELDs */
+ i = fixed_cnt * sizeof(PINEFIELD);
+ pfields = (PINEFIELD *)fs_get((size_t) i);
+ memset(pfields, 0, (size_t) i);
+
+ /* temporary headerentry array for pico */
+ i = (total_cnt + 1) * sizeof(struct headerentry);
+ headents = (struct headerentry *)fs_get((size_t) i);
+ memset(headents, 0, (size_t) i);
+
+ i = total_cnt * sizeof(PINEFIELD *);
+ sending_order = (PINEFIELD **)fs_get((size_t) i);
+ memset(sending_order, 0, (size_t) i);
+
+ pbf->headents = headents;
+ header.env = outgoing;
+ header.local = pfields;
+ header.sending_order = sending_order;
+
+ /* custom part of PINEFIELDs */
+ header.custom = custom;
+
+ he = headents;
+ pf = pfields;
+
+ /*
+ * For Address types, pf->addr points to an ADDRESS *.
+ * If that address is in the "outgoing" envelope, it will
+ * be freed by the caller, otherwise, it should be freed here.
+ * Pf->textbuf for an Address is used a little to set up a default,
+ * but then is freed right away below. Pf->scratch is used for a
+ * pointer to some alloced space for pico to edit in. Addresses in
+ * the custom area are freed by free_customs().
+ *
+ * For FreeText types, pf->addr is not used. Pf->text points to a
+ * pointer that points to the text. Pf->textbuf points to a copy of
+ * the text that must be freed before we leave, otherwise, it is
+ * probably a pointer into the envelope and that gets freed by the
+ * caller.
+ *
+ * He->realaddr is the pointer to the text that pico actually edits.
+ */
+
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+# define NN 4
+#else
+# define NN 3
+#endif
+
+ if(outgoing->newsgroups && *outgoing->newsgroups)
+ use_news_order++;
+
+ /* initialize the fixed header elements of the two temp arrays */
+ for(i=0; i < fixed_cnt; i++, pf++){
+ static int news_order[] = {
+ N_AUTHRCVD,N_FROM, N_REPLYTO, N_NEWS, N_TO, N_CC, N_BCC,
+ N_FCC, N_LCC, N_ATTCH, N_SUBJ, N_REF, N_DATE, N_INREPLY,
+ N_MSGID, N_PRIORITY, N_USERAGENT, N_NOBODY, N_POSTERR, N_RPLUID, N_RPLMBOX,
+ N_SMTP, N_NNTP, N_CURPOS, N_OURREPLYTO, N_OURHDRS
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+ , N_SENDER
+#endif
+ };
+
+ index = i;
+ /* slightly different editing order if sending to news */
+ if(use_news_order &&
+ index >= 0 && index < sizeof(news_order)/sizeof(news_order[0]))
+ index = news_order[i];
+
+ /* copy the templates */
+ *he = he_template[index];
+
+ pf->name = cpystr(pf_template[index].name);
+ if(index == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global))
+ /* slide string over so it is Sender instead of X-X-Sender */
+ for(p=pf->name; *(p+1); p++)
+ *p = *(p+4);
+
+ pf->type = pf_template[index].type;
+ pf->canedit = pf_template[index].canedit;
+ pf->rcptto = pf_template[index].rcptto;
+ pf->writehdr = pf_template[index].writehdr;
+ pf->localcopy = pf_template[index].localcopy;
+ pf->extdata = he;
+ pf->next = pf + 1;
+
+ he->rich_header = view_as_rich(pf->name, he->rich_header);
+ if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global))
+ he->nickcmpl = NULL;
+
+ switch(pf->type){
+ case FreeText: /* realaddr points to c-client env */
+ if(index == N_NEWS){
+ sending_order[1] = pf;
+ he->realaddr = &outgoing->newsgroups;
+ he_news = he;
+
+ switch(set_default_hdrval(pf, custom)){
+ case Replace:
+ if(*he->realaddr)
+ fs_give((void **)he->realaddr);
+
+ *he->realaddr = pf->textbuf;
+ pf->textbuf = NULL;
+ he->sticky = 1;
+ break;
+
+ case Combine:
+ if(*he->realaddr){ /* combine values */
+ if(pf->textbuf && *pf->textbuf){
+ char *combined_hdr;
+ size_t l;
+
+ l = strlen(*he->realaddr) + strlen(pf->textbuf) + 1;
+ combined_hdr = (char *) fs_get((l+1) * sizeof(char));
+ strncpy(combined_hdr, *he->realaddr, l);
+ combined_hdr[l] = '\0';
+ strncat(combined_hdr, ",", l+1-1-strlen(combined_hdr));
+ combined_hdr[l] = '\0';
+ strncat(combined_hdr, pf->textbuf, l+1-1-strlen(combined_hdr));
+ combined_hdr[l] = '\0';
+
+ fs_give((void **)he->realaddr);
+ *he->realaddr = combined_hdr;
+ q_status_message(SM_ORDER, 3, 3,
+ "Adding newsgroup from role");
+ he->sticky = 1;
+ }
+ }
+ else{
+ *he->realaddr = pf->textbuf;
+ pf->textbuf = NULL;
+ }
+
+ break;
+
+ case UseAsDef:
+ /* if no value, use default */
+ if(!*he->realaddr){
+ *he->realaddr = pf->textbuf;
+ pf->textbuf = NULL;
+ }
+
+ break;
+
+ case NoMatch:
+ break;
+ }
+
+ /* If there is a newsgroup, we'd better show it */
+ if(outgoing->newsgroups && *outgoing->newsgroups)
+ he->rich_header = 0; /* force on by default */
+
+ if(pf->textbuf)
+ fs_give((void **)&pf->textbuf);
+
+ pf->text = he->realaddr;
+ }
+ else if(index == N_DATE){
+ sending_order[2] = pf;
+ pf->text = (char **) &outgoing->date;
+ pf->extdata = NULL;
+ }
+ else if(index == N_INREPLY){
+ sending_order[NN+9] = pf;
+ pf->text = &outgoing->in_reply_to;
+ pf->extdata = NULL;
+ }
+ else if(index == N_MSGID){
+ sending_order[NN+10] = pf;
+ pf->text = &outgoing->message_id;
+ pf->extdata = NULL;
+ }
+ else if(index == N_REF){
+ sending_order[NN+11] = pf;
+ pf->text = &outgoing->references;
+ pf->extdata = NULL;
+ }
+ else if(index == N_PRIORITY){
+ sending_order[NN+12] = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_USERAGENT){
+ sending_order[NN+13] = pf;
+ pf->text = &pf->textbuf;
+ pf->textbuf = generate_user_agent();
+ pf->extdata = NULL;
+ }
+ else if(index == N_POSTERR){
+ sending_order[NN+14] = pf;
+ pf_err = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_RPLUID){
+ sending_order[NN+15] = pf;
+ pf_uid = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_RPLMBOX){
+ sending_order[NN+16] = pf;
+ pf_mbox = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_SMTP){
+ sending_order[NN+17] = pf;
+ pf_smtp_server = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_NNTP){
+ sending_order[NN+18] = pf;
+ pf_nntp_server = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_CURPOS){
+ sending_order[NN+19] = pf;
+ pf_curpos = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_OURREPLYTO){
+ sending_order[NN+20] = pf;
+ pf_ourrep = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_OURHDRS){
+ sending_order[NN+21] = pf;
+ pf_ourhdrs = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else if(index == N_AUTHRCVD){
+ sending_order[0] = pf;
+ pf_ourhdrs = pf;
+ pf->text = &pf->textbuf;
+ pf->extdata = NULL;
+ }
+ else{
+ q_status_message(SM_ORDER | SM_DING, 3, 7,
+ "Botched: Unmatched FreeText header in pine_send");
+ }
+
+ break;
+
+ /* can't do a default for this one */
+ case Attachment:
+ /* If there is an attachment already, we'd better show them */
+ if(body && *body && (*body)->type != TYPETEXT)
+ he->rich_header = 0; /* force on by default */
+
+ break;
+
+ case Address:
+ switch(index){
+ case N_FROM:
+ sending_order[3] = pf;
+ pf->addr = &outgoing->from;
+ if(role && role->from){
+ if(ps_global->never_allow_changing_from)
+ q_status_message(SM_ORDER, 3, 3, _("Site policy doesn't allow changing From address so role's From has no effect"));
+ else{
+ outgoing->from = copyaddrlist(role->from);
+ he->display_it = 1; /* show it */
+ he->rich_header = 0;
+ }
+ }
+
+ he_from = he;
+ break;
+
+ case N_TO:
+ sending_order[NN+2] = pf;
+ pf->addr = &outgoing->to;
+ /* If already set, make it act like we typed it in */
+ if(outgoing->to
+ && outgoing->to->mailbox
+ && outgoing->to->mailbox[0]
+ && flags & PS_STICKY_TO)
+ he->sticky = 1;
+
+ he_to = he;
+ pf_to = pf;
+ break;
+
+ case N_NOBODY:
+ sending_order[NN+5] = pf;
+ pf_nobody = pf;
+ if(ps_global->VAR_EMPTY_HDR_MSG
+ && !ps_global->VAR_EMPTY_HDR_MSG[0]){
+ pf->addr = NULL;
+ }
+ else{
+ nobody_addr = mail_newaddr();
+ nobody_addr->next = mail_newaddr();
+ nobody_addr->mailbox = cpystr(rfc1522_encode(tmp_20k_buf,
+ SIZEOF_20KBUF,
+ (unsigned char *)(ps_global->VAR_EMPTY_HDR_MSG
+ ? ps_global->VAR_EMPTY_HDR_MSG
+ : "undisclosed-recipients"),
+ ps_global->posting_charmap));
+ pf->addr = &nobody_addr;
+ }
+
+ break;
+
+ case N_CC:
+ sending_order[NN+3] = pf;
+ pf->addr = &outgoing->cc;
+ break;
+
+ case N_BCC:
+ sending_order[NN+4] = pf;
+ pf->addr = &outgoing->bcc;
+ /* if bcc exists, make sure it's exposed so nothing's
+ * sent by mistake...
+ */
+ if(outgoing->bcc)
+ he->display_it = 1;
+
+ break;
+
+ case N_REPLYTO:
+ sending_order[NN+1] = pf;
+ pf->addr = &outgoing->reply_to;
+ if(role && role->replyto){
+ if(outgoing->reply_to)
+ mail_free_address(&outgoing->reply_to);
+
+ outgoing->reply_to = copyaddrlist(role->replyto);
+ he->display_it = 1; /* show it */
+ he->rich_header = 0;
+ }
+
+ break;
+
+ case N_LCC:
+ sending_order[NN+7] = pf;
+ pf->addr = &lcc_addr;
+ he_lcc = he;
+ if(lcc_arg){
+ build_address(lcc_arg, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(&lcc_addr, addr,
+ ps_global->maildomain);
+ fs_give((void **)&addr);
+ he->display_it = 1;
+ }
+
+ break;
+
+#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH)
+ case N_SENDER:
+ sending_order[4] = pf;
+ pf->addr = &outgoing->sender;
+ break;
+#endif
+
+ default:
+ q_status_message1(SM_ORDER,3,7,
+ "Internal error: Address header %s", comatose(index));
+ break;
+ }
+
+ /*
+ * If this is a reply to news, don't show the regular email
+ * recipient headers (unless they are non-empty).
+ */
+ if((outgoing->newsgroups && *outgoing->newsgroups)
+ && (index == N_TO || index == N_CC
+ || index == N_BCC || index == N_LCC)
+ && (pf->addr && !*pf->addr)){
+ if((ct=set_default_hdrval(pf, custom)) >= UseAsDef &&
+ pf->textbuf && *pf->textbuf){
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(pf->addr, addr,
+ ps_global->maildomain);
+ fs_give((void **)&addr);
+ if(ct > UseAsDef)
+ he->sticky = 1;
+ }
+ else
+ he->rich_header = 1; /* hide */
+ }
+
+ /*
+ * If this address doesn't already have a value, then we check
+ * for a default value assigned by the user.
+ */
+ else if(pf->addr && !*pf->addr){
+ if((ct=set_default_hdrval(pf, custom)) >= UseAsDef &&
+ (index != N_FROM ||
+ (!ps_global->never_allow_changing_from &&
+ F_ON(F_ALLOW_CHANGING_FROM, ps_global))) &&
+ pf->textbuf && *pf->textbuf){
+
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+
+ /*
+ * Try to set To based on Lcc. Don't attempt Fcc.
+ */
+ if(index == N_LCC && !he_to->sticky && pf_to && pf_to->addr){
+ BUILDER_ARG *barg = NULL;
+ char *ppp = NULL;
+
+ if(*pf_to->addr)
+ ppp = addr_list_string(*pf_to->addr, NULL, 1);
+
+ if(!ppp)
+ ppp = cpystr("");
+
+ barg = (BUILDER_ARG *) fs_get(sizeof(*barg));
+ memset(barg, 0, sizeof(*barg));
+ barg->me = &(he->bldr_private);
+ barg->aff = &(he_to->bldr_private);
+ barg->tptr = cpystr(ppp);
+
+ build_addr_lcc(pf->textbuf, &addr, NULL, barg, NULL);
+ he->display_it = 1;
+
+ rfc822_parse_adrlist(pf->addr, addr,
+ ps_global->maildomain);
+ if(addr)
+ fs_give((void **) &addr);
+
+ if(ct > UseAsDef)
+ he->sticky = 1;
+
+ if(barg && barg->tptr && strcmp(ppp, barg->tptr)){
+ ADDRESS *a = NULL;
+
+ rfc822_parse_adrlist(&a, barg->tptr,
+ ps_global->maildomain);
+ if(a){
+ if(pf_to->addr)
+ mail_free_address(pf_to->addr);
+
+ *pf_to->addr = a;
+ }
+ }
+
+ if(barg){
+ if(barg->tptr)
+ fs_give((void **) &barg->tptr);
+
+ fs_give((void **) &barg);
+ }
+
+ if(ppp)
+ fs_give((void **) &ppp);
+ }
+ else{
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(pf->addr, addr,
+ ps_global->maildomain);
+ if(addr)
+ fs_give((void **) &addr);
+
+ if(ct > UseAsDef)
+ he->sticky = 1;
+ }
+ }
+
+ /* if we still don't have a from */
+ if(index == N_FROM && !*pf->addr)
+ *pf->addr = generate_from();
+ }
+
+ /*
+ * Addr is already set in the rest of the cases.
+ */
+ else if((index == N_FROM || index == N_REPLYTO) && pf->addr){
+ ADDRESS *adr = NULL;
+
+ /*
+ * We get to this case of the ifelse if the from or reply-to
+ * addr was set by a role above.
+ */
+
+ /* figure out the default value */
+ (void)set_default_hdrval(pf, custom);
+ if(pf->textbuf && *pf->textbuf){
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(&adr, addr,
+ ps_global->maildomain);
+ fs_give((void **)&addr);
+ }
+
+ /* if value set by role is different from default, show it */
+ if(adr && !address_is_same(*pf->addr, adr))
+ he->display_it = 1; /* start this off showing */
+
+ /* malformed */
+ if(!(*pf->addr)->mailbox){
+ fs_give((void **)pf->addr);
+ he->display_it = 1;
+ }
+
+ if(adr)
+ mail_free_address(&adr);
+ }
+ else if((index == N_TO || index == N_CC || index == N_BCC)
+ && pf->addr){
+ ADDRESS *a = NULL, **tail;
+
+ /*
+ * These three are different from the others because we
+ * might add the addresses to what is already there instead
+ * of replacing.
+ */
+
+ switch(set_default_hdrval(pf, custom)){
+ case Replace:
+ if(*pf->addr)
+ mail_free_address(pf->addr);
+
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
+ fs_give((void **)&addr);
+ he->sticky = 1;
+ break;
+
+ case Combine:
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(&a, addr, ps_global->maildomain);
+ fs_give((void **)&addr);
+ he->sticky = 1;
+ if(a){
+ for(tail = pf->addr; *tail; tail = &(*tail)->next)
+ ;
+ *tail = reply_cp_addr(ps_global, 0, NULL, NULL,
+ *pf->addr, NULL, a, RCA_ALL);
+ q_status_message(SM_ORDER, 3, 3,
+ "Adding addresses from role");
+ mail_free_address(&a);
+ }
+
+ break;
+
+ case UseAsDef:
+ case NoMatch:
+ break;
+ }
+
+ he->display_it = 1; /* start this off showing */
+ }
+ else if(pf->addr){
+ switch(set_default_hdrval(pf, custom)){
+ case Replace:
+ case Combine:
+ if(*pf->addr)
+ mail_free_address(pf->addr);
+
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
+ fs_give((void **)&addr);
+ he->sticky = 1;
+ break;
+
+ case UseAsDef:
+ case NoMatch:
+ break;
+ }
+
+ he->display_it = 1;
+ }
+
+ if(pf->addr && *pf->addr && !(*pf->addr)->mailbox){
+ mail_free_address(pf->addr);
+ he->display_it = 1; /* start this off showing */
+ }
+
+ if(pf->textbuf) /* free default value in any case */
+ fs_give((void **)&pf->textbuf);
+
+ /* outgoing2strings will alloc the string pf->scratch below */
+ he->realaddr = &pf->scratch;
+ break;
+
+ case Fcc:
+ sending_order[NN+8] = pf;
+ pf_fcc = pf;
+ if(role && role->fcc)
+ fcc = role->fcc;
+ else
+ fcc = get_fcc(fcc_arg);
+
+ if(fcc_to_free){
+ fs_give((void **)&fcc_to_free);
+ fcc_arg = NULL;
+ }
+
+ if(((flags & PS_STICKY_FCC) && fcc[0]) || (role && role->fcc))
+ he->sticky = 1;
+
+ if(role)
+ role->fcc = NULL;
+
+ if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC) != 0)
+ he->display_it = 1; /* start this off showing */
+
+ he->realaddr = &fcc;
+ pf->text = &fcc;
+ he_fcc = he;
+ break;
+
+ case Subject :
+ sending_order[NN+6] = pf;
+
+ switch(set_default_hdrval(pf, custom)){
+ case Replace:
+ case Combine:
+ pf->scratch = pf->textbuf;
+ pf->textbuf = NULL;
+ he->sticky = 1;
+ if(outgoing->subject)
+ fs_give((void **)&outgoing->subject);
+
+ break;
+
+ case UseAsDef:
+ case NoMatch:
+ /* if no value, use default */
+ if(outgoing->subject){
+ pf->scratch = cpystr(outgoing->subject);
+ }
+ else{
+ pf->scratch = pf->textbuf;
+ pf->textbuf = NULL;
+ }
+
+ break;
+ }
+
+ he->realaddr = &pf->scratch;
+ pf->text = &outgoing->subject;
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,3,7,
+ "Unknown header type %d in pine_send",
+ (void *)pf->type);
+ break;
+ }
+
+ /*
+ * We may or may not want to give the user the chance to edit
+ * the From and Reply-To lines. If they are listed in either
+ * Default-composer-hdrs or Customized-hdrs, then they can edit
+ * them, else no.
+ * If canedit is not set, that means that this header is not in
+ * the user's customized-hdrs. If rich_header is set, that
+ * means that this header is not in the user's
+ * default-composer-hdrs (since From and Reply-To are rich
+ * by default). So, don't give it an he to edit with in that case.
+ *
+ * For other types, just not setting canedit will cause it to be
+ * uneditable, regardless of what the user does.
+ */
+ switch(index){
+ case N_FROM:
+ /* to allow it, we let this fall through to the reply-to case below */
+ if(ps_global->never_allow_changing_from ||
+ (F_OFF(F_ALLOW_CHANGING_FROM, ps_global) &&
+ !(role && role->from))){
+ if(pf->canedit || !he->rich_header)
+ q_status_message(SM_ORDER, 3, 3,
+ _("Not allowed to change header \"From\""));
+
+ memset(he, 0, (size_t)sizeof(*he));
+ pf->extdata = NULL;
+ break;
+ }
+
+ case N_REPLYTO:
+ if(!pf->canedit && he->rich_header){
+ memset(he, 0, (size_t)sizeof(*he));
+ pf->extdata = NULL;
+ }
+ else{
+ pf->canedit = 1;
+ he++;
+ }
+
+ break;
+
+ default:
+ if(!pf->canedit){
+ memset(he, 0, (size_t)sizeof(*he));
+ pf->extdata = NULL;
+ }
+ else
+ he++;
+
+ break;
+ }
+ }
+
+ /*
+ * This is so the builder can tell the composer to fill the affected
+ * field based on the value in the field on the left.
+ *
+ * Note that this mechanism isn't completely general. Each entry has
+ * only a single next_affected, so if some other entry points an
+ * affected entry at an entry with a next_affected, they all inherit
+ * that next_affected. Since this isn't used much a careful ordering
+ * of the affected fields should make it a sufficient mechanism.
+ */
+ he_to->affected_entry = he_fcc;
+ he_news->affected_entry = he_fcc;
+ he_lcc->affected_entry = he_to;
+ he_to->next_affected = he_fcc;
+
+ (--pf)->next = (total_cnt != fixed_cnt) ? header.custom : NULL;
+
+ i--; /* subtract one because N_ATTCH doesn't get a sending_order slot */
+ /*
+ * Set up headerentries for custom fields.
+ * NOTE: "i" is assumed to now index first custom field in sending
+ * order.
+ */
+ for(pf = pf->next; pf && pf->name; pf = pf->next){
+ char *addr;
+
+ if(pf->standard)
+ continue;
+
+ pf->extdata = he;
+ pf->canedit = 1;
+ pf->rcptto = 0;
+ pf->writehdr = 1;
+ pf->localcopy = 1;
+
+ switch(pf->type){
+ case Address:
+ if(pf->addr){ /* better be set */
+ sending_order[i++] = pf;
+ *he = he_custom_addr_templ;
+ /* change default text into an ADDRESS */
+ /* strip quotes around whole default */
+ removing_trailing_white_space(pf->textbuf);
+ (void)removing_double_quotes(pf->textbuf);
+ build_address(pf->textbuf, &addr, NULL, NULL, NULL);
+ rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
+ fs_give((void **)&addr);
+ if(pf->textbuf)
+ fs_give((void **)&pf->textbuf);
+
+ he->realaddr = &pf->scratch;
+ if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global))
+ he->nickcmpl = NULL;
+ }
+
+ break;
+
+ case FreeText:
+ sending_order[i++] = pf;
+ *he = he_custom_free_templ;
+ he->realaddr = &pf->textbuf;
+ pf->text = &pf->textbuf;
+ if(((!pf->val || !pf->val[0]) && pf->textbuf && pf->textbuf[0]) ||
+ (pf->val && (!pf->textbuf || strcmp(pf->textbuf, pf->val))))
+ he->display_it = 1; /* show it */
+
+ break;
+
+ default:
+ q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d",
+ (void *)pf->type);
+ break;
+ }
+
+ he->name = pf->name;
+
+ /* use first 8 characters for prompt */
+ he->prompt = cpystr(" : ");
+ strncpy(he->prompt, he->name, MIN(strlen(he->name), he->prwid - 2));
+
+ he->rich_header = view_as_rich(he->name, he->rich_header);
+ he++;
+ }
+
+ /*
+ * Make sure at least *one* field is displayable...
+ */
+ for(index = -1, i=0, pf=header.local; pf && pf->name; pf=pf->next, i++)
+ if(HE(pf) && !HE(pf)->rich_header){
+ index = i;
+ break;
+ }
+
+ /*
+ * None displayable!!! Warn and display defaults.
+ */
+ if(index == -1){
+ q_status_message(SM_ORDER,0,5,
+ "No default-composer-hdrs matched, displaying defaults");
+ for(i = 0, pf = header.local; pf; pf = pf->next, i++)
+ if((i == N_TO || i == N_CC || i == N_SUBJ || i == N_ATTCH)
+ && HE(pf))
+ HE(pf)->rich_header = 0;
+ }
+
+ /*
+ * Save information about body which set_mime_type_by_grope might change.
+ * Then, if we get an error sending, we reset these things so that
+ * grope can do it's thing again after we edit some more.
+ */
+ if ((*body)->type == TYPEMULTIPART)
+ bp = save_body_particulars(&(*body)->nested.part->body);
+ else
+ bp = save_body_particulars(*body);
+
+
+ local_redraft_pos = redraft_pos;
+
+ /*----------------------------------------------------------------------
+ Loop calling the editor until everything goes well
+ ----*/
+ while(1){
+ int saved_user_timeout;
+
+ /* Reset body to what it was when we started. */
+ if ((*body)->type == TYPEMULTIPART)
+ reset_body_particulars(bp, &(*body)->nested.part->body);
+ else
+ reset_body_particulars(bp,*body);
+ /*
+ * set initial cursor position based on how many times we've been
+ * thru the loop...
+ */
+ if(reply && reply->pseudo){
+ pbf->pine_flags |= reply->data.pico_flags;
+ }
+ else if(body_start){
+ pbf->pine_flags |= P_BODY;
+ body_start = 0; /* maybe not next time */
+ }
+ else if(local_redraft_pos){
+ pbf->edit_offset = local_redraft_pos->offset;
+ /* set the start_here bit in correct header */
+ for(pf = header.local; pf && pf->name; pf = pf->next)
+ if(strcmp(pf->name, local_redraft_pos->hdrname) == 0
+ && HE(pf)){
+ HE(pf)->start_here = 1;
+ break;
+ }
+
+ /* If didn't find it, we start in body. */
+ if(!pf || !pf->name)
+ pbf->pine_flags |= P_BODY;
+ }
+ else if(reply && (!reply->forw && !reply->forwarded)){
+ pbf->pine_flags |= P_BODY;
+ }
+
+ /* in case these were turned on in previous pass through loop */
+ if(pf_nobody){
+ pf_nobody->writehdr = 0;
+ pf_nobody->localcopy = 0;
+ }
+
+ if(pf_fcc)
+ pf_fcc->localcopy = 0;
+
+ /*
+ * If a sending attempt failed after we passed the message text
+ * thru a user-defined filter, "orig_so" points to the original
+ * text. Replace the body's encoded data with the original...
+ */
+ if(orig_so){
+ STORE_S **so = (STORE_S **)(((*body)->type == TYPEMULTIPART)
+ ? &(*body)->nested.part->body.contents.text.data
+ : &(*body)->contents.text.data);
+ so_give(so);
+ *so = orig_so;
+ orig_so = NULL;
+ }
+
+ /*
+ * Convert the envelope and body to the string format that
+ * pico can edit
+ */
+ outgoing2strings(&header, *body, &pbf->msgtext, &pbf->attachments, 0);
+
+ for(pf = header.local; pf && pf->name; pf = pf->next){
+ /*
+ * If this isn't the first time through this loop, we may have
+ * freed some of the FreeText headers below so that they wouldn't
+ * show up as empty headers in the finished message. Need to
+ * alloc them again here so they can be edited.
+ */
+ if(pf->type == FreeText && HE(pf) && !*HE(pf)->realaddr)
+ *HE(pf)->realaddr = cpystr("");
+
+ if(pf->type != Attachment && HE(pf) && *HE(pf)->realaddr)
+ HE(pf)->maxlen = strlen(*HE(pf)->realaddr);
+ }
+
+ /*
+ * If From is exposed, probably by a role, then start the cursor
+ * on the first line which isn't filled in. If it isn't, then we
+ * don't move the cursor, mostly for back-compat.
+ */
+ if((!reply || reply->forw || reply->forwarded) &&
+ !local_redraft_pos && !(pbf->pine_flags & P_BODY) && he_from &&
+ (he_from->display_it || !he_from->rich_header)){
+ for(pf = header.local; pf && pf->name; pf = pf->next)
+ if(HE(pf) &&
+ (HE(pf)->display_it || !HE(pf)->rich_header) &&
+ HE(pf)->realaddr &&
+ (!*HE(pf)->realaddr || !**HE(pf)->realaddr)){
+ HE(pf)->start_here = 1;
+ break;
+ }
+ }
+
+#ifdef _WINDOWS
+ mswin_setwindowmenu (MENU_COMPOSER);
+#endif
+
+ cancel_busy_cue(-1);
+ flush_status_messages(1);
+
+ /* turn off user input timeout when in composer */
+ saved_user_timeout = ps_global->hours_to_timeout;
+ ps_global->hours_to_timeout = 0;
+ dprint((1, "\n ---- COMPOSER ----\n"));
+ editor_result = pico(pbf);
+ dprint((4, "... composer returns (0x%x)\n", editor_result));
+ ps_global->hours_to_timeout = saved_user_timeout;
+
+#ifdef _WINDOWS
+ mswin_setwindowmenu (MENU_DEFAULT);
+#endif
+ fix_windsize(ps_global);
+
+ /*
+ * Only reinitialize signals if we didn't receive an interesting
+ * one while in pico, since pico's return is part of processing that
+ * signal and it should continue to be ignored.
+ */
+ if(!(editor_result & COMP_GOTHUP))
+ init_signals(); /* Pico has it's own signal stuff */
+
+ /*
+ * We're going to save in DEADLETTER. Dump attachments first.
+ */
+ if(editor_result & COMP_CANCEL)
+ free_attachment_list(&pbf->attachments);
+
+ /* Turn strings back into structures */
+ strings2outgoing(&header, body, pbf->attachments, flowing_requested);
+
+ /* Make newsgroups NULL if it is "" (so won't show up in headers) */
+ if(outgoing->newsgroups){
+ sqzspaces(outgoing->newsgroups);
+ if(!outgoing->newsgroups[0])
+ fs_give((void **)&(outgoing->newsgroups));
+ }
+
+ /* Make subject NULL if it is "" (so won't show up in headers) */
+ if(outgoing->subject && !outgoing->subject[0])
+ fs_give((void **)&(outgoing->subject));
+
+ /* remove custom fields that are empty */
+ for(pf = header.local; pf && pf->name; pf = pf->next){
+ if(pf->type == FreeText && pf->textbuf){
+ if(pf->textbuf[0] == '\0'){
+ fs_give((void **)&pf->textbuf);
+ pf->text = NULL;
+ }
+ }
+ }
+
+ removing_trailing_white_space(fcc);
+
+ /*-------- Stamp it with a current date -------*/
+ if(outgoing->date) /* update old date */
+ fs_give((void **)&(outgoing->date));
+
+ if(F_ON(F_QUELL_TIMEZONE, ps_global))
+ mail_parameters(NULL, SET_DISABLE822TZTEXT, (void *) TRUE);
+
+ rfc822_date(tmp_20k_buf); /* format and copy new date */
+ if(F_ON(F_QUELL_TIMEZONE, ps_global))
+ mail_parameters(NULL, SET_DISABLE822TZTEXT, (void *) FALSE);
+
+ outgoing->date = (unsigned char *) cpystr(tmp_20k_buf);
+
+ /* Set return_path based on From which is going to be used */
+ if(outgoing->return_path)
+ mail_free_address(&outgoing->return_path);
+
+ outgoing->return_path = rfc822_cpy_adr(outgoing->from);
+
+ /*
+ * Don't ever believe the sender that is there.
+ * If From doesn't look quite right, generate our own sender.
+ */
+ if(outgoing->sender)
+ mail_free_address(&outgoing->sender);
+
+ /*
+ * If the LHS of the address doesn't match, or the RHS
+ * doesn't match one of localdomain or hostname,
+ * then add a sender line (really X-X-Sender).
+ *
+ * Don't add a personal_name since the user can change that.
+ */
+ if(F_OFF(F_DISABLE_SENDER, ps_global)
+ &&
+ (!outgoing->from
+ || !outgoing->from->mailbox
+ || strucmp(outgoing->from->mailbox, ps_global->VAR_USER_ID) != 0
+ || !outgoing->from->host
+ || !(strucmp(outgoing->from->host, ps_global->localdomain) == 0
+ || strucmp(outgoing->from->host, ps_global->hostname) == 0))){
+
+ outgoing->sender = mail_newaddr();
+ outgoing->sender->mailbox = cpystr(ps_global->VAR_USER_ID);
+ outgoing->sender->host = cpystr(ps_global->hostname);
+ }
+
+ if(ps_global->newthread){
+ if(outgoing->in_reply_to) fs_give((void **)&outgoing->in_reply_to);
+ if(outgoing->references) fs_give((void **)&outgoing->references);
+ }
+
+ /*----- Message is edited, now decide what to do with it ----*/
+ if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
+ /*=========== Postpone or Interrupted message ============*/
+ CONTEXT_S *fcc_cntxt = NULL;
+ char folder[MAXPATH+1];
+ int fcc_result = 0;
+ char label[50];
+
+ dprint((4, "pine_send:%s handling\n",
+ (editor_result & COMP_SUSPEND)
+ ? "SUSPEND"
+ : (editor_result & COMP_GOTHUP)
+ ? "HUP"
+ : (editor_result & COMP_CANCEL)
+ ? "CANCEL" : "HUH?"));
+ if((editor_result & COMP_CANCEL)
+ && (F_ON(F_QUELL_DEAD_LETTER, ps_global)
+ || ps_global->deadlets == 0)){
+ q_status_message(SM_ORDER, 0, 3, "Message cancelled");
+ break;
+ }
+
+ /*
+ * The idea here is to use the Fcc: writing facility
+ * to append to the special postponed message folder...
+ *
+ * NOTE: the strategy now is to write the message and
+ * all attachments as they exist at composition time.
+ * In other words, attachments are postponed by value
+ * and not reference. This may change later, but we'll
+ * need a local "message/external-body" type that
+ * outgoing2strings knows how to properly set up for
+ * the composer. Maybe later...
+ */
+
+ label[0] = '\0';
+
+ if(F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)
+ && (editor_result & COMP_SUSPEND)
+ && (check_addresses(&header) == CA_BAD)){
+ /*--- Addresses didn't check out---*/
+ q_status_message(SM_ORDER, 7, 7,
+ _("Not allowed to postpone message until addresses are qualified"));
+ continue;
+ }
+
+ /*
+ * Build the local message copy so.
+ *
+ * In the HUP case, we'll write the bezerk delimiter by
+ * hand and output the message directly into the folder.
+ * It's not only faster, we don't have to worry about
+ * c-client reentrance and less hands paw over the data so
+ * there's less chance of a problem.
+ *
+ * In the Postpone case, just create it if the user wants to
+ * and create a temporary storage object to write into. */
+ fake_hup:
+ lmc.all_written = lmc.text_only = lmc.text_written = 0;
+ if(editor_result & (COMP_GOTHUP | COMP_CANCEL)){
+ int newfile = 1;
+ time_t now = time((time_t *)0);
+
+#if defined(DOS) || defined(OS2)
+ /*
+ * we can't assume anything about root or home dirs, so
+ * just plunk it down in the same place as the pinerc
+ */
+ if(!getenv("HOME")){
+ char *lc = last_cmpnt(ps_global->pinerc);
+ folder[0] = '\0';
+ if(lc != NULL){
+ strncpy(folder,ps_global->pinerc,
+ MIN(lc-ps_global->pinerc,sizeof(folder)-1));
+ folder[MIN(lc-ps_global->pinerc,sizeof(folder)-1)]='\0';
+ }
+
+ strncat(folder, (editor_result & COMP_GOTHUP)
+ ? INTERRUPTED_MAIL : DEADLETTER,
+ sizeof(folder)-strlen(folder)-1);
+ }
+ else
+#endif
+ build_path(folder,
+ ps_global->VAR_OPER_DIR
+ ? ps_global->VAR_OPER_DIR : ps_global->home_dir,
+ (editor_result & COMP_GOTHUP)
+ ? INTERRUPTED_MAIL : DEADLETTER,
+ sizeof(folder));
+
+ if(editor_result & COMP_CANCEL){
+ char filename[MAXPATH+1], newfname[MAXPATH+1], nbuf[5];
+
+ if(strlen(folder) + 1 < sizeof(filename))
+ for(i = ps_global->deadlets - 1; i > 0 && i < 9; i--){
+ strncpy(filename, folder, sizeof(filename));
+ filename[sizeof(filename)-1] = '\0';
+ strncpy(newfname, filename, sizeof(newfname));
+ newfname[sizeof(newfname)-1] = '\0';
+
+ if(i > 1){
+ snprintf(nbuf, sizeof(nbuf), "%d", i);
+ nbuf[sizeof(nbuf)-1] = '\0';
+ strncat(filename, nbuf,
+ sizeof(filename)-strlen(filename)-1);
+ filename[sizeof(filename)-1] = '\0';
+ }
+
+ snprintf(nbuf, sizeof(nbuf), "%d", i+1);
+ nbuf[sizeof(nbuf)-1] = '\0';
+ strncat(newfname, nbuf,
+ sizeof(newfname)-strlen(newfname)-1);
+ newfname[sizeof(newfname)-1] = '\0';
+ (void) rename_file(filename, newfname);
+ }
+
+ our_unlink(folder);
+ }
+ else
+ newfile = can_access(folder, ACCESS_EXISTS);
+
+ if((lmc.so = so_get(FCC_SOURCE, NULL, WRITE_ACCESS)) != NULL){
+ if (outgoing->from){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%sFrom %s@%s %.24s\015\012",
+ newfile ? "" : "\015\012",
+ outgoing->from->mailbox,
+ outgoing->from->host, ctime(&now));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ if(!so_puts(lmc.so, tmp_20k_buf)){
+ if(editor_result & COMP_CANCEL)
+ q_status_message2(SM_ORDER | SM_DING, 3, 3,
+ "Can't write \"%s\": %s",
+ folder, error_description(errno));
+ else
+ dprint((1, "* * * CAN'T WRITE %s: %s\n",
+ folder ? folder : "?",
+ error_description(errno)));
+ }
+ }
+ }
+ }
+ else{ /* Must be COMP_SUSPEND */
+ if(!ps_global->VAR_POSTPONED_FOLDER
+ || !ps_global->VAR_POSTPONED_FOLDER[0]){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("No postponed file defined"));
+ continue;
+ }
+
+ /*
+ * Store the cursor position
+ *
+ * First find the header entry with the start_here
+ * bit set, if any. This means the editor is telling
+ * us to start on this header field next time.
+ */
+ start_here_name = NULL;
+ for(pf = header.local; pf && pf->name; pf = pf->next)
+ if(HE(pf) && HE(pf)->start_here){
+ start_here_name = pf->name;
+ break;
+ }
+
+ /* If there wasn't one, ":" means we start in the body. */
+ if(!start_here_name || !*start_here_name)
+ start_here_name = ":";
+
+ if(ps_global->VAR_FORM_FOLDER
+ && ps_global->VAR_FORM_FOLDER[0]
+ && postpone_prompt() == 'f'){
+ strncpy(folder, ps_global->VAR_FORM_FOLDER,
+ sizeof(folder)-1);
+ folder[sizeof(folder)-1] = '\0';
+ strncpy(label, "form letter", sizeof(label));
+ label[sizeof(label)-1] = '\0';
+ }
+ else{
+ strncpy(folder, ps_global->VAR_POSTPONED_FOLDER,
+ sizeof(folder)-1);
+ folder[sizeof(folder)-1] = '\0';
+ strncpy(label, "postponed message", sizeof(label));
+ label[sizeof(label)-1] = '\0';
+ }
+
+ lmc.so = open_fcc(folder,&fcc_cntxt, 1, NULL, NULL);
+ }
+
+ if(lmc.so){
+ size_t sz;
+ char *lmq = NULL;
+
+ /* copy fcc line to postponed or interrupted folder */
+ if(pf_fcc)
+ pf_fcc->localcopy = 1;
+
+ /* plug error into header for later display to user */
+ if((editor_result & ~0xff) && (lmq = last_message_queued()) != NULL){
+ pf_err->writehdr = 1;
+ pf_err->localcopy = 1;
+ pf_err->textbuf = lmq;
+ }
+
+ /*
+ * if reply, write (UID)folder header field so we can
+ * later flag the replied-to message \\ANSWERED
+ * DON'T save MSGNO's.
+ */
+ if(reply && reply->uid){
+ char uidbuf[MAILTMPLEN], *p;
+ long i;
+
+ for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){
+ if(i)
+ sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf));
+
+ sstrncpy(&p,ulong2string(reply->data.uid.msgs[i]),SIZEOF_20KBUF-(p-tmp_20k_buf));
+ }
+
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ pf_uid->writehdr = 1;
+ pf_uid->localcopy = 1;
+ snprintf(uidbuf, sizeof(uidbuf), "(%s%s%s)(%ld %lu %s)%s",
+ reply->prefix ? int2string(strlen(reply->prefix))
+ : (reply->forwarded) ? "": "0 ",
+ reply->prefix ? " " : "",
+ reply->prefix ? reply->prefix : "",
+ i, reply->data.uid.validity,
+ tmp_20k_buf, reply->mailbox);
+ uidbuf[sizeof(uidbuf)-1] = '\0';
+ pf_uid->textbuf = cpystr(uidbuf);
+
+ /*
+ * Logically, this ought to be part of pf_uid, but this
+ * was added later and so had to be in a separate header
+ * for backwards compatibility.
+ */
+ pf_mbox->writehdr = 1;
+ pf_mbox->localcopy = 1;
+ pf_mbox->textbuf = cpystr(reply->origmbox
+ ? reply->origmbox
+ : reply->mailbox);
+ }
+
+ /* Save cursor position */
+ if(start_here_name && *start_here_name){
+ char curposbuf[MAILTMPLEN];
+
+ pf_curpos->writehdr = 1;
+ pf_curpos->localcopy = 1;
+ snprintf(curposbuf, sizeof(curposbuf), "%s %ld", start_here_name,
+ pbf->edit_offset);
+ curposbuf[sizeof(curposbuf)-1] = '\0';
+ pf_curpos->textbuf = cpystr(curposbuf);
+ }
+
+ /*
+ * Work around c-client reply-to bug. C-client will
+ * return a reply_to in an envelope even if there is
+ * no reply-to header field. We want to note here whether
+ * the reply-to is real or not.
+ */
+ if(outgoing->reply_to || hdr_is_in_list("reply-to", custom)){
+ pf_ourrep->writehdr = 1;
+ pf_ourrep->localcopy = 1;
+ if(outgoing->reply_to)
+ pf_ourrep->textbuf = cpystr("Full");
+ else
+ pf_ourrep->textbuf = cpystr("Empty");
+ }
+
+ /* Save the role-specific smtp server */
+ if(role && role->smtp && role->smtp[0]){
+ char *q, *smtp = NULL;
+ char **lp;
+ size_t len = 0;
+
+ /*
+ * Turn the list of smtp servers into a space-
+ * delimited list in a single string.
+ */
+ for(lp = role->smtp; (q = *lp) != NULL; lp++)
+ len += (strlen(q) + 1);
+
+ if(len){
+ smtp = (char *) fs_get(len * sizeof(char));
+ smtp[0] = '\0';
+ for(lp = role->smtp; (q = *lp) != NULL; lp++){
+ if(lp != role->smtp)
+ strncat(smtp, " ", len-strlen(smtp)-1);
+
+ strncat(smtp, q, len-strlen(smtp)-1);
+ }
+
+ smtp[len-1] = '\0';
+ }
+
+ pf_smtp_server->writehdr = 1;
+ pf_smtp_server->localcopy = 1;
+ if(smtp)
+ pf_smtp_server->textbuf = smtp;
+ else
+ pf_smtp_server->textbuf = cpystr("");
+ }
+
+ /* Save the role-specific nntp server */
+ if(suggested_nntp_server ||
+ (role && role->nntp && role->nntp[0])){
+ char *q, *nntp = NULL;
+ char **lp;
+ size_t len = 0;
+
+ if(role && role->nntp && role->nntp[0]){
+ /*
+ * Turn the list of nntp servers into a space-
+ * delimited list in a single string.
+ */
+ for(lp = role->nntp; (q = *lp) != NULL; lp++)
+ len += (strlen(q) + 1);
+
+ if(len){
+ nntp = (char *) fs_get(len * sizeof(char));
+ nntp[0] = '\0';
+ for(lp = role->nntp; (q = *lp) != NULL; lp++){
+ if(lp != role->nntp)
+ strncat(nntp, " ", len-strlen(nntp)-1);
+
+ strncat(nntp, q, len-strlen(nntp)-1);
+ }
+
+ nntp[len-1] = '\0';
+ }
+ }
+ else
+ nntp = cpystr(suggested_nntp_server);
+
+ pf_nntp_server->writehdr = 1;
+ pf_nntp_server->localcopy = 1;
+ if(nntp)
+ pf_nntp_server->textbuf = nntp;
+ else
+ pf_nntp_server->textbuf = cpystr("");
+ }
+
+ /*
+ * Write the list of custom headers to the
+ * X-Our-Headers header so that we can recover the
+ * list in redraft.
+ */
+ sz = 0;
+ for(pf = header.custom; pf && pf->name; pf = pf->next)
+ sz += strlen(pf->name) + 1;
+
+ if(sz){
+ char *q;
+
+ pf_ourhdrs->writehdr = 1;
+ pf_ourhdrs->localcopy = 1;
+ pf_ourhdrs->textbuf = (char *)fs_get(sz);
+ memset(pf_ourhdrs->textbuf, 0, sz);
+ q = pf_ourhdrs->textbuf;
+ for(pf = header.custom; pf && pf->name; pf = pf->next){
+ if(pf != header.custom)
+ sstrncpy(&q, ",", sz-(q-pf_ourhdrs->textbuf));
+
+ sstrncpy(&q, pf->name, sz-(q-pf_ourhdrs->textbuf));
+ }
+
+ pf_ourhdrs->textbuf[sz-1] = '\0';;
+ }
+
+ /*
+ * We need to make sure any header values that got cleared
+ * get written to the postponed message (they won't if
+ * pf->text is NULL). Otherwise, we can't tell previously
+ * non-existent custom headers or default values from
+ * custom (or other) headers that got blanked in the
+ * composer...
+ */
+ for(pf = header.local; pf && pf->name; pf = pf->next)
+ if(pf->type == FreeText && HE(pf) && !*(HE(pf)->realaddr))
+ *(HE(pf)->realaddr) = cpystr("");
+
+ /*
+ * We're saving the message for use later. It may be that the
+ * characters in the message are not all convertible to the
+ * user's posting_charmap. We'll save it as UTF-8 instead
+ * and worry about that the next time they try to send it.
+ * Use a different save pointer just to be sure we don't
+ * mess up the other stuff. We should probably make the
+ * charset an argument.
+ *
+ * We also need to fix the charset of the body part
+ * the user is editing so that we can read it back
+ * successfully when we resume the composition.
+ */
+ ps_global->post_utf8 = 1;
+
+ {
+ PARAMETER *pm;
+ BODY *bp;
+ if((*body)->type == TYPEMULTIPART)
+ bp = &(*body)->nested.part->body;
+ else
+ bp = *body;
+
+ for(pm = bp->parameter;
+ pm && strucmp(pm->attribute, "charset") != 0;
+ pm = pm->next)
+ ;
+
+ if(pm){
+ if(pm->value)
+ fs_give((void **) &pm->value);
+
+ pm->value = cpystr("UTF-8");
+ }
+ }
+
+ if(pine_rfc822_output(&header,*body,NULL,NULL) >= 0L){
+ if(editor_result & (COMP_GOTHUP | COMP_CANCEL)){
+ char *err;
+ STORE_S *hup_so;
+ gf_io_t gc, pc;
+ int we_cancel = 0;
+
+ if(editor_result & COMP_CANCEL){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "Saving to \"%s\"", folder);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ we_cancel = busy_cue((char *)tmp_20k_buf, NULL, 1);
+ }
+
+ if((hup_so =
+ so_get(FileStar, folder, WRITE_ACCESS|OWNER_ONLY)) != NULL){
+ gf_set_so_readc(&gc, lmc.so);
+ gf_set_so_writec(&pc, hup_so);
+ so_seek(lmc.so, 0L, 0); /* read msg copy and */
+ so_seek(hup_so, 0L, 2); /* append to folder */
+ gf_filter_init();
+ gf_link_filter(gf_nvtnl_local, NULL);
+ if(!(fcc_result = !(err = gf_pipe(gc, pc))))
+ dprint((1, "*** PIPE FAILED: %s\n",
+ err ? err : "?"));
+
+ gf_clear_so_readc(lmc.so);
+ gf_clear_so_writec(hup_so);
+ so_give(&hup_so);
+ }
+ else
+ dprint((1, "*** CAN'T CREATE %s: %s\n",
+ folder ? folder : "?",
+ error_description(errno)));
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+ }
+ else
+ fcc_result = write_fcc(folder, fcc_cntxt,
+ lmc.so, NULL, label, NULL);
+ }
+
+ /* discontinue coerced UTF-8 posting */
+ ps_global->post_utf8 = 0;
+
+ so_give(&lmc.so);
+ }
+ else
+ dprint((1, "***CAN'T ALLOCATE temp store: %s ",
+ error_description(errno)));
+
+ if(editor_result & COMP_GOTHUP){
+ /*
+ * Special Hack #291: if any hi-byte bits are set in
+ * editor's result, we put them there.
+ */
+ if(editor_result & 0xff00)
+ exit(editor_result >> 8);
+
+ dprint((1, "Save composition on HUP %sED\n",
+ fcc_result ? "SUCCEED" : "FAIL"));
+ hup_signal(); /* Do what we normally do on SIGHUP */
+ }
+ else if((editor_result & COMP_SUSPEND) && fcc_result){
+ if(ps_global->VAR_FORM_FOLDER
+ && ps_global->VAR_FORM_FOLDER[0]
+ && !strcmp(folder, ps_global->VAR_FORM_FOLDER))
+ q_status_message(SM_ORDER, 0, 3,
+ _("Composition saved to Form Letter Folder. Select Compose to send."));
+ else
+ q_status_message(SM_ORDER, 0, 3,
+ _("Composition postponed. Select Compose to resume."));
+
+ break; /* postpone went OK, get out of here */
+ }
+ else if(editor_result & COMP_CANCEL){
+ char *lc = NULL;
+
+ if(fcc_result && folder)
+ lc = last_cmpnt(folder);
+
+ q_status_message3(SM_ORDER, 0, 3,
+ _("Message cancelled%s%s%s"),
+ (lc && *lc) ? " and copied to \"" : "",
+ (lc && *lc) ? lc : "",
+ (lc && *lc) ? "\" file" : "");
+ break;
+ }
+ else{
+ q_status_message(SM_ORDER, 0, 4,
+ _("Continuing composition. Message not postponed or sent"));
+ body_start = 1;
+ continue; /* postpone failed, jump back in to composer */
+ }
+ }
+ else{
+ /*------ Must be sending mail or posting ! -----*/
+ int result, valid_addr, continue_with_only_fcc = 0;
+ CONTEXT_S *fcc_cntxt = NULL;
+
+ result = 0;
+ dprint((4, "=== sending: "));
+
+ /* --- If posting, confirm with user ----*/
+ if(outgoing->newsgroups && *outgoing->newsgroups
+ && F_OFF(F_QUELL_EXTRA_POST_PROMPT, ps_global)
+ && want_to(POST_PMT, 'n', 'n', NO_HELP, WT_NORM) == 'n'){
+ q_status_message(SM_ORDER, 0, 3, _("Message not posted"));
+ dprint((4, "no post, continuing\n"));
+ continue;
+ }
+
+ if(!(outgoing->to || outgoing->cc || outgoing->bcc || lcc_addr
+ || outgoing->newsgroups)){
+ if(fcc && fcc[0]){
+ if(F_OFF(F_AUTO_FCC_ONLY, ps_global) &&
+ want_to(_("No recipients, really copy only to Fcc "),
+ 'n', 'n', h_send_fcc_only, WT_NORM) != 'y')
+ continue;
+
+ continue_with_only_fcc++;
+ }
+ else{
+ q_status_message(SM_ORDER, 3, 4,
+ _("No recipients specified!"));
+ dprint((4, "no recip, continuing\n"));
+ continue;
+ }
+ }
+
+ if((valid_addr = check_addresses(&header)) == CA_BAD){
+ /*--- Addresses didn't check out---*/
+ dprint((4, "addrs failed, continuing\n"));
+ continue;
+ }
+
+ if(F_ON(F_WARN_ABOUT_NO_TO_OR_CC, ps_global)
+ && !continue_with_only_fcc
+ && !(outgoing->to || outgoing->cc || lcc_addr
+ || outgoing->newsgroups)
+ && (want_to(_("No To, Cc, or Newsgroup specified, send anyway "),
+ 'n', 'n', h_send_check_to_cc, WT_NORM) != 'y')){
+ dprint((4, "No To or CC or Newsgroup, continuing\n"));
+ if(local_redraft_pos && local_redraft_pos != redraft_pos)
+ free_redraft_pos(&local_redraft_pos);
+
+ local_redraft_pos
+ = (REDRAFT_POS_S *) fs_get(sizeof(*local_redraft_pos));
+ memset((void *) local_redraft_pos,0,sizeof(*local_redraft_pos));
+ local_redraft_pos->hdrname = cpystr(TONAME);
+ continue;
+ }
+
+ if(F_ON(F_WARN_ABOUT_NO_SUBJECT, ps_global)
+ && check_for_subject(&header) == CF_MISSING){
+ dprint((4, "No subject, continuing\n"));
+ if(local_redraft_pos && local_redraft_pos != redraft_pos)
+ free_redraft_pos(&local_redraft_pos);
+
+ local_redraft_pos
+ = (REDRAFT_POS_S *) fs_get(sizeof(*local_redraft_pos));
+ memset((void *) local_redraft_pos,0,sizeof(*local_redraft_pos));
+ local_redraft_pos->hdrname = cpystr(SUBJNAME);
+ continue;
+ }
+
+ if(F_ON(F_WARN_ABOUT_NO_FCC, ps_global)
+ && check_for_fcc(fcc) == CF_MISSING){
+ dprint((4, "No fcc, continuing\n"));
+ if(local_redraft_pos && local_redraft_pos != redraft_pos)
+ free_redraft_pos(&local_redraft_pos);
+
+ local_redraft_pos
+ = (REDRAFT_POS_S *) fs_get(sizeof(*local_redraft_pos));
+ memset((void *) local_redraft_pos,0,sizeof(*local_redraft_pos));
+ local_redraft_pos->hdrname = cpystr("Fcc");
+ continue;
+ }
+
+ set_last_fcc(fcc);
+
+ /*---- Check out fcc -----*/
+ if(fcc && *fcc){
+ /*
+ * If special name "inbox" then replace it with the
+ * real inbox path.
+ */
+ if(ps_global->VAR_INBOX_PATH
+ && strucmp(fcc, ps_global->inbox_name) == 0){
+ char *replace_fcc;
+
+ replace_fcc = cpystr(ps_global->VAR_INBOX_PATH);
+ fs_give((void **)&fcc);
+ fcc = replace_fcc;
+ }
+
+ lmc.all_written = lmc.text_written = 0;
+ /* lmc.text_only set on command line */
+ if(!(lmc.so = open_fcc(fcc, &fcc_cntxt, 0, NULL, NULL))){
+ /* ---- Open or allocation of fcc failed ----- */
+ dprint((4,"can't open/allocate fcc, cont'g\n"));
+
+ /*
+ * Find field entry associated with fcc, and start
+ * composer on it...
+ */
+ for(pf = header.local; pf && pf->name; pf = pf->next)
+ if(pf->type == Fcc && HE(pf))
+ HE(pf)->start_here = 1;
+
+ continue;
+ }
+ else
+ so_truncate(lmc.so, fcc_size_guess(*body) + 2048);
+ }
+ else
+ lmc.so = NULL;
+
+ /*---- Take care of any requested prefiltering ----*/
+ if(sending_filter_requested
+ && !filter_message_text(sending_filter_requested, outgoing,
+ *body, &orig_so, &header)){
+ q_status_message1(SM_ORDER, 3, 3,
+ _("Problem filtering! Nothing sent%s."),
+ fcc ? " or saved to fcc" : "");
+ continue;
+ }
+
+ /*------ Actually post -------*/
+ if(outgoing->newsgroups){
+ char **alt_nntp = NULL, *alt_nntp_p[2];
+ if(((role && role->nntp)
+ || suggested_nntp_server)){
+ if(ps_global->FIX_NNTP_SERVER
+ && ps_global->FIX_NNTP_SERVER[0])
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+ "Using nntp-server that is administratively fixed");
+ else if(role && role->nntp)
+ alt_nntp = role->nntp;
+ else{
+ alt_nntp_p[0] = suggested_nntp_server;
+ alt_nntp_p[1] = NULL;
+ alt_nntp = alt_nntp_p;
+ }
+ }
+ if(news_poster(&header, *body, alt_nntp, pipe_callback) < 0){
+ dprint((1, "Post failed, continuing\n"));
+ if(outgoing->message_id)
+ fs_give((void **) &outgoing->message_id);
+
+ outgoing->message_id = generate_message_id();
+
+ continue;
+ }
+ else
+ result |= P_NEWS_WIN;
+ }
+
+ /*
+ * BUG: IF we've posted the message *and* an fcc was specified
+ * then we've already got a neatly formatted message in the
+ * lmc.so. It'd be nice not to have to re-encode everything
+ * to insert it into the smtp slot...
+ */
+
+ /*
+ * Turn on "undisclosed recipients" header if no To or cc.
+ */
+ if(!(outgoing->to || outgoing->cc)
+ && (outgoing->bcc || lcc_addr) && pf_nobody && pf_nobody->addr){
+ pf_nobody->writehdr = 1;
+ pf_nobody->localcopy = 1;
+ }
+
+ if(priority_requested){
+ (void) set_priority_header(&header, priority_requested);
+ fs_give((void **) &priority_requested);
+ }
+
+#if defined(BACKGROUND_POST) && defined(SIGCHLD)
+ /*
+ * If requested, launch backgroud posting...
+ */
+ if(background_requested && !(call_mailer_flags & CM_VERBOSE)){
+ ps_global->post = (POST_S *)fs_get(sizeof(POST_S));
+ memset(ps_global->post, 0, sizeof(POST_S));
+ if(fcc)
+ ps_global->post->fcc = cpystr(fcc);
+
+ if((ps_global->post->pid = fork()) == 0){
+ /*
+ * Put us in new process group...
+ */
+ setpgrp(0, ps_global->post->pid);
+
+ /* BUG: should fix argv[0] to indicate what we're up to */
+
+ /*
+ * If there are any live streams, pretend we never
+ * knew them. Problem is two processes writing
+ * same server process.
+ * This is not clean but we're just going to exit
+ * right away anyway. We just want to be sure to leave
+ * the stuff that the parent is going to use alone.
+ * The next three lines will disable the re-use of the
+ * existing streams and cause us to open a new one if
+ * needed.
+ */
+ ps_global->mail_stream = NULL;
+ ps_global->s_pool.streams = NULL;
+ ps_global->s_pool.nstream = 0;
+
+ /* quell any display output */
+ ps_global->in_init_seq = 1;
+
+ /*------- Actually mail the message ------*/
+ if(valid_addr == CA_OK
+ && (outgoing->to || outgoing->cc
+ || outgoing->bcc || lcc_addr)){
+ char **alt_smtp = NULL;
+
+ if(role && role->smtp){
+ if(ps_global->FIX_SMTP_SERVER
+ && ps_global->FIX_SMTP_SERVER[0])
+ q_status_message(SM_ORDER | SM_DING, 5, 5, "Use of a role-defined smtp-server is administratively prohibited");
+ else
+ alt_smtp = role->smtp;
+ }
+
+ result |= (call_mailer(&header, *body, alt_smtp,
+ call_mailer_flags,
+ call_mailer_file_result,
+ pipe_callback) > 0)
+ ? P_MAIL_WIN : P_MAIL_LOSE;
+
+ if(result & P_MAIL_LOSE)
+ mark_address_failure_for_pico(&header);
+ }
+
+ /*----- Was there an fcc involved? -----*/
+ if(lmc.so){
+ /*------ Write it if at least something worked ------*/
+ if((result & (P_MAIL_WIN | P_NEWS_WIN))
+ || (!(result & (P_MAIL_BITS | P_NEWS_BITS))
+ && pine_rfc822_output(&header, *body,
+ NULL, NULL))){
+ char label[50];
+
+ strncpy(label, "Fcc", sizeof(label));
+ label[sizeof(label)-1] = '\0';
+ if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
+ snprintf(label + 3, sizeof(label)-3, " to %s", fcc);
+ label[sizeof(label)-1] = '\0';
+ }
+
+ /*-- Now actually copy to fcc folder and close --*/
+ result |= (write_fcc(fcc, fcc_cntxt, lmc.so,
+ NULL, label,
+ F_ON(F_MARK_FCC_SEEN, ps_global)
+ ? "\\SEEN" : NULL))
+ ? P_FCC_WIN : P_FCC_LOSE;
+ }
+ else if(!(result & (P_MAIL_BITS | P_NEWS_BITS))){
+ q_status_message(SM_ORDER, 3, 5,
+ _("Fcc Failed!. No message saved."));
+ dprint((1,
+ "explicit fcc write failed!\n"));
+ result |= P_FCC_LOSE;
+ }
+
+ so_give(&lmc.so);
+ }
+
+ if(result & (P_MAIL_LOSE | P_NEWS_LOSE | P_FCC_LOSE)){
+ /*
+ * Encode child's result in hi-byte of
+ * editor's result
+ */
+ editor_result = ((result << 8) | COMP_GOTHUP);
+ goto fake_hup;
+ }
+
+ exit(result);
+ }
+
+ if(ps_global->post->pid > 0){
+ q_status_message(SM_ORDER, 3, 3,
+ _("Message handed off for posting"));
+ break; /* up to our child now */
+ }
+ else{
+ q_status_message1(SM_ORDER | SM_DING, 3, 3,
+ "Can't fork for send: %s",
+ error_description(errno));
+ if(ps_global->post->fcc)
+ fs_give((void **) &ps_global->post->fcc);
+
+ fs_give((void **) &ps_global->post);
+ }
+
+ if(lmc.so) /* throw away unused store obj */
+ so_give(&lmc.so);
+
+ if(outgoing->message_id)
+ fs_give((void **) &outgoing->message_id);
+
+ outgoing->message_id = generate_message_id();
+
+ continue; /* if we got here, there was a prob */
+ }
+#endif /* BACKGROUND_POST */
+
+ /*------- Actually mail the message ------*/
+ if(valid_addr == CA_OK
+ && (outgoing->to || outgoing->cc || outgoing->bcc || lcc_addr)){
+ char **alt_smtp = NULL;
+
+ if(role && role->smtp){
+ if(ps_global->FIX_SMTP_SERVER
+ && ps_global->FIX_SMTP_SERVER[0])
+ q_status_message(SM_ORDER | SM_DING, 5, 5, "Use of a role-defined smtp-server is administratively prohibited");
+ else
+ alt_smtp = role->smtp;
+ }
+
+ result |= (call_mailer(&header, *body, alt_smtp,
+ call_mailer_flags,
+ call_mailer_file_result,
+ pipe_callback) > 0)
+ ? P_MAIL_WIN : P_MAIL_LOSE;
+
+ if(result & P_MAIL_LOSE)
+ mark_address_failure_for_pico(&header);
+ }
+
+ /*----- Was there an fcc involved? -----*/
+ if(lmc.so){
+ /*------ Write it if at least something worked ------*/
+ if((result & (P_MAIL_WIN | P_NEWS_WIN))
+ || (!(result & (P_MAIL_BITS | P_NEWS_BITS))
+ && pine_rfc822_output(&header, *body, NULL, NULL))){
+ char label[50];
+
+ strncpy(label, "Fcc", sizeof(label));
+ label[sizeof(label)-1] = '\0';
+ if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){
+ snprintf(label + 3, sizeof(label)-3, " to %s", fcc);
+ label[sizeof(label)-1] = '\0';
+ }
+
+ /*-- Now actually copy to fcc folder and close --*/
+ result |= (write_fcc(fcc, fcc_cntxt, lmc.so, NULL, label,
+ F_ON(F_MARK_FCC_SEEN, ps_global)
+ ? "\\SEEN" : NULL))
+ ? P_FCC_WIN : P_FCC_LOSE;
+ }
+ else if(!(result & (P_MAIL_BITS | P_NEWS_BITS))){
+ q_status_message(SM_ORDER,3,5,
+ _("Fcc Failed!. No message saved."));
+ dprint((1, "explicit fcc write failed!\n"));
+ result |= P_FCC_LOSE;
+ }
+
+ so_give(&lmc.so);
+ }
+
+ /*----- Mail Post FAILED, back to composer -----*/
+ if(result & (P_MAIL_LOSE | P_FCC_LOSE)){
+ dprint((1, "Send failed, continuing\n"));
+
+ if(result & P_FCC_LOSE){
+ /*
+ * Find field entry associated with fcc, and start
+ * composer on it...
+ */
+ for(pf = header.local; pf && pf->name; pf = pf->next)
+ if(pf->type == Fcc && HE(pf))
+ HE(pf)->start_here = 1;
+
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ pine_send_status(result, fcc,
+ tmp_20k_buf, SIZEOF_20KBUF, NULL));
+ }
+
+ if(outgoing->message_id)
+ fs_give((void **) &outgoing->message_id);
+
+ outgoing->message_id = generate_message_id();
+
+ continue;
+ }
+
+ /*
+ * If message sent *completely* successfully, there's a
+ * reply struct AND we're allowed to write back state, do it.
+ * But also protect against shifted message numbers due
+ * to new mail arrival. Since the number passed is based
+ * on the real imap msg no, AND we're sure no expunge has
+ * been done, just fix up the sorted number...
+ */
+ update_answered_flags(reply);
+
+ /*----- Signed, sealed, delivered! ------*/
+ q_status_message(SM_ORDER, 0, 3,
+ pine_send_status(result, fcc, tmp_20k_buf, SIZEOF_20KBUF, NULL));
+
+ break; /* All's well, pop out of here */
+ }
+ }
+
+ if(orig_so)
+ so_give(&orig_so);
+
+ if(fcc)
+ fs_give((void **)&fcc);
+
+ free_body_particulars(bp);
+
+ free_attachment_list(&pbf->attachments);
+
+ standard_picobuf_teardown(pbf);
+
+ for(i=0; i < fixed_cnt; i++){
+ if(pfields[i].textbuf)
+ fs_give((void **)&pfields[i].textbuf);
+
+ fs_give((void **)&pfields[i].name);
+ }
+
+ if(lcc_addr)
+ mail_free_address(&lcc_addr);
+
+ if(nobody_addr)
+ mail_free_address(&nobody_addr);
+
+ free_prompts(header.custom);
+ free_customs(header.custom);
+ fs_give((void **)&pfields);
+ free_headents(&headents);
+ fs_give((void **)&sending_order);
+ if(suggested_nntp_server)
+ fs_give((void **)&suggested_nntp_server);
+ if(title)
+ fs_give((void **)&title);
+
+ if(local_redraft_pos && local_redraft_pos != redraft_pos)
+ free_redraft_pos(&local_redraft_pos);
+
+ pbf = save_previous_pbuf;
+ g_rolenick = NULL;
+
+ dprint((4, "=== send returning ===\n"));
+}
+
+
+/*
+ * Check for subject in outgoing message.
+ *
+ * Asks user whether to proceed with no subject.
+ */
+int
+check_for_subject(METAENV *header)
+{
+ PINEFIELD *pf;
+ int rv = CF_OK;
+
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Subject){
+ if(pf->text && *pf->text && **pf->text)
+ rv = CF_OK;
+ else{
+ if(want_to("No Subject, send anyway ",
+ 'n', 'n', h_send_check_subj, WT_NORM) == 'y')
+ rv = CF_OK;
+ else
+ rv = CF_MISSING;
+ }
+
+ break;
+ }
+
+
+ return(rv);
+}
+
+
+/*
+ * Check for fcc in outgoing message.
+ *
+ * Asks user whether to proceed with no fcc.
+ */
+int
+check_for_fcc(char *fcc)
+{
+ int rv = CF_OK;
+
+ if(fcc && *fcc)
+ rv = CF_OK;
+ else{
+ if(want_to("No Fcc, send anyway ", 'n', 'n', h_send_check_fcc, WT_NORM) == 'y')
+ rv = CF_OK;
+ else
+ rv = CF_MISSING;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * Confirm that the user wants to send to MAILER-DAEMON
+ */
+int
+confirm_daemon_send(void)
+{
+ return(want_to("Really send this message to the MAILER-DAEMON",
+ 'n', 'n', NO_HELP, WT_NORM) == 'y');
+}
+
+
+void
+free_prompts(PINEFIELD *head)
+{
+ PINEFIELD *pf;
+
+ for(pf = head; pf && pf->name; pf = pf->next){
+ if(HE(pf) && HE(pf)->prompt)
+ fs_give((void **)& HE(pf)->prompt);
+ }
+}
+
+
+int
+postpone_prompt(void)
+{
+ static ESCKEY_S pstpn_form_opt[] = { {'p', 'p', "P", N_("Postponed Folder")},
+ {'f', 'f', "F", N_("Form Letter Folder")},
+ {-1, 0, NULL, NULL} };
+
+ return(radio_buttons(PSTPN_FORM_PMT, -FOOTER_ROWS(ps_global),
+ pstpn_form_opt, 'p', 0, NO_HELP, RB_FLUSH_IN));
+}
+
+
+/*
+ * call__mailer_file_result - some results from call_mailer might be in a file.
+ * dislplay that file.
+ */
+void
+call_mailer_file_result(char *filename, int style)
+{
+ if(filename){
+ if(style & CM_BR_VERBOSE){
+ display_output_file(filename, "Verbose SMTP Interaction", NULL, DOF_BRIEF);
+ }
+ else{
+ display_output_file(filename, "POSTING ERRORS", "Posting Error", DOF_EMPTY);
+ }
+ }
+}
+
+void
+mark_address_failure_for_pico(METAENV *header)
+{
+ PINEFIELD *pf;
+ ADDRESS *a;
+ int error_count = 0;
+ struct headerentry *last_he = NULL;
+
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr)
+ for(a = *pf->addr; a != NULL; a = a->next)
+ if(a->error != NULL
+ && error_count++ < MAX_ADDR_ERROR
+ && (HE(pf))){
+ if(last_he) /* start last reported err */
+ last_he->start_here = 0;
+
+ (last_he = HE(pf))->start_here = 1;
+ }
+}
+
+
+
+/*
+ * This is specialized routine. It assumes that the only things that we
+ * care about restoring are the body type, subtype, encoding and the
+ * state of the charset parameter. It also assumes that if the charset
+ * parameter exists when we save it, it won't be removed later.
+ */
+BODY_PARTICULARS_S *
+save_body_particulars(struct mail_bodystruct *body)
+{
+ BODY_PARTICULARS_S *bp;
+ PARAMETER *pm;
+
+ bp = (BODY_PARTICULARS_S *)fs_get(sizeof(BODY_PARTICULARS_S));
+
+ bp->type = body->type;
+ bp->encoding = body->encoding;
+ bp->subtype = body->subtype ? cpystr(body->subtype) : NULL;
+ bp->parameter = body->parameter;
+ for(pm = bp->parameter;
+ pm && strucmp(pm->attribute, "charset") != 0;
+ pm = pm->next)
+ ;/* searching for possible charset parameter */
+
+ if(pm){ /* found one */
+ bp->had_csp = 1; /* saved body had charset parameter */
+ bp->charset = pm->value ? cpystr(pm->value) : NULL;
+ }
+ else{
+ bp->had_csp = 0;
+ bp->charset = NULL;
+ }
+
+ return(bp);
+}
+
+
+void
+reset_body_particulars(BODY_PARTICULARS_S *bp, struct mail_bodystruct *body)
+{
+ body->type = bp->type;
+ body->encoding = bp->encoding;
+ if(body->subtype)
+ fs_give((void **)&body->subtype);
+
+ body->subtype = bp->subtype ? cpystr(bp->subtype) : NULL;
+
+ if(bp->parameter){
+ PARAMETER *pm, *pm_prev = NULL;
+
+ for(pm = body->parameter;
+ pm && strucmp(pm->attribute, "charset") != 0;
+ pm = pm->next)
+ pm_prev = pm;
+
+ if(pm){ /* body has charset parameter */
+ if(bp->had_csp){ /* reset to what it used to be */
+ if(pm->value)
+ fs_give((void **)&pm->value);
+
+ pm->value = bp->charset ? cpystr(bp->charset) : NULL;
+ }
+ else{ /* remove charset parameter */
+ if(pm_prev)
+ pm_prev->next = pm->next;
+ else
+ body->parameter = pm->next;
+
+ mail_free_body_parameter(&pm);
+ }
+ }
+ else{
+ if(bp->had_csp){
+ /*
+ * This can't happen because grope never removes
+ * the charset parameter.
+ */
+ q_status_message(SM_ORDER | SM_DING, 5, 5,
+"Programmer error: saved charset but no current charset param in pine_send");
+ }
+ /*
+ else{
+ ok, still no parameter
+ }
+ */
+ }
+ }
+ else{
+ if(body->parameter)
+ mail_free_body_parameter(&body->parameter);
+
+ body->parameter = NULL;
+ }
+}
+
+
+void
+free_body_particulars(BODY_PARTICULARS_S *bp)
+{
+ if(bp){
+ if(bp->subtype)
+ fs_give((void **)&bp->subtype);
+
+ if(bp->charset)
+ fs_give((void **)&bp->charset);
+
+ fs_give((void **)&bp);
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Build a status message suitable for framing
+
+Returns: pointer to resulting buffer
+ ---*/
+char *
+pine_send_status(int result, char *fcc_name, char *buf, size_t buflen, int *goodorbad)
+{
+ int avail = ps_global->ttyo->screen_cols - 2;
+ int fixedneed, need, lenfcc;
+ char *part1, *part2, *part3, *part4, *part5;
+ char fbuf[MAILTMPLEN+1];
+
+ part1 = (result & P_NEWS_WIN)
+ ? "posted"
+ : (result & P_NEWS_LOSE)
+ ? "NOT POSTED"
+ : "";
+ part2 = ((result & P_NEWS_BITS) && (result & P_MAIL_BITS)
+ && (result & P_FCC_BITS))
+ ? ", "
+ : ((result & P_NEWS_BITS) && (result & (P_MAIL_BITS | P_FCC_BITS)))
+ ? " and "
+ : "";
+ part3 = (result & P_MAIL_WIN)
+ ? "sent"
+ : (result & P_MAIL_LOSE)
+ ? "NOT SENT"
+ : "";
+ part4 = ((result & P_MAIL_BITS) && (result & P_FCC_BITS))
+ ? " and "
+ : "";
+ part5 = ((result & P_FCC_WIN) && !(result & (P_MAIL_WIN | P_NEWS_WIN)))
+ ? "ONLY copied to "
+ : (result & P_FCC_WIN)
+ ? "copied to "
+ : (result & P_FCC_LOSE)
+ ? "NOT copied to "
+ : "";
+ lenfcc = MIN(sizeof(fbuf)-1, (result & P_FCC_BITS) ? strlen(fcc_name) : 0);
+
+ fixedneed = 9 + strlen(part1) + strlen(part2) + strlen(part3) +
+ strlen(part4) + strlen(part5);
+ need = fixedneed + ((result & P_FCC_BITS) ? 2 : 0) + lenfcc;
+
+ if(need > avail && fixedneed + 3 >= avail){
+ /* dots on end of fixed, no fcc */
+ snprintf(fbuf, sizeof(fbuf), "Message %s%s%s%s%s ",
+ part1, part2, part3, part4, part5);
+ short_str(fbuf, buf, buflen, avail, EndDots);
+ }
+ else if(need > avail){
+ /* include whole fixed part, quotes and dots at end of fcc name */
+ if(lenfcc > 0)
+ lenfcc = MAX(1, lenfcc-(need-avail));
+
+ snprintf(buf, buflen, "Message %s%s%s%s%s%s%s%s.",
+ part1, part2, part3, part4, part5,
+ (result & P_FCC_BITS) ? "\"" : "",
+ short_str((result & P_FCC_BITS) ? fcc_name : "",
+ fbuf, sizeof(fbuf), lenfcc, FrontDots),
+ (result & P_FCC_BITS) ? "\"" : "");
+ }
+ else{
+ /* whole thing */
+ snprintf(buf, buflen, "Message %s%s%s%s%s%s%s%s.",
+ part1, part2, part3, part4, part5,
+ (result & P_FCC_BITS) ? "\"" : "",
+ (result & P_FCC_BITS) ? fcc_name : "",
+ (result & P_FCC_BITS) ? "\"" : "");
+ }
+
+ if(goodorbad)
+ *goodorbad = (result & (P_MAIL_LOSE | P_NEWS_LOSE | P_FCC_LOSE)) == 0;
+
+ return(buf);
+}
+
+/* Callback from Pico to set the conditions for Alpine to start a new thread
+ */
+
+void
+new_thread_on_blank_subject(void)
+{
+ ps_global->newthread = F_ON(F_NEW_THREAD_ON_BLANK_SUBJECT, ps_global);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Call back for pico to insert the specified message's text
+
+Args: n -- message number to format
+ f -- function to use to output the formatted message
+
+
+Returns: returns msg number formatted on success, zero on error.
+----*/
+long
+message_format_for_pico(long int n, int (*f) (int))
+{
+ ENVELOPE *e;
+ BODY *b;
+ char *old_quote = NULL;
+ long rv = n;
+
+ if(!(n > 0L && n <= mn_get_total(ps_global->msgmap)
+ && (e = pine_mail_fetchstructure(ps_global->mail_stream,
+ mn_m2raw(ps_global->msgmap, n), &b)))){
+ q_status_message(SM_ORDER|SM_DING,3,3,"Error inserting Message");
+ flush_status_messages(0);
+ return(0L);
+ }
+
+ /* temporarily assign a new quote string */
+ old_quote = pbf->quote_str;
+ pbf->quote_str = reply_quote_str(e);
+
+ /* build separator line */
+ reply_delimiter(e, NULL, f);
+
+ /* actually write message text */
+ if(!format_message(mn_m2raw(ps_global->msgmap, n), e, b, NULL,
+ FM_NEW_MESS | FM_DISPLAY | FM_NOCOLOR | FM_NOINDENT, f)){
+ q_status_message(SM_ORDER|SM_DING,3,3,"Error inserting Message");
+ flush_status_messages(0);
+ rv = 0L;
+ }
+
+ fs_give((void **)&pbf->quote_str);
+ pbf->quote_str = old_quote;
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Call back for pico to prompt the user for exit confirmation
+
+Args: dflt -- default answer for confirmation prompt
+
+Returns: either NULL if the user accepts exit, or string containing
+ reason why the user declined.
+----*/
+int
+send_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
+ char **result)
+{
+ int i, rv, c, verbose_label = 0, bg_label = 0, old_suspend;
+ int dsn_label = 0, fcc_label = 0, lparen;
+ int flowing_label = 0, double_rad;
+ char *rstr = NULL, *p, *lc, *optp;
+ char dsn_string[30];
+ void (*redraw)(void) = ps_global->redrawer;
+ ESCKEY_S opts[18];
+ struct filters {
+ char *filter;
+ int index;
+ struct filters *prev, *next;
+ } *filters = NULL, *fp;
+
+ sending_filter_requested = NULL;
+ call_mailer_flags = 0;
+ background_requested = 0;
+ flowing_requested = allow_flowed ? 1 : 0;
+ lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0;
+ if(priority_requested)
+ fs_give((void **) &priority_requested);
+
+ if(background_posting(FALSE)){
+ if(result)
+ *result = "Can't send while background posting. Use postpone.";
+
+ return(1);
+ }
+
+ if(F_ON(F_SEND_WO_CONFIRM, ps_global)){
+ if(result)
+ *result = NULL;
+
+ return(0);
+ }
+
+ ps_global->redrawer = redraw_pico;
+
+ if((old_suspend = F_ON(F_CAN_SUSPEND, ps_global)) != 0)
+ (void) F_SET(F_CAN_SUSPEND, ps_global, 0);
+
+ /*
+ * Build list of available filters...
+ */
+ for(i=0; ps_global->VAR_SEND_FILTER && ps_global->VAR_SEND_FILTER[i]; i++){
+ for(p = ps_global->VAR_SEND_FILTER[i];
+ *p && !isspace((unsigned char)*p); p++)
+ ;
+
+ c = *p;
+ *p = '\0';
+ if(!(is_absolute_path(ps_global->VAR_SEND_FILTER[i])
+ && can_access(ps_global->VAR_SEND_FILTER[i],EXECUTE_ACCESS) ==0)){
+ *p = c;
+ continue;
+ }
+
+ fp = (struct filters *)fs_get(sizeof(struct filters));
+ fp->index = i;
+ if((lc = last_cmpnt(ps_global->VAR_SEND_FILTER[i])) != NULL){
+ fp->filter = cpystr(lc);
+ }
+ else if((p - ps_global->VAR_SEND_FILTER[i]) > 20){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "...%s", p - 17);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ fp->filter = cpystr(tmp_20k_buf);
+ }
+ else
+ fp->filter = cpystr(ps_global->VAR_SEND_FILTER[i]);
+
+ *p = c;
+
+ if(filters){
+ fp->next = filters;
+ fp->prev = filters->prev;
+ fp->prev->next = filters->prev = fp;
+ }
+ else{
+ filters = (struct filters *)fs_get(sizeof(struct filters));
+ filters->index = -1;
+ filters->filter = NULL;
+ filters->next = filters->prev = fp;
+ fp->next = fp->prev = filters;
+ }
+ }
+
+ i = 0;
+ opts[i].ch = 'y';
+ opts[i].rval = 'y';
+ opts[i].name = "Y";
+ opts[i++].label = N_("Yes");
+
+ opts[i].ch = 'n';
+ opts[i].rval = 'n';
+ opts[i].name = "N";
+ opts[i++].label = N_("No");
+
+ if(filters){
+ /* set global_filter_pointer to desired filter or NULL if none */
+ /* prepare two keymenu slots for selecting filter */
+ opts[i].ch = ctrl('P');
+ opts[i].rval = 10;
+ opts[i].name = "^P";
+ opts[i++].label = N_("Prev Filter");
+
+ opts[i].ch = ctrl('N');
+ opts[i].rval = 11;
+ opts[i].name = "^N";
+ opts[i++].label = N_("Next Filter");
+
+ if(F_ON(F_FIRST_SEND_FILTER_DFLT, ps_global))
+ filters = filters->next;
+ }
+
+ if(F_ON(F_VERBOSE_POST, ps_global)){
+ /* setup keymenu slot to toggle verbose mode */
+ opts[i].ch = ctrl('W');
+ opts[i].rval = 12;
+ opts[i].name = "^W";
+ verbose_label = i++;
+ }
+
+ if(allow_flowed){
+ /* setup keymenu slot to toggle flowed mode */
+ opts[i].ch = ctrl('V');
+ opts[i].rval = 22;
+ opts[i].name = "^V";
+ flowing_label = i++;
+ flowing_requested = 1;
+ }
+
+ if(F_ON(F_NO_FCC_ATTACH, ps_global)){
+ /* setup keymenu slot to toggle attacment on fcc */
+ opts[i].ch = ctrl('F');
+ opts[i].rval = 21;
+ opts[i].name = "^F";
+ fcc_label = i++;
+ }
+
+#if defined(BACKGROUND_POST) && defined(SIGCHLD)
+ if(F_ON(F_BACKGROUND_POST, ps_global)){
+ opts[i].ch = ctrl('R');
+ opts[i].rval = 15;
+ opts[i].name = "^R";
+ bg_label = i++;
+ }
+#endif
+
+ opts[i].ch = 'p';
+ opts[i].rval = 'p';
+ opts[i].name = "P";
+ opts[i++].label = N_("Priority");
+
+#ifdef SMIME
+ if(F_OFF(F_DONT_DO_SMIME, ps_global)){
+ opts[i].ch = 'e';
+ opts[i].rval = 'e';
+ opts[i].name = "E";
+ opts[i++].label = "Encrypt";
+
+ opts[i].ch = 'g';
+ opts[i].rval = 'g';
+ opts[i].name = "G";
+ opts[i++].label = "Sign";
+
+ if(ps_global->smime){
+ ps_global->smime->do_encrypt = F_ON(F_ENCRYPT_DEFAULT_ON, ps_global);
+ ps_global->smime->do_sign = F_ON(F_SIGN_DEFAULT_ON, ps_global);
+ }
+ }
+#endif
+
+ double_rad = i;
+
+ if(F_ON(F_DSN, ps_global)){
+ /* setup keymenu slots to toggle dsn bits */
+ opts[i].ch = 'd';
+ opts[i].rval = 'd';
+ opts[i].name = "D";
+ opts[i].label = N_("DSNOpts");
+ dsn_label = i++;
+ opts[i].ch = -2;
+ opts[i].rval = 's';
+ opts[i].name = "S";
+ opts[i++].label = "";
+ opts[i].ch = -2;
+ opts[i].rval = 'x';
+ opts[i].name = "X";
+ opts[i++].label = "";
+ opts[i].ch = -2;
+ opts[i].rval = 'h';
+ opts[i].name = "H";
+ opts[i++].label = "";
+ }
+
+ if(filters){
+ opts[i].ch = KEY_UP;
+ opts[i].rval = 10;
+ opts[i].name = "";
+ opts[i++].label = "";
+
+ opts[i].ch = KEY_DOWN;
+ opts[i].rval = 11;
+ opts[i].name = "";
+ opts[i++].label = "";
+ }
+
+ opts[i].ch = -1;
+
+ fix_windsize(ps_global);
+
+ while(1){
+ if(filters && filters->filter && (p = strindex(filters->filter, ' ')))
+ *p = '\0';
+ else
+ p = NULL;
+
+ lparen = 0;
+ strncpy(tmp_20k_buf, "Send message", SIZEOF_20KBUF);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ optp = tmp_20k_buf + strlen(tmp_20k_buf);
+
+ if(F_ON(F_NO_FCC_ATTACH, ps_global) && !lmc.text_only)
+ sstrncpy(&optp, " and Fcc Atmts", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+
+ if(allow_flowed && !flowing_requested){
+ if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){
+ if(!lparen){
+ *optp++ = ' ';
+ *optp++ = '(';
+ lparen++;
+ }
+ else
+ *optp++ = ' ';
+ }
+
+ sstrncpy(&optp, "not flowed", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ }
+
+ if(filters){
+ if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){
+ if(!lparen){
+ *optp++ = ' ';
+ *optp++ = '(';
+ lparen++;
+ }
+ else{
+ *optp++ = ',';
+ *optp++ = ' ';
+ }
+ }
+
+ if(filters->filter){
+ sstrncpy(&optp, "filtered thru \"", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ sstrncpy(&optp, filters->filter, SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ if((optp-tmp_20k_buf) < SIZEOF_20KBUF)
+ *optp++ = '\"';
+ }
+ else
+ sstrncpy(&optp, "unfiltered", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ }
+
+ if((call_mailer_flags & CM_VERBOSE) || background_requested){
+ if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){
+ if(!lparen){
+ *optp++ = ' ';
+ *optp++ = '(';
+ lparen++;
+ }
+ else
+ *optp++ = ' ';
+ }
+
+ sstrncpy(&optp, "in ", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ if(call_mailer_flags & CM_VERBOSE)
+ sstrncpy(&optp, "verbose ", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+
+ if(background_requested)
+ sstrncpy(&optp, "background ", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+
+ sstrncpy(&optp, "mode", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ }
+
+ if(g_rolenick && !(he && he[N_FROM].dirty)){
+ if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){
+ if(!lparen){
+ *optp++ = ' ';
+ *optp++ = '(';
+ lparen++;
+ }
+ else
+ *optp++ = ' ';
+ }
+
+ sstrncpy(&optp, "as \"", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ sstrncpy(&optp, g_rolenick, SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ sstrncpy(&optp, "\"", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ }
+
+ if(call_mailer_flags & CM_DSN_SHOW){
+ if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){
+ if(!lparen){
+ *optp++ = ' ';
+ *optp++ = '(';
+ lparen++;
+ }
+ else{
+ *optp++ = ',';
+ *optp++ = ' ';
+ }
+ }
+
+ sstrncpy(&optp, dsn_string, SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ }
+
+#ifdef SMIME
+ if(ps_global->smime && ps_global->smime->do_encrypt){
+ if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){
+ if(!lparen){
+ *optp++ = ' ';
+ *optp++ = '(';
+ lparen++;
+ }
+ else{
+ *optp++ = ',';
+ *optp++ = ' ';
+ }
+ }
+
+ sstrncpy(&optp, "Encrypted", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ }
+
+ if(ps_global->smime && ps_global->smime->do_sign){
+ if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){
+ if(!lparen){
+ *optp++ = ' ';
+ *optp++ = '(';
+ lparen++;
+ }
+ else{
+ *optp++ = ',';
+ *optp++ = ' ';
+ }
+ }
+
+ sstrncpy(&optp, "Signed", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ }
+#endif
+
+ if(lparen && (optp-tmp_20k_buf) < SIZEOF_20KBUF)
+ *optp++ = ')';
+
+ sstrncpy(&optp, "? ", SIZEOF_20KBUF-(optp-tmp_20k_buf));
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+
+ if(p)
+ *p = ' ';
+
+ if(flowing_label)
+ opts[flowing_label].label = flowing_requested ? N_("NoFlow") : N_("Flow");
+
+ if(verbose_label)
+ opts[verbose_label].label = (call_mailer_flags & CM_VERBOSE) ? N_("Normal") : N_("Verbose");
+
+ if(bg_label)
+ opts[bg_label].label = background_requested
+ ? N_("Foreground") : N_("Background");
+
+ if(fcc_label)
+ opts[fcc_label].label = lmc.text_only ? N_("Fcc Attchmnts")
+ : N_("No Fcc Atmts ");
+
+ if(F_ON(F_DSN, ps_global)){
+ if(call_mailer_flags & CM_DSN_SHOW){
+ opts[dsn_label].label = (call_mailer_flags & CM_DSN_DELAY)
+ ? N_("NoDelay") : N_("Delay");
+ opts[dsn_label+1].ch = 's';
+ opts[dsn_label+1].label = (call_mailer_flags & CM_DSN_SUCCESS)
+ ? N_("NoSuccess") : N_("Success");
+ opts[dsn_label+2].ch = 'x';
+ opts[dsn_label+2].label = (call_mailer_flags & CM_DSN_NEVER)
+ ? N_("ErrRets") : N_("NoErrRets");
+ opts[dsn_label+3].ch = 'h';
+ opts[dsn_label+3].label = (call_mailer_flags & CM_DSN_FULL)
+ ? N_("RetHdrs") : N_("RetFull");
+ }
+ }
+
+ if(double_rad +
+ ((call_mailer_flags & CM_DSN_SHOW)
+ ? 4 : F_ON(F_DSN, ps_global) ? 1 : 0) > 11)
+ rv = double_radio_buttons(tmp_20k_buf, -FOOTER_ROWS(ps_global), opts,
+ 'y', 'z',
+ (F_ON(F_DSN, ps_global) && allow_flowed)
+ ? h_send_prompt_dsn_flowed :
+ F_ON(F_DSN, ps_global) ? h_send_prompt_dsn :
+ allow_flowed ? h_send_prompt_flowed :
+ h_send_prompt,
+ RB_NORM);
+ else
+ rv = radio_buttons(tmp_20k_buf, -FOOTER_ROWS(ps_global), opts,
+ 'y', 'z',
+ (double_rad +
+ ((call_mailer_flags & CM_DSN_SHOW)
+ ? 4 : F_ON(F_DSN, ps_global) ? 1 : 0) == 11)
+ ? NO_HELP :
+ (F_ON(F_DSN, ps_global) && allow_flowed)
+ ? h_send_prompt_dsn_flowed :
+ F_ON(F_DSN, ps_global) ? h_send_prompt_dsn :
+ allow_flowed ? h_send_prompt_flowed :
+ h_send_prompt,
+ RB_NORM);
+
+ if(rv == 'y'){ /* user ACCEPTS! */
+ break;
+ }
+ else if(rv == 'n'){ /* Declined! */
+ rstr = _("No Message Sent");
+ break;
+ }
+ else if(rv == 'z'){ /* Cancelled! */
+ rstr = _("Send Cancelled");
+ break;
+ }
+ else if(rv == 10){ /* PREVIOUS filter */
+ filters = filters->prev;
+ }
+ else if(rv == 11){ /* NEXT filter */
+ filters = filters->next;
+ }
+ else if(rv == 21){
+ lmc.text_only = !lmc.text_only;
+ }
+ else if(rv == 12){ /* flip verbose bit */
+ if(call_mailer_flags & CM_VERBOSE)
+ call_mailer_flags &= ~CM_VERBOSE;
+ else
+ call_mailer_flags |= CM_VERBOSE;
+
+ if((call_mailer_flags & CM_VERBOSE) && background_requested)
+ background_requested = 0;
+ }
+ else if(rv == 22){ /* flip flowing bit */
+ flowing_requested = !flowing_requested;
+ }
+ else if(rv == 15){
+ if((background_requested = !background_requested)
+ && (call_mailer_flags & CM_VERBOSE))
+ call_mailer_flags &= ~CM_VERBOSE; /* clear verbose */
+ }
+ else if(call_mailer_flags & CM_DSN_SHOW){
+ if(rv == 's'){ /* flip success bit */
+ call_mailer_flags ^= CM_DSN_SUCCESS;
+ /* turn off related bits */
+ if(call_mailer_flags & CM_DSN_SUCCESS)
+ call_mailer_flags &= ~(CM_DSN_NEVER);
+ }
+ else if(rv == 'd'){ /* flip delay bit */
+ call_mailer_flags ^= CM_DSN_DELAY;
+ /* turn off related bits */
+ if(call_mailer_flags & CM_DSN_DELAY)
+ call_mailer_flags &= ~(CM_DSN_NEVER);
+ }
+ else if(rv == 'x'){ /* flip never bit */
+ call_mailer_flags ^= CM_DSN_NEVER;
+ /* turn off related bits */
+ if(call_mailer_flags & CM_DSN_NEVER)
+ call_mailer_flags &= ~(CM_DSN_SUCCESS | CM_DSN_DELAY);
+ }
+ else if(rv == 'h'){ /* flip full bit */
+ call_mailer_flags ^= CM_DSN_FULL;
+ }
+ }
+ else if(rv == 'd'){ /* show dsn options */
+ /*
+ * When you turn on DSN, the default is to notify on
+ * failure, success, or delay; and to return the whole
+ * body on failure.
+ */
+ call_mailer_flags |= (CM_DSN_SHOW | CM_DSN_SUCCESS | CM_DSN_DELAY | CM_DSN_FULL);
+ }
+ else if(rv == 'p'){ /* choose X-Priority */
+ char *prio;
+
+ prio = choose_a_priority(priority_requested);
+ if((ps_global->redrawer = redraw_pico) != NULL){
+ (*ps_global->redrawer)();
+ fix_windsize(ps_global);
+ }
+
+ if(prio){
+ if(priority_requested)
+ fs_give((void **) &priority_requested);
+
+ if(prio[0])
+ priority_requested = prio;
+ else
+ fs_give((void **) &prio);
+ }
+ }
+#ifdef SMIME
+ else if(rv=='e'){
+ if(ps_global->smime)
+ ps_global->smime->do_encrypt = !ps_global->smime->do_encrypt;
+ }
+ else if(rv=='g'){
+ if(ps_global->smime)
+ ps_global->smime->do_sign = !ps_global->smime->do_sign;
+ }
+#endif
+
+ snprintf(dsn_string, sizeof(dsn_string), "DSN requested[%s%s%s%s]",
+ (call_mailer_flags & CM_DSN_NEVER)
+ ? "Never" : "F",
+ (call_mailer_flags & CM_DSN_DELAY)
+ ? "D" : "",
+ (call_mailer_flags & CM_DSN_SUCCESS)
+ ? "S" : "",
+ (call_mailer_flags & CM_DSN_NEVER)
+ ? ""
+ : (call_mailer_flags & CM_DSN_FULL) ? "-Full"
+ : "-Hdrs");
+ dsn_string[sizeof(dsn_string)-1] = '\0';
+ }
+
+ /* remember selection */
+ if(filters && filters->index > -1)
+ sending_filter_requested = ps_global->VAR_SEND_FILTER[filters->index];
+
+ if(filters){
+ filters->prev->next = NULL; /* tie off list */
+ while(filters){ /* then free it */
+ fp = filters->next;
+ if(filters->filter)
+ fs_give((void **)&filters->filter);
+
+ fs_give((void **)&filters);
+ filters = fp;
+ }
+ }
+
+ if(old_suspend)
+ (void) F_SET(F_CAN_SUSPEND, ps_global, 1);
+
+ if(result)
+ *result = rstr;
+
+ ps_global->redrawer = redraw;
+
+ return((rstr == NULL) ? 0 : 1);
+}
+
+
+/*
+ * Allow user to choose a priority for sending.
+ *
+ * Returns an allocated priority on success, NULL otherwise.
+ */
+char *
+choose_a_priority(char *default_val)
+{
+ char *choice = NULL;
+ char **priority_list, **lp;
+ char *starting_val = NULL;
+ char *none;
+ int i, cnt;
+ PRIORITY_S *p;
+
+ for(cnt = 0, p = priorities; p && p->desc; p++)
+ cnt++;
+
+ cnt++; /* for NONE entry */
+ lp = priority_list = (char **) fs_get((cnt + 1) * sizeof(*priority_list));
+ memset(priority_list, 0, (cnt+1) * sizeof(*priority_list));
+
+ for(i = 0, p = priorities; p && p->desc; p++){
+ *lp = cpystr(p->desc);
+ if(default_val && !strcmp(default_val, p->desc))
+ starting_val = (*lp);
+
+ lp++;
+ }
+
+ none = _("NONE - No X-Priority header included");
+ *lp = cpystr(none);
+ if(!starting_val)
+ starting_val = (*lp);
+
+ /* TRANSLATORS: SELECT A PRIORITY is a screen title
+ TRANSLATORS: Print something1 using something2.
+ "priorities" is something1 */
+ choice = choose_item_from_list(priority_list, NULL, _("SELECT A PRIORITY"),
+ _("priorities"), h_select_priority_screen,
+ _("HELP FOR SELECTING A PRIORITY"),
+ starting_val);
+
+ if(!choice)
+ q_status_message(SM_ORDER, 1, 4, _("No change"));
+ else if(!strcmp(choice, none))
+ choice[0] = '\0';
+
+ free_list_array(&priority_list);
+
+ return(choice);
+}
+
+
+int
+dont_flow_this_time(void)
+{
+ return(flowing_requested ? 0 : 1);
+}
+
+
+/*----------------------------------------------------------------------
+ Call back for pico to display mime type of attachment
+
+Args: file -- filename being attached
+
+Returns: returns 1 on success (message queued), zero otherwise (don't know
+ type so nothing queued).
+----*/
+int
+mime_type_for_pico(char *file)
+{
+ BODY *body;
+ int rv;
+ void *file_contents;
+
+ body = mail_newbody();
+ body->type = TYPEOTHER;
+ body->encoding = ENCOTHER;
+
+ /* don't know where the cursor's been, reset it */
+ clear_cursor_pos();
+ if(!set_mime_type_by_extension(body, file)){
+ if((file_contents=(void *)so_get(FileStar,file,READ_ACCESS|READ_FROM_LOCALE)) != NULL){
+ body->contents.text.data = file_contents;
+ set_mime_type_by_grope(body);
+ }
+ }
+
+ if(body->type != TYPEOTHER){
+ rv = 1;
+ q_status_message3(SM_ORDER, 0, 3,
+ _("File %s attached as type %s/%s"), file,
+ body_types[body->type],
+ body->subtype ? body->subtype : rfc822_default_subtype(body->type));
+ }
+ else
+ rv = 0;
+
+ pine_free_body(&body);
+ return(rv);
+}
+
+
+/*----------------------------------------------------------------------
+ Call back for pico to receive an uploaded message
+
+ Args: fname -- name for uploaded file (empty if they want us to assign it)
+ size -- pointer to long to hold the attachment's size
+
+ Notes: the attachment is uploaded to a temp file, and
+
+ Returns: TRUE on success, FALSE otherwise
+----*/
+int
+upload_msg_to_pico(char *fname, size_t fnlen, long int *size)
+{
+ char cmd[MAXPATH+1], *fnp = NULL;
+ char *locale_name = NULL;
+ long l;
+ PIPE_S *syspipe;
+
+ dprint((1, "Upload cmd called to xfer \"%s\"\n",
+ fname ? fname : "<NO FILE>"));
+
+ if(!fname) /* no place for file name */
+ return(0);
+
+ if(!*fname){ /* caller wants temp file */
+ if((fnp = temp_nam(NULL, "pu")) != NULL){
+ strncpy(fname, fnp, fnlen);
+ fname[fnlen-1] = '\0';
+ our_unlink(fnp);
+ fs_give((void **)&fnp);
+ }
+ }
+ else
+ locale_name = convert_to_locale(fname);
+
+ build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_UPLOAD_CMD_PREFIX,
+ ps_global->VAR_UPLOAD_CMD, locale_name ? locale_name : fname);
+ if((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_USER | PIPE_RESET,
+ 0, pipe_callback, pipe_report_error)) != NULL){
+ (void) close_system_pipe(&syspipe, NULL, pipe_callback);
+ if((l = name_file_size(locale_name ? locale_name : fname)) < 0L){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "Error determining size of %s: %s", fname,
+ fnp = error_description(errno));
+ dprint((1,
+ "!!! Upload cmd \"%s\" failed for \"%s\": %s\n",
+ cmd ? cmd : "?",
+ fname ? fname : "?",
+ fnp ? fnp : "?"));
+ }
+ else if(size)
+ *size = l;
+
+ if(locale_name)
+ fs_give((void **) &locale_name);
+
+ return(l >= 0);
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4, _("Error opening pipe"));
+
+ if(locale_name)
+ fs_give((void **) &locale_name);
+
+ return(0);
+}
+
+
+char *
+cancel_for_pico(void (*redraw_pico)(void))
+{
+ int rv;
+ char *rstr = NULL;
+ char *prompt =
+ _("Cancel message (answering \"Confirm\" will abandon your mail message) ? ");
+ void (*redraw)(void) = ps_global->redrawer;
+ static ESCKEY_S opts[] = {
+ {'c', 'c', "C", N_("Confirm")},
+ {'n', 'n', "N", N_("No")},
+ {'y', 'y', "", ""},
+ {-1, 0, NULL, NULL}
+ };
+
+ ps_global->redrawer = redraw_pico;
+ fix_windsize(ps_global);
+
+ while(1){
+ rv = radio_buttons(prompt, -FOOTER_ROWS(ps_global), opts,
+ 'n', 'x', h_confirm_cancel, RB_NORM);
+ if(rv == 'c'){ /* user ACCEPTS! */
+ rstr = "";
+ break;
+ }
+ else if(rv == 'y'){
+ q_status_message(SM_INFO, 1, 3, _(" Type \"C\" to cancel message "));
+ display_message('x');
+ }
+ else
+ break;
+ }
+
+ ps_global->redrawer = redraw;
+ return(rstr);
+}
+
+
+/*----------------------------------------------------------------------
+ Pass the first text segment of the message thru the "send filter"
+
+Args: body pointer and address for storage object of old data
+
+Returns: returns 1 on success, zero on error.
+----*/
+int
+filter_message_text(char *fcmd, ENVELOPE *outgoing, struct mail_bodystruct *body,
+ STORE_S **old, METAENV *header)
+{
+ char *cmd, *tmpf = NULL, *resultf = NULL, *errstr = NULL, *mtf = NULL;
+ int key = 0, include_hdrs = 0;
+ gf_io_t gc, pc;
+ STORE_S **so = (STORE_S **)((body->type == TYPEMULTIPART)
+ ? &body->nested.part->body.contents.text.data
+ : &body->contents.text.data),
+ *tmp_so = NULL, *tmpf_so,
+ *save_local_so, *readthis_so, *our_tmpf_so = NULL;
+#define DO_HEADERS 1
+
+ if(fcmd
+ && (cmd=expand_filter_tokens(fcmd, outgoing, &tmpf, &resultf, &mtf,
+ &key, &include_hdrs))){
+ if(tmpf){
+ /*
+ * We need WRITE_TO_LOCALE here because the user is going to
+ * be operating on tmpf. We need to change it back after they
+ * are done.
+ */
+ if((tmpf_so = so_get(FileStar, tmpf, EDIT_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
+ if(key){
+ so_puts(tmpf_so, filter_session_key());
+ so_puts(tmpf_so, NEWLINE);
+ }
+
+ /*
+ * If the headers are wanted for filtering, we can just
+ * stick them in the tmpf file that is already there before
+ * putting the body in.
+ */
+ if(include_hdrs){
+ save_local_so = lmc.so;
+ lmc.so = tmpf_so; /* write it to tmpf_so */
+ lmc.all_written = lmc.text_written = lmc.text_only = 0;
+ pine_rfc822_header(header, body, NULL, NULL);
+ lmc.so = save_local_so;
+ }
+
+ so_seek(*so, 0L, 0);
+ gf_set_so_readc(&gc, *so);
+ gf_set_so_writec(&pc, tmpf_so);
+ gf_filter_init();
+ errstr = gf_pipe(gc, pc);
+ gf_clear_so_readc(*so);
+ gf_clear_so_writec(tmpf_so);
+ so_give(&tmpf_so);
+ }
+ else
+ errstr = "Can't create space for filter temporary file.";
+ }
+ else if(include_hdrs){
+ /*
+ * Gf_filter wants a single storage object to read from.
+ * If headers are wanted for filtering we'll have to put them
+ * and the body into a temp file first and then use that
+ * as the storage object for gf_filter.
+ * We don't use WRITE_TO_LOCALE in this case because gf_filter
+ * takes care of that.
+ */
+ if((our_tmpf_so = so_get(TmpFileStar, NULL, EDIT_ACCESS|OWNER_ONLY)) != NULL){
+ /* put headers in our_tmpf_so */
+ save_local_so = lmc.so;
+ lmc.so = our_tmpf_so; /* write it to our_tmpf_so */
+ lmc.all_written = lmc.text_written = lmc.text_only = 0;
+ pine_rfc822_header(header, body, NULL, NULL);
+ lmc.so = save_local_so;
+
+ /* put body in our_tmpf_so */
+ so_seek(*so, 0L, 0);
+ gf_set_so_readc(&gc, *so);
+ gf_set_so_writec(&pc, our_tmpf_so);
+ gf_filter_init();
+ errstr = gf_pipe(gc, pc);
+ gf_clear_so_readc(*so);
+ gf_clear_so_writec(our_tmpf_so);
+
+ /* tell gf_filter to read from our_tmpf_so instead of *so */
+ readthis_so = our_tmpf_so;
+ }
+ else
+ errstr = "Can't create space for temporary file.";
+ }
+ else
+ readthis_so = *so;
+
+ if(!errstr){
+ if((tmp_so = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
+ gf_set_so_writec(&pc, tmp_so);
+ ps_global->mangled_screen = 1;
+ suspend_busy_cue();
+ ClearScreen();
+ fflush(stdout);
+ if(tmpf){
+ PIPE_S *fpipe;
+
+ if((fpipe = open_system_pipe(cmd, NULL, NULL,
+ PIPE_NOSHELL | PIPE_RESET,
+ 0, pipe_callback, pipe_report_error)) != NULL){
+ if(close_system_pipe(&fpipe, NULL, pipe_callback) == 0){
+
+ /* now we undo the WRITE_FROM_LOCALE change in tmpf */
+ if((tmpf_so = so_get(FileStar, tmpf, READ_ACCESS|READ_FROM_LOCALE)) != NULL){
+ gf_set_so_readc(&gc, tmpf_so);
+ gf_filter_init();
+ errstr = gf_pipe(gc, pc);
+ gf_clear_so_readc(tmpf_so);
+ so_give(&tmpf_so);
+ }
+ else
+ errstr = "Can't open temp file filter wrote.";
+ }
+ else
+ errstr = "Filter command returned error.";
+ }
+ else
+ errstr = "Can't exec filter text.";
+ }
+ else
+ errstr = gf_filter(cmd, key ? filter_session_key() : NULL,
+ readthis_so, pc, NULL, 0,
+ pipe_callback);
+
+ if(our_tmpf_so)
+ so_give(&our_tmpf_so);
+
+ gf_clear_so_writec(tmp_so);
+
+#ifdef _WINDOWS
+ if(!errstr){
+ /*
+ * Can't really be using stdout, so don't print message that
+ * gets printed in the else. Ideally the program being called
+ * will wait after showing the message, we might want to look
+ * into doing the waiting in console based apps... or not.
+ */
+#else
+ if(errstr){
+ unsigned long ch;
+
+ fprintf(stdout, "\r\n%s Hit return to continue.", errstr);
+ fflush(stdout);
+ while((ch = read_char(300)) != ctrl('M')
+ && ch != NO_OP_IDLE)
+ putchar(BELL);
+ }
+ else{
+#endif /* _WINDOWS */
+ BODY *b = (body->type == TYPEMULTIPART)
+ ? &body->nested.part->body : body;
+
+ *old = *so; /* save old so */
+ *so = tmp_so; /* return new one */
+ (*so)->attr = copy_parameters((*old)->attr);
+
+ /*
+ * If the command said it would return new MIME
+ * mime type data, check it out...
+ */
+ if(mtf){
+ char buf[MAILTMPLEN], *s;
+ FILE *fp;
+
+ if((fp = our_fopen(mtf, "rb")) != NULL){
+ if(fgets(buf, sizeof(buf), fp)
+ && !struncmp(buf, "content-", 8)
+ && (s = strchr(buf+8, ':'))){
+ BODY *nb = mail_newbody();
+
+ for(*s++ = '\0'; *s == ' '; s++)
+ ;
+
+ rfc822_parse_content_header(nb,
+ (char *) ucase((unsigned char *) buf+8),s);
+ if(nb->type == TYPETEXT
+ && nb->subtype
+ && (!b->subtype
+ || strucmp(b->subtype, nb->subtype))){
+ if(b->subtype)
+ fs_give((void **) &b->subtype);
+
+ b->subtype = nb->subtype;
+ nb->subtype = NULL;
+
+ mail_free_body_parameter(&b->parameter);
+ b->parameter = nb->parameter;
+ nb->parameter = NULL;
+ mail_free_body_parameter(&nb->parameter);
+ }
+
+ mail_free_body(&nb);
+ }
+
+ fclose(fp);
+ }
+ }
+
+ /*
+ * Reevaluate the encoding in case form's changed...
+ */
+ b->encoding = ENCOTHER;
+ set_mime_type_by_grope(b);
+ }
+
+ ClearScreen();
+ resume_busy_cue(0);
+ }
+ else
+ errstr = "Can't create space for filtered text.";
+ }
+
+ fs_give((void **)&cmd);
+ }
+ else
+ return(0);
+
+ if(tmpf){
+ our_unlink(tmpf);
+ fs_give((void **)&tmpf);
+ }
+
+ if(mtf){
+ our_unlink(mtf);
+ fs_give((void **) &mtf);
+ }
+
+ if(resultf){
+ if(name_file_size(resultf) > 0L)
+ display_output_file(resultf, "Filter", NULL, DOF_BRIEF);
+
+ fs_give((void **)&resultf);
+ }
+ else if(errstr){
+ if(tmp_so)
+ so_give(&tmp_so);
+
+ q_status_message1(SM_ORDER | SM_DING, 3, 6, _("Problem filtering: %s"),
+ errstr);
+ dprint((1, "Filter FAILED: %s\n",
+ errstr ? errstr : "?"));
+ }
+
+ return(errstr == NULL);
+}
+
+
+/*----------------------------------------------------------------------
+ Copy the newsgroup name of the given mailbox into the given buffer
+
+Args:
+
+Returns:
+----*/
+void
+pine_send_newsgroup_name(char *mailbox, char *group_name, size_t len)
+{
+ NETMBX mb;
+
+ if(*mailbox == '#'){ /* Strip the leading "#news." */
+ strncpy(group_name, mailbox + 6, len-1);
+ group_name[len-1] = '\0';
+ }
+ else if(mail_valid_net_parse(mailbox, &mb)){
+ pine_send_newsgroup_name(mb.mailbox, group_name, len);
+ }
+ else
+ *group_name = '\0';
+}
+
+
+/*----------------------------------------------------------------------
+ Generate and send a message back to the pine development team
+
+Args: none
+
+Returns: none
+----*/
+void
+phone_home(char *addr)
+{
+ char tmp[MAX_ADDRESS];
+ ENVELOPE *outgoing;
+ BODY *body;
+
+ outgoing = mail_newenvelope();
+ if(!addr || !strindex(addr, '@')){
+ snprintf(addr = tmp, sizeof(tmp), "alpine%s@%s", PHONE_HOME_VERSION, PHONE_HOME_HOST);
+ tmp[sizeof(tmp)-1] = '\0';
+ }
+
+ rfc822_parse_adrlist(&outgoing->to, addr, ps_global->maildomain);
+
+ outgoing->message_id = generate_message_id();
+ outgoing->subject = cpystr("Document Request");
+ outgoing->from = phone_home_from();
+
+ body = mail_newbody();
+ body->type = TYPETEXT;
+
+ if((body->contents.text.data = (void *)so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){
+ so_puts((STORE_S *)body->contents.text.data, "Document request: ");
+ so_puts((STORE_S *)body->contents.text.data, "Alpine-");
+ so_puts((STORE_S *)body->contents.text.data, ALPINE_VERSION);
+ if(ps_global->first_time_user)
+ so_puts((STORE_S *)body->contents.text.data, " for New Users");
+
+ if(ps_global->VAR_INBOX_PATH && ps_global->VAR_INBOX_PATH[0] == '{')
+ so_puts((STORE_S *)body->contents.text.data, " and IMAP");
+
+ if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0]
+ && ps_global->VAR_NNTP_SERVER[0][0])
+ so_puts((STORE_S *)body->contents.text.data, " and NNTP");
+
+ (void)pine_simple_send(outgoing, &body, NULL,NULL,NULL,NULL, SS_NULLRP);
+
+ q_status_message(SM_ORDER, 1, 3, "Thanks for being counted!");
+ }
+ else
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Problem creating space for message text.");
+
+ mail_free_envelope(&outgoing);
+ pine_free_body(&body);
+
+}
+
+
+/*----------------------------------------------------------------------
+ Set up fields for passing to pico. Assumes first text part is
+ intended to be passed along for editing, and is in the form of
+ of a storage object brought into existence sometime before pico_send().
+ -----*/
+void
+outgoing2strings(METAENV *header, struct mail_bodystruct *bod, void **text,
+ PATMT **pico_a, int from_bounce)
+{
+ PINEFIELD *pf;
+
+ /*
+ * SIMPLIFYING ASSUMPTION #37: the first TEXT part's storage object
+ * is guaranteed to be of type PicoText!
+ */
+ if(bod->type == TYPETEXT){
+ *text = so_text((STORE_S *) bod->contents.text.data);
+
+ /* mark storage object as user edited */
+ if(!from_bounce)
+ (void) so_attr((STORE_S *) bod->contents.text.data, "edited", "1");
+ }
+ else if(bod->type == TYPEMULTIPART){
+ PART *part;
+ PATMT **ppa;
+ char *type, *name, *p;
+ int name_l;
+
+ /*
+ * We used to jump out the window if the first part wasn't text,
+ * but that may not be the case when bouncing a message with
+ * a leading non-text segment. So, IT'S UNDERSTOOD that the
+ * contents of the first part to send is still ALWAYS in a
+ * PicoText storage object, *AND* if that object doesn't contain
+ * data of type text, then it must contain THE ENCODED NON-TEXT
+ * DATA of the piece being sent.
+ *
+ * It's up to the programmer to make sure that such a message is
+ * sent via pine_simple_send and never get to the composer via
+ * pine_send.
+ *
+ * Make sense?
+ */
+ *text = so_text((STORE_S *) bod->nested.part->body.contents.text.data);
+
+ /* mark storage object as user edited */
+ if(!from_bounce)
+ (void) so_attr((STORE_S *) bod->nested.part->body.contents.text.data, "edited", "1");
+
+ /*
+ * If we already had a list, blast it now, so we can build a new
+ * attachment list that reflects what's really there...
+ */
+ if(pico_a){
+ free_attachment_list(pico_a);
+
+
+ /* Simplifyihg assumption #28e. (see cross reference)
+ All parts in the body passed in here that are not already
+ in the attachments list are added to the end of the attachments
+ list. Attachment items not in the body list will be taken care
+ of in strings2outgoing, but they are unlikey to occur
+ */
+
+ for(part = bod->nested.part->next; part != NULL; part = part->next) {
+ /* Already in list? */
+ for(ppa = pico_a;
+ *ppa && strcmp((*ppa)->id, part->body.id);
+ ppa = &(*ppa)->next)
+ ;
+
+ if(!*ppa){ /* Not in the list! append it... */
+ *ppa = (PATMT *)fs_get(sizeof(PATMT));
+
+ if(part->body.description){
+ char *p;
+ size_t len;
+
+ len = 4*strlen(part->body.description)+1;
+ p = (char *)fs_get(len*sizeof(char));
+ if(rfc1522_decode_to_utf8((unsigned char *)p,
+ len, part->body.description) == (unsigned char *) p){
+ (*ppa)->description = p;
+ }
+ else{
+ fs_give((void **)&p);
+ (*ppa)->description = cpystr(part->body.description);
+ }
+ }
+ else
+ (*ppa)->description = cpystr("");
+
+ type = type_desc(part->body.type, part->body.subtype,
+ part->body.parameter, NULL, 0);
+
+ /*
+ * If we can find a "name" parm, display that too...
+ */
+ if((name = parameter_val(part->body.parameter, "name")) != NULL){
+ /* Convert any [ or ]'s the name contained */
+ for(p = name; *p ; p++)
+ if(*p == '[')
+ *p = '(';
+ else if(*p == ']')
+ *p = ')';
+
+ name_l = p - name;
+ }
+ else
+ name_l = 0;
+
+ (*ppa)->filename = fs_get(strlen(type) + name_l + 5);
+
+ snprintf((*ppa)->filename, strlen(type) + name_l + 5, "[%s%s%s]", type,
+ name ? ": " : "", name ? name : "");
+ (*ppa)->filename[strlen(type) + name_l + 5 - 1] = '\0';
+
+ if(name)
+ fs_give((void **) &name);
+
+ (*ppa)->flags = A_FLIT;
+ (*ppa)->size = cpystr(byte_string(
+ send_body_size(&part->body)));
+ if(!part->body.id)
+ part->body.id = generate_message_id();
+
+ (*ppa)->id = cpystr(part->body.id);
+ (*ppa)->next = NULL;
+ }
+ }
+ }
+ }
+
+
+ /*------------------------------------------------------------------
+ Malloc strings to pass to composer editor because it expects
+ such strings so it can realloc them
+ -----------------------------------------------------------------*/
+ /*
+ * turn any address fields into text strings
+ */
+ /*
+ * SIMPLIFYING ASSUMPTION #116: all header strings are understood
+ * NOT to be RFC1522 decoded. Said differently, they're understood
+ * to be RFC1522 ENCODED as necessary. The intent is to preserve
+ * original charset tagging as far into the compose/send pipe as
+ * we can.
+ */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->canedit)
+ switch(pf->type){
+ case Address :
+ if(pf->addr){
+ char *p, *t, *u;
+ long l;
+
+ pf->scratch = addr_list_string(*pf->addr, NULL, 1);
+
+ /*
+ * Scan for and fix-up patently bogus fields.
+ *
+ * NOTE: collaboration with this code and what's done in
+ * reply.c:reply_cp_addr to package up the bogus stuff
+ * is required.
+ */
+ for(p = pf->scratch; (p = strstr(p, "@" RAWFIELD)); )
+ for(t = p; ; t--)
+ if(*t == '&'){ /* find "leading" token */
+ int replacelen;
+
+ /*
+ * Rfc822_cat has been changed so that it now quotes
+ * this sometimes. So we have to look out for quotes
+ * which confuse the decoder. It was only quoting
+ * because we were putting \r \n in the input, I think.
+ */
+ if(t > pf->scratch && t[-1] == '\"' && p[-1] == '\"')
+ t[-1] = p[-1] = ' ';
+
+ *t++ = ' '; /* replace token */
+ *p = '\0'; /* tie off string */
+ u = rfc822_base64((unsigned char *) t,
+ (unsigned long) strlen(t),
+ (unsigned long *) &l);
+ if(!u)
+ u = "";
+
+ replacelen = strlen(t);
+ *p = '@'; /* restore 'p' */
+ rplstr(p, strlen(p), 12, ""); /* clear special token */
+ rplstr(t, strlen(u)-replacelen+1, replacelen, u);
+ if(u)
+ fs_give((void **) &u);
+
+ if(HE(pf))
+ HE(pf)->start_here = 1;
+
+ break;
+ }
+ else if(t == pf->scratch)
+ break;
+
+ removing_leading_white_space(pf->scratch);
+ if(pf->scratch){
+ size_t l;
+
+ /*
+ * Replace control characters with ^C notation, unless
+ * some conditions are met (see istrncpy).
+ * If user doesn't edit, then we switch back to the
+ * original version. If user does edit, then all bets
+ * are off.
+ */
+ iutf8ncpy((char *)tmp_20k_buf, pf->scratch, SIZEOF_20KBUF-1);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ if((l=strlen((char *)tmp_20k_buf)) > strlen(pf->scratch)){
+ fs_give((void **)&pf->scratch);
+ pf->scratch = (char *)fs_get((l+1) * sizeof(char));
+ }
+
+ strncpy(pf->scratch, (char *)tmp_20k_buf, l+1);
+ }
+ }
+
+ break;
+
+ case Subject :
+ if(pf->text){
+ char *p, *src;
+ size_t len;
+
+ src = pf->scratch ? pf->scratch
+ : (*pf->text) ? *pf->text : "";
+
+ len = 4*strlen(src)+1;
+ p = (char *)fs_get(len * sizeof(char));
+ if(rfc1522_decode_to_utf8((unsigned char *)p, len, src) == (unsigned char *) p){
+ if(pf->scratch)
+ fs_give((void **)&pf->scratch);
+
+ pf->scratch = p;
+ }
+ else{
+ fs_give((void **)&p);
+ if(!pf->scratch)
+ pf->scratch = cpystr(src);
+ }
+
+ if(pf->scratch){
+ size_t l;
+
+ /*
+ * Replace control characters with ^C notation, unless
+ * some conditions are met (see istrncpy).
+ * If user doesn't edit, then we switch back to the
+ * original version. If user does edit, then all bets
+ * are off.
+ */
+ iutf8ncpy((char *)tmp_20k_buf, pf->scratch, SIZEOF_20KBUF-1);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ if((l=strlen((char *)tmp_20k_buf)) > strlen(pf->scratch)){
+ fs_give((void **)&pf->scratch);
+ pf->scratch = (char *)fs_get((l+1) * sizeof(char));
+ }
+
+ strncpy(pf->scratch, (char *)tmp_20k_buf, l+1);
+ }
+ }
+
+ break;
+
+ default :
+ break;
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Restore fields returned from pico to form useful to sending
+ routines.
+ -----*/
+void
+strings2outgoing(METAENV *header, struct mail_bodystruct **bod, PATMT *attach, int flow_it)
+{
+ PINEFIELD *pf;
+ int we_cancel = 0;
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+
+ /*
+ * turn any local address strings into address lists
+ */
+ for(pf = header->local; pf && pf->name; pf = pf->next)
+ if(pf->scratch){
+ char *the_address = NULL;
+
+ switch(pf->type){
+ case Address :
+ removing_trailing_white_space(pf->scratch);
+
+ if((the_address || *pf->scratch) && pf->addr){
+ ADDRESS *new_addr = NULL;
+ static char *fakedomain = "@";
+
+ if(!the_address)
+ the_address = pf->scratch;
+
+ rfc822_parse_adrlist(&new_addr, the_address,
+ (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global))
+ ? fakedomain : ps_global->maildomain);
+ mail_free_address(pf->addr); /* free old addrs */
+ *pf->addr = new_addr; /* assign new addr */
+ }
+ else if(pf->addr)
+ mail_free_address(pf->addr); /* free old addrs */
+
+ break;
+
+ case Subject :
+ if(*pf->text)
+ fs_give((void **)pf->text);
+
+ if(*pf->scratch){
+ *pf->text = cpystr(pf->scratch);
+ }
+
+ break;
+
+ default :
+ break;
+ }
+
+ fs_give((void **)&pf->scratch); /* free now useless text */
+ }
+
+ create_message_body(bod, attach, flow_it);
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+}
+
+
+/*----------------------------------------------------------------------
+
+ The head of the body list here is always either TEXT or MULTIPART. It may be
+changed from TEXT to MULTIPART if there are attachments to be added
+and it is not already multipart.
+ ----*/
+void
+create_message_body(struct mail_bodystruct **b, PATMT *attach, int flow_it)
+{
+ PART *p, **pp;
+ PATMT *pa;
+ BODY *tmp_body, *text_body = NULL;
+ void *file_contents;
+ PARAMETER **parmp;
+ char *lc;
+
+ TIME_STAMP("create_body start.", 1);
+ /*
+ * if conditions are met short circuit MIME wrapping
+ */
+ if((*b)->type != TYPEMULTIPART && !attach){
+
+ /* only override assigned encoding if it might need upgrading */
+ if((*b)->type == TYPETEXT && (*b)->encoding == ENC7BIT)
+ (*b)->encoding = ENCOTHER;
+
+ create_message_body_text(*b, flow_it);
+
+ if(F_ON(F_COMPOSE_ALWAYS_DOWNGRADE, ps_global)
+ || !((*b)->encoding == ENC8BIT
+ || (*b)->encoding == ENCBINARY)){
+ TIME_STAMP("create_body end.", 1);
+ return;
+ }
+ else /* protect 8bit in multipart */
+ text_body = *b;
+ }
+
+ if((*b)->type == TYPETEXT) {
+ /*-- Current type is text, but there are attachments to add --*/
+ /*-- Upgrade to a TYPEMULTIPART --*/
+ tmp_body = (BODY *)mail_newbody();
+ tmp_body->type = TYPEMULTIPART;
+ tmp_body->nested.part = mail_newbody_part();
+ if(text_body){
+ /*
+ * Why do we do this?
+ * The problem is that base64 or quoted-printable encoding is
+ * sensitive to having random data appended to it's end. If
+ * we use a single part TEXT message and something in between
+ * us and the end appends advertising without adjusting for
+ * the encoding, the message is screwed up. So we wrap the
+ * text part inside a multipart and then the appended data
+ * will come after the boundary.
+ *
+ * We wish we could do this on the way out the door in a
+ * child of post_rfc822_output because at that point we know
+ * the character set and the encoding being used. For example,
+ * iso-2022-jp is an encoding that is not sensitive to data
+ * appended to the end, so it wouldn't need to be wrapped.
+ * We could conceivably have post_rfc822_body inspect the
+ * body and change it before doing the output. It would work
+ * but would be very fragile. We'd be passed a body from
+ * c-client to output and instead of just doing the output
+ * we'd change the body and then output it. Not worth it
+ * since the multipart wrapping is completely correct for
+ * MIME-aware mailers.
+ */
+ (void) copy_body(&(tmp_body->nested.part->body), *b);
+ /* move contents which were NOT copied */
+ tmp_body->nested.part->body.contents.text.data = (*b)->contents.text.data;
+ (*b)->contents.text.data = NULL;
+ }
+ else{
+ tmp_body->nested.part->body = **b;
+
+ (*b)->subtype = (*b)->id = (*b)->description = NULL;
+ (*b)->parameter = NULL;
+ (*b)->contents.text.data = NULL;
+ }
+
+ pine_free_body(b);
+ *b = tmp_body;
+ }
+
+ if(!text_body){
+ /*-- Now type must be MULTIPART with first part text --*/
+ (*b)->nested.part->body.encoding = ENCOTHER;
+ create_message_body_text(&((*b)->nested.part->body), flow_it);
+ }
+
+ /*------ Go through the parts list remove those to be deleted -----*/
+ for(pp = &(*b)->nested.part->next; *pp;){
+ for(pa = attach; pa && (*pp)->body.id; pa = pa->next)
+ /* already existed? */
+ if(pa->id && strcmp(pa->id, (*pp)->body.id) == 0){
+ char *orig_descp = NULL, *cs = NULL;
+
+ /*
+ * decode original to see if it matches what was decoded
+ * when we sent it in.
+ */
+
+ if((*pp)->body.description)
+ orig_descp = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
+ SIZEOF_20KBUF, (*pp)->body.description);
+
+ if(!(*pp)->body.description /* update description? */
+ || (pa->description && strcmp(pa->description, orig_descp))){
+ if((*pp)->body.description)
+ fs_give((void **) &(*pp)->body.description);
+
+ /* encoding happens as msg text is written */
+ (*pp)->body.description = cpystr(pa->description);
+ }
+
+ if(cs)
+ fs_give((void **) &cs);
+
+ break;
+ }
+
+ if(pa == NULL){
+ p = *pp; /* prepare to zap *pp */
+ *pp = p->next; /* pull next one in list up */
+ p->next = NULL; /* tie off removed node */
+
+ pine_free_body_data(&p->body); /* clean up contained data */
+ mail_free_body_part(&p); /* free up the part */
+ }
+ else
+ pp = &(*pp)->next;
+ }
+
+ /*---------- Now add any new attachments ---------*/
+ for(p = (*b)->nested.part ; p->next != NULL; p = p->next);
+ for(pa = attach; pa != NULL; pa = pa->next) {
+ if(pa->id != NULL)
+ continue; /* Has an ID, it's old */
+
+ /*
+ * the idea is handle ALL attachments as open FILE *'s. Actual
+ * encoding and such is handled at the time the message
+ * is shoved into the mail slot or written to disk...
+ *
+ * Also, we never unlink a file, so it's up to whoever opens
+ * it to deal with tmpfile issues.
+ */
+ if((file_contents = (void *)so_get(FileStar, pa->filename,
+ READ_ACCESS)) == NULL){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ _("Error \"%s\", couldn't attach file \"%s\""),
+ error_description(errno), pa->filename);
+ display_message('x');
+ continue;
+ }
+
+ p->next = mail_newbody_part();
+ p = p->next;
+ p->body.id = generate_message_id();
+ p->body.contents.text.data = file_contents;
+
+ /*
+ * Set type to unknown and let set_mime_type_by_* figure it out.
+ * Always encode attachments we add as BINARY.
+ */
+ p->body.type = TYPEOTHER;
+ p->body.encoding = ENCBINARY;
+ p->body.size.bytes = name_file_size(pa->filename);
+ if(!set_mime_type_by_extension(&p->body, pa->filename)){
+ set_mime_type_by_grope(&p->body);
+ set_charset_possibly_to_ascii(&p->body, ps_global->keyboard_charmap);
+ }
+
+ so_release((STORE_S *)p->body.contents.text.data);
+
+ if(pa->description) /* encoding happens when msg written */
+ p->body.description = cpystr(pa->description);
+
+ /* Add name attribute for backward compatibility */
+ for(parmp = &p->body.parameter; *parmp; )
+ if(!struncmp((*parmp)->attribute, "name", 4)
+ && (!*((*parmp)->attribute + 4)
+ || *((*parmp)->attribute + 4) == '*')){
+ PARAMETER *free_me = *parmp;
+ *parmp = (*parmp)->next;
+ free_me->next = NULL;
+ mail_free_body_parameter(&free_me);
+ }
+ else
+ parmp = &(*parmp)->next;
+
+ set_parameter(parmp, "name",
+ pa->filename
+ ? ((lc = last_cmpnt(pa->filename)) ? lc : pa->filename)
+ : NULL);
+
+ /* Then set the Content-Disposition ala RFC1806 */
+ if(!p->body.disposition.type){
+ p->body.disposition.type = cpystr("attachment");
+ for(parmp = &p->body.disposition.parameter; *parmp; )
+ if(!struncmp((*parmp)->attribute, "filename", 4)
+ && (!*((*parmp)->attribute + 4)
+ || *((*parmp)->attribute + 4) == '*')){
+ PARAMETER *free_me = *parmp;
+ *parmp = (*parmp)->next;
+ free_me->next = NULL;
+ mail_free_body_parameter(&free_me);
+ }
+ else
+ parmp = &(*parmp)->next;
+
+ set_parameter(parmp, "filename",
+ pa->filename
+ ? ((lc = last_cmpnt(pa->filename)) ? lc : pa->filename)
+ : NULL);
+ }
+
+ p->next = NULL;
+ pa->id = cpystr(p->body.id);
+ }
+
+ /*
+ * Now, if this multipart has but one text piece (that is, no
+ * attachments), then downgrade from a composite type to a discrete
+ * text/plain message if CTE is not 8bit.
+ */
+ if(!(*b)->nested.part->next
+ && (*b)->nested.part->body.type == TYPETEXT
+ && (F_ON(F_COMPOSE_ALWAYS_DOWNGRADE, ps_global)
+ || !((*b)->nested.part->body.encoding == ENC8BIT
+ || (*b)->nested.part->body.encoding == ENCBINARY))){
+ /* Clone the interesting body part */
+ tmp_body = mail_newbody();
+ *tmp_body = (*b)->nested.part->body;
+ /* and rub out what we don't want cleaned up when it's free'd */
+ mail_initbody(&(*b)->nested.part->body);
+ mail_free_body(b);
+ *b = tmp_body;
+ }
+
+
+ TIME_STAMP("create_body end.", 1);
+}
+
+
+/*
+ * Fill in text BODY part's structure
+ *
+ */
+void
+create_message_body_text(struct mail_bodystruct *b, int flow_it)
+{
+ set_mime_type_by_grope(b);
+ if(F_OFF(F_QUELL_FLOWED_TEXT, ps_global)
+ && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global)
+ && flow_it)
+ set_parameter(b ? &b->parameter : NULL, "format", "flowed");
+
+ set_body_size(b);
+}
+
+
+/*
+ * free_attachment_list - free attachments in given list
+ */
+void
+free_attachment_list(PATMT **alist)
+{
+ PATMT *leading;
+
+ while(alist && *alist){ /* pointer pointing to something */
+ leading = (*alist)->next;
+ if((*alist)->description)
+ fs_give((void **)&(*alist)->description);
+
+ if((*alist)->filename){
+ if((*alist)->flags & A_TMP)
+ if(our_unlink((*alist)->filename) < 0)
+ dprint((1, "-- Can't unlink(%s): %s\n",
+ (*alist)->filename ? (*alist)->filename : "?",
+ error_description(errno)));
+
+ fs_give((void **)&(*alist)->filename);
+ }
+
+ if((*alist)->size)
+ fs_give((void **)&(*alist)->size);
+
+ if((*alist)->id)
+ fs_give((void **)&(*alist)->id);
+
+ fs_give((void **)alist);
+
+ *alist = leading;
+ }
+}
+
+
+void
+set_body_size(struct mail_bodystruct *b)
+{
+ unsigned char c;
+ int we_cancel = 0;
+
+ we_cancel = busy_cue(NULL, NULL, 1);
+ so_seek((STORE_S *)b->contents.text.data, 0L, 0);
+ b->size.bytes = 0L;
+ while(so_readc(&c, (STORE_S *)b->contents.text.data))
+ b->size.bytes++;
+
+ if(we_cancel)
+ cancel_busy_cue(-1);
+}
+
+
+/*
+ * view_as_rich - set the rich_header flag
+ *
+ * name - name of the header field
+ * deflt - default value to return if user didn't set it
+ *
+ * Note: if the user tries to turn them all off with "", then
+ * we take that to mean default, since otherwise there is no
+ * way to get to the headers.
+ */
+int
+view_as_rich(char *name, int deflt)
+{
+ char **p;
+ char *q;
+
+ p = ps_global->VAR_COMP_HDRS;
+
+ if(p && *p && **p){
+ for(; (q = *p) != NULL; p++){
+ if(!struncmp(q, name, strlen(name)))
+ return 0; /* 0 means we *do* view it by default */
+ }
+
+ return 1; /* 1 means it starts out hidden */
+ }
+ return(deflt);
+}
+
+
+/*
+ * background_posting - return whether or not we're already in the process
+ * of posting
+ */
+int
+background_posting(int gripe)
+{
+ if(ps_global->post){
+ if(gripe)
+ q_status_message(SM_ORDER|SM_DING, 3, 3,
+ _("Can't post while posting!"));
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Validate the given subject relative to any news groups.
+
+Args: none
+
+Returns: always returns 1, but also returns error if
+----*/
+int
+valid_subject(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ struct headerentry *hp;
+
+ if(expanded)
+ *expanded = cpystr(given);
+
+ if(error){
+ /*
+ * Now look for any header entry we passed to pico that has to do
+ * with news. If there's no subject, gripe.
+ */
+ for(hp = pbf->headents; hp->prompt; hp++)
+ if(hp->help == h_composer_news){
+ if(hp->hd_text->text[0] && !*given)
+ *error = cpystr(
+ _("News postings MUST have a subject! Please add one!"));
+
+ break;
+ }
+ }
+
+ return(0);
+}
+
+
+/*
+ * This is the build_address used by the composer to check for an address
+ * in the addrbook.
+ *
+ * Args: to -- the passed in line to parse
+ * full_to -- Address of a pointer to return the full address in.
+ * This will be allocated here and freed by the caller.
+ * error -- Address of a pointer to return an error message in.
+ * This will be allocated here and freed by the caller.
+ * barg -- Address of a pointer to return the fcc in is in
+ * fcc->tptr. It will have already been allocated by the
+ * caller but we may free it and reallocate if we wish.
+ * Caller will free it.
+ *
+ * Result: 0 is returned if address was OK,
+ * -1 if address wasn't OK.
+ *
+ * Side effect: Can flush addrbook entry cache entries so they need to be
+ * re-fetched afterwords.
+ */
+int
+build_address(char *to, char **full_to, char **error, BUILDER_ARG *barg, int *mangled)
+{
+ char *p;
+ int ret_val, no_repo = 0, *save_nesting_level;
+ BuildTo bldto;
+ PrivateTop *pt = NULL;
+ PrivateAffector *af = NULL;
+ char *fcc_local = NULL;
+ jmp_buf save_jmp_buf;
+
+ dprint((5, "- build_address - (%s)\n", to ? to : "nul"));
+
+ /* check to see if to string is empty to avoid work */
+ for(p = to; p && *p && isspace((unsigned char)*p); p++)
+ ;/* do nothing */
+
+ if(!p || !*p){
+ if(full_to)
+ *full_to = cpystr(to ? to : ""); /* because pico does a strcmp() */
+
+ return 0;
+ }
+
+ if(full_to != NULL)
+ *full_to = (char *)NULL;
+
+ if(error != NULL)
+ *error = (char *)NULL;
+
+ /* No guarantee cursor or status line is how we saved it */
+ clear_cursor_pos();
+ mark_status_unknown();
+
+ if(ps_global->remote_abook_validity > 0 &&
+ adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+
+ /*
+ * If we end up jumping back here because somebody else changed one of
+ * our addrbooks out from underneath us, we may well leak some memory.
+ * That's probably ok since this will be very rare.
+ *
+ * The reason for the memcpy of the jmp_buf is that we may actually
+ * be indirectly calling this function from within the address book.
+ * For example, we may be in the address book screen and then run
+ * the ComposeTo command which puts us in the composer, then we call
+ * build_address from there which resets addrbook_changed_unexpectedly.
+ * Once we leave build_address we need to reset addrbook_changed_un...
+ * because this position on the stack will no longer be valid.
+ * Same is true of the other setjmp's in this file which are wrapped
+ * in memcpy calls.
+ */
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ no_repo = 0;
+ pt = NULL;
+ af = NULL;
+ fcc_local = NULL;
+ if(error != NULL)
+ *error = (char *)NULL;
+
+ if(full_to && *full_to)
+ fs_give((void **)full_to);
+
+ q_status_message(SM_ORDER, 3, 5, "Resetting address book...");
+ dprint((1,
+ "RESETTING address book... build_address(%s)!\n", to ? to : "?"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+ bldto.type = Str;
+ bldto.arg.str = to;
+ ret_val = build_address_internal(bldto, full_to, error,
+ barg ? &fcc_local : NULL,
+ &no_repo, NULL, save_and_restore,
+ 0, mangled);
+ ab_nesting_level--;
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ /*
+ * Have to rfc1522_decode the full_to string before sending it back.
+ */
+ if(full_to && *full_to ){
+ char *q;
+ size_t len;
+
+ len = 4*strlen(*full_to)+1;
+ q = (char *)fs_get(len * sizeof(char));
+ p = (char *)rfc1522_decode_to_utf8((unsigned char *)q, len, *full_to);
+
+ /* p == q means that decoding happened, p is decoded *full_to */
+ if(p == q){
+ fs_give((void **)full_to);
+ *full_to = p;
+ }
+ else
+ fs_give((void **)&q);
+ }
+
+ if(fcc_local){
+ unsigned long csum;
+
+ /* Pt will point to headents[Fcc].bldr_private */
+ pt = NULL;
+ if(barg && barg->aff)
+ pt = (PrivateTop *)(*barg->aff);
+
+ /*
+ * If *barg->aff is set, that means fcc was set from a list
+ * during some previous builder call.
+ * If the current To line contains the old expansion as a prefix, then
+ * we should leave things as they are. In order to decide that,
+ * we look at a hash value computed from the strings.
+ */
+ if(pt && (af=pt->affector) && af->who == BP_To){
+ int len;
+
+ len = strlen(to);
+ if(len >= af->cksumlen){
+ int save;
+
+ save = to[af->cksumlen];
+ to[af->cksumlen] = '\0';
+ csum = line_hash(to);
+ to[af->cksumlen] = save;
+ }
+ else
+ csum = af->cksumval + 1; /* something not equal to cksumval */
+ }
+
+ if(!pt ||
+ !pt->affector ||
+ (pt->affector->who == BP_To && csum != pt->affector->cksumval)){
+
+ /* replace fcc value */
+ if(barg->tptr)
+ fs_give((void **)&barg->tptr);
+
+ barg->tptr = fcc_local;
+
+ if(barg->aff){
+ if(!pt){
+ *barg->aff = (void *)fs_get(sizeof(PrivateTop));
+ pt = (PrivateTop *)(*barg->aff);
+ memset((void *)pt, 0, sizeof(PrivateTop));
+ }
+
+ if(no_repo){
+ if(!pt->affector)
+ pt->affector =
+ (PrivateAffector *)fs_get(sizeof(PrivateAffector));
+
+ af = pt->affector;
+ af->who = BP_To;
+ af->cksumlen = strlen(((full_to && *full_to)
+ ? *full_to : ""));
+ af->cksumval = line_hash(((full_to && *full_to)
+ ? *full_to : ""));
+ }
+ else{
+ /*
+ * If result is reproducible, we don't keep track here.
+ */
+ if(pt->affector)
+ fs_give((void **)&pt->affector);
+ }
+ }
+ }
+ else
+ fs_give((void **)&fcc_local); /* unused in this case */
+ }
+
+ /* This is so pico will erase the old message */
+ if(error != NULL && *error == NULL)
+ *error = cpystr("");
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ flush_status_messages(1);
+ return(ret_val);
+}
+
+
+/*
+ * This is the builder used by the composer for the Lcc line.
+ *
+ * Args: lcc -- the passed in Lcc line to parse
+ * full_lcc -- Address of a pointer to return the full address in.
+ * This will be allocated here and freed by the caller.
+ * error -- Address of a pointer to return an error message in.
+ * This is not allocated so should not be freed by the caller.
+ * barg -- This is a pointer to text for affected entries which
+ * we may be changing. The first one in the list is the
+ * To entry. We may put the name of the list in empty
+ * group syntax form there (like List Name: ;).
+ * The second one in the list is the fcc field.
+ * The tptr members already point to text allocated in the
+ * caller. We may free and reallocate here, caller will
+ * free the result in any case.
+ *
+ * Result: 0 is returned if address was OK,
+ * -1 if address wasn't OK.
+ *
+ * Side effect: Can flush addrbook entry cache entries so they need to be
+ * re-fetched afterwords.
+ */
+int
+build_addr_lcc(char *lcc, char **full_lcc, char **error, BUILDER_ARG *barg, int *mangled)
+{
+ int ret_val,
+ no_repo = 0; /* fcc or lcc not reproducible */
+ int *save_nesting_level;
+ BuildTo bldlcc;
+ PrivateTop *pt = NULL;
+ PrivateAffector *af = NULL;
+ char *p,
+ *fcc_local = NULL,
+ *to = NULL,
+ *dummy;
+ jmp_buf save_jmp_buf;
+
+ dprint((5, "- build_addr_lcc - (%s)\n", lcc ? lcc : "nul"));
+
+ /* check to see if to string is empty to avoid work */
+ for(p = lcc; p && *p && isspace((unsigned char)*p); p++)
+ ;/* do nothing */
+
+ if(!p || !*p){
+ if(full_lcc)
+ *full_lcc = cpystr(lcc ? lcc : ""); /* because pico does a strcmp() */
+
+ return 0;
+ }
+
+ if(error != NULL)
+ *error = (char *)NULL;
+
+ if(ps_global->remote_abook_validity > 0 &&
+ adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled)
+ *mangled |= BUILDER_SCREEN_MANGLED;
+
+ /*
+ * If we end up jumping back here because somebody else changed one of
+ * our addrbooks out from underneath us, we may well leak some memory.
+ * That's probably ok since this will be very rare.
+ */
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ no_repo = 0;
+ pt = NULL;
+ af = NULL;
+ fcc_local = NULL;
+ to = NULL;
+ if(error != NULL)
+ *error = (char *)NULL;
+
+ if(full_lcc && *full_lcc)
+ fs_give((void **)full_lcc);
+
+ q_status_message(SM_ORDER, 3, 5, "Resetting address book...");
+ dprint((1,
+ "RESETTING address book... build_address(%s)!\n", lcc ? lcc : "?"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+ bldlcc.type = Str;
+ bldlcc.arg.str = lcc;
+
+ /*
+ * To is first affected_entry and Fcc is second.
+ * The conditional stuff for the fcc argument says to only change the
+ * fcc if the fcc pointer is passed in non-null, and the To pointer
+ * is also non-null. If they are null, that means they've already been
+ * entered (are sticky). We don't affect fcc if either fcc or To has
+ * been typed in.
+ */
+ ret_val = build_address_internal(bldlcc,
+ full_lcc,
+ error,
+ (barg && barg->next && barg->next->tptr && barg->tptr)
+ ? &fcc_local : NULL,
+ &no_repo,
+ (barg && barg->tptr) ? &to : NULL,
+ save_and_restore, 0, mangled);
+
+ ab_nesting_level--;
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+
+ /* full_lcc is what ends up in the Lcc: line */
+ if(full_lcc && *full_lcc){
+ size_t len;
+
+ /*
+ * Have to rfc1522_decode the full_lcc string before sending it back.
+ */
+ len = 4*strlen(*full_lcc)+1;
+ p = (char *)fs_get(len * sizeof(char));
+ if(rfc1522_decode_to_utf8((unsigned char *)p, len, *full_lcc) == (unsigned char *)p){
+ fs_give((void **)full_lcc);
+ *full_lcc = p;
+ }
+ else
+ fs_give((void **)&p);
+ }
+
+ /* to is what ends up in the To: line */
+ if(to && *to){
+ unsigned long csum;
+ size_t len;
+
+ /*
+ * Have to rfc1522_decode the full_to string before sending it back.
+ */
+ len = 4*strlen(to)+1;
+ p = (char *)fs_get(len * sizeof(char));
+ dummy = NULL;
+ if(rfc1522_decode_to_utf8((unsigned char *)p, len, to) == (unsigned char *)p){
+ /*
+ * If the caller wants us to try to preserve the charset
+ * information (they set aff) we copy it into encoded->etext.
+ * We don't have to worry about pasting together pieces of
+ * etext like we do in build_address because whenever the
+ * Lcc line is setting the To line it will be setting the
+ * whole line, not modifying it.
+ * Pt will point to headents[To].bldr_private.
+ */
+ if(barg && barg->aff){
+ pt = (PrivateTop *)(*barg->aff);
+
+ if(!pt){
+ *barg->aff = (void *)fs_get(sizeof(PrivateTop));
+ pt = (PrivateTop *)(*barg->aff);
+ memset((void *)pt, 0, sizeof(PrivateTop));
+ }
+ }
+
+ fs_give((void **)&to);
+ to = p;
+ }
+ else
+ fs_give((void **)&p);
+
+ if(dummy)
+ fs_give((void **)&dummy);
+
+
+ /*
+ * This part is recording the fact that the To line was set to
+ * what it is by entering something on the Lcc line. In particular,
+ * if a list alias was entered here then the fullname of the list
+ * goes in the To line. We save this affector information so that
+ * we can tell it shouldn't be modified if we call build_addr_lcc
+ * again unless we actually modified what's in the Lcc line so that
+ * it doesn't start with the same thing. The problem we're solving
+ * is that the contents of the Lcc line no longer look like the
+ * list they were derived from.
+ * Pt will point to headents[To].bldr_private.
+ */
+ if(barg && barg->aff)
+ pt = (PrivateTop *)(*barg->aff);
+
+ if(pt && (af=pt->affector) && af->who == BP_Lcc){
+ int len;
+
+ len = strlen(lcc);
+ if(len >= af->cksumlen){
+ int save;
+
+ save = lcc[af->cksumlen];
+ lcc[af->cksumlen] = '\0';
+ csum = line_hash(lcc);
+ lcc[af->cksumlen] = save;
+ }
+ else
+ csum = af->cksumval + 1; /* so they aren't equal */
+ }
+
+ if(!pt ||
+ !pt->affector ||
+ pt->affector->who != BP_Lcc ||
+ (pt->affector->who == BP_Lcc && csum != pt->affector->cksumval)){
+
+ /* replace to value */
+ if(barg->tptr && barg->tptr[0]){
+ size_t l;
+ char *t;
+
+ l = strlen(barg->tptr) + strlen(to ? to : "") + 2;
+ t = (char *)fs_get((l+1) * sizeof(char));
+ snprintf(t, l+1, "%s%s%s",
+ barg->tptr,
+ (to && *to) ? ", " : "",
+ (to && *to) ? to : "");
+ fs_give((void **)&barg->tptr);
+ if(to)
+ fs_give((void **)&to);
+
+ barg->tptr = t;
+ }
+ else{
+ if(barg->tptr)
+ fs_give((void **)&barg->tptr);
+
+ barg->tptr = to;
+ }
+
+ if(barg->aff){
+ if(!pt){
+ *barg->aff = (void *)fs_get(sizeof(PrivateTop));
+ pt = (PrivateTop *)(*barg->aff);
+ memset((void *)pt, 0, sizeof(PrivateTop));
+ }
+
+ if(no_repo){
+ if(!pt->affector)
+ pt->affector =
+ (PrivateAffector *)fs_get(sizeof(PrivateAffector));
+
+ af = pt->affector;
+ af->who = BP_Lcc;
+ af->cksumlen = strlen(((full_lcc && *full_lcc)
+ ? *full_lcc : ""));
+ af->cksumval = line_hash(((full_lcc && *full_lcc)
+ ? *full_lcc : ""));
+ }
+ else{
+ /*
+ * If result is reproducible, we don't keep track here.
+ */
+ if(pt->affector)
+ fs_give((void **)&pt->affector);
+ }
+ }
+ }
+ else
+ fs_give((void **)&to); /* unused in this case */
+ }
+
+ if(fcc_local){
+ unsigned long csum;
+
+ /*
+ * If *barg->next->aff is set, that means fcc was set from a list
+ * during some previous builder call. If the current Lcc line
+ * contains the old expansion as a prefix, then we should leave
+ * things as they are. In order to decide that we look at a hash
+ * value computed from the strings.
+ * Pt will point to headents[Fcc].bldr_private
+ */
+ pt = NULL;
+ if(barg && barg->next && barg->next->aff)
+ pt = (PrivateTop *)(*barg->next->aff);
+
+ if(pt && (af=pt->affector) && af->who == BP_Lcc){
+ int len;
+
+ len = strlen(lcc);
+ if(len >= af->cksumlen){
+ int save;
+
+ save = lcc[af->cksumlen];
+ lcc[af->cksumlen] = '\0';
+ csum = line_hash(lcc);
+ lcc[af->cksumlen] = save;
+ }
+ else
+ csum = af->cksumval + 1; /* something not equal to cksumval */
+ }
+
+ if(!pt ||
+ !pt->affector ||
+ pt->affector->who != BP_Lcc ||
+ (pt->affector->who == BP_Lcc && csum != pt->affector->cksumval)){
+
+ /* replace fcc value */
+ if(barg->next->tptr)
+ fs_give((void **)&barg->next->tptr);
+
+ barg->next->tptr = fcc_local;
+
+ if(barg->next->aff){
+ if(!pt){
+ *barg->next->aff = (void *)fs_get(sizeof(PrivateTop));
+ pt = (PrivateTop *)(*barg->next->aff);
+ memset((void *)pt, 0, sizeof(PrivateTop));
+ }
+
+ if(no_repo){
+ if(!pt->affector)
+ pt->affector =
+ (PrivateAffector *)fs_get(sizeof(PrivateAffector));
+
+ af = pt->affector;
+ af->who = BP_Lcc;
+ af->cksumlen = strlen(((full_lcc && *full_lcc)
+ ? *full_lcc : ""));
+ af->cksumval = line_hash(((full_lcc && *full_lcc)
+ ? *full_lcc : ""));
+ }
+ else{
+ /*
+ * If result is reproducible, we don't keep track here.
+ */
+ if(pt->affector)
+ fs_give((void **)&pt->affector);
+ }
+ }
+ }
+ else
+ fs_give((void **)&fcc_local); /* unused in this case */
+ }
+
+
+ if(error != NULL && *error == NULL)
+ *error = cpystr("");
+
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ flush_status_messages(0);
+ return(ret_val);
+}
+
+
+/*----------------------------------------------------------------------
+ Verify and canonicalize news groups names.
+ Called from the message composer
+
+Args: given_group -- List of groups typed by user
+ expanded_group -- pointer to point to expanded list, which will be
+ allocated here and freed in caller. If this is
+ NULL, don't attempt to validate.
+ error -- pointer to store error message
+ fcc -- pointer to point to fcc, which will be
+ allocated here and freed in caller
+
+Returns: 0 if all is OK
+ -1 if addresses weren't valid
+
+Test the given list of newstroups against those recognized by our nntp
+servers. Testing by actually trying to open the list is much cheaper, both
+in bandwidth and memory, than yanking the whole list across the wire.
+ ----*/
+int
+news_build(char *given_group, char **expanded_group, char **error, BUILDER_ARG *fcc, int *mangled)
+{
+ int rv;
+ char *fccptr = NULL;
+
+ if(fcc && fcc->tptr)
+ fccptr = cpystr(fcc->tptr);
+
+ clear_cursor_pos();
+
+ rv = news_grouper(given_group, expanded_group, error, &fccptr, news_build_busy);
+
+ /* assign any new fcc to the BUILDER_ARG */
+ if(fccptr){
+ if(fcc){
+ /* it changed */
+ if(fcc->tptr && strcmp(fcc->tptr, fccptr)){
+ fs_give((void **) &fcc->tptr);
+ fcc->tptr = fccptr;
+ fccptr = NULL;
+ }
+ }
+
+ if(fccptr)
+ fs_give((void **) &fccptr);
+ }
+
+ /* deal with any busy indicator */
+ if(news_busy_cue){
+ news_busy_cue = 0;
+ cancel_busy_cue(0);
+ mark_status_dirty();
+ display_message('x');
+ if(mangled)
+ *mangled |= BUILDER_MESSAGE_DISPLAYED;
+ }
+
+
+ return(rv);
+}
+
+
+void
+news_build_busy(void)
+{
+ news_busy_cue = busy_cue("Validating newsgroup(s)", NULL, 0);
+}
+
+
+#if defined(DOS) || defined(OS2)
+
+/*----------------------------------------------------------------------
+ Verify that the necessary pieces are around to allow for
+ message sending under DOS
+
+Args: strict -- tells us if a remote stream is required before
+ sending is permitted.
+
+The idea is to make sure pine knows enough to put together a valid
+from line. The things we MUST know are a user-id, user-domain and
+smtp server to dump the message off on. Typically these are
+provided in pine's configuration file, but if not, the user is
+queried here.
+ ----*/
+int
+dos_valid_from()
+{
+ char prompt[100], answer[80];
+ int rc, i, flags;
+ HelpType help;
+
+ /*
+ * query for user name portion of address, use IMAP login
+ * name as default
+ */
+ if(!ps_global->VAR_USER_ID || ps_global->VAR_USER_ID[0] == '\0'){
+ NETMBX mb;
+ int no_prompt_user_id = 0;
+
+ if(ps_global->mail_stream && ps_global->mail_stream->mailbox
+ && mail_valid_net_parse(ps_global->mail_stream->mailbox, &mb)
+ && *mb.user){
+ strncpy(answer, mb.user, sizeof(answer)-1);
+ answer[sizeof(answer)-1] = '\0';
+ }
+ else if(F_ON(F_QUELL_USER_ID_PROMPT, ps_global)){
+ /* no user-id prompting if set */
+ no_prompt_user_id = 1;
+ rc = 0;
+ if(!ps_global->mail_stream)
+ do_broach_folder(ps_global->inbox_name,
+ ps_global->context_list, NULL, DB_INBOXWOCNTXT);
+ if(ps_global->mail_stream && ps_global->mail_stream->mailbox
+ && mail_valid_net_parse(ps_global->mail_stream->mailbox, &mb)
+ && *mb.user){
+ strncpy(answer, mb.user, sizeof(answer)-1);
+ answer[sizeof(answer)-1] = '\0';
+ }
+ else
+ answer[0] = '\0';
+ }
+ else
+ answer[0] = '\0';
+
+ if(F_ON(F_QUELL_USER_ID_PROMPT, ps_global) && answer[0]){
+ /* No prompt, just assume mailbox login is user-id */
+ no_prompt_user_id = 1;
+ rc = 0;
+ }
+
+ snprintf(prompt,sizeof(prompt),_("User-id for From address : "));
+ prompt[sizeof(prompt)-1] = '\0';
+
+ help = NO_HELP;
+ while(!no_prompt_user_id) {
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(answer,-FOOTER_ROWS(ps_global),0,
+ sizeof(answer),prompt,NULL,help,&flags);
+ if(rc == 2)
+ continue;
+
+ if(rc == 3){
+ help = (help == NO_HELP) ? h_sticky_user_id : NO_HELP;
+ continue;
+ }
+
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 1 || (rc == 0 && !answer[0])) {
+ q_status_message(SM_ORDER, 3, 4,
+ _("Send cancelled (User-id must be provided before sending)"));
+ return(0);
+ }
+
+ /* save the name */
+ snprintf(prompt, sizeof(prompt), _("Preserve %.*s as \"user-id\" in PINERC"),
+ sizeof(prompt)-50, answer);
+ prompt[sizeof(prompt)-1] = '\0';
+ if(ps_global->blank_user_id
+ && !no_prompt_user_id
+ && want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){
+ set_variable(V_USER_ID, answer, 1, 1, Main);
+ }
+ else{
+ fs_give((void **)&(ps_global->VAR_USER_ID));
+ ps_global->VAR_USER_ID = cpystr(answer);
+ }
+ }
+
+ /* query for personal name */
+ if(!ps_global->VAR_PERSONAL_NAME || ps_global->VAR_PERSONAL_NAME[0]=='\0'
+ && F_OFF(F_QUELL_PERSONAL_NAME_PROMPT, ps_global)){
+ answer[0] = '\0';
+ snprintf(prompt, sizeof(prompt), _("Personal name for From address : "));
+ prompt[sizeof(prompt)-1] = '\0';
+
+ help = NO_HELP;
+ while(1) {
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(answer,-FOOTER_ROWS(ps_global),0,
+ sizeof(answer),prompt,NULL,help,&flags);
+ if(rc == 2)
+ continue;
+
+ if(rc == 3){
+ help = (help == NO_HELP) ? h_sticky_personal_name : NO_HELP;
+ continue;
+ }
+
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 0 && answer){ /* save the name */
+ snprintf(prompt, sizeof(prompt), _("Preserve %.*s as \"personal-name\" in PINERC"),
+ sizeof(prompt)-50, answer);
+ prompt[sizeof(prompt)-1] = '\0';
+ if(ps_global->blank_personal_name
+ && want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){
+ set_variable(V_PERSONAL_NAME, answer, 1, 1, Main);
+ }
+ else{
+ fs_give((void **)&(ps_global->VAR_PERSONAL_NAME));
+ ps_global->VAR_PERSONAL_NAME = cpystr(answer);
+ }
+ }
+ }
+
+ /*
+ * query for host/domain portion of address, using IMAP
+ * host as default
+ */
+ if(ps_global->blank_user_domain
+ || ps_global->maildomain == ps_global->localdomain
+ || ps_global->maildomain == ps_global->hostname){
+ if(ps_global->inbox_name[0] == '{'){
+ for(i=0;
+ i < sizeof(answer)-1 && ps_global->inbox_name[i+1] != '}'; i++)
+ answer[i] = ps_global->inbox_name[i+1];
+
+ answer[i] = '\0';
+ }
+ else
+ answer[0] = '\0';
+
+ snprintf(prompt,sizeof(prompt),_("Host/domain for From address : "));
+ prompt[sizeof(prompt)-1] = '\0';
+
+ help = NO_HELP;
+ while(1) {
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(answer,-FOOTER_ROWS(ps_global),0,
+ sizeof(answer),prompt,NULL,help,&flags);
+ if(rc == 2)
+ continue;
+
+ if(rc == 3){
+ help = (help == NO_HELP) ? h_sticky_domain : NO_HELP;
+ continue;
+ }
+
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 1 || (rc == 0 && !answer[0])) {
+ q_status_message(SM_ORDER, 3, 4,
+ _("Send cancelled (Host/domain name must be provided before sending)"));
+ return(0);
+ }
+
+ /* save the name */
+ snprintf(prompt, sizeof(prompt), _("Preserve %.*s as \"user-domain\" in PINERC"),
+ sizeof(prompt)-50, answer);
+ prompt[sizeof(prompt)-1] = '\0';
+ if(!ps_global->userdomain && !ps_global->blank_user_domain
+ && want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){
+ set_variable(V_USER_DOMAIN, answer, 1, 1, Main);
+ fs_give((void **)&(ps_global->maildomain)); /* blast old val */
+ ps_global->userdomain = cpystr(answer);
+ ps_global->maildomain = ps_global->userdomain;
+ }
+ else{
+ fs_give((void **)&(ps_global->maildomain));
+ ps_global->userdomain = cpystr(answer);
+ ps_global->maildomain = ps_global->userdomain;
+ }
+ }
+
+ /* check for smtp server */
+ if(!ps_global->VAR_SMTP_SERVER ||
+ !ps_global->VAR_SMTP_SERVER[0] ||
+ !ps_global->VAR_SMTP_SERVER[0][0]){
+ char **list;
+
+ if(ps_global->inbox_name[0] == '{'){
+ for(i=0;
+ i < sizeof(answer)-1 && ps_global->inbox_name[i+1] != '}'; i++)
+ answer[i] = ps_global->inbox_name[i+1];
+
+ answer[i] = '\0';
+ }
+ else
+ answer[0] = '\0';
+
+ snprintf(prompt,sizeof(prompt),_("SMTP server to forward message : "));
+ prompt[sizeof(prompt)-1] = '\0';
+
+ help = NO_HELP;
+ while(1) {
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(answer,-FOOTER_ROWS(ps_global),0,
+ sizeof(answer),prompt,NULL,help,&flags);
+ if(rc == 2)
+ continue;
+
+ if(rc == 3){
+ help = (help == NO_HELP) ? h_sticky_smtp : NO_HELP;
+ continue;
+ }
+
+ if(rc != 4)
+ break;
+ }
+
+ if(rc == 1 || (rc == 0 && answer[0] == '\0')) {
+ q_status_message(SM_ORDER, 3, 4,
+ _("Send cancelled (SMTP server must be provided before sending)"));
+ return(0);
+ }
+
+ /* save the name */
+ list = (char **) fs_get(2 * sizeof(char *));
+ list[0] = cpystr(answer);
+ list[1] = NULL;
+ set_variable_list(V_SMTP_SERVER, list, TRUE, Main);
+ fs_give((void *)&list[0]);
+ fs_give((void *)list);
+ }
+
+ return(1);
+}
+
+#endif /* defined(DOS) || defined(OS2) */
diff --git a/alpine/send.h b/alpine/send.h
new file mode 100644
index 00000000..4d216dd5
--- /dev/null
+++ b/alpine/send.h
@@ -0,0 +1,53 @@
+/*
+ * $Id: send.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_SEND_INCLUDED
+#define PINE_SEND_INCLUDED
+
+
+#include "../pith/send.h"
+#include "../pith/reply.h"
+#include "../pith/state.h"
+#include "../pith/pattern.h"
+#include "../pith/filter.h"
+
+
+#define SS_PROMPTFORTO 0x01 /* Simple_send: prompt for To */
+#define SS_NULLRP 0x02 /* Null Return-path */
+
+
+/* pine_send flags */
+#define PS_STICKY_FCC 0x01
+#define PS_STICKY_TO 0x02
+
+
+/* exported protoypes */
+void compose_screen(struct pine *);
+void alt_compose_screen(struct pine *);
+void compose_mail(char *, char *, ACTION_S *, PATMT *, gf_io_t);
+int pine_simple_send(ENVELOPE *, BODY **, ACTION_S *, char *, char *, char **, int);
+void pine_send(ENVELOPE *, BODY **, char *, ACTION_S *, char *, REPLY_S *,
+ REDRAFT_POS_S *, char *, PINEFIELD *, int);
+int upload_msg_to_pico(char *, size_t, long *);
+void phone_home(char *);
+void create_message_body(BODY **, PATMT *, int);
+char *pine_send_status(int, char *, char *, size_t, int *);
+int confirm_daemon_send(void);
+int build_address(char *, char **,char **, BUILDER_ARG *, int *);
+void free_attachment_list(PATMT **);
+
+
+#endif /* PINE_SEND_INCLUDED */
diff --git a/alpine/setup.c b/alpine/setup.c
new file mode 100644
index 00000000..3ac90f21
--- /dev/null
+++ b/alpine/setup.c
@@ -0,0 +1,1131 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: setup.c 918 2008-01-23 19:39:38Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "setup.h"
+#include "keymenu.h"
+#include "status.h"
+#include "confscroll.h"
+#include "colorconf.h"
+#include "reply.h"
+#include "radio.h"
+#include "listsel.h"
+#include "folder.h"
+#include "mailcmd.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/util.h"
+#include "../pith/sort.h"
+#include "../pith/folder.h"
+#include "../pith/list.h"
+#include "../pith/icache.h"
+
+
+/*
+ * Internal prototypes
+ */
+int inbox_path_text_tool(struct pine *, int, CONF_S **, unsigned);
+int incoming_monitoring_list_tool(struct pine *, int, CONF_S **, unsigned);
+int stayopen_list_tool(struct pine *, int, CONF_S **, unsigned);
+char **adjust_list_of_monitored_incoming(CONTEXT_S *, EditWhich, int);
+int to_charsets_text_tool(struct pine *, int, CONF_S **, unsigned);
+
+
+#define CONFIG_SCREEN_TITLE _("SETUP CONFIGURATION")
+#define CONFIG_SCREEN_TITLE_EXC _("SETUP CONFIGURATION EXCEPTIONS")
+
+
+
+/*----------------------------------------------------------------------
+ Present pinerc data for manipulation
+
+ Args: None
+
+ Result: help edit certain pinerc fields.
+ ---*/
+void
+option_screen(struct pine *ps, int edit_exceptions)
+{
+ char tmp[MAXPATH+1], *pval, **lval;
+ int i, j, ln = 0, readonly_warning = 0;
+ struct variable *vtmp;
+ CONF_S *ctmpa = NULL, *ctmpb, *first_line = NULL;
+ FEATURE_S *feature;
+ PINERC_S *prc = NULL;
+ SAVED_CONFIG_S *vsave;
+ OPT_SCREEN_S screen;
+ int expose_hidden_config, add_hidden_vars_title = 0;
+
+ dprint((3, "-- option_screen --\n"));
+
+ expose_hidden_config = F_ON(F_EXPOSE_HIDDEN_CONFIG, ps_global);
+ treat_color_vars_as_text = expose_hidden_config;
+
+ ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;
+
+ if(ps->restricted)
+ readonly_warning = 1;
+ else{
+ switch(ew){
+ case Main:
+ prc = ps->prc;
+ break;
+ case Post:
+ prc = ps->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ readonly_warning = prc ? prc->readonly : 1;
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ treat_color_vars_as_text = 0;
+ return;
+ }
+ }
+
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ mailcap_free(); /* free resources we won't be using for a while */
+
+ if(ps->fix_fixed_warning)
+ offer_to_fix_pinerc(ps);
+
+ /*
+ * First, find longest variable name
+ */
+ for(vtmp = ps->vars; vtmp->name; vtmp++){
+ if(exclude_config_var(ps, vtmp, expose_hidden_config))
+ continue;
+
+ if((i = utf8_width(pretty_var_name(vtmp->name))) > ln)
+ ln = i;
+ }
+
+ dprint((9, "initialize config list\n"));
+
+ /*
+ * Next, allocate and initialize config line list...
+ */
+ for(vtmp = ps->vars; vtmp->name; vtmp++){
+ /*
+ * INCOMING_FOLDERS is currently the first of the normally
+ * hidden variables. Should probably invent a more robust way
+ * to keep this up to date.
+ */
+ if(expose_hidden_config && vtmp == &ps->vars[V_INCOMING_FOLDERS])
+ add_hidden_vars_title = 1;
+
+ if(exclude_config_var(ps, vtmp, expose_hidden_config))
+ continue;
+
+ if(add_hidden_vars_title){
+
+ add_hidden_vars_title = 0;
+
+ new_confline(&ctmpa); /* Blank line */
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->help = NO_HELP;
+ ctmpa->valoffset = 2;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr("--- [ Normally hidden configuration options ] ---");
+
+ new_confline(&ctmpa); /* Blank line */
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+ }
+
+ if(vtmp->is_list)
+ lval = LVAL(vtmp, ew);
+ else
+ pval = PVAL(vtmp, ew);
+
+ new_confline(&ctmpa)->var = vtmp;
+ if(!first_line)
+ first_line = ctmpa;
+
+ ctmpa->valoffset = ln + 3;
+ if(vtmp->is_list)
+ ctmpa->keymenu = &config_text_wshuf_keymenu;
+ else
+ ctmpa->keymenu = &config_text_keymenu;
+
+ ctmpa->help = config_help(vtmp - ps->vars, 0);
+ ctmpa->tool = text_tool;
+
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.100w =", ln, pretty_var_name(vtmp->name));
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmpa->varname = cpystr(tmp);
+ ctmpa->varnamep = ctmpb = ctmpa;
+ ctmpa->flags |= CF_STARTITEM;
+ if(vtmp == &ps->vars[V_FEATURE_LIST]){ /* special checkbox case */
+ char *this_sect, *new_sect;
+
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->tool = NULL;
+
+ /* put a nice delimiter before list */
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = checkbox_tool;
+ ctmpa->valoffset = feature_indent();
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr("Set Feature Name");
+
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = checkbox_tool;
+ ctmpa->valoffset = feature_indent();
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr("--- ----------------------");
+
+ for(i = 0, this_sect = NULL; (feature = feature_list(i)); i++)
+ if((new_sect = feature_list_section(feature)) &&
+ (strcmp(new_sect, HIDDEN_PREF) != 0)){
+ if(this_sect != new_sect){
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = checkbox_tool;
+ ctmpa->valoffset = 2;
+ ctmpa->flags |= (CF_NOSELECT | CF_STARTITEM);
+ snprintf(tmp, sizeof(tmp), "[ %s ]", this_sect = new_sect);
+ tmp[sizeof(tmp)-1] = '\0';
+ ctmpa->value = cpystr(tmp);
+ }
+
+ new_confline(&ctmpa)->var = vtmp;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->help = config_help(vtmp-ps->vars,
+ feature->id);
+ ctmpa->tool = checkbox_tool;
+ ctmpa->valoffset = feature_indent();
+ ctmpa->varmem = i;
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+ }
+ else if(standard_radio_var(ps, vtmp)){
+ standard_radio_setup(ps, &ctmpa, vtmp, NULL);
+ }
+ else if(vtmp == &ps->vars[V_SORT_KEY]){ /* radio case */
+ SortOrder def_sort;
+ int def_sort_rev;
+
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->keymenu = &config_radiobutton_keymenu;
+ ctmpa->tool = NULL;
+
+ /* put a nice delimiter before list */
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_radiobutton_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = radiobutton_tool;
+ ctmpa->valoffset = 12;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr("Set Sort Options");
+
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_radiobutton_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = radiobutton_tool;
+ ctmpa->valoffset = 12;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr("--- ----------------------");
+
+ decode_sort(pval, &def_sort, &def_sort_rev);
+
+ for(j = 0; j < 2; j++){
+ for(i = 0; ps->sort_types[i] != EndofList; i++){
+ new_confline(&ctmpa)->var = vtmp;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_radiobutton_keymenu;
+ ctmpa->help = config_help(vtmp - ps->vars, 0);
+ ctmpa->tool = radiobutton_tool;
+ ctmpa->valoffset = 12;
+ ctmpa->varmem = i + (j * EndofList);
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+ }
+ }
+ else if(vtmp == &ps->vars[V_USE_ONLY_DOMAIN_NAME]){ /* yesno case */
+ ctmpa->keymenu = &config_yesno_keymenu;
+ ctmpa->tool = yesno_tool;
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+ else if(vtmp == &ps->vars[V_LITERAL_SIG]){
+ ctmpa->tool = litsig_text_tool;
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+ else if(vtmp == &ps->vars[V_INBOX_PATH]){
+ ctmpa->tool = inbox_path_text_tool;
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+ else if(vtmp == &ps->vars[V_POST_CHAR_SET]
+#ifndef _WINDOWS
+ || vtmp == &ps->vars[V_CHAR_SET]
+ || vtmp == &ps->vars[V_KEY_CHAR_SET]
+#endif /* !_WINDOWS */
+ || vtmp == &ps->vars[V_UNK_CHAR_SET]){
+ ctmpa->keymenu = &config_text_to_charsets_keymenu;
+ ctmpa->tool = to_charsets_text_tool;
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+ else if(vtmp->is_list){
+ int (*t_tool)(struct pine *, int, CONF_S **, unsigned);
+ struct key_menu *km;
+
+ t_tool = NULL;
+ km = NULL;
+ if(vtmp == &ps->vars[V_INCCHECKLIST]){
+ t_tool = incoming_monitoring_list_tool;
+ km = &config_text_keymenu;
+ }
+ else if(vtmp == &ps->vars[V_PERMLOCKED]){
+ t_tool = stayopen_list_tool;
+ km = &config_text_wshufandfldr_keymenu;
+ }
+
+ if(lval){
+ for(i = 0; lval[i]; i++){
+ if(i)
+ (void)new_confline(&ctmpa);
+
+ ctmpa->var = vtmp;
+ ctmpa->varmem = i;
+ ctmpa->valoffset = ln + 3;
+ ctmpa->value = pretty_value(ps, ctmpa);
+ ctmpa->keymenu = km ? km : &config_text_wshuf_keymenu;
+ ctmpa->help = config_help(vtmp - ps->vars, 0);
+ ctmpa->tool = t_tool ? t_tool : text_tool;
+ ctmpa->varnamep = ctmpb;
+ }
+ }
+ else{
+ ctmpa->varmem = 0;
+ ctmpa->value = pretty_value(ps, ctmpa);
+ ctmpa->tool = t_tool ? t_tool : text_tool;
+ ctmpa->keymenu = km ? km : &config_text_wshuf_keymenu;
+ }
+ }
+ else{
+ if(vtmp == &ps->vars[V_FILLCOL]
+ || vtmp == &ps->vars[V_QUOTE_SUPPRESSION]
+ || vtmp == &ps->vars[V_OVERLAP]
+ || vtmp == &ps->vars[V_MAXREMSTREAM]
+ || vtmp == &ps->vars[V_MARGIN]
+ || vtmp == &ps->vars[V_DEADLETS]
+ || vtmp == &ps->vars[V_NMW_WIDTH]
+ || vtmp == &ps->vars[V_STATUS_MSG_DELAY]
+ || vtmp == &ps->vars[V_ACTIVE_MSG_INTERVAL]
+ || vtmp == &ps->vars[V_MAILCHECK]
+ || vtmp == &ps->vars[V_MAILCHECKNONCURR]
+ || vtmp == &ps->vars[V_MAILDROPCHECK]
+ || vtmp == &ps->vars[V_NNTPRANGE]
+ || vtmp == &ps->vars[V_TCPOPENTIMEO]
+ || vtmp == &ps->vars[V_TCPREADWARNTIMEO]
+ || vtmp == &ps->vars[V_TCPWRITEWARNTIMEO]
+ || vtmp == &ps->vars[V_TCPQUERYTIMEO]
+ || vtmp == &ps->vars[V_RSHOPENTIMEO]
+ || vtmp == &ps->vars[V_SSHOPENTIMEO]
+ || vtmp == &ps->vars[V_INCCHECKTIMEO]
+ || vtmp == &ps->vars[V_INCCHECKINTERVAL]
+ || vtmp == &ps->vars[V_INC2NDCHECKINTERVAL]
+ || vtmp == &ps->vars[V_USERINPUTTIMEO]
+ || vtmp == &ps->vars[V_REMOTE_ABOOK_VALIDITY]
+ || vtmp == &ps->vars[V_REMOTE_ABOOK_HISTORY])
+ ctmpa->flags |= CF_NUMBER;
+
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+ }
+
+ dprint((9, "add hidden features\n"));
+
+ /* add the hidden features */
+ if(expose_hidden_config){
+ char *new_sect;
+
+ new_confline(&ctmpa); /* Blank line */
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->help = NO_HELP;
+ ctmpa->valoffset = 2;
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr("--- [ Normally hidden configuration features ] ---");
+
+ new_confline(&ctmpa); /* Blank line */
+ ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
+
+ vtmp = &ps->vars[V_FEATURE_LIST];
+
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->tool = NULL;
+
+ /* put a nice delimiter before list */
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = checkbox_tool;
+ ctmpa->valoffset = feature_indent();
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr("Set Feature Name");
+
+ new_confline(&ctmpa)->var = NULL;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->help = NO_HELP;
+ ctmpa->tool = checkbox_tool;
+ ctmpa->valoffset = feature_indent();
+ ctmpa->flags |= CF_NOSELECT;
+ ctmpa->value = cpystr("--- ----------------------");
+
+ for(i = 0; (feature = feature_list(i)); i++)
+ if((new_sect = feature_list_section(feature)) &&
+ (strcmp(new_sect, HIDDEN_PREF) == 0)){
+
+ new_confline(&ctmpa)->var = vtmp;
+ ctmpa->varnamep = ctmpb;
+ ctmpa->keymenu = &config_checkbox_keymenu;
+ ctmpa->help = config_help(vtmp-ps->vars,
+ feature->id);
+ ctmpa->tool = checkbox_tool;
+ ctmpa->valoffset = feature_indent();
+ ctmpa->varmem = i;
+ ctmpa->value = pretty_value(ps, ctmpa);
+ }
+ }
+
+ vsave = save_config_vars(ps, expose_hidden_config);
+ first_line = first_sel_confline(first_line);
+
+ memset(&screen, 0, sizeof(screen));
+ screen.ro_warning = readonly_warning;
+ /* TRANSLATORS: Print something1 using something2.
+ "configuration" is something1 */
+ switch(conf_scroll_screen(ps, &screen, first_line,
+ edit_exceptions ? CONFIG_SCREEN_TITLE_EXC
+ : CONFIG_SCREEN_TITLE,
+ _("configuration"), 0)){
+ case 0:
+ break;
+
+ case 1:
+ write_pinerc(ps, ew, WRP_NONE);
+ break;
+
+ case 10:
+ revert_to_saved_config(ps, vsave, expose_hidden_config);
+ if(prc)
+ prc->outstanding_pinerc_changes = 0;
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER,7,10,
+ "conf_scroll_screen bad ret, not supposed to happen");
+ break;
+ }
+
+ pval = PVAL(&ps->vars[V_SORT_KEY], ew);
+ if(vsave[V_SORT_KEY].saved_user_val.p && pval
+ && strcmp(vsave[V_SORT_KEY].saved_user_val.p, pval)){
+ if(!mn_get_mansort(ps_global->msgmap)){
+ clear_index_cache(ps_global->mail_stream, 0);
+ reset_sort_order(SRT_VRB);
+ }
+ }
+
+ treat_color_vars_as_text = 0;
+ free_saved_config(ps, &vsave, expose_hidden_config);
+#ifdef _WINDOWS
+ mswin_set_quit_confirm (F_OFF(F_QUIT_WO_CONFIRM, ps_global));
+#endif
+}
+
+
+int
+litsig_text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ char **apval;
+ int rv = 0;
+
+ if(cmd != MC_EXIT && fixed_var((*cl)->var, NULL, NULL))
+ return(rv);
+
+ apval = APVAL((*cl)->var, ew);
+
+ switch(cmd){
+ case MC_ADD:
+ case MC_EDIT :
+ if(apval){
+ char *input = NULL, *result = NULL, *err = NULL, *cstring_version;
+ char *olddefval = NULL, *start_with;
+ size_t len;
+
+ if(!*apval && (*cl)->var->current_val.p &&
+ (*cl)->var->current_val.p[0]){
+ if(!strncmp((*cl)->var->current_val.p,
+ DSTRING,
+ (len=strlen(DSTRING)))){
+ /* strip DSTRING and trailing paren */
+ olddefval = (char *)fs_get(strlen((*cl)->var->current_val.p)+1);
+ strncpy(olddefval, (*cl)->var->current_val.p+len,
+ strlen((*cl)->var->current_val.p)-len-1);
+ olddefval[strlen((*cl)->var->current_val.p)-len-1] = '\0';
+ start_with = olddefval;
+ }
+ else{
+ olddefval = cpystr((*cl)->var->current_val.p);
+ start_with = olddefval;
+ }
+ }
+ else
+ start_with = (*apval) ? *apval : "";
+
+ input = (char *)fs_get((strlen(start_with)+1) * sizeof(char));
+ input[0] = '\0';
+ cstring_to_string(start_with, input);
+ err = signature_edit_lit(input, &result,
+ ((*cl)->var == role_comment_ptr)
+ ? "COMMENT EDITOR"
+ : "SIGNATURE EDITOR",
+ ((*cl)->var == role_comment_ptr)
+ ? h_composer_commentedit
+ : h_composer_sigedit);
+
+ if(!err){
+ if(olddefval && !strcmp(input, result) &&
+ want_to(_("Leave unset and use default "), 'y',
+ 'y', NO_HELP, WT_FLUSH_IN) == 'y'){
+ rv = 0;
+ }
+ else{
+ cstring_version = string_to_cstring(result);
+
+ if(apval && *apval)
+ fs_give((void **)apval);
+
+ if(apval){
+ *apval = cstring_version;
+ cstring_version = NULL;
+ }
+
+ if(cstring_version)
+ fs_give((void **)&cstring_version);
+
+ rv = 1;
+ }
+ }
+ else
+ rv = 0;
+
+ if(err){
+ q_status_message1(SM_ORDER, 3, 5, "%s", err);
+ fs_give((void **)&err);
+ }
+
+ if(result)
+ fs_give((void **)&result);
+ if(olddefval)
+ fs_give((void **)&olddefval);
+ if(input)
+ fs_give((void **)&input);
+ }
+
+ ps->mangled_screen = 1;
+ break;
+
+ default:
+ rv = text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ /*
+ * At this point, if changes occurred, var->user_val.X is set.
+ * So, fix the current_val, and handle special cases...
+ *
+ * NOTE: we don't worry about the "fixed variable" case here, because
+ * editing such vars should have been prevented above...
+ */
+ if(rv == 1){
+ /*
+ * Now go and set the current_val based on user_val changes
+ * above. Turn off command line settings...
+ */
+ set_current_val((*cl)->var, TRUE, FALSE);
+
+ if((*cl)->value)
+ fs_give((void **)&(*cl)->value);
+
+ (*cl)->value = pretty_value(ps, *cl);
+
+ exception_override_warning((*cl)->var);
+
+ /*
+ * The value of literal sig can affect whether signature file is
+ * used or not. So it affects what we display for sig file variable.
+ */
+ if((*cl)->next && (*cl)->next->var == &ps->vars[V_SIGNATURE_FILE]){
+ if((*cl)->next->value)
+ fs_give((void **)&(*cl)->next->value);
+
+ (*cl)->next->value = pretty_value(ps, (*cl)->next);
+ }
+ }
+
+ return(rv);
+}
+
+
+int
+inbox_path_text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ char **apval;
+ int rv = 0;
+ char new_inbox_path[2*MAXFOLDER+1];
+ char *def = NULL;
+ CONTEXT_S *cntxt;
+
+ if(cmd != MC_EXIT && fixed_var((*cl)->var, NULL, NULL))
+ return(rv);
+
+ apval = APVAL((*cl)->var, ew);
+
+ switch(cmd){
+ case MC_ADD:
+ case MC_EDIT:
+ cntxt = ps->context_list;
+ if(cmd == MC_EDIT && (*cl)->var){
+ if(ew == Post && (*cl)->var->post_user_val.p)
+ def = (*cl)->var->post_user_val.p;
+ else if(ew == Main && (*cl)->var->main_user_val.p)
+ def = (*cl)->var->main_user_val.p;
+ else if((*cl)->var->current_val.p)
+ def = (*cl)->var->current_val.p;
+ }
+
+ rv = add_new_folder(cntxt, ew, V_INBOX_PATH, new_inbox_path,
+ sizeof(new_inbox_path), NULL, def);
+ rv = rv ? 1 : 0;
+
+ ps->mangled_screen = 1;
+ break;
+
+ default:
+ rv = text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ /*
+ * This is just like the end of text_tool.
+ */
+ if(rv == 1){
+ /*
+ * Now go and set the current_val based on user_val changes
+ * above. Turn off command line settings...
+ */
+ set_current_val((*cl)->var, TRUE, FALSE);
+ fix_side_effects(ps, (*cl)->var, 0);
+
+ if((*cl)->value)
+ fs_give((void **) &(*cl)->value);
+
+ (*cl)->value = pretty_value(ps, *cl);
+
+ exception_override_warning((*cl)->var);
+ }
+
+ return(rv);
+}
+
+
+int
+incoming_monitoring_list_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+ CONTEXT_S *cntxt;
+ char **the_list;
+ CONF_S *ctmp;
+ char ***alval;
+ OPT_SCREEN_S *saved_screen;
+
+ if(cmd != MC_EXIT && fixed_var((*cl)->var, NULL, NULL))
+ return(rv);
+
+ switch(cmd){
+ case MC_ADD:
+ case MC_EDIT:
+ cntxt = ps->context_list;
+ if(!(cntxt && cntxt->use & CNTXT_INCMNG)){
+ q_status_message1(SM_ORDER, 3, 3, _("Turn on incoming folders with Config feature \"%s\""), pretty_feature_name(feature_list_name(F_ENABLE_INCOMING), -1));
+ return(rv);
+ }
+
+ saved_screen = opt_screen;
+
+ the_list = adjust_list_of_monitored_incoming(cntxt, ew, V_INCCHECKLIST);
+
+ /* adjust top if it might be necessary */
+ for(ctmp = (*cl)->varnamep;
+ ctmp && ctmp->varnamep == (*cl)->varnamep;
+ ctmp = next_confline(ctmp))
+ if(ctmp == opt_screen->top_line)
+ opt_screen->top_line = (*cl)->varnamep;
+
+ if(the_list){
+ alval = ALVAL((*cl)->var, ew);
+ free_list_array(alval);
+ if(!*the_list){
+ q_status_message(SM_ORDER, 0, 3, _("Using default, monitor all incoming folders"));
+ if(alval){
+ /*
+ * Remove config lines except for first one.
+ */
+ *cl = (*cl)->varnamep;
+ while((*cl)->next && (*cl)->next->varnamep == (*cl)->varnamep)
+ snip_confline(&(*cl)->next);
+ }
+ }
+ else
+ config_add_list(ps, cl, the_list, NULL, 0);
+
+ /* only have to free the top-level pointer */
+ fs_give((void **) &the_list);
+ rv = 1;
+ }
+ else{
+ if(LVAL((*cl)->var, ew))
+ q_status_message(SM_ORDER, 0, 3, _("List is unchanged"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Using default, monitor all incoming folders"));
+ }
+
+ opt_screen = saved_screen;
+ ps->mangled_screen = 1;
+ break;
+
+ default:
+ rv = text_tool(ps, cmd, cl, flags);
+ /* if we deleted last one, reverts to default */
+ if(cmd == MC_DELETE && rv == 1 && (*cl)->varmem == 0
+ && (!(*cl)->next || (*cl)->next->varnamep != (*cl)))
+ q_status_message(SM_ORDER, 0, 3, _("Using default, monitor all incoming folders"));
+
+ break;
+ }
+
+ /*
+ * This is just like the end of text_tool.
+ */
+ if(rv == 1){
+ /*
+ * Now go and set the current_val based on user_val changes
+ * above. Turn off command line settings...
+ */
+ set_current_val((*cl)->var, TRUE, FALSE);
+ fix_side_effects(ps, (*cl)->var, 0);
+
+ if((*cl)->value)
+ fs_give((void **) &(*cl)->value);
+
+ (*cl)->value = pretty_value(ps, *cl);
+
+ exception_override_warning((*cl)->var);
+ }
+
+ return(rv);
+}
+
+
+char **
+adjust_list_of_monitored_incoming(CONTEXT_S *cntxt, EditWhich which, int varnum)
+{
+ LIST_SEL_S *listhead, *p, *ls;
+ int i, cnt, ftotal;
+ long width;
+ FOLDER_S *f;
+ char **the_list = NULL, buf[1000];
+
+ if(!(cntxt && cntxt->use & CNTXT_INCMNG))
+ return(the_list);
+
+ p = listhead = NULL;
+
+ /* this width is determined by select_from_list_screen() */
+ width = ps_global->ttyo->screen_cols - 4;
+
+ /*
+ * Put together a list of folders to select from.
+ * We could choose to use the list associated with
+ * the config we're editing, and that may be correct.
+ * However, we think most users will expect the list
+ * to be the list that is in use. In any case, these
+ * are almost always the same.
+ */
+
+ ftotal = folder_total(FOLDERS(cntxt));
+
+ for(i = 0; i < ftotal; i++){
+
+ f = folder_entry(i, FOLDERS(cntxt));
+
+ ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(ls, 0, sizeof(*ls));
+
+ if(f && f->nickname){
+ ls->item = cpystr(f->nickname);
+ snprintf(buf, sizeof(buf), "%s (%s)", f->nickname, f->name ? f->name : "?");
+ ls->display_item = cpystr(buf);
+ }
+ else
+ ls->item = cpystr((f && f->name) ? f->name : "?");
+
+ if(f && f->last_unseen_update != LUU_NEVERCHK)
+ ls->selected = 1;
+
+ if(p){
+ p->next = ls;
+ p = p->next;
+ }
+ else{
+ /* add a heading */
+ listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(listhead, 0, sizeof(*listhead));
+ listhead->flags = SFL_NOSELECT;
+ listhead->display_item = cpystr(_("Incoming folders to be monitored"));
+ listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
+ memset(listhead->next, 0, sizeof(*listhead));
+ listhead->next->flags = SFL_NOSELECT;
+ listhead->next->display_item = cpystr(repeat_char(width, '-'));
+
+ listhead->next->next = ls;
+ p = ls;
+ }
+ }
+
+ if(!listhead){
+ q_status_message1(SM_ORDER, 3, 3, _("Turn on incoming folders with Config feature \"%s\""), pretty_feature_name(feature_list_name(F_ENABLE_INCOMING), -1));
+ return(the_list);
+ }
+
+ if(!select_from_list_screen(listhead,
+ SFL_ALLOW_LISTMODE|SFL_STARTIN_LISTMODE|SFL_ONLY_LISTMODE|SFL_CTRLC,
+ _("SELECT FOLDERS TO MONITOR"), _("folders"),
+ h_select_incoming_to_monitor,
+ _("HELP FOR SELECTING FOLDERS"), NULL)){
+
+ for(cnt = 0, p = listhead; p; p = p->next)
+ if(p->selected)
+ cnt++;
+
+ if(cnt >= 0 && cnt <= ftotal){
+ the_list = (char **) fs_get((cnt+1) * sizeof(*the_list));
+ memset(the_list, 0, (cnt+1) * sizeof(*the_list));
+ for(i = 0, p = listhead; p; p = p->next)
+ if(p->selected)
+ the_list[i++] = cpystr(p->item ? p->item : "");
+ }
+ }
+
+ free_list_sel(&listhead);
+
+ return(the_list);
+}
+
+
+int
+stayopen_list_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+ char **newval = NULL;
+ char **ltmp = NULL;
+ char *folder = NULL;
+ void (*prev_screen)(struct pine *) = ps->prev_screen,
+ (*redraw)(void) = ps->redrawer;
+ OPT_SCREEN_S *saved_screen = NULL;
+
+ switch(cmd){
+ case MC_CHOICE:
+ if(fixed_var((*cl)->var, NULL, NULL))
+ break;
+
+ ps->redrawer = NULL;
+ ps->next_screen = SCREEN_FUN_NULL;
+ saved_screen = opt_screen;
+ folder = folder_for_config(FOR_OPTIONSCREEN);
+ removing_leading_and_trailing_white_space(folder);
+ if(folder && *folder){
+ ltmp = (char **) fs_get(2 * sizeof(char *));
+ ltmp[0] = cpystr(folder);
+ ltmp[1] = NULL;
+
+ config_add_list(ps, cl, ltmp, &newval, 0);
+
+ if(ltmp)
+ fs_give((void **) &ltmp);
+
+ rv = 1;
+
+ /* this stuff is from bottom of text_toolit() */
+
+ /*
+ * At this point, if changes occurred, var->user_val.X is set.
+ * So, fix the current_val, and handle special cases...
+ *
+ * NOTE: we don't worry about the "fixed variable" case here, because
+ * editing such vars should have been prevented above...
+ */
+
+ /*
+ * Now go and set the current_val based on user_val changes
+ * above. Turn off command line settings...
+ */
+ set_current_val((*cl)->var, TRUE, FALSE);
+ fix_side_effects(ps, (*cl)->var, 0);
+
+ /*
+ * Delay setting the displayed value until "var.current_val" is set
+ * in case current val get's changed due to a special case above.
+ */
+ if(newval){
+ if(*newval)
+ fs_give((void **) newval);
+
+ *newval = pretty_value(ps, *cl);
+ }
+
+ exception_override_warning((*cl)->var);
+
+ if(folder)
+ fs_give((void **) &folder);
+ }
+ else{
+ ps->next_screen = prev_screen;
+ ps->redrawer = redraw;
+ rv = 0;
+ }
+
+ opt_screen = saved_screen;
+ ps->mangled_screen = 1;
+ break;
+
+ default:
+ rv = text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ return(rv);
+}
+
+
+int
+to_charsets_text_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ int rv = 0;
+ char **newval = NULL;
+ void (*prev_screen)(struct pine *) = ps->prev_screen,
+ (*redraw)(void) = ps->redrawer;
+ OPT_SCREEN_S *saved_screen = NULL;
+ char **apval;
+ char *charset = NULL;
+
+ switch(cmd){
+ case MC_CHOICE:
+ if(fixed_var((*cl)->var, NULL, NULL))
+ break;
+
+ apval = APVAL((*cl)->var, ew);
+
+ if(apval){
+ int cac_flags = CAC_ALL;
+
+ ps->redrawer = NULL;
+ ps->next_screen = SCREEN_FUN_NULL;
+ saved_screen = opt_screen;
+
+ if((*cl)->var == &ps->vars[V_POST_CHAR_SET])
+ cac_flags = CAC_POSTING;
+#ifndef _WINDOWS
+ else if((*cl)->var == &ps->vars[V_CHAR_SET]
+ || (*cl)->var == &ps->vars[V_KEY_CHAR_SET])
+ cac_flags = CAC_DISPLAY;
+#endif /* !_WINDOWS */
+
+ charset = choose_a_charset(cac_flags);
+
+ removing_leading_and_trailing_white_space(charset);
+ if(charset && *charset){
+ rv = 1;
+ if(apval && *apval)
+ fs_give((void **) apval);
+
+ *apval = charset;
+ charset = NULL;
+ newval = &(*cl)->value;
+
+ /* this stuff is from bottom of text_toolit() */
+
+ /*
+ * At this point, if changes occurred, var->user_val.X is set.
+ * So, fix the current_val, and handle special cases...
+ *
+ * NOTE: we don't worry about the "fixed variable" case here, because
+ * editing such vars should have been prevented above...
+ */
+
+ /*
+ * Now go and set the current_val based on user_val changes
+ * above. Turn off command line settings...
+ */
+ set_current_val((*cl)->var, TRUE, FALSE);
+ fix_side_effects(ps, (*cl)->var, 0);
+ if(newval){
+ if(*newval)
+ fs_give((void **) newval);
+
+ *newval = pretty_value(ps, *cl);
+ }
+
+ exception_override_warning((*cl)->var);
+ }
+ else{
+ ps->next_screen = prev_screen;
+ ps->redrawer = redraw;
+ rv = 0;
+ }
+
+ if(charset)
+ fs_give((void **) &charset);
+
+ opt_screen = saved_screen;
+ ps->mangled_screen = 1;
+ }
+ break;
+
+ default:
+ rv = text_tool(ps, cmd, cl, flags);
+ break;
+ }
+
+ return(rv);
+}
+
+
+/*
+ * pretty_var_name - return a pleasantly displayable form
+ * of variable name variable
+ */
+char *
+pretty_var_name(char *varname)
+{
+ struct variable *v;
+ int i, upper = 1;
+ static char vbuf[100];
+
+ vbuf[0] = '\0';
+ v = var_from_name(varname);
+
+ if(!v)
+ return(vbuf);
+
+ if(v->dname && v->dname[0])
+ return(v->dname);
+
+ if(!(v->name && v->name[0]))
+ return(vbuf);
+
+ /* default: uppercase first letters, dashes to spaces */
+ for(i = 0; i < sizeof(vbuf)-1 && v->name[i]; i++)
+ if(upper){
+ vbuf[i] = (islower((unsigned char) v->name[i]))
+ ? toupper((unsigned char) v->name[i])
+ : v->name[i];
+ upper = 0;
+ }
+ else if(v->name[i] == '-'){
+ vbuf[i] = SPACE;
+ upper++;
+ }
+ else
+ vbuf[i] = v->name[i];
+
+ vbuf[i] = '\0';
+ return(vbuf);
+}
+
+
+/*
+ * pretty_feature_name - return a pleasantly displayable form
+ * of feature name variable
+ */
+char *
+pretty_feature_name(char *feat, int width)
+{
+ FEATURE_S *f;
+ int i, upper = 1;
+ static char fbuf[100];
+
+ f = feature_list(feature_list_index(feature_list_id(feat)));
+ if(f && f->dname && f->dname[0])
+ return(f->dname);
+
+ /* default: uppercase first letters, dashes become spaces */
+ for(i = 0; i < sizeof(fbuf)-1 && feat[i]; i++)
+ if(upper){
+ fbuf[i] = (islower((unsigned char) feat[i]))
+ ? toupper((unsigned char) feat[i])
+ : feat[i];
+ upper = 0;
+ }
+ else if(feat[i] == '-'){
+ fbuf[i] = SPACE;
+ upper++;
+ }
+ else
+ fbuf[i] = feat[i];
+
+ fbuf[i] = '\0';
+
+ /* cut off right hand edge if necessary */
+ if(width > 0){
+ char *p, gbuf[100];
+
+ p = short_str(fbuf, gbuf, sizeof(gbuf), width, EndDots);
+
+ if(p != fbuf){
+ strncpy(fbuf, p, sizeof(fbuf)-1);
+ fbuf[sizeof(fbuf)-1] = '\0';
+ }
+ }
+
+ return(fbuf);
+}
diff --git a/alpine/setup.h b/alpine/setup.h
new file mode 100644
index 00000000..b722f8ef
--- /dev/null
+++ b/alpine/setup.h
@@ -0,0 +1,32 @@
+/*
+ * $Id: setup.h 812 2007-11-10 01:00:15Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_SETUP_INCLUDED
+#define PINE_SETUP_INCLUDED
+
+
+#include "confscroll.h"
+#include "../pith/state.h"
+
+
+/* exported protoypes */
+void option_screen(struct pine *, int);
+int litsig_text_tool(struct pine *, int, CONF_S **, unsigned);
+char *pretty_var_name(char *);
+char *pretty_feature_name(char *, int);
+
+
+#endif /* PINE_SETUP_INCLUDED */
diff --git a/alpine/signal.c b/alpine/signal.c
new file mode 100644
index 00000000..1e027e51
--- /dev/null
+++ b/alpine/signal.c
@@ -0,0 +1,921 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: signal.c 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $";
+#endif
+
+/* ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ Different signal handlers for different signals
+ - Catches all the abort signals, cleans up tty modes and then coredumps
+ - Not much to do for SIGHUP
+ - Not much to do for SIGTERM
+ - turn SIGWINCH into a KEY_RESIZE command
+ - No signals for ^Z/suspend, but do it here anyway
+ - Also set up the signal handlers, and hold signals for
+ critical imap sections of code.
+ ====*/
+
+
+#include "headers.h"
+#include "alpine.h"
+#include "signal.h"
+#include "status.h"
+#include "dispfilt.h"
+#include "keymenu.h"
+#include "titlebar.h"
+#include "mailcmd.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/detach.h"
+#include "../pith/pipe.h"
+#include "../pith/util.h"
+#include "../pith/icache.h"
+#include "../pith/newmail.h"
+#include "../pith/imap.h"
+#include "../pith/adrbklib.h"
+#include "../pith/remote.h"
+#include "../pith/osdep/pipe.h"
+
+
+/*
+ * Internal Prototypes
+ */
+static RETSIGTYPE auger_in_signal(int);
+void init_sighup(void);
+void end_sighup(void);
+RETSIGTYPE term_signal(void);
+void fast_clean_up(void);
+static RETSIGTYPE usr2_signal(int);
+static RETSIGTYPE winch_signal(int);
+static RETSIGTYPE intr_signal(int);
+void suspend_notice(char *);
+void suspend_warning(void);
+
+
+
+static int cleanup_called_from_sig_handler;
+
+
+
+/*----------------------------------------------------------------------
+ Install handlers for all the signals we care to catch
+ ----------------------------------------------------------------------*/
+void
+init_signals(void)
+{
+ dprint((9, "init_signals()\n"));
+ init_sighup();
+
+#ifdef _WINDOWS
+#else
+# ifdef DEBUG
+# define CUSHION_SIG (debug < 7)
+# else
+# define CUSHION_SIG (1)
+# endif
+
+ if(CUSHION_SIG){
+ signal(SIGILL, auger_in_signal);
+ signal(SIGTRAP, auger_in_signal);
+# ifdef SIGEMT
+ signal(SIGEMT, auger_in_signal);
+# endif
+ signal(SIGBUS, auger_in_signal);
+ signal(SIGSEGV, auger_in_signal);
+ signal(SIGSYS, auger_in_signal);
+ signal(SIGQUIT, auger_in_signal);
+ /* Don't catch SIGFPE cause it's rare and we use it in a hack below*/
+ }
+
+ init_sigwinch();
+
+ /*
+ * Set up SIGUSR2 to catch signal from other software using the
+ * c-client to tell us that other access to the folder is being
+ * attempted. THIS IS A TEST: if it turns out that simply
+ * going R/O when another pine is started or the same folder is opened,
+ * then we may want to install a smarter handler that uses idle time
+ * or even prompts the user to see if it's ok to give up R/O access...
+ */
+ signal(SIGUSR2, usr2_signal);
+
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+
+# ifdef SIGTSTP
+ /* Some unexplained behaviour on Ultrix 4.2 (Hardy) seems to be
+ resulting in Pine getting sent a SIGTSTP. Ignore it here.
+ probably better to ignore it than let it happen in any case
+ */
+ signal(SIGTSTP, SIG_IGN);
+# endif /* SIGTSTP */
+
+# ifdef SIGCHLD
+ signal(SIGCHLD, child_signal);
+# endif
+#endif /* !_WINDOWS */
+}
+
+
+/*----------------------------------------------------------------------
+ Return all signal handling back to normal
+ ----------------------------------------------------------------------*/
+void
+end_signals(int blockem)
+{
+#ifdef _WINDOWS
+
+ signal(SIGHUP, blockem ? SIG_IGN : SIG_DFL);
+
+#else
+#ifndef SIG_ERR
+#define SIG_ERR (RETSIGTYPE (*)())-1
+#endif
+
+ dprint((5, "end_signals(%d)\n", blockem));
+ if(signal(SIGILL, blockem ? SIG_IGN : SIG_DFL) == SIG_ERR){
+ fprintf(stderr, "Error resetting signals: %s\n",
+ error_description(errno));
+ exit(-1);
+ }
+
+ signal(SIGTRAP, blockem ? SIG_IGN : SIG_DFL);
+#ifdef SIGEMT
+ signal(SIGEMT, blockem ? SIG_IGN : SIG_DFL);
+#endif
+ signal(SIGBUS, blockem ? SIG_IGN : SIG_DFL);
+ signal(SIGSEGV, blockem ? SIG_IGN : SIG_DFL);
+ signal(SIGSYS, blockem ? SIG_IGN : SIG_DFL);
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+ signal(SIGWINCH, blockem ? SIG_IGN : SIG_DFL);
+#endif
+ signal(SIGQUIT, blockem ? SIG_IGN : SIG_DFL);
+#ifdef SIGTSTP
+ signal(SIGTSTP, blockem ? SIG_IGN : SIG_DFL);
+#endif /* SIGTSTP */
+ signal(SIGHUP, blockem ? SIG_IGN : SIG_DFL);
+ signal(SIGTERM, blockem ? SIG_IGN : SIG_DFL);
+ signal(SIGINT, blockem ? SIG_IGN : SIG_DFL);
+
+#endif /* !_WINDOWS */
+}
+
+
+/*----------------------------------------------------------------------
+ Handle signals caused by aborts -- SIGSEGV, SIGILL, etc
+
+Call panic which cleans up tty modes and then core dumps
+ ----------------------------------------------------------------------*/
+static RETSIGTYPE
+auger_in_signal(int sig)
+{
+ char buf[100];
+
+ end_signals(1); /* don't catch any more signals */
+ imap_flush_passwd_cache(FALSE);
+
+ snprintf(buf, sizeof(buf), "Received abort signal(sig=%d)", sig);
+ buf[sizeof(buf)-1] = '\0';
+
+ panic(buf); /* clean up and get out */
+
+ exit(-1); /* in case panic doesn't kill us */
+}
+
+
+/*----------------------------------------------------------------------
+ Install signal handler to deal with hang up signals -- SIGHUP, SIGTERM
+
+ ----------------------------------------------------------------------*/
+void
+init_sighup(void)
+{
+#if !(defined(DOS) && !defined(_WINDOWS))
+#if defined(_WINDOWS) || defined(OS2)
+ signal(SIGHUP, (void *) hup_signal);
+#else
+ signal(SIGHUP, hup_signal);
+#endif
+#endif
+#if !(defined(DOS) || defined(OS2))
+ signal(SIGTERM, term_signal);
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ De-Install signal handler to deal with hang up signals -- SIGHUP, SIGTERM
+
+ ----------------------------------------------------------------------*/
+void
+end_sighup(void)
+{
+#if !(defined(DOS) && !defined(_WINDOWS))
+ signal(SIGHUP, SIG_IGN);
+#endif
+#if !(defined(DOS) || defined(OS2))
+ signal(SIGTERM, SIG_IGN);
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ handle hang up signal -- SIGHUP
+
+Not much to do. Rely on periodic mail file check pointing.
+ ----------------------------------------------------------------------*/
+RETSIGTYPE
+hup_signal(void)
+{
+ end_signals(1); /* don't catch any more signals */
+ dprint((1, "\n\n** Received SIGHUP **\n\n\n\n"));
+ cleanup_called_from_sig_handler = 1;
+ fast_clean_up();
+#if defined(DEBUG)
+ if(debugfile)
+ fclose(debugfile);
+#endif
+
+ _exit(0); /* cleaning up can crash */
+}
+
+
+/*----------------------------------------------------------------------
+ Timeout when no user input for a long, long time.
+ Treat it pretty much the same as if we got a HUP.
+ Only difference is we sometimes turns the timeout off (when composing).
+ ----------------------------------------------------------------------*/
+void
+user_input_timeout_exit(int to_hours)
+{
+ char msg[80];
+
+ dprint((1,
+ "\n\n** Exiting: user input timeout (%d hours) **\n\n\n\n",
+ to_hours));
+ snprintf(msg, sizeof(msg), _("\n\nAlpine timed out (No user input for %d %s)\n"), to_hours,
+ to_hours > 1 ? "hours" : "hour");
+ msg[sizeof(msg)-1] = '\0';
+ fast_clean_up();
+ end_screen(msg, 0);
+ end_titlebar();
+ end_keymenu();
+ end_keyboard(F_ON(F_USE_FK,ps_global));
+ end_tty_driver(ps_global);
+ end_signals(0);
+#if defined(DEBUG) && (!defined(DOS) || defined(_WINDOWS))
+ if(debugfile)
+ fclose(debugfile);
+#endif
+ exit(0);
+}
+
+
+/*----------------------------------------------------------------------
+ handle terminate signal -- SIGTERM
+
+Not much to do. Rely on periodic mail file check pointing.
+ ----------------------------------------------------------------------*/
+RETSIGTYPE
+term_signal(void)
+{
+#if !defined(DOS) && !defined(OS2)
+ end_signals(1); /* don't catch any more signals */
+ dprint((1, "\n\n** Received SIGTERM **\n\n\n\n"));
+ cleanup_called_from_sig_handler = 1;
+ fast_clean_up();
+#if defined(DEBUG) && (!defined(DOS) || defined(_WINDOWS))
+ if(debugfile)
+ fclose(debugfile);
+#endif
+ printf(_("\n\nAlpine finished. Received terminate signal\n\n"));
+#endif /* !DOS */
+ exit(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Handle cleaning up mail streams and tty modes...
+Not much to do. Rely on periodic mail file check pointing. Don't try
+cleaning up screen or flushing output since stdout is likely already
+gone. To be safe, though, we'll at least restore the original tty mode.
+Also delete any remnant _DATAFILE_ from sending-filters.
+ ----------------------------------------------------------------------*/
+void
+fast_clean_up(void)
+{
+#if !defined(DOS) && !defined(OS2)
+ int i;
+ MAILSTREAM *m;
+#endif
+
+ dprint((1, "fast_clean_up()\n"));
+
+ if(ps_global->expunge_in_progress){
+ PineRaw(0);
+ return;
+ }
+
+ /*
+ * This gets rid of temporary cache files for remote addrbooks.
+ */
+ completely_done_with_adrbks();
+
+ /*
+ * This flushes out deferred changes and gets rid of temporary cache
+ * files for remote config files.
+ */
+ if(ps_global->prc){
+ if(ps_global->prc->outstanding_pinerc_changes)
+ write_pinerc(ps_global, Main,
+ cleanup_called_from_sig_handler ? WRP_NOUSER : WRP_NONE);
+
+ if(ps_global->prc->rd)
+ rd_close_remdata(&ps_global->prc->rd);
+
+ free_pinerc_s(&ps_global->prc);
+ }
+
+ /* as does this */
+ if(ps_global->post_prc){
+ if(ps_global->post_prc->outstanding_pinerc_changes)
+ write_pinerc(ps_global, Post,
+ cleanup_called_from_sig_handler ? WRP_NOUSER : WRP_NONE);
+
+ if(ps_global->post_prc->rd)
+ rd_close_remdata(&ps_global->post_prc->rd);
+
+ free_pinerc_s(&ps_global->post_prc);
+ }
+
+ /*
+ * Can't figure out why this section is inside the ifdef, but no
+ * harm leaving it that way, I guess.
+ */
+#if !defined(DOS) && !defined(OS2)
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ m = ps_global->s_pool.streams[i];
+ if(m && !m->lock)
+ pine_mail_actually_close(m);
+ }
+
+ PineRaw(0);
+
+#endif /* !DOS */
+
+ imap_flush_passwd_cache(TRUE);
+
+ dprint((1, "done with fast_clean_up\n"));
+}
+
+
+#if !defined(DOS) && !defined(OS2)
+/*----------------------------------------------------------------------
+ handle hang up signal -- SIGUSR2
+
+Not much to do. Rely on periodic mail file check pointing.
+ ----------------------------------------------------------------------*/
+static RETSIGTYPE
+usr2_signal(int sig)
+{
+ char c, *mbox, mboxbuf[20];
+ int i;
+ MAILSTREAM *stream;
+ NETMBX mb;
+
+ for(i = 0; i < ps_global->s_pool.nstream; i++){
+ stream = ps_global->s_pool.streams[i];
+ if(stream
+ && sp_flagged(stream, SP_LOCKED)
+ && !sp_dead_stream(stream)
+ && !stream->lock
+ && !stream->rdonly
+ && stream->mailbox
+ && (c = *stream->mailbox) != '{' && c != '*'){
+ pine_mail_check(stream); /* write latest state */
+ stream->rdonly = 1; /* and become read-only */
+ (void) pine_mail_ping(stream);
+ mbox = stream->mailbox;
+ if(!strucmp(stream->mailbox, ps_global->inbox_name)
+ || !strcmp(stream->mailbox, ps_global->VAR_INBOX_PATH)
+ || !strucmp(stream->original_mailbox, ps_global->inbox_name)
+ || !strcmp(stream->original_mailbox, ps_global->VAR_INBOX_PATH))
+ mbox = "INBOX";
+ else if(mail_valid_net_parse(stream->mailbox, &mb) && mb.mailbox)
+ mbox = mb.mailbox;
+
+ q_status_message1(SM_ASYNC, 3, 7,
+ _("Another email program is accessing %s. Session now Read-Only."),
+ short_str((mbox && *mbox) ? mbox : "folder",
+ mboxbuf, sizeof(mboxbuf), 19, FrontDots));
+ dprint((1, "** folder %s went read-only **\n\n",
+ stream->mailbox));
+ }
+ }
+}
+#endif
+
+
+/*----------------------------------------------------------------------
+ Install signal handler to deal with window resize signal -- SIGWINCH
+
+ ----------------------------------------------------------------------*/
+void
+init_sigwinch (void)
+{
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+ signal(SIGWINCH, winch_signal);
+#endif
+}
+
+
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+/*----------------------------------------------------------------------
+ Handle window resize signal -- SIGWINCH
+
+ The planned strategy is just force a redraw command. This is similar
+ to new mail handling which forces a noop command. The signals are
+ help until pine reads input. Then a KEY_RESIZE is forced into the command
+ stream .
+ Note that ready_for_winch is only non-zero inside the read_char function,
+ so the longjmp only ever happens there, and it is really just a jump
+ from lower down in the function up to the top of that function. Its
+ purpose is to return a KEY_RESIZE from read_char when interrupted
+ out of the select lower down in read_char.
+ ----------------------------------------------------------------------*/
+extern jmp_buf winch_state;
+extern int ready_for_winch, winch_occured;
+
+static RETSIGTYPE
+winch_signal(int sig)
+{
+ clear_cursor_pos();
+ init_sigwinch();
+ winch_cleanup();
+}
+#endif
+
+
+/*
+ * winch_cleanup -
+ */
+void
+winch_cleanup(void)
+{
+#if defined(SIGWINCH) && defined(TIOCGWINSZ)
+ if(ready_for_winch)
+ longjmp(winch_state, 1);
+ else
+ winch_occured = 1;
+#endif
+}
+
+
+#ifdef SIGCHLD
+/*----------------------------------------------------------------------
+ Handle child status change -- SIGCHLD
+
+ The strategy here is to install the handler when we spawn a child, and
+ to let the system tell us when the child's state has changed. In the
+ mean time, we can do whatever. Typically, "whatever" is spinning in a
+ loop alternating between sleep and new_mail calls intended to keep the
+ IMAP stream alive.
+
+ ----------------------------------------------------------------------*/
+static short child_signalled, child_jump;
+static jmp_buf child_state;
+
+RETSIGTYPE
+child_signal(int sig)
+{
+#ifdef BACKGROUND_POST
+ /*
+ * reap background posting process
+ */
+ if(ps_global->post && ps_global->post->pid){
+ pid_t pid;
+ int es;
+
+ pid = process_reap(ps_global->post->pid, &es, PR_NOHANG);
+ if(pid == ps_global->post->pid){
+ ps_global->post->status = es;
+ ps_global->post->pid = 0;
+ return;
+ }
+ else if(pid < 0 && errno != EINTR){
+ fs_give((void **) &ps_global->post);
+ }
+ }
+#endif /* BACKGROUND_POST */
+
+ child_signalled = 1;
+ if(child_jump)
+ longjmp(child_state, 1);
+}
+#endif /* SIGCHLD */
+
+
+/*
+ * pipe_callback - handle pine-specific pipe setup like child
+ * signal handler and possibly any display stuff
+ * BUG: this function should probably be in a "alpine/pipe.c"
+ */
+void
+pipe_callback(PIPE_S *syspipe, int flags, void *data)
+{
+#ifdef _WINDOWS
+ bitmap_t bitmap;
+#endif
+ if(flags & OSB_PRE_OPEN){
+ dprint((5, "Opening pipe: (%s%s%s%s%s%s)\n",
+ (syspipe->mode & PIPE_WRITE) ? "W":"", (syspipe->mode & PIPE_READ) ? "R":"",
+ (syspipe->mode & PIPE_NOSHELL) ? "N":"", (syspipe->mode & PIPE_PROT) ? "P":"",
+ (syspipe->mode & PIPE_USER) ? "U":"", (syspipe->mode & PIPE_RESET) ? "T":""));
+
+#ifdef _WINDOWS
+ q_status_message(SM_ORDER, 0, 0,
+ "Waiting for called program to finish...");
+
+ flush_status_messages(1);
+ setbitmap(bitmap);
+ draw_keymenu(&pipe_cancel_keymenu, bitmap, ps_global->ttyo->screen_cols,
+ 1-FOOTER_ROWS(ps_global), 0, FirstMenu);
+#else /* UNIX */
+
+ if(!(syspipe->mode & (PIPE_WRITE | PIPE_READ)) && !(syspipe->mode & PIPE_SILENT)){
+ flush_status_messages(0); /* just clean up display */
+ ClearScreen();
+ fflush(stdout);
+ }
+
+ if(syspipe->mode & PIPE_RESET)
+ ttyfix(0);
+
+#ifdef SIGCHLD
+ /*
+ * Prepare for demise of child. Use SIGCHLD if it's available so
+ * we can do useful things, like keep the IMAP stream alive, while
+ * we're waiting on the child. The handler may have been changed by
+ * something in the composer, in particular, by an alt_editor call.
+ * So we need to re-set it to child_signal and then set it back
+ * when we're done.
+ */
+ child_signalled = child_jump = 0;
+ syspipe->chld = signal(SIGCHLD, child_signal);
+#endif
+#endif /* UNIX */
+ }
+ else if(flags & OSB_POST_OPEN){
+#ifdef _WINDOWS
+
+ clearfooter(ps_global);
+ ps_global->mangled_footer = 1;
+
+ if((int) data == -2)
+ q_status_message1(SM_ORDER, 2, 3, "Ignoring completion of %s", syspipe->command);
+
+#else /* UNIX */
+ if(!(syspipe->mode & (PIPE_WRITE | PIPE_READ)) && !(syspipe->mode & PIPE_SILENT)){
+ ClearScreen();
+ ps_global->mangled_screen = 1;
+ }
+
+ if(syspipe->mode & PIPE_RESET)
+ ttyfix(1);
+
+#ifdef SIGCHLD
+ (void) signal(SIGCHLD, SIG_DFL);
+#endif
+#endif /* UNIX */
+ }
+ else if(flags & OSB_PRE_CLOSE){
+#ifdef SIGCHLD
+ /*
+ * this is here so close_system_pipe so it has something
+ * to do while we're waiting on the other end of the
+ * pipe to complete. When we're in the background for
+ * a shell, the the side effect is pinging
+ */
+ RETSIGTYPE (*alarm_sig)();
+ int old_cue = F_ON(F_SHOW_DELAY_CUE, ps_global);
+
+ /*
+ * remember the current SIGALRM handler, and make sure it's
+ * installed when we're finished just in case the longjmp
+ * out of the SIGCHLD handler caused sleep() to lose it.
+ * Don't pay any attention to that man behind the curtain.
+ */
+ alarm_sig = signal(SIGALRM, SIG_IGN);
+ (void) F_SET(F_SHOW_DELAY_CUE, ps_global, 0);
+ ps_global->noshow_timeout = 1;
+ while(!child_signalled){
+ /* wake up and prod server */
+ if(!(syspipe->mode & PIPE_NONEWMAIL))
+ new_mail(0, 2,
+ (syspipe->mode & PIPE_RESET) ? NM_NONE : NM_DEFER_SORT);
+
+ if(!child_signalled){
+ if(setjmp(child_state) == 0){
+ child_jump = 1; /* prepare to wake up */
+ sleep(600); /* give it 5mins to happend */
+ }
+ else
+ our_sigunblock(SIGCHLD);
+ }
+
+ child_jump = 0;
+ }
+
+ ps_global->noshow_timeout = 0;
+ F_SET(F_SHOW_DELAY_CUE, ps_global, old_cue);
+ (void) signal(SIGALRM, alarm_sig);
+ (void) signal(SIGCHLD, syspipe->chld);
+#endif
+ }
+ else if(flags & OSB_POST_CLOSE){
+ if(syspipe->mode & PIPE_RESET) /* restore our tty modes */
+ ttyfix(1);
+
+ if(!(syspipe->mode & (PIPE_WRITE | PIPE_READ | PIPE_SILENT))){
+ ClearScreen(); /* No I/O to forked child */
+ ps_global->mangled_screen = 1;
+ }
+ }
+}
+
+
+/*
+ * Command interrupt support.
+ */
+
+static RETSIGTYPE
+intr_signal(int sig)
+{
+ ps_global->intr_pending = 1;
+}
+
+
+int
+intr_handling_on(void)
+{
+#ifdef _WINDOWS
+ return 0; /* No interrupts in Windows */
+#else /* UNIX */
+ if(signal(SIGINT, intr_signal) == intr_signal)
+ return 0; /* already installed */
+
+ intr_proc(1);
+ if(ps_global && ps_global->ttyo)
+ draw_cancel_keymenu();
+
+ return 1;
+#endif /* UNIX */
+}
+
+
+void
+intr_handling_off(void)
+{
+#ifdef _WINDOWS
+#else /* UNIX */
+ if(signal(SIGINT, SIG_IGN) == SIG_IGN) /* already off! */
+ return;
+
+ ps_global->intr_pending = 0;
+ intr_proc(0);
+ if(ps_global && ps_global->ttyo)
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+
+ ps_global->mangled_footer = 1;
+#endif /* UNIX */
+}
+
+
+/*----------------------------------------------------------------------
+ Set or reset what needs to be set when coming out of pico to run
+ an alternate editor.
+
+ Args: come_back -- If come_back is 0 we're going out of our environment
+ to set up for an external editor.
+ If come_back is 1 we're coming back into pine.
+ ----------------------------------------------------------------------*/
+int
+ttyfix(int come_back)
+{
+#if defined(DEBUG) && (!defined(DOS) || defined(_WINDOWS))
+ if(debugfile)
+ fflush(debugfile);
+#endif
+
+ if(come_back){
+#ifdef OS2
+ enter_text_mode(NULL);
+#endif
+ init_screen();
+ init_tty_driver(ps_global);
+ init_keyboard(F_ON(F_USE_FK,ps_global));
+#ifdef OS2
+ dont_interrupt();
+#endif
+ fix_windsize(ps_global);
+ }
+ else{
+ EndInverse();
+ end_keyboard(F_ON(F_USE_FK,ps_global));
+ end_tty_driver(ps_global);
+ end_screen(NULL, 0);
+#ifdef OS2
+ interrupt_ok();
+#endif
+ }
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Suspend Pine. Reset tty and suspend. Suspend is finished when this returns
+
+ Args: The pine structure
+
+ Result: Execution suspended for a while. Screen will need redrawing
+ after this is done.
+
+ Instead of the usual handling of ^Z by catching a signal, we actually read
+the ^Z and then clean up the tty driver, then kill ourself to stop, and
+pick up where we left off when execution resumes.
+ ----------------------------------------------------------------------*/
+UCS
+do_suspend(void)
+{
+ struct pine *pine = ps_global;
+ time_t now;
+ UCS retval;
+#if defined(DOS) || defined(OS2)
+ int result, orig_cols, orig_rows;
+#else
+ int isremote;
+#endif
+#ifdef DOS
+ static char *shell = NULL;
+#define STD_SHELL "COMMAND.COM"
+#else
+#ifdef OS2
+ static char *shell = NULL;
+#define STD_SHELL "CMD.EXE"
+#endif
+#endif
+
+ if(!have_job_control()){
+ bogus_command(ctrl('Z'), F_ON(F_USE_FK, pine) ? "F1" : "?");
+ return(NO_OP_COMMAND);
+ }
+
+ if(F_OFF(F_CAN_SUSPEND, pine)){
+ q_status_message(SM_ORDER | SM_DING, 3, 3,
+ _("Alpine suspension not enabled - see help text"));
+ return(NO_OP_COMMAND);
+ }
+
+#ifdef _WINDOWS
+ mswin_minimize();
+ return(NO_OP_COMMAND);
+#else
+
+ isremote = (ps_global->mail_stream && ps_global->mail_stream->mailbox
+ && (*ps_global->mail_stream->mailbox == '{'
+ || (*ps_global->mail_stream->mailbox == '*'
+ && *(ps_global->mail_stream->mailbox + 1) == '{')));
+
+ now = time((time_t *)0);
+ dprint((1, "\n\n --- %s - SUSPEND ---- %s",
+ isremote ? "REMOTE" : "LOCAL", ctime(&now)));
+ ttyfix(0);
+
+#if defined(DOS) || defined(OS2)
+ suspend_notice("exit");
+ if (!shell){
+ char *p;
+
+ if (!((shell = getenv("SHELL")) || (shell = getenv("COMSPEC"))))
+ shell = STD_SHELL;
+
+ shell = cpystr(shell); /* copy in free storage */
+ for(p = shell; (p = strchr(p, '/')); p++)
+ *p = '\\';
+ }
+
+ result = system(shell);
+#else
+ if(F_ON(F_SUSPEND_SPAWNS, ps_global)){
+ PIPE_S *syspipe;
+ int flag = some_stream_is_locked() ? PIPE_NONEWMAIL : 0;
+
+ flag |= PIPE_USER|PIPE_RESET;
+ if((syspipe = open_system_pipe(NULL, NULL, NULL, flag,
+ 0, pipe_callback, pipe_report_error)) != NULL){
+ suspend_notice("exit");
+#ifndef SIGCHLD
+ if(isremote)
+ suspend_warning();
+#endif
+ (void) close_system_pipe(&syspipe, NULL, pipe_callback);
+ }
+ }
+ else{
+ suspend_notice("fg");
+
+ if(isremote)
+ suspend_warning();
+
+ stop_process();
+ }
+#endif /* DOS */
+
+ now = time((time_t *)0);
+ dprint((1, "\n\n ---- RETURN FROM SUSPEND ---- %s",
+ ctime(&now)));
+
+ ttyfix(1);
+
+#if defined(DOS) || defined(OS2)
+ orig_cols = pine->ttyo->screen_cols;
+ orig_rows = pine->ttyo->screen_rows;
+#endif
+
+ fix_windsize(pine);
+
+#if defined(DOS) || defined(OS2)
+ if(orig_cols != pine->ttyo->screen_cols ||
+ orig_rows != pine->ttyo->screen_rows)
+ retval = KEY_RESIZE;
+ else
+#endif
+ retval = ctrl('L');;
+
+#if defined(DOS) || defined(OS2)
+ if(result == -1)
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ _("Error loading \"%s\""), shell);
+#endif
+
+ if(isremote && !ps_global->mail_stream->lock
+ && !pine_mail_ping(ps_global->mail_stream))
+ q_status_message(SM_ORDER | SM_DING, 4, 9,
+ _("Suspended for too long, IMAP connection broken"));
+
+ return(retval);
+#endif /* !_WINDOWS */
+}
+
+
+
+/*----------------------------------------------------------------------
+ ----*/
+void
+suspend_notice(char *s)
+{
+ printf(_("\nAlpine suspended. Give the \"%s\" command to come back.\n"), s);
+ fflush(stdout);
+}
+
+
+/*----------------------------------------------------------------------
+ ----*/
+void
+suspend_warning(void)
+{
+ puts(_("Warning: Your IMAP connection will be closed if Alpine"));
+ puts(_("is suspended for more than 30 minutes\n"));
+ fflush(stdout);
+}
+
+
+/*----------------------------------------------------------------------
+ ----*/
+void
+fix_windsize(struct pine *pine)
+{
+ int old_width = pine->ttyo->screen_cols;
+
+ dprint((9, "fix_windsize()\n"));
+ mark_keymenu_dirty();
+ mark_status_dirty();
+ mark_titlebar_dirty();
+ clear_cursor_pos();
+ get_windsize(pine->ttyo);
+
+ if(old_width != pine->ttyo->screen_cols)
+ clear_index_cache(pine->mail_stream, 0);
+}
diff --git a/alpine/signal.h b/alpine/signal.h
new file mode 100644
index 00000000..0abf6924
--- /dev/null
+++ b/alpine/signal.h
@@ -0,0 +1,44 @@
+/*
+ * $Id: signal.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_SIGNAL_INCLUDED
+#define PINE_SIGNAL_INCLUDED
+
+
+#include <general.h>
+
+#include "../pith/osdep/pipe.h" /* for PIPE_S */
+
+#include "../pith/state.h"
+#include "../pith/signal.h"
+
+#define MAX_BM 150 /* max length of busy message */
+
+
+/* exported protoypes */
+RETSIGTYPE hup_signal(void);
+RETSIGTYPE child_signal(int);
+void user_input_timeout_exit(int);
+void init_signals(void);
+void init_sigwinch(void);
+void end_signals(int);
+int ttyfix(int);
+UCS do_suspend(void);
+void winch_cleanup(void);
+void pipe_callback(PIPE_S *, int, void *);
+void fix_windsize(struct pine *);
+
+
+#endif /* PINE_SIGNAL_INCLUDED */
diff --git a/alpine/smime.c b/alpine/smime.c
new file mode 100644
index 00000000..bcf88acb
--- /dev/null
+++ b/alpine/smime.c
@@ -0,0 +1,1151 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: smime.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*
+ * This is based on a contribution from Jonathan Paisley
+ *
+ * File: smime.c
+ * Author: paisleyj@dcs.gla.ac.uk
+ * Date: 01/2001
+ */
+
+
+#include "headers.h"
+
+#ifdef SMIME
+
+#include "../pith/charconv/utf8.h"
+#include "../pith/status.h"
+#include "../pith/store.h"
+#include "../pith/conf.h"
+#include "../pith/list.h"
+#include "radio.h"
+#include "keymenu.h"
+#include "mailview.h"
+#include "conftype.h"
+#include "confscroll.h"
+#include "setup.h"
+#include "smime.h"
+
+
+/* internal prototypes */
+void format_smime_info(int pass, BODY *body, long msgno, gf_io_t pc);
+void print_separator_line(int percent, int ch, gf_io_t pc);
+void output_cert_info(X509 *cert, gf_io_t pc);
+void output_X509_NAME(X509_NAME *name, gf_io_t pc);
+void side_by_side(STORE_S *left, STORE_S *right, gf_io_t pc);
+void get_fingerprint(X509 *cert, const EVP_MD *type, char *buf, size_t maxLen);
+STORE_S *wrap_store(STORE_S *in, int width);
+void smime_config_init_display(struct pine *, CONF_S **, CONF_S **);
+void revert_to_saved_smime_config(struct pine *ps, SAVED_CONFIG_S *vsave);
+SAVED_CONFIG_S *save_smime_config_vars(struct pine *ps);
+void free_saved_smime_config(struct pine *ps, SAVED_CONFIG_S **vsavep);
+int smime_helper_tool(struct pine *, int, CONF_S **, unsigned);
+
+
+/*
+ * prompt the user for their passphrase
+ * (possibly prompting with the email address in s_passphrase_emailaddr)
+ */
+int
+smime_get_passphrase(void)
+{
+ int rc;
+ int flags;
+ char prompt[500];
+ HelpType help = NO_HELP;
+
+ assert(ps_global->smime != NULL);
+ snprintf(prompt, sizeof(prompt),
+ _("Enter passphrase for <%s>: "), (ps_global->smime && ps_global->smime->passphrase_emailaddr) ? ps_global->smime->passphrase_emailaddr : "unknown");
+
+ do {
+ flags = OE_PASSWD | OE_DISALLOW_HELP;
+ ((char *) ps_global->smime->passphrase)[0] = '\0';
+ rc = optionally_enter((char *) ps_global->smime->passphrase,
+ -FOOTER_ROWS(ps_global), 0,
+ sizeof(ps_global->smime->passphrase),
+ prompt, NULL, help, &flags);
+ } while (rc!=0 && rc!=1 && rc>0);
+
+ if(rc==0){
+ if(ps_global->smime)
+ ps_global->smime->entered_passphrase = 1;
+ }
+
+ return rc; /* better return rc and make the caller check its return value */
+}
+
+
+void
+smime_info_screen(struct pine *ps)
+{
+ long msgno;
+ OtherMenu what;
+ int offset = 0;
+ BODY *body;
+ ENVELOPE *env;
+ HANDLE_S *handles = NULL;
+ SCROLL_S scrollargs;
+ STORE_S *store = NULL;
+
+ ps->prev_screen = smime_info_screen;
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ if(mn_total_cur(ps->msgmap) > 1L){
+ q_status_message(SM_ORDER | SM_DING, 0, 3,
+ _("Can only view one message's information at a time."));
+ return;
+ }
+ /* else check for existence of smime bits */
+
+ msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
+
+ env = mail_fetch_structure(ps->mail_stream, msgno, &body, 0);
+ if(!env || !body){
+ q_status_message(SM_ORDER, 0, 3,
+ _("Can't fetch body of message."));
+ return;
+ }
+
+ what = FirstMenu;
+
+ store = so_get(CharStar, NULL, EDIT_ACCESS);
+
+ while(ps->next_screen == SCREEN_FUN_NULL){
+
+ ClearLine(1);
+
+ so_truncate(store, 0);
+
+ view_writec_init(store, &handles, HEADER_ROWS(ps),
+ HEADER_ROWS(ps) +
+ ps->ttyo->screen_rows - (HEADER_ROWS(ps)
+ + HEADER_ROWS(ps)));
+
+ gf_puts_uline("Overview", view_writec);
+ gf_puts(NEWLINE, view_writec);
+
+ format_smime_info(1, body, msgno, view_writec);
+ gf_puts(NEWLINE, view_writec);
+ format_smime_info(2, body, msgno, view_writec);
+
+ view_writec_destroy();
+
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ memset(&scrollargs, 0, sizeof(SCROLL_S));
+ scrollargs.text.text = so_text(store);
+ scrollargs.text.src = CharStar;
+ scrollargs.text.desc = "S/MIME information";
+ scrollargs.body_valid = 1;
+
+ if(offset){ /* resize? preserve paging! */
+ scrollargs.start.on = Offset;
+ scrollargs.start.loc.offset = offset;
+ offset = 0L;
+ }
+
+ scrollargs.bar.title = "S/MIME INFORMATION";
+/* scrollargs.end_scroll = view_end_scroll; */
+ scrollargs.resize_exit = 1;
+ scrollargs.help.text = NULL;
+ scrollargs.help.title = "HELP FOR S/MIME INFORMATION VIEW";
+ scrollargs.keys.menu = &smime_info_keymenu;
+ scrollargs.keys.what = what;
+ setbitmap(scrollargs.keys.bitmap);
+
+ if(scrolltool(&scrollargs) == MC_RESIZE)
+ offset = scrollargs.start.loc.offset;
+ }
+
+ so_give(&store);
+}
+
+
+void
+format_smime_info(int pass, BODY *body, long msgno, gf_io_t pc)
+{
+ PKCS7 *p7;
+ int i;
+
+ if(body->type == TYPEMULTIPART){
+ PART *p;
+
+ for(p=body->nested.part; p; p=p->next)
+ format_smime_info(pass, &p->body, msgno, pc);
+ }
+
+ p7 = body->sparep;
+ if(p7){
+
+ if(PKCS7_type_is_signed(p7)){
+ STACK_OF(X509) *signers;
+
+ switch(pass){
+ case 1:
+ gf_puts(_("This message was cryptographically signed."), pc);
+ gf_puts(NEWLINE, pc);
+ break;
+
+ case 2:
+ signers = PKCS7_get0_signers(p7, NULL, 0);
+
+ if(signers){
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Certificate%s used for signing"),
+ plural(sk_X509_num(signers)));
+ gf_puts_uline(tmp_20k_buf, pc);
+ gf_puts(NEWLINE, pc);
+ print_separator_line(100, '-', pc);
+
+ for(i=0; i<sk_X509_num(signers); i++){
+ X509 *x = sk_X509_value(signers, i);
+
+ if(x){
+ output_cert_info(x, pc);
+ gf_puts(NEWLINE, pc);
+ }
+ }
+ }
+
+ sk_X509_free(signers);
+ break;
+ }
+
+ }
+ else if(PKCS7_type_is_enveloped(p7)){
+
+ switch(pass){
+ case 1:
+ gf_puts(_("This message was encrypted."), pc);
+ gf_puts(NEWLINE, pc);
+ break;
+
+ case 2:
+ if(p7->d.enveloped && p7->d.enveloped->enc_data){
+ X509_ALGOR *alg = p7->d.enveloped->enc_data->algorithm;
+ STACK_OF(PKCS7_RECIP_INFO) *ris = p7->d.enveloped->recipientinfo;
+ int found = 0;
+
+ gf_puts(_("The algorithm used to encrypt was "), pc);
+
+ if(alg){
+ char *n = (char *) OBJ_nid2sn( OBJ_obj2nid(alg->algorithm));
+
+ gf_puts(n ? n : "<unknown>", pc);
+
+ }
+ else
+ gf_puts("<unknown>", pc);
+
+ gf_puts("." NEWLINE NEWLINE, pc);
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Certificate%s for decrypting"),
+ plural(sk_PKCS7_RECIP_INFO_num(ris)));
+ gf_puts_uline(tmp_20k_buf, pc);
+ gf_puts(NEWLINE, pc);
+ print_separator_line(100, '-', pc);
+
+ for(i=0; i<sk_PKCS7_RECIP_INFO_num(ris); i++){
+ PKCS7_RECIP_INFO *ri;
+ PERSONAL_CERT *pcert;
+
+ ri = sk_PKCS7_RECIP_INFO_value(ris, i);
+ if(!ri)
+ continue;
+
+ pcert = find_certificate_matching_recip_info(ri);
+
+ if(pcert){
+ if(found){
+ print_separator_line(25, '*', pc);
+ gf_puts(NEWLINE, pc);
+ }
+
+ found = 1;
+
+ output_cert_info(pcert->cert, pc);
+ gf_puts(NEWLINE, pc);
+
+ }
+ }
+
+ if(!found){
+ gf_puts(_("No certificate capable of decrypting could be found."), pc);
+ gf_puts(NEWLINE, pc);
+ gf_puts(NEWLINE, pc);
+ }
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+
+void
+print_separator_line(int percent, int ch, gf_io_t pc)
+{
+ int i, start, len;
+
+ len = ps_global->ttyo->screen_cols * percent / 100;
+ start = (ps_global->ttyo->screen_cols - len)/2;
+
+ for(i=0; i<start; i++)
+ pc(' ');
+
+ for(i=start; i<start+len; i++)
+ pc(ch);
+
+ gf_puts(NEWLINE, pc);
+}
+
+
+void
+output_cert_info(X509 *cert, gf_io_t pc)
+{
+ char buf[256];
+ STORE_S *left,*right;
+ gf_io_t spc;
+ int len;
+
+ left = so_get(CharStar, NULL, EDIT_ACCESS);
+ right = so_get(CharStar, NULL, EDIT_ACCESS);
+ if(!(left && right))
+ return;
+
+ gf_set_so_writec(&spc, left);
+
+ if(!cert->cert_info){
+ gf_puts("Couldn't find certificate info.", spc);
+ gf_puts(NEWLINE, spc);
+ }
+ else{
+ gf_puts_uline("Subject (whose certificate it is)", spc);
+ gf_puts(NEWLINE, spc);
+
+ output_X509_NAME(cert->cert_info->subject, spc);
+ gf_puts(NEWLINE, spc);
+
+ gf_puts_uline("Serial Number", spc);
+ gf_puts(NEWLINE, spc);
+
+ snprintf(buf, sizeof(buf), "%ld", ASN1_INTEGER_get(cert->cert_info->serialNumber));
+ gf_puts(buf, spc);
+ gf_puts(NEWLINE, spc);
+ gf_puts(NEWLINE, spc);
+
+ gf_puts_uline("Validity", spc);
+ gf_puts(NEWLINE, spc);
+ {
+ BIO *mb = BIO_new(BIO_s_mem());
+ char iobuf[4096];
+
+ gf_puts("Not Before: ", spc);
+
+ (void) BIO_reset(mb);
+ ASN1_UTCTIME_print(mb, cert->cert_info->validity->notBefore);
+ (void) BIO_flush(mb);
+ while((len = BIO_read(mb, iobuf, sizeof(iobuf))) > 0)
+ gf_nputs(iobuf, len, spc);
+
+ gf_puts(NEWLINE, spc);
+
+ gf_puts("Not After: ", spc);
+
+ (void) BIO_reset(mb);
+ ASN1_UTCTIME_print(mb, cert->cert_info->validity->notAfter);
+ (void) BIO_flush(mb);
+ while((len = BIO_read(mb, iobuf, sizeof(iobuf))) > 0)
+ gf_nputs(iobuf, len, spc);
+
+ gf_puts(NEWLINE, spc);
+ gf_puts(NEWLINE, spc);
+
+ BIO_free(mb);
+ }
+ }
+
+ gf_clear_so_writec(left);
+
+ gf_set_so_writec(&spc, right);
+
+ if(!cert->cert_info){
+ gf_puts(_("Couldn't find certificate info."), spc);
+ gf_puts(NEWLINE, spc);
+ }
+ else{
+ gf_puts_uline("Issuer", spc);
+ gf_puts(NEWLINE, spc);
+
+ output_X509_NAME(cert->cert_info->issuer, spc);
+ gf_puts(NEWLINE, spc);
+ }
+
+ gf_clear_so_writec(right);
+
+ side_by_side(left, right, pc);
+
+ gf_puts_uline("SHA1 Fingerprint", pc);
+ gf_puts(NEWLINE, pc);
+ get_fingerprint(cert, EVP_sha1(), buf, sizeof(buf));
+ gf_puts(buf, pc);
+ gf_puts(NEWLINE, pc);
+
+ gf_puts_uline("MD5 Fingerprint", pc);
+ gf_puts(NEWLINE, pc);
+ get_fingerprint(cert, EVP_md5(), buf, sizeof(buf));
+ gf_puts(buf, pc);
+ gf_puts(NEWLINE, pc);
+
+ so_give(&left);
+ so_give(&right);
+}
+
+
+void
+output_X509_NAME(X509_NAME *name, gf_io_t pc)
+{
+ int i, c;
+ char buf[256];
+
+ c = X509_NAME_entry_count(name);
+
+ for(i=c-1; i>=0; i--){
+ X509_NAME_ENTRY *e;
+
+ e = X509_NAME_get_entry(name,i);
+ if(!e)
+ continue;
+
+ X509_NAME_get_text_by_OBJ(name, e->object, buf, sizeof(buf));
+
+ gf_puts(buf, pc);
+ gf_puts(NEWLINE, pc);
+ }
+}
+
+
+/*
+ * Output the contents of the given stores (left and right)
+ * to the given gf_io_t.
+ * The width of the terminal is inspected and two columns
+ * are created to fit the stores into. They are then wrapped
+ * and merged.
+ */
+void
+side_by_side(STORE_S *left, STORE_S *right, gf_io_t pc)
+{
+ STORE_S *left_wrapped;
+ STORE_S *right_wrapped;
+ char buf_l[256];
+ char buf_r[256];
+ char *b;
+ int i;
+ int w = ps_global->ttyo->screen_cols/2 - 1;
+
+ so_seek(left, 0, 0);
+ so_seek(right, 0, 0);
+
+ left_wrapped = wrap_store(left, w);
+ right_wrapped = wrap_store(right, w);
+
+ so_seek(left_wrapped, 0, 0);
+ so_seek(right_wrapped, 0, 0);
+
+ for(;;){
+
+ if(!so_fgets(left_wrapped, buf_l, sizeof(buf_l)))
+ break;
+
+ if(!so_fgets(right_wrapped, buf_r, sizeof(buf_r)))
+ break;
+
+ for(i=0, b=buf_l; i<w && *b && *b!='\r' && *b!='\n'; i++,b++){
+ pc(*b);
+ /* reduce accumulated width if an embed tag is discovered */
+ if(*b==TAG_EMBED)
+ i-=2;
+ }
+
+ if(buf_r[0]){
+ while(i<w){
+ pc(' ');
+ i++;
+ }
+
+ for(i=0, b=buf_r; i<w && *b && *b!='\r' && *b!='\n'; i++,b++)
+ pc(*b);
+ }
+
+ gf_puts(NEWLINE, pc);
+ }
+
+ so_give(&left_wrapped);
+ so_give(&right_wrapped);
+}
+
+
+void
+get_fingerprint(X509 *cert, const EVP_MD *type, char *buf, size_t maxLen)
+{
+ unsigned char md[128];
+ char *b;
+ unsigned int len, i;
+
+ len = sizeof(md);
+
+ X509_digest(cert, type, md, &len);
+
+ b = buf;
+ *b = 0;
+ for(i=0; i<len; i++){
+ if(b-buf+3>=maxLen)
+ break;
+
+ if(i != 0)
+ *b++ = ':';
+
+ snprintf(b, maxLen - (b-buf), "%02x", md[i]);
+ b+=2;
+ }
+}
+
+
+/*
+ * Wrap the text in the given store to the given width.
+ * A new store is created for the result.
+ */
+STORE_S *
+wrap_store(STORE_S *in, int width)
+{
+ STORE_S *result;
+ void *ws;
+ gf_io_t ipc,opc;
+
+ if(width<10)
+ width = 10;
+
+ result = so_get(CharStar, NULL, EDIT_ACCESS);
+ ws = gf_wrap_filter_opt(width, width, NULL, 0, 0);
+
+ gf_filter_init();
+ gf_link_filter(gf_wrap, ws);
+
+ gf_set_so_writec(&opc, result);
+ gf_set_so_readc(&ipc, in);
+
+ gf_pipe(ipc, opc);
+
+ gf_clear_so_readc(in);
+ gf_clear_so_writec(result);
+
+ return result;
+}
+
+
+void
+smime_config_screen(struct pine *ps, int edit_exceptions)
+{
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ SAVED_CONFIG_S *vsave;
+ OPT_SCREEN_S screen;
+ int ew, readonly_warning = 0;
+
+ dprint((9, "smime_config_screen()"));
+ ps->next_screen = SCREEN_FUN_NULL;
+
+ if(ps->fix_fixed_warning)
+ offer_to_fix_pinerc(ps);
+
+ ew = edit_exceptions ? ps_global->ew_for_except_vars : Main;
+
+ if(ps->restricted)
+ readonly_warning = 1;
+ else{
+ PINERC_S *prc = NULL;
+
+ switch(ew){
+ case Main:
+ prc = ps->prc;
+ break;
+ case Post:
+ prc = ps->post_prc;
+ break;
+ default:
+ break;
+ }
+
+ readonly_warning = prc ? prc->readonly : 1;
+ if(prc && prc->quit_to_edit){
+ quit_to_edit_msg(prc);
+ return;
+ }
+ }
+
+ smime_config_init_display(ps, &ctmp, &first_line);
+
+ vsave = save_smime_config_vars(ps);
+
+ memset(&screen, 0, sizeof(screen));
+ screen.deferred_ro_warning = readonly_warning;
+ switch(conf_scroll_screen(ps, &screen, first_line,
+ edit_exceptions ? _("SETUP S/MIME EXCEPTIONS")
+ : _("SETUP S/MIME"),
+ /* TRANSLATORS: Print something1 using something2.
+ configuration is something1 */
+ _("configuration"), 0)){
+ case 0:
+ break;
+
+ case 1:
+ write_pinerc(ps, ew, WRP_NONE);
+ break;
+
+ case 10:
+ revert_to_saved_smime_config(ps, vsave);
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 7, 10,
+ _("conf_scroll_screen bad ret in smime_config"));
+ break;
+ }
+
+ free_saved_smime_config(ps, &vsave);
+ smime_deinit();
+}
+
+
+int
+smime_related_var(struct pine *ps, struct variable *var)
+{
+ return(var == &ps->vars[V_PUBLICCERT_DIR] ||
+ var == &ps->vars[V_PUBLICCERT_CONTAINER] ||
+ var == &ps->vars[V_PRIVATEKEY_DIR] ||
+ var == &ps->vars[V_PRIVATEKEY_CONTAINER] ||
+ var == &ps->vars[V_CACERT_DIR] ||
+ var == &ps->vars[V_CACERT_CONTAINER]);
+}
+
+void
+smime_config_init_display(struct pine *ps, CONF_S **ctmp, CONF_S **first_line)
+{
+ char tmp[200];
+ int i, ind, ln = 0;
+ struct variable *vtmp;
+ CONF_S *ctmpb;
+ FEATURE_S *feature;
+
+ /* find longest variable name */
+ for(vtmp = ps->vars; vtmp->name; vtmp++){
+ if(!(smime_related_var(ps, vtmp)))
+ continue;
+
+ if((i = utf8_width(pretty_var_name(vtmp->name))) > ln)
+ ln = i;
+ }
+
+ for(vtmp = ps->vars; vtmp->name; vtmp++){
+ if(!(smime_related_var(ps, vtmp)))
+ continue;
+
+ new_confline(ctmp)->var = vtmp;
+ if(first_line && !*first_line)
+ *first_line = *ctmp;
+
+ (*ctmp)->valoffset = ln+3;
+ (*ctmp)->keymenu = &config_text_keymenu;
+ (*ctmp)->help = config_help(vtmp - ps->vars, 0);
+ (*ctmp)->tool = text_tool;
+
+ utf8_snprintf(tmp, sizeof(tmp), "%-*.100w =", ln, pretty_var_name(vtmp->name));
+ tmp[sizeof(tmp)-1] = '\0';
+
+ (*ctmp)->varname = cpystr(tmp);
+ (*ctmp)->varnamep = (*ctmp);
+ (*ctmp)->flags = CF_STARTITEM;
+ (*ctmp)->value = pretty_value(ps, *ctmp);
+ }
+
+
+ vtmp = &ps->vars[V_FEATURE_LIST];
+
+ new_confline(ctmp);
+ ctmpb = (*ctmp);
+ (*ctmp)->flags |= CF_NOSELECT | CF_STARTITEM;
+ (*ctmp)->keymenu = &config_checkbox_keymenu;
+ (*ctmp)->tool = NULL;
+
+ /* put a nice delimiter before list */
+ new_confline(ctmp)->var = NULL;
+ (*ctmp)->varnamep = ctmpb;
+ (*ctmp)->keymenu = &config_checkbox_keymenu;
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->tool = checkbox_tool;
+ (*ctmp)->valoffset = feature_indent();
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr("Set Feature Name");
+
+ new_confline(ctmp)->var = NULL;
+ (*ctmp)->varnamep = ctmpb;
+ (*ctmp)->keymenu = &config_checkbox_keymenu;
+ (*ctmp)->help = NO_HELP;
+ (*ctmp)->tool = checkbox_tool;
+ (*ctmp)->valoffset = feature_indent();
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr("--- ----------------------");
+
+ ind = feature_list_index(F_DONT_DO_SMIME);
+ feature = feature_list(ind);
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->varnamep = ctmpb;
+ (*ctmp)->keymenu = &config_checkbox_keymenu;
+ (*ctmp)->help = config_help(vtmp-ps->vars, feature->id);
+ (*ctmp)->tool = checkbox_tool;
+ (*ctmp)->valoffset = feature_indent();
+ (*ctmp)->varmem = ind;
+ (*ctmp)->value = pretty_value(ps, (*ctmp));
+
+ ind = feature_list_index(F_ENCRYPT_DEFAULT_ON);
+ feature = feature_list(ind);
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->varnamep = ctmpb;
+ (*ctmp)->keymenu = &config_checkbox_keymenu;
+ (*ctmp)->help = config_help(vtmp-ps->vars, feature->id);
+ (*ctmp)->tool = checkbox_tool;
+ (*ctmp)->valoffset = feature_indent();
+ (*ctmp)->varmem = ind;
+ (*ctmp)->value = pretty_value(ps, (*ctmp));
+
+ ind = feature_list_index(F_REMEMBER_SMIME_PASSPHRASE);
+ feature = feature_list(ind);
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->varnamep = ctmpb;
+ (*ctmp)->keymenu = &config_checkbox_keymenu;
+ (*ctmp)->help = config_help(vtmp-ps->vars, feature->id);
+ (*ctmp)->tool = checkbox_tool;
+ (*ctmp)->valoffset = feature_indent();
+ (*ctmp)->varmem = ind;
+ (*ctmp)->value = pretty_value(ps, (*ctmp));
+
+ ind = feature_list_index(F_SIGN_DEFAULT_ON);
+ feature = feature_list(ind);
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->varnamep = ctmpb;
+ (*ctmp)->keymenu = &config_checkbox_keymenu;
+ (*ctmp)->help = config_help(vtmp-ps->vars, feature->id);
+ (*ctmp)->tool = checkbox_tool;
+ (*ctmp)->valoffset = feature_indent();
+ (*ctmp)->varmem = ind;
+ (*ctmp)->value = pretty_value(ps, (*ctmp));
+
+#ifdef APPLEKEYCHAIN
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(_("Mac OS X specific features"));
+
+ ind = feature_list_index(F_PUBLICCERTS_IN_KEYCHAIN);
+ feature = feature_list(ind);
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->varnamep = ctmpb;
+ (*ctmp)->keymenu = &config_checkbox_keymenu;
+ (*ctmp)->help = config_help(vtmp-ps->vars, feature->id);
+ (*ctmp)->tool = checkbox_tool;
+ (*ctmp)->valoffset = feature_indent();
+ (*ctmp)->varmem = ind;
+ (*ctmp)->value = pretty_value(ps, (*ctmp));
+#endif /* APPLEKEYCHAIN */
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr("---------------------------------------------------------------------------");
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr(_("Be careful with the following commands, they REPLACE contents in the target"));
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT;
+ (*ctmp)->value = cpystr("---------------------------------------------------------------------------");
+
+ new_confline(ctmp);
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* copy public directory to container */
+ new_confline(ctmp);
+ (*ctmp)->tool = smime_helper_tool;
+ (*ctmp)->keymenu = &config_smime_helper_keymenu;
+ (*ctmp)->help = h_config_smime_transfer_pub_to_con;
+ (*ctmp)->value = cpystr(_("Transfer public certs FROM directory TO container"));
+ (*ctmp)->varmem = 1;
+
+ /* copy private directory to container */
+ new_confline(ctmp);
+ (*ctmp)->tool = smime_helper_tool;
+ (*ctmp)->keymenu = &config_smime_helper_keymenu;
+ (*ctmp)->help = h_config_smime_transfer_priv_to_con;
+ (*ctmp)->value = cpystr(_("Transfer private keys FROM directory TO container"));
+ (*ctmp)->varmem = 3;
+
+ /* copy cacert directory to container */
+ new_confline(ctmp);
+ (*ctmp)->tool = smime_helper_tool;
+ (*ctmp)->keymenu = &config_smime_helper_keymenu;
+ (*ctmp)->help = h_config_smime_transfer_cacert_to_con;
+ (*ctmp)->value = cpystr(_("Transfer CA certs FROM directory TO container"));
+ (*ctmp)->varmem = 5;
+
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* copy public container to directory */
+ new_confline(ctmp);
+ (*ctmp)->tool = smime_helper_tool;
+ (*ctmp)->keymenu = &config_smime_helper_keymenu;
+ (*ctmp)->help = h_config_smime_transfer_pub_to_dir;
+ (*ctmp)->value = cpystr(_("Transfer public certs FROM container TO directory"));
+ (*ctmp)->varmem = 2;
+
+ /* copy private container to directory */
+ new_confline(ctmp);
+ (*ctmp)->tool = smime_helper_tool;
+ (*ctmp)->keymenu = &config_smime_helper_keymenu;
+ (*ctmp)->help = h_config_smime_transfer_priv_to_dir;
+ (*ctmp)->value = cpystr(_("Transfer private keys FROM container TO directory"));
+ (*ctmp)->varmem = 4;
+
+ /* copy cacert container to directory */
+ new_confline(ctmp);
+ (*ctmp)->tool = smime_helper_tool;
+ (*ctmp)->keymenu = &config_smime_helper_keymenu;
+ (*ctmp)->help = h_config_smime_transfer_cacert_to_dir;
+ (*ctmp)->value = cpystr(_("Transfer CA certs FROM container TO directory"));
+ (*ctmp)->varmem = 6;
+
+#ifdef APPLEKEYCHAIN
+
+ new_confline(ctmp)->var = vtmp;
+ (*ctmp)->flags |= CF_NOSELECT | CF_B_LINE;
+
+ /* copy public container to keychain */
+ new_confline(ctmp);
+ (*ctmp)->tool = smime_helper_tool;
+ (*ctmp)->keymenu = &config_smime_helper_keymenu;
+ (*ctmp)->help = h_config_smime_transfer_pubcon_to_key;
+ (*ctmp)->value = cpystr(_("Transfer public certs FROM container TO keychain"));
+ (*ctmp)->varmem = 7;
+
+ /* copy public keychain to container */
+ new_confline(ctmp);
+ (*ctmp)->tool = smime_helper_tool;
+ (*ctmp)->keymenu = &config_smime_helper_keymenu;
+ (*ctmp)->help = h_config_smime_transfer_pubkey_to_con;
+ (*ctmp)->value = cpystr(_("Transfer public certs FROM keychain TO container"));
+ (*ctmp)->varmem = 8;
+
+#endif /* APPLEKEYCHAIN */
+}
+
+
+int
+smime_helper_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned flags)
+{
+ int rv = 0;
+
+ switch(cmd){
+ case MC_CHOICE:
+ switch((*cl)->varmem){
+ case 1:
+ rv = copy_publiccert_dir_to_container();
+ if(rv == 0)
+ q_status_message(SM_ORDER, 1, 3, _("Public certs transferred to container"));
+ else{
+ q_status_message(SM_ORDER, 3, 3, _("Problem transferring certs"));
+ rv = 0;
+ }
+
+ break;
+
+ case 2:
+ rv = copy_publiccert_container_to_dir();
+ if(rv == 0)
+ q_status_message(SM_ORDER, 1, 3, _("Public certs transferred to directory, delete Container config to use"));
+ else{
+ q_status_message(SM_ORDER, 3, 3, _("Problem transferring certs"));
+ rv = 0;
+ }
+
+ break;
+
+ case 3:
+ rv = copy_privatecert_dir_to_container();
+ if(rv == 0)
+ q_status_message(SM_ORDER, 1, 3, _("Private keys transferred to container"));
+ else{
+ q_status_message(SM_ORDER, 3, 3, _("Problem transferring certs"));
+ rv = 0;
+ }
+
+ break;
+
+ case 4:
+ rv = copy_privatecert_container_to_dir();
+ if(rv == 0)
+ q_status_message(SM_ORDER, 1, 3, _("Private keys transferred to directory, delete Container config to use"));
+ else{
+ q_status_message(SM_ORDER, 3, 3, _("Problem transferring certs"));
+ rv = 0;
+ }
+
+ break;
+
+ case 5:
+ rv = copy_cacert_dir_to_container();
+ if(rv == 0)
+ q_status_message(SM_ORDER, 1, 3, _("CA certs transferred to container"));
+ else{
+ q_status_message(SM_ORDER, 3, 3, _("Problem transferring certs"));
+ rv = 0;
+ }
+
+ break;
+
+ case 6:
+ rv = copy_cacert_container_to_dir();
+ if(rv == 0)
+ q_status_message(SM_ORDER, 1, 3, _("CA certs transferred to directory, delete Container config to use"));
+ else{
+ q_status_message(SM_ORDER, 3, 3, _("Problem transferring certs"));
+ rv = 0;
+ }
+
+ break;
+
+#ifdef APPLEKEYCHAIN
+ case 7:
+ rv = copy_publiccert_container_to_keychain();
+ if(rv == 0)
+ q_status_message(SM_ORDER, 1, 3, _("Public certs transferred to keychain"));
+ else{
+ q_status_message(SM_ORDER, 3, 3, _("Command not implemented yet"));
+ rv = 0;
+ }
+
+ break;
+
+ case 8:
+ rv = copy_publiccert_keychain_to_container();
+ if(rv == 0)
+ q_status_message(SM_ORDER, 1, 3, _("Public certs transferred to container"));
+ else{
+ q_status_message(SM_ORDER, 3, 3, _("Command not implemented yet"));
+ rv = 0;
+ }
+
+ break;
+#endif /* APPLEKEYCHAIN */
+
+ default:
+ rv = -1;
+ break;
+ }
+
+ break;
+
+ case MC_EXIT:
+ rv = config_exit_cmd(flags);
+ break;
+
+ default:
+ rv = -1;
+ break;
+ }
+
+ return rv;
+}
+
+
+/*
+ * Compare saved user_val with current user_val to see if it changed.
+ * If any have changed, change it back and take the appropriate action.
+ */
+void
+revert_to_saved_smime_config(struct pine *ps, SAVED_CONFIG_S *vsave)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *v;
+ int i, n;
+ int changed = 0;
+ char *pval, **apval, **lval, ***alval;
+
+ v = vsave;
+ for(vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!(smime_related_var(ps, vreal) || vreal==&ps->vars[V_FEATURE_LIST]))
+ continue;
+
+ if(vreal->is_list){
+ lval = LVAL(vreal, ew);
+ alval = ALVAL(vreal, ew);
+
+ if((v->saved_user_val.l && !lval)
+ || (!v->saved_user_val.l && lval))
+ changed++;
+ else if(!v->saved_user_val.l && !lval)
+ ;/* no change, nothing to do */
+ else
+ for(i = 0; v->saved_user_val.l[i] || lval[i]; i++)
+ if((v->saved_user_val.l[i]
+ && (!lval[i]
+ || strcmp(v->saved_user_val.l[i], lval[i])))
+ ||
+ (!v->saved_user_val.l[i] && lval[i])){
+ changed++;
+ break;
+ }
+
+ if(changed){
+ char **list;
+
+ if(alval){
+ if(*alval)
+ free_list_array(alval);
+
+ /* copy back the original one */
+ if(v->saved_user_val.l){
+ list = v->saved_user_val.l;
+ n = 0;
+ /* count how many */
+ while(list[n])
+ n++;
+
+ *alval = (char **)fs_get((n+1) * sizeof(char *));
+
+ for(i = 0; i < n; i++)
+ (*alval)[i] = cpystr(v->saved_user_val.l[i]);
+
+ (*alval)[n] = NULL;
+ }
+ }
+ }
+ }
+ else{
+ pval = PVAL(vreal, ew);
+ apval = APVAL(vreal, ew);
+
+ if((v->saved_user_val.p &&
+ (!pval || strcmp(v->saved_user_val.p, pval))) ||
+ (!v->saved_user_val.p && pval)){
+ /* It changed, fix it */
+ changed++;
+ if(apval){
+ /* free the changed value */
+ if(*apval)
+ fs_give((void **)apval);
+
+ if(v->saved_user_val.p)
+ *apval = cpystr(v->saved_user_val.p);
+ }
+ }
+ }
+
+ if(changed){
+ if(vreal == &ps->vars[V_FEATURE_LIST])
+ set_feature_list_current_val(vreal);
+ else
+ set_current_val(vreal, TRUE, FALSE);
+
+ fix_side_effects(ps, vreal, 1);
+ }
+ }
+}
+
+
+SAVED_CONFIG_S *
+save_smime_config_vars(struct pine *ps)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *vsave, *v;
+
+ vsave = (SAVED_CONFIG_S *)fs_get((V_LAST_VAR+1)*sizeof(SAVED_CONFIG_S));
+ memset((void *)vsave, 0, (V_LAST_VAR+1)*sizeof(SAVED_CONFIG_S));
+ v = vsave;
+ for(vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!(smime_related_var(ps, vreal) || vreal==&ps->vars[V_FEATURE_LIST]))
+ continue;
+
+ if(vreal->is_list){
+ int n, i;
+ char **list;
+
+ if(LVAL(vreal, ew)){
+ /* count how many */
+ n = 0;
+ list = LVAL(vreal, ew);
+ while(list[n])
+ n++;
+
+ v->saved_user_val.l = (char **)fs_get((n+1) * sizeof(char *));
+ memset((void *)v->saved_user_val.l, 0, (n+1)*sizeof(char *));
+ for(i = 0; i < n; i++)
+ v->saved_user_val.l[i] = cpystr(list[i]);
+
+ v->saved_user_val.l[n] = NULL;
+ }
+ }
+ else{
+ if(PVAL(vreal, ew))
+ v->saved_user_val.p = cpystr(PVAL(vreal, ew));
+ }
+ }
+
+ return(vsave);
+}
+
+
+void
+free_saved_smime_config(struct pine *ps, SAVED_CONFIG_S **vsavep)
+{
+ struct variable *vreal;
+ SAVED_CONFIG_S *v;
+
+ if(vsavep && *vsavep){
+ for(v = *vsavep, vreal = ps->vars; vreal->name; vreal++,v++){
+ if(!(smime_related_var(ps, vreal)))
+ continue;
+
+ if(vreal->is_list){ /* free saved_user_val.l */
+ if(v && v->saved_user_val.l)
+ free_list_array(&v->saved_user_val.l);
+ }
+ else if(v && v->saved_user_val.p)
+ fs_give((void **)&v->saved_user_val.p);
+ }
+
+ fs_give((void **)vsavep);
+ }
+}
+
+#endif /* SMIME */
diff --git a/alpine/smime.h b/alpine/smime.h
new file mode 100644
index 00000000..048a497c
--- /dev/null
+++ b/alpine/smime.h
@@ -0,0 +1,34 @@
+/*
+ * $Id: smime.h 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifdef SMIME
+#ifndef PINE_SMIME_INCLUDED
+#define PINE_SMIME_INCLUDED
+
+
+#include "../pith/state.h"
+#include "../pith/send.h"
+#include "../pith/smime.h"
+
+
+/* exported protoypes */
+int smime_get_passphrase(void);
+void smime_info_screen(struct pine *ps);
+void smime_config_screen(struct pine *, int edit_exceptions);
+int smime_related_var(struct pine *, struct variable *);
+
+
+#endif /* PINE_SMIME_INCLUDED */
+#endif /* SMIME */
diff --git a/alpine/status.c b/alpine/status.c
new file mode 100644
index 00000000..b51c640f
--- /dev/null
+++ b/alpine/status.c
@@ -0,0 +1,1282 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: status.c 840 2007-12-01 01:34:49Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ status.c
+ Functions that manage the status line (third from the bottom)
+ - put messages on the queue to be displayed
+ - display messages on the queue with timers
+ - check queue to figure out next timeout
+ - prompt for yes/no type of questions
+ ====*/
+
+#include "headers.h"
+#include "status.h"
+#include "keymenu.h"
+#include "mailview.h"
+#include "mailcmd.h"
+#include "busy.h"
+#include "after.h"
+
+#include "../pith/charconv/utf8.h"
+
+#include "../pith/conf.h"
+#include "../pith/bitmap.h"
+
+
+/*
+ * Internal queue of messages. The circular, double-linked list's
+ * allocated on demand, and cleared as each message is displayed.
+ */
+typedef struct message {
+ char *text;
+ unsigned flags:8;
+ unsigned shown:1;
+ unsigned saw_it:1;
+ unsigned pending_removal:1;
+ int min_display_time, max_display_time;
+ struct message *next, *prev;
+} SMQ_T;
+
+
+/*
+ * Internal prototypes
+ */
+void pause_for_and_dq_cur_msg(void);
+SMQ_T *top_of_queue(void);
+int new_info_msg_need_not_be_queued(void);
+int is_last_message(SMQ_T *);
+void d_q_status_message(void);
+int output_message(SMQ_T *, int);
+int output_message_modal(SMQ_T *, int);
+void delay_cmd_cue(int);
+int modal_bogus_input(UCS);
+int messages_in_queue(void);
+int status_message_remaining_nolock(void);
+void set_saw_it_to_zero();
+void mark_modals_done();
+SMQ_T *copy_status_queue(SMQ_T *);
+
+
+
+/*----------------------------------------------------------------------
+ Manage the second line from the bottom where status and error messages
+are displayed. A small queue is set up and messages are put on the queue
+by calling one of the q_status_message routines. Even though this is a queue
+most of the time message will go right on through. The messages are
+displayed just before the read for the next command, or when a read times
+out. Read timeouts occur every minute or so for new mail checking and every
+few seconds when there are still messages on the queue. Hopefully, this scheme
+will not let messages fly past that the user can't see.
+ ----------------------------------------------------------------------*/
+
+
+static SMQ_T *message_queue = NULL;
+static short needs_clearing = 0, /* Flag set by want_to()
+ and optionally_enter() */
+ prevstartcol, prevendcol;
+static char prevstatusbuf[6*MAX_SCREEN_COLS+1];
+static time_t displayed_time;
+
+
+/*----------------------------------------------------------------------
+ Put a message for the status line on the queue
+
+ Args: time -- the min time in seconds to display the message
+ message -- message string
+
+ Result: queues message on queue represented by static variables
+
+ This puts a single message on the queue to be shown.
+ ----------*/
+void
+q_status_message(int flags, int min_time, int max_time, char *message)
+{
+ SMQ_T *new, *q;
+ char *clean_msg;
+ size_t mlen;
+
+ status_message_lock();
+
+ /*
+ * If there is already a message queued and
+ * new message is just informational, discard it.
+ */
+ if(flags & SM_INFO && new_info_msg_need_not_be_queued()){
+ status_message_unlock();
+ return;
+ }
+
+ /*
+ * By convention, we have min_time equal to zero in messages which we
+ * think are not as important, so-called comfort messages. We have
+ * min_time >= 3 for messages which we think the user should see for
+ * sure. Some users don't like to wait so we've provided a way for them
+ * to live on the edge.
+ * status_msg_delay == -1 => min time == MIN(0, min_time)
+ * status_msg_delay == -2 => min time == MIN(1, min_time)
+ * status_msg_delay == -3 => min time == MIN(2, min_time)
+ * ...
+ */
+ if(ps_global->status_msg_delay < 0)
+ min_time = MIN(-1 - ps_global->status_msg_delay, min_time);
+
+ /* The 40 is room for 40 escaped control characters */
+ mlen = strlen(message) + 40;
+ clean_msg = (char *)fs_get(mlen + 1);
+ iutf8ncpy(clean_msg, message, mlen); /* does the cleaning */
+
+ clean_msg[mlen] = '\0';
+
+ if((q = message_queue) != NULL){ /* the queue exists */
+
+ /*
+ * Scan through all of the messages currently in the queue.
+ * If the new message is already queued, don't add it again.
+ */
+ do {
+ if(!q->pending_removal && q->text && !strcmp(q->text, clean_msg)){
+ q->shown = 0;
+ if(q->min_display_time < min_time)
+ q->min_display_time = min_time;
+
+ if(q->max_display_time < max_time)
+ q->max_display_time = max_time;
+
+ dprint((9, "q_status_message(%s): skipping duplicate msg\n",
+ clean_msg ? clean_msg : "?"));
+
+ if(clean_msg)
+ fs_give((void **)&clean_msg);
+
+ status_message_unlock();
+ return;
+ }
+
+ q = q->next;
+
+ } while(q != message_queue);
+ }
+
+ new = (SMQ_T *)fs_get(sizeof(SMQ_T));
+ memset(new, 0, sizeof(SMQ_T));
+ new->text = clean_msg;
+ new->min_display_time = min_time;
+ new->max_display_time = max_time;
+ new->flags = flags;
+ if(message_queue){
+ new->next = message_queue;
+ new->prev = message_queue->prev;
+ new->prev->next = message_queue->prev = new;
+ }
+ else
+ message_queue = new->next = new->prev = new;
+
+ status_message_unlock();
+
+ dprint((9, "q_status_message(%s)\n",
+ clean_msg ? clean_msg : "?"));
+}
+
+
+/*----------------------------------------------------------------------
+ Mark the status line as dirty so it gets cleared next chance
+ ----*/
+void
+mark_status_dirty(void)
+{
+ mark_status_unknown();
+ needs_clearing++;
+}
+
+
+/*----------------------------------------------------------------------
+ Cause status line drawing optimization to be turned off, because we
+ don't know what the status line looks like.
+ ----*/
+void
+mark_status_unknown(void)
+{
+ prevstartcol = -1;
+ prevendcol = -1;
+ prevstatusbuf[0] = '\0';
+}
+
+
+/*----------------------------------------------------------------------
+ Wait a suitable amount of time for the currently displayed message
+ ----*/
+void
+pause_for_and_dq_cur_msg(void)
+{
+ if(top_of_queue()){
+ int w;
+
+ if((w = status_message_remaining_nolock()) != 0){
+ delay_cmd_cue(1);
+ sleep(w);
+ delay_cmd_cue(0);
+ }
+
+ d_q_status_message();
+ }
+}
+
+
+/*
+ * This relies on the global displayed_time being properly set
+ * for this message.
+ */
+void
+pause_for_and_mark_specific_msg(SMQ_T *msg)
+{
+ if(msg){
+ int w;
+
+ w = (int) (displayed_time - time(0)) + msg->min_display_time;
+ w = (w > 0) ? w : 0;
+ if(w){
+ delay_cmd_cue(1);
+ sleep(w);
+ delay_cmd_cue(0);
+ }
+
+ msg->pending_removal = 1;
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Time remaining for current message's minimum display
+ ----*/
+int
+status_message_remaining(void)
+{
+ int ret;
+
+ status_message_lock();
+ ret = status_message_remaining_nolock();
+ status_message_unlock();
+
+ return(ret);
+}
+
+
+int
+status_message_remaining_nolock(void)
+{
+ SMQ_T *q;
+ int d = 0;
+
+ if((q = top_of_queue()) != NULL)
+ d = (int) (displayed_time - time(0)) + q->min_display_time;
+
+ return((d > 0) ? d : 0);
+}
+
+
+/*
+ * Return first message in queue that isn't pending_removal.
+ */
+SMQ_T *
+top_of_queue(void)
+{
+ SMQ_T *p;
+
+ if((p = message_queue) != NULL){
+ do
+ if(!p->pending_removal)
+ return(p);
+ while((p = p->next) != message_queue);
+ }
+
+ return(NULL);
+}
+
+
+int
+new_info_msg_need_not_be_queued(void)
+{
+ SMQ_T *q;
+
+ if(status_message_remaining_nolock() > 0)
+ return 1;
+
+ if((q = top_of_queue()) != NULL && (q = q->next) != message_queue){
+ do
+ if(!q->pending_removal && !(q->flags & SM_INFO))
+ return 1;
+ while((q = q->next) != message_queue);
+ }
+
+ return 0;
+}
+
+
+/*----------------------------------------------------------------------
+ Find out how many messages are queued for display
+
+ Args: dtime -- will get set to minimum display time for current message
+
+ Result: number of messages in the queue.
+
+ ---------*/
+int
+messages_queued(long int *dtime)
+{
+ SMQ_T *q;
+ int ret;
+
+ status_message_lock();
+ if(dtime && (q = top_of_queue()) != NULL)
+ *dtime = (long) MAX(q->min_display_time, 1L);
+
+ ret = ps_global->in_init_seq ? 0 : messages_in_queue();
+
+ status_message_unlock();
+
+ return(ret);
+}
+
+
+/*----------------------------------------------------------------------
+ Return number of messages in queue
+ ---------*/
+int
+messages_in_queue(void)
+{
+ int n = 0;
+ SMQ_T *p;
+
+ if((p = message_queue) != NULL){
+ do
+ if(!p->pending_removal)
+ n++;
+ while((p = p->next) != message_queue);
+ }
+
+ return(n);
+}
+
+
+/*----------------------------------------------------------------------
+ Return last message queued
+ ---------*/
+char *
+last_message_queued(void)
+{
+ SMQ_T *p, *r = NULL;
+ char *ret = NULL;
+
+ status_message_lock();
+ if((p = message_queue) != NULL){
+ do
+ if(p->flags & SM_ORDER && !p->pending_removal)
+ r = p;
+ while((p = p->next) != message_queue);
+ }
+
+ ret = (r && r->text) ? cpystr(r->text) : NULL;
+
+ status_message_unlock();
+
+ return(ret);
+}
+
+
+int
+is_last_message(SMQ_T *msg)
+{
+ SMQ_T *p, *r = NULL;
+
+ if(msg && !msg->pending_removal){
+ if((p = message_queue) != NULL){
+ do
+ if(!p->pending_removal)
+ r = p;
+ while((p = p->next) != message_queue);
+ }
+ }
+
+ return(r && msg && (r == msg));
+}
+
+
+/*----------------------------------------------------------------------
+ Update status line, clearing or displaying a message
+
+ Arg: command -- The command that is about to be executed
+
+ Result: status line cleared or
+ next message queued is displayed or
+ current message is redisplayed.
+ if next message displayed, it's min display time
+ is returned else if message already displayed, it's
+ time remaining on the display is returned, else 0.
+
+ This is called when ready to display the next message, usually just
+before reading the next command from the user. We pass in the nature
+of the command because it affects what we do here. If the command just
+executed by the user is a redraw screen, we don't want to reset or go to
+next message because it might not have been seen. Also if the command
+is just a noop, which are usually executed when checking for new mail
+and happen every few minutes, we don't clear the message.
+
+If it was really a command and there's nothing more to show, then we
+clear, because we know the user has seen the message. In this case the
+user might be typing commands very quickly and miss a message, so
+there is a time stamp and time check that each message has been on the
+screen for a few seconds. If it hasn't we just return and let it be
+taken care of next time.
+----------------------------------------------------------------------*/
+int
+display_message(UCS command)
+{
+ SMQ_T *q, *copy_of_q;
+ int need_to_unlock;
+ int ding;
+
+ if(ps_global == NULL || ps_global->ttyo == NULL
+ || ps_global->ttyo->screen_rows < 1 || ps_global->in_init_seq)
+ return(0);
+
+ status_message_lock();
+
+ /*---- Deal with any previously displayed message ----*/
+ if((q = top_of_queue()) != NULL && q->shown){
+ int rv = -1;
+
+ if(command == ctrl('L')){ /* just repaint it, and go on */
+ mark_status_unknown();
+ mark_keymenu_dirty();
+ mark_titlebar_dirty();
+ rv = 0;
+ }
+ else{ /* ensure sufficient time's passed */
+ time_t now;
+ int diff;
+
+ now = time(0);
+ diff = (int)(displayed_time - now)
+ + ((command == NO_OP_COMMAND || command == NO_OP_IDLE)
+ ? q->max_display_time
+ : q->min_display_time);
+ dprint((9,
+ "STATUS: diff:%d, displayed: %ld, now: %ld\n",
+ diff, (long) displayed_time, (long) now));
+ if(diff > 0)
+ rv = diff; /* check again next time */
+ else if(is_last_message(q)
+ && (command == NO_OP_COMMAND || command == NO_OP_IDLE)
+ && q->max_display_time)
+ rv = 0; /* last msg, no cmd, has max */
+ }
+
+ if(rv >= 0){ /* leave message displayed? */
+ if(prevstartcol < 0){ /* need to redisplay it? */
+ ding = q->flags & SM_DING;
+ q->flags &= ~SM_DING;
+ if(q->flags & SM_MODAL && !q->shown){
+ copy_of_q = copy_status_queue(q);
+ mark_modals_done();
+ status_message_unlock();
+ output_message_modal(copy_of_q, ding);
+ }
+ else{
+ output_message(q, ding);
+ status_message_unlock();
+ }
+ }
+ else
+ status_message_unlock();
+
+ return(rv);
+ }
+
+ d_q_status_message(); /* remove it from queue and */
+ needs_clearing++; /* clear the line if needed */
+ }
+
+ if(!top_of_queue() && (command == ctrl('L') || needs_clearing)){
+ int inverse;
+ struct variable *vars = ps_global->vars;
+ char *last_bg = NULL;
+
+ dprint((9, "Clearing status line\n"));
+ inverse = InverseState(); /* already in inverse? */
+ if(inverse && pico_usingcolor() && VAR_STATUS_FORE_COLOR &&
+ VAR_STATUS_BACK_COLOR){
+ last_bg = pico_get_last_bg_color();
+ pico_set_nbg_color(); /* so ClearLine will clear in bg color */
+ }
+
+ ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
+ if(last_bg){
+ (void)pico_set_bg_color(last_bg);
+ if(last_bg)
+ fs_give((void **)&last_bg);
+ }
+
+ mark_status_unknown();
+ if(command == ctrl('L')){
+ mark_keymenu_dirty();
+ mark_titlebar_dirty();
+ }
+ }
+
+ need_to_unlock = 1;
+
+ /* display next message, weeding out 0 display times */
+ for(q = top_of_queue(); q && !q->shown; q = top_of_queue()){
+ if(q->min_display_time || is_last_message(q)){
+ displayed_time = time(0);
+ ding = q->flags & SM_DING;
+ q->flags &= ~SM_DING;
+ if(q->flags & SM_MODAL && !q->shown){
+ copy_of_q = copy_status_queue(q);
+ mark_modals_done();
+ status_message_unlock();
+ output_message_modal(copy_of_q, ding);
+ need_to_unlock = 0;
+ }
+ else
+ output_message(q, ding);
+
+ break;
+ }
+ /* zero display time message, log it, delete it */
+ else{
+ if(q->text){
+ char buf[1000];
+ char *append = " [not actually shown]";
+ char *ptr;
+ size_t len;
+
+ len = strlen(q->text) + strlen(append);
+ if(len < sizeof(buf))
+ ptr = buf;
+ else
+ ptr = (char *) fs_get((len+1) * sizeof(char));
+
+ strncpy(ptr, q->text, len);
+ ptr[len] = '\0';
+ strncat(ptr, append, len+1-1-strlen(ptr));
+ ptr[len] = '\0';
+ add_review_message(ptr, -1);
+ if(ptr != buf)
+ fs_give((void **) &ptr);
+ }
+
+ d_q_status_message();
+ }
+ }
+
+ needs_clearing = 0; /* always cleared or written */
+ fflush(stdout);
+
+ if(need_to_unlock)
+ status_message_unlock();
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Display all the messages on the queue as quickly as possible
+ ----*/
+void
+flush_status_messages(int skip_last_pause)
+{
+ SMQ_T *q, *copy_of_q;
+ int ding;
+
+start_over:
+ status_message_lock();
+
+ for(q = top_of_queue(); q; q = top_of_queue()){
+ /* don't have to wait for this one */
+ if(is_last_message(q) && skip_last_pause && q->shown)
+ break;
+
+ if(q->shown)
+ pause_for_and_dq_cur_msg();
+
+ /* find next one we need to show and show it */
+ for(q = top_of_queue(); q && !q->shown; q = top_of_queue()){
+ if((q->min_display_time || is_last_message(q))){
+ displayed_time = time(0);
+ ding = q->flags & SM_DING;
+ q->flags &= ~SM_DING;
+ if(q->flags & SM_MODAL){
+ copy_of_q = copy_status_queue(q);
+ mark_modals_done();
+ status_message_unlock();
+ output_message_modal(copy_of_q, ding);
+
+ /*
+ * Because we unlock the message queue in order
+ * to display modal messages we have to worry
+ * about the queue being changed while we have
+ * it unlocked. So start the whole process over.
+ */
+ goto start_over;
+ }
+ else
+ output_message(q, ding);
+ }
+ else{
+ d_q_status_message();
+ }
+ }
+ }
+
+ status_message_unlock();
+}
+
+
+/*----------------------------------------------------------------------
+ Make sure any and all SM_ORDER messages get displayed.
+
+ Note: This flags the message line as having nothing displayed.
+ The idea is that it's a function called by routines that want
+ the message line for a prompt or something, and that they're
+ going to obliterate the message anyway.
+ ----*/
+void
+flush_ordered_messages(void)
+{
+ SMQ_T *q, *copy_of_q;
+ int firsttime = 1;
+ int ding;
+
+ status_message_lock();
+
+ set_saw_it_to_zero();
+
+start_over2:
+ if(!firsttime)
+ status_message_lock();
+ else
+ firsttime = 0;
+
+ if((q = message_queue) != NULL){
+ do{
+ if(q->pending_removal || q->saw_it || q->shown){
+ if(!q->pending_removal && q->shown)
+ pause_for_and_mark_specific_msg(q);
+
+ q->saw_it = 1;
+ q = (q->next != message_queue) ? q->next : NULL;
+ }
+ else{
+ /* find next one we need to show and show it */
+ do{
+ if(q->pending_removal || q->saw_it || q->shown){
+ q->saw_it = 1;
+ q = (q->next != message_queue) ? q->next : NULL;
+ }
+ else{
+ if((q->flags & (SM_ORDER | SM_MODAL))
+ && q->min_display_time){
+ displayed_time = time(0);
+ ding = q->flags & SM_DING;
+ q->flags &= ~SM_DING;
+ if(q->flags & SM_MODAL){
+ copy_of_q = copy_status_queue(q);
+ mark_modals_done();
+ q->saw_it = 1;
+ status_message_unlock();
+ output_message_modal(copy_of_q, ding);
+ goto start_over2;
+ }
+ else{
+ output_message(q, ding);
+ }
+ }
+ else{
+ q->saw_it = 1;
+ if(!(q->flags & SM_ASYNC))
+ q->pending_removal = 1;
+
+ q = (q->next != message_queue) ? q->next : NULL;
+ }
+ }
+ }while(q && !q->shown);
+ }
+ }while(q);
+ }
+
+ status_message_unlock();
+}
+
+
+void
+set_saw_it_to_zero()
+{
+ SMQ_T *q;
+
+ /* set saw_it to zero */
+ if((q = message_queue) != NULL){
+ do{
+ q->saw_it = 0;
+ q = (q->next != message_queue) ? q->next : NULL;
+ }while(q);
+ }
+}
+
+
+void
+mark_modals_done()
+{
+ SMQ_T *q;
+
+ /* set shown to one */
+ if((q = message_queue) != NULL){
+ do{
+ if(q->flags & SM_MODAL){
+ q->shown = 1;
+ q->pending_removal = 1;
+ }
+
+ q = (q->next != message_queue) ? q->next : NULL;
+ }while(q);
+ }
+}
+
+
+/*
+ * Caller needs to free the memory.
+ */
+SMQ_T *
+copy_status_queue(SMQ_T *start)
+{
+ SMQ_T *q, *new, *head = NULL;
+
+ if((q = start) != NULL){
+ do{
+ new = (SMQ_T *) fs_get(sizeof(SMQ_T));
+ *new = *q;
+
+ if(q->text)
+ new->text = cpystr(q->text);
+ else
+ new->text = NULL;
+
+ if(head){
+ new->next = head;
+ new->prev = head->prev;
+ new->prev->next = head->prev = new;
+ }
+ else
+ head = new->next = new->prev = new;
+
+ q = (q->next != start) ? q->next : NULL;
+ }while(q);
+ }
+
+ return(head);
+}
+
+
+/*----------------------------------------------------------------------
+ Removes the first message from the message queue.
+ Returns 0 if all was well, -1 if had to skip the removal and
+ message_queue is unchanged.
+ ----*/
+void
+d_q_status_message(void)
+{
+ int the_last_one = 0;
+ SMQ_T *q, *p, *last_one;
+
+ /* mark the top one for removal */
+ if((p = top_of_queue()) != NULL)
+ p->pending_removal = 1;
+
+ if(message_queue){
+
+ /* flush out pending removals */
+ the_last_one = 0; /* loop control */
+ q = message_queue;
+ last_one = message_queue->prev;
+ while(!the_last_one){
+ if(q == last_one)
+ the_last_one++;
+
+ if(q->pending_removal){
+ p = q;
+ if(q == q->next){ /* last item in queue */
+ q = message_queue = NULL;
+ }
+ else{
+ p->next->prev = p->prev;
+ q = p->prev->next = p->next; /* here's the increment */
+ if(message_queue == p)
+ message_queue = q;
+ }
+
+ if(p){
+ if(p->text)
+ fs_give((void **) &p->text);
+
+ fs_give((void **) &p);
+ }
+ }
+ else
+ q = q->next;
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------
+ Actually output the message to the screen
+
+ Args: message -- The message to output
+ from_alarm_handler -- Called from alarm signal handler.
+ We don't want to add this message to the review
+ message list since we may mess with the malloc
+ arena here, and the interrupt may be from
+ the middle of something malloc'ing.
+ ----*/
+int
+status_message_write(char *message, int from_alarm_handler)
+{
+ int col, row, max_width, invert;
+ int bytes;
+ char newstatusbuf[6*MAX_SCREEN_COLS + 1];
+ struct variable *vars = ps_global->vars;
+ COLOR_PAIR *lastc = NULL, *newc;
+
+ if(!from_alarm_handler)
+ add_review_message(message, -1);
+
+ invert = !InverseState(); /* already in inverse? */
+ row = MAX(0, ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
+
+ /* Put [] around message and truncate to screen width */
+ max_width = ps_global->ttyo != NULL ? ps_global->ttyo->screen_cols : 80;
+ max_width = MIN(max_width, MAX_SCREEN_COLS);
+ newstatusbuf[0] = '[';
+ newstatusbuf[1] = '\0';
+
+ bytes = utf8_to_width(newstatusbuf+1, message, sizeof(newstatusbuf)-1, max_width-2, NULL);
+ newstatusbuf[1+bytes] = ']';
+ newstatusbuf[1+bytes+1] = '\0';
+
+ if(prevstartcol == -1 || strcmp(newstatusbuf, prevstatusbuf)){
+ UCS *prevbuf = NULL, *newbuf = NULL;
+ size_t plen;
+
+ if(prevstartcol != -1){
+ prevbuf = utf8_to_ucs4_cpystr(prevstatusbuf);
+ newbuf = utf8_to_ucs4_cpystr(newstatusbuf);
+ }
+
+ /*
+ * Simple optimization. If the strings are the same length
+ * and width just skip leading and trailing strings of common
+ * characters and only write whatever's in between. Otherwise,
+ * write out the whole thing.
+ * We could do something more complicated but the odds of
+ * getting any optimization goes way down if they aren't the
+ * same length and width and the complexity goes way up.
+ */
+ if(prevbuf && newbuf
+ && (plen=ucs4_strlen(prevbuf)) == ucs4_strlen(newbuf)
+ && ucs4_str_width(prevbuf) == ucs4_str_width(newbuf)){
+ UCS *start_of_unequal, *end_of_unequal;
+ UCS *pprev, *endnewbuf;
+ char *to_screen = NULL;
+ int column;
+
+ pprev = prevbuf;
+ start_of_unequal = newbuf;
+ endnewbuf = newbuf + plen;
+ col = column = prevstartcol;
+
+ while(start_of_unequal < endnewbuf && (*start_of_unequal) == (*pprev)){
+ int w;
+
+ w = wcellwidth(*start_of_unequal);
+ if(w >= 0)
+ column += w;
+
+ pprev++;
+ start_of_unequal++;
+ }
+
+ end_of_unequal = endnewbuf-1;
+ pprev = prevbuf + plen - 1;
+
+ while(end_of_unequal > start_of_unequal && (*end_of_unequal) == (*pprev)){
+ *end_of_unequal = '\0';
+ pprev--;
+ end_of_unequal--;
+ }
+
+ if(pico_usingcolor() && VAR_STATUS_FORE_COLOR &&
+ VAR_STATUS_BACK_COLOR &&
+ pico_is_good_color(VAR_STATUS_FORE_COLOR) &&
+ pico_is_good_color(VAR_STATUS_BACK_COLOR)){
+ lastc = pico_get_cur_color();
+ if(lastc){
+ newc = new_color_pair(VAR_STATUS_FORE_COLOR,
+ VAR_STATUS_BACK_COLOR);
+ (void)pico_set_colorp(newc, PSC_NONE);
+ free_color_pair(&newc);
+ }
+ }
+ else if(invert)
+ StartInverse();
+
+ /* PutLine wants UTF-8 string */
+ if(start_of_unequal && (*start_of_unequal))
+ to_screen = ucs4_to_utf8_cpystr(start_of_unequal);
+
+ if(to_screen){
+ PutLine0(row, column, to_screen);
+ fs_give((void **) &to_screen);
+
+ strncpy(prevstatusbuf, newstatusbuf, sizeof(prevstatusbuf));
+ prevstatusbuf[sizeof(prevstatusbuf)-1] = '\0';
+ }
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+ else if(invert)
+ EndInverse();
+ }
+ else{
+ if(pico_usingcolor())
+ lastc = pico_get_cur_color();
+
+ if(!invert && pico_usingcolor() && VAR_STATUS_FORE_COLOR &&
+ VAR_STATUS_BACK_COLOR &&
+ pico_is_good_color(VAR_STATUS_FORE_COLOR) &&
+ pico_is_good_color(VAR_STATUS_BACK_COLOR))
+ pico_set_nbg_color(); /* so ClearLine uses bg color */
+
+ ClearLine(row);
+
+ if(pico_usingcolor() && VAR_STATUS_FORE_COLOR &&
+ VAR_STATUS_BACK_COLOR &&
+ pico_is_good_color(VAR_STATUS_FORE_COLOR) &&
+ pico_is_good_color(VAR_STATUS_BACK_COLOR)){
+ if(lastc){
+ newc = new_color_pair(VAR_STATUS_FORE_COLOR,
+ VAR_STATUS_BACK_COLOR);
+ (void)pico_set_colorp(newc, PSC_NONE);
+ free_color_pair(&newc);
+ }
+ }
+ else if(invert){
+ if(lastc)
+ free_color_pair(&lastc);
+
+ StartInverse();
+ }
+
+ col = Centerline(row, newstatusbuf);
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+ else if(invert)
+ EndInverse();
+
+ strncpy(prevstatusbuf, newstatusbuf, sizeof(prevstatusbuf));
+ prevstatusbuf[sizeof(prevstatusbuf)-1] = '\0';
+ prevstartcol = col;
+ prevendcol = col + utf8_width(prevstatusbuf) - 1;
+ }
+
+ /* move cursor to a consistent position */
+ if(F_ON(F_SHOW_CURSOR, ps_global))
+ MoveCursor(row, MIN(MAX(0,col+1),ps_global->ttyo->screen_cols-1));
+ else
+ MoveCursor(row, 0);
+
+ fflush(stdout);
+
+ if(prevbuf)
+ fs_give((void **) &prevbuf);
+
+ if(newbuf)
+ fs_give((void **) &newbuf);
+ }
+ else
+ col = prevstartcol;
+
+ return(col);
+}
+
+
+/*----------------------------------------------------------------------
+ Write the given status message to the display.
+
+ Args: mq_entry -- pointer to message queue entry to write.
+
+ ----*/
+int
+output_message(SMQ_T *mq_entry, int ding)
+{
+ int col = 0;
+
+ dprint((9, "output_message(%s)\n",
+ (mq_entry && mq_entry->text) ? mq_entry->text : "?"));
+
+ if(ding && F_OFF(F_QUELL_BEEPS, ps_global)){
+ Writechar(BELL, 0); /* ring bell */
+ fflush(stdout);
+ }
+
+ if(!(mq_entry->flags & SM_MODAL)){
+ col = status_message_write(mq_entry->text, 0);
+ if(ps_global->status_msg_delay > 0){
+ if(F_ON(F_SHOW_CURSOR, ps_global))
+ /* col+1 because col is "[" character */
+ MoveCursor(ps_global->ttyo->screen_rows-FOOTER_ROWS(ps_global),
+ MIN(MAX(0,col+1),ps_global->ttyo->screen_cols-1));
+ else
+ MoveCursor(ps_global->ttyo->screen_rows-FOOTER_ROWS(ps_global), 0);
+
+ fflush(stdout);
+ sleep(ps_global->status_msg_delay);
+ }
+
+ mq_entry->shown = 1;
+ }
+
+ return(col);
+}
+
+
+/*
+ * This is split off from output_message due to the locking
+ * on the status message queue data. This calls scrolltool
+ * which can call back into q_status and so on. So instead of
+ * keeping the queue locked for a long time we copy the
+ * data, unlock the queue, and display the data.
+ */
+int
+output_message_modal(SMQ_T *mq_entry, int ding)
+{
+ int rv = 0;
+ SMQ_T *m, *mnext;
+
+ if(!mq_entry)
+ return(rv);
+
+ dprint((9, "output_message_modal(%s)\n",
+ (mq_entry && mq_entry->text) ? mq_entry->text : "?"));
+
+ if(ding && F_OFF(F_QUELL_BEEPS, ps_global)){
+ Writechar(BELL, 0); /* ring bell */
+ fflush(stdout);
+ }
+
+ if(!mq_entry->shown){
+ int i = 0,
+ pad = MAX(0, (ps_global->ttyo->screen_cols - 59) / 2);
+ char *p, *q, *s, *t;
+ SCROLL_S sargs;
+
+ /* Count the number of modal messsages and add up their lengths. */
+ for(m = mq_entry->next; m != mq_entry; m = m->next)
+ if((m->flags & SM_MODAL) && !m->shown){
+ i++;
+ }
+
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%*s%s\n%*s%s\n%*s%s\n%*s%s\n%*s%s\n%*s%s\n%*s%s\n\n",
+ /* 1 2 3 4 5 6*/
+ /*23456789012345678901234567890123456789012345678901234567890*/
+ pad, "",
+ "***********************************************************",
+ pad, "", i ?
+ "* What follows are advisory messages. After reading them *" :
+ "* What follows is an advisory message. After reading it *",
+
+ pad, "",
+ "* simply hit \"Return\" to continue your Alpine session. *",
+ pad, "",
+ "* *",
+ pad, "", i ?
+ "* To review these messages later, press 'J' from the *" :
+ "* To review this message later, press 'J' from the *",
+ pad, "",
+ "* MAIN MENU. *",
+ pad, "",
+ "***********************************************************");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ t = tmp_20k_buf + strlen(tmp_20k_buf);
+
+ m = mq_entry;
+ do{
+ if((m->flags & SM_MODAL) && !m->shown){
+ int indent;
+
+ indent = ps_global->ttyo->screen_cols > 80
+ ? (ps_global->ttyo->screen_cols - 80) / 3 : 0;
+
+ if(t - tmp_20k_buf > 19000){
+ snprintf(t, SIZEOF_20KBUF-(t-tmp_20k_buf), "\n%*s* * * Running out of buffer space * * *", indent, "");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ t += strlen(t);
+ snprintf(t, SIZEOF_20KBUF-(t-tmp_20k_buf), "\n%*s* * * Press RETURN for more messages * * *", indent, "");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ break;
+ }
+
+ add_review_message(m->text, -1);
+
+ if((p = strstr(m->text, "[ALERT]")) != NULL){
+ snprintf(t, SIZEOF_20KBUF-(t-tmp_20k_buf), "%*.*s\n", indent + p - m->text, p - m->text, m->text);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ t += strlen(t);
+
+ for(p += 7; *p && isspace((unsigned char)*p); p++)
+ ;
+ indent += 8;
+ }
+ else{
+ p = m->text;
+ }
+
+ while(strlen(p) > ps_global->ttyo->screen_cols - 2 * indent){
+ for(q = p + ps_global->ttyo->screen_cols - 2 * indent;
+ q > p && !isspace((unsigned char)*q); q--)
+ ;
+
+ snprintf(t, SIZEOF_20KBUF-(t-tmp_20k_buf), "\n%*.*s", indent + q - p, q - p, p);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ t += strlen(t);
+ p = q + 1;
+ }
+
+ snprintf(t, SIZEOF_20KBUF-(t-tmp_20k_buf), "\n%*s%s", indent, "", p);
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ t += strlen(t);
+
+ if(i--){
+ snprintf(t, SIZEOF_20KBUF-(t-tmp_20k_buf), "\n\n%*s\n", pad + 30, "- - -");
+ tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
+ t += strlen(t);
+ }
+ }
+ m = m->next;
+ } while(m != mq_entry);
+
+ s = cpystr(tmp_20k_buf);
+ ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
+
+ memset(&sargs, 0, sizeof(SCROLL_S));
+ sargs.text.text = s;
+ sargs.text.src = CharStar;
+ sargs.bar.title = "Status Message";
+ sargs.bogus_input = modal_bogus_input;
+ sargs.no_stat_msg = 1;
+ sargs.keys.menu = &modal_message_keymenu;
+ setbitmap(sargs.keys.bitmap);
+
+ scrolltool(&sargs);
+
+ fs_give((void **)&s);
+ ps_global->mangled_screen = 1;
+ }
+
+ /* free the passed in queue */
+ m = mq_entry;
+ do{
+ if(m->text)
+ fs_give((void **) &m->text);
+
+ mnext = m->next;
+ fs_give((void **) &m);
+ m = mnext;
+ } while(m != mq_entry);
+
+ return(rv);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Write or clear delay cue
+
+ Args: on -- whether to turn it on or not
+
+ ----*/
+void
+delay_cmd_cue(int on)
+{
+ COLOR_PAIR *lastc;
+ struct variable *vars = ps_global->vars;
+
+ if(prevstartcol >= 0 && prevendcol < ps_global->ttyo->screen_cols){
+ MoveCursor(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global),
+ MAX(prevstartcol - 1, 0));
+ lastc = pico_set_colors(VAR_STATUS_FORE_COLOR, VAR_STATUS_BACK_COLOR,
+ PSC_REV|PSC_RET);
+ Write_to_screen(on ? (prevstartcol ? "[>" : ">")
+ : (prevstartcol ? " [" : "["));
+
+ MoveCursor(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global),
+ prevendcol);
+
+ Write_to_screen(on ? (prevendcol < ps_global->ttyo->screen_cols-1 ? "<]" : "<")
+ : (prevendcol < ps_global->ttyo->screen_cols-1 ? "] " : "]"));
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ if(F_ON(F_SHOW_CURSOR, ps_global))
+ MoveCursor(ps_global->ttyo->screen_rows-FOOTER_ROWS(ps_global),
+ MIN(MAX(0,(prevstartcol + on ? 2 : 1)),ps_global->ttyo->screen_cols-1));
+ else
+ MoveCursor(ps_global->ttyo->screen_rows-FOOTER_ROWS(ps_global), 0);
+ }
+
+ fflush(stdout);
+#ifdef _WINDOWS
+ mswin_setcursor ((on) ? MSWIN_CURSOR_BUSY : MSWIN_CURSOR_ARROW);
+#endif
+}
+
+
+/*
+ * modal_bogus_input - used by scrolltool to complain about
+ * invalid user input.
+ */
+int
+modal_bogus_input(UCS ch)
+{
+ char s[MAX_SCREEN_COLS+1];
+
+ snprintf(s, sizeof(s), _("Command \"%s\" not allowed. Press RETURN to continue Alpine."),
+ pretty_command(ch));
+ s[sizeof(s)-1] = '\0';
+ status_message_write(s, 0);
+ Writechar(BELL, 0);
+ return(0);
+}
diff --git a/alpine/status.h b/alpine/status.h
new file mode 100644
index 00000000..4076c8e0
--- /dev/null
+++ b/alpine/status.h
@@ -0,0 +1,32 @@
+/*
+ * $Id: status.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_STATUS_INCLUDED
+#define PINE_STATUS_INCLUDED
+
+
+#include "../pith/status.h"
+
+
+/* exported protoypes */
+int messages_queued(long *);
+char *last_message_queued(void);
+void flush_ordered_messages(void);
+int status_message_write(char *, int);
+void mark_status_dirty(void);
+void mark_status_unknown(void);
+
+
+#endif /* PINE_STATUS_INCLUDED */
diff --git a/alpine/takeaddr.c b/alpine/takeaddr.c
new file mode 100644
index 00000000..ec89c017
--- /dev/null
+++ b/alpine/takeaddr.c
@@ -0,0 +1,3520 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: takeaddr.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ takeaddr.c
+ Mostly support for Take Address command.
+ ====*/
+
+
+#include "headers.h"
+#include "takeaddr.h"
+#include "addrbook.h"
+#include "adrbkcmd.h"
+#include "status.h"
+#include "confscroll.h"
+#include "keymenu.h"
+#include "radio.h"
+#include "titlebar.h"
+#include "alpine.h"
+#include "help.h"
+#include "mailcmd.h"
+#include "mailpart.h"
+#include "roleconf.h"
+#include "../pith/state.h"
+#include "../pith/msgno.h"
+#include "../pith/adrbklib.h"
+#include "../pith/bldaddr.h"
+#include "../pith/bitmap.h"
+#include "../pith/util.h"
+#include "../pith/addrstring.h"
+#include "../pith/remote.h"
+#include "../pith/newmail.h"
+#include "../pith/list.h"
+#include "../pith/abdlc.h"
+#include "../pith/ablookup.h"
+#include "../pith/stream.h"
+#include "../pith/mailcmd.h"
+#include "../pith/busy.h"
+
+
+typedef struct takeaddress_screen {
+ ScreenMode mode;
+ TA_S *current,
+ *top_line;
+} TA_SCREEN_S;
+
+static TA_SCREEN_S *ta_screen;
+static char *fakedomain = "@";
+
+static ESCKEY_S save_or_export[] = {
+ {'s', 's', "S", N_("Save")},
+ {'e', 'e', "E", N_("Export")},
+ {-1, 0, NULL, NULL}};
+
+
+/* internal prototypes */
+int edit_nickname(AdrBk *, AddrScrn_Disp *, int, char *, char *, HelpType, int, int);
+void add_abook_entry(TA_S *, char *, char *, char *, char *, int, TA_STATE_S **, char *);
+void take_to_addrbooks_frontend(char **, char *, char *, char *, char *,
+ char *, int, TA_STATE_S **, char *);
+void take_to_addrbooks(char **, char *, char *, char *, char *,
+ char *, int, TA_STATE_S **, char *);
+PerAddrBook *use_this_addrbook(int, char *);
+PerAddrBook *check_for_addrbook(char *);
+int takeaddr_screen(struct pine *, TA_S *, int, ScreenMode, TA_STATE_S **, char *);
+void takeaddr_bypass(struct pine *, TA_S *, TA_STATE_S **);
+int ta_do_take(TA_S *, int, int, TA_STATE_S **, char *);
+TA_S *whereis_taline(TA_S *);
+int ta_take_marked_addrs(int, TA_S *, int, TA_STATE_S **, char *);
+int ta_take_single_addr(TA_S *, int, TA_STATE_S **, char *);
+int update_takeaddr_screen(struct pine *, TA_S *, TA_SCREEN_S *, Pos *);
+void takeaddr_screen_redrawer_list(void);
+void takeaddr_screen_redrawer_single(void);
+int attached_addr_handler(TA_S *, int);
+int take_without_edit(TA_S *, int, int, TA_STATE_S **, char *);
+void export_vcard_att(struct pine *, int, long, ATTACH_S *);
+int take_export_tool(struct pine *, int, CONF_S **, unsigned);
+
+#ifdef _WINDOWS
+int ta_scroll_up(long);
+int ta_scroll_down(long);
+int ta_scroll_to_pos(long);
+int ta_scroll_callback(int, long);
+#endif
+
+
+/*
+ * Edit a nickname field.
+ *
+ * Args: abook -- the addressbook handle
+ * dl -- display list line (NULL if new entry)
+ * command_line -- line to prompt on
+ * orig -- nickname to edit
+ * prompt -- prompt
+ * this_help -- help
+ * return_existing -- changes the behavior when a user types in a nickname
+ * which already exists in this abook. If not set, it
+ * will just keep looping until the user changes; if set,
+ * it will return -8 to the caller and orig will be set
+ * to the matching nickname.
+ *
+ * Returns: -10 to cancel
+ * -9 no change
+ * -7 only case of nickname changed (only happens if dl set)
+ * -8 existing nickname chosen (only happens if return_existing set)
+ * 0 new value copied into orig
+ */
+int
+edit_nickname(AdrBk *abook, AddrScrn_Disp *dl, int command_line, char *orig,
+ char *prompt, HelpType this_help, int return_existing, int takeaddr)
+{
+ char edit_buf[MAX_NICKNAME + 1];
+ HelpType help;
+ int i, flags, lastrc, rc;
+ AdrBk_Entry *check, *passed_in_ae;
+ ESCKEY_S ekey[3];
+ SAVE_STATE_S state; /* For saving state of addrbooks temporarily */
+ char *error = NULL;
+
+ ekey[i = 0].ch = ctrl('T');
+ ekey[i].rval = 2;
+ ekey[i].name = "^T";
+ ekey[i++].label = N_("To AddrBk");
+
+ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
+ ekey[i].ch = ctrl('I');
+ ekey[i].rval = 11;
+ ekey[i].name = "TAB";
+ ekey[i++].label = N_("Complete");
+ }
+
+ ekey[i].ch = -1;
+
+ strncpy(edit_buf, orig, sizeof(edit_buf)-1);
+ edit_buf[sizeof(edit_buf)-1] = '\0';
+ if(dl)
+ passed_in_ae = adrbk_get_ae(abook, (a_c_arg_t) dl->elnum);
+ else
+ passed_in_ae = (AdrBk_Entry *)NULL;
+
+ help = NO_HELP;
+ rc = 0;
+ check = NULL;
+ do{
+ if(error){
+ q_status_message(SM_ORDER, 3, 4, error);
+ fs_give((void **)&error);
+ }
+
+ /* display a message because adrbk_lookup_by_nick returned positive */
+ if(check){
+ if(return_existing){
+ strncpy(orig, edit_buf, sizeof(edit_buf)-1);
+ orig[sizeof(edit_buf)-1] = '\0';
+ if(passed_in_ae)
+ (void)adrbk_get_ae(abook, (a_c_arg_t) dl->elnum);
+ return -8;
+ }
+
+ q_status_message1(SM_ORDER, 0, 4,
+ _("Already an entry with nickname \"%s\""), edit_buf);
+ }
+
+ lastrc = rc;
+ if(rc == 3)
+ help = (help == NO_HELP ? this_help : NO_HELP);
+
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(edit_buf, command_line, 0, sizeof(edit_buf),
+ prompt, ekey, help, &flags);
+
+ if(rc == 1) /* ^C */
+ break;
+
+ if(rc == 2){ /* ^T */
+ void (*redraw) (void) = ps_global->redrawer;
+ char *returned_nickname;
+
+ push_titlebar_state();
+ save_state(&state);
+ if(takeaddr)
+ returned_nickname = addr_book_takeaddr();
+ else
+ returned_nickname = addr_book_selnick();
+
+ restore_state(&state);
+ if(returned_nickname){
+ strncpy(edit_buf, returned_nickname, sizeof(edit_buf)-1);
+ edit_buf[sizeof(edit_buf)-1] = '\0';
+ fs_give((void **)&returned_nickname);
+ }
+
+ ClearScreen();
+ pop_titlebar_state();
+ redraw_titlebar();
+ if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
+ (*ps_global->redrawer)();
+ }
+ else if(rc == 11){ /* TAB */
+ if(edit_buf[0]){
+ char *new_nickname = NULL;
+ int ambiguity;
+
+ ambiguity = abook_nickname_complete(edit_buf, &new_nickname,
+ (lastrc==rc && !(flags & OE_USER_MODIFIED)), 0);
+ if(new_nickname){
+ if(*new_nickname){
+ strncpy(edit_buf, new_nickname, sizeof(edit_buf));
+ edit_buf[sizeof(edit_buf)-1] = '\0';
+ }
+
+ fs_give((void **) &new_nickname);
+ }
+
+ if(ambiguity != 2)
+ Writechar(BELL, 0);
+ }
+ }
+
+ }while(rc == 2 ||
+ rc == 3 ||
+ rc == 4 ||
+ rc == 11||
+ nickname_check(edit_buf, &error) ||
+ ((check =
+ adrbk_lookup_by_nick(abook, edit_buf, (adrbk_cntr_t *)NULL)) &&
+ check != passed_in_ae));
+
+ if(rc != 0){
+ if(passed_in_ae)
+ (void)adrbk_get_ae(abook, (a_c_arg_t) dl->elnum);
+
+ return -10;
+ }
+
+ /* only the case of nickname changed */
+ if(passed_in_ae && check == passed_in_ae && strcmp(edit_buf, orig)){
+ (void)adrbk_get_ae(abook, (a_c_arg_t) dl->elnum);
+ strncpy(orig, edit_buf, sizeof(edit_buf)-1);
+ orig[sizeof(edit_buf)-1] = '\0';
+ return -7;
+ }
+
+ if(passed_in_ae)
+ (void)adrbk_get_ae(abook, (a_c_arg_t) dl->elnum);
+
+ if(strcmp(edit_buf, orig) == 0) /* no change */
+ return -9;
+
+ strncpy(orig, edit_buf, sizeof(edit_buf)-1);
+ orig[sizeof(edit_buf)-1] = '\0';
+ return 0;
+}
+
+
+/*
+ * Add an entry to address book.
+ * It is for capturing addresses off incoming mail.
+ * This is a front end for take_to_addrbooks.
+ * It is also used for replacing an existing entry and for adding a single
+ * new address to an existing list.
+ *
+ * The reason this is here is so that when Taking a single address, we can
+ * rearrange the fullname to be Last, First instead of First Last.
+ *
+ * Args: ta_entry -- the entry from the take screen
+ * command_line -- line to prompt on
+ *
+ * Result: item is added to one of the address books,
+ * an error message is queued if appropriate.
+ */
+void
+add_abook_entry(TA_S *ta_entry, char *nick, char *fullname, char *fcc,
+ char *comment, int command_line, TA_STATE_S **tas, char *cmd)
+{
+ ADDRESS *addr;
+ char new_fullname[6*MAX_FULLNAME + 1], new_address[6*MAX_ADDRESS + 1];
+ char **new_list;
+
+ dprint((5, "-- add_abook_entry --\n"));
+
+ /*-- rearrange full name (Last, First) ---*/
+ new_fullname[0] = '\0';
+ addr = ta_entry->addr;
+ if(!fullname && addr->personal != NULL){
+ if(F_ON(F_DISABLE_TAKE_LASTFIRST, ps_global)){
+ strncpy(new_fullname, addr->personal, sizeof(new_fullname)-1);
+ new_fullname[sizeof(new_fullname)-1] = '\0';
+ }
+ else{
+ char old_fullname[6*MAX_FULLNAME + 1];
+
+ snprintf(old_fullname, sizeof(old_fullname), "%s", addr->personal);
+ old_fullname[sizeof(old_fullname)-1] = '\0';
+ switch_to_last_comma_first(old_fullname, new_fullname, sizeof(new_fullname));
+ }
+ }
+
+ /* initial value for new address */
+ new_address[0] = '\0';
+ if(addr->mailbox && addr->mailbox[0]){
+ char *scratch, *p, *t, *u;
+ size_t es;
+ unsigned long l;
+ RFC822BUFFER rbuf;
+
+ es = est_size(addr);
+ scratch = (char *) fs_get(es);
+ scratch[0] = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = scratch;
+ rbuf.cur = scratch;
+ rbuf.end = scratch+es-1;
+ rfc822_output_address_list(&rbuf, addr, 0L, NULL);
+ *rbuf.cur = '\0';
+ if((p = srchstr(scratch, "@" RAWFIELD)) != NULL){
+ for(t = p; ; t--)
+ if(*t == '&'){ /* find "leading" token */
+ *t++ = ' '; /* replace token */
+ *p = '\0'; /* tie off string */
+ u = (char *)rfc822_base64((unsigned char *)t,
+ (unsigned long)strlen(t), &l);
+ *p = '@'; /* restore 'p' */
+ rplstr(p, es-(p-scratch), 12, ""); /* clear special token */
+ rplstr(t, es-(t-scratch), strlen(t), u); /* Null u is handled */
+ if(u)
+ fs_give((void **)&u);
+ }
+ else if(t == scratch)
+ break;
+ }
+
+ strncpy(new_address, scratch, sizeof(new_address)-1);
+ new_address[sizeof(new_address)-1] = '\0';
+
+ if(scratch)
+ fs_give((void **)&scratch);
+ }
+
+ if(ta_entry->frwrded){
+ ADDRESS *a;
+ int i, j;
+
+ for(i = 0, a = addr; a; i++, a = a->next)
+ ;/* just counting for alloc below */
+
+ /* catch special case where empty addr was set in vcard_to_ta */
+ if(i == 1 && !addr->host && !addr->mailbox && !addr->personal)
+ i = 0;
+
+ new_list = (char **) fs_get((i+1) * sizeof(char *));
+ for(j = 0, a = addr; i && a; j++, a = a->next){
+ ADDRESS *next_addr;
+ char *bufp;
+ size_t len;
+
+ next_addr = a->next;
+ a->next = NULL;
+ len = est_size(a);
+ bufp = (char *) fs_get(len * sizeof(char));
+ new_list[j] = cpystr(addr_string(a, bufp, len));
+ a->next = next_addr;
+ fs_give((void **) &bufp);
+ }
+
+ new_list[j] = NULL;
+ }
+ else{
+ int i = 0, j;
+
+ j = (ta_entry->strvalue && ta_entry->strvalue[0]) ? 2 : 1;
+
+ new_list = (char **) fs_get(j * sizeof(char *));
+
+ if(j == 2)
+ new_list[i++] = cpystr(ta_entry->strvalue);
+
+ new_list[i] = NULL;
+ }
+
+ take_to_addrbooks_frontend(new_list, nick,
+ fullname ? fullname : new_fullname,
+ new_address, fcc, comment, command_line,
+ tas, cmd);
+ free_list_array(&new_list);
+}
+
+
+void
+take_to_addrbooks_frontend(char **new_entries, char *nick, char *fullname,
+ char *addr, char *fcc, char *comment, int cmdline,
+ TA_STATE_S **tas, char *cmd)
+{
+ jmp_buf save_jmp_buf;
+ int *save_nesting_level;
+
+ dprint((5, "-- take_to_addrbooks_frontend --\n"));
+
+ if(ps_global->remote_abook_validity > 0 &&
+ adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0))
+ ps_global->mangled_footer = 1;
+
+ save_nesting_level = cpyint(ab_nesting_level);
+ memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
+ if(setjmp(addrbook_changed_unexpectedly)){
+ q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
+ dprint((1,
+ "RESETTING address book... take_to_addrbooks_frontend!\n"));
+ addrbook_reset();
+ ab_nesting_level = *save_nesting_level;
+ }
+
+ ab_nesting_level++;
+ take_to_addrbooks(new_entries, nick, fullname, addr, fcc, comment, cmdline,
+ tas, cmd);
+ memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
+ ab_nesting_level--;
+ if(save_nesting_level)
+ fs_give((void **)&save_nesting_level);
+}
+
+
+/*
+ * Add to address book, called from take screen.
+ * It is also used for adding to an existing list or replacing an existing
+ * entry.
+ *
+ * Args: new_entries -- a list of addresses to add to a list or to form
+ * a new list with
+ * nick -- if adding new entry, suggest this for nickname
+ * fullname -- if adding new entry, use this for fullname
+ * addr -- if only one new_entry, this is its addr
+ * fcc -- if adding new entry, use this for fcc
+ * comment -- if adding new entry, use this for comment
+ * command_line -- line to prompt on
+ *
+ * Result: item is added to one of the address books,
+ * an error message is queued if appropriate.
+ */
+void
+take_to_addrbooks(char **new_entries, char *nick, char *fullname, char *addr,
+ char *fcc, char *comment, int command_line, TA_STATE_S **tas, char *cmd)
+{
+ char new_nickname[6*MAX_NICKNAME + 1], exist_nick[6*MAX_NICKNAME + 1];
+ char prompt[200], **p;
+ int rc, listadd = 0, ans, i;
+ AdrBk *abook;
+ SAVE_STATE_S state;
+ PerAddrBook *pab;
+ AdrBk_Entry *abe = (AdrBk_Entry *)NULL, *abe_copy;
+ adrbk_cntr_t entry_num = NO_NEXT;
+ size_t tot_size, new_size, old_size;
+ Tag old_tag;
+ char *tmp_a_string;
+ char *simple_a = NULL;
+ ADDRESS *a = NULL;
+
+
+ dprint((5, "-- take_to_addrbooks --\n"));
+
+ if(tas && *tas)
+ pab = (*tas)->pab;
+ else
+ pab = setup_for_addrbook_add(&state, command_line, cmd);
+
+ /* check we got it opened ok */
+ if(pab == NULL || pab->address_book == NULL)
+ goto take_to_addrbooks_cancel;
+
+ adrbk_check_validity(pab->address_book, 1L);
+ if(pab->address_book->flags & FILE_OUTOFDATE ||
+ (pab->address_book->rd &&
+ pab->address_book->rd->flags & REM_OUTOFDATE)){
+ q_status_message3(SM_ORDER, 0, 4,
+ "Address book%s%s has changed: %stry again",
+ (as.n_addrbk > 1 && pab->abnick) ? " " : "",
+ (as.n_addrbk > 1 && pab->abnick) ? pab->abnick : "",
+ (ps_global->remote_abook_validity == -1) ? "resynchronize and " : "");
+ if(tas && *tas){
+ restore_state(&((*tas)->state));
+ (*tas)->pab = NULL;
+ }
+ else
+ restore_state(&state);
+
+ return;
+ }
+
+ abook = pab->address_book;
+ new_nickname[0] = '\0';
+ exist_nick[0] = '\0';
+
+ if(addr){
+ simple_a = NULL;
+ a = NULL;
+ /* rfc822_parse_adrlist feels free to destroy input so send copy */
+ tmp_a_string = cpystr(addr);
+ rfc822_parse_adrlist(&a, tmp_a_string, fakedomain);
+ if(tmp_a_string)
+ fs_give((void **)&tmp_a_string);
+
+ if(a){
+ simple_a = simple_addr_string(a, tmp_20k_buf, SIZEOF_20KBUF);
+ mail_free_address(&a);
+ }
+
+ if(simple_a && *simple_a)
+ abe = adrbk_lookup_by_addr(abook, simple_a, NULL);
+
+ if(abe){
+ snprintf(prompt, sizeof(prompt), _("Warning: address exists with %s%s, continue "),
+ (abe->nickname && abe->nickname[0]) ? "nickname "
+ : (abe->fullname && abe->fullname[0]) ? "fullname "
+ : "no nickname",
+ (abe->nickname && abe->nickname[0]) ? abe->nickname
+ : (abe->fullname && abe->fullname[0]) ? abe->fullname
+ : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ switch(want_to(prompt, 'y', 'x', NO_HELP, WT_NORM)){
+ case 'y':
+ if(abe->nickname && abe->nickname[0]){
+ strncpy(new_nickname, abe->nickname, sizeof(new_nickname));
+ new_nickname[sizeof(new_nickname)-1] = '\0';
+ strncpy(exist_nick, new_nickname, sizeof(exist_nick));
+ exist_nick[sizeof(exist_nick)-1] = '\0';
+ }
+
+ break;
+
+ default:
+ goto take_to_addrbooks_cancel;
+ }
+ }
+ }
+
+get_nick:
+ abe = NULL;
+ old_tag = NotSet;
+ entry_num = NO_NEXT;
+
+ /*----- nickname ------*/
+ snprintf(prompt, sizeof(prompt),
+ _("Enter new or existing nickname (one word and easy to remember): "));
+ prompt[sizeof(prompt)-1] = '\0';
+ if(!new_nickname[0] && nick){
+ strncpy(new_nickname, nick, sizeof(new_nickname));
+ new_nickname[sizeof(new_nickname)-1] = '\0';
+ }
+
+ rc = edit_nickname(abook, (AddrScrn_Disp *)NULL, command_line,
+ new_nickname, prompt, h_oe_takenick, 1, 1);
+ if(rc == -8){ /* this means an existing nickname was entered */
+ abe = adrbk_lookup_by_nick(abook, new_nickname, &entry_num);
+ if(!abe){ /* this shouldn't happen */
+ q_status_message1(SM_ORDER, 0, 4,
+ _("Already an entry %s in address book!"), new_nickname);
+ goto take_to_addrbooks_cancel;
+ }
+
+ old_tag = abe->tag;
+
+ if(abe->tag == Single && !strcmp(new_nickname, exist_nick)){
+ static ESCKEY_S choices[] = {
+ {'r', 'r', "R", N_("Replace")},
+ {'n', 'n', "N", N_("No")},
+ {-1, 0, NULL, NULL}};
+
+ snprintf(prompt, sizeof(prompt), _("Entry %s (%s) exists, replace ? "),
+ new_nickname,
+ (abe->fullname && abe->fullname[0])
+ ? (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fullname)
+ : "<no long name>");
+ prompt[sizeof(prompt)-1] = '\0';
+ ans = radio_buttons(prompt,
+ command_line,
+ choices,
+ 'r',
+ 'x',
+ h_oe_take_replace,
+ RB_NORM);
+ }
+ else{
+ static ESCKEY_S choices[] = {
+ {'r', 'r', "R", N_("Replace")},
+ {'a', 'a', "A", N_("Add")},
+ {'n', 'n', "N", N_("No")},
+ {-1, 0, NULL, NULL}};
+
+ snprintf(prompt, sizeof(prompt),
+ _("%s %s (%s) exists, replace or add addresses to it ? "),
+ abe->tag == List ? "List" : "Entry",
+ new_nickname,
+ (abe->fullname && abe->fullname[0])
+ ? (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, abe->fullname)
+ : "<no long name>");
+ prompt[sizeof(prompt)-1] = '\0';
+
+ ans = radio_buttons(prompt,
+ command_line,
+ choices,
+ 'a',
+ 'x',
+ h_oe_take_replace_or_add,
+ RB_NORM);
+ }
+
+ switch(ans){
+ case 'y':
+ case 'r':
+ break;
+
+ case 'n':
+ goto get_nick;
+ break;
+
+ case 'a':
+ listadd++;
+ break;
+
+ default:
+ goto take_to_addrbooks_cancel;
+ }
+ }
+ else if(rc != 0 && rc != -9) /* -9 means a null nickname */
+ goto take_to_addrbooks_cancel;
+
+ if((long)abook->count > MAX_ADRBK_SIZE ||
+ (old_tag == NotSet && (long)abook->count >= MAX_ADRBK_SIZE)){
+ q_status_message(SM_ORDER, 3, 5,
+ _("Address book is at maximum size, cancelled."));
+ dprint((2, "Addrbook at Max size, TakeAddr cancelled\n"));
+ goto take_to_addrbooks_cancel;
+ }
+
+ if(listadd){
+ /* count up size of existing list */
+ if(abe->tag == List){
+ for(p = abe->addr.list; p != NULL && *p != NULL; p++)
+ ;/* do nothing */
+
+ old_size = p - abe->addr.list;
+ }
+ /* or size of existing single address */
+ else if(abe->addr.addr && abe->addr.addr[0])
+ old_size = 1;
+ else
+ old_size = 0;
+ }
+ else /* don't care about old size, they will be tossed in edit_entry */
+ old_size = 0;
+
+ /* make up an abe to pass to edit_entry */
+ abe_copy = adrbk_newentry();
+ abe_copy->nickname = cpystr(new_nickname);
+ abe_copy->tag = List;
+ abe_copy->addr.list = NULL;
+
+ if(listadd){
+ abe_copy->fullname = cpystr((abe->fullname && abe->fullname[0])
+ ? abe->fullname : "");
+ abe_copy->fcc = cpystr((abe->fcc && abe->fcc[0]) ? abe->fcc : "");
+ abe_copy->extra = cpystr((abe->extra&&abe->extra[0]) ? abe->extra : "");
+ }
+ else{
+ /*
+ * use passed in info if available
+ */
+ abe_copy->fullname = cpystr((fullname && fullname[0])
+ ? fullname
+ : (abe && abe->fullname)
+ ? abe->fullname
+ : "");
+ abe_copy->fcc = cpystr((fcc && fcc[0])
+ ? fcc
+ : (abe && abe->fcc)
+ ? abe->fcc
+ : "");
+ abe_copy->extra = cpystr((comment && comment[0])
+ ? comment
+ : (abe && abe->extra)
+ ? abe->extra
+ : "");
+ }
+
+ /* get rid of duplicates */
+ if(listadd){
+ if(abe->tag == List){
+ int elim_dup;
+ char **q, **r;
+ ADDRESS *newadr, *oldadr;
+
+ for(q = new_entries; q != NULL && *q != NULL;){
+
+ tmp_a_string = cpystr(*q);
+ newadr = NULL;
+ rfc822_parse_adrlist(&newadr, tmp_a_string, fakedomain);
+ fs_give((void **) &tmp_a_string);
+
+ elim_dup = (newadr == NULL);
+ for(p = abe->addr.list;
+ !elim_dup && p != NULL && *p != NULL;
+ p++){
+ tmp_a_string = cpystr(*p);
+ oldadr = NULL;
+ rfc822_parse_adrlist(&oldadr, tmp_a_string, fakedomain);
+ fs_give((void **) &tmp_a_string);
+
+ if(address_is_same(newadr, oldadr))
+ elim_dup++;
+
+ if(oldadr)
+ mail_free_address(&oldadr);
+ }
+
+ /* slide the addresses down one to eliminate newadr */
+ if(elim_dup){
+ char *f;
+
+ f = *q;
+ for(r = q; r != NULL && *r != NULL; r++)
+ *r = *(r+1);
+
+ if(f)
+ fs_give((void **) &f);
+ }
+ else
+ q++;
+
+ if(newadr)
+ mail_free_address(&newadr);
+ }
+ }
+ else{
+ char **q, **r;
+ ADDRESS *newadr, *oldadr;
+
+ tmp_a_string = cpystr(abe->addr.addr ? abe->addr.addr : "");
+ oldadr = NULL;
+ rfc822_parse_adrlist(&oldadr, tmp_a_string, fakedomain);
+ fs_give((void **) &tmp_a_string);
+
+ for(q = new_entries; q != NULL && *q != NULL;){
+
+ tmp_a_string = cpystr(*q);
+ newadr = NULL;
+ rfc822_parse_adrlist(&newadr, tmp_a_string, fakedomain);
+ fs_give((void **) &tmp_a_string);
+
+ /* slide the addresses down one to eliminate newadr */
+ if(address_is_same(newadr, oldadr)){
+ char *f;
+
+ f = *q;
+ for(r = q; r != NULL && *r != NULL; r++)
+ *r = *(r+1);
+
+ if(f)
+ fs_give((void **) &f);
+ }
+ else
+ q++;
+
+ if(newadr)
+ mail_free_address(&newadr);
+ }
+
+ if(oldadr)
+ mail_free_address(&oldadr);
+ }
+
+ if(!new_entries || !*new_entries){
+ q_status_message1(SM_ORDER, 0, 4,
+ _("All of the addresses are already included in \"%s\""),
+ new_nickname);
+ free_ae(&abe_copy);
+ if(tas && *tas){
+ restore_state(&((*tas)->state));
+ (*tas)->pab = NULL;
+ }
+ else
+ restore_state(&state);
+
+ return;
+ }
+ }
+
+ /* count up size of new list */
+ for(p = new_entries; p != NULL && *p != NULL; p++)
+ ;/* do nothing */
+
+ new_size = p - new_entries;
+ tot_size = old_size + new_size;
+ abe_copy->addr.list = (char **) fs_get((tot_size+1) * sizeof(char *));
+ memset((void *) abe_copy->addr.list, 0, (tot_size+1) * sizeof(char *));
+ if(old_size > 0){
+ if(abe->tag == List){
+ for(i = 0; i < old_size; i++)
+ abe_copy->addr.list[i] = cpystr(abe->addr.list[i]);
+ }
+ else
+ abe_copy->addr.list[0] = cpystr(abe->addr.addr);
+ }
+
+ /* add new addresses to list */
+ if(tot_size == 1 && addr)
+ abe_copy->addr.list[0] = cpystr(addr);
+ else
+ for(i = 0; i < new_size; i++)
+ abe_copy->addr.list[old_size + i] = cpystr(new_entries[i]);
+
+ abe_copy->addr.list[tot_size] = NULL;
+
+ if(F_ON(F_DISABLE_TAKE_FULLNAMES, ps_global)){
+ for(i = 0; abe_copy->addr.list[i]; i++){
+ simple_a = NULL;
+ a = NULL;
+ tmp_a_string = cpystr(abe_copy->addr.list[i]);
+ rfc822_parse_adrlist(&a, tmp_a_string, fakedomain);
+ if(tmp_a_string)
+ fs_give((void **) &tmp_a_string);
+
+ if(a){
+ simple_a = simple_addr_string(a, tmp_20k_buf, SIZEOF_20KBUF);
+ mail_free_address(&a);
+ }
+
+ /* replace the old addr string with one with no full name */
+ if(simple_a && *simple_a){
+ if(abe_copy->addr.list[i])
+ fs_give((void **) &abe_copy->addr.list[i]);
+
+ abe_copy->addr.list[i] = cpystr(simple_a);
+ }
+ }
+ }
+
+ edit_entry(abook, abe_copy, (a_c_arg_t) entry_num, old_tag, 0, NULL, cmd);
+
+ /* free copy */
+ free_ae(&abe_copy);
+ if(tas && *tas){
+ restore_state(&((*tas)->state));
+ (*tas)->pab = NULL;
+ }
+ else
+ restore_state(&state);
+
+ return;
+
+take_to_addrbooks_cancel:
+ q_status_message(SM_INFO, 0, 2, _("Address book addition cancelled"));
+ if(tas && *tas){
+ restore_state(&((*tas)->state));
+ (*tas)->pab = NULL;
+ }
+ else
+ restore_state(&state);
+}
+
+
+/*
+ * Prep addrbook for TakeAddr add operation.
+ *
+ * Arg: savep -- Address of a pointer to save addrbook state in.
+ * stp -- Address of a pointer to save addrbook state in.
+ *
+ * Returns: a PerAddrBook pointer, or NULL.
+ */
+PerAddrBook *
+setup_for_addrbook_add(SAVE_STATE_S *state, int command_line, char *cmd)
+{
+ PerAddrBook *pab;
+ int save_rem_abook_valid = 0;
+
+ init_ab_if_needed();
+ save_state(state);
+
+ if(as.n_addrbk == 0){
+ q_status_message(SM_ORDER, 3, 4, _("No address book configured!"));
+ return NULL;
+ }
+ else
+ pab = use_this_addrbook(command_line, cmd);
+
+ if(!pab)
+ return NULL;
+
+ if((pab->type & REMOTE_VIA_IMAP) && ps_global->remote_abook_validity == -1){
+ save_rem_abook_valid = -1;
+ ps_global->remote_abook_validity = 0;
+ }
+
+ /* initialize addrbook so we can add to it */
+ init_abook(pab, Open);
+
+ if(save_rem_abook_valid)
+ ps_global->remote_abook_validity = save_rem_abook_valid;
+
+ if(pab->ostatus != Open){
+ q_status_message(SM_ORDER, 3, 4, _("Can't open address book!"));
+ return NULL;
+ }
+
+ if(pab->access != ReadWrite){
+ if(pab->access == ReadOnly)
+ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
+ else if(pab->access == NoAccess)
+ q_status_message(SM_ORDER, 3, 4,
+ _("AddressBook not accessible, permission denied"));
+
+ return NULL;
+ }
+
+ return(pab);
+}
+
+
+/*
+ * Interact with user to figure out which address book they want to add a
+ * new entry (TakeAddr) to.
+ *
+ * Args: command_line -- just the line to prompt on
+ *
+ * Results: returns a pab pointing to the selected addrbook, or NULL.
+ */
+PerAddrBook *
+use_this_addrbook(int command_line, char *cmd)
+{
+ HelpType help;
+ int rc = 0;
+ PerAddrBook *pab, *the_only_pab;
+#define MAX_ABOOK 2000
+ int i, abook_num, count_read_write;
+ char addrbook[MAX_ABOOK + 1],
+ prompt[MAX_ABOOK + 81];
+ static ESCKEY_S ekey[] = {
+ {-2, 0, NULL, NULL},
+ {ctrl('P'), 10, "^P", N_("Prev AddrBook")},
+ {ctrl('N'), 11, "^N", N_("Next AddrBook")},
+ {KEY_UP, 10, "", ""},
+ {KEY_DOWN, 11, "", ""},
+ {-1, 0, NULL, NULL}};
+
+ dprint((9, "- use_this_addrbook -\n"));
+
+ /* check for only one ReadWrite addrbook */
+ count_read_write = 0;
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ /*
+ * NoExists is counted, too, so the user can add to an empty
+ * addrbook the first time.
+ */
+ if(pab->access == ReadWrite ||
+ pab->access == NoExists ||
+ pab->access == MaybeRorW){
+ count_read_write++;
+ the_only_pab = &as.adrbks[i];
+ }
+ }
+
+ /* only one usable addrbook, use it */
+ if(count_read_write == 1)
+ return(the_only_pab);
+
+ /* no addrbook to write to */
+ if(count_read_write == 0){
+ q_status_message2(SM_ORDER | SM_DING, 3, 4,
+ "No %sAddressbook to %s to!",
+ (as.n_addrbk > 0) ? "writable " : "", cmd);
+ return NULL;
+ }
+
+ /* start with the first addrbook */
+ abook_num = 0;
+ pab = &as.adrbks[abook_num];
+ strncpy(addrbook, pab->abnick, sizeof(addrbook)-1);
+ addrbook[sizeof(addrbook)-1] = '\0';
+ snprintf(prompt, sizeof(prompt), "%c%s to which addrbook : %s",
+ islower((unsigned char)(*cmd)) ? toupper((unsigned char)*cmd) : *cmd,
+ cmd+1,
+ (pab->access == ReadOnly || pab->access == NoAccess) ?
+ "[ReadOnly] " : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ help = NO_HELP;
+ ps_global->mangled_footer = 1;
+ do{
+ int flags;
+
+ if(!pab)
+ q_status_message1(SM_ORDER, 3, 4, _("No addressbook \"%s\""),
+ addrbook);
+
+ if(rc == 3)
+ help = (help == NO_HELP ? h_oe_chooseabook : NO_HELP);
+
+ flags = OE_APPEND_CURRENT;
+ rc = optionally_enter(addrbook, command_line, 0, sizeof(addrbook),
+ prompt, ekey, help, &flags);
+
+ if(rc == 1){ /* ^C */
+ char capcmd[50];
+
+ snprintf(capcmd, sizeof(capcmd),
+ "%c%s",
+ islower((unsigned char)(*cmd)) ? toupper((unsigned char)*cmd)
+ : *cmd,
+ cmd+1);
+ capcmd[sizeof(capcmd)-1] = '\0';
+ cmd_cancelled(capcmd);
+ break;
+ }
+
+ if(rc == 10){ /* Previous addrbook */
+ if(--abook_num < 0)
+ abook_num = as.n_addrbk - 1;
+
+ pab = &as.adrbks[abook_num];
+ strncpy(addrbook, pab->abnick, sizeof(addrbook)-1);
+ addrbook[sizeof(addrbook)-1] = '\0';
+ snprintf(prompt, sizeof(prompt), "%s to which addrbook : %s", cmd,
+ (pab->access == ReadOnly || pab->access == NoAccess) ?
+ "[ReadOnly] " : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+ else if(rc == 11){ /* Next addrbook */
+ if(++abook_num > as.n_addrbk - 1)
+ abook_num = 0;
+
+ pab = &as.adrbks[abook_num];
+ strncpy(addrbook, pab->abnick, sizeof(addrbook)-1);
+ addrbook[sizeof(addrbook)-1] = '\0';
+ snprintf(prompt, sizeof(prompt), "%s to which addrbook : %s", cmd,
+ (pab->access == ReadOnly || pab->access == NoAccess) ?
+ "[ReadOnly] " : "");
+ prompt[sizeof(prompt)-1] = '\0';
+ }
+
+ }while(rc == 2 || rc == 3 || rc == 4 || rc == 10 || rc == 11 || rc == 12 ||
+ !(pab = check_for_addrbook(addrbook)));
+
+ ps_global->mangled_footer = 1;
+
+ if(rc != 0)
+ return NULL;
+
+ return(pab);
+}
+
+
+/*
+ * Return a pab pointer to the addrbook which corresponds to the argument.
+ *
+ * Args: addrbook -- the string representing the addrbook.
+ *
+ * Results: returns a PerAddrBook pointer for the referenced addrbook, NULL
+ * if none. First the nicknames are checked and then the filenames.
+ * This must be one of the existing addrbooks.
+ */
+PerAddrBook *
+check_for_addrbook(char *addrbook)
+{
+ register int i;
+ register PerAddrBook *pab;
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(strcmp(pab->abnick, addrbook) == 0)
+ break;
+ }
+
+ if(i < as.n_addrbk)
+ return(pab);
+
+ for(i = 0; i < as.n_addrbk; i++){
+ pab = &as.adrbks[i];
+ if(strcmp(pab->filename, addrbook) == 0)
+ break;
+ }
+
+ if(i < as.n_addrbk)
+ return(pab);
+
+ return NULL;
+}
+
+
+/*
+ * Screen for selecting which addresses to Take to address book.
+ *
+ * Args: ps -- Pine state
+ * ta_list -- Screen is formed from this list of addresses
+ * how_many_selected -- how many checked initially in ListMode
+ * mode -- which mode to start in
+ *
+ * Result: an address book may be updated
+ * Returns -- 0 normally
+ * 1 if it returns before redrawing screen
+ */
+int
+takeaddr_screen(struct pine *ps, TA_S *ta_list, int how_many_selected,
+ ScreenMode mode, TA_STATE_S **tas, char *command)
+{
+ UCS ch = 'x';
+ int cmd, dline, give_warn_message, command_line;
+ int km_popped = 0,
+ directly_to_take = 0,
+ ret = 0,
+ done = 0;
+ TA_S *current = NULL,
+ *ctmp = NULL;
+ TA_SCREEN_S screen;
+ Pos cursor_pos;
+ char *utf8str;
+ struct key_menu *km;
+
+ dprint((2, "- takeaddr_screen -\n"));
+
+ command_line = -FOOTER_ROWS(ps); /* third line from the bottom */
+
+ screen.current = screen.top_line = NULL;
+ screen.mode = mode;
+
+ if(ta_list == NULL){
+ /* TRANSLATORS: something like
+ No addresses to save, cancelled */
+ q_status_message1(SM_INFO, 0, 2, "No addresses to %s, cancelled",
+ command);
+ return 1;
+ }
+
+ current = first_sel_taline(ta_list);
+ ps->mangled_screen = 1;
+ ta_screen = &screen;
+
+ if(is_talist_of_one(current)){
+ directly_to_take++;
+ screen.mode = SingleMode;
+ }
+ else if(screen.mode == ListMode)
+ q_status_message(SM_INFO, 0, 1,
+ _("List mode: Use \"X\" to mark addresses to be included in list"));
+ else
+ q_status_message(SM_INFO, 0, 1,
+ _("Single mode: Use \"P\" or \"N\" to select desired address"));
+
+ while(!done){
+ if(km_popped){
+ km_popped--;
+ if(km_popped == 0){
+ clearfooter(ps);
+ ps->mangled_body = 1;
+ }
+ }
+
+ if(screen.mode == ListMode)
+ ps->redrawer = takeaddr_screen_redrawer_list;
+ else
+ ps->redrawer = takeaddr_screen_redrawer_single;
+
+ if(ps->mangled_screen){
+ ps->mangled_header = 1;
+ ps->mangled_footer = 1;
+ ps->mangled_body = 1;
+ ps->mangled_screen = 0;
+ }
+
+ /*----------- Check for new mail -----------*/
+ if(new_mail(0, NM_TIMING(ch), NM_STATUS_MSG | NM_DEFER_SORT) >= 0)
+ ps->mangled_header = 1;
+
+#ifdef _WINDOWS
+ mswin_beginupdate();
+#endif
+ if(ps->mangled_header){
+ char tbuf[40];
+
+ snprintf(tbuf, sizeof(tbuf), "TAKE ADDRESS SCREEN (%s Mode)",
+ (screen.mode == ListMode) ? "List"
+ : "Single");
+ tbuf[sizeof(tbuf)-1] = '\0';
+ set_titlebar(tbuf, ps->mail_stream, ps->context_current,
+ ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0,
+ NULL);
+ ps->mangled_header = 0;
+ }
+
+ dline = update_takeaddr_screen(ps, current, &screen, &cursor_pos);
+ if(F_OFF(F_SHOW_CURSOR, ps)){
+ cursor_pos.row = ps->ttyo->screen_rows - FOOTER_ROWS(ps);
+ cursor_pos.col = 0;
+ }
+
+ /*---- This displays new mail notification, or errors ---*/
+ if(km_popped){
+ FOOTER_ROWS(ps_global) = 3;
+ mark_status_unknown();
+ }
+
+ display_message(ch);
+ if(km_popped){
+ FOOTER_ROWS(ps_global) = 1;
+ mark_status_unknown();
+ }
+
+ /*---- Redraw footer ----*/
+ if(ps->mangled_footer){
+ bitmap_t bitmap;
+
+ if(km_popped){
+ FOOTER_ROWS(ps) = 3;
+ clearfooter(ps);
+ }
+
+ setbitmap(bitmap);
+ ps->mangled_footer = 0;
+
+ km = (screen.mode == ListMode) ? &ta_keymenu_lm : &ta_keymenu_sm;
+
+ menu_clear_binding(km, KEY_LEFT);
+ menu_clear_binding(km, KEY_RIGHT);
+ if(F_ON(F_ARROW_NAV, ps_global)){
+ int cmd;
+
+ if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
+ menu_add_binding(km, '<', cmd);
+ menu_add_binding(km, KEY_LEFT, cmd);
+ }
+
+ if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
+ menu_add_binding(km, '>', cmd);
+ menu_add_binding(km, KEY_RIGHT, cmd);
+ }
+ }
+
+ draw_keymenu(km, bitmap, ps->ttyo->screen_cols,
+ 1 - FOOTER_ROWS(ps_global), 0, FirstMenu);
+
+ if(km_popped){
+ FOOTER_ROWS(ps) = 1;
+ mark_keymenu_dirty();
+ }
+ }
+
+#ifdef _WINDOWS
+ mswin_endupdate();
+#endif
+ /*------ Read the command from the keyboard ----*/
+ MoveCursor(cursor_pos.row, cursor_pos.col);
+
+ if(directly_to_take){ /* bypass this screen */
+ cmd = MC_TAKE;
+ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
+ }
+ else {
+#ifdef MOUSE
+ mouse_in_content(KEY_MOUSE, -1, -1, 0, 0);
+ register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
+ ps_global->ttyo->screen_rows - (FOOTER_ROWS(ps)+1),
+ ps_global->ttyo->screen_cols);
+#endif
+
+#ifdef _WINDOWS
+ mswin_setscrollcallback(ta_scroll_callback);
+#endif
+ ch = READ_COMMAND(&utf8str);
+#ifdef MOUSE
+ clear_mfunc(mouse_in_content);
+#endif
+
+#ifdef _WINDOWS
+ mswin_setscrollcallback(NULL);
+#endif
+ cmd = menu_command(ch, km);
+ if (ta_screen->current)
+ current = ta_screen->current;
+
+ if(km_popped)
+ switch(cmd){
+ case MC_NONE :
+ case MC_OTHER :
+ case MC_RESIZE :
+ case MC_REPAINT :
+ km_popped++;
+ break;
+
+ default:
+ clearfooter(ps);
+ break;
+ }
+ }
+
+ switch(cmd){
+ case MC_HELP : /* help! */
+ if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
+ km_popped = 2;
+ ps_global->mangled_footer = 1;
+ break;
+ }
+
+ helper(h_takeaddr_screen, _("HELP FOR TAKE ADDRESS SCREEN"),
+ HLPD_SIMPLE);
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_EXIT: /* exit takeaddr screen */
+ q_status_message(SM_INFO, 0, 2, _("Address book addition cancelled"));
+ ret = 1;
+ done++;
+ break;
+
+ case MC_TAKE:
+ if(ta_do_take(current, how_many_selected, command_line, tas,
+ command))
+ done++;
+ else
+ directly_to_take = 0;
+
+ break;
+
+ case MC_CHARDOWN : /* next list element */
+ if((ctmp = next_sel_taline(current)) != NULL)
+ current = ctmp;
+ else
+ q_status_message(SM_INFO, 0, 1, _("Already on last line."));
+
+ break;
+
+ case MC_CHARUP: /* previous list element */
+ if((ctmp = pre_sel_taline(current)) != NULL)
+ current = ctmp;
+ else
+ q_status_message(SM_INFO, 0, 1, _("Already on first line."));
+
+ break;
+
+ case MC_PAGEDN : /* page forward */
+ give_warn_message = 1;
+ while(dline++ < ps->ttyo->screen_rows - FOOTER_ROWS(ps)){
+ if((ctmp = next_sel_taline(current)) != NULL){
+ current = ctmp;
+ give_warn_message = 0;
+ }
+ else
+ break;
+ }
+
+ if(give_warn_message)
+ q_status_message(SM_INFO, 0, 1, _("Already on last page."));
+
+ break;
+
+ case MC_PAGEUP : /* page backward */
+ /* move to top of screen */
+ give_warn_message = 1;
+ while(dline-- > HEADER_ROWS(ps_global)){
+ if((ctmp = pre_sel_taline(current)) != NULL){
+ current = ctmp;
+ give_warn_message = 0;
+ }
+ else
+ break;
+ }
+
+ /* page back one screenful */
+ while(++dline < ps->ttyo->screen_rows - FOOTER_ROWS(ps)){
+ if((ctmp = pre_sel_taline(current)) != NULL){
+ current = ctmp;
+ give_warn_message = 0;
+ }
+ else
+ break;
+ }
+
+ if(give_warn_message)
+ q_status_message(SM_INFO, 0, 1, _("Already on first page."));
+
+ break;
+
+ case MC_WHEREIS : /* whereis */
+ if((ctmp = whereis_taline(current)) != NULL)
+ current = ctmp;
+
+ ps->mangled_footer = 1;
+ break;
+
+ case KEY_SCRLTO:
+ /* no op for now */
+ break;
+
+#ifdef MOUSE
+ case MC_MOUSE:
+ {
+ MOUSEPRESS mp;
+
+ mouse_get_last(NULL, &mp);
+ mp.row -= HEADER_ROWS(ps_global);
+ ctmp = screen.top_line;
+ if(mp.doubleclick){
+ if(screen.mode == SingleMode){
+ if(ta_do_take(current, how_many_selected, command_line,
+ tas, command))
+ done++;
+ else
+ directly_to_take = 0;
+ }
+ else{
+ current->checked = !current->checked; /* flip it */
+ how_many_selected += (current->checked ? 1 : -1);
+ }
+ }
+ else{
+ while(mp.row && ctmp != NULL){
+ --mp.row;
+ do ctmp = ctmp->next;
+ while(ctmp != NULL && ctmp->skip_it && !ctmp->print);
+ }
+
+ if(ctmp != NULL && !ctmp->skip_it)
+ current = ctmp;
+ }
+ }
+ break;
+#endif
+
+ case MC_REPAINT :
+ case MC_RESIZE :
+ ClearScreen();
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_CHOICE : /* [UN]select this addr */
+ current->checked = !current->checked; /* flip it */
+ how_many_selected += (current->checked ? 1 : -1);
+ break;
+
+ case MC_SELALL : /* select all */
+ how_many_selected = ta_mark_all(first_sel_taline(current));
+ ps->mangled_body = 1;
+ break;
+
+ case MC_UNSELALL: /* unselect all */
+ how_many_selected = ta_unmark_all(first_sel_taline(current));
+ ps->mangled_body = 1;
+ break;
+
+ case MC_LISTMODE: /* switch to SingleMode */
+ if(screen.mode == ListMode){
+ screen.mode = SingleMode;
+ q_status_message(SM_INFO, 0, 1,
+ _("Single mode: Use \"P\" or \"N\" to select desired address"));
+ }
+ else{
+ screen.mode = ListMode;
+ q_status_message(SM_INFO, 0, 1,
+ _("List mode: Use \"X\" to mark addresses to be included in list"));
+
+ if(how_many_selected <= 1){
+ how_many_selected =
+ ta_unmark_all(first_sel_taline(current));
+ current->checked = 1;
+ how_many_selected++;
+ }
+ }
+
+ ps->mangled_screen = 1;
+ break;
+
+
+ case MC_NONE : /* simple timeout */
+ break;
+
+
+ /* Unbound (or not dealt with) keystroke */
+ case MC_CHARRIGHT :
+ case MC_CHARLEFT :
+ case MC_GOTOBOL :
+ case MC_GOTOEOL :
+ case MC_UNKNOWN :
+ default:
+ bogus_command(ch, F_ON(F_USE_FK, ps) ? "F1" : "?");
+ break;
+
+ case MC_UTF8:
+ bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps) ? "F1" : "?");
+ break;
+ }
+ }
+
+ ps->mangled_screen = 1;
+
+ return(ret);
+}
+
+
+/*
+ * Do what takeaddr_screen does except bypass the takeaddr_screen and
+ * go directly to do_take.
+ */
+void
+takeaddr_bypass(struct pine *ps, TA_S *current, TA_STATE_S **tasp)
+{
+ TA_SCREEN_S screen; /* We have to fake out ta_do_take because */
+ /* we're bypassing takeaddr_screen. */
+ ta_screen = &screen;
+ ta_screen->mode = SingleMode;
+ current = first_sel_taline(current);
+ (void) ta_do_take(current, 1, -FOOTER_ROWS(ps_global), tasp, _("save"));
+ ps->mangled_screen = 1;
+}
+
+
+/*
+ *
+ */
+int
+ta_do_take(TA_S *current, int how_many_selected, int command_line,
+ TA_STATE_S **tas, char *cmd)
+{
+ return((ta_screen->mode == ListMode)
+ ? ta_take_marked_addrs(how_many_selected,
+ first_sel_taline(current),
+ command_line, tas, cmd)
+ : ta_take_single_addr(current, command_line, tas, cmd));
+}
+
+
+/*
+ * WhereIs for TakeAddr screen.
+ *
+ * Returns the line match is found in or NULL.
+ */
+TA_S *
+whereis_taline(TA_S *current)
+{
+ TA_S *p;
+ int rc, found = 0, wrapped = 0, flags;
+ char *result = NULL, buf[MAX_SEARCH+1], tmp[MAX_SEARCH+20];
+ static char last[MAX_SEARCH+1];
+ HelpType help;
+ static ESCKEY_S ekey[] = {
+ {0, 0, "", ""},
+ {ctrl('Y'), 10, "^Y", N_("Top")},
+ {ctrl('V'), 11, "^V", N_("Bottom")},
+ {-1, 0, NULL, NULL}};
+
+ if(!current)
+ return NULL;
+
+ /*--- get string ---*/
+ buf[0] = '\0';
+ snprintf(tmp, sizeof(tmp), _("Word to find %s%.*s%s: "),
+ (last[0]) ? "[" : "",
+ sizeof(tmp)-20, (last[0]) ? last : "",
+ (last[0]) ? "]" : "");
+ tmp[sizeof(tmp)-1] = '\0';
+ help = NO_HELP;
+ flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
+ while(1){
+ rc = optionally_enter(buf,-FOOTER_ROWS(ps_global),0,sizeof(buf),
+ tmp,ekey,help,&flags);
+ if(rc == 3)
+ help = help == NO_HELP ? h_config_whereis : NO_HELP;
+ else if(rc == 0 || rc == 1 || rc == 10 || rc == 11 || !buf[0]){
+ if(rc == 0 && !buf[0] && last[0]){
+ strncpy(buf, last, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = '\0';
+ }
+
+ break;
+ }
+ }
+
+ if(rc == 0 && buf[0]){
+ p = current;
+ while((p = next_taline(p)) != NULL)
+ if(srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, p->strvalue),
+ buf)){
+ found++;
+ break;
+ }
+
+ if(!found){
+ p = first_taline(current);
+
+ while(p != current)
+ if(srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, p->strvalue),
+ buf)){
+ found++;
+ wrapped++;
+ break;
+ }
+ else
+ p = next_taline(p);
+ }
+ }
+ else if(rc == 10){
+ current = first_sel_taline(current);
+ result = _("Searched to top");
+ }
+ else if(rc == 11){
+ current = last_sel_taline(current);
+ result = _("Searched to bottom");
+ }
+ else{
+ current = NULL;
+ result = _("WhereIs cancelled");
+ }
+
+ if(found){
+ current = p;
+ result = wrapped ? _("Search wrapped to beginning") : _("Word found");
+ strncpy(last, buf, sizeof(last)-1);
+ last[sizeof(last)-1] = '\0';
+ }
+
+ q_status_message(SM_ORDER,0,3,result ? result : _("Word not found"));
+ return(current);
+}
+
+
+/*
+ * Call the addrbook functions which add the checked addresses.
+ *
+ * Args: how_many_selected -- how many addresses are checked
+ * f_line -- the first ta line
+ *
+ * Returns: 1 -- we're done, caller should return
+ * 0 -- we're not done
+ */
+int
+ta_take_marked_addrs(int how_many_selected, TA_S *f_line, int command_line,
+ TA_STATE_S **tas, char *cmd)
+{
+ char **new_list;
+ TA_S *p;
+
+ if(how_many_selected == 0){
+ q_status_message(SM_ORDER, 0, 4,
+ _("No addresses marked for taking. Use ExitTake to leave TakeAddr screen"));
+ return 0;
+ }
+
+ if(how_many_selected == 1){
+ for(p = f_line; p; p = next_sel_taline(p))
+ if(p->checked && !p->skip_it)
+ break;
+
+ if(p)
+ add_abook_entry(p,
+ (p->nickname && p->nickname[0]) ? p->nickname : NULL,
+ (p->fullname && p->fullname[0]) ? p->fullname : NULL,
+ (p->fcc && p->fcc[0]) ? p->fcc : NULL,
+ (p->comment && p->comment[0]) ? p->comment : NULL,
+ command_line, tas, cmd);
+ }
+ else{
+ new_list = list_of_checked(f_line);
+ for(p = f_line; p; p = next_sel_taline(p))
+ if(p->checked && !p->skip_it)
+ break;
+
+ take_to_addrbooks_frontend(new_list, p ? p->nickname : NULL,
+ p ? p->fullname : NULL, NULL, p ? p->fcc : NULL,
+ p ? p->comment : NULL, command_line, tas, cmd);
+ free_list_array(&new_list);
+ }
+
+ return 1;
+}
+
+
+int
+ta_take_single_addr(TA_S *cur, int command_line, TA_STATE_S **tas, char *cmd)
+{
+ add_abook_entry(cur,
+ (cur->nickname && cur->nickname[0]) ? cur->nickname : NULL,
+ (cur->fullname && cur->fullname[0]) ? cur->fullname : NULL,
+ (cur->fcc && cur->fcc[0]) ? cur->fcc : NULL,
+ (cur->comment && cur->comment[0]) ? cur->comment : NULL,
+ command_line, tas, cmd);
+
+ return 1;
+}
+
+
+/*
+ * Manage display of the Take Address screen.
+ *
+ * Args: ps -- pine state
+ * current -- the current TA line
+ * screen -- the TA screen
+ * cursor_pos -- return good cursor position here
+ */
+int
+update_takeaddr_screen(struct pine *ps, TA_S *current, TA_SCREEN_S *screen, Pos *cursor_pos)
+{
+ int dline;
+ TA_S *top_line,
+ *ctmp;
+ int longest, i, j;
+ char buf1[6*MAX_SCREEN_COLS + 30];
+ char buf2[6*MAX_SCREEN_COLS + 30];
+ char *p, *q;
+ int screen_width = ps->ttyo->screen_cols;
+ Pos cpos;
+
+ cpos.row = HEADER_ROWS(ps); /* default return value */
+
+ /* calculate top line of display */
+ dline = 0;
+ top_line = 0;
+
+ if (ta_screen->top_line){
+ for(dline = 0, ctmp = ta_screen->top_line;
+ ctmp && ctmp != current; ctmp = next_taline(ctmp))
+ dline++;
+
+ if (ctmp && (dline < ps->ttyo->screen_rows - HEADER_ROWS(ps)
+ - FOOTER_ROWS(ps)))
+ top_line = ta_screen->top_line;
+ }
+
+ if (!top_line){
+ dline = 0;
+ ctmp = top_line = first_taline(current);
+ do
+ if(((dline++) % (ps->ttyo->screen_rows - HEADER_ROWS(ps)
+ - FOOTER_ROWS(ps))) == 0)
+ top_line = ctmp;
+ while(ctmp != current && (ctmp = next_taline(ctmp)));
+ }
+
+#ifdef _WINDOWS
+ /*
+ * Figure out how far down the top line is from the top and how many
+ * total lines there are. Dumb to loop every time thru, but
+ * there aren't that many lines, and it's cheaper than rewriting things
+ * to maintain a line count in each structure...
+ */
+ for(dline = 0, ctmp = pre_taline(top_line); ctmp; ctmp = pre_taline(ctmp))
+ dline++;
+
+ scroll_setpos(dline);
+
+ for(ctmp = next_taline(top_line); ctmp ; ctmp = next_taline(ctmp))
+ dline++;
+
+ scroll_setrange(ps->ttyo->screen_rows - FOOTER_ROWS(ps) - HEADER_ROWS(ps),
+ dline);
+#endif
+
+
+ /* mangled body or new page, force redraw */
+ if(ps->mangled_body || screen->top_line != top_line)
+ screen->current = NULL;
+
+ /* find width of longest line for nicer formatting */
+ longest = 0;
+ for(ctmp = first_taline(top_line); ctmp; ctmp = next_taline(ctmp)){
+ int width;
+
+ if(ctmp
+ && !ctmp->print
+ && ctmp->strvalue
+ && longest < (width = utf8_width((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, ctmp->strvalue))))
+ longest = width;
+ }
+
+#define LENGTH_OF_THAT_STRING 5 /* "[X] " */
+ longest = MIN(longest, ps->ttyo->screen_cols);
+
+ /* loop thru painting what's needed */
+ for(dline = 0, ctmp = top_line;
+ dline < ps->ttyo->screen_rows - FOOTER_ROWS(ps) - HEADER_ROWS(ps);
+ dline++, ctmp = next_taline(ctmp)){
+
+ /*
+ * only fall thru painting if something needs painting...
+ */
+ if(!ctmp || !screen->current || ctmp == screen->current ||
+ ctmp == top_line || ctmp == current){
+ ClearLine(dline + HEADER_ROWS(ps));
+ if(!ctmp || !ctmp->strvalue)
+ continue;
+ }
+
+ p = buf1;
+ if(ctmp == current){
+ cpos.row = dline + HEADER_ROWS(ps); /* col set below */
+ StartInverse();
+ }
+
+ if(ctmp->print)
+ j = 0;
+ else
+ j = LENGTH_OF_THAT_STRING;
+
+ /*
+ * Copy the value to a temp buffer expanding tabs, and
+ * making sure not to write beyond screen right...
+ */
+ q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, ctmp->strvalue);
+
+ for(i = 0; q[i] && j < ps->ttyo->screen_cols && p-buf1 < sizeof(buf1); i++){
+ if(q[i] == ctrl('I')){
+ do
+ *p++ = SPACE;
+ while(j < ps->ttyo->screen_cols && ((++j)&0x07) && p-buf1 < sizeof(buf1));
+
+ }
+ else{
+ *p++ = q[i];
+ j++;
+ }
+ }
+
+ if(p-buf1 < sizeof(buf1))
+ *p = '\0';
+
+ if(utf8_width(buf1) < longest){
+ (void) utf8_pad_to_width(buf2, buf1, sizeof(buf2), longest, 1);
+ /* it's expected to be in buf1 below */
+ strncpy(buf1, buf2, sizeof(buf1));
+ buf1[sizeof(buf1)-1] = '\0';
+ }
+
+ /* mark lines which have check marks */
+ if(ctmp == current){
+ if(screen->mode == ListMode){
+ snprintf(buf2, sizeof(buf2), "[%c] %s", ctmp->checked ? 'X' : SPACE, buf1);
+ buf2[sizeof(buf2)-1] = '\0';
+ cpos.col = 1; /* position on the X */
+ }
+ else{
+ snprintf(buf2, sizeof(buf2), " %s", buf1);
+ buf2[sizeof(buf2)-1] = '\0';
+ cpos.col = 5; /* 5 spaces before text */
+ }
+ }
+ else{
+ if(ctmp->print){
+ int width, actual_width;
+ size_t len;
+
+ /*
+ * In buf2 make ------string--------
+ * which reaches all the way across the screen. String will
+ * already have a leading and trailing space.
+ */
+ width = utf8_width(buf1);
+
+ if(width > screen_width){
+ actual_width = utf8_truncate(buf1, screen_width);
+ /* it might be 1 less */
+ if(actual_width < screen_width && (len=strlen(buf1))+1 < sizeof(buf1)){
+ buf1[len] = SPACE;
+ buf1[len+1] = '\0';
+ }
+ }
+ else{
+ snprintf(buf2, sizeof(buf2), "%s%s",
+ repeat_char((screen_width-width)/2, '-'), buf1);
+ buf2[sizeof(buf2)-1] = '\0';
+ len = strlen(buf2);
+ width = utf8_width(buf2);
+ snprintf(buf2+len, sizeof(buf2)-len, "%s",
+ repeat_char(screen_width-width, '-'));
+ buf2[sizeof(buf2)-1] = '\0';
+ }
+ }
+ else{
+ if(screen->mode == ListMode)
+ snprintf(buf2, sizeof(buf2), "[%c] %.*s", ctmp->checked ? 'X' : SPACE,
+ sizeof(buf2)-6, buf1);
+ else
+ snprintf(buf2, sizeof(buf2), " %.*s", sizeof(buf2)-6, buf1);
+
+ buf2[sizeof(buf2)-1] = '\0';
+ }
+ }
+
+ PutLine0(dline + HEADER_ROWS(ps), 0, buf2);
+
+ if(ctmp == current)
+ EndInverse();
+ }
+
+ ps->mangled_body = 0;
+ screen->top_line = top_line;
+ screen->current = current;
+ if(cursor_pos)
+ *cursor_pos = cpos;
+
+ return(cpos.row);
+}
+
+
+void
+takeaddr_screen_redrawer_list(void)
+{
+ ps_global->mangled_body = 1;
+ (void)update_takeaddr_screen(ps_global, ta_screen->current, ta_screen,
+ (Pos *)NULL);
+}
+
+
+void
+takeaddr_screen_redrawer_single(void)
+{
+ ps_global->mangled_body = 1;
+ (void)update_takeaddr_screen(ps_global, ta_screen->current, ta_screen,
+ (Pos *)NULL);
+}
+
+
+/* jpf work in progress
+ * Execute command to take addresses out of message and put in the address book
+ *
+ * Args: ps -- pine state
+ * msgmap -- the MessageMap
+ * agg -- this is aggregate operation if set
+ *
+ * Result: The entry is added to an address book.
+ */
+int
+cmd_take_addr(struct pine *ps, MSGNO_S *msgmap, int agg)
+{
+ TA_S *ta_list = NULL;
+ int how_many_selected = 0, rtype;
+
+ /* Ask user what kind of Take they want to do */
+ if(!agg && F_ON(F_ENABLE_ROLE_TAKE, ps)){
+ rtype = rule_setup_type(ps,
+ RS_RULES |
+ ((mn_get_total(msgmap) > 0)
+ ? (F_ON(F_ENABLE_TAKE_EXPORT, ps)
+ ? (RS_INCADDR | RS_INCEXP)
+ : RS_INCADDR)
+ : RS_NONE),
+ "Take to : ");
+ }
+ else if(F_ON(F_ENABLE_TAKE_EXPORT, ps) && mn_get_total(msgmap) > 0)
+ rtype = rule_setup_type(ps, RS_INCADDR | RS_INCEXP, "Take to : ");
+ else
+ rtype = 'a';
+
+ if(rtype == 'x' || rtype == 'Z'){
+ if(rtype == 'x')
+ cmd_cancelled(NULL);
+ else if(rtype == 'Z')
+ q_status_message(SM_ORDER | SM_DING, 3, 5,
+ "Try turning on color with the Setup/Kolor command.");
+ return -1;
+ }
+
+ ps->mangled_footer = 1;
+
+ if(rtype > 0)
+ switch(rtype){
+ case 'e':
+ {
+ LINES_TO_TAKE *lines_to_take = NULL;
+
+ rtype = set_up_takeaddr('e', ps, msgmap, &ta_list, &how_many_selected,
+ agg ? TA_AGG : 0, NULL);
+
+ if(rtype >= 0){
+ if(convert_ta_to_lines(ta_list, &lines_to_take)){
+ while(lines_to_take && lines_to_take->prev)
+ lines_to_take = lines_to_take->prev;
+
+ take_to_export(ps, lines_to_take);
+
+ free_ltlines(&lines_to_take);
+ }
+ else
+ q_status_message(SM_ORDER, 3, 4, _("Can't find anything to export"));
+ }
+ }
+
+ break;
+
+ case 'r':
+ case 's':
+ case 'i':
+ case 'f':
+ case 'o':
+ case 'c':
+ case 'x':
+ role_take(ps, msgmap, rtype);
+ break;
+
+ case 'a':
+ rtype = set_up_takeaddr('a', ps, msgmap, &ta_list, &how_many_selected,
+ agg ? TA_AGG : 0, attached_addr_handler);
+ if(rtype >= 0){
+ (void) takeaddr_screen(ps, ta_list, how_many_selected,
+ agg ? ListMode : SingleMode,
+ NULL, _("take"));
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* clean up */
+ free_talines(&ta_list);
+ env_for_pico_callback = NULL;
+ body_for_pico_callback = NULL;
+
+ return(rtype >= 0 ? 1 : 0);
+}
+
+
+int
+attached_addr_handler(TA_S *current, int added)
+{
+ char prompt[200];
+ int command_line = -FOOTER_ROWS(ps_global);
+
+ snprintf(prompt, sizeof(prompt),
+ "Take %d entries from attachment to addrbook all at once ",
+ added);
+ switch(want_to(prompt, 'n', 'x', NO_HELP, WT_NORM)){
+ case 'y':
+ if(take_without_edit(current, added, command_line, NULL, "take") >= 0)
+ return(0); /* all taken care of */
+ else
+ return(-1); /* problem */
+
+ case 'x':
+ cmd_cancelled("Take");
+ return(-1); /* problem */
+
+ default:
+ return(1); /* proceed */
+ }
+}
+
+
+int
+take_without_edit(TA_S *ta_list, int num_in_list, int command_line, TA_STATE_S **tas, char *cmd)
+{
+ PerAddrBook *pab_dst;
+ SAVE_STATE_S state; /* For saving state of addrbooks temporarily */
+ int rc, total_to_copy;
+ int how_many_dups = 0, how_many_to_copy = 0, skip_dups = 0;
+ int ret = 0;
+ int err = 0, need_write = 0, we_cancel = 0;
+ adrbk_cntr_t new_entry_num;
+ char warn[2][MAX_NICKNAME+1];
+ char tmp[200];
+ TA_S *current;
+ SWOOP_S *swoop_list = NULL, *sw;
+
+ dprint((2, "\n - take_without_edit(%d) - \n",
+ num_in_list));
+
+ /* move to beginning of the list */
+ if(ta_list)
+ while(ta_list->prev)
+ ta_list = ta_list->prev;
+
+ pab_dst = setup_for_addrbook_add(&state, command_line, cmd);
+ if(!pab_dst)
+ goto get_out;
+
+ swoop_list = (SWOOP_S *)fs_get((num_in_list+1) * sizeof(SWOOP_S));
+ memset((void *)swoop_list, 0, (num_in_list+1) * sizeof(SWOOP_S));
+ sw = swoop_list;
+
+ /*
+ * Look through all the vcards for those with nicknames already
+ * existing in the destination abook (dups) and build a list of
+ * entries to be acted on.
+ */
+ for(current = ta_list; current; current = current->next){
+ adrbk_cntr_t dst_enum;
+
+ if(current->skip_it)
+ continue;
+
+ /* check to see if this nickname already exists in the dest abook */
+ if(current->nickname && current->nickname[0]){
+ AdrBk_Entry *abe;
+
+ current->checked = 0;
+ abe = adrbk_lookup_by_nick(pab_dst->address_book,
+ current->nickname, &dst_enum);
+ /*
+ * This nickname already exists.
+ */
+ if(abe){
+ sw->dup = 1;
+ sw->dst_enum = dst_enum;
+ if(how_many_dups < 2){
+ strncpy(warn[how_many_dups], current->nickname, MAX_NICKNAME);
+ warn[how_many_dups][MAX_NICKNAME] = '\0';
+ }
+
+ how_many_dups++;
+ }
+ }
+
+ sw->ta = current;
+ sw++;
+ how_many_to_copy++;
+ }
+
+ /*
+ * If there are some nicknames which already exist in the selected
+ * abook, ask user what to do.
+ */
+ if(how_many_dups > 0){
+ if(how_many_dups == 1){
+ if(how_many_to_copy == 1 && num_in_list == 1){
+ ret = 'T'; /* use Take */
+ if(tas && *tas){
+ (*tas)->state = state;
+ (*tas)->pab = pab_dst;
+ }
+
+ goto get_out;
+ }
+ else{
+ snprintf(tmp, sizeof(tmp),
+ "Entry with nickname \"%.*s\" already exists, replace ",
+ sizeof(tmp)-50, warn[0]);
+ }
+ }
+ else if(how_many_dups == 2)
+ snprintf(tmp, sizeof(tmp),
+ "Nicknames \"%.*s\" and \"%.*s\" already exist, replace ",
+ (sizeof(tmp)-50)/2, warn[0], (sizeof(tmp)-50)/2, warn[1]);
+ else
+ snprintf(tmp, sizeof(tmp), "%d of the nicknames already exist, replace ",
+ how_many_dups);
+
+ switch(want_to(tmp, 'n', 'x', h_ab_copy_dups, WT_NORM)){
+ case 'n':
+ skip_dups++;
+ break;
+
+ case 'y':
+ break;
+
+ case 'x':
+ err++;
+ goto get_out;
+ }
+ }
+
+ /*
+ * Because the deletes happen immediately we have to delete from high
+ * entry number towards lower entry numbers so that we are deleting
+ * the correct entries. In order to do that we'll sort the swoop_list
+ * to give us a safe order.
+ */
+ if(!skip_dups && how_many_dups > 1)
+ qsort((qsort_t *)swoop_list, (size_t)num_in_list, sizeof(*swoop_list),
+ cmp_swoop_list);
+
+ we_cancel = busy_cue("Saving addrbook entries", NULL, 0);
+ total_to_copy = how_many_to_copy - (skip_dups ? how_many_dups : 0);
+
+ /*
+ * Add the list of entries to the destination abook.
+ */
+ for(sw = swoop_list; sw && sw->ta; sw++){
+ Tag tag;
+ char abuf[MAX_ADDRESS + 1];
+ int count_of_addrs;
+
+ if(skip_dups && sw->dup)
+ continue;
+
+ /*
+ * Delete existing dups and replace them.
+ */
+ if(sw->dup){
+
+ /* delete the existing entry */
+ rc = 0;
+ if(adrbk_delete(pab_dst->address_book,
+ (a_c_arg_t)sw->dst_enum, 1, 0, 0, 0) == 0){
+ need_write++;
+ }
+ else{
+ q_status_message2(SM_ORDER | SM_DING, 3, 5,
+ "Error replacing entry in %.200s: %.200s",
+ pab_dst->abnick,
+ error_description(errno));
+ err++;
+ goto get_out;
+ }
+ }
+
+ /*
+ * We need to count the number of addresses in this entry in order
+ * to tell the adrbk routines if it is a List or a Single, and in
+ * order to pass the right stuff to be added.
+ */
+ count_of_addrs = count_addrs(sw->ta->addr);
+ tag = (count_of_addrs > 1) ? List : Single;
+ if(tag == Single){
+ if(sw->ta->addr->mailbox && sw->ta->addr->mailbox[0]){
+ char *scratch, *p, *t, *u;
+ unsigned long l;
+ RFC822BUFFER rbuf;
+ size_t es;
+
+ es = est_size(sw->ta->addr);
+ scratch = (char *) fs_get(es * sizeof(char));
+ scratch[0] = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = scratch;
+ rbuf.cur = scratch;
+ rbuf.end = scratch+es-1;
+ rfc822_output_address_list(&rbuf, sw->ta->addr, 0L, NULL);
+ *rbuf.cur = '\0';
+ if((p = srchstr(scratch, "@" RAWFIELD)) != NULL){
+ for(t = p; ; t--)
+ if(*t == '&'){ /* find "leading" token */
+ *t++ = ' '; /* replace token */
+ *p = '\0'; /* tie off string */
+ u = (char *)rfc822_base64((unsigned char *)t,
+ (unsigned long)strlen(t), &l);
+ *p = '@'; /* restore 'p' */
+ rplstr(p, es-(p-scratch), 12, ""); /* clear special token */
+ rplstr(t, es-(t-scratch), strlen(t), u); /* Null u is handled */
+ if(u)
+ fs_give((void **)&u);
+ }
+ else if(t == scratch)
+ break;
+ }
+
+ strncpy(abuf, scratch, sizeof(abuf)-1);
+ abuf[sizeof(abuf)-1] = '\0';
+
+ if(scratch)
+ fs_give((void **)&scratch);
+ }
+ else
+ abuf[0] = '\0';
+ }
+
+
+ /*
+ * Now we have a clean slate to work with.
+ */
+ if(total_to_copy <= 1)
+ rc = adrbk_add(pab_dst->address_book,
+ NO_NEXT,
+ sw->ta->nickname,
+ sw->ta->fullname,
+ tag == Single ? abuf : NULL,
+ sw->ta->fcc,
+ sw->ta->comment,
+ tag,
+ &new_entry_num,
+ (int *)NULL,
+ 0,
+ 0,
+ 0);
+ else
+ rc = adrbk_append(pab_dst->address_book,
+ sw->ta->nickname,
+ sw->ta->fullname,
+ tag == Single ? abuf : NULL,
+ sw->ta->fcc,
+ sw->ta->comment,
+ tag,
+ &new_entry_num);
+
+ if(rc == 0)
+ need_write++;
+
+ /*
+ * If the entry we copied is a list, we also have to add
+ * the list members to the copy.
+ */
+ if(rc == 0 && tag == List){
+ int i, save_sort_rule;
+ ADDRESS *a, *save_next;
+ char **list;
+
+ list = (char **)fs_get((count_of_addrs + 1) * sizeof(char *));
+ memset((void *)list, 0, (count_of_addrs+1) * sizeof(char *));
+ i = 0;
+ for(a = sw->ta->addr; a; a = a->next){
+ save_next = a->next;
+ a->next = NULL;
+
+ if(a->mailbox && a->mailbox[0]){
+ char *scratch, *p, *t, *u;
+ unsigned long l;
+ RFC822BUFFER rbuf;
+ size_t es;
+
+ es = est_size(a);
+ scratch = (char *) fs_get(es * sizeof(char));
+ scratch[0] = '\0';
+ rbuf.f = dummy_soutr;
+ rbuf.s = NULL;
+ rbuf.beg = scratch;
+ rbuf.cur = scratch;
+ rbuf.end = scratch+es-1;
+ rfc822_output_address_list(&rbuf, a, 0L, NULL);
+ *rbuf.cur = '\0';
+ if((p = srchstr(scratch, "@" RAWFIELD)) != NULL){
+ for(t = p; ; t--)
+ if(*t == '&'){ /* find "leading" token */
+ *t++ = ' '; /* replace token */
+ *p = '\0'; /* tie off string */
+ u = (char *)rfc822_base64((unsigned char *)t,
+ (unsigned long)strlen(t), &l);
+ *p = '@'; /* restore 'p' */
+ rplstr(p, es-(p-scratch), 12, ""); /* clear special token */
+ rplstr(t, es-(t-scratch), strlen(t), u); /* Null u is handled */
+ if(u)
+ fs_give((void **)&u);
+ }
+ else if(t == scratch)
+ break;
+ }
+
+ strncpy(abuf, scratch, sizeof(abuf)-1);
+ abuf[sizeof(abuf)-1] = '\0';
+
+ if(scratch)
+ fs_give((void **)&scratch);
+ }
+ else
+ abuf[0] = '\0';
+
+ list[i++] = cpystr(abuf);
+ a->next = save_next;
+ }
+
+ /*
+ * We want it to copy the list in the exact order
+ * without sorting it.
+ */
+ save_sort_rule = pab_dst->address_book->sort_rule;
+ pab_dst->address_book->sort_rule = AB_SORT_RULE_NONE;
+
+ rc = adrbk_nlistadd(pab_dst->address_book,
+ (a_c_arg_t)new_entry_num, NULL, NULL,
+ list, 0, 0, 0);
+
+ pab_dst->address_book->sort_rule = save_sort_rule;
+ free_list_array(&list);
+ }
+
+ if(rc != 0){
+ q_status_message1(SM_ORDER | SM_DING, 3, 5,
+ "Error saving: %.200s",
+ error_description(errno));
+ err++;
+ goto get_out;
+ }
+ }
+
+ if(need_write){
+ int sort_happened = 0;
+
+ if(adrbk_write(pab_dst->address_book, 0, NULL, &sort_happened, 0, 1)){
+ err++;
+ goto get_out;
+ }
+
+ if(sort_happened)
+ ps_global->mangled_screen = 1;
+ }
+
+get_out:
+ if(we_cancel)
+ cancel_busy_cue(1);
+
+ if(!ret)
+ restore_state(&state);
+
+ if(swoop_list)
+ fs_give((void **)&swoop_list);
+
+ ps_global->mangled_footer = 1;
+
+ if(err){
+ char capcmd[50];
+
+ ret = -1;
+ snprintf(capcmd, sizeof(capcmd),
+ "%c%.*s",
+ islower((unsigned char)(*cmd)) ? toupper((unsigned char)*cmd)
+ : *cmd,
+ sizeof(capcmd)-2, cmd+1);
+ if(need_write)
+ q_status_message1(SM_ORDER | SM_DING, 3, 4,
+ "%.200s only partially completed", capcmd);
+ else
+ cmd_cancelled(capcmd);
+ }
+ else if(ret != 'T' && total_to_copy > 0){
+
+ ret = 1;
+ snprintf(tmp, sizeof(tmp), "Saved %d %s to \"%.*s\"",
+ total_to_copy,
+ (total_to_copy > 1) ? "entries" : "entry",
+ sizeof(tmp)-30, pab_dst->abnick);
+ q_status_message(SM_ORDER, 4, 4, tmp);
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Special case interface to allow a more interactive Save in the case where
+ * the user seems to be wanting to save an exact copy of an existing entry.
+ * For example, they might be trying to save a copy of a list with the intention
+ * of changing it a little bit. The regular save doesn't allow this, since no
+ * editing takes place, but this version plops them into the address book
+ * editor.
+ */
+void
+take_this_one_entry(struct pine *ps, TA_STATE_S **tasp, AdrBk *abook, long int cur_line)
+{
+ AdrBk_Entry *abe;
+ AddrScrn_Disp *dl;
+ char *fcc = NULL, *comment = NULL, *fullname = NULL,
+ *nickname = NULL;
+ ADDRESS *addr;
+ int how_many_selected;
+ TA_S *current = NULL;
+
+ dl = dlist(cur_line);
+ abe = ae(cur_line);
+ if(!abe){
+ q_status_message(SM_ORDER, 0, 4, _("Nothing to save, cancelled"));
+ return;
+ }
+
+ if(dl->type == ListHead || dl->type == Simple){
+ fcc = (abe->fcc && abe->fcc[0]) ? abe->fcc : NULL;
+ comment = (abe->extra && abe->extra[0]) ? abe->extra : NULL;
+ fullname = (abe->fullname && abe->fullname[0]) ? abe->fullname : NULL;
+ nickname = (abe->nickname && abe->nickname[0]) ? abe->nickname : NULL;
+ }
+
+ addr = abe_to_address(abe, dl, abook, &how_many_selected);
+ if(!addr){
+ addr = mail_newaddr();
+ addr->host = cpystr("");
+ addr->mailbox = cpystr("");
+ }
+
+ switch(abe->tag){
+ case Single:
+#ifdef ENABLE_LDAP
+ /*
+ * Special case. When user is saving an entry with a runtime
+ * ldap lookup address, they may be doing it because the lookup
+ * has become stale. Give them a way to get the old address out
+ * of the lookup entry so they can save that, instead.
+ */
+ if(!addr->personal && !strncmp(addr->mailbox, RUN_LDAP, LEN_RL)){
+ LDAP_SERV_S *info = NULL;
+ int i = 'l';
+ static ESCKEY_S backup_or_ldap[] = {
+ {'b', 'b', "B", N_("Backup")},
+ {'l', 'l', "L", N_("LDAP")},
+ {-1, 0, NULL, NULL}};
+
+ info = break_up_ldap_server(addr->mailbox + LEN_RL);
+ if(info && info->mail && *info->mail)
+ i = radio_buttons(_("Copy backup address or retain LDAP search criteria ? "),
+ -FOOTER_ROWS(ps_global), backup_or_ldap,
+ 'b', 'x',
+ h_ab_backup_or_ldap, RB_NORM);
+
+ if(i == 'b'){
+ ADDRESS *a = NULL;
+
+ rfc822_parse_adrlist(&a, info->mail, fakedomain);
+
+ if(a){
+ if(addr->mailbox)
+ fs_give((void **)&addr->mailbox);
+ if(addr->host)
+ fs_give((void **)&addr->host);
+
+ addr->mailbox = a->mailbox;
+ a->mailbox = NULL;
+ addr->host = a->host;
+ a->host = NULL;
+ mail_free_address(&a);
+ }
+ }
+
+ if(info)
+ free_ldap_server_info(&info);
+ }
+#endif /* ENABLE_LDAP */
+ current = fill_in_ta(&current, addr, 0, (char *)NULL);
+ break;
+
+ case List:
+ /*
+ * The empty string for the last argument is significant. Fill_in_ta
+ * copies the whole adrlist into a single TA if there is a print
+ * string.
+ */
+ if(dl->type == ListHead)
+ current = fill_in_ta(&current, addr, 1, "");
+ else
+ current = fill_in_ta(&current, addr, 0, (char *)NULL);
+
+ break;
+
+ default:
+ dprint((1,
+ "default case in take_this_one_entry, shouldn't happen\n"));
+ return;
+ }
+
+ if(current->strvalue && !strcmp(current->strvalue, "@")){
+ fs_give((void **)&current->strvalue);
+ if(fullname && fullname[0])
+ current->strvalue = cpystr(fullname);
+ else if(nickname && nickname[0])
+ current->strvalue = cpystr(nickname);
+ else
+ current->strvalue = cpystr("?");
+
+ convert_possibly_encoded_str_to_utf8(&current->strvalue);
+ }
+
+ if(addr)
+ mail_free_address(&addr);
+
+ if(current){
+ current = first_sel_taline(current);
+ if(fullname && *fullname){
+ current->fullname = cpystr(fullname);
+ convert_possibly_encoded_str_to_utf8(&current->fullname);
+ }
+
+ if(fcc && *fcc){
+ current->fcc = cpystr(fcc);
+ convert_possibly_encoded_str_to_utf8(&current->fcc);
+ }
+
+ if(comment && *comment){
+ current->comment = cpystr(comment);
+ convert_possibly_encoded_str_to_utf8(&current->comment);
+ }
+
+ if(nickname && *nickname){
+ current->nickname = cpystr(nickname);
+ convert_possibly_encoded_str_to_utf8(&current->nickname);
+ }
+
+ takeaddr_bypass(ps, current, tasp);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4, _("Nothing to save, cancelled"));
+
+ free_talines(&current);
+}
+
+
+/*
+ * Execute command to save addresses out of vcard attachment.
+ */
+void
+save_vcard_att(struct pine *ps, int qline, long int msgno, ATTACH_S *a)
+{
+ int how_many_selected, j;
+ TA_S *current = NULL;
+ TA_STATE_S tas, *tasp;
+
+
+ dprint((2, "\n - saving vcard attachment - \n"));
+
+ j = radio_buttons(_("Save to address book or Export to filesystem ? "),
+ qline, save_or_export, 's', 'x',
+ h_ab_save_exp, RB_NORM|RB_SEQ_SENSITIVE);
+
+ switch(j){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
+ return;
+
+ case 'e':
+ export_vcard_att(ps, qline, msgno, a);
+ return;
+
+ case 's':
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3, "can't happen in save_vcard_att");
+ return;
+ }
+
+ dprint((2, "\n - saving attachment into address book - \n"));
+ ps->mangled_footer = 1;
+ current = NULL;
+ how_many_selected = process_vcard_atts(ps->mail_stream, msgno, NULL,
+ a->body, a->number, &current);
+ if(how_many_selected > 0){
+ tas.pab = NULL;
+ tasp = &tas;
+ if(how_many_selected == 1){
+ takeaddr_bypass(ps, current, NULL);
+ }
+ else if(take_without_edit(current, how_many_selected, qline,
+ &tasp, "save") == 'T'){
+ /*
+ * Eliminate dups.
+ */
+ how_many_selected -=
+ eliminate_dups_but_not_us(first_sel_taline(current));
+
+ (void)takeaddr_screen(ps, current, how_many_selected, SingleMode,
+ &tasp, _("save"));
+
+ /*
+ * If takeaddr_screen or its children didn't do this for us,
+ * we do it here.
+ */
+ if(tas.pab)
+ restore_state(&(tas.state));
+ }
+ }
+ else if(how_many_selected == 0)
+ q_status_message(SM_ORDER, 0, 3,
+ _("Save cancelled: no entries in attachment"));
+
+ free_talines(&current);
+}
+
+
+/*
+ * Execute command to export vcard attachment.
+ */
+void
+export_vcard_att(struct pine *ps, int qline, long int msgno, ATTACH_S *a)
+{
+ int how_many_selected, i;
+ TA_S *current;
+ STORE_S *srcstore = NULL;
+ SourceType srctype;
+ static ESCKEY_S vcard_or_addresses[] = {
+ {'a', 'a', "A", N_("Address List")},
+ {'v', 'v', "V", N_("VCard")},
+ {-1, 0, NULL, NULL}};
+
+ if(ps->restricted){
+ q_status_message(SM_ORDER, 0, 3,
+ "Alpine demo can't export addresses to files");
+ return;
+ }
+
+ dprint((2, "\n - exporting vcard attachment - \n"));
+
+ i = radio_buttons(_("Export list of addresses or vCard text ? "),
+ qline, vcard_or_addresses, 'a', 'x',
+ h_ab_export_vcard, RB_NORM|RB_SEQ_SENSITIVE);
+
+ switch(i){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book export cancelled"));
+ return;
+
+ case 'a':
+ break;
+
+ case 'v':
+ write_attachment(qline, msgno, a, "EXPORT");
+ return;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3, _("can't happen in export_vcard_att"));
+ return;
+ }
+
+ ps->mangled_footer = 1;
+ current = NULL;
+ how_many_selected = process_vcard_atts(ps->mail_stream, msgno, NULL,
+ a->body, a->number, &current);
+ /*
+ * Run through all of the list and run through
+ * the addresses in each ta->addr, writing them into a storage object.
+ * Then export to filesystem.
+ */
+ srctype = CharStar;
+ if(how_many_selected > 0 &&
+ (srcstore = so_get(srctype, NULL, EDIT_ACCESS)) != NULL){
+ ADDRESS *aa, *bb;
+ int are_some = 0;
+
+ for(current = first_taline(current);
+ current;
+ current = next_taline(current)){
+
+ for(aa = current->addr; aa; aa = aa->next){
+ bb = aa->next;
+ aa->next = NULL;
+ so_puts(srcstore, addr_list_string(aa, NULL, 0));
+ are_some++;
+ so_puts(srcstore, "\n");
+ aa->next = bb;
+ }
+ }
+
+ if(are_some)
+ simple_export(ps, so_text(srcstore), srctype, "addresses", NULL);
+ else
+ q_status_message(SM_ORDER, 0, 3, _("No addresses to export"));
+
+ so_give(&srcstore);
+ }
+ else{
+ if(how_many_selected == 0)
+ q_status_message(SM_ORDER, 0, 3, _("Nothing to export"));
+ else
+ q_status_message(SM_ORDER,0,2, _("Error allocating space"));
+ }
+}
+
+
+void
+take_to_export(struct pine *ps, LINES_TO_TAKE *lines_to_take)
+{
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ OPT_SCREEN_S screen;
+ LINES_TO_TAKE *li;
+ char *help_title = _("HELP FOR TAKE EXPORT SCREEN");
+ char *p;
+ ScreenMode listmode = SingleMode;
+
+ for(li = lines_to_take; li; li = li->next){
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_STARTITEM;
+ if(li->flags & LT_NOSELECT)
+ ctmp->flags |= CF_NOSELECT;
+ else if(!first_line)
+ first_line = ctmp;
+
+ p = li->printval ? li->printval : "";
+
+ if(ctmp->flags & CF_NOSELECT)
+ ctmp->value = cpystr(p);
+ else{
+ size_t l;
+
+ /* 5 is for "[X] " */
+ l = strlen(p)+5;
+ ctmp->value = (char *)fs_get((l+1) * sizeof(char));
+ snprintf(ctmp->value, l+1, " %s", p);
+ ctmp->value[l] = '\0';
+ }
+
+ /* this points to data, it doesn't have its own copy */
+ ctmp->d.t.exportval = li->exportval ? li->exportval : NULL;
+ ctmp->d.t.selected = 0;
+ ctmp->d.t.listmode = &listmode;
+
+ ctmp->tool = take_export_tool;
+ ctmp->help_title = help_title;
+ ctmp->help = h_takeexport_screen;
+ ctmp->keymenu = &take_export_keymenu_sm;
+ }
+
+ if(!first_line)
+ q_status_message(SM_ORDER, 3, 3, _("No lines to export"));
+ else{
+ memset(&screen, 0, sizeof(screen));
+ conf_scroll_screen(ps, &screen, first_line, _("Take Export"), NULL, 0);
+ }
+}
+
+
+int
+take_export_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
+{
+ CONF_S *ctmp;
+ int retval = 0;
+ int some_selected = 0, something_to_export = 0;
+ SourceType srctype;
+ STORE_S *srcstore = NULL;
+ char *prompt_msg;
+
+ switch(cmd){
+ case MC_TAKE :
+ srctype = CharStar;
+ if((srcstore = so_get(srctype, NULL, EDIT_ACCESS)) != NULL){
+ if(*(*cl)->d.t.listmode == SingleMode){
+ some_selected++;
+ if((*cl)->d.t.exportval && (*cl)->d.t.exportval[0]){
+ so_puts(srcstore, (*cl)->d.t.exportval);
+ so_puts(srcstore, "\n");
+ something_to_export++;
+ prompt_msg = "selection";
+ }
+ }
+ else{
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ for(; ctmp; ctmp = next_confline(ctmp))
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->d.t.selected){
+ some_selected++;
+ if(ctmp->d.t.exportval && ctmp->d.t.exportval[0]){
+ so_puts(srcstore, ctmp->d.t.exportval);
+ so_puts(srcstore, "\n");
+ something_to_export++;
+ prompt_msg = "selections";
+ }
+ }
+ }
+ }
+
+ if(!srcstore)
+ q_status_message(SM_ORDER, 0, 3, _("Error allocating space"));
+ else if(something_to_export)
+ simple_export(ps, so_text(srcstore), srctype, prompt_msg, NULL);
+ else if(!some_selected && *(*cl)->d.t.listmode == ListMode)
+ q_status_message(SM_ORDER, 0, 3, _("Use \"X\" to mark selections"));
+ else
+ q_status_message(SM_ORDER, 0, 3, _("Nothing to export"));
+
+ if(srcstore)
+ so_give(&srcstore);
+
+ break;
+
+ case MC_LISTMODE :
+ if(*(*cl)->d.t.listmode == SingleMode){
+ /*
+ * UnHide the checkboxes
+ */
+
+ *(*cl)->d.t.listmode = ListMode;
+
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ for(; ctmp; ctmp = next_confline(ctmp))
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->value){
+ ctmp->value[0] = '[';
+ ctmp->value[1] = ctmp->d.t.selected ? 'X' : SPACE;
+ ctmp->value[2] = ']';
+ ctmp->keymenu = &take_export_keymenu_lm;
+ }
+ }
+ else{
+ /*
+ * Hide the checkboxes
+ */
+
+ *(*cl)->d.t.listmode = SingleMode;
+
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ for(; ctmp; ctmp = next_confline(ctmp))
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->value){
+ ctmp->value[0] = ctmp->value[1] = ctmp->value[2] = SPACE;
+ ctmp->keymenu = &take_export_keymenu_sm;
+ }
+ }
+
+ ps->mangled_body = ps->mangled_footer = 1;
+ break;
+
+ case MC_CHOICE :
+ if((*cl)->value[1] == 'X'){
+ (*cl)->d.t.selected = 0;
+ (*cl)->value[1] = SPACE;
+ }
+ else{
+ (*cl)->d.t.selected = 1;
+ (*cl)->value[1] = 'X';
+ }
+
+ ps->mangled_body = 1;
+ break;
+
+ case MC_SELALL :
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ for(; ctmp; ctmp = next_confline(ctmp)){
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->value){
+ ctmp->d.t.selected = 1;
+ if(ctmp->value)
+ ctmp->value[1] = 'X';
+ }
+ }
+
+ ps->mangled_body = 1;
+ break;
+
+ case MC_UNSELALL :
+ /* go to first line */
+ for(ctmp = *cl; prev_confline(ctmp); ctmp = prev_confline(ctmp))
+ ;
+
+ for(; ctmp; ctmp = next_confline(ctmp)){
+ if(!(ctmp->flags & CF_NOSELECT) && ctmp->value){
+ ctmp->d.t.selected = 0;
+ if(ctmp->value)
+ ctmp->value[1] = SPACE;
+ }
+ }
+
+ ps->mangled_body = 1;
+ break;
+
+ case MC_EXIT :
+ retval = simple_exit_cmd(flags);
+ break;
+
+ default:
+ retval = -1;
+ break;
+ }
+
+ return(retval);
+}
+
+#ifdef ENABLE_LDAP
+/*
+ * Save an LDAP directory entry somewhere
+ *
+ * Args: ps -- pine struct
+ * e -- the entry to save
+ * save -- If this is set, then bypass the question about whether
+ * to save or export and just do the save.
+ */
+void
+save_ldap_entry(struct pine *ps, LDAP_CHOOSE_S *e, int save)
+{
+ char *fullname = NULL,
+ *address = NULL,
+ *first = NULL,
+ *last = NULL,
+ *comment = NULL;
+ char **cn = NULL,
+ **mail = NULL,
+ **sn = NULL,
+ **givenname = NULL,
+ **title = NULL,
+ **telephone = NULL,
+ **elecmail = NULL,
+ **note = NULL,
+ **ll;
+ int j,
+ export = 0;
+
+
+ dprint((2, "\n - save_ldap_entry - \n"));
+
+ if(e){
+ char *a;
+ BerElement *ber;
+
+ for(a = ldap_first_attribute(e->ld, e->selected_entry, &ber);
+ a != NULL;
+ a = ldap_next_attribute(e->ld, e->selected_entry, ber)){
+
+ dprint((9, " %s", a ? a : "?"));
+ if(strcmp(a, e->info_used->cnattr) == 0){
+ if(!cn)
+ cn = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->mailattr) == 0){
+ if(!mail)
+ mail = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, "electronicmail") == 0){
+ if(!elecmail)
+ elecmail = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, "comment") == 0){
+ if(!note)
+ note = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->snattr) == 0){
+ if(!sn)
+ sn = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, e->info_used->gnattr) == 0){
+ if(!givenname)
+ givenname = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, "telephonenumber") == 0){
+ if(!telephone)
+ telephone = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ else if(strcmp(a, "title") == 0){
+ if(!title)
+ title = ldap_get_values(e->ld, e->selected_entry, a);
+ }
+ }
+
+ if(!save){
+ j = radio_buttons(_("Save to address book or Export to filesystem ? "),
+ -FOOTER_ROWS(ps), save_or_export, 's', 'x',
+ h_ab_save_exp, RB_NORM);
+
+ switch(j){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book save cancelled"));
+ break;
+
+ case 'e':
+ export++;
+ break;
+
+ case 's':
+ save++;
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3,
+ "can't happen in save_ldap_ent");
+ break;
+ }
+ }
+ }
+
+ if(elecmail){
+ if(elecmail[0] && elecmail[0][0] && !mail)
+ mail = elecmail;
+ else
+ ldap_value_free(elecmail);
+
+ elecmail = NULL;
+ }
+
+ if(save){ /* save into the address book */
+ ADDRESS *addr;
+ char *d,
+ *fakedomain = "@";
+ size_t len;
+ char **cm;
+ int how_many_selected = 0;
+
+
+ if(cn && cn[0] && cn[0][0])
+ fullname = cpystr(cn[0]);
+ if(sn && sn[0] && sn[0][0])
+ last = cpystr(sn[0]);
+ if(givenname && givenname[0] && givenname[0][0])
+ first = cpystr(givenname[0]);
+
+ if(note && note[0] && note[0][0])
+ cm = note;
+ else
+ cm = title;
+
+ for(ll = cm, len = 0; ll && *ll; ll++)
+ len += strlen(*ll) + 2;
+
+ if(len){
+ comment = (char *)fs_get(len * sizeof(char));
+ d = comment;
+ ll = cm;
+ while(*ll){
+ sstrncpy(&d, *ll, len-(d-comment));
+ ll++;
+ if(*ll)
+ sstrncpy(&d, "; ", len-(d-comment));
+ }
+
+ comment[len-1] = '\0';
+ }
+
+ for(ll = mail, len = 0; ll && *ll; ll++)
+ len += strlen(*ll) + 1;
+
+ /* paste the email addresses together */
+ if(len){
+ address = (char *)fs_get(len * sizeof(char));
+ d = address;
+ ll = mail;
+ while(*ll){
+ sstrncpy(&d, *ll, len-(d-address));
+ ll++;
+ if(*ll)
+ sstrncpy(&d, ", ", len-(d-address));
+ }
+
+ address[len-1] = '\0';
+ }
+
+ addr = NULL;
+ if(address)
+ rfc822_parse_adrlist(&addr, address, fakedomain);
+
+ if(addr && fullname && !(first && last)){
+ if(addr->personal)
+ fs_give((void **)&addr->personal);
+
+ addr->personal = cpystr(fullname);
+ }
+
+ if(addr && e->serv && *e->serv){ /* save by reference */
+ char *dn, *edn = NULL;
+
+ dn = ldap_get_dn(e->ld, e->selected_entry);
+ if(dn){
+ edn = add_backslash_escapes(dn);
+ our_ldap_dn_memfree(dn);
+ }
+
+ if(e->serv && *e->serv && edn && *edn){
+ char buf[MAILTMPLEN+1];
+ char *backup_mail = NULL;
+
+ how_many_selected++;
+
+ if(addr && addr->mailbox && addr->host){
+ strncpy(buf, addr->mailbox, sizeof(buf)-2),
+ buf[sizeof(buf)-2] = '\0';
+ strncat(buf, "@", sizeof(buf)-1-strlen(buf));
+ strncat(buf, addr->host, sizeof(buf)-1-strlen(buf));
+ buf[sizeof(buf)-1] = '\0';
+ backup_mail = cpystr(buf);
+ }
+
+ /*
+ * We only need one addr which we will use to hold the
+ * pointer to the query.
+ */
+ if(addr->next)
+ mail_free_address(&addr->next);
+
+ if(addr->mailbox)
+ fs_give((void **)&addr->mailbox);
+ if(addr->host)
+ fs_give((void **)&addr->host);
+ if(addr->personal)
+ fs_give((void **)&addr->personal);
+
+ snprintf(buf, sizeof(buf),
+ "%s%s /base=%s/scope=base/cust=(objectclass=*)%s%s",
+ RUN_LDAP,
+ e->serv,
+ edn,
+ backup_mail ? "/mail=" : "",
+ backup_mail ? backup_mail : "");
+ buf[sizeof(buf)-1] = '\0';
+
+ if(backup_mail)
+ fs_give((void **)&backup_mail);
+
+ /*
+ * Put the search parameters in mailbox and put @ in
+ * host so that expand_address accepts it as an unqualified
+ * address and doesn't try to add localdomain.
+ */
+ addr->mailbox = cpystr(buf);
+ addr->host = cpystr("@");
+ }
+
+ if(edn)
+ fs_give((void **)&edn);
+ }
+ else{ /* save by value */
+ how_many_selected++;
+ if(!addr)
+ addr = mail_newaddr();
+ }
+
+ if(how_many_selected > 0){
+ TA_S *current = NULL;
+
+ /*
+ * The empty string for the last argument is significant.
+ * Fill_in_ta copies the whole adrlist into a single TA if
+ * there is a print string.
+ */
+ current = fill_in_ta(&current, addr, 1, "");
+ current = first_sel_taline(current);
+
+ if(first && last && current){
+ char *p;
+ size_t l;
+
+ l = strlen(last) + 2 + strlen(first);
+ p = (char *)fs_get((l+1) * sizeof(char));
+ snprintf(p, l, "%s, %s", last, first);
+ p[l] = '\0';
+ current->fullname = p;
+ convert_possibly_encoded_str_to_utf8(&current->fullname);
+ }
+
+ if(comment && current){
+ current->comment = cpystr(comment);
+ convert_possibly_encoded_str_to_utf8(&current->comment);
+ }
+
+ /*
+ * We don't want the personal name to make it into the address
+ * field in an LDAP: query sort of address, so move it
+ * out of the addr.
+ */
+ if(e->serv && *e->serv && current && fullname){
+ if(current->fullname)
+ fs_give((void **)&current->fullname);
+
+ current->fullname = fullname;
+ fullname = NULL;
+ convert_possibly_encoded_str_to_utf8(&current->fullname);
+ }
+
+ mail_free_address(&addr);
+
+ if(current)
+ takeaddr_bypass(ps, current, NULL);
+ else
+ q_status_message(SM_ORDER, 0, 4, "Nothing to save");
+
+ free_talines(&current);
+ }
+ else
+ q_status_message(SM_ORDER, 0, 4, "Nothing to save");
+
+ }
+ else if(export){ /* export to filesystem */
+ STORE_S *srcstore = NULL;
+ SourceType srctype;
+ static ESCKEY_S text_or_vcard[] = {
+ {'t', 't', "T", N_("Text")},
+ {'a', 'a', "A", N_("Addresses")},
+ {'v', 'v', "V", N_("VCard")},
+ {-1, 0, NULL, NULL}};
+
+ j = radio_buttons(
+ _("Export text of entry, address, or VCard format ? "),
+ -FOOTER_ROWS(ps), text_or_vcard, 't', 'x',
+ h_ldap_text_or_vcard, RB_NORM);
+
+ switch(j){
+ case 'x':
+ q_status_message(SM_INFO, 0, 2, _("Address book export cancelled"));
+ break;
+
+ case 't':
+ srctype = CharStar;
+ if(!(srcstore = prep_ldap_for_viewing(ps, e, srctype, NULL)))
+ q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
+ else{
+ (void)simple_export(ps_global, so_text(srcstore), srctype,
+ "text", NULL);
+ so_give(&srcstore);
+ }
+
+ break;
+
+ case 'a':
+ if(mail && mail[0] && mail[0][0]){
+
+ srctype = CharStar;
+ if(!(srcstore = so_get(srctype, NULL, EDIT_ACCESS)))
+ q_status_message(SM_ORDER,0,2, _("Error allocating space"));
+ else{
+ for(ll = mail; ll && *ll; ll++){
+ so_puts(srcstore, *ll);
+ so_puts(srcstore, "\n");
+ }
+
+ (void)simple_export(ps_global, so_text(srcstore),
+ srctype, "addresses", NULL);
+ so_give(&srcstore);
+ }
+ }
+
+ break;
+
+ case 'v':
+ srctype = CharStar;
+ if(!(srcstore = so_get(srctype, NULL, EDIT_ACCESS)))
+ q_status_message(SM_ORDER,0,2, _("Error allocating space"));
+ else{
+ gf_io_t pc;
+ VCARD_INFO_S *vinfo;
+
+ vinfo = (VCARD_INFO_S *)fs_get(sizeof(VCARD_INFO_S));
+ memset((void *)vinfo, 0, sizeof(VCARD_INFO_S));
+
+ if(cn && cn[0] && cn[0][0])
+ vinfo->fullname = copy_list_array(cn);
+
+ if(note && note[0] && note[0][0])
+ vinfo->note = copy_list_array(note);
+
+ if(title && title[0] && title[0][0])
+ vinfo->title = copy_list_array(title);
+
+ if(telephone && telephone[0] && telephone[0][0])
+ vinfo->tel = copy_list_array(telephone);
+
+ if(mail && mail[0] && mail[0][0])
+ vinfo->email = copy_list_array(mail);
+
+ if(sn && sn[0] && sn[0][0])
+ vinfo->last = cpystr(sn[0]);
+
+ if(givenname && givenname[0] && givenname[0][0])
+ vinfo->first = cpystr(givenname[0]);
+
+ gf_set_so_writec(&pc, srcstore);
+
+ write_single_vcard_entry(ps_global, pc, vinfo);
+
+ free_vcard_info(&vinfo);
+
+ (void)simple_export(ps_global, so_text(srcstore),
+ srctype, "vcard text", NULL);
+ so_give(&srcstore);
+ }
+
+ break;
+
+ default:
+ q_status_message(SM_ORDER, 3, 3, "can't happen in text_or_vcard");
+ break;
+ }
+ }
+
+ if(cn)
+ ldap_value_free(cn);
+ if(mail)
+ ldap_value_free(mail);
+ if(elecmail)
+ ldap_value_free(elecmail);
+ if(note)
+ ldap_value_free(note);
+ if(sn)
+ ldap_value_free(sn);
+ if(givenname)
+ ldap_value_free(givenname);
+ if(telephone)
+ ldap_value_free(telephone);
+ if(title)
+ ldap_value_free(title);
+ if(fullname)
+ fs_give((void **)&fullname);
+ if(address)
+ fs_give((void **)&address);
+ if(first)
+ fs_give((void **)&first);
+ if(last)
+ fs_give((void **)&last);
+ if(comment)
+ fs_give((void **)&comment);
+}
+#endif /* ENABLE_LDAP */
+
+
+#ifdef _WINDOWS
+
+int
+ta_scroll_up(count)
+ long count;
+{
+ if(count<0)
+ return(ta_scroll_down(-count));
+ else if (count){
+ long i=count;
+ TA_S *next_sel;
+
+ while(i && ta_screen->top_line->next){
+ if(ta_screen->top_line == ta_screen->current){
+ if(next_sel = next_sel_taline(ta_screen->current)){
+ ta_screen->current = next_sel;
+ ta_screen->top_line = next_taline(ta_screen->top_line);
+ i--;
+ }
+ else i = 0;
+ }
+ else{
+ ta_screen->top_line = next_taline(ta_screen->top_line);
+ i--;
+ }
+ }
+ }
+ return(TRUE);
+}
+
+int
+ta_scroll_down(count)
+ long count;
+{
+ if(count < 0)
+ return(ta_scroll_up(-count));
+ else if (count){
+ long i,dline;
+ long page_size = ps_global->ttyo->screen_rows -
+ FOOTER_ROWS(ps_global) - HEADER_ROWS(ps_global);
+ TA_S *ctmp;
+ TA_S *first = first_taline(ta_screen->top_line);
+
+ i=count;
+ dline=0;
+ for(ctmp = ta_screen->top_line;
+ ctmp != ta_screen->current; ctmp = next_taline(ctmp))
+ dline++;
+ while(i && ta_screen->top_line != first){
+ ta_screen->top_line = pre_taline(ta_screen->top_line);
+ i--;
+ dline++;
+ if(dline >= page_size){
+ ctmp = pre_sel_taline(ta_screen->current);
+ if(ctmp == NULL){
+ i = 0;
+ ta_screen->top_line = next_taline(ta_screen->top_line);
+ }
+ else {
+ for(; ctmp != ta_screen->current;
+ ta_screen->current = pre_taline(ta_screen->current))
+ dline--;
+ }
+ }
+ }
+ }
+ return(TRUE);
+}
+
+int ta_scroll_to_pos(line)
+ long line;
+{
+ TA_S *ctmp;
+ int dline;
+
+ for(dline = 0, ctmp = first_taline(ta_screen->top_line);
+ ctmp != ta_screen->top_line; ctmp = next_taline(ctmp))
+ dline++;
+
+ if (!ctmp)
+ dline = 1;
+
+ return(ta_scroll_up(line - dline));
+}
+
+int
+ta_scroll_callback (cmd, scroll_pos)
+ int cmd;
+ long scroll_pos;
+{
+ int paint = TRUE;
+ long page_size = ps_global->ttyo->screen_rows - HEADER_ROWS(ps_global)
+ - FOOTER_ROWS(ps_global);
+
+ switch (cmd) {
+ case MSWIN_KEY_SCROLLUPLINE:
+ paint = ta_scroll_down (scroll_pos);
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNLINE:
+ paint = ta_scroll_up (scroll_pos);
+ break;
+
+ case MSWIN_KEY_SCROLLUPPAGE:
+ paint = ta_scroll_down (page_size);
+ break;
+
+ case MSWIN_KEY_SCROLLDOWNPAGE:
+ paint = ta_scroll_up (page_size);
+ break;
+
+ case MSWIN_KEY_SCROLLTO:
+ paint = ta_scroll_to_pos (scroll_pos);
+ break;
+ }
+
+ if(paint)
+ update_takeaddr_screen(ps_global, ta_screen->current, ta_screen, (Pos *)NULL);
+
+ return(paint);
+}
+
+#endif /* _WINDOWS */
diff --git a/alpine/takeaddr.h b/alpine/takeaddr.h
new file mode 100644
index 00000000..37890023
--- /dev/null
+++ b/alpine/takeaddr.h
@@ -0,0 +1,40 @@
+/*
+ * $Id: takeaddr.h 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_TAKEADDR_INCLUDED
+#define PINE_TAKEADDR_INCLUDED
+
+
+#include "../pith/takeaddr.h"
+#include "../pith/state.h"
+#include "../pith/adrbklib.h"
+#include "../pith/msgno.h"
+#include "../pith/mailpart.h"
+#include "../pith/ldap.h"
+
+
+/* exported prototypes */
+int cmd_take_addr(struct pine *, MSGNO_S *, int);
+void take_this_one_entry(struct pine *, TA_STATE_S **, AdrBk *, long);
+void save_vcard_att(struct pine *, int, long, ATTACH_S *);
+void take_to_export(struct pine *, LINES_TO_TAKE *);
+PerAddrBook *setup_for_addrbook_add(SAVE_STATE_S *, int, char *);
+#ifdef ENABLE_LDAP
+void save_ldap_entry(struct pine *, LDAP_CHOOSE_S *, int);
+#endif
+
+
+#endif /* PINE_TAKEADDR_INCLUDED */
diff --git a/alpine/talk.h b/alpine/talk.h
new file mode 100644
index 00000000..86337bc0
--- /dev/null
+++ b/alpine/talk.h
@@ -0,0 +1,37 @@
+/*
+ * $Id: talk.h 761 2007-10-23 22:35:18Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PITH_TALK_INCLUDED
+#define PITH_TALK_INCLUDED
+
+
+/*
+ * Macros to aid hack to turn off talk permission.
+ * Bit 020 is usually used to turn off talk permission, we turn off
+ * 002 also for good measure, since some mesg commands seem to do that.
+ */
+#define TALK_BIT 020 /* mode bits */
+#define GM_BIT 002
+#define TMD_CLEAR 0 /* functions */
+#define TMD_SET 1
+#define TMD_RESET 2
+#define allow_talk(p) tty_chmod((p), TALK_BIT, TMD_SET)
+#define disallow_talk(p) tty_chmod((p), TALK_BIT|GM_BIT, TMD_CLEAR)
+
+
+/* exported protoypes */
+
+
+#endif /* PITH_TALK_INCLUDED */
diff --git a/alpine/titlebar.c b/alpine/titlebar.c
new file mode 100644
index 00000000..7f0d2cc3
--- /dev/null
+++ b/alpine/titlebar.c
@@ -0,0 +1,1236 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: titlebar.c 1075 2008-06-04 00:19:39Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "headers.h"
+#include "titlebar.h"
+#include "folder.h"
+#include "../pith/state.h"
+#include "../pith/bitmap.h"
+#include "../pith/msgno.h"
+#include "../pith/thread.h"
+#include "../pith/conf.h"
+#include "../pith/init.h"
+#include "../pith/sort.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/folder.h"
+
+/*
+ * Internal prototypes
+ */
+int digit_count(long);
+void output_titlebar(TITLE_S *);
+char sort_letter(SortOrder);
+char *percentage(long, long, int);
+
+
+/*
+ * some useful macros...
+ */
+#define MS_DEL (0x01)
+#define MS_NEW (0x02)
+#define MS_ANS (0x04)
+#define MS_FWD (0x08)
+
+#define STATUS_BITS(X) (!(X && (X)->valid) ? 0 \
+ : (X)->deleted ? MS_DEL \
+ : (X)->answered ? MS_ANS \
+ : (as.stream && user_flag_is_set(as.stream, (X)->msgno, FORWARDED_FLAG)) ? MS_FWD \
+ : (as.stream \
+ && (ps_global->unseen_in_view \
+ || (!(X)->seen \
+ && (!IS_NEWS(as.stream) \
+ || F_ON(F_FAKE_NEW_IN_NEWS, \
+ ps_global))))) \
+ ? MS_NEW : 0)
+
+#define BAR_STATUS(X) (((X) & MS_DEL) ? "DEL" \
+ : ((X) & MS_ANS) ? "ANS" \
+ : ((X) & MS_FWD) ? "FWD" \
+ : (as.stream \
+ && (!IS_NEWS(as.stream) \
+ || F_ON(F_FAKE_NEW_IN_NEWS, ps_global)) \
+ && ((X) & MS_NEW)) ? "NEW" : " ")
+
+
+static struct titlebar_state {
+ MAILSTREAM *stream;
+ MSGNO_S *msgmap;
+ char *title,
+ *folder_name,
+ *context_name;
+ long current_msg,
+ current_line,
+ current_thrd,
+ total_lines;
+ int msg_state,
+ cur_mess_col,
+ del_column,
+ percent_column,
+ page_column,
+ screen_cols;
+ enum {Normal, OnlyRead, Closed} stream_status;
+ TitleBarType style;
+ TITLE_S titlecontainer;
+} as, titlebar_stack;
+
+
+static int titlebar_is_dirty = 1;
+
+
+void
+push_titlebar_state(void)
+{
+ titlebar_stack = as;
+ as.folder_name = NULL; /* erase knowledge of malloc'd data */
+ as.context_name = NULL;
+}
+
+
+void
+pop_titlebar_state(void)
+{
+ /* guard against case where push pushed no state */
+ if(titlebar_stack.style != TitleBarNone){
+ fs_give((void **)&(as.folder_name)); /* free malloc'd values */
+ fs_give((void **)&(as.context_name));
+ as = titlebar_stack;
+ }
+}
+
+
+int
+digit_count(long int n)
+{
+ int i;
+
+ return((n > 9)
+ ? (1 + (((i = digit_count(n / 10L)) == 3 || i == 7) ? i+1 : i))
+ : 1);
+}
+
+
+void
+mark_titlebar_dirty(void)
+{
+ titlebar_is_dirty = 1;
+}
+
+
+/*----------------------------------------------------------------------
+ Sets up style and contents of current titlebar line
+
+ All of the text is assumed to be UTF-8 text, which probably isn't
+ true yet.
+
+ Args: title -- The title that appears in the center of the line
+ This title is in UTF-8 characters.
+ display_on_screen -- flag whether to display on screen or generate
+ string
+ style -- The format/style of the titlebar line
+ msgmap -- MSGNO_S * to selected message map
+ current_pl -- The current page or line number
+ total_pl -- The total line or page count
+
+ Set the contents of the anchor line. It's called an anchor line
+because it's always present and titlebars the user. This accesses a
+number of global variables, but doesn't change any. There are several
+different styles of the titlebar line.
+
+It's OK to call this with a bogus current message - it is only used
+to look up status of current message
+
+Formats only change the right section (part3).
+ FolderName: "<folder>" xx Messages
+ MessageNumber: "<folder>" message x,xxx of x,xxx XXX
+ TextPercent: line xxx of xxx xx%
+ MsgTextPercent: "<folder>" message x,xxx of x,xxx xx% XXX
+ FileTextPercent: "<filename>" line xxx of xxx xx%
+
+Several strings and column numbers are saved so later updates to the status
+line for changes in message number or percentage can be done efficiently.
+ ----*/
+
+char *
+set_titlebar(char *title, MAILSTREAM *stream, CONTEXT_S *cntxt, char *folder,
+ MSGNO_S *msgmap, int display_on_screen, TitleBarType style,
+ long int current_pl, long int total_pl, COLOR_PAIR *color)
+{
+ struct variable *vars = ps_global->vars;
+ MESSAGECACHE *mc = NULL;
+ PINETHRD_S *thrd = NULL;
+ TITLE_S *tc;
+
+ dprint((9, "set_titlebar - style: %d current message cnt:%ld",
+ style, mn_total_cur(msgmap)));
+ dprint((9, " current_pl: %ld total_pl: %ld\n",
+ current_pl, total_pl));
+
+ as.current_msg = (mn_get_total(msgmap) > 0L)
+ ? MAX(0, mn_get_cur(msgmap)) : 0L;
+ as.msgmap = msgmap;
+ as.style = style;
+ as.title = title;
+ as.stream = stream;
+ as.stream_status = (!as.stream || (sp_dead_stream(as.stream)))
+ ? Closed : as.stream->rdonly ? OnlyRead : Normal;
+
+ if(ps_global->first_open_was_attempted
+ && as.stream_status == Closed
+ && VAR_TITLECLOSED_FORE_COLOR && VAR_TITLECLOSED_BACK_COLOR){
+ memset(&as.titlecontainer.color, 0, sizeof(as.titlecontainer.color));
+ strncpy(as.titlecontainer.color.fg,
+ VAR_TITLECLOSED_FORE_COLOR, MAXCOLORLEN);
+ as.titlecontainer.color.fg[MAXCOLORLEN] = '\0';
+ strncpy(as.titlecontainer.color.bg,
+ VAR_TITLECLOSED_BACK_COLOR, MAXCOLORLEN);
+ as.titlecontainer.color.bg[MAXCOLORLEN] = '\0';
+ }
+ else{
+ if(color){
+ memset(&as.titlecontainer.color, 0, sizeof(as.titlecontainer.color));
+ if(color->fg){
+ strncpy(as.titlecontainer.color.fg, color->fg, MAXCOLORLEN);
+ as.titlecontainer.color.fg[MAXCOLORLEN] = '\0';
+ }
+
+ if(color->bg){
+ strncpy(as.titlecontainer.color.bg, color->bg, MAXCOLORLEN);
+ as.titlecontainer.color.bg[MAXCOLORLEN] = '\0';
+ }
+ }
+ else{
+ memset(&as.titlecontainer.color, 0, sizeof(as.titlecontainer.color));
+ if(VAR_TITLE_FORE_COLOR){
+ strncpy(as.titlecontainer.color.fg,
+ VAR_TITLE_FORE_COLOR, MAXCOLORLEN);
+ as.titlecontainer.color.fg[MAXCOLORLEN] = '\0';
+ }
+
+ if(VAR_TITLE_BACK_COLOR){
+ strncpy(as.titlecontainer.color.bg,
+ VAR_TITLE_BACK_COLOR, MAXCOLORLEN);
+ as.titlecontainer.color.bg[MAXCOLORLEN] = '\0';
+ }
+ }
+ }
+
+ if(as.folder_name)
+ fs_give((void **)&as.folder_name);
+
+ if(folder){
+ unsigned char *fname;
+ fname = folder_name_decoded((unsigned char *) folder);
+ if(!strucmp(folder, ps_global->inbox_name) && cntxt == ps_global->context_list)
+ as.folder_name = cpystr(pretty_fn(fname ? (char *) fname : folder));
+ else
+ as.folder_name = cpystr(fname ? (char *) fname : folder);
+ if(fname) fs_give((void **)&fname);
+ }
+
+ if(!as.folder_name)
+ as.folder_name = cpystr("");
+
+ if(as.context_name)
+ fs_give((void **)&as.context_name);
+
+ /*
+ * Handle setting up the context if appropriate.
+ */
+ if(cntxt && context_isambig(folder) && ps_global->context_list->next
+ && (strucmp(as.folder_name, ps_global->inbox_name) || cntxt != ps_global->context_list)){
+ /*
+ * if there's more than one context and the current folder
+ * is in it (ambiguous name), set the context name...
+ */
+ as.context_name = cpystr(cntxt->nickname
+ ? cntxt->nickname
+ : cntxt->context);
+ }
+
+ if(!as.context_name)
+ as.context_name = cpystr("");
+
+ if(as.stream && style != FolderName
+ && style != ThrdIndex && as.current_msg > 0L) {
+ long rawno;
+
+ if((rawno = mn_m2raw(msgmap, as.current_msg)) > 0L
+ && rawno <= as.stream->nmsgs
+ && !((mc = mail_elt(as.stream, rawno)) && mc->valid)){
+ pine_mail_fetch_flags(as.stream, long2string(rawno), NIL);
+ mc = mail_elt(as.stream, rawno);
+ }
+ }
+
+ if(style == ThrdIndex || style == ThrdMsgNum || style == ThrdMsgPercent){
+ if(mn_get_total(msgmap) > 0L)
+ thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
+
+ if(thrd && thrd->top && thrd->top != thrd->rawno)
+ thrd = fetch_thread(stream, thrd->top);
+
+ if(thrd)
+ as.current_thrd = thrd->thrdno;
+ }
+
+ switch(style) {
+ case ThrdIndex:
+ as.total_lines = msgmap->max_thrdno;
+ break;
+
+ case TextPercent:
+ case MsgTextPercent:
+ case FileTextPercent :
+ case ThrdMsgPercent:
+ as.total_lines = total_pl;
+ as.current_line = current_pl;
+ /* fall through to set state */
+ case ThrdMsgNum:
+ case MessageNumber:
+ as.msg_state = STATUS_BITS(mc);
+
+ case FolderName: /* nothing more to do for this one */
+ break;
+ default:
+ break;
+ }
+
+ tc = format_titlebar();
+ if(display_on_screen)
+ output_titlebar(tc);
+
+ return(tc->titlebar_line);
+}
+
+
+void
+redraw_titlebar(void)
+{
+ output_titlebar(format_titlebar());
+}
+
+
+void
+output_titlebar(TITLE_S *tc)
+{
+ COLOR_PAIR *lastc = NULL, *newcolor;
+
+ if(ps_global->ttyo
+ && (ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)) < 1){
+ titlebar_is_dirty = 1;
+ return;
+ }
+
+ newcolor = tc ? &tc->color : NULL;
+
+ if(newcolor)
+ lastc = pico_set_colorp(newcolor, PSC_REV | PSC_RET);
+
+ if(tc && tc->titlebar_line)
+ PutLine0(0, 0, tc->titlebar_line);
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ fflush(stdout);
+}
+
+
+void
+titlebar_stream_closing(MAILSTREAM *stream)
+{
+ if(stream == as.stream)
+ as.stream = NULL;
+}
+
+
+/* caller should free returned color pair */
+COLOR_PAIR *
+current_titlebar_color(void)
+{
+ TITLE_S *tc;
+ COLOR_PAIR *col;
+ COLOR_PAIR *the_color = NULL;
+
+ tc = format_titlebar();
+ col = tc ? &tc->color : NULL;
+
+ if(col && col->fg && col->fg[0] && col->bg && col->bg[0])
+ the_color = new_color_pair(col->fg, col->bg);
+
+ return(the_color);
+}
+
+
+/*----------------------------------------------------------------------
+ Redraw or draw the top line, the title bar
+
+ The titlebar has Four fields:
+ 1) "Version" of fixed length and is always positioned two spaces
+ in from left display edge.
+ 2) "Location" which is fixed for each style of titlebar and
+ is positioned two spaces from the right display edge
+ 3) "Title" which is of fixed length, and is centered if
+ there's space
+ 4) "Folder" whose existence depends on style and which can
+ have it's length adjusted (within limits) so it will
+ equally share the space between 1) and 2) with the
+ "Title". The rule for existence is that in the
+ space between 1) and 2) there must be one space between
+ 3) and 4) AND at least 50% of 4) must be displayed.
+
+ Returns - Formatted title bar
+ ----*/
+TITLE_S *
+format_titlebar(void)
+{
+ char version[50], fold_tmp[6*MAXPATH+1], *titlebar_line,
+ loc1[200], loc_label[10], *thd_label, *ss_string, *q,
+ *plus, *loc2 = "", title[200];
+ int title_len = 0, ver_len, loc1_len = 0, loc2_len = 0, fold_len = 0, num_len,
+ s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, s6 = 0, tryloc = 1,
+ cur_mess_col_offset = -1, percent_column_offset = -1, page_column_offset = -1,
+ ss_len, thd_len, is_context, avail, extra;
+
+ titlebar_is_dirty = 0;
+
+ if(!as.title)
+ return NULL;
+
+ if(!as.folder_name)
+ as.folder_name = cpystr("");
+
+ if(!as.context_name)
+ as.context_name = cpystr("");
+
+ /*
+ * Full blown title looks like:
+ *
+ * | LV vers VT title TF folder FL location LR |
+ */
+#define LV 2 /* space between Left edge and Version, must be >= 2 */
+#define VT 3 /* space between Version and Title */
+#define TF 1 /* space between Title and Folder */
+#define FL 2 /* space between Folder and Location */
+#define LR 2 /* space between Location and Right edge, >= 2 */
+/* half of n but round up */
+#define HRU(n) (((n) <= 0) ? 0 : (((n)%2) ? ((n)+1)/2 : (n)/2))
+/* half of n but round down */
+#define HRD(n) (((n) <= 0) ? 0 : ((n)/2))
+
+ titlebar_line = as.titlecontainer.titlebar_line;
+
+ avail = MIN(ps_global->ttyo->screen_cols, MAX_SCREEN_COLS);
+
+ /* initialize */
+ as.del_column = -1;
+ as.cur_mess_col = -1;
+ as.percent_column = -1;
+ as.page_column = -1;
+
+ is_context = as.context_name ? strlen(as.context_name) : 0;
+
+ snprintf(version, sizeof(version), "ALPINE %s", ALPINE_VERSION);
+ version[sizeof(version)-1] = '\0';
+ ver_len = (int) utf8_width(version); /* fixed version field width */
+
+ title[0] = '\0';
+ if(as.title){
+ strncpy(title, as.title, sizeof(title));
+ title[sizeof(title)-1] = '\0';
+ }
+
+ /* Add Sort indicator to title */
+ if(F_ON(F_SHOW_SORT, ps_global) &&
+ !(as.style == FileTextPercent || as.style == TextPercent)){
+ SortOrder current_sort;
+ int current_rev;
+ char let;
+
+ current_sort = mn_get_sort(ps_global->msgmap);
+ current_rev = mn_get_revsort(ps_global->msgmap);
+
+ /* turn current_sort into a letter */
+ let = sort_letter(current_sort);
+ if(let == 'A' && current_rev){
+ let = 'R';
+ current_rev = 0;
+ }
+
+ snprintf(title+strlen(title), sizeof(title)-strlen(title),
+ " [%s%c]", current_rev ? "R" : "", let);
+ title[sizeof(title)-1] = '\0';
+ }
+
+ title_len = (int) utf8_width(title); /* title field width */
+
+ s1 = MAX(MIN(LV, avail), 0); /* left two spaces */
+ avail -= s1;
+
+ s6 = MAX(MIN(LR, avail), 0); /* right two spaces */
+ avail -= s6;
+
+ title_len = MAX(MIN(title_len, avail), 0);
+ avail -= title_len;
+
+ if(ver_len + VT > avail){ /* can only fit title */
+ ver_len = 0;
+
+ s2 = MAX(MIN(HRD(avail), avail), 0);
+ avail -= s2;
+
+ s3 = MAX(0, avail);
+ }
+ else{
+ s2 = MAX(MIN(VT, avail), 0);
+ avail -= s2;
+
+ ver_len = MAX(MIN(ver_len, avail), 0);
+ avail -= ver_len;
+
+try_smaller_loc:
+
+ /*
+ * set location field's length and value based on requested style
+ */
+ if(as.style == ThrdIndex)
+ /* TRANSLATORS: In titlebar, Thd is an abbreviation for Thread, Msg for Message.
+ They are used when there isn't enough space so need to be short.
+ The formatting isn't very flexible. These come before the number
+ of the message or thread, as in
+ Message 17
+ when reading message number 17. */
+ snprintf(loc_label, sizeof(loc_label), "%s ", (is_context || tryloc==2) ? _("Thd") : _("Thread"));
+ else
+ snprintf(loc_label, sizeof(loc_label), "%s ", (is_context || tryloc==2) ? _("Msg") : _("Message"));
+
+ if(tryloc == 3 && as.style != FolderName && mn_get_total(as.msgmap))
+ loc_label[0] = '\0';
+
+ loc_label[sizeof(loc_label)-1] = '\0';
+
+ if(as.style == ThrdMsgNum || as.style == ThrdMsgPercent){
+ thd_label = is_context ? _("Thd") : _("Thread");
+ thd_len = (int) utf8_width(thd_label);
+ }
+
+ loc1_len = (int) utf8_width(loc_label); /* initial length */
+
+ if(!mn_get_total(as.msgmap)){
+ loc_label[strlen(loc_label)-1] = 's';
+ snprintf(loc1, sizeof(loc1), "%s %s", _("No"), loc_label);
+ loc1[sizeof(loc1)-1]= '\0';
+ }
+ else{
+ switch(as.style){
+ case FolderName : /* "x,xxx <loc_label>s" */
+ if(*loc_label){
+ if(mn_get_total(as.msgmap) != 1)
+ loc_label[strlen(loc_label)-1] = 's';
+ else
+ loc_label[strlen(loc_label)-1] = '\0';
+ }
+
+ snprintf(loc1, sizeof(loc1), "%s %s", comatose(mn_get_total(as.msgmap)), loc_label);
+ loc1[sizeof(loc1)-1]= '\0';
+ break;
+
+ case MessageNumber : /* "<loc_label> xxx of xxx DEL" */
+ num_len = strlen(comatose(mn_get_total(as.msgmap)));
+ cur_mess_col_offset = loc1_len;
+ snprintf(loc1, sizeof(loc1), "%s%*.*s of %s", loc_label,
+ num_len, num_len,
+ comatose(as.current_msg),
+ comatose(mn_get_total(as.msgmap)));
+ loc1[sizeof(loc1)-1]= '\0';
+ loc2 = BAR_STATUS(as.msg_state);
+ loc2_len = 3;
+ break;
+
+ case ThrdIndex : /* "<loc_label> xxx of xxx" */
+ num_len = strlen(comatose(as.total_lines));
+ cur_mess_col_offset = loc1_len;
+ snprintf(loc1, sizeof(loc1), "%s%*.*s of %s", loc_label,
+ num_len, num_len,
+ comatose(as.current_thrd),
+ comatose(as.total_lines));
+ loc1[sizeof(loc1)-1]= '\0';
+ break;
+
+ case ThrdMsgNum : /* "<loc_label> xxx in Thd xxx DEL" */
+ num_len = strlen(comatose(mn_get_total(as.msgmap)));
+ cur_mess_col_offset = loc1_len;
+ snprintf(loc1, sizeof(loc1), "%s%*.*s in %s %s", loc_label,
+ num_len, num_len,
+ comatose(as.current_msg),
+ thd_label,
+ comatose(as.current_thrd));
+ loc1[sizeof(loc1)-1]= '\0';
+ loc2 = BAR_STATUS(as.msg_state);
+ loc2_len = 3;
+ break;
+
+ case MsgTextPercent : /* "<loc_label> xxx of xxx xx% DEL" */
+ num_len = strlen(comatose(mn_get_total(as.msgmap)));
+ cur_mess_col_offset = loc1_len;
+ percent_column_offset = 3;
+ snprintf(loc1, sizeof(loc1), "%s%*.*s of %s %s", loc_label,
+ num_len, num_len,
+ comatose(as.current_msg),
+ comatose(mn_get_total(as.msgmap)),
+ percentage(as.current_line, as.total_lines, 1));
+ loc1[sizeof(loc1)-1]= '\0';
+ loc2 = BAR_STATUS(as.msg_state);
+ loc2_len = 3;
+ break;
+
+ case ThrdMsgPercent : /* "<loc_label> xxx in Thd xxx xx% DEL" */
+ num_len = strlen(comatose(mn_get_total(as.msgmap)));
+ cur_mess_col_offset = loc1_len;
+ percent_column_offset = 3;
+ snprintf(loc1, sizeof(loc1), "%s%*.*s in %s %s %s", loc_label,
+ num_len, num_len,
+ comatose(as.current_msg),
+ thd_label,
+ comatose(as.current_thrd),
+ percentage(as.current_line, as.total_lines, 1));
+ loc1[sizeof(loc1)-1]= '\0';
+ loc2 = BAR_STATUS(as.msg_state);
+ loc2_len = 3;
+ break;
+
+ case TextPercent :
+ /* NOTE: no fold_tmp setup below for TextPercent style */
+ case FileTextPercent : /* "Line xxx of xxx xx%" */
+ num_len = strlen(comatose(as.total_lines));
+ page_column_offset = 5;
+ percent_column_offset = 3;
+ snprintf(loc1, sizeof(loc1), "Line %*.*s of %s %s",
+ num_len, num_len,
+ comatose(as.current_line),
+ comatose(as.total_lines),
+ percentage(as.current_line, as.total_lines, 1));
+ loc1[sizeof(loc1)-1]= '\0';
+ break;
+ default:
+ break;
+ }
+ }
+
+ loc1_len = utf8_width(loc1);
+
+ if(loc1_len + loc2_len + ((loc2_len > 0) ? 1 : 0) >= avail){ /* can't fit location in */
+ if(tryloc < 3){
+ tryloc++;
+ goto try_smaller_loc;
+ }
+
+ loc1_len = loc2_len = 0;
+
+ avail += s2; /* re-allocate s2 to center title */
+
+ s2 = MAX(MIN(HRD(avail), avail), 0);
+ avail -= s2;
+
+ s3 = MAX(0, avail);
+ }
+ else{
+ loc1_len = MAX(MIN(loc1_len, avail), 0);
+ avail -= loc1_len;
+
+ loc2_len = MAX(MIN(loc2_len, avail), 0);
+ avail -= loc2_len;
+
+ if(loc2_len > 0){
+ s5 = MAX(MIN(1, avail), 0);
+ avail -= s5;
+ }
+ else
+ s5 = 0;
+
+ s3 = MAX(MIN(TF, avail), 0);
+ avail -= s3;
+
+ s4 = MAX(MIN(FL, avail), 0);
+ avail -= s4;
+
+ if(avail){
+ /* TRANSLATORS: it might say READONLY or CLOSED in the titlebar, referring to
+ the current folder. */
+ ss_string = as.stream_status == Closed ? _("(CLOSED)") :
+ (as.stream_status == OnlyRead
+ && !IS_NEWS(as.stream))
+ ? _("(READONLY)") : "";
+ ss_len = (int) utf8_width(ss_string);
+
+ /* figure folder_length and what's to be displayed */
+ fold_tmp[0] = '\0';
+ if(as.style == FileTextPercent || as.style == TextPercent){
+ if(as.style == FileTextPercent){
+ extra = (int) utf8_width("File: ");
+ fold_len = (int) utf8_width(as.folder_name);
+ if(fold_len + extra <= avail){ /* all of folder fit? */
+ strncpy(fold_tmp, "File: ", sizeof(fold_tmp));
+ q = fold_tmp + strlen(fold_tmp);
+ strncpy(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ else if(HRU(fold_len) + extra+3 <= avail){
+ /*
+ * fold_tmp = ...partial_folder_name
+ */
+ strncpy(fold_tmp, "File: ...", sizeof(fold_tmp));
+ q = fold_tmp + strlen(fold_tmp);
+ (void) utf8_to_width_rhs(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp), avail-(extra+3));
+ }
+ }
+ /* else leave folder/file name blank */
+ }
+ else{
+ int ct_len;
+
+ fold_len = (int) utf8_width(as.folder_name);
+
+ if(is_context
+ && as.stream_status != Closed
+ && (ct_len = (int) utf8_width(as.context_name))){
+
+ extra = 3; /* length from "<" ">" and SPACE */
+
+ if(ct_len + fold_len + ss_len + extra <= avail){
+ q = fold_tmp;
+ *q++ = '<';
+ strncpy(q, as.context_name, sizeof(fold_tmp)-(q-fold_tmp));
+ q += strlen(q);
+ *q++ = '>';
+ *q++ = ' ';
+ strncpy(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp));
+ q += strlen(q);
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ else if(ct_len + fold_len + ss_len + extra <= avail){
+ q = fold_tmp;
+ *q++ = '<';
+ strncpy(q, as.context_name, sizeof(fold_tmp)-(q-fold_tmp));
+ q += strlen(q);
+ *q++ = '>';
+ *q++ = ' ';
+ strncpy(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp));
+ q += strlen(q);
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ else if(HRU(ct_len) + fold_len + ss_len + extra <= avail){
+ q = fold_tmp;
+ *q++ = '<';
+ q += utf8_pad_to_width(q, as.context_name, sizeof(fold_tmp)-(q-fold_tmp), avail-(fold_len+ss_len+extra), 1);
+ *q++ = '>';
+ *q++ = ' ';
+ strncpy(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp));
+ q += strlen(q);
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ else if(HRU(ct_len) + HRU(fold_len) + ss_len + extra <= avail){
+ q = fold_tmp;
+ *q++ = '<';
+ q += utf8_pad_to_width(q, as.context_name, sizeof(fold_tmp)-(q-fold_tmp), HRU(ct_len), 1);
+ *q++ = '>';
+ *q++ = ' ';
+ q += utf8_to_width_rhs(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp), avail-(HRU(ct_len)+ss_len+extra));
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ else if(ss_len > 0 && ss_len <= avail){
+ q = fold_tmp;
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ /* else leave it out */
+ }
+ else{
+ /* TRANSLATORS: the name of the open folder follows this in the titlebar */
+ extra = strlen(_("Folder: "));
+ if(fold_len + ss_len + extra <= avail){
+ q = fold_tmp;
+ strncpy(q, "Folder: ", sizeof(fold_tmp)-(q-fold_tmp));
+ q += strlen(q);
+ strncpy(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp));
+ q += strlen(q);
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ else{
+ if(fold_len + ss_len <= avail){
+ q = fold_tmp;
+ strncpy(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp));
+ q += strlen(q);
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ else if(((HRU(fold_len) + ss_len <= avail)
+ || (avail > ss_len+3 && avail > 10)) && fold_len > 5){
+ q = fold_tmp;
+ strncpy(q, "...", sizeof(fold_tmp));
+ q += strlen(q);
+ q += utf8_to_width_rhs(q, as.folder_name, sizeof(fold_tmp)-(q-fold_tmp), avail-(3+ss_len));
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ else if(ss_len > 0 && ss_len <= avail){
+ q = fold_tmp;
+ strncpy(q, ss_string, sizeof(fold_tmp)-(q-fold_tmp));
+ }
+ /* else leave it out */
+ }
+ }
+ }
+
+ fold_tmp[sizeof(fold_tmp)-1] = '\0';
+
+ /* write title, location and, optionally, the folder name */
+ fold_len = (int) utf8_width(fold_tmp);
+ }
+
+ fold_len = MAX(MIN(fold_len, avail), 0);
+ avail -= fold_len;
+
+ /* center folder in its space */
+ if(avail){
+ int inc;
+
+ inc = HRU(avail);
+
+ s3 += inc;
+ avail -= inc;
+
+ s4 += avail;
+ }
+ }
+ }
+
+ plus = "";
+
+ if(as.style != FileTextPercent && as.style != TextPercent){
+ NETMBX mb;
+
+ if(as.stream
+ && as.stream->mailbox
+ && mail_valid_net_parse(as.stream->mailbox, &mb)
+ && (mb.sslflag || mb.tlsflag))
+ plus = "+";
+ }
+
+
+ if(loc1_len > 0 && cur_mess_col_offset >= 0)
+ as.cur_mess_col = s1+ver_len+s2+title_len+s3+fold_len+s4 + cur_mess_col_offset;
+
+ if(loc1_len > 0 && page_column_offset >= 0)
+ as.page_column = s1+ver_len+s2+title_len+s3+fold_len+s4 + page_column_offset;
+
+ if(loc2_len > 0)
+ as.del_column = s1+ver_len+s2+title_len+s3+fold_len+s4+loc1_len+s5;
+
+ if(loc1_len > 0 && percent_column_offset > 0)
+ as.percent_column = s1+ver_len+s2+title_len+s3+fold_len+s4+loc1_len - percent_column_offset;
+
+
+ utf8_snprintf(titlebar_line, 6*MAX_SCREEN_COLS, "%*.*s%-*.*w%*.*s%-*.*w%*.*s%-*.*w%*.*s%-*.*w%*.*s%-*.*w%*.*s",
+ s1, s1, "",
+ ver_len, ver_len, version,
+ s2, s2, "",
+ title_len, title_len, title,
+ s3, s3, "",
+ fold_len, fold_len, fold_tmp,
+ s4, s4, "",
+ loc1_len, loc1_len, loc1,
+ s5, s5, "",
+ loc2_len, loc2_len, loc2,
+ s6, s6, plus);
+
+ return(&as.titlecontainer);
+}
+
+
+char
+sort_letter(SortOrder sort)
+{
+ char let = 'A', *p;
+
+ if((p = sort_name(sort)) != NULL && *p){
+ while(*(p+1) && islower((unsigned char) *p))
+ p++;
+
+ if(*p)
+ let = *p;
+ }
+
+ return(let);
+}
+
+
+/*
+ * Update the titlebar line if the message number changed
+ *
+ * Args: None, uses state setup on previous call to set_titlebar.
+ */
+void
+update_titlebar_message(void)
+{
+ long curnum, maxnum, oldnum;
+ PINETHRD_S *thrd = NULL;
+ COLOR_PAIR *lastc = NULL, *titlecolor;
+ char buf[50];
+ int num_len;
+ unsigned long rawno;
+
+ if(ps_global->ttyo
+ && (ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)) < 1){
+ titlebar_is_dirty = 1;
+ return;
+ }
+
+ if(as.style == ThrdIndex){
+
+ oldnum = as.current_thrd;
+
+ if(as.stream && (rawno=mn_m2raw(as.msgmap, mn_get_cur(as.msgmap))))
+ thrd = fetch_thread(as.stream, rawno);
+
+ if(thrd && thrd->top && (thrd=fetch_thread(as.stream, thrd->top)))
+ curnum = thrd->thrdno;
+ }
+ else if(as.cur_mess_col >= 0){
+ curnum = mn_get_cur(as.msgmap);
+ oldnum = as.current_msg;
+ }
+
+ if(as.cur_mess_col >= 0 && oldnum != curnum){
+
+ if(as.style == ThrdIndex){
+ as.current_thrd = curnum;
+ maxnum = as.msgmap->max_thrdno;
+ }
+ else{
+ as.current_msg = curnum;
+ maxnum = mn_get_total(as.msgmap);
+ }
+
+ titlecolor = &as.titlecontainer.color;
+
+ if(titlecolor)
+ lastc = pico_set_colorp(titlecolor, PSC_REV|PSC_RET);
+
+ num_len = strlen(comatose(maxnum));
+
+ snprintf(buf, sizeof(buf), "%*.*s", num_len, num_len, comatose(curnum));
+
+ PutLine0(0, as.cur_mess_col, buf);
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ fflush(stdout);
+ }
+}
+
+
+
+/*
+ * Update titlebar line's message status field ("DEL", "NEW", etc)
+ *
+ * Args: None, operates on state set during most recent set_titlebar call
+ */
+int
+update_titlebar_status(void)
+{
+ unsigned long rawno;
+ MESSAGECACHE *mc;
+ COLOR_PAIR *lastc = NULL, *titlecolor;
+
+ if(ps_global->ttyo
+ && (ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)) < 1){
+ titlebar_is_dirty = 1;
+ return(0);
+ }
+
+ if(!as.stream || as.current_msg <= 0L || as.del_column < 0)
+ return(1);
+
+ if(as.current_msg != mn_get_cur(as.msgmap))
+ update_titlebar_message();
+
+ mc = ((rawno = mn_m2raw(as.msgmap, as.current_msg)) > 0L
+ && rawno <= as.stream->nmsgs)
+ ? mail_elt(as.stream, rawno) : NULL;
+
+ if(!(mc && mc->valid))
+ return(0); /* indeterminate */
+
+ if(mc->deleted){ /* deleted takes precedence */
+ if(as.msg_state & MS_DEL)
+ return(1);
+ }
+ else if(mc->answered){ /* then answered */
+ if(as.msg_state & MS_ANS)
+ return(1);
+ } /* then forwarded */
+ else if(user_flag_is_set(as.stream, mc->msgno, FORWARDED_FLAG)){
+ if(as.msg_state & MS_FWD)
+ return(1);
+ }
+ else if(!mc->seen && as.stream
+ && (!IS_NEWS(as.stream)
+ || F_ON(F_FAKE_NEW_IN_NEWS, ps_global))){
+ if(as.msg_state & MS_NEW) /* then seen */
+ return(1);
+ }
+ else{
+ if(as.msg_state == 0) /* nothing to change */
+ return(1);
+ }
+
+ as.msg_state = STATUS_BITS(mc);
+
+ titlecolor = &as.titlecontainer.color;
+
+ if(titlecolor)
+ lastc = pico_set_colorp(titlecolor, PSC_REV|PSC_RET);
+
+ PutLine0(0, as.del_column, BAR_STATUS(as.msg_state));
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ fflush(stdout);
+ return(1);
+}
+
+
+/*
+ * Update the percentage shown in the titlebar line
+ *
+ * Args: new_line_number -- line number to calculate new percentage
+ */
+void
+update_titlebar_percent(long int new_line_number)
+{
+ COLOR_PAIR *lastc = NULL, *titlecolor;
+
+ if(as.percent_column < 0 || new_line_number == as.current_line)
+ return;
+
+ if(ps_global->ttyo
+ && (ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)) < 1){
+ titlebar_is_dirty = 1;
+ return;
+ }
+
+ as.current_line = new_line_number;
+
+ titlecolor = &as.titlecontainer.color;
+
+ if(titlecolor)
+ lastc = pico_set_colorp(titlecolor, PSC_REV|PSC_RET);
+
+ PutLine0(0, as.percent_column,
+ percentage(as.current_line, as.total_lines, 0));
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ fflush(stdout);
+}
+
+
+/*
+ * Update the percentage AND line number shown in the titlebar line
+ *
+ * Args: new_line_number -- line number to calculate new percentage
+ */
+void
+update_titlebar_lpercent(long int new_line_number)
+{
+ COLOR_PAIR *lastc = NULL, *titlecolor;
+ int num_len;
+ char buf[50];
+
+ if(as.page_column < 0 || new_line_number == as.current_line)
+ return;
+
+ if(ps_global->ttyo
+ && (ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)) < 1){
+ titlebar_is_dirty = 1;
+ return;
+ }
+
+ as.current_line = new_line_number;
+
+ titlecolor = &as.titlecontainer.color;
+
+ if(titlecolor)
+ lastc = pico_set_colorp(titlecolor, PSC_REV|PSC_RET);
+
+ num_len = strlen(comatose(as.total_lines));
+ snprintf(buf, sizeof(buf), "%*.*s", num_len, num_len, comatose(as.current_line));
+
+ PutLine0(0, as.page_column, buf);
+
+ PutLine0(0, as.percent_column,
+ percentage(as.current_line, as.total_lines, 0));
+
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ fflush(stdout);
+}
+
+
+/*----------------------------------------------------------------------
+ Return static buf containing portion of lines displayed
+
+ Args: part -- how much so far
+ total -- how many total
+
+ ---*/
+char *
+percentage(long int part, long int total, int suppress_top)
+{
+ static char percent[4];
+
+ if(total == 0L || (total <= ps_global->ttyo->screen_rows
+ - HEADER_ROWS(ps_global)
+ - FOOTER_ROWS(ps_global)))
+ strncpy(percent, "ALL", sizeof(percent));
+ else if(!suppress_top && part <= ps_global->ttyo->screen_rows
+ - HEADER_ROWS(ps_global)
+ - FOOTER_ROWS(ps_global))
+ strncpy(percent, "TOP", sizeof(percent));
+ else if(part >= total)
+ strncpy(percent, "END", sizeof(percent));
+ else
+ snprintf(percent, sizeof(percent), "%2ld%%", (100L * part)/total);
+
+ percent[sizeof(percent)-1] = '\0';
+
+ return(percent);
+}
+
+
+/*
+ * end_titlebar - free resources associated with titlebar state struct
+ */
+void
+end_titlebar(void)
+{
+ if(as.folder_name)
+ fs_give((void **)&as.folder_name);
+
+ if(as.context_name)
+ fs_give((void **)&as.context_name);
+}
+
+
+/*----------------------------------------------------------------------
+ Exported method to display status of mail check
+
+ Args: putstr -- should be NO LONGER THAN 2 bytes
+
+ Result: putstr displayed at upper-left-hand corner of screen
+ ----*/
+void
+check_cue_display(char *putstr)
+{
+ COLOR_PAIR *lastc = NULL, *titlecolor;
+ static char check_cue_char;
+
+ if(ps_global->read_predicted &&
+ (check_cue_char == putstr[0]
+ || (putstr[0] == ' ' && putstr[1] == '\0')))
+ return;
+ else{
+ if(putstr[0] == ' ')
+ check_cue_char = '\0';
+ else
+ check_cue_char = putstr[0];
+ }
+
+ titlecolor = &as.titlecontainer.color;
+
+ if(titlecolor)
+ lastc = pico_set_colorp(titlecolor, PSC_REV|PSC_RET);
+
+ PutLine0(0, 0, putstr); /* show delay cue */
+ if(lastc){
+ (void)pico_set_colorp(lastc, PSC_NONE);
+ free_color_pair(&lastc);
+ }
+
+ fflush(stdout);
+}
+
+
+/*----------------------------------------------------------------------
+ Mandatory function of ../pith/newmail.c
+
+ Args: none
+
+ Result: newmail cue displayed at upper-left-hand corner of screen
+ ----*/
+void
+newmail_check_cue(int onoroff)
+{
+ if(F_ON(F_SHOW_DELAY_CUE, ps_global) && !ps_global->in_init_seq){
+ check_cue_display((onoroff == TRUE) ? " *" : " ");
+ MoveCursor(ps_global->ttyo->screen_rows-FOOTER_ROWS(ps_global), 0);
+ }
+
+#ifdef _WINDOWS
+ if(onoroff)
+ mswin_setcursor (MSWIN_CURSOR_BUSY);
+ else
+ mswin_setcursor (MSWIN_CURSOR_ARROW);
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Mandatory function of ../pith/newmail.c
+
+ Args: none
+
+ Result: checkpoint delay cue displayed at upper-left-hand corner of screen
+ ----*/
+void
+newmail_check_point_cue(int onoroff)
+{
+ if(F_ON(F_SHOW_DELAY_CUE, ps_global)){
+ check_cue_display((onoroff == TRUE) ? "**" : " ");
+ MoveCursor(ps_global->ttyo->screen_rows-FOOTER_ROWS(ps_global), 0);
+ }
+
+#ifdef _WINDOWS
+ if(onoroff)
+ mswin_setcursor (MSWIN_CURSOR_BUSY);
+ else
+ mswin_setcursor (MSWIN_CURSOR_ARROW);
+#endif
+}
diff --git a/alpine/titlebar.h b/alpine/titlebar.h
new file mode 100644
index 00000000..65c094b6
--- /dev/null
+++ b/alpine/titlebar.h
@@ -0,0 +1,55 @@
+/*
+ * $Id: titlebar.h 769 2007-10-24 00:15:40Z hubert@u.washington.edu $
+ *
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#ifndef PINE_TITLEBAR_INCLUDED
+#define PINE_TITLEBAR_INCLUDED
+
+
+#include "../pith/context.h"
+#include "../pith/msgno.h"
+#include "../pith/osdep/color.h"
+
+
+typedef struct titlebarcontainer {
+ char titlebar_line[6*MAX_SCREEN_COLS+1];
+ COLOR_PAIR color;
+} TITLE_S;
+
+
+typedef enum {TitleBarNone = 0, FolderName, MessageNumber, MsgTextPercent,
+ TextPercent, FileTextPercent, ThrdIndex,
+ ThrdMsgNum, ThrdMsgPercent} TitleBarType;
+
+
+/* exported protoypes */
+void end_titlebar(void);
+void push_titlebar_state(void);
+void pop_titlebar_state(void);
+void mark_titlebar_dirty(void);
+char *set_titlebar(char *, MAILSTREAM *, CONTEXT_S *, char *, MSGNO_S *, int,
+ TitleBarType, long, long, COLOR_PAIR *);
+void redraw_titlebar(void);
+TITLE_S *format_titlebar(void);
+COLOR_PAIR *current_titlebar_color(void);
+void update_titlebar_message(void);
+void update_titlebar_percent(long);
+void update_titlebar_lpercent(long);
+void titlebar_stream_closing(MAILSTREAM *);
+int update_titlebar_status(void);
+void check_cue_display(char *);
+
+
+#endif /* PINE_TITLEBAR_INCLUDED */