summaryrefslogtreecommitdiffstats
path: root/curses
diff options
context:
space:
mode:
Diffstat (limited to 'curses')
-rw-r--r--curses/.gitignore1
-rw-r--r--curses/Makefile.am35
-rw-r--r--curses/configure.in103
-rw-r--r--curses/curses.C524
-rw-r--r--curses/cursesbutton.C136
-rw-r--r--curses/cursesbutton.H103
-rw-r--r--curses/curseschoicebutton.C38
-rw-r--r--curses/curseschoicebutton.H81
-rw-r--r--curses/cursescontainer.C178
-rw-r--r--curses/cursescontainer.H76
-rw-r--r--curses/cursesdialog.C233
-rw-r--r--curses/cursesdialog.H74
-rw-r--r--curses/cursesfield.C689
-rw-r--r--curses/cursesfield.H160
-rw-r--r--curses/cursesfilereq.C808
-rw-r--r--curses/cursesfilereq.H175
-rw-r--r--curses/cursesflowedline.H32
-rw-r--r--curses/curseskeyhandler.C75
-rw-r--r--curses/curseskeyhandler.H79
-rw-r--r--curses/curseslabel.C96
-rw-r--r--curses/curseslabel.H47
-rw-r--r--curses/cursesmainscreen.C76
-rw-r--r--curses/cursesmainscreen.H66
-rw-r--r--curses/cursesmoronize.C97
-rw-r--r--curses/cursesmoronize.H52
-rw-r--r--curses/cursesmultilinelabel.C126
-rw-r--r--curses/cursesmultilinelabel.H54
-rw-r--r--curses/cursesobject.C40
-rw-r--r--curses/cursesobject.H102
-rw-r--r--curses/cursesscreen.C885
-rw-r--r--curses/cursesscreen.H94
-rw-r--r--curses/cursesstatusbar.C781
-rw-r--r--curses/cursesstatusbar.H185
-rw-r--r--curses/cursestitlebar.C110
-rw-r--r--curses/cursestitlebar.H40
-rw-r--r--curses/cursesvscroll.C148
-rw-r--r--curses/cursesvscroll.H71
-rw-r--r--curses/mycurses.H439
-rw-r--r--curses/timer.C172
-rw-r--r--curses/timer.H124
-rw-r--r--curses/widechar.C498
-rw-r--r--curses/widechar.H678
42 files changed, 8581 insertions, 0 deletions
diff --git a/curses/.gitignore b/curses/.gitignore
new file mode 100644
index 0000000..eec2732
--- /dev/null
+++ b/curses/.gitignore
@@ -0,0 +1 @@
+/curses_config.h.in
diff --git a/curses/Makefile.am b/curses/Makefile.am
new file mode 100644
index 0000000..4206bd4
--- /dev/null
+++ b/curses/Makefile.am
@@ -0,0 +1,35 @@
+#
+# Copyright 2002, Double Precision Inc.
+#
+# See COPYING for distribution information.
+#
+
+noinst_LIBRARIES=libcurses.a
+
+libcurses_a_SOURCES=mycurses.H curses.C \
+ cursesbutton.H cursesbutton.C \
+ curseschoicebutton.H curseschoicebutton.C \
+ cursescontainer.H cursescontainer.C \
+ cursesdialog.H cursesdialog.C \
+ cursesfield.H cursesfield.C \
+ cursesfilereq.H cursesfilereq.C \
+ cursesflowedline.H \
+ curseskeyhandler.H curseskeyhandler.C \
+ curseslabel.H curseslabel.C \
+ cursesmainscreen.H cursesmainscreen.C \
+ cursesmultilinelabel.H cursesmultilinelabel.C \
+ cursesmoronize.H cursesmoronize.C \
+ cursesobject.H cursesobject.C \
+ cursesscreen.H cursesscreen.C \
+ cursesstatusbar.H cursesstatusbar.C \
+ cursestitlebar.H cursestitlebar.C \
+ cursesvscroll.H cursesvscroll.C \
+ timer.C timer.H \
+ widechar.C widechar.H
+
+noinst_DATA=curseslib
+
+DISTCLEANFILES=curseslib
+
+curseslib: config.status
+ echo @CURSESLIB@ >curseslib
diff --git a/curses/configure.in b/curses/configure.in
new file mode 100644
index 0000000..57a576d
--- /dev/null
+++ b/curses/configure.in
@@ -0,0 +1,103 @@
+dnl Process this file with autoconf to produce a configure script.
+
+#
+# Copyright 2002-2004, Double Precision Inc.
+#
+# See COPYING for distribution information.
+#
+AC_PREREQ(2.59)
+AC_INIT(curses, 0.11, courier-cone@lists.sourceforge.net)
+AC_CONFIG_SRCDIR(mycurses.H)
+AC_CONFIG_AUX_DIR(../..)
+AM_CONFIG_HEADER(curses_config.h)
+AM_INIT_AUTOMAKE
+>confdefs.h
+
+dnl Checks for programs.
+AC_USE_SYSTEM_EXTENSIONS
+AC_PROG_CXX
+AC_PROG_AWK
+AC_PROG_CC
+AC_PROG_RANLIB
+AC_PROG_INSTALL
+AC_PROG_LN_S
+
+dnl Checks for libraries.
+
+CURSESLIBRARY=""
+
+for f in ncursesw cursesw ncurses curses
+do
+ AC_CHECK_LIB($f, wgetch,
+ [if test "$CURSESLIBRARY" = ""
+ then
+ CURSESLIBRARY="$f"
+ fi
+ ])
+done
+
+if test "$CURSESLIBRARY" = ""
+then
+ AC_MSG_ERROR([curses library not found.])
+fi
+
+CURSESLIB="-l$CURSESLIBRARY"
+AC_SUBST(CURSESLIB)
+
+dnl Checks for header files.
+AC_CHECK_HEADERS(sys/time.h unistd.h sys/wait.h glob.h)
+AC_CHECK_HEADERS(ncursesw/curses.h)
+
+AC_HEADER_TIME
+AC_HEADER_DIRENT
+AC_HEADER_SYS_WAIT
+
+save_LIBS="$LIBS"
+LIBS="$CURSESLIB $LIBS"
+
+AC_MSG_CHECKING(for use_default_colors())
+
+AC_TRY_LINK([
+#include <curses.h>
+],[
+ use_default_colors();
+], [AC_MSG_RESULT(yes)
+
+AC_DEFINE_UNQUOTED(HAS_USE_DEFAULT_COLORS,1,[Whether we have use_default_colors()])
+], AC_MSG_RESULT(no))
+
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
+#if HAVE_NCURSESW_CURSES_H
+#include <ncursesw/curses.h>
+#else
+#include <curses.h>
+#endif
+
+#ifdef KEY_F
+#if KEY_F(0)+63 == KEY_F(63)
+#define SANEFKEY
+#endif
+#endif
+
+#ifndef SANEFKEY
+#error Not a sane FKEY
+#endif
+]])],
+AC_DEFINE_UNQUOTED(HAS_FUNCTIONKEYS,1,[Whether curses implements function keys with known semantics]))
+
+LIBS="$save_LIBS"
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+dnl Checks for library functions.
+AC_TYPE_SIGNAL
+AC_SYS_LARGEFILE
+
+AC_CHECK_FUNCS(glob)
+if test "$GXX" = "yes"
+then
+ CPPFLAGS="-Wall $CPPFLAGS"
+fi
+
+AC_OUTPUT(Makefile)
diff --git a/curses/curses.C b/curses/curses.C
new file mode 100644
index 0000000..5dbf22f
--- /dev/null
+++ b/curses/curses.C
@@ -0,0 +1,524 @@
+/*
+**
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+
+#include "mycurses.H"
+#include "widechar.H"
+#include "cursescontainer.H"
+#include "curseskeyhandler.H"
+
+#include <algorithm>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <iostream>
+
+bool Curses::keepgoing=false;
+bool Curses::shiftmode=false;
+
+std::string Curses::suspendCommand;
+
+#define TABSIZE 8
+
+const char Curses::Key::LEFT[]="LEFT",
+ Curses::Key::RIGHT[]="RIGHT",
+ Curses::Key::SHIFTLEFT[]="SHIFTLEFT",
+ Curses::Key::SHIFTRIGHT[]="SHIFTRIGHT",
+ Curses::Key::UP[]="UP",
+ Curses::Key::DOWN[]="DOWN",
+ Curses::Key::SHIFTUP[]="SHIFTUP",
+ Curses::Key::SHIFTDOWN[]="SHIFTDOWN",
+ Curses::Key::DEL[]="DEL",
+ Curses::Key::CLREOL[]="CLREOL",
+ Curses::Key::BACKSPACE[]="BACKSPACE",
+ Curses::Key::ENTER[]="ENTER",
+ Curses::Key::PGUP[]="PGUP",
+ Curses::Key::PGDN[]="PGDN",
+ Curses::Key::SHIFTPGUP[]="SHIFTPGUP",
+ Curses::Key::SHIFTPGDN[]="SHIFTPGDN",
+ Curses::Key::HOME[]="HOME",
+ Curses::Key::END[]="END",
+ Curses::Key::SHIFTHOME[]="SHIFTHOME",
+ Curses::Key::SHIFTEND[]="SHIFTEND",
+ Curses::Key::SHIFT[]="SHIFT",
+ Curses::Key::RESIZE[]="RESIZE";
+
+bool Curses::Key::operator==(const std::vector<unicode_char> &v) const
+{
+ return keycode == 0 &&
+ std::find(v.begin(), v.end(), ukey) != v.end();
+}
+
+Curses::Curses(CursesContainer *parentArg) : parent(parentArg),
+ row(0), col(0),
+ alignment(Curses::LEFT)
+{
+ if (parent != NULL)
+ parent->addChild(this);
+}
+
+Curses::~Curses()
+{
+ CursesContainer *p=getParent();
+
+ if (p)
+ p->deleteChild(this);
+
+ if (hasFocus())
+ currentFocus=0;
+}
+
+int Curses::getScreenWidth() const
+{
+ const CursesContainer *p=getParent();
+
+ while (p && p->getParent())
+ p=p->getParent();
+
+ return (p ? p->getWidth():0);
+}
+
+int Curses::getScreenHeight() const
+{
+ const CursesContainer *p=getParent();
+
+ while (p && p->getParent())
+ p=p->getParent();
+
+ return (p ? p->getHeight():0);
+}
+
+int Curses::getRow() const
+{
+ return row;
+}
+
+int Curses::getCol() const
+{
+ return col;
+}
+
+void Curses::setRow(int r)
+{
+ row=r;
+}
+
+void Curses::scrollTo(size_t r)
+{
+ if (parent)
+ parent->scrollTo(r + row);
+}
+
+void Curses::setCol(int c)
+{
+ col=c;
+}
+
+void Curses::resized()
+{
+ draw();
+}
+
+void Curses::getVerticalViewport(size_t &first_row,
+ size_t &nrows)
+{
+ CursesContainer *p=getParent();
+
+ if (!p)
+ {
+ first_row=0;
+ nrows=getHeight();
+ return;
+ }
+
+ p->getVerticalViewport(first_row, nrows);
+ size_t r=getRow();
+ size_t h=getHeight();
+
+
+ if (r + h <= first_row || r >= first_row + nrows)
+ {
+ first_row=nrows=0;
+ return;
+ }
+
+ if (first_row < r)
+ {
+ nrows -= r-first_row;
+ first_row=r;
+ }
+
+ first_row -= r;
+
+ if (first_row + nrows > h)
+ nrows=h-first_row;
+}
+
+void Curses::setAlignment(Alignment alignmentArg)
+{
+ alignment=alignmentArg;
+}
+
+Curses::Alignment Curses::getAlignment()
+{
+ return alignment;
+}
+
+int Curses::getRowAligned() const
+{
+ return row;
+}
+
+int Curses::getColAligned() const
+{
+ int c=col;
+
+ if (alignment == PARENTCENTER)
+ {
+ const CursesContainer *p=getParent();
+
+ if (p != NULL)
+ {
+ c=p->getWidth()/2;
+ c -= getWidth()/2;
+ }
+ }
+ else if (alignment == CENTER)
+ c -= getWidth() / 2;
+ else if (alignment == RIGHT)
+ c -= getWidth();
+ return c;
+}
+
+Curses *Curses::getDialogChild() const
+{
+ return NULL;
+}
+
+bool Curses::writeText(const char *text, int r, int c,
+ const CursesAttr &attr) const
+{
+ CursesContainer *p=parent;
+
+ if (!isDialog() && p && p->getDialogChild())
+ return false; // Parent has a dialog and it ain't us
+
+ if (p)
+ return p->writeText(text, r+getRowAligned(),
+ c+getColAligned(), attr);
+ return false;
+}
+
+bool Curses::writeText(const std::vector<unicode_char> &text, int r, int c,
+ const Curses::CursesAttr &attr) const
+{
+ CursesContainer *p=parent;
+
+ if (!isDialog() && p && p->getDialogChild())
+ return false; // Parent has a dialog and it ain't us
+
+ if (p)
+ return p->writeText(text, r+getRowAligned(),
+ c+getColAligned(), attr);
+ return false;
+}
+
+
+void Curses::writeText(std::string text, int row, int col,
+ const CursesAttr &attr) const
+{
+ writeText(text.c_str(), row, col, attr);
+}
+
+bool Curses::isDialog() const
+{
+ return false;
+}
+
+void Curses::erase()
+{
+ const CursesContainer *p=getParent();
+
+ if (!isDialog() && p && p->getDialogChild())
+ return; // Parent has a dialog and it ain't us
+
+ size_t w=getWidth();
+ size_t h=getHeight();
+
+ std::vector<unicode_char> spaces;
+
+ spaces.insert(spaces.end(), w, ' ');
+
+ size_t i;
+
+ CursesAttr attr;
+
+ for (i=0; i<h; i++)
+ writeText(spaces, i, 0, attr);
+}
+
+void Curses::beepError()
+{
+ CursesContainer *p=getParent();
+
+ if (p)
+ p->beepError();
+}
+
+Curses *Curses::currentFocus=0;
+
+void Curses::requestFocus()
+{
+ Curses *oldFocus=currentFocus;
+
+ currentFocus=NULL;
+
+ cursesPtr<Curses> ptr=this;
+
+ if (oldFocus)
+ oldFocus->focusLost();
+
+ if (!ptr.isDestroyed() && currentFocus == NULL)
+ {
+ currentFocus=this;
+ focusGained();
+ }
+}
+
+void Curses::dropFocus()
+{
+ Curses *oldFocus=currentFocus;
+
+ currentFocus=NULL;
+
+ if (oldFocus)
+ oldFocus->focusLost();
+}
+
+void Curses::focusGained()
+{
+ draw();
+}
+
+void Curses::focusLost()
+{
+ draw();
+}
+
+void Curses::flush()
+{
+ CursesContainer *p=getParent();
+
+ if (p)
+ p->flush();
+}
+
+bool Curses::hasFocus()
+{
+ return currentFocus == this;
+}
+
+int Curses::getCursorPosition(int &r, int &c)
+{
+ r += getRowAligned();
+ c += getColAligned();
+
+ CursesContainer *parent=getParent();
+ if (parent)
+ return parent->getCursorPosition(r, c);
+
+ return 1;
+}
+
+
+bool Curses::processKey(const Key &k)
+{
+ if (k == Key::RESIZE)
+ return true; // No-op.
+
+ return CursesKeyHandler::handle(k, currentFocus);
+}
+
+bool Curses::processKeyInFocus(const Key &key)
+{
+ if ((key.plain() && key.ukey == '\t')
+ || key == key.DOWN || key == key.SHIFTDOWN
+ || key == key.ENTER)
+ {
+ transferNextFocus();
+ return true;
+ }
+
+ if (key == key.UP || key == key.SHIFTUP)
+ {
+ transferPrevFocus();
+ return true;
+ }
+
+ if (key == key.PGUP || key == key.SHIFTPGUP)
+ {
+ int h=getScreenHeight();
+
+ if (h > 5)
+ h -= 5;
+
+ if (h < 5)
+ h=5;
+
+ while (h)
+ {
+ processKey(Key(key ==
+ Key::PGUP ? Key::UP:Key::SHIFTUP));
+ --h;
+ }
+ return true;
+ }
+
+ if (key == key.PGDN || key == key.SHIFTPGDN)
+ {
+ int h=getScreenHeight();
+
+ if (h > 5)
+ h -= 5;
+
+ if (h < 5)
+ h=5;
+
+ while (h)
+ {
+ processKey(Key(key ==
+ Key::PGDN ? Key::DOWN:Key::SHIFTDOWN));
+ --h;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool Curses::isFocusable()
+{
+ return 0;
+}
+
+void Curses::transferNextFocus()
+{
+ Curses *p=this;
+
+ do
+ {
+ p=p->getNextFocus();
+
+ } while (p != NULL && !p->isFocusable());
+
+ if (p)
+ p->requestFocus();
+}
+
+void Curses::transferPrevFocus()
+{
+ Curses *p=this;
+
+ do
+ {
+ p=p->getPrevFocus();
+ } while (p != NULL && !p->isFocusable());
+
+ if (p)
+ p->requestFocus();
+}
+
+bool Curses::childPositionCompareFunc(Curses *a, Curses *b)
+{
+ if (a->getRow() < b->getRow())
+ return true;
+
+ if (a->getRow() == b->getRow())
+ {
+ if (a->getCol() < b->getCol())
+ return true;
+ if (a->getCol() == b->getCol())
+ {
+ CursesContainer *p=a->getParent();
+ size_t ai, bi;
+
+ for (ai=0; ai<p->children.size(); ai++)
+ if (a == p->children[ai].child)
+ {
+ for (bi=0; bi<p->children.size(); bi++)
+ if (b == p->children[bi].child)
+ return ai < bi;
+ break;
+ }
+ }
+ }
+ return false;
+}
+
+Curses *Curses::getNextFocus()
+{
+ CursesContainer *p=getParent();
+ Curses *child=this;
+
+ while (p != NULL)
+ {
+ size_t i;
+ Curses *nextFocusPtr=NULL;
+
+ for (i=0; i<p->children.size(); i++)
+ if ( childPositionCompareFunc(child,
+ p->children[i].child) &&
+ (nextFocusPtr == NULL ||
+ childPositionCompareFunc(p->children[i].child,
+ nextFocusPtr)))
+ nextFocusPtr=p->children[i].child;
+
+ if (nextFocusPtr)
+ return nextFocusPtr;
+
+ if (p->isDialog())
+ break; // Do not cross dialog box boundaries.
+
+ child=p;
+ p=p->getParent();
+ }
+ return NULL;
+}
+
+Curses *Curses::getPrevFocus()
+{
+ CursesContainer *p=getParent();
+ Curses *child=this;
+
+ while (p != NULL)
+ {
+ size_t i;
+
+ Curses *prevFocusPtr=NULL;
+
+ for (i=0; i<p->children.size(); i++)
+ if ( childPositionCompareFunc(p->children[i].child,
+ child) &&
+ (prevFocusPtr == NULL ||
+ childPositionCompareFunc(prevFocusPtr,
+ p->children[i].child)))
+ prevFocusPtr=p->children[i].child;
+
+ if (prevFocusPtr)
+ return prevFocusPtr;
+
+ if (p->isDialog())
+ break; // Do not cross dialog box boundaries.
+
+ child=p;
+ p=p->getParent();
+ }
+ return NULL;
+}
diff --git a/curses/cursesbutton.C b/curses/cursesbutton.C
new file mode 100644
index 0000000..3638860
--- /dev/null
+++ b/curses/cursesbutton.C
@@ -0,0 +1,136 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesbutton.H"
+
+using namespace std;
+
+CursesButton::CursesButton(CursesContainer *parent,
+ string textArg,
+ int toggle) : CursesLabel(parent, ""),
+ toggleButton(toggle),
+ buttonName(textArg)
+{
+ setStyle(toggleButton ? TOGGLE:NORMAL);
+}
+
+CursesButton::~CursesButton()
+{
+ erase();
+}
+
+void CursesButton::setText(string textArg)
+{
+ buttonName=textArg;
+ setStyle(currentStyle);
+}
+
+void CursesButton::setToggled(bool flag)
+{
+ toggleButton= flag ? -1:1;
+ setStyle(TOGGLE);
+}
+
+void CursesButton::setStyle(Style style)
+{
+ currentStyle=style;
+
+ switch (currentStyle) {
+ case TOGGLE:
+ CursesLabel::setText((toggleButton < 0 ? "[X] ":"[ ] ") + buttonName);
+ break;
+ case MENU:
+ CursesLabel::setText(buttonName);
+ break;
+ default:
+ CursesLabel::setText("[ " + buttonName + " ]");
+ break;
+ }
+}
+
+void CursesButton::draw()
+{
+ int fg=attribute.getFgColor();
+ int bg=attribute.getBgColor();
+
+ switch (currentStyle) {
+ case TOGGLE:
+ attribute=Curses::CursesAttr();
+ break;
+ case MENU:
+ attribute=Curses::CursesAttr().setReverse(hasFocus());
+ break;
+ default:
+ if (hasFocus())
+ attribute=Curses::CursesAttr().setReverse();
+ else
+ attribute=Curses::CursesAttr().setHighlight();
+ break;
+ }
+
+ attribute.setFgColor(fg);
+ attribute.setBgColor(bg);
+ CursesLabel::draw();
+}
+
+bool CursesButton::isFocusable()
+{
+ return true;
+}
+
+void CursesButton::focusGained()
+{
+ draw();
+}
+
+void CursesButton::focusLost()
+{
+ draw();
+}
+
+int CursesButton::getCursorPosition(int &r, int &c)
+{
+ r=0;
+
+ if (currentStyle == TOGGLE)
+ {
+ c=1;
+ CursesLabel::getCursorPosition(r, c);
+ return 1;
+ }
+
+ c=CursesLabel::getWidth();
+
+ CursesLabel::getCursorPosition(r, c);
+
+ return 0;
+}
+
+int CursesButton::getWidth() const
+{
+ return CursesLabel::getWidth()+1;
+}
+
+void CursesButton::clicked()
+{
+}
+
+bool CursesButton::processKeyInFocus(const Key &key)
+{
+ if (key == key.ENTER || (key.plain() && key.ukey == ' '))
+ {
+ if (toggleButton)
+ {
+ toggleButton= -toggleButton;
+ setStyle(TOGGLE);
+ }
+ clicked();
+ return true;
+ }
+
+ return CursesLabel::processKeyInFocus(key);
+}
diff --git a/curses/cursesbutton.H b/curses/cursesbutton.H
new file mode 100644
index 0000000..216c248
--- /dev/null
+++ b/curses/cursesbutton.H
@@ -0,0 +1,103 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesbutton_H
+#define cursesbutton_H
+
+#include "mycurses.H"
+#include "curseslabel.H"
+
+////////////////////////////////////////////////////////////////////////
+//
+// A plain, garden variety, button. Centered, or right-aligned, perhaps.
+//
+// CursesButton simply subclasses CursesLabel, makes it focusable, and
+// overrides WriteText.
+//
+
+class CursesButton : public CursesLabel {
+
+ int toggleButton;
+
+ std::string buttonName;
+
+public:
+ // Button style:
+
+ enum Style {
+ NORMAL,
+ TOGGLE,
+ MENU
+ };
+
+private:
+ Style currentStyle;
+public:
+ CursesButton(CursesContainer *parent,
+ std::string textArg, int toggle=0);
+ ~CursesButton();
+
+ void setStyle(Style);
+ void draw();
+
+ bool isFocusable();
+ void focusGained();
+ void focusLost();
+ void setText(std::string textArg);
+
+ int getCursorPosition(int &r, int &c);
+
+ int getWidth() const;
+
+ bool processKeyInFocus(const Curses::Key &key);
+ virtual void clicked();
+
+ int getSelected() { return toggleButton < 0; }
+ void setToggled(bool flag);
+};
+
+//////////////////////////////////////////////////////////////////////////
+//
+// Instead of subclassing CursesButton, here's a template to have it be a
+// member of another class. Typical usage:
+//
+// class X {
+//
+// CursesButtonRedirect<X> button1;
+//
+// void button1clicked();
+// } ;
+//
+// X::X()
+// {
+// button1=this;
+// button1=&X::button1clicked;
+// }
+
+template<class T> class CursesButtonRedirect : public CursesButton {
+
+ T *myClass;
+ void (T::*mymethod)();
+public:
+ CursesButtonRedirect(CursesContainer *parent,
+ std::string textArg, int toggle=0)
+ : CursesButton(parent, textArg, toggle)
+ {
+ }
+
+ ~CursesButtonRedirect()
+ {
+ }
+
+ void operator=(T *p) { myClass=p; }
+ void operator=(void (T::*p)()) {mymethod=p; }
+
+ void clicked() { (myClass->*mymethod)(); }
+
+ T *getObject() const { return myClass; }
+};
+
+#endif
diff --git a/curses/curseschoicebutton.C b/curses/curseschoicebutton.C
new file mode 100644
index 0000000..be607d4
--- /dev/null
+++ b/curses/curseschoicebutton.C
@@ -0,0 +1,38 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "curseschoicebutton.H"
+
+using namespace std;
+
+CursesChoiceButton::CursesChoiceButton(CursesContainer *parent)
+ : CursesButton(parent, ""),
+ selectedOption(0)
+{
+}
+
+void CursesChoiceButton::setOptions(const vector<string> &optionListArg,
+ size_t initialOption)
+{
+ optionList=optionListArg;
+ selectedOption=initialOption;
+ setText(optionListArg[selectedOption]);
+}
+
+CursesChoiceButton::~CursesChoiceButton()
+{
+}
+
+void CursesChoiceButton::clicked()
+{
+ if (optionList.size() > 0)
+ {
+ selectedOption = (selectedOption+1) % optionList.size();
+ setText(optionList[selectedOption]);
+ }
+}
+
diff --git a/curses/curseschoicebutton.H b/curses/curseschoicebutton.H
new file mode 100644
index 0000000..bfa223b
--- /dev/null
+++ b/curses/curseschoicebutton.H
@@ -0,0 +1,81 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef curseschoicebutton_H
+#define curseschoicebutton_H
+
+#include "mycurses.H"
+#include "cursesbutton.H"
+
+#include <vector>
+
+////////////////////////////////////////////////////////////////////////
+//
+// A button that cycles through a small list of options, each time it
+// is "clicked".
+//
+
+class CursesChoiceButton : public CursesButton {
+
+ std::vector<std::string> optionList;
+ size_t selectedOption;
+
+public:
+ CursesChoiceButton(CursesContainer *parent);
+ ~CursesChoiceButton();
+
+ void setOptions(const std::vector<std::string> &optionListArg,
+ size_t initialOption);
+
+ size_t getSelectedOption() const { return selectedOption; }
+
+ void clicked();
+};
+
+//////////////////////////////////////////////////////////////////////////
+//
+// Instead of subclassing CursesChoiceButton, here's a template to have it be a
+// member of another class. Typical usage:
+//
+// class X {
+//
+// CursesChoiceButtonRedirect<X> button1;
+//
+// void button1clicked();
+// } ;
+//
+// X::X()
+// {
+// button1=this;
+// button1=&X::button1clicked;
+// }
+
+template<class T> class CursesChoiceButtonRedirect
+ : public CursesChoiceButton {
+
+ T *myClass;
+ void (T::*mymethod)();
+public:
+ CursesChoiceButtonRedirect(CursesContainer *parent)
+ : CursesChoiceButton(parent)
+ {
+ }
+
+ ~CursesChoiceButtonRedirect()
+ {
+ }
+
+ void operator=(T *p) { myClass=p; }
+ void operator=(void (T::*p)()) {mymethod=p; }
+
+ void clicked()
+ {
+ CursesChoiceButton::clicked();
+ (myClass->*mymethod)();
+ }
+};
+
+#endif
diff --git a/curses/cursescontainer.C b/curses/cursescontainer.C
new file mode 100644
index 0000000..9ce1db7
--- /dev/null
+++ b/curses/cursescontainer.C
@@ -0,0 +1,178 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+
+#include "cursescontainer.H"
+
+using namespace std;
+
+CursesContainer::CursesContainer(CursesContainer *parent)
+ : Curses(parent), drawIndex(0)
+{
+}
+
+int CursesContainer::getWidth() const
+{
+ vector<Child>::const_iterator b=children.begin(), e=children.end();
+
+ int maxw=0;
+
+ while (b != e)
+ {
+ const Child &c= *b++;
+
+ // If the child's alignment is parent's center, child's
+ // getColAligned() will call this->getWidth() again!
+ // Prevent this infinite loop by considering child's
+ // position at column 0
+
+ int w= c.child->getAlignment() == PARENTCENTER
+ ? 0:c.child->getColAligned();
+
+ w += c.child->getWidth();
+
+ if (w > maxw)
+ maxw=w;
+ }
+ return maxw;
+}
+
+int CursesContainer::getHeight() const
+{
+ vector<Child>::const_iterator b=children.begin(), e=children.end();
+
+ int maxh=0;
+
+ while (b != e)
+ {
+ const Child &c= *b++;
+
+ int h=c.child->getRowAligned() + c.child->getHeight();
+
+ if (h > maxh)
+ maxh=h;
+ }
+ return maxh;
+}
+
+void CursesContainer::addChild(Curses *child)
+{
+ if (child->getParent())
+ child->getParent()->deleteChild(child);
+
+ children.push_back(Child(child));
+
+ child->setParent(this);
+}
+
+void CursesContainer::deleteChild(Curses *child)
+{
+ vector<Child>::iterator b=children.begin(), e=children.end();
+
+ while (b != e)
+ {
+ Child &c= *b;
+
+ if (c.child == child)
+ {
+ child->setParent(0);
+ children.erase(b, b+1);
+ break;
+ }
+ b++;
+ }
+}
+
+CursesContainer::~CursesContainer()
+{
+ vector<Child>::iterator b=children.begin(), e=children.end();
+
+ while (b != e)
+ (*b++).child->setParent(0);
+}
+
+Curses *CursesContainer::getDialogChild() const
+{
+ vector<Child>::const_iterator b, e;
+
+ b=children.begin();
+ e=children.end();
+
+ while (b != e)
+ {
+ Curses *p=(*b++).child;
+
+ if (p->isDialog())
+ return p;
+ }
+
+ return NULL;
+}
+
+void CursesContainer::draw()
+{
+ vector<Child>::iterator b, e;
+
+ Curses *p=getDialogChild();
+
+ if (p)
+ {
+ p->draw();
+ return;
+ }
+
+ b=children.begin();
+ e=children.end();
+
+ while (b != e)
+ (*b++).child->draw();
+}
+
+void CursesContainer::erase()
+{
+ vector<Child>::iterator b, e;
+
+ Curses *p=getDialogChild();
+
+ if (p)
+ {
+ p->erase();
+ return;
+ }
+
+ b=children.begin();
+ e=children.end();
+
+ while (b != e)
+ (*b++).child->erase();
+}
+
+void CursesContainer::resized()
+{
+ vector<Child>::iterator b=children.begin(), e=children.end();
+
+ while (b != e)
+ (*b++).child->resized();
+}
+
+Curses *CursesContainer::getNextFocus()
+{
+ if (children.size() == 0)
+ return Curses::getNextFocus();
+
+ return children[0].child;
+}
+
+Curses *CursesContainer::getPrevFocus()
+{
+ size_t n=children.size();
+
+ if (n == 0)
+ return Curses::getNextFocus();
+ --n;
+
+ return children[n].child;
+}
diff --git a/curses/cursescontainer.H b/curses/cursescontainer.H
new file mode 100644
index 0000000..11f45f3
--- /dev/null
+++ b/curses/cursescontainer.H
@@ -0,0 +1,76 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursescontainer_H
+#define cursescontainer_H
+
+#include "../curses/curses_config.h"
+#include "mycurses.H"
+
+#include <wchar.h>
+#include <vector>
+
+////////////////////////////////////////////////////////////////////////
+//
+// CursesContainer is a superclass for all Curses objects that contain
+// other Curses object.
+
+class CursesContainer : public Curses {
+
+ class Child {
+ public:
+ Curses *child;
+
+ Child(Curses *childArg) : child(childArg)
+ {
+ }
+ };
+
+ std::vector<Child> children;
+
+ std::vector<Child>::iterator drawIndex;
+
+public:
+ friend class Curses;
+
+ CursesContainer(CursesContainer *parent=0);
+ ~CursesContainer();
+
+ // getWidth()/getHeight() computes the largest rectangle that
+ // contains all current children.
+
+ virtual int getWidth() const;
+ virtual int getHeight() const;
+
+ virtual Curses *getDialogChild() const;
+
+ // draw/erase recursively invoke draw/erase methods of all children,
+ // unless a child CursesObject's isDialog() method returns true, in
+ // which case only that child's draw/erase method is called.
+
+ virtual void draw();
+ virtual void erase();
+
+ // The default resized() method recursively invokes the resized()
+ // method of all children.
+
+ virtual void resized();
+
+ // The constructor and the destructor of a Curses object automatically
+ // calls addChild/deleteChild to register the new child process.
+
+ virtual void addChild(Curses *child);
+ virtual void deleteChild(Curses *child);
+
+ // The default implementation of getNextFocus()/getPrevFocus()
+ // return the pointer to the first or the last child.
+
+ virtual Curses *getNextFocus();
+ virtual Curses *getPrevFocus();
+};
+
+
+#endif
diff --git a/curses/cursesdialog.C b/curses/cursesdialog.C
new file mode 100644
index 0000000..fb87a97
--- /dev/null
+++ b/curses/cursesdialog.C
@@ -0,0 +1,233 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesdialog.H"
+#include "curseslabel.H"
+#include "cursesfield.H"
+
+using namespace std;
+
+CursesDialog::CursesDialog(CursesContainer *parent)
+ : CursesContainer(parent), max_label_width(0), max_field_width(0),
+ draw_flag(0)
+{
+}
+
+CursesDialog::~CursesDialog()
+{
+}
+
+// Automatically size to the largest prompt/input field combo
+
+int CursesDialog::getWidth() const
+{
+ return max_label_width + max_field_width;
+}
+
+// Automatically size to the lowest input field
+
+int CursesDialog::getHeight() const
+{
+ int h=0;
+
+ vector< pair<CursesLabel *, Curses *> >::const_iterator
+ b=prompts.begin(), e=prompts.end();
+
+ while (b != e)
+ {
+ if (b->first && b->first->getRow() >= h)
+ h=b->first->getRow()+1;
+
+ if (b->second && b->second->getRow() >= h)
+ h=b->second->getRow()+1;
+ b++;
+
+ }
+
+ return h;
+}
+
+void CursesDialog::draw()
+{
+ draw_flag=1;
+ CursesContainer::draw();
+}
+
+void CursesDialog::addPrompt(CursesLabel *label, Curses *field)
+{
+ size_t maxrow=0;
+
+ vector< pair<CursesLabel *, Curses *> >::iterator
+ b=prompts.begin(), e=prompts.end();
+
+ while (b != e)
+ {
+ if ( b->first != NULL && (size_t)b->first->getRow() >= maxrow)
+ maxrow=b->first->getRow()+1;
+
+ if ( b->second != NULL && (size_t)b->second->getRow() >=maxrow)
+ maxrow=b->second->getRow()+1;
+
+ b++;
+ }
+
+ addPrompt(label, field, maxrow);
+}
+
+
+void CursesDialog::addPrompt(CursesLabel *label, Curses *field, size_t atRow)
+{
+ vector< pair<CursesLabel *, Curses *> >::iterator
+ b=prompts.begin(), e=prompts.end();
+
+ // If the new field should precede the existing fields, push them down
+ // by one row.
+
+ while (b != e)
+ {
+ if ( b->first != NULL && (size_t)b->first->getRow() >= atRow)
+ b->first->setRow(b->first->getRow()+1);
+
+ if ( b->second != NULL && (size_t)b->second->getRow() >= atRow)
+ b->second->setRow(b->second->getRow()+1);
+ b++;
+ }
+
+ if (label != NULL)
+ {
+ label->setAlignment(Curses::RIGHT);
+ label->setRow(atRow);
+ addChild(label);
+ }
+
+ if (field != NULL)
+ {
+ field->setRow(atRow);
+ addChild(field);
+ }
+
+ prompts.push_back(make_pair(label, field));
+
+ int w;
+
+ if (label != NULL)
+ {
+ w=label->getWidth();
+
+ if (w > max_label_width)
+ max_label_width=w;
+ }
+
+ if (field != NULL)
+ {
+ w=field->getWidth();
+
+ if (w > max_field_width)
+ max_field_width=w;
+ }
+
+ b=prompts.begin();
+ e=prompts.end();
+
+ while (b != e)
+ {
+ if (b->first != NULL)
+ b->first->setCol(max_label_width);
+ if (b->second != NULL)
+ b->second->setCol(max_label_width);
+ b++;
+ }
+}
+
+void CursesDialog::delPrompt(CursesLabel *label)
+{
+ vector< pair<CursesLabel *, Curses *> >::iterator
+ b=prompts.begin(), e=prompts.end();
+
+ while (b != e)
+ {
+ if (b->first == label)
+ {
+ delPrompt(b, b->first->getRow());
+ return;
+ }
+ b++;
+ }
+}
+
+void CursesDialog::delPrompt(Curses *field)
+{
+ vector< pair<CursesLabel *, Curses *> >::iterator
+ b=prompts.begin(), e=prompts.end();
+
+ while (b != e)
+ {
+ if (b->second == field)
+ {
+ delPrompt(b, b->second->getRow());
+ return;
+ }
+ b++;
+ }
+}
+
+void CursesDialog::delPrompt(vector< pair<CursesLabel *, Curses *> >::iterator
+ p, int row)
+{
+ prompts.erase(p);
+
+ vector< pair<CursesLabel *, Curses *> >::iterator
+ b=prompts.begin(), e=prompts.end();
+
+ while (b != e)
+ {
+ if (b->first && b->first->getRow() > row)
+ b->first->setRow(b->first->getRow()-1);
+
+ if (b->second && b->second->getRow() > row)
+ b->second->setRow(b->second->getRow()-1);
+ b++;
+ }
+}
+
+bool CursesDialog::writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const
+{
+ if (draw_flag)
+ return CursesContainer::writeText(text, row, col, attr);
+ return false;
+}
+
+bool CursesDialog::writeText(const std::vector<unicode_char> &text,
+ int row, int col,
+ const Curses::CursesAttr &attr) const
+{
+ if (draw_flag)
+ CursesContainer::writeText(text, row, col, attr);
+ return false;
+}
+
+
+void CursesDialog::deleteChild(Curses *child)
+{
+ vector< pair<CursesLabel *, Curses *> >::iterator
+ b=prompts.begin(), e=prompts.end();
+
+ while (b != e)
+ {
+ if ( b->first != NULL &&
+ ((Curses *)b->first) == child)
+ b->first=NULL;
+
+ if ( b->second != NULL &&
+ ((Curses *)b->second) == child)
+ b->second=NULL;
+ b++;
+ }
+
+ CursesContainer::deleteChild(child);
+}
diff --git a/curses/cursesdialog.H b/curses/cursesdialog.H
new file mode 100644
index 0000000..f31d1cc
--- /dev/null
+++ b/curses/cursesdialog.H
@@ -0,0 +1,74 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesdialog_H
+#define cursesdialog_H
+
+#include "../curses/curses_config.h"
+#include "cursescontainer.H"
+
+#include <vector>
+
+////////////////////////////////////////////////////////////////////////////
+//
+// A helper container that formats dialog screens.
+//
+// The CursesDialog contains a list of fields, and their corresponding labels.
+// The labels are right aligned.
+//
+
+class CursesLabel;
+class CursesField;
+
+class CursesDialog : public CursesContainer {
+
+ std::vector< std::pair<CursesLabel *, Curses *> > prompts;
+
+ int max_label_width;
+ int max_field_width;
+
+ // To prevent a lot of extra work, quietly eat all WriteText()s
+ // until the dialog is complete.
+ int draw_flag;
+
+public:
+ CursesDialog(CursesContainer *parent);
+ ~CursesDialog();
+
+ // Calculate max size.
+
+ int getWidth() const;
+ int getHeight() const;
+
+ void draw();
+
+ // Add a new input field, and its corresponding prompt
+ // (either one may be NULL, both can be NULL to append some useful
+ // filler).
+
+ virtual void addPrompt(CursesLabel *label,
+ Curses *field);
+ virtual void addPrompt(CursesLabel *label,
+ Curses *field,
+ size_t atRow);
+ void delPrompt(CursesLabel *label);
+ void delPrompt(Curses *field);
+
+private:
+ void delPrompt(std::vector< std::pair<CursesLabel *, Curses *> >::iterator p,
+ int row);
+public:
+
+ void deleteChild(Curses *child);
+
+ bool writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const;
+ bool writeText(const std::vector<unicode_char> &text, int row, int col,
+ const Curses::CursesAttr &attr) const;
+
+};
+
+#endif
diff --git a/curses/cursesfield.C b/curses/cursesfield.C
new file mode 100644
index 0000000..498ab67
--- /dev/null
+++ b/curses/cursesfield.C
@@ -0,0 +1,689 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesfield.H"
+#include "curseskeyhandler.H"
+#include "cursesmoronize.H"
+#include "widechar.H"
+#include <algorithm>
+#include <iostream>
+
+std::vector<unicode_char> CursesField::yesKeys, CursesField::noKeys;
+unicode_char CursesField::yankKey='\x19';
+unicode_char CursesField::clrEolKey='\x0B';
+
+std::list<CursesFlowedLine> CursesField::cutBuffer; // for cut/paste
+
+CursesField::CursesField(CursesContainer *parent,
+ size_t widthArg,
+ size_t maxlengthArg,
+ std::string initValue)
+ : Curses(parent), width(widthArg), maxlength(maxlengthArg),
+ shiftoffset(0),
+ selectpos(0), passwordChar(0), yesnoField(false),
+ isUnderlined(true)
+{
+ if (yesKeys.size() == 0)
+ mail::iconvert::convert(std::string("yY"),
+ unicode_default_chset(),
+ yesKeys);
+
+ if (noKeys.size() == 0)
+ mail::iconvert::convert(std::string("nN"),
+ unicode_default_chset(),
+ yesKeys);
+
+ std::vector<unicode_char> utext;
+
+ mail::iconvert::convert(initValue, unicode_default_chset(), utext);
+
+ text.set_contents(utext, std::vector<unicode_char>());
+
+ selectpos=text.before_insert.graphemes.size();
+}
+
+void CursesField::setAttribute(Curses::CursesAttr attr)
+{
+ attribute=attr;
+ draw();
+}
+
+CursesField::~CursesField()
+{
+ if (optionHelp.size() > 0)
+ CursesKeyHandler::handlerListModified=true;
+}
+
+void CursesField::setOptionHelp(const std::vector< std::pair<std::string,
+ std::string> >
+ &help)
+{
+ CursesKeyHandler::handlerListModified=true;
+ optionHelp=help;
+}
+
+void CursesField::setRow(int row)
+{
+ erase();
+ Curses::setRow(row);
+ draw();
+}
+
+void CursesField::setCol(int col)
+{
+ erase();
+ Curses::setCol(col);
+ draw();
+}
+
+void CursesField::setText(std::string textArg)
+{
+ std::vector<unicode_char> utext;
+
+ mail::iconvert::convert(textArg, unicode_default_chset(), utext);
+
+ text.set_contents(utext, std::vector<unicode_char>());
+
+ selectpos=text.before_insert.graphemes.size();
+ shiftoffset=0;
+ draw();
+}
+
+std::string CursesField::getText() const
+{
+ std::vector<unicode_char> b, a;
+
+ text.get_contents(b, a);
+
+ b.insert(b.end(), a.begin(), a.end());
+
+ return mail::iconvert::convert(b, unicode_default_chset());
+}
+
+int CursesField::getWidth() const
+{
+ return width;
+}
+
+void CursesField::setWidth(int w)
+{
+ if (w > 0)
+ width=w;
+ draw();
+}
+
+int CursesField::getHeight() const
+{
+ return 1;
+}
+
+void CursesField::getbeforeafter(widecharbuf &wbefore, widecharbuf &wafter)
+{
+ std::vector<unicode_char> before, after;
+ text.get_contents(before, after);
+
+ wbefore.init_unicode(before.begin(), before.end());
+ wafter.init_unicode(after.begin(), after.end());
+}
+
+void CursesField::draw()
+{
+ widecharbuf wbefore, wafter;
+
+ getbeforeafter(wbefore, wafter);
+
+ wafter.expandtabs(wbefore.expandtabs(0));
+
+ size_t a=wbefore.graphemes.size();
+ size_t b=selectpos;
+
+ if (a > b)
+ {
+ size_t c=a; a=b; b=c;
+ }
+
+ if (a < shiftoffset)
+ a=shiftoffset;
+
+ if (b < shiftoffset)
+ b=shiftoffset;
+
+ a -= shiftoffset;
+ b -= shiftoffset;
+
+ wbefore += wafter;
+
+ std::vector<unicode_char> ubeforesel, usel, uaftersel;
+ size_t usel_pos=0, uaftersel_pos=0;
+
+ size_t p=shiftoffset;
+ size_t col=0;
+
+ size_t virtual_col=0;
+
+ for (size_t i=0; i<p && i < wbefore.graphemes.size(); ++i)
+ virtual_col += wbefore.graphemes[i].wcwidth(virtual_col);
+
+ std::vector<unicode_char> unicodes;
+
+ while (p < wbefore.graphemes.size() && col < width)
+ {
+ const widecharbuf::grapheme_t &grapheme=wbefore.graphemes[p];
+
+ size_t grapheme_width=grapheme.wcwidth(virtual_col);
+
+ if (grapheme_width + col > width)
+ break;
+
+ std::vector<unicode_char> *vp;
+
+ if (a)
+ {
+ vp= &ubeforesel;
+ --a;
+ --b;
+ usel_pos += grapheme_width;
+ uaftersel_pos += grapheme_width;
+ }
+ else if (b)
+ {
+ vp= &usel;
+ --b;
+ uaftersel_pos += grapheme_width;
+ }
+ else
+ vp= &uaftersel;
+
+ unicodes.clear();
+
+ if (passwordChar)
+ vp->insert(vp->end(), grapheme_width, passwordChar);
+ else
+ vp->insert(vp->end(), grapheme.uptr,
+ grapheme.uptr+grapheme.cnt);
+
+ col += grapheme_width;
+ virtual_col += grapheme_width;
+ ++p;
+ }
+
+ uaftersel.insert(uaftersel.end(), width-col, ' ');
+
+ bool underlined=isUnderlined;
+
+ {
+ Curses::CursesAttr aa=attribute;
+
+ if (ubeforesel.size())
+ writeText(ubeforesel, 0, 0,
+ aa.setUnderline(underlined));
+ }
+
+ {
+ Curses::CursesAttr aa=attribute;
+
+ if (usel.size())
+ writeText(usel, 0, usel_pos,
+ aa.setReverse().setUnderline(underlined));
+ }
+
+ {
+ Curses::CursesAttr aa=attribute;
+
+ if (uaftersel.size())
+ writeText(uaftersel, 0, uaftersel_pos,
+ aa.setUnderline(underlined));
+ }
+}
+
+void CursesField::erase()
+{
+ std::vector<unicode_char> v;
+
+ v.insert(v.end(), width, ' ');
+
+ writeText(v, 0, 0, CursesAttr());
+}
+
+bool CursesField::isFocusable()
+{
+ return true;
+}
+
+void CursesField::focusGained()
+{
+ text.to_before();
+
+ selectpos=0;
+ shiftoffset=0;
+ draw();
+}
+
+void CursesField::focusLost()
+{
+ text.to_after();
+ selectpos=shiftoffset=0;
+ draw();
+}
+
+int CursesField::getCursorPosition(int &row, int &col)
+{
+ // Automatically scroll field to keep the cursor visible
+
+ row=0;
+
+ size_t save_shiftoffset=shiftoffset;
+
+ widecharbuf wbefore, wafter;
+
+ col=text.adjust_shift_pos(shiftoffset, width, wbefore, wafter);
+
+ if (save_shiftoffset != shiftoffset)
+ draw();
+
+ Curses::getCursorPosition(row, col);
+
+ return selectpos == wbefore.graphemes.size();
+}
+
+// Set selection position to current position
+
+void CursesField::setselectpos()
+{
+ text.insert_to_before();
+
+ widecharbuf wbefore, wafter;
+
+ getbeforeafter(wbefore, wafter);
+
+ selectpos=wbefore.graphemes.size();
+}
+
+bool CursesField::processKeyInFocus(const Key &key)
+{
+ if (key == key.HOME)
+ {
+ text.to_after();
+ shiftoffset=0;
+ selectpos=0;
+ draw();
+ return true;
+ }
+
+ if (key == key.SHIFTHOME)
+ {
+ text.to_after();
+ shiftoffset=0;
+ draw();
+ return true;
+ }
+
+ if (key == key.END)
+ {
+ text.to_before();
+ setselectpos();
+ draw();
+ return true;
+ }
+
+ if (key == key.SHIFTEND)
+ {
+ setselectpos();
+ text.to_before();
+ draw();
+ return true;
+ }
+
+ if (key == key.LEFT)
+ {
+ left();
+ draw();
+ return true;
+ }
+
+ if (key == key.SHIFTLEFT)
+ {
+ size_t savepos=selectpos;
+ left();
+ selectpos=savepos;
+ draw();
+ return true;
+ }
+
+ if (key == key.RIGHT)
+ {
+ right();
+ draw();
+ return true;
+ }
+
+ if (key == key.SHIFTRIGHT)
+ {
+ size_t savepos=selectpos;
+ right();
+
+ selectpos=savepos;
+ draw();
+ return true;
+ }
+
+ if ((key.plain() && key.ukey == '\t') ||
+ key == key.DOWN || key == key.UP ||
+ key == key.SHIFTDOWN || key == key.SHIFTUP || key == key.ENTER ||
+ key == key.PGDN || key == key.SHIFTPGDN ||
+ key == key.PGUP || key == key.SHIFTPGUP)
+ {
+ setselectpos();
+ draw();
+ return Curses::processKeyInFocus(key);
+ }
+
+ std::vector<unicode_char> cut_text;
+
+ if (text.inserted.empty() && key != key.SHIFT)
+ text.contents_cut(selectpos, cut_text);
+
+ if (!cut_text.empty())
+ {
+ if (!(key.plain() && key.ukey == yankKey))
+ {
+ cutBuffer.clear();
+ cutBuffer.push_back(CursesFlowedLine(mail::iconvert
+ ::convert(cut_text,
+ "utf-8"),
+ false));
+ }
+
+ selectpos=text.before_insert.graphemes.size();
+ draw();
+
+ if (key == key.BACKSPACE || key == key.DEL)
+ return true;
+ }
+
+ if (key == key.BACKSPACE)
+ {
+ text.insert_to_before();
+
+ if (text.before_insert.graphemes.size() > 0)
+ {
+ text.contents_cut(text.before_insert
+ .graphemes.size()-1, cut_text);
+ setselectpos();
+ }
+ draw();
+ return true;
+ }
+
+ if (key == key.DEL)
+ {
+ text.insert_to_before();
+
+ if (text.after_insert.graphemes.size() > 0)
+ {
+ text.contents_cut(text.before_insert
+ .graphemes.size()+1, cut_text);
+ setselectpos();
+ }
+ draw();
+ return true;
+ }
+
+ if (!key.plain())
+ {
+ return Curses::processKeyInFocus(key);
+ }
+
+ unicode_char k=key.ukey;
+
+ if (k == yankKey && !yesnoField &&
+ optionField.size() == 0 && !cutBuffer.empty())
+ ; // Will paste later
+ else if (k < ' ')
+ {
+ return Curses::processKeyInFocus(key);
+ }
+
+ bool doYesNoField=true;
+
+ if (optionField.size() > 0)
+ {
+ text.inserted.push_back(k);
+
+ std::vector<unicode_char> before, after;
+
+ text.get_contents(before, after);
+
+ before.insert(before.end(), after.begin(), after.end());
+
+ if (before.empty())
+ return true;
+
+ text.inserted.pop_back();
+
+ std::vector<unicode_char>::iterator p=
+ find(optionField.begin(),
+ optionField.end(), *before.begin());
+
+
+ if (p == optionField.end())
+ {
+ if (!yesnoField)
+ return true;
+ }
+ else doYesNoField=false;
+
+ }
+
+ if (doYesNoField && yesnoField)
+ {
+ text.inserted.push_back(k);
+
+ std::vector<unicode_char> before, after;
+
+ text.get_contents(before, after);
+
+ before.insert(before.end(), after.begin(), after.end());
+
+ if (before.empty())
+ return true;
+
+ std::vector<unicode_char>::iterator p=
+ find(yesKeys.begin(),
+ yesKeys.end(), *before.begin());
+
+ if (p != yesKeys.end())
+ {
+ before.clear();
+ after.clear();
+ before.push_back('Y');
+ text.set_contents(before, after);
+ this->processKeyInFocus(Key(Key::ENTER));
+ // Automatic enter
+ return true;
+ }
+
+ p=find(noKeys.begin(), noKeys.end(), *before.begin());
+
+ if (p != noKeys.end())
+ {
+ before.clear();
+ after.clear();
+ before.push_back('N');
+ text.set_contents(before, after);
+ this->processKeyInFocus(Key(Key::ENTER));
+ // Automatic enter
+ return true;
+ }
+
+ text.inserted.pop_back();
+ }
+
+ if (k == yankKey)
+ {
+ std::vector<unicode_char> ucut;
+
+ if (!cutBuffer.empty())
+ mail::iconvert::convert(cutBuffer.front().text,
+ "utf-8",
+ ucut);
+
+ text.insert_to_before();
+
+ std::vector<unicode_char> before, after;
+ text.get_contents(before, after);
+
+ before.insert(before.end(), ucut.begin(), ucut.end());
+
+ widecharbuf wbefore, wafter;
+
+ wbefore.init_unicode(before.begin(), before.end());
+ wafter.init_unicode(after.begin(), after.end());
+
+ size_t wbefore_w=wbefore.wcwidth(0);
+ size_t wafter_w=wafter.wcwidth(wbefore_w);
+
+ if (wbefore_w + wafter_w > maxlength)
+ beepError();
+ else
+ text.set_contents(before, after);
+ }
+ else
+ {
+ text.inserted.push_back(k);
+
+ widecharbuf wbefore, wafter;
+
+ getbeforeafter(wbefore, wafter);
+
+ size_t wbefore_w=wbefore.wcwidth(0);
+ size_t wafter_w=wafter.wcwidth(wbefore_w);
+
+ if (wbefore_w + wafter_w > maxlength)
+ {
+ beepError();
+ text.inserted.pop_back();
+ }
+ }
+
+ {
+ widecharbuf wbefore, wafter;
+
+ getbeforeafter(wbefore, wafter);
+
+ selectpos=wbefore.graphemes.size();
+ }
+
+ draw();
+
+ if (yesnoField || optionField.size() > 0)
+ this->processKeyInFocus(Key(Key::ENTER)); // Automatic enter
+ else if (CursesMoronize::enabled && !passwordChar &&
+ !text.inserted.empty())
+ {
+ size_t i;
+ unicode_char m_buf[CursesMoronize::max_keycode_len+1];
+ std::vector<unicode_char>::iterator b(text.inserted.begin()),
+ e(text.inserted.end()), p=e;
+
+ for (i=0; i<sizeof(m_buf)/sizeof(m_buf[0])-1; ++i)
+ {
+ if (b == p)
+ break;
+
+ --p;
+
+ m_buf[i]= *p;
+ }
+
+ m_buf[i]=0;
+
+ if (m_buf[0] && CursesMoronize::enabled)
+ {
+ std::vector<unicode_char> repl_c;
+
+ i=CursesMoronize::moronize(m_buf, repl_c);
+
+ if (i > 0)
+ {
+ while (i > 0)
+ {
+ processKeyInFocus(Curses::Key
+ (Curses::Key::BACKSPACE));
+ --i;
+ }
+
+ std::string s(mail::iconvert::convert
+ (repl_c, unicode_default_chset()));
+
+ std::vector<wchar_t> wc;
+
+ towidechar(s.begin(), s.end(), wc);
+
+ for (std::vector<wchar_t>::iterator
+ b(wc.begin()), e(wc.end()); b != e;
+ ++b)
+ {
+ processKeyInFocus(Curses::Key(*b));
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+void CursesField::left()
+{
+ text.insert_to_before();
+
+ if (text.before_insert.graphemes.size() > 0)
+ {
+ std::vector<unicode_char> cut_text;
+
+ text.contents_cut(text.before_insert.graphemes.size()-1,
+ cut_text);
+
+ std::vector<unicode_char> before, after;
+ text.get_contents(before, after);
+
+ after.insert(after.begin(), cut_text.begin(), cut_text.end());
+
+ text.set_contents(before, after);
+ }
+ setselectpos();
+ draw();
+}
+
+void CursesField::right()
+{
+ text.insert_to_before();
+
+ if (text.after_insert.graphemes.size() > 0)
+ {
+ std::vector<unicode_char> cut_text;
+
+ text.contents_cut(text.before_insert.graphemes.size()+1,
+ cut_text);
+
+ std::vector<unicode_char> before, after;
+ text.get_contents(before, after);
+
+ before.insert(before.end(), cut_text.begin(), cut_text.end());
+
+ text.set_contents(before, after);
+ }
+ setselectpos();
+ draw();
+}
+
+void CursesField::setCursorPos(size_t o)
+{
+ text.to_position(o);
+ setselectpos();
+ draw();
+}
diff --git a/curses/cursesfield.H b/curses/cursesfield.H
new file mode 100644
index 0000000..164ae4c
--- /dev/null
+++ b/curses/cursesfield.H
@@ -0,0 +1,160 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesfield_H
+#define cursesfield_H
+
+#include "mycurses.H"
+#include "cursesflowedline.H"
+#include <wchar.h>
+#include <vector>
+
+///////////////////////////////////////////////////////////////////////////
+//
+// An editable field.
+//
+
+class CursesField : public Curses {
+
+ editablewidechar text; // Editable text.
+
+ void getbeforeafter(widecharbuf &, widecharbuf &);
+
+ void setselectpos();
+
+ size_t width; // Field width
+ size_t maxlength; // Maximum text length
+
+ size_t shiftoffset; // First text char shown
+ size_t selectpos; // Start of selection position
+
+ unicode_char passwordChar; // password overlay character
+ bool yesnoField; // True if yes/no field.
+
+ std::vector<unicode_char> optionField; // One character list of options
+
+ std::vector<std::pair<std::string, std::string> > optionHelp; // Option help keys
+ // This field is of primary use by cursesstatusbar
+
+ bool isUnderlined;
+protected:
+ Curses::CursesAttr attribute;
+public:
+ static std::vector<unicode_char> yesKeys, noKeys;
+ static unicode_char yankKey, clrEolKey;
+
+ static std::list<CursesFlowedLine> cutBuffer; // for cut/paste.
+
+ CursesField(CursesContainer *parent,
+ size_t widthArg=20,
+ size_t maxlengthArg=255,
+ std::string initValue="");
+ ~CursesField();
+
+ void setUnderlined(bool flag) { isUnderlined=flag; }
+ void setRow(int row);
+ void setCol(int col);
+ void setText(std::string textArg);
+ void setAttribute(Curses::CursesAttr attr);
+ void setCursorPos(size_t o);
+ std::string getText() const; // Return entered text
+
+ int getWidth() const; // Our width is known
+ int getHeight() const; // We're one row high
+
+ void setWidth(int);
+
+ void setPasswordChar(unicode_char ch='*') { passwordChar=ch; }
+ void setYesNo() { yesnoField=true; }
+ void setOptionField(const std::vector<unicode_char> &vec)
+ {
+ optionField=vec;
+ }
+
+ void setOptionHelp(const std::vector< std::pair<std::string, std::string> > &help);
+
+ const std::vector< std::pair<std::string, std::string> > &getOptionHelp() const
+ {
+ return optionHelp;
+ }
+
+ void draw();
+
+ int getCursorPosition(int &row, int &col);
+ bool processKeyInFocus(const Key &key);
+
+ bool isFocusable(); // Yes we are
+ void focusGained(); // Select old text automatically at entry.
+ void focusLost(); // Unselect any selected text at exit.
+
+ void erase();
+private:
+ void left();
+ void right();
+};
+
+//////////////////////////////////////////////////////////////////////////
+//
+// Instead of subclassing a CursesField, provide a template to make it easier
+// to have CursesField be a member of another object. Typical usage:
+//
+// class X {
+//
+// CursesFieldRedirect<X> field1;
+// CursesFieldRedirect<X> field2;
+//
+// void field1Enter(); // ENTER key pressed in field1
+// void field2Enter(); // ENTER key pressed in field2
+//
+// };
+//
+//
+// X::X()
+// {
+//
+// field1=this;
+// field2=this;
+//
+// field1= &X::field1Enter;
+// field2= &X::field2Enter;
+// }
+//
+
+template<class T> class CursesFieldRedirect : public CursesField {
+
+public:
+ T *myClass;
+ void (T::*myMethod)(void);
+
+ CursesFieldRedirect(CursesContainer *parent,
+ size_t widthArg=20,
+ size_t maxlengthArg=255,
+ std::string initValue="")
+ : CursesField(parent, widthArg, maxlengthArg, initValue),
+ myClass(0), myMethod(0)
+ {
+ }
+
+ ~CursesFieldRedirect()
+ {
+ }
+
+ void operator=(T *p) { myClass=p; }
+ void operator=( void (T::*p)(void) ) { myMethod=p; }
+
+ bool processKeyInFocus(const Key &key)
+ {
+ if (key == Key::ENTER && myClass && myMethod)
+ {
+ (myClass->*myMethod)();
+ return true;
+ }
+
+ return CursesField::processKeyInFocus(key);
+ }
+};
+
+#endif
diff --git a/curses/cursesfilereq.C b/curses/cursesfilereq.C
new file mode 100644
index 0000000..1d2aac4
--- /dev/null
+++ b/curses/cursesfilereq.C
@@ -0,0 +1,808 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesfilereq.H"
+#include "cursesmainscreen.H"
+
+#include <algorithm>
+
+#include <sstream>
+#include <string.h>
+#include <pwd.h>
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <sys/types.h>
+#if HAVE_DIRENT_H
+#include <dirent.h>
+#define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+#define dirent direct
+#define NAMLEN(dirent) (dirent)->d_namlen
+#if HAVE_SYS_NDIR_H
+#include <sys/ndir.h>
+#endif
+#if HAVE_SYS_DIR_H
+#include <sys/dir.h>
+#endif
+#if HAVE_NDIR_H
+#include <ndir.h>
+#endif
+#endif
+#include <sys/stat.h>
+
+#if HAVE_GLOB_H
+#include <glob.h>
+#endif
+
+CursesFileReq::Dirlist::Dirlist(CursesFileReq *parentArg)
+ : CursesVScroll(parentArg), parent(parentArg), currentRow(-1)
+{
+}
+
+CursesFileReq::Dirlist::~Dirlist()
+{
+}
+
+int CursesFileReq::Dirlist::getHeight() const
+{
+ int h=parent->getHeight();
+ int r=getRow();
+
+ return (r < h ? h-r:1);
+}
+
+int CursesFileReq::Dirlist::getWidth() const
+{
+ return (parent->getWidth());
+}
+
+void CursesFileReq::Dirlist::draw()
+{
+ size_t n, wh;
+
+ getVerticalViewport(n, wh);
+
+ size_t h=directory.size() / 2;
+
+ size_t i;
+
+ for (i=0; i<wh; i++)
+ {
+ size_t p=n+i;
+
+ if (p < h)
+ drawItem(p);
+
+ if (p + h < directory.size())
+ drawItem(p+h);
+ }
+}
+
+void CursesFileReq::Dirlist::drawItem(size_t n)
+{
+ if (n >= directory.size())
+ return;
+
+ size_t h=directory.size()/2;
+
+ size_t w=getWidth()/2;
+
+ if (w > 2)
+ --w;
+
+ if (w <= 0)
+ w=1;
+
+ size_t rowNum=n;
+ size_t colNum=0;
+
+ if (rowNum >= h)
+ {
+ rowNum -= h;
+ colNum = w+2;
+ }
+
+ std::pair<std::vector<unicode_char>, size_t> nameW;
+
+ {
+ widecharbuf wc;
+
+ wc.init_string(directory[n].name);
+ wc.expandtabs(0);
+ nameW=wc.get_unicode_truncated(w, 0);
+ }
+
+ std::string s= (*CursesFileReq::fmtSizeFunc)(directory[n].size,
+ directory[n].isDir);
+
+ std::pair<std::vector<unicode_char>, size_t> sizeW;
+
+ sizeW.second=0;
+ if (nameW.second < w)
+ {
+ widecharbuf wc;
+
+ wc.init_string(s);
+ wc.expandtabs(0);
+ sizeW=wc.get_unicode_truncated(w-nameW.second, 0);
+ }
+
+ nameW.first.insert(nameW.first.end(), w - nameW.second - sizeW.second,
+ ' ');
+
+ nameW.first.insert(nameW.first.end(), sizeW.first.begin(),
+ sizeW.first.end());
+
+ writeText(nameW.first, rowNum, colNum,
+ CursesAttr().setReverse(currentRow >= 0 &&
+ (size_t)currentRow == n));
+}
+
+void CursesFileReq::Dirlist::operator=(std::vector<Direntry> &directoryArg)
+{
+ directory=directoryArg;
+ currentRow= -1;
+ scrollTo(0);
+ draw();
+}
+
+int CursesFileReq::Dirlist::getCursorPosition(int &row, int &col)
+{
+ if (currentRow < 0)
+ {
+ row=0;
+ col=0;
+ CursesVScroll::getCursorPosition(row, col);
+ return 0;
+ }
+
+ size_t h=directory.size()/2;
+
+ size_t w=getWidth()/2;
+
+ if (w > 2)
+ --w;
+
+ if (w <= 0)
+ w=1;
+
+ row=currentRow;
+ col=w-1;
+
+ if ( (size_t)currentRow >= h)
+ {
+ row -= h;
+ col += w+2;
+ }
+ CursesVScroll::getCursorPosition(row, col);
+ return 0;
+}
+
+bool CursesFileReq::Dirlist::isFocusable()
+{
+ return true;
+}
+
+bool CursesFileReq::Dirlist::processKeyInFocus(const Key &key)
+{
+ if (key == key.UP)
+ {
+ if (currentRow == 0)
+ {
+ Curses::transferPrevFocus();
+ return true;
+ }
+
+ --currentRow;
+ drawItem(currentRow);
+ drawItem(currentRow+1);
+ return true;
+ }
+
+ if (key == key.DOWN)
+ {
+ if ((size_t)currentRow + 1 >= directory.size())
+ {
+ Curses::transferNextFocus();
+ return true;
+ }
+
+ ++currentRow;
+ drawItem(currentRow);
+ drawItem(currentRow-1);
+ return true;
+ }
+
+ if (key == key.PGUP)
+ {
+ size_t h=getHeight();
+
+ size_t oldRow=currentRow;
+
+ currentRow=h < (size_t)currentRow ? currentRow-h:0;
+
+ drawItem(oldRow);
+ drawItem(currentRow);
+ return true;
+ }
+
+ if (key == key.PGDN)
+ {
+ size_t h=getHeight();
+
+ size_t oldRow=currentRow;
+
+ currentRow=currentRow + h < directory.size() ? currentRow+h:
+ directory.size() > 0 ? directory.size()-1:0;
+
+ drawItem(oldRow);
+ drawItem(currentRow);
+ return true;
+ }
+
+ if (key == key.LEFT)
+ {
+ size_t h=directory.size() / 2;
+
+ if ((size_t)currentRow >= h && (size_t)currentRow - h < h)
+ {
+ currentRow -= h;
+ drawItem(currentRow);
+ drawItem(currentRow+h);
+ }
+ return true;
+ }
+
+ if (key == key.RIGHT)
+ {
+ size_t h=directory.size() / 2;
+
+ if ((size_t)currentRow < h &&
+ (size_t)currentRow + h < directory.size())
+ {
+ currentRow += h;
+ drawItem(currentRow);
+ drawItem(currentRow-h);
+ }
+ return true;
+ }
+
+ if (key == key.ENTER)
+ {
+ if (currentRow != -1 &&
+ (size_t)currentRow < directory.size())
+ parent->select(directory[currentRow]);
+ return true;
+ }
+
+ return false;
+}
+
+// Must override getPrevFocus/getNextFocus in CursesContainer, because we
+// do not have any children and should behave like Curses.
+
+Curses *CursesFileReq::Dirlist::getPrevFocus()
+{
+ return Curses::getPrevFocus();
+}
+
+Curses *CursesFileReq::Dirlist::getNextFocus()
+{
+ return Curses::getNextFocus();
+}
+
+void CursesFileReq::Dirlist::focusGained()
+{
+ currentRow=0;
+ draw();
+}
+
+void CursesFileReq::Dirlist::focusLost()
+{
+ currentRow= -1;
+ draw();
+}
+
+std::string CursesFileReq::currentDir;
+
+static std::string defaultSizeFunc(unsigned long size, bool isDir)
+{
+ if (isDir)
+ return " (Dir)";
+
+ std::string buffer;
+
+ {
+ std::ostringstream o;
+
+ o << " (" << size << ")";
+ buffer=o.str();
+ }
+
+ return buffer;
+}
+
+std::string (*CursesFileReq::fmtSizeFunc)(unsigned long, bool)= &defaultSizeFunc;
+
+CursesFileReq::CursesFileReq(CursesMainScreen *mainScreen,
+ const char *filenameLabelArg,
+ const char *directoryLabelArg)
+ : CursesContainer(mainScreen), CursesKeyHandler(PRI_DIALOGHANDLER),
+ directoryLabel(this, directoryLabelArg),
+ directoryName(this, ""),
+ filenameLabel(this, filenameLabelArg),
+ filenameField(this),
+ dirlist(this),
+ parent(mainScreen)
+{
+ filenameField=this;
+ filenameField= &CursesFileReq::filenameEnter;
+
+ int w=directoryLabel.getWidth();
+ int ww=filenameLabel.getWidth();
+
+ if (ww > w)
+ w=ww;
+
+ directoryLabel.setRow(0);
+ directoryLabel.setCol(w);
+ directoryLabel.setAlignment(directoryLabel.RIGHT);
+
+ filenameLabel.setRow(2);
+ filenameLabel.setCol(w);
+ filenameLabel.setAlignment(filenameLabel.RIGHT);
+
+ directoryName.setRow(0);
+ directoryName.setCol(w);
+
+ filenameField.setRow(2);
+ filenameField.setCol(w);
+
+ dirlist.setRow(4);
+
+ if (currentDir.size() == 0)
+ {
+ std::string homedir=getenv("HOME");
+
+ if (homedir.size() == 0)
+ {
+ struct passwd *pw=getpwuid(getuid());
+
+ if (pw)
+ homedir=pw->pw_dir;
+ else
+ homedir="";
+ }
+
+ currentDir=homedir;
+ }
+
+ doresized();
+ opendir();
+}
+
+int CursesFileReq::getWidth() const
+{
+ return parent->getWidth();
+}
+
+int CursesFileReq::getHeight() const
+{
+ return (parent->getHeight());
+}
+
+CursesFileReq::~CursesFileReq()
+{
+}
+
+bool CursesFileReq::isDialog() const
+{
+ return true;
+}
+
+void CursesFileReq::resized()
+{
+ doresized();
+ CursesContainer::resized();
+}
+
+void CursesFileReq::draw()
+{
+ CursesContainer::draw();
+}
+
+void CursesFileReq::doresized()
+{
+ int w=getWidth();
+
+ int c=filenameField.getCol();
+
+ filenameField.setWidth(c > w ? 1: w-c);
+
+ // If too narrow fo the full directory name, truncate it.
+
+ c=directoryName.getCol();
+
+ size_t dls=c < w ? w-c:1;
+
+ std::vector<unicode_char> directoryW;
+
+ mail::iconvert::convert(currentDir, unicode_default_chset(),
+ directoryW);
+
+ size_t directoryW_size;
+
+ {
+ widecharbuf wc;
+
+ wc.init_unicode(directoryW.begin(), directoryW.end());
+ wc.expandtabs(0);
+
+ wc.tounicode(directoryW);
+
+ directoryW_size=wc.wcwidth(0);
+ }
+
+ if (directoryW_size > dls)
+ {
+ std::vector<unicode_char>::iterator b, e, c, lastSlash;
+
+ b=directoryW.begin();
+ e=directoryW.end();
+
+ lastSlash=b;
+
+ for (c=b; c != e; )
+ {
+ if (*c++ == '/')
+ lastSlash=c;
+ }
+
+ std::vector<unicode_char> replVector;
+
+ replVector.push_back('/');
+ replVector.push_back('.');
+ replVector.push_back('.');
+ replVector.push_back('.');
+ replVector.push_back('/');
+
+ replVector.insert(replVector.end(), lastSlash, e);
+
+ size_t suffixSize;
+
+ {
+ widecharbuf wc;
+
+ wc.init_unicode(replVector.begin(), replVector.end());
+
+ suffixSize=wc.wcwidth(0);
+ }
+
+ if (suffixSize < dls)
+ {
+ widecharbuf wc;
+
+ wc.init_unicode(directoryW.begin(), lastSlash);
+
+ directoryW=wc.get_unicode_truncated(dls-suffixSize, 0)
+ .first;
+
+ lastSlash=b=directoryW.begin();
+ e=directoryW.end();
+
+ for (c=b; c != e; ++c)
+ {
+ if (*c == '/')
+ lastSlash=c;
+ }
+
+ }
+ else
+ {
+ lastSlash=directoryW.begin();
+ }
+
+ directoryW.erase(lastSlash, directoryW.end());
+
+ directoryW.insert(directoryW.end(),
+ replVector.begin(), replVector.end());
+ }
+
+ directoryName.setText(mail::iconvert::convert(directoryW,
+ unicode_default_chset()));
+}
+
+void CursesFileReq::requestFocus()
+{
+ filenameField.requestFocus();
+}
+
+void CursesFileReq::select(Direntry &d)
+{
+ if (d.isDir) // Open this directory.
+ {
+ currentDir = currentDir + "/" + d.name;
+ opendir();
+ Curses::erase();
+ doresized();
+ draw();
+ filenameField.requestFocus();
+ return;
+ }
+
+ std::vector<std::string> filenameList;
+
+ filenameList.push_back(currentDir + "/" + d.name);
+ selected(filenameList);
+}
+
+void CursesFileReq::expand(std::string pattern,
+ std::vector<std::string> &filenameList)
+{
+ filenameList.clear();
+
+#if HAVE_GLOB
+
+ glob_t pglob;
+
+ if (glob(pattern.c_str(), GLOB_ERR|GLOB_NOCHECK
+
+#ifdef GLOB_BRACE
+ | GLOB_BRACE
+#endif
+
+#ifdef GLOB_NOMAGIC
+ | GLOB_NOMAGIC
+#endif
+
+#ifdef GLOB_TILDE
+ | GLOB_TILDE
+#endif
+ , NULL, &pglob) == 0)
+ {
+ if (pglob.gl_pathc > 0)
+ {
+ try {
+ size_t i;
+
+ for (i=0; i<pglob.gl_pathc; i++)
+ {
+ filenameList
+ .push_back(washfname(pglob
+ .gl_pathv
+ [i]));
+ }
+ } catch (...) {
+ globfree(&pglob);
+ throw;
+ }
+ }
+ globfree(&pglob);
+ }
+
+#endif
+ if (filenameList.size() == 0)
+ {
+ filenameList.push_back(washfname(pattern));
+ }
+}
+
+
+void CursesFileReq::filenameEnter()
+{
+ std::string s=filenameField.getText();
+
+ if (s.size() == 0)
+ return;
+
+ // Relative path? Prepend current directory
+
+ if (s[0] != '/')
+ s=currentDir + "/" + s;
+
+ std::vector<std::string> filenameList;
+
+ expand(s, filenameList);
+
+ struct stat stat_buf;
+
+ if (filenameList.size() == 1 &&
+ stat(filenameList[0].c_str(), &stat_buf) == 0 &&
+ S_ISDIR(stat_buf.st_mode))
+ {
+ currentDir=filenameList[0];
+ filenameField.setText("");
+ opendir();
+ Curses::erase();
+ doresized();
+ draw();
+ filenameField.requestFocus();
+ return;
+ }
+
+
+ selected(filenameList);
+}
+
+bool CursesFileReq::processKey(const Curses::Key &key)
+{
+ if (key.plain() && key.ukey == '\x03')
+ {
+ abort();
+ return true;
+ }
+ return key.plain() && (unsigned char)key.ukey == key.ukey &&
+ (unsigned char)key.ukey < ' ';
+}
+
+void CursesFileReq::selected(std::vector<std::string> &filenameList)
+{
+ selectedFiles=filenameList;
+ keepgoing=false;
+}
+
+void CursesFileReq::abort()
+{
+ selectedFiles.clear();
+ keepgoing=false;
+}
+
+void CursesFileReq::opendir()
+{
+ currentDir=washfname(currentDir); // cleanup filename.
+
+ std::vector<Direntry> newDirList;
+
+ {
+ std::vector<std::string> filenames;
+
+ DIR *dirp= ::opendir(currentDir.c_str());
+
+ struct dirent *de;
+
+ try {
+ while (dirp && (de=readdir(dirp)) != NULL)
+ filenames.push_back(de->d_name);
+
+ if (dirp)
+ closedir(dirp);
+ } catch (...) {
+ if (dirp)
+ closedir(dirp);
+ throw;
+ }
+
+ // stat everything, figure out if its a dir or not
+
+ std::vector<std::string>::iterator b=filenames.begin(),
+ e=filenames.end();
+
+ while (b != e)
+ {
+ Direntry d;
+
+ d.name= *b;
+
+ struct stat statInfo;
+
+ std::string n=currentDir + "/" + *b++;
+
+ if (stat(n.c_str(), &statInfo) < 0)
+ continue;
+
+ d.size=statInfo.st_size;
+
+ d.isDir= S_ISDIR(statInfo.st_mode);
+
+ newDirList.push_back(d);
+ }
+ }
+
+ sort(newDirList.begin(), newDirList.end(), DirentryComparator());
+
+ dirlist=newDirList;
+}
+
+std::string CursesFileReq::washfname(std::string filename)
+{
+ filename += "/"; // Temporary thing.
+
+ std::string::iterator b, c, e;
+
+ b=filename.begin();
+ e=filename.end();
+ c=b;
+
+ // Combine multiple /s into one.
+
+ while (b != e)
+ {
+ if (*b != '/')
+ {
+ *c++ = *b++;
+ continue;
+ }
+
+ while (b != e && *b == '/')
+ b++;
+
+ *c++ = '/';
+ }
+
+ filename.erase(c, e);
+
+ b=filename.begin();
+ e=filename.end();
+ c=b;
+
+ while (b != e)
+ {
+ if (*b == '/')
+ {
+ if (e - b >= 3 &&
+ b[1] == '.' && b[2] == '/')
+ {
+ b += 2;
+ continue;
+ }
+ else if (e - b >= 4 &&
+ b[1] == '.' && b[2] == '.' && b[3] == '/')
+ {
+ b += 3;
+
+ while (c != filename.begin())
+ {
+ if (*--c == '/')
+ break;
+ }
+ continue;
+ }
+ }
+
+ *c++ = *b++;
+ }
+
+ if (c != filename.begin())
+ --c; // Trailing /
+
+ filename.erase(c, e);
+ return filename;
+}
+
+// Sort filename list. Directories first, then files.
+
+int CursesFileReq::DirentryComparator::group(const Direntry &d)
+{
+ if (d.name == ".")
+ return 0;
+
+ if (d.name == "..")
+ return 1;
+
+ if (d.isDir)
+ return 2;
+ return 3;
+}
+
+bool CursesFileReq::DirentryComparator::operator()(const Direntry &a,
+ const Direntry &b)
+{
+ int d=group(a) - group(b);
+
+ if (d != 0)
+ return (d < 0);
+
+ return (strcoll(a.name.c_str(), b.name.c_str()) < 0);
+}
+
+bool CursesFileReq::listKeys( std::vector< std::pair<std::string, std::string> > &list)
+{
+ return true;
+}
diff --git a/curses/cursesfilereq.H b/curses/cursesfilereq.H
new file mode 100644
index 0000000..da83a1f
--- /dev/null
+++ b/curses/cursesfilereq.H
@@ -0,0 +1,175 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesfilereq_H
+#define cursesfilereq_H
+
+#include "../curses/curses_config.h"
+
+#include "cursescontainer.H"
+#include "curseskeyhandler.H"
+#include "cursesfield.H"
+#include "curseslabel.H"
+#include "cursesvscroll.H"
+
+#include <vector>
+
+class CursesMainScreen;
+
+////////////////////////////////////////////////////////////////////////////
+//
+// A file requester dialog. It consists of an editable field, where file
+// or a directory name may be manually entered, and scrollable contents of
+// the current directory (and some miscellaneous labels, here and there)
+
+class CursesFileReq : public CursesContainer,
+ public CursesKeyHandler {
+
+ struct Direntry {
+ std::string name;
+ unsigned long size;
+ bool isDir;
+ };
+
+ // Sorting collator - directories go first.
+
+ class DirentryComparator {
+
+ static int group(const Direntry &d);
+
+ public:
+ bool operator()(const Direntry &a, const Direntry &b);
+ };
+
+
+ class Dirlist : public CursesVScroll {
+
+ CursesFileReq *parent;
+ std::vector<Direntry> directory; // True - this is a dir entry
+ int currentRow; // Current row where the cursor is
+
+ void drawItem(size_t n);
+ public:
+ Dirlist(CursesFileReq *parentArg);
+ ~Dirlist();
+
+ // This is a child of CursesFileReq, and its size is
+ // automatically extended to the bottom of its parent.
+ // Its width is the same as the parent's width.
+
+ int getHeight() const;
+ int getWidth() const;
+
+ bool isFocusable(); // Yes we are.
+
+ void focusGained(); // Move to the first row
+ void focusLost(); // Turn off cursor
+ void draw();
+
+ // Even though this is a CursesContainer subclass, its focus
+ // behavior must be the same as Curses's focus behavior
+ // (the default CursesContainer implementation doesn't work,
+ // because this object does not have any children).
+
+ Curses *getPrevFocus();
+ Curses *getNextFocus();
+
+ void operator=(std::vector<Direntry> &directoryArg);
+
+ int getCursorPosition(int &row, int &col);
+ bool processKeyInFocus(const Key &key);
+
+ };
+
+ CursesLabel directoryLabel;
+ CursesLabel directoryName;
+ CursesLabel filenameLabel;
+ CursesFieldRedirect<CursesFileReq> filenameField;
+
+ Dirlist dirlist; // Current directory's contents.
+
+ void doresized(); // Reset directoryName's contents
+
+ bool isDialog() const; // Yes we are
+
+ CursesMainScreen *parent; // My parent
+
+ void opendir(); // Open a new directory
+
+public:
+
+ // The current directory (persistent).
+ static std::string currentDir;
+
+ //
+ // An externally-provided function that converts a byte count to
+ // a displayable size (such as "14Kb")
+ //
+ // A default implementation is provided, but it can be overriden,
+ // if necessary.
+
+ static std::string (*fmtSizeFunc)(unsigned long size, bool isDir);
+
+ CursesFileReq(CursesMainScreen *mainScreen,
+ const char *filenameLabel="Filename: ",
+ const char *directoryLabel="Directory: ");
+ ~CursesFileReq();
+
+ // Provide a default filename
+
+ void setFilename(std::string n)
+ {
+ filenameField.setText(n);
+ }
+ void resized();
+ void draw();
+ void requestFocus();
+ int getWidth() const;
+ int getHeight() const;
+
+ // Select the following entry.
+
+ void select(Direntry &d);
+
+ // Callback function - file(s) have been selected.
+ // The default implementation saves the filenames in selectedFile,
+ // and sets Curses::keepgoing to false.
+ //
+ // More than one file can be selected using globs.
+
+ virtual void selected(std::vector<std::string> &);
+
+ // Callback function - file selection aborted.
+ // The default implementation sets selectedFiles to empty
+ // and sets Curses::keepgoing to false.
+ virtual void abort();
+
+private:
+ bool processKey(const Curses::Key &key);
+
+ void filenameEnter(); // Enter pressed in the filename field
+
+ std::vector<std::string> selectedFiles; // The selected files
+
+ //
+ // Standize the filename. Remove all references to "/./" and "/../"
+ //
+
+public:
+ static std::string washfname(std::string);
+
+ static void expand(std::string pattern,
+ std::vector<std::string> &filenameList);
+
+ bool listKeys( std::vector< std::pair<std::string, std::string> > &list);
+
+ std::vector<std::string> &getFilenameList()
+ {
+ return selectedFiles;
+ }
+};
+
+#endif
diff --git a/curses/cursesflowedline.H b/curses/cursesflowedline.H
new file mode 100644
index 0000000..c46fc6d
--- /dev/null
+++ b/curses/cursesflowedline.H
@@ -0,0 +1,32 @@
+/*
+** Copyright 2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesflowedline_H
+#define cursesflowedline_H
+
+#include <string>
+
+//
+// A line of text, a string, encoded in UTF-8, and a flag whether this line
+// flows (or wraps) into the next one.
+//
+
+class CursesFlowedLine {
+
+public:
+ std::string text;
+ bool flowed;
+
+ CursesFlowedLine(const std::string &textArg="", bool flowedArg=false)
+ : text(textArg), flowed(flowedArg)
+ {
+ }
+
+ CursesFlowedLine(const std::vector<unicode_char> &textArg,
+ bool flowedArg);
+};
+
+#endif
diff --git a/curses/curseskeyhandler.C b/curses/curseskeyhandler.C
new file mode 100644
index 0000000..3ca30bd
--- /dev/null
+++ b/curses/curseskeyhandler.C
@@ -0,0 +1,75 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "curseskeyhandler.H"
+
+#include <functional>
+#include <algorithm>
+
+using namespace std;
+
+list<CursesKeyHandler *> CursesKeyHandler::handlers;
+bool CursesKeyHandler::handlerListModified=true;
+
+CursesKeyHandler::CursesKeyHandler(int priorityArg)
+ : priority(priorityArg)
+{
+ list<CursesKeyHandler *>::iterator b=handlers.begin(),
+ e=handlers.end();
+
+ while (b != e)
+ {
+ if ( (*b)->priority > priorityArg)
+ break;
+ b++;
+ }
+
+ handlers.insert(b, 1, this);
+
+ handlerListModified=true;
+}
+
+CursesKeyHandler::~CursesKeyHandler()
+{
+ list<CursesKeyHandler *>::iterator me=
+ find_if(handlers.begin(), handlers.end(),
+ bind2nd(equal_to<CursesKeyHandler *>(), this));
+
+ handlers.erase(me);
+ handlerListModified=true;
+}
+
+bool CursesKeyHandler::listKeys( vector< pair<string, string> > &list)
+{
+ return false;
+}
+
+bool CursesKeyHandler::handle(const Curses::Key &key, Curses *focus)
+{
+ list<CursesKeyHandler *>::iterator b=handlers.begin(),
+ e=handlers.end();
+
+ while (b != e)
+ {
+ CursesKeyHandler *p= *b++;
+
+ if (p->priority >= 0 && focus)
+ {
+ if (focus->processKeyInFocus(key))
+ return true;
+ focus=NULL;
+ }
+
+ if ( p->processKey(key))
+ return true;
+ }
+
+ if (focus)
+ if (focus->processKeyInFocus(key))
+ return true;
+ return false;
+}
diff --git a/curses/curseskeyhandler.H b/curses/curseskeyhandler.H
new file mode 100644
index 0000000..7b95b5e
--- /dev/null
+++ b/curses/curseskeyhandler.H
@@ -0,0 +1,79 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef curseskeyhandler_H
+#define curseskeyhandler_H
+
+#include <list>
+
+#include "mycurses.H"
+
+#define PRI_STATUSOVERRIDEHANDLER -4 // Override even the status line
+#define PRI_STATUSHANDLER -3 // Status line
+#define PRI_DIALOGHANDLER -2 // Dialog
+#define PRI_PRISCREENHANDLER -1 // Normal screenwide handler,
+ // but prior to any focus handling
+#define PRI_SCREENHANDLER 0 // Normal screenwide handler
+#define PRI_DEFAULTCTRLCHANDLER 1 // CTRL-C handler
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// A list of prioritized key handlers. CursesKeyHandler objects are created
+// whenever a function key shortcut should be used. Each CursesKeyHandler
+// defines a processKey() method that returns true if it has processed the
+// received key input. The object should also implement listKeys(), to append
+// a list of keys it handles to the list argument.
+//
+// Each key handler has a defined priority. The handle() method runs
+// the processKey method of all defined handlers, in priority order, until
+// processKey returns true.
+// The processKeyInFocus method of the specified Curses object may be called
+// (if focus is not NULL), in the event that no key handler with a negative
+// priority processed the key (and if processKeyInFocus also doesn't handle
+// the key, any remaining non-negative keyhandlers are given a crack at this).
+
+class CursesKeyHandler {
+
+ int priority;
+
+public:
+ CursesKeyHandler(int priorityArg);
+ virtual ~CursesKeyHandler();
+
+ static bool handle(const Curses::Key &key, Curses *focus);
+ // Returns true if the key was consumed.
+
+protected:
+ virtual bool processKey(const Curses::Key &key)=0;
+
+ // Key handlers should subclass this and enumerate all the keys
+ // they handle. <key name, description> should be added to list.
+ // Subclass should return true to ignore the rest of keyhandlers.
+public:
+ virtual bool listKeys( std::vector< std::pair<std::string,
+ std::string> > &list);
+
+private:
+ static std::list<CursesKeyHandler *> handlers;
+
+public:
+ static std::list<CursesKeyHandler *>::const_iterator begin()
+ {
+ return handlers.begin();
+ }
+
+ static std::list<CursesKeyHandler *>::const_iterator end()
+ {
+ return handlers.end();
+ }
+
+ static bool handlerListModified;
+ // Reset to true each time a handler is added or removed, used to
+ // indicate when the status line should be redrawn
+
+};
+
+#endif
diff --git a/curses/curseslabel.C b/curses/curseslabel.C
new file mode 100644
index 0000000..fe49828
--- /dev/null
+++ b/curses/curseslabel.C
@@ -0,0 +1,96 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "curseslabel.H"
+
+using namespace std;
+
+CursesLabel::CursesLabel(CursesContainer *parent,
+ string textArg,
+ Curses::CursesAttr attributeArg)
+ : Curses(parent), attribute(attributeArg)
+{
+ setutext(textArg);
+}
+
+CursesLabel::~CursesLabel()
+{
+}
+
+void CursesLabel::setRow(int row)
+{
+ erase();
+ Curses::setRow(row);
+ draw();
+}
+
+void CursesLabel::setCol(int col)
+{
+ erase();
+ Curses::setCol(col);
+ draw();
+}
+
+void CursesLabel::setAlignment(Alignment newAlignment)
+{
+ erase();
+ Curses::setAlignment(newAlignment);
+ draw();
+}
+
+void CursesLabel::setAttribute(Curses::CursesAttr attr)
+{
+ attribute=attr;
+ draw();
+}
+
+void CursesLabel::setText(string textArg)
+{
+ erase();
+ setutext(textArg);
+ draw();
+}
+
+void CursesLabel::setutext(const std::string &textArg)
+{
+ std::vector<unicode_char> buf;
+
+ mail::iconvert::convert(textArg, unicode_default_chset(), buf);
+
+ widecharbuf wc;
+
+ wc.init_unicode(buf.begin(), buf.end());
+ wc.expandtabs(0);
+
+ wc.tounicode(utext);
+ w=wc.wcwidth(0);
+}
+
+int CursesLabel::getWidth() const
+{
+ return w;
+}
+
+int CursesLabel::getHeight() const
+{
+ return 1;
+}
+
+void CursesLabel::draw()
+{
+ writeText(utext, 0, 0, attribute);
+}
+
+void CursesLabel::erase()
+{
+ std::vector<unicode_char> s;
+
+ s.insert(s.end(), getWidth(), ' ');
+
+ writeText(s, 0, 0, CursesAttr());
+}
+
diff --git a/curses/curseslabel.H b/curses/curseslabel.H
new file mode 100644
index 0000000..574ff25
--- /dev/null
+++ b/curses/curseslabel.H
@@ -0,0 +1,47 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef curseslabel_H
+#define curseslabel_H
+
+#include "mycurses.H"
+
+////////////////////////////////////////////////////////////////////////
+//
+// A plain, garden variety, label. Centered, or right-aligned, perhaps.
+//
+// A lot of work, just for this
+
+class CursesLabel : public Curses {
+
+ std::vector<unicode_char> utext;
+
+ size_t w;
+
+ void setutext(const std::string &textArg);
+
+protected:
+ Curses::CursesAttr attribute;
+public:
+ CursesLabel(CursesContainer *parent,
+ std::string textArg,
+ Curses::CursesAttr attributeArg=Curses::CursesAttr());
+ ~CursesLabel();
+
+ void setRow(int row);
+ void setCol(int col);
+ void setAlignment(Alignment alignment);
+ void setAttribute(Curses::CursesAttr attr);
+ virtual void setText(std::string textArg);
+
+ int getWidth() const;
+ int getHeight() const;
+
+ void draw();
+ void erase();
+};
+
+#endif
diff --git a/curses/cursesmainscreen.C b/curses/cursesmainscreen.C
new file mode 100644
index 0000000..dae7539
--- /dev/null
+++ b/curses/cursesmainscreen.C
@@ -0,0 +1,76 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+
+#include "cursesmainscreen.H"
+
+CursesMainScreen::CursesMainScreen(CursesContainer *parent,
+ Curses *titleBarArg,
+ Curses *statusBarArg)
+ : CursesVScroll(parent), titleBar(titleBarArg),
+ statusBar(statusBarArg), lockcnt(0)
+{
+ setRow(titleBar->getHeight());
+}
+
+CursesMainScreen::~CursesMainScreen()
+{
+}
+
+int CursesMainScreen::getWidth() const
+{
+ return getParent()->getWidth();
+}
+
+int CursesMainScreen::getHeight() const
+{
+ int h=getParent()->getHeight();
+
+ int hh=titleBar->getHeight() + statusBar->getHeight();
+
+ return (hh < h ? h-hh:0);
+}
+
+CursesMainScreen::Lock::Lock(CursesMainScreen *p, bool noupdateArg)
+ : screen(p), noupdate(noupdateArg)
+{
+ if (p)
+ ++p->lockcnt;
+}
+
+CursesMainScreen::Lock::~Lock()
+{
+ if (!screen.isDestroyed())
+ {
+ if (--screen->lockcnt == 0 && !noupdate)
+ {
+ screen->erase();
+ screen->draw();
+ }
+ }
+}
+
+bool CursesMainScreen::writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const
+{
+ char dummy=0;
+
+ if (lockcnt > 0)
+ text= &dummy;
+
+ return CursesVScroll::writeText(text, row, col, attr);
+}
+
+bool CursesMainScreen::writeText(const std::vector<unicode_char> &text,
+ int row, int col,
+ const Curses::CursesAttr &attr) const
+{
+ std::vector<unicode_char> dummy;
+
+ return CursesVScroll::writeText(lockcnt > 0 ? dummy:text,
+ row, col, attr);
+}
diff --git a/curses/cursesmainscreen.H b/curses/cursesmainscreen.H
new file mode 100644
index 0000000..fd667f6
--- /dev/null
+++ b/curses/cursesmainscreen.H
@@ -0,0 +1,66 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesmainscreen_H
+#define cursesmainscreen_H
+
+#include "mycurses.H"
+#include "cursesvscroll.H"
+
+//
+// A CursesVScroll used for the main screen. It's parent is the CursesScreen,
+// so it's size is defined as the width of its parent, and its parent's
+// height minus the status bar height and the title bar height.
+//
+
+class CursesMainScreen : public CursesVScroll {
+
+ Curses *titleBar, *statusBar;
+
+ // A lock can be obtained that blocks out updates to the screen.
+ // This is useful when we know in advance about multiple changes to
+ // the screen that will occur over a period of time, which might be
+ // confusing. A Lock structure prevents changes to the main screen
+ // from taking place. Instantiating a Lock structure increments
+ // lockcnt. The destructor decrements lockcnt.
+ // WriteText() is overriden and will terminate without doing anything
+ // if lockcnt is non-zero.
+ // When lockcnt is decrement back to 0 the entire object is redrawn.
+
+ unsigned lockcnt;
+
+public:
+
+ class Lock {
+ cursesPtr<CursesMainScreen> screen;
+
+ Lock(const Lock &); // UNIMPLEMENTED
+ Lock &operator=(const Lock &); // UNIMPLEMENTED
+
+ bool noupdate;
+
+ public:
+ Lock(CursesMainScreen *, bool noupdateArg=false);
+ ~Lock();
+ };
+
+ friend class Lock;
+
+ CursesMainScreen(CursesContainer *parent,
+ Curses *titleBarArg,
+ Curses *statusBarArg);
+ ~CursesMainScreen();
+
+ int getWidth() const;
+ int getHeight() const;
+
+ bool writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const;
+ bool writeText(const std::vector<unicode_char> &text, int row, int col,
+ const Curses::CursesAttr &attr) const;
+};
+
+#endif
diff --git a/curses/cursesmoronize.C b/curses/cursesmoronize.C
new file mode 100644
index 0000000..9e3060c
--- /dev/null
+++ b/curses/cursesmoronize.C
@@ -0,0 +1,97 @@
+/*
+** Copyright 2003-2004, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesmoronize.H"
+
+bool CursesMoronize::enabled=false;
+
+static const unicode_char plain_169[]={ ')', 'C', '('};
+static const unicode_char plain_174[]={ ')', 'R', '('};
+static const unicode_char plain_177[]={ '-', '/', '+'};
+static const unicode_char plain_188[]={ ' ', '4', '/', '1'};
+static const unicode_char plain_189[]={ ' ', '2', '/', '1'};
+static const unicode_char plain_190[]={ ' ', '4', '/', '3'};
+static const unicode_char plain_8482[]={ ']', 'm', 't', '['};
+static const unicode_char plain_8592[]={ '-', '<'};
+static const unicode_char plain_8594[]={ '>', '-'};
+static const unicode_char plain_8220[]={ '`', '`'};
+static const unicode_char plain_8221[]={ '\'', '\''};
+static const unicode_char plain_8226[]={ ' ', '*', ' '};
+static const unicode_char plain_8230[]={ '.', '.', '.'};
+static const unicode_char plain_8211[]={ ' ', '-', '-', ' '};
+static const unicode_char plain_8212[]={ ' ', '-', '-', '-', ' '};
+
+
+static const unicode_char repl_169[]={ 169, 0};
+static const unicode_char repl_174[]={ 174, 0};
+static const unicode_char repl_177[]={ 177, 0};
+static const unicode_char repl_188[]={ 188, 0};
+static const unicode_char repl_189[]={ 189, 0};
+static const unicode_char repl_190[]={ 190, 0};
+static const unicode_char repl_8482[]={ 8482, 0};
+static const unicode_char repl_8592[]={ 8592, 0};
+static const unicode_char repl_8594[]={ 8594, 0};
+static const unicode_char repl_8220[]={ 8220, 0};
+static const unicode_char repl_8221[]={ 8221, 0};
+static const unicode_char repl_8226[]={ 8226, 0};
+static const unicode_char repl_8230[]={ 8230, 0};
+static const unicode_char repl_8211[]={ ' ', 8211, ' ', 0};
+static const unicode_char repl_8212[]={ ' ', 8212, ' ', 0};
+
+
+CursesMoronize::Entry CursesMoronize::moronizationList[] = {
+ { plain_169, 3, repl_169},
+ { plain_174, 3, repl_174},
+ { plain_177, 3, repl_177},
+ { plain_188, 4, repl_188},
+ { plain_189, 4, repl_189},
+ { plain_190, 4, repl_190},
+ { plain_8482, 4, repl_8482},
+ { plain_8592, 2, repl_8592},
+ { plain_8594, 2, repl_8594},
+ { plain_8220, 2, repl_8220},
+ { plain_8221, 2, repl_8221},
+ { plain_8226, 3, repl_8226},
+ { plain_8230, 3, repl_8230},
+ { plain_8211, 4, repl_8211},
+ { plain_8212, 5, repl_8212},
+ { NULL, 0, 0}};
+
+size_t CursesMoronize::moronize(const unicode_char *buf,
+ std::vector<unicode_char> &nreplaced)
+{
+ Entry *e=moronizationList;
+
+ nreplaced.clear();
+
+ while (e->keycode)
+ {
+ size_t i=0;
+
+ for (i=0; ; ++i)
+ {
+ if (i == e->keycodeLen)
+ {
+ if (e->replacements == 0)
+ return 0;
+
+ for (i=0; e->replacements[i]; ++i)
+ nreplaced.push_back(e->replacements[i]);
+ return e->keycodeLen;
+ }
+
+ if (i == e->keycodeLen || buf[i] == 0)
+ break;
+
+ if (e->keycode[i] != buf[i])
+ break;
+ }
+
+ ++e;
+ }
+ return 0;
+}
diff --git a/curses/cursesmoronize.H b/curses/cursesmoronize.H
new file mode 100644
index 0000000..c4d5371
--- /dev/null
+++ b/curses/cursesmoronize.H
@@ -0,0 +1,52 @@
+/*
+** Copyright 2003-2004, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesmoronize_H
+#define cursesmoronize_H
+
+#include "mycurses.H"
+
+///////////////////////////////////////////////////////////////////////////
+//
+// "Smart characters" processing, a.k.a. moronization
+// Example: Convert typed "1/4" to the ISO-8859-1 character for 1/4.
+//
+
+class CursesMoronize {
+
+public:
+
+
+ static size_t moronize(const unicode_char *buf,
+ std::vector<unicode_char> &nreplaced);
+ //
+ // 'buf' should be the characters just preceding the current cursor
+ // position, IN REVERSE ORDER. So, if the cursor is now:
+ //
+ // ...1/4_ _ marks the cursor position
+ //
+ // ... then 'buf' should be '4/1...'. buf can be as long as the
+ // caller wants. We search for a suitable replacement using
+ // strncmp
+ //
+ // If found a replacement, returns non-zero chars to replace, and
+ // set nreplaced to the replacement character
+
+ class Entry {
+ public:
+ const unicode_char *keycode;
+ size_t keycodeLen;
+ const unicode_char *replacements;
+ };
+
+ static bool enabled;
+ static Entry moronizationList[];
+
+ static const int max_keycode_len=5;
+
+};
+
+#endif
diff --git a/curses/cursesmultilinelabel.C b/curses/cursesmultilinelabel.C
new file mode 100644
index 0000000..5075914
--- /dev/null
+++ b/curses/cursesmultilinelabel.C
@@ -0,0 +1,126 @@
+/*
+** Copyright 2004, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesmultilinelabel.H"
+
+CursesMultilineLabel::CursesMultilineLabel(CursesContainer *parent,
+ std::string textArg,
+ Curses::CursesAttr attributeArg)
+ : Curses(parent), width(0), attribute(attributeArg)
+{
+ mail::iconvert::convert(textArg, unicode_default_chset(), text);
+}
+
+void CursesMultilineLabel::init()
+{
+ int w=getWidth();
+
+ if (w < 10)
+ w=10;
+
+ lines.clear();
+
+ std::back_insert_iterator< std::vector< std::vector<unicode_char> > >
+ insert_iter(lines);
+
+ unicodewordwrap(text.begin(), text.end(),
+ unicoderewrapnone(), insert_iter, w, true);
+}
+
+CursesMultilineLabel::~CursesMultilineLabel()
+{
+}
+
+void CursesMultilineLabel::setRow(int row)
+{
+ erase();
+ Curses::setRow(row);
+ draw();
+}
+
+void CursesMultilineLabel::setCol(int col)
+{
+ erase();
+ Curses::setCol(col);
+ draw();
+}
+
+void CursesMultilineLabel::setText(std::string newText)
+{
+ erase();
+ text.clear();
+ mail::iconvert::convert(newText, unicode_default_chset(), text);
+ init();
+ draw();
+}
+
+void CursesMultilineLabel::setAlignment(Alignment newAlignment)
+{
+ erase();
+ Curses::setAlignment(newAlignment);
+ draw();
+}
+
+void CursesMultilineLabel::setAttribute(Curses::CursesAttr attr)
+{
+ attribute=attr;
+ draw();
+}
+
+int CursesMultilineLabel::getWidth() const
+{
+ return width;
+}
+
+void CursesMultilineLabel::setWidth(int w)
+{
+ erase();
+ width=w;
+ init();
+ draw();
+}
+
+int CursesMultilineLabel::getHeight() const
+{
+ int n=(int)lines.size();
+
+ if (n <= 0)
+ n=1;
+ return n;
+}
+
+void CursesMultilineLabel::resized()
+{
+ erase();
+ init();
+ draw();
+}
+
+void CursesMultilineLabel::draw()
+{
+ erase();
+
+ size_t row=0;
+
+ for ( std::vector< std::vector<unicode_char> >::iterator
+ b(lines.begin()), e(lines.end()); b != e; ++b, ++row)
+ {
+ writeText(*b, row, 0, attribute);
+ }
+}
+
+void CursesMultilineLabel::erase()
+{
+ std::vector<unicode_char> uc;
+
+ uc.insert(uc.end(), getWidth(), ' ');
+
+ size_t i, n=getHeight();
+ for (i=0; i<n; i++)
+ writeText(uc, i, 0, attribute);
+}
+
diff --git a/curses/cursesmultilinelabel.H b/curses/cursesmultilinelabel.H
new file mode 100644
index 0000000..00ec206
--- /dev/null
+++ b/curses/cursesmultilinelabel.H
@@ -0,0 +1,54 @@
+/*
+** Copyright 2004-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesmultilinelabel_H
+#define cursesmultilinelabel_H
+
+#include "mycurses.H"
+
+////////////////////////////////////////////////////////////////////////
+//
+// A multiline label that's word-wrapped to a given width.
+// A plain, garden variety, label. Centered, or right-aligned, perhaps.
+//
+// A lot of work, just for this
+
+class CursesMultilineLabel : public Curses {
+
+ std::vector<unicode_char> text;
+
+ std::vector< std::vector<unicode_char> > lines;
+
+protected:
+ int width;
+
+ Curses::CursesAttr attribute;
+public:
+ void init(); // Subclass must call init() in constructor.
+
+ CursesMultilineLabel(CursesContainer *parent,
+ std::string textArg,
+ Curses::CursesAttr attributeArg
+ =Curses::CursesAttr());
+ ~CursesMultilineLabel();
+
+ void setRow(int row);
+ void setCol(int col);
+ void resized();
+ void setAlignment(Alignment alignment);
+ void setAttribute(Curses::CursesAttr attr);
+ virtual void setText(std::string textArg);
+
+ int getWidth() const;
+ int getHeight() const;
+
+ void setWidth(int w);
+
+ void draw();
+ void erase();
+};
+
+#endif
diff --git a/curses/cursesobject.C b/curses/cursesobject.C
new file mode 100644
index 0000000..155f492
--- /dev/null
+++ b/curses/cursesobject.C
@@ -0,0 +1,40 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesobject.H"
+
+using namespace std;
+
+cursesPtrBase::cursesPtrBase()
+{
+}
+
+cursesPtrBase::~cursesPtrBase()
+{
+}
+
+CursesObj::CursesObj()
+{
+}
+
+CursesObj::CursesObj(const CursesObj &o)
+{
+}
+
+CursesObj &CursesObj::operator=(const CursesObj &o)
+{
+ return *this;
+}
+
+CursesObj::~CursesObj()
+{
+ set<cursesPtrBase *>::iterator b=cursesBase.begin(),
+ e=cursesBase.end();
+
+ while (b != e)
+ (*b++)->ptrDestroyed();
+}
diff --git a/curses/cursesobject.H b/curses/cursesobject.H
new file mode 100644
index 0000000..377c539
--- /dev/null
+++ b/curses/cursesobject.H
@@ -0,0 +1,102 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesobject_H
+#define cursesobject_H
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Helper class that detects when the object being pointed to is destroyed.
+//
+// Subclass cursesObj. Declare CursesPtr<T>, where T is cursesObj's
+// subclass. After CursesObj is destroyed, CursesPtr<T>::operator T *()
+// will return NULL.
+//
+// The cursesObj object maintains a list of all CursesPtr<T>s that point to
+// it. cursesObj's destructor nulls them out.
+
+#include "../curses/curses_config.h"
+#include <set>
+#include <cstdio>
+
+class cursesPtrBase {
+public:
+ cursesPtrBase();
+ virtual ~cursesPtrBase();
+ virtual void ptrDestroyed()=0;
+};
+
+template<class T> class cursesPtr : public cursesPtrBase {
+
+ T *ptr;
+
+public:
+ cursesPtr(T *ptrArg) : ptr(NULL)
+ {
+ if (ptrArg)
+ ptrArg->cursesBase.insert(this);
+ ptr=ptrArg;
+ }
+
+ cursesPtr(const cursesPtr &o) : ptr(NULL)
+ {
+ (*this)=o;
+ }
+
+ cursesPtr &operator=(const cursesPtr &o)
+ {
+ if (o.ptr == NULL ||
+ o.ptr->cursesBase.count(this) == 0)
+ {
+ if (o.ptr)
+ o.ptr->cursesBase.insert(this);
+
+ if (ptr && ptr->cursesBase.count(this) > 0)
+ ptr->cursesBase.erase(ptr->cursesBase
+ .find(this));
+ }
+ ptr=o.ptr;
+
+ return *this;
+ }
+
+ ~cursesPtr()
+ {
+ if (ptr && ptr->cursesBase.count(this) > 0)
+ ptr->cursesBase.erase(ptr->cursesBase
+ .find(this));
+ }
+
+ operator T *() const
+ {
+ return ptr;
+ }
+
+ T * operator->() const
+ {
+ return ptr;
+ }
+
+ bool isDestroyed() const { return ptr == 0; }
+
+ void ptrDestroyed() { ptr=NULL; }
+
+};
+
+class CursesObj {
+
+public:
+ std::set<cursesPtrBase *> cursesBase;
+
+ CursesObj();
+ virtual ~CursesObj();
+
+ // UNDEFINED:
+ CursesObj(const CursesObj &);
+ CursesObj &operator=(const CursesObj &);
+};
+
+#endif
diff --git a/curses/cursesscreen.C b/curses/cursesscreen.C
new file mode 100644
index 0000000..0b0458a
--- /dev/null
+++ b/curses/cursesscreen.C
@@ -0,0 +1,885 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <iomanip>
+#include <iostream>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
+#endif
+#ifndef WIFEXITED
+#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
+#endif
+
+#include "cursesscreen.H"
+#include "cursesfield.H"
+
+static unsigned char termStopKey= 'Z' & 31;
+
+static RETSIGTYPE bye(int dummy)
+{
+ endwin();
+ kill( getpid(), SIGKILL);
+ _exit(0);
+}
+
+int Curses::getColorCount()
+{
+ if (has_colors())
+ return COLORS;
+ return 0;
+}
+
+
+CursesScreen::KeyReader::KeyReader()
+ : h(iconv_open(libmail_u_ucs4_native, unicode_default_chset())),
+ winput_cnt(0)
+{
+ if (h == (iconv_t)-1)
+ {
+ std::cerr << "Unable to initiale "
+ << unicode_default_chset()
+ << " mapping to UCS-4" << std::endl;
+ exit(1);
+ }
+}
+
+CursesScreen::KeyReader::~KeyReader()
+{
+ if (h != (iconv_t)-1)
+ iconv_close(h);
+}
+
+void CursesScreen::KeyReader::operator<<(char ch)
+{
+ input_buf.push_back(ch);
+
+ winput_buf.resize(winput_cnt+128);
+
+ while (input_buf.size() > 0)
+ {
+
+ char *inbuf=&input_buf[0], *outbuf=&winput_buf[winput_cnt];
+ size_t inbytesleft=input_buf.size(),
+ outbytesleft=winput_buf.size()-winput_cnt;
+
+ size_t res=iconv(h, &inbuf, &inbytesleft, &outbuf,
+ &outbytesleft);
+
+ if (inbuf != &input_buf[0])
+ input_buf.erase(input_buf.begin(),
+ input_buf.begin() +
+ (inbuf - &input_buf[0]));
+
+ winput_cnt=outbuf - &winput_buf[0];
+
+ if (res == (size_t)-1 && errno == EILSEQ)
+ {
+ if (input_buf.size() > 0)
+ input_buf.erase(input_buf.begin(),
+ input_buf.begin()+1);
+ continue;
+ }
+
+ if (res == (size_t)-1 && errno == E2BIG)
+ winput_buf.resize(winput_buf.size()+128);
+
+ if (res == (size_t)-1 && errno == EINVAL)
+ break;
+ }
+}
+
+bool CursesScreen::KeyReader::operator>>(unicode_char &ch)
+{
+ if (winput_cnt < sizeof(ch))
+ return false;
+
+ memcpy(&ch, &winput_buf[0], sizeof(ch));
+
+ winput_buf.erase(winput_buf.begin(),
+ winput_buf.begin()+sizeof(ch));
+ winput_cnt -= sizeof(ch);
+ return true;
+}
+
+CursesScreen::CursesScreen() : inputcounter(0)
+{
+ initscr();
+ start_color();
+
+#if HAS_USE_DEFAULT_COLORS
+ use_default_colors();
+#endif
+
+ // Assign meaningful colors
+
+ if (has_colors())
+ {
+ short f;
+ short b;
+
+ int i;
+
+ pair_content(0, &f, &b);
+
+ int colorcount=getColorCount();
+
+ if (colorcount > 0)
+ --colorcount;
+
+ for (i=1; i<COLOR_PAIRS; i++)
+ {
+ if (colorcount <= 1)
+ f=b=0;
+ else
+ {
+ f=(i / COLORS) % COLORS;
+ b=(i % COLORS);
+ }
+
+#if HAS_USE_DEFAULT_COLORS
+ if (b == 0)
+ b= -1;
+#endif
+ init_pair(i, f, b);
+ }
+ }
+
+ raw();
+ noecho();
+ nodelay(stdscr, true);
+ timeout(0);
+ nonl();
+ intrflush(stdscr, FALSE);
+ keypad(stdscr, TRUE);
+
+ // fcntl(0, F_SETFL, O_NONBLOCK);
+
+ signal(SIGHUP, bye);
+ signal(SIGTERM, bye);
+
+ save_w=COLS;
+ save_h=LINES;
+ shiftmode=false;
+
+ const char *underline_hack_env=getenv("UNDERLINE_HACK");
+
+ underline_hack=underline_hack_env && atoi(underline_hack_env);
+}
+
+CursesScreen::~CursesScreen()
+{
+ endwin();
+}
+
+int CursesScreen::getWidth() const
+{
+ return COLS;
+}
+
+int CursesScreen::getHeight() const
+{
+ return LINES;
+}
+
+void CursesScreen::draw()
+{
+ attroff(A_STANDOUT | A_BOLD | A_UNDERLINE | A_REVERSE);
+ clear();
+ CursesContainer::draw();
+}
+
+void CursesScreen::resized()
+{
+ CursesContainer::resized();
+ draw();
+}
+
+bool CursesScreen::writeText(const char *ctext, int row, int col,
+ const Curses::CursesAttr &attr) const
+{
+ std::vector<unicode_char> ubuf;
+
+ mail::iconvert::convert(ctext, unicode_default_chset(), ubuf);
+
+ return writeText(ubuf, row, col, attr);
+}
+
+//
+// Eliminate time-consuming expantabs() call in the hot writeText() codepath
+// by loading widecharbuf from an iterator that replaces tabs with a single
+// space character. writeText() should not get a string with tabs anyway.
+//
+// As a bonus, any NUL character gets also replaced with a space.
+//
+
+class CursesScreen::repltabs_spaces :
+ public std::iterator<std::random_access_iterator_tag,
+ const unicode_char> {
+
+ const unicode_char *p;
+
+ unicode_char tmp;
+
+public:
+ repltabs_spaces() : p(0) {}
+
+ repltabs_spaces(const unicode_char *pVal) : p(pVal) {}
+
+ bool operator==(const repltabs_spaces &v)
+ {
+ return p == v.p;
+ }
+
+ bool operator!=(const repltabs_spaces &v)
+ {
+ return p != v.p;
+ }
+
+ bool operator<(const repltabs_spaces &v)
+ {
+ return p < v.p;
+ }
+
+ bool operator<=(const repltabs_spaces &v)
+ {
+ return p <= v.p;
+ }
+
+ bool operator>(const repltabs_spaces &v)
+ {
+ return p < v.p;
+ }
+
+ bool operator>=(const repltabs_spaces &v)
+ {
+ return p <= v.p;
+ }
+
+ unicode_char operator*() const { return operator[](0); }
+
+ repltabs_spaces &operator++() { ++p; return *this; }
+
+ const unicode_char *operator++(int) { tmp=operator*(); ++p; return &tmp; }
+
+ repltabs_spaces &operator--() { --p; return *this; }
+
+ const unicode_char *operator--(int) { tmp=operator*(); --p; return &tmp; }
+
+ repltabs_spaces &operator+=(difference_type diff) { p += diff; return *this; }
+
+ repltabs_spaces &operator-=(difference_type diff) { p -= diff; return *this; }
+
+ repltabs_spaces operator+(difference_type diff) const { repltabs_spaces tmp=*this; tmp += diff; return tmp; }
+ repltabs_spaces operator-(difference_type diff) const { repltabs_spaces tmp=*this; tmp -= diff; return tmp; }
+
+ difference_type operator-(const repltabs_spaces b) const { return p-b.p; }
+
+ const unicode_char operator[](difference_type diff) const { return p[diff] == 0 || p[diff] == '\t' ? ' ':p[diff]; }
+};
+
+
+//
+// When writing EM and EN dashes, use the EM dash character, and write it out
+// multiple times. Corresponds to logic in widecharbuf::charwidth().
+//
+
+class CursesScreen::writetext_iter_helper
+ : public std::iterator<std::input_iterator_tag,
+ char, void, void, void> {
+
+ const unicode_char *uptr;
+ size_t multcnt;
+
+public:
+ writetext_iter_helper(const unicode_char *uptrArg)
+ : uptr(uptrArg), multcnt(0) {}
+
+ unicode_char operator*()
+ {
+ unicode_char c=*uptr;
+
+ if (c == 0x2014)
+ c=0x2013;
+ return c;
+ }
+
+ writetext_iter_helper &operator++()
+ {
+ if (multcnt == 0)
+ switch (*uptr) {
+ case 0x2013:
+ multcnt=2;
+ break;
+ case 0x2014:
+ multcnt=3;
+ break;
+ default:
+ multcnt=1;
+ }
+
+ if (--multcnt == 0)
+ ++uptr;
+ return *this;
+ }
+
+ writetext_iter_helper operator++(int)
+ {
+ writetext_iter_helper helper=*this;
+
+ operator++();
+ return helper;
+ }
+
+ bool operator==(const writetext_iter_helper &o) const
+ {
+ return uptr == o.uptr;
+ }
+
+ bool operator!=(const writetext_iter_helper &o) const
+ {
+ return !operator==(o);
+ }
+};
+
+
+
+bool CursesScreen::writeText(const std::vector<unicode_char> &utext,
+ int row, int col,
+ const Curses::CursesAttr &attr) const
+{
+ // Truncate text to fit within the display
+
+ if (row < 0 || row >= getHeight() || col >= getWidth())
+ return false;
+
+ widecharbuf wc;
+
+ {
+ repltabs_spaces ptr(utext.size() ? &*utext.begin():NULL);
+
+ wc.init_unicode(ptr, ptr+utext.size());
+ }
+
+ // Advance past any part of the script beyond the left margin
+
+ bool left_margin_crossed=false;
+
+ std::vector<widecharbuf::grapheme_t>::const_iterator
+ b(wc.graphemes.begin()), e(wc.graphemes.end());
+
+ if (b != e)
+ {
+ while (col < 0)
+ {
+ if (b == e)
+ return false;
+
+ col += b->wcwidth(col);
+ ++b;
+ left_margin_crossed=true;
+ }
+
+ if (col < 0 || b == e)
+ return false;
+ }
+
+ if (left_margin_crossed)
+ {
+ // Clear any cells that contain a partial graphemes that
+ // extends from beyond the left margin
+ move(row, 0);
+
+ for (int i=0; i<col; i++)
+ addstr(" ");
+ }
+ else
+ move(row, col);
+
+ int w=getWidth();
+
+ if (col >= w)
+ return false;
+
+ if (b == e)
+ return true;
+
+ size_t cells=w-col;
+
+ const unicode_char *uptr=b->uptr;
+ size_t ucnt=0;
+
+ bool right_margin_crossed=false;
+
+ while (b != e)
+ {
+ size_t grapheme_width=b->wcwidth(col);
+
+ if (grapheme_width > cells)
+ {
+ if (cells)
+ right_margin_crossed=true;
+ break;
+ }
+
+ ucnt += b->cnt;
+ cells -= grapheme_width;
+ col += grapheme_width;
+ ++b;
+ }
+
+ std::vector<wchar_t> text_buf;
+
+ {
+ std::string s;
+
+ mail::iconvert::fromu::convert(writetext_iter_helper(uptr),
+ writetext_iter_helper(uptr+ucnt),
+ unicode_default_chset(), s);
+
+ towidechar(s.begin(), s.end(), text_buf);
+ }
+ text_buf.push_back(0);
+
+ wchar_t *text=&text_buf[0];
+
+ int c=0;
+
+ // If color was requested, then the colors will wrap if the requested
+ // color number exceeds the # of colors reported by curses.
+
+ int ncolors=getColorCount();
+
+ if (ncolors > 2 && COLOR_PAIRS > 0)
+ {
+ int bg=attr.getBgColor();
+ int fg=attr.getFgColor();
+
+ if (fg || bg)
+ {
+ if (bg > 0)
+ bg= ((bg - 1) % (ncolors-1)) + 1;
+
+ fg %= ncolors;
+
+ if (fg == bg)
+ fg = (fg + ncolors/2) % ncolors;
+ }
+
+ c=bg + ncolors * fg;
+
+ c %= COLOR_PAIRS;
+ }
+
+ if (c > 0)
+ attron(COLOR_PAIR(c));
+
+ if (attr.getHighlight())
+ attron(A_BOLD);
+ if (attr.getReverse())
+ attron(A_REVERSE);
+
+ if (attr.getUnderline())
+ {
+ if (termattrs() & A_UNDERLINE)
+ {
+ if (underline_hack)
+ {
+ size_t i=0;
+
+ for (i=0; text[i]; i++)
+ if (text[i] == ' ')
+ text[i]=0xA0;
+ }
+ attron(A_UNDERLINE);
+ }
+ else
+ {
+ size_t i;
+
+ for (i=0; text[i]; i++)
+ if (text[i] == ' ')
+ text[i]='_';
+ }
+ }
+
+ addwstr(text);
+
+ if (c)
+ attroff(COLOR_PAIR(c));
+ attroff(A_STANDOUT | A_BOLD | A_UNDERLINE | A_REVERSE);
+
+ if (right_margin_crossed)
+ clrtoeol();
+
+ return true;
+}
+
+void CursesScreen::flush()
+{
+ refresh();
+}
+
+void (*Curses::suspendedHook)(void)=&Curses::suspendedStub;
+
+void Curses::suspendedStub(void)
+{
+}
+
+void Curses::setSuspendHook(void (*func)(void))
+{
+ suspendedHook=func;
+}
+
+Curses::Key CursesScreen::getKey()
+{
+ for (;;)
+ {
+ Key k=doGetKey();
+
+ if (k.plain())
+ {
+ if (k.plain() && k.ukey == termStopKey)
+ {
+ if (suspendCommand.size() > 0)
+ {
+ std::vector<const char *> argv;
+
+ const char *p=getenv("SHELL");
+
+ if (!p || !*p)
+ p="/bin/sh";
+
+ argv.push_back(p);
+ argv.push_back("-c");
+ argv.push_back(suspendCommand.c_str());
+ argv.push_back(NULL);
+ Curses::runCommand(argv, -1, "");
+ draw();
+ (*suspendedHook)();
+ continue;
+ }
+
+ endwin();
+ kill( getpid(), SIGSTOP );
+ draw();
+ refresh();
+ (*suspendedHook)();
+ continue;
+ }
+ }
+ else if (k == Key::RESIZE)
+ {
+ save_w=COLS;
+ save_h=LINES;
+
+ ::erase(); // old window
+ resized();
+ touchwin(stdscr);
+ flush();
+ draw();
+ flush();
+ }
+ return k;
+ }
+}
+
+int Curses::runCommand(std::vector<const char *> &argv,
+ int stdinpipe,
+ std::string continuePrompt)
+{
+ endwin();
+ if (continuePrompt.size() > 0)
+ {
+ std::cout << std::endl << std::endl;
+ std::cout.flush();
+ }
+
+ pid_t editor_pid, p2;
+
+#ifdef WIFSTOPPED
+
+ RETSIGTYPE (*save_tstp)(int)=signal(SIGTSTP, SIG_IGN);
+ RETSIGTYPE (*save_cont)(int)=signal(SIGCONT, SIG_IGN);
+#endif
+
+ editor_pid=fork();
+ int waitstat;
+
+ signal(SIGCHLD, SIG_DFL);
+
+ if (editor_pid < 0)
+ {
+#ifdef WIFSTOPPED
+ signal(SIGTSTP, save_tstp);
+ signal(SIGCONT, save_cont);
+#endif
+ beep();
+ return -1;
+ }
+
+ if (editor_pid == 0)
+ {
+ signal(SIGTSTP, SIG_DFL);
+ signal(SIGCONT, SIG_DFL);
+ if (continuePrompt.size() > 0)
+ {
+ dup2(1, 2);
+ }
+ signal(SIGINT, SIG_DFL);
+ if (stdinpipe >= 0)
+ {
+ dup2(stdinpipe, 0);
+ close(stdinpipe);
+ }
+ execvp(argv[0], (char **)&argv[0]);
+ kill(getpid(), SIGKILL);
+ _exit(1);
+ }
+
+ if (stdinpipe >= 0)
+ close(stdinpipe);
+
+ signal(SIGINT, SIG_IGN);
+
+ for (;;)
+ {
+
+#ifdef WIFSTOPPED
+
+ p2=waitpid(editor_pid, &waitstat, WUNTRACED);
+
+ if (p2 != editor_pid)
+ continue;
+
+ if (WIFSTOPPED(waitstat))
+ {
+ kill(getpid(), SIGSTOP);
+ kill(editor_pid, SIGCONT);
+ continue;
+ }
+#else
+
+ p2=wait(&waitstat);
+
+ if (p2 != editor_pid)
+ continue;
+#endif
+ break;
+ }
+
+ signal(SIGINT, SIG_DFL);
+
+#ifdef WIFSTOPPED
+ signal(SIGTSTP, save_tstp);
+ signal(SIGCONT, save_cont);
+#endif
+
+ if (continuePrompt.size() > 0)
+ {
+ char buffer[80];
+
+ std::cout << std::endl << continuePrompt;
+ std::cout.flush();
+ std::cin.getline(buffer, sizeof(buffer));
+ }
+
+ refresh();
+
+ return WIFEXITED(waitstat) ? WEXITSTATUS(waitstat):-1;
+}
+
+Curses::Key CursesScreen::doGetKey()
+{
+ // If, for some reason, a unicode key is available, take it
+
+ {
+ unicode_char uk;
+
+ if (keyreader >> uk)
+ return doGetPlainKey(uk);
+ }
+
+ // Position cursor first.
+
+ int row=getHeight();
+ int col=getWidth();
+
+ if (row > 0) --row;
+ if (col > 0) --col;
+
+ if (currentFocus != NULL)
+ {
+ int new_row=currentFocus->getHeight();
+ if (new_row > 0) --new_row;
+
+ int new_col=currentFocus->getWidth();
+ if (new_col > 0) --new_col;
+ curs_set(currentFocus->getCursorPosition(new_row, new_col));
+
+ if (new_row < row)
+ row=new_row;
+
+ if (new_col < col)
+ col=new_col;
+ }
+ else
+ curs_set(0);
+
+ move(row, col);
+
+ // As long as the bytes from the keyboard keep getting read,
+ // accumulate them.
+
+ while (1)
+ {
+ int k=getch();
+
+ if (k == ERR)
+ {
+ flush();
+
+ if (++inputcounter >= 100)
+ {
+ // Detect closed xterm without SIGHUP
+
+ if (write(1, "", 1) < 0)
+ bye(0);
+ inputcounter=0;
+ }
+
+ if (LINES != save_h || COLS != save_w)
+ return (Key(Key::RESIZE));
+
+ return Key((unicode_char)0);
+ }
+ inputcounter=0;
+
+#ifdef KEY_SUSPEND
+
+ if (k == KEY_SUSPEND)
+ k=termStopKey;
+#endif
+
+ static const struct {
+ int keyval;
+ const char *keycode;
+ } keys[]={
+
+#define K(v,c) { v, Curses::Key::c }
+
+ K(KEY_LEFT, LEFT),
+ K(KEY_RIGHT, RIGHT),
+ K(KEY_SLEFT, SHIFTLEFT),
+ K(KEY_SRIGHT, SHIFTRIGHT),
+ K(KEY_UP, UP),
+ K(KEY_DOWN, DOWN),
+ K(KEY_ENTER, ENTER),
+ K(KEY_PPAGE, PGUP),
+ K(KEY_NPAGE, PGDN),
+ K(KEY_HOME, HOME),
+ K(KEY_END, END),
+ K(KEY_SHOME, SHIFTHOME),
+ K(KEY_SEND, SHIFTEND),
+ K(KEY_RESIZE, RESIZE),
+ K(KEY_BACKSPACE, BACKSPACE),
+ K(KEY_DC, DEL),
+ K(KEY_EOL, CLREOL),
+#undef K
+ };
+
+ if (shiftmode)
+ {
+ switch (k) {
+ case KEY_PPAGE:
+ return (Key(Key::SHIFTPGUP));
+ case KEY_NPAGE:
+ return (Key(Key::SHIFTPGDN));
+ case KEY_UP:
+ return (Key(Key::SHIFTUP));
+ case KEY_DOWN:
+ return (Key(Key::SHIFTDOWN));
+ case KEY_LEFT:
+ k=KEY_SLEFT;
+ break;
+ case KEY_RIGHT:
+ k=KEY_SRIGHT;
+ break;
+ case KEY_HOME:
+ k=KEY_SHOME;
+ break;
+ case KEY_END:
+ k=KEY_SEND;
+ break;
+ default:
+ shiftmode=0;
+ }
+ }
+
+ size_t i;
+
+ for (i=0; i<sizeof(keys)/sizeof(keys[0]); i++)
+ if (keys[i].keyval == k)
+ return Key(keys[i].keycode);
+
+#if HAS_FUNCTIONKEYS
+ if (k >= KEY_F(0) && k <= KEY_F(63))
+ {
+ Key kk("FKEY");
+ kk.ukey=k-KEY_F(0);
+ return kk;
+ }
+#endif
+
+ if ( (unsigned char)k == k)
+ {
+ // Plain key
+
+ keyreader << k;
+
+ unicode_char uk;
+
+ if (keyreader >> uk)
+ return doGetPlainKey(uk);
+ }
+ }
+}
+
+Curses::Key CursesScreen::doGetPlainKey(unicode_char k)
+{
+ if (k == 0)
+ {
+ shiftmode= !shiftmode;
+
+ return Key(Key::SHIFT);
+ }
+
+ if (k == (unicode_char)CursesField::clrEolKey)
+ return Key(Key::CLREOL);
+
+ if (k == '\r')
+ return Key(Key::ENTER);
+
+ shiftmode=false;
+
+ return (Key(k));
+}
+
+void CursesScreen::beepError()
+{
+ beep();
+}
diff --git a/curses/cursesscreen.H b/curses/cursesscreen.H
new file mode 100644
index 0000000..e781020
--- /dev/null
+++ b/curses/cursesscreen.H
@@ -0,0 +1,94 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesscreen_H
+#define cursesscreen_H
+
+#include "../curses/curses_config.h"
+#include "../unicode/unicode.h"
+#include "cursescontainer.H"
+
+#if HAVE_NCURSESW_CURSES_H
+#include <ncursesw/curses.h>
+#else
+#include <curses.h>
+#endif
+
+#include <iconv.h>
+#include <vector>
+
+//
+// A libcurses implementation. A CursesScreen object is typically the
+// root object of the Curses hierarchy. The constructor initializes libcurses,
+// the destructor cleans it up.
+//
+
+class CursesScreen : public CursesContainer {
+
+ int save_w, save_h; // Fix some libcurses.a resizing bugs.
+
+ int inputcounter;
+
+ bool underline_hack;
+
+ // Read keyboard input, convert to unicode_chars
+
+ class KeyReader {
+
+ iconv_t h;
+
+ std::vector<char> input_buf;
+ std::vector<char> winput_buf;
+
+ size_t winput_cnt;
+
+ public:
+ KeyReader();
+ ~KeyReader();
+
+ void operator<<(char);
+
+ bool operator>>(unicode_char &);
+ };
+
+ KeyReader keyreader;
+
+ class repltabs_spaces;
+
+public:
+ CursesScreen();
+ ~CursesScreen();
+
+ // Calculate max size.
+ int getWidth() const;
+ int getHeight() const;
+
+ void flush();
+ void draw();
+
+ class writetext_iter_helper;
+
+ bool writeText(const char *text, int row, int col,
+ const Curses::CursesAttr &attr) const;
+
+ bool writeText(const std::vector<unicode_char> &text, int row, int col,
+ const Curses::CursesAttr &attr) const;
+
+ // Return keyboard input. Returns Key::nokey() if no keyboard input
+ // isavailable.
+ Key getKey();
+
+ void beepError();
+private:
+
+ Key doGetKey();
+ Key doGetPlainKey(unicode_char);
+
+public:
+ void resized();
+};
+
+#endif
diff --git a/curses/cursesstatusbar.C b/curses/cursesstatusbar.C
new file mode 100644
index 0000000..2f2cb22
--- /dev/null
+++ b/curses/cursesstatusbar.C
@@ -0,0 +1,781 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesstatusbar.H"
+#include "cursesscreen.H"
+#include "cursescontainer.H"
+
+#include <algorithm>
+
+std::string CursesStatusBar::extendedErrorPrompt;
+std::string CursesStatusBar::shortcut_next_key;
+std::string CursesStatusBar::shortcut_next_descr;
+unicode_char CursesStatusBar::shortcut_next_keycode= 'O' & 31;
+
+CursesStatusBar::CursesStatusBar(CursesScreen *parent) :
+ CursesContainer(parent), CursesKeyHandler(PRI_STATUSHANDLER),
+ busyCounter(0), parentScreen(parent), fieldActive(NULL),
+ currentShortcutPage(0)
+{
+ clearStatusTimer=this;
+ clearStatusTimer= &CursesStatusBar::clearstatus;
+ resetRow();
+}
+
+// Reposition the status bar after a wrapped text message appeared or
+// disappered.
+
+void CursesStatusBar::resetRow()
+{
+ int h=parentScreen->getHeight();
+ int hh=getHeight();
+
+ if (h > hh)
+ {
+ h -= hh;
+ setRow(h);
+ }
+ else
+ setRow(2);
+}
+
+CursesStatusBar::~CursesStatusBar()
+{
+ if (fieldActive)
+ {
+ delete fieldActive;
+ CursesKeyHandler::handlerListModified=true;
+ // Rebuild shortcuts
+ }
+}
+
+int CursesStatusBar::getWidth() const
+{
+ return getParent()->getWidth();
+}
+
+// The height is usually 3 rows, but may be larger when a wrapped message
+// is shown
+
+int CursesStatusBar::getHeight() const
+{
+ int r=3;
+
+ if (extendedErrorMsg.size() > 0)
+ r += extendedErrorMsg.size() + 1;
+
+ return r;
+}
+
+void CursesStatusBar::resized()
+{
+ resetRow();
+ CursesKeyHandler::handlerListModified=true; // Rebuild shortcuts
+ draw();
+}
+
+bool CursesStatusBar::progressWanted()
+{
+ if (extendedErrorMsg.size() > 0)
+ return false;
+
+ time_t now=time(NULL);
+
+ if (progressText.size() > 0 && progressTime == now)
+ return false; // Too soon
+
+ progressTime=now;
+ return true;
+}
+
+void CursesStatusBar::progress(std::string progress)
+{
+ progressText.clear();
+ mail::iconvert::convert(progress,
+ unicode_default_chset(),
+ progressText);
+
+ draw();
+ flush();
+}
+
+void CursesStatusBar::setStatusBarAttr(CursesAttr a)
+{
+ attrStatusBar=a;
+ draw();
+}
+
+void CursesStatusBar::setHotKeyAttr(CursesAttr a)
+{
+ attrHotKey=a;
+ draw();
+}
+
+void CursesStatusBar::setHotKeyDescr(CursesAttr a)
+{
+ attrHotKeyDescr=a;
+ draw();
+}
+
+
+void CursesStatusBar::draw()
+{
+ std::string spaces="";
+
+ // To save some time, call rebuildShortcuts() only when necessary
+
+ if (CursesKeyHandler::handlerListModified)
+ rebuildShortcuts();
+
+ static const char throbber[]={'|', '/', '-', '\\'};
+
+ size_t w=getWidth();
+
+ std::vector<unicode_char> statusLine=statusText;
+
+ statusLine.insert(statusLine.begin(), ' ');
+
+ if (busyCounter)
+ {
+ if (busyCounter > (int)sizeof(throbber))
+ busyCounter=1;
+ statusLine.insert(statusLine.begin(),
+ (unicode_char)throbber[busyCounter-1]);
+ }
+ else
+ statusLine.insert(statusLine.begin(),
+ ' ');
+
+ if (extendedErrorMsg.size() == 0)
+ statusLine.insert(statusLine.end(),
+ progressText.begin(),
+ progressText.end());
+
+
+ CursesAttr attr=attrStatusBar;
+
+ attr.setReverse();
+
+ {
+ widecharbuf wc;
+
+ wc.init_unicode(statusLine.begin(), statusLine.end());
+
+ wc.expandtabs(0);
+
+ std::pair<std::vector<unicode_char>, size_t>
+ trunc(wc.get_unicode_truncated(w, 0));
+
+ if (trunc.second < w)
+ trunc.first.insert(trunc.first.end(), w-trunc.second,
+ ' ');
+
+ writeText(trunc.first, getHeight()-3, 0, attr);
+
+ }
+
+ if (extendedErrorMsg.size() > 0)
+ {
+ statusLine.clear();
+ statusLine.insert(statusLine.begin(), w, ' ');
+ writeText(statusLine, 0, 0, attr);
+ size_t n;
+
+ for (n=0; n<extendedErrorMsg.size(); n++)
+ {
+ writeText(extendedErrorMsg[n],
+ n+1, 0, attrStatusBar);
+ }
+ }
+
+ // Clear the shortcut area.
+
+ statusLine.clear();
+ statusLine.insert(statusLine.begin(), w, ' ');
+
+ int r=1;
+
+ if (extendedErrorMsg.size() > 0)
+ r += extendedErrorMsg.size()+1;
+
+ writeText(statusLine, r, 0, CursesAttr());
+ writeText(statusLine, r+1, 0, CursesAttr());
+
+ // Draw the current shortcut page.
+
+ if (extendedErrorMsg.size() == 0 &&
+ currentShortcutPage < shortcuts.size())
+ {
+
+ std::vector<std::vector<
+ std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char> > > >
+ &shortcutPage= shortcuts[currentShortcutPage];
+
+ size_t i;
+
+ for (i=0; i < shortcutPage.size(); i++)
+ {
+ std::vector< std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char> > >
+ &column=shortcutPage[i];
+
+ size_t j;
+
+ for (j=0; j<column.size(); j++)
+ {
+ std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char> >
+ &row=column[j];
+
+ std::vector<unicode_char> cpy=row.first;
+
+ widecharbuf wc;
+
+ wc.init_unicode(cpy.begin(), cpy.end());
+ wc.expandtabs(0);
+
+ if (shortcuts.size() > 1 ||
+ row.first.size() > 1 ||
+ row.second.size() > 1)
+ {
+ size_t w=wc.wcwidth(0);
+
+ if (w < max_sc_nlen)
+ cpy.insert(cpy.begin(),
+ max_sc_nlen - w,
+ ' ');
+ }
+
+ CursesAttr h=attrHotKey;
+
+ writeText( cpy, j+1,
+ i * (max_sc_nlen + max_sc_dlen + 2),
+ h.setReverse());
+
+ CursesAttr a;
+
+ if (row.second.size() > 2 &&
+ row.second[0] == '/' &&
+ row.second[1] >= '0' &&
+ row.second[1] <= '9')
+ {
+ a.setBgColor(row.second[1] - '0');
+ row.second.erase(row.second.begin(),
+ row.second.begin()+2);
+ }
+ else
+ {
+ a=attrHotKeyDescr;
+ }
+
+ writeText( row.second, j+1,
+ i * (max_sc_nlen + max_sc_dlen + 2)
+ + max_sc_nlen+1, a);
+ }
+ }
+ }
+ CursesContainer::draw();
+}
+
+class CursesStatusBarShortcutSort {
+public:
+ bool operator()(std::pair<std::string, std::string> a,
+ std::pair<std::string, std::string> b)
+ {
+ return (strcoll( a.first.c_str(), b.first.c_str()) < 0);
+ }
+};
+
+//
+// Create a sorted key shortcut list from all installed keyhandlers.
+//
+
+static void createShortcuts(size_t &max_sc_nlen, size_t &max_sc_dlen,
+ std::vector< std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char> > > &keys_w)
+{
+ std::vector< std::pair<std::string, std::string> > keys;
+
+ std::list<CursesKeyHandler *>::const_iterator
+ kb=CursesKeyHandler::begin(),
+ ke=CursesKeyHandler::end();
+
+ while (kb != ke)
+ if ((*kb++)->listKeys(keys))
+ break;
+
+ sort(keys.begin(), keys.end(),
+ CursesStatusBarShortcutSort());
+
+ std::vector< std::pair<std::string, std::string> >::iterator
+ b=keys.begin(), e=keys.end();
+
+ while (b != e)
+ {
+ std::vector<unicode_char> first_w;
+ std::vector<unicode_char> second_w;
+
+ mail::iconvert::convert(b->first, unicode_default_chset(),
+ first_w);
+
+ mail::iconvert::convert(b->second, unicode_default_chset(),
+ second_w);
+
+ b++;
+
+ {
+ widecharbuf wc;
+
+ wc.init_unicode(first_w.begin(), first_w.end());
+ wc.expandtabs(0);
+
+ size_t w=wc.wcwidth(0);
+
+ if (w > max_sc_nlen)
+ max_sc_nlen=w;
+ }
+
+ {
+ std::vector<unicode_char>::iterator b=second_w.begin();
+
+ if (second_w.size() && second_w[0] == '/' &&
+ second_w[1] >= '0' &&
+ second_w[1] <= '9')
+ b += 2;
+
+ widecharbuf wc;
+
+ wc.init_unicode(b, second_w.end());
+
+ wc.expandtabs(0);
+
+ size_t w=wc.wcwidth(0);
+
+ if (w > max_sc_dlen)
+ max_sc_dlen=w;
+ }
+
+ keys_w.push_back(std::make_pair(first_w, second_w));
+ }
+}
+
+void CursesStatusBar::rebuildShortcuts()
+{
+ CursesKeyHandler::handlerListModified=false;
+
+ currentShortcutPage=0;
+
+ std::vector<unicode_char> nextpage_n;
+ std::vector<unicode_char> nextpage_d;
+
+ if (shortcut_next_key.size() == 0)
+ shortcut_next_key="^O";
+
+ if (shortcut_next_descr.size() == 0)
+ shortcut_next_descr="...mOre";
+
+ mail::iconvert::convert(shortcut_next_key, unicode_default_chset(),
+ nextpage_n);
+
+ mail::iconvert::convert(shortcut_next_descr, unicode_default_chset(),
+ nextpage_d);
+
+ {
+ widecharbuf wc;
+
+ wc.init_unicode(nextpage_n.begin(), nextpage_n.end());
+ wc.expandtabs(0);
+ max_sc_nlen=wc.wcwidth(0);
+ }
+
+ {
+ widecharbuf wc;
+
+ wc.init_unicode(nextpage_d.begin(), nextpage_d.end());
+ wc.expandtabs(0);
+ max_sc_dlen=wc.wcwidth(0);
+ }
+
+ std::vector< std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char> > > keys_w;
+
+ createShortcuts(max_sc_nlen, max_sc_dlen, keys_w);
+
+ // Does everything fit on one page?
+
+ size_t w=getWidth();
+
+ size_t ncols=w / (max_sc_nlen + 2 + max_sc_dlen);
+
+ if (ncols <= 0)
+ ncols=1;
+
+ shortcuts.clear();
+ bool multiplePages=false;
+
+ size_t n=keys_w.size();
+ size_t i=0;
+
+ while (i < n)
+ {
+ if (n - i > ncols * 2)
+ multiplePages=true;
+
+ std::vector< std::vector< std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char>
+ > > >
+ columns;
+
+ size_t j;
+
+ for (j=0; j < ncols; j++ )
+ {
+ std::vector< std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char> > >
+ column;
+
+ if (i < n)
+ {
+ column.push_back( keys_w[i++] );
+ }
+ else
+ {
+ std::vector<unicode_char> zero;
+
+ column.push_back(std::make_pair(zero, zero));
+ }
+
+ // Add ^O to last cell on each page.
+
+ if (j + 1 == ncols && multiplePages)
+ {
+ std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char> >
+ p=std::make_pair(nextpage_n,
+ nextpage_d);
+ column.push_back(p);
+ }
+ else if (i < n)
+ {
+ column.push_back( keys_w[i++] );
+ }
+ else
+ {
+ std::vector<unicode_char> zero;
+ column.push_back (make_pair(zero, zero));
+ }
+
+ columns.push_back(column);
+ }
+
+ shortcuts.push_back(columns);
+ }
+}
+
+void CursesStatusBar::status(std::string text, statusLevel level)
+{
+ size_t origSize;
+
+ if (((statusText.size() > 0 || extendedErrorMsg.size() > 0) &&
+ currentLevel > level) || fieldActive != NULL)
+ {
+ // Message too low of a priority
+
+ if (CursesKeyHandler::handlerListModified || busyCounter)
+ {
+ busyCounter=0;
+ draw(); // Update the list of shortcuts
+ }
+ return;
+ }
+
+ origSize=extendedErrorMsg.size();
+
+ busyCounter=0;
+
+ statusText.clear();
+ progressText.clear();
+ extendedErrorMsg.clear();
+
+ currentLevel=level;
+
+ if (origSize > 0)
+ parentScreen->resized();
+
+ size_t w=getWidth();
+
+ if (w > 10)
+ w -= 4;
+
+ {
+ std::vector<unicode_char> uc;
+
+ mail::iconvert::convert(text, unicode_default_chset(), uc);
+
+ extendedErrorMsg.clear();
+
+ std::back_insert_iterator
+ < std::vector< std::vector<unicode_char> > >
+ insert_iter(extendedErrorMsg);
+
+ unicodewordwrap(uc.begin(), uc.end(),
+ unicoderewrapnone(),
+ insert_iter, w, true);
+ }
+
+ switch (extendedErrorMsg.size()) {
+ case 0:
+ extendedErrorMsg.push_back(std::vector<unicode_char>());
+ // FALLTHROUGH
+ case 1:
+ statusText=extendedErrorMsg.front();
+ extendedErrorMsg.clear();
+ break;
+ default:
+ extendedErrorMsg.push_back(std::vector<unicode_char>());
+
+ {
+ std::vector<unicode_char> statusLine;
+
+ mail::iconvert::convert(extendedErrorPrompt,
+ unicode_default_chset(),
+ statusLine);
+
+ extendedErrorMsg.push_back(statusLine);
+ }
+
+ clearStatusTimer.cancelTimer();
+ statusText.clear();
+ parentScreen->resized();
+ return;
+ }
+
+ clearStatusTimer.cancelTimer();
+ draw();
+}
+
+void CursesStatusBar::clearstatus()
+{
+ if (fieldActive != NULL)
+ return;
+
+ statusText.clear();
+ progressText.clear();
+ busyCounter=0;
+ if (extendedErrorMsg.size() > 0)
+ {
+ extendedErrorMsg.clear();
+ parentScreen->resized();
+ return;
+ }
+ draw();
+}
+
+void CursesStatusBar::busy()
+{
+ ++busyCounter;
+ draw();
+}
+
+void CursesStatusBar::notbusy()
+{
+ busyCounter=0;
+ draw();
+}
+
+bool CursesStatusBar::processKey(const Curses::Key &key)
+{
+ if (key.plain() && key.ukey == shortcut_next_keycode)
+ {
+ if (++currentShortcutPage >= shortcuts.size())
+ currentShortcutPage=0;
+ draw();
+ return true;
+
+ }
+
+ if (extendedErrorMsg.size() == 0)
+ {
+ // Status line messages are erased ten seconds after
+ // the first key is received.
+
+ if (statusText.size() > 0 &&
+ clearStatusTimer.expired())
+ clearStatusTimer.setTimer(10);
+
+ return false;
+ }
+
+ clearstatus(); // Clear wrapped popup
+ return true;
+}
+
+bool CursesStatusBar::listKeys( std::vector< std::pair<std::string, std::string> > &list)
+{
+ if (!fieldActive)
+ return false;
+
+ list.insert(list.end(), fieldActive->getOptionHelp().begin(),
+ fieldActive->getOptionHelp().end());
+ return true;
+}
+
+CursesField *CursesStatusBar::createPrompt(std::string prompt, std::string initvalue)
+{
+ if (fieldActive) // Old prompt?
+ {
+ Curses *f=fieldActive;
+
+ fieldActive=NULL;
+ delete f;
+ }
+
+ // The following requestFocus() may end up invoking focusLost()
+ // method of another Curses object. That, in turn, _may_ run some
+ // code that creates another dialog prompt. We'd rather that this
+ // happen at THIS point. Otherwise, the second dialog prompt will
+ // now blow away this dialog.
+
+ dropFocus();
+
+ clearstatus();
+
+ size_t maxW=getWidth();
+
+ if (maxW > 20)
+ maxW -= 20;
+ else
+ maxW=0;
+
+ size_t textW;
+
+ {
+ std::vector<unicode_char> uc;
+
+ mail::iconvert::convert(prompt, unicode_default_chset(), uc);
+
+ widecharbuf wc;
+
+ wc.init_unicode(uc.begin(), uc.end());
+
+ std::pair<std::vector<unicode_char>, size_t>
+ fragment=wc.get_unicode_truncated(maxW, 0);
+
+ statusText=fragment.first;
+ textW=fragment.second;
+ }
+
+ size_t fieldSize=getWidth()-textW;
+
+ if (fieldSize > 2)
+ fieldSize -= 2;
+ else
+ fieldSize=1;
+
+ fieldActive=new Field(this, fieldSize, 255, initvalue);
+ if (!fieldActive)
+ return NULL;
+
+ fieldActive->setAttribute(attrStatusBar);
+
+ CursesKeyHandler::handlerListModified=true;
+ // Rebuild shortcuts
+
+ fieldActive->setCol(textW+2);
+ draw();
+ if (fieldActive)
+ fieldActive->requestFocus();
+ return fieldActive;
+}
+
+// Callback from Field, when ENTER is pressed
+
+void CursesStatusBar::fieldEnter()
+{
+ fieldValue=fieldActive->getText();
+ delete fieldActive;
+ fieldActive=NULL;
+ fieldAborted=false;
+ CursesKeyHandler::handlerListModified=true;
+ keepgoing=false;
+ clearstatus();
+}
+
+void CursesStatusBar::fieldAbort()
+{
+ fieldValue="";
+ delete fieldActive;
+ fieldActive=NULL;
+ fieldAborted=true;
+ CursesKeyHandler::handlerListModified=true;
+ keepgoing=false;
+ clearstatus();
+}
+
+//////////////////////////////////////////////////////////////////////
+//
+// The custom field
+
+CursesStatusBar::Field::Field(CursesStatusBar *parent, size_t widthArg,
+ size_t maxlengthArg,
+ std::string initValue)
+ : CursesField(parent, widthArg, maxlengthArg, initValue),
+ me(parent)
+{
+}
+
+CursesStatusBar::Field::~Field()
+{
+}
+
+bool CursesStatusBar::Field::processKeyInFocus(const Key &key)
+{
+ if ((key.plain() && key.ukey == '\t') ||
+ key == key.DOWN || key == key.UP ||
+ key == key.SHIFTDOWN || key == key.SHIFTUP ||
+ key == key.PGDN || key == key.PGUP ||
+ key == key.SHIFTPGDN || key == key.SHIFTPGUP)
+
+ return true; // No changing focus, please.
+
+ if (key.plain() && key.ukey == '\x03')
+ {
+ setText("");
+ me->fieldAbort();
+ return true;
+ }
+
+ if (key == key.ENTER)
+ {
+ me->fieldEnter();
+ return true;
+ }
+
+ return CursesField::processKeyInFocus(key);
+}
+
+bool CursesStatusBar::Field::writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const
+{
+ CursesAttr attr_cpy=attr;
+ attr_cpy.setReverse(!attr_cpy.getReverse());
+ attr_cpy.setUnderline(false);
+
+ return CursesField::writeText(text, row, col, attr_cpy);
+}
+
+bool CursesStatusBar::Field::writeText(const std::vector<unicode_char> &text,
+ int row, int col,
+ const Curses::CursesAttr &attr) const
+{
+ CursesAttr attr_cpy=attr;
+ attr_cpy.setReverse(!attr_cpy.getReverse());
+ attr_cpy.setUnderline(false);
+
+ return CursesField::writeText(text, row, col, attr_cpy);
+}
diff --git a/curses/cursesstatusbar.H b/curses/cursesstatusbar.H
new file mode 100644
index 0000000..50bc4c3
--- /dev/null
+++ b/curses/cursesstatusbar.H
@@ -0,0 +1,185 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesstatusbar_H
+#define cursesstatusbar_H
+
+#include "mycurses.H"
+#include "curseskeyhandler.H"
+#include "cursescontainer.H"
+#include "cursesfield.H"
+
+#include "timer.H"
+
+#include <string>
+#include <vector>
+#include <wchar.h>
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// The status bar - moves itself to the bottom of the screen. Occupies three
+// last lines: a one line status/help line, and two lines that list current
+// keyboard shortcuts.
+//
+// Status messages are prioritized, and only messages of the same or higher
+// priority level are shown. The clearstatus() clears the current message,
+// which is also automatically cleared 10 seconds after the first keypress
+//
+// Messages that cannot fit on one line are automatically word-wrapped, and
+// are shown in a larger popup that comes up from the bottom of the screen,
+// which must be cleared with a keypress
+//
+// The status bar can also be converted to a one-line input field, by calling
+// the createPrompt() method, which returns a pointer to the created
+// CursesField object (which may be further customized). The status bar
+// automatically destroys the CursesField abort when the input concludes
+// (by pressing ENTER, or by manually calling fieldAbort() ).
+//
+// The first character of the status line may be a busy-throbber (a rotating
+// line). The throbber must be animated by repeatedly calling the busy()
+// method (which should be kicked off a 1 second timer). The throbber is
+// cleared by clearstatus().
+//
+// The status bar also includes a summary of all installed KeyHandler
+// objects. If the list of available function keys exceeds the available
+// space, the list is shown in pieces, with a control key cycling through
+// to the next status key summary piece.
+
+class CursesScreen;
+
+class CursesStatusBar : public CursesContainer, public CursesKeyHandler {
+
+ std::vector<unicode_char> statusText;
+ std::vector<unicode_char> progressText;
+ time_t progressTime;
+
+ int busyCounter;
+
+ CursesScreen *parentScreen;
+
+ std::vector< std::vector<unicode_char> > extendedErrorMsg;
+
+ // The status bar is sometimes turned into a one-line field entry.
+ // This is done by creating a CursesField child, which is why
+ // CursesStatusBar subclasses CursesContainer.
+ // We need to subclass CursesField in order to fix up a few things...
+
+ class Field : public CursesField {
+ CursesStatusBar *me;
+ public:
+ Field(CursesStatusBar *parent, size_t widthArg,
+ size_t maxlengthArg,
+ std::string initValue);
+ ~Field();
+
+ bool processKeyInFocus(const Key &key);
+
+ bool writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const;
+ bool writeText(const std::vector<unicode_char> &text,
+ int row, int col,
+ const Curses::CursesAttr &attr) const;
+ };
+
+ Field *fieldActive; // Not NULL when input takes place
+
+ std::string fieldValue; // Entered input
+ bool fieldAborted; // Whether input was aborted
+
+ TimerRedirect<CursesStatusBar> clearStatusTimer;
+
+ CursesAttr attrStatusBar, attrHotKey, attrHotKeyDescr;
+
+public:
+ void fieldEnter(); // Accept input
+ void fieldAbort(); // Abort input
+private:
+
+ // Shortcut keys.
+
+ size_t max_sc_nlen; // Largest shortcut name
+ size_t max_sc_dlen; // Largest shortcut description
+
+ std::vector <std::vector <std::vector <
+ std::pair< std::vector<unicode_char>,
+ std::vector<unicode_char> > > > > shortcuts;
+
+ // A std::pair of name/description string, inside a two-element vector
+ // (the two-row column on the status bar), inside a vector of all
+ // columns that fit on one screen, inside a vector of all pages of
+ // shortcut descriptions
+
+ size_t currentShortcutPage; // Current page of shortcuts shown
+
+ void rebuildShortcuts(); // Rebuild the shortcuts array
+public:
+
+ static std::string shortcut_next_key;
+ static std::string shortcut_next_descr;
+ static unicode_char shortcut_next_keycode;
+
+ friend class Field;
+
+ // Status line priorities
+
+ enum statusLevel {
+ NORMAL, // Normal message
+ INPROGRESS, // Something's in progress
+ EXPUNGED, // Folder's been expunged
+ SYSERROR, // System error
+ DISCONNECTED, // Server dropped connection
+ SERVERERROR, // Other server error
+ LOGINERROR // Login error
+ };
+
+ CursesStatusBar(CursesScreen *parent);
+ ~CursesStatusBar();
+
+ static std::string extendedErrorPrompt; // Non empty - wrapped text
+
+ int getWidth() const;
+ int getHeight() const;
+
+ void setStatusBarAttr(CursesAttr);
+ void setHotKeyAttr(CursesAttr);
+ void setHotKeyDescr(CursesAttr);
+
+ void draw();
+ void resized();
+
+ void status(std::string statusText, statusLevel level=NORMAL);
+ void progress(std::string progress);
+ bool progressWanted();
+
+ void clearstatus();
+
+ void busy(); // Throbber animation
+
+ void notbusy(); // Not busy any more
+
+ // Create a one-line input field.
+
+ CursesField *createPrompt(std::string prompt, std::string initvalue="");
+
+ bool prompting() const { return fieldActive != NULL; }
+ bool promptAborted() const { return fieldAborted; }
+
+ std::string getPromptValue() const
+ {
+ return (fieldActive != NULL ? fieldActive->getText()
+ : fieldValue);
+ }
+
+private:
+ statusLevel currentLevel;
+
+ void resetRow();
+
+ bool processKey(const Curses::Key &key);
+ bool listKeys( std::vector< std::pair<std::string, std::string> > &list);
+};
+
+#endif
diff --git a/curses/cursestitlebar.C b/curses/cursestitlebar.C
new file mode 100644
index 0000000..c100dbc
--- /dev/null
+++ b/curses/cursestitlebar.C
@@ -0,0 +1,110 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursestitlebar.H"
+#include "cursescontainer.H"
+
+using namespace std;
+
+CursesTitleBar::CursesTitleBar(CursesContainer *parent, string titleArg)
+ : Curses(parent), title(titleArg)
+{
+ setRow(0);
+ setCol(0);
+}
+
+CursesTitleBar::~CursesTitleBar()
+{
+}
+
+int CursesTitleBar::getHeight() const
+{
+ return 1;
+}
+
+int CursesTitleBar::getWidth() const
+{
+ return getParent()->getWidth();
+}
+
+void CursesTitleBar::setTitles(string left, string right)
+{
+ leftTitle=left;
+ rightTitle=right;
+ draw();
+}
+
+void CursesTitleBar::setAttribute(CursesAttr attr)
+{
+ attribute=attr;
+ draw();
+}
+
+void CursesTitleBar::draw()
+{
+ size_t w=getWidth();
+
+ Curses *p=getParent();
+
+ if (p == NULL)
+ return;
+
+ // First wipe out the status line
+
+ string spaces="";
+
+ spaces.append(w, ' ');
+
+ CursesAttr rev=attribute;
+
+ rev.setReverse();
+
+ p->writeText(spaces, 0, 0, rev);
+
+ // Now, write out the text strings
+
+ p->writeText(leftTitle, 0, 1, rev);
+
+ size_t l;
+
+ {
+ widecharbuf wc;
+
+ wc.init_string(rightTitle);
+ wc.expandtabs(0);
+
+ l=wc.wcwidth(0);
+
+ if (l + 1< w)
+ {
+ std::vector<unicode_char> uc;
+
+ wc.tounicode(uc);
+
+ p->writeText(uc, 0, w - 1 - l, rev);
+ }
+ }
+
+ {
+ widecharbuf wc;
+
+ wc.init_string(title);
+ wc.expandtabs(0);
+
+ l=wc.wcwidth(0);
+
+ if (l < w)
+ {
+
+ std::vector<unicode_char> uc;
+
+ wc.tounicode(uc);
+
+ p->writeText(uc, 0, (w-l)/2, rev);
+ }
+ }
+}
diff --git a/curses/cursestitlebar.H b/curses/cursestitlebar.H
new file mode 100644
index 0000000..d77b19a
--- /dev/null
+++ b/curses/cursestitlebar.H
@@ -0,0 +1,40 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursestitlebar_H
+#define cursestitlebar_H
+
+#include "mycurses.H"
+
+#include <string>
+
+//
+// The title bar - moves itself to the first line on the parent screen,
+// centers the title.
+//
+
+class CursesTitleBar : public Curses {
+
+ std::string title;
+ std::string leftTitle;
+ std::string rightTitle;
+
+ CursesAttr attribute;
+
+public:
+ CursesTitleBar(CursesContainer *parent, std::string title);
+ ~CursesTitleBar();
+
+ void setTitles(std::string left, std::string right);
+
+ void setAttribute(CursesAttr attr);
+
+ int getWidth() const;
+ int getHeight() const;
+ void draw();
+};
+
+#endif
diff --git a/curses/cursesvscroll.C b/curses/cursesvscroll.C
new file mode 100644
index 0000000..f24e2ba
--- /dev/null
+++ b/curses/cursesvscroll.C
@@ -0,0 +1,148 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "cursesvscroll.H"
+
+CursesVScroll::CursesVScroll(CursesContainer *parent)
+ : CursesContainer(parent), firstRowShown(0)
+{
+}
+
+CursesVScroll::~CursesVScroll()
+{
+}
+
+int CursesVScroll::getCursorPosition(int &row, int &col)
+{
+ scrollTo(row);
+
+ row -= firstRowShown;
+
+ return (CursesContainer::getCursorPosition(row, col));
+}
+
+
+int CursesVScroll::getWidth() const
+{
+ return getScreenWidth();
+}
+
+void CursesVScroll::getVerticalViewport(size_t &first_row,
+ size_t &nrows)
+{
+ first_row=firstRowShown;
+ nrows=getHeight();
+}
+
+void CursesVScroll::erase()
+{
+ size_t w=getWidth(), h=getHeight();
+
+ std::vector<unicode_char> spaces;
+
+ spaces.insert(spaces.end(), w, ' ');
+
+ size_t i;
+
+ for (i=0; i<h; i++)
+ CursesContainer::writeText(spaces, i, 0, CursesAttr());
+}
+
+void CursesVScroll::deleteChild(Curses *child)
+{
+ CursesContainer::deleteChild(child);
+ scrollTo(0);
+}
+
+void CursesVScroll::scrollTo(size_t row)
+{
+ size_t h=getHeight();
+
+#if 0
+ if (row < 0 || (size_t)row < h)
+ {
+ if (firstRowShown > 0)
+ {
+ firstRowShown=0;
+ redraw();
+ }
+ }
+ else
+#endif
+ if ((size_t)row < firstRowShown)
+ {
+ if (row + 1 < firstRowShown)
+ firstRowShown=row;
+ else
+ {
+ if (h > 6)
+ {
+ firstRowShown=row+5;
+
+ if (firstRowShown >= h)
+ firstRowShown -= h;
+ else
+ firstRowShown=0;
+ }
+ else
+ firstRowShown=row;
+ }
+ redraw();
+ }
+ else if ((size_t)row >= firstRowShown + h)
+ {
+ if (firstRowShown + h < (size_t)row)
+ {
+ firstRowShown=row + 1 - h;
+ }
+ else
+ {
+ if (h > 6)
+ firstRowShown=row-5;
+ else
+ firstRowShown=row + 1 - h;
+ }
+ redraw();
+ }
+}
+
+bool CursesVScroll::writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const
+{
+ if (row < 0 ||
+ (size_t)row < firstRowShown ||
+ (size_t)row >= firstRowShown + getHeight())
+ return false; // This row is not shown
+
+ row -= firstRowShown;
+
+ return CursesContainer::writeText(text, row, col, attr);
+}
+
+bool CursesVScroll::writeText(const std::vector<unicode_char> &text,
+ int row, int col,
+ const Curses::CursesAttr &attr) const
+{
+ if (row < 0 ||
+ (size_t)row < firstRowShown ||
+ (size_t)row >= firstRowShown + getHeight())
+ return false; // Ditto.
+
+ row -= firstRowShown;
+
+ return CursesContainer::writeText(text, row, col, attr);
+}
+
+//
+// We just logically scrolled. Clear the window, redraw.
+//
+
+void CursesVScroll::redraw()
+{
+ erase();
+ draw();
+}
diff --git a/curses/cursesvscroll.H b/curses/cursesvscroll.H
new file mode 100644
index 0000000..d27342f
--- /dev/null
+++ b/curses/cursesvscroll.H
@@ -0,0 +1,71 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef cursesvscroll_H
+#define cursesvscroll_H
+
+#include "mycurses.H"
+#include "cursescontainer.H"
+
+//
+// A vertically-scrolling viewport. The subclass must define getWidth()
+// and getHeight().
+//
+
+class CursesVScroll : public CursesContainer {
+
+ size_t firstRowShown;
+
+public:
+ CursesVScroll(CursesContainer *parent);
+ ~CursesVScroll();
+
+ // When something is deleted from the viewport, jump the view port
+ // to the top
+
+ void deleteChild(Curses *child);
+
+ // Automatically scroll the viewport to following keyboard input.
+ // This is done by overriding getCursorPosition(), calling
+ // scrollTo(), then simply subtracting the topmost row shown.
+
+ int getCursorPosition(int &row, int &col);
+
+ //
+ // Adjust for vertical scroll position:
+ //
+
+ bool writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const;
+ bool writeText(const std::vector<unicode_char> &text,
+ int row, int col,
+ const Curses::CursesAttr &attr) const;
+
+ //
+ // Make sure the following row is visible right now.
+ //
+ void scrollTo(size_t rowNum);
+
+ // Intercept getVerticalViewport(), and return reality.
+
+ void getVerticalViewport(size_t &first_row,
+ size_t &nrows);
+
+ void erase();
+
+ int getWidth() const;
+ size_t getFirstRowShown() const { return firstRowShown; }
+ void setFirstRowShown(size_t rowNum)
+ {
+ firstRowShown=rowNum;
+ redraw();
+ }
+
+private:
+ void redraw();
+};
+
+#endif
diff --git a/curses/mycurses.H b/curses/mycurses.H
new file mode 100644
index 0000000..9a305a4
--- /dev/null
+++ b/curses/mycurses.H
@@ -0,0 +1,439 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef curses_H
+#define curses_H
+
+#include "../curses/curses_config.h"
+#include "../unicode/unicode.h"
+#include <string.h>
+
+#include <wchar.h>
+#include <string>
+#include <vector>
+
+#include "cursesobject.H"
+#include "widechar.H"
+
+class Curses;
+class CursesContainer;
+
+///////////////////////////////////////////////////////////////////////////
+//
+// A simple OO interface to libcurses.
+//
+// The Curses object is the common abstract superclass. All other objects
+// subclass from Curses.
+//
+// Curses objects are arranged in a tree hierarchy. The root of the tree
+// is the CursesScreen objects, which implements the WriteText() methods
+// that use libcurses. All Curses objects are anchored at some row/column
+// pair, relative to its hierarchy parent. The Curses object provides a
+// default WriteText() implementation that adds the given text position to
+// its row/column position, and then calls its parent's WriteText() method.
+//
+// Two WriteText methods are provided: one for text in the current
+// (possibly multibyte) character set; and one for wide-character text.
+//
+// The remaining methods generally follow a similar path - a default
+// Curses implementation that performs local processing, then calls its
+// parent's method.
+
+class Curses : public CursesObj {
+
+ // My parent, possibly NULL;
+
+ CursesContainer *parent;
+
+ int row, col; // My location.
+
+ // Comparison function compares Curses objects based on their
+ // location. This is used to determine input focus tabbing.
+
+ static bool childPositionCompareFunc(Curses *, Curses *);
+
+public:
+
+ // Curses object that receives keyboard input at this time.
+ // May be NULL
+
+ static Curses *currentFocus;
+
+ // Keepgoing is initialized to true. Some objects, (FileReq,
+ // StatusBar) reset it to false to indicate that they're done.
+ // keepgoing must be manually reset to true.
+
+ static bool keepgoing;
+
+ // Emulate SHIFT keys for keyboards that can't directly generate
+ // then. shiftmode is set to true if the previous keypress was the
+ // SHIFT keypress.
+
+ static bool shiftmode;
+
+ static std::string suspendCommand; // Run this instead of SIGSTOP
+
+ static int runCommand(std::vector<const char *> &argv,
+ int stdinpipe,
+ std::string continuePrompt);
+ // Disable curses, run the command, reenable curses
+
+ // CursesAttr encapsulates generic text attributes, for WriteText
+
+ class CursesAttr {
+
+ int bgcolor;
+ int fgcolor;
+ bool highlight;
+ bool reverse;
+ bool underline;
+ public:
+ CursesAttr() : bgcolor(0),
+ fgcolor(0),
+ highlight(0), reverse(0), underline(0)
+ {
+ }
+ ~CursesAttr()
+ {
+ }
+
+ CursesAttr &setBgColor(int c)
+ {
+ bgcolor=c;
+ return *this;
+ }
+
+ CursesAttr &setFgColor(int c)
+ {
+ fgcolor=c;
+ return *this;
+ }
+
+ CursesAttr &setHighlight(bool h=true)
+ {
+ highlight=h;
+ return *this;
+ }
+
+ CursesAttr &setReverse(bool r=1)
+ {
+ reverse=r;
+ return *this;
+ }
+
+ CursesAttr &setUnderline(bool u=1)
+ {
+ underline=u;
+ return *this;
+ }
+
+ int getBgColor() const { return bgcolor; }
+ int getFgColor() const { return fgcolor; }
+ bool getHighlight() const { return highlight; }
+ bool getReverse() const { return reverse; }
+ bool getUnderline() const { return underline; }
+ };
+
+ // Generic encapsulation of keyboard input. Keyboard input is either
+ // a key, wchar_t, or a special function key, like a cursor key.
+ // Rather than pull in all the baggage of curses.h, just to get
+ // the key definition, we define our own constants. Which also
+ // makes it possible to define magic keys that are not defined by
+ // curses.h
+
+ class Key {
+ public:
+ unicode_char ukey;
+ const char *keycode;
+
+ Key(unicode_char ch) : ukey(ch), keycode(0) {}
+
+ Key(const char *k) : ukey(0), keycode(k) {}
+
+ bool plain() const { return keycode == 0; }
+
+ bool nokey() const { return ukey == 0 && keycode == 0; }
+
+ bool fkey() const { return keycode &&
+ strcmp(keycode, "FKEY") == 0; }
+ // This is a function key
+
+ int fkeynum() const { return (int)ukey; }
+
+ static const char LEFT[],
+ RIGHT[],
+ SHIFTLEFT[],
+ SHIFTRIGHT[],
+ UP[],
+ DOWN[],
+ SHIFTUP[],
+ SHIFTDOWN[],
+ DEL[],
+ CLREOL[],
+ BACKSPACE[],
+ ENTER[],
+ PGUP[],
+ PGDN[],
+ SHIFTPGUP[],
+ SHIFTPGDN[],
+ HOME[],
+ END[],
+ SHIFTHOME[],
+ SHIFTEND[],
+ SHIFT[],
+ RESIZE[];
+
+ bool operator==(const char *p) const
+ {
+ return keycode != 0 && strcmp(keycode, p) == 0;
+ }
+
+#if 0
+ bool operator==(unicode_char k) const
+ {
+ return keycode == 0 && key == k;
+ }
+#endif
+
+ bool operator==(const Key &k) const
+ {
+ return strcmp(keycode ? keycode:"",
+ k.keycode ? k.keycode:"") == 0 &&
+ ukey == k.ukey;
+ }
+
+ bool operator==(const std::vector<unicode_char> &v) const;
+
+ bool operator!=(const char *p) const
+ {
+ return !operator==(p);
+ }
+
+#if 0
+ bool operator!=(wchar_t k) const
+ {
+ return !operator==(k);
+ }
+#endif
+ bool operator!=(const std::vector<unicode_char> &v) const
+ {
+ return !operator==(v);
+ }
+ };
+
+ // This humble function received and handles keyboard input.
+ // processKey() runs either an installed key handler, or runs
+ // currentFocus->processKeyInFocus(). When a file requester, or
+ // StatusBar input prompt is active, at exit keepgoing may be set
+ // to false (ENTER key closes the file requester or status bar input
+ // field).
+
+ static bool processKey(const Key &k);
+
+ // How to interpret Curses::col
+
+ enum Alignment {
+ LEFT, // col is the left corner
+ CENTER, // col is the center of this Curses object
+ RIGHT, // col is the right corner
+ PARENTCENTER}; // Ignore col, center this object in its parent
+
+ Curses(CursesContainer *parent=0);
+ virtual ~Curses();
+
+ // Subclasses must define getWidth() and getHeight() to provide their
+ // sizes.
+
+ virtual int getWidth() const=0;
+ virtual int getHeight() const=0;
+
+ // Children of CursesVScroll may not have their entire contents
+ // shown. To optimize their draw() implementation, instead of
+ // writing out their contents in entire, getVerticalViewport may
+ // be called to obtain the first line of this object that's actually
+ // viewable, and the total number of rows that are viewable.
+ // Calls to write text to lines outside this range will be no-oped.
+ // nrows may be 0, if this object is entirely off-screen.
+ //
+ // The default Curses implementation sets first_row to 0, and
+ // nrows to getHeight(), then calls the getVerticalViewPort() method
+ // of its parent. The results of parent's GetVerticalViewPort(),
+ // combined with this object's getRow() method, is used to potentially
+ // narrow down the first_row/nrows range. For example, if the parent's
+ // vertical viewport is rows 5-9, and this object's row is 4, and
+ // its height is 10, then the resulting first_row/nrows will be 1/5
+ // (instead of 0/10).
+
+ virtual void getVerticalViewport(size_t &first_row,
+ size_t &nrows);
+
+ // The default implementation of getScreenWidth()/getScreenHeight()
+ // walk up the parent chain, and invoke the hierarchy root's Curses
+ // object's getWidth/getHeight methods, which should reflect the
+ // should be the actual screen size.
+
+ int getScreenWidth() const;
+ int getScreenHeight() const;
+
+ // get/set row/col methods have an obvious default implementation,
+ // that some subclasses may override (the usual reason is to
+ // automatically redraw the curses object if it moves)
+
+ virtual int getRow() const;
+ virtual int getCol() const;
+ virtual void setRow(int row);
+ virtual void setCol(int col);
+
+ // The default implementation of scrollTo adds the supplied row number
+ // to the starting row of this curses object, and recursively
+ // calls the parent's scrollTo() method. CursesVScroll overrides
+ // this method to make sure that the indicated row is currently
+ // visible.
+
+ virtual void scrollTo(size_t row);
+
+ CursesContainer *getParent() { return (parent); }
+ const CursesContainer *getParent() const { return (parent); }
+ void setParent(CursesContainer *p) { parent=p; }
+
+ // If CursesContainer sees an isDialog() child, it will get drawn
+ // instead of all the other children. Uses by CursesFileReq, for
+ // example, to take over the display.
+
+ virtual bool isDialog() const;
+
+ // Find my dialog child
+
+ virtual Curses *getDialogChild() const;
+
+ // draw() must be subclassed to invoked WriteText() to actually
+ // print the Curses object's contents.
+
+ virtual void draw()=0;
+
+ // The default implementation of erase() calls getWidth()/getHeight()
+ // then writes out a bunch of whitespace to clear everything out.
+
+ virtual void erase();
+
+ // The default implementation of flush() calls the parent's flush()
+ // method. The top level CursesScreen object calls libcurses.a
+ // instead.
+
+ virtual void flush();
+
+ // The screen has been resized.
+ // The default implementation of resized() just calls draw()
+ //
+
+ virtual void resized();
+
+ static int getColorCount();
+ // # of colors supported by display (0 if no colors)
+
+ // See the beginning of this file for a description of writeText().
+
+ virtual bool writeText(const char *text, int row, int col,
+ const CursesAttr &attr) const;
+ virtual bool writeText(const std::vector<unicode_char> &text,
+ int row, int col,
+ const Curses::CursesAttr &attr) const;
+
+ void writeText(std::string text, int row, int col,
+ const CursesAttr &attr) const;
+
+ // Beep the terminal.
+
+ virtual void beepError();
+
+private:
+ Alignment alignment;
+
+public:
+ // Return the coordinates of the top/left corner of this object,
+ // in the context of this parent, after taking into account all
+ // the alignment information.
+
+ int getRowAligned() const;
+ int getColAligned() const;
+
+ virtual void setAlignment(Alignment alignmentArg);
+ virtual Alignment getAlignment();
+
+ // Indicate whether this object can handle keyboard input
+ // (default: no).
+
+ virtual bool isFocusable();
+
+ // If this object is handling keyboard input, return the next or the
+ // previous object, in the natural tabbing order, that should receive
+ // keyboard input.
+
+ virtual Curses *getNextFocus();
+ virtual Curses *getPrevFocus();
+
+ // Actually move the keyboard focus to the next or prev curses object.
+
+ virtual void transferNextFocus();
+ virtual void transferPrevFocus();
+
+ // Explicitly request keyboard input
+
+ virtual void requestFocus();
+
+ // Callback function that is invoked whenever this object begins
+ // handling keyboard input. The default implementation just calls
+ // draw()
+
+ virtual void focusGained();
+
+ // Callback function that is invoked whenever this object stops
+ // handling keyboard input. The default implementation just calls
+ // draw()
+
+ virtual void focusLost();
+
+ // Remove keyboard input from any object that's currently receiving
+ // keyboard input (explicitly calling focusLost()). This can be
+ // useful to force focusLost() processing (to make sure that any
+ // further popup notice activity won't cause any side effects).
+ // Eventually, somebody's requestFocus() method should be called
+ // to reenable input processing
+
+ static void dropFocus();
+
+ // Return whether this object is handling keyboard input
+
+ virtual bool hasFocus();
+
+ // Handle keyboard input. The default implementation changes
+ // keyboard input focus in response to basic cursor movement
+ // keys. Subclasses typically override, but also call the superclass's
+ // method for the default action.
+ // processKeyInFocus returns true if the key was processed, or if the
+ // function did not recognize and process the key.
+
+ virtual bool processKeyInFocus(const Key &key);
+
+ // Map the coordinates of a character sell in this Curses object to
+ // the screen coordinates. The default implementation just adds
+ // row/col to the top/left starting coords of this object, then runs
+ // the parent's getCursorPosition() method.
+
+ virtual int getCursorPosition(int &row, int &col);
+
+
+ // Set a callback function invoked after a suspend
+
+ static void setSuspendHook( void (*)(void) );
+
+ static void (*suspendedHook)(void);
+private:
+ static void suspendedStub(void);
+
+};
+
+#endif
diff --git a/curses/timer.C b/curses/timer.C
new file mode 100644
index 0000000..8097f37
--- /dev/null
+++ b/curses/timer.C
@@ -0,0 +1,172 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "timer.H"
+
+std::set<Timer *> Timer::timer_list;
+
+Timer::Timer()
+{
+ timeout.tv_sec=0;
+ timeout.tv_usec=0;
+
+ timer_list.insert(this);
+}
+
+Timer::~Timer()
+{
+ timer_list.erase(this);
+}
+
+void Timer::setTimer(int nSeconds)
+{
+ gettimeofday(&timeout, NULL);
+ timeout.tv_sec += nSeconds;
+}
+
+void Timer::setTimer(struct timeval tv)
+{
+ gettimeofday(&timeout, NULL);
+ timeout.tv_sec += tv.tv_sec;
+
+ timeout.tv_usec += tv.tv_usec;
+
+ if (timeout.tv_usec >= 1000000)
+ {
+ ++timeout.tv_sec;
+ timeout.tv_usec %= 1000000;
+ }
+}
+
+struct timeval Timer::getTimer() const
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+
+ return getTimer(tv);
+}
+
+struct timeval Timer::getTimer(const struct timeval &tv) const
+{
+ struct timeval t;
+
+ t.tv_sec=0;
+ t.tv_usec=0;
+
+ if (timeout.tv_sec == 0 && timeout.tv_usec == 0)
+ return t;
+
+ if (tv.tv_sec > timeout.tv_sec)
+ return t;
+
+ if (tv.tv_sec == timeout.tv_sec &&
+ tv.tv_usec >= timeout.tv_usec)
+ return t;
+
+ t=timeout;
+
+ t.tv_sec -= tv.tv_sec;
+ t.tv_usec -= tv.tv_usec;
+
+ if (t.tv_usec < 0)
+ {
+ t.tv_usec += 1000000;
+ t.tv_sec--;
+ }
+
+ return t;
+}
+
+#define DEBUG 1
+#undef DEBUG
+
+struct timeval Timer::getNextTimeout(bool &alarmCalledFlag)
+{
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+
+ bool alarmed;
+
+ bool wasAlarmed=false;
+
+ struct timeval s;
+
+ alarmCalledFlag=false;
+
+ do
+ {
+#if DEBUG
+ cerr << "In getNextTimeout:" << endl;
+#endif
+
+ alarmed=false;
+
+ std::set<Timer *>::iterator b=timer_list.begin(),
+ e=timer_list.end();
+
+ s.tv_sec=0;
+ s.tv_usec=0;
+
+ while (b != e)
+ {
+ Timer *t= *b++;
+
+ if (t->timeout.tv_sec == 0 &&
+ t->timeout.tv_usec == 0)
+ continue;
+
+ struct timeval v=t->getTimer(now);
+
+#if DEBUG
+ cerr << "Timer " << t << ": " << v.tv_sec
+ << "." << v.tv_usec << endl;
+#endif
+
+ if (v.tv_sec == 0 && v.tv_usec == 0)
+ {
+#if DEBUG
+ cerr << "ALARM: " <<
+ (wasAlarmed ? "ignored":"handled")
+ << endl;
+#endif
+ if (wasAlarmed)
+ {
+ // We can get here if an alarm
+ // went off, and the alarm handler
+ // reset the alarm to 0 seconds again.
+ // We'll get it on the next go-round
+
+ s=v;
+ break; // Kick the alarm next time
+ }
+ t->timeout=v;
+ t->alarm();
+ alarmCalledFlag=true;
+ alarmed=true;
+ }
+ else if ((s.tv_sec == 0 && s.tv_usec == 0) ||
+ v.tv_sec < s.tv_sec ||
+ (v.tv_sec == s.tv_sec
+ && v.tv_usec < s.tv_usec))
+ s=v;
+ }
+
+ wasAlarmed=alarmed;
+
+ } while (alarmed);
+
+#if DEBUG
+ cerr << "getNextTimeout: " << s.tv_sec << "." << s.tv_usec << endl;
+#endif
+ return s;
+}
+
+void Timer::alarm()
+{
+}
diff --git a/curses/timer.H b/curses/timer.H
new file mode 100644
index 0000000..b4b533d
--- /dev/null
+++ b/curses/timer.H
@@ -0,0 +1,124 @@
+/*
+** Copyright 2002, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef timer_H
+#define timer_H
+
+#include "../curses/curses_config.h"
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <set>
+
+////////////////////////////////////////////////////////////////////////////
+//
+// The Timer object is used primarily by CursesStatusBar to automatically
+// clear messages after a short delay, but it can be used as a general
+// purpose timer.
+//
+// Timer processing depends on the application repeatedly calling the
+// getNextTimeout() method, which checks for any timers that need to go
+// off. getNextTimeout() returns a timeval when the getNextTimeout() should
+// be called again, to check for any more timers.
+//
+// The Timer object may be instantiated as is, or subclassed. The expired()
+// method may be invoked, after getNextTimeout(), to check if the timer has
+// gone off. Alternativelly, the subclass can implement the alarm() method.
+
+class Timer {
+ struct timeval timeout;
+
+ static std::set<Timer *> timer_list;
+
+public:
+ Timer();
+ virtual ~Timer();
+
+ // Set a timeout for the given number of seconds.
+
+ void setTimer(int);
+
+ // Set a timeout for the given number of seconds/milliseconds.
+
+ void setTimer(struct timeval tv);
+
+ // Cancel this timer.
+
+ void cancelTimer() { timeout.tv_sec=0; timeout.tv_usec=0; }
+ bool expired() { return timeout.tv_sec == 0 && timeout.tv_usec == 0; }
+
+ // Compute how long before this timer goes off.
+
+ struct timeval getTimer() const;
+
+ // Compute how long before this timer goes off, if the current time is
+ // 'now'.
+
+ struct timeval getTimer(const struct timeval &now) const;
+
+ // The timer has gone off.
+
+ virtual void alarm();
+
+ // Trigger any pending timers. alarmCalledFlag gets set to true if
+ // any alarms were kicked off
+
+ static struct timeval getNextTimeout(bool &alarmCalledFlag);
+};
+
+//////////////////////////////////////////////////////////////////////////
+//
+// A helpful template that embeds Timer as a member of another class.
+// Rather than subclassing from Timer, multiple Timer objects may be
+// members of a class. Typical usage:
+//
+// class X {
+// ...
+//
+// TimerRedirect<X> timera, timerb;
+//
+// void timeraFunc();
+// void timerbFunc();
+// };
+//
+// X::X()
+// {
+// timera=this;
+// timerb=this;
+//
+// timera= &X::timerAFunc;
+// timerb= &X::timerBFunc;
+// }
+//
+// ...
+//
+// timera.setTimer(10);
+//
+
+template<class T> class TimerRedirect : public Timer {
+
+ T *myClass;
+ void (T::*myMethod)();
+
+public:
+ TimerRedirect() : myClass(0), myMethod(0) {}
+ ~TimerRedirect() {}
+
+ void operator=(T *classPtr) { myClass=classPtr; }
+ void operator=( void (T::*methodPtr)() ) { myMethod=methodPtr; }
+
+ void alarm() { if (myClass && myMethod) (myClass->*myMethod)(); }
+};
+
+#endif
diff --git a/curses/widechar.C b/curses/widechar.C
new file mode 100644
index 0000000..95e03a5
--- /dev/null
+++ b/curses/widechar.C
@@ -0,0 +1,498 @@
+/*
+** Copyright 2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#include "curses_config.h"
+#include "widechar.H"
+#include "cursesflowedline.H"
+#include <algorithm>
+
+bool unicoderewrapnone::operator()(size_t n) const
+{
+ return false;
+}
+
+void widecharbuf::tounicode(std::vector<unicode_char> &text) const
+{
+ text=ustring;
+}
+
+void widecharbuf::init_string(const std::string &str)
+{
+ std::vector<unicode_char> uc;
+
+ mail::iconvert::convert(str, unicode_default_chset(), uc);
+
+ init_unicode(uc.begin(), uc.end());
+}
+
+std::pair<std::string, size_t>
+widecharbuf::get_string_truncated(size_t maxwidth, ssize_t col) const
+{
+ std::pair<std::vector<unicode_char>, size_t>
+ tempret(get_unicode_truncated(maxwidth, col));
+
+ std::pair<std::string, size_t> ret;
+
+ ret.second=tempret.second;
+
+ mail::iconvert::fromu::convert(tempret.first, unicode_default_chset(),
+ ret.first);
+ return ret;
+}
+
+std::string widecharbuf::get_substring(size_t first_grapheme,
+ size_t grapheme_cnt) const
+{
+ return mail::iconvert::fromu
+ ::convert(get_unicode_substring(first_grapheme,
+ grapheme_cnt),
+ unicode_default_chset());
+}
+
+std::vector<unicode_char>
+widecharbuf::get_unicode_substring(size_t first_grapheme,
+ size_t grapheme_cnt) const
+{
+ if (first_grapheme >= graphemes.size())
+ return std::vector<unicode_char>();
+
+ size_t max_graphemes=graphemes.size() - first_grapheme;
+
+ if (grapheme_cnt > max_graphemes)
+ grapheme_cnt=max_graphemes;
+
+ return std::vector<unicode_char>(ustring.begin()
+ +first_grapheme,
+ ustring.begin()
+ +first_grapheme+grapheme_cnt);
+}
+
+std::pair<std::vector<unicode_char>, size_t>
+widecharbuf::get_unicode_truncated(size_t maxwidth, ssize_t col) const
+{
+ size_t i;
+ size_t cnt=0;
+ size_t width=0;
+
+ maxwidth += col;
+
+ for (i=0; i<graphemes.size(); ++i)
+ {
+ size_t w=graphemes[i].wcwidth(col);
+
+ if (col + w > maxwidth)
+ break;
+
+ col += w;
+ width += w;
+ cnt += graphemes[i].cnt;
+ }
+
+ std::string s;
+
+ mail::iconvert::fromu::convert(ustring.begin(),
+ ustring.begin()+cnt,
+ unicode_default_chset(),
+ s);
+ return std::make_pair(std::vector<unicode_char>
+ (ustring.begin(), ustring.begin()+cnt), width);
+}
+
+std::vector<unicode_char> widecharbuf::get_unicode_fixedwidth(size_t width,
+ ssize_t atcol)
+ const
+{
+ std::pair<std::vector<unicode_char>, size_t>
+ truncated=get_unicode_truncated(width, atcol);
+
+ if (truncated.second < width)
+ truncated.first.insert(truncated.first.end(),
+ width-truncated.second, ' ');
+
+ return truncated.first;
+}
+
+size_t widecharbuf::wcwidth(ssize_t start_col) const
+{
+ size_t n=0;
+
+ for (std::vector<grapheme_t>::const_iterator
+ b(graphemes.begin()), e(graphemes.end()); b != e; ++b)
+ {
+ size_t grapheme_width=b->wcwidth(start_col);
+
+ n += grapheme_width;
+ start_col += grapheme_width;
+ }
+ return n;
+}
+
+void widecharbuf::resetgraphemes()
+{
+ graphemes.clear();
+
+ if (ustring.empty())
+ return;
+
+ const unicode_char *b=&*ustring.begin(), *e(b+ustring.size());
+
+ do
+ {
+ const unicode_char *start_pos=b;
+
+ unicode_char first, second=*b;
+
+ do
+ {
+ first=second;
+ ++b;
+
+ } while (b != e && (second=*b) != '\t' && first != '\t' &&
+ !unicode_grapheme_break(first, second));
+
+ graphemes.push_back(grapheme_t(start_pos, b-start_pos));
+ } while (b != e);
+}
+
+widecharbuf &widecharbuf::operator+=(const widecharbuf &o)
+{
+ ustring.insert(ustring.end(), o.ustring.begin(), o.ustring.end());
+ resetgraphemes();
+ resetgraphemes();
+ return *this;
+}
+
+widecharbuf &widecharbuf::operator=(const widecharbuf &o)
+{
+ ustring=o.ustring;
+ resetgraphemes();
+ return *this;
+}
+
+size_t widecharbuf::charwidth(wchar_t ch, ssize_t atcol)
+{
+ // We visually multiply dashes, corresponds to code
+ // in CursesScreen::writeText()
+
+ if (ch == 0x2013)
+ return 2; // EN DASH
+
+ if (ch == 0x2014)
+ return 3; // EM DASH
+
+ if (ch != '\t')
+ {
+ int s=::wcwidth(ch);
+
+ if (s == -1)
+ s=1; // Unprintable character, punt
+ return s;
+ }
+
+ size_t cnt=0;
+
+ do
+ {
+ ++atcol;
+ ++cnt;
+ } while (atcol & 7);
+ return cnt;
+}
+
+
+size_t widecharbuf::grapheme_t::wcwidth(ssize_t col) const
+{
+ std::string s;
+
+ mail::iconvert::fromu::convert(uptr, uptr+cnt,
+ unicode_default_chset(), s);
+
+ return towidechar(s.begin(), s.end(), towidechar_wcwidth_iter(col));
+}
+
+ssize_t widecharbuf::expandtabs(ssize_t col)
+{
+ std::vector<unicode_char> replbuf;
+
+ replbuf.reserve(ustring.size()*2);
+
+ for (std::vector<grapheme_t>::iterator
+ b(graphemes.begin()),
+ e(graphemes.end()); b != e; ++b)
+ {
+ size_t w=b->wcwidth(col);
+
+ if (*b->uptr == '\t')
+ {
+ replbuf.insert(replbuf.end(), w, ' ');
+ }
+ else
+ {
+ replbuf.insert(replbuf.end(), b->uptr,
+ b->uptr + b->cnt);
+ }
+
+ col += w;
+ }
+ ustring=replbuf;
+ resetgraphemes();
+ return col;
+}
+
+void editablewidechar::set_contents(const std::vector<unicode_char>
+ &before,
+ const std::vector<unicode_char>
+ &after)
+{
+ before_insert.init_unicode(before.begin(), before.end());
+ after_insert.init_unicode(after.begin(), after.end());
+ inserted.clear();
+}
+
+void editablewidechar::get_contents(std::vector<unicode_char> &before,
+ std::vector<unicode_char> &after)
+ const
+{
+ before.clear();
+
+ before_insert.tounicode(before);
+ before.insert(before.end(), inserted.begin(), inserted.end());
+ after_insert.tounicode(after);
+}
+
+void editablewidechar::contents_cut(size_t cut_pos,
+ std::vector<unicode_char> &cut_text)
+{
+ if (cut_pos == before_insert.graphemes.size() &&
+ inserted.empty())
+ return;
+
+ std::vector<unicode_char> before, after;
+
+ if (cut_pos < before_insert.graphemes.size())
+ {
+ before=before_insert.get_unicode_substring(0, cut_pos);
+ cut_text=before_insert
+ .get_unicode_substring(cut_pos,
+ before_insert.graphemes.size()
+ -cut_pos);
+ }
+ else
+ before_insert.tounicode(before);
+
+ if (cut_pos > before_insert.graphemes.size())
+ {
+ cut_pos -= before_insert.graphemes.size();
+
+ cut_text=after_insert.get_unicode_substring(0, cut_pos);
+ after=after_insert
+ .get_unicode_substring(cut_pos,
+ after_insert.graphemes.size()
+ -cut_pos);
+ }
+ else
+ after_insert.tounicode(after);
+
+ set_contents(before, after);
+}
+
+void editablewidechar::insert_to_before()
+{
+ std::vector<unicode_char> before, after;
+
+ get_contents(before, after);
+ set_contents(before, after);
+}
+
+void editablewidechar::to_before()
+{
+ insert_to_before();
+ before_insert += after_insert;
+ after_insert.clear();
+}
+
+void editablewidechar::to_after()
+{
+ insert_to_before();
+ before_insert += after_insert;
+ after_insert=before_insert;
+ before_insert.clear();
+}
+
+void editablewidechar::to_position(size_t o)
+{
+ insert_to_before();
+
+ std::vector<unicode_char> cut_text, before, after;
+
+ size_t col=0;
+
+ for (std::vector<widecharbuf::grapheme_t>::iterator
+ b(before_insert.graphemes.begin()),
+ e(before_insert.graphemes.end()); b != e; ++b)
+ {
+ size_t grapheme_width=b->wcwidth(col);
+
+ if (grapheme_width > o)
+ {
+ contents_cut(b-before_insert.graphemes.begin(),
+ cut_text);
+ get_contents(before, after);
+ after.insert(after.begin(), cut_text.begin(),
+ cut_text.end());
+ set_contents(before, after);
+ return;
+ }
+ o -= grapheme_width;
+ col += grapheme_width;
+ }
+
+ std::vector<widecharbuf::grapheme_t>::iterator b, e;
+
+ for (b=after_insert.graphemes.begin(),
+ e=after_insert.graphemes.end(); b != e; ++b)
+ {
+ size_t grapheme_width=b->wcwidth(col);
+
+ if (grapheme_width > o)
+ break;
+ o -= grapheme_width;
+ col += grapheme_width;
+ }
+
+ contents_cut(b-after_insert.graphemes.begin(), cut_text);
+ get_contents(before, after);
+ before.insert(before.end(), cut_text.begin(), cut_text.end());
+ set_contents(before, after);
+}
+
+size_t editablewidechar::adjust_shift_pos(size_t &shiftoffset,
+ size_t width,
+ widecharbuf &wbefore,
+ widecharbuf &wafter)
+{
+ {
+ std::vector<unicode_char> before, after;
+ get_contents(before, after);
+
+ wbefore.init_unicode(before.begin(), before.end());
+ wafter.init_unicode(after.begin(), after.end());
+ }
+
+ if (wbefore.graphemes.size() < shiftoffset)
+ // Cursor position before the current left margin
+ shiftoffset=wbefore.graphemes.size();
+
+ size_t last_grapheme_width=0;
+
+ size_t virtual_col=0;
+
+ for (size_t i=0; i<wbefore.graphemes.size(); ++i)
+ virtual_col += (last_grapheme_width=wbefore.graphemes[i]
+ .wcwidth(virtual_col));
+
+ if (shiftoffset > 0
+ && shiftoffset == wbefore.graphemes.size()
+ && width > last_grapheme_width)
+ // If the field position is shifted, and the cursor is position
+ // on the left margin, and the field width is sufficient to
+ // show the last grapheme, shift left. This basically keeps
+ // the cursor off the left margin if the field is shifted
+ // horizontally.
+ --shiftoffset;
+
+
+ size_t w=0;
+
+ for (std::vector<widecharbuf::grapheme_t>::const_iterator
+ b(wbefore.graphemes.begin()),
+ e(wbefore.graphemes.end()); b != e; ++b)
+ {
+ if (w >= width)
+ break;
+
+ w += b->wcwidth(w);
+ }
+
+ for (std::vector<widecharbuf::grapheme_t>::const_iterator
+ b(wafter.graphemes.begin()),
+ e(wafter.graphemes.end()); b != e; ++b)
+ {
+ if (w >= width)
+ break;
+
+ w += b->wcwidth(w);
+ }
+
+ if (w < width && shiftoffset != 0)
+ // Entire contents fit, so shift offset must be 0
+ shiftoffset=0;
+
+ // Compute shift offset for the cursor position to be on the last
+ // position in the field.
+
+ w=width;
+
+ if (w)
+ --w;
+
+ virtual_col=0;
+ for (std::vector<widecharbuf::grapheme_t>::const_iterator
+ b(wbefore.graphemes.begin()),
+ e(wbefore.graphemes.end()); b != e; )
+ {
+ --e;
+
+ size_t grapheme_width=e->wcwidth(virtual_col);
+
+ if (grapheme_width > w)
+ {
+ size_t newoffset=e-b+1;
+
+ if (newoffset > shiftoffset)
+ {
+ // Must shift right.
+
+ shiftoffset=newoffset;
+ break;
+ }
+ }
+ w -= grapheme_width;
+ virtual_col += w;
+ }
+
+ size_t col=0;
+
+ if (shiftoffset < wbefore.graphemes.size())
+ {
+ virtual_col=0;
+
+ for (std::vector<widecharbuf::grapheme_t>::const_iterator
+ b(wbefore.graphemes.begin()),
+ e(wbefore.graphemes.begin()+shiftoffset);
+ b != e; ++b)
+ virtual_col += b->wcwidth(virtual_col);
+
+ for (std::vector<widecharbuf::grapheme_t>::const_iterator
+ b(wbefore.graphemes.begin()+shiftoffset),
+ e(wbefore.graphemes.end()); b != e; ++b)
+ {
+ size_t grapheme_width=b->wcwidth(virtual_col);
+
+ col += grapheme_width;
+ virtual_col += grapheme_width;
+ }
+ }
+
+ return col;
+}
+
+CursesFlowedLine::CursesFlowedLine(const std::vector<unicode_char> &textArg,
+ bool flowedArg)
+ : text(mail::iconvert::convert(textArg, "utf-8")), flowed(flowedArg)
+{
+}
+
diff --git a/curses/widechar.H b/curses/widechar.H
new file mode 100644
index 0000000..b541a25
--- /dev/null
+++ b/curses/widechar.H
@@ -0,0 +1,678 @@
+/*
+** Copyright 2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+
+#ifndef widechar_H
+#define widechar_H
+
+#include "../curses/curses_config.h"
+#include "../unicode/unicode.h"
+#include <string.h>
+
+#include <wchar.h>
+#include <vector>
+#include <iterator>
+
+/*
+** Convert a char sequence defined by beg_iter and end_iter iterators into
+** a wchar_t sequence, written to the out_iter output iterator
+*/
+
+template<typename input_iter, typename output_iter>
+output_iter towidechar(input_iter beg_iter,
+ input_iter end_iter,
+ output_iter out_iter)
+{
+ mbstate_t ps;
+ memset(&ps, 0, sizeof(ps));
+
+ std::vector<char> cbuf;
+ cbuf.reserve(MB_CUR_MAX*2);
+
+ int cnt=MB_CUR_MAX*2;
+
+ while (1)
+ {
+ if (cnt == 0 || beg_iter == end_iter)
+ {
+ const char *b=&cbuf[0];
+ const char *e=&cbuf[cbuf.size()];
+ size_t cnt=0;
+
+ while (b != e)
+ {
+ wchar_t wc;
+ size_t r=mbrtowc(&wc, b, e-b, &ps);
+
+ if (r == (size_t)-1)
+ {
+ wc='?';
+ memset(&ps, 0, sizeof(ps));
+ r=1;
+ }
+
+ if (r > (size_t)(e-b))
+ // must be (size_t)-2 -
+ // Incomplete multibyte sequence
+ break;
+
+ if (r == 0)
+ {
+ wc=0;
+ r=1;
+ }
+
+ *out_iter++=wc;
+
+ b += r;
+ cnt += r;
+ }
+ cbuf.clear();
+ cnt=MB_CUR_MAX;
+
+ if (beg_iter == end_iter)
+ break;
+ }
+
+ cbuf.push_back(*beg_iter++);
+ --cnt;
+ }
+
+ return out_iter;
+}
+
+/*
+** Convenience function.
+*/
+
+template<typename input_iter>
+void towidechar(input_iter beg_iter,
+ input_iter end_iter,
+ std::vector<wchar_t> &uc)
+{
+ uc.clear();
+
+ towidechar(beg_iter, end_iter,
+ std::back_insert_iterator<std::vector<wchar_t> >(uc));
+}
+
+/*
+** Convert a wchar_t sequence defined by beg_iter and end_iter iterators into
+** a char sequence, written to the out_iter output iterator
+*/
+
+template<typename input_iter,
+ typename output_iter>
+output_iter fromwidechar(input_iter b, input_iter e, output_iter out_iter)
+{
+ mbstate_t ps;
+ std::vector<char> buf;
+
+ buf.resize(MB_CUR_MAX*2);
+
+ memset(&ps, 0, sizeof(ps));
+
+ for (; b != e; ++b)
+ {
+ size_t n=wcrtomb(&buf[0], *b, &ps);
+
+ if (n == (size_t)-1)
+ continue;
+
+ out_iter=std::copy(buf.begin(), buf.begin()+n, out_iter);
+ }
+
+ size_t n=wcrtomb(&buf[0], 0, &ps);
+
+ if (n != (size_t)-1)
+ {
+ if (n > 0 && buf[n-1] == 0)
+ --n;
+ out_iter=std::copy(buf.begin(), buf.begin()+n, out_iter);
+ }
+ return out_iter;
+}
+
+/*
+** Convenience function.
+*/
+
+template<typename input_iter>
+std::string fromwidechar(input_iter beg_iter,
+ input_iter end_iter)
+{
+ std::string s;
+
+ fromwidechar(beg_iter, end_iter,
+ std::back_insert_iterator<std::string>(s));
+
+ return s;
+}
+
+// A unicode string demarcated at grapheme boundaries
+
+class widecharbuf {
+
+public:
+ widecharbuf() {}
+
+ // Initialize from a string
+
+ void init_string(const std::string &str);
+
+ // Given the maximum desired column width of the string,
+ // return a pair of string, and the width of the returned
+ // string.
+ std::pair<std::string, size_t>
+ get_string_truncated(size_t maxwidth, ssize_t atcol) const;
+
+ // Return as a unicode string
+ std::pair<std::vector<unicode_char>, size_t>
+ get_unicode_truncated(size_t maxwidth, ssize_t atcol) const;
+
+ // Return a substring
+ std::string get_substring(size_t first_grapheme,
+ size_t grapheme_cnt) const;
+
+ // Return a unicode substring
+ std::vector<unicode_char> get_unicode_substring(size_t first_grapheme,
+ size_t grapheme_cnt)
+ const;
+
+ // Return as a unicode string, truncated or padded to the given width
+
+ std::vector<unicode_char> get_unicode_fixedwidth(size_t width,
+ ssize_t atcol) const;
+
+ // Initialize from a beginning and ending iterator, iterating
+ // over unicode_chars.
+
+ template<typename iter_type>
+ void init_unicode(iter_type b, iter_type e)
+ {
+ std::string s;
+
+ mail::iconvert::fromu::convert(b, e,
+ unicode_default_chset(), s);
+
+ std::vector<wchar_t> wc;
+
+ towidechar(s.begin(), s.end(), wc);
+
+ s=fromwidechar(wc.begin(), wc.end());
+
+ mail::iconvert::tou::convert(s.begin(), s.end(),
+ unicode_default_chset(),
+ ustring);
+ resetgraphemes();
+ }
+
+ // The unicode string
+
+ std::vector<unicode_char> ustring;
+
+ // A grapheme: a pointer somewhere in ustring, plus wchar count
+
+ class grapheme_t {
+
+ public:
+ const unicode_char *uptr; // Offset into ustring
+ size_t cnt; // How many unicode chars in the grapheme
+
+ grapheme_t(const unicode_char *uptrArg, size_t cntArg)
+ : uptr(uptrArg), cnt(cntArg) {}
+
+ size_t wcwidth(ssize_t start_col) const;
+ };
+
+ std::vector<grapheme_t> graphemes;
+
+ // Reset wchar_t in each grapheme
+ void resetgraphemes();
+
+ void clear()
+ {
+ ustring.clear();
+ graphemes.clear();
+ }
+
+ ssize_t expandtabs(ssize_t col);
+
+ // Append
+ widecharbuf &operator+=(const widecharbuf &);
+
+ // Replace
+
+ widecharbuf(const widecharbuf &o)
+ {
+ operator=(o);
+ }
+
+ widecharbuf &operator=(const widecharbuf &o);
+
+ size_t wcwidth(ssize_t start_col) const;
+
+ void tounicode(std::vector<unicode_char> &text) const;
+
+ static size_t charwidth(wchar_t ch, ssize_t atcol);
+};
+
+//
+// Output iterator for towidechar() that throws away the wide character,
+// but keeps track of its width.
+//
+// widecharbuf::grapheme_t::wcwidth() is hot. Saving the output of
+// towidechar() into a vector is very expensive.
+
+class towidechar_wcwidth_iter
+ : public std::iterator<std::output_iterator_tag, void, void, void, void>
+{
+
+ size_t col;
+ size_t w;
+
+public:
+
+ towidechar_wcwidth_iter(size_t colArg) : col(colArg), w(0) {}
+
+ towidechar_wcwidth_iter &operator++() { return *this; }
+ towidechar_wcwidth_iter &operator++(int) { return *this; }
+ towidechar_wcwidth_iter &operator*() { return *this; }
+
+ void operator=(wchar_t wc)
+ {
+ size_t ww=widecharbuf::charwidth(wc, col);
+
+ col += ww;
+ w += ww;
+ }
+
+ operator size_t() const { return w; }
+};
+
+// Editable wchar_ts, together with an insertion point.
+//
+// The wchar_ts get internally divided into three parts: before the
+// insertion point, at the insertion point, and past the insertion
+// point. Before and after contain valid graphemes. The current
+// insertion point may or may not contain valid graphemes.
+//
+// set_contents() initializes wchar_ts before and after the
+// insertion point, setting at the insertion point to an empty list.
+//
+// add() adds a wchar_t at the insertion point.
+//
+// get_contents() retrieves the contents as unicode characters,
+// combining the insertion point wchar_ts with wchar_ts before the
+// insertion point.
+//
+// contents_cut() removes graphemes between the cut_pos and the
+// insertion point. contents_cut() may only be invoked when the
+// insertion point is empty. The removed graphemes get placed into
+// cut_text.
+//
+// insert_to_before() calls get_contents() then set_contents(),
+// essentially converting wchar_ts at the insertion point to
+// valid graphemes, and moving them to before_insert.
+//
+// to_before() calls insert_to_before(), then moves after_insert
+// to before_insert().
+//
+// to_after() calls insert_to_before(), then moves before_isnert
+// to after_insert().
+//
+// to_position() calls insert_to_before() then moves the insertion
+// point to the given position.
+//
+// adjust_shift_position() calculates horizontal scrolling. It takes
+// the editable field's width, and a reference to the current
+// horizontal shift position, which gets adjusted, as necessary, to
+// keep the cursor position visible, and returns the cursor position.
+
+class editablewidechar {
+
+public:
+ widecharbuf before_insert;
+
+ std::vector<unicode_char> inserted;
+
+ widecharbuf after_insert;
+
+ void set_contents(const std::vector<unicode_char> &before,
+ const std::vector<unicode_char> &after);
+
+ void get_contents(std::vector<unicode_char> &before,
+ std::vector<unicode_char> &after) const;
+
+ void contents_cut(size_t cut_pos,
+ std::vector<unicode_char> &cut_text);
+
+ void insert_char(unicode_char wc) { inserted.push_back(wc); }
+
+ void insert_to_before();
+
+ void to_before();
+
+ void to_after();
+
+ void clear()
+ {
+ before_insert.clear();
+ after_insert.clear();
+ inserted.clear();
+ }
+
+ void to_position(size_t pos);
+
+ size_t adjust_shift_pos(size_t &shiftoffset, size_t width,
+
+ // wbefore and wafter: wide characters
+ // before and after cursor position,
+ // as computed by adjust_shift_pos().
+ // Useful to the caller
+ widecharbuf &wbefore,
+ widecharbuf &wafter);
+};
+
+/*
+** Helper class for the word-wrapping logic.
+**
+** This class receives unicode_char pieces, that can be broken. The collector
+** converts them to system wchars, measures each piece, and collects them
+** until the next piece can't fit within the alloted width.
+*/
+
+template<typename output_sink>
+class wordwrap_collector {
+
+public:
+ /* Collected line */
+ std::vector<unicode_char> linebuf;
+
+ /* Width of the collected line */
+ size_t col;
+
+ /*
+ ** iter(line) gets invoked for each assembled line, where
+ ** line is a std::vector<wchar_t>.
+ */
+
+ output_sink &iter;
+
+ /*
+ ** Desired line width.
+ */
+
+ size_t towidth;
+
+ /*
+ ** If true, the trailing space on each line gets removed.
+ ** In all cases, each line is wrapped at towidth() characters,
+ ** where possible.
+ */
+
+ bool delsp;
+
+ wordwrap_collector(output_sink &iterArg,
+ size_t towidthArg,
+ bool delspArg) : col(0), iter(iterArg),
+ towidth(towidthArg),
+ delsp(delspArg)
+ {
+ }
+
+ void addsegment(const std::vector<unicode_char> &segment)
+ {
+ std::vector<wchar_t> wsegment;
+
+ {
+ std::string s=mail::iconvert
+ ::convert(segment, unicode_default_chset());
+
+ towidechar(s.begin(), s.end(), wsegment);
+
+ if (wsegment.empty())
+ return;
+ }
+
+ size_t width=0;
+
+ for (std::vector<wchar_t>::const_iterator
+ b(wsegment.begin()),
+ e(wsegment.end()); b != e; ++b)
+ width += widecharbuf::charwidth(*b, col+width);
+
+ if (!(delsp && width + col == towidth+1 &&
+ *--wsegment.end() == ' '))
+ {
+ if (width + col > towidth && !linebuf.empty())
+ breakline();
+ }
+
+ linebuf.insert(linebuf.end(), segment.begin(), segment.end());
+ col += width;
+ }
+
+ void breakline()
+ {
+ if (delsp && !linebuf.empty() &&
+ linebuf[linebuf.size()-1] == ' ')
+ linebuf.pop_back();
+
+ *iter++=linebuf;
+ col=0;
+ linebuf.clear();
+ }
+};
+
+
+/*
+** A default rewrap helper object that does not rewrap anything.
+*/
+
+class unicoderewrapnone {
+
+public:
+ bool operator()(size_t n) const;
+};
+
+/*
+** Unicode-based linewrapping logic.
+**
+** This template defines an output iterator that takes in unicode_chars.
+** The constructor receives a rewrap helper object reference, an output
+** iterator, the requested width, and whether to trim the trailing
+** space from each wrapped line.
+**
+** As this iterator is iterated over unicode_chars, it will iterate the
+** received output iterator over std::vector<unicode_char>s, representing
+** each line wrapped to the requested width.
+**
+** The trim flag should normally be false. This properly preserves all
+** whitespace in the unicode character sequence. The trim flag may be true
+** only in contexts where the wrapped text will never be rewrapped again.
+** Removal of a trailing space on each line allows an extra character to be
+** present instead of the trailing space.
+**
+** The rewrap helper object instance must define a bool operator()(size_t n)
+** const. size_t receives a character offset, and should return true if
+** character #n is the first character in an original line of text. If so,
+** and the unicode word wrap algorithm does not indicate that there's a
+** potential linebreak here, a space gets appended at this point. This is
+** used to rewrap existing lines of text which may not end with a space
+** character.
+**
+** After the original unicode chars are iterated over, eof() must be
+** invoked in order to output any partially-wrapped content that's still
+** held internally in this iterator.
+*/
+
+template<typename output_sink_t,
+ typename rewrap_helper_t=unicoderewrapnone> class unicodewordwrapper :
+ public std::iterator<std::output_iterator_tag, void, void, void, void> {
+
+ // State maintained by the iterator. This iterator is copyable.
+ // The state is associated with only one iterator instance. Copying
+ // an iterator copies the state from the original iterator into the
+ // new one.
+
+ class buffer : public mail::linebreakc_callback_save_buf {
+
+ public:
+ std::vector<unicode_char> segment; // Current word
+ size_t cnt; // Counts characters that are being wrapped.
+
+ wordwrap_collector<output_sink_t> collector;
+ // The collector object.
+
+ const rewrap_helper_t &rewrapper;
+ // The rewrap helper object.
+
+ buffer(const rewrap_helper_t &rewrapperArg,
+ output_sink_t &out_iter,
+ size_t towidth,
+ bool delsp)
+ : cnt(0),
+ collector(out_iter, towidth, delsp),
+ rewrapper(rewrapperArg) {}
+ };
+
+ mutable buffer *buf;
+
+ typedef unicodewordwrapper<output_sink_t, rewrap_helper_t> iter_t;
+
+public:
+ // Iterator constructor
+ unicodewordwrapper(const rewrap_helper_t &rewrap_helper,
+ output_sink_t &out_iter,
+ size_t towidth,
+ bool delsp)
+ : buf(new buffer(rewrap_helper, out_iter, towidth, delsp))
+ {
+ buf->set_opts(UNICODE_LB_OPT_PRBREAK|
+ UNICODE_LB_OPT_SYBREAK|
+ UNICODE_LB_OPT_DASHWJ);
+ }
+
+ // End iterator constructor
+ unicodewordwrapper() : buf(NULL)
+ {
+ }
+
+ ~unicodewordwrapper()
+ {
+ eof();
+ }
+
+ // Assignment operator moves the state
+
+ iter_t &operator=(const iter_t &o)
+ {
+ if (buf)
+ delete buf;
+
+ buf=o.buf;
+ o.buf=NULL;
+ return *this;
+ }
+
+ // Copy constructor moves the state
+ unicodewordwrapper(const iter_t &o) : buf(o.buf)
+ {
+ o.buf=NULL;
+ }
+
+ // Operator implementation
+
+ iter_t &operator++() { return *this; }
+ iter_t &operator++(int) { return *this; }
+ iter_t &operator*() { return *this; }
+
+ void operator=(unicode_char ch)
+ {
+ if (!buf)
+ return;
+
+ // Feed into the linebreaking algorithm.
+
+ buf->operator<<(ch);
+
+ // Process linebreaking algorithm output.
+
+ while (!buf->lb_buf.empty())
+ {
+ std::pair<int, unicode_char> ch(buf->lb_buf.front());
+ buf->lb_buf.pop_front();
+
+ // If text is being rewrapped, and the linebreaking
+ // algorithm prohibits a linebreak here, but this
+ // was the first character of the pre-wrapped line,
+ // then there must've been a space character here,
+ // which allows a break.
+
+ if (ch.first == UNICODE_LB_NONE && buf->cnt > 0 &&
+ (buf->rewrapper)(buf->cnt))
+ {
+ buf->segment.push_back(' ');
+ ch.first=UNICODE_LB_ALLOWED;
+ }
+ ++buf->cnt;
+
+ // Process a potential linebreak.
+
+ if (ch.first != UNICODE_LB_NONE)
+ {
+ buf->collector.addsegment(buf->segment);
+
+ if (ch.first == UNICODE_LB_MANDATORY)
+ buf->collector.breakline();
+ buf->segment.clear();
+ }
+
+ if (ch.second != '\r' && ch.second != '\n')
+ buf->segment.push_back(ch.second);
+ }
+ }
+
+ // Finish remaining content, and clean up.
+
+ void eof()
+ {
+ if (buf)
+ {
+ buf->collector.addsegment(buf->segment);
+ if (!buf->collector.linebuf.empty())
+ buf->collector.breakline();
+
+ delete buf;
+ buf=NULL;
+ }
+ }
+};
+
+// A convenience function to iterate over an arbitrary sequence defined
+// by a beginning and an ending iterator, and wrap it.
+
+template<typename input_iter, typename output_sink,
+ typename rewrap_helper_t>
+void unicodewordwrap(input_iter beg_iter,
+ input_iter end_iter,
+ const rewrap_helper_t &rewrap_helper,
+ output_sink &out_iter,
+ size_t towidth,
+ bool delsp)
+{
+ unicodewordwrapper<output_sink, rewrap_helper_t>
+ iter(rewrap_helper, out_iter, towidth, delsp);
+
+ while (beg_iter != end_iter)
+ {
+ iter= *beg_iter;
+ ++beg_iter;
+ }
+ iter.eof();
+}
+
+#endif