From 094ca96844842928810f14844413109fc6cdd890 Mon Sep 17 00:00:00 2001 From: Eduardo Chappa Date: Sun, 3 Feb 2013 00:59:38 -0700 Subject: Initial Alpine Version --- pico/Makefile.am | 37 + pico/Makefile.in | 777 +++ pico/attach.c | 1515 ++++++ pico/basic.c | 955 ++++ pico/bind.c | 406 ++ pico/blddate.c | 53 + pico/browse.c | 2915 ++++++++++ pico/buffer.c | 354 ++ pico/composer.c | 4823 +++++++++++++++++ pico/display.c | 3306 ++++++++++++ pico/ebind.h | 167 + pico/edef.h | 166 + pico/efunc.h | 258 + pico/estruct.h | 366 ++ pico/file.c | 1090 ++++ pico/fileio.c | 157 + pico/headers.h | 68 + pico/keydefs.h | 164 + pico/line.c | 778 +++ pico/main.c | 871 +++ pico/makefile.wnt | 71 + pico/mode.h | 50 + pico/msmem.c | 1301 +++++ pico/mswin.def | 16 + pico/mswinver.c | 66 + pico/osdep/Makefile.am | 20 + pico/osdep/Makefile.in | 549 ++ pico/osdep/altedit.c | 712 +++ pico/osdep/altedit.h | 29 + pico/osdep/chkpoint.c | 98 + pico/osdep/chkpoint.h | 24 + pico/osdep/color.c | 1797 +++++++ pico/osdep/color.h | 38 + pico/osdep/filesys.c | 1027 ++++ pico/osdep/filesys.h | 43 + pico/osdep/fsync.c | 31 + pico/osdep/fsync.h | 30 + pico/osdep/getkey.c | 565 ++ pico/osdep/getkey.h | 36 + pico/osdep/makefile.wnt | 69 + pico/osdep/mouse.c | 446 ++ pico/osdep/mouse.h | 33 + pico/osdep/msdlg.c | 1050 ++++ pico/osdep/msmenu.h | 97 + pico/osdep/mswin.bmp | Bin 0 -> 2166 bytes pico/osdep/mswin.c | 12448 +++++++++++++++++++++++++++++++++++++++++++ pico/osdep/mswin.h | 404 ++ pico/osdep/mswin.ico | Bin 0 -> 3262 bytes pico/osdep/mswin.rc | 236 + pico/osdep/mswin_aspell.c | 427 ++ pico/osdep/mswin_aspell.h | 30 + pico/osdep/mswin_spell.DLG | 18 + pico/osdep/mswin_spell.c | 547 ++ pico/osdep/mswin_spell.h | 9 + pico/osdep/mswin_tw.c | 765 +++ pico/osdep/mswin_tw.h | 58 + pico/osdep/mswinhnd.cur | Bin 0 -> 326 bytes pico/osdep/newmail.c | 66 + pico/osdep/newmail.h | 25 + pico/osdep/os-win.h | 183 + pico/osdep/os-wnt.h | 181 + pico/osdep/pico.ico | Bin 0 -> 2238 bytes pico/osdep/popen.c | 68 + pico/osdep/popen.h | 26 + pico/osdep/raw.c | 449 ++ pico/osdep/raw.h | 38 + pico/osdep/read.c | 223 + pico/osdep/read.h | 35 + pico/osdep/resource.h | 189 + pico/osdep/shell.c | 171 + pico/osdep/shell.h | 27 + pico/osdep/signals.c | 181 + pico/osdep/signals.h | 62 + pico/osdep/spell.c | 323 ++ pico/osdep/spell.h | 25 + pico/osdep/terminal.c | 1762 ++++++ pico/osdep/terminal.h | 50 + pico/osdep/truncate.c | 20 + pico/osdep/truncate.h | 21 + pico/osdep/tty.c | 372 ++ pico/osdep/tty.h | 36 + pico/pico-win.lnk | 6 + pico/pico.c | 1939 +++++++ pico/pico.h | 452 ++ pico/picolib.def | 94 + pico/pilot.c | 472 ++ pico/random.c | 427 ++ pico/region.c | 364 ++ pico/search.c | 1037 ++++ pico/utf8stub.c | 86 + pico/utf8stub.h | 29 + pico/window.c | 47 + pico/word.c | 1179 ++++ 93 files changed, 53031 insertions(+) create mode 100644 pico/Makefile.am create mode 100644 pico/Makefile.in create mode 100644 pico/attach.c create mode 100644 pico/basic.c create mode 100644 pico/bind.c create mode 100644 pico/blddate.c create mode 100644 pico/browse.c create mode 100644 pico/buffer.c create mode 100644 pico/composer.c create mode 100644 pico/display.c create mode 100644 pico/ebind.h create mode 100644 pico/edef.h create mode 100644 pico/efunc.h create mode 100644 pico/estruct.h create mode 100644 pico/file.c create mode 100644 pico/fileio.c create mode 100644 pico/headers.h create mode 100644 pico/keydefs.h create mode 100644 pico/line.c create mode 100644 pico/main.c create mode 100644 pico/makefile.wnt create mode 100644 pico/mode.h create mode 100644 pico/msmem.c create mode 100644 pico/mswin.def create mode 100644 pico/mswinver.c create mode 100644 pico/osdep/Makefile.am create mode 100644 pico/osdep/Makefile.in create mode 100644 pico/osdep/altedit.c create mode 100644 pico/osdep/altedit.h create mode 100644 pico/osdep/chkpoint.c create mode 100644 pico/osdep/chkpoint.h create mode 100644 pico/osdep/color.c create mode 100644 pico/osdep/color.h create mode 100644 pico/osdep/filesys.c create mode 100644 pico/osdep/filesys.h create mode 100644 pico/osdep/fsync.c create mode 100644 pico/osdep/fsync.h create mode 100644 pico/osdep/getkey.c create mode 100644 pico/osdep/getkey.h create mode 100644 pico/osdep/makefile.wnt create mode 100644 pico/osdep/mouse.c create mode 100644 pico/osdep/mouse.h create mode 100644 pico/osdep/msdlg.c create mode 100644 pico/osdep/msmenu.h create mode 100644 pico/osdep/mswin.bmp create mode 100644 pico/osdep/mswin.c create mode 100644 pico/osdep/mswin.h create mode 100644 pico/osdep/mswin.ico create mode 100644 pico/osdep/mswin.rc create mode 100644 pico/osdep/mswin_aspell.c create mode 100644 pico/osdep/mswin_aspell.h create mode 100644 pico/osdep/mswin_spell.DLG create mode 100644 pico/osdep/mswin_spell.c create mode 100644 pico/osdep/mswin_spell.h create mode 100644 pico/osdep/mswin_tw.c create mode 100644 pico/osdep/mswin_tw.h create mode 100644 pico/osdep/mswinhnd.cur create mode 100644 pico/osdep/newmail.c create mode 100644 pico/osdep/newmail.h create mode 100644 pico/osdep/os-win.h create mode 100644 pico/osdep/os-wnt.h create mode 100644 pico/osdep/pico.ico create mode 100644 pico/osdep/popen.c create mode 100644 pico/osdep/popen.h create mode 100644 pico/osdep/raw.c create mode 100644 pico/osdep/raw.h create mode 100644 pico/osdep/read.c create mode 100644 pico/osdep/read.h create mode 100644 pico/osdep/resource.h create mode 100644 pico/osdep/shell.c create mode 100644 pico/osdep/shell.h create mode 100644 pico/osdep/signals.c create mode 100644 pico/osdep/signals.h create mode 100644 pico/osdep/spell.c create mode 100644 pico/osdep/spell.h create mode 100644 pico/osdep/terminal.c create mode 100644 pico/osdep/terminal.h create mode 100644 pico/osdep/truncate.c create mode 100644 pico/osdep/truncate.h create mode 100644 pico/osdep/tty.c create mode 100644 pico/osdep/tty.h create mode 100644 pico/pico-win.lnk create mode 100644 pico/pico.c create mode 100644 pico/pico.h create mode 100644 pico/picolib.def create mode 100644 pico/pilot.c create mode 100644 pico/random.c create mode 100644 pico/region.c create mode 100644 pico/search.c create mode 100644 pico/utf8stub.c create mode 100644 pico/utf8stub.h create mode 100644 pico/window.c create mode 100644 pico/word.c (limited to 'pico') diff --git a/pico/Makefile.am b/pico/Makefile.am new file mode 100644 index 00000000..444eea3b --- /dev/null +++ b/pico/Makefile.am @@ -0,0 +1,37 @@ +## 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 +# +# ======================================================================== + +SUBDIRS = osdep + +bin_PROGRAMS = pico pilot + +pico_SOURCES = main.c utf8stub.c + +pilot_SOURCES = pilot.c utf8stub.c + +pico_LDADD = $(LDADD) $(INTLLIBS) + +pilot_LDADD = $(LDADD) $(INTLLIBS) + +LDADD = ../c-client/utf8.o libpico.a osdep/libpicoosd.a \ + ../pith/osdep/libpithosd.a ../pith/charconv/libpithcc.a + +noinst_LIBRARIES = libpico.a + +libpico_a_SOURCES = attach.c basic.c bind.c browse.c buffer.c composer.c display.c file.c \ + fileio.c line.c pico.c random.c region.c search.c window.c word.c \ + ebind.h edef.h efunc.h estruct.h headers.h keydefs.h mode.h pico.h + +AM_CPPFLAGS = -I@top_srcdir@/include + diff --git a/pico/Makefile.in b/pico/Makefile.in new file mode 100644 index 00000000..a27487ee --- /dev/null +++ b/pico/Makefile.in @@ -0,0 +1,777 @@ +# 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@ +bin_PROGRAMS = pico$(EXEEXT) pilot$(EXEEXT) +subdir = pico +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 +libpico_a_AR = $(AR) $(ARFLAGS) +libpico_a_LIBADD = +am_libpico_a_OBJECTS = attach.$(OBJEXT) basic.$(OBJEXT) bind.$(OBJEXT) \ + browse.$(OBJEXT) buffer.$(OBJEXT) composer.$(OBJEXT) \ + display.$(OBJEXT) file.$(OBJEXT) fileio.$(OBJEXT) \ + line.$(OBJEXT) pico.$(OBJEXT) random.$(OBJEXT) \ + region.$(OBJEXT) search.$(OBJEXT) window.$(OBJEXT) \ + word.$(OBJEXT) +libpico_a_OBJECTS = $(am_libpico_a_OBJECTS) +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_pico_OBJECTS = main.$(OBJEXT) utf8stub.$(OBJEXT) +pico_OBJECTS = $(am_pico_OBJECTS) +am__DEPENDENCIES_1 = +pico_DEPENDENCIES = $(LDADD) $(am__DEPENDENCIES_1) +am_pilot_OBJECTS = pilot.$(OBJEXT) utf8stub.$(OBJEXT) +pilot_OBJECTS = $(am_pilot_OBJECTS) +pilot_DEPENDENCIES = $(LDADD) $(am__DEPENDENCIES_1) +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 = $(libpico_a_SOURCES) $(pico_SOURCES) $(pilot_SOURCES) +DIST_SOURCES = $(libpico_a_SOURCES) $(pico_SOURCES) $(pilot_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 = @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@ +SUBDIRS = osdep +pico_SOURCES = main.c utf8stub.c +pilot_SOURCES = pilot.c utf8stub.c +pico_LDADD = $(LDADD) $(INTLLIBS) +pilot_LDADD = $(LDADD) $(INTLLIBS) +LDADD = ../c-client/utf8.o libpico.a osdep/libpicoosd.a \ + ../pith/osdep/libpithosd.a ../pith/charconv/libpithcc.a + +noinst_LIBRARIES = libpico.a +libpico_a_SOURCES = attach.c basic.c bind.c browse.c buffer.c composer.c display.c file.c \ + fileio.c line.c pico.c random.c region.c search.c window.c word.c \ + ebind.h edef.h efunc.h estruct.h headers.h keydefs.h mode.h pico.h + +AM_CPPFLAGS = -I@top_srcdir@/include +all: 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 pico/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign pico/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) +libpico.a: $(libpico_a_OBJECTS) $(libpico_a_DEPENDENCIES) + -rm -f libpico.a + $(libpico_a_AR) libpico.a $(libpico_a_OBJECTS) $(libpico_a_LIBADD) + $(RANLIB) libpico.a +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 +pico$(EXEEXT): $(pico_OBJECTS) $(pico_DEPENDENCIES) + @rm -f pico$(EXEEXT) + $(LINK) $(pico_OBJECTS) $(pico_LDADD) $(LIBS) +pilot$(EXEEXT): $(pilot_OBJECTS) $(pilot_DEPENDENCIES) + @rm -f pilot$(EXEEXT) + $(LINK) $(pilot_OBJECTS) $(pilot_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/attach.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/basic.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bind.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/browse.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/composer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/display.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileio.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/line.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pico.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pilot.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/random.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/region.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8stub.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/word.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: check-recursive +all-am: Makefile $(LIBRARIES) $(PROGRAMS) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: 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: + +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-recursive + +clean-am: clean-binPROGRAMS clean-generic clean-libtool \ + clean-noinstLIBRARIES 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) ctags-recursive \ + 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 clean-noinstLIBRARIES 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 + + +# 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/pico/attach.c b/pico/attach.c new file mode 100644 index 00000000..05605f96 --- /dev/null +++ b/pico/attach.c @@ -0,0 +1,1515 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: attach.c 1082 2008-06-12 18:39:50Z 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 + * + * ======================================================================== + * + * Program: Routines to support attachments in the Pine composer + */ + +#include "headers.h" +#include "../pith/charconv/filesys.h" +#include "../pith/string.h" + +#include + +#ifdef ATTACHMENTS + + +int ParseAttach(struct hdr_line **, int *, char *, + size_t, char *, size_t, char *, size_t, int *); +PATMT *NewAttach(char *, long, char *); +void ZotAttach(struct pico_atmt *); +void sinserts(UCS *, int, UCS *, int); +int AttachUpload(char *, size_t, char *, size_t); +int AttachCancel(char *); + + +#define HIBIT_WARN "Only ASCII characters allowed in attachment comments" + + +/* + * AskAttach - ask for attachment fields and build resulting structure + * return pointer to that struct if OK, NULL otherwise + */ +int +AskAttach(char *cmnt, size_t cmntlen, LMLIST **lm) +{ + int i, status, fbrv, upload = 0; + int fb_flags = FB_READ | FB_ATTACH; + off_t attsz = 0; + size_t len; + char bfn[NLINE]; + char fn[NLINE], sz[32]; + LMLIST *new; + EML eml; + + i = 2; /* 2 is prompt for file, 1 is prompt for comment */ + fn[0] = '\0'; + sz[0] = '\0'; + cmnt[0] = '\0'; + + while(i){ + if(i == 2){ + EXTRAKEYS menu_attach[4]; + int n; + + menu_attach[n = 0].name = "^T"; + menu_attach[n].label = N_("To Files"); + menu_attach[n].key = (CTRL|'T'); + + if(gmode & MDCMPLT){ + menu_attach[++n].name = "TAB"; + menu_attach[n].label = N_("Complete"); + menu_attach[n].key = (CTRL|'I'); + } + +#if !defined(DOS) && !defined(MAC) + if(Pmaster && Pmaster->upload){ + /* + * The Plan: ^R prompts for uploaded file's name which + * is passed to the defined upload command when the user + * hits Return to confirm the name. + * NOTE: this is different than upload into message + * text in which case the uploaded name isn't useful so + * a temp file is ok (be sure to fix the file mode). + */ + menu_attach[++n].name = "^Y"; + menu_attach[n].key = (CTRL|'Y'); + /* TRANSLATORS: Read File is a prompt for the name of + a file to be read into the composer. */ + menu_attach[n].label = upload ? N_("Read File") : N_("RcvUpload"); + } +#endif + + menu_attach[++n].name = NULL; + KS_OSDATASET(&menu_attach[0], KS_NONE); + status = mlreply_utf8(upload ? _("Name to give uploaded attachment: ") + /* TRANSLATORS: User is being asked for the name + of the file they want to attach to a message. */ + : _("File to attach: "), + fn, sizeof(fn), QNORML, menu_attach); + } + else + /* TRANSLATORS: This is a prompt for a comment about the file + they have attached. */ + status = mlreply_utf8(_("Attachment comment: "), cmnt, cmntlen, QNODQT, NULL); + + switch(status){ + case HELPCH: + if(Pmaster){ + VARS_TO_SAVE *saved_state; + + saved_state = save_pico_state(); + (*Pmaster->helper)(Pmaster->attach_help, _("Attach Help"), 1); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + + pico_refresh(FALSE, 1); + update(); + continue; + } + else{ + eml.s = (i == 2) ? "file" : "comment"; + emlwrite("No Attachment %s help yet!", &eml); + sleep(3); + } + + break; + + case (CTRL|'I') : + if(i == 2){ + char *fname, *p; + int dirlen; + + bfn[0] = '\0'; + if(*fn && (p = strrchr(fn, C_FILESEP))){ + fname = p + 1; + dirlen = p - fn; + if(p == fn){ + strncpy(bfn, S_FILESEP, sizeof(bfn)); + bfn[sizeof(bfn)-1] = '\0'; + } +#ifdef DOS + else if(fn[0] == C_FILESEP + || (isalpha((unsigned char)fn[0]) + && fn[1] == ':')){ + if(fn[1] == ':' && p == fn+2) + dirlen = fname - fn; + + if(dirlen < sizeof(bfn)){ + strncpy(bfn, fn, dirlen); + bfn[dirlen] = '\0'; + } + } +#else + else if (fn[0] == C_FILESEP || fn[0] == '~'){ + if(dirlen < sizeof(bfn)){ + strncpy(bfn, fn, dirlen); + bfn[dirlen] = '\0'; + } + } +#endif + else + snprintf(bfn, sizeof(bfn), "%s%c%.*s", + (gmode & MDCURDIR) + ? "." + : ((gmode & MDTREE) || opertree[0]) + ? opertree : gethomedir(NULL), + C_FILESEP, p - fn, fn); + } + else{ + fname = fn; + strncpy(bfn, (gmode & MDCURDIR) + ? "." + : ((gmode & MDTREE) || opertree[0]) + ? opertree : gethomedir(NULL), sizeof(bfn)); + bfn[sizeof(bfn)-1] = '\0'; + } + + if(!pico_fncomplete(bfn, fname, sizeof(fn)-(fname-fn))) + (*term.t_beep)(); + } + else + (*term.t_beep)(); + + break; + + case (CTRL|'T'): + if(i != 2){ + (*term.t_beep)(); + break; + } + + *bfn = '\0'; + if(*fn == '\0' || !isdir(fn, NULL, NULL)){ + strncpy(fn, (gmode & MDCURDIR) + ? (browse_dir[0] ? browse_dir : ".") + : ((gmode & MDTREE) || opertree[0]) + ? opertree + : (browse_dir[0] ? browse_dir + : gethomedir(NULL)), sizeof(fn)); + fn[sizeof(fn)-1] = '\0'; + } + + if((fbrv = FileBrowse(fn, sizeof(fn), bfn, sizeof(bfn), sz, sizeof(sz), + upload ? fb_flags + : fb_flags|FB_LMODEPOS, + upload ? NULL + : lm)) == 1){ + if (upload && (strlen(fn)+strlen(S_FILESEP)+strlen(bfn)) < sizeof(fn)){ + size_t len1, len2; + + len1 = strlen(bfn); + len2 = strlen(fn); + if((new=(LMLIST *)malloc(sizeof(*new))) == NULL + || (new->fname=malloc((len1+1) * sizeof(char))) == NULL + || (new->dir=malloc((len2+1) * sizeof(char))) == NULL){ + emlwrite("\007Can't malloc space for filename", NULL); + return(-1); + } + + strncpy(new->fname, bfn, len1); + new->fname[len1] = '\0'; + strncpy(new->dir, fn, len2); + new->dir[len2] = '\0'; + strncpy(new->size, sz, sizeof(new->size)-1); + new->size[sizeof(new->size)-1] = '\0'; + new->next = NULL; + *lm = new; + + strncat(fn, S_FILESEP, sizeof(fn)-strlen(fn)-1); + fn[sizeof(fn)-1] = '\0'; + strncat(fn, bfn, sizeof(fn)-strlen(fn)-1); + fn[sizeof(fn)-1] = '\0'; + if(!AttachUpload(fn, sizeof(fn), sz, sizeof(sz))){ + i = 2; /* keep prompting for file */ + sleep(3); /* problem, show error! */ + } + else{ + i--; /* go prompt for comment */ + } + } + else if(!upload){ + if(lm && *lm && !(*lm)->next) /* get comment */ + i--; + else{ /* no comments if multiple files */ + update(); + return(1); + } + } + else{ /* trouble */ + *fn = '\0'; + AttachCancel(fn); + pico_refresh(FALSE,1); + update(); + emlwrite("\007File name too BIG, cannot select!", NULL); + sleep(3); + } + } + else if (!fbrv) + *fn = '\0'; + else{ + *fn = '\0'; + AttachCancel(fn); + pico_refresh(FALSE, 1); + update(); + emlwrite("\007File name too big, cannot select!", NULL); + sleep(3); + } + + /* fall thru to clean up the screen */ + + case (CTRL|'L'): + pico_refresh(FALSE, 1); + update(); + continue; + +#if !defined(DOS) && !defined(MAC) + case (CTRL|'Y'): /* upload? */ + if(i == 2) + upload ^= 1; /* flip mode */ + else + (*term.t_beep)(); + + break; +#endif + + case ABORT: + return(AttachCancel((upload && i == 1) ? fn : NULL)); + + case TRUE: /* some comment */ + case FALSE: /* No comment */ + if(i-- == 2){ + if(upload){ + fixpath(fn, sizeof(fn)); /* names relative to ~ */ + status = AttachUpload(fn, sizeof(fn), sz, sizeof(sz)); + pico_refresh(FALSE, 1); + update(); + if(!status){ + i = 2; /* keep prompting for file */ + sleep(3); /* problem, show error! */ + } + } + else { + if(*fn == '\"' && fn[strlen(fn)-1] == '\"'){ + int j; + + for(j = 0; (fn[j] = fn[j+1]); j++) + ; + + fn[j-1] = '\0'; + } + + if(fn[0]){ + if((gmode & MDTREE) + && !compresspath(opertree, fn, sizeof(fn))){ + eml.s = (gmode&MDSCUR) ? _("home directory") : opertree; + emlwrite( + /* TRANSLATORS: the %s is replaced with the name of a directory */ + _("Restricted mode allows attachments from %s only: too many ..'s"), + &eml); + return(0); + } + else{ + fixpath(fn, sizeof(fn)); /* names relative to ~ */ + if((gmode&MDTREE) && !in_oper_tree(fn)){ + eml.s = (gmode&MDSCUR) ? _("home directory") : opertree; + emlwrite( + _("\007Restricted mode allows attachments from %s only"), &eml); + return(0); + } + } + + if((status = fexist(fn, "r", &attsz)) != FIOSUC){ + fioperr(status, fn); /* file DOESN'T exist! */ + return(0); + } + + len = strlen(fn); + if((new=(LMLIST *)malloc(sizeof(*new))) == NULL + || (new->fname=malloc((len+1)*sizeof(char))) == NULL){ + emlwrite("\007Can't malloc space for filename", NULL); + return(-1); + } + + new->dir = NULL; + strncpy(new->fname, fn, len); + new->fname[len] = '\0'; + strncpy(new->size, prettysz(attsz), sizeof(new->size)); + new->size[sizeof(new->size)-1] = '\0'; + new->next = NULL; + *lm = new; + } + else + return(AttachCancel((upload && i == 1) ? fn : NULL)); + } + } + else{ + mlerase(); + return(1); /* mission accomplished! */ + } + + break; + default: + break; + } + } + + return(0); +} + + +/* + * AttachUpload - Use call back to run the external upload command. + */ +int +AttachUpload(char *fn, size_t fnlen, char *sz, size_t szlen) +{ + long l; + + if(gmode&MDSCUR){ + emlwrite("\007Restricted mode disallows uploaded command", NULL); + return(0); + } + + if(Pmaster && Pmaster->upload && (*Pmaster->upload)(fn, fnlen, &l)){ + strncpy(sz, prettysz((off_t)l), szlen); + sz[szlen-1] = '\0'; + return(1); + } + + return(0); +} + + +/* + * AttachCancel - + */ +int +AttachCancel(char *fn) +{ + emlwrite(_("Attach cancelled"), NULL); + if(fn && fn[0]) + our_unlink(fn); /* blast uploaded file */ + + return(0); +} + + +extern struct headerentry *headents; + +/* + * SyncAttach - given a pointer to a linked list of attachment structures, + * return with that structure sync'd with what's displayed. + * delete any attachments in list of structs that's not on + * the display, and add any that aren't in list but on display. + */ +int +SyncAttach(void) +{ + int offset = 0, /* the offset to begin */ + rv = 0, + ki = 0, /* number of known attmnts */ + bi = 0, /* build array index */ + nbld = 0, /* size of build array */ + na, /* old number of attachmnt */ + status, i, n = 0; + size_t j; + char file[NLINE], /* buffers to hold it all */ + size[32], + comment[1024]; + struct hdr_line *lp; /* current line in header */ + struct headerentry *entry; + PATMT *tp, **knwn = NULL, **bld; + EML eml; + + if(Pmaster == NULL) + return(-1); + + for(entry = headents; entry->name != NULL; entry++) { + if(entry->is_attach) + break; + } + + for(tp = Pmaster->attachments; tp; tp = tp->next) + ki++; /* Count known attachments */ + + if(ki){ + if((knwn = (PATMT **)malloc((ki+1) * (sizeof(PATMT *)))) == NULL){ + eml.s = comatose(ki + 1); + emlwrite("\007Can't allocate space for %s known attachment array entries", + &eml); + rv = -1; + goto exit_early; + } + for(i=0, tp = Pmaster->attachments; i < ki; i++, tp = tp->next){ + knwn[i] = tp; /* fill table of */ + /* known attachments */ + } + } + + + /* + * As a quick hack to avoid too many reallocs, check to see if + * there are more header lines than known attachments. + */ + for(lp = entry->hd_text; lp ; lp = lp->next) + nbld++; /* count header lines */ + + nbld = nbld > ki ? nbld : ki + 1; + + if((bld = (PATMT **)malloc(nbld * (sizeof(PATMT *)))) == NULL){ + eml.s = comatose(nbld); + emlwrite("\007Can't allocate space for %s build array entries", &eml); + rv = -1; + goto exit_early; + } + + lp = entry->hd_text; + while(lp != NULL){ + char fn[NLINE]; + na = ++n; + + if(bi == nbld){ /* need to grow build array? */ + if((bld = (PATMT **)realloc(bld, ++nbld * sizeof(PATMT *))) == NULL){ + eml.s = comatose(nbld); + emlwrite("\007Can't resize build array to %s entries ", &eml); + rv = -1; + goto exit_early; + } + } + + if((status = ParseAttach(&lp, &offset, file, sizeof(file), size, sizeof(size), + comment, sizeof(comment), &na)) != 0) + rv = (rv < 0) ? rv : status ; /* remember worst case */ + + if(*file == '\0'){ + if(n != na && na > 0 && na <= ki && (knwn[na-1]->flags & A_FLIT)){ + bld[bi++] = knwn[na-1]; + knwn[na-1] = NULL; + } + continue; + } + + if((gmode&MDTREE) + && file[0] != '[' + && (!in_oper_tree(file) + || !compresspath(file, fn, sizeof(fn)))) + /* no attachments outside ~ in secure mode! */ + continue; + + tp = NULL; + for(i = 0; i < ki; i++){ /* already know about it? */ + /* + * this is kind of gruesome. what we want to do is keep track + * of literal attachment entries because they may not be + * actual files we can access or that the user can readily + * access. + */ + if(knwn[i] + && ((!(knwn[i]->flags&A_FLIT) + && !strcmp(file, knwn[i]->filename)) + || ((knwn[i]->flags&A_FLIT) && i+1 == na))){ + tp = knwn[i]; + knwn[i] = NULL; /* forget we know about it */ + + if(status == -1) /* ignore garbage! */ + break; + + if((tp->flags&A_FLIT) && strcmp(file, tp->filename)){ + rv = 1; + if((j=strlen(file)) > strlen(tp->filename)){ + if((tp->filename = (char *)realloc(tp->filename, + sizeof(char)*(j+1))) == NULL){ + emlwrite("\007Can't realloc filename space",NULL); + rv = -1; + goto exit_early; + } + } + + strncpy(tp->filename, file, j); + tp->filename[j] = '\0'; + } + else if(tp->size && strcmp(tp->size, size)){ + rv = 1; + if((j=strlen(size)) > strlen(tp->size)){ + if((tp->size=(char *)realloc(tp->size, + sizeof(char)*(j+1))) == NULL){ + emlwrite("\007Can't realloc space for size", NULL); + rv = -1; + goto exit_early; + } + } + + strncpy(tp->size, size, j); + tp->size[j] = '\0'; + } + + if(strcmp(tp->description, comment)){ /* new comment */ + rv = 1; + if((j=strlen(comment)) > strlen(tp->description)){ + if((tp->description=(char *)realloc(tp->description, + sizeof(char)*(j+1))) == NULL){ + emlwrite("\007Can't realloc description", NULL); + rv = -1; + goto exit_early; + } + } + + strncpy(tp->description, comment, j); + tp->description[j] = '\0'; + } + break; + } + } + + if(tp){ + bld[bi++] = tp; + } + else{ + if(file[0] != '['){ + if((tp = NewAttach(file, atol(size), comment)) == NULL){ + rv = -1; + goto exit_early; + } + bld[bi++] = tp; + } + else break; + } + + if(status < 0) + tp->flags |= A_ERR; /* turn ON error bit */ + else + tp->flags &= ~(A_ERR); /* turn OFF error bit */ + } + + if(bi){ + for(i=0; i < bi-1; i++) /* link together newly built list */ + bld[i]->next = bld[i+1]; + + bld[i]->next = NULL; /* tie it off */ + Pmaster->attachments = bld[0]; + } + else + Pmaster->attachments = NULL; + +exit_early: + if(knwn){ + for(i = 0; i < ki; i++){ /* kill old/unused references */ + if(knwn[i]){ + ZotAttach(knwn[i]); + free((char *) knwn[i]); + } + } + free((void *)knwn); + } + + if(bld) + free((void *)bld); + + return(rv); +} + + +/* + * ParseAttach - given a header line and an offset into it, return with + * the three given fields filled in. Size of fn and cmnt + * buffers should be passed in fnlen and cmntlen. + * Always updates header fields that have changed or are + * fixed. An error advances offset to next attachment. + * + * returns: 1 if a field changed + * 0 nothing changed + * -1 on error + */ +int +ParseAttach(struct hdr_line **lp, /* current header line */ + int *off, /* offset into that line */ + char *fn, /* return file name field */ + size_t fnlen, /* fn buffer size */ + char *sz, + size_t szlen, + char *cmnt, /* places to return fields */ + size_t cmntlen, + int *no) /* attachment number */ +{ + int j, status, bod, eod = -1, + rv = 0, /* return value */ + orig_offset, + lbln = 0, /* label'd attachment */ + hibit = 0, + quoted = 0, + add_quotes = 0, + escaped = 0; + off_t attsz; /* attachment length */ + EML eml; + UCS c, c_lookahead; + UCS tmp[1024], *p, *u, quotechar[2]; + char ctmp[1024]; + char *utf8 = NULL; + char *lblsz = NULL, /* label'd attchmnt's size */ + number[8]; + struct hdr_line *lprev = NULL; + enum { /* parse levels */ + LWS, /* leading white space */ + NUMB, /* attachment number */ + WSN, /* white space after number */ + TAG, /* attachments tag (fname) */ + WST, /* white space after tag */ + ASIZE, /* attachments size */ + SWS, /* white space after size */ + CMMNT, /* attachment comment */ + TG} level; /* trailing garbage */ + + *fn = *sz = *cmnt = '\0'; /* initialize return strings */ + p = tmp; + orig_offset = bod = *off; + quotechar[0] = '\"'; + quotechar[1] = '\0'; + + level = LWS; /* start at beginning */ + while(*lp != NULL){ + + if((c=(*lp)->text[*off]) == '\0'){ /* end of display line */ + if(level == LWS && bod != *off){ + (*lp)->text[bod] = '\0'; + rv = 1; + } + lprev = *lp; + if((*lp = (*lp)->next) != NULL) + c = (*lp)->text[*off = bod = 0]; /* reset offset */ + } + + if(c != '\0'){ + c_lookahead = (*lp)->text[*off + 1]; + if(c_lookahead == '\0'){ /* end of display line */ + if((*lp)->next != NULL) + c_lookahead = (*lp)->next->text[0]; + } + } + + switch(level){ + case LWS: /* skip leading white space */ + if(c <= 0xff && (isspace((unsigned char)c) || c == ',')){ + c = ' '; + break; + } + + if(c == '\0'){ + if(bod > 0 && *off >= bod && lprev){ + lprev->text[bod - 1] = '\0'; + rv = 1; + } + } + else if(*off > bod && *lp){ /* wipe out whitespace */ + memcpy(&(*lp)->text[bod], &(*lp)->text[*off], + ucs4_strlen(&(*lp)->text[*off]) + 1); + *off = bod; /* reset pointer */ + rv = 1; + } + + if(c == '\0') + break; + + if(c > 0xff || !isdigit((unsigned char)c)){ /* add a number */ + snprintf(number, sizeof(number), "%d. ", *no); + *no = 0; /* no previous number! */ + u = utf8_to_ucs4_cpystr(number); + if(u){ + sinserts((*lp == NULL) ? &lprev->text[*off] + : &(*lp)->text[*off], + 0, u, j=ucs4_strlen(u)); + + fs_give((void **) &u); + } + + *off += j - 1; + rv = 1; + level = TAG; /* interpret the name */ + break; + } + level = NUMB; + case NUMB: /* attachment number */ + if(c == '\0' || c == ','){ /* got to end, no number yet */ + *p = '\0'; + snprintf(number, sizeof(number), "%d. ", *no); + *no = 0; /* no previous number! */ + if(c == '\0') + *lp = lprev; /* go back and look at prev */ + + c = (*lp)->text[*off = orig_offset]; /* reset offset */ + u = utf8_to_ucs4_cpystr(number); + if(u){ + sinserts((*lp == NULL) ? &lprev->text[*off] + : &(*lp)->text[*off], + 0, u, j=ucs4_strlen(u)); + + fs_give((void **) &u); + } + *off += j - 1; + rv = 1; + p = tmp; + level = WSN; /* what's next... */ + break; + } + else if(c == '.' && c_lookahead <= 0xff && isspace((unsigned char)c_lookahead)){ + /* finished grabbing number */ + /* if not space is not number */ + /* + * replace number if it's not right + */ + *p = '\0'; + snprintf(number, sizeof(number), "%d", *no); /* record the current... */ + utf8 = ucs4_to_utf8_cpystr(tmp); + *no = atoi(utf8); /* and the old place in list */ + if(strcmp(number, utf8)){ + if(p-tmp > *off){ /* where to begin replacemnt */ + UCS uu[1]; + + uu[0] = '\0'; + j = (p-tmp) - *off; + sinserts((*lp)->text, *off, uu, 0); + u = utf8_to_ucs4_cpystr(number); + if(u){ + sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j, + u, ucs4_strlen(u)); + + fs_give((void **) &u); + } + + *off = 0; + } + else{ + j = (*off) - (p-tmp); + u = utf8_to_ucs4_cpystr(number); + if(u){ + sinserts((*lp == NULL) ? &lprev->text[j] + : &(*lp)->text[j], + p-tmp, u, ucs4_strlen(u)); + + fs_give((void **) &u); + } + + *off += strlen(number) - (p-tmp); + } + rv = 1; + } + + if(utf8) + fs_give((void **) &utf8); + + p = tmp; + level = WSN; /* what's next... */ + } + else if(c < '0' || c > '9'){ /* Must be part of tag */ + snprintf(number, sizeof(number), "%d. ", *no); + u = utf8_to_ucs4_cpystr(number); + if(u){ + sinserts((*lp == NULL) ? &lprev->text[*off - (p - tmp)] + : &(*lp)->text[*off - (p - tmp)], + 0, u, j=ucs4_strlen(u)); + + fs_give((void **) &u); + } + + *off += j; + level = TAG; /* interpret the name */ + goto process_tag; /* in case already past end of tag */ + } + else + *p++ = c; + + break; + + case WSN: /* blast whitespace */ + if(c <= 0xff && (isspace((unsigned char)c) || c == '\0')){ + break; + } + else if(c == '['){ /* labeled attachment */ + lbln++; + } + else if(c == ',' || c == ' '){ + /* TRANSLATORS: Attchmnt is an abbreviation for Attachment and + the %s is replaced with the character that is not + allowed in the name. */ + eml.s = (c == ',') ? "," : "space"; + emlwrite(_("\007Attchmnt: '%s' not allowed in file name"), &eml); + rv = -1; + level = TG; /* eat rest of garbage */ + break; + } + level = TAG; + + case TAG: /* get and check filename */ + /* or labeled attachment */ +process_tag: /* enclosed in [] */ + if(c == '\0' + || (lbln && c == ']') + || (quoted && p != tmp && c == '\"') + || (!(lbln || quoted) + && (c <= 0xff && ((isspace((unsigned char) c) && c_lookahead == (UCS) '(') || strchr(",(\"", c))))){ + if(p == tmp){ + if(c == '\"') + quoted++; + } + else{ + *p = '\0'; /* got something */ + + utf8 = ucs4_to_utf8_cpystr(tmp); + if(utf8){ + if(strlen(utf8) > fnlen) + emlwrite("File name too big!",NULL); + + strncpy(fn, utf8, fnlen); /* store file name */ + fn[fnlen-1] = '\0'; + fs_give((void **) &utf8); + } + + if(!lbln){ /* normal file attachment */ + if((gmode & MDTREE) + && !compresspath(opertree, fn, fnlen)){ + eml.s = (gmode&MDSCUR) ? _("home directory") : opertree; + emlwrite( + _("Attachments allowed only from %s: too many ..'s"), + &eml); + rv = -1; + level = TG; + break; + } + else{ + fixpath(fn, fnlen); + if((status=fexist(fn, "r", &attsz)) != FIOSUC){ + fioperr(status, fn); + rv = -1; + level = TG; /* munch rest of garbage */ + break; + } + + if((gmode & MDTREE) && !in_oper_tree(fn)){ + eml.s = (gmode&MDSCUR) ? _("home directory") : opertree; + emlwrite(_("\007Attachments allowed only from %s"), &eml); + rv = -1; + level = TG; + break; + } + } + + utf8 = ucs4_to_utf8_cpystr(tmp); + + if(utf8 && strcmp(fn, utf8)){ /* fn changed: display it */ + if(*off >= p - tmp){ /* room for it? */ + u = utf8_to_ucs4_cpystr(fn); + if(u){ + /* + * This whole parsing of the attachment line + * thing is ad hoc and susceptible to problems, + * and this particular part is no exception. + * Quote the filename if it contains spaces. + */ + if(add_quotes){ + sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)] + : &(*lp)->text[*off - (p-tmp)], + 0, quotechar, 1); + (*off)++; + } + + sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)] + : &(*lp)->text[*off - (p-tmp)], + p-tmp, u, j=ucs4_strlen(u)); + + *off += j - (p - tmp); /* advance offset */ + + if(add_quotes){ + sinserts((*lp == NULL) ? &lprev->text[*off] + : &(*lp)->text[*off], + 0, quotechar, 1); + (*off)++; + add_quotes = 0; + } + + fs_give((void **) &u); + } + + rv = 1; + } + else{ + emlwrite("\007Attchmnt: Problem displaying real file path", NULL); + } + } + + if(utf8) + fs_give((void **) &utf8); + } + else{ /* labelled attachment! */ + /* + * should explain about labelled attachments: + * these are attachments that came into the composer + * with meaningless file names (up to caller of + * composer to decide), for example, attachments + * being forwarded from another message. here, we + * just make sure the size stays what was passed + * to us. The user is SOL if they change the label + * since, as it is now, after changed, it will + * just get dropped from the list of what gets + * passed back to the caller. + */ + PATMT *tp; + + if(c != ']'){ /* legit label? */ + eml.s = fn; + emlwrite(_("\007Attchmnt: Expected ']' after \"%s\""), + &eml); + rv = -1; + level = TG; + break; + } + + strncat(fn, "]", fnlen-strlen(fn)-1); + fn[fnlen-1] = '\0'; + + /* + * This is kind of cheating since otherwise + * ParseAttach doesn't know about the attachment + * struct. OK if filename's not found as it will + * get taken care of later... + */ + tp = Pmaster->attachments; /* caller check Pmaster! */ + j = 0; + while(tp != NULL){ + if(++j == *no){ + lblsz = tp->size; + break; + } + + tp = tp->next; + } + + if(tp == NULL){ + eml.s = fn; + emlwrite("\007Attchmnt: Unknown reference: %s", &eml); + lblsz = "XXX"; + } + } + + if(add_quotes){ + sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)] + : &(*lp)->text[*off - (p-tmp)], + 0, quotechar, 1); + (*off)++; + sinserts((*lp == NULL) ? &lprev->text[*off] + : &(*lp)->text[*off], + 0, quotechar, 1); + (*off)++; + add_quotes = 0; + } + + p = tmp; /* reset p in tmp */ + level = WST; + } + + if(!lbln && c == '(') /* no space 'tween file, size*/ + level = ASIZE; + else if(c == '\0' + || (!(lbln || quoted) && (c == ',' || c == '\"'))){ + strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen); + sz[szlen-1] = '\0'; + + snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\""); + u = utf8_to_ucs4_cpystr(ctmp); + if(u){ + ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0])); + tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0'; + fs_give((void **) &u); + } + + sinserts((*lp == NULL) ? &lprev->text[*off] + : &(*lp)->text[*off], + 0, tmp, j=ucs4_strlen(tmp)); + *off += j; + rv = 1; + level = (c == '\"') ? CMMNT : TG;/* cmnt or eat trash */ + } + } + else if(!(lbln || quoted) + && (c == ',' || /** c == ' ' || **/ c == '[' || c == ']')){ + eml.s = c == ',' ? "," + : c == ' ' ? "space" + : c == '[' ? "[" : "]"; + emlwrite(_("\007Attchmnt: '%s' not allowed in file name"), &eml); + rv = -1; /* bad char in file name */ + level = TG; /* gobble garbage */ + } + else if(!(lbln || quoted) && (c <= 0xff && isspace((unsigned char) c))){ + add_quotes++; + *p++ = c; /* add char to name */ + } + else + *p++ = c; /* add char to name */ + + break; + + case WST: /* skip white space */ + if(c > 0xff || !isspace((unsigned char)c)){ + /* + * whole attachment, comment or done! + */ + if(c == ',' || c == '\0' || c == '\"'){ + strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), sizeof(sz)); + sz[sizeof(sz)-1] = '\0'; + + snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\""); + u = utf8_to_ucs4_cpystr(ctmp); + if(u){ + ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0])); + tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0'; + fs_give((void **) &u); + } + + sinserts((*lp == NULL) ? &lprev->text[*off] + : &(*lp)->text[*off], + 0, tmp, j=ucs4_strlen(tmp)); + *off += j; + rv = 1; + level = (c == '\"') ? CMMNT : TG; + lbln = 0; /* reset flag */ + } + else if(c == '('){ /* get the size */ + level = ASIZE; + } + else{ + eml.s = fn; + emlwrite(_("\007Attchmnt: Expected '(' or '\"' after %s"), &eml); + rv = -1; /* bag it all */ + level = TG; + } + } + break; + + case ASIZE: /* check size */ + if(c == ')'){ /* finished grabbing size */ + *p = '\0'; + /* + * replace sizes if they don't match! + */ + utf8 = ucs4_to_utf8_cpystr(tmp); + if(utf8){ + strncpy(sz, utf8, szlen); + sz[szlen-1] = '\0'; + fs_give((void **) &utf8); + } + + if(strcmp(sz, (lblsz) ? lblsz : prettysz(attsz))){ + strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen); + sz[szlen-1] = '\0'; + if(p-tmp > *off){ /* where to begin replacemnt */ + UCS uu[1]; + + uu[0] = '\0'; + j = (p-tmp) - *off; + sinserts((*lp)->text, *off, uu, 0); + u = utf8_to_ucs4_cpystr(sz); + if(u){ + sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j, + u, ucs4_strlen(u)); + fs_give((void **) &u); + } + + *off = 0; + } + else{ + j = (*off) - (p-tmp); + u = utf8_to_ucs4_cpystr(sz); + if(u){ + sinserts((*lp == NULL) ? &lprev->text[j] + : &(*lp)->text[j], + p-tmp , u, ucs4_strlen(u)); + *off += ucs4_strlen(u) - (p-tmp); + fs_give((void **) &u); + } + } + rv = 1; + } + + p = tmp; + level = SWS; /* what's next... */ + } + else if(c == '\0' || c == ','){ + *p = '\0'; + utf8 = ucs4_to_utf8_cpystr(tmp); + eml.s = utf8; + emlwrite(_("\007Attchmnt: Size field missing ')': \"%s\""), &eml); + if(utf8) + fs_give((void **) &utf8); + + rv = -1; + level = TG; + } + else + *p++ = c; + + break; + + case SWS: /* skip white space */ + if(c > 0xff || !isspace((unsigned char)c)){ + if(c == ','){ /* no description */ + level = TG; /* munch rest of garbage */ + lbln = 0; /* reset flag */ + } + else if(c != '\"' && c != '\0'){ + emlwrite(_("\007Attchmnt: Malformed comment, quotes required"), NULL); + rv = -1; + level = TG; + } + else + level = CMMNT; + } + break; + + case CMMNT: /* slurp up comment */ + if((c == '\"' && !escaped) || c == '\0'){ + *p = '\0'; /* cap it off */ + p = tmp; /* reset p */ + utf8 = ucs4_to_utf8_cpystr(tmp); + if(utf8){ + if(strlen(utf8) > cmntlen) + emlwrite("Comment too long!",NULL); + + strncpy(cmnt,utf8,cmntlen-1); /* copy the comment */ + cmnt[cmntlen-1] = '\0'; + fs_give((void **) &utf8); + if(c == '\0'){ + emlwrite(_("\007Attchmnt: Closing quote required at end of comment"), NULL); + rv = -1; + } + } + + level = TG; /* prepare for next one */ + lbln = 0; /* reset flag */ + } + else if(c == '\\' && !escaped){ /* something escaped? */ + escaped = 1; + } + else{ + if(escaped){ + if(c != '\"') /* we only quote escapes */ + *p++ = '\\'; + + escaped = 0; + } + + if(((*p++ = c) & 0x80) && (gmode & MDHBTIGN) && !hibit++) + emlwrite(HIBIT_WARN, NULL); + } + + break; + + case TG: /* get comma or final EOL */ + if(eod < 0) + eod = *off; + if(c > 0xff || !isspace((unsigned char)c)){ + switch(c){ + case '\0': + if(eod != *off) + lprev->text[*off = eod] = '\0'; + break; + case ',': + if(eod != *off){ + memcpy(&(*lp)->text[eod], &(*lp)->text[*off], + ucs4_strlen(&(*lp)->text[*off]) + 1); + *off = eod; + rv = 1; + } + break; + default: + if(rv != -1) + emlwrite(_("\007Attchmnt: Comma must separate attachments"), NULL); + rv = -1; + } + } + break; + + default: /* something's very wrong */ + emlwrite("\007Attchmnt: Weirdness in ParseAttach", NULL); + return(-1); /* just give up */ + } + + if(c == '\0') /* we're done */ + break; + + (*off)++; + + /* + * not in comment or label name? done. + */ + if(c == ',' && (level != TAG && level != CMMNT && !lbln)) + break; /* put offset past ',' */ + } + + return(rv); +} + + +/* + * NewAttach - given a filename (assumed to accessible) and comment, creat + */ +PATMT * +NewAttach(char *f, long l, char *c) +{ + PATMT *tp; + size_t len; + + if((tp=(PATMT *)malloc(sizeof(PATMT))) == NULL){ + emlwrite("No memory to add attachment", NULL); + return(NULL); + } + else + memset(tp, 0, sizeof(PATMT)); + + /* file and size malloc */ + len = strlen(f); + if((tp->filename = (char *) malloc((len+1) * sizeof(char))) == NULL){ + emlwrite("Can't malloc name for attachment", NULL); + free((char *) tp); + return(NULL); + } + + strncpy(tp->filename, f, len); + tp->filename[len] = '\0'; + + if(l > -1){ + len = strlen(prettysz((off_t) l)); + tp->size = (char *) malloc((len+1) * sizeof(char)); + if(tp->size == NULL){ + emlwrite("Can't malloc size for attachment", NULL); + free((char *) tp->filename); + free((char *) tp); + return(NULL); + } + else{ + strncpy(tp->size, prettysz((off_t) l), len); + tp->size[len] = '\0'; + } + } + + /* description malloc */ + len = strlen(c); + if((tp->description = (char *) malloc((len+1) * sizeof(char))) == NULL){ + emlwrite("Can't malloc description for attachment", NULL); + free((char *) tp->size); + free((char *) tp->filename); + free((char *) tp); + return(NULL); + } + + strncpy(tp->description, c, len); + tp->description[len] = '\0'; + + /* callback to show user the mime type that will be used for attachment */ + if(Pmaster->mimetype && (*Pmaster->mimetype)(f) > 0){ + int rv ; + + clearcursor(); + mlerase(); + rv = (*Pmaster->showmsg)('x'); + ttresize(); + picosigs(); + if(rv) /* Did showmsg corrupt the screen? */ + PaintBody(0); /* Yes, repaint it */ + + mpresf = 1; + } + + return(tp); +} + + +/* + * AttachError - Sniff list of attachments, returning TRUE if there's + * any sign of trouble... + */ +int +AttachError(void) +{ + PATMT *ap; + + if(!Pmaster) + return(0); + + ap = Pmaster->attachments; + while(ap){ + if((ap->flags) & A_ERR) + return(1); + + ap = ap->next; + } + + return(FALSE); +} + + +char * +QuoteAttach(char *fn, size_t fnlen) +{ + char *p; + + if(*fn && strpbrk(fn, " \t,(\"")){ /* Quote it? */ + p = &fn[strlen(fn)]; + if(p+2-fn < fnlen){ + *(p+2) = '\0'; + *(p+1) = '\"'; + + do + *p = *(p-1); + while(--p != fn); + *p = '\"'; + } + } + + return(fn); +} + + +void +ZotAttach(PATMT *p) +{ + if(!p) + return; + + if(p->description) + free((char *)p->description); + + if(p->filename){ + if(p->flags & A_TMP) + our_unlink(p->filename); + + free((char *)p->filename); + } + + if(p->size) + free((char *)p->size); + + if(p->id) + free((char *)p->id); + + p->next = NULL; +} +#endif /* ATTACHMENTS */ + + +/* + * intag - return TRUE if i is in a column that makes up an + * attachment line number + */ +int +intag(UCS *s, int i) +{ + UCS *p = s; + int n = 0; + + while(*p != '\0' && (p-s) < 5){ /* is there a tag? it */ + if(n && *p == '.') /* can't be more than 4 */ + return(i <= p-s); /* chars long! */ + + if(*p < '0' || *p > '9') + break; + else + n = (n * 10) + (*p - '0'); + + p++; + } + + return(FALSE); +} + + +/* + * prettysz - return pointer to string containing nice size description + */ +char * +prettysz(off_t l) +{ + static char b[32]; + long sz, left, right; + + sz = (long) l; + b[0] = '\0'; + + if(sz < 1000L){ + snprintf(b, sizeof(b), "%ld B", sz); /* xxx B */ + } + else if(sz < 9950L){ + left = (sz + 50L) / 1000L; + right = ((sz + 50L) - left * 1000L) / 100L; + snprintf(b, sizeof(b), "%ld.%ld KB", left, right); /* x.x KB */ + } + else if(sz < 999500L){ + snprintf(b, sizeof(b), "%ld KB", (sz + 500L) / 1000L); /* xxx KB */ + } + else if(sz < 9950000L){ + left = (sz + 50000L) / 1000000L; + right = ((sz + 50000L) - left * 1000000L) / 100000L; + snprintf(b, sizeof(b), "%ld.%ld MB", left, right); /* x.x MB */ + } + else{ + snprintf(b, sizeof(b), "%ld MB", (sz + 500000L) / 1000000L); /* xxx MB */ + } + + return(b); +} + + +/* + * sinserts - s insert into another string + */ +void +sinserts(UCS *ds, /* dest string */ + int dl, /* where to begin insert */ + UCS *ss, /* source string */ + int sl) /* length of ss */ +{ + UCS *dp, *edp; /* pointers into dest. */ + size_t j; /* jump difference */ + + if(sl >= dl){ /* source bigger than dest. */ + dp = ds + dl; /* shift dest. to make room */ + if((edp = ucs4_strchr(dp, '\0')) != NULL){ + j = sl - dl; + + for( ;edp >= dp; edp--) + edp[j] = *edp; + + while(sl--) + *ds++ = *ss++; + } + else + emlwrite("\007No end of line???", NULL); /* can this happen? */ + } + else{ /* dest is longer, shrink it */ + j = dl - sl; /* difference in lengths */ + + while(sl--) /* copy u onto ds */ + *ds++ = *ss++; + + if(ucs4_strlen(ds) > j){ /* shuffle the rest left */ + do + *ds = ds[j]; + while(*ds++ != '\0'); + } + else + *ds = '\0'; + } +} diff --git a/pico/basic.c b/pico/basic.c new file mode 100644 index 00000000..49b04bd6 --- /dev/null +++ b/pico/basic.c @@ -0,0 +1,955 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: basic.c 831 2007-11-27 01:04:19Z 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 + * + * ======================================================================== + * + * Program: Cursor manipulation functions + */ + +/* + * The routines in this file move the cursor around on the screen. They + * compute a new value for the cursor, then adjust ".". The display code + * always updates the cursor location, so only moves between lines, or + * functions that adjust the top line in the window and invalidate the + * framing, are hard. + */ +#include "headers.h" + +#include "osdep/terminal.h" + + +/* + * Move the cursor to the + * beginning of the current line. + * Trivial. + */ +int +gotobol(int f, int n) +{ + curwp->w_doto = 0; + return (TRUE); +} + +/* + * Move the cursor backwards by "n" characters. If "n" is less than zero call + * "forwchar" to actually do the move. Otherwise compute the new cursor + * location. Error if you try and move out of the buffer. Set the flag if the + * line pointer for dot changes. + */ +int +backchar(int f, int n) +{ + register LINE *lp; + + if (n < 0) + return (forwchar(f, -n)); + + while (n--) { + if (curwp->w_doto == 0) { + if ((lp=lback(curwp->w_dotp)) == curbp->b_linep){ + if(Pmaster && Pmaster->headents) + /* + * go up into editing the mail header if on + * the top line and the user hits the left arrow!!! + * + * if the editor returns anything except -1, the + * user requested something special, so let + * pico know... + */ + return(HeaderEditor(2, 1)); + else + return (FALSE); + } + + curwp->w_dotp = lp; + curwp->w_doto = llength(lp); + curwp->w_flag |= WFMOVE; + } else + curwp->w_doto--; + } + + return (TRUE); +} + +/* + * Move the cursor backwards by "n" characters. If "n" is less than zero call + * "forwchar" to actually do the move. Otherwise compute the new cursor + * location. Error if you try and move out of the buffer. Set the flag if the + * line pointer for dot changes. + * + * This routine does _not_ do the header editor checks. It is used by + * backword() in pico\word.c which gets stuck in a loop trying to go + * back if you've jumped into a header. + */ +int +backchar_no_header_editor(int f, int n) +{ + register LINE *lp; + + if (n < 0) + return (forwchar(f, -n)); + + while (n--) { + if (curwp->w_doto == 0) { + if ((lp=lback(curwp->w_dotp)) == curbp->b_linep){ + return (FALSE); + } + + curwp->w_dotp = lp; + curwp->w_doto = llength(lp); + curwp->w_flag |= WFMOVE; + } else + curwp->w_doto--; + } + + return (TRUE); +} + +/* + * Move the cursor to the end of the current line. Trivial. No errors. + */ +int +gotoeol(int f, int n) +{ + curwp->w_doto = llength(curwp->w_dotp); + return (TRUE); +} + + +/* + * Move the cursor forwwards by "n" characters. If "n" is less than zero call + * "backchar" to actually do the move. Otherwise compute the new cursor + * location, and move ".". Error if you try and move off the end of the + * buffer. Set the flag if the line pointer for dot changes. + */ +int +forwchar(int f, int n) +{ + if (n < 0) + return (backchar(f, -n)); + + while (n--) { + if (curwp->w_doto == llength(curwp->w_dotp)) { + if (curwp->w_dotp == curbp->b_linep) + return (FALSE); + + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_doto = 0; + curwp->w_flag |= WFMOVE; + } + else + curwp->w_doto++; + } + + return (TRUE); +} + + +/* + * move to a particular line. + * argument (n) must be a positive integer for + * this to actually do anything + */ +int +gotoline(int f, int n) +{ + if (n < 1) /* if a bogus argument...then leave */ + return(FALSE); + + /* first, we go to the start of the buffer */ + curwp->w_dotp = lforw(curbp->b_linep); + curwp->w_doto = 0; + return(forwline(f, n-1)); +} + + +/* + * Goto the beginning of the buffer. Massive adjustment of dot. This is + * considered to be hard motion; it really isn't if the original value of dot + * is the same as the new value of dot. Normally bound to "M-<". + */ +int +gotobob(int f, int n) +{ + curwp->w_dotp = lforw(curbp->b_linep); + curwp->w_doto = 0; + curwp->w_flag |= WFHARD; + return (TRUE); +} + + +/* + * Move to the end of the buffer. Dot is always put at the end of the file + * (ZJ). The standard screen code does most of the hard parts of update. + * Bound to "M->". + */ +int +gotoeob(int f, int n) +{ + curwp->w_dotp = curbp->b_linep; + curwp->w_doto = 0; + curwp->w_flag |= WFHARD; + return (TRUE); +} + + +/* + * Move forward by full lines. If the number of lines to move is less than + * zero, call the backward line function to actually do it. The last command + * controls how the goal column is set. Bound to "C-N". No errors are + * possible. + */ +int +forwline(int f, int n) +{ + register LINE *dlp; + + if (n < 0) + return (backline(f, -n)); + + if ((lastflag&CFCPCN) == 0) /* Reset goal if last */ + curgoal = getccol(FALSE); /* not C-P or C-N */ + + thisflag |= CFCPCN; + dlp = curwp->w_dotp; + while (n-- && dlp!=curbp->b_linep) + dlp = lforw(dlp); + + curwp->w_dotp = dlp; + curwp->w_doto = getgoal(dlp); + curwp->w_flag |= WFMOVE; + return (TRUE); +} + + +/* + * This function is like "forwline", but goes backwards. The scheme is exactly + * the same. Check for arguments that are less than zero and call your + * alternate. Figure out the new line and call "movedot" to perform the + * motion. No errors are possible. Bound to "C-P". + */ +int +backline(int f, int n) +{ + register LINE *dlp; + + if (n < 0) + return (forwline(f, -n)); + + if(Pmaster && Pmaster->headents){ + /* + * go up into editing the mail header if on the top line + * and the user hits the up arrow!!! + */ + if (lback(curwp->w_dotp) == curbp->b_linep) + /* + * if the editor returns anything except -1 then the user + * has requested something special, so let pico know... + */ + return(HeaderEditor(1, 1)); + } + + if ((lastflag&CFCPCN) == 0) /* Reset goal if the */ + curgoal = getccol(FALSE); /* last isn't C-P, C-N */ + + thisflag |= CFCPCN; + dlp = curwp->w_dotp; + while (n-- && lback(dlp)!=curbp->b_linep) + dlp = lback(dlp); + + curwp->w_dotp = dlp; + curwp->w_doto = getgoal(dlp); + curwp->w_flag |= WFMOVE; + return (TRUE); +} + + +/* + * go back to the begining of the current paragraph + * here we look for a or or + * combination to delimit the begining of a paragraph + */ +int +gotobop(int f, int n) +{ + int quoted, qlen; + UCS qstr[NLINE], qstr2[NLINE]; + + if (n < 0) /* the other way...*/ + return(gotoeop(f, -n)); + + while (n-- > 0) { /* for each one asked for */ + + while(lisblank(curwp->w_dotp) + && lback(curwp->w_dotp) != curbp->b_linep){ + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_doto = 0; + } + + /* scan line by line until we come to a line ending with + * a or or + * + * PLUS: if there's a quote string, a quoted-to-non-quoted + * line transition. + */ + quoted = glo_quote_str ? quote_match(glo_quote_str, curwp->w_dotp, qstr, NLINE) : 0; + qlen = quoted ? ucs4_strlen(qstr) : 0; + while(lback(curwp->w_dotp) != curbp->b_linep + && llength(lback(curwp->w_dotp)) > qlen + && (glo_quote_str + ? (quoted == quote_match(glo_quote_str, + lback(curwp->w_dotp), + qstr2, NLINE) + && !ucs4_strcmp(qstr, qstr2)) + : 1) + && lgetc(curwp->w_dotp, qlen).c != TAB + && lgetc(curwp->w_dotp, qlen).c != ' ') + curwp->w_dotp = lback(curwp->w_dotp); + + if(n){ + /* keep looking */ + if(lback(curwp->w_dotp) == curbp->b_linep) + break; + else + curwp->w_dotp = lback(curwp->w_dotp); + + curwp->w_doto = 0; + } + else{ + /* leave cursor on first word in para */ + curwp->w_doto = 0; + while(ucs4_isspace(lgetc(curwp->w_dotp, curwp->w_doto).c)) + if(++curwp->w_doto >= llength(curwp->w_dotp)){ + curwp->w_doto = 0; + curwp->w_dotp = lforw(curwp->w_dotp); + if(curwp->w_dotp == curbp->b_linep) + break; + } + } + } + + curwp->w_flag |= WFMOVE; /* force screen update */ + return(TRUE); +} + + +/* + * go forword to the end of the current paragraph + * here we look for a or or + * combination to delimit the begining of a paragraph + */ +int +gotoeop(int f, int n) +{ + int quoted, qlen; + UCS qstr[NLINE], qstr2[NLINE]; + + if (n < 0) /* the other way...*/ + return(gotobop(f, -n)); + + while (n-- > 0) { /* for each one asked for */ + + while(lisblank(curwp->w_dotp)){ + curwp->w_doto = 0; + if((curwp->w_dotp = lforw(curwp->w_dotp)) == curbp->b_linep) + break; + } + + /* scan line by line until we come to a line ending with + * a or or + * + * PLUS: if there's a quote string, a quoted-to-non-quoted + * line transition. + */ + quoted = glo_quote_str + ? quote_match(glo_quote_str, + curwp->w_dotp, qstr, NLINE) : 0; + qlen = quoted ? ucs4_strlen(qstr) : 0; + + while(curwp->w_dotp != curbp->b_linep + && llength(lforw(curwp->w_dotp)) > qlen + && (glo_quote_str + ? (quoted == quote_match(glo_quote_str, + lforw(curwp->w_dotp), + qstr2, NLINE) + && !ucs4_strcmp(qstr, qstr2)) + : 1) + && lgetc(lforw(curwp->w_dotp), qlen).c != TAB + && lgetc(lforw(curwp->w_dotp), qlen).c != ' ') + curwp->w_dotp = lforw(curwp->w_dotp); + + curwp->w_doto = llength(curwp->w_dotp); + + /* still looking? */ + if(n){ + if(curwp->w_dotp == curbp->b_linep) + break; + else + curwp->w_dotp = lforw(curwp->w_dotp); + + curwp->w_doto = 0; + } + } + + curwp->w_flag |= WFMOVE; /* force screen update */ + return(curwp->w_dotp != curbp->b_linep); +} + +/* + * This routine, given a pointer to a LINE, and the current cursor goal + * column, return the best choice for the offset. The offset is returned. + * Used by "C-N" and "C-P". + */ +int +getgoal(LINE *dlp) +{ + UCS c; + register int col; + register int newcol; + register int dbo; + + col = 0; + dbo = 0; + while (dbo != llength(dlp)) { + c = lgetc(dlp, dbo).c; + newcol = col; + + if (c == '\t'){ + newcol |= 0x07; + ++newcol; + } + else if (ISCONTROL(c)){ + newcol += 2; + } + else{ + int w; + + w = wcellwidth(c); + newcol += (w >= 0 ? w : 1); + } + + if (newcol > curgoal) + break; + + col = newcol; + ++dbo; + } + + return (dbo); +} + + +/* + * Scroll the display forward (up) n lines. + */ +int +scrollforw(int n, int movedot) +{ + register LINE *lp; + LINE *lp2; + register int nl; + int i; + + nl = n; + lp = curwp->w_linep; + while (n-- && lp!=curbp->b_linep) + lp = lforw(lp); + + if (movedot) { /* Move dot to top of page. */ + curwp->w_dotp = lp; + curwp->w_doto = 0; + } + + curwp->w_flag |= WFHARD; + if(lp == curbp->b_linep) + return(TRUE); + else + curwp->w_linep = lp; + + /* + * if the header is open, close it ... + */ + if(Pmaster && Pmaster->headents && ComposerTopLine != COMPOSER_TOP_LINE){ + n -= ComposerTopLine - COMPOSER_TOP_LINE; + ToggleHeader(0); + } + + /* + * scroll down from the top the same number of lines we've moved + * forward + */ + if(TERM_OPTIMIZE) + scrollup(curwp, -1, nl-n-1); + + if(!movedot){ + /* Requested to not move the dot. Look for the dot in the current + * window. loop through all lines, stop when at end of window + * or endof buffer. If the dot is found, it can stay where it + * is, otherwise we do need to move it. + */ + movedot = TRUE; + for ( lp2 = lp, i = 0; + lp2 != curbp->b_linep && i < curwp->w_ntrows; + lp2 = lforw(lp2), ++i) { + if (curwp->w_dotp == lp2) { + movedot = FALSE; + break; + } + } + if (movedot) { + /* Dot not found in window. Move to first line of window. */ + curwp->w_dotp = lp; + curwp->w_doto = 0; + } + } + + return (TRUE); +} + + +/* + * Scroll forward by a specified number of lines, or by a full page if no + * argument. Bound to "C-V". The "2" in the arithmetic on the window size is + * the overlap; this value is the default overlap value in ITS EMACS. Because + * this zaps the top line in the display window, we have to do a hard update. + */ +int +forwpage(int f, int n) +{ + + if (f == FALSE) { + n = curwp->w_ntrows - 2; /* Default scroll. */ + if (n <= 0) /* Forget the overlap */ + n = 1; /* if tiny window. */ + } else if (n < 0) + return (backpage(f, -n)); +#if CVMVAS + else /* Convert from pages */ + n *= curwp->w_ntrows; /* to lines. */ +#endif + return (scrollforw (n, TRUE)); +} + + +/* + * Scroll back (down) number of lines. + */ +int +scrollback(int n, int movedot) +{ + register LINE *lp, *tp; + register int nl; + int i; + + if(Pmaster && Pmaster->headents){ + /* + * go up into editing the mail header if on the top line + * and the user hits the up arrow!!! + */ + if (lback(curwp->w_dotp) == curbp->b_linep){ + /* + * if the editor returns anything except -1 then the user + * has requested something special, so let pico know... + */ + return(HeaderEditor(1, 1)); + } + } + + /* + * Count back the number of lines requested. + */ + nl = n; + lp = curwp->w_linep; + while (n-- && lback(lp)!=curbp->b_linep) + lp = lback(lp); + + curwp->w_linep = lp; + curwp->w_flag |= WFHARD; + + /* + * scroll down from the top the same number of lines we've moved + * forward + * + * This isn't too cool, but it has to be this way so we can + * gracefully scroll in the message header + */ + if(Pmaster && Pmaster->headents){ + if((lback(lp)==curbp->b_linep) && (ComposerTopLine==COMPOSER_TOP_LINE)) + n -= entry_line(1000, TRUE); /* never more than 1000 headers */ + if(nl-n-1 < curwp->w_ntrows) + if(TERM_OPTIMIZE) + scrolldown(curwp, -1, nl-n-1); + } + else + if(TERM_OPTIMIZE) + scrolldown(curwp, -1, nl-n-1); + + if(Pmaster && Pmaster->headents){ + /* + * if we're at the top of the page, and the header is closed, + * open it ... + */ + if((lback(lp) == curbp->b_linep) + && (ComposerTopLine == COMPOSER_TOP_LINE)){ + ToggleHeader(1); + movecursor(ComposerTopLine, 0); + } + } + + /* + * Decide if we move the dot or not. Calculation done AFTER deciding + * if we display the header because that will change the number of + * lines on the screen. + */ + if (movedot) { + /* Dot gets put at top of window. */ + curwp->w_dotp = curwp->w_linep; + curwp->w_doto = 0; + } + else { + /* Requested not to move dot, but we do need to keep in on + * the screen. Verify that it is still in the range of lines + * visable in the window. Loop from the first line to the + * last line, until we reach the end of the buffer or the end + * of the window. If we find the dot, then we don't need + * to move it. */ + movedot = TRUE; + for ( tp = curwp->w_linep, i = 0; + tp != curbp->b_linep && i < curwp->w_ntrows; + tp = lforw(tp), ++i) { + if (curwp->w_dotp == tp) { + movedot = FALSE; + break; + } + } + if (movedot) { + /* Dot not found in window. Move to last line of window. */ + curwp->w_dotp = lback (tp); + curwp->w_doto = 0; + } + } + + return (TRUE); +} + + + + +/* + * This command is like "forwpage", but it goes backwards. The "2", like + * above, is the overlap between the two windows. The value is from the ITS + * EMACS manual. Bound to "M-V". We do a hard update for exactly the same + * reason. + */ +int +backpage(int f, int n) +{ + + if (f == FALSE) { + n = curwp->w_ntrows - 2; /* Default scroll. */ + if (n <= 0) /* Don't blow up if the */ + n = 1; /* window is tiny. */ + } else if (n < 0) + return (forwpage(f, -n)); +#if CVMVAS + else /* Convert from pages */ + n *= curwp->w_ntrows; /* to lines. */ +#endif + return (scrollback (n, TRUE)); +} + + +int +scrollupline(int f, int n) +{ + return (scrollback (1, FALSE)); +} + + +int +scrolldownline(int f, int n) +{ + return (scrollforw (1, FALSE)); +} + + + +/* + * Scroll to a position. + */ +int +scrollto(int f, int n) +{ +#ifdef _WINDOWS + long scrollLine; + LINE *lp; + int i; + + scrollLine = mswin_getscrollto (); + + /* + * Starting at the first data line in the buffer, step forward + * 'scrollLine' lines to find the new top line. It is a circular + * list of buffers, so watch for the first line to reappear. if + * it does, we have some sort of internal error, abort scroll + * operation. Also watch for NULL, just in case. + */ + lp = lforw (curbp->b_linep); + for (i = 0; i < scrollLine && lp != curbp->b_linep && lp != NULL; ++i) + lp = lforw(lp); + + if (lp == curbp->b_linep || lp == NULL) + return (FALSE); /* Whoops! */ + + + /* Set the new top line for the window and flag a redraw. */ + curwp->w_linep = lp; + curwp->w_dotp = lp; + curwp->w_doto = 0; + curwp->w_flag |= WFHARD; + + if(Pmaster && Pmaster->headents){ + /* + * If we are at the top of the page and header not open, open it. + * If we are not at the top of the page and the header is open, + * close it. + */ + if((lback(lp) == curbp->b_linep) + && (ComposerTopLine == COMPOSER_TOP_LINE)){ + ToggleHeader(1); + movecursor(ComposerTopLine, 0); + } + else if((lback(lp) != curbp->b_linep) + && (ComposerTopLine != COMPOSER_TOP_LINE)){ + ToggleHeader (0); + } + } +#endif + + return (TRUE); +} + + + +/* + * Set the mark in the current window to the value of "." in the window. No + * errors are possible. Bound to "M-.". If told to set an already set mark + * unset it. + */ +int +setmark(int f, int n) +{ + if(!curwp->w_markp){ + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = curwp->w_doto; + if(n) + emlwrite("Mark Set", NULL); + } + else{ + /* clear inverse chars between here and dot */ + markregion(0); + curwp->w_markp = NULL; + if(n) + emlwrite("Mark UNset", NULL); + } + +#ifdef _WINDOWS + mswin_allowcopycut(curwp->w_markp ? kremove : NULL); +#endif + return (TRUE); +} + + +/* + * Swap the values of "." and "mark" in the current window. This is pretty + * easy, bacause all of the hard work gets done by the standard routine + * that moves the mark about. The only possible error is "no mark". Bound to + * "C-X C-X". + */ +int +swapmark(int f, int n) +{ + register LINE *odotp; + register int odoto; + + if (curwp->w_markp == NULL) { + if(Pmaster == NULL) + emlwrite("No mark in this window", NULL); + return (FALSE); + } + + odotp = curwp->w_dotp; + odoto = curwp->w_doto; + curwp->w_dotp = curwp->w_markp; + curwp->w_doto = curwp->w_marko; + curwp->w_markp = odotp; + curwp->w_marko = odoto; + curwp->w_flag |= WFMOVE; + return (TRUE); +} + + +/* + * Set the mark in the current window to the value of "." in the window. No + * errors are possible. Bound to "M-.". If told to set an already set mark + * unset it. + */ +int +setimark(int f, int n) +{ + curwp->w_imarkp = curwp->w_dotp; + curwp->w_imarko = curwp->w_doto; + return(TRUE); +} + + +/* + * Swap the values of "." and "mark" in the current window. This is pretty + * easy, bacause all of the hard work gets done by the standard routine + * that moves the mark about. The only possible error is "no mark". Bound to + * "C-X C-X". + */ +int +swapimark(int f, int n) +{ + register LINE *odotp; + register int odoto; + + if (curwp->w_imarkp == NULL) { + if(Pmaster == NULL) + emlwrite("Programmer botch! No mark in this window", NULL); + return (FALSE); + } + + odotp = curwp->w_dotp; + odoto = curwp->w_doto; + curwp->w_dotp = curwp->w_imarkp; + curwp->w_doto = curwp->w_imarko; + curwp->w_imarkp = odotp; + curwp->w_imarko = odoto; + curwp->w_flag |= WFMOVE; + return (TRUE); +} + + +/* + * If dot comes before mark, do nothing. + * If mark comes before dot, swap them. + */ +void +swap_mark_and_dot_if_mark_comes_first(void) +{ + LINE *blp, *flp; + + if(!(curwp && curwp->w_dotp && curwp->w_markp)) + return; + + if(curwp->w_dotp == curwp->w_markp){ /* they are in the same line */ + if(curwp->w_doto > curwp->w_marko) + swapmark(0,1); + + return; + } + + /* + * Search forward and backward from dot to see if mark + * is less than or greater than dot. + */ + flp = blp = curwp->w_dotp; + while(flp != curbp->b_linep || lback(blp) != curbp->b_linep){ + if(flp != curbp->b_linep){ + flp = lforw(flp); + if(flp == curwp->w_markp) /* dot already less than mark */ + return; + } + + if(lback(blp) != curbp->b_linep){ + blp = lback(blp); + if(blp == curwp->w_markp){ + swapmark(0, 1); + return; + } + } + } +} + + +#ifdef MOUSE + +/* + * Handle a mouse down. + */ +int +mousepress(int f, int n) +{ + MOUSEPRESS mp; + LINE *lp; + int i; + + + mouse_get_last (NULL, &mp); + + + lp = curwp->w_linep; + i = mp.row - ((Pmaster && Pmaster->headents) ? ComposerTopLine : 2); + if (i < 0) { + if (Pmaster) { + /* Clear existing region. */ + if (curwp->w_markp) + setmark(0,1); + + /* Move to top of document before editing header. */ + curwp->w_dotp = curwp->w_linep; + curwp->w_doto = 0; + curwp->w_flag |= WFMOVE; + update (); /* And update. */ + + return (HeaderEditor (1, 1)); + } + } + else { + while(i-- && lp != curbp->b_linep) + lp = lforw(lp); + + curgoal = mp.col; + curwp->w_dotp = lp; + curwp->w_doto = getgoal(lp); + curwp->w_flag |= WFMOVE; + + if(mp.doubleclick) + setmark(0, 1); + } + + return(FALSE); +} + + +int +toggle_xterm_mouse(int f, int n) +{ +#ifndef _WINDOWS + int e; + + (e=mouseexist()) ? end_mouse() : (void) init_mouse(); + if(e != mouseexist()){ + mouseexist() ? emlwrite(_("Xterm mouse tracking on!"), NULL) + : emlwrite(_("Xterm mouse tracking off!"), NULL); + } + else if(!e) + emlwrite(_("Xterm mouse tracking still off ($DISPLAY variable set?)"), NULL); +#endif + return(TRUE); +} +#endif diff --git a/pico/bind.c b/pico/bind.c new file mode 100644 index 00000000..83e93627 --- /dev/null +++ b/pico/bind.c @@ -0,0 +1,406 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: bind.c 857 2007-12-08 00:49:45Z 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 + * + * ======================================================================== + * + * Program: Key binding routines + */ + +/* This file is for functions having to do with key bindings, + descriptions, help commands, and command line execution. + + written 11-feb-86 by Daniel Lawrence + */ + +#include "headers.h" + +int arraylen(char **); + +/* + * help - help function for pico (UW pared down version of uemacs). + * this function will intentionally garbage with helpful + * tips and then wait for a ' ' to be hit to then update the + * screen. + */ + + +static char *helptext[] = { + /* TRANSLATORS: The next several lines go together as help text. + Leave the ~ characters where they are, they cause the following + character to be printed in boldface as long as the first character + of the line is also a ~. */ + N_(" Pico Help Text"), + " ", + N_(" Pico is designed to be a simple, easy-to-use text editor with a"), + N_(" layout very similar to the Alpine mailer. The status line at the"), + N_(" top of the display shows pico's version, the current file being"), + N_(" edited and whether or not there are outstanding modifications"), + N_(" that have not been saved. The third line from the bottom is used"), + N_(" to report informational messages and for additional command input."), + N_(" The bottom two lines list the available editing commands."), + " ", + N_(" Each character typed is automatically inserted into the buffer"), + N_(" at the current cursor position. Editing commands and cursor"), + N_(" movement (besides arrow keys) are given to pico by typing"), + N_(" special control-key sequences. A caret, '^', is used to denote"), + N_("~ the control key, sometimes marked \"CTRL\", so the ~C~T~R~L~-~q key"), + N_("~ combination is written as ~^~Q."), + " ", + N_(" The following functions are available in pico (where applicable,"), + N_(" corresponding function key commands are in parentheses)."), + " ", + N_("~ ~^~G (~F~1) Display this help text."), + " ", + N_("~ ~^~F move Forward a character."), + N_("~ ~^~B move Backward a character."), + N_("~ ~^~P move to the Previous line."), + N_("~ ~^~N move to the Next line."), + N_("~ ~^~A move to the beginning of the current line."), + N_("~ ~^~E move to the End of the current line."), + N_("~ ~^~V (~F~8) move forward a page of text."), + N_("~ ~^~Y (~F~7) move backward a page of text."), + " ", + N_("~ ~^~W (~F~6) Search for (where is) text, neglecting case."), + N_("~ ~^~L Refresh the display."), + " ", + N_("~ ~^~D Delete the character at the cursor position."), + N_("~ ~^~^ Mark cursor position as beginning of selected text."), + N_(" Note: Setting mark when already set unselects text."), + N_("~ ~^~K (~F~9) Cut selected text (displayed in inverse characters)."), + N_(" Note: The selected text's boundary on the cursor side"), + N_(" ends at the left edge of the cursor. So, with "), + N_(" selected text to the left of the cursor, the "), + N_(" character under the cursor is not selected."), + N_("~ ~^~U (~F~1~0) Uncut (paste) last cut text inserting it at the"), + N_(" current cursor position."), + N_("~ ~^~I Insert a tab at the current cursor position."), + " ", + N_("~ ~^~J (~F~4) Format (justify) the current paragraph."), + N_(" Note: paragraphs delimited by blank lines or indentation."), + N_("~ ~^~T (~F~1~2) To invoke the spelling checker"), + N_("~ ~^~C (~F~1~1) Report current cursor position"), + " ", + N_("~ ~^~R (~F~5) Insert an external file at the current cursor position."), + N_("~ ~^~O (~F~3) Output the current buffer to a file, saving it."), + N_("~ ~^~X (~F~2) Exit pico, saving buffer."), + " ", + N_(" End of Help."), + " ", + NULL +}; + + +/* + * arraylen - return the number of bytes in an array of char + */ +int +arraylen(char **array) +{ + register int i=0; + + while(array[i++] != NULL) ; + return(i); +} + + +/* + * whelp - display help text for the composer and pico + */ +int +whelp(int f, int n) +{ + if(term.t_mrow == 0){ /* blank keymenu in effect */ + if(km_popped == 0){ + /* cause keymenu display */ + km_popped = 2; + if(!Pmaster) + sgarbf = TRUE; + + return(TRUE); + } + } + + if(Pmaster){ + VARS_TO_SAVE *saved_state; + + saved_state = save_pico_state(); + (*Pmaster->helper)(Pmaster->composer_help, + Pmaster->headents + ? _("Help for the Alpine Composer") + : _("Help for Signature Editor"), + 1); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + + ttresize(); + picosigs(); /* restore any altered handlers */ + curwp->w_flag |= WFMODE; + if(km_popped) /* this will unpop us */ + curwp->w_flag |= WFHARD; + } + else{ + int mrow_was_zero = 0; + + /* always want keyhelp showing during help */ + if(term.t_mrow == 0){ + mrow_was_zero++; + term.t_mrow = 2; + } + + /* TRANSLATORS: Pico is the name of a program */ + pico_help(helptext, _("Help for Pico"), 1); + /* put it back the way it was */ + if(mrow_was_zero) + term.t_mrow = 0; + } + + sgarbf = TRUE; + return(FALSE); +} + +static KEYMENU menu_scroll[] = { + {NULL, NULL, KS_NONE}, {NULL, NULL, KS_NONE}, + {NULL, NULL, KS_NONE}, {NULL, NULL, KS_NONE}, + {NULL, NULL, KS_NONE}, {NULL, NULL, KS_NONE}, + {"^X", N_("Exit Help"), KS_NONE}, {NULL, NULL, KS_NONE}, + {NULL, NULL, KS_NONE}, {NULL, NULL, KS_NONE}, + {NULL, NULL, KS_NONE}, {NULL, NULL, KS_NONE} +}; +#define PREV_KEY 3 +#define NEXT_KEY 9 + + +#define OVERLAP 2 /* displayed page overlap */ + +/* + * scrollw - takes beginning row and ending row to diplay an array + * of text lines. returns either 0 if scrolling terminated + * normally or the value of a ctrl character typed to end it. + * + * updates - + * 01/11/89 - added stripe call if 1st char is tilde - '~' + * + */ +int +wscrollw(int begrow, int endrow, char *utf8textp[], int textlen) +{ + register int loffset = 0; + register int prevoffset = -1; + register int dlines; + register int i; + register int cont; + register int done = 0; + register char *buf; + UCS c; + + dlines = endrow - begrow - 1; + while(!done) { + /* + * diplay a page loop ... + */ + if(prevoffset != loffset){ + for(i = 0; i < dlines; i++){ + movecursor(i + begrow, 0); + peeol(); + if((loffset+i) < textlen){ + buf = _(&(utf8textp[loffset+i][0])); + if(*buf == '~'){ + buf++; + wstripe(begrow+i, 0, buf, '~'); + } + else{ + pputs_utf8(buf, 0); + } + } + } + /* + * put up the options prompt + */ + movecursor(begrow + dlines, 0); + cont = (loffset+dlines < textlen); + if(cont){ /* continue ? */ + menu_scroll[NEXT_KEY].name = "^V"; + /* TRANSLATORS: Next Page, a command key label */ + menu_scroll[NEXT_KEY].label = N_("Next Pg"); + } + else + menu_scroll[NEXT_KEY].name = NULL; + + if(loffset){ + menu_scroll[PREV_KEY].name = "^Y"; + /* TRANSLATORS: Previous Page */ + menu_scroll[PREV_KEY].label = N_("Prev Pg"); + } + else + menu_scroll[PREV_KEY].name = NULL; + + wkeyhelp(menu_scroll); + } + + (*term.t_flush)(); + + c = GetKey(); + + prevoffset = loffset; + switch(c){ + case (CTRL|'X') : /* quit */ + case F2 : + done = 1; + break; + case (CTRL|'Y') : /* prev page */ + case F7 : /* prev page */ + if((loffset-dlines-OVERLAP) > 0){ + loffset -= (dlines-OVERLAP); + } + else{ + if(loffset != 0){ + prevoffset = -1; + } + else{ + (*term.t_beep)(); + } + loffset = 0; + } + break; + case (CTRL|'V') : /* next page */ + case F8 : + if(cont){ + loffset += (dlines-OVERLAP); + } + else{ + (*term.t_beep)(); + } + break; + case '\016' : /* prev-line */ + case (CTRL|'N') : + if(cont) + loffset++; + else + (*term.t_beep)(); + break; + case '\020' : /* prev-line */ + case (CTRL|'P') : + if(loffset > 0) + loffset--; + else + (*term.t_beep)(); + break; + case '\014' : /* refresh */ + case (CTRL|'L') : /* refresh */ + modeline(curwp); + update(); + prevoffset = -1; + break; + case NODATA : + break; +#ifdef notdef + /* + * We don't handle window resize events correctly when in pico help. + * resize_pico() redraws the edit window instead of the help window. + * A ^L will redraw the help text. What we'd like is something like + * a KEY_RESIZE return from GetKey. If we had that we could exit + * wscrollw with a FALSE return value and have that cause us to loop + * back into wscrollw with the adjusted size. That would still mean + * the edit text would be redrawn first... + */ +#endif /* notdef */ + default : + unknown_command(c); + break; + } + } + + return(TRUE); +} + + +/* + * normalize_cmd - given a char and list of function key to command key + * mappings, return, depending on gmode, the right command. + * The list is an array of (Fkey, command-key) pairs. + * sc is the index in the array that means to ignore fkey + * vs. command key mapping + * + * rules: 1. if c not in table (either fkey or command), let it thru + * 2. if c matches, but in other mode, punt it + */ +UCS +normalize_cmd(UCS c, UCS list[][2], int sc) +{ + int i; + + for(i=0; i < 12; i++){ + if(c == list[i][(FUNC&c) ? 0 : 1]){ /* in table? */ + if(i == sc) /* SPECIAL CASE! */ + return(list[i][1]); + + if(list[i][1] == NODATA) /* no mapping ! */ + return(c); + + if(((FUNC&c) == FUNC) && !((gmode&MDFKEY) == MDFKEY)) + return(c); /* not allowed, let caller handle it */ + else + return(list[i][1]); /* never return func keys */ + } + } + + return(c); +} + + +/* + * rebind - replace the first function with the second + */ +void +rebindfunc(int (*a)(int, int), int (*b)(int, int)) +{ + KEYTAB *kp; + + kp = (Pmaster) ? &keytab[0] : &pkeytab[0]; + + while(kp->k_fp != NULL){ /* go thru whole list, and */ + if(kp->k_fp == a) + kp->k_fp = b; /* replace all occurances */ + kp++; + } +} + + +/* + * bindtokey - bind function f to command c + */ +int +bindtokey(UCS c, int (*f)(int, int)) +{ + KEYTAB *kp, *ktab = (Pmaster) ? &keytab[0] : &pkeytab[0]; + + for(kp = ktab; kp->k_fp; kp++) + if(kp->k_code == c){ + kp->k_fp = f; /* set to new function */ + break; + } + + /* not found? create new binding */ + if(!kp->k_fp && kp < &ktab[NBINDS]){ + kp->k_code = c; /* assign new code and function */ + kp->k_fp = f; + (++kp)->k_code = 0; /* and null out next slot */ + kp->k_fp = NULL; + } + + return(TRUE); +} diff --git a/pico/blddate.c b/pico/blddate.c new file mode 100644 index 00000000..00f0b558 --- /dev/null +++ b/pico/blddate.c @@ -0,0 +1,53 @@ +#include +#include + +/* + * ======================================================================== + * 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 + * + * ======================================================================== + */ + +main(argc, argv) + int argc; + char **argv; +{ + struct tm *t; + FILE *outfile=stdout; + time_t ltime; + + if(argc > 1 && (outfile = fopen(argv[1], "w")) == NULL){ + /* TRANSLATORS: an error message when trying to create a file */ + fprintf(stderr, _("can't create '%s'\n"), argv[1]); + exit(1); + } + + time(<ime); + t = localtime(<ime); + fprintf(outfile,"char datestamp[]=\"%02d-%s-%d\";\n", t->tm_mday, + (t->tm_mon == 0) ? "Jan" : + (t->tm_mon == 1) ? "Feb" : + (t->tm_mon == 2) ? "Mar" : + (t->tm_mon == 3) ? "Apr" : + (t->tm_mon == 4) ? "May" : + (t->tm_mon == 5) ? "Jun" : + (t->tm_mon == 6) ? "Jul" : + (t->tm_mon == 7) ? "Aug" : + (t->tm_mon == 8) ? "Sep" : + (t->tm_mon == 9) ? "Oct" : + (t->tm_mon == 10) ? "Nov" : + (t->tm_mon == 11) ? "Dec" : "UKN", + 1900 + t->tm_year); + + fprintf(outfile, "char hoststamp[]=\"random-pc\";\n"); + + fclose(outfile); + + exit(0); +} diff --git a/pico/browse.c b/pico/browse.c new file mode 100644 index 00000000..46e01fd0 --- /dev/null +++ b/pico/browse.c @@ -0,0 +1,2915 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: browse.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 + * + * ======================================================================== + * + * Program: Routines to support file browser in pico and Pine composer + * + * + * NOTES: + * + * Misc. thoughts (mss, 5 Apr 92) + * + * This is supposed to be just a general purpose browser, equally + * callable from either pico or the pine composer. Someday, it could + * even be used to "wrap" the unix file business for really novice + * users. The stubs are here for renaming, copying, creating directories, + * deleting, undeleting (thought is delete just moves files to + * ~/.pico.deleted directory or something and undelete offers the + * files in there for undeletion: kind of like the mac trashcan). + * + * Nice side effects + * + * Since the full path name is always maintained and referencing ".." + * stats the path stripped of its trailing name, the unpleasantness of + * symbolic links is hidden. + * + * Fleshed out the file managements stuff (mss, 24 June 92) + * + * + */ +#include "headers.h" +#include "../c-client/mail.h" +#include "../c-client/rfc822.h" +#include "../pith/osdep/collate.h" +#include "../pith/charconv/filesys.h" +#include "../pith/conf.h" + +#ifndef _WINDOWS + +#if defined(bsd) || defined(lnx) +extern int errno; +#endif + + +/* + * directory cell structure + */ +struct fcell { + char *fname; /* file name */ + unsigned mode; /* file's mode */ + char size[16]; /* file's size in s */ + struct fcell *next; + struct fcell *prev; +}; + + +/* + * master browser structure + */ +static struct bmaster { + struct fcell *head; /* first cell in list */ + struct fcell *top; /* cell in top left */ + struct fcell *current; /* currently selected */ + int longest; /* longest file name (in screen width) */ + int fpl; /* file names per line */ + int cpf; /* chars / file / line */ + int flags; + char dname[NLINE]; /* this dir's name (UTF-8) */ + char *names; /* malloc'd name array (UTF-8) */ + LMLIST *lm; +} *gmp; /* global master ptr */ + + +/* + * title for exported browser display + */ +static char *browser_title = NULL; + + +struct bmaster *getfcells(char *, int); +void PaintCell(int, int, int, struct fcell *, int); +void PaintBrowser(struct bmaster *, int, int *, int *); +void BrowserKeys(void); +void layoutcells(struct bmaster *); +void percdircells(struct bmaster *); +int PlaceCell(struct bmaster *, struct fcell *, int *, int *); +void zotfcells(struct fcell *); +void zotmaster(struct bmaster **); +struct fcell *FindCell(struct bmaster *, char *); +int sisin(char *, char *); +void p_chdir(struct bmaster *); +void BrowserAnchor(char *); +void ClearBrowserScreen(void); +void BrowserRunChild(char *, char *); +int fcell_is_selected(struct fcell *, struct bmaster *); +void add_cell_to_lmlist(struct fcell *, struct bmaster *); +void del_cell_from_lmlist(struct fcell *, struct bmaster *); + + +static KEYMENU menu_browse[] = { + {"?", N_("Get Help"), KS_SCREENHELP}, {NULL, NULL, KS_NONE}, + {NULL, NULL, KS_NONE}, {"-", N_("Prev Pg"), KS_PREVPAGE}, + {"D", N_("Delete"), KS_NONE}, {"C",N_("Copy"), KS_NONE}, + {NULL, NULL, KS_NONE}, {NULL, NULL, KS_NONE}, + {"W", N_("Where is"), KS_NONE}, {"Spc", N_("Next Pg"), KS_NEXTPAGE}, + {"R", N_("Rename"), KS_NONE}, {NULL, NULL, KS_NONE} +}; +#define QUIT_KEY 1 +#define EXEC_KEY 2 +#define GOTO_KEY 6 +#define SELECT_KEY 7 +#define PICO_KEY 11 + + +#define DIRWORD "dir" +#define PARENTDIR "parent" +#define SELECTWORD "SELECT" + + +/* + * Default pager used by the stand-alone file browser. + */ +#define BROWSER_PAGER ((gmode & MDFKEY) ? "pine -k -F" : "pine -F") + + +/* + * function key mappings for callable browser + */ +static UCS bfmappings[2][12][2] = { { { F1, '?'}, /* stand alone */ + { F2, NODATA }, /* browser function */ + { F3, 'q'}, /* key mappings... */ + { F4, 'v'}, + { F5, 'l'}, + { F6, 'w'}, + { F7, '-'}, + { F8, ' '}, + { F9, 'd'}, + { F10, 'r'}, + { F11, 'c'}, + { F12, 'e'} }, + { { F1, '?'}, /* callable browser */ + { F2, NODATA }, /* function key */ + { F3, 'e'}, /* mappings... */ + { F4, 's'}, + { F5, NODATA }, + { F6, 'w'}, + { F7, '-'}, + { F8, ' '}, + { F9, 'd'}, + { F10, 'r'}, + { F11, 'c'}, + { F12, 'a'} } }; + + +/* + * Browser help for pico (pine composer help handed to us by pine) + */ +static char *BrowseHelpText[] = { +/* TRANSLATORS: The next several lines go together. The ~ characters + should be left in front of the characters they cause to be bold. */ +N_("Help for Browse Command"), +" ", +N_(" Pico's file browser is used to select a file from the"), +N_(" file system for inclusion in the edited text."), +" ", +N_("~ Both directories and files are displayed. Press ~S"), +N_("~ or ~R~e~t~u~r~n to select a file or directory. When a file"), +N_(" is selected during the \"Read File\" command, it is"), +N_(" inserted into edited text. Answering \"yes\" to the"), +N_(" verification question after a directory is selected causes"), +N_(" the contents of that directory to be displayed for selection."), +" ", +N_(" The file named \"..\" is special, and means the \"parent\""), +N_(" of the directory being displayed. Select this directory"), +N_(" to move upward in the directory tree."), +" ", +N_("End of Browser Help."), +" ", +NULL +}; + +/* + * Help for standalone browser (pilot) + */ +static char *sa_BrowseHelpText[] = { +/* TRANSLATORS: Some more help text */ +N_("Help for Pilot (PIne's Looker-upper Of Things"), +" ", +N_(" Pilot is a simple, display-oriented file system browser based on the"), +N_("~ Alpine message system composer. As with Alpine, commands are displayed at"), +N_("~ the bottom of the screen, and context-sensitive help is provided."), +" ", +N_("~ Pilot displays the current working directory at the top of the screen."), +N_("~ The directory's contents are displayed in columns of file name, file"), +N_("~ size pairs. Names that are directories are indicated by the name"), +N_("~ ~(~d~i~r~) in place of the file size. The parent of the current working"), +N_("~ directory is indicated by the file name ~.~. and size of ~(~p~a~r~e~n~t~ ~d~i~r~)."), +N_("~ File names that are symbolic links to other files are displayed with a"), +N_("~ file size of ~-~-."), +" ", +N_(" The following function keys are available in Pilot:"), +" ", +N_("~ ~? Display this help text."), +N_("~ ~Q Quit Pilot."), +" ", +N_("~ ~V View the currently selected file or open the selected directory."), +N_("~ Note: Pilot invokes ~p~i~n~e ~-~F, or the program defined by the ~P~A~G~E~R"), +N_("~ environment variable, to view the file."), +N_("~ ~L Launch an external application program."), +" ", +N_("~ ~W Search for a file by name."), +N_("~ ~- Scroll up one page."), +N_("~ ~S~p~a~c~e Scroll down one page."), +N_("~ ~N,~^~F Move forward (right) one column."), +N_("~ ~P,~^~B Move back (left) one column."), +N_("~ ~^~N Move down one row."), +N_("~ ~^~P Move up one row."), +" ", +N_("~ ~D Delete the selected file."), +N_("~ ~R Rename the selected file or directory."), +N_("~ ~C Copy the selected file."), +N_("~ ~E Edit the selected file."), +N_("~ Note: Pilot invokes ~p~i~c~o, or the program defined by the ~E~D~I~T~O~R"), +N_("~ environment variable, to edit the file."), +" ", +N_("End of Pilot Help."), +NULL +}; + + + +/* + * pico_file_browse - Exported version of FileBrowse below. + */ +int +pico_file_browse(PICO *pdata, char *dir, size_t dirlen, char *fn, size_t fnlen, + char *sz, size_t szlen, int flags) +{ + int rv; + char title_buf[64]; + + Pmaster = pdata; + gmode = pdata->pine_flags | MDEXTFB; + km_popped = 0; + + /* only init screen bufs for display and winch handler */ + if(!vtinit()) + return(-1); + + if(Pmaster){ + term.t_mrow = Pmaster->menu_rows; + if(Pmaster->oper_dir) + strncpy(opertree, Pmaster->oper_dir, NLINE); + + if(*opertree) + fixpath(opertree, sizeof(opertree)); + } + + /* install any necessary winch signal handler */ + ttresize(); + + snprintf(title_buf, sizeof(title_buf), "%s FILE", pdata->pine_anchor); + set_browser_title(title_buf); + rv = FileBrowse(dir, dirlen, fn, fnlen, sz, szlen, flags, NULL); + set_browser_title(NULL); + vttidy(); /* clean up tty handling */ + zotdisplay(); /* and display structures */ + Pmaster = NULL; /* and global config structure */ + return(rv); +} + + + +/* + * FileBrowse - display contents of given directory dir + * + * intput: + * dir points to initial dir to browse. + * dirlen- buffer size of dir + * fn initial file name. + * fnlen- buffer size of fn + * + * returns: + * dir points to currently selected directory (without + * trailing file system delimiter) + * fn points to currently selected file + * sz points to size of file if ptr passed was non-NULL + * flags + * + * Special dispensation for FB_LMODE. If the caller sets + * FB_LMODEPOS, and the caller passes a non-null lmreturn, + * then the return values will be in lmreturn instead of + * in dir and fn. The caller is responsible for freeing + * the contents of lmreturn. + * + * 1 if a file's been selected + * 0 if no files selected + * -1 if there were problems + */ +int +FileBrowse(char *dir, size_t dirlen, char *fn, size_t fnlen, + char *sz, size_t szlen, int fb_flags, LMLIST **lmreturn) +{ + UCS c, new_c; + int status, i, j; + int row, col, crow, ccol; + char *p, *envp, child[NLINE], tmp[NLINE]; + struct bmaster *mp; + struct fcell *tp; + EML eml; +#ifdef MOUSE + MOUSEPRESS mousep; +#endif + + child[0] = '\0'; + + if((gmode&MDTREE) && !in_oper_tree(dir)){ + eml.s = opertree; + emlwrite(_("\007Can't read outside of %s in restricted mode"), &eml); + sleep(2); + return(0); + } + + if(gmode&MDGOTO){ + /* fix up function key mapping table */ + /* fix up key menu labels */ + } + + /* build contents of cell structures */ + if((gmp = getfcells(dir, fb_flags)) == NULL) + return(-1); + + tp = NULL; + if(fn && *fn){ + if((tp = FindCell(gmp, fn)) != NULL){ + gmp->current = tp; + PlaceCell(gmp, gmp->current, &row, &col); + } + } + + /* paint screen */ + PaintBrowser(gmp, 0, &crow, &ccol); + + while(1){ /* the big loop */ + if(!(gmode&MDSHOCUR)){ + crow = term.t_nrow-term.t_mrow; + ccol = 0; + } + + if(!(gmode&MDSHOCUR)) + movecursor(crow, ccol); + else if(gmp->flags & FB_LMODEPOS){ + if(gmp->flags & FB_LMODE && gmp->current->mode != FIODIR) + movecursor(crow, ccol+1); + else + movecursor(crow, ccol+4); + } + else + movecursor(crow, ccol); + + if(km_popped){ + km_popped--; + if(km_popped == 0) + /* cause bottom three lines to repaint */ + PaintBrowser(gmp, 0, &crow, &ccol); + } + + if(km_popped){ /* temporarily change to cause menu to paint */ + term.t_mrow = 2; + movecursor(term.t_nrow-2, 0); /* clear status line */ + peeol(); + BrowserKeys(); + term.t_mrow = 0; + } + + (*term.t_flush)(); + +#ifdef MOUSE + mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); + register_mfunc(mouse_in_content,2,0,term.t_nrow-(term.t_mrow+1), + term.t_ncol); +#endif + c = GetKey(); +#ifdef MOUSE + clear_mfunc(mouse_in_content); +#endif + + if(Pmaster){ + if(Pmaster->newmail && (c == NODATA || time_to_check())){ + if((*Pmaster->newmail)(c == NODATA ? 0 : 2, 1) >= 0){ + int rv; + + if(km_popped){ /* restore display */ + km_popped = 0; + PaintBrowser(gmp, 0, &crow, &ccol); + } + + clearcursor(); + mlerase(); + rv = (*Pmaster->showmsg)(c); + ttresize(); + picosigs(); + if(rv) /* Did showmsg corrupt the display? */ + PaintBrowser(gmp, 0, &crow, &ccol); /* Yes, repaint */ + + mpresf = 1; + } + + clearcursor(); + if(gmp->flags & FB_LMODEPOS){ + if(gmp->flags & FB_LMODE && gmp->current->mode != FIODIR) + movecursor(crow, ccol+1); + else + movecursor(crow, ccol+4); + } + else + movecursor(crow, ccol); + } + } + else{ + if(get_input_timeout() && (c == NODATA || time_to_check())) + if(pico_new_mail()) + emlwrite(_("You may possibly have new mail."), NULL); + } + + if(km_popped) + switch(c){ + case NODATA: + case (CTRL|'L'): + km_popped++; + break; + + default: + /* clear bottom three lines */ + movecursor(term.t_nrow-2, 0); + peeol(); + movecursor(term.t_nrow-1, 0); + peeol(); + movecursor(term.t_nrow, 0); + peeol(); + break; + } + + if(c == NODATA) /* GetKey timed out */ + continue; + else if(Pmaster) + (*Pmaster->keybinput)(); + + + if(mpresf){ /* blast old messages */ + if(mpresf++ > MESSDELAY){ /* every few keystrokes */ + mlerase(); + } + } + + /* process commands */ + switch(new_c = normalize_cmd(c,bfmappings[(gmode&MDBRONLY)?0:1],2)){ + + case KEY_RIGHT: /* move right */ + case (CTRL|'@'): + case (CTRL|'F'): /* forward */ + case 'n' : + case 'N' : + if(gmp->current->next == NULL){ + (*term.t_beep)(); + break; + } + + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = gmp->current->next; + if(PlaceCell(gmp, gmp->current, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + break; + + case KEY_LEFT: /* move left */ + case (CTRL|'B'): /* back */ + case 'p' : + case 'P' : + if(gmp->current->prev == NULL){ + (*term.t_beep)(); + break; + } + + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = gmp->current->prev; + if(PlaceCell(gmp, gmp->current, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + break; + + case (CTRL|'A'): /* beginning of line */ + tp = gmp->current; + i = col; + while(i > 0){ + i -= gmp->cpf; + if(tp->prev != NULL) + tp = tp->prev; + } + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = tp; + if(PlaceCell(gmp, tp, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + break; + + case (CTRL|'E'): /* end of line */ + tp = gmp->current; + i = col + gmp->cpf; + while(i+gmp->cpf <= gmp->cpf * gmp->fpl){ + i += gmp->cpf; + if(tp->next != NULL) + tp = tp->next; + } + + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = tp; + if(PlaceCell(gmp, tp, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + break; + + case (CTRL|'V'): /* page forward */ + case ' ': + case KEY_PGDN : + case KEY_END : + tp = gmp->top; + i = term.t_nrow - term.t_mrow - 2; + + while(i-- && tp->next != NULL){ + j = 0; + while(++j <= gmp->fpl && tp->next != NULL) + tp = tp->next; + } + + if(tp == NULL) + continue; + + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = tp; + if(PlaceCell(gmp, tp, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + break; + + case '-' : + case (CTRL|'Y'): /* page backward */ + case KEY_PGUP : + case KEY_HOME : + tp = gmp->top; + i = term.t_nrow - term.t_mrow - 4; + while(i-- && tp != NULL){ + j = gmp->fpl; + while(j-- && tp != NULL) + tp = tp->prev; + } + + if(tp || (gmp->current != gmp->top)){ /* clear old hilite */ + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + } + + if(tp) /* new page ! */ + gmp->current = tp; + else if(gmp->current != gmp->top) /* goto top of page */ + gmp->current = gmp->top; + else /* do nothing */ + continue; + + if(PlaceCell(gmp, gmp->current, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + + break; + + case KEY_DOWN : + case (CTRL|'N'): /* next */ + tp = gmp->current; + i = gmp->fpl; + while(i--){ + if(tp->next == NULL){ + (*term.t_beep)(); + break; + } + else + tp = tp->next; + } + if(i != -1) /* can't go down */ + break; + + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = tp; + if(PlaceCell(gmp, tp, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + break; + + case KEY_UP : + case (CTRL|'P'): /* previous */ + tp = gmp->current; + i = gmp->fpl; + while(i-- && tp) + tp = tp->prev; + + if(tp == NULL) + break; + + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = tp; + if(PlaceCell(gmp, tp, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + break; + +#ifdef MOUSE + case KEY_MOUSE: + mouse_get_last (NULL, &mousep); + if (mousep.doubleclick) { + goto Selected; + } + else { + row = mousep.row -= 2; /* Adjust for header*/ + col = mousep.col; + i = row * gmp->fpl + (col / gmp->cpf); /* Count from top */ + tp = gmp->top; /* start at top. */ + for (; i > 0 && tp != NULL; --i) /* Count cells. */ + tp = tp->next; + if (tp != NULL) { /* Valid cell? */ + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = tp; + if(PlaceCell(gmp, tp, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + } + } + break; +#endif + + case 'e': /* exit or edit */ + case 'E': + if(gmode&MDBRONLY){ /* run "pico" */ + snprintf(child, sizeof(child), "%s%c%s", gmp->dname, C_FILESEP, + gmp->current->fname); + /* make sure selected isn't a directory or executable */ + if(!LikelyASCII(child)){ + emlwrite(_("Can't edit non-text file. Try Launch."), NULL); + break; + } + + if((envp = (char *) getenv("EDITOR")) != NULL) + snprintf(tmp, sizeof(tmp), "%s \'%s\'", envp, child); + else + snprintf(tmp, sizeof(tmp), "pico%s%s%s \'%s\'", + (gmode & MDFKEY) ? " -f" : "", + (gmode & MDSHOCUR) ? " -g" : "", + (gmode & MDMOUSE) ? " -m" : "", + child); + + BrowserRunChild(tmp, gmp->dname); /* spawn pico */ + PaintBrowser(gmp, 0, &crow, &ccol); /* redraw browser */ + } + else{ + zotmaster(&gmp); + return(0); + } + + break; + + case 'q': /* user exits wrong */ + case 'Q': + if(gmode&MDBRONLY){ + zotmaster(&gmp); + return(0); + } + + unknown_command(c); + break; + + case 'x': /* toggle selection */ + case 'X': + if(!(gmp->flags & FB_LMODE)){ + if(gmp->flags & FB_LMODEPOS) + emlwrite(_("\007Type L command to use ListMode"), NULL); + else + unknown_command(c); + + break; + } + + if(gmp->current->mode == FIODIR){ + emlwrite(_("\007Can't Set directories"), NULL); + break; + } + + if(fcell_is_selected(gmp->current, gmp)) + del_cell_from_lmlist(gmp->current, gmp); + else + add_cell_to_lmlist(gmp->current, gmp); + + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 1); + break; + + case 'l': /* run Command */ + case 'L': /* or ListMode */ + if(gmp->flags & FB_LMODEPOS){ + if(gmp->flags & FB_LMODE){ + /* + * Unless we make it so you can get out of ListMode + * once you're in ListMode, this must be an error. + */ + emlwrite(_("\007Already in ListMode"), NULL); + break; + } + else{ + gmp->flags |= FB_LMODE; + PaintBrowser(gmp, 0, &crow, &ccol); + } + + break; + } + + if(!(gmode&MDBRONLY)){ + unknown_command(c); + break; + } + +/* add subcommands to invoke pico and insert selected filename */ +/* perhaps: add subcmd to scroll command history */ + + tmp[0] = '\0'; + i = 0; + snprintf(child, sizeof(child), "%s%c%s", gmp->dname, C_FILESEP, + gmp->current->fname); + while(!i){ + static EXTRAKEYS opts[] = { + {"^X", N_("Add Name"), CTRL|'X', KS_NONE}, + {NULL, NULL, 0, KS_NONE}, + }; + + status = mlreply_utf8(_("Command to execute: "), + tmp, NLINE, QNORML, opts); + switch(status){ + case HELPCH: + emlwrite(_("\007No help yet!"), NULL); +/* remove break and sleep after help text is installed */ + sleep(3); + break; + case (CTRL|'X'): + strncat(tmp, child, sizeof(tmp)-strlen(tmp)-1); + tmp[sizeof(tmp)-1] = '\0'; + break; + case (CTRL|'L'): + PaintBrowser(gmp, 0, &crow, &ccol); + break; + case ABORT: + emlwrite(_("Command cancelled"), NULL); + i++; + break; + case FALSE: + case TRUE: + i++; + + if(tmp[0] == '\0'){ + emlwrite(_("No command specified"), NULL); + break; + } + + BrowserRunChild(tmp, gmp->dname); + PaintBrowser(gmp, 0, &crow, &ccol); + break; + default: + break; + } + } + + BrowserKeys(); + break; + + case 'd': /* delete */ + case 'D': + if(gmp->current->mode == FIODIR){ +/* BUG: if dir is empty it should be deleted */ + emlwrite(_("\007Can't delete a directory"), NULL); + break; + } + + if(gmode&MDSCUR){ /* not allowed! */ + emlwrite(_("Delete not allowed in restricted mode"),NULL); + break; + } + + snprintf(child, sizeof(child), "%s%c%s", gmp->dname, C_FILESEP, + gmp->current->fname); + + i = 0; + while(i++ < 2){ /* verify twice!! */ + if(i == 1){ + if(fexist(child, "w", (off_t *)NULL) != FIOSUC){ + strncpy(tmp, _("File is write protected! OVERRIDE"), sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + } + else + /* TRANSLATORS: This is a question, Delete file */ + snprintf(tmp, sizeof(tmp), _("Delete file \"%.*s\""), NLINE - 20, child); + } + else{ + strncpy(tmp, _("File CANNOT be UNdeleted! Really delete"), sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + } + + if((status = mlyesno_utf8(tmp, FALSE)) != TRUE){ + emlwrite((status == ABORT) + ? _("Delete Cancelled") + : _("File Not Deleted"), + NULL); + break; + } + } + + if(status == TRUE){ + if(our_unlink(child) < 0){ + eml.s = errstr(errno); + emlwrite(_("Delete Failed: %s"), &eml); + } + else{ /* fix up pointers and redraw */ + tp = gmp->current; + if(tp->next){ + gmp->current = tp->next; + if((tp->next->prev = tp->prev) != NULL) + tp->prev->next = tp->next; + } + else if(tp->prev) { + gmp->current = tp->prev; + if((tp->prev->next = tp->next) != NULL) + tp->next->prev = tp->prev; + } + + if(tp == gmp->head) + gmp->head = tp->next; + + tp->fname = NULL; + tp->next = tp->prev = NULL; + if(tp != gmp->current) + free((char *) tp); + + if((tp = FindCell(gmp, gmp->current->fname)) != NULL){ + gmp->current = tp; + PlaceCell(gmp, gmp->current, &row, &col); + } + + PaintBrowser(gmp, 1, &crow, &ccol); + mlerase(); + } + } + + BrowserKeys(); + break; + + case '?': /* HELP! */ + case (CTRL|'G'): + if(term.t_mrow == 0){ + if(km_popped == 0){ + km_popped = 2; + break; + } + } + + if(Pmaster){ + VARS_TO_SAVE *saved_state; + + saved_state = save_pico_state(); + (*Pmaster->helper)(Pmaster->browse_help, + _("Help for Browsing"), 1); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + } + else if(gmode&MDBRONLY) + pico_help(sa_BrowseHelpText, _("Browser Help"), 1); + else + pico_help(BrowseHelpText, _("Help for Browsing"), 1); + /* fall thru to repaint everything */ + + case (CTRL|'L'): + PaintBrowser(gmp, 0, &crow, &ccol); + break; + + case 'g': /* jump to a directory */ + case 'G': + + if(!(gmode&MDGOTO)) + goto Default; + + i = 0; + child[0] = '\0'; + + while(!i){ + + /* TRANSLATORS: A prompt asking for a directory to + move into */ + status = mlreply_utf8(_("Directory to go to: "), child, NLINE, QNORML, + NULL); + + switch(status){ + case HELPCH: + emlwrite(_("\007No help yet!"), NULL); + /* remove break and sleep after help text is installed */ + sleep(3); + break; + case (CTRL|'L'): + PaintBrowser(gmp, 0, &crow, &ccol); + break; + case ABORT: + emlwrite(_("Goto cancelled"), NULL); + i++; + break; + case FALSE: + case TRUE: + i++; + + if(*child == '\0'){ + strncpy(child, gethomedir(NULL), sizeof(child)); + child[sizeof(child)-1] = '\0'; + } + + if(!compresspath(gmp->dname, child, sizeof(child))){ + eml.s = child; + emlwrite(_("Invalid Directory: %s"), &eml); + break; + } + + if((gmode&MDSCUR) && homeless(child)){ + emlwrite(_("Restricted mode browsing limited to home directory"),NULL); + break; + } + + if((gmode&MDTREE) && !in_oper_tree(child)){ + eml.s = opertree; + emlwrite(_("\007 Can't go outside of %s in restricted mode"), + &eml); + break; + } + + if(isdir(child, (long *) NULL, NULL)){ + if((mp = getfcells(child, fb_flags)) == NULL){ + /* getfcells should explain what happened */ + i++; + break; + } + + zotmaster(&gmp); + gmp = mp; + PaintBrowser(gmp, 0, &crow, &ccol); + } + else{ + eml.s = child; + emlwrite(_("\007Not a directory: \"%s\""), &eml); + } + + break; + default: + break; + } + } + BrowserKeys(); + break; + + case 'a': /* Add */ + case 'A': + if(gmode&MDSCUR){ /* not allowed! */ + emlwrite(_("Add not allowed in restricted mode"),NULL); + break; + } + + i = 0; + child[0] = '\0'; + /* pass in default filename */ + if(fn && *fn){ + strncpy(child, fn, sizeof(child) - 1); + child[sizeof(child) - 1] = '\0'; + } + + while(!i){ + + switch(status=mlreply_utf8(_("Name of file to add: "), child, NLINE, + QFFILE, NULL)){ + case HELPCH: + emlwrite(_("\007No help yet!"), NULL); +/* remove break and sleep after help text is installed */ + sleep(3); + break; + case (CTRL|'L'): + PaintBrowser(gmp, 0, &crow, &ccol); + break; + case ABORT: + emlwrite(_("Add File Cancelled"), NULL); + i++; + break; + case FALSE: + /* + * Support default filename. A return of 'FALSE' means + * 'No change in filename' which is fine if we have + * provided a default. + * John Berthels + */ + /* FALLTHROUGH */ + case TRUE: + i++; + + if(child[0] == '\0'){ + emlwrite(_("No file named. Add Cancelled."), NULL); + break; + } + + if(!compresspath(gmp->dname, child, sizeof(child))){ + emlwrite(_("Too many ..'s in name"), NULL); + break; + } + + if((gmode&MDTREE) && !in_oper_tree(child)){ + eml.s = opertree; + emlwrite(_("\007Restricted mode allows Add in %s only"), + &eml); + break; + } + + if((status = fexist(child, "w", (off_t *)NULL)) == FIOSUC){ + snprintf(tmp, sizeof(tmp), _("File \"%.*s\" already exists!"), + NLINE - 20, child); + emlwrite(tmp, NULL); + break; + } + else if(status != FIOFNF){ + fioperr(status, child); + break; + } + + if(ffwopen(child, FALSE) != FIOSUC){ + /* ffwopen should've complained */ + break; + } + else{ /* highlight new file */ + ffclose(); + eml.s = child; + emlwrite(_("Added File \"%s\""), &eml); + + if((p = strrchr(child, C_FILESEP)) == NULL){ + emlwrite(_("Problems refiguring browser"), NULL); + break; + } + + *p = '\0'; + if(p != child){ + strncpy(tmp, child, sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + j = 0; + while((child[j++] = *++p) != '\0') + ; + } + else{ + strncpy(tmp, S_FILESEP, sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + } + + /* + * new file in same dir? if so, refigure files + * and redraw... + */ + if(!strcmp(tmp, gmp->dname)){ + if((mp = getfcells(gmp->dname, fb_flags)) == NULL) + /* getfcells should explain what happened */ + break; + + zotmaster(&gmp); + gmp = mp; + if((tp = FindCell(gmp, child)) != NULL){ + gmp->current = tp; + PlaceCell(gmp, gmp->current, &row, &col); + } + + PaintBrowser(gmp, 1, &crow, &ccol); + } + } + break; + default: + break; + } + } + + BrowserKeys(); + break; + + case 'c': /* copy */ + case 'C': + if(gmp->current->mode == FIODIR){ + emlwrite(_("\007Can't copy a directory"), NULL); + break; + } + + if(gmode&MDSCUR){ /* not allowed! */ + emlwrite(_("Copy not allowed in restricted mode"),NULL); + break; + } + + i = 0; + child[0] = '\0'; + + while(!i){ + + switch(status=mlreply_utf8(_("Name of new copy: "), child, NLINE, + QFFILE, NULL)){ + case HELPCH: + emlwrite(_("\007No help yet!"), NULL); +/* remove break and sleep after help text is installed */ + sleep(3); + break; + case (CTRL|'L'): + PaintBrowser(gmp, 0, &crow, &ccol); + break; + case ABORT: + emlwrite(_("Make Copy Cancelled"), NULL); + i++; + break; + case FALSE: + i++; + mlerase(); + break; + case TRUE: + i++; + + if(child[0] == '\0'){ + emlwrite(_("No destination, file not copied"), NULL); + break; + } + + if(!strcmp(gmp->current->fname, child)){ + emlwrite(_("\007Can't copy file on to itself!"), NULL); + break; + } + + if(!compresspath(gmp->dname, child, sizeof(child))){ + emlwrite(_("Too many ..'s in name"), NULL); + break; + } + + if((gmode&MDTREE) && !in_oper_tree(child)){ + eml.s = opertree; + emlwrite(_("\007Restricted mode allows Copy in %s only"), + &eml); + break; + } + + if((status = fexist(child, "w", (off_t *)NULL)) == FIOSUC){ + /* TRANSLATORS: A question: File exists! Replace? */ + snprintf(tmp, sizeof(tmp), _("File \"%.*s\" exists! OVERWRITE"), + NLINE - 20, child); + if((status = mlyesno_utf8(tmp, 0)) != TRUE){ + emlwrite((status == ABORT) + ? _("Make copy cancelled") + : _("File Not Renamed"), + NULL); + break; + } + } + else if(status != FIOFNF){ + fioperr(status, child); + break; + } + + snprintf(tmp, sizeof(tmp), "%s%c%s", gmp->dname, C_FILESEP, + gmp->current->fname); + + if(copy(tmp, child) < 0){ + /* copy() will report any error messages */ + break; + } + else{ /* highlight new file */ + eml.s = child; + emlwrite(_("File copied to %s"), &eml); + + if((p = strrchr(child, C_FILESEP)) == NULL){ + emlwrite(_("Problems refiguring browser"), NULL); + break; + } + + *p = '\0'; + strncpy(tmp, (p == child) ? S_FILESEP: child, sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + + /* + * new file in same dir? if so, refigure files + * and redraw... + */ + if(!strcmp(tmp, gmp->dname)){ + strncpy(child, gmp->current->fname, sizeof(child)); + child[sizeof(child)-1] = '\0'; + if((mp = getfcells(gmp->dname, fb_flags)) == NULL) + /* getfcells should explain what happened */ + break; + + zotmaster(&gmp); + gmp = mp; + if((tp = FindCell(gmp, child)) != NULL){ + gmp->current = tp; + PlaceCell(gmp, gmp->current, &row, &col); + } + + PaintBrowser(gmp, 1, &crow, &ccol); + } + } + break; + default: + break; + } + } + BrowserKeys(); + break; + + case 'r': /* rename */ + case 'R': + i = 0; + + if(!strcmp(gmp->current->fname, "..")){ + emlwrite(_("\007Can't rename \"..\""), NULL); + break; + } + + if(gmode&MDSCUR){ /* not allowed! */ + emlwrite(_("Rename not allowed in restricted mode"),NULL); + break; + } + + strncpy(child, gmp->current->fname, sizeof(child)); + child[sizeof(child)-1] = '\0'; + + while(!i){ + + switch(status=mlreply_utf8(_("Rename file to: "), child, NLINE, QFFILE, + NULL)){ + case HELPCH: + emlwrite(_("\007No help yet!"), NULL); +/* remove break and sleep after help text is installed */ + sleep(3); + break; + case (CTRL|'L'): + PaintBrowser(gmp, 0, &crow, &ccol); + break; + case ABORT: + emlwrite(_("Rename cancelled"), NULL); + i++; + break; + case FALSE: + case TRUE: + i++; + + if(child[0] == '\0' || status == FALSE){ + mlerase(); + break; + } + + if(!compresspath(gmp->dname, child, sizeof(child))){ + emlwrite(_("Too many ..'s in name"), NULL); + break; + } + + if((gmode&MDTREE) && !in_oper_tree(child)){ + eml.s = opertree; + emlwrite(_("\007Restricted mode allows Rename in %s only"), + &eml); + break; + } + + status = fexist(child, "w", (off_t *)NULL); + if(status == FIOSUC || status == FIOFNF){ + if(status == FIOSUC){ + snprintf(tmp, sizeof(tmp), _("File \"%.*s\" exists! OVERWRITE"), + NLINE - 20, child); + + if((status = mlyesno_utf8(tmp, FALSE)) != TRUE){ + emlwrite((status == ABORT) + ? _("Rename cancelled") + : _("Not Renamed"), + NULL); + break; + } + } + + snprintf(tmp, sizeof(tmp), "%s%c%s", gmp->dname, C_FILESEP, + gmp->current->fname); + + if(our_rename(tmp, child) < 0){ + eml.s = errstr(errno); + emlwrite(_("Rename Failed: %s"), &eml); + } + else{ + if((p = strrchr(child, C_FILESEP)) == NULL){ + emlwrite(_("Problems refiguring browser"), NULL); + break; + } + + *p = '\0'; + strncpy(tmp, (p == child) ? S_FILESEP: child, sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + + if((mp = getfcells(tmp, fb_flags)) == NULL) + /* getfcells should explain what happened */ + break; + + zotmaster(&gmp); + gmp = mp; + + if((tp = FindCell(gmp, ++p)) != NULL){ + gmp->current = tp; + PlaceCell(gmp, gmp->current, &row, &col); + } + + PaintBrowser(gmp, 1, &crow, &ccol); + mlerase(); + } + } + else{ + fioperr(status, child); + } + break; + default: + break; + } + } + BrowserKeys(); + break; + + case 'v': /* stand-alone */ + case 'V': /* browser "view" */ + case 's': /* user "select" */ + case 'S': + case (CTRL|'M'): + Selected: + + if(((new_c == 'S' || new_c == 's') && (gmode&MDBRONLY)) + || ((new_c == 'V' || new_c == 'v') && !(gmode&MDBRONLY))) + goto Default; + + if(gmp->current->mode == FIODIR){ + *child = '\0'; + strncpy(tmp, gmp->dname, sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + p = gmp->current->fname; + if(p[0] == '.' && p[1] == '.' && p[2] == '\0'){ + if((p=strrchr(tmp, C_FILESEP)) != NULL){ + *p = '\0'; + + if((gmode&MDTREE) && !in_oper_tree(tmp)){ + eml.s = PARENTDIR; + emlwrite( + _("\007Can't visit %s in restricted mode"), + &eml); + break; + } + + strncpy(child, &p[1], sizeof(child)); + child[sizeof(child)-1] = '\0'; + + if +#if defined(DOS) || defined(OS2) + (p == tmp || (tmp[1] == ':' && tmp[2] == '\0')) +#else + (p == tmp) +#endif + { /* is it root? */ +#if defined(DOS) || defined(OS2) + if(*child){ + strncat(tmp, S_FILESEP, sizeof(tmp)-strlen(tmp)-1); + tmp[sizeof(tmp)-1] = '\0'; + } +#else + if(*child){ + strncpy(tmp, S_FILESEP, sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + } +#endif + else{ + emlwrite(_("\007Can't move up a directory"), + NULL); + break; + } + } + } + } + else if((fb_flags&FB_SAVE) && p[0] == '.' && p[1] == '\0'){ + if ((strlen(gmp->dname) < dirlen) && + (strlen(gmp->current->fname) < fnlen)){ + strncpy(dir, gmp->dname, dirlen); + dir[dirlen-1] = '\0'; + } + + zotmaster(&gmp); + return(0); /* just change the directory, still return no selection */ + } + else{ + if(tmp[1] != '\0'){ /* were in root? */ + strncat(tmp, S_FILESEP, sizeof(tmp)-strlen(tmp)-1); + tmp[sizeof(tmp)-1] = '\0'; + } + + strncat(tmp, gmp->current->fname, sizeof(tmp)-strlen(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + } + + if((mp = getfcells(tmp, fb_flags)) == NULL) + /* getfcells should explain what happened */ + break; + + if(gmp->flags & FB_LMODE){ + mp->flags |= FB_LMODE; + mp->lm = gmp->lm; + gmp->lm = NULL; + } + + zotmaster(&gmp); + gmp = mp; + tp = NULL; + + if(*child){ + if((tp = FindCell(gmp, child)) != NULL){ + gmp->current = tp; + PlaceCell(gmp, gmp->current, &row, &col); + } + else{ + eml.s = child; + emlwrite(_("\007Problem finding dir \"%s\""), &eml); + } + } + + PaintBrowser(gmp, 0, &crow, &ccol); + if(!*child){ + char b[100]; + + snprintf(b, sizeof(b), "Select/View \" .. %s %s\" to return to previous directory.", PARENTDIR, DIRWORD); + emlwrite(b, NULL); + } + + break; + } + else if(gmode&MDBRONLY){ + snprintf(child, sizeof(child), "%s%c%s", gmp->dname, C_FILESEP, + gmp->current->fname); + + if(LikelyASCII(child)){ + snprintf(tmp, sizeof(tmp), "%s \'%s\'", + (envp = (char *) getenv("PAGER")) + ? envp : BROWSER_PAGER, child); + BrowserRunChild(tmp, gmp->dname); + PaintBrowser(gmp, 0, &crow, &ccol); + } + + break; + } + else{ /* just return */ + if(gmp->flags & FB_LMODEPOS){ + + if(!lmreturn){ + emlwrite("Programming error, called FileBrowse with LMODEPOS but no lmreturn", NULL); + zotmaster(&gmp); + return(-1); + } + + /* user actually used ListMode, the list is finished */ + if(gmp->flags & FB_LMODE){ + if(!gmp->lm){ + (*term.t_beep)(); + emlwrite(_("No files are selected, use \"X\" to mark files for selection"), NULL); + break; + } + + *lmreturn = gmp->lm; + gmp->lm = NULL; + } + else{ /* construct an lmreturn for user */ + LMLIST *new; + size_t flen, dlen; + + if((new=(LMLIST *)malloc(sizeof(*new))) == NULL + || (new->fname=malloc(gmp->current->fname ? (flen=strlen(gmp->current->fname))+1 : 1)) == NULL + || (new->dir=malloc((dlen=strlen(gmp->dname))+1)) == NULL){ + emlwrite("\007Can't malloc space for filename", NULL); + return(-1); + } + + strncpy(new->fname, + gmp->current->fname ? gmp->current->fname : "", flen); + new->fname[flen] = '\0'; + strncpy(new->dir, gmp->dname, dlen); + new->dir[dlen] = '\0'; + strncpy(new->size, gmp->current->size, sizeof(new->size)); + new->size[sizeof(new->size)-1] = '\0'; + new->next = NULL; + *lmreturn = new; + } + + zotmaster(&gmp); + return(1); + } + + if ((strlen(gmp->dname) < dirlen) && + (strlen(gmp->current->fname) < fnlen)){ + strncpy(dir, gmp->dname, dirlen); + dir[dirlen-1] = '\0'; + strncpy(fn, gmp->current->fname, fnlen); + fn[fnlen-1] = '\0'; + } + else { + zotmaster(&gmp); + return(-1); + } + if(sz != NULL){ /* size uninteresting */ + strncpy(sz, gmp->current->size, szlen); + sz[szlen-1] = '\0'; + } + + zotmaster (&gmp); + return (1); + } + break; + + case 'w': /* Where is */ + case 'W': + case (CTRL|'W'): + i = 0; + + while(!i){ + + switch(readpattern(_("File name to find"), FALSE)){ + case HELPCH: + emlwrite(_("\007No help yet!"), NULL); +/* remove break and sleep after help text is installed */ + sleep(3); + break; + case (CTRL|'L'): + PaintBrowser(gmp, 0, &crow, &ccol); + break; + case (CTRL|'Y'): /* first first cell */ + for(tp = gmp->top; tp->prev; tp = tp->prev) + ; + + i++; + /* fall thru to repaint */ + case (CTRL|'V'): + if(!i){ + do{ + tp = gmp->top; + if((i = term.t_nrow - term.t_mrow - 2) <= 0) + break; + + while(i-- && tp->next){ + j = 0; + while(++j <= gmp->fpl && tp->next) + tp = tp->next; + } + + if(i < 0) + gmp->top = tp; + } + while(tp->next); + + emlwrite(_("Searched to end of directory"), NULL); + } + else + emlwrite(_("Searched to start of directory"), NULL); + + if(tp){ + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = tp; + if(PlaceCell(gmp, gmp->current, &row, &col)){ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + } + + i++; /* make sure we jump out */ + break; + case ABORT: + emlwrite(_("Whereis cancelled"), NULL); + i++; + break; + case FALSE: + mlerase(); + i++; + break; + case TRUE: + { + char *utf8 = NULL; + + if(pat && pat[0]) + utf8 = ucs4_to_utf8_cpystr(pat); + + if(utf8 && (tp = FindCell(gmp, utf8)) != NULL){ + PlaceCell(gmp, gmp->current, &row, &col); + PaintCell(row, col, gmp->cpf, gmp->current, 0); + gmp->current = tp; + + if(PlaceCell(gmp, tp, &row, &col)){ /* top changed */ + PaintBrowser(gmp, 1, &crow, &ccol); + } + else{ + PaintCell(row, col, gmp->cpf, gmp->current, 1); + crow = row; + ccol = col; + } + mlerase(); + } + else{ + eml.s = utf8; + emlwrite(_("\"%s\" not found"), &eml); + } + + if(utf8) + fs_give((void **) &utf8); + } + + i++; + break; + default: + break; + } + } + + BrowserKeys(); + break; + + case (CTRL|'\\') : +#if defined MOUSE && !defined(_WINDOWS) + toggle_xterm_mouse(0,1); +#else + unknown_command(c); +#endif + break; + + case (CTRL|'Z'): + if(gmode&MDSSPD){ + bktoshell(0, 1); + PaintBrowser(gmp, 0, &crow, &ccol); + break; + } /* fall thru with error! */ + + default: /* what? */ + Default: + unknown_command(c); + + case NODATA: /* no op */ + break; + } + } +} + + +/* + * getfcells - make a master browser struct and fill it in + * return NULL if there's a problem. + */ +struct bmaster * +getfcells(char *dname, int fb_flags) +{ + int i, /* various return codes */ + flength, + nentries = 0; /* number of dir ents */ + off_t attsz; + char *np, /* names of files in dir */ + *dcp, /* to add file to path */ + *tmpstr, + **filtnames, /* array filtered names */ + errbuf[NLINE]; + struct fcell *ncp, /* new cell pointer */ + *tcp; /* trailing cell ptr */ + struct bmaster *mp; + EML eml; + + errbuf[0] = '\0'; + if((mp=(struct bmaster *)malloc(sizeof(struct bmaster))) == NULL){ + emlwrite("\007Can't malloc space for master filename cell", NULL); + return(NULL); + } + + memset(mp, 0, sizeof(*mp)); + + if(dname[0] == '.' && dname[1] == '\0'){ /* remember this dir */ + if(!getcwd(mp->dname, 256)) + mp->dname[0] = '\0'; + } + else if(dname[0] == '.' && dname[1] == '.' && dname[2] == '\0'){ + if(!getcwd(mp->dname, 256)) + mp->dname[0] = '\0'; + else{ + if((np = (char *)strrchr(mp->dname, C_FILESEP)) != NULL) + if(np != mp->dname) + *np = '\0'; + } + } + else{ + strncpy(mp->dname, dname, sizeof(mp->dname)); + mp->dname[sizeof(mp->dname)-1] = '\0'; + } + + mp->head = mp->top = NULL; + mp->cpf = mp->fpl = 0; + mp->longest = 5; /* .. must be labeled! */ + mp->flags = fb_flags; + + eml.s = mp->dname; + emlwrite("Building file list of %s...", &eml); + + if((mp->names = getfnames(mp->dname, NULL, &nentries, errbuf, sizeof(errbuf))) == NULL){ + free((char *) mp); + if(*errbuf) + emlwrite(errbuf, NULL); + + return(NULL); + } + + /* + * this is the fun part. build an array of pointers to the fnames we're + * interested in (i.e., do any filtering), then pass that off to be + * sorted before building list of cells... + * + * right now default filtering on ".*" except "..", but this could + * easily be made a user option later on... + */ + if((filtnames=(char **)malloc((nentries+1) * sizeof(char *))) == NULL){ + emlwrite("\007Can't malloc space for name array", NULL); + zotmaster(&mp); + return(NULL); + } + + i = 0; /* index of filt'd array */ + np = mp->names; + while(nentries--){ + int ii; + int width; + + /* + * Filter dot files? Always filter ".", never filter "..", + * and sometimes fitler ".*"... + */ + if(*np == '.' && (!(*(np+1) == '.' && *(np+2) == '\0') + && !(*(np+1) == '\0' && (fb_flags&FB_SAVE))) + && (*(np+1) == '\0' || !(gmode & MDDOTSOK))){ + np += strlen(np) + 1; + continue; + } + + filtnames[i++] = np; + + ii = (int) strlen(np); + if((width = (int) utf8_width(np)) > mp->longest) + mp->longest = width; /* remember longest */ + + np += ii + 1; /* advance name pointer */ + } + + nentries = i; /* new # of entries */ + + /* + * sort files case independently + */ + qsort((qsort_t *)filtnames, (size_t)nentries, sizeof(char *), sstrcasecmp); + + /* + * this is so we use absolute path names for stats. + * remember: be careful using dname as directory name, and fix mp->dname + * when we're done + */ + dcp = (char *) strchr(mp->dname, '\0'); + if(dcp == mp->dname || dcp[-1] != C_FILESEP){ + dcp[0] = C_FILESEP; + dcp[1] = '\0'; + } + else + dcp--; + + i = 0; + while(nentries--){ /* stat filtered files */ + /* get a new cell */ + if((ncp=(struct fcell *)malloc(sizeof(struct fcell))) == NULL){ + emlwrite("\007Can't malloc cells for browser!", NULL); + zotfcells(mp->head); /* clean up cells */ + free((char *) filtnames); + free((char *) mp); + return(NULL); /* bummer. */ + } + + ncp->next = ncp->prev = NULL; + + if(mp->head == NULL){ /* tie it onto the list */ + mp->head = mp->top = mp->current = ncp; + } + else{ + tcp->next = ncp; + ncp->prev = tcp; + } + + tcp = ncp; + + /* fill in the new cell */ + ncp->fname = filtnames[i++]; + + /* fill in file's mode */ + if ((flength = strlen(ncp->fname) + 1 + strlen(dname)) < sizeof(mp->dname)){ + strncpy(&dcp[1], ncp->fname, sizeof(mp->dname)-(dcp+1-mp->dname)); /* use absolute path! */ + mp->dname[sizeof(mp->dname)-1] = '\0'; + tmpstr = mp->dname; + } + else{ + if((tmpstr = (char *)malloc((flength+1)*sizeof(char))) == NULL){ + emlwrite("\007Can't malloc cells for temp buffer!", NULL); + zotfcells(mp->head); /* clean up cells */ + free((char *) filtnames); + free((char *) mp); + return(NULL); /* bummer. */ + } + + strncpy(tmpstr, dname, flength); + tmpstr[flength] = '\0'; + tmpstr = strncat(tmpstr, S_FILESEP, flength+1-1-strlen(tmpstr)); + tmpstr[flength] = '\0'; + tmpstr = strncat(tmpstr, ncp->fname, flength+1-1-strlen(tmpstr)); + tmpstr[flength] = '\0'; + } + + switch(fexist(tmpstr, "t", &attsz)){ + case FIODIR : + ncp->mode = FIODIR; + snprintf(ncp->size, sizeof(ncp->size), "(%s%s%s)", + (ncp->fname[0] == '.' && ncp->fname[1] == '.' + && ncp->fname[2] == '\0') ? PARENTDIR : + ((fb_flags&FB_SAVE) && ncp->fname[0] == '.' && ncp->fname[1] == '\0' + ? SELECTWORD : ""), + (ncp->fname[0] == '.' && ncp->fname[1] == '.' + && ncp->fname[2] == '\0') ? " " : + ((fb_flags&FB_SAVE) && ncp->fname[0] == '.' && ncp->fname[1] == '\0' + ? " " : ""), + DIRWORD); + break; + + case FIOSYM : + ncp->mode = FIOSYM; + strncpy(ncp->size, "--", sizeof(ncp->size)); + ncp->size[sizeof(ncp->size)-1] = '\0'; + break; + + default : + ncp->mode = FIOSUC; /* regular file */ + strncpy(ncp->size, prettysz(attsz), sizeof(ncp->size)); + ncp->size[sizeof(ncp->size)-1] = '\0'; + break; + } + + if (flength >= NLINE) + free((char *) tmpstr); + } + + dcp[(dcp == mp->dname) ? 1 : 0] = '\0'; /* remember to cap dname */ + free((char *) filtnames); /* 'n blast filt'd array*/ + + percdircells(mp); + layoutcells(mp); + if(strlen(mp->dname) < sizeof(browse_dir)){ + strncpy(browse_dir, mp->dname, sizeof(browse_dir)); + browse_dir[sizeof(browse_dir)-1] = '\0'; + } + else + browse_dir[0] = '\0'; + + return(mp); +} + + +int +fcell_is_selected(struct fcell *cell, struct bmaster *mp) +{ + LMLIST *lm; + + if(cell && cell->fname){ + for(lm = mp ? mp->lm : NULL; lm; lm = lm->next){ + /* directory has to match */ + if(!((mp->dname[0] == '\0' && (!lm->dir || lm->dir[0] =='\0')) + || (mp->dname[0] != '\0' && lm->dir && lm->dir[0] !='\0' + && !strcmp(mp->dname, lm->dir)))) + continue; + + if(lm->fname && !strcmp(cell->fname, lm->fname)) + return(1); + } + } + + return(0); +} + + +/* + * Adds a new name to the head of the lmlist + */ +void +add_cell_to_lmlist(struct fcell *cell, struct bmaster *mp) +{ + LMLIST *new; + size_t flen, dlen; + + if(mp && cell && cell->fname && cell->fname[0]){ + if((new=(LMLIST *)malloc(sizeof(*new))) == NULL || + (new->fname=malloc(sizeof(char)*((flen=strlen(cell->fname))+1))) == NULL || + (new->dir=malloc(sizeof(char)*((dlen=strlen(mp->dname))+1))) == NULL){ + emlwrite("\007Can't malloc space for filename", NULL); + return; + } + + strncpy(new->fname, cell->fname, flen); + new->fname[flen] = '\0'; + strncpy(new->dir, mp->dname, dlen); + new->dir[dlen] = '\0'; + new->size[0] = '\0'; + if(cell->size[0]){ + strncpy(new->size, cell->size, sizeof(new->size)); + new->size[sizeof(new->size)-1] = '\0'; + } + + new->next = mp->lm; + mp->lm = new; + } +} + + +/* + * Deletes a name from the lmlist + */ +void +del_cell_from_lmlist(struct fcell *cell, struct bmaster *mp) +{ + LMLIST *lm, *lmprev = NULL; + + if(mp && cell && cell->fname && cell->fname[0]) + for(lm = mp ? mp->lm : NULL; lm; lm = lm->next){ + if(lm->fname && strcmp(cell->fname, lm->fname) == 0){ + free((char *) lm->fname); + if(lm->dir) + free((char *) lm->dir); + + if(lmprev) + lmprev->next = lm->next; + else + mp->lm = lm->next; + + free((char *) lm); + + break; + } + + lmprev = lm; + } +} + + +/* + * PaintCell - print the given cell at the given location on the display + * the format of a printed cell is: + * + * " " + */ +void +PaintCell(int row, int col, + int sc, /* screen columns available for this cell */ + struct fcell *cell, int inverted) +{ + char buf1[NLINE], buf2[NLINE]; + char lbuf[5]; + int need, l_wid, f_wid, f_to_s_wid, s_wid; + UCS *ucs; + + if(cell == NULL) + return; + + l_wid = (gmp && ((gmp->flags & FB_LMODEPOS) > 4)) ? 4 : 0; + f_wid = utf8_width(cell->fname ? cell->fname : ""); + f_to_s_wid = 1; + s_wid = utf8_width(cell->size ? cell->size : ""); + + /* the two is the space between cell columns */ + sc = MIN(sc-2, term.t_ncol-col); + + if(l_wid){ + if(gmp && gmp->flags & FB_LMODE && cell->mode != FIODIR){ + /* + * We have to figure out here if it is selected or not + * and use that to write the X or space. + */ + lbuf[0] = '['; + if(fcell_is_selected(cell, gmp)) + lbuf[1] = 'X'; + else + lbuf[1] = ' '; + + lbuf[2] = ']'; + lbuf[3] = ' '; + } + else{ + lbuf[0] = lbuf[1] = lbuf[2] = lbuf[3] = ' '; + } + + lbuf[4] = '\0'; + } + + sc -= l_wid; + + need = f_wid+f_to_s_wid+s_wid; + + /* space between name and size */ + if(need < sc) + f_to_s_wid += (sc-need); + + /* + * If the width isn't enough to handle everything we're just putting a single + * space between fname and size and truncating on the right. That means that + * the sizes in the right hand column won't line up correctly when there is + * a lack of space. Instead, we opt for displaying as much info as possible + * in a slightly discordant way. + */ + + movecursor(row, col); + if(l_wid){ + ucs = utf8_to_ucs4_cpystr(lbuf); + if(ucs){ + pputs(ucs, 0); + fs_give((void **) &ucs); + } + } + + utf8_snprintf(buf1, sizeof(buf1), "%*.*w%*.*w%*.*w", + f_wid, f_wid, cell->fname ? cell->fname : "", + f_to_s_wid, f_to_s_wid, "", + s_wid, s_wid, cell->size ? cell->size : ""); + + utf8_snprintf(buf2, sizeof(buf2), "%*.*w", sc, sc, buf1); + + if(inverted) + (*term.t_rev)(1); + + ucs = utf8_to_ucs4_cpystr(buf2); + if(ucs){ + pputs(ucs, 0); + fs_give((void **) &ucs); + } + + if(inverted) + (*term.t_rev)(0); +} + + +/* + * PaintBrowse - with the current data, display the browser. if level == 0 + * paint the whole thing, if level == 1 just paint the cells + * themselves + */ +void +PaintBrowser(struct bmaster *mp, int level, int *row, int *col) +{ + int i, cl; + struct fcell *tp; + + if(!level){ + ClearBrowserScreen(); + BrowserAnchor(mp->dname); + } + + i = 0; + tp = mp->top; + cl = COMPOSER_TOP_LINE; /* current display line */ + while(tp){ + + PaintCell(cl, mp->cpf * i, mp->cpf, tp, tp == mp->current); + + if(tp == mp->current){ + if(row) + *row = cl; + + if(col) + *col = mp->cpf * i; + } + + if(++i >= mp->fpl){ + i = 0; + if(++cl > term.t_nrow-(term.t_mrow+1)) + break; + } + + tp = tp->next; + } + + if(level){ + while(cl <= term.t_nrow - (term.t_mrow+1)){ + if(!i) + movecursor(cl, 0); + peeol(); + movecursor(++cl, 0); + } + } + else{ + BrowserKeys(); + } +} + + +/* + * BrowserKeys - just paints the keyhelp at the bottom of the display + */ +void +BrowserKeys(void) +{ + menu_browse[QUIT_KEY].name = (gmode&MDBRONLY) ? "Q" : "E"; + /* TRANSLATORS: Brwsr is an abbreviation for Browser, which is + a command used to look through something */ + menu_browse[QUIT_KEY].label = (gmode&MDBRONLY) ? N_("Quit") : N_("Exit Brwsr"); + menu_browse[GOTO_KEY].name = (gmode&MDGOTO) ? "G" : NULL; + menu_browse[GOTO_KEY].label = (gmode&MDGOTO) ? N_("Goto") : NULL; + if(gmode & MDBRONLY){ + menu_browse[EXEC_KEY].name = "L"; + menu_browse[EXEC_KEY].label = N_("Launch"); + menu_browse[SELECT_KEY].name = "V"; + menu_browse[SELECT_KEY].label = "[" N_("View") "]"; + menu_browse[PICO_KEY].name = "E"; + menu_browse[PICO_KEY].label = N_("Edit"); + } + else{ + menu_browse[SELECT_KEY].name = "S"; + menu_browse[SELECT_KEY].label = "[" N_("Select") "]"; + menu_browse[PICO_KEY].name = "A"; + menu_browse[PICO_KEY].label = N_("Add"); + + if(gmp && gmp->flags & FB_LMODEPOS){ + if(gmp && gmp->flags & FB_LMODE){ /* ListMode is already on */ + menu_browse[EXEC_KEY].name = "X"; + menu_browse[EXEC_KEY].label = N_("Set/Unset"); + } + else{ /* ListMode is possible */ + menu_browse[EXEC_KEY].name = "L"; + menu_browse[EXEC_KEY].label = N_("ListMode"); + } + } + else{ /* No ListMode possible */ + menu_browse[EXEC_KEY].name = NULL; + menu_browse[EXEC_KEY].label = NULL; + } + } + + wkeyhelp(menu_browse); +} + + +/* + * layoutcells - figure out max length of cell and how many cells can + * go on a line of the display + */ +void +layoutcells(struct bmaster *mp) +{ + static int wid = -1; + /* + * Max chars/file. Actually this is max screen cells/file + * Longest name + "(parent dir)" + */ + if(wid < 0) + wid = MAX(utf8_width(PARENTDIR),utf8_width(SELECTWORD)) + utf8_width(DIRWORD); + + mp->cpf = mp->longest + wid + 3; + + if(mp->flags & FB_LMODEPOS) /* "[X] " */ + mp->cpf += 4; + + if(gmode & MDONECOL){ + mp->fpl = 1; + } + else{ + int i = 1; + + while(i*mp->cpf - 2 <= term.t_ncol) /* no force... */ + i++; /* like brute force! */ + + mp->fpl = i - 1; /* files per line */ + } + + if(mp->fpl == 0) + mp->fpl = 1; +} + + +/* + * percdircells - bubble all the directory cells to the top of the + * list. + */ +void +percdircells(struct bmaster *mp) +{ + struct fcell *dirlp, /* dir cell list pointer */ + *lp, *nlp; /* cell list ptr and next */ + + dirlp = NULL; + for(lp = mp->head; lp; lp = nlp){ + nlp = lp->next; + if(lp->mode == FIODIR){ + if(lp->prev) /* clip from list */ + lp->prev->next = lp->next; + + if(lp->next) + lp->next->prev = lp->prev; + + if((lp->prev = dirlp) != NULL){ /* tie it into dir portion */ + if((lp->next = dirlp->next) != NULL) + lp->next->prev = lp; + + dirlp->next = lp; + dirlp = lp; + } + else{ + if((dirlp = lp) != mp->head) + dirlp->next = mp->head; + + if(dirlp->next) + dirlp->next->prev = dirlp; + + mp->head = mp->top = mp->current = dirlp; + } + } + } +} + + +/* + * PlaceCell - given a browser master and a cell, return row and col of the display that + * it should go on. + * + * return 1 if mp->top has changed, x,y relative to new page + * return 0 if otherwise (same page) + * return -1 on error + */ +int +PlaceCell(struct bmaster *mp, struct fcell *cp, int *x, int *y) +{ + int cl = COMPOSER_TOP_LINE; /* current line */ + int ci = 0; /* current index on line */ + int rv = 0; + int secondtry = 0; + struct fcell *tp; + + /* will cp fit on screen? */ + tp = mp->top; + while(1){ + if(tp == cp){ /* bingo! */ + *x = cl; + *y = ci * mp->cpf; + break; + } + + if((tp = tp->next) == NULL){ /* above top? */ + if(secondtry++){ + emlwrite("\007Internal error: can't find fname cell", NULL); + return(-1); + } + else{ + tp = mp->top = mp->head; /* try from the top! */ + cl = COMPOSER_TOP_LINE; + ci = 0; + rv = 1; + continue; /* start over! */ + } + } + + if(++ci >= mp->fpl){ /* next line? */ + ci = 0; + if(++cl > term.t_nrow-(term.t_mrow+1)){ /* next page? */ + ci = mp->fpl; /* tp is at bottom right */ + while(ci--) /* find new top */ + tp = tp->prev; + mp->top = tp; + ci = 0; + cl = COMPOSER_TOP_LINE; /* keep checking */ + rv = 1; + } + } + + } + + /* not on display! */ + return(rv); +} + + +/* + * zotfcells - clean up malloc'd cells of file names + */ +void +zotfcells(struct fcell *hp) +{ + struct fcell *tp; + + while(hp){ + tp = hp; + hp = hp->next; + tp->next = NULL; + free((char *) tp); + } +} + + +/* + * zotmaster - blast the browser master struct + */ +void +zotmaster(struct bmaster **mp) +{ + if(mp && *mp){ + zotfcells((*mp)->head); /* free cells */ + zotlmlist((*mp)->lm); /* free lmlist */ + if((*mp)->names) + free((char *)(*mp)->names); /* free file names */ + + free((char *)*mp); /* free master */ + *mp = NULL; /* make double sure */ + } +} + + +/* + * FindCell - starting from the current cell find the first occurance of + * the given string wrapping around if necessary + */ +struct fcell * +FindCell(struct bmaster *mp, char *utf8string) +{ + struct fcell *tp, *fp; + + if(*utf8string == '\0') + return(NULL); + + fp = NULL; + tp = mp->current->next; + + while(tp && !fp){ + if(sisin(tp->fname, utf8string)) + fp = tp; + else + tp = tp->next; + } + + tp = mp->head; + while(tp != mp->current && !fp){ + if(sisin(tp->fname, utf8string)) + fp = tp; + else + tp = tp->next; + } + + return(fp); +} + + +/* + * sisin - case insensitive substring matching function + * + * We can't really do case-insensitive for non-ascii, so restrict + * that to ascii characters. The strings will be utf8. + */ +int +sisin(char *bigstr, char *utf8substr) +{ + register int j; + + while(*bigstr){ + j = 0; + while(bigstr[j] == utf8substr[j] + || ((unsigned char)bigstr[j] < 0x80 && (unsigned char)utf8substr[j] < 0x80 + && toupper((unsigned char)bigstr[j]) == toupper((unsigned char)utf8substr[j]))) + if(utf8substr[++j] == '\0') /* bingo! */ + return(1); + + bigstr++; + } + + return(0); +} + + +/* + * set_browser_title - + */ +void +set_browser_title(char *s) +{ + browser_title = s; +} + + +/* + * BrowserAnchor - draw the browser's anchor line. + */ +void +BrowserAnchor(char *utf8dir) +{ + char *pdir; + char titlebuf[NLINE]; + char buf[NLINE]; + char dirbuf[NLINE]; + char *dots = "..."; + char *br = "BROWSER"; + char *dir = "Dir: "; + UCS *ucs; + int need, extra, avail; + int title_wid, b_wid, t_to_b_wid, b_to_d_wid, d_wid, dot_wid, dir_wid, after_dir_wid; + COLOR_PAIR *lastc = NULL; + + if(!utf8dir) + utf8dir = ""; + + pdir = utf8dir; + + if(browser_title) + snprintf(titlebuf, sizeof(buf), " %s", browser_title); + else if(Pmaster) + snprintf(titlebuf, sizeof(buf), " ALPINE %s", Pmaster->pine_version); + else + snprintf(titlebuf, sizeof(buf), " UW PICO %s", (gmode&MDBRONLY) ? "BROWSER" : version); + + title_wid = utf8_width(titlebuf); + t_to_b_wid = 15; + b_wid = utf8_width(br); + b_to_d_wid = 4; + d_wid = utf8_width(dir); + dot_wid = 0; + dir_wid = utf8_width(pdir); + + need = title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid+dir_wid; + + if(need > term.t_ncol){ + extra = need - term.t_ncol; + t_to_b_wid -= MIN(extra, 10); + need -= MIN(extra, 10); + + if(need > term.t_ncol){ + extra = need - term.t_ncol; + b_to_d_wid -= MIN(extra, 2); + need -= MIN(extra, 2); + } + + if(need > term.t_ncol){ + titlebuf[0] = titlebuf[1] = ' '; + titlebuf[2] = '\0'; + title_wid = utf8_width(titlebuf); + t_to_b_wid = 0; + b_to_d_wid = 4; + need = title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid+dir_wid; + if(need > term.t_ncol){ + extra = need - term.t_ncol; + b_to_d_wid -= MIN(extra, 2); + need -= MIN(extra, 2); + } + + need = title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid+dir_wid; + if(need > term.t_ncol){ + t_to_b_wid = 0; + b_wid = 0; + b_to_d_wid = 0; + } + + need = title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid+dir_wid; + if(need > term.t_ncol) + d_wid = 0; + + need = title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid+dir_wid; + + if(need > term.t_ncol && dir_wid > 0){ + dot_wid = utf8_width(dots); + need = title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid+dir_wid; + while(need > term.t_ncol && (pdir = strchr(pdir+1, C_FILESEP))){ + dir_wid = utf8_width(pdir); + need = title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid+dir_wid; + } + + if(!pdir){ /* adjust other widths to fill up space */ + avail = term.t_ncol - (title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid); + if(avail > 2) + utf8_to_width_rhs(dirbuf, utf8dir, sizeof(dirbuf), avail); + else + dirbuf[0] = '\0'; + + pdir = dirbuf; + } + + dir_wid = utf8_width(pdir); + } + } + } + + extra = term.t_ncol - (title_wid+t_to_b_wid+b_wid+b_to_d_wid+d_wid+dot_wid+dir_wid); + + if(extra >= 0) + after_dir_wid = extra; + else + after_dir_wid = 0; + + utf8_snprintf(buf, sizeof(buf), "%*.*w%*.*w%*.*w%*.*w%*.*w%*.*w%*.*w%*.*w", + title_wid, title_wid, titlebuf, + t_to_b_wid, t_to_b_wid, "", + b_wid, b_wid, br, + b_to_d_wid, b_to_d_wid, "", + d_wid, d_wid, dir, + dot_wid, dot_wid, dots, + dir_wid, dir_wid, pdir, + after_dir_wid, after_dir_wid, ""); + + /* just making sure */ + utf8_snprintf(titlebuf, sizeof(titlebuf), "%-*.*w", term.t_ncol, term.t_ncol, buf); + + movecursor(0, 0); + if(Pmaster && Pmaster->colors && Pmaster->colors->tbcp + && pico_is_good_colorpair(Pmaster->colors->tbcp)){ + lastc = pico_get_cur_color(); + (void) pico_set_colorp(Pmaster->colors->tbcp, PSC_NONE); + } + else + (*term.t_rev)(1); + + ucs = utf8_to_ucs4_cpystr(titlebuf); + if(ucs){ + pputs(ucs, 0); + fs_give((void **) &ucs); + } + + if(lastc){ + (void) pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(0); +} + + +/* + * ResizeBrowser - handle a resize event + */ +int +ResizeBrowser(void) +{ + if(gmp){ + layoutcells(gmp); + PaintBrowser(gmp, 0, NULL, NULL); + return(1); + } + else + return(0); +} + + +void +ClearBrowserScreen(void) +{ + int i; + + for(i = 0; i <= term.t_nrow; i++){ /* clear screen */ + movecursor(i, 0); + peeol(); + } +} + + +void +BrowserRunChild(char *child, char *dir) +{ + int status; + char tmp[NLINE]; + time_t t_in, t_out; + + ClearBrowserScreen(); + movecursor(0, 0); + (*term.t_close)(); + if(!isdir(dir, NULL, &t_in)) + t_in = 0; + + fflush(stdout); + status = system(child); + (*term.t_open)(); + if(t_in && isdir(dir, NULL, &t_out) && t_in < t_out){ + struct bmaster *mp; + + if((mp = getfcells(dir, 0)) != NULL){ + zotmaster(&gmp); + gmp = mp; + } + /* else getfcells should explain what happened */ + } + + /* complain about non-zero exit status */ + if((status >> 8) & 0xff){ + + movecursor(term.t_nrow - 1, 0); + snprintf(tmp, sizeof(tmp), "[ \"%.30s\" exit with error value: %d ]", + child, (status >> 8) & 0xff); + pputs_utf8(tmp, 1); + movecursor(term.t_nrow, 0); + pputs_utf8("[ Hit RETURN to continue ]", 1); + fflush(stdout); + + while(GetKey() != (CTRL|'M')){ + (*term.t_beep)(); + fflush(stdout); + } + } +} + + +/* + * imitate pc-pine memory for where we last called the file browser. + */ +void +p_chdir(struct bmaster *mp) +{ + if(mp && mp->dname) + chdir(mp->dname); +} +#else /* _WINDOWS */ + + +/* + * pico_file_browse - Exported version of FileBrowse below. + */ +int +pico_file_browse(PICO *pdata, char *dir, size_t dirlen, char *fn, size_t fnlen, + char *sz, size_t szlen, int flags) +{ + return(FileBrowse(dir, dirlen, fn, fnlen, sz, szlen, flags, NULL)); +} + +int +ResizeBrowser (void) +{ + return (0); +} + +/* + * FileBrowse - Windows version of above function + */ +int +FileBrowse(char *dir, size_t dirlen, char *fn, size_t fnlen, + char *sz, size_t szlen, int fb_flags, LMLIST **lmreturn) +{ + struct stat sbuf; + int rc; + char lfn[NLINE]; + + if (fb_flags & FB_SAVE){ + rc = mswin_savefile(dir, dirlen, fn, MIN(dirlen,fnlen)); + } + else{ + *fn = '\0'; /* No initial file names for + * open. */ + if(fb_flags & FB_LMODEPOS){ + /* + * We're going to allow multiple filenames to be returned so + * we don't want to use the passed in fn for that. Instead, make + * a bigger space and use that. + */ + char f[20000]; + size_t flen, dlen; + + memset(f, 0, sizeof(f)); + + rc = mswin_multopenfile(dir, dirlen, f, sizeof(f), + (fb_flags & FB_ATTACH) ? NULL : "Text Files (*.txt)#*.txt"); + + if(rc == 1){ + LMLIST *lmhead = NULL, *new; + char *p; + + /* + * Build an LMLIST to return to the caller. + */ + for(p = f; *p; p += strlen(p)+1){ + flen = strlen(p); + dlen = strlen(dir ? dir : ""); + new = (LMLIST *) fs_get(sizeof(*new)); + new->fname = (char *) fs_get((flen+1) * sizeof(char)); + new->dir = (char *) fs_get((dlen+1) * sizeof(char)); + + strncpy(new->fname, p, flen); + new->fname[flen] = '\0'; + strncpy(new->dir, dir ? dir : "", dlen); + new->dir[dlen] = '\0'; + + /* build full path to stat file. */ + if((strlen(new->dir) + strlen(S_FILESEP) + + strlen(new->fname) + 1) < sizeof(lfn)){ + strncpy(lfn, new->dir, sizeof(lfn)); + lfn[sizeof(lfn)-1] = '\0'; + strncat(lfn, S_FILESEP, sizeof(lfn)-strlen(lfn)-1); + strncat(lfn, new->fname, sizeof(lfn)-strlen(lfn)-1); + lfn[sizeof(lfn)-1] = '\0'; + if(our_stat(lfn, &sbuf) < 0) + strncpy(new->size, "0", 32); + else + strncpy(new->size, prettysz((off_t)sbuf.st_size), 32); + + new->size[32-1] = '\0'; + } + + new->next = lmhead; + lmhead = new; + } + + *lmreturn = lmhead; + return(1); + } + } + else + rc = mswin_openfile (dir, dirlen, fn, fnlen, + (fb_flags & FB_ATTACH) ? NULL : "Text Files (*.txt)#*.txt"); + } + + if(rc == 1){ + if(sz != NULL){ + /* build full path to stat file. */ + if((strlen(dir) + strlen(S_FILESEP) + strlen(fn) + 1) > NLINE) + return -1; + + strncpy(lfn, dir, sizeof(lfn)); + lfn[sizeof(lfn)-1] = '\0'; + strncat(lfn, S_FILESEP, sizeof(lfn)-strlen(lfn)-1); + lfn[sizeof(lfn)-1] = '\0'; + strncat(lfn, fn, sizeof(lfn)-strlen(lfn)-1); + lfn[sizeof(lfn)-1] = '\0'; + if(our_stat(lfn, &sbuf) < 0){ + strncpy(sz, "0", szlen); + sz[szlen-1] = '\0'; + } + else{ + strncpy(sz, prettysz ((off_t)sbuf.st_size), szlen); + sz[szlen-1] = '\0'; + } + } + + return(1); + } + + return(rc ? -1 : 0); +} + +#endif /* _WINDOWS */ + + +/* + * LikelyASCII - make a rough guess as to the displayability of the + * given file. + */ +int +LikelyASCII(char *file) +{ +#define LA_TEST_BUF 1024 +#define LA_LINE_LIMIT 300 + int n, i, line, rv = FALSE; + unsigned char buf[LA_TEST_BUF]; + FILE *fp; + EML eml; + + if((fp = our_fopen(file, "rb")) != NULL){ + clearerr(fp); + if((n = fread(buf, sizeof(char), LA_TEST_BUF * sizeof(char), fp)) > 0 + || !ferror(fp)){ + /* + * If we don't hit any newlines in a reasonable number, + * LA_LINE_LIMIT, of characters or the file contains NULLs, + * bag out... + */ + rv = TRUE; + for(i = line = 0; i < n; i++) + if((line = (buf[i] == '\n') ? 0 : line + 1) >= LA_LINE_LIMIT + || !buf[i]){ + rv = FALSE; + emlwrite(_("Can't display non-text file. Try \"Launch\"."), + NULL); + break; + } + } + else{ + eml.s = file; + emlwrite(_("Can't read file: %s"), &eml); + } + + fclose(fp); + } + else{ + eml.s = file; + emlwrite(_("Can't open file: %s"), &eml); + } + + return(rv); +} + + +void +zotlmlist(LMLIST *lm) +{ + LMLIST *tp; + + while(lm){ + if(lm->fname) + free(lm->fname); + + if(lm->dir) + free(lm->dir); + + tp = lm; + lm = lm->next; + tp->next = NULL; + free((char *) tp); + } +} + + +/* + * time_to_check - checks the current time against the last time called + * and returns true if the elapsed time is > below. + * Newmail won't necessarily check, but we want to give it + * a chance to check or do a keepalive. + */ +int +time_to_check(void) +{ + static time_t lasttime = 0L; + + if(!get_input_timeout()) + return(FALSE); + + if(time((time_t *) 0) - lasttime > (Pmaster ? (time_t)(FUDGE-10) : get_input_timeout())){ + lasttime = time((time_t *) 0); + return(TRUE); + } + else + return(FALSE); +} diff --git a/pico/buffer.c b/pico/buffer.c new file mode 100644 index 00000000..87423d28 --- /dev/null +++ b/pico/buffer.c @@ -0,0 +1,354 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: buffer.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 + * + * ======================================================================== + * + * Program: Buffer management routines + */ + +/* + * Buffer management. + * Some of the functions are internal, + * and some are actually attached to user + * keys. Like everyone else, they set hints + * for the display system. + */ +#include "headers.h" + +int sgetline(char **, int *, char *, int); + + +/* + * Look through the list of + * buffers. Return TRUE if there + * are any changed buffers. Buffers + * that hold magic internal stuff are + * not considered; who cares if the + * list of buffer names is hacked. + * Return FALSE if no buffers + * have been changed. + */ +int +anycb(void) +{ + register BUFFER *bp; + + bp = bheadp; + while (bp != NULL) { + if ((bp->b_flag&BFTEMP)==0 && (bp->b_flag&BFCHG)!=0) + return (TRUE); + bp = bp->b_bufp; + } + return (FALSE); +} + +/* + * Find a buffer, by name. Return a pointer + * to the BUFFER structure associated with it. If + * the named buffer is found, but is a TEMP buffer (like + * the buffer list) conplain. If the buffer is not found + * and the "cflag" is TRUE, create it. The "bflag" is + * the settings for the flags in in buffer. + */ +BUFFER * +bfind(char *bname, int cflag, int bflag) +{ + register BUFFER *bp; + register BUFFER *sb; /* buffer to insert after */ + register LINE *lp; + + bp = bheadp; + while (bp != NULL) { + if (strcmp(bname, bp->b_bname) == 0) { + if ((bp->b_flag&BFTEMP) != 0) { + mlwrite_utf8("Cannot select builtin buffer", NULL); + return (NULL); + } + return (bp); + } + bp = bp->b_bufp; + } + if (cflag != FALSE) { + if ((bp=(BUFFER *)malloc(sizeof(BUFFER))) == NULL) + return (NULL); + if ((lp=lalloc(0)) == NULL) { + free((char *) bp); + return (NULL); + } + /* find the place in the list to insert this buffer */ + if (bheadp == NULL || strcmp(bheadp->b_bname, bname) > 0) { + /* insert at the begining */ + bp->b_bufp = bheadp; + bheadp = bp; + } else { + sb = bheadp; + while (sb->b_bufp != NULL) { + if (strcmp(sb->b_bufp->b_bname, bname) > 0) + break; + sb = sb->b_bufp; + } + + /* and insert it */ + bp->b_bufp = sb->b_bufp; + sb->b_bufp = bp; + } + + /* and set up the other buffer fields */ + bp->b_active = TRUE; + bp->b_dotp = lp; + bp->b_doto = 0; + bp->b_markp = NULL; + bp->b_marko = 0; + bp->b_flag = bflag; + bp->b_mode = gmode; + bp->b_nwnd = 0; + bp->b_linep = lp; + strncpy(bp->b_fname, "", sizeof(bp->b_fname)); + bp->b_fname[sizeof(bp->b_fname)-1] = '\0'; + strncpy(bp->b_bname, bname, sizeof(bp->b_bname)); + bp->b_bname[sizeof(bp->b_bname)-1] = '\0'; + lp->l_fp = lp; + lp->l_bp = lp; + } + return (bp); +} + +/* + * This routine blows away all of the text + * in a buffer. If the buffer is marked as changed + * then we ask if it is ok to blow it away; this is + * to save the user the grief of losing text. The + * window chain is nearly always wrong if this gets + * called; the caller must arrange for the updates + * that are required. Return TRUE if everything + * looks good. + */ +int +bclear(BUFFER *bp) +{ + register LINE *lp; + register int s = FALSE; + + if(Pmaster){ + if ((bp->b_flag&BFTEMP) == 0 /* Not scratch buffer. */ + && (bp->b_flag&BFCHG) != 0){ /* Something changed */ + emlwrite("buffer lines not freed.", NULL); + return (s); + } + } + else{ + if ((bp->b_flag&BFTEMP) == 0 /* Not scratch buffer. */ + && (bp->b_flag&BFCHG) != 0 /* Something changed */ + /* TRANSLATORS: A question asking whether to forget about + the changes and revert to the unchanged version. */ + && (s=mlyesno_utf8(_("Discard changes"), -1)) != TRUE){ + return (s); + } + } + + bp->b_flag &= ~BFCHG; /* Not changed */ + while ((lp=lforw(bp->b_linep)) != bp->b_linep) + lfree(lp); + bp->b_dotp = bp->b_linep; /* Fix "." */ + bp->b_doto = 0; + bp->b_markp = NULL; /* Invalidate "mark" */ + bp->b_marko = 0; + return (TRUE); +} + + +/* + * packbuf - will pack up the main buffer in the buffer provided + * to be returned to the program that called pico. + * if need be, allocate memory for the new message. + * will also free the memory associated with the editor + * buffer, by calling zotedit. + */ +int +packbuf(char **buf, + int *blen, + int lcrlf) /* EOLs are local or CRLF */ +{ + register int i = 0; + register LINE *lp; + register int retval = 0; + register char *bufp; + register char *eobuf; + + if(anycb() != FALSE){ + + lp = lforw(curbp->b_linep); + do{ /* how many chars? */ + i += llength(lp); + /* + * add extra for new lines to be inserted later + */ + i += 2; + lp = lforw(lp); + } + while(lp != curbp->b_linep); + + if(i > *blen){ /* new buffer ? */ + /* + * don't forget to add one for the null terminator!!! + */ + if((bufp = (char *)malloc((i+1)*sizeof(char))) == NULL){ + zotedit(); /* bag it! */ + return(COMP_FAILED); + } + free(*buf); + *buf = bufp; + *blen = i; + } + else{ + bufp = *buf; + } + + eobuf = bufp + *blen; + lp = lforw(curbp->b_linep); /* First line. */ + do { + for (i = 0; i < llength(lp); i++){ /* copy into buffer */ + if((bufp+1) < eobuf){ + *bufp++ = (lp->l_text[i].c & 0xFF); + } + else{ + /* + * the idea is to malloc enough space for the new + * buffer... + */ + *bufp = '\0'; + zotedit(); + return(BUF_CHANGED|COMP_FAILED); + } + } + if(lcrlf){ + *bufp++ = '\n'; /* EOLs use local convention */ + } + else{ + *bufp++ = 0x0D; /* EOLs use net standard */ + *bufp++ = 0x0A; + } + lp = lforw(lp); + } + while (lp != curbp->b_linep); + if(lcrlf) + *--bufp = '\0'; + else + *bufp = '\0'; + retval = BUF_CHANGED; + } + + zotedit(); + return(retval); +} + + +/* + * readbuf - reads in a buffer. + */ +void +readbuf(char **buf) +{ + register LINE *lp1; + register LINE *lp2; + register BUFFER *bp; + register WINDOW *wp; + register int i; + register int s; + char *sptr; /* pointer into buffer string */ + int nbytes; + char line[NLINE]; + CELL ac; + + bp = curbp; + bp->b_flag &= ~(BFTEMP|BFCHG); + sptr = *buf; + ac.a = 0; + + while((s=sgetline(&sptr,&nbytes,line,NLINE)) == FIOSUC || s == FIOLNG){ + + if ((lp1=lalloc(nbytes)) == NULL) { + s = FIOERR; /* Keep message on the */ + break; /* display. */ + } + lp2 = lback(curbp->b_linep); + lp2->l_fp = lp1; + lp1->l_fp = curbp->b_linep; + lp1->l_bp = lp2; + curbp->b_linep->l_bp = lp1; + for (i=0; iw_wndp) { + if (wp->w_bufp == curbp) { + wheadp->w_linep = lforw(curbp->b_linep); + wheadp->w_dotp = lback(curbp->b_linep); + wheadp->w_doto = 0; + wheadp->w_markp = NULL; + wheadp->w_marko = 0; + wheadp->w_flag |= WFHARD; + } + } + + strncpy(bp->b_bname, "main", sizeof(bp->b_bname)); + bp->b_bname[sizeof(bp->b_bname)-1] = '\0'; + strncpy(bp->b_fname, "", sizeof(bp->b_fname)); + bp->b_fname[sizeof(bp->b_fname)-1] = '\0'; + + bp->b_dotp = bp->b_linep; + bp->b_doto = 0; +} + + +/* + * sgetline - copy characters from ibuf to obuf, ending at the first + * newline. return with ibuf pointing to first char after + * newline. + */ +int +sgetline(char **ibuf, int *nchars, char *obuf, int blen) +{ + register char *len; + register char *cbuf = *ibuf; + register char *bufp = obuf; + register int retval = FIOSUC; +#define CR '\015' +#define LF '\012' + + *nchars = 0; + if(*cbuf == '\0'){ + retval = FIOEOF; + } + else{ + len = obuf + blen; + while (*cbuf != CR && *cbuf != LF && *cbuf != '\0'){ + if(bufp < len){ + *bufp++ = *cbuf++; + (*nchars)++; + } + else{ + *bufp = '\0'; + retval = FIOLNG; + break; + } + } + } + *bufp = '\0'; /* end retured line */ + *ibuf = (*cbuf == CR) ? ++cbuf : cbuf; + *ibuf = (*cbuf == LF) ? ++cbuf : cbuf; + return(retval); +} diff --git a/pico/composer.c b/pico/composer.c new file mode 100644 index 00000000..c801d385 --- /dev/null +++ b/pico/composer.c @@ -0,0 +1,4823 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: composer.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 + * + * ======================================================================== + * + * Program: Pine composer routines + * + * NOTES: + * + * - composer.c is the composer for the PINE mail system + * + * - tabled 01/19/90 + * + * Notes: These routines aren't incorporated yet, because the composer as + * a whole still needs development. These are ideas that should + * be implemented in later releases of PINE. See the notes in + * pico.c concerning what needs to be done .... + * + * - untabled 01/30/90 + * + * Notes: Installed header line editing, with wrapping and unwrapping, + * context sensitive help, and other mail header editing features. + * + * - finalish code cleanup 07/15/91 + * + * Notes: Killed/yanked header lines use emacs kill buffer. + * Arbitrarily large headers are handled gracefully. + * All formatting handled by FormatLines. + * + * - Work done to optimize display painting 06/26/92 + * Not as clean as it should be, needs more thought + * + */ +#include "headers.h" + +#include "osdep/terminal.h" +#include "../pith/string.h" + +int InitEntryText(char *, struct headerentry *); +int HeaderOffset(int); +int HeaderFocus(int, int); +UCS LineEdit(int, UCS *); +int header_downline(int, int); +int header_upline(int); +int FormatLines(struct hdr_line *, char *, int, int, int); +int FormatSyncAttach(void); +UCS *ucs4_strqchr(UCS *, UCS, int *, int); +int ComposerHelp(int); +void NewTop(int); +void display_delimiter(int); +void zotentry(struct hdr_line *); +int InvertPrompt(int, int); +int partial_entries(void); +int physical_line(struct hdr_line *); +int strend(UCS *, UCS); +int KillHeaderLine(struct hdr_line *, int); +int SaveHeaderLines(void); +UCS *break_point(UCS *, int, UCS, int *); +int hldelete(struct hdr_line *); +int is_blank(int, int, int); +int zotcomma(UCS *); +struct hdr_line *first_hline(int *); +struct hdr_line *first_sel_hline(int *); +struct hdr_line *next_hline(int *, struct hdr_line *); +struct hdr_line *next_sel_hline(int *, struct hdr_line *); +struct hdr_line *prev_hline(int *, struct hdr_line *); +struct hdr_line *prev_sel_hline(int *, struct hdr_line *); +struct hdr_line *first_requested_hline(int *); +void fix_mangle_and_err(int *, char **, char *); +void mark_sticky(struct headerentry *); + + +/* + * definition header field array, structures defined in pico.h + */ +struct headerentry *headents; + + +/* + * structure that keeps track of the range of header lines that are + * to be displayed and other fun stuff about the header + */ +struct on_display ods; /* global on_display struct */ + +/* a pointer to the subject line. This is so that we do not have to compute + * the header line in every call. It saves a few CPU cycles + */ + +struct hdr_line *subject_line = NULL; + +/* + * useful macros + */ +#define HALLOC() (struct hdr_line *)malloc(sizeof(struct hdr_line)) +#define LINEWID() (((term.t_ncol - headents[ods.cur_e].prwid) > 0) ? ((unsigned) (term.t_ncol - headents[ods.cur_e].prwid)) : 0) +#define BOTTOM() (term.t_nrow - term.t_mrow) +#define FULL_SCR() (BOTTOM() - 3) +#define HALF_SCR() (FULL_SCR()/2) + +#ifdef MOUSE +/* + * Redefine HeaderEditor to install wrapper required for mouse event + * handling... + */ +#define HeaderEditor HeaderEditorWork +#endif /* MOUSE */ + +#define HDR_DELIM "----- Message Text -----" + +/* + * useful declarations + */ +static short delim_ps = 0; /* previous state */ +static short invert_ps = 0; /* previous state */ + + +static KEYMENU menu_header[] = { + /* TRANSLATORS: command key labels, Send means send the message + we are currently working on. */ + {"^G", N_("Get Help"), KS_SCREENHELP}, {"^X", N_("Send"), KS_SEND}, + /* TRANSLATORS: Rich Headers is a command to display more headers. It + is triggered with the ^R key. PrvPg stands for Previous Page. */ + {"^R", N_("Rich Hdr"), KS_RICHHDR}, {"^Y", N_("PrvPg/Top"), KS_PREVPAGE}, + /* TRANSLATORS: Cut Line means remove a line. Postpone means to save + a message being composed so that it can be worked on later. */ + {"^K", N_("Cut Line"), KS_CURPOSITION}, {"^O", N_("Postpone"), KS_POSTPONE}, + /* TRANSLATORS: Del Char is Delete Character */ + {"^C", N_("Cancel"), KS_CANCEL}, {"^D", N_("Del Char"), KS_NONE}, + /* TRANSLATORS: Next Page */ + {"^J", N_("Attach"), KS_ATTACH}, {"^V", N_("NxtPg/End"), KS_NEXTPAGE}, + /* TRANSLATORS: Undelete a line that was just deleted */ + {"^U", N_("UnDel Line"), KS_NONE}, {NULL, NULL} +}; +#define SEND_KEY 1 +#define RICH_KEY 2 +#define CUT_KEY 4 +#define PONE_KEY 5 +#define DEL_KEY 7 +#define ATT_KEY 8 +#define UDEL_KEY 10 +#define TO_KEY 11 + + +/* + * function key mappings for header editor + */ +static UCS ckm[12][2] = { + { F1, (CTRL|'G')}, + { F2, (CTRL|'C')}, + { F3, (CTRL|'X')}, + { F4, (CTRL|'D')}, + { F5, (CTRL|'R')}, + { F6, (CTRL|'J')}, + { F7, 0 }, + { F8, 0 }, + { F9, (CTRL|'K')}, + { F10, (CTRL|'U')}, + { F11, (CTRL|'O')}, + { F12, (CTRL|'T')} +}; + + +/* + * InitMailHeader - initialize header array, and set beginning editor row + * range. The header entry structure should look just like + * what is written on the screen, the vector + * (entry, line, offset) will describe the current cursor + * position in the header. + * + * Returns: TRUE if special header handling was requested, + * FALSE under standard default behavior. + */ +int +InitMailHeader(PICO *mp) +{ + char *addrbuf; + struct headerentry *he; + int rv; + + if(!mp->headents){ + headents = NULL; + return(FALSE); + } + + /* + * initialize some of on_display structure, others below... + */ + ods.p_ind = 0; + ods.p_line = COMPOSER_TOP_LINE; + ods.top_l = ods.cur_l = NULL; + + headents = mp->headents; + /*--- initialize the fields in the headerent structure ----*/ + for(he = headents; he->name != NULL; he++){ + he->hd_text = NULL; + he->display_it = he->display_it ? he->display_it : !he->rich_header; + if(he->is_attach) { + /*--- A lot of work to do since attachments are special ---*/ + he->maxlen = 0; + if(mp->attachments != NULL){ + char buf[NLINE]; + int attno = 0; + int l1, ofp, ofp1, ofp2; /* OverFlowProtection */ + size_t addrbuflen = 4 * NLINE; /* malloc()ed size of addrbuf */ + PATMT *ap = mp->attachments; + + ofp = NLINE - 35; + + addrbuf = (char *)malloc(addrbuflen); + addrbuf[0] = '\0'; + buf[0] = '\0'; + while(ap){ + if((l1 = strlen(ap->filename)) <= ofp){ + ofp1 = l1; + ofp2 = ofp - l1; + } + else{ + ofp1 = ofp; + ofp2 = 0; + } + + if(ap->filename){ + snprintf(buf, sizeof(buf), "%d. %.*s%s %s%s%s\"", + ++attno, + ofp1, + ap->filename, + (l1 > ofp) ? "...]" : "", + ap->size ? "(" : "", + ap->size ? ap->size : "", + ap->size ? ") " : ""); + + /* append description, escaping quotes */ + if(ap->description){ + char *dp = ap->description, *bufp = &buf[strlen(buf)]; + int escaped = 0; + + do + if(*dp == '\"' && !escaped){ + *bufp++ = '\\'; + ofp2--; + } + else if(escaped){ + *bufp++ = '\\'; + escaped = 0; + } + while(--ofp2 > 0 && (*bufp++ = *dp++)); + + *bufp = '\0'; + } + + snprintf(buf + strlen(buf), sizeof(buf)-strlen(buf), "\"%s", ap->next ? "," : ""); + + if(strlen(addrbuf) + strlen(buf) >= addrbuflen){ + addrbuflen += NLINE * 4; + if(!(addrbuf = (char *)realloc(addrbuf, addrbuflen))){ + emlwrite("\007Can't realloc addrbuf to %d bytes", + (void *) addrbuflen); + return(ABORT); + } + } + + strncat(addrbuf, buf, addrbuflen-strlen(addrbuf)-1); + addrbuf[addrbuflen-1] = '\0'; + } + ap = ap->next; + } + InitEntryText(addrbuf, he); + free((char *)addrbuf); + } else { + InitEntryText("", he); + } + he->realaddr = NULL; + } else { + if(!he->blank) + addrbuf = *(he->realaddr); + else + addrbuf = ""; + + InitEntryText(addrbuf, he); + } + } + + /* + * finish initialization and then figure out display layout. + * first, look for any field the caller requested we start in, + * and set the offset into that field. + */ + if((ods.cur_l = first_requested_hline(&ods.cur_e)) != NULL){ + ods.top_e = 0; /* init top_e */ + ods.top_l = first_hline(&ods.top_e); + if(!HeaderFocus(ods.cur_e, Pmaster ? Pmaster->edit_offset : 0)) + HeaderFocus(ods.cur_e, 0); + + rv = TRUE; + } + else{ + ods.top_l = first_hline(&ods.cur_e); + ods.cur_l = first_sel_hline(&ods.cur_e); + ods.top_e = ods.cur_e; + rv = 0; + } + + UpdateHeader(0); + return(rv); +} + + + +/* + * InitEntryText - Add the given header text into the header entry + * line structure. + */ +int +InitEntryText(char *utf8text, struct headerentry *e) +{ + struct hdr_line *curline; + register int longest; + + /* + * get first chunk of memory, and tie it to structure... + */ + if((curline = HALLOC()) == NULL){ + emlwrite("Unable to make room for full Header.", NULL); + return(FALSE); + } + + longest = term.t_ncol - e->prwid - 1; + curline->text[0] = '\0'; + curline->next = NULL; + curline->prev = NULL; + e->hd_text = curline; /* tie it into the list */ + + if(FormatLines(curline, utf8text, longest, e->break_on_comma, 0) == -1) + return(FALSE); + else + return(TRUE); +} + + + +/* + * ResizeHeader - Handle resizing display when SIGWINCH received. + * + * notes: + * works OK, but needs thorough testing + * + */ +int +ResizeHeader(void) +{ + register struct headerentry *i; + register int offset; + + if(!headents) + return(TRUE); + + offset = (ComposerEditing) ? HeaderOffset(ods.cur_e) : 0; + + for(i=headents; i->name; i++){ /* format each entry */ + if(FormatLines(i->hd_text, "", (term.t_ncol - i->prwid), + i->break_on_comma, 0) == -1){ + return(-1); + } + } + + if(ComposerEditing) /* restart at the top */ + HeaderFocus(ods.cur_e, offset); /* fix cur_l and p_ind */ + else { + struct hdr_line *l; + int e; + + for(i = headents; i->name != NULL; i++); /* Find last line */ + i--; + e = i - headents; + l = headents[e].hd_text; + if(!headents[e].display_it || headents[e].blank) + l = prev_sel_hline(&e, l); /* last selectable line */ + + if(!l){ + e = i - headents; + l = headents[e].hd_text; + } + + HeaderFocus(e, -1); /* put focus on last line */ + } + + if(ComposerTopLine != COMPOSER_TOP_LINE) + UpdateHeader(0); + + PaintBody(0); + + if(ComposerEditing) + movecursor(ods.p_line, ods.p_ind+headents[ods.cur_e].prwid); + + (*term.t_flush)(); + return(TRUE); +} + + + +/* + * HeaderOffset - return the character offset into the given header + */ +int +HeaderOffset(int h) +{ + register struct hdr_line *l; + int i = 0; + + l = headents[h].hd_text; + + while(l != ods.cur_l){ + i += ucs4_strlen(l->text); + l = l->next; + } + return(i+ods.p_ind); +} + + + +/* + * HeaderFocus - put the dot at the given offset into the given header + */ +int +HeaderFocus(int h, int offset) +{ + register struct hdr_line *l; + register int i; + int last = 0; + + if(offset == -1) /* focus on last line */ + last = 1; + + l = headents[h].hd_text; + while(1){ + if(last && l->next == NULL){ + break; + } + else{ + if((i=ucs4_strlen(l->text)) >= offset) + break; + else + offset -= i; + } + if((l = l->next) == NULL) + return(FALSE); + } + + ods.cur_l = l; + ods.p_len = ucs4_strlen(l->text); + ods.p_ind = (last) ? 0 : offset; + + return(TRUE); +} + + + +/* + * HeaderEditor() - edit the mail header field by field, trapping + * important key sequences, hand the hard work off + * to LineEdit(). + * returns: + * -3 if we drop out bottom *and* want to process mouse event + * -1 if we drop out the bottom + * FALSE if editing is cancelled + * TRUE if editing is finished + */ +int +HeaderEditor(int f, int n) +{ + register int i; + UCS ch = 0, lastch; + register char *bufp; + struct headerentry *h; + int cur_e, mangled, retval = -1, + hdr_only = (gmode & MDHDRONLY) ? 1 : 0; + char *errmss, *err; +#ifdef MOUSE + MOUSEPRESS mp; +#endif + + if(!headents) + return(TRUE); /* nothing to edit! */ + + ComposerEditing = TRUE; + display_delimiter(0); /* provide feedback */ + +#ifdef _WINDOWS + mswin_setscrollrange (0, 0); +#endif /* _WINDOWS */ + + if(Pmaster) + for (subject_line = NULL, i=0; headents[i].name; i++) + if(strcmp(headents[i].name, "Subject") == 0) + subject_line = headents[i].hd_text; + + /* + * Decide where to begin editing. if f == TRUE begin editing + * at the bottom. this case results from the cursor backing + * into the editor from the bottom. otherwise, the user explicitly + * requested editing the header, and we begin at the top. + * + * further, if f == 1, we moved into the header by hitting the up arrow + * in the message text, else if f == 2, we moved into the header by + * moving past the left edge of the top line in the message. so, make + * the end of the last line of the last entry the current cursor position + * lastly, if f == 3, we moved into the header by hitting backpage() on + * the top line of the message, so scroll a page back. + */ + if(f){ + if(f == 2){ /* 2 leaves cursor at end */ + struct hdr_line *l = ods.cur_l; + int e = ods.cur_e; + + /*--- make sure on last field ---*/ + while((l = next_sel_hline(&e, l)) != NULL) + if(headents[ods.cur_e].display_it){ + ods.cur_l = l; + ods.cur_e = e; + } + + ods.p_ind = 1000; /* and make sure at EOL */ + } + else{ + /* + * note: assumes that ods.cur_e and ods.cur_l haven't changed + * since we left... + */ + + /* fix postition */ + if(curwp->w_doto < headents[ods.cur_e].prwid) + ods.p_ind = 0; + else if(curwp->w_doto < ods.p_ind + headents[ods.cur_e].prwid) + ods.p_ind = curwp->w_doto - headents[ods.cur_e].prwid; + else + ods.p_ind = 1000; + + /* and scroll back if needed */ + if(f == 3) + for(i = 0; header_upline(0) && i <= FULL_SCR(); i++) + ; + } + + ods.p_line = ComposerTopLine - 2; + } + /* else just trust what ods contains */ + + InvertPrompt(ods.cur_e, TRUE); /* highlight header field */ + sgarbk = 1; + + do{ + lastch = ch; + if(km_popped){ + km_popped--; + if(km_popped == 0) + sgarbk = 1; + } + + if(sgarbk){ + if(km_popped){ /* temporarily change to cause menu to paint */ + term.t_mrow = 2; + curwp->w_ntrows -= 2; + movecursor(term.t_nrow-2, 0); /* clear status line, too */ + peeol(); + } + else if(term.t_mrow == 0) + PaintBody(1); + + ShowPrompt(); /* display correct options */ + sgarbk = 0; + if(km_popped){ + term.t_mrow = 0; + curwp->w_ntrows += 2; + } + } + + ch = LineEdit(!(gmode&MDVIEW), &lastch); /* work on the current line */ + + if(km_popped) + switch(ch){ + case NODATA: + case (CTRL|'L'): + km_popped++; + break; + + default: + movecursor(term.t_nrow-2, 0); + peeol(); + movecursor(term.t_nrow-1, 0); + peeol(); + movecursor(term.t_nrow, 0); + peeol(); + break; + } + + switch (ch){ + case (CTRL|'R') : /* Toggle header display */ + if(Pmaster->pine_flags & MDHDRONLY){ + if(Pmaster->expander){ + packheader(); + call_expander(); + PaintBody(0); + break; + } + else + goto bleep; + } + + /*---- Are there any headers to expand above us? ---*/ + for(h = headents; h != &headents[ods.cur_e]; h++) + if(h->rich_header) + break; + if(h->rich_header) + InvertPrompt(ods.cur_e, FALSE); /* Yes, don't leave inverted */ + + mangled = 0; + err = NULL; + if(partial_entries()){ + /*--- Just turned off all rich headers --*/ + if(headents[ods.cur_e].rich_header){ + /*-- current header got turned off too --*/ +#ifdef ATTACHMENTS + if(headents[ods.cur_e].is_attach){ + /* verify the attachments */ + if((i = FormatSyncAttach()) != 0){ + FormatLines(headents[ods.cur_e].hd_text, "", + term.t_ncol - headents[ods.cur_e].prwid, + headents[ods.cur_e].break_on_comma, 0); + } + } +#endif + if(headents[ods.cur_e].builder) /* verify text */ + i = call_builder(&headents[ods.cur_e], &mangled, &err)>0; + /* Check below */ + for(cur_e =ods.cur_e; headents[cur_e].name!=NULL; cur_e++) + if(!headents[cur_e].rich_header) + break; + if(headents[cur_e].name == NULL) { + /* didn't find one, check above */ + for(cur_e =ods.cur_e; headents[cur_e].name!=NULL; + cur_e--) + if(!headents[cur_e].rich_header) + break; + + } + ods.p_ind = 0; + ods.cur_e = cur_e; + ods.cur_l = headents[ods.cur_e].hd_text; + } + } + + ods.p_line = 0; /* force update */ + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + PaintBody(1); + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + break; + + case (CTRL|'C') : /* bag whole thing ?*/ + if(abort_composer(1, 0) == TRUE) + return(FALSE); + + break; + + case (CTRL|'X') : /* Done. Send it. */ + i = 0; +#ifdef ATTACHMENTS + if(headents[ods.cur_e].is_attach){ + /* verify the attachments, and pretty things up in case + * we come back to the composer due to error... + */ + if((i = FormatSyncAttach()) != 0){ + sleep(2); /* give time for error to absorb */ + FormatLines(headents[ods.cur_e].hd_text, "", + term.t_ncol - headents[ods.cur_e].prwid, + headents[ods.cur_e].break_on_comma, 0); + } + } + else +#endif + mangled = 0; + err = NULL; + if(headents[ods.cur_e].builder) /* verify text? */ + i = call_builder(&headents[ods.cur_e], &mangled, &err); + + if(i < 0){ /* don't leave without a valid addr */ + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + break; + } + else if(i > 0){ + ods.cur_l = headents[ods.cur_e].hd_text; /* attach cur_l */ + ods.p_ind = 0; + ods.p_line = 0; /* force realignment */ + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + NewTop(0); + } + + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + + if(wquit(1,0) == TRUE) + return(TRUE); + + if(i > 0){ + /* + * need to be careful here because pointers might be messed up. + * also, could do a better job of finding the right place to + * put the dot back (i.e., the addr/list that was expanded). + */ + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + PaintBody(1); + } + break; + + case (CTRL|'Z') : /* Suspend compose */ + if(gmode&MDSSPD){ /* is it allowed? */ + bktoshell(0, 1); + PaintBody(0); + } + else + unknown_command(ch); + + break; + + case (CTRL|'\\') : +#if defined MOUSE && !defined(_WINDOWS) + toggle_xterm_mouse(0,1); +#else + unknown_command(ch); +#endif + break; + + case (CTRL|'O') : /* Suspend message */ + if(Pmaster->pine_flags & MDHDRONLY) + goto bleep; + + i = 0; + mangled = 0; + err = NULL; + if(headents[ods.cur_e].is_attach){ + if(FormatSyncAttach() < 0){ + if(mlyesno_utf8(_("Problem with attachments. Postpone anyway?"), + FALSE) != TRUE){ + if(FormatLines(headents[ods.cur_e].hd_text, "", + term.t_ncol - headents[ods.cur_e].prwid, + headents[ods.cur_e].break_on_comma, 0) == -1) + emlwrite("\007Format lines failed!", NULL); + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + PaintBody(1); + continue; + } + } + } + else if(headents[ods.cur_e].builder) + i = call_builder(&headents[ods.cur_e], &mangled, &err); + + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + + if(i < 0) /* don't leave without a valid addr */ + break; + + suspend_composer(1, 0); + return(TRUE); + +#ifdef ATTACHMENTS + case (CTRL|'J') : /* handle attachments */ + if(Pmaster->pine_flags & MDHDRONLY) + goto bleep; + + { char cmt[NLINE]; + LMLIST *lm = NULL, *lmp; + char buf[NLINE], *bfp; + int saved_km_popped; + size_t len; + + /* + * Attachment questions mess with km_popped and assume + * it is zero to start with. If we don't set it to zero + * on entry, the message about mime type will be erased + * by PaintBody. If we don't reset it when we come back, + * the bottom three lines may be messed up. + */ + saved_km_popped = km_popped; + km_popped = 0; + + if(AskAttach(cmt, sizeof(cmt), &lm)){ + + for(lmp = lm; lmp; lmp = lmp->next){ + size_t space; + + len = lmp->dir ? strlen(lmp->dir)+1 : 0; + len += lmp->fname ? strlen(lmp->fname) : 0; + + if(len+3 > sizeof(buf)){ + space = len+3; + bfp = malloc(space*sizeof(char)); + if(bfp == NULL){ + emlwrite("\007Can't malloc space for filename", + NULL); + continue; + } + } + else{ + bfp = buf; + space = sizeof(buf); + } + + if(lmp->dir && lmp->dir[0]) + snprintf(bfp, space, "%s%c%s", lmp->dir, C_FILESEP, + lmp->fname ? lmp->fname : ""); + else + snprintf(bfp, space, "%s", lmp->fname ? lmp->fname : ""); + + (void) QuoteAttach(bfp, space); + + (void) AppendAttachment(bfp, lm->size, cmt); + + if(bfp != buf) + free(bfp); + } + + zotlmlist(lm); + } + + km_popped = saved_km_popped; + sgarbk = 1; /* clean up prompt */ + } + break; +#endif + + case (CTRL|'I') : /* tab */ + if(headents[ods.cur_e].nickcmpl != NULL){ + char *new_nickname = NULL; + UCS *strng; + UCS *start = NULL, *end = NULL, *before_start = NULL; + UCS *uprefix = NULL, *up1, *up2; + char *prefix = NULL, *saveprefix = NULL, *insert = NULL; + char *ambig = NULL; + int offset, prefixlen, add_a_comma = 0; + size_t l, l1, l2; + int ambiguity, fallthru = 1; + + strng = ods.cur_l->text; + offset = HeaderOffset(ods.cur_e); + + if(ods.p_ind > 0 + && (start = &strng[ods.p_ind-1]) + && (!*(start+1) + || ucs4_isspace(*(start+1)) + || *(start+1) == ',') + && (*start + && !ucs4_isspace(*start) + && *start != ',')){ + + end = start+1; + + while(start > strng + && *(start-1) + && *(start-1) != ',') + start--; + + while(ucs4_isspace(*start)) + start++; + + if(*end != ',' && ods.cur_l->next) + add_a_comma++; + + /* + * Nickname prefix begins with start and ends + * with end-1. Replace those characters with + * completed nickname. + */ + prefixlen = end-start; + uprefix = (UCS *) fs_get((prefixlen+1) * sizeof(UCS)); + ucs4_strncpy(uprefix, start, prefixlen); + uprefix[prefixlen] = '\0'; + prefix = ucs4_to_utf8_cpystr(uprefix); + fs_give((void **) &uprefix); + + /* + * Ambiguity == 0 means no matches exist. + * 1 means it is ambiguous and the longest + * unambiguous prefix beginning with prefix + * is in new_nickname. + * 2 means it is unambiguous and the answer + * is in new_nickname + * + * The expansion from == 2 doesn't have to have prefix + * as a prefix. For example, if the prefix is a prefix + * of the full name or a prefix of a nickname the answer + * might be the full address instead of the expanded + * prefix. In fact, that's probably the expected thing. + * + * Due to the way the build_abook_tries sets up the tries + * it is unfortunately the case that the expanded address + * is not entered in any trie, so after you get an + * unambiguous expansion if you try TAB again at that + * point you'll probably get a no match returned instead + * of an unambiguous. So be aware of that and handle + * that case ok by falling through to header_downline. + */ + ambiguity = (*(headents[ods.cur_e].nickcmpl))(prefix, + &new_nickname, (lastch == ch), 0); + + /* + * Don't fall through on no-match TAB unless + * it is the second TAB. + */ + if(ambiguity == 0 && lastch != ch){ + lastch = 0; + break; + } + + if(ambiguity != 1) + lastch = 0; + + if(ambiguity == 0) + goto nomore_to_complete; + + if(new_nickname){ + if(strcmp(new_nickname, prefix)){ + /* + * We're trying to work with the way + * FormatLines works. It inserts text at the + * beginning of the line we pass in. + * So, remove the beginning of the line and + * have FormatLines put it back. + */ + + /* save part before start */ + fallthru = 0; + before_start = strng; + uprefix = (UCS *) fs_get((start-before_start+1) * sizeof(UCS)); + ucs4_strncpy(uprefix, before_start, start-before_start); + uprefix[start-before_start] = '\0'; + saveprefix = ucs4_to_utf8_cpystr(uprefix); + + /* figure out new cursor offset */ + up1 = utf8_to_ucs4_cpystr(new_nickname); + if(up1){ + offset += (ucs4_strlen(up1) - prefixlen); + fs_give((void **) &up1); + } + + fs_give((void **) &uprefix); + + /* + * Delete everything up to end by + * copying characters to start of buffer. + */ + up1 = before_start; + up2 = end; + for(i = ods.p_len - (end - before_start) + 1; i > 0; i--) + *up1++ = *up2++; + + ods.p_len -= (end - before_start); + + if(saveprefix){ + l1 = strlen(saveprefix); + l2 = strlen(new_nickname); + l = l1 + l2; + + /* add a comma? */ + if(add_a_comma && ambiguity == 2){ + l++; + offset++; + } + else + add_a_comma = 0; + + insert = (char *) fs_get((l+1) * sizeof(char)); + + /* + * Insert is the before start stuff plus the + * new nickname, and we're going to let + * FormatLines put it together for us. + */ + if(insert){ + strncpy(insert, saveprefix, l); + strncpy(insert+l1, new_nickname, l-l1); + if(add_a_comma) + insert[l-1] = ','; + + insert[l] = '\0'; + } + + fs_give((void **) &saveprefix); + } + + + if(insert && FormatLines(ods.cur_l, insert, + term.t_ncol - headents[ods.cur_e].prwid, + headents[ods.cur_e].break_on_comma,0)==-1){ + emlwrite("\007Format lines failed!", NULL); + } + + if(insert) + fs_give((void **) &insert); + + HeaderFocus(ods.cur_e, offset); + } + else{ + (*term.t_beep)(); + } + + ambig = new_nickname; + new_nickname = NULL; + } + + if(!ambig && prefix){ + ambig = prefix; + prefix = NULL; + } + + + if(ambiguity == 2 && fallthru){ + if(prefix) + fs_give((void **) &prefix); + + if(new_nickname) + fs_give((void **) &new_nickname); + + if(ambig) + fs_give((void **) &ambig); + + UpdateHeader(0); + PaintBody(0); + goto nomore_to_complete; + } + + UpdateHeader(0); + PaintBody(0); + + if(prefix) + fs_give((void **) &prefix); + + if(new_nickname) + fs_give((void **) &new_nickname); + + if(ambig) + fs_give((void **) &ambig); + } + else{ + goto nomore_to_complete; + } + + break; + } + else{ +nomore_to_complete: + ods.p_ind = 0; /* fall through... */ + } + + case (CTRL|'N') : + case KEY_DOWN : + header_downline(!hdr_only, hdr_only); + break; + + case (CTRL|'P') : + case KEY_UP : + header_upline(1); + break; + + case (CTRL|'V') : /* down a page */ + case KEY_PGDN : + cur_e = ods.cur_e; + if(!next_sel_hline(&cur_e, ods.cur_l)){ + header_downline(!hdr_only, hdr_only); + if(!(gmode & MDHDRONLY)) + retval = -1; /* tell caller we fell out */ + } + else{ + int move_down, bot_pline; + struct hdr_line *new_cur_l, *line, *next_line, *prev_line; + + move_down = BOTTOM() - 2 - ods.p_line; + if(move_down < 0) + move_down = 0; + + /* + * Count down move_down lines to find the pointer to the line + * that we want to become the current line. + */ + new_cur_l = ods.cur_l; + cur_e = ods.cur_e; + for(i = 0; i < move_down; i++){ + next_line = next_hline(&cur_e, new_cur_l); + if(!next_line) + break; + + new_cur_l = next_line; + } + + if(headents[cur_e].blank){ + next_line = next_sel_hline(&cur_e, new_cur_l); + if(!next_line) + break; + + new_cur_l = next_line; + } + + /* + * Now call header_downline until we get down to the + * new current line, so that the builders all get called. + * New_cur_l will remain valid since we won't be calling + * a builder for it during this loop. + */ + while(ods.cur_l != new_cur_l && header_downline(0, 0)) + ; + + /* + * Count back up, if we're at the bottom, to find the new + * top line. + */ + cur_e = ods.cur_e; + if(next_hline(&cur_e, ods.cur_l) == NULL){ + /* + * Cursor stops at bottom of headers, which is where + * we are right now. Put as much of headers on + * screen as will fit. Count up to figure + * out which line is top_l and which p_line cursor is on. + */ + cur_e = ods.cur_e; + line = ods.cur_l; + /* leave delimiter on screen, too */ + bot_pline = BOTTOM() - 1 - ((gmode & MDHDRONLY) ? 0 : 1); + for(i = COMPOSER_TOP_LINE; i < bot_pline; i++){ + prev_line = prev_hline(&cur_e, line); + if(!prev_line) + break; + + line = prev_line; + } + + ods.top_l = line; + ods.top_e = cur_e; + ods.p_line = i; + + } + else{ + ods.top_l = ods.cur_l; + ods.top_e = ods.cur_e; + /* + * We don't want to scroll down further than the + * delimiter, so check to see if that is the case. + * If it is, we move the p_line down the screen + * until the bottom line is where we want it. + */ + bot_pline = BOTTOM() - 1 - ((gmode & MDHDRONLY) ? 0 : 1); + cur_e = ods.cur_e; + line = ods.cur_l; + for(i = bot_pline; i > COMPOSER_TOP_LINE; i--){ + next_line = next_hline(&cur_e, line); + if(!next_line) + break; + + line = next_line; + } + + /* + * i is the desired value of p_line. + * If it is greater than COMPOSER_TOP_LINE, then + * we need to adjust top_l. + */ + ods.p_line = i; + line = ods.top_l; + cur_e = ods.top_e; + for(; i > COMPOSER_TOP_LINE; i--){ + prev_line = prev_hline(&cur_e, line); + if(!prev_line) + break; + + line = prev_line; + } + + ods.top_l = line; + ods.top_e = cur_e; + + /* + * Special case. If p_line is within one of the bottom, + * move it to the bottom. + */ + if(ods.p_line == bot_pline - 1){ + header_downline(0, 0); + /* but put these back where we want them */ + ods.p_line = bot_pline; + ods.top_l = line; + ods.top_e = cur_e; + } + } + + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + PaintBody(1); + } + + break; + + case (CTRL|'Y') : /* up a page */ + case KEY_PGUP : + for(i = 0; header_upline(0) && i <= FULL_SCR(); i++) + if(i < 0) + break; + + break; + +#ifdef MOUSE + case KEY_MOUSE: + mouse_get_last (NULL, &mp); + switch(mp.button){ + case M_BUTTON_LEFT : + if (!mp.doubleclick) { + if (mp.row < ods.p_line) { + for (i = ods.p_line - mp.row; + i > 0 && header_upline(0); + --i) + ; + } + else { + for (i = mp.row-ods.p_line; + i > 0 && header_downline(!hdr_only, 0); + --i) + ; + } + + if((ods.p_ind = mp.col - headents[ods.cur_e].prwid) <= 0) + ods.p_ind = 0; + + /* -3 is returned if we drop out bottom + * *and* want to process a mousepress. The Headereditor + * wrapper should make sense of this return code. + */ + if (ods.p_line >= ComposerTopLine) + retval = -3; + } + + break; + + case M_BUTTON_RIGHT : +#ifdef _WINDOWS + pico_popup(); +#endif + case M_BUTTON_MIDDLE : + default : /* NOOP */ + break; + } + + break; +#endif /* MOUSE */ + + case (CTRL|'T') : /* Call field selector */ + errmss = NULL; + if(headents[ods.cur_e].is_attach) { + /*--- selector for attachments ----*/ + char dir[NLINE], fn[NLINE], sz[NLINE]; + LMLIST *lm = NULL, *lmp; + + strncpy(dir, (gmode & MDCURDIR) + ? (browse_dir[0] ? browse_dir : ".") + : ((gmode & MDTREE) || opertree[0]) + ? opertree + : (browse_dir[0] ? browse_dir + : gethomedir(NULL)), sizeof(dir)); + dir[sizeof(dir)-1] = '\0'; + fn[0] = sz[0] = '\0'; + if(FileBrowse(dir, sizeof(dir), fn, sizeof(fn), sz, sizeof(sz), + FB_READ | FB_ATTACH | FB_LMODEPOS, &lm) == 1){ + char buf[NLINE], *bfp; + size_t len; + + for(lmp = lm; lmp; lmp = lmp->next){ + size_t space; + + len = lmp->dir ? strlen(lmp->dir)+1 : 0; + len += lmp->fname ? strlen(lmp->fname) : 0; + len += 7; + len += strlen(lmp->size); + + if(len+3 > sizeof(buf)){ + space = len+3; + bfp = malloc(space*sizeof(char)); + if(bfp == NULL){ + emlwrite("\007Can't malloc space for filename", + NULL); + continue; + } + } + else{ + bfp = buf; + space = sizeof(buf); + } + + if(lmp->dir && lmp->dir[0]) + snprintf(bfp, space, "%s%c%s", lmp->dir, C_FILESEP, + lmp->fname ? lmp->fname : ""); + else + snprintf(bfp, space, "%s", lmp->fname ? lmp->fname : ""); + + (void) QuoteAttach(bfp, space); + + snprintf(bfp + strlen(bfp), space-strlen(bfp), " (%s) \"\"%s", lmp->size, + (!headents[ods.cur_e].hd_text->text[0]) ? "":","); + + if(FormatLines(headents[ods.cur_e].hd_text, bfp, + term.t_ncol - headents[ods.cur_e].prwid, + headents[ods.cur_e].break_on_comma,0)==-1){ + emlwrite("\007Format lines failed!", NULL); + } + + if(bfp != buf) + free(bfp); + + UpdateHeader(0); + } + + zotlmlist(lm); + } /* else, nothing of interest */ + } else if (headents[ods.cur_e].selector != NULL) { + VARS_TO_SAVE *saved_state; + + /*---- General selector for non-attachments -----*/ + + /* + * Since the selector may make a new call back to pico() + * we need to save and restore the pico state here. + */ + if((saved_state = save_pico_state()) != NULL){ + bufp = (*(headents[ods.cur_e].selector))(&errmss); + restore_pico_state(saved_state); + free_pico_state(saved_state); + ttresize(); /* fixup screen bufs */ + picosigs(); /* restore altered signals */ + } + else{ + char *s = "Not enough memory"; + size_t len; + + len = strlen(s); + errmss = (char *)malloc((len+1) * sizeof(char)); + strncpy(errmss, s, len+1); + errmss[len] = '\0'; + bufp = NULL; + } + + if(bufp != NULL) { + mangled = 0; + err = NULL; + if(headents[ods.cur_e].break_on_comma) { + /*--- Must be an address ---*/ + + /* + * If current line is empty and there are more + * lines that follow, delete the empty lines + * before adding the new address. + */ + if(ods.cur_l->text[0] == '\0' && ods.cur_l->next){ + do { + KillHeaderLine(ods.cur_l, 1); + ods.p_len = ucs4_strlen(ods.cur_l->text); + } while(ods.cur_l->text[0] == '\0' && ods.cur_l->next); + } + + ods.p_ind = 0; + + if(ods.cur_l->text[0] != '\0'){ + struct hdr_line *h, *start_of_addr; + int q = 0; + + /* cur is not first line */ + if(ods.cur_l != headents[ods.cur_e].hd_text){ + /* + * Protect against adding a new entry into + * the middle of a long, continued entry. + */ + start_of_addr = NULL; /* cur_l is a good place to be */ + q = 0; + for(h = headents[ods.cur_e].hd_text; h; h = h->next){ + if(ucs4_strqchr(h->text, ',', &q, -1)){ + start_of_addr = NULL; + q = 0; + } + else if(start_of_addr == NULL) + start_of_addr = h; + + if(h->next == ods.cur_l) + break; + } + + if(start_of_addr){ + ods.cur_l = start_of_addr; + ods.p_len = ucs4_strlen(ods.cur_l->text); + } + } + + for(i = ++ods.p_len; i; i--) + ods.cur_l->text[i] = ods.cur_l->text[i-1]; + + ods.cur_l->text[0] = ','; + } + + if(FormatLines(ods.cur_l, bufp, + (term.t_ncol-headents[ods.cur_e].prwid), + headents[ods.cur_e].break_on_comma, 0) == -1){ + emlwrite("Problem adding address to header !", + NULL); + (*term.t_beep)(); + break; + } + + /* + * If the "selector" has a "builder" as well, pass + * what was just selected thru the builder... + */ + if(headents[ods.cur_e].builder){ + struct hdr_line *l; + int cur_row, top_too = 0; + + for(l = headents[ods.cur_e].hd_text, cur_row = 0; + l && l != ods.cur_l; + l = l->next, cur_row++) + ; + + top_too = headents[ods.cur_e].hd_text == ods.top_l; + + if(call_builder(&headents[ods.cur_e], &mangled, + &err) < 0){ + fix_mangle_and_err(&mangled, &err, + headents[ods.cur_e].name); + } + + for(ods.cur_l = headents[ods.cur_e].hd_text; + ods.cur_l->next && cur_row; + ods.cur_l = ods.cur_l->next, cur_row--) + ; + + if(top_too) + ods.top_l = headents[ods.cur_e].hd_text; + } + + UpdateHeader(0); + } else { + UCS *u; + + u = utf8_to_ucs4_cpystr(bufp); + if(u){ + ucs4_strncpy(headents[ods.cur_e].hd_text->text, u, HLSZ); + headents[ods.cur_e].hd_text->text[HLSZ-1] = '\0'; + fs_give((void **) &u); + } + } + + free(bufp); + /* mark this entry dirty */ + mark_sticky(&headents[ods.cur_e]); + headents[ods.cur_e].dirty = 1; + fix_mangle_and_err(&mangled,&err,headents[ods.cur_e].name); + } + } else { + /*----- No selector -----*/ + (*term.t_beep)(); + continue; + } + + PaintBody(0); + if(errmss != NULL) { + (*term.t_beep)(); + emlwrite(errmss, NULL); + free(errmss); + errmss = NULL; + } + continue; + + case (CTRL|'G'): /* HELP */ + if(term.t_mrow == 0){ + if(km_popped == 0){ + km_popped = 2; + sgarbk = 1; /* bring up menu */ + break; + } + } + + if(!ComposerHelp(ods.cur_e)) + break; /* else, fall through... */ + + case (CTRL|'L'): /* redraw requested */ + PaintBody(0); + break; + + case (CTRL|'_'): /* file editor */ + if(headents[ods.cur_e].fileedit != NULL){ + struct headerentry *e; + struct hdr_line *line; + int sz = 0; + char *filename = NULL; + VARS_TO_SAVE *saved_state; + + /* + * Since the fileedit will make a new call back to pico() + * we need to save and restore the pico state here. + */ + if((saved_state = save_pico_state()) != NULL){ + UCS *u; + + e = &headents[ods.cur_e]; + + for(line = e->hd_text; line != NULL; line = line->next) + sz += ucs4_strlen(line->text); + + u = (UCS *)malloc((sz+1) * sizeof(*u)); + if(u){ + u[0] = '\0'; + for(line = e->hd_text; line != NULL; line = line->next) + ucs4_strncat(u, line->text, sz+1-ucs4_strlen(u)-1); + + filename = ucs4_to_utf8_cpystr(u); + free(u); + } + + errmss = (*(headents[ods.cur_e].fileedit))(filename); + + if(filename) + free(filename); + + restore_pico_state(saved_state); + free_pico_state(saved_state); + ttresize(); /* fixup screen bufs */ + picosigs(); /* restore altered signals */ + } + else{ + char *s = "Not enough memory"; + size_t len; + + len = strlen(s); + errmss = (char *)malloc((len+1) * sizeof(char)); + strncpy(errmss, s, len+1); + errmss[len] = '\0'; + } + + PaintBody(0); + + if(errmss != NULL) { + (*term.t_beep)(); + emlwrite(errmss, NULL); + free(errmss); + errmss = NULL; + } + + continue; + } + else + goto bleep; + + break; + + default : /* huh? */ +bleep: + unknown_command(ch); + + case NODATA: + break; + } + } + while (ods.p_line < ComposerTopLine); + + display_delimiter(1); + curwp->w_flag |= WFMODE; + movecursor(currow, curcol); + ComposerEditing = FALSE; + if (ComposerTopLine == BOTTOM()){ + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + PaintBody(1); + } + + return(retval); +} + + +/* + * + */ +int +header_downline(int beyond, int gripe) +{ + struct hdr_line *new_l, *l; + int new_e, status, fullpaint, len, e, incr = 0; + + /* calculate the next line: physical *and* logical */ + status = 0; + new_e = ods.cur_e; + if((new_l = next_sel_hline(&new_e, ods.cur_l)) == NULL && !beyond){ + + if(gripe){ + char xx[81]; + + strncpy(xx, "Can't move down. Use ^X to ", sizeof(xx)); + xx[sizeof(xx)-1] = '\0'; + strncat(xx, (Pmaster && Pmaster->exit_label) + ? Pmaster->exit_label + : (gmode & MDHDRONLY) + ? "eXit/Save" + : (gmode & MDVIEW) + ? "eXit" + : "Send", sizeof(xx)-strlen(xx)-1); + xx[sizeof(xx)-1] = '\0'; + strncat(xx, ".", sizeof(xx)-strlen(xx)-1); + xx[sizeof(xx)-1] = '\0'; + emlwrite(xx, NULL); + } + + return(0); + } + + /* + * Because of blank header lines the cursor may need to move down + * more than one line. Figure out how far. + */ + e = ods.cur_e; + l = ods.cur_l; + while(l != new_l){ + if((l = next_hline(&e, l)) != NULL) + incr++; + else + break; /* can't happen */ + } + + ods.p_line += incr; + fullpaint = ods.p_line >= BOTTOM(); /* force full redraw? */ + + /* expand what needs expanding */ + if(new_e != ods.cur_e || !new_l){ /* new (or last) field ! */ + if(new_l) + InvertPrompt(ods.cur_e, FALSE); /* turn off current entry */ + + if(headents[ods.cur_e].is_attach) { /* verify data ? */ + if((status = FormatSyncAttach()) != 0){ /* fixup if 1 or -1 */ + headents[ods.cur_e].rich_header = 0; + if(FormatLines(headents[ods.cur_e].hd_text, "", + term.t_ncol-headents[new_e].prwid, + headents[ods.cur_e].break_on_comma, 0) == -1) + emlwrite("\007Format lines failed!", NULL); + } + } else if(headents[ods.cur_e].builder) { /* expand addresses */ + int mangled = 0; + char *err = NULL; + + if((status = call_builder(&headents[ods.cur_e], &mangled, &err))>0){ + struct hdr_line *l; /* fixup ods.cur_l */ + ods.p_line = 0; /* force top line recalc */ + for(l = headents[ods.cur_e].hd_text; l; l = l->next) + ods.cur_l = l; + + if(new_l) /* if new_l, force validity */ + new_l = headents[new_e].hd_text; + + NewTop(0); /* get new top_l */ + } + else if(status < 0){ /* bad addr? no leave! */ + --ods.p_line; + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + InvertPrompt(ods.cur_e, TRUE); + return(0); + } + + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + } + + if(new_l){ /* if one below, turn it on */ + InvertPrompt(new_e, TRUE); + sgarbk = 1; /* paint keymenu too */ + } + } + + if(new_l){ /* fixup new pointers */ + ods.cur_l = (ods.cur_e != new_e) ? headents[new_e].hd_text : new_l; + ods.cur_e = new_e; + if(ods.p_ind > (len = ucs4_strlen(ods.cur_l->text))) + ods.p_ind = len; + } + + if(!new_l || status || fullpaint){ /* handle big screen paint */ + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + PaintBody(1); + + if(!new_l){ /* make sure we're done */ + ods.p_line = ComposerTopLine; + InvertPrompt(ods.cur_e, FALSE); /* turn off current entry */ + } + } + + return(new_l ? 1 : 0); +} + + +/* + * + */ +int +header_upline(int gripe) +{ + struct hdr_line *new_l, *l; + int new_e, status, fullpaint, len, e, incr = 0; + EML eml; + + /* calculate the next line: physical *and* logical */ + status = 0; + new_e = ods.cur_e; + if(!(new_l = prev_sel_hline(&new_e, ods.cur_l))){ /* all the way up! */ + ods.p_line = COMPOSER_TOP_LINE; + if(gripe){ + eml.s = (Pmaster->pine_flags & MDHDRONLY) ? "entry" : "header"; + emlwrite(_("Can't move beyond top of %s"), &eml); + } + + return(0); + } + + /* + * Because of blank header lines the cursor may need to move up + * more than one line. Figure out how far. + */ + e = ods.cur_e; + l = ods.cur_l; + while(l != new_l){ + if((l = prev_hline(&e, l)) != NULL) + incr++; + else + break; /* can't happen */ + } + + ods.p_line -= incr; + fullpaint = ods.p_line <= COMPOSER_TOP_LINE; + + if(new_e != ods.cur_e){ /* new field ! */ + InvertPrompt(ods.cur_e, FALSE); + if(headents[ods.cur_e].is_attach){ + if((status = FormatSyncAttach()) != 0){ /* non-zero ? reformat field */ + headents[ods.cur_e].rich_header = 0; + if(FormatLines(headents[ods.cur_e].hd_text, "", + term.t_ncol - headents[ods.cur_e].prwid, + headents[ods.cur_e].break_on_comma,0) == -1) + emlwrite("\007Format lines failed!", NULL); + } + } + else if(headents[ods.cur_e].builder){ + int mangled = 0; + char *err = NULL; + + if((status = call_builder(&headents[ods.cur_e], &mangled, + &err)) >= 0){ + /* repair new_l */ + for(new_l = headents[new_e].hd_text; + new_l->next; + new_l=new_l->next) + ; + + /* and cur_l (required in fix_and... */ + ods.cur_l = new_l; + } + else{ + ++ods.p_line; + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + InvertPrompt(ods.cur_e, TRUE); + return(0); + } + + fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); + } + + InvertPrompt(new_e, TRUE); + sgarbk = 1; + } + + ods.cur_e = new_e; /* update pointers */ + ods.cur_l = new_l; + if(ods.p_ind > (len = ucs4_strlen(ods.cur_l->text))) + ods.p_ind = len; + + if(status > 0 || fullpaint){ + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + PaintBody(1); + } + + return(1); +} + + +/* + * + */ +int +AppendAttachment(char *fn, char *sz, char *cmt) +{ + int a_e, status, spaces; + struct hdr_line *lp; + char b[256]; + UCS *u; + + /*--- Find headerentry that is attachments (only first) --*/ + for(a_e = 0; headents[a_e].name != NULL; a_e++ ) + if(headents[a_e].is_attach){ + /* make sure field stays displayed */ + headents[a_e].rich_header = 0; + headents[a_e].display_it = 1; + break; + } + + /* append new attachment line */ + for(lp = headents[a_e].hd_text; lp->next; lp=lp->next) + ; + + /* build new attachment line */ + if(lp->text[0]){ /* adding a line? */ + UCS comma[2]; + + comma[0] = ','; + comma[1] = '\0'; + ucs4_strncat(lp->text, comma, HLSZ-ucs4_strlen(lp->text)-1); /* append delimiter */ + if((lp->next = HALLOC()) != NULL){ /* allocate new line */ + lp->next->prev = lp; + lp->next->next = NULL; + lp = lp->next; + } + else{ + emlwrite("\007Can't allocate line for new attachment!", NULL); + return(0); + } + } + + + spaces = (*fn == '\"') ? 0 : (strpbrk(fn, "(), \t") != NULL); + snprintf(b, sizeof(b), "%s%s%s (%s) \"%.*s\"", + spaces ? "\"" : "", fn, spaces ? "\"" : "", + sz ? sz : "", 80, cmt ? cmt : ""); + u = utf8_to_ucs4_cpystr(b); + if(u){ + ucs4_strncpy(lp->text, u, HLSZ); + lp->text[HLSZ-1] = '\0'; + fs_give((void **) &u); + } + + /* validate the new attachment, and reformat if needed */ + if((status = SyncAttach()) != 0){ + EML eml; + + if(status < 0){ + eml.s = fn; + emlwrite("\007Problem attaching: %s", &eml); + } + + if(FormatLines(headents[a_e].hd_text, "", + term.t_ncol - headents[a_e].prwid, + headents[a_e].break_on_comma, 0) == -1){ + emlwrite("\007Format lines failed!", NULL); + return(0); + } + } + + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, status != 0); + PaintBody(1); + return(status != 0); +} + + +/* + * LineEdit - Always use insert mode and handle line wrapping + * + * returns: + * Any characters typed in that aren't printable + * (i.e. commands) + * + * notes: + * Assume we are guaranteed that there is sufficiently + * more buffer space in a line than screen width (just one + * less thing to worry about). If you want to change this, + * then pputc will have to be taught to check the line buffer + * length, and HALLOC() will probably have to become a func. + */ +UCS +LineEdit(int allowedit, UCS *lastch) +{ + register struct hdr_line *lp; /* temporary line pointer */ + register int i; + UCS ch = 0; + register int status; /* various func's return val */ + UCS *tbufp; /* temporary buffer pointers */ + int skipmove = 0; + UCS *strng; + UCS last_key; /* last keystroke */ + + strng = ods.cur_l->text; /* initialize offsets */ + ods.p_len = MIN(ucs4_strlen(strng), HLSZ); + if(ods.p_ind < 0) /* offset within range? */ + ods.p_ind = 0; + else if(ods.p_ind > ods.p_len) + ods.p_ind = ods.p_len; + else if(ucs4_str_width_ptr_to_ptr(&strng[0], &strng[ods.p_ind]) > LINEWID()){ + UCS *u; + + u = ucs4_particular_width(strng, LINEWID()); + ods.p_ind = u - strng; + } + + while(1){ /* edit the line... */ + + if(Pmaster && subject_line != NULL + && ods.cur_l == subject_line + && ods.cur_l->text[0] == 0) + (*Pmaster->newthread)(); + + if(skipmove) + skipmove = 0; + else + HeaderPaintCursor(); + + last_key = ch; + if(ch && lastch) + *lastch = ch; + + (*term.t_flush)(); /* get everything out */ + +#ifdef MOUSE + mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); + register_mfunc(mouse_in_content,2,0,term.t_nrow-(term.t_mrow+1), + term.t_ncol); +#endif +#ifdef _WINDOWS + mswin_setdndcallback (composer_file_drop); + mswin_mousetrackcallback(pico_cursor); +#endif + + ch = GetKey(); + + if (term.t_nrow < 6 && ch != NODATA){ + (*term.t_beep)(); + emlwrite(_("Please make the screen larger."), NULL); + continue; + } + +#ifdef MOUSE + clear_mfunc(mouse_in_content); +#endif +#ifdef _WINDOWS + mswin_cleardndcallback (); + mswin_mousetrackcallback(NULL); +#endif + + switch(ch){ + case DEL : + if(gmode & P_DELRUBS) + ch = KEY_DEL; + + default : + (*Pmaster->keybinput)(); + if(!time_to_check()) + break; + + case NODATA : /* new mail ? */ + if((*Pmaster->newmail)(ch == NODATA ? 0 : 2, 1) >= 0){ + int rv; + + if(km_popped){ + term.t_mrow = 2; + curwp->w_ntrows -= 2; + } + + clearcursor(); + mlerase(); + rv = (*Pmaster->showmsg)(ch); + ttresize(); + picosigs(); + if(rv) /* Did showmsg corrupt the display? */ + PaintBody(0); /* Yes, repaint */ + + mpresf = 1; + if(km_popped){ + term.t_mrow = 0; + curwp->w_ntrows += 2; + } + } + + clearcursor(); + movecursor(ods.p_line, + ucs4_str_width_ptr_to_ptr(strng, &strng[ods.p_ind])+headents[ods.cur_e].prwid); + if(ch == NODATA) /* GetKey timed out */ + continue; + + break; + } + + if(mpresf){ /* blast old messages */ + if(mpresf++ > NMMESSDELAY){ /* every few keystrokes */ + mlerase(); + movecursor(ods.p_line, + ucs4_str_width_ptr_to_ptr(strng, &strng[ods.p_ind])+headents[ods.cur_e].prwid); + } + } + + tbufp = &strng[ods.p_len]; + + if(VALID_KEY(ch)){ /* char input */ + /* + * if we are allowing editing, insert the new char + * end up leaving tbufp pointing to newly + * inserted character in string, and offset to the + * index of the character after the inserted ch ... + */ + if(allowedit){ + if(headents[ods.cur_e].is_attach && intag(strng,ods.p_ind)){ + emlwrite(_("\007Can't edit attachment number!"), NULL); + continue; + } + + if(headents[ods.cur_e].single_space){ + if(ch == ' ' + && (strng[ods.p_ind]==' ' || strng[ods.p_ind-1]==' ')) + continue; + } + + /* + * go ahead and add the character... + */ + if(ods.p_len < HLSZ){ + tbufp = &strng[++ods.p_len]; /* find the end */ + do{ + *tbufp = tbufp[-1]; + } while(--tbufp > &strng[ods.p_ind]); /* shift right */ + strng[ods.p_ind++] = ch; /* add char to str */ + } + + /* mark this entry dirty */ + mark_sticky(&headents[ods.cur_e]); + headents[ods.cur_e].dirty = 1; + + /* + * then find out where things fit... + */ + if(ucs4_str_width_ptr_to_ptr(&strng[0], &strng[ods.p_len]) < LINEWID()){ + CELL c; + + c.c = ch & CELLMASK; + c.a = 0; + if(pinsert(c)){ /* add char to str */ + skipmove++; /* must'a been optimal */ + continue; /* on to the next! */ + } + } + else{ + if((status = FormatLines(ods.cur_l, "", LINEWID(), + headents[ods.cur_e].break_on_comma,0)) == -1){ + (*term.t_beep)(); + continue; + } + else{ + /* + * during the format, the dot may have moved + * down to the next line... + */ + if(ods.p_ind >= ucs4_strlen(strng)){ + ods.p_line++; + ods.p_ind -= ucs4_strlen(strng); + ods.cur_l = ods.cur_l->next; + strng = ods.cur_l->text; + } + + ods.p_len = ucs4_strlen(strng); + } + + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + PaintBody(1); + continue; + } + } + else{ + rdonly(); + continue; + } + } + else { /* interpret ch as a command */ + switch (ch = normalize_cmd(ch, ckm, 2)) { + case (CTRL|KEY_LEFT): /* word skip left */ + if(ods.p_ind > 0) /* Scoot one char left if possible */ + ods.p_ind--; + + if(ods.p_ind == 0) + { + if(ods.p_line != COMPOSER_TOP_LINE) + ods.p_ind = 1000; /* put cursor at end of line */ + return(KEY_UP); + } + + while(ods.p_ind > 0 && !ucs4_isalnum(strng[ods.p_ind])) + ods.p_ind--; /* skip any whitespace we're in */ + + while(ods.p_ind > 0) { + /* Bail if the character right before this one is whitespace */ + if(ods.p_ind > 1 && !ucs4_isalnum(strng[ods.p_ind - 1])) + break; + ods.p_ind--; + } + continue; + + case (CTRL|'@') : /* word skip */ + case (CTRL|KEY_RIGHT): + while(ucs4_isalnum(strng[ods.p_ind])) + ods.p_ind++; /* skip any text we're in */ + + while(strng[ods.p_ind] && !ucs4_isalnum(strng[ods.p_ind])) + ods.p_ind++; /* skip any whitespace after it */ + + if(strng[ods.p_ind] == '\0'){ + ods.p_ind = 0; /* end of line, let caller handle it */ + return(KEY_DOWN); + } + + continue; + + case (CTRL|'K') : /* kill line cursor's on */ + if(!allowedit){ + rdonly(); + continue; + } + + lp = ods.cur_l; + if (!(gmode & MDDTKILL)) + ods.p_ind = 0; + + if(KillHeaderLine(lp, (last_key == (CTRL|'K')))){ + if(TERM_OPTIMIZE && + !(ods.cur_l->prev==NULL && ods.cur_l->next==NULL)) + scrollup(wheadp, ods.p_line, 1); + + if(ods.cur_l->next == NULL) + if(zotcomma(ods.cur_l->text)){ + if(ods.p_ind > 0) + ods.p_ind = ucs4_strlen(ods.cur_l->text); + } + + i = (ods.p_line == COMPOSER_TOP_LINE); + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, TRUE); + + if(km_popped){ + km_popped--; + movecursor(term.t_nrow, 0); + peeol(); + } + + PaintBody(1); + + } + strng = ods.cur_l->text; + ods.p_len = ucs4_strlen(strng); + headents[ods.cur_e].sticky = 0; + headents[ods.cur_e].dirty = 1; + continue; + + case (CTRL|'U') : /* un-delete deleted lines */ + if(!allowedit){ + rdonly(); + continue; + } + + if(SaveHeaderLines()){ + UpdateHeader(0); + PaintHeader(COMPOSER_TOP_LINE, FALSE); + if(km_popped){ + km_popped--; + movecursor(term.t_nrow, 0); + peeol(); + } + + PaintBody(1); + strng = ods.cur_l->text; + ods.p_len = ucs4_strlen(strng); + mark_sticky(&headents[ods.cur_e]); + headents[ods.cur_e].dirty = 1; + } + else + /* TRANSLATORS: Killing text is deleting it and + Unkilling text is undeleting killed text. */ + emlwrite(_("Problem Unkilling text"), NULL); + continue; + + case (CTRL|'F') : + case KEY_RIGHT: /* move character right */ + if(ods.p_ind < ods.p_len){ + ods.p_ind++; + continue; + } + else if(gmode & MDHDRONLY) + continue; + + ods.p_ind = 0; + return(KEY_DOWN); + + case (CTRL|'B') : + case KEY_LEFT : /* move character left */ + if(ods.p_ind > 0){ + ods.p_ind--; + continue; + } + if(ods.p_line != COMPOSER_TOP_LINE) + ods.p_ind = 1000; /* put cursor at end of line */ + return(KEY_UP); + + case (CTRL|'M') : /* goto next field */ + ods.p_ind = 0; + return(KEY_DOWN); + + case KEY_HOME : + case (CTRL|'A') : /* goto beginning of line */ + ods.p_ind = 0; + continue; + + case KEY_END : + case (CTRL|'E') : /* goto end of line */ + ods.p_ind = ods.p_len; + continue; + + case (CTRL|'D') : /* blast this char */ + case KEY_DEL : + if(!allowedit){ + rdonly(); + continue; + } + else if(ods.p_ind >= ucs4_strlen(strng)) + continue; + + if(headents[ods.cur_e].is_attach && intag(strng, ods.p_ind)){ + emlwrite(_("\007Can't edit attachment number!"), NULL); + continue; + } + + pputc(strng[ods.p_ind++], 0); /* drop through and rubout */ + + case DEL : /* blast previous char */ + case (CTRL|'H') : + if(!allowedit){ + rdonly(); + continue; + } + + if(headents[ods.cur_e].is_attach && intag(strng, ods.p_ind-1)){ + emlwrite(_("\007Can't edit attachment number!"), NULL); + continue; + } + + if(ods.p_ind > 0){ /* just shift left one char */ + ods.p_len--; + headents[ods.cur_e].dirty = 1; + if(ods.p_len == 0) + headents[ods.cur_e].sticky = 0; + else + mark_sticky(&headents[ods.cur_e]); + + tbufp = &strng[--ods.p_ind]; + while(*tbufp++ != '\0') + tbufp[-1] = *tbufp; + tbufp = &strng[ods.p_ind]; + if(pdel()) /* physical screen delete */ + skipmove++; /* must'a been optimal */ + } + else{ /* may have work to do */ + if(ods.cur_l->prev == NULL){ + (*term.t_beep)(); /* no erase into next field */ + continue; + } + + ods.p_line--; + ods.cur_l = ods.cur_l->prev; + strng = ods.cur_l->text; + if((i=ucs4_strlen(strng)) > 0){ + strng[i-1] = '\0'; /* erase the character */ + ods.p_ind = i-1; + } + else{ + headents[ods.cur_e].sticky = 0; + ods.p_ind = 0; + } + + tbufp = &strng[ods.p_ind]; + } + + if((status = FormatLines(ods.cur_l, "", LINEWID(), + headents[ods.cur_e].break_on_comma,0))==-1){ + (*term.t_beep)(); + continue; + } + else{ + /* + * beware, the dot may have moved... + */ + while((ods.p_len=ucs4_strlen(strng)) < ods.p_ind){ + ods.p_line++; + ods.p_ind -= ucs4_strlen(strng); + ods.cur_l = ods.cur_l->next; + strng = ods.cur_l->text; + ods.p_len = ucs4_strlen(strng); + tbufp = &strng[ods.p_ind]; + status = TRUE; + } + + if(UpdateHeader(0)) + status = TRUE; + + PaintHeader(COMPOSER_TOP_LINE, FALSE); + if(status == TRUE) + PaintBody(1); + } + + movecursor(ods.p_line, + ucs4_str_width_ptr_to_ptr(strng, &strng[ods.p_ind])+headents[ods.cur_e].prwid); + + if(skipmove) + continue; + + break; + + default : + return(ch); + } + } + + while ((tbufp-strng) < HLSZ && *tbufp != '\0') /* synchronizing loop */ + pputc(*tbufp++, 0); + + if(ucs4_str_width_ptr_to_ptr(&strng[0], &strng[ods.p_len]) < LINEWID()) + peeol(); + } +} + + +void +HeaderPaintCursor(void) +{ + movecursor(ods.p_line, ucs4_str_width_ptr_to_ptr(&ods.cur_l->text[0], &ods.cur_l->text[ods.p_ind])+headents[ods.cur_e].prwid); +} + + + +/* + * FormatLines - Place the given text at the front of the given line->text + * making sure to properly format the line, then check + * all lines below for proper format. + * + * notes: + * Not much optimization at all. Right now, it recursively + * fixes all remaining lines in the entry. Some speed might + * gained if this was built to iteratively scan the lines. + * + * returns: + * -1 on error + * FALSE if only this line is changed + * TRUE if text below the first line is changed + */ +int +FormatLines(struct hdr_line *h, /* where to begin formatting */ + char *utf8_instr, /* input string */ + int maxwid, /* max width of a line */ + int break_on_comma, /* break lines on commas */ + int quoted) /* this line inside quotes */ +{ + int retval = FALSE; + int i, l, len; + char *utf8; + UCS *ostr; /* pointer to output string */ + UCS *free_istr = NULL; + UCS *istr = NULL; + UCS *breakp; /* pointer to line break */ + UCS *bp, *tp; /* temporary pointers */ + UCS *buf; /* string to add later */ + struct hdr_line *nlp, *lp; + + ostr = h->text; + nlp = h->next; + if(utf8_instr) + free_istr = istr = utf8_to_ucs4_cpystr(utf8_instr); + + len = ucs4_strlen(istr) + ucs4_strlen(ostr); + if((buf = (UCS *) malloc((len+10) * sizeof(*buf))) == NULL){ + if(free_istr) + fs_give((void **) &free_istr); + + return(-1); + } + + if(ucs4_str_width(istr) + ucs4_str_width(ostr) >= maxwid){ /* break then fixup below */ + + if((l=ucs4_str_width(istr)) < maxwid){ /* room for more */ + + if(break_on_comma && (bp = ucs4_strqchr(istr, ',', "ed, -1))){ + bp += (bp[1] == ' ') ? 2 : 1; + for(tp = bp; *tp && *tp == ' '; tp++) + ; + + ucs4_strncpy(buf, tp, len+10); + buf[len+10-1] = '\0'; + ucs4_strncat(buf, ostr, len+10-ucs4_strlen(buf)-1); + buf[len+10-1] = '\0'; + + for(i = 0; &istr[i] < bp; i++) + ostr[i] = istr[i]; + + ostr[i] = '\0'; + retval = TRUE; + } + else{ + breakp = break_point(ostr, maxwid-ucs4_str_width(istr), + break_on_comma ? ',' : ' ', + break_on_comma ? "ed : NULL); + + if(breakp == ostr){ /* no good breakpoint */ + if(break_on_comma && *breakp == ','){ + breakp = ostr + 1; + retval = TRUE; + } + else if(ucs4_strchr(istr,(break_on_comma && !quoted)?',':' ')){ + ucs4_strncpy(buf, ostr, len+10); + buf[len+10-1] = '\0'; + ucs4_strncpy(ostr, istr, HLSZ); + ostr[HLSZ-1] = '\0'; + } + else{ /* istr's broken as we can get it */ + /* + * Break at maxwid - width of istr + */ + breakp = ucs4_particular_width(ostr, maxwid - ucs4_str_width(istr)-1); + retval = TRUE; + } + } + else + retval = TRUE; + + if(retval){ + ucs4_strncpy(buf, breakp, len+10); /* save broken line */ + buf[len+10-1] = '\0'; + if(breakp == ostr){ + ucs4_strncpy(ostr, istr, HLSZ); /* simple if no break */ + ostr[HLSZ-1] = '\0'; + } + else{ + *breakp = '\0'; /* more work to break it */ + i = ucs4_strlen(istr); + /* + * shift ostr i chars + */ + for(bp=breakp; bp >= ostr && i; bp--) + *(bp+i) = *bp; + for(tp=ostr, bp=istr; *bp != '\0'; tp++, bp++) + *tp = *bp; /* then add istr */ + } + } + } + } + /* + * Short-circuit the recursion in this case. + * No time right now to figure out how to do it better. + */ + else if(l > 2*maxwid){ + UCS *istrp, *saveostr = NULL; + + retval = TRUE; + if(ostr && *ostr) + saveostr = ucs4_cpystr(ostr); + + istrp = istr; + while(l > 2*maxwid){ + if(break_on_comma || maxwid == 1){ + breakp = (!(bp = ucs4_strqchr(istrp, ',', "ed, maxwid)) + || ucs4_str_width_ptr_to_ptr(istrp, bp) >= maxwid || maxwid == 1) + ? ucs4_particular_width(istrp, maxwid) + : bp + ((bp[1] == ' ') ? 2 : 1); + } + else{ + breakp = break_point(istrp, maxwid, ' ', NULL); + + if(breakp == istrp) /* no good break point */ + breakp = ucs4_particular_width(istrp, maxwid-1); + } + + for(tp=ostr,bp=istrp; bp < breakp; tp++, bp++) + *tp = *bp; + + *tp = '\0'; + l -= ucs4_str_width_ptr_to_ptr(istrp, breakp); + istrp = breakp; + + if((lp = HALLOC()) == NULL){ + emlwrite("Can't allocate any more lines for header!", NULL); + free(buf); + if(free_istr) + fs_give((void **) &free_istr); + + return(-1); + } + + lp->next = h->next; + if(h->next) + h->next->prev = lp; + + h->next = lp; + lp->prev = h; + lp->text[0] = '\0'; + h = h->next; + ostr = h->text; + } + + /* + * Now l is still > maxwid. Do it the recursive way, + * like the else clause below. Surely we could fix up the + * flow control some here, but this works for now. + */ + + nlp = h->next; + istr = istrp; + if(saveostr){ + ucs4_strncpy(ostr, saveostr, HLSZ); + ostr[HLSZ-1] = '\0'; + fs_give((void **) &saveostr); + } + + if(break_on_comma || maxwid == 1){ + breakp = (!(bp = ucs4_strqchr(istrp, ',', "ed, maxwid)) + || ucs4_str_width_ptr_to_ptr(istrp, bp) >= maxwid || maxwid == 1) + ? ucs4_particular_width(istrp, maxwid) + : bp + ((bp[1] == ' ') ? 2 : 1); + } + else{ + breakp = break_point(istrp, maxwid, ' ', NULL); + + if(breakp == istrp) /* no good break point */ + breakp = ucs4_particular_width(istrp, maxwid-1); + } + + ucs4_strncpy(buf, breakp, len+10); /* save broken line */ + buf[len+10-1] = '\0'; + ucs4_strncat(buf, ostr, len+10-ucs4_strlen(buf)-1); /* add line that was there */ + buf[len+10-1] = '\0'; + + for(tp=ostr,bp=istr; bp < breakp; tp++, bp++) + *tp = *bp; + + *tp = '\0'; + } + else{ /* utf8_instr > maxwid ! */ + if(break_on_comma || maxwid == 1){ + breakp = (!(bp = ucs4_strqchr(istr, ',', "ed, maxwid)) + || ucs4_str_width_ptr_to_ptr(istr, bp) >= maxwid || maxwid == 1) + ? ucs4_particular_width(istr, maxwid) + : bp + ((bp[1] == ' ') ? 2 : 1); + } + else{ + breakp = break_point(istr, maxwid, ' ', NULL); + + if(breakp == istr) /* no good break point */ + breakp = ucs4_particular_width(istr, maxwid-1); + } + + ucs4_strncpy(buf, breakp, len+10); /* save broken line */ + buf[len+10-1] = '\0'; + ucs4_strncat(buf, ostr, len+10-ucs4_strlen(buf)-1); /* add line that was there */ + buf[len+10-1] = '\0'; + + for(tp=ostr,bp=istr; bp < breakp; tp++, bp++) + *tp = *bp; + + *tp = '\0'; + } + + if(nlp == NULL){ /* no place to add below? */ + if((lp = HALLOC()) == NULL){ + emlwrite("Can't allocate any more lines for header!", NULL); + free(buf); + if(free_istr) + fs_give((void **) &free_istr); + + return(-1); + } + + if(TERM_OPTIMIZE && (i = physical_line(h)) != -1) + scrolldown(wheadp, i - 1, 1); + + h->next = lp; /* fix up links */ + lp->prev = h; + lp->next = NULL; + lp->text[0] = '\0'; + nlp = lp; + retval = TRUE; + } + } + else{ /* combined width < max */ + buf[0] = '\0'; + if(istr && *istr){ + ucs4_strncpy(buf, istr, len+10); /* insert istr before ostr */ + buf[len+10-1] = '\0'; + + ucs4_strncat(buf, ostr, len+10-ucs4_strlen(buf)-1); + buf[len+10-1] = '\0'; + + ucs4_strncpy(ostr, buf, HLSZ); /* copy back to ostr */ + ostr[HLSZ-1] = '\0'; + } + + *buf = '\0'; + breakp = NULL; + + if(break_on_comma && (breakp = ucs4_strqchr(ostr, ',', "ed, -1))){ + breakp += (breakp[1] == ' ') ? 2 : 1; + ucs4_strncpy(buf, breakp, len+10); + buf[len+10-1] = '\0'; + *breakp = '\0'; + + if(*buf && !nlp){ + if((lp = HALLOC()) == NULL){ + emlwrite("Can't allocate any more lines for header!",NULL); + free(buf); + if(free_istr) + fs_give((void **) &free_istr); + + return(-1); + } + + if(TERM_OPTIMIZE && (i = physical_line(h)) != -1) + scrolldown(wheadp, i - 1, 1); + + h->next = lp; /* fix up links */ + lp->prev = h; + lp->next = NULL; + lp->text[0] = '\0'; + nlp = lp; + retval = TRUE; + } + } + + if(nlp){ + if(!*buf && !breakp){ + if(ucs4_str_width(ostr) + ucs4_str_width(nlp->text) >= maxwid){ + breakp = break_point(nlp->text, maxwid-ucs4_str_width(ostr), + break_on_comma ? ',' : ' ', + break_on_comma ? "ed : NULL); + + if(breakp == nlp->text){ /* commas this line? */ + for(tp=ostr; *tp && *tp != ' '; tp++) + ; + + if(!*tp){ /* no commas, get next best */ + breakp += maxwid - ucs4_str_width(ostr) - 1; + retval = TRUE; + } + else + retval = FALSE; + } + else + retval = TRUE; + + if(retval){ /* only if something to do */ + for(tp = &ostr[ucs4_strlen(ostr)],bp=nlp->text; bptext, bp=breakp; *bp != '\0'; tp++, bp++) + *tp = *bp; /* shift next line to left */ + *tp = '\0'; + } + } + else{ + ucs4_strncat(ostr, nlp->text, HLSZ-ucs4_strlen(ostr)-1); + ostr[HLSZ-1] = '\0'; + + if(TERM_OPTIMIZE && (i = physical_line(nlp)) != -1) + scrollup(wheadp, i, 1); + + hldelete(nlp); + + if(!(nlp = h->next)){ + free(buf); + if(free_istr) + fs_give((void **) &free_istr); + + return(TRUE); /* can't go further */ + } + else + retval = TRUE; /* more work to do? */ + } + } + } + else{ + free(buf); + if(free_istr) + fs_give((void **) &free_istr); + + return(FALSE); + } + + } + + utf8 = ucs4_to_utf8_cpystr(buf); + free(buf); + if(free_istr) + fs_give((void **) &free_istr); + + if(utf8){ + int rv; + + i = FormatLines(nlp, utf8, maxwid, break_on_comma, quoted); + fs_give((void **) &utf8); + switch(i){ + case -1: /* bubble up worst case */ + rv = -1; + break; + case FALSE: + if(retval == FALSE){ + rv = FALSE; + break; + } + default: + rv = TRUE; + } + + return(rv); + } + else + return(-1); +} + +/* + * Format the lines before parsing attachments so we + * don't expand a bunch of attachments that we don't + * have the buffer space for. + */ +int +FormatSyncAttach(void) +{ + FormatLines(headents[ods.cur_e].hd_text, "", + term.t_ncol - headents[ods.cur_e].prwid, + headents[ods.cur_e].break_on_comma, 0); + return(SyncAttach()); +} + + +/* + * PaintHeader - do the work of displaying the header from the given + * physical screen line the end of the header. + * + * 17 July 91 - fixed reshow to deal with arbitrarily large headers. + */ +void +PaintHeader(int line, /* physical line on screen */ + int clear) /* clear before painting */ +{ + struct hdr_line *lp; + int curline; + int curindex; + int curoffset; + UCS buf[NLINE]; + UCS *bufp; + int i, e, w; + + if(clear) + pclear(COMPOSER_TOP_LINE, ComposerTopLine-1); + + curline = COMPOSER_TOP_LINE; + curindex = curoffset = 0; + + for(lp = ods.top_l, e = ods.top_e; ; curline++){ + if((curline == line) || ((lp = next_hline(&e, lp)) == NULL)) + break; + } + + while(headents[e].name != NULL){ /* begin to redraw */ + while(lp != NULL){ + buf[0] = '\0'; + if((!lp->prev || curline == COMPOSER_TOP_LINE) && !curoffset){ + if(InvertPrompt(e, (e == ods.cur_e && ComposerEditing)) == -1 + && !is_blank(curline, 0, headents[e].prwid)){ + for(i = 0; i < headents[e].prwid; i++) + buf[i] = ' '; + + buf[i] = '\0'; + } + } + else if(!is_blank(curline, 0, headents[e].prwid)){ + for(i = 0; i < headents[e].prwid; i++) + buf[i] = ' '; + + buf[i] = '\0'; + } + + if(*(bufp = buf) != '\0'){ /* need to paint? */ + movecursor(curline, 0); /* paint the line... */ + while(*bufp != '\0') + pputc(*bufp++, 0); + } + + bufp = &(lp->text[curindex]); /* skip chars already there */ + curoffset += headents[e].prwid; + curindex = index_from_col(curline, curoffset); + while(pscr(curline, curindex) != NULL && + *bufp == pscr(curline, curindex)->c && *bufp != '\0'){ + w = wcellwidth(*bufp); + curoffset += (w >= 0 ? w : 1); + ++bufp; + ++curindex; + if(curoffset >= term.t_ncol) + break; + } + + if(*bufp != '\0'){ /* need to move? */ + movecursor(curline, curoffset); + while(*bufp != '\0'){ /* display what's not there */ + pputc(*bufp, 0); + w = wcellwidth(*bufp); + curoffset += (w >= 0 ? w : 1); + ++bufp; + ++curindex; + } + } + + if(curoffset < term.t_ncol){ + movecursor(curline, curoffset); + peeol(); + } + curline++; + curindex = curoffset = 0; + if(curline >= BOTTOM()) + break; + + lp = lp->next; + } + + if(curline >= BOTTOM()) + return; /* don't paint delimiter */ + + while(headents[++e].name != NULL) + if(headents[e].display_it){ + lp = headents[e].hd_text; + break; + } + } + + display_delimiter(ComposerEditing ? 0 : 1); +} + + + +/* + * PaintBody() - generic call to handle repainting everything BUT the + * header + * + * notes: + * The header redrawing in a level 0 body paint gets done + * in update() + */ +void +PaintBody(int level) +{ + curwp->w_flag |= WFHARD; /* make sure framing's right */ + if(level == 0) /* specify what to update */ + sgarbf = TRUE; + + update(); /* display message body */ + + if(level == 0 && ComposerEditing){ + mlerase(); /* clear the error line */ + ShowPrompt(); + } +} + + +/* + * display_for_send - paint the composer from the top line and return. + */ +void +display_for_send(void) +{ + int i = 0; + struct hdr_line *l; + + /* if first header line isn't displayed, there's work to do */ + if(headents && ((l = first_hline(&i)) != ods.top_l + || ComposerTopLine == COMPOSER_TOP_LINE + || !ods.p_line)){ + struct on_display orig_ods; + int orig_edit = ComposerEditing, + orig_ct_line = ComposerTopLine; + + /* + * fake that the cursor's in the first header line + * and force repaint... + */ + orig_ods = ods; + ods.cur_e = i; + ods.top_l = ods.cur_l = l; + ods.top_e = ods.cur_e; + ods.p_line = COMPOSER_TOP_LINE; + ComposerEditing = TRUE; /* to fool update() */ + setimark(FALSE, 1); /* remember where we were */ + gotobob(FALSE, 1); + + UpdateHeader(0); /* redraw whole enchilada */ + PaintHeader(COMPOSER_TOP_LINE, TRUE); + PaintBody(0); + + ods = orig_ods; /* restore original state */ + ComposerEditing = orig_edit; + ComposerTopLine = curwp->w_toprow = orig_ct_line; + curwp->w_ntrows = BOTTOM() - ComposerTopLine; + swapimark(FALSE, 1); + + /* in case we don't exit, set up restoring the screen */ + sgarbf = TRUE; /* force redraw */ + } +} + + +/* + * ArrangeHeader - set up display parm such that header is reasonably + * displayed + */ +void +ArrangeHeader(void) +{ + int e; + register struct hdr_line *l; + + ods.p_line = ods.p_ind = 0; + e = ods.top_e = 0; + l = ods.top_l = headents[e].hd_text; + while(headents[e+1].name || (l && l->next)) + if((l = next_sel_hline(&e, l)) != NULL){ + ods.cur_l = l; + ods.cur_e = e; + } + + UpdateHeader(1); +} + + +/* + * ComposerHelp() - display mail help in a context sensitive way + * based on the level passed ... + */ +int +ComposerHelp(int level) +{ + char buf[80]; + VARS_TO_SAVE *saved_state; + + curwp->w_flag |= WFMODE; + sgarbf = TRUE; + + if(level < 0 || !headents[level].name){ + (*term.t_beep)(); + emlwrite("Sorry, I can't help you with that.", NULL); + sleep(2); + return(FALSE); + } + + snprintf(buf, sizeof(buf), "Help for %s %.40s Field", + (Pmaster->pine_flags & MDHDRONLY) ? "Address Book" + : "Composer", + headents[level].name); + saved_state = save_pico_state(); + (*Pmaster->helper)(headents[level].help, buf, 1); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + + ttresize(); + picosigs(); /* restore altered handlers */ + return(TRUE); +} + + + +/* + * ToggleHeader() - set or unset pico values to the full screen size + * painting header if need be. + */ +int +ToggleHeader(int show) +{ + /* + * check to see if we need to display the header... + */ + if(show){ + UpdateHeader(0); /* figure bounds */ + PaintHeader(COMPOSER_TOP_LINE, FALSE); /* draw it */ + } + else{ + /* + * set bounds for no header display + */ + curwp->w_toprow = ComposerTopLine = COMPOSER_TOP_LINE; + curwp->w_ntrows = BOTTOM() - ComposerTopLine; + } + return(TRUE); +} + + + +/* + * HeaderLen() - return the length in lines of the exposed portion of the + * header + */ +int +HeaderLen(void) +{ + register struct hdr_line *lp; + int e; + int i; + + i = 1; + lp = ods.top_l; + e = ods.top_e; + while(lp != NULL){ + lp = next_hline(&e, lp); + i++; + } + return(i); +} + + + +/* + * first_hline() - return a pointer to the first displayable header line + * + * returns: + * 1) pointer to first displayable line in header and header + * entry, via side effect, that the first line is a part of + * 2) NULL if no next line, leaving entry at LASTHDR + */ +struct hdr_line * +first_hline(int *entry) +{ + /* init *entry so we're sure to start from the top */ + for(*entry = 0; headents[*entry].name; (*entry)++) + if(headents[*entry].display_it) + return(headents[*entry].hd_text); + + *entry = 0; + return(NULL); /* this shouldn't happen */ +} + + +/* + * first_sel_hline() - return a pointer to the first selectable header line + * + * returns: + * 1) pointer to first selectable line in header and header + * entry, via side effect, that the first line is a part of + * 2) NULL if no next line, leaving entry at LASTHDR + */ +struct hdr_line * +first_sel_hline(int *entry) +{ + /* init *entry so we're sure to start from the top */ + for(*entry = 0; headents[*entry].name; (*entry)++) + if(headents[*entry].display_it && !headents[*entry].blank) + return(headents[*entry].hd_text); + + *entry = 0; + return(NULL); /* this shouldn't happen */ +} + + + +/* + * next_hline() - return a pointer to the next line structure + * + * returns: + * 1) pointer to next displayable line in header and header + * entry, via side effect, that the next line is a part of + * 2) NULL if no next line, leaving entry at LASTHDR + */ +struct hdr_line * +next_hline(int *entry, struct hdr_line *line) +{ + if(line == NULL) + return(NULL); + + if(line->next == NULL){ + while(headents[++(*entry)].name != NULL){ + if(headents[*entry].display_it) + return(headents[*entry].hd_text); + } + --(*entry); + return(NULL); + } + else + return(line->next); +} + + +/* + * next_sel_hline() - return a pointer to the next selectable line structure + * + * returns: + * 1) pointer to next selectable line in header and header + * entry, via side effect, that the next line is a part of + * 2) NULL if no next line, leaving entry at LASTHDR + */ +struct hdr_line * +next_sel_hline(int *entry, struct hdr_line *line) +{ + if(line == NULL) + return(NULL); + + if(line->next == NULL){ + while(headents[++(*entry)].name != NULL){ + if(headents[*entry].display_it && !headents[*entry].blank) + return(headents[*entry].hd_text); + } + --(*entry); + return(NULL); + } + else + return(line->next); +} + + + +/* + * prev_hline() - return a pointer to the next line structure back + * + * returns: + * 1) pointer to previous displayable line in header and + * the header entry that the next line is a part of + * via side effect + * 2) NULL if no next line, leaving entry unchanged from + * the value it had on entry. + */ +struct hdr_line * +prev_hline(int *entry, struct hdr_line *line) +{ + if(line == NULL) + return(NULL); + + if(line->prev == NULL){ + int orig_entry; + + orig_entry = *entry; + while(--(*entry) >= 0){ + if(headents[*entry].display_it){ + line = headents[*entry].hd_text; + while(line->next != NULL) + line = line->next; + return(line); + } + } + + *entry = orig_entry; + return(NULL); + } + else + return(line->prev); +} + + +/* + * prev_sel_hline() - return a pointer to the previous selectable line + * + * returns: + * 1) pointer to previous selectable line in header and + * the header entry that the next line is a part of + * via side effect + * 2) NULL if no next line, leaving entry unchanged from + * the value it had on entry. + */ +struct hdr_line * +prev_sel_hline(int *entry, struct hdr_line *line) +{ + if(line == NULL) + return(NULL); + + if(line->prev == NULL){ + int orig_entry; + + orig_entry = *entry; + while(--(*entry) >= 0){ + if(headents[*entry].display_it && !headents[*entry].blank){ + line = headents[*entry].hd_text; + while(line->next != NULL) + line = line->next; + return(line); + } + } + + *entry = orig_entry; + return(NULL); + } + else + return(line->prev); +} + + + +/* + * first_requested_hline() - return pointer to first line that pico's caller + * asked that we start on. + */ +struct hdr_line * +first_requested_hline(int *ent) +{ + int i, reqfield; + struct hdr_line *rv = NULL; + + for(reqfield = -1, i = 0; headents[i].name; i++) + if(headents[i].start_here){ + headents[i].start_here = 0; /* clear old setting */ + if(reqfield < 0){ /* if not already, set up */ + headents[i].display_it = 1; /* make sure it's shown */ + *ent = reqfield = i; + rv = headents[i].hd_text; + } + } + + return(rv); +} + + + +/* + * UpdateHeader() - determines the best range of lines to be displayed + * using the global ods value for the current line and the + * top line, also sets ComposerTopLine and pico limits + * + * showtop -- Attempt to show all header lines if they'll fit. + * + * notes: + * This is pretty ugly because it has to keep the current line + * on the screen in a reasonable location no matter what. + * There are also a couple of rules to follow: + * 1) follow paging conventions of pico (ie, half page + * scroll) + * 2) if more than one page, always display last half when + * pline is toward the end of the header + * + * returns: + * TRUE if anything changed (side effects: new p_line, top_l + * top_e, and pico parms) + * FALSE if nothing changed + * + */ +int +UpdateHeader(int showtop) +{ + register struct hdr_line *lp; + int i, le; + int ret = FALSE; + int old_top = ComposerTopLine; + int old_p = ods.p_line; + + if(ods.p_line < COMPOSER_TOP_LINE || + ((ods.p_line == ComposerTopLine-2) ? 2: 0) + ods.p_line >= BOTTOM()){ + /* NewTop if cur header line is at bottom of screen or two from */ + /* the bottom of the screen if cur line is bottom header line */ + NewTop(showtop); /* get new top_l */ + ret = TRUE; + } + else{ /* make sure p_line's OK */ + i = COMPOSER_TOP_LINE; + lp = ods.top_l; + le = ods.top_e; + while(lp != ods.cur_l){ + /* + * this checks to make sure cur_l is below top_l and that + * cur_l is on the screen... + */ + if((lp = next_hline(&le, lp)) == NULL || ++i >= BOTTOM()){ + NewTop(0); + ret = TRUE; + break; + } + } + } + + ods.p_line = COMPOSER_TOP_LINE; /* find p_line... */ + lp = ods.top_l; + le = ods.top_e; + while(lp && lp != ods.cur_l){ + lp = next_hline(&le, lp); + ods.p_line++; + } + + if(!ret) + ret = !(ods.p_line == old_p); + + ComposerTopLine = ods.p_line; /* figure top composer line */ + while(lp && ComposerTopLine <= BOTTOM()){ + lp = next_hline(&le, lp); + ComposerTopLine += (lp) ? 1 : 2; /* allow for delim at end */ + } + + if(!ret) + ret = !(ComposerTopLine == old_top); + + if(wheadp->w_toprow != ComposerTopLine){ /* update pico params... */ + wheadp->w_toprow = ComposerTopLine; + wheadp->w_ntrows = ((i = BOTTOM() - ComposerTopLine) > 0) ? i : 0; + ret = TRUE; + } + return(ret); +} + + + +/* + * NewTop() - calculate a new top_l based on the cur_l + * + * showtop -- Attempt to show all the header lines if they'll fit + * + * returns: + * with ods.top_l and top_e pointing at a reasonable line + * entry + */ +void +NewTop(int showtop) +{ + register struct hdr_line *lp; + register int i; + int e; + + lp = ods.cur_l; + e = ods.cur_e; + i = showtop ? FULL_SCR() : HALF_SCR(); + + while(lp != NULL && (--i > 0)){ + ods.top_l = lp; + ods.top_e = e; + lp = prev_hline(&e, lp); + } +} + + + +/* + * display_delimiter() - just paint the header/message body delimiter with + * inverse value specified by state. + */ +void +display_delimiter(int state) +{ + UCS *bufp, *buf; + + if(ComposerTopLine - 1 >= BOTTOM()) /* silently forget it */ + return; + + buf = utf8_to_ucs4_cpystr((gmode & MDHDRONLY) ? "" : HDR_DELIM); + if(!buf) + return; + + bufp = buf; + + if(state == delim_ps){ /* optimize ? */ + for(delim_ps = 0; bufp[delim_ps] && pscr(ComposerTopLine-1,delim_ps) != NULL && pscr(ComposerTopLine-1,delim_ps)->c == bufp[delim_ps];delim_ps++) + ; + + if(bufp[delim_ps] == '\0' && !(gmode & MDHDRONLY)){ + delim_ps = state; + fs_give((void **) &buf); + return; /* already displayed! */ + } + } + + delim_ps = state; + + movecursor(ComposerTopLine - 1, 0); + if(state) + (*term.t_rev)(1); + + while(*bufp != '\0') + pputc(*bufp++, state ? 1 : 0); + + if(state) + (*term.t_rev)(0); + + peeol(); + fs_give((void **) &buf); +} + + + +/* + * InvertPrompt() - invert the prompt associated with header entry to state + * state (true if invert, false otherwise). + * returns: + * non-zero if nothing done + * 0 if prompt inverted successfully + * + * notes: + * come to think of it, this func and the one above could + * easily be combined + */ +int +InvertPrompt(int entry, int state) +{ + UCS *buf, *bufp; + UCS *end; + int i; + + buf = utf8_to_ucs4_cpystr(headents[entry].prompt); /* fresh prompt paint */ + if(!buf) + return(-1); + + bufp = buf; + if((i = entry_line(entry, FALSE)) == -1){ + fs_give((void **) &buf); + return(-1); /* silently forget it */ + } + + end = buf + ucs4_strlen(buf); + + /* + * Makes sure that the prompt doesn't take up more than prwid of screen space. + * The caller should do that, too, in order to make it look right so + * this should most likely be a no-op + */ + if(ucs4_str_width_ptr_to_ptr(buf, end) > headents[entry].prwid){ + end = ucs4_particular_width(buf, headents[entry].prwid); + *end = '\0'; + } + + if(entry < 16 && (invert_ps&(1<c == bufp[j]; j++) + ; + + if(bufp[j] == '\0'){ + if(state) + invert_ps |= 1< 16, cannot be stored in invert_ps */ + if(state) + invert_ps |= 1<rich_header && h->name != NULL; h++) + ; + + is_on = h->display_it; + for(h = headents; h->name != NULL; h++) + if(h->rich_header) + h->display_it = ! is_on; + + return(is_on); +} + + + +/* + * entry_line() - return the physical line on the screen associated + * with the given header entry field. Note: the field + * may span lines, so if the last char is set, return + * the appropriate value. + * + * returns: + * 1) physical line number of entry + * 2) -1 if entry currently not on display + */ +int +entry_line(int entry, int lastchar) +{ + register int p_line = COMPOSER_TOP_LINE; + int i; + register struct hdr_line *line; + + for(line = ods.top_l, i = ods.top_e; + headents && headents[i].name && i <= entry; + p_line++){ + if(p_line >= BOTTOM()) + break; + if(i == entry){ + if(lastchar){ + if(line->next == NULL) + return(p_line); + } + else if(line->prev == NULL) + return(p_line); + else + return(-1); + } + line = next_hline(&i, line); + } + return(-1); +} + + + +/* + * physical_line() - return the physical line on the screen associated + * with the given header line pointer. + * + * returns: + * 1) physical line number of entry + * 2) -1 if entry currently not on display + */ +int +physical_line(struct hdr_line *l) +{ + register int p_line = COMPOSER_TOP_LINE; + register struct hdr_line *lp; + int i; + + for(lp=ods.top_l, i=ods.top_e; headents[i].name && lp != NULL; p_line++){ + if(p_line >= BOTTOM()) + break; + + if(lp == l) + return(p_line); + + lp = next_hline(&i, lp); + } + return(-1); +} + + + +/* + * call_builder() - resolve any nicknames in the address book associated + * with the given entry... + * + * NOTES: + * + * BEWARE: this function can cause cur_l and top_l to get lost so BE + * CAREFUL before and after you call this function!!! + * + * There could to be something here to resolve cur_l and top_l + * reasonably into the new linked list for this entry. + * + * The reason this would mostly work without it is resolve_niks gets + * called for the most part in between fields. Since we're moving + * to the beginning or end (i.e. the next/prev pointer in the old + * freed cur_l is NULL) of the next entry, we get a new cur_l + * pointing at a good line. Then since top_l is based on cur_l in + * NewTop() we have pretty much lucked out. + * + * Where we could get burned is in a canceled exit (ctrl|x). Here + * nicknames get resolved into addresses, which invalidates cur_l + * and top_l. Since we don't actually leave, we could begin editing + * again with bad pointers. This would usually results in a nice + * core dump. + * + * NOTE: The mangled argument is a little strange. It's used on both + * input and output. On input, if it is not set, then that tells the + * builder not to do anything that might take a long time, like a + * white pages lookup. On return, it tells the caller that the screen + * and signals may have been mangled so signals should be reset, window + * resized, and screen redrawn. + * + * RETURNS: + * > 0 if any names where resolved, otherwise + * 0 if not, or + * < 0 on error + * -1: move to next line + * -2: don't move off this line + */ +int +call_builder(struct headerentry *entry, int *mangled, char **err) +{ + register int retval = 0; + register int i; + register struct hdr_line *line; + int quoted = 0; + int sbuflen; + char *sbuf; + char *s = NULL; + char *tmp; + struct headerentry *e; + BUILDER_ARG *nextarg, *arg = NULL, *headarg = NULL; + VARS_TO_SAVE *saved_state; + + if(!entry->builder) + return(0); + + line = entry->hd_text; + sbuflen = 0; + while(line != NULL){ + sbuflen += (6*term.t_ncol); + line = line->next; + } + + if((sbuf=(char *)malloc(sbuflen * sizeof(*sbuf))) == NULL){ + emlwrite("Can't malloc space to expand address", NULL); + return(-1); + } + + *sbuf = '\0'; + + /* + * cat the whole entry into one string... + */ + line = entry->hd_text; + while(line != NULL){ + i = ucs4_strlen(line->text); + + /* + * To keep pine address builder happy, addresses should be separated + * by ", ". Add this space if needed, otherwise... + * (This is some ancient requirement that is no longer needed.) + * + * If this line is NOT a continuation of the previous line, add + * white space for pine's address builder if its not already there... + * (This is some ancient requirement that is no longer needed.) + * + * Also if it's not a continuation (i.e., there's already and addr on + * the line), and there's another line below, treat the new line as + * an implied comma. + * (This should only be done for address-type lines, not for regular + * text lines like subjects. Key off of the break_on_comma bit which + * should only be set on those that won't mind a comma being added.) + */ + if(entry->break_on_comma){ + UCS *space, commaspace[3]; + + commaspace[0] = ','; + commaspace[1] = ' '; + commaspace[2] = '\0'; + space = commaspace+1; + + if(i && line->text[i-1] == ','){ + ucs4_strncat(line->text, space, HLSZ-i-1); /* help address builder */ + line->text[HLSZ-1] = '\0'; + } + else if(line->next != NULL && !strend(line->text, ',')){ + if(ucs4_strqchr(line->text, ',', "ed, -1)){ + ucs4_strncat(line->text, commaspace, HLSZ-i-1); /* implied comma */ + line->text[HLSZ-1] = '\0'; + } + } + else if(line->prev != NULL && line->next != NULL){ + if(ucs4_strchr(line->prev->text, ' ') != NULL + && line->text[i-1] != ' '){ + ucs4_strncat(line->text, space, HLSZ-i-1); + line->text[HLSZ-1] = '\0'; + } + } + } + + tmp = ucs4_to_utf8_cpystr(line->text); + if(tmp){ + strncat(sbuf, tmp, sbuflen-strlen(sbuf)-1); + sbuf[sbuflen-1] = '\0'; + fs_give((void **) &tmp); + } + + line = line->next; + } + + if(entry->affected_entry){ + /* check if any non-sticky affected entries */ + for(e = entry->affected_entry; e; e = e->next_affected) + if(!e->sticky) + break; + + /* there is at least one non-sticky so make a list to pass */ + if(e){ + for(e = entry->affected_entry; e; e = e->next_affected){ + if(!arg){ + headarg = arg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG)); + if(!arg){ + emlwrite("Can't malloc space for fcc", NULL); + return(-1); + } + else{ + arg->next = NULL; + arg->tptr = NULL; + arg->aff = &(e->bldr_private); + arg->me = &(entry->bldr_private); + } + } + else{ + nextarg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG)); + if(!nextarg){ + emlwrite("Can't malloc space for fcc", NULL); + return(-1); + } + else{ + nextarg->next = NULL; + nextarg->tptr = NULL; + nextarg->aff = &(e->bldr_private); + nextarg->me = &(entry->bldr_private); + arg->next = nextarg; + arg = arg->next; + } + } + + if(!e->sticky){ + line = e->hd_text; + arg->tptr = ucs4_to_utf8_cpystr(line->text); + } + } + } + } + + /* + * Even if there are no affected entries, we still need the arg + * to pass the "me" pointer. + */ + if(!headarg){ + headarg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG)); + if(!headarg){ + emlwrite("Can't malloc space", NULL); + return(-1); + } + else{ + headarg->next = NULL; + headarg->tptr = NULL; + headarg->aff = NULL; + headarg->me = &(entry->bldr_private); + } + } + + /* + * The builder may make a new call back to pico() so we save and + * restore the pico state. + */ + saved_state = save_pico_state(); + retval = (*entry->builder)(sbuf, &s, err, headarg, mangled); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + + if(mangled && *mangled & BUILDER_MESSAGE_DISPLAYED){ + *mangled &= ~ BUILDER_MESSAGE_DISPLAYED; + if(mpresf == FALSE) + mpresf = TRUE; + } + + if(mangled && *mangled & BUILDER_FOOTER_MANGLED){ + *mangled &= ~ BUILDER_FOOTER_MANGLED; + sgarbk = TRUE; + pclear(term.t_nrow-1, term.t_nrow); + } + + if(retval >= 0){ + if(strcmp(sbuf, s)){ + line = entry->hd_text; + InitEntryText(s, entry); /* arrange new one */ + zotentry(line); /* blast old list o'entries */ + entry->dirty = 1; /* mark it dirty */ + retval = 1; + } + + for(e = entry->affected_entry, arg = headarg; + e; + e = e->next_affected, arg = arg ? arg->next : NULL){ + if(!e->sticky){ + line = e->hd_text; + tmp = ucs4_to_utf8_cpystr(line->text); + if(strcmp(tmp, arg ? arg->tptr : "")){ /* it changed */ + /* make sure they see it if changed */ + e->display_it = 1; + InitEntryText(arg ? arg->tptr : "", e); + if(line == ods.top_l) + ods.top_l = e->hd_text; + + zotentry(line); /* blast old list o'entries */ + e->dirty = 1; /* mark it dirty */ + retval = 1; + } + + if(tmp) + fs_give((void **) &tmp); + } + } + } + + if(s) + free(s); + + for(arg = headarg; arg; arg = nextarg){ + /* Don't free xtra or me, they just point to headerentry data */ + nextarg = arg->next; + if(arg->tptr) + free(arg->tptr); + + free(arg); + } + + free(sbuf); + return(retval); +} + + +void +call_expander(void) +{ + char **s = NULL; + VARS_TO_SAVE *saved_state; + int expret; + + if(!Pmaster->expander) + return; + + /* + * Since expander may make a call back to pico() we need to + * save and restore pico state. + */ + if((saved_state = save_pico_state()) != NULL){ + + expret = (*Pmaster->expander)(headents, &s); + + restore_pico_state(saved_state); + free_pico_state(saved_state); + ttresize(); + picosigs(); + + if(expret > 0 && s){ + char *tbuf, *p; + int i, biggest = 100; + struct headerentry *e; + + /* + * Use tbuf to cat together multiple line entries before comparing. + */ + tbuf = (char *)malloc((biggest+1) * sizeof(*tbuf)); + for(e = headents, i=0; e->name != NULL; e++,i++){ + int sz = 0; + struct hdr_line *line; + + while(e->name && e->blank) + e++; + + if(e->name == NULL) + continue; + + for(line = e->hd_text; line != NULL; line = line->next){ + p = ucs4_to_utf8_cpystr(line->text); + if(p){ + sz += strlen(p); + fs_give((void **) &p); + } + } + + if(sz > biggest){ + biggest = sz; + free(tbuf); + tbuf = (char *)malloc((biggest+1) * sizeof(*tbuf)); + } + + tbuf[0] = '\0'; + for(line = e->hd_text; line != NULL; line = line->next){ + p = ucs4_to_utf8_cpystr(line->text); + if(p){ + strncat(tbuf, p, biggest+1-strlen(tbuf)-1); + tbuf[biggest] = '\0'; + fs_give((void **) &p); + } + } + + if(strcmp(tbuf, s[i])){ /* it changed */ + struct hdr_line *zline; + + line = zline = e->hd_text; + InitEntryText(s[i], e); + + /* + * If any of the lines for this entry are current or + * top, fix that. + */ + for(; line != NULL; line = line->next){ + if(line == ods.top_l) + ods.top_l = e->hd_text; + + if(line == ods.cur_l) + ods.cur_l = e->hd_text; + } + + zotentry(zline); /* blast old list o'entries */ + } + } + + free(tbuf); + } + + if(s){ + char **p; + + for(p = s; *p; p++) + free(*p); + + free(s); + } + } + + return; +} + + +/* + * strend - neglecting white space, returns TRUE if c is at the + * end of the given line. otherwise FALSE. + */ +int +strend(UCS *s, UCS ch) +{ + UCS *b; + + if(s == NULL || *s == '\0') + return(FALSE); + + for(b = &s[ucs4_strlen(s)] - 1; *b && ucs4_isspace(*b); b--){ + if(b == s) + return(FALSE); + } + + return(*b == ch); +} + + +/* + * ucs4_strqchr - returns pointer to first non-quote-enclosed occurance of ch in + * the given string. otherwise NULL. + * s -- the string + * ch -- the character we're looking for + * q -- q tells us if we start out inside quotes on entry and is set + * correctly on exit. + * m -- max characters we'll check for ch (set to -1 for no max) + */ +UCS * +ucs4_strqchr(UCS *s, UCS ch, int *q, int m) +{ + int quoted = (q) ? *q : 0; + + for(; s && *s && m != 0; s++, m--){ + if(*s == '"'){ + quoted = !quoted; + if(q) + *q = quoted; + } + + if(!quoted && *s == ch) + return(s); + } + + return(NULL); +} + + +/* + * KillHeaderLine() - kill a line in the header + * + * notes: + * This is pretty simple. Just using the emacs kill buffer + * and its accompanying functions to cut the text from lines. + * + * returns: + * TRUE if hldelete worked + * FALSE otherwise + */ +int +KillHeaderLine(struct hdr_line *l, int append) +{ + UCS *c; + int i = ods.p_ind; + int nl = TRUE; + + if(!append) + kdelete(); + + c = l->text; + if (gmode & MDDTKILL){ + if (c[i] == '\0') /* don't insert a new line after this line*/ + nl = FALSE; + /*put to be deleted part into kill buffer */ + for (i=ods.p_ind; c[i] != '\0'; i++) + kinsert(c[i]); + }else{ + while(*c != '\0') /* splat out the line */ + kinsert(*c++); + } + + if (nl) + kinsert('\n'); /* helpful to yank in body */ + +#ifdef _WINDOWS + mswin_killbuftoclip (kremove); +#endif + + if (gmode & MDDTKILL){ + if (l->text[0]=='\0'){ + + if(l->next && l->prev) + ods.cur_l = next_hline(&ods.cur_e, l); + else if(l->prev) + ods.cur_l = prev_hline(&ods.cur_e, l); + + if(l == ods.top_l) + ods.top_l = ods.cur_l; + + return(hldelete(l)); + } + else { + l->text[ods.p_ind]='\0'; /* delete part of the line from the cursor */ + return(TRUE); + } + }else{ + if(l->next && l->prev) + ods.cur_l = next_hline(&ods.cur_e, l); + else if(l->prev) + ods.cur_l = prev_hline(&ods.cur_e, l); + + if(l == ods.top_l) + ods.top_l = ods.cur_l; + + return(hldelete(l)); /* blast it */ + } +} + + + +/* + * SaveHeaderLines() - insert the saved lines in the list before the + * current line in the header + * + * notes: + * Once again, just using emacs' kill buffer and its + * functions. + * + * returns: + * TRUE if something good happend + * FALSE otherwise + */ +int +SaveHeaderLines(void) +{ + UCS *buf; /* malloc'd copy of buffer */ + UCS *bp; /* pointer to above buffer */ + register unsigned i; /* index */ + UCS *work_buf, *work_buf_begin; + char empty[1]; + int len, buf_len, work_buf_len, tentative_p_ind = 0; + struct hdr_line *travel, *tentative_cur_l = NULL; + + if(ksize()){ + if((bp = buf = (UCS *) malloc((ksize()+5) * sizeof(*buf))) == NULL){ + emlwrite("Can't malloc space for saved text", NULL); + return(FALSE); + } + } + else + return(FALSE); + + for(i=0; i < ksize(); i++) + if(kremove(i) != '\n') /* filter out newlines */ + *bp++ = (UCS) kremove(i); + + *bp = '\0'; + + while(--bp >= buf) /* kill trailing white space */ + if(*bp != ' '){ + if(ods.cur_l->text[0] != '\0'){ + if(*bp == '>'){ /* inserting an address */ + *++bp = ','; /* so add separator */ + *++bp = '\0'; + } + } + else{ /* nothing in field yet */ + if(*bp == ','){ /* so blast any extra */ + *bp = '\0'; /* separators */ + } + } + break; + } + + /* insert new text at the dot position */ + buf_len = ucs4_strlen(buf); + tentative_p_ind = ods.p_ind + buf_len; + work_buf_len = ucs4_strlen(ods.cur_l->text) + buf_len; + work_buf = (UCS *) malloc((work_buf_len + 1) * sizeof(UCS)); + if (work_buf == NULL) { + emlwrite("Can't malloc space for saved text", NULL); + return(FALSE); + } + + work_buf[0] = '\0'; + work_buf_begin = work_buf; + i = MIN(ods.p_ind, work_buf_len); + ucs4_strncpy(work_buf, ods.cur_l->text, i); + work_buf[i] = '\0'; + ucs4_strncat(work_buf, buf, work_buf_len+1-ucs4_strlen(work_buf)-1); + work_buf[work_buf_len] = '\0'; + ucs4_strncat(work_buf, &ods.cur_l->text[ods.p_ind], work_buf_len+1-ucs4_strlen(work_buf)-1); + work_buf[work_buf_len] = '\0'; + empty[0]='\0'; + ods.p_ind = 0; + + i = TRUE; + + /* insert text in HLSZ character chunks */ + while(work_buf_len + ods.p_ind > HLSZ) { + ucs4_strncpy(&ods.cur_l->text[ods.p_ind], work_buf, HLSZ-ods.p_ind); + work_buf += (HLSZ - ods.p_ind); + work_buf_len -= (HLSZ - ods.p_ind); + + if(FormatLines(ods.cur_l, empty, LINEWID(), + headents[ods.cur_e].break_on_comma, 0) == -1) { + i = FALSE; + break; + } else { + i = TRUE; + len = 0; + travel = ods.cur_l; + while (len < HLSZ){ + len += ucs4_strlen(travel->text); + if (len >= HLSZ) + break; + + /* + * This comes after the break above because it will + * be accounted for in the while loop below. + */ + if(!tentative_cur_l){ + if(tentative_p_ind <= ucs4_strlen(travel->text)) + tentative_cur_l = travel; + else + tentative_p_ind -= ucs4_strlen(travel->text); + } + + travel = travel->next; + } + + ods.cur_l = travel; + ods.p_ind = ucs4_strlen(travel->text) - len + HLSZ; + } + } + + /* insert the remainder of text */ + if (i != FALSE && work_buf_len > 0) { + ucs4_strncpy(&ods.cur_l->text[ods.p_ind], work_buf, HLSZ-ods.p_ind); + ods.cur_l->text[HLSZ-1] = '\0'; + work_buf = work_buf_begin; + free(work_buf); + + if(FormatLines(ods.cur_l, empty, LINEWID(), + headents[ods.cur_e].break_on_comma, 0) == -1) { + i = FALSE; + } else { + len = 0; + travel = ods.cur_l; + while (len < work_buf_len + ods.p_ind){ + if(!tentative_cur_l){ + if(tentative_p_ind <= ucs4_strlen(travel->text)) + tentative_cur_l = travel; + else + tentative_p_ind -= ucs4_strlen(travel->text); + } + + len += ucs4_strlen(travel->text); + if (len >= work_buf_len + ods.p_ind) + break; + + travel = travel->next; + } + + ods.cur_l = travel; + ods.p_ind = ucs4_strlen(travel->text) - len + work_buf_len + ods.p_ind; + if(tentative_cur_l + && tentative_p_ind >= 0 + && tentative_p_ind <= ucs4_strlen(tentative_cur_l->text)){ + ods.cur_l = tentative_cur_l; + ods.p_ind = tentative_p_ind; + } + } + } + + free(buf); + return(i); +} + + + +/* + * break_point - Break the given line at the most reasonable character breakch + * within maxwid max characters. + * + * returns: + * Pointer to the best break point in s, or + * Pointer to the beginning of s if no break point found + */ +UCS * +break_point(UCS *line, int maxwid, UCS breakch, int *quotedarg) +{ + UCS *bp; /* break point */ + int quoted; + + /* + * Start at maxwid and work back until first opportunity to break. + */ + bp = ucs4_particular_width(line, maxwid); + + /* + * Quoted should be set up for the start of line. Since we want + * to move to bp and work our way back we need to scan through the + * line up to bp setting quoted appropriately. + */ + if(quotedarg) + ucs4_strqchr(line, '\0', quotedarg, bp-line); + + quoted = quotedarg ? *quotedarg : 0; + + while(bp != line){ + if(breakch == ',' && *bp == '"') /* don't break on quoted ',' */ + quoted = !quoted; /* toggle quoted state */ + + if(*bp == breakch && !quoted){ + if(breakch == ' '){ + if(ucs4_str_width_ptr_to_ptr(line, bp+1) < maxwid){ + bp++; /* leave the ' ' */ + break; + } + } + else{ + /* + * if break char isn't a space, leave a space after + * the break char. + */ + if(!(ucs4_str_width_ptr_to_ptr(line, bp+1) >= maxwid + || (bp[1] == ' ' && ucs4_str_width_ptr_to_ptr(line, bp+2) >= maxwid))){ + bp += (bp[1] == ' ') ? 2 : 1; + break; + } + } + } + + bp--; + } + + if(quotedarg) + *quotedarg = quoted; + + return((quoted) ? line : bp); +} + + + + +/* + * hldelete() - remove the header line pointed to by l from the linked list + * of lines. + * + * notes: + * the case of first line in field is kind of bogus. since + * the array of headers has a pointer to the first line, and + * i don't want to worry about this too much, i just copied + * the line below and removed it rather than the first one + * from the list. + * + * returns: + * TRUE if it worked + * FALSE otherwise + */ +int +hldelete(struct hdr_line *l) +{ + register struct hdr_line *lp; + + if(l == NULL) + return(FALSE); + + if(l->next == NULL && l->prev == NULL){ /* only one line in field */ + l->text[0] = '\0'; + return(TRUE); /* no free only line in list */ + } + else if(l->next == NULL){ /* last line in field */ + l->prev->next = NULL; + } + else if(l->prev == NULL){ /* first line in field */ + ucs4_strncpy(l->text, l->next->text, HLSZ); + l->text[HLSZ-1] = '\0'; + lp = l->next; + if((l->next = lp->next) != NULL) + l->next->prev = l; + l = lp; + } + else{ /* some where in field */ + l->prev->next = l->next; + l->next->prev = l->prev; + } + + l->next = NULL; + l->prev = NULL; + free((char *)l); + return(TRUE); +} + + + +/* + * is_blank - returns true if the next n chars from coordinates row, col + * on display are spaces + */ +int +is_blank(int row, int col, int n) +{ + n += col; + for( ;col < n; col++){ + if(pscr(row, col) == NULL || pscr(row, col)->c != ' ') + return(0); + } + return(1); +} + + +/* + * ShowPrompt - display key help corresponding to the current header entry + */ +void +ShowPrompt(void) +{ + if(headents[ods.cur_e].key_label){ + menu_header[TO_KEY].name = "^T"; + menu_header[TO_KEY].label = headents[ods.cur_e].key_label; + KS_OSDATASET(&menu_header[TO_KEY], KS_OSDATAGET(&headents[ods.cur_e])); + } + else + menu_header[TO_KEY].name = NULL; + + if(Pmaster && Pmaster->exit_label) + menu_header[SEND_KEY].label = Pmaster->exit_label; + else if(gmode & (MDVIEW | MDHDRONLY)) + menu_header[SEND_KEY].label = (gmode & MDHDRONLY) ? "eXit/Save" : "eXit"; + else + menu_header[SEND_KEY].label = N_("Send"); + + if(gmode & MDVIEW){ + menu_header[CUT_KEY].name = NULL; + menu_header[DEL_KEY].name = NULL; + menu_header[UDEL_KEY].name = NULL; + } + else{ + menu_header[CUT_KEY].name = "^K"; + menu_header[DEL_KEY].name = "^D"; + menu_header[UDEL_KEY].name = "^U"; + } + + if(Pmaster->ctrlr_label){ + menu_header[RICH_KEY].label = Pmaster->ctrlr_label; + menu_header[RICH_KEY].name = "^R"; + } + else if(gmode & MDHDRONLY){ + menu_header[RICH_KEY].name = NULL; + } + else{ + menu_header[RICH_KEY].label = N_("Rich Hdr"); + menu_header[RICH_KEY].name = "^R"; + } + + if(gmode & MDHDRONLY){ + if(headents[ods.cur_e].fileedit){ + menu_header[PONE_KEY].name = "^_"; + menu_header[PONE_KEY].label = N_("Edit File"); + } + else + menu_header[PONE_KEY].name = NULL; + + menu_header[ATT_KEY].name = NULL; + } + else{ + menu_header[PONE_KEY].name = "^O"; + menu_header[PONE_KEY].label = N_("Postpone"); + + menu_header[ATT_KEY].name = "^J"; + } + + wkeyhelp(menu_header); +} + + +/* + * packheader - packup all of the header fields for return to caller. + * NOTE: all of the header info passed in, including address + * of the pointer to each string is contained in the + * header entry array "headents". + */ +int +packheader(void) +{ + register int i = 0; /* array index */ + register int count; /* count of chars in a field */ + register int retval = TRUE; + register char *bufp; + register struct hdr_line *line; + char *p; + + if(!headents) + return(TRUE); + + while(headents[i].name != NULL){ +#ifdef ATTACHMENTS + /* + * attachments are special case, already in struct we pass back + */ + if(headents[i].is_attach){ + i++; + continue; + } +#endif + + if(headents[i].blank){ + i++; + continue; + } + + /* + * count chars to see if we need a new malloc'd space for our + * array. + */ + line = headents[i].hd_text; + count = 0; + while(line != NULL){ + /* + * add one for possible concatination of a ' ' character ... + */ + p = ucs4_to_utf8_cpystr(line->text); + if(p){ + count += strlen(p); + if(p[0] && p[strlen(p)-1] == ',') + count++; + + fs_give((void **) &p); + } + + line = line->next; + } + + line = headents[i].hd_text; + if(count <= headents[i].maxlen){ + *headents[i].realaddr[0] = '\0'; + } + else{ + /* + * don't forget to include space for the null terminator!!!! + */ + if((bufp = (char *)malloc((count+1) * sizeof(char))) != NULL){ + *bufp = '\0'; + + free(*headents[i].realaddr); + *headents[i].realaddr = bufp; + headents[i].maxlen = count; + } + else{ + emlwrite("Can't make room to pack header field.", NULL); + retval = FALSE; + } + } + + if(retval != FALSE){ + int saw_current_line = 0; + + while(line != NULL){ + + /* pass the cursor offset back in Pmaster struct */ + if(headents[i].start_here && Pmaster && !saw_current_line){ + if(ods.cur_l == line) + saw_current_line++; + else + Pmaster->edit_offset += ucs4_strlen(line->text); + } + + p = ucs4_to_utf8_cpystr(line->text); + if(p){ + strncat(*headents[i].realaddr, p, headents[i].maxlen+1-strlen(*headents[i].realaddr)-1); + (*headents[i].realaddr)[headents[i].maxlen] = '\0'; + + if(p[0] && p[strlen(p)-1] == ','){ + strncat(*headents[i].realaddr, " ", headents[i].maxlen+1-strlen(*headents[i].realaddr)-1); + (*headents[i].realaddr)[headents[i].maxlen] = '\0'; + } + + fs_give((void **) &p); + } + + line = line->next; + } + } + + i++; + } + + return(retval); +} + + + +/* + * zotheader - free all malloc'd lines associated with the header structs + */ +void +zotheader(void) +{ + register struct headerentry *i; + + for(i = headents; headents && i->name; i++) + zotentry(i->hd_text); +} + + +/* + * zotentry - free malloc'd space associated with the given linked list + */ +void +zotentry(struct hdr_line *l) +{ + register struct hdr_line *ld, *lf = l; + + while((ld = lf) != NULL){ + lf = ld->next; + ld->next = ld->prev = NULL; + free((char *) ld); + } +} + + + +/* + * zotcomma - blast any trailing commas and white space from the end + * of the given line + */ +int +zotcomma(UCS *s) +{ + UCS *p; + int retval = FALSE; + + p = &s[ucs4_strlen(s)]; + while(--p >= s){ + if(*p != ' '){ + if(*p == ','){ + *p = '\0'; + retval = TRUE; + } + + return(retval); + } + } + + return(retval); +} + + +/* + * Save the current state of global variables so that we can restore + * them later. This is so we can call pico again. + * Also have to initialize some variables that normally would be set to + * zero on startup. + */ +VARS_TO_SAVE * +save_pico_state(void) +{ + VARS_TO_SAVE *ret; + extern int vtrow; + extern int vtcol; + extern int lbound; + extern VIDEO **vscreen; + extern VIDEO **pscreen; + extern int pico_all_done; + extern jmp_buf finstate; + extern UCS *pico_anchor; + + if((ret = (VARS_TO_SAVE *)malloc(sizeof(VARS_TO_SAVE))) == NULL) + return(ret); + + ret->vtrow = vtrow; + ret->vtcol = vtcol; + ret->lbound = lbound; + ret->vscreen = vscreen; + ret->pscreen = pscreen; + ret->ods = ods; + ret->delim_ps = delim_ps; + ret->invert_ps = invert_ps; + ret->pico_all_done = pico_all_done; + memcpy(ret->finstate, finstate, sizeof(jmp_buf)); + ret->pico_anchor = pico_anchor; + ret->Pmaster = Pmaster; + ret->fillcol = fillcol; + if((ret->pat = (UCS *)malloc(sizeof(UCS) * (ucs4_strlen(pat)+1))) != NULL) + ucs4_strncpy(ret->pat, pat, ucs4_strlen(pat)+1); + + ret->ComposerTopLine = ComposerTopLine; + ret->ComposerEditing = ComposerEditing; + ret->gmode = gmode; + ret->alt_speller = alt_speller; + ret->quote_str = glo_quote_str; + ret->wordseps = glo_wordseps; + ret->currow = currow; + ret->curcol = curcol; + ret->thisflag = thisflag; + ret->lastflag = lastflag; + ret->curgoal = curgoal; + ret->opertree = (char *) malloc(sizeof(char) * (strlen(opertree) + 1)); + if(ret->opertree != NULL) + strncpy(ret->opertree, opertree, strlen(opertree)+1); + + ret->curwp = curwp; + ret->wheadp = wheadp; + ret->curbp = curbp; + ret->bheadp = bheadp; + ret->km_popped = km_popped; + ret->mrow = term.t_mrow; + + /* Initialize for next pico call */ + wheadp = NULL; + curwp = NULL; + bheadp = NULL; + curbp = NULL; + + return(ret); +} + + +void +restore_pico_state(VARS_TO_SAVE *state) +{ + extern int vtrow; + extern int vtcol; + extern int lbound; + extern VIDEO **vscreen; + extern VIDEO **pscreen; + extern int pico_all_done; + extern jmp_buf finstate; + extern UCS *pico_anchor; + + clearcursor(); + vtrow = state->vtrow; + vtcol = state->vtcol; + lbound = state->lbound; + vscreen = state->vscreen; + pscreen = state->pscreen; + ods = state->ods; + delim_ps = state->delim_ps; + invert_ps = state->invert_ps; + pico_all_done = state->pico_all_done; + memcpy(finstate, state->finstate, sizeof(jmp_buf)); + pico_anchor = state->pico_anchor; + Pmaster = state->Pmaster; + if(Pmaster) + headents = Pmaster->headents; + + fillcol = state->fillcol; + if(state->pat) + ucs4_strncpy(pat, state->pat, NPAT); + + ComposerTopLine = state->ComposerTopLine; + ComposerEditing = state->ComposerEditing; + gmode = state->gmode; + alt_speller = state->alt_speller; + glo_quote_str = state->quote_str; + glo_wordseps = state->wordseps; + currow = state->currow; + curcol = state->curcol; + thisflag = state->thisflag; + lastflag = state->lastflag; + curgoal = state->curgoal; + if(state->opertree){ + strncpy(opertree, state->opertree, sizeof(opertree)); + opertree[sizeof(opertree)-1] = '\0'; + } + + curwp = state->curwp; + wheadp = state->wheadp; + curbp = state->curbp; + bheadp = state->bheadp; + km_popped = state->km_popped; + term.t_mrow = state->mrow; +} + + +void +free_pico_state(VARS_TO_SAVE *state) +{ + if(state->pat) + free(state->pat); + + if(state->opertree) + free(state->opertree); + + free(state); +} + + +/* + * Ok to call this twice in a row because it won't do anything the second + * time. + */ +void +fix_mangle_and_err(int *mangled, char **errmsg, char *name) +{ + if(mangled && *mangled){ + ttresize(); + picosigs(); + PaintBody(0); + *mangled = 0; + } + + if(errmsg && *errmsg){ + if(**errmsg){ + char err[500]; + + snprintf(err, sizeof(err), "%s field: %s", name, *errmsg); + (*term.t_beep)(); + emlwrite(err, NULL); + } + else + mlerase(); + + free(*errmsg); + *errmsg = NULL; + } +} + + +/* + * What is this for? + * This is so that the To line will be appended to by an Lcc + * entry unless the user types in the To line after the Lcc + * has already been set. + */ +void +mark_sticky(struct headerentry *h) +{ + if(h && (!h->sticky_special || h->bldr_private)) + h->sticky = 1; +} + + +#ifdef MOUSE +#undef HeaderEditor + +/* + * Wraper function for the real header editor. + * Does the important tasks of: + * 1) verifying that we _can_ edit the headers. + * 2) acting on the result code from the header editor. + */ +int +HeaderEditor(int f, int n) +{ + int retval; + + +#ifdef _WINDOWS + /* Sometimes we get here from a scroll callback, which + * is no good at all because mswin is not ready to process input and + * this _headeredit() will never do anything. + * Putting this test here was the most general solution I could think + * of. */ + if (!mswin_caninput()) + return (-1); +#endif + + retval = HeaderEditorWork(f, n); + if (retval == -3) { + retval = mousepress(0,0); + } + return (retval); +} +#endif diff --git a/pico/display.c b/pico/display.c new file mode 100644 index 00000000..2741be3e --- /dev/null +++ b/pico/display.c @@ -0,0 +1,3306 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: display.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 + * + * ======================================================================== + * + * Program: Display functions + * + */ + +/* + * The functions in this file handle redisplay. There are two halves, the + * ones that update the virtual display screen, and the ones that make the + * physical display screen the same as the virtual display screen. These + * functions use hints that are left in the windows by the commands. + * + */ + +#include "../c-client/mail.h" +#include "../c-client/utf8.h" + +#ifdef _WINDOWS +/* wingdi.h uses ERROR (!) and we aren't using the c-client ERROR so... */ +#undef ERROR +#endif + +#include "headers.h" +#include "../pith/charconv/filesys.h" +#include "../pith/charconv/utf8.h" + + +void vtmove(int, int); +void vtputc(CELL); +void vteeol(void); +void updateline(int, CELL *, CELL *, short *); +void updext(void); +void mlputi(int, int); +void pprints(int, int); +void mlputli(long, int); +void showCompTitle(void); +int nlforw(void); +int dumbroot(int, int); +int dumblroot(long, int); +unsigned cellwidth_ptr_to_ptr(CELL *pstart, CELL *pend); +unsigned vcellwidth_a_to_b(int row, int a, int b); +#ifdef _WINDOWS +void pico_config_menu_items (KEYMENU *); +int update_scroll (void); +#endif /* _WINDOWS */ + + +/* + * Standard pico keymenus... + */ +static KEYMENU menu_pico[] = { + {"^G", N_("Get Help"), KS_SCREENHELP}, {"^O", N_("WriteOut"), KS_SAVEFILE}, + {"^R", N_("Read File"), KS_READFILE}, {"^Y", N_("Prev Pg"), KS_PREVPAGE}, + {"^K", N_("Cut Text"), KS_NONE}, {"^C", N_("Cur Pos"), KS_CURPOSITION}, + {"^X", N_("Exit"), KS_EXIT}, {"^J", N_("Justify"), KS_JUSTIFY}, + {"^W", N_("Where is"), KS_WHEREIS}, {"^V", N_("Next Pg"), KS_NEXTPAGE}, + {"^U", NULL, KS_NONE}, +#ifdef SPELLER + {"^T", N_("To Spell"), KS_SPELLCHK} +#else + {"^D", N_("Del Char"), KS_NONE} +#endif +}; +#define UNCUT_KEY 10 + + +static KEYMENU menu_compose[] = { + {"^G", N_("Get Help"), KS_SCREENHELP}, {"^X", NULL, KS_SEND}, + {"^R", N_("Read File"), KS_READFILE}, {"^Y", N_("Prev Pg"), KS_PREVPAGE}, + {"^K", N_("Cut Text"), KS_NONE}, {"^O", N_("Postpone"), KS_POSTPONE}, + /* TRANSLATORS: Justify is to reformat a paragraph automatically */ + {"^C", N_("Cancel"), KS_CANCEL}, {"^J", N_("Justify"), KS_JUSTIFY}, + {NULL, NULL, KS_NONE}, {"^V", N_("Next Pg"), KS_NEXTPAGE}, + {"^U", NULL, KS_NONE}, +#ifdef SPELLER + {"^T", N_("To Spell"), KS_SPELLCHK} +#else + {"^D", N_("Del Char"), KS_NONE} +#endif +}; +#define EXIT_KEY 1 +#define PSTPN_KEY 5 +#define WHERE_KEY 8 + + +/* + * Definition's for pico's modeline + */ +#define PICO_TITLE " UW PICO %s" +#define PICO_MOD_MSG "Modified" +#define PICO_NEWBUF_MSG "New Buffer" + +#define WFDEBUG 0 /* Window flag debug. */ + +#define VFCHG 0x0001 /* Changed flag */ +#define VFEXT 0x0002 /* extended (beyond column 80) */ +#define VFREV 0x0004 /* reverse video status */ +#define VFREQ 0x0008 /* reverse video request */ + +int vtrow = 0; /* Row location of SW cursor */ +int vtcol = 0; /* Column location of SW cursor */ +int vtind = 0; /* Index into row array of SW cursor */ +int ttrow = FARAWAY; /* Row location of HW cursor */ +int ttcol = FARAWAY; /* Column location of HW cursor */ +int lbound = 0; /* leftmost column of current line + being displayed */ + +VIDEO **vscreen; /* Virtual screen. */ +VIDEO **pscreen; /* Physical screen. */ + + +/* + * Initialize the data structures used by the display code. The edge vectors + * used to access the screens are set up. The operating system's terminal I/O + * channel is set up. All the other things get initialized at compile time. + * The original window has "WFCHG" set, so that it will get completely + * redrawn on the first call to "update". + */ +int +vtinit(void) +{ + int i, j; + VIDEO *vp; + CELL ac; + + ac.c = ' '; + ac.a = 0; + + if(Pmaster == NULL) + vtterminalinfo(gmode & MDTCAPWINS); + + (*term.t_open)(); + + (*term.t_rev)(FALSE); + vscreen = (VIDEO **) malloc((term.t_nrow+1)*sizeof(VIDEO *)); + memset(vscreen, 0, (term.t_nrow+1)*sizeof(VIDEO *)); + if (vscreen == NULL){ + emlwrite("Allocating memory for virtual display failed.", NULL); + return(FALSE); + } + + pscreen = (VIDEO **) malloc((term.t_nrow+1)*sizeof(VIDEO *)); + memset(pscreen, 0, (term.t_nrow+1)*sizeof(VIDEO *)); + if (pscreen == NULL){ + free((void *)vscreen); + emlwrite("Allocating memory for physical display failed.", NULL); + return(FALSE); + } + + + for (i = 0; i <= term.t_nrow; ++i) { + vp = (VIDEO *) malloc(sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); + + if (vp == NULL){ + free((void *)vscreen); + free((void *)pscreen); + emlwrite("Allocating memory for virtual display lines failed.", + NULL); + return(FALSE); + } + else + for(j = 0; j < term.t_ncol; j++) + vp->v_text[j] = ac; + + vp->v_flag = 0; + vscreen[i] = vp; + + vp = (VIDEO *) malloc(sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); + + if (vp == NULL){ + free((void *)vscreen[i]); + while(--i >= 0){ + free((void *)vscreen[i]); + free((void *)pscreen[i]); + } + + free((void *)vscreen); + free((void *)pscreen); + emlwrite("Allocating memory for physical display lines failed.", + NULL); + return(FALSE); + } + else + for(j = 0; j < term.t_ncol; j++) + vp->v_text[j] = ac; + + vp->v_flag = 0; + pscreen[i] = vp; + } + + return(TRUE); +} + +int +vtterminalinfo(int termcap_wins) +{ + return((term.t_terminalinfo) ? (*term.t_terminalinfo)(termcap_wins) + : (Pmaster ? 0 : TRUE)); +} + + +/* + * Clean up the virtual terminal system, in anticipation for a return to the + * operating system. Move down to the last line and clear it out (the next + * system prompt will be written in the line). Shut down the channel to the + * terminal. + */ +void +vttidy(void) +{ + movecursor(term.t_nrow-1, 0); + peeol(); + movecursor(term.t_nrow, 0); + peeol(); + (*term.t_close)(); +} + + +/* + * Set the virtual cursor to the specified row and column on the virtual + * screen. There is no checking for nonsense values; this might be a good + * idea during the early stages. + */ +void +vtmove(int row, int col) +{ + vtrow = row; + vtcol = col; + + if(vtcol < 0) + vtind = -1; + else if(vtcol == 0) + vtind = vtcol; + else{ + /* + * This is unused so don't worry about it. + */ + assert(0); + } +} + + +/* + * Write a character to the virtual screen. The virtual row and column are + * updated. If the line is too long put a "$" in the last column. This routine + * only puts printing characters into the virtual terminal buffers. Only + * column overflow is checked. + */ +void +vtputc(CELL c) +{ + VIDEO *vp; + CELL ac; + int w; + + vp = vscreen[vtrow]; + ac.c = ' '; + ac.a = c.a; + + if (vtcol >= term.t_ncol) { + /* + * What's this supposed to be doing? This sets vtcol + * to the even multiple of 8 >= vtcol. Why are we doing that? + * Must make tab work correctly. + * 24 -> 24 + * 25 -> 32 + * ... + * 31 -> 32 + * 32 -> 32 + * 33 -> 40 + */ + vtcol = (vtcol + 0x07) & ~0x07; + ac.c = '$'; + + /* + * If we get to here that means that there must be characters + * past the right hand edge, so we want to put a $ character + * in the last visible character. It would be nice to replace + * the last visible character by a double-$ if it is double-width + * but we aren't doing that because we'd have to add up the widths + * starting at the left hand margin each time through. + */ + if(vtind > 0 && vtind <= term.t_ncol) + vp->v_text[vtind-1] = ac; + } + else if (c.c == '\t') { + do { + vtputc(ac); + } + while (((vtcol + (vtrow==currow ? lbound : 0)) & 0x07) != 0 && vtcol < term.t_ncol); + } + else if (ISCONTROL(c.c)){ + ac.c = '^'; + vtputc(ac); + ac.c = ((c.c & 0x7f) | 0x40); + vtputc(ac); + } + else{ + + /* + * Have to worry about what happens if we skip over 0 + * with a double-width character. There may be a better + * place to be setting vtind, or maybe we could make do + * without it. + */ + + w = wcellwidth((UCS) c.c); + w = (w >= 0 ? w : 1); + + if(vtcol == 0 || (vtcol < 0 && vtcol + w == 1)){ + vtind = 0; + if(vtcol < 0) + vtcol = 0; + } + + /* + * Double-width character overlaps right edge. + * Replace it with a $. + */ + if(vtcol + w > term.t_ncol){ + ac.c = '$'; + c = ac; + w = 1; + } + + if(vtind >= 0 && vtind < term.t_ncol) + vp->v_text[vtind++] = c; + + vtcol += w; + } +} + + +/* + * Erase from the end of the software cursor to the end of the line on which + * the software cursor is located. + */ +void +vteeol(void) +{ + register VIDEO *vp; + CELL c; + + c.c = ' '; + c.a = 0; + vp = vscreen[vtrow]; + + if(vtind >= 0) + while (vtind < term.t_ncol) + vp->v_text[vtind++] = c; + + vtcol = term.t_ncol; +} + + +/* + * Make sure that the display is right. This is a three part process. First, + * scan through all of the windows looking for dirty ones. Check the framing, + * and refresh the screen. Second, make sure that "currow" and "curcol" are + * correct for the current window. Third, make the virtual and physical + * screens the same. + */ +void +update(void) +{ + LINE *lp; + WINDOW *wp; + VIDEO *vp1; + VIDEO *vp2; + int i; + int j; + int scroll = 0; + CELL c; + +#if TYPEAH + if (typahead()) + return; +#endif + +#ifdef _WINDOWS + /* This tells our MS Windows module to not bother updating the + * cursor position while a massive screen update is in progress. + */ + mswin_beginupdate (); +#endif + +/* + * BUG: setting and unsetting whole region at a time is dumb. fix this. + */ + if(curwp->w_markp){ + unmarkbuffer(); + markregion(1); + } + + wp = wheadp; + + while (wp != NULL){ + /* Look at any window with update flags set on. */ + + if (wp->w_flag != 0){ + /* If not force reframe, check the framing. */ + + if ((wp->w_flag & WFFORCE) == 0){ + lp = wp->w_linep; + + for (i = 0; i < wp->w_ntrows; ++i){ + if (lp == wp->w_dotp) + goto out; + + if (lp == wp->w_bufp->b_linep) + break; + + lp = lforw(lp); + } + } + + /* Not acceptable, better compute a new value for the line at the + * top of the window. Then set the "WFHARD" flag to force full + * redraw. + */ + i = wp->w_force; + + if (i > 0){ + --i; + + if (i >= wp->w_ntrows) + i = wp->w_ntrows-1; + } + else if (i < 0){ + i += wp->w_ntrows; + + if (i < 0) + i = 0; + } + else if(TERM_OPTIMIZE){ + /* + * find dotp, if its been moved just above or below the + * window, use scrollxxx() to facilitate quick redisplay... + */ + lp = lforw(wp->w_dotp); + if(lp != wp->w_dotp){ + if(lp == wp->w_linep && lp != wp->w_bufp->b_linep){ + scroll = 1; + } + else { + lp = wp->w_linep; + for(j=0;j < wp->w_ntrows; ++j){ + if(lp != wp->w_bufp->b_linep) + lp = lforw(lp); + else + break; + } + if(lp == wp->w_dotp && j == wp->w_ntrows) + scroll = 2; + } + } + j = i = wp->w_ntrows/2; + } + else + i = wp->w_ntrows/2; + + lp = wp->w_dotp; + + while (i != 0 && lback(lp) != wp->w_bufp->b_linep){ + --i; + lp = lback(lp); + } + + /* + * this is supposed to speed things up by using tcap sequences + * to efficiently scroll the terminal screen. the thinking here + * is that its much faster to update pscreen[] than to actually + * write the stuff to the screen... + */ + if(TERM_OPTIMIZE){ + switch(scroll){ + case 1: /* scroll text down */ + j = j-i+1; /* add one for dot line */ + /* + * do we scroll down the header as well? Well, only + * if we're not editing the header, we've backed up + * to the top, and the composer is not being + * displayed... + */ + if(Pmaster && Pmaster->headents && !ComposerEditing + && (lback(lp) == wp->w_bufp->b_linep) + && (ComposerTopLine == COMPOSER_TOP_LINE)) + j += entry_line(1000, TRUE); /* Never > 1000 headers */ + + scrolldown(wp, -1, j); + break; + case 2: /* scroll text up */ + j = wp->w_ntrows - (j-i); /* we chose new top line! */ + if(Pmaster && j){ + /* + * do we scroll down the header as well? Well, only + * if we're not editing the header, we've backed up + * to the top, and the composer is not being + * displayed... + */ + if(!ComposerEditing + && (ComposerTopLine != COMPOSER_TOP_LINE)) + scrollup(wp, COMPOSER_TOP_LINE, + j+entry_line(1000, TRUE)); + else + scrollup(wp, -1, j); + } + else + scrollup(wp, -1, j); + break; + default : + break; + } + } + + wp->w_linep = lp; + wp->w_flag |= WFHARD; /* Force full. */ +out: + /* + * if the line at the top of the page is the top line + * in the body, show the header... + */ + if(Pmaster && Pmaster->headents && !ComposerEditing){ + if(lback(wp->w_linep) == wp->w_bufp->b_linep){ + if(ComposerTopLine == COMPOSER_TOP_LINE){ + i = term.t_nrow - 2 - term.t_mrow - HeaderLen(); + if(i > 0 && nlforw() >= i) { /* room for header ? */ + if((i = nlforw()/2) == 0 && term.t_nrow&1) + i = 1; + while(wp->w_linep != wp->w_bufp->b_linep && i--) + wp->w_linep = lforw(wp->w_linep); + + } + else + ToggleHeader(1); + } + } + else{ + if(ComposerTopLine != COMPOSER_TOP_LINE) + ToggleHeader(0); /* hide it ! */ + } + } + + /* Try to use reduced update. Mode line update has its own special + * flag. The fast update is used if the only thing to do is within + * the line editing. + */ + lp = wp->w_linep; + i = wp->w_toprow; + + if ((wp->w_flag & ~WFMODE) == WFEDIT){ + while (lp != wp->w_dotp){ + ++i; + lp = lforw(lp); + } + + vscreen[i]->v_flag |= VFCHG; + vtmove(i, 0); + + for (j = 0; j < llength(lp); ++j) + vtputc(lgetc(lp, j)); + + vteeol(); + } + else if ((wp->w_flag & (WFEDIT | WFHARD)) != 0){ + while (i < wp->w_toprow+wp->w_ntrows){ + vscreen[i]->v_flag |= VFCHG; + vtmove(i, 0); + + /* if line has been changed */ + if (lp != wp->w_bufp->b_linep){ + for (j = 0; j < llength(lp); ++j) + vtputc(lgetc(lp, j)); + + lp = lforw(lp); + } + + vteeol(); + ++i; + } + } +#if ~WFDEBUG + if ((wp->w_flag&WFMODE) != 0) + modeline(wp); + + wp->w_flag = 0; + wp->w_force = 0; +#endif + } +#if WFDEBUG + modeline(wp); + wp->w_flag = 0; + wp->w_force = 0; +#endif + + /* and onward to the next window */ + wp = wp->w_wndp; + } + + /* Always recompute the row and column number of the hardware cursor. This + * is the only update for simple moves. + */ + lp = curwp->w_linep; + currow = curwp->w_toprow; + + while (lp != curwp->w_dotp){ + ++currow; + lp = lforw(lp); + } + + curcol = 0; + i = 0; + + while (i < curwp->w_doto){ + c = lgetc(lp, i++); + + if(c.c == '\t'){ + curcol |= 0x07; + ++curcol; + } + else if(ISCONTROL(c.c)){ + curcol += 2; + } + else{ + int w; + + w = wcellwidth((UCS) c.c); + curcol += (w >= 0 ? w : 1); + } + } + + if (curcol >= term.t_ncol) { /* extended line. */ + /* flag we are extended and changed */ + vscreen[currow]->v_flag |= VFEXT | VFCHG; + updext(); /* and output extended line */ + } else + lbound = 0; /* not extended line */ + + /* make sure no lines need to be de-extended because the cursor is + * no longer on them + */ + + wp = wheadp; + + while (wp != NULL) { + lp = wp->w_linep; + i = wp->w_toprow; + + while (i < wp->w_toprow + wp->w_ntrows) { + if (vscreen[i]->v_flag & VFEXT) { + /* always flag extended lines as changed */ + vscreen[i]->v_flag |= VFCHG; + if ((wp != curwp) || (lp != wp->w_dotp) || + (curcol < term.t_ncol)) { + vtmove(i, 0); + for (j = 0; j < llength(lp); ++j) + vtputc(lgetc(lp, j)); + vteeol(); + + /* this line no longer is extended */ + vscreen[i]->v_flag &= ~VFEXT; + } + } + lp = lforw(lp); + ++i; + } + /* and onward to the next window */ + wp = wp->w_wndp; + } + + /* Special hacking if the screen is garbage. Clear the hardware screen, + * and update your copy to agree with it. Set all the virtual screen + * change bits, to force a full update. + */ + + if (sgarbf != FALSE){ + if(Pmaster){ + int rv; + + showCompTitle(); + + if(ComposerTopLine != COMPOSER_TOP_LINE){ + UpdateHeader(0); /* arrange things */ + PaintHeader(COMPOSER_TOP_LINE, TRUE); + } + + /* + * since we're using only a portion of the screen and only + * one buffer, only clear enough screen for the current window + * which is to say the *only* window. + */ + for(i=wheadp->w_toprow;i<=term.t_nrow; i++){ + movecursor(i, 0); + peeol(); + vscreen[i]->v_flag |= VFCHG; + } + rv = (*Pmaster->showmsg)('X' & 0x1f); /* ctrl-L */ + ttresize(); + picosigs(); /* restore altered handlers */ + if(rv) /* Did showmsg corrupt the display? */ + PaintBody(0); /* Yes, repaint */ + movecursor(wheadp->w_toprow, 0); + } + else{ + for (i = 0; i < term.t_nrow-term.t_mrow; ++i){ + vscreen[i]->v_flag |= VFCHG; + vp1 = pscreen[i]; + c.c = ' '; + c.a = 0; + for (j = 0; j < term.t_ncol; ++j) + vp1->v_text[j] = c; + } + + movecursor(0, 0); /* Erase the screen. */ + (*term.t_eeop)(); + + } + + sgarbf = FALSE; /* Erase-page clears */ + mpresf = FALSE; /* the message area. */ + + if(Pmaster) + modeline(curwp); + else + sgarbk = TRUE; /* fix the keyhelp as well...*/ + } + + /* Make sure that the physical and virtual displays agree. Unlike before, + * the "updateline" code is only called with a line that has been updated + * for sure. + */ + if(Pmaster) + i = curwp->w_toprow; + else + i = 0; + + if (term.t_nrow > term.t_mrow) + c.c = term.t_nrow - term.t_mrow; + else + c.c = 0; + + for (; i < (int)c.c; ++i){ + + vp1 = vscreen[i]; + + /* for each line that needs to be updated, or that needs its + reverse video status changed, call the line updater */ + j = vp1->v_flag; + if (j & VFCHG){ + +#if TYPEAH + if (typahead()){ +#ifdef _WINDOWS + mswin_endupdate (); +#endif + return; + } +#endif + vp2 = pscreen[i]; + + updateline(i, &vp1->v_text[0], &vp2->v_text[0], &vp1->v_flag); + } + } + + if(Pmaster == NULL){ + + if(sgarbk != FALSE){ + if(term.t_mrow > 0){ + movecursor(term.t_nrow-1, 0); + peeol(); + movecursor(term.t_nrow, 0); + peeol(); + } + + if(lastflag&CFFILL){ + /* TRANSLATORS: UnJustify means undo the previous + Justify command. */ + menu_pico[UNCUT_KEY].label = N_("UnJustify"); + if(!(lastflag&CFFLBF)){ + emlwrite(_("Can now UnJustify!"), NULL); + mpresf = FARAWAY; /* remove this after next keystroke! */ + } + } + else + menu_pico[UNCUT_KEY].label = N_("UnCut Text"); + + wkeyhelp(menu_pico); + sgarbk = FALSE; + } + } + + if(lastflag&CFFLBF){ + emlwrite(_("Can now UnJustify!"), NULL); + mpresf = FARAWAY; /* remove this after next keystroke! */ + } + + /* Finally, update the hardware cursor and flush out buffers. */ + + movecursor(currow, curcol - lbound); +#ifdef _WINDOWS + mswin_endupdate (); + + /* + * Update the scroll bars. This function is where curbp->b_linecnt + * is really managed. See update_scroll. + */ + update_scroll (); +#endif + (*term.t_flush)(); +} + + +/* updext - update the extended line which the cursor is currently + * on at a column greater than the terminal width. The line + * will be scrolled right or left to let the user see where + * the cursor is + */ +void +updext(void) +{ + int rcursor; /* real cursor location */ + LINE *lp; /* pointer to current line */ + int j; /* index into line */ + int w = 0; + int ww; + + /* + * Calculate what column the real cursor will end up in. + * The cursor will be in the rcursor'th column. So if we're + * counting columns 0 1 2 3 and rcursor is 8, then rcursor + * will be over cell 7. + * + * What this effectively does is to scroll the screen as we're + * moving to the right when the cursor first passes off the + * screen's right edge. It would be nice if it did the same + * thing coming back to the left. Instead, in order that the + * screen's display depends only on the curcol and not on + * how we got there, the screen scrolls when we pass the + * t_margin column. It's also kind of funky that you can't + * see the character under the $ but you can delete it. + */ + rcursor = ((curcol - term.t_ncol) % (term.t_ncol - term.t_margin + 1)) + term.t_margin; + lbound = curcol - rcursor + 1; + + /* + * Make sure lbound is set so that a double-width character does + * not straddle the boundary. If it does, move over one cell. + */ + lp = curwp->w_dotp; /* line to output */ + for (j=0; j= 0 ? ww : 1); + } + + if(w > lbound) + lbound = w; + + + /* scan through the line outputing characters to the virtual screen + * once we reach the left edge + */ + vtmove(currow, -lbound); /* start scanning offscreen */ + for (j=0; jv_text[0].c); + vscreen[currow]->v_text[0].c = '$'; + vscreen[currow]->v_text[0].a = 0; + if(w == 2){ + /* + * We want to put $ in the first two columns so that it + * takes up the right amount of space, but that means we + * have to scoot the real characters over one slot. + */ + for (j = term.t_ncol-1; j >= 2; --j) + vscreen[currow]->v_text[j] = vscreen[currow]->v_text[j-1]; + + vscreen[currow]->v_text[1].c = '$'; + vscreen[currow]->v_text[1].a = 0; + } +} + + +/* + * Update a single line. This does not know how to use insert or delete + * character sequences; we are using VT52 functionality. Update the physical + * row and column variables. + */ +void +updateline(int row, /* row on screen */ + CELL vline[], /* what we want it to end up as */ + CELL pline[], /* what it looks like now */ + short *flags) +{ + CELL *cp1, *cp2, *cp3, *cp4, *cp5, *cp6, *cp7; + int display = TRUE; + int nbflag; /* non-blanks to the right flag? */ + int cleartoeol = 0; + + if(row < 0 || row > term.t_nrow) + return; + + /* set up pointers to virtual and physical lines */ + cp1 = &vline[0]; + cp2 = &pline[0]; + cp3 = &vline[term.t_ncol]; + + /* advance past any common chars at the left */ + while (cp1 != cp3 && cp1[0].c == cp2[0].c && cp1[0].a == cp2[0].a) { + ++cp1; + ++cp2; + } + +/* This can still happen, even though we only call this routine on changed + * lines. A hard update is always done when a line splits, a massive + * change is done, or a buffer is displayed twice. This optimizes out most + * of the excess updating. A lot of computes are used, but these tend to + * be hard operations that do a lot of update, so I don't really care. + */ + /* if both lines are the same, no update needs to be done */ + if (cp1 == cp3){ + *flags &= ~VFCHG; /* mark it clean */ + return; + } + + /* find out if there is a match on the right */ + nbflag = FALSE; + cp3 = &vline[term.t_ncol]; + cp4 = &pline[term.t_ncol]; + + if(cellwidth_ptr_to_ptr(cp1, cp3) == cellwidth_ptr_to_ptr(cp2, cp4)) + while (cp3[-1].c == cp4[-1].c && cp3[-1].a == cp4[-1].a) { + --cp3; + --cp4; + if (cp3[0].c != ' ' || cp3[0].a != 0) /* Note if any nonblank */ + nbflag = TRUE; /* in right match. */ + } + + cp5 = cp3; + + if (nbflag == FALSE && TERM_EOLEXIST) { /* Erase to EOL ? */ + while (cp5 != cp1 && cp5[-1].c == ' ' && cp5[-1].a == 0) + --cp5; + + if (cp3-cp5 <= 3) /* Use only if erase is */ + cp5 = cp3; /* fewer characters. */ + } + + /* go to start of differences */ + movecursor(row, cellwidth_ptr_to_ptr(&vline[0], cp1)); + + if (!nbflag) { /* use insert or del char? */ + cp6 = cp3; + cp7 = cp4; + + if(TERM_INSCHAR + &&(cp7!=cp2 && cp6[0].c==cp7[-1].c && cp6[0].a==cp7[-1].a)){ + while (cp7 != cp2 && cp6[0].c==cp7[-1].c && cp6[0].a==cp7[-1].a){ + --cp7; + --cp6; + } + + if (cp7==cp2 && cp4-cp2 > 3){ + int ww; + + (*term.t_rev)(cp1->a); /* set inverse for this char */ + o_insert((UCS) cp1->c); /* insert the char */ + ww = wcellwidth((UCS) cp1->c); + ttcol += (ww >= 0 ? ww : 1); + display = FALSE; /* only do it once!! */ + } + } + else if(TERM_DELCHAR && cp3 != cp1 && cp7[0].c == cp6[-1].c + && cp7[0].a == cp6[-1].a){ + while (cp6 != cp1 && cp7[0].c==cp6[-1].c && cp7[0].a==cp6[-1].a){ + --cp7; + --cp6; + } + + if (cp6==cp1 && cp5-cp6 > 3){ + int w; + + w = wcellwidth((UCS) cp7[0].c); + w = (w >= 0 ? w : 1); + while(w-- > 0) /* in case double-width char */ + o_delete(); /* delete the char */ + display = FALSE; /* only do it once!! */ + } + } + } + + if(cp1 != cp5 && display){ + int w1, w2; + + /* + * If we need to copy characters from cp1 to cp2 and + * we need to display them, then we have to worry about + * the characters that we are replacing being of a different + * width than the new characters, else the display may be + * messed up. + * + * If the new width (w1) is less than the old width, that means + * we will leave behind some old remnants if we aren't careful. + * If the new width is larger than the old width, we have to + * make sure we draw the characters all the way to the end + * in order to get it right. Take advantage of clear to end + * of line if we have it. + */ + w1 = cellwidth_ptr_to_ptr(cp1, cp3); + w2 = cellwidth_ptr_to_ptr(cp2, cp2 + (cp3-cp1)); + + if(w1 < w2 || (nbflag && w1 != w2)){ + if(TERM_EOLEXIST){ + if(nbflag){ + /* + * Draw all of the characters starting with cp1 + * until we get to all spaces, then clear to the end of + * line from there. Watch out we don't run over the + * right hand edge, which shouldn't happen. + * + * Set cp5 to the first of the repeating spaces. + */ + cp5 = &vline[term.t_ncol]; + while (cp5 != cp1 && cp5[-1].c == ' ' && cp5[-1].a == 0) + --cp5; + } + + /* + * In the !nbflag case we want spaces from cp5 on. + * Setting cp3 to something different from cp5 triggers + * the clear to end of line below. + */ + if(cellwidth_ptr_to_ptr(&vline[0], cp5) < term.t_ncol) + cleartoeol++; + } + else{ + int w; + + /* + * No peeol so draw all the way to the edge whether they + * are spaces or not. + */ + cp3 = &vline[0]; + for(w = 0; w < term.t_ncol; cp3++){ + int ww; + + ww = wcellwidth((UCS) cp3->c); + w += (ww >= 0 ? ww : 1); + } + + cp5 = cp3; + } + } + } + + while (cp1 != cp5) { /* Ordinary. */ + int ww; + + if(display){ + (*term.t_rev)(cp1->a); /* set inverse for this char */ + (*term.t_putchar)(cp1->c); + } + + ww = wcellwidth((UCS) cp1->c); + ttcol += (ww >= 0 ? ww : 1); + + *cp2++ = *cp1++; + } + + (*term.t_rev)(0); /* turn off inverse anyway! */ + + if (cp5 != cp3 || cleartoeol) { /* Erase. */ + if(display) + peeol(); + else + while (cp1 != cp3) + *cp2++ = *cp1++; + } + + *flags &= ~VFCHG; /* flag this line is changed */ +} + + +/* + * Redisplay the mode line for the window pointed to by the "wp". This is the + * only routine that has any idea of how the modeline is formatted. You can + * change the modeline format by hacking at this routine. Called by "update" + * any time there is a dirty window. + */ +void +modeline(WINDOW *wp) +{ + if(Pmaster){ + if(ComposerEditing) + ShowPrompt(); + else{ + menu_compose[EXIT_KEY].label = (Pmaster->headents) + ? N_("Send") :N_("Exit"); + menu_compose[PSTPN_KEY].name = (Pmaster->headents) + ? "^O" : NULL; + menu_compose[PSTPN_KEY].label = (Pmaster->headents) + ? N_("Postpone") : NULL; + menu_compose[WHERE_KEY].name = (Pmaster->alt_ed) ? "^_" : "^W"; + menu_compose[WHERE_KEY].label = (Pmaster->alt_ed) ? N_("Alt Edit") + : N_("Where is"); + KS_OSDATASET(&menu_compose[WHERE_KEY], + (Pmaster->alt_ed) ? KS_ALTEDITOR : KS_WHEREIS); + menu_compose[UNCUT_KEY].label = (thisflag&CFFILL) ? N_("UnJustify") + : N_("UnCut Text"); + wkeyhelp(menu_compose); + } + } + else{ + BUFFER *bp; + char t1[NLINE], t2[NLINE], t3[NLINE], tline[NLINE]; + int w1, w2, w3, w1_to_2, w2_to_3, w3_to_r; + UCS *ucs; + + vtmove(1, 0); + vteeol(); + vscreen[0]->v_flag |= VFCHG; /* Redraw next time. */ + vtmove(0, 0); /* Seek to right line. */ + + snprintf(t1, sizeof(t1), PICO_TITLE, version); /* write version */ + + bp = wp->w_bufp; + if(bp->b_fname[0]) /* File name? */ + snprintf(t2, sizeof(t2), "File: %s", bp->b_fname); + else{ + strncpy(t2, PICO_NEWBUF_MSG, sizeof(t2)); + t2[sizeof(t2)-1] = '\0'; + } + + if(bp->b_flag&BFCHG){ /* "MOD" if changed. */ + strncpy(t3, PICO_MOD_MSG, sizeof(t3)); + t3[sizeof(t3)-1] = '\0'; + } + else + t3[0] = '\0'; + +#define ALLOFTHEM (w1+w1_to_2+w2+w2_to_3+w3+w3_to_r) +#define ALLBUTSPACE (w1+w2+w3+w3_to_r) + + w1 = utf8_width(t1); + w2 = utf8_width(t2); + w3 = utf8_width(t3); + w1_to_2 = w2_to_3 = 1; /* min values for separation */ + w3_to_r = 2; + + if(ALLOFTHEM <= term.t_ncol){ /* everything fits */ + w1_to_2 = (term.t_ncol - ALLBUTSPACE)/2; + w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); + } + else{ + w1 = 2; + w1_to_2 = 0; + if(ALLOFTHEM <= term.t_ncol) + w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); + else{ + w1 = w1_to_2 = w3_to_r = 0; + if(ALLOFTHEM <= term.t_ncol) + w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); + else{ + if(bp->b_fname[0]){ + snprintf(t2, sizeof(t2), "%s", bp->b_fname); + w2 = utf8_width(t2); + } + + if(ALLOFTHEM <= term.t_ncol) + w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); + else{ + w2 = 8; + if(bp->b_fname[0] && ALLOFTHEM <= term.t_ncol){ + /* reduce size of file */ + w2 = term.t_ncol - (ALLOFTHEM - w2); + t2[0] = t2[1] = t2[2] = '.'; + utf8_to_width_rhs(t2+3, bp->b_fname, sizeof(t2)-3, w2-3); + w2 = utf8_width(t2); + } + else + w2 = utf8_width(t2); + + if(ALLOFTHEM <= term.t_ncol) + w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); + else{ + w1 = w1_to_2 = w2 = w2_to_3 = w3_to_r = 0; + if(ALLOFTHEM <= term.t_ncol) + w2_to_3 = term.t_ncol - ALLBUTSPACE; + else + w3 = 0; + } + } + } + } + } + + utf8_snprintf(tline, sizeof(tline), + "%*.*w%*.*w%*.*w%*.*w%*.*w%*.*w", + w1, w1, t1, + w1_to_2, w1_to_2, "", + w2, w2, t2, + w2_to_3, w2_to_3, "", + w3, w3, t3, + w3_to_r, w3_to_r, ""); + + ucs = NULL; + if(utf8_width(tline) <= term.t_ncol) + ucs = utf8_to_ucs4_cpystr(tline); + + if(ucs){ + UCS *ucsp; + CELL c; + + c.a = 1; + ucsp = ucs; + while((c.c = CELLMASK & *ucsp++)) + vtputc(c); + + fs_give((void **) &ucs); + } + } +} + + + +/* + * Send a command to the terminal to move the hardware cursor to row "row" + * and column "col". The row and column arguments are origin 0. Optimize out + * random calls. Update "ttrow" and "ttcol". + */ +void +movecursor(int row, int col) +{ + if (row!=ttrow || col!=ttcol) { + ttrow = row; + ttcol = col; + (*term.t_move)(MIN(MAX(row,0),term.t_nrow), MIN(MAX(col,0),term.t_ncol-1)); + } +} + + +/* + * Erase any sense we have of the cursor's HW location... + */ +void +clearcursor(void) +{ + ttrow = ttcol = FARAWAY; +} + +void +get_cursor(int *row, int *col) +{ + if(row) + *row = ttrow; + if(col) + *col = ttcol; +} + + +/* + * Erase the message line. This is a special routine because the message line + * is not considered to be part of the virtual screen. It always works + * immediately; the terminal buffer is flushed via a call to the flusher. + */ +void +mlerase(void) +{ + if (term.t_nrow < term.t_mrow) + return; + + movecursor(term.t_nrow - term.t_mrow, 0); + (*term.t_rev)(0); + if (TERM_EOLEXIST == TRUE) + peeol(); + else{ + if(ttrow == term.t_nrow){ + while(ttcol++ < term.t_ncol-1) + (*term.t_putchar)(' '); + } + else{ + while(ttcol++ < term.t_ncol) /* track's ttcol */ + (*term.t_putchar)(' '); + } + } + + (*term.t_flush)(); + mpresf = FALSE; +} + + +int +mlyesno_utf8(char *utf8prompt, int dflt) +{ + int ret; + UCS *prompt; + + prompt = utf8_to_ucs4_cpystr(utf8prompt ? utf8prompt : ""); + + ret = mlyesno(prompt, dflt); + + if(prompt) + fs_give((void **) &prompt); + + return(ret); +} + + +/* + * Ask a yes or no question in the message line. Return either TRUE, FALSE, or + * ABORT. The ABORT status is returned if the user bumps out of the question + * with a ^G. if d >= 0, d is the default answer returned. Otherwise there + * is no default. + */ +int +mlyesno(UCS *prompt, int dflt) +{ + int rv; + UCS buf[NLINE], lbuf[10]; + KEYMENU menu_yesno[12]; + COLOR_PAIR *lastc = NULL; + +#ifdef _WINDOWS + if (mswin_usedialog ()) + switch (mswin_yesno (prompt)) { + default: + case 0: return (ABORT); + case 1: return (TRUE); + case 2: return (FALSE); + } +#endif + + for(rv = 0; rv < 12; rv++){ + menu_yesno[rv].name = NULL; + KS_OSDATASET(&menu_yesno[rv], KS_NONE); + } + + menu_yesno[1].name = "Y"; + menu_yesno[1].label = (dflt == TRUE) ? "[" N_("Yes") "]" : N_("Yes"); + menu_yesno[6].name = "^C"; + menu_yesno[6].label = N_("Cancel"); + menu_yesno[7].name = "N"; + menu_yesno[7].label = (dflt == FALSE) ? "[" N_("No") "]" : N_("No"); + wkeyhelp(menu_yesno); /* paint generic menu */ + sgarbk = TRUE; /* mark menu dirty */ + if(Pmaster && curwp) + curwp->w_flag |= WFMODE; + + ucs4_strncpy(buf, prompt, NLINE); + buf[NLINE-1] = '\0'; + lbuf[0] = ' '; lbuf[1] = '?'; lbuf[2] = ' '; lbuf[3] = '\0'; + ucs4_strncat(buf, lbuf, NLINE - ucs4_strlen(buf) - 1); + buf[NLINE-1] = '\0'; + mlwrite(buf, NULL); + if(Pmaster && Pmaster->colors && Pmaster->colors->prcp + && pico_is_good_colorpair(Pmaster->colors->prcp)){ + lastc = pico_get_cur_color(); + (void) pico_set_colorp(Pmaster->colors->prcp, PSC_NONE); + } + else + (*term.t_rev)(1); + + rv = -1; + while(1){ + switch(GetKey()){ + case (CTRL|'M') : /* default */ + if(dflt >= 0){ + pputs_utf8((dflt) ? _("Yes") : _("No"), 1); + rv = dflt; + } + else + (*term.t_beep)(); + + break; + + case (CTRL|'C') : /* Bail out! */ + case F2 : + pputs_utf8(_("ABORT"), 1); + rv = ABORT; + break; + + case 'y' : + case 'Y' : + case F3 : + pputs_utf8(_("Yes"), 1); + rv = TRUE; + break; + + case 'n' : + case 'N' : + case F4 : + pputs_utf8(_("No"), 1); + rv = FALSE; + break; + + case (CTRL|'G') : + if(term.t_mrow == 0 && km_popped == 0){ + movecursor(term.t_nrow-2, 0); + peeol(); + term.t_mrow = 2; + if(lastc){ + (void) pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(0); + + wkeyhelp(menu_yesno); /* paint generic menu */ + mlwrite(buf, NULL); + if(Pmaster && Pmaster->colors && Pmaster->colors->prcp + && pico_is_good_colorpair(Pmaster->colors->prcp)){ + lastc = pico_get_cur_color(); + (void) pico_set_colorp(Pmaster->colors->prcp, PSC_NONE); + } + else + (*term.t_rev)(1); + + sgarbk = TRUE; /* mark menu dirty */ + km_popped++; + break; + } + /* else fall through */ + + default: + (*term.t_beep)(); + case NODATA : + break; + } + + (*term.t_flush)(); + if(rv != -1){ + if(lastc){ + (void) pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(0); + + if(km_popped){ + term.t_mrow = 0; + movecursor(term.t_nrow, 0); + peeol(); + sgarbf = 1; + km_popped = 0; + } + + return(rv); + } + } +} + + +/* + * Write a prompt into the message line, then read back a response. Keep + * track of the physical position of the cursor. If we are in a keyboard + * macro throw the prompt away, and return the remembered response. This + * lets macros run at full speed. The reply is always terminated by a carriage + * return. Handle erase, kill, and abort keys. + */ +int +mlreply_utf8(char *utf8prompt, char *utf8buf, int nbuf, int flg, EXTRAKEYS *extras) +{ + return(mlreplyd_utf8(utf8prompt, utf8buf, nbuf, flg|QDEFLT, extras)); +} + + +int +mlreply(UCS *prompt, UCS *buf, int nbuf, int flg, EXTRAKEYS *extras) +{ + return(mlreplyd(prompt, buf, nbuf, flg|QDEFLT, extras)); +} + + +/* + * function key mappings + */ +static UCS rfkm[12][2] = { + { F1, (CTRL|'G')}, + { F2, (CTRL|'C')}, + { F3, 0 }, + { F4, 0 }, + { F5, 0 }, + { F6, 0 }, + { F7, 0 }, + { F8, 0 }, + { F9, 0 }, + { F10, 0 }, + { F11, 0 }, + { F12, 0 } +}; + + +int +mlreplyd_utf8(char *utf8prompt, char *utf8buf, int nbuf, int flg, EXTRAKEYS *extras) +{ + int ret; + UCS *b, *buf; + char *utf8; + UCS *prompt; + + buf = (UCS *) fs_get(nbuf * sizeof(*b)); + b = utf8_to_ucs4_cpystr(utf8buf); + if(b){ + ucs4_strncpy(buf, b, nbuf); + buf[nbuf-1] = '\0'; + fs_give((void **) &b); + } + + prompt = utf8_to_ucs4_cpystr(utf8prompt ? utf8prompt : ""); + + ret = mlreplyd(prompt, buf, nbuf, flg, extras); + + utf8 = ucs4_to_utf8_cpystr(buf); + if(utf8){ + strncpy(utf8buf, utf8, nbuf); + utf8buf[nbuf-1] = '\0'; + fs_give((void **) &utf8); + } + + if(buf) + fs_give((void **) &buf); + + if(prompt) + fs_give((void **) &prompt); + + return(ret); +} + + +void +writeachar(UCS ucs) +{ + pputc(ucs, 0); +} + + +/* + * mlreplyd - write the prompt to the message line along with a default + * answer already typed in. Carriage return accepts the + * default. answer returned in buf which also holds the initial + * default, nbuf is its length, def set means use default value. + */ +int +mlreplyd(UCS *prompt, UCS *buf, int nbuf, int flg, EXTRAKEYS *extras) +{ + UCS c; /* current char */ + UCS *b; /* pointer in buf */ + int i, j; + int plen; + int changed = FALSE; + int return_val = 0; + KEYMENU menu_mlreply[12]; + UCS extra_v[12]; + struct display_line dline; + COLOR_PAIR *lastc = NULL; + +#ifdef _WINDOWS + if(mswin_usedialog()){ + MDlgButton btn_list[12]; + LPTSTR free_names[12]; + LPTSTR free_labels[12]; + int i, j; + + memset(&free_names, 0, sizeof(LPTSTR) * 12); + memset(&free_labels, 0, sizeof(LPTSTR) * 12); + memset(&btn_list, 0, sizeof (MDlgButton) * 12); + j = 0; + for(i = 0; extras && extras[i].name != NULL; ++i) { + if(extras[i].label[0] != '\0') { + if((extras[i].key & CTRL) == CTRL) + btn_list[j].ch = (extras[i].key & ~CTRL) - '@'; + else + btn_list[j].ch = extras[i].key; + + btn_list[j].rval = extras[i].key; + free_names[j] = utf8_to_lptstr(extras[i].name); + btn_list[j].name = free_names[j]; + free_labels[j] = utf8_to_lptstr(extras[i].label); + btn_list[j].label = free_labels[j]; + j++; + } + } + + btn_list[j].ch = -1; + + return_val = mswin_dialog(prompt, buf, nbuf, ((flg&QDEFLT) > 0), + FALSE, btn_list, NULL, 0); + + if(return_val == 3) + return_val = HELPCH; + + 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]); + } + + return(return_val); + } +#endif + + menu_mlreply[0].name = "^G"; + menu_mlreply[0].label = N_("Get Help"); + KS_OSDATASET(&menu_mlreply[0], KS_SCREENHELP); + for(j = 0, i = 1; i < 6; i++){ /* insert odd extras */ + menu_mlreply[i].name = NULL; + KS_OSDATASET(&menu_mlreply[i], KS_NONE); + rfkm[2*i][1] = 0; + if(extras){ + for(; extras[j].name && j != 2*(i-1); j++) + ; + + if(extras[j].name){ + rfkm[2*i][1] = extras[j].key; + menu_mlreply[i].name = extras[j].name; + menu_mlreply[i].label = extras[j].label; + KS_OSDATASET(&menu_mlreply[i], KS_OSDATAGET(&extras[j])); + } + } + } + + menu_mlreply[6].name = "^C"; + menu_mlreply[6].label = N_("Cancel"); + KS_OSDATASET(&menu_mlreply[6], KS_NONE); + for(j = 0, i = 7; i < 12; i++){ /* insert even extras */ + menu_mlreply[i].name = NULL; + rfkm[2*(i-6)+1][1] = 0; + if(extras){ + for(; extras[j].name && j != (2*(i-6)) - 1; j++) + ; + + if(extras[j].name){ + rfkm[2*(i-6)+1][1] = extras[j].key; + menu_mlreply[i].name = extras[j].name; + menu_mlreply[i].label = extras[j].label; + KS_OSDATASET(&menu_mlreply[i], KS_OSDATAGET(&extras[j])); + } + } + } + + /* set up what to watch for and return values */ + memset(extra_v, 0, sizeof(extra_v)); + for(i = 0, j = 0; i < 12 && extras && extras[i].name; i++) + extra_v[j++] = extras[i].key; + + plen = mlwrite(prompt, NULL); /* paint prompt */ + + if(!(flg&QDEFLT)) + *buf = '\0'; + + dline.vused = ucs4_strlen(buf); + dline.dwid = term.t_ncol - plen; + dline.row = term.t_nrow - term.t_mrow; + dline.col = plen; + + 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 = writeachar; + + dline.vl = buf; + dline.vlen = nbuf-1; + dline.vbase = 0; + + b = &buf[(flg & QBOBUF) ? 0 : ucs4_strlen(buf)]; + + wkeyhelp(menu_mlreply); /* paint generic menu */ + + sgarbk = 1; /* mark menu dirty */ + + if(Pmaster && Pmaster->colors && Pmaster->colors->prcp + && pico_is_good_colorpair(Pmaster->colors->prcp)){ + lastc = pico_get_cur_color(); + (void) pico_set_colorp(Pmaster->colors->prcp, PSC_NONE); + } + else + (*term.t_rev)(1); + + for(;;){ + + line_paint(b-buf, &dline, NULL); + (*term.t_flush)(); + +#ifdef MOUSE + mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0); + register_mfunc(mouse_in_content, + term.t_nrow - term.t_mrow, plen, + term.t_nrow - term.t_mrow, term.t_ncol-1); +#endif +#ifdef _WINDOWS + mswin_allowpaste(MSWIN_PASTE_LINE); +#endif + while((c = GetKey()) == NODATA) + ; + +#ifdef MOUSE + clear_mfunc(mouse_in_content); +#endif +#ifdef _WINDOWS + mswin_allowpaste(MSWIN_PASTE_DISABLE); +#endif + + switch(c = normalize_cmd(c, rfkm, 1)){ + case (CTRL|'A') : /* CTRL-A beginning */ + case KEY_HOME : + b = buf; + continue; + + case (CTRL|'B') : /* CTRL-B back a char */ + case KEY_LEFT: + if(b <= buf) + (*term.t_beep)(); + else + b--; + + continue; + + case (CTRL|'C') : /* CTRL-C abort */ + pputs_utf8(_("ABORT"), 1); + ctrlg(FALSE, 0); + return_val = ABORT; + goto ret; + + case (CTRL|'E') : /* CTRL-E end of line */ + case KEY_END : + b = &buf[ucs4_strlen(buf)]; + continue; + + case (CTRL|'F') : /* CTRL-F forward a char*/ + case KEY_RIGHT : + if(*b == '\0') + (*term.t_beep)(); + else + b++; + + continue; + + case (CTRL|'G') : /* CTRL-G help */ + if(term.t_mrow == 0 && km_popped == 0){ + movecursor(term.t_nrow-2, 0); + peeol(); + sgarbk = 1; /* mark menu dirty */ + km_popped++; + term.t_mrow = 2; + if(lastc){ + (void) pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(0); + + wkeyhelp(menu_mlreply); /* paint generic menu */ + plen = mlwrite(prompt, NULL); /* paint prompt */ + if(Pmaster && Pmaster->colors && Pmaster->colors->prcp + && pico_is_good_colorpair(Pmaster->colors->prcp)){ + lastc = pico_get_cur_color(); + (void) pico_set_colorp(Pmaster->colors->prcp, PSC_NONE); + } + else + (*term.t_rev)(1); + + pputs(buf, 1); + break; + } + + pputs_utf8(_("HELP"), 1); + return_val = HELPCH; + goto ret; + + case (CTRL|'H') : /* CTRL-H backspace */ + case 0x7f : /* rubout */ + if (b <= buf){ + (*term.t_beep)(); + break; + } + else + b--; + + case (CTRL|'D') : /* CTRL-D delete char */ + case KEY_DEL : + if (!*b){ + (*term.t_beep)(); + break; + } + + changed=TRUE; + i = 0; + dline.vused--; + do /* blat out left char */ + b[i] = b[i+1]; + while(b[i++] != '\0'); + break; + + case (CTRL|'L') : /* CTRL-L redraw */ + return_val = (CTRL|'L'); + goto ret; + + case (CTRL|'K') : /* CTRL-K kill line */ + changed=TRUE; + buf[0] = '\0'; + dline.vused = 0; + b = buf; + break; + + case F1 : /* sort of same thing */ + return_val = HELPCH; + goto ret; + + case (CTRL|'M') : /* newline */ + return_val = changed; + goto ret; + +#ifdef MOUSE + case KEY_MOUSE : + { + MOUSEPRESS mp; + + mouse_get_last (NULL, &mp); + + /* The clicked line have anything special on it? */ + switch(mp.button){ + case M_BUTTON_LEFT : /* position cursor */ + mp.col -= plen; /* normalize column */ + if(mp.col >= 0 && mp.col <= ucs4_strlen(buf)) + b = buf + mp.col; + + break; + + case M_BUTTON_RIGHT : +#ifdef _WINDOWS + 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; + } + } + + continue; +#endif + + default : + + /* look for match in extra_v */ + for(i = 0; i < 12; i++) + if(c && c == extra_v[i]){ + return_val = c; + goto ret; + } + + changed=TRUE; + + if(c & (CTRL | FUNC)){ /* bag ctrl_special chars */ + (*term.t_beep)(); + } + else{ + i = ucs4_strlen(b); + if(flg&QNODQT){ /* reject double quotes? */ + if(c == '"'){ + (*term.t_beep)(); + continue; + } + } + + if(dline.vused >= nbuf-1){ + (*term.t_beep)(); + continue; + } + + do /* blat out left char */ + b[i+1] = b[i]; + while(i-- > 0); + + dline.vused++; + *b++ = c; + } + } + } + +ret: + if(lastc){ + (void) pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(0); + + (*term.t_flush)(); + + if(km_popped){ + term.t_mrow = 0; + movecursor(term.t_nrow, 0); + peeol(); + sgarbf = 1; + km_popped = 0; + } + + if(dline.dl) + fs_give((void **) &dline.dl); + + if(dline.olddl) + fs_give((void **) &dline.olddl); + + return(return_val); +} + + +void +emlwrite(char *utf8message, EML *eml) +{ + UCS *message; + + message = utf8_to_ucs4_cpystr(utf8message ? utf8message : ""); + + emlwrite_ucs4(message, eml); + + if(message) + fs_give((void **) &message); +} + + +/* + * emlwrite() - write the message string to the error half of the screen + * center justified. much like mlwrite (which is still used + * to paint the line for prompts and such), except it center + * the text. + */ +void +emlwrite_ucs4(UCS *message, EML *eml) +{ + UCS *bufp, *ap; + int width; + COLOR_PAIR *lastc = NULL; + + mlerase(); + + if(!(message && *message) || term.t_nrow < 2) + return; /* nothing to write or no space to write, bag it */ + + bufp = message; + + width = ucs4_str_width(message); + + /* + * next, figure out where the to move the cursor so the message + * comes out centered + */ + if((ap=ucs4_strchr(message, '%')) != NULL){ + width -= 2; + switch(ap[1]){ + case '%': + case 'c': + width += (eml && eml->c) ? wcellwidth(eml->c) : 1; + break; + case 'd': + width += dumbroot(eml ? eml->d : 0, 10); + break; + case 'D': + width += dumblroot(eml ? eml->l : 0L, 10); + break; + case 'o': + width += dumbroot(eml ? eml->d : 0, 8); + break; + case 'x': + width += dumbroot(eml ? eml->d : 0, 16); + break; + case 's': /* string arg is UTF-8 */ + width += (eml && eml->s) ? utf8_width(eml->s) : 2; + break; + } + } + + if(width+4 <= term.t_ncol) + movecursor(term.t_nrow-term.t_mrow, (term.t_ncol - (width + 4))/2); + else + movecursor(term.t_nrow-term.t_mrow, 0); + + if(Pmaster && Pmaster->colors && Pmaster->colors->stcp + && pico_is_good_colorpair(Pmaster->colors->stcp)){ + lastc = pico_get_cur_color(); + (void) pico_set_colorp(Pmaster->colors->stcp, PSC_NONE); + } + else + (*term.t_rev)(1); + + pputs_utf8("[ ", 1); + while (*bufp != '\0' && ttcol < term.t_ncol-2){ + if(*bufp == '\007') + (*term.t_beep)(); + else if(*bufp == '%'){ + switch(*++bufp){ + case 'c': + if(eml && eml->c) + pputc(eml->c, 0); + else { + pputs_utf8("%c", 0); + } + break; + case 'd': + mlputi(eml ? eml->d : 0, 10); + break; + case 'D': + mlputli(eml ? eml->l : 0L, 10); + break; + case 'o': + mlputi(eml ? eml->d : 0, 16); + break; + case 'x': + mlputi(eml ? eml->d : 0, 8); + break; + case 's': + pputs_utf8((eml && eml->s) ? eml->s : "%s", 0); + break; + case '%': + default: + pputc(*bufp, 0); + break; + } + } + else + pputc(*bufp, 0); + bufp++; + } + + pputs_utf8(" ]", 1); + + if(lastc){ + (void) pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(0); + + (*term.t_flush)(); + + mpresf = TRUE; +} + + +int +mlwrite_utf8(char *utf8fmt, void *arg) +{ + UCS *fmt; + int ret; + + fmt = utf8_to_ucs4_cpystr(utf8fmt ? utf8fmt : ""); + ret = mlwrite(fmt, arg); + if(fmt) + fs_give((void **) &fmt); + + return(ret); +} + + +/* + * Write a message into the message line. Keep track of the physical cursor + * position. A small class of printf like format items is handled. Assumes the + * stack grows down; this assumption is made by the "++" in the argument scan + * loop. Set the "message line" flag TRUE. + */ +int +mlwrite(UCS *fmt, void *arg) +{ + int ret, ww; + UCS c; + char *ap; + COLOR_PAIR *lastc = NULL; + + /* + * the idea is to only highlight if there is something to show + */ + mlerase(); + movecursor(ttrow, 0); + + if(Pmaster && Pmaster->colors && Pmaster->colors->prcp + && pico_is_good_colorpair(Pmaster->colors->prcp)){ + lastc = pico_get_cur_color(); + (void) pico_set_colorp(Pmaster->colors->prcp, PSC_NONE); + } + else + (*term.t_rev)(1); + + ap = (char *) &arg; + + while ((c = *fmt++) != 0) { + if (c != '%') { + pputc(c, 1); + } + else { + c = *fmt++; + switch (c){ + case 'd': + mlputi(*(int *)ap, 10); + ap += sizeof(int); + break; + + case 'o': + mlputi(*(int *)ap, 8); + ap += sizeof(int); + break; + + case 'x': + mlputi(*(int *)ap, 16); + ap += sizeof(int); + break; + + case 'D': + mlputli(*(long *)ap, 10); + ap += sizeof(long); + break; + + case 's': + pputs_utf8(*(char **)ap, 1); + ap += sizeof(char *); + break; + + default: + pputc(c, 1); + ww = wcellwidth(c); + ttcol += (ww >= 0 ? ww : 1); + } + } + } + + ret = ttcol; + while(ttcol < term.t_ncol) + pputc(' ', 0); + + movecursor(term.t_nrow - term.t_mrow, ret); + + if(lastc){ + (void) pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(0); + + (*term.t_flush)(); + mpresf = TRUE; + + return(ret); +} + + +/* + * Write out an integer, in the specified radix. Update the physical cursor + * position. This will not handle any negative numbers; maybe it should. + */ +void +mlputi(int i, int r) +{ + register int q; + static char hexdigits[] = "0123456789ABCDEF"; + + if (i < 0){ + i = -i; + pputc('-', 1); + } + + q = i/r; + + if (q != 0) + mlputi(q, r); + + pputc(hexdigits[i%r], 1); +} + + +/* + * do the same except as a long integer. + */ +void +mlputli(long l, int r) +{ + register long q; + + if (l < 0){ + l = -l; + pputc('-', 1); + } + + q = l/r; + + if (q != 0) + mlputli(q, r); + + pputc((int)(l%r)+'0', 1); +} + + +void +unknown_command(UCS c) +{ + char buf[10], ch, *s; + EML eml; + + buf[0] = '\0'; + s = buf; + + if(!c){ + /* fall through */ + } + else if(c & CTRL && c >= (CTRL|'@') && c <= (CTRL|'_')){ + ch = c - (CTRL|'@') + '@'; + snprintf(s, sizeof(buf), "^%c", ch); + } + else + 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 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 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 F1 : + case F2 : + case F3 : + case F4 : + case F5 : + case F6 : + case F7 : + case F8 : + case F9 : + case F10 : + case F11 : + case F12 : + snprintf(s, sizeof(buf), "F%ld", (long) (c - PF1 + 1)); + break; + + default: + if(c < CTRL) + utf8_put((unsigned char *) s, (unsigned long) c); + + break; + } + + eml.s = s; + emlwrite("Unknown Command: %s", &eml); + (*term.t_beep)(); +} + + +/* + * scrolldown - use stuff to efficiently move blocks of text on the + * display, and update the pscreen array to reflect those + * moves... + * + * wp is the window to move in + * r is the row at which to begin scrolling + * n is the number of lines to scrol + */ +void +scrolldown(WINDOW *wp, int r, int n) +{ +#ifdef TERMCAP + register int i; + register int l; + register VIDEO *vp1; + register VIDEO *vp2; + + if(!n) + return; + + if(r < 0){ + r = wp->w_toprow; + l = wp->w_ntrows; + } + else{ + if(r > wp->w_toprow) + vscreen[r-1]->v_flag |= VFCHG; + l = wp->w_toprow+wp->w_ntrows-r; + } + + o_scrolldown(r, n); + + for(i=l-n-1; i >= 0; i--){ + vp1 = pscreen[r+i]; + vp2 = pscreen[r+i+n]; + memcpy(vp2, vp1, term.t_ncol * sizeof(CELL)); + } + pprints(r+n-1, r); + ttrow = FARAWAY; + ttcol = FARAWAY; +#endif /* TERMCAP */ +} + + +/* + * scrollup - use tcap stuff to efficiently move blocks of text on the + * display, and update the pscreen array to reflect those + * moves... + */ +void +scrollup(WINDOW *wp, int r, int n) +{ +#ifdef TERMCAP + register int i; + register VIDEO *vp1; + register VIDEO *vp2; + + if(!n) + return; + + if(r < 0) + r = wp->w_toprow; + + o_scrollup(r, n); + + i = 0; + while(1){ + if(Pmaster){ + if(!(r+i+n < wp->w_toprow+wp->w_ntrows)) + break; + } + else{ + if(!((i < wp->w_ntrows-n)&&(r+i+n < wp->w_toprow+wp->w_ntrows))) + break; + } + vp1 = pscreen[r+i+n]; + vp2 = pscreen[r+i]; + memcpy(vp2, vp1, term.t_ncol * sizeof(CELL)); + i++; + } + pprints(wp->w_toprow+wp->w_ntrows-n, wp->w_toprow+wp->w_ntrows-1); + ttrow = FARAWAY; + ttcol = FARAWAY; +#endif /* TERMCAP */ +} + + +/* + * print spaces in the physical screen starting from row abs(n) working in + * either the positive or negative direction (depending on sign of n). + */ +void +pprints(int x, int y) +{ + register int i; + register int j; + + if(x < y){ + for(i = x;i <= y; ++i){ + for(j = 0; j < term.t_ncol; j++){ + pscreen[i]->v_text[j].c = ' '; + pscreen[i]->v_text[j].a = 0; + } + } + } + else{ + for(i = x;i >= y; --i){ + for(j = 0; j < term.t_ncol; j++){ + pscreen[i]->v_text[j].c = ' '; + pscreen[i]->v_text[j].a = 0; + } + } + } + ttrow = y; + ttcol = 0; +} + + +/* + * doton - return the physical line number that the dot is on in the + * current window, and by side effect the number of lines remaining + */ +int +doton(int *r, unsigned *chs) +{ + register int i = 0; + register LINE *lp = curwp->w_linep; + int l = -1; + + assert(r != NULL && chs != NULL); + + *chs = 0; + while(i++ < curwp->w_ntrows){ + if(lp == curwp->w_dotp) + l = i-1; + lp = lforw(lp); + if(lp == curwp->w_bufp->b_linep){ + i++; + break; + } + if(l >= 0) + (*chs) += llength(lp); + } + *r = i - l - term.t_mrow; + return(l+curwp->w_toprow); +} + + + +/* + * resize_pico - given new window dimensions, allocate new resources + */ +int +resize_pico(int row, int col) +{ + int old_nrow, old_ncol; + register int i; + register VIDEO *vp; + + old_nrow = term.t_nrow; + old_ncol = term.t_ncol; + + term.t_nrow = row; + term.t_ncol = col; + + if (old_ncol == term.t_ncol && old_nrow == term.t_nrow) + return(TRUE); + + if(curwp){ + curwp->w_toprow = 2; + curwp->w_ntrows = term.t_nrow - curwp->w_toprow - term.t_mrow; + } + + if(Pmaster){ + fillcol = Pmaster->fillcolumn; + (*Pmaster->resize)(); + } + else if(userfillcol > 0) + fillcol = userfillcol; + else + fillcol = term.t_ncol - 6; /* we control the fill column */ + + /* + * free unused screen space ... + */ + for(i=term.t_nrow+1; i <= old_nrow; ++i){ + free((char *) vscreen[i]); + free((char *) pscreen[i]); + } + + /* + * realloc new space for screen ... + */ + if((vscreen=(VIDEO **)realloc(vscreen,(term.t_nrow+1)*sizeof(VIDEO *))) == NULL){ + if(Pmaster) + return(-1); + else + exit(1); + } + + if((pscreen=(VIDEO **)realloc(pscreen,(term.t_nrow+1)*sizeof(VIDEO *))) == NULL){ + if(Pmaster) + return(-1); + else + exit(1); + } + + for (i = 0; i <= term.t_nrow; ++i) { + if(i <= old_nrow) + vp = (VIDEO *) realloc(vscreen[i], sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); + else + vp = (VIDEO *) malloc(sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); + + if (vp == NULL) + exit(1); + vp->v_flag = VFCHG; + vscreen[i] = vp; + if(old_ncol < term.t_ncol){ /* don't let any garbage in */ + vtrow = i; + vtcol = (i < old_nrow) ? old_ncol : 0; + vteeol(); + } + + if(i <= old_nrow) + vp = (VIDEO *) realloc(pscreen[i], sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); + else + vp = (VIDEO *) malloc(sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); + + if (vp == NULL) + exit(1); + + vp->v_flag = VFCHG; + pscreen[i] = vp; + } + + if(!ResizeBrowser()){ + if(Pmaster && Pmaster->headents){ + ResizeHeader(); + } + else{ + curwp->w_flag |= (WFHARD | WFMODE); + pico_refresh(0, 1); /* redraw whole enchilada. */ + update(); /* do it */ + } + } + + return(TRUE); +} + +void +redraw_pico_for_callback(void) +{ + pico_refresh(0, 1); + update(); +} + + +/* + * showCompTitle - display the anchor line passed in from pine + */ +void +showCompTitle(void) +{ + if(Pmaster){ + UCS *bufp; + extern UCS *pico_anchor; + COLOR_PAIR *lastc = NULL; + + if((bufp = pico_anchor) == NULL) + return; + + movecursor(COMPOSER_TITLE_LINE, 0); + if (Pmaster->colors && Pmaster->colors->tbcp && + pico_is_good_colorpair(Pmaster->colors->tbcp)){ + lastc = pico_get_cur_color(); + (void)pico_set_colorp(Pmaster->colors->tbcp, PSC_NONE); + } + else + (*term.t_rev)(1); + + while (ttcol < term.t_ncol) + if(*bufp != '\0') + pputc(*bufp++, 1); + else + pputc(' ', 1); + + if (lastc){ + (void)pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(0); + + movecursor(COMPOSER_TITLE_LINE + 1, 0); + peeol(); + } +} + + + +/* + * zotdisplay - blast malloc'd space created for display maps + */ +void +zotdisplay(void) +{ + register int i; + + for (i = 0; i <= term.t_nrow; ++i){ /* free screens */ + free((char *) vscreen[i]); + free((char *) pscreen[i]); + } + + free((char *) vscreen); + free((char *) pscreen); +} + + + +/* + * nlforw() - returns the number of lines from the top to the dot + */ +int +nlforw(void) +{ + register int i = 0; + register LINE *lp = curwp->w_linep; + + while(lp != curwp->w_dotp){ + lp = lforw(lp); + i++; + } + return(i); +} + + + +/* + * pputc - output the given char, keep track of it on the physical screen + * array, and keep track of the cursor + */ +void +pputc(UCS c, /* char to write */ + int a) /* and its attribute */ +{ + int ind, width, printable_ascii = 0; + + /* + * This is necessary but not sufficient to allow us to draw. Note that + * ttrow runs from 0 to t_nrow (so total number of rows is t_nrow+1) + * ttcol runs from 0 to t_ncol-1 (so total number of cols is t_ncol) + */ + if((ttcol >= 0 && ttcol < term.t_ncol) && (ttrow >= 0 && ttrow <= term.t_nrow)){ + + /* + * Width is the number of screen columns a character will occupy. + */ + if(c < 0x80 && isprint(c)){ + printable_ascii++; + width = 1; + } + else + width = wcellwidth(c); + + if(width < 0) + width = 1; /* will be a '?' */ + + if(ttcol + width <= term.t_ncol){ /* it fits */ + /* + * Some terminals scroll when you write in the lower right corner + * of the screen, so don't write there. + */ + if(!(ttrow == term.t_nrow && ttcol+width == term.t_ncol)){ + (*term.t_putchar)(c); /* write it */ + ind = index_from_col(ttrow, ttcol); + pscreen[ttrow]->v_text[ind].c = c; /* keep track of it */ + pscreen[ttrow]->v_text[ind].a = a; /* keep track of it */ + } + } + else{ + /* + * Character overlaps right edge of screen. Hopefully the higher + * layers will prevent this but we're making sure. + * + * We may want to do something like writing a space character + * into the cells that are on the screen. We'll see. + */ + } + + ttcol = MIN(term.t_ncol, ttcol+width); + } +} + + +/* + * pputs - print a string and keep track of the cursor + */ +void +pputs(UCS *s, /* string to write */ + int a) /* and its attribute */ +{ + while (*s != '\0') + pputc(*s++, a); +} + + +void +pputs_utf8(char *s, int a) +{ + UCS *ucsstr = NULL; + + if(s && *s){ + ucsstr = utf8_to_ucs4_cpystr(s); + if(ucsstr){ + pputs(ucsstr, a); + fs_give((void **) &ucsstr); + } + } +} + + +/* + * peeol - physical screen array erase to end of the line. remember to + * track the cursor. + */ +void +peeol(void) +{ + int i, width = 0, ww; + CELL cl; + + if(ttrow < 0 || ttrow > term.t_nrow) + return; + + cl.c = ' '; + cl.a = 0; + + /* + * Don't clear if we think we are sitting past the last column, + * that erases the last column if we just wrote it. + */ + if(ttcol < term.t_ncol) + (*term.t_eeol)(); + + /* + * Because the characters are variable width it's a little tricky + * to erase the rest of the line. What we do is add up the + * widths of the characters until we reach ttcol + * then set the rest to the space character. + */ + for(i = 0; i < term.t_ncol && width < ttcol; i++){ + ww = wcellwidth((UCS) pscreen[ttrow]->v_text[i].c); + width += (ww >= 0 ? ww : 1); + } + + while(i < term.t_ncol) + pscreen[ttrow]->v_text[i++] = cl; +} + + +/* + * pscr - return the character cell on the physical screen map on the + * given line, l, and offset, o. + */ +CELL * +pscr(int l, int o) +{ + if((l >= 0 && l <= term.t_nrow) && (o >= 0 && o < term.t_ncol)) + return(&(pscreen[l]->v_text[o])); + else + return(NULL); +} + + +/* + * pclear() - clear the physical screen from row x through row y (inclusive) + * row is zero origin, min row = 0 max row = t_nrow + * Clear whole screen -- pclear(0, term.t_nrow) + * Clear bottom two rows -- pclear(term.t_nrow-1, term.t_nrow) + * Clear bottom three rows -- pclear(term.t_nrow-2, term.t_nrow) + */ +void +pclear(int x, int y) +{ + register int i; + + x = MIN(MAX(0, x), term.t_nrow); + y = MIN(MAX(0, y), term.t_nrow); + + for(i=x; i <= y; i++){ + movecursor(i, 0); + peeol(); + } +} + + +/* + * dumbroot - just get close + */ +int +dumbroot(int x, int b) +{ + if(x < b) + return(1); + else + return(dumbroot(x/b, b) + 1); +} + + +/* + * dumblroot - just get close + */ +int +dumblroot(long x, int b) +{ + if(x < b) + return(1); + else + return(dumblroot(x/b, b) + 1); +} + + +/* + * pinsertc - use optimized insert, fixing physical screen map. + * returns true if char written, false otherwise + */ +int +pinsert(CELL c) +{ + int i, ind = 0, ww; + CELL *p; + + if(ttrow < 0 || ttrow > term.t_nrow) + return(0); + + if(o_insert((UCS) c.c)){ /* if we've got it, use it! */ + p = pscreen[ttrow]->v_text; /* then clean up physical screen */ + + ind = index_from_col(ttrow, ttcol); + + for(i = term.t_ncol-1; i > ind; i--) + p[i] = p[i-1]; /* shift right */ + + p[ind] = c; /* insert new char */ + + ww = wcellwidth((UCS) c.c); + ttcol += (ww >= 0 ? ww : 1); + + return(1); + } + + return(0); +} + + +/* + * pdel - use optimized delete to rub out the current char and + * fix the physical screen array. + * returns true if optimized the delete, false otherwise + */ +int +pdel(void) +{ + int i, ind = 0, w; + CELL *p; + + if(ttrow < 0 || ttrow > term.t_nrow) + return(0); + + if(TERM_DELCHAR){ /* if we've got it, use it! */ + p = pscreen[ttrow]->v_text; + ind = index_from_col(ttrow, ttcol); + + if(ind > 0){ + --ind; + w = wcellwidth((UCS) p[ind].c); + w = (w >= 0 ? w : 1); + ttcol -= w; + + for(i = 0; i < w; i++){ + (*term.t_putchar)('\b'); /* move left a char */ + o_delete(); /* and delete it */ + } + + /* then clean up physical screen */ + for(i=ind; i < term.t_ncol-1; i++) + p[i] = p[i+1]; + + p[i].c = ' '; + p[i].a = 0; + } + + return(1); + } + + return(0); +} + + + +/* + * wstripe - write out the given string at the given location, and reverse + * video on flagged characters. Does the same thing as pine's + * stripe. + * + * I believe this needs to be fixed to work with non-ascii utf8pmt, but maybe + * only if you want to put the tildes before multi-byte chars. + */ +void +wstripe(int line, int column, char *utf8pmt, int key) +{ + UCS *ucs4pmt, *u; + int i = 0, col = 0; + int j = 0; + int l, ww; + COLOR_PAIR *lastc = NULL; + COLOR_PAIR *kncp = NULL; + COLOR_PAIR *klcp = NULL; + + if(line < 0 || line > term.t_nrow) + return; + + if (Pmaster && Pmaster->colors){ + if(pico_is_good_colorpair(Pmaster->colors->klcp)) + klcp = Pmaster->colors->klcp; + + if(klcp && pico_is_good_colorpair(Pmaster->colors->kncp)) + kncp = Pmaster->colors->kncp; + + lastc = pico_get_cur_color(); + } + + ucs4pmt = utf8_to_ucs4_cpystr(utf8pmt); + l = ucs4_strlen(ucs4pmt); + while(1){ + if(i >= term.t_ncol || col >= term.t_ncol || j >= l) + return; /* equal strings */ + + if(ucs4pmt[j] == (UCS) key) + j++; + + if (pscr(line, i) == NULL) + return; + + if(pscr(line, i)->c != ucs4pmt[j]){ + if(j >= 1 && ucs4pmt[j-1] == (UCS) key) + j--; + break; + } + + ww = wcellwidth((UCS) pscr(line, i)->c); + col += (ww >= 0 ? ww : 1); + j++; + i++; + } + + movecursor(line, column+col); + if(klcp) (void)pico_set_colorp(klcp, PSC_NONE); + u = &ucs4pmt[j]; + do{ + if(*u == (UCS) key){ + u++; + if(kncp) + (void)pico_set_colorp(kncp, PSC_NONE); + else + (void)(*term.t_rev)(1); + + pputc(*u, 1); + if(kncp) + (void)pico_set_colorp(klcp, PSC_NONE); + else + (void)(*term.t_rev)(0); + } + else{ + pputc(*u, 0); + } + } + while(*++u != '\0'); + + if(ucs4pmt) + fs_give((void **) &ucs4pmt); + + peeol(); + if (lastc){ + (void)pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + (*term.t_flush)(); +} + + + +/* + * wkeyhelp - paint list of possible commands on the bottom + * of the display (yet another pine clone) + * NOTE: function key mode is handled here since all the labels + * are the same... + * + * The KEYMENU definitions have names and labels defined as UTF-8 strings, + * and wstripe expects UTF-8. + */ +void +wkeyhelp(KEYMENU *keymenu) +{ + char *obufp, *p, fkey[4]; + char linebuf[2*NLINE]; /* "2" is for space for invert tokens */ + int row, slot, tspace, adjusted_tspace, nspace[6], index, n; +#ifdef MOUSE + char nbuf[NLINE]; +#endif + +#ifdef _WINDOWS + pico_config_menu_items (keymenu); +#endif + + if(term.t_mrow == 0) + return; + + if(term.t_nrow < 1) + return; + + /* + * Calculate amount of space for the names column by column... + */ + for(index = 0; index < 6; index++) + if(!(gmode&MDFKEY)){ + nspace[index] = (keymenu[index].name) + ? utf8_width(keymenu[index].name) : 0; + if(keymenu[index+6].name + && (n = utf8_width(keymenu[index+6].name)) > nspace[index]) + nspace[index] = n; + + nspace[index]++; + } + else + nspace[index] = (index < 4) ? 3 : 4; + + tspace = term.t_ncol/6; /* total space for each item */ + + /* + * Avoid writing in bottom right corner so we won't scroll screens that + * scroll when you do that. The way this is setup, we won't do that + * unless the number of columns is evenly divisible by 6. + */ + adjusted_tspace = (6 * tspace == term.t_ncol) ? tspace - 1 : tspace; + + index = 0; + for(row = 0; row <= 1; row++){ + linebuf[0] = '\0'; + obufp = &linebuf[0]; + for(slot = 0; slot < 6; slot++){ + if(keymenu[index].name && keymenu[index].label){ + size_t l; + char this_label[200], tmp_label[200]; + + if(keymenu[index].label[0] == '[' && keymenu[index].label[(l=strlen(keymenu[index].label))-1] == ']' && l > 2){ + strncpy(tmp_label, &keymenu[index].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, _(keymenu[index].label), sizeof(this_label)); + + this_label[sizeof(this_label)-1] = '\0'; + + if(gmode&MDFKEY){ + p = fkey; + snprintf(fkey, sizeof(fkey), "F%d", (2 * slot) + row + 1); + } + else + p = keymenu[index].name; +#ifdef MOUSE + snprintf(nbuf, sizeof(nbuf), "%.*s %s", nspace[slot], p, this_label); + register_key(index, + (gmode&MDFKEY) ? F1 + (2 * slot) + row: + (keymenu[index].name[0] == '^') + ? (CTRL | keymenu[index].name[1]) + : (keymenu[index].name[0] == 'S' + && !strcmp(keymenu[index].name, "Spc")) + ? ' ' + : keymenu[index].name[0], + nbuf, invert_label, + term.t_nrow - 1 + row, (slot * tspace), + strlen(nbuf), + (Pmaster && Pmaster->colors) + ? Pmaster->colors->kncp: NULL, + (Pmaster && Pmaster->colors) + ? Pmaster->colors->klcp: NULL); +#endif + + n = nspace[slot]; + while(p && *p && n--){ + *obufp++ = '~'; /* insert "invert" token */ + *obufp++ = *p++; + } + + while(n-- > 0) + *obufp++ = ' '; + + p = this_label; + n = ((slot == 5 && row == 1) ? adjusted_tspace + : tspace) - nspace[slot]; + while(p && *p && n-- > 0) + *obufp++ = *p++; + + while(n-- > 0) + *obufp++ = ' '; + } + else{ + n = (slot == 5 && row == 1) ? adjusted_tspace : tspace; + while(n--) + *obufp++ = ' '; + +#ifdef MOUSE + register_key(index, NODATA, "", NULL, 0, 0, 0, NULL, NULL); +#endif + } + + *obufp = '\0'; + index++; + } + + wstripe(term.t_nrow - 1 + row, 0, linebuf, '~'); + } +} + + +/* + * This returns the screen width between pstart (inclusive) and + * pend (exclusive) where the pointers point into an array of CELLs. + */ +unsigned +cellwidth_ptr_to_ptr(CELL *pstart, CELL *pend) +{ + CELL *p; + unsigned width = 0; + int ww; + + if(pstart) + for(p = pstart; p < pend; p++){ + ww = wcellwidth((UCS) p->c); + width += (ww >= 0 ? ww : 1); + } + + return(width); +} + + +/* + * This returns the virtual screen width in row from index a to b (exclusive). + */ +unsigned +vcellwidth_a_to_b(int row, int a, int b) +{ + CELL *pstart, *pend; + VIDEO *vp; + + if(row < 0 || row > term.t_nrow) + return 0; + + if(a >= b) + return 0; + + a = MIN(MAX(0, a), term.t_ncol-1); + b = MIN(MAX(0, a), term.t_ncol); /* b is past where we stop */ + + vp = vscreen[row]; + pstart = &vp->v_text[a]; + pend = &vp->v_text[b]; + + return(cellwidth_ptr_to_ptr(pstart, pend)); +} + + +/* + * This returns the physical screen width in row from index a to b (exclusive). + */ +unsigned +pcellwidth_a_to_b(int row, int a, int b) +{ + CELL *pstart, *pend; + VIDEO *vp; + + if(row < 0 || row > term.t_nrow) + return 0; + + if(a >= b) + return 0; + + a = MIN(MAX(0, a), term.t_ncol-1); + b = MIN(MAX(0, a), term.t_ncol); /* b is past where we stop */ + + vp = pscreen[row]; + pstart = &vp->v_text[a]; + pend = &vp->v_text[b]; + + return(cellwidth_ptr_to_ptr(pstart, pend)); +} + + +int +index_from_col(int row, int col) +{ + CELL *p_start, *p_end, *p_limit; + int w_consumed = 0, w, done = 0; + + if(row < 0 || row > term.t_nrow) + return 0; + + p_end = p_start = pscreen[row]->v_text; + p_limit = p_start + term.t_ncol; + + if(p_start) + while(!done && p_end < p_limit && p_end->c && w_consumed <= col){ + w = wcellwidth((UCS) p_end->c); + w = (w >= 0 ? w : 1); + if(w_consumed + w <= col){ + w_consumed += w; + ++p_end; + } + else + ++done; + } + + /* MIN and MAX just to be sure */ + return(MIN(MAX(0, p_end - p_start), term.t_ncol-1)); +} + +#ifdef _WINDOWS + +void +pico_config_menu_items (KEYMENU *keymenu) +{ + int i; + KEYMENU *k; + UCS key; + + mswin_menuitemclear (); + + /* keymenu's seem to be hardcoded at 12 entries. */ + for (i = 0, k = keymenu; i < 12; ++i, ++k) { + if (k->name != NULL && k->label != NULL && + k->menuitem != KS_NONE) { + + if (k->name[0] == '^') + key = CTRL | k->name[1]; + else if (strcmp(k->name, "Ret") == 0) + key = '\r'; + else + key = k->name[0]; + + mswin_menuitemadd (key, k->label, k->menuitem, 0); + } + } +} + +/* + * Update the scroll range and position. (exported) + * + * This is where curbp->b_linecnt is really managed. With out this function + * to count the number of lines when needed curbp->b_linecnt will never + * really be correct. BUT, this function is only compiled into the + * windows version, so b_linecnt will only ever be right in the windows + * version. OK for now because that is the only version that + * looks at b_linecnt. + */ +int +update_scroll (void) +{ + long scr_pos; + long scr_range; + LINE *lp; + static LINE *last_top_line = NULL; + static long last_scroll_pos = -1; + + + if (ComposerEditing) { + /* Editing header - don't allow scroll bars. */ + mswin_setscrollrange (0, 0); + return(0); + } + + + /* + * Count the number of lines in the current bufer. Done when: + * + * when told to recount: curbp->b_linecnt == -1 + * when the top line changed: curwp->w_linep != last_top_line + * when we don't know the scroll pos: last_scroll_pos == -1 + * + * The first line in the list is a "place holder" line and is not + * counted. The list is circular, when we return the to place + * holder we have reached the end. + */ + if(curbp->b_linecnt == -1 || curwp->w_linep != last_top_line + || last_scroll_pos == -1) { + scr_range = 0; + scr_pos = 0; + for (lp = lforw (curbp->b_linep); lp != curbp->b_linep; + lp = lforw (lp)) { + if (lp == curwp->w_linep) + scr_pos = scr_range; + + ++scr_range; + } + + curbp->b_linecnt = scr_range; + last_scroll_pos = scr_pos; + last_top_line = curwp->w_linep; + } + + /* + * Set new scroll range and position. + */ + mswin_setscrollrange (curwp->w_ntrows - 2, curbp->b_linecnt - 1); + mswin_setscrollpos (last_scroll_pos); + return (0); +} +#endif /* _WINDOWS */ diff --git a/pico/ebind.h b/pico/ebind.h new file mode 100644 index 00000000..4f1687b4 --- /dev/null +++ b/pico/ebind.h @@ -0,0 +1,167 @@ +/* + * $Id: ebind.h 807 2007-11-09 01:21:33Z 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 + * + * ======================================================================== + * + * Program: Default key bindings + * + * NOTES: + * + * This files describes the key bindings for pico and the pine + * composer. The binds are static, (i.e., no way for the user + * to change them) so as to keep pico/composer as simple to use + * as possible. This, of course, means the number of functions is + * greatly reduced, but, then again, this is seen as very desirable. + * + * There are very limited number of flat ctrl-key bindings left, and + * most of them are slated for yet-to-be implemented functions, like + * invoking an alternate editor in the composer and necessary funcs + * for imlementing attachment handling. We really want to avoid + * going to multiple keystroke functions. -mss + * + */ + +/* EBIND: Initial default key to function bindings for + MicroEMACS 3.2 + + written by Dave G. Conroy + modified by Steve Wilhite, George Jones + greatly modified by Daniel Lawrence +*/ + +#ifndef EBIND_H +#define EBIND_H + + +/* + * Command table. + * This table is *roughly* in ASCII order, left to right across the + * characters of the command. This expains the funny location of the + * control-X commands. + */ +KEYTAB keytab[NBINDS] = { + {KEY_UP, backline}, + {KEY_DOWN, forwline}, + {KEY_RIGHT, forwchar}, + {KEY_LEFT, backchar}, + {KEY_PGUP, backpage}, + {KEY_PGDN, forwpage}, + {KEY_HOME, gotobol}, + {KEY_END, gotoeol}, + {KEY_DEL, forwdel}, +#ifdef MOUSE + {KEY_MOUSE, mousepress}, +#ifndef _WINDOWS + {CTRL|'\\', toggle_xterm_mouse}, +#endif +#endif + {CTRL|'A', gotobol}, + {CTRL|'B', backchar}, + {CTRL|'C', abort_composer}, + {CTRL|'D', forwdel}, + {CTRL|'E', gotoeol}, + {CTRL|'F', forwchar}, + {CTRL|'G', whelp}, + {CTRL|'H', backdel}, + {CTRL|'I', tab}, + {CTRL|'J', fillpara}, + {CTRL|'K', killregion}, + {CTRL|'L', pico_refresh}, + {CTRL|'M', newline}, + {CTRL|'N', forwline}, + {CTRL|'O', suspend_composer}, + {CTRL|'P', backline}, + {CTRL|'R', insfile}, +#ifdef SPELLER + {CTRL|'T', spell}, +#endif /* SPELLER */ + {CTRL|'U', yank}, + {CTRL|'V', forwpage}, + {CTRL|'W', forwsearch}, + {CTRL|'X', wquit}, + {CTRL|'Y', backpage}, +#if defined(SIGTSTP) || defined(_WINDOWS) + {CTRL|'Z', bktoshell}, +#endif + {CTRL|'@', forwword}, + {CTRL|'^', setmark}, + {CTRL|'_', alt_editor}, + {CTRL|KEY_LEFT, backword}, + {CTRL|KEY_RIGHT,forwword}, + {CTRL|KEY_HOME, gotobob}, + {CTRL|KEY_END, gotoeob}, + {0x7F, backdel}, + {0, NULL} +}; + + +/* + * Command table. + * This table is *roughly* in ASCII order, left to right across the + * characters of the command. This expains the funny location of the + * control-X commands. + */ +KEYTAB pkeytab[NBINDS] = { + {KEY_UP, backline}, + {KEY_DOWN, forwline}, + {KEY_RIGHT, forwchar}, + {KEY_LEFT, backchar}, + {KEY_PGUP, backpage}, + {KEY_PGDN, forwpage}, + {KEY_HOME, gotobol}, + {KEY_END, gotoeol}, + {KEY_DEL, forwdel}, +#ifdef MOUSE + {KEY_MOUSE, mousepress}, +#ifndef _WINDOWS + {CTRL|'\\', toggle_xterm_mouse}, +#endif +#endif + {CTRL|'A', gotobol}, + {CTRL|'B', backchar}, + {CTRL|'C', showcpos}, + {CTRL|'D', forwdel}, + {CTRL|'E', gotoeol}, + {CTRL|'F', forwchar}, + {CTRL|'G', whelp}, + {CTRL|'H', backdel}, + {CTRL|'I', tab}, + {CTRL|'J', fillpara}, + {CTRL|'K', killregion}, + {CTRL|'L', pico_refresh}, + {CTRL|'M', newline}, + {CTRL|'N', forwline}, + {CTRL|'O', filewrite}, + {CTRL|'P', backline}, + {CTRL|'R', insfile}, +#ifdef SPELLER + {CTRL|'T', spell}, +#endif /* SPELLER */ + {CTRL|'U', yank}, + {CTRL|'V', forwpage}, + {CTRL|'W', forwsearch}, + {CTRL|'X', wquit}, + {CTRL|'Y', backpage}, +#if defined(SIGTSTP) || defined(_WINDOWS) + {CTRL|'Z', bktoshell}, +#endif + {CTRL|'@', forwword}, + {CTRL|'^', setmark}, + {CTRL|KEY_LEFT, backword}, + {CTRL|KEY_RIGHT,forwword}, + {CTRL|KEY_HOME, gotobob}, + {CTRL|KEY_END, gotoeob}, + {0x7F, backdel}, + {0, NULL} +}; + +#endif /* EBIND_H */ diff --git a/pico/edef.h b/pico/edef.h new file mode 100644 index 00000000..209a4600 --- /dev/null +++ b/pico/edef.h @@ -0,0 +1,166 @@ +/* + * $Id: edef.h 900 2008-01-05 01:13:26Z 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 + * + * ======================================================================== + * + * Program: Global definitions and initializations + */ + +/* EDEF: Global variable definitions for + MicroEMACS 3.2 + + written by Dave G. Conroy + modified by Steve Wilhite, George Jones + greatly modified by Daniel Lawrence +*/ + +#ifndef EDEF_H +#define EDEF_H + +#ifdef maindef + +/* for MAIN.C */ + +/* initialized global definitions */ + +int fillcol = 72; /* Current fill column */ +int userfillcol = -1; /* Fillcol set from cmd line */ +UCS pat[NPAT]; /* Search pattern */ +UCS rpat[NPAT]; /* Replace pattern */ +int sgarbk = TRUE; /* TRUE if keyhelp garbaged */ +int sup_keyhelp = FALSE; /* TRUE if keyhelp is suppressed*/ +int mline_open = FALSE; /* TRUE if message line is open */ +int ComposerTopLine = 2; /* TRUE if message line is open */ +int ComposerEditing = FALSE; /* TRUE if editing the headers */ +char modecode[] = "WCSEVO"; /* letters to represent modes */ +long gmode = MDWRAP|MDREPLACE; /* global editor mode */ +int sgarbf = TRUE; /* TRUE if screen is garbage */ +int mpresf = FALSE; /* TRUE if message in last line */ +int clexec = FALSE; /* command line execution flag */ +char *alt_speller = NULL; /* alt spell checking command */ +int preserve_start_stop = FALSE; /* TRUE if pass ^S/^Q to term */ +UCS *glo_quote_str = NULL; /* points to quote string if set*/ +char *glo_quote_str_orig = NULL; +int use_system_translation = FALSE; +char *display_character_set = NULL; +char *keyboard_character_set = NULL; +UCS *glo_wordseps = NULL; /* points to word separators if set */ +char *glo_wordseps_orig = NULL; + +/* uninitialized global definitions */ +int currow; /* Cursor row */ +int curcol; /* Cursor column */ +int thisflag; /* Flags, this command */ +int lastflag; /* Flags, last command */ +int curgoal; /* Goal for C-P, C-N */ +char opertree[NLINE+1]; /* operate within this tree */ +char browse_dir[NLINE+1]; /* directory of last browse (cwd) */ +WINDOW *curwp; /* Current window */ +BUFFER *curbp; /* Current buffer */ +WINDOW *wheadp; /* Head of list of windows */ +BUFFER *bheadp; /* Head of list of buffers */ +BUFFER *blistp; /* Buffer for C-X C-B */ + +BUFFER *bfind(char *, int, int); /* Lookup a buffer by name */ +LINE *lalloc(int used); /* Allocate a line */ +int km_popped; /* menu popped up */ +#if defined(HAS_TERMCAP) || defined(HAS_TERMINFO) || defined(VMS) +KBESC_T *kbesc; /* keyboard esc sequence trie */ +#endif /* HAS_TERMCAP/HAS_TERMINFO/VMS */ +void *input_cs; /* passed to mbtow() via kbseq() */ + +#else /* maindef */ + +/* for all the other .C files */ + +/* initialized global external declarations */ + +extern int fillcol; /* Fill column */ +extern int userfillcol; /* Fillcol set from cmd line */ +extern UCS pat[]; /* Search pattern */ +extern UCS rpat[]; /* Replace pattern */ +extern int sgarbk; +extern int sup_keyhelp; +extern int mline_open; /* Message line is open */ +extern int ComposerTopLine; /* TRUE if message line is open */ +extern int ComposerEditing; /* TRUE if message line is open */ +extern char modecode[]; /* letters to represent modes */ +extern KEYTAB keytab[]; /* key bind to functions table */ +extern KEYTAB pkeytab[]; /* pico's function table */ +extern long gmode; /* global editor mode */ +extern int sgarbf; /* State of screen unknown */ +extern int mpresf; /* Stuff in message line */ +extern int clexec; /* command line execution flag */ +extern char *alt_speller; /* alt spell checking command */ +extern int preserve_start_stop; /* TRUE if pass ^S/^Q to term */ +extern UCS *glo_quote_str; /* points to quote string if set*/ +extern char *glo_quote_str_orig; +extern int use_system_translation; +extern char *display_character_set; +extern char *keyboard_character_set; +extern UCS *glo_wordseps; +extern char *glo_wordseps_orig; +/* initialized global external declarations */ +extern int currow; /* Cursor row */ +extern int curcol; /* Cursor column */ +extern int thisflag; /* Flags, this command */ +extern int lastflag; /* Flags, last command */ +extern int curgoal; /* Goal for C-P, C-N */ +extern char opertree[NLINE+1]; /* operate within this tree */ +extern char browse_dir[NLINE+1]; /* directory of last browse (cwd) */ +extern WINDOW *curwp; /* Current window */ +extern BUFFER *curbp; /* Current buffer */ +extern WINDOW *wheadp; /* Head of list of windows */ +extern BUFFER *bheadp; /* Head of list of buffers */ +extern BUFFER *blistp; /* Buffer for C-X C-B */ + +extern BUFFER *bfind(char *, int, int); /* Lookup a buffer by name */ +extern LINE *lalloc(int used); /* Allocate a line */ +extern int km_popped; /* menu popped up */ +/* + * This is a weird one. It has to be defined differently for pico and for + * pine. It seems to need to be defined at startup as opposed to set later. + * It doesn't work to set it later in pico. When pico is used with a + * screen reader it seems to jump to the cursor every time through the + * mswin_charavail() loop in GetKey, and the timeout is this long. So we + * just need to set it higher than we do in pine. If we understood this + * we would probably see that we don't need any timer at all in pico, but + * we don't remember why it is here so we'd better leave it. + * + * This is defined in .../pico/main.c and in .../alpine/alpine.c. + */ +extern int my_timer_period; /* here so can be set */ +#ifdef MOUSE +extern MENUITEM menuitems[]; /* key labels and functions */ +extern MENUITEM *mfunc; /* single generic function */ +extern mousehandler_t mtrack; /* func used to track the mouse */ +#endif /* MOUSE */ + +#if defined(HAS_TERMCAP) || defined(HAS_TERMINFO) || defined(VMS) +extern KBESC_T *kbesc; /* keyboard esc sequence trie */ +#endif /* HAS_TERMCAP/HAS_TERMINFO/VMS */ +extern void *input_cs; /* passed to mbtow() via kbseq() */ + +#endif /* maindef */ + +/* terminal table defined only in TERM.C */ + +#ifndef termdef +#if defined(VMS) && !defined(__ALPHA) +globalref +#else +extern +#endif /* VMS */ + TERM term; /* Terminal information. */ +#endif /* termdef */ + +#endif /* EDEF_H */ diff --git a/pico/efunc.h b/pico/efunc.h new file mode 100644 index 00000000..b551dfc7 --- /dev/null +++ b/pico/efunc.h @@ -0,0 +1,258 @@ +/* + * $Id: efunc.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 + * + * ======================================================================== + * + * Program: Pine's composer and pico's function declarations + */ + +/* EFUNC.H: MicroEMACS function declarations and names + + This file list all the C code functions used by MicroEMACS + and the names to use to bind keys to them. To add functions, + declare it here in both the extern function list and the name + binding table. + + Update History: + + Daniel Lawrence +*/ + +#ifndef EFUNC_H +#define EFUNC_H + + +/* External function declarations */ +/* attach.c */ +extern int AskAttach(char *, size_t, LMLIST **); +extern int SyncAttach(void); +extern int intag(UCS *, int); +extern char *prettysz(off_t); +extern int AttachError(void); +extern char *QuoteAttach(char *, size_t); +extern int getccol(int); + +/* basic.c */ +extern int gotobol(int, int); +extern int backchar(int, int); +extern int gotoeol(int, int); +extern int forwchar(int, int); +extern int gotoline(int, int); +extern int gotobob(int, int); +extern int gotoeob(int, int); +extern int forwline(int, int); +extern int backline(int, int); +extern int gotobop(int, int); +extern int gotoeop(int, int); +extern int forwpage(int, int); +extern int backpage(int, int); +extern int scrollupline(int, int); +extern int scrolldownline(int, int); +extern int scrollto(int, int); +extern int setmark(int, int); +extern int swapmark(int, int); +extern int setimark(int, int); +extern int swapimark(int, int); +extern int mousepress(int, int); +extern int toggle_xterm_mouse(int, int); +extern void swap_mark_and_dot_if_mark_comes_first(void); +extern int backchar_no_header_editor(int, int); +extern int getgoal(struct LINE *); + +/* bind.c */ +extern UCS normalize_cmd(UCS c, UCS list[][2], int sc); +extern int whelp(int, int); +extern int wscrollw(int, int, char **, int); +extern int normal(int, int (*)[2], int); +extern void rebindfunc(int (*)(int, int),int (*)(int, int)); +extern int bindtokey(UCS c, int (*f)(int, int)); + +/* browse.c */ +extern int FileBrowse(char *, size_t, char *, size_t, char *, size_t, int, LMLIST **); +extern int ResizeBrowser(void); +extern void set_browser_title(char *); +extern void zotlmlist(LMLIST *); +extern int time_to_check(void); +extern int LikelyASCII(char *); + +/* buffer.c */ +extern int anycb(void); +extern struct BUFFER *bfind(char *, int, int); +extern int bclear(struct BUFFER *); +extern int packbuf(char **, int *, int); +extern void readbuf(char **); + +/* composer.c */ +extern int InitMailHeader(struct pico_struct *); +extern int ResizeHeader(void); +extern int HeaderEditor(int, int); +extern void PaintHeader(int, int); +extern void ArrangeHeader(void); +extern int ToggleHeader(int); +extern int HeaderLen(void); +extern int UpdateHeader(int); +extern int entry_line(int, int); +extern int call_builder(struct headerentry *, int *, char **); +extern void call_expander(void); +extern void ShowPrompt(void); +extern int packheader(void); +extern void zotheader(void); +extern void display_for_send(void); +extern VARS_TO_SAVE *save_pico_state(void); +extern void restore_pico_state(VARS_TO_SAVE *); +extern void free_pico_state(VARS_TO_SAVE *); +extern void HeaderPaintCursor(void); +extern void PaintBody(int); +extern int AppendAttachment(char *, char *, char *); + +/* display.c */ +extern int vtinit(void); +extern int vtterminalinfo(int); +extern void vttidy(void); +extern void update(void); +extern void modeline(struct WINDOW *); +extern void movecursor(int, int); +extern void clearcursor(void); +extern void mlerase(void); +extern int mlyesno_utf8(char *, int); +extern int mlyesno(UCS *, int); +extern int mlreply_utf8(char *, char *, int, int, EXTRAKEYS *); +extern int mlreply(UCS *, UCS *, int, int, EXTRAKEYS *); +extern int mlreplyd_utf8(char *, char *, int, int, EXTRAKEYS *); +extern int mlreplyd(UCS *, UCS *, int, int, EXTRAKEYS *); +extern int mlwrite_utf8(char *, void *); +extern int mlwrite(UCS *, void *); +extern void emlwrite(char *, EML *); +extern void emlwrite_ucs4(UCS *, EML *); +extern void unknown_command(UCS); +extern void scrolldown(struct WINDOW *, int, int); +extern void scrollup(struct WINDOW *, int, int); +extern int doton(int *, unsigned int *); +extern int resize_pico(int, int); +extern void zotdisplay(void); +extern void pputc(UCS c, int a); +extern void pputs(UCS *s, int a); +extern void pputs_utf8(char *s, int a); +extern void peeol(void); +extern CELL *pscr(int, int); +extern void pclear(int, int); +extern int pinsert(CELL); +extern int pdel(void); +extern void wstripe(int, int, char *, int); +extern void wkeyhelp(KEYMENU *); +extern void get_cursor(int *, int *); +extern unsigned vcellwidth_a_to_b(int row, int a, int b); +extern int index_from_col(int row, int col); + +/* file.c */ +extern int fileread(int, int); +extern int insfile(int, int); +extern int readin(char *, int, int); +extern int filewrite(int, int); +extern int filesave(int, int); +extern int writeout(char *, int); +extern char *writetmp(int, char *); +extern int filename(int, int); +extern int in_oper_tree(char *); +extern int ifile(char *); + +/* fileio.c */ +extern int ffropen(char *); +extern int ffputline(CELL *, int); +extern int ffgetline(UCS *, size_t, size_t *, int); + +/* line.c */ +extern struct LINE *lalloc(int used); +extern void lfree(struct LINE *); +extern void lchange(int); +extern int linsert(int n, UCS c); +extern int geninsert(LINE **dotp, int *doto, LINE *linep, UCS c, int attb, int n, long *lines); +extern int lnewline(void); +extern int ldelete(long, int (*)(UCS)); +extern int lisblank(struct LINE *); +extern void kdelete(void); +extern int kinsert(UCS); +extern long kremove(int); +extern int ksize(void); +extern void fdelete(void); +extern int finsert(UCS); +extern long fremove(int); +extern void set_last_region_added(REGION *); +extern REGION *get_last_region_added(void); + +/* os.c */ +extern int o_insert(UCS); +extern int o_delete(void); + +/* pico.c */ +extern int pico(struct pico_struct *pm); +extern void edinit(char *); +extern int execute(UCS c, int f, int n); +extern int quickexit(int, int); +extern int abort_composer(int, int); +extern int suspend_composer(int, int); +extern int wquit(int, int); +extern int ctrlg(int, int); +extern int rdonly(void); +extern int pico_help(char **, char *, int); +extern void zotedit(void); +#ifdef _WINDOWS +int composer_file_drop(int, int, char *); +int pico_cursor(int, long); +#endif + +/* random.c */ +extern int showcpos(int, int); +extern int tab(int, int); +extern int newline(int, int); +extern int forwdel(int, int); +extern int backdel(int, int); +extern int killtext(int, int); +extern int yank(int, int); + +/* region.c */ +extern int killregion(int, int); +extern int deleteregion(int, int); +extern int markregion(int); +extern int getregion(REGION *, LINE *, int); +extern void unmarkbuffer(void); + +/* search.c */ +extern int forwsearch(int, int); +extern int readpattern(char *, int); +extern int forscan(int *, UCS *, LINE *, int, int); +extern void chword(UCS *, UCS *); + +/* spell.c */ +#ifdef SPELLER +extern int spell(int, int); +#endif + +/* window.c */ +extern int pico_refresh(int, int); +extern void redraw_pico_for_callback(void); + +/* word.c */ +extern int wrapword(void); +extern int backword(int, int); +extern int forwword(int, int); +extern int fillpara(int, int); +extern int fillbuf(int, int); +extern int inword(void); +extern int quote_match(UCS *, LINE *, UCS *, size_t); +extern int ucs4_isalnum(UCS); +extern int ucs4_isalpha(UCS); +extern int ucs4_isspace(UCS); +extern int ucs4_ispunct(UCS); + +#endif /* EFUNC_H */ diff --git a/pico/estruct.h b/pico/estruct.h new file mode 100644 index 00000000..80afd537 --- /dev/null +++ b/pico/estruct.h @@ -0,0 +1,366 @@ +/* + * $Id: estruct.h 767 2007-10-24 00:03:59Z 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 + * + * ======================================================================== + * + * Program: Struct and preprocessor definitions + * + */ + +/* ESTRUCT: Structure and preprocesser defined for + MicroEMACS 3.6 + + written by Dave G. Conroy + modified by Steve Wilhite, George Jones + greatly modified by Daniel Lawrence +*/ + +#ifndef ESTRUCT_H +#define ESTRUCT_H + +/* Configuration options */ + +#define CVMVAS 1 /* arguments to page forward/back in pages */ +#define NFWORD 1 /* forward word jumps to begining of word */ +#define TYPEAH 0 /* type ahead causes update to be skipped */ +#define REVSTA 1 /* Status line appears in reverse video */ + + +/* internal constants */ + +#define NBINDS 50 /* max # of bound keys */ +#ifdef MAXPATH +#define NFILEN MAXPATH /* # of bytes, file name */ +#else +#define NFILEN 200 /* # of bytes, file name */ +#endif +#define NBUFN 16 /* # of bytes, buffer name */ +#define NLINE 500 /* # of bytes, line */ +#define NSTRING 256 /* # of bytes, string buffers */ +#define NPAT 80 /* # of bytes, pattern */ +#define FARAWAY 1000 /* Huge number */ +#define NLOCKS 100 /* max # of file locks active */ + +#define AGRAVE 0x60 /* M- prefix, Grave (LK201) */ +#define METACH 0x1B /* M- prefix, Control-[, ESC */ +#define CTMECH 0x1C /* C-M- prefix, Control-\ */ +#define EXITCH 0x1D /* Exit level, Control-] */ +#define CTRLCH 0x1E /* C- prefix, Control-^ */ +#define HELPCH 0x1F /* Help key, Control-_ */ + +#define QNORML 0x0000 /* Flag meaning no flag ;) */ +#define QFFILE 0x0001 /* Flag buffer for file neme */ +#define QDEFLT 0x0002 /* Flag to use default answer */ +#define QBOBUF 0x0004 /* Start with cursor at BOL */ +#define QNODQT 0x0008 /* Don't accept double quotes */ + +#define FIOSUC 0 /* File I/O, success. */ +#define FIOFNF 1 /* File I/O, file not found. */ +#define FIOEOF 2 /* File I/O, end of file. */ +#define FIOERR 3 /* File I/O, error. */ +#define FIOLNG 4 /*line longer than allowed len */ +#define FIODIR 5 /* File is a directory */ +#define FIONWT 6 /* File lacks write permission */ +#define FIONRD 7 /* File lacks read permission */ +#define FIONEX 8 /* File lacks exec permission */ +#define FIOSYM 9 /* File is a symbolic link */ +#define FIOPER 10 /* Generic permission denied */ + + +#define CFCPCN 0x0001 /* Last command was C-P, C-N */ +#define CFKILL 0x0002 /* Last command was a kill */ +#define CFFILL 0x0004 /* Last command was a fill */ +#define CFSRCH 0x0008 /* Last command was WhereIs */ +#define CFFLBF 0x0010 /* Last cmd was full buf fill */ +#define CFFLPA 0x0020 /* Last cmd was paragraph fill */ + +/* + * macros to help filter character input + */ +#define CELLMASK 0xffffff +#define LOBIT_CHAR(C) ((C) > 0x1f && (C) < 0x7f) +#define HIBIT_CHAR(C) ((C) > 0x7f && (C) <= CELLMASK) +#define HIBIT_OK(C) (!(gmode & MDHBTIGN)) +#define VALID_KEY(C) (LOBIT_CHAR(C) || (HIBIT_OK(C) && HIBIT_CHAR(C))) +#define ctrl(c) ((c) & 0x1f) /* control character mapping */ + + +/* + * Placeholders for keymenu tags used in some ports (well, only in the + * windows port for now) to turn on commands in the Menu Bar. + */ +#ifndef KS_OSDATAVAR +#define KS_OSDATAVAR +#define KS_OSDATAGET(X) +#define KS_OSDATASET(X, Y) + +#define KS_NONE +#define KS_VIEW +#define KS_EXPUNGE +#define KS_ZOOM +#define KS_SORT +#define KS_HDRMODE +#define KS_MAINMENU +#define KS_FLDRLIST +#define KS_FLDRINDEX +#define KS_COMPOSER +#define KS_PREVPAGE +#define KS_PREVMSG +#define KS_NEXTMSG +#define KS_ADDRBOOK +#define KS_WHEREIS +#define KS_PRINT +#define KS_REPLY +#define KS_FORWARD +#define KS_BOUNCE +#define KS_DELETE +#define KS_UNDELETE +#define KS_FLAG +#define KS_SAVE +#define KS_EXPORT +#define KS_TAKEADDR +#define KS_SELECT +#define KS_APPLY +#define KS_POSTPONE +#define KS_SEND +#define KS_CANCEL +#define KS_ATTACH +#define KS_TOADDRBOOK +#define KS_READFILE +#define KS_JUSTIFY +#define KS_ALTEDITOR +#define KS_GENERALHELP +#define KS_SCREENHELP +#define KS_EXIT +#define KS_NEXTPAGE +#define KS_SAVEFILE +#define KS_CURPOSITION +#define KS_GOTOFLDR +#define KS_JUMPTOMSG +#define KS_RICHHDR +#define KS_EXITMODE +#define KS_REVIEW +#define KS_KEYMENU +#define KS_SELECTCUR +#define KS_UNDO +#define KS_SPELLCHK +#endif /* !KS_OSDATAVAR */ + + +/* + * There is a window structure allocated for every active display window. The + * windows are kept in a big list, in top to bottom screen order, with the + * listhead at "wheadp". Each window contains its own values of dot and mark. + * The flag field contains some bits that are set by commands to guide + * redisplay; although this is a bit of a compromise in terms of decoupling, + * the full blown redisplay is just too expensive to run for every input + * character. + */ +typedef struct WINDOW { + struct WINDOW *w_wndp; /* Next window */ + struct BUFFER *w_bufp; /* Buffer displayed in window */ + struct LINE *w_linep; /* Top line in the window */ + struct LINE *w_dotp; /* Line containing "." */ + int w_doto; /* Byte offset for "." */ + struct LINE *w_markp; /* Line containing "mark" */ + int w_marko; /* Byte offset for "mark" */ + struct LINE *w_imarkp; /* INTERNAL Line with "mark" */ + int w_imarko; /* INTERNAL "mark" byte offset */ + signed char w_toprow; /* Origin 0 top row of window */ + signed char w_ntrows; /* # of rows of text in window */ + char w_force; /* If NZ, forcing row. */ + char w_flag; /* Flags. */ +} WINDOW; + +#define WFFORCE 0x01 /* Window needs forced reframe */ +#define WFMOVE 0x02 /* Movement from line to line */ +#define WFEDIT 0x04 /* Editing within a line */ +#define WFHARD 0x08 /* Better to a full display */ +#define WFMODE 0x10 /* Update mode line. */ + +/* + * Text is kept in buffers. A buffer header, described below, exists for every + * buffer in the system. The buffers are kept in a big list, so that commands + * that search for a buffer by name can find the buffer header. There is a + * safe store for the dot and mark in the header, but this is only valid if + * the buffer is not being displayed (that is, if "b_nwnd" is 0). The text for + * the buffer is kept in a circularly linked list of lines, with a pointer to + * the header line in "b_linep". + * Buffers may be "Inactive" which means the files accosiated with them + * have not been read in yet. These get read in at "use buffer" time. + */ +typedef struct BUFFER { + struct BUFFER *b_bufp; /* Link to next BUFFER */ + struct LINE *b_dotp; /* Link to "." LINE structure */ + int b_doto; /* Offset of "." in above LINE */ + struct LINE *b_markp; /* The same as the above two, */ + int b_marko; /* but for the "mark" */ + struct LINE *b_linep; /* Link to the header LINE */ + long b_linecnt; /* Lines in buffer (mswin only) */ + long b_mode; /* editor mode of this buffer */ + char b_active; /* window activated flag */ + char b_nwnd; /* Count of windows on buffer */ + char b_flag; /* Flags */ + char b_fname[NFILEN]; /* File name */ + char b_bname[NBUFN]; /* Buffer name */ +} BUFFER; + +#define BFTEMP 0x01 /* Internal temporary buffer */ +#define BFCHG 0x02 /* Changed since last write */ +#define BFWRAPOPEN 0x04 /* Wordwrap should open new line */ + + +/* + * The starting position of a region, and the size of the region in + * characters, is kept in a region structure. Used by the region commands. + */ +typedef struct { + struct LINE *r_linep; /* Origin LINE address. */ + int r_offset; /* Origin LINE offset. */ + long r_size; /* Length in characters. */ +} REGION; + + +/* + * character and attribute pair. The basic building block + * of the editor. The bitfields may have to be changed to a char + * and short if there are problems... + */ +typedef struct CELL { + unsigned int c : 24; /* Character value in cell */ + unsigned int a : 8; /* Its attributes */ +} CELL; + +/* flags for color_options */ +#define COLOR_ANSI8_OPT 0x01 +#define COLOR_ANSI16_OPT 0x02 +#define COLOR_ANSI256_OPT 0x04 +#define COLOR_TRANS_OPT 0x08 + +/* + * All text is kept in circularly linked lists of "LINE" structures. These + * begin at the header line (which is the blank line beyond the end of the + * buffer). This line is pointed to by the "BUFFER". Each line contains the + * number of bytes in the line (the "used" size), the size of the text array, + * and the text. The end of line is not stored as a byte; it's implied. Future + * additions will include update hints, and a list of marks into the line. + */ +typedef struct LINE { + struct LINE *l_fp; /* Link to the next line */ + struct LINE *l_bp; /* Link to the previous line */ + int l_size; /* Allocated size */ + int l_used; /* Used size */ + CELL l_text[1]; /* A bunch of characters. */ +} LINE; + +#define lforw(lp) ((lp)->l_fp) +#define lback(lp) ((lp)->l_bp) +#define lgetc(lp, n) ((lp)->l_text[(n)]) +#define lputc(lp, n, c) ((lp)->l_text[(n)]=(c)) +#define llength(lp) ((lp)->l_used) + +/* + * The editor communicates with the display using a high level interface. A + * "TERM" structure holds useful variables, and indirect pointers to routines + * that do useful operations. The low level get and put routines are here too. + * This lets a terminal, in addition to having non standard commands, have + * funny get and put character code too. The calls might get changed to + * "termp->t_field" style in the future, to make it possible to run more than + * one terminal type. + */ +typedef struct { + short t_nrow; /* Number of rows - 1. */ + short t_ncol; /* Number of columns. */ + short t_margin; /* min margin for extended lines*/ + short t_mrow; /* Number of rows in menu */ + int (*t_open)(void); /* Open terminal at the start. */ + int (*t_terminalinfo)(int); /* Set up terminal info */ + int (*t_close)(void); /* Close terminal at end. */ + int (*t_getchar)(int return_on_intr, + int (*recorder)(int), + void (*bail_handler)(void)); + /* Get character from keyboard. */ + int (*t_putchar)(UCS); /* Put character to display. */ + int (*t_flush)(void); /* Flush output buffers. */ + int (*t_move)(int, int); /* Move the cursor, origin 0. */ + int (*t_eeol)(void); /* Erase to end of line. */ + int (*t_eeop)(void); /* Erase to end of page. */ + int (*t_beep)(void); /* Beep. */ + int (*t_rev)(int); /* set reverse video state */ +} TERM; + +/* structure for the table of initial key bindings */ + +typedef struct { + UCS k_code; /* Key code */ + int (*k_fp)(); /* Routine to handle it */ +} KEYTAB; + +/* sturcture used for key menu painting */ + +typedef struct { + char *name; /* key to display (UTF-8) */ + char *label; /* function name key invokes (UTF-8) */ + KS_OSDATAVAR /* port-specific data */ +} KEYMENU; + +typedef struct { + char *name; /* key to display */ + char *label; /* function name key invokes */ + UCS key; /* what to watch for and return */ + KS_OSDATAVAR /* port-specific data */ +} EXTRAKEYS; + +typedef struct lmlist { + char *dir; /* these 3 are all UTF-8 strings */ + char *fname; + char size[32]; + struct lmlist *next; +} LMLIST; + + +typedef struct VIDEO { + short v_flag; /* Flags */ + CELL v_text[1]; /* Screen data. */ +} VIDEO; + + +typedef union eml { + char *s; + int d; + long l; + UCS c; +} EML; + +typedef struct _file_io_data { + FILE *fp; /* stdio stream into file */ + long flags; /* state flags */ + char *name; /* pointer to file name */ +} FIOINFO; + +#define FIOINFO_READ 0x01 +#define FIOINFO_WRITE 0x02 + +#define ISCONTROL(C) ((C) < 0x20 || (C) == 0x7F \ + || ((gmode & P_HICTRL) && ((C) > 0x7F && (C) < 0xA0))) + +#ifdef MOUSE + +/* Test if mouse position (R, C) is in menu item (X) */ +#define M_ACTIVE(R, C, X) (((unsigned)(R)) >= (X)->tl.r \ + && ((unsigned)(R)) <= (X)->br.r \ + && ((unsigned)(C)) >= (X)->tl.c \ + && ((unsigned)(C)) < (X)->br.c) +#endif /* MOUSE */ + +#endif /* ESTRUCT_H */ diff --git a/pico/file.c b/pico/file.c new file mode 100644 index 00000000..2493d02b --- /dev/null +++ b/pico/file.c @@ -0,0 +1,1090 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: file.c 1082 2008-06-12 18:39:50Z 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 + * + * ======================================================================== + * + * Program: High level file input and output routines + * + */ + +/* + * The routines in this file + * handle the reading and writing of + * disk files. All of details about the + * reading and writing of the disk are + * in "fileio.c". + */ +#include "headers.h" +#include "../pith/charconv/filesys.h" + + +void init_insmsgchar_cbuf(void); +int insmsgchar(int); +char *file_split(char *, size_t, char *, int); + +/* + * Read a file into the current + * buffer. This is really easy; all you do it + * find the name of the file, and call the standard + * "read a file into the current buffer" code. + * Bound to "C-X C-R". + */ +int +fileread(int f, int n) +{ + register int s; + char fname[NFILEN]; + EML eml; + + if ((s=mlreply_utf8(_("Read file: "), fname, NFILEN, QNORML, NULL)) != TRUE) + return(s); + + if(gmode&MDSCUR){ + emlwrite(_("File reading disabled in secure mode"),NULL); + return(0); + } + + if (strlen(fname) == 0) { + emlwrite(_("No file name entered"),NULL); + return(0); + } + + if((gmode & MDTREE) && !in_oper_tree(fname)){ + eml.s = opertree; + emlwrite(_("Can't read file from outside of %s"), &eml); + return(0); + } + + return(readin(fname, TRUE, TRUE)); +} + + + +static char *inshelptext[] = { + /* TRANSLATORS: several lines of help text */ + N_("Insert File Help Text"), + " ", + N_(" Type in a file name to have it inserted into your editing"), + N_(" buffer between the line that the cursor is currently on"), + N_(" and the line directly below it. You may abort this by "), + N_("~ typing the ~F~2 (~^~C) key after exiting help."), + " ", + N_("End of Insert File Help"), + " ", + NULL +}; + +static char *writehelp[] = { + /* TRANSLATORS: several lines of help text */ + N_("Write File Help Text"), + " ", + N_(" Type in a file name to have it written out, thus saving"), + N_(" your buffer, to a file. You can abort this by typing "), + N_("~ the ~F~2 (~^~C) key after exiting help."), + " ", + N_("End of Write File Help"), + " ", + " ", + NULL +}; + + +/* + * Insert a file into the current + * buffer. This is really easy; all you do it + * find the name of the file, and call the standard + * "insert a file into the current buffer" code. + * Bound to "C-X C-I". + */ +int +insfile(int f, int n) +{ + register int s; + char fname[NLINE], dir[NLINE]; + int retval, bye = 0, msg = 0; + char prompt[64], *infile; + EXTRAKEYS menu_ins[5]; + EML eml; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + fname[0] = dir[0] = '\0'; + while(!bye){ + /* set up keymenu stuff */ + if(!msg){ + int last_menu = 0; + + menu_ins[last_menu].name = "^T"; + menu_ins[last_menu].key = (CTRL|'T'); + menu_ins[last_menu].label = N_("To Files"); + KS_OSDATASET(&menu_ins[last_menu], KS_NONE); + + if(Pmaster && Pmaster->msgntext){ + menu_ins[++last_menu].name = "^W"; + menu_ins[last_menu].key = (CTRL|'W'); + /* TRANSLATORS: Insert File is a key label for a command + that inserts a file into a message being composed. + InsertMsg means Insert Message and it inserts a different + message into the message being composed. */ + menu_ins[last_menu].label = msg ? N_("InsertFile") : N_("InsertMsg"); + KS_OSDATASET(&menu_ins[last_menu], KS_NONE); + } + +#if !defined(DOS) && !defined(MAC) + if(Pmaster && Pmaster->upload){ + menu_ins[++last_menu].name = "^Y"; + menu_ins[last_menu].key = (CTRL|'Y'); + menu_ins[last_menu].label = "RcvUpload"; + KS_OSDATASET(&menu_ins[last_menu], KS_NONE); + } +#endif /* !(DOS || MAC) */ + + if(gmode & MDCMPLT){ + menu_ins[++last_menu].name = msg ? "" : "TAB"; + menu_ins[last_menu].key = (CTRL|'I'); + menu_ins[last_menu].label = msg ? "" : N_("Complete"); + KS_OSDATASET(&menu_ins[last_menu], KS_NONE); + } + + menu_ins[++last_menu].name = NULL; + } + + snprintf(prompt, sizeof(prompt), "%s to insert from %s %s: ", + msg ? "Number of message" : "File", + (msg || (gmode&MDCURDIR)) + ? "current" + : ((gmode & MDTREE) || opertree[0]) ? opertree : "home", + msg ? "folder" : "directory"); + s = mlreplyd_utf8(prompt, fname, NLINE, QDEFLT, msg ? NULL : menu_ins); + /* something to read and it was edited or the default accepted */ + if(fname[0] && (s == TRUE || s == FALSE)){ + bye++; + if(msg){ + init_insmsgchar_cbuf(); + if((*Pmaster->msgntext)(atol(fname), insmsgchar)){ + eml.s = fname; + emlwrite(_("Message %s included"), &eml); + } + } + else{ + bye++; + if(gmode&MDSCUR){ + emlwrite(_("Can't insert file in restricted mode"),NULL); + } + else{ + if((gmode & MDTREE) + && !compresspath(opertree, fname, sizeof(fname))){ + eml.s = opertree; + emlwrite( + _("Can't insert file from outside of %s: too many ..'s"), &eml); + } + else{ + fixpath(fname, sizeof(fname)); + + if((gmode & MDTREE) && !in_oper_tree(fname)){ + eml.s = opertree; + emlwrite(_("Can't insert file from outside of %s"), &eml); + } + else + retval = ifile(fname); + } + } + } + } + else{ + switch(s){ + case (CTRL|'I') : + { + char *fn; + + dir[0] = '\0'; + fn = file_split(dir, sizeof(dir), fname, 0); + + if(!pico_fncomplete(dir, fn, sizeof(fname)-(fn-fname))) + (*term.t_beep)(); + } + + break; + case (CTRL|'W'): + msg = !msg; /* toggle what to insert */ + break; + case (CTRL|'T'): + if(msg){ + emlwrite(_("Can't select messages yet!"), NULL); + } + else{ + char *fn; + + fn = file_split(dir, sizeof(dir), fname, 1); + if(!isdir(dir, NULL, NULL)){ + strncpy(dir, (gmode&MDCURDIR) + ? (browse_dir[0] ? browse_dir : ".") + : ((gmode & MDTREE) || opertree[0]) + ? opertree + : (browse_dir[0] ? browse_dir + :gethomedir(NULL)), sizeof(dir)); + dir[sizeof(dir)-1] = '\0'; + } + else{ + if(*fn){ + int dirlen; + + dirlen = strlen(dir); + if(dirlen && dir[dirlen - 1] != C_FILESEP){ + strncat(dir, S_FILESEP, sizeof(dir)-strlen(dir)-1); + dir[sizeof(dir)-1] = '\0'; + } + + strncat(dir, fn, sizeof(dir)-strlen(dir)-1); + dir[sizeof(dir)-1] = '\0'; + if(!isdir(dir, NULL, NULL)) + dir[MIN(dirlen,sizeof(dir)-1)] = '\0'; + } + } + + fname[0] = '\0'; + if((s = FileBrowse(dir, sizeof(dir), fname, sizeof(fname), + NULL, 0, FB_READ, NULL)) == 1){ + if(gmode&MDSCUR){ + emlwrite(_("Can't insert in restricted mode"), + NULL); + sleep(2); + } + else{ + size_t len; + + len = strlen(dir)+strlen(S_FILESEP)+strlen(fname); + if ((infile = (char *)malloc((len+1)*sizeof(char))) != NULL){ + strncpy(infile, dir, len); + infile[len] = '\0'; + strncat(infile, S_FILESEP, len+1-1-strlen(infile)); + infile[len] = '\0'; + strncat(infile, fname, len+1-1-strlen(infile)); + infile[len] = '\0'; + retval = ifile(infile); + free((char *) infile); + } + else { + emlwrite("Trouble allocating space for insert!" + ,NULL); + sleep(3); + } + } + bye++; + } + else + fname[0] = '\0'; + + pico_refresh(FALSE, 1); + if(s != 1){ + update(); /* redraw on return */ + continue; + } + } + + break; + +#if !defined(DOS) && !defined(MAC) + case (CTRL|'Y') : + if(Pmaster && Pmaster->upload){ + char tfname[NLINE]; + + if(gmode&MDSCUR){ + emlwrite( + _("\007Restricted mode disallows uploaded command"), + NULL); + return(0); + } + + tfname[0] = '\0'; + retval = (*Pmaster->upload)(tfname, sizeof(tfname), NULL); + + pico_refresh(FALSE, 1); + update(); + + if(retval){ + retval = ifile(tfname); + bye++; + } + else + sleep(3); /* problem, show error! */ + + if(tfname[0]) /* clean up temp file */ + our_unlink(tfname); + } + else + (*term.t_beep)(); /* what? */ + + break; +#endif /* !(DOS || MAC) */ + case HELPCH: + if(Pmaster){ + VARS_TO_SAVE *saved_state; + + saved_state = save_pico_state(); + (*Pmaster->helper)(msg ? Pmaster->ins_m_help + : Pmaster->ins_help, + _("Help for Insert File"), 1); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + } + else + pico_help(inshelptext, _("Help for Insert File"), 1); + case (CTRL|'L'): + pico_refresh(FALSE, 1); + update(); + continue; + default: + ctrlg(FALSE, 0); + retval = s; + bye++; + } + } + } + curwp->w_flag |= WFMODE|WFHARD; + + return(retval); +} + +/* + * split out the file name from the path. + * Copy path into dirbuf, return filename, + * which is a pointer into orig_fname. + * is_for_browse - use browse dir if possible + * don't want to use it for TAB-completion + */ +char * +file_split(char *dirbuf, size_t dirbuflen, char *orig_fname, int is_for_browse) +{ + char *p, *fn; + size_t dirlen; + + if(*orig_fname && (p = strrchr(orig_fname, C_FILESEP))){ + fn = p + 1; + dirlen = p - orig_fname; + if(p == orig_fname){ + strncpy(dirbuf, S_FILESEP, dirbuflen); + dirbuf[dirbuflen-1] = '\0'; + } +#ifdef DOS + else if(orig_fname[0] == C_FILESEP + || (isalpha((unsigned char)orig_fname[0]) + && orig_fname[1] == ':')){ + if(orig_fname[1] == ':' && p == orig_fname+2) + dirlen = fn - orig_fname; + + dirlen = MIN(dirlen, dirbuflen-1); + strncpy(dirbuf, orig_fname, dirlen); + dirbuf[dirlen] = '\0'; + } +#else + else if (orig_fname[0] == C_FILESEP || orig_fname[0] == '~'){ + dirlen = MIN(dirlen, dirbuflen-1); + strncpy(dirbuf, orig_fname, dirlen); + dirbuf[dirlen] = '\0'; + } +#endif + else + snprintf(dirbuf, dirbuflen, "%s%c%.*s", + (gmode & MDCURDIR) + ? ((is_for_browse && browse_dir[0]) ? browse_dir : ".") + : ((gmode & MDTREE) || opertree[0]) + ? opertree + : ((is_for_browse && browse_dir[0]) + ? browse_dir : gethomedir(NULL)), + C_FILESEP, p - orig_fname, orig_fname); + } + else{ + fn = orig_fname; + strncpy(dirbuf, (gmode & MDCURDIR) + ? ((is_for_browse && browse_dir[0]) ? browse_dir : ".") + : ((gmode & MDTREE) || opertree[0]) + ? opertree + : ((is_for_browse && browse_dir[0]) + ? browse_dir : gethomedir(NULL)), dirbuflen); + dirbuf[dirbuflen-1] = '\0'; + } + + return fn; +} + + +static CBUF_S insmsgchar_cb; + +void +init_insmsgchar_cbuf(void) +{ + insmsgchar_cb.cbuf[0] = '\0'; + insmsgchar_cb.cbufp = insmsgchar_cb.cbuf; + insmsgchar_cb.cbufend = insmsgchar_cb.cbuf; +} + + +/* + * This is called with a stream of UTF-8 characters. + * We change that stream into UCS characters and insert + * those characters. + */ +int +insmsgchar(int c) +{ + if(c == '\n'){ + UCS *u; + + lnewline(); + for(u = glo_quote_str; u && *u; u++) + if(!linsert(1, *u)) + return(0); + } + else if(c != '\r'){ /* ignore CR (likely CR of CRLF) */ + int outchars; + UCS ucs; + + if((outchars = utf8_to_ucs4_oneatatime(c, &insmsgchar_cb, &ucs, NULL)) != 0) + if(!linsert(1, ucs)) + return(0); + } + + return(1); +} + + +/* + * Read file "fname" into the current + * buffer, blowing away any text found there. Called + * by both the read and find commands. Return the final + * status of the read. Also called by the mainline, + * to read in a file specified on the command line as + * an argument. + */ +int +readin(char fname[], /* name of file to read */ + int lockfl, /* check for file locks? */ + int rename) /* don't rename if reading from, say, alt speller */ +{ + UCS line[NLINE], *linep; + long nline; + int s, done, newline; + + curbp->b_linecnt = -1; /* Must be recalculated */ + if ((s = bclear(curbp)) != TRUE) /* Might be old. */ + return (s); + + if(rename){ + strncpy(curbp->b_fname, fname, sizeof(curbp->b_fname)); + curbp->b_fname[sizeof(curbp->b_fname)-1] = '\0'; + } + + if ((s=ffropen(fname)) != FIOSUC){ /* Hard file open. */ + if(s == FIOFNF) /* File not found. */ + emlwrite(_("New file"), NULL); + else + fioperr(s, fname); + } + else{ + size_t charsread = 0; + + emlwrite(_("Reading file"), NULL); + nline = 0L; + done = newline = 0; + while(!done) + if((s = ffgetline(line, NLINE, &charsread, 1)) == FIOEOF){ + char b[200]; + + curbp->b_flag &= ~(BFTEMP|BFCHG); + gotobob(FALSE, 1); + if(nline > 1) + snprintf(b, sizeof(b), _("Read %ld lines"), nline); + else + snprintf(b, sizeof(b), _("Read 1 line")); + + emlwrite(b, NULL); + + break; + } + else{ + if(newline){ + lnewline(); + newline = 0; + } + + switch(s){ + case FIOSUC : + nline++; + newline = 1; + + case FIOLNG : + for(linep = line; charsread-- > 0; linep++) + linsert(1, *linep); + + break; + + default : + done++; + break; + } + } + + ffclose(); /* Ignore errors. */ + } + + return(s != FIOERR && s != FIOFNF); /* true if success */ +} + + +/* + * Ask for a file name, and write the + * contents of the current buffer to that file. + * Update the remembered file name and clear the + * buffer changed flag. This handling of file names + * is different from the earlier versions, and + * is more compatable with Gosling EMACS than + * with ITS EMACS. Bound to "C-X C-W". + */ +int +filewrite(int f, int n) +{ + register WINDOW *wp; + register int s; + char fname[NFILEN]; + char shows[NLINE], origshows[NLINE], *bufp; + EXTRAKEYS menu_write[3]; + EML eml; + + if(curbp->b_fname[0] != 0){ + strncpy(fname, curbp->b_fname, sizeof(curbp->b_fname)); + curbp->b_fname[sizeof(curbp->b_fname)-1] = '\0'; + } + else + fname[0] = '\0'; + + menu_write[0].name = "^T"; + menu_write[0].label = N_("To Files"); + menu_write[0].key = (CTRL|'T'); + menu_write[1].name = "TAB"; + menu_write[1].label = N_("Complete"); + menu_write[1].key = (CTRL|'I'); + menu_write[2].name = NULL; + for(;!(gmode & MDTOOL);){ + /* TRANSLATORS: Asking for name of file to write data into */ + s = mlreplyd_utf8(_("File Name to write : "), fname, NFILEN, + QDEFLT|QFFILE, menu_write); + + switch(s){ + case FALSE: + if(!fname[0]){ /* no file name to write to */ + ctrlg(FALSE, 0); + return(s); + } + case TRUE: + if((gmode & MDTREE) && !compresspath(opertree, fname, sizeof(fname))){ + eml.s = opertree; + emlwrite(_("Can't write outside of %s: too many ..'s"), &eml); + sleep(2); + continue; + } + else{ + fixpath(fname, sizeof(fname)); /* fixup ~ in file name */ + if((gmode & MDTREE) && !in_oper_tree(fname)){ + eml.s = opertree; + emlwrite(_("Can't write outside of %s"), &eml); + sleep(2); + continue; + } + } + + break; + + case (CTRL|'I'): + { + char *fn, *p, dir[NFILEN]; + int l = NFILEN; + + dir[0] = '\0'; + if(*fname && (p = strrchr(fname, C_FILESEP))){ + fn = p + 1; + l -= fn - fname; + if(p == fname){ + strncpy(dir, S_FILESEP, sizeof(dir)); + dir[sizeof(dir)-1] = '\0'; + } +#ifdef DOS + else if(fname[0] == C_FILESEP + || (isalpha((unsigned char)fname[0]) + && fname[1] == ':')) +#else + else if (fname[0] == C_FILESEP || fname[0] == '~') +#endif + { + strncpy(dir, fname, MIN(p - fname, sizeof(dir)-1)); + dir[MIN(p-fname, sizeof(dir)-1)] = '\0'; + } + else + snprintf(dir, sizeof(dir), "%s%c%.*s", + (gmode & MDCURDIR) + ? "." + : ((gmode & MDTREE) || opertree[0]) + ? opertree : gethomedir(NULL), + C_FILESEP, p - fname, fname); + } + else{ + fn = fname; + strncpy(dir, (gmode & MDCURDIR) + ? "." + : ((gmode & MDTREE) || opertree[0]) + ? opertree : gethomedir(NULL), sizeof(dir)); + dir[sizeof(dir)-1] = '\0'; + } + + if(!pico_fncomplete(dir, fn, sizeof(fname)-(fn-fname))) + (*term.t_beep)(); + } + + continue; + + case (CTRL|'T'): + /* If we have a file name, break up into path and file name.*/ + *shows = 0; + if(*fname) { + if(isdir(fname, NULL, NULL)) { + /* fname is a directory. */ + strncpy(shows, fname, sizeof(shows)); + shows[sizeof(shows)-1] = '\0'; + *fname = '\0'; + } + else { + /* Find right most separator. */ + bufp = strrchr (fname, C_FILESEP); + if (bufp != NULL) { + /* Copy directory part to 'shows', and file + * name part to front of 'fname'. */ + *bufp = '\0'; + strncpy(shows, fname, sizeof(shows)); + shows[sizeof(shows)-1] = '\0'; + strncpy(fname, bufp+1, MIN(strlen(bufp+1)+1, sizeof(fname))); + fname[sizeof(fname)-1] = '\0'; + } + } + } + + /* If we did not end up with a valid directory, use home. */ + if (!*shows || !isdir (shows, NULL, NULL)){ + strncpy(shows, ((gmode & MDTREE) || opertree[0]) + ? opertree + : (browse_dir[0] ? browse_dir + : gethomedir(NULL)), sizeof(shows)); + shows[sizeof(shows)-1] = '\0'; + } + + strncpy(origshows, shows, sizeof(origshows)); + origshows[sizeof(origshows)-1] = '\0'; + if ((s = FileBrowse(shows, sizeof(shows), fname, sizeof(fname), NULL, 0, + FB_SAVE, NULL)) == 1) { + if (strlen(shows)+strlen(S_FILESEP)+strlen(fname) < NLINE){ + strncat(shows, S_FILESEP, sizeof(shows)-strlen(shows)-1); + shows[sizeof(shows)-1] = '\0'; + strncat(shows, fname, sizeof(shows)-strlen(shows)-1); + shows[sizeof(shows)-1] = '\0'; + strncpy(fname, shows, sizeof(fname)); + fname[sizeof(fname)-1] = '\0'; + } + else { + emlwrite("Cannot write. File name too long!!",NULL); + sleep(3); + } + } + else if (s == 0 && strcmp(shows, origshows)){ + strncat(shows, S_FILESEP, sizeof(shows)-strlen(shows)-1); + shows[sizeof(shows)-1] = '\0'; + strncat(shows, fname, sizeof(shows)-strlen(shows)-1); + shows[sizeof(shows)-1] = '\0'; + strncpy(fname, shows, sizeof(fname)); + fname[sizeof(fname)-1] = '\0'; + } + else if (s == -1){ + emlwrite("Cannot write. File name too long!!",NULL); + sleep(3); + } + + pico_refresh(FALSE, 1); + update(); + if(s == 1) + break; + else + continue; + + case HELPCH: + pico_help(writehelp, "", 1); + case (CTRL|'L'): + pico_refresh(FALSE, 1); + update(); + continue; + + default: + return(s); + break; + } + + if(strcmp(fname, curbp->b_fname) == 0) + break; + + if((s=fexist(fname, "w", (off_t *)NULL)) == FIOSUC){ + /*exists overwrite? */ + + snprintf(shows, sizeof(shows), _("File \"%s\" exists, OVERWRITE"), fname); + if((s=mlyesno_utf8(shows, FALSE)) == TRUE) + break; + } + else if(s == FIOFNF){ + break; /* go write it */ + } + else{ /* some error, can't write */ + fioperr(s, fname); + return(ABORT); + } + } + + emlwrite("Writing...", NULL); + + if ((s=writeout(fname, 0)) != -1) { + if(!(gmode&MDTOOL)){ + strncpy(curbp->b_fname, fname, sizeof(curbp->b_fname)); + curbp->b_fname[sizeof(curbp->b_fname)-1] = '\0'; + curbp->b_flag &= ~BFCHG; + + wp = wheadp; /* Update mode lines. */ + while (wp != NULL) { + if (wp->w_bufp == curbp) + if((Pmaster && s == TRUE) || Pmaster == NULL) + wp->w_flag |= WFMODE; + wp = wp->w_wndp; + } + } + + if(s > 1){ + eml.s = comatose(s); + emlwrite(_("Wrote %s lines"), &eml); + } + else + emlwrite(_("Wrote 1 line"), NULL); + } + + return ((s == -1) ? FALSE : TRUE); +} + + +/* + * Save the contents of the current + * buffer in its associatd file. No nothing + * if nothing has changed (this may be a bug, not a + * feature). Error if there is no remembered file + * name for the buffer. Bound to "C-X C-S". May + * get called by "C-Z". + */ +int +filesave(int f, int n) +{ + register WINDOW *wp; + register int s; + EML eml; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + if ((curbp->b_flag&BFCHG) == 0) /* Return, no changes. */ + return (TRUE); + if (curbp->b_fname[0] == 0) { /* Must have a name. */ + emlwrite(_("No file name"), NULL); + sleep(2); + return (FALSE); + } + + emlwrite("Writing...", NULL); + if ((s=writeout(curbp->b_fname, 0)) != -1) { + curbp->b_flag &= ~BFCHG; + wp = wheadp; /* Update mode lines. */ + while (wp != NULL) { + if (wp->w_bufp == curbp) + if(Pmaster == NULL) + wp->w_flag |= WFMODE; + wp = wp->w_wndp; + } + if(s > 1){ + eml.s = comatose(s); + emlwrite(_("Wrote %s lines"), &eml); + } + else + emlwrite(_("Wrote 1 line"), NULL); + } + return (s); +} + + +/* + * This function performs the details of file + * writing. Uses the file management routines in the + * "fileio.c" package. The number of lines written is + * displayed. Sadly, it looks inside a LINE; provide + * a macro for this. Most of the grief is error + * checking of some sort. + * + * If the argument readonly is set, the file is created with user read + * and write permission only if it doesn't already exist. Note that the + * word readonly is misleading. It is user r/w permission only. + * + * CHANGES: 1 Aug 91: returns number of lines written or -1 on error, MSS + */ +int +writeout(char *fn, int readonly) +{ + register int s; + register int t; + register LINE *lp; + register int nline; + + if (!((s = ffwopen(fn, readonly)) == FIOSUC && ffelbowroom())) + return (-1); /* Open writes message. */ + + lp = lforw(curbp->b_linep); /* First line. */ + nline = 0; /* Number of lines. */ + while (lp != curbp->b_linep) { + if ((s=ffputline(&lp->l_text[0], llength(lp))) != FIOSUC) + break; + ++nline; + lp = lforw(lp); + } + + t = ffclose(); /* ffclose complains if error */ + + if(s == FIOSUC) + s = t; /* report worst case */ + + return ((s == FIOSUC) ? nline : -1); +} + + +/* + * writetmp - write a temporary file for message text, mindful of + * access restrictions and included text. If n is true, include + * lines that indicated included message text, otw forget them + * If dir is non-null, put the temp file in that directory. + */ +char * +writetmp(int n, char *dir) +{ + static char fn[NFILEN]; + register int s; + register int t; + register LINE *lp; + register int nline; + + tmpname(dir, fn); + + /* Open writes message */ + if (!fn[0] || (s=ffwopen(fn, TRUE)) != FIOSUC){ + if(fn[0]) + our_unlink(fn); + + return(NULL); + } + + lp = lforw(curbp->b_linep); /* First line. */ + nline = 0; /* Number of lines. */ + while (lp != curbp->b_linep) { + if(n || (!n && lp->l_text[0].c != '>')) + if ((s=ffputline(&lp->l_text[0], llength(lp))) != FIOSUC) + break; + + ++nline; + lp = lforw(lp); + } + + t = ffclose(); /* ffclose complains if error */ + + if(s == FIOSUC) + s = t; /* remember worst case */ + + if (s != FIOSUC){ /* Some sort of error. */ + our_unlink(fn); + return(NULL); + } + + return(fn); +} + + +/* + * Insert file "fname" into the current + * buffer, Called by insert file command. Return the final + * status of the read. + */ +int +ifile(char fname[]) +{ + UCS line[NLINE], *linep; + long nline; + int s, done, newline; + size_t charsread = 0; + EML eml; + + if ((s=ffropen(fname)) != FIOSUC){ /* Hard file open. */ + fioperr(s, fname); + return(FALSE); + } + + gotobol(FALSE, 1); + + curbp->b_flag |= BFCHG; /* we have changed */ + curbp->b_flag &= ~BFTEMP; /* and are not temporary*/ + curbp->b_linecnt = -1; /* must be recalculated */ + + eml.s = fname; + emlwrite(_("Inserting %s."), &eml); + done = newline = 0; + nline = 0L; + while(!done) + if((s = ffgetline(line, NLINE, &charsread, 1)) == FIOEOF){ + char b[200]; + + if(llength(curwp->w_dotp) > curwp->w_doto) + lnewline(); + else + forwchar(FALSE, 1); + + if(nline > 1) + snprintf(b, sizeof(b), _("Inserted %ld lines"), nline); + else + snprintf(b, sizeof(b), _("Inserted 1 line")); + + emlwrite(b, NULL); + break; + } + else{ + if(newline){ + lnewline(); + newline = 0; + } + + switch(s){ + case FIOSUC : /* copy line into buf */ + nline++; + newline = 1; + + case FIOLNG : + for(linep = line; charsread-- > 0; linep++) + linsert(1, *linep); + + break; + + default : + done++; + } + } + + ffclose(); /* Ignore errors. */ + + return(s != FIOERR); +} + + +/* + * pico_fncomplete - pico's function to complete the given file name + */ +int +pico_fncomplete(char *dirarg, char *fn, size_t fnlen) +{ + char *p, *dlist, tmp[NLINE], dir[NLINE]; + int n, i, match = -1; +#ifdef DOS +#define FILECMP(x, y) (toupper((unsigned char)(x))\ + == toupper((unsigned char)(y))) +#else +#define FILECMP(x, y) ((x) == (y)) +#endif + + strncpy(dir, dirarg, sizeof(dir)); + dir[sizeof(dir)-1] = '\0'; + pfnexpand(dir, sizeof(dir)); + if(*fn && (dlist = p = getfnames(dir, fn, &n, NULL, 0))){ + memset(tmp, 0, sizeof(tmp)); + while(n--){ /* any names in it */ + for(i = 0; i < sizeof(tmp)-1 && fn[i] && FILECMP(p[i], fn[i]); i++) + ; + + if(!fn[i]){ /* match or more? */ + if(tmp[0]){ + for(; p[i] && FILECMP(p[i], tmp[i]); i++) + ; + + match = !p[i] && !tmp[i]; + tmp[i] = '\0'; /* longest common string */ + } + else{ + match = 1; /* may be it!?! */ + strncpy(tmp, p, sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + } + } + + p += strlen(p) + 1; + } + + free(dlist); + } + + if(match >= 0){ + strncpy(fn, tmp, fnlen); + fn[fnlen-1] = '\0'; + if(match == 1){ + if ((strlen(dir)+strlen(S_FILESEP)+strlen(fn)) < sizeof(dir)){ + strncat(dir, S_FILESEP, sizeof(dir)-strlen(dir)-1); + dir[sizeof(dir)-1] = '\0'; + strncat(dir, fn, sizeof(dir)-strlen(dir)-1); + dir[sizeof(dir)-1] = '\0'; + if(isdir(dir, NULL, NULL)){ + strncat(fn, S_FILESEP, fnlen-strlen(fn)-1); + fn[fnlen-1] = '\0'; + } + } + else{ + emlwrite("File name too BIG!!",0); + sleep(3); + *fn = '\0'; + } + + } + } + + return(match == 1); +} + + +/* + * in_oper_tree - returns true if file "f" does reside in opertree + */ +int +in_oper_tree(char *f) +{ + int end = strlen(opertree); + + return(!strncmp(opertree, f, end) + && (opertree[end-1] == '/' + || opertree[end-1] == '\\' + || f[end] == '\0' + || f[end] == '/' + || f[end] == '\\')); +} diff --git a/pico/fileio.c b/pico/fileio.c new file mode 100644 index 00000000..5cf124c0 --- /dev/null +++ b/pico/fileio.c @@ -0,0 +1,157 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: fileio.c 769 2007-10-24 00:15:40Z 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 + * + * ======================================================================== + * + * Program: file reading routines + * + */ + +/* + * The routines in this file read and write ASCII files from the disk. All of + * the knowledge about files are here. A better message writing scheme should + * be used. + */ +#include "headers.h" +#include "../pith/charconv/filesys.h" + + +#if defined(bsd) || defined(lnx) +extern int errno; +#endif + + + +FIOINFO g_pico_fio; + +/* + * Open a file for reading. + */ +int +ffropen(char *fn) +{ + int status; + + if ((status = fexist(g_pico_fio.name = fn, "r", (off_t *)NULL)) == FIOSUC){ + g_pico_fio.flags = FIOINFO_READ; + if((g_pico_fio.fp = our_fopen(g_pico_fio.name, "r")) == NULL) + status = FIOFNF; + } + + return (status); +} + + +/* + * Write a line to the already opened file. The "buf" points to the buffer, + * and the "nbuf" is its length, less the free newline. Return the status. + * Check only at the newline. + */ +int +ffputline(CELL buf[], int nbuf) +{ + register int i; + EML eml; + + for(i = 0; i < nbuf; ++i) + if(write_a_wide_char((UCS) buf[i].c, g_pico_fio.fp) == EOF) + break; + + if(i == nbuf) + write_a_wide_char((UCS) '\n', g_pico_fio.fp); + + if(ferror(g_pico_fio.fp)){ + eml.s = errstr(errno); + emlwrite("\007Write error: %s", &eml); + sleep(5); + return FIOERR; + } + + return FIOSUC; +} + + +/* + * Read a line from a file, and store the bytes in the supplied buffer. The + * "nbuf" is the length of the buffer. Complain about long lines and lines + * at the end of the file that don't have a newline present. Check for I/O + * errors too. Return status. + * + * Translate the line from the user's locale charset to UCS-4. + */ +int +ffgetline(UCS buf[], size_t nbuf, size_t *charsreturned, int msg) +{ + size_t i; + UCS ucs; + + if(charsreturned) + *charsreturned = 0; + + i = 0; + + while((ucs = read_a_wide_char(g_pico_fio.fp, input_cs)) != CCONV_EOF && ucs != '\n'){ + /* + * Don't blat the CR should the newline be CRLF and we're + * running on a unix system. NOTE: this takes care of itself + * under DOS since the non-binary open turns newlines into '\n'. + */ + if(ucs == '\r'){ + if((ucs = read_a_wide_char(g_pico_fio.fp, input_cs)) == CCONV_EOF || ucs == '\n') + break; + + if(i < nbuf-2) /* Bare CR. Insert it and go on... */ + buf[i++] = '\r'; /* else, we're up a creek */ + } + + if(i >= nbuf-2){ + buf[nbuf - 2] = ucs; /* store last char read */ + buf[nbuf - 1] = '\0'; /* and terminate it */ + if(charsreturned) + *charsreturned = nbuf - 1; + + if(msg) + emlwrite("File has long line", NULL); + + return FIOLNG; + } + + buf[i++] = ucs; + } + + if(ucs == CCONV_EOF){ + if(ferror(g_pico_fio.fp)){ + emlwrite("File read error", NULL); + if(charsreturned) + *charsreturned = i; + + return FIOERR; + } + + if(i != 0) + emlwrite("File doesn't end with newline. Adding one.", NULL); + else{ + if(charsreturned) + *charsreturned = i; + return FIOEOF; + } + } + + buf[i] = '\0'; + + if(charsreturned) + *charsreturned = i; + + return FIOSUC; +} diff --git a/pico/headers.h b/pico/headers.h new file mode 100644 index 00000000..5c6e8cab --- /dev/null +++ b/pico/headers.h @@ -0,0 +1,68 @@ +/* + * $Id: headers.h 764 2007-10-23 23:44:49Z 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 + * + * ======================================================================== + * + * + * + * headers.h + * + * The include file to always include that includes a few other things + * - includes the most general system files and other pine include files + * - declares the global variables + */ + + +#ifndef _PICO_HEADERS_INCLUDED +#define _PICO_HEADERS_INCLUDED + + +#include +#include + +/*---------------------------------------------------------------------- + Include files + + ----*/ + +#ifdef _WINDOWS +#include "osdep/msmenu.h" +#include "osdep/mswin.h" +#endif /* _WINDOWS */ +#include "estruct.h" +#include "mode.h" +#include "pico.h" +#include "keydefs.h" +#include "edef.h" +#include "efunc.h" +#include "utf8stub.h" + +/* include osdep proto's defn's */ +#include "osdep/altedit.h" +#include "osdep/chkpoint.h" +#include "osdep/color.h" +#include "osdep/filesys.h" +#include "osdep/getkey.h" +#include "osdep/mouse.h" +#include "osdep/newmail.h" +#include "osdep/popen.h" +#include "osdep/raw.h" +#include "osdep/read.h" +#include "osdep/shell.h" +#include "osdep/signals.h" +#include "osdep/spell.h" +#include "osdep/terminal.h" +#include "osdep/tty.h" + + +#endif /* _PICO_HEADERS_INCLUDED */ diff --git a/pico/keydefs.h b/pico/keydefs.h new file mode 100644 index 00000000..335391c0 --- /dev/null +++ b/pico/keydefs.h @@ -0,0 +1,164 @@ +/* + * $Id: keydefs.h 766 2007-10-23 23:59:00Z 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 PICO_KEYDEFS_INCLUDED +#define PICO_KEYDEFS_INCLUDED + +/* + * Key value definitions for pico/pine + */ + + +/* + * Char conversion constants + * Make these different values than c-client's U8G_ERROR constants, though I don't + * think that is necessary. + * These values are not possible as regular UCS-4 values. + */ + +#undef CTRL +#define CTRL 0x1000000 +#define FUNC 0x2000000 + +#define KEY_BASE 0x40000000 +#define CCONV_NEEDMORE (KEY_BASE+1) /* need more octets */ +#define CCONV_BADCHAR (KEY_BASE+2) /* can tell it's bad conversion */ +#define CCONV_EOF (KEY_BASE+3) + +/* + * defs for keypad and function keys... + */ +#define KEY_UP (KEY_BASE+0x100) +#define KEY_DOWN (KEY_BASE+0x101) +#define KEY_RIGHT (KEY_BASE+0x102) +#define KEY_LEFT (KEY_BASE+0x103) +#define KEY_PGUP (KEY_BASE+0x104) +#define KEY_PGDN (KEY_BASE+0x105) +#define KEY_HOME (KEY_BASE+0x106) +#define KEY_END (KEY_BASE+0x107) +#define KEY_DEL (KEY_BASE+0x108) +#define BADESC (KEY_BASE+0x109) +#define KEY_MOUSE (KEY_BASE+0x10a) +#define KEY_SCRLUPL (KEY_BASE+0x10b) +#define KEY_SCRLDNL (KEY_BASE+0x10c) +#define KEY_SCRLTO (KEY_BASE+0x10d) +#define KEY_XTERM_MOUSE (KEY_BASE+0x10e) +#define KEY_DOUBLE_ESC (KEY_BASE+0x10f) +#define KEY_SWALLOW_Z (KEY_BASE+0x110) +#define KEY_SWAL_UP (KEY_BASE+0x111) /* These four have to be in the same order */ +#define KEY_SWAL_DOWN (KEY_BASE+0x112) /* as KEY_UP, KEY_DOWN, ... */ +#define KEY_SWAL_RIGHT (KEY_BASE+0x113) +#define KEY_SWAL_LEFT (KEY_BASE+0x114) +#define KEY_KERMIT (KEY_BASE+0x115) +#define KEY_JUNK (KEY_BASE+0x116) +#define KEY_RESIZE (KEY_BASE+0x117) +#define CTRL_KEY_UP (KEY_BASE+0x118) +#define CTRL_KEY_DOWN (KEY_BASE+0x119) +#define CTRL_KEY_RIGHT (KEY_BASE+0x11a) +#define CTRL_KEY_LEFT (KEY_BASE+0x11b) + +/* + * Don't think we are using the fact that this is zero anywhere, + * but just in case we'll leave it. + */ +#define NO_OP_COMMAND 0x0 /* no-op for short timeouts */ + +#define NO_OP_IDLE (KEY_BASE+0x120) /* no-op for >25 second timeouts */ +#define READY_TO_READ (KEY_BASE+0x121) +#define BAIL_OUT (KEY_BASE+0x122) +#define PANIC_NOW (KEY_BASE+0x123) +#define READ_INTR (KEY_BASE+0x124) +#define NODATA (KEY_BASE+0x125) +#define KEY_UTF8 (KEY_BASE+0x126) +#define KEY_UNKNOWN (KEY_BASE+0x127) + +/* + * defines for function keys + */ +#define F1 (KEY_BASE+FUNC+0) /* Function key one */ +#define F2 (KEY_BASE+FUNC+1) /* Function key two */ +#define F3 (KEY_BASE+FUNC+2) /* Function key three */ +#define F4 (KEY_BASE+FUNC+3) /* Function key four */ +#define F5 (KEY_BASE+FUNC+4) /* Function key five */ +#define F6 (KEY_BASE+FUNC+5) /* Function key six */ +#define F7 (KEY_BASE+FUNC+6) /* Function key seven */ +#define F8 (KEY_BASE+FUNC+7) /* Function key eight */ +#define F9 (KEY_BASE+FUNC+8) /* Function key nine */ +#define F10 (KEY_BASE+FUNC+9) /* Function key ten */ +#define F11 (KEY_BASE+FUNC+0xa) /* Function key eleven */ +#define F12 (KEY_BASE+FUNC+0xb) /* Function key twelve */ + +/* 1st tier pine function keys */ +#define PF1 F1 +#define PF2 F2 +#define PF3 F3 +#define PF4 F4 +#define PF5 F5 +#define PF6 F6 +#define PF7 F7 +#define PF8 F8 +#define PF9 F9 +#define PF10 F10 +#define PF11 F11 +#define PF12 F12 + +#define PF2OPF(x) (x + 0x10) +#define PF2OOPF(x) (x + 0x20) +#define PF2OOOPF(x) (x + 0x30) + +/* 2nd tier pine function keys */ +#define OPF1 PF2OPF(PF1) +#define OPF2 PF2OPF(PF2) +#define OPF3 PF2OPF(PF3) +#define OPF4 PF2OPF(PF4) +#define OPF5 PF2OPF(PF5) +#define OPF6 PF2OPF(PF6) +#define OPF7 PF2OPF(PF7) +#define OPF8 PF2OPF(PF8) +#define OPF9 PF2OPF(PF9) +#define OPF10 PF2OPF(PF10) +#define OPF11 PF2OPF(PF11) +#define OPF12 PF2OPF(PF12) + +/* 3rd tier pine function keys */ +#define OOPF1 PF2OOPF(PF1) +#define OOPF2 PF2OOPF(PF2) +#define OOPF3 PF2OOPF(PF3) +#define OOPF4 PF2OOPF(PF4) +#define OOPF5 PF2OOPF(PF5) +#define OOPF6 PF2OOPF(PF6) +#define OOPF7 PF2OOPF(PF7) +#define OOPF8 PF2OOPF(PF8) +#define OOPF9 PF2OOPF(PF9) +#define OOPF10 PF2OOPF(PF10) +#define OOPF11 PF2OOPF(PF11) +#define OOPF12 PF2OOPF(PF12) + +/* 4th tier pine function keys */ +#define OOOPF1 PF2OOOPF(PF1) +#define OOOPF2 PF2OOOPF(PF2) +#define OOOPF3 PF2OOOPF(PF3) +#define OOOPF4 PF2OOOPF(PF4) +#define OOOPF5 PF2OOOPF(PF5) +#define OOOPF6 PF2OOOPF(PF6) +#define OOOPF7 PF2OOOPF(PF7) +#define OOOPF8 PF2OOOPF(PF8) +#define OOOPF9 PF2OOOPF(PF9) +#define OOOPF10 PF2OOOPF(PF10) +#define OOOPF11 PF2OOOPF(PF11) +#define OOOPF12 PF2OOOPF(PF12) + +#endif /* PICO_KEYDEFS_INCLUDED */ diff --git a/pico/line.c b/pico/line.c new file mode 100644 index 00000000..e5df3670 --- /dev/null +++ b/pico/line.c @@ -0,0 +1,778 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: line.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 + * + * ======================================================================== + * + * Program: Line management routines + */ + +/* + * The functions in this file are a general set of line management utilities. + * They are the only routines that touch the text. They also touch the buffer + * and window structures, to make sure that the necessary updating gets done. + * There are routines in this file that handle the kill buffer too. It isn't + * here for any good reason. + * + * Note that this code only updates the dot and mark values in the window list. + * Since all the code acts on the current window, the buffer that we are + * editing must be being displayed, which means that "b_nwnd" is non zero, + * which means that the dot and mark values in the buffer headers are nonsense. + */ + +#include "headers.h" + +#define NBLOCK 16 /* Line block chunk size */ +#define KBLOCK 1024 /* Kill buffer block size */ + + +/* + * Struct to manage the kill and justify buffers + */ +struct pkchunk { + short used; /* # of characters used in this buffer */ + UCS bufp[KBLOCK]; /* buffer containing text */ + struct pkchunk *next; /* pointer to next chunk */ +}; + +static struct pkbuf { + long total; /* # of UCS characters used in buffer */ + struct pkchunk *first; /* first one of these in the chain */ + struct pkchunk *last; /* last one of these in the chain */ +} *kbufp, *fbufp; + + +void insspace(int, int); +int ldelnewline(void); +void pkbufdel(struct pkbuf **); +void pkchunkdel(struct pkchunk **); +int pkbufinsert(UCS, struct pkbuf **); +long pkbufremove(int, struct pkbuf *); + + +/* + * This routine allocates a block of memory large enough to hold a LINE + * containing "used" characters. The block is always rounded up a bit. Return + * a pointer to the new block, or NULL if there isn't any memory left. Print a + * message in the message line if no space. + */ +LINE * +lalloc(int used) +{ + register LINE *lp; + register int size; + EML eml; + + if((size = (used+NBLOCK-1) & ~(NBLOCK-1)) > NLINE) + size *= 2; + + if (size == 0) /* Assume that an empty */ + size = NBLOCK; /* line is for type-in. */ + + if ((lp = (LINE *) malloc(sizeof(LINE)+(size*sizeof(CELL)))) == NULL) { + eml.s = comatose(size); + emlwrite("Cannot allocate %s bytes", &eml); + return (NULL); + } + + lp->l_size = size; + lp->l_used = used; + return (lp); +} + +/* + * Delete line "lp". Fix all of the links that might point at it (they are + * moved to offset 0 of the next line. Unlink the line from whatever buffer it + * might be in. Release the memory. The buffers are updated too; the magic + * conditions described in the above comments don't hold here. + */ +void +lfree(LINE *lp) +{ + register BUFFER *bp; + register WINDOW *wp; + + wp = wheadp; + while (wp != NULL) { + if (wp->w_linep == lp) + wp->w_linep = lp->l_fp; + + if (wp->w_dotp == lp) { + wp->w_dotp = lp->l_fp; + wp->w_doto = 0; + } + + if (wp->w_markp == lp) { + wp->w_markp = lp->l_fp; + wp->w_marko = 0; + } + + if (wp->w_imarkp == lp) { + wp->w_imarkp = lp->l_fp; + wp->w_imarko = 0; + } + + wp = wp->w_wndp; + } + + bp = bheadp; + while (bp != NULL) { + if (bp->b_nwnd == 0) { + if (bp->b_dotp == lp) { + bp->b_dotp = lp->l_fp; + bp->b_doto = 0; + } + + if (bp->b_markp == lp) { + bp->b_markp = lp->l_fp; + bp->b_marko = 0; + } + } + + bp = bp->b_bufp; + } + + lp->l_bp->l_fp = lp->l_fp; + lp->l_fp->l_bp = lp->l_bp; + free((char *) lp); +} + + +/* + * This routine gets called when a character is changed in place in the current + * buffer. It updates all of the required flags in the buffer and window + * system. The flag used is passed as an argument; if the buffer is being + * displayed in more than 1 window we change EDIT to HARD. Set MODE if the + * mode line needs to be updated (the "*" has to be set). + */ +void +lchange(int flag) +{ + register WINDOW *wp; + + if (curbp->b_nwnd != 1) /* Ensure hard. */ + flag = WFHARD; + + if ((curbp->b_flag&BFCHG) == 0) { /* First change, so */ + if(Pmaster == NULL) + flag |= WFMODE; /* update mode lines. */ + curbp->b_flag |= BFCHG; + } + + wp = wheadp; + while (wp != NULL) { + if (wp->w_bufp == curbp) + wp->w_flag |= flag; + wp = wp->w_wndp; + } +} + + +/* + * insert spaces forward into text + * default flag and numeric argument + */ +void +insspace(int f, int n) +{ + linsert(n, ' '); + backchar(f, n); +} + +/* + * Insert "n" copies of the character "c" at the current location of dot. In + * the easy case all that happens is the text is stored in the line. In the + * hard case, the line has to be reallocated. When the window list is updated, + * take special care; I screwed it up once. You always update dot in the + * current window. You update mark, and a dot in another window, if it is + * greater than the place where you did the insert. Return TRUE if all is + * well, and FALSE on errors. + */ +int +linsert(int n, UCS c) +{ + register LINE *dotp; + register int doto; + register WINDOW *wp; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + dotp = curwp->w_dotp; + doto = curwp->w_doto; + lchange(WFEDIT); + + if(!geninsert(&(curwp->w_dotp), &(curwp->w_doto), curbp->b_linep, + c, (curwp->w_markp) ? 1 : 0, n, &curbp->b_linecnt)) + return(FALSE); + + wp = wheadp; /* Update windows */ + while (wp != NULL) { + if (wp->w_linep == dotp) + wp->w_linep = wp->w_dotp; + + if (wp->w_imarkp == dotp) { /* added for internal mark */ + wp->w_imarkp = wp->w_dotp; + if (wp->w_imarko > doto) + wp->w_imarko += n; + } + + if (wp->w_markp == dotp) { + wp->w_markp = dotp; + if (wp->w_marko > doto) + wp->w_marko += n; + } + wp = wp->w_wndp; + } + + return (TRUE); +} + + +/* + * geninsert - do the actual work of inserting a character into + * the list of lines. + */ +int +geninsert(LINE **dotp, int *doto, LINE *linep, UCS c, int attb, int n, long *lines) +{ + register LINE *lp1; + register LINE *lp2; + register CELL *cp1; + register CELL *cp2; + CELL ac; + + ac.a = attb; + if (*dotp == linep) { /* At the end: special */ + if (*doto != 0) { + emlwrite("Programmer botch: geninsert", NULL); + return (FALSE); + } + + if ((lp1=lalloc(n)) == NULL) /* Allocate new line */ + return (FALSE); + + lp2 = (*dotp)->l_bp; /* Previous line */ + lp2->l_fp = lp1; /* Link in */ + lp1->l_fp = *dotp; + (*dotp)->l_bp = lp1; + lp1->l_bp = lp2; + *doto = n; + *dotp = lp1; + ac.c = (c & CELLMASK); + cp1 = &(*dotp)->l_text[0]; + while(n--) + *cp1++ = ac; + + if(lines) + (*lines)++; + + return (TRUE); + } + + if ((*dotp)->l_used+n > (*dotp)->l_size) { /* Hard: reallocate */ + if ((lp1=lalloc((*dotp)->l_used+n)) == NULL) + return (FALSE); + + cp1 = &(*dotp)->l_text[0]; + cp2 = &lp1->l_text[0]; + while (cp1 != &(*dotp)->l_text[*doto]) + *cp2++ = *cp1++; + + cp2 += n; + while (cp1 != &(*dotp)->l_text[(*dotp)->l_used]) + *cp2++ = *cp1++; + + (*dotp)->l_bp->l_fp = lp1; + lp1->l_fp = (*dotp)->l_fp; + (*dotp)->l_fp->l_bp = lp1; + lp1->l_bp = (*dotp)->l_bp; + + /* global may be keeping track of mark/imark */ + if(wheadp){ + if (wheadp->w_imarkp == *dotp) + wheadp->w_imarkp = lp1; + + if (wheadp->w_markp == *dotp) + wheadp->w_markp = lp1; + } + + free((char *) (*dotp)); + *dotp = lp1; + } else { /* Easy: in place */ + (*dotp)->l_used += n; + cp2 = &(*dotp)->l_text[(*dotp)->l_used]; + cp1 = cp2-n; + while (cp1 != &(*dotp)->l_text[*doto]) + *--cp2 = *--cp1; + } + + ac.c = (c & CELLMASK); + while(n--) /* add the chars */ + (*dotp)->l_text[(*doto)++] = ac; + + return(TRUE); +} + + +/* + * Insert a newline into the buffer at the current location of dot in the + * current window. The funny ass-backwards way it does things is not a botch; + * it just makes the last line in the file not a special case. Return TRUE if + * everything works out and FALSE on error (memory allocation failure). The + * update of dot and mark is a bit easier than in the above case, because the + * split forces more updating. + */ +int +lnewline(void) +{ + register CELL *cp1; + register CELL *cp2; + register LINE *lp1; + register LINE *lp2; + register int doto; + register WINDOW *wp; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + lchange(WFHARD); + lp1 = curwp->w_dotp; /* Get the address and */ + doto = curwp->w_doto; /* offset of "." */ + if ((lp2=lalloc(doto)) == NULL) /* New first half line */ + return (FALSE); + + cp1 = &lp1->l_text[0]; /* Shuffle text around */ + cp2 = &lp2->l_text[0]; + while (cp1 != &lp1->l_text[doto]) + *cp2++ = *cp1++; + + cp2 = &lp1->l_text[0]; + while (cp1 != &lp1->l_text[lp1->l_used]) + *cp2++ = *cp1++; + + lp1->l_used -= doto; + lp2->l_bp = lp1->l_bp; + lp1->l_bp = lp2; + lp2->l_bp->l_fp = lp2; + lp2->l_fp = lp1; + wp = wheadp; /* Windows */ + while (wp != NULL) { + if (wp->w_linep == lp1) + wp->w_linep = lp2; + + if (wp->w_dotp == lp1) { + if (wp->w_doto < doto) + wp->w_dotp = lp2; + else + wp->w_doto -= doto; + } + + if (wp->w_imarkp == lp1) { /* ADDED for internal mark */ + if (wp->w_imarko < doto) + wp->w_imarkp = lp2; + else + wp->w_imarko -= doto; + } + + if (wp->w_markp == lp1) { + if (wp->w_marko < doto) + wp->w_markp = lp2; + else + wp->w_marko -= doto; + } + wp = wp->w_wndp; + } + + /* + * Keep track of the number of lines in the buffer. + */ + ++curbp->b_linecnt; + return (TRUE); +} + + +/* + * This function deletes "n" characters, starting at dot. It understands how do deal + * with end of lines, etc. It returns TRUE if all of the characters were + * deleted, and FALSE if they were not (because dot ran into the end of the + * buffer. The "preserve" function is used to save what was deleted. + */ +int +ldelete(long n, int (*preserve)(UCS)) +{ + register CELL *cp1; + register CELL *cp2; + register LINE *dotp; + register int doto; + register int chunk; + register WINDOW *wp; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + while (n != 0) { + dotp = curwp->w_dotp; + doto = curwp->w_doto; + if (dotp == curbp->b_linep) /* Hit end of buffer. */ + return (FALSE); + chunk = dotp->l_used-doto; /* Size of chunk. */ + if (chunk > n) + chunk = n; + if (chunk == 0) { /* End of line, merge. */ + lchange(WFHARD); + if (ldelnewline() == FALSE + || (preserve ? (*preserve)('\n') == FALSE : 0)) + return (FALSE); + --n; + continue; + } + + lchange(WFEDIT); + cp1 = &dotp->l_text[doto]; /* Scrunch text. */ + cp2 = cp1 + chunk; + if (preserve) { /* Kill? */ + while (cp1 != cp2) { + if ((*preserve)(cp1->c) == FALSE) + return (FALSE); + ++cp1; + } + cp1 = &dotp->l_text[doto]; + } + + while (cp2 != &dotp->l_text[dotp->l_used]) + *cp1++ = *cp2++; + + dotp->l_used -= chunk; + wp = wheadp; /* Fix windows */ + while (wp != NULL) { + if (wp->w_dotp==dotp && wp->w_doto>=doto) { + wp->w_doto -= chunk; + if (wp->w_doto < doto) + wp->w_doto = doto; + } + + if (wp->w_markp==dotp && wp->w_marko>=doto) { + wp->w_marko -= chunk; + if (wp->w_marko < doto) + wp->w_marko = doto; + } + + if (wp->w_imarkp==dotp && wp->w_imarko>=doto) { + wp->w_imarko -= chunk; + if (wp->w_imarko < doto) + wp->w_imarko = doto; + } + + wp = wp->w_wndp; + } + n -= chunk; + } + +#ifdef _WINDOWS + if (preserve == kinsert && ksize() > 0) + mswin_killbuftoclip (kremove); +#endif + + return (TRUE); +} + +/* + * Delete a newline. Join the current line with the next line. If the next line + * is the magic header line always return TRUE; merging the last line with the + * header line can be thought of as always being a successful operation, even + * if nothing is done, and this makes the kill buffer work "right". Easy cases + * can be done by shuffling data around. Hard cases require that lines be moved + * about in memory. Return FALSE on error and TRUE if all looks ok. Called by + * "ldelete" only. + */ +int +ldelnewline(void) +{ + register CELL *cp1; + register CELL *cp2; + register LINE *lp1; + register LINE *lp2; + register LINE *lp3; + register WINDOW *wp; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + lp1 = curwp->w_dotp; + lp2 = lp1->l_fp; + if (lp2 == curbp->b_linep) { /* At the buffer end. */ + if (lp1->l_used == 0) { /* Blank line. */ + lfree(lp1); + --curbp->b_linecnt; + } + + return (TRUE); + } + + if (lp2->l_used <= lp1->l_size-lp1->l_used) { + cp1 = &lp1->l_text[lp1->l_used]; + cp2 = &lp2->l_text[0]; + while (cp2 != &lp2->l_text[lp2->l_used]) + *cp1++ = *cp2++; + + wp = wheadp; + while (wp != NULL) { + if (wp->w_linep == lp2) + wp->w_linep = lp1; + if (wp->w_dotp == lp2) { + wp->w_dotp = lp1; + wp->w_doto += lp1->l_used; + } + if (wp->w_markp == lp2) { + wp->w_markp = lp1; + wp->w_marko += lp1->l_used; + } + if (wp->w_imarkp == lp2) { + wp->w_imarkp = lp1; + wp->w_imarko += lp1->l_used; + } + wp = wp->w_wndp; + } + lp1->l_used += lp2->l_used; + lp1->l_fp = lp2->l_fp; + lp2->l_fp->l_bp = lp1; + free((char *) lp2); + --curbp->b_linecnt; + return (TRUE); + } + + if ((lp3=lalloc(lp1->l_used+lp2->l_used)) == NULL) + return (FALSE); + + cp1 = &lp1->l_text[0]; + cp2 = &lp3->l_text[0]; + while (cp1 != &lp1->l_text[lp1->l_used]) + *cp2++ = *cp1++; + + cp1 = &lp2->l_text[0]; + while (cp1 != &lp2->l_text[lp2->l_used]) + *cp2++ = *cp1++; + + lp1->l_bp->l_fp = lp3; + lp3->l_fp = lp2->l_fp; + lp2->l_fp->l_bp = lp3; + lp3->l_bp = lp1->l_bp; + wp = wheadp; + while (wp != NULL) { + if (wp->w_linep==lp1 || wp->w_linep==lp2) + wp->w_linep = lp3; + if (wp->w_dotp == lp1) + wp->w_dotp = lp3; + else if (wp->w_dotp == lp2) { + wp->w_dotp = lp3; + wp->w_doto += lp1->l_used; + } + if (wp->w_markp == lp1) + wp->w_markp = lp3; + else if (wp->w_markp == lp2) { + wp->w_markp = lp3; + wp->w_marko += lp1->l_used; + } + if (wp->w_imarkp == lp1) + wp->w_imarkp = lp3; + else if (wp->w_imarkp == lp2) { + wp->w_imarkp = lp3; + wp->w_imarko += lp1->l_used; + } + wp = wp->w_wndp; + } + + free((char *) lp1); + free((char *) lp2); + --curbp->b_linecnt; + return (TRUE); +} + + +/* + * Tell the caller if the given line is blank or not. + */ +int +lisblank(LINE *line) +{ + int n = 0; + UCS qstr[NLINE]; + + n = (glo_quote_str + && quote_match(glo_quote_str, line, qstr, NLINE)) + ? ucs4_strlen(qstr) : 0; + + for(; n < llength(line); n++) + if(!ucs4_isspace(lgetc(line, n).c)) + return(FALSE); + + return(TRUE); +} + + +/* + * Delete all of the text saved in the kill buffer. Called by commands when a + * new kill context is being created. The kill buffer array is released, just + * in case the buffer has grown to immense size. No errors. + */ +void +kdelete(void) +{ + pkbufdel(&kbufp); +} + +void +fdelete(void) +{ + pkbufdel(&fbufp); +} + +void +pkbufdel(struct pkbuf **buf) +{ + if (*buf) { + pkchunkdel(&(*buf)->first); + free((char *) *buf); + *buf = NULL; + } +} + + +void +pkchunkdel(struct pkchunk **chunk) +{ + if(chunk){ + if((*chunk)->next) + pkchunkdel(&(*chunk)->next); + + free((char *) *chunk); + *chunk = NULL; + } +} + + +/* + * Insert a character to the kill buffer, enlarging the buffer if there isn't + * any room. Always grow the buffer in chunks, on the assumption that if you + * put something in the kill buffer you are going to put more stuff there too + * later. Return TRUE if all is well, and FALSE on errors. + */ +int +kinsert(UCS c) +{ + return(pkbufinsert(c, &kbufp)); +} + +int +finsert(UCS c) +{ + return(pkbufinsert(c, &fbufp)); +} + +int +pkbufinsert(UCS c, struct pkbuf **buf) +{ + if(!*buf){ + if((*buf = (struct pkbuf *) malloc(sizeof(struct pkbuf))) != NULL) + memset(*buf, 0, sizeof(struct pkbuf)); + else + return(FALSE); + } + + if((*buf)->total % KBLOCK == 0){ + struct pkchunk *p = (*buf)->last; + if(((*buf)->last = (struct pkchunk *) malloc(sizeof(struct pkchunk))) != NULL){ + memset((*buf)->last, 0, sizeof(struct pkchunk)); + if(p) + p->next = (*buf)->last; + else + (*buf)->first = (*buf)->last; + } + else + return(FALSE); + } + + (*buf)->last->bufp[(*buf)->last->used++] = c; + (*buf)->total++; + return (TRUE); +} + + +/* + * These functions get characters from the requested buffer. If the + * character index "n" is off the end, it returns "-1". This lets the + * caller just scan along until it gets a "-1" back. + */ +long +kremove(int n) +{ + return(pkbufremove(n, kbufp)); +} + +long +fremove(int n) +{ + return(pkbufremove(n, fbufp)); +} + + +/* + * This would be type UCS except that it needs to return -1. + */ +long +pkbufremove(int n, struct pkbuf *buf) +{ + if(n >= 0 && buf && n < buf->total){ + register struct pkchunk *p = buf->first; + int block = n / KBLOCK; + + while(block--) + if(!(p = p->next)) + return(-1); + + return(p->bufp[n % KBLOCK]); + } + else + return(-1); +} + + +/* + * This function just returns the current size of the kill buffer + */ +int +ksize(void) +{ + return(kbufp ? (int) kbufp->total : 0); +} + + +static REGION last_region_added; + +void +set_last_region_added(REGION *region) +{ + if(region) + last_region_added = (*region); + else + memset(&last_region_added, 0, sizeof(last_region_added)); +} + + +REGION * +get_last_region_added(void) +{ + return(&last_region_added); +} diff --git a/pico/main.c b/pico/main.c new file mode 100644 index 00000000..ac5bc388 --- /dev/null +++ b/pico/main.c @@ -0,0 +1,871 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: main.c 1184 2008-12-16 23:52:15Z 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 + * + * ======================================================================== + */ + +/* + * Program: Main stand-alone Pine Composer routines + * + * + * WEEMACS/PICO NOTES: + * + * 08 Jan 92 - removed PINE defines to simplify compiling + * + * 08 Apr 92 - removed PINE stub calls + * + */ + +#include "headers.h" +#include "../c-client/mail.h" +#include "../c-client/rfc822.h" +#include "../pith/osdep/collate.h" +#include "../pith/charconv/filesys.h" +#include "../pith/charconv/utf8.h" +#include "../pith/conf.h" + + +/* + * Useful internal prototypes + */ +#ifdef _WINDOWS +int pico_file_drop(int, int, char *); +#endif + +/* + * this isn't defined in the library, because it's a pine global + * which we use for GetKey's timeout + */ +int timeoutset = 0; + + +int my_timer_period = (300 * 1000); + +/* + * function key mappings + */ +static UCS fkm[12][2] = { + { F1, (CTRL|'G')}, + { F2, (CTRL|'X')}, + { F3, (CTRL|'O')}, + { F4, (CTRL|'J')}, + { F5, (CTRL|'R')}, + { F6, (CTRL|'W')}, + { F7, (CTRL|'Y')}, + { F8, (CTRL|'V')}, + { F9, (CTRL|'K')}, + { F10, (CTRL|'U')}, + { F11, (CTRL|'C')}, +#ifdef SPELLER + { F12, (CTRL|'T')} +#else + { F12, (CTRL|'D')} +#endif +}; + +char *pico_args(int, char **, int *, int *, int *); +void pico_args_help(void); +void pico_vers_help(void); +void pico_display_args_err(char *, char **, int); + +/* TRANSLATORS: An error message about an unknown flag (option) + on the command line */ +char args_pico_missing_flag[] = N_("unknown flag \"%c\""); +/* TRANSLATORS: error message about command line */ +char args_pico_missing_arg[] = N_("missing or empty argument to \"%c\" flag"); +char args_pico_missing_num[] = N_("non numeric argument for \"%c\" flag"); +char args_pico_missing_color[] = N_("missing color for \"%s\" flag"); +char args_pico_missing_charset[] = N_("missing character set for \"%s\" flag"); +char args_pico_input_charset[] = N_("input character set \"%s\" is unsupported"); +char args_pico_output_charset[] = N_("output character set \"%s\" is unsupported"); + +char *args_pico_args[] = { +/* TRANSLATORS: little help printed out when incorrect arguments + are given to pico program. */ +N_("Possible Starting Arguments for Pico editor:"), +"", +N_("\tArgument\t\tMeaning"), +N_("\t -e \t\tComplete - allow file name completion"), +N_("\t -k \t\tCut - let ^K cut from cursor position to end of line"), +N_("\t -a \t\tShowDot - show dot files in file browser"), +N_("\t -j \t\tGoto - allow 'Goto' command in file browser"), +N_("\t -g \t\tShow - show cursor in file browser"), +N_("\t -m \t\tMouse - turn on mouse support"), +N_("\t -x \t\tNoKeyhelp - suppress keyhelp"), +N_("\t -p \t\tPreserveStartStop - preserve \"start\"(^Q) and \"stop\"(^S) characters"), +N_("\t -q \t\tTermdefWins - termcap or terminfo takes precedence over defaults"), +N_("\t -Q \tSet quote string (eg. \"> \") esp. for composing email"), +N_("\t -d \t\tRebind - let delete key delete current character"), +N_("\t -f \t\tKeys - force use of function keys"), +N_("\t -b \t\tReplace - allow search and replace"), +N_("\t -h \t\tHelp - give this list of options"), +N_("\t -r[#cols] \tFill - set fill column to #cols columns, default=72"), +N_("\t -n[#s] \tMail - notify about new mail every #s seconds, default=180"), +N_("\t -s \tSpeller - specify alternative speller"), +N_("\t -t \t\tShutdown - enable special shutdown mode"), +N_("\t -o \tOperation - specify the operating directory"), +N_("\t -z \t\tSuspend - allow use of ^Z suspension"), +N_("\t -w \t\tNoWrap - turn off word wrap"), +N_("\t -W \tSet word separators other than whitespace"), +#ifndef _WINDOWS +N_("\t -dcs \tdefault uses LANG or LC_CTYPE from environment"), +N_("\t -kcs \tdefaults to display_character_set"), +N_("\t -syscs\t\tuse system-supplied translation routines"), +#endif /* ! _WINDOWS */ +#ifdef _WINDOWS +N_("\t -cnf color \tforeground color"), +N_("\t -cnb color \tbackground color"), +N_("\t -crf color \treverse foreground color"), +N_("\t -crb color \treverse background color"), +#endif /* _WINDOWS */ +N_("\t +[line#] \tLine - start on line# line, default=1"), +N_("\t -v \t\tView - view file"), +N_("\t -no_setlocale_collate\tdo not do setlocale(LC_COLLATE)"), +N_("\t -version\tPico version number"), +"", +N_("\t All arguments may be followed by a file name to display."), +"", +NULL +}; + + +/* + * main standalone pico routine + */ +#ifdef _WINDOWS +int +app_main (int argc, char *argv[]) +#else +int +main(int argc, char *argv[]) +#endif +{ + UCS c; + register int f; + register int n; + register BUFFER *bp; + int viewflag = FALSE; /* are we starting in view mode?*/ + int starton = 0; /* where's dot to begin with? */ + int setlocale_collate = 1; + char bname[NBUFN]; /* buffer name of file to read */ + char *file_to_edit = NULL; + char *display_charmap = NULL, *dc; + char *keyboard_charmap = NULL; + int use_system = 0; + char *err = NULL; + + set_input_timeout(600); + Pmaster = NULL; /* turn OFF composer functionality */ + km_popped = 0; + + /* + * Read command line flags before initializing, otherwise, we never + * know to init for f_keys... + */ + file_to_edit = pico_args(argc, argv, &starton, &viewflag, &setlocale_collate); + + set_collation(setlocale_collate, 1); + +#define cpstr(s) strcpy((char *)fs_get(1+strlen(s)), s) + +#ifdef _WINDOWS + init_utf8_display(1, NULL); +#else /* UNIX */ + + + if(display_character_set) + display_charmap = cpstr(display_character_set); +#if HAVE_LANGINFO_H && defined(CODESET) + else if((dc = nl_langinfo_codeset_wrapper()) != NULL) + display_charmap = cpstr(dc); +#endif + + if(!display_charmap) + display_charmap = cpstr("US-ASCII"); + + if(keyboard_character_set) + keyboard_charmap = cpstr(keyboard_character_set); + else + keyboard_charmap = cpstr(display_charmap); + + + if(use_system_translation){ +#if PREREQ_FOR_SYS_TRANSLATION + use_system++; + /* This modifies its arguments */ + if(setup_for_input_output(use_system, &display_charmap, &keyboard_charmap, + &input_cs, &err) == -1){ + fprintf(stderr, "%s\n", err ? err : "trouble with character set"); + exit(1); + } + else if(err){ + fprintf(stderr, "%s\n", err); + fs_give((void **) &err); + } +#endif + } + + if(!use_system){ + if(setup_for_input_output(use_system, &display_charmap, &keyboard_charmap, + &input_cs, &err) == -1){ + fprintf(stderr, "%s\n", err ? err : "trouble with character set"); + exit(1); + } + else if(err){ + fprintf(stderr, "%s\n", err); + fs_give((void **) &err); + } + } + + if(keyboard_charmap){ + set_locale_charmap(keyboard_charmap); + free((void *) keyboard_charmap); + } + + if(display_charmap) + free((void *) display_charmap); + +#endif /* UNIX */ + + /* + * There are a couple arguments that we need to be sure + * are converted for internal use. + */ + if(alt_speller) + alt_speller = cpstr(fname_to_utf8(alt_speller)); + + if(opertree && opertree[0]){ + strncpy(opertree, fname_to_utf8(opertree), sizeof(opertree)); + opertree[sizeof(opertree)-1] = '\0'; + } + + if(glo_quote_str_orig) + glo_quote_str = utf8_to_ucs4_cpystr(fname_to_utf8(glo_quote_str_orig)); + + if(glo_wordseps_orig) + glo_wordseps = utf8_to_ucs4_cpystr(fname_to_utf8(glo_wordseps_orig)); + + if(file_to_edit) + file_to_edit = cpstr(fname_to_utf8(file_to_edit)); + +#undef cpstr + +#if defined(DOS) || defined(OS2) + if(file_to_edit){ /* strip quotes? */ + int l; + + if(strchr("'\"", file_to_edit[0]) + && (l = strlen(file_to_edit)) > 1 + && file_to_edit[l-1] == file_to_edit[0]){ + file_to_edit[l-1] = '\0'; /* blat trailing quote */ + file_to_edit++; /* advance past leading quote */ + } + } +#endif + + if(!vtinit()) /* Displays. */ + exit(1); + + strncpy(bname, "main", sizeof(bname)); /* default buffer name */ + bname[sizeof(bname)-1] = '\0'; + edinit(bname); /* Buffers, windows. */ + + update(); /* let the user know we are here */ + +#ifdef _WINDOWS + mswin_setwindow(NULL, NULL, NULL, NULL, NULL, NULL); + mswin_showwindow(); + mswin_showcaret(1); /* turn on for main window */ + mswin_allowpaste(MSWIN_PASTE_FULL); + mswin_setclosetext("Use the ^X command to exit Pico."); + mswin_setscrollcallback (pico_scroll_callback); +#endif + +#if defined(USE_TERMCAP) || defined(USE_TERMINFO) || defined(VMS) + if(kbesc == NULL){ /* will arrow keys work ? */ + (*term.t_putchar)('\007'); + emlwrite("Warning: keypad keys may be non-functional", NULL); + } +#endif /* USE_TERMCAP/USE_TERMINFO/VMS */ + + if(file_to_edit){ /* Any file to edit? */ + + makename(bname, file_to_edit); /* set up a buffer for this file */ + + bp = curbp; /* read in first file */ + makename(bname, file_to_edit); + strncpy(bp->b_bname, bname, sizeof(bp->b_bname)); + bp->b_bname[sizeof(bp->b_bname)-1] = '\0'; + + if(strlen(file_to_edit) >= NFILEN){ + char buf[128]; + + snprintf(buf, sizeof(buf), "Filename \"%.10s...\" too long", file_to_edit); + emlwrite(buf, NULL); + file_to_edit = NULL; + } + else{ + strncpy(bp->b_fname, file_to_edit, sizeof(bp->b_fname)); + bp->b_fname[sizeof(bp->b_fname)-1] = '\0'; + if (((gmode&MDTREE) && !in_oper_tree(file_to_edit)) || + readin(file_to_edit, (viewflag==FALSE), TRUE) == ABORT) { + if ((gmode&MDTREE) && !in_oper_tree(file_to_edit)){ + EML eml; + eml.s = opertree; + emlwrite(_("Can't read file from outside of %s"), &eml); + } + + file_to_edit = NULL; + } + } + + if(!file_to_edit){ + strncpy(bp->b_bname, "main", sizeof(bp->b_bname)); + bp->b_bname[sizeof(bp->b_bname)-1] = '\0'; + strncpy(bp->b_fname, "", sizeof(bp->b_fname)); + bp->b_fname[sizeof(bp->b_fname)-1] = '\0'; + } + + bp->b_dotp = bp->b_linep; + bp->b_doto = 0; + + if (viewflag) /* set the view mode */ + bp->b_mode |= MDVIEW; + } + + /* setup to process commands */ + lastflag = 0; /* Fake last flags. */ + curbp->b_mode |= gmode; /* and set default modes*/ + + curwp->w_flag |= WFMODE; /* and force an update */ + + if(timeoutset){ + EML eml; + + eml.s = comatose(get_input_timeout()); + emlwrite(_("Checking for new mail every %s seconds"), &eml); + } + + + forwline(0, starton - 1); /* move dot to specified line */ + + while(1){ + + if(km_popped){ + km_popped--; + if(km_popped == 0) /* cause bottom three lines to be repainted */ + curwp->w_flag |= WFHARD; + } + + if(km_popped){ /* temporarily change to cause menu to be painted */ + term.t_mrow = 2; + curwp->w_ntrows -= 2; + curwp->w_flag |= WFMODE; + movecursor(term.t_nrow-2, 0); /* clear status line, too */ + peeol(); + } + + update(); /* Fix up the screen */ + if(km_popped){ + term.t_mrow = 0; + curwp->w_ntrows += 2; + } + +#ifdef MOUSE +#ifdef EX_MOUSE + /* New mouse function for real mouse text seletion. */ + register_mfunc(mouse_in_pico, 2, 0, term.t_nrow - (term.t_mrow + 1), + term.t_ncol); +#else + mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); + register_mfunc(mouse_in_content, 2, 0, term.t_nrow - (term.t_mrow + 1), + term.t_ncol); +#endif +#endif +#ifdef _WINDOWS + mswin_setdndcallback (pico_file_drop); + mswin_mousetrackcallback(pico_cursor); +#endif + c = GetKey(); +#ifdef MOUSE +#ifdef EX_MOUSE + clear_mfunc(mouse_in_pico); +#else + clear_mfunc(mouse_in_content); +#endif +#endif +#ifdef _WINDOWS + mswin_cleardndcallback (); + mswin_mousetrackcallback(NULL); +#endif + + if(timeoutset && (c == NODATA || time_to_check())){ + if(pico_new_mail()) + emlwrite(_("You may possibly have new mail."), NULL); + } + + if(km_popped) + switch(c){ + case NODATA: + case (CTRL|'L'): + km_popped++; + break; + + default: + /* clear bottom three lines */ + mlerase(); + break; + } + + if(c == NODATA) + continue; + + if(mpresf){ /* erase message line? */ + if(mpresf++ > MESSDELAY) + mlerase(); + } + + f = FALSE; + n = 1; + +#ifdef MOUSE + clear_mfunc(mouse_in_content); +#endif + /* Do it. */ + execute(normalize_cmd(c, fkm, 1), f, n); + } +} + + +/* + * Parse the command line args. + * + * Args ac + * av + * starton -- place to return starton value + * viewflag -- place to return viewflag value + * + * Result: command arguments parsed + * possible printing of help for command line + * various global flags set + * returns the name of any file to open, else NULL + */ +char * +pico_args(int ac, char **av, int *starton, int *viewflag, int *setlocale_collate) +{ + int c, usage = 0; + char *str; + char tmp_1k_buf[1000]; /* tmp buf to contain err msgs */ + +Loop: + /* while more arguments with leading - or + */ + while(--ac > 0 && (**++av == '-' || **av == '+')){ + if(**av == '+'){ + if(*++*av) + str = *av; + else if(--ac) + str = *++av; + else{ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_missing_arg), '+'); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + goto Loop; + } + + if(!isdigit((unsigned char)str[0])){ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_missing_num), '+'); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + + if(starton) + *starton = atoi(str); + + goto Loop; + } + + /* while more chars in this argument */ + else while(*++*av){ + + if(strcmp(*av, "version") == 0){ + pico_vers_help(); + } + else if(strcmp(*av, "no_setlocale_collate") == 0){ + *setlocale_collate = 0; + goto Loop; + } +#ifndef _WINDOWS + else if(strcmp(*av, "syscs") == 0){ + use_system_translation = !use_system_translation; + goto Loop; + } +#endif /* ! _WINDOWS */ +#ifdef _WINDOWS + else if(strcmp(*av, "cnf") == 0 + || strcmp(*av, "cnb") == 0 + || strcmp(*av, "crf") == 0 + || strcmp(*av, "crb") == 0){ + + char *cmd = *av; /* save it to use below */ + + if(--ac){ + str = *++av; + if(cmd[1] == 'n'){ + if(cmd[2] == 'f') + pico_nfcolor(str); + else if(cmd[2] == 'b') + pico_nbcolor(str); + } + else if(cmd[1] == 'r'){ + if(cmd[2] == 'f') + pico_rfcolor(str); + else if(cmd[2] == 'b') + pico_rbcolor(str); + } + } + else{ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_missing_color), cmd); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + + goto Loop; + } +#endif /* _WINDOWS */ +#ifndef _WINDOWS + else if(strcmp(*av, "dcs") == 0 || strcmp(*av, "kcs") == 0){ + char *cmd = *av; + + if(--ac){ + if(strcmp(*av, "dcs") == 0){ + display_character_set = *++av; + if(!output_charset_is_supported(display_character_set)){ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_output_charset), display_character_set); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + } + else{ + keyboard_character_set = *++av; + if(!input_charset_is_supported(keyboard_character_set)){ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_input_charset), keyboard_character_set); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + } + } + else{ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_missing_charset), cmd); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + + goto Loop; + } +#endif /* ! _WINDOWS */ + + /* + * Single char options. + */ + switch(c = **av){ + /* + * These don't take arguments. + */ + case 'a': + gmode ^= MDDOTSOK; /* show dot files */ + break; + case 'b': +#ifdef notdef + /* + * Make MDREPLACE always on instead + * Don't leave this to allow turning off of MDREPLACE because the + * polarity of -b will have changed. Don't think anybody wants to + * turn it off. + */ + gmode ^= MDREPLACE; /* -b for replace string in where is command */ +#endif + break; + case 'd': /* -d for rebind delete key */ + bindtokey(0x7f, forwdel); + break; + case 'e': /* file name completion */ + gmode ^= MDCMPLT; + break; + case 'f': /* -f for function key use */ + gmode ^= MDFKEY; + break; + case 'g': /* show-cursor in file browser */ + gmode ^= MDSHOCUR; + break; + case 'h': + usage++; + break; + case 'j': /* allow "Goto" in file browser */ + gmode ^= MDGOTO; + break; + case 'k': /* kill from dot */ + gmode ^= MDDTKILL; + break; + case 'm': /* turn on mouse support */ + gmode ^= MDMOUSE; + break; + case 'p': + preserve_start_stop = 1; + break; + case 'q': /* -q for termcap takes precedence */ + gmode ^= MDTCAPWINS; + break; + case 't': /* special shutdown mode */ + gmode ^= MDTOOL; + rebindfunc(wquit, quickexit); + break; + case 'v': /* -v for View File */ + case 'V': + *viewflag = !*viewflag; + break; /* break back to inner-while */ + case 'w': /* -w turn off word wrap */ + gmode ^= MDWRAP; + break; + case 'x': /* suppress keyhelp */ + sup_keyhelp = !sup_keyhelp; + break; + case 'z': /* -z to suspend */ + gmode ^= MDSSPD; + break; + + /* + * These do take arguments. + */ + case 'r': /* set fill column */ + case 'n': /* -n for new mail notification */ + case 's' : /* speller */ + case 'o' : /* operating tree */ + case 'Q' : /* Quote string */ + case 'W' : /* Word separators */ + if(*++*av) + str = *av; + else if(--ac) + str = *++av; + else{ + if(c == 'r') + str= "72"; + else if(c == 'n') + str = "180"; + else{ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_missing_arg), c); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + goto Loop; + } + } + + switch(c){ + case 's': + alt_speller = str; + break; + case 'o': + strncpy(opertree, str, NLINE); + gmode ^= MDTREE; + break; + case 'Q': + glo_quote_str_orig = str; + break; + case 'W': + glo_wordseps_orig = str; + break; + + /* numeric args */ + case 'r': + case 'n': + if(!isdigit((unsigned char)str[0])){ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_missing_num), c); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + + if(c == 'r'){ + if((userfillcol = atoi(str)) < 1) + userfillcol = 72; + } + else{ + timeoutset = 1; + set_input_timeout(180); + if(set_input_timeout(atoi(str)) < 30) + set_input_timeout(180); + } + + break; + } + + goto Loop; + + default: /* huh? */ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pico_missing_flag), c); + pico_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + break; + } + } + } + + if(usage) + pico_args_help(); + + /* return the first filename for editing */ + if(ac > 0) + return(*av); + else + return(NULL); +} + + +#ifdef _WINDOWS +/* + * + */ +int +pico_file_drop(int x, int y, char *filename) +{ + /* + * if current buffer is unchanged + * *or* "new buffer" and no current text + */ + if(((curwp->w_bufp->b_flag & BFCHG) == 0) + || (curwp->w_bufp->b_fname[0] == '\0' + && curwp->w_bufp->b_linep == lforw(curwp->w_bufp->b_linep) + && curwp->w_doto == 0)){ + register BUFFER *bp = curwp->w_bufp; + char bname[NBUFN]; + + makename(bname, filename); + strncpy(bp->b_bname, bname, sizeof(bp->b_bname)); + bp->b_bname[sizeof(bp->b_bname)-1] = '\0'; + strncpy(bp->b_fname, filename, sizeof(bp->b_fname)); + bp->b_fname[sizeof(bp->b_fname)-1] = '\0'; + bp->b_flag &= ~BFCHG; /* turn off change bit */ + if (readin(filename, 1, 1) == ABORT) { + strncpy(bp->b_bname, "", sizeof(bp->b_bname)); + bp->b_bname[sizeof(bp->b_bname)-1] = '\0'; + strncpy(bp->b_fname, "", sizeof(bp->b_fname)); + bp->b_fname[sizeof(bp->b_fname)-1] = '\0'; + } + + bp->b_dotp = bp->b_linep; + bp->b_doto = 0; + } + else{ + EML eml; + + ifile(filename); + curwp->w_flag |= WFHARD; + update(); + eml.s = filename; + emlwrite("Inserted dropped file \"%s\"", &eml); + } + + curwp->w_flag |= WFHARD; + update(); /* restore cursor */ + return(1); +} +#endif + + +/*---------------------------------------------------------------------- + print a few lines of help for command line arguments + + Args: none + + Result: prints help messages + ----------------------------------------------------------------------*/ +void +pico_args_help(void) +{ + char **a; + char *pp[2]; + + pp[1] = NULL; + + /** print out possible starting arguments... **/ + + for(a=args_pico_args; a && *a; a++){ + pp[0] = _(*a); + pico_display_args_err(NULL, pp, 0); + } + + exit(1); +} + + +void +pico_vers_help(void) +{ + char v0[100]; + char *v[2]; + + snprintf(v0, sizeof(v0), "Pico %.50s", version); + v[0] = v0; + v[1] = NULL; + + pico_display_args_err(NULL, v, 0); + exit(1); +} + + +/*---------------------------------------------------------------------- + write argument error to the display... + + Args: none + + Result: prints help messages + ----------------------------------------------------------------------*/ +void +pico_display_args_err(char *s, char **a, int err) +{ + char errstr[256], *errp; + FILE *fp = err ? stderr : stdout; +#ifdef _WINDOWS + char tmp_20k_buf[SIZEOF_20KBUF]; +#endif + + + if(err && s) + snprintf(errp = errstr, sizeof(errstr), _("Argument Error: %.200s"), s); + else + errp = s; + +#ifdef _WINDOWS + if(errp) + mswin_messagebox(errp, err); + + if(a && *a){ + strncpy(tmp_20k_buf, *a++, SIZEOF_20KBUF); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + while(a && *a){ + strncat(tmp_20k_buf, "\n", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + strncat(tmp_20k_buf, *a++, SIZEOF_20KBUF-strlen(tmp_20k_buf)-1); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + } + + mswin_messagebox(tmp_20k_buf, err); + } +#else + if(errp) + fprintf(fp, "%s\n", errp); + + while(a && *a) + fprintf(fp, "%s\n", *a++); +#endif +} diff --git a/pico/makefile.wnt b/pico/makefile.wnt new file mode 100644 index 00000000..bed9d814 --- /dev/null +++ b/pico/makefile.wnt @@ -0,0 +1,71 @@ +# $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 libpico.lib and pico.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 = + +LIBS= oldnames.lib kernel32.lib advapi32.lib ws2_32.lib user32.lib gdi32.lib shell32.lib comdlg32.lib \ + libpico.lib osdep/libpicoosd.lib osdep/mswin.res \ + ../c-client/utf8.obj ../pith/osdep/libpithosd.lib ../pith/charconv/libpithcc.lib +LIBER=lib +LIBARGS=/nologo /verbose + +HFILES= ../include/system.h ../include/general.h \ + ebind.h edef.h efunc.h estruct.h headers.h keydefs.h mode.h pico.h + +OFILES= attach.obj basic.obj bind.obj browse.obj buffer.obj composer.obj \ + display.obj file.obj fileio.obj line.obj pico.obj random.obj \ + region.obj search.obj utf8stub.obj window.obj word.obj + +all: blddate.exe pico.exe + +.c.obj: + $(CC) -c $(CFLAGS) "$(MAKEDIR)"\$*.c + +$(OFILES): $(HFILES) + +libpico.lib: $(OFILES) + $(RM) libpico.lib || rem + $(LIBER) /out:libpico.lib $(OFILES) + +blddate.exe: blddate.c + $(CC) $(CFLAGS) blddate.c + +pico.exe: main.obj libpico.lib $(OFILES) mswinver.obj osdep/libpicoosd.lib ../pith/osdep/libpithosd.lib ../c-client/utf8.obj ../pith/charconv/libpithcc.lib osdep/mswin.res + blddate > bdate.c + $(CC) /c $(CFLAGS) bdate.c + link /subsystem:windows /entry:wWinMainCRTStartup /out:pico.exe $(LFLAGS) bdate.obj mswinver.obj main.obj $(LIBS) + +clean: + $(RM) *.lib + $(RM) *.obj + $(RM) *.exe diff --git a/pico/mode.h b/pico/mode.h new file mode 100644 index 00000000..f09494e0 --- /dev/null +++ b/pico/mode.h @@ -0,0 +1,50 @@ +/* + * $Id: mode.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 MODE_H +#define MODE_H + + +/* + * definitions for various PICO modes + */ +#define MDWRAP 0x00000001 /* word wrap */ +#define MDSPELL 0x00000002 /* spell error parcing */ +#define MDEXACT 0x00000004 /* Exact matching for searches */ +#define MDVIEW 0x00000008 /* read-only buffer */ +#define MDFKEY 0x00000010 /* function key mode */ +#define MDSCUR 0x00000020 /* secure (for demo) mode */ +#define MDSSPD 0x00000040 /* suspendable mode */ +#define MDADVN 0x00000080 /* Pico's advanced mode */ +#define MDTOOL 0x00000100 /* "tool" mode (quick exit) */ +#define MDBRONLY 0x00000200 /* indicates standalone browser */ +#define MDCURDIR 0x00000400 /* use current dir for lookups */ +#define MDALTNOW 0x00000800 /* enter alt ed sans hesitation */ +#define MDSPWN 0x00001000 /* spawn subshell for suspend */ +#define MDCMPLT 0x00002000 /* enable file name completion */ +#define MDDTKILL 0x00004000 /* kill from dot to eol */ +#define MDSHOCUR 0x00008000 /* cursor follows hilite in browser */ +#define MDHBTIGN 0x00010000 /* ignore chars with hi bit set */ +#define MDDOTSOK 0x00020000 /* browser displays dot files */ +#define MDEXTFB 0x00040000 /* stand alone File browser */ +#define MDTREE 0x00080000 /* confine to a subtree */ +#define MDMOUSE 0x00100000 /* allow mouse (part. in xterm) */ +#define MDONECOL 0x00200000 /* single column browser */ +#define MDHDRONLY 0x00400000 /* header editing exclusively */ +#define MDGOTO 0x00800000 /* support "Goto" in file browser */ +#define MDREPLACE 0x01000000 /* allow "Replace" in "Where is"*/ +#define MDTCAPWINS 0x02000000 /* Termcap overrides defaults */ + +#endif /* MODE_H */ diff --git a/pico/msmem.c b/pico/msmem.c new file mode 100644 index 00000000..4565e5dc --- /dev/null +++ b/pico/msmem.c @@ -0,0 +1,1301 @@ +#ifndef MSC_MALLOC + +#define WIN31 +#define STRICT + +#include +#include +#include + +#include "mswin.h" + + + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Memory allocation routines. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* + * The plan is to allocate small blocks in the local heap and + * large blocks in the global heap. The intention is to keep + * the number of global allocations to a minimum. + * + * The boundry between small memory and large memory is + * controld by the constant SMALL_MEM_BOUNDRY. Blocks smaller + * than SMALL_MEM_BOUNDRY go into the local heap. This should + * be set large enough to capture the majority of small memory + * blocks in the local heap. But if it is too large, the local + * heap will fill up and we will end up allocating a lot of small + * blocks in the global heap. + * + * Unfortunatly, pine seems to need a large stack. With the + * stack, some global data, and the heap all cramed in to a 64K + * segment we end up with a heap that is smaller than ideal. + * This could be improved by reducing the size of the stack, or + * by moving the heap to a seperate memory block. I did a little + * work on moving the heap, but was not successful. My attepts + * can be seen in the function MemATest(). + * + * Currently (7/8/94) I've set the stack to 32K (in pine/makefile.win), + * the heap to 12K, and the small memory boundry to 32 bytes. + * Statistics on pine memory allocation suggest that it would be better + * to have a 25K local heap and a 95 byte small memory boundry. + * + * Statistics on memory use can be gathered by logging memory debugging + * to a file, then running the following awk script: + * + + # mem.awk + # + # Looks through log and find blocks that were allocated but not + # freed uses block id numbers, rather than addresses, since this is + # more accurate (and since this is what block ids exist!) + # + # awk may run out of memory if the logfile gets too big. If this + # happens, then another strategy will be needed... + # + + BEGIN { + FS = "[ ()]+"; + + b[0] = 16; + b[1] = 32; + b[2] = 64; + b[3] = 96; + b[4] = 128; + b[5] = 256; + b[6] = 512; + b[7] = 1024; + b[8] = 2048; + b[9] = 4096; + b[10] = 9600; + b[11] = 20000; + b[12] = 40000; + b[13] = 80000; + b[14] = 200000000; + b[15] = 0; + bcount = 15; + for (i = 0; i < bcount; ++i) + c[i] = 0; + + maxmem = 0; + maxsmallmem = 0; + + allocs = 0; + frees = 0; + globalallocs = 0; + pallocs = 0; + pfrees = 0; + } + + { + #print "one", $1, "two", $2, "three", $3, "four ", $4, "five ", $5; + if( $1 == "MemAlloc:" ) { + m[$5] = $0; + + ++allocs; + if ($9 == 1) ++globalallocs; + + for (i = 0; i < bcount; ++i) { + if (b[i] > $7) { + ++c[i]; + break; + } + } + } + else if( $1 == "MemFree:" ) { + delete m[$5]; + ++frees; + } + if( $1 == "PageAlloc:" ) { + p[$5] = $0; + + ++pallocs; + } + else if( $1 == "PageFree:" ) { + delete p[$5]; + ++pfrees; + } + else if ($1 == "Memory") { + if ($6 > maxmem) maxmem = $6; + } + else if ($1 == "Small") { + if ($7 > maxsmallmem) maxsmallmem = $7; + } + } + + + END { + for( i in m ) { + print m[i] + } + for (i in p) { + print p[i] + } + + cumulative = 0; + for (i = 0; i < bcount; ++i) { + cumulative += c[i]; + printf "%9d : %5d (%5d)\n", b[i], c[i], cumulative; + } + + print; + print "Max memory use: ", maxmem; + print "Max small memory use: ", maxsmallmem; + print; + print "Local allocations ", allocs - globalallocs; + print "Global allocations ", globalallocs; + print "Total allocations ", allocs; + print "Total memory frees ", frees; + print "Blocks not freed ", allocs - frees; + print; + print "Page allocations ", pallocs; + print "Page frees ", pfrees; + print "Pages not freed ", pallocs - pfrees; + } + + * + * Each memory block is assigned a unique id. This is only used + * to match allocations with frees in the debug log. + */ + + + +/* + * SEGHEAP selectes between two different implementations of the memory + * management functions. Defined and it uses a sub allocation scheme. + * Undefined and it comples a direct allocation scheme. + * + * The sub allocation scheme is greatly prefered because it greatly reduces + * the number of global memory allocations. + */ +#define SEGHEAP /* Use the sub allocation scheme. */ + + + + +#define MEM_DEBUG /* Compile in memory debugging code.*/ +#define MEM_DEBUG_LEVEL 8 /* Pine debug level at which memory + * debug messages will be generated.*/ +#ifdef MEM_DEBUG +static int MemDebugLevel = 0; /* Doing debugging. */ +static FILE *MemDebugFile = NULL; /* File to write to. */ +#endif + + + +#ifdef DEBUG +#define LOCAL +#else +#define LOCAL static +#endif + + +#define GET_SEGMENT(x) (0xffff & ((DWORD)(x) >> 16)) +#define GET_OFFSET(x) (0xffff & (DWORD)(x)) + + +#undef malloc +#undef realloc +#undef free + + +void MemATest (void); + + + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Standard memory allcation functions. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + + +void * +malloc (size_t size) +{ + return (MemAlloc (size)); +} + + +void __far * +_fmalloc (size_t size) +{ + return (MemAlloc (size)); +} + + +void __far * +realloc (void *memblock, size_t size) +{ + return (MemRealloc (memblock, size)); +} + + +void __far * +_frealloc (void *memblock, size_t size) +{ + return (MemRealloc (memblock, size)); +} + + +void +free (void *memblock) +{ + MemFree (memblock); +} + +void +_ffree (void *memblock) +{ + MemFree (memblock); +} + + +/* + * Turn on memory debugging and specify file to write to. + */ +void +MemDebug (int debug, FILE *debugFile) +{ +#ifdef MEM_DEBUG + if (debugFile == NULL) { + MemDebugLevel = 0; + MemDebugFile = NULL; + } + else { + MemDebugLevel = debug; + MemDebugFile = debugFile; + fprintf (MemDebugFile, "Memory Debuging set on\n"); + } +#endif /* MEM_DEBUG */ +} + + + + + + + +#ifdef SEGHEAP + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * SEGHEAP Memory allocation routines. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* + * This implementation allocates memory in pages then sub alloates from the + * pages. This greatly reduces the number of global memory blocks allocated. + * This code originally written by Stephen Chung and posted to a Usenet news + * group. I've modified them for use in Pine. The author says: + * + * + * Copyright (C) Stephen Chung, 1991-1992. All rights reserved. + * + * Afterwords + * ---------- + * + * Theoretically, you are required to obtain special approval from me (because + * I copyrighted these routines) if you want to use them in your programs. + * However, I usually don't really care if you are not using these routines in + * a commercial, shareware etc. product. + * + * Any questions and/or bug fixes, please send email to: + * + * Stephen Chung stephenc@cunixf.cc.columbia.edu + * + * If it bounces, then try schung@cogsci.Berkeley.EDU + * + * Have fun! + * + */ + + +/* + * The folloing control debugging code for testing out of memory conditions. + * there are to test code. + * + * MEM_ALLOC_LIMT + * Setting this to anything other than zero will limit memory allocation + * to (rougly) that many bytes. + * + * MEM_FAIL_SOON + * Compiles in a function which will cause the memory allocation to fail + * soon after that function is called. this can be used to test + * mem alloc failurs in specific code segments. + */ +#define MEM_ALLOC_LIMIT 0 +#define MEM_FAIL_SOON 0 + + +#if MEM_FAIL_SOON +static long MemFailSoonLimit = 0; +#endif + + + +#define MAGIC 0x42022667 +#define MAGIC2 0x56743296 + +typedef struct MemoryStruct { + long int magic; + void far *page; + WORD id; + MemSize size; + BOOL allocated; + struct MemoryStruct far *next, far *prev; + long int magic2; +} MEMHEADER; + +typedef struct PageHeaderStruct { + long int magic; + HANDLE handle; + WORD id; + MemSize size; + MemSize used; + MemSize overhead; + MEMHEADER far *data, far *empty; + struct PageHeaderStruct far *next, far *prev; + long int magic2; +} MEMPAGEHEADER; + +typedef struct { + MEMPAGEHEADER far *pages; + int nr_pages; +} MAINMEMHEADER; + +#define PAGESIZE (6 * 1024) +#define USEABLESIZE (PAGESIZE - sizeof(MEMPAGEHEADER) - sizeof(MEMHEADER)) + + +LOCAL MAINMEMHEADER MemHeader = { NULL, 0 }; +LOCAL WORD MemID = 0; /* Keep track of ID. */ +LOCAL WORD PageID = 0; +LOCAL unsigned long MemInUse = 0; /* Total bytes in use. */ +LOCAL unsigned long MemInUseMax = 0; /* max in use at one time. */ +LOCAL unsigned long PageMemInUse = 0; +LOCAL unsigned long PageMemInUseMax = 0; + + + +static MEMPAGEHEADER far * +AddPage(MemSize n) +{ + void far *cp; + MEMHEADER far *mp; + MEMPAGEHEADER far *p; + HANDLE handle = NULL; + + +#if MEM_ALLOC_LIMIT + if (n + PageMemInUse > MEM_ALLOC_LIMIT) { + MessageBox (NULL, "PageAlloc: Above preset limit, allocation fails", + "Out Of Memory", MB_ICONSTOP | MB_OK); + return (NULL); + } +#endif + + handle = GlobalAlloc(GHND, n); + if (handle == NULL) { +#ifdef MEM_DEBUG + if (MemDebugLevel >= 1) + fprintf (MemDebugFile, "***\n*** Out of memory: allocating %d bytes\n***\n", n); +#endif + return (NULL); + } + + if (MemHeader.pages == NULL || MemHeader.nr_pages <= 0) { + p = MemHeader.pages = (MEMPAGEHEADER far *) GlobalLock(handle); + p->prev = NULL; + } else { + for (p = MemHeader.pages; p->next != NULL; p = p->next); + p->next = (MEMPAGEHEADER far *) GlobalLock(handle); + p->next->prev = p; + p = p->next; + } + + p->magic = MAGIC; + p->handle = handle; + p->next = NULL; + p->id = PageID++; + p->size = n; + p->used = 0; + p->overhead = sizeof(MEMPAGEHEADER) + sizeof(MEMHEADER); + p->magic2 = MAGIC2; + + cp = ((char far *) p) + sizeof(MEMPAGEHEADER); + mp = (MEMHEADER far *) cp; + + p->data = p->empty = mp; + + mp->magic = 0L; + mp->magic2 = 0L; + mp->allocated = FALSE; + mp->page = p; + mp->size = p->size - p->overhead; + mp->next = mp->prev = NULL; + + MemHeader.nr_pages++; + + +#ifdef MEM_DEBUG + if (MemDebugLevel >= MEM_DEBUG_LEVEL) { + fprintf (MemDebugFile, "PageAlloc: addr(%lx) id(%u) size(%ld) global(%d)\n", + p, p->id, (long)n, 1); + fflush (MemDebugFile); + } +#endif /* MEM_DEBUG */ + + PageMemInUse += n; + if (PageMemInUse > PageMemInUseMax) + PageMemInUseMax = PageMemInUse; + + + return (p); +} + + + +static void +DeletePage (MEMPAGEHEADER far *p) +{ +#ifdef MEM_DEBUG + /* Deubgging info... */ + if (MemDebugLevel >= 4) { + if (PageMemInUse == PageMemInUseMax) + fprintf (MemDebugFile, "Page usage is up to %lu\n", PageMemInUseMax); + } + if (MemDebugLevel >= MEM_DEBUG_LEVEL) { + fprintf (MemDebugFile, "PageFree: addr(%lx) id(%u)\n", + p, p->id); + fflush (MemDebugFile); + } +#endif /* MEM_DEBUG */ + + PageMemInUse -= p->size; + + + if (p->next == NULL && p->prev == NULL) { + MemHeader.pages = NULL; + MemHeader.nr_pages = 0; + GlobalUnlock (p->handle); + GlobalFree (p->handle); + } else { + if (p == MemHeader.pages) MemHeader.pages = p->next; + MemHeader.nr_pages--; + + if (p->prev != NULL) p->prev->next = p->next; + if (p->next != NULL) p->next->prev = p->prev; + + GlobalUnlock (p->handle); + GlobalFree (p->handle); + } +} + + +/* + * Segmented heap memory allocation. + */ + +MemPtr +_MemAlloc (MemSize n, char __far * file, int line) +{ + MEMPAGEHEADER far *p; + MEMHEADER far *mp; + char far *cp; + + if (n >= 65535) { + assert (n < 65535); + goto AllocFail; + } + +#if MEM_FAIL_SOON + if (MemFailSoonLimit != 0 && MemFailSoonLimit < MemInUse + n) { + MessageBox (NULL, "MemAlloc: Told to fail here, allocation fails", + "Out Of Memory", MB_ICONSTOP | MB_OK); + return (NULL); + } +#endif + + /* Larger than page size? */ + + if (n > USEABLESIZE) { + p = AddPage(n + sizeof(MEMPAGEHEADER) + sizeof(MEMHEADER)); + if (p == NULL) + goto AllocFail; + + mp = p->data; + mp->magic = MAGIC; + mp->magic2 = MAGIC2; + mp->id = MemID++; + mp->allocated = TRUE; + + + p->used = n; + p->empty = NULL; + + cp = ((char far *) mp) + sizeof(MEMHEADER); +#ifdef MEM_DEBUG + if (MemDebugLevel >= MEM_DEBUG_LEVEL) { + fprintf (MemDebugFile, "MemAlloc: addr(%lx) id(%u) size(%ld) global(%d)\n", + cp, mp->id, (long)n, 0); + fflush (MemDebugFile); + } +#endif /* MEM_DEBUG */ + + MemInUse += n; + if (MemInUse > MemInUseMax) + MemInUseMax = MemInUse; + return ((MemPtr) cp); + } + + + /* Search for the hole */ + + for (p = MemHeader.pages; p != NULL; p = p->next) { + /* Scan the chains */ + if (p->size - p->used - p->overhead <= 0) continue; + if (p->empty == NULL) continue; + + for (mp = p->empty; mp != NULL; mp = mp->next) { + if (!mp->allocated && mp->size >= n) break; + } + + if (mp != NULL) break; + } + + /* New page needed? */ + + if (p == NULL) { + p = AddPage(PAGESIZE); + if (p == NULL) + goto AllocFail; + mp = p->data; + } + + /* Do we need to break it up? */ + + if (mp->size - n > sizeof(MEMHEADER)) { + MEMHEADER far *mp2; + + cp = ((char far *) mp) + n + sizeof(MEMHEADER); + mp2 = (MEMHEADER far *) cp; + + mp2->magic = 0L; + mp2->magic2 = 0L; + mp2->allocated = FALSE; + mp2->page = p; + mp2->size = mp->size - n - sizeof(MEMHEADER); + + mp2->next = mp->next; + mp2->prev = mp; + if (mp->next != NULL) mp->next->prev = mp2; + mp->next = mp2; + + + p->overhead += sizeof(MEMHEADER); + + mp->size = n; + } + + mp->magic = MAGIC; + mp->magic2 = MAGIC2; + mp->allocated = TRUE; + mp->id = MemID++; + + p->used += n; + cp = ((char far *) mp) + sizeof(MEMHEADER); + + + /* Debugging info... */ +#ifdef MEM_DEBUG + if (MemDebugLevel >= MEM_DEBUG_LEVEL) { + fprintf (MemDebugFile, "MemAlloc: addr(%lx) id(%u) size(%ld) global(%d)\n", + cp, mp->id, (long)n, 0); + fflush (MemDebugFile); + } +#endif /* MEM_DEBUG */ + + MemInUse += n; + if (MemInUse > MemInUseMax) + MemInUseMax = MemInUse; + + + /* Search for the next empty hole */ + + for (; mp != NULL; mp = mp->next) { + if (!mp->allocated && mp->size > 0) break; + } + + p->empty = mp; + + return ((MemPtr) cp); + + +AllocFail: +#if 0 + assert (FALSE /* Memory allocation failed! */);*/ +#endif + return (NULL); +} + + + +/* + * Segmented heap memory free. + */ +int +_MemFree (MemPtr vp, char __far *file, int line) +{ + MEMPAGEHEADER far *p; + MEMHEADER far *mp, far *mp2; + char far *cp; + + if (vp == NULL) + return (0); + + + cp = ((char far *) vp) - sizeof(MEMHEADER); + mp = (MEMHEADER far *) cp; + + if (mp->magic != MAGIC || mp->magic2 != MAGIC2|| !mp->allocated) { + assert (mp->magic == MAGIC); + assert (mp->magic2 == MAGIC2); + assert (mp->allocated); + return (-1); + } + +#ifdef MEM_DEBUG + /* Deubgging info... */ + if (MemDebugLevel >= 4) { + if (MemInUse == MemInUseMax) + fprintf (MemDebugFile, "Memory usage is up to %lu\n", MemInUseMax); + } + if (MemDebugLevel >= MEM_DEBUG_LEVEL) { + fprintf (MemDebugFile, "MemFree: addr(%lx) id(%u)\n", vp, mp->id); + fflush (MemDebugFile); + } +#endif /* MEM_DEBUG */ + + MemInUse -= mp->size; + + + p = (MEMPAGEHEADER far *) mp->page; + p->used -= mp->size; + + mp->magic = 0L; + mp->magic2 = 0L; + mp->allocated = FALSE; + + /* Merge? */ + + mp2 = mp->prev; + + if (mp2 != NULL && !mp2->allocated) { + mp2->next = mp->next; + if (mp->next != NULL) mp->next->prev = mp2; + mp2->size += mp->size + sizeof(MEMHEADER); + + p->overhead -= sizeof(MEMHEADER); + + mp = mp2; + } + + mp2 = mp->next; + + if (mp2 != NULL && !mp2->allocated) { + mp->next = mp2->next; + if (mp2->next != NULL) + mp2->next->prev = mp; + + mp->size += mp2->size + sizeof(MEMHEADER); + + p->overhead -= sizeof(MEMHEADER); + } + + if (mp->prev == NULL && mp->next == NULL) { + DeletePage(p); + } else { + if (p->empty == NULL || mp < p->empty) p->empty = mp; + } + return (0); +} + + + +MemPtr +_MemRealloc (MemPtr p, MemSize n, char __far *file, int line) +{ + MEMHEADER far *mp; + char far *cp; + + if (p != NULL) { + /* Block already large enough? */ + cp = ((char far *) p) - sizeof(MEMHEADER); + mp = (MEMHEADER far *) cp; + + if (mp->magic != MAGIC || mp->magic2 != MAGIC2) { + assert (mp->magic == MAGIC); + assert (mp->magic2 == MAGIC2); + return (p); + } + + if (mp->size >= n) return (p); /* No need to do anything */ + } + /* Else swap to another block */ + + cp = MemAlloc (n); + if (cp == NULL) + return (NULL); + + if (p != NULL) { + _fmemcpy(cp, p, (size_t)((mp->size >= n) ? n : mp->size)); + MemFree (p); + } + + return ((void far *) cp); +} + + + +void +MemFailSoon (MemSize n) +{ +#if MEM_FAIL_SOON + MemFailSoonLimit = MemInUse + n; +#ifdef MEM_DEBUG + if (MemDebugLevel >= 1) { + fprintf (MemDebugFile, + "MemFailSoon: Fail when allocation increases by %ld (Max %ld)\n", + n, MemFailSoonLimit); + } +#endif +#endif +} + + + + + +MemSize +MemBlkSize (MemPtr p) +{ + MEMHEADER far *mp; + char far *cp; + + if (p == NULL) return (0); + cp = ((char far *) p) - sizeof(MEMHEADER); + + mp = (MEMHEADER far *) cp; + + if (mp->magic != MAGIC || mp->magic2 != MAGIC2) { + assert (mp->magic == MAGIC); + assert (mp->magic2 == MAGIC2); + return (0); + } + + return (mp->size); +} + + +#if 0 +MemPtr +MemDup (void far *p) +{ + unsigned int len; + void far *p1; + + len = MgetBlkSize (p); + p1 = MemAlloc (len); + if (p1 != NULL) + _fmemcpy(p1, p, len); + + return (p1); +} + + +void +MemoryStatistics (long int *allocated, long int *used, long int *overhead) +{ + MEMPAGEHEADER far *p; + + *allocated = *used = *overhead = 0L; + + for (p = MemHeader.pages; p != NULL; p = p->next) { + *allocated += p->size; + *used += p->used; + *overhead += p->overhead; + } +} +#endif + + +void +MemFreeAll (void) +{ + MEMPAGEHEADER far *p, far *p1; + + for (p = MemHeader.pages; p != NULL; ) { + p1 = p->next; + GlobalUnlock (p->handle); + GlobalFree (p->handle); + p = p1; + } + + MemHeader.pages = NULL; + MemHeader.nr_pages = 0; +} + + +#ifdef MEM_DEBUG + +/* For debugging purposes... not very pretty */ + +void PrintMemoryChains(void) +{ + MEMPAGEHEADER far *p; + MEMHEADER far *mp; + char far *cp; + char buffer[100]; + + /* Block already large enough? */ + + + for (p = MemHeader.pages; p != NULL; p = p->next) { + for (mp = p->data; mp != NULL; mp = mp->next) { + fprintf (MemDebugFile, "%Fp | %u | %s", mp, mp->size, mp->allocated ? "Alloc" : "Free"); + } + } +} + +#endif /* DEBUG */ + + + +#else /* !SEGHEAP. Old version, not used. */ + + + + + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Direct memory allocation + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* + * This following implementation allocates large memory blocks directly + * from global memory and small memory blocks from the local heap. The + * problem with this method is that pine's local heap is quite small + * so most of the memory ends up comming from the global heap. + */ + + +#define GUARD_LOW0 0xbbbb +#define GUARD_LOW 0x9999 +#define GUARD_HIGH 0xaaaaaaaa + +#define SMALL_MEM_BOUNDRY 32 + +#define HEAP_SIZE 32000 + + +/* Memory block header. Placed at beginning of each allocated block. */ +typedef struct { /*size len */ + WORD guard0; /* 00 - 02 */ + HGLOBAL handle; /* 02 - 02 */ + short globalBlk; /* 04 - 02 */ + MemSize size; /* 06 - 04 */ + WORD id; /* 0A - 02 */ + WORD guard1; /* 0C - 02 */ +} MemBlk; /* Total size: 0E */ + +typedef MemBlk __far * MemBlkPtr; + + +/* Memory high guard tailer. Placed at end of each allocated block. */ +typedef struct { + unsigned long guard1; +} MemHighGuard; + +typedef MemHighGuard __far *MemHighGuardPtr; + + +/* + * Memory allocation globals. + */ +LOCAL WORD MemID = 0; /* Keep track of ID. */ +LOCAL unsigned long MemLocalFails = 0; +LOCAL BOOL MemLocalFull = FALSE; /* True when local heap full*/ +#ifdef MEM_DEBUG +LOCAL unsigned long MemInUse = 0; /* Total bytes in use. */ +LOCAL unsigned long MemInUseMax = 0; /* max in use at one time. */ +LOCAL unsigned long SmallMemInUse = 0; +LOCAL unsigned long SmallMemInUseMax = 0; +#endif /* MEM_DEBUG */ + + + +/* + * Allocate a memory block. + * The file and line indicate where we are called from (for debugging) + * but in pine these mostly point to a bottel neck routine and are + * useless. + */ +MemPtr +_MemAlloc (MemSize size, char __far * file, int line) +{ + MemBlkPtr pBlk; + MemHighGuardPtr pHG; + HGLOBAL hBlk; + HLOCAL hLBlk; + UINT totalSize; + BYTE __far * pb; + + assert (size <= MEM_BLOCK_SIZE_MAX); + + + /* + * Calculate total size we need to allocate. + */ + totalSize = (UINT)size + sizeof (MemBlk) + sizeof (MemHighGuard); + + + pBlk = NULL; + + /* + * If it's a small block and the local heap is not full, try + * allocating from the local heap. + */ + if (size <= SMALL_MEM_BOUNDRY && !MemLocalFull) { + + /* Allocate block from local storage. */ + hLBlk = LocalAlloc (LMEM_MOVEABLE, totalSize); + if (hLBlk != NULL) { + + /* Lock block and dereference. */ + pBlk = (MemBlkPtr) LocalLock (hLBlk); + if (pBlk != NULL) { + pBlk->handle = hLBlk; + pBlk->globalBlk = FALSE; + } + else + LocalFree (hLBlk); + } + else { + ++MemLocalFails; + MemLocalFull = TRUE; +#ifdef MEM_DEBUG + if (MemDebugLevel >= MEM_DEBUG_LEVEL) + fprintf (MemDebugFile, "Local Memory alloc failed, %lu fails, %lu bytes in use\n", + MemLocalFails, SmallMemInUse); +#endif + } + } + + + /* + * If it is a large block, or local alloc failed, we allocate from + * global space. + */ + if (pBlk == NULL) { + + /* Allocate block from global storage. */ + hBlk = GlobalAlloc (GMEM_MOVEABLE, totalSize); + if (hBlk == NULL) + return (NULL); + + + /* Lock block and dereference. */ + pBlk = (MemBlkPtr) GlobalLock (hBlk); + if (pBlk == NULL) { + GlobalFree (hBlk); + return (NULL); + } + pBlk->handle = hBlk; + pBlk->globalBlk = TRUE; + } + + + + /* Fill rest of header. */ + pBlk->guard0 = GUARD_LOW0; + pBlk->size = size; + pBlk->id = ++MemID; + pBlk->guard1 = GUARD_LOW; + + + /* Find address that will be returned to caller. */ + pb = (BYTE __far *) (pBlk + 1); + + + /* Find high guard and fill. */ + pHG = (MemHighGuardPtr) (pb + size); + pHG->guard1 = GUARD_HIGH; + + + /* Debugging info... */ +#ifdef MEM_DEBUG + if (MemDebugLevel >= MEM_DEBUG_LEVEL) { + if( !file ) file = "??"; + fprintf (MemDebugFile, "MemAlloc: addr(%lx) id(%u) size(%ld) global(%d)\n", + pb, pBlk->id, (long)size, pBlk->globalBlk); + fflush (MemDebugFile); + } + MemInUse += totalSize; + if (MemInUse > MemInUseMax) + MemInUseMax = MemInUse; + if (size <= SMALL_MEM_BOUNDRY) { + SmallMemInUse += totalSize; + if (SmallMemInUse > SmallMemInUseMax) + SmallMemInUseMax = SmallMemInUse; + } +#endif /* MEM_DEBUG */ + + + return ((MemPtr) pb); +} + + + + +/* + * Free a block. + */ +int +_MemFree (MemPtr block, char __far *file, int line) +{ + MemBlkPtr pBlk; + MemHighGuardPtr pHG; + HGLOBAL hBlk; + HLOCAL hLBlk; + BOOL brc; + UINT totalSize; + + if (block == NULL) + return (0); + + + /* Find header and high guard and check them. */ + pBlk = ((MemBlkPtr)block) - 1; + pHG = (MemHighGuardPtr) ((char __far *)block + pBlk->size); + + totalSize = pBlk->size + sizeof (MemBlk) + sizeof (MemHighGuard); + + /* If these changed them someone wrote where the should not have. */ + assert (pBlk->guard0 == GUARD_LOW0); + assert (pBlk->guard1 == GUARD_LOW); + assert (pHG->guard1 == GUARD_HIGH); + + + +#ifdef MEM_DEBUG + /* Deubgging info... */ + if (MemDebugLevel >= MEM_DEBUG_LEVEL) { + if (pBlk->size <= SMALL_MEM_BOUNDRY && + SmallMemInUse == SmallMemInUseMax) + fprintf (MemDebugFile, "Small memory usage is up to %lu\n", SmallMemInUseMax); + if (MemInUse == MemInUseMax) + fprintf (MemDebugFile, "Memory usage is up to %lu\n", MemInUseMax); + } + MemInUse -= totalSize; + if (pBlk->size <= SMALL_MEM_BOUNDRY) + SmallMemInUse -= totalSize; + if (MemDebugLevel >= MEM_DEBUG_LEVEL) { + fprintf (MemDebugFile, "MemFree: addr(%lx) id(%u)\n", + block, pBlk->id); + fflush (MemDebugFile); + } +#endif /* MEM_DEBUG */ + + + + /* + * Header indicates which block it came from + */ + if (!pBlk->globalBlk) { + /* Unlock block */ + hLBlk = pBlk->handle; + brc = LocalUnlock (hLBlk); + assert (!brc); + + /* And free block. */ + hLBlk = LocalFree (hLBlk); + assert (hLBlk == NULL); + MemLocalFull = FALSE; + } + else { + /* Unlock block */ + hBlk = pBlk->handle; + brc = GlobalUnlock (hBlk); + assert (!brc); + + /* And free block. */ + hBlk = GlobalFree (hBlk); + assert (hBlk == NULL); + } + return (0); +} + + + + +/* + * Reallocate a memory block. Simplistic approach. + */ +MemPtr +_MemRealloc (MemPtr block, MemSize size, char __far * file, int line) +{ + MemPtr newBlock; + + + newBlock = MemAlloc (size); + if (newBlock == NULL) + return (NULL); + + if (block != NULL) { + _fmemcpy (newBlock, block , (size_t)MIN (size, MemBlkSize (block))); + MemFree (block); + } + + return (newBlock); +} + + + +/* + * Return the size of a memory block + */ +MemSize +MemBlkSize (MemPtr block) +{ + MemBlkPtr pBlk; + + if (block == NULL) return (0); + pBlk = ((MemBlkPtr)block) - 1; + assert (pBlk->guard1 == GUARD_LOW); + + return (pBlk->size); +} + + +#ifdef MEM_DEBUG +struct testblock { + struct testblock __far * prev; + HLOCAL h; +}; + + + +void +MemATest (void) +{ + void __near *n; + struct testblock __far *p; + struct testblock __far *pnew; + HLOCAL hl; + int bcount; + UINT segment, start, end; + void __far *f; + HGLOBAL hg; + DWORD dw; + LOCALINFO li; + UINT DataSeg; + +#if 0 + hg = GlobalAlloc (GMEM_FIXED, HEAP_SIZE); /* Allocate global block */ + if (hg == NULL) + return; + f = GlobalLock (hg); /* Lock and get pointer. */ + if (f == NULL) + goto Fail1; + segment = (UINT) GET_SEGMENT (f); /* Get segment and offsets. */ + start = (UINT) GET_OFFSET (f); + end = start + HEAP_SIZE - 1; + start += 16; + if (!LocalInit (segment, start, end)) /* Init it as the local heap*/ + goto Fail2; +#endif +#if 0 + __asm MOV DataSeg,DS; /* Get current DS. */ + __asm MOV DS,segment; /* Set DS to new heap. */ + hl = LocalAlloc (0, SMALL_MEM_BOUNDRY); /* Now allocate something. */ + __asm MOV DS,DataSeg; /* Restore DS. */ + if (hl == NULL) + return; + n = LocalLock (hl); /* Find where it is. */ + f = (void __far *)n; + segment = GET_SEGMENT(f); /* What Segment. */ + dw = GlobalHandle (segment); + hg = (HGLOBAL) (dw & 0xffff); + if (hg == NULL) + return; + + li.dwSize = sizeof (li); /* What size. */ + if (!LocalInfo (&li, hg)) + return; + + dw = GlobalSize (hg); + f = GlobalLock (hg); + GlobalUnlock (hg); + + LocalUnlock (hl); + LocalFree (hl); + + +#endif + + + + + p = NULL; + pnew = NULL; + bcount = 0; + + do { + hl = LocalAlloc (0, SMALL_MEM_BOUNDRY); + if (hl != NULL) { + ++bcount; + n = LocalLock (hl); + pnew = (struct testblock __far*) n; + pnew->h = hl; + pnew->prev = p; + p = pnew; + } + } while (hl != NULL); + + + if (MemDebugFile != NULL) + fprintf (MemDebugFile, "Allocated %d blocks of size %d\n", + bcount, SMALL_MEM_BOUNDRY); + + while (p != NULL) { + pnew = p->prev; + hl = p->h; + LocalUnlock (hl); + LocalFree (hl); + p = pnew; + } + fflush (MemDebugFile); +#if 0 +Fail2: GlobalUnlock (hg); +Fail1: GlobalFree (hg); +#endif + return; +} +#endif /* MEM_DEBUG */ + +#endif /* ifdef SEGHEAP */ + +#endif /* MSC_MALLOC */ diff --git a/pico/mswin.def b/pico/mswin.def new file mode 100644 index 00000000..c62d295d --- /dev/null +++ b/pico/mswin.def @@ -0,0 +1,16 @@ +NAME PICO +EXETYPE WINDOWS +DESCRIPTION 'Pico for Windows (Character)' +STUB 'winstub.exe' + +CODE PRELOAD DISCARDABLE SHARED +DATA PRELOAD MULTIPLE + +HEAPSIZE 4096 + +EXPORTS + PWNDPROC @1 + TWWNDPROC @2 + ABOUTDLGPROC @3 + + diff --git a/pico/mswinver.c b/pico/mswinver.c new file mode 100644 index 00000000..6c169721 --- /dev/null +++ b/pico/mswinver.c @@ -0,0 +1,66 @@ +/* + * ======================================================================== + * 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 5 +#define VER_MINOR 5 +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() +{ + return(""); +} + +/* + * Return specific windows version... + */ +char * +mswin_specific_winver() +{ + return(""); +} diff --git a/pico/osdep/Makefile.am b/pico/osdep/Makefile.am new file mode 100644 index 00000000..85cffef3 --- /dev/null +++ b/pico/osdep/Makefile.am @@ -0,0 +1,20 @@ +## 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 = libpicoosd.a + +libpicoosd_a_SOURCES = altedit.c chkpoint.c color.c filesys.c fsync.c getkey.c mouse.c \ + newmail.c popen.c raw.c read.c shell.c signals.c spell.c terminal.c truncate.c tty.c + +AM_CPPFLAGS = -I@top_builddir@/include -I@top_srcdir@/include diff --git a/pico/osdep/Makefile.in b/pico/osdep/Makefile.in new file mode 100644 index 00000000..65619a10 --- /dev/null +++ b/pico/osdep/Makefile.in @@ -0,0 +1,549 @@ +# 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 = pico/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 +libpicoosd_a_AR = $(AR) $(ARFLAGS) +libpicoosd_a_LIBADD = +am_libpicoosd_a_OBJECTS = altedit.$(OBJEXT) chkpoint.$(OBJEXT) \ + color.$(OBJEXT) filesys.$(OBJEXT) fsync.$(OBJEXT) \ + getkey.$(OBJEXT) mouse.$(OBJEXT) newmail.$(OBJEXT) \ + popen.$(OBJEXT) raw.$(OBJEXT) read.$(OBJEXT) shell.$(OBJEXT) \ + signals.$(OBJEXT) spell.$(OBJEXT) terminal.$(OBJEXT) \ + truncate.$(OBJEXT) tty.$(OBJEXT) +libpicoosd_a_OBJECTS = $(am_libpicoosd_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 = $(libpicoosd_a_SOURCES) +DIST_SOURCES = $(libpicoosd_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 = libpicoosd.a +libpicoosd_a_SOURCES = altedit.c chkpoint.c color.c filesys.c fsync.c getkey.c mouse.c \ + newmail.c popen.c raw.c read.c shell.c signals.c spell.c terminal.c truncate.c tty.c + +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 pico/osdep/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign pico/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) +libpicoosd.a: $(libpicoosd_a_OBJECTS) $(libpicoosd_a_DEPENDENCIES) + -rm -f libpicoosd.a + $(libpicoosd_a_AR) libpicoosd.a $(libpicoosd_a_OBJECTS) $(libpicoosd_a_LIBADD) + $(RANLIB) libpicoosd.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/altedit.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chkpoint.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/color.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filesys.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsync.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getkey.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mouse.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newmail.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/popen.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/raw.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/read.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/shell.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signals.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spell.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/terminal.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/truncate.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty.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/pico/osdep/altedit.c b/pico/osdep/altedit.c new file mode 100644 index 00000000..934ed016 --- /dev/null +++ b/pico/osdep/altedit.c @@ -0,0 +1,712 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: altedit.c 854 2007-12-07 17:44:43Z 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 +#include + +#include "../../pith/osdep/pipe.h" +#include "../../pith/osdep/forkwait.h" +#include "../../pith/charconv/filesys.h" + +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../edef.h" +#include "../efunc.h" +#include "../keydefs.h" +#include "../utf8stub.h" +#include "tty.h" +#include "filesys.h" + +#ifdef _WINDOWS +#include "mswin.h" +#endif + +#include "altedit.h" +#ifndef _WINDOWS +#include "signals.h" + +#ifdef SIGCHLD +static jmp_buf pico_child_state; +static short pico_child_jmp_ok, pico_child_done; +#endif /* SIGCHLD */ + +#endif /* !_WINDOWS */ + + +/* internal prototypes */ +#ifndef _WINDOWS +#ifdef SIGCHLD +RETSIGTYPE child_handler(int); +#endif /* SIGCHLD */ +#else /* _WINDOWS */ +int alt_editor_valid(char *, char *, size_t); +int alt_editor_valid_fp(char *); +#endif /* _WINDOWS */ + + +/* + * alt_editor - fork off an alternate editor for mail message composition + * if one is configured and passed from pine. If not, only + * ask for the editor if advanced user flag is set, and + * suggest environment's EDITOR value as default. + */ +int +alt_editor(int f, int n) +{ +#ifndef _WINDOWS + char eb[NLINE]; /* buf holding edit command */ + char *fn; /* tmp holder for file name */ + char result[128]; /* result string */ + char prmpt[128]; + int i, done = 0, ret = 0, rv, trv; + pid_t child, pid; + RETSIGTYPE (*ohup)(int); + RETSIGTYPE (*oint)(int); + RETSIGTYPE (*osize)(int); + int status; + EML eml; + + eml.s = f ? "speller" : "editor"; + + if(gmode&MDSCUR){ + emlwrite("Alternate %s not available in restricted mode", &eml); + return(-1); + } + + strncpy(result, "Alternate %s complete.", sizeof(result)); + result[sizeof(result)-1] = '\0'; + + if(f){ + if(alt_speller){ + strncpy(eb, alt_speller, sizeof(eb)); + eb[sizeof(eb)-1] = '\0'; + } + else + return(-1); + } + else if(Pmaster == NULL){ + return(-1); + } + else{ + eb[0] = '\0'; + + if(Pmaster->alt_ed){ + char **lp, *wsp, *path, fname[MAXPATH+1]; + int c; + + for(lp = Pmaster->alt_ed; *lp && **lp; lp++){ + if((wsp = strpbrk(*lp, " \t")) != NULL){ + c = *wsp; + *wsp = '\0'; + } + + if(strchr(*lp, '/')){ + rv = fexist(*lp, "x", (off_t *)NULL); + } + else{ + if(!(path = getenv("PATH"))) + path = ":/bin:/usr/bin"; + + rv = ~FIOSUC; + while(rv != FIOSUC && *path && pathcat(fname, &path, *lp)) + rv = fexist(fname, "x", (off_t *)NULL); + } + + if(wsp) + *wsp = c; + + if(rv == FIOSUC){ + strncpy(eb, *lp, sizeof(eb)); + eb[sizeof(eb)-1] = '\0'; + break; + } + } + } + + if(!eb[0]){ + if(!(gmode&MDADVN)){ + unknown_command(0); + return(-1); + } + + if(getenv("EDITOR")){ + strncpy(eb, (char *)getenv("EDITOR"), sizeof(eb)); + eb[sizeof(eb)-1] = '\0'; + } + else + *eb = '\0'; + + while(!done){ + pid = mlreplyd_utf8("Which alternate editor ? ", eb, + sizeof(eb), QDEFLT, NULL); + switch(pid){ + case ABORT: + curwp->w_flag |= WFMODE; + return(-1); + case HELPCH: + emlwrite("no alternate editor help yet", NULL); + +/* take sleep and break out after there's help */ + sleep(3); + break; + case (CTRL|'L'): + sgarbf = TRUE; + update(); + break; + case TRUE: + case FALSE: /* does editor exist ? */ + if(*eb == '\0'){ /* leave silently? */ + mlerase(); + curwp->w_flag |= WFMODE; + return(-1); + } + + done++; + break; + default: + break; + } + } + } + } + + if((fn=writetmp(1, NULL)) == NULL){ /* get temp file */ + emlwrite("Problem writing temp file for alt editor", NULL); + return(-1); + } + + strncat(eb, " ", sizeof(eb)-strlen(eb)-1); + eb[sizeof(eb)-1] = '\0'; + strncat(eb, fn, sizeof(eb)-strlen(eb)-1); + eb[sizeof(eb)-1] = '\0'; + + + for(i = 0; i <= ((Pmaster) ? term.t_nrow : term.t_nrow - 1); i++){ + movecursor(i, 0); + if(!i){ + fputs("Invoking alternate ", stdout); + fputs(f ? "speller..." : "editor...", stdout); + } + + peeol(); + } + + (*term.t_flush)(); + if(Pmaster) + (*Pmaster->tty_fix)(0); + else + vttidy(); + +#ifdef SIGCHLD + if(Pmaster){ + /* + * The idea here is to keep any mail connections our caller + * may have open while our child's out running around... + */ + pico_child_done = pico_child_jmp_ok = 0; + (void) signal(SIGCHLD, child_handler); + } +#endif + + if((child = fork()) > 0){ /* wait for the child to finish */ + ohup = signal(SIGHUP, SIG_IGN); /* ignore signals for now */ + oint = signal(SIGINT, SIG_IGN); +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + osize = signal(SIGWINCH, SIG_IGN); +#endif + +#ifdef SIGCHLD + if(Pmaster){ + while(!pico_child_done){ + (*Pmaster->newmail)(0, 0); + if(!pico_child_done){ + if(setjmp(pico_child_state) == 0){ + pico_child_jmp_ok = 1; + sleep(600); + } + else + our_sigunblock(SIGCHLD); + } + + pico_child_jmp_ok = 0; + } + } +#endif + + trv = process_reap(child, &status, PR_NONE); + + signal(SIGHUP, ohup); /* restore signals */ + signal(SIGINT, oint); +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + signal(SIGWINCH, osize); +#endif + + /* + * Report child's unnatural or unhappy exit... + */ + if(trv > 0 && status == 0){ + strncpy(result, "Alternate %s done", sizeof(result)); + result[sizeof(result)-1] = '\0'; + } + else { + snprintf(result, sizeof(result), "Alternate %%s terminated abnormally (%d)", + (trv == 0) ? status : -1); + if(f) + ret = -1; + else{ + snprintf(prmpt, sizeof(prmpt), "Alt editor failed, use file %.20s of size %%ld chars anyway", fn); + ret = -2; + } + } + } + else if(child == 0){ /* spawn editor */ + signal(SIGHUP, SIG_DFL); /* let editor handle signals */ + signal(SIGINT, SIG_DFL); +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + signal(SIGWINCH, SIG_DFL); +#endif +#ifdef SIGCHLD + (void) signal(SIGCHLD, SIG_DFL); +#endif + if(execl("/bin/sh", "sh", "-c", fname_to_locale(eb), (char *) NULL) < 0) + exit(-1); + } + else { /* error! */ + snprintf(result, sizeof(result), "\007Can't fork %%s: %s", errstr(errno)); + ret = -1; + } + +#ifdef SIGCHLD + (void) signal(SIGCHLD, SIG_DFL); +#endif + + if(Pmaster) + (*Pmaster->tty_fix)(1); + + /* + * replace edited text with new text + */ + curbp->b_flag &= ~BFCHG; /* make sure old text gets blasted */ + + if(ret == -2){ + off_t filesize; + char prompt[128]; + + rv = fexist(fn, "r", &filesize); + if(rv == FIOSUC && filesize > 0){ + snprintf(prompt, sizeof(prompt), prmpt, (long) filesize); + /* clear bottom 3 rows */ + pclear(term.t_nrow-2, term.t_nrow); + i = mlyesno_utf8(prompt, FALSE); + if(i == TRUE){ + ret = 0; + strncpy(result, "OK, alternate %s done", sizeof(result)); + result[sizeof(result)-1] = '\0'; + } + else + ret = -1; + } + else + ret = -1; + } + + if(ret == 0) + readin(fn, 0, 0); /* read new text overwriting old */ + + our_unlink(fn); /* blast temp file */ + curbp->b_flag |= BFCHG; /* mark dirty for packbuf() */ + ttopen(); /* reset the signals */ + pico_refresh(0, 1); /* redraw */ + update(); + emlwrite(result, &eml); + return(ret); +#else /* _WINDOWS */ + char eb[2 * PATH_MAX]; /* buf holding edit command */ + char *fn; /* tmp holder for file name */ + char errbuf[128]; + char **lp; + int status; + int done; + int rc; + + + if(Pmaster == NULL) + return(-1); + + if(gmode&MDSCUR){ + emlwrite("Alternate editor not available in restricted mode", NULL); + return(-1); + } + + eb[0] = '\0'; + + if(Pmaster->alt_ed){ + char *p; + + for(lp = Pmaster->alt_ed; *lp && **lp; lp++) + if(*lp[0] == '\"'){ + if(p = strchr(*lp + 1, '\"')){ + *p = '\0'; + rc = alt_editor_valid((*lp) + 1, eb, sizeof(eb)); + *p = '\"'; + if(rc){ + strncat(eb, p + 1, sizeof(eb)-strlen(eb)-1); + eb[sizeof(eb)-1] = '\0'; + break; + } + } + } + else if(p = strchr(*lp, ' ')){ + for(rc = 0; !rc; ){ + if(p) + *p = '\0'; + + rc = alt_editor_valid(*lp, eb, sizeof(eb)); + + if(p) + *p = ' '; + + if(rc){ + if(p){ + strncat(eb, p, sizeof(eb)-strlen(eb)-1); + eb[sizeof(eb)-1] = '\0'; + } + + break; + } + else if(p) + p = strchr(p + 1, ' '); + else + break; + } + + if(rc) + break; + } + else if(alt_editor_valid(*lp, eb, sizeof(eb))) + break; + } + + if(!eb[0]){ + if(!(gmode&MDADVN)){ + unknown_command(0); + return(-1); + } + + /* Guess which editor they want. */ + if(getenv("EDITOR")){ + strncpy(eb, (char *)getenv("EDITOR"), sizeof(eb)); + eb[sizeof(eb)-1] = '\0'; + } + else + *eb = '\0'; + + done = FALSE; + while(!done){ + rc = mlreplyd_utf8("Which alternate editor ? ", eb, + sizeof(eb),QDEFLT,NULL); + + switch(rc){ + case ABORT: + curwp->w_flag |= WFMODE; + return(-1); + case HELPCH: + emlwrite("no alternate editor help yet", NULL); + +/* take sleep and break out after there's help */ + sleep(3); + break; + case (CTRL|'L'): + sgarbf = TRUE; + update(); + break; + case TRUE: + case FALSE: /* does editor exist ? */ + if(*eb == '\0'){ /* leave silently? */ + mlerase(); + curwp->w_flag |= WFMODE; + return(-1); + } + + done++; + break; + default: + break; + } + } + } + + if((fn=writetmp(1, NULL)) == NULL){ /* get temp file */ + emlwrite("Problem writing temp file for alt editor", NULL); + return(-1); + } + + emlwrite("Waiting for alternate editor to finish...", NULL); + +#ifdef ALTED_DOT + if(eb[0] == '.' && eb[1] == '\0') { + status = mswin_exec_and_wait ("alternate editor", eb, fn, fn, + NULL, 0); + } + else +#endif /* ALTED_DOT */ + { /* just here in case ALTED_DOT is defined */ + strncat(eb, " ", sizeof(eb)-strlen(eb)-1); + eb[sizeof(eb)-1] = '\0'; + strncat(eb, fn, sizeof(eb)-strlen(eb)-1); + eb[sizeof(eb)-1] = '\0'; + + status = mswin_exec_and_wait ("alternate editor", eb, NULL, NULL, + NULL, 0); + } + + switch (status) { + + case 0: + /* + * Success: replace edited text with new text + */ + curbp->b_flag &= ~BFCHG; /* make sure old text gets blasted */ + readin(fn, 0, 0); /* read new text overwriting old */ + our_unlink(fn); /* blast temp file */ + curbp->b_flag |= BFCHG; /* mark dirty for packbuf() */ + ttopen(); /* reset the signals */ + pico_refresh(0, 1); /* redraw */ + return(0); + + + /* + * Possible errors. + */ + case -1: + /* Failed to map return from WinExec to a HTASK. */ + emlwrite("Problem finding alternate editor task handle.", NULL); + return (-1); + + case -2: + /* User decided to abandon the alternate editor.*/ + emlwrite("Alternate editor abandoned.", NULL); + return (-1); + + default: + mswin_exec_err_msg ("alternate editor", status, errbuf, sizeof(errbuf)); + emlwrite (errbuf, NULL); + return (-1); + } + return (-1); +#endif /*_WINDOWS */ +} + + + +#ifndef _WINDOWS +int +pathcat(char *buf, char **path, char *file) +{ + register int n = 0; + + while(**path && **path != ':'){ + if(n++ > MAXPATH) + return(FALSE); + + *buf++ = *(*path)++; + } + + if(n){ + if(n++ > MAXPATH) + return(FALSE); + + *buf++ = '/'; + } + + while((*buf = *file++) != '\0'){ + if(n++ > MAXPATH) + return(FALSE); + + buf++; + } + + if(**path == ':') + (*path)++; + + return(TRUE); +} + + +#ifdef SIGCHLD +/* + * child_handler - handle child status change signal + */ +RETSIGTYPE +child_handler(int sig) +{ + pico_child_done = 1; + if(pico_child_jmp_ok){ +#ifdef sco + /* + * Villy Kruse says: + * This probably only affects older unix systems with a "broken" sleep + * function such as AT&T System V rel 3.2 and systems derived from + * that version. The problem is that the sleep function on these + * versions of unix will set up a signal handler for SIGALRM which + * will longjmp into the sleep function when the alarm goes off. + * This gives problems if another signal handler longjmps out of the + * sleep function, thus making the stack frame for the signal function + * invalid, and when the ALARM handler later longjmps back into the + * sleep function it does no longer have a valid stack frame. + * My sugested fix is to cancel the pending alarm in the SIGCHLD + * handler before longjmp'ing. This shouldn't hurt as there + * shouldn't be any ALARM pending at this point except possibly from + * the sleep call. + * + * + * [Even though it shouldn't hurt, we'll make it sco-specific for now.] + * + * + * The sleep call might have set up a signal handler which would + * longjmp back into the sleep code, and that would cause a crash. + */ + signal(SIGALRM, SIG_IGN); /* Cancel signal handeler */ + alarm(0); /* might longjmp back into sleep */ +#endif + longjmp(pico_child_state, 1); + } +} +#endif /* SIGCHLD */ + +#else /* _WINDOWS */ +/* + * alt_editor_valid -- test the given exe name exists, and if so + * return full name in "cmdbuf". + */ +int +alt_editor_valid(char *exe, char *cmdbuf, size_t ncmdbuf) +{ + +/**** + **** This isn't the right way to do this. I believe we + **** should be doing all of this in TCHARs starting in + **** the alt_editor function instead of trying to convert + **** to char * here. + ****/ + + if(!exe) + return(FALSE); + +#ifdef ALTED_DOT + if(exe[0] == '.' && exe[1] == '\0') { + cmdbuf[0] = '.'; + cmdbuf[1] = '\0'; + return(TRUE); + } + else +#endif /* ALTED_DOT */ + + if((isalpha((unsigned char) *exe) && *exe == ':') || strchr(exe, '\\')){ + char *path; + + if(alt_editor_valid_fp(path = exe)){ + if(strchr(path, ' ')) + snprintf(cmdbuf, ncmdbuf, "\"%s\"", path); + else{ + strncpy(cmdbuf, path, ncmdbuf); + cmdbuf[ncmdbuf-1] = '\0'; + } + + return(TRUE); + } + } + else{ + TCHAR pathbuflpt[PATH_MAX+1]; + LPTSTR name, exelpt; + LPTSTR cmdbuflpt; + char *utf8; + + cmdbuflpt = (LPTSTR) MemAlloc(ncmdbuf * sizeof(TCHAR)); + cmdbuflpt[0] = L'0'; + + exelpt = utf8_to_lptstr(exe); + + if(SearchPath(NULL, exelpt, NULL, PATH_MAX+1, pathbuflpt, &name)){ + + if(_tcschr(pathbuflpt, L' ')) + _stprintf_s(cmdbuflpt, ncmdbuf, TEXT("\"%s\""), pathbuflpt); + else + _stprintf_s(cmdbuflpt, ncmdbuf, TEXT("%s"), pathbuflpt); + + if(exelpt) + fs_give((void **) &exelpt); + + utf8 = lptstr_to_utf8(cmdbuflpt); + if(utf8){ + strncpy(cmdbuf, utf8, ncmdbuf); + cmdbuf[ncmdbuf-1] = '\0'; + fs_give((void **) &utf8); + } + + if(cmdbuflpt) + MemFree((void *) cmdbuflpt); + + return(TRUE); + } + + if(exelpt) + fs_give((void **) &exelpt); + + if(cmdbuflpt) + MemFree((void *) cmdbuflpt); + } + + return(FALSE); +} + + +/* + * alt_editor_valid_fp -- test the given full path name exists + */ +int +alt_editor_valid_fp(path) + char *path; +{ + static char *exts[] = {".exe", ".com", ".bat", NULL}; + char pathcopy[PATH_MAX + 1], *dot = NULL; + int i, j; + + for(i = 0; pathcopy[i] = path[i]; i++) + if(path[i] == '.') + dot = &path[i]; + + if(dot && (!strucmp(dot, ".exe") + || !strucmp(dot, ".com") || !strucmp(dot, ".bat"))){ + if(fexist(path, "x", (off_t *) NULL) == FIOSUC) + return(TRUE); + } + else{ + for(j = 0; exts[j]; j++){ + strncpy(&pathcopy[i], exts[j], sizeof(pathcopy)-i); + pathcopy[sizeof(pathcopy)-1] = '\0'; + if(fexist(pathcopy, "x", (off_t *) NULL) == FIOSUC) + return(TRUE); + } + } + + return(FALSE); +} +#endif diff --git a/pico/osdep/altedit.h b/pico/osdep/altedit.h new file mode 100644 index 00000000..7e592f6f --- /dev/null +++ b/pico/osdep/altedit.h @@ -0,0 +1,29 @@ +/* + * $Id: altedit.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 PICO_OSDEP_ALTEDIT_INCLUDED +#define PICO_OSDEP_ALTEDIT_INCLUDED + + + +/* exported prototypes */ +int alt_editor(int, int); +#ifndef _WINDOWS +int pathcat(char *, char **, char *); +#endif + + +#endif /* PICO_OSDEP_ALTEDIT_INCLUDED */ diff --git a/pico/osdep/chkpoint.c b/pico/osdep/chkpoint.c new file mode 100644 index 00000000..235cf83b --- /dev/null +++ b/pico/osdep/chkpoint.c @@ -0,0 +1,98 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: chkpoint.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 +#include + +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../edef.h" +#include "../efunc.h" +#include "../keydefs.h" +#include "filesys.h" + +#include "../../pith/charconv/filesys.h" + +#include "chkpoint.h" + + +/* + * chkptinit -- initialize anything we need to support composer + * checkpointing + */ +void +chkptinit(char *file, size_t filelen) +{ +#ifndef _WINDOWS + unsigned pid; + char *chp; +#endif + + if(!file[0]){ + long gmode_save = gmode; + + if(gmode&MDCURDIR) + gmode &= ~MDCURDIR; /* so fixpath will use home dir */ + +#ifndef _WINDOWS + strncpy(file, "#picoXXXXX#", filelen); +#else + strncpy(file, "#picoTM0.txt", filelen); +#endif + file[filelen-1] = '\0'; + fixpath(file, filelen); + gmode = gmode_save; + } + else{ + size_t l = strlen(file); + + if(l+2 <= filelen && file[l-1] != C_FILESEP){ + file[l++] = C_FILESEP; + file[l] = '\0'; + } + +#ifndef _WINDOWS + strncpy(file + l, "#picoXXXXX#", filelen-l); +#else + strncpy(file + l, "#picoTM0.txt", filelen-l); +#endif + file[filelen-1] = '\0'; + } + +#ifndef _WINDOWS + pid = (unsigned)getpid(); + for(chp = file+strlen(file) - 2; *chp == 'X'; chp--){ + *chp = (pid % 10) + '0'; + pid /= 10; + } +#else + if(fexist(file, "r", (off_t *)NULL) == FIOSUC){ /* does file exist? */ + char copy[NLINE]; + + strncpy(copy, "#picoTM1.txt", sizeof(copy)); + copy[sizeof(copy)-1] = '\0'; + fixpath(copy, sizeof(copy)); + our_rename(file, copy); /* save so we don't overwrite it */ + } +#endif /* _WINDOWS */ + + our_unlink(file); +} + diff --git a/pico/osdep/chkpoint.h b/pico/osdep/chkpoint.h new file mode 100644 index 00000000..66ca1baf --- /dev/null +++ b/pico/osdep/chkpoint.h @@ -0,0 +1,24 @@ +/* + * $Id: chkpoint.h 776 2007-10-24 23:14:26Z 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 PICO_OSDEP_CHKPOINT_INCLUDED +#define PICO_OSDEP_CHKPOINT_INCLUDED + + +/* exported prototypes */ +void chkptinit(char *, size_t); + + +#endif /* PICO_OSDEP_CHKPOINT_INCLUDED */ diff --git a/pico/osdep/color.c b/pico/osdep/color.c new file mode 100644 index 00000000..5a9d3710 --- /dev/null +++ b/pico/osdep/color.c @@ -0,0 +1,1797 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: color.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 +#include + +#include "../headers.h" + +#include "terminal.h" +#include "color.h" +#include "../../pith/osdep/color.h" +#include "../../pith/osdep/collate.h" + +#ifndef _WINDOWS + +struct color_name_list { + char *name; + int namelen; + struct color_name_list *next; +}; + +struct color_table { + struct color_name_list *names; + char *rgb; + int red, green, blue; + int val; +}; + + +/* useful definitions */ +#define ANSI8_COLOR() (color_options & COLOR_ANSI8_OPT) +#define ANSI16_COLOR() (color_options & COLOR_ANSI16_OPT) +#define ANSI256_COLOR() (color_options & COLOR_ANSI256_OPT) +#define ANSI_COLOR() (color_options & (COLOR_ANSI8_OPT | COLOR_ANSI16_OPT | COLOR_ANSI256_OPT)) +/* transparent (default) color is possible */ +#define COL_TRANS() ((color_options & COLOR_TRANS_OPT) ? 1 : 0) /* transparent */ +#define END_PSEUDO_REVERSE "EndInverse" +#define A_UNKNOWN -1 + + +/* local globals */ +static unsigned color_options; +static COLOR_PAIR *the_rev_color, *the_normal_color; +static COLOR_PAIR *color_blasted_by_attrs; +static int pinvstate; /* physical state of inverse (standout) attribute */ +static int pboldstate; /* physical state of bold attribute */ +static int pulstate; /* physical state of underline attribute */ +static int rev_color_state; + +static int boldstate; /* should-be state of bold attribute */ +static int ulstate; /* should-be state of underline attribute */ +static int invstate; /* should-be state of Inverse, which could be a color + change or an actual setinverse */ +static int _color_inited, _using_color; +static char *_nfcolor, *_nbcolor, *_rfcolor, *_rbcolor; +static char *_last_fg_color, *_last_bg_color; +static int _force_fg_color_change; +static int _force_bg_color_change; + +static struct color_table *color_tbl; + +/* external references */ +extern char *_op, *_oc, *_setaf, *_setab, *_setf, *_setb, *_scp; +extern int _bce, _colors; + + +/* internal prototypes */ +void flip_rev_color(int); +void flip_bold(int); +void flip_ul(int); +void reset_attr_state(void); +void add_to_color_name_list(struct color_table *t, char *name); +void free_color_name_list(struct color_name_list **nl); + + +#if HAS_TERMINFO +extern char *tparm(char *, ...); +#endif +extern char *tgoto(); +void tinitcolor(void); +int tfgcolor(int); +int tbgcolor(int); +struct color_table *init_color_table(void); +void free_color_table(struct color_table **); +int color_to_val(char *); + + + +/* + * Start or end bold mode + * + * Result: escape sequence to go into or out of reverse color is output + * + * (This is only called when there is a reverse color.) + * + * Arg state = ON set bold + * OFF set normal + */ +void +flip_rev_color(int state) +{ + if((rev_color_state = state) == TRUE) + (void)pico_set_colorp(the_rev_color, PSC_NONE); + else + pico_set_normal_color(); +} + + +/* + * Start or end bold mode + * + * Result: escape sequence to go into or out of bold is output + * + * Arg state = ON set bold + * OFF set normal + */ +void +flip_bold(int state) +{ + extern char *_setbold, *_clearallattr; + + if((pboldstate = state) == TRUE){ + if(_setbold != NULL) + putpad(_setbold); + } + else{ + if(_clearallattr != NULL){ + if(!color_blasted_by_attrs) + color_blasted_by_attrs = pico_get_cur_color(); + + _force_fg_color_change = _force_bg_color_change = 1; + putpad(_clearallattr); + pinvstate = state; + pulstate = state; + rev_color_state = state; + } + } +} + + +/* + * Start or end inverse mode + * + * Result: escape sequence to go into or out of inverse is output + * + * Arg state = ON set inverse + * OFF set normal + */ +void +flip_inv(int state) +{ + extern char *_setinverse, *_clearinverse; + + if((pinvstate = state) == TRUE){ + if(_setinverse != NULL) + putpad(_setinverse); + } + else{ + /* + * Unfortunately, some termcap entries configure end standout to + * be clear all attributes. + */ + if(_clearinverse != NULL){ + if(!color_blasted_by_attrs) + color_blasted_by_attrs = pico_get_cur_color(); + + _force_fg_color_change = _force_bg_color_change = 1; + putpad(_clearinverse); + pboldstate = (pboldstate == FALSE) ? pboldstate : A_UNKNOWN; + pulstate = (pulstate == FALSE) ? pulstate : A_UNKNOWN; + rev_color_state = A_UNKNOWN; + } + } +} + + +/* + * Start or end underline mode + * + * Result: escape sequence to go into or out of underline is output + * + * Arg state = ON set underline + * OFF set normal + */ +void +flip_ul(int state) +{ + extern char *_setunderline, *_clearunderline; + + if((pulstate = state) == TRUE){ + if(_setunderline != NULL) + putpad(_setunderline); + } + else{ + /* + * Unfortunately, some termcap entries configure end underline to + * be clear all attributes. + */ + if(_clearunderline != NULL){ + if(!color_blasted_by_attrs) + color_blasted_by_attrs = pico_get_cur_color(); + + _force_fg_color_change = _force_bg_color_change = 1; + putpad(_clearunderline); + pboldstate = (pboldstate == FALSE) ? pboldstate : A_UNKNOWN; + pinvstate = (pinvstate == FALSE) ? pinvstate : A_UNKNOWN; + rev_color_state = A_UNKNOWN; + } + } +} + + +void +StartInverse(void) +{ + invstate = TRUE; + reset_attr_state(); +} + + +void +EndInverse(void) +{ + invstate = FALSE; + reset_attr_state(); +} + + +int +InverseState(void) +{ + return(invstate); +} + + +int +StartBold(void) +{ + extern char *_setbold; + + boldstate = TRUE; + reset_attr_state(); + return(_setbold != NULL); +} + + +void +EndBold(void) +{ + boldstate = FALSE; + reset_attr_state(); +} + + +void +StartUnderline(void) +{ + ulstate = TRUE; + reset_attr_state(); +} + + +void +EndUnderline(void) +{ + ulstate = FALSE; + reset_attr_state(); +} + + +void +reset_attr_state(void) +{ + /* + * If we have to turn some attributes off, do that first since that + * may turn off other attributes as a side effect. + */ + if(boldstate == FALSE && pboldstate != boldstate) + flip_bold(boldstate); + + if(ulstate == FALSE && pulstate != ulstate) + flip_ul(ulstate); + + if(invstate == FALSE){ + if(pico_get_rev_color()){ + if(rev_color_state != invstate) + flip_rev_color(invstate); + } + else{ + if(pinvstate != invstate) + flip_inv(invstate); + } + } + + /* + * Now turn everything on that needs turning on. + */ + if(boldstate == TRUE && pboldstate != boldstate) + flip_bold(boldstate); + + if(ulstate == TRUE && pulstate != ulstate) + flip_ul(ulstate); + + if(invstate == TRUE){ + if(pico_get_rev_color()){ + if(rev_color_state != invstate) + flip_rev_color(invstate); + } + else{ + if(pinvstate != invstate) + flip_inv(invstate); + } + } + + if(color_blasted_by_attrs){ + (void)pico_set_colorp(color_blasted_by_attrs, PSC_NONE); + free_color_pair(&color_blasted_by_attrs); + } + +} + + +void +tinitcolor(void) +{ + if(_color_inited || panicking()) + return; + + if(ANSI_COLOR() || (_colors > 0 && ((_setaf && _setab) || (_setf && _setb) +/**** not sure how to do this yet + || _scp +****/ + ))){ + _color_inited = 1; + color_tbl = init_color_table(); + + if(ANSI_COLOR()) + putpad("\033[39;49m"); + else{ + if(_op) + putpad(_op); + if(_oc) + putpad(_oc); + } + } +} + + +#if HAS_TERMCAP +/* + * Treading on thin ice here. Tgoto wasn't designed for this. It takes + * arguments column and row. We only use one of them, so we put it in + * the row argument. The 0 is just a placeholder. + */ +#define tparm(s, c) tgoto(s, 0, c) +#endif + +int +tfgcolor(int color) +{ + if(!_color_inited) + tinitcolor(); + + if(!_color_inited) + return(-1); + + if((ANSI8_COLOR() && (color < 0 || color >= 8+COL_TRANS())) || + (ANSI16_COLOR() && (color < 0 || color >= 16+COL_TRANS())) || + (ANSI256_COLOR() && (color < 0 || color >= 256+COL_TRANS())) || + (!ANSI_COLOR() && (color < 0 || color >= _colors+COL_TRANS()))) + return(-1); + + if(ANSI_COLOR()){ + char buf[20]; + + if(COL_TRANS() && color == pico_count_in_color_table()-1) + snprintf(buf, sizeof(buf), "\033[39m"); + else if(ANSI256_COLOR()) + snprintf(buf, sizeof(buf), "\033[38;5;%dm", color); + else{ + if(color < 8) + snprintf(buf, sizeof(buf), "\033[3%cm", color + '0'); + else + snprintf(buf, sizeof(buf), "\033[9%cm", (color-8) + '0'); + } + + putpad(buf); + } + else if(COL_TRANS() && color == pico_count_in_color_table()-1 && _op){ + char bg_color_was[MAXCOLORLEN+1]; + + bg_color_was[0] = '\0'; + + /* + * Setting transparent (default) foreground color. + * We don't know how to set only the default foreground color, + * _op sets both foreground and background. So we need to + * + * get current background color + * set default colors + * if (current background was not default) reset it + */ + if(_last_bg_color && strncmp(_last_bg_color, MATCH_TRAN_COLOR, RGBLEN)){ + strncpy(bg_color_was, _last_bg_color, sizeof(bg_color_was)); + bg_color_was[sizeof(bg_color_was)-1] = '\0'; + } + + putpad(_op); + if(bg_color_was[0]){ + _force_bg_color_change = 1; + pico_set_bg_color(bg_color_was); + } + } + else if(_setaf) + putpad(tparm(_setaf, color)); + else if(_setf) + putpad(tparm(_setf, color)); + else if(_scp){ + /* set color pair method */ + } + + return(0); +} + + +int +tbgcolor(int color) +{ + if(!_color_inited) + tinitcolor(); + + if(!_color_inited) + return(-1); + + if((ANSI8_COLOR() && (color < 0 || color >= 8+COL_TRANS())) || + (ANSI16_COLOR() && (color < 0 || color >= 16+COL_TRANS())) || + (ANSI256_COLOR() && (color < 0 || color >= 256+COL_TRANS())) || + (!ANSI_COLOR() && (color < 0 || color >= _colors+COL_TRANS()))) + return(-1); + + if(ANSI_COLOR()){ + char buf[20]; + + if(COL_TRANS() && color == pico_count_in_color_table()-1) + snprintf(buf, sizeof(buf), "\033[49m"); + else if(ANSI256_COLOR()) + snprintf(buf, sizeof(buf), "\033[48;5;%dm", color); + else{ + if(color < 8) + snprintf(buf, sizeof(buf), "\033[4%cm", color + '0'); + else + snprintf(buf, sizeof(buf), "\033[10%cm", (color-8) + '0'); + } + + putpad(buf); + } + else if(COL_TRANS() && color == pico_count_in_color_table()-1 && _op){ + char fg_color_was[MAXCOLORLEN+1]; + + fg_color_was[0] = '\0'; + + /* + * Setting transparent (default) background color. + * We don't know how to set only the default background color, + * _op sets both foreground and background. So we need to + * + * get current foreground color + * set default colors + * if (current foreground was not default) reset it + */ + if(_last_fg_color && strncmp(_last_fg_color, MATCH_TRAN_COLOR, RGBLEN)){ + strncpy(fg_color_was, _last_fg_color, sizeof(fg_color_was)); + fg_color_was[sizeof(fg_color_was)-1] = '\0'; + } + + putpad(_op); + if(fg_color_was[0]){ + _force_fg_color_change = 1; + pico_set_fg_color(fg_color_was); + } + } + else if(_setab) + putpad(tparm(_setab, color)); + else if(_setb) + putpad(tparm(_setb, color)); + else if(_scp){ + /* set color pair method */ + } + + return(0); +} + + + +/* + * We're not actually using the RGB value other than as a string which + * maps into the color. + * In fact, on some systems color 1 and color 4 are swapped, and color 3 + * and color 6 are swapped. So don't believe the RGB values. + * Still, it feels right to have them be the canonical values, so we do that. + * + * Actually we are using them more now. In color_to_val we map to the closest + * color if we don't get an exact match. We ignore values over 255. + * + * More than one "name" can map to the same "rgb". + * More than one "name" can map to the same "val". + * The "val" for a "name" and for its "rgb" are the same. + */ +struct color_table * +init_color_table(void) +{ + struct color_table *ct = NULL, *t; + int i, count; + char colorname[12]; + + count = pico_count_in_color_table(); + + if(count > 0 && count <= 256+COL_TRANS()){ + int ind, graylevel; + struct { + char rgb[RGBLEN+1]; + int red, green, blue; + } cube[256]; + + ct = (struct color_table *) fs_get((count+1) * sizeof(struct color_table)); + if(ct) + memset(ct, 0, (count+1) * sizeof(struct color_table)); + + /* + * We boldly assume that 256 colors means xterm 256-color + * color cube and grayscale. + */ + if(ANSI_COLOR() && (count == 256+COL_TRANS())){ + int r, g, b, gray; + + for(r = 0; r < 6; r++) + for(g = 0; g < 6; g++) + for(b = 0; b < 6; b++){ + ind = 16 + 36*r + 6*g + b; + cube[ind].red = r ? (40*r + 55) : 0; + cube[ind].green = g ? (40*g + 55) : 0; + cube[ind].blue = b ? (40*b + 55) : 0; + snprintf(cube[ind].rgb, sizeof(cube[ind].rgb), "%3.3d,%3.3d,%3.3d", + cube[ind].red, cube[ind].green, cube[ind].blue); + } + + for(gray = 0; gray < 24; gray++){ + ind = gray + 232; + graylevel = 10*gray + 8; + cube[ind].red = graylevel; + cube[ind].green = graylevel; + cube[ind].blue = graylevel; + snprintf(cube[ind].rgb, sizeof(cube[ind].rgb), "%3.3d,%3.3d,%3.3d", + graylevel, graylevel, graylevel); + } + } + + for(i = 0, t = ct; t && i < count; i++, t++){ + t->val = i; + + switch(i){ + case COL_BLACK: + strncpy(colorname, "black", sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + break; + case COL_RED: + strncpy(colorname, "red", sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + break; + case COL_GREEN: + strncpy(colorname, "green", sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + break; + case COL_YELLOW: + strncpy(colorname, "yellow", sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + break; + case COL_BLUE: + strncpy(colorname, "blue", sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + break; + case COL_MAGENTA: + strncpy(colorname, "magenta", sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + break; + case COL_CYAN: + strncpy(colorname, "cyan", sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + break; + case COL_WHITE: + strncpy(colorname, "white", sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + break; + default: + snprintf(colorname, sizeof(colorname), "color%3.3d", i); + break; + } + + if(COL_TRANS() && i == count-1){ + strncpy(colorname, MATCH_TRAN_COLOR, sizeof(colorname)); + colorname[sizeof(colorname)-1] = '\0'; + } + + add_to_color_name_list(t, colorname); + + if(count == 8+COL_TRANS()){ + if(COL_TRANS() && i == count-1){ + t->red = t->green = t->blue = -1; + } + else + switch(i){ + case COL_BLACK: + t->red = t->green = t->blue = 0; + add_to_color_name_list(t, "color008"); + add_to_color_name_list(t, "colordgr"); + add_to_color_name_list(t, "colormgr"); + break; + case COL_RED: + t->red = 255; + t->green = t->blue = 0; + add_to_color_name_list(t, "color009"); + break; + case COL_GREEN: + t->green = 255; + t->red = t->blue = 0; + add_to_color_name_list(t, "color010"); + break; + case COL_YELLOW: + t->red = t->green = 255; + t->blue = 0; + add_to_color_name_list(t, "color011"); + break; + case COL_BLUE: + t->red = t->green = 0; + t->blue = 255; + add_to_color_name_list(t, "color012"); + break; + case COL_MAGENTA: + t->red = t->blue = 255; + t->green = 0; + add_to_color_name_list(t, "color013"); + break; + case COL_CYAN: + t->red = 0; + t->green = t->blue = 255; + add_to_color_name_list(t, "color014"); + break; + case COL_WHITE: + t->red = t->green = t->blue = 255; + add_to_color_name_list(t, "color015"); + add_to_color_name_list(t, "colorlgr"); + break; + } + } + else if(count == 16+COL_TRANS() || count == 256+COL_TRANS()){ + if(COL_TRANS() && i == count-1){ + t->red = t->green = t->blue = -1; + } + else + /* + * This set of RGB values seems to come close to describing + * what a 16-color xterm gives you. + */ + switch(i){ + case COL_BLACK: + t->red = t->green = t->blue = 0; + break; + case COL_RED: /* actually dark red */ + t->red = 174; + t->green = t->blue = 0; + break; + case COL_GREEN: /* actually dark green */ + t->green = 174; + t->red = t->blue = 0; + break; + case COL_YELLOW: /* actually dark yellow */ + t->blue = 0; + t->red = t->green = 174; + break; + case COL_BLUE: /* actually dark blue */ + t->blue = 174; + t->red = t->green = 0; + break; + case COL_MAGENTA: /* actually dark magenta */ + t->green = 0; + t->red = t->blue = 174; + break; + case COL_CYAN: /* actually dark cyan */ + t->red = 0; + t->green = t->blue = 174; + break; + case COL_WHITE: /* actually light gray */ + t->red = t->green = t->blue = 174; + if(count == 16) + add_to_color_name_list(t, "colorlgr"); + + break; + case 8: /* dark gray */ + t->red = t->green = t->blue = 64; + if(count == 16){ + add_to_color_name_list(t, "colordgr"); + add_to_color_name_list(t, "colormgr"); + } + + break; + case 9: /* red */ + t->red = 255; + t->green = t->blue = 0; + break; + case 10: /* green */ + t->green = 255; + t->red = t->blue = 0; + break; + case 11: /* yellow */ + t->blue = 0; + t->red = t->green = 255; + break; + case 12: /* blue */ + t->blue = 255; + t->red = t->green = 0; + break; + case 13: /* magenta */ + t->green = 0; + t->red = t->blue = 255; + break; + case 14: /* cyan */ + t->red = 0; + t->green = t->blue = 255; + break; + case 15: /* white */ + t->red = t->green = t->blue = 255; + break; + default: + t->red = cube[i].red; + t->green = cube[i].green; + t->blue = cube[i].blue; + switch(i){ + case 238: + add_to_color_name_list(t, "colordgr"); + break; + + case 244: + add_to_color_name_list(t, "colormgr"); + break; + + case 250: + add_to_color_name_list(t, "colorlgr"); + break; + } + + break; + } + } + else{ + if(COL_TRANS() && i == count-1){ + t->red = t->green = t->blue = -1; + } + else + switch(i){ + case COL_BLACK: + t->red = t->green = t->blue = 0; + break; + case COL_RED: /* actually dark red */ + t->red = 255; + t->green = t->blue = 0; + break; + case COL_GREEN: /* actually dark green */ + t->green = 255; + t->red = t->blue = 0; + break; + case COL_YELLOW: /* actually dark yellow */ + t->blue = 0; + t->red = t->green = 255; + break; + case COL_BLUE: /* actually dark blue */ + t->blue = 255; + t->red = t->green = 0; + break; + case COL_MAGENTA: /* actually dark magenta */ + t->green = 0; + t->red = t->blue = 255; + break; + case COL_CYAN: /* actually dark cyan */ + t->red = 0; + t->green = t->blue = 255; + break; + case COL_WHITE: /* actually light gray */ + t->red = t->green = t->blue = 255; + break; + default: + /* this will just be a string match */ + t->red = t->green = t->blue = 256+i; + break; + } + } + } + + for(i = 0, t = ct; t && i < count; i++, t++){ + t->rgb = (char *)fs_get((RGBLEN+1) * sizeof(char)); + if(COL_TRANS() && i == count-1) + snprintf(t->rgb, RGBLEN+1, MATCH_TRAN_COLOR); + else + snprintf(t->rgb, RGBLEN+1, "%3.3d,%3.3d,%3.3d", t->red, t->green, t->blue); + } + } + + return(ct); +} + + +void +add_to_color_name_list(struct color_table *t, char *name) +{ + if(t && name && *name){ + struct color_name_list *new_name; + + new_name = (struct color_name_list *) fs_get(sizeof(struct color_name_list)); + if(new_name){ + memset(new_name, 0, sizeof(*new_name)); + new_name->namelen = strlen(name); + + new_name->name = (char *) fs_get((new_name->namelen+1) * sizeof(char)); + if(new_name->name){ + strncpy(new_name->name, name, new_name->namelen+1); + new_name->name[new_name->namelen] = '\0'; + + if(t->names){ + struct color_name_list *nl; + for(nl = t->names; nl->next; nl = nl->next) + ; + + nl->next = new_name; + } + else + t->names = new_name; + } + } + } +} + + +void +free_color_name_list(struct color_name_list **nl) +{ + if(nl && *nl){ + if((*nl)->next) + free_color_name_list(&(*nl)->next); + + if((*nl)->name) + fs_give((void **) &(*nl)->name); + + fs_give((void **) nl); + } +} + + +/* + * Map from integer color value to canonical color name. + */ +char * +colorx(int color) +{ + struct color_table *ct; + static char cbuf[12]; + + /* before we get set up, we still need to use this a bit */ + if(!color_tbl){ + switch(color){ + case COL_BLACK: + return("black"); + case COL_RED: + return("red"); + case COL_GREEN: + return("green"); + case COL_YELLOW: + return("yellow"); + case COL_BLUE: + return("blue"); + case COL_MAGENTA: + return("magenta"); + case COL_CYAN: + return("cyan"); + case COL_WHITE: + return("white"); + default: + snprintf(cbuf, sizeof(cbuf), "color%3.3d", color); + return(cbuf); + } + } + + for(ct = color_tbl; ct->names; ct++) + if(ct->val == color) + break; + + /* rgb _is_ the canonical name */ + if(ct->names) + return(ct->rgb); + + /* not supposed to get here */ + snprintf(cbuf, sizeof(cbuf), "color%3.3d", color); + return(cbuf); +} + + +/* + * Argument is a color name which could be an RGB string, a name like "blue", + * or a name like "color011". + * + * Returns a pointer to the canonical name of the color. + */ +char * +color_to_canonical_name(char *s) +{ + struct color_table *ct; + struct color_name_list *nl; + int done; + + if(!s || !color_tbl) + return(NULL); + + if(*s == ' ' || isdigit(*s)){ + /* check for rgb string instead of name */ + for(ct = color_tbl; ct->rgb; ct++) + if(!strncmp(ct->rgb, s, RGBLEN)) + break; + } + else{ + for(done=0, ct = color_tbl; !done && ct->names; ct++){ + for(nl = ct->names; !done && nl; nl = nl->next) + if(nl->name && !struncmp(nl->name, s, nl->namelen)) + done++; + + if(done) + break; + } + } + + /* rgb is the canonical name */ + if(ct->names) + return(ct->rgb); + else if(!struncmp(s, MATCH_NORM_COLOR, RGBLEN) || !struncmp(s, MATCH_NONE_COLOR, RGBLEN)) + return(s); + + return(""); +} + + +/* + * Argument is the name of a color or an RGB value that we recognize. + * The table should be set up so that the val returned is the same whether + * or not we choose the canonical name. + * + * Returns the integer value for the color. + */ +int +color_to_val(char *s) +{ + struct color_table *ct; + struct color_name_list *nl; + int done; + + if(!s || !color_tbl) + return(-1); + + if(*s == ' ' || isdigit(*s)){ + /* check for rgb string instead of name */ + for(ct = color_tbl; ct->rgb; ct++) + if(!strncmp(ct->rgb, s, RGBLEN)) + break; + + /* + * Didn't match any. Find "closest" to match. + */ + if(!ct->rgb){ + int r = -1, g = -1, b = -1; + char *p, *comma, scopy[RGBLEN+1]; + + strncpy(scopy, s, sizeof(scopy)); + scopy[sizeof(scopy)-1] = '\0'; + + p = scopy; + comma = strchr(p, ','); + if(comma){ + *comma = '\0'; + r = atoi(p); + p = comma+1; + if(r >= 0 && r <= 255 && *p){ + comma = strchr(p, ','); + if(comma){ + *comma = '\0'; + g = atoi(p); + p = comma+1; + if(g >= 0 && g <= 255 && *p){ + b = atoi(p); + } + } + } + } + + if(r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255){ + struct color_table *closest = NULL; + int closest_value = 1000000; + int cv; + + for(ct = color_tbl; ct->rgb; ct++){ + + if(ct->red >= 0 && ct->red <= 255 + && ct->green >= 0 && ct->green <= 255 + && ct->blue >= 0 && ct->blue <= 255){ + cv = (ct->red - r) * (ct->red - r) + + (ct->green - g) * (ct->green - g) + + (ct->blue - b) * (ct->blue - b); + if(cv < closest_value){ + closest_value = cv; + closest = ct; + } + } + } + + if(closest) + ct = closest; + } + } + } + else{ + for(done=0, ct = color_tbl; !done && ct->names; ct++){ + for(nl = ct->names; !done && nl; nl = nl->next) + if(nl->name && !struncmp(nl->name, s, nl->namelen)) + done++; + + if(done) + break; + } + } + + if(ct->names) + return(ct->val); + else + return(-1); +} + + +void +free_color_table(struct color_table **ctbl) +{ + struct color_table *t; + + if(ctbl && *ctbl){ + for(t = *ctbl; t->names; t++){ + free_color_name_list(&t->names); + + if(t->rgb) + fs_give((void **) &t->rgb); + } + + fs_give((void **) ctbl); + } +} + + +int +pico_count_in_color_table(void) +{ + return( + (ANSI_COLOR() + ? (ANSI8_COLOR() ? 8 : ANSI16_COLOR() ? 16 : 256) + : _colors) + + COL_TRANS()); +} + + +void +pico_nfcolor(char *s) +{ + if(_nfcolor) + fs_give((void **) &_nfcolor); + + if(s){ + size_t len; + + len = strlen(s); + _nfcolor = (char *) fs_get((len+1) * sizeof(char)); + if(_nfcolor){ + strncpy(_nfcolor, s, len+1); + _nfcolor[len] = '\0'; + } + + if(the_normal_color){ + strncpy(the_normal_color->fg, _nfcolor, MAXCOLORLEN+1); + the_normal_color->fg[MAXCOLORLEN] = '\0'; + } + } + else if(the_normal_color) + free_color_pair(&the_normal_color); +} + + +void +pico_nbcolor(char *s) +{ + if(_nbcolor) + fs_give((void **) &_nbcolor); + + if(s){ + size_t len; + + len = strlen(s); + _nbcolor = (char *) fs_get((len+1) * sizeof(char)); + if(_nbcolor){ + strncpy(_nbcolor, s, len+1); + _nbcolor[len] = '\0'; + } + + if(the_normal_color){ + strncpy(the_normal_color->bg, _nbcolor, MAXCOLORLEN+1); + the_normal_color->bg[MAXCOLORLEN] = '\0'; + } + } + else if(the_normal_color) + free_color_pair(&the_normal_color); +} + +void +pico_rfcolor(char *s) +{ + if(_rfcolor) + fs_give((void **) &_rfcolor); + + if(s){ + size_t len; + + len = strlen(s); + _rfcolor = (char *) fs_get((len+1) * sizeof(char)); + if(_rfcolor){ + strncpy(_rfcolor, s, len+1); + _rfcolor[len] = '\0'; + } + + if(the_rev_color){ + strncpy(the_rev_color->fg, _rfcolor, MAXCOLORLEN+1); + the_rev_color->fg[MAXCOLORLEN] = '\0'; + } + } + else if(the_rev_color) + free_color_pair(&the_rev_color); +} + +void +pico_rbcolor(char *s) +{ + if(_rbcolor) + fs_give((void **) &_rbcolor); + + if(s){ + size_t len; + + len = strlen(s); + _rbcolor = (char *) fs_get((len+1) * sizeof(char)); + if(_rbcolor){ + strncpy(_rbcolor, s, len+1); + _rbcolor[len] = '\0'; + } + + if(the_rev_color){ + strncpy(the_rev_color->bg, _rbcolor, MAXCOLORLEN+1); + the_rev_color->bg[MAXCOLORLEN] = '\0'; + } + } + else if(the_rev_color) + free_color_pair(&the_rev_color); +} + + +int +pico_hascolor(void) +{ + if(!_color_inited) + tinitcolor(); + + return(_color_inited); +} + + +int +pico_usingcolor(void) +{ + return(_using_color && pico_hascolor()); +} + + +/* + * This should only be called when we're using the + * unix termdef color, as opposed to the ANSI defined + * color stuff or the Windows stuff. + */ +int +pico_trans_color(void) +{ + return(_bce && _op); +} + + +void +pico_toggle_color(int on) +{ + if(on){ + if(pico_hascolor()) + _using_color = 1; + } + else{ + _using_color = 0; + if(_color_inited){ + _color_inited = 0; + if(!panicking()) + free_color_table(&color_tbl); + + if(ANSI_COLOR()) + putpad("\033[39;49m"); + else{ + if(_op) + putpad(_op); + if(_oc) + putpad(_oc); + } + } + } +} + + +unsigned +pico_get_color_options(void) +{ + return(color_options); +} + + +int +pico_trans_is_on(void) +{ + return(COL_TRANS()); +} + + +/* + * Absolute set of options. Caller has to OR things together and so forth. + */ +void +pico_set_color_options(unsigned flags) +{ + color_options = flags; +} + +void +pico_endcolor(void) +{ + pico_toggle_color(0); + if(panicking()) + return; + + if(_nfcolor) + fs_give((void **) &_nfcolor); + + if(_nbcolor) + fs_give((void **) &_nbcolor); + + if(_rfcolor) + fs_give((void **) &_rfcolor); + + if(_rbcolor) + fs_give((void **) &_rbcolor); + + if(_last_fg_color) + fs_give((void **) &_last_fg_color); + + if(_last_bg_color) + fs_give((void **) &_last_bg_color); + + if(the_rev_color) + free_color_pair(&the_rev_color); + + if(the_normal_color) + free_color_pair(&the_normal_color); +} + + +void +pico_set_nfg_color(void) +{ + if(_nfcolor) + (void)pico_set_fg_color(_nfcolor); +} + + +void +pico_set_nbg_color(void) +{ + if(_nbcolor) + (void)pico_set_bg_color(_nbcolor); +} + + +void +pico_set_normal_color(void) +{ + if(!_nfcolor || !_nbcolor || + !pico_set_fg_color(_nfcolor) || !pico_set_bg_color(_nbcolor)){ + (void)pico_set_fg_color(DEFAULT_NORM_FORE_RGB); + (void)pico_set_bg_color(DEFAULT_NORM_BACK_RGB); + } +} + + +/* + * If inverse is a color, returns a pointer to that color. + * If not, returns NULL. + * + * NOTE: Don't free this! + */ +COLOR_PAIR * +pico_get_rev_color(void) +{ + if(pico_usingcolor() && _rfcolor && _rbcolor && + pico_is_good_color(_rfcolor) && pico_is_good_color(_rbcolor)){ + if(!the_rev_color) + the_rev_color = new_color_pair(_rfcolor, _rbcolor); + + return(the_rev_color); + } + else + return(NULL); +} + + +/* + * Returns a pointer to the normal color. + * + * NOTE: Don't free this! + */ +COLOR_PAIR * +pico_get_normal_color(void) +{ + if(pico_usingcolor() && _nfcolor && _nbcolor && + pico_is_good_color(_nfcolor) && pico_is_good_color(_nbcolor)){ + if(!the_normal_color) + the_normal_color = new_color_pair(_nfcolor, _nbcolor); + + return(the_normal_color); + } + else + return(NULL); +} + + +/* + * Sets color to (fg,bg). + * Flags == PSC_NONE No alternate default if fg,bg fails. + * == PSC_NORM Set it to Normal color on failure. + * == PSC_REV Set it to Reverse color on failure. + * + * If flag PSC_RET is set, returns an allocated copy of the previous + * color pair, otherwise returns NULL. + */ +COLOR_PAIR * +pico_set_colors(char *fg, char *bg, int flags) +{ + int uc; + COLOR_PAIR *cp = NULL, *rev = NULL; + + if(flags & PSC_RET) + cp = pico_get_cur_color(); + + if(fg && !strcmp(fg, END_PSEUDO_REVERSE)){ + EndInverse(); + if(cp) + free_color_pair(&cp); + } + else if(!((uc=pico_usingcolor()) && fg && bg && + pico_set_fg_color(fg) && pico_set_bg_color(bg))){ + + if(uc && flags & PSC_NORM) + pico_set_normal_color(); + else if(flags & PSC_REV){ + if((rev = pico_get_rev_color()) != NULL){ + pico_set_fg_color(rev->fg); /* these will succeed */ + pico_set_bg_color(rev->bg); + } + else{ + StartInverse(); + if(cp){ + strncpy(cp->fg, END_PSEUDO_REVERSE, MAXCOLORLEN+1); + cp->fg[MAXCOLORLEN] = '\0'; + strncpy(cp->bg, END_PSEUDO_REVERSE, MAXCOLORLEN+1); + cp->bg[MAXCOLORLEN] = '\0'; + } + } + } + } + + return(cp); +} + + +int +pico_is_good_color(char *s) +{ + struct color_table *ct; + struct color_name_list *nl; + int done; + + if(!s || !color_tbl) + return(FALSE); + + if(!strcmp(s, END_PSEUDO_REVERSE)) + return(TRUE); + else if(!struncmp(s, MATCH_NORM_COLOR, RGBLEN) || !struncmp(s, MATCH_NONE_COLOR, RGBLEN)) + return(TRUE); + else if(*s == ' ' || isdigit(*s)){ + /* check for rgb string instead of name */ + for(ct = color_tbl; ct->rgb; ct++) + if(!strncmp(ct->rgb, s, RGBLEN)) + break; + + /* if no match it's still ok if rgb */ + if(!ct->rgb){ + int r = -1, g = -1, b = -1; + char *p, *comma, scopy[RGBLEN+1]; + + strncpy(scopy, s, sizeof(scopy)); + scopy[sizeof(scopy)-1] = '\0'; + + p = scopy; + comma = strchr(p, ','); + if(comma){ + *comma = '\0'; + r = atoi(p); + p = comma+1; + if(r >= 0 && r <= 255 && *p){ + comma = strchr(p, ','); + if(comma){ + *comma = '\0'; + g = atoi(p); + p = comma+1; + if(g >= 0 && g <= 255 && *p){ + b = atoi(p); + } + } + } + } + + if(r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) + ct = color_tbl; /* to force TRUE */ + } + } + else{ + for(done=0, ct = color_tbl; !done && ct->names; ct++){ + for(nl = ct->names; !done && nl; nl = nl->next) + if(nl->name && !struncmp(nl->name, s, nl->namelen)) + done++; + + if(done) + break; + } + } + + return(ct->names ? TRUE : FALSE); +} + + +/* + * Return TRUE on success. + */ +int +pico_set_fg_color(char *s) +{ + int val; + + if(!s || !color_tbl) + return(FALSE); + + if(!strcmp(s, END_PSEUDO_REVERSE)){ + EndInverse(); + return(TRUE); + } + + if(!struncmp(s, MATCH_NORM_COLOR, RGBLEN)) + s = _nfcolor; + else if(!struncmp(s, MATCH_NONE_COLOR, RGBLEN)) + return(TRUE); + + if((val = color_to_val(s)) >= 0){ + size_t len; + int changed; + + changed = !_last_fg_color || strcmp(_last_fg_color,colorx(val)); + + /* already set correctly */ + if(!_force_fg_color_change && !changed) + return(TRUE); + + _force_fg_color_change = 0; + + if(changed){ + if(_last_fg_color) + fs_give((void **) &_last_fg_color); + + len = strlen(colorx(val)); + if((_last_fg_color = (char *) fs_get((len+1) * sizeof(char))) != NULL){ + strncpy(_last_fg_color, colorx(val), len+1); + _last_fg_color[len] = '\0'; + } + } + + tfgcolor(val); + return(TRUE); + } + else + return(FALSE); +} + + +int +pico_set_bg_color(char *s) +{ + int val; + + if(!s || !color_tbl) + return(FALSE); + + if(!strcmp(s, END_PSEUDO_REVERSE)){ + EndInverse(); + return(TRUE); + } + + if(!struncmp(s, MATCH_NORM_COLOR, RGBLEN)) + s = _nbcolor; + else if(!struncmp(s, MATCH_NONE_COLOR, RGBLEN)) + return(TRUE); + + if((val = color_to_val(s)) >= 0){ + size_t len; + int changed; + + changed = !_last_bg_color || strcmp(_last_bg_color,colorx(val)); + + /* already set correctly */ + if(!_force_bg_color_change && !changed) + return(TRUE); + + _force_bg_color_change = 0; + + if(changed){ + if(_last_bg_color) + fs_give((void **) &_last_bg_color); + + len = strlen(colorx(val)); + if((_last_bg_color = (char *) fs_get((len+1) * sizeof(char))) != NULL){ + strncpy(_last_bg_color, colorx(val), len+1); + _last_bg_color[len] = '\0'; + } + } + + tbgcolor(val); + return(TRUE); + } + else + return(FALSE); +} + + +/* + * Return a pointer to an rgb string for the input color. The output is 11 + * characters long and looks like rrr,ggg,bbb. + * + * Args colorName -- The color to convert to ascii rgb. + * + * Returns Pointer to a static buffer containing the rgb string. Can use up + * to three returned values at once before the first is overwritten. + */ +char * +color_to_asciirgb(char *colorName) +{ + static char c_to_a_buf[3][RGBLEN+1]; + static int whichbuf = 0; + struct color_table *ct; + struct color_name_list *nl; + int done; + + whichbuf = (whichbuf + 1) % 3; + + c_to_a_buf[whichbuf][0] = '\0'; + + for(done=0, ct = color_tbl; !done && ct->names; ct++){ + for(nl = ct->names; !done && nl; nl = nl->next) + if(nl->name && !struncmp(nl->name, colorName, nl->namelen)) + done++; + + if(done) + break; + } + + if(ct && ct->names){ + strncpy(c_to_a_buf[whichbuf], ct->rgb, sizeof(c_to_a_buf[0])); + c_to_a_buf[whichbuf][sizeof(c_to_a_buf[0])-1] = '\0'; + } + else if(*colorName == ' ' || isdigit(*colorName)){ + /* check for rgb string instead of name */ + for(ct = color_tbl; ct->rgb; ct++) + if(!strncmp(ct->rgb, colorName, RGBLEN)) + break; + + /* if no match it's still ok if rgb */ + if(!ct->rgb){ + int r = -1, g = -1, b = -1; + char *p, *comma, scopy[RGBLEN+1]; + + strncpy(scopy, colorName, sizeof(scopy)); + scopy[sizeof(scopy)-1] = '\0'; + + p = scopy; + comma = strchr(p, ','); + if(comma){ + *comma = '\0'; + r = atoi(p); + p = comma+1; + if(r >= 0 && r <= 255 && *p){ + comma = strchr(p, ','); + if(comma){ + *comma = '\0'; + g = atoi(p); + p = comma+1; + if(g >= 0 && g <= 255 && *p){ + b = atoi(p); + } + } + } + } + + if(r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255){ + /* it was already RGB */ + snprintf(c_to_a_buf[whichbuf], sizeof(c_to_a_buf[0]), "%3.3d,%3.3d,%3.3d", r, g, b); + } + } + else{ + strncpy(c_to_a_buf[whichbuf], ct->rgb, sizeof(c_to_a_buf[0])); + c_to_a_buf[whichbuf][sizeof(c_to_a_buf[0])-1] = '\0'; + } + } + + if(!c_to_a_buf[whichbuf][0]){ + int l; + + /* + * If we didn't find the color it could be that it is the + * normal color (MATCH_NORM_COLOR) or the none color + * (MATCH_NONE_COLOR). If that is the case, this strncpy thing + * will work out correctly because those two strings are + * RGBLEN long. Otherwise we're in a bit of trouble. This + * most likely means that the user is using the same pinerc on + * two terminals, one with more colors than the other. We didn't + * find a match because this color isn't present on this terminal. + * Since the return value of this function is assumed to be + * RGBLEN long, we'd better make it that long. + * It still won't work correctly because colors will be screwed up, + * but at least the embedded colors in filter.c will get properly + * sucked up when they're encountered. + */ + strncpy(c_to_a_buf[whichbuf], "xxxxxxxxxxx", RGBLEN); /* RGBLEN is 11 */ + l = strlen(colorName); + strncpy(c_to_a_buf[whichbuf], colorName, (l < RGBLEN) ? l : RGBLEN); + c_to_a_buf[whichbuf][RGBLEN] = '\0'; + } + + return(c_to_a_buf[whichbuf]); +} + + +char * +pico_get_last_fg_color(void) +{ + char *ret = NULL; + + if(_last_fg_color){ + size_t len; + + len = strlen(_last_fg_color); + if((ret = (char *) fs_get((len+1) * sizeof(char))) != NULL){ + strncpy(ret, _last_fg_color, len+1); + ret[len] = '\0'; + } + } + + return(ret); +} + + +char * +pico_get_last_bg_color(void) +{ + char *ret = NULL; + + if(_last_bg_color){ + size_t len; + + len = strlen(_last_bg_color); + if((ret = (char *) fs_get((len+1) * sizeof(char))) != NULL){ + strncpy(ret, _last_bg_color, len+1); + ret[len] = '\0'; + } + } + + return(ret); +} + + +COLOR_PAIR * +pico_get_cur_color(void) +{ + return(new_color_pair(_last_fg_color, _last_bg_color)); +} + +#else /* _WINDOWS */ +static short _in_inverse, _in_bold, _in_uline; + +int +pico_trans_is_on(void) +{ + return(0); +} + +void +StartInverse() +{ + if(!_in_inverse) + mswin_rev(_in_inverse = 1); +} + +void +EndInverse() +{ + if(_in_inverse) + mswin_rev(_in_inverse = 0); +} + +int +InverseState() +{ + return(_in_inverse); +} + +void +StartUnderline() +{ + if(!_in_uline) + mswin_uline(_in_uline = 1); +} + +void +EndUnderline() +{ + if(_in_uline) + mswin_uline(_in_uline = 0); +} + +int +StartBold() +{ + if(!_in_bold) + mswin_bold(_in_bold = 1); + + return(1); +} + +void +EndBold() +{ + if(_in_bold) + mswin_bold(_in_bold = 0); +} + + +#endif /* _WINDOWS */ diff --git a/pico/osdep/color.h b/pico/osdep/color.h new file mode 100644 index 00000000..0dced80c --- /dev/null +++ b/pico/osdep/color.h @@ -0,0 +1,38 @@ +/* + * $Id: color.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 PICO_OSDEP_COLOR_INCLUDED +#define PICO_OSDEP_COLOR_INCLUDED + + +/* exported prototypes */ +void StartInverse(void); +void flip_inv(int); +void EndInverse(void); +int InverseState(void); +int StartBold(void); +void EndBold(void); +void StartUnderline(void); +void EndUnderline(void); +int pico_trans_color(void); +void pico_endcolor(void); +void pico_toggle_color(int); +void pico_set_nfg_color(void); +void pico_set_nbg_color(void); + + +#endif /* PICO_OSDEP_COLOR_INCLUDED */ diff --git a/pico/osdep/filesys.c b/pico/osdep/filesys.c new file mode 100644 index 00000000..4471882e --- /dev/null +++ b/pico/osdep/filesys.c @@ -0,0 +1,1027 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: filesys.c 770 2007-10-24 00:23:09Z 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 +#include + +#if HAVE_PWD_H +# include +#endif + +#include "../../pith/osdep/temp_nam.h" +#include "../../pith/charconv/filesys.h" + +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../edef.h" +#include "../efunc.h" +#include "../keydefs.h" + +#include "fsync.h" +#include "filesys.h" + + +#if HAVE_DIRENT_H +# include +#else +# define dirent direct +# if HAVE_SYS_NDIR_H +# include +# endif +# if HAVE_SYS_DIR_H +# include +# endif +# if HAVE_NDIR_H +# include +# endif +#endif + + +/* + * fexist - returns TRUE if the file exists with mode passed in m, + * FALSE otherwise. By side effect returns length of file in l + * File is assumed to be a UTF-8 string. + */ +int +fexist(char *file, + char *m, /* files mode: r,w,rw,t or x */ + off_t *l) /* t means use lstat */ +{ +#ifndef _WINDOWS + struct stat sbuf; + int rv; + int (*stat_f)() = (m && *m == 't') ? our_lstat : our_stat; + + if(l) + *l = (off_t)0; + + rv = (*stat_f)(file, &sbuf); + if(rv < 0){ + switch(errno){ + case ENOENT : /* File not found */ + rv = FIOFNF; + break; +#ifdef ENAMETOOLONG + case ENAMETOOLONG : /* Name is too long */ + rv = FIOLNG; + break; +#endif + case EACCES : /* File not found */ + rv = FIOPER; + break; + default: /* Some other error */ + rv = FIOERR; + break; + } + + return(rv); + } + + if(l) + *l = (off_t)sbuf.st_size; + + if((sbuf.st_mode&S_IFMT) == S_IFDIR) + return(FIODIR); + else if(*m == 't'){ + struct stat sbuf2; + + /* + * If it is a symbolic link pointing to a directory, treat + * it like it is a directory, not a link. + */ + if((sbuf.st_mode&S_IFMT) == S_IFLNK){ + rv = our_stat(file, &sbuf2); + if(rv < 0){ + switch(errno){ + case ENOENT : /* File not found */ + rv = FIOSYM; + break; +#ifdef ENAMETOOLONG + case ENAMETOOLONG : /* Name is too long */ + rv = FIOLNG; + break; +#endif + case EACCES : /* File not found */ + rv = FIOPER; + break; + default: /* Some other error */ + rv = FIOERR; + break; + } + + return(rv); + } + + if((sbuf2.st_mode&S_IFMT) == S_IFDIR) + return(FIODIR); + } + + return(((sbuf.st_mode&S_IFMT) == S_IFLNK) ? FIOSYM : FIOSUC); + } + + if(*m == 'r'){ /* read access? */ + if(*(m+1) == 'w'){ /* and write access? */ + rv = (can_access(file,READ_ACCESS)==0) + ? (can_access(file,WRITE_ACCESS)==0) + ? FIOSUC + : FIONWT + : FIONRD; + } + else if(!*(m+1)){ /* just read access? */ + rv = (can_access(file,READ_ACCESS)==0) ? FIOSUC : FIONRD; + } + } + else if(*m == 'w' && !*(m+1)) /* write access? */ + rv = (can_access(file,WRITE_ACCESS)==0) ? FIOSUC : FIONWT; + else if(*m == 'x' && !*(m+1)) /* execute access? */ + rv = (can_access(file,EXECUTE_ACCESS)==0) ? FIOSUC : FIONEX; + else + rv = FIOERR; /* bad m arg */ + + return(rv); +#else /* _WINDOWS */ + struct stat sbuf; + + if(l != NULL) + *l = (off_t)0; + + if(our_stat(file, &sbuf) < 0){ + if(errno == ENOENT) /* File not found */ + return(FIOFNF); + else + return(FIOERR); + } + + if(l != NULL) + *l = (off_t)sbuf.st_size; + + if(sbuf.st_mode & S_IFDIR) + return(FIODIR); + else if(*m == 't') /* no links, just say yes */ + return(FIOSUC); + + if(m[0] == 'r') /* read access? */ + return((S_IREAD & sbuf.st_mode) ? FIOSUC : FIONRD); + else if(m[0] == 'w') /* write access? */ + return((S_IWRITE & sbuf.st_mode) ? FIOSUC : FIONWT); + else if(m[0] == 'x') /* execute access? */ + return((S_IEXEC & sbuf.st_mode) ? FIOSUC : FIONEX); + return(FIOERR); /* what? */ +#endif /* _WINDOWS */ +} + + +/* + * isdir - returns true if fn is a readable directory, false otherwise + * silent on errors (we'll let someone else notice the problem;)). + */ +int +isdir(char *fn, long *l, time_t *d) +{ + struct stat sbuf; + + if(l) + *l = 0; + + if(our_stat(fn, &sbuf) < 0) + return(0); + + if(l) + *l = sbuf.st_size; + + if(d) + *d = sbuf.st_mtime; + + return((sbuf.st_mode&S_IFMT) == S_IFDIR); +} + + +/* + * gethomedir - returns the users home directory in UTF-8 string + * Note: home is malloc'd for life of pico + */ +char * +gethomedir(int *l) +{ + static char *home = NULL; + static short hlen = 0; + + if(home == NULL){ + char buf[NLINE]; + +#ifndef _WINDOWS + strncpy(buf, "~", sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + fixpath(buf, sizeof(buf)); /* let fixpath do the work! */ +#else /* _WINDOWS */ + if(Pmaster && Pmaster->home_dir) + snprintf(buf, sizeof(buf), "%s", Pmaster->home_dir); + else + snprintf(buf, sizeof(buf), "%c:\\", _getdrive() + 'A' - 1); +#endif /* _WINDOWS */ + hlen = strlen(buf); + if((home = (char *)malloc((hlen + 1) * sizeof(char))) == NULL){ + emlwrite("Problem allocating space for home dir", NULL); + return(0); + } + + strncpy(home, buf, hlen); + home[hlen] = '\0'; + } + + if(l) + *l = hlen; + + return(home); +} + + +/* + * homeless - returns true if given file does not reside in the current + * user's home directory tree. + */ +int +homeless(char *f) +{ + char *home; + int len; + + home = gethomedir(&len); + return(strncmp(home, f, len)); +} + + +/* + * getfnames - return all file names in the given directory in a single + * malloc'd string. n contains the number of names + */ +char * +getfnames(char *dn, char *pat, int *n, char *e, size_t elen) +{ + size_t l, avail, alloced, incr = 1024; + char *names, *np, *p; + struct stat sbuf; +#if defined(ct) + FILE *dirp; + char fn[DIRSIZ+1]; +#else +#ifdef _WINDOWS + char buf[NLINE+1]; + struct _finddata_t dbuf; + long findrv; +#else + DIR *dirp; /* opened directory */ +#endif +#endif +#if defined(ct) || !defined(_WINDOWS) + struct dirent *dp; +#endif + + *n = 0; + + if(our_stat(dn, &sbuf) < 0){ + switch(errno){ + case ENOENT : /* File not found */ + if(e) + snprintf(e, elen, "\007File not found: \"%s\"", dn); + + break; +#ifdef ENAMETOOLONG + case ENAMETOOLONG : /* Name is too long */ + if(e) + snprintf(e, elen, "\007File name too long: \"%s\"", dn); + + break; +#endif + default: /* Some other error */ + if(e) + snprintf(e, elen, "\007Error getting file info: \"%s\"", dn); + + break; + } + return(NULL); + } + else{ + /* + * We'd like to use 512 * st_blocks as an initial estimate but + * some systems have a stat struct with no st_blocks in it. + */ + avail = alloced = MAX(sbuf.st_size, incr); + if((sbuf.st_mode&S_IFMT) != S_IFDIR){ + if(e) + snprintf(e, elen, "\007Not a directory: \"%s\"", dn); + + return(NULL); + } + } + + if((names=(char *)malloc(alloced * sizeof(char))) == NULL){ + if(e) + snprintf(e, elen, "\007Can't malloc space for file names"); + + return(NULL); + } + +#ifndef _WINDOWS + errno = 0; + if((dirp=opendir(fname_to_locale(dn))) == NULL){ + if(e) + snprintf(e, elen, "\007Can't open \"%s\": %s", dn, errstr(errno)); + + free((char *)names); + return(NULL); + } +#endif + + np = names; + +#if defined(ct) + while(fread(&dp, sizeof(struct direct), 1, dirp) > 0) { + /* skip empty slots with inode of 0 */ + if(dp.d_ino == 0) + continue; + (*n)++; /* count the number of active slots */ + (void)strncpy(fn, dp.d_name, DIRSIZ); + fn[14] = '\0'; + p = fname_to_utf8(fn); + while((*np++ = *p++) != '\0') + ; + } +#else /* !defined(ct) */ +#ifdef _WINDOWS + strncpy(buf, dn, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + snprintf(buf, sizeof(buf), "%s%s%s*%s%s", dn, + (dn[strlen(dn)-1] == '\\') ? "" : "\\", + (pat && *pat) ? pat : "", + (pat && *pat && strchr(pat, '.')) ? "" : ".", + (pat && *pat && strchr(pat, '.')) ? "" : "*"); + if((findrv = _findfirst(buf, &dbuf)) < 0){ + if(e) + sprintf(e, "Can't find first file in \"%s\"", dn); + + free((char *) names); + return(NULL); + } + + do { + p = fname_to_utf8(dbuf.name); +#else /* UNIX */ + while((dp = readdir(dirp)) != NULL){ + p = fname_to_utf8(dp->d_name); + if(!pat || !*pat || !strncmp(p, pat, strlen(pat))){ +#endif /* UNIX */ + (*n)++; + l = strlen(p); + while(avail < l+1){ + char *oldnames; + + alloced += incr; + avail += incr; + oldnames = names; + if((names=(char *)realloc((void *)names, alloced * sizeof(char))) + == NULL){ + if(e) + snprintf(e, elen, "\007Can't malloc enough space for file names"); + + return(NULL); + } + + np = names + (np-oldnames); + } + + avail -= (l+1); + + while((*np++ = *p++) != '\0') + ; +#ifdef _WINDOWS + } + while(_findnext(findrv, &dbuf) == 0); +#else /* UNIX */ + } + } +#endif /* UNIX */ +#endif /* !defined(ct) */ + +#ifdef _WINDOWS + _findclose(findrv); +#else + closedir(dirp); /* shut down */ +#endif + return(names); +} + + +/* + * fioperr - given the error number and file name, display error + */ +void +fioperr(int e, char *f) +{ + EML eml; + + eml.s = f; + + switch(e){ + case FIOFNF: /* File not found */ + emlwrite("\007File \"%s\" not found", &eml); + break; + case FIOEOF: /* end of file */ + emlwrite("\007End of file \"%s\" reached", &eml); + break; + case FIOLNG: /* name too long */ + emlwrite("\007File name \"%s\" too long", &eml); + break; + case FIODIR: /* file is a directory */ + emlwrite("\007File \"%s\" is a directory", &eml); + break; + case FIONWT: + emlwrite("\007Write permission denied: %s", &eml); + break; + case FIONRD: + emlwrite("\007Read permission denied: %s", &eml); + break; + case FIOPER: + emlwrite("\007Permission denied: %s", &eml); + break; + case FIONEX: + emlwrite("\007Execute permission denied: %s", &eml); + break; + default: + emlwrite("\007File I/O error: %s", &eml); + } +} + + + +/* + * pfnexpand - pico's function to expand the given file name if there is + * a leading '~'. Converts the homedir from user's locale to UTF-8. + * Fn is assumed to already be UTF-8. + */ +char * +pfnexpand(char *fn, size_t fnlen) +{ + register char *x, *y, *z; + char *home = NULL; +#ifndef _WINDOWS + struct passwd *pw; + char name[50]; + + if(*fn == '~'){ + for(x = fn+1, y = name; + *x != '/' && *x != '\0' && y-name < sizeof(name)-1; + *y++ = *x++) + ; + + *y = '\0'; + if(x == fn + 1){ /* ~/ */ + if (!(home = (char *) getenv("HOME"))) + if ((pw = getpwuid(geteuid())) != NULL) + home = pw->pw_dir; + } + else if(*name){ /* ~username/ */ + if((pw = getpwnam(name)) != NULL) + home = pw->pw_dir; + } + + if(!home || (strlen(home) + strlen(fn) >= fnlen)) + return(NULL); +#else /* _WINDOWS */ + char name[_MAX_PATH]; + + if(*fn == '~' && *(x = fn + 1) == '\\') { + if(!(home = (char *) getenv("HOME")) + && getenv("HOMEDRIVE") && getenv("HOMEPATH")) + snprintf(home = name, sizeof(name), "%s%s", + (char *) getenv("HOMEDRIVE"), (char *) getenv("HOMEPATH")); +#endif /* _WINDOWS */ + home = fname_to_utf8(home); + + /* make room for expanded path */ + for(z = x + strlen(x), y = fn + strlen(x) + strlen(home); + z >= x; + *y-- = *z--) + ; + + /* and insert the expanded address */ + for(x = fn, y = home; *y != '\0' && x-fn < fnlen; *x++ = *y++) + ; + } + + fn[fnlen-1] = '\0'; + + return(fn); +} + + +/* + * fixpath - make the given pathname into an absolute path, incoming + * name is UTF-8. + */ +void +fixpath(char *name, size_t namelen) +{ +#ifdef _WINDOWS + char file[_MAX_PATH]; + int dr; + + if(!namelen) + return; + + /* return the full path of given file, so drive spec? */ + if(name[1] == ':' && isalpha((unsigned char) name[0])){ + if(name[2] != '\\'){ /* including path? */ + dr = toupper((unsigned char)name[0]) - 'A' + 1; + if((void *)_getdcwd(dr, file, _MAX_PATH) != NULL){ + if(file[strlen(file)-1] != '\\') + strncat(file, "\\", sizeof(file)-1-strlen(file)); + + /* add file name */ + strncat(file, &name[2], sizeof(file)-1-strlen(file)); + } + else + return; + } + else + return; /* fully qualified with drive and path! */ + } + else if(name[0] == '\\' && name[1] != '\\') { /* no drive spec! */ + sprintf(file, "%c:%.*s", _getdrive()+'A'-1, namelen-3, name); + } + else if(name[0] == '\\' && name[1] == '\\'){ /* Windows network drive */ + return; + } + else{ + if(Pmaster && !(gmode & MDCURDIR)){ + strncpy(file, ((gmode & MDTREE) || opertree[0]) + ? opertree : gethomedir(NULL), sizeof(file)-1); + file[sizeof(file)-1] = '\0'; + } + else if(!_getcwd(file, sizeof(file))) /* no qualification */ + return; + + if(*name){ /* if name, append it */ + if(*file && file[strlen(file)-1] != '\\') + strncat(file, "\\", sizeof(file)-1-strlen(file)); + + strncat(file, name, sizeof(file)-1-strlen(file)); + } + } + + strncpy(name, file, namelen-1); /* copy back to real buffer */ + name[namelen-1] = '\0'; /* tie off just in case */ +#else /* UNIX */ + char *shft; + + /* filenames relative to ~ */ + if(!((name[0] == '/') + || (name[0] == '.' + && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))))){ + if(Pmaster && !(gmode&MDCURDIR) + && (*name != '~' && strlen(name)+2 < namelen)){ + + if(gmode&MDTREE && strlen(name)+strlen(opertree)+1 < namelen){ + int off = strlen(opertree); + + for(shft = strchr(name, '\0'); shft >= name; shft--) + shft[off+1] = *shft; + + strncpy(name, opertree, MIN(off,namelen-1)); + name[MIN(off,namelen-1)] = '/'; + } + else{ + for(shft = strchr(name, '\0'); shft >= name; shft--) + shft[2] = *shft; + + name[0] = '~'; + name[1] = '/'; + } + } + + pfnexpand(name, namelen); + } +#endif /* UNIX */ +} + + +/* + * compresspath - given a base path and an additional directory, collapse + * ".." and "." elements and return absolute path (appending + * base if necessary). + * + * returns 1 if OK, + * 0 if there's a problem + * new path, by side effect, if things went OK + */ +int +compresspath(char *base, char *path, size_t pathlen) +{ + register int i; + int depth = 0; + char *p; + char *stack[32]; + char pathbuf[NLINE]; + +#define PUSHD(X) (stack[depth++] = X) +#define POPD() ((depth > 0) ? stack[--depth] : "") + +#ifndef _WINDOWS + if(*path == '~'){ + fixpath(path, pathlen); + strncpy(pathbuf, path, sizeof(pathbuf)); + pathbuf[sizeof(pathbuf)-1] = '\0'; + } + else if(*path != C_FILESEP) + snprintf(pathbuf, sizeof(pathbuf), "%s%c%s", base, C_FILESEP, path); + else{ + strncpy(pathbuf, path, sizeof(pathbuf)); + pathbuf[sizeof(pathbuf)-1] = '\0'; + } +#else /* _WINDOWS */ + strncpy(pathbuf, path, sizeof(pathbuf)); + pathbuf[sizeof(pathbuf)-1] = '\0'; + fixpath(pathbuf, sizeof(pathbuf)); +#endif /* _WINDOWS */ + + p = &pathbuf[0]; + for(i=0; pathbuf[i] != '\0'; i++){ /* pass thru path name */ + if(pathbuf[i] == C_FILESEP){ + if(p != pathbuf) + PUSHD(p); /* push dir entry */ + + p = &pathbuf[i+1]; /* advance p */ + pathbuf[i] = '\0'; /* cap old p off */ + continue; + } + + if(pathbuf[i] == '.'){ /* special cases! */ + if(pathbuf[i+1] == '.' /* parent */ + && (pathbuf[i+2] == C_FILESEP || pathbuf[i+2] == '\0')){ + if(!strcmp(POPD(), "")) /* bad news! */ + return(0); + + i += 2; + p = (pathbuf[i] == '\0') ? "" : &pathbuf[i+1]; + } + else if(pathbuf[i+1] == C_FILESEP || pathbuf[i+1] == '\0'){ + i++; + p = (pathbuf[i] == '\0') ? "" : &pathbuf[i+1]; + } + } + } + + if(*p != '\0') + PUSHD(p); /* get last element */ + + path[0] = '\0'; + for(i = 0; i < depth; i++){ + strncat(path, S_FILESEP, pathlen-strlen(path)-1); + path[pathlen-1] = '\0'; + strncat(path, stack[i], pathlen-strlen(path)-1); + path[pathlen-1] = '\0'; + } + + return(1); /* everything's ok */ +} + + + +/* + * tmpname - return a temporary file name in the given buffer, the filename + * is in the directory dir unless dir is NULL. The file did not exist at + * the time of the temp_nam call, but was created by temp_nam. + */ +void +tmpname(char *dir, char *name) +{ + char *t; +#ifndef _WINDOWS + if((t = temp_nam((dir && *dir) ? dir : NULL, "pico.")) != NULL){ +#else /* _WINDOWS */ + char tmp[_MAX_PATH]; + + if(!((dir && *dir) || + (dir = getenv("TMPDIR")) || + (dir = getenv("TMP")) || + (dir = getenv("TEMP")))) + if(!(getcwd(dir = tmp, _MAX_PATH) + && fexist(dir, "w", (off_t *) NULL) == FIOSUC)) + dir = "c:\\"; + + if((t = temp_nam_ext(dir, "ae", "txt")) != NULL){ +#endif /* _WINDOWS */ + strncpy(name, t, NFILEN-1); + name[NFILEN-1] = '\0'; + free((void *)t); + } + else { + emlwrite("Unable to construct temp file name", NULL); + name[0] = '\0'; + } +} + + +/* + * Take a file name, and from it + * fabricate a buffer name. This routine knows + * about the syntax of file names on the target system. + * I suppose that this information could be put in + * a better place than a line of code. + */ +void +makename(char bname[], char fname[]) +{ + register char *cp1; + register char *cp2; + + cp1 = &fname[0]; + while (*cp1 != 0) + ++cp1; + + while (cp1!=&fname[0] && cp1[-1]!=C_FILESEP) + --cp1; + + cp2 = &bname[0]; + while (cp2!=&bname[NBUFN-1] && *cp1!=0 && *cp1!=';') + *cp2++ = *cp1++; + + *cp2 = 0; +} + + +/* + * copy - copy contents of file 'a' into a file named 'b'. Return error + * if either isn't accessible or is a directory + */ +int +copy(char *a, char *b) +{ + int in, out, n, rv = 0; + char *cb; + struct stat tsb, fsb; + EML eml; + extern int errno; + + if(our_stat(a, &fsb) < 0){ /* get source file info */ + eml.s = errstr(errno); + emlwrite("Can't Copy: %s", &eml); + return(-1); + } + + if(!(fsb.st_mode&S_IREAD)){ /* can we read it? */ + eml.s = a; + emlwrite("\007Read permission denied: %s", &eml); + return(-1); + } + + if((fsb.st_mode&S_IFMT) == S_IFDIR){ /* is it a directory? */ + eml.s = a; + emlwrite("\007Can't copy: %s is a directory", &eml); + return(-1); + } + + if(our_stat(b, &tsb) < 0){ /* get dest file's mode */ + switch(errno){ + case ENOENT: + break; /* these are OK */ + default: + eml.s = errstr(errno); + emlwrite("\007Can't Copy: %s", &eml); + return(-1); + } + } + else{ + if(!(tsb.st_mode&S_IWRITE)){ /* can we write it? */ + eml.s = b; + emlwrite("\007Write permission denied: %s", &eml); + return(-1); + } + + if((tsb.st_mode&S_IFMT) == S_IFDIR){ /* is it directory? */ + eml.s = b; + emlwrite("\007Can't copy: %s is a directory", &eml); + return(-1); + } + + if(fsb.st_dev == tsb.st_dev && fsb.st_ino == tsb.st_ino){ + emlwrite("\007Identical files. File not copied", NULL); + return(-1); + } + } + + if((in = our_open(a, O_RDONLY|O_BINARY, 0600)) < 0){ + eml.s = errstr(errno); + emlwrite("Copy Failed: %s", &eml); + return(-1); + } + + if((out=our_creat(b, fsb.st_mode&0xfff)) < 0){ + eml.s = errstr(errno); + emlwrite("Can't Copy: %s", &eml); + close(in); + return(-1); + } + + if((cb = (char *)malloc(NLINE*sizeof(char))) == NULL){ + emlwrite("Can't allocate space for copy buffer!", NULL); + close(in); + close(out); + return(-1); + } + + while(1){ /* do the copy */ + if((n = read(in, cb, NLINE)) < 0){ + eml.s = errstr(errno); + emlwrite("Can't Read Copy: %s", &eml); + rv = -1; + break; /* get out now */ + } + + if(n == 0) /* done! */ + break; + + if(write(out, cb, n) != n){ + eml.s = errstr(errno); + emlwrite("Can't Write Copy: %s", &eml); + rv = -1; + break; + } + } + + free(cb); + close(in); + close(out); + return(rv); +} + + +/* + * Open a file for writing. Return TRUE if all is well, and FALSE on error + * (cannot create). + */ +int +ffwopen(char *fn, int readonly) +{ + extern FIOINFO g_pico_fio; +#ifndef _WINDOWS + int fd; + EML eml; +#endif +#ifndef MODE_READONLY +#define MODE_READONLY (0600) +#endif + + /* + * Call open() by hand since we don't want O_TRUNC -- it'll + * screw over-quota users. The strategy is to overwrite the + * existing file's data and call ftruncate before close to lop + * off bytes + */ + + g_pico_fio.flags = FIOINFO_WRITE; + g_pico_fio.name = fn; +#ifndef _WINDOWS + if((fd = our_open(fn, O_CREAT|O_WRONLY, readonly ? MODE_READONLY : 0666)) >= 0 + && (g_pico_fio.fp = fdopen(fd, "w")) != NULL + && fseek(g_pico_fio.fp, 0L, 0) == 0) + return (FIOSUC); + + + eml.s = errstr(errno); + emlwrite("Cannot open file for writing: %s", &eml); + return (FIOERR); +#else /* _WINDOWS */ + if ((g_pico_fio.fp = our_fopen(fn, "w")) == NULL) { + emlwrite("Cannot open file for writing", NULL); + return (FIOERR); + } + +#ifdef MODE_READONLY + if(readonly) + our_chmod(fn, MODE_READONLY); /* fix access rights */ +#endif + + return (FIOSUC); +#endif /* _WINDOWS */ +} + + +/* + * Close a file. Should look at the status in all systems. + */ +int +ffclose(void) +{ + extern FIOINFO g_pico_fio; +#ifndef _WINDOWS + EML eml; + + errno = 0; + if((g_pico_fio.flags & FIOINFO_WRITE) + && (fflush(g_pico_fio.fp) == EOF + || ftruncate(fileno(g_pico_fio.fp), + (off_t) ftell(g_pico_fio.fp)) < 0)){ + eml.s = errstr(errno); + emlwrite("\007Error preparing to close file: %s", &eml); + sleep(5); + } + + if (fclose(g_pico_fio.fp) == EOF) { + eml.s = errstr(errno); + emlwrite("\007Error closing file: %s", &eml); + return(FIOERR); + } +#else /* _WINDOWS */ + if (fclose(g_pico_fio.fp) != FALSE) { + emlwrite("Error closing file", NULL); + return(FIOERR); + } +#endif /* _WINDOWS */ + + return(FIOSUC); +} + + + +#define EXTEND_BLOCK 1024 + + +/* + * ffelbowroom - make sure the destination's got enough room to receive + * what we're about to write... + */ +int +ffelbowroom(void) +{ +#ifndef _WINDOWS + register LINE *lp; + register long n; + int x; + char s[EXTEND_BLOCK], *errstring = NULL; + struct stat fsbuf; + extern FIOINFO g_pico_fio; + + /* Figure out how much room do we need */ + /* first, what's total */ + for(n=0L, lp=lforw(curbp->b_linep); lp != curbp->b_linep; lp=lforw(lp)) + n += (llength(lp) + 1); + + errno = 0; /* make sure previous error are cleared */ + + if(fstat(fileno(g_pico_fio.fp), &fsbuf) == 0){ + n -= fsbuf.st_size; + + if(n > 0L){ /* must be growing file, extend it */ + memset(s, 'U', EXTEND_BLOCK); + if(fseek(g_pico_fio.fp, fsbuf.st_size, 0) == 0){ + for( ; n > 0L; n -= EXTEND_BLOCK){ + x = (n < EXTEND_BLOCK) ? (int) n : EXTEND_BLOCK; + if(fwrite(s, x * sizeof(char), 1, g_pico_fio.fp) != 1){ + errstring = errstr(errno); + break; + } + } + + if(!errstring + && (fflush(g_pico_fio.fp) == EOF + || fsync(fileno(g_pico_fio.fp)) < 0)) + errstring = errstr(errno); + + if(errstring) /* clean up */ + (void) ftruncate(fileno(g_pico_fio.fp), (off_t) fsbuf.st_size); + else if(fseek(g_pico_fio.fp, 0L, 0) != 0) + errstring = errstr(errno); + } + else + errstring = errstr(errno); + } + } + else + errstring = errstr(errno); + + if(errstring){ + snprintf(s, sizeof(s), "Error writing to %s: %s", g_pico_fio.name, errstring); + emlwrite(s, NULL); + (void) fclose(g_pico_fio.fp); + return(FALSE); + } +#endif /* UNIX */ + return(TRUE); +} diff --git a/pico/osdep/filesys.h b/pico/osdep/filesys.h new file mode 100644 index 00000000..eadbdc86 --- /dev/null +++ b/pico/osdep/filesys.h @@ -0,0 +1,43 @@ +/* + * $Id: filesys.h 764 2007-10-23 23:44:49Z 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 PICO_OSDEP_FILESYS_INCLUDED +#define PICO_OSDEP_FILESYS_INCLUDED + + +#include "../../pith/osdep/canaccess.h" /* for *_ACCESS */ + + +/* exported prototypes */ +int fexist(char *, char *, off_t *); +int isdir(char *, long *, time_t *); +char *gethomedir(int *); +int homeless(char *); +char *getfnames(char *, char *, int *, char *, size_t); +void fioperr(int, char *); +char *pfnexpand(char *, size_t); +void fixpath(char *, size_t); +int compresspath(char *, char *, size_t); +void tmpname(char *, char *); +void makename(char *, char *); +int copy(char *, char *); +int ffwopen(char *, int); +int ffclose(void); +int ffelbowroom(void); + + +#endif /* PICO_OSDEP_FILESYS_INCLUDED */ diff --git a/pico/osdep/fsync.c b/pico/osdep/fsync.c new file mode 100644 index 00000000..14a15d29 --- /dev/null +++ b/pico/osdep/fsync.c @@ -0,0 +1,31 @@ +/* + * Program: File sync emulator + * + * ======================================================================== + * 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 + + +#ifndef HAVE_FSYNC +/* Emulator for BSD fsync() call + * Accepts: file name + * Returns: 0 if successful, -1 if failure + */ +int +our_fsync(int fd) +{ + sync(); + return 0; +} +#endif /* !HAVE_FSYNC */ + diff --git a/pico/osdep/fsync.h b/pico/osdep/fsync.h new file mode 100644 index 00000000..e7df6f93 --- /dev/null +++ b/pico/osdep/fsync.h @@ -0,0 +1,30 @@ +/* + * $Id: fsync.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 PICO_OSDEP_FSYNC_INCLUDED +#define PICO_OSDEP_FSYNC_INCLUDED + +#ifndef HAVE_FSYNC + +#define fsync(F) our_fsync(F) + +/* exported prototypes */ +int our_fsync(int); + + +#endif /* !HAVE_FSYNC */ + +#endif /* PICO_OSDEP_FSYNC_INCLUDED */ diff --git a/pico/osdep/getkey.c b/pico/osdep/getkey.c new file mode 100644 index 00000000..b55dd092 --- /dev/null +++ b/pico/osdep/getkey.c @@ -0,0 +1,565 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: getkey.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 +#include + +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../edef.h" +#include "../efunc.h" +#include "../keydefs.h" + +#include "tty.h" +#include "getkey.h" +#include "read.h" +#include "mouse.h" + +#ifdef _WINDOWS +#include "mswin.h" +static int MapMSKEYtoPK(int c); +#endif /* _WINDOWS */ + + +/* internal declarations */ +static int timeo = 0; + +/* these next two are declared in pith/conf.h */ +int +set_input_timeout(int t) +{ + int oldtimeo = timeo; + + timeo = t; + return(oldtimeo); +} + +int +get_input_timeout(void) +{ + return(timeo); +} + + +#ifndef _WINDOWS + + +/* internal prototypes */ +void bail(void); +int ReadyForKey(int); + + + +void +bail(void) +{ + sleep(30); /* see if os receives SIGHUP */ + kill(getpid(), SIGHUP); /* eof or bad error */ +} + + +#if TYPEAH +/* + * typahead - Check to see if any characters are already in the + * keyboard buffer + */ +int +typahead(void) +{ + int x; /* holds # of pending chars */ + + return((ioctl(0,FIONREAD,&x) < 0) ? 0 : x); +} +#endif /* TYPEAH */ + + + +/* + * ReadyForKey - return true if there's no timeout or we're told input + * is available... + */ +int +ReadyForKey(int timeout) +{ + switch(input_ready(timeout)){ + case READY_TO_READ: + return(1); + break; + + case NO_OP_COMMAND: + case NO_OP_IDLE: + case READ_INTR: + return(0); + + case BAIL_OUT: + case PANIC_NOW: + emlwrite("\007Problem reading from keyboard!", NULL); + kill(getpid(), SIGHUP); /* Bomb out (saving our work)! */ + /* no return */ + } + + /* can't happen */ + return(0); +} + + + +/* + * GetKey - Read in a key. + * Do the standard keyboard preprocessing. Convert the keys to the internal + * character set. Resolves escape sequences and returns no-op if global + * timeout value exceeded. + */ +UCS +GetKey(void) +{ + UCS ch, status, cc; + + if(!ReadyForKey(FUDGE-5)) + return(NODATA); + + switch(status = kbseq(simple_ttgetc, NULL, bail, input_cs, &ch)){ + case 0: /* regular character */ + break; + + case KEY_DOUBLE_ESC: + /* + * Special hack to get around comm devices eating control characters. + */ + if(!ReadyForKey(5)) + return(BADESC); /* user typed ESC ESC, then stopped */ + else + switch(status = kbseq(simple_ttgetc, NULL, bail, input_cs, &ch)){ + 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 F1 : + case F2 : + case F3 : + case F4 : + case F5 : + case F6 : + case F7 : + case F8 : + case F9 : + case F10 : + case F11 : + case F12 : + return(CTRL | status); + break; + + case 0: /* regular character */ + break; + + default: /* punt the whole thing */ + (*term.t_beep)(); + return(BADESC); + break; + } + + ch &= 0x7f; + if(isdigit((unsigned char)ch)){ + int n = 0, i = ch - '0'; + + if(i < 0 || i > 2) + return(BADESC); /* bogus literal char value */ + + while(n++ < 2){ + if(!ReadyForKey(5) + || (!isdigit((unsigned char) (ch = + (*term.t_getchar)(NODATA, NULL, bail))) + || (n == 1 && i == 2 && ch > '5') + || (n == 2 && i == 25 && ch > '5'))){ + return(BADESC); + } + + i = (i * 10) + (ch - '0'); + } + + ch = i; + } + else{ + if(islower((unsigned char)ch)) /* canonicalize if alpha */ + ch = toupper((unsigned char)ch); + + return((isalpha((unsigned char)ch) || ch == '@' + || (ch >= '[' && ch <= '_')) + ? (CTRL | ch) : ((ch == ' ') ? (CTRL | '@') : ch)); + } + + break; + +#ifdef MOUSE + case KEY_XTERM_MOUSE: + { + /* + * 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; + + button = (*term.t_getchar)(NODATA, NULL, bail) & 0x03; + + x = (*term.t_getchar)(NODATA, NULL, bail) - '!'; + y = (*term.t_getchar)(NODATA, NULL, bail) - '!'; + + if(button == 0){ + down = 1; + if(checkmouse(&cmd, 1, x, y)) + return((UCS) cmd); + } + else if(down && button == 3){ + down = 0; + if(checkmouse(&cmd, 0, x, y)) + return((UCS) cmd); + } + + return(NODATA); + } + + 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 F1 : + case F2 : + case F3 : + case F4 : + case F5 : + case F6 : + case F7 : + case F8 : + case F9 : + case F10 : + case F11 : + case F12 : + return(status); + + case CTRL_KEY_UP : + return(CTRL | KEY_UP); + case CTRL_KEY_DOWN : + return(CTRL | KEY_DOWN); + case CTRL_KEY_RIGHT : + return(CTRL | KEY_RIGHT); + case CTRL_KEY_LEFT : + return(CTRL | KEY_LEFT); + + case KEY_SWALLOW_Z: + status = BADESC; + case KEY_SWAL_UP: + case KEY_SWAL_DOWN: + case KEY_SWAL_LEFT: + case KEY_SWAL_RIGHT: + do + if(!ReadyForKey(2)){ + status = BADESC; + break; + } + while(!strchr("~qz", (*term.t_getchar)(NODATA, NULL, bail))); + + return((status == BADESC) + ? status + : status - (KEY_SWAL_UP - KEY_UP)); + break; + + case KEY_KERMIT: + do{ + cc = ch; + if(!ReadyForKey(2)) + return(BADESC); + else + ch = (*term.t_getchar)(NODATA, NULL, bail) & 0x7f; + }while(cc != '\033' && ch != '\\'); + + ch = NODATA; + break; + + case BADESC: + (*term.t_beep)(); + return(status); + + default: /* punt the whole thing */ + (*term.t_beep)(); + break; + } + + if (ch >= 0x00 && ch <= 0x1F) /* C0 control -> C- */ + ch = CTRL | (ch+'@'); + + return(ch); +} + + +/* + * kbseq - looks at an escape sequence coming from the keyboard and + * compares it to a trie of known keyboard escape sequences, and + * returns the function bound to the escape sequence. + * + * Args: getcfunc -- Function to get a single character from stdin, + * called with the next two arguments to this + * function as its arguments. + * recorder -- If non-NULL, function used to record keystroke. + * bail_handler -- Function used to bail out on read error. + * c -- Pointer to returned character. + * + * Returns: BADESC + * The escaped function. + * 0 if a regular char with char stuffed in location c. + */ +UCS +kbseq(int (*getcfunc)(int (*recorder)(int ), void (*bail_handler)(void )), + int (*recorder)(int), + void (*bail_handler)(void), + void *data, + UCS *ch) +{ + unsigned char c; + int first = 1; + KBESC_T *current; + + current = kbesc; + if(current == NULL) /* bag it */ + return(BADESC); + + while(1){ + c = (*getcfunc)(recorder, bail_handler); + + while(current->value != c){ + if(current->left == NULL){ /* NO MATCH */ + if(first){ + unsigned long octets_so_far, remaining_octets; + unsigned char *inputp; + UCS ucs; + unsigned char inputbuf[20]; + + /* + * Regular character. + * Read enough bytes to make up a character and convert it to UCS-4. + */ + memset(inputbuf, 0, sizeof(inputbuf)); + inputbuf[0] = c; + octets_so_far = 1; + for(;;){ + remaining_octets = octets_so_far; + inputp = inputbuf; + ucs = mbtow(data, &inputp, &remaining_octets); + switch(ucs){ + case CCONV_BADCHAR: + /* + * Not really a BADESC but that ought to + * be sufficient. We can add another type if + * we need to. + */ + return(BADESC); + + case CCONV_NEEDMORE: + if(octets_so_far >= sizeof(inputbuf)) + return(BADESC); + + c = (*getcfunc)(recorder, bail_handler); + inputbuf[octets_so_far++] = c; + break; + + default: + /* got a good UCS-4 character */ + *ch = ucs; + return(0); + } + } + + /* NOTREACHED */ + return(0); + } + else + return(BADESC); + } + current = current->left; + } + + if(current->down == NULL) /* match!!!*/ + return(current->func); + else + current = current->down; + + first = 0; + } +} + + +#define newnode() (KBESC_T *)malloc(sizeof(KBESC_T)) + +/* + * kpinsert - insert a keystroke escape sequence into the global search + * structure. + */ +void +kpinsert(char *kstr, int kval, int termcap_wins) +{ + register char *buf; + register KBESC_T *temp; + register KBESC_T *trail; + + if(kstr == NULL) + return; + + /* + * Don't allow escape sequences that don't start with ESC unless + * termcap_wins. This is to protect against mistakes in termcap files. + */ + if(!termcap_wins && *kstr != '\033') + return; + + temp = trail = kbesc; + buf = kstr; + + for(;;){ + if(temp == NULL){ + temp = newnode(); + temp->value = *buf; + temp->func = 0; + temp->left = NULL; + temp->down = NULL; + if(kbesc == NULL) + kbesc = temp; + else + trail->down = temp; + } + else{ /* first entry */ + while((temp != NULL) && (temp->value != *buf)){ + trail = temp; + temp = temp->left; + } + + if(temp == NULL){ /* add new val */ + temp = newnode(); + temp->value = *buf; + temp->func = 0; + temp->left = NULL; + temp->down = NULL; + trail->left = temp; + } + } + + if(*(++buf) == '\0') + break; + else{ + /* + * Ignore attempt to overwrite shorter existing escape sequence. + * That means that sequences with higher priority should be + * set up first, so if we want termcap sequences to override + * hardwired sequences, put the kpinsert calls for the + * termcap sequences first. (That's what you get if you define + * TERMCAP_WINS.) + */ + if(temp->func != 0) + return; + + trail = temp; + temp = temp->down; + } + } + + /* + * Ignore attempt to overwrite longer sequences we are a prefix + * of (down != NULL) and exact same sequence (func != 0). + */ + if(temp != NULL && temp->down == NULL && temp->func == 0) + temp->func = kval; +} + + + +/* + * kbdestroy() - kills the key pad function key search tree + * and frees all lines associated with it + * + * Should be called with arg kbesc, the top of the tree. + */ +void +kbdestroy(KBESC_T *kb) +{ + if(kb){ + kbdestroy(kb->left); + kbdestroy(kb->down); + free((char *)kb); + kb = NULL; + } +} + +#else /* _WINDOWS */ + +/* + * Read in a key. + * Do the standard keyboard preprocessing. Convert the keys to the internal + * character set. Resolves escape sequences and returns no-op if global + * timeout value exceeded. + */ +UCS +GetKey(void) +{ + UCS ch = 0; + long timein; + + + ch = NODATA; + timein = time(0L); + + /* + * Main character processing loop. + */ + while(!mswin_charavail()) { + +#ifdef MOUSE + /* Check Mouse. If we get a mouse event, convert to char + * event and return that. */ + if (checkmouse (&ch,0,0,0)) { + curwp->w_flag |= WFHARD; + return (ch); + } +#endif /* MOUSE */ + + + /* Check Timeout. */ + if(time(0L) >= timein+(FUDGE-10)) + return(NODATA); + } + + + return (mswin_getc_fast()); +} + +#endif /* _WINDOWS */ diff --git a/pico/osdep/getkey.h b/pico/osdep/getkey.h new file mode 100644 index 00000000..98d52aa7 --- /dev/null +++ b/pico/osdep/getkey.h @@ -0,0 +1,36 @@ +/* + * $Id: getkey.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 PICO_OSDEP_GETKEY_INCLUDED +#define PICO_OSDEP_GETKEY_INCLUDED + + +#include + + +/* exported prototypes */ +UCS GetKey(void); +void kpinsert(char *, int, int); +#if TYPEAH +int typahead(void); +#endif /* TYPEAH */ +#ifndef _WINDOWS +UCS kbseq(int (*getcfunc)(int (*recorder)(int ), void (*bail_handler)(void )), + int (*recorder)(int), void (*bail_handler)(void), void *, UCS *); +void kbdestroy(KBESC_T *); +#endif + +#endif /* PICO_OSDEP_GETKEY_INCLUDED */ diff --git a/pico/osdep/makefile.wnt b/pico/osdep/makefile.wnt new file mode 100644 index 00000000..0eb3d1e7 --- /dev/null +++ b/pico/osdep/makefile.wnt @@ -0,0 +1,69 @@ +# $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 libpicoosd.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= #-D_UNICODE -DUNICODE + +LIBER=lib +LIBARGS=/nologo /verbose + +HFILES= ../../include/system.h ../../include/general.h \ + altedit.h chkpoint.h color.h filesys.h fsync.h \ + getkey.h mswin.h mswin_tw.h mswin_aspell.h mswin_spell.h mouse.h \ + newmail.h popen.h read.h \ + shell.h signals.h spell.h terminal.h truncate.h \ + tty.h + +OFILES= altedit.obj chkpoint.obj color.obj filesys.obj fsync.obj \ + getkey.obj msdlg.obj mswin.obj mswin_tw.obj mswin_aspell.obj mswin_spell.obj \ + mouse.obj newmail.obj popen.obj \ + read.obj shell.obj signals.obj spell.obj terminal.obj truncate.obj \ + tty.obj + +all: libpicoosd.lib mswin.res + +.c.obj: + $(CC) -c $(CFLAGS) "$(MAKEDIR)"\$*.c + +$(OFILES): $(HFILES) + +libpicoosd.lib: $(OFILES) + $(RM) libpicoosd.lib || rem + $(LIBER) /out:libpicoosd.lib $(OFILES) + +mswin.res: mswin.rc pico.ico mswinhnd.cur resource.h + $(RC) $(RCFLAGS) /fo mswin.res mswin.rc + +clean: + $(RM) *.lib + $(RM) *.obj + $(RM) mswin.res diff --git a/pico/osdep/mouse.c b/pico/osdep/mouse.c new file mode 100644 index 00000000..b671f8c1 --- /dev/null +++ b/pico/osdep/mouse.c @@ -0,0 +1,446 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: mouse.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 +#include + +#include "../headers.h" + +#include "getkey.h" + +#ifdef _WINDOWS +#include "mswin.h" +#endif + +#include "mouse.h" + +#ifdef MOUSE + + +#ifndef _WINDOWS + +/* useful definitions */ +#define XTERM_MOUSE_ON "\033[?1000h" /* DECSET with parm 1000 */ +#define XTERM_MOUSE_OFF "\033[?1000l" /* DECRST with parm 1000 */ + + +/* useful declarations */ +static int mexist = 0; /* is the mouse driver installed? */ +static unsigned mnoop; + + + +/* internal prototypes */ +void mouseon(void); +void mouseoff(void); + + + +/* + * init_mouse - check for xterm and initialize mouse tracking if present... + */ +int +init_mouse(void) +{ + if(mexist) + return(TRUE); + + if(getenv("DISPLAY")){ + mouseon(); + kpinsert("\033[M", KEY_XTERM_MOUSE, 1); + return(mexist = TRUE); + } + else + return(FALSE); +} + + +/* + * end_mouse - clear xterm mouse tracking if present... + */ +void +end_mouse(void) +{ + if(mexist){ + mexist = 0; /* just see if it exists here. */ + mouseoff(); + } +} + + +/* + * mouseexist - function to let outsiders know if mouse is turned on + * or not. + */ +int +mouseexist(void) +{ + return(mexist); +} + + +/* + * mouseon - call made available for programs calling pico to turn ON the + * mouse cursor. + */ +void +mouseon(void) +{ + fputs(XTERM_MOUSE_ON, stdout); +} + + +/* + * mouseon - call made available for programs calling pico to turn OFF the + * mouse cursor. + */ +void +mouseoff(void) +{ + fputs(XTERM_MOUSE_OFF, stdout); +} + + +/* + * checkmouse - look for mouse events in key menu and return + * appropriate value. + */ +int +checkmouse(unsigned long *ch, int down, int mcol, int mrow) +{ + static int oindex; + int i = 0, rv = 0; + MENUITEM *mp; + + if(!mexist || mcol < 0 || mrow < 0) + return(FALSE); + + if(down) /* button down */ + oindex = -1; + + for(mp = mfunc; mp; mp = mp->next) + if(mp->action && M_ACTIVE(mrow, mcol, mp)) + break; + + if(mp){ + unsigned long r; + + r = (*mp->action)(down ? M_EVENT_DOWN : M_EVENT_UP, + mrow, mcol, M_BUTTON_LEFT, 0); + if(r){ + *ch = r; + rv = TRUE; + } + } + else{ + while(1){ /* see if we understand event */ + if(i >= 12){ + i = -1; + break; + } + + if(M_ACTIVE(mrow, mcol, &menuitems[i])) + break; + + i++; + } + + if(down){ /* button down */ + oindex = i; /* remember where */ + if(i != -1 + && menuitems[i].label_hiliter != NULL + && menuitems[i].val != mnoop) /* invert label */ + (*menuitems[i].label_hiliter)(1, &menuitems[i]); + } + else{ /* button up */ + if(oindex != -1){ + if(i == oindex){ + *ch = menuitems[i].val; + rv = TRUE; + } + } + } + } + + /* restore label */ + if(!down + && oindex != -1 + && menuitems[oindex].label_hiliter != NULL + && menuitems[oindex].val != mnoop) + (*menuitems[oindex].label_hiliter)(0, &menuitems[oindex]); + + return(rv); +} + + +/* + * invert_label - highlight the label of the given menu item. + */ +void +invert_label(int state, MENUITEM *m) +{ + unsigned i, j; + int col_offset, savettrow, savettcol; + char *lp; + + get_cursor(&savettrow, &savettcol); + + /* + * Leave the command name bold + */ + col_offset = (state || !(lp=strchr(m->label, ' '))) ? 0 : (lp - m->label); + movecursor((int)m->tl.r, (int)m->tl.c + col_offset); + flip_inv(state); + + for(i = m->tl.r; i <= m->br.r; i++) + for(j = m->tl.c + col_offset; j <= m->br.c; j++) + if(i == m->lbl.r && j == m->lbl.c + col_offset && m->label){ + lp = m->label + col_offset; /* show label?? */ + while(*lp && j++ < m->br.c) + putc(*lp++, stdout); + + continue; + } + else + putc(' ', stdout); + + if(state) + flip_inv(FALSE); + + movecursor(savettrow, savettcol); +} + +#else /* _WINDOWS */ + +#define MOUSE_BUTTONS 3 + +static int mexist = 0; /* is the mouse driver installed? */ +static int nbuttons; /* number of buttons on the mouse */ +static unsigned mnoop; + +/* + * init_mouse - check for and initialize mouse driver... + */ + +int +init_mouse(void) +{ + nbuttons = MOUSE_BUTTONS; + return (mexist = TRUE); /* Mouse always exists under windows */ +} + +/* + * end_mouse - a no-op on Windows + */ +void +end_mouse(void) +{ +} + +/* + * mouseexist - function to let outsiders know if mouse is turned on + * or not. + */ +int +mouseexist(void) +{ + return(mexist); +} + +/* + * checkmouse - Check mouse and return maped command. + * + * EXPORTED to pico. + * NOTE: "down", "xxx", and "yyy" aren't used under windows. + */ +int +checkmouse (unsigned long *ch, int ddd, int xxx, int yyy) +{ + static int oindex; /* Index of previous mouse down. */ + int mcol; /* current mouse column */ + int mrow; /* current mouse row */ + unsigned long r; + int rv = 0; /* TRUE when we have something to return. */ + MEvent mouse; + int i = 0; + MENUITEM *mp; + + + *ch = 0; + + /* Mouse installed? */ + if (!mexist) + return (FALSE); + + if (!mswin_getmouseevent (&mouse)) + return (FALSE); + + + /* Location of mouse event. */ + mcol = mouse.nColumn; + mrow = mouse.nRow; + + + + /* + * If there is a tracking function it gets all the mouse events + * reguardless of where they occur. + */ + if (mtrack != NULL) { + r = mtrack (mouse.event, mrow, mcol, mouse.button, mouse.keys); + if (r & 0xffff){ + *ch = r; + rv = TRUE; + } + return (rv); + } + + + + + /* Mouse down or up? */ + if (mouse.event == M_EVENT_DOWN) { /* button down */ + oindex = -1; /* No Previous mouse down. */ + } + + + /* In special screen region? */ + for(mp = mfunc; mp; mp = mp->next) + if(mp->action && M_ACTIVE(mrow, mcol, mp)) + break; + + if(mp){ + + r = (*mp->action)(mouse.event, mrow, mcol, mouse.button, mouse.keys); + if (r){ + *ch = r; + rv = TRUE; + } + } + else if(mouse.button == M_BUTTON_LEFT){ + + /* In any of the menuitems? */ + while(1){ /* see if we understand event */ + if(i >= 12){ + i = -1; /* Not Found. */ + break; + } + + if(M_ACTIVE(mrow, mcol, &menuitems[i])) + break; /* Found. */ + + i++; /* Next. */ + } + + /* Now, was that a mouse down or mouse up? */ + if (mouse.event == M_EVENT_DOWN) { /* button down */ + oindex = i; /* remember where */ + if(i != -1){ /* invert label */ + if(menuitems[i].label_hiliter != NULL) + (*menuitems[i].label_hiliter)(1, &menuitems[i]); + else + invert_label(1, &menuitems[i]); + } + } + else if (mouse.event == M_EVENT_UP) {/* button up */ + if (oindex != -1) { /* If up in menu item. */ + if (i == oindex){ /* And same item down in. */ + *ch = menuitems[i].val; /* Return menu character. */ + rv = 1; + } + } + } + } + else if(mouse.button == M_BUTTON_RIGHT){ + if (mouse.event == M_EVENT_UP) { + mswin_keymenu_popup(); + } + } + + /* If this is mouse up AND there was a mouse down in a menu item + * then uninvert that menu item */ + if(mouse.event == M_EVENT_UP && oindex != -1){ + if(menuitems[oindex].label_hiliter != NULL) + (*menuitems[oindex].label_hiliter)(0, &menuitems[oindex]); + else + invert_label(0, &menuitems[oindex]); + } + + return(rv); +} + +/* + * invert_label - highlight the label of the given menu item. + */ +void +invert_label(int state, MENUITEM *m) +{ + int r, c; + unsigned i, j; + char *lp; + int old_state; + int wasShown; + int col_offset; + COLOR_PAIR *lastc = NULL; + if(m->val == mnoop) + return; + + + mswin_getpos (&r, &c); /* get cursor position */ + wasShown = mswin_showcaret (0); + old_state = mswin_getrevstate (); + /* + * Leave the command name bold + */ + col_offset = (state || !(lp=strchr(m->label, ' '))) ? 0 : (lp - m->label); + (*term.t_move)(m->tl.r, m->tl.c + col_offset); + if(state && m->kncp) + lastc = pico_set_colorp(m->kncp, PSC_REV|PSC_RET); + else if(!state && m->klcp) + lastc = pico_set_colorp(m->klcp, PSC_NORM|PSC_RET); + else + (*term.t_rev)(state); + + for(i = m->tl.r; i <= m->br.r; i++) { + for(j = m->tl.c + col_offset; j <= m->br.c; j++) { + if(i == m->lbl.r && j == m->lbl.c + col_offset){ /* show label?? */ + lp = m->label + col_offset; + while(*lp && j++ < m->br.c) + (*term.t_putchar)(*lp++); + + continue; + } + else + (*term.t_putchar)(' '); + } + } + + if(lastc){ + (void)pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + else + (*term.t_rev)(old_state); + mswin_move (r, c); + mswin_showcaret (wasShown); +} + + +#endif /* _WINDOWS */ + +#endif /* MOUSE */ diff --git a/pico/osdep/mouse.h b/pico/osdep/mouse.h new file mode 100644 index 00000000..db7946b3 --- /dev/null +++ b/pico/osdep/mouse.h @@ -0,0 +1,33 @@ +/* + * $Id: mouse.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 PICO_OSDEP_MOUSE_INCLUDED +#define PICO_OSDEP_MOUSE_INCLUDED + +#ifdef MOUSE + + +/* exported prototypes */ +int init_mouse(void); +void end_mouse(void); +int mouseexist(void); +int checkmouse(unsigned long *, int, int, int); +void invert_label(int, MENUITEM *); + + +#endif /* MOUSE */ + +#endif /* PICO_OSDEP_MOUSE_INCLUDED */ diff --git a/pico/osdep/msdlg.c b/pico/osdep/msdlg.c new file mode 100644 index 00000000..c4121dac --- /dev/null +++ b/pico/osdep/msdlg.c @@ -0,0 +1,1050 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: msdlg.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 + * + * ======================================================================== + */ + +/*--------------------------------------------------------------------------- + * + * Module: msdlg.c + * + * Thomas Unger + * Networks and Distributed Computing + * Computing and Communications + * University of Washington + * Administration Builiding, AG-44 + * Seattle, Washington, 98195, USA + * Internet: tunger@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-1998 by the University of Washington. + * + * The full text of our legal notices is contained in the file called + * CPYRIGHT, included with this distribution. + * + *--------------------------------------------------------------------------*/ + +#define WIN31 +#define STRICT + +#include +#include + +#include +#include + +#include "mswin.h" +#include "msmenu.h" +#include "resource.h" + +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../keydefs.h" +#include "../edef.h" +#include "../efunc.h" +#include "../utf8stub.h" +#include "../../pith/charconv/filesys.h" + + +extern HWND ghTTYWnd; +extern HINSTANCE ghInstance; +extern BOOL gfUseDialogs; +extern FILE *mswin_debugfile; +extern int mswin_debug; +extern TCHAR gszAppName[45]; + + + +#define BTN_FIRSTID 200 +#define BSPACE 4 +#define BWIDTH_MIN 120 + + +typedef struct { + LPTSTR prompt; /* Prompt. */ + LPTSTR string; /* Resulting string. */ + int strlen; /* Length of buffer. */ + int append; /* Append to existing string. */ + int passwd; /* Passwd, don't echo (1 use asterisk, 10 space). */ + unsigned flags; /* other flags. */ + int dflt; + int cancel; + int button_count; /* Number of additional buttons. */ + MDlgButton *button_list; /* List of other buttons. */ + char **helptext; /* Help text. */ + char helpkey; + int result; +} OEInfo; + + +static OEInfo gOEInfo; +static BOOL gDoHelpFirst; +static WNDPROC gpOldEditProc; /* Old Edit window proc. */ +static WNDPROC gpEditProc; +static WNDPROC gpOldBtnProc; /* Old Edit window proc. */ +static WNDPROC gpBtnProc; + + + + +/* + * Forward function declaratins. + */ +LONG FAR PASCAL __export EditProc(HWND hBtn, UINT msg, WPARAM wParam, + LPARAM lParam); +LONG FAR PASCAL __export ButtonProc(HWND hBtn, UINT msg, WPARAM wParam, + LPARAM lParam); +BOOL CALLBACK __export mswin_dialog_proc (HWND hDlg, UINT uMsg, + WPARAM wParam, LPARAM lParam); +BOOL CALLBACK __export mswin_select_proc (HWND hDlg, UINT uMsg, + WPARAM wParam, LPARAM lParam); +LONG FAR PASCAL __export KBCProc (HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam); + + +static void BuildButtonList (HWND, MDlgButton *, int, MDlgButton *); +static BOOL ProcessChar (HWND, WPARAM, OEInfo *, long *); +static void GetBtnPos (HWND, HWND, RECT *); + +int mswin_yesno_lptstr (LPTSTR); + + +int +mswin_usedialog (void) +{ + return (gfUseDialogs); +} + + + +/*---------------------------------------------------------------------- + + + Generic text entry. Prompt user for a string. + + + Args: + prompt -- The string to prompt with + string -- the buffer result is returned in, and original string (if + any) is passed in. + field_len -- Maximum length of string to accept + append_current -- flag indicating string should not be truncated before + accepting input + passwd -- a pass word is being fetch. Don't echo on screen + button_list -- pointer to array of mDlgButton's. input chars matching + those in list return value from list. Nearly identical + to Pine's ESCKEY structure. + help -- Arrary of strings for help text in bottom screen lines + flags -- flags + + Result: editing input string + returns -1 unexpected errors + returns 0 typed CR or pressed "OK" + returns 1 typed ESC or pressed "Cancel" + returns 3 typed ^G or PF1 (help) + returns 4 typed ^L for a screen redraw + or one of the other return values defined in button_list. + + 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. + + + Note About Help: We don't get the help text on the first call. If we + want help text we have to return 3. We'll get called again + with help text. To make it look good, we want to display + this the second time we're called. But we don't want to + display the help text every time we're called with help + text. To know the difference we set gDoHelpFirst when + exiting to request help text. If this is set then we + display help. If not, then no help. + + +----------------------------------------------------------------------*/ + + +/* + * xxx implement flags. + * xxx display.d uses flags QFFILE, QDEFLT, and QBOBUF + */ + +int +mswin_dialog(UCS *prompt, UCS *buf, int nbuf, int append_current, + int passwd, MDlgButton *button_list, char **help, unsigned flags) +{ + DLGPROC dlgprc; + int c; + LPTSTR promptlpt, buflpt, b; + UCS *ucs; + + mswin_flush(); + + promptlpt = ucs4_to_lptstr(prompt); + buflpt = (LPTSTR) fs_get(nbuf * sizeof(TCHAR)); + b = ucs4_to_lptstr(buf); + if(b){ + int i; + + for(i = 0; i < nbuf && b[i]; i++) + buflpt[i] = b[i]; + + if(i < nbuf) + buflpt[i] = 0; + + buflpt[nbuf-1] = 0; + fs_give((void **) &b); + } + + gOEInfo.prompt = promptlpt; + gOEInfo.string = buflpt; + gOEInfo.strlen = nbuf; + gOEInfo.append = append_current; + gOEInfo.passwd = passwd; + gOEInfo.helptext = help; + gOEInfo.helpkey = 0x07; /* ^G */ + gOEInfo.flags = flags; + gOEInfo.button_list = button_list; + gOEInfo.result = 0; + + for (c = 0; button_list && button_list[c].ch != -1; ++c) + ; + + gOEInfo.button_count = c; + + gpEditProc = (WNDPROC) MakeProcInstance((FARPROC) EditProc, ghInstance); + gpBtnProc = (WNDPROC) MakeProcInstance((FARPROC) ButtonProc, ghInstance); + dlgprc = (DLGPROC) MakeProcInstance((FARPROC) mswin_dialog_proc, ghInstance); + + DialogBox(ghInstance, MAKEINTRESOURCE (IDD_OPTIONALYENTER), ghTTYWnd, dlgprc); + + FreeProcInstance((FARPROC) dlgprc); + FreeProcInstance((FARPROC) gpBtnProc); + FreeProcInstance((FARPROC) gpEditProc); + + if(promptlpt) + fs_give((void **) &promptlpt); + + ucs = lptstr_to_ucs4(buflpt); + if(ucs){ + int i; + + for(i = 0; i < nbuf && ucs[i]; i++) + buf[i] = ucs[i]; + + if(i < nbuf) + buf[i] = '\0'; + + buf[nbuf-1] = '\0'; + fs_give((void **) &ucs); + } + + return(gOEInfo.result); +} + + + + +/* + * Dialog procedure for the generic text entry dialog box. + */ +BOOL CALLBACK __export +mswin_dialog_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + BOOL ret = FALSE; + int i; + int biCount; + HWND hEdit; + MDlgButton built_in[3]; + long l; + + + switch (uMsg) { + case WM_INITDIALOG: + /* + * Set the prompt text. + */ + SetDlgItemText(hDlg, IDC_PROMPT, gOEInfo.prompt); + + /* + * Set the initial edit text and configure the edit control + */ + if (!gOEInfo.append) + *gOEInfo.string = '\0'; + + SetDlgItemText(hDlg, IDC_RESPONCE, gOEInfo.string); + + SendDlgItemMessage (hDlg, IDC_RESPONCE, EM_SETPASSWORDCHAR, + (gOEInfo.passwd == 1) ? '*' : gOEInfo.passwd ? ' ' : 0, 0L); + SendDlgItemMessage (hDlg, IDC_RESPONCE, EM_LIMITTEXT, + gOEInfo.strlen - 1, 0L); + l = (long) lstrlen(gOEInfo.string); + SendDlgItemMessage(hDlg, IDC_RESPONCE, EM_SETSEL, 0, l); + hEdit = GetDlgItem (hDlg, IDC_RESPONCE); + SetFocus (hEdit); + gpOldEditProc = (WNDPROC) GetWindowLong (hEdit, GWL_WNDPROC); + SetWindowLong (hEdit, GWL_WNDPROC, (DWORD)(FARPROC)gpEditProc); + + + /* + * Subclass the standard buttons and build buttons for each item + * in the button_list. + */ + gpOldBtnProc = (WNDPROC) GetWindowLong (GetDlgItem (hDlg, IDOK), GWL_WNDPROC); + SetWindowLong (GetDlgItem (hDlg, IDOK), GWL_WNDPROC, + (DWORD)(FARPROC)gpBtnProc); + SetWindowLong (GetDlgItem (hDlg, IDCANCEL), GWL_WNDPROC, + (DWORD)(FARPROC)gpBtnProc); + + memset (&built_in, 0, sizeof (built_in)); + built_in[0].id = IDOK; + built_in[1].id = IDCANCEL; + if (1) { + built_in[2].id = IDC_GETHELP; + biCount = 3; + } + else { + DestroyWindow (GetDlgItem (hDlg, IDC_GETHELP)); + biCount = 2; + } + BuildButtonList (hDlg, built_in, biCount, gOEInfo.button_list); + + + if (gOEInfo.helptext && gDoHelpFirst) { + mswin_showhelpmsg ((WINHAND)hDlg, gOEInfo.helptext); + gDoHelpFirst = FALSE; + } + return (0); + + + case WM_ACTIVATE: + /* + * Keep focus on the edit item so key strokes are always + * received. + */ + SetFocus (GetDlgItem (hDlg, IDC_RESPONCE)); + break; + + + case WM_COMMAND: + switch (wParam) { + case IDOK: + /* + * Normal exit. Accept the new text. + */ + GetDlgItemText(hDlg, IDC_RESPONCE, gOEInfo.string, gOEInfo.strlen); + + gOEInfo.result = 0; + EndDialog (hDlg, gOEInfo.result); + ret = TRUE; + break; + + case IDCANCEL: + /* + * Cancel operation. Don't retreive new text. + */ + gOEInfo.result = 1; + EndDialog (hDlg, gOEInfo.result); + ret = TRUE; + break; + + case IDC_GETHELP: + /* + * Get help. If we have help text display it. If not, + * return value 3, which tells the caller to provide us + * with help text. + */ + if (gOEInfo.helptext != NULL) { + mswin_showhelpmsg ((WINHAND)hDlg, gOEInfo.helptext); + } + else { + GetDlgItemText(hDlg, IDC_RESPONCE, gOEInfo.string, gOEInfo.strlen); + gOEInfo.result = 3; + gDoHelpFirst = TRUE; + EndDialog (hDlg, gOEInfo.result); + } + ret = TRUE; + break; + + default: + /* + * Search button list for button with this ID. If found + * return it's result code. Retreive text. + */ + if ( wParam >= BTN_FIRSTID && + wParam < BTN_FIRSTID + (WPARAM) gOEInfo.button_count) { + GetDlgItemText(hDlg, IDC_RESPONCE, gOEInfo.string, gOEInfo.strlen); + i = wParam - BTN_FIRSTID; + gOEInfo.result = gOEInfo.button_list[i].rval; + EndDialog (hDlg, gOEInfo.result); + ret = TRUE; + } + } + + } + return (ret) ; +} + + + + +/* + * Subclassed window procedure for Edit control on generic text + * entry dialog box. + */ +LONG FAR PASCAL __export +EditProc (HWND hEdit, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hDlg; + LONG ret; + + hDlg = GetParent (hEdit); + + if (msg == WM_GETDLGCODE) { + /* + * Tell windows that we want to receive ALL keystrokes. + */ + ret = CallWindowProc (gpOldEditProc, hEdit, msg, wParam, lParam); + ret |= DLGC_WANTALLKEYS; + return (ret); + } + + + if (msg == WM_CHAR) { + /* + * See if the character typed is a shortcut for any of the + * buttons. + */ + if (ProcessChar (hDlg, wParam, &gOEInfo, &ret)) + return (ret); + + /* No... Fall through for deault processing. */ + } + + return (CallWindowProc (gpOldEditProc, hEdit, msg, wParam, lParam)); +} + + + + +/* + * Subclass button windows. + * + * These buttons will automatically return the input focus to + * a control with id IDC_RESPONCE. + */ +LONG FAR PASCAL __export +ButtonProc (HWND hBtn, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + HWND hDlg; + LONG ret; + + + if (uMsg == WM_LBUTTONUP || uMsg == WM_MBUTTONUP || uMsg == WM_RBUTTONUP) { + /* + * On mouse button up restore input focus to IDC_RESPONCE, which + * processes keyboard input. + */ + ret = CallWindowProc (gpOldBtnProc, hBtn, uMsg, wParam, lParam); + hDlg = GetParent (hBtn); + SetFocus (GetDlgItem (hDlg, IDC_RESPONCE)); + return (ret); + } + + return (CallWindowProc (gpOldBtnProc, hBtn, uMsg, wParam, lParam)); +} + + + +int +mswin_yesno(UCS *prompt_ucs4) +{ + int ret; + LPTSTR prompt_lptstr; + + prompt_lptstr = ucs4_to_lptstr(prompt_ucs4); + ret = mswin_yesno_lptstr(prompt_lptstr); + fs_give((void **) &prompt_lptstr); + + return(ret); +} + + +int +mswin_yesno_utf8(char *prompt_utf8) +{ + int ret; + LPTSTR prompt_lptstr; + + prompt_lptstr = utf8_to_lptstr(prompt_utf8); + ret = mswin_yesno_lptstr(prompt_lptstr); + fs_give((void **) &prompt_lptstr); + + return(ret); +} + +/* + * Ask a yes/no question with the MessageBox procedure. + * + * Returns: + * 0 - Cancel operation. + * 1 - "Yes" was selected. + * 2 - "No" was selected. + */ +int +mswin_yesno_lptstr(LPTSTR prompt) +{ + int ret; + + mswin_flush (); + + mswin_killsplash(); + + ret = MessageBox (ghTTYWnd, prompt, gszAppName, + MB_APPLMODAL | MB_ICONQUESTION | MB_YESNOCANCEL); + + switch (ret) { + case IDYES: ret = 1; break; + case IDNO: ret = 2; break; + default: + case IDCANCEL: ret = 0; break; + } + return (ret); +} + + +/*---------------------------------------------------------------------- + + + Generic selection routine. Display a prompt and a set of buttons for + each possible answer. + + + + Args: + prompt -- The string to prompt with. + button_list -- pointer to array of mDlgButton's. input chars + matching those in list return value from + mlist. Nearly identical to Pine's ESCKEY + mstructure. + dflt -- Value returned when "Enter" is pressed. + on_ctrl_C -- Value returned to cancel dialog (ESC). + help -- Arrary of strings for help text in bottom screen + lines + flags -- flags + + Result: + dflt -- Default option selected. + on_ctrl_C -- Calcel operation. + or one of the other return values defined in button_list. + + +Note: To prcess keyboard in put we use a custom dialog control + which is invisible but always has the input focus. + +----------------------------------------------------------------------*/ + +int +mswin_select(char *utf8prompt, MDlgButton *button_list, int dflt, int on_ctrl_C, + char **help, unsigned flags) +{ + WNDCLASS wndclass; + DLGPROC dlgprc; + WNDPROC kbcProc; + int c; + LPTSTR promptlpt; + + mswin_flush (); + + promptlpt = utf8_to_lptstr(utf8prompt); + + /* + * Setup dialog config structure. + */ + gOEInfo.prompt = promptlpt; + gOEInfo.string = NULL; + gOEInfo.strlen = 0; + gOEInfo.append = 0; + gOEInfo.passwd = 0; + gOEInfo.helptext = help; + gOEInfo.helpkey = 'g'; + gOEInfo.dflt = dflt; + gOEInfo.cancel = on_ctrl_C; + gOEInfo.flags = flags; + gOEInfo.button_list = button_list; + gOEInfo.result = 0; + + /* + * Count the buttons. + */ + for(c = 0; button_list && button_list[c].ch != -1; ++c) + ; + + gOEInfo.button_count = c; + + /* + * Register the class for the user control which will receive keyboard + * input events. + */ + kbcProc = (WNDPROC) MakeProcInstance((FARPROC) KBCProc, ghInstance); + wndclass.style = 0; + wndclass.lpfnWndProc = kbcProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = ghInstance; + wndclass.hIcon = NULL; + wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclass.hbrBackground = 0; + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = TEXT("KeyboardCapture"); + + if(RegisterClass (&wndclass) == 0){ + /* + * There is no good return value to indicate an error. + * Cancel the operation. + */ + return(on_ctrl_C); + } + + /* + * Make other procedure instances. + */ + dlgprc = (DLGPROC) MakeProcInstance((FARPROC) mswin_select_proc, ghInstance); + gpBtnProc = (WNDPROC) MakeProcInstance((FARPROC) ButtonProc, ghInstance); + + /* + * Bring up the dialog box. + */ + DialogBox(ghInstance, MAKEINTRESOURCE(IDD_SELECT), ghTTYWnd, dlgprc); + + /* + * Free the proc instances and window class. + */ + FreeProcInstance((FARPROC) dlgprc); + FreeProcInstance((FARPROC) gpBtnProc); + UnregisterClass (TEXT("KeyboardCapture"), ghInstance); + FreeProcInstance((FARPROC) kbcProc); + + if(promptlpt) + fs_give((void **) &promptlpt); + + return(gOEInfo.result); +} + + + + +/* + * Dialog procedure for the button selection dialog box. + */ +BOOL CALLBACK __export +mswin_select_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + BOOL ret = FALSE; + int i; + int biCount; + MDlgButton built_in[3]; + + if (mswin_debug >= 1) + fprintf (mswin_debugfile, "Select: uMsg x%x (%d), wParam x%x, lParam x%lx\n", + uMsg, uMsg, wParam, lParam); + + + switch (uMsg) { + case WM_INITDIALOG: + /* + * Show the prompt. + */ + SetDlgItemText(hDlg, IDC_PROMPT, gOEInfo.prompt); + + /* + * Set focus to the invisible custom control + */ + SetFocus (GetDlgItem (hDlg, IDC_RESPONCE)); + + /* + * Subclass the standard buttons and build buttons for each item + * in the button_list. + */ + gpOldBtnProc = (WNDPROC) GetWindowLong (GetDlgItem (hDlg, IDCANCEL), GWL_WNDPROC); + SetWindowLong (GetDlgItem (hDlg, IDC_GETHELP), GWL_WNDPROC, + (DWORD)(FARPROC)gpBtnProc); + SetWindowLong (GetDlgItem (hDlg, IDCANCEL), GWL_WNDPROC, + (DWORD)(FARPROC)gpBtnProc); + + memset (&built_in, 0, sizeof (built_in)); + built_in[0].id = IDCANCEL; + if (gOEInfo.helptext != NULL) { + built_in[1].id = IDC_GETHELP; + biCount = 2; + } + else { + DestroyWindow (GetDlgItem (hDlg, IDC_GETHELP)); + gOEInfo.helpkey = 0; + biCount = 1; + } + BuildButtonList (hDlg, built_in, biCount, gOEInfo.button_list); + gDoHelpFirst = FALSE; + return (0); + + + case WM_ACTIVATE: + /* + * Keep focus on the custom control item so key strokes are always + * received. + */ + SetFocus (GetDlgItem (hDlg, IDC_RESPONCE)); + break; + + + case WM_COMMAND: + switch (wParam) { + case IDOK: + gOEInfo.result = gOEInfo.dflt; + EndDialog (hDlg, gOEInfo.result); + ret = TRUE; + break; + + case IDCANCEL: + gOEInfo.result = gOEInfo.cancel; + EndDialog (hDlg, gOEInfo.result); + ret = TRUE; + break; + + case IDC_GETHELP: + /* + * Get help. If we have help text display it. If not, + * return value 3, which tells the caller to provide us + * with help text. + */ + if (gOEInfo.helptext != NULL) { + mswin_showhelpmsg ((WINHAND) hDlg, gOEInfo.helptext); + } + ret = TRUE; + break; + + default: + if ( wParam >= BTN_FIRSTID && + wParam < BTN_FIRSTID + (WPARAM) gOEInfo.button_count) { + i = wParam - BTN_FIRSTID; + gOEInfo.result = gOEInfo.button_list[i].rval; + EndDialog (hDlg, gOEInfo.result); + ret = TRUE; + } + } + + } + return (ret) ; +} + + + +/* + * Window procedure for the hidden custom control. + */ +LONG FAR PASCAL __export +KBCProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + LONG ret; + + + if (uMsg == WM_GETDLGCODE) { + /* + * Tell windows that we want all the character messages. + */ + ret = DefWindowProc (hWnd, uMsg, wParam, lParam); + ret |= DLGC_WANTALLKEYS; + return (ret); + } + + + if (uMsg == WM_CHAR) { + /* + * See if the character typed is a shortcut for any of the + * buttons. + */ + if (ProcessChar (GetParent (hWnd), wParam, &gOEInfo, &ret)) + return (ret); + + /* Fall through for default processing. */ + } + + return (DefWindowProc (hWnd, uMsg, wParam, lParam)); +} + + + + + + +/* + * Check if character activates any button. If it does, send WM_COMMAND + * to target window. + * + * Args: + * hWnd - Window to send message to. + * wParam - character typed. + * oeinfo - dialog config structure. Contains button list. + * ret - value to return to windows. + * + * Returns: + * TRUE - Found a match. Exit from Window Procedure returning + * *ret to windows. + * + * FALSE - No match. Continue with default processing of character + * message. + * + */ +static BOOL +ProcessChar (HWND hWnd, WPARAM wParam, OEInfo *oeinfo, long *ret) +{ + int i; + int id; + + + *ret = 0; + if (wParam == 0x0d) { + /* + * "Enter" is same as clicking on OK button. + */ + PostMessage (hWnd, WM_COMMAND, IDOK, + MAKELONG (GetDlgItem (hWnd, IDOK), 1)); + return (TRUE); + } + if (wParam == 0x1b || wParam == 0x03) { + /* + * Esc is same as clicking on Cancel button. + */ + PostMessage (hWnd, WM_COMMAND, IDCANCEL, + MAKELONG (GetDlgItem (hWnd, IDCANCEL), 1)); + return (TRUE); + } + + /* + * Any of the custom buttons respond to this key? + */ + for (i = 0; i < oeinfo->button_count; ++i) { + if (wParam == oeinfo->button_list[i].ch) { + id = oeinfo->button_list[i].id; + PostMessage (hWnd, WM_COMMAND, id, + MAKELONG (GetDlgItem (hWnd, id), 1)); + return (TRUE); + } + } + + /* + * Lastly, is it the help key? + */ + if (oeinfo->helpkey != 0 && wParam == oeinfo->helpkey) { + id = IDC_GETHELP; + PostMessage (hWnd, WM_COMMAND, id, + MAKELONG (GetDlgItem (hWnd, id), 1)); + return (TRUE); + } + + + /* + * Nothing we understand. + */ + return (FALSE); +} + + + + + +/* + * 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); +} + + + + +/* + * Add buttons to the dialog box. + */ +static void +BuildButtonList(HWND hDlg, MDlgButton *built_in, int biCount, MDlgButton *button_list) +{ + RECT rDlg, rb1, rb2, r; + HDC hdc; + MDlgButton *pB; /* pointer to button struct*/ + HWND hBtn, hBtn1, hBtn2; /* handle to buttons */ + int btnCount; /* extra button count */ + int rows, cols; /* button rows and columns */ + int bpos; /* button position */ + int i; + int maxstrIdx, maxstrLen; /* button w/ longest caption*/ + DWORD textExtent; /* width of button caption */ + int margine; /* left and right margines */ + int btop, bleft; /* button position */ + int bwidth, bheight, w; /* button size */ + int bMinWidth; /* minimum buttonwidth */ + int bvertSpace, bhorzSpace; /* button spacing */ + int newWHeight, delta; /* window resizing */ + TCHAR caption[128]; + size_t ncaption; + HFONT btnFont; + SIZE size; + + /* + * Are there any buttons to add? + */ + if(button_list == NULL) + return; + + maxstrIdx = 0; + for(btnCount = 0; button_list[btnCount].ch != -1; ++btnCount){ + if(lstrlen(button_list[btnCount].label) > + lstrlen(button_list[maxstrIdx].label)) + maxstrIdx = btnCount; + } + + if(btnCount == 0) + return; + + + /* + * Get the size of the dialog box and the positions of the + * first and, if there is one, the second button. Calculate or + * default button offsets and positions. + */ + GetClientRect(hDlg, &rDlg); + hBtn1 = GetDlgItem(hDlg, built_in[0].id); + GetBtnPos(hDlg, hBtn1, &rb1); + margine = rb1.left; /* left and right margine */ + bheight = rb1.bottom - rb1.top; /* button width */ + bwidth = rb1.right - rb1.left; /* button height. */ + if(biCount > 1){ + hBtn2 = GetDlgItem(hDlg, built_in[1].id); + GetBtnPos(hDlg, hBtn2, &rb2); + bvertSpace = rb2.top - rb1.top; /* vertical spacing */ + } + else{ + bvertSpace = bheight + BSPACE; /* vertical spacing */ + } + + + /* + * Get the button font. + */ + btnFont = (HFONT) SendMessage (hBtn1, WM_GETFONT, 0, 0); + + /* + * Get the screen extent of the longest button label. min width + * is the extent, plus the average width of 5 extra characters for + * key stroke, plus margine. + */ + hdc = GetDC (hBtn1); + ncaption = sizeof(caption)/sizeof(TCHAR); + _sntprintf(caption, ncaption, TEXT("%s '%s'"), button_list[maxstrIdx].label, + button_list[maxstrIdx].name); + + maxstrLen = lstrlen(caption); + GetTextExtentPoint32(hdc, caption, maxstrLen, &size); + + textExtent = size.cx; + + ReleaseDC(hBtn1, hdc); + bMinWidth = LOWORD (textExtent) + (2 * (LOWORD(textExtent) / maxstrLen)); + if(bwidth < bMinWidth) + bwidth = bMinWidth; + + /* + * Calculate button positions. If the buttons are going to extend + * past the right edge of the dialog box, shrink their width. But + * if they get narrower than the min width calculated above then + * increase the number of rows. + */ + rows = 1; + do { + ++rows; /* More rows. */ + w = bwidth; /* Original button width. */ + cols = 1 + ((biCount + btnCount) - 1) / rows; /* Calc num cols. */ + + /* Need to srink button width? */ + if (2 * margine + bwidth * cols + BSPACE * (cols - 1) > rDlg.right) { + w = ((rDlg.right - (2 * margine)) - (BSPACE * (cols-1))) / cols; + } + /* Did buttons get too narrow? */ + } while (w < bMinWidth && cols > 1); + + bwidth = w; + bhorzSpace = bwidth + BSPACE; /* horizontal spacing */ + + + /* + * Resize window. + */ + newWHeight = rb1.top + (rows * bvertSpace); + delta = newWHeight - rDlg.bottom; + if (delta != 0) { + GetWindowRect (hDlg, &r); + MoveWindow (hDlg, r.left, r.top, r.right - r.left, + (r.bottom - r.top) + delta, FALSE); + } + + + + /* + * Create new buttons. + */ + for(bpos = 0; bpos < biCount + btnCount; ++bpos) { + /* + * Calculate position for this button. + */ + btop = rb1.top + (bpos % rows) * bvertSpace; + bleft = rb1.left + (bpos / rows) * bhorzSpace; + + if(bpos < biCount){ + /* + * Resize existing buttons. + */ + MoveWindow(GetDlgItem(hDlg, built_in[bpos].id), + bleft, btop, bwidth, bheight, FALSE); + } + else{ + i = bpos - biCount; + pB = &button_list[i]; + + _sntprintf(caption, ncaption, TEXT("%s '%s'"), pB->label, pB->name); + hBtn = CreateWindow(TEXT("BUTTON"), caption, + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + bleft, btop, bwidth, bheight, + hDlg, NULL, ghInstance, NULL); + + pB->id = BTN_FIRSTID + i; + SetWindowLong(hBtn, GWL_ID, pB->id); + SendMessage(hBtn, WM_SETFONT, (WPARAM)btnFont, MAKELPARAM (0, 0)); + + /* Subclass button. */ + SetWindowLong(hBtn, GWL_WNDPROC, (DWORD)(FARPROC)gpBtnProc); + EnableWindow(hBtn, TRUE); + } + } +} diff --git a/pico/osdep/msmenu.h b/pico/osdep/msmenu.h new file mode 100644 index 00000000..0bf63c53 --- /dev/null +++ b/pico/osdep/msmenu.h @@ -0,0 +1,97 @@ +/* + * ======================================================================== + * 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 MSMENU_H +#define MSMENU_H + +#include "resource.h" + + +/* + * var in pine's key structure we'll use + */ +#define KS_OSDATAVAR short menuitem; +#define KS_OSDATAGET(X) ((X)->menuitem) +#define KS_OSDATASET(X, Y) ((X)->menuitem = (Y)) + +/* + * Menu key definitions. + * Should be same values as in resouce.h + * These are all in a range from KS_RANGESTART to KS_RANGEEND + * with no holes. + */ +#define KS_NONE 0 +#define KS_RANGESTART IDM_MI_VIEW + +#define KS_VIEW IDM_MI_VIEW +#define KS_EXPUNGE IDM_MI_EXPUNGE +#define KS_ZOOM IDM_MI_ZOOM +#define KS_SORT IDM_MI_SORT +#define KS_HDRMODE IDM_MI_HDRMODE +#define KS_MAINMENU IDM_MI_MAINMENU +#define KS_FLDRLIST IDM_MI_FLDRLIST +#define KS_FLDRINDEX IDM_MI_FLDRINDEX +#define KS_COMPOSER IDM_MI_COMPOSER +#define KS_PREVPAGE IDM_MI_PREVPAGE +#define KS_PREVMSG IDM_MI_PREVMSG +#define KS_NEXTMSG IDM_MI_NEXTMSG +#define KS_ADDRBOOK IDM_MI_ADDRBOOK +#define KS_WHEREIS IDM_MI_WHEREIS +#define KS_PRINT IDM_MI_PRINT +#define KS_REPLY IDM_MI_REPLY +#define KS_FORWARD IDM_MI_FORWARD +#define KS_BOUNCE IDM_MI_BOUNCE +#define KS_DELETE IDM_MI_DELETE +#define KS_UNDELETE IDM_MI_UNDELETE +#define KS_FLAG IDM_MI_FLAG +#define KS_SAVE IDM_MI_SAVE +#define KS_EXPORT IDM_MI_EXPORT +#define KS_TAKEADDR IDM_MI_TAKEADDR +#define KS_SELECT IDM_MI_SELECT +#define KS_APPLY IDM_MI_APPLY +#define KS_POSTPONE IDM_MI_POSTPONE +#define KS_SEND IDM_MI_SEND +#define KS_CANCEL IDM_MI_CANCEL +#define KS_ATTACH IDM_MI_ATTACH +#define KS_TOADDRBOOK IDM_MI_TOADDRBOOK +#define KS_READFILE IDM_MI_READFILE +#define KS_JUSTIFY IDM_MI_JUSTIFY +#define KS_ALTEDITOR IDM_MI_ALTEDITOR +#define KS_GENERALHELP IDM_MI_GENERALHELP +#define KS_SCREENHELP IDM_MI_SCREENHELP +#define KS_EXIT IDM_MI_EXIT +#define KS_NEXTPAGE IDM_MI_NEXTPAGE +#define KS_SAVEFILE IDM_MI_SAVEFILE +#define KS_CURPOSITION IDM_MI_CURPOSITION +#define KS_GOTOFLDR IDM_MI_GOTOFLDR +#define KS_JUMPTOMSG IDM_MI_JUMPTOMSG +#define KS_RICHHDR IDM_MI_RICHHDR +#define KS_EXITMODE IDM_MI_EXITMODE +#define KS_REVIEW IDM_MI_REVIEW +#define KS_KEYMENU IDM_MI_KEYMENU +#define KS_SELECTCUR IDM_MI_SELECTCUR +#define KS_UNDO IDM_MI_UNDO +#define KS_SPELLCHK IDM_MI_SPELLCHK + +#define KS_RANGEEND IDM_MI_SPELLCHK + +#define KS_COUNT ((KS_RANGEEND - KS_RANGESTART) + 1) + + + +#define MENU_DEFAULT 300 /* Default menu for application. */ +#define MENU_COMPOSER 301 /* Menu for pine's composer. */ + +#endif /* MSMENU_H */ diff --git a/pico/osdep/mswin.bmp b/pico/osdep/mswin.bmp new file mode 100644 index 00000000..e6882628 Binary files /dev/null and b/pico/osdep/mswin.bmp differ diff --git a/pico/osdep/mswin.c b/pico/osdep/mswin.c new file mode 100644 index 00000000..58d2ab3b --- /dev/null +++ b/pico/osdep/mswin.c @@ -0,0 +1,12448 @@ +/* + * ======================================================================== + * 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 + * + * ======================================================================== + */ + +#define WIN31 +#define STRICT + +#define termdef 1 /* don't define "term" external */ + +#include "../headers.h" + +#include +#include + +#include "mswin.h" +#include "resource.h" + +#include "../../pith/osdep/pipe.h" +#include "../../pith/osdep/canaccess.h" + +#include "../../pith/charconv/filesys.h" +#include "../../pith/charconv/utf8.h" + +#include "../../pith/filttype.h" +#include "../../pith/osdep/color.h" + +#include "mswin_tw.h" + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Defines + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + + +#define ICON + + +/* For debugging, export locals so debugger can see them. */ +#ifdef DEBUG +#define LOCAL +#else +#define LOCAL static +#endif + + +/* + * Define which debugging is desired. Generally only FDEBUG. + */ +#define FDEBUG /* Standard file debugging. */ + +#undef SDEBUG /* Verbose debugging of startup and windows handling*/ +#undef CDEBUG /* Verbose debugging of character input timeing. */ + +#undef OWN_DEBUG_FILE /* Define if we want to write to our own debug file, + * not pine's. */ + + +/* Max size permitted for the screen. */ +#define MAXNROW 200 +#define MAXNCOLUMN NLINE + +#define MINNROW 10 /* Minimum screen size */ +#define MINNCOLUMN 32 + +#define MARGINE_LEFT 3 +#define MARGINE_TOP 1 + +#define FRAME_3D_SIZE 1 + +#define WIN_MIN_X_SIZE 190 /* Minimum window size. */ +#define WIN_MIN_Y_SIZE 180 + +#define WIN_X_BORDER_SIZE 8 /* Space taked by window frame. */ +#define WIN_Y_BORDER_SIZE 65 + +#define FONT_MIN_SIZE 5 +#define FONT_MAX_SIZE 21 + +#define PRINT_TAB_SIZE 8 /* Tab size used by print code. */ + + +#define TB_HEIGHT 32 /* Tool Bar Height. */ +#define TB_BUTTONHEIGHT 16 /* Button Height. */ +#define TB_BUTTONSPACING 8 /* Space between buttons. */ + + +/* Some string lengths. */ +#define MAXLEN_TEMPSTR 256 /* Max size for temp storage. */ + +#define WIN_POS_STR_MAX_LEN 21 /* Max length for window-position + * string. */ + +#define MENU_ITEM_NAME_LEN 32 /* Menu item name lengths. */ + + +/* Length of keyboard input queue. */ +#define CHARACTER_QUEUE_LENGTH 32 +#define MOUSE_QUEUE_LENGTH 32 + +/* Number of resize callback functions we can keep track of. */ +#define RESIZE_CALLBACK_ARRAY_SIZE 3 + +/* Number of bytes held in the write accumulator. */ +#define WRITE_ACCUM_SIZE 200 + +/* Max time that may pass between calls to GetMessage. See mswin_charavail() + */ +#define GM_MAX_TIME 3000 /* In milliseconds.*/ + +/* My Timer Message */ +#define MY_TIMER_ID 33 +/* timeout period in miliseconds. */ +#define MY_TIMER_PERIOD (UINT)((IDLE_TIMEOUT + 1)*1000) +/***** We use variable my_timer_period now instead so that we can set + it differently when in pine and when in regular old pico. + We're not entirely sure we need it in pico, but we will leave + it there because we don't understand. + *****/ +#define MY_TIMER_SHORT_PERIOD (UINT)5000 /* used when there is a task in + the OnTask list. */ +#define MY_TIMER_VERY_SHORT_PERIOD (UINT)500 /* used when SIGALRM and alarm() + is set. */ +#define MY_TIMER_EXCEEDINGLY_SHORT_PERIOD (UINT)80 /* used when + gAllowMouseTracking is set */ + +#define TIMER_FAIL_MESSAGE TEXT("Failed to get all necessary Windows resources (timers). Alpine will run, but may not be able to keep the connection to the server alive. Quitting other applications and restarting Alpine may solve the problem.") + +/* + * Task bar notification Icon id and call back message for it. + */ +#define TASKBAR_ICON_NEWMAIL 1 +#define TASKBAR_ICON_MESSAGE WM_USER+1000 + + +/* + * Below here are fixed constancs that really should not be changed. + */ + +/* Auto Wrap States. */ +#define WRAP_OFF 0 /* Never wrap to next line. */ +#define WRAP_ON 1 /* Wrap to next line. */ +#define WRAP_NO_SCROLL 2 /* Wrap to next line but DON'T scroll + screen to do it. */ +/* Speicial keys in the Character Queue. */ +#define CQ_FLAG_DOWN 0x01 +#define CQ_FLAG_EXTENDED 0x02 +#define CQ_FLAG_ALT 0x04 + +/* Special ASCII characters. */ +#define ASCII_BEL 0x07 +#define ASCII_BS 0x08 +#define ASCII_TAB 0x09 +#define ASCII_LF 0x0A +#define ASCII_CR 0x0D +#define ASCII_XON 0x11 +#define ASCII_XOFF 0x13 + +/* Character Attributes. */ +#define CHAR_ATTR_NORM 0x00 /* Normal. */ +#define CHAR_ATTR_REV 0x01 /* Reverse Video. */ +#define CHAR_ATTR_BOLD 0x02 /* Reverse Video. */ +#define CHAR_ATTR_ULINE 0x04 /* Reverse Video. */ +#define CHAR_ATTR_SEL 0x08 /* Selected text. */ +#define CHAR_ATTR_NOT 0x80 /* No attributes. */ + +/* + * Different applications that we know about. + */ +#define APP_UNKNOWN 0 +#define APP_PICO 1 +#define APP_PICO_IDENT TEXT("pico") +#define APP_PINE 2 +#define APP_PINE_IDENT TEXT("pine") + +/* + * Control values for call to AccelCtl. + */ +/*#undef ACCELERATORS*/ +#define ACCELERATORS +#define ACCEL_UNLOAD 0 /* Unload the accelerators. */ +#define ACCEL_LOAD 1 /* Load the accelerators. */ +#define ACCEL_TOGGLE 2 /* Toggle the accelerators. */ + +/* + * flag bits to control which edit menu options can get lit + */ +#define EM_NONE 0L +#define EM_CP 0x0001 +#define EM_CP_APPEND 0x0002 +#define EM_FIND 0x0004 +#define EM_PST 0x0008 +#define EM_PST_ABORT 0x0010 +#define EM_CUT 0x0020 +#define EM_SEL_ALL 0x0040 + +#define EM_MAX_ACCEL 6 + +/* Offsets to objects in window extra storage. */ +#define GWL_PTTYINFO 0 /* Offset in Window extra storage. */ + +/* + * + */ +#define FONT_CHARSET_FONT DEFAULT_CHARSET + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Typedefs + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* Type that the screen array is made up of. */ +#define CHAR unsigned char + +/* define possible caret shapes */ +typedef enum { + CaretBlock = 0, + CaretSmallBlock, + CaretHorizBar, + CaretVertBar +} CARETS; + +/* Type that the attribute array is made up of. */ +typedef struct _character_attribute { + unsigned style:8; + DWORD rgbFG, rgbBG; +} CharAttrib; + +typedef int (*ResizeCallBackProc)(); +typedef int (*FileDropCallBackProc)(); +typedef short CORD; + +/* NOTE: There is currently code that assumes that CHAR and CharAttrib + * are one byte in size. All this code is flaged with a preceeding + * assert () */ + +/* Struct that defines command menu entries. */ +typedef struct tagMenuItem { + BOOL miActive; + UCS miKey; + short miIndex; + char *miLabel; +} MenuItem; + + +/* List of child window IDs and previous window procedures. */ +typedef struct tagBtnList { + WORD wndID; + WNDPROC wndProc; +} BtnList; + + +/* General info. */ +typedef struct tagTTYINFO { + TCHAR *pScreen; /* Screen. */ + int *pCellWidth; /* how wide to paint the char */ + CharAttrib *pAttrib; /* Attributes. */ + TCHAR writeAccum[WRITE_ACCUM_SIZE]; + int writeAccumCount; + CARETS cCaretStyle; /* Current caret's style */ + int scrollRange; /* Current scroll bar range. */ + long scrollPos; /* Current scroll position. */ + long scrollTo; /* Possition of last scroll to. */ + HFONT hTTYFont; + LOGFONT lfTTYFont; + DWORD rgbFGColor; /* Normal forground color. */ + DWORD rgbBGColor; /* Normal background color. */ + DWORD rgbRFGColor; /* Reverse forground color. */ + DWORD rgbRBGColor; /* Reverse background color */ + unsigned screenDirty:1; /* TRUE if screen needs update. */ + unsigned eraseScreen:1; /* TRUE if need to erase whole screen */ + unsigned fMinimized:1; /* True when window is minimized. */ + unsigned fMaximized:1; /* True when window is maximized. */ + unsigned fFocused:1; /* True when we have focus. */ + unsigned fNewLine:1; /* Auto LF on CR. */ + unsigned fMassiveUpdate:1;/* True when in Massive screen update. */ + unsigned fNewMailIcon:1; /* True when new mail has arrived. */ + unsigned fMClosedIcon:1; /* True when no mailbox is open. */ + unsigned fCursorOn:1; /* True when cursor's shown */ + unsigned fCaretOn:1; /* True if caret's displayed */ + unsigned fTrayIcon:1; /* True if tool tray icon's on */ + ResizeCallBackProc resizer[RESIZE_CALLBACK_ARRAY_SIZE]; + FileDropCallBackProc dndhandler; + int autoWrap; /* Auto wrap to next line. */ + CharAttrib curAttrib; /* Current character attributes. */ + int actNRow, actNColumn; /* Actual number of rows and comumns + * displayed. */ + CORD xSize, ySize; /* Size of screen in pixels */ + CORD xScroll, yScroll; /* ?? */ + CORD xOffset, yOffset; /* Offset from the left and top of + * window contents. */ + CORD nColumn, nRow; /* Current position of cursor in + * cells. */ + CORD xChar, yChar; /* Width of a char in pixels. */ + int yCurOffset; /* offset of cursor Y-size */ + CORD fDesiredSize; /* TRUE when there is a specific size + * the window should be expanded to + * after being minimized. */ + CORD xDesPos, yDesPos; /* Desired position. */ + CORD xDesSize, yDesSize; /* Desired window position. */ + int curWinMenu; /* Current window menu. */ + HACCEL hAccel; /* Handle to accelorator keys. */ + UINT fAccel; /* vector of bound accelerator keys. */ + CORD toolBarSize; /* Size of toolbar. */ + BOOL toolBarTop; /* Toolbar on top? */ + int curToolBarID; + BtnList *toolBarBtns; + HWND hTBWnd; + HBRUSH hTBBrush; + BOOL menuItemsCurrent; + BOOL noScrollUpdate; + BOOL scrollRangeChanged; + long noSUpdatePage; + long noSUpdateRange; + short menuItemsIndex; + MenuItem menuItems[KS_COUNT]; +} TTYINFO, *PTTYINFO ; + + +#define MAXCLEN (MAX(MAXCOLORLEN,RGBLEN+1)) +typedef struct MSWINColor { + char *colorName; + char *canonicalName; + COLORREF colorRef; +} MSWINColor; + + +typedef struct { + char *name; + CARETS style; +} MSWinCaret_t; + + +/* + * Entry in the OnTask list. This is a list of actions to perform + * when a task exits. Currently, the only thing we do is delete + * files. if that changes this structure will get more complex. + * + * hTask == NULL means "This program" and can be used to arrange for + * deletion of files when this program exits. + */ +typedef struct ontask { + struct ontask *next; + HTASK hTask; + char path[PATH_MAX+1]; +} OnTaskItem; + +typedef void (__cdecl *SignalType)(int); + +typedef struct _iconlist { + HICON hIcon; +/* char path[PATH_MAX]; */ +/* WORD index; */ + int id; + short row; + struct _iconlist *next; +} IconList; + +/* + * char * array for printing registry settings. + */ +typedef struct MSWR_LINE_BUFFER { + char **linep; /* store these as utf8, since that's what we have to pass back */ + unsigned long size; + unsigned long offset; +} MSWR_LINE_BUFFER_S; + + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Forward function declarations. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +LOCAL void * +MyGetWindowLongPtr(HWND hwnd, int nIndex) +{ + return (void *)(LONG_PTR)GetWindowLongPtr(hwnd, nIndex); +} + +LOCAL LONG_PTR +MySetWindowLongPtr(HWND hwnd, int nIndex, void *NewLongPtr) +{ +// warning C4244: 'function': conversion from 'LONG_PTR' to 'LONG', +// possible loss of data +#pragma warning(push) +#pragma warning(disable: 4244) + return SetWindowLongPtr(hwnd, nIndex, (LONG_PTR)NewLongPtr); +#pragma warning(pop) +} + +#ifdef WIN32 +#define GET_HINST( hWnd ) ((HINSTANCE) MyGetWindowLongPtr( hWnd, GWLP_HINSTANCE )) +#define GET_ID( hWnd ) (LOWORD(MyGetWindowLongPtr( hWnd, GWLP_ID ))) +#else +#define GET_HINST( hWnd ) ((HINSTANCE) GetWindowWord( hWnd, GWW_HINSTANCE )) +#define GET_ID( hWnd ) ((WORD) GetWindowWord( hWnd, GWW_ID )) +#endif + +#define CONSTRAIN(v,min,max) ((v) = (v) < (min) ? (min) : (v) > (max) ? (max) : (v)) + + +/* function prototypes (private) */ +int app_main (int argc, char *argv[]); + +void WinExit (void); + +LOCAL void mswin_invalidparameter(const wchar_t *, const wchar_t *, + const wchar_t *, unsigned int, uintptr_t); + +LOCAL BOOL InitApplication (HANDLE); +LOCAL HWND InitInstance (HANDLE, int); +LOCAL void MakeArgv (HINSTANCE hInstance, LPSTR cmdLine, int *pargc, + CHAR ***pargv); +LOCAL LRESULT NEAR CreateTTYInfo (HWND hWnd); +LOCAL BOOL NEAR DestroyTTYInfo (HWND hWnd); +LOCAL int ResizeTTYScreen (HWND hWnd, PTTYINFO pTTYInfo, + int newNRow, int newNColumn); +LOCAL BOOL ResetTTYFont (HWND, PTTYINFO, LOGFONT *); +LOCAL BOOL EraseTTY (HWND, HDC); +LOCAL BOOL PaintTTY (HWND); +LOCAL BOOL GetMinMaxInfoTTY (HWND hWnd, MINMAXINFO __far *lpmmi); +LOCAL BOOL AboutToSizeTTY (HWND hWnd, WINDOWPOS *winPos); +LOCAL BOOL SizeTTY (HWND, int, CORD, CORD); +LOCAL BOOL SizingTTY (HWND hWnd, int fwSide, LPRECT pRect); +LOCAL void FrameRect3D(HDC hdc, RECT * pRC, int width, BOOL raised); +LOCAL void FillRectColor(HDC hDC, RECT * pRC, COLORREF color); + + +LOCAL BOOL MoveTTY (HWND hWnd, int xPos, int yPos); +LOCAL void ScrollTTY (HWND hWnd, int wScrollCode, int nPos, HWND hScroll); +LOCAL void CaretTTY (HWND hWnd, CARETS cStyle); +LOCAL void CaretCreateTTY (HWND hWnd); +#ifdef WIN32 +LOCAL void MouseWheelTTY (HWND hWnd, int xPos, int yPos, + int fwKeys, int zDelta); +LOCAL void MouseWheelMultiplier (); +#endif +BOOL CALLBACK NoMsgsAreSent (void); +LOCAL BOOL SetTTYFocus (HWND); +LOCAL BOOL KillTTYFocus (HWND); +LOCAL BOOL MoveTTYCursor (HWND); +LOCAL BOOL ProcessTTYKeyDown (HWND hWnd, TCHAR bOut); +LOCAL BOOL ProcessTTYCharacter (HWND hWnd, TCHAR bOut); +LOCAL BOOL ProcessTTYMouse (HWND hWnd, int mevent, int button, CORD xPos, + CORD yPos, WPARAM keys); +LOCAL BOOL ProcessTTYFileDrop (HANDLE wParam); +LOCAL void ProcessTimer (void); +LOCAL BOOL WriteTTYBlock (HWND, LPTSTR, int); +LOCAL BOOL WriteTTYText (HWND, LPTSTR, int); +LOCAL BOOL WriteTTYChar (HWND, TCHAR); + +LOCAL VOID GoModalDialogBoxParam (HINSTANCE, LPTSTR, HWND, + DLGPROC, LPARAM); +LOCAL BOOL SelectTTYFont (HWND); +LOCAL void SetColorAttribute (COLORREF *cf, char *colorName); +LOCAL void SetReverseColor (void); +LOCAL BOOL ConvertRGBString (char *colorName, COLORREF *cf); +LOCAL char *ConvertStringRGB (char *colorName, size_t ncolorName, COLORREF colorRef); +LOCAL BOOL ScanInt (char *str, int min, int max, int *val); + +LOCAL void TBToggle (HWND); +LOCAL void TBPosToggle (HWND); +LOCAL void TBShow (HWND); +LOCAL void TBHide (HWND); +LOCAL void TBSwap (HWND, int); +LOCAL unsigned scrwidth(LPTSTR lpText, int nLength); +LOCAL long pscreen_offset_from_cord(int row, int col, PTTYINFO pTTYInfo); + +LOCAL int tcsucmp(LPTSTR o, LPTSTR r); +LOCAL int _print_send_page(void); + +/* defined in region.c */ +int copyregion(int f, int n); + + +#ifdef ACCELERATORS +#ifdef ACCELERATORS_OPT +LOCAL void AccelCtl (HWND hWnd, int ctl, BOOL saveChange); +#endif +LOCAL void AccelManage (HWND hWnd, long accels); +LOCAL void UpdateAccelerators (HWND hWnd); +#endif + +LOCAL UINT UpdateEditAllowed(HWND hWnd); +LOCAL void PopupConfig(HMENU hMenu, MPopup *members, int *n); +LOCAL MPopup *PopupId(MPopup *members, int id); +LOCAL BOOL CopyCutPopup (HWND hWnd, UINT fAllowed); + +LOCAL void SelStart (int nRow, int nColumn); +LOCAL void SelFinish (int nRow, int nColumn); +LOCAL void SelClear (void); +LOCAL void SelTrackXYMouse (int xPos, int yPos); +LOCAL void SelTrackMouse (int nRow, int nColumn); +LOCAL BOOL SelAvailable (void); +LOCAL void SelDoCopy (HANDLE hCB, DWORD lenCB); + +LOCAL void UpdateTrayIcon(DWORD dwMsg, HICON hIcon, LPTSTR tip); + +LOCAL void FlushWriteAccum (void); + +LOCAL int mswin_reg_lptstr(int, int, LPTSTR, size_t); + +LOCAL BOOL MSWRPoke(HKEY hKey, LPTSTR subkey, LPTSTR valstr, LPTSTR data); +LOCAL int MSWRClear(HKEY hKey, LPTSTR pSubKey); +LOCAL void MSWRAlpineSet(HKEY hRootKey, LPTSTR subkey, LPTSTR val, + int update, LPTSTR data); +LOCAL int MSWRProtocolSet(HKEY hKey, int type, LPTSTR path_lptstr); +LOCAL void MSWRAlpineSetHandlers(int update, LPTSTR path); +LOCAL int MSWRAlpineGet(HKEY hKey, LPTSTR subkey, LPTSTR val, + LPTSTR data_lptstr, size_t len); + +LOCAL void MSWIconAddList(int row, int id, HICON hIcon); +LOCAL int MSWIconPaint(int row, HDC hDC); +LOCAL void MSWIconFree(IconList **ppIcon); +LOCAL void MSWRLineBufAdd(MSWR_LINE_BUFFER_S *lpLineBuf, LPTSTR line); +LOCAL int MSWRDump(HKEY hKey, LPTSTR pSubKey, int keyDepth, + MSWR_LINE_BUFFER_S *lpLineBuf); + + + /* ... interface routines ... */ + + +void ProcessMenuItem (HWND hWnd, WPARAM wParam); + +void AlarmDeliver (void); +void HUPDeliver (void); + +void PrintFontSameAs (HWND hWnd); +void PrintFontSelect (HWND hWnd); +void ExtractFontInfo(LOGFONT *pFont, + LPTSTR fontName, size_t nfontName, + int *fontSize, + LPTSTR fontStyle, size_t nfontStyle, + int ppi, + LPTSTR fontCharSet, size_t nfontCharSet); + +LOCAL void DidResize (PTTYINFO pTTYInfo); + +LOCAL void UpdateMenu (HWND hWnd); +LOCAL void EditCut (void); +LOCAL void EditCopy (void); +LOCAL void EditCopyAppend (void); +LOCAL void EditDoCopyData (HANDLE hCB, DWORD lenCB); +LOCAL void EditPaste (void); +LOCAL void EditCancelPaste (void); +LOCAL UCS EditPasteGet (void); +LOCAL BOOL EditPasteAvailable (void); +LOCAL void EditSelectAll (void); + +LOCAL void MSWHelpShow (cbstr_t); +LOCAL int MSWHelpSetMenu (HMENU hmenu); + + +LOCAL void SortHandler (int order, int reverse); +LOCAL void FlagHandler (int order, int reverse); + +LOCAL void MyTimerSet (void); + +LOCAL LRESULT ConfirmExit (void); + +LOCAL void CQInit (void); +LOCAL BOOL CQAvailable (void); +LOCAL BOOL CQAdd (UCS c, BOOL fKeyControlDown); +LOCAL BOOL CQAddUniq (UCS c, BOOL fKeyControlDown); +LOCAL UCS CQGet (); + +LOCAL void MQInit (void); +LOCAL BOOL MQAvailable (void); +LOCAL BOOL MQAdd (int mevent, int button, int nRow, int nColumn, + int keys, int flags); +LOCAL BOOL MQGet (MEvent * pmouse); +LOCAL BOOL MQClear (int flag); + +LOCAL UCS mswin_getc (void); + +LOCAL DWORD ExplainSystemErr(void); + +LOCAL void RestoreMouseCursor(); + +LOCAL BYTE mswin_string2charsetid(LPTSTR); +LOCAL int mswin_charsetid2string (LPTSTR fontCharSet, + size_t nfontCharSet, BYTE lfCharSet); + +LOCAL int mswin_tw_init(MSWIN_TEXTWINDOW *mswin_tw, int id, + LPCTSTR title); +LOCAL MSWIN_TEXTWINDOW *mswin_tw_displaytext_lptstr (LPTSTR, LPTSTR, size_t, + LPTSTR *, MSWIN_TEXTWINDOW *mswin_tw, + int); +LOCAL void mswin_tw_print_callback(MSWIN_TEXTWINDOW *mswin_tw); + + +/* Functions exported to MS Windows. */ + +LRESULT FAR PASCAL __export PWndProc (HWND, UINT, WPARAM, LPARAM); +BOOL FAR PASCAL __export ToolBarProc (HWND, UINT, WPARAM, LPARAM); +LRESULT FAR PASCAL __export TBBtnProc (HWND, UINT, WPARAM, LPARAM); +BOOL FAR PASCAL __export AboutDlgProc (HWND, UINT, WPARAM, LPARAM); +BOOL FAR PASCAL __export SplashDlgProc (HWND, UINT, WPARAM, LPARAM); + +LOCAL HDDEDATA CALLBACK DdeCallback(UINT uType, UINT uFmt, HCONV hconv, + HSZ hsz1, HSZ hsz2, HDDEDATA hdata, + DWORD dwData1, DWORD dwData2); + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Module globals. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + + +PTTYINFO gpTTYInfo; +HWND ghTTYWnd; +HINSTANCE ghInstance; +BOOL gfUseDialogs; +FILE *mswin_debugfile = NULL; +int mswin_debug = 0; +TCHAR gszAppName[45]; + +LOCAL TCHAR TempBuf [MAXLEN_TEMPSTR]; + +LOCAL TCHAR gszTTYClass[] = TEXT("PineWnd"); +LOCAL int gAppIdent = APP_UNKNOWN; + +LOCAL int gNMW_width; + +LOCAL HCURSOR ghCursorArrow = NULL; +LOCAL HCURSOR ghCursorBusy = NULL; +LOCAL HCURSOR ghCursorIBeam = NULL; +LOCAL HCURSOR ghCursorHand = NULL; +LOCAL HCURSOR ghCursorCurrent = NULL; + +LOCAL HWND ghSplashWnd = NULL; + +LOCAL COLOR_PAIR *the_rev_color, *the_normal_color; + +/* Used for Pasting text. */ +LOCAL HANDLE ghPaste = NULL; /* Handle to Paste data. */ +LOCAL TCHAR *gpPasteNext = NULL; /* Pointer to current char. */ +LOCAL size_t gPasteBytesRemain = 0; /* Count of bytes left. */ +LOCAL BOOL gPasteWasCR = FALSE; /* Previous char was CR. */ +LOCAL int gPasteEnabled = MSWIN_PASTE_DISABLE; +LOCAL getc_t gCopyCutFunction = NULL; +LOCAL cbarg_t gScrollCallback = NULL; +LOCAL BOOL gScrolling = FALSE; /* Keeps track of when we are + * in scroll routine. */ +LOCAL cbarg_t gSortCallback = NULL; +LOCAL cbarg_t gFlagCallback = NULL; +LOCAL cbarg_t gHdrCallback = NULL; +LOCAL cbarg_t gZoomCallback = NULL; +LOCAL cbarg_t gFkeyCallback = NULL; +LOCAL cbarg_t gSelectedCallback = NULL; +LOCAL BOOL gMouseTracking = FALSE; /* Keeps track of when we are + * tracking the mouse. */ +LOCAL FARPROC gWSBlockingProc = NULL; +LOCAL DLGPROC gToolBarProc = NULL; +LOCAL WNDPROC gTBBtnProc = NULL; + +LOCAL BOOL gAllowCopy = FALSE; +LOCAL BOOL gAllowCut = FALSE; + +LOCAL BOOL gAllowMouseTrack = FALSE;/* Upper layer interested in + * mouse tracking. */ +LOCAL short gsMWMultiplier; +LOCAL MEvent gMTEvent; + +LOCAL cbstr_t gHelpGenCallback = NULL; +LOCAL BOOL gfHelpGenMenu = FALSE; /* TRUE when help menu + * installed. */ +LOCAL cbstr_t gHelpCallback = NULL; +LOCAL BOOL gfHelpMenu = FALSE; /* TRUE when help menu + * installed. */ +LOCAL char *gpCloseText; + +LOCAL DWORD gGMLastCall = 0; /* Last time I called + * GetMessage. */ +LOCAL BOOL gConfirmExit = FALSE; +LOCAL HICON ghNormalIcon = NULL; +LOCAL HICON ghNewMailIcon = NULL; +LOCAL HICON ghMClosedIcon = NULL; + +LOCAL int gPrintFontSize; +LOCAL TCHAR gPrintFontName[LF_FACESIZE]; +LOCAL TCHAR gPrintFontStyle[64]; +LOCAL TCHAR gPrintFontCharSet[256]; +LOCAL BOOL gPrintFontSameAs = TRUE; + +LOCAL UINT gTimerCurrentPeriod = 0; + +LOCAL cbvoid_t gPeriodicCallback = NULL; /* Function to call. */ +LOCAL DWORD gPeriodicCBTimeout = 0; /* Time of next call. */ +LOCAL DWORD gPeriodicCBTime = 0; /* Delay between calls. */ + +//========================================================================= +// n +//========================================================================= +MSWIN_TEXTWINDOW gMswinAltWin = {0}; +MSWIN_TEXTWINDOW gMswinNewMailWin = {0}; + +MSWIN_TEXTWINDOW gMswinIMAPTelem = {0}; +LOCAL cbvoid_t gIMAPDebugONCallback = NULL; +LOCAL cbvoid_t gIMAPDebugOFFCallback = NULL; +LOCAL cbvoid_t gEraseCredsCallback = NULL; +LOCAL cbvoid_t gViewInWindCallback = NULL; + +LOCAL cbvoid_t gConfigScreenCallback = NULL; + +LOCAL cbarg_t gMouseTrackCallback = NULL; + +/* Currently only implement one SIGNAL so only need single variable. */ +LOCAL SignalType gSignalAlarm = SIG_DFL; +LOCAL DWORD gAlarmTimeout = 0; /* Time alarm expires in + * seconds + * (GetTickCount()/1000) */ +LOCAL SignalType gSignalHUP = SIG_DFL; + +LOCAL IconList *gIconList = NULL; + + +/* + * There is some coordination between these names and the similar names + * in osdep/unix. The names in the left column are mapped to the colors + * in the right column when displaying. Note that the last eight colors map + * to the same thing as the 1st eight colors. This is an attempt to inter- + * operate with 16-color xterms. When editing a color and writing the color + * into the config file, we use the canonical_name (middle column). So if + * the user chooses green from the menu we write color010 in the config + * file. [This has changed. Now we put the RGB value in the config file + * instead. We leave this so that we can interpret color010 in the config + * file from old versions of pine.] + * We display that as green later. The xterm also displays that as + * green, because the bright green (color010) on the xterm is what we want + * to consider to be green, and the other one (color002) is dark green. + * And dark green sucks. + * On the PC we only use the first 11 colors in this table when giving the + * user the set color display, since the last eight are repeats. + */ +LOCAL MSWINColor MSWINColorTable[] = { + "black", "000,000,000", RGB(0,0,0), + "red", "255,000,000", RGB(255,0,0), + "green", "000,255,000", RGB(0,255,0), + "yellow", "255,255,000", RGB(255,255,0), + "blue", "000,000,255", RGB(0,0,255), + "magenta", "255,000,255", RGB(255,0,255), + "cyan", "000,255,255", RGB(0,255,255), + "white", "255,255,255", RGB(255,255,255), + "colorlgr", "192,192,192", RGB(192,192,192), /* lite gray */ + "colormgr", "128,128,128", RGB(128,128,128), /* med. gray */ + "colordgr", "064,064,064", RGB(64,64,64), /* dark gray */ + "color008", "000,000,000", RGB(0,0,0), + "color009", "255,000,000", RGB(255,0,0), + "color010", "000,255,000", RGB(0,255,0), + "color011", "255,255,000", RGB(255,255,0), + "color012", "000,000,255", RGB(0,0,255), + "color013", "255,000,255", RGB(255,0,255), + "color014", "000,255,255", RGB(0,255,255), + "color015", "255,255,255", RGB(255,255,255), + NULL, NULL, 0 +}; + +#define fullColorTableSize ((sizeof(MSWINColorTable) / sizeof(MSWINColorTable[0])) - 1) +#define visibleColorTableSize (fullColorTableSize - 8) + + +LOCAL MSWinCaret_t MSWinCaretTable[] = { + "Block", CaretBlock, + "ShortBlock", CaretSmallBlock, + "Underline", CaretHorizBar, + "VertBar", CaretVertBar, + NULL, 0 +}; + + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Windows Functions. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/*--------------------------------------------------------------------------- + * int PASCAL WinMain( HANDLE hInstance, HANDLE hPrevInstance, + * LPSTR lpszCmdLine, int nCmdShow ) + * + * Description: + * This is the main window loop! + * + * Parameters: + * As documented for all WinMain() functions. + * + *--------------------------------------------------------------------------*/ +int PASCAL +wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPTSTR lpszCmdLine, int nCmdShow ) +{ + CHAR **argv; + int argc, i, nosplash = 0; + char *cmdline_utf8; + + ghInstance = hInstance; + if (!hPrevInstance) + if (!InitApplication( hInstance )) + return ( FALSE ) ; + + /* + * Instead of crashing into Dr. Watson on invalid parameter calls + * into the C library, just return the error indication that these + * functions are normally expected to return. + */ + _set_invalid_parameter_handler (mswin_invalidparameter); + +#ifdef OWN_DEBUG_FILE /* Want to write to seperate memdebug.txt file. */ + mswin_debugfile = fopen ("memdebug.txt", "w"); + fprintf (mswin_debugfile, "Beginning of mswin debug log\n"); + if (mswin_debugfile != NULL) { + mswin_debug = 4; + MemDebug (mswin_debug, mswin_debugfile); + fprintf (mswin_debugfile, "Show window as: %d\n", nCmdShow); + fflush (mswin_debugfile); + } +#endif + + if (NULL == (ghTTYWnd = InitInstance (hInstance, nCmdShow))) + return (FALSE); + + /* cmdline_utf8 memory is never freed */ + cmdline_utf8 = lptstr_to_utf8(lpszCmdLine); + + MakeArgv (hInstance, cmdline_utf8, &argc, &argv); + + for(i = 0; i < argc; i++) + if(strcmp((const char *)argv[i], "-nosplash") == 0){ + nosplash = 1; + break; + } + /* Create Splash window */ + if(nosplash == 0){ + ghSplashWnd = CreateDialog(hInstance, + MAKEINTRESOURCE( SPLASHDLGBOX ), + ghTTYWnd, (DLGPROC)SplashDlgProc); + ShowWindow (ghSplashWnd, SW_SHOWNORMAL); + } + + atexit (WinExit); + + app_main (argc, (char **)argv); + + return (TRUE); +} + + +LOCAL void +mswin_invalidparameter(const wchar_t *expression, + const wchar_t *function, + const wchar_t *file, + unsigned int line, + uintptr_t reserved) +{ + /* do nothing for now */ +} + + +void +WinExit (void) +{ + MSG msg; + + if (ghTTYWnd == NULL) + return; + + UpdateTrayIcon(NIM_DELETE, 0, NULL); + + /* Destroy main window and process remaining events. */ + DestroyWindow (ghTTYWnd); + while (GetMessage (&msg, NULL, 0, 0)) { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + +#ifdef OWN_DEBUG_FILE + fclose (mswin_debugfile); +#endif + if (gWSBlockingProc != NULL) + FreeProcInstance (gWSBlockingProc); + + MemFreeAll (); + ghTTYWnd = NULL; +} + + +/*--------------------------------------------------------------------------- + * BOOL InitApplication( HANDLE hInstance ) + * + * Description: + * First time initialization stuff. This registers information + * such as window classes. + * + * Parameters: + * HANDLE hInstance + * Handle to this instance of the application. + * + *--------------------------------------------------------------------------*/ +LOCAL BOOL +InitApplication (HANDLE hInstance) +{ + WNDCLASS wndclass; + + /* + * Register tty window class. + */ + wndclass.style = 0; /* CS_NOCLOSE; */ + wndclass.lpfnWndProc = PWndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = sizeof (LONG); + wndclass.hInstance = hInstance ; + /* In win16 we paint our own icon. */ + wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (ALPINEICON)); + wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); + wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); + wndclass.lpszMenuName = MAKEINTRESOURCE (ALPINEMENU); + wndclass.lpszClassName = gszTTYClass ; + + return (RegisterClass (&wndclass)); +} + + +/*--------------------------------------------------------------------------- + * HWND InitInstance( HANDLE hInstance, int nCmdShow ) + * + * Description: + * Initializes instance specific information. + * + * Parameters: + * HANDLE hInstance + * Handle to instance + * + * int nCmdShow + * How do we show the window? + * +/*--------------------------------------------------------------------------*/ +LOCAL HWND +InitInstance (HANDLE hInstance, int nCmdShow) +{ + HWND hTTYWnd; + TCHAR appIdent[32]; + SCROLLINFO scrollInfo; + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "InitInstance::: entered, nCmdShow %d\n", + nCmdShow); +#endif + + LoadString (hInstance, IDS_APPNAME, gszAppName, sizeof(gszAppName) / sizeof(TCHAR)); + + /* create the TTY window */ + hTTYWnd = CreateWindow (gszTTYClass, gszAppName, + WS_OVERLAPPEDWINDOW | WS_VSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + HWND_DESKTOP, NULL, hInstance, NULL); + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_RANGE | SIF_POS; + scrollInfo.nMin = 0; + scrollInfo.nMax = 1; + scrollInfo.nPage = 1; + scrollInfo.nPos = 0; + SetScrollInfo(hTTYWnd, SB_VERT, &scrollInfo, FALSE); + EnableScrollBar (hTTYWnd, SB_VERT, ESB_DISABLE_BOTH); + + if (NULL == hTTYWnd) + return (NULL); + + ghTTYWnd = hTTYWnd; + + ghNormalIcon = LoadIcon (hInstance, MAKEINTRESOURCE (ALPINEICON)); + ghNewMailIcon = LoadIcon (hInstance, MAKEINTRESOURCE (NEWMAILICON)); + ghMClosedIcon = LoadIcon (hInstance, MAKEINTRESOURCE (MCLOSEDICON)); + + ghCursorArrow = LoadCursor (NULL, IDC_ARROW); + ghCursorBusy = LoadCursor (NULL, IDC_WAIT); + ghCursorIBeam = LoadCursor (NULL, IDC_IBEAM); +#ifdef IDC_HAND + ghCursorHand = LoadCursor (NULL, IDC_HAND); +#else + ghCursorHand = LoadCursor (hInstance, MAKEINTRESOURCE( PICOHAND )); +#endif + ghCursorCurrent = ghCursorArrow; + + MouseWheelMultiplier(); + + CQInit (); + MQInit (); + + + /* + * Load a resource with the name of the application. Compare to + * known applications to determine who we are running under. + * currently, only differentiation is the WINSOCK blocking hook. + */ + LoadString (hInstance, IDS_APPIDENT, appIdent, sizeof(appIdent) / sizeof(TCHAR)); + if (_tcscmp (appIdent, APP_PINE_IDENT) == 0) { + gAppIdent = APP_PINE; + gWSBlockingProc = MakeProcInstance ( (FARPROC) NoMsgsAreSent, + hInstance); + } + else if (_tcscmp (appIdent, APP_PICO_IDENT) == 0) + gAppIdent = APP_PICO; + else + gAppIdent = APP_UNKNOWN; + + + return (hTTYWnd); +} + + +/*--------------------------------------------------------------------------- + * void MakeArgv () + * + * Description: + * Build a standard C argc, argv pointers into the command line string. + * + * + * Parameters: + * cmdLine - Command line. + * *argc - Count of words. + * ***argc - Pointer to Pointer to array of pointers to + * characters. + * + *--------------------------------------------------------------------------*/ +LOCAL void +MakeArgv (HINSTANCE hInstance, char *cmdLine_utf8, int *pargc, CHAR ***pargv) +{ + CHAR **argv; + LPSTR c; + BOOL inWord, inQuote; + int wordCount; +#define CMD_PATH_LEN 128 + LPTSTR modPath_lptstr; + DWORD mpLen; + + + /* Count words in cmdLine. */ + wordCount = 0; + inWord = FALSE; + inQuote = FALSE; + for (c = cmdLine_utf8; *c != '\0'; ++c) { + if (inQuote) { + if(*c == '"' && (*(c+1) == ' ' || *(c+1) == '\t' || *(c+1) == '\0')){ + inQuote = inWord = FALSE; + } + } + else { + if(inWord && (*c == ' ' || *c == '\t' || *c == '\0')){ + inWord = FALSE; + } + else if(!inWord && (*c != ' ' && *c != '\t')){ + inWord = TRUE; + wordCount++; + if(*c == '"') + inQuote = TRUE; + } + } + } + + ++wordCount; /* One for program name. */ + argv = (CHAR **) MemAlloc (sizeof (CHAR *) * (wordCount + 1)); + *pargv = argv; + *pargc = wordCount; + + modPath_lptstr = (LPTSTR) MemAlloc (CMD_PATH_LEN*sizeof(TCHAR)); + mpLen = GetModuleFileName (hInstance, modPath_lptstr, CMD_PATH_LEN); + if (mpLen > 0) { + *(modPath_lptstr + mpLen) = '\0'; + *(argv++) = (unsigned char *)lptstr_to_utf8(modPath_lptstr); + } + else + *(argv++) = (unsigned char *)"Alpine/Pico"; + + MemFree((void *)modPath_lptstr); + + /* Now break up command line. */ + inWord = FALSE; + inQuote = FALSE; + for (c = cmdLine_utf8; *c != '\0'; ++c) { + if (inQuote) { + if(*c == '"' && (*(c+1) == ' ' || *(c+1) == '\t' || *(c+1) == '\0')){ + inQuote = inWord = FALSE; + *c = '\0'; + } + } + else { + if(inWord && (*c == ' ' || *c == '\t' || *c == '\0')){ + *c = '\0'; + inWord = FALSE; + } + else if(!inWord && (*c != ' ' && *c != '\t')){ + inWord = TRUE; + if(*c == '"'){ + inQuote = TRUE; + *(argv++) = (unsigned char *)c+1; + } + else + *(argv++) = (unsigned char *)c; + } + } + } + + *argv = NULL; /* tie off argv */ +} + + +/*--------------------------------------------------------------------------- + * LRESULT FAR PASCAL __export PWndProc( HWND hWnd, UINT uMsg, + * WPARAM wParam, LPARAM lParam ) + * + * Description: + * This is the TTY Window Proc. This handles ALL messages + * to the tty window. + * + * Parameters: + * As documented for Window procedures. + * +/*--------------------------------------------------------------------------*/ +LRESULT FAR PASCAL __export +PWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ +#ifdef CDEBUG + if (mswin_debug > 12) { + fprintf (mswin_debugfile, "PWndProc:: uMsg = 0x%x\n", uMsg); + fflush (mswin_debugfile); + } +#endif + switch (uMsg) { + case WM_CREATE: + MyTimerSet (); + return (CreateTTYInfo (hWnd)); + + case WM_COMMAND: + switch ((WORD) wParam) { + + case IDM_OPT_SETFONT: + SelectTTYFont (hWnd); + break ; + + case IDM_OPT_FONTSAMEAS: + PrintFontSameAs (hWnd); + break; + + case IDM_OPT_TOOLBAR: + TBToggle (hWnd); + break; + + case IDM_OPT_TOOLBARPOS: + TBPosToggle (hWnd); + break; + + case IDM_OPT_USEDIALOGS: { + PTTYINFO pTTYInfo; + + gfUseDialogs = !gfUseDialogs; + pTTYInfo = (PTTYINFO)(LONG_PTR)MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo) + DidResize (pTTYInfo); + break; + } + + case IDM_OPT_ERASE_CREDENTIALS: + if(gEraseCredsCallback) + (*gEraseCredsCallback)(); + break; + + case IDM_MI_VIEWINWIND: + if(gViewInWindCallback) + (*gViewInWindCallback)(); + break; + + case IDM_OPT_IMAPTELEM: + mswin_tw_init(&gMswinIMAPTelem, (int)LOWORD(wParam), + TEXT("IMAP Telemetry")); + SetFocus (ghTTYWnd); + break; + + case IDM_OPT_NEWMAILWIN: + mswin_tw_init(&gMswinNewMailWin, (int)LOWORD(wParam), + TEXT("New Mail View")); + SetFocus (ghTTYWnd); + break; + +#ifdef ACCELERATORS_OPT + case IDM_OPT_USEACCEL: + AccelCtl (hWnd, ACCEL_TOGGLE, TRUE); + break; +#endif + + case IDM_OPT_SETPRINTFONT: + PrintFontSelect (hWnd); + break; + + case IDM_OPT_CARETBLOCK : + CaretTTY(hWnd, CaretBlock); + break; + + case IDM_OPT_CARETSMALLBLOCK : + CaretTTY(hWnd, CaretSmallBlock); + break; + + case IDM_OPT_CARETVBAR : + CaretTTY(hWnd, CaretVertBar); + break; + + case IDM_OPT_CARETHBAR : + CaretTTY(hWnd, CaretHorizBar); + break; + + case IDM_ABOUT: + GoModalDialogBoxParam ( GET_HINST( hWnd ), + MAKEINTRESOURCE( ABOUTDLGBOX ), + hWnd, + (DLGPROC)AboutDlgProc, (LPARAM) 0 ) ; + break; + + case IDM_EDIT_CUT: + EditCut (); + break; + + case IDM_EDIT_COPY: + EditCopy (); + break; + + case IDM_EDIT_COPY_APPEND: + EditCopyAppend (); + break; + + case IDM_EDIT_PASTE: + EditPaste (); +#ifdef ACCELERATORS + UpdateAccelerators (hWnd); +#endif + break; + + case IDM_EDIT_CANCEL_PASTE: + EditCancelPaste (); +#ifdef ACCELERATORS + UpdateAccelerators (hWnd); +#endif + break; + + case IDM_EDIT_SEL_ALL : + EditSelectAll(); + break; + + case IDM_HELP: + MSWHelpShow (gHelpCallback); + break; + + case IDM_MI_GENERALHELP: + MSWHelpShow (gHelpGenCallback); + break; + + case IDM_MI_WHEREIS : + CQAdd (gpTTYInfo->menuItems[KS_WHEREIS - KS_RANGESTART].miKey, 0); + break; + + case IDM_MI_SORTSUBJECT : + case IDM_MI_SORTARRIVAL : + case IDM_MI_SORTSIZE : + case IDM_MI_SORTFROM : + case IDM_MI_SORTTO : + case IDM_MI_SORTCC : + case IDM_MI_SORTDATE : + case IDM_MI_SORTORDERSUB : + case IDM_MI_SORTSCORE : + case IDM_MI_SORTTHREAD : + SortHandler((int)(wParam - IDM_MI_SORTSUBJECT), 0); + break; + + case IDM_MI_SORTREVERSE : + SortHandler(-1, 1); + break; + + case IDM_MI_FLAGIMPORTANT : + case IDM_MI_FLAGNEW : + case IDM_MI_FLAGANSWERED : + case IDM_MI_FLAGDELETED : + FlagHandler((int)(wParam - IDM_MI_FLAGIMPORTANT), 0); + break; + + default: + /* value falling within the menu item range are handled here. */ + if (wParam >= KS_RANGESTART && wParam <= KS_RANGEEND){ + ProcessMenuItem (hWnd, wParam); + break; + } + break; + } + break ; + + case WM_VSCROLL: + ScrollTTY (hWnd, LOWORD(wParam), HIWORD(wParam), (HWND) lParam); + break; + + case WM_MOUSEWHEEL: + MouseWheelTTY (hWnd, LOWORD(lParam), HIWORD(lParam), + LOWORD(wParam), (short) HIWORD(wParam)); + break; + + case WM_ERASEBKGND: + if (IsIconic (hWnd)) + return (DefWindowProc (hWnd, WM_ICONERASEBKGND, wParam, lParam)); + else + EraseTTY (hWnd, (HDC) wParam); + break; + + case WM_QUERYDRAGICON: + return ((LRESULT)ghNormalIcon); + + case WM_PAINT: + PaintTTY (hWnd); + break ; + + case WM_GETMINMAXINFO: + GetMinMaxInfoTTY (hWnd, (MINMAXINFO __far *)lParam); + break; + + case WM_SIZE: + SizeTTY (hWnd, (int)wParam, HIWORD(lParam), LOWORD(lParam)); + break ; + + case WM_SIZING : + return(SizingTTY(hWnd, (int)wParam, (LPRECT) lParam)); + + case WM_MOVE: +/* MoveTTY (hWnd, (int) LOWORD(lParam), (int) HIWORD(lParam)); */ + break; + + case WM_WINDOWPOSCHANGING: + /* Allows us to adjust new size of window. */ + AboutToSizeTTY (hWnd, (WINDOWPOS FAR *) lParam); + break; + + + case TASKBAR_ICON_MESSAGE: + /* Notification of a mouse event in the task bar tray. + * If they are clicking on it we will restore the window. */ + if (lParam == WM_LBUTTONDOWN){ + if(gpTTYInfo->fMinimized) + ShowWindow (hWnd, SW_RESTORE); + else + SetForegroundWindow(hWnd); + } + + break; + + + /* + * WM_KEYDOWN is sent for every "key press" and reports on the + * keyboard key, with out processing shift and control keys. + * WM_CHAR is a synthetic event, created from KEYDOWN and KEYUP + * events. It includes processing or control and shift characters. + * But does not get generated for extended keys suchs as arrow + * keys. + * I'm going to try to use KEYDOWN for processing just extended keys + * and let CHAR handle the the rest. + * + * The only key combo that is special is ^-space. For that, I'll use + * WM_KEYDOWN and WM_KEYUP to track the state of the control key. + */ + case WM_CHAR: + /* + * If the windows cursor is visible and we have keyboard input + * then hide the windows cursor + */ + mswin_showcursor(FALSE); + ProcessTTYCharacter (hWnd, (TCHAR)wParam); + break ; + + case WM_KEYDOWN: + if (ProcessTTYKeyDown (hWnd, (TCHAR) wParam)) + return (0); + + return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ; + + case WM_SYSCHAR: + if (gFkeyCallback && (*gFkeyCallback)(0, 0) + && LOBYTE (wParam) == VK_F10){ + ProcessTTYCharacter (hWnd, (TCHAR)wParam); + return (0); + } + + return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ; + + case WM_SYSKEYDOWN: + /* + * lParam specifies the context code. Bit 29 is 1 if the ALT key is down + * while the key is pressed. + */ + if (!(lParam & (1<<29)) + && gFkeyCallback && (*gFkeyCallback)(0, 0) + && LOBYTE (wParam) == VK_F10 + && ProcessTTYKeyDown (hWnd, (TCHAR) wParam)) + return (0); + + return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ; + + + case WM_LBUTTONDOWN: + ProcessTTYMouse (hWnd, M_EVENT_DOWN, M_BUTTON_LEFT, LOWORD (lParam), + HIWORD (lParam), wParam); + break; + + case WM_LBUTTONUP: + if (ProcessTTYMouse (hWnd, M_EVENT_UP, M_BUTTON_LEFT, LOWORD (lParam), + HIWORD (lParam), wParam)) + goto callDef; + break; + + case WM_MBUTTONDOWN: + ProcessTTYMouse (hWnd, M_EVENT_DOWN, M_BUTTON_MIDDLE, LOWORD (lParam), + HIWORD (lParam), wParam); + break; + + case WM_MBUTTONUP: + ProcessTTYMouse (hWnd, M_EVENT_UP, M_BUTTON_MIDDLE, LOWORD (lParam), + HIWORD (lParam), wParam); + break; + + case WM_RBUTTONDOWN: + ProcessTTYMouse (hWnd, M_EVENT_DOWN, M_BUTTON_RIGHT, LOWORD (lParam), + HIWORD (lParam), wParam); + break; + + case WM_RBUTTONUP: + ProcessTTYMouse (hWnd, M_EVENT_UP, M_BUTTON_RIGHT, LOWORD (lParam), + HIWORD (lParam), wParam); + break; + + case WM_MOUSEMOVE: + ProcessTTYMouse (hWnd, M_EVENT_TRACK, 0, LOWORD (lParam), + HIWORD (lParam), wParam); + break; + + case WM_NCMOUSEMOVE: + mswin_showcursor(TRUE); + goto callDef; /* pretend it never happened */ + + case WM_SETFOCUS: + SetTTYFocus (hWnd); + break; + + case WM_KILLFOCUS: + KillTTYFocus (hWnd); + break; + + case WM_SETCURSOR: + /* Set cursor. If in client, leave as is. Otherwise, pass to + * DefWindow Proc */ + if (LOWORD(lParam) == HTCLIENT) { + SetCursor (ghCursorCurrent); + return (TRUE); + } + + return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ; + + case WM_INITMENU: + UpdateMenu (hWnd); + break; + + case WM_TIMER: + /* Really just used so that we continue to receive messages even while + * in background. Causes mswin_getc() to process message and return + * to caller so that it can get some periodic processing in. */ + ProcessTimer (); + break; + + case WM_QUERYENDSESSION: + /* Returns non-zero if I can exit, otherwize zero, and the end + * session operation stops. */ + return ((LRESULT)ConfirmExit ()); + + case WM_DESTROY: + KillTimer (hWnd, MY_TIMER_ID); + DestroyTTYInfo (hWnd); + PostQuitMessage (0); + break; + + + case WM_DROPFILES: + if(ProcessTTYFileDrop((HANDLE) wParam) == TRUE) + SetForegroundWindow(hWnd); + + break; + + + case WM_CLOSE: + /* If the quit menu is active then insert the quit command + * Otherwise, abort. */ + if (gpTTYInfo->menuItems[KS_EXIT - KS_RANGESTART].miActive) { + CQAdd (gpTTYInfo->menuItems[KS_EXIT - KS_RANGESTART].miKey, 0); + } + else if (gSignalHUP != SIG_DFL && gSignalHUP != SIG_IGN) { + if (MessageBox (hWnd, + TEXT("Abort PINE/PICO, possibly losing current work?"), + gszAppName, MB_OKCANCEL | MB_ICONQUESTION) == IDOK) + HUPDeliver (); + } + break; + + + default: +callDef: + return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ; + } + return 0L ; + +} /* end of PWndProc() */ + + +/*--------------------------------------------------------------------------- + * LRESULT NEAR CreateTTYInfo( HWND hWnd ) + * + * Description: + * Creates the tty information structure and sets + * menu option availability. Returns -1 if unsuccessful. + * + * Parameters: + * HWND hWnd + * Handle to main window. + * + *-------------------------------------------------------------------------*/ +LOCAL LRESULT NEAR +CreateTTYInfo (HWND hWnd) +{ + HMENU hMenu; + PTTYINFO pTTYInfo; + LOGFONT newFont; + int i, ppi; + HDC hDC; + HFONT testFont; +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "CreateTTYInfo::: entered\n"); +#endif + + hDC = GetDC (ghTTYWnd); + ppi = GetDeviceCaps (hDC, LOGPIXELSY); + ReleaseDC (ghTTYWnd, hDC); + + pTTYInfo = (PTTYINFO) MemAlloc (sizeof (TTYINFO)); + if (pTTYInfo == NULL) + return ((LRESULT) - 1); + gpTTYInfo = pTTYInfo; + + /* initialize TTY info structure */ + memset (pTTYInfo, 0, sizeof (TTYINFO)); + /* Shown but not focused. */ + pTTYInfo->cCaretStyle = CaretBlock; + pTTYInfo->fMinimized = FALSE; + pTTYInfo->fMaximized = FALSE; + pTTYInfo->fFocused = FALSE; + pTTYInfo->fNewLine = FALSE; + pTTYInfo->fMassiveUpdate = FALSE; + pTTYInfo->fNewMailIcon = FALSE; + pTTYInfo->fMClosedIcon = FALSE; + pTTYInfo->autoWrap = WRAP_NO_SCROLL; + pTTYInfo->xOffset = MARGINE_LEFT; + pTTYInfo->yOffset = MARGINE_TOP; + pTTYInfo->fDesiredSize = FALSE; + pTTYInfo->fCursorOn = TRUE; + pico_nfcolor(NULL); + pico_nbcolor(NULL); + pico_rfcolor(NULL); + pico_rbcolor(NULL); + pico_set_normal_color(); + pTTYInfo->toolBarTop = TRUE; + pTTYInfo->curToolBarID = IDD_TOOLBAR; + + /* Clear menu item array. */ + pTTYInfo->curWinMenu = ALPINEMENU; + for (i = 0; i < KS_COUNT; ++i) + pTTYInfo->menuItems[i].miActive = FALSE; + pTTYInfo->menuItemsCurrent = FALSE; + + /* Clear resize callback procs. */ + for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) + pTTYInfo->resizer[i] = NULL; + + + /* clear screen space */ + pTTYInfo->pScreen = NULL; + pTTYInfo->pCellWidth = NULL; + pTTYInfo->pAttrib = NULL; + + /* setup default font information */ + + newFont.lfHeight = -MulDiv(12, ppi, 72); + newFont.lfWidth = 0; + newFont.lfEscapement = 0; + newFont.lfOrientation = 0; + newFont.lfWeight = 0; + newFont.lfItalic = 0; + newFont.lfUnderline = 0; + newFont.lfStrikeOut = 0; + newFont.lfCharSet = FONT_CHARSET_FONT; + newFont.lfOutPrecision = OUT_DEFAULT_PRECIS; + newFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + newFont.lfQuality = DEFAULT_QUALITY; + newFont.lfPitchAndFamily = FIXED_PITCH | FF_MODERN; + _sntprintf(newFont.lfFaceName, LF_FACESIZE, TEXT("%s"), TEXT("Courier New")); + testFont = CreateFontIndirect(&newFont); + if(NULL == testFont) + newFont.lfFaceName[0] = '\0'; + else + DeleteObject(testFont); + + /* set TTYInfo handle before any further message processing. */ + + MySetWindowLongPtr (hWnd, GWL_PTTYINFO, pTTYInfo); + + /* reset the character information, etc. */ + + ResetTTYFont (hWnd, pTTYInfo, &newFont); + + + + hMenu = GetMenu (hWnd); + EnableMenuItem (hMenu, IDM_EDIT_CUT, MF_BYCOMMAND | MF_GRAYED); + EnableMenuItem (hMenu, IDM_EDIT_COPY, MF_BYCOMMAND | MF_GRAYED); + EnableMenuItem (hMenu, IDM_EDIT_COPY_APPEND, MF_BYCOMMAND | MF_GRAYED); + EnableMenuItem (hMenu, IDM_EDIT_PASTE, MF_BYCOMMAND | MF_GRAYED); + return ((LRESULT) TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL NEAR DestroyTTYInfo( HWND hWnd ) + * + * Description: + * Destroys block associated with TTY window handle. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + *-------------------------------------------------------------------------*/ +LOCAL BOOL NEAR +DestroyTTYInfo (HWND hWnd) +{ + PTTYINFO pTTYInfo; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + +#ifdef ACCELERATORS + if(pTTYInfo->hAccel){ + DestroyAcceleratorTable(pTTYInfo->hAccel); + pTTYInfo->hAccel = NULL; + pTTYInfo->fAccel = EM_NONE; + } +#endif + + if(pTTYInfo->hTBWnd != NULL) + DestroyWindow (pTTYInfo->hTBWnd); + + if(pTTYInfo->hTBBrush != NULL) + DeleteObject(pTTYInfo->hTBBrush); + + DeleteObject (pTTYInfo->hTTYFont); + + MemFree (pTTYInfo); + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * void ResizeTTYScreen( HWND hWnd, PTTYINFO pTTYInfo, + * int newNrow, int newNColumn); + * + * Description: + * Resize the screen to new size, copying data. + * + * Parameters: + * PTTYINFO pTTYInfo + * pointer to TTY info structure + * newNCo.umn, newNRow + * new size of screen. + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +ResizeTTYScreen (HWND hWnd, PTTYINFO pTTYInfo, int newNRow, int newNColumn) +{ + CharAttrib *pNewAttrib, tmpAttrib, *pSourceAtt, *pDestAtt; + TCHAR *pNewScreen, *pSource, *pDest; + int *pNewCW, *pSourceCW, *pDestCW; + size_t len; + int cells; + int r, i; + extern TERM term; + + + if (newNColumn < MINNCOLUMN) + newNColumn = MINNCOLUMN; + if (newNRow < MINNROW) + newNRow = MINNROW; + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "ResizeTTYScreen::: entered, new row %d, col %d\n", + newNRow, newNColumn); +#endif + + + SelClear (); + cells = newNColumn * newNRow; + pNewScreen = (TCHAR *)MemAlloc (cells * sizeof (TCHAR)); + if (pNewScreen == NULL) + return (FALSE); + + pNewCW = (int *)MemAlloc(cells * sizeof(int)); + if(pNewCW == NULL){ + MemFree((void *)pNewScreen); + return(FALSE); + } + + pNewAttrib = (CharAttrib *)MemAlloc (cells * sizeof (CharAttrib)); + if (pNewAttrib == NULL) { + MemFree ((void *)pNewScreen); + MemFree ((void *)pNewCW); + return (FALSE); + } + + + /* + * Clear new screen. + */ + + for(i = 0; i < cells; i++){ + pNewScreen[i] = ' '; + pNewCW[i] = pTTYInfo->xChar; /* xChar set yet ? */ + } + + tmpAttrib.style = CHAR_ATTR_NORM; + tmpAttrib.rgbFG = pTTYInfo->rgbFGColor; + tmpAttrib.rgbBG = pTTYInfo->rgbBGColor; + for(r = 0; r < cells; r++) + pNewAttrib[r] = tmpAttrib; + + /* + * Copy old screen onto new screen. + */ + if (pTTYInfo->pScreen != NULL) { + + for (r = 1; r <= newNRow && r <= pTTYInfo->actNRow; ++r) { + pSource = pTTYInfo->pScreen + ((pTTYInfo->actNRow - r) * + pTTYInfo->actNColumn); + pDest = pNewScreen + ((newNRow - r) * newNColumn); + len = MIN (newNColumn, pTTYInfo->actNColumn); + for(i = 0; i < len; i++) + pDest[i] = pSource[i]; + + pSourceCW = pTTYInfo->pCellWidth + + ((pTTYInfo->actNRow - r) * pTTYInfo->actNColumn); + pDestCW = pNewCW + ((newNRow - r) * newNColumn); + memcpy(pDestCW, pSourceCW, len * sizeof(int)); + + pSourceAtt = pTTYInfo->pAttrib + + ((pTTYInfo->actNRow - r) * pTTYInfo->actNColumn); + pDestAtt = pNewAttrib + ((newNRow - r) * newNColumn); + len = MIN (newNColumn, pTTYInfo->actNColumn); + memcpy (pDestAtt, pSourceAtt, len * sizeof(CharAttrib)); + } + + pTTYInfo->nColumn = (CORD)MIN (pTTYInfo->nColumn, newNColumn); + pTTYInfo->nRow = (CORD)MAX (0, + pTTYInfo->nRow + (newNRow - pTTYInfo->actNRow)); + MemFree (pTTYInfo->pScreen); + MemFree (pTTYInfo->pCellWidth); + MemFree (pTTYInfo->pAttrib); + } + else { + pTTYInfo->nColumn = (CORD)MIN (pTTYInfo->nColumn, newNColumn); + pTTYInfo->nRow = (CORD)MIN (pTTYInfo->nRow, newNRow); + } + + pTTYInfo->pScreen = pNewScreen; + pTTYInfo->pCellWidth = pNewCW; + pTTYInfo->pAttrib = pNewAttrib; + pTTYInfo->actNColumn = newNColumn; + pTTYInfo->actNRow = newNRow; + + + /* Repaint whole screen. */ + pTTYInfo->screenDirty = TRUE; + pTTYInfo->eraseScreen = TRUE; + InvalidateRect (hWnd, NULL, FALSE); + + + + /* Pico specific. */ + if (term.t_nrow == 0) { + term.t_nrow = (short)(newNRow - 1); + term.t_ncol = (short)newNColumn; + } + + + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL ResetTTYFont( HWND hWnd, PTTYINFO pTTYInfo, LOGFONT *newFont) + * + * Description: + * Resets the TTY character information and causes the + * screen to resize to update the scroll information. + * + * Parameters: + * PTTYINFO pTTYInfo + * pointer to TTY info structure + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +ResetTTYFont (HWND hWnd, PTTYINFO pTTYInfo, LOGFONT *newFont) +{ + HDC hDC; + HFONT hFont; + TEXTMETRIC tm; + int newNRow; + int newNColumn; + BOOL newsize; + + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "ResetTTYFont::: entered, curent window size X %d, Y %d\n", + pTTYInfo->xSize, pTTYInfo->ySize); +#endif + + + if (NULL == pTTYInfo) + return (FALSE); + + SelClear (); + + /* + * Create new font. + */ + hFont = CreateFontIndirect (newFont); + if (hFont == NULL) + return (FALSE); + hDC = GetDC (hWnd); + SelectObject (hDC, hFont); + GetTextMetrics (hDC, &tm); + ReleaseDC (hWnd, hDC); + + + /* + * Replace old font. + */ + if (NULL != pTTYInfo->hTTYFont) + DeleteObject (pTTYInfo->hTTYFont); + pTTYInfo->hTTYFont = hFont; + memcpy (&pTTYInfo->lfTTYFont, newFont, sizeof (LOGFONT)); + + + /* Update the char cell size. */ + pTTYInfo->xChar = (CORD)tm.tmAveCharWidth; + pTTYInfo->yChar = (CORD)(tm.tmHeight + tm.tmExternalLeading); + + /* Update the current number of rows and cols. Don't allow + * either to be less than zero. */ + newNRow = MAX (MINNROW, + MIN (MAXNROW, + (pTTYInfo->ySize - pTTYInfo->toolBarSize - (2 * MARGINE_TOP))/ + pTTYInfo->yChar)); + newNColumn = MAX (MINNCOLUMN, + MIN (MAXNCOLUMN, (pTTYInfo->xSize - (2 * pTTYInfo->xOffset))/ + pTTYInfo->xChar)); + + newsize = newNRow != pTTYInfo->actNRow || + newNColumn != pTTYInfo->actNColumn; + if (newsize) + ResizeTTYScreen (hWnd, pTTYInfo, newNRow, newNColumn); + + /* Resize the caret as well. */ + if(pTTYInfo->fCaretOn) + HideCaret (hWnd); + + DestroyCaret (); + CaretCreateTTY (hWnd); + + /* Redraw screen and, if the "size" changed, tell the upper layers. */ + pTTYInfo->screenDirty = TRUE; + pTTYInfo->eraseScreen = TRUE; + InvalidateRect (hWnd, NULL, FALSE); + + /* Always call the resize functions - even if the screen size + * has not changed, the font style may have. */ + DidResize (pTTYInfo); + + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL EraseTTY (HWND hWnd, HDC hDC) + * + * Description: + * Erase the tty background. + * + * + * Parameters: + * HWND hWnd + * handle to TTY window (as always) + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +EraseTTY (HWND hWnd, HDC hDC) +{ + RECT erect; + HBRUSH hBrush; + + + GetClientRect (hWnd, &erect); + hBrush = CreateSolidBrush (gpTTYInfo->rgbBGColor); + if (hBrush != NULL) { + FillRect (hDC, &erect, hBrush); + DeleteObject (hBrush); + } + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL PaintTTY( HWND hWnd ) + * + * Description: + * Paints the rectangle determined by the paint struct of + * the DC. + * + * Parameters: + * HWND hWnd + * handle to TTY window (as always) + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +PaintTTY (HWND hWnd) +{ + int nRow, nCol; /* Top left corner of update. */ + int nEndRow, nEndCol; /* lower right corner of update. */ + int nHorzPos, nVertPos; /* Position of each text write. */ + int col; /* start col of run of similar attr */ + int count; /* count of run of similar attrib. */ + int endCount; /* How far to count. */ + CharAttrib *pAttrib; + HDC hDC; + LOGFONT tmpFont; + HFONT hOrigFont, hOldFont = NULL, hTmpFont; + PTTYINFO pTTYInfo; + PAINTSTRUCT ps; + RECT rect; + RECT erect; + HBRUSH hBrush; + long offset; /* Offset into pScreen array */ + long endoffset; /* Offset of nEndCol in each row array */ + CharAttrib *pLastAttrib; /* Attributes of last text write. */ + CharAttrib *pNewAttrib; /* Attributes of this text write. */ + + +#ifdef CDEBUG + if (mswin_debug >= 9) + fprintf (mswin_debugfile, "PaintTTY::: entered\n"); +#endif + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + if (IsIconic (hWnd)) + return (TRUE); + + hDC = BeginPaint (hWnd, &ps); + rect = ps.rcPaint; + + hOrigFont = SelectObject (hDC, pTTYInfo->hTTYFont); + SetTextColor (hDC, pTTYInfo->rgbFGColor); + SetBkColor (hDC, pTTYInfo->rgbBGColor); + SetBkMode (hDC, OPAQUE); + + nRow = (rect.top - pTTYInfo->yOffset) / pTTYInfo->yChar; + CONSTRAIN (nRow, 0, pTTYInfo->actNRow - 1); + + nEndRow = MIN(pTTYInfo->actNRow - 1, + ((rect.bottom - pTTYInfo->yOffset - 1) / pTTYInfo->yChar)); + nCol = MIN(pTTYInfo->actNColumn - 1, + MAX(0, (rect.left - pTTYInfo->xOffset) / pTTYInfo->xChar)); + nEndCol = MIN(pTTYInfo->actNColumn - 1, + ((rect.right - pTTYInfo->xOffset - 1) / pTTYInfo->xChar)); + + pLastAttrib = NULL; + + /* Erase screen if necessary. */ + if (pTTYInfo->eraseScreen) { + erect.top = 0; + erect.left = 0; + erect.bottom = pTTYInfo->ySize; + erect.right = pTTYInfo->xSize; + hBrush = CreateSolidBrush (pTTYInfo->rgbBGColor); + if (hBrush != NULL) { + FillRect (hDC, &erect, hBrush); + DeleteObject (hBrush); + } + pTTYInfo->eraseScreen = FALSE; + } + + + /* Paint an inset frame around the text region. */ + if (pTTYInfo->toolBarSize == 0) { + erect.top = 0; + erect.bottom = pTTYInfo->ySize; + } + else if (pTTYInfo->toolBarTop) { + erect.top = pTTYInfo->toolBarSize; + erect.bottom = pTTYInfo->ySize; + } + else { + erect.top = 0; + erect.bottom = pTTYInfo->ySize - pTTYInfo->toolBarSize; + } + erect.left = 0; + erect.right = pTTYInfo->xSize; + FrameRect3D (hDC, &erect, FRAME_3D_SIZE, FALSE); + + /* Paint rows of text. */ + for (; nRow <= nEndRow; nRow++) { + nVertPos = (nRow * pTTYInfo->yChar) + pTTYInfo->yOffset; + rect.top = nVertPos; + rect.bottom = nVertPos + pTTYInfo->yChar; + + /* Paint runs of similar attributes. */ + col = nCol; /* Start at left. */ + + if(col == 0 && MSWIconPaint(nRow, hDC)) + col += 2; + + /* + * col is the column on the screen, not the index + * into the array. + */ + while (col <= nEndCol) { /* While not past right. */ + + /* Starting with Character at nRow, col, what is its attribute? */ + + /* offset is an index into the array */ + offset = pscreen_offset_from_cord(nRow, col, pTTYInfo); + pNewAttrib = pTTYInfo->pAttrib + offset; + + hTmpFont = NULL; + if (!(pLastAttrib + && pNewAttrib->style == pLastAttrib->style + && pNewAttrib->rgbFG == pLastAttrib->rgbFG + && pNewAttrib->rgbBG == pLastAttrib->rgbBG)) { + /* + * Require new font? + */ + if(!pLastAttrib + || (pNewAttrib->style & CHAR_ATTR_ULINE) + != (pLastAttrib->style & CHAR_ATTR_ULINE)){ + if(pNewAttrib->style & CHAR_ATTR_ULINE){ + /* + * Find suitable attribute font... + */ + memcpy (&tmpFont, &pTTYInfo->lfTTYFont, + sizeof (LOGFONT)); + + tmpFont.lfHeight = - pTTYInfo->yChar; + tmpFont.lfWidth = - pTTYInfo->xChar; + + tmpFont.lfUnderline = (BYTE)((pNewAttrib->style + & CHAR_ATTR_ULINE) + == CHAR_ATTR_ULINE); + + hTmpFont = CreateFontIndirect (&tmpFont); + + hOldFont = SelectObject (hDC, hTmpFont); + } + } + + /* + * Set new color attributes. If Reverse or Selected, then + * show in reverse colors. But if neither, or both, then + * normal colors. + */ + if(pNewAttrib->style & CHAR_ATTR_SEL){ + SetTextColor (hDC, pNewAttrib->rgbBG); + SetBkColor (hDC, pNewAttrib->rgbFG); + } + else { + if(!(pLastAttrib + && pNewAttrib->rgbFG == pLastAttrib->rgbFG) + || (pLastAttrib->style & CHAR_ATTR_SEL)) + SetTextColor (hDC, pNewAttrib->rgbFG); + + if(!(pLastAttrib + && pNewAttrib->rgbBG == pLastAttrib->rgbBG) + || (pLastAttrib->style & CHAR_ATTR_SEL)) + SetBkColor (hDC, pNewAttrib->rgbBG); + } + } + + /* Find run of similar attributes. */ + count = 1; + pAttrib = pTTYInfo->pAttrib + (offset + 1); + /* endoffset is an index into the pScreen array */ + endoffset = pscreen_offset_from_cord(nRow, nEndCol, pTTYInfo); + endCount = endoffset - offset; + while (count <= endCount + && pAttrib->style == pNewAttrib->style + && pAttrib->rgbFG == pNewAttrib->rgbFG + && pAttrib->rgbBG == pNewAttrib->rgbBG){ + ++pAttrib; + ++count; + } + + if(hTmpFont != NULL){ +/* BUG: compute new offsets based on hTmpFont font if required */ + nHorzPos = (col * pTTYInfo->xChar) + pTTYInfo->xOffset; + rect.left = nHorzPos; + rect.right = nHorzPos + pTTYInfo->xChar * scrwidth(pTTYInfo->pScreen+offset, count); + } + else{ + /* Paint run of characters from nRow, col to nRow, col + count + * rect.top and rect.bottom have already been calculated. */ + nHorzPos = (col * pTTYInfo->xChar) + pTTYInfo->xOffset; + rect.left = nHorzPos; + rect.right = nHorzPos + pTTYInfo->xChar * scrwidth(pTTYInfo->pScreen+offset, count); + } + + ExtTextOut (hDC, nHorzPos, nVertPos, ETO_OPAQUE | ETO_CLIPPED, + &rect, (LPTSTR) (pTTYInfo->pScreen + offset), + count, (int *)(pTTYInfo->pCellWidth+offset)); + + /* Overstrike bold chars by hand to preserve char cell size */ + if(pNewAttrib->style & CHAR_ATTR_BOLD){ + int old_mode = GetBkMode(hDC); + SetBkMode (hDC, TRANSPARENT); + ExtTextOut (hDC, nHorzPos + 1, nVertPos, 0, + &rect, (LPTSTR) (pTTYInfo->pScreen + offset), + count, (int *)(pTTYInfo->pCellWidth+offset)); + if(old_mode) + SetBkMode (hDC, old_mode); + } + + /* Move pointer to end of this span of characters. */ + col += MAX(scrwidth(pTTYInfo->pScreen+offset, count), 1); + pLastAttrib = pNewAttrib; + + if(hTmpFont != NULL){ + SelectObject(hDC, hOldFont); + DeleteObject(hTmpFont); + } + } + } + + SelectObject (hDC, hOrigFont); + EndPaint (hWnd, &ps); + MoveTTYCursor (hWnd); + pTTYInfo->screenDirty = FALSE; + return (TRUE); +} + + +/* FillRectColor + * + * + * Description: + * FillRectColor is similar to PatB in toolbar.c + * + * Code based on MFC source code, so presumably efficient. + * + */ +LOCAL void +FillRectColor(HDC hDC, RECT * pRC, COLORREF color) +{ + SetBkColor(hDC, color); + ExtTextOut(hDC, 0, 0, ETO_OPAQUE, pRC, NULL, 0, NULL); +} + + +/** FrameRect3D + * + * + * Inputs: + * hdc - HDC + * pRC - pointer to rectangle + * width - width for frame (usually one) + * raised - TRUE for raised effect, FALSE for sunken effect + * + * Outputs: + * none + * + * Returns: + * void + * + * Description + * Draws a frame with a 3D effect. + * + * If 'raised' is true, the rectangle will look raised (like + * a button); otherwise, the rectangle will look sunk. + * + */ +void +FrameRect3D(HDC hdc, RECT * pRC, int width, BOOL raised) +{ + COLORREF hilite, shadow; + RECT rcTemp; + + shadow = GetSysColor(COLOR_BTNSHADOW); + hilite = GetSysColor(COLOR_BTNHIGHLIGHT); + + rcTemp = *pRC; + + rcTemp.right = rcTemp.left + width; + FillRectColor(hdc, &rcTemp, raised ? hilite : shadow); + rcTemp.right = pRC->right; + + rcTemp.bottom = rcTemp.top + width; + FillRectColor(hdc, &rcTemp, raised ? hilite : shadow); + rcTemp.bottom = pRC->bottom; + + rcTemp.left = rcTemp.right - width; + FillRectColor(hdc, &rcTemp, raised ? shadow : hilite); + rcTemp.left = pRC->left; + + rcTemp.top = rcTemp.bottom - width; + FillRectColor(hdc, &rcTemp, raised ? shadow : hilite); +} + + +/*--------------------------------------------------------------------------- + * BOOL GetMinMaxInfoTTY (HWND hWnd, (MINMAXINFO __far *)lParam) + * + * Description: + * Return the min and max size that the window can be. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * MINMAXINFO + * Info structure that Windows would like us to fill. + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +GetMinMaxInfoTTY (HWND hWnd, MINMAXINFO __far *lpmmi) +{ + PTTYINFO pTTYInfo; + + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "GetMinMaxInfoTTY::: entered\n"); +#endif + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + lpmmi->ptMaxTrackSize.x = lpmmi->ptMaxSize.x = MIN (lpmmi->ptMaxSize.x, + pTTYInfo->xChar * MAXNCOLUMN + WIN_X_BORDER_SIZE); + lpmmi->ptMaxTrackSize.y = lpmmi->ptMaxSize.y = MIN (lpmmi->ptMaxSize.y, + pTTYInfo->yChar * MAXNROW + WIN_Y_BORDER_SIZE); + + lpmmi->ptMinTrackSize.x = MAX (WIN_MIN_X_SIZE, + pTTYInfo->xChar * MINNCOLUMN + WIN_X_BORDER_SIZE); + lpmmi->ptMinTrackSize.y = MAX (WIN_MIN_Y_SIZE, + pTTYInfo->yChar * MINNROW + WIN_Y_BORDER_SIZE); + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL AboutToSizeTTY (HWND hWnd, WINDOWPOS *winPos) + * + * Description: + * Called just before Windows resizes our window. We can change the + * values in 'winPos' to change the new size of the window. + * + * If mswin_setwindow() was called when the window was minimized we + * set the new size here. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * WORD wVertSize + * new vertical size + * + * WORD wHorzSize + * new horizontal size + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +AboutToSizeTTY (HWND hWnd, WINDOWPOS *winPos) +{ + PTTYINFO pTTYInfo; + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return ( FALSE ); + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "AboutToSizeTTY::: After x%lx, pos %d, %d, size %d, %d, flags x%x\n", + winPos->hwndInsertAfter, winPos->x, winPos->y, winPos->cx, + winPos->cy, winPos->flags); + +#endif + + /* + * Was the window minimized AND is there a desired new size for it? + * AND is this a call that specifies a new size and position. + */ + if (pTTYInfo->fMinimized && pTTYInfo->fDesiredSize && + (winPos->flags & (SWP_NOSIZE | SWP_NOMOVE)) == 0) { +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "AboutToSizeTTY::: substitue pos (%d, %d), size (%d, %d)\n", + pTTYInfo->xDesPos, pTTYInfo->yDesPos, + pTTYInfo->xDesSize, pTTYInfo->yDesSize); +#endif + pTTYInfo->fDesiredSize = FALSE; + winPos->x = pTTYInfo->xDesPos; + winPos->y = pTTYInfo->yDesPos; + winPos->cx = pTTYInfo->xDesSize; + winPos->cy = pTTYInfo->yDesSize; + } + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL SizeTTY( HWND hWnd, int fwSizeType, CORD wVertSize, + * CORD wHorzSize) + * + * Description: + * Sizes TTY and sets up scrolling regions. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * WORD wVertSize + * new vertical size + * + * WORD wHorzSize + * new horizontal size + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +SizeTTY (HWND hWnd, int fwSizeType, CORD wVertSize, CORD wHorzSize) +{ + PTTYINFO pTTYInfo; + int newNColumn; + int newNRow; + + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "SizeTTY::: entered, sizeType %d, New screen size %d, %d pixels\n", + fwSizeType, wHorzSize, wVertSize); +#endif + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return ( FALSE ); + + + /* + * Is the window being minimized or maximized? + */ + switch (fwSizeType) { + case SIZE_MINIMIZED: + pTTYInfo->fMinimized = TRUE; + pTTYInfo->fMaximized = FALSE; + return (TRUE); + case SIZE_MAXIMIZED: + pTTYInfo->fMinimized = FALSE; + pTTYInfo->fMaximized = TRUE; + break; + default: + pTTYInfo->fMinimized = pTTYInfo->fMaximized = FALSE; + break; + } + + pTTYInfo->ySize = (CORD) wVertSize; + newNRow = MAX(MINNROW, MIN(MAXNROW, + (pTTYInfo->ySize - pTTYInfo->toolBarSize - (2 * MARGINE_TOP)) / + pTTYInfo->yChar)); + if (pTTYInfo->toolBarTop) + pTTYInfo->yOffset = MARGINE_TOP + pTTYInfo->toolBarSize; + else + pTTYInfo->yOffset = MARGINE_TOP; + + + pTTYInfo->xSize = (CORD) wHorzSize; + newNColumn = MAX(MINNCOLUMN, + MIN(MAXNCOLUMN, (pTTYInfo->xSize - (2 * MARGINE_LEFT)) / + pTTYInfo->xChar)); + pTTYInfo->xOffset = MARGINE_LEFT; + + if(newNRow == pTTYInfo->actNRow && newNColumn == pTTYInfo->actNColumn) + return(FALSE); + + ResizeTTYScreen (hWnd, pTTYInfo, newNRow, newNColumn); + pTTYInfo->screenDirty = TRUE; + pTTYInfo->eraseScreen = TRUE; + InvalidateRect (hWnd, NULL, FALSE); + + if (pTTYInfo->hTBWnd) { + if (pTTYInfo->toolBarTop) + /* Position at top of window. */ + SetWindowPos (pTTYInfo->hTBWnd, HWND_TOP, + 0, 0, + wHorzSize, pTTYInfo->toolBarSize, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW); + else + /* Position at bottom of window. */ + SetWindowPos (pTTYInfo->hTBWnd, HWND_TOP, + 0, pTTYInfo->ySize - pTTYInfo->toolBarSize, + wHorzSize, pTTYInfo->toolBarSize, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW); + } + + + DidResize (pTTYInfo); + + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL SizingTTY( HWND hWnd, int fwSide, LPRECT pRect) + * + * Description: + * Snaps the drag rectangle to char width/height boundaries + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * WORD fwSide + * edge of window being sized + * + * LPRECT + * screen coords of drag rectangle in and desired size on return + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +SizingTTY (HWND hWnd, int fwSide, LPRECT pRect) +{ + PTTYINFO pTTYInfo; + int newNRow, newNCol, xClient, yClient, + xSys, ySys, xDiff, yDiff; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + xSys = (2 * GetSystemMetrics(SM_CXSIZEFRAME)) + + GetSystemMetrics(SM_CXVSCROLL); + ySys = (2 * GetSystemMetrics(SM_CYSIZEFRAME)) + + GetSystemMetrics(SM_CYCAPTION) + + GetSystemMetrics(SM_CYMENU); + + newNCol = (((pRect->right - pRect->left) - xSys) + - (2 * MARGINE_LEFT)) / pTTYInfo->xChar; + newNRow = (((pRect->bottom - pRect->top) - ySys) - (2 * MARGINE_TOP) + - pTTYInfo->toolBarSize) / pTTYInfo->yChar; + + xClient = (newNCol * pTTYInfo->xChar) + (2 * MARGINE_LEFT); + yClient = (newNRow * pTTYInfo->yChar) + (2 * MARGINE_TOP) + + pTTYInfo->toolBarSize; + + xDiff = (pRect->left + xClient + xSys) - pRect->right; + yDiff = (pRect->top + yClient + ySys) - pRect->bottom; + + if(!(xDiff || yDiff)) + return(FALSE); + + switch(fwSide){ + case WMSZ_BOTTOM : /* Bottom edge */ + pRect->bottom += yDiff; + break; + + case WMSZ_BOTTOMLEFT : /*Bottom-left corner */ + pRect->bottom += yDiff; + pRect->left -= xDiff; + break; + + case WMSZ_BOTTOMRIGHT : /* Bottom-right corner */ + pRect->bottom += yDiff; + pRect->right += xDiff; + break; + + case WMSZ_LEFT : /* Left edge */ + pRect->left -= xDiff; + break; + + case WMSZ_RIGHT : /* Right edge */ + pRect->right += xDiff; + break; + + case WMSZ_TOP : /* Top edge */ + pRect->top -= yDiff; + break; + + case WMSZ_TOPLEFT : /* Top-left corner */ + pRect->top -= yDiff; + pRect->left -= xDiff; + break; + + case WMSZ_TOPRIGHT : /* Top-right corner */ + pRect->top -= yDiff; + pRect->right += xDiff; + break; + + default : + break; + } + + if(!(newNRow == pTTYInfo->actNRow && newNCol == pTTYInfo->actNColumn)) + SizeTTY(hWnd, SIZE_RESTORED, (CORD) yClient, (CORD) xClient); + + return(TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL MoveTTY (HWND hWnd, int xPos, int yPos) + * + * Description: + * Notes the fact that the window has moved. + * Only real purpose is so we can tell pine which can the write the + * new window position to the 'pinerc' file. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * int xPos, yPos + * New position of the top left corner. + * + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +MoveTTY (HWND hWnd, int xPos, int yPos) +{ +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "MoveTTY::: entered\n"); +#endif + + DidResize (gpTTYInfo); + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * void ScrollTTY () + * + * Description: + * Respond to a scroll message by either calling the scroll + * callback or inserting a scroll character into the input + * stream. + * + * Scrolling in the TTY window is complicated by the way pine + * process events. Normal windows applications are entirly event + * driven. The top level does nothing but dispatch events. In + * pine, the top level implements the logic. Events are only + * dispatched by the lowest levels. + * + * In normal applications, mouse down in the scroll bar causes + * an internal scroll function to be entered. It tracks the + * mouse and issues scroll messages as needed. If the + * application redraws the screen the scroll function also + * dispatches the WM_PAINT message to the application. The + * important thing is that this internal scroll function does + * not exit until the mouse is released. + * + * We implement two methods for pine's screen managers to deal + * with scroll events. They can receive scroll events as + * characters in the normal input stream or they can register a + * callback function. + * + * In the "insert a character in the queue" mode, the scroll + * event never gets process until the mouse is release. Auto + * repeat scroll events (generated as the mouse is held down) + * will cause multiple chars to be inserted in the queue, none + * of which will get processed till the mouse is release. In a + * compromise, we allow only one scroll char in the queue, + * which prevents makes for a more friendly and controllable + * behavior. + * + * In the callback mode, the callback repaints the screen, and + * then it calls mswin_flush() which PROCESSES EVENTS! The + * Windows internal scroll function does NOT expect that. This + * behavior can confuses the scroll function, causing it to + * miss mouse up events. We avoid this by setting gScrolling TRUE + * when this routine is entered and FALSE when this routine exits + * All PeekMessage processors avoid processing any message when + * gScrolling is TRUE. + * +/*--------------------------------------------------------------------------*/ +LOCAL void +ScrollTTY (HWND hWnd, int wScrollCode, int nPos, HWND hScroll) +{ + PTTYINFO pTTYInfo; + int cmd = 0; + long scroll_pos = 0; + BOOL noAction = FALSE; + BOOL didScroll; + FARPROC prevBlockingProc; + + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + + if (pTTYInfo == NULL || gScrolling) + return; + + gScrolling = TRUE; + if (gWSBlockingProc != NULL) + prevBlockingProc = WSASetBlockingHook (gWSBlockingProc); + + + + + switch (wScrollCode) { + case SB_BOTTOM: + cmd = MSWIN_KEY_SCROLLTO; + scroll_pos = pTTYInfo->scrollTo = 0; + break; + + case SB_TOP: + cmd = MSWIN_KEY_SCROLLTO; + scroll_pos = pTTYInfo->scrollTo = pTTYInfo->scrollRange; + break; + + case SB_LINEDOWN: + cmd = MSWIN_KEY_SCROLLDOWNLINE; + scroll_pos = 1; + break; + + case SB_LINEUP: + cmd = MSWIN_KEY_SCROLLUPLINE; + scroll_pos = 1; + break; + + case SB_PAGEDOWN: + cmd = MSWIN_KEY_SCROLLDOWNPAGE; + scroll_pos = 1; + break; + + case SB_PAGEUP: + cmd = MSWIN_KEY_SCROLLUPPAGE; + scroll_pos = 1; + break; + + case SB_THUMBTRACK: + case SB_THUMBPOSITION: + cmd = MSWIN_KEY_SCROLLTO; + scroll_pos = pTTYInfo->scrollTo = (long) ((float)nPos); + break; + + default: + noAction = TRUE; + break; + } + + + /* + * If there is a scroll callback call that. If there is no scroll + * callback or the callback says it did not handle the event (returned, + * FALSE) queue the scroll cmd. + */ + if (!noAction) { + SelClear (); + didScroll = FALSE; + if (gScrollCallback != NULL) { + /* Call scrolling callback. Set blocking hook to our routine + * which prevents messages from being dispatched. */ + if (gWSBlockingProc != NULL) + WSASetBlockingHook (gWSBlockingProc); + didScroll = gScrollCallback (cmd, scroll_pos); + if (gWSBlockingProc != NULL) + WSAUnhookBlockingHook (); + } + /* + * If no callback or callback did not do the scrolling operation, + * insert a scroll cmd in the input stream. + */ + if (!didScroll) + CQAddUniq ((UCS)cmd, 0); + } + + + gScrolling = FALSE; + return; +} + + +#ifdef WIN32 +/*--------------------------------------------------------------------------- + * void MouseWheelTTY () + * + * Description: + * Respond to a WM_MOUSEWHEEL event by calling scroll callback + * ala ScrollTTY. + * + * +/*--------------------------------------------------------------------------*/ +LOCAL void +MouseWheelTTY (HWND hWnd, int xPos, int yPos, int fwKeys, int zDelta) +{ + PTTYINFO pTTYInfo; + int cmd; + long scroll_pos; + FARPROC prevBlockingProc; + SCROLLINFO scrollInfo; + static int zDelta_accumulated; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + + if (pTTYInfo == NULL || gScrolling) + return; + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_POS | SIF_RANGE; + GetScrollInfo(hWnd, SB_VERT, &scrollInfo); + if((zDelta < 0 && scrollInfo.nPos < scrollInfo.nMin) + || (zDelta > 0 && scrollInfo.nPos >= scrollInfo.nMax)) + return; + + gScrolling = TRUE; + if (gWSBlockingProc != NULL) + prevBlockingProc = WSASetBlockingHook (gWSBlockingProc); + + if(fwKeys == MK_MBUTTON) + zDelta *= 2; /* double the effect! */ + + if(abs(zDelta += zDelta_accumulated) < WHEEL_DELTA){ + zDelta_accumulated = zDelta; + } + else{ + /* Remember any partial increments */ + zDelta_accumulated = zDelta % WHEEL_DELTA; + + scroll_pos = (long)(gsMWMultiplier * abs((zDelta / WHEEL_DELTA))); + + cmd = (zDelta < 0) ? MSWIN_KEY_SCROLLDOWNLINE : MSWIN_KEY_SCROLLUPLINE; + + SelClear (); + if (gScrollCallback != NULL) { + /* Call scrolling callback. Set blocking hook to our routine + * which prevents messages from being dispatched. */ + if (gWSBlockingProc != NULL) + WSASetBlockingHook (gWSBlockingProc); + (void) gScrollCallback (cmd, scroll_pos); + if (gWSBlockingProc != NULL) + WSAUnhookBlockingHook (); + } + } + + gScrolling = FALSE; + return; +} + + +LOCAL void +MouseWheelMultiplier() +{ + TCHAR lines[8]; + DWORD llen = sizeof(lines)/sizeof(TCHAR); + + /* HKEY_CURRENT_USER\Control Panel\Desktop holds the key */ + gsMWMultiplier = (MSWRPeek(HKEY_CURRENT_USER, TEXT("Control Panel\\Desktop"), + TEXT("WheelScrollLines"), lines, &llen) == TRUE) + ? (short)_ttoi(lines) : 1; +} +#endif + + +/*--------------------------------------------------------------------------- + * void CaretTTY (HWND hWnd, CARETS cStyle) + * + * Description: + * Adjusts the Caret to the user supplied style + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * int wStyle + * New style to take on + * +/*--------------------------------------------------------------------------*/ +LOCAL void +CaretTTY (HWND hWnd, CARETS cStyle) +{ + PTTYINFO pTTYInfo; + + if(pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO)){ + pTTYInfo->cCaretStyle = cStyle; + CaretCreateTTY (hWnd); + DidResize (gpTTYInfo); + } +} + + +/*--------------------------------------------------------------------------- + * void CaretCreateTTY (HWND hWnd, BOOL wPosition) + * + * Description: + * Adjusts the Caret to the user supplied style + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * BOOL wPosition + * whether or not to position it too + * +/*--------------------------------------------------------------------------*/ +LOCAL void +CaretCreateTTY (HWND hWnd) +{ + PTTYINFO pTTYInfo; + + if(pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO)){ + int n = 0, x, y; + + switch(pTTYInfo->cCaretStyle){ + case CaretHorizBar : + x = pTTYInfo->xChar; + y = pTTYInfo->yChar / 5; + n = pTTYInfo->yChar - y; + break; + + case CaretVertBar : + x = pTTYInfo->xChar / 4; + y = pTTYInfo->yChar; + break; + + case CaretSmallBlock : + x = pTTYInfo->xChar; + y = pTTYInfo->yChar / 2; + n = pTTYInfo->yChar - y; + break; + + default : + x = pTTYInfo->xChar; + y = pTTYInfo->yChar; + break; + } + + CreateCaret (hWnd, NULL, x, y); + pTTYInfo->yCurOffset = n; + + if(pTTYInfo->fCaretOn){ + ShowCaret(hWnd); + MoveTTYCursor(hWnd); + } + } +} + + +/* + * This routine is inserted as the winsock blocking hook. It's main perpos + * is to NOT dispatch messages. + */ +BOOL CALLBACK __export +NoMsgsAreSent (void) +{ + return (FALSE); +} + + +/*--------------------------------------------------------------------------- + * BOOL SetTTYFocus( HWND hWnd ) + * + * Description: + * Sets the focus to the TTY window also creates caret. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +SetTTYFocus (HWND hWnd) +{ + PTTYINFO pTTYInfo; + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "SetTTYFocus::: entered\n"); +#endif + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + mswin_showcursor(TRUE); + + pTTYInfo->fFocused = TRUE; + + CaretCreateTTY (hWnd); + + MoveTTYCursor (hWnd); + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL KillTTYFocus( HWND hWnd ) + * + * Description: + * Kills TTY focus and destroys the caret. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +KillTTYFocus (HWND hWnd) +{ + PTTYINFO pTTYInfo; + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "KillTTYFocus::: entered\n"); +#endif + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + mswin_showcursor(TRUE); + + if(pTTYInfo->fCaretOn) + HideCaret (hWnd); + + DestroyCaret(); + + pTTYInfo->fFocused = FALSE; + + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL MoveTTYCursor( HWND hWnd ) + * + * Description: + * Moves caret to current position. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +MoveTTYCursor (HWND hWnd) +{ + PTTYINFO pTTYInfo; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + if(pTTYInfo->fCaretOn && !pTTYInfo->fMassiveUpdate) { + HideCaret (hWnd); + SetCaretPos ((pTTYInfo->nColumn * pTTYInfo->xChar) + pTTYInfo->xOffset, + (pTTYInfo->nRow * pTTYInfo->yChar) + + pTTYInfo->yCurOffset + pTTYInfo->yOffset); + ShowCaret (hWnd); + } + + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL ProcessTTYKeyDown ( HWND hWnd, WORD bOut ) + * + * Description: + * Called to process MW_KEYDOWN message. We are only interested in + * virtual keys that pico/pine use. All others get passed on to + * the default message handler. Regular key presses will return + * latter as a WM_CHAR message, with SHIFT and CONTROL processing + * already done. + * + * We do watch for VK_CONTROL to keep track of it's state such + * that we can implement ^_space. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * BYTE key + * Virtual key code. + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +ProcessTTYKeyDown (HWND hWnd, TCHAR key) +{ + UCS myKey; + BOOL fKeyControlDown = GetKeyState(VK_CONTROL) < 0; + BOOL fKeyAltDown = GetKeyState(VK_MENU) < 0; + + // If the alt key is down, let Windows handle the message. This will + // allow the Ctrl+Alt (AltGr) processing to work. + if(fKeyAltDown) + return FALSE; + + switch (key) { + case VK_MENU: + case VK_CONTROL: + case VK_SHIFT: + return FALSE; + case VK_UP: myKey = KEY_UP; break; + case VK_DOWN: myKey = KEY_DOWN; break; + case VK_RIGHT: + /* Ctrl-@ is used to advance to the next word. */ + myKey = fKeyControlDown ? '@': KEY_RIGHT; + break; + case VK_LEFT: + /* Ctrl-left is used to advance to the previous word. */ + myKey = KEY_LEFT; + break; + case VK_HOME: + /* Ctrl-home is used to advance to the beginning of buffer. */ + myKey = KEY_HOME; + break; + case VK_END: + /* Ctrl-end is used to advance to the end of buffer. */ + myKey = KEY_END; + break; + case VK_PRIOR: myKey = KEY_PGUP; break; + case VK_NEXT: myKey = KEY_PGDN; break; + case VK_DELETE: myKey = KEY_DEL; break; + case VK_F1: myKey = F1; break; + case VK_F2: myKey = F2; break; + case VK_F3: myKey = F3; break; + case VK_F4: myKey = F4; break; + case VK_F5: myKey = F5; break; + case VK_F6: myKey = F6; break; + case VK_F7: myKey = F7; break; + case VK_F8: myKey = F8; break; + case VK_F9: myKey = F9; break; + case VK_F10: myKey = F10; break; + case VK_F11: myKey = F11; break; + case VK_F12: myKey = F12; break; + + default: + if(fKeyControlDown && !(GetKeyState(VK_SHIFT) < 0)) { + if(key == '6') { + /* + * Ctrl-^ is used to set and clear the mark in the + * composer (pico) On most other systems Ctrl-6 does the + * same thing. Allow that on windows too. + */ + myKey = '^'; + break; + } else if(key == '2') { + /* Ctrl-@ is used to advance to the next word. */ + myKey = '@'; + break; + } + } + + return (FALSE); /* Message NOT handled.*/ + } + + CQAdd (myKey, fKeyControlDown); + + set_time_of_last_input(); + + return (TRUE); /* Message handled .*/ +} + + +#ifdef CDEBUG +char * +dtime() +{ + static char timestring[23]; + time_t t; + struct _timeb timebuffer; + + timestring[0] = '\0'; + t = time((time_t *) 0); + _ftime(&timebuffer); + snprintf(timestring, sizeof(timestring), "%.8s.%03ld", ctime(&t)+11, timebuffer.millitm); + + return(timestring); +} +#endif + + +/*--------------------------------------------------------------------------- + * BOOL ProcessTTYCharacter( HWND hWnd, WORD bOut ) + * + * Description: + * Place the character into a queue. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * BYTE bOut + * byte from keyboard + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +ProcessTTYCharacter (HWND hWnd, TCHAR bOut) +{ + // Only check for control key being down if the alt key isn't also down. + // Windows uses Ctrl+Alt as AltGr. + BOOL fKeyAltDown = GetKeyState(VK_MENU) < 0; + BOOL fKeyControlDown = fKeyAltDown ? + FALSE : (GetKeyState(VK_CONTROL) < 0); + + if(fKeyControlDown) { + if(bOut == ' ') + bOut = '@'; + else + bOut += '@'; + } + + CQAdd ((UCS)bOut, fKeyControlDown); + +#ifdef ACCELERATORS + UpdateAccelerators (hWnd); +#endif + + set_time_of_last_input(); + + return (TRUE); /* Message handled. */ +} + + +/*--------------------------------------------------------------------------- + * BOOL ProcessTTYMouse(HWND hWnd, int mevent, int button, + * int xPos, int yPos, WPARAM keys) + * + * Description: + * This is the central control for all mouse events. Every event + * gets put into a queue to wait for the upper layer. + * + * The upper's input routine calls checkmouse() which pulls the + * mouse event off the input queue. checkmouse() has a list of + * of screen regions. Some regions correspond to a "menu" item + * (text button at bottom of screen). There is generally one + * region for the central region of the screen. + * + * Because pine/pico do not interpret mouse drags, we do that here. + * When the user presses the button and drags the mouse across the + * screen this select the text in the region defined by the drag. + * The operation is local to mswin.c, and can only get what text + * is on the screen. + * + * The one exception is that now pico interprets mouse drag events + * in the body. pico signals that it wants to track the mouse + * by calling mswin_allowmousetrack(). This will 1) turn off + * our mouse tracking and 2) cause mouse movement events to + * be put on the mouse queue. + * + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * BYTE bOut + * byte from keyboard + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +ProcessTTYMouse (HWND hWnd, int mevent, int button, + CORD xPos, CORD yPos, WPARAM winkeys) +{ + int nRow; + int nColumn; + int keys; + + /* + * Convert to cell position. + */ + nColumn = (xPos - gpTTYInfo->xOffset) / gpTTYInfo->xChar; + if (xPos < gpTTYInfo->xOffset) + --nColumn; + nRow = (yPos - gpTTYInfo->yOffset) / gpTTYInfo->yChar; + if (yPos < gpTTYInfo->yOffset) + --nRow; + + /* + * Convert window's keys. + */ + keys = 0; + if (winkeys & MK_CONTROL) + keys |= M_KEY_CONTROL; + if (winkeys & MK_SHIFT) + keys |= M_KEY_SHIFT; + + /* Adjust the cursor */ + if((unsigned long) mevent != M_EVENT_UP){ + if(gMouseTracking) + mswin_setcursor(MSWIN_CURSOR_IBEAM); + else if(ghCursorCurrent == ghCursorBusy) + mswin_setcursor(MSWIN_CURSOR_BUSY); + else if(mouse_on_key(nRow, nColumn)) + mswin_setcursor(MSWIN_CURSOR_HAND); + else if(gMouseTrackCallback) + mswin_setcursor((*gMouseTrackCallback)(nColumn, (long) nRow)); + else + mswin_setcursor(MSWIN_CURSOR_ARROW); + } + + /* + * Tracking event or mouse up/down? + */ + if ((unsigned long) mevent == M_EVENT_TRACK) { + /* + * Who is doing the tracking? + */ + if (gAllowMouseTrack) { + /* For tracking, Button info is different. */ + if (keys & MK_LBUTTON) + button = M_BUTTON_LEFT; + else if (keys & MK_MBUTTON) + button = M_BUTTON_MIDDLE; + else if (keys & MK_RBUTTON) + button = M_BUTTON_RIGHT; + MQAdd (mevent, button, nRow, nColumn, keys, + MSWIN_MF_REPLACING); + } + else + SelTrackMouse (nRow, nColumn); + } + else{ + /* + * Tracking. Only start tracking mouse down in the text region + * But allow mouse up anywhere. + */ + if ( (nRow >= 0 && nRow < gpTTYInfo->actNRow && + nColumn >= 0 && nColumn < gpTTYInfo->actNColumn) + || (unsigned long) mevent == M_EVENT_UP) { + + /* + * Mouse tracking. When the mouse goes down we start + * capturing all mouse movement events. If no one else wants + * them we will start defining a text selection. + */ + if ((unsigned long) mevent == M_EVENT_DOWN) { + gMouseTracking = TRUE; + SetCapture (ghTTYWnd); + if (!gAllowMouseTrack && button == M_BUTTON_LEFT) + SelStart (nRow, nColumn); + } + else { + ReleaseCapture (); + if (!gAllowMouseTrack && button == M_BUTTON_LEFT) + SelFinish (nRow, nColumn); + gMouseTracking = FALSE; + + /* + * If right mouse button, toss pop-up menu offering + * cut/copy/paste + */ + if(button == M_BUTTON_RIGHT && SelAvailable()){ + UINT fAllowed = (EM_CP | EM_CP_APPEND); + + if(gAllowCut) + fAllowed |= EM_CUT; + + if(CopyCutPopup(hWnd, fAllowed) == TRUE) + mevent = M_EVENT_TRACK; /* don't add to input queue! */ + } + } + + /* + * Insert event into queue. + */ + if((unsigned long) mevent != M_EVENT_TRACK) + MQAdd (mevent, button, nRow, nColumn, keys, 0); + } + } + + mswin_showcursor(TRUE); /* make sure it's visible */ + +#ifdef ACCELERATORS + UpdateAccelerators (hWnd); +#endif + + set_time_of_last_input(); + + return (0); /* Message handled. */ +} + + +/*--------------------------------------------------------------------------- + * BOOL ProcessTimer () + * + * Description: + * Process the periodic timer calls. + * + * + * Parameters: + * None. + * +/*--------------------------------------------------------------------------*/ +LOCAL void +ProcessTimer (void) +{ + /* Time to deliver an alarm signal? */ + if (gAlarmTimeout != 0 && GetTickCount () / 1000 > gAlarmTimeout) + AlarmDeliver (); + + /* Time to make the periodic callback. */ + if (gPeriodicCallback != NULL && + GetTickCount() / 1000 > gPeriodicCBTimeout) { + gPeriodicCBTimeout = GetTickCount() / 1000 + + gPeriodicCBTime; + gPeriodicCallback (); + } + + /* + * If tracking the mouse, insert a fake mouse tracking message + * At the last know location of the mouse. + */ + if (gAllowMouseTrack) { + gMTEvent.event = M_EVENT_TRACK; + MQAdd (gMTEvent.event, gMTEvent.button, gMTEvent.nRow, + gMTEvent.nColumn, gMTEvent.keys, MSWIN_MF_REPLACING); + } +} + + +/*--------------------------------------------------------------------------- + * BOOL WriteTTYBlock( HWND hWnd, LPSTR lpBlock, int nLength ) + * + * Description: + * Writes block to TTY screen. Nothing fancy - just + * straight TTY. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * LPSTR lpBlock + * far pointer to block of data + * + * int nLength + * length of block + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +WriteTTYBlock (HWND hWnd, LPTSTR lpBlock, int nLength) +{ + int i, j, width; + PTTYINFO pTTYInfo; + RECT rect; + BOOL fNewLine; + long offset; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + for (i = 0 ; i < nLength; i++) { + switch (lpBlock[i]) { + case ASCII_BEL: + /* Bell */ + MessageBeep (0) ; + break ; + + case ASCII_BS: + /* Backspace over a whole character */ + offset = pscreen_offset_from_cord(pTTYInfo->nRow, pTTYInfo->nColumn, pTTYInfo); + width = (offset > pTTYInfo->nRow * pTTYInfo->actNColumn) + ? scrwidth(pTTYInfo->pScreen+offset-1, 1) : 0; + + if(pTTYInfo->nColumn > 0) + pTTYInfo->nColumn = (CORD)(pTTYInfo->nColumn - width); + + MoveTTYCursor (hWnd); + break; + + case ASCII_CR: + /* Carriage return */ + pTTYInfo->nColumn = 0 ; + MoveTTYCursor (hWnd); + if (!pTTYInfo->fNewLine) + break; + + /* fall through */ + + case ASCII_LF: + /* Line feed */ + if (++pTTYInfo->nRow == pTTYInfo->actNRow) { + + /* Scroll the Screen. */ + + /* slide rows 1 - n-1 up to row 0 */ + memmove ((LPTSTR)pTTYInfo->pScreen, + (LPTSTR) (pTTYInfo->pScreen + pTTYInfo->actNColumn), + (pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn * sizeof (TCHAR)); + + /* initialize new row n-1 */ + for(j = (pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn; + j < pTTYInfo->actNColumn; j++) + pTTYInfo->pScreen[j] = (TCHAR) ' '; + + + /* Scroll the Cell Widths */ + memmove ((int *)pTTYInfo->pCellWidth, + (int *) (pTTYInfo->pCellWidth + pTTYInfo->actNColumn), + (pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn * sizeof (int)); + for(j = (pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn; + j < pTTYInfo->actNColumn; j++) + pTTYInfo->pCellWidth[j] = pTTYInfo->xChar; /* xChar set yet ? */ + + /* Scroll the Attributes. */ + + /* slide rows 1 - n-1 up to row 0 */ + memmove ((CharAttrib *) pTTYInfo->pAttrib, + (CharAttrib *) (pTTYInfo->pAttrib + pTTYInfo->actNColumn), + (pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn * sizeof(CharAttrib)); + + /* initialize new row n-1 to zero */ + memset ((CharAttrib *) (pTTYInfo->pAttrib + + (pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn), + 0, pTTYInfo->actNColumn*sizeof(CharAttrib)); + + pTTYInfo->screenDirty = TRUE; + pTTYInfo->eraseScreen = TRUE; + InvalidateRect (hWnd, NULL, FALSE); + --pTTYInfo->nRow; + } + + MoveTTYCursor (hWnd); + break; + + default: + offset = pscreen_offset_from_cord(pTTYInfo->nRow, pTTYInfo->nColumn, pTTYInfo); + pTTYInfo->pScreen[offset] = lpBlock[i]; + pTTYInfo->pCellWidth[offset] = wcellwidth((UCS)lpBlock[i]) * pTTYInfo->xChar; + pTTYInfo->pAttrib[offset] = pTTYInfo->curAttrib; + rect.left = (pTTYInfo->nColumn * pTTYInfo->xChar) + + pTTYInfo->xOffset; + rect.right = rect.left + pTTYInfo->xChar; + rect.top = (pTTYInfo->nRow * pTTYInfo->yChar) + + pTTYInfo->yOffset; + rect.bottom = rect.top + pTTYInfo->yChar; + pTTYInfo->screenDirty = TRUE; + InvalidateRect (hWnd, &rect, FALSE); + + /* Line Wrap. */ + if (pTTYInfo->nColumn < pTTYInfo->actNColumn - 1) + pTTYInfo->nColumn++ ; + else if (pTTYInfo->autoWrap == WRAP_ON || + (pTTYInfo->autoWrap == WRAP_NO_SCROLL && + pTTYInfo->nRow < pTTYInfo->actNRow - 1)) { + fNewLine = pTTYInfo->fNewLine; + pTTYInfo->fNewLine = FALSE; + WriteTTYBlock (hWnd, TEXT("\r\n"), 2); + pTTYInfo->fNewLine = fNewLine; + } + break; + } + } + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL WriteTTYText ( HWND hWnd, LPSTR lpBlock, int nLength ) + * + * Description: + * Like WriteTTYBlock but optimized for strings that are text only, + * no carrage control characters. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * LPSTR lpBlock + * far pointer to block of data + * + * int nLength + * length of block + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +WriteTTYText (HWND hWnd, LPTSTR lpText, int nLength) +{ + int i; + PTTYINFO pTTYInfo; + RECT rect; + long offset, endOffset; + long colEnd; + long screenEnd; + int screenwidth; + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return ( FALSE ); + + + /* Calculate offset of cursor, end of current column, and end of screen */ + offset = pscreen_offset_from_cord(pTTYInfo->nRow, pTTYInfo->nColumn, pTTYInfo); + + colEnd = (pTTYInfo->nRow + 1) * pTTYInfo->actNColumn; + screenEnd = pTTYInfo->actNRow * pTTYInfo->actNColumn; + + + /* Text is allowed to wrap around to subsequent lines, but not past end + * of screen */ + endOffset = offset + nLength; + if (endOffset >= screenEnd) { + nLength = screenEnd - offset; + endOffset = offset + nLength - 1; /* Last cell, not one past last */ + } + + + /* Calculate bounding rectangle. */ + if (endOffset <= colEnd) { + /* Single line. */ + + screenwidth = scrwidth(lpText, nLength); + + rect.left = (pTTYInfo->nColumn * pTTYInfo->xChar) + pTTYInfo->xOffset; + rect.right = rect.left + (pTTYInfo->xChar * screenwidth); + rect.top = (pTTYInfo->nRow * pTTYInfo->yChar) + pTTYInfo->yOffset; + rect.bottom = rect.top + pTTYInfo->yChar; + /* Advance cursor on cur line but not past end. */ + pTTYInfo->nColumn = (CORD)MIN(pTTYInfo->nColumn + screenwidth, + pTTYInfo->actNColumn - 1); + } + else { + /* Wraps across multiple lines. Calculate one rect to cover all + * lines. */ + rect.left = 0; + rect.right = pTTYInfo->xSize; + rect.top = (pTTYInfo->nRow * pTTYInfo->yChar) + pTTYInfo->yOffset; + rect.bottom = ((((offset + nLength) / pTTYInfo->actNColumn) + 1) * + pTTYInfo->yChar) + pTTYInfo->yOffset; + pTTYInfo->nRow = (CORD)(endOffset / pTTYInfo->actNColumn); + pTTYInfo->nColumn = (CORD)(endOffset % pTTYInfo->actNColumn); + } + + + /* Apply text and attributes to screen in one smooth motion. */ + for(i = 0; i < nLength; i++) + (pTTYInfo->pScreen+offset)[i] = lpText[i]; + for(i = 0; i < nLength; i++) + (pTTYInfo->pCellWidth+offset)[i] = wcellwidth((UCS)lpText[i]) * pTTYInfo->xChar; + for(i = 0; i < nLength; i++) + pTTYInfo->pAttrib[offset+i] = pTTYInfo->curAttrib; + + /* Invalidate rectangle */ + pTTYInfo->screenDirty = TRUE; + InvalidateRect (hWnd, &rect, FALSE); + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * BOOL WriteTTYChar (HWND hWnd, char ch) + * + * Description: + * Write a single character to the cursor position and advance the + * cursor. Does not handle carage control. + * + * Parameters: + * HWND hWnd + * handle to TTY window + * + * char ch + * character being written. + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +WriteTTYChar (HWND hWnd, TCHAR ch) +{ + PTTYINFO pTTYInfo; + RECT rect; + long offset; + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + offset = (pTTYInfo->nRow * pTTYInfo->actNColumn) + + pTTYInfo->nColumn; + + *(pTTYInfo->pScreen + offset) = ch; + pTTYInfo->pCellWidth[offset] = wcellwidth((UCS)ch) * pTTYInfo->xChar; + pTTYInfo->pAttrib[offset] = pTTYInfo->curAttrib; + + rect.left = (pTTYInfo->nColumn * pTTYInfo->xChar) + pTTYInfo->xOffset; + rect.right = rect.left + pTTYInfo->xChar; + rect.top = (pTTYInfo->nRow * pTTYInfo->yChar) + pTTYInfo->yOffset; + rect.bottom = rect.top + pTTYInfo->yChar; + pTTYInfo->screenDirty = TRUE; + InvalidateRect (hWnd, &rect, FALSE); + + + + /* Line Wrap. */ + if (pTTYInfo->nColumn < pTTYInfo->actNColumn - 1) + pTTYInfo->nColumn++ ; + else if ((pTTYInfo->autoWrap == WRAP_ON || + pTTYInfo->autoWrap == WRAP_NO_SCROLL) && + pTTYInfo->nRow < pTTYInfo->actNRow - 1) { + pTTYInfo->nRow++; + pTTYInfo->nColumn = 0; + } + return (TRUE); +} + + +/*--------------------------------------------------------------------------- + * VOID GoModalDialogBoxParam( HINSTANCE hInstance, + * LPTSTR lpszTemplate, HWND hWnd, + * DLGPROC lpDlgProc, LPARAM lParam ) + * + * Description: + * It is a simple utility function that simply performs the + * MPI and invokes the dialog box with a DWORD paramter. + * + * Parameters: + * similar to that of DialogBoxParam() with the exception + * that the lpDlgProc is not a procedure instance + * +/*--------------------------------------------------------------------------*/ +LOCAL VOID +GoModalDialogBoxParam( HINSTANCE hInstance, LPTSTR lpszTemplate, + HWND hWnd, DLGPROC lpDlgProc, LPARAM lParam ) +{ + DLGPROC lpProcInstance ; + + lpProcInstance = (DLGPROC) MakeProcInstance( (FARPROC) lpDlgProc, + hInstance ) ; + DialogBoxParam( hInstance, lpszTemplate, hWnd, lpProcInstance, lParam ) ; + FreeProcInstance( (FARPROC) lpProcInstance ) ; +} + + +/*--------------------------------------------------------------------------- + * BOOL FAR PASCAL __export AboutDlgProc( HWND hDlg, UINT uMsg, + * WPARAM wParam, LPARAM lParam ) + * + * Description: + * Simulates the Windows System Dialog Box. + * + * Parameters: + * Same as standard dialog procedures. + * +/*--------------------------------------------------------------------------*/ +BOOL FAR PASCAL __export +AboutDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) { + case WM_INITDIALOG: + { + TCHAR szTemp [81]; + + /* sets up version number for PINE */ + GetDlgItemText (hDlg, IDD_VERSION, szTemp, sizeof(szTemp)/sizeof(TCHAR)); + /* szTemp is unicode, mswin_compilation_date etc are cast as %S in mswin.rc */ + _sntprintf (TempBuf, sizeof(TempBuf)/sizeof(TCHAR), szTemp, mswin_specific_winver(), + mswin_majorver(), mswin_minorver(), + mswin_compilation_remarks(), + mswin_compilation_date()); + SetDlgItemText (hDlg, IDD_VERSION, (LPTSTR) TempBuf); + + /* get by-line */ + LoadString (GET_HINST (hDlg), IDS_BYLINE, TempBuf, + sizeof(TempBuf) / sizeof(TCHAR)); + SetDlgItemText (hDlg, IDD_BYLINE, TempBuf); + + } + return ( TRUE ) ; + + case WM_CLOSE: + EndDialog( hDlg, TRUE ) ; + return ( TRUE ) ; + + case WM_COMMAND: + switch((WORD) wParam){ + case IDD_OK : + EndDialog( hDlg, TRUE ) ; + return ( TRUE ) ; + + default : + break; + } + + break; + } + return ( FALSE ) ; + +} /* end of AboutDlgProc() */ + + +/*--------------------------------------------------------------------------- + * + * Description: + * Simulates the Windows System Dialog Box. + * + * Parameters: + * Same as standard dialog procedures. + * +/*--------------------------------------------------------------------------*/ +BOOL FAR PASCAL __export +SplashDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static HBITMAP hbmpSplash; + + switch (uMsg) { + case WM_INITDIALOG: + if(hbmpSplash = LoadBitmap(GET_HINST(hDlg), + MAKEINTRESOURCE(ALPINESPLASH))){ + BITMAP stBitmap; + int cx, cy; + + cx = GetSystemMetrics(SM_CXSCREEN); + cy = GetSystemMetrics(SM_CYSCREEN); + GetObject(hbmpSplash, sizeof(BITMAP), &stBitmap); + + SetWindowPos(hDlg, HWND_TOPMOST, + (cx - stBitmap.bmWidth) / 2, + (cy - stBitmap.bmHeight) / 2, + stBitmap.bmWidth, stBitmap.bmHeight, + SWP_NOCOPYBITS /* | SWP_SHOWWINDOW */); + } + + return(TRUE); + + case WM_CTLCOLORDLG : + return(TRUE); + + case WM_ERASEBKGND : + { + HDC hMemDC; + POINT stPoint; + BITMAP stBitmap; + HGDIOBJ hObject; + + if((hMemDC = CreateCompatibleDC((HDC) wParam)) != NULL){ + hObject = SelectObject(hMemDC, hbmpSplash); + SetMapMode(hMemDC, GetMapMode((HDC) wParam)); + + GetObject(hbmpSplash, sizeof(BITMAP), &stBitmap); + stPoint.x = stBitmap.bmWidth; + stPoint.y = stBitmap.bmHeight; + DPtoLP((HDC) wParam, &stPoint, 1); + + BitBlt((HDC) wParam, + 0, 0, stPoint.x, stPoint.y, + hMemDC, 0, 0, SRCCOPY); + SelectObject(hMemDC, hObject); + DeleteDC(hMemDC) ; + } + + return(TRUE); + } + + break; + + case WM_CLOSE: + DestroyWindow(hDlg); + return(TRUE); + + case WM_DESTROY : + if(hbmpSplash) + DeleteObject(hbmpSplash); + + break; + + case WM_COMMAND: /* No commands! */ + DestroyWindow(hDlg); + return(TRUE); + } + + return(FALSE); + +} + + +#if 0 +UINT APIENTRY +SelectTTYFontHook(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch(uMsg){ + case WM_INITDIALOG : + { + /* + * Deactivate the Style combo box... + */ + HWND hWnd = GetDlgItem(hDlg, cmb2); + EnableWindow(hWnd, FALSE); + SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_HIDEWINDOW); + return(TRUE); + } + + break; + + case WM_COMMAND : + switch ((WORD) wParam) { + case psh3 : + { + LOGFONT curfont; + + SendMessage(hDlg, WM_CHOOSEFONT_GETLOGFONT, + 0, (LPARAM) &curfont); + ResetTTYFont (ghTTYWnd, gpTTYInfo, &curfont); + return (TRUE); + } + + break; + + default : + break; + } + + break; + + default : + break; + } + + return(FALSE); +} +#endif + + +/*--------------------------------------------------------------------------- + * BOOL SelectTTYFont( HWND hDlg ) + * + * Description: + * Selects the current font for the TTY screen. + * Uses the Common Dialog ChooseFont() API. + * + * Parameters: + * HWND hDlg + * handle to settings dialog + * +/*--------------------------------------------------------------------------*/ +LOCAL BOOL +SelectTTYFont (HWND hWnd) +{ + CHOOSEFONT cfTTYFont; + LOGFONT newFont, origFont; + PTTYINFO pTTYInfo; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return (FALSE); + + memcpy (&newFont, &gpTTYInfo->lfTTYFont, sizeof (LOGFONT)); + memcpy (&origFont, &gpTTYInfo->lfTTYFont, sizeof (LOGFONT)); + + cfTTYFont.lStructSize = sizeof (CHOOSEFONT); + cfTTYFont.hwndOwner = hWnd ; + cfTTYFont.hDC = NULL ; + cfTTYFont.lpLogFont = &newFont; + cfTTYFont.Flags = CF_SCREENFONTS | CF_FIXEDPITCHONLY | + CF_INITTOLOGFONTSTRUCT | +#if 0 + CF_FORCEFONTEXIST | CF_LIMITSIZE | + CF_ENABLEHOOK | CF_APPLY; +#else + CF_FORCEFONTEXIST | CF_LIMITSIZE; +#endif + cfTTYFont.nSizeMin = FONT_MIN_SIZE; + cfTTYFont.nSizeMax = FONT_MAX_SIZE; + cfTTYFont.lCustData = (long) 0 ; +#if 0 + cfTTYFont.lpfnHook = SelectTTYFontHook ; +#else + cfTTYFont.lpfnHook = NULL; +#endif + cfTTYFont.lpTemplateName = NULL ; + cfTTYFont.hInstance = GET_HINST (hWnd); + + + + if (ChooseFont (&cfTTYFont)) { + ResetTTYFont (hWnd, pTTYInfo, &newFont); + } +#if 0 + else{ + ResetTTYFont (hWnd, pTTYInfo, &origFont); + } +#endif + + return (TRUE); +} + + +/* + * Set a specific color (forground, background, reverse, normal) to + * the color specified by name. + */ +LOCAL void +SetColorAttribute (COLORREF *cf, char *colorName) +{ + /* color name not in table. Try converting RGB string. */ + ConvertRGBString (colorName, cf); + + /* Redraw screen. */ + gpTTYInfo->screenDirty = TRUE; + gpTTYInfo->eraseScreen = TRUE; + InvalidateRect (ghTTYWnd, NULL, FALSE); +} + + +/* + * Set current color attribute to reverse color + */ +void +SetReverseColor() +{ + FlushWriteAccum (); + gpTTYInfo->curAttrib.rgbFG = gpTTYInfo->rgbRFGColor; + gpTTYInfo->curAttrib.rgbBG = gpTTYInfo->rgbRBGColor; +} + + +/* + * Convert a string to an integer. + */ +LOCAL BOOL +ScanInt (char *str, int min, int max, int *val) +{ + char *c; + int v; + int neg = 1; + + + if (str == NULL) return (FALSE); + if (*str == '\0' || strlen (str) > 9) return (FALSE); + + /* Check for a negative sign. */ + if (*str == '-') { + neg = -1; + ++str; + } + + /* Check for all digits. */ + for (c = str; *c != '\0'; ++c) { + if (!isdigit((unsigned char)*c)) + return (FALSE); + } + + /* Convert from ascii to int. */ + v = atoi (str) * neg; + + /* Check constraints. */ + if (v < min || v > max) + return (FALSE); + *val = v; + return (TRUE); +} + + +/* + * Convert a RGB string to a color ref. The string should look like: + * rrr,ggg,bbb + * where rrr, ggg, and bbb are numbers between 0 and 255 that represent + * red, gree, and blue values. Must be comma seperated. + * Returns: + * TRUE - Successfully converted string. + * FALSE - Bad format, 'cf' unchanged. + */ +LOCAL BOOL +ConvertRGBString (char *colorName, COLORREF *cf) +{ + int i, j, n, rgb[3]; + MSWINColor *ct; + + if(!colorName) + return(FALSE); + + /* Is the name in the global color table? */ + for(ct = MSWINColorTable; ct->colorName; ct++) + if(!struncmp(ct->colorName, colorName, (int)strlen(ct->colorName))){ + *cf = ct->colorRef; + return(TRUE); + } + + /* Not a named color, try RRR,GGG,BBB */ + for(i = 0; i < 3; i++){ + if(i && *colorName++ != ',') + return(FALSE); + + for(rgb[i] = 0, j = 0; j < 3; j++) + if((n = *colorName++ - '0') < 0 || n > 9) + return(FALSE); + else + rgb[i] = (rgb[i] * 10) + n; + } + + *cf = RGB (rgb[0], rgb[1], rgb[2]); + return (TRUE); +} + + +LOCAL char * +ConvertStringRGB(char *colorName, size_t ncolorName, COLORREF colorRef) +{ + MSWINColor *cf; + + for(cf = MSWINColorTable; + cf->colorName && cf->colorRef != colorRef; + cf++) + ; + + if(cf->colorName){ + strncpy(colorName, cf->colorName, ncolorName); + colorName[ncolorName-1] = '\0'; + } + else + snprintf(colorName, ncolorName, "%.3d,%.3d,%.3d", + GetRValue(colorRef), GetGValue(colorRef), GetBValue(colorRef)); + + return(colorName); +} + + +/* + * Map from integer color value to canonical color name. + */ +char * +colorx(int color) +{ + MSWINColor *ct; + static char cbuf[RGBLEN+1]; + + if(color < fullColorTableSize){ + ct = &MSWINColorTable[color]; + if(ct->canonicalName) + return(ct->canonicalName); + } + + /* not supposed to get here */ + snprintf(cbuf, sizeof(cbuf), "color%03.3d", color); + return(cbuf); +} + + +/* + * Argument is a color name which could be an RGB string, a name like "blue", + * or a name like "color011". + * + * Returns a pointer to the canonical name of the color. + */ +char * +color_to_canonical_name(char *s) +{ + int i, j, n, rgb[3]; + MSWINColor *ct; + COLORREF cr; + static char cn[RGBLEN+1]; + + if(!s) + return(NULL); + + if(!struncmp(s, MATCH_NORM_COLOR, RGBLEN) || !struncmp(s, MATCH_NONE_COLOR, RGBLEN)) + return(s); + + for(ct = MSWINColorTable; ct->colorName; ct++) + if(!struncmp(ct->colorName, s, (int)strlen(ct->colorName))) + break; + + if(ct->colorName) + return(ct->canonicalName); + + /* maybe it is RGB? */ + for(i = 0; i < 3; i++){ + if(i && *s++ != ',') + return(""); + + for(rgb[i] = 0, j = 0; j < 3; j++) + if((n = *s++ - '0') < 0 || n > 9) + return(""); + else + rgb[i] = (rgb[i] * 10) + n; + } + + cr = RGB(rgb[0], rgb[1], rgb[2]); + + /* + * Now compare that RGB against the color table RGBs. If it is + * in the table, return the canonical name, else return the RGB string. + */ + for(ct = MSWINColorTable; ct->colorName; ct++) + if(ct->colorRef == cr) + break; + + if(ct->colorName) + return(ct->canonicalName); + else{ + snprintf(cn, sizeof(cn), "%.3d,%.3d,%.3d", + GetRValue(cr), GetGValue(cr), GetBValue(cr)); + return(cn); + } +} + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Toolbar setup routines. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +LOCAL void +TBToggle (HWND hWnd) +{ + PTTYINFO pTTYInfo; + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + if (pTTYInfo->toolBarSize > 0) + TBHide (hWnd); + else + TBShow (hWnd); +} + + +LOCAL void +TBPosToggle (HWND hWnd) +{ + PTTYINFO pTTYInfo; + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + pTTYInfo->toolBarTop = !pTTYInfo->toolBarTop; + if(pTTYInfo->hTBWnd){ + TBHide (hWnd); + TBShow (hWnd); + } +} + + +LOCAL void +TBShow (HWND hWnd) +{ + PTTYINFO pTTYInfo; + RECT rc; + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + + /* + * Make sure the tool bar not already shown. + */ + if (pTTYInfo->toolBarSize > 0) + return; + + + + /* + * Make procinstance for dialog funciton. + */ + HideCaret (hWnd); + if (gToolBarProc == NULL) + gToolBarProc = (DLGPROC) MakeProcInstance( (FARPROC) ToolBarProc, + ghInstance ) ; + if (gTBBtnProc == NULL) + gTBBtnProc = (WNDPROC) MakeProcInstance( (FARPROC) TBBtnProc, + ghInstance ) ; + + + /* + * Create the dialog box. + */ + pTTYInfo->hTBWnd = CreateDialog (ghInstance, + MAKEINTRESOURCE (pTTYInfo->curToolBarID), + hWnd, + gToolBarProc); + if (pTTYInfo->hTBWnd == NULL) { + ShowCaret (hWnd); + return; + } + + SetFocus (hWnd); + + + /* + * Adjust the window size. + */ + GetWindowRect (pTTYInfo->hTBWnd, &rc); /* Get Toolbar size. */ + pTTYInfo->toolBarSize = (CORD)(rc.bottom - rc.top); + + GetClientRect (hWnd, &rc); /* Get TTY window size. */ + SizeTTY (hWnd, 0, (CORD)rc.bottom, (CORD)rc.right); + ShowCaret (hWnd); +} + + +LOCAL void +TBHide (HWND hWnd) +{ + PTTYINFO pTTYInfo; + RECT rc; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + + if (pTTYInfo->toolBarSize == 0) + return; + + DestroyWindow (pTTYInfo->hTBWnd); + pTTYInfo->hTBWnd = NULL; + if (pTTYInfo->toolBarBtns != NULL) + MemFree (pTTYInfo->toolBarBtns); + pTTYInfo->toolBarBtns = NULL; + + + /* + * Adjust the window size. + */ + pTTYInfo->toolBarSize = 0; + GetClientRect (hWnd, &rc); + SizeTTY (hWnd, 0, (CORD)rc.bottom, (CORD)rc.right); +} + + +LOCAL void +TBSwap (HWND hWnd, int newID) +{ + PTTYINFO pTTYInfo; + RECT rc; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + if (pTTYInfo->toolBarSize == 0 || pTTYInfo->curToolBarID == newID) + return; + + /* + * Dispose of old tool bar window. + */ + HideCaret (hWnd); + + DestroyWindow (pTTYInfo->hTBWnd); + pTTYInfo->hTBWnd = NULL; + if (pTTYInfo->toolBarBtns != NULL) + MemFree (pTTYInfo->toolBarBtns); + pTTYInfo->toolBarBtns = NULL; + + + + /* + * Create the new dialog box. + */ + pTTYInfo->hTBWnd = CreateDialog (ghInstance, + MAKEINTRESOURCE (newID), + hWnd, + gToolBarProc); + if (pTTYInfo->hTBWnd == NULL) { + ShowCaret (hWnd); + return; + } + pTTYInfo->curToolBarID = newID; + SetFocus (hWnd); /* Return focus to parent. */ + + + /* + * Fit new tool bar into old tool bars position. This assumes that + * all tool bars are about the same height. + */ + GetClientRect (hWnd, &rc); /* Get TTY window size. */ + if (pTTYInfo->toolBarTop) + /* Position at top of window. */ + SetWindowPos (pTTYInfo->hTBWnd, HWND_TOP, + 0, 0, + rc.right, pTTYInfo->toolBarSize, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW); + else + /* Position at bottom of window. */ + SetWindowPos (pTTYInfo->hTBWnd, HWND_TOP, + 0, pTTYInfo->ySize - pTTYInfo->toolBarSize, + rc.right, pTTYInfo->toolBarSize, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW); + + ShowCaret (hWnd); +} + + +BOOL FAR PASCAL __export +ToolBarProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + RECT rc; + BOOL ret; + int height; + HBRUSH hBrush; + HWND hCld; + int btnCount; + int i; + PTTYINFO pTTYInfo; + + + pTTYInfo = gpTTYInfo; + + ret = FALSE; + switch (msg) { + + case WM_INITDIALOG: + /* Fit dialog to window. */ + GetWindowRect (hWnd, &rc); + height = rc.bottom - rc.top; + GetClientRect (GetParent (hWnd), &rc); + SetWindowPos (hWnd, HWND_TOP, 0, 0, rc.right, height, + SWP_NOZORDER | SWP_NOACTIVATE); + + /* Count child windows.*/ + btnCount = 0; + for (hCld = GetWindow (hWnd, GW_CHILD); + hCld; + hCld = GetWindow (hCld, GW_HWNDNEXT)) + ++btnCount; + + /* Allocate a list of previous child procs. */ + if (pTTYInfo->toolBarBtns != NULL) + MemFree (pTTYInfo->toolBarBtns); + pTTYInfo->toolBarBtns = MemAlloc (sizeof (BtnList) * (btnCount + 1)); + + /* Subclass all child windows. */ + for (i = 0, hCld = GetWindow (hWnd, GW_CHILD); + hCld; + ++i, hCld = GetWindow (hCld, GW_HWNDNEXT)) { + pTTYInfo->toolBarBtns[i].wndID = GET_ID (hCld); + pTTYInfo->toolBarBtns[i].wndProc = + (WNDPROC)(LONG_PTR)MyGetWindowLongPtr (hCld, + GWLP_WNDPROC); + MySetWindowLongPtr (hCld, GWLP_WNDPROC, (void *)(LONG_PTR)TBBtnProc); + } + pTTYInfo->toolBarBtns[i].wndID = 0; + pTTYInfo->toolBarBtns[i].wndProc = NULL; + + ret = FALSE; + break; + + + case WM_COMMAND: + if (wParam >= KS_RANGESTART && wParam <= KS_RANGEEND){ + ProcessMenuItem (GetParent (hWnd), wParam); + /* Set input focus back to parent. */ + SetFocus (GetParent (hWnd)); + ret = TRUE; + break; + } + break; + +#ifdef WIN32 + case WM_CTLCOLORBTN: +#else + case WM_CTLCOLOR: +#endif + if (HIWORD (lParam) == CTLCOLOR_DLG) { + if(pTTYInfo->hTBBrush != NULL){ + DeleteObject(pTTYInfo->hTBBrush); + pTTYInfo->hTBBrush = NULL; + } + + hBrush = CreateSolidBrush (GetSysColor (COLOR_ACTIVEBORDER)); + return ((BOOL)!!(pTTYInfo->hTBBrush = hBrush)); + } + } + return (ret); +} + + +/* + * Subclass toolbar button windows. + * + * These buttons will automatically return the input focus to + * the toolbar's parent + */ +LRESULT FAR PASCAL __export +TBBtnProc (HWND hBtn, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + PTTYINFO pTTYInfo; + HWND hPrnt; + int i; + WORD id; + LRESULT ret; + WNDPROC wndProc; + + + /* + * Find previous window proc. + */ + pTTYInfo = gpTTYInfo; + id = GET_ID (hBtn); + for (i = 0; pTTYInfo->toolBarBtns[i].wndID != 0; ++i) + if (pTTYInfo->toolBarBtns[i].wndID == id) + goto FoundWindow; + /* Whoops! Didn't find window, don't know how to pass message. */ + return (0); + + +FoundWindow: + wndProc = pTTYInfo->toolBarBtns[i].wndProc; + + + + if (uMsg == WM_LBUTTONUP || uMsg == WM_MBUTTONUP || uMsg == WM_RBUTTONUP) { + /* + * On mouse button up restore input focus to IDC_RESPONCE, which + * processes keyboard input. + */ + ret = CallWindowProc (wndProc, hBtn, uMsg, wParam, lParam); + hPrnt = GetParent (GetParent (hBtn)); + if (hPrnt) + SetFocus (hPrnt); + return (ret); + } + + return (CallWindowProc (wndProc, hBtn, uMsg, wParam, lParam)); +} + + +/* + * return bitmap of allowed Edit Menu items + */ +LOCAL UINT +UpdateEditAllowed(HWND hWnd) +{ + PTTYINFO pTTYInfo; + UINT fAccel = EM_NONE; + + if((pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO)) != NULL){ + if(EditPasteAvailable()) + fAccel |= (EM_PST | EM_PST_ABORT); + else if(IsClipboardFormatAvailable (CF_UNICODETEXT) && gPasteEnabled) + fAccel |= EM_PST; + + if(SelAvailable()){ + fAccel |= (EM_CP | EM_CP_APPEND); + } + else{ + if(gAllowCut) + fAccel |= EM_CUT; + + if(gAllowCopy) + fAccel |= (EM_CP | EM_CP_APPEND); + } + + if (pTTYInfo->menuItems[KS_WHEREIS - KS_RANGESTART].miActive) + fAccel |= EM_FIND; + } + return(fAccel); +} + + +#ifdef ACCELERATORS +#ifdef ACCELERATORS_OPT +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Accelorator key routines. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +LOCAL void +AccelCtl (HWND hWnd, int ctl, BOOL saveChange) +{ + PTTYINFO pTTYInfo; + BOOL load, changed; + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + switch (ctl) { + case ACCEL_LOAD : + load = TRUE; + break; + case ACCEL_UNLOAD : + load = FALSE; + break; + case ACCEL_TOGGLE : + load = pTTYInfo->hAccel == NULL; + break; + default : + load = FALSE; + break; + } + + changed = FALSE; + if (load && pTTYInfo->hAccel == NULL) { + /* Load em up. */ + pTTYInfo->hAccel = LoadAccelerators (ghInstance, + MAKEINTRESOURCE (IDR_ACCEL_PINE)); + changed = TRUE; + } + else if(!load && pTTYInfo->hAccel) { + /* unload em. */ + FreeResource (pTTYInfo->hAccel); + pTTYInfo->hAccel = NULL; + changed = TRUE; + } + + if (changed && saveChange) + DidResize (pTTYInfo); +} +#endif + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Accelorator key routines. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +LOCAL void +AccelManage (HWND hWnd, long accels) +{ + PTTYINFO pTTYInfo; + ACCEL accelarray[EM_MAX_ACCEL]; + int n; + static ACCEL am_cp = { + FVIRTKEY | FCONTROL | FSHIFT | FNOINVERT, 'C', IDM_EDIT_COPY + }; + static ACCEL am_cp_append = { + FVIRTKEY | FCONTROL | FALT | FNOINVERT, 'C', IDM_EDIT_COPY_APPEND + }; + static ACCEL am_find = { + FVIRTKEY | FCONTROL | FSHIFT | FNOINVERT, 'F', IDM_MI_WHEREIS + }; + static ACCEL am_pst = { + FVIRTKEY | FCONTROL | FSHIFT | FNOINVERT, 'V', IDM_EDIT_PASTE + }; + static ACCEL am_pst_abort = { + FVIRTKEY | FCONTROL | FALT | FNOINVERT, 'V', IDM_EDIT_CANCEL_PASTE + }; + static ACCEL am_cut = { + FVIRTKEY | FCONTROL | FSHIFT | FNOINVERT, 'X', IDM_EDIT_CUT + }; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL || pTTYInfo->fAccel == (UINT)accels) + return; + + if(pTTYInfo->hAccel){ + DestroyAcceleratorTable(pTTYInfo->hAccel); + pTTYInfo->hAccel = NULL; + pTTYInfo->fAccel = EM_NONE; + } + + n = 0; + + if(accels & EM_CP) + accelarray[n++] = am_cp; + + if(accels & EM_CP_APPEND) + accelarray[n++] = am_cp_append; + + if(accels & EM_FIND) + accelarray[n++] = am_find; + + if(accels & EM_PST) + accelarray[n++] = am_pst; + + if(accels & EM_PST_ABORT) + accelarray[n++] = am_pst_abort; + + if(accels & EM_CUT) + accelarray[n++] = am_cut; + + /* Load em up. */ + if(n && (pTTYInfo->hAccel = CreateAcceleratorTable(accelarray, n))) + pTTYInfo->fAccel = accels; + else + pTTYInfo->fAccel = EM_NONE; +} + + +LOCAL void +UpdateAccelerators (HWND hWnd) +{ + AccelManage (hWnd, UpdateEditAllowed(hWnd)); +} +#endif + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Mouse Selection routines + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +LOCAL BOOL SelSelected = FALSE; +LOCAL BOOL SelTracking = FALSE; +LOCAL int SelAnchorRow; +LOCAL int SelAnchorCol; +LOCAL int SelPointerRow; +LOCAL int SelPointerCol; +typedef struct { + TCHAR *pRow; + int len; +} CopyRow; + + +LOCAL void +SelRSet (int oStart, int oEnd) +{ + CharAttrib *pca; + + for (pca = gpTTYInfo->pAttrib + oStart; oStart < oEnd; ++pca, ++oStart) + pca->style |= CHAR_ATTR_SEL; +} + + +LOCAL void +SelRClear (int oStart, int oEnd) +{ + CharAttrib *pca; + + for (pca = gpTTYInfo->pAttrib + oStart; oStart < oEnd; ++pca, ++oStart) + pca->style &= ~CHAR_ATTR_SEL; +} + + +LOCAL void +SelRInvalidate (int oStart, int oEnd) +{ + RECT rect; + int sRow, sCol; + int eRow, eCol; + + sRow = oStart / gpTTYInfo->actNColumn; + sCol = oStart % gpTTYInfo->actNColumn; + eRow = oEnd / gpTTYInfo->actNColumn; + eCol = oEnd % gpTTYInfo->actNColumn; + + rect.top = (sRow * gpTTYInfo->yChar) + gpTTYInfo->yOffset; + rect.bottom = ((eRow+1) * gpTTYInfo->yChar) + gpTTYInfo->yOffset; + if (sRow == eRow) { + rect.left = (sCol * gpTTYInfo->xChar) + gpTTYInfo->xOffset; + rect.right = ((eCol+1) * gpTTYInfo->xChar) + gpTTYInfo->xOffset; + } else { + rect.left = gpTTYInfo->xOffset; + rect.right = (gpTTYInfo->actNColumn * gpTTYInfo->xChar) + + gpTTYInfo->xOffset; + } + InvalidateRect (ghTTYWnd, &rect, FALSE); +} + + +/* + * Start a mouse selection. + */ +LOCAL void +SelStart (int nRow, int nColumn) +{ + SelClear (); + SelTracking = TRUE; + SelSelected = TRUE; + SelPointerRow = SelAnchorRow = nRow; + SelPointerCol = SelAnchorCol = nColumn; + return; +} + + +/* + * Finish a mouse selection. + */ +LOCAL void +SelFinish (int nRow, int nColumn) +{ + if (nRow == SelAnchorRow && nColumn == SelAnchorCol) { + /* Mouse up in same place it went down - no selection. */ + SelClear (); + } + else { + /* Update screen selection and set final position of mouse + * then turn of mouse tracking. Selection remains in effect + * until SelClear is called. */ + SelTrackMouse (nRow, nColumn); + SelTracking = FALSE; + } +} + + +LOCAL void +SelClear (void) +{ + int a, p; + int s, e; + + if (!SelSelected) + return; + + /* Convert the anchor and point coordinates to offsets then + * order the offsets. */ + a = (SelAnchorRow * gpTTYInfo->actNColumn) + SelAnchorCol; + p = (SelPointerRow * gpTTYInfo->actNColumn) + SelPointerCol; + if (a < p) { + s = a; + e = p; + } else { + s = p; + e = a; + } + + /* Clear selected attribute of those cells in range. */ + SelRClear (s, e); + SelRInvalidate (s, e); + SelSelected = FALSE; + SelTracking = FALSE; +} + + +/* + * Update the position of the mouse point. + */ +LOCAL void +SelTrackXYMouse (int xPos, int yPos) +{ + int nRow; + int nColumn; + + nColumn = (xPos - gpTTYInfo->xOffset) / gpTTYInfo->xChar; + nRow = (yPos - gpTTYInfo->yOffset) / gpTTYInfo->yChar; + + SelTrackMouse (nRow, nColumn); +} + + +/* + * Update the position of the mouse point. + */ +LOCAL void +SelTrackMouse (int nRow, int nColumn) +{ + int a, p, n; + + if (!SelTracking) + return; + + /* Constrain the cel position to be on the screen. But allow + * for the Column to be one past the right edge of the screen so + * the user can select the right most cel of a row. */ + nColumn = MAX(0, nColumn); + nColumn = MIN(gpTTYInfo->actNColumn, nColumn); + nRow = MAX(0, nRow); + nRow = MIN(gpTTYInfo->actNRow-1, nRow); + + + /* Convert the anchor, previous mouse position, and new mouse + * position to offsets. */ + a = (SelAnchorRow * gpTTYInfo->actNColumn) + SelAnchorCol; + p = (SelPointerRow * gpTTYInfo->actNColumn) + SelPointerCol; + n = (nRow * gpTTYInfo->actNColumn) + nColumn; + + /* If previous position same as current position, do nothing. */ + if (p == n) + return; + + /* there are six possible orderings of the points, each with + * a different action: + * order clear set redraw + * n p a n - p n - p + * p n a p - n p - n + * p a n p - a a - n p - n + * a p n p - n p - n + * a n p n - p n - p + * n a p a - p n - a n - p + */ + if (p < a) { + if (n < a) { + if (n < p) { + SelRSet (n, p); + SelRInvalidate (n, p); + } else { + SelRClear (p, n); + SelRInvalidate (p, n); + } + } else { + SelRClear (p, a); + SelRSet (a, n); + SelRInvalidate (p, n); + } + } else { + if (n > a) { + if (n > p) { + SelRSet (p, n); + SelRInvalidate (p, n); + } else { + SelRClear (n, p); + SelRInvalidate (n, p); + } + } else { + SelRClear (a, p); + SelRSet (n, a); + SelRInvalidate (n, p); + } + } + + /* Set new pointer. */ + SelPointerRow = nRow; + SelPointerCol = nColumn; +} + + +LOCAL BOOL +SelAvailable (void) +{ + return (SelSelected); +} + + +/* + * Copy screen data to clipboard. Actually appends data from screen to + * existing handle so as we can implement a "Copy Append" to append to + * existing clipboard data. + * + * The screen does not have real line terminators. We decide where the + * actual screen data ends by scanning the line (row) from end backwards + * to find the first non-space. + * + * I don't know how many bytes of data I'll be appending to the clipboard. + * So I implemented in two passes. The first finds all the row starts + * and length while the second copies the data. + */ +LOCAL void +SelDoCopy (HANDLE hCB, DWORD lenCB) +{ + HANDLE newCB; /* Used in reallocation. */ + TCHAR *pCB; /* Points to CB data. */ + TCHAR *p2; /* Temp pointer to screen data. */ + int sRow, eRow; /* Start and End Rows. */ + int sCol, eCol; /* Start and End columns. */ + int row, c1, c2; /* temp row and column indexes. */ + int totalLen; /* total len of new data. */ + CopyRow *rowTable, *rp; /* pointers to table of rows. */ + BOOL noLastCRLF = FALSE; + + + if (OpenClipboard (ghTTYWnd)) { /* ...and we get the CB. */ + if (EmptyClipboard ()) { /* ...and clear previous CB.*/ + + + /* Find the start and end row and column. */ + if ( (SelAnchorRow * gpTTYInfo->actNColumn) + SelAnchorCol < + (SelPointerRow * gpTTYInfo->actNColumn) + SelPointerCol) { + sRow = SelAnchorRow; + sCol = SelAnchorCol; + eRow = SelPointerRow; + eCol = SelPointerCol; + } + else { + sRow = SelPointerRow; + sCol = SelPointerCol; + eRow = SelAnchorRow; + eCol = SelAnchorCol; + } + + /* Allocate a table in which we store info on rows. */ + rowTable = (CopyRow *) MemAlloc (sizeof (CopyRow) * (eRow-sRow+1)); + if (rowTable == NULL) + goto Fail1; + + /* Find the start and length of each row. */ + totalLen = 0; + for (row = sRow, rp = rowTable; row <= eRow; ++row, ++rp) { + /* Find beginning and end columns, which depends on if + * this is the first or last row in the selection. */ + c1 = (row == sRow ? sCol : 0); + c2 = (row == eRow ? eCol : gpTTYInfo->actNColumn); + + /* Calculate pointer to beginning of this line. */ + rp->pRow = gpTTYInfo->pScreen + + ((row * gpTTYInfo->actNColumn) + c1); + + /* Back down from end column to find first non space. + * noLastCRLF indicates if it looks like the selection + * should include a CRLF on the end of the line. It + * gets set for each line, but only the setting for the + * last line in the selection is remembered (which is all + * we're interested in). */ + p2 = gpTTYInfo->pScreen + + ((row * gpTTYInfo->actNColumn) + c2); + noLastCRLF = TRUE; + while (c2 > c1) { + if (*(p2-1) != (TCHAR)' ') + break; + noLastCRLF = FALSE; + --c2; + --p2; + } + + /* Calculate size of line, then increment totalLen plus 2 for + * the CRLF which will terminate each line. */ + rp->len = c2 - c1; + totalLen += rp->len + 2; + } + + /* Reallocate the memory block. Add one byte for null terminator. */ + newCB = GlobalReAlloc (hCB, (lenCB + totalLen + 1)*sizeof(TCHAR), 0); + if (newCB == NULL) + goto Fail2; + hCB = newCB; + + pCB = GlobalLock (hCB); + if (pCB == NULL) + goto Fail2; + + /* Append each of the rows, deliminated by a CRLF. */ + pCB += lenCB; + for (row = sRow, rp = rowTable; row <= eRow; ++row, ++rp) { + if (rp->len > 0) { + memcpy (pCB, rp->pRow, rp->len * sizeof(TCHAR)); + pCB += rp->len; + } + if (row < eRow || !noLastCRLF) { + *pCB++ = (TCHAR)ASCII_CR; + *pCB++ = (TCHAR)ASCII_LF; + } + } + *pCB = (TCHAR)'\0'; /* Null terminator. */ + MemFree (rowTable); + GlobalUnlock (hCB); + + /* Attempt to pass the data to the clipboard, then release clipboard + * and exit function. */ + if (SetClipboardData (CF_UNICODETEXT, hCB) == NULL) + /* Failed! Free the data. */ + GlobalFree (hCB); + CloseClipboard (); + } + } + return; + + + /* Error exit. */ +Fail2: MemFree (rowTable); +Fail1: GlobalFree (hCB); + CloseClipboard (); + return; +} + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Upper Layer Screen routines. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* + * Flush the write accumulator buffer. + */ +LOCAL void +FlushWriteAccum (void) +{ + if (gpTTYInfo->writeAccumCount > 0) { + WriteTTYText (ghTTYWnd, gpTTYInfo->writeAccum, + gpTTYInfo->writeAccumCount); + gpTTYInfo->writeAccumCount = 0; + } +} + + +/* + * Set window's title + */ +void +mswin_settitle(char *utf8_title) +{ + TCHAR buf[256]; + LPTSTR lptstr_title; + + lptstr_title = utf8_to_lptstr(utf8_title); + _sntprintf(buf, 256, TEXT("%.*s - Alpine"), 80, lptstr_title); + fs_give((void **) &lptstr_title); + + SetWindowText(ghTTYWnd, buf); +} + + +/* + * Return the application instance. + */ +WINHAND +mswin_gethinstance () +{ + return ((WINHAND)ghInstance); +} + + +WINHAND +mswin_gethwnd () +{ + return ((WINHAND)ghTTYWnd); +} + + +/* + * destroy splash screen + */ +void +mswin_killsplash() +{ + if(ghSplashWnd != NULL){ + DestroyWindow(ghSplashWnd); + ghSplashWnd = NULL; + } +} + + +/* + * Called to get mouse event. + */ +int +mswin_getmouseevent (MEvent * pMouse) +{ + return (MQGet (pMouse)); +} + + +/* + * Make a pop-up menu and track it + */ +int +mswin_popup(MPopup *members) +{ + HMENU hMenu; + POINT point; + MPopup *mp; + int n = 1; + + if(members && (hMenu = CreatePopupMenu())){ + + PopupConfig(hMenu, members, &n); + if(GetCursorPos(&point) + && (n = TrackPopupMenu(hMenu, + TPM_LEFTALIGN | TPM_TOPALIGN + | TPM_RIGHTBUTTON | TPM_LEFTBUTTON + | TPM_NONOTIFY | TPM_RETURNCMD, + point.x, point.y, 0, ghTTYWnd, NULL)) + && (mp = PopupId(members, n))){ + /* Find the member with the internal.id of n */ + n = mp->internal.id - 1; /* item's "selectable" index */ + switch(mp->type){ + case tQueue : + CQAdd (mp->data.val, 0); + break; + + case tMessage : + SendMessage(ghTTYWnd, WM_COMMAND, mp->data.msg, 0); + break; + + default : + break; + } + } + else + n = -1; /* error or nothing selected; same diff */ + + DestroyMenu(hMenu); + } + else + n = -1; /* error */ + + return(n); +} + + +LOCAL void +PopupConfig(HMENU hMenu, MPopup *members, int *n) +{ + MENUITEMINFO mitem; + int index; + TCHAR tcbuf[256]; + + for(index = 0; members->type != tTail; members++, index++){ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE; + + if(members->type == tSubMenu){ + mitem.fMask |= MIIM_SUBMENU; + mitem.fType = MFT_STRING; + if(mitem.hSubMenu = CreatePopupMenu()){ + /* members->label.string is still just a char * */ + _sntprintf(tcbuf, sizeof(tcbuf)/sizeof(TCHAR), + TEXT("%S"), members->label.string); + mitem.dwTypeData = tcbuf; + mitem.cch = (UINT)_tcslen(tcbuf); + InsertMenuItem(hMenu, index, TRUE, &mitem); + + PopupConfig(mitem.hSubMenu, members->data.submenu, n); + } + } + else{ + switch(members->type){ + case tSeparator : + mitem.fType = MFT_SEPARATOR; + break; + + default : + mitem.fMask |= MIIM_ID; + mitem.wID = members->internal.id = (*n)++; + switch(members->label.style){ + case lChecked : + mitem.fMask |= MIIM_STATE; + mitem.fState = MFS_CHECKED; + break; + + case lDisabled : + mitem.fMask |= MIIM_STATE; + mitem.fState = MFS_GRAYED; + break; + + case lNormal : + default : + break; + } + + mitem.fType = MFT_STRING; + _sntprintf(tcbuf, sizeof(tcbuf)/sizeof(TCHAR), + TEXT("%S"), members->label.string); + mitem.dwTypeData = tcbuf; + mitem.cch = (UINT)_tcslen(tcbuf); + break; + } + + InsertMenuItem(hMenu, index, TRUE, &mitem); + } + } +} + + +LOCAL MPopup * +PopupId(MPopup *members, int id) +{ + MPopup *mp; + + for(; members->type != tTail; members++) + switch(members->type){ + case tSubMenu : + if(mp = PopupId(members->data.submenu, id)) + return(mp); + + break; + + case tSeparator : + break; + + default : + if(members->internal.id == id) + return(members); + + break; + } + + return(NULL); +} + + +/* + * Make a pop-up offering Copy/Cut/Paste menu and track it + */ +LOCAL BOOL +CopyCutPopup(HWND hWnd, UINT fAllowed) +{ + MENUITEMINFO mitem; + HMENU hMenu; + POINT point; + BOOL rv = FALSE; + int n = -1; + + /* + * If nothing to do just silently return + */ + if(fAllowed != EM_NONE && (hMenu = CreatePopupMenu())){ + if(fAllowed & EM_CUT){ + /* Insert a "Copy" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID; + mitem.wID = IDM_EDIT_CUT; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Cut"); + mitem.cch = 3; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + } + + if(fAllowed & EM_CP){ + /* Insert a "Copy" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID; + mitem.wID = IDM_EDIT_COPY; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Copy"); + mitem.cch = 4; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + } + + if(fAllowed & EM_CP_APPEND){ + /* Insert a "Copy Append" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID; + mitem.wID = IDM_EDIT_COPY_APPEND; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Copy Append"); + mitem.cch = 11; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + } + + if(fAllowed & EM_PST){ + /* Insert a "Paste" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID; + mitem.wID = IDM_EDIT_PASTE; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Paste"); + mitem.cch = 5; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + } + + if((fAllowed & EM_SEL_ALL) && !(fAllowed & (EM_CP | EM_CP_APPEND))){ + /* Insert a "Select All" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID; + mitem.wID = IDM_EDIT_SEL_ALL; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Select &All"); + mitem.cch = 11; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + } + + if(n >= 0 && GetCursorPos(&point)){ + TrackPopupMenu(hMenu, + TPM_LEFTALIGN | TPM_TOPALIGN + | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, + point.x, point.y, 0, hWnd, NULL); + rv = TRUE; + } + + DestroyMenu(hMenu); + } + + return(rv); +} + + + +/* + * + */ +void +pico_popup() +{ + MENUITEMINFO mitem; + HMENU hMenu; + POINT point; + UINT fAllow; + int n = -1; + + /* + * If nothing to do just silently return + */ + if(hMenu = CreatePopupMenu()){ + + if((fAllow = UpdateEditAllowed(ghTTYWnd)) != EM_NONE){ + /* Insert a "Cut" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; + mitem.wID = IDM_EDIT_CUT; + mitem.fState = (fAllow & EM_CUT) ? MFS_ENABLED : MFS_GRAYED; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Cut"); + mitem.cch = 3; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + + /* Insert a "Copy" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; + mitem.wID = IDM_EDIT_COPY; + mitem.fState = (fAllow & EM_CP) ? MFS_ENABLED : MFS_GRAYED; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Copy"); + mitem.cch = 4; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + + /* Insert a "Copy Append" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; + mitem.wID = IDM_EDIT_COPY_APPEND; + mitem.fState = (fAllow & EM_CP_APPEND) ? MFS_ENABLED : MFS_GRAYED; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Copy Append"); + mitem.cch = 11; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + + /* Insert a "Paste" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; + mitem.wID = IDM_EDIT_PASTE; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Paste"); + mitem.fState = (fAllow & EM_PST) ? MFS_ENABLED : MFS_GRAYED; + mitem.cch = 5; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + } + + /* Insert a "Select All" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; + mitem.wID = IDM_EDIT_SEL_ALL; + mitem.fType = MFT_STRING; + mitem.fState = (fAllow & (EM_CP | EM_CP_APPEND)) + ? MFS_GRAYED : MFS_ENABLED; + mitem.dwTypeData = TEXT("Select &All"); + mitem.cch = 11; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + + if(n >= 0 && GetCursorPos(&point)) + TrackPopupMenu(hMenu, + TPM_LEFTALIGN | TPM_TOPALIGN + | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, + point.x, point.y, 0, ghTTYWnd, NULL); + + DestroyMenu(hMenu); + } +} + + + +/* + * + */ +void +mswin_paste_popup() +{ + MENUITEMINFO mitem; + HMENU hMenu; + POINT point; + UINT fAllow; + int n = -1; + + /* + * If nothing to do just silently return + */ + if(hMenu = CreatePopupMenu()){ + + if((fAllow = UpdateEditAllowed(ghTTYWnd)) != EM_NONE){ + /* Insert a "Paste" option */ + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; + mitem.wID = IDM_EDIT_PASTE; + mitem.fType = MFT_STRING; + mitem.dwTypeData = TEXT("Paste"); + mitem.fState = (fAllow & EM_PST) ? MFS_ENABLED : MFS_GRAYED; + mitem.cch = 5; + InsertMenuItem(hMenu, ++n, FALSE, &mitem); + + if(n >= 0 && GetCursorPos(&point)) + TrackPopupMenu(hMenu, + TPM_LEFTALIGN | TPM_TOPALIGN + | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, + point.x, point.y, 0, ghTTYWnd, NULL); + } + + DestroyMenu(hMenu); + } +} + + + +void +mswin_keymenu_popup() +{ + HMENU hBarMenu, hMenu; + MENUITEMINFO mitem; + POINT point; + int i, j, n; + TCHAR tcbuf[256]; + + /* + * run thru menubar from left to right and down each list building + * a popup of all active members. we run down the menu's rather + * than thru the menuItems array so we can preserve order... + */ + if((hMenu = CreatePopupMenu())){ + if(hBarMenu = GetMenu(ghTTYWnd)){ + /* For each possible index, look for matching registered key */ + for(n = 0, i = 1; i <= KS_COUNT; i++) + for(j = 0; j < KS_COUNT; j++) + if(gpTTYInfo->menuItems[j].miIndex == i){ + if(gpTTYInfo->menuItems[j].miActive == TRUE + && gpTTYInfo->menuItems[j].miLabel){ + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_ID | MIIM_STATE | MIIM_TYPE; + mitem.wID = j + KS_RANGESTART; + mitem.fState = MFS_ENABLED; + mitem.fType = MFT_STRING; + /* miLabel is still plain old char *, not utf8 */ + _sntprintf(tcbuf, sizeof(tcbuf)/sizeof(TCHAR), + TEXT("%S"), gpTTYInfo->menuItems[j].miLabel); + mitem.dwTypeData = tcbuf; + mitem.cch = (UINT)_tcslen(tcbuf); + InsertMenuItem(hMenu, n++, TRUE, &mitem); + + if(j + KS_RANGESTART == IDM_MI_SCREENHELP + && gHelpCallback){ + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_ID | MIIM_STATE | MIIM_TYPE; + mitem.wID = IDM_HELP; + mitem.fState = MFS_ENABLED; + mitem.fType = MFT_STRING; + _sntprintf(tcbuf, sizeof(tcbuf)/sizeof(TCHAR), + TEXT("%S"), "Help in New Window"); + mitem.dwTypeData = tcbuf; + mitem.cch = (UINT)_tcslen(tcbuf); + InsertMenuItem(hMenu, n++, TRUE, &mitem); + } + } + + break; + } + + if(n > 0 && GetCursorPos(&point)){ + TrackPopupMenu(hMenu, + TPM_LEFTALIGN | TPM_TOPALIGN + | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, + point.x, point.y, 0, ghTTYWnd, NULL); + } + } + + DestroyMenu(hMenu); + } +} + + + +/* + * + */ +void +mswin_registericon(int row, int id, char *utf8_file) +{ + LPTSTR sPathCopy; + HICON hIcon; + WORD iIcon; + IconList *pIcon; + + /* Turn this off until it can get tuned */ + return; + + /* Already registered? */ + for(pIcon = gIconList; pIcon; pIcon = pIcon->next) + if(pIcon->id == id){ + pIcon->row = (short)row; + return; + } + + sPathCopy = utf8_to_lptstr(utf8_file); + + if(hIcon = ExtractAssociatedIcon(ghInstance, sPathCopy, &iIcon)) + MSWIconAddList(row, id, hIcon); + + fs_give((void **) &sPathCopy); +} + + +void +mswin_destroyicons() +{ + MSWIconFree(&gIconList); +} + + +void +MSWIconAddList(int row, int id, HICON hIcon) +{ + IconList **ppIcon; + + for(ppIcon = &gIconList; *ppIcon; ppIcon = &(*ppIcon)->next) + ; + + *ppIcon = (IconList *) MemAlloc (sizeof (IconList)); + memset(*ppIcon, 0, sizeof(IconList)); + (*ppIcon)->hIcon = hIcon; + (*ppIcon)->id = id; + (*ppIcon)->row = (short)row; +} + + +int +MSWIconPaint(int row, HDC hDC) +{ + IconList *pIcon; + int rv = 0; + + for(pIcon = gIconList; pIcon && pIcon->row != row; pIcon = pIcon->next) + ; + + if(pIcon){ + /* Invalidate rectange covering singel character. */ + DrawIconEx(hDC, 0, (row * gpTTYInfo->yChar) + gpTTYInfo->yOffset, + pIcon->hIcon, 2 * gpTTYInfo->xChar, gpTTYInfo->yChar, + 0, NULL, DI_NORMAL); + + rv = 1; + } + + return(rv); +} + + +void +MSWIconFree(IconList **ppIcon) +{ + if(ppIcon && *ppIcon){ + if((*ppIcon)->next) + MSWIconFree(&(*ppIcon)->next); + + DestroyIcon((*ppIcon)->hIcon); + MemFree (*ppIcon); + *ppIcon = NULL; + } +} + + +/* + * Set up debugging stuff. + */ +void +mswin_setdebug (int debug, FILE *debugfile) +{ + /* Accept new file only if we don't already have a file. */ + if (mswin_debugfile == 0) { + mswin_debug = debug; + mswin_debugfile = debugfile; + MemDebug (debug, mswin_debugfile); + } +} + + +void +mswin_setnewmailwidth (int width) +{ + gNMW_width = width; + gNMW_width = gNMW_width < 20 ? 20 : gNMW_width; + gNMW_width = gNMW_width > 170 ? 170 : gNMW_width; +} + + +/* + * Event handler to deal with File Drop events + */ +LOCAL BOOL +ProcessTTYFileDrop (HANDLE wDrop) +{ + HDROP hDrop = wDrop; + POINT pos; + int col, row, i, n; + TCHAR fname[1024]; + char *utf8_fname; + + if(!gpTTYInfo->dndhandler) + return(FALSE); + + /* translate drop point to character cell */ + DragQueryPoint(hDrop, &pos); + col = (pos.x - gpTTYInfo->xOffset) / gpTTYInfo->xChar; + if (pos.x < gpTTYInfo->xOffset) + --col; + row = (pos.y - gpTTYInfo->yOffset) / gpTTYInfo->yChar; + if (pos.y < gpTTYInfo->yOffset) + --row; + + for(n = DragQueryFile(hDrop, (UINT)-1, NULL, 0), i = 0; i < n; i++){ + DragQueryFile(hDrop, i, fname, 1024); + utf8_fname = lptstr_to_utf8(fname); + gpTTYInfo->dndhandler (row, col, utf8_fname); + fs_give((void **) &utf8_fname); + } + + DragFinish(hDrop); + + set_time_of_last_input(); + + return(TRUE); +} + + +/* + * Set a callback to deal with Drag 'N Drop events + */ +int +mswin_setdndcallback (int (*cb)()) +{ + if(gpTTYInfo->dndhandler) + gpTTYInfo->dndhandler = NULL; + + if(cb){ + gpTTYInfo->dndhandler = cb; + DragAcceptFiles(ghTTYWnd, TRUE); + } + + return(1); +} + + +/* + * Clear previously installed callback to handle Drag 'N Drop + * events + */ +int +mswin_cleardndcallback () +{ + gpTTYInfo->dndhandler = NULL; + DragAcceptFiles(ghTTYWnd, FALSE); + return(1); +} + + +/* + * Set a callback for function 'ch' + */ +int +mswin_setresizecallback (int (*cb)()) +{ + int i; + int e; + + + /* + * Look through whole array for this call back function. Don't + * insert duplicate. Also look for empty slot. + */ + e = -1; + for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) { + if (gpTTYInfo->resizer[i] == cb) + return (0); + if (e == -1 && gpTTYInfo->resizer[i] == NULL) + e = i; + } + + /* + * Insert in empty slot or return an error. + */ + if (e != -1) { + gpTTYInfo->resizer[e] = cb; + return (0); + } + return (-1); +} + + +/* + * Clear all instances of the callback function 'cb' + */ +int +mswin_clearresizecallback (int (*cb)()) +{ + int i; + int status; + + status = -1; + for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) { + if (gpTTYInfo->resizer[i] == cb) { + gpTTYInfo->resizer[i] = NULL; + status = 0; + } + } + return (status); +} + + +void +mswin_beginupdate (void) +{ + gpTTYInfo->fMassiveUpdate = TRUE; +} + + +void +mswin_endupdate (void) +{ + gpTTYInfo->fMassiveUpdate = FALSE; + MoveTTYCursor (ghTTYWnd); +} + + +int +mswin_charsetid2string(LPTSTR fontCharSet, size_t nfontCharSet, BYTE lfCharSet) +{ + TCHAR buf[1024]; + + buf[0] = '\0'; + switch(lfCharSet){ + case DEFAULT_CHARSET: + break; + case ANSI_CHARSET: + _tcsncpy(buf, TEXT("ANSI_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case OEM_CHARSET: + _tcsncpy(buf, TEXT("OEM_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case BALTIC_CHARSET: + _tcsncpy(buf, TEXT("BALTIC_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case CHINESEBIG5_CHARSET: + _tcsncpy(buf, TEXT("CHINESE_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case EASTEUROPE_CHARSET: + _tcsncpy(buf, TEXT("EASTEUROPE_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case GB2312_CHARSET: + _tcsncpy(buf, TEXT("GF2312_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case GREEK_CHARSET: + _tcsncpy(buf, TEXT("GREEK_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case HANGUL_CHARSET: + _tcsncpy(buf, TEXT("HANGUL_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case MAC_CHARSET: + _tcsncpy(buf, TEXT("MAC_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case RUSSIAN_CHARSET: + _tcsncpy(buf, TEXT("RUSSIAN_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case SHIFTJIS_CHARSET: + _tcsncpy(buf, TEXT("SHIFTJIS_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case SYMBOL_CHARSET: + _tcsncpy(buf, TEXT("SYMBOL_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case TURKISH_CHARSET: + _tcsncpy(buf, TEXT("TURKISH_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case VIETNAMESE_CHARSET: + _tcsncpy(buf, TEXT("VIETNAMESE_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case JOHAB_CHARSET: + _tcsncpy(buf, TEXT("JOHAB_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case ARABIC_CHARSET: + _tcsncpy(buf, TEXT("ARABIC_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case HEBREW_CHARSET: + _tcsncpy(buf, TEXT("HEBREW_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + case THAI_CHARSET: + _tcsncpy(buf, TEXT("THAI_CHARSET"), sizeof(buf)/sizeof(TCHAR)); + break; + default: + /* default char set if we can't figure it out */ + break; + } + + buf[sizeof(buf)/sizeof(TCHAR) - 1] = '\0'; + + _tcsncpy(fontCharSet, buf, nfontCharSet); + fontCharSet[nfontCharSet-1] = '\0'; + + return 0; +} + + +BYTE +mswin_string2charsetid(LPTSTR str) +{ + TCHAR tstr[1024]; + + if(!str || (lstrlen(str) >= 1024)) + return DEFAULT_CHARSET; + + if((lstrlen(str) > lstrlen(TEXT("_CHARSET"))) + && (tcsucmp(str + lstrlen(str) - lstrlen(TEXT("_CHARSET")), TEXT("_CHARSET")) == 0)){ + _tcsncpy(tstr, str, 1024); + tstr[lstrlen(str) - lstrlen(TEXT("_CHARSET"))] = '\0'; + tstr[1024-1] = '\0'; + if(tcsucmp(tstr, TEXT("ANSI")) == 0) + return ANSI_CHARSET; + else if(tcsucmp(tstr, TEXT("DEFAULT")) == 0) + return DEFAULT_CHARSET; + else if(tcsucmp(tstr, TEXT("OEM")) == 0) + return OEM_CHARSET; + else if(tcsucmp(tstr, TEXT("BALTIC")) == 0) + return BALTIC_CHARSET; + else if(tcsucmp(tstr, TEXT("CHINESEBIG5")) == 0) + return CHINESEBIG5_CHARSET; + else if(tcsucmp(tstr, TEXT("EASTEUROPE")) == 0) + return EASTEUROPE_CHARSET; + else if(tcsucmp(tstr, TEXT("GB2312")) == 0) + return GB2312_CHARSET; + else if(tcsucmp(tstr, TEXT("GREEK")) == 0) + return GREEK_CHARSET; + else if(tcsucmp(tstr, TEXT("HANGUL")) == 0) + return HANGUL_CHARSET; + else if(tcsucmp(tstr, TEXT("MAC")) == 0) + return MAC_CHARSET; + else if(tcsucmp(tstr, TEXT("RUSSIAN")) == 0) + return RUSSIAN_CHARSET; + else if(tcsucmp(tstr, TEXT("SHIFTJIS")) == 0) + return SHIFTJIS_CHARSET; + else if(tcsucmp(tstr, TEXT("SYMBOL")) == 0) + return SYMBOL_CHARSET; + else if(tcsucmp(tstr, TEXT("TURKISH")) == 0) + return TURKISH_CHARSET; + else if(tcsucmp(tstr, TEXT("VIETNAMESE")) == 0) + return VIETNAMESE_CHARSET; + else if(tcsucmp(tstr, TEXT("JOHAB")) == 0) + return JOHAB_CHARSET; + else if(tcsucmp(tstr, TEXT("ARABIC")) == 0) + return ARABIC_CHARSET; + else if(tcsucmp(tstr, TEXT("HEBREW")) == 0) + return HEBREW_CHARSET; + else if(tcsucmp(tstr, TEXT("THAI")) == 0) + return THAI_CHARSET; + else + return DEFAULT_CHARSET; + } + else{ + if(_tstoi(str) > 0) + return((BYTE)(_tstoi(str))); + else + return DEFAULT_CHARSET; + } +} + + +int +mswin_setwindow(char *fontName_utf8, char *fontSize_utf8, char *fontStyle_utf8, + char *windowPosition, char *caretStyle, char *fontCharSet_utf8) +{ + LOGFONT newFont; + int newHeight; + HDC hDC; + int ppi; + RECT wndRect, cliRect; + char *c; + char *n; + int i; + BOOL toolBar = FALSE; + BOOL toolBarTop = TRUE; + int wColumns, wRows; + int wXPos, wYPos; + int wXBorder, wYBorder; + int wXSize, wYSize; + char wp[WIN_POS_STR_MAX_LEN + 1]; + int showWin = 1; + LPTSTR fontName_lpt = NULL; + LPTSTR fontCharSet_lpt; + +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "mswin_setwindow::: entered, minimized: %d\n", + gpTTYInfo->fMinimized); +#endif + + /* Require a font name to set font info. */ + if(fontName_utf8 != NULL && *fontName_utf8 != '\0' && + (fontName_lpt = utf8_to_lptstr(fontName_utf8)) && + _tcslen(fontName_lpt) <= LF_FACESIZE - 1){ + + hDC = GetDC(ghTTYWnd); + ppi = GetDeviceCaps(hDC, LOGPIXELSY); + ReleaseDC(ghTTYWnd, hDC); + + /* Default height, then examin the requested fontSize. */ + newFont.lfHeight = -MulDiv(12, ppi, 72); + if(ScanInt(fontSize_utf8, FONT_MIN_SIZE, FONT_MAX_SIZE, &newHeight)){ + newHeight = abs(newHeight); + newFont.lfHeight = -MulDiv(newHeight, ppi, 72); + } + + /* Default font style, then read requested style. */ + newFont.lfWeight = 0; + newFont.lfItalic = 0; + if(fontStyle_utf8 != NULL){ + _strlwr(fontStyle_utf8); + if(strstr(fontStyle_utf8, "bold")) + newFont.lfWeight = FW_BOLD; + if(strstr(fontStyle_utf8, "italic")) + newFont.lfItalic = 1; + } + + /* Everything else takes the default. */ + newFont.lfWidth = 0; + newFont.lfEscapement = 0; + newFont.lfOrientation = 0; + newFont.lfUnderline = 0; + newFont.lfStrikeOut = 0; + fontCharSet_lpt = utf8_to_lptstr(fontCharSet_utf8); + if(fontCharSet_lpt){ + newFont.lfCharSet = mswin_string2charsetid(fontCharSet_lpt); + fs_give((void **) &fontCharSet_lpt); + } + + newFont.lfOutPrecision = OUT_DEFAULT_PRECIS; + newFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + newFont.lfQuality = DEFAULT_QUALITY; + newFont.lfPitchAndFamily = FIXED_PITCH; + _sntprintf(newFont.lfFaceName, LF_FACESIZE, TEXT("%s"), fontName_lpt); + ResetTTYFont (ghTTYWnd, gpTTYInfo, &newFont); + } + + if(fontName_lpt) + fs_give((void **) &fontName_lpt); + + /* + * Set window position. String format is: CxR+X+Y + * Where C is the number of columns, R is the number of rows, + * and X and Y specify the top left corner of the window. + */ + if(windowPosition != NULL && *windowPosition != '\0'){ + if(strlen(windowPosition) > sizeof(wp)-1) + return(0); + + strncpy(wp, windowPosition, sizeof(wp)); + wp[sizeof(wp)-1] = '\0'; + + /* + * Flag characters are at the end of the string. Strip them + * off till we get to a number. + */ + i = (int)strlen(wp) - 1; + while(i > 0 && (*(wp+i) < '0' || *(wp+i) > '9')){ + if(*(wp+i) == 't' || *(wp+i) == 'b'){ + toolBar = TRUE; + toolBarTop = (*(wp+i) == 't'); + } + + if(*(wp+i) == 'd') + gfUseDialogs = TRUE; + +#ifdef ACCELERATORS_OPT + if(*(wp+i) == 'a') + AccelCtl(ghTTYWnd, ACCEL_LOAD, FALSE); +#endif + + *(wp+i) = 0; + --i; + } + + if(strcmp(wp, "MIN0") == 0){ + mswin_killsplash(); + showWin = 0; + ShowWindow(ghTTYWnd, SW_MINIMIZE); + if(toolBar){ + gpTTYInfo->toolBarTop = toolBarTop; + TBShow(ghTTYWnd); + } + } + else{ + /* Look for Columns, deliminated by 'x'. */ + c = strchr (wp, 'x'); + if (c == NULL) + return(0); + + *c = '\0'; + if(!ScanInt(wp, -999, 9999, &wColumns)) + return(0); + + /* Look for Rows, deliminated by '+'. */ + n = c + 1; + c = strchr(n, '+'); + if(c == NULL) + return (0); + + *c = '\0'; + if(!ScanInt(n, -999, 9999, &wRows)) + return(0); + + /* Look for X position, deliminated by '+'. */ + n = c + 1; + c = strchr(n, '+'); + if(c == NULL) + return(0); + + *c = '\0'; + if(!ScanInt(n, -999, 9999, &wXPos)) + return(0); + + /* And get Y position, deliminated by end of string. */ + n = c + 1; + if(!ScanInt(n, -999, 9999, &wYPos)) + return(0); + + + /* Constrain the window position and size. */ + if(wXPos < 0) + wXPos = 0; + + if(wYPos < 0) + wYPos = 0; + + GetWindowRect(GetDesktopWindow(), &wndRect); + if(wXPos > wndRect.right - 20) + wXPos = wndRect.right - 100; + + if(wYPos > wndRect.bottom - 20) + wYPos = wndRect.bottom - 100; + + /* Get the current window rect and client area. */ + GetWindowRect(ghTTYWnd, &wndRect); + GetClientRect(ghTTYWnd, &cliRect); + + /* Calculate boarder sizes. */ + wXBorder = wndRect.right - wndRect.left - cliRect.right; + wYBorder = wndRect.bottom - wndRect.top - cliRect.bottom; + + /* Show toolbar before calculating content size. */ + if(toolBar){ + gpTTYInfo->toolBarTop = toolBarTop; + TBShow(ghTTYWnd); + } + + /* Calculate new window pos and size. */ + wXSize = wXBorder + (wColumns * gpTTYInfo->xChar) + + (2 * gpTTYInfo->xOffset); + wYSize = wYBorder + (wRows * gpTTYInfo->yChar) + + gpTTYInfo->toolBarSize + (2 * MARGINE_TOP); + if(!gpTTYInfo->fMinimized) + MoveWindow(ghTTYWnd, wXPos, wYPos, wXSize, wYSize, TRUE); + else{ + gpTTYInfo->fDesiredSize = TRUE; + gpTTYInfo->xDesPos = (CORD)wXPos; + gpTTYInfo->yDesPos = (CORD)wYPos; + gpTTYInfo->xDesSize = (CORD)wXSize; + gpTTYInfo->yDesSize = (CORD)wYSize; + } + } + } + + if(caretStyle != NULL && *caretStyle != '\0') + for(i = 0; MSWinCaretTable[i].name; i++) + if(!strucmp(MSWinCaretTable[i].name, caretStyle)){ + CaretTTY(ghTTYWnd, MSWinCaretTable[i].style); + break; + } + + return(0); +} + + +int +mswin_showwindow() +{ + mswin_killsplash(); + ShowWindow (ghTTYWnd, SW_SHOWNORMAL); + UpdateWindow (ghTTYWnd); + + return(0); +} + + +/* + * Retreive the current font name, font size, and window position + * These get stored in the pinerc file and will be passed to + * mswin_setwindow() when pine starts up. See pinerc for comments + * on the format. + */ +int +mswin_getwindow(char *fontName_utf8, size_t nfontName, + char *fontSize_utf8, size_t nfontSize, + char *fontStyle_utf8, size_t nfontStyle, + char *windowPosition, size_t nwindowPosition, + char *foreColor, size_t nforeColor, + char *backColor, size_t nbackColor, + char *caretStyle, size_t ncaretStyle, + char *fontCharSet_utf8, size_t nfontCharSet) +{ + HDC hDC; + int ppi; + RECT wndRect; + char *t; + + if(fontName_utf8 != NULL){ + t = lptstr_to_utf8(gpTTYInfo->lfTTYFont.lfFaceName); + if(strlen(t) < nfontName) + snprintf(fontName_utf8, nfontName, "%s", t); + + fs_give((void **) &t); + } + + if(fontCharSet_utf8 != NULL){ + LPTSTR lpt; + + lpt = (LPTSTR) fs_get(nfontCharSet * sizeof(TCHAR)); + if(lpt){ + lpt[0] = '\0'; + mswin_charsetid2string(lpt, nfontCharSet, gpTTYInfo->lfTTYFont.lfCharSet); + t = lptstr_to_utf8(lpt); + if(strlen(t) < nfontCharSet) + snprintf(fontCharSet_utf8, nfontCharSet, "%s", t); + + fs_give((void **) &t); + + fs_give((void **) &lpt); + } + } + + if(fontSize_utf8 != NULL){ + hDC = GetDC(ghTTYWnd); + ppi = GetDeviceCaps(hDC, LOGPIXELSY); + ReleaseDC(ghTTYWnd, hDC); + snprintf(fontSize_utf8, nfontSize, "%d", MulDiv(-gpTTYInfo->lfTTYFont.lfHeight, 72, ppi)); + } + + if(fontStyle_utf8 != NULL){ + char *sep[] = {"", ", "}; + int iSep = 0; + + *fontStyle_utf8 = '\0'; + if(gpTTYInfo->lfTTYFont.lfWeight >= FW_BOLD){ + strncpy(fontStyle_utf8, "bold", nfontStyle); + fontStyle_utf8[nfontStyle-1] = '\0'; + iSep = 1; + } + + if(gpTTYInfo->lfTTYFont.lfItalic){ + strncat(fontStyle_utf8, sep[iSep], nfontStyle-strlen(fontStyle_utf8)-1); + fontStyle_utf8[nfontStyle-1] = '\0'; + strncat(fontStyle_utf8, "italic", nfontStyle-strlen(fontStyle_utf8)-1); + fontStyle_utf8[nfontStyle-1] = '\0'; + } + } + + if(windowPosition != NULL){ + if(gpTTYInfo->fMinimized){ + strncpy(windowPosition, "MIN0", nwindowPosition); + windowPosition[nwindowPosition-1] = '\0'; + } + else{ + /* + * Get the window position. Constrain the top left corner + * to be on the screen. + */ + GetWindowRect(ghTTYWnd, &wndRect); + if(wndRect.left < 0) + wndRect.left = 0; + + if(wndRect.top < 0) + wndRect.top = 0; + + snprintf(windowPosition, nwindowPosition, "%dx%d+%d+%d", gpTTYInfo->actNColumn, + gpTTYInfo->actNRow, wndRect.left, wndRect.top); + } + + if(gpTTYInfo->toolBarSize > 0){ + strncat(windowPosition, gpTTYInfo->toolBarTop ? "t" : "b", + nwindowPosition-strlen(windowPosition)-1); + windowPosition[nwindowPosition-1] = '\0'; + } + + if(gfUseDialogs){ + strncat(windowPosition, "d", nwindowPosition-strlen(windowPosition)-1); + windowPosition[nwindowPosition-1] = '\0'; + } + + if(gpTTYInfo->hAccel){ + strncat(windowPosition, "a", nwindowPosition-strlen(windowPosition)-1); + windowPosition[nwindowPosition-1] = '\0'; + } + + if(gpTTYInfo->fMaximized){ + strncat(windowPosition, "!", nwindowPosition-strlen(windowPosition)-1); + windowPosition[nwindowPosition-1] = '\0'; + } + } + + if(foreColor != NULL) + ConvertStringRGB(foreColor, nforeColor, gpTTYInfo->rgbFGColor); + + if(backColor != NULL) + ConvertStringRGB(backColor, nbackColor, gpTTYInfo->rgbBGColor); + + if(caretStyle != NULL){ + int i; + + for(i = 0; MSWinCaretTable[i].name; i++) + if(MSWinCaretTable[i].style == gpTTYInfo->cCaretStyle){ + strncpy(caretStyle, MSWinCaretTable[i].name, ncaretStyle); + caretStyle[ncaretStyle-1] = '\0'; + break; + } + } + + return (0); +} + + +void +mswin_noscrollupdate(int flag) +{ + gpTTYInfo->noScrollUpdate = (flag != 0); + if(flag == 0 && gpTTYInfo->scrollRangeChanged){ + mswin_setscrollrange(gpTTYInfo->noSUpdatePage, gpTTYInfo->noSUpdateRange); + gpTTYInfo->noSUpdatePage = gpTTYInfo->noSUpdateRange = 0; + gpTTYInfo->scrollRangeChanged = 0; + } +} + + +/* + * Set the scroll range. + */ +void +mswin_setscrollrange (long page, long max) +{ + SCROLLINFO scrollInfo; + + if(gpTTYInfo->noScrollUpdate){ + gpTTYInfo->noSUpdatePage = page; + gpTTYInfo->noSUpdateRange = max; + gpTTYInfo->scrollRangeChanged = 1; + return; + } + if (max != gpTTYInfo->scrollRange) { + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_DISABLENOSCROLL | SIF_PAGE | SIF_RANGE; + scrollInfo.nMin = 0; + + if (max > 0) { + scrollInfo.nMax = (int) max; + scrollInfo.nPage = page; + SetScrollInfo(ghTTYWnd, SB_VERT, &scrollInfo, TRUE); + EnableScrollBar (ghTTYWnd, SB_VERT, ESB_ENABLE_BOTH); + } + else { + max = 0; + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask |= SIF_POS; + scrollInfo.nMax = 1; + scrollInfo.nPos = 0; + SetScrollInfo(ghTTYWnd, SB_VERT, &scrollInfo, TRUE); + EnableScrollBar (ghTTYWnd, SB_VERT, ESB_DISABLE_BOTH); + gpTTYInfo->scrollPos = 0; + } + gpTTYInfo->scrollRange = (int)max; + } +} + + +/* + * Set the current scroll position. + */ +void +mswin_setscrollpos (long pos) +{ + SCROLLINFO scrollInfo; + + if (pos != gpTTYInfo->scrollPos) { + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_PAGE | SIF_RANGE; + + GetScrollInfo(ghTTYWnd, SB_VERT, &scrollInfo); + + scrollInfo.fMask |= SIF_POS; + scrollInfo.nPos = (int) pos; + SetScrollInfo(ghTTYWnd, SB_VERT, &scrollInfo, TRUE); + + gpTTYInfo->scrollPos = pos; + } +} + + +/* + * retreive the current scroll postion. + */ +long +mswin_getscrollpos (void) +{ + return ((long)((float)GetScrollPos (ghTTYWnd, SB_VERT))); +} + + +/* + * retreive the latest scroll to position. + */ +long +mswin_getscrollto (void) +{ + return (gpTTYInfo->scrollTo); +} + +/* + * install function to deal with LINEDOWN events + */ +void +mswin_setscrollcallback (cbarg_t cbfunc) +{ + gScrollCallback = cbfunc; +} + + +/* + * install function to deal with sort menu messages + */ +void +mswin_setsortcallback (cbarg_t cbfunc) +{ + gSortCallback = cbfunc; +} + + +/* + * install function to deal with sort menu messages + */ +void +mswin_setflagcallback (cbarg_t cbfunc) +{ + gFlagCallback = cbfunc; +} + + +void +mswin_set_erasecreds_callback (cbvoid_t cbfunc) +{ + gEraseCredsCallback = cbfunc; +} + +/* + * install function to deal with header mode flipping + */ +void +mswin_sethdrmodecallback (cbarg_t cbfunc) +{ + gHdrCallback = cbfunc; +} + + +/* + * install function to deal with view in new window messages + */ +void +mswin_setviewinwindcallback (cbvoid_t cbfunc) +{ + gViewInWindCallback = cbfunc; +} + + +/* + * install function to deal with zoom mode flipping + */ +void +mswin_setzoomodecallback (cbarg_t cbfunc) +{ + gZoomCallback = cbfunc; +} + + +/* + * install function to deal with function key mode flipping + */ +void +mswin_setfkeymodecallback (cbarg_t cbfunc) +{ + gFkeyCallback = cbfunc; +} + + +/* + * install function to deal with apply mode flipping + */ +void +mswin_setselectedcallback (cbarg_t cbfunc) +{ + gSelectedCallback = cbfunc; +} + + +/* + * return 1 if new mail window is being used + */ +int +mswin_newmailwinon (void) +{ + return(gMswinNewMailWin.hwnd ? 1 : 0); +} + + +/* + */ +void +mswin_setdebugoncallback (cbvoid_t cbfunc) +{ + gIMAPDebugONCallback = cbfunc; +} + + +void +mswin_setdebugoffcallback (cbvoid_t cbfunc) +{ + gIMAPDebugOFFCallback = cbfunc; +} + + +int +mswin_setconfigcallback (cbvoid_t cffunc) +{ + gConfigScreenCallback = cffunc; + return(1); +} + + +/* + * Set the printer font + */ +void +mswin_setprintfont(char *fontName, char *fontSize, + char *fontStyle, char *fontCharSet) +{ + LPTSTR fn = NULL, fstyle = NULL, fc = NULL; + + if(fontName) + fn = utf8_to_lptstr(fontName); + + if(fontStyle) + fstyle = utf8_to_lptstr(fontStyle); + + if(fontCharSet) + fc = utf8_to_lptstr(fontCharSet); + + /* Require a font name to set font info. */ + if(fn != NULL && *fn != '\0' && lstrlen(fn) <= LF_FACESIZE - 1){ + + _tcsncpy(gPrintFontName, fn, sizeof(gPrintFontName)/sizeof(TCHAR)); + gPrintFontName[sizeof(gPrintFontName)/sizeof(TCHAR)-1] = 0; + if(fstyle){ + _tcsncpy(gPrintFontStyle, fstyle, sizeof(gPrintFontStyle)/sizeof(TCHAR)); + gPrintFontStyle[sizeof(gPrintFontStyle)/sizeof(TCHAR)-1] = 0; + _tcslwr(gPrintFontStyle); /* Lower case font style. */ + } + + if(fc){ + _tcsncpy(gPrintFontCharSet, fc, sizeof(gPrintFontCharSet)/sizeof(TCHAR)); + gPrintFontCharSet[sizeof(gPrintFontCharSet)/sizeof(TCHAR)-1] = 0; + } + + gPrintFontSize = 12; + if(ScanInt(fontSize, FONT_MIN_SIZE, FONT_MAX_SIZE, &gPrintFontSize)) + gPrintFontSize = abs(gPrintFontSize); + + gPrintFontSameAs = FALSE; + } + else{ + gPrintFontName[0] = '\0'; + gPrintFontSameAs = TRUE; + } + + if(fn) + fs_give((void **) &fn); + + if(fstyle) + fs_give((void **) &fstyle); + + if(fc) + fs_give((void **) &fc); +} + + +void +mswin_getprintfont(char *fontName_utf8, size_t nfontName, + char *fontSize_utf8, size_t nfontSize, + char *fontStyle_utf8, size_t nfontStyle, + char *fontCharSet_utf8, size_t nfontCharSet) +{ + if(gPrintFontName[0] == '\0' || gPrintFontSameAs){ + if(fontName_utf8 != NULL) + *fontName_utf8 = '\0'; + if(fontSize_utf8 != NULL) + *fontSize_utf8 = '\0'; + if(fontStyle_utf8 != NULL) + *fontStyle_utf8 = '\0'; + if(fontCharSet_utf8 != NULL) + *fontCharSet_utf8 = '\0'; + } + else{ + char *u; + + if(fontName_utf8 != NULL){ + u = lptstr_to_utf8(gPrintFontName); + if(u){ + strncpy(fontName_utf8, u, nfontName); + fontName_utf8[nfontName-1] = 0; + fs_give((void **) &u); + } + } + + if(fontSize_utf8 != NULL) + snprintf(fontSize_utf8, nfontSize, "%d", gPrintFontSize); + + + if(fontStyle_utf8 != NULL){ + u = lptstr_to_utf8(gPrintFontStyle); + if(u){ + strncpy(fontStyle_utf8, u, nfontStyle); + fontStyle_utf8[nfontStyle-1] = 0; + fs_give((void **) &u); + } + } + + if(fontCharSet_utf8 != NULL){ + u = lptstr_to_utf8(gPrintFontCharSet); + if(u){ + strncpy(fontCharSet_utf8, u, nfontCharSet); + fontCharSet_utf8[nfontCharSet-1] = 0; + fs_give((void **) &u); + } + } + } +} + + +/* + * Set the window help text. Add or delete the Help menu item as needed. + */ +int +mswin_sethelptextcallback(cbstr_t cbfunc) +{ + HMENU hMenu; + + gHelpCallback = cbfunc; + + hMenu = GetMenu (ghTTYWnd); + if (hMenu == NULL) + return (1); + + return(MSWHelpSetMenu (hMenu)); + +} + + +/* + * Set the window help text. Add or delete the Help menu item as needed. + */ +int +mswin_setgenhelptextcallback(cbstr_t cbfunc) +{ + HMENU hMenu; + + gHelpGenCallback = cbfunc; + + hMenu = GetMenu (ghTTYWnd); + if (hMenu == NULL) + return (1); + + return(MSWHelpSetMenu (hMenu)); + +} + + +int +MSWHelpSetMenu(HMENU hMenu) +{ + BOOL brc; + int count; + + /* + * Find menu and update it. + */ + count = GetMenuItemCount (hMenu); + if (count == -1) + return (1); + + hMenu = GetSubMenu (hMenu, count - 1); + if (hMenu == NULL) + return (1); + + /* + * Insert or delete generic help item + */ + if (gHelpGenCallback == NULL){ + if (gfHelpGenMenu) { + brc = DeleteMenu (hMenu, IDM_MI_GENERALHELP, MF_BYCOMMAND); + DrawMenuBar (ghTTYWnd); + } + gfHelpGenMenu = FALSE; + } + else { + if (!gfHelpGenMenu) { + brc = InsertMenu (hMenu, 0, + MF_BYPOSITION | MF_STRING, + IDM_MI_GENERALHELP, TEXT("&General Help")); + DrawMenuBar (ghTTYWnd); + } + gfHelpGenMenu = TRUE; + } + + /* + * Insert or delete screen help item + */ + if (gHelpCallback == NULL){ + if (gfHelpMenu) { + brc = DeleteMenu (hMenu, IDM_HELP, MF_BYCOMMAND); + DrawMenuBar (ghTTYWnd); + } + gfHelpMenu = FALSE; + } + else { + if (!gfHelpMenu) { + brc = InsertMenu (hMenu, gHelpGenCallback ? 2 : 1, + MF_BYPOSITION | MF_STRING, + IDM_HELP, TEXT("Screen Help in &New Window")); + DrawMenuBar (ghTTYWnd); + } + gfHelpMenu = TRUE; + } + + return (0); +} + + +/* + * Set the text displayed when someone tries to close the application + * the wrong way. + */ +void +mswin_setclosetext (char *pCloseText) +{ + gpCloseText = pCloseText; +} + + +/* + * Called when upper layer is in a busy loop. Allows us to yeild to + * Windows and perhaps process some events. Does not yeild control + * to other applications. + */ +int +mswin_yield (void) +{ + MSG msg; + DWORD start; + int didmsg = FALSE; + + if (gScrolling) + return (TRUE); + + start = GetTickCount (); +#ifdef CDEBUG + if (mswin_debug > 16) + fprintf (mswin_debugfile, "mswin_yeild:: Entered\n"); +#endif + if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { + if (gpTTYInfo->hAccel == NULL || + !TranslateAccelerator (ghTTYWnd, gpTTYInfo->hAccel, &msg)) { + TranslateMessage (&msg); + DispatchMessage (&msg); + didmsg = TRUE; + } + } +#ifdef CDEBUG + if (mswin_debug > 16) + fprintf (mswin_debugfile, "mswin_yeild:: Delay %ld msec\n", + GetTickCount () - start); +#endif + return (didmsg); +} + + +/* + * Called to see if we can process input. + * We can't process input when we are in a scrolling mode. + */ +int +mswin_caninput (void) +{ + return (!gScrolling && !gMouseTracking); +} + + +/* + * Called to see if there is a character available. + */ +int +mswin_charavail (void) +{ + MSG msg; + BOOL ca, pa, ma; + + if (gScrolling) + return (FALSE); + + RestoreMouseCursor(); + + /* + * If there are no windows messages waiting for this app, GetMessage + * can take a long time. So before calling GetMessage check if + * there is anything I should be doing. If there is, then only + * call PeekMessage. + * BUT! Don't let too much time elapse between calls to GetMessage + * or we'll shut out other windows. + */ + ca = CQAvailable (); + pa = EditPasteAvailable (); +#ifdef CDEBUG + start = GetTickCount (); + if (mswin_debug > 16) + fprintf (mswin_debugfile, "%s mswin_charavail:: Entered, ca %d, pa %d\n",dtime(),ca,pa); +#endif + if ((ca || pa) && GetTickCount () < gGMLastCall + GM_MAX_TIME) + ma = PeekMessage (&msg, NULL, 0, 0, PM_NOYIELD | PM_REMOVE); + else { + ma = GetMessage (&msg, NULL, 0, 0); + gGMLastCall = GetTickCount (); + } + if (ma) { + if (gpTTYInfo->hAccel == NULL || + !TranslateAccelerator (ghTTYWnd, gpTTYInfo->hAccel, &msg)) { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + } +#ifdef CDEBUG + if (mswin_debug > 16) + fprintf (mswin_debugfile, "%s mswin_charavail:: Delay %ld msec\n", + dtime(), GetTickCount () - start); +#endif + + return (pa || ca || CQAvailable ()); +} + + +/* + * Call to get next character. Dispatch one message. + */ +UCS +mswin_getc (void) +{ + BOOL ca, pa, ma; + MSG msg; + + if (gScrolling) + return (NODATA); + + RestoreMouseCursor(); + + mswin_flush(); + + + /* + * If there are no windows messages waiting for this app, GetMessage + * can take a long time. So before calling GetMessage check if + * there is anything I should be doing. If there is, then only + * call PeekMessage. + */ + ca = CQAvailable (); + pa = EditPasteAvailable (); +#ifdef CDEBUG + if (mswin_debug > 16) { + start = GetTickCount (); + fprintf (mswin_debugfile, "mswin_getc:: Entered, ca %d pa %d\n", ca, pa); + } +#endif + if ((ca || pa) && GetTickCount () < gGMLastCall + GM_MAX_TIME) + ma = PeekMessage (&msg, NULL, 0, 0, PM_NOYIELD | PM_REMOVE); + else { + ma = GetMessage (&msg, NULL, 0, 0); + gGMLastCall = GetTickCount (); + } + if (ma) { + if (gpTTYInfo->hAccel == NULL || + !TranslateAccelerator (ghTTYWnd, gpTTYInfo->hAccel, &msg)) { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + } +#ifdef CDEBUG + if (mswin_debug > 16) + fprintf (mswin_debugfile, "mswin_getc:: Delay %ld msec\n", + GetTickCount () - start); +#endif + + + if (pa) { + SelClear (); + return (EditPasteGet ()); + } + if (ca || CQAvailable ()) { + SelClear(); + return ((UCS)(CQGet ())); + } + + return (NODATA); +} + + +/* + * Like mswin_getc, but don't yield control. Returns a CTRL key values + * where, for example, ctrl+c --> CTRL|'C'. + */ +LOCAL UCS +mswin_getc_fast (void) +{ + RestoreMouseCursor(); + + if (EditPasteAvailable ()) { + SelClear (); + return (EditPasteGet ()); + } + if (CQAvailable ()) { + SelClear (); + return ((UCS)CQGet ()); + } + + return (NODATA); +} + + +/* + * Flush the character input queue. + */ +void +mswin_flush_input(void) +{ + /* + * GetQueueStatus tells us if there are any input messages in the message + * queue. If there are, we call mswin_getc which will retrieve the + * next message (which may or may not be an input message) and process + * it. We do that until all the input messages are gone. + */ + CQInit(); + + while(GetQueueStatus(QS_INPUT)) + (void) mswin_getc(); + + /* And we clear Pine's character input queue, too. */ + CQInit(); +} + + +/* + * ibmmove - Move cursor to... + */ +int +mswin_move (int row, int column) + +{ + FlushWriteAccum (); + if (row < 0 || row >= gpTTYInfo->actNRow) + return (-1); + if (column < 0 || column >= gpTTYInfo->actNColumn) + return (-1); + gpTTYInfo->nRow = (CORD)row; + gpTTYInfo->nColumn = (CORD)column; + MoveTTYCursor (ghTTYWnd); + return (0); +} + + +int +mswin_getpos (int *row, int *column) +{ + FlushWriteAccum (); + if (row == NULL || column == NULL) + return (-1); + *row = gpTTYInfo->nRow; + *column = gpTTYInfo->nColumn; + return (0); +} + + +int +mswin_getscreensize (int *row, int *column) +{ + if (row == NULL || column == NULL) + return (-1); + *row = gpTTYInfo->actNRow; + *column = gpTTYInfo->actNColumn; +#ifdef SDEBUG + if (mswin_debug >= 5) + fprintf (mswin_debugfile, "mswin_getscreensize::: reported size %d, %d\n", + *row, *column); +#endif + + *column = MIN(*column, NLINE-1); + + return (0); +} + + +void +mswin_minimize() +{ + if (!gpTTYInfo->fMinimized) + ShowWindow(ghTTYWnd, SW_MINIMIZE); +} + + +int +mswin_showcursor (int show) +{ + int was_shown = gpTTYInfo->fCursorOn; + + if (show) { + /* if the cursor was not already show, show it now. */ + if (!was_shown) { + gpTTYInfo->fCursorOn = TRUE; + ShowCursor(TRUE); + } + } + else { + /* If the cursor is shown, hide it. */ + if (was_shown){ + gpTTYInfo->fCursorOn = FALSE; + ShowCursor(FALSE); + } + } + + return (was_shown); +} + + +int +mswin_showcaret (int show) +{ + int was_shown = gpTTYInfo->fCaretOn; + + if (show) { + /* if the caret was not already show, show it now. */ + if (!was_shown) { + gpTTYInfo->fCaretOn = TRUE; + ShowCaret(ghTTYWnd); + } + } + else { + /* If the caret is shown, hide it. */ + if (was_shown){ + gpTTYInfo->fCaretOn = FALSE; + HideCaret(ghTTYWnd); + } + } + + return (was_shown); +} + + +int +mswin_puts (char *utf8_str) +{ + int strLen; + LPTSTR lptstr_str; + + FlushWriteAccum (); + if (utf8_str == NULL) + return (-1); + if(!(lptstr_str = utf8_to_lptstr(utf8_str))) + return(-1); + strLen = (int)_tcslen (lptstr_str); + if (strLen > 0) + WriteTTYText (ghTTYWnd, lptstr_str, strLen); + + fs_give((void **) &lptstr_str); + return (0); +} + + +int +mswin_puts_n (char *utf8_str, int n) +{ + LPTSTR lptstr_str, lptstr_p; + + FlushWriteAccum (); + if (utf8_str == NULL) + return (-1); + lptstr_str = utf8_to_lptstr(utf8_str); + if(n < _tcslen(lptstr_str)) + lptstr_str[n] = '\0'; + for (lptstr_p = lptstr_str; n > 0 && *lptstr_p; n--, lptstr_p++) + ; + if (lptstr_p > lptstr_str) + WriteTTYText (ghTTYWnd, lptstr_str, (int)(lptstr_p - lptstr_str)); + + fs_give((void **) &lptstr_str); + return (0); +} + + +int +mswin_putblock (char *utf8_str, int strLen) +{ + LPTSTR lptstr_str; + + FlushWriteAccum (); + if (utf8_str == NULL) + return (-1); + lptstr_str = utf8_to_lptstr(utf8_str); + if (strLen > 0) + WriteTTYText (ghTTYWnd, lptstr_str, strLen); + fs_give((void **) &lptstr_str); + return (0); +} + + +/* + * mswin_putc - put a character at the current position in the + * current colors + */ +int +mswin_putc (UCS ucs) +{ + TCHAR cc = (TCHAR)ucs; + if (ucs >= (UCS)(' ')) { + /* Not carriage control. */ + gpTTYInfo->writeAccum[gpTTYInfo->writeAccumCount++] = (TCHAR)ucs; + if (gpTTYInfo->writeAccumCount == WRITE_ACCUM_SIZE) + FlushWriteAccum (); + } + else { + /* Carriage control. Need to flush write accumulator and + * write this char. */ + FlushWriteAccum (); + WriteTTYBlock (ghTTYWnd, &cc, 1); + } + return (0); +} + + +/* + * ibmoutc - output a single character with the right attributes, but + * don't advance the cursor + */ +int +mswin_outc (char c) +{ + RECT rect; + long offset; + + FlushWriteAccum (); + + switch (c) { + case ASCII_BEL: + MessageBeep (0); + break; + + case ASCII_BS: + case ASCII_CR: + case ASCII_LF: + /* Do nothing for these screen motion characters. */ + break; + + + default: + /* Paint character to screen. */ + offset = (gpTTYInfo->nRow * gpTTYInfo->actNColumn) + gpTTYInfo->nColumn; + gpTTYInfo->pScreen[offset] = c; + gpTTYInfo->pCellWidth[offset] = wcellwidth((UCS)c) * gpTTYInfo->xChar; + gpTTYInfo->pAttrib[offset] = gpTTYInfo->curAttrib; + + /* Invalidate rectange covering singel character. */ + rect.left = (gpTTYInfo->nColumn * gpTTYInfo->xChar) + + gpTTYInfo->xOffset; + rect.right = rect.left + gpTTYInfo->xChar; + rect.top = (gpTTYInfo->nRow * gpTTYInfo->yChar) + + gpTTYInfo->yOffset; + rect.bottom = rect.top + gpTTYInfo->yChar; + gpTTYInfo->screenDirty = TRUE; + InvalidateRect (ghTTYWnd, &rect, FALSE); + break; + } + return (0); +} + +/* + * ibmrev - change reverse video state + */ +int +mswin_rev (int state) +{ + int curState = (gpTTYInfo->curAttrib.style & CHAR_ATTR_REV) != 0; + + if (state != curState){ + FlushWriteAccum (); + if (state) { + gpTTYInfo->curAttrib.style |= CHAR_ATTR_REV; + SetReverseColor(); + } + else{ + gpTTYInfo->curAttrib.style &= ~CHAR_ATTR_REV; + pico_set_normal_color(); + } + } + + return (0); +} + + +/* + * Get current reverse video state. + */ +int +mswin_getrevstate (void) +{ + return ((gpTTYInfo->curAttrib.style & CHAR_ATTR_REV) != 0); +} + + +/* + * ibmrev - change reverse video state + */ +int +mswin_bold (int state) +{ + int curState = (gpTTYInfo->curAttrib.style & CHAR_ATTR_BOLD) != 0; + + if (state != curState){ + FlushWriteAccum (); + if (state) + gpTTYInfo->curAttrib.style |= CHAR_ATTR_BOLD; + else + gpTTYInfo->curAttrib.style &= ~CHAR_ATTR_BOLD; + } + + return (0); +} + + +/* + * ibmrev - change reverse video state + */ +int +mswin_uline (int state) +{ + int curState = (gpTTYInfo->curAttrib.style & CHAR_ATTR_ULINE) != 0; + + if (state != curState){ + FlushWriteAccum (); + if (state) + gpTTYInfo->curAttrib.style |= CHAR_ATTR_ULINE; + else + gpTTYInfo->curAttrib.style &= ~CHAR_ATTR_ULINE; + } + + return (0); +} + + +/* + * ibmeeol - erase to the end of the line + */ +int +mswin_eeol (void) +{ + TCHAR *cStart; + CharAttrib *aStart; + int *cwStart; + long length, i; + RECT rect; + + FlushWriteAccum (); + + /* From current position to end of line. */ + length = gpTTYInfo->actNColumn - gpTTYInfo->nColumn; + + cStart = gpTTYInfo->pScreen + (gpTTYInfo->nRow * gpTTYInfo->actNColumn) + + gpTTYInfo->nColumn; + cwStart = gpTTYInfo->pCellWidth + (gpTTYInfo->nRow * gpTTYInfo->actNColumn) + + gpTTYInfo->nColumn; + for(i = 0; i < length; i++){ + cStart[i] = (TCHAR)(' '); + cwStart[i] = gpTTYInfo->xChar; + } + + aStart = gpTTYInfo->pAttrib + + (gpTTYInfo->nRow * gpTTYInfo->actNColumn) + + gpTTYInfo->nColumn; + for(; length > 0; length--, aStart++){ + aStart->style = CHAR_ATTR_NORM; + aStart->rgbFG = gpTTYInfo->curAttrib.rgbFG; + aStart->rgbBG = gpTTYInfo->curAttrib.rgbBG; + } + + rect.left = (gpTTYInfo->nColumn * gpTTYInfo->xChar) + + gpTTYInfo->xOffset; + rect.right = gpTTYInfo->xSize; + rect.top = (gpTTYInfo->nRow * gpTTYInfo->yChar) + + gpTTYInfo->yOffset; + rect.bottom = rect.top + gpTTYInfo->yChar; + gpTTYInfo->screenDirty = TRUE; + InvalidateRect (ghTTYWnd, &rect, FALSE); + + return (0); +} + + +/* + * ibmeeop - clear from cursor to end of page + */ +int +mswin_eeop (void) +{ + TCHAR *cStart; + CharAttrib *aStart; + int *cwStart; + long length, i; + RECT rect; + + FlushWriteAccum (); + /* From current position to end of screen. */ + + cStart = gpTTYInfo->pScreen + (gpTTYInfo->nRow * gpTTYInfo->actNColumn) + + gpTTYInfo->nColumn; + cwStart = gpTTYInfo->pCellWidth + (gpTTYInfo->nRow * gpTTYInfo->actNColumn) + + gpTTYInfo->nColumn; + length = (long)((gpTTYInfo->pScreen + + (gpTTYInfo->actNColumn * gpTTYInfo->actNRow)) + - cStart); + for(i = 0; i < length; i ++){ + cStart[i] = (TCHAR)(' '); + cwStart[i] = gpTTYInfo->xChar; + } + + aStart = gpTTYInfo->pAttrib + + (gpTTYInfo->nRow * gpTTYInfo->actNColumn) + + gpTTYInfo->nColumn; + + for(; length-- > 0; aStart++){ + aStart->style = CHAR_ATTR_NORM; + aStart->rgbFG = gpTTYInfo->curAttrib.rgbFG; + aStart->rgbBG = gpTTYInfo->curAttrib.rgbBG; + } + + /* Invalidate a rectangle that includes all of the current line down + * to the bottom of the window. */ + rect.left = 0; + rect.right = gpTTYInfo->xSize; + rect.top = (gpTTYInfo->nRow * gpTTYInfo->yChar) + + gpTTYInfo->yOffset; + rect.bottom = gpTTYInfo->ySize; + gpTTYInfo->screenDirty = TRUE; + InvalidateRect (ghTTYWnd, &rect, FALSE); + + return (0); +} + + +/* + * ibmbeep - system beep... + */ +int +mswin_beep (void) +{ + MessageBeep (MB_OK); + return (0); +} + + +/* + * pause - wait in function for specified number of seconds. + */ +void +mswin_pause (int seconds) +{ + DWORD stoptime; + + stoptime = GetTickCount () + (DWORD) seconds * 1000; + while (stoptime > GetTickCount ()) + mswin_yield (); +} + + +/* + * ibmflush - Flush output to screen. + * + */ +int +mswin_flush (void) +{ + + /* + * Flush cached changes, then update the window. + */ + FlushWriteAccum (); + UpdateWindow (ghTTYWnd); + + return (0); +} + + +/* + * A replacement for fflush + * relies on #define fflush mswin_fflush + */ +#undef fflush +int +mswin_fflush (FILE *f) +{ + if (f == stdout) { + mswin_flush(); + } + else + fflush (f); + + return(0); +} + + +/* + * Set the cursor + */ +void +mswin_setcursor (int newcursor) +{ + HCURSOR hNewCursor; + + switch(newcursor){ + case MSWIN_CURSOR_BUSY : + hNewCursor = ghCursorBusy; + mswin_showcursor(TRUE); + break; + + case MSWIN_CURSOR_IBEAM : + hNewCursor = ghCursorIBeam; + break; + + case MSWIN_CURSOR_HAND : + if(hNewCursor = ghCursorHand) + break; + /* ELSE fall thru to normal cursor */ + + case MSWIN_CURSOR_ARROW : + default : + hNewCursor = ghCursorArrow; + break; + } + + /* If new cursor requested, select it. */ + if (ghCursorCurrent != hNewCursor) + SetCursor (ghCursorCurrent = hNewCursor); +} + + +void +RestoreMouseCursor() +{ + if(ghCursorCurrent == ghCursorBusy) + mswin_setcursor(MSWIN_CURSOR_ARROW); +} + + +/* + * Display message in windows dialog box. + */ +void +mswin_messagebox (char *msg_utf8, int err) +{ + LPTSTR msg_lptstr; + + mswin_killsplash(); + + msg_lptstr = utf8_to_lptstr(msg_utf8); + MessageBox (NULL, msg_lptstr, gszAppName, + MB_OK | ((err) ? MB_ICONSTOP : MB_ICONINFORMATION)); + fs_give((void **) &msg_lptstr); +} + + +/* + * Signals whether or not Paste should be turned on in the + * menu bar. + */ +void +mswin_allowpaste (int on) +{ + static short stackp = 0; + static unsigned long stack = 0L; + + switch(on){ + case MSWIN_PASTE_DISABLE : + if(stackp){ /* previous state? */ + if((stackp -= 2) < 0) + stackp = 0; + + gPasteEnabled = ((stack >> stackp) & 0x03); + } + else + gPasteEnabled = MSWIN_PASTE_DISABLE; + + break; + + case MSWIN_PASTE_FULL : + case MSWIN_PASTE_LINE : + if(gPasteEnabled){ /* current state? */ + stack |= ((unsigned long) gPasteEnabled << stackp); + stackp += 2; + } + + gPasteEnabled = on; + break; + } + +#ifdef ACCELERATORS + UpdateAccelerators(ghTTYWnd); +#endif +} + + +/* + * Signals whether or not Copy/Cut should be turned on in the + * menu bar. + */ +void +mswin_allowcopy (getc_t copyfunc) +{ + gCopyCutFunction = copyfunc; + gAllowCopy = (copyfunc != NULL); +#ifdef ACCELERATORS + UpdateAccelerators(ghTTYWnd); +#endif +} + + +/* + * Signals whether or not Copy/Cut should be turned on in the + * menu bar. + */ +void +mswin_allowcopycut (getc_t copyfunc) +{ + gCopyCutFunction = copyfunc; + gAllowCopy = gAllowCut = (copyfunc != NULL); +#ifdef ACCELERATORS + UpdateAccelerators(ghTTYWnd); +#endif +} + + +/* + * Replace the clipboard's contents with the given string + */ +void +mswin_addclipboard(char *s) +{ + HANDLE hCB; + char *pCB; + size_t sSize; + + if(s && (sSize = strlen(s))){ + if (OpenClipboard (ghTTYWnd)) { /* ...and we get the CB. */ + if (EmptyClipboard () + && (hCB = GlobalAlloc (GMEM_MOVEABLE, sSize+2))){ + pCB = GlobalLock (hCB); + memcpy (pCB, s, sSize); + pCB[sSize] = '\0'; /* tie it off */ + + GlobalUnlock (hCB); + + if (SetClipboardData (CF_TEXT, hCB) == NULL) + /* Failed! Free the data. */ + GlobalFree (hCB); + } + + CloseClipboard (); + } + } +} + + +/* + * Signals if the upper layer wants to track the mouse. + */ +void +mswin_allowmousetrack (int b) +{ + gAllowMouseTrack = b; + if (b) + SelClear (); + MyTimerSet (); +} + + +/* + * register's callback to warn + */ +void +mswin_mousetrackcallback(cbarg_t cbfunc) +{ + if(!(gMouseTrackCallback = cbfunc)) + mswin_setcursor (MSWIN_CURSOR_ARROW); +} + + +/* + * Add text to the new mail icon. + * Nothing done in win 3.1 (consider changing the program name?) + * For win95 we add some tool tip text to the tray icon. + */ +void +mswin_newmailtext (char *t_utf8) +{ + LPTSTR t_lptstr; + + /* + * If we're given text, then blip the icon to let the user know. + * (NOTE: the new shell also gets an updated tooltip.) + * Otherwise, we're being asked to resume our normal state... + */ + if(t_utf8){ + /* + * Change the appearance of minimized icon so user knows there's new + * mail waiting for them. On win 3.1 systems we redraw the icon. + * on win95 systems we update the icon in the task bar, + * and possibly udpate the small icon in the taskbar tool tray. + */ + t_lptstr = utf8_to_lptstr(t_utf8); + UpdateTrayIcon(NIM_MODIFY, ghNewMailIcon, t_lptstr); + fs_give((void **) &t_lptstr); + PostMessage(ghTTYWnd,WM_SETICON,ICON_BIG,(LPARAM) ghNewMailIcon); + PostMessage(ghTTYWnd,WM_SETICON,ICON_SMALL,(LPARAM) ghNewMailIcon); + + gpTTYInfo->fNewMailIcon = TRUE; + } + else if(gpTTYInfo->fNewMailIcon) { + UpdateTrayIcon(NIM_MODIFY, ghNormalIcon, TEXT("Alpine")); + PostMessage(ghTTYWnd,WM_SETICON,ICON_BIG,(LPARAM) ghNormalIcon); + PostMessage(ghTTYWnd,WM_SETICON,ICON_SMALL,(LPARAM) ghNormalIcon); + + gpTTYInfo->fNewMailIcon = FALSE; + } +} + + +void +mswin_mclosedtext (char *t_utf8) +{ + LPTSTR t_lptstr; + + if(t_utf8 && gpTTYInfo->fMClosedIcon == FALSE){ + /* + * Change the appearance of minimized icon so user knows + * the mailbox closed. + */ + t_lptstr = utf8_to_lptstr(t_utf8); + UpdateTrayIcon(NIM_MODIFY, ghMClosedIcon, t_lptstr); + fs_give((void **) &t_lptstr); + PostMessage(ghTTYWnd,WM_SETICON,ICON_BIG,(LPARAM) ghMClosedIcon); + PostMessage(ghTTYWnd,WM_SETICON,ICON_SMALL,(LPARAM) ghMClosedIcon); + + gpTTYInfo->fMClosedIcon = TRUE; + } + else if(t_utf8 == NULL && gpTTYInfo->fMClosedIcon) { + /* only go from yellow to green */ + UpdateTrayIcon(NIM_MODIFY, ghNormalIcon, TEXT("Alpine")); + PostMessage(ghTTYWnd,WM_SETICON,ICON_BIG,(LPARAM) ghNormalIcon); + PostMessage(ghTTYWnd,WM_SETICON,ICON_SMALL,(LPARAM) ghNormalIcon); + + gpTTYInfo->fMClosedIcon = FALSE; + } +} + +void +mswin_trayicon(int show) +{ + if(show){ + if(!gpTTYInfo->fTrayIcon){ + UpdateTrayIcon(NIM_ADD, ghNormalIcon, TEXT("Alpine")); + gpTTYInfo->fTrayIcon = TRUE; + } + } + else{ + if(gpTTYInfo->fTrayIcon){ + UpdateTrayIcon(NIM_DELETE, 0, NULL); + gpTTYInfo->fTrayIcon = FALSE; + } + } +} + + +void +UpdateTrayIcon(DWORD dwMsg, HICON hIcon, LPTSTR tip) +{ + NOTIFYICONDATA nt; + + nt.cbSize = sizeof (nt); + nt.hWnd = ghTTYWnd; + nt.uID = TASKBAR_ICON_NEWMAIL; + switch(dwMsg){ + case NIM_DELETE : + nt.uFlags = 0; + break; + + case NIM_ADD : /* send msg to add icon to tray */ + nt.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nt.uCallbackMessage = TASKBAR_ICON_MESSAGE; + + if(tip){ + _tcsncpy (nt.szTip, tip, 63); + nt.szTip[63] = '\0'; + } + else + nt.szTip[0] = '\0'; + nt.hIcon = hIcon; + break; + + case NIM_MODIFY : /* send msg to modify icon in tray */ + nt.uFlags = NIF_ICON | NIF_TIP; + if(tip){ + _tcsncpy (nt.szTip, tip, 63); + nt.szTip[63] = '\0'; + } + else + nt.szTip[0] = '\0'; + nt.hIcon = hIcon; + break; + + default : + return; + } + + Shell_NotifyIcon (dwMsg, &nt); +} + + + +/*--------------------------------------------------------------------------- + * + * Client level menu item stuff. + * + * These are menu items that activate commands in the "client" program. + * Generally, the client calls us to tell us which menu items are active + * and what key stroke they generate. When such an item is selected it's + * key stroke is injected into the character queue as if it was typed by + * the user. + * + *-------------------------------------------------------------------------*/ + +/* + * Clear active status of all "menu items". + */ +void +mswin_menuitemclear (void) +{ + int i; + HWND hWnd; + + + for (i = 0; i < KS_COUNT; ++i) { + gpTTYInfo->menuItems[i].miActive = FALSE; + if (gpTTYInfo->toolBarSize > 0) { + hWnd = GetDlgItem(gpTTYInfo->hTBWnd, i + KS_RANGESTART); + if (hWnd != NULL) + EnableWindow(hWnd, FALSE); + } + } + + gpTTYInfo->menuItemsCurrent = FALSE; + gpTTYInfo->menuItemsIndex = 1; + for(i = 0; i < KS_COUNT; i++) + gpTTYInfo->menuItems[i].miIndex = 0; +} + + +/* + * Add an item to the cmdmenu + */ +void +mswin_menuitemadd (UCS key, char *label, int menuitem, int flags) +{ + int i; + HWND hWnd; + + if (menuitem >= KS_RANGESTART && menuitem <= KS_RANGEEND) { + + gpTTYInfo->menuItemsCurrent = FALSE; + + i = menuitem - KS_RANGESTART; + gpTTYInfo->menuItems[i].miActive = TRUE; + gpTTYInfo->menuItems[i].miKey = key; + if(!gpTTYInfo->menuItems[i].miIndex){ + gpTTYInfo->menuItems[i].miLabel = label; + gpTTYInfo->menuItems[i].miIndex = gpTTYInfo->menuItemsIndex++; + } + + if (gpTTYInfo->toolBarSize > 0) { + hWnd = GetDlgItem(gpTTYInfo->hTBWnd, menuitem); + if (hWnd != NULL) + EnableWindow(hWnd, TRUE); + } + } +} + + +/* + * Called when a menu command arrives with an unknown ID. If it is + * within the range of the additional menu items, insert the + * corresponding character into the char input queue. + */ +void +ProcessMenuItem (HWND hWnd, WPARAM wParam) +{ + PTTYINFO pTTYInfo; + int i; + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + + if (wParam >= KS_RANGESTART && wParam <= KS_RANGEEND) { + i = (int)(wParam - KS_RANGESTART); + if (pTTYInfo->menuItems[i].miActive) + CQAdd (pTTYInfo->menuItems[i].miKey, 0); + } +} + + +/* + * Called to set a new menu. + */ +int +mswin_setwindowmenu (int menu) +{ + int oldmenu; + HMENU holdmenu; + HMENU hmenu; + + + oldmenu = gpTTYInfo->curWinMenu; + holdmenu = GetMenu (ghTTYWnd); + if (gpTTYInfo->curWinMenu != menu) { + + hmenu = LoadMenu (ghInstance, MAKEINTRESOURCE (menu)); + if (hmenu != NULL) { + if (SetMenu (ghTTYWnd, hmenu) != 0) { + DestroyMenu (holdmenu); + gfHelpMenu = gfHelpGenMenu = FALSE; + gpTTYInfo->curWinMenu = menu; + } + } + + if(menu == MENU_DEFAULT){ + TBSwap (ghTTYWnd, IDD_TOOLBAR); + if(hmenu != NULL) + (void) MSWHelpSetMenu(hmenu); + } + else{ + TBSwap (ghTTYWnd, IDD_COMPOSER_TB); + } + } + return (oldmenu); +} + + + +/*--------------------------------------------------------------------------- + * + * Printing stuff + * + *-------------------------------------------------------------------------*/ + +/* + * Printing globals + */ +LOCAL HDC P_PrintDC; /* Printer device context. */ +LOCAL int P_PageRows; /* Number of rows we put on a page. */ +LOCAL int P_PageColumns; /* Number of columns we put on a page. */ +LOCAL int P_RowHeight; /* Hight of a row in printer pixels. */ +LOCAL int P_CurRow; /* Current row, starting at zero */ +LOCAL int P_CurCol; /* Current col, starting at zero. */ +LOCAL int P_TopOffset; /* Top Margin offset, in pixels. */ +LOCAL int P_LeftOffset; /* Top Margin offset, in pixels. */ +LOCAL HFONT P_hFont; /* Handle to printing font. */ +LPTSTR P_LineText; /* Pointer to line buffer. */ + + + + +/* + * Define the margin as number of lines at top and bottom of page. + * (might be better to define as a percent of verticle page size) + */ +#define VERTICLE_MARGIN 3 /* lines at top and bottom of page. */ +#define HORIZONTAL_MARGIN 1 /* margine at left & right in chars */ + +/* + * Several errors that can be reported. + */ +#define PE_DIALOG_FAILED 1 +#define PE_USER_CANCEL 2 +#define PE_CANT_START_DOC 3 +#define PE_CANT_FINISH_DOC 4 +#define PE_OUT_OF_MEMORY 5 +#define PE_GENERAL_ERROR 6 +#define PE_OUT_OF_DISK 7 +#define PE_PRINTER_NOT_FOUND 8 +#define PE_PINE_INTERNAL 9 +#define PE_FONT_FAILED 10 + + +LOCAL struct pe_error_message { + int error_code; + char *error_message; + } P_ErrorMessages[] = { + { PE_DIALOG_FAILED, "Print Dialog Failed"}, + { PE_USER_CANCEL, "User canceled" }, + { PE_CANT_START_DOC, "Can't start document" }, + { PE_OUT_OF_MEMORY, "Out of memory" }, + { PE_CANT_FINISH_DOC, "Can't finish document" }, + { PE_OUT_OF_MEMORY, "Out of memory" }, + { PE_OUT_OF_DISK, "Out of disk space" }, + { PE_PRINTER_NOT_FOUND, "Printer not found" }, + { PE_PINE_INTERNAL, "Pine internal error" }, + { PE_FONT_FAILED, "Failed to create font" }, + { 0, NULL }}; + + + +/* + * Send text in the line buffer to the printer. + * Advance to next page if necessary. + */ +LOCAL int +_print_send_line (void) +{ + int status; + + status = 0; + if (P_CurCol > 0) + TextOut (P_PrintDC, P_LeftOffset, + P_TopOffset + (P_CurRow * P_RowHeight), + P_LineText, P_CurCol); + P_CurCol = 0; + if (++P_CurRow >= P_PageRows) + status = _print_send_page (); + + return (status); +} + + + +/* + * Advance one page. + */ +int +_print_send_page (void) +{ + DWORD status; + + if((status = EndPage (P_PrintDC)) > 0){ + P_CurRow = 0; + if((status = StartPage (P_PrintDC)) > 0){ + SelectObject (P_PrintDC, P_hFont); + return(0); + } + } + +#ifdef WIN32 + ExplainSystemErr(); + return(PE_GENERAL_ERROR); +#else + switch (status) { + case SP_USERABORT: return (PE_USER_CANCEL); + case SP_OUTOFDISK: return (PE_OUT_OF_DISK); + case SP_OUTOFMEMORY: return (PE_OUT_OF_MEMORY); + default: + case SP_ERROR: break; + } +#endif + return (PE_GENERAL_ERROR); +} + + + +/* + * Map errors reported to my own error set. + */ +int +_print_map_dlg_error (DWORD error) +{ + switch (error) { + case 0: return (PE_USER_CANCEL); + case CDERR_MEMALLOCFAILURE: + case CDERR_MEMLOCKFAILURE: + return (PE_OUT_OF_MEMORY); + case PDERR_PRINTERNOTFOUND: + case PDERR_NODEVICES: + return (PE_PRINTER_NOT_FOUND); + case CDERR_STRUCTSIZE: + return (PE_PINE_INTERNAL); + default: + return (PE_GENERAL_ERROR); + } +} + + +/* + * This is used for converting from UTF-8 to UCS and is + * global so that mswin_print_ready can initialize it. + */ +static CBUF_S print_cb; + +/* + * Get the printer ready. Returns ZERO for success, or an error code that + * can be passed to mswin_print_error() to get a text message. + */ +int +mswin_print_ready(WINHAND hWnd, LPTSTR docDesc) +{ + PRINTDLG pd; + DOCINFO di; + TEXTMETRIC tm; + HDC hDC; + int fontSize; /* Size in Points. */ + int ppi; /* Pixels per inch in device. */ + int xChar; + int status; + HFONT oldFont; + LOGFONT newFont; + + + status = 0; + P_PrintDC = NULL; + + print_cb.cbufp = print_cb.cbuf; + + /* + * Show print dialog. + */ + memset(&pd, 0, sizeof(PRINTDLG)); + pd.lStructSize = sizeof (PRINTDLG); + pd.hwndOwner = (hWnd ? (HWND) hWnd : ghTTYWnd); + pd.hDevMode = NULL; + pd.hDevNames = NULL; + pd.Flags = PD_ALLPAGES | PD_NOSELECTION | PD_NOPAGENUMS | + PD_HIDEPRINTTOFILE | PD_RETURNDC; + pd.nCopies = 1; + if(PrintDlg (&pd) == 0) + return(_print_map_dlg_error (CommDlgExtendedError())); + + /* + * Returns the device name which we could use to remember what printer + * they selected. But instead, we just toss them. + */ + if (pd.hDevNames) + GlobalFree (pd.hDevNames); + if (pd.hDevMode) + GlobalFree (pd.hDevMode); + + /* + * Get the device drawing context. + * (does PringDlg() ever return success but fail to return a DC?) + */ + if (pd.hDC != NULL) + P_PrintDC = pd.hDC; + else { + status = PE_DIALOG_FAILED; + goto Done; + } + + /* + * Start Document + */ + memset(&di, 0, sizeof (DOCINFO)); + di.cbSize = sizeof (DOCINFO); + di.lpszDocName = docDesc; /* This appears in the print manager*/ + di.lpszOutput = NULL; /* Could suply a file name to print + to. */ + if(StartDoc(P_PrintDC, &di) <= 0) { + ExplainSystemErr(); + status = PE_CANT_START_DOC; + DeleteDC (P_PrintDC); + P_PrintDC = NULL; + goto Done; + } + + /* + * Printer font is either same as window font, or is it's own + * font. + */ + if (gPrintFontSameAs) { + + /* + * Get the current font size in points, then create a new font + * of same size for printer. Do the calculation using the actual + * screen resolution instead of the logical resolution so that + * we get pretty close to the same font size on the printer + * as we see on the screen. + */ + hDC = GetDC (ghTTYWnd); /* Temp screen DC. */ + ppi = (int) ((float)GetDeviceCaps (hDC, VERTRES) / + ((float) GetDeviceCaps (hDC, VERTSIZE) / 25.3636)); +#ifdef FDEBUG + if (mswin_debug >= 8) { + fprintf (mswin_debugfile, "mswin_print_ready: Screen res %d ppi, font height %d pixels\n", + ppi, -gpTTYInfo->lfTTYFont.lfHeight); + fprintf (mswin_debugfile, " Screen height %d pixel, %d mm\n", + GetDeviceCaps (hDC, VERTRES), GetDeviceCaps (hDC, VERTSIZE)); + } +#endif + ReleaseDC (ghTTYWnd, hDC); + + /* Convert from screen pixels to points. */ + fontSize = MulDiv (-gpTTYInfo->lfTTYFont.lfHeight, 72, ppi); + ++fontSize; /* Fudge a little. */ + + + /* Get printer resolution and convert form points to printer pixels. */ + ppi = GetDeviceCaps (P_PrintDC, LOGPIXELSY); + newFont.lfHeight = -MulDiv (fontSize, ppi, 72); + _tcsncpy(newFont.lfFaceName, gpTTYInfo->lfTTYFont.lfFaceName, LF_FACESIZE); + newFont.lfFaceName[LF_FACESIZE-1] = 0; + newFont.lfItalic = gpTTYInfo->lfTTYFont.lfItalic; + newFont.lfWeight = gpTTYInfo->lfTTYFont.lfWeight; + newFont.lfCharSet = gpTTYInfo->lfTTYFont.lfCharSet; + + +#ifdef FDEBUG + if (mswin_debug >= 8) { + fprintf (mswin_debugfile, " font Size %d points\n", + fontSize); + fprintf (mswin_debugfile, " printer res %d ppi, font height %d pixels\n", + ppi, -newFont.lfHeight); + fprintf (mswin_debugfile, " paper height %d pixel, %d mm\n", + GetDeviceCaps (P_PrintDC, VERTRES), + GetDeviceCaps (P_PrintDC, VERTSIZE)); + } +#endif + } + else { + ppi = GetDeviceCaps (P_PrintDC, LOGPIXELSY); + newFont.lfHeight = -MulDiv (gPrintFontSize, ppi, 72); + _tcsncpy(newFont.lfFaceName, gPrintFontName, LF_FACESIZE); + newFont.lfFaceName[LF_FACESIZE-1] = 0; + newFont.lfWeight = 0; + if(_tcsstr(gPrintFontStyle, TEXT("bold"))) + newFont.lfWeight = FW_BOLD; + + newFont.lfItalic = 0; + if(_tcsstr(gPrintFontStyle, TEXT("italic"))) + newFont.lfItalic = 1; + + newFont.lfCharSet = mswin_string2charsetid(gPrintFontCharSet); + } + + + /* Fill out rest of font description and request font. */ + newFont.lfWidth = 0; + newFont.lfEscapement = 0; + newFont.lfOrientation = 0; + newFont.lfUnderline = 0; + newFont.lfStrikeOut = 0; + newFont.lfOutPrecision = OUT_DEFAULT_PRECIS; + newFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + newFont.lfQuality = DEFAULT_QUALITY; + newFont.lfPitchAndFamily = FIXED_PITCH; + P_hFont = CreateFontIndirect (&newFont); + if (P_hFont == NULL) { + status = PE_FONT_FAILED; + DeleteDC (P_PrintDC); + goto Done; + } + + + /* + * Start page. + * Must select font for each page or it returns to default. + * Windows seems good about maping selected font to a font that + * will actually print on the printer. + */ + StartPage (P_PrintDC); + oldFont = SelectObject (P_PrintDC, P_hFont); + + + /* + * Find out about the font we got and set up page size and margins. + * This assumes all pages are the same size - which seems reasonable. + */ + GetTextMetrics (P_PrintDC, &tm); + xChar = tm.tmAveCharWidth; + P_RowHeight = tm.tmHeight + tm.tmExternalLeading; + + /* HORZRES and VERTRES report size of page in printer pixels. */ + P_PageColumns = GetDeviceCaps (P_PrintDC, HORZRES) / xChar; + P_PageRows = GetDeviceCaps (P_PrintDC, VERTRES) / P_RowHeight; + + /* We allow a margin at top and bottom measured in text rows. */ + P_PageRows -= VERTICLE_MARGIN * 2; + P_TopOffset = VERTICLE_MARGIN * P_RowHeight; + + /* And allow for a left and right margine measured in characters. */ + P_PageColumns -= HORIZONTAL_MARGIN * 2; + P_LeftOffset = HORIZONTAL_MARGIN * xChar; + + P_CurRow = 0; /* Start counting at row 0. */ + P_CurCol = 0; /* At character 0. */ + P_LineText = (LPTSTR) MemAlloc((P_PageColumns + 1) * sizeof(TCHAR)); + if(P_LineText == NULL){ + if(EndDoc (P_PrintDC) <= 0) + ExplainSystemErr(); + + DeleteObject (P_hFont); + P_hFont = NULL; + DeleteDC (P_PrintDC); + P_PrintDC = NULL; + status = PE_OUT_OF_MEMORY; + goto Done; + } + + +Done: + return (status); +} + + +/* + * Called when printing is done. + * xxx what happens if there is an error? Will this get called? + */ +int +mswin_print_done(void) +{ + if(P_PrintDC != NULL){ + if(P_LineText != NULL) + MemFree((void *) P_LineText); + + if(EndPage (P_PrintDC) <= 0 || EndDoc (P_PrintDC) <= 0) + ExplainSystemErr(); + + DeleteObject(P_hFont); + P_hFont = NULL; + DeleteDC(P_PrintDC); + P_PrintDC = NULL; + } + + return (0); +} + + +/* + * Return ponter to a text string that describes the erorr. + */ +char * +mswin_print_error(int error_code) +{ + int i; + + for(i = 0; P_ErrorMessages[i].error_message != NULL; ++i) + if(P_ErrorMessages[i].error_code == error_code) + return(P_ErrorMessages[i].error_message); + + return("(Unknown error)"); +} + + +/* + * Add a single character to the current line. + * Only handles CRLF carrage control. + */ +int +mswin_print_char(TCHAR c) +{ + switch(c){ + case ASCII_CR: + return(0); + + case ASCII_LF: + return(_print_send_line()); + + case ASCII_TAB: + do{ + if(P_CurCol == P_PageColumns) + return(_print_send_line()); + + *(P_LineText + P_CurCol++) = ' '; + } + while(P_CurCol % PRINT_TAB_SIZE != 0); + return(0); + + default: + if(P_CurCol == P_PageColumns){ + int status; + + if((status = _print_send_line()) != 0) + return(status); + } + + *(P_LineText + P_CurCol++) = c; + return(0); + } +} + + +int +mswin_print_char_utf8(int c) +{ + UCS ucs; + TCHAR tc; + int ret = 0; + + if(utf8_to_ucs4_oneatatime(c, &print_cb, &ucs, NULL)){ + /* bogus conversion ignores UTF-16 */ + tc = (TCHAR) ucs; + ret = mswin_print_char(tc); + } + + return(ret); +} + + +/* + * Send a string to the printer. + */ +int +mswin_print_text(LPTSTR text) +{ + int status; + + if(text) + while(*text) + if((status = mswin_print_char(*(text++))) != 0) + return(status); + + return(0); +} + + +int +mswin_print_text_utf8(char *text_utf8) +{ + LPTSTR text_lpt; + int ret = 0; + + if(text_utf8){ + text_lpt = utf8_to_lptstr(text_utf8); + if(text_lpt){ + ret = mswin_print_text(text_lpt); + fs_give((void **) &text_lpt); + } + } + + return(ret); +} + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * File dialog boxes. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +LOCAL TCHAR gHomeDir[PATH_MAX]; +LOCAL TCHAR gLastDir[PATH_MAX]; + +/* + * Keep track of the last dir visited. Most of the time pine just passes us + * the "home directory", which usually is not where the user wants to start. + * Assume that the first time we are called we are being passed the home + * direcory. + */ +static void +FillInitialDir (LPCTSTR *iDir, LPTSTR targDir) +{ + if (_tcslen (gHomeDir) == 0) { + _tcscpy (gHomeDir, targDir); + *iDir = targDir; + } + else if (_tcscmp (gHomeDir, targDir) == 0 && *gLastDir) + *iDir = gLastDir; + else + *iDir = targDir; +} + + + +/* + * Display a save file dialog box. + * + * dir_utf8 > directory to start search in + * < directory finished in. + * fName_utf8 < Name of file selected + * nMaxDName length of dir_utf8 + * nMaxFName length of fName_utf8. + * + * Possible return values: + * 0 no file selected + * 1 file selected + * -1 some sort of error + */ +int +mswin_savefile(char *dir_utf8, int nMaxDName, char *fName_utf8, int nMaxFName) +{ + OPENFILENAME ofn; + TCHAR filters[128], moniker[128]; + DWORD rc, len; + LPTSTR p, extlist_lpt; + LPTSTR fName_lpt, f, dir_lpt; + char *cp; + + /* convert fName_utf8 to LPTSTR */ + fName_lpt = (LPTSTR) fs_get(nMaxFName * sizeof(TCHAR)); + fName_lpt[0] = '\0'; + if(fName_utf8 && fName_utf8[0]){ + f = utf8_to_lptstr(fName_utf8); + if(f){ + _tcsncpy(fName_lpt, f, nMaxFName); + fName_lpt[nMaxFName-1] = '\0'; + fs_give((void **) &f); + } + } + + dir_lpt = (LPTSTR) fs_get(nMaxDName * sizeof(TCHAR)); + dir_lpt[0] = '\0'; + if(dir_utf8 && dir_utf8[0]){ + f = utf8_to_lptstr(dir_utf8); + if(f){ + _tcsncpy(dir_lpt, f, nMaxDName); + dir_lpt[nMaxDName-1] = '\0'; + fs_give((void **) &f); + } + } + + for(extlist_lpt = NULL, p = _tcschr(fName_lpt, '.'); p; p = _tcschr(++p, '.')) + extlist_lpt = p; + + len = sizeof(moniker)/sizeof(TCHAR); + if(extlist_lpt && MSWRPeek(HKEY_CLASSES_ROOT, extlist_lpt, NULL, moniker, &len) == TRUE){ + len = sizeof(filters)/sizeof(TCHAR); + filters[0] = '\0'; + if(MSWRPeek(HKEY_CLASSES_ROOT, moniker, NULL, filters, &len) == TRUE) + _sntprintf(filters + _tcslen(filters), + sizeof(filters)/sizeof(TCHAR) - _tcslen(filters), + TEXT(" (*%s)#*%s#"), extlist_lpt, extlist_lpt); + else + _sntprintf(filters, sizeof(filters)/sizeof(TCHAR), + TEXT("%s (*%s)#*%s#"), moniker, extlist_lpt, extlist_lpt); + } + else + filters[0] = '\0'; + + _tcsncat(filters, TEXT("Text Files (*.txt)#*.txt#All Files (*.*)#*.*#"), + sizeof(filters)/sizeof(TCHAR)); + filters[sizeof(filters)/sizeof(TCHAR) - 1] = '\0'; + + for(p = filters; *p != '\0'; ++p) + if(*p == L'#') + *p = '\0'; + + /* Set up the BIG STRUCTURE. */ + memset (&ofn, 0, sizeof(ofn)); + /* + * sizeof(OPENFILENAME) used to work but doesn't work now with + * pre-Windows 2000. This is supposed to be the magic constant to + * make it work in both cases, according to MSDN. + */ + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = ghTTYWnd; + ofn.lpstrFilter = filters; + ofn.lpstrCustomFilter = NULL; + ofn.nFilterIndex = 1; + ofn.lpstrFile = fName_lpt; + ofn.nMaxFile = nMaxFName; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + FillInitialDir (&ofn.lpstrInitialDir, dir_lpt); + ofn.lpstrTitle = TEXT("Save To File"); + ofn.Flags = OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; + ofn.lpstrDefExt = TEXT("txt"); + + if(GetSaveFileName(&ofn)){ + if(ofn.nFileOffset > nMaxDName-1){ + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + return(0); + } + + /* Copy directory name to dir_lpt. */ + _tcsncpy(dir_lpt, fName_lpt, nMaxDName-1); + dir_lpt[nMaxDName-1] = '\0'; + if(dir_lpt[ofn.nFileOffset-1] == '\\' + && !(ofn.nFileOffset == 3 && _istalpha(dir_lpt[0]) && dir_lpt[1] == ':')) + dir_lpt[ofn.nFileOffset-1] = '\0'; + else + dir_lpt[ofn.nFileOffset] = '\0'; + + /* Remember last dir visited. */ + _tcsncpy(gLastDir, dir_lpt, PATH_MAX); + gLastDir[PATH_MAX-1] = '\0'; + + /* convert back to UTF-8 */ + cp = lptstr_to_utf8(dir_lpt); + if(cp){ + strncpy(dir_utf8, cp, nMaxDName-1); + dir_utf8[nMaxDName-1] = '\0'; + fs_give((void **) &cp); + } + + p = fName_lpt + ofn.nFileOffset; + + /* convert fName back to UTF-8 */ + cp = lptstr_to_utf8(p); + if(cp){ + strncpy(fName_utf8, cp, nMaxFName-1); + fName_utf8[nMaxFName-1] = '\0'; + fs_give((void **) &cp); + } + + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + return(1); + } + else{ + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + rc = CommDlgExtendedError(); + return(rc ? -1 : 0); + } +} + + + + +/* + * Display an open file dialog box. + * + * dir_utf8 > directory to start search in + * < directory finished in. + * fName_utf8 < Name of file selected + * nMaxDName length of dir_utf8 + * nMaxFName length of fName_utf8. + * + * Possible return values: + * 0 no file selected + * 1 file selected + * -1 some sort of error + */ +int +mswin_openfile(char *dir_utf8, int nMaxDName, char *fName_utf8, + int nMaxFName, char *extlist_utf8) +{ + OPENFILENAME ofn; + TCHAR filters[1024]; + DWORD rc; + LPTSTR p; + LPTSTR extlist_lpt = NULL; + LPTSTR fName_lpt, f, dir_lpt; + char *cp; + + + if(extlist_utf8) + extlist_lpt = utf8_to_lptstr(extlist_utf8); + + /* + * Set filters array. (pairs of null terminated strings, terminated + * by a double null). + */ + _sntprintf(filters, sizeof(filters)/sizeof(TCHAR), + TEXT("%s%sAll Files (*.*)#*.*#"), + extlist_lpt ? extlist_lpt : TEXT(""), extlist_lpt ? TEXT("#") : TEXT("")); + + if(extlist_lpt) + fs_give((void **) &extlist_lpt); + + for(p = filters; *p != '\0'; ++p) + if(*p == L'#') + *p = '\0'; + + /* Set up the BIG STRUCTURE. */ + memset(&ofn, 0, sizeof(ofn)); + + /* convert fName_utf8 to LPTSTR */ + fName_lpt = (LPTSTR) fs_get(nMaxFName * sizeof(TCHAR)); + fName_lpt[0] = '\0'; + if(fName_utf8 && fName_utf8[0]){ + f = utf8_to_lptstr(fName_utf8); + if(f){ + _tcsncpy(fName_lpt, f, nMaxFName); + fName_lpt[nMaxFName-1] = '\0'; + fs_give((void **) &f); + } + } + + dir_lpt = (LPTSTR) fs_get(nMaxDName * sizeof(TCHAR)); + dir_lpt[0] = '\0'; + if(dir_utf8 && dir_utf8[0]){ + f = utf8_to_lptstr(dir_utf8); + if(f){ + _tcsncpy(dir_lpt, f, nMaxDName); + dir_lpt[nMaxDName-1] = '\0'; + fs_give((void **) &f); + } + } + + /* + * sizeof(OPENFILENAME) used to work but doesn't work now with + * pre-Windows 2000. This is supposed to be the magic constant to + * make it work in both cases, according to MSDN. + */ + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = ghTTYWnd; + ofn.lpstrFilter = filters; + ofn.lpstrCustomFilter = NULL; + ofn.nFilterIndex = 1; + ofn.lpstrFile = fName_lpt; + ofn.nMaxFile = nMaxFName; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + FillInitialDir(&ofn.lpstrInitialDir, dir_lpt); + ofn.lpstrTitle = TEXT("Select File"); + ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; + ofn.lpstrDefExt = TEXT("txt"); + + if(GetOpenFileName(&ofn)){ + if(ofn.nFileOffset > nMaxDName-1){ + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + return(0); + } + + /* Copy directory name to dir_lpt. */ + _tcsncpy(dir_lpt, fName_lpt, nMaxDName-1); + dir_lpt[nMaxDName-1] = '\0'; + if(dir_lpt[ofn.nFileOffset-1] == '\\' + && !(ofn.nFileOffset == 3 && _istalpha(dir_lpt[0]) && dir_lpt[1] == ':')) + dir_lpt[ofn.nFileOffset-1] = '\0'; + else + dir_lpt[ofn.nFileOffset] = '\0'; + + /* Remember last dir visited. */ + _tcsncpy(gLastDir, dir_lpt, PATH_MAX); + gLastDir[PATH_MAX-1] = '\0'; + + /* convert back to UTF-8 */ + cp = lptstr_to_utf8(dir_lpt); + if(cp){ + strncpy(dir_utf8, cp, nMaxDName-1); + dir_utf8[nMaxDName-1] = '\0'; + fs_give((void **) &cp); + } + + p = fName_lpt + ofn.nFileOffset; + + /* convert fName back to UTF-8 */ + cp = lptstr_to_utf8(p); + if(cp){ + strncpy(fName_utf8, cp, nMaxFName-1); + fName_utf8[nMaxFName-1] = '\0'; + fs_give((void **) &cp); + } + + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + return(1); + } + else{ + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + rc = CommDlgExtendedError(); + return(rc ? -1 : 0); + } +} + + +/* + * Display an open file dialog box. + * Allow selection of multiple files. + * + * dir_utf8 > directory to start search in + * < directory finished in. + * fName_utf8 < Names of files selected + * nMaxDName length of dir_utf8 + * nMaxFName length of fName_utf8. + * + * Possible return values: + * 0 no file selected + * 1 file selected + * -1 some sort of error + */ +int +mswin_multopenfile(char *dir_utf8, int nMaxDName, char *fName_utf8, + int nMaxFName, char *extlist_utf8) +{ + OPENFILENAME ofn; + TCHAR filters[1024]; + DWORD rc; + LPTSTR p; + LPTSTR extlist_lpt = NULL; + LPTSTR fName_lpt, f, dir_lpt; + char *cp, *q; + + + if(extlist_utf8) + extlist_lpt = utf8_to_lptstr(extlist_utf8); + + /* + * Set filters array. (pairs of null terminated strings, terminated + * by a double null). + */ + _sntprintf(filters, sizeof(filters)/sizeof(TCHAR), + TEXT("%s%sAll Files (*.*)#*.*#"), + extlist_lpt ? extlist_lpt : TEXT(""), extlist_lpt ? TEXT("#") : TEXT("")); + + if(extlist_lpt) + fs_give((void **) &extlist_lpt); + + for(p = filters; *p != '\0'; ++p) + if(*p == L'#') + *p = '\0'; + + /* Set up the BIG STRUCTURE. */ + memset (&ofn, 0, sizeof(ofn)); + + /* convert fName_utf8 to LPTSTR */ + fName_lpt = (LPTSTR) fs_get(nMaxFName * sizeof(TCHAR)); + memset(fName_lpt, 0, nMaxFName * sizeof(TCHAR)); + if(fName_utf8 && fName_utf8[0]){ + f = utf8_to_lptstr(fName_utf8); + if(f){ + _tcsncpy(fName_lpt, f, nMaxFName); + fName_lpt[nMaxFName-1] = '\0'; + fs_give((void **) &f); + } + } + + dir_lpt = (LPTSTR) fs_get(nMaxDName * sizeof(TCHAR)); + memset(dir_lpt, 0, nMaxDName * sizeof(TCHAR)); + if(dir_utf8 && dir_utf8[0]){ + f = utf8_to_lptstr(dir_utf8); + if(f){ + _tcsncpy(dir_lpt, f, nMaxDName); + dir_lpt[nMaxDName-1] = '\0'; + fs_give((void **) &f); + } + } + + /* + * sizeof(OPENFILENAME) used to work but doesn't work now with + * pre-Windows 2000. This is supposed to be the magic constant to + * make it work in both cases, according to MSDN. + */ + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = ghTTYWnd; + ofn.lpstrFilter = filters; + ofn.lpstrCustomFilter = NULL; + ofn.nFilterIndex = 1; + ofn.lpstrFile = fName_lpt; + ofn.nMaxFile = nMaxFName; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + FillInitialDir(&ofn.lpstrInitialDir, dir_lpt); + ofn.lpstrTitle = TEXT("Select Files"); + ofn.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER | + OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; + ofn.lpstrDefExt = TEXT("txt"); + + if(GetOpenFileName(&ofn)){ + if(ofn.nFileOffset > nMaxDName-1){ + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + return(0); + } + + /* Copy directory name to dir_lpt. */ + _tcsncpy(dir_lpt, fName_lpt, nMaxDName-1); + dir_lpt[nMaxDName-1] = '\0'; + if(dir_lpt[ofn.nFileOffset-1] == '\\' + && !(ofn.nFileOffset == 3 && _istalpha(dir_lpt[0]) && dir_lpt[1] == ':')) + dir_lpt[ofn.nFileOffset-1] = '\0'; + else + dir_lpt[ofn.nFileOffset] = '\0'; + + /* Remember last dir visited. */ + _tcsncpy(gLastDir, dir_lpt, PATH_MAX); + gLastDir[PATH_MAX-1] = '\0'; + + /* convert back to UTF-8 */ + cp = lptstr_to_utf8(dir_lpt); + if(cp){ + strncpy(dir_utf8, cp, nMaxDName-1); + dir_utf8[nMaxDName-1] = '\0'; + fs_give((void **) &cp); + } + + /* + * The file names are all in the same directory and are separated + * by '\0' characters and terminated by double '\0'. + * This fact depends on the OFN_EXPLORER bit being set in the flags + * above. + * + * This is complicated because we need to convert all of these file + * names to UTF-8. + */ + for(q=fName_utf8, p=fName_lpt + ofn.nFileOffset; *p; p += _tcslen(p)+1){ + cp = lptstr_to_utf8(p); + if(cp){ + sstrncpy(&q, cp, (int)(nMaxFName-(q-fName_utf8))); + if(q-fName_utf8 < nMaxFName){ + *q++ = '\0'; + if(q-fName_utf8 < nMaxFName) + *q = '\0'; /* the double null if this is the end */ + } + else + fName_utf8[nMaxFName-1] = '\0'; + + fs_give((void **) &cp); + } + } + + fName_utf8[nMaxFName-1] = fName_utf8[nMaxFName-2] = '\0'; + + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + return (1); + } + else{ + if(fName_lpt) + fs_give((void **) &fName_lpt); + + if(dir_lpt) + fs_give((void **) &dir_lpt); + + rc = CommDlgExtendedError(); + return(rc ? -1 : 0); + } +} + + +/*--------------------------------------------------------------------------- + */ + +/* + * pico_XXcolor() - each function sets a particular attribute + */ +void +pico_nfcolor(char *s) +{ + char cbuf[MAXCLEN]; + + if(s){ + SetColorAttribute (&gpTTYInfo->rgbFGColor, s); + pico_set_nfg_color(); + + if(the_normal_color){ + strncpy(the_normal_color->fg, + ConvertStringRGB(cbuf, sizeof(cbuf), gpTTYInfo->rgbFGColor), + MAXCOLORLEN+1); + the_normal_color->fg[MAXCOLORLEN] = '\0'; + } + } + else{ + gpTTYInfo->rgbFGColor = GetSysColor (COLOR_WINDOWTEXT); + if(the_normal_color) + free_color_pair(&the_normal_color); + } + + // Update all textwindows with the new FG color. + mswin_tw_setcolor((MSWIN_TEXTWINDOW *)-1, + gpTTYInfo->rgbFGColor, gpTTYInfo->rgbBGColor); +} + + +void +pico_nbcolor(char *s) +{ + char cbuf[MAXCLEN]; + + if(s){ + SetColorAttribute (&gpTTYInfo->rgbBGColor, s); + pico_set_nbg_color(); + + if(the_normal_color){ + strncpy(the_normal_color->bg, + ConvertStringRGB(cbuf, sizeof(cbuf), gpTTYInfo->rgbBGColor), + MAXCOLORLEN+1); + the_normal_color->fg[MAXCOLORLEN] = '\0'; + } + } + else{ + gpTTYInfo->rgbBGColor = GetSysColor (COLOR_WINDOW); + if(the_normal_color) + free_color_pair(&the_normal_color); + } + + // Update all textwindows with the new BG color. + mswin_tw_setcolor((MSWIN_TEXTWINDOW *)-1, + gpTTYInfo->rgbFGColor, gpTTYInfo->rgbBGColor); +} + + +void +pico_rfcolor(char *s) +{ + char cbuf[MAXCLEN]; + + if(s){ + SetColorAttribute (&gpTTYInfo->rgbRFGColor, s); + + if(the_rev_color){ + strncpy(the_rev_color->fg, + ConvertStringRGB(cbuf, sizeof(cbuf), gpTTYInfo->rgbRFGColor), + MAXCOLORLEN+1); + the_rev_color->fg[MAXCOLORLEN] = '\0'; + } + } + else{ + gpTTYInfo->rgbRFGColor = GetSysColor (COLOR_HIGHLIGHTTEXT); + if(the_rev_color) + free_color_pair(&the_rev_color); + } +} + + +void +pico_rbcolor(char *s) +{ + char cbuf[MAXCLEN]; + + if(s){ + SetColorAttribute (&gpTTYInfo->rgbRBGColor, s); + + if(the_rev_color){ + strncpy(the_rev_color->bg, + ConvertStringRGB(cbuf, sizeof(cbuf), gpTTYInfo->rgbRBGColor), + MAXCOLORLEN+1); + the_rev_color->bg[MAXCOLORLEN] = '\0'; + } + } + else{ + gpTTYInfo->rgbRBGColor = GetSysColor (COLOR_HIGHLIGHT); + if(the_rev_color) + free_color_pair(&the_rev_color); + } +} + + +int +pico_usingcolor() +{ + return(TRUE); +} + + +int +pico_count_in_color_table() +{ + return(visibleColorTableSize); +} + + +/* + * Return a pointer to an rgb string for the input color. The output is 11 + * characters long and looks like rrr,ggg,bbb. + * + * Args colorName -- The color to convert to ascii rgb. + * + * Returns Pointer to a static buffer containing the rgb string. + */ +char * +color_to_asciirgb(char *colorName) +{ + static char c_to_a_buf[3][RGBLEN+1]; + static int whichbuf = 0; + COLORREF cf; + int l; + + whichbuf = (whichbuf + 1) % 3; + + if(ConvertRGBString(colorName, &cf)){ + snprintf(c_to_a_buf[whichbuf], sizeof(c_to_a_buf[0]), "%.3d,%.3d,%.3d", + GetRValue(cf), GetGValue(cf), GetBValue(cf)); + } + else{ + /* + * If we didn't find the color it could be that it is the + * normal color (MATCH_NORM_COLOR) or the none color + * (MATCH_NONE_COLOR). If that is the case, this strncpy thing + * will work out correctly because those two strings are + * RGBLEN long. Otherwise we're in a bit of trouble. This + * most likely means that the user is using the same pinerc on + * two terminals, one with more colors than the other. We didn't + * find a match because this color isn't present on this terminal. + * Since the return value of this function is assumed to be + * RGBLEN long, we'd better make it that long. + * It still won't work correctly because colors will be screwed up, + * but at least the embedded colors in filter.c will get properly + * sucked up when they're encountered. + */ + strncpy(c_to_a_buf[whichbuf], "xxxxxxxxxxx", RGBLEN); + l = (int)strlen(colorName); + strncpy(c_to_a_buf[whichbuf], colorName, (l < RGBLEN) ? l : RGBLEN); + c_to_a_buf[whichbuf][RGBLEN] = '\0'; + } + + return(c_to_a_buf[whichbuf]); +} + + +void +pico_set_nfg_color() +{ + FlushWriteAccum (); + gpTTYInfo->curAttrib.rgbFG = gpTTYInfo->rgbFGColor; +} + +void +pico_set_nbg_color() +{ + FlushWriteAccum (); + gpTTYInfo->curAttrib.rgbBG = gpTTYInfo->rgbBGColor; +} + +void +pico_set_normal_color() +{ + pico_set_nfg_color(); + pico_set_nbg_color(); +} + +COLOR_PAIR * +pico_get_rev_color() +{ + char fgbuf[MAXCLEN], bgbuf[MAXCLEN]; + + if(!the_rev_color) + the_rev_color = + new_color_pair(ConvertStringRGB(fgbuf,sizeof(fgbuf),gpTTYInfo->rgbRFGColor), + ConvertStringRGB(bgbuf,sizeof(bgbuf),gpTTYInfo->rgbRBGColor)); + return(the_rev_color); +} + +COLOR_PAIR * +pico_get_normal_color() +{ + char fgbuf[MAXCLEN], bgbuf[MAXCLEN]; + + if(!the_normal_color) + the_normal_color = + new_color_pair(ConvertStringRGB(fgbuf,sizeof(fgbuf),gpTTYInfo->rgbFGColor), + ConvertStringRGB(bgbuf,sizeof(bgbuf),gpTTYInfo->rgbBGColor)); + return(the_normal_color); +} + +/* + * Sets color to (fg,bg). + * Flags == PSC_NONE No alternate default if fg,bg fails. + * == PSC_NORM Set it to Normal color on failure. + * == PSC_REV Set it to Reverse color on failure. + * + * If flag PSC_RET is set, returns an allocated copy of the previous + * color pair, otherwise returns NULL. + */ +COLOR_PAIR * +pico_set_colors(char *fg, char *bg, int flags) +{ + COLOR_PAIR *cp = NULL; + + if(flags & PSC_RET) + cp = pico_get_cur_color(); + + if(!(fg && bg && pico_set_fg_color(fg) && pico_set_bg_color(bg))){ + + if(flags & PSC_NORM) + pico_set_normal_color(); + else if(flags & PSC_REV) + SetReverseColor(); + } + + return(cp); +} + + +int +pico_is_good_color(char *colorName) +{ + COLORREF cf; + + if(!struncmp(colorName, MATCH_NORM_COLOR, RGBLEN) + || !struncmp(colorName, MATCH_NONE_COLOR, RGBLEN)) + return(TRUE); + + return(ConvertRGBString(colorName, &cf)); +} + + +int +pico_set_fg_color(char *colorName) +{ + char fgbuf[MAXCLEN]; + + FlushWriteAccum (); + + if(!struncmp(colorName, MATCH_NORM_COLOR, RGBLEN)){ + ConvertStringRGB(fgbuf,sizeof(fgbuf),gpTTYInfo->rgbFGColor); + colorName = fgbuf; + } + else if(!struncmp(colorName, MATCH_NONE_COLOR, RGBLEN)) + return(TRUE); + + return(ConvertRGBString(colorName, &gpTTYInfo->curAttrib.rgbFG)); +} + + +int +pico_set_bg_color(char *colorName) +{ + char bgbuf[MAXCLEN]; + + FlushWriteAccum (); + + if(!struncmp(colorName, MATCH_NORM_COLOR, RGBLEN)){ + ConvertStringRGB(bgbuf,sizeof(bgbuf),gpTTYInfo->rgbBGColor); + colorName = bgbuf; + } + else if(!struncmp(colorName, MATCH_NONE_COLOR, RGBLEN)) + return(TRUE); + + return(ConvertRGBString(colorName, &gpTTYInfo->curAttrib.rgbBG)); +} + + +char * +pico_get_last_fg_color() +{ + return(NULL); +} + + +char * +pico_get_last_bg_color() +{ + return(NULL); +} + + +unsigned +pico_get_color_options() +{ + return((unsigned)0); +} + + +void +pico_set_color_options(unsigned int opts) +{ +} + + +COLOR_PAIR * +pico_get_cur_color() +{ + char fgbuf[MAXCLEN], bgbuf[MAXCLEN]; + + return(new_color_pair(ConvertStringRGB(fgbuf,sizeof(fgbuf),gpTTYInfo->curAttrib.rgbFG), + ConvertStringRGB(bgbuf,sizeof(bgbuf),gpTTYInfo->curAttrib.rgbBG))); +} + + +char * +mswin_rgbchoice(char *pOldRGB) +{ + CHOOSECOLOR cc; + static COLORREF custColors[16] = { + RGB(0,0,0), + RGB(0,0,255), + RGB(0,255,0), + RGB(0,255,255), + RGB(255,0,0), + RGB(255,0,255), + RGB(255,255,0), + RGB(255,255,255), + RGB(192,192,192), + RGB(128,128,128), + RGB(64,64,64) + }; + + memset(&cc, 0, sizeof(CHOOSECOLOR)); + + cc.lStructSize = sizeof(CHOOSECOLOR); + cc.hwndOwner = ghTTYWnd; + cc.Flags = CC_ANYCOLOR; + cc.lpCustColors = &custColors[0]; + + if(pOldRGB){ + int i; + + ConvertRGBString (pOldRGB, &cc.rgbResult); + cc.Flags |= CC_RGBINIT; + + for(i = 0; i < 11 && custColors[i] != cc.rgbResult; i++) + ; + + if(i == 11){ + custColors[i] = cc.rgbResult; + cc.Flags |= CC_FULLOPEN; + } + + } + + if(ChooseColor(&cc)){ + char rgbbuf[MAXCLEN], *p; + + ConvertStringRGB(rgbbuf, sizeof(rgbbuf), cc.rgbResult); + if(p = MemAlloc(MAXCLEN * sizeof(char))){ + strncpy(p, rgbbuf, MAXCLEN); + p[MAXCLEN-1] = '\0'; + return(p); + } + } + + return(NULL); +} + + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Signal and alarm functions + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* + * Provide a rough implementation of the SIGALRM and alarm functions + */ + + + +#if 0 +/* + * Set a new handler for a signal. + */ +void (__cdecl * __cdecl signal (int sig,void (__cdecl *hndlr)(int)))(int) + +{ + SignalType oldValue; + + switch(sig) { + case SIGALRM : + oldValue = gSignalAlarm; + gSignalAlarm = hndlr; + break; + + case SIGHUP : + oldValue = gSignalHUP; + gSignalHUP = hndlr; + + default: + /* All other's are always ignored. */ + oldValue = SIG_IGN; + break; + } + + return (oldValue); +} +#endif + + +/* + * Set the alarm expiration time (in seconds) + */ +int +mswin_alarm (int seconds) +{ + int prevtime; + + prevtime = gAlarmTimeout ? (gAlarmTimeout - (GetTickCount () / 1000)): 0; + gAlarmTimeout = seconds ? (GetTickCount() / 1000) + seconds : 0; + MyTimerSet (); + return (prevtime); +} + + + +/* + * Deliver and clear the alarm. + */ +void +AlarmDeliver () +{ + if (gSignalAlarm != SIG_DFL && gSignalAlarm != SIG_IGN) { + /* Clear AlarmTimeout BEFORE calling handler. handler may call back + * to reset timeout. */ + gAlarmTimeout = 0; + MyTimerSet (); + gSignalAlarm (SIGALRM); + } +} + + + +void +HUPDeliver () +{ + if (gSignalHUP) { + gSignalHUP (SIGHUP); + exit (0); + } +} + + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Printer font selection menu + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* + * Set the print font to be the same as the window font. + * Toggle setting. + */ +void +PrintFontSameAs (HWND hWnd) +{ + HDC hDC; + int ppi; + PTTYINFO pTTYInfo; + + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + if (gPrintFontSameAs) { + + /* No longer same as window font. Use window font as starting point + * for new printer font. User may later modify printer font. */ + hDC = GetDC (hWnd); + ppi = GetDeviceCaps (hDC, LOGPIXELSY); + ReleaseDC (ghTTYWnd, hDC); + ExtractFontInfo(&pTTYInfo->lfTTYFont, + gPrintFontName, sizeof(gPrintFontName)/sizeof(TCHAR), + &gPrintFontSize, + gPrintFontStyle, sizeof(gPrintFontStyle)/sizeof(TCHAR), + ppi, + gPrintFontCharSet, sizeof(gPrintFontCharSet)/sizeof(TCHAR)); + gPrintFontSameAs = FALSE; + } + else { + + /* Set to be same as the printer font. Destroy printer font info + * and set "sameAs" flag to TRUE. */ + gPrintFontName[0] = '\0'; + gPrintFontSameAs = TRUE; + } + DidResize (gpTTYInfo); +} + + + + + +void +PrintFontSelect (HWND hWnd) +{ + CHOOSEFONT cfTTYFont; + LOGFONT newFont; + DWORD drc; + int ppi; + HDC hDC; + + + + hDC = GetDC (hWnd); + ppi = GetDeviceCaps (hDC, LOGPIXELSY); + ReleaseDC (ghTTYWnd, hDC); + + + newFont.lfHeight = -MulDiv (gPrintFontSize, ppi, 72); + _tcsncpy(newFont.lfFaceName, gPrintFontName, LF_FACESIZE); + newFont.lfFaceName[LF_FACESIZE-1] = 0; + newFont.lfWeight = 0; + if(_tcsstr(gPrintFontStyle, TEXT("bold"))) + newFont.lfWeight = FW_BOLD; + + newFont.lfItalic = 0; + if(_tcsstr(gPrintFontStyle, TEXT("italic"))) + newFont.lfItalic = 1; + + newFont.lfWidth = 0; + newFont.lfEscapement = 0; + newFont.lfOrientation = 0; + newFont.lfUnderline = 0; + newFont.lfStrikeOut = 0; + newFont.lfCharSet = mswin_string2charsetid(gPrintFontCharSet); + newFont.lfOutPrecision = OUT_DEFAULT_PRECIS; + newFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + newFont.lfQuality = DEFAULT_QUALITY; + newFont.lfPitchAndFamily = FIXED_PITCH; + + + cfTTYFont.lStructSize = sizeof (CHOOSEFONT); + cfTTYFont.hwndOwner = hWnd ; + cfTTYFont.hDC = NULL ; + cfTTYFont.rgbColors = 0; + cfTTYFont.lpLogFont = &newFont; + cfTTYFont.Flags = CF_BOTH | CF_FIXEDPITCHONLY | + CF_INITTOLOGFONTSTRUCT | CF_ANSIONLY | + CF_FORCEFONTEXIST | CF_LIMITSIZE; + cfTTYFont.nSizeMin = FONT_MIN_SIZE; + cfTTYFont.nSizeMax = FONT_MAX_SIZE; + cfTTYFont.lCustData = 0 ; + cfTTYFont.lpfnHook = NULL ; + cfTTYFont.lpTemplateName = NULL ; + cfTTYFont.hInstance = GET_HINST (hWnd); + + + if (ChooseFont (&cfTTYFont)) { + ExtractFontInfo(&newFont, + gPrintFontName, sizeof(gPrintFontName)/sizeof(TCHAR), + &gPrintFontSize, + gPrintFontStyle, sizeof(gPrintFontStyle)/sizeof(TCHAR), + ppi, + gPrintFontCharSet, sizeof(gPrintFontCharSet)/sizeof(TCHAR)); + DidResize (gpTTYInfo); + } + else + /* So I can see with the debugger. */ + drc = CommDlgExtendedError(); +} + + +void +ExtractFontInfo(LOGFONT *pFont, LPTSTR fontName, size_t nfontName, + int *fontSize, LPTSTR fontStyle, size_t nfontStyle, + int ppi, LPTSTR fontCharSet, size_t nfontCharSet) +{ + TCHAR *sep[] = {TEXT(""), TEXT(", ")}; + int iSep = 0; + + + _tcsncpy(fontName, pFont->lfFaceName, nfontName); + fontName[nfontName-1] = '\0'; + + *fontStyle = '\0'; + if(pFont->lfWeight >= FW_BOLD) { + _tcsncpy(fontStyle, TEXT("bold"), nfontStyle); + fontStyle[nfontStyle-1] = '\0'; + iSep = 1; + } + + if(pFont->lfItalic){ + _tcsncat(fontStyle, sep[iSep], nfontStyle - _tcslen(fontStyle)); + fontStyle[nfontStyle-1] = '\0'; + + _tcsncat(fontStyle, TEXT("italic"), nfontStyle - _tcslen(fontStyle)); + fontStyle[nfontStyle-1] = '\0'; + } + + mswin_charsetid2string(fontCharSet, nfontCharSet, pFont->lfCharSet); + + if(fontSize) + *fontSize = MulDiv(-pFont->lfHeight, 72, ppi); +} + + +LOCAL void +DidResize (PTTYINFO pTTYInfo) +{ + int i; + + for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) { + if (pTTYInfo->resizer[i] != NULL) + pTTYInfo->resizer[i] (pTTYInfo->actNRow, pTTYInfo->actNColumn); + } + /* + * Put a null character into the input queue so that the data input + * loops stop and return to their callers who will then re-calculate the + * mouse regions so that the user can click on the new regions of the + * screen and have the right thing happen. + */ + CQAdd (NODATA, 0); +} + + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Cut, Copy, and Paste operations + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + + +/* + * Gets called right before the menu is displayed so we can make + * any last minute adjustments. + */ +LOCAL void +UpdateMenu (HWND hWnd) +{ + HMENU hMenu; + PTTYINFO pTTYInfo; + int i; +#ifdef ACCELERATORS + UINT fAccel = EM_NONE; +#endif + + pTTYInfo = (PTTYINFO) MyGetWindowLongPtr (hWnd, GWL_PTTYINFO); + if (pTTYInfo == NULL) + return; + + hMenu = GetMenu (hWnd); + if (hMenu == NULL) + return; + + if (ghPaste) { + /* Currently pasting so disable paste and enable cancel paste. */ + EnableMenuItem (hMenu, IDM_EDIT_PASTE, MF_BYCOMMAND|MF_GRAYED); + EnableMenuItem (hMenu, IDM_EDIT_CANCEL_PASTE, MF_BYCOMMAND|MF_ENABLED); +#ifdef ACCELERATORS + fAccel |= (EM_PST | EM_PST_ABORT); +#endif + } + else { + /* + * Not pasting. If text is available on clipboard and we are + * at a place where we can paste, enable past menu option. + */ + if (IsClipboardFormatAvailable (CF_UNICODETEXT) && gPasteEnabled){ + EnableMenuItem (hMenu, IDM_EDIT_PASTE, MF_BYCOMMAND|MF_ENABLED); +#ifdef ACCELERATORS + fAccel |= EM_PST; +#endif + } + else + EnableMenuItem (hMenu, IDM_EDIT_PASTE, MF_BYCOMMAND|MF_GRAYED); + + EnableMenuItem (hMenu, IDM_EDIT_CANCEL_PASTE, MF_BYCOMMAND|MF_GRAYED); + } + + if (SelAvailable ()) { + EnableMenuItem (hMenu, IDM_EDIT_CUT, MF_BYCOMMAND|MF_GRAYED); + EnableMenuItem (hMenu, IDM_EDIT_COPY, MF_BYCOMMAND|MF_ENABLED); + EnableMenuItem (hMenu, IDM_EDIT_COPY_APPEND, MF_BYCOMMAND|MF_ENABLED); +#ifdef ACCELERATORS + fAccel |= (EM_CP | EM_CP_APPEND); +#endif + } + else { + if (gAllowCut){ + EnableMenuItem (hMenu, IDM_EDIT_CUT, MF_BYCOMMAND | MF_ENABLED); +#ifdef ACCELERATORS + fAccel |= EM_CUT; +#endif + } + else + EnableMenuItem (hMenu, IDM_EDIT_CUT, MF_BYCOMMAND | MF_GRAYED); + + if (gAllowCopy) { + EnableMenuItem (hMenu, IDM_EDIT_COPY, MF_BYCOMMAND | MF_ENABLED); + EnableMenuItem (hMenu, IDM_EDIT_COPY_APPEND, + MF_BYCOMMAND | MF_ENABLED); +#ifdef ACCELERATORS + fAccel |= (EM_CP | EM_CP_APPEND); +#endif + } + else { + EnableMenuItem (hMenu, IDM_EDIT_COPY, MF_BYCOMMAND | MF_GRAYED); + EnableMenuItem (hMenu, IDM_EDIT_COPY_APPEND, + MF_BYCOMMAND | MF_GRAYED); + } + } + + /* + * Set up Font selection menu + */ + if (gPrintFontName[0] == '\0') { + CheckMenuItem (hMenu, IDM_OPT_FONTSAMEAS, MF_BYCOMMAND | MF_CHECKED); + EnableMenuItem (hMenu, IDM_OPT_SETPRINTFONT, + MF_BYCOMMAND | MF_GRAYED); + } + else { + CheckMenuItem (hMenu, IDM_OPT_FONTSAMEAS, + MF_BYCOMMAND | MF_UNCHECKED); + EnableMenuItem (hMenu, IDM_OPT_SETPRINTFONT, + MF_BYCOMMAND | MF_ENABLED); + } + + /* + * Setup Caret selection menu + */ + EnableMenuItem (hMenu, IDM_OPT_CARETBLOCK, MF_BYCOMMAND | MF_ENABLED); + EnableMenuItem (hMenu, IDM_OPT_CARETSMALLBLOCK, MF_BYCOMMAND | MF_ENABLED); + EnableMenuItem (hMenu, IDM_OPT_CARETHBAR, MF_BYCOMMAND | MF_ENABLED); + EnableMenuItem (hMenu, IDM_OPT_CARETVBAR, MF_BYCOMMAND | MF_ENABLED); + CheckMenuRadioItem(hMenu, IDM_OPT_CARETBLOCK, IDM_OPT_CARETVBAR, + IDM_OPT_CARETBLOCK + pTTYInfo->cCaretStyle, + MF_BYCOMMAND); + + /* + * Check toolbar menu. + */ + EnableMenuItem (hMenu, IDM_OPT_TOOLBAR, MF_BYCOMMAND | MF_ENABLED); + CheckMenuItem (hMenu, IDM_OPT_TOOLBAR, MF_BYCOMMAND | + (pTTYInfo->toolBarSize > 0 ? MF_CHECKED : MF_UNCHECKED)); + EnableMenuItem (hMenu, IDM_OPT_TOOLBARPOS, MF_BYCOMMAND | MF_ENABLED); + CheckMenuItem (hMenu, IDM_OPT_TOOLBARPOS, MF_BYCOMMAND | + (pTTYInfo->toolBarTop > 0 ? MF_CHECKED : MF_UNCHECKED)); + + + + /* + * Check the dialogs menu. + */ + /* xxx EnableMenuItem (hMenu, IDM_OPT_USEDIALOGS, MF_BYCOMMAND | MF_ENABLED);*/ + CheckMenuItem (hMenu, IDM_OPT_USEDIALOGS, MF_BYCOMMAND | + (gfUseDialogs ? MF_CHECKED : MF_UNCHECKED)); + + /* + * Enable the Erase Credentials menu + */ + EnableMenuItem (hMenu, IDM_OPT_ERASE_CREDENTIALS, + MF_BYCOMMAND | (gEraseCredsCallback ? MF_ENABLED : MF_GRAYED)); + + /* + * Enable the View in New Window menu item + */ + EnableMenuItem (hMenu, IDM_MI_VIEWINWIND, + MF_BYCOMMAND | (gViewInWindCallback ? MF_ENABLED : MF_GRAYED)); + +#ifdef ACCELERATORS_OPT + CheckMenuItem (hMenu, IDM_OPT_USEACCEL, MF_BYCOMMAND | + (pTTYInfo->hAccel ? MF_CHECKED : MF_UNCHECKED)); +#endif + + /* + * Setup the sort menu... + */ + if(gSortCallback){ + i = (*gSortCallback)(0, 0); + + /* NOTE: this func's args are dependent on definition order + * in resource.h + */ + CheckMenuRadioItem(hMenu, IDM_MI_SORTSUBJECT, IDM_MI_SORTTHREAD, + IDM_MI_SORTSUBJECT + (i & 0x00ff), MF_BYCOMMAND); + CheckMenuItem(hMenu, IDM_MI_SORTREVERSE, + MF_BYCOMMAND|((i & 0x0100) ? MF_CHECKED : MF_UNCHECKED)); + } + else{ + CheckMenuRadioItem(hMenu, IDM_MI_SORTSUBJECT, IDM_MI_SORTTHREAD, + IDM_MI_SORTARRIVAL, MF_BYCOMMAND); + CheckMenuItem(hMenu, IDM_MI_SORTREVERSE, MF_BYCOMMAND | MF_UNCHECKED); + } + + /* + * Setup the flag menu... + */ + if(gFlagCallback){ + int flags = (*gFlagCallback)(0, 0); + for(i = IDM_MI_FLAGIMPORTANT; i <= IDM_MI_FLAGDELETED; i++) + CheckMenuItem(hMenu, i, MF_BYCOMMAND + | (((flags >> (i - IDM_MI_FLAGIMPORTANT)) & 0x0001) + ? MF_CHECKED : MF_UNCHECKED)); + } + + /* + * + */ + if(gHdrCallback){ + i = (*gHdrCallback)(0, 0); + CheckMenuItem(hMenu, IDM_MI_HDRMODE, + MF_BYCOMMAND|((i != 0) ? MF_CHECKED : MF_UNCHECKED)); + } + + /* + * + */ + if(gZoomCallback){ + i = (*gZoomCallback)(0, 0); + CheckMenuItem(hMenu, IDM_MI_ZOOM, + MF_BYCOMMAND | (i ? MF_CHECKED : MF_UNCHECKED)); + } + /* + * Set up command menu. + */ + if (!pTTYInfo->menuItemsCurrent) { + for (i = 0; i < KS_COUNT; ++i) + if(i + KS_RANGESTART != KS_GENERALHELP) + EnableMenuItem (hMenu, i + KS_RANGESTART, + MF_BYCOMMAND | ((pTTYInfo->menuItems[i].miActive) + ? MF_ENABLED : MF_GRAYED)); + + /* + * Special command-specific knowledge here + */ + for(i = IDM_MI_SORTSUBJECT; i <= IDM_MI_SORTREVERSE; i++) + EnableMenuItem (hMenu, i, + MF_BYCOMMAND + | ((pTTYInfo->menuItems[KS_SORT-KS_RANGESTART].miActive) + ? MF_ENABLED : MF_GRAYED)); + + for(i = IDM_MI_FLAGIMPORTANT; i <= IDM_MI_FLAGDELETED; i++) + EnableMenuItem (hMenu, i, + MF_BYCOMMAND + | ((pTTYInfo->menuItems[KS_FLAG - KS_RANGESTART].miActive) + ? MF_ENABLED : MF_GRAYED)); + + } + + /* + * deal with any callback state dependent enabling + */ + if(pTTYInfo->menuItems[IDM_MI_APPLY - KS_RANGESTART].miActive) + EnableMenuItem(hMenu, IDM_MI_APPLY, + MF_BYCOMMAND | ((gSelectedCallback + && (*gSelectedCallback)(0, 0)) + ? MF_ENABLED : MF_GRAYED)); + + if(pTTYInfo->menuItems[IDM_MI_ZOOM - KS_RANGESTART].miActive) + EnableMenuItem(hMenu, IDM_MI_ZOOM, + MF_BYCOMMAND | ((gSelectedCallback + && (*gSelectedCallback)(0, 0)) + ? MF_ENABLED : MF_GRAYED)); + +#ifdef ACCELERATORS + if(pTTYInfo->menuItems[KS_WHEREIS - KS_RANGESTART].miActive) + fAccel |= EM_FIND; + + AccelManage (hWnd, fAccel); +#endif + + pTTYInfo->menuItemsCurrent = TRUE; +} + + + +/* + * Cut region to kill buffer. + */ +LOCAL void +EditCut (void) +{ + HANDLE hCB; + + if(gCopyCutFunction == (getc_t)kremove){ + hCB = GlobalAlloc (GMEM_MOVEABLE, 0); + if (hCB != NULL) { + kdelete(); /* Clear current kill buffer. */ + killregion (1, 0); /* Kill Region (and copy to clipboard). */ + update (); /* And update the screen */ + } + } +} + + + +/* + * This function copies the kill buffer to the window's clip board. + * (actually, it can copy any buffer for which a copyfunc is provided). + * Called from ldelete(). + */ +void +mswin_killbuftoclip (getc_t copyfunc) +{ + HANDLE hCB; + getc_t oldfunc; + + /* Save old copy function. */ + oldfunc = gCopyCutFunction; + gCopyCutFunction = copyfunc; + + /* Allocate clip buffer. */ + hCB = GlobalAlloc (GMEM_MOVEABLE, 0); + if (hCB != NULL) { + EditDoCopyData (hCB, 0); + } + + /* restore copy function. */ + gCopyCutFunction = oldfunc; +} + + + +/* + * Copy region to kill buffer. + */ +LOCAL void +EditCopy (void) +{ + HANDLE hCB; + + if (SelAvailable()) { + /* This is a copy of the windows selection. */ + hCB = GlobalAlloc (GMEM_MOVEABLE, 0); + if (hCB != NULL) + SelDoCopy (hCB, 0); + } + else { + + /* Otherwise, it's a Pico/Pine copy. */ + if(gCopyCutFunction == (getc_t)kremove){ + kdelete(); /* Clear current kill buffer. */ + copyregion (1, 0); + } + + hCB = GlobalAlloc (GMEM_MOVEABLE, 0); + if (hCB != NULL) + EditDoCopyData (hCB, 0); + } +} + + + +/* + * Called in responce to "Copy Append" menu command, when there is an active + * Windows selection on the screen. + */ +LOCAL void +EditCopyAppend (void) +{ + HANDLE hCB; + HANDLE hMyCopy; + TCHAR *pCB; + TCHAR *pMyCopy; + size_t cbSize = 0; + + /* Attempt to copy clipboard data to my own handle. */ + hMyCopy = NULL; + if (OpenClipboard (ghTTYWnd)) { /* And can get clipboard. */ + hCB = GetClipboardData (CF_UNICODETEXT); + if (hCB != NULL) { /* And can get data. */ + pCB = GlobalLock (hCB); + cbSize = _tcslen (pCB); /* It's a null term string. */ + hMyCopy = GlobalAlloc (GMEM_MOVEABLE, (cbSize+1)*sizeof(*pCB)); + if (hMyCopy != NULL) { /* And can get memory. */ + pMyCopy = GlobalLock (hMyCopy); + if (pMyCopy != NULL) { + memcpy (pMyCopy, pCB, cbSize*sizeof(*pCB)); /* Copy data. */ + GlobalUnlock (hMyCopy); + } + else { + GlobalFree (hMyCopy); + hMyCopy = NULL; + } + } + GlobalUnlock (hCB); + } /* GetClipboardData. */ + CloseClipboard (); + } /* OpenClipboard. */ + + + + /* Now, if I got a copy, append current selection to that + * and stuff it back into the clipboard. */ + if (hMyCopy != NULL) { + if (SelAvailable ()) { + SelDoCopy (hMyCopy, (DWORD)cbSize); + } + else { + if(gCopyCutFunction == (getc_t)kremove) { + kdelete(); /* Clear current kill buffer. */ + copyregion (1, 0); + } + EditDoCopyData (hMyCopy, (DWORD)cbSize); + } + } +} + + +/* + * Copy data from the kill buffer to the clipboard. Handle LF->CRLF + * translation if necessary. + */ +LOCAL void +EditDoCopyData (HANDLE hCB, DWORD lenCB) +{ + TCHAR *pCB; + TCHAR *p; + long c; /* would be TCHAR but needs -1 retval from callback */ + TCHAR lastc = (TCHAR)'\0'; + DWORD cbSize; /* Allocated size of hCB. */ + DWORD i; +#define BUF_INC 4096 + + if (gCopyCutFunction != NULL) { /* If there really is data. */ + if (OpenClipboard (ghTTYWnd)) { /* ...and we get the CB. */ + if (EmptyClipboard ()) { /* ...and clear previous CB.*/ + pCB = GlobalLock (hCB); + p = pCB + lenCB; + cbSize = lenCB; + /* Copy it. (BUG: change int arg) */ + for(i = 0L; (c = (*gCopyCutFunction)((int)i)) != -1; i++){ + /* + * Rather than fix every function that might + * get called for character retrieval to supply + * CRLF EOLs, let's just fix it here. The downside + * is a much slower copy for large buffers, but + * hey, what do they want? + */ + if(lenCB + 2L >= cbSize){ + cbSize += BUF_INC; + GlobalUnlock (hCB); + hCB = GlobalReAlloc (hCB, cbSize*sizeof(TCHAR), GMEM_MOVEABLE); + if (hCB == NULL) + return; + + pCB = GlobalLock (hCB); + p = pCB + lenCB; + } + + if(c == (TCHAR)ASCII_LF && lastc != (TCHAR)ASCII_CR) { + *p++ = (TCHAR)ASCII_CR; /* insert CR before LF */ + lenCB++; + } + + *p++ = lastc = (TCHAR)c; + lenCB++; + } + + /* Only if we got some data. */ + if (lenCB > 0) { + *p = (TCHAR)'\0'; + GlobalUnlock (hCB); + + if (SetClipboardData (CF_UNICODETEXT, hCB) == NULL) + /* Failed! Free the data. */ + GlobalFree (hCB); + } + else { + /* There was no data copied. */ + GlobalUnlock (hCB); + GlobalFree (hCB); + } + } + CloseClipboard (); + } + } +} + + +/* + * Get a handle to the current (text) clipboard and make my own copy. + * Keep my copy locked because I'll be using it to read bytes from. + */ +LOCAL void +EditPaste (void) +{ + HANDLE hCB; + LPTSTR pCB; + LPTSTR pPaste; + size_t cbSize; + + if (ghPaste == NULL) { /* If we are not already pasting. */ + if (OpenClipboard (ghTTYWnd)) { /* And can get clipboard. */ + hCB = GetClipboardData (CF_UNICODETEXT); + if (hCB != NULL) { /* And can get data. */ + pCB = GlobalLock (hCB); + cbSize = _tcslen (pCB); /* It's a null term string. */ + ghPaste = GlobalAlloc (GMEM_MOVEABLE, (cbSize+1)*sizeof(TCHAR)); + if (ghPaste != NULL) { /* And can get memory. */ + gpPasteNext = GlobalLock (ghPaste); + memcpy (gpPasteNext, pCB, (cbSize+1)*sizeof(TCHAR)); /* Copy data. */ + /* Keep ghPaste locked. */ + + /* + * If we're paste is enabled but limited to the first + * line of the clipboard, prune the paste buffer... + */ + if(gPasteEnabled == MSWIN_PASTE_LINE + && (pPaste = _tcschr(gpPasteNext, (TCHAR)ASCII_CR))){ + *pPaste = (TCHAR)'\0'; + cbSize = _tcslen(gpPasteNext); + } + + /* + * If there is a selection (gCopyCutFunction != NULL) + * then delete it so that it will be replaced by + * the pasted text. + */ + if (gCopyCutFunction != NULL) + deleteregion (1, 0); + + gPasteBytesRemain = cbSize; + gPasteWasCR = FALSE; +#ifdef FDEBUG + if (mswin_debug > 8) + fprintf (mswin_debugfile, "EditPaste:: Paste %d bytes\n", + gPasteBytesRemain); +#endif + } + GlobalUnlock (hCB); + } + CloseClipboard (); + } + } +} + + + + + + +/* + * Cancel an active paste operation. + */ +LOCAL void +EditCancelPaste (void) +{ + if (ghPaste != NULL) { /* Must be pasting. */ + GlobalUnlock (ghPaste); /* Then Unlock... */ + GlobalFree (ghPaste); /* ...and free the paste buffer. */ + ghPaste = NULL; /* Indicates no paste data. */ + gpPasteNext = NULL; /* Just being tidy. */ + gPasteBytesRemain = 0; /* ditto. */ +#ifdef FDEBUG + if (mswin_debug > 8) + fprintf (mswin_debugfile, "EditCancelPaste:: Free Paste Data\n"); +#endif + } +} + + +/* + * Get the next byte from the paste buffer. If all bytes have been + * retreived, free the paste buffer. + * Map all CRLF sequence to a single CR. + */ +LOCAL UCS +EditPasteGet (void) +{ + UCS b = NODATA; + + if (ghPaste != NULL) { /* ghPaste tells if we are pasting. */ + if (gPasteBytesRemain > 0) { /* Just in case... */ + /* Get one byte and move pointer. */ + b = (TCHAR) *gpPasteNext++; + --gPasteBytesRemain; /* one less. */ + if (gPasteWasCR && b == (TCHAR)ASCII_LF) { + if (gPasteBytesRemain) { + /* Skip of LF. */ + b = (TCHAR) *gpPasteNext++; + --gPasteBytesRemain; + } + else + b = NODATA; /* Ignore last LF. */ + } + gPasteWasCR = (b == (TCHAR)ASCII_CR); +#ifdef FDEBUG + if (mswin_debug > 8) + fprintf (mswin_debugfile, "EditPasteGet:: char %c, gPasteWasCR %d, gPasteBytesRemain %d\n", + b, gPasteWasCR, gPasteBytesRemain); +#endif + } + if (gPasteBytesRemain <= 0) { /* All Done? */ + GlobalUnlock (ghPaste); /* Then Unlock... */ + GlobalFree (ghPaste); /* ...and free the paste buffer. */ + ghPaste = NULL; /* Indicates no paste data. */ + gpPasteNext = NULL; /* Just being tidy. */ + gPasteBytesRemain = 0; /* ditto. */ +#ifdef FDEBUG + if (mswin_debug > 8) + fprintf (mswin_debugfile, "EditPasteGet:: Free Paste Data\n"); +#endif + } + } + + if(b < ' ') { + b += '@'; + b |= CTRL; + } + + return (b); +} + + +/* + * Return true if Paste data is available. If gpPaste != NULL then there + * is paste data. + */ +LOCAL BOOL +EditPasteAvailable (void) +{ + return (ghPaste != NULL); +} + + + +/* + * Select everything in the buffer + */ +LOCAL void +EditSelectAll() +{ + if(ComposerEditing){ + } + else{ + gotobob(0, 1); + setmark(0, 1); + gotoeob(0, 1); + update (); /* And update the screen */ + } +} + + +LOCAL void +SortHandler(int order, int reverse) +{ + int old = (*gSortCallback)(0, 0); + + if(order < 0){ + old ^= 0x0100; /* flip reverse bit */ + (*gSortCallback)(1, old); + } + else + (*gSortCallback)(1, order | (old & 0x0100)); +} + + +LOCAL void +FlagHandler(int index, int args) +{ + if(gFlagCallback) + (void) (*gFlagCallback)(index + 1, 0L); +} + + +LOCAL void +MSWHelpShow (cbstr_t fpHelpCallback) +{ + if (fpHelpCallback != NULL){ + char title[256], *help; + + if(help = (*fpHelpCallback) (title)) + mswin_displaytext (title, help, strlen(help), NULL, NULL, 0); + } +} + + + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Adjust the timer frequency as needed. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +LOCAL void +MyTimerSet (void) +{ + UINT period; + /* Decide on period to use. */ + if (gAllowMouseTrack) + period = MY_TIMER_EXCEEDINGLY_SHORT_PERIOD; + else + period = my_timer_period; + + if (period != gTimerCurrentPeriod) { + if (SetTimer (ghTTYWnd, MY_TIMER_ID, period, NULL) == 0) + MessageBox (ghTTYWnd, TIMER_FAIL_MESSAGE, NULL, + MB_OK | MB_ICONINFORMATION); + else + gTimerCurrentPeriod = period; + } +} + + + + + +void +mswin_setperiodiccallback (cbvoid_t periodiccb, long period) +{ + if (periodiccb != NULL && period > 0) { + gPeriodicCallback = periodiccb; + gPeriodicCBTime = period; + gPeriodicCBTimeout = GetTickCount () / 1000 + gPeriodicCBTime; + } + else { + gPeriodicCallback = NULL; + } +} + +/* + * Structure for variables used by mswin_exec_and_wait which need to be + * freed in multiple places. + */ +typedef struct MSWIN_EXEC_DATA { + HANDLE infd; + HANDLE outfd; + LPTSTR lptstr_whatsit; + LPTSTR lptstr_command; + LPTSTR lptstr_infile; + LPTSTR lptstr_outfile; + MSWIN_TEXTWINDOW *mswin_tw; +} MSWIN_EXEC_DATA; + +LOCAL void +mswin_exec_data_init(MSWIN_EXEC_DATA *exec_data) +{ + memset(exec_data, 0, sizeof(MSWIN_EXEC_DATA)); + exec_data->infd = INVALID_HANDLE_VALUE; + exec_data->outfd = INVALID_HANDLE_VALUE; +} + +LOCAL void +mswin_exec_data_free(MSWIN_EXEC_DATA *exec_data, BOOL delete_outfile) +{ + if(exec_data->infd != INVALID_HANDLE_VALUE) + CloseHandle(exec_data->infd); + + if(exec_data->outfd != INVALID_HANDLE_VALUE) { + CloseHandle(exec_data->outfd); + if(delete_outfile) + _tunlink(exec_data->lptstr_outfile); + } + + if(exec_data->lptstr_infile) + fs_give((void **) &exec_data->lptstr_infile); + if(exec_data->lptstr_outfile) + fs_give((void **) &exec_data->lptstr_outfile); + if(exec_data->lptstr_whatsit) + fs_give((void **) &exec_data->lptstr_whatsit); + if(exec_data->lptstr_command) + fs_give((void **) &exec_data->lptstr_command); + + if(exec_data->mswin_tw) { + /* + * Set the out_file is zero. We don't need mswin_tw + * to save the file anymore since we're bailing. + */ + exec_data->mswin_tw->out_file = NULL; + + /* + * If the window is still open, then set the id to 0 so + * mswin_tw_close_callback() will free the memory whenever + * the window closes. Otherwise free it now. + */ + if(exec_data->mswin_tw->hwnd) + exec_data->mswin_tw->id = 0; + else + MemFree(exec_data->mswin_tw); + } +} + +/* + * Execute command and wait for the child to exit + * + * whatsit - description of reason exec being called + * command - command to run + * infile - name of file to pass as stdin + * outfile - name of file to pass as stdout + * exit_val - where to store return value of the process + * mswe_flags - + * MSWIN_EAW_CAPT_STDERR - capture stderr along with stdout + * MSWIN_EAW_CTRL_C_CANCELS - if user presses ctrl-c, detach child + * Returns: 0, successfully completed program + * -1, errors occurred + * -2, user chose to stop waiting for program before it finished + */ +int +mswin_exec_and_wait (char *utf8_whatsit, char *utf8_command, + char *utf8_infile, char *utf8_outfile, + int *exit_val, unsigned mswe_flags) +{ + MEvent mouse; + BOOL brc; + int rc; + TCHAR waitingFor[256]; + PROCESS_INFORMATION proc_info; + DWORD exit_code; + MSWIN_EXEC_DATA exec_data; +#ifdef ALTED_DOT + BOOL b_use_mswin_tw; +#endif + + mswin_exec_data_init(&exec_data); + + memset(&proc_info, 0, sizeof(proc_info)); + + mswin_flush (); + + exec_data.lptstr_infile = utf8_infile ? utf8_to_lptstr(utf8_infile) : NULL; + exec_data.lptstr_outfile = utf8_outfile ? utf8_to_lptstr(utf8_outfile) : NULL; + + exec_data.lptstr_command = utf8_to_lptstr(utf8_command); + exec_data.lptstr_whatsit = utf8_to_lptstr(utf8_whatsit); + +#ifdef ALTED_DOT + /* If the command is '.', then use mswin_tw to open the file. */ + b_use_mswin_tw = utf8_command && + utf8_command[0] == '.' && utf8_command[1] == '\0'; + + if(b_use_mswin_tw) { + + proc_info.hThread = INVALID_HANDLE_VALUE; + proc_info.hProcess = INVALID_HANDLE_VALUE; + + exec_data.mswin_tw = mswin_tw_displaytext_lptstr( + exec_data.lptstr_whatsit, exec_data.lptstr_infile, 4, NULL, + exec_data.mswin_tw, MSWIN_DT_FILLFROMFILE); + + if(exec_data.mswin_tw) { + mswin_set_readonly(exec_data.mswin_tw, FALSE); + + /* Tell mswin_tw to write the edit contents to this file. */ + exec_data.mswin_tw->out_file = exec_data.lptstr_outfile; + + /* Make sure mswin_tw isn't freed behind our back. */ + exec_data.mswin_tw->id = (UINT)-1; + brc = TRUE; + } + else { + brc = FALSE; + } + } + else +#endif /* ALTED_DOT */ + { + SECURITY_ATTRIBUTES atts; + STARTUPINFO start_info; + + memset(&atts, 0, sizeof(atts)); + memset(&start_info, 0, sizeof(start_info)); + + /* set file attributes of temp files*/ + atts.nLength = sizeof(SECURITY_ATTRIBUTES); + atts.bInheritHandle = TRUE; + atts.lpSecurityDescriptor = NULL; + + /* open files if asked for */ + if(utf8_infile + && ((exec_data.infd = CreateFile(exec_data.lptstr_infile, + GENERIC_READ, 0, &atts, + OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL)) + == INVALID_HANDLE_VALUE)){ + + mswin_exec_data_free(&exec_data, TRUE); + return(-1); + } + + if(utf8_outfile + && ((exec_data.outfd = CreateFile(exec_data.lptstr_outfile, + GENERIC_WRITE, 0, &atts, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) + == INVALID_HANDLE_VALUE)){ + + mswin_exec_data_free(&exec_data, TRUE); + return(-1); + } + + start_info.dwFlags = STARTF_FORCEONFEEDBACK | STARTF_USESHOWWINDOW; + start_info.wShowWindow = (utf8_infile || utf8_outfile) ? SW_SHOWMINNOACTIVE : SW_SHOWNA; + + /* set up i/o redirection */ + if(utf8_infile) + start_info.hStdInput = exec_data.infd; + if(utf8_outfile) + start_info.hStdOutput = exec_data.outfd; + if(utf8_outfile && (mswe_flags & MSWIN_EAW_CAPT_STDERR)) + start_info.hStdError = exec_data.outfd; + if(utf8_infile || utf8_outfile) + start_info.dwFlags |= STARTF_USESTDHANDLES; + + brc = CreateProcess(NULL, exec_data.lptstr_command, NULL, NULL, + (utf8_infile || utf8_outfile) ? TRUE : FALSE, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL, NULL, &start_info, &proc_info); + } + + if(brc) { + _sntprintf(waitingFor, sizeof(waitingFor)/sizeof(TCHAR), + TEXT("%s is currently waiting for the %s (%s) to complete. Click \"Cancel\" to stop waiting, or \"OK\" to continue waiting."), + gszAppName, exec_data.lptstr_whatsit, exec_data.lptstr_command); + + if(proc_info.hThread != INVALID_HANDLE_VALUE) { + /* Don't need the thread handle, close it now. */ + CloseHandle (proc_info.hThread); + } + + /* + * Go into holding pattern until the other application terminates + * or we are told to stop waiting. + */ + while(TRUE){ + +#ifdef ALTED_DOT + if(b_use_mswin_tw) + { + if(!exec_data.mswin_tw) + break; + + exit_code = exec_data.mswin_tw->hwnd && exec_data.mswin_tw->out_file ? + STILL_ACTIVE : 0; + } + else +#endif /* ALTED_DOT */ + { + if(GetExitCodeProcess(proc_info.hProcess, &exit_code) == FALSE) + break; + } + + if(exit_code == STILL_ACTIVE){ + rc = mswin_getc(); + brc = mswin_getmouseevent (&mouse); + + if (rc != NODATA || + (brc && mouse.event == M_EVENT_DOWN)) { + if(mswe_flags & MSWIN_EAW_CTRL_C_CANCELS){ + if(rc == (CTRL|'C')) + rc = IDCANCEL; + } + else{ + rc = MessageBox (ghTTYWnd, waitingFor, exec_data.lptstr_whatsit, + MB_ICONSTOP | MB_OKCANCEL); + } + SelClear (); + if (rc == IDCANCEL){ + /* terminate message to child ? */ + mswin_exec_data_free(&exec_data, TRUE); + return (-2); + } + } + } + else{ + if(proc_info.hProcess != INVALID_HANDLE_VALUE) { + /* do something about child's exit status */ + CloseHandle (proc_info.hProcess); + } + if(exit_val) + *exit_val = exit_code; + break; + } + } + + if (gpTTYInfo->fMinimized) + ShowWindow (ghTTYWnd, SW_SHOWNORMAL); + + /* + * If we're using a mswin_tw and we're not capturing the output, we + * just bailed immediately. If that's the case, don't bring the main + * window up over the top of the textwindow we just brought up. + */ +#ifdef ALTED_DOT + if(!b_use_mswin_tw || !exec_data.mswin_tw || exec_data.mswin_tw->out_file) +#endif /* ALTED_DOT */ + BringWindowToTop (ghTTYWnd); + + mswin_exec_data_free(&exec_data, FALSE); + return (0); + } + else{ + mswin_exec_data_free(&exec_data, TRUE); + return((rc = (int) GetLastError()) ? rc : -1); /* hack */ + } + + /* NOTREACHED */ + return(-1); +} + + +int +mswin_shell_exec(char *command_utf8, HINSTANCE *pChildProc) +{ + int quoted = 0; + SHELLEXECUTEINFO shell_info; + LPTSTR command_lpt, free_command_lpt; + LPTSTR p, q, parm = NULL; + TCHAR buf[1024]; + + if(!command_utf8) + return(-1); + + free_command_lpt = command_lpt = utf8_to_lptstr(command_utf8); + if(!command_lpt) + return(-1); + + mswin_flush (); + + /* + * Pick first arg apart by whitespace until what's to the left + * is no longer a valid path/file. Everything else is then an + * command line arg... + */ + if(*(p = command_lpt) == '\"'){ + p = ++command_lpt; /* don't include quote */ + quoted++; + } + + q = buf; + while(1) + if(!quoted && _istspace(*p)){ + char *buf_utf8; + + *q = '\0'; + buf_utf8 = lptstr_to_utf8(buf); + if(*buf == '*' || (buf_utf8 && fexist(buf_utf8, "x", (off_t *) NULL) == FIOSUC)){ + parm = p; + if(buf_utf8) + fs_give((void **) &buf_utf8); + + break; + } + + if(buf_utf8) + fs_give((void **) &buf_utf8); + } + else if(quoted && *p == '\"'){ + parm = p; + break; + } + else if(!(*q++ = *p++)){ + parm = p - 1; + break; + } + + if(*command_lpt && parm){ + do + *parm++ = '\0'; + while(*parm && _istspace((unsigned char) *parm)); + + + /* + * HACK -- since star is very unlikely to actually appear + * in a command name thats launched via a shell command line, + * a leading one indicates special handling. + */ + if(command_lpt[0] == '*'){ + if(!_tcsncmp(command_lpt + 1, TEXT("Shell*"), 8)){ + /* Leave it to ShellExecute magic to "open" the thing */ + command_lpt = parm; + parm = NULL; + } + } + } + else{ + if(free_command_lpt) + fs_give((void **) &free_command_lpt); + + return(-1); + } + + memset(&shell_info, 0, sizeof(SHELLEXECUTEINFO)); + shell_info.cbSize = sizeof(SHELLEXECUTEINFO); + shell_info.fMask = SEE_MASK_DOENVSUBST + | SEE_MASK_NOCLOSEPROCESS + | SEE_MASK_FLAG_DDEWAIT; + shell_info.hwnd = ghTTYWnd; + shell_info.lpFile = command_lpt; + shell_info.lpParameters = parm; + shell_info.lpDirectory = NULL; /* default is current directory */ + shell_info.nShow = SW_SHOWNORMAL; + + ShellExecuteEx(&shell_info); + + if((int)(LONG_PTR)shell_info.hInstApp > 32){ + if(pChildProc) + *pChildProc = shell_info.hProcess; + + if(free_command_lpt) + fs_give((void **) &free_command_lpt); + + return(0); /* success! */ + } + + if(free_command_lpt) + fs_give((void **) &free_command_lpt); + + return(-1); +} + + +/* + * Generate an error message for a failed windows exec or loadlibrary. + */ +void +mswin_exec_err_msg(char *what, int status, char *buf, size_t buflen) +{ + switch(status){ + case 2: + case 3: + snprintf(buf, buflen, "%s not found.", what); + break; + + case 8: + snprintf(buf, buflen, "Not enough memory to run %s.", what); + break; + + default: + snprintf(buf, buflen, "Error %d starting %s.", status, what); + break; + } +} + + +int +mswin_set_quit_confirm (int confirm) +{ + gConfirmExit = (confirm != 0); + return (confirm); +} + + +/* + * Called when Windows is in shutting down. Before actually shutting down + * Windows goes around to all the applications and asks if it is OK with + * them to shut down (WM_QUERYENDSESSION). + * If gConfirmExit is set, ask the user if they want to exit. + * Returning zero will stop the shutdown, non-zero allows it to proceed. + */ +LOCAL LRESULT +ConfirmExit (void) +{ + TCHAR msg[256]; + int rc; + + if(gConfirmExit){ + _sntprintf(msg, sizeof(msg)/sizeof(TCHAR), + TEXT("Exiting may cause you to lose work in %s, Exit?"), + gszAppName); + rc = MessageBox (ghTTYWnd, msg, gszAppName, MB_ICONSTOP | MB_OKCANCEL); + if(rc == IDCANCEL) + return(0); + } + + return(1); +} + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Registry access functions + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* + * Useful def's + */ +#define MSWR_KEY_MAX 128 +#define MSWR_VAL_MAX 128 +#define MSWR_CLASS_MAX 128 +#define MSWR_DATA_MAX 1024 + + +#define MSWR_ROOT TEXT("Software\\University of Washington\\Alpine\\1.0") +#define MSWR_CAPABILITIES TEXT("Software\\University of Washington\\Alpine\\1.0\\Capabilities") +#define MSWR_APPNAME TEXT("Alpine") +#define MSWR_DLLPATH TEXT("DLLPath") +#define MSWR_DLLNAME TEXT("pmapi32.dll") +#define MSWRDBUF 1152 + + +struct mswin_reg_key { + HKEY rhk; /* root key (HKEY_LOCAL_MACHINE, ...) */ + LPTSTR *knames; /* NULL terminated list of keys */ +}; + +LPTSTR mswin_pine_hklm_regs[] = { + MSWR_ROOT, + TEXT("Software\\Clients\\Mail\\Alpine"), + TEXT("Software\\Clients\\News\\Alpine"), + TEXT("Software\\Classes\\Alpine.Url.Mailto"), + TEXT("Software\\Classes\\Alpine.Url.News"), + TEXT("Software\\Classes\\Alpine.Url.Nntp"), + TEXT("Software\\Classes\\Alpine.Url.Imap"), + NULL +}; + +LPTSTR mswin_pine_hkcu_regs[] = { + MSWR_ROOT, + NULL +}; + +static struct mswin_reg_key mswin_pine_regs[] = { + {HKEY_LOCAL_MACHINE, mswin_pine_hklm_regs}, + {HKEY_CURRENT_USER, mswin_pine_hkcu_regs}, + {NULL, NULL} +}; + + +/* + * data: unitialized buffer, could be null + */ +int +mswin_reg(int op, int tree, char *data_utf8, size_t size) +{ + LPTSTR data_lptstr = NULL; + int rv; + + if(data_utf8){ + if(size == 0){ + /* size is zero when op & MSWR_OP_SET */ + data_lptstr = utf8_to_lptstr(data_utf8); + } + else { + data_lptstr = (LPTSTR)MemAlloc(size * sizeof(TCHAR)); + data_lptstr[0] = '\0'; + } + } + + rv = mswin_reg_lptstr(op, tree, data_lptstr, size); + + if(data_utf8 && data_lptstr){ + char *t_utf8str; + if(size){ + t_utf8str = lptstr_to_utf8(data_lptstr); + strncpy(data_utf8, t_utf8str, size); + data_utf8[size-1] = '\0'; + MemFree((void *)t_utf8str); + } + MemFree((void *)data_lptstr); + } + return(rv); +} + +int +mswin_reg_lptstr(int op, int tree, LPTSTR data_lptstr, size_t size) +{ + if(op & MSWR_OP_SET){ + switch(tree){ + case MSWR_PINE_RC : + MSWRAlpineSet(HKEY_CURRENT_USER, NULL, + TEXT("PineRC"), op & MSWR_OP_FORCE, data_lptstr); + break; + + case MSWR_PINE_CONF : + MSWRAlpineSet(HKEY_CURRENT_USER, NULL, + TEXT("PineConf"), op & MSWR_OP_FORCE, data_lptstr); + break; + + case MSWR_PINE_AUX : + MSWRAlpineSet(HKEY_CURRENT_USER, NULL, + TEXT("PineAux"), op & MSWR_OP_FORCE, data_lptstr); + break; + + case MSWR_PINE_DIR : + MSWRAlpineSet(HKEY_LOCAL_MACHINE, NULL, + TEXT("Pinedir"), op & MSWR_OP_FORCE, data_lptstr); + MSWRAlpineSetHandlers(op & MSWR_OP_FORCE, data_lptstr); + break; + + case MSWR_PINE_EXE : + MSWRAlpineSet(HKEY_LOCAL_MACHINE, NULL, + TEXT("PineEXE"), op & MSWR_OP_FORCE, data_lptstr); + break; + + case MSWR_PINE_POS : + MSWRAlpineSet(HKEY_CURRENT_USER, NULL, + TEXT("PinePos"), op & MSWR_OP_FORCE, data_lptstr); + break; + + default : + break; + } + } + else if(op & MSWR_OP_GET){ + switch(tree){ + case MSWR_PINE_RC : + return(MSWRAlpineGet(HKEY_CURRENT_USER, NULL, + TEXT("PineRC"), data_lptstr, size)); + case MSWR_PINE_CONF : + return(MSWRAlpineGet(HKEY_CURRENT_USER, NULL, + TEXT("PineConf"), data_lptstr, size)); + case MSWR_PINE_AUX : + return(MSWRAlpineGet(HKEY_CURRENT_USER, NULL, + TEXT("PineAux"), data_lptstr, size)); + case MSWR_PINE_DIR : + return(MSWRAlpineGet(HKEY_LOCAL_MACHINE, NULL, + TEXT("Pinedir"), data_lptstr, size)); + case MSWR_PINE_EXE : + return(MSWRAlpineGet(HKEY_LOCAL_MACHINE, NULL, + TEXT("PineEXE"), data_lptstr, size)); + case MSWR_PINE_POS : + return(MSWRAlpineGet(HKEY_CURRENT_USER, NULL, + TEXT("PinePos"), data_lptstr, size)); + default : + break; + } + } + else if(op & MSWR_OP_BLAST){ + int rv = 0, i, j; + + for(i = 0; mswin_pine_regs[i].rhk; i++){ + for(j = 0; mswin_pine_regs[i].knames[j]; j++) + MSWRClear(mswin_pine_regs[i].rhk, mswin_pine_regs[i].knames[j]); + } + if(rv) return -1; + } + /* else, ignore unknown op? */ + + return(0); +} + + +LOCAL void +MSWRAlpineSet(HKEY hRootKey, LPTSTR subkey, LPTSTR val, int update, LPTSTR data) +{ + HKEY hKey; + TCHAR keybuf[MSWR_KEY_MAX+1]; + + _sntprintf(keybuf, MSWR_KEY_MAX+1, TEXT("%s%s%s"), MSWR_ROOT, + (subkey && *subkey != '\\') ? TEXT("\\") : TEXT(""), + (subkey) ? TEXT("\\") : TEXT("")); + + if(RegCreateKeyEx(hRootKey, keybuf, 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hKey, NULL) == ERROR_SUCCESS){ + if(update || RegQueryValueEx(hKey, val, NULL, NULL, + NULL, NULL) != ERROR_SUCCESS) + RegSetValueEx(hKey, val, 0, REG_SZ, (LPBYTE)data, (DWORD)(_tcslen(data)+1)*sizeof(TCHAR)); + + RegCloseKey(hKey); + } +} + + + +LOCAL int +MSWRAlpineGet(HKEY hKey, LPTSTR subkey, LPTSTR val, LPTSTR data_lptstr, size_t len) +{ + TCHAR keybuf[MSWR_KEY_MAX+1]; + DWORD dlen = (DWORD)len; + + _sntprintf(keybuf, MSWR_KEY_MAX+1, TEXT("%s%s%s"), MSWR_ROOT, + (subkey && *subkey != '\\') ? TEXT("\\") : TEXT(""), + (subkey) ? TEXT("\\") : TEXT("")); + + return(MSWRPeek(hKey, keybuf, val, data_lptstr, &dlen) == TRUE); +} + + + +LOCAL void +MSWRAlpineSetHandlers(int update, LPTSTR path_lptstr) +{ + HKEY hKey, hSubKey; + DWORD dwDisp; + BYTE tmp_b[MSWR_DATA_MAX]; + unsigned long tmplen = MSWR_DATA_MAX, tmp_lptstr_tcharlen = MSWR_DATA_MAX/sizeof(TCHAR); + int exists; + LPTSTR tmp_lptstr = (LPTSTR)tmp_b; + + /* Register as a mail client on this system */ + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, + MSWR_ROOT, 0, KEY_ALL_ACCESS, + &hKey) == ERROR_SUCCESS){ + if(RegCreateKeyEx(HKEY_LOCAL_MACHINE, MSWR_CAPABILITIES, 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hSubKey, &dwDisp) == ERROR_SUCCESS){ + MSWRPoke(hSubKey, NULL, TEXT("ApplicationDescription"), + TEXT("Alpine - A program for sending, receiving, and filing email and news, whether stored locally or accessed over the network via IMAP, POP3, or NNTP. Alpine is the successor to Pine, and also is maintained by the University of Washington.")); + MSWRPoke(hSubKey, NULL, TEXT("ApplicationName"), + TEXT("Alpine")); + _sntprintf(tmp_lptstr, tmp_lptstr_tcharlen, TEXT("%salpine.exe,0"), path_lptstr); + MSWRPoke(hSubKey, NULL, TEXT("ApplicationIcon"), tmp_lptstr); + MSWRPoke(hSubKey, TEXT("UrlAssociations"), TEXT("mailto"), TEXT("Alpine.Url.Mailto")); + MSWRPoke(hSubKey, TEXT("UrlAssociations"), TEXT("news"), TEXT("Alpine.Url.News")); + MSWRPoke(hSubKey, TEXT("UrlAssociations"), TEXT("nntp"), TEXT("Alpine.Url.Nntp")); + MSWRPoke(hSubKey, TEXT("UrlAssociations"), TEXT("imap"), TEXT("Alpine.Url.Imap")); + RegCloseKey(hSubKey); + } + RegCloseKey(hKey); + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\RegisteredApplications"), 0, + KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS){ + MSWRPoke(hKey, NULL, TEXT("Alpine"), MSWR_CAPABILITIES); + RegCloseKey(hKey); + } + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, + TEXT("Software\\Classes"), 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS){ + if(RegCreateKeyEx(hKey, TEXT("Alpine.Url.Mailto"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hSubKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hSubKey, MSWR_SDC_MAIL, path_lptstr); + RegCloseKey(hSubKey); + } + if(RegCreateKeyEx(hKey, TEXT("Alpine.Url.Nntp"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hSubKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hSubKey, MSWR_SDC_NNTP, path_lptstr); + RegCloseKey(hSubKey); + } + if(RegCreateKeyEx(hKey, TEXT("Alpine.Url.News"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hSubKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hSubKey, MSWR_SDC_NEWS, path_lptstr); + RegCloseKey(hSubKey); + } + if(RegCreateKeyEx(hKey, TEXT("Alpine.Url.Imap"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hSubKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hSubKey, MSWR_SDC_IMAP, path_lptstr); + RegCloseKey(hSubKey); + } + RegCloseKey(hKey); + } + } + + if((exists = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + TEXT("SOFTWARE\\Clients\\Mail\\Alpine"), + 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) + || RegCreateKeyEx(HKEY_LOCAL_MACHINE, + TEXT("SOFTWARE\\Clients\\Mail\\Alpine"), + 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hKey, &dwDisp) == ERROR_SUCCESS){ + if(update || !exists){ + DWORD dType; + char *tmp_utf8str = NULL; + + MSWRPoke(hKey, NULL, NULL, MSWR_APPNAME); + /* set up MAPI dll stuff */ + *tmp_b = 0; + RegQueryValueEx(hKey, MSWR_DLLPATH, NULL, &dType, tmp_b, &tmplen); + tmp_lptstr = (LPTSTR)tmp_b; + if(!(*tmp_lptstr) + || (can_access(tmp_utf8str = lptstr_to_utf8(tmp_lptstr), ACCESS_EXISTS) != 0)){ + if(*tmp_lptstr) + RegDeleteValue(hKey, MSWR_DLLPATH); + if(tmp_utf8str) + MemFree((void *)tmp_utf8str); + + _sntprintf(tmp_lptstr, tmp_lptstr_tcharlen, + TEXT("%s%s"), path_lptstr, MSWR_DLLNAME); + + if(can_access(tmp_utf8str = lptstr_to_utf8(tmp_lptstr), ACCESS_EXISTS) == 0) + MSWRPoke(hKey, NULL, MSWR_DLLPATH, tmp_lptstr); + } + if(tmp_utf8str) + MemFree((void *)tmp_utf8str); + /* Set "mailto" handler */ + if(RegCreateKeyEx(hKey, TEXT("Protocols\\Mailto"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hSubKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hSubKey, MSWR_SDC_MAIL, path_lptstr); + RegCloseKey(hSubKey); + } + + /* Set normal handler */ + _sntprintf(tmp_lptstr, tmp_lptstr_tcharlen, + TEXT("\"%salpine.exe\""), path_lptstr); + MSWRPoke(hKey, TEXT("shell\\open\\command"), NULL, tmp_lptstr); + } + + RegCloseKey(hKey); + } + + /* Register as a news client on this system */ + if((exists = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + TEXT("SOFTWARE\\Clients\\News\\Alpine"), + 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) + || RegCreateKeyEx(HKEY_LOCAL_MACHINE, + TEXT("SOFTWARE\\Clients\\News\\Alpine"), + 0, TEXT("REG_SZ"), REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hKey, &dwDisp) == ERROR_SUCCESS){ + if(update || !exists){ + MSWRPoke(hKey, NULL, NULL, MSWR_APPNAME); + + /* Set "news" handler */ + if(RegCreateKeyEx(hKey, TEXT("Protocols\\news"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hSubKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hSubKey, MSWR_SDC_NEWS, path_lptstr); + RegCloseKey(hSubKey); + } + /* Set "nntp" handler */ + if(RegCreateKeyEx(hKey, TEXT("Protocols\\nntp"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hSubKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hSubKey, MSWR_SDC_NNTP, path_lptstr); + RegCloseKey(hSubKey); + } + + /* Set normal handler */ + _sntprintf(tmp_lptstr, tmp_lptstr_tcharlen, + TEXT("\"%salpine.exe\""), path_lptstr); + MSWRPoke(hKey, TEXT("shell\\open\\command"), NULL, tmp_lptstr); + } + + RegCloseKey(hKey); + } + + /* Register as a IMAP url handler */ + if((exists = RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("imap"), + 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) + || RegCreateKeyEx(HKEY_CLASSES_ROOT, TEXT("imap"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hKey, &dwDisp) == ERROR_SUCCESS){ + if(update || !exists) + MSWRProtocolSet(hKey, MSWR_SDC_IMAP, path_lptstr); + + RegCloseKey(hKey); + } +} + + + +char * +mswin_reg_default_browser(char *url_utf8) +{ + TCHAR scheme[MSWR_KEY_MAX+1], *p; + LPTSTR url_lptstr; + char cmdbuf[MSWR_DATA_MAX], *cmd = NULL; + + url_lptstr = utf8_to_lptstr(url_utf8); + + if(url_lptstr && (p = _tcschr(url_lptstr, ':')) && p - url_lptstr < MSWR_KEY_MAX){ + _tcsncpy(scheme, url_lptstr, p - url_lptstr); + scheme[p-url_lptstr] = '\0'; + + if(MSWRShellCanOpen(scheme, cmdbuf, MSWR_DATA_MAX, 0)){ + size_t len; + + len = strlen(cmdbuf) + 2; + cmd = (char *) fs_get((len+1) * sizeof(char)); + if(cmd){ + if(strchr(cmdbuf, '*')) + snprintf(cmd, len+1, "\"%s\"", cmdbuf); + else{ + strncpy(cmd, cmdbuf, len); + cmd[len] = '\0'; + } + } + } + } + + MemFree((void *)url_lptstr); + + return(cmd); +} + + +int +mswin_is_def_client(int type) +{ + TCHAR buf[MSWR_KEY_MAX+1]; + DWORD buflen = MSWR_KEY_MAX; + + if(type != MSWR_SDC_MAIL && type != MSWR_SDC_NEWS) + return -1; + + if(MSWRPeek(HKEY_CURRENT_USER, + type == MSWR_SDC_MAIL ? TEXT("Software\\Clients\\Mail") + : TEXT("Software\\Clients\\News"), NULL, + buf, &buflen) && !_tcscmp(buf, TEXT("Alpine"))) + return 1; + buflen = MSWR_KEY_MAX; + if(MSWRPeek(HKEY_LOCAL_MACHINE, + type == MSWR_SDC_MAIL ? TEXT("Software\\Clients\\Mail") + : TEXT("Software\\Clients\\News"), NULL, + buf, &buflen) && !_tcscmp(buf, TEXT("Alpine"))) + return 1; + return 0; +} + +int +mswin_set_def_client(int type) +{ + HKEY hKey; + int successful_set = 0; + TCHAR path_lptstr[MSWR_DATA_MAX]; + DWORD dwDisp; + + if(type != MSWR_SDC_MAIL && type != MSWR_SDC_NEWS) + return 1; + if(RegOpenKeyEx(HKEY_CURRENT_USER, + type == MSWR_SDC_MAIL ? TEXT("Software\\Clients\\Mail") + : TEXT("Software\\Clients\\News"), + 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS){ + successful_set = MSWRPoke(hKey, NULL, NULL, TEXT("Alpine")); + RegCloseKey(hKey); + } + else if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, + type == MSWR_SDC_MAIL ? TEXT("Software\\Clients\\Mail") + : TEXT("Software\\Clients\\News"), + 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS){ + successful_set = MSWRPoke(hKey, NULL, NULL, TEXT("Alpine")); + RegCloseKey(hKey); + } + if(successful_set){ + mswin_reg_lptstr(MSWR_OP_GET, MSWR_PINE_DIR, path_lptstr, sizeof(path_lptstr)/sizeof(TCHAR)); + if(type == MSWR_SDC_MAIL){ + MSWRClear(HKEY_CLASSES_ROOT, TEXT("mailto")); + if(RegCreateKeyEx(HKEY_CLASSES_ROOT, TEXT("mailto"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hKey, MSWR_SDC_MAIL, path_lptstr); + RegCloseKey(hKey); + } + } + else if(type == MSWR_SDC_NEWS){ + MSWRClear(HKEY_CLASSES_ROOT, TEXT("news")); + if(RegCreateKeyEx(HKEY_CLASSES_ROOT, TEXT("news"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hKey, MSWR_SDC_NEWS, path_lptstr); + RegCloseKey(hKey); + } + MSWRClear(HKEY_CLASSES_ROOT, TEXT("nntp")); + if(RegCreateKeyEx(HKEY_CLASSES_ROOT, TEXT("nntp"), 0, TEXT("REG_SZ"), + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hKey, &dwDisp) == ERROR_SUCCESS){ + MSWRProtocolSet(hKey, MSWR_SDC_NNTP, path_lptstr); + RegCloseKey(hKey); + } + } + } + return 0; +} + +int +MSWRProtocolSet(HKEY hKey, int type, LPTSTR path_lptstr) +{ + TCHAR tmp_lptstr[MSWR_DATA_MAX]; + BYTE EditFlags[4]; + unsigned long tmp_lptstr_len = MSWR_DATA_MAX; + + if(type != MSWR_SDC_MAIL && type != MSWR_SDC_NEWS + && type != MSWR_SDC_NNTP && type != MSWR_SDC_IMAP) + return -1; + MSWRPoke(hKey, NULL, NULL, type == MSWR_SDC_MAIL + ? TEXT("URL:MailTo Protocol") + : type == MSWR_SDC_NEWS ? TEXT("URL:News Protocol") + : type == MSWR_SDC_NNTP ? TEXT("URL:NNTP Protocol") + : TEXT("URL:IMAP Prototcol")); + MSWRPoke(hKey, NULL, TEXT("URL Protocol"), TEXT("")); + + EditFlags[0] = 0x02; + EditFlags[1] = EditFlags[2] = EditFlags[3] = 0; + + (void) RegDeleteValue(hKey, TEXT("EditFlags")); + (void) RegSetValueEx(hKey, TEXT("EditFlags"), 0, REG_BINARY, + EditFlags, (DWORD) 4); + + _sntprintf(tmp_lptstr, tmp_lptstr_len, + TEXT("%salpine.exe,0"), path_lptstr); + MSWRPoke(hKey, TEXT("DefaultIcon"), NULL, tmp_lptstr); + + _sntprintf(tmp_lptstr, tmp_lptstr_len, + TEXT("\"%salpine.exe\" -url \"%%1\""), path_lptstr); + MSWRPoke(hKey, TEXT("shell\\open\\command"), NULL, tmp_lptstr); + return 0; +} + + +/* cmdbuf can stay char * since it's our string */ +BOOL +MSWRShellCanOpen(LPTSTR key, char *cmdbuf, int clen, int allow_noreg) +{ + HKEY hKey; + BOOL rv = FALSE; + + /* See if Shell provides a method to open the thing... */ + if(RegOpenKeyEx(HKEY_CLASSES_ROOT, key, + 0, KEY_READ, &hKey) == ERROR_SUCCESS){ + + if(cmdbuf){ + strncpy(cmdbuf, "*Shell*", clen); + cmdbuf[clen-1] = '\0'; + } + + rv = TRUE; + + RegCloseKey(hKey); + } + else if(allow_noreg && cmdbuf){ + strncpy(cmdbuf, "*Shell*", clen); + cmdbuf[clen-1] = '\0'; + rv = TRUE; + } + + return(rv); +} + + +/* + * Fundamental registry access function that queries for particular values. + */ +LOCAL BOOL +MSWRPeek(HKEY hRootKey, LPTSTR subkey, LPTSTR valstr, LPTSTR data_lptstr, DWORD *dlen) +{ + HKEY hKey; + DWORD dtype, dlen_bytes = (dlen ? *dlen : 0) * sizeof(TCHAR); + LONG rv = !ERROR_SUCCESS; + + if(RegOpenKeyEx(hRootKey, subkey, 0, KEY_READ, &hKey) == ERROR_SUCCESS){ + rv = RegQueryValueEx(hKey, valstr, NULL, &dtype, (LPBYTE)data_lptstr, &dlen_bytes); + if(dlen) + (*dlen) = dlen_bytes; + RegCloseKey(hKey); + } + + return(rv == ERROR_SUCCESS); +} + + +/* + * Fundamental registry access function that sets particular values. + */ +LOCAL BOOL +MSWRPoke(HKEY hKey, LPTSTR subkey, LPTSTR valstr, LPTSTR data_lptstr) +{ + DWORD dtype, dwDisp, dlen = MSWR_DATA_MAX; + BYTE olddata[MSWR_DATA_MAX]; + BOOL rv = FALSE; + + if(!subkey + || RegCreateKeyEx(hKey, subkey, + 0, TEXT("REG_SZ"), REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, NULL, + &hKey, &dwDisp) == ERROR_SUCCESS){ + + if(RegQueryValueEx(hKey, valstr, NULL, &dtype, + olddata, &dlen) != ERROR_SUCCESS + || _tcscmp((LPTSTR)olddata, data_lptstr)){ + (void) RegDeleteValue(hKey, valstr); + rv = RegSetValueEx(hKey, valstr, 0, REG_SZ, + (LPBYTE)data_lptstr, + (DWORD)(_tcslen(data_lptstr) + 1)*sizeof(TCHAR)) == ERROR_SUCCESS; + } + + if(subkey) + RegCloseKey(hKey); + } + + return(rv); +} + + +LOCAL void +MSWRLineBufAdd(MSWR_LINE_BUFFER_S *lpLineBuf, LPTSTR line) +{ + if(lpLineBuf->offset >= lpLineBuf->size){ + /* this probably won't happen, but just in case */ + lpLineBuf->size *= 2; + lpLineBuf->linep = (char **)MemRealloc(lpLineBuf->linep, + (lpLineBuf->size + 1)*sizeof(char *)); + } + + lpLineBuf->linep[lpLineBuf->offset++] = lptstr_to_utf8(line); + lpLineBuf->linep[lpLineBuf->offset] = NULL; +} + +/* + * Dump all of the registry values from a list of keys into an array + * of UTF8-formatted strings. + */ +char ** +mswin_reg_dump(void) +{ + MSWR_LINE_BUFFER_S lineBuf; + unsigned long initial_size = 256; + int i, j; + + lineBuf.linep = (char **)MemAlloc((initial_size+1)*sizeof(char *)); + lineBuf.size = initial_size; + lineBuf.offset = 0; + + MSWRLineBufAdd(&lineBuf, TEXT("Registry values for Alpine:")); + MSWRLineBufAdd(&lineBuf, TEXT("")); + + for(i = 0; mswin_pine_regs[i].rhk; i++){ + MSWRLineBufAdd(&lineBuf, mswin_pine_regs[i].rhk == HKEY_LOCAL_MACHINE + ? TEXT("HKEY_LOCAL_MACHINE") + : TEXT("HKEY_CURRENT_USER")); + for(j = 0; mswin_pine_regs[i].knames[j]; j++) + MSWRDump(mswin_pine_regs[i].rhk, + mswin_pine_regs[i].knames[j], + 1, &lineBuf); + } + + return(lineBuf.linep); +} + + +/* + * Recursive function to crawl a registry hierarchy and print the contents. + * + * Returns: 0 + */ +LOCAL int +MSWRDump(HKEY hKey, LPTSTR pSubKey, int keyDepth, MSWR_LINE_BUFFER_S *lpLineBuf) +{ + HKEY hSubKey; + TCHAR KeyBuf[MSWR_KEY_MAX+1]; + TCHAR ValBuf[MSWR_VAL_MAX+1]; + BYTE DataBuf[MSWR_DATA_MAX+1]; + DWORD dwKeyIndex, dwKeyLen; + DWORD dwValIndex, dwValLen, dwDataLen; + DWORD dwType; + FILETIME ftKeyTime; + TCHAR new_buf[1024]; + unsigned int new_buf_len = 1024; + int i, j, k, tab_width = 4; + + /* open the passed subkey */ + if(RegOpenKeyEx(hKey, pSubKey, 0, + KEY_READ, &hSubKey) == ERROR_SUCCESS){ + + /* print out key name here */ + for(i = 0, k = 0; i < keyDepth % 8; i++) + for(j = 0; j < tab_width; j++) + new_buf[k++] = ' '; + _sntprintf(new_buf+k, new_buf_len - k, TEXT("%s"), pSubKey); + new_buf[new_buf_len - 1] = '\0'; + MSWRLineBufAdd(lpLineBuf, new_buf); + + keyDepth++; + + /* Loop through the string values and print their data */ + for(dwValIndex = 0L, dwValLen = MSWR_VAL_MAX + 1, dwDataLen = MSWR_DATA_MAX + 1; + RegEnumValue(hSubKey, dwValIndex, ValBuf, &dwValLen, NULL, &dwType, + DataBuf, &dwDataLen) == ERROR_SUCCESS; + dwValIndex++, dwValLen = MSWR_VAL_MAX + 1, dwDataLen = MSWR_DATA_MAX + 1){ + + /* print out value here */ + for(i = 0, k = 0; i < keyDepth % 8; i++) + for(j = 0; j < tab_width; j++) + new_buf[k++] = ' '; + _sntprintf(new_buf+k, new_buf_len - k, + TEXT("%.*s = %.*s"), + dwValLen ? dwValLen : 128, + dwValLen ? ValBuf : TEXT("(Default)"), + dwType == REG_SZ && dwDataLen ? dwDataLen/sizeof(TCHAR) : 128, + (dwType == REG_SZ + ? (dwDataLen ? (LPTSTR)DataBuf : TEXT("(No data)")) + : TEXT("(Some non-string data)"))); + new_buf[new_buf_len - 1] = '\0'; + MSWRLineBufAdd(lpLineBuf, new_buf); + } + + /* Loop through the subkeys and recursively print their data */ + for(dwKeyIndex = 0L, dwKeyLen = MSWR_KEY_MAX + 1; + RegEnumKeyEx(hSubKey, dwKeyIndex, KeyBuf, &dwKeyLen, + NULL, NULL, NULL, &ftKeyTime) == ERROR_SUCCESS; + dwKeyIndex++, dwKeyLen = MSWR_KEY_MAX + 1){ + MSWRDump(hSubKey, KeyBuf, keyDepth, lpLineBuf); + } + } + else { + /* Couldn't open the key. Must not be defined. */ + for(i = 0, k = 0; i < keyDepth % 8; i++) + for(j = 0; j < tab_width; j++) + new_buf[k++] = ' '; + _sntprintf(new_buf+k, new_buf_len - k, TEXT("%s - Not Defined"), pSubKey); + new_buf[new_buf_len - 1] = '\0'; + MSWRLineBufAdd(lpLineBuf, new_buf); + } + + return 0; +} + + +/* + * Fundamental registry access function that removes a registry key + * and all its subkeys, their values and data + */ +LOCAL int +MSWRClear(HKEY hKey, LPTSTR pSubKey) +{ + HKEY hSubKey; + TCHAR KeyBuf[MSWR_KEY_MAX+1]; + DWORD dwKeyIndex, dwKeyLen; + FILETIME ftKeyTime; + int rv = 0; + + if(RegOpenKeyEx(hKey, pSubKey, 0, + KEY_READ, &hSubKey) == ERROR_SUCCESS){ + RegCloseKey(hSubKey); + if(RegOpenKeyEx(hKey, pSubKey, 0, + KEY_ALL_ACCESS, &hSubKey) == ERROR_SUCCESS){ + for(dwKeyIndex = 0L, dwKeyLen = MSWR_KEY_MAX + 1; + RegEnumKeyEx(hSubKey, dwKeyIndex, KeyBuf, &dwKeyLen, + NULL, NULL, NULL, &ftKeyTime) == ERROR_SUCCESS; + dwKeyLen = MSWR_KEY_MAX + 1) + if(MSWRClear(hSubKey, KeyBuf)!= 0){ + rv = -1; + dwKeyIndex++; + } + RegCloseKey(hSubKey); + if(RegDeleteKey(hKey, pSubKey) != ERROR_SUCCESS || rv) + return -1; + return 0; + } + return -1; + } + return 0; +} + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Text display Windows. + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/* + * Show a help message. + * Help text comes as a null terminated array of pointers to lines of + * text. Stuff these into a buffer and pass that to MessageBox. + */ +void +mswin_showhelpmsg(WINHAND wnd, char **helplines) +{ + char **l; + char *helptext_utf8, *p; + size_t buflen; + HWND hWnd; + LPTSTR helptext_lpt; + + hWnd = (HWND) wnd; + if(hWnd == NULL) + hWnd = ghTTYWnd; + + buflen = 0; + for(l = helplines; *l != NULL; ++l) + buflen += (strlen(*l)+1); + + helptext_utf8 = (char *) fs_get((buflen + 1) * sizeof(char)); + if(helptext_utf8 == NULL) + return; + + *helptext_utf8 = '\0'; + + p = helptext_utf8; + for(l = helplines; *l != NULL; ++l){ + snprintf(p, buflen+1-(p-helptext_utf8), "%s%s", (p == helptext_utf8) ? "" : " ", *l); + p += strlen(p); + } + + helptext_lpt = utf8_to_lptstr(helptext_utf8); + + MessageBox(hWnd, helptext_lpt, TEXT("Help"), + MB_APPLMODAL | MB_ICONINFORMATION | MB_OK); + + fs_give((void **) &helptext_utf8); + fs_give((void **) &helptext_lpt); +} + +/* + * Callback for when new mail or imap telem window gets canned. + */ +LOCAL void +mswin_tw_close_imap_telem_or_new_mail(MSWIN_TEXTWINDOW *mswin_tw) +{ + HMENU hMenu; + + if((mswin_tw->id == IDM_OPT_IMAPTELEM) && gIMAPDebugOFFCallback) + (*gIMAPDebugOFFCallback)(); + + if(hMenu = GetMenu(ghTTYWnd)) + CheckMenuItem (hMenu, mswin_tw->id, MF_BYCOMMAND | MF_UNCHECKED); +} + +/* + * Callback for when new mail or imap telem window gets cleared. + */ +LOCAL void +mswin_tw_clear_imap_telem_or_new_mail(MSWIN_TEXTWINDOW *mswin_tw) +{ + char *tmtxt; + time_t now = time((time_t *)0); + LPCTSTR desc = (mswin_tw->id == IDM_OPT_IMAPTELEM) ? + TEXT("IMAP telemetry recorder") : + TEXT("New Mail window"); + + tmtxt = ctime(&now); + + mswin_tw_printf(mswin_tw, TEXT("%s started at %.*S"), + desc, MIN(100, strlen(tmtxt)-1), tmtxt); + + if(mswin_tw->id == IDM_OPT_NEWMAILWIN) + { + int i; + int fromlen, subjlen, foldlen, len; + TCHAR ring2[256]; + + foldlen = (int)(.18 * gNMW_width); + foldlen = foldlen > 5 ? foldlen : 5; + fromlen = (int)(.28 * gNMW_width); + subjlen = gNMW_width - 2 - foldlen - fromlen; + + mswin_tw_printf(mswin_tw, + TEXT(" %-*s%-*s%-*s"), + fromlen, TEXT("From:"), + subjlen, TEXT("Subject:"), + foldlen, TEXT("Folder:")); + + len = 2 + fromlen + subjlen + foldlen; + if(len >= ARRAYSIZE(ring2) + 1) + len = ARRAYSIZE(ring2) - 2; + for(i = 0; i < len; i++) + ring2[i] = '-'; + ring2[i] = '\0'; + mswin_tw_puts_lptstr(mswin_tw, ring2); + } +} + +/* + * Init new mail or imap telem window + */ +LOCAL int +mswin_tw_init(MSWIN_TEXTWINDOW *mswin_tw, int id, LPCTSTR title) +{ + if(mswin_tw->hwnd){ + /* destroy it */ + mswin_tw_close(mswin_tw); + } + else{ + HMENU hMenu; + + mswin_tw->id = id; + mswin_tw->hInstance = ghInstance; + mswin_tw->print_callback = mswin_tw_print_callback; + mswin_tw->close_callback = mswin_tw_close_imap_telem_or_new_mail; + mswin_tw->clear_callback = mswin_tw_clear_imap_telem_or_new_mail; + + // If the rcSize rect is empty, then init it to something resembling + // the size of the current Pine window. Otherwise we just re-use + // whatever the last position/size was. + if(IsRectEmpty(&mswin_tw->rcSize)) + { + RECT cliRect; + RECT sizeRect; + + GetClientRect(ghTTYWnd, &cliRect); + sizeRect.left = CW_USEDEFAULT; + sizeRect.top = CW_USEDEFAULT; + sizeRect.right = cliRect.right; + sizeRect.bottom = cliRect.bottom; + mswin_tw->rcSize = sizeRect; + } + + if(!mswin_tw_create(mswin_tw, title)) + return 1; + + mswin_tw_setfont(mswin_tw, gpTTYInfo->hTTYFont); + mswin_tw_setcolor(mswin_tw, gpTTYInfo->rgbFGColor, gpTTYInfo->rgbBGColor); + + mswin_tw_clear(mswin_tw); + + if(id == IDM_OPT_IMAPTELEM) + { + if(gIMAPDebugONCallback) + (*gIMAPDebugONCallback)(); + + mswin_tw_showwindow(mswin_tw, SW_SHOWNA); + } + else if(id == IDM_OPT_NEWMAILWIN){ + mswin_tw_showwindow(mswin_tw, SW_SHOW); + } + + if(hMenu = GetMenu(ghTTYWnd)) + CheckMenuItem (hMenu, mswin_tw->id, MF_BYCOMMAND | MF_CHECKED); + } + + return(0); +} + +/* + * Display text in a window. + * + * Parameters: + * title - Title of window. + * pText - address of text to display. + * textLen - Length of text, in bytes. Limited to 64K. + * pLines - Array of pointers to lines of text. Each + * line is a sepreate allocation block. The + * entry in the array of pointers should be a + * NULL. + * + * The text can be supplied as a buffer (pText and textLen) in which + * lines are terminated by CRLF (including the last line in buffer). + * This buffer should be NULL terminated, too. + * Or it can be supplied as a NULL terminated array of pointers to + * lines. Each entry points to a separately allocated memory block + * containing a null terminated string. + * + * If the function succeeds the memory containing the text will be + * used until the user closes the window, at which point it will be + * freed. + * + * Returns: + * mswin_tw - SUCCESS + * NULL - Failed. + */ +MSWIN_TEXTWINDOW * +mswin_displaytext(char *title_utf8, char *pText_utf8, size_t npText, + char **pLines_utf8, MSWIN_TEXTWINDOW *mswin_tw, int flags) +{ + LPTSTR title_lpt = NULL, pText_lpt = NULL, *pLines_lpt = NULL; + char **l; + size_t pText_lpt_len = 0; + int i, count = 0; + + if(pLines_utf8 != NULL){ + for(count=0, l = pLines_utf8; *l != NULL; ++l) + ++count; + + pLines_lpt = (LPTSTR *) fs_get((count + 1) * sizeof(LPTSTR)); + memset(pLines_lpt, 0, (count + 1) * sizeof(LPTSTR)); + for(i=0, l = pLines_utf8; *l != NULL && i < count; ++l, ++i) + pLines_lpt[i] = utf8_to_lptstr(*l); + + /*caller expects this to be freed */ + if(!(flags & MSWIN_DT_NODELETE)){ + for(l = pLines_utf8; *l != NULL; ++l) + fs_give((void **) l); + + fs_give((void **) &pLines_utf8); + } + } + + if(pText_utf8 != NULL && npText > 0){ + pText_lpt = utf8_to_lptstr(pText_utf8); + pText_lpt_len = lstrlen(pText_lpt); + + /*caller expects this to be freed */ + if(!(flags & MSWIN_DT_NODELETE)) + fs_give((void **) &pText_utf8); + } + + if(title_utf8 != NULL) + title_lpt = utf8_to_lptstr(title_utf8); + + mswin_tw = mswin_tw_displaytext_lptstr(title_lpt, pText_lpt, pText_lpt_len, + pLines_lpt, mswin_tw, flags); + + if(pLines_lpt != NULL){ + for(i=0; i < count; ++i) + if(pLines_lpt[i]) + fs_give((void **) &pLines_lpt[i]); + + fs_give((void **) &pLines_lpt); + } + + if(pText_lpt != NULL) + fs_give((void **) &pText_lpt); + + if(title_lpt != NULL) + fs_give((void **) &title_lpt); + + return(mswin_tw); +} + +/* + * Callback for when a generic mswin_tw gets killed. + */ +LOCAL void +mswin_tw_close_callback(MSWIN_TEXTWINDOW *mswin_tw) +{ + if(mswin_tw->id != -1) + MemFree(mswin_tw); +} + +/* + * Create a new mswin_tw window. If the MSWIN_DT_USEALTWINDOW flag is set, + * then (re)use gMswinAltWin. + */ +LOCAL MSWIN_TEXTWINDOW * +mswin_tw_displaytext_lptstr (LPTSTR title, LPTSTR pText, size_t textLen, LPTSTR *pLines, + MSWIN_TEXTWINDOW *mswin_tw, int flags) +{ + if (pText == NULL && pLines == NULL) + return (NULL); + + /* Was a valid existing window supplied? */ + if(!mswin_tw) + { + int ctrl_down = GetKeyState(VK_CONTROL) < 0; + + if((flags & MSWIN_DT_USEALTWINDOW) && !ctrl_down) + { + mswin_tw = &gMswinAltWin; + mswin_tw->id = (UINT)-1; // Tell mswin_tw_close_callback not + // to free this buffer. + } + else + { + mswin_tw = (MSWIN_TEXTWINDOW *)MemAlloc (sizeof (MSWIN_TEXTWINDOW)); + if(!mswin_tw) + return NULL; + + memset(mswin_tw, 0, sizeof(MSWIN_TEXTWINDOW)); + mswin_tw->id = 0; + } + + mswin_tw->hInstance = ghInstance; + mswin_tw->print_callback = mswin_tw_print_callback; + mswin_tw->close_callback = mswin_tw_close_callback; + mswin_tw->clear_callback = NULL; + } + + /* Create a new window. */ + if (!mswin_tw->hwnd) { + + if(IsRectEmpty(&mswin_tw->rcSize)) + { + RECT sizeRect; + RECT cliRect; + + GetClientRect(ghTTYWnd, &cliRect); + sizeRect.left = CW_USEDEFAULT; + sizeRect.top = CW_USEDEFAULT; + sizeRect.right = cliRect.right; + sizeRect.bottom = cliRect.bottom; + mswin_tw->rcSize = sizeRect; + } + + if(!mswin_tw_create(mswin_tw, title)) { + MemFree (mswin_tw); + return (NULL); + } + + mswin_tw_setfont(mswin_tw, gpTTYInfo->hTTYFont); + mswin_tw_setcolor(mswin_tw, gpTTYInfo->rgbFGColor, gpTTYInfo->rgbBGColor); + } + else { + /* Invalidate whole window, change title, and move to top. */ + SetWindowText (mswin_tw->hwnd, title); + SetWindowPos (mswin_tw->hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } + + mswin_tw_clear(mswin_tw); + + /* + * How was text supplied? + */ + if (pLines != NULL) { + LPTSTR *l; + + /* Array of pointers to lines supplied. Count lines. */ + for (l = pLines; *l != NULL; ++l){ + mswin_tw_puts_lptstr(mswin_tw, *l); + } + } + else { +#ifdef ALTED_DOT + if(flags & MSWIN_DT_FILLFROMFILE) + mswin_tw_fill_from_file(mswin_tw, pText); + else +#endif /* ALTED_DOT */ + /* Pointer to block of text supplied. */ + mswin_tw_puts_lptstr(mswin_tw, pText); + } + + mswin_tw_setsel(mswin_tw, 0, 0); + + mswin_tw_showwindow(mswin_tw, SW_SHOW); + return mswin_tw; +} + +void +mswin_enableimaptelemetry(int state) +{ + HMENU hMenu; + MENUITEMINFO mitem; + TCHAR buf[256]; + int i; + + hMenu = GetMenu (ghTTYWnd); + if (hMenu == NULL) + return; + + /* + * Make sure hMenu's the right menubar + */ + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = (MIIM_SUBMENU | MIIM_TYPE); + mitem.fType = MFT_STRING; + mitem.dwTypeData = buf; + mitem.cch = 255; + if(GetMenuItemCount(hMenu) == 7 + && GetMenuItemInfo(hMenu, 5, TRUE, &mitem)){ + if(mitem.fType == MFT_STRING + && !_tcscmp(mitem.dwTypeData, TEXT("&Config"))) + hMenu = mitem.hSubMenu; + else + return; + } + else + return; + + i = GetMenuItemCount(hMenu); + if(state == TRUE && i < 10){ + mitem.fMask = MIIM_TYPE; + mitem.fType = MFT_SEPARATOR; + InsertMenuItem (hMenu, 8, TRUE, &mitem); + + mitem.fMask = (MIIM_TYPE | MIIM_ID); + mitem.fType = MFT_STRING; + mitem.wID = IDM_OPT_IMAPTELEM; + mitem.dwTypeData = TEXT("&IMAP Telemetry"); + mitem.cch = 15; + InsertMenuItem (hMenu, 9, TRUE, &mitem); + + DrawMenuBar (ghTTYWnd); + } + else if(state == FALSE && i == 10){ + DeleteMenu (hMenu, 8, MF_BYPOSITION); + DeleteMenu (hMenu, IDM_OPT_IMAPTELEM, MF_BYCOMMAND); + DrawMenuBar (ghTTYWnd); + } +} + +int +mswin_imaptelemetry(char *msg) +{ + if(gMswinIMAPTelem.hwnd){ + LPTSTR msg_lptstr; + msg_lptstr = utf8_to_lptstr(msg); + mswin_tw_puts_lptstr(&gMswinIMAPTelem, msg_lptstr); + fs_give((void **) &msg_lptstr); + return(1); + } + return(0); +} + + +/* + * The newmail window has a format proportional to screen width. + * At this point, we've figured out the sizes of the fields, now + * we fill that field to it's desired column size. This used to + * be a lot eaier when it was one char per column, but in the + * unicode world it's not that easy. This code does make the + * assumption that ASCII characters are 1 column. + */ +LPTSTR +format_newmail_string(LPTSTR orig_lptstr, int format_len) +{ + LPTSTR new_lptstr; + int i, colLen; + + new_lptstr = (LPTSTR)MemAlloc((format_len+1)*sizeof(TCHAR)); + + /* + * Fill up string till we reach the format_len, we can backtrack + * if we need elipses. + */ + for(i = 0, colLen = 0; + i < format_len && colLen < format_len && orig_lptstr && orig_lptstr[i]; + i++){ + new_lptstr[i] = orig_lptstr[i]; + colLen += wcellwidth(new_lptstr[i]); + } + + if(colLen > format_len || (colLen == format_len && orig_lptstr[i])){ + /* + * If we hit the edge of the format and there's still stuff + * to write, go back and add ".." + */ + i--; + if(wcellwidth(new_lptstr[i]) > 1){ + colLen -= wcellwidth(new_lptstr[i]); + } + else{ + colLen -= wcellwidth(new_lptstr[i]); + i--; + colLen -= wcellwidth(new_lptstr[i]); + } + while(colLen < format_len && i < format_len){ + new_lptstr[i++] = '.'; + colLen++; + } + } + else{ + /* + * If we've hit the end of the string, add spaces until + * we get to the correct length. + */ + for(; colLen < format_len && i < format_len; i++, colLen++){ + new_lptstr[i] = ' '; + } + } + + if(i <= format_len) + new_lptstr[i] = '\0'; + else + new_lptstr[format_len] = '\0'; + + return(new_lptstr); +} + +/* + * We're passed the relevant fields, now format them according to window with + * and put up for display + */ +int +mswin_newmailwin(int is_us, char *from_utf8, char *subject_utf8, char *folder_utf8) +{ + TCHAR tcbuf[256]; + int foldlen, fromlen, subjlen; + LPTSTR from_lptstr = NULL, subject_lptstr = NULL, folder_lptstr = NULL; + LPTSTR from_format, subject_format, folder_format; + + if(!gMswinNewMailWin.hwnd) + return 0; + + if(from_utf8) + from_lptstr = utf8_to_lptstr(from_utf8); + if(subject_utf8) + subject_lptstr = utf8_to_lptstr(subject_utf8); + if(folder_utf8) + folder_lptstr = utf8_to_lptstr(folder_utf8); + + + foldlen = (int)(.18 * gNMW_width); + foldlen = foldlen > 5 ? foldlen : 5; + fromlen = (int)(.28 * gNMW_width); + subjlen = gNMW_width - 2 - foldlen - fromlen; + + + from_format = format_newmail_string(from_lptstr + ? from_lptstr : TEXT(""), + fromlen - 1); + subject_format = format_newmail_string(subject_lptstr + ? subject_lptstr : TEXT("(no subject)"), + subjlen - 1); + folder_format = format_newmail_string(folder_lptstr + ? folder_lptstr : TEXT("INBOX"), + foldlen); + + _sntprintf(tcbuf, 256, TEXT("%c %s %s %s"), is_us ? '+' : ' ', + from_format, subject_format, folder_format); + + if(from_lptstr) + fs_give((void **) &from_lptstr); + if(subject_lptstr) + fs_give((void **) &subject_lptstr); + if(folder_lptstr) + fs_give((void **) &folder_lptstr); + MemFree((void *)from_format); + MemFree((void *)subject_format); + MemFree((void *)folder_format); + + mswin_tw_puts_lptstr(&gMswinNewMailWin, tcbuf); + return 1; +} + +/* + * Mouse up, end selection + */ + +LOCAL void +mswin_tw_print_callback(MSWIN_TEXTWINDOW *mswin_tw) +{ + LPTSTR text; + UINT text_len; + int rc; +#define DESC_LEN 180 + TCHAR description[DESC_LEN+1]; + + GetWindowText(mswin_tw->hwnd, description, DESC_LEN); + + rc = mswin_print_ready((WINHAND)mswin_tw->hwnd, description); + if (rc != 0) { + if (rc != PE_USER_CANCEL) { + LPTSTR e; + + e = utf8_to_lptstr(mswin_print_error(rc)); + if(e){ + _sntprintf(description, DESC_LEN+1, TEXT("Printing failed: %s"), e); + fs_give((void **) &e); + } + + MessageBox(mswin_tw->hwnd, description, TEXT("Print Failed"), + MB_OK | MB_ICONEXCLAMATION); + } + return; + } + + text_len = mswin_tw_gettextlength(mswin_tw); + text = (LPTSTR) fs_get(text_len * sizeof(TCHAR)); + mswin_tw_gettext(mswin_tw, text, text_len); + mswin_print_text(text); + fs_give((void **)&text); + + mswin_print_done(); +} + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Character Queue + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + + +typedef struct { + BOOL fKeyControlDown; + UCS c; /* Bigger than TCHAR for CTRL and MENU setting */ +} CQEntry; + +LOCAL CQEntry CQBuffer [CHARACTER_QUEUE_LENGTH]; +LOCAL int CQHead; +LOCAL int CQTail; +LOCAL int CQCount; + + +/*--------------------------------------------------------------------------- + * BOOL CQInit () + * + * Description: + * Initialize the Character queue. + * + * Parameters: + * + * +/*--------------------------------------------------------------------------*/ +LOCAL void +CQInit (void) +{ + CQHead = 0; + CQTail = 0; + CQCount = 0; +} + + +/*--------------------------------------------------------------------------- + * BOOL CQAvailable (void) + * + * Description: + * Return TRUE if there are characters in the queue. + * + * Parameters: + * + * +/*--------------------------------------------------------------------------*/ + +LOCAL BOOL +CQAvailable (void) +{ + return (CQCount > 0); +} + + + +/*--------------------------------------------------------------------------- + * BOOL CQAdd (WORD c, DWORC keyData) + * + * Description: + * Add 'c' to the end of the character queue. + * + * Parameters: + * return true if successfull. + * +/*--------------------------------------------------------------------------*/ + +LOCAL BOOL +CQAdd (UCS c, BOOL fKeyControlDown) +{ + if (CQCount == CHARACTER_QUEUE_LENGTH) + return (FALSE); + + CQBuffer[CQTail].fKeyControlDown = fKeyControlDown; + CQBuffer[CQTail].c = c; + CQTail = (CQTail + 1) % CHARACTER_QUEUE_LENGTH; + ++CQCount; + return (TRUE); +} + + + +/*--------------------------------------------------------------------------- + * BOOL CQAddUniq (WORD c, DWORC keyData) + * + * Description: + * Add 'c' to the end of the character queue, only if + * there is no other 'c' in the queue + * + * Parameters: + * return true if successfull. + * +/*--------------------------------------------------------------------------*/ + +LOCAL BOOL +CQAddUniq (UCS c, BOOL fKeyControlDown) +{ + int i; + int pos; + + if (CQCount == CHARACTER_QUEUE_LENGTH) + return (FALSE); + + pos = CQHead; + for (i = 0; i < CQCount; ++i) { + if (CQBuffer[pos].c == c) + return (FALSE); + pos = (pos + 1) % CHARACTER_QUEUE_LENGTH; + } + return (CQAdd (c, fKeyControlDown)); +} + + + + +/*--------------------------------------------------------------------------- + * int CQGet () + * + * Description: + * Return the next byte from the head of the queue. If there is + * no byte available, returns 0, which is indistinquishable from + * '\0'. So it is a good idea to call CQAvailable first. + * + * Parameters: + * none. + * +/*--------------------------------------------------------------------------*/ + +LOCAL UCS +CQGet () +{ + UCS c; + + if (CQCount == 0) + return (0); + + c = CQBuffer[CQHead].c; + + if(CQBuffer[CQHead].fKeyControlDown) + c |= CTRL; + + if(c < ' ') { + c += '@'; + c |= CTRL; + } + + CQHead = (CQHead + 1) % CHARACTER_QUEUE_LENGTH; + --CQCount; + return (c); +} + + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * + * Mouse Event Queue + * + *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + + + +LOCAL MEvent MQBuffer [MOUSE_QUEUE_LENGTH]; +LOCAL int MQHead; +LOCAL int MQTail; +LOCAL int MQCount; + + +/*--------------------------------------------------------------------------- + * BOOL MQInit () + * + * Description: + * Initialize the Character queue. + * + * Parameters: + * + * +/*--------------------------------------------------------------------------*/ +LOCAL void +MQInit (void) +{ + MQHead = 0; + MQTail = 0; + MQCount = 0; +} + + +/*--------------------------------------------------------------------------- + * BOOL MQAvailable (void) + * + * Description: + * Return TRUE if there are characters in the queue. + * + * Parameters: + * + * +/*--------------------------------------------------------------------------*/ + +LOCAL BOOL +MQAvailable (void) +{ + return (MQCount > 0); +} + + + +/*--------------------------------------------------------------------------- + * BOOL MQAdd () + * + * Description: + * Add 'c' to the end of the character queue. + * + * Parameters: + * return true if successfull. + * +/*--------------------------------------------------------------------------*/ + +LOCAL BOOL +MQAdd (int mevent, int button, int nRow, int nColumn, int keys, int flags) +{ + int c; + int i = 0; + BOOL found = FALSE; + + + /* + * Find a queue insertion point. + */ + if (flags & MSWIN_MF_REPLACING) { + /* Search for same event on queue. */ + for ( i = MQHead, c = MQCount; + c > 0; + i = (i + 1) % MOUSE_QUEUE_LENGTH, --c) { + if (MQBuffer[i].event == mevent) { + found = TRUE; + break; + } + } + } + if (!found) { + if (MQCount == MOUSE_QUEUE_LENGTH) + return (FALSE); + i = MQTail; + MQTail = (MQTail + 1) % MOUSE_QUEUE_LENGTH; + ++MQCount; + } + + + /* + * Record data. + */ + MQBuffer[i].event = mevent; + MQBuffer[i].button = button; + MQBuffer[i].nRow = nRow; + MQBuffer[i].nColumn = nColumn; + MQBuffer[i].keys = keys; + MQBuffer[i].flags = flags; + + /* + * Keep record of last mouse position. + */ + gMTEvent = MQBuffer[i]; + + return (TRUE); +} + + + + + +/*--------------------------------------------------------------------------- + * BOOL MQGet () + * + * Description: + * Return the next byte from the head of the queue. If there is + * no byte available, returns 0, which is indistinquishable from + * '\0'. So it is a good idea to call MQAvailable first. + * + * Parameters: + * none. + * +/*--------------------------------------------------------------------------*/ + +LOCAL BOOL +MQGet (MEvent * pMouse) +{ + if (MQCount == 0) + return (FALSE); + + + *pMouse = MQBuffer[MQHead]; + MQHead = (MQHead + 1) % MOUSE_QUEUE_LENGTH; + --MQCount; + return (TRUE); +} + + + + + + +DWORD +ExplainSystemErr() +{ + DWORD status; + void *lpMsgBuf = NULL; + status = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + status, + MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, 0, NULL); + + if(lpMsgBuf){ + char *msg; + + msg = lptstr_to_utf8(lpMsgBuf); + if(msg){ + mswin_messagebox(msg, 1); + fs_give((void **) &msg); + } + + LocalFree(lpMsgBuf); + } + + return(status); +} + + +/* + * Called by mswin to scroll text in window in responce to the scrollbar. + * + * 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 +pico_scroll_callback (int cmd, long scroll_pos) +{ + switch (cmd) { + case MSWIN_KEY_SCROLLUPLINE: + scrollupline (0, 1); + break; + + case MSWIN_KEY_SCROLLDOWNLINE: + scrolldownline (0, 1); + break; + + case MSWIN_KEY_SCROLLUPPAGE: + backpage (0, 1); + break; + + case MSWIN_KEY_SCROLLDOWNPAGE: + forwpage (0, 1); + break; + + case MSWIN_KEY_SCROLLTO: + scrollto (0, 0); + break; + } + + update (); + return (TRUE); +} + +/* + * sleep the given number of seconds + */ +int +sleep(int t) +{ + time_t out = (time_t)t + time((long *) 0); + while(out > time((long *) 0)) + ; + return(0); +} + +int +tcsucmp(LPTSTR o, LPTSTR r) +{ + return(o ? (r ? _tcsicmp(o, r) : 1) : (r ? -1 : 0)); +} + +int +tcsruncmp(LPTSTR o, LPTSTR r, int n) +{ + return(o ? (r ? _tcsnicmp(o, r, n) : 1) : (r ? -1 : 0)); +} + +int +strucmp(char *o, char *r) +{ + return(o ? (r ? stricmp(o, r) : 1) : (r ? -1 : 0)); +} + + +int +struncmp(char *o, char *r, int n) +{ + return(o ? (r ? strnicmp(o, r, n) : 1) : (r ? -1 : 0)); +} + + +/* + * Returns screen width of len characters of lpText. + * The width is in character cells. That is, an ascii "A" has + * width 1 but a CJK character probably has width 2. + */ +unsigned +scrwidth(LPTSTR lpText, int len) +{ + UCS ubuf[1000]; + unsigned w; + int i, thislen, offset; + + offset = w = 0; + + while(len > 0){ + thislen = MIN(len, 1000); + for(i = 0; i < thislen; i++) + ubuf[i] = lpText[offset+i]; + + w += ucs4_str_width_ptr_to_ptr(&ubuf[0], &ubuf[thislen]); + + offset += thislen; + len -= thislen; + } + + return w; +} + + +/* + * Returns the index into pScreen given the row,col location + * on the screen, taking into account characters with widths + * other than 1 cell. + */ +long +pscreen_offset_from_cord(int row, int col, PTTYINFO pTTYInfo) +{ + int offset_due_to_row, offset_due_to_col, width; + + /* + * Each row starts at a specific offset into pScreen. + */ + offset_due_to_row = row * pTTYInfo->actNColumn; + + /* + * Start with col (all chars single width) and go from there. + * We need to find the offset that makes the string col wide. + * Hopefully we won't ever get a rectange that causes us to + * draw half characters, but in case we do we need to err on the + * side of too much width instead of not enough. It's a little + * tricky because we want to include following zero-width + * characters. + * fewer characters it would be <= desired width, one more would + * be greater than desired width, this one is >= desired width. + */ + + /* first go to some offset where width is > col */ + offset_due_to_col = col; + width = scrwidth(pTTYInfo->pScreen+offset_due_to_row, offset_due_to_col); + while(width <= col && offset_due_to_col < pTTYInfo->actNColumn-1){ + offset_due_to_col++; + width = scrwidth(pTTYInfo->pScreen+offset_due_to_row, offset_due_to_col); + } + + /* Now back up until width is no longer > col */ + while(width > col){ + offset_due_to_col--; + width = scrwidth(pTTYInfo->pScreen+offset_due_to_row, offset_due_to_col); + } + + /* But, if we're now less than col, go forward one again. */ + if(width < col && offset_due_to_col < pTTYInfo->actNColumn-1) + offset_due_to_col++; + + return(offset_due_to_row + offset_due_to_col); +} + + +/* + * 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 +mswin_store_pass_prompt(void) +{ + /* + * We shouldn't need to prompt anymore, but always return 1 + * just in case + */ + return(1); +} diff --git a/pico/osdep/mswin.h b/pico/osdep/mswin.h new file mode 100644 index 00000000..238d5fd7 --- /dev/null +++ b/pico/osdep/mswin.h @@ -0,0 +1,404 @@ +/* + * ======================================================================== + * 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 MSWIN_H +#define MSWIN_H + + +#define __far +#define _far +#define __export + +/* + * Equivalent to a windows handle. + */ +typedef void *WINHAND; + +/* + * Mouse input events. + */ +typedef struct { + int event; + int button; + int nRow; + int nColumn; + int keys; + int flags; +} MEvent; + + +/* + * These define how mouse events get queued. + */ +#define MSWIN_MF_REPLACING 0x1000 +#define MSWIN_MF_REPEATING 0x2000 + + +typedef struct { + int ch; + int rval; + LPTSTR name; + LPTSTR label; + int id; +} MDlgButton; + + + +/* + * Struct to tell pico how to build and what to return when + * it's asked to build a popup menu. + */ +typedef struct _popup { + enum {tIndex, tQueue, tMessage, tSubMenu, + tSeparator, tTail} type; + struct { /* menu's label */ + char *string; + enum {lNormal = 0, lChecked, lDisabled} style; + } label; + union { + UCS val; /* Queue: inserted into input queue */ + UCS msg; /* Message: gets posted to ghTTYWnd */ + struct _popup *submenu; /* Submenu: array of submenu entries */ + } data; + struct { /* Internal data */ + int id; + } internal; +} MPopup; + + + +/* + * Type of function expected by mswin_allowcopy and mswin_allowcopycut + * and used in EditDoCopyData + */ +typedef int (*getc_t)(int pos); + +/* + * Callback used to fetch text for display in alternate window. Text + * can be returned as either a pointer to a null terminated block of + * text (a string), with CRLF deliminating lines. OR as a pointer to + * an array of pointers to lines, each line being a null terminated + * string. + */ +typedef int (*gettext_t)(char *title, void **text, long *len, int *format); + +/* + * Type used by line up/down event handlers to move the body of the + * displayed text, by sort callback to get/set sort order, by + * header mode to get/set header state... + */ +typedef int (*cbarg_t)(int action, long args); + + +/* + * Callback used for periodic callback. + */ +typedef void (*cbvoid_t)(void); +typedef char *(*cbstr_t)(char *); + + +#define GETTEXT_TITLELEN 128 +#define GETTEXT_TEXT 1 /* 'text' is pointer to text. */ +#define GETTEXT_LINES 2 /* 'text' is pointer to array of + * pointers to lines. */ + + +#ifndef PATH_MAX +#define PATH_MAX 128 /* Max size of a directory path. */ +#endif + +/* + * Scroll callback values. Used to be mapped in the MSWIN_RANGE_START..END + * range, but now are mapped directly to keydefs.h values. Except two of + * them which don't have map values in keydefs.h so they're still in the + * MSWIN_RANGE_START..END 0x7000 range. + */ +#define MSWIN_KEY_SCROLLUPPAGE 0x7000 +#define MSWIN_KEY_SCROLLDOWNPAGE 0x7001 +#define MSWIN_KEY_SCROLLUPLINE KEY_SCRLUPL +#define MSWIN_KEY_SCROLLDOWNLINE KEY_SCRLDNL +#define MSWIN_KEY_SCROLLTO KEY_SCRLTO + +#define MSWIN_PASTE_DISABLE 0 +#define MSWIN_PASTE_FULL 1 +#define MSWIN_PASTE_LINE 2 + + +#define MSWIN_CURSOR_ARROW 0 +#define MSWIN_CURSOR_BUSY 1 +#define MSWIN_CURSOR_IBEAM 2 +#define MSWIN_CURSOR_HAND 3 + +/* + * Flags for mswin_displaytext + */ +#define MSWIN_DT_NODELETE 0x0001 /* Don't delete text when + * window closes. */ +#define MSWIN_DT_USEALTWINDOW 0x0002 /* Put text in alt window if already + * open. Open if not. */ +#define MSWIN_DT_FILLFROMFILE 0x0004 /* pText_utf8 is a filename. */ + +/* + * functions from mswin.c + */ + + +WINHAND mswin_gethinstance(); +WINHAND mswin_gethwnd (); +void mswin_killsplash(); +void mswin_settitle(char *); +int mswin_getmouseevent (MEvent * pMouse); +void mswin_mousetrackcallback (cbarg_t cbfunc); +int mswin_popup(MPopup *members); +void mswin_keymenu_popup (); +void mswin_registericon(int row, int id, char *file); +void mswin_destroyicons(); +void mswin_finishicons(); +void mswin_show_icon (int x, int y, char *file); +void mswin_setdebug (int debug, FILE *debugfile); +void mswin_setnewmailwidth (int width); +int mswin_setdndcallback (int (*cb)()); +int mswin_cleardndcallback (void); +int mswin_setresizecallback (int (*cb)()); +int mswin_clearresizecallback (int (*cb)()); +void mswin_setdebugoncallback (cbvoid_t cbfunc); +void mswin_setdebugoffcallback (cbvoid_t cbfunc); +int mswin_setconfigcallback (cbvoid_t cffunc); +int mswin_sethelpcallback (cbstr_t cbfunc); +int mswin_setgenhelpcallback (cbstr_t cbfunc); +void mswin_setclosetext (char *pCloseText); +int mswin_setwindow (char *fontName, char *fontSize, + char *fontStyle, char *windowPosition, + char *cursorStyle, char *fontCharSet); +int mswin_showwindow(); +int mswin_getwindow (char *fontName, size_t nfontName, + char *fontSize, size_t nfontSize, + char *fontStyle, size_t nfontStyle, + char *windowPosition, size_t nwindowPosition, + char *foreColor, size_t nforeColor, + char *backColor, size_t nbackColor, + char *cursorStyle, size_t ncursorStyle, + char *fontCharSet, size_t nfontCharSet); +void mswin_noscrollupdate (int flag); +void mswin_setscrollrange (long page, long max); +void mswin_setscrollpos (long pos); +long mswin_getscrollpos (void); +long mswin_getscrollto (void); +void mswin_setscrollcallback (cbarg_t cbfunc); +void mswin_setsortcallback (cbarg_t cbfunc); +void mswin_setflagcallback (cbarg_t cbfunc); +void mswin_sethdrmodecallback (cbarg_t cbfunc); +void mswin_setselectedcallback (cbarg_t cbfunc); +void mswin_setzoomodecallback (cbarg_t cbfunc); +void mswin_setfkeymodecallback (cbarg_t cbfunc); +void mswin_setprintfont (char *fontName, char *fontSize, + char *fontStyle, char *fontCharSet); +void mswin_getprintfont (char *, size_t, char *, size_t, + char *, size_t, char *, size_t); +int mswin_yield (void); +int mswin_charavail (void); +UCS mswin_getc_fast (void); +void mswin_flush_input (void); +int mswin_showcursor (int show); +int mswin_showcaret (int show); +void mswin_trayicon (int show); +int mswin_move (int row, int column); +int mswin_getpos (int *row, int *column); +int mswin_getscreensize (int *row, int *column); +void mswin_minimize (void); +int mswin_putblock (char *utf8_str, int strLen); +int mswin_puts (char *utf8_str); +int mswin_puts_n (char *utf8_str, int n); +int mswin_putc (UCS c); +int mswin_outc (char c); +int mswin_rev (int state); +int mswin_getrevstate (void); +int mswin_bold (int state); +int mswin_uline (int state); +int mswin_eeol (void); +int mswin_eeop (void); +int mswin_beep (void); +void mswin_pause (int); +int mswin_flush (void); +void mswin_setcursor (int); +void mswin_messagebox (char *msg, int); +void mswin_allowpaste (int); +void mswin_allowcopycut (getc_t); +void mswin_allowcopy (getc_t); +void mswin_addclipboard (char *s); +void mswin_allowmousetrack (int); +int mswin_newmailicon (void); +void mswin_newmailtext (char *t); +void mswin_newmaildone (void); +void mswin_mclosedtext (char *t); +void mswin_menuitemclear (void); +void mswin_menuitemadd (UCS key, char *label, int menuitem, + int flags); +int mswin_setwindowmenu (int menu); +int mswin_print_ready (WINHAND hWnd, LPTSTR docDesc); +int mswin_print_done (void); +char * mswin_print_error (int errorcode); +int mswin_print_char (TCHAR c); +int mswin_print_char_utf8 (int c); +int mswin_print_text (LPTSTR text); +int mswin_print_text_utf8 (char *text); +int mswin_savefile (char *dir, int dirlen, char *fName, int nMaxFName); +int mswin_openfile (char *dir, int nMaxDName, char *fName, int nMaxFName, char *extlist); +int mswin_multopenfile (char *dir, int nMaxDName, char *fName, int nMaxFName, char *extlist); +char *mswin_rgbchoice(char *pOldRGB); +void mswin_killbuftoclip (getc_t copyfunc); +void pico_popup(); +void mswin_paste_popup(); +int mswin_fflush (FILE *f); +void mswin_setperiodiccallback (cbvoid_t periodiccb, long period); +WINHAND mswin_inst2task (WINHAND hInst); +int mswin_ontask_del (WINHAND hTask, char *path); +int mswin_exec_and_wait (char *whatsit, char *command, + char *infile, char *outfile, + int *exit_val, + unsigned mseaw_flags); +int mswin_shell_exec (char *command, WINHAND *childproc); +void mswin_exec_err_msg (char *what, int status, char *buf, + size_t buflen); +int mswin_onexit_del (char *path); +int mswin_set_quit_confirm (int); +void mswin_showhelpmsg (WINHAND hWnd, char **helplines); + +typedef struct MSWIN_TEXTWINDOW MSWIN_TEXTWINDOW; +MSWIN_TEXTWINDOW *mswin_displaytext (char *title, char *pText, size_t textLen, + char **pLines, MSWIN_TEXTWINDOW *mswin_tw, + int flags); +int mswin_imaptelemetry(char *msg); +void mswin_enableimaptelemetry(int state); +int mswin_newmailwin(int is_us, char *from, + char *subject, char *folder); +int mswin_newmailwinon(void); + +char *mswin_reg_default_browser(char *url); +int mswin_reg(int op, int tree, char *data, size_t size); +int mswin_is_def_client(int type); +int mswin_set_def_client(int type); +char **mswin_reg_dump(void); +int mswin_majorver(); +int mswin_minorver(); +char *mswin_compilation_date(); +char *mswin_compilation_remarks(); +char *mswin_specific_winver(); + + +int mswin_usedialog (void); +int mswin_dialog(UCS *prompt, UCS *string, int field_len, + int append_current, int passwd, + MDlgButton *button_list, char **help, unsigned flags); +int mswin_select(char *utf8prompt, MDlgButton *button_list, + int dflt, int on_ctrl_C, char **help, unsigned flags); +int mswin_yesno(UCS *); +int mswin_yesno_utf8(char *); + +BOOL MSWRShellCanOpen(LPTSTR key, char *cmdbuf, int clen, int allow_noreg); +BOOL MSWRPeek(HKEY hRootKey, LPTSTR subkey, LPTSTR valstr, + LPTSTR data, DWORD *dlen); +int mswin_store_pass_prompt(void); +void mswin_set_erasecreds_callback(cbvoid_t); +void mswin_setviewinwindcallback (cbvoid_t); +int mswin_setgenhelptextcallback(cbstr_t); +int mswin_caninput(void); +void mswin_beginupdate(void); +void mswin_endupdate(void); +int mswin_sethelptextcallback(cbstr_t); +int strucmp(char *, char *); +int struncmp(char *, char *, int); + +#ifdef MSC_MALLOC +/* + * These definitions will disable the SEGHEAP allocation routines, in + * favor of the compliler libraries usual allocators. This is useful + * when external debugging tools and the SEGHEAP debugging routines step + * on each other... + */ +#define MemAlloc(X) malloc(X) +#define MemFree(X) free(X) +#define MemRealloc(X,Y) realloc(X,Y) +#define MemFreeAll() +#define MemDebug(X,Y) +#else +/* + * Memory management stuff, from msmem.c + */ +typedef unsigned long MemSize; +typedef void __far * MemPtr; + +#define MemAlloc(s) _MemAlloc (s, __FILE__, __LINE__) +#define malloc(s) _MemAlloc (s, __FILE__, __LINE__) +#define MemFree(b) _MemFree (b, __FILE__, __LINE__) +#define free(b) _MemFree (b, __FILE__, __LINE__) +#define MemRealloc(b,s) _MemRealloc (b, s, __FILE__, __LINE__) +#define realloc(b,s) _MemRealloc (b, s, __FILE__, __LINE__) +#define MEM_BLOCK_SIZE_MAX 0xff00 + +void MemDebug (int debug, FILE *debugFile); +void __far * _MemAlloc (MemSize size, char __far * file, int line); +int _MemFree (void __far *block, char __far *file, int line); +void __far * _MemRealloc (void __far *block, MemSize size, + char __far * file, int line); +MemSize MemBlkSize (MemPtr block); +void MemFreeAll (void); +void MemFailSoon (MemSize); +#endif + +/* functions from win moved to mswin.c */ +int pico_scroll_callback (int, long); +#undef sleep +int sleep (int); + +/* + * Signals that are not defined by MS C + */ +#define SIGHUP 1 /* Terminal hangup. */ +#define SIGINT 2 /* Ctrl-C sequence */ +#define SIGILL 4 /* illegal instruction - invalid function image */ +#define SIGSEGV 11 /* segment violation */ +#define SIGALRM 14 /* alarm clock */ +#define SIGTERM 15 /* Software termination signal from kill */ +#define SIGABRT 22 /* abnormal termination triggered by abort call */ +#define SIGWINCH 28 /* Change in window size. */ + + +#define fflush mswin_fflush + +#define alarm mswin_alarm + +/* + * Registry setting constants + */ +#define MSWR_PINE_RC 1 +#define MSWR_PINE_DIR 2 +#define MSWR_PINE_EXE 3 +#define MSWR_PINE_AUX 4 +#define MSWR_PINE_POS 5 +#define MSWR_PINE_CONF 6 + +#define MSWR_SDC_MAIL 1 +#define MSWR_SDC_NEWS 2 +#define MSWR_SDC_NNTP 3 +#define MSWR_SDC_IMAP 4 + +#define MSWR_NULL 0x00 +#define MSWR_OP_SET 0x01 +#define MSWR_OP_FORCE 0x02 +#define MSWR_OP_GET 0x04 +#define MSWR_OP_BLAST 0x08 + +#endif /* MSWIN_H */ diff --git a/pico/osdep/mswin.ico b/pico/osdep/mswin.ico new file mode 100644 index 00000000..d6b7af76 Binary files /dev/null and b/pico/osdep/mswin.ico differ diff --git a/pico/osdep/mswin.rc b/pico/osdep/mswin.rc new file mode 100644 index 00000000..5462b554 --- /dev/null +++ b/pico/osdep/mswin.rc @@ -0,0 +1,236 @@ +//Microsoft App 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 + + +////////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +ALPINEICON ICON DISCARDABLE "PICO.ICO" + +////////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + + +///////////////////////////////////////////////////////////////////////////// +// +// Cursor +// + +PICOHAND CURSOR "MSWINHND.CUR" + +////////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +ALPINEMENU MENU DISCARDABLE +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Save File", IDM_MI_SAVEFILE + 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 "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 "Show Current &Position", IDM_MI_CURPOSITION + MENUITEM "&Where Is", IDM_MI_WHEREIS + MENUITEM "&Justify Paragraph", IDM_MI_JUSTIFY + END + POPUP "C&onfig" + BEGIN + MENUITEM "Set &Font", IDM_OPT_SETFONT + 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 + +TEXTWINMENU MENU DISCARDABLE +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Close", IDM_FILE_CLOSE + MENUITEM SEPARATOR + MENUITEM "&Print", IDM_FILE_PRINT + END +END + + +#include "mswin_spell.dlg" + +////////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +ABOUTDLGBOX DIALOG DISCARDABLE 10, 25, 220, 80 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Pico" +FONT 8, "Helv" +BEGIN + ICON ALPINEICON,IDD_ABOUTICON,10,5,18,20 + CTEXT "Pico for Windows%S\nVersion %d.%02d%S\nBuild (%S)", + IDD_VERSION,46,8,120,26,SS_NOPREFIX | NOT WS_GROUP + LTEXT "",IDD_BYLINE,45,40,172,56,SS_NOPREFIX | NOT WS_GROUP + DEFPUSHBUTTON "OK",IDD_OK,177,8,40,14 + PUSHBUTTON "",IDC_BUTTON2,10,5,30,30,BS_ICON | NOT + WS_VISIBLE | WS_DISABLED,WS_EX_TRANSPARENT +END + +IDD_TOOLBAR DIALOG DISCARDABLE 0, 0, 302, 12 +STYLE WS_CHILD | WS_VISIBLE +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Quit",IDM_MI_EXIT,1,0,25,11 + PUSHBUTTON "Save",IDM_MI_SAVEFILE,26,0,26,11 + PUSHBUTTON "Find",IDM_MI_WHEREIS,52,0,22,11 + PUSHBUTTON "Spell",IDM_MI_SPELLCHK,74,0,26,11 +END + +#ifdef APSTUDIO_INVOKED +////////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +0X00020000L 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 + + +////////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_BYLINE "Copyright 2006-2009 University of Washington" + IDS_APPNAME "Pico" + IDS_APPIDENT "pico" +END + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 5,05,0,0 + PRODUCTVERSION 5,05,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/pine\0" + VALUE "CompanyName", "University of Washington\0" + VALUE "FileDescription", "Pico\0" + VALUE "FileVersion", "5.05\0" + VALUE "InternalName", "pico\0" + VALUE "LegalCopyright", "Copyright 2006-2009\0" + VALUE "OriginalFilename", "pico.exe\0" + VALUE "ProductName", " pico\0" + VALUE "ProductVersion", "5.05\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + +#ifndef APSTUDIO_INVOKED +//////////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/pico/osdep/mswin_aspell.c b/pico/osdep/mswin_aspell.c new file mode 100644 index 00000000..26e998da --- /dev/null +++ b/pico/osdep/mswin_aspell.c @@ -0,0 +1,427 @@ +/* + * ======================================================================== + * FILE: MSWIN_ASPELL.C + * + * 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 +#include "mswin_aspell.h" + +/* + * Aspell typedefs + */ +typedef struct AspellConfig AspellConfig; +typedef struct AspellSpeller AspellSpeller; +typedef struct AspellCanHaveError AspellCanHaveError; +typedef struct AspellStringEnumeration AspellStringEnumeration; +typedef struct AspellWordList AspellWordList; +typedef struct AspellError AspellError; + +/* + * Aspell function prototypes + */ +typedef AspellCanHaveError * (__cdecl NEW_ASPELL_SPELLER)(AspellConfig * config); +typedef AspellConfig * (__cdecl NEW_ASPELL_CONFIG)(); +typedef AspellSpeller * (__cdecl TO_ASPELL_SPELLER)(AspellCanHaveError * obj); +typedef int (__cdecl ASPELL_CONFIG_REPLACE)(AspellConfig * ths, const char * key, const char * value); +typedef void (__cdecl DELETE_ASPELL_CONFIG)(AspellConfig * ths); +typedef const char * (__cdecl ASPELL_CONFIG_RETRIEVE)(AspellConfig * ths, const char * key); +typedef int (__cdecl ASPELL_SPELLER_ADD_TO_PERSONAL)(AspellSpeller * ths, const char * word, int word_size); +typedef int (__cdecl ASPELL_SPELLER_ADD_TO_SESSION)(AspellSpeller * ths, const char * word, int word_size); +typedef int (__cdecl ASPELL_SPELLER_CHECK)(AspellSpeller * ths, const char * word, int word_size); +typedef const AspellWordList * (__cdecl ASPELL_SPELLER_SUGGEST)(AspellSpeller * ths, const char * word, int word_size); +typedef int (__cdecl ASPELL_SPELLER_SAVE_ALL_WORD_LISTS)(AspellSpeller * ths); +typedef int (__cdecl ASPELL_SPELLER_STORE_REPLACEMENT)(AspellSpeller * ths, const char * mis, int mis_size, const char * cor, int cor_size); +typedef const char * (__cdecl ASPELL_STRING_ENUMERATION_NEXT)(AspellStringEnumeration * ths); +typedef AspellStringEnumeration *(__cdecl ASPELL_WORD_LIST_ELEMENTS)(const AspellWordList * ths); +typedef void (__cdecl DELETE_ASPELL_SPELLER)(AspellSpeller * ths); +typedef void (__cdecl DELETE_ASPELL_STRING_ENUMERATION)(AspellStringEnumeration * ths); +typedef void (__cdecl DELETE_ASPELL_CAN_HAVE_ERROR)(AspellCanHaveError * ths); +typedef const char * (__cdecl ASPELL_ERROR_MESSAGE)(const AspellCanHaveError * ths); +typedef unsigned int (__cdecl ASPELL_ERROR_NUMBER)(const AspellCanHaveError * ths); +typedef const AspellError * (__cdecl ASPELL_SPELLER_ERROR)(const AspellSpeller * ths); +typedef const char * (__cdecl ASPELL_SPELLER_ERROR_MESSAGE)(const struct AspellSpeller * ths); +typedef AspellConfig * (__cdecl ASPELL_SPELLER_CONFIG)(AspellSpeller * ths); + +/* + * MsWin speller information structure + */ +typedef struct ASPELLINFO +{ + AspellSpeller *speller; + AspellStringEnumeration *suggestion_elements; + const char *error_message; + + AspellCanHaveError *err; + + HMODULE mod_aspell; + + NEW_ASPELL_SPELLER *new_aspell_speller; + NEW_ASPELL_CONFIG *new_aspell_config; + TO_ASPELL_SPELLER *to_aspell_speller; + ASPELL_CONFIG_REPLACE *aspell_config_replace; + DELETE_ASPELL_CONFIG *delete_aspell_config; + ASPELL_CONFIG_RETRIEVE *aspell_config_retrieve; + ASPELL_SPELLER_ADD_TO_PERSONAL *aspell_speller_add_to_personal; + ASPELL_SPELLER_ADD_TO_SESSION *aspell_speller_add_to_session; + ASPELL_SPELLER_CHECK *aspell_speller_check; + ASPELL_SPELLER_SUGGEST *aspell_speller_suggest; + ASPELL_SPELLER_SAVE_ALL_WORD_LISTS *aspell_speller_save_all_word_lists; + ASPELL_SPELLER_STORE_REPLACEMENT *aspell_speller_store_replacement; + ASPELL_STRING_ENUMERATION_NEXT *aspell_string_enumeration_next; + ASPELL_WORD_LIST_ELEMENTS *aspell_word_list_elements; + DELETE_ASPELL_SPELLER *delete_aspell_speller; + DELETE_ASPELL_STRING_ENUMERATION *delete_aspell_string_enumeration; + DELETE_ASPELL_CAN_HAVE_ERROR *delete_aspell_can_have_error; + ASPELL_ERROR_MESSAGE *aspell_error_message; + ASPELL_ERROR_NUMBER *aspell_error_number; + ASPELL_SPELLER_ERROR *aspell_speller_error; + ASPELL_SPELLER_ERROR_MESSAGE *aspell_speller_error_message; + ASPELL_SPELLER_CONFIG *aspell_speller_config; +} ASPELLINFO; + +/* + * Find, aspell-15.dll, load it, and attempt to initialize our func pointers. + * 1:success, 0:failed + */ +static int +speller_load_aspell_library(ASPELLINFO *aspellinfo) +{ + HKEY hKey; + int success = 1; + HMODULE mod_aspell = NULL; + TCHAR aspell_fullname[MAX_PATH + 1]; + static const TCHAR aspell_name[] = TEXT("aspell-15.dll"); + + aspell_fullname[0] = '\0'; + + // Try to open the Aspell Registry key. + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Aspell"), + 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + DWORD dtype; + TCHAR aspell_path[MAX_PATH + 1]; + DWORD aspell_path_len = sizeof(aspell_path); + + // Query the path. + if(RegQueryValueEx(hKey, TEXT("Path"), NULL, &dtype, + (LPBYTE)aspell_path, &aspell_path_len) == ERROR_SUCCESS) + { + _sntprintf(aspell_fullname, ARRAYSIZE(aspell_fullname), + TEXT("%s\\%s"), aspell_path, aspell_name); + aspell_fullname[ARRAYSIZE(aspell_fullname) - 1] = 0; + } + + RegCloseKey(hKey); + } + + // If the registry thing didn't work, then just try doing a Loadlibrary + // using the standard Windows LoadLibrary search gunk. + if(!aspell_fullname[0]) + { + _tcsncpy(aspell_fullname, aspell_name, ARRAYSIZE(aspell_fullname)); + aspell_fullname[ARRAYSIZE(aspell_fullname) - 1] = 0; + } + + // Load the library. + mod_aspell = LoadLibrary(aspell_fullname); + if(!mod_aspell) + { + // Failed. + success = 0; + } + else + { + // Found the library - now try to initialize our function pointers. + // +#define GET_ASPELL_PROC_ADDR(_functype, _func) \ + aspellinfo->_func = (_functype *)GetProcAddress(mod_aspell, #_func); \ + if(!aspellinfo->_func) \ + success = 0 + + GET_ASPELL_PROC_ADDR(NEW_ASPELL_SPELLER, new_aspell_speller); + GET_ASPELL_PROC_ADDR(NEW_ASPELL_CONFIG, new_aspell_config); + GET_ASPELL_PROC_ADDR(TO_ASPELL_SPELLER, to_aspell_speller); + GET_ASPELL_PROC_ADDR(ASPELL_CONFIG_REPLACE, aspell_config_replace); + GET_ASPELL_PROC_ADDR(DELETE_ASPELL_CONFIG, delete_aspell_config); + GET_ASPELL_PROC_ADDR(ASPELL_CONFIG_RETRIEVE, aspell_config_retrieve); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_ADD_TO_PERSONAL, aspell_speller_add_to_personal); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_ADD_TO_SESSION, aspell_speller_add_to_session); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_CHECK, aspell_speller_check); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_SUGGEST, aspell_speller_suggest); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_SAVE_ALL_WORD_LISTS, aspell_speller_save_all_word_lists); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_STORE_REPLACEMENT, aspell_speller_store_replacement); + GET_ASPELL_PROC_ADDR(ASPELL_STRING_ENUMERATION_NEXT, aspell_string_enumeration_next); + GET_ASPELL_PROC_ADDR(ASPELL_WORD_LIST_ELEMENTS, aspell_word_list_elements); + GET_ASPELL_PROC_ADDR(DELETE_ASPELL_SPELLER, delete_aspell_speller); + GET_ASPELL_PROC_ADDR(DELETE_ASPELL_STRING_ENUMERATION, delete_aspell_string_enumeration); + GET_ASPELL_PROC_ADDR(DELETE_ASPELL_CAN_HAVE_ERROR, delete_aspell_can_have_error); + GET_ASPELL_PROC_ADDR(ASPELL_ERROR_MESSAGE, aspell_error_message); + GET_ASPELL_PROC_ADDR(ASPELL_ERROR_NUMBER, aspell_error_number); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_ERROR, aspell_speller_error); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_ERROR_MESSAGE, aspell_speller_error_message); + GET_ASPELL_PROC_ADDR(ASPELL_SPELLER_CONFIG, aspell_speller_config); + +#undef GET_ASPELL_PROC_ADDR + + if(!success) + { + FreeLibrary(mod_aspell); + mod_aspell = NULL; + } + } + + aspellinfo->mod_aspell = mod_aspell; + return success; +} + +/* + * Find, load, and initialize the aspell library. + * NULL on failure, otherwise an ASPELLINFO pointer. + */ +ASPELLINFO * +speller_init(char *lang) +{ + ASPELLINFO *aspellinfo; + + aspellinfo = (ASPELLINFO *)malloc(sizeof(ASPELLINFO)); + if(aspellinfo) + { + AspellConfig *config; + AspellSpeller *speller; + + memset(aspellinfo, 0, sizeof(ASPELLINFO)); + + // Load the aspell library and set up our function pointers, etc. + if(!speller_load_aspell_library(aspellinfo)) + { + speller_close(aspellinfo); + return NULL; + } + + config = aspellinfo->new_aspell_config(); + + if(lang) + { + aspellinfo->aspell_config_replace(config, "lang", lang); + } + + aspellinfo->aspell_config_replace(config, "encoding", "utf-8"); + + aspellinfo->err = aspellinfo->new_aspell_speller(config); + aspellinfo->delete_aspell_config(config); + + if(aspellinfo->aspell_error_number(aspellinfo->err)) + { + aspellinfo->error_message = + aspellinfo->aspell_error_message(aspellinfo->err); + } + else + { + aspellinfo->speller = aspellinfo->to_aspell_speller(aspellinfo->err); + aspellinfo->err = NULL; + + /* + TODO: Log the current speller config somewhere? + + config = aspell_speller_config(speller); + + fputs("Using: ", stdout); + fputs(aspell_config_retrieve(config, "lang"), stdout); + fputs("-", stdout); + fputs(aspell_config_retrieve(config, "jargon"), stdout); + fputs("-", stdout); + fputs(aspell_config_retrieve(config, "size"), stdout); + fputs("-", stdout); + fputs(aspell_config_retrieve(config, "module"), stdout); + fputs("\n\n", stdout); + */ + } + } + + return aspellinfo; +} + +/* + * Get the last aspell error message. + */ +const char * +speller_get_error_message(ASPELLINFO *aspellinfo) +{ + return aspellinfo ? aspellinfo->error_message : NULL; +} + +/* + * Close up shop. + */ +void +speller_close(ASPELLINFO *aspellinfo) +{ + if(aspellinfo) + { + // Make sure someone hasn't missed a speller_suggestion_close + assert(!aspellinfo->suggestion_elements); + + if(aspellinfo->err) + { + aspellinfo->delete_aspell_can_have_error(aspellinfo->err); + aspellinfo->err = NULL; + } + + if(aspellinfo->speller) + { + aspellinfo->delete_aspell_speller(aspellinfo->speller); + aspellinfo->speller = NULL; + } + + if(aspellinfo->mod_aspell) + { + FreeLibrary(aspellinfo->mod_aspell); + aspellinfo->mod_aspell = NULL; + } + + free(aspellinfo); + } +} + +/* + * Check the dictionary for a word. + * 1:found, 0:not found, -1:error + */ +int +speller_check_word(ASPELLINFO *aspellinfo, const char *word, int word_size) +{ + int have = 1; + + if(!isdigit(*word)) + { + have = aspellinfo->aspell_speller_check(aspellinfo->speller, word, word_size); + if(have == -1) + { + aspellinfo->error_message = + aspellinfo->aspell_speller_error_message(aspellinfo->speller); + } + + } + + return have; +} + +/* + * Add word to dictionary. + * 1:success, 0:failed + */ +int +speller_add_to_dictionary(ASPELLINFO *aspellinfo, const char *word, int word_size) +{ + aspellinfo->aspell_speller_add_to_personal(aspellinfo->speller, + word, word_size); + if(aspellinfo->aspell_speller_error(aspellinfo->speller)) + { + aspellinfo->error_message = + aspellinfo->aspell_speller_error_message(aspellinfo->speller); + return 0; + } + + aspellinfo->aspell_speller_save_all_word_lists(aspellinfo->speller); + if(aspellinfo->aspell_speller_error(aspellinfo->speller)) + { + aspellinfo->error_message = + aspellinfo->aspell_speller_error_message(aspellinfo->speller); + return 0; + } + + return 1; +} + +/* + * Add this word to the current spelling session. + * 1:success, 0:failed + */ +int +speller_ignore_all(ASPELLINFO *aspellinfo, const char *word, int word_size) +{ + aspellinfo->aspell_speller_add_to_session(aspellinfo->speller, word, word_size); + if(aspellinfo->aspell_speller_error(aspellinfo->speller)) + { + aspellinfo->error_message = + aspellinfo->aspell_speller_error_message(aspellinfo->speller); + return 0; + } + + return 1; +} + +/* + * Replace a word. + * 1:success, 0:failed + */ +int +speller_replace_word(ASPELLINFO *aspellinfo, + const char * mis, int mis_size, const char * cor, int cor_size) +{ + int ret; + + ret = aspellinfo->aspell_speller_store_replacement(aspellinfo->speller, + mis, mis_size, cor, cor_size); + if(ret == -1) + { + aspellinfo->error_message = + aspellinfo->aspell_speller_error_message(aspellinfo->speller); + return 0; + } + + return 1; +} + +/* + * Init the suggestions element array for a specific word. + * 1:success, 0:failed + */ +int +speller_suggestion_init(ASPELLINFO *aspellinfo, const char *word, int word_size) +{ + const AspellWordList *suggestions; + + // Make sure someone hasn't missed a speller_suggestion_close + assert(!aspellinfo->suggestion_elements); + + suggestions = aspellinfo->aspell_speller_suggest(aspellinfo->speller, + word, word_size); + if(!suggestions) + { + aspellinfo->error_message = + aspellinfo->aspell_speller_error_message(aspellinfo->speller); + return 0; + } + + aspellinfo->suggestion_elements = + aspellinfo->aspell_word_list_elements(suggestions); + return 1; +} + +/* + * Find next suggestion. Returns NULL when no more are left. + */ +const char * +speller_suggestion_getnext(ASPELLINFO *aspellinfo) +{ + return aspellinfo->aspell_string_enumeration_next( + aspellinfo->suggestion_elements); +} + +/* + * Close the suggestion element array. + */ +void +speller_suggestion_close(ASPELLINFO *aspellinfo) +{ + aspellinfo->delete_aspell_string_enumeration(aspellinfo->suggestion_elements); + aspellinfo->suggestion_elements = NULL; +} diff --git a/pico/osdep/mswin_aspell.h b/pico/osdep/mswin_aspell.h new file mode 100644 index 00000000..623890ad --- /dev/null +++ b/pico/osdep/mswin_aspell.h @@ -0,0 +1,30 @@ +/* + * ======================================================================== + * FILE: MSWIN_ASPELL.H + * + * 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 + * + * ======================================================================== + */ + +typedef struct ASPELLINFO ASPELLINFO; + +ASPELLINFO *speller_init(char *lang); +void speller_close(ASPELLINFO *aspellinfo); + +const char *speller_get_error_message(ASPELLINFO *aspellinfo); + +int speller_check_word(ASPELLINFO *aspellinfo, const char *word, int word_size); +int speller_add_to_dictionary(ASPELLINFO *aspellinfo, const char *word, int word_size); +int speller_ignore_all(ASPELLINFO *aspellinfo, const char *word, int word_size); +int speller_replace_word(ASPELLINFO *aspellinfo, const char * mis, int mis_size, + const char * cor, int cor_size); + +int speller_suggestion_init(ASPELLINFO *aspellinfo, const char *word, int word_size); +const char *speller_suggestion_getnext(ASPELLINFO *aspellinfo); +void speller_suggestion_close(ASPELLINFO *aspellinfo); + diff --git a/pico/osdep/mswin_spell.DLG b/pico/osdep/mswin_spell.DLG new file mode 100644 index 00000000..53e4aa5a --- /dev/null +++ b/pico/osdep/mswin_spell.DLG @@ -0,0 +1,18 @@ +#include "mswin_spell.h" + +DLG_SPELL DIALOG 16, 34, 229, 92 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog Title" +FONT 8, "MS Shell Dlg" +BEGIN + COMBOBOX ID_COMBO_SUGGESTIONS, 4, 5, 79, 80, CBS_SIMPLE | WS_VSCROLL | + WS_TABSTOP | CBS_AUTOHSCROLL + + DEFPUSHBUTTON "&Change", ID_BUTTON_CHANGE, 88, 8, 62, 14, WS_GROUP | WS_TABSTOP + PUSHBUTTON "Change &All", ID_BUTTON_CHANGEALL, 88, 27, 62, 14, WS_TABSTOP + PUSHBUTTON "&Ignore Once", ID_BUTTON_IGNOREONCE, 88, 46, 62, 14, WS_TABSTOP + PUSHBUTTON "I&gnore All", ID_BUTTON_IGNOREALL, 88, 65, 62, 14, WS_TABSTOP + PUSHBUTTON "Add to &Dictionary", ID_BUTTON_ADD_TO_DICT, 156, 8, 68, 14, WS_TABSTOP + PUSHBUTTON "Ca&ncel", ID_BUTTON_CANCEL, 156, 27, 68, 14, WS_TABSTOP +END diff --git a/pico/osdep/mswin_spell.c b/pico/osdep/mswin_spell.c new file mode 100644 index 00000000..1379beb6 --- /dev/null +++ b/pico/osdep/mswin_spell.c @@ -0,0 +1,547 @@ +/* + * ======================================================================== + * MSWIN_SPELL.C + * + * 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 "mswin_aspell.h" +#include "mswin_spell.h" + +#include + +typedef struct WORD_INFO +{ + ASPELLINFO *aspellinfo; // Aspell information + + const char *utf8_err_message; // Error message, if any + + char *word_utf8; // utf-8 + LPTSTR word_lptstr; + + int word_doto; // UCS + LINE *word_dotp; // + int word_size; // +} WORD_INFO; + +extern HINSTANCE ghInstance; +extern HWND ghTTYWnd; + +/* + * Function prototypes + */ +static int get_next_bad_word(WORD_INFO *word_info); +static void free_word_info_words(WORD_INFO *word_info); +static INT_PTR CALLBACK spell_dlg_proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +/* + * spell() - check for potentially missspelled words and offer them for + * correction. Microsoft Windows specific version. + */ +int +spell(int f, int n) +{ + int w_marko_bak; + LINE *w_markp_bak; + WORD_INFO word_info; + ASPELLINFO *aspellinfo; + + emlwrite(_("Checking spelling..."), NULL); /* greetings! */ + + memset(&word_info, 0, sizeof(WORD_INFO)); + + aspellinfo = speller_init(NULL); + if(!aspellinfo || + (word_info.utf8_err_message = speller_get_error_message(aspellinfo))) + { + if(word_info.utf8_err_message) + emlwrite((char *)word_info.utf8_err_message, NULL); /* sad greetings! */ + else + emlwrite(_("Spelling: initializing Aspell failed"), NULL); + + speller_close(aspellinfo); + return -1; + } + + // Back up current mark. + w_marko_bak = curwp->w_marko; + w_markp_bak = curwp->w_markp; + + setimark(0, 1); + gotobob(0, 1); + + word_info.aspellinfo = aspellinfo; + + if(get_next_bad_word(&word_info)) + { + DialogBoxParam(ghInstance, MAKEINTRESOURCE(DLG_SPELL), ghTTYWnd, + spell_dlg_proc, (LPARAM)&word_info); + } + + // Restore original mark if possible. + if(curwp->w_markp) + { + // Clear any set marks. + setmark(0, 1); + + if(w_markp_bak && llength(w_markp_bak)) + { + // Make sure we don't set mark off the line. + if(w_marko_bak >= llength(w_markp_bak)) + w_marko_bak = llength(w_markp_bak) - 1; + + curwp->w_marko = w_marko_bak; + curwp->w_markp = w_markp_bak; + } + } + + if(word_info.utf8_err_message) + emlwrite((char *)word_info.utf8_err_message, NULL); + else + emlwrite(_("Done checking spelling"), NULL); + + speller_close(aspellinfo); + free_word_info_words(&word_info); + + swapimark(0, 1); + curwp->w_flag |= WFHARD|WFMODE; + + update(); + return 1; +} + +/* + * handle_cb_editchange() - combobox contents have changed so enable/disable + * the Change and Change All buttons accordingly. + */ +static void +handle_cb_editchange(HWND hDlg, HWND hwnd_combo) +{ + int text_len = ComboBox_GetTextLength(hwnd_combo); + + Button_Enable(GetDlgItem(hDlg, ID_BUTTON_CHANGE), !!text_len); + Button_Enable(GetDlgItem(hDlg, ID_BUTTON_CHANGEALL), !!text_len); +} + +/* + * spell_dlg_proc_set_word() - Set up the dialog to display spelling + * information and suggestions for the current word. + */ +static void +spell_dlg_proc_set_word(HWND hDlg, WORD_INFO *word_info) +{ + TCHAR dlg_title[256]; + HWND hwnd_combo = GetDlgItem(hDlg, ID_COMBO_SUGGESTIONS); + + // Clear the combobox. + ComboBox_ResetContent(hwnd_combo); + + // Set the dialog box title + _sntprintf(dlg_title, ARRAYSIZE(dlg_title), TEXT("Not in Dictionary: %s"), + word_info->word_lptstr); + dlg_title[ARRAYSIZE(dlg_title) - 1] = 0; + SetWindowText(hDlg, dlg_title); + + // Populate the combobox with suggestions + if(speller_suggestion_init(word_info->aspellinfo, word_info->word_utf8, -1)) + { + const char *suggestion_utf8; + + while(suggestion_utf8 = speller_suggestion_getnext(word_info->aspellinfo)) + { + LPTSTR suggestion_lptstr = utf8_to_lptstr((LPSTR)suggestion_utf8); + + if(suggestion_lptstr) + { + ComboBox_AddString(hwnd_combo, suggestion_lptstr); + fs_give((void **)&suggestion_lptstr); + } + } + + // Select the first one. + ComboBox_SetCurSel(hwnd_combo, 0); + + speller_suggestion_close(word_info->aspellinfo); + } + + handle_cb_editchange(hDlg, hwnd_combo); +} + +/* + * spell_dlg_next_word() - Move to the next misspelled word and display + * the information for it. If no more bad words, kill the dialog. + */ +static void +spell_dlg_next_word(HWND hDlg, WORD_INFO *word_info) +{ + if(!get_next_bad_word(word_info)) + { + EndDialog(hDlg, 0); + return; + } + + spell_dlg_proc_set_word(hDlg, word_info); +} + +/* + * chword_n() - change the given word_len pointed to by the curwp->w_dot + * pointers to the word in cb + */ +static void +chword_n(int wb_len, UCS *cb) +{ + ldelete(wb_len, NULL); /* not saved in kill buffer */ + + while(*cb != '\0') + linsert(1, *cb++); + + curwp->w_flag |= WFEDIT; +} + +/* + * replace_all() - replace all instances of oldword with newword from + * the current '.' on. Mark is where curwp will get restored to. + */ +static void +replace_all(UCS *oldword, int oldword_len, UCS *newword) +{ + int wrap; + + // Mark should be at "." right now - ie the start of the word we're + // checking. We're going to use mark below to restore curwp since + // chword_n can potentially realloc LINES and it knows about markp. + assert(curwp->w_markp); + assert(curwp->w_markp == curwp->w_dotp); + assert(curwp->w_marko == curwp->w_doto); + + curwp->w_bufp->b_mode |= MDEXACT; /* case sensitive */ + while(forscan(&wrap, oldword, NULL, 0, 1)) + { + if(wrap) + break; /* wrap NOT allowed! */ + + /* + * We want to minimize the number of substrings that we report + * as matching a misspelled word... + */ + if(lgetc(curwp->w_dotp, 0).c != '>') + { + LINE *lp = curwp->w_dotp; /* for convenience */ + int off = curwp->w_doto; + + if(off == 0 || !ucs4_isalpha(lgetc(lp, off - 1).c)) + { + off += oldword_len; + + if(off == llength(lp) || !ucs4_isalpha(lgetc(lp, off).c)) + { + // Replace this word + chword_n(oldword_len, newword); + } + } + } + + forwchar(0, 1); /* move on... */ + + } + curwp->w_bufp->b_mode ^= MDEXACT; /* case insensitive */ + + curwp->w_dotp = curwp->w_markp; + curwp->w_doto = curwp->w_marko; +} + +/* + * handle_button_change() - Someone pressed the Change or Change All button. + * So deal with it. + */ +static void +handle_button_change(HWND hDlg, WORD_INFO *word_info, int do_replace_all) +{ + int str_lptstr_len; + TCHAR str_lptstr[128]; + + // Get the length of what they want to change to. + str_lptstr_len = ComboBox_GetText(GetDlgItem(hDlg, ID_COMBO_SUGGESTIONS), + str_lptstr, ARRAYSIZE(str_lptstr)); + if(str_lptstr_len) + { + UCS *str_ucs4; + char *str_utf8; + + // Notify the speller so it'll suggest this word next time around. + str_utf8 = lptstr_to_utf8(str_lptstr); + if(str_utf8) + { + speller_replace_word(word_info->aspellinfo, + word_info->word_utf8, -1, str_utf8, -1); + fs_give((void **)&str_utf8); + } + + str_ucs4 = lptstr_to_ucs4(str_lptstr); + if(!str_ucs4) + { + // Uh oh - massive error. + word_info->utf8_err_message = _("Spelling: lptstr_to_ucs4() failed"); + EndDialog(hDlg, -1); + return; + } + + // Move back to start of this word. + curwp->w_doto = word_info->word_doto; + curwp->w_dotp = word_info->word_dotp; + + if(do_replace_all) + { + int i; + int word_size; + UCS word[128]; + + word_size = word_info->word_size; + if(word_size >= ARRAYSIZE(word) - 1) + word_size = ARRAYSIZE(word) - 1; + + for(i = 0; i < word_size; i++) + word[i] = lgetc(curwp->w_dotp, curwp->w_doto + i).c; + + word[i] = 0; + + replace_all(word, word_size, str_ucs4); + + // Skip over the word we just inserted. + forwchar(FALSE, (int)ucs4_strlen(str_ucs4)); + } + else + { + // Swap it with the new improved version. + chword_n(word_info->word_size, str_ucs4); + } + fs_give((void **)&str_ucs4); + } + + update(); + spell_dlg_next_word(hDlg, word_info); +} + +/* + * center_dialog() - Center a dialog with respect to its parent. + */ +static void +center_dialog(HWND hDlg) +{ + int dx; + int dy; + RECT rcDialog; + RECT rcParent; + + GetWindowRect(GetParent(hDlg), &rcParent); + GetWindowRect(hDlg, &rcDialog); + + dx = ((rcParent.right - rcParent.left) - + (rcDialog.right - rcDialog.left)) / 2 + rcParent.left - rcDialog.left; + + dy = ((rcParent.bottom - rcParent.top) - + (rcDialog.bottom - rcDialog.top)) / 2 + rcParent.top - rcDialog.top; + + OffsetRect(&rcDialog, dx, dy); + + SetWindowPos(hDlg, NULL, rcDialog.left, rcDialog.top, 0, 0, + SWP_NOSIZE | SWP_NOZORDER); +} + +/* + * spell_dlg_on_command() - Handle the WM_COMMAND stuff for the spell dialog. + */ +static void +spell_dlg_on_command(HWND hDlg, int id, HWND hwndCtl, UINT codeNotify) +{ + WORD_INFO *word_info = + (WORD_INFO *)(LONG_PTR)GetWindowLongPtr(hDlg, GWLP_USERDATA); + + switch(id) + { + case ID_COMBO_SUGGESTIONS: + if(codeNotify == CBN_EDITCHANGE) + handle_cb_editchange(hDlg, hwndCtl); + break; + + case ID_BUTTON_CHANGE: + handle_button_change(hDlg, word_info, 0); + break; + + case ID_BUTTON_CHANGEALL: + handle_button_change(hDlg, word_info, 1); + break; + + case ID_BUTTON_IGNOREONCE: + spell_dlg_next_word(hDlg, word_info); + break; + + case ID_BUTTON_IGNOREALL: + speller_ignore_all(word_info->aspellinfo, word_info->word_utf8, -1); + spell_dlg_next_word(hDlg, word_info); + break; + + case ID_BUTTON_ADD_TO_DICT: + speller_add_to_dictionary(word_info->aspellinfo, word_info->word_utf8, -1); + spell_dlg_next_word(hDlg, word_info); + break; + + case ID_BUTTON_CANCEL: + EndDialog(hDlg, 0); + break; + } +} + +/* + * spell_dlg_proc() - Main spell dialog proc. + */ +static INT_PTR CALLBACK +spell_dlg_proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + WORD_INFO *word_info; + + switch(message) + { + case WM_INITDIALOG: + center_dialog(hDlg); + + // Limit how much junk they can type in. + ComboBox_LimitText(GetDlgItem(hDlg, ID_COMBO_SUGGESTIONS), 128); + + word_info = (WORD_INFO *)lParam; + SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)word_info); + + spell_dlg_proc_set_word(hDlg, word_info); + return TRUE; + + case WM_COMMAND: + HANDLE_WM_COMMAND(hDlg, wParam, lParam, spell_dlg_on_command); + return TRUE; + } + + return FALSE; +} + +/* + * get_next_word() - Get the next word from dot. And skip words in + * quoted lines. + */ +static int +get_next_word(WORD_INFO *word_info) +{ + // Skip quoted lines + while(lgetc(curwp->w_dotp, 0).c == '>') + { + if(curwp->w_dotp == curbp->b_linep) + return 0; + + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_doto = 0; + curwp->w_flag |= WFMOVE; + } + + // Move to a word. + while(inword() == FALSE) + { + if(forwchar(FALSE, 1) == FALSE) + { + // There is no next word. + return 0; + } + } + + // If mark is currently set, clear it. + if(curwp->w_markp) + setmark(0, 1); + + // Set mark to beginning of this word. + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = curwp->w_doto; + + // Get word length + word_info->word_doto = curwp->w_doto; + word_info->word_dotp = curwp->w_dotp; + word_info->word_size = 0; + + while((inword() != FALSE) && (word_info->word_dotp == curwp->w_dotp)) + { + word_info->word_size++; + + if(forwchar(FALSE, 1) == FALSE) + break; + } + + word_info->word_utf8 = ucs4_to_utf8_cpystr_n( + (UCS *)&word_info->word_dotp->l_text[word_info->word_doto], + word_info->word_size); + return 1; +} + +/* + * get_next_word() - free the word_info members word_utf8 and word_lptstr. + */ +static void +free_word_info_words(WORD_INFO *word_info) +{ + if(word_info->word_utf8) + { + fs_give((void **)&word_info->word_utf8); + word_info->word_utf8 = NULL; + } + + if(word_info->word_lptstr) + { + fs_give((void **)&word_info->word_lptstr); + word_info->word_lptstr = NULL; + } +} + +/* + * get_next_bad_word() - Search from '.' for the next word we think is + * rotten. Mark it and highlight it. + */ +static int +get_next_bad_word(WORD_INFO *word_info) +{ + free_word_info_words(word_info); + + while(get_next_word(word_info)) + { + int ret; + + /* pass over words that contain @ */ + if(strchr(word_info->word_utf8, '@')) + ret = 1; + else + ret = speller_check_word(word_info->aspellinfo, word_info->word_utf8, -1); + + if(ret == -1) + { + word_info->utf8_err_message = + speller_get_error_message(word_info->aspellinfo); + if(!word_info->utf8_err_message) + word_info->utf8_err_message = _("Spelling: speller_check_word() failed"); + return 0; + } + else if(ret == 0) + { + // Highlight word. + update(); + + word_info->word_lptstr = utf8_to_lptstr(word_info->word_utf8); + return 1; + } + + free_word_info_words(word_info); + } + + return 0; +} diff --git a/pico/osdep/mswin_spell.h b/pico/osdep/mswin_spell.h new file mode 100644 index 00000000..bfa78116 --- /dev/null +++ b/pico/osdep/mswin_spell.h @@ -0,0 +1,9 @@ +#define DLG_SPELL 300 + +#define ID_COMBO_SUGGESTIONS 100 +#define ID_BUTTON_CHANGE IDOK +#define ID_BUTTON_CHANGEALL 101 +#define ID_BUTTON_IGNOREONCE 102 +#define ID_BUTTON_IGNOREALL 103 +#define ID_BUTTON_ADD_TO_DICT 104 +#define ID_BUTTON_CANCEL IDCANCEL diff --git a/pico/osdep/mswin_tw.c b/pico/osdep/mswin_tw.c new file mode 100644 index 00000000..663c8141 --- /dev/null +++ b/pico/osdep/mswin_tw.c @@ -0,0 +1,765 @@ +/* + * ======================================================================== + * 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 + * + * ======================================================================== + */ +#define STRICT +#define UNICODE +#define _UNICODE +#include +#include +#include + +/* + article entitled "About Rich Edit Controls" here (remove all spaces from url): + http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/ + platform/commctls/richedit/richeditcontrols/aboutricheditcontrols.asp + + Rich Edit version DLL + 1.0 Riched32.dll + 2.0 Riched20.dll + 3.0 Riched20.dll + 4.1 Msftedit.dll + + The following list describes which versions of Rich Edit are included in + which releases of Microsoft Windows. + Windows XP SP1 Includes Rich Edit 4.1, Rich Edit 3.0, and a Rich Edit 1.0 emulator. + Windows XP Includes Rich Edit 3.0 with a Rich Edit 1.0 emulator. + Windows Me Includes Rich Edit 1.0 and 3.0. + Windows 2000 Includes Rich Edit 3.0 with a Rich Edit 1.0 emulator. + Windows NT 4.0 Includes Rich Edit 1.0 and 2.0. + Windows 98 Includes Rich Edit 1.0 and 2.0. + Windows 95 Includes only Rich Edit 1.0. However, Riched20.dll is + compatible with Windows 95 and may be installed by an + application that requires it. + + We're using richedit v2 since it is the first to have Unicode support. Does + potentially limit us to Win98 unless we install riched20.dll or it's + already there. + */ +#define _RICHEDIT_VER 0x0200 +#include +#include "resource.h" + +#include "mswin_tw.h" + +/* + * Globals + */ +static const TCHAR g_mswin_tw_class_name[] = TEXT("PineTWClass"); + +// Maximum amount of text allowed in these textwindows. +// Set via a EM_EXLIMITTEXT message. +static const LPARAM g_max_text = 8 * 1024 * 1024 - 1; + +/* + * Function prototypes + */ +static LRESULT CALLBACK mswin_tw_wndproc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); +static UINT Edit_ExSetSel(HWND hwnd_edit, LONG cpMin, LONG cpMax); +static UINT Edit_ExGetTextLen(HWND hwnd_edit, DWORD flags); +static BOOL Edit_ExIsReadOnly(HWND hwnd_edit); + +/* + * mswin_tw_create() - Create a mswin textwindow. + */ +int +mswin_tw_create(MSWIN_TEXTWINDOW *mswin_tw, LPCTSTR title) +{ + int width, height; + static int s_mswin_tw_class_registered = 0; + + mswin_tw->hwnd = NULL; + mswin_tw->hwnd_edit = NULL; + + if(!s_mswin_tw_class_registered) + { + WNDCLASS wndclass; + + LoadLibrary(TEXT("riched20.dll")); + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.style = CS_BYTEALIGNWINDOW; + wndclass.lpfnWndProc = mswin_tw_wndproc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = sizeof(ULONG_PTR); + wndclass.hInstance = mswin_tw->hInstance ; + wndclass.hIcon = LoadIcon (mswin_tw->hInstance, MAKEINTRESOURCE( ALPINEICON)); + wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); + wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wndclass.lpszMenuName = MAKEINTRESOURCE(TEXTWINMENU); + wndclass.lpszClassName = g_mswin_tw_class_name ; + + RegisterClass(&wndclass); + + s_mswin_tw_class_registered = 1; + } + + // If we're passed CW_USEDEFAULT as the position, then use right/bottom + // as the width/height. Otherwise use the full rect. + width = (mswin_tw->rcSize.left == CW_USEDEFAULT) ? + mswin_tw->rcSize.right : mswin_tw->rcSize.right - mswin_tw->rcSize.left; + height = (mswin_tw->rcSize.top == CW_USEDEFAULT) ? + mswin_tw->rcSize.bottom : mswin_tw->rcSize.bottom - mswin_tw->rcSize.top; + + mswin_tw->hwnd = CreateWindow( + g_mswin_tw_class_name, title, + WS_OVERLAPPEDWINDOW, + mswin_tw->rcSize.left, + mswin_tw->rcSize.top, + width, + height, + HWND_DESKTOP, NULL, + mswin_tw->hInstance, mswin_tw); + if(!mswin_tw->hwnd) + return 0; + + return 1; +} + +/* + * mswin_tw_close() - close the mswin textwindow. + */ +void +mswin_tw_close(MSWIN_TEXTWINDOW *mswin_tw) +{ + if(mswin_tw && mswin_tw->hwnd) + DestroyWindow(mswin_tw->hwnd); +} + +/* + * EditStreamOutCallback - callback for mswin_tw_write_to_file. + */ +static DWORD CALLBACK +EditStreamOutCallback(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, PLONG pcb) +{ + HANDLE hFile = (HANDLE)dwCookie; + return !WriteFile(hFile, lpBuff, cb, (DWORD *)pcb, NULL); +} + +/* + * mswin_tw_fill_from_file() - write textwindow contents to a file. + */ +BOOL +mswin_tw_write_to_file(MSWIN_TEXTWINDOW *mswin_tw, LPCTSTR file) +{ + BOOL fSuccess = FALSE; + HANDLE hFile = CreateFile(file, GENERIC_WRITE, 0, + 0, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + if(hFile != INVALID_HANDLE_VALUE) + { + EDITSTREAM es; + + es.dwCookie = (DWORD_PTR)hFile; + es.dwError = 0; + es.pfnCallback = EditStreamOutCallback; + + if(SendMessage(mswin_tw->hwnd_edit, EM_STREAMOUT, SF_TEXT, (LPARAM)&es) && + es.dwError == 0) + { + fSuccess = TRUE; + } + } + + CloseHandle(hFile); + return fSuccess; +} + + +#ifdef ALTED_DOT + +/* + * EditStreamCallback - callback for mswin_tw_fill_from_file. + */ +static DWORD CALLBACK +EditStreamInCallback(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, PLONG pcb) +{ + HANDLE hFile = (HANDLE)dwCookie; + return !ReadFile(hFile, lpBuff, cb, (DWORD *)pcb, NULL); +} + + +/* + * mswin_tw_fill_from_file() - read file contents into the mswin textwindow. + */ +BOOL +mswin_tw_fill_from_file(MSWIN_TEXTWINDOW *mswin_tw, LPCTSTR file) +{ + BOOL fSuccess = FALSE; + HANDLE hFile = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, + 0, OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if(hFile != INVALID_HANDLE_VALUE) + { + EDITSTREAM es; + + es.dwCookie = (DWORD_PTR)hFile; + es.dwError = 0; + es.pfnCallback = EditStreamInCallback; + + if(SendMessage(mswin_tw->hwnd_edit, EM_STREAMIN, SF_TEXT, (LPARAM)&es) && + es.dwError == 0) + { + fSuccess = TRUE; + } + } + + CloseHandle(hFile); + return fSuccess; +} + +#endif /* ALTED_DOT */ + +/* + * mswin_tw_showwindow() - show the main hwnd (SW_SHOWNORMAL, etc.). + */ +void +mswin_tw_showwindow(MSWIN_TEXTWINDOW *mswin_tw, int nCmdShow) +{ + if(mswin_tw && mswin_tw->hwnd) + ShowWindow(mswin_tw->hwnd, nCmdShow); +} + +/* + * mswin_tw_setfont() - Sets the hfont for the entire edit control. + */ +void +mswin_tw_setfont(MSWIN_TEXTWINDOW *mswin_tw, HFONT hfont) +{ + if(mswin_tw && mswin_tw->hwnd_edit) + SetWindowFont(mswin_tw->hwnd_edit, hfont, TRUE); +} + +/* + * mswin_tw_setcolor() - Set colors for entire edit control. If we're + * passed -1 for mswin_tw, then set the colors for all textwindows. + */ +void +mswin_tw_setcolor(MSWIN_TEXTWINDOW *mswin_tw, + COLORREF TextColor, COLORREF BackColor) +{ + if(mswin_tw == (MSWIN_TEXTWINDOW *)-1) + { + HWND hwnd = NULL; + + while(hwnd = FindWindowEx(NULL, hwnd, g_mswin_tw_class_name, NULL)) + { + mswin_tw = (MSWIN_TEXTWINDOW *)(LONG_PTR)GetWindowLongPtr( + hwnd, GWLP_USERDATA); + if(mswin_tw) + { + mswin_tw_setcolor(mswin_tw, TextColor, BackColor); + } + } + } + else if(mswin_tw && mswin_tw->hwnd_edit) + { + CHARFORMAT2W cf2; + + memset(&cf2, 0, sizeof(cf2)); + cf2.cbSize = sizeof(cf2); + cf2.dwMask = CFM_COLOR; + cf2.crTextColor = TextColor; + + SendMessage(mswin_tw->hwnd_edit, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&cf2); + SendMessage(mswin_tw->hwnd_edit, EM_SETBKGNDCOLOR, 0, BackColor); + } +} + +/* + * mswin_tw_puts_lptstr() - Stuffs a LPTSTR string at the end of our edit + * control. + */ +int +mswin_tw_puts_lptstr(MSWIN_TEXTWINDOW *mswin_tw, LPTSTR msg) +{ + if(mswin_tw && mswin_tw->hwnd_edit) + { + TCHAR lf_str[] = TEXT("\n"); + + Edit_ExSetSel(mswin_tw->hwnd_edit, -1, -1); + Edit_ReplaceSel(mswin_tw->hwnd_edit, msg); + + Edit_ReplaceSel(mswin_tw->hwnd_edit, lf_str); + + Edit_ScrollCaret(mswin_tw->hwnd_edit); + return 1; + } + + return 0; +} + +/* + * mswin_tw_printf() - printf version of mswin_tw_puts_lptstr. + */ +int +mswin_tw_printf(MSWIN_TEXTWINDOW *mswin_tw, LPCTSTR fmt, ...) +{ + TCHAR msg[1024]; + va_list vlist; + + va_start(vlist, fmt); + _vsntprintf(msg, ARRAYSIZE(msg), fmt, vlist); + va_end(vlist); + + msg[ARRAYSIZE(msg) - 1] = 0; + return mswin_tw_puts_lptstr(mswin_tw, msg); +} + +/* + * mswin_tw_gettextlength() - Returns the number of TCHARs in the edit control. + */ +UINT +mswin_tw_gettextlength(MSWIN_TEXTWINDOW *mswin_tw) +{ + if(mswin_tw && mswin_tw->hwnd_edit) + return (UINT)Edit_ExGetTextLen(mswin_tw->hwnd_edit, GTL_USECRLF); + + return 0; +} + +/* + * mswin_tw_gettext() - Return value is the number of TCHARs copied into the + * output buffer. + */ +UINT +mswin_tw_gettext(MSWIN_TEXTWINDOW *mswin_tw, LPTSTR lptstr_ret, int lptstr_len) +{ + if(mswin_tw && mswin_tw->hwnd_edit) + { + GETTEXTEX gt; + LRESULT lresult; + + gt.cb = lptstr_len * sizeof(TCHAR); + gt.flags = GT_DEFAULT | GT_USECRLF; + gt.codepage = 1200; + gt.lpDefaultChar = NULL; + gt.lpUsedDefChar = NULL; + + lptstr_ret[0] = 0; + lresult = SendMessage(mswin_tw->hwnd_edit, EM_GETTEXTEX, + (WPARAM)>, (LPARAM)lptstr_ret); + return (int)lresult; + } + + return 0; +} + +/* + * mswin_tw_setsel() - Set edit control selection. + */ +void +mswin_tw_setsel(MSWIN_TEXTWINDOW *mswin_tw, LONG min, LONG max) +{ + if(mswin_tw && mswin_tw->hwnd_edit) + { + Edit_ExSetSel(mswin_tw->hwnd_edit, min, max); + } +} + +/* + * mswin_set_readonly() - Set readonly status. + */ +void +mswin_set_readonly(MSWIN_TEXTWINDOW *mswin_tw, BOOL read_only) +{ + if(mswin_tw && mswin_tw->hwnd_edit) + Edit_SetReadOnly(mswin_tw->hwnd_edit, read_only); +} + +/* + * mswin_tw_clear() - Clear all text from edit control. + */ +void +mswin_tw_clear(MSWIN_TEXTWINDOW *mswin_tw) +{ + if(mswin_tw && mswin_tw->hwnd_edit) + { + SETTEXTEX stex; + + stex.flags = ST_DEFAULT; + stex.codepage = 1200; // Unicode (see richedit.h) + + SendMessage(mswin_tw->hwnd_edit, EM_SETTEXTEX, + (WPARAM)&stex, (LPARAM)TEXT("")); + + if(mswin_tw->clear_callback) + mswin_tw->clear_callback(mswin_tw); + } +} + +/* + * MySetWindowLongPtr() - Little wrapper routine which calls the + * Windows SetWindowLongPtr() and removes the stupid warning which is + * just seriously lame. + */ +static LONG_PTR +MySetWindowLongPtr(HWND hwnd, int nIndex, void *NewLongPtr) +{ +// warning C4244: 'function': conversion from 'LONG_PTR' to 'LONG', +// possible loss of data +#pragma warning(push) +#pragma warning(disable: 4244) + return SetWindowLongPtr(hwnd, nIndex, (LONG_PTR)NewLongPtr); +#pragma warning(pop) +} + +/* + * mswin_tw_wm_command() - WM_CONTEXTMENU handler for textwindows + */ +static void +mswin_tw_wm_contextmenu(MSWIN_TEXTWINDOW *mswin_tw, HWND hwnd, HWND hwndContext, + int xPos, int yPos) +{ + HMENU hMenu; + + hMenu = CreatePopupMenu(); + if(hMenu) + { + int i; + CHARRANGE cr; + MENUITEMINFO mitem; + static const struct + { + UINT wID; + LPTSTR dwTypeData; + } s_popup_menu[] = + { + { IDM_EDIT_COPY, TEXT("&Copy") }, + { IDM_EDIT_COPY_APPEND, TEXT("Copy &Append") }, + { IDM_EDIT_CLEAR, TEXT("Clea&r") }, + }; + + memset(&mitem, 0, sizeof(MENUITEMINFO)); + mitem.cbSize = sizeof(MENUITEMINFO); + mitem.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; + mitem.fType = MFT_STRING; + + SendMessage(mswin_tw->hwnd_edit, EM_EXGETSEL, (WPARAM)0, (LPARAM)&cr); + + for(i = 0; i < ARRAYSIZE(s_popup_menu); i++) + { + switch(s_popup_menu[i].wID) + { + case IDM_EDIT_CLEAR: + // Only enable it if there is a clear callback set. + mitem.fState = (mswin_tw->clear_callback) ? + MFS_ENABLED : MFS_GRAYED; + break; + case IDM_EDIT_COPY: + case IDM_EDIT_COPY_APPEND: + // Only enable if there is a selection. + mitem.fState = (cr.cpMax > cr.cpMin) ? + MFS_ENABLED : MFS_GRAYED; + break; + default: + mitem.fState = MFS_ENABLED; + break; + } + + mitem.wID = s_popup_menu[i].wID; + mitem.dwTypeData = s_popup_menu[i].dwTypeData; + mitem.cch = (UINT)_tcslen(s_popup_menu[i].dwTypeData); + InsertMenuItem(hMenu, i, FALSE, &mitem); + } + + TrackPopupMenu(hMenu, + TPM_LEFTALIGN | TPM_TOPALIGN | + TPM_RIGHTBUTTON | TPM_LEFTBUTTON, + xPos, yPos, 0, hwnd, NULL); + + DestroyMenu(hMenu); + } +} + +/* + * mswin_tw_wm_command() - WM_COMMAND handler for textwindows + */ +static void +mswin_tw_wm_command(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) +{ + MSWIN_TEXTWINDOW *mswin_tw; + + mswin_tw = (MSWIN_TEXTWINDOW *)(LONG_PTR)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch(id) + { + case IDM_FILE_CLOSE: + DestroyWindow(hwnd); + break; + + case IDM_FILE_PRINT: + if(mswin_tw->print_callback) + mswin_tw->print_callback(mswin_tw); + break; + + case IDM_EDIT_COPY: + SendMessage(mswin_tw->hwnd_edit, WM_COPY, 0, 0); + break; + + case IDM_EDIT_CLEAR: + mswin_tw_clear(mswin_tw); + break; + + case IDM_EDIT_COPY_APPEND: + { + CHARRANGE cr; + int text_len0, text_len1; + HWND hwnd_edit = mswin_tw->hwnd_edit; + BOOL EditIsReadOnly = Edit_ExIsReadOnly(hwnd_edit); + + SetWindowRedraw(hwnd_edit, FALSE); + Edit_SetReadOnly(hwnd_edit, FALSE); + + // Get current selection. + SendMessage(hwnd_edit, EM_EXGETSEL, (WPARAM)0, (LPARAM)&cr); + + // Get current length. + text_len0 = Edit_ExGetTextLen(hwnd_edit, 0); + + // Paste current clip right before our new selection. + Edit_ExSetSel(hwnd_edit, cr.cpMin, cr.cpMin); + SendMessage(hwnd_edit, WM_PASTE, 0, 0); + + // Get new length. + text_len1 = Edit_ExGetTextLen(hwnd_edit, 0); + + // Select new and old clip and copy em. + Edit_ExSetSel(hwnd_edit, cr.cpMin, cr.cpMax + text_len1 - text_len0); + SendMessage(hwnd_edit, WM_COPY, 0, 0); + + // Undo our paste and restore original selection. + SendMessage(hwnd_edit, WM_UNDO, 0, 0); + SendMessage(hwnd_edit, EM_EXSETSEL, (WPARAM)0, (LPARAM)&cr); + + // Set back to read only. + Edit_SetReadOnly(hwnd_edit, EditIsReadOnly); + SetWindowRedraw(hwnd_edit, TRUE); + break; + } + + case IDM_EDIT_SEL_ALL: + Edit_ExSetSel(mswin_tw->hwnd_edit, 0, -1); + break; + } +} + +/* + * mswin_tw_wm_notify() - WM_NOTIFY handler for textwindows + */ +static LRESULT +mswin_tw_wm_notify(HWND hwnd, int idCtrl, NMHDR *nmhdr) +{ + HWND hwnd_edit = nmhdr->hwndFrom; + + if(nmhdr->code == EN_MSGFILTER) + { + MSGFILTER *msg_filter = (MSGFILTER *)nmhdr; + + if(msg_filter->msg == WM_KEYDOWN) + { + if(msg_filter->wParam == 'E') + { + int control_down = GetKeyState(VK_CONTROL) < 0; + int shift_down = GetKeyState(VK_SHIFT) < 0; + + // Ctrl+Shift+E toggles the readonly attribute on the text + // buffer. + if(control_down && shift_down) + Edit_SetReadOnly(hwnd_edit, !Edit_ExIsReadOnly(hwnd_edit)); + return TRUE; + } + } + else if(msg_filter->msg == WM_CHAR) + { + // Only override these keys if this buffer is readonly. + if(Edit_ExIsReadOnly(hwnd_edit)) + { + switch(msg_filter->wParam) + { + case 'k': + SendMessage(hwnd_edit, EM_SCROLL, SB_LINEUP, 0); + return TRUE; + case 'j': + SendMessage(hwnd_edit, EM_SCROLL, SB_LINEDOWN, 0); + return TRUE; + case '-': + case 'b': + SendMessage(hwnd_edit, EM_SCROLL, SB_PAGEUP, 0); + return TRUE; + case ' ': + case 'f': + SendMessage(hwnd_edit, EM_SCROLL, SB_PAGEDOWN, 0); + return TRUE; + } + } + } + } + else if(nmhdr->code == EN_LINK) + { + ENLINK *enlink = (ENLINK *)nmhdr; + + if(enlink->msg == WM_LBUTTONDOWN) + { + TEXTRANGE tr; + TCHAR link_buf[1024]; + + link_buf[0] = 0; + tr.lpstrText = link_buf; + + tr.chrg = enlink->chrg; + if(tr.chrg.cpMax - tr.chrg.cpMin > ARRAYSIZE(link_buf)) + tr.chrg.cpMax = tr.chrg.cpMin + ARRAYSIZE(link_buf); + + SendMessage(hwnd_edit, EM_GETTEXTRANGE, 0, (LPARAM)&tr); + ShellExecute(hwnd, TEXT("Open"), link_buf, NULL, NULL, SW_SHOWNORMAL); + return TRUE; + } + } + + return FALSE; +} + +/* + * mswin_tw_wndproc() - Main window proc for mswin textwindows. + */ +static LRESULT CALLBACK +mswin_tw_wndproc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) +{ + MSWIN_TEXTWINDOW *mswin_tw; + + mswin_tw = (MSWIN_TEXTWINDOW *)(LONG_PTR)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch(msg) + { + case WM_CREATE: + { + CREATESTRUCT *pcs = (CREATESTRUCT *)lp; + + mswin_tw = (MSWIN_TEXTWINDOW *)pcs->lpCreateParams; + + MySetWindowLongPtr(hwnd, GWLP_USERDATA, mswin_tw); + + mswin_tw->hwnd_edit = CreateWindowEx( + WS_EX_CLIENTEDGE, RICHEDIT_CLASS, 0, + WS_VISIBLE | WS_CHILD | + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | + WS_VSCROLL | WS_HSCROLL | + ES_MULTILINE | ES_READONLY | ES_NOHIDESEL, + 0, 0, 1, 1, + hwnd, 0, mswin_tw->hInstance, 0); + + // We want link and key event notifications. + SendMessage(mswin_tw->hwnd_edit, EM_SETEVENTMASK, + 0, (ENM_KEYEVENTS | ENM_LINK)); + + // Specifies the maximum amount of text that can be entered. + SendMessage(mswin_tw->hwnd_edit, EM_EXLIMITTEXT, 0, g_max_text); + + // Enable automatic detection of URLs by our rich edit control. + SendMessage(mswin_tw->hwnd_edit, EM_AUTOURLDETECT, TRUE, 0); + break; + } + + case WM_CONTEXTMENU: + mswin_tw_wm_contextmenu(mswin_tw, hwnd, (HWND)wp, GET_X_LPARAM(lp), GET_Y_LPARAM(lp)); + break; + + case WM_NOTIFY: + return mswin_tw_wm_notify(hwnd, (int)wp, (NMHDR *)lp); + + case WM_COMMAND: + HANDLE_WM_COMMAND(hwnd, wp, lp, mswin_tw_wm_command); + break; + + case WM_SETFOCUS: + SetFocus(mswin_tw->hwnd_edit); + return TRUE; + + case WM_SIZE: + MoveWindow(mswin_tw->hwnd_edit, 0, 0, LOWORD(lp), HIWORD(lp), TRUE); + break; + + case WM_WINDOWPOSCHANGED: + if(!IsIconic(hwnd)) + { + WINDOWPOS *wpos = (WINDOWPOS *)lp; + + mswin_tw->rcSize.left = wpos->x; + mswin_tw->rcSize.top = wpos->y; + mswin_tw->rcSize.right = wpos->x + wpos->cx; + mswin_tw->rcSize.bottom = wpos->y + wpos->cy; + } + break; + + case WM_DESTROY: + if(mswin_tw->out_file) + { + mswin_tw->out_file_ret = mswin_tw_write_to_file(mswin_tw, + mswin_tw->out_file); + } + + mswin_tw->hwnd = NULL; + mswin_tw->hwnd_edit = NULL; + + if(mswin_tw->close_callback) + mswin_tw->close_callback(mswin_tw); + return TRUE; + + default: + break; + } + + return DefWindowProc(hwnd, msg, wp, lp); +} + +/* + * Edit_ExGetTextLen() - Helper routine for getting count of chars. + */ +static UINT +Edit_ExGetTextLen(HWND hwnd_edit, DWORD flags) +{ + GETTEXTLENGTHEX gtl; + + gtl.flags = GTL_PRECISE | GTL_NUMCHARS | flags; + gtl.codepage = 1200; // Unicode (see richedit.h) + return (UINT)SendMessage(hwnd_edit, EM_GETTEXTLENGTHEX, (WPARAM)>l, 0); +} + +/* + * Edit_ExSetSel() - Helper routine for setting edit selection. + */ +static UINT +Edit_ExSetSel(HWND hwnd_edit, LONG cpMin, LONG cpMax) +{ + CHARRANGE cr; + + if(cpMin == -1) + cpMin = Edit_ExGetTextLen(hwnd_edit, 0); + + cr.cpMin = cpMin; + cr.cpMax = cpMax; + return (UINT)SendMessage(hwnd_edit, EM_EXSETSEL, (WPARAM)0, (LPARAM)&cr); +} + +/* + * Edit_ExIsReadOnly() - TRUE if edit buffer is read only. + */ +static BOOL +Edit_ExIsReadOnly(HWND hwnd_edit) +{ + LRESULT edit_opts; + + edit_opts = SendMessage(hwnd_edit, EM_GETOPTIONS, 0, 0); + return !!(edit_opts & ECO_READONLY); +} + diff --git a/pico/osdep/mswin_tw.h b/pico/osdep/mswin_tw.h new file mode 100644 index 00000000..93eb7b76 --- /dev/null +++ b/pico/osdep/mswin_tw.h @@ -0,0 +1,58 @@ +/* + * ======================================================================== + * 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 + * + * ======================================================================== + */ +typedef struct MSWIN_TEXTWINDOW MSWIN_TEXTWINDOW; +typedef void (MSWIN_TW_CALLBACK)(MSWIN_TEXTWINDOW *mswin_tw); + +typedef struct MSWIN_TEXTWINDOW +{ + // User should set these fields before calling mswin_tw_create() + UINT id; // Caller can use to determine which + // textwindow this is (Ie: IDM_OPT_NEWMAILWIN) + HANDLE hInstance; + RECT rcSize; // Window position + + MSWIN_TW_CALLBACK *print_callback; // Print menu selected callback routine. + MSWIN_TW_CALLBACK *close_callback; // Callback for when window is closed. + MSWIN_TW_CALLBACK *clear_callback; // Callback after text is cleared. + + LPCTSTR out_file; // Save edit contents on close to this file. + BOOL out_file_ret; // TRUE - out_file written, FALSE - nope. + + // internal fields + HWND hwnd; // hwnd for this mswin textwindow. + HWND hwnd_edit; +} MSWIN_TEXTWINDOW; + +int mswin_tw_create(MSWIN_TEXTWINDOW *mswin_tw, LPCTSTR title); +void mswin_tw_close(MSWIN_TEXTWINDOW *mswin_tw); + +void mswin_tw_showwindow(MSWIN_TEXTWINDOW *mswin_tw, int nCmdShow); + +BOOL mswin_tw_fill_from_file(MSWIN_TEXTWINDOW *mswin_tw, LPCTSTR file); +BOOL mswin_tw_write_to_file(MSWIN_TEXTWINDOW *mswin_tw, LPCTSTR file); + +void mswin_tw_setfont(MSWIN_TEXTWINDOW *mswin_tw, HFONT hfont); +void mswin_tw_setcolor(MSWIN_TEXTWINDOW *mswin_tw, + COLORREF TextColor, COLORREF BackColor); + +int mswin_tw_puts_lptstr(MSWIN_TEXTWINDOW *mswin_tw, LPTSTR msg); +int mswin_tw_printf(MSWIN_TEXTWINDOW *mswin_tw, LPCTSTR fmt, ...); + +UINT mswin_tw_gettext(MSWIN_TEXTWINDOW *mswin_tw, LPTSTR lptstr_ret, int lptstr_len); +UINT mswin_tw_gettextlength(MSWIN_TEXTWINDOW *mswin_tw); + +void mswin_tw_setsel(MSWIN_TEXTWINDOW *mswin_tw, LONG min, LONG max); +void mswin_tw_clear(MSWIN_TEXTWINDOW *mswin_tw); + +void mswin_set_readonly(MSWIN_TEXTWINDOW *mswin_tw, BOOL read_only); diff --git a/pico/osdep/mswinhnd.cur b/pico/osdep/mswinhnd.cur new file mode 100644 index 00000000..253c3d33 Binary files /dev/null and b/pico/osdep/mswinhnd.cur differ diff --git a/pico/osdep/newmail.c b/pico/osdep/newmail.c new file mode 100644 index 00000000..f900a634 --- /dev/null +++ b/pico/osdep/newmail.c @@ -0,0 +1,66 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: newmail.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 +#include + +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../edef.h" +#include "../efunc.h" +#include "../keydefs.h" + +#include "../../pith/charconv/filesys.h" + +#include "newmail.h" + + + + +/* + * pico_new_mail - just checks mtime and atime of mail file and notifies user + * if it's possible that they have new mail. + */ +int +pico_new_mail(void) +{ +#ifndef _WINDOWS + int ret = 0; + static time_t lastchk = 0; + struct stat sbuf; + char inbox[256], *p; + + if((p = (char *)getenv("MAIL")) != NULL) + /* fix unsafe sprintf - noticed by petter wahlman */ + snprintf(inbox, sizeof(inbox), "%s", p); + else + snprintf(inbox, sizeof(inbox), "%s/%s", MAILDIR, (char *) getlogin()); + + if(our_stat(inbox, &sbuf) == 0){ + ret = sbuf.st_atime <= sbuf.st_mtime && + (lastchk < sbuf.st_mtime && lastchk < sbuf.st_atime); + lastchk = sbuf.st_mtime; + return(ret); + } + else + return(ret); +#else /* _WINDOWS */ + return(0); +#endif +} diff --git a/pico/osdep/newmail.h b/pico/osdep/newmail.h new file mode 100644 index 00000000..5af645e1 --- /dev/null +++ b/pico/osdep/newmail.h @@ -0,0 +1,25 @@ +/* + * $Id: newmail.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 PICO_OSDEP_NEWMAIL_INCLUDED +#define PICO_OSDEP_NEWMAIL_INCLUDED + + +/* exported prototypes */ +int pico_new_mail(void); + + +#endif /* PICO_OSDEP_NEWMAIL_INCLUDED */ diff --git a/pico/osdep/os-win.h b/pico/osdep/os-win.h new file mode 100644 index 00000000..2ececda1 --- /dev/null +++ b/pico/osdep/os-win.h @@ -0,0 +1,183 @@ +#ifndef _PICO_OS_INCLUDED +#define _PICO_OS_INCLUDED + + +/*---------------------------------------------------------------------- + + OS dependencies, WIN version. See also the os-win.c files. + The following stuff may need to be changed for a new port, but once + the port is done it won't change. Further down in the file are a few + constants that you may want to configure differently than they + are configured, but probably not. + + ----*/ + + + +/*----------------- Are we ANSI? ---------------------------------------*/ +#define ANSI /* this is an ANSI compiler */ + +/*------ If our compiler doesn't understand type void ------------------*/ +/* #define void char */ + +/*-------- Standard ANSI functions usually defined in stdlib.h ---------*/ +#include +#include +#include +#include +#include +#undef CTRL +#include +#include + + +/*-------- Standard windows defines and then our window module defines. */ +#include +#include +#include +#ifndef WIN32 +#include +#include +#endif +#include +#include +#include + +#include "mswin.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Windows only version and resource defines. */ +#include "resource.h" + + +#undef ERROR + + +/*---- Declare sys_errlist() if not already declared -------------------*/ +/* extern char *sys_errlist[]; */ + + + +/*----------------- locale.h -------------------------------------------*/ +/* #include */ /* To make matching and sorting work right */ +#define collator strucmp + + + +/*----------------- time.h ---------------------------------------------*/ +#include +/* plain time.h isn't enough on some systems */ +/* #include */ /* For struct timeval usually in time.h */ + + + +/*--------------- signal.h ---------------------------------------------*/ +#include +/* #include */ + +#define SigType void /* value returned by sig handlers is void */ +/* #define SigType int */ /* value returned by sig handlers is int */ + +/* #define POSIX_SIGNALS */ +/* #define SYSV_SIGNALS */ /* use System-V signal semantics (ttyin.c) */ + +#define SIG_PROTO(args) args + + + +/*-------------- A couple typedef's for integer sizes ------------------*/ +typedef unsigned long usign32_t; +typedef unsigned short usign16_t; + + + +/*-------------- qsort argument type -----------------------------------*/ +#define QSType void +/* #define QSType char */ + +#define QcompType const void + + +/*-------- Is window resizing available? -------------------------------*/ +/* #define RESIZING */ + /* Actually, under windows it is, but RESIZING compiles in UNIX + * signals code for determining when the window resized. Window's + * works differently. */ + + + + +/*-------- If no vfork, use regular fork -------------------------------*/ +/* #define vfork fork */ + + + +/*---- When no screen size can be discovered this is the size used -----*/ +#define DEFAULT_LINES_ON_TERMINAL (25) +#define DEFAULT_COLUMNS_ON_TERMINAL (80) +#define NROW DEFAULT_LINES_ON_TERMINAL +#define NCOL DEFAULT_COLUMNS_ON_TERMINAL + + + +/* + * File name separators, char and string + */ +#define C_FILESEP '\\' +#define S_FILESEP "\\" + + +/* + * What and where the tool that checks spelling is located. If this is + * undefined, then the spelling checker is not compiled into pico. + */ +#define SPELLER + + +/* + * Mode passed chmod() to make tmp files exclusively user read/write-able + */ +/*#define MODE_READONLY (S_IREAD | S_IWRITE) */ + + +#ifdef maindef +/* possible names and paths of help files under different OSs */ + +char *pathname[] = { + "picorc", + "pico.hlp", + "\\usr\\local\\", + "\\usr\\lib\\", + "" +}; + +#define NPNAMES (sizeof(pathname)/sizeof(char *)) + + +#endif + + +/* Define function that mswin.c calls back for scrolling. */ +int pico_scroll_callback (); + + +#include "mswin.h" +#include "msmenu.h" + +/* + * Make sys_errlist visible + */ +/* extern char *sys_errlist[]; */ +/* extern int sys_nerr; */ + + +#endif /* _PICO_OS_INCLUDED */ diff --git a/pico/osdep/os-wnt.h b/pico/osdep/os-wnt.h new file mode 100644 index 00000000..a1eb9c3d --- /dev/null +++ b/pico/osdep/os-wnt.h @@ -0,0 +1,181 @@ +#ifndef _PICO_OS_INCLUDED +#define _PICO_OS_INCLUDED + + +/*---------------------------------------------------------------------- + + OS dependencies, WIN NT version. See also the os-wnt.c files. + The following stuff may need to be changed for a new port, but once + the port is done it won't change. Further down in the file are a few + constants that you may want to configure differently than they + are configured, but probably not. + + ----*/ + + + +/*----------------- Are we ANSI? ---------------------------------------*/ +#define ANSI /* this is an ANSI compiler */ + +/*------ If our compiler doesn't understand type void ------------------*/ +/* #define void char */ + +/*-------- Standard ANSI functions usually defined in stdlib.h ---------*/ +#include +#include +#include +#include +#include +#undef CTRL +#include +#include + +#define _WIN32_WINNT WINVER + +/*-------- Standard windows defines and then our window module defines. */ +#include +#include +#include +#include +#include +#include + +#include "mswin.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Windows only version and resource defines. */ +#include "resource.h" + + +#undef ERROR + + +/*---- Declare sys_errlist() if not already declared -------------------*/ +/* extern char *sys_errlist[]; */ + + + +/*----------------- locale.h -------------------------------------------*/ +#include /* To make matching and sorting work right */ +#define collator strcoll + + + +/*----------------- time.h ---------------------------------------------*/ +#include +/* plain time.h isn't enough on some systems */ +/* #include */ /* For struct timeval usually in time.h */ + + + +/*--------------- signal.h ---------------------------------------------*/ +#include +/* #include */ + +#define SigType void /* value returned by sig handlers is void */ +/* #define SigType int */ /* value returned by sig handlers is int */ + +/* #define POSIX_SIGNALS */ +/* #define SYSV_SIGNALS */ /* use System-V signal semantics (ttyin.c) */ + +#define SIG_PROTO(args) args + + + +/*-------------- A couple typedef's for integer sizes ------------------*/ +typedef unsigned long usign32_t; +typedef unsigned short usign16_t; + + + +/*-------------- qsort argument type -----------------------------------*/ +#define QSType void +/* #define QSType char */ + +#define QcompType const void + + +/*-------- Is window resizing available? -------------------------------*/ +/* #define RESIZING */ + /* Actually, under windows it is, but RESIZING compiles in UNIX + * signals code for determining when the window resized. Window's + * works differently. */ + + + + +/*-------- If no vfork, use regular fork -------------------------------*/ +/* #define vfork fork */ + + + +/*---- When no screen size can be discovered this is the size used -----*/ +#define DEFAULT_LINES_ON_TERMINAL (25) +#define DEFAULT_COLUMNS_ON_TERMINAL (80) +#define NROW DEFAULT_LINES_ON_TERMINAL +#define NCOL DEFAULT_COLUMNS_ON_TERMINAL + + +#define ftruncate chsize + + +/* + * File name separators, char and string + */ +#define C_FILESEP '\\' +#define S_FILESEP "\\" + + +/* + * What and where the tool that checks spelling is located. If this is + * undefined, then the spelling checker is not compiled into pico. + */ +#define SPELLER + + +/* + * Mode passed chmod() to make tmp files exclusively user read/write-able + */ +/*#define MODE_READONLY (S_IREAD | S_IWRITE) */ + + +#ifdef maindef +/* possible names and paths of help files under different OSs */ + +char *pathname[] = { + "picorc", + "pico.hlp", + "\\usr\\local\\", + "\\usr\\lib\\", + "" +}; + +#define NPNAMES (sizeof(pathname)/sizeof(char *)) + + +#endif + + +#include "mswin.h" +#include "msmenu.h" + +/* memmove() is a built-in for DOS/Windows */ +#define bcopy(a,b,s) memmove (b, a, s) + +/* + * Make sys_errlist visible + */ +/* extern char *sys_errlist[]; */ +/* extern int sys_nerr; */ + + +#endif /* _PICO_OS_INCLUDED */ diff --git a/pico/osdep/pico.ico b/pico/osdep/pico.ico new file mode 100644 index 00000000..b78a05ad Binary files /dev/null and b/pico/osdep/pico.ico differ diff --git a/pico/osdep/popen.c b/pico/osdep/popen.c new file mode 100644 index 00000000..575929a6 --- /dev/null +++ b/pico/osdep/popen.c @@ -0,0 +1,68 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: popen.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 +#include +#include "../estruct.h" + + + + +/* + * P_open - run the given command in a sub-shell returning a file pointer + * from which to read the output + * + * note: + * For OS's other than unix, you will have to rewrite this function. + * Hopefully it'll be easy to exec the command into a temporary file, + * and return a file pointer to that opened file or something. + */ +int +P_open(char *s) +{ +#if HAVE_POPEN + extern FIOINFO g_pico_fio; + + g_pico_fio.flags = FIOINFO_READ; + g_pico_fio.name = "pipe"; + + if((g_pico_fio.fp = popen(s, "r")) != NULL) + return(FIOSUC); + + return(FIOERR); +#else + /* Windows never did this, but piping has been done elsewhere */ + return(0); +#endif +} + + + +/* + * P_close - close the given descriptor + * + */ +void +P_close(void) +{ +#if HAVE_PCLOSE + extern FIOINFO g_pico_fio; + + if(g_pico_fio.fp) + (void) pclose(g_pico_fio.fp); +#endif +} diff --git a/pico/osdep/popen.h b/pico/osdep/popen.h new file mode 100644 index 00000000..6e7f686d --- /dev/null +++ b/pico/osdep/popen.h @@ -0,0 +1,26 @@ +/* + * $Id: popen.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 PICO_OSDEP_POPEN_INCLUDED +#define PICO_OSDEP_POPEN_INCLUDED + + +/* exported prototypes */ +int P_open(char *); +void P_close(void); + + +#endif /* PICO_OSDEP_POPEN_INCLUDED */ diff --git a/pico/osdep/raw.c b/pico/osdep/raw.c new file mode 100644 index 00000000..a886e0b6 --- /dev/null +++ b/pico/osdep/raw.c @@ -0,0 +1,449 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: raw.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 + * + * ======================================================================== + */ + +/* + * TTY setup routines. + */ + +#include +#include + +#include "../estruct.h" + +#include "raw.h" + + +#if HAS_TERMIOS +/* + * These are the TERMIOS-style (POSIX) routines. + */ + +static struct termios _raw_tty, _original_tty; + +#elif HAS_TERMIO +/* + * These are the TERMIO-style (System V) routines. + */ + +static struct termio _raw_tty, _original_tty; + +#else /* RAW_BSD */ +/* + * These are for the BSD-style routines. + */ + +static struct sgttyb _raw_tty, _original_tty; +static struct ltchars _raw_ltchars, _original_ltchars; +static struct tchars _raw_tchars, _original_tchars; +static int _raw_lmode, _original_lmode; + +#endif + +/* + * current raw state + */ +static short _inraw = 0; + +/* + * Set up the tty driver + * + * Args: state -- which state to put it in. 1 means go into raw (cbreak), + * 0 out of raw. + * + * Result: returns 0 if successful and -1 if not. + */ +int +Raw(int state) +{ + /** state is either ON or OFF, as indicated by call **/ + /* Check return code only on first call. If it fails we're done for and + if it goes OK the others will probably go OK too. */ + + if(state == 0 && _inraw){ + /*----- restore state to original -----*/ + +#if HAS_TERMIOS + + if(tcsetattr(STDIN_FD, TCSADRAIN, &_original_tty) < 0) + return -1; + +#elif HAS_TERMIO + + if(ioctl(STDIN_FD, TCSETAW, &_original_tty) < 0) + return(-1); + +#else /* RAW_BSD */ + + if(ioctl(STDIN_FD, TIOCSETP, &_original_tty) < 0) + return(-1); + + (void)ioctl(STDIN_FD, TIOCSLTC, &_original_ltchars); + (void)ioctl(STDIN_FD, TIOCSETC, &_original_tchars); + (void)ioctl(STDIN_FD, TIOCLSET, &_original_lmode); + +#endif + + _inraw = 0; + } + else if(state == 1 && ! _inraw){ + /*----- Go into raw mode (cbreak actually) ----*/ + +#if HAS_TERMIOS + + if(tcgetattr(STDIN_FD, &_original_tty) < 0) + return -1; + + tcgetattr(STDIN_FD, &_raw_tty); + _raw_tty.c_lflag &= ~(ICANON | ECHO | IEXTEN); /* noecho raw mode */ + _raw_tty.c_lflag &= ~ISIG; /* disable signals */ + _raw_tty.c_iflag &= ~ICRNL; /* turn off CR->NL on input */ + _raw_tty.c_oflag &= ~ONLCR; /* turn off NL->CR on output */ + + _raw_tty.c_cc[VMIN] = '\01'; /* min # of chars to queue */ + _raw_tty.c_cc[VTIME] = '\0'; /* min time to wait for input*/ + _raw_tty.c_cc[VINTR] = ctrl('C'); /* make it our special char */ + _raw_tty.c_cc[VQUIT] = 0; + _raw_tty.c_cc[VSUSP] = 0; + tcsetattr(STDIN_FD, TCSADRAIN, &_raw_tty); + +#elif HAS_TERMIO + + if(ioctl(STDIN_FD, TCGETA, &_original_tty) < 0) + return(-1); + + (void)ioctl(STDIN_FD, TCGETA, &_raw_tty); /** again! **/ + _raw_tty.c_lflag &= ~(ICANON | ECHO); /* noecho raw mode */ + _raw_tty.c_lflag &= ~ISIG; /* disable signals */ + _raw_tty.c_iflag &= ~ICRNL; /* turn off CR->NL on input */ + _raw_tty.c_oflag &= ~ONLCR; /* turn off NL->CR on output */ + _raw_tty.c_cc[VMIN] = 1; /* min # of chars to queue */ + _raw_tty.c_cc[VTIME] = 0; /* min time to wait for input*/ + _raw_tty.c_cc[VINTR] = ctrl('C'); /* make it our special char */ + _raw_tty.c_cc[VQUIT] = 0; + (void)ioctl(STDIN_FD, TCSETAW, &_raw_tty); + +#else /* RAW_BSD */ + if(ioctl(STDIN_FD, TIOCGETP, &_original_tty) < 0) + return(-1); + + (void)ioctl(STDIN_FD, TIOCGETP, &_raw_tty); + (void)ioctl(STDIN_FD, TIOCGETC, &_original_tchars); + (void)ioctl(STDIN_FD, TIOCGETC, &_raw_tchars); + (void)ioctl(STDIN_FD, TIOCGLTC, &_original_ltchars); + (void)ioctl(STDIN_FD, TIOCGLTC, &_raw_ltchars); + (void)ioctl(STDIN_FD, TIOCLGET, &_original_lmode); + (void)ioctl(STDIN_FD, TIOCLGET, &_raw_lmode); + + _raw_tty.sg_flags &= ~(ECHO); /* echo off */ + _raw_tty.sg_flags |= CBREAK; /* raw on */ + _raw_tty.sg_flags &= ~CRMOD; /* Turn off CR -> LF mapping */ + + _raw_tchars.t_intrc = -1; /* Turn off ^C and ^D */ + _raw_tchars.t_eofc = -1; + + _raw_ltchars.t_lnextc = -1; /* Turn off ^V so we can use it */ + _raw_ltchars.t_dsuspc = -1; /* Turn off ^Y so we can use it */ + _raw_ltchars.t_suspc = -1; /* Turn off ^Z; we just read 'em */ + _raw_ltchars.t_werasc = -1; /* Turn off ^w word erase */ + _raw_ltchars.t_rprntc = -1; /* Turn off ^R reprint line */ + _raw_ltchars.t_flushc = -1; /* Turn off ^O output flush */ + + (void)ioctl(STDIN_FD, TIOCSETP, &_raw_tty); + (void)ioctl(STDIN_FD, TIOCSLTC, &_raw_ltchars); + (void)ioctl(STDIN_FD, TIOCSETC, &_raw_tchars); + (void)ioctl(STDIN_FD, TIOCLSET, &_raw_lmode); +#endif + _inraw = 1; + } + + return(0); +} + + +/* + * Set up the tty driver to use XON/XOFF flow control + * + * Args: state -- True to make sure XON/XOFF turned on, FALSE off. + * + * Result: none. + */ +void +xonxoff_proc(int state) +{ + if(_inraw){ + if(state){ +#if HAS_TERMIOS + + if(!(_raw_tty.c_iflag & IXON)){ + _raw_tty.c_iflag |= IXON; + tcsetattr (STDIN_FD, TCSADRAIN, &_raw_tty); + } + +#elif HAS_TERMIO + + if(!(_raw_tty.c_iflag & IXON)){ + _raw_tty.c_iflag |= IXON; /* turn ON ^S/^Q on input */ + (void)ioctl(STDIN_FD, TCSETAW, &_raw_tty); + +#else /* RAW_BSD */ + + if(_raw_tchars.t_startc == -1 || _raw_tchars.t_stopc == -1){ + _raw_tchars.t_startc = 'Q' - '@'; /* Turn ON ^S/^Q */ + _raw_tchars.t_stopc = 'S' - '@'; + (void)ioctl(STDIN_FD, TIOCSETC, &_raw_tchars); + } + +#endif + } + else{ +#if HAS_TERMIOS + + if(_raw_tty.c_iflag & IXON){ + _raw_tty.c_iflag &= ~IXON; /* turn off ^S/^Q on input */ + tcsetattr (STDIN_FD, TCSADRAIN, &_raw_tty); + } + +#elif HAS_TERMIO + + if(_raw_tty.c_iflag & IXON){ + _raw_tty.c_iflag &= ~IXON; /* turn off ^S/^Q on input */ + (void)ioctl(STDIN_FD, TCSETAW, &_raw_tty); + } + +#else /* RAW_BSD */ + if(!(_raw_tchars.t_startc == -1 && _raw_tchars.t_stopc == -1)){ + _raw_tchars.t_startc = -1; /* Turn off ^S/^Q */ + _raw_tchars.t_stopc = -1; + (void)ioctl(STDIN_FD, TIOCSETC, &_raw_tchars); + } +#endif + } + } +} + + +/* + * Set up the tty driver to do LF->CR translation + * + * Args: state -- True to turn on translation, false to write raw LF's + * + * Result: none. + */ +void +crlf_proc(int state) +{ + if(_inraw){ + if(state){ /* turn ON NL->CR on output */ +#if HAS_TERMIOS + + if(!(_raw_tty.c_oflag & ONLCR)){ + _raw_tty.c_oflag |= ONLCR; + tcsetattr (STDIN_FD, TCSADRAIN, &_raw_tty); + } + +#elif HAS_TERMIO + + if(!(_raw_tty.c_oflag & ONLCR)){ + _raw_tty.c_oflag |= ONLCR; + (void)ioctl(STDIN_FD, TCSETAW, &_raw_tty); + } + +#else /* RAW_BSD */ + if(!(_raw_tty.sg_flags & CRMOD)){ + _raw_tty.sg_flags |= CRMOD; + (void)ioctl(STDIN_FD, TIOCSETP, &_raw_tty); + } +#endif + } + else{ /* turn OFF NL-CR on output */ +#if HAS_TERMIOS + + if(_raw_tty.c_oflag & ONLCR){ + _raw_tty.c_oflag &= ~ONLCR; + tcsetattr (STDIN_FD, TCSADRAIN, &_raw_tty); + } + +#elif HAS_TERMIO + + if(_raw_tty.c_oflag & ONLCR){ + _raw_tty.c_oflag &= ~ONLCR; + (void)ioctl(STDIN_FD, TCSETAW, &_raw_tty); + } + +#else /* RAW_BSD */ + + if(_raw_tty.sg_flags & CRMOD){ + _raw_tty.sg_flags &= ~CRMOD; + (void)ioctl(STDIN_FD, TIOCSETP, &_raw_tty); + } + +#endif + } + } +} + + +/* + * Set up the tty driver to hanle interrupt char + * + * Args: state -- True to turn on interrupt char, false to not + * + * Result: tty driver that'll send us SIGINT or not + */ +void +intr_proc(int state) +{ + if(_inraw){ + if(state){ +#if HAS_TERMIOS + + _raw_tty.c_lflag |= ISIG; /* enable signals */ + tcsetattr(STDIN_FD, TCSADRAIN, &_raw_tty); + +#elif HAS_TERMIO + + _raw_tty.c_lflag |= ISIG; /* enable signals */ + (void)ioctl(STDIN_FD, TCSETAW, &_raw_tty); + +#else /* RAW_BSD */ + + _raw_tchars.t_intrc = ctrl('C'); + (void)ioctl(STDIN_FD, TIOCSETC, &_raw_tchars); + +#endif + } + else{ +#if HAS_TERMIOS + + _raw_tty.c_lflag &= ~ISIG; /* disable signals */ + tcsetattr(STDIN_FD, TCSADRAIN, &_raw_tty); + +#elif HAS_TERMIO + + _raw_tty.c_lflag &= ~ISIG; /* disable signals */ + (void)ioctl(STDIN_FD, TCSETAW, &_raw_tty); + +#else /* RAW_BSD */ + _raw_tchars.t_intrc = -1; + (void)ioctl(STDIN_FD, TIOCSETC, &_raw_tchars); +#endif + } + } +} + + +/* + * Discard any pending input characters + * + * Args: none + * + * Result: pending input buffer flushed + */ +void +flush_input(void) +{ +#if HAS_TERMIOS + + tcflush(STDIN_FD, TCIFLUSH); + +#elif HAS_TERMIO + + ioctl(STDIN_FD, TCFLSH, 0); + +#else /* RAW_BSD */ + +#ifdef TIOCFLUSH +#ifdef FREAD + int i = FREAD; +#else + int i = 1; +#endif + ioctl(STDIN_FD, TIOCFLUSH, &i); +#endif /* TIOCFLUSH */ + +#endif +} + + +/* + * Turn off hi bit stripping + */ +void +bit_strip_off(void) +{ +#if HAS_TERMIOS + + _raw_tty.c_iflag &= ~ISTRIP; + tcsetattr(STDIN_FD, TCSADRAIN, &_raw_tty); + +#elif HAS_TERMIO + + /* no op */ + +#else /* RAW_BSD */ + +#ifdef LPASS8 + _raw_lmode |= LPASS8; +#endif + +#ifdef LPASS8OUT + _raw_lmode |= LPASS8OUT; +#endif + + (void)ioctl(STDIN_FD, TIOCLSET, &_raw_lmode); + +#endif +} + + +/* + * Turn off quit character (^\) if possible + */ +void +quit_char_off(void) +{ +#if HAS_TERMIOS + +#elif HAS_TERMIO + +#else /* RAW_BSD */ + _raw_tchars.t_quitc = -1; + (void)ioctl(STDIN_FD, TIOCSETC, &_raw_tchars); +#endif +} + + +/* + * Returns TRUE if tty is < 4800, 0 otherwise. + */ +int +ttisslow(void) +{ +#if HAS_TERMIOS + + return(cfgetospeed(&_raw_tty) < B4800); + +#elif HAS_TERMIO + + return((_raw_tty.c_cflag&CBAUD) < (unsigned int)B4800); + +#else /* RAW_BSD */ + + return((int)_raw_tty.sg_ispeed < B4800); + +#endif +} diff --git a/pico/osdep/raw.h b/pico/osdep/raw.h new file mode 100644 index 00000000..cd5fa203 --- /dev/null +++ b/pico/osdep/raw.h @@ -0,0 +1,38 @@ +/* + * $Id: raw.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 PICO_OSDEP_RAW_INCLUDED +#define PICO_OSDEP_RAW_INCLUDED + + +/* useful definitions */ +#define STDIN_FD 0 +#define STDOUT_FD 1 +#define STDERR_FD 2 + + +/* exported prototypes */ +int Raw(int); +void bit_strip_off(void); +void quit_char_off(void); +int ttisslow(void); +void xonxoff_proc(int); +void flush_input(void); +void crlf_proc(int); +void intr_proc(int); + + +#endif /* PICO_OSDEP_RAW_INCLUDED */ diff --git a/pico/osdep/read.c b/pico/osdep/read.c new file mode 100644 index 00000000..52fe92e5 --- /dev/null +++ b/pico/osdep/read.c @@ -0,0 +1,223 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: read.c 763 2007-10-23 23:37:34Z 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 + * + * ======================================================================== + */ + +/* + * Keyboard input test and read functions + */ + +#include /* os-dep defs/includes */ +#include /* generally useful definitions */ + +#include "../keydefs.h" + +#ifndef _WINDOWS +#include "raw.h" +#endif /* !_WINDOWS */ + +#include "read.h" + + +static time_t _time_of_last_input; + +time_t +time_of_last_input(void) +{ + if(_time_of_last_input == 0) + _time_of_last_input = time((time_t *) 0); + + return(_time_of_last_input); +} + +#ifndef _WINDOWS +#if HAVE_SELECT + +#ifdef HAVE_SYS_SELECT_H +# include +#endif + + +/* + * Check whether or not a character is ready to be read, or time out. + * This version uses a select call. + * + * Args: time_out -- number of seconds before it will time out. + * + * Result: NO_OP_IDLE: timed out before any chars ready, time_out > 25 + * NO_OP_COMMAND: timed out before any chars ready, time_out <= 25 + * READ_INTR: read was interrupted + * READY_TO_READ: input is available + * BAIL_OUT: reading input failed, time to bail gracefully + * PANIC_NOW: system call error, die now + */ +UCS +input_ready(int time_out) +{ + struct timeval tmo; + fd_set readfds, errfds; + int res; + + fflush(stdout); + + if(time_out > 0){ + /* Check to see if there are bytes to read with a timeout */ + FD_ZERO(&readfds); + FD_ZERO(&errfds); + FD_SET(STDIN_FD, &readfds); + FD_SET(STDIN_FD, &errfds); + tmo.tv_sec = time_out; + tmo.tv_usec = 0; + res = select(STDIN_FD+1, &readfds, 0, &errfds, &tmo); + if(res < 0){ + if(errno == EINTR || errno == EAGAIN) + return(READ_INTR); + + return(BAIL_OUT); + } + + if(res == 0){ /* the select timed out */ +#ifndef __CYGWIN__ + if(getppid() == 1){ + /* Parent is init! */ + return(BAIL_OUT); + } +#endif + + /* + * "15" is the minimum allowed mail check interval. + * Anything less, and we're being told to cycle thru + * the command loop because some task is pending... + */ + return(time_out < IDLE_TIMEOUT ? NO_OP_COMMAND : NO_OP_IDLE); + } + } + + _time_of_last_input = time((time_t *) 0); + return(READY_TO_READ); +} + + +#elif HAVE_POLL + + +#ifdef HAVE_STROPTS_H +# include +#endif + +#ifdef HAVE_SYS_POLL_H +# include +#endif + +/* + * Check whether or not a character is ready to be read, or time out. + * This version uses a poll call. + * + * Args: time_out -- number of seconds before it will time out. + * + * Result: NO_OP_IDLE: timed out before any chars ready, time_out > 25 + * NO_OP_COMMAND: timed out before any chars ready, time_out <= 25 + * READ_INTR: read was interrupted + * READY_TO_READ: input is available + * BAIL_OUT: reading input failed, time to bail gracefully + * PANIC_NOW: system call error, die now + */ +UCS +input_ready(int time_out) +{ + struct pollfd pollfd; + int res; + + fflush(stdout); + + if(time_out > 0){ + /* Check to see if there are bytes to read with a timeout */ + pollfd.fd = STDIN_FD; + pollfd.events = POLLIN; + res = poll (&pollfd, 1, time_out * 1000); + if(res >= 0){ /* status bits OK? */ + if(pollfd.revents & (POLLERR | POLLNVAL)) + res = -1; /* bad news, exit below! */ + else if(pollfd.revents & POLLHUP) + return(BAIL_OUT); + } + + if(res < 0){ + if(errno == EINTR || errno == EAGAIN) + return(READ_INTR); + + return(PANIC_NOW); + } + + if(res == 0){ /* the select timed out */ + if(getppid() == 1){ + /* Parent is init! */ + return(BAIL_OUT); + } + + /* + * "15" is the minimum allowed mail check interval. + * Anything less, and we're being told to cycle thru + * the command loop because some task is pending... + */ + return(time_out < IDLE_TIMEOUT ? NO_OP_COMMAND : NO_OP_IDLE); + } + } + + _time_of_last_input = time((time_t *) 0); + return(READY_TO_READ); +} + +#endif /* HAVE_POLL */ + + +/* + * Read one character from STDIN. + * + * Result: -- the single character read + * READ_INTR -- read was interrupted + * BAIL_OUT -- read error of some sort + */ +int +read_one_char(void) +{ + int res; + unsigned char c; + + res = read(STDIN_FD, &c, 1); + + if(res <= 0){ + /* + * Error reading from terminal! + * The only acceptable failure is being interrupted. If so, + * return a value indicating such... + */ + if(res < 0 && errno == EINTR) + return(READ_INTR); + else + return(BAIL_OUT); + } + + return((int)c); +} + +#else /* _WINDOWS */ +int +set_time_of_last_input(void) +{ + _time_of_last_input = time(0L); + return(0); +} +#endif /* _WINDOWS */ diff --git a/pico/osdep/read.h b/pico/osdep/read.h new file mode 100644 index 00000000..2ba6c631 --- /dev/null +++ b/pico/osdep/read.h @@ -0,0 +1,35 @@ +/* + * $Id: read.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 PICO_OSDEP_READ_INCLUDED +#define PICO_OSDEP_READ_INCLUDED + + +#include + + +/* exported prototypes */ +#ifndef _WINDOWS +UCS input_ready(int); +int read_one_char(void); +#else /* _WINDOWS */ +int set_time_of_last_input(void); +#endif /* _WINDOWS */ + +time_t time_of_last_input(void); + + +#endif /* PICO_OSDEP_READ_INCLUDED */ diff --git a/pico/osdep/resource.h b/pico/osdep/resource.h new file mode 100644 index 00000000..4796f16a --- /dev/null +++ b/pico/osdep/resource.h @@ -0,0 +1,189 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by mswin.rc +// +#define IDD_OK 1 +#define ABOUTDLGBOX 100 +#define SPLASHDLGBOX 101 +#define IDM_OPT_SETFONT 103 +#define IDM_ABOUT 104 +#define IDM_EDIT_CUT 105 +#define IDM_EDIT_COPY 106 +#define IDM_EDIT_PASTE 107 +#define TEXTWINMENU 108 +#define IDM_EDIT_CANCEL_PASTE 109 +#define IDM_HELP 110 +#define IDD_TOOLBAR 110 +#define IDM_EDIT_COPY_APPEND 111 +#define IDD_COMPOSER_TB 111 +#define IDM_FILE_EXIT 112 +#define IDD_OPTIONALYENTER 112 +#define IDM_OPT_FONTSAMEAS 113 +#define IDD_SELECT 113 +#define IDM_OPT_SETPRINTFONT 114 +#define IDD_SELECTSORT 114 +#define IDM_FILE_CLOSE 115 +#define IDR_ACCEL_PINE 115 +#define IDD_SELECTFLAG 115 +#define IDM_FILE_PRINT 116 +#define IDD_LOGINDLG 116 +#define IDM_OPT_TOOLBAR 117 +#define IDD_CONFIGDLG 117 +#define IDM_OPT_TOOLBARPOS 118 +#define IDD_CFVARSDLG 118 +#define IDM_OPT_USEDIALOGS 119 +#define IDD_LOGINDLG2 119 +#define IDM_OPT_USEACCEL 120 +#define IDM_OPT_IMAPTELEM 121 +#define IDM_EDIT_SEL_ALL 122 +#define IDM_MI_SORTSUBJECT 123 +#define IDM_MI_SORTARRIVAL 124 +#define IDM_MI_SORTFROM 125 +#define IDM_MI_SORTTO 126 +#define IDM_MI_SORTCC 127 +#define IDM_MI_SORTDATE 128 +#define IDM_MI_SORTSIZE 129 +#define IDM_MI_SORTORDERSUB 130 +#define IDM_MI_SORTSCORE 131 +#define IDM_MI_SORTTHREAD 132 +#define IDM_MI_SORTREVERSE 133 +#define IDM_MI_FLAGIMPORTANT 134 +#define IDM_MI_FLAGNEW 135 +#define IDM_MI_FLAGANSWERED 136 +#define IDM_MI_FLAGDELETED 137 + +// This group must all be consecutive. +// These are all regular commands that end +// up in menus at the top of the screen and +// there are a couple checks for the range (see msmenu.h). +#define IDM_MI_VIEW 150 +#define IDM_MI_EXPUNGE 151 +#define IDM_MI_ZOOM 152 +#define IDM_MI_SORT 153 +#define IDM_MI_HDRMODE 154 +#define IDM_MI_MAINMENU 155 +#define IDM_MI_FLDRLIST 156 +#define IDM_MI_FLDRINDEX 157 +#define IDM_MI_COMPOSER 158 +#define IDM_MI_PREVPAGE 159 +#define IDM_MI_PREVMSG 160 +#define IDM_MI_NEXTMSG 161 +#define IDM_MI_ADDRBOOK 162 +#define IDM_MI_WHEREIS 163 +#define IDM_MI_PRINT 164 +#define IDM_MI_REPLY 165 +#define IDM_MI_FORWARD 166 +#define IDM_MI_BOUNCE 167 +#define IDM_MI_DELETE 168 +#define IDM_MI_UNDELETE 169 +#define IDM_MI_FLAG 170 +#define IDM_MI_SAVE 171 +#define IDM_MI_EXPORT 172 +#define IDM_MI_TAKEADDR 173 +#define IDM_MI_SELECT 174 +#define IDM_MI_APPLY 175 +#define IDM_MI_POSTPONE 176 +#define IDM_MI_SEND 177 +#define IDM_MI_CANCEL 178 +#define IDM_MI_ATTACH 179 +#define IDM_MI_TOADDRBOOK 180 +#define IDM_MI_READFILE 181 +#define IDM_MI_JUSTIFY 182 +#define IDM_MI_ALTEDITOR 183 +#define IDM_MI_GENERALHELP 184 +#define IDM_MI_SCREENHELP 185 +#define IDM_MI_EXIT 186 +#define IDM_MI_NEXTPAGE 187 +#define IDM_MI_SAVEFILE 188 +#define IDM_MI_CURPOSITION 189 +#define IDM_MI_GOTOFLDR 190 +#define IDM_MI_JUMPTOMSG 191 +#define IDM_MI_RICHHDR 192 +#define IDM_MI_EXITMODE 193 +#define IDM_MI_REVIEW 194 +#define IDM_MI_KEYMENU 195 +#define IDM_MI_SELECTCUR 196 +#define IDM_MI_UNDO 197 +#define IDM_MI_SPELLCHK 198 + +#define IDD_ARGLIST 199 +#define IDM_OPT_CARETBLOCK 200 +#define IDM_OPT_CARETSMALLBLOCK 201 +#define IDM_OPT_CARETHBAR 202 +#define IDM_OPT_CARETVBAR 203 +#define IDM_OPT_NEWMAILWIN 204 +#define IDM_EDIT_CLEAR 205 +#define IDM_OPT_ERASE_CREDENTIALS 205 +#define IDM_MI_VIEWINWIND 206 +#define ALPINEMENU 300 +#define COMPOSERMENU 301 +#define ALPINEICON 400 +#define NEWMAILICON 401 +#define PICOHAND 402 +#define MCLOSEDICON 403 +#define ALPINESPLASH 501 +#define IDD_ABOUTICON 0x210 +#define IDD_VERSION 0x212 +#define IDD_BYLINE 0x213 +#define IDS_BYLINE 773 +#define IDS_APPNAME 774 +#define IDS_APPIDENT 775 +#define IDC_RESPONCE 1002 +#define IDC_PROMPT 1003 +#define IDC_GETHELP 1004 +#define IDC_SORTARRIVAL 1005 +#define IDC_SORTFIRSTBUTTON IDC_SORTARRIVAL +#define IDC_SORTORDERSUB 1006 +#define IDC_SORTSUBJECT 1007 +#define IDC_SORTSIZE 1008 +#define IDC_SORTCC 1009 +#define IDC_SORTTO 1010 +#define IDC_SORTFROM 1011 +#define IDC_SORTDATE 1012 +#define IDC_SORTTHREAD 1013 +#define IDC_SORTSCORE 1014 +#define IDC_SORTLASTBUTTON IDC_SORTSCORE +#define IDC_SORTREVERSE 1015 +#define IDC_FLAGCOL1 1016 +#define IDC_FLAGCOL2 1017 +#define IDC_BUTTON2 1018 +#define IDC_ARGTEXT 1019 +#define IDC_RLOGINE 1020 +#define IDC_RPASSWORD 1021 +#define IDC_RPWTEXT 1022 +#define IDC_PRESPASS 1023 +#define IDC_CONFRRADIO 1023 +#define IDC_CFV_PNAME 1023 +#define IDC_CONFLRADIO 1024 +#define IDC_CFV_EMAILADR 1024 +#define IDC_CONFSRVRTXT 1025 +#define IDC_CFV_MSERVER 1025 +#define IDC_CONFESERVER 1026 +#define IDC_CFV_IMAP 1026 +#define IDC_CONFUNTXT 1027 +#define IDC_CFV_POP3 1027 +#define IDC_CONFEUSERNAME 1028 +#define IDC_CFV_LOGIN 1028 +#define IDC_CONFDFLTFLDR 1029 +#define IDC_CFV_SMTPSERVER 1029 +#define IDC_CONFFLDRTXT 1030 +#define IDC_CFV_DEFMAILER 1030 +#define IDC_CONFEFLDRNAME 1031 +#define IDC_CFV_DEFNEWSRDR 1031 +#define IDC_CONFFNTXT 1032 +#define IDC_CONFEFN 1033 +#define IDC_CONFBROWSE 1034 +#define IDC_CONFDFLTSET 1035 +#define IDC_CONFTEXT 1036 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 116 +#define _APS_NEXT_COMMAND_VALUE 122 +#define _APS_NEXT_CONTROL_VALUE 1023 +#define _APS_NEXT_SYMED_VALUE 103 +#endif +#endif diff --git a/pico/osdep/shell.c b/pico/osdep/shell.c new file mode 100644 index 00000000..cdb701dd --- /dev/null +++ b/pico/osdep/shell.c @@ -0,0 +1,171 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: shell.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 +#include + +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../edef.h" +#include "../efunc.h" +#include "../keydefs.h" + +#include "tty.h" +#include "signals.h" + +#ifdef _WINDOWS +#include "mswin.h" +#endif + +#include "shell.h" + + + +/* internal prototypes */ +#ifdef SIGCONT +RETSIGTYPE rtfrmshell(int); +#endif + + + +#ifdef SIGTSTP +/* + * bktoshell - suspend and wait to be woken up + */ +int +bktoshell(int f, int n) +{ + UCS z = CTRL | 'Z'; + + if(!(gmode&MDSSPD)){ + unknown_command(z); + return(FALSE); + } + + if(Pmaster){ + if(!Pmaster->suspend){ + unknown_command(z); + return(FALSE); + } + + if((*Pmaster->suspend)() == NO_OP_COMMAND){ + int rv; + + if(km_popped){ + term.t_mrow = 2; + curwp->w_ntrows -= 2; + } + + clearcursor(); + mlerase(); + rv = (*Pmaster->showmsg)('x'); + ttresize(); + picosigs(); + if(rv) /* Did showmsg corrupt the display? */ + pico_refresh(0, 1); /* Yes, repaint */ + + mpresf = 1; + if(km_popped){ + term.t_mrow = 0; + curwp->w_ntrows += 2; + } + } + else{ + ttresize(); + pclear(0, term.t_nrow); + pico_refresh(0, 1); + } + + return(TRUE); + } + + if(gmode&MDSPWN){ + char *shell; + int dummy; + + vttidy(); + movecursor(0, 0); + (*term.t_eeop)(); + printf("\n\n\nUse \"exit\" to return to Pi%s\n", + (gmode & MDBRONLY) ? "lot" : "co"); + system((shell = (char *)getenv("SHELL")) ? shell : "/bin/csh"); + rtfrmshell(dummy); /* fixup tty */ + } + else { + movecursor(term.t_nrow-1, 0); + peeol(); + movecursor(term.t_nrow, 0); + peeol(); + movecursor(term.t_nrow, 0); + printf("\n\n\nUse \"fg\" to return to Pi%s\n", + (gmode & MDBRONLY) ? "lot" : "co"); + ttclose(); + movecursor(term.t_nrow, 0); + peeol(); + (*term.t_flush)(); + + signal(SIGCONT, rtfrmshell); /* prepare to restart */ + signal(SIGTSTP, SIG_DFL); /* prepare to stop */ + kill(0, SIGTSTP); + } + + return(TRUE); +} +#else +#ifdef _WINDOWS +int +bktoshell(int f, int n) +{ + UCS z = CTRL | 'Z'; + + if(!(gmode&MDSSPD)){ + unknown_command(z); + return(FALSE); + } + if(Pmaster){ + if(!Pmaster->suspend){ + unknown_command(z); + return(FALSE); + } + (*Pmaster->suspend)(); + return(TRUE); + } + + mswin_minimize(); + return(TRUE); +} +#endif /* _WINDOWS */ + +#endif /* SIGTSTP */ + +#ifdef SIGCONT +/* + * rtfrmshell - back from shell, fix modes and return + */ +RETSIGTYPE +rtfrmshell(int sig) +{ + signal(SIGCONT, SIG_DFL); + ttopen(); + ttresize(); + pclear(0, term.t_nrow); + pico_refresh(0, 1); +} +#endif /* SIGCONT */ diff --git a/pico/osdep/shell.h b/pico/osdep/shell.h new file mode 100644 index 00000000..f356485f --- /dev/null +++ b/pico/osdep/shell.h @@ -0,0 +1,27 @@ +/* + * $Id: shell.h 807 2007-11-09 01:21:33Z 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 PICO_OSDEP_SHELL_INCLUDED +#define PICO_OSDEP_SHELL_INCLUDED + + +/* exported prototypes */ +#if defined(SIGTSTP) || defined(_WINDOWS) +int bktoshell(int, int); +#endif + + +#endif /* PICO_OSDEP_SHELL_INCLUDED */ diff --git a/pico/osdep/signals.c b/pico/osdep/signals.c new file mode 100644 index 00000000..10f02ba1 --- /dev/null +++ b/pico/osdep/signals.c @@ -0,0 +1,181 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: signals.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 +#include + +#include "../headers.h" + +#include "../../pith/charconv/filesys.h" + +#include "tty.h" + +#include "signals.h" + + +/* internal prototypes */ +#ifndef _WINDOWS +RETSIGTYPE do_hup_signal(int); +#endif + + +/* + * picosigs - Install any handlers for the signals we're interested + * in catching. + */ +void +picosigs(void) +{ +#ifndef _WINDOWS + signal(SIGHUP, do_hup_signal); /* deal with SIGHUP */ + signal(SIGTERM, do_hup_signal); /* deal with SIGTERM */ +#ifdef SIGTSTP + signal(SIGTSTP, SIG_DFL); +#endif +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + signal(SIGWINCH, winch_handler); /* window size changes */ +#endif +#endif /* !_WINDOWS */ +} + + + + +#ifndef _WINDOWS +/* + * do_hup_signal - jump back in the stack to where we can handle this + */ +RETSIGTYPE +do_hup_signal(int sig) +{ + signal(SIGHUP, SIG_IGN); /* ignore further SIGHUP's */ + signal(SIGTERM, SIG_IGN); /* ignore further SIGTERM's */ + if(Pmaster){ + extern jmp_buf finstate; + + longjmp(finstate, COMP_GOTHUP); + } + else{ + /* + * if we've been interrupted and the buffer is changed, + * save it... + */ + if(anycb() == TRUE){ /* time to save */ + if(curbp->b_fname[0] == '\0'){ /* name it */ + strncpy(curbp->b_fname, "pico.save", sizeof(curbp->b_fname)); + curbp->b_fname[sizeof(curbp->b_fname)-1] = '\0'; + } + else{ + strncat(curbp->b_fname, ".save", sizeof(curbp->b_fname)-strlen(curbp->b_fname)-1); + curbp->b_fname[sizeof(curbp->b_fname)-1] = '\0'; + } + + our_unlink(curbp->b_fname); + writeout(curbp->b_fname, TRUE); + } + + vttidy(); + exit(1); + } +} +#endif /* !_WINDOWS */ + + +#if defined(SIGWINCH) && defined(TIOCGWINSZ) +/* + * winch_handler - handle window change signal + */ +RETSIGTYPE +winch_handler(int sig) +{ + int i; + signal(SIGWINCH, winch_handler); + if(wheadp != NULL) + ttresize(); + else if (Pmaster){ + i = Pmaster->arm_winch_cleanup; + Pmaster->arm_winch_cleanup = 1; + } + if(Pmaster && Pmaster->winch_cleanup && Pmaster->arm_winch_cleanup) + (*Pmaster->winch_cleanup)(); + if(wheadp == NULL && Pmaster != NULL) + Pmaster->arm_winch_cleanup = i; +} +#endif /* SIGWINCH and friends */ + + + +#ifdef POSIX_SIGNALS +/*---------------------------------------------------------------------- + Reset signals after critical imap code + ----*/ +void +(*posix_signal(int sig_num, RETSIGTYPE (*action)(int)))(int) +{ + struct sigaction new_action, old_action; + + memset((void *)&new_action, 0, sizeof(struct sigaction)); + sigemptyset (&new_action.sa_mask); + new_action.sa_handler = action; +#ifdef SA_RESTART + new_action.sa_flags = SA_RESTART; +#else + new_action.sa_flags = 0; +#endif + sigaction(sig_num, &new_action, &old_action); + return(old_action.sa_handler); +} + +int +posix_sigunblock(int mask) +{ + sigset_t sig_mask; + + sigemptyset(&sig_mask); + sigaddset(&sig_mask, mask); +#if HAVE_PTHREAD +# define sigprocmask pthread_sigmask +#endif + return(sigprocmask(SIG_UNBLOCK, &sig_mask, NULL)); +} +#endif /* POSIX_SIGNALS */ + + +#if defined(sv3) || defined(ct) +/* Placed by rll to handle the rename function not found in AT&T */ +int +rename(char *oldname, char *newname) +{ + int rtn; + char b[NLINE]; + + strncpy(b, fname_to_locale(oldname), sizeof(b)); + b[sizeof(b)-1] = '\0'; + + if ((rtn = link(b, fname_to_locale(newname))) != 0) { + perror("Was not able to rename file."); + return(rtn); + } + + if ((rtn = our_unlink(b)) != 0) + perror("Was not able to unlink file."); + + return(rtn); +} +#endif + diff --git a/pico/osdep/signals.h b/pico/osdep/signals.h new file mode 100644 index 00000000..2ea33cc4 --- /dev/null +++ b/pico/osdep/signals.h @@ -0,0 +1,62 @@ +/* + * $Id: signals.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 PICO_OSDEP_SIGNALS_INCLUDED +#define PICO_OSDEP_SIGNALS_INCLUDED + + +/* + * [Re]Define signal functions as needed... + */ +#ifdef POSIX_SIGNALS +/* + * Redefine signal call to our wrapper of POSIX sigaction + */ +#define signal(SIG,ACT) posix_signal(SIG,ACT) +#define our_sigunblock(SIG) posix_sigunblock(SIG) +#else /* !POSIX_SIGNALS */ +#ifdef SYSV_SIGNALS +/* + * Redefine signal calls to SYSV style call. + */ +#define signal(SIG,ACT) sigset(SIG,ACT) +#define our_sigunblock(SIG) sigrelse(SIG) +#else /* !SYSV_SIGNALS */ +#ifdef _WINDOWS +#define our_sigunblock(SIG) +#else /* !_WINDOWS */ +/* + * Good ol' BSD signals. + */ +#define our_sigunblock(SIG) +#endif /* !_WINDOWS */ +#endif /* !SYSV_SIGNALS */ +#endif /* !POSIX_SIGNALS */ + + + +/* exported prototypes */ +void picosigs(void); +#if defined(SIGWINCH) && defined(TIOCGWINSZ) +RETSIGTYPE winch_handler(int); +#endif + +#ifdef POSIX_SIGNALS +void (*posix_signal(int, RETSIGTYPE (*)()))(int); +int posix_sigunblock(int); +#endif + +#endif /* PICO_OSDEP_SIGNALS_INCLUDED */ diff --git a/pico/osdep/spell.c b/pico/osdep/spell.c new file mode 100644 index 00000000..87b345eb --- /dev/null +++ b/pico/osdep/spell.c @@ -0,0 +1,323 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: spell.c 854 2007-12-07 17:44:43Z 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 + * + * ======================================================================== + * + * Program: spell.c + */ + + +#include +#include + +#include "../headers.h" +/* +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../edef.h" +#include "../efunc.h" +#include "../keydefs.h" +*/ + +#include "../../pith/charconv/filesys.h" + +#include "popen.h" +#include "altedit.h" +#include "spell.h" + + +#ifdef SPELLER + +int movetoword(UCS *); + + +static char *spellhelp[] = { +/* TRANSLATORS: Some help text. The ~ characters cause + the characters they are in front of to be bold. */ + N_("Spell Check Help"), + " ", + N_(" The spell checker examines all words in the text. It then"), + N_(" offers each misspelled word for correction while simultaneously"), + N_(" highlighting it in the text. To leave a word unchanged simply"), + N_("~ hit ~R~e~t~u~r~n at the edit prompt. If a word has been corrected,"), + N_(" each occurrence of the incorrect word is offered for replacement."), + " ", + N_("~ Spell checking can be cancelled at any time by typing ~^~C (~F~3)"), + N_(" after exiting help."), + " ", + N_("End of Spell Check Help"), + " ", + NULL +}; + + +static char *pinespellhelp[] = { + N_("Spell Check Help"), + " ", + N_("\tThe spell checker examines all words in the text. It then"), + N_("\toffers each misspelled word for correction while simultaneously"), + N_("\thighlighting it in the text. To leave a word unchanged simply"), + N_("\thit Return at the edit prompt. If a word has been corrected,"), + N_("\teach occurrence of the incorrect word is offered for replacement."), + " ", + N_("\tSpell checking can be cancelled at any time by typing ^C (F3)"), + N_("\tafter exiting help."), + " ", + N_("End of Spell Check Help"), + " ", + NULL +}; + +#ifndef _WINDOWS +/* + * spell() - check for potentially missspelled words and offer them for + * correction + */ +int +spell(int f, int n) +{ + int status, next, ret; + char ccb[NLINE], *sp, *fn, *lp, *wsp, c, spc[NLINE]; + UCS *b; + UCS wb[NLINE], cb[NLINE]; + EML eml; + + setimark(0, 1); + emlwrite(_("Checking spelling..."), NULL); /* greetings */ + + if(alt_speller) + return(alt_editor(1, 0)); /* f == 1 means fork speller */ + + if((fn = writetmp(0, NULL)) == NULL){ + emlwrite(_("Can't write temp file for spell checker"), NULL); + return(-1); + } + + if((sp = (char *)getenv("SPELL")) == NULL) + sp = SPELLER; + + /* exists? */ + ret = (strlen(sp) + 1); + snprintf(spc, sizeof(spc), "%s", sp); + + for(lp = spc, ret = FIOERR; *lp; lp++){ + if((wsp = strpbrk(lp, " \t")) != NULL){ + c = *wsp; + *wsp = '\0'; + } + + if(strchr(lp, '/')){ + ret = fexist(lp, "x", (off_t *)NULL); + } + else{ + char *path, fname[MAXPATH+1]; + + if(!(path = getenv("PATH"))) + path = ":/bin:/usr/bin"; + + ret = ~FIOSUC; + while(ret != FIOSUC && *path && pathcat(fname, &path, lp)) + ret = fexist(fname, "x", (off_t *)NULL); + } + + if(wsp) + *wsp = c; + + if(ret == FIOSUC) + break; + } + + if(ret != FIOSUC){ + eml.s = sp; + emlwrite(_("\007Spell-checking file \"%s\" not found"), &eml); + return(-1); + } + + snprintf(ccb, sizeof(ccb), "( %s ) < %s", sp, fn); + if(P_open(ccb) != FIOSUC){ /* read output from command */ + our_unlink(fn); + emlwrite(_("Can't fork spell checker"), NULL); + return(-1); + } + + ret = 1; + while(ffgetline(wb, NLINE, NULL, 0) == FIOSUC && ret){ + if((b = ucs4_strchr(wb, (UCS) '\n')) != NULL) + *b = '\0'; + + ucs4_strncpy(cb, wb, NLINE); + cb[NLINE-1] = '\0'; + + gotobob(0, 1); + + status = TRUE; + next = 1; + + while(status){ + if(next++) + if(movetoword(wb) != TRUE) + break; + + update(); + (*term.t_rev)(1); + pputs(wb, 1); /* highlight word */ + (*term.t_rev)(0); + + if(ucs4_strcmp(cb, wb)){ + char prompt[2*NLINE + 32]; + char *wbu, *cbu; + + wbu = ucs4_to_utf8_cpystr(wb); + cbu = ucs4_to_utf8_cpystr(cb); + + snprintf(prompt, sizeof(prompt), _("Replace \"%s\" with \"%s\""), wbu, cbu); + status=mlyesno_utf8(prompt, TRUE); + if(wbu) + fs_give((void **) &wbu); + if(cbu) + fs_give((void **) &cbu); + } + else{ + UCS *p; + + p = utf8_to_ucs4_cpystr(_("Edit a replacement: ")); + status=mlreplyd(p, cb, NLINE, QDEFLT, NULL); + if(p) + fs_give((void **) &p); + } + + + curwp->w_flag |= WFMOVE; /* put cursor back */ + sgarbk = 0; /* fake no-keymenu-change! */ + update(); + pputs(wb, 0); /* un-highlight */ + + switch(status){ + case TRUE: + chword(wb, cb); /* correct word */ + case FALSE: + update(); /* place cursor */ + break; + case ABORT: + emlwrite(_("Spell Checking Cancelled"), NULL); + ret = FALSE; + status = FALSE; + break; + case HELPCH: + if(Pmaster){ + VARS_TO_SAVE *saved_state; + + saved_state = save_pico_state(); + (*Pmaster->helper)(pinespellhelp, + _("Help with Spelling Checker"), 1); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + } + else + pico_help(spellhelp, _("Help with Spelling Checker"), 1); + + case (CTRL|'L'): + next = 0; /* don't get next word */ + sgarbf = TRUE; /* repaint full screen */ + update(); + status = TRUE; + continue; + default: + emlwrite("Huh?", NULL); /* shouldn't get here, but.. */ + status = TRUE; + sleep(1); + break; + } + + forwword(0, 1); /* goto next word */ + } + } + + P_close(); /* clean up */ + our_unlink(fn); + swapimark(0, 1); + curwp->w_flag |= WFHARD|WFMODE; + sgarbk = TRUE; + + if(ret) + emlwrite(_("Done checking spelling"), NULL); + + return(ret); +} + + +#endif /* UNIX */ + + +/* + * movetoword() - move to the first occurance of the word w + * + * returns: + * TRUE upon success + * FALSE otherwise + */ +int +movetoword(UCS *w) +{ + int i; + int ret = FALSE; + int olddoto; + LINE *olddotp; + register int off; /* curwp offset */ + register LINE *lp; /* curwp line */ + + olddoto = curwp->w_doto; /* save where we are */ + olddotp = curwp->w_dotp; + + curwp->w_bufp->b_mode |= MDEXACT; /* case sensitive */ + while(forscan(&i, w, NULL, 0, 1) == TRUE){ + if(i) + break; /* wrap NOT allowed! */ + + lp = curwp->w_dotp; /* for convenience */ + off = curwp->w_doto; + + /* + * We want to minimize the number of substrings that we report + * as matching a misspelled word... + */ + if(off == 0 || !ucs4_isalpha(lgetc(lp, off - 1).c)){ + off += ucs4_strlen(w); + if((!ucs4_isalpha(lgetc(lp, off).c) || off == llength(lp)) + && lgetc(lp, 0).c != '>'){ + ret = TRUE; + break; + } + } + + forwchar(0, 1); /* move on... */ + + } + curwp->w_bufp->b_mode ^= MDEXACT; /* case insensitive */ + + if(ret == FALSE){ + curwp->w_dotp = olddotp; + curwp->w_doto = olddoto; + } + else + curwp->w_flag |= WFHARD; + + return(ret); +} + +#endif /* SPELLER */ diff --git a/pico/osdep/spell.h b/pico/osdep/spell.h new file mode 100644 index 00000000..ed7a4428 --- /dev/null +++ b/pico/osdep/spell.h @@ -0,0 +1,25 @@ +/* + * $Id: spell.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 PICO_OSDEP_SPELL_INCLUDED +#define PICO_OSDEP_SPELL_INCLUDED + + +/* exported prototypes */ +int spell(int, int); + + +#endif /* PICO_OSDEP_SPELL_INCLUDED */ diff --git a/pico/osdep/terminal.c b/pico/osdep/terminal.c new file mode 100644 index 00000000..72206c01 --- /dev/null +++ b/pico/osdep/terminal.c @@ -0,0 +1,1762 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: terminal.c 921 2008-01-31 02:09:25Z 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 + * + * ======================================================================== + * + * Program: Display routines + */ + +#include +#include + + +#include "../estruct.h" +#include "../keydefs.h" +#include "../pico.h" +#include "../mode.h" + +#include "raw.h" +#include "color.h" +#include "tty.h" +#include "terminal.h" +#include "getkey.h" + +#ifndef _WINDOWS +extern long gmode; + + +/* + * Useful Defaults + */ +#define MARGIN 8 /* size of minimim margin and */ +#define MROW 2 /* rows in menu */ + + +/* any special properties of the current terminal */ +unsigned term_capabilities = (TT_EOLEXIST | TT_SCROLLEXIST | TT_INSCHAR | TT_DELCHAR); + + +/* + * + */ +unsigned +tthascap(void) +{ + return(term_capabilities); +} + + +#if HAS_TERMINFO + +/* + * terminfo-based terminal i/o and control routines + */ + + +/* internal prototypes */ +static int tinfomove(int, int); +static int tinfoeeol(void); +static int tinfoeeop(void); +static int tinfobeep(void); +static int tinforev(int); +static int tinfoopen(void); +static int tinfoterminalinfo(int); +static int tinfoclose(void); +static void setup_dflt_esc_seq(void); +static void tinfoinsert(UCS); +static void tinfodelete(void); + +extern int tput(); +extern int tputs(char *, int, int (*)(int)); +extern char *tgoto(char *, int, int); +extern char *tigetstr (); +extern int setupterm(char *, int, int *); +extern int tigetnum(char *); +extern int tigetflag(char *); + +/** + ** Note: The tgoto calls should really be replaced by tparm calls for + ** modern terminfo. tgoto(s, x, y) == tparm(s, y, x). + **/ + +int _tlines, _tcolumns; +char *_clearscreen, *_moveto, *_up, *_down, *_right, *_left, + *_setinverse, *_clearinverse, + *_setunderline, *_clearunderline, + *_setbold, *_clearallattr, /* there is no clear only bold! */ + *_cleartoeoln, *_cleartoeos, + *_deleteline, /* delete line */ + *_insertline, /* insert line */ + *_scrollregion, /* define a scrolling region, vt100 */ + *_insertchar, /* insert character, preferable to : */ + *_startinsert, /* set insert mode and, */ + *_endinsert, /* end insert mode */ + *_deletechar, /* delete character */ + *_startdelete, /* set delete mode and, */ + *_enddelete, /* end delete mode */ + *_scrolldown, /* scroll down */ + *_scrollup, /* scroll up */ + *_termcap_init, /* string to start termcap */ + *_termcap_end, /* string to end termcap */ + *_op, *_oc, *_setaf, *_setab, *_setf, *_setb, *_scp; +int _colors, _pairs, _bce; +char term_name[40]; + +TERM term = { + NROW-1, + NCOL, + MARGIN, + MROW, + tinfoopen, + tinfoterminalinfo, + tinfoclose, + ttgetc, + ttputc, + ttflush, + tinfomove, + tinfoeeol, + tinfoeeop, + tinfobeep, + tinforev +}; + + +/* + * Add default keypad sequences to the trie. + */ +static void +setup_dflt_esc_seq(void) +{ + /* + * this is sort of a hack [no kidding], but it allows us to use + * the function keys on pc's running telnet + */ + + /* + * UW-NDC/UCS vt10[02] application mode. + */ + kpinsert("\033OP", F1, 1); + kpinsert("\033OQ", F2, 1); + kpinsert("\033OR", F3, 1); + kpinsert("\033OS", F4, 1); + kpinsert("\033Op", F5, 1); + kpinsert("\033Oq", F6, 1); + kpinsert("\033Or", F7, 1); + kpinsert("\033Os", F8, 1); + kpinsert("\033Ot", F9, 1); + kpinsert("\033Ou", F10, 1); + kpinsert("\033Ov", F11, 1); + kpinsert("\033Ow", F12, 1); + + /* + * DEC vt100, ANSI and cursor key mode. + */ + kpinsert("\033OA", KEY_UP, 1); + kpinsert("\033OB", KEY_DOWN, 1); + kpinsert("\033OC", KEY_RIGHT, 1); + kpinsert("\033OD", KEY_LEFT, 1); + + /* + * special keypad functions + */ + kpinsert("\033[4J", KEY_PGUP, 1); + kpinsert("\033[3J", KEY_PGDN, 1); + kpinsert("\033[2J", KEY_HOME, 1); + kpinsert("\033[N", KEY_END, 1); + + /* + * vt220? + */ + kpinsert("\033[5~", KEY_PGUP, 1); + kpinsert("\033[6~", KEY_PGDN, 1); + kpinsert("\033[1~", KEY_HOME, 1); + kpinsert("\033[4~", KEY_END, 1); + + /* + * konsole, XTerm (XFree 4.x.x) keyboard setting + */ + kpinsert("\033[H", KEY_HOME, 1); + kpinsert("\033[F", KEY_END, 1); + + /* + * gnome-terminal 2.6.0, don't know why it + * changed from 2.2.1 + */ + kpinsert("\033OH", KEY_HOME, 1); + kpinsert("\033OF", KEY_END, 1); + + /* + * "\033[2~" was common for KEY_HOME in a quick survey + * of terminals (though typically the Insert key). + * Teraterm 2.33 sends the following escape sequences, + * which is quite incompatible with everything + * else: + * Home: "\033[2~" End: "\033[5~" PgUp: "\033[3~" + * PgDn: "\033[6~" + * The best thing to do would be to fix TeraTerm + * keymappings or to tweak terminfo. + */ + + /* + * ANSI mode. + */ + kpinsert("\033[=a", F1, 1); + kpinsert("\033[=b", F2, 1); + kpinsert("\033[=c", F3, 1); + kpinsert("\033[=d", F4, 1); + kpinsert("\033[=e", F5, 1); + kpinsert("\033[=f", F6, 1); + kpinsert("\033[=g", F7, 1); + kpinsert("\033[=h", F8, 1); + kpinsert("\033[=i", F9, 1); + kpinsert("\033[=j", F10, 1); + kpinsert("\033[=k", F11, 1); + kpinsert("\033[=l", F12, 1); + + /* + * DEC vt100, ANSI and cursor key mode reset. + */ + kpinsert("\033[A", KEY_UP, 1); + kpinsert("\033[B", KEY_DOWN, 1); + kpinsert("\033[C", KEY_RIGHT, 1); + kpinsert("\033[D", KEY_LEFT, 1); + + /* + * DEC vt52 mode. + */ + kpinsert("\033A", KEY_UP, 1); + kpinsert("\033B", KEY_DOWN, 1); + kpinsert("\033C", KEY_RIGHT, 1); + kpinsert("\033D", KEY_LEFT, 1); + + /* + * DEC vt52 application keys, and some Zenith 19. + */ + kpinsert("\033?r", KEY_DOWN, 1); + kpinsert("\033?t", KEY_LEFT, 1); + kpinsert("\033?v", KEY_RIGHT, 1); + kpinsert("\033?x", KEY_UP, 1); + + /* + * Some Ctrl-Arrow keys + */ + kpinsert("\033[5A", CTRL_KEY_UP, 1); + kpinsert("\033[5B", CTRL_KEY_DOWN, 1); + kpinsert("\033[5C", CTRL_KEY_RIGHT, 1); + kpinsert("\033[5D", CTRL_KEY_LEFT, 1); + + kpinsert("\033[1;5A", CTRL_KEY_UP, 1); + kpinsert("\033[1;5B", CTRL_KEY_DOWN, 1); + kpinsert("\033[1;5C", CTRL_KEY_RIGHT, 1); + kpinsert("\033[1;5D", CTRL_KEY_LEFT, 1); + + /* + * Map some shift+up/down/left/right to their shiftless counterparts + */ + kpinsert("\033[1;2A", KEY_UP, 1); + kpinsert("\033[1;2B", KEY_DOWN, 1); + kpinsert("\033[1;2C", KEY_RIGHT, 1); + kpinsert("\033[1;2D", KEY_LEFT, 1); + kpinsert("\033[a", KEY_UP, 1); + kpinsert("\033[b", KEY_DOWN, 1); + kpinsert("\033[c", KEY_RIGHT, 1); + kpinsert("\033[d", KEY_LEFT, 1); + + /* + * Sun Console sequences. + */ + kpinsert("\033[1", KEY_SWALLOW_Z, 1); + kpinsert("\033[215", KEY_SWAL_UP, 1); + kpinsert("\033[217", KEY_SWAL_LEFT, 1); + kpinsert("\033[219", KEY_SWAL_RIGHT, 1); + kpinsert("\033[221", KEY_SWAL_DOWN, 1); + + /* + * Kermit App Prog Cmd, gobble until ESC \ (kermit should intercept this) + */ + kpinsert("\033_", KEY_KERMIT, 1); + + /* + * Fake a control character. + */ + kpinsert("\033\033", KEY_DOUBLE_ESC, 1); +} + + +static int +tinfoterminalinfo(int termcap_wins) +{ + char *_ku, *_kd, *_kl, *_kr, + *_kppu, *_kppd, *_kphome, *_kpend, *_kpdel, + *_kf1, *_kf2, *_kf3, *_kf4, *_kf5, *_kf6, + *_kf7, *_kf8, *_kf9, *_kf10, *_kf11, *_kf12; + char *ttnm; + + if (Pmaster) { + /* + * setupterm() automatically retrieves the value + * of the TERM variable. + */ + int err; + ttnm = getenv("TERM"); + if(!ttnm) + return(-1); + + strncpy(term_name, ttnm, sizeof(term_name)); + term_name[sizeof(term_name)-1] = '\0'; + setupterm ((char *) 0, 1, &err); + if (err != 1) return(err-2); + } + else { + /* + * setupterm() issues a message and exits, if the + * terminfo data base is gone or the term type is + * unknown, if arg2 is 0. + */ + setupterm ((char *) 0, 1, (int *) 0); + } + + _clearscreen = tigetstr("clear"); + _moveto = tigetstr("cup"); + _up = tigetstr("cuu1"); + _down = tigetstr("cud1"); + _right = tigetstr("cuf1"); + _left = tigetstr("cub1"); + _setinverse = tigetstr("smso"); + _clearinverse = tigetstr("rmso"); + _setunderline = tigetstr("smul"); + _clearunderline = tigetstr("rmul"); + _setbold = tigetstr("bold"); + _clearallattr = tigetstr("sgr0"); + _cleartoeoln = tigetstr("el"); + _cleartoeos = tigetstr("ed"); + _deletechar = tigetstr("dch1"); + _insertchar = tigetstr("ich1"); + _startinsert = tigetstr("smir"); + _endinsert = tigetstr("rmir"); + _deleteline = tigetstr("dl1"); + _insertline = tigetstr("il1"); + _scrollregion = tigetstr("csr"); + _scrolldown = tigetstr("ind"); + _scrollup = tigetstr("ri"); + _termcap_init = tigetstr("smcup"); + _termcap_end = tigetstr("rmcup"); + _startdelete = tigetstr("smdc"); + _enddelete = tigetstr("rmdc"); + _ku = tigetstr("kcuu1"); + _kd = tigetstr("kcud1"); + _kl = tigetstr("kcub1"); + _kr = tigetstr("kcuf1"); + _kppu = tigetstr("kpp"); + _kppd = tigetstr("knp"); + _kphome = tigetstr("khome"); + _kpend = tigetstr("kend"); + _kpdel = tigetstr("kdch1"); + _kf1 = tigetstr("kf1"); + _kf2 = tigetstr("kf2"); + _kf3 = tigetstr("kf3"); + _kf4 = tigetstr("kf4"); + _kf5 = tigetstr("kf5"); + _kf6 = tigetstr("kf6"); + _kf7 = tigetstr("kf7"); + _kf8 = tigetstr("kf8"); + _kf9 = tigetstr("kf9"); + _kf10 = tigetstr("kf10"); + _kf11 = tigetstr("kf11"); + _kf12 = tigetstr("kf12"); + + _colors = tigetnum("colors"); + _pairs = tigetnum("pairs"); + _setaf = tigetstr("setaf"); + _setab = tigetstr("setab"); + _setf = tigetstr("setf"); + _setb = tigetstr("setb"); + _scp = tigetstr("scp"); + _op = tigetstr("op"); + _oc = tigetstr("oc"); + _bce = tigetflag("bce"); + + _tlines = tigetnum("lines"); + if(_tlines == -1){ + char *er; + int rr; + + /* tigetnum failed, try $LINES */ + er = getenv("LINES"); + if(er && (rr = atoi(er)) > 0) + _tlines = rr; + } + + _tcolumns = tigetnum("cols"); + if(_tcolumns == -1){ + char *ec; + int cc; + + /* tigetnum failed, try $COLUMNS */ + ec = getenv("COLUMNS"); + if(ec && (cc = atoi(ec)) > 0) + _tcolumns = cc; + } + + /* + * Add default keypad sequences to the trie. + * Since these come first, they will override any conflicting termcap + * or terminfo escape sequences defined below. An escape sequence is + * considered conflicting if one is a prefix of the other. + * So, without TERMCAP_WINS, there will likely be some termcap/terminfo + * escape sequences that don't work, because they conflict with default + * sequences defined here. + */ + if(!termcap_wins) + setup_dflt_esc_seq(); + + /* + * add termcap/info escape sequences to the trie... + */ + + if(_ku != NULL && _kd != NULL && _kl != NULL && _kr != NULL){ + kpinsert(_ku, KEY_UP, termcap_wins); + kpinsert(_kd, KEY_DOWN, termcap_wins); + kpinsert(_kl, KEY_LEFT, termcap_wins); + kpinsert(_kr, KEY_RIGHT, termcap_wins); + } + + if(_kppu != NULL && _kppd != NULL){ + kpinsert(_kppu, KEY_PGUP, termcap_wins); + kpinsert(_kppd, KEY_PGDN, termcap_wins); + } + + kpinsert(_kphome, KEY_HOME, termcap_wins); + kpinsert(_kpend, KEY_END, termcap_wins); + kpinsert(_kpdel, KEY_DEL, termcap_wins); + + kpinsert(_kf1, F1, termcap_wins); + kpinsert(_kf2, F2, termcap_wins); + kpinsert(_kf3, F3, termcap_wins); + kpinsert(_kf4, F4, termcap_wins); + kpinsert(_kf5, F5, termcap_wins); + kpinsert(_kf6, F6, termcap_wins); + kpinsert(_kf7, F7, termcap_wins); + kpinsert(_kf8, F8, termcap_wins); + kpinsert(_kf9, F9, termcap_wins); + kpinsert(_kf10, F10, termcap_wins); + kpinsert(_kf11, F11, termcap_wins); + kpinsert(_kf12, F12, termcap_wins); + + /* + * Add default keypad sequences to the trie. + * Since these come after the termcap/terminfo escape sequences above, + * the termcap/info sequences will override any conflicting default + * escape sequences defined here. + * So, with TERMCAP_WINS, some of the default sequences will be missing. + * This means that you'd better get all of your termcap/terminfo entries + * correct if you define TERMCAP_WINS. + */ + if(termcap_wins) + setup_dflt_esc_seq(); + + if(Pmaster) + return(0); + else + return(TRUE); +} + +static int +tinfoopen(void) +{ + int row, col; + + /* + * determine the terminal's communication speed and decide + * if we need to do optimization ... + */ + if(ttisslow()) + term_capabilities |= TT_OPTIMIZE; + + col = _tcolumns; + row = _tlines; + if(row >= 0) + row--; + + ttgetwinsz(&row, &col); + term.t_nrow = (short) row; + term.t_ncol = (short) col; + + if(_cleartoeoln != NULL) /* able to use clear to EOL? */ + term_capabilities |= TT_EOLEXIST; + else + term_capabilities &= ~TT_EOLEXIST; + + if(_setinverse != NULL) + term_capabilities |= TT_REVEXIST; + else + term_capabilities &= ~TT_REVEXIST; + + if(_deletechar == NULL && (_startdelete == NULL || _enddelete == NULL)) + term_capabilities &= ~TT_DELCHAR; + + if(_insertchar == NULL && (_startinsert == NULL || _endinsert == NULL)) + term_capabilities &= ~TT_INSCHAR; + + if((_scrollregion == NULL || _scrolldown == NULL || _scrollup == NULL) + && (_deleteline == NULL || _insertline == NULL)) + term_capabilities &= ~TT_SCROLLEXIST; + + if(_clearscreen == NULL || _moveto == NULL || _up == NULL){ + if(Pmaster == NULL){ + puts("Incomplete terminfo entry\n"); + exit(1); + } + } + + ttopen(); + + if(_termcap_init && !Pmaster) { + putpad(_termcap_init); /* any init terminfo requires */ + if (_scrollregion) + putpad(tgoto(_scrollregion, term.t_nrow, 0)) ; + } + + /* + * Initialize UW-modified NCSA telnet to use its functionkeys + */ + if((gmode & MDFKEY) && Pmaster == NULL) + puts("\033[99h"); + + /* return ignored */ + return(0); +} + + +static int +tinfoclose(void) +{ + if(!Pmaster){ + if(gmode&MDFKEY) + puts("\033[99l"); /* reset UW-NCSA telnet keys */ + + if(_termcap_end) /* any clean up terminfo requires */ + putpad(_termcap_end); + } + + ttclose(); + + /* return ignored */ + return(0); +} + + +/* + * tinfoinsert - insert a character at the current character position. + * _insertchar takes precedence. + */ +static void +tinfoinsert(UCS ch) +{ + if(_insertchar != NULL){ + putpad(_insertchar); + ttputc(ch); + } + else{ + putpad(_startinsert); + ttputc(ch); + putpad(_endinsert); + } +} + + +/* + * tinfodelete - delete a character at the current character position. + */ +static void +tinfodelete(void) +{ + if(_startdelete == NULL && _enddelete == NULL) + putpad(_deletechar); + else{ + putpad(_startdelete); + putpad(_deletechar); + putpad(_enddelete); + } +} + + +/* + * o_scrolldown() - open a line at the given row position. + * use either region scrolling or deleteline/insertline + * to open a new line. + */ +int +o_scrolldown(int row, int n) +{ + register int i; + + if(_scrollregion != NULL){ + putpad(tgoto(_scrollregion, term.t_nrow - (term.t_mrow+1), row)); + tinfomove(row, 0); + for(i = 0; i < n; i++) + putpad((_scrollup != NULL && *_scrollup != '\0') ? _scrollup : "\n" ); + putpad(tgoto(_scrollregion, term.t_nrow, 0)); + tinfomove(row, 0); + } + else{ + /* + * this code causes a jiggly motion of the keymenu when scrolling + */ + for(i = 0; i < n; i++){ + tinfomove(term.t_nrow - (term.t_mrow+1), 0); + putpad(_deleteline); + tinfomove(row, 0); + putpad(_insertline); + } +#ifdef NOWIGGLYLINES + /* + * this code causes a sweeping motion up and down the display + */ + tinfomove(term.t_nrow - term.t_mrow - n, 0); + for(i = 0; i < n; i++) + putpad(_deleteline); + tinfomove(row, 0); + for(i = 0; i < n; i++) + putpad(_insertline); +#endif + } + + /* return ignored */ + return(0); +} + + +/* + * o_scrollup() - open a line at the given row position. + * use either region scrolling or deleteline/insertline + * to open a new line. + */ +int +o_scrollup(int row, int n) +{ + register int i; + + if(_scrollregion != NULL){ + putpad(tgoto(_scrollregion, term.t_nrow - (term.t_mrow+1), row)); + /* setting scrolling region moves cursor to home */ + tinfomove(term.t_nrow-(term.t_mrow+1), 0); + for(i = 0;i < n; i++) + putpad((_scrolldown == NULL || _scrolldown[0] == '\0') ? "\n" + : _scrolldown); + putpad(tgoto(_scrollregion, term.t_nrow, 0)); + tinfomove(2, 0); + } + else{ + for(i = 0; i < n; i++){ + tinfomove(row, 0); + putpad(_deleteline); + tinfomove(term.t_nrow - (term.t_mrow+1), 0); + putpad(_insertline); + } +#ifdef NOWIGGLYLINES + /* see note above */ + tinfomove(row, 0); + for(i = 0; i < n; i++) + putpad(_deleteline); + tinfomove(term.t_nrow - term.t_mrow - n, 0); + for(i = 0;i < n; i++) + putpad(_insertline); +#endif + } + + /* return ignored */ + return(0); +} + + + +/* + * o_insert - use terminfo to optimized character insert + * returns: true if it optimized output, false otherwise + */ +int +o_insert(UCS c) +{ + if(term_capabilities & TT_INSCHAR){ + tinfoinsert(c); + return(1); /* no problems! */ + } + + return(0); /* can't do it. */ +} + + +/* + * o_delete - use terminfo to optimized character insert + * returns true if it optimized output, false otherwise + */ +int +o_delete(void) +{ + if(term_capabilities & TT_DELCHAR){ + tinfodelete(); + return(1); /* deleted, no problem! */ + } + + return(0); /* no dice. */ +} + + +static int +tinfomove(int row, int col) +{ + putpad(tgoto(_moveto, col, row)); + + /* return ignored */ + return(0); +} + + +static int +tinfoeeol(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)){ + extern int ttcol, ttrow; + + starting_col = ttcol; + starting_line = ttrow; + last_bg_color = pico_get_last_bg_color(); + pico_set_nbg_color(); + for(c = ttcol; c < term.t_ncol; c++) + ttputc(' '); + + tinfomove(starting_line, starting_col); + if(last_bg_color){ + pico_set_bg_color(last_bg_color); + free(last_bg_color); + } + } + else if(_cleartoeoln) + putpad(_cleartoeoln); + + /* return ignored */ + return(0); +} + + +static int +tinfoeeop(void) +{ + int i, starting_col, starting_row; + + /* + * 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)){ + extern int ttcol, ttrow; + + starting_col = ttcol; + starting_row = ttrow; + tinfoeeol(); /* rest of this line */ + for(i = ttrow+1; i <= term.t_nrow; i++){ /* the remaining lines */ + tinfomove(i, 0); + tinfoeeol(); + } + + tinfomove(starting_row, starting_col); + } + else if(_cleartoeos) + putpad(_cleartoeos); + + /* return ignored */ + return(0); +} + + +static int +tinforev(int state) /* change reverse video status */ +{ /* FALSE = normal video, TRUE = rev video */ + if(state) + StartInverse(); + else + EndInverse(); + + return(1); +} + + +static int +tinfobeep(void) +{ + ttputc(BELL); + + /* return ignored */ + return(0); +} + + +void +putpad(char *str) +{ + tputs(str, 1, putchar); +} + +#elif HAS_TERMCAP + +/* + * termcap-based terminal i/o and control routines + */ + + +/* internal prototypes */ +static int tcapmove(int, int); +static int tcapeeol(void); +static int tcapeeop(void); +static int tcapbeep(void); +static int tcaprev(int); +static int tcapopen(void); +static int tcapterminalinfo(int); +static int tcapclose(void); +static void setup_dflt_esc_seq(void); +static void tcapinsert(UCS); +static void tcapdelete(void); + +extern int tput(); +extern char *tgoto(char *, int, int); + +/* + * This number used to be 315. No doubt there was a reason for that but we + * don't know what it was. It's a bit of a hassle to make it dynamic, and most + * modern systems seem to be using terminfo, so we'll just change it to 800. + * We weren't stopping on overflow before, so we'll do that, too. + */ +#define TCAPSLEN 800 +char tcapbuf[TCAPSLEN]; +int _tlines, _tcolumns; +char *_clearscreen, *_moveto, *_up, *_down, *_right, *_left, + *_setinverse, *_clearinverse, + *_setunderline, *_clearunderline, + *_setbold, *_clearallattr, /* there is no clear only bold! */ + *_cleartoeoln, *_cleartoeos, + *_deleteline, /* delete line */ + *_insertline, /* insert line */ + *_scrollregion, /* define a scrolling region, vt100 */ + *_insertchar, /* insert character, preferable to : */ + *_startinsert, /* set insert mode and, */ + *_endinsert, /* end insert mode */ + *_deletechar, /* delete character */ + *_startdelete, /* set delete mode and, */ + *_enddelete, /* end delete mode */ + *_scrolldown, /* scroll down */ + *_scrollup, /* scroll up */ + *_termcap_init, /* string to start termcap */ + *_termcap_end, /* string to end termcap */ + *_op, *_oc, *_setaf, *_setab, *_setf, *_setb, *_scp; +int _colors, _pairs, _bce; +char term_name[40]; + +TERM term = { + NROW-1, + NCOL, + MARGIN, + MROW, + tcapopen, + tcapterminalinfo, + tcapclose, + ttgetc, + ttputc, + ttflush, + tcapmove, + tcapeeol, + tcapeeop, + tcapbeep, + tcaprev +}; + + +/* + * Add default keypad sequences to the trie. + */ +static void +setup_dflt_esc_seq(void) +{ + /* + * this is sort of a hack, but it allows us to use + * the function keys on pc's running telnet + */ + + /* + * UW-NDC/UCS vt10[02] application mode. + */ + kpinsert("\033OP", F1, 1); + kpinsert("\033OQ", F2, 1); + kpinsert("\033OR", F3, 1); + kpinsert("\033OS", F4, 1); + kpinsert("\033Op", F5, 1); + kpinsert("\033Oq", F6, 1); + kpinsert("\033Or", F7, 1); + kpinsert("\033Os", F8, 1); + kpinsert("\033Ot", F9, 1); + kpinsert("\033Ou", F10, 1); + kpinsert("\033Ov", F11, 1); + kpinsert("\033Ow", F12, 1); + + /* + * DEC vt100, ANSI and cursor key mode. + */ + kpinsert("\033OA", KEY_UP, 1); + kpinsert("\033OB", KEY_DOWN, 1); + kpinsert("\033OC", KEY_RIGHT, 1); + kpinsert("\033OD", KEY_LEFT, 1); + + /* + * special keypad functions + */ + kpinsert("\033[4J", KEY_PGUP, 1); + kpinsert("\033[3J", KEY_PGDN, 1); + kpinsert("\033[2J", KEY_HOME, 1); + kpinsert("\033[N", KEY_END, 1); + + /* + * vt220? + */ + kpinsert("\033[5~", KEY_PGUP, 1); + kpinsert("\033[6~", KEY_PGDN, 1); + kpinsert("\033[1~", KEY_HOME, 1); + kpinsert("\033[4~", KEY_END, 1); + + /* + * konsole, XTerm (XFree 4.x.x) keyboard setting + */ + kpinsert("\033[H", KEY_HOME, 1); + kpinsert("\033[F", KEY_END, 1); + + /* + * gnome-terminal 2.6.0, don't know why it + * changed from 2.2.1 + */ + kpinsert("\033OH", KEY_HOME, 1); + kpinsert("\033OF", KEY_END, 1); + + /* + * "\033[2~" was common for KEY_HOME in a quick survey + * of terminals (though typically the Insert key). + * Teraterm 2.33 sends the following escape sequences, + * which is quite incompatible with everything + * else: + * Home: "\033[2~" End: "\033[5~" PgUp: "\033[3~" + * PgDn: "\033[6~" + * The best thing to do would be to fix TeraTerm + * keymappings or to tweak terminfo. + */ + + /* + * ANSI mode. + */ + kpinsert("\033[=a", F1, 1); + kpinsert("\033[=b", F2, 1); + kpinsert("\033[=c", F3, 1); + kpinsert("\033[=d", F4, 1); + kpinsert("\033[=e", F5, 1); + kpinsert("\033[=f", F6, 1); + kpinsert("\033[=g", F7, 1); + kpinsert("\033[=h", F8, 1); + kpinsert("\033[=i", F9, 1); + kpinsert("\033[=j", F10, 1); + kpinsert("\033[=k", F11, 1); + kpinsert("\033[=l", F12, 1); + + /* + * DEC vt100, ANSI and cursor key mode reset. + */ + kpinsert("\033[A", KEY_UP, 1); + kpinsert("\033[B", KEY_DOWN, 1); + kpinsert("\033[C", KEY_RIGHT, 1); + kpinsert("\033[D", KEY_LEFT, 1); + + /* + * DEC vt52 mode. + */ + kpinsert("\033A", KEY_UP, 1); + kpinsert("\033B", KEY_DOWN, 1); + kpinsert("\033C", KEY_RIGHT, 1); + kpinsert("\033D", KEY_LEFT, 1); + + /* + * DEC vt52 application keys, and some Zenith 19. + */ + kpinsert("\033?r", KEY_DOWN, 1); + kpinsert("\033?t", KEY_LEFT, 1); + kpinsert("\033?v", KEY_RIGHT, 1); + kpinsert("\033?x", KEY_UP, 1); + + /* + * Some Ctrl-Arrow keys + */ + kpinsert("\033[5A", CTRL_KEY_UP, 1); + kpinsert("\033[5B", CTRL_KEY_DOWN, 1); + kpinsert("\033[5C", CTRL_KEY_RIGHT, 1); + kpinsert("\033[5D", CTRL_KEY_LEFT, 1); + + kpinsert("\033[1;5A", CTRL_KEY_UP, 1); + kpinsert("\033[1;5B", CTRL_KEY_DOWN, 1); + kpinsert("\033[1;5C", CTRL_KEY_RIGHT, 1); + kpinsert("\033[1;5D", CTRL_KEY_LEFT, 1); + + /* + * Map some shift+up/down/left/right to their shiftless counterparts + */ + kpinsert("\033[1;2A", KEY_UP, 1); + kpinsert("\033[1;2B", KEY_DOWN, 1); + kpinsert("\033[1;2C", KEY_RIGHT, 1); + kpinsert("\033[1;2D", KEY_LEFT, 1); + kpinsert("\033[a", KEY_UP, 1); + kpinsert("\033[b", KEY_DOWN, 1); + kpinsert("\033[c", KEY_RIGHT, 1); + kpinsert("\033[d", KEY_LEFT, 1); + + /* + * Sun Console sequences. + */ + kpinsert("\033[1", KEY_SWALLOW_Z, 1); + kpinsert("\033[215", KEY_SWAL_UP, 1); + kpinsert("\033[217", KEY_SWAL_LEFT, 1); + kpinsert("\033[219", KEY_SWAL_RIGHT, 1); + kpinsert("\033[221", KEY_SWAL_DOWN, 1); + + /* + * Kermit App Prog Cmd, gobble until ESC \ (kermit should intercept this) + */ + kpinsert("\033_", KEY_KERMIT, 1); + + /* + * Fake a control character. + */ + kpinsert("\033\033", KEY_DOUBLE_ESC, 1); +} + + +/* + * Read termcap and set some global variables. Initialize input trie to + * decode escape sequences. + */ +static int +tcapterminalinfo(int termcap_wins) +{ + char *p, *tgetstr(); + char tcbuf[2*1024]; + char *tv_stype; + char err_str[72]; + int err; + char *_ku, *_kd, *_kl, *_kr, + *_kppu, *_kppd, *_kphome, *_kpend, *_kpdel, + *_kf1, *_kf2, *_kf3, *_kf4, *_kf5, *_kf6, + *_kf7, *_kf8, *_kf9, *_kf10, *_kf11, *_kf12; + + if (!(tv_stype = getenv("TERM")) || !strncpy(term_name, tv_stype, sizeof(term_name))){ + if(Pmaster){ + return(-1); + } + else{ + puts("Environment variable TERM not defined!"); + exit(1); + } + } + + term_name[sizeof(term_name)-1] = '\0'; + + if((err = tgetent(tcbuf, tv_stype)) != 1){ + if(Pmaster){ + return(err - 2); + } + else{ + snprintf(err_str, sizeof(err_str), "Unknown terminal type %s!", tv_stype); + puts(err_str); + exit(1); + } + } + + p = tcapbuf; + + _clearscreen = tgetstr("cl", &p); + _moveto = tgetstr("cm", &p); + _up = tgetstr("up", &p); + _down = tgetstr("do", &p); + _right = tgetstr("nd", &p); + _left = tgetstr("bs", &p); + _setinverse = tgetstr("so", &p); + _clearinverse = tgetstr("se", &p); + _setunderline = tgetstr("us", &p); + _clearunderline = tgetstr("ue", &p); + _setbold = tgetstr("md", &p); + _clearallattr = tgetstr("me", &p); + _cleartoeoln = tgetstr("ce", &p); + _cleartoeos = tgetstr("cd", &p); + _deletechar = tgetstr("dc", &p); + _insertchar = tgetstr("ic", &p); + _startinsert = tgetstr("im", &p); + _endinsert = tgetstr("ei", &p); + _deleteline = tgetstr("dl", &p); + _insertline = tgetstr("al", &p); + _scrollregion = tgetstr("cs", &p); + _scrolldown = tgetstr("sf", &p); + _scrollup = tgetstr("sr", &p); + _termcap_init = tgetstr("ti", &p); + _termcap_end = tgetstr("te", &p); + _startdelete = tgetstr("dm", &p); + _enddelete = tgetstr("ed", &p); + _ku = tgetstr("ku", &p); + _kd = tgetstr("kd", &p); + _kl = tgetstr("kl", &p); + _kr = tgetstr("kr", &p); + _kppu = tgetstr("kP", &p); + _kppd = tgetstr("kN", &p); + _kphome = tgetstr("kh", &p); + _kpend = tgetstr("kH", &p); + _kpdel = tgetstr("kD", &p); + _kf1 = tgetstr("k1", &p); + _kf2 = tgetstr("k2", &p); + _kf3 = tgetstr("k3", &p); + _kf4 = tgetstr("k4", &p); + _kf5 = tgetstr("k5", &p); + _kf6 = tgetstr("k6", &p); + _kf7 = tgetstr("k7", &p); + _kf8 = tgetstr("k8", &p); + _kf9 = tgetstr("k9", &p); + if((_kf10 = tgetstr("k;", &p)) == NULL) + _kf10 = tgetstr("k0", &p); + _kf11 = tgetstr("F1", &p); + _kf12 = tgetstr("F2", &p); + + _colors = tgetnum("Co"); + _pairs = tgetnum("pa"); + _setaf = tgetstr("AF", &p); + _setab = tgetstr("AB", &p); + _setf = tgetstr("Sf", &p); + _setb = tgetstr("Sb", &p); + _scp = tgetstr("sp", &p); + _op = tgetstr("op", &p); + _oc = tgetstr("oc", &p); + _bce = tgetflag("ut"); + + if (p >= &tcapbuf[TCAPSLEN]){ + puts("Terminal description too big!\n"); + if(Pmaster) + return(-3); + else + exit(1); + } + + _tlines = tgetnum("li"); + if(_tlines == -1){ + char *er; + int rr; + + /* tgetnum failed, try $LINES */ + er = getenv("LINES"); + if(er && (rr = atoi(er)) > 0) + _tlines = rr; + } + + _tcolumns = tgetnum("co"); + if(_tcolumns == -1){ + char *ec; + int cc; + + /* tgetnum failed, try $COLUMNS */ + ec = getenv("COLUMNS"); + if(ec && (cc = atoi(ec)) > 0) + _tcolumns = cc; + } + + /* + * Add default keypad sequences to the trie. + * Since these come first, they will override any conflicting termcap + * or terminfo escape sequences defined below. An escape sequence is + * considered conflicting if one is a prefix of the other. + * So, without TERMCAP_WINS, there will likely be some termcap/terminfo + * escape sequences that don't work, because they conflict with default + * sequences defined here. + */ + if(!termcap_wins) + setup_dflt_esc_seq(); + + /* + * add termcap/info escape sequences to the trie... + */ + + if(_ku != NULL && _kd != NULL && _kl != NULL && _kr != NULL){ + kpinsert(_ku, KEY_UP, termcap_wins); + kpinsert(_kd, KEY_DOWN, termcap_wins); + kpinsert(_kl, KEY_LEFT, termcap_wins); + kpinsert(_kr, KEY_RIGHT, termcap_wins); + } + + if(_kppu != NULL && _kppd != NULL){ + kpinsert(_kppu, KEY_PGUP, termcap_wins); + kpinsert(_kppd, KEY_PGDN, termcap_wins); + } + + kpinsert(_kphome, KEY_HOME, termcap_wins); + kpinsert(_kpend, KEY_END, termcap_wins); + kpinsert(_kpdel, KEY_DEL, termcap_wins); + + kpinsert(_kf1, F1, termcap_wins); + kpinsert(_kf2, F2, termcap_wins); + kpinsert(_kf3, F3, termcap_wins); + kpinsert(_kf4, F4, termcap_wins); + kpinsert(_kf5, F5, termcap_wins); + kpinsert(_kf6, F6, termcap_wins); + kpinsert(_kf7, F7, termcap_wins); + kpinsert(_kf8, F8, termcap_wins); + kpinsert(_kf9, F9, termcap_wins); + kpinsert(_kf10, F10, termcap_wins); + kpinsert(_kf11, F11, termcap_wins); + kpinsert(_kf12, F12, termcap_wins); + + /* + * Add default keypad sequences to the trie. + * Since these come after the termcap/terminfo escape sequences above, + * the termcap/info sequences will override any conflicting default + * escape sequences defined here. + * So, with TERMCAP_WINS, some of the default sequences will be missing. + * This means that you'd better get all of your termcap/terminfo entries + * correct if you define TERMCAP_WINS. + */ + if(termcap_wins) + setup_dflt_esc_seq(); + + if(Pmaster) + return(0); + else + return(TRUE); +} + +static int +tcapopen(void) +{ + int row, col; + + /* + * determine the terminal's communication speed and decide + * if we need to do optimization ... + */ + if(ttisslow()) + term_capabilities |= TT_OPTIMIZE; + + col = _tcolumns; + row = _tlines; + if(row >= 0) + row--; + + ttgetwinsz(&row, &col); + term.t_nrow = (short) row; + term.t_ncol = (short) col; + + if(_cleartoeoln != NULL) /* able to use clear to EOL? */ + term_capabilities |= TT_EOLEXIST; + else + term_capabilities &= ~TT_EOLEXIST; + + if(_setinverse != NULL) + term_capabilities |= TT_REVEXIST; + else + term_capabilities &= ~TT_REVEXIST; + + if(_deletechar == NULL && (_startdelete == NULL || _enddelete == NULL)) + term_capabilities &= ~TT_DELCHAR; + + if(_insertchar == NULL && (_startinsert == NULL || _endinsert == NULL)) + term_capabilities &= ~TT_INSCHAR; + + if((_scrollregion == NULL || _scrolldown == NULL || _scrollup == NULL) + && (_deleteline == NULL || _insertline == NULL)) + term_capabilities &= ~TT_SCROLLEXIST; + + if(_clearscreen == NULL || _moveto == NULL || _up == NULL){ + if(Pmaster == NULL){ + puts("Incomplete termcap entry\n"); + exit(1); + } + } + + ttopen(); + + if(_termcap_init && !Pmaster) { + putpad(_termcap_init); /* any init termcap requires */ + if (_scrollregion) + putpad(tgoto(_scrollregion, term.t_nrow, 0)) ; + } + + /* + * Initialize UW-modified NCSA telnet to use it's functionkeys + */ + if(gmode&MDFKEY && Pmaster == NULL) + puts("\033[99h"); + + /* return ignored */ + return(0); +} + + +static int +tcapclose(void) +{ + if(!Pmaster){ + if(gmode&MDFKEY) + puts("\033[99l"); /* reset UW-NCSA telnet keys */ + + if(_termcap_end) /* any cleanup termcap requires */ + putpad(_termcap_end); + } + + ttclose(); + + /* return ignored */ + return(0); +} + + + +/* + * tcapinsert - insert a character at the current character position. + * _insertchar takes precedence. + */ +static void +tcapinsert(UCS ch) +{ + if(_insertchar != NULL){ + putpad(_insertchar); + ttputc(ch); + } + else{ + putpad(_startinsert); + ttputc(ch); + putpad(_endinsert); + } +} + + +/* + * tcapdelete - delete a character at the current character position. + */ +static void +tcapdelete(void) +{ + if(_startdelete == NULL && _enddelete == NULL) + putpad(_deletechar); + else{ + putpad(_startdelete); + putpad(_deletechar); + putpad(_enddelete); + } +} + + +/* + * o_scrolldown - open a line at the given row position. + * use either region scrolling or deleteline/insertline + * to open a new line. + */ +int +o_scrolldown(int row, int n) +{ + register int i; + + if(_scrollregion != NULL){ + putpad(tgoto(_scrollregion, term.t_nrow - (term.t_mrow+1), row)); + tcapmove(row, 0); + for(i = 0; i < n; i++) + putpad( (_scrollup != NULL && *_scrollup != '\0') + ? _scrollup : "\n" ); + putpad(tgoto(_scrollregion, term.t_nrow, 0)); + tcapmove(row, 0); + } + else{ + /* + * this code causes a jiggly motion of the keymenu when scrolling + */ + for(i = 0; i < n; i++){ + tcapmove(term.t_nrow - (term.t_mrow+1), 0); + putpad(_deleteline); + tcapmove(row, 0); + putpad(_insertline); + } +#ifdef NOWIGGLYLINES + /* + * this code causes a sweeping motion up and down the display + */ + tcapmove(term.t_nrow - term.t_mrow - n, 0); + for(i = 0; i < n; i++) + putpad(_deleteline); + tcapmove(row, 0); + for(i = 0; i < n; i++) + putpad(_insertline); +#endif + } + + /* return ignored */ + return(0); +} + + +/* + * o_scrollup - open a line at the given row position. + * use either region scrolling or deleteline/insertline + * to open a new line. + */ +int +o_scrollup(int row, int n) +{ + register int i; + + if(_scrollregion != NULL){ + putpad(tgoto(_scrollregion, term.t_nrow - (term.t_mrow+1), row)); + /* setting scrolling region moves cursor to home */ + tcapmove(term.t_nrow-(term.t_mrow+1), 0); + for(i = 0;i < n; i++) + putpad((_scrolldown == NULL || _scrolldown[0] == '\0') + ? "\n" : _scrolldown); + putpad(tgoto(_scrollregion, term.t_nrow, 0)); + tcapmove(2, 0); + } + else{ + for(i = 0; i < n; i++){ + tcapmove(row, 0); + putpad(_deleteline); + tcapmove(term.t_nrow - (term.t_mrow+1), 0); + putpad(_insertline); + } +#ifdef NOWIGGLYLINES + /* see note above */ + tcapmove(row, 0); + for(i = 0; i < n; i++) + putpad(_deleteline); + tcapmove(term.t_nrow - term.t_mrow - n, 0); + for(i = 0;i < n; i++) + putpad(_insertline); +#endif + } + + /* return ignored */ + return(0); +} + + +/* + * o_insert - use termcap info to optimized character insert + * returns: true if it optimized output, false otherwise + */ +int +o_insert(UCS c) +{ + if(term_capabilities & TT_INSCHAR){ + tcapinsert(c); + return(1); /* no problems! */ + } + + return(0); /* can't do it. */ +} + + +/* + * o_delete - use termcap info to optimized character insert + * returns true if it optimized output, false otherwise + */ +int +o_delete(void) +{ + if(term_capabilities & TT_DELCHAR){ + tcapdelete(); + return(1); /* deleted, no problem! */ + } + + return(0); /* no dice. */ +} + + +static int +tcapmove(int row, int col) +{ + putpad(tgoto(_moveto, col, row)); + + /* return ignored */ + return(0); +} + + +static int +tcapeeol(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)){ + extern int ttcol, ttrow; + + starting_col = ttcol; + starting_line = ttrow; + last_bg_color = pico_get_last_bg_color(); + pico_set_nbg_color(); + for(c = ttcol; c < term.t_ncol; c++) + ttputc(' '); + + tcapmove(starting_line, starting_col); + if(last_bg_color){ + pico_set_bg_color(last_bg_color); + free(last_bg_color); + } + } + else if(_cleartoeoln) + putpad(_cleartoeoln); + + /* return ignored */ + return(0); +} + + +static int +tcapeeop(void) +{ + int i, starting_col, starting_row; + + /* + * 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)){ + extern int ttcol, ttrow; + + starting_col = ttcol; + starting_row = ttrow; + tcapeeol(); /* rest of this line */ + for(i = ttrow+1; i <= term.t_nrow; i++){ /* the remaining lines */ + tcapmove(i, 0); + tcapeeol(); + } + + tcapmove(starting_row, starting_col); + } + else if(_cleartoeos) + putpad(_cleartoeos); + + /* return ignored */ + return(0); +} + + +static int +tcaprev(int state) /* change reverse video status */ +{ /* FALSE = normal video, TRUE = reverse video */ + if(state) + StartInverse(); + else + EndInverse(); + + return(1); +} + + +static int +tcapbeep(void) +{ + ttputc(BELL); + + /* return ignored */ + return(0); +} + + +void +putpad(char *str) +{ + tputs(str, 1, putchar); +} + +#else /* HARD_CODED_ANSI_TERMINAL */ + +/* + * ANSI-specific terminal i/o and control routines + */ + + +#define BEL 0x07 /* BEL character. */ +#define ESC 0x1B /* ESC character. */ + + +extern int ttflush(); + +extern int ansimove(int, int); +extern int ansieeol(void); +extern int ansieeop(void); +extern int ansibeep(void); +extern int ansiparm(int); +extern int ansiopen(void); +extern int ansiterminalinfo(int); +extern int ansirev(int); + +/* + * Standard terminal interface dispatch table. Most of the fields point into + * "termio" code. + */ +#if defined(VAX) && !defined(__ALPHA) +globaldef +#endif + +TERM term = { + NROW-1, + NCOL, + MARGIN, + MROW, + ansiopen, + ansiterminalinfo, + ttclose, + ttgetc, + ttputc, + ttflush, + ansimove, + ansieeol, + ansieeop, + ansibeep, + ansirev +}; + +int +ansimove(int row, int col) +{ + ttputc(ESC); + ttputc('['); + ansiparm(row+1); + ttputc(';'); + ansiparm(col+1); + ttputc('H'); +} + +int +ansieeol(void) +{ + ttputc(ESC); + ttputc('['); + ttputc('K'); +} + +int +ansieeop(void) +{ + ttputc(ESC); + ttputc('['); + ttputc('J'); +} + +int +ansirev(int state) /* change reverse video state */ +{ /* TRUE = reverse, FALSE = normal */ + static int PrevState = 0; + + if(state != PrevState) { + PrevState = state ; + ttputc(ESC); + ttputc('['); + ttputc(state ? '7': '0'); + ttputc('m'); + } +} + +int +ansibeep(void) +{ + ttputc(BEL); + ttflush(); +} + +int +ansiparm(int n) +{ + register int q; + + q = n/10; + if (q != 0) + ansiparm(q); + ttputc((n%10) + '0'); +} + + +int +ansiterminalinfo(int termcap_wins) +{ +#if V7 + register char *cp; + char *getenv(); + + if ((cp = getenv("TERM")) == NULL) { + puts("Shell variable TERM not defined!"); + exit(1); + } + if (strcmp(cp, "vt100") != 0) { + puts("Terminal type not 'vt100'!"); + exit(1); + } +#endif + /* revexist = TRUE; dead code? */ +} + +int +ansiopen(void) +{ + ttopen(); +} + + +#endif /* HARD_CODED_ANSI_TERMINAL */ +#else /* _WINDOWS */ + +/* These are all just noops in Windows */ + +unsigned +tthascap(void) +{ + return(0); +} + +/* + * o_insert - optimize screen insert of char c + */ +int +o_insert(UCS c) +{ + return(0); +} + + +/* + * o_delete - optimized character deletion + */ +int +o_delete(void) +{ + return(0); +} + + + +#endif /* _WINDOWS */ diff --git a/pico/osdep/terminal.h b/pico/osdep/terminal.h new file mode 100644 index 00000000..6449c32c --- /dev/null +++ b/pico/osdep/terminal.h @@ -0,0 +1,50 @@ +/* + * $Id: terminal.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 PICO_OSDEP_TERMINAL_INCLUDED +#define PICO_OSDEP_TERMINAL_INCLUDED + + +/* + * Useful definitions + */ +#define NROW DEFAULT_LINES_ON_TERMINAL +#define NCOL DEFAULT_COLUMNS_ON_TERMINAL + +#define TT_OPTIMIZE 0x01 /* optimize flag(cf line speed) */ +#define TT_EOLEXIST 0x02 /* does clear to EOL exist */ +#define TT_SCROLLEXIST 0x04 /* does insert line exist */ +#define TT_REVEXIST 0x08 /* does reverse video exist? */ +#define TT_INSCHAR 0x10 /* does insert character exist */ +#define TT_DELCHAR 0x20 /* does delete character exist */ + + +#define TERM_OPTIMIZE (tthascap() & TT_OPTIMIZE) +#define TERM_EOLEXIST (tthascap() & TT_EOLEXIST) +#define TERM_SCROLLEXIST (tthascap() & TT_SCROLLEXIST) +#define TERM_REVEXIST (tthascap() & TT_REVEXIST) +#define TERM_INSCHAR (tthascap() & TT_INSCHAR) +#define TERM_DELCHAR (tthascap() & TT_DELCHAR) + + +/* exported prototypes */ +unsigned tthascap(void); +#if HAS_TERMINFO || HAS_TERMCAP +void putpad(char *); +#endif + + +#endif /* PICO_OSDEP_TERMINAL_INCLUDED */ diff --git a/pico/osdep/truncate.c b/pico/osdep/truncate.c new file mode 100644 index 00000000..8330338e --- /dev/null +++ b/pico/osdep/truncate.c @@ -0,0 +1,20 @@ +/* + * ======================================================================== + * 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 + * + * ======================================================================== + * + * + * Tim Rice tim@trr.metro.net Mon Jun 3 16:57:26 PDT 1996 + * + * a quick and dirty trancate() + * Altos System V (5.3.1) does not have one + * neither does SCO Open Server Enterprise 3.0 + * + */ diff --git a/pico/osdep/truncate.h b/pico/osdep/truncate.h new file mode 100644 index 00000000..73c2decf --- /dev/null +++ b/pico/osdep/truncate.h @@ -0,0 +1,21 @@ +/* + * $Id: truncate.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 PICO_OSDEP_TRUNCATE_INCLUDED +#define PICO_OSDEP_TRUNCATE_INCLUDED + + +#endif /* PICO_OSDEP_TRUNCATE_INCLUDED */ diff --git a/pico/osdep/tty.c b/pico/osdep/tty.c new file mode 100644 index 00000000..100b37ea --- /dev/null +++ b/pico/osdep/tty.c @@ -0,0 +1,372 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: tty.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 + * + * ======================================================================== + * + * Program: tty routines + */ + +#include +#include + +#include "../estruct.h" +#include "../mode.h" +#include "../pico.h" +#include "../edef.h" +#include "../efunc.h" +#include "../keydefs.h" + +#include "signals.h" +#ifndef _WINDOWS +#include "terminal.h" +#include "raw.h" +#include "read.h" +#else +#include "mswin.h" +#endif /* _WINDOWS */ + +#ifdef MOUSE +#include "mouse.h" +#endif /* MOUSE */ + +#include "tty.h" + + +#ifndef _WINDOWS +/* + * ttopen - this function is called once to set up the terminal device + * streams. if called as pine composer, don't mess with + * tty modes, but set signal handlers. + */ +int +ttopen(void) +{ + if(Pmaster == NULL){ + Raw(1); +#ifdef MOUSE + if(gmode & MDMOUSE) + init_mouse(); +#endif /* MOUSE */ + xonxoff_proc(preserve_start_stop); + } + + picosigs(); + + return(1); +} + + +/* + * ttclose - this function gets called just before we go back home to + * the command interpreter. If called as pine composer, don't + * worry about modes, but set signals to default, pine will + * rewire things as needed. + */ +int +ttclose(void) +{ + if(Pmaster){ + signal(SIGHUP, SIG_DFL); +#ifdef SIGCONT + signal(SIGCONT, SIG_DFL); +#endif +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + signal(SIGWINCH, SIG_DFL); +#endif + } + else{ + Raw(0); +#ifdef MOUSE + end_mouse(); +#endif + } + + return(1); +} + + +/* + * ttgetc - Read a character from the terminal, performing no editing + * and doing no echo at all. + * + * Args: return_on_intr -- Function to get a single character from stdin, + * recorder -- If non-NULL, function used to record keystroke. + * bail_handler -- Function used to bail out on read error. + * + * Returns: The character read from stdin. + * Return_on_intr is returned if read is interrupted. + * If read error, BAIL_OUT is returned unless bail_handler is + * non-NULL, in which case it is called (and usually it exits). + * + * If recorder is non-null, it is used to record the keystroke. + */ +int +ttgetc(int return_on_intr, int (*recorder)(int), void (*bail_handler)(void)) +{ + int c; + + switch(c = read_one_char()){ + case READ_INTR: + return(return_on_intr); + + case BAIL_OUT: + if(bail_handler) + (*bail_handler)(); + else + return(BAIL_OUT); + + default: + return(recorder ? (*recorder)(c) : c); + } +} + + +/* + * Simple version of ttgetc with simple error handling + * + * Args: recorder -- If non-NULL, function used to record keystroke. + * bail_handler -- Function used to bail out on read error. + * + * Returns: The character read from stdin. + * If read error, BAIL_OUT is returned unless bail_handler is + * non-NULL, in which case it is called (and usually it exits). + * + * If recorder is non-null, it is used to record the keystroke. + * Retries if interrupted. + */ +int +simple_ttgetc(int (*recorder)(int), void (*bail_handler)(void)) +{ + int res; + unsigned char c; + + while((res = read(STDIN_FD, &c, 1)) <= 0) + if(!(res < 0 && errno == EINTR)) + (*bail_handler)(); + + return(recorder ? (*recorder)((int)c) : (int)c); +} + + +/* + * ttputc - Write a character to the display. + */ +int +ttputc(UCS ucs) +{ + unsigned char obuf[MAX(MB_LEN_MAX,32)]; + int r, i, width = 0, outchars = 0; + int ret = 0; + + if(ucs < 0x80) + return(putchar((unsigned char) ucs)); + + width = wcellwidth(ucs); + + if(width < 0){ + width = 1; + obuf[outchars++] = '?'; + } + else{ + /* + * Convert the ucs into the multibyte + * character that corresponds to the + * ucs in the users locale. + */ + outchars = wtomb((char *) obuf, ucs); + if(outchars < 0){ + width = 1; + obuf[0] = '?'; + outchars = 1; + } + } + + for(i = 0; i < outchars; i++){ + r = putchar(obuf[i]); + ret = (ret == EOF) ? EOF : r; + } + + return(ret); +} + + +/* + * ttflush - flush terminal buffer. Does real work where the terminal + * output is buffered up. A no-operation on systems where byte + * at a time terminal I/O is done. + */ +int +ttflush(void) +{ + return(fflush(stdout)); +} + + +/* + * ttresize - recompute the screen dimensions if necessary, and then + * adjust pico's internal buffers accordingly. + */ +void +ttresize(void) +{ + int row = -1, col = -1; + + ttgetwinsz(&row, &col); + resize_pico(row, col); +} + + + +/* + * ttgetwinsz - set global row and column values (if we can get them) + * and return. + */ +void +ttgetwinsz(int *row, int *col) +{ + extern int _tlines, _tcolumns; + + if(*row < 0) + *row = (_tlines > 0) ? _tlines - 1 : NROW - 1; + if(*col <= 0) + *col = (_tcolumns > 0) ? _tcolumns : NCOL; +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + { + struct winsize win; + + if (ioctl(0, TIOCGWINSZ, &win) == 0) { /* set to anything useful.. */ + if(win.ws_row) /* ... the tty drivers says */ + *row = win.ws_row - 1; + + if(win.ws_col) + *col = win.ws_col; + } + + signal(SIGWINCH, winch_handler); /* window size changes */ + } +#endif + + if(*col > NLINE-1) + *col = NLINE-1; +} + +#else /* _WINDOWS */ + +#define MARGIN 8 /* size of minimim margin and */ +#define SCRSIZ 64 /* scroll size for extended lines */ +#define MROW 2 /* rows in menu */ + +/* internal prototypes */ +int mswin_resize (int, int); + +/* + * Standard terminal interface dispatch table. Fields point to functions + * that operate the terminal. All these functions live in mswin.c, but + * this structure is defined here because it is specific to pico. + */ +TERM term = { + 0, + 0, + MARGIN, + MROW, + ttopen, + NULL, + ttclose, + NULL, /* was mswin_getc, but not used? */ + mswin_putc, + mswin_flush, + mswin_move, + mswin_eeol, + mswin_eeop, + mswin_beep, + mswin_rev +}; + +/* + * This function is called once to set up the terminal device streams. + */ +int +ttopen(void) +{ + int rows, columns; + + + mswin_getscreensize (&rows, &columns); + term.t_nrow = rows - 1; + term.t_ncol = columns; + /* term.t_scrsiz = (columns * 2) / 3; */ + + /* + * Do we implement optimized character insertion and deletion? + * o_insert() and o_delete() + */ + /* inschar = delchar = FALSE; */ + /* revexist = TRUE; dead code? */ + + mswin_setresizecallback (mswin_resize); + + init_mouse(); + + return(1); +} + +/* + * This function gets called just before we go back home to the command + * interpreter. + */ +int +ttclose(void) +{ + mswin_clearresizecallback (mswin_resize); + return(1); +} + +/* + * Flush terminal buffer. Does real work where the terminal output is buffered + * up. A no-operation on systems where byte at a time terminal I/O is done. + */ +int +ttflush(void) +{ + return(1); +} + +/* + * ttresize - recompute the screen dimensions if necessary, and then + * adjust pico's internal buffers accordingly. + */ +void +ttresize(void) +{ + int row, col; + + mswin_getscreensize(&row, &col); + resize_pico (row-1, col); +} + + +/* + * mswin_resize - windows specific callback to set pico's internal tables + * to new screen dimensions. + */ +int +mswin_resize(int row, int col) +{ + if (wheadp) + resize_pico (row-1, col); + return (0); +} + +#endif /* _WINDOWS */ diff --git a/pico/osdep/tty.h b/pico/osdep/tty.h new file mode 100644 index 00000000..ca0a7570 --- /dev/null +++ b/pico/osdep/tty.h @@ -0,0 +1,36 @@ +/* + * $Id: tty.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 PICO_OSDEP_TTY_INCLUDED +#define PICO_OSDEP_TTY_INCLUDED + + +#include + + +/* exported prototypes */ +int ttopen(void); +int ttclose(void); +int ttflush(void); +void ttresize(void); +#ifndef _WINDOWS +int ttgetc(int, int (*recorder)(int), void (*bail_handler)(void)); +int simple_ttgetc(int (*recorder)(int), void (*bail_handler)(void)); +int ttputc(UCS); +void ttgetwinsz(int *, int *); +#endif /* !_WINDOWS */ + +#endif /* PICO_OSDEP_TTY_INCLUDED */ diff --git a/pico/pico-win.lnk b/pico/pico-win.lnk new file mode 100644 index 00000000..e5d9a931 --- /dev/null +++ b/pico/pico-win.lnk @@ -0,0 +1,6 @@ +bdate+mswinver+main +pico +pico +libpico+libw+shell+toolhelp+llibcew+oldnames+commdlg+winsock+ +..\spell\spell +mswin.def diff --git a/pico/pico.c b/pico/pico.c new file mode 100644 index 00000000..166a3d3d --- /dev/null +++ b/pico/pico.c @@ -0,0 +1,1939 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: pico.c 921 2008-01-31 02:09:25Z 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 + * + * ======================================================================== + */ + +/* + * Program: Main Pine Composer routines + * + * + * WEEMACS/PICO NOTES: + * + * 01 Nov 89 - MicroEmacs 3.6 vastly pared down and becomes a function call + * weemacs() and plugged into the Pine mailer. Lots of unused + * MicroEmacs code laying around. + * + * 17 Jan 90 - weemacs() became weemacs() the composer. Header editing + * functions added. + * + * 09 Sep 91 - weemacs() renamed pico() for the PIne COmposer. + * + */ + + +/* + * This program is in public domain; written by Dave G. Conroy. + * This file contains the main driving routine, and some keyboard processing + * code, for the MicroEMACS screen editor. + * + * REVISION HISTORY: + * + * 1.0 Steve Wilhite, 30-Nov-85 + * - Removed the old LK201 and VT100 logic. Added code to support the + * DEC Rainbow keyboard (which is a LK201 layout) using the the Level + * 1 Console In ROM INT. See "rainbow.h" for the function key defs + * Steve Wilhite, 1-Dec-85 + * - massive cleanup on code in display.c and search.c + * + * 2.0 George Jones, 12-Dec-85 + * - Ported to Amiga. + * + * 3.0 Daniel Lawrence, 29-Dec-85 + * 16-apr-86 + * - updated documentation and froze development for 3.6 net release + */ + +/* make global definitions not external */ +#define maindef + +#include "../c-client/mail.h" +#include "../c-client/utf8.h" + +#ifdef _WINDOWS +/* wingdi.h uses ERROR (!) and we aren't using the c-client ERROR so... */ +#undef ERROR + +#endif +#include "headers.h" +#include "ebind.h" /* default key bindings */ +#include "../pith/charconv/filesys.h" + + +void func_init(void); +void breplace(void *w); +int any_header_changes(void); +int cleanwhitespace(void); +int isquotedspace(LINE *); + + +/* + * function key mappings + */ +static UCS pfkm[12][2] = { + { F1, (CTRL|'G')}, + { F2, (CTRL|'C')}, + { F3, (CTRL|'X')}, + { F4, (CTRL|'J')}, + { F5, (CTRL|'R')}, + { F6, (CTRL|'W')}, + { F7, (CTRL|'Y')}, + { F8, (CTRL|'V')}, + { F9, (CTRL|'K')}, + { F10, (CTRL|'U')}, + { F11, (CTRL|'O')}, +#ifdef SPELLER + { F12, (CTRL|'T')} +#else + { F12, (CTRL|'D')} +#endif +}; + + +/* + * flag for the various functions in pico() to set when ready + * for pico() to return... + */ +int pico_all_done = 0; +jmp_buf finstate; +UCS *pico_anchor = NULL; +extern struct headerentry *headents; + +/* + * pico - the main routine for Pine's composer. + * + */ +int +pico(PICO *pm) +{ + UCS c; + register int f; + register int n; + char bname[NBUFN]; /* buffer name of file to read */ + extern struct on_display ods; + int checkpointcnt = 0, input = 0; + int ret; + char chkptfile[NLINE]; +#ifdef _WINDOWS + int cursor_shown; +#endif + + Pmaster = pm; + gmode = MDWRAP; + gmode |= pm->pine_flags; /* high 4 bits rsv'd for pine */ + + alt_speller = pm->alt_spell; + pico_all_done = 0; + km_popped = 0; + + if(!vtinit()) /* Init Displays. */ + return(COMP_CANCEL); + + strncpy(bname, "main", sizeof(bname)); /* default buffer name */ + bname[sizeof(bname)-1] = '\0'; + edinit(bname); /* Buffers, windows. */ + + if(InitMailHeader(pm)) /* init mail header structure */ + gmode &= ~(P_BODY | P_HEADEND); /* flip off special header stuff */ + + /* setup to process commands */ + lastflag = 0; /* Fake last flags. */ + curbp->b_mode |= gmode; /* and set default modes*/ + + if(Pmaster->pine_anchor) + pico_anchor = utf8_to_ucs4_cpystr(Pmaster->pine_anchor); + else + pico_anchor = NULL; + + if(Pmaster->quote_str) + glo_quote_str = utf8_to_ucs4_cpystr(Pmaster->quote_str); + else + glo_quote_str = NULL; + + if(Pmaster->wordseps) + glo_wordseps = ucs4_cpystr(Pmaster->wordseps); + else + glo_wordseps = NULL; + + bindtokey(DEL, (gmode & P_DELRUBS) ? forwdel : backdel); + + if(pm->msgtext) + breplace(pm->msgtext); + +#ifdef _WINDOWS + cursor_shown = mswin_showcaret(1); /* turn on for main window */ + mswin_allowpaste(MSWIN_PASTE_FULL); + mswin_setscrollcallback (pico_scroll_callback); +#endif + + /* prepare for checkpointing */ + chkptfile[0] = '\0'; + chkptinit((*Pmaster->ckptdir)(chkptfile, sizeof(chkptfile)), sizeof(chkptfile)); + if(gmode & P_CHKPTNOW) + writeout(chkptfile, TRUE); + + pico_all_done = setjmp(finstate); /* jump out of HUP handler ? */ + + if(gmode & MDALTNOW){ + while(!pico_all_done){ + if(((gmode & P_BODY) || !Pmaster->headents) + && alt_editor(0, 1) < 0) + break; /* if problem, drop into pico */ + + if(Pmaster->headents){ + update(); /* paint screen, n' start editing... */ + HeaderEditor((gmode & (P_HEADEND | P_BODY)) ? 2 : 0, 0); + gmode |= P_BODY; /* make sure we enter alt ed next */ + } + else + pico_all_done = COMP_EXIT; + } + } + else if(!pico_all_done){ + if(gmode & P_BODY){ /* begin editing the header? */ + ArrangeHeader(); /* line up pointers */ + /* + * Move to the offset pine asked us to move to. + * Perhaps we should be checking to see if this is + * a reasonable number before moving. + */ + if(Pmaster && Pmaster->edit_offset) + forwchar(FALSE, Pmaster->edit_offset); + } + else{ + update(); /* paint screen, */ + HeaderEditor((gmode & P_HEADEND) ? 2 : 0, 0); + } + } + + while(1){ + if(pico_all_done){ +#ifdef _WINDOWS + if(!cursor_shown) + mswin_showcaret(0); + + mswin_allowpaste(MSWIN_PASTE_DISABLE); + mswin_setscrollcallback (NULL); +#endif + ret = anycb() ? BUF_CHANGED : 0; + switch(pico_all_done){ /* prepare for/handle final events */ + case COMP_EXIT : /* already confirmed */ + packheader(); + if(Pmaster + && (Pmaster->strip_ws_before_send + || Pmaster->allow_flowed_text)) + cleanwhitespace(); + ret |= COMP_EXIT; + break; + + case COMP_CANCEL : /* also already confirmed */ + packheader(); + ret = COMP_CANCEL; + break; + + case COMP_GOTHUP: + /* + * pack up and let caller know that we've received a SIGHUP + */ + if(ComposerEditing) /* expand addr if needed */ + call_builder(&headents[ods.cur_e], NULL, NULL); + + packheader(); + ret |= COMP_GOTHUP; + break; + + case COMP_SUSPEND : + default: /* safest if internal error */ + /* + * If we're in the headers mark the current header line + * with start_here bit so caller knows where to reset. + * Also set the edit_offset, which is either the offset + * into this header line or the offset into the body. + * Packheader will adjust edit_offset for multi-line + * headers. + */ + if(ComposerEditing){ /* in the headers */ + headents[ods.cur_e].start_here = 1; + Pmaster->edit_offset = ods.p_ind; + } + else{ + register LINE *clp; + register long offset; + + for(clp = lforw(curbp->b_linep), offset = 0L; + clp != curwp->w_dotp; + clp = lforw(clp)) + offset += (llength(clp) + 1); + + Pmaster->edit_offset = offset + curwp->w_doto; + } + + packheader(); + ret |= COMP_SUSPEND; + break; + } + + if(pico_anchor) + fs_give((void **) &pico_anchor); + if(glo_quote_str) + fs_give((void **) &glo_quote_str); + if(glo_wordseps) + fs_give((void **) &glo_wordseps); + + vttidy(); /* clean up tty modes */ + zotdisplay(); /* blast display buffers */ + zotedit(); + our_unlink(chkptfile); + Pmaster = NULL; /* blat global */ + + return(ret); + } + + if(km_popped){ + km_popped--; + if(km_popped == 0) /* cause bottom three lines to be repainted */ + curwp->w_flag |= WFHARD; + } + + if(km_popped){ /* temporarily change to cause menu to be painted */ + term.t_mrow = 2; + curwp->w_ntrows -= 2; + curwp->w_flag |= WFMODE; + movecursor(term.t_nrow-2, 0); /* clear status line, too */ + peeol(); + } + + update(); /* Fix up the screen */ + if(km_popped){ + term.t_mrow = 0; + curwp->w_ntrows += 2; + } + +#ifdef MOUSE +#ifdef EX_MOUSE + /* New mouse function for real mouse text seletion. */ + register_mfunc(mouse_in_pico, 2, 0, term.t_nrow - (term.t_mrow+1), + term.t_ncol); +#else + mouse_in_content(KEY_MOUSE, -1, -1, -1, 0); + register_mfunc(mouse_in_content, 2, 0, term.t_nrow - (term.t_mrow + 1), + term.t_ncol); +#endif +#endif +#ifdef _WINDOWS + mswin_setdndcallback (composer_file_drop); + mswin_mousetrackcallback(pico_cursor); +#endif + c = GetKey(); + if (term.t_nrow < 6 && c != NODATA){ + (*term.t_beep)(); + emlwrite(_("Please make the screen bigger."), NULL); + continue; + } + +#ifdef MOUSE +#ifdef EX_MOUSE + clear_mfunc(mouse_in_pico); +#else + clear_mfunc(mouse_in_content); +#endif +#endif +#ifdef _WINDOWS + mswin_cleardndcallback (); + mswin_mousetrackcallback(NULL); +#endif + if(c == NODATA || time_to_check()){ /* new mail ? */ + if((*Pmaster->newmail)(c == NODATA ? 0 : 2, 1) >= 0){ + int rv; + + if(km_popped){ + term.t_mrow = 2; + curwp->w_ntrows -= 2; + curwp->w_flag |= WFHARD; + km_popped = 0; + } + + clearcursor(); + mlerase(); + rv = (*Pmaster->showmsg)(c); + ttresize(); + picosigs(); /* restore altered handlers */ + if(rv) /* Did showmsg corrupt the display? */ + PaintBody(0); /* Yes, repaint */ + + mpresf = 1; + input = 0; + } + + clearcursor(); + movecursor(0, 0); + } + + if(km_popped) + switch(c){ + case NODATA: + case (CTRL|'L'): + km_popped++; + break; + + default: + mlerase(); + break; + } + + if(c == NODATA) /* no op, getkey timed out */ + continue; + else if(!input++) + (*Pmaster->keybinput)(); + + if (mpresf != FALSE) { /* message stay around only */ + if (mpresf++ > NMMESSDELAY) /* so long! */ + mlerase(); + } + + f = FALSE; /* vestigial */ + n = 1; + /* Do it. */ + execute(normalize_cmd(c, pfkm, 2), f, n); + if(++checkpointcnt >= CHKPTDELAY){ + checkpointcnt = 0; + writeout(chkptfile, TRUE); + } + } +} + +/* + * Initialize all of the buffers and windows. The buffer name is passed down + * as an argument, because the main routine may have been told to read in a + * file by default, and we want the buffer name to be right. + */ + +/* + * For the pine composer, we don't want to take over the whole screen + * for editing. the first some odd lines are to be used for message + * header information editing. + */ +void +edinit(char bname[]) +{ + register BUFFER *bp; + register WINDOW *wp; + + if(Pmaster) + func_init(); + + bp = bfind(bname, TRUE, BFWRAPOPEN); /* First buffer */ + wp = (WINDOW *) malloc(sizeof(WINDOW)); /* First window */ + + if (bp==NULL || wp==NULL){ + if(Pmaster) + return; + else + exit(1); + } + + curbp = bp; /* Make this current */ + wheadp = wp; + curwp = wp; + wp->w_wndp = NULL; /* Initialize window */ + wp->w_bufp = bp; + bp->b_nwnd = 1; /* Displayed. */ + wp->w_linep = bp->b_linep; + wp->w_dotp = bp->b_linep; + wp->w_doto = 0; + wp->w_markp = wp->w_imarkp = NULL; + wp->w_marko = wp->w_imarko = 0; + bp->b_linecnt = -1; + + if(Pmaster){ + term.t_mrow = Pmaster->menu_rows; + wp->w_toprow = ComposerTopLine = COMPOSER_TOP_LINE; + wp->w_ntrows = term.t_nrow - COMPOSER_TOP_LINE - term.t_mrow; + fillcol = Pmaster->fillcolumn; + strncpy(opertree, + (Pmaster->oper_dir && strlen(Pmaster->oper_dir) < NLINE) + ? Pmaster->oper_dir : "", sizeof(opertree)); + opertree[sizeof(opertree)-1] = '\0'; + input_cs = Pmaster->input_cs; + } + else{ + if(sup_keyhelp) + term.t_mrow = 0; + else + term.t_mrow = 2; + + wp->w_toprow = 2; + wp->w_ntrows = term.t_nrow - 2 - term.t_mrow; + if(userfillcol > 0) /* set fill column */ + fillcol = userfillcol; + else + fillcol = term.t_ncol - 6; + } + + /* + * MDSCUR mode implies MDTREE mode with a opertree of home directory, + * unless opertree has been set differently. + */ + if((gmode & MDSCUR) && !opertree[0]){ + strncpy(opertree, gethomedir(NULL), sizeof(opertree)); + opertree[sizeof(opertree)-1] = '\0'; + } + + if(*opertree) + fixpath(opertree, sizeof(opertree)); + + wp->w_force = 0; + wp->w_flag = WFMODE|WFHARD; /* Full. */ +} + + +/* + * This is the general command execution routine. It handles the fake binding + * of all the keys to "self-insert". It also clears out the "thisflag" word, + * and arranges to move it to the "lastflag", so that the next command can + * look at it. Return the status of command. + */ +int +execute(UCS c, int f, int n) +{ + KEYTAB *ktp; + int status, ww; + + ktp = (Pmaster) ? &keytab[0] : &pkeytab[0]; + + while (ktp->k_fp != NULL) { + if (ktp->k_code == c) { + + if(lastflag&CFFILL){ + curwp->w_flag |= WFMODE; + if(Pmaster == NULL) + sgarbk = TRUE; + } + + thisflag = 0; + status = (*ktp->k_fp)(f, n); + if((lastflag & CFFILL) && !(thisflag & CFFILL)) + fdelete(); + if((lastflag & CFFLBF) && !(thisflag & CFFLBF)) + kdelete(); + + lastflag = thisflag; + + /* + * Reset flag saying wrap should open a new line whenever + * we execute a command (as opposed to just typing in text). + * However, if that command leaves us in the same line on the + * screen, then don't reset. + */ + if(curwp->w_flag & (WFMOVE | WFHARD)) + curbp->b_flag |= BFWRAPOPEN; /* wrap should open new line */ + + return (status); + } + ++ktp; + } + + if(lastflag & CFFILL) /* blat unusable fill data */ + fdelete(); + if(lastflag & CFFLBF) + kdelete(); + + if (VALID_KEY(c)) { /* Self inserting. */ + + if (n <= 0) { /* Fenceposts. */ + lastflag = 0; + return (n<0 ? FALSE : TRUE); + } + thisflag = 0; /* For the future. */ + + /* do the appropriate insertion */ + /* pico never does C mode, this is simple */ + status = linsert(n, c); + + /* + * Check to make sure we didn't go off of the screen + * with that character. Take into account tab expansion. + * If so wrap the line... + */ + if(curwp->w_bufp->b_mode & MDWRAP){ + int j, wid; + + wid = 0; + for(j = 0; j < llength(curwp->w_dotp); j++) + if(ucs4_isspace(lgetc(curwp->w_dotp, j).c)){ + if(lgetc(curwp->w_dotp, j).c == TAB){ + ++wid; + while(wid & 0x07) + ++wid; + } + else + ++wid; + } + else{ + ww = wcellwidth((UCS) lgetc(curwp->w_dotp, j).c); + wid += (ww >= 0 ? ww : 1); + if(wid > fillcol){ + wrapword(); + break; + } + } + } + + lastflag = thisflag; + return (status); + } + + unknown_command(c); + + lastflag = 0; /* Fake last flags. */ + return (FALSE); +} + + + +/* + * Fancy quit command, as implemented by Norm. If the any buffer has + * changed do a write on that buffer and exit emacs, otherwise simply exit. + */ +int +quickexit(int f, int n) +{ + register BUFFER *bp; /* scanning pointer to buffers */ + + bp = bheadp; + while (bp != NULL) { + if ((bp->b_flag&BFCHG) != 0 /* Changed. */ + && (bp->b_flag&BFTEMP) == 0) { /* Real. */ + curbp = bp; /* make that buffer cur */ + filesave(f, n); + } + bp = bp->b_bufp; /* on to the next buffer */ + } + return(wquit(f, n)); /* conditionally quit */ +} + + + +/* + * abort_composer - ask the question here, then go quit or + * return FALSE + */ +int +abort_composer(int f, int n) +{ + char *result; + + result = ""; + + Pmaster->arm_winch_cleanup++; + if(Pmaster->canceltest){ + if(((Pmaster->pine_flags & MDHDRONLY) && !any_header_changes()) + || (result = (*Pmaster->canceltest)(redraw_pico_for_callback))){ + pico_all_done = COMP_CANCEL; + emlwrite(result, NULL); + Pmaster->arm_winch_cleanup--; + return(TRUE); + } + else{ + /* TRANSLATORS: The user typed the Cancel command and was + asked to confirm that. Instead they canceled the cancel + command. */ + emlwrite(_("Cancel Cancelled"), NULL); + curwp->w_flag |= WFMODE; /* and modeline so we */ + sgarbk = TRUE; /* redraw the keymenu */ + pclear(term.t_nrow-1, term.t_nrow); + Pmaster->arm_winch_cleanup--; + return(FALSE); + } + } + else switch(mlyesno_utf8(Pmaster->headents + ? _("Cancel message (answering \"Yes\" will abandon your mail message)") + : (anycb() == FALSE) + ? _("Cancel Edit (and abandon changes)") + : _("Cancel Edit"), + FALSE)){ + case TRUE: + pico_all_done = COMP_CANCEL; + return(TRUE); + + case ABORT: + emlwrite(_("\007Cancel Cancelled"), NULL); + break; + + default: + mlerase(); + } + return(FALSE); +} + + +/* + * suspend_composer - return to pine with what's been edited so far + */ +int +suspend_composer(int f, int n) +{ + if(Pmaster && Pmaster->headents) + pico_all_done = COMP_SUSPEND; + else + (*term.t_beep)(); + + return(TRUE); +} + + + +/* + * Quit command. If an argument, always quit. Otherwise confirm if a buffer + * has been changed and not written out. Normally bound to "C-X C-C". + */ +int +wquit(int f, int n) +{ + register int s; + + if(Pmaster){ + char *result = NULL; + int ret; + + /* First, make sure there are no outstanding problems */ + if(AttachError()){ + emlwrite(_("\007Problem with attachments! Fix errors or delete attachments."), NULL); + return(FALSE); + } + +#ifdef SPELLER + if(Pmaster->always_spell_check) + if(spell(0, 0) == -1) + sleep(3); /* problem, show error */ +#endif + /* + * if we're not in header, show some of it as we verify sending... + */ + display_for_send(); + packheader(); + Pmaster->arm_winch_cleanup++; + if((!(Pmaster->pine_flags & MDHDRONLY) || any_header_changes()) + && (ret = (*Pmaster->exittest)(Pmaster->headents, + redraw_pico_for_callback, + Pmaster->allow_flowed_text, + &result))){ + Pmaster->arm_winch_cleanup--; + + if(ret == -1){ + pico_all_done = COMP_CANCEL; + } + else{ + if(sgarbf) + update(); + + lchange(WFHARD); /* set update flags... */ + curwp->w_flag |= WFMODE; /* and modeline so we */ + sgarbk = TRUE; /* redraw the keymenu */ + pclear(term.t_nrow-2, term.t_nrow); + } + + if(result && *result) + emlwrite(result, NULL); + } + else{ + Pmaster->arm_winch_cleanup--; + pico_all_done = COMP_EXIT; + return(TRUE); + } + } + else{ + if (f != FALSE /* Argument forces it. */ + || anycb() == FALSE /* All buffers clean. */ + /* User says it's OK. */ + /* TRANSLATORS: buffer is the in-memory copy of a file */ + || (s=mlyesno_utf8(_("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES)"), -1)) == FALSE) { + vttidy(); +#if defined(USE_TERMCAP) || defined(USE_TERMINFO) || defined(VMS) + kbdestroy(kbesc); +#endif + exit(0); + } + + if(s == TRUE){ + if(filewrite(0,1) == TRUE) + wquit(1, 0); + } + else if(s == ABORT){ + emlwrite(_("Exit cancelled"), NULL); + if(term.t_mrow == 0) + curwp->w_flag |= WFHARD; /* cause bottom 3 lines to paint */ + } + return(s); + } + + return(FALSE); +} + + +/* + * Has any editing been done to headers? + */ +int +any_header_changes(void) +{ + struct headerentry *he; + + for(he = Pmaster->headents; he->name != NULL; he++) + if(he->dirty) + break; + + return(he->name && he->dirty); +} + +int +isquotedspace(LINE *line) +{ + int i, was_quote = 0; + for(i = 0; i < llength(line); i++){ + if(lgetc(line, i).c == '>') + was_quote = 1; + else if(was_quote && lgetc(line, i).c == ' '){ + if(i+1 < llength(line) && ucs4_isspace(lgetc(line,i+1).c)) + return 1; + else + return 0; + } + else + return 0; + } + return 0; +} + +/* + * This function serves two purposes, 1) to strip white space when + * Pmaster asks that the composition have its trailing white space + * stripped, or 2) to prepare the text as flowed text, as Pmaster + * is telling us that we're working with flowed text. + * + * What flowed currently means to us is stripping all trailing white + * space, except for one space if the following line is a continuation + * of the paragraph. Also, we space-stuff all lines beginning + * with white-space, and leave siglines alone. + */ +int +cleanwhitespace(void) +{ + LINE *cursor_dotp = NULL, **lp = NULL; + int i = 0, cursor_doto = 0, is_cursor_line = 0; + int do_space_stuffing = 0; + + if(Pmaster && Pmaster->allow_flowed_text && !(*Pmaster->user_says_noflow)()) + do_space_stuffing++; + + cursor_dotp = curwp->w_dotp; + cursor_doto = curwp->w_doto; + gotobob(FALSE, 1); + + for(lp = &curwp->w_dotp; (*lp) != curbp->b_linep; (*lp) = lforw(*lp)){ + if(!(llength(*lp) == 3 + && lgetc(*lp, 0).c == '-' + && lgetc(*lp, 1).c == '-' + && lgetc(*lp, 2).c == ' ') + && llength(*lp)){ + is_cursor_line = (cursor_dotp == (*lp)); + /* trim trailing whitespace, to be added back if flowing */ + for(i = llength(*lp); i; i--) + if(!ucs4_isspace(lgetc(*lp, i - 1).c)) + break; + if(i != llength(*lp)){ + int flow_line = 0; + + if(Pmaster && !Pmaster->strip_ws_before_send + && lforw(*lp) != curbp->b_linep + && llength(lforw(*lp)) + && !(ucs4_isspace(lgetc(lforw(*lp), 0).c) + || isquotedspace(lforw(*lp))) + && !(llength(lforw(*lp)) == 3 + && lgetc(lforw(*lp), 0).c == '-' + && lgetc(lforw(*lp), 1).c == '-' + && lgetc(lforw(*lp), 2).c == ' ')) + flow_line = 1; + if(flow_line && i && lgetc(*lp, i).c == ' '){ + /* flowed line ending with space */ + i++; + if(i != llength(*lp)){ + curwp->w_doto = i; + ldelete(llength(*lp) - i, NULL); + } + } + else if(flow_line && i && ucs4_isspace(lgetc(*lp, i).c)){ + /* flowed line ending with whitespace other than space*/ + curwp->w_doto = i; + ldelete(llength(*lp) - i, NULL); + linsert(1, ' '); + } + else{ + curwp->w_doto = i; + ldelete(llength(*lp) - i, NULL); + } + } + if(do_space_stuffing && llength(*lp) && ucs4_isspace(lgetc(*lp, 0).c)){ + /* space-stuff only if flowed */ + if(Pmaster) + Pmaster->space_stuffed = 1; + curwp->w_doto = 0; + if(is_cursor_line && cursor_doto) + cursor_doto++; + linsert(1, ' '); + } + if(is_cursor_line) + cursor_dotp = (*lp); + } + } + + /* put the cursor back where we found it */ + gotobob(FALSE, 1); + curwp->w_dotp = cursor_dotp; + curwp->w_doto = (cursor_doto < llength(curwp->w_dotp)) + ? cursor_doto : llength(curwp->w_dotp) - 1; + + return(0); +} + +/* + * Remove all trailing white space from the text + */ +int +stripwhitespace(void) +{ + int i; + LINE *cur_line = lforw(curbp->b_linep); + + do{ + /* we gotta test for the sigdash case here */ + if(!(cur_line->l_used == 3 && + lgetc(cur_line, 0).c == '-' && + lgetc(cur_line, 1).c == '-' && + lgetc(cur_line, 2).c == ' ')) + for(i = cur_line->l_used - 1; i >= 0; i--) + if(ucs4_isspace(lgetc(cur_line, i).c)) + cur_line->l_used--; + else + break; + }while((cur_line = lforw(cur_line)) != curbp->b_linep); + return 0; +} + +/* + * Abort. + * Beep the beeper. Kill off any keyboard macro, etc., that is in progress. + * Sometimes called as a routine, to do general aborting of stuff. + */ +int +ctrlg(int f, int n) +{ + emlwrite(_("Cancelled"), NULL); + return (ABORT); +} + + +/* tell the user that this command is illegal while we are in + * VIEW (read-only) mode + */ +int +rdonly(void) +{ + (*term.t_beep)(); + emlwrite("Key illegal in VIEW mode", NULL); + return(FALSE); +} + + + +/* + * reset all globals to their initial values + */ +void +func_init(void) +{ + extern int vtrow; + extern int vtcol; + extern int lbound; + + /* + * re-initialize global buffer type variables .... + */ + fillcol = (term.t_ncol > 80) ? 77 : term.t_ncol - 6; + sgarbf = TRUE; + mpresf = FALSE; + mline_open = FALSE; + ComposerEditing = FALSE; + + /* + * re-initialize hardware display variables .... + */ + vtrow = vtcol = lbound = 0; + clearcursor(); + + pat[0] = rpat[0] = '\0'; + browse_dir[0] = '\0'; +} + + +/* + * pico_help - help function for standalone composer + * Oops - looks like utf8title is unused! + * + * This should be fixed to handle TAB characters. + */ +int +pico_help(char *utf8text[], char *utf8title, int i) +{ + register int numline = 0; + char **p; + + p = utf8text; + while(*p++ != NULL) + numline++; + + return(wscrollw(COMPOSER_TOP_LINE, term.t_nrow-1, utf8text, numline)); +} + + + +/* + * zotedit() - kills the buffer and frees all lines associated with it!!! + */ +void +zotedit(void) +{ + wheadp->w_linep = wheadp->w_dotp = wheadp->w_markp = wheadp->w_imarkp = NULL; + bheadp->b_linep = bheadp->b_dotp = bheadp->b_markp = NULL; + + free((char *) wheadp); /* clean up window */ + wheadp = NULL; + curwp = NULL; + + free((char *) bheadp); /* clean up buffers */ + bheadp = NULL; + curbp = NULL; + + zotheader(); /* blast header lines */ + + kdelete(); /* blast kill buffer */ + +} + + +#ifdef MOUSE +/* + * Generic mouse handling functions + */ +MENUITEM menuitems[12]; /* key labels and functions */ +MENUITEM *mfunc = NULL; /* list of regional functions */ +mousehandler_t mtrack; /* mouse tracking handler */ + +/* last mouse position */ +static unsigned long levent = 0L; +static int lrow = 0, lcol = 0, doubleclick, lbutton, lflags; +#ifdef DOS +static clock_t lastcalled = 0; +#else +static time_t lastcalled = 0; +#endif +static mousehandler_t lastf; + + +/* + * register_mfunc - register the given function to get called + * on mouse events in the given display region + */ +int +register_mfunc(mousehandler_t f, int tlr, int tlc, int brr, int brc) +{ + MENUITEM **mp; + + if(!mouseexist()) + return(FALSE); + + for(mp = &mfunc; *mp; mp = &(*mp)->next) + ; + + *mp = (MENUITEM *)malloc(sizeof(MENUITEM)); + memset(*mp, 0, sizeof(MENUITEM)); + + (*mp)->action = f; + (*mp)->tl.r = tlr; + (*mp)->br.r = brr; + (*mp)->tl.c = tlc; + (*mp)->br.c = brc; + (*mp)->lbl.c = (*mp)->lbl.r = 0; + (*mp)->label = ""; + return(TRUE); +} + + +/* + * clear_mfunc - clear any previously set mouse function + */ +void +clear_mfunc(mousehandler_t f) +{ + MENUITEM *mp, *tp; + + if((mp = mfunc) != NULL){ + if(mp->action == f) + mfunc = mp->next; + else + for(tp = mp; tp->next; tp = tp->next) + if(tp->next->action == f){ + mp = tp->next; + tp->next = tp->next->next; + break; + } + + if(mp){ + mp->action = NULL; + free(mp); + } + } +} + + + +#ifdef EX_MOUSE + +void +clear_mtrack(void) +{ + mtrack = NULL; + mswin_allowmousetrack (FALSE); +} + + +void +register_mtrack(mousehandler_t f) +{ + if (f) { + mtrack = f; + mswin_allowmousetrack (TRUE); + } + else + clear_mtrack (); +} + + +static void +move_dot_to(int row, int col) +{ + LINE *lp; + int i; + + lp = curwp->w_linep; + i = row - ((Pmaster) ? ComposerTopLine : 2); + while(i-- && lp != curbp->b_linep) /* count from top */ + lp = lforw(lp); + curgoal = col; + curwp->w_dotp = lp; /* to new dot. */ + curwp->w_doto = getgoal(lp); + curwp->w_flag |= WFMOVE; +} + + +/* + * mouse_in_pico + * + * When the mouse goes down in the body we set the mark and start + * tracking. + * + * As the mouse moves we update the dot and redraw the screen. + * + * If the mouse moves above or below the pico body region of the + * screen we scroll the text and update the dot position. + * + * When the mouse comes up we clean up. If the mouse did not + * move, then we clear the mark and turn off the selection. + * + * Most of the mouse processing is handled here. The exception is + * mouse down in the header. Can't call HeaderEditor() from here so + * we send up the KEY_MOUSE character, which gets dispatched to + * mousepress(), which _can_ call HeaderEditor(). + */ +unsigned long +mouse_in_pico(unsigned long mevent, int row, int col, int button, int flags) +{ + unsigned long rv = 0; /* Our return value. */ + int trow, tcol; /* translated row and col. */ + + static int lheader = FALSE; /* Mouse down was in header. */ + + + /* + * What's up. + */ + switch (mevent) { + case M_EVENT_DOWN: + if(button != M_BUTTON_LEFT) + break; + + /* Ignore mouse down if not in pico body region. */ + if (row < 2 || row > term.t_nrow - (term.t_mrow+1)) { + clear_mtrack (); + break; + } + + /* Detect double clicks. Not that we do anything with em, just + * detect them. */ +#ifdef DOS +#ifdef CLOCKS_PER_SEC + doubleclick = (lrow == row && lcol == col + && clock() < (lastcalled + CLOCKS_PER_SEC/2)); +#else +#ifdef CLK_TCK +doubleclick = (lrow == row && lcol == col + && clock() < (lastcalled + CLK_TCK/2)); +#else + doubleclick = FALSE; +#endif +#endif + lastcalled = clock(); +#else + doubleclick = (lrow == row && lcol == col + && time(0) < (lastcalled + 2)); + lastcalled = time(0); +#endif + lheader = FALSE; /* Rember mouse down position. */ + levent = mevent; + lrow = row; + lcol = col; + lbutton = button; + lflags = flags; + + /* Mouse down in body? */ + if (row < (Pmaster ? ComposerTopLine : 2)) { + /* Mouse down in message header -> no tracking, just remember + * where */ + lheader = TRUE; + } + else { + /* Mouse down in message. + * If no shift key and an existing mark -> clear the mark. + * If shift key and no existing mark -> set mark before moving */ + if (!(flags & M_KEY_SHIFT) && curwp->w_markp) + setmark (0,1); /* this clears the mark. */ + else if (flags & M_KEY_SHIFT && !curwp->w_markp) + setmark (0,1); /* while this sets the mark. */ + + /* Reposition dot to mouse down. */ + move_dot_to (row, col); + + /* Set the mark to dot if no existing mark. */ + if (curwp->w_markp == NULL) + setmark (0,1); + + /* Track mouse movement. */ + register_mtrack (mouse_in_pico); + update (); + lheader = FALSE; /* Just to be sure. */ + } + break; + + + case M_EVENT_TRACK: + /* Mouse tracking. */ + if (lheader) /* Ignore mouse movement in header. */ + break; + + /* If above or below body, scroll body and adjust the row and col. */ + if (row < (Pmaster ? ComposerTopLine : 2)) { + /* Scroll text down screen and move dot to top left corner. */ + scrollupline (0,1); + trow = (Pmaster) ? ComposerTopLine : 2; + tcol = 0; + } + else if (row > term.t_nrow - (term.t_mrow + 1)) { + /* Scroll text up screen and move dot to bottom right corner. */ + scrolldownline (0,1); + trow = term.t_nrow - (term.t_mrow + 1); + tcol = term.t_ncol; + } + else { + trow = row; + tcol = col; + } + + /* Move dot to target column. */ + move_dot_to (trow, tcol); + + /* Update screen. */ + update (); + break; + + + case M_EVENT_UP: + if(button == M_BUTTON_RIGHT){ +#ifdef _WINDOWS + pico_popup(); +#endif + break; + } + else if(button != M_BUTTON_LEFT) + break; + + if (lheader) { + lheader = FALSE; + /* Last down in header. */ + if (row == lrow && col == lcol) { + /* Mouse up and down in same place in header. Means the + * user want to edit the header. Return KEY_MOUSE which + * will cause mousepress to be called, which will + * call HeaderEditor. Can't call HeaderEditor from here + * because that would mess up layering. */ + if (curwp->w_marko) + setmark (0,1); + rv = (unsigned long) KEY_MOUSE; + } + } + else { + /* If up at same place, clear mark */ + if (curwp->w_markp == curwp->w_dotp && + curwp->w_marko == curwp->w_doto) { + setmark (0,1); + curwp->w_flag |= WFMOVE; + } + clear_mtrack (); + update (); + } + break; + } + + return(rv); +} +#endif + + + +/* + * mouse_in_content - general mechanism used to pass recognized mouse + * events in predefined region back thru the usual + * keyboard input stream. The actual return value + * passed back from this function is set dynamically + * via the "down" argument which is read when both the + * "row" and "col" arguments are negative. + */ +unsigned long +mouse_in_content(unsigned long mevent, int row, int col, int button, int flags) +{ + unsigned long rv = 0; + static unsigned long mouse_val = KEY_MOUSE; + + if(row == -1 && col == -1){ + mouse_val = mevent; /* setting return value */ + } + else { + /* A real event. */ + levent = mevent; + switch (mevent) { + case M_EVENT_DOWN: + /* Mouse down does not mean anything, just keep track of + * where it went down and if this is a double click. */ +#ifdef DOS +#ifdef CLOCKS_PER_SEC + doubleclick = (lrow == row && lcol == col + && clock() < (lastcalled + CLOCKS_PER_SEC/2)); +#else +#ifdef CLK_TCK + doubleclick = (lrow == row && lcol == col + && clock() < (lastcalled + CLK_TCK/2)); +#else + doubleclick = FALSE; +#endif +#endif + lastcalled = clock(); +#else + doubleclick = (lrow == row && lcol == col + && time(0) < (lastcalled + 2)); + lastcalled = time(0); +#endif + lrow = row; + lcol = col; + lbutton = button; + lflags = flags; + break; + + case M_EVENT_UP: + /* Mouse up. If in the same position as it went down + * then we return the value set above, which goes into + * the character input stream, which gets processed as + * a mouse event by some upper layer, which calls to + * mouse_get_last(). */ + if (lrow == row && lcol == col) { + rv = mouse_val; + } + break; + + case M_EVENT_TRACK: + break; + } + } + + return(rv); +} + + +/* + * mouse_get_last - Get last mouse event. + * + */ +void +mouse_get_last(mousehandler_t *f, MOUSEPRESS *mp) +{ + if (f != NULL) + *f = lastf; + if (mp != NULL) { + mp->mevent = levent; + mp->row = lrow; + mp->col = lcol; + mp->doubleclick = doubleclick; + mp->button = lbutton; + mp->flags = lflags; + } +} + + + +/* + * register_key - register the given keystroke to accept mouse events + */ +void +register_key(int i, unsigned rval, char *label, void (*label_printer)(), + int row, int col, int len, COLOR_PAIR *kn, COLOR_PAIR *kl) +{ + if(i > 11) + return; + + menuitems[i].val = rval; + menuitems[i].tl.r = menuitems[i].br.r = row; + menuitems[i].tl.c = col; + menuitems[i].br.c = col + len; + menuitems[i].lbl.r = menuitems[i].tl.r; + menuitems[i].lbl.c = menuitems[i].tl.c; + menuitems[i].label_hiliter = label_printer; + if(menuitems[i].label){ + free(menuitems[i].label); + menuitems[i].label = NULL; + } + if(menuitems[i].kncp) + free_color_pair(&menuitems[i].kncp); + if(menuitems[i].klcp) + free_color_pair(&menuitems[i].klcp); + if(kn) + menuitems[i].kncp = new_color_pair(kn->fg, kn->bg); + else + menuitems[i].kncp = NULL; + if(kl) + menuitems[i].klcp = new_color_pair(kl->fg, kl->bg); + else + menuitems[i].klcp = NULL; + + if(label){ + size_t len; + + len = strlen(label); + if((menuitems[i].label = (char *)malloc((len+1)*sizeof(char))) != NULL){ + strncpy(menuitems[i].label, label, len); + menuitems[i].label[len] = '\0'; + } + } +} + + +int +mouse_on_key(int row, int col) +{ + int i; + + for(i = 0; i < 12; i++) + if(M_ACTIVE(row, col, &menuitems[i])) + return(TRUE); + + return(FALSE); +} +#endif /* MOUSE */ + + +/* + * Below are functions for use outside pico to manipulate text + * in a pico's native format (circular linked list of lines). + * + * The idea is to streamline pico use by making it fairly easy + * for outside programs to prepare text intended for pico's use. + * The simple char * alternative is messy as it requires two copies + * of the same text, and isn't very economic in limited memory + * situations (THANKS BELLEVUE-BILLY.). + */ +typedef struct picotext { + LINE *linep; + LINE *dotp; + int doto; + short crinread; +} PICOTEXT; + +#define PT(X) ((PICOTEXT *)(X)) + +/* + * pico_get - return window struct pointer used as a handle + * to the other pico_xxx routines. + */ +void * +pico_get(void) +{ + PICOTEXT *wp = NULL; + LINE *lp = NULL; + + if((wp = (PICOTEXT *)malloc(sizeof(PICOTEXT))) != NULL){ + wp->crinread = 0; + if((lp = lalloc(0)) == NULL){ + free(wp); + return(NULL); + } + + wp->dotp = wp->linep = lp->l_fp = lp->l_bp = lp; + wp->doto = 0; + } + else + emlwrite("Can't allocate space for text", NULL); + + return((void *)wp); +} + +/* + * pico_give - free resources and give up picotext struct + */ +void +pico_give(void *w) +{ + register LINE *lp; + register LINE *fp; + + fp = lforw(PT(w)->linep); + while((lp = fp) != PT(w)->linep){ + fp = lforw(lp); + free(lp); + } + free(PT(w)->linep); + free((PICOTEXT *)w); +} + +/* + * pico_readc - return char at current point. Up to calling routines + * to keep cumulative count of chars. + * The characters in PT are UCS-4 characters. The caller + * of pico_readc is expecting UTF-8 chars. We convert + * each UCS-4 character to a string of UTF-8 characters + * and return them one at a time. + */ +int +pico_readc(void *w, unsigned char *c, int flags) +{ + int rv = 0; + UCS ucs; + static unsigned char obuf[6]; + static unsigned char *obufpend = obuf; + static unsigned char *obufpnext = obuf; + + if(!(flags & PICOREADC_NOUCS)){ + if(obufpend > obuf){ + *c = *obufpnext++; + rv++; + if(obufpnext >= obufpend){ + obufpend = obuf; + obufpnext = obuf; + } + + return(rv); + } + } + + if(PT(w)->crinread){ + *c = '\012'; /* return LF */ + PT(w)->crinread = 0; + rv++; + } + else if(PT(w)->doto < llength(PT(w)->dotp)){ /* normal char to return */ + if(flags & PICOREADC_NOUCS){ + *c = (unsigned char) lgetc(PT(w)->dotp, (PT(w)->doto)++).c; + rv++; + } + else{ + rv++; + ucs = lgetc(PT(w)->dotp, (PT(w)->doto)++).c; + obufpend = utf8_put(obuf, (unsigned long) ucs); + obufpnext = obuf; + if(obufpend > obuf){ + *c = *obufpnext++; + if(obufpnext >= obufpend){ + obufpend = obuf; + obufpnext = obuf; + } + } + else + *c = '?'; + } + } + else if(PT(w)->dotp != PT(w)->linep){ /* return line break */ + PT(w)->dotp = lforw(PT(w)->dotp); + PT(w)->doto = 0; +#if defined(DOS) || defined(OS2) + *c = '\015'; + PT(w)->crinread++; +#else + *c = '\012'; /* return local eol! */ +#endif + rv++; + } /* else no chars to return */ + + return(rv); +} + + +/* + * pico_writec - write a char into picotext and advance pointers. + * Up to calling routines to keep track of total chars + * written. + * Incoming text (c) is UTF-8 and written chars are UCS-4. + * We need to collect up multiple chars to make a single + * UCS-4 char, so there needs to be some state between calls. + */ +int +pico_writec(void *w, int c, int flags) +{ + int rv = 0; + + if(c == '\r') /* ignore CR's */ + rv++; /* so fake it */ + else if(c == '\n'){ /* insert newlines on LF */ + /* + * OK, if there are characters on the current line or + * dotp is pointing to the delimiter line, insert a newline + * No here's the tricky bit; preserve the implicit EOF newline. + */ + if(lforw(PT(w)->dotp) == PT(w)->linep && PT(w)->dotp != PT(w)->linep){ + PT(w)->dotp = PT(w)->linep; + PT(w)->doto = 0; + } + else{ + register LINE *lp; + + if((lp = lalloc(0)) == NULL){ + emlwrite("Can't allocate space for more characters",NULL); + return(0); + } + + if(PT(w)->dotp == PT(w)->linep){ + lforw(lp) = PT(w)->linep; + lback(lp) = lback(PT(w)->linep); + lforw(lback(lp)) = lback(PT(w)->linep) = lp; + } + else{ + lforw(lp) = lforw(PT(w)->dotp); + lback(lp) = PT(w)->dotp; + lback(lforw(lp)) = lforw(PT(w)->dotp) = lp; + PT(w)->dotp = lp; + PT(w)->doto = 0; + } + } + + rv++; + } + else{ + if(flags & PICOREADC_NOUCS){ + /* + * With this flag we're reverting to the old behavior where no + * UTF-8 to UCS-4 translation takes place. We assume nothing + * about the incoming byte stream. We just write a byte at a + * time. So even though it's being stored in PT each + * item is really limited to a single octet value. + */ + rv = geninsert(&PT(w)->dotp, &PT(w)->doto, PT(w)->linep, c, 0, 1, NULL); + } + else{ + static unsigned char cbuf[6]; + static unsigned char *cbufp = cbuf; + UCS obuf[MAX(MB_LEN_MAX,32)]; + int i, outchars = 0; + + if(cbufp < cbuf+sizeof(cbuf)){ + unsigned char *inputp; + unsigned long remaining_octets; + UCS ucs; + + *cbufp++ = (unsigned char) c; + 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++) + obuf[outchars++] = '?'; + + cbufp = cbuf; + } + else{ + /* got a character */ + if(ucs >= 0x80 && wcellwidth(ucs) < 0){ + /* + * This happens when we have a UTF-8 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 UTF-8 characters that can't be printed. + * Print a '?' instead. + This may be the wrong thing to do. What happens if user + is just forwarding and doesn't edit. We are going to lose + the original value, aren't we? Maybe we do this only + when printing to the screen instead. + */ + obuf[outchars++] = '?'; + } + else{ + /* + * A regular ucs character + */ + obuf[outchars++] = 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 */ + obuf[0] = '?'; + outchars = 1; + cbufp = cbuf; /* start over */ + } + + /* + * Unless we have trouble translating outchars will be + * either 1 or 0. It is the UCS-4 character that we converted + * the input to. It's an array just so it can hold ? when + * there is trouble. + */ + rv = 1; + for(i = 0; rv && i < outchars; i++) + if(!geninsert(&PT(w)->dotp, &PT(w)->doto, PT(w)->linep, obuf[i], 0, 1, NULL)) + rv = 0; + } + } + + /* + * Warning, this is no longer number written, because when we have a + * multibyte UTF-8 character we won't write anything until we get all + * the bytes. + */ + return((rv) ? 1 : 0); /* return number written */ +} + + +/* + * pico_puts - just write the given string into the text + */ +int +pico_puts(void *w, char *s, int flags) +{ + int rv = 0; + + if(*s != '\0'){ + rv = 1; + while(rv && *s != '\0') + if(!pico_writec(w, (int)*s++, flags)) + rv = 0; + } + + return((rv) ? 1 : 0); +} + + +/* + * pico_seek - position dotp and dot at requested location + */ +int +pico_seek(void *w, long offset, int orig) +{ + register LINE *lp; + + PT(w)->crinread = 0; + switch(orig){ + case 0 : /* SEEK_SET */ + PT(w)->dotp = lforw(PT(w)->linep); + PT(w)->doto = 0; + case 1 : /* SEEK_CUR */ + lp = PT(w)->dotp; + while(lp != PT(w)->linep){ + if(offset <= llength(lp)){ + PT(w)->doto = (int)offset; + PT(w)->dotp = lp; + break; + } + + offset -= ((long)llength(lp) +#if defined(DOS) || defined(OS2) + + 2L); +#else + + 1L); +#endif + lp = lforw(lp); + } + break; + + case 2 : /* SEEK_END */ + PT(w)->dotp = lback(PT(w)->linep); + PT(w)->doto = llength(PT(w)->dotp); + break; + default : + return(-1); + } + + return(0); +} + + +/* + * breplace - replace the current window's text with the given + * LINEs + */ +void +breplace(void *w) +{ + register LINE *lp; + register LINE *fp; + + fp = lforw(curbp->b_linep); + while((lp = fp) != curbp->b_linep){ /* blast old lines */ + fp = lforw(lp); + free(lp); + } + free(curbp->b_linep); + + curbp->b_linep = PT(w)->linep; /* arrange pointers */ + + /* + * Undo space-stuffing that was done when we were preparing to send. + * Some error happened so we're back to the composer. + */ + if(Pmaster && Pmaster->space_stuffed){ + Pmaster->space_stuffed = 0; + for(lp = lforw(curbp->b_linep); lp != curbp->b_linep; lp = lforw(lp)){ + if(llength(lp) && ucs4_isspace(lgetc(lp, 0).c)){ + curwp->w_dotp = lp; + curwp->w_doto = 0; + forwdel(FALSE,1); + } + } + } + + curwp->w_linep = lforw(curbp->b_linep); + curwp->w_dotp = lforw(curbp->b_linep); + curwp->w_doto = 0; + curwp->w_markp = curwp->w_imarkp = NULL; + curwp->w_marko = curwp->w_imarko = 0; + + curbp->b_dotp = curwp->w_dotp; + curbp->b_doto = curbp->b_marko = 0; + curbp->b_markp = NULL; + curbp->b_linecnt = -1; + + curwp->w_flag |= WFHARD; +} + + +#ifdef _WINDOWS +/* + * + */ +int +composer_file_drop(int x, int y, char *filename) +{ + int attached = 0; + EML eml; + + if((ComposerTopLine > 2 && x <= ComposerTopLine) + || !LikelyASCII(filename)){ + AppendAttachment(filename, NULL, NULL); + attached++; + } + else{ + setimark(FALSE, 1); + ifile(filename); + swapimark(FALSE, 1); + } + + if(ComposerEditing){ /* update display */ + PaintBody(0); + } + else{ + pico_refresh(0, 1); + update(); + } + + eml.s = filename; + if(attached) + emlwrite(_("Attached dropped file \"%s\""), &eml); + else + emlwrite(_("Inserted dropped file \"%s\""), &eml); + + if(ComposerEditing){ /* restore cursor */ + HeaderPaintCursor(); + } + else{ + curwp->w_flag |= WFHARD; + update(); + } + + return(1); +} + + +int +pico_cursor(int col, long row) +{ + return((row > 1 && row <= term.t_nrow - (term.t_mrow + 1)) + ? MSWIN_CURSOR_IBEAM + : MSWIN_CURSOR_ARROW); +} +#endif /* _WINDOWS */ diff --git a/pico/pico.h b/pico/pico.h new file mode 100644 index 00000000..cf25abea --- /dev/null +++ b/pico/pico.h @@ -0,0 +1,452 @@ +/* + * $Id: pico.h 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $ + * + * ======================================================================== + * 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 + * + * ======================================================================== + * + * Program: pico.h - definitions for Pine's composer library + */ + +#ifndef PICO_H +#define PICO_H + +#include "mode.h" + +#include "../pith/osdep/color.h" +#include "../pith/osdep/err_desc.h" +#include "../pith/charconv/utf8.h" + +#define errstr(n) error_description(n) + +/* + * Defined for attachment support + */ +#define ATTACHMENTS 1 + + +/* + * defs of return codes from pine mailer composer. + */ +#define BUF_CHANGED 0x01 +#define COMP_CANCEL 0x02 +#define COMP_EXIT 0x04 +#define COMP_FAILED 0x08 +#define COMP_SUSPEND 0x10 +#define COMP_GOTHUP 0x20 + + +/* + * top line from the top of the screen for the editor to do + * its stuff + */ +#define COMPOSER_TOP_LINE 2 +#define COMPOSER_TITLE_LINE 0 + + + +#define HLSZ NLINE + +/* + * definitions of Mail header structures + */ +struct hdr_line { + UCS text[HLSZ]; + struct hdr_line *next; + struct hdr_line *prev; +}; + + +/* must be the same as HelpType in pith/helptext.h */ +#define HELP_T char ** + + +/* + * This structure controls the header line items on the screen. An + * instance of this should be created and passed in as an argument when + * pico is called. The list is terminated by an entry with the name + * element NULL. + */ + +struct headerentry { + char *prompt; + char *name; + HELP_T help; + int prwid; /* prompt width on screen */ + int maxlen; + char **realaddr; + int (*builder)(); /* Function to verify/canonicalize val */ + struct headerentry *affected_entry, *next_affected; + /* entry builder's 4th arg affects */ + char *(*selector)(); /* Browser for possible values */ + char *key_label; /* Key label for key to call browser */ + char *(*fileedit)(); /* Editor for file named in header */ + int (*nickcmpl)(); /* routine that helps with nickname completion */ + unsigned display_it:1; /* field is to be displayed by default */ + unsigned break_on_comma:1; /* Field breaks on commas */ + unsigned is_attach:1; /* Special case field for attachments */ + unsigned rich_header:1; /* Field is part of rich header */ + unsigned only_file_chars:1; /* Field is a file name */ + unsigned single_space:1; /* Crush multiple spaces into one */ + unsigned sticky:1; /* Can't change this via affected_entry*/ + unsigned dirty:1; /* We've changed this entry */ + unsigned start_here:1; /* begin composer on first on lit */ + unsigned blank:1; /* blank line separator */ + unsigned sticky_special:1; /* special treatment */ +#ifdef KS_OSDATAVAR + KS_OSDATAVAR /* Port-Specific keymenu data */ +#endif + void *bldr_private; /* Data managed by builders */ + struct hdr_line *hd_text; +}; + + +/* + * Structure to pass as arg to builders. + * + * me -- A pointer to the bldr_private data for the entry currently + * being edited. + * tptr -- A malloc'd copy of the displayed text for the affected_entry + * pointed to by the aff argument. + * aff -- A pointer to the bldr_private data for the affected_entry (the + * entry that this entry affects). + * next -- The next affected_entry in the list. For example, the Lcc entry + * affects the To entry which affects the Fcc entry. + */ +typedef struct bld_arg { + void **me; + char *tptr; + void **aff; + struct bld_arg *next; +} BUILDER_ARG; + + +/* + * structure to keep track of header display + */ +struct on_display { + int p_ind; /* index into line */ + int p_len; /* length of line */ + int p_line; /* physical line on screen */ + int top_e; /* topline's header entry */ + struct hdr_line *top_l; /* top line on display */ + int cur_e; /* current header entry */ + struct hdr_line *cur_l; /* current hd_line */ +}; /* global on_display struct */ + + +/* + * Structure to handle attachments + */ +typedef struct pico_atmt { + char *description; /* attachment description */ + char *filename; /* file/pseudonym for attachment */ + char *size; /* size of attachment */ + char *id; /* attachment id */ + unsigned short flags; + struct pico_atmt *next; +} PATMT; + +/* + * Structure to contain color options + */ +typedef struct pico_colors { + COLOR_PAIR *tbcp; /* title bar color pair */ + COLOR_PAIR *klcp; /* key label color pair */ + COLOR_PAIR *kncp; /* key name color pair */ + COLOR_PAIR *stcp; /* status color pair */ + COLOR_PAIR *prcp; /* prompt color pair */ +} PCOLORS; + +/* + * Flags for attachment handling + */ +#define A_FLIT 0x0001 /* Accept literal file and size */ +#define A_ERR 0x0002 /* Problem with specified attachment */ +#define A_TMP 0x0004 /* filename is temporary, delete it */ + + +/* + * Master pine composer structure. Right now there's not much checking + * that any of these are pointing to something, so pine must have them pointing + * somewhere. + */ +typedef struct pico_struct { + void *msgtext; /* ptrs to malloc'd arrays of char */ + char *pine_anchor; /* ptr to pine anchor line */ + char *pine_version; /* string containing Pine's version */ + char *oper_dir; /* Operating dir (confine to tree) */ + char *home_dir; /* Home directory that should be used (WINDOWS) */ + char *quote_str; /* prepended to lines of quoted text */ + char *exit_label; /* Label for ^X in keymenu */ + char *ctrlr_label; /* Label for ^R in keymenu */ + char *alt_spell; /* Checker to use other than "spell" */ + char **alt_ed; /* name of alternate editor or NULL */ + UCS *wordseps; /* word separator characters other than space */ + int fillcolumn; /* where to wrap */ + int menu_rows; /* number of rows in menu (0 or 2) */ + int space_stuffed; /* space-stuffed for flowed, in case needs undoing */ + long edit_offset; /* offset into hdr line or body */ + PATMT *attachments; /* linked list of attachments */ + PCOLORS *colors; /* colors for titlebar and keymenu */ + void *input_cs; /* passed to mbtow() via kbseq() */ + long pine_flags; /* entry mode flags */ + /* The next few bits are features that don't fit in pine_flags */ + /* If we had this to do over, it would probably be one giant bitmap */ + unsigned always_spell_check:1; /* always spell-checking upon quit */ + unsigned strip_ws_before_send:1; /* don't default strip bc of flowed */ + unsigned allow_flowed_text:1; /* clean text when done to keep flowed */ + int (*helper)(); /* Pine's help function */ + int (*showmsg)(); /* Pine's display_message */ + UCS (*suspend)(); /* Pine's suspend */ + void (*keybinput)(); /* Pine's keyboard input indicator */ + int (*tty_fix)(); /* Let Pine fix tty state */ + long (*newmail)(); /* Pine's report_new_mail */ + long (*msgntext)(); /* callback to get msg n's text */ + int (*upload)(); /* callback to rcv uplaoded text */ + char *(*ckptdir)(); /* callback for checkpoint file dir */ + int (*exittest)(); /* callback to verify exit request */ + char *(*canceltest)(); /* callback to verify cancel request */ + int (*mimetype)(); /* callback to display mime type */ + int (*expander)(); /* callback to expand address lists */ + int (*user_says_noflow)(); /* callback to tell us we're not flowing */ + void (*resize)(); /* callback handling screen resize */ + void (*winch_cleanup)(); /* callback handling screen resize */ + void (*newthread)(); /* callback to create new thread */ + int arm_winch_cleanup; /* do the winch_cleanup if resized */ + HELP_T search_help; + HELP_T ins_help; + HELP_T ins_m_help; + HELP_T composer_help; + HELP_T browse_help; + HELP_T attach_help; + struct headerentry *headents; +} PICO; + + +/* + * Used to save and restore global pico variables that are destroyed by + * calling pico a second time. This happens when pico calls a selector + * in the HeaderEditor and that selector calls pico again. + */ +typedef struct save_stuff { + int vtrow, + vtcol, + lbound; + VIDEO **vscreen, + **pscreen; /* save pointers */ + struct on_display ods; /* save whole struct */ + short delim_ps, + invert_ps; + int pico_all_done; + jmp_buf finstate; + UCS *pico_anchor; /* save pointer */ + PICO *Pmaster; /* save pointer */ + int fillcol; + UCS *pat; /* save array */ + int ComposerTopLine, + ComposerEditing; + long gmode; + char *alt_speller; /* save pointer */ + UCS *quote_str; /* save pointer */ + UCS *wordseps; /* save pointer */ + int currow, + curcol, + thisflag, + lastflag, + curgoal; + char *opertree; /* save array */ + WINDOW *curwp; /* save pointer */ + WINDOW *wheadp; /* save pointer */ + BUFFER *curbp; /* save pointer */ + BUFFER *bheadp; /* save pointer */ + int km_popped; + int mrow; +} VARS_TO_SAVE; + + +#ifdef MOUSE +/* + * Mouse buttons. + */ +#define M_BUTTON_LEFT 0 +#define M_BUTTON_MIDDLE 1 +#define M_BUTTON_RIGHT 2 + + +/* + * Flags. (modifier keys) + */ +#define M_KEY_CONTROL 0x01 /* Control key was down. */ +#define M_KEY_SHIFT 0x02 /* Shift key was down. */ + + +/* + * Mouse Events + */ +#define M_EVENT_DOWN 0x01 /* Mouse went down. */ +#define M_EVENT_UP 0x02 /* Mouse went up. */ +#define M_EVENT_TRACK 0x04 /* Mouse tracking */ + +/* + * Mouse event information. + */ +typedef struct mouse_struct { + unsigned long mevent; /* Indicates type of event: Down, Up or Track */ + char down; /* TRUE when mouse down event */ + char doubleclick; /* TRUE when double click. */ + int button; /* button pressed. */ + int flags; /* What other keys pressed. */ + int row; + int col; +} MOUSEPRESS; + + + +typedef unsigned long (*mousehandler_t)(unsigned long, int, int, int, int); + +typedef struct point { + unsigned r:8; /* row value */ + unsigned c:8; /* column value */ +} MPOINT; + + +typedef struct menuitem { + unsigned val; /* return value */ + mousehandler_t action; /* action to perform */ + MPOINT tl; /* top-left corner of active area */ + MPOINT br; /* bottom-right corner of active area */ + MPOINT lbl; /* where the label starts */ + char *label; + void (*label_hiliter)(); + COLOR_PAIR *kncp; /* key name color pair */ + COLOR_PAIR *klcp; /* key label color pair */ + struct menuitem *next; +} MENUITEM; +#endif + + +/* + * Structure used to manage keyboard input that comes as escape + * sequences (arrow keys, function keys, etc.) + */ +typedef struct KBSTREE { + char value; + int func; /* Routine to handle it */ + struct KBSTREE *down; + struct KBSTREE *left; +} KBESC_T; + + +/* + * various flags that they may passed to PICO + */ +#define P_HICTRL 0x80000000 /* overwrite mode */ +#define P_CHKPTNOW 0x40000000 /* do the checkpoint on entry */ +#define P_DELRUBS 0x20000000 /* map ^H to forwdel */ +#define P_LOCALLF 0x10000000 /* use local vs. NVT EOL */ +#define P_BODY 0x08000000 /* start composer in body */ +#define P_HEADEND 0x04000000 /* start composer at end of header */ +#define P_VIEW MDVIEW /* read-only */ +#define P_FKEYS MDFKEY /* run in function key mode */ +#define P_SECURE MDSCUR /* run in restricted (demo) mode */ +#define P_TREE MDTREE /* restrict to a subtree */ +#define P_SUSPEND MDSSPD /* allow ^Z suspension */ +#define P_ADVANCED MDADVN /* enable advanced features */ +#define P_CURDIR MDCURDIR /* use current dir for lookups */ +#define P_ALTNOW MDALTNOW /* enter alt ed sans hesitation */ +#define P_SUBSHELL MDSPWN /* spawn subshell for suspend */ +#define P_COMPLETE MDCMPLT /* enable file name completion */ +#define P_DOTKILL MDDTKILL /* kill from dot to eol */ +#define P_SHOCUR MDSHOCUR /* cursor follows hilite in browser*/ +#define P_HIBITIGN MDHBTIGN /* ignore chars with hi bit set */ +#define P_DOTFILES MDDOTSOK /* browser displays dot files */ +#define P_NOBODY MDHDRONLY /* Operate only on given headers */ +#define P_ALLOW_GOTO MDGOTO /* support "Goto" in file browser */ +#define P_REPLACE MDREPLACE /* allow "Replace" in "Where is" */ + + +/* + * Main defs + */ +#ifdef maindef +PICO *Pmaster = NULL; /* composer specific stuff */ +char *version = "5.05"; /* PICO version number */ + +#else +extern PICO *Pmaster; /* composer specific stuff */ +extern char *version; /* pico version! */ + +#endif /* maindef */ + + +/* + * Flags for FileBrowser call + */ +#define FB_READ 0x0001 /* Looking for a file to read. */ +#define FB_SAVE 0x0002 /* Looking for a file to save. */ +#define FB_ATTACH 0x0004 /* Looking for a file to attach */ +#define FB_LMODEPOS 0x0008 /* ListMode is a possibility */ +#define FB_LMODE 0x0010 /* Using ListMode now */ + + +/* + * Flags for pico_readc/pico_writec. + */ +#define PICOREADC_NONE 0x00 +#define PICOREADC_NOUCS 0x01 + + +/* + * number of keystrokes to delay removing an error message, or new mail + * notification, or checkpointing + */ +#define MESSDELAY 25 +#define NMMESSDELAY 60 +#ifndef CHKPTDELAY +#define CHKPTDELAY 100 +#endif + +#include "./keydefs.h" + + +/* + * useful function definitions + */ +int pico(PICO *pm); +int pico_file_browse(PICO *, char *, size_t, char *, size_t, char *, size_t, int); +void *pico_get(void); +void pico_give(void *w); +int pico_readc(void *w, unsigned char *c, int flags); +int pico_writec(void *w, int c, int flags); +int pico_puts(void *w, char *s, int flags); +int pico_seek(void *w, long offset, int orig); +int pico_replace(void *, char *); +int pico_fncomplete(char *, char *, size_t); +#if defined(DOS) || defined(OS2) +int pico_nfsetcolor(char *); +int pico_nbsetcolor(char *); +int pico_rfsetcolor(char *); +int pico_rbsetcolor(char *); +#endif +#ifdef MOUSE +int register_mfunc(mousehandler_t, int, int, int, int); +void clear_mfunc(mousehandler_t); +unsigned long mouse_in_content(unsigned long, int, int, int, int); +unsigned long mouse_in_pico(unsigned long, int, int, int, int); +void mouse_get_last(mousehandler_t *, MOUSEPRESS *); +void register_key(int, unsigned, char *, void (*)(), + int, int, int, COLOR_PAIR *, COLOR_PAIR *); +int mouse_on_key(int, int); +#endif /* MOUSE */ + + +#endif /* PICO_H */ diff --git a/pico/picolib.def b/pico/picolib.def new file mode 100644 index 00000000..2f22a750 --- /dev/null +++ b/pico/picolib.def @@ -0,0 +1,94 @@ +; +; pico.def +; Linker module definition file for pico editor interface +; +LIBRARY PICOLIB INITINSTANCE +DESCRIPTION 'UW pico editor interface' +DATA + MULTIPLE NONSHARED +EXPORTS + ; pico interface + pico + pico_get + pico_give + pico_seek + pico_new_mail + pico_nbcolor + pico_rbcolor + pico_nfcolor + pico_rfcolor + pico_puts + pico_readc + pico_writec + pico_fncomplete + pico_file_browse + ; pc functions + ibmgetc + ibmbeep + ibmputc + ibmeeol + ibmeeop + ibmmove + ibmopen + ibmclose + ibmrev + vidUpdate + ; keyboard interface + kbd_ready + kbd_getkey + kbd_flush + ; mouse/key functions + clear_mfunc + register_mfunc + init_mouse + end_mouse + checkmouse + mouseon + mouseoff + register_key + mouse_get_last + ; utility functions + interrupt_ok + dont_interrupt + win_multiplex + enter_text_mode + exit_text_mode + normalize_cmd + rebindfunc + time_to_check + wquit + readin + update + edinit + mpresf + vtinit + movecursor + GetKey + mouse_in_content + mlerase + execute + makename + forwdel + forwline + emlwrite + bindtokey + quickexit + fixpath + set_browser_title + gethomedir + FileBrowse + ; exported global data + timeout + userfillcol + alt_speller + sup_keyhelp + term + peeol + gmode + curbp + curwp + Pmaster + lastflag + opertree + km_popped + os2_fflush diff --git a/pico/pilot.c b/pico/pilot.c new file mode 100644 index 00000000..c00b257d --- /dev/null +++ b/pico/pilot.c @@ -0,0 +1,472 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: pilot.c 1184 2008-12-16 23:52:15Z 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 + * + * ======================================================================== + */ + +/* + * Program: Main stand-alone Pine File Browser routines + * + * + * BROWSER NOTES: + * + * 30 Sep 92 - Stand alone PIne's "Lister of Things" came into being. + * It's built against libpico.a from a command line like: + * + * cc pilot.c libpico.a -ltermcap -lc -o pilot + * + * should it become a fleshed out tool, we'll move it into + * the normal build process. + */ + +#include "headers.h" +#include "../c-client/mail.h" +#include "../c-client/rfc822.h" +#include "../pith/osdep/collate.h" +#include "../pith/charconv/filesys.h" +#include "../pith/charconv/utf8.h" +#include "../pith/conf.h" + + +#define PILOT_VERSION "UW PILOT 2.99" + + +extern char *gethomedir(int *); + +char *pilot_args(int, char **, int *); +void pilot_args_help(void); +void pilot_display_args_err(char *, char **, int); + +char args_pilot_missing_flag[] = N_("unknown flag \"%c\""); +char args_pilot_missing_arg[] = N_("missing or empty argument to \"%c\" flag"); +char args_pilot_missing_num[] = N_("non numeric argument for \"%c\" flag"); +char args_pilot_missing_color[] = N_("missing color for \"%s\" flag"); +char args_pilot_missing_charset[] = N_("missing character set for \"%s\" flag"); +char args_pilot_input_charset[] = N_("input character set \"%s\" is unsupported"); +char args_pilot_output_charset[] = N_("output character set \"%s\" is unsupported"); + +char *args_pilot_args[] = { +/* TRANSLATORS: little help printed out when incorrect arguments are + given for pilot program. */ +N_("Possible Starting Arguments for Pilot file browser:"), +"", +N_("\tArgument\t\tMeaning"), +N_("\t -a \t\tShowDot - show dot files in file browser"), +N_("\t -j \t\tGoto - allow 'Goto' command in file browser"), +N_("\t -g \t\tShow - show cursor in file browser"), +N_("\t -m \t\tMouse - turn on mouse support"), +N_("\t -v \t\tOneColumn - use single column display"), +N_("\t -x \t\tNoKeyhelp - suppress keyhelp"), +N_("\t -q \t\tTermdefWins - termcap or terminfo takes precedence over defaults"), +N_("\t -f \t\tKeys - force use of function keys"), +N_("\t -h \t\tHelp - give this list of options"), +#ifndef _WINDOWS +N_("\t -dcs \tdefault uses LANG or LC_CTYPE from environment"), +N_("\t -kcs \tdefaults to display_character_set"), +N_("\t -syscs\t\tuse system-supplied translation routines"), +#endif /* ! _WINDOWS */ +N_("\t -n[#s] \tMail - notify about new mail every #s seconds, default=180"), +N_("\t -t \t\tShutdown - enable special shutdown mode"), +N_("\t -o \tOperation - specify the operating directory"), +N_("\t -z \t\tSuspend - allow use of ^Z suspension"), +#ifdef _WINDOWS +N_("\t -cnf color \tforeground color"), +N_("\t -cnb color \tbackground color"), +N_("\t -crf color \treverse foreground color"), +N_("\t -crb color \treverse background color"), +#endif /* _WINDOWS */ +N_("\t -no_setlocale_collate\tdo not do setlocale(LC_COLLATE)"), +"", +N_("\t All arguments may be followed by a directory name to start in."), +"", +NULL +}; + + +/* + * main standalone browser routine + */ +int +main(int argc, char *argv[]) +{ + char bname[NBUFN]; /* buffer name of file to read */ + char filename[NSTRING]; + char filedir[NSTRING]; + char *dir; + char *display_charmap = NULL, *dc; + char *keyboard_charmap = NULL; + int use_system = 0; + char *err = NULL; + int setlocale_collate = 1; + + set_input_timeout(0); + Pmaster = NULL; /* turn OFF composer functionality */ + km_popped = 0; + opertree[0] = '\0'; opertree[NLINE] = '\0'; + filename[0] ='\0'; + gmode |= MDBRONLY; /* turn on exclusive browser mode */ + + /* + * Read command line flags before initializing, otherwise, we never + * know to init for f_keys... + */ + if((dir = pilot_args(argc, argv, &setlocale_collate)) != NULL){ + strncpy(filedir, dir, sizeof(filedir)); + filedir[sizeof(filedir)-1] = '\0'; + fixpath(filedir, sizeof(filedir)); + } + else{ + strncpy(filedir, gethomedir(NULL), sizeof(filedir)); + filedir[sizeof(filedir)-1] = '\0'; + } + + set_collation(setlocale_collate, 1); + +#ifdef _WINDOWS + init_utf8_display(1, NULL); +#else /* UNIX */ + +#define cpstr(s) strcpy((char *)fs_get(1+strlen(s)), s) + + if(display_character_set) + display_charmap = cpstr(display_character_set); +#if HAVE_LANGINFO_H && defined(CODESET) + else if((dc = nl_langinfo_codeset_wrapper()) != NULL) + display_charmap = cpstr(dc); +#endif + + if(!display_charmap) + display_charmap = cpstr("US-ASCII"); + + if(keyboard_character_set) + keyboard_charmap = cpstr(keyboard_character_set); + else + keyboard_charmap = cpstr(display_charmap); + +#undef cpstr + + if(use_system_translation){ +#if PREREQ_FOR_SYS_TRANSLATION + use_system++; + /* This modifies its arguments */ + if(setup_for_input_output(use_system, &display_charmap, &keyboard_charmap, + &input_cs, &err) == -1){ + fprintf(stderr, "%s\n", err ? err : "trouble with character set"); + exit(1); + } + else if(err){ + fprintf(stderr, "%s\n", err); + fs_give((void **) &err); + } +#endif + } + + if(!use_system){ + if(setup_for_input_output(use_system, &display_charmap, &keyboard_charmap, + &input_cs, &err) == -1){ + fprintf(stderr, "%s\n", err ? err : "trouble with character set"); + exit(1); + } + else if(err){ + fprintf(stderr, "%s\n", err); + fs_give((void **) &err); + } + } + + if(keyboard_charmap){ + set_locale_charmap(keyboard_charmap); + free((void *) keyboard_charmap); + } + + if(display_charmap) + free((void *) display_charmap); + +#endif /* UNIX */ + + if(!vtinit()) /* Displays. */ + exit(1); + + strncpy(bname, "main", sizeof(bname)); /* default buffer name */ + bname[sizeof(bname)-1] = '\0'; + edinit(bname); /* Buffers, windows. */ +#if defined(USE_TERMCAP) || defined(USE_TERMINFO) || defined(VMS) + if(kbesc == NULL){ /* will arrow keys work ? */ + (*term.t_putchar)('\007'); + emlwrite("Warning: keypad keys may be non-functional", NULL); + } +#endif /* USE_TERMCAP/USE_TERMINFO/VMS */ + + curbp->b_mode |= gmode; /* and set default modes*/ + if(get_input_timeout()){ + EML eml; + + eml.s = comatose(get_input_timeout()); + emlwrite(_("Checking for new mail every %s seconds"), &eml); + } + + + set_browser_title(PILOT_VERSION); + FileBrowse(filedir, sizeof(filedir), filename, sizeof(filename), NULL, 0, 0, NULL); + wquit(1, 0); + exit(0); +} + + +/* + * Parse the command line args. + * + * Args ac + * av + * + * Result: command arguments parsed + * possible printing of help for command line + * various global flags set + * returns the name of directory to start in if specified, else NULL + */ +char * +pilot_args(int ac, char **av, int *setlocale_collate) +{ + int c, usage = 0; + char *str; + char tmp_1k_buf[1000]; /* tmp buf to contain err msgs */ + +Loop: + /* while more arguments with leading - */ + while(--ac > 0 && **++av == '-'){ + /* while more chars in this argument */ + while(*++*av){ + + if(strcmp(*av, "no_setlocale_collate") == 0){ + *setlocale_collate = 0; + goto Loop; + } +#ifndef _WINDOWS + else if(strcmp(*av, "syscs") == 0){ + use_system_translation = !use_system_translation; + goto Loop; + } +#endif /* ! _WINDOWS */ +#ifdef _WINDOWS + if(strcmp(*av, "cnf") == 0 + || strcmp(*av, "cnb") == 0 + || strcmp(*av, "crf") == 0 + || strcmp(*av, "crb") == 0){ + + char *cmd = *av; /* save it to use below */ + + if(--ac){ + str = *++av; + if(cmd[1] == 'n'){ + if(cmd[2] == 'f') + pico_nfcolor(str); + else if(cmd[2] == 'b') + pico_nbcolor(str); + } + else if(cmd[1] == 'r'){ + if(cmd[2] == 'f') + pico_rfcolor(str); + else if(cmd[2] == 'b') + pico_rbcolor(str); + } + } + else{ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pilot_missing_color), cmd); + pilot_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + + goto Loop; + } +#endif /* _WINDOWS */ +#ifndef _WINDOWS + else if(strcmp(*av, "dcs") == 0 || strcmp(*av, "kcs") == 0){ + char *cmd = *av; + + if(--ac){ + if(strcmp(*av, "dcs") == 0){ + display_character_set = *++av; + if(!output_charset_is_supported(display_character_set)){ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pilot_output_charset), display_character_set); + pilot_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + } + else{ + keyboard_character_set = *++av; + if(!input_charset_is_supported(keyboard_character_set)){ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pilot_input_charset), keyboard_character_set); + pilot_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + } + } + else{ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pilot_missing_charset), cmd); + pilot_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + + goto Loop; + } +#endif /* ! _WINDOWS */ + + /* + * Single char options. + */ + switch(c = **av){ + /* + * These don't take arguments. + */ + case 'a': + gmode ^= MDDOTSOK; /* show dot files */ + break; + case 'f': /* -f for function key use */ + gmode ^= MDFKEY; + break; + case 'j': /* allow "Goto" in file browser */ + gmode ^= MDGOTO; + break; + case 'g': /* show-cursor in file browser */ + gmode ^= MDSHOCUR; + break; + case 'm': /* turn on mouse support */ + gmode ^= MDMOUSE; + break; + case 'v': /* single column display */ + gmode ^= MDONECOL; + break; /* break back to inner-while */ + case 'x': /* suppress keyhelp */ + sup_keyhelp = !sup_keyhelp; + break; + case 'q': /* -q for termcap takes precedence */ + gmode ^= MDTCAPWINS; + break; + case 'z': /* -z to suspend */ + gmode ^= MDSSPD; + break; + case 'h': + usage++; + break; + + /* + * These do take arguments. + */ + case 'n': /* -n for new mail notification */ + case 'o' : /* operating tree */ + if(*++*av) + str = *av; + else if(--ac) + str = *++av; + else{ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pilot_missing_arg), c); + pilot_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + goto Loop; + } + + switch(c){ + case 'o': + strncpy(opertree, str, NLINE); + gmode ^= MDTREE; + break; + + /* numeric args */ + case 'n': + if(!isdigit((unsigned char)str[0])){ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pilot_missing_num), c); + pilot_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + } + + set_input_timeout(180); + + if(set_input_timeout(atoi(str)) < 30) + set_input_timeout(180); + + break; + } + + goto Loop; + + default: /* huh? */ + snprintf(tmp_1k_buf, sizeof(tmp_1k_buf), _(args_pilot_missing_flag), c); + pilot_display_args_err(tmp_1k_buf, NULL, 1); + usage++; + break; + } + } + } + + if(usage) + pilot_args_help(); + + /* return the directory */ + if(ac > 0) + return(*av); + else + return(NULL); +} + + +/*---------------------------------------------------------------------- + print a few lines of help for command line arguments + + Args: none + + Result: prints help messages + ----------------------------------------------------------------------*/ +void +pilot_args_help(void) +{ + char **a; + char *pp[2]; + + pp[1] = NULL; + + /** print out possible starting arguments... **/ + + for(a=args_pilot_args; a && *a; a++){ + pp[0] = _(*a); + pilot_display_args_err(NULL, pp, 0); + } + + exit(1); +} + + +/*---------------------------------------------------------------------- + write argument error to the display... + + Args: none + + Result: prints help messages + ----------------------------------------------------------------------*/ +void +pilot_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), _("Argument Error: %.200s"), s); + else + errp = s; + + if(errp) + fprintf(fp, "%s\n", errp); + + while(a && *a) + fprintf(fp, "%s\n", *a++); +} diff --git a/pico/random.c b/pico/random.c new file mode 100644 index 00000000..780c083e --- /dev/null +++ b/pico/random.c @@ -0,0 +1,427 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: random.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 + * + * ======================================================================== + */ + +/* + * Program: Random routines + * + * This file contains the command processing functions for a number of random + * commands. There is no functional grouping here, for sure. + */ + +#include "headers.h" + +#include "osdep/terminal.h" + +int worthit(int *); + +int tabsize; /* Tab size (0: use real tabs) */ + + +/* + * Display the current position of the cursor, in origin 1 X-Y coordinates, + * the character that is under the cursor (in octal), and the fraction of the + * text that is before the cursor. The displayed column is not the current + * column, but the column that would be used on an infinite width display. + * Normally this is bound to "C-X =". + */ +int +showcpos(int f, int n) +{ + register LINE *clp; + register long nch; + register int cbo; + register long nbc; + register int lines; + register int thisline; + char buffer[80]; + + clp = lforw(curbp->b_linep); /* Grovel the data. */ + cbo = 0; + nch = 0L; + lines = 0; + for (;;) { + if (clp==curwp->w_dotp && cbo==curwp->w_doto) { + thisline = lines; + nbc = nch; + } + if (cbo == llength(clp)) { + if (clp == curbp->b_linep) + break; + clp = lforw(clp); + cbo = 0; + lines++; + } else + ++cbo; + ++nch; + } + + snprintf(buffer,sizeof(buffer),"line %d of %d (%d%%%%), character %ld of %ld (%d%%%%)", + thisline+1, lines+1, (int)((100L*(thisline+1))/(lines+1)), + nbc, nch, (nch) ? (int)((100L*nbc)/nch) : 0); + + emlwrite(buffer, NULL); + return (TRUE); +} + + +/* + * Return current column. Stop at first non-blank given TRUE argument. + */ +int +getccol(int bflg) +{ + UCS c; + int i, col; + + col = 0; + for (i=0; iw_doto; ++i) { + c = lgetc(curwp->w_dotp, i).c; + if (c!=' ' && c!='\t' && bflg) + break; + + if (c == '\t'){ + col |= 0x07; + ++col; + } + else if (ISCONTROL(c)){ + col += 2; + } + else{ + int ww; + + ww = wcellwidth(c); + col += (ww >= 0 ? ww : 1); + } + } + + return(col); +} + + + +/* + * Set tab size if given non-default argument (n <> 1). Otherwise, insert a + * tab into file. If given argument, n, of zero, change to true tabs. + * If n > 1, simulate tab stop every n-characters using spaces. This has to be + * done in this slightly funny way because the tab (in ASCII) has been turned + * into "C-I" (in 10 bit code) already. Bound to "C-I". + */ +int +tab(int f, int n) +{ + if (n < 0) + return (FALSE); + + if (n == 0 || n > 1) { + tabsize = n; + return(TRUE); + } + + if (! tabsize) + return(linsert(1, '\t')); + + return(linsert(tabsize - (getccol(FALSE) % tabsize), ' ')); +} + + +/* + * Insert a newline. Bound to "C-M". + */ +int +newline(int f, int n) +{ + register int s; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if (n < 0) + return (FALSE); + + if(TERM_OPTIMIZE && (curwp->w_dotp != curwp->w_bufp->b_linep)){ + int l; + + if(worthit(&l)){ + if(curwp->w_doto != 0) + l++; + scrolldown(curwp, l, n); + } + } + + /* if we are in C mode and this is a default */ + /* pico's never in C mode */ + + if(Pmaster && Pmaster->allow_flowed_text && curwp->w_doto + && ucs4_isspace(lgetc(curwp->w_dotp, curwp->w_doto - 1).c) + && !(curwp->w_doto == 3 + && lgetc(curwp->w_dotp, 0).c == '-' + && lgetc(curwp->w_dotp, 1).c == '-' + && lgetc(curwp->w_dotp, 2).c == ' ')){ + /* + * flowed mode, make the newline a hard one by + * stripping trailing space. + */ + int i, dellen; + for(i = curwp->w_doto - 1; + i && ucs4_isspace(lgetc(curwp->w_dotp, i - 1).c); + i--); + dellen = curwp->w_doto - i; + curwp->w_doto = i; + ldelete(dellen, NULL); + } + /* insert some lines */ + while (n--) { + if ((s=lnewline()) != TRUE) + return (s); + } + return (TRUE); +} + + + +/* + * Delete forward. This is real easy, because the basic delete routine does + * all of the work. Watches for negative arguments, and does the right thing. + * If any argument is present, it kills rather than deletes, to prevent loss + * of text if typed with a big argument. Normally bound to "C-D". + */ +int +forwdel(int f, int n) +{ + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if (n < 0) + return (backdel(f, -n)); + + if(TERM_OPTIMIZE && (curwp->w_dotp != curwp->w_bufp->b_linep)){ + int l; + + if(worthit(&l) && curwp->w_doto == llength(curwp->w_dotp)) + scrollup(curwp, l+1, 1); + } + + if (f != FALSE) { /* Really a kill. */ + if ((lastflag&CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + } + + return (ldelete((long) n, f ? kinsert : NULL)); +} + + + +/* + * Delete backwards. This is quite easy too, because it's all done with other + * functions. Just move the cursor back, and delete forwards. Like delete + * forward, this actually does a kill if presented with an argument. Bound to + * both "RUBOUT" and "C-H". + */ +int +backdel(int f, int n) +{ + register int s; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if (n < 0) + return (forwdel(f, -n)); + + if(TERM_OPTIMIZE && curwp->w_dotp != curwp->w_bufp->b_linep){ + int l; + + if(worthit(&l) && curwp->w_doto == 0 && + lback(curwp->w_dotp) != curwp->w_bufp->b_linep){ + if(l == curwp->w_toprow) + scrollup(curwp, l+1, 1); + else if(llength(lback(curwp->w_dotp)) == 0) + scrollup(curwp, l-1, 1); + else + scrollup(curwp, l, 1); + } + } + + if (f != FALSE) { /* Really a kill. */ + if ((lastflag&CFKILL) == 0) + kdelete(); + + thisflag |= CFKILL; + } + + if ((s=backchar(f, n)) == TRUE) + s = ldelete((long) n, f ? kinsert : NULL); + + return (s); +} + + + +/* + * killtext - delete the line that the cursor is currently in. + * a greatly pared down version of its former self. + */ +int +killtext(int f, int n) +{ + register int chunk; + int opt_scroll = 0; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if ((lastflag&CFKILL) == 0) /* Clear kill buffer if */ + kdelete(); /* last wasn't a kill. */ + + if(gmode & MDDTKILL){ /* */ + if((chunk = llength(curwp->w_dotp) - curwp->w_doto) == 0){ + chunk = 1; + if(TERM_OPTIMIZE) + opt_scroll = 1; + } + } + else{ + gotobol(FALSE, 1); /* wack from bol past newline */ + chunk = llength(curwp->w_dotp) + 1; + if(TERM_OPTIMIZE) + opt_scroll = 1; + } + + /* optimize what motion we can */ + if(opt_scroll && (curwp->w_dotp != curwp->w_bufp->b_linep)){ + int l; + + if(worthit(&l)) + scrollup(curwp, l, 1); + } + + thisflag |= CFKILL; + return(ldelete((long) chunk, kinsert)); +} + + +/* + * Yank text back from the kill buffer. This is really easy. All of the work + * is done by the standard insert routines. All you do is run the loop, and + * check for errors. Bound to "C-Y". + */ +int +yank(int f, int n) +{ + int c, i; + REGION region, *added_region; + LINE *dotp; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if (n < 0) + return (FALSE); + + if(TERM_OPTIMIZE && (curwp->w_dotp != curwp->w_bufp->b_linep)){ + int l; + + if(worthit(&l) && !(lastflag&CFFILL)){ + register int t = 0; + register int i = 0; + register int ch; + + while((ch=fremove(i++)) >= 0) + if(ch == '\n') + t++; + if(t+l < curwp->w_toprow+curwp->w_ntrows) + scrolldown(curwp, l, t); + } + } + + if(lastflag & CFFILL){ /* if last command was fillpara() */ + if(lastflag & CFFLBF){ + gotoeob(FALSE, 1); + dotp = curwp->w_dotp; + gotobob(FALSE, 1); + curwp->w_doto = 0; + getregion(®ion, dotp, llength(dotp)); + } + else{ + added_region = get_last_region_added(); + if(added_region){ + curwp->w_dotp = added_region->r_linep; + curwp->w_doto = added_region->r_offset; + region = (*added_region); + } + else + return(FALSE); + } + + if(!ldelete(region.r_size, NULL)) + return(FALSE); + } /* then splat out the saved buffer */ + + while (n--) { + i = 0; + while ((c = ((lastflag&CFFILL) + ? ((lastflag & CFFLBF) ? kremove(i) : fremove(i)) + : kremove(i))) >= 0) { + if (c == '\n') { + if (lnewline() == FALSE) + return (FALSE); + } else { + if (linsert(1, c) == FALSE) + return (FALSE); + } + + ++i; + } + } + + if(lastflag&CFFLPA){ /* if last command was fill paragraph */ + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_doto = 0; + + curwp->w_flag |= WFMODE; + + if(!Pmaster){ + sgarbk = TRUE; + emlwrite("", NULL); + } + } + + return (TRUE); +} + + + +/* + * worthit - generic sort of test to roughly gage usefulness of using + * optimized scrolling. + * + * note: + * returns the line on the screen, l, that the dot is currently on + */ +int +worthit(int *l) +{ + int i; /* l is current line */ + unsigned below; /* below is avg # of ch/line under . */ + + *l = doton(&i, &below); + below = (i > 0) ? below/(unsigned)i : 0; + + return(below > 3); +} diff --git a/pico/region.c b/pico/region.c new file mode 100644 index 00000000..e1e65896 --- /dev/null +++ b/pico/region.c @@ -0,0 +1,364 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: region.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 + * + * ======================================================================== + */ + +/* + * Program: Region management routines + * + * The routines in this file + * deal with the region, that magic space + * between "." and mark. Some functions are + * commands. Some functions are just for + * internal use. + */ +#include "headers.h" + +/* + * Kill the region. Ask "getregion" + * to figure out the bounds of the region. + * Move "." to the start, and kill the characters. + * Bound to "C-W". + */ +int +killregion(int f, int n) +{ + REGION region; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if (getregion(®ion, curwp->w_markp, curwp->w_marko) != TRUE){ + return (killtext(f, n)); + }else { + mlerase(); + } + + if ((lastflag&CFKILL) == 0) /* This is a kill type */ + kdelete(); /* command, so do magic */ + + thisflag |= CFKILL; /* kill buffer stuff. */ + curwp->w_dotp = region.r_linep; + curwp->w_doto = region.r_offset; + curwp->w_markp = NULL; +#ifdef _WINDOWS + mswin_allowcopycut(NULL); +#endif + + if(ldelete(region.r_size, kinsert)){ + if(curwp->w_dotp == curwp->w_linep && curwp->w_dotp == curbp->b_linep){ + curwp->w_force = 0; /* Center dot. */ + curwp->w_flag |= WFFORCE; + } + + return(TRUE); + } + + return (FALSE); +} + + +/* + * Blast the region without saving . Ask "getregion" + * to figure out the bounds of the region. + * Move "." to the start, and kill the characters. + * Bound to "C-W". + */ +int +deleteregion(int f, int n) +{ + REGION region; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if (getregion(®ion, curwp->w_markp, curwp->w_marko) == TRUE){ + curwp->w_dotp = region.r_linep; + curwp->w_doto = region.r_offset; + curwp->w_markp = NULL; +#ifdef _WINDOWS + mswin_allowcopycut(NULL); +#endif + if(ldelete(region.r_size, NULL)){ + if(curwp->w_dotp == curwp->w_linep + && curwp->w_dotp == curbp->b_linep){ + curwp->w_force = 0; /* Center dot. */ + curwp->w_flag |= WFFORCE; + } + + return(TRUE); + } + } + + return (FALSE); +} + + +/* + * Copy all of the characters in the + * region to the kill buffer. Don't move dot + * at all. This is a bit like a kill region followed + * by a yank. Bound to "M-W". + */ +int +copyregion(int f, int n) +{ + register LINE *linep; + register int loffs; + register int s; + REGION region; + + if ((s=getregion(®ion, curwp->w_markp, curwp->w_marko)) != TRUE) + return (s); + + if ((lastflag&CFKILL) == 0) /* Kill type command. */ + kdelete(); + + thisflag |= CFKILL; + linep = region.r_linep; /* Current line. */ + loffs = region.r_offset; /* Current offset. */ + while (region.r_size--) { + if (loffs == llength(linep)) { /* End of line. */ + if ((s=kinsert('\n')) != TRUE) + return (s); + linep = lforw(linep); + loffs = 0; + } else { /* Middle of line. */ + if ((s=kinsert(lgetc(linep, loffs).c)) != TRUE) + return (s); + ++loffs; + } + } + + return (TRUE); +} + + +/* + * Lower case region. Zap all of the upper + * case characters in the region to lower case. Use + * the region code to set the limits. Scan the buffer, + * doing the changes. Call "lchange" to ensure that + * redisplay is done in all buffers. Bound to + * "C-X C-L". + */ +int +lowerregion(int f, int n) +{ + register LINE *linep; + register int loffs; + register int c; + register int s; + REGION region; + CELL ac; + + ac.a = 0; + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if ((s=getregion(®ion, curwp->w_markp, curwp->w_marko)) != TRUE) + return (s); + + lchange(WFHARD); + linep = region.r_linep; + loffs = region.r_offset; + while (region.r_size--) { + if (loffs == llength(linep)) { + linep = lforw(linep); + loffs = 0; + } else { + c = lgetc(linep, loffs).c; + if (c>='A' && c<='Z'){ + ac.c = c+'a'-'A'; + lputc(linep, loffs, ac); + } + ++loffs; + } + } + + return (TRUE); +} + +/* + * Upper case region. Zap all of the lower + * case characters in the region to upper case. Use + * the region code to set the limits. Scan the buffer, + * doing the changes. Call "lchange" to ensure that + * redisplay is done in all buffers. Bound to + * "C-X C-L". + */ +int +upperregion(int f, int n) +{ + register LINE *linep; + register int loffs; + register int c; + register int s; + REGION region; + CELL ac; + + ac.a = 0; + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if ((s=getregion(®ion, curwp->w_markp, curwp->w_marko)) != TRUE) + return (s); + + lchange(WFHARD); + linep = region.r_linep; + loffs = region.r_offset; + while (region.r_size--) { + if (loffs == llength(linep)) { + linep = lforw(linep); + loffs = 0; + } else { + c = lgetc(linep, loffs).c; + if (c>='a' && c<='z'){ + ac.c = c - 'a' + 'A'; + lputc(linep, loffs, ac); + } + ++loffs; + } + } + + return (TRUE); +} + +/* + * This routine figures out the + * bounds of the region in the current window, and + * fills in the fields of the "REGION" structure pointed + * to by "rp". Because the dot and mark are usually very + * close together, we scan outward from dot looking for + * mark. This should save time. Return a standard code. + * Callers of this routine should be prepared to get + * an "ABORT" status; we might make this have the + * conform thing later. + */ +int +getregion(REGION *rp, LINE *markp, int marko) +{ + register LINE *flp; + register LINE *blp; + long fsize; + register long bsize; + + if (markp == NULL) { + return (FALSE); + } + + if (curwp->w_dotp == markp) { + rp->r_linep = curwp->w_dotp; + if (curwp->w_doto < marko) { + rp->r_offset = curwp->w_doto; + rp->r_size = marko - curwp->w_doto; + } else { + rp->r_offset = marko; + rp->r_size = curwp->w_doto - marko; + } + return (TRUE); + } + + blp = curwp->w_dotp; + bsize = curwp->w_doto; + flp = curwp->w_dotp; + fsize = llength(flp)-curwp->w_doto+1; + while (flp!=curbp->b_linep || lback(blp)!=curbp->b_linep) { + if (flp != curbp->b_linep) { + flp = lforw(flp); + if (flp == markp) { + rp->r_linep = curwp->w_dotp; + rp->r_offset = curwp->w_doto; + rp->r_size = fsize + marko; + return (TRUE); + } + + fsize += llength(flp) + 1; + } + + if (lback(blp) != curbp->b_linep) { + blp = lback(blp); + bsize += llength(blp)+1; + if (blp == markp) { + rp->r_linep = blp; + rp->r_offset = marko; + rp->r_size = bsize - marko; + return (TRUE); + } + } + } + + emlwrite("Bug: lost mark", NULL); + return (FALSE); +} + + +/* + * set the highlight attribute accordingly on all characters in region + */ +int +markregion(int attr) +{ + register LINE *linep; + register int loffs; + register int s; + REGION region; + CELL ac; + + if ((s=getregion(®ion, curwp->w_markp, curwp->w_marko)) != TRUE) + return (s); + + lchange(WFHARD); + linep = region.r_linep; + loffs = region.r_offset; + while (region.r_size--) { + if (loffs == llength(linep)) { + linep = lforw(linep); + loffs = 0; + } else { + ac = lgetc(linep, loffs); + ac.a = attr; + lputc(linep, loffs, ac); + ++loffs; + } + } + + return (TRUE); +} + + +/* + * clear all the attributes of all the characters in the buffer? + * this is real dumb. Movement with mark set needs to be smarter! + */ +void +unmarkbuffer(void) +{ + register LINE *linep; + register int n; + CELL c; + + linep = curwp->w_linep; + while(lforw(linep) != curwp->w_linep){ + n = llength(linep); + for(n=0; n < llength(linep); n++){ + c = lgetc(linep, n); + c.a = 0; + lputc(linep, n, c); + } + + linep = lforw(linep); + } +} diff --git a/pico/search.c b/pico/search.c new file mode 100644 index 00000000..0886b24c --- /dev/null +++ b/pico/search.c @@ -0,0 +1,1037 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: search.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 + * + * ======================================================================== + */ + +/* + * Program: Searching routines + * + * The functions in this file implement commands that search in the forward + * and backward directions. There are no special characters in the search + * strings. Probably should have a regular expression search, or something + * like that. + * + */ + +#include "headers.h" + +int eq(UCS, UCS); +int expandp(UCS *, UCS *, int); +int readnumpat(char *); +void get_pat_cases(UCS *, UCS *); +int srpat(char *, UCS *, size_t, int); +int readpattern(char *, int); +int replace_pat(UCS *, int *); +int replace_all(UCS *, UCS *); + + +#define FWS_RETURN(RV) { \ + thisflag |= CFSRCH; \ + curwp->w_flag |= WFMODE; \ + sgarbk = TRUE; \ + return(RV); \ + } + + +/* + * Search forward. Get a search string from the user, and search, beginning at + * ".", for the string. If found, reset the "." to be just after the match + * string, and [perhaps] repaint the display. Bound to "C-S". + */ + +/* string search input parameters */ + +#define PTBEG 1 /* leave the point at the begining on search */ +#define PTEND 2 /* leave the point at the end on search */ + +#define NPMT (2*NLINE+32) + + +static char *SearchHelpText[] = { +/* TRANSLATORS: Some help text that goes together in a group. */ +N_("Help for Search Command"), +" ", +N_(" Enter the words or characters you would like to search"), +N_("~ for, then press ~R~e~t~u~r~n. The search then takes place."), +N_(" When the characters or words that you entered "), +N_(" are found, the buffer will be redisplayed with the cursor "), +N_(" at the beginning of the selected text."), +" ", +N_(" The most recent string for which a search was made is"), +N_(" displayed in the \"Search\" prompt between the square"), +N_(" brackets. This string is the default search prompt."), +N_("~ Hitting only ~R~e~t~u~r~n or at the prompt will cause the"), +N_(" search to be made with the default value."), +" ", +N_(" The text search is not case sensitive, and will examine the"), +N_(" entire message."), +" ", +N_(" Should the search fail, a message will be displayed."), +" ", +N_("End of Search Help."), +" ", +NULL +}; + + +/* + * Compare two characters. The "bc" comes from the buffer. It has it's case + * folded out. The "pc" is from the pattern. + */ +int +eq(UCS bc, UCS pc) +{ + if ((curwp->w_bufp->b_mode & MDEXACT) == 0){ + if (bc>='a' && bc<='z') + bc -= 0x20; + + if (pc>='a' && pc<='z') + pc -= 0x20; + } + + return(bc == pc); +} + + +int +forwsearch(int f, int n) +{ + int status; + int wrapt = FALSE, wrapt2 = FALSE; + int repl_mode = FALSE; + UCS defpat[NPAT]; + int search = FALSE; + EML eml; + + /* resolve the repeat count */ + if (n == 0) + n = 1; + + if (n < 1) /* search backwards */ + FWS_RETURN(0); + + defpat[0] = '\0'; + + /* ask the user for the text of a pattern */ + while(1){ + + if (gmode & MDREPLACE) + status = srpat("Search", defpat, NPAT, repl_mode); + else + status = readpattern("Search", TRUE); + + switch(status){ + case TRUE: /* user typed something */ + search = TRUE; + break; + + case HELPCH: /* help requested */ + if(Pmaster){ + VARS_TO_SAVE *saved_state; + + saved_state = save_pico_state(); + (*Pmaster->helper)(Pmaster->search_help, + _("Help for Searching"), 1); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + } + else + pico_help(SearchHelpText, _("Help for Searching"), 1); + + case (CTRL|'L'): /* redraw requested */ + pico_refresh(FALSE, 1); + update(); + break; + + case (CTRL|'V'): + gotoeob(0, 1); + mlerase(); + FWS_RETURN(TRUE); + + case (CTRL|'Y'): + gotobob(0, 1); + mlerase(); + FWS_RETURN(TRUE); + + case (CTRL|'T') : + switch(status = readnumpat(_("Search to Line Number : "))){ + case -1 : + emlwrite(_("Search to Line Number Cancelled"), NULL); + FWS_RETURN(FALSE); + + case 0 : + emlwrite(_("Line number must be greater than zero"), NULL); + FWS_RETURN(FALSE); + + case -2 : + emlwrite(_("Line number must contain only digits"), NULL); + FWS_RETURN(FALSE); + + case -3 : + continue; + + default : + gotoline(0, status); + mlerase(); + FWS_RETURN(TRUE); + } + + break; + + case (CTRL|'W'): + { + LINE *linep = curwp->w_dotp; + int offset = curwp->w_doto; + + gotobop(0, 1); + gotobol(0, 1); + + /* + * if we're asked to backup and we're already + * + */ + if((lastflag & CFSRCH) + && linep == curwp->w_dotp + && offset == curwp->w_doto + && !(offset == 0 && lback(linep) == curbp->b_linep)){ + backchar(0, 1); + gotobop(0, 1); + gotobol(0, 1); + } + } + + mlerase(); + FWS_RETURN(TRUE); + + case (CTRL|'O'): + if(curwp->w_dotp != curbp->b_linep){ + gotoeop(0, 1); + forwchar(0, 1); + } + + mlerase(); + FWS_RETURN(TRUE); + + case (CTRL|'U'): + fillbuf(0, 1); + mlerase(); + FWS_RETURN(TRUE); + + case (CTRL|'R'): /* toggle replacement option */ + repl_mode = !repl_mode; + break; + + default: + if(status == ABORT) + emlwrite(_("Search Cancelled"), NULL); + else + mlerase(); + + FWS_RETURN(FALSE); + } + + /* replace option is disabled */ + if (!(gmode & MDREPLACE)){ + ucs4_strncpy(defpat, pat, NPAT); + defpat[NPAT-1] = '\0'; + break; + } + else if (search){ /* search now */ + ucs4_strncpy(pat, defpat, NPAT); /* remember this search for the future */ + pat[NPAT-1] = '\0'; + break; + } + } + + /* + * This code is kind of dumb. What I want is successive C-W 's to + * move dot to successive occurences of the pattern. So, if dot is + * already sitting at the beginning of the pattern, then we'll move + * forward a char before beginning the search. We'll let the + * automatic wrapping handle putting the dot back in the right + * place... + */ + status = 0; /* using "status" as int temporarily! */ + while(1){ + if(defpat[status] == '\0'){ + forwchar(0, 1); + break; /* find next occurence! */ + } + + if(status + curwp->w_doto >= llength(curwp->w_dotp) || + !eq(defpat[status],lgetc(curwp->w_dotp, curwp->w_doto + status).c)) + break; /* do nothing! */ + status++; + } + + /* search for the pattern */ + + while (n-- > 0) { + if((status = forscan(&wrapt,defpat,NULL,0,PTBEG)) == FALSE) + break; + } + + /* and complain if not there */ + if (status == FALSE){ + char *utf8; + UCS x[1]; + + x[0] = '\0'; + + utf8 = ucs4_to_utf8_cpystr(defpat ? defpat : x); + /* TRANSLATORS: reporting the result of a failed search */ + eml.s = utf8; + emlwrite(_("\"%s\" not found"), &eml); + if(utf8) + fs_give((void **) &utf8); + } + else if((gmode & MDREPLACE) && repl_mode == TRUE){ + status = replace_pat(defpat, &wrapt2); /* replace pattern */ + if (wrapt == TRUE || wrapt2 == TRUE){ + eml.s = (status == ABORT) ? "cancelled but wrapped" : "Wrapped"; + emlwrite("Replacement %s", &eml); + } + } + else if(wrapt == TRUE){ + emlwrite("Search Wrapped", NULL); + } + else if(status == TRUE){ + emlwrite("", NULL); + } + + FWS_RETURN(status); +} + + +/* Replace a pattern with the pattern the user types in one or more times. */ +int +replace_pat(UCS *defpat, int *wrapt) +{ + register int status; + UCS lpat[NPAT], origpat[NPAT]; /* case sensitive pattern */ + EXTRAKEYS menu_pat[2]; + int repl_all = FALSE; + UCS *b; + char utf8tmp[NPMT]; + UCS prompt[NPMT]; + UCS *promptp; + + forscan(wrapt, defpat, NULL, 0, PTBEG); /* go to word to be replaced */ + + lpat[0] = '\0'; + + /* additional 'replace all' menu option */ + menu_pat[0].name = "^X"; + menu_pat[0].key = (CTRL|'X'); + menu_pat[0].label = N_("Repl All"); + KS_OSDATASET(&menu_pat[0], KS_NONE); + menu_pat[1].name = NULL; + + while(1) { + + update(); + (*term.t_rev)(1); + get_pat_cases(origpat, defpat); + pputs(origpat, 1); /* highlight word */ + (*term.t_rev)(0); + + snprintf(utf8tmp, NPMT, "Replace%s \"", repl_all ? " every" : ""); + b = utf8_to_ucs4_cpystr(utf8tmp); + if(b){ + ucs4_strncpy(prompt, b, NPMT); + prompt[NPMT-1] = '\0'; + fs_give((void **) &b); + } + + promptp = &prompt[ucs4_strlen(prompt)]; + + expandp(defpat, promptp, NPMT-(promptp-prompt)); + prompt[NPMT-1] = '\0'; + promptp += ucs4_strlen(promptp); + + b = utf8_to_ucs4_cpystr("\" with"); + if(b){ + ucs4_strncpy(promptp, b, NPMT-(promptp-prompt)); + promptp += ucs4_strlen(promptp); + prompt[NPMT-1] = '\0'; + fs_give((void **) &b); + } + + if(rpat[0] != '\0'){ + if((promptp-prompt) < NPMT-2){ + *promptp++ = ' '; + *promptp++ = '['; + *promptp = '\0'; + } + + expandp(rpat, promptp, NPMT-(promptp-prompt)); + prompt[NPMT-1] = '\0'; + promptp += ucs4_strlen(promptp); + + if((promptp-prompt) < NPMT-1){ + *promptp++ = ']'; + *promptp = '\0'; + } + } + + if((promptp-prompt) < NPMT-3){ + *promptp++ = ' '; + *promptp++ = ':'; + *promptp++ = ' '; + *promptp = '\0'; + } + + prompt[NPMT-1] = '\0'; + + status = mlreplyd(prompt, lpat, NPAT, QDEFLT, menu_pat); + + curwp->w_flag |= WFMOVE; + + switch(status){ + + case TRUE : + case FALSE : + if(lpat[0]){ + ucs4_strncpy(rpat, lpat, NPAT); /* remember default */ + rpat[NPAT-1] = '\0'; + } + else{ + ucs4_strncpy(lpat, rpat, NPAT); /* use default */ + lpat[NPAT-1] = '\0'; + } + + if (repl_all){ + status = replace_all(defpat, lpat); + } + else{ + chword(defpat, lpat); /* replace word */ + update(); + status = TRUE; + } + + if(status == TRUE) + emlwrite("", NULL); + + return(status); + + case HELPCH: /* help requested */ + if(Pmaster){ + VARS_TO_SAVE *saved_state; + + saved_state = save_pico_state(); + (*Pmaster->helper)(Pmaster->search_help, + _("Help for Searching"), 1); + if(saved_state){ + restore_pico_state(saved_state); + free_pico_state(saved_state); + } + } + else + pico_help(SearchHelpText, _("Help for Searching"), 1); + + case (CTRL|'L'): /* redraw requested */ + pico_refresh(FALSE, 1); + update(); + break; + + case (CTRL|'X'): /* toggle replace all option */ + if (repl_all){ + repl_all = FALSE; + /* TRANSLATORS: abbreviation for Replace All occurences */ + menu_pat[0].label = N_("Repl All"); + } + else{ + repl_all = TRUE; + /* TRANSLATORS: Replace just one occurence */ + menu_pat[0].label = N_("Repl One"); + } + + break; + + default: + if(status == ABORT){ + emlwrite(_("Replacement Cancelled"), NULL); + pico_refresh(FALSE, 1); + } + else{ + mlerase(); + chword(defpat, origpat); + } + + update(); + return(FALSE); + } + } +} + + +/* Since the search is not case sensitive, we must obtain the actual pattern + that appears in the text, so that we can highlight (and unhighlight) it + without using the wrong cases */ +void +get_pat_cases(UCS *realpat, UCS *searchpat) +{ + int i, searchpatlen, curoff; + + curoff = curwp->w_doto; + searchpatlen = ucs4_strlen(searchpat); + + for (i = 0; i < searchpatlen; i++) + realpat[i] = lgetc(curwp->w_dotp, curoff++).c; + + realpat[searchpatlen] = '\0'; +} + + +/* Ask the user about every occurence of orig pattern and replace it with a + repl pattern if the response is affirmative. */ +int +replace_all(UCS *orig, UCS *repl) +{ + register int status = 0; + UCS *b; + UCS realpat[NPAT]; + char utf8tmp[NPMT]; + UCS *promptp; + UCS prompt[NPMT]; + int wrapt, n = 0; + LINE *stop_line = curwp->w_dotp; + int stop_offset = curwp->w_doto; + EML eml; + + while (1) + if (forscan(&wrapt, orig, stop_line, stop_offset, PTBEG)){ + curwp->w_flag |= WFMOVE; /* put cursor back */ + + update(); + (*term.t_rev)(1); + get_pat_cases(realpat, orig); + pputs(realpat, 1); /* highlight word */ + (*term.t_rev)(0); + fflush(stdout); + + snprintf(utf8tmp, NPMT, "Replace \""); + b = utf8_to_ucs4_cpystr(utf8tmp); + if(b){ + ucs4_strncpy(prompt, b, NPMT); + prompt[NPMT-1] = '\0'; + fs_give((void **) &b); + } + + promptp = &prompt[ucs4_strlen(prompt)]; + + expandp(orig, promptp, NPMT-(promptp-prompt)); + prompt[NPMT-1] = '\0'; + promptp += ucs4_strlen(promptp); + + b = utf8_to_ucs4_cpystr("\" with \""); + if(b){ + ucs4_strncpy(promptp, b, NPMT-(promptp-prompt)); + promptp += ucs4_strlen(promptp); + prompt[NPMT-1] = '\0'; + fs_give((void **) &b); + } + + expandp(repl, promptp, NPMT-(promptp-prompt)); + prompt[NPMT-1] = '\0'; + promptp += ucs4_strlen(promptp); + + if((promptp-prompt) < NPMT-1){ + *promptp++ = '\"'; + *promptp = '\0'; + } + + prompt[NPMT-1] = '\0'; + + status = mlyesno(prompt, TRUE); /* ask user */ + + if (status == TRUE){ + n++; + chword(realpat, repl); /* replace word */ + update(); + }else{ + chword(realpat, realpat); /* replace word by itself */ + update(); + if(status == ABORT){ /* if cancelled return */ + eml.s = comatose(n); + emlwrite("Replace All cancelled after %s changes", &eml); + return (ABORT); /* ... else keep looking */ + } + } + } + else{ + char *utf8; + + utf8 = ucs4_to_utf8_cpystr(orig); + if(utf8){ + eml.s = utf8; + emlwrite(_("No more matches for \"%s\""), &eml); + fs_give((void **) &utf8); + } + else + emlwrite(_("No more matches"), NULL); + + return (FALSE); + } +} + + +/* Read a replacement pattern. Modeled after readpattern(). */ +int +srpat(char *utf8prompt, UCS *defpat, size_t defpatlen, int repl_mode) +{ + register int s; + int i = 0; + UCS *b; + UCS prompt[NPMT]; + UCS *promptp; + EXTRAKEYS menu_pat[8]; + + menu_pat[i = 0].name = "^Y"; + menu_pat[i].label = N_("FirstLine"); + menu_pat[i].key = (CTRL|'Y'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^V"; + menu_pat[i].label = N_("LastLine"); + menu_pat[i].key = (CTRL|'V'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^R"; + menu_pat[i].label = repl_mode ? N_("No Replace") : N_("Replace"); + menu_pat[i].key = (CTRL|'R'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + if(!repl_mode){ + menu_pat[++i].name = "^T"; + menu_pat[i].label = N_("LineNumber"); + menu_pat[i].key = (CTRL|'T'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^W"; + /* TRANSLATORS: Start of paragraph */ + menu_pat[i].label = N_("Start of Para"); + menu_pat[i].key = (CTRL|'W'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^O"; + menu_pat[i].label = N_("End of Para"); + menu_pat[i].key = (CTRL|'O'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^U"; + /* TRANSLATORS: Instead of justifying (formatting) just a + single paragraph, Full Justify justifies the entire + message. */ + menu_pat[i].label = N_("FullJustify"); + menu_pat[i].key = (CTRL|'U'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + } + + menu_pat[++i].name = NULL; + + b = utf8_to_ucs4_cpystr(utf8prompt); + if(b){ + ucs4_strncpy(prompt, b, NPMT); + prompt[NPMT-1] = '\0'; + fs_give((void **) &b); + } + + promptp = &prompt[ucs4_strlen(prompt)]; + + if(repl_mode){ + b = utf8_to_ucs4_cpystr(" (to replace)"); + if(b){ + ucs4_strncpy(promptp, b, NPMT-(promptp-prompt)); + promptp += ucs4_strlen(promptp); + prompt[NPMT-1] = '\0'; + fs_give((void **) &b); + } + } + + if(pat[0] != '\0'){ + if((promptp-prompt) < NPMT-2){ + *promptp++ = ' '; + *promptp++ = '['; + *promptp = '\0'; + } + + expandp(pat, promptp, NPMT-(promptp-prompt)); + prompt[NPMT-1] = '\0'; + promptp += ucs4_strlen(promptp); + + if((promptp-prompt) < NPMT-1){ + *promptp++ = ']'; + *promptp = '\0'; + } + } + + if((promptp-prompt) < NPMT-2){ + *promptp++ = ':'; + *promptp++ = ' '; + *promptp = '\0'; + } + + prompt[NPMT-1] = '\0'; + + s = mlreplyd(prompt, defpat, defpatlen, QDEFLT, menu_pat); + + if (s == TRUE || s == FALSE){ /* changed or not, they're done */ + if(!defpat[0]){ /* use default */ + ucs4_strncpy(defpat, pat, defpatlen); + defpat[defpatlen-1] = '\0'; + } + else if(ucs4_strcmp(pat, defpat)){ /* Specified */ + ucs4_strncpy(pat, defpat, NPAT); + pat[NPAT-1] = '\0'; + rpat[0] = '\0'; + } + + s = TRUE; /* let caller know to proceed */ + } + + return(s); +} + + +/* + * Read a pattern. Stash it in the external variable "pat". The "pat" is not + * updated if the user types in an empty line. If the user typed an empty line, + * and there is no old pattern, it is an error. Display the old pattern, in the + * style of Jeff Lomicka. There is some do-it-yourself control expansion. + * change to using to delemit the end-of-pattern to allow s in + * the search string. + */ + +int +readnumpat(char *utf8prompt) +{ + int i, n; + char numpat[NPMT]; + EXTRAKEYS menu_pat[2]; + + menu_pat[i = 0].name = "^T"; + menu_pat[i].label = N_("No Line Number"); + menu_pat[i].key = (CTRL|'T'); + KS_OSDATASET(&menu_pat[i++], KS_NONE); + + menu_pat[i].name = NULL; + + numpat[0] = '\0'; + while(1) + switch(mlreplyd_utf8(utf8prompt, numpat, NPMT, QNORML, menu_pat)){ + case TRUE : + if(*numpat){ + for(i = n = 0; numpat[i]; i++) + if(strchr("0123456789", numpat[i])){ + n = (n * 10) + (numpat[i] - '0'); + } + else + return(-2); + + return(n); + } + + case FALSE : + default : + return(-1); + + case (CTRL|'T') : + return(-3); + + case (CTRL|'L') : + case HELPCH : + break; + } +} + + +int +readpattern(char *utf8prompt, int text_mode) +{ + register int s; + int i; + UCS *b; + UCS tpat[NPAT+20]; + UCS *tpatp; + EXTRAKEYS menu_pat[7]; + + menu_pat[i = 0].name = "^Y"; + menu_pat[i].label = N_("FirstLine"); + menu_pat[i].key = (CTRL|'Y'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^V"; + menu_pat[i].label = N_("LastLine"); + menu_pat[i].key = (CTRL|'V'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + if(text_mode){ + menu_pat[++i].name = "^T"; + menu_pat[i].label = N_("LineNumber"); + menu_pat[i].key = (CTRL|'T'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^W"; + menu_pat[i].label = N_("Start of Para"); + menu_pat[i].key = (CTRL|'W'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^O"; + menu_pat[i].label = N_("End of Para"); + menu_pat[i].key = (CTRL|'O'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + + menu_pat[++i].name = "^U"; + menu_pat[i].label = N_("FullJustify"); + menu_pat[i].key = (CTRL|'U'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + } + + menu_pat[++i].name = NULL; + + b = utf8_to_ucs4_cpystr(utf8prompt); + if(b){ + ucs4_strncpy(tpat, b, NPAT+20); + tpat[NPAT+20-1] = '\0'; + fs_give((void **) &b); + } + + tpatp = &tpat[ucs4_strlen(tpat)]; + + if(pat[0] != '\0'){ + if((tpatp-tpat) < NPAT+20-2){ + *tpatp++ = ' '; + *tpatp++ = '['; + *tpatp = '\0'; + } + + expandp(pat, tpatp, NPAT+20-(tpatp-tpat)); + tpat[NPAT+20-1] = '\0'; + tpatp += ucs4_strlen(tpatp); + + if((tpatp-tpat) < NPAT+20-1){ + *tpatp++ = ']'; + *tpatp = '\0'; + } + } + + if((tpatp-tpat) < NPAT+20-3){ + *tpatp++ = ' '; + *tpatp++ = ':'; + *tpatp++ = ' '; + *tpatp = '\0'; + } + + tpat[NPAT+20-1] = '\0'; + + s = mlreplyd(tpat, tpat, NPAT, QNORML, menu_pat); + + if ((s == TRUE) && ucs4_strcmp(pat,tpat)){ /* Specified */ + ucs4_strncpy(pat, tpat, NPAT); + pat[NPAT-1] = '\0'; + rpat[0] = '\0'; + } + else if (s == FALSE && pat[0] != '\0') /* CR, but old one */ + s = TRUE; + + return(s); +} + + +/* search forward for a */ +int +forscan(int *wrapt, /* boolean indicating search wrapped */ + UCS *patrn, /* string to scan for */ + LINE *limitp, /* stop searching if reached */ + int limito, /* stop searching if reached */ + int leavep) /* place to leave point + PTBEG = begining of match + PTEND = at end of match */ + +{ + LINE *curline; /* current line during scan */ + int curoff; /* position within current line */ + LINE *lastline; /* last line position during scan */ + int lastoff; /* position within last line */ + UCS c; /* character at current position */ + LINE *matchline; /* current line during matching */ + int matchoff; /* position in matching line */ + UCS *patptr; /* pointer into pattern */ + int stopoff; /* offset to stop search */ + LINE *stopline; /* line to stop search */ + + *wrapt = FALSE; + + /* + * the idea is to set the character to end the search at the + * next character in the buffer. thus, let the search wrap + * completely around the buffer. + * + * first, test to see if we are at the end of the line, + * otherwise start searching on the next character. + */ + if(curwp->w_doto == llength(curwp->w_dotp)){ + /* + * dot is not on end of a line + * start at 0 offset of the next line + */ + stopoff = curoff = 0; + stopline = curline = lforw(curwp->w_dotp); + if (curwp->w_dotp == curbp->b_linep) + *wrapt = TRUE; + } + else{ + stopoff = curoff = curwp->w_doto; + stopline = curline = curwp->w_dotp; + } + + /* scan each character until we hit the head link record */ + + /* + * maybe wrapping is a good idea + */ + while (curline){ + + if (curline == curbp->b_linep) + *wrapt = TRUE; + + /* save the current position in case we need to + restore it on a match */ + + lastline = curline; + lastoff = curoff; + + /* get the current character resolving EOLs */ + if (curoff == llength(curline)) { /* if at EOL */ + curline = lforw(curline); /* skip to next line */ + curoff = 0; + c = '\n'; /* and return a */ + } + else + c = lgetc(curline, curoff++).c; /* get the char */ + + /* test it against first char in pattern */ + if (eq(c, patrn[0]) != FALSE) { /* if we find it..*/ + /* setup match pointers */ + matchline = curline; + matchoff = curoff; + patptr = &patrn[0]; + + /* scan through patrn for a match */ + while (*++patptr != '\0') { + /* advance all the pointers */ + if (matchoff == llength(matchline)) { + /* advance past EOL */ + matchline = lforw(matchline); + matchoff = 0; + c = '\n'; + } else + c = lgetc(matchline, matchoff++).c; + + if(matchline == limitp && matchoff == limito) + return(FALSE); + + /* and test it against the pattern */ + if (eq(*patptr, c) == FALSE) + goto fail; + } + + /* A SUCCESSFULL MATCH!!! */ + /* reset the global "." pointers */ + if (leavep == PTEND) { /* at end of string */ + curwp->w_dotp = matchline; + curwp->w_doto = matchoff; + } + else { /* at begining of string */ + curwp->w_dotp = lastline; + curwp->w_doto = lastoff; + } + + curwp->w_flag |= WFMOVE; /* flag that we have moved */ + return(TRUE); + + } + +fail:; /* continue to search */ + if(((curline == stopline) && (curoff == stopoff)) + || (curline == limitp && curoff == limito)) + break; /* searched everywhere... */ + } + /* we could not find a match */ + + return(FALSE); +} + + + +/* expandp: expand control key sequences for output */ +int +expandp(UCS *srcstr, /* string to expand */ + UCS *deststr, /* destination of expanded string */ + int maxlength) /* maximum chars in destination */ +{ + UCS c; /* current char to translate */ + + /* scan through the string */ + while ((c = *srcstr++) != 0) { + if (c == '\n') { /* its an EOL */ + *deststr++ = '<'; + *deststr++ = 'N'; + *deststr++ = 'L'; + *deststr++ = '>'; + maxlength -= 4; + } else if (c < 0x20 || c == 0x7f) { /* control character */ + *deststr++ = '^'; + *deststr++ = c ^ 0x40; + maxlength -= 2; + } else if (c == '%') { + *deststr++ = '%'; + *deststr++ = '%'; + maxlength -= 2; + } else { /* any other character */ + *deststr++ = c; + maxlength--; + } + + /* check for maxlength */ + if (maxlength < 4) { + *deststr++ = '$'; + *deststr = '\0'; + return(FALSE); + } + } + + *deststr = '\0'; + return(TRUE); +} + + +/* + * chword() - change the given word, wp, pointed to by the curwp->w_dot + * pointers to the word in cb + */ +void +chword(UCS *wb, UCS *cb) +{ + ldelete((long) ucs4_strlen(wb), NULL); /* not saved in kill buffer */ + while(*cb != '\0') + linsert(1, *cb++); + + curwp->w_flag |= WFEDIT; +} diff --git a/pico/utf8stub.c b/pico/utf8stub.c new file mode 100644 index 00000000..d8738c9a --- /dev/null +++ b/pico/utf8stub.c @@ -0,0 +1,86 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: utf8stub.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 +#include "utf8stub.h" + + +/* + * Stub functions to fill in functions used in utf8 support routines + */ + + +void * +fs_get (size_t size) +{ + void *block = malloc (size ? size : (size_t) 1); + if (!block) fatal ("Out of memory"); + return (block); +} + + +void +fs_resize(void **block, size_t size) +{ + if (!(*block = realloc (*block,size ? size : (size_t) 1))) + fatal ("Can't resize memory"); +} + +void +fs_give (void **block) +{ + free (*block); + *block = NULL; +} + +void +fatal(char *s) +{ + ; +} + + +int +compare_ulong(unsigned long l1, unsigned long l2) +{ + if (l1 < l2) return -1; + if (l1 > l2) return 1; + return 0; +} + + +int +compare_cstring(unsigned char *s1, unsigned char *s2) +{ + int i; + if (!s1) return s2 ? -1 : 0; /* empty string cases */ + else if (!s2) return 1; + for (; *s1 && *s2; s1++,s2++) + if ((i = (compare_ulong (islower (*s1) ? toupper (*s1) : *s1, + islower (*s2) ? toupper (*s2) : *s2))) != 0) + return i; /* found a difference */ + if (*s1) return 1; /* first string is longer */ + return *s2 ? -1 : 0; /* second string longer : strings identical */ +} + +int +panicking(void) +{ + return(0); +} diff --git a/pico/utf8stub.h b/pico/utf8stub.h new file mode 100644 index 00000000..666716ab --- /dev/null +++ b/pico/utf8stub.h @@ -0,0 +1,29 @@ +/* + * $Id: utf8stub.h 764 2007-10-23 23:44:49Z 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 UTF8STUB_H +#define UTF8STUB_H + +void *fs_get(size_t size); +void fs_resize(void **block,size_t size); +void fs_give(void **block); +void fatal(char *s); +int compare_ulong(unsigned long l1, unsigned long l2); +int compare_cstring(unsigned char *s1, unsigned char *s2); +int panicking(void); + +#endif /* UTF8STUB_H */ diff --git a/pico/window.c b/pico/window.c new file mode 100644 index 00000000..7187e16f --- /dev/null +++ b/pico/window.c @@ -0,0 +1,47 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: window.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 + * + * ======================================================================== + */ + +/* + * Window management. Some of the functions are internal, and some are + * attached to keys that the user actually types. + */ + +#include "headers.h" + + +/* + * Refresh the screen. With no argument, it just does the refresh. With an + * argument it recenters "." in the current window. Bound to "C-L". + */ +int +pico_refresh(int f, int n) +{ + /* + * since pine mode isn't using the traditional mode line, sgarbf isn't + * enough. + */ + if(Pmaster && curwp) + curwp->w_flag |= WFMODE; + + if (f == FALSE) + sgarbf = TRUE; + else if(curwp){ + curwp->w_force = 0; /* Center dot. */ + curwp->w_flag |= WFFORCE; + } + + return (TRUE); +} diff --git a/pico/word.c b/pico/word.c new file mode 100644 index 00000000..145fc1d1 --- /dev/null +++ b/pico/word.c @@ -0,0 +1,1179 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: word.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 + * + * ======================================================================== + */ + +/* + * Program: Word at a time routines + * + * The routines in this file implement commands that work word at a time. + * There are all sorts of word mode commands. If I do any sentence and/or + * paragraph mode commands, they are likely to be put in this file. + */ + +#include "headers.h" + + +int fpnewline(UCS *quote); +int fillregion(UCS *qstr, REGION *addedregion); +int setquotelevelinregion(int quotelevel, REGION *addedregion); +int is_user_separator(UCS c); + + +/* Word wrap on n-spaces. Back-over whatever precedes the point on the current + * line and stop on the first word-break or the beginning of the line. If we + * reach the beginning of the line, jump back to the end of the word and start + * a new line. Otherwise, break the line at the word-break, eat it, and jump + * back to the end of the word. + * Returns TRUE on success, FALSE on errors. + */ +int +wrapword(void) +{ + register int cnt; /* size of word wrapped to next line */ + register int bp; /* index to wrap on */ + register int first = -1; + int wid, ww; + + if(curwp->w_doto <= 0) /* no line to wrap? */ + return(FALSE); + + wid = 0; + for(bp = cnt = 0; cnt < llength(curwp->w_dotp) && !bp; cnt++){ + if(ucs4_isspace(lgetc(curwp->w_dotp, cnt).c)){ + first = 0; + if(lgetc(curwp->w_dotp, cnt).c == TAB){ + ++wid; + while(wid & 0x07) + ++wid; + } + else + ++wid; + } + else{ + ww = wcellwidth((UCS) lgetc(curwp->w_dotp, cnt).c); + wid += (ww >= 0 ? ww : 1); + if(!first) + first = cnt; + } + + if(first > 0 && wid > fillcol) + bp = first; + } + + if(!bp) + return(FALSE); + + /* bp now points to the first character of the next line */ + cnt = curwp->w_doto - bp; + curwp->w_doto = bp; + + if(!lnewline()) /* break the line */ + return(FALSE); + + /* + * if there's a line below, it doesn't start with whitespace + * and there's room for this line... + */ + if(!(curbp->b_flag & BFWRAPOPEN) + && lforw(curwp->w_dotp) != curbp->b_linep + && llength(lforw(curwp->w_dotp)) + && !ucs4_isspace(lgetc(lforw(curwp->w_dotp), 0).c) + && (llength(curwp->w_dotp) + llength(lforw(curwp->w_dotp)) < fillcol)){ + gotoeol(0, 1); /* then pull text up from below */ + if(lgetc(curwp->w_dotp, curwp->w_doto - 1).c != ' ') + linsert(1, ' '); + + forwdel(0, 1); + gotobol(0, 1); + } + + curbp->b_flag &= ~BFWRAPOPEN; /* don't open new line next wrap */ + /* restore dot (account for NL) */ + if(cnt && !forwchar(0, cnt < 0 ? cnt-1 : cnt)) + return(FALSE); + + return(TRUE); +} + + +/* + * Move the cursor backward by "n" words. All of the details of motion are + * performed by the "backchar" and "forwchar" routines. Error if you try to + * move beyond the buffers. + */ +int +backword(int f, int n) +{ + if (n < 0) + return (forwword(f, -n)); + if (backchar_no_header_editor(FALSE, 1) == FALSE) + return (FALSE); + while (n--) { + while (inword() == FALSE) { + if (backchar_no_header_editor(FALSE, 1) == FALSE) + return (FALSE); + } + while (inword() != FALSE) { + if (backchar_no_header_editor(FALSE, 1) == FALSE) + return (FALSE); + } + } + return (forwchar(FALSE, 1)); +} + +/* + * Move the cursor forward by the specified number of words. All of the motion + * is done by "forwchar". Error if you try and move beyond the buffer's end. + */ +int +forwword(int f, int n) +{ + if (n < 0) + return (backword(f, -n)); + while (n--) { +#if NFWORD + while (inword() != FALSE) { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } +#endif + while (inword() == FALSE) { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } +#if NFWORD == 0 + while (inword() != FALSE) { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } +#endif + } + return(TRUE); +} + +int +ucs4_isalnum(UCS c) +{ + return((c && c <= 0x7f && isalnum((unsigned char) c)) + || (c >= 0xA0 && !SPECIAL_SPACE(c))); +} + +int +ucs4_isalpha(UCS c) +{ + return((c && c <= 0x7f && isalpha((unsigned char) c)) + || (c >= 0xA0 && !SPECIAL_SPACE(c))); +} + +int +ucs4_isspace(UCS c) +{ + return((c < 0xff && isspace((unsigned char) c)) || SPECIAL_SPACE(c)); +} + +int +ucs4_ispunct(UCS c) +{ + return !ucs4_isalnum(c) && !ucs4_isspace(c); +} + +#ifdef MAYBELATER +/* + * Move the cursor forward by the specified number of words. As you move, + * convert any characters to upper case. Error if you try and move beyond the + * end of the buffer. Bound to "M-U". + */ +int +upperword(int f, int n) +{ + register int c; + CELL ac; + + ac.a = 0; + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + if (n < 0) + return (FALSE); + while (n--) { + while (inword() == FALSE) { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + while (inword() != FALSE) { + c = lgetc(curwp->w_dotp, curwp->w_doto).c; + if (c>='a' && c<='z') { + ac.c = (c -= 'a'-'A'); + lputc(curwp->w_dotp, curwp->w_doto, ac); + lchange(WFHARD); + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + } + return (TRUE); +} + +/* + * Move the cursor forward by the specified number of words. As you move + * convert characters to lower case. Error if you try and move over the end of + * the buffer. Bound to "M-L". + */ +int +lowerword(int f, int n) +{ + register int c; + CELL ac; + + ac.a = 0; + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + if (n < 0) + return (FALSE); + while (n--) { + while (inword() == FALSE) { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + while (inword() != FALSE) { + c = lgetc(curwp->w_dotp, curwp->w_doto).c; + if (c>='A' && c<='Z') { + ac.c (c += 'a'-'A'); + lputc(curwp->w_dotp, curwp->w_doto, ac); + lchange(WFHARD); + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + } + return (TRUE); +} + +/* + * Move the cursor forward by the specified number of words. As you move + * convert the first character of the word to upper case, and subsequent + * characters to lower case. Error if you try and move past the end of the + * buffer. Bound to "M-C". + */ +int +capword(int f, int n) +{ + register int c; + CELL ac; + + ac.a = 0; + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + if (n < 0) + return (FALSE); + while (n--) { + while (inword() == FALSE) { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + if (inword() != FALSE) { + c = lgetc(curwp->w_dotp, curwp->w_doto).c; + if (c>='a' && c<='z') { + ac.c = (c -= 'a'-'A'); + lputc(curwp->w_dotp, curwp->w_doto, ac); + lchange(WFHARD); + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + while (inword() != FALSE) { + c = lgetc(curwp->w_dotp, curwp->w_doto).c; + if (c>='A' && c<='Z') { + ac.c = (c += 'a'-'A'); + lputc(curwp->w_dotp, curwp->w_doto, ac); + lchange(WFHARD); + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + } + } + return (TRUE); +} + +/* + * Kill forward by "n" words. Remember the location of dot. Move forward by + * the right number of words. Put dot back where it was and issue the kill + * command for the right number of characters. Bound to "M-D". + */ +int +delfword(int f, int n) +{ + register long size; + register LINE *dotp; + register int doto; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + if (n < 0) + return (FALSE); + dotp = curwp->w_dotp; + doto = curwp->w_doto; + size = 0L; + while (n--) { +#if NFWORD + while (inword() != FALSE) { + if (forwchar(FALSE,1) == FALSE) + return(FALSE); + ++size; + } +#endif + while (inword() == FALSE) { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + ++size; + } +#if NFWORD == 0 + while (inword() != FALSE) { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + ++size; + } +#endif + } + curwp->w_dotp = dotp; + curwp->w_doto = doto; + return (ldelete(size, kinsert)); +} + +/* + * Kill backwards by "n" words. Move backwards by the desired number of words, + * counting the characters. When dot is finally moved to its resting place, + * fire off the kill command. Bound to "M-Rubout" and to "M-Backspace". + */ +int +delbword(int f, int n) +{ + register long size; + + if (curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + if (n < 0) + return (FALSE); + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + size = 0L; + while (n--) { + while (inword() == FALSE) { + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + ++size; + } + while (inword() != FALSE) { + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + ++size; + } + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + return (ldelete(size, kinsert)); +} +#endif /* MAYBELATER */ + +/* + * Return TRUE if the character at dot is a character that is considered to be + * part of a word. + */ +int +inword(void) +{ + if(curwp->w_doto < llength(curwp->w_dotp)) + { + if(ucs4_isalnum(lgetc(curwp->w_dotp, curwp->w_doto).c)) + { + return(TRUE); + } + else if(ucs4_ispunct(lgetc(curwp->w_dotp, curwp->w_doto).c) + && !is_user_separator(lgetc(curwp->w_dotp, curwp->w_doto).c)) + { + if((curwp->w_doto > 0) && + ucs4_isalnum(lgetc(curwp->w_dotp, curwp->w_doto - 1).c) && + (curwp->w_doto + 1 < llength(curwp->w_dotp)) && + ucs4_isalnum(lgetc(curwp->w_dotp, curwp->w_doto + 1).c)) + { + return(TRUE); + } + } + } + + return(FALSE); +} + + +int +is_user_separator(UCS c) +{ + UCS *u; + + if(glo_wordseps) + for(u = glo_wordseps; *u; u++) + if(*u == c) + return 1; + + return 0; +} + + +/* + * Return number of quotes if whatever starts the line matches the quote string + */ +int +quote_match(UCS *q, LINE *l, UCS *buf, size_t buflen) +{ + register int i, n, j, qb; + + *buf = '\0'; + if(*q == '\0') + return(1); + + qb = (ucs4_strlen(q) > 1 && q[ucs4_strlen(q)-1] == ' ') ? 1 : 0; + for(n = 0, j = 0; ;){ + for(i = 0; j <= llength(l) && qb ? q[i+1] : q[i]; i++, j++) + if(q[i] != lgetc(l, j).c) + return(n); + + n++; + if((!qb && q[i] == '\0') || (qb && q[i+1] == '\0')){ + if(ucs4_strlen(buf) + ucs4_strlen(q) + 1 < buflen){ + ucs4_strncat(buf, q, buflen-ucs4_strlen(q)-1); + buf[buflen-1] = '\0'; + if(qb && (j > llength(l) || lgetc(l, j).c != ' ')) + buf[ucs4_strlen(buf)-1] = '\0'; + } + } + if(j > llength(l)) + return(n); + else if(qb && lgetc(l, j).c == ' ') + j++; + } + return(n); /* never reached */ +} + + +/* Justify the entire buffer instead of just a paragraph */ +int +fillbuf(int f, int n) +{ + LINE *eobline; + REGION region; + + if(curbp->b_mode&MDVIEW){ /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + } + else if (fillcol == 0) { /* no fill column set */ + mlwrite_utf8("No fill column set", NULL); + return(FALSE); + } + + if((lastflag & CFFILL) && (lastflag & CFFLBF)){ + /* no use doing a full justify twice */ + thisflag |= (CFFLBF | CFFILL); + return(TRUE); + } + + /* record the pointer of the last line */ + if(gotoeob(FALSE, 1) == FALSE) + return(FALSE); + + eobline = curwp->w_dotp; /* last line of buffer */ + if(!llength(eobline)) + eobline = lback(eobline); + + /* and back to the beginning of the buffer */ + gotobob(FALSE, 1); + + thisflag |= CFFLBF; /* CFFILL also gets set in fillpara */ + + if(!Pmaster) + sgarbk = TRUE; + + curwp->w_flag |= WFMODE; + + /* + * clear the kill buffer, that's where we'll store undo + * information, we can't do the fill buffer because + * fillpara relies on its contents + */ + kdelete(); + curwp->w_doto = 0; + getregion(®ion, eobline, llength(eobline)); + + /* Put full message in the kill buffer for undo */ + if(!ldelete(region.r_size, kinsert)) + return(FALSE); + + /* before yank'ing, clear lastflag so we don't just unjustify */ + lastflag &= ~(CFFLBF | CFFILL); + + /* Now in kill buffer, bring back text to use in fillpara */ + yank(FALSE, 1); + + gotobob(FALSE, 1); + + /* call fillpara until we're at the end of the buffer */ + while(curwp->w_dotp != curbp->b_linep) + if(!(fillpara(FALSE, 1))) + return(FALSE); + + return(TRUE); +} + + +/* + * Fill the current paragraph according to the current fill column + */ +int +fillpara(int f, int n) +{ + UCS *qstr, qstr2[NSTRING], c; + int quotelevel = -1; + REGION addedregion; + char action = 'P'; + + if(curbp->b_mode&MDVIEW){ /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + } + else if (fillcol == 0) { /* no fill column set */ + mlwrite_utf8("No fill column set", NULL); + return(FALSE); + } + else if(curwp->w_dotp == curbp->b_linep && !curwp->w_markp) /* don't wrap! */ + return(FALSE); + + /* + * If there is already a region set, then we may use it + * instead of the current paragraph. + */ + + if(curwp->w_markp){ + int k, rv; + KEYMENU menu_justify[12]; + char prompt[100]; + + for(k = 0; k < 12; k++){ + menu_justify[k].name = NULL; + KS_OSDATASET(&menu_justify[k], KS_NONE); + } + + menu_justify[1].name = "R"; + menu_justify[1].label = "[" N_("Region") "]"; + menu_justify[6].name = "^C"; + menu_justify[6].label = N_("Cancel"); + menu_justify[7].name = "P"; + menu_justify[7].label = N_("Paragraph"); + menu_justify[2].name = "Q"; + menu_justify[2].label = N_("Quotelevel"); + + wkeyhelp(menu_justify); /* paint menu */ + sgarbk = TRUE; + if(Pmaster && curwp) + curwp->w_flag |= WFMODE; + + strncpy(prompt, "justify Region, Paragraph; or fix Quotelevel ? ", sizeof(prompt)); + prompt[sizeof(prompt)-1] = '\0'; + mlwrite_utf8(prompt, NULL); + (*term.t_rev)(1); + rv = -1; + while(1){ + switch(c = GetKey()){ + + case (CTRL|'C') : /* Bail out! */ + case F2 : + pputs_utf8(_("ABORT"), 1); + rv = ABORT; + emlwrite("", NULL); + break; + + case (CTRL|'M') : /* default */ + case 'r' : + case 'R' : + case F3 : + pputs_utf8(_("Region"), 1); + rv = 'R'; + break; + + case 'p' : + case 'P' : + case F7 : + pputs_utf8(_("Paragraph"), 1); + rv = 'P'; + break; + + case 'q' : + case 'Q' : + case F8 : + case '0' : case '1' : case '2' : case '3' : case '4' : + case '5' : case '6' : case '7' : case '8' : case '9' : + pputs_utf8(_("Quotelevel"), 1); + while(rv == -1){ + switch(c){ + case 'q' : + case 'Q' : + case F8 : + {char num[20]; + + num[0] = '\0'; + switch(mlreplyd_utf8("Quote Level ? ", num, sizeof(num), QNORML, NULL)){ + case TRUE: + if(isdigit(num[0])){ + quotelevel = atoi(num); + if(quotelevel < 0){ + emlwrite("Quote Level cannot be negative", NULL); + sleep(3); + } + else if(quotelevel > 20){ + emlwrite("Quote Level should be less than 20", NULL); + rv = ABORT; + } + else{ + rv = 'Q'; + } + } + else if(num[0]){ + emlwrite("Quote Level should be a number", NULL); + sleep(3); + } + + break; + + case HELPCH: + emlwrite("Enter the number of quotes you want before the text", NULL); + sleep(3); + break; + + default: + emlwrite("Quote Level is a number", NULL); + rv = ABORT; + break; + } + } + + break; + + case '0' : case '1' : case '2' : case '3' : case '4' : + case '5' : case '6' : case '7' : case '8' : case '9' : + rv = 'Q'; + quotelevel = (int) (c - '0'); + break; + } + } + + break; + + case (CTRL|'G') : + if(term.t_mrow == 0 && km_popped == 0){ + movecursor(term.t_nrow-2, 0); + peeol(); + term.t_mrow = 2; + (*term.t_rev)(0); + wkeyhelp(menu_justify); + mlwrite_utf8(prompt, NULL); + (*term.t_rev)(1); + sgarbk = TRUE; /* mark menu dirty */ + km_popped++; + break; + } + /* else fall through */ + + default: + (*term.t_beep)(); + + case NODATA : + break; + } + + (*term.t_flush)(); + if(rv != -1){ + (*term.t_rev)(0); + if(km_popped){ + term.t_mrow = 0; + movecursor(term.t_nrow, 0); + peeol(); + sgarbf = 1; + km_popped = 0; + } + + action = rv; + break; + } + } + + if(action != ABORT) + emlwrite("", NULL); + } + + if(action == 'R' && curwp->w_markp){ + /* let yank() know that it may be restoring a paragraph */ + thisflag |= CFFILL; + + if(!Pmaster) + sgarbk = TRUE; + + curwp->w_flag |= WFMODE; + + swap_mark_and_dot_if_mark_comes_first(); + + /* determine if we're justifying quoted text or not */ + qstr = (glo_quote_str + && quote_match(glo_quote_str, + curwp->w_doto > 0 ? curwp->w_dotp->l_fp : curwp->w_dotp, + qstr2, NSTRING) + && *qstr2) ? qstr2 : NULL; + + + /* + * Fillregion moves dot to the end of the filled region. + */ + if(!fillregion(qstr, &addedregion)) + return(FALSE); + + set_last_region_added(&addedregion); + } + else if(action == 'P'){ + + /* + * Justfiy the current paragraph. + */ + + if(curwp->w_markp) /* clear mark if already set */ + setmark(0,0); + + if(gotoeop(FALSE, 1) == FALSE) + return(FALSE); + + /* determine if we're justifying quoted text or not */ + qstr = (glo_quote_str + && quote_match(glo_quote_str, + curwp->w_dotp, qstr2, NSTRING) + && *qstr2) ? qstr2 : NULL; + + setmark(0,0); /* mark last line of para */ + + /* jump back to the beginning of the paragraph */ + gotobop(FALSE, 1); + + /* let yank() know that it may be restoring a paragraph */ + thisflag |= (CFFILL | CFFLPA); + + if(!Pmaster) + sgarbk = TRUE; + + curwp->w_flag |= WFMODE; + + curwp->w_doto = 0; /* start region at beginning of line */ + + /* + * Fillregion moves dot to the end of the filled region. + */ + if(!fillregion(qstr, &addedregion)) + return(FALSE); + + set_last_region_added(&addedregion); + + /* Leave cursor on first char of first line after justified region */ + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_doto = 0; + + if(curwp->w_markp) + setmark(0,0); /* clear mark */ + } + else if(action == 'Q'){ + /* let yank() know that it may be restoring a paragraph */ + thisflag |= CFFILL; + + if(!Pmaster) + sgarbk = TRUE; + + curwp->w_flag |= WFHARD; + + swap_mark_and_dot_if_mark_comes_first(); + + if(!setquotelevelinregion(quotelevel, &addedregion)) + return(FALSE); + + set_last_region_added(&addedregion); + } + else{ + /* abort */ + } + + return(TRUE); +} + + +/* + * The region we're filling is the region from dot to mark. + * We cut out that region and then put it back in filled. + * The cut out part is saved in the ldelete call and the + * reinstalled region is noted in addedregion, so that yank() + * can delete it and restore the saved part. + */ +int +fillregion(UCS *qstr, REGION *addedregion) +{ + long c, sz, last_char = 0; + int i, j, qlen, same_word, + spaces, word_len, word_ind, line_len, ww; + int starts_midline = 0; + int ends_midline = 0; + int offset_into_start; + LINE *line_before_start, *lp; + UCS line_last, word[NSTRING]; + REGION region; + + /* if region starts midline insert a newline */ + if(curwp->w_doto > 0 && curwp->w_doto < llength(curwp->w_dotp)) + starts_midline++; + + /* if region ends midline insert a newline at end */ + if(curwp->w_marko > 0 && curwp->w_marko < llength(curwp->w_markp)) + ends_midline++; + + /* cut the paragraph into our fill buffer */ + fdelete(); + if(!getregion(®ion, curwp->w_markp, curwp->w_marko)) + return(FALSE); + + if(!ldelete(region.r_size, finsert)) + return(FALSE); + + line_before_start = lback(curwp->w_dotp); + offset_into_start = curwp->w_doto; + + if(starts_midline) + lnewline(); + + /* Now insert it back wrapped */ + spaces = word_len = word_ind = line_len = same_word = 0; + qlen = qstr ? ucs4_strlen(qstr) : 0; + + /* Beginning with leading quoting... */ + if(qstr){ + i = 0; + while(qstr[i]){ + ww = wcellwidth(qstr[i]); + line_len += (ww >= 0 ? ww : 1); + linsert(1, qstr[i++]); + } + + line_last = ' '; /* no word-flush space! */ + } + + /* remove first leading quotes if any */ + if(starts_midline) + i = 0; + else + for(i = qlen; (c = fremove(i)) == ' ' || c == TAB; i++){ + linsert(1, line_last = (UCS) c); + line_len += ((c == TAB) ? (~line_len & 0x07) + 1 : 1); + } + + /* then digest the rest... */ + while((c = fremove(i++)) >= 0){ + last_char = c; + switch(c){ + case '\n' : + /* skip next quote string */ + j = 0; + while(j < qlen && ((c = fremove(i+j)) == qstr[j] || c == ' ')) + j++; + + i += j; + + + if(!spaces) + spaces++; + break; + + case TAB : + case ' ' : + spaces++; + break; + + default : + if(spaces){ /* flush word? */ + if((line_len - qlen > 0) + && line_len + word_len + 1 > fillcol + && ((ucs4_isspace(line_last)) + || (linsert(1, ' '))) + && same_word == 0 + && (line_len = fpnewline(qstr))) + line_last = ' '; /* no word-flush space! */ + + if(word_len){ /* word to write? */ + if(line_len && !ucs4_isspace(line_last)){ + linsert(1, ' '); /* need padding? */ + line_len++; + } + + line_len += word_len; + for(j = 0; j < word_ind; j++) + linsert(1, line_last = word[j]); + + if(spaces > 1 && strchr(".?!:;\")", line_last)){ + linsert(2, line_last = ' '); + line_len += 2; + } + + same_word = word_len = word_ind = 0; + } + + spaces = 0; + } + + if(word_ind + 1 >= NSTRING){ + /* Magic! Fake that we output a wrapped word */ + if((line_len - qlen > 0) && same_word == 0){ + if(!ucs4_isspace(line_last)) + linsert(1, ' '); + line_len = fpnewline(qstr); + } + same_word = 1; + line_len += word_len; + for(j = 0; j < word_ind; j++) + linsert(1, word[j]); + + word_len = word_ind = 0; + line_last = ' '; + } + + word[word_ind++] = (UCS) c; + ww = wcellwidth((UCS) c); + word_len += (ww >= 0 ? ww : 1); + + break; + } + } + + if(word_len){ + if((line_len - qlen > 0) && (line_len + word_len + 1 > fillcol) && same_word == 0){ + if(!ucs4_isspace(line_last)) + linsert(1, ' '); + (void) fpnewline(qstr); + } + else if(line_len && !ucs4_isspace(line_last)) + linsert(1, ' '); + + for(j = 0; j < word_ind; j++) + linsert(1, word[j]); + } + + if(last_char == '\n') + lnewline(); + + if(ends_midline) + (void) fpnewline(qstr); + + /* + * Calculate the size of the region that was added. + */ + swapmark(0,1); /* mark current location after adds */ + addedregion->r_linep = lforw(line_before_start); + addedregion->r_offset = offset_into_start; + lp = addedregion->r_linep; + sz = llength(lp) - addedregion->r_offset; + if(lforw(lp) != curwp->w_markp->l_fp){ + lp = lforw(lp); + while(lp != curwp->w_markp->l_fp){ + sz += llength(lp) + 1; + lp = lforw(lp); + } + } + + sz -= llength(curwp->w_markp) - curwp->w_marko; + addedregion->r_size = sz; + + swapmark(0,1); + + if(ends_midline){ + /* + * We want to back up to the end of the original + * region instead of being here after the added newline. + */ + curwp->w_doto = 0; + backchar(0, 1); + unmarkbuffer(); + markregion(1); + } + + return(TRUE); +} + + +/* + * fpnewline - output a fill paragraph newline mindful of quote string + */ +int +fpnewline(UCS *quote) +{ + int len; + + lnewline(); + for(len = 0; quote && *quote; quote++){ + int ww; + + ww = wcellwidth(*quote); + len += (ww >= 0 ? ww : 1); + linsert(1, *quote); + } + + return(len); +} + + +int +setquotelevelinregion(int quotelevel, REGION *addedregion) +{ + int i, standards_based = 0; + int quote_chars = 0, backuptoprevline = 0; + int starts_midline = 0, ends_midline = 0, offset_into_start; + long c, sz; + UCS qstr_def1[] = { '>', ' ', 0}, qstr_def2[] = { '>', 0}; + LINE *lp, *line_before_start; + REGION region; + + if(curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if(!glo_quote_str + || !ucs4_strcmp(glo_quote_str, qstr_def1) + || !ucs4_strcmp(glo_quote_str, qstr_def2)) + standards_based++; + + if(!standards_based){ + emlwrite("Quote level setting only works with standard \"> \" quotes", NULL); + return(FALSE); + } + + /* if region starts midline insert a newline */ + if(curwp->w_doto > 0 && curwp->w_doto < llength(curwp->w_dotp)) + starts_midline++; + + /* if region ends midline insert a newline at end */ + if(curwp->w_marko > 0 && curwp->w_marko < llength(curwp->w_markp)){ + ends_midline++; + backuptoprevline++; + /* count quote chars for re-insertion */ + for(i = 0; i < llength(curwp->w_markp); ++i) + if(lgetc(curwp->w_markp, i).c != '>') + break; + + quote_chars = i; + } + else if(curwp->w_marko == 0) + backuptoprevline++; + + /* find the size of the region */ + getregion(®ion, curwp->w_markp, curwp->w_marko); + + /* cut the paragraph into our fill buffer */ + fdelete(); + if(!ldelete(region.r_size, finsert)) + return(FALSE); + + line_before_start = lback(curwp->w_dotp); + offset_into_start = curwp->w_doto; + + /* if region starts midline add a newline */ + if(starts_midline) + lnewline(); + + i = 0; + while(fremove(i) >= 0){ + + /* remove all quote strs from current line */ + if(standards_based){ + while((c = fremove(i)) == '>') + i++; + + if(c == ' ') + i++; + } + else{ + } + + /* insert quotelevel quote strs */ + if(standards_based){ + linsert(quotelevel, '>'); + if(quotelevel > 0) + linsert(1, ' '); + } + else{ + } + + /* put back the actual line */ + while((c = fremove(i++)) >= 0 && c != '\n') + linsert(1, (UCS) c); + + if(c == '\n') + lnewline(); + } + + /* if region ends midline add a newline */ + if(ends_midline){ + lnewline(); + if(quote_chars){ + linsert(quote_chars, '>'); + if(curwp->w_doto < llength(curwp->w_dotp) + && lgetc(curwp->w_dotp, curwp->w_doto).c != ' ') + linsert(1, ' '); + } + } + + /* + * Calculate the size of the region that was added. + */ + swapmark(0,1); /* mark current location after adds */ + addedregion->r_linep = lforw(line_before_start); + addedregion->r_offset = offset_into_start; + lp = addedregion->r_linep; + sz = llength(lp) - addedregion->r_offset; + if(lforw(lp) != curwp->w_markp->l_fp){ + lp = lforw(lp); + while(lp != curwp->w_markp->l_fp){ + sz += llength(lp) + 1; + lp = lforw(lp); + } + } + + sz -= llength(curwp->w_markp) - curwp->w_marko; + addedregion->r_size = sz; + + swapmark(0,1); + + /* + * This puts us at the end of the quoted region instead + * of on the following line. This makes it convenient + * for the user to follow a quotelevel adjustment with + * a Justify if desired. + */ + if(backuptoprevline){ + curwp->w_doto = 0; + backchar(0, 1); + } + + if(ends_midline){ /* doesn't need fixing otherwise */ + unmarkbuffer(); + markregion(1); + } + + return (TRUE); +} -- cgit v1.2.3-70-g09d2