summaryrefslogtreecommitdiff
path: root/src/vscreen
diff options
context:
space:
mode:
authorDaniel Burrows <dburrows@debian.org>2005-10-01 23:40:49 +0000
committerDaniel Burrows <dburrows@debian.org>2005-10-01 23:40:49 +0000
commitdb949f313eb10b747a875067623b89c47ee2b81d (patch)
tree95891553696a84cc382aa9a92bacdc88950361e1 /src/vscreen
parente5434a5aaf63b1602c81606824b94f0368e4aaa0 (diff)
downloadaptitude-db949f313eb10b747a875067623b89c47ee2b81d.tar.gz
[aptitude @ Import the Subversion repository into darcs.]
Diffstat (limited to 'src/vscreen')
-rw-r--r--src/vscreen/Makefile.am91
-rw-r--r--src/vscreen/README.layout131
-rw-r--r--src/vscreen/columnify.cc180
-rw-r--r--src/vscreen/columnify.h77
-rw-r--r--src/vscreen/config/Makefile.am18
-rw-r--r--src/vscreen/config/colors.cc100
-rw-r--r--src/vscreen/config/colors.h49
-rw-r--r--src/vscreen/config/column_definition.cc276
-rw-r--r--src/vscreen/config/column_definition.h141
-rw-r--r--src/vscreen/config/keybindings.cc424
-rw-r--r--src/vscreen/config/keybindings.h157
-rw-r--r--src/vscreen/config/style.cc43
-rw-r--r--src/vscreen/config/style.h249
-rw-r--r--src/vscreen/curses++.cc411
-rw-r--r--src/vscreen/curses++.h628
-rw-r--r--src/vscreen/fragment.cc1519
-rw-r--r--src/vscreen/fragment.h353
-rw-r--r--src/vscreen/fragment_cache.cc97
-rw-r--r--src/vscreen/fragment_cache.h84
-rw-r--r--src/vscreen/fragment_contents.h216
-rw-r--r--src/vscreen/ref_ptr.h156
-rw-r--r--src/vscreen/testvscreen.cc511
-rw-r--r--src/vscreen/transcode.cc354
-rw-r--r--src/vscreen/transcode.h163
-rw-r--r--src/vscreen/vs_bin.cc149
-rw-r--r--src/vscreen/vs_bin.h54
-rw-r--r--src/vscreen/vs_button.cc150
-rw-r--r--src/vscreen/vs_button.h95
-rw-r--r--src/vscreen/vs_center.cc61
-rw-r--r--src/vscreen/vs_center.h31
-rw-r--r--src/vscreen/vs_container.cc35
-rw-r--r--src/vscreen/vs_container.h63
-rw-r--r--src/vscreen/vs_editline.cc486
-rw-r--r--src/vscreen/vs_editline.h138
-rw-r--r--src/vscreen/vs_frame.cc71
-rw-r--r--src/vscreen/vs_frame.h44
-rw-r--r--src/vscreen/vs_label.cc136
-rw-r--r--src/vscreen/vs_label.h96
-rw-r--r--src/vscreen/vs_layout_item.cc149
-rw-r--r--src/vscreen/vs_layout_item.h101
-rw-r--r--src/vscreen/vs_menu.cc665
-rw-r--r--src/vscreen/vs_menu.h245
-rw-r--r--src/vscreen/vs_menubar.cc778
-rw-r--r--src/vscreen/vs_menubar.h152
-rw-r--r--src/vscreen/vs_minibuf_win.cc230
-rw-r--r--src/vscreen/vs_minibuf_win.h104
-rw-r--r--src/vscreen/vs_multiplex.cc582
-rw-r--r--src/vscreen/vs_multiplex.h161
-rw-r--r--src/vscreen/vs_pager.cc443
-rw-r--r--src/vscreen/vs_pager.h267
-rw-r--r--src/vscreen/vs_passthrough.cc108
-rw-r--r--src/vscreen/vs_passthrough.h44
-rw-r--r--src/vscreen/vs_radiogroup.cc114
-rw-r--r--src/vscreen/vs_radiogroup.h86
-rw-r--r--src/vscreen/vs_scrollbar.cc90
-rw-r--r--src/vscreen/vs_scrollbar.h72
-rw-r--r--src/vscreen/vs_size_box.cc58
-rw-r--r--src/vscreen/vs_size_box.h54
-rw-r--r--src/vscreen/vs_stacked.cc202
-rw-r--r--src/vscreen/vs_stacked.h88
-rw-r--r--src/vscreen/vs_staticitem.cc46
-rw-r--r--src/vscreen/vs_staticitem.h38
-rw-r--r--src/vscreen/vs_statuschoice.cc96
-rw-r--r--src/vscreen/vs_statuschoice.h83
-rw-r--r--src/vscreen/vs_subtree.h257
-rw-r--r--src/vscreen/vs_table.cc1452
-rw-r--r--src/vscreen/vs_table.h211
-rw-r--r--src/vscreen/vs_text_layout.cc305
-rw-r--r--src/vscreen/vs_text_layout.h193
-rw-r--r--src/vscreen/vs_togglebutton.cc81
-rw-r--r--src/vscreen/vs_togglebutton.h161
-rw-r--r--src/vscreen/vs_transient.cc58
-rw-r--r--src/vscreen/vs_transient.h59
-rw-r--r--src/vscreen/vs_tree.cc1001
-rw-r--r--src/vscreen/vs_tree.h197
-rw-r--r--src/vscreen/vs_treeitem.cc50
-rw-r--r--src/vscreen/vs_treeitem.h398
-rw-r--r--src/vscreen/vs_util.cc396
-rw-r--r--src/vscreen/vs_util.h153
-rw-r--r--src/vscreen/vscreen.cc1053
-rw-r--r--src/vscreen/vscreen.h183
-rw-r--r--src/vscreen/vscreen_widget.cc303
-rw-r--r--src/vscreen/vscreen_widget.h503
83 files changed, 20107 insertions, 0 deletions
diff --git a/src/vscreen/Makefile.am b/src/vscreen/Makefile.am
new file mode 100644
index 00000000..9b34dedf
--- /dev/null
+++ b/src/vscreen/Makefile.am
@@ -0,0 +1,91 @@
+MAINTAINERCLEANFILES=Makefile.in
+
+SUBDIRS=config
+
+EXTRA_DIST=README.layout
+
+localedir = $(datadir)/locale
+INCLUDES = -Wall @WERROR@ -I$(top_builddir) -I$(srcdir) -I$(top_srcdir) -I$(top_srcdir)/src
+DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
+LDADD = @LIBINTL@
+
+noinst_LIBRARIES=libvscreen.a
+noinst_PROGRAMS=testvscreen
+
+libvscreen_a_SOURCES= \
+ curses++.cc \
+ curses++.h \
+ fragment.h \
+ fragment.cc \
+ fragment_cache.h\
+ fragment_cache.cc\
+ fragment_contents.h \
+ ref_ptr.h \
+ vscreen.cc \
+ vscreen.h \
+ vscreen_widget.h\
+ vscreen_widget.cc\
+ vs_button.h \
+ vs_button.cc \
+ vs_bin.h \
+ vs_bin.cc \
+ vs_center.h \
+ vs_center.cc \
+ vs_container.h \
+ vs_container.cc \
+ vs_editline.h \
+ vs_editline.cc \
+ vs_frame.h \
+ vs_frame.cc \
+ vs_label.h \
+ vs_label.cc \
+ vs_layout_item.h\
+ vs_layout_item.cc\
+ vs_menu.h \
+ vs_menu.cc \
+ vs_menubar.h \
+ vs_menubar.cc \
+ vs_minibuf_win.h \
+ vs_minibuf_win.cc\
+ vs_multiplex.h \
+ vs_multiplex.cc \
+ vs_pager.h \
+ vs_pager.cc \
+ vs_passthrough.h\
+ vs_passthrough.cc\
+ vs_scrollbar.h \
+ vs_scrollbar.cc \
+ vs_statuschoice.h\
+ vs_statuschoice.cc\
+ vs_radiogroup.h \
+ vs_radiogroup.cc\
+ vs_size_box.cc \
+ vs_size_box.h \
+ vs_stacked.h \
+ vs_stacked.cc \
+ vs_staticitem.h \
+ vs_staticitem.cc\
+ vs_subtree.h \
+ vs_text_layout.h\
+ vs_text_layout.cc\
+ vs_togglebutton.h\
+ vs_togglebutton.cc\
+ vs_transient.h \
+ vs_transient.cc \
+ vs_treeitem.h \
+ vs_treeitem.cc \
+ vs_util.h \
+ vs_util.cc \
+ vs_table.h \
+ vs_table.cc \
+ vs_tree.h \
+ vs_tree.cc \
+ columnify.h \
+ columnify.cc \
+ transcode.h \
+ transcode.cc
+
+testvscreen_SOURCES= \
+ testvscreen.cc
+
+testvscreen_LDADD=libvscreen.a config/libconf.a $(top_builddir)/src/generic/util/libgeneric-util.a
diff --git a/src/vscreen/README.layout b/src/vscreen/README.layout
new file mode 100644
index 00000000..bc753a83
--- /dev/null
+++ b/src/vscreen/README.layout
@@ -0,0 +1,131 @@
+vscreen/README.layout
+Daniel Burrows <Daniel_Burrows@brown.edu> -- written 7/17/00
+
+ A brief dissertation on the layout system to be used in vscreen. I
+saw ad-hoc fixes starting to accumulate, and I figure that a
+thought-out design document won't hurt at all, even if I have to
+rewrite most of the code to comply with it :-)
+
+So, there is a distinct need for vscreen-widgets to (a) advertise
+information about their desired size, size constraints, und so weiter,
+(b) learn when they have been given a new physical size and adjust
+themselves accordingly (possibly resizing more widgets in a recursive
+manner), and (c) announce when their requested size has changed and
+they might (consequently) need a resize.
+
+ So, here's how we go about doing it. This is not necessarily the
+objectively *best* way, it is merely *a* way that is fairly logical, capable,
+and not overly difficult to implement. Complaints should come with an attached
+and working example of a better layout system, please :-)
+
+SIZE REQUESTS
+
+To request a desired size, widgets should implement the "size_request"
+virtual method. This essentially calculates the desired size and
+returns it. (it would be possible, but IMO too much work, to cache
+this in a member variable. Size requests should happen relatively
+rarely.. *crosses fingers*)
+
+Sizes will be stored as a separate type, in which w and h values have
+the expected meanings (ie, w==0 h==0 means the widget is so small it's
+invisible) In particular, the (0,0) "infinite-resize" hack is NOT
+supported in this system; other mechanisms should be used to make
+widgets occupy all available space.
+
+The desired size will be treated not only as a target size, but also
+(whenever possible) as a *minimum* size. This means that widgets
+which lay other widgets out should never give a widget less size than
+it requests, unless doing so is absolutely necessary (and even then
+they should avoid it :P ) Unfortunately, this may not be
+possible--which means that properly-written widgets should handle
+situations where they are allocated dramatically smaller windows than
+expected and not blow up, summon demons, etc. (well, summoning demons
+is permissible if and only if the user has requested such action) As a
+special case, the widget will be automatically "hidden" (by setting
+its window to NULL) if either the width or height is 0.
+
+GIVING SIZES TO WIDGETS
+
+When a widget's owner assigns it a size, it should call
+widget->alloc_size(x, y, w, h). This routine will set
+all the various `official' things needed to give the widget the
+appropriate coordinates inside the enclosing widget.
+
+alloc_size will make the widget invisible if either w or h is 0, as
+described above.
+
+Widgets which contain other widgets should call alloc_size on all
+their visible children when they themselves have their size allocated
+(which is why this is a virtual method :) )
+
+ANNOUNCING SIZE CHANGES
+
+Calling the "vscreen_queuelayout()" routine will cause a widget to be
+laid out again when the vscreen main loop is re-entered. In the main
+loop of the code (or in a vscreen_poll()), the queue will be examined
+and layout operations performed on the topmost ancestor of each item
+in the queue. (no widget will be laid out more than once -- at least,
+not in the final version :) ) This will take place before queued
+screen redraws, of course :-)
+
+(an interesting note is that since, of course, all widgets have their ultimate
+ ancestor in the toplevel widget, a queued resize just means that that widget
+ is laid out again)
+
+WHEN TO LAYOUT
+
+A signal "do_layout" is available; when this is emitted, widgets should
+recalculate their layout and reassign sizes and windows to child widgets via
+alloc_size() (this may be triggered from within alloc_size)
+
+TABLE LAYOUT
+
+Well, this isn't strictly within the bounds of what I was originally
+planning to says, but it's complicated and bears writing down...
+
+The mechanism for table layout is derived from GTK+ code and -- again --
+is not particularly optimal but should work (mostly). I will
+only describe the computations necessary for rows; the algorithm is
+perfectly symmetrical with respect to rows and columns. (that is,
+rows and columns are orthogonal to one another :-) )
+
+Each algorithm will temporarily store information about the width of
+rows and columns. (this can optionally be cached after the algorithm
+completes, of course)
+
+ REQUISITION:
+
+ (a) The table whose size is being requested will collect all its
+widgets and sort them according to how many rows each occupies.
+
+ (b) The table will iterate over the new list by row count in
+increasing order. For each widget, the total height of the rows it
+spans will be calculated and compared to its requested height. If the
+total width is less than the widget's requested width, the height of
+each row spanned by the widget is incremented by height_diff/nrows,
+where height_diff is the shortfall and nrows is the number of rows the
+widget spans. (doing this is a bit tricky; I like the GTK+ approach,
+which is to iterate over the rows, incrementing each by
+height_diff/nrows, then decrementing height_diff by that amount and
+decrementing nrows by 1)
+
+ ALLOCATION:
+
+ This is trickier, as there may be shortfall or leftover size.
+
+ (a) Initial row sizes are calculated as in REQUISITION
+
+ (b) If the allocated size is larger than the requested size, the
+number of rows which are allowed to be expanded (ie, at least one
+member of the row requests expansion (attributes are determined at
+time of insertion into the table..)) is calculated. The extra size is
+then distributed amongst these rows in the same way that extra size is
+distributed in step (b) above.
+
+ (c) If, on the other hand, the allocated size is less than the
+requested size, the number of rows which are allowed to be SHRUNK is
+calculated, and an attempt is made to shrink the table until it fits
+within the requested space.
+ IMPORTANT NOTE: this may not be successful. In this case, the table
+will bail and simply truncate its lower extremities. However, this
+shouldn't be necessary (cross fingers..) except in extreme circumstances..
diff --git a/src/vscreen/columnify.cc b/src/vscreen/columnify.cc
new file mode 100644
index 00000000..4e9e7c0f
--- /dev/null
+++ b/src/vscreen/columnify.cc
@@ -0,0 +1,180 @@
+// columnify.cc
+//
+// Copyright 2000, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "columnify.h"
+
+#include "transcode.h"
+
+using namespace std;
+
+column_disposition::column_disposition(const std::string &_text,
+ int _minx,
+ const char *encoding)
+ :text(transcode(_text, encoding)), minx(_minx)
+{
+}
+
+wstring columnify(const layout &format, int width)
+{
+ wstring rval;
+
+ layout final_info;
+
+ unsigned int colnum=0;
+ for(layout::const_iterator i=format.begin();
+ i!=format.end();
+ ++i, ++colnum)
+ {
+ assert(colnum<format.size());
+
+ final_info.push_back(column(i->info, i->width, i->expand, i->shrink));
+ }
+
+ // Reconcile the widths.
+ int totalwidth=0;
+ for(layout::iterator i=final_info.begin();
+ i!=final_info.end(); ++i)
+ totalwidth+=i->width;
+
+ if(totalwidth<width)
+ // Yaaay, we had too much space :)
+ // Divvy it up.
+ {
+ int excess=width-totalwidth;
+ int nexpandable=0;
+ int startloc=0;
+
+ // Figure out how to divide the wealth:
+ for(layout::iterator i=final_info.begin();
+ i!=final_info.end(); ++i)
+ if(i->expand)
+ ++nexpandable;
+
+ // Now expand the columns
+ for(layout::iterator i=final_info.begin();
+ i!=final_info.end() && nexpandable>0; ++i)
+ {
+ if(i->expand)
+ {
+ int amt=excess/nexpandable;
+
+ i->width+=amt;
+ excess-=amt;
+ --nexpandable;
+ }
+
+ startloc+=i->width;
+
+ layout::iterator j=i;
+ ++j;
+
+ // FIXME: HACK.
+ if(j!=final_info.end() && startloc<j->info.minx)
+ {
+ int amt2=j->info.minx-startloc;
+ if(amt2>excess)
+ amt2=excess;
+
+ i->width+=amt2;
+ excess-=amt2;
+ }
+ }
+ }
+ else if(totalwidth>width)
+ // Do everything again if we were allocated less space than requested. :(
+ {
+ int nshrinkable=0;
+ int shortfall=totalwidth-width;
+ int startloc=0;
+
+ for(layout::iterator i=final_info.begin();
+ i!=final_info.end(); ++i)
+ if(i->shrink)
+ ++nshrinkable;
+
+ // Shrink the columns
+ for(layout::iterator i=final_info.begin();
+ i!=final_info.end() && nshrinkable>0; ++i)
+ {
+ if(i->shrink)
+ {
+ int amt=shortfall/nshrinkable;
+ if(amt>i->width)
+ amt=i->width;
+
+ i->width-=amt;
+ shortfall-=amt;
+ --nshrinkable;
+ }
+
+ startloc+=i->width;
+ }
+
+ if(shortfall>0) // Egad! Truncate things at random, R->L..
+ for(layout::reverse_iterator i=final_info.rbegin();
+ i!=final_info.rend() && shortfall>0; i++)
+ if(i->width<=shortfall)
+ {
+ shortfall-=i->width;
+ i->width=0;
+ }
+ else
+ {
+ i->width-=shortfall;
+ shortfall=0;
+ }
+ }
+
+ int curwidth=0, nextwidth=0;
+ const int spacewidth=wcwidth(L' ');
+ for(layout::iterator i=final_info.begin();
+ i!=final_info.end(); ++i)
+ {
+ wstring::size_type amt=0;
+ nextwidth+=i->width;
+ for( ; curwidth<nextwidth &&
+ amt<i->info.text.size(); ++amt)
+ {
+ wchar_t wch=i->info.text[amt];
+ // Watch out for wide characters overrunning the column
+ // boundary!
+ if(curwidth+wcwidth(wch)<=nextwidth)
+ {
+ rval+=wch;
+ curwidth+=wcwidth(wch);
+ }
+ else
+ break;
+ }
+ // Is it sane for spacewidth to ever differ from unity?
+ while(curwidth+spacewidth<=nextwidth)
+ {
+ rval+=L' ';
+ curwidth+=spacewidth;
+ }
+ }
+
+ while(curwidth<width)
+ {
+ rval+=L' ';
+ curwidth+=spacewidth;
+ }
+
+ return rval;
+}
diff --git a/src/vscreen/columnify.h b/src/vscreen/columnify.h
new file mode 100644
index 00000000..ffd0cf64
--- /dev/null
+++ b/src/vscreen/columnify.h
@@ -0,0 +1,77 @@
+// columnify.h -*-c++-*-
+//
+// Copyright 2000, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Support for creating column-formatted strings. Columns which exceed their
+// size limit are truncated. Columns are collected into 'groups', each of
+// which starts at a particular location. This allows things such as
+// right-justification of text. If one column group overlaps another, the
+// conflict is resolved by adjusting the column which is situated farthest to
+// the right (by moving it farther to the right).
+//
+// Right-justification is now handled (somewhat hackily) by columnify; to
+// use it, set the start_off to something negative. The group will be aligned
+// so the the right edge of the column is start_off+1 characters from the
+// right-hand screen edge -- so to align something exactly with the right-hand
+// edge of the screen, set start_off to -1.
+
+#ifndef COLUMNIFY_H
+#define COLUMNIFY_H
+
+#include <list>
+#include <string>
+#include <assert.h>
+
+struct column_disposition
+{
+ std::wstring text; // The contents of the columns
+ int minx; // The minimum x value to start this column at (useful for
+ // indenting stuff in trees)
+
+ column_disposition(const std::wstring &_text, int _minx):text(_text), minx(_minx) {}
+
+ /** Generate a column from the multibyte string _text. */
+ column_disposition(const std::string &_text, int _minx,
+ const char *encoding=NULL);
+};
+
+struct column
+{
+ column_disposition info;
+ int width;
+ bool expand, shrink;
+
+ column(const column_disposition &_info, int _width, bool _expand, bool _shrink)
+ :info(_info), width(_width), expand(_expand), shrink(_shrink)
+ {
+ assert(_width>=0);
+ }
+};
+
+typedef std::list<column> column_list;
+
+typedef std::list<column> layout;
+
+/* \return a string formatted as requested. The string will be no
+ * wider than width columns. I could probably use printf-style
+ * strings, but with the groups it would probably turn into a major
+ * pain..
+ */
+std::wstring columnify(const layout &format, int width);
+
+#endif
diff --git a/src/vscreen/config/Makefile.am b/src/vscreen/config/Makefile.am
new file mode 100644
index 00000000..cd876a0e
--- /dev/null
+++ b/src/vscreen/config/Makefile.am
@@ -0,0 +1,18 @@
+MAINTAINERCLEANFILES=Makefile.in
+
+localedir = $(datadir)/locale
+INCLUDES = -Wall @WERROR@ -I$(top_builddir) -I$(srcdir) -I$(top_srcdir) -I$(top_srcdir)/src
+DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
+LDADD = @LIBINTL@
+
+noinst_LIBRARIES=libconf.a
+
+libconf_a_SOURCES=\
+ colors.h \
+ colors.cc \
+ column_definition.h\
+ column_definition.cc\
+ keybindings.h \
+ keybindings.cc \
+ style.h \
+ style.cc
diff --git a/src/vscreen/config/colors.cc b/src/vscreen/config/colors.cc
new file mode 100644
index 00000000..5a0f9c80
--- /dev/null
+++ b/src/vscreen/config/colors.cc
@@ -0,0 +1,100 @@
+// colors.cc -*-c++-*-
+//
+// Copyright 1999-2001, 2003-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "colors.h"
+
+#include <vscreen/curses++.h>
+
+static bool colors_avail=false;
+static bool default_colors_avail = false;
+
+// Simplistic allocation scheme for colors: (fg,bg) => fg*COLORS+bg
+
+void init_colors()
+{
+ if(COLORS == 0 || COLOR_PAIRS < COLORS * COLORS)
+ return;
+
+ colors_avail=true;
+ default_colors_avail = (use_default_colors() != ERR);
+
+ for(short fg=0; fg<COLORS; ++fg)
+ for(short bg=0; bg<COLORS; ++bg)
+ {
+ if(default_colors_avail && fg == bg)
+ init_pair(fg * COLORS + bg, fg, -1);
+ else if(fg == 0 && bg == 0)
+ // do nothing; on some terminals, doing this causes the
+ // cursor to become INVISIBLE, and black-on-black text is a
+ // bad idea anyway..
+ ;
+ /*assume_default_colors(0, 0);*/
+ else
+ init_pair(fg*COLORS+bg, fg, bg);
+ }
+}
+
+int get_color_pair(short fg, short bg)
+{
+ if(!colors_avail)
+ return 0;
+ else
+ {
+ assert(fg >= 0 && bg >= -1 && fg < COLORS && bg < COLORS);
+
+ if(bg == -1)
+ return fg * COLORS + fg;
+ else if(fg == bg && default_colors_avail)
+ // Pick an arbitrary distinct foreground color to match with
+ // the background.
+ {
+ if(bg == COLOR_WHITE)
+ return COLOR_BLACK * COLORS + COLOR_WHITE;
+ else
+ return COLOR_WHITE * COLORS + bg;
+ }
+ else
+ return fg * COLORS + bg;
+ }
+}
+
+int mix_color(short color, short fg, short bg)
+{
+ if(!colors_avail)
+ return 0;
+ else if(fg == -1 && bg == -2)
+ return color & A_COLOR;
+ else
+ {
+ short old_fg = PAIR_NUMBER(color) / COLORS;
+ short old_bg = PAIR_NUMBER(color) % COLORS;
+
+ if(old_fg == old_bg && default_colors_avail)
+ old_bg = -1;
+
+ if(bg == -1 && !default_colors_avail)
+ return 0;
+ else if(fg == -1)
+ return COLOR_PAIR(get_color_pair(old_fg, bg));
+ else if(bg == -2)
+ return COLOR_PAIR(get_color_pair(fg, old_bg));
+ else
+ return COLOR_PAIR(get_color_pair(fg, bg));
+ }
+}
diff --git a/src/vscreen/config/colors.h b/src/vscreen/config/colors.h
new file mode 100644
index 00000000..33fa08ae
--- /dev/null
+++ b/src/vscreen/config/colors.h
@@ -0,0 +1,49 @@
+// colors.h -*-c++-*-
+//
+// Copyright 1999-2001, 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Manages color allocation so as to allow any combination of
+// foreground/background colors to be used. If there aren't enough
+// color pairs available to handle all color combinations, this will
+// act as though no colors are available. NOTE: colors whose
+// foreground and background are the same will be reduced to an
+// arbitrary color of that background; it is expected that the caller
+// will apply A_INVIS to such colors. This is done to conserve color
+// pairs so as to allow the use of the 'default' color.
+
+#ifndef COLORS_H
+#define COLORS_H
+
+/** Set up the colors as we expect them to be. Call this once
+ * when the program starts.
+ */
+void init_colors();
+
+/** \return a color pair for the given foreground and background. */
+int get_color_pair(short fg, short bg);
+
+/** \param color attributes containing the starting color value
+ * \param fg the new foreground (-1 to use color)
+ * \param bg the new background (-2 to use color; -1 to use the default background)
+ *
+ * \return a color pair created by mixing the given foreground
+ * and background into color.
+ */
+int mix_color(short color, short fg, short bg);
+
+#endif
diff --git a/src/vscreen/config/column_definition.cc b/src/vscreen/config/column_definition.cc
new file mode 100644
index 00000000..11577655
--- /dev/null
+++ b/src/vscreen/config/column_definition.cc
@@ -0,0 +1,276 @@
+// column_definition.cc
+//
+// Copyright 2000,2001, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "column_definition.h"
+
+#include <apt-pkg/error.h>
+#include <apt-pkg/strutl.h>
+
+// For _()
+#include <aptitude.h>
+
+using namespace std;
+
+column_parameters::~column_parameters()
+{
+}
+
+int empty_column_parameters::param_count()
+{
+ return 0;
+}
+
+wstring empty_column_parameters::get_param(int n)
+{
+ abort();
+}
+
+column_generator::~column_generator()
+{
+}
+
+column_definition_list *parse_columns(wstring config,
+ column_parser_func parser,
+ column_type_defaults *defaults)
+{
+ column_definition_list *rval=new column_definition_list;
+
+ wstring::size_type start=0;
+ wstring::size_type firstbreak;
+ do
+ {
+ firstbreak=config.find_first_of(L"%#", start);
+ if(firstbreak!=start)
+ {
+ wstring literal=wstring(config,
+ start,
+ firstbreak==config.npos?config.npos:firstbreak-start);
+
+ // Grok backslashes, sort of
+ wstring de_backslashified;
+
+ for(wstring::size_type i=0; i<literal.size(); ++i)
+ {
+ if(literal[i]!=L'\\' || i==literal.size()-1)
+ de_backslashified+=literal[i];
+ else switch(literal[++i])
+ {
+ case L'n':
+ de_backslashified+=L'\n';
+ break;
+ default:
+ de_backslashified+=literal[i];
+ }
+ }
+
+ rval->push_back(column_definition(de_backslashified, false, false));
+ }
+
+ if(firstbreak!=config.npos)
+ {
+ if(config[firstbreak]==L'#')
+ {
+ if(rval->empty())
+ rval->push_back(column_definition(L"", true, false));
+ else
+ rval->back().expand=true;
+
+ firstbreak++;
+ }
+ else if(config[firstbreak]==L'%')
+ {
+ int width=-1;
+ bool dynamic_width=false;
+
+ firstbreak++;
+
+ if(firstbreak==config.size())
+ {
+ _error->Error(_("Formatting marker with missing format code"));
+ delete rval;
+ return NULL;
+ }
+
+ if(iswdigit(config[firstbreak]))
+ {
+ unsigned long val;
+
+ wstring tocvt=L"";
+ while(firstbreak < config.size() && iswdigit(config[firstbreak]))
+ {
+ tocvt+=config[firstbreak];
+ ++firstbreak;
+ }
+
+ if(firstbreak==config.size())
+ {
+ _error->Error(_("Formatting marker with missing format code"));
+ delete rval;
+ return NULL;
+ }
+
+ wchar_t *endptr;
+ val=wcstol(tocvt.c_str(), &endptr, 0);
+ if(*endptr!=L'\0')
+ {
+ _error->Error(_("Bad number in format string: '%ls'"), tocvt.c_str());
+ delete rval;
+ return NULL;
+ }
+
+ width=val;
+ }
+
+ if(config[firstbreak]==L'?')
+ {
+ ++firstbreak;
+ dynamic_width=true;
+ }
+
+ if(config[firstbreak]==L'%')
+ {
+ rval->push_back(column_definition(L"%",
+ false,
+ false));
+ ++firstbreak;
+ }
+ else if(config[firstbreak]==L'#')
+ // Parameter substitution is introduced by %[nn]#:
+ {
+ ++firstbreak;
+
+ if(!iswdigit(config[firstbreak]))
+ {
+ _error->Error(_("Missing parameter number in format string"));
+ delete rval;
+ return NULL;
+ }
+
+ unsigned long val;
+
+ wstring tocvt=L"";
+ while(firstbreak<config.size() && iswdigit(config[firstbreak]))
+ {
+ tocvt+=config[firstbreak];
+ ++firstbreak;
+ }
+
+ wchar_t *endptr;
+ val=wcstol(tocvt.c_str(), &endptr, 0);
+ if(*endptr!=L'\0')
+ {
+ _error->Error(_("Bad number in format string: '%ls'"), tocvt.c_str());
+ delete rval;
+ return NULL;
+ }
+
+ if(val<1)
+ {
+ _error->Error(_("Parameter numbers must be 1 or greater, not %ld"), val);
+ delete rval;
+ return NULL;
+ }
+
+ // Default for parameters is to be dynamic.
+ if(width == -1)
+ {
+ width=0;
+ dynamic_width=true;
+ }
+
+ rval->push_back(column_definition(column_definition::COLUMN_PARAM,
+ val-1,
+ width,
+ false,
+ false,
+ dynamic_width));
+ }
+ else
+ {
+ int itemtype=parser(config[firstbreak]);
+ ++firstbreak;
+
+ if(itemtype==-1)
+ {
+ _error->Error(_("Unknown formatting code '%lc'"),
+ config[firstbreak]);
+ delete rval;
+ return NULL;
+ }
+
+ if(width==-1)
+ width=defaults[itemtype].width;
+
+ rval->push_back(column_definition(column_definition::COLUMN_GENERATED,
+ itemtype,
+ width,
+ defaults[itemtype].expand,
+ defaults[itemtype].shrink,
+ dynamic_width));
+ }
+ }
+ start=firstbreak;
+ }
+ } while(firstbreak!=config.npos);
+ return rval;
+}
+
+wstring column_generator::layout_columns(unsigned int width,
+ column_parameters &p)
+{
+ layout l;
+
+ for(column_definition_list::iterator j=columns.begin();
+ j!=columns.end();
+ j++)
+ {
+ column_disposition disp(L"", 0);
+ unsigned int width;
+
+ if(j->type == column_definition::COLUMN_LITERAL)
+ {
+ disp = column_disposition(j->arg, 0);
+ width = wcswidth(j->arg.c_str(), j->arg.size());
+ }
+ else
+ {
+ assert(j->type == column_definition::COLUMN_GENERATED ||
+ j->type == column_definition::COLUMN_PARAM);
+
+ if(j->type == column_definition::COLUMN_GENERATED)
+ disp = setup_column(j->ival);
+ else
+ {
+ if(p.param_count() <= j->ival)
+ disp = column_disposition(_("Bad format parameter"), 0);
+ else
+ disp = column_disposition(p.get_param(j->ival), 0);
+ }
+
+ if(j->dynamic_size)
+ width = wcswidth(disp.text.c_str(), disp.text.size());
+ else
+ width = j->width;
+ }
+
+ l.push_back(column(disp, width, j->expand, j->shrink));
+ }
+
+ return columnify(l, width);
+}
diff --git a/src/vscreen/config/column_definition.h b/src/vscreen/config/column_definition.h
new file mode 100644
index 00000000..b2984c7d
--- /dev/null
+++ b/src/vscreen/config/column_definition.h
@@ -0,0 +1,141 @@
+// column_definition.h -*-c++-*-
+//
+// Copyright 2000, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// This routine provides a general interface for parsing configuration data
+// about a column format and later instantiating that information. The caller
+// has to provide some information, in the form of tables and callbacks, that's
+// used to do the actual formatting. (the callbacks are actually classes,
+// since we need closureish behavior and classes are the best way to hack that
+// into C++)
+//
+// Originally arbitrary strings could be used to represent column information.
+// This was cute and readable, but not very flexible. So I've switched to a
+// "printf" style; the major reason is that this allows arbitrary strings to
+// be embedded in the format. This also means that the "padding" parmeters
+// that I used before are no longer necessary, and eliminates various nasty
+// hacks.
+//
+// Column types are integers; -1 is reserved for internal use. Column types
+// can take arguments (although this isn't implemented yet, it's used
+// internally for the "literal" column type)
+
+#ifndef COLUMN_DEFINITION_H
+#define COLUMN_DEFINITION_H
+
+#include <list>
+#include <string>
+
+#include <assert.h>
+
+#include <vscreen/columnify.h>
+
+/** Defined the default settings for a particular column type. */
+struct column_type_defaults
+{
+ unsigned int width;
+ bool expand, shrink;
+};
+
+/** Abstraction of the parameters passed into the layout process. */
+class column_parameters
+{
+public:
+ virtual int param_count()=0;
+ virtual std::wstring get_param(int n)=0;
+
+ virtual ~column_parameters();
+};
+
+/** An empty list of parameters. */
+class empty_column_parameters : public column_parameters
+{
+public:
+ int param_count();
+ std::wstring get_param(int n);
+};
+
+/** Defines how a single column is to be generated. */
+struct column_definition
+{
+ // A literal column is taken from a literal string passed as a
+ // parameter. A generated one is created by calling a virtual
+ // method. A parameterized column is taken from a column_parameters
+ // object passed into the layout method.
+ enum column_type {COLUMN_LITERAL, COLUMN_GENERATED, COLUMN_PARAM};
+ column_type type;
+
+ // For parametric columns, this is the parameter number;
+ // for generated ones, it's the type of column:
+ int ival;
+
+ // For literal columns only:
+ std::wstring arg;
+
+ // For generated or parametric columns:
+ unsigned int width;
+ bool expand:1, shrink:1;
+
+ // For generated or parametric columns. If \b true, "width"
+ // will be ignored and the true width of the incoming string will
+ // be given to the layout algorithm.
+ bool dynamic_size:1;
+
+ column_definition(const std::wstring &_arg, bool _expand, bool _shrink)
+ :type(COLUMN_LITERAL), arg(_arg), expand(_expand), shrink(_shrink)
+ {
+ }
+
+ column_definition(column_type _type,
+ int _ival, int _width, bool _expand, bool _shrink,
+ bool _dynamic_size)
+ :type(_type), ival(_ival), width(_width),
+ expand(_expand), shrink(_shrink), dynamic_size(_dynamic_size)
+ {
+ assert(_width>=0);
+ }
+};
+
+typedef std::list<column_definition> column_definition_list;
+
+typedef int (*column_parser_func)(char id);
+
+class column_generator
+// Stores the information needed to parse and then generate columns.
+{
+ column_definition_list columns;
+protected:
+ virtual column_disposition setup_column(int type)=0;
+ // Sets up a column of the given type (the width field may be overridden?)
+public:
+ column_generator(const column_definition_list &_columns)
+ :columns(_columns) {}
+
+ virtual ~column_generator();
+
+ std::wstring layout_columns(unsigned int width,
+ column_parameters &p);
+ // Lays out the columns into columns.
+};
+
+column_definition_list *parse_columns(std::wstring config,
+ column_parser_func parser,
+ column_type_defaults *defaults);
+// Allocates the array itself; the caller must delete[] it.
+
+#endif
diff --git a/src/vscreen/config/keybindings.cc b/src/vscreen/config/keybindings.cc
new file mode 100644
index 00000000..a9fdc9c0
--- /dev/null
+++ b/src/vscreen/config/keybindings.cc
@@ -0,0 +1,424 @@
+// keybindings.h, -*-c++-*-
+//
+// Copyright 1999-2001, 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// A global repository for keybindings.
+
+#include "keybindings.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// For _()
+#include <aptitude.h>
+
+#ifdef HAVE_LIBAPT_PKG
+#include <apt-pkg/error.h>
+#endif
+
+#include <ctype.h>
+
+#include <map>
+
+using namespace std;
+using namespace __gnu_cxx;
+
+keybindings global_bindings;
+
+map<wstring, key> keynames;
+map<wstring, key> s_keynames;
+// For simplicity, we use the convention that the names are stored in
+// lowercase; however, the routines to parse keys take this into account and
+// convert the input to lowercase before checking it.
+// FIXME: Function keys (F0-Fx) really ought to be handled specially
+map<key, wstring> rev_keynames;
+
+bool key_tables_initialized=false;
+
+void init_key_tables()
+{
+ if(key_tables_initialized)
+ return;
+
+ key_tables_initialized=true;
+
+ keynames[L"tab"]=key(L'\t', false);
+ rev_keynames[key(L'\t', false)]=L"tab";
+ keynames[L"space"]=key(L' ', false);
+ rev_keynames[key(L' ', false)]=L"space";
+
+ keynames[L"comma"]=key(L',', false);
+ rev_keynames[key(L',', false)]=L"comma";
+
+ keynames[L"break"]=key(KEY_BREAK, true);
+ rev_keynames[key(KEY_BREAK, true)]=L"break";
+ keynames[L"down"]=key(KEY_DOWN, true);
+ rev_keynames[key(KEY_DOWN, true)]=L"down";
+ keynames[L"up"]=key(KEY_UP, true);
+ rev_keynames[key(KEY_UP, true)]=L"up";
+ keynames[L"left"]=key(KEY_LEFT, true);
+ rev_keynames[key(KEY_LEFT, true)]=L"left";
+ keynames[L"right"]=key(KEY_RIGHT, true);
+ rev_keynames[key(KEY_RIGHT, true)]=L"right";
+ keynames[L"home"]=key(KEY_HOME, true);
+ rev_keynames[key(KEY_HOME, true)]=L"home";
+ keynames[L"backspace"]=key(KEY_BACKSPACE, true);
+ rev_keynames[key(KEY_BACKSPACE, true)]=L"backspace";
+ keynames[L"f0"]=key(KEY_F(0), true);
+ rev_keynames[key(KEY_F(0), true)]=L"f0";
+ keynames[L"f1"]=key(KEY_F(1), true);
+ rev_keynames[key(KEY_F(1), true)]=L"f1";
+ keynames[L"f2"]=key(KEY_F(2), true);
+ rev_keynames[key(KEY_F(2), true)]=L"f2";
+ keynames[L"f3"]=key(KEY_F(3), true);
+ rev_keynames[key(KEY_F(3), true)]=L"f3";
+ keynames[L"f4"]=key(KEY_F(4), true);
+ rev_keynames[key(KEY_F(4), true)]=L"f4";
+ keynames[L"f5"]=key(KEY_F(5), true);
+ rev_keynames[key(KEY_F(5), true)]=L"f5";
+ keynames[L"f6"]=key(KEY_F(6), true);
+ rev_keynames[key(KEY_F(6), true)]=L"f6";
+ keynames[L"f7"]=key(KEY_F(7), true);
+ rev_keynames[key(KEY_F(7), true)]=L"f7";
+ keynames[L"f8"]=key(KEY_F(8), true);
+ rev_keynames[key(KEY_F(8), true)]=L"f8";
+ keynames[L"f9"]=key(KEY_F(9), true);
+ rev_keynames[key(KEY_F(9), true)]=L"f9";
+ keynames[L"f10"]=key(KEY_F(10), true);
+ rev_keynames[key(KEY_F(10), true)]=L"f10";
+ keynames[L"delete_line"]=key(KEY_DL, true);
+ rev_keynames[key(KEY_DL, true)]=L"delete_line";
+ keynames[L"insert_line"]=key(KEY_IL, true);
+ rev_keynames[key(KEY_IL, true)]=L"insert_line";
+ keynames[L"delete"]=key(KEY_DC, true);
+ rev_keynames[key(KEY_DC, true)]=L"delete";
+ keynames[L"insert"]=key(KEY_IC, true);
+ rev_keynames[key(KEY_IC, true)]=L"insert";
+ keynames[L"insert_exit"]=key(KEY_EIC, true);
+ rev_keynames[key(KEY_EIC, true)]=L"insert_exit";
+ keynames[L"clear"]=key(KEY_CLEAR, true);
+ rev_keynames[key(KEY_CLEAR, true)]=L"clear";
+ keynames[L"clear_eos"]=key(KEY_EOS, true);
+ rev_keynames[key(KEY_EOS, true)]=L"clear_eos";
+ keynames[L"clear_eol"]=key(KEY_EOL, true);
+ rev_keynames[key(KEY_EOL, true)]=L"clear_eol";
+ keynames[L"scrollf"]=key(KEY_SF, true);
+ rev_keynames[key(KEY_SF, true)]=L"scrollf";
+ keynames[L"scrollr"]=key(KEY_SR, true);
+ rev_keynames[key(KEY_SR, true)]=L"scrollr";
+ keynames[L"pagedown"]=key(KEY_NPAGE, true);
+ rev_keynames[key(KEY_NPAGE, true)]=L"pagedown";
+ keynames[L"pageup"]=key(KEY_PPAGE, true);
+ rev_keynames[key(KEY_PPAGE, true)]=L"pageup";
+ keynames[L"enter"]=key(KEY_ENTER, true);
+ rev_keynames[key(KEY_ENTER, true)]=L"enter";
+ keynames[L"return"]=key(KEY_ENTER, true);
+ keynames[L"print"]=key(KEY_PRINT, true);
+ rev_keynames[key(KEY_PRINT, true)]=L"print";
+ keynames[L"a1"]=key(KEY_A1, true);
+ rev_keynames[key(KEY_A1, true)]=L"a1";
+ keynames[L"a3"]=key(KEY_A3, true);
+ rev_keynames[key(KEY_A3, true)]=L"a3";
+ keynames[L"b2"]=key(KEY_B2, true);
+ rev_keynames[key(KEY_B2, true)]=L"b2";
+ keynames[L"c1"]=key(KEY_C1, true);
+ rev_keynames[key(KEY_C1, true)]=L"c1";
+ keynames[L"c3"]=key(KEY_C3, true);
+ rev_keynames[key(KEY_C3, true)]=L"c3";
+ keynames[L"backtab"]=key(KEY_BTAB, true);
+ rev_keynames[key(KEY_BTAB, true)]=L"backtab";
+ keynames[L"begin"]=key(KEY_BEG, true);
+ rev_keynames[key(KEY_BEG, true)]=L"begin";
+ keynames[L"cancel"]=key(KEY_CANCEL, true);
+ rev_keynames[key(KEY_CANCEL, true)]=L"cancel";
+ keynames[L"close"]=key(KEY_CLOSE, true);
+ rev_keynames[key(KEY_CLOSE, true)]=L"close";
+ keynames[L"command"]=key(KEY_COMMAND, true);
+ rev_keynames[key(KEY_COMMAND, true)]=L"command";
+ keynames[L"copy"]=key(KEY_COPY, true);
+ rev_keynames[key(KEY_COPY, true)]=L"copy";
+ keynames[L"create"]=key(KEY_CREATE, true);
+ rev_keynames[key(KEY_CREATE, true)]=L"create";
+ keynames[L"end"]=key(KEY_END, true);
+ rev_keynames[key(KEY_END, true)]=L"end";
+ keynames[L"exit"]=key(KEY_EXIT, true);
+ rev_keynames[key(KEY_EXIT, true)]=L"exit";
+ keynames[L"find"]=key(KEY_FIND, true);
+ rev_keynames[key(KEY_FIND, true)]=L"find";
+ keynames[L"help"]=key(KEY_HELP, true);
+ rev_keynames[key(KEY_HELP, true)]=L"help";
+ keynames[L"mark"]=key(KEY_MARK, true);
+ rev_keynames[key(KEY_MARK, true)]=L"mark";
+ keynames[L"message"]=key(KEY_MESSAGE, true);
+ rev_keynames[key(KEY_MESSAGE, true)]=L"message";
+ keynames[L"move"]=key(KEY_MOVE, true);
+ rev_keynames[key(KEY_MOVE, true)]=L"move";
+ keynames[L"next"]=key(KEY_NEXT, true);
+ rev_keynames[key(KEY_NEXT, true)]=L"next";
+ keynames[L"open"]=key(KEY_OPEN, true);
+ rev_keynames[key(KEY_OPEN, true)]=L"open";
+ keynames[L"options"]=key(KEY_OPTIONS, true);
+ rev_keynames[key(KEY_OPTIONS, true)]=L"options";
+ keynames[L"previous"]=key(KEY_PREVIOUS, true);
+ rev_keynames[key(KEY_PREVIOUS, true)]=L"previous";
+ keynames[L"redo"]=key(KEY_REDO, true);
+ rev_keynames[key(KEY_REDO, true)]=L"redo";
+ keynames[L"reference"]=key(KEY_REFERENCE, true);
+ rev_keynames[key(KEY_REFERENCE, true)]=L"reference";
+ keynames[L"refresh"]=key(KEY_REFRESH, true);
+ rev_keynames[key(KEY_REFRESH, true)]=L"refresh";
+ keynames[L"replace"]=key(KEY_REPLACE, true);
+ rev_keynames[key(KEY_REPLACE, true)]=L"replace";
+ keynames[L"restart"]=key(KEY_RESTART, true);
+ rev_keynames[key(KEY_RESTART, true)]=L"restart";
+ keynames[L"resume"]=key(KEY_RESUME, true);
+ rev_keynames[key(KEY_RESUME, true)]=L"resume";
+ keynames[L"save"]=key(KEY_SAVE, true);
+ rev_keynames[key(KEY_SAVE, true)]=L"save";
+ keynames[L"select"]=key(KEY_SELECT, true);
+ rev_keynames[key(KEY_SELECT, true)]=L"select";
+ keynames[L"suspend"]=key(KEY_SUSPEND, true);
+ rev_keynames[key(KEY_SUSPEND, true)]=L"suspend";
+ keynames[L"undo"]=key(KEY_UNDO, true);
+ rev_keynames[key(KEY_UNDO, true)]=L"undo";
+
+ s_keynames[L"begin"]=key(KEY_SBEG, true);
+ rev_keynames[key(KEY_SBEG, true)]=L"begin";
+ s_keynames[L"cancel"]=key(KEY_SCANCEL, true);
+ rev_keynames[key(KEY_SCANCEL, true)]=L"cancel";
+ s_keynames[L"command"]=key(KEY_SCOMMAND, true);
+ rev_keynames[key(KEY_SCOMMAND, true)]=L"command";
+ s_keynames[L"copy"]=key(KEY_SCOPY, true);
+ rev_keynames[key(KEY_SCOPY, true)]=L"copy";
+ s_keynames[L"create"]=key(KEY_SCREATE, true);
+ rev_keynames[key(KEY_SCREATE, true)]=L"create";
+ s_keynames[L"delete"]=key(KEY_SDC, true);
+ rev_keynames[key(KEY_SDC, true)]=L"delete";
+ s_keynames[L"delete_line"]=key(KEY_SDL, true);
+ rev_keynames[key(KEY_SDL, true)]=L"delete_line";
+ s_keynames[L"end"]=key(KEY_SEND, true);
+ rev_keynames[key(KEY_SEND, true)]=L"end";
+ s_keynames[L"clear_eol"]=key(KEY_SEOL, true);
+ rev_keynames[key(KEY_SEOL, true)]=L"clear_eol";
+ s_keynames[L"exit"]=key(KEY_SEXIT, true);
+ rev_keynames[key(KEY_SEXIT, true)]=L"exit";
+ s_keynames[L"find"]=key(KEY_SFIND, true);
+ rev_keynames[key(KEY_SFIND, true)]=L"find";
+ s_keynames[L"help"]=key(KEY_SHELP, true);
+ rev_keynames[key(KEY_SHELP, true)]=L"help";
+ s_keynames[L"home"]=key(KEY_SHOME, true);
+ rev_keynames[key(KEY_SHOME, true)]=L"home";
+ s_keynames[L"insert"]=key(KEY_SIC, true);
+ rev_keynames[key(KEY_SIC, true)]=L"insert";
+ s_keynames[L"left"]=key(KEY_SLEFT, true);
+ rev_keynames[key(KEY_SLEFT, true)]=L"left";
+ s_keynames[L"message"]=key(KEY_SMESSAGE, true);
+ rev_keynames[key(KEY_SMESSAGE, true)]=L"message";
+ s_keynames[L"move"]=key(KEY_SMOVE, true);
+ rev_keynames[key(KEY_SMOVE, true)]=L"move";
+ s_keynames[L"next"]=key(KEY_SNEXT, true);
+ rev_keynames[key(KEY_SNEXT, true)]=L"next";
+ s_keynames[L"options"]=key(KEY_SOPTIONS, true);
+ rev_keynames[key(KEY_SOPTIONS, true)]=L"options";
+ s_keynames[L"previous"]=key(KEY_SPREVIOUS, true);
+ rev_keynames[key(KEY_SPREVIOUS, true)]=L"previous";
+ s_keynames[L"print"]=key(KEY_SPRINT, true);
+ rev_keynames[key(KEY_SPRINT, true)]=L"print";
+ s_keynames[L"redo"]=key(KEY_SREDO, true);
+ rev_keynames[key(KEY_SREDO, true)]=L"redo";
+ s_keynames[L"replace"]=key(KEY_SREPLACE, true);
+ rev_keynames[key(KEY_SREPLACE, true)]=L"replace";
+ s_keynames[L"right"]=key(KEY_SRIGHT, true);
+ rev_keynames[key(KEY_SRIGHT, true)]=L"right";
+ s_keynames[L"resume"]=key(KEY_SRSUME, true);
+ rev_keynames[key(KEY_SRSUME, true)]=L"resume";
+ s_keynames[L"save"]=key(KEY_SSAVE, true);
+ rev_keynames[key(KEY_SSAVE, true)]=L"save";
+ s_keynames[L"suspend"]=key(KEY_SSUSPEND, true);
+ rev_keynames[key(KEY_SSUSPEND, true)]=L"suspend";
+ s_keynames[L"undo"]=key(KEY_SUNDO, true);
+ rev_keynames[key(KEY_SUNDO, true)]=L"undo";
+}
+
+void keybindings::set(string tag, keybinding strokes)
+{
+ keymap[tag]=strokes;
+}
+
+bool keybindings::key_matches(const key &k, string tag)
+{
+ hash_map<string, keybinding>::iterator found=keymap.find(tag);
+ if(found==keymap.end())
+ return parent?parent->key_matches(k, tag):false;
+ else
+ {
+ for(keybinding::iterator i=found->second.begin(); i!=found->second.end(); i++)
+ {
+ if(*i==key(KEY_ENTER, true))
+ {
+ if(k==key(KEY_ENTER, true) ||
+ k==key(L'\r', false) || k==key(L'\n', false))
+ return true;
+ }
+ else if(k==*i)
+ return true;
+ }
+ return false;
+ }
+}
+
+key parse_key(wstring keystr)
+{
+ bool sfound=false,cfound=false,afound=false;
+ wstring tmpstr=keystr;
+ key rval((wint_t) ERR, true);
+
+ init_key_tables();
+
+ while(tmpstr.size()>2 && tmpstr[1]==L'-')
+ {
+ switch(tmpstr[0])
+ {
+ case L's':
+ case L'S':
+ sfound=true;
+ break;
+ case L'a':
+ case L'A':
+ case L'm':
+ case L'M':
+ afound=true;
+ break;
+ case L'c':
+ case L'C':
+ cfound=true;
+ break;
+ default:
+#ifdef HAVE_LIBAPT_PKG
+ _error->Error(_("Cannot parse key description: %ls"), keystr.c_str());
+#endif
+ return key((wint_t) ERR, true);
+ }
+ tmpstr=wstring(tmpstr,2);
+ }
+
+ if(tmpstr.size()==0)
+ {
+#ifdef HAVE_LIBAPT_PKG
+ _error->Error(_("Invalid null keybinding"));
+#endif
+ return key((wint_t) ERR, true);
+ }
+
+ if(cfound && tmpstr.size()>1)
+ {
+#ifdef HAVE_LIBAPT_PKG
+ _error->Error(_("Sorry, control modifiers may not be used with unprintable characters"));
+#endif
+ return key((wint_t) ERR, true);
+ }
+
+ if(tmpstr.size()==1)
+ {
+ wint_t ch=sfound?towupper(tmpstr[0]):tmpstr[0];
+ // How do control-keys interact with wide characters?
+ if(cfound)
+ return KEY_CTRL(ch);
+ if(afound)
+ return KEY_ALT(ch);
+ return rval;
+ }
+ else
+ {
+ for(unsigned int i=0; i<tmpstr.size(); i++)
+ tmpstr[i]=towlower(tmpstr[i]);
+ map<wstring, key>::iterator found=(sfound?s_keynames:keynames).find(tmpstr);
+ if(found==(sfound?s_keynames:keynames).end())
+ return key((wint_t) ERR, true);
+ else
+ {
+ rval=found->second;
+ if(afound)
+ rval=KEY_ALT(rval.ch);
+
+ return rval;
+ }
+ }
+}
+
+wstring keyname(const key &k)
+{
+ init_key_tables();
+
+ // This is a nasty special-case..all of this stuff is nasty special-cases..
+ // someday I need to learn the underlying logic, if there is any..
+ if(k.ch==31 && k.function_key)
+ return L"C-_";
+
+ // Control characters are characters whose 64-place is 0. (what
+ // about others? hm)
+ if(k.ch < 32 && !k.function_key)
+ return L"C-"+keyname(key(k.ch|64, false));
+ // and 200 seems to be the ALT-character?
+ else if(k.ch&0x200)
+ return L"A-"+keyname(key(k.ch&~0x200, false));
+ else
+ {
+ map<key, wstring>::iterator found=rev_keynames.find(k);
+
+ if(found!=rev_keynames.end())
+ return found->second;
+ else
+ {
+ wchar_t tmp[2];
+ tmp[0]=k.ch;
+ tmp[1]=0;
+ return wstring(tmp);
+ }
+ }
+}
+
+wstring readable_keyname(const key &k)
+{
+ if(k == key(L',', false))
+ return L",";
+ else
+ return keyname(k);
+}
+
+// Doesn't return all available bindings as that gets way too long.
+wstring keybindings::keyname(const string &tag)
+{
+ hash_map<string, keybinding>::iterator found=keymap.find(tag);
+
+ if(found!=keymap.end())
+ return ::keyname(found->second.front());
+ else
+ return L"";
+}
+
+wstring keybindings::readable_keyname(const string &tag)
+{
+ hash_map<string, keybinding>::iterator found=keymap.find(tag);
+
+ if(found != keymap.end())
+ return ::readable_keyname(found->second.front());
+ else
+ return L"";
+}
diff --git a/src/vscreen/config/keybindings.h b/src/vscreen/config/keybindings.h
new file mode 100644
index 00000000..119ca161
--- /dev/null
+++ b/src/vscreen/config/keybindings.h
@@ -0,0 +1,157 @@
+// keybindings.h, -*-c++-*-
+//
+// Copyright 1999-2001, 2003-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#ifndef KEYBINDINGS_H
+#define KEYBINDINGS_H
+
+#include <string>
+#include <list>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_HASH_MAP
+#include <hash_map>
+#else
+#ifdef HAVE_EXT_HASH_MAP
+#include <ext/hash_map>
+#else
+// Fallback to the non-hashing map class
+#include <map>
+#define hash_map map
+#endif
+#endif
+
+#include <generic/util/strhash.h>
+#include <vscreen/curses++.h>
+
+/** Represents a keystroke as seen by curses. Since the function keys
+ * can overlap Unicode codepoints, we need to include a value that
+ * distinguishes them.
+ */
+struct key
+{
+ /** The key code. */
+ wint_t ch;
+
+ /** If \b true, this is a function key. */
+ bool function_key;
+
+ key()
+ :ch((wint_t) ERR), function_key(true)
+ {
+ }
+
+ key(wint_t _ch, bool _function_key)
+ :ch(_ch), function_key(_function_key)
+ {
+ }
+
+ /** Lexicographic ordering on keys. */
+ bool operator<(const key &other) const
+ {
+ return ch < other.ch || (ch == other.ch &&
+ !function_key && other.function_key);
+ }
+
+ bool operator==(const key &other) const
+ {
+ return ch == other.ch && function_key == other.function_key;
+ }
+};
+
+typedef std::vector<key> keybinding;
+
+class keybindings
+{
+ HASH_NAMESPACE::hash_map<std::string, keybinding> keymap;
+
+ keybindings *parent;
+
+ // It's way too easy to accidentally invoke the automatic copy
+ // constructor instead of the real one.
+ keybindings(const keybindings &_parent);
+public:
+ keybindings(keybindings *_parent=NULL):parent(_parent) {}
+
+ /** \return the first binding of the given key, in a form suitable
+ * for inclusion in a config file.
+ */
+ std::wstring keyname(const std::string &tag);
+
+
+ /** \return a human-readable string identifying the given keystroke
+ * (as opposed to 'keyname', which is a strict reverse mapping).
+ */
+ std::wstring readable_keyname(const std::string &tag);
+
+ keybinding get(std::string tag)
+ // Returns the keybinding for the given string. Almost never needed.
+ {
+ HASH_NAMESPACE::hash_map<std::string, keybinding>::iterator found=keymap.find(tag);
+
+ if(found==keymap.end())
+ return keybinding();
+ else
+ return found->second;
+ }
+
+ void set(std::string tag, keybinding strokes);
+ // Adds a setting for the given binding, clobbering whatever was there
+ // previously.
+
+ void set(std::string tag, const key &stroke)
+ {
+ keybinding strokes;
+ strokes.push_back(stroke);
+ set(tag, strokes);
+ }
+
+ bool key_matches(const key &k, std::string tag);
+ // Tests whether the given keystroke matches the keybinding with the given
+ // name. If no keybinding by that name exists, the match fails.
+};
+
+key parse_key(std::wstring keystr);
+// Parses a string to a keycode. Returns ERR if the parse fails.
+
+std::wstring keyname(const key &k);
+// Returns a string identifying the given keystroke.
+
+/** \return a human-readable string identifying the given keystroke
+ * (as opposed to 'keyname', which is a strict reverse mapping).
+ */
+std::wstring readable_keyname(const key &k);
+
+extern keybindings global_bindings;
+// For now, this is where the global bindings are stored (I might want to move
+// it in the future, hmmm..)
+
+// Stolen from pinfo. I don't like the looks of it, but presumably it works
+// (in some circumstances). This is a FIXME, btw :)
+/* adapted from Midnight Commander */
+
+// Having read a bit more, it appears that the control modifier
+// clears bits 5 and 4. I think KEY_ALT is utterly broken.
+#define KEY_CTRL(x) key(((x)&~(64|32)), false)
+#define KEY_ALT(x) key((0x200 | (x)), false)
+
+
+#endif
diff --git a/src/vscreen/config/style.cc b/src/vscreen/config/style.cc
new file mode 100644
index 00000000..66f13dec
--- /dev/null
+++ b/src/vscreen/config/style.cc
@@ -0,0 +1,43 @@
+// style.cc
+//
+// Copyright (C) 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "style.h"
+
+// Be lazy, use the standard map.
+#include <map>
+
+using namespace std;
+
+map<string, style> styles;
+
+const style &get_style(const std::string &name)
+{
+ static style null_style;
+ map<string, style>::const_iterator found=styles.find(name);
+
+ if(found == styles.end())
+ return null_style;
+ else
+ return found->second;
+}
+
+void set_style(const std::string &name, const style &style)
+{
+ styles[name]=style;
+}
diff --git a/src/vscreen/config/style.h b/src/vscreen/config/style.h
new file mode 100644
index 00000000..db51b440
--- /dev/null
+++ b/src/vscreen/config/style.h
@@ -0,0 +1,249 @@
+// style.h -*-c++-*-
+//
+// Copyright (C) 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#ifndef STYLE_H
+#define STYLE_H
+
+#include <ncursesw/curses.h>
+
+#include <vscreen/curses++.h>
+
+#include <string>
+
+#include "colors.h"
+
+#include <assert.h>
+
+/** A "style" is a setting to be applied to a display element (widget,
+ * text, etc). This means color (foreground and background) and
+ * attributes. A style may contain settings for any or all of these;
+ * settings which are unspecified are taken from the context in which
+ * the display element occurs. For instance, the "button
+ * highlighted" style might simply toggle the REVERSE flag.
+ *
+ * The interface of styles is high-level: each style is viewed as a
+ * function that applies to the current text attributes; you can also
+ * extract the style's "current" state in order to merge it with a
+ * cchar (this is the style applied to the "null" or default style).
+ * However, as there are only a relatively limited number of things
+ * each style can change, the representation doesn't rely on virtual
+ * functions or even on keeping an array of operations to be applied;
+ * instead, it just keeps a summary of the operations it represents.
+ */
+class style
+{
+ /** The foreground color to be used. (if negative, no change) */
+ short fg;
+ /** The background color to be used. (if -2, no change; if -1, the
+ * 'default' color)
+ */
+ short bg;
+
+ /** Attributes to set. */
+ attr_t set_attrs;
+ /** Attributes to clear. */
+ attr_t clear_attrs;
+ /** Attributes to flip. These are always applied after set_attrs
+ * and clear_attrs.
+ */
+ attr_t flip_attrs;
+
+ // Note: it is assumed that set_attrs and clear_attrs are pairwise
+ // disjoint.
+
+public:
+ /** Initialize an "empty" style. */
+ style():fg(-1), bg(-2), set_attrs(0), clear_attrs(0), flip_attrs(0)
+ {
+ }
+
+ /** Set the foreground color. There is no change if the
+ * new foreground color is "empty".
+ */
+ void set_fg(short _fg) {if(_fg >= 0) fg=_fg;}
+
+ /** Set the background color. There is no change if the
+ * new background color is "empty".
+ */
+ void set_bg(short _bg) {if(_bg >= -1) bg = _bg;}
+
+ /** Set the given attribute(s). */
+ void attrs_on(attr_t attrs)
+ {
+ set_attrs|=attrs;
+ clear_attrs&=~attrs;
+ flip_attrs&=~attrs;
+ }
+
+ /** Clear the given attribute(s). */
+ void attrs_off(attr_t attrs)
+ {
+ clear_attrs|=attrs;
+ set_attrs&=~attrs;
+ flip_attrs&=~attrs;
+ }
+
+ /** Flip the given attribute(s). */
+ void attrs_flip(attr_t attrs)
+ {
+ flip_attrs^=attrs;
+ }
+
+ /** Update this style by applying the settings in the other
+ * style.
+ */
+ void apply_style(const style &other)
+ {
+ set_fg(other.fg);
+ set_bg(other.bg);
+ attrs_on(other.set_attrs);
+ attrs_off(other.clear_attrs);
+ attrs_flip(other.flip_attrs);
+ }
+
+ /** \return a new style formed by composing this style with other.
+ * Note that this is a noncommutative operator!
+ */
+ style operator+(const style &other) const
+ {
+ style rval(*this);
+ rval+=other;
+ return rval;
+ }
+
+ /** Shorthand for apply_style. */
+ style &operator+=(const style &other)
+ {
+ apply_style(other);
+ return *this;
+ }
+
+ bool operator==(const style &other) const
+ {
+ return fg == other.fg && bg == other.bg &&
+ set_attrs == other.set_attrs && clear_attrs == other.clear_attrs &&
+ flip_attrs == other.flip_attrs;
+ }
+
+ bool operator!=(const style &other) const
+ {
+ return fg != other.fg || bg != other.bg ||
+ set_attrs != other.set_attrs || clear_attrs != other.clear_attrs ||
+ flip_attrs != other.flip_attrs;
+ }
+
+ /** \return the foreground color. */
+ short get_fg() const {return fg<0?0:fg;}
+ /** \return the background color. */
+ short get_bg() const {return bg<0?0:bg;}
+ /** \return the current attributes. */
+ attr_t get_attrs() const
+ {
+ attr_t rval=0;
+ rval|=set_attrs;
+ rval&=~clear_attrs;
+ rval^=flip_attrs;
+ rval|=mix_color(0, fg, bg);
+ if(fg == bg)
+ rval |= A_INVIS;
+ return rval;
+ }
+
+ /** \return the given character with its attributes updated with ours. */
+ chtype apply_to(chtype ch) const
+ {
+ // Relies somewhat on the bitwise representation of attributes;
+ // the multicharacter-capable stuff needed for utf8 will make this
+ // go away (for better or for worse..)
+ return (ch & A_CHARTEXT) |
+ mix_color(ch, fg, bg) |
+ ((((ch & ~ (A_CHARTEXT | A_COLOR)) | set_attrs) & ~clear_attrs) ^ flip_attrs);
+ }
+
+ /** \return the given character with its attributes updated with ours. */
+ wchtype apply_to(wchtype ch) const
+ {
+ // Relies somewhat on the bitwise representation of attributes;
+ // the multicharacter-capable stuff needed for utf8 will make this
+ // go away (for better or for worse..)
+ return wchtype(ch.ch,
+ mix_color(ch.attrs, fg, bg) |
+ ((((ch.attrs & ~ A_COLOR) | set_attrs) & ~clear_attrs) ^ flip_attrs));
+ }
+};
+
+// To allow styles to be built functionally, the following
+// 'constructors' are provided. The main idea here is to make
+// default-setting more compact and less obscure.
+
+/** \return a style that just sets the foreground color to the
+ * given value.
+ */
+inline style style_fg(short fg)
+{
+ style rval;
+ rval.set_fg(fg);
+ return rval;
+}
+
+/** \return a style that just sets the background color to the
+ * given value.
+ */
+inline style style_bg(short bg)
+{
+ style rval;
+ rval.set_bg(bg);
+ return rval;
+}
+
+/** \return a style that just sets the given attributes.
+ */
+inline style style_attrs_on(attr_t attrs)
+{
+ style rval;
+ rval.attrs_on(attrs);
+ return rval;
+}
+
+/** \return a style that just clears the given attributes. */
+inline style style_attrs_off(attr_t attrs)
+{
+ style rval;
+ rval.attrs_off(attrs);
+ return rval;
+}
+
+/** \return a style that just flips the given attributes. */
+inline style style_attrs_flip(attr_t attrs)
+{
+ style rval;
+ rval.attrs_flip(attrs);
+ return rval;
+}
+
+/** Look up a style in the global registry.
+ *
+ * \throws NoSuchStyle if the style does not exist.
+ */
+const style &get_style(const std::string &name);
+
+/** Place a style in the global registry. */
+void set_style(const std::string &name, const style &style);
+
+#endif // STYLE_H
diff --git a/src/vscreen/curses++.cc b/src/vscreen/curses++.cc
new file mode 100644
index 00000000..4e5cd57f
--- /dev/null
+++ b/src/vscreen/curses++.cc
@@ -0,0 +1,411 @@
+// curses++.cc
+//
+// Copyright 1999-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// A few initialization routines and so on.
+
+#include "curses++.h"
+#include "config/style.h"
+
+#include <stdarg.h>
+
+#include <string>
+
+// Note: resize handling is *really* nasty. REALLY nasty. I mean it. :)
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+cwindow rootwin=NULL;
+cwindow rootwinhack=NULL;
+
+using namespace std;
+
+chstring::chstring(const string &s)
+{
+ (*this)=s;
+}
+
+chstring::chstring(const string &s, const style &st)
+{
+ (*this)=s;
+ apply_style(st);
+}
+
+chstring::chstring(const chstring &s, const style &st)
+ :super(s)
+{
+ (*this)=s;
+ apply_style(st);
+}
+
+void chstring::apply_style(const style &st)
+{
+ for(iterator i=begin(); i!=end(); ++i)
+ *i=st.apply_to(*i);
+}
+
+wchstring::wchstring(const wstring &s)
+{
+ for(wstring::const_iterator i=s.begin(); i!=s.end(); ++i)
+ push_back(wchtype(*i, A_NORMAL));
+}
+
+wchstring::wchstring(const wstring &s, const style &st)
+{
+ attr_t attrs=st.get_attrs();
+
+ for(wstring::const_iterator i=s.begin(); i!=s.end(); ++i)
+ push_back(wchtype(*i, attrs));
+}
+
+wchstring::wchstring(const wchstring &s, const style &st)
+ :super(s)
+{
+ (*this)=s;
+ apply_style(st);
+}
+
+void wchstring::apply_style(const style &st)
+{
+ for(iterator i=begin(); i!=end(); ++i)
+ *i=st.apply_to(*i);
+}
+
+int wchstring::width() const
+{
+ int rval=0;
+ for(const_iterator i=begin(); i!=end(); ++i)
+ rval+=wcwidth(i->ch);
+
+ return rval;
+}
+
+int char_traits<chtype>::compare(const chtype *s1,
+ const chtype *s2,
+ size_t n)
+{
+ const chtype *s1end=s1+n;
+
+ while(s1!=s1end)
+ {
+ chtype c1=*s1;
+ chtype c2=*s2;
+
+ if(c1<c2)
+ return -1;
+ else if(c1>c2)
+ return 1;
+
+ ++s1;
+ ++s2;
+ }
+
+ return 0;
+}
+
+size_t char_traits<chtype>::length (const char_type* s)
+{
+ size_t rval=0;
+
+ while(*s!=eos())
+ {
+ ++rval;
+ ++s;
+ }
+
+ return rval;
+}
+
+chtype *char_traits<chtype>::assign(char_type *s,
+ size_t n,
+ const char_type &c)
+{
+ char_type *ends=s+n;
+
+ while(s!=ends)
+ {
+ *s=c;
+ ++s;
+ }
+
+ return s;
+}
+
+int char_traits<wchtype>::compare(const wchtype *s1,
+ const wchtype *s2,
+ size_t n)
+{
+ const wchtype *s1end=s1+n;
+
+ while(s1!=s1end)
+ {
+ wchtype c1=*s1;
+ wchtype c2=*s2;
+
+ if(c1<c2)
+ return -1;
+ else if(c1>c2)
+ return 1;
+
+ ++s1;
+ ++s2;
+ }
+
+ return 0;
+}
+
+size_t char_traits<wchtype>::length (const char_type* s)
+{
+ size_t rval=0;
+
+ while(*s!=eos())
+ {
+ ++rval;
+ ++s;
+ }
+
+ return rval;
+}
+
+wchtype *char_traits<wchtype>::assign(char_type *s,
+ size_t n,
+ const char_type &c)
+{
+ char_type *ends=s+n;
+
+ while(s!=ends)
+ {
+ *s=c;
+ ++s;
+ }
+
+ return s;
+}
+
+chstring &chstring::operator=(const std::string &s)
+{
+ erase();
+
+ for(std::string::const_iterator i=s.begin();
+ i!=s.end(); ++i)
+ push_back((*i)|A_NORMAL);
+
+ return *this;
+}
+
+void init_curses()
+{
+ rootwin=initscr();
+ rootwinhack=rootwin;
+
+ cbreak();
+ noecho();
+ nonl();
+ intrflush(stdscr,FALSE);
+ keypad(stdscr,TRUE);
+
+ start_color();
+ init_colors();
+}
+
+void resize()
+{
+ int fd;
+
+ if( (fd=open("/dev/tty",O_RDONLY)!=-1))
+ {
+ struct winsize w;
+ if(ioctl(fd, TIOCGWINSZ, &w)!=-1)
+ {
+ resize_term(w.ws_row,w.ws_col);
+ rootwin=newwin(w.ws_row, w.ws_col, 0, 0);
+ assert(rootwin);
+ //assert(rootwin.getmaxy()==w.ws_row);
+ //assert(rootwin.getmaxx()==w.ws_col);
+ return;
+ }
+ else
+ {
+ beep();
+ perror("ioctl");
+ }
+ close(fd);
+ }
+ else
+ {
+ beep();
+ perror("open");
+ }
+}
+
+int cwindow::printw(char *str, ...)
+{
+ va_list args;
+ int amt;
+
+ va_start(args, str);
+ amt=vwprintw(win, str, args);
+ va_end(args);
+
+ return amt;
+}
+
+void cwindow::show_string_as_progbar(int x, int y, const wstring &s,
+ int attr1, int attr2, int size1,
+ int totalsize)
+{
+ int width,height;
+
+ getmaxyx(height,width);
+
+ attrset(attr1);
+ move(y, x);
+
+ size_t loc=0;
+ while(x<width)
+ {
+ if(x>=size1)
+ attrset(attr2);
+
+ wchar_t ch=L' ';
+ if(loc<s.size())
+ {
+ ch=s[loc];
+ ++loc;
+ }
+
+ add_wch(ch);
+ x+=wcwidth(ch);
+ }
+}
+
+void cwindow::display_header(wstring s, const attr_t attr)
+{
+ attrset(attr);
+
+ int width,height;
+ getmaxyx(height,width);
+
+ move(0, 0);
+ int x=0;
+
+ wstring::size_type sloc=0;
+
+ while(x<width)
+ {
+ if(sloc<s.size())
+ {
+ const wchar_t wch=s[sloc];
+
+ add_wch(wch);
+ x+=wcwidth(wch);
+ ++sloc;
+ }
+ else
+ {
+ add_wch(L' ');
+ x+=wcwidth(L' ');
+ }
+ }
+}
+
+void cwindow::display_status(wstring s, attr_t attr)
+{
+ attrset(attr);
+
+ int width,height;
+ getmaxyx(height,width);
+
+ move(height-1, 0);
+ int x=0;
+
+ wstring::size_type sloc=0;
+
+ while(x<width)
+ {
+ if(sloc<s.size())
+ {
+ const wchar_t wch=s[sloc];
+
+ add_wch(wch);
+ x+=wcwidth(wch);
+ ++sloc;
+ }
+ else
+ {
+ add_wch(L' ');
+ x+=wcwidth(L' ');
+ }
+ }
+}
+
+int cwindow::addstr(const wchstring &str)
+{
+ return addnstr(str, str.size());
+}
+
+int cwindow::addnstr(const wchstring &str, size_t n)
+{
+ int rval=OK;
+
+ for(string::size_type i=0; i<n && i<str.size(); ++i)
+ {
+ // Construct a cchar_t from the single character at the head of
+ // the string. The weird intermediate single-character string
+ // exists to work around the vagueness of the setcchar
+ // semantics.
+
+
+ cchar_t wch;
+ wchar_t dummy[2];
+
+ dummy[0]=str[i].ch;
+ dummy[1]=L'\0';
+
+ // How can I notify the user of errors?
+ if(setcchar(&wch, dummy, str[i].attrs,
+ PAIR_NUMBER(str[i].attrs), 0) == ERR)
+ {
+ rval=ERR;
+ attr_t a=get_style("Error").get_attrs();
+ if(setcchar(&wch, L"?", a, PAIR_NUMBER(a), 0) == ERR)
+ continue;
+ }
+
+ if(wadd_wch(win, &wch) == ERR)
+ rval=ERR;
+ }
+
+ return rval;
+}
+
+int cwindow::mvaddstr(int y, int x, const wchstring &str)
+{
+ return mvaddnstr(y, x, str, str.size());
+}
+
+int cwindow::mvaddnstr(int y, int x, const wchstring &str, size_t n)
+{
+ if(move(y, x) == ERR)
+ return ERR;
+ else
+ return addnstr(str, n);
+}
diff --git a/src/vscreen/curses++.h b/src/vscreen/curses++.h
new file mode 100644
index 00000000..cb2dd529
--- /dev/null
+++ b/src/vscreen/curses++.h
@@ -0,0 +1,628 @@
+// curses++.h (this is -*-c++-*-)
+//
+// Copyright 1999-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Simple class wrappers around various CURSES functions.
+
+#ifndef CURSES_PLUSPLUS_H
+#define CURSES_PLUSPLUS_H
+
+#include <string>
+#include <ncursesw/curses.h>
+
+#include <assert.h>
+
+// For isspace
+#include <ctype.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/** A structure that amalgamates a wchar_t together with attributes.
+ * This is similar to cchar_t, but has the advantage of having
+ * well-defined properties and behavior; the routines to manipulate
+ * cchar_t's are vaguely documented and a lot of their behavior has
+ * to be guessed at or inferred from source code. I don't trust
+ * interfaces where I have to guess at their behavior, and I think
+ * that I won't lose too much efficiency by doing things this way.
+ */
+struct wchtype
+{
+ /** The character value associated with this string. This code
+ * presently assumes that wchar_t is large enough to hold any
+ * single Unicode (UCS-4) character, which is ok for GNU but might
+ * not port everywhere.
+ */
+ wchar_t ch;
+
+ /** The text attributes (including color) associated with this
+ * character.
+ */
+ attr_t attrs;
+
+ wchtype()
+ {
+ }
+
+ wchtype(const wchar_t &_ch, const attr_t &_attrs)
+ :ch(_ch), attrs(_attrs)
+ {
+ }
+
+ bool operator==(const wchtype &other) const
+ {
+ return ch==other.ch && attrs==other.attrs;
+ }
+
+ bool operator!=(const wchtype &other) const
+ {
+ return ch!=other.ch || attrs!=other.attrs;
+ }
+
+ bool operator<(const wchtype &other) const
+ {
+ return ch<other.ch || ch==other.ch && attrs<other.attrs;
+ }
+
+ bool operator<=(const wchtype &other) const
+ {
+ return (*this) == other || (*this) < other;
+ }
+
+ bool operator>(const wchtype &other) const
+ {
+ return !((*this)<=other);
+ }
+
+ bool operator>=(const wchtype &other) const
+ {
+ return !((*this)<other);
+ }
+};
+
+/** Based on libstdc++-3's instantiation of this for characters.
+ *
+ * This could do something clever, such as changing comparisons to
+ * only apply to the text itself, but it doesn't; I'm not sure what
+ * the effect of changes like that would be.
+ *
+ * The int_type stuff is not implemented; it could perhaps be
+ * implemented later if it's needed.
+ */
+namespace std {
+template <>
+struct TRAITS_CLASS<chtype> {
+ typedef chtype char_type;
+
+ static void assign (char_type& c1, const char_type& c2)
+ { c1 = c2; }
+ static bool eq (const char_type & c1, const char_type& c2)
+ { return (c1 == c2); }
+ static bool ne (const char_type& c1, const char_type& c2)
+ { return (c1 != c2); }
+ static bool lt (const char_type& c1, const char_type& c2)
+ { return (c1 < c2); }
+ static char_type eos () { return 0; }
+ static bool is_del(char_type a) { return isspace(a|A_CHARTEXT); }
+
+ static int compare (const char_type* s1, const char_type* s2, size_t n);
+ static size_t length (const char_type* s);
+ static char_type* copy (char_type* s1, const char_type* s2, size_t n)
+ { return (char_type*) memcpy (s1, s2, n*sizeof(char_type)); }
+ static char_type* move (char_type* s1, const char_type* s2, size_t n)
+ { return (char_type*) memmove (s1, s2, n*sizeof(char_type)); }
+ static char_type* assign (char_type* s1, size_t n, const char_type& c);
+};
+
+ template <>
+ struct TRAITS_CLASS<wchtype> {
+ typedef wchtype char_type;
+
+ static void assign (char_type& c1, const char_type& c2)
+ { c1 = c2; }
+ static bool eq (const char_type & c1, const char_type& c2)
+ { return (c1 == c2); }
+ static bool ne (const char_type& c1, const char_type& c2)
+ { return (c1 != c2); }
+ static bool lt (const char_type& c1, const char_type& c2)
+ { return (c1 < c2); }
+ static char_type eos () { return wchtype(0,0); }
+ static bool is_del(char_type a) { return isspace(a.ch); }
+
+ static int compare (const char_type* s1, const char_type* s2, size_t n);
+ static size_t length (const char_type* s);
+ static char_type* copy (char_type* s1, const char_type* s2, size_t n)
+ { return (char_type*) memcpy (s1, s2, n*sizeof(char_type)); }
+ static char_type* move (char_type* s1, const char_type* s2, size_t n)
+ { return (char_type*) memmove (s1, s2, n*sizeof(char_type)); }
+ static char_type* assign (char_type* s1, size_t n, const char_type& c);
+ };
+}
+
+class style;
+
+/** A string class which stores attributes along with characters.
+ *
+ * This has to derive from basic_string in order to implement operator=
+ * (which can't be a global function -- it has to be a nonstatic member)
+ */
+class chstring:public std::basic_string<chtype>
+{
+ typedef std::basic_string<chtype> super;
+public:
+ chstring(const std::basic_string<chtype> &s)
+ :std::basic_string<chtype>(s) {}
+
+ chstring(const std::string &s);
+ chstring(const std::string &s, const style &st);
+
+ chstring(const chstring &s):super(s) {}
+ /** Apply the given style to the given chstring, and set ourselves
+ * to the result.
+ */
+ chstring(const chstring &s, const style &st);
+
+ chstring(const chstring &s, size_t loc, size_t n=npos)
+ :super(s, loc, n) {}
+
+ chstring(size_t n, chtype c)
+ :super(n, c) {}
+
+ /** Assign the characters of s to this, setting all attributes to A_NORMAL. */
+ chstring &operator=(const std::string &s);
+
+ /** Change the attributes of this string by using the given style. */
+ void apply_style(const style &st);
+};
+
+class wchstring:public std::basic_string<wchtype>
+{
+ typedef std::basic_string<wchtype> super;
+public:
+ wchstring(const std::basic_string<wchtype> &s)
+ :std::basic_string<wchtype>(s) {}
+
+ /** Create a new wchstring with empty attribute information. */
+ wchstring(const std::wstring &s);
+
+ /** Create a new wchstring from the given wide string with the given
+ * attributes.
+ */
+ wchstring(const std::wstring &s, const style &st);
+
+ wchstring(const wchstring &s):super(s) {}
+ /** Apply the given style to the given chstring, and set ourselves
+ * to the result.
+ */
+ wchstring(const wchstring &s, const style &st);
+
+ wchstring(const wchstring &s, size_t loc, size_t n=npos)
+ :super(s, loc, n) {}
+
+ wchstring(size_t n, wchtype c)
+ :super(n, c) {}
+
+ wchstring(size_t n, wchar_t c, attr_t a)
+ :super(n, wchtype(c, a)) {}
+
+ /** Change the attributes of this string by using the given style. */
+ void apply_style(const style &st);
+
+ /** Return the number of columns occupied by this string. */
+ int width() const;
+};
+
+inline chtype _getbkgd(WINDOW *win)
+{
+ return getbkgd(win);
+}
+
+inline int _box(WINDOW *win, chtype verch, chtype horch)
+{
+ return box(win, verch, horch);
+}
+
+inline void _getyx(WINDOW *win, int &y, int &x)
+{
+ getyx(win, y, x);
+}
+
+inline void _getparyx(WINDOW *win, int &y, int &x)
+{
+ getparyx(win, y, x);
+}
+
+inline void _getbegyx(WINDOW *win, int &y, int &x)
+{
+ getbegyx(win, y, x);
+}
+
+inline void _getmaxyx(WINDOW *win, int &y, int &x)
+{
+ getmaxyx(win, y, x);
+}
+
+inline int _getmaxy(WINDOW *win)
+{
+ return getmaxy(win);
+}
+
+inline int _getmaxx(WINDOW *win)
+{
+ return getmaxx(win);
+}
+
+inline int _touchwin(WINDOW *win)
+{
+ return touchwin(win);
+}
+
+inline int _untouchwin(WINDOW *win)
+{
+ return untouchwin(win);
+}
+
+#undef getbkgd
+#undef box
+
+#undef getyx
+#undef getparyx
+#undef getbegyx
+#undef getmaxyx
+#undef getmaxy
+#undef getmaxx
+
+#undef addch
+#undef addchnstr
+#undef addchstr
+#undef add_wch
+#undef addnstr
+#undef addstr
+#undef attroff
+#undef attron
+#undef attrset
+#undef attr_set
+#undef bkgd
+#undef bkgdset
+#undef clear
+#undef clrtobot
+#undef clrtoeol
+#undef delch
+#undef deleteln
+#undef echochar
+#undef erase
+#undef getch
+#undef get_wch
+#undef getstr
+#undef inch
+#undef inchnstr
+#undef innstr
+#undef insch
+#undef insdelln
+#undef insertln
+#undef insnstr
+#undef insstr
+#undef instr
+#undef move
+#undef refresh
+#undef scrl
+#undef scroll
+#undef setscrreg
+#undef standend
+#undef standout
+#undef timeout
+
+#undef mvaddch
+#undef mvadd_wch
+#undef mvaddchnstr
+#undef mvaddchstr
+#undef mvaddnstr
+#undef mvaddstr
+#undef mvdelch
+#undef mvgetch
+#undef mvget_wch
+#undef mvgetnstr
+#undef mvgetstr
+#undef mvhline
+#undef mvinch
+#undef mvinchnstr
+#undef mvinchstr
+#undef mvinnstr
+#undef mvinsch
+#undef mvinsnstr
+#undef mvinsstr
+#undef mvinstr
+#undef mvvline
+
+#undef border
+#undef hline
+#undef vline
+
+#undef touchline
+
+// The following class encapsulates a CURSES window, mostly with inlined
+// versions of w* and mvw*. subwin and newwin are encapsulated with
+// constructors; casting to WINDOW * is also supported.
+//
+// er, these will be inlined. Right?
+class cwindow
+{
+ // Blech. Used to memory-manage WINDOW *s
+ class cwindow_master
+ {
+ WINDOW *win;
+ int refs;
+ cwindow_master *parent;
+
+ friend class cwindow;
+
+ ~cwindow_master()
+ {
+ assert(refs==0);
+
+ if(win)
+ delwin(win);
+ if(parent)
+ parent->deref();
+ }
+ public:
+ cwindow_master(WINDOW *_win, cwindow_master *_parent)
+ :win(_win), refs(0), parent(_parent)
+ {
+ if(parent)
+ parent->ref();
+ }
+
+ inline void ref()
+ {
+ refs++;
+ }
+
+ // ??????
+ inline void deref()
+ {
+ refs--;
+
+ if(refs==0)
+ delete this;
+ }
+ };
+
+ WINDOW *win;
+ // The actual curses window
+
+ cwindow_master *master;
+ // Keeps track of where we got this from (so we can deref() it later)
+
+ cwindow(WINDOW *_win, cwindow_master *_master)
+ :win(_win), master(_master)
+ {
+ master->ref();
+ }
+public:
+ cwindow(WINDOW *_win):win(_win), master(new cwindow_master(_win, NULL))
+ {
+ master->ref();
+ }
+ cwindow(const cwindow &a):win(a.win), master(a.master)
+ {
+ master->ref();
+ }
+
+ ~cwindow()
+ {
+ master->deref();
+ }
+
+ cwindow derwin(int h, int w, int y, int x)
+ {
+ WINDOW *new_win=::derwin(win, h, w, y, x);
+ return cwindow(new_win, new cwindow_master(new_win, master));
+ }
+
+ int mvwin(int y, int x) {return ::mvwin(win, y, x);}
+
+ void syncup() {wsyncup(win);}
+ int syncok(bool bf) {return ::syncok(win, bf);}
+ void cursyncup() {wcursyncup(win);}
+ void syncdown() {wsyncdown(win);}
+
+ int scroll(int n=1) {return wscrl(win, n);}
+ // Does both scroll() and wscsrl()
+
+ int addch(chtype ch) {return waddch(win, ch);}
+ int mvaddch(int y, int x, chtype ch) {return mvwaddch(win, y, x, ch);}
+
+ int add_wch(wchar_t wch)
+ {
+ wchar_t tmp[2];
+ tmp[0]=wch;
+ tmp[1]=0;
+
+ cchar_t cch;
+ if(setcchar(&cch, tmp, 0, 0, 0)==ERR)
+ return ERR;
+ else
+ return wadd_wch(win, &cch);
+ }
+
+ int mvadd_wch(int y, int x, wchar_t wch)
+ {
+ move(y, x);
+ return add_wch(wch);
+ }
+
+ int add_wch(const cchar_t *cch)
+ {
+ return wadd_wch(win, cch);
+ }
+
+ int mvadd_wch(int y, int x, const cchar_t *cch)
+ {
+ return mvwadd_wch(win, y, x, cch);
+ }
+
+ int addstr(const std::wstring &str) {return addstr(str.c_str());}
+ int addnstr(const std::wstring &str, int n) {return addnstr(str.c_str(), n);}
+ int mvaddstr(int y, int x, const std::wstring &str) {return mvaddstr(y, x, str.c_str());}
+ int mvaddnstr(int y, int x, const std::wstring &str, int n) {return mvaddnstr(y, x, str.c_str(), n);}
+
+ int addstr(const wchar_t *str) {return waddwstr(win, str);}
+ int addnstr(const wchar_t *str, int n) {return waddnwstr(win, str, n);}
+ int mvaddstr(int y, int x, const wchar_t *str) {return mvwaddwstr(win, y, x, str);}
+ int mvaddnstr(int y, int x, const wchar_t *str, int n) {return mvwaddnwstr(win, y, x, str, n);}
+
+ int addstr(const char *str) {return waddstr(win, str);}
+ int addnstr(const char *str, int n) {return waddnstr(win, str, n);}
+ int mvaddstr(int y, int x, const char *str) {return mvwaddstr(win, y, x, str);}
+ int mvaddnstr(int y, int x, const char *str, int n) {return mvwaddnstr(win, y, x, str, n);}
+
+ // The following are implemented hackily due to the weirdness of
+ // curses. NB: they don't work with characters of negative width.
+ int addstr(const wchstring &str);
+ int addnstr(const wchstring &str, size_t n);
+ int mvaddstr(int y, int x, const wchstring &str);
+ int mvaddnstr(int y, int x, const wchstring &str, size_t n);
+
+ int addstr(const chstring &str) {return waddchstr(win, str.c_str());}
+ int addnstr(const chstring &str, int n) {return waddchnstr(win, str.c_str(), n);}
+ int mvaddstr(int y, int x, const chstring &str) {return mvwaddchstr(win, y, x, str.c_str());}
+ int mvaddnstr(int y, int x, const chstring &str, int n) {return mvwaddchnstr(win, y, x, str.c_str(), n);}
+
+ int attroff(int attrs) {return wattroff(win, attrs);}
+ int attron(int attrs) {return wattron(win, attrs);}
+ int attrset(int attrs) {return wattrset(win, attrs);}
+ // int attr_set(int attrs, void *opts) {return wattr_set(win, attrs, opts);}
+
+ void bkgdset(const chtype ch) {wbkgdset(win, ch);}
+ int bkgd(const chtype ch) {return wbkgd(win, ch);}
+ chtype getbkgd() {return _getbkgd(win);}
+
+ int border(chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br)
+ {return wborder(win, ls, rs, ts, bs, tl, tr, bl, br);}
+
+ int box(chtype verch, chtype horch) {return _box(win, verch, horch);}
+ int hline(chtype ch, int n) {return whline(win, ch, n);}
+ int vline(chtype ch, int n) {return wvline(win, ch, n);}
+ int mvhline(int y, int x, chtype ch, int n) {return mvwhline(win, y, x, ch, n);}
+ int mvvline(int y, int x, chtype ch, int n) {return mvwvline(win, y, x, ch, n);}
+
+ int delch() {return wdelch(win);}
+ int mvdelch(int y, int x) {return mvwdelch(win, y, x);}
+
+ int deleteln() {return wdeleteln(win);}
+ int insdelln(int n) {return winsdelln(win,n);}
+ int insertln() {return winsertln(win);}
+
+ int echochar(chtype ch) {return wechochar(win, ch);}
+
+ int getch() {return wgetch(win);}
+ int mvgetch(int y, int x) {return mvwgetch(win, y, x);}
+
+ int get_wch(wint_t *wch) {return wget_wch(win, wch);}
+ int mvget_wch(int y, int x, wint_t *wch) {return mvwget_wch(win, y, x, wch);}
+
+ int move(int y, int x) {return wmove(win, y, x);}
+ void getyx(int &y, int &x) {_getyx(win, y, x);}
+ void getparyx(int &y, int &x) {_getparyx(win, y, x);}
+ void getbegyx(int &y, int &x) {_getbegyx(win, y, x);}
+ void getmaxyx(int &y, int &x) {_getmaxyx(win, y, x);}
+ int getmaxy() {return _getmaxy(win);}
+ int getmaxx() {return _getmaxx(win);}
+
+ void show_string_as_progbar(int x, int y, const std::wstring &s,
+ int attr1, int attr2, int size1,
+ int totalsize);
+ // Glitz bit :) Displays the given string with a progress bar behind it.
+
+ void display_header(std::wstring s, const attr_t attr);
+ void display_status(std::wstring s, const attr_t attr);
+ // Make it easier to write interfaces that have a header and status line..
+ // they do what they say :)
+
+ int overlay(cwindow &dstwin) {return ::overlay(win, dstwin.win);}
+ int overwrite(cwindow &dstwin) {return ::overwrite(win, dstwin.win);}
+ int copywin(cwindow &dstwin, int sminrow, int smincol, int dminrow, int dmincol, int dmaxrow, int dmaxcol, int overlay)
+ {return ::copywin(win, dstwin.win, sminrow, smincol, dminrow, dmincol, dmaxrow, dmaxcol, overlay);}
+
+ int refresh() {return wrefresh(win);}
+ int noutrefresh() {return wnoutrefresh(win);}
+
+ int touch() {return _touchwin(win);}
+ int untouch() {return _untouchwin(win);}
+ int touchln(int y, int n, int changed) {return ::wtouchln(win, y, n, changed);}
+ int touchline(int start, int count) {return touchln(start, count, 1);}
+ int untouchline(int start, int count) {return touchln(start, count, 0);}
+
+ int erase() {return werase(win);}
+ int clear() {return wclear(win);}
+ int clrtobot() {return wclrtobot(win);}
+ int clrtoeol() {return wclrtoeol(win);}
+
+ int keypad(bool bf) {return ::keypad(win,bf);}
+ int meta(bool bf) {return ::meta(win,bf);}
+ int nodelay(bool bf) {return ::nodelay(win, bf);}
+ int notimeout(bool bf) {return ::notimeout(win, bf);}
+ void timeout(int delay) {wtimeout(win, delay);}
+
+ int clearok(bool bf) {return ::clearok(win, bf);}
+ int idlok(bool bf) {return ::idlok(win, bf);}
+ void idcok(bool bf) {::idcok(win, bf);}
+ void immedok(bool bf) {::immedok(win, bf);}
+#if defined(NCURSES_VERSION_MAJOR) && NCURSES_VERSION_MAJOR>=5
+ int leaveok(bool bf) {int rval=::leaveok(win, bf); curs_set(bf?0:1); return rval;}
+#else
+ int leaveok(bool bf) {return ::leaveok(win, bf);}
+#endif
+ int setscrreg(int top, int bot) {return wsetscrreg(win, top, bot);}
+ int scrollok(bool bf) {return ::scrollok(win,bf);}
+
+ int printw(char *str, ...);
+ /* You guessed it.. :) */
+
+ bool enclose(int y, int x) {return wenclose(win, y, x);}
+
+ WINDOW *getwin() {return win;}
+ operator bool () {return win!=NULL;}
+ cwindow &operator =(const cwindow &a)
+ {
+ cwindow_master *newmaster=a.master;
+ newmaster->ref();
+
+ master->deref();
+ master=newmaster;
+ win=a.win;
+ return *this;
+ }
+ bool operator ==(cwindow &other) {return win==other.win;}
+ bool operator !=(cwindow &other) {return win!=other.win;}
+
+ static void remove_cruft();
+};
+
+extern cwindow rootwin;
+// This is stdscr, but calling it 'stdscr' would confuse the compiler, so I'm
+// confusing the programmer instead :)
+
+void init_curses();
+// Initializes curses and sets rootwin to the correct value
+
+void resize();
+// Called when a terminal resize is detected.
+#endif
diff --git a/src/vscreen/fragment.cc b/src/vscreen/fragment.cc
new file mode 100644
index 00000000..93d4ff0b
--- /dev/null
+++ b/src/vscreen/fragment.cc
@@ -0,0 +1,1519 @@
+// fragment.cc
+//
+//
+// Copyright (C) 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "fragment.h"
+#include "transcode.h"
+
+#include "config/colors.h"
+
+#include <stdarg.h>
+
+#include <algorithm>
+
+#include <wctype.h>
+
+using namespace std;
+
+fragment::~fragment()
+{
+}
+
+/** A fragment of text, possibly with attached attributes. It's
+ * assumed that the text contains no literal newlines.
+ */
+class _text_fragment:public fragment
+{
+public:
+ _text_fragment(const wstring &_s):s(_s) {}
+
+ fragment_contents layout(size_t firstw, size_t restw,
+ const style &st)
+ {
+ fragment_contents rval;
+ rval.push_back(fragment_line(s, st));
+ return rval;
+ }
+
+ size_t max_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ return first_indent+wcswidth(s.c_str(), s.size());
+ }
+
+ size_t trailing_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ return first_indent+wcswidth(s.c_str(), s.size());
+ }
+
+ bool final_newline() const
+ {
+ return false;
+ }
+private:
+ wstring s;
+};
+
+fragment *text_fragment(const wstring &s) { return new _text_fragment(s); }
+fragment *text_fragment(const wstring &s,
+ const style &st)
+{
+ return style_fragment(text_fragment(s),
+ st);
+}
+
+fragment *text_fragment(const string &s,
+ const char *encoding)
+{
+ wstring decoded;
+
+ // The error code is not translated, because that would require an
+ // additional decoding step and could result in infinite loops and
+ // other bad stuff. Besides, something is very wrong if it appears;
+ // better to just output a diagnostic message that can be cut+paste.
+ if(!transcode(s.c_str(), decoded, encoding))
+ return sequence_fragment(text_fragment(decoded),
+ text_fragment(L"Error decoding multibyte string",
+ get_style("Error")),
+ NULL);
+ else
+ return text_fragment(decoded);
+}
+
+fragment *text_fragment(const string &s,
+ const style &st,
+ const char *encoding)
+{
+ return style_fragment(text_fragment(s, encoding),
+ st);
+}
+
+/** This fragment just expands to a newline. */
+class _newline_fragment:public fragment
+{
+public:
+ _newline_fragment() {}
+
+ fragment_contents layout(size_t firstw, size_t restw, const style &st)
+ {
+ fragment_contents rval;
+ rval.set_final_nl(true);
+ return rval;
+ }
+
+ size_t max_width(size_t first_indent, size_t rest_indent) const
+ {
+ return first_indent;
+ }
+
+ size_t trailing_width(size_t first_indent, size_t rest_indent) const
+ {
+ return rest_indent;
+ }
+
+ bool final_newline() const
+ {
+ return true;
+ }
+};
+
+fragment *newline_fragment()
+{
+ return new _newline_fragment;
+}
+
+class _style_fragment:public fragment
+{
+public:
+ _style_fragment(fragment *_contents, const style &_st)
+ :contents(_contents), st(_st)
+ {
+ }
+ ~_style_fragment() {delete contents;}
+
+ fragment_contents layout(size_t firstw, size_t restw,
+ const style &st2)
+ {
+ return contents->layout(firstw, restw, st2+st);
+ }
+
+ size_t max_width(size_t first_indent, size_t rest_indent) const
+ {
+ return contents->max_width(first_indent, rest_indent);
+ }
+
+ size_t trailing_width(size_t first_indent, size_t rest_indent) const
+ {
+ return contents->trailing_width(first_indent, rest_indent);
+ }
+
+ bool final_newline() const
+ {
+ return contents->final_newline();
+ }
+private:
+ fragment *contents;
+ style st;
+};
+
+fragment *style_fragment(fragment *f,
+ const style &st)
+{
+ return new _style_fragment(f, st);
+}
+
+/** A base class for fragment containers that supports caching
+ * information about the children. (yum, premature optimization)
+ */
+class fragment_container:public fragment
+{
+ void update_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ if(width_stale ||
+ first_indent != stale_first_indent ||
+ rest_indent != stale_rest_indent)
+ {
+ max_width_cache=calc_max_width(first_indent, rest_indent);
+ trailing_width_cache=calc_trailing_width(first_indent,
+ rest_indent);
+ width_stale=false;
+ stale_first_indent=first_indent;
+ stale_rest_indent=rest_indent;
+ }
+ }
+public:
+ fragment_container()
+ :width_stale(true), final_nl_stale(true)
+ {
+ }
+
+ /** Actually calculate the maximum width. */
+ virtual size_t calc_max_width(size_t first_indent,
+ size_t rest_indent) const=0;
+
+ /** Actually calculate the trailing width. */
+ virtual size_t calc_trailing_width(size_t first_indent,
+ size_t rest_indent) const=0;
+
+ /** Actually calculate the final-nl status. */
+ virtual bool calc_final_newline() const=0;
+
+ size_t max_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ update_width(first_indent, rest_indent);
+ return max_width_cache;
+ }
+
+ size_t trailing_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ update_width(first_indent, rest_indent);
+ return trailing_width_cache;
+ }
+
+ bool final_newline() const
+ {
+ if(final_nl_stale)
+ {
+ final_nl_cache=calc_final_newline();
+ final_nl_stale=false;
+ }
+
+ return final_nl_cache;
+ }
+
+private:
+ mutable size_t max_width_cache, trailing_width_cache;
+ mutable size_t stale_first_indent, stale_rest_indent;
+ mutable bool final_nl_cache:1;
+ mutable bool width_stale:1, final_nl_stale:1;
+};
+
+/** A fragment generated by composing a sequence of other fragments. */
+class _sequence_fragment:public fragment_container
+{
+public:
+ _sequence_fragment(const vector<fragment*> &_contents):contents(_contents)
+
+ {
+ }
+
+ fragment_contents layout(size_t firstw, const size_t restw,
+ const style &st)
+ {
+ fragment_contents rval;
+
+ rval.push_back(fragment_line(L""));
+
+ for(vector<fragment*>::const_iterator i=contents.begin();
+ i!=contents.end(); ++i)
+ {
+ fragment_contents lines=(*i)->layout(firstw, restw, st);
+
+ // Update firstw appropriately.
+ if(lines.get_final_nl())
+ firstw=restw;
+ else if(lines.size()>0)
+ {
+ int deduct_from;
+
+ if(lines.size()==1)
+ deduct_from=firstw;
+ else
+ deduct_from=restw;
+
+ if(deduct_from>=lines.back().width())
+ firstw=deduct_from-lines.back().width();
+ else
+ firstw=0;
+ }
+
+ // Make sure that implicit newlines are handled correctly.
+ if(lines.size()==0)
+ {
+ if(rval.get_final_nl() && lines.get_final_nl())
+ rval.push_back(fragment_line(L""));
+
+ rval.set_final_nl(rval.get_final_nl() || lines.get_final_nl());
+ }
+
+ for(fragment_contents::const_iterator j=lines.begin();
+ j!=lines.end(); ++j)
+ {
+ if(!rval.get_final_nl())
+ {
+ rval.back()+=*j;
+ rval.set_final_nl(true);
+ }
+ else
+ rval.push_back(*j);
+ }
+
+ rval.set_final_nl(lines.get_final_nl());
+ }
+
+ return rval;
+ }
+
+ ~_sequence_fragment()
+ {
+ for(vector<fragment*>::const_iterator i=contents.begin();
+ i!=contents.end(); ++i)
+ delete *i;
+ }
+
+ size_t calc_max_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ size_t rval=0;
+ size_t partial=first_indent;
+ for(vector<fragment*>::const_iterator i=contents.begin();
+ i!=contents.end(); ++i)
+ {
+ rval=max(rval, (*i)->max_width(partial, rest_indent));
+
+ if((*i)->final_newline())
+ rval=max(partial, rval);
+
+ partial=(*i)->trailing_width(partial, rest_indent);
+ }
+
+ rval=max(partial, rval);
+
+ return rval;
+ }
+
+ size_t calc_trailing_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ size_t rval=first_indent;
+
+ for(vector<fragment*>::const_iterator i=contents.begin();
+ i!=contents.end(); ++i)
+ rval=(*i)->trailing_width(rval, rest_indent);
+
+ return rval;
+ }
+
+ bool calc_final_newline() const
+ {
+ return !contents.empty() && contents.back()->final_newline();
+ }
+
+private:
+ const vector<fragment*> contents;
+};
+
+fragment *sequence_fragment(const vector<fragment*> &contents)
+{
+ return new _sequence_fragment(contents);
+}
+
+fragment *sequence_fragment(fragment *f, ...)
+{
+ vector<fragment*> fragments;
+
+ if(f==NULL)
+ return new _sequence_fragment(fragments);
+
+ fragments.push_back(f);
+
+ va_list lst;
+
+ va_start(lst, f);
+ do
+ {
+ f=va_arg(lst, fragment*);
+ if(f!=NULL)
+ fragments.push_back(f);
+ } while(f!=NULL);
+
+ va_end(lst);
+
+ return new _sequence_fragment(fragments);
+}
+
+// Fall back on sequence_fragment [for now?]
+fragment *join_fragments(const vector<fragment *> &fragments,
+ const wstring &between)
+{
+ vector<fragment*> rval;
+
+ for(vector<fragment *>::const_iterator i=fragments.begin();
+ i!=fragments.end();
+ ++i)
+ {
+ if(i!=fragments.begin())
+ rval.push_back(text_fragment(between));
+
+ rval.push_back(*i);
+ }
+
+ return sequence_fragment(rval);
+}
+
+class _flowbox:public fragment_container
+{
+public:
+ _flowbox(fragment *_contents):contents(_contents) {}
+
+ fragment_contents layout(size_t firstw, const size_t restw,
+ const style &st)
+ {
+ if(restw==0)
+ return fragment_contents();
+
+ fragment_contents rval,lines=contents->layout(firstw, restw,
+ st);
+
+ for(fragment_contents::const_iterator i=lines.begin();
+ i!=lines.end(); ++i)
+ {
+ fragment_line s=*i;
+
+ // Make sure we at least advance the cursor for every line read.
+ //
+ // Is there a less gross way to express this?
+ bool output_something=false;
+
+ size_t first=0;
+
+ // Flow the current line:
+ while(first<s.size())
+ {
+ // Strip leading whitespace.
+ while(first<s.size() && iswspace(s[first].ch))
+ ++first;
+
+ if(first==s.size())
+ break;
+
+ // If there are few enough characters to fit on a single
+ // line, do that.
+ fragment_line maybe_curr(s, first, s.size()-first);
+ if(maybe_curr.width()<=(signed) firstw)
+ {
+ rval.push_back(maybe_curr);
+ firstw=restw;
+ first=s.size();
+ output_something=true;
+ }
+ else
+ {
+ int chunkw=0;
+ size_t chars=0;
+ while(chunkw<(signed) firstw && chars+first<s.size())
+ {
+ chunkw+=wcwidth(s[first+chars].ch);
+ ++chars;
+ }
+
+ // Save this for later (see below). Note that if we
+ // actually overshot the width goal, we need to
+ // possibly strip the last character off (it's
+ // guaranteed to be only one since otherwise we'd have
+ // stopped sooner...)
+ const size_t high_water_mark=chars;
+ const int high_water_width=chunkw;
+
+ // We pushed the line as far as possible; back up
+ // until we are no longer in the middle of a word AND
+ // the string is short enough. (we know it's not at
+ // the end of the whole string because of the earlier
+ // test)
+ while(chars>0 && (chunkw>(signed) firstw ||
+ !iswspace(s[first+chars].ch)))
+ {
+ --chars;
+ chunkw-=wcwidth(s[first+chars].ch);
+ }
+
+ if(chars==0)
+ {
+ // Oops, there's a word that's longer than the
+ // current line. Push as much as fits onto the
+ // current line.
+
+ // First, try to exclude any characters that
+ // overlap the right margin.
+ chars=high_water_mark;
+ chunkw=high_water_width;
+ while(chars>0 && chunkw>(signed) firstw)
+ {
+ --chars;
+ chunkw-=wcwidth(s[first+chars].ch);
+ }
+
+ // If even that's impossible, go ahead and push a
+ // single character onto the end. Note that this
+ // means we're probably in such a tiny space that
+ // the result will suck no matter what..
+ if(chars==0)
+ chars=1;
+
+ rval.push_back(fragment_line(s, first, chars));
+ first+=chars;
+ firstw=restw;
+ output_something=true;
+ }
+ else
+ {
+ // Strip trailing whitespace, then `output' the
+ // line.
+ while(chars>0 &&
+ iswspace(s[first+chars-1].ch))
+ --chars;
+
+ rval.push_back(fragment_line(s, first, chars));
+ firstw=restw;
+ first+=chars;
+ output_something=true;
+ }
+ }
+ }
+
+ if(!output_something)
+ {
+ rval.push_back(fragment_line(L""));
+ firstw=restw;
+ }
+
+ // Ok, go to the next line now.
+ }
+
+ // flowboxes always have a final newline.
+ rval.set_final_nl(true);
+
+ return rval;
+ }
+
+ size_t calc_max_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ return contents->max_width(first_indent, rest_indent);
+ }
+
+ size_t calc_trailing_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ return rest_indent;
+ }
+
+ bool calc_final_newline() const
+ {
+ return true;
+ }
+
+ ~_flowbox() { delete contents; }
+
+private:
+ fragment * const contents;
+};
+
+fragment *flowbox(fragment *contents) {return new _flowbox(contents);}
+
+class _fillbox:public fragment_container
+{
+public:
+ _fillbox(fragment *_contents):contents(_contents) {}
+
+ fragment_contents layout(size_t firstw, size_t restw,
+ const style &st)
+ {
+ // As far as I know, this is valid everywhere...but it would be
+ // rather tricky to write this algorithm without making this
+ // assumption.
+ assert(wcwidth(L' ')==1);
+
+ if(restw==0)
+ return fragment_contents();
+
+ fragment_contents rval,lines=contents->layout(firstw, restw,
+ st);
+
+ for(fragment_contents::const_iterator i=lines.begin();
+ i!=lines.end(); ++i)
+ {
+ fragment_line s=*i;
+
+ size_t first=0;
+
+ // Build a list of words on the current line.
+ vector<fragment_line> words;
+
+ bool output_something=false;
+
+ while(first<s.size())
+ {
+ // Strip leading whitespace.
+ while(first<s.size() && iswspace(s[first].ch))
+ ++first;
+
+ size_t amt=0;
+ while(first+amt<s.size() && !iswspace(s[first+amt].ch))
+ ++amt;
+
+ if(amt>0)
+ words.push_back(fragment_line(s, first, amt));
+
+ first+=amt;
+ }
+
+ // Now place them onto output lines.
+
+ size_t word_start=0;
+
+ while(word_start<words.size())
+ {
+ size_t curwidth=0;
+ size_t nwords=0;
+
+ // As long as adding the *next* word doesn't put us
+ // past the right edge, add it.
+ while(word_start+nwords < words.size() &&
+ curwidth+words[word_start+nwords].width()+nwords <= firstw)
+ {
+ curwidth+=words[word_start+nwords].width();
+ ++nwords;
+ }
+
+ if(nwords==0)
+ {
+ // Split a single word: just chop the beginning off.
+ size_t chars=0;
+ fragment_line &word=words[word_start];
+ while(chars<word.size() && curwidth<firstw)
+ {
+ curwidth+=wcwidth(word[chars].ch);
+ ++chars;
+ }
+
+ while(chars>0 && curwidth>firstw)
+ {
+ --chars;
+ curwidth-=wcwidth(word[chars].ch);
+ }
+
+ if(chars==0)
+ chars=1;
+
+ rval.push_back(fragment_line(words[word_start], 0, chars));
+ words[word_start]=fragment_line(words[word_start], chars);
+ firstw=restw;
+ output_something=true;
+ }
+ else
+ {
+ size_t diff;
+
+ if(word_start+nwords<words.size())
+ diff=firstw-(curwidth+nwords-1);
+ else
+ // Cheat to disable filling on the last line of the
+ // paragraph.
+ diff=0;
+
+ // Now spit the words into an output string, filled
+ // left and right.
+ fragment_line final(L"");
+
+ // This is similar to the famous algorithm for drawing
+ // a line. The idea is to add diff/(words-1) spaces
+ // (in addition to one space per word); since
+ // fractional spaces aren't allowed, I approximate by
+ // adding a number of spaces equal to the integral
+ // part, then keeping the remainder for the next word.
+ size_t extra_spaces=0;
+
+ for(size_t word=0; word<nwords; ++word)
+ {
+ if(word>0)
+ // Insert spaces between words:
+ {
+ extra_spaces+=diff;
+
+ size_t nspaces=1+extra_spaces/(nwords-1);
+ extra_spaces%=nwords-1;
+
+ final+=fragment_line(nspaces, L' ', st.get_attrs());
+ }
+
+ final+=words[word+word_start];
+ }
+
+ output_something=true;
+ rval.push_back(final);
+ firstw=restw;
+
+ word_start+=nwords;
+ }
+ }
+
+ if(!output_something)
+ {
+ rval.push_back(fragment_line(L""));
+ firstw=restw;
+ }
+ }
+
+ // fillboxes always have a final newline.
+ rval.set_final_nl(true);
+
+ return rval;
+ }
+
+ size_t calc_max_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ return contents->max_width(first_indent, rest_indent);
+ }
+
+ size_t calc_trailing_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ return rest_indent;
+ }
+
+ bool calc_final_newline() const
+ {
+ return true;
+ }
+
+ ~_fillbox() { delete contents; }
+
+private:
+ fragment * const contents;
+};
+
+fragment *fillbox(fragment *contents) {return new _fillbox(contents);}
+
+class _hardwrapbox:public fragment_container
+{
+public:
+ _hardwrapbox(fragment *_contents):contents(_contents) {}
+
+ ~_hardwrapbox() {delete contents;}
+
+ fragment_contents layout(size_t firstw, const size_t restw,
+ const style &st)
+ {
+ if(restw==0)
+ return fragment_contents();
+
+ fragment_contents rval, lines=contents->layout(firstw, restw,
+ st);
+
+ for(fragment_contents::const_iterator i=lines.begin();
+ i!=lines.end(); ++i)
+ {
+ if(i->empty())
+ {
+ rval.push_back(fragment_line(L""));
+ firstw=restw;
+ }
+ else
+ {
+ fragment_line s=*i;
+ fragment_line::size_type start=0;
+
+ while(start<s.size())
+ {
+ size_t chars=0;
+ int width=0;
+
+ while(width<(signed) firstw && start+chars<s.size())
+ {
+ width+=wcwidth(s[start+chars].ch);
+ ++chars;
+ }
+
+ // If we spilled over, it's the last character that's
+ // responsible.
+ if(width>(signed) firstw && chars>1)
+ --chars;
+
+ rval.push_back(fragment_line(s, start, chars));
+ start+=chars;
+ firstw=restw;
+ }
+ }
+ }
+
+ rval.set_final_nl(true);
+
+ return rval;
+ }
+
+ size_t calc_max_width(size_t first_indent, size_t rest_indent) const
+ {
+ return contents->max_width(first_indent, rest_indent);
+ }
+
+ size_t calc_trailing_width(size_t first_indent, size_t rest_indent) const
+ {
+ return rest_indent;
+ }
+
+ bool calc_final_newline() const
+ {
+ return true;
+ }
+
+private:
+ fragment * const contents;
+};
+
+fragment *hardwrapbox(fragment *contents)
+{
+ return new _hardwrapbox(contents);
+}
+
+class _clipbox:public fragment_container
+{
+public:
+ _clipbox(fragment *_contents):contents(_contents) {}
+
+ fragment_contents layout(size_t firstw, const size_t restw,
+ const style &st)
+ {
+ fragment_contents rval, lines=contents->layout(firstw, restw,
+ st);
+
+ for(fragment_contents::const_iterator i=lines.begin(); i!=lines.end(); ++i)
+ {
+ size_t chars=0;
+ int width=0;
+
+ while(width<(signed) firstw && chars<i->size())
+ {
+ width+=wcwidth((*i)[chars].ch);
+ ++chars;
+ }
+
+ if(width>(signed) firstw && chars>1)
+ --chars;
+
+ rval.push_back(fragment_line(*i, 0, chars));
+ firstw=restw;
+ }
+
+ // Clipboxes are always followed by a final newline.
+ rval.set_final_nl(true);
+
+ return rval;
+ }
+
+ size_t calc_max_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ return contents->max_width(first_indent, rest_indent);
+ }
+
+ size_t calc_trailing_width(size_t first_indent,
+ size_t rest_indent) const
+ {
+ return rest_indent;
+ }
+
+ bool calc_final_newline() const
+ {
+ return true;
+ }
+
+ ~_clipbox() { delete contents; }
+
+private:
+ fragment *contents;
+};
+
+fragment *clipbox(fragment *contents) {return new _clipbox(contents);}
+
+class _indentbox:public fragment_container
+{
+public:
+ _indentbox(size_t _firstindent, size_t _restindent, fragment *_contents)
+ :contents(_contents), firstindent(_firstindent),
+ restindent(_restindent) {}
+
+ fragment_contents layout(size_t firstw, size_t restw,
+ const style &st)
+ {
+ if(restw<=restindent)
+ return fragment_contents();
+
+ fragment_line firstprepend(firstindent, L' ', st.get_attrs());
+ fragment_line restprepend(restindent, L' ', st.get_attrs());
+
+ firstprepend.apply_style(st);
+ restprepend.apply_style(st);
+
+ size_t child_firstw=firstw>=firstindent?firstw-firstindent:0;
+ size_t child_restw=restw>=restindent?restw-restindent:0;
+
+ fragment_contents rval, lines=contents->layout(child_firstw,
+ child_restw,
+ st);
+
+ for(fragment_contents::const_iterator i=lines.begin(); i!=lines.end(); ++i)
+ {
+ fragment_line l=((i==lines.begin())?firstprepend:restprepend)+*i;
+
+ rval.push_back(l);
+ }
+
+ // Indentboxes are always followed by a final newline.
+ rval.set_final_nl(true);
+
+ return rval;
+ }
+
+ size_t calc_max_width(size_t my_first_indent,
+ size_t my_rest_indent) const
+ {
+ return contents->max_width(my_first_indent+firstindent,
+ my_rest_indent+restindent);
+ }
+
+ size_t calc_trailing_width(size_t my_first_indent,
+ size_t my_rest_indent) const
+ {
+ return my_rest_indent+restindent;
+ }
+
+ bool calc_final_newline() const
+ {
+ return true;
+ }
+
+ ~_indentbox() { delete contents; }
+private:
+ fragment *contents;
+
+ size_t firstindent, restindent;
+};
+
+fragment *indentbox(size_t firstindent,
+ size_t restindent,
+ fragment *contents)
+{
+ return new _indentbox(firstindent, restindent, contents);
+}
+
+class _fragment_columns : public fragment_container
+{
+ vector<fragment_column_entry> columns;
+
+ void update_widths(vector<size_t> &widths,
+ size_t w) const
+ {
+ size_t total = 0, denominator = 0;
+
+ for(size_t i = 0; i < columns.size(); ++i)
+ {
+ if(columns[i].proportional)
+ {
+ widths[i] = 0;
+ denominator += columns[i].width;
+ }
+ else
+ {
+ widths[i] = columns[i].width;
+ total += widths[i];
+ }
+ }
+
+ if(total < w && denominator > 0)
+ {
+ size_t remainder = w - total;
+
+ for(size_t i = 0; i < columns.size(); ++i)
+ if(columns[i].proportional)
+ {
+ widths[i] += (remainder * columns[i].width) / denominator;
+ denominator -= columns[i].width;
+ remainder -= widths[i];
+ }
+ }
+
+ // Clip.
+ for(size_t i = 0; i < columns.size(); ++i)
+ {
+ widths[i] = min<int>(w, widths[i]);
+ w -= widths[i];
+ }
+ }
+public:
+ _fragment_columns(const vector<fragment_column_entry> &_columns)
+ :columns(_columns)
+ {
+ }
+
+ ~_fragment_columns()
+ {
+ for(vector<fragment_column_entry>::const_iterator
+ i = columns.begin(); i != columns.end(); ++i)
+ delete i->f;
+ }
+
+ fragment_contents layout(size_t firstw, size_t restw, const style &st)
+ {
+ assert(firstw == restw);
+
+ vector<size_t> widths(columns.size());
+ update_widths(widths, restw);
+
+ vector<fragment_contents> child_layouts(columns.size());
+
+ for(size_t i = 0; i < columns.size(); ++i)
+ if(columns[i].f != NULL)
+ child_layouts[i] = columns[i].f->layout(widths[i], widths[i], st);
+
+ size_t height = 0;
+ for(size_t i = 0; i < columns.size(); ++i)
+ if(child_layouts[i].size() > height)
+ height = child_layouts[i].size();
+
+ vector<size_t> starting_lines(columns.size());
+
+ for(size_t i = 0; i < columns.size(); ++i)
+ {
+ switch(columns[i].vert_align)
+ {
+ case fragment_column_entry::top:
+ starting_lines[i] = 0;
+ break;
+
+ case fragment_column_entry::center:
+ starting_lines[i] = (height - child_layouts[i].size()) / 2;
+ break;
+
+ case fragment_column_entry::bottom:
+ starting_lines[i] = height - child_layouts[i].size();
+ break;
+ }
+ }
+
+ fragment_contents rval;
+ for(size_t y = 0; y < height; ++y)
+ {
+ fragment_line tmp(L"");
+
+ for(size_t i = 0; i < columns.size(); ++i)
+ if(columns[i].f != NULL &&
+ y >= starting_lines[i] &&
+ y < starting_lines[i] + child_layouts[i].size())
+ {
+ fragment_line &s = child_layouts[i][y - starting_lines[i]];
+ tmp += s;
+
+ if(widths[i] > s.size())
+ tmp += fragment_line(widths[i] - s.size(), L' ',
+ st.get_attrs());
+ }
+ else
+ tmp += fragment_line(widths[i], L' ', st.get_attrs());
+
+ rval.push_back(tmp);
+ }
+
+ rval.set_final_nl(true);
+
+ return rval;
+ }
+
+ size_t calc_max_width(size_t first_indent, size_t rest_indent) const
+ {
+ assert(first_indent == rest_indent);
+
+ size_t rval = 0;
+
+ for(vector<fragment_column_entry>::const_iterator
+ i = columns.begin(); i != columns.end(); ++i)
+ {
+ size_t thisw =
+ i->f == NULL ? 0 : i->f->max_width(first_indent, rest_indent);
+
+ if(!i->proportional && thisw < i->width)
+ thisw = i->width;
+
+ rval += thisw;
+
+ if(first_indent < thisw)
+ first_indent = 0;
+ else
+ first_indent -= thisw;
+
+ if(rest_indent < thisw)
+ rest_indent = 0;
+ else
+ rest_indent -= thisw;
+ }
+
+ return rval;
+ }
+
+ size_t calc_trailing_width(size_t first_indent, size_t rest_indent) const
+ {
+ assert(first_indent == rest_indent);
+
+ return rest_indent;
+ }
+
+ bool calc_final_newline() const
+ {
+ return true;
+ }
+};
+
+fragment *fragment_columns(const vector<fragment_column_entry> &columns)
+{
+ return new _fragment_columns(columns);
+}
+
+struct argument
+{
+ argument():format(0) {}
+
+ char format;
+ /** If \b true, the 'l' modifier was attached to this argument. */
+ bool islong:1;
+ union
+ {
+ fragment *F;
+ const char *s;
+ const wchar_t *ls;
+ int attr;
+ };
+};
+
+// hack
+string char_to_str(char code)
+{
+ string s;
+
+ if(isprint(code))
+ s+=code;
+ else
+ {
+ char buf[64];
+
+ snprintf(buf, 64, "\\%d", code);
+ s+=buf;
+ }
+
+ return s;
+}
+
+fragment *fragf(const char *format, ...)
+{
+ int argcount=0;
+ int posargcount=0;
+
+ const char *start=format;
+ // find all the arguments.
+ char *nextpercent=strchr(start, '%');
+
+ // loop 1: count the arguments.
+ while(nextpercent!=NULL)
+ {
+ if(*(nextpercent+1)=='l')
+ ++nextpercent;
+
+ switch(*(nextpercent+1))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ char *endptr;
+
+ int pos=strtol(nextpercent+1, &endptr, 10);
+
+ if(*endptr!='$' || (*(endptr+1)!='F' && *(endptr+1)!='s'))
+ {
+ string s="Internal error: bad character in positional argument: '"+char_to_str(*endptr)+"'";
+
+ return text_fragment(s, get_style("Error"));
+ }
+
+ posargcount=max(posargcount, pos);
+
+ start=endptr+2;
+ }
+ break;
+
+ case 'B':
+ case 'b':
+ case 'R':
+ case 'r':
+ case 'D':
+ case 'd':
+ case 'n':
+ case 'N':
+ case '%':
+ start=nextpercent+2;
+ break;
+ case 's':
+ case 'F':
+ case 'A':
+ case 'C':
+ case 'S':
+ ++argcount;
+ start=nextpercent+2;
+ break;
+ default:
+ return text_fragment("Internal error: bad format string code '"+char_to_str(*(nextpercent+1))+"'",
+ get_style("Error"));
+ }
+
+ nextpercent=strchr(start, '%');
+ }
+
+ int nargs=max(argcount, posargcount);
+
+ argument arguments[nargs];
+
+ argcount=0;
+
+ // loop 2: read the list of arguments and parse their type.
+ start=format;
+ nextpercent=strchr(start, '%');
+ while(nextpercent!=NULL)
+ {
+ bool islong=false;
+ if(*(nextpercent+1)=='l')
+ {
+ islong=true;
+ ++nextpercent;
+ }
+
+ switch(*(nextpercent+1))
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ char *endptr;
+
+ int pos=strtol(nextpercent+1, &endptr, 10)-1;
+
+ // if we saw it before it had better be the same type.
+ if(arguments[pos].format!=0 &&
+ (arguments[pos].format!=*(endptr+1) ||
+ arguments[pos].islong!=islong))
+ return text_fragment("Bad argument string to fragf: inconsistent positional parameter types!",
+ get_style("Error"));
+
+ arguments[pos].format=*(endptr+1);
+
+ start=endptr+2;
+ }
+ break;
+
+ case 'B':
+ case 'b':
+ case 'R':
+ case 'r':
+ case 'D':
+ case 'd':
+ case 'n':
+ case 'N':
+ case '%':
+ start=nextpercent+2;
+ break;
+ case 's':
+ case 'F':
+ case 'S':
+ if(arguments[argcount].format!=0 && arguments[argcount].format!=*(nextpercent+1))
+ return text_fragment("Bad argument string to fragf: inconsistent parameter types!",
+ get_style("Error"));
+
+ arguments[argcount].format=*(nextpercent+1);
+
+ ++argcount;
+ start=nextpercent+2;
+ break;
+ default:
+ return text_fragment("Internal error: bad format string code '"+char_to_str(*(nextpercent+1))+"'",
+ get_style("Error"));
+ }
+
+ nextpercent=strchr(start, '%');
+ }
+
+ style st;
+
+ va_list arglst;
+ va_start(arglst, format);
+
+ for(int i=0; i<nargs; ++i)
+ {
+ switch(arguments[i].format)
+ {
+ case 0:
+ break; // I suppose unused arguments are ok
+ case 'F':
+ arguments[i].F=va_arg(arglst, fragment *);
+ break;
+ case 's':
+ arguments[i].s=va_arg(arglst, const char *);
+ break;
+ case 'S':
+ arguments[i].s=va_arg(arglst, const char *);
+ break;
+ default:
+ return text_fragment("Internal error: bad argument format '"+char_to_str(arguments[i].format)+"'",
+ get_style("Error"));
+ }
+ }
+
+ va_end(arglst);
+
+ // Now parse this and generate a result.
+ vector<fragment *> rval;
+
+ start=format;
+ nextpercent=strchr(start, '%');
+
+ // Current argument for non-positional arguments.
+ argcount=0;
+
+ // Optimization: don't create lots of unnecessary text fragments
+ // when one will do.
+ string curstr("");
+
+ // Loop 3: execute the program
+
+ while(nextpercent!=NULL)
+ {
+ bool islong=false;
+
+ // First, make a fragment for everything until the percent:
+ curstr+=string(start, nextpercent-start);
+
+ if(*(nextpercent+1)=='l')
+ {
+ islong=true;
+ ++nextpercent;
+ }
+
+ // This is almost always what we want; in the cases when it's not,
+ // I override it explicitly.
+ start=nextpercent+2;
+
+ // Now, find what's at the percent.
+ switch(*(nextpercent+1))
+ {
+ case '%':
+ curstr+="%";
+ break;
+ case 'B':
+ case 'b':
+ case 'R':
+ case 'r':
+ case 'D':
+ case 'd':
+ if(!curstr.empty())
+ {
+ rval.push_back(text_fragment(curstr, st));
+ curstr="";
+ }
+ switch(*(nextpercent+1))
+ {
+ case 'B':
+ case 'b':
+ st.attrs_flip(A_BOLD);
+ break;
+ case 'R':
+ case 'r':
+ st.attrs_flip(A_REVERSE);
+ break;
+ case 'D':
+ case 'd':
+ st.attrs_flip(A_DIM);
+ break;
+ }
+ break;
+ case 'n':
+ if(!curstr.empty())
+ {
+ rval.push_back(text_fragment(curstr, st));
+ curstr="";
+ }
+
+ rval.push_back(newline_fragment());
+ break;
+ case 'N':
+ if(!curstr.empty())
+ {
+ rval.push_back(text_fragment(curstr, st));
+ curstr="";
+ }
+ st=style();
+ break;
+ case 'F':
+ // should have been verified above.
+ assert(arguments[argcount].format=='F');
+
+ if(!curstr.empty())
+ {
+ rval.push_back(text_fragment(curstr, st));
+ curstr="";
+ }
+ rval.push_back(arguments[argcount].F);
+ ++argcount;
+ break;
+ case 's':
+ // should have been verified above.
+ assert(arguments[argcount].format=='s');
+
+ if(islong)
+ curstr+=transcode(arguments[argcount].ls);
+ else
+ curstr+=arguments[argcount].s;
+ ++argcount;
+ break;
+ case 'S':
+ assert(arguments[argcount].format=='S');
+
+ if(!curstr.empty())
+ {
+ rval.push_back(text_fragment(curstr, st));
+ curstr="";
+ }
+
+ st+=get_style(arguments[argcount].s);
+ ++argcount;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ char *endptr;
+
+ int pos=strtol(nextpercent+1, &endptr, 10)-1;
+
+ assert(arguments[pos].format==*(endptr+1));
+
+ switch(*(endptr+1))
+ {
+ case 'F':
+ if(!curstr.empty())
+ {
+ rval.push_back(text_fragment(curstr, st));
+ curstr="";
+ }
+
+ rval.push_back(arguments[pos].F);
+ break;
+ case 's':
+ curstr+=arguments[pos].s;
+ break;
+ case 'S':
+ if(!curstr.empty())
+ {
+ rval.push_back(text_fragment(curstr, st));
+ curstr="";
+ }
+
+ st+=get_style(arguments[pos].s);
+ break;
+ }
+
+ start=endptr+2;
+ break;
+ }
+ }
+
+ nextpercent=strchr(start, '%');
+ }
+
+ // Get any trailing bit o' string:
+ if(*start!=0)
+ curstr+=start;
+
+ if(!curstr.empty())
+ rval.push_back(text_fragment(curstr, st));
+
+ return sequence_fragment(rval);
+}
diff --git a/src/vscreen/fragment.h b/src/vscreen/fragment.h
new file mode 100644
index 00000000..eca3c778
--- /dev/null
+++ b/src/vscreen/fragment.h
@@ -0,0 +1,353 @@
+// fragment.h -*-c++-*-
+//
+// Copyright (C) 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Fragments are pieces of text that live in a vs_text_layout widget.
+// See vs_text_layout.h for details.
+
+#ifndef FRAGMENT_H
+#define FRAGMENT_H
+
+#include "fragment_contents.h"
+
+#include "config/style.h"
+
+#include <string>
+#include <vector>
+
+/** A fragment represents a logical unit of text.
+ */
+class fragment
+{
+public:
+ /** Return all the lines of this fragment, given the "shape" of the
+ * fragment. Note that some fragments ignore the given widths, so
+ * the caller is expected to either put everything in a formatting
+ * box (one that forces its contents to stay "in bounds") or
+ * manually clip the return value.
+ *
+ * \param firstw the width to which the first line of the fragment
+ * should be formatted.
+ *
+ * \param w the width to which subsequent lines of the fragment
+ * should be formatted.
+ *
+ * \param s the enclosing style of this fragment. The fragment's
+ * size is guaranteed to be independent of s.
+ *
+ * \return the lines of this fragment; the caller is responsible
+ * for deleting it.
+ */
+ virtual fragment_contents layout(size_t firstw,
+ size_t w,
+ const style &st)=0;
+
+ /** \param first_indent the indentation of the first line, relative
+ * to a baseline (which may be outside this fragment).
+ *
+ * \param rest_indent the indentation of any other lines.
+ *
+ * \return the maximum length of any line in this fragment. Any
+ * call to layout() with a width greater than this maximum length
+ * will produce the same result.
+ */
+ virtual size_t max_width(size_t first_indent,
+ size_t rest_indent) const=0;
+
+ /** \param first_indent the indentation of the first line.
+ *
+ * \param rest_indent the indentation of any other lines.
+ *
+ * \return the length of any "trailing" line in the fragment,
+ * including indentation.
+ */
+ virtual size_t trailing_width(size_t first_indent,
+ size_t rest_indent) const=0;
+
+ /** \return \b true if this fragment ends in a newline. */
+ virtual bool final_newline() const=0;
+
+ /** Nothing to do in the base class */
+ virtual ~fragment();
+};
+
+// Factory methods to avoid cluttering the .h file:
+
+/** Create a fragment from a string of text. The text will simply be
+ * formatted as a single line without clipping. The text should not
+ * contain a newline (\n).
+ *
+ * \param s the text to use
+ *
+ * \return the new fragment
+ */
+fragment *text_fragment(const std::wstring &s);
+
+/** Create a fragment from a string of text. The text will simply be
+ * formatted as a single line without clipping.
+ *
+ * \param s the text to use
+ * \param style the base style for the new fragment; an implicit
+ * style_fragment for this style is wrapped around the new text_fragment.
+ *
+ * \return the new fragment
+ */
+fragment *text_fragment(const std::wstring &s,
+ const style &st);
+
+/** Create a fragment from a string of multibyte-encoded text.
+ *
+ * \param s the text to use
+ * \param encoding the text's encoding; if this is \b null or
+ * unspecified, LC_CTYPE will be used.
+ *
+ * \return the new fragment
+ */
+fragment *text_fragment(const std::string &s,
+ const char *encoding=NULL);
+
+/** Create a fragment from a string of multibyte-encoded text,
+ * wrapping an implicit style_fragment around it.
+ */
+fragment *text_fragment(const std::string &s,
+ const style &st,
+ const char *encoding=NULL);
+
+/** Create a fragment from a string of text. The text will simply be
+ * formatted as a single line without clipping.
+ *
+ * \param s the text to use
+ * \param attr attributes to assign to it
+ *
+ * \return the new fragment
+ */
+inline fragment *text_fragment(const char *s,
+ const style &st=style())
+{
+ return text_fragment(std::string(s), st);
+}
+
+/** Create a fragment which simply produces a newline wherever it occurs. */
+fragment *newline_fragment();
+
+/** Create a fragment which alters the style of its contents.
+ *
+ * \param f the child of this fragment
+ * \param attr the text attribute which should be assigned to f
+ *
+ * \return the new fragment
+ */
+fragment *style_fragment(fragment *f,
+ const style &st);
+
+/** Create a fragment from a sequence of other fragments.
+ *
+ * The fragment will simply "shove" the two sequences together,
+ * respecting the value of final_nl on each.
+ *
+ * \todo can this be made more efficient? It should be possible to
+ * just "plug" the sequences together if they're lists -- but that only
+ * works if they aren't cached elsewhere.
+ *
+ * \param fragments the fragments in the sequence
+ *
+ * \return the new fragment
+ */
+fragment *sequence_fragment(const std::vector<fragment *> &fragments);
+
+/** Create a fragment from a sequence of other fragments.
+ *
+ * The fragment will simply "shove" the two sequences together,
+ * respecting the value of final_nl on each.
+ *
+ * \param f the first fragment in the sequence; the sequence should
+ * be terminated with a NULL pointer.
+ *
+ * \return the new fragment
+ */
+fragment *sequence_fragment(fragment *f, ...);
+
+/** Join fragments into a single fragment, placing text between them.
+ *
+ * This is useful for creating lists, for instance. The new fragment
+ * takes ownership of all pointers in fragments.
+ *
+ * \param fragments the list of fragments to join
+ * \param between a string to place between adjacent items in the input list
+ */
+fragment *join_fragments(const std::vector<fragment *> &fragments,
+ const std::wstring &between);
+
+/** Create a flowbox.
+ *
+ * Each line of the fragment placed inside the flowbox will be
+ * reflowed (word-wrapped) to the current width, possibly to
+ * several lines.
+ *
+ * The contents of a flowbox always have final_nl=\b true. (ie, a
+ * flowbox is always followed by a line break)
+ *
+ * \param contents the contents of the flowbox
+ *
+ * \return the new flowbox
+ */
+fragment *flowbox(fragment *contents);
+
+/** Create a fillbox.
+ *
+ * Each line of the fragment placed inside the fillbox will be
+ * reflowed (word-wrapped) and expanded to the current width,
+ * possibly to several lines.
+ *
+ * The contents of a fillbox always have final_nl=\b true.
+ *
+ * \param contents the contents of the fillbox
+ *
+ * \return the new fillbox
+ */
+fragment *fillbox(fragment *contents);
+
+/** Create a hardwrapbox.
+ *
+ * Each line of the fragment inside the box will be hard-wrapped
+ * to the current width.
+ *
+ * The contents of a wrapbox always have final_nl=\b true.
+ *
+ * \param contents the contents of the hardwrapbox
+ *
+ * \return the new hardwrapbox
+ */
+fragment *hardwrapbox(fragment *contents);
+
+/** Create a clipbox.
+ *
+ * Each line of the fragment placed inside the clipbox will be
+ * clipped to the current width. The whole layout widget
+ * implicitly uses one of these, but there may be other uses for
+ * clipboxes as well.
+ *
+ * \param contents the contents of the clipbox
+ *
+ * \return the new clipbox
+ */
+fragment *clipbox(fragment *contents);
+
+/** Create an indentbox.
+ *
+ * Each line of the indentbox will be indented by the specified
+ * amount. (this effectively decreases the width of each line) If
+ * desired, the first line can be indented a different amount
+ * (typically less) than the remaining lines, although it is
+ * formatted to the same width; this supports things like bulletted
+ * lists.
+ *
+ * \param firstindent the number of spaces of indentation to use for the first line
+ * \param restindent the number of spaces of indentation to use for later lines
+ * \param contents the contents of the indentbox
+ * \return the new indentbox
+ */
+fragment *indentbox(size_t firstindent, size_t restindent, fragment *contents);
+
+/** Stores information on a single column of fragments. */
+struct fragment_column_entry
+{
+ /** If \b true, this column is allocated space proportionally;
+ * otherwise, its width is exactly what is specified.
+ */
+ bool proportional;
+
+ /** If proportional is \b true, this is a number giving the relative
+ * size of this column compared to other proportional columns;
+ * otherwise, this is the width of the column in character cells.
+ */
+ size_t width;
+
+ enum align {top, center, bottom};
+
+ /** The vertical alignment of the column. If top, the top of this
+ * column is placed at the top of the fragment. If center, the
+ * center of this column is aligned with the center of the
+ * fragment. And if bottom, the bottom of this column is aligned
+ * with the bottom of the fragment.
+ */
+ align vert_align;
+
+ /** The fragment to display, or \b NULL for blank space. */
+ fragment *f;
+
+ fragment_column_entry(bool _proportional, size_t _width, align _vert_align,
+ fragment *_f)
+ :proportional(_proportional),
+ width(_width),
+ vert_align(_vert_align),
+ f(_f)
+ {
+ }
+
+ fragment_column_entry()
+ :proportional(false), width(0), vert_align(top)
+ {
+ }
+};
+
+/** A fragment that formats its contents into columns. If the
+ * fixed-width columns overflow the available space, they will be
+ * clipped hard.
+ *
+ * This fragment may NOT be placed inside an indent box or any other
+ * box that alters the shape of its contents. Doing so will cause
+ * the program to abort.
+ *
+ * \param columns a list of column entry information ordered from
+ * left to right.
+ */
+fragment *fragment_columns(const std::vector<fragment_column_entry> &columns);
+
+/** A printf-alike for fragments.
+ *
+ * Formatting codes:
+ *
+ * - %F: substitutes a fragment into the sequence being built
+ * - %s: substitutes a const char * into the sequence being built
+ * with transcoding according to LC_CTYPE
+ * - %n: substitutes a newline fragment into the sequence being built
+ * - %%: inserts a literal %
+ * - %B/%b: toggle the bold character attribute
+ * - %R/%r: toggle the reverse video character attribute
+ * - %D/%d: toggle the dim character attribute
+ * - %S: Apply the style corresponding to a
+ * string (looked up via get_style) to the attributes.
+ * - %N: Reset the text style to the empty ("null") style.
+ *
+ * For instance,
+ *
+ * fragf("%S%BWARNING%b: something bad happened in routine %s,"
+ * "expect a segfault.", "Error", some_routine);
+ *
+ * Note: if you use a parameter index multiple times, you are virtually
+ * GUARANTEED to segfault!
+ *
+ * Note 2: as usual, format should not contain literal newlines.
+ *
+ * \param format the format string
+ * \return the formatted fragment, or NULL if there is an error in the format.
+ */
+fragment *fragf(const char *format, ...);
+
+#endif
diff --git a/src/vscreen/fragment_cache.cc b/src/vscreen/fragment_cache.cc
new file mode 100644
index 00000000..c766d69d
--- /dev/null
+++ b/src/vscreen/fragment_cache.cc
@@ -0,0 +1,97 @@
+// fragment_cache.cc
+//
+// Copyright (C) 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "fragment_cache.h"
+
+fragment_cache::fragment_cache(fragment *_contents)
+ :contents(_contents), cached_lines_valid(false),
+ cached_max_width_valid(false), cached_trailing_width_valid(false),
+ cached_final_nl_valid(false)
+{
+}
+
+fragment_cache::~fragment_cache()
+{
+ delete contents;
+}
+
+void fragment_cache::invalidate()
+{
+ cached_lines_valid=cached_max_width_valid=false;
+ cached_trailing_width_valid=cached_final_nl=false;
+}
+
+fragment_contents fragment_cache::layout(size_t firstw, size_t restw,
+ const style &st)
+{
+ if(!cached_lines_valid ||
+ cached_lines_first_width != firstw ||
+ cached_lines_rest_width != restw ||
+ cached_lines_style != st)
+ {
+ cached_lines=contents->layout(firstw, restw, st);
+ cached_lines_first_width=firstw;
+ cached_lines_rest_width=restw;
+ cached_lines_style=st;
+ cached_lines_valid=true;
+ }
+
+ return cached_lines;
+}
+
+size_t fragment_cache::max_width(size_t first_indent, size_t rest_indent) const
+{
+ if(!cached_max_width_valid ||
+ first_indent != cached_max_width_first_indent ||
+ rest_indent != cached_max_width_rest_indent)
+ {
+ cached_max_width=contents->max_width(first_indent, rest_indent);
+ cached_max_width_first_indent=first_indent;
+ cached_max_width_rest_indent=rest_indent;
+ cached_max_width_valid=true;
+ }
+
+ return cached_max_width;
+}
+
+size_t fragment_cache::trailing_width(size_t first_indent, size_t rest_indent) const
+{
+ if(!cached_trailing_width_valid ||
+ first_indent != cached_trailing_width_first_indent ||
+ rest_indent != cached_trailing_width_rest_indent)
+ {
+ cached_trailing_width=contents->trailing_width(first_indent, rest_indent);
+ cached_trailing_width_first_indent=first_indent;
+ cached_trailing_width_rest_indent=rest_indent;
+ cached_trailing_width_valid=true;
+ }
+
+ return cached_trailing_width;
+}
+
+bool fragment_cache::final_newline() const
+{
+ if(!cached_final_nl_valid)
+ {
+ cached_final_nl=contents->final_newline();
+ cached_final_nl_valid=true;
+ }
+
+ return cached_final_nl;
+}
diff --git a/src/vscreen/fragment_cache.h b/src/vscreen/fragment_cache.h
new file mode 100644
index 00000000..0c39cf03
--- /dev/null
+++ b/src/vscreen/fragment_cache.h
@@ -0,0 +1,84 @@
+// fragment_cache.h -*-c++-*-
+//
+// Copyright (C) 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// A special fragment class that caches its contents.
+
+#ifndef FRAGMENT_CACHE_H
+#define FRAGMENT_CACHE_H
+
+#include "fragment.h"
+
+/** A fragment that caches its contents; a cached result is used if
+ * the same width is passed to the layout routine twice in a row.
+ * Obviously this should only be done if you know that the contents
+ * are static.
+ */
+class fragment_cache:public fragment
+{
+ fragment *contents;
+
+ /** The last cached result. */
+ mutable fragment_contents cached_lines;
+
+ /** If cached_lines is valid, it was formatted in this style: */
+ mutable style cached_lines_style;
+
+ /** If cached_lines is valid, cached_lines was formatted for these
+ * widths.
+ */
+ mutable size_t cached_lines_first_width, cached_lines_rest_width;
+
+ /** The cached max_width value. */
+ mutable size_t cached_max_width;
+
+ /** For what indents is cached_max_width valid? */
+ mutable size_t cached_max_width_first_indent, cached_max_width_rest_indent;
+
+ /** The cached trailing_width value. */
+ mutable size_t cached_trailing_width;
+
+ /** For what indents is cached_trailing_width valid? */
+ mutable size_t cached_trailing_width_first_indent, cached_trailing_width_rest_indent;
+
+ /** The cached final_newline value. */
+ mutable bool cached_final_nl:1;
+
+ /** If \b true, the corresponding property is valid. */
+ mutable bool cached_lines_valid:1, cached_max_width_valid:1;
+ /** If \b true, the corresponding property is valid. */
+ mutable bool cached_trailing_width_valid:1, cached_final_nl_valid:1;
+public:
+ fragment_cache(fragment *_contents);
+ ~fragment_cache();
+
+ void invalidate();
+
+ fragment_contents layout(size_t firstw, size_t restw,
+ const style &st);
+
+ void set_attr(int attr);
+
+ size_t max_width(size_t first_indent, size_t rest_indent) const;
+ size_t trailing_width(size_t first_indent, size_t rest_indent) const;
+
+ bool final_newline() const;
+};
+
+
+#endif
diff --git a/src/vscreen/fragment_contents.h b/src/vscreen/fragment_contents.h
new file mode 100644
index 00000000..648da8b5
--- /dev/null
+++ b/src/vscreen/fragment_contents.h
@@ -0,0 +1,216 @@
+// fragment_contents.h -*-c++-*-
+//
+// Copyright 2004 Daniel Burrows
+//
+// A nice way of storing the contents of a fragment.
+
+#ifndef FRAGMENT_CONTENTS_H
+#define FRAGMENT_CONTENTS_H
+
+#include "curses++.h"
+
+#include <vector>
+
+/** The type used to represent a line of a fragment; it might be
+ * worthwhile to change this to a rope<chtype> if this class is
+ * used to format larger pieces of text.
+ */
+typedef wchstring fragment_line;
+
+/** This class represents the formatted contents of a fragment.
+ *
+ * To minimize silly mistakes, the lines are reference-counted.
+ *
+ * \todo give this proper const_iterators and deal with the
+ * mutable-sharing problem (this structure is mutable but has shared
+ * data).
+ */
+class fragment_contents
+{
+ class fragment_lines
+ {
+ std::vector<fragment_line> v;
+ mutable int refs;
+
+ public:
+ class const_iterator;
+
+ class iterator
+ {
+ typedef std::vector<fragment_line>::iterator ittype;
+
+ fragment_lines *lines;
+ ittype i;
+
+ public:
+ iterator(fragment_lines *_lines, const ittype &_i)
+ :lines(_lines), i(_i)
+ { lines->incref(); }
+
+ iterator(const iterator &other)
+ :lines(other.lines), i(other.i)
+ { lines->incref(); }
+
+ ~iterator() { lines->decref(); }
+
+ iterator &operator++() { ++i; return *this; }
+ iterator &operator--() { --i; return *this; }
+
+ fragment_line &operator*() { return *i; }
+ fragment_line *operator->() { return &*i; }
+
+ iterator &operator=(const iterator &other) {
+ other.lines->incref();
+ lines->decref();
+ lines=other.lines;
+ i=other.i;
+
+ return *this;
+ }
+
+ bool operator==(const iterator &other) const {return i==other.i;}
+ bool operator!=(const iterator &other) const {return i!=other.i;}
+
+ friend class const_iterator;
+ };
+
+ class const_iterator
+ {
+ typedef std::vector<fragment_line>::const_iterator ittype;
+
+ const fragment_lines *lines;
+ ittype i;
+
+ public:
+ const_iterator(const fragment_lines *_lines, const ittype &_i)
+ :lines(_lines), i(_i)
+ { lines->incref(); }
+
+ const_iterator(const iterator &other)
+ :lines(other.lines), i(other.i)
+ { lines->incref(); }
+
+ const_iterator(const const_iterator &other)
+ :lines(other.lines), i(other.i)
+ { lines->incref(); }
+
+ ~const_iterator() { lines->decref(); }
+
+ const_iterator &operator++() { ++i; return *this; }
+ const_iterator &operator--() { --i; return *this; }
+
+ const fragment_line &operator*() { return *i; }
+ const fragment_line *operator->() { return &*i; }
+
+ const_iterator &operator=(const const_iterator &other) {
+ other.lines->incref();
+ lines->decref();
+ lines=other.lines;
+ i=other.i;
+
+ return *this;
+ }
+
+ const_iterator &operator=(const iterator &other) {
+ other.lines->incref();
+ lines->decref();
+ lines=other.lines;
+ i=other.i;
+
+ return *this;
+ }
+
+ bool operator==(const iterator &other) const {return i==other.i;}
+ bool operator==(const const_iterator &other) const {return i==other.i;}
+
+ bool operator!=(const iterator &other) const {return i!=other.i;}
+ bool operator!=(const const_iterator &other) const {return i!=other.i;}
+ };
+
+ fragment_lines():refs(0) {}
+
+ void incref() const {++refs;}
+ void decref() const {--refs; if(refs==0) delete this;}
+
+ void push_back(const fragment_line &l) {v.push_back(l);}
+
+ iterator begin() {return iterator(this, v.begin());}
+ const_iterator begin() const {return const_iterator(this, v.begin());}
+
+ iterator end() {return iterator(this, v.end());}
+ const_iterator end() const {return const_iterator(this, v.end());}
+
+ fragment_line &front() {return v.front();}
+ const fragment_line &front() const {return v.front();}
+
+ fragment_line &back() {return v.back();}
+ const fragment_line &back() const {return v.back();}
+
+ fragment_line &operator[](int i) { return v[i]; }
+ const fragment_line &operator[](int i) const { return v[i]; }
+
+ size_t size() const {return v.size();}
+ };
+public:
+ typedef fragment_lines::iterator iterator;
+ typedef fragment_lines::const_iterator const_iterator;
+
+ /** Generate empty contents for a fragment. */
+ fragment_contents():lines(new fragment_lines), final_nl(false)
+ {lines->incref();}
+
+ /** Copy constructor. */
+ fragment_contents(const fragment_contents &other)
+ :lines(other.lines), final_nl(other.final_nl)
+ {
+ lines->incref();
+ }
+
+ /** When this is destroyed, decrement the lines' reference count. */
+ ~fragment_contents() {lines->decref();}
+
+ void push_back(const fragment_line &l) {lines->push_back(l);}
+
+ iterator begin() {return lines->begin();}
+ const_iterator begin() const {return lines->begin();}
+
+ iterator end() {return lines->end();}
+ iterator end() const {return lines->end();}
+
+ fragment_line &front() {return lines->front();}
+ const fragment_line &front() const {return lines->front();}
+
+ fragment_line &back() {return lines->back();}
+ const fragment_line &back() const {return lines->back();}
+
+ size_t size() const {return lines->size();}
+
+ void set_final_nl(bool final_nl_new) {final_nl=final_nl_new;}
+
+ bool get_final_nl() {return final_nl;}
+
+ fragment_line &operator[](int i) { return (*lines)[i]; }
+ const fragment_line &operator[](int i) const { return (*lines)[i]; }
+
+ fragment_contents &operator=(const fragment_contents &other)
+ {
+ other.lines->incref();
+ lines->decref();
+
+ lines=other.lines;
+ final_nl=other.final_nl;
+
+ return *this;
+ }
+
+private:
+ /** The vector of lines that this contents object is associated with. */
+ fragment_lines *lines;
+
+ /** If \b true, indicates that the last line of this fragment
+ * should be followed by a newline.
+ */
+ bool final_nl;
+};
+
+#endif
diff --git a/src/vscreen/ref_ptr.h b/src/vscreen/ref_ptr.h
new file mode 100644
index 00000000..c282a83b
--- /dev/null
+++ b/src/vscreen/ref_ptr.h
@@ -0,0 +1,156 @@
+// ref_ptr.h -*-c++-*-
+//
+// A reference-counting pointer. The object behind the pointer should
+// implement incref() and decref(); the pointer arranges for these
+// methods to be called at the appropriate times.
+
+#ifndef REF_PTR_H
+#define REF_PTR_H
+
+#include <sigc++/reference_wrapper.h>
+
+#include <assert.h>
+
+template<class T>
+class ref_ptr
+{
+ T *ref;
+
+public:
+ ref_ptr(T *_ref)
+ :ref(_ref)
+ {
+ if(ref != 0)
+ ref->incref();
+ }
+
+ ref_ptr(const ref_ptr &other)
+ :ref(other.ref)
+ {
+ if(ref != 0)
+ ref->incref();
+ }
+
+ template<class S>
+ ref_ptr(const ref_ptr<S> &other)
+ :ref(other.unsafe_get_ref())
+ {
+ if(ref != 0)
+ ref->incref();
+ }
+
+ ref_ptr()
+ :ref(0)
+ {
+ }
+
+ ~ref_ptr()
+ {
+ if(ref != 0)
+ ref->decref();
+ }
+
+ ref_ptr &operator=(const ref_ptr &other)
+ {
+ if(other.ref != 0)
+ other.ref->incref();
+
+ if(ref != 0)
+ ref->decref();
+
+ ref = other.ref;
+
+ return *this;
+ }
+
+ const sigc::reference_wrapper<T> weak_ref() const
+ {
+ assert(ref != 0);
+
+ return sigc::ref(*ref);
+ }
+
+ // If S is assignment-compatible with T and both have
+ // reference-counting methods, perform this assignment.
+ //
+ // Read: upcasting pointers.
+ template<class S>
+ ref_ptr<T> &operator=(const ref_ptr<S> &other)
+ {
+ S * const other_ref = other.unsafe_get_ref();
+
+ if(other_ref != 0)
+ other_ref->incref();
+
+ if(ref != 0)
+ ref->decref();
+
+ ref = other_ref;
+
+ return *this;
+ }
+
+ template<class S>
+ bool operator==(const ref_ptr<S> &other) const
+ {
+ return ref == other.unsafe_get_ref();
+ }
+
+ template<class S>
+ bool operator!=(const ref_ptr<S> &other) const
+ {
+ return ref != other.unsafe_get_ref();
+ }
+
+ template<class S>
+ bool operator<(const ref_ptr<S> &other) const
+ {
+ return ref < other.unsafe_get_ref();
+ }
+
+ template<class S>
+ bool operator>(const ref_ptr<S> &other) const
+ {
+ return ref > other.unsafe_get_ref();
+ }
+
+ template<class S>
+ bool operator<=(const ref_ptr<S> &other) const
+ {
+ return ref <= other.unsafe_get_ref();
+ }
+
+ template<class S>
+ bool operator>=(const ref_ptr<S> &other) const
+ {
+ return ref >= other.unsafe_get_ref();
+ }
+
+ // Safe downcasting.
+ template<class S>
+ ref_ptr<S> dyn_downcast() const
+ {
+ return ref_ptr<S>(dynamic_cast<S*>(ref));
+ }
+
+ bool valid() const
+ {
+ return ref != 0;
+ }
+
+ T *operator->() const
+ {
+ return ref;
+ }
+
+ /** Extract the pointer. Should generally be used with care (but is
+ * used in the implementation to cast/compare between differently
+ * templated instances).
+ */
+ T *unsafe_get_ref() const
+ {
+ return ref;
+ }
+};
+
+#endif
diff --git a/src/vscreen/testvscreen.cc b/src/vscreen/testvscreen.cc
new file mode 100644
index 00000000..8eb220b7
--- /dev/null
+++ b/src/vscreen/testvscreen.cc
@@ -0,0 +1,511 @@
+// testvscreen.cc
+//
+// I really need a test for the vscreen libraries that's separate from the
+// main Aptitude program. This is it.
+
+#include "fragment.h"
+#include "transcode.h"
+#include "vscreen.h"
+#include "vscreen_widget.h"
+#include "vs_button.h"
+#include "vs_center.h"
+#include "vs_editline.h"
+#include "vs_frame.h"
+#include "vs_label.h"
+#include "vs_layout_item.h"
+#include "vs_multiplex.h"
+#include "vs_minibuf_win.h"
+#include "vs_menu.h"
+#include "vs_menubar.h"
+#include "vs_pager.h"
+#include "vs_radiogroup.h"
+#include "vs_scrollbar.h"
+#include "vs_size_box.h"
+#include "vs_stacked.h"
+#include "vs_subtree.h"
+#include "vs_table.h"
+#include "vs_text_layout.h"
+#include "vs_togglebutton.h"
+#include "vs_tree.h"
+#include "vs_util.h"
+
+#include <config/colors.h>
+
+#include <string>
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+#include <sigc++/functors/ptr_fun.h>
+
+using namespace std;
+
+// Er..having this be global is not so great?
+vs_stacked_ref stacker=vs_stacked::create(0,0);
+
+class test_keyname:public vs_label
+{
+public:
+ test_keyname():vs_label("")
+ {
+ set_bg_style(get_style("EditLine"));
+ }
+
+ size size_request()
+ {
+ return size(10, 0);
+ }
+
+ bool focus_me() {return true;}
+
+ bool handle_key(const key &k)
+ {
+ set_text(readable_keyname(k));
+ return true;
+ }
+};
+
+class silly_block:public vscreen_widget
+{
+public:
+ silly_block(const style &st)
+ {
+ set_bg_style(st);
+ }
+
+ size size_request()
+ {
+ return size(0,0);
+ }
+
+ void paint()
+ {
+ }
+};
+
+class silly_treeitem:public vs_treeitem
+{
+ wstring txt;
+public:
+ silly_treeitem(const wstring &_txt):txt(_txt) {}
+ silly_treeitem(const string &_txt):txt(transcode(_txt)) {}
+
+ void paint(vs_tree *win, int y, bool hierarchical, const style &st)
+ {
+ vs_treeitem::paint(win, y, hierarchical, txt);
+ }
+
+ const wchar_t *tag() {return txt.c_str();}
+ const wchar_t *label() {return txt.c_str();}
+};
+
+class silly_subtree:public vs_subtree_generic
+{
+ wstring txt;
+public:
+ silly_subtree(bool expanded, const wstring &_txt)
+ :vs_subtree_generic(expanded), txt(_txt) {}
+
+ silly_subtree(bool expanded, const string &_txt)
+ :vs_subtree_generic(expanded), txt(transcode(_txt)) {}
+
+ void paint(vs_tree *win, int y, bool hierarchical, const style &st)
+ {
+ vs_subtree_generic::paint(win, y, hierarchical, txt);
+ }
+
+ const wchar_t *tag() {return txt.c_str();}
+ const wchar_t *label() {return txt.c_str();}
+};
+
+void do_toggle_hierarchical(vs_tree &tree)
+{
+ tree.set_hierarchical(!tree.get_hierarchical());
+}
+
+vs_widget_ref make_test_treewidget()
+{
+ vs_tree_ref tree=vs_tree::create();
+
+ silly_subtree *root=new silly_subtree(true, "The Root Of It All");
+ silly_subtree *tree1=new silly_subtree(false, "Money");
+ tree1->add_child(new silly_treeitem("Gold"));
+ tree1->add_child(new silly_treeitem("Copper"));
+ tree1->add_child(new silly_treeitem("Silver"));
+ tree1->add_child(new silly_treeitem("Paper"));
+ root->add_child(tree1);
+
+ silly_subtree *tree2=new silly_subtree(false, "Entropy");
+ silly_subtree *tree3=new silly_subtree(true, "Mortality");
+ tree3->add_child(new silly_treeitem("Death"));
+ tree3->add_child(new silly_treeitem("Famine"));
+ tree3->add_child(new silly_treeitem("Pestilence"));
+ tree3->add_child(new silly_treeitem("War"));
+ tree2->add_child(tree3);
+ tree2->add_child(new silly_treeitem("Rot"));
+ tree2->add_child(new silly_treeitem("Decay"));
+ tree2->add_child(new silly_treeitem("Bad Things"));
+ root->add_child(tree2);
+
+ // A commentary on the analytical machine, translated by Lady Ada.
+ silly_subtree *anaengine=new silly_subtree(false, "Charles B.");
+ anaengine->add_child(new vs_layout_item(sequence_fragment(flowbox(text_fragment("Those labours which belong to the various branches of the mathematical sciences, although on first consideration they seem to be the exclusive province of intellect, may, nevertheless, be divided into two distinct sections; one of which may be called the mechanical, because it is subjected to precise and invariable laws, that are capable of being expressed by means of the operations of matter; while the other, demanding the intervention of reasoning, belongs more specially to the domain of the understanding. This admitted, we may propose to execute, by means of machinery, the mechanical branch of these labours, reserving for pure intellect that which depends on the reasoning faculties. Thus the rigid exactness of those laws which regulate numerical calculations must frequently have suggested the employment of material instruments, either for executing the whole of such calculations or for abridging them; and thence have arisen several inventions having this object in view, but which have in general but partially attained it. For instance, the much-admired machine of Pascal is now simply an object of curiosity, which, whilst it displays the powerful intellect of its inventor, is yet of little utility in itself. Its powers extended no further than the execution of the first four operations of arithmetic, and indeed were in reality confined to that of the first two, since multiplication and division were the result of a series of additions and subtractions. The chief drawback hitherto on most of such machines is, that they require the continual intervention of a human agent to regulate their movements, and thence arises a source of errors; so that, if their use has not become general for large numerical calculations, it is because they have not in fact resolved the double problem which the question presents, that of correctness in the results, united with economy of time.")),
+ newline_fragment(),
+ flowbox(text_fragment("Struck with similar reflections, Mr. Babbage has devoted some years to the realization of a gigantic idea. He proposed to himself nothing less than the construction of a machine capable of executing not merely arithmetical calculations, but even all those of analysis, if their laws are known. The imagination is at first astounded at the idea of such an undertaking; but the more calm reflection we bestow on it, the less impossible does success appear, and it is felt that it may depend on the discovery of some principle so general, that, if applied to machinery, the latter may be capable of mechanically translating the operations which may be indicated to it by algebraical notation. The illustrious inventor having been kind enough to communicate to me some of his views on this subject during a visit he made at Turin, I have, with his approbation, thrown together the impressions they have left on my mind. But the reader must not expect to find a description of Mr. Babbage's engine; the comprehension of this would entail studies of much length; and I shall endeavour merely to give an insight into the end proposed, and to develop the principles on which its attainment depends.")),
+ NULL)));
+ root->add_child(anaengine);
+
+ silly_subtree *tree4=new silly_subtree(false, "More stuff");
+ for(int i=0; i<10; i++)
+ {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "Category %d", i);
+
+ silly_subtree *sub=new silly_subtree(false, buf);
+ for(int j=0; j<10; j++)
+ {
+ snprintf(buf, sizeof(buf), "Item %d", j);
+ sub->add_child(new silly_treeitem(buf));
+ }
+
+ tree4->add_child(sub);
+ }
+ root->add_child(tree4);
+ root->sort();
+
+ tree->set_root(root, true);
+
+ tree->connect_key("ToggleHier", &global_bindings,
+ sigc::bind(sigc::ptr_fun(do_toggle_hierarchical),
+ tree.weak_ref()));
+
+ return tree;
+}
+
+void show_nasty_message()
+{
+ stacker->add_visible_widget(vs_dialog_ok(flowbox(fragf("Your mother was a hamster, and your father smelt of elderberry!%n%nNow go away, or I shall taunt you a second time!")), NULL), true);
+}
+
+void interrogate()
+{
+ stacker->add_visible_widget(vs_dialog_yesno(flowbox(fragf("Do you like Debian?")), NULL, arg(sigc::ptr_fun(show_nasty_message))), true);
+}
+
+void dobeep()
+{
+ beep();
+}
+
+vs_menu_info test_file_menu[]=
+{
+ vs_menu_info(vs_menu_info::VS_MENU_ITEM, "^Interrogate", NULL,
+ "NO-ONE EXPECTS THE SPANISH INQUISITION!", sigc::ptr_fun(&interrogate)),
+ VS_MENU_SEPARATOR,
+ vs_menu_info(vs_menu_info::VS_MENU_ITEM, "^Quit", "Quit",
+ "Leave this wonderful program", sigc::ptr_fun(&vscreen_exitmain)),
+ VS_MENU_END,
+};
+
+vs_menu_info test_test_menu[]=
+{
+ vs_menu_info(vs_menu_info::VS_MENU_ITEM, "Test ^Item 1", NULL, "Foo", sigc::ptr_fun(&dobeep)),
+ vs_menu_info(vs_menu_info::VS_MENU_ITEM, "Test Item ^2", NULL, "Bar", sigc::ptr_fun(&dobeep)),
+ VS_MENU_SEPARATOR,
+ vs_menu_info(vs_menu_info::VS_MENU_ITEM, "Test Item ^3", NULL, "Baz", sigc::ptr_fun(&dobeep)),
+ VS_MENU_END
+};
+
+vs_menu_info test_help_menu[]=
+{
+ vs_menu_info(vs_menu_info::VS_MENU_ITEM, "^About", NULL, "Useless dialog box",
+ VS_MENU_NOP),
+ VS_MENU_SEPARATOR,
+ vs_menu_info(vs_menu_info::VS_MENU_ITEM, "^Help...", "Help", "unimplemented",
+ VS_MENU_NOP),
+ vs_menu_info(vs_menu_info::VS_MENU_ITEM, "^Don't help...", NULL, "unimplemented",
+ VS_MENU_NOP),
+ VS_MENU_END
+};
+
+void radio_button_selected(int id, vs_label &l)
+{
+ char buf[128];
+
+ snprintf(buf, 128, "You have selected button %i", id);
+ l.set_text(buf);
+}
+
+vs_widget_ref button_mania()
+{
+ vs_table_ref rval=vs_table::create();
+ vs_label_ref label=vs_label::create("ERROR");
+
+ rval->add_widget(label, 5, 0, 1, 4, false);
+
+ for(int i=0; i<4; i++)
+ {
+ vs_radiogroup *g=NULL;
+
+ if(i==3)
+ {
+ g=new vs_radiogroup;
+ g->item_selected.connect(sigc::bind(sigc::ptr_fun(&radio_button_selected),
+ label.weak_ref()));
+ rval->destroyed.connect(sigc::mem_fun(g, &vs_radiogroup::destroy));
+ }
+ for(int j=0; j<4; j++)
+ {
+ char buf[256];
+ snprintf(buf, 256, "%i", i*4+j);
+ fragment *f=fragf("Button %B%s%b", buf);
+ if(j==2)
+ f=fragf("Line 1%n%F%nLine 3", f);
+
+ if(i!=3)
+ rval->add_widget_opts(vs_button::create(f), i, j, 1, 1,
+ vs_table::ALIGN_CENTER, vs_table::ALIGN_CENTER);
+ else
+ {
+ vs_togglebutton_ref b=vs_radiobutton::create(f);
+ rval->add_widget_opts(b, i, j, 1, 1,
+ vs_table::ALIGN_CENTER | vs_table::EXPAND, vs_table::ALIGN_CENTER);
+ g->add_button(b, i*4+j);
+ }
+ }
+ }
+
+ rval->add_widget_opts(vs_checkbutton::create("Button 16"), 4, 0, 1, 2,
+ vs_table::ALIGN_CENTER,
+ vs_table::ALIGN_CENTER | vs_table::EXPAND);
+ rval->add_widget_opts(vs_button::create("Button 17"), 4, 2, 1, 2,
+ vs_table::ALIGN_CENTER,
+ vs_table::ALIGN_CENTER | vs_table::EXPAND);
+
+ return rval;
+}
+
+static void do_load_file(wstring s, vs_editline &p)
+{
+ p.set_text(L"");
+}
+
+vs_widget_ref pager_test()
+{
+ vs_table_ref tbl=vs_table::create();
+ vs_editline_ref ln=vs_editline::create("Enter a filename to load: ");
+ const char *s="This space for rent.\n\nApply above.";
+ vs_file_pager_ref pager=vs_file_pager::create(s, strlen(s));
+ vs_scrollbar_ref scrl=vs_scrollbar::create(vs_scrollbar::VERTICAL, 0, 0);
+
+ tbl->add_widget_opts(ln, 0, 0, 1, 2, vs_table::EXPAND | vs_table::FILL | vs_table::SHRINK, vs_table::SHRINK);
+ tbl->add_widget_opts(pager, 1, 0, 1, 1, vs_table::SHRINK, vs_table::EXPAND | vs_table::FILL);
+ tbl->add_widget_opts(scrl, 1, 1, 1, 1, vs_table::ALIGN_LEFT, vs_table::EXPAND | vs_table::FILL);
+
+ ln->entered.connect(sigc::bind(sigc::ptr_fun(&do_load_file), ln.weak_ref()));
+ ln->entered.connect(sigc::mem_fun(*pager.unsafe_get_ref(), (void (vs_file_pager::*)(const std::wstring &)) &vs_file_pager::load_file));
+ pager->line_changed.connect(sigc::mem_fun(*scrl.unsafe_get_ref(), &vs_scrollbar::set_slider));
+ pager->do_line_signal();
+ scrl->scrollbar_interaction.connect(sigc::mem_fun(*pager.unsafe_get_ref(), &vs_pager::scroll_page));
+
+ return tbl;
+}
+
+void update_menu_status(const vs_menu_item *item, vs_label &label)
+{
+ if(item)
+ {
+ label.show();
+ label.set_text(item->get_description(),
+ get_style("Status"));
+ }
+ else
+ label.hide();
+}
+
+void do_editline_history(std::wstring s, vs_editline &l)
+{
+ l.add_to_history(s);
+ l.reset_history();
+ l.set_text("");
+}
+
+fragment *dickens_fragment()
+{
+
+ vector<fragment*> v;
+
+ v.push_back(indentbox(2, 2, flowbox(fragf("%s %F %s", "A", style_fragment(text_fragment("Christmas"), get_style("Error")), "Carol"))));
+
+ v.push_back(newline_fragment());
+
+ v.push_back(flowbox(fragf("%BMarley was %s%b: to begin with. There is %F whatever about that. The register of his burial was signed by the clergyman, the clerk, the undertaker, and the chief mourner. Scrooge signed it. And Scrooge's name was good upon 'Change, for anything he chose to put his hand to. Old Marley was as dead as a door-nail.",
+ "dead",
+ text_fragment("no doubt", style_attrs_on(A_BOLD)))));
+
+ v.push_back(newline_fragment());
+
+ // Test positional arguments.
+ v.push_back(fillbox(fragf("%2$F! I don't mean to say that I know, of my own knowledge, what there is particularly dead about a door-nail. I might have been inclined, myself, to regard a coffin-nail as the deadest piece of ironmongery in the trade. But the wisdom of our ancestors is in the simile; and my unhallowed hands shall not disturb it, or the Country's done for. You will therefore permit me to repeat, emphatically, that Marley was as %3$F as a %1$F.%n%n%n%nScrooge knew he was dead? Of course he did. How could it be otherwise? Scrooge and he were partners for I don't know how many years. Scrooge was his sole executor, his sole administrator, his sole assign, his sole residuary legatee, his sole friend, and sole mourner. And even Scrooge was not so dreadfully cut up by the sad event, but that he was an excellent man of business on the very day of the funeral, and solemnised it with an undoubted bargain.",
+ text_fragment("door-nail", style_attrs_on(A_BOLD)),
+ style_fragment(text_fragment("Mind"), style_attrs_on(A_BOLD)),
+ text_fragment("dead", style_attrs_on(A_BOLD)))));
+
+ v.push_back(newline_fragment());
+
+ v.push_back(hardwrapbox(fragf("The mention of Marley's funeral brings me back to the point I started from. There is no doubt that Marley was dead. This must be distinctly understood, or nothing wonderful can come of the story I am going to relate. If we were not perfectly convinced that Hamlet's Father died before the play began, there would be nothing more remarkable in his taking a stroll at night, in an easterly wind, upon his own ramparts, than there would be in any other middle-aged gentleman rashly turning out after dark in a breezy spot -- say Saint Paul's Churchyard for instance -- literally to astonish his son's weak mind.")));
+
+ v.push_back(newline_fragment());
+
+ vector<fragment_column_entry> column_entries;
+
+ column_entries.push_back(fragment_column_entry(true, 5, fragment_column_entry::top, flowbox(fragf("Scrooge never painted out Old Marley's name. There it stood, years afterwards, above the warehouse door: Scrooge and Marley. The firm was known as Scrooge and Marley. Sometimes people new to the business called Scrooge Scrooge, and sometimes Marley, but he answered to both names: it was all the same to him.%n%nOh! But he was a tight-fisted hand at the grind-stone, Scrooge! a squeezing, wrenching, grasping, scraping, clutching, covetous, old sinner! Hard and sharp as flint, from which no steel had ever struck out generous fire; secret, and self-contained, and solitary as an oyster. The cold within him froze his old features, nipped his pointed nose, shriveled his cheek, stiffened his gait; made his eyes red, his thin lips blue and spoke out shrewdly in his grating voice. A frosty rime was on his head, and on his eyebrows, and his wiry chin. He carried his own low temperature always about with him; he iced his office in the dogdays; and didn't thaw it one degree at Christmas."))));
+ column_entries.push_back(fragment_column_entry(false, 1, fragment_column_entry::top, NULL));
+ column_entries.push_back(fragment_column_entry(true, 1, fragment_column_entry::bottom, flowbox(fragf("Here we see further evidence of Scrooge's miserly behavior: to save himself a bit of money on paint and labour, he leaves an inaccurate (and somewhat spooky) sign above the door of his business."))));
+
+ v.push_back(fragment_columns(column_entries));
+
+ return sequence_fragment(v);
+}
+
+// test wrapping around utf8.
+fragment *chinese_fragment()
+{
+ vector<fragment *> v;
+
+ v.push_back(clipbox(fragf("Here is some Chinese text:%n%n%ls", L"\u6211\u7684\u6c23\u588a\u8239\u5145\u6eff\u4e86\u9c54\u9b5a")));
+
+ v.push_back(newline_fragment());
+
+ v.push_back(flowbox(fragf("Here is some Chinese text:%n%n%ls", L"\u6211\u7684\u6c23\u588a\u8239\u5145\u6eff\u4e86\u9c54\u9b5a")));
+
+ v.push_back(newline_fragment());
+
+ v.push_back(hardwrapbox(fragf("Here is some Chinese text:%n%n%ls", L"\u6211\u7684\u6c23\u588a\u8239\u5145\u6eff\u4e86\u9c54\u9b5a")));
+
+ v.push_back(newline_fragment());
+
+ v.push_back(fillbox(fragf("Here is some Chinese text:%n%n%ls", L"\u6211\u7684\u6c23\u588a\u8239\u5145\u6eff\u4e86\u9c54\u9b5a")));
+
+ return sequence_fragment(v);
+}
+
+// This isn't a very comprehensive test yet.
+vs_widget_ref make_layout_test(fragment *f)
+{
+ vs_table_ref t=vs_table::create();
+
+
+ vs_text_layout_ref l=vs_text_layout::create(f);
+ vs_scrollbar_ref s=vs_scrollbar::create(vs_scrollbar::VERTICAL);
+ l->location_changed.connect(sigc::mem_fun(*s.unsafe_get_ref(), &vs_scrollbar::set_slider));
+ s->scrollbar_interaction.connect(sigc::mem_fun(*l.unsafe_get_ref(), &vs_text_layout::scroll));
+
+ t->add_widget_opts(l, 0, 0, 1, 1, vs_table::EXPAND | vs_table::SHRINK, vs_table::EXPAND);
+ t->add_widget_opts(s, 0, 1, 1, 1, 0, vs_table::EXPAND | vs_table::FILL);
+
+ return t;
+}
+
+int main(int argc, char *argv[])
+{
+ setlocale(LC_ALL, "");
+
+ vscreen_init();
+
+ global_bindings.set("CycleScreen", key(KEY_F(6), true));
+ global_bindings.set("CycleScreenBack", key(KEY_F(7), true));
+ global_bindings.set("ToggleCellVisible", key(KEY_F(5), true));
+ global_bindings.set("ToggleHier", key(L'h', false));
+
+ vs_menubar_ref menubar=vs_menubar::create();
+
+ menubar->connect_key_post("Quit", &global_bindings, sigc::ptr_fun(&vscreen_exitmain));
+
+ vs_label_ref menu_display=vs_label::create("", get_style("Status"));
+
+ vs_menu_ref menu(vs_menu::create(0, 0, 0, test_file_menu));
+ menubar->append_item(L"File", menu);
+ menu->item_highlighted.connect(sigc::bind(sigc::ptr_fun(&update_menu_status),
+ menu_display.weak_ref()));
+
+ menu=vs_menu::create(0, 0, 0, test_test_menu);
+ menubar->append_item(L"Test", menu);
+ menu->item_highlighted.connect(sigc::bind(sigc::ptr_fun(&update_menu_status),
+ menu_display.weak_ref()));
+
+ menu=vs_menu::create(0, 0, 0, test_help_menu);
+ menubar->append_item(L"Help", menu);
+ menu->item_highlighted.connect(sigc::bind(sigc::ptr_fun(&update_menu_status),
+ menu_display.weak_ref()));
+
+ vs_minibuf_win_ref toplevel=vs_minibuf_win::create();
+
+ toplevel->add_widget(menu_display);
+
+ menubar->set_subwidget(toplevel);
+
+ vs_multiplex_ref switcher=vs_multiplex::create(true);
+ switcher->connect_key("CycleScreen",
+ &global_bindings,
+ sigc::mem_fun(*switcher.unsafe_get_ref(), &vs_multiplex::cycle_forward));
+ switcher->connect_key("CycleScreenBack",
+ &global_bindings,
+ sigc::mem_fun(*switcher.unsafe_get_ref(), &vs_multiplex::cycle_backward));
+
+ switcher->add_visible_widget(pager_test(), true);
+ switcher->add_visible_widget(button_mania(), true);
+
+ switcher->add_visible_widget(vs_center::create(vs_size_box::create(size(20, 8), vs_frame::create(vs_center::create(vs_label::create(flowbox(fragf("This is another screen.%nNotice that this label is properly word-wrapped."))))))), true);
+ switcher->add_visible_widget(vs_label::create("This is one screen."), true);
+ switcher->add_visible_widget(vs_dialog_ok(transcode("Press any key to hide this widget")), true);
+ switcher->add_visible_widget(make_test_treewidget(), true);
+
+ vs_table_ref ttable=vs_table::create();
+ ttable->add_widget(vs_frame::create(vs_label::create("Press a key:")));
+ ttable->add_widget(vs_frame::create(new test_keyname));
+ ttable->show_all();
+ switcher->add_widget(ttable);
+
+ vs_table_ref table=vs_table::create();
+ table->set_colsep(3);
+ vs_widget_ref w=vs_frame::create(vs_center::create(vs_label::create("Pane 1")));
+ table->add_widget(w, 0, 0, 2, 1, false);
+ w=vs_frame::create(vs_center::create(vs_label::create("Pane 2")));
+ table->add_widget_opts(w, 0, 1, 1, 1, vs_table::EXPAND | vs_table::FILL, 0);
+
+ vs_editline::history_list h;
+ vs_editline_ref l=vs_editline::create(20, "Input: ", "Pane 3", &h);
+ l->entered.connect(sigc::bind(sigc::ptr_fun(&do_editline_history),
+ l.weak_ref()));
+
+ w=vs_frame::create(vs_center::create(l));
+ table->add_widget(w, 1, 1);
+ w=vs_frame::create(vs_center::create(vs_label::create("Pane 4 - a pane with a very long label in it\nwhich should crowd out the other table cells")));
+ table->connect_key("ToggleCellVisible",
+ &global_bindings,
+ sigc::mem_fun(*w.unsafe_get_ref(), &vscreen_widget::toggle_visible));
+ table->add_widget(w, 0, 2, 2);
+ table->show_all();
+ switcher->add_widget(table);
+
+ switcher->add_visible_widget(make_layout_test(chinese_fragment()), true);
+ switcher->add_visible_widget(make_layout_test(dickens_fragment()), true);
+
+ stacker->add_visible_widget(switcher, true);
+
+ stacker->add_visible_widget(vs_dialog_ok(transcode("Vscreen test program - press any key to begin")), true);
+
+ toplevel->set_main_widget(stacker);
+ stacker->show();
+ toplevel->set_header("testvscreen");
+ toplevel->set_header("This program tests the vscreen text UI library");
+
+ vscreen_settoplevel(menubar);
+
+ vscreen_mainloop();
+
+ vscreen_shutdown();
+
+ return 0;
+}
diff --git a/src/vscreen/transcode.cc b/src/vscreen/transcode.cc
new file mode 100644
index 00000000..e92b7f09
--- /dev/null
+++ b/src/vscreen/transcode.cc
@@ -0,0 +1,354 @@
+// transcode.cc
+//
+// Copyright (C) 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "transcode.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <iconv.h>
+
+using namespace std;
+
+static std::string default_wtomb_err(int error,
+ const std::string &partial,
+ const std::wstring &input)
+{
+ return partial;
+}
+
+static std::wstring default_mbtow_err(int error,
+ const std::wstring &partial,
+ const std::string &input)
+{
+ return partial;
+}
+
+std::string (*transcode_wtomb_err)(int error,
+ const std::string &partial,
+ const std::wstring &input)=default_wtomb_err;
+
+std::wstring (*transcode_mbtow_err)(int error,
+ const std::wstring &partial,
+ const std::string &input)=default_mbtow_err;
+
+
+/** Does the dirty iconv work, given that an iconv session has been
+ * opened and we want to fully decode the "inbuf". If the outbuf
+ * isn't large enough, it will be repeatedly doubled.
+ *
+ * \param state the iconv state to be used
+ *
+ * \param outbuf the buffer to which the string should be decoded.
+ * If \b null, a new buffer will be allocated.
+ *
+ * \param outbufsize the initial size of "outbuf", updated if
+ * outbuf is increased. If this value is 0, an arbitrary small
+ * starting value will be used.
+ *
+ * \param inbuf the string to be decoded.
+ *
+ * \param inbufsize the size of inbuf.
+ *
+ * \param decoded location to write the number of bytes in the decoded string.
+ *
+ * \param errf a callback to handle encoding errors: it is passed the
+ * current decoding state, and returns 'true' to continue and 'false'
+ * to abort (after possibly adjusting said state).
+ */
+static bool transcode_buffer(iconv_t &state,
+ char *&outbuf,
+ size_t &outbufsize,
+ const char *inbuf,
+ size_t inbufsize,
+ size_t &decoded,
+ const char *outencoding)
+{
+ bool rval=true;
+
+ if(outbufsize == 0 || outbuf == NULL)
+ {
+ free(outbuf);
+ // arbitrary initial starting size; expected to be large enough
+ // for most "small" strings.
+ if(outbufsize == 0)
+ outbufsize = 1024;
+ outbuf = (char *) malloc(outbufsize);
+ if(outbuf == NULL)
+ {
+ errno = ENOMEM;
+ decoded=0;
+ return false;
+ }
+ }
+
+ char *outbufcur = outbuf;
+
+ size_t outremaining = outbufsize;
+ size_t inremaining = inbufsize;
+
+ while(inremaining>0)
+ {
+ if(iconv(state,
+ const_cast<char **>(&inbuf), &inremaining,
+ &outbufcur, &outremaining) == ((size_t)-1))
+ {
+ // Some error conditions can be corrected. There are three
+ // reasons iconv can terminate abnormally:
+ //
+ // (1) an invalid multibyte sequence occured. We do not
+ // attempt to recover in this case.
+ //
+ // (2) an incomplete multibyte sequence occured; as the
+ // input string is all the input we have, this reduces
+ // to case (1).
+ //
+ // (3) no room left in the output buffer. We respond by
+ // doubling the output buffer's size, or failing if
+ // it's doubled as far as it can go.
+
+ if(errno != E2BIG)
+ {
+ rval=false;
+ // Reset the output to initial state.
+ size_t result = iconv(state, NULL, NULL, &outbufcur, &outremaining);
+
+ while(result == (size_t)(-1))
+ {
+ assert(errno == E2BIG);
+
+ size_t idx = outbufcur-outbuf;
+ outremaining += outbufsize;
+ outbufsize *= 2;
+ outbuf = (char *) realloc(outbuf,outbufsize);
+ outbufcur = outbuf+idx;
+
+ result = iconv(state, NULL, NULL, &outbufcur, &outremaining);
+ }
+
+ // Open a *new* iconv to spit a '?' onto the decoded
+ // output.
+ iconv_t state2 = iconv_open(outencoding, "ASCII");
+
+ if(state2 == (iconv_t)(-1))
+ {
+ decoded = outbufsize-outremaining;
+ return false;
+ }
+
+ const char *errbuf = "?";
+ size_t errbufsize = strlen(errbuf);
+
+ result = iconv(state2, const_cast<char **>(&errbuf),
+ &errbufsize, &outbufcur, &outremaining);
+
+
+ while(result == (size_t)(-1))
+ {
+ if(errno != E2BIG)
+ {
+ decoded = outbufsize-outremaining;
+ iconv_close(state2);
+ return false;
+ }
+
+ size_t idx = outbufcur-outbuf;
+ outremaining += outbufsize;
+ outbufsize *= 2;
+ outbuf = (char *) realloc(outbuf, outbufsize);
+ outbufcur = outbuf+idx;
+
+ result = iconv(state2, const_cast<char **>(&errbuf),
+ &errbufsize, &outbufcur, &outremaining);
+ }
+
+ assert(errbufsize == 0);
+
+ // Return again to initial shift state
+ result = iconv(state2, NULL, NULL, &outbufcur, &outremaining);
+ while(result == (size_t)(-1))
+ {
+ assert(errno == E2BIG);
+
+ size_t idx = outbufcur-outbuf;
+ outremaining += outbufsize;
+ outbufsize *= 2;
+ outbuf = (char *) realloc(outbuf, outbufsize);
+ outbufcur = outbuf+idx;
+
+ result = iconv(state2, NULL, NULL, &outbufcur, &outremaining);
+ }
+
+ iconv_close(state2);
+
+ // Ok, skip the bad input character.
+ ++inbuf;
+ --inremaining;
+ }
+ else
+ {
+ size_t idx = outbufcur-outbuf;
+ outremaining += outbufsize;
+ outbufsize *= 2;
+ outbuf = (char *) realloc(outbuf, outbufsize);
+ outbufcur = outbuf + idx;
+ }
+ }
+ else
+ // if this fails, my understanding of iconv is wrong: the
+ // iconv docs say that if it doesn't fail, then the whole
+ // input sequence was converted.
+ assert(inremaining == 0);
+ }
+
+ decoded=outbufsize-outremaining;
+
+ return rval;
+}
+
+bool transcode(const char *s,
+ wstring &out,
+ const char *encoding)
+{
+ if(encoding == NULL)
+ encoding = nl_langinfo(CODESET);
+
+ iconv_t converter=iconv_open("WCHAR_T", encoding);
+
+ if(converter == ((iconv_t)-1))
+ return false;
+
+ char *outbuf = NULL;
+ size_t outbufsize = 0;
+ size_t result_size = 0;
+
+ bool rval = transcode_buffer(converter, outbuf, outbufsize,
+ s, strlen(s), result_size, "WCHAR_T");
+
+ if(outbuf != NULL)
+ {
+ out = wstring((wchar_t *) outbuf, result_size/sizeof(wchar_t));
+ free(outbuf);
+ }
+
+ if(iconv_close(converter) == -1)
+ rval = false;
+
+ return rval;
+}
+
+std::wstring transcode(const std::string &s,
+ const char *encoding,
+ std::wstring (*errf)(int error,
+ const std::wstring &partial,
+ const std::string &input))
+{
+ std::wstring rval;
+ if(transcode(s, rval, encoding))
+ return rval;
+ else
+ {
+ if(errf == NULL)
+ errf=transcode_mbtow_err;
+ return errf(errno, rval, s);
+ }
+}
+
+std::wstring transcode(const char *s,
+ const char *encoding,
+ std::wstring (*errf)(int error,
+ const std::wstring &partial,
+ const std::string &input))
+{
+ std::wstring rval;
+ if(transcode(s, rval, encoding))
+ return rval;
+ else
+ {
+ if(errf == NULL)
+ errf=transcode_mbtow_err;
+ return errf(errno, rval, s);
+ }
+}
+
+bool transcode(const wchar_t *s,
+ string &out,
+ const char *encoding)
+{
+ if(encoding == NULL)
+ encoding = nl_langinfo(CODESET);
+
+ iconv_t converter = iconv_open(encoding, "WCHAR_T");
+
+ if(converter == ((iconv_t)-1))
+ return false;
+
+ char *outbuf = NULL;
+ size_t outbufsize = 0;
+ size_t result_size = 0;
+
+ bool rval = transcode_buffer(converter, outbuf, outbufsize,
+ (char *) s,
+ wcslen(s)*sizeof(wchar_t),
+ result_size, encoding);
+
+ if(outbuf != NULL)
+ {
+ out = string(outbuf, result_size);
+ free(outbuf);
+ }
+
+ if(iconv_close(converter) == -1)
+ rval = false;
+
+ return rval;
+}
+
+std::string transcode(const std::wstring &s,
+ const char *encoding,
+ std::string (*errf)(int error,
+ const std::string &partial,
+ const std::wstring &input))
+{
+ std::string rval;
+ if(transcode(s, rval, encoding))
+ return rval;
+ else
+ {
+ if(errf == NULL)
+ errf=transcode_wtomb_err;
+ return errf(errno, rval, s);
+ }
+}
+
+std::string transcode(const wchar_t *s,
+ const char *encoding,
+ std::string (*errf)(int error,
+ const std::string &partial,
+ const std::wstring &input))
+{
+ std::string rval;
+ if(transcode(s, rval, encoding))
+ return rval;
+ else
+ {
+ if(errf == NULL)
+ errf=transcode_wtomb_err;
+ return errf(errno, rval, s);
+ }
+}
diff --git a/src/vscreen/transcode.h b/src/vscreen/transcode.h
new file mode 100644
index 00000000..adf71c6f
--- /dev/null
+++ b/src/vscreen/transcode.h
@@ -0,0 +1,163 @@
+// transcode.h -*-c++-*-
+//
+// Copyright (C) 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#ifndef TRANSCODE_H
+#define TRANSCODE_H
+
+#include <string>
+
+/** Convenience function to convert a multibyte encoding to wide
+ * characters. This is a wrapper around iconv.
+ *
+ * \param s the string to decode
+ * \param out the location to write the transcoded string
+ * \param encoding the encoding of s; if \b null or unspecified,
+ * the value of LC_CTYPE is used.
+ *
+ * \return \b true if the entire string was successfully transcoded;
+ * if transcoding failed, returns \b false and sets errno.
+ */
+bool transcode(const char *s,
+ std::wstring &out,
+ const char *encoding=NULL);
+
+inline bool transcode(const std::string &s,
+ std::wstring &out,
+ const char *encoding=NULL)
+{
+ return transcode(s.c_str(), out, encoding);
+}
+
+/** The error handler for converting multibyte strings to wide
+ * strings: it is passed a partially-decoded string and the error
+ * code, and its return value becomes the return value of the
+ * function. The default handler just returns "partial".
+ */
+extern std::wstring (*transcode_mbtow_err)(int error,
+ const std::wstring &partial,
+ const std::string &input);
+
+/** Convenience function to convert a multibyte encoding to wide
+ * characters, where the caller doesn't need to directly handle
+ * errors. This is a wrapper around iconv.
+ *
+ * \param s the string to decode
+ *
+ * \param encoding the encoding of s; if \b null or unspecified, the
+ * value of LC_CTYPE is used.
+ *
+ * \param errf the error handler, or \b null to use the default
+ * handler (transcode_mbtow_err).
+ */
+std::wstring transcode(const std::string &s,
+ const char *encoding=NULL,
+ std::wstring (*errf)(int error,
+ const std::wstring &partial,
+ const std::string &input)=NULL);
+
+/** Convenience function to convert a multibyte encoding to wide
+ * characters, where the caller doesn't need to directly handle
+ * errors. This is a wrapper around iconv.
+ *
+ * \param s the string to decode
+ *
+ * \param encoding the encoding of s; if \b null or unspecified, the
+ * value of LC_CTYPE is used.
+ *
+ * \param errf the error handler, or \b null to use the default
+ * handler (transcode_mbtow_err).
+ */
+std::wstring transcode(const char *s,
+ const char *encoding=NULL,
+ std::wstring (*errf)(int error,
+ const std::wstring &partial,
+ const std::string &input)=NULL);
+
+// Note: would it be saner to express errors via exceptions?
+
+/** Convenience function to convert the native wide character encoding
+ * to a multibyte encoding. This is a wrapper around iconv.
+ *
+ * \param s the wide string to encode
+ * \param out the location to write the multibyte string
+ * \param encoding the encoding of out; if \b null or unspecified,
+ * the value of LC_CTYPE is used.
+ *
+ * \return \b true if the entire string was successfully transcoded;
+ * if transcoding failed, returns \b false and sets errno.
+ */
+bool transcode(const wchar_t *s,
+ std::string &out,
+ const char *encoding=NULL);
+
+
+inline bool transcode(const std::wstring &s,
+ std::string &out,
+ const char *encoding=NULL)
+{
+ return transcode(s.c_str(), out, encoding);
+}
+
+
+/** The error handler for converting multibyte strings to wide
+ * strings: it is passed a partially-decoded string and the error
+ * code, and its return value becomes the return value of the
+ * function. The default handler just returns "partial".
+ */
+extern std::string (*transcode_wtomb_err)(int error,
+ const std::string &partial,
+ const std::wstring &input);
+
+/** Convenience function to convert a multibyte encoding to wide
+ * characters, where the caller doesn't need to directly handle
+ * errors. This is a wrapper around iconv.
+ *
+ * \param s the string to decode
+ *
+ * \param encoding the encoding of s; if \b null or unspecified, the
+ * value of LC_CTYPE is used.
+ *
+ * \param errf the error handler, or \b null to use the default
+ * handler (transcode_mbtow_err).
+ */
+std::string transcode(const std::wstring &s,
+ const char *encoding=NULL,
+ std::string (*errf)(int error,
+ const std::string &partial,
+ const std::wstring &input)=NULL);
+
+/** Convenience function to convert a multibyte encoding to wide
+ * characters, where the caller doesn't need to directly handle
+ * errors. This is a wrapper around iconv.
+ *
+ * \param s the string to decode
+ *
+ * \param encoding the encoding of s; if \b null or unspecified, the
+ * value of LC_CTYPE is used.
+ *
+ * \param errf the error handler, or \b null to use the default
+ * handler (transcode_mbtow_err).
+ */
+std::string transcode(const wchar_t *s,
+ const char *encoding=NULL,
+ std::string (*errf)(int error,
+ const std::string &partial,
+ const std::wstring &input)=NULL);
+
+#endif // TRANSCODE_H
diff --git a/src/vscreen/vs_bin.cc b/src/vscreen/vs_bin.cc
new file mode 100644
index 00000000..0bc3777a
--- /dev/null
+++ b/src/vscreen/vs_bin.cc
@@ -0,0 +1,149 @@
+// vs_bin.cc
+
+#include "vs_bin.h"
+
+#include "vscreen.h"
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+vs_bin::vs_bin()
+ :vs_passthrough(), subwidget(NULL)
+{
+}
+
+vs_bin::~vs_bin()
+{
+ if(subwidget.valid())
+ set_subwidget(NULL);
+}
+
+vs_widget_ref vs_bin::get_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = subwidget;
+
+ if(w.valid() && w->get_visible())
+ return w;
+ else
+ return NULL;
+}
+
+void vs_bin::set_subwidget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ if(subwidget.valid())
+ {
+ subwidget->set_owner(NULL);
+ subwidget->unfocussed();
+ subwidget=NULL;
+ show_conn.disconnect();
+ hide_conn.disconnect();
+ }
+
+ subwidget = w;
+
+ if(w.valid())
+ {
+ show_conn = w->shown_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_bin::show_widget_bare), w.weak_ref()));
+ hide_conn = w->hidden_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_bin::hide_widget_bare), w.weak_ref()));
+ w->set_owner(this);
+ if(get_isfocussed())
+ w->focussed();
+ }
+
+ vscreen_queuelayout();
+}
+
+void vs_bin::destroy()
+{
+ vs_widget_ref tmpref(this);
+
+ if(subwidget.valid())
+ subwidget->destroy();
+ assert(!subwidget.valid());
+
+ vs_container::destroy();
+}
+
+void vs_bin::add_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ assert(!subwidget.valid());
+ assert(w.valid());
+
+ set_subwidget(w);
+
+ // I assume that we're hidden right now.
+ if(w->get_visible())
+ show();
+
+ if(get_isfocussed())
+ w->focussed();
+}
+
+void vs_bin::rem_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ assert(w == subwidget);
+ set_subwidget(NULL);
+
+ if(get_visible())
+ hide();
+
+ if(get_isfocussed())
+ w->unfocussed();
+}
+
+void vs_bin::show_all()
+{
+ vs_widget_ref tmpref(this);
+
+ if(subwidget.valid())
+ subwidget->show_all();
+
+ show();
+}
+
+void vs_bin::show_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ assert(w==subwidget);
+
+ show();
+}
+
+void vs_bin::show_widget_bare(vscreen_widget &w)
+{
+ vs_widget_ref tmpref(this);
+
+ show_widget(vs_widget_ref(&w));
+}
+
+void vs_bin::hide_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ assert(w==subwidget);
+ hide();
+}
+
+void vs_bin::hide_widget_bare(vscreen_widget &w)
+{
+ vs_widget_ref tmpref(this);
+
+ hide_widget(vs_widget_ref(&w));
+}
+
+void vs_bin::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ if(subwidget.valid() && subwidget->get_visible())
+ subwidget->display(st);
+}
diff --git a/src/vscreen/vs_bin.h b/src/vscreen/vs_bin.h
new file mode 100644
index 00000000..3d107f04
--- /dev/null
+++ b/src/vscreen/vs_bin.h
@@ -0,0 +1,54 @@
+// vs_bin.h -*-c++-*-
+//
+// Generic stuff for a container that can only handle one child.
+
+#ifndef VS_BIN_H
+#define VS_BIN_H
+
+#include "vs_passthrough.h"
+
+#include <sigc++/connection.h>
+
+class vs_bin:public vs_passthrough
+{
+ vs_widget_ref subwidget;
+
+ // These are unfortunate necessities; when a widget is /removed/
+ // (but not destroyed), it is necessary to delete the connections to
+ // it. :-(
+ sigc::connection show_conn, hide_conn;
+
+ // right now these just show or hide the bin itself
+ void show_widget(const vs_widget_ref &w);
+ void hide_widget(const vs_widget_ref &w);
+
+ void show_widget_bare(vscreen_widget &w);
+ void hide_widget_bare(vscreen_widget &w);
+
+protected:
+ vs_bin();
+
+public:
+ virtual ~vs_bin();
+
+ void set_subwidget(const ref_ptr<vscreen_widget> &w);
+ void set_subwidget(vscreen_widget &w)
+ {
+ set_subwidget(ref_ptr<vscreen_widget>(&w));
+ }
+
+ vs_widget_ref get_subwidget() {return subwidget;}
+
+ void destroy();
+
+ virtual void show_all();
+
+ virtual void add_widget(const vs_widget_ref &w);
+ virtual void rem_widget(const vs_widget_ref &w);
+
+ vs_widget_ref get_focus();
+
+ void paint(const style &st);
+};
+
+#endif
diff --git a/src/vscreen/vs_button.cc b/src/vscreen/vs_button.cc
new file mode 100644
index 00000000..76b86769
--- /dev/null
+++ b/src/vscreen/vs_button.cc
@@ -0,0 +1,150 @@
+// vs_button.cc
+
+#include "vs_button.h"
+
+#include "fragment.h"
+#include "fragment_cache.h"
+#include "vscreen.h"
+
+#include "config/keybindings.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+vs_button::vs_button(fragment *_label)
+ :label(new fragment_cache(_label))
+{
+ focussed.connect(sigc::mem_fun(*this, &vs_button::accept_focus));
+ unfocussed.connect(sigc::mem_fun(*this, &vs_button::lose_focus));
+}
+
+vs_button::vs_button(const std::wstring &_label)
+ :label(new fragment_cache(text_fragment(_label)))
+{
+ focussed.connect(sigc::mem_fun(*this, &vs_button::accept_focus));
+ unfocussed.connect(sigc::mem_fun(*this, &vs_button::lose_focus));
+}
+
+vs_button::vs_button(const std::string &_label)
+ :label(new fragment_cache(text_fragment(_label)))
+{
+ focussed.connect(sigc::mem_fun(*this, &vs_button::accept_focus));
+ unfocussed.connect(sigc::mem_fun(*this, &vs_button::lose_focus));
+}
+
+vs_button::~vs_button()
+{
+ delete label;
+}
+
+bool vs_button::focus_me()
+{
+ return true;
+}
+
+bool vs_button::get_cursorvisible()
+{
+ return get_isfocussed();
+}
+
+point vs_button::get_cursorloc()
+{
+ return point(0,0);
+}
+
+void vs_button::accept_focus()
+{
+ vscreen_update();
+}
+
+void vs_button::lose_focus()
+{
+ vscreen_update();
+}
+
+void vs_button::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ size_t labelw=getmaxx()>=4?getmaxx()-4:0;
+
+ const style my_style=
+ get_isfocussed()?st+style_attrs_flip(A_REVERSE):st;
+
+ apply_style(my_style);
+
+ fragment_contents lines=label->layout(labelw, labelw, my_style);
+
+ // TODO: create a "bracebox" fragment that places left&right braces
+ // automatically.
+ for(size_t i=0; i<lines.size(); ++i)
+ {
+ move(i, 0);
+
+ if(lines.size() == 1)
+ add_wch(L'[');
+ else if(i==0)
+ add_wch(WACS_ULCORNER);
+ else if(i+1==lines.size())
+ add_wch(WACS_LLCORNER);
+ else
+ add_wch(WACS_VLINE);
+
+ add_wch(L' ');
+
+ const fragment_line &l=lines[i];
+ addstr(l);
+ int w=2+l.width();
+
+ while(w+1<getmaxx())
+ {
+ ++w;
+ add_wch(L' ');
+ }
+
+ if(lines.size() == 1)
+ add_wch(L']');
+ else if(i==0)
+ add_wch(WACS_URCORNER);
+ else if(i+1==lines.size())
+ add_wch(WACS_LRCORNER);
+ else
+ add_wch(WACS_VLINE);
+ }
+}
+
+void vs_button::dispatch_mouse(short id, int x, int y, int z, mmask_t bmask)
+{
+ vs_widget_ref tmpref(this);
+
+ if(bmask & (BUTTON1_CLICKED | BUTTON2_CLICKED |
+ BUTTON3_CLICKED | BUTTON4_CLICKED |
+ BUTTON1_RELEASED | BUTTON2_RELEASED |
+ BUTTON3_RELEASED | BUTTON4_RELEASED))
+ pressed();
+}
+
+bool vs_button::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ if(global_bindings.key_matches(k, "PushButton") ||
+ global_bindings.key_matches(k, "Confirm"))
+ {
+ pressed();
+ return true;
+ }
+ else
+ return vscreen_widget::handle_key(k);
+}
+
+int vs_button::width_request()
+{
+ return label->max_width(0, 0)+4;
+}
+
+int vs_button::height_request(int width)
+{
+ size_t label_width=(width>=4)?width-4:0;
+
+ return label->layout(label_width, label_width, style()).size();
+}
diff --git a/src/vscreen/vs_button.h b/src/vscreen/vs_button.h
new file mode 100644
index 00000000..0713673f
--- /dev/null
+++ b/src/vscreen/vs_button.h
@@ -0,0 +1,95 @@
+// vs_button.h -*-c++-*-
+//
+// A button is just a widget which accepts keyboard focus and can be
+// "pressed". I'm going to make a stab at sharing code between
+// normal buttons, radio buttons, and checkbuttons..this may not be
+// worth it..
+
+#ifndef VS_BUTTON_H
+#define VS_BUTTON_H
+
+#include "vscreen_widget.h"
+
+#include <string>
+
+class fragment;
+class fragment_cache;
+
+/** This class represents a push-button. */
+class vs_button:public vscreen_widget
+{
+ fragment_cache *label;
+
+ void accept_focus();
+ void lose_focus();
+
+protected:
+ bool handle_key(const key &k);
+ fragment_cache *get_label() const {return label;}
+
+ /** Instantiate a vs_button.
+ *
+ * \param _label the new label of this button; it will be placed
+ * inside a simple text_fragment.
+ */
+ vs_button(const std::wstring &_label);
+ vs_button(fragment *_label);
+ vs_button(const std::string &_label);
+public:
+
+ ~vs_button();
+
+ static ref_ptr<vs_button>
+ create(const std::wstring &label)
+ {
+ ref_ptr<vs_button> rval(new vs_button(label));
+ // Remove the initial construction reference.
+ rval->decref();
+ return rval;
+ }
+
+ /** Instantiate a vs_button.
+ *
+ * \param _label the new label of this button; the button is
+ * responsible for deleting it.
+ */
+ static ref_ptr<vs_button> create(fragment *label)
+ {
+ ref_ptr<vs_button> rval(new vs_button(label));
+ rval->decref();
+ return rval;
+ }
+
+ /** Instantiate a vs_button.
+ *
+ * \param _label the new label of this button; it will be placed
+ * inside a simple text_fragment.
+ */
+ static ref_ptr<vs_button> create(const std::string &label)
+ {
+ ref_ptr<vs_button> rval(new vs_button(label));
+ rval->decref();
+ return rval;
+ }
+
+ void paint(const style &st);
+
+ bool get_cursorvisible();
+ point get_cursorloc();
+ bool focus_me();
+
+ int width_request();
+ int height_request(int width);
+ void dispatch_mouse(short id, int x, int y, int z, mmask_t bmask);
+
+ void set_label(const fragment *_label);
+
+ // Signals:
+
+ // The button has been "pressed" (activated)
+ sigc::signal0<void> pressed;
+};
+
+typedef ref_ptr<vs_button> vs_button_ref;
+
+#endif
diff --git a/src/vscreen/vs_center.cc b/src/vscreen/vs_center.cc
new file mode 100644
index 00000000..8917650d
--- /dev/null
+++ b/src/vscreen/vs_center.cc
@@ -0,0 +1,61 @@
+// vs_center.cc
+
+#include "vs_center.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+vs_center::vs_center(const vs_widget_ref &w)
+{
+ set_subwidget(w);
+ set_opaque(false);
+
+ do_layout.connect(sigc::mem_fun(*this, &vs_center::layout_me));
+}
+
+int vs_center::width_request()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref subwidget = get_subwidget();
+
+ if(subwidget.valid() && subwidget->get_visible())
+ return subwidget->width_request();
+ else
+ return 0;
+}
+
+int vs_center::height_request(int width)
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref subwidget = get_subwidget();
+
+ if(subwidget.valid() && subwidget->get_visible())
+ return subwidget->height_request(width);
+ else
+ return 0;
+}
+
+void vs_center::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref child=get_subwidget();
+
+ if(child.valid())
+ {
+ if(child->get_visible())
+ {
+ int child_w=child->width_request();
+ if(child_w>getmaxx())
+ child_w=getmaxx();
+
+ int child_h=child->height_request(child_w);
+ if(child_h>getmaxy())
+ child_h=getmaxy();
+ child->alloc_size((getmaxx()-child_w)/2, (getmaxy()-child_h)/2, child_w, child_h);
+ }
+ else
+ child->alloc_size(0, 0, 0, 0);
+ }
+}
diff --git a/src/vscreen/vs_center.h b/src/vscreen/vs_center.h
new file mode 100644
index 00000000..b5e508c1
--- /dev/null
+++ b/src/vscreen/vs_center.h
@@ -0,0 +1,31 @@
+// vs_center.h -*-c++-*-
+//
+// A simple container/layout widget which centers its child in itself.
+
+#ifndef VS_CENTER_H
+#define VS_CENTER_H
+
+#include "vs_bin.h"
+
+class vs_center:public vs_bin
+{
+ void layout_me();
+
+protected:
+ vs_center(const vs_widget_ref &w = NULL);
+
+public:
+ static ref_ptr<vs_center> create(const vs_widget_ref &w = NULL)
+ {
+ ref_ptr<vs_center> rval(new vs_center(w));
+ rval->decref();
+ return rval;
+ }
+
+ int width_request();
+ int height_request(int width);
+};
+
+typedef ref_ptr<vs_center> vs_center_ref;
+
+#endif
diff --git a/src/vscreen/vs_container.cc b/src/vscreen/vs_container.cc
new file mode 100644
index 00000000..62abfd19
--- /dev/null
+++ b/src/vscreen/vs_container.cc
@@ -0,0 +1,35 @@
+// vs_container.cc
+//
+// Copyright (C) 2000, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "vs_container.h"
+
+vs_container::~vs_container()
+{
+}
+
+void vs_container::add_visible_widget(const vs_widget_ref &w,
+ bool visible)
+{
+ vs_widget_ref tmpref(this);
+
+ add_widget(w);
+
+ if(visible)
+ w->show_all();
+}
diff --git a/src/vscreen/vs_container.h b/src/vscreen/vs_container.h
new file mode 100644
index 00000000..1cdd2573
--- /dev/null
+++ b/src/vscreen/vs_container.h
@@ -0,0 +1,63 @@
+// vs_container.h -*-c++-*-
+//
+//
+// Copyright (C) 2000, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// A generic interface for a vscreen_widget that can hold other
+// widgets.
+
+#ifndef VS_CONTAINER_H
+#define VS_CONTAINER_H
+
+#include "vscreen_widget.h"
+
+class vs_container:public vscreen_widget
+{
+public:
+ vs_container():vscreen_widget() {}
+ ~vs_container();
+
+ virtual void add_widget(const vs_widget_ref &)=0;
+ void add_visible_widget(const vs_widget_ref &, bool visible);
+ virtual void rem_widget(const vs_widget_ref &)=0;
+
+ // Variants of the above that take a bare reference; used for weak
+ // slot connections.
+ void add_widget_bare(vscreen_widget &w)
+ {
+ add_widget(vs_widget_ref(&w));
+ }
+
+ void add_visible_widget_bare(vscreen_widget &w, bool visible)
+ {
+ add_visible_widget(vs_widget_ref(&w), visible);
+ }
+
+ void rem_widget_bare(vscreen_widget &w)
+ {
+ rem_widget(vs_widget_ref(&w));
+ }
+
+ /** Return the currently "active" child of this container, or \b NULL. */
+ virtual vs_widget_ref get_active_widget() = 0;
+
+ /** Display this widget and all its subwidgets. */
+ virtual void show_all()=0;
+};
+
+#endif
diff --git a/src/vscreen/vs_editline.cc b/src/vscreen/vs_editline.cc
new file mode 100644
index 00000000..a49e169b
--- /dev/null
+++ b/src/vscreen/vs_editline.cc
@@ -0,0 +1,486 @@
+// vs_editline.cc
+//
+// Copyright 2000 Daniel Burrows
+
+#include "vs_editline.h"
+
+#include "config/colors.h"
+#include "config/keybindings.h"
+#include "transcode.h"
+#include "vscreen.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+using namespace std;
+
+keybindings *vs_editline::bindings=NULL;
+
+vs_editline::vs_editline(const string &_prompt, const string &_text,
+ history_list *_history)
+ :vscreen_widget(), curloc(_text.size()),
+ startloc(0), desired_size(-1), history(_history),
+ history_loc(0), using_history(false)
+{
+ // Just spew a partial/null string if errors happen for now.
+ transcode(_prompt.c_str(), prompt);
+ transcode(_text.c_str(), text);
+
+ set_bg_style(get_style("EditLine"));
+
+ // This ensures that the cursor is set to the right location when the
+ // widget is displayed or resized:
+ do_layout.connect(sigc::mem_fun(*this, &vs_editline::normalize_cursor));
+}
+
+vs_editline::vs_editline(const wstring &_prompt, const wstring &_text,
+ history_list *_history)
+ :vscreen_widget(), prompt(_prompt), text(_text), curloc(_text.size()),
+ startloc(0), desired_size(-1), history(_history),
+ history_loc(0), using_history(false)
+{
+ set_bg_style(get_style("EditLine"));
+
+ // This ensures that the cursor is set to the right location when the
+ // widget is displayed or resized:
+ do_layout.connect(sigc::mem_fun(*this, &vs_editline::normalize_cursor));
+}
+
+vs_editline::vs_editline(int maxlength, const string &_prompt,
+ const string &_text, history_list *_history)
+ :vscreen_widget(), curloc(0),
+ startloc(0), desired_size(maxlength), history(_history), history_loc(0),
+ using_history(false)
+{
+ // As above, ignore errors.
+ transcode(_prompt, prompt);
+ transcode(_text, text);
+
+ set_bg_style(get_style("EditLine"));
+ do_layout.connect(sigc::mem_fun(*this, &vs_editline::normalize_cursor));
+}
+
+vs_editline::vs_editline(int maxlength, const wstring &_prompt,
+ const wstring &_text, history_list *_history)
+ :vscreen_widget(), prompt(_prompt), text(_text), curloc(0),
+ startloc(0), desired_size(maxlength), history(_history), history_loc(0),
+ using_history(false)
+{
+ set_bg_style(get_style("EditLine"));
+ do_layout.connect(sigc::mem_fun(*this, &vs_editline::normalize_cursor));
+}
+
+wchar_t vs_editline::get_char(size_t loc)
+{
+ vs_widget_ref tmpref(this);
+
+ if(loc>=prompt.size())
+ return text[loc-prompt.size()];
+ else
+ return prompt[loc];
+}
+
+void vs_editline::normalize_cursor()
+{
+ vs_widget_ref tmpref(this);
+
+ if(get_width() <= 0)
+ return;
+
+ int w=get_width();
+
+ int promptwidth=wcswidth(prompt.c_str(), prompt.size());
+ int textwidth=wcswidth(text.c_str(), text.size());
+
+ int cursorx=0;
+ if(curloc+prompt.size()>startloc)
+ for(size_t i=startloc; i<curloc+prompt.size(); ++i)
+ cursorx+=wcwidth(get_char(i));
+ else
+ for(size_t i=curloc+prompt.size(); i<startloc; ++i)
+ cursorx-=wcwidth(get_char(i));
+
+ if(promptwidth+textwidth+1<w)
+ startloc=0;
+ else if(w>2)
+ {
+ // Need to move the screen start to this far behind the cursor
+ // loc.
+ int decamt=0;
+ bool needs_move=false;
+
+ if(cursorx>=w-2)
+ {
+ decamt=w-2;
+ needs_move=true;
+ }
+ else if(cursorx<2)
+ {
+ decamt=2;
+ needs_move=true;
+ }
+
+ if(needs_move) // equivalent to but more readable than decamt!=0
+ {
+ // Do it by moving back this many chars
+ size_t chars=0;
+
+ while(decamt>0 && chars<curloc+prompt.size())
+ {
+ ++chars;
+ decamt-=wcwidth(get_char(prompt.size()+curloc-chars));
+ }
+
+ if(decamt<0 && chars>1)
+ --chars;
+
+ startloc=curloc+prompt.size()-chars;
+ }
+ }
+ else
+ {
+ // if width=1, use a primitive approach (we're screwed anyway in
+ // this case)
+ if(cursorx>=w)
+ startloc=prompt.size()+curloc-w+1;
+
+ if(cursorx<0)
+ startloc=prompt.size()+curloc;
+ }
+
+ vscreen_updatecursor();
+}
+
+bool vs_editline::get_cursorvisible()
+{
+ return true;
+}
+
+point vs_editline::get_cursorloc()
+{
+ vs_widget_ref tmpref(this);
+
+ if(getmaxx()>0)
+ {
+ int x=0;
+
+ for(size_t loc=startloc; loc<curloc+prompt.size(); ++loc)
+ x+=wcwidth(get_char(loc));
+
+ return point(x, 0);
+ }
+ else
+ return point(0,0);
+}
+
+bool vs_editline::focus_me()
+{
+ return true;
+}
+
+bool vs_editline::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ if(bindings->key_matches(k, "DelBack"))
+ {
+ if(curloc>0)
+ {
+ text.erase(--curloc, 1);
+ normalize_cursor();
+ text_changed(wstring(text));
+ vscreen_update();
+ }
+ else
+ {
+ beep();
+ // refresh();
+ }
+ return true;
+ }
+ else if(bindings->key_matches(k, "DelForward"))
+ {
+ if(curloc<text.size())
+ {
+ text.erase(curloc, 1);
+ normalize_cursor();
+ text_changed(wstring(text));
+ vscreen_update();
+ }
+ else
+ {
+ beep();
+ // refresh();
+ }
+ return true;
+ }
+ else if(bindings->key_matches(k, "Confirm"))
+ {
+ // I create a new string here because otherwise modifications to
+ // "text" are seen by the widgets! (grr, sigc++)
+ entered(wstring(text));
+ return true;
+ }
+ else if(bindings->key_matches(k, "Left"))
+ {
+ if(curloc>0)
+ {
+ curloc--;
+ normalize_cursor();
+ vscreen_update();
+ }
+ else
+ {
+ beep();
+ // refresh();
+ }
+ return true;
+ }
+ else if(bindings->key_matches(k, "Right"))
+ {
+ if(curloc<text.size())
+ {
+ curloc++;
+ normalize_cursor();
+ vscreen_update();
+ }
+ else
+ {
+ beep();
+ // refresh();
+ }
+ return true;
+ }
+ else if(bindings->key_matches(k, "Begin"))
+ {
+ curloc=0;
+ startloc=0;
+ normalize_cursor();
+ vscreen_update();
+ return true;
+ }
+ else if(bindings->key_matches(k, "End"))
+ {
+ curloc=text.size();
+ normalize_cursor();
+ vscreen_update();
+ return true;
+ }
+ else if(bindings->key_matches(k, "DelEOL"))
+ {
+ text.erase(curloc);
+ normalize_cursor();
+ text_changed(wstring(text));
+ vscreen_update();
+ return true;
+ }
+ else if(bindings->key_matches(k, "DelBOL"))
+ {
+ text.erase(0, curloc);
+ curloc=0;
+ normalize_cursor();
+ text_changed(wstring(text));
+ vscreen_update();
+ return true;
+ }
+ else if(history && bindings->key_matches(k, "HistoryPrev"))
+ {
+ if(history->size()==0)
+ return true;
+
+ if(!using_history)
+ {
+ using_history=true;
+ history_loc=history->size()-1;
+ pre_history_text=text;
+ }
+ else if(history_loc>0)
+ --history_loc;
+ else
+ // Break out
+ return true;
+
+ text=(*history)[history_loc];
+ curloc=text.size();
+ startloc=0;
+ normalize_cursor();
+ text_changed(wstring(text));
+ vscreen_update();
+
+ return true;
+ }
+ else if(history && bindings->key_matches(k, "HistoryNext"))
+ {
+ if(history->size()==0 || !using_history)
+ return true;
+
+ if(history_loc>=history->size()-1)
+ {
+ using_history=false;
+ history_loc=0;
+ text=pre_history_text;
+ pre_history_text=L"";
+ curloc=text.size();
+ startloc=0;
+ normalize_cursor();
+ text_changed(wstring(text));
+ vscreen_update();
+
+ // FIXME: store the pre-history edit and restore that.
+ return true;
+ }
+
+ if(history_loc>=0)
+ {
+ ++history_loc;
+ text=(*history)[history_loc];
+ curloc=text.size();
+ startloc=0;
+ normalize_cursor();
+ text_changed(wstring(text));
+ vscreen_update();
+
+ return true;
+ }
+ else
+ return true;
+ }
+ else if(k.function_key)
+ return vscreen_widget::handle_key(k);
+ else if(k.ch=='\t') // HACK
+ return false;
+ else
+ {
+ text.insert(curloc++, 1, k.ch);
+ normalize_cursor();
+ text_changed(wstring(text));
+ vscreen_update();
+ return true;
+ }
+}
+
+void vs_editline::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ int width=getmaxx();
+
+ int used=0;
+ size_t chars=0;
+
+ while(used<width && startloc+chars<prompt.size()+text.size())
+ {
+ wchar_t ch=get_char(startloc+chars);
+ used+=wcwidth(ch);
+ ++chars;
+ }
+
+ if(used>width && chars>1)
+ --chars;
+
+ wstring todisp=prompt+text;
+ mvaddstr(0, 0, wstring(todisp, startloc, chars));
+}
+
+void vs_editline::dispatch_mouse(short id, int x, int y, int z, mmask_t bstate)
+{
+ vs_widget_ref tmpref(this);
+
+ size_t mouseloc=startloc; // The character at which the mouse press occured
+ while(mouseloc<prompt.size()+text.size() && x>0)
+ {
+ int curwidth=wcwidth(get_char(mouseloc));
+
+ if(curwidth>x)
+ break;
+ else
+ {
+ ++mouseloc;
+ x-=curwidth;
+ }
+ }
+
+ if(mouseloc>=prompt.size())
+ {
+ mouseloc-=prompt.size();
+
+ if(mouseloc<=text.size())
+ curloc=mouseloc;
+ else
+ curloc=text.size();
+ }
+ else
+ return; // Break out
+
+ vscreen_update();
+}
+
+void vs_editline::add_to_history(std::wstring s,
+ history_list *lst)
+{
+ assert(lst);
+
+ if(lst->empty() || lst->back()!=s)
+ lst->push_back(s);
+}
+
+void vs_editline::add_to_history(std::wstring s)
+{
+ vs_widget_ref tmpref(this);
+
+ if(history)
+ add_to_history(s, history);
+}
+
+void vs_editline::reset_history()
+{
+ vs_widget_ref tmpref(this);
+
+ pre_history_text=L"";
+ using_history=false;
+ history_loc=0;
+}
+
+void vs_editline::set_text(wstring _text)
+{
+ vs_widget_ref tmpref(this);
+
+ text=_text;
+ if(curloc>text.size())
+ curloc=text.size();
+ text_changed(wstring(text));
+ vscreen_update();
+}
+
+void vs_editline::set_text(string _text)
+{
+ vs_widget_ref tmpref(this);
+
+ wstring wtext;
+ if(transcode(_text, wtext))
+ set_text(wtext);
+}
+
+void vs_editline::init_bindings()
+{
+ bindings=new keybindings(&global_bindings);
+
+ bindings->set("Left", key(KEY_LEFT, true));
+ bindings->set("Right", key(KEY_RIGHT, true));
+ // Override these for the case where left and right have multiple bindings
+ // in the global keymap
+}
+
+int vs_editline::width_request()
+{
+ vs_widget_ref tmpref(this);
+
+ if(desired_size == -1)
+ return wcswidth(prompt.c_str(), prompt.size())+wcswidth(text.c_str(), text.size());
+ else
+ return desired_size;
+}
+
+int vs_editline::height_request(int width)
+{
+ return 1;
+}
diff --git a/src/vscreen/vs_editline.h b/src/vscreen/vs_editline.h
new file mode 100644
index 00000000..53126682
--- /dev/null
+++ b/src/vscreen/vs_editline.h
@@ -0,0 +1,138 @@
+// vs_editline.h -*-c++-*-
+//
+// Copyright 2000 Daniel Burrows
+//
+// A simple line-editor widget.
+
+#ifndef VS_EDITLINE_H
+#define VS_EDITLINE_H
+
+#include "vscreen_widget.h"
+
+#include <vector>
+
+class keybindings;
+
+class vs_editline:public vscreen_widget
+{
+public:
+ typedef std::vector<std::wstring> history_list;
+private:
+
+ std::wstring prompt;
+ std::wstring text;
+
+ std::wstring pre_history_text;
+ // Used as a "virtual" history entry.
+
+ std::wstring::size_type curloc, startloc;
+
+ int desired_size;
+
+ // The history of the edit-line. (if NULL, there is no history)
+ history_list *history;
+ // The current location in the history
+ history_list::size_type history_loc;
+ // True if we're flipping through the history. (to avoid signedness
+ // problems)
+ bool using_history;
+
+ void normalize_cursor();
+
+ /** \return the nth char of the visual representation (from either
+ * the prompt or the string being edited)
+ */
+ wchar_t get_char(size_t n);
+protected:
+ bool handle_key(const key &k);
+
+ vs_editline(const std::wstring &_prompt,
+ const std::wstring &_text=L"",
+ history_list *history=NULL);
+
+ /** Transcodes its input strings from the system charset. */
+ vs_editline(const std::string &_prompt,
+ const std::string &_text="",
+ history_list *history=NULL);
+
+ vs_editline(int maxlength, const std::wstring &_prompt,
+ const std::wstring &_text, history_list *history);
+
+ /** Transcodes its input strings from the system charset. */
+ vs_editline(int maxlength, const std::string &_prompt,
+ const std::string &_text, history_list *history);
+
+public:
+ static ref_ptr<vs_editline>
+ create(const std::wstring &prompt, const std::wstring &text = L"",
+ history_list *history = NULL)
+ {
+ ref_ptr<vs_editline> rval(new vs_editline(prompt, text, history));
+ rval->decref();
+ return rval;
+ }
+
+ static ref_ptr<vs_editline>
+ create(const std::string &prompt, const std::string &text = "",
+ history_list *history = NULL)
+ {
+ ref_ptr<vs_editline> rval(new vs_editline(prompt, text, history));
+ rval->decref();
+ return rval;
+ }
+
+ static ref_ptr<vs_editline>
+ create(int maxlength, const std::wstring &prompt,
+ const std::wstring &text = L"", history_list *history = NULL)
+ {
+ ref_ptr<vs_editline> rval(new vs_editline(maxlength, prompt, text, history));
+ rval->decref();
+ return rval;
+ }
+
+ static ref_ptr<vs_editline>
+ create(int maxlength, const std::string &prompt,
+ const std::string &text = "", history_list *history = NULL)
+ {
+ ref_ptr<vs_editline> rval(new vs_editline(maxlength, prompt, text, history));
+ rval->decref();
+ return rval;
+ }
+
+ bool focus_me();
+ void paint(const style &st);
+ void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+ sigc::signal1<void, std::wstring> entered;
+ // Called when the user presses Enter to confirm the text
+ sigc::signal1<void, std::wstring> text_changed;
+ // Called when the text is altered.
+
+ std::wstring get_text() {return text;}
+ void set_text(std::wstring _text);
+
+ /** Decodes the given multibyte string, and sets the current text
+ * of this edit-line to it.
+ */
+ void set_text(std::string _text);
+
+ bool get_cursorvisible();
+ point get_cursorloc();
+
+ int width_request();
+ int height_request(int height);
+
+ static void add_to_history(std::wstring s,
+ history_list *history);
+ // Appends the string to the end of the history list (convenience routine)
+
+ void add_to_history(std::wstring s);
+ void reset_history();
+
+ static keybindings *bindings;
+ static void init_bindings();
+};
+
+typedef ref_ptr<vs_editline> vs_editline_ref;
+
+#endif
diff --git a/src/vscreen/vs_frame.cc b/src/vscreen/vs_frame.cc
new file mode 100644
index 00000000..507b905e
--- /dev/null
+++ b/src/vscreen/vs_frame.cc
@@ -0,0 +1,71 @@
+// vs_frame.cc
+
+#include "vs_frame.h"
+
+#include "config/colors.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+vs_frame::vs_frame(const vs_widget_ref &w)
+ :vs_bin()
+{
+ set_subwidget(w);
+
+ do_layout.connect(sigc::mem_fun(*this, &vs_frame::layout_me));
+}
+
+int vs_frame::width_request()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref subwidget = get_subwidget();
+
+ if(subwidget.valid() && subwidget->get_visible())
+ return subwidget->width_request()+2;
+ else
+ return 2;
+}
+
+int vs_frame::height_request(int width)
+{
+ vs_widget_ref tmpref(this);
+
+ if(width<2)
+ return 0;
+ else
+ {
+ vs_widget_ref subwidget = get_subwidget();
+
+ if(subwidget.valid() && subwidget->get_visible())
+ return subwidget->height_request(width-2)+2;
+ else
+ return 2;
+ }
+}
+
+void vs_frame::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref subwidget = get_subwidget();
+
+ if(subwidget.valid())
+ {
+ if(subwidget->get_visible())
+ subwidget->alloc_size(1, 1, getmaxx()-2, getmaxy()-2);
+ else
+ subwidget->alloc_size(0, 0, 0, 0);
+ }
+}
+
+void vs_frame::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ border(0,0,0,0,0,0,0,0);
+
+ vs_widget_ref subwidget = get_subwidget();
+
+ if(subwidget.valid() && subwidget->get_visible())
+ subwidget->display(st);
+}
diff --git a/src/vscreen/vs_frame.h b/src/vscreen/vs_frame.h
new file mode 100644
index 00000000..e21d578a
--- /dev/null
+++ b/src/vscreen/vs_frame.h
@@ -0,0 +1,44 @@
+// vs_frame.h -*-c++-*-
+//
+// A container that draws a frame around the widget it contains.
+// (needs a lot more work to gracefully handle layout issues :) )
+
+#ifndef VS_FRAME_H
+#define VS_FRAME_H
+
+#include "vs_bin.h"
+
+class vs_frame:public vs_bin
+{
+ void layout_me();
+
+protected:
+ vs_frame(const vs_widget_ref &w);
+
+public:
+ static ref_ptr<vs_frame> create(const vs_widget_ref &w)
+ {
+ ref_ptr<vs_frame> rval(new vs_frame(w));
+ rval->decref();
+ return rval;
+ }
+
+ /** \return the desired width of the frame. A frame is 2 larger
+ * than its contents in every direction.
+ */
+ int width_request();
+
+ /** Calculate the desired height of the frame. A frame is 2 larger
+ * than its contents in every direction.
+ *
+ * \param width the width of the frame
+ * \return the desired height
+ */
+ int height_request(int width);
+
+ virtual void paint(const style &st);
+};
+
+typedef ref_ptr<vs_frame> vs_frame_ref;
+
+#endif
diff --git a/src/vscreen/vs_label.cc b/src/vscreen/vs_label.cc
new file mode 100644
index 00000000..61bd8eab
--- /dev/null
+++ b/src/vscreen/vs_label.cc
@@ -0,0 +1,136 @@
+// vs_label.cc
+
+#include "vs_label.h"
+
+#include "fragment_cache.h"
+#include "vscreen.h"
+
+#include <config/colors.h>
+
+#include <algorithm>
+
+using namespace std;
+
+vs_label::vs_label(fragment *f)
+ :txt(new fragment_cache(f))
+{
+}
+
+vs_label::vs_label(const string &_txt, const style &st)
+ :txt(new fragment_cache(text_fragment(_txt)))
+{
+ set_bg_style(st);
+}
+
+vs_label::vs_label(const string &_txt)
+ :txt(new fragment_cache(text_fragment(_txt)))
+{
+}
+
+vs_label::vs_label(const wstring &_txt, const style &st)
+ :txt(new fragment_cache(text_fragment(_txt)))
+{
+ set_bg_style(st);
+}
+
+vs_label::vs_label(const wstring &_txt)
+ :txt(new fragment_cache(text_fragment(_txt)))
+{
+}
+
+vs_label_ref vs_label::create(const string &txt, const style &st)
+{
+ vs_label_ref rval(new vs_label(txt, st));
+ rval->decref();
+ return rval;
+}
+
+vs_label_ref vs_label::create(const string &txt)
+{
+ vs_label_ref rval(new vs_label(txt));
+ rval->decref();
+ return rval;
+}
+
+vs_label_ref vs_label::create(const wstring &txt, const style &st)
+{
+ vs_label_ref rval(new vs_label(txt, st));
+ rval->decref();
+ return rval;
+}
+
+vs_label_ref vs_label::create(const wstring &txt)
+{
+ vs_label_ref rval(new vs_label(txt));
+ rval->decref();
+ return rval;
+}
+
+vs_label::~vs_label()
+{
+ delete txt;
+}
+
+bool vs_label::get_cursorvisible()
+{
+ return false;
+}
+
+point vs_label::get_cursorloc()
+{
+ return point(0,0);
+}
+
+void vs_label::set_text(const string &_txt, const style &st)
+{
+ set_text(text_fragment(_txt));
+ set_bg_style(st);
+}
+
+void vs_label::set_text(const string &_txt)
+{
+ set_text(text_fragment(_txt));
+}
+
+void vs_label::set_text(const wstring &_txt, const style &st)
+{
+ set_text(text_fragment(_txt));
+ set_bg_style(st);
+}
+
+void vs_label::set_text(const wstring &_txt)
+{
+ set_text(text_fragment(_txt));
+}
+
+void vs_label::set_text(fragment *f)
+{
+ delete txt;
+ txt=new fragment_cache(f);
+ // Our size might have changed, so re-layout the screen.
+ vscreen_queuelayout();
+}
+
+void vs_label::paint(const style &st)
+{
+ fragment_contents lines=txt->layout(getmaxx(), getmaxx(), st);
+
+ for(size_t i=0; i<lines.size() && i<(unsigned) getmaxy(); ++i)
+ mvaddnstr(i, 0, lines[i], lines[i].size());
+}
+
+int vs_label::width_request()
+{
+ return txt->max_width(0, 0);
+}
+
+int vs_label::height_request(int width)
+{
+ return txt->layout(width, width, style()).size();
+}
+
+bool vs_transientlabel::handle_char(chtype ch)
+{
+ destroy();
+ return true;
+}
diff --git a/src/vscreen/vs_label.h b/src/vscreen/vs_label.h
new file mode 100644
index 00000000..48fbbba9
--- /dev/null
+++ b/src/vscreen/vs_label.h
@@ -0,0 +1,96 @@
+// vs_label.h -*-c++-*-
+
+#ifndef VS_LABEL_H
+#define VS_LABEL_H
+
+#include "vscreen_widget.h"
+
+class fragment;
+class fragment_cache;
+
+/** vs_label widgets display some (possibly formatted) text
+ * statically. The text cannot be scrolled or selected in any way;
+ * if there isn't room for it, it just gets clipped.
+ *
+ * Passing a "background" style into the constructor modifies the
+ * background style of the widget (as set_bg_style would); this
+ * differs from wrapping the text in a style_fragment in that it
+ * even affects parts of the widget which aren't covered by text.
+ */
+class vs_label:public vscreen_widget
+{
+ fragment_cache *txt;
+protected:
+ vs_label(fragment *f);
+ vs_label(const std::string &_txt, const style &st);
+ vs_label(const std::string &_txt);
+ vs_label(const std::wstring &_txt, const style &st);
+ vs_label(const std::wstring &_txt);
+
+public:
+ static ref_ptr<vs_label> create(fragment *f)
+ {
+ ref_ptr<vs_label> rval(new vs_label(f));
+ rval->decref();
+ return rval;
+ }
+
+ /** Create a vs_label with the given text and background. */
+ static ref_ptr<vs_label> create(const std::string &txt, const style &st);
+
+ /** Create a vs_label with the given text. */
+ static ref_ptr<vs_label> create(const std::string &txt);
+
+ /** Create a vs_label with the given text and background. */
+ static ref_ptr<vs_label> create(const std::wstring &txt, const style &st);
+
+ /** CReate a vs_label with the given text. */
+ static ref_ptr<vs_label> create(const std::wstring &txt);
+
+
+ ~vs_label();
+
+ bool get_cursorvisible();
+ point get_cursorloc();
+
+ /** \return the maximum width of any line in the label. */
+ int width_request();
+
+ /** \return the number of lines in the label. */
+ int height_request(int width);
+
+ void paint(const style &st);
+ void set_text(const std::string &_txt, const style &st);
+ void set_text(const std::string &_txt);
+ void set_text(const std::wstring &_txt, const style &st);
+ void set_text(const std::wstring &_txt);
+ void set_text(fragment *f);
+};
+
+class vs_transientlabel:public vs_label
+// Displays a transient message -- grabs the input focus and vanishes when a
+// key is pressed.
+{
+protected:
+ virtual bool handle_char(chtype ch);
+
+ vs_transientlabel(const std::string &msg, const style &st)
+ :vs_label(msg, st)
+ {
+ }
+public:
+ static
+ ref_ptr<vs_transientlabel> create(const std::string &msg,
+ const style &st)
+ {
+ return new vs_transientlabel(msg, st);
+ }
+
+ bool focus_me() {return true;}
+};
+
+typedef ref_ptr<vs_label> vs_label_ref;
+
+typedef ref_ptr<vs_transientlabel> vs_transientlabel_ref;
+
+#endif
diff --git a/src/vscreen/vs_layout_item.cc b/src/vscreen/vs_layout_item.cc
new file mode 100644
index 00000000..b036f89d
--- /dev/null
+++ b/src/vscreen/vs_layout_item.cc
@@ -0,0 +1,149 @@
+// vs_layout_item.cc
+//
+// Copyright (C) 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+
+#include "vs_layout_item.h"
+
+#include "fragment.h"
+#include "vs_tree.h"
+
+vs_layout_item::vs_layout_line::vs_layout_line(int _n, vs_layout_item &_parent)
+ :vs_treeitem(false),n(_n), parent(_parent)
+{
+ set_depth(parent.get_depth());
+}
+
+const wchar_t *vs_layout_item::vs_layout_line::tag() {return L"";}
+const wchar_t *vs_layout_item::vs_layout_line::label() {return L"";}
+
+
+vs_layout_item::levelref::levelref(const levelref &x)
+ :vs_tree_levelref(x), item_num(x.item_num), lines(x.lines) {}
+
+vs_layout_item::levelref::levelref(size_t n, const child_list &_lines)
+ :item_num(n), lines(_lines)
+{
+}
+
+vs_treeitem *vs_layout_item::levelref::get_item()
+{
+ return item_num<lines.size()?lines[item_num]:lines.back();
+}
+
+void vs_layout_item::levelref::advance_next() {++item_num;}
+void vs_layout_item::levelref::return_prev() {--item_num;}
+bool vs_layout_item::levelref::is_begin() {return item_num==0;}
+bool vs_layout_item::levelref::is_end() {return item_num>=lines.size();}
+
+vs_layout_item::levelref *vs_layout_item::levelref::clone() const
+{
+ return new levelref(*this);
+}
+
+
+
+vs_layout_item::vs_layout_item(fragment *_f)
+ :f(_f), lastw(0), lastbasex(-1)
+{
+}
+
+// These don't need to be defined (?)
+const wchar_t *vs_layout_item::tag() {return L"";}
+const wchar_t *vs_layout_item::label() {return L"";}
+
+int vs_layout_item::get_normal_attr()
+{
+ return A_BOLD;
+}
+
+vs_layout_item::levelref *vs_layout_item::begin() {
+ return (!children.empty())?new levelref(0, children):NULL;
+}
+
+vs_layout_item::levelref *vs_layout_item::end()
+{
+ return (!children.empty())?new levelref(children.size(), children):NULL;
+}
+
+bool vs_layout_item::has_visible_children()
+{
+ return !children.empty();
+}
+
+vs_layout_item::~vs_layout_item()
+{
+ for(child_list::iterator i=children.begin(); i!=children.end(); ++i)
+ delete *i;
+ delete f;
+}
+
+const fragment_line &vs_layout_item::get_line(vs_tree *win, size_t n,
+ int basex, const style &st)
+{
+ if(win->getmaxx()!=lastw || basex != lastbasex)
+ {
+ fragment_contents tmplines=f->layout(win->getmaxx()-basex,
+ win->getmaxx()-basex,
+ st);
+
+ lines=fragment_contents();
+ attr_t attr=st.get_attrs();
+ for(fragment_contents::const_iterator i=tmplines.begin();
+ i!=tmplines.end(); ++i)
+ lines.push_back(fragment_line(basex, wchtype(L' ', attr))+*i);
+
+ for(child_list::iterator i=children.begin(); i!=children.end(); ++i)
+ delete *i;
+
+ children.clear();
+
+ for(size_t i=1; i<lines.size(); ++i)
+ children.push_back(new vs_layout_line(i, *this));
+
+ lastw=win->getmaxx();
+ lastbasex=basex;
+ }
+
+ if(n>=lines.size())
+ return lines.back();
+ else
+ return lines[n];
+}
+
+void vs_layout_item::paint_line(int n, vs_tree *win, int y, bool hierarchical, const style &st)
+{
+ int basex=hierarchical?2*get_depth():0;
+
+ const fragment_line &s=get_line(win, n, basex, st);
+
+ win->mvaddnstr(y, 0, s, s.size());
+}
+
+void vs_layout_item::vs_layout_line::paint(vs_tree *win, int y,
+ bool hierarchical, const style &st)
+{
+ parent.paint_line(n, win, y, hierarchical, st);
+}
+
+void vs_layout_item::paint(vs_tree *win, int y, bool hierarchical,
+ const style &st)
+{
+ paint_line(0, win, y, hierarchical, st);
+}
+
diff --git a/src/vscreen/vs_layout_item.h b/src/vscreen/vs_layout_item.h
new file mode 100644
index 00000000..09b2b58a
--- /dev/null
+++ b/src/vscreen/vs_layout_item.h
@@ -0,0 +1,101 @@
+// vs_layout_item.h -*-c++-*-
+//
+// Copyright (C) 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// A bridge from the layout code to the tree code.
+
+#ifndef VS_LAYOUT_ITEM_H
+#define VS_LAYOUT_ITEM_H
+
+#include "vs_text_layout.h"
+#include "vs_treeitem.h"
+
+class vs_layout_item:public vs_treeitem
+{
+protected:
+ class vs_layout_line;
+
+private:
+ typedef std::vector<vs_layout_line *> child_list;
+
+ child_list children;
+ fragment *f;
+ fragment_contents lines;
+
+ int lastw;
+ int lastbasex;
+
+protected:
+ class vs_layout_line:public vs_treeitem
+ {
+ int n;
+ vs_layout_item &parent;
+ public:
+ vs_layout_line(int _n, vs_layout_item &_parent);
+
+ void paint(vs_tree *win, int y, bool hierarchical,
+ const style &st);
+
+ const wchar_t *tag();
+ const wchar_t *label();
+ };
+
+ // Assumes that children.size()>0
+ class levelref:public vs_tree_levelref
+ {
+ size_t item_num;
+ const child_list &lines;
+
+ public:
+ levelref(const levelref &x);
+ levelref(size_t n, const child_list &_lines);
+
+ vs_treeitem *get_item();
+ virtual void advance_next();
+ virtual void return_prev();
+ bool is_begin();
+ bool is_end();
+ levelref *clone() const;
+ };
+
+public:
+ vs_layout_item(fragment *f);
+
+ const wchar_t *tag();
+ const wchar_t *label();
+
+ /** Paints the nth line of this item at the given location in 'win'. */
+ void paint_line(int n,
+ vs_tree *win, int y, bool hierarchical,
+ const style &st);
+ void paint(vs_tree *win, int y, bool hierarchical,
+ const style &st);
+
+ int get_normal_attr();
+
+ levelref *begin();
+ levelref *end();
+ bool has_visible_children();
+
+ const fragment_line &get_line(vs_tree *win, size_t n, int basex,
+ const style &st);
+
+ ~vs_layout_item();
+};
+
+#endif
diff --git a/src/vscreen/vs_menu.cc b/src/vscreen/vs_menu.cc
new file mode 100644
index 00000000..2b8c0a00
--- /dev/null
+++ b/src/vscreen/vs_menu.cc
@@ -0,0 +1,665 @@
+// vs_menu.cc
+//
+// Copyright (C) 2000-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+
+#include "vs_menu.h"
+#include "vscreen.h"
+#include "config/colors.h"
+
+#include "transcode.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+#include <algorithm>
+#include <ctype.h>
+
+using namespace std;
+
+keybindings *vs_menu::bindings=NULL;
+
+vs_menu_info::vs_menu_info(item_types type, const char *name, const char *binding,
+ const char *description, sigc::slot0<void> slot)
+ :item_type(type), item_name(name), item_binding(binding),
+ item_description(description), item_slot(slot), item_enabled(NULL)
+{
+}
+
+vs_menu_info::vs_menu_info(item_types type, const char *name, const char *binding,
+ const char *description, sigc::slot0<void> *slot)
+ :item_type(type), item_name(name), item_binding(binding),
+ item_description(description), item_slot(slot), item_enabled(NULL)
+{
+}
+
+vs_menu_info::vs_menu_info(item_types type, const char *name, const char *binding,
+ const char *description, sigc::slot0<void> slot,
+ sigc::slot0<bool> enabled)
+ :item_type(type), item_name(name), item_binding(binding),
+ item_description(description), item_slot(slot), item_enabled(enabled)
+{
+}
+
+vs_menu_info::vs_menu_info(item_types type, const char *name, const char *binding,
+ const char *description, sigc::slot0<void> *slot,
+ sigc::slot0<bool> enabled)
+ :item_type(type), item_name(name), item_binding(binding),
+ item_description(description), item_slot(slot), item_enabled(enabled)
+{
+}
+
+vs_menu_info::vs_menu_info(item_types type)
+ :item_type(type), item_name(NULL), item_binding(NULL),
+ item_description(NULL), item_slot(NULL), item_enabled(NULL)
+{
+}
+
+vs_menu_item::vs_menu_item(const wstring &_title, const string &_binding,
+ const wstring &_description)
+ :title(_title), description(_description), binding(_binding),
+ hotkey((chtype) ERR)
+{
+ for(wstring::size_type i=0; i<title.size(); i++)
+ if(title[i]==L'^' && i+1<title.size())
+ {
+ hotkey=title[i+1];
+ break;
+ }
+}
+
+bool vs_menu_item::is_enabled() const
+{
+ if(enabled.empty())
+ return !selected.empty();
+ else
+ return enabled();
+}
+
+vs_menu::vs_menu()
+ :vscreen_widget(), cursorloc(0), min_width(2)
+{
+ shown_sig.connect(sigc::mem_fun(*this, &vs_menu::appear));
+ hidden_sig.connect(sigc::mem_fun(*this, &vs_menu::disappear));
+ do_layout.connect(sigc::mem_fun(*this, &vs_menu::update_startloc));
+}
+
+vs_menu::~vs_menu()
+{
+ for(itemlist::iterator i=items.begin(); i!=items.end(); i++)
+ delete *i;
+}
+
+vs_menu::vs_menu(int x, int y, int w, vs_menu_info *inf)
+ :vscreen_widget(), cursorloc(0), startloc(0), min_width(w)
+{
+ while(inf->item_type!=vs_menu_info::VS_MENU_END)
+ {
+ switch(inf->item_type)
+ {
+ case vs_menu_info::VS_MENU_ITEM:
+ assert(inf->item_name!=NULL);
+
+ {
+ vs_menu_item *newitem=new vs_menu_item(transcode(inf->item_name),
+ inf->item_binding?inf->item_binding:"",
+ transcode(inf->item_description?inf->item_description:""));
+
+ if(inf->item_slot)
+ newitem->selected.connect(*inf->item_slot);
+
+ if(inf->item_enabled)
+ newitem->enabled.connect(*inf->item_enabled);
+
+ append_item(newitem);
+ }
+ break;
+ case vs_menu_info::VS_MENU_SEPARATOR:
+ assert(inf->item_name==NULL);
+
+ append_item(NULL);
+ break;
+ default:
+ fprintf(stderr, "ERROR: unknown item type code %i\n", inf->item_type);
+ abort();
+ }
+
+ inf++;
+ }
+
+ shown_sig.connect(sigc::mem_fun(*this, &vs_menu::appear));
+ hidden_sig.connect(sigc::mem_fun(*this, &vs_menu::disappear));
+ do_layout.connect(sigc::mem_fun(*this, &vs_menu::update_startloc));
+}
+
+void vs_menu::append_item(vs_menu_item *newitem)
+{
+ vs_widget_ref tmpref(this);
+
+ items.push_back(newitem);
+
+ if(get_visible())
+ {
+ vscreen_queuelayout();
+ vscreen_update();
+ }
+}
+
+void vs_menu::remove_item(vs_menu_item *item)
+{
+ vs_widget_ref tmpref(this);
+
+ itemlist::size_type idx=0;
+
+ while(idx<items.size() && items[idx]!=item)
+ ++idx;
+
+ assert(idx<items.size());
+
+ for(itemlist::size_type newidx=idx; newidx<items.size()-1; ++newidx)
+ items[newidx]=items[newidx+1];
+
+ items.pop_back();
+
+ if(items.size()==0)
+ set_cursor(0);
+ else if(idx==cursorloc)
+ set_cursor(prev_selectable(next_selectable(items.size()-1)));
+
+ while(startloc >= items.size())
+ --startloc;
+
+ if(get_visible())
+ vscreen_queuelayout();
+}
+
+int vs_menu::width_request()
+{
+ int req_width=min_width;
+
+ vs_widget_ref tmpref(this);
+
+ for(itemlist::const_iterator item=items.begin();
+ item!=items.end(); ++item)
+ if(*item)
+ {
+ int titlewidth=0, shortcutwidth=0;
+ const wstring &title=(*item)->get_title();
+ const string &binding=(*item)->get_binding();
+
+ for(wstring::size_type i=0; i<title.size(); ++i)
+ if(title[i]!=L'^')
+ titlewidth+=wcwidth(title[i]);
+
+ if(binding.empty())
+ shortcutwidth=0;
+ else
+ {
+ wstring keyname=global_bindings.readable_keyname(binding);
+ shortcutwidth=wcswidth(keyname.c_str(), keyname.size())+1;
+ }
+
+ req_width=max<int>(titlewidth+2+shortcutwidth,req_width);
+ }
+
+ return req_width;
+}
+
+int vs_menu::height_request(int w)
+{
+ return items.size()+2;
+}
+
+void vs_menu::highlight_current()
+{
+ vs_widget_ref tmpref(this);
+
+ if(cursorloc>=0 && cursorloc<items.size())
+ item_highlighted(items[cursorloc]);
+ else
+ item_highlighted(NULL);
+}
+
+void vs_menu::update_startloc()
+{
+ unsigned int h = get_height();
+
+ if(h <= 2)
+ return;
+ else if(h - 2 >= items.size())
+ {
+ startloc = 0;
+ return;
+ }
+ else if(cursorloc >= items.size() || cursorloc < items.size())
+ ; // Do nothing, but also do the final sanitization below.
+ else if(cursorloc < startloc)
+ startloc = cursorloc;
+ else if(startloc + h - 2 <= cursorloc)
+ startloc = cursorloc - (h - 2 - 1);
+
+ if(startloc + (h - 2) > items.size())
+ startloc = items.size() - (h - 2);
+}
+
+void vs_menu::set_cursor(itemlist::size_type pos)
+{
+ vs_widget_ref tmpref(this);
+
+ if(cursorloc!=pos)
+ {
+ cursorloc = pos;
+
+ update_startloc();
+
+ highlight_current();
+
+ if(get_visible())
+ vscreen_update();
+ }
+}
+
+void vs_menu::appear()
+{
+ vs_widget_ref tmpref(this);
+
+ cursorloc = next_selectable(0);
+ startloc = 0;
+
+ update_startloc();
+
+ highlight_current();
+}
+
+void vs_menu::disappear()
+{
+ vs_widget_ref tmpref(this);
+
+ set_cursor(items.size());
+}
+
+bool vs_menu::focus_me()
+{
+ return true;
+}
+
+bool vs_menu::selectable(itemlist::size_type pos)
+{
+ vs_widget_ref tmpref(this);
+
+ return pos>=0 && pos<items.size() && items[pos] && items[pos]->is_enabled();
+}
+
+vs_menu::itemlist::size_type vs_menu::next_selectable(itemlist::size_type pos)
+{
+ vs_widget_ref tmpref(this);
+
+ if(pos<0 || pos>=items.size())
+ pos=0;
+
+ while(pos<items.size() && (!items[pos] ||
+ !items[pos]->is_enabled()))
+ ++pos;
+
+ return pos;
+}
+
+vs_menu::itemlist::size_type vs_menu::prev_selectable(itemlist::size_type pos)
+{
+ vs_widget_ref tmpref(this);
+
+ if(pos<0 || pos>=items.size())
+ pos=items.size()-1;
+
+ while(pos>=0 && pos<items.size() && (!items[pos] ||
+ !items[pos]->is_enabled()))
+ --pos;
+
+ if(pos<0 || pos>=items.size())
+ pos=items.size();
+
+ return pos;
+}
+
+void vs_menu::sanitize_cursor(bool forward)
+{
+ vs_widget_ref tmpref(this);
+
+ if(forward)
+ // Double-invoking next_selectable will search from the
+ // start of the menu if searching forwards fails.
+ cursorloc=next_selectable(next_selectable(cursorloc));
+ else
+ cursorloc=prev_selectable(prev_selectable(cursorloc));
+
+ update_startloc();
+
+ highlight_current();
+}
+
+void vs_menu::move_selection_up()
+{
+ if(cursorloc > 0)
+ {
+ itemlist::size_type newloc = prev_selectable(cursorloc-1);
+
+ if(newloc >= 0 && newloc < items.size())
+ {
+ if(newloc < startloc)
+ --startloc;
+
+ if(newloc >= startloc)
+ set_cursor(newloc);
+ }
+ else if(startloc > 0)
+ --startloc;
+
+ update_startloc();
+
+ vscreen_update();
+ }
+ else if(startloc > 0)
+ {
+ --startloc;
+ vscreen_update();
+ }
+}
+
+void vs_menu::move_selection_down()
+{
+ int h = get_height();
+
+ if(cursorloc < items.size() - 1)
+ {
+ itemlist::size_type newloc = next_selectable(cursorloc+1);
+
+ if(newloc >= 0 && newloc < items.size())
+ {
+ if(newloc >= startloc + h - 2)
+ ++startloc;
+
+ if(newloc < startloc + h - 2)
+ set_cursor(newloc);
+ }
+ else if(startloc + h < items.size())
+ ++startloc;
+
+ vscreen_update();
+ }
+ else if(startloc + h - 2 < items.size())
+ {
+ ++startloc;
+ vscreen_update();
+ }
+}
+
+void vs_menu::move_selection_top()
+{
+ startloc = 0;
+ set_cursor(next_selectable(startloc));
+ vscreen_update();
+}
+
+void vs_menu::move_selection_bottom()
+{
+ startloc = items.size() - 1;
+ set_cursor(prev_selectable(startloc));
+ vscreen_update();
+}
+
+bool vs_menu::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ // This will ensure that the cursor is in bounds if possible, and that
+ // if it is in bounds, a "real" item is selected.
+ sanitize_cursor(true);
+
+ if(bindings->key_matches(k, "Up"))
+ move_selection_up();
+ else if(bindings->key_matches(k, "Down"))
+ move_selection_down();
+ else if(bindings->key_matches(k, "Begin"))
+ move_selection_top();
+ else if(bindings->key_matches(k, "End"))
+ move_selection_bottom();
+ else if(bindings->key_matches(k, "Confirm"))
+ {
+ itemlist::size_type selected=cursorloc;
+
+ menus_goaway();
+ item_highlighted(NULL);
+
+ if(selectable(selected))
+ items[selected]->selected();
+ }
+ else
+ {
+ for(itemlist::iterator i=items.begin(); i!=items.end(); i++)
+ if(*i && (*i)->is_enabled() &&
+ towupper(k.ch)==towupper((*i)->get_hotkey()))
+ {
+ menus_goaway();
+ item_highlighted(NULL);
+
+ (*i)->selected();
+
+ return true;
+ }
+ return vscreen_widget::handle_key(k);
+ }
+
+ return true;
+}
+
+void vs_menu::dispatch_mouse(short id, int x, int y, int z, mmask_t bmask)
+{
+ vs_widget_ref tmpref(this);
+
+ if(bmask & (BUTTON1_RELEASED | BUTTON2_RELEASED |
+ BUTTON3_RELEASED | BUTTON4_RELEASED |
+ BUTTON1_CLICKED | BUTTON2_CLICKED |
+ BUTTON3_CLICKED | BUTTON4_CLICKED))
+ {
+ itemlist::size_type num=y-1;
+
+ if(selectable(num))
+ {
+ menus_goaway();
+ item_highlighted(NULL);
+ items[num]->selected();
+ }
+ }
+ else if(bmask & (BUTTON1_PRESSED | BUTTON2_PRESSED |
+ BUTTON3_PRESSED | BUTTON4_PRESSED))
+ {
+ itemlist::size_type num=y-1;
+
+ if(selectable(num))
+ set_cursor(num);
+ }
+}
+
+// FIXME: handle truncated menus (eg, from menus appearing that are
+// larger than the screen). REALLY FIX THIS! It's incredibly annoying in, eg,
+// GTK+, and text screens have even less space to play with..
+void vs_menu::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ int width, height;
+ const style border_style=st+get_style("MenuBorder");
+ const style highlighted_style=st+get_style("HighlightedMenuEntry");
+ const style entry_style=st+get_style("MenuEntry");
+ const style disabled_style=st+get_style("DisabledMenuEntry");
+
+ getmaxyx(height, width);
+
+ apply_style(border_style);
+ mvadd_wch(0, 0, WACS_ULCORNER);
+
+ const bool up_arrows_visible = (startloc != 0);
+ for(int i = 1; i < width-1; i++)
+ {
+ add_wch((up_arrows_visible && i % 3 == 0) ? WACS_UARROW : WACS_HLINE);
+ }
+ add_wch(WACS_URCORNER);
+
+ // Make sure that whatever is selected is really selectable.
+ sanitize_cursor(true);
+
+ for(itemlist::size_type i = startloc; i < items.size(); ++i)
+ if(items[i])
+ {
+ int y = i - startloc + 1;
+ bool boldthis=false;
+
+ apply_style(border_style);
+ mvadd_wch(y, 0, WACS_VLINE);
+ mvadd_wch(y, width-1, WACS_VLINE);
+
+ wstring title=items[i]->get_title();
+ wstring righttext=items[i]->get_binding().empty()?L"":global_bindings.readable_keyname(items[i]->get_binding());
+
+ bool enabled=items[i]->is_enabled();
+ style textst;
+
+ if(i==cursorloc)
+ textst=highlighted_style;
+ else if(enabled)
+ textst=entry_style;
+ else
+ textst=disabled_style;
+
+ apply_style(textst);
+
+ move(y, 1);
+
+ wstring::size_type titleloc=0, rightloc=0;
+ int rightwidth=wcswidth(righttext.c_str(), righttext.size());
+ int curw=1;
+
+ while(curw<width-1)
+ {
+ while(titleloc<title.size() && title[titleloc]=='^')
+ {
+ boldthis=enabled;
+ ++titleloc;
+ }
+
+ if(titleloc==title.size())
+ {
+ add_wch(L' ');
+ ++titleloc;
+ curw+=wcwidth(L' ');;
+ }
+ else if(titleloc>title.size())
+ {
+ if(curw<width-1-rightwidth)
+ {
+ add_wch(L' ');
+ curw+=wcwidth(L' ');
+ }
+ else
+ {
+ wchar_t wch=righttext[rightloc];
+
+ add_wch(wch);
+ curw+=wcwidth(wch);
+ ++rightloc;
+ }
+ }
+ else if(boldthis)
+ {
+ wchar_t wch=title[titleloc];
+
+ apply_style(textst+style_attrs_on(A_BOLD));
+ add_wch(wch);
+ apply_style(textst);
+ boldthis=false;
+
+ curw+=wcwidth(wch);
+ ++titleloc;
+ }
+ else
+ {
+ wchar_t wch=title[titleloc];
+
+ add_wch(wch);
+ curw+=wcwidth(wch);
+ ++titleloc;
+ }
+ }
+ }
+ else
+ {
+ int y = i - startloc + 1;
+
+ apply_style(border_style);
+ mvadd_wch(y, 0, WACS_LTEE);
+ for(int j=1; j<width-1; j++)
+ add_wch(WACS_HLINE);
+ add_wch(WACS_RTEE);
+ }
+
+ apply_style(border_style);
+
+ for(int i=items.size()+1; i<height-1; i++)
+ {
+ move(i, 0);
+ add_wch(WACS_VLINE);
+
+ apply_style(entry_style);
+ for(int j=0; j<width-2; j++)
+ add_wch(L' ');
+ apply_style(border_style);
+
+ add_wch(WACS_VLINE);
+ }
+
+ mvadd_wch(height-1, 0, WACS_LLCORNER);
+
+ const bool down_arrows_visible = startloc + height - 2 < items.size();
+
+ for(int i = 1; i < width-1; ++i)
+ add_wch((down_arrows_visible && i % 3 == 0) ? WACS_DARROW : WACS_HLINE);
+
+ add_wch(WACS_LRCORNER);
+}
+
+bool vs_menu::get_cursorvisible()
+{
+ vs_widget_ref tmpref(this);
+
+ sanitize_cursor(true);
+
+ return cursorloc>=0 && cursorloc<items.size();
+}
+
+point vs_menu::get_cursorloc()
+{
+ vs_widget_ref tmpref(this);
+
+ sanitize_cursor(true);
+
+ return point(0, 1 + cursorloc - startloc);
+}
+
+void vs_menu::init_bindings()
+{
+ bindings=new keybindings(&global_bindings);
+}
diff --git a/src/vscreen/vs_menu.h b/src/vscreen/vs_menu.h
new file mode 100644
index 00000000..569d500e
--- /dev/null
+++ b/src/vscreen/vs_menu.h
@@ -0,0 +1,245 @@
+// vs_menu.h -*-c++-*-
+//
+// Copyright (C) 2000-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// A nice menu with selectable entries. (fairly basic atm)
+
+#ifndef VS_MENU_H
+#define VS_MENU_H
+
+#include "vscreen_widget.h"
+#include "config/keybindings.h"
+
+#include <generic/util/bool_accumulate.h>
+#include <generic/util/slotarg.h>
+
+#include <vector>
+
+// Currently, menu-items aren't full widgets--it's simply too much
+// baggage (lots of signals and a Curses window) for something that's
+// quite simple and special-purpose.
+class vs_menu_item
+{
+ // The text displayed in the menu entry
+ std::wstring title;
+
+ // A string describing the item's function
+ std::wstring description;
+
+ // The keybinding whose definition (in the GLOBAL bindings list) is
+ // displayed to the right of the menu entry.
+ std::string binding;
+
+ // The key that directly activates this item *while the menu is open*
+ chtype hotkey;
+public:
+ // Infers the hotkey from the title
+ vs_menu_item(const std::wstring &_title, const std::string &_binding,
+ const std::wstring &_description);
+
+ std::wstring get_title() const {return title;}
+ std::string get_binding() const {return binding;}
+ std::wstring get_description() const {return description;}
+ chtype get_hotkey() const {return hotkey;}
+
+ /** The canonical way to test whether an item is really enabled. */
+ bool is_enabled() const;
+
+ /** Emitted when the menu item is selected. */
+ sigc::signal0<void> selected;
+
+ /** Emitted to test whether the menu item should be displayed as "active".
+ *
+ * If this signal is empty, the item will be displayed as "active" iff
+ * "selected" is non-empty. Otherwise, the return value of the signal
+ * is used.
+ */
+ sigc::signal0<bool, accumulate_or> enabled;
+};
+
+#define VS_MENU_NOP NULL
+
+// Info for easy static generation of menus
+
+struct vs_menu_info
+{
+public:
+ // VS_MENU_ITEM: a "real" menu-item
+ // VS_MENU_END: the last item in this information block
+ enum item_types {VS_MENU_ITEM, VS_MENU_SEPARATOR, VS_MENU_END} item_type;
+
+ /** item_name and item_description are multibyte representations. */
+ const char *item_name, *item_binding, *item_description;
+
+ // How to communicate with the outside world..
+ slot0arg item_slot;
+
+ // For activation
+ slotarg<sigc::slot0<bool> > item_enabled;
+
+ vs_menu_info(item_types type, const char *name, const char *binding,
+ const char *description, sigc::slot0<void> slot);
+
+ vs_menu_info(item_types type, const char *name, const char *binding,
+ const char *description, sigc::slot0<void> *slot);
+
+ vs_menu_info(item_types type, const char *name, const char *binding,
+ const char *description, sigc::slot0<void> slot,
+ sigc::slot0<bool> enabled);
+
+ vs_menu_info(item_types type, const char *name, const char *binding,
+ const char *description, sigc::slot0<void> *slot,
+ sigc::slot0<bool> enabled);
+
+ vs_menu_info(item_types type);
+};
+
+const vs_menu_info VS_MENU_SEPARATOR(vs_menu_info::VS_MENU_SEPARATOR);
+const vs_menu_info VS_MENU_END(vs_menu_info::VS_MENU_END);
+
+class vs_menu:public vscreen_widget
+{
+ typedef std::vector<vs_menu_item *> itemlist;
+
+ // A set of menu items. NULL indicates a separator.
+ // These items are deleted with the menu.
+ itemlist items;
+
+ /** The location of the cursor, or items.size() if no item is selected. */
+ itemlist::size_type cursorloc;
+
+ /** The first visible item in the menu. */
+ itemlist::size_type startloc;
+
+ /** The minimum width of this menu. */
+ int min_width;
+
+ // connected to "shown"
+ void appear();
+
+ // connected to "hidden"
+ void disappear();
+
+ /** Update the starting location from the current height and cursor
+ * location.
+ */
+ void update_startloc();
+
+ /** Returns \b true iff the given item is selectable. */
+ bool selectable(itemlist::size_type pos);
+
+ /** Select the given location. */
+ void set_cursor(itemlist::size_type pos);
+
+ /** Highlight the currently selected item (highlight NULL if no
+ * item is selected)
+ */
+ void highlight_current();
+
+ /** Search for the first selectable item from the given location; if
+ * the search runs off the end of the list, return items.size().
+ *
+ * \param pos the starting location of the search; if it is out of
+ * bounds, the search starts with the first menu item.
+ */
+ itemlist::size_type next_selectable(itemlist::size_type pos);
+
+ /** Search backwards for the first selectable item from the given
+ * location; if the search runs off the beginning of the list,
+ * return items.size().
+ *
+ * \param pos the starting location of the search; if it is out of
+ * bounds, the search starts with the last menu item.
+ */
+ itemlist::size_type prev_selectable(itemlist::size_type pos);
+
+ /** If the cursor is "out of bounds" or on a disabled item, attempt
+ * to select the first enabled non-separator item.
+ *
+ * \param forward if \b true, search forward in the list for an
+ * enabled item; otherwise, search backward.
+ */
+ void sanitize_cursor(bool forward);
+
+protected:
+ virtual bool handle_key(const key &k);
+
+ /** Create a blank menu. */
+ vs_menu();
+
+ // Initialize a menu from a block of information. If there is no
+ // VS_MENU_END in the block, RANDOM ERRORS WILL OCCUR!!
+ vs_menu(int x, int y, int w, vs_menu_info *inf);
+public:
+ static ref_ptr<vs_menu> create()
+ {
+ ref_ptr<vs_menu> rval(new vs_menu);
+ rval->decref();
+ return rval;
+ }
+
+ static ref_ptr<vs_menu> create(int x, int y, int w, vs_menu_info *inf)
+ {
+ ref_ptr<vs_menu> rval(new vs_menu(x, y, w, inf));
+ rval->decref();
+ return rval;
+ }
+
+ // Deletes the items it holds!
+ ~vs_menu();
+
+ bool get_cursorvisible();
+ point get_cursorloc();
+
+ int width_request();
+ int height_request(int width);
+
+ void append_item(vs_menu_item *newitem);
+ void remove_item(vs_menu_item *item);
+
+ /** Move the selection up, as if Up had been pressed. */
+ void move_selection_up();
+
+ /** Move the selection down, as if Down had been pressed. */
+ void move_selection_down();
+
+ /** Move the selection to the top of the menu, as if Home had been pressed. */
+ void move_selection_top();
+
+ /** Move the selection to the bottom of the menu, as if End had been pressed. */
+ void move_selection_bottom();
+
+ virtual bool focus_me();
+ virtual void paint(const style &st);
+ virtual void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+ // Emitted when an item is highlighted or when the selection "goes away".
+ // In the latter case, the argument is NULL. (happens only when
+ // the menu is hidden -- FIXME?)
+ sigc::signal1<void, vs_menu_item *> item_highlighted;
+
+ // FIXME: there should be a less hacky way..
+ sigc::signal0<void> menus_goaway;
+
+ static keybindings *bindings;
+ static void init_bindings();
+};
+
+typedef ref_ptr<vs_menu> vs_menu_ref;
+
+#endif
diff --git a/src/vscreen/vs_menubar.cc b/src/vscreen/vs_menubar.cc
new file mode 100644
index 00000000..046ea97e
--- /dev/null
+++ b/src/vscreen/vs_menubar.cc
@@ -0,0 +1,778 @@
+// vs_menubar.cc
+//
+// Copyright (C) 2000-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "vs_container.h"
+#include "vs_menubar.h"
+#include "vs_menu.h"
+#include "vscreen.h"
+
+#include "config/colors.h"
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+using namespace std;
+
+keybindings *vs_menubar::bindings=NULL;
+
+vs_menubar::vs_menubar(bool _always_visible)
+ :vs_container(), startloc(0), active(false),
+ always_visible(_always_visible), curloc(0), subwidget(NULL)
+{
+ do_layout.connect(sigc::mem_fun(*this, &vs_menubar::layout_me));
+
+ focussed.connect(sigc::mem_fun(*this, &vs_menubar::got_focus));
+ unfocussed.connect(sigc::mem_fun(*this, &vs_menubar::lost_focus));
+}
+
+vs_menubar::~vs_menubar()
+{
+ assert(!subwidget.valid());
+ assert(items.empty());
+ assert(active_menus.empty());
+}
+
+vs_widget_ref vs_menubar::get_active_widget()
+{
+ return subwidget;
+}
+
+void vs_menubar::destroy()
+{
+ vs_widget_ref tmpref(this);
+
+ if(subwidget.valid())
+ subwidget->destroy();
+ assert(!subwidget.valid());
+
+ // ew. You see, we need to individually destroy the subwidgets, but
+ // doing so will cause them to be removed, so we can't iterate over
+ // the main list...
+ vector<item> curr_items(items);
+
+ for(vector<item>::const_iterator i = curr_items.begin();
+ i != curr_items.end(); ++i)
+ i->menu->destroy();
+
+ assert(items.empty());
+ assert(active_menus.empty());
+
+ vs_container::destroy();
+}
+
+void vs_menubar::got_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w=get_focus();
+ if(w.valid())
+ w->focussed();
+}
+
+void vs_menubar::lost_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w=get_focus();
+ if(w.valid())
+ w->unfocussed();
+}
+
+bool vs_menubar::focus_me()
+{
+ if(active)
+ return true;
+ else if(subwidget.valid() && subwidget->focus_me())
+ return true;
+ else
+ return vscreen_widget::focus_me();
+}
+
+vs_widget_ref vs_menubar::get_focus()
+{
+ if(active)
+ {
+ if(!active_menus.empty())
+ return active_menus.front();
+ else
+ return NULL;
+ }
+ else if(subwidget.valid())
+ return subwidget;
+ else
+ return NULL;
+ // NB: could just end with 'return subwidget' but this makes the
+ // order more explicit.
+}
+
+bool vs_menubar::get_cursorvisible()
+{
+ vs_widget_ref w = get_focus();
+ return (w.valid() && w->get_cursorvisible()) ||
+ (!w.valid() && active);
+}
+
+point vs_menubar::get_cursorloc()
+{
+ vs_widget_ref w = get_focus();
+
+ if(w.valid())
+ {
+ point p=w->get_cursorloc();
+ p.x+=w->get_startx();
+ p.y+=w->get_starty();
+ return p;
+ }
+ else if(active)
+ return point(get_menustart(curloc), 0);
+ else
+ return point(0, 0);
+}
+
+int vs_menubar::get_menustart(itemlist::size_type idx) const
+{
+ int rval = 0;
+
+ if(idx >= startloc)
+ {
+ for(itemlist::size_type i = startloc; i < idx; ++i)
+ {
+ const wstring &title = items[i].title;
+ rval += wcswidth(title.c_str(), title.size());
+ }
+ }
+ else
+ {
+ for(itemlist::size_type i = idx; i < startloc; ++i)
+ {
+ const wstring &title = items[i].title;
+ rval -= wcswidth(title.c_str(), title.size());
+ }
+ }
+
+ return rval;
+}
+
+void vs_menubar::update_x_start()
+{
+ if(!active)
+ startloc = 0;
+ else if(curloc < startloc)
+ startloc = curloc;
+ else
+ {
+ int width = get_width();
+ if(width == 0)
+ return;
+
+
+ int start_x = get_menustart(startloc);
+
+ const wstring &curr_title = items[curloc].title;
+ int curr_x = get_menustart(curloc);
+ int curr_width = wcswidth(curr_title.c_str(),
+ curr_title.size());
+
+ if(width < curr_width)
+ while(curr_x >= start_x + width)
+ {
+ const wstring &title = items[startloc].title;
+ start_x += wcswidth(title.c_str(), title.size());
+ ++startloc;
+ }
+ else
+ while(curr_x + curr_width > start_x + width)
+ {
+ const wstring &title = items[startloc].title;
+ start_x += wcswidth(title.c_str(), title.size());
+ ++startloc;
+ }
+ }
+}
+
+void vs_menubar::append_item(const wstring &title,
+ const vs_menu_ref &menu)
+{
+ vs_widget_ref tmpref(this);
+
+ items.push_back(item(L' '+title+L' ', menu));
+
+ menu->shown_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_menubar::show_menu_bare), menu.weak_ref()));
+ menu->hidden_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_menubar::hide_menu_bare), menu.weak_ref()));
+ menu->menus_goaway.connect(sigc::mem_fun(*this, &vs_menubar::disappear));
+ menu->set_owner(this);
+
+ vscreen_update();
+}
+
+void vs_menubar::set_subwidget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ if(subwidget.valid())
+ {
+ subwidget->set_owner(NULL);
+ subwidget->unfocussed();
+ }
+
+ subwidget=w;
+
+ if(subwidget.valid())
+ {
+ subwidget->set_owner(this);
+ subwidget->focussed();
+ }
+
+ vscreen_queuelayout();
+}
+
+void vs_menubar::show_all()
+{
+ vs_widget_ref tmpref(this);
+
+ if(subwidget.valid())
+ subwidget->show_all();
+}
+
+void vs_menubar::add_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ assert(!subwidget.valid());
+
+ set_subwidget(w);
+}
+
+void vs_menubar::rem_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ if(w == subwidget)
+ set_subwidget(NULL);
+ else
+ {
+ assert(w->get_owner().unsafe_get_ref() == this);
+
+ bool found = false;
+
+ // make a new strong reference
+ vs_widget_ref w2 = w;
+
+ // hrm.
+ for(vector<item>::iterator i = items.begin();
+ i != items.end(); ++i)
+ {
+ if(i->menu == w2)
+ {
+ found = true;
+ items.erase(i);
+ break;
+ }
+ }
+
+ assert(found);
+
+ active_menus.remove(w2);
+
+ w2->set_owner(NULL);
+ }
+}
+
+int vs_menubar::width_request()
+{
+ vs_widget_ref tmpref(this);
+
+ int w=0;
+
+ // Calculate the size of the bar itself.
+ for(itemlist::size_type i=0; i<items.size(); i++)
+ {
+ const wstring &title=items[i].title;
+
+ w+=wcswidth(title.c_str(), title.size());
+ }
+
+ // Expand the width as needed to account for active menus.
+ for(activemenulist::iterator i=active_menus.begin(), num=0;
+ i!=active_menus.end();
+ i++, num++)
+ {
+ int menux=0;
+
+ // Calculate the starting X location of this menu.
+ for(itemlist::size_type j=0; j<items.size(); j++)
+ {
+ if(items[j].menu==*i)
+ break;
+
+ const wstring &title=items[j].title;
+
+ menux+=wcswidth(title.c_str(), title.size());
+ }
+
+ // Now expand our width request.
+ w=max(w, menux+(*i)->width_request());
+ }
+
+ // Expand the width to account for the subwidget.
+ if(subwidget.valid())
+ w=max(w, subwidget->width_request());
+
+ return w;
+}
+
+// TODO: What if the width is insufficient: should I scroll left/right
+// or wrap to the next line?
+int vs_menubar::height_request(int w)
+{
+ vs_widget_ref tmpref(this);
+
+ int h=always_visible?1:0;
+
+ for(activemenulist::iterator i=active_menus.begin(), num=0;
+ i!=active_menus.end();
+ i++, num++)
+ h=max(h, 1+(*i)->height_request(w));
+
+ if(subwidget.valid())
+ {
+ int subwidget_h=subwidget->height_request(w);
+
+ if(always_visible)
+ subwidget_h+=1;
+
+ h=max(h, subwidget_h);
+ }
+
+ return h;
+}
+
+void vs_menubar::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ update_x_start();
+
+ // Find the starting X location of each active menu.
+ for(activemenulist::iterator i=active_menus.begin();
+ i!=active_menus.end();
+ i++)
+ {
+ int menuloc = -1;
+
+ for(itemlist::size_type j = 0; j < items.size(); j++)
+ {
+ if(items[j].menu == *i)
+ {
+ menuloc = j;
+ break;
+ }
+ }
+
+ int menux = get_menustart(menuloc);
+
+ int req_w=(*i)->width_request();
+
+ if(menux < 0)
+ menux = 0;
+ else if(getmaxx() < menux + req_w)
+ {
+ if(getmaxx() >= req_w)
+ menux = getmaxx() - req_w;
+ else
+ {
+ menux = 0;
+ req_w = getmaxx();
+ }
+ }
+
+ int req_h = (*i)->height_request(req_w);
+
+ if(getmaxy() < 1 + req_h)
+ req_h = getmaxy()-1;
+
+ (*i)->alloc_size(menux,
+ 1,
+ req_w,
+ req_h);
+ }
+
+ if(subwidget.valid())
+ subwidget->alloc_size(0,
+ always_visible?1:0,
+ getmaxx(),
+ always_visible?getmaxy()-1:getmaxy());
+}
+
+void vs_menubar::show_menu(const vs_menu_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ if(active)
+ {
+ vs_widget_ref old_focus=get_focus();
+
+ for(activemenulist::iterator i=active_menus.begin();
+ i!=active_menus.end();
+ i++)
+ assert(w != *i);
+
+ if(old_focus.valid())
+ old_focus->unfocussed();
+
+ active_menus.push_front(w);
+
+ w->focussed();
+
+ vscreen_queuelayout();
+ vscreen_update();
+ }
+}
+
+void vs_menubar::hide_menu(const vs_menu_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ if(active)
+ {
+ for(activemenulist::iterator i=active_menus.begin();
+ i!=active_menus.end();
+ i++)
+ {
+ if(*i==w)
+ {
+ w->unfocussed();
+ active_menus.remove(w);
+
+ vs_widget_ref new_focus=get_focus();
+ if(new_focus.valid())
+ new_focus->focussed();
+
+ vscreen_queuelayout();
+ vscreen_update();
+ return;
+ }
+ }
+
+ abort();
+ }
+}
+
+void vs_menubar::hide_menu_bare(vs_menu &w)
+{
+ hide_menu(vs_menu_ref(&w));
+}
+
+void vs_menubar::show_menu_bare(vs_menu &w)
+{
+ show_menu(vs_menu_ref(&w));
+}
+
+void vs_menubar::appear()
+{
+ vs_widget_ref tmpref(this);
+
+ if(!active)
+ {
+ active=true;
+ if(subwidget.valid())
+ subwidget->unfocussed();
+
+ // Added to eliminate the weird "titles are selected but contents aren't"
+ // state that was normal with the previous settings:
+ if(items.size()>0)
+ items[curloc].menu->show();
+
+ update_x_start();
+ vscreen_update();
+ }
+}
+
+void vs_menubar::disappear()
+{
+ vs_widget_ref tmpref(this);
+
+ if(active)
+ {
+ while(!active_menus.empty())
+ active_menus.front()->hide();
+
+ active=false;
+ if(subwidget.valid())
+ subwidget->focussed();
+
+ curloc=0;
+
+ vscreen_update();
+ }
+}
+
+bool vs_menubar::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ if(bindings->key_matches(k, "ToggleMenuActive"))
+ {
+ if(active)
+ disappear();
+ else
+ appear();
+ }
+ else if(active)
+ {
+ if(bindings->key_matches(k, "Cancel"))
+ {
+ disappear();
+
+ //if(!active_menus.empty())
+ // active_menus.front()->hide();
+ //else
+ // disappear();
+
+ vscreen_update();
+ }
+ else if(!active_menus.empty())
+ {
+ if(bindings->key_matches(k, "Right"))
+ {
+ if(items.size()>0)
+ {
+ while(!active_menus.empty())
+ active_menus.front()->hide();
+
+ active_menus.clear();
+
+ if(curloc<items.size()-1)
+ curloc++;
+ else
+ curloc=0;
+
+ items[curloc].menu->show();
+
+ update_x_start();
+ vscreen_update();
+ }
+ }
+ else if(bindings->key_matches(k, "Left"))
+ {
+ if(items.size()>0)
+ {
+ while(!active_menus.empty())
+ active_menus.front()->hide();
+
+ active_menus.clear();
+
+ if(curloc>0)
+ curloc--;
+ else
+ curloc=items.size()-1;
+
+ items[curloc].menu->show();
+
+ update_x_start();
+ vscreen_update();
+ }
+ }
+ else if(active_menus.front()->dispatch_key(k))
+ return true;
+ else
+ return vscreen_widget::handle_key(k);
+ }
+ else if(bindings->key_matches(k, "Right"))
+ {
+ if(items.size()>0)
+ {
+ if(curloc<items.size()-1)
+ curloc++;
+ else
+ curloc=0;
+
+ update_x_start();
+ vscreen_update();
+ }
+ }
+ else if(bindings->key_matches(k, "Left"))
+ {
+ if(items.size()>0)
+ {
+ if(curloc>0)
+ curloc--;
+ else
+ curloc=items.size()-1;
+
+ update_x_start();
+ vscreen_update();
+ }
+ }
+ else if(bindings->key_matches(k, "Down") ||
+ bindings->key_matches(k, "Confirm"))
+ {
+ if(items.size()>0)
+ items[curloc].menu->show();
+ }
+ else
+ return vscreen_widget::handle_key(k);
+
+ return true;
+ }
+ else if(subwidget.valid() && subwidget->dispatch_key(k))
+ return true;
+ else
+ return vscreen_widget::handle_key(k);
+
+ return true;
+}
+
+void vs_menubar::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ if(subwidget.valid())
+ subwidget->display(st);
+
+ if(active || always_visible)
+ {
+ const style menubar_style=get_style("MenuBar");
+ const style highlightedmenubar_style=get_style("HighlightedMenuBar");
+
+ if(active)
+ for(activemenulist::reverse_iterator i=active_menus.rbegin();
+ i!=active_menus.rend();
+ i++)
+ (*i)->display(st);
+
+ int pos=0, maxx=getmaxx();
+
+ apply_style(menubar_style);
+ move(0, 0);
+ for(int i=0; i<maxx; i+=wcwidth(L' '))
+ add_wch(L' ');
+
+ move(0, 0);
+
+ itemlist::size_type i;
+ for(i = startloc; i < items.size() && pos < maxx; ++i)
+ {
+ if(active && i==curloc)
+ apply_style(highlightedmenubar_style);
+ else
+ apply_style(menubar_style);
+
+ wstring &title = items[i].title;
+ size_t titleloc = 0;
+
+ while(titleloc < title.size() && pos < maxx)
+ {
+ wchar_t wch=title[titleloc];
+
+ add_wch(wch);
+ pos+=wcwidth(wch);
+ ++titleloc;
+ }
+ }
+
+ apply_style(menubar_style);
+
+ if(startloc > 0)
+ mvadd_wch(0, 0, WACS_LARROW);
+ if(i < items.size() || pos >= maxx)
+ mvadd_wch(0, maxx-1, WACS_RARROW);
+ }
+}
+
+void vs_menubar::dispatch_mouse(short id, int x, int y, int z, mmask_t bmask)
+{
+ vs_widget_ref tmpref(this);
+
+ if(y==0)
+ {
+ if(bmask & (BUTTON1_CLICKED | BUTTON2_CLICKED |
+ BUTTON3_CLICKED | BUTTON4_CLICKED |
+ BUTTON1_RELEASED | BUTTON2_RELEASED |
+ BUTTON3_RELEASED | BUTTON4_RELEASED |
+ BUTTON1_PRESSED | BUTTON2_PRESSED |
+ BUTTON3_PRESSED | BUTTON4_PRESSED))
+ {
+ if(!active)
+ appear();
+
+ int loc=0;
+ activemenulist::size_type i=0;
+
+ if(items.size()>0)
+ {
+ loc+=items[0].title.size();
+
+ while(i<items.size()-1 && loc<=x)
+ {
+ loc+=items[i+1].title.size();
+
+ ++i;
+ }
+ }
+
+ if(i<items.size())
+ {
+ while(!active_menus.empty())
+ active_menus.front()->hide();
+
+ active_menus.clear();
+
+ curloc=i;
+
+ items[curloc].menu->show();
+
+ vscreen_update();
+ }
+ }
+ }
+ else if(active)
+ {
+ for(activemenulist::iterator i=active_menus.begin();
+ i!=active_menus.end();
+ i++)
+ if((*i)->enclose(y, x))
+ {
+ (*i)->dispatch_mouse(id,
+ x-(*i)->get_startx(), y-(*i)->get_starty(), z,
+ bmask);
+ return;
+ }
+ }
+
+ if(subwidget.valid())
+ subwidget->dispatch_mouse(id,
+ x-subwidget->get_startx(),
+ y-subwidget->get_starty(), z, bmask);
+}
+
+void vs_menubar::init_bindings()
+{
+ bindings=new keybindings(&global_bindings);
+}
+
+void vs_menubar::set_always_visible(bool _always_visible)
+{
+ if(_always_visible!=always_visible)
+ {
+ always_visible=_always_visible;
+ vscreen_update();
+ vscreen_queuelayout();
+ }
+}
diff --git a/src/vscreen/vs_menubar.h b/src/vscreen/vs_menubar.h
new file mode 100644
index 00000000..e53fbc69
--- /dev/null
+++ b/src/vscreen/vs_menubar.h
@@ -0,0 +1,152 @@
+// vs_menubar.h -*-c++-*-
+//
+// Copyright (C) 2000-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Provides a horizontal menubar and a space for submenus. This widget and
+// its menus are superimposed on top of another widget.
+
+#ifndef VS_MENUBAR_H
+#define VS_MENUBAR_H
+
+#include "vscreen_widget.h"
+#include "vs_container.h"
+#include "config/keybindings.h"
+
+#include <string>
+#include <vector>
+
+class vs_menu;
+
+typedef ref_ptr<vs_menu> vs_menu_ref;
+
+class vs_menubar:public vs_container
+{
+ struct item
+ {
+ std::wstring title;
+ ref_ptr<vs_menu> menu;
+
+ item(std::wstring _title, ref_ptr<vs_menu> _menu)
+ :title(_title), menu(_menu)
+ {
+ }
+ };
+
+ typedef std::vector<item> itemlist;
+ typedef std::list<vs_widget_ref> activemenulist;
+
+ // A list of the items in the menubar itself
+ itemlist items;
+ // A list of active menus
+ activemenulist active_menus;
+
+ /** The index of the leftmost visible menu item. */
+ itemlist::size_type startloc;
+
+ // True if the menu-bar is visible and/or being used
+ bool active;
+
+ // True if the menu-bar should always be visible
+ bool always_visible;
+
+ /** The index of the currently selected item. */
+ itemlist::size_type curloc;
+
+ // the widget underneath this one.
+ vs_widget_ref subwidget;
+
+ // Returns the starting X location of the given item in the menu
+ int get_menustart(itemlist::size_type idx) const;
+
+ /** Re-calculates the starting X location, moving it left or right
+ * if needed.
+ */
+ void update_x_start();
+
+ // Show/hide menus
+ void show_menu(const vs_menu_ref &w);
+ void show_menu_bare(vs_menu &w);
+
+ void hide_menu(const vs_menu_ref &w);
+ void hide_menu_bare(vs_menu &w);
+
+ void appear();
+ void disappear();
+
+ // Similar to the passthrough widget's routine (there's not enough
+ // similarity, though, to justify making this a passthrough widget)
+ vs_widget_ref get_focus();
+
+ void got_focus();
+ void lost_focus();
+protected:
+ virtual bool handle_key(const key &k);
+
+ vs_menubar(bool _always_visible);
+public:
+ static ref_ptr<vs_menubar> create(bool always_visible = true)
+ {
+ ref_ptr<vs_menubar> rval(new vs_menubar(always_visible));
+ rval->decref();
+ return rval;
+ }
+
+ ~vs_menubar();
+
+ /** The 'active' widget of a menubar is always its subwidget. */
+ vs_widget_ref get_active_widget();
+
+ void destroy();
+
+ int width_request();
+ int height_request(int w);
+ void layout_me();
+
+ void set_subwidget(const vs_widget_ref &w);
+
+ void append_item(const std::wstring &title, const vs_menu_ref &menu);
+ void append_item(const std::wstring &title, vs_menu &menu)
+ {
+ append_item(title, vs_menu_ref(&menu));
+ }
+
+ void show_all();
+
+ /** Add a widget as the new subwidget, like a bin. */
+ void add_widget(const vs_widget_ref &w);
+ /** Remove the subwidget OR a menu. */
+ void rem_widget(const vs_widget_ref &w);
+
+ virtual void paint(const style &st);
+ virtual bool focus_me();
+ virtual void dispatch_mouse(short id, int x, int y, int z,
+ mmask_t bmask);
+
+ bool get_cursorvisible();
+ point get_cursorloc();
+
+ bool get_always_visible() {return always_visible;}
+ void set_always_visible(bool _always_visible);
+
+ static keybindings *bindings;
+ static void init_bindings();
+};
+
+typedef ref_ptr<vs_menubar> vs_menubar_ref;
+
+#endif
diff --git a/src/vscreen/vs_minibuf_win.cc b/src/vscreen/vs_minibuf_win.cc
new file mode 100644
index 00000000..2f8b1dcf
--- /dev/null
+++ b/src/vscreen/vs_minibuf_win.cc
@@ -0,0 +1,230 @@
+// vs_minibuf_win.cc
+//
+// Copyright 2000-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "vs_minibuf_win.h"
+#include "vs_label.h"
+#include "vs_multiplex.h"
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+using namespace std;
+
+vs_minibuf_win::vs_minibuf_win()
+ :vs_passthrough(), main_widget(NULL)
+{
+ do_layout.connect(sigc::mem_fun(*this, &vs_minibuf_win::layout_me));
+
+ status=vs_multiplex::create();
+ status_lbl=vs_label::create("");
+ status_lbl->set_bg_style(get_style("Status"));
+ status->add_widget(status_lbl);
+ header=vs_label::create("");
+ header->set_bg_style(get_style("Header"));
+
+ status->set_owner(this);
+ header->set_owner(this);
+ status_lbl->show();
+ status->show();
+ header->show();
+}
+
+vs_minibuf_win::~vs_minibuf_win()
+{
+}
+
+void vs_minibuf_win::destroy()
+{
+ vs_widget_ref tmpref(this);
+
+ if(main_widget.valid())
+ main_widget->destroy();
+ assert(!main_widget.valid());
+
+ header->destroy();
+ status->destroy();
+
+ assert(!header.valid());
+ assert(!status.valid());
+
+ vs_container::destroy();
+}
+
+void vs_minibuf_win::set_main_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ defocus();
+
+ if(main_widget.valid())
+ {
+ main_destroy_conn.disconnect();
+ main_widget->set_owner(NULL);
+ }
+
+ main_widget=w;
+
+ if(main_widget.valid())
+ {
+ main_widget->set_owner(this);
+ main_destroy_conn=main_widget->destroyed.connect(sigc::bind(sigc::mem_fun(*this, &vs_minibuf_win::set_main_widget), (vscreen_widget *) NULL));
+ }
+ refocus();
+
+ vscreen_queuelayout();
+ vscreen_update();
+}
+
+int vs_minibuf_win::width_request()
+{
+ vs_widget_ref tmpref(this);
+
+ int w=0;
+
+ if(status.valid())
+ w=max(w, status->width_request());
+
+ if(header.valid())
+ w=max(w, header->width_request());
+
+ if(main_widget.valid())
+ w=max(w, main_widget->width_request());
+
+ return w;
+}
+
+int vs_minibuf_win::height_request(int w)
+{
+ vs_widget_ref tmpref(this);
+
+ int h=2;
+
+ if(main_widget.valid())
+ h=max(h, main_widget->height_request(w));
+ return h;
+}
+
+void vs_minibuf_win::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ if(header.valid())
+ header->alloc_size(0, 0, getmaxx(), 1);
+
+ if(getmaxy()>1)
+ {
+ if(getmaxy()>2 && main_widget.valid())
+ main_widget->alloc_size(0, 1, getmaxx(), getmaxy()-2);
+
+ if(status.valid())
+ status->alloc_size(0, getmaxy()-1, getmaxx(), 1);
+ }
+}
+
+void vs_minibuf_win::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ if(main_widget.valid() && main_widget->get_visible())
+ main_widget->display(st);
+
+ if(status.valid())
+ status->display(st);
+ if(header.valid())
+ header->display(st);
+}
+
+void vs_minibuf_win::set_header(string new_header)
+{
+ vs_widget_ref tmpref(this);
+
+ header->set_text(new_header);
+}
+
+void vs_minibuf_win::set_status(string new_status)
+{
+ vs_widget_ref tmpref(this);
+
+ status_lbl->set_text(new_status);
+}
+
+void vs_minibuf_win::add_widget(const vs_widget_ref &widget)
+{
+ vs_widget_ref tmpref(this);
+
+ defocus();
+ status->add_widget(widget);
+ refocus();
+}
+
+void vs_minibuf_win::rem_widget(const vs_widget_ref &widget)
+{
+ vs_widget_ref tmpref(this);
+
+ assert(widget.valid());
+
+ if(widget == header)
+ {
+ header->set_owner(NULL);
+ header = NULL;
+ }
+ else if(widget == status)
+ {
+ status->set_owner(NULL);
+ status = NULL;
+ }
+ else if(widget == main_widget)
+ {
+ main_widget->set_owner(NULL);
+ main_widget = NULL;
+ }
+ else
+ {
+ defocus();
+ status->rem_widget(widget);
+ refocus();
+ }
+}
+
+void vs_minibuf_win::show_all()
+{
+ vs_widget_ref tmpref(this);
+
+ if(main_widget.valid())
+ main_widget->show_all();
+ status->show();
+ header->show();
+}
+
+vs_widget_ref vs_minibuf_win::get_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ if(status.valid() && status->focus_me())
+ return status;
+ else if(main_widget.valid() && main_widget->get_visible() && main_widget->focus_me())
+ return main_widget;
+ else
+ return NULL;
+}
+
+void vs_minibuf_win::display_error(string err)
+{
+ add_widget(vs_transientlabel::create(err, get_style("Error")));
+}
diff --git a/src/vscreen/vs_minibuf_win.h b/src/vscreen/vs_minibuf_win.h
new file mode 100644
index 00000000..998a4d0c
--- /dev/null
+++ b/src/vscreen/vs_minibuf_win.h
@@ -0,0 +1,104 @@
+// vs_minibuf_win.h -*-c++-*-
+//
+// Copyright 2000-2001, 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Apologies for the lame name, but I couldn't think of anything else.
+//
+// This class provides basic support for a common UI theme: a widget with a
+// header and status line, where the status line can contain various widgets
+// for getting input, displaying messages, etc. (the header line will probably
+// vanish in the future)
+
+#ifndef VS_MINIBUF_WIN_H
+#define VS_MINIBUF_WIN_H
+
+#include "vscreen.h"
+#include "vscreen_widget.h"
+#include "vs_passthrough.h"
+#include "config/style.h"
+
+#include <list>
+
+#include <sigc++/connection.h>
+
+class vs_minibuf;
+class vs_label;
+class vs_multiplex;
+
+class vs_minibuf_win:public vs_passthrough
+{
+ ref_ptr<vs_label> status_lbl, header;
+
+ vs_widget_ref main_widget;
+ // This is displayed in the center of the screen.
+
+ ref_ptr<vs_multiplex> status;
+
+ sigc::connection main_destroy_conn;
+
+protected:
+ vs_minibuf_win();
+public:
+ static
+ ref_ptr<vs_minibuf_win> create()
+ {
+ ref_ptr<vs_minibuf_win> rval = new vs_minibuf_win;
+ rval->decref();
+ return rval;
+ }
+
+ ~vs_minibuf_win();
+
+ void destroy();
+
+ void set_main_widget(const vs_widget_ref &w);
+
+ int width_request();
+ int height_request(int w);
+ void layout_me();
+
+ virtual void paint(const style &st);
+
+ void show_header();
+ void show_status();
+ // Should mainly be used if it's necessary to force an update of the status
+ // line
+
+ void set_status(std::string new_status);
+ void set_header(std::string new_header);
+ // Set the status and header lines of the window, respectively.
+ // These routines do NOT call refresh()!
+
+ vs_widget_ref get_focus();
+
+ static const style &retr_header_style() {return get_style("Header");}
+ static const style &retr_status_style() {return get_style("Status");}
+ void display_error(std::string err);
+
+ void add_widget(const vs_widget_ref &widget);
+ // Adds a widget. Widgets are automatically shown if they're the first
+ // one to be added, otherwise show() should be called.
+ void rem_widget(const vs_widget_ref &widget);
+ // Removes a widget
+
+ void show_all();
+};
+
+typedef ref_ptr<vs_minibuf_win> vs_minibuf_win_ref;
+
+#endif
diff --git a/src/vscreen/vs_multiplex.cc b/src/vscreen/vs_multiplex.cc
new file mode 100644
index 00000000..5860b054
--- /dev/null
+++ b/src/vscreen/vs_multiplex.cc
@@ -0,0 +1,582 @@
+// vs_multiplex.cc
+//
+// Copyright 1999-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+#include "vscreen.h"
+#include "vs_multiplex.h"
+
+#include "config/colors.h"
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+#include <assert.h>
+
+using namespace std;
+
+vs_multiplex::vs_multiplex(bool _show_tabs)
+ :vs_passthrough(), visible_child(children.end()), show_tabs(_show_tabs)
+{
+ do_layout.connect(sigc::mem_fun(*this, &vs_multiplex::layout_me));
+
+ focussed.connect(sigc::mem_fun(*this, &vs_multiplex::got_focus));
+ unfocussed.connect(sigc::mem_fun(*this, &vs_multiplex::lost_focus));
+}
+
+vs_multiplex::~vs_multiplex()
+{
+ assert(children.empty());
+}
+
+void vs_multiplex::destroy()
+{
+ vs_widget_ref tmpref(this);
+
+ while(!children.empty())
+ children.front().w->destroy();
+
+ vs_passthrough::destroy();
+}
+
+bool vs_multiplex::tabs_visible() const
+{
+ if(!show_tabs)
+ return false;
+
+ bool one_visible=false;
+
+ for(list<child_info>::const_iterator i=children.begin();
+ i!=children.end();
+ i++)
+ if(i->w->get_visible())
+ {
+ if(!one_visible)
+ one_visible=true;
+ else
+ return true;
+ }
+
+ return false;
+}
+
+int vs_multiplex::width_request()
+{
+ vs_widget_ref tmpref(this);
+
+ int rval=0;
+
+ for(list<child_info>::iterator i=children.begin();
+ i!=children.end(); ++i)
+ if(i->w->get_visible())
+ rval=max(rval, i->w->width_request());
+
+ return rval;
+}
+
+int vs_multiplex::height_request(int width)
+{
+ vs_widget_ref tmpref(this);
+
+ int rval=0;
+
+ for(list<child_info>::iterator i=children.begin();
+ i!=children.end(); ++i)
+ if(i->w->get_visible())
+ rval=max(rval, i->w->height_request(width));
+
+ if(tabs_visible())
+ return rval+1;
+ else
+ return rval;
+}
+
+
+void vs_multiplex::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ if(visible_child!=children.end())
+ {
+ if(tabs_visible())
+ visible_child->w->alloc_size(0, 1, getmaxx(), getmaxy()-1);
+ else
+ visible_child->w->alloc_size(0, 0, getmaxx(), getmaxy());
+ }
+}
+
+void vs_multiplex::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ if(tabs_visible())
+ {
+ int visible_children=0;
+
+ for(list<child_info>::iterator i=children.begin();
+ i!=children.end(); ++i)
+ if(i->w->get_visible())
+ ++visible_children;
+
+ assert(visible_children>0);
+
+ int remaining_w=getmaxx();
+ move(0, 0);
+
+ const style tab_style=get_style("MultiplexTab");
+ const style tabhighlighted_style=get_style("MultiplexTabHighlighted");
+
+ for(list<child_info>::iterator i=children.begin();
+ i!=children.end(); ++i)
+ if(i->w->get_visible()) // draw one
+ {
+ if(i == visible_child)
+ apply_style(tabhighlighted_style);
+ else
+ apply_style(tab_style);
+
+ int thisw=remaining_w/visible_children;
+ --visible_children;
+ remaining_w-=thisw;
+
+ const wstring &title = i->title;
+ int titlew = wcswidth(title.c_str(), title.size());
+ unsigned int leftpadding = (titlew<=thisw) ? (thisw-titlew)/2 : 0;
+
+ while(leftpadding>0)
+ {
+ add_wch(L' ');
+ int chw=wcwidth(L' ');
+
+ leftpadding-=chw;
+ thisw-=chw;
+ }
+
+ size_t loc=0;
+ while(thisw>0 && loc<title.size())
+ {
+ wchar_t ch=title[loc];
+ add_wch(ch);
+ thisw-=wcwidth(ch);
+ ++loc;
+ }
+
+ while(thisw>0)
+ {
+ add_wch(L' ');
+ thisw-=wcwidth(L' ');
+ }
+ }
+ assert(visible_children == 0);
+ }
+
+ if(visible_child!=children.end())
+ visible_child->w->display(st);
+}
+
+void vs_multiplex::dispatch_mouse(short id, int x, int y, int z,
+ mmask_t bstate)
+{
+ vs_widget_ref tmpref(this);
+
+ if(tabs_visible() && y == 0)
+ {
+ // FIXME: duplicated code from above, is there a good way to
+ // unduplicate it? (eg, an "iterator" for child locations)
+ int visible_children=0;
+
+ for(list<child_info>::iterator i=children.begin();
+ i!=children.end(); ++i)
+ if(i->w->get_visible())
+ ++visible_children;
+
+ assert(visible_children>0);
+
+ int startx=0;
+ int remaining_w=getmaxx();
+
+ for(list<child_info>::iterator i=children.begin();
+ i!=children.end(); ++i)
+ if(i->w->get_visible())
+ {
+ int thisw=remaining_w/visible_children;
+ --visible_children;
+ remaining_w-=thisw;
+
+ if(x>=startx && x<startx+thisw)
+ {
+ visible_child=i;
+ vscreen_queuelayout();
+ return;
+ }
+
+ startx+=thisw;
+ }
+
+ assert(x<0 || x>=getmaxx());
+ assert(visible_children == 0);
+ }
+ else if(visible_child!=children.end())
+ visible_child->w->dispatch_mouse(id,
+ x-visible_child->w->get_startx(),
+ y-visible_child->w->get_starty(),
+ z, bstate);
+}
+
+void vs_multiplex::got_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ if(visible_child!=children.end())
+ visible_child->w->focussed();
+}
+
+void vs_multiplex::lost_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ if(visible_child!=children.end())
+ visible_child->w->unfocussed();
+}
+
+void vs_multiplex::show_all()
+{
+ vs_widget_ref tmpref(this);
+
+ show();
+
+ if(visible_child!=children.end())
+ visible_child->w->show_all();
+}
+
+void vs_multiplex::show_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ assert(!children.empty());
+
+ list<child_info>::iterator new_visible=visible_child;
+
+ if(new_visible!=children.end())
+ ++new_visible;
+ else
+ new_visible=children.begin();
+
+ while(new_visible!=visible_child)
+ {
+ if(new_visible==children.end())
+ new_visible=children.begin();
+ else if(new_visible->w==w)
+ break;
+ else
+ ++new_visible;
+ }
+
+ if(visible_child!=children.end() && get_isfocussed())
+ visible_child->w->unfocussed();
+
+ list<child_info>::iterator old_visible = visible_child;
+ visible_child=new_visible;
+
+ if(visible_child!=children.end() && get_isfocussed())
+ visible_child->w->focussed();
+
+ if(visible_child != old_visible)
+ {
+ cycled();
+ vscreen_queuelayout();
+ vscreen_update();
+ }
+}
+
+void vs_multiplex::hide_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ assert(!children.empty());
+
+ if(visible_child!=children.end() && visible_child->w==w)
+ {
+ list<child_info>::iterator new_visible=visible_child;
+
+ if(new_visible!=children.begin())
+ --new_visible;
+ else
+ {
+ new_visible=children.end();
+ --new_visible;
+ }
+
+ while(new_visible!=visible_child)
+ {
+ if(new_visible->w->get_visible())
+ break;
+ else if(new_visible==children.begin())
+ {
+ new_visible=children.end();
+ --new_visible;
+ }
+ else
+ --new_visible;
+ }
+
+ if(visible_child!=children.end() && get_isfocussed())
+ visible_child->w->unfocussed();
+
+ list<child_info>::iterator old_visible = visible_child;
+
+ if(new_visible==visible_child)
+ visible_child=children.end();
+ else
+ visible_child=new_visible;
+
+ if(visible_child!=children.end() && get_isfocussed())
+ visible_child->w->focussed();
+
+
+ // Since we just hid the previously-visible child, this MUST be
+ // the case.
+ assert(visible_child != old_visible);
+
+ cycled();
+ vscreen_queuelayout();
+ vscreen_update();
+ }
+}
+
+void vs_multiplex::show_widget_bare(vscreen_widget &w)
+{
+ show_widget(vs_widget_ref(&w));
+}
+
+void vs_multiplex::hide_widget_bare(vscreen_widget &w)
+{
+ hide_widget(vs_widget_ref(&w));
+}
+
+vs_widget_ref vs_multiplex::get_focus()
+{
+ return visible_child==children.end()?NULL:visible_child->w;
+}
+
+void vs_multiplex::rem_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ hide_widget(w);
+
+ for(list<child_info>::iterator i=children.begin(); i!=children.end(); i++)
+ {
+ if(i->w==w)
+ {
+ assert(w->get_owner().unsafe_get_ref() == this);
+
+ w->set_owner(NULL);
+
+ break;
+ }
+ }
+
+ list<child_info>::iterator i=children.begin(),j;
+ while(i!=children.end())
+ {
+ j=i;
+ ++j;
+
+ if(i->w==w)
+ children.erase(i);
+
+ i=j;
+ }
+
+ vscreen_queuelayout();
+ vscreen_update();
+}
+
+void vs_multiplex::add_widget(const vs_widget_ref &w,
+ const wstring &title)
+{
+ vs_widget_ref tmpref(this);
+
+ w->shown_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_multiplex::show_widget_bare), w.weak_ref()));
+ w->hidden_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_multiplex::hide_widget_bare), w.weak_ref()));
+
+ children.push_back(child_info(w, title));
+ w->set_owner(this);
+
+ if(w->get_visible())
+ show_widget(w);
+}
+
+void vs_multiplex::add_widget(const vs_widget_ref &w)
+{
+ add_widget(w, L"Untitled");
+}
+
+void vs_multiplex::add_widget_after(const vs_widget_ref &w,
+ const vs_widget_ref &after,
+ const wstring &title)
+{
+ vs_widget_ref tmpref(this);
+
+ for(list<child_info>::iterator i=children.begin();
+ i!=children.end();
+ i++)
+ {
+ if(i->w==after)
+ {
+ ++i;
+
+ w->shown_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_multiplex::show_widget_bare), w.weak_ref()));
+ w->hidden_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_multiplex::hide_widget_bare), w.weak_ref()));
+
+ children.insert(i, child_info(w, title));
+ w->set_owner(this);
+
+ if(w->get_visible())
+ show_widget(w);
+
+ return;
+ }
+ }
+
+ // Fallback to something reasonable. (or just abort? dunno)
+ add_widget(w);
+}
+
+void vs_multiplex::add_widget_after(const vs_widget_ref &w,
+ const vs_widget_ref &after)
+{
+ add_widget_after(w, after, L"Untitled");
+}
+
+void vs_multiplex::cycle_forward()
+{
+ vs_widget_ref tmpref(this);
+
+ if(!children.empty())
+ {
+ list<child_info>::iterator new_visible=visible_child;
+
+ if(new_visible!=children.end())
+ ++new_visible;
+ else
+ new_visible=children.begin();
+
+ while(new_visible!=visible_child)
+ {
+ if(new_visible==children.end())
+ new_visible=children.begin();
+ else if(new_visible->w->get_visible())
+ break;
+ else
+ ++new_visible;
+ }
+
+ list<child_info>::iterator old_visible = visible_child;
+
+ if(visible_child!=children.end() && get_isfocussed())
+ visible_child->w->unfocussed();
+
+ visible_child=new_visible;
+
+ if(visible_child!=children.end() && get_isfocussed())
+ visible_child->w->focussed();
+
+
+ if(visible_child != old_visible)
+ {
+ cycled();
+ vscreen_queuelayout();
+ vscreen_update();
+ }
+ }
+}
+
+void vs_multiplex::cycle_backward()
+{
+ vs_widget_ref tmpref(this);
+
+ if(!children.empty())
+ {
+ list<child_info>::iterator new_visible=visible_child;
+
+ if(new_visible!=children.begin())
+ --new_visible;
+ else
+ {
+ new_visible=children.end();
+ --new_visible;
+ }
+
+ while(new_visible!=visible_child)
+ {
+ if(new_visible->w->get_visible())
+ break;
+ else if(new_visible==children.begin())
+ {
+ new_visible=children.end();
+ --new_visible;
+ }
+ else
+ --new_visible;
+ }
+
+ list<child_info>::iterator old_visible = visible_child;
+
+ if(visible_child!=children.end() && get_isfocussed())
+ visible_child->w->unfocussed();
+
+ visible_child=new_visible;
+
+ if(visible_child!=children.end() && get_isfocussed())
+ visible_child->w->focussed();
+
+ if(visible_child != old_visible)
+ {
+ cycled();
+
+ vscreen_queuelayout();
+ vscreen_update();
+ }
+ }
+}
+
+vs_widget_ref vs_multiplex::visible_widget()
+{
+ if(visible_child!=children.end())
+ return visible_child->w;
+ else
+ return NULL;
+}
+
+unsigned int vs_multiplex::num_children()
+{
+ return children.size();
+}
+
+unsigned int vs_multiplex::num_visible()
+{
+ unsigned int n=0;
+
+ for(list<child_info>::iterator i=children.begin();
+ i!=children.end(); ++i)
+ if(i->w->get_visible())
+ ++n;
+
+ return n;
+}
diff --git a/src/vscreen/vs_multiplex.h b/src/vscreen/vs_multiplex.h
new file mode 100644
index 00000000..77f70041
--- /dev/null
+++ b/src/vscreen/vs_multiplex.h
@@ -0,0 +1,161 @@
+// vs_multiplex.h (This is -*-c++-*-)
+// Copyright 1999-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// This file defines a widget with the behavior of the old vscreen class.
+// In essence, this widget can multiplex multiple sub-widgets. When a
+// subwidget emits the "hide" signal, the vscreen does what vscreen_hide()
+// used to do. "shown" is actually emitted by a vscreen when a particular
+// subwidget becomes visible. (strange but true)
+// (you can no longer show/hide at a particular point in the stack, at least
+// not right now. I don't think there's much point)
+
+#ifndef VSMULTIPLEX_H
+#define VSMULTIPLEX_H
+
+#include "curses++.h"
+#include "vs_passthrough.h"
+
+#include <assert.h>
+
+#include <list>
+#include <string>
+
+/** This widget displays exactly one of its children at once. It is
+ * the same size as its currently active child (plus an optional "tab
+ * bar" displaying all its children).
+ */
+class vs_multiplex:public vs_passthrough
+{
+ struct child_info
+ {
+ vs_widget_ref w;
+ std::wstring title;
+
+ child_info(const vs_widget_ref &_w, const std::wstring &_title)
+ :w(_w), title(_title)
+ {
+ }
+ };
+
+ std::list<child_info> children;
+
+ std::list<child_info>::iterator visible_child;
+
+ /** If \b true and more than one child is visible, "tabs" are
+ * displayed at the top of the widget, corresponding to the visible
+ * children.
+ */
+ bool show_tabs;
+
+ /** \return \b true if show_tabs is \b true and more than one
+ * child is visible.
+ */
+ bool tabs_visible() const;
+
+ void show_widget(const vs_widget_ref &widget);
+ // Used to bring a widget to the front
+ void hide_widget(const vs_widget_ref &widget);
+ // Used to hide a widget
+
+ void show_widget_bare(vscreen_widget &widget);
+ void hide_widget_bare(vscreen_widget &widget);
+
+ void got_focus();
+ void lost_focus();
+protected:
+ bool winavail() {return get_win();}
+
+ vs_multiplex(bool _show_tabs);
+public:
+ static ref_ptr<vs_multiplex> create(bool show_tabs = false)
+ {
+ ref_ptr<vs_multiplex> rval(new vs_multiplex(show_tabs));
+ rval->decref();
+ return rval;
+ }
+
+ virtual ~vs_multiplex();
+
+ /** Returns the maximum width requested by any child. */
+ int width_request();
+
+ /** Returns the maximum height requested by any child. */
+ int height_request(int width);
+
+ void destroy();
+
+ void layout_me();
+
+ virtual vs_widget_ref get_focus();
+ vs_widget_ref visible_widget();
+ unsigned int num_children();
+ // Returns the number of widgets in the multiplexer.
+ unsigned int num_visible();
+
+ virtual void paint(const style &st);
+ void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+ void show_all();
+
+ /** Add a title-less widget. Provided to implement a required
+ * function and for backwards compatibility; use of this routine is
+ * deprecated.
+ */
+ void add_widget(const vs_widget_ref &widget);
+ void add_widget(const vs_widget_ref &widget, const std::wstring &title);
+ void add_widget_bare(vscreen_widget &widget, const std::wstring &title)
+ {
+ add_widget(vs_widget_ref(&widget), title);
+ }
+
+ void add_widget_after(const vs_widget_ref &widget,
+ const vs_widget_ref &after);
+
+ void add_widget_after_bare(vscreen_widget &widget,
+ vscreen_widget &after)
+ {
+ add_widget_after(vs_widget_ref(&widget), vs_widget_ref(&after));
+ }
+
+
+ void add_widget_after(const vs_widget_ref &widget,
+ const vs_widget_ref &after,
+ const std::wstring &title);
+
+
+ void add_widget_after_bare(vscreen_widget &widget,
+ vscreen_widget &after,
+ const std::wstring &title)
+ {
+ add_widget_after(vs_widget_ref(&widget), vs_widget_ref(&after), title);
+ }
+
+
+ void rem_widget(const vs_widget_ref &widget);
+
+ // These cycle forward and backwards through the list of visible items.
+ void cycle_forward();
+ void cycle_backward();
+
+ /** Emitted when the currently visible widget changes. */
+ sigc::signal0<void> cycled;
+};
+
+typedef ref_ptr<vs_multiplex> vs_multiplex_ref;
+
+#endif
diff --git a/src/vscreen/vs_pager.cc b/src/vscreen/vs_pager.cc
new file mode 100644
index 00000000..c1445d24
--- /dev/null
+++ b/src/vscreen/vs_pager.cc
@@ -0,0 +1,443 @@
+// vs_pager.cc
+//
+// Copyright 2000 Daniel Burrows
+
+#include "vs_pager.h"
+
+// For _()
+#include "../aptitude.h"
+
+#include "transcode.h"
+#include "vscreen.h"
+#include "vs_minibuf_win.h"
+#include "vs_editline.h"
+#include "config/keybindings.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+
+#include <sigc++/functors/mem_fun.h>
+
+using namespace std;
+
+keybindings *vs_pager::bindings=NULL;
+
+vs_pager::vs_pager(const char *text, int len, const char *encoding)
+ :vscreen_widget(), first_line(0), first_column(0), text_width(0)
+{
+ set_text(text, len, encoding);
+
+ do_layout.connect(sigc::mem_fun(*this, &vs_pager::layout_me));
+}
+
+vs_pager::vs_pager(const string &s, const char *encoding)
+ :vscreen_widget(), first_line(0), first_column(0), text_width(0)
+{
+ set_text(s, encoding);
+
+ do_layout.connect(sigc::mem_fun(*this, &vs_pager::layout_me));
+}
+
+vs_pager::vs_pager(const wstring &s)
+ :vscreen_widget(), first_line(0), first_column(0), text_width(0)
+{
+ set_text(s);
+
+ do_layout.connect(sigc::mem_fun(*this, &vs_pager::layout_me));
+}
+
+vs_pager::~vs_pager() {}
+
+void vs_pager::set_text(const string &s, const char *encoding)
+{
+ set_text(transcode(s, encoding));
+}
+
+void vs_pager::set_text(const char *txt, string::size_type len,
+ const char *encoding)
+{
+ // FIXME: get rid of the intermediate copy of the data if possible.
+ set_text(transcode(string(txt, len), encoding));
+}
+
+void vs_pager::set_text(const wstring &s)
+{
+ vs_widget_ref tmpref(this);
+
+ wstring::size_type loc=0;
+
+ text_width=0;
+
+ lines.clear();
+
+ while(loc<s.size())
+ {
+ wstring curline;
+ col_count cur_width=0;
+
+ while(loc<s.size() && s[loc]!=L'\n')
+ {
+ wchar_t ch=s[loc];
+ bool printable=iswprint(ch);
+
+ if(ch==L'\t')
+ cur_width+=8;
+ else if(printable)
+ cur_width+=wcwidth(ch);
+
+ if(printable)
+ curline+=ch;
+
+ ++loc;
+ }
+
+ if(loc<s.size())
+ ++loc;
+
+ text_width=max(cur_width, text_width);
+
+ lines.push_back(curline);
+ }
+
+ // Bouncing to the start is easiest.
+ first_line=0;
+ first_column=0;
+
+ do_line_signal();
+ vscreen_queuelayout();
+ vscreen_redraw();
+}
+
+void vs_pager::do_line_signal()
+{
+ vs_widget_ref tmpref(this);
+
+ int realmax=max<int>(lines.size()-getmaxy(), 0);
+ line_changed(first_line, realmax);
+}
+
+void vs_pager::do_column_signal()
+{
+ vs_widget_ref tmpref(this);
+
+ int realmax=max<int>(text_width-getmaxx(), 0);
+ column_changed(first_column, realmax);
+}
+
+void vs_pager::scroll_up(line_count nlines)
+{
+ vs_widget_ref tmpref(this);
+
+ if(first_line<nlines)
+ first_line=0;
+ else
+ first_line-=nlines;
+
+ do_line_signal();
+ vscreen_update();
+}
+
+void vs_pager::scroll_down(line_count nlines)
+{
+ vs_widget_ref tmpref(this);
+
+ first_line=min(first_line+nlines, lines.size()-getmaxy());
+
+ do_line_signal();
+ vscreen_update();
+}
+
+void vs_pager::scroll_left(col_count ncols)
+{
+ vs_widget_ref tmpref(this);
+
+ if(first_column<ncols)
+ first_column=0;
+ else
+ first_column-=ncols;
+
+ do_column_signal();
+ vscreen_update();
+}
+
+void vs_pager::scroll_right(col_count ncols)
+{
+ vs_widget_ref tmpref(this);
+
+ first_column=min(first_column+ncols, text_width-getmaxx());
+
+ do_column_signal();
+ vscreen_update();
+}
+
+void vs_pager::scroll_top()
+{
+ vs_widget_ref tmpref(this);
+
+ first_line=0;
+
+ do_line_signal();
+ vscreen_update();
+}
+
+void vs_pager::scroll_bottom()
+{
+ vs_widget_ref tmpref(this);
+
+ first_line=lines.size()-getmaxy();
+
+ do_line_signal();
+ vscreen_update();
+}
+
+void vs_pager::scroll_page(bool dir)
+{
+ vs_widget_ref tmpref(this);
+
+ if(dir)
+ scroll_up(getmaxy());
+ else
+ scroll_down(getmaxy());
+}
+
+void vs_pager::search_omnidirectional_for(const wstring &s, bool forward)
+{
+ vs_widget_ref tmpref(this);
+
+ if(s!=L"")
+ last_search=s;
+ else if(last_search==L"")
+ {
+ beep();
+ return;
+ }
+
+ line_count i = forward ? first_line + 1 : first_line - 1;
+
+ while(i > 0 && i < lines.size())
+ {
+ wstring::size_type loc
+ = forward ? lines[i].find(last_search) : lines[i].rfind(last_search);
+
+ if(loc!=wstring::npos)
+ {
+ int last_search_width=wcswidth(last_search.c_str(), last_search.size());
+ wstring &line=lines[i];
+ col_count foundcol=0;
+ for(wstring::size_type j=0; j<loc; ++j)
+ foundcol+=wcwidth(line[j]);
+
+ first_line=i;
+ do_line_signal();
+
+ if(foundcol<first_column)
+ {
+ first_column=foundcol;
+ do_column_signal();
+ }
+ else if(foundcol+last_search_width>=first_column+getmaxx())
+ {
+ if(last_search_width>(col_count) getmaxx())
+ first_column=foundcol;
+ else
+ first_column=foundcol+last_search_width-getmaxx();
+
+ do_column_signal();
+ }
+
+ vscreen_update();
+ return;
+ }
+
+ if(forward)
+ ++i;
+ else
+ --i;
+ }
+ beep();
+}
+
+bool vs_pager::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ if(bindings->key_matches(k, "Up"))
+ scroll_up(1);
+ else if(bindings->key_matches(k, "Down"))
+ scroll_down(1);
+ else if(bindings->key_matches(k, "Left"))
+ scroll_left(1);
+ else if(bindings->key_matches(k, "Right"))
+ scroll_right(1);
+ else if(bindings->key_matches(k, "PrevPage"))
+ scroll_up(getmaxy());
+ else if(bindings->key_matches(k, "NextPage"))
+ scroll_down(getmaxy());
+ else if(bindings->key_matches(k, "Begin"))
+ scroll_top();
+ else if(bindings->key_matches(k, "End"))
+ scroll_bottom();
+ else
+ return vscreen_widget::handle_key(k);
+
+ return true;
+}
+
+// Could find out what dimensions changed and only update along those?
+void vs_pager::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ do_line_signal();
+ do_column_signal();
+}
+
+void vs_pager::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ int width,height;
+ getmaxyx(height, width);
+
+ for(int y=0; y<height && first_line+y<lines.size(); ++y)
+ {
+ const wstring &s=lines[first_line+y];
+ col_count x=0;
+ wstring::size_type curr=0;
+
+ while(curr<s.size() && x<first_column+width)
+ {
+ if(s[curr]=='\t')
+ x+=8;
+ else
+ {
+ wchar_t ch=s[curr];
+ // No nonprintables other than \t should appear
+ // (set_text screens them out)
+ assert(iswprint(ch));
+
+ if(x>=first_column)
+ {
+
+ mvadd_wch(y, x-first_column, ch);
+ x+=wcwidth(ch);
+ }
+ else
+ x+=wcwidth(ch);
+ }
+
+ ++curr;
+ }
+ }
+}
+
+int vs_pager::width_request()
+{
+ return text_width;
+}
+
+int vs_pager::height_request(int w)
+{
+ return lines.size();
+}
+
+void vs_pager::init_bindings()
+{
+ bindings=new keybindings(&global_bindings);
+}
+
+vs_file_pager::vs_file_pager():vs_pager("")
+{
+}
+
+vs_file_pager::vs_file_pager(const string &filename,
+ const char *encoding):vs_pager("")
+{
+ load_file(filename, encoding);
+}
+
+vs_file_pager::vs_file_pager(const wstring &filename,
+ const char *encoding):vs_pager("")
+{
+ load_file(filename, encoding);
+}
+
+vs_file_pager::vs_file_pager(const char *text, int size,
+ const char *encoding)
+ :vs_pager(text, size, encoding)
+{
+}
+
+void vs_file_pager::load_file(const string &filename,
+ const char *encoding)
+{
+ vs_widget_ref tmpref(this);
+
+ int fd=open(filename.c_str(), O_RDONLY, 0644);
+
+ if(fd==-1)
+ set_text("open: "+filename+": "+strerror(errno));
+ else
+ {
+ struct stat buf;
+ if(fstat(fd, &buf)<0)
+ {
+ close(fd);
+ fd=-1;
+ set_text("fstat: "+filename+": "+strerror(errno));
+ }
+ else
+ {
+ const char *contents=(const char *) mmap(NULL,
+ buf.st_size,
+ PROT_READ,
+ MAP_SHARED,
+ fd,
+ 0);
+
+ if(contents==MAP_FAILED)
+ {
+ close(fd);
+ fd=-1;
+ set_text("mmap: "+filename+": "+strerror(errno));
+ // FIXME: just display something in the widget itself?
+ }
+ else
+ vs_pager::set_text(contents, buf.st_size, encoding);
+
+
+ if(fd!=-1)
+ {
+ munmap((void *) contents, buf.st_size);
+ close(fd);
+ }
+ }
+ }
+}
+
+void vs_file_pager::load_file(const wstring &filename,
+ const char *encoding)
+{
+ vs_widget_ref tmpref(this);
+
+ string mbfilename;
+
+ if(transcode(filename, mbfilename))
+ load_file(mbfilename, encoding);
+ else
+ {
+ wchar_t buf[512];
+
+ swprintf(buf, sizeof(buf)/sizeof(wchar_t),
+ transcode(_("Unable to load filename: the string %ls has no multibyte representation.")).c_str(), filename.c_str());
+ set_text(buf);
+ }
+}
+
+void vs_file_pager::load_file(const wstring &filename)
+{
+ load_file(filename, NULL);
+}
diff --git a/src/vscreen/vs_pager.h b/src/vscreen/vs_pager.h
new file mode 100644
index 00000000..fe912d29
--- /dev/null
+++ b/src/vscreen/vs_pager.h
@@ -0,0 +1,267 @@
+// vs_pager.h -*-c++-*-
+//
+// Copyright 2000-2004 Daniel Burrows
+//
+// A vscreen which acts as a text pager. Recently rewritten to use
+// an internal vector of lines (makes its behavior much nicer from
+// the user's point of view, even if it's slightly less efficient).
+
+#ifndef VS_PAGER_H
+#define VS_PAGER_H
+
+#include "vscreen_widget.h"
+
+#include <string>
+#include <vector>
+
+class keybindings;
+
+class vs_pager:public vscreen_widget
+{
+public:
+ typedef std::vector<std::wstring>::size_type line_count;
+ typedef int col_count;
+private:
+ /** The lines of text being displayed: */
+ std::vector<std::wstring> lines;
+
+ /** The first visible line. */
+ line_count first_line;
+
+ /** How much the widget is scrolled to the right. */
+ col_count first_column;
+
+ /** The bounds of the text contained in the pager. */
+ col_count text_width;
+
+ /** The last string the user searched for (so we can repeat searches) */
+ std::wstring last_search;
+
+ /** Handles resizing the widget. */
+ void layout_me();
+
+ /** The workhorse search routine. */
+ void search_omnidirectional_for(const std::wstring &s, bool forward);
+
+protected:
+ vs_pager(const char *text, int len, const char *encoding = NULL);
+ vs_pager(const std::string &s, const char *encoding = NULL);
+ vs_pager(const std::wstring &s);
+
+public:
+ /** Create a vs_pager from the given memory region.
+ *
+ * \param text the text to display
+ * \param len the length of the buffer
+ * \param encoding the encoding of text, or \b NULL to use LC_CTYPE
+ */
+ static ref_ptr<vs_pager>
+ create(const char *text, int len, const char *encoding = NULL)
+ {
+ ref_ptr<vs_pager> rval(new vs_pager(text, len, encoding));
+ rval->decref();
+ return rval;
+ }
+
+ /** Create a vs_pager from a string.
+ *
+ * \param s the text to display
+ * \param encoding the encoding of s, or \b NULL to use LC_CTYPE
+ */
+ static ref_ptr<vs_pager>
+ create(const std::string &s, const char *encoding = NULL)
+ {
+ ref_ptr<vs_pager> rval(new vs_pager(s, encoding));
+ rval->decref();
+ return rval;
+ }
+
+ /** Create a vs_pager from a wide character string.
+ *
+ * \param s the text to display
+ */
+ static ref_ptr<vs_pager>
+ create (const std::wstring &s)
+ {
+ ref_ptr<vs_pager> rval(new vs_pager(s));
+
+ rval->decref();
+
+ return rval;
+ }
+
+ /** Destroy this vs_pager. */
+ virtual ~vs_pager();
+
+ /** Set the text to the given memory region.
+ *
+ * \param text the text to display
+ * \param len the length of the buffer
+ * \param encoding the encoding of text, or \b NULL to use LC_CTYPE
+ */
+ virtual void set_text(const char *text,
+ std::string::size_type len,
+ const char *encoding=NULL);
+
+ /** Change the displayed text.
+ *
+ * \param s the text to display
+ * \param encoding the encoding of s, or \b NULL to use LC_CTYPE
+ */
+ virtual void set_text(const std::string &s, const char *encoding=NULL);
+
+ /** Change the displayed text.
+ *
+ * \param s the text to display
+ */
+ virtual void set_text(const std::wstring &s);
+
+ /** Scroll the screen up by the given number of lines. */
+ void scroll_up(line_count nlines);
+
+ /** Scroll the screen down by the given number of lines. */
+ void scroll_down(line_count nlines);
+
+ /** Scroll the screen right by the given number of columns. */
+ void scroll_right(col_count ncols);
+
+ /** Scroll the screen left by the given number of columns. */
+ void scroll_left(col_count ncols);
+
+ /** Scroll to the top of the screen. */
+ void scroll_top();
+
+ /** Scroll to the bottom of the screen. */
+ void scroll_bottom();
+
+ /** Scroll by a page in the given direction.
+ *
+ * \param dir if \b true, scroll a page up; otherwise, scroll a
+ * page down.
+ */
+ void scroll_page(bool dir);
+
+ /** Find the next line containing the given string.
+ *
+ * \param s the string to search for
+ */
+ void search_for(const std::wstring &s)
+ {
+ search_omnidirectional_for(s, true);
+ }
+
+ /** Find the previous line containing the given string.
+ *
+ * \param s the string to search for
+ */
+ void search_back_for(const std::wstring &s)
+ {
+ search_omnidirectional_for(s, false);
+ }
+
+ /** Return the last string which the user searched for. */
+ std::wstring get_last_search() {return last_search;}
+
+ line_count get_first_line() {return first_line;}
+ line_count get_num_lines() {return lines.size();}
+ col_count get_first_column() {return first_column;}
+ col_count get_num_columns() {return text_width;}
+
+ /** Emits a signal describing the verical location of the display
+ * within the text.
+ */
+ void do_line_signal();
+
+ /** Emits a signal describing the horizontal location of the display
+ * within the text.
+ */
+ void do_column_signal();
+
+ // vscreen overrides:
+
+ virtual bool handle_key(const key &k);
+ virtual bool focus_me() {return true;}
+ virtual void paint(const style &st);
+
+ int width_request();
+ int height_request(int w);
+ bool get_cursorvisible() {return true;}
+ point get_cursorloc() {return point(0,0);}
+
+ /** Announces that the user has scrolled vertically. */
+ sigc::signal2<void, int, int> line_changed;
+
+ /** Announces that the user has scrolled horizontally. */
+ sigc::signal2<void, int, int> column_changed;
+
+ static keybindings *bindings;
+ static void init_bindings();
+};
+
+/** Load a file from disk; it's assumed to be ASCII for now. */
+class vs_file_pager:public vs_pager
+{
+protected:
+ vs_file_pager();
+ vs_file_pager(const std::string &filename, const char *encoding = NULL);
+ vs_file_pager(const std::wstring &filename, const char *encoding = NULL);
+
+ vs_file_pager(const char *text, int len, const char *encoding = NULL);
+public:
+ static ref_ptr<vs_file_pager> create()
+ {
+ return new vs_file_pager;
+ }
+
+ static ref_ptr<vs_file_pager> create(const std::string &filename, const char *encoding=NULL)
+ {
+ return new vs_file_pager(filename, encoding);
+ }
+
+ /** Attempts to convert the string to a multibyte representation and
+ * then load it; a nonconvertible string is treated as any other
+ * load failure would be.
+ */
+ static ref_ptr<vs_file_pager>
+ create(const std::wstring &filename, const char *encoding=NULL)
+ {
+ return new vs_file_pager(filename, encoding);
+ }
+
+ static ref_ptr<vs_file_pager>
+ create(const char *text, int len, const char *encoding=NULL)
+ {
+ return new vs_file_pager(text, len, encoding);
+ }
+
+ /** Loads the given file into the pager.
+ *
+ * \param filename the name of the file to load
+ * \param encoding the encoding of the file's contents; if \b NULL, LC_CTYPE
+ * is used.
+ */
+ void load_file(const std::string &filename, const char *encoding=NULL);
+
+ /** Attempts to convert the string to a multibyte representation and
+ * then load it; a nonconvertible string is treated as any other
+ * load failure would be.
+ *
+ * \param filename the name of the file to load
+ * \param encoding the encoding of the file's contents; if \b NULL, LC_CTYPE is used.
+ */
+ void load_file(const std::wstring &filename, const char *encoding);
+
+ /** Attempts to convert the string to a multibyte representation and
+ * then load it; a nonconvertible string is treated as any other
+ * load failure would be. The file is assumed to contain text in
+ * the encoding specified by LC_CTYPE.
+ *
+ * \param filename the name of the file to load
+ */
+ void load_file(const std::wstring &filename);
+};
+
+typedef ref_ptr<vs_pager> vs_pager_ref;
+typedef ref_ptr<vs_file_pager> vs_file_pager_ref;
+
+#endif
diff --git a/src/vscreen/vs_passthrough.cc b/src/vscreen/vs_passthrough.cc
new file mode 100644
index 00000000..50fa10e6
--- /dev/null
+++ b/src/vscreen/vs_passthrough.cc
@@ -0,0 +1,108 @@
+// vs_passthrough.cc
+
+#include "vs_passthrough.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+vs_passthrough::vs_passthrough()
+{
+ focussed.connect(sigc::mem_fun(*this, &vs_passthrough::gained_focus));
+ unfocussed.connect(sigc::mem_fun(*this, &vs_passthrough::lost_focus));
+}
+
+vs_widget_ref vs_passthrough::get_active_widget()
+{
+ return get_focus();
+}
+
+void vs_passthrough::defocus()
+{
+ lost_focus();
+}
+
+void vs_passthrough::refocus()
+{
+ gained_focus();
+}
+
+void vs_passthrough::gained_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = get_focus();
+
+ if(w.valid())
+ w->focussed();
+}
+
+void vs_passthrough::lost_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = get_focus();
+
+ if(w.valid())
+ w->unfocussed();
+}
+
+bool vs_passthrough::focus_me()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = get_focus();
+
+ if(w.valid() && w->focus_me())
+ return true;
+ else
+ return vs_container::focus_me();
+}
+
+bool vs_passthrough::get_cursorvisible()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = get_focus();
+
+ return w.valid() && w->get_cursorvisible();
+}
+
+point vs_passthrough::get_cursorloc()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = get_focus();
+
+ if(w.valid())
+ {
+ point p=w->get_cursorloc();
+ p.x+=w->get_startx();
+ p.y+=w->get_starty();
+
+ return p;
+ }
+ else
+ return point(0, 0);
+}
+
+bool vs_passthrough::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = get_focus();
+
+ if(w.valid() && w->get_visible() && w->focus_me())
+ return w->dispatch_key(k) || vs_container::handle_key(k);
+ else
+ return vs_container::handle_key(k);
+}
+
+void vs_passthrough::dispatch_mouse(short id, int x, int y, int z,
+ mmask_t bstate)
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = get_focus();
+
+ if(w.valid() && w->get_visible())
+ w->dispatch_mouse(id, x-w->get_startx(), y-w->get_starty(), z, bstate);
+}
diff --git a/src/vscreen/vs_passthrough.h b/src/vscreen/vs_passthrough.h
new file mode 100644
index 00000000..0491cc71
--- /dev/null
+++ b/src/vscreen/vs_passthrough.h
@@ -0,0 +1,44 @@
+// vs_passthrough.h -*-c++-*-
+//
+// A widget that by default passes focus and cursor handling through to
+// a "currently focussed" widget.
+
+#ifndef VS_PASSTHROUGH_H
+#define VS_PASSTHROUGH_H
+
+#include "vs_container.h"
+
+class vs_passthrough:public vs_container
+{
+ void gained_focus();
+ void lost_focus();
+
+protected:
+ virtual bool handle_key(const key &k);
+
+ // These call focussed() and unfocussed() on the result of get_focus().
+ // (convenience methods)
+ //
+ // Provided to make it easier to manage focus simply.
+ void defocus();
+ void refocus();
+
+protected:
+ vs_passthrough();
+
+public:
+ // Returns the currently focussed widget, if any.
+ virtual vs_widget_ref get_focus()=0;
+
+ vs_widget_ref get_active_widget();
+
+ virtual void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+ virtual bool focus_me();
+ virtual bool get_cursorvisible();
+ virtual point get_cursorloc();
+};
+
+typedef ref_ptr<vs_passthrough> vs_passthrough_ref;
+
+#endif
diff --git a/src/vscreen/vs_radiogroup.cc b/src/vscreen/vs_radiogroup.cc
new file mode 100644
index 00000000..92ef2268
--- /dev/null
+++ b/src/vscreen/vs_radiogroup.cc
@@ -0,0 +1,114 @@
+// vs_radiogroup.cc
+
+#include "vs_radiogroup.h"
+
+#include "vs_togglebutton.h"
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+vs_radiogroup::vs_radiogroup()
+ :selected(items.max_size())
+{
+}
+
+vs_radiogroup::~vs_radiogroup()
+{
+}
+
+bool vs_radiogroup::selection_valid()
+{
+ return selected != items.max_size();
+}
+
+int vs_radiogroup::get_selected()
+{
+ return items[selected].id;
+}
+
+void vs_radiogroup::button_pressed(itemlist::size_type index)
+{
+ assert(index<items.size());
+
+ if(selected!=items.max_size())
+ items[selected].b->set_checked(false);
+ selected=index;
+
+ if(index!=items.max_size())
+ {
+ items[index].b->set_checked(true);
+ item_selected(items[index].id);
+ }
+}
+
+void vs_radiogroup::add_button(const vs_togglebutton_ref &b, int id)
+{
+ assert(id>=0);
+
+ for(itemlist::iterator i=items.begin(); i!=items.end(); i++)
+ assert(i->b!=b);
+
+ items.push_back(item(b, id,
+ b->destroyed.connect(sigc::bind(sigc::mem_fun(*this, &vs_radiogroup::rem_button_bare), b.weak_ref())),
+ b->pressed.connect(sigc::bind(sigc::mem_fun(*this, &vs_radiogroup::button_pressed), items.size()))));
+
+ if(selected==items.max_size() || b->get_checked())
+ button_pressed(items.size()-1);
+}
+
+void vs_radiogroup::rem_button(const vs_togglebutton_ref &b)
+{
+ for(itemlist::size_type i=0; i<items.size(); i++)
+ if(items[i].b==b)
+ {
+ items[i].destroyed_conn.disconnect();
+ items[i].pressed_conn.disconnect();
+ if(selected==i)
+ {
+ if(i>0)
+ button_pressed(i-1);
+ else if(i+1<items.size())
+ button_pressed(i+1);
+ else
+ assert(items.size() == 1);
+ }
+
+ if(i==items.size()-1)
+ items.pop_back();
+ else
+ {
+ items[i]=items[items.size()-1];
+ if(selected==items.size()-1)
+ selected=i;
+ items.pop_back();
+ items[i].pressed_conn.disconnect();
+ items[i].pressed_conn=items[i].b->pressed.connect(sigc::bind(sigc::mem_fun(*this, &vs_radiogroup::button_pressed), i));
+ }
+
+ return;
+ }
+}
+
+void vs_radiogroup::rem_button_bare(vs_togglebutton &b)
+{
+ rem_button(vs_togglebutton_ref(&b));
+}
+
+void vs_radiogroup::select(int id)
+{
+ for(itemlist::size_type i=0; i<items.size(); i++)
+ if(items[i].id==id)
+ {
+ button_pressed(i);
+
+ return;
+ }
+
+ // Er, no buttons match the given ID.
+ abort();
+}
+
+void vs_radiogroup::destroy()
+{
+ delete this;
+}
diff --git a/src/vscreen/vs_radiogroup.h b/src/vscreen/vs_radiogroup.h
new file mode 100644
index 00000000..e3b16087
--- /dev/null
+++ b/src/vscreen/vs_radiogroup.h
@@ -0,0 +1,86 @@
+// vs_radiogroup.h -*-c++-*-
+//
+// Ok, here's how radio-button-like behavior is implemented:
+//
+// Radio-groups store some group of buttons. They constrain the buttons
+// so that exactly one is on. This is done by various devious means:
+// the first button added is always selected, and subsequently added selected
+// buttons override previously selected buttons. You can manually select a
+// button through this class (by ID) or via the button's own set_checked
+// routine.
+//
+// (note that radio-groups are NOT WIDGETS!!!)
+// (note that this does NOT attempt to memory-manage its "children"!)
+// (note that you should generally delete this at the same time as its
+// children, or Bad Things[tm] may happen.. (more specifically, if you
+// delete a selected child, some other random option will be selected))
+//
+// Oh, one more note: although any togglebutton can be used in a radio
+// widget, passing in checkbuttons has a high probability of causing weird
+// things. Use radiobuttons. (if you want them to look like checkbuttons,
+// use the extended togglebutton constructor..)
+
+#ifndef VS_RADIOGROUP_H
+#define VS_RADIOGROUP_H
+
+#include "ref_ptr.h"
+
+#include <vector>
+
+#include <sigc++/connection.h>
+#include <sigc++/trackable.h>
+
+class vs_togglebutton;
+
+class vs_radiogroup:public sigc::trackable
+{
+ struct item
+ {
+ ref_ptr<vs_togglebutton> b;
+ int id;
+
+ // Needed, unfortunately.
+ sigc::connection destroyed_conn, pressed_conn;
+
+ item(const ref_ptr<vs_togglebutton> &_b, int _id,
+ const sigc::connection &_dconn, const sigc::connection &_pconn)
+ :b(_b), id(_id), destroyed_conn(_dconn), pressed_conn(_pconn) {}
+ };
+
+ typedef std::vector<item> itemlist;
+
+ itemlist items;
+
+ // The index of the currently selected button
+ itemlist::size_type selected;
+
+ // Called when a particular button is selected.
+ // The argument is the *index* of the button.
+ void button_pressed(itemlist::size_type index);
+public:
+ vs_radiogroup();
+ ~vs_radiogroup();
+
+ void add_button(const ref_ptr<vs_togglebutton> &b, int id);
+ void rem_button(const ref_ptr<vs_togglebutton> &b);
+
+ void rem_button_bare(vs_togglebutton &b);
+
+ /** \return \b true if a button is selected. */
+ bool selection_valid();
+
+ /** \return the id of the selected button, if the selection is valid. */
+ int get_selected();
+
+ // Selects a button by id.
+ void select(int id);
+
+ /** Destroy this radio group. */
+ void destroy();
+
+ // Emitted when one of the sub-items is chosen. (you could also collect
+ // the individual button signals; this is just a higher-level view of it)
+ sigc::signal1<void, int> item_selected;
+};
+
+#endif
diff --git a/src/vscreen/vs_scrollbar.cc b/src/vscreen/vs_scrollbar.cc
new file mode 100644
index 00000000..6bda7873
--- /dev/null
+++ b/src/vscreen/vs_scrollbar.cc
@@ -0,0 +1,90 @@
+// vs_scrollbar.cc -*-c++-*-
+
+#include "vs_scrollbar.h"
+
+#include <vscreen.h>
+
+int vs_scrollbar::get_slider()
+{
+ vs_widget_ref tmpref(this);
+
+ int width = dir==HORIZONTAL?getmaxx():getmaxy();
+ return max==0?-1:(width-1)*val/max;
+}
+
+void vs_scrollbar::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ if(dir==HORIZONTAL)
+ {
+ int width=getmaxx();
+ int thumbloc=get_slider();
+
+ for(int x=0; x<width; x++)
+ if(x==thumbloc)
+ mvadd_wch(0, x, L'#');
+ else
+ mvadd_wch(0, x, WACS_CKBOARD);
+ }
+ else
+ {
+ int height=getmaxy();
+ int thumbloc=get_slider();
+
+ for(int y=0; y<height; y++)
+ if(y==thumbloc)
+ mvadd_wch(y, 0, L'#');
+ else
+ mvadd_wch(y, 0, WACS_CKBOARD);
+ }
+}
+
+int vs_scrollbar::width_request()
+{
+ return 1;
+}
+
+int vs_scrollbar::height_request(int w)
+{
+ return 1;
+}
+
+void vs_scrollbar::set_slider(int newval, int newmax)
+{
+ if(max!=newmax || val!=newval)
+ {
+ max=newmax;
+ val=newval;
+
+ vscreen_update();
+ }
+}
+
+bool vs_scrollbar::get_cursorvisible()
+{
+ return false;
+}
+
+point vs_scrollbar::get_cursorloc()
+{
+ return point(0, 0);
+}
+
+void vs_scrollbar::dispatch_mouse(short id,
+ int x, int y, int z,
+ mmask_t bstate)
+{
+ vs_widget_ref tmpref(this);
+
+ int slider_loc=get_slider();
+ int mloc = dir==HORIZONTAL?x:y;
+
+ if(slider_loc!=-1)
+ {
+ if(mloc>slider_loc)
+ scrollbar_interaction(false);
+ else
+ scrollbar_interaction(true);
+ }
+}
diff --git a/src/vscreen/vs_scrollbar.h b/src/vscreen/vs_scrollbar.h
new file mode 100644
index 00000000..dec22db4
--- /dev/null
+++ b/src/vscreen/vs_scrollbar.h
@@ -0,0 +1,72 @@
+// vs_scrollbar.h -*-c++-*-
+//
+// Guess. >=)
+//
+// Scrollbars in text-mode are, of course, only a visual effect, but they're
+// useful nonetheless and a requested feature..
+
+#ifndef VS_SCROLLBAR_H
+#define VS_SCROLLBAR_H
+
+#include "vscreen_widget.h"
+
+class vs_scrollbar:public vscreen_widget
+{
+public:
+ enum direction {HORIZONTAL, VERTICAL};
+
+private:
+ direction dir;
+
+ int max, val;
+ // The current slider maximum and value (FIXME: use floats?)
+
+ /** Get the current X or Y location of the slider in the widget.
+ *
+ * \return the slider location, or -1 if it is not visible.
+ */
+ int get_slider();
+protected:
+ vs_scrollbar(direction _dir, int _val, int _max)
+ :dir(_dir), max(_max), val(_val) {}
+
+ vs_scrollbar(direction _dir)
+ :dir(_dir), max(0), val(0) {}
+public:
+ static
+ ref_ptr<vs_scrollbar> create(direction dir, int val, int max)
+ {
+ ref_ptr<vs_scrollbar> rval(new vs_scrollbar(dir, val, max));
+ rval->decref();
+ return rval;
+ }
+
+ static
+ ref_ptr<vs_scrollbar> create(direction dir)
+ {
+ ref_ptr<vs_scrollbar> rval(new vs_scrollbar(dir));
+ rval->decref();
+ return rval;
+ }
+
+ void paint(const style &st);
+
+ int width_request();
+ int height_request(int w);
+
+ bool get_cursorvisible();
+ point get_cursorloc();
+ void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+ void set_slider(int newval, int newmax);
+
+ /** This signal is emitted if the user "pages up" or "pages down"
+ * via the scrollbar. Its argument is \b true for a "page up" and
+ * \b false for a "page down".
+ */
+ sigc::signal1<void, bool> scrollbar_interaction;
+};
+
+typedef ref_ptr<vs_scrollbar> vs_scrollbar_ref;
+
+#endif
diff --git a/src/vscreen/vs_size_box.cc b/src/vscreen/vs_size_box.cc
new file mode 100644
index 00000000..2677a24e
--- /dev/null
+++ b/src/vscreen/vs_size_box.cc
@@ -0,0 +1,58 @@
+// vs_size_box.cc
+//
+// Copyright 2004 Daniel Burrows
+
+#include "vs_size_box.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+#include <utility>
+
+using namespace std;
+
+vs_size_box::vs_size_box(size s, const vs_widget_ref &w):min_size(s)
+{
+ set_subwidget(w);
+ set_opaque(false);
+
+ do_layout.connect(sigc::mem_fun(*this, &vs_size_box::layout_me));
+}
+
+int vs_size_box::width_request()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref child = get_subwidget();
+
+ if(child.valid())
+ return max(child->width_request(), min_size.w);
+ else
+ return min_size.w;
+}
+
+int vs_size_box::height_request(int w)
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref child = get_subwidget();
+
+ if(child.valid())
+ return max(child->height_request(w), min_size.h);
+ else
+ return min_size.h;
+}
+
+void vs_size_box::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref child = get_subwidget();
+
+ if(child.valid())
+ {
+ if(child->get_visible())
+ child->alloc_size(0, 0, getmaxx(), getmaxy());
+ else
+ child->alloc_size(0, 0, 0, 0);
+ }
+}
diff --git a/src/vscreen/vs_size_box.h b/src/vscreen/vs_size_box.h
new file mode 100644
index 00000000..5a25f142
--- /dev/null
+++ b/src/vscreen/vs_size_box.h
@@ -0,0 +1,54 @@
+// vs_size_box.h -*-c++-*-
+//
+// A container to ensure that its child has a particular minimum size
+// (at least).
+//
+// Copyright 2004 Daniel Burrows
+
+#ifndef VS_SIZE_BOX_H
+#define VS_SIZE_BOX_H
+
+#include "vs_bin.h"
+
+/** A vs_size_box ensures that the requested size of its child is a
+ * given size or larger.
+ */
+class vs_size_box:public vs_bin
+{
+ size min_size;
+
+ /** Internal: actually allocates the child's size. */
+ void layout_me();
+protected:
+ vs_size_box(size s, const vs_widget_ref &w);
+
+public:
+ /** Create a vs_size_box.
+ *
+ * \param s the minimum size of this box
+ * \param w the widget initially contained in this box (\b NULL to
+ * create an initially empty box)
+ */
+ static ref_ptr<vs_size_box> create(size s, const vs_widget_ref &w=NULL)
+ {
+ ref_ptr<vs_size_box> rval(new vs_size_box(s, w));
+ rval->decref();
+ return rval;
+ }
+
+ /** \return the least upper bound of the minimum size passed to the
+ * constructor and the true size request of the child.
+ */
+ int width_request();
+
+ /** \param w the width for which a height should be calculated.
+ *
+ * \return the least upper bound of the minimum size passed to the
+ * constructor and the true size request of the child.
+ */
+ int height_request(int w);
+};
+
+typedef ref_ptr<vs_size_box> vs_size_box_ref;
+
+#endif
diff --git a/src/vscreen/vs_stacked.cc b/src/vscreen/vs_stacked.cc
new file mode 100644
index 00000000..8bec6a43
--- /dev/null
+++ b/src/vscreen/vs_stacked.cc
@@ -0,0 +1,202 @@
+// vs_stacked.cc
+
+#include "vscreen.h"
+#include "vs_stacked.h"
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+vs_stacked::vs_stacked(int w, int h)
+ :req_w(w), req_h(h)
+{
+ do_layout.connect(sigc::mem_fun(*this, &vs_stacked::layout_me));
+}
+
+vs_stacked::~vs_stacked()
+{
+ assert(children.empty());
+}
+
+void vs_stacked::destroy()
+{
+ vs_widget_ref tmpref(this);
+
+ while(!children.empty())
+ children.front().w->destroy();
+
+ vs_passthrough::destroy();
+}
+
+void vs_stacked::add_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ sigc::connection shown_conn=w->shown_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_stacked::raise_widget_bare), w.weak_ref()));
+ sigc::connection hidden_conn=w->hidden_sig.connect(sigc::mem_fun(*this, &vs_stacked::hide_widget));
+
+ defocus();
+
+ children.push_back(child_info(w, shown_conn, hidden_conn));
+
+ w->set_owner(this);
+
+ refocus();
+
+ if(w->get_visible())
+ vscreen_update();
+}
+
+void vs_stacked::hide_widget()
+{
+ vscreen_update();
+}
+
+void vs_stacked::rem_widget(const vs_widget_ref &wBare)
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w(wBare);
+
+ for(childlist::iterator i=children.begin();
+ i!=children.end();
+ i++)
+ {
+ if(i->w==w)
+ {
+ i->shown_conn.disconnect();
+ i->hidden_conn.disconnect();
+
+ children.erase(i);
+ w->set_owner(NULL);
+ if(w->get_visible())
+ vscreen_update();
+
+ w->unfocussed();
+ refocus();
+
+ return;
+ }
+ }
+}
+
+void vs_stacked::raise_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin();
+ i!=children.end();
+ i++)
+ if(i->w==w)
+ {
+ defocus();
+
+ children.push_front(*i);
+ children.erase(i);
+
+ refocus();
+
+ vscreen_update();
+ return;
+ }
+}
+
+void vs_stacked::lower_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin();
+ i!=children.end();
+ i++)
+ if(i->w==w)
+ {
+ defocus();
+
+ children.push_back(*i);
+ children.erase(i);
+
+ refocus();
+
+ vscreen_update();
+ return;
+ }
+}
+
+void vs_stacked::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ // Go through the children back-to-front (reverse order)
+ for(childlist::reverse_iterator i=children.rbegin();
+ i!=children.rend();
+ i++)
+ if(i->w->get_visible())
+ i->w->display(st);
+}
+
+void vs_stacked::dispatch_mouse(short id, int x, int y, int z, mmask_t bstate)
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin();
+ i!=children.end();
+ ++i)
+ if(i->w->get_visible() && i->w->enclose(y, x))
+ {
+ i->w->dispatch_mouse(id, x-i->w->get_startx(), y-i->w->get_starty(),
+ z, bstate);
+ return;
+ }
+}
+
+vs_widget_ref vs_stacked::get_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin();
+ i!=children.end();
+ i++)
+ if(i->w->get_visible() && i->w->focus_me())
+ return i->w;
+ else
+ return NULL;
+
+ return NULL;
+}
+
+void vs_stacked::show_all()
+{
+ vs_widget_ref tmpref(this);
+
+ defocus();
+
+ for(childlist::iterator i=children.begin();
+ i!=children.end();
+ i++)
+ {
+ i->shown_conn.disconnect();
+
+ i->w->show_all();
+
+ i->shown_conn=i->w->shown_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_stacked::raise_widget_bare), i->w.weak_ref()));
+ }
+
+ refocus();
+}
+
+int vs_stacked::width_request()
+{
+ return req_w;
+}
+
+int vs_stacked::height_request(int w)
+{
+ return req_h;
+}
+
+void vs_stacked::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); i++)
+ i->w->alloc_size(0, 0, getmaxx(), getmaxy());
+}
diff --git a/src/vscreen/vs_stacked.h b/src/vscreen/vs_stacked.h
new file mode 100644
index 00000000..ab580936
--- /dev/null
+++ b/src/vscreen/vs_stacked.h
@@ -0,0 +1,88 @@
+// vs_stacked.h -*-c++-*-
+//
+// Manages a set of overlapping widgets, displaying them in a consistent
+// order (it is possible to change the stacking order)
+//
+// The size of the widget is unrelated to the sizes of its components.
+// (why? why not size it in a more flexible way?)
+
+#ifndef VS_STACKED_H
+#define VS_STACKED_H
+
+#include "vs_passthrough.h"
+
+#include <sigc++/connection.h>
+
+class vs_stacked:public vs_passthrough
+{
+ // bleach, but we need somewhere to store the info on what the signals to
+ // disconnect are :(
+ struct child_info
+ {
+ vs_widget_ref w;
+
+ sigc::connection shown_conn, hidden_conn;
+
+ child_info(const vs_widget_ref &_w,
+ SigC::Connection &_shown_conn,
+ SigC::Connection &_hidden_conn)
+ :w(_w), shown_conn(_shown_conn),
+ hidden_conn(_hidden_conn)
+ {
+ }
+ };
+
+ typedef std::list<child_info> childlist;
+
+ childlist children;
+
+ int req_w, req_h;
+
+ void layout_me();
+
+ void hide_widget();
+protected:
+ void paint(const style &st);
+
+ // The size passed in is used as a preferred size. (what we get might be
+ // larger or smaller)
+ vs_stacked(int w, int h);
+public:
+ ~vs_stacked();
+
+ void destroy();
+
+ static ref_ptr<vs_stacked> create(int w=0, int h=0)
+ {
+ ref_ptr<vs_stacked> rval(new vs_stacked(w, h));
+ rval->decref();
+ return rval;
+ }
+
+ void add_widget(const vs_widget_ref &w);
+ void rem_widget(const vs_widget_ref &w);
+ void raise_widget(const vs_widget_ref &w);
+ void lower_widget(const vs_widget_ref &w);
+
+ void raise_widget_bare(vscreen_widget &w)
+ {
+ raise_widget(vs_widget_ref(&w));
+ }
+ void lower_widget_bare(vscreen_widget &w)
+ {
+ lower_widget(vs_widget_ref(&w));
+ }
+
+ void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+ vs_widget_ref vs_stacked::get_focus();
+
+ void show_all();
+
+ int width_request();
+ int height_request(int w);
+};
+
+typedef ref_ptr<vs_stacked> vs_stacked_ref;
+
+#endif
diff --git a/src/vscreen/vs_staticitem.cc b/src/vscreen/vs_staticitem.cc
new file mode 100644
index 00000000..b59e1c3c
--- /dev/null
+++ b/src/vscreen/vs_staticitem.cc
@@ -0,0 +1,46 @@
+// vs_staticitem.cc
+//
+// Copyright 2000, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Displays static information
+
+#include "vs_staticitem.h"
+#include "vs_tree.h"
+
+void vs_staticitem::paint(vs_tree *win, int y, bool hierarchical, const style &st)
+{
+ int basex=hierarchical?2*get_depth():0;
+ int width,height;
+ win->getmaxyx(height,width);
+
+ win->attron(A_BOLD);
+ win->move(y,0);
+ for(int i=0; i<basex && i<width; i++)
+ win->addch(' ');
+ if(basex >= width)
+ return;
+
+ win->addnstr(name.c_str(), width - basex);
+ if((basex + name.size()) >= (unsigned) (width - basex))
+ return;
+
+ win->attroff(A_BOLD);
+ win->addnstr(value.c_str(), width - basex - name.size());
+ for(int newx = basex + name.size() + value.size(); newx < width; newx++)
+ win->addch(' ');
+}
diff --git a/src/vscreen/vs_staticitem.h b/src/vscreen/vs_staticitem.h
new file mode 100644
index 00000000..febb60fb
--- /dev/null
+++ b/src/vscreen/vs_staticitem.h
@@ -0,0 +1,38 @@
+// vs_staticitem.h -*-c++-*-
+//
+// Copyright 2000, 2001, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+//
+
+#ifndef VS_STATICITEM_H
+#define VS_STATICITEM_H
+
+#include "vs_treeitem.h"
+
+class vs_staticitem:public vs_treeitem
+{
+ std::wstring name,value;
+public:
+ vs_staticitem(std::wstring _name, std::wstring _value)
+ :vs_treeitem(false),name(_name),value(_value) {}
+ void paint(vs_tree *win, int y, bool hierarchical, const style &st);
+ const wchar_t *tag() {return value.c_str();}
+ const wchar_t *label() {return value.c_str();}
+};
+
+#endif
diff --git a/src/vscreen/vs_statuschoice.cc b/src/vscreen/vs_statuschoice.cc
new file mode 100644
index 00000000..b8f2194c
--- /dev/null
+++ b/src/vscreen/vs_statuschoice.cc
@@ -0,0 +1,96 @@
+// vs_statuschoice.cc
+//
+// Copyright 2000, 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// A status widget to let the user choose one of several choices.
+
+#include "vs_statuschoice.h"
+#include "vs_minibuf_win.h"
+#include "config/keybindings.h"
+
+keybindings *vs_statuschoice::bindings=NULL;
+
+using namespace std;
+
+bool vs_statuschoice::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ if(bindings->key_matches(k, "Confirm"))
+ {
+ chosen(0);
+ destroy();
+ return true;
+ }
+ else if(bindings->key_matches(k, "Cancel"))
+ {
+ destroy();
+ return true;
+ }
+ else if(k.function_key)
+ {
+ beep();
+ return true;
+ }
+ else
+ {
+ std::wstring::size_type where=choices.find(k.ch);
+ if(where==wstring::npos)
+ beep();
+ else
+ {
+ chosen(where);
+ destroy();
+ }
+ return true;
+ }
+}
+
+void vs_statuschoice::paint(const style &st)
+{
+ wstring todisp=prompt+L" ["+choices[0]+L"]";
+ for(unsigned int i=1; i<choices.size(); i++)
+ todisp+=choices[i];
+ mvaddstr(0, 0, todisp.c_str());
+}
+
+void vs_statuschoice::init_bindings()
+{
+ bindings=new keybindings(&global_bindings);
+}
+
+int vs_statuschoice::width_request()
+{
+ return wcswidth(prompt.c_str(), prompt.size())
+ +wcswidth(choices.c_str(), choices.size())+5;
+}
+
+int vs_statuschoice::height_request(int w)
+{
+ return 1;
+}
+
+bool vs_statuschoice::get_cursorvisible()
+{
+ return true;
+}
+
+point vs_statuschoice::get_cursorloc()
+{
+ return point(prompt.size()+choices.size()+4, 0);
+}
diff --git a/src/vscreen/vs_statuschoice.h b/src/vscreen/vs_statuschoice.h
new file mode 100644
index 00000000..7829e337
--- /dev/null
+++ b/src/vscreen/vs_statuschoice.h
@@ -0,0 +1,83 @@
+// vs_statuschoice.h -*-c++-*-
+//
+// Copyright 2000, 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Lets the user choose between one of several options with a keystroke;
+// displays in the window's status line. The options should probably be
+// typable characters (as opposed to, say, left-arrow)
+//
+// The options are given in a string, of which the first character should
+// be the default value. This is the value chosen if the user presses Return.
+// The string should be non-empty.
+
+#ifndef VS_STATUSCHOICE_H
+#define VS_STATUSCHOICE_H
+
+#include <string>
+#include <assert.h>
+
+#include "vscreen_widget.h"
+
+class keybindings;
+
+class vs_statuschoice:public vscreen_widget
+{
+ std::wstring prompt;
+ std::wstring choices;
+ // A string containing all possible choices; the first one is considered
+ // to be the "default".
+
+protected:
+ bool handle_key(const key &k);
+
+ vs_statuschoice(const std::wstring &_prompt, const std::wstring &_choices)
+ :vscreen_widget(), prompt(_prompt), choices(_choices)
+ {
+ assert(choices.size()>0);
+ }
+
+public:
+ static ref_ptr<vs_statuschoice> create(const std::wstring &prompt,
+ const std::wstring &choices)
+ {
+ ref_ptr<vs_statuschoice> rval(new vs_statuschoice(prompt, choices));
+ rval->decref();
+ return rval;
+ }
+
+ int width_request();
+ int height_request(int w);
+
+ bool get_cursorvisible();
+ point get_cursorloc();
+
+ bool focus_me() {return true;}
+
+ void paint(const style &st);
+
+ sigc::signal1<void, int> chosen;
+ // Called when one of the choices is selected (the arguments is the
+ // position of the choice in the "choices" string)
+
+ static keybindings *bindings;
+ static void init_bindings();
+};
+
+typedef ref_ptr<vs_statuschoice> vs_statuschoice_ref;
+
+#endif
diff --git a/src/vscreen/vs_subtree.h b/src/vscreen/vs_subtree.h
new file mode 100644
index 00000000..7477a6b1
--- /dev/null
+++ b/src/vscreen/vs_subtree.h
@@ -0,0 +1,257 @@
+// vs_subtree.h (this is -*-c++-*-)
+//
+// Copyright 1999-2003, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Subtrees for vs_trees.
+
+#ifndef VS_SUBTREE_H
+#define VS_SUBTREE_H
+
+#include <list>
+#include "vs_treeitem.h"
+#include "config/keybindings.h"
+// Hmm, this seems to be necessary for the bindings..
+#include "vs_tree.h"
+
+class vs_tree;
+
+template<class childtype, class default_sorter=tag_sort_policy>
+class vs_subtree:virtual public vs_treeitem
+// A tree-item which can contain other tree items. Still abstract -- the
+// display routines have to be filled in, and you may want to add more behavior
+// on keypresses -- but we're getting there :)
+{
+protected:
+
+ typedef std::list<childtype *> child_list;
+ typedef typename std::list<childtype *>::iterator child_iterator;
+
+ class levelref:public vs_tree_levelref
+ {
+ child_iterator realitem;
+
+ child_list *parent_list;
+ public:
+ levelref(const levelref &x)
+ :vs_tree_levelref(x), realitem(x.realitem), parent_list(x.parent_list) {}
+ levelref(const child_iterator &_realitem, child_list *_parent_list)
+ :realitem(_realitem), parent_list(_parent_list)
+ {
+ }
+
+ vs_treeitem *get_item() {assert(realitem!=parent_list->end()); return *realitem;}
+ virtual void advance_next() {++realitem;}
+ virtual void return_prev() {--realitem;}
+ bool is_begin() {return realitem==parent_list->begin();}
+ bool is_end() {return realitem==parent_list->end();}
+ levelref *clone() const {return new levelref(*this);}
+ };
+
+private:
+ bool expanded;
+
+ child_list children;
+
+protected:
+ child_iterator get_children_begin() {return children.begin();}
+ child_iterator get_children_end() {return children.end();}
+
+public:
+ typedef vs_treeiterator iterator;
+ typedef default_sorter default_sort;
+
+ vs_subtree(bool _expanded):vs_treeitem(),expanded(_expanded) {}
+
+ bool get_expanded() {return expanded;}
+
+ void expand() {expanded=true;}
+
+ void expand_all()
+ {
+ expanded=true;
+ for(child_iterator i=children.begin(); i!=children.end(); i++)
+ (*i)->expand_all();
+ }
+
+ void collapse_all()
+ {
+ expanded=false;
+ for(child_iterator i=children.begin(); i!=children.end(); i++)
+ (*i)->collapse_all();
+ }
+
+ void paint(vs_tree *win, int y, bool hierarchical,
+ const std::wstring &str, int depth_shift=2)
+ {
+ int width, height;
+ int basex=hierarchical?depth_shift*get_depth():0;
+ win->getmaxyx(height,width);
+
+ win->move(y,0);
+
+ int x=0;
+ while(x<basex && x<width)
+ {
+ win->add_wch(L' ');
+ x+=wcwidth(L' ');
+ }
+
+ if(basex>width)
+ return;
+
+ const wchar_t *ws;
+ if(hierarchical)
+ ws=get_expanded()?L"--\\ ":L"--- ";
+ else
+ ws=L"-> ";
+
+ while(*ws!=0 && x<width)
+ {
+ win->add_wch(*ws);
+ x+=wcwidth(*ws);
+ ++ws;
+ }
+
+ if(x>=width)
+ return;
+
+ size_t i=0;
+ while(i<str.size() && x<width)
+ {
+ wchar_t ch=str[i];
+
+ win->add_wch(ch);
+ x+=wcwidth(ch);
+ ++i;
+ }
+
+ while(x<width)
+ {
+ win->add_wch(L' ');
+ x+=wcwidth(L' ');
+ }
+ }
+
+ void set_depth(int _depth)
+ {
+ for(child_iterator i=children.begin(); i!=children.end(); i++)
+ (*i)->set_depth(_depth+1);
+
+ vs_treeitem::set_depth(_depth);
+ }
+
+ void add_child(childtype *newchild)
+ {
+ newchild->set_depth(get_depth()+1);
+
+ children.push_back(newchild);
+ }
+
+ // Adds a new child item at an unspecified location -- you should call sort()
+ // after adding children or the tree will have an undetermined order. (yes,
+ // you can deduce the real order. Don't.)
+ void sort(sortpolicy &sort_method)
+ {
+ for(child_iterator i=children.begin(); i!=children.end(); i++)
+ (*i)->sort(sort_method);
+
+ children.sort(sortpolicy_wrapper(sort_method));
+ }
+
+ void sort()
+ {
+ default_sort sorter;
+ sort(sorter);
+ }
+
+ virtual bool dispatch_key(const key &k, vs_tree *owner)
+ {
+ if(vs_tree::bindings->key_matches(k, "ToggleExpanded"))
+ {
+ expanded=!expanded;
+ return true;
+ }
+ else if(vs_tree::bindings->key_matches(k, "ExpandTree"))
+ {
+ if(!expanded)
+ {
+ expanded=true;
+ return true;
+ }
+ else
+ return false;
+ }
+ else if(vs_tree::bindings->key_matches(k, "CollapseTree"))
+ {
+ if(expanded)
+ {
+ expanded=false;
+ return true;
+ } else
+ return false;
+ }
+ else if(vs_tree::bindings->key_matches(k, "ExpandAll"))
+ {
+ expand_all();
+ return true;
+ }
+ else if(vs_tree::bindings->key_matches(k, "CollapseAll"))
+ {
+ collapse_all();
+ return true;
+ }
+ return false;
+ }
+ // The default action is to expand or shrink the tree when Enter is pressed.
+ // FIXME: should I use '+' and '-' here? That would appear to conflict with
+ // the other keybindings I need. Hm.
+
+ virtual void dispatch_mouse(short id, int x, mmask_t bstate, vs_tree *owner)
+ {
+ if(bstate & (BUTTON1_DOUBLE_CLICKED | BUTTON2_DOUBLE_CLICKED |
+ BUTTON3_DOUBLE_CLICKED | BUTTON4_DOUBLE_CLICKED |
+ BUTTON1_TRIPLE_CLICKED | BUTTON2_TRIPLE_CLICKED |
+ BUTTON3_TRIPLE_CLICKED | BUTTON4_TRIPLE_CLICKED))
+ expanded=!expanded;
+ }
+
+ virtual levelref *begin() {return new levelref(children.begin(), &children);}
+ virtual levelref *end() {return new levelref(children.end(), &children);}
+
+ bool has_visible_children() {return expanded && children.size()>0;}
+ bool has_children() {return children.size()>0;}
+
+ virtual ~vs_subtree()
+ {
+ child_iterator i,j;
+ for(i=children.begin(); i!=children.end(); i=j)
+ {
+ j=i;
+ j++;
+ delete *i;
+ }
+ }
+};
+
+class vs_subtree_generic:public vs_subtree<vs_treeitem>
+{
+public:
+ vs_subtree_generic(int _expanded):vs_subtree<vs_treeitem>(_expanded) {}
+};
+
+#endif
diff --git a/src/vscreen/vs_table.cc b/src/vscreen/vs_table.cc
new file mode 100644
index 00000000..e2ebcebe
--- /dev/null
+++ b/src/vscreen/vs_table.cc
@@ -0,0 +1,1452 @@
+// vs_table.cc -*-c++-*-
+//
+// Implementation of the vs_table class
+
+#include "vs_table.h"
+
+#include "vscreen.h"
+
+#include "config/keybindings.h"
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+#include <numeric>
+
+using namespace std;
+
+keybindings *vs_table::bindings=NULL;
+
+// #define DEBUG_TABLES
+
+#ifdef DEBUG_TABLES
+FILE *debug=fopen("/tmp/vs.log", "w");
+#endif
+
+vs_table::child_info::child_info(const vs_widget_ref &_w, int _row_start, int _col_start,
+ int _row_span, int _col_span, int xopts, int yopts,
+ sigc::connection &_shown_conn, sigc::connection &_hidden_conn)
+ :w(_w), row_start(_row_start), col_start(_col_start),
+ row_span(_row_span), col_span(_col_span),
+ shown_conn(_shown_conn), hidden_conn(_hidden_conn)
+{
+ expand_x=((xopts&EXPAND)!=0);
+ expand_y=((yopts&EXPAND)!=0);
+
+ shrink_x=((xopts&SHRINK)!=0);
+ shrink_y=((yopts&SHRINK)!=0);
+
+ fill_x=((xopts&FILL)!=0);
+ fill_y=((yopts&FILL)!=0);
+
+ align_left_x=((xopts&ALIGN_LEFT)!=0);
+ align_right_x=((xopts&ALIGN_RIGHT)!=0);
+
+ align_left_y=((yopts&ALIGN_LEFT)!=0);
+ align_right_y=((yopts&ALIGN_RIGHT)!=0);
+
+ ignore_size_x=((xopts&IGNORE_SIZE_REQUEST)!=0);
+ ignore_size_y=((yopts&IGNORE_SIZE_REQUEST)!=0);
+}
+
+vs_table::vs_table()
+ :rowsep(0), colsep(0), num_rows(0), num_cols(0)
+{
+ do_layout.connect(sigc::mem_fun(*this, &vs_table::layout_me));
+ focus=children.end();
+
+ focussed.connect(sigc::mem_fun(*this, &vs_table::got_focus));
+ unfocussed.connect(sigc::mem_fun(*this, &vs_table::lost_focus));
+}
+
+vs_table::~vs_table()
+{
+ assert(children.empty());
+}
+
+void vs_table::destroy()
+{
+ vs_widget_ref tmpref(this);
+
+ while(!children.empty())
+ children.front().w->destroy();
+
+ vs_passthrough::destroy();
+}
+
+void vs_table::set_rowsep(int n)
+{
+ vs_widget_ref tmpref(this);
+
+ if(n!=rowsep)
+ {
+ rowsep=n;
+ if(get_visible())
+ vscreen_update();
+ }
+}
+
+void vs_table::set_colsep(int n)
+{
+ vs_widget_ref tmpref(this);
+
+ if(n!=colsep)
+ {
+ colsep=n;
+ if(get_visible())
+ vscreen_update();
+ }
+}
+
+// We need to call get_focus() here to update the "focus" pointer.
+void vs_table::got_focus()
+{
+ vs_widget_ref w=get_focus();
+
+ if(w.valid())
+ w->focussed();
+}
+
+void vs_table::lost_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w=get_focus();
+
+ if(w.valid())
+ w->unfocussed();
+}
+
+void vs_table::add_widget_bare(vscreen_widget &w, int row_start, int col_start, int row_span, int col_span, bool expand, bool shrink)
+{
+ vs_widget_ref tmpref(this);
+
+ add_widget(vs_widget_ref(&w), row_start, col_start,
+ row_span, col_span, expand, shrink);
+}
+
+void vs_table::add_widget(const vs_widget_ref &w, int row_start, int col_start, int row_span, int col_span, bool expand, bool shrink)
+{
+ vs_widget_ref tmpref(this);
+
+ int opts=ALIGN_CENTER;
+ if(expand)
+ opts|=EXPAND|FILL;
+ if(shrink)
+ opts|=SHRINK;
+
+ add_widget_opts(w, row_start, col_start, row_span, col_span, opts, opts);
+}
+
+void vs_table::add_widget_opts_bare(vscreen_widget &w, int row_start, int col_start, int row_span, int col_span, int xopts, int yopts)
+{
+ vs_widget_ref tmpref(this);
+
+ add_widget_opts(vs_widget_ref(&w), row_start, col_start,
+ row_span, col_span, xopts, yopts);
+}
+
+void vs_table::add_widget_opts(const vs_widget_ref &w, int row_start, int col_start, int row_span, int col_span, int xopts, int yopts)
+{
+ vs_widget_ref tmpref(this);
+
+ // sanity check
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w==w)
+ // FIXME: throw something/print a nasty error message?
+ abort();
+
+ sigc::connection shown_conn=w->shown_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_table::show_widget_bare), w.weak_ref()));
+ sigc::connection hidden_conn=w->hidden_sig.connect(sigc::bind(sigc::mem_fun(*this, &vs_table::hide_widget_bare), w.weak_ref()));
+
+ children.push_back(child_info(w, row_start, col_start, row_span, col_span, xopts, yopts, shown_conn, hidden_conn));
+
+ num_rows=max(num_rows, row_start+row_span);
+ num_cols=max(num_cols, col_start+col_span);
+
+ w->set_owner(this);
+
+ if(focus==children.end() && w->focus_me() && w->get_visible())
+ {
+ focus=children.end();
+ focus--;
+
+ if(focus!=children.end() && get_isfocussed())
+ focus->w->focussed();
+ }
+
+ vscreen_queuelayout();
+}
+
+void vs_table::hide_widget_bare(vscreen_widget &w)
+{
+ vs_widget_ref tmpref(this);
+
+ hide_widget(vs_widget_ref(&w));
+}
+
+void vs_table::hide_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ if(focus!=children.end() && w==focus->w)
+ {
+ if(get_isfocussed())
+ focus->w->unfocussed();
+
+ focus++;
+
+ while(focus != children.end() &&
+ (!focus->w->get_visible() || !focus->w->focus_me()))
+ focus++;
+
+ if(focus==children.end())
+ {
+ focus=children.begin();
+
+ while(focus != children.end() &&
+ (!focus->w->get_visible() || !focus->w->focus_me()))
+ focus++;
+ }
+
+ if(focus!=children.end() && get_isfocussed())
+ focus->w->focussed();
+ }
+
+ vscreen_queuelayout();
+}
+
+void vs_table::show_widget_bare(vscreen_widget &w)
+{
+ vs_widget_ref tmpref(this);
+
+ show_widget(vs_widget_ref(&w));
+}
+
+void vs_table::show_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ if(w->focus_me() && focus==children.end())
+ {
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w==w)
+ {
+ focus=i;
+ if(get_isfocussed())
+ focus->w->focussed();
+ break;
+ }
+ }
+
+ vscreen_queuelayout();
+}
+
+void vs_table::add_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ add_widget(w, num_rows, 0, 1);
+}
+
+void vs_table::calc_dimensions()
+{
+ vs_widget_ref tmpref(this);
+
+ num_rows=0;
+ num_cols=0;
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ {
+ num_rows=max(num_rows, i->row_start+i->row_span);
+ num_cols=max(num_cols, i->col_start+i->col_span);
+ }
+
+ num_rows=num_rows;
+ num_cols=num_cols;
+}
+
+void vs_table::rem_widget(const vs_widget_ref &wBare)
+{
+ vs_widget_ref tmpref(this);
+
+ vs_widget_ref w = wBare;
+
+ for(childlist::iterator i = children.begin(); i != children.end(); ++i)
+ if(i->w == w)
+ {
+ if(i == focus)
+ {
+ if(get_isfocussed())
+ focus->w->unfocussed();
+
+ focus++;
+
+ while(focus != children.end() && (focus == i ||
+ !focus->w->get_visible() ||
+ !focus->w->focus_me()))
+ focus++;
+
+ if(focus == children.end())
+ {
+ focus = children.begin();
+
+ while(focus != children.end() && (focus == i ||
+ !focus->w->get_visible() ||
+ focus->w->focus_me()))
+ focus++;
+ }
+
+ if(focus != children.end())
+ {
+ assert(focus != i);
+
+ if(get_isfocussed())
+ focus->w->focussed();
+ }
+ }
+
+ i->shown_conn.disconnect();
+ i->hidden_conn.disconnect();
+
+ children.erase(i);
+
+
+ vscreen_queuelayout();
+ w->set_owner(NULL);
+
+ // No better way to do this..
+ calc_dimensions();
+
+ return;
+ }
+}
+
+void vs_table::focus_widget(const vs_widget_ref &w)
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w==w)
+ {
+ if(i!=focus)
+ {
+ assert(i->w->get_visible() && i->w->focus_me());
+
+ if(focus!=children.end() && get_isfocussed())
+ focus->w->unfocussed();
+
+ focus=i;
+
+ if(get_isfocussed())
+ focus->w->focussed();
+
+ vscreen_update();
+ }
+ return;
+ }
+}
+
+void vs_table::focus_widget_bare(vscreen_widget &w)
+{
+ focus_widget(vs_widget_ref(&w));
+}
+
+vs_widget_ref vs_table::get_focus()
+{
+ vs_widget_ref tmpref(this);
+
+ if(focus!=children.end() && focus->w->get_visible() &&
+ focus->w->focus_me())
+ return focus->w;
+ else
+ {
+ if(focus!=children.end() && get_isfocussed())
+ focus->w->unfocussed();
+
+ for(focus=children.begin(); focus!=children.end(); focus++)
+ {
+ if(focus->w->get_visible() && focus->w->focus_me())
+ {
+ focus->w->focussed();
+ return focus->w;
+ }
+ }
+
+ return NULL;
+ }
+}
+
+void vs_table::show_all()
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ i->w->show_all();
+
+ show();
+}
+
+class vs_table::better_fit
+{
+ const child_info &base;
+
+ int dx;
+ int dy;
+
+ // the table dimensions
+ int width;
+ int height;
+public:
+ better_fit(const child_info &c,
+ int _dx, int _dy, int _width, int _height)
+ :base(c), dx(_dx), dy(_dy), width(_width), height(_height)
+ {
+ }
+
+ // Basically operate by shifting the world so that the base object lies in
+ // the lower-right corner, then working from there.
+ inline bool operator()(const childlist::iterator &a,
+ const childlist::iterator &b)
+ {
+ int aminx=a->col_start-base.col_start-base.col_span;
+ if(aminx<0)
+ aminx+=width;
+
+ int aminy=a->row_start-base.row_start-base.row_span;
+ if(aminy<0)
+ aminy+=height;
+
+ int bminx=b->col_start-base.col_start-base.col_span;
+ if(bminx<0)
+ bminx+=width;
+
+ int bminy=b->row_start-base.row_start-base.row_span;
+ if(bminy<0)
+ bminy+=height;
+
+ int amaxy=a->row_start+a->row_span-base.row_start-base.row_span-1;
+ if(amaxy<0)
+ amaxy+=height;
+
+ int bmaxy=b->row_start+b->row_span-base.row_start-base.row_span-1;
+ if(bmaxy<0)
+ bmaxy+=height;
+
+ int amaxx=a->col_start+a->col_span-base.col_start-base.col_span-1;
+ if(amaxx<0)
+ amaxx+=width;
+
+ int bmaxx=b->col_start+b->col_span-base.col_start-base.col_span-1;
+ if(bmaxx<0)
+ bmaxx+=width;
+
+ if(dy==0)
+ {
+ if(dx>0)
+ {
+ if(aminx<bminx)
+ return true;
+ else if(aminx>bminx)
+ return false;
+ }
+ else
+ {
+ if(bmaxx<amaxx)
+ return true;
+ else if(bmaxx>amaxx)
+ return false;
+ }
+
+ int besty=(height-base.row_span)/2;
+
+ int adiff=abs((aminy+amaxy)/2-besty);
+ int bdiff=abs((bminy+bmaxy)/2-besty);
+
+ if(adiff<bdiff)
+ return true;
+ else if(adiff>bdiff)
+ return false;
+
+ // ERRRR, they're exactly the same?
+
+ return false;
+ }
+ else
+ {
+ if(dy>0)
+ {
+ if(aminy<bminy)
+ return true;
+ else if(aminy>bminy)
+ return false;
+ }
+ else
+ {
+ if(bmaxy<amaxy)
+ return true;
+ else if(bmaxy>amaxy)
+ return false;
+ }
+
+ int bestx=(width-base.col_span)/2;
+
+ int adiff=abs((aminx+amaxx)/2-bestx);
+ int bdiff=abs((bminx+bmaxx)/2-bestx);
+
+ if(adiff<bdiff)
+ return true;
+ else if(adiff>bdiff)
+ return false;
+
+ return false;
+ }
+
+ }
+};
+
+// a simple predicate--separated from the function below to keep
+// find_best_focus's high-level logic as clean as possible.
+// Checks whether the given child lies within the "shadow" of the given
+// base widget in the given direction.
+
+// There are two cases to worry about: either the "beginning" of the child is
+// within the base, or it isn't.
+// If it is, we can determine that the child does overlap the base in that
+// dimension.
+// If it is not, and it lies before the "beginning" of the base, we have to
+// check its ending. If its ending lies after the "beginning" of the base,
+// there is (again) clearly overlap. Otherwise, there is not.
+// If it is not, and it lies after the "ending" of the base, there is no
+// overlap.
+
+inline bool vs_table::lies_on_axis(const child_info &base,
+ bool horizontal,
+ const child_info &c)
+{
+ if(horizontal)
+ return (c.row_start<=base.row_start && c.row_start+c.row_span-1>=base.row_start) ||
+ (c.row_start>=base.row_start && c.row_start<=base.row_start+base.row_span-1);
+ else
+ return (c.col_start<=base.col_start && c.col_start+c.col_span-1>=base.col_start) ||
+ (c.col_start>=base.col_start && c.col_start<=base.col_start+base.col_span-1);
+}
+
+// FIXME: either dx or dy must be 0; only their signs are checked..
+vs_table::childlist::iterator vs_table::find_best_focus(childlist::iterator start,
+ int dx,
+ int dy)
+{
+ assert(start!=children.end());
+ assert(dx==0 || dy==0);
+ assert(!(dx==dy));
+
+ list<childlist::iterator> sorted_children;
+
+ for(childlist::iterator i=children.begin();
+ i!=children.end();
+ ++i)
+ if(i!=start && i->w->get_visible() &&
+ i->w->focus_me() && lies_on_axis(*start, (dy==0), *i))
+ sorted_children.push_back(i);
+
+ if(sorted_children.size()==0)
+ return start;
+
+ sorted_children.sort(better_fit(*start, dx, dy, num_cols, num_rows));
+
+ return sorted_children.front();
+}
+
+bool vs_table::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ if(focus!=children.end())
+ {
+ vs_widget_ref w = focus->w;
+
+ if(w->dispatch_key(k))
+ return true;
+ else if(bindings->key_matches(k, "Cycle"))
+ {
+ childlist::iterator oldfocus=focus;
+
+ focus++;
+
+ while(focus!=children.end() &&
+ !(focus->w->get_visible() && focus->w->focus_me()))
+ focus++;
+
+ if(focus==children.end())
+ {
+ focus=children.begin();
+ while(focus!=children.end() &&
+ !(focus->w->get_visible() && focus->w->focus_me()))
+ focus++;
+ }
+
+ if(focus!=children.end() && focus!=oldfocus)
+ {
+ if(get_isfocussed())
+ {
+ oldfocus->w->unfocussed();
+ focus->w->focussed();
+ }
+ vscreen_updatecursor();
+ }
+
+ return focus!=oldfocus;
+ }
+ else if(bindings->key_matches(k, "Left"))
+ {
+ childlist::iterator oldfocus=focus;
+
+ focus=find_best_focus(focus, -1, 0);
+
+ if(focus!=children.end() && focus!=oldfocus)
+ {
+ if(get_isfocussed())
+ {
+ oldfocus->w->unfocussed();
+ focus->w->focussed();
+ }
+ vscreen_updatecursor();
+ }
+
+ return focus!=oldfocus;
+ }
+ else if(bindings->key_matches(k, "Right"))
+ {
+ childlist::iterator oldfocus=focus;
+
+ focus=find_best_focus(focus, 1, 0);
+
+ if(focus!=children.end() && focus!=oldfocus)
+ {
+ if(get_isfocussed())
+ {
+ oldfocus->w->unfocussed();
+ focus->w->focussed();
+ }
+ vscreen_updatecursor();
+ }
+
+ return focus!=oldfocus;
+ }
+ else if(bindings->key_matches(k, "Up"))
+ {
+ childlist::iterator oldfocus=focus;
+
+ focus=find_best_focus(focus, 0, -1);
+
+ if(focus!=children.end() && focus!=oldfocus)
+ {
+ if(get_isfocussed())
+ {
+ oldfocus->w->unfocussed();
+ focus->w->focussed();
+ }
+ vscreen_updatecursor();
+ }
+
+ return focus!=oldfocus;
+ }
+ else if(bindings->key_matches(k, "Down"))
+ {
+ childlist::iterator oldfocus=focus;
+
+ focus=find_best_focus(focus, 0, 1);
+
+ if(focus!=children.end() && focus!=oldfocus)
+ {
+ if(get_isfocussed())
+ {
+ oldfocus->w->unfocussed();
+ focus->w->focussed();
+ }
+ vscreen_updatecursor();
+ }
+
+ return focus!=oldfocus;
+ }
+ else
+ return vs_passthrough::handle_key(k);
+ }
+ else
+ return vs_passthrough::handle_key(k);
+}
+
+class vs_table::nrow_lt
+{
+public:
+ inline bool operator()(const child_info *a,
+ const child_info *b)
+ {
+ return a->row_span<b->row_span;
+ }
+};
+
+class vs_table::ncol_lt
+{
+public:
+ inline bool operator()(const child_info *a,
+ const child_info *b)
+ {
+ return a->col_span<b->col_span;
+ }
+};
+
+/** Allocate "ideal" widths to all widgets: make every widget as large
+ * as it wants and expand other widgets to accomodate. This routine
+ * also calculates the width_request member of the child.
+ *
+ * \param col_sizes a vector of size num_cols; the size of each
+ * column will be stored here.
+ */
+void vs_table::alloc_ideal_widths(vector<int> &col_sizes)
+{
+ vs_widget_ref tmpref(this);
+
+ vector<bool> col_expandable(num_cols, false);
+ vector<child_info *> sorted_children;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "---------- Begin ideal width allocation for 0x%x (w=%d,h=%d) ----------\n", this, getmaxx(), getmaxy());
+#endif
+
+ for(vector<int>::iterator i=col_sizes.begin(); i!=col_sizes.end(); ++i)
+ *i=0;
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w->get_visible())
+ sorted_children.push_back(&*i);
+
+ // Sort the children according to how many columns they span
+ sort(sorted_children.begin(), sorted_children.end(), ncol_lt());
+
+ // Decide which columns to expand: first mark smaller widgets for
+ // expansion; then, if a larger widget doesn't overlap any smaller
+ // widget that's to be expanded, mark all of its cols for expansion.
+ for(vector<child_info *>::const_iterator i=sorted_children.begin();
+ i!=sorted_children.end(); ++i)
+ if((*i)->expand_x)
+ {
+ bool expanded=false;
+
+ for(int j=0; j<(*i)->col_span; ++j)
+ if(col_expandable[j+(*i)->col_start])
+ {
+ expanded=true;
+ break;
+ }
+
+ if(!expanded)
+ for(int j=0; j<(*i)->col_span; ++j)
+ col_expandable[j+(*i)->col_start]=true;
+ }
+
+ // Now try to expand columns.
+ for(vector<child_info *>::const_iterator i=sorted_children.begin();
+ i!=sorted_children.end(); ++i)
+ {
+ // If this widget doesn't have enough space, we need to expand
+ // some of the columns it spans. Otherwise, figure out which
+ // columns are expandable and try to expand them. If none of
+ // the columns are expandable, just expand each column that it
+ // spans equally.
+
+ int current_width=0;
+ int n_expandable=0;
+
+ for(int j=(*i)->col_start; j<(*i)->col_start+(*i)->col_span; ++j)
+ {
+ current_width+=col_sizes[j];
+ if(col_expandable[j])
+ ++n_expandable;
+ }
+
+ if(n_expandable==0)
+ n_expandable=(*i)->col_span;
+
+ if(!(*i)->ignore_size_x)
+ (*i)->request_w=(*i)->w->width_request();
+ else
+ (*i)->request_w=0;
+ int shortfall=(*i)->request_w-current_width;
+ if(shortfall>0)
+ for(int j=(*i)->col_start; n_expandable>0; --n_expandable,++j)
+ {
+ int amt=shortfall/n_expandable;
+
+ col_sizes[j]+=amt;
+ shortfall-=amt;
+ }
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Child at (%d,%d) requested %d columns\n",
+ (*i)->col_start, (*i)->row_start, (*i)->request_w, current_width);
+#endif
+ }
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Column sizes after:");
+ for(int i=0; i<num_rows; ++i)
+ fprintf(debug, " %d", col_sizes[i]);
+ fprintf(debug, "\n");
+
+ fprintf(debug, "---------------- end ideal allocation for 0x%x -----------------\n\n", this);
+#endif
+}
+
+/** Expand the widths of all columns. The resulting widths will give
+ * each widget the amount of width it requested, and ensure that the
+ * widget occupies at least w columns. If the widget occupies less
+ * than w columns prior to invoking this method, it will occupy at
+ * most w columns when the method terminates.
+ *
+ * If no widget is expandable, then nothing will be expanded by this
+ * algorithm.
+ *
+ * \param target_w the minimum width to occupy.
+ *
+ * \param col_sizes a vector of length num_cols containing the
+ * current size of each column; will be modified to contain the new
+ * sizes after this algorithm runs.
+ */
+void vs_table::expand_widths(vector<int> &col_sizes, int target_w)
+{
+ vs_widget_ref tmpref(this);
+
+ int current_width=accumulate(col_sizes.begin(), col_sizes.end(), 0);
+
+ int shortfall=target_w-current_width;
+ // If we're already "too big", don't bother doing anything.
+ if(shortfall<=0)
+ return;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "**************** Expanding 0x%x (w=%d, h=%d) to %d columns ******************\n", this, getmaxx(), getmaxy(), target_w);
+#endif
+
+ // Note that this is redundant with the previous algorithm. Merge?
+ vector<child_info *> sorted_children;
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w->get_visible() && i->expand_x)
+ sorted_children.push_back(&*i);
+
+ // Sort the children according to how many columns they span
+ sort(sorted_children.begin(), sorted_children.end(), ncol_lt());
+
+
+ // This is also redundant; merge?
+ vector<bool> col_expandable(num_cols, false);
+
+ int n_expandable=0;
+ for(vector<child_info *>::const_iterator i=sorted_children.begin();
+ i!=sorted_children.end(); ++i)
+ {
+ bool expanded=false;
+
+ for(int j=0; j<(*i)->col_span; ++j)
+ if(col_expandable[j+(*i)->col_start])
+ {
+ expanded=true;
+ break;
+ }
+
+ if(!expanded)
+ for(int j=0; j<(*i)->col_span; ++j)
+ col_expandable[j+(*i)->col_start]=true;
+ }
+
+ for(int i=0; i<num_cols; ++i)
+ if(col_expandable[i])
+ ++n_expandable;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Column sizes before:");
+ for(int i=0; i<num_cols; ++i)
+ fprintf(debug, " %d", col_sizes[i]);
+ fprintf(debug, "\n");
+#endif
+
+ // Now expand columns from left to right.
+ for(int i=0; i<num_cols && n_expandable>0; ++i)
+ if(col_expandable[i])
+ {
+ int amt=shortfall/n_expandable;
+ col_sizes[i]+=amt;
+ shortfall-=amt;
+ --n_expandable;
+ }
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Column sizes after:");
+ for(int i=0; i<num_cols; ++i)
+ fprintf(debug, " %d", col_sizes[i]);
+ fprintf(debug, "\n");
+
+ fprintf(debug, "********************* end expanding columns of 0x%x ********************\n\n", this);
+#endif
+}
+
+/** Shrink the width of each column of the table to meet the given
+ * target width. If the table is already small enough, no action
+ * will be taken. In the worst case, some widgets may be shrunk to
+ * invisibility.
+ *
+ * \param col_sizes a vector of (proposed) sizes of the table; will
+ * be modified to hold the newly shrunk sizes by this method.
+ * \param target_w how small the table should be after shrinkage.
+ */
+void vs_table::shrink_widths(vector<int> &col_sizes, int target_w)
+{
+ vs_widget_ref tmpref(this);
+
+ vector<bool> col_shrinkable(num_cols, false);
+ int n_shrinkable=0;
+ int current_width=accumulate(col_sizes.begin(), col_sizes.end(), 0);
+ int overflow=current_width-target_w;
+
+ if(overflow<=0)
+ return;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "++++++++++ Shrinking columns of 0x%x (w=%d, h=%d) to %d ++++++++\n", this, getmaxx(), getmaxy(), target_w);
+#endif
+
+ for(int i=0; i<num_cols; ++i)
+ col_shrinkable[i]=(col_sizes[i]>1);
+
+ for(childlist::const_iterator i=children.begin(); i!=children.end(); ++i)
+ {
+ if(i->w->get_visible() && !i->shrink_x)
+ {
+ for(int j=0; j<i->col_span; ++j)
+ col_shrinkable[j+i->col_start]=false;
+ }
+ }
+
+ for(int i=0; i<num_cols; ++i)
+ if(col_shrinkable[i])
+ ++n_shrinkable;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Column sizes before:");
+ for(int i=0; i<num_cols; ++i)
+ fprintf(debug, " %d", col_sizes[i]);
+ fprintf(debug, "\n");
+#endif
+
+ while(n_shrinkable>0 && overflow>0)
+ {
+ int toshrink=n_shrinkable;
+
+ // Actually try to shrink stuff.
+ for(int i=0; i<num_cols && toshrink>0; ++i)
+ if(col_shrinkable[i])
+ {
+ int amt=min(overflow/toshrink, col_sizes[i]-1);
+
+ col_sizes[i]-=amt;
+ overflow-=amt;
+ --toshrink;
+
+ if(col_sizes[i]<=1)
+ {
+ col_shrinkable[i]=false;
+ --n_shrinkable;
+ }
+ }
+ }
+
+ // It was impossible to shrink enough widgets; just clip the end of
+ // the table off.
+ if(overflow>0)
+ {
+ for(int i=num_cols-1; i>=0 && overflow>0; --i)
+ {
+ int amt=min(overflow, col_sizes[i]);
+
+ col_sizes[i]-=amt;
+ overflow-=amt;
+ }
+
+ assert(overflow==0);
+ }
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Column sizes after:");
+ for(int i=0; i<num_cols; ++i)
+ fprintf(debug, " %d", col_sizes[i]);
+ fprintf(debug, "\n");
+
+ fprintf(debug, "++++++++++++++++++++++++ end shrinking 0x%x +++++++++++++++++++++\n\n", this);
+#endif
+}
+
+/** Allocate "ideal" heights to all widgets: make every widget as
+ * large as it wants and expand other widgets to accomodate. This
+ * routine also calculates the height_request member of the child.
+ *
+ * \param row_sizes a vector of length num_rows; the size of each row
+ * will be stored here.
+ *
+ * \param col_sizes a vector of length num_cols containing the size
+ * of each column.
+ */
+void vs_table::alloc_ideal_heights(vector<int> &row_sizes,
+ const vector<int> &col_sizes)
+{
+ vs_widget_ref tmpref(this);
+
+ vector<bool> row_expandable(num_rows, false);
+ vector<child_info *> sorted_children;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "---------- Begin ideal height allocation for 0x%x (w=%d,h=%d) ----------\n", this, getmaxx(), getmaxy());
+#endif
+
+ for(vector<int>::iterator i=row_sizes.begin(); i!=row_sizes.end(); ++i)
+ *i=0;
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w->get_visible())
+ sorted_children.push_back(&*i);
+
+ // Sort the children according to how many rows they span
+ sort(sorted_children.begin(), sorted_children.end(), nrow_lt());
+
+ // Decide which rows to expand: first mark smaller widgets for
+ // expansion; then, if a larger widget doesn't overlap any smaller
+ // widget that's to be expanded, mark all of its rows for expansion.
+ for(vector<child_info *>::const_iterator i=sorted_children.begin();
+ i!=sorted_children.end(); ++i)
+ if((*i)->expand_y)
+ {
+ bool expanded=false;
+
+ for(int j=0; j<(*i)->row_span; ++j)
+ if(row_expandable[j+(*i)->row_start])
+ {
+ expanded=true;
+ break;
+ }
+
+ if(!expanded)
+ for(int j=0; j<(*i)->row_span; ++j)
+ row_expandable[j+(*i)->row_start]=true;
+ }
+
+ // Now try to expand rows.
+ for(vector<child_info *>::const_iterator i=sorted_children.begin();
+ i!=sorted_children.end(); ++i)
+ {
+ // If this widget doesn't have enough space, we need to expand
+ // some of the rows it spans. Otherwise, figure out which
+ // rows are expandable and try to expand them. If none of
+ // the rows are expandable, just expand each row that it
+ // spans equally.
+
+ int current_width=0;
+ for(int j=(*i)->col_start; j<(*i)->col_start+(*i)->col_span; ++j)
+ current_width+=col_sizes[j];
+
+ int current_height=0;
+ int n_expandable=0;
+
+ for(int j=(*i)->row_start; j<(*i)->row_start+(*i)->row_span; ++j)
+ {
+ current_height+=row_sizes[j];
+ if(row_expandable[j])
+ ++n_expandable;
+ }
+
+ if(n_expandable==0)
+ n_expandable=(*i)->row_span;
+
+ if(!(*i)->ignore_size_y)
+ (*i)->request_h=(*i)->w->height_request(current_width);
+ else
+ (*i)->request_h=0;
+ int shortfall=(*i)->request_h-current_height;
+
+ if(shortfall>0)
+ for(int j=(*i)->row_start; n_expandable>0; --n_expandable,++j)
+ {
+ int amt=shortfall/n_expandable;
+
+ row_sizes[j]+=amt;
+ shortfall-=amt;
+ }
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Child at (%d,%d) requested %d rows for %d columns\n",
+ (*i)->col_start, (*i)->row_start, (*i)->request_h, current_width);
+#endif
+ }
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Row sizes after:");
+ for(int i=0; i<num_rows; ++i)
+ fprintf(debug, " %d", row_sizes[i]);
+ fprintf(debug, "\n");
+
+ fprintf(debug, "---------------- end ideal allocation for 0x%x -----------------\n\n", this);
+#endif
+}
+
+/** Expand the heights of all rows. The resulting heights will give
+ * each widget the amount of height it requested, and try to ensure
+ * that the widget occupies at least w rows. If the widget occupies
+ * less than w rows prior to invoking this method, it will occupy at
+ * most w rows when the method terminates.
+ *
+ * If no widget is expandable, then nothing will be expanded by this
+ * algorithm.
+ *
+ * \param target_h the minimum height to occupy.
+ *
+ * \param row_sizes a vector of length num_rows containing the
+ * current size of each row; will be modified to contain the new
+ * sizes after this algorithm runs.
+ */
+void vs_table::expand_heights(vector<int> &row_sizes, int target_h)
+{
+ vs_widget_ref tmpref(this);
+
+ int current_height=accumulate(row_sizes.begin(), row_sizes.end(), 0);
+
+ int shortfall=target_h-current_height;
+ // If we're already "too big", don't bother doing anything.
+ if(shortfall<=0)
+ return;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "**************** Expanding 0x%x (w=%d, h=%d) to %d rows ******************\n", this, getmaxx(), getmaxy(), target_h);
+#endif
+
+ // Note that this is redundant with the previous algorithm. Merge?
+ vector<child_info *> sorted_children;
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w->get_visible() && i->expand_y)
+ sorted_children.push_back(&*i);
+
+ // Sort the children according to how many rows they span
+ sort(sorted_children.begin(), sorted_children.end(), nrow_lt());
+
+
+ // This is also redundant; merge?
+ vector<bool> row_expandable(num_rows, false);
+
+ int n_expandable=0;
+ for(vector<child_info *>::const_iterator i=sorted_children.begin();
+ i!=sorted_children.end(); ++i)
+ {
+ bool expanded=false;
+
+ for(int j=0; j<(*i)->row_span; ++j)
+ if(row_expandable[j+(*i)->row_start])
+ {
+ expanded=true;
+ break;
+ }
+
+ if(!expanded)
+ for(int j=0; j<(*i)->row_span; ++j)
+ row_expandable[j+(*i)->row_start]=true;
+ }
+
+ for(int i=0; i<num_rows; ++i)
+ if(row_expandable[i])
+ ++n_expandable;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Row sizes before:");
+ for(int i=0; i<num_rows; ++i)
+ fprintf(debug, " %d", row_sizes[i]);
+ fprintf(debug, "\n");
+#endif
+
+ // Now expand rows from left to right.
+ for(int i=0; i<num_rows && n_expandable>0; ++i)
+ if(row_expandable[i])
+ {
+ int amt=shortfall/n_expandable;
+ row_sizes[i]+=amt;
+ shortfall-=amt;
+ --n_expandable;
+ }
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Row sizes after:");
+ for(int i=0; i<num_rows; ++i)
+ fprintf(debug, " %d", row_sizes[i]);
+ fprintf(debug, "\n");
+
+ fprintf(debug, "********************* end expanding rows of 0x%x ********************\n\n", this);
+#endif
+}
+
+/** Shrink the height of each row of the table to meet the given
+ * target height. If the table is already small enough, no action
+ * will be taken. In the worst case, some widgets may be shrunk to
+ * invisibility.
+ *
+ * \param row_sizes a vector of (proposed) sizes of the table; will
+ * be modified to hold the newly shrunk sizes by this method.
+ * \param target_h how small the table should be after shrinkage.
+ */
+void vs_table::shrink_heights(vector<int> &row_sizes, int target_h)
+{
+ vs_widget_ref tmpref(this);
+
+ vector<bool> row_shrinkable(num_rows, false);
+ int n_shrinkable=0;
+ int current_height=accumulate(row_sizes.begin(), row_sizes.end(), 0);
+ int overflow=current_height-target_h;
+
+ if(overflow<=0)
+ return;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "++++++++++ Shrinking rows of 0x%x (w=%d, h=%d) to %d ++++++++\n", this, getmaxx(), getmaxy(), target_h);
+#endif
+
+ for(int i=0; i<num_rows; ++i)
+ row_shrinkable[i]=(row_sizes[i]>1);
+
+ for(childlist::const_iterator i=children.begin(); i!=children.end(); ++i)
+ {
+ if(i->w->get_visible() && !i->shrink_y)
+ {
+ for(int j=0; j<i->row_span; ++j)
+ row_shrinkable[j+i->row_start]=false;
+ }
+ }
+
+ for(int i=0; i<num_rows; ++i)
+ if(row_shrinkable[i])
+ ++n_shrinkable;
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Row sizes before:");
+ for(int i=0; i<num_rows; ++i)
+ fprintf(debug, " %d", row_sizes[i]);
+ fprintf(debug, "\n");
+#endif
+
+ while(n_shrinkable>0 && overflow>0)
+ {
+ int toshrink=n_shrinkable;
+
+ // Actually try to shrink stuff.
+ for(int i=0; i<num_rows && toshrink>0; ++i)
+ if(row_shrinkable[i])
+ {
+ int amt=min(overflow/toshrink, row_sizes[i]-1);
+
+ row_sizes[i]-=amt;
+ overflow-=amt;
+ --toshrink;
+
+ if(row_sizes[i]<=1)
+ {
+ row_shrinkable[i]=false;
+ --n_shrinkable;
+ }
+ }
+ }
+
+ // It was impossible to shrink enough widgets; just clip the end of
+ // the table off.
+ if(overflow>0)
+ {
+ for(int i=num_rows-1; i>=0 && overflow>0; --i)
+ {
+ int amt=min(overflow, row_sizes[i]);
+
+ row_sizes[i]-=amt;
+ overflow-=amt;
+ }
+
+ assert(overflow==0);
+ }
+
+#ifdef DEBUG_TABLES
+ fprintf(debug, "Row sizes after:");
+ for(int i=0; i<num_rows; ++i)
+ fprintf(debug, " %d", row_sizes[i]);
+ fprintf(debug, "\n");
+
+ fprintf(debug, "++++++++++++++++++++++++ end shrinking 0x%x +++++++++++++++++++++\n\n", this);
+#endif
+}
+
+/** Uses the given column and row sizes to allocate space to all child
+ * widgets. Assumes that request_* entries are filled in
+ * appropriately, to avoid re-executing size request methods.
+ *
+ * \param col_sizes a vector of length num_cols representing the
+ * width of each column.
+ *
+ * \param row_sizes a vector of length num_rows representing the
+ * height of each row.
+ */
+void vs_table::alloc_child_sizes(const vector<int> &col_sizes,
+ const vector<int> &row_sizes)
+{
+ vs_widget_ref tmpref(this);
+
+ vector<int> col_starts(num_cols), row_starts(num_rows);
+
+ // partial_sum almost works...but not quite.
+ int startx=0;
+ for(int i=0; i<num_cols; ++i)
+ {
+ col_starts[i]=startx;
+ startx+=col_sizes[i];
+ }
+
+ int starty=0;
+ for(int i=0; i<num_rows; ++i)
+ {
+ row_starts[i]=starty;
+ starty+=row_sizes[i];
+ }
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w->get_visible())
+ {
+ int x=col_starts[i->col_start];
+ int y=row_starts[i->row_start];
+ int width=0, height=0;
+
+ for(int j=0; j<i->col_span; ++j)
+ width+=col_sizes[j+i->col_start];
+
+ for(int j=0; j<i->row_span; ++j)
+ height+=row_sizes[j+i->row_start];
+
+ assert(x+width<=getmaxx());
+ assert(y+height<=getmaxy());
+
+ // If the widget can't be filled and it was allocated too much
+ // space, make sure it's aligned in the space:
+
+ if(width>i->request_w && !i->fill_x)
+ {
+ if(i->align_left_x && i->align_right_x)
+ x+=(width-i->request_w)/2;
+ else if(i->align_right_x)
+ x+=(width-i->request_w);
+
+ width=i->request_w;
+ }
+
+ if(height>i->request_h && !i->fill_y)
+ {
+ if(i->align_left_y && i->align_right_y)
+ y+=(height-i->request_h)/2;
+ else if(i->align_right_y)
+ y+=(height-i->request_h);
+
+ height=i->request_h;
+ }
+
+ i->w->alloc_size(x, y, width, height);
+ }
+}
+
+// Returns the sum of the ideal column widths; doesn't save anything.
+int vs_table::width_request()
+{
+ vs_widget_ref tmpref(this);
+
+ vector<int> col_sizes(num_cols);
+
+ alloc_ideal_widths(col_sizes);
+
+ return accumulate(col_sizes.begin(), col_sizes.end(), 0);
+}
+
+// Allocates provisional widths for all widgets; doesn't save
+// anything.
+int vs_table::height_request(int w)
+{
+ vs_widget_ref tmpref(this);
+
+ vector<int> col_sizes(num_cols), row_sizes(num_rows);
+
+ alloc_ideal_widths(col_sizes);
+ expand_widths(col_sizes, w);
+ shrink_widths(col_sizes, w);
+
+ alloc_ideal_heights(row_sizes, col_sizes);
+
+ return accumulate(row_sizes.begin(), row_sizes.end(), 0);
+}
+
+void vs_table::layout_me()
+{
+ vs_widget_ref tmpref(this);
+
+ get_focus();
+
+ if(get_win())
+ {
+ int w=getmaxx(), h=getmaxy();
+
+ vector<int> col_sizes(num_cols), row_sizes(num_rows);
+
+ alloc_ideal_widths(col_sizes);
+ expand_widths(col_sizes, w);
+ shrink_widths(col_sizes, w);
+
+ alloc_ideal_heights(row_sizes, col_sizes);
+ expand_heights(row_sizes, h);
+ shrink_heights(row_sizes, h);
+
+ alloc_child_sizes(col_sizes, row_sizes);
+ }
+ else
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ i->w->alloc_size(0, 0, 0, 0);
+}
+
+void vs_table::paint(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ if(i->w->get_visible())
+ i->w->display(st);
+}
+
+void vs_table::dispatch_mouse(short id, int x, int y, int z, mmask_t bstate)
+{
+ vs_widget_ref tmpref(this);
+
+ for(childlist::iterator i=children.begin(); i!=children.end(); ++i)
+ {
+ vs_widget_ref w = i->w;
+
+ if(w->get_visible() && w->enclose(y, x))
+ {
+ if(w->focus_me())
+ focus_widget(w);
+
+ w->dispatch_mouse(id, x-w->get_startx(), y-w->get_starty(),
+ z, bstate);
+ return;
+ }
+ }
+}
+
+void vs_table::init_bindings()
+{
+ bindings=new keybindings(&global_bindings);
+}
diff --git a/src/vscreen/vs_table.h b/src/vscreen/vs_table.h
new file mode 100644
index 00000000..9c8640b7
--- /dev/null
+++ b/src/vscreen/vs_table.h
@@ -0,0 +1,211 @@
+// vs_table.h -*-c++-*-
+//
+// Your usual table-layout stuff.
+
+#ifndef VS_TABLE_H
+#define VS_TABLE_H
+
+#include "vs_passthrough.h"
+#include <list>
+#include <vector>
+
+#include <sigc++/connection.h>
+
+class keybindings;
+
+class vs_table:public vs_passthrough
+{
+public:
+ // Options for laying out the widget..
+ static const int EXPAND=0x1, SHRINK=0x2, FILL=0x4;
+ static const int ALIGN_LEFT=0x8, ALIGN_RIGHT=0x10;
+ static const int ALIGN_CENTER=ALIGN_LEFT|ALIGN_RIGHT;
+ static const int IGNORE_SIZE_REQUEST=0x20;
+private:
+ struct child_info
+ {
+ // The widget itself
+ vs_widget_ref w;
+
+ // The upper-left corner of this widget
+ int row_start, col_start;
+
+ // How big is it?
+ int row_span, col_span;
+
+ /** The amount of space (perhaps provisionally) allocated to this
+ * widget. A scratchpad for internal algorithms.
+ */
+ int alloc_w, alloc_h;
+
+ /** The amount of space that the widget requested. A scratchpad
+ * for internal algorithms.
+ */
+ int request_w, request_h;
+
+ sigc::connection shown_conn, hidden_conn;
+
+ /** If \b true, expand the widget in the given direction. */
+ bool expand_x:1, expand_y:1;
+
+ /** If \b true, the widget will expand to fill its whole cell even
+ * if expand_* are false.
+ */
+ bool fill_x:1, fill_y:1;
+
+ /** If \b true, shrink the widget in the given direction. */
+ bool shrink_x:1, shrink_y:1;
+
+ /** Should the widget be aligned left/right on the given axis? If both
+ * flags are set, it will be centered in its cell.
+ */
+ bool align_left_x:1, align_left_y:1, align_right_x:1, align_right_y:1;
+
+ /** If set, ignore the widget's requisition in the given dimension
+ * and pretend that it requested (0,0).
+ */
+ bool ignore_size_x:1, ignore_size_y:1;
+
+ child_info(const vs_widget_ref &_w, int _row_start, int _col_start,
+ int _row_span, int _col_span, int xopts, int yopts,
+ sigc::connection &_shown_conn, sigc::connection &_hidden_conn);
+ };
+
+ bool lies_on_axis(const child_info &base,
+ bool horizontal,
+ const child_info &c);
+ class better_fit;
+ class nrow_lt;
+ class ncol_lt;
+
+ typedef std::list<child_info> childlist;
+
+ // Tables have an automatic behavior similar to dialogs in other widget
+ // sets -- they can give the focus to any widget that can handle it.
+ //
+ // Widgets are given focus in the order in which they are added to the
+ // table (cyclically)
+ childlist children;
+ childlist::iterator focus;
+
+ // Separation between rows/columns; initially 0.
+ int rowsep, colsep;
+
+ /** Recalculate the dimensions of the table. */
+ void calc_dimensions();
+
+ /** The number of rows in the table. */
+ int num_rows;
+
+ /** The number of columns in the table. */
+ int num_cols;
+
+ void layout_me();
+
+ // Focus-handling stuff
+ vs_widget_ref get_focus();
+ void hide_widget(const vs_widget_ref &w);
+ void hide_widget_bare(vscreen_widget &w);
+ void show_widget(const vs_widget_ref &w);
+ void show_widget_bare(vscreen_widget &w);
+
+ /** Populates the given vector with lists of the widgets in each
+ * row.
+ *
+ * \param row_contents a vector of length num_rows.
+ */
+ void get_row_contents(std::vector<std::vector<child_info *> > row_contents);
+
+ /** Populates the given vector with lists of the widgets in each
+ * column.
+ *
+ * \param col_contents a vector of length num_cols.
+ */
+ void get_col_contents(std::vector<std::vector<child_info *> > col_contents);
+
+ void vs_table::alloc_ideal_widths(std::vector<int> &col_sizes);
+ void vs_table::expand_widths(std::vector<int> &col_sizes, int target_w);
+ void vs_table::shrink_widths(std::vector<int> &col_sizes, int target_w);
+ void vs_table::alloc_ideal_heights(std::vector<int> &row_sizes,
+ const std::vector<int> &col_sizes);
+ void vs_table::expand_heights(std::vector<int> &row_sizes, int target_h);
+ void vs_table::shrink_heights(std::vector<int> &row_sizes, int target_h);
+ void vs_table::alloc_child_sizes(const std::vector<int> &col_sizes,
+ const std::vector<int> &row_sizes);
+
+
+
+ void got_focus();
+ void lost_focus();
+
+ // Moves the focus in the given direction
+ childlist::iterator find_best_focus(childlist::iterator start,
+ int dx,
+ int dy);
+
+protected:
+ bool handle_key(const key &k);
+ vs_table();
+
+public:
+ static ref_ptr<vs_table> create()
+ {
+ ref_ptr<vs_table> rval(new vs_table);
+ rval->decref();
+ return rval;
+ }
+
+ ~vs_table();
+
+ void destroy();
+
+ void add_widget_opts(const vs_widget_ref &w, int row_start, int col_start, int row_span, int col_span, int xopts, int yopts);
+ void add_widget_opts_bare(vscreen_widget &w, int row_start, int col_start, int row_span, int col_span, int xopts, int yopts);
+
+ void add_widget(const vs_widget_ref &w, int row_start, int col_start, int row_span=1, int col_span=1, bool expand=true, bool shrink=true);
+ void add_widget_bare(vscreen_widget &w, int row_start, int col_start, int row_span=1, int col_span=1, bool expand=true, bool shrink=true);
+
+ void add_widget(const vs_widget_ref &w);
+
+ void rem_widget(const vs_widget_ref &w);
+
+ void focus_widget(const vs_widget_ref &w);
+ void focus_widget_bare(vscreen_widget &w);
+
+ /** Set the separation between adjacent rows to the given number of
+ * characters.
+ */
+ void set_rowsep(int n);
+
+ /** Set the separation between adjacent rows to the given number of
+ * characters.
+ */
+ void set_colsep(int n);
+
+ void show_all();
+
+ /** Calculates the requested width of the entire table.
+ *
+ * \return the requested width
+ */
+ int width_request();
+
+ /** Calculates the requested height of the entire table. At the
+ * moment, this is a bit wasteful, since it goes ahead and
+ * provisionally allocates widths that will be re-allocated anyway.
+ *
+ * \param w the width of the table
+ * \return the requested height
+ */
+ int height_request(int w);
+
+ void paint(const style &st);
+ void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+ static keybindings *bindings;
+ static void init_bindings();
+};
+
+typedef ref_ptr<vs_table> vs_table_ref;
+
+#endif
diff --git a/src/vscreen/vs_text_layout.cc b/src/vscreen/vs_text_layout.cc
new file mode 100644
index 00000000..98b78099
--- /dev/null
+++ b/src/vscreen/vs_text_layout.cc
@@ -0,0 +1,305 @@
+// vs_text_layout.cc
+//
+// Copyright (C) 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+
+#include "vs_text_layout.h"
+
+#include "fragment.h"
+#include "fragment_contents.h"
+#include "vscreen.h"
+
+#include "config/keybindings.h"
+
+#include <algorithm>
+
+#include <sigc++/functors/mem_fun.h>
+
+using namespace std;
+
+keybindings *vs_text_layout::bindings;
+
+vs_text_layout::vs_text_layout():start(0), f(newline_fragment()), stale(true), lastw(0)
+{
+ do_layout.connect(sigc::mem_fun(*this, &vs_text_layout::layout_me));
+}
+
+vs_text_layout::vs_text_layout(fragment *_f):start(0), f(_f), stale(true), lastw(0)
+{
+ do_layout.connect(sigc::mem_fun(*this, &vs_text_layout::layout_me));
+}
+
+void vs_text_layout::init_bindings()
+{
+ bindings=new keybindings(&global_bindings);
+}
+
+bool vs_text_layout::handle_key(const key &k)
+{
+ if(bindings->key_matches(k, "Up"))
+ line_up();
+ else if(bindings->key_matches(k, "Down"))
+ line_down();
+ else if(bindings->key_matches(k, "Begin"))
+ move_to_top();
+ else if(bindings->key_matches(k, "End"))
+ move_to_bottom();
+ else if(bindings->key_matches(k, "PrevPage"))
+ page_up();
+ else if(bindings->key_matches(k, "NextPage"))
+ page_down();
+ else
+ return vscreen_widget::handle_key(k);
+
+ return true;
+}
+
+int vs_text_layout::width_request()
+{
+ if(f!=NULL)
+ return f->max_width(0, 0);
+ else
+ return 0;
+}
+
+int vs_text_layout::height_request(int w)
+{
+ // Wasteful: calculate the contents and throw them away.
+ if(f!=NULL)
+ return f->layout(w, w, style()).size();
+ else
+ return 0;
+}
+
+vs_text_layout::~vs_text_layout()
+{
+ delete f;
+}
+
+void vs_text_layout::set_fragment(fragment *_f)
+{
+ delete f;
+ f=_f;
+
+ stale=true;
+
+ // Don't just do an update, because our ideal width might change,
+ // which means other stuff also has to change around.
+ vscreen_queuelayout();
+}
+
+void vs_text_layout::append_fragment(fragment *_f)
+{
+ f=sequence_fragment(f, _f, NULL);
+ stale=true;
+
+ vscreen_queuelayout();
+}
+
+void vs_text_layout::set_start(unsigned int new_start)
+{
+ if(new_start!=start)
+ {
+ start=new_start;
+ do_signal();
+ vscreen_update();
+ }
+}
+
+void vs_text_layout::layout_me()
+{
+ // If the width has changed, we need to recalculate the layout.
+ if(getmaxx()!=lastw)
+ stale=true;
+}
+
+bool vs_text_layout::get_cursorvisible()
+{
+ return true;
+}
+
+/** The cursor is always located in the upper-left-hand corner. */
+point vs_text_layout::get_cursorloc()
+{
+ return point(0,0);
+}
+
+/** This widget can get focus if it can scroll: ie, if its contents take
+ * up more lines than it was allocated.
+ */
+bool vs_text_layout::focus_me()
+{
+ freshen_contents(lastst);
+
+ if(start>0 || contents.size()>(unsigned) getmaxy())
+ return true;
+ else
+ return false;
+}
+
+/** Paint by refreshing the contents [if necessary], then drawing,
+ * starting from the current line.
+ */
+void vs_text_layout::paint(const style &st)
+{
+ freshen_contents(st);
+
+ if(start>=contents.size())
+ {
+ if(contents.size()==0)
+ set_start(0);
+ else
+ set_start(contents.size()-1);
+ }
+
+ for(int i=0; i<getmaxy() && i+start<contents.size(); ++i)
+ mvaddnstr(i, 0, contents[i+start], contents[i+start].size());
+}
+
+void vs_text_layout::freshen_contents(const style &st)
+{
+ if(stale || lastw != getmaxx() || lastst != st)
+ {
+ contents=f->layout(getmaxx(), getmaxx(), st);
+ stale=false;
+ lastw=getmaxx();
+ lastst=st;
+
+ do_signal();
+ }
+}
+
+void vs_text_layout::line_down()
+{
+ freshen_contents(lastst);
+
+ if(start+getmaxy()<contents.size())
+ set_start(start+1);
+}
+
+void vs_text_layout::line_up()
+{
+ freshen_contents(lastst);
+
+ if(start>0)
+ set_start(start-1);
+}
+
+
+void vs_text_layout::move_to_top()
+{
+ set_start(0);
+}
+
+void vs_text_layout::move_to_bottom()
+{
+ freshen_contents(lastst);
+
+ set_start(max(start, contents.size()-getmaxy()));
+}
+
+void vs_text_layout::page_up()
+{
+ if(start<(unsigned) getmaxy())
+ set_start(0);
+ else
+ set_start(start-getmaxy());
+}
+
+void vs_text_layout::page_down()
+{
+ freshen_contents(lastst);
+
+ if(start+getmaxy()<contents.size())
+ set_start(start+getmaxy());
+}
+
+// Assumes the contents are already fresh.
+void vs_text_layout::do_signal()
+{
+ if(((unsigned) getmaxy())>=contents.size() && start==0)
+ location_changed(start, 0);
+ else if(start+getmaxy()>=contents.size())
+ location_changed(1, 1);
+ else
+ location_changed(start, contents.size()-getmaxy());
+}
+
+void vs_text_layout::search_for(const wstring &s, bool search_forward)
+{
+ freshen_contents(lastst);
+
+ if(getmaxy() == 0)
+ return;
+
+ // Very simplistic routine. Could be made quicker by incorporating
+ // a more sophisticated search algorithm.
+ size_t new_start = search_forward ? start + 1 : start - 1;
+
+ // Look for the first character of the string.
+ while(new_start > 0 && new_start < contents.size())
+ {
+ fragment_line &line(contents[new_start]);
+
+ // Search this line and the following lines (if there's an
+ // overrun) for our string.
+ for(fragment_line::const_iterator i = line.begin();
+ i != line.end(); ++i)
+ {
+ if(i->ch == s[0])
+ {
+ size_t tmp = new_start;
+ fragment_line::const_iterator j = i;
+ wstring::const_iterator loc = s.begin();
+
+ while(tmp < contents.size() && loc != s.end() &&
+ j->ch == *loc)
+ {
+ ++loc;
+ ++j;
+
+ if(j == contents[tmp].end())
+ {
+ ++tmp;
+ if(tmp < contents.size())
+ j = contents[tmp].begin();
+ }
+ }
+
+ if(loc == s.end()) // success
+ {
+ set_start(new_start);
+ return;
+ }
+ }
+ }
+
+ if(search_forward)
+ ++new_start;
+ else
+ --new_start;
+ }
+}
+
+void vs_text_layout::scroll(bool dir)
+{
+ if(dir)
+ page_up();
+ else
+ page_down();
+}
diff --git a/src/vscreen/vs_text_layout.h b/src/vscreen/vs_text_layout.h
new file mode 100644
index 00000000..532757da
--- /dev/null
+++ b/src/vscreen/vs_text_layout.h
@@ -0,0 +1,193 @@
+// vs_text_layout.h -*-c++-*-
+//
+// Copyright (C) 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+
+
+#ifndef VS_TEXT_LAYOUT_H
+#define VS_TEXT_LAYOUT_H
+
+#include "vscreen_widget.h"
+#include "fragment_contents.h"
+
+class fragment;
+
+/** Code to display formatted text.
+ *
+ * The text to display is composed of a tree of "fragments". A
+ * fragment stores some amount of text; at any time, it can be
+ * formatted to a particular width. The main layout mechanisms
+ * provided are flowboxes and clipboxes: flowboxes word-wrap their
+ * contents to a particular width, while clipboxes clip their
+ * contents to a particular width. These boxes can be nested, if the
+ * user feels like it for some reason or other, although some
+ * nestings are non-sensical (for instance, placing a flowbox inside
+ * a smaller flowbox is likely to lead to really ugly text).
+ *
+ * This provides some primitive layout mechanisms; higher-level
+ * layouts can be expressed in terms of these.
+ */
+class vs_text_layout:public vscreen_widget
+{
+protected:
+ vs_text_layout();
+ vs_text_layout(fragment *f);
+public:
+ /** Create an empty vs_text_layout. */
+ static ref_ptr<vs_text_layout> create()
+ {
+ ref_ptr<vs_text_layout> rval(new vs_text_layout);
+ rval->decref();
+ return rval;
+ }
+
+ /** Create a vs_text_layout with the given root fragment.
+ *
+ * All fragments are implicitly placed within a clipbox of width
+ * equal to the width of this widget.
+ */
+ static ref_ptr<vs_text_layout> create(fragment *f)
+ {
+ ref_ptr<vs_text_layout> rval(new vs_text_layout(f));
+ rval->decref();
+ return rval;
+ }
+
+ /** Handle the given keypress. Returns \b true if the keystroke
+ * was "consumed" by this widget.
+ */
+ bool handle_key(const key &k);
+
+ /** Change the fragment being displayed in this layout widget. */
+ void set_fragment(fragment *f);
+
+ /** Append the given fragment to the current fragment.
+ *
+ * \note this is slightly less efficient than placing the two
+ * fragments into the text_layout up-front via a single
+ * sequence_fragment. Normally this isn't a problem, but if you
+ * want to append hundreds of fragments this way, it might be.
+ *
+ * \todo this is only needed for memory-management reasons
+ * (otherwise I could safely extract the current fragment and
+ * create my own sequence). Would refcounting help?
+ *
+ * \todo if this becomes very useful, it would be better to just
+ * explicitly store a sequence of fragments in the layout.
+ */
+ void append_fragment(fragment *f);
+
+ /** Return the requested width of this widget. The requested width
+ * will be the largest possible width of any line.
+ */
+ int width_request();
+
+ /** Return the requested height of this widget given its width, by
+ * running the fragment-layout algorithm.
+ */
+ int height_request(int w);
+
+ /** Return \b true iff the cursor is visible in this widget. */
+ bool get_cursorvisible();
+
+ /** Return the location of the cursor in this widget. */
+ point get_cursorloc();
+
+ /** Return \b true iff this widget should be given focus. */
+ bool focus_me();
+
+ /** Paint this widget. */
+ void paint(const style &st);
+
+ /** Move the view one line down. */
+ void line_down();
+
+ /** Move the view one line up. */
+ void line_up();
+
+ /** Move the view to the top of the widget. */
+ void move_to_top();
+
+ /** Move the view to the bottom of the widget. */
+ void move_to_bottom();
+
+ /** Move a page forward. */
+ void page_down();
+
+ /** Move a page back. */
+ void page_up();
+
+ /** Search either forwards or backwards for the string s. The
+ * search will start on either the next or the previous line
+ * from the top of the screen.
+ */
+ void search_for(const std::wstring &s,
+ bool search_forwards);
+
+ /** Page based on a scrollbar signal.
+ *
+ * \param dir the direction to page: if \b true, call page_up();
+ * else call page_down().
+ */
+ void scroll(bool dir);
+
+ /** Delete the root fragment. */
+ ~vs_text_layout();
+
+ /** A signal that is called whenever the "location" of the view
+ * within the text changes.
+ */
+ sigc::signal2<void, int, int> location_changed;
+
+ static keybindings *bindings;
+
+ static void init_bindings();
+private:
+ /** Move the starting location of the widget. */
+ void set_start(unsigned int new_start);
+
+ /** Update the cached contents of the widget, if necessary. */
+ void freshen_contents(const style &st);
+
+ /** Called when this needs layout. */
+ void layout_me();
+
+ /** Emits the above signal based on the present location of the display. */
+ void do_signal();
+
+ /** The line which is currently at the top of the widget. */
+ size_t start;
+
+ /** The root fragment of this layout. This is always a clipbox. */
+ fragment *f;
+
+ /** Cache the current contents of the widget. */
+ fragment_contents contents;
+
+ /** If \b true, the current cached contents need to be updated. */
+ bool stale;
+
+ /** The width of the widget the last time we updated the cached contents. */
+ int lastw;
+
+ /** The enclosing display style the last time we updated the cached contents. */
+ style lastst;
+};
+
+typedef ref_ptr<vs_text_layout> vs_text_layout_ref;
+
+#endif
diff --git a/src/vscreen/vs_togglebutton.cc b/src/vscreen/vs_togglebutton.cc
new file mode 100644
index 00000000..6fd471b6
--- /dev/null
+++ b/src/vscreen/vs_togglebutton.cc
@@ -0,0 +1,81 @@
+// vs_togglebutton.cc
+
+#include "vscreen.h"
+#include "vs_togglebutton.h"
+
+#include "fragment.h"
+#include "fragment_cache.h"
+
+#include <algorithm>
+
+using namespace std;
+
+vs_togglebutton::vs_togglebutton(char _bracketl, char _mark, char _bracketr,
+ fragment *_label, bool _checked)
+:vs_button(_label), checked(_checked),
+ bracketl(_bracketl), mark(_mark), bracketr(_bracketr)
+{
+}
+
+vs_togglebutton::vs_togglebutton(char _bracketl, char _mark, char _bracketr,
+ const std::string &_label, bool _checked)
+:vs_button(_label), checked(_checked),
+ bracketl(_bracketl), mark(_mark), bracketr(_bracketr)
+{
+}
+
+void vs_togglebutton::paint_check(int row)
+{
+ mvaddch(row, 0, bracketl);
+
+ if(checked)
+ addch(mark);
+ else
+ addch(' ');
+
+ addch(bracketr);
+}
+
+point vs_togglebutton::get_cursorloc()
+{
+ return point(0, getmaxy()/2);
+}
+
+void vs_togglebutton::paint(const style &st)
+{
+ const size_t labelw=getmaxx()>=4?getmaxx()-4:0;
+ const fragment_contents lines=get_label()->layout(labelw, labelw, st);
+ const size_t checkheight=getmaxy()/2;
+
+ const style button_style=get_isfocussed()?st+style_attrs_flip(A_REVERSE):st;
+
+ for(size_t i=0; i<min<size_t>(lines.size(), getmaxy()); ++i)
+ {
+ if(i==checkheight)
+ {
+ apply_style(button_style);
+
+ paint_check(i);
+
+ apply_style(st);
+ }
+
+ mvaddnstr(i, 4, lines[i], lines[i].size());
+ }
+}
+
+void vs_togglebutton::do_toggle()
+{
+ checked=!checked;
+ toggled();
+ vscreen_update();
+}
+
+void vs_togglebutton::silent_set_checked(bool _checked)
+{
+ if(checked!=_checked)
+ {
+ checked=_checked;
+ vscreen_update();
+ }
+}
diff --git a/src/vscreen/vs_togglebutton.h b/src/vscreen/vs_togglebutton.h
new file mode 100644
index 00000000..bcbd9a0e
--- /dev/null
+++ b/src/vscreen/vs_togglebutton.h
@@ -0,0 +1,161 @@
+// vs_togglebutton.h -*-c++-*-
+//
+// I like having a "togglable" button which doesn't force a particular
+// policy..it makes radio buttons much easier to do right.
+
+#ifndef VS_TOGGLEBUTTON_H
+#define VS_TOGGLEBUTTON_H
+
+#include "vs_button.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+class vs_togglebutton:public vs_button
+{
+ bool checked;
+ char bracketl, mark, bracketr;
+
+ void paint_check(int row);
+
+protected:
+ void silent_set_checked(bool _checked);
+ // to be used mainly to avoid emitting signals (eg, if you're trying to
+ // coordinate a togglebutton with an underlying option)
+
+ vs_togglebutton(char _bracketl, char _mark, char _bracketr,
+ fragment *_label, bool _checked);
+
+ vs_togglebutton(char _bracketl, char _mark, char _bracketr,
+ const std::string &_label, bool _checked);
+
+public:
+ static ref_ptr<vs_togglebutton>
+ create(char bracketl, char mark, char bracketr,
+ fragment *label, bool checked = false)
+ {
+ ref_ptr<vs_togglebutton>
+ rval(new vs_togglebutton(bracketl, mark, bracketr,
+ label, checked));
+ rval->decref();
+ return rval;
+ }
+
+ static ref_ptr<vs_togglebutton>
+ create(char bracketl, char mark, char bracketr,
+ const std::string &label, bool checked = false)
+ {
+ ref_ptr<vs_togglebutton>
+ rval(new vs_togglebutton(bracketl, mark, bracketr,
+ label, checked));
+ rval->decref();
+ return rval;
+ }
+
+ point get_cursorloc();
+
+ void paint(const style &st);
+
+ bool get_checked() {return checked;}
+ void set_checked(bool _checked)
+ {
+ if(checked!=_checked)
+ do_toggle();
+ }
+
+ // named "do_toggle" to avoid typos wrt "toggled"
+ void do_toggle();
+
+ sigc::signal0<void> toggled;
+};
+
+class vs_checkbutton:public vs_togglebutton
+{
+protected:
+ vs_checkbutton(fragment *_label, bool _checked)
+ :vs_togglebutton('[', 'X', ']', _label, _checked)
+ {
+ pressed.connect(sigc::mem_fun(*this, &vs_togglebutton::do_toggle));
+ }
+
+ vs_checkbutton(const std::string &_label, bool _checked)
+ :vs_togglebutton('[', 'X', ']', _label, _checked)
+ {
+ pressed.connect(sigc::mem_fun(*this, &vs_togglebutton::do_toggle));
+ }
+
+ vs_checkbutton(char bracketr, char mark, char bracketl,
+ fragment *_label, bool _checked)
+ :vs_togglebutton(bracketr, mark, bracketl, _label, _checked)
+ {
+ pressed.connect(sigc::mem_fun(*this, &vs_togglebutton::do_toggle));
+ }
+
+ vs_checkbutton(char bracketr, char mark, char bracketl,
+ const std::string &_label, bool _checked)
+ :vs_togglebutton(bracketr, mark, bracketl, _label, _checked)
+ {
+ pressed.connect(sigc::mem_fun(*this, &vs_togglebutton::do_toggle));
+ }
+
+public:
+ static ref_ptr<vs_checkbutton>
+ create(fragment *label, bool checked = false)
+ {
+ return new vs_checkbutton(label, checked);
+ }
+
+ static ref_ptr<vs_checkbutton>
+ create(const std::string &label, bool checked = false)
+ {
+ return new vs_checkbutton(label, checked);
+ }
+
+ static ref_ptr<vs_checkbutton>
+ create(char bracketr, char mark, char bracketl,
+ fragment *label, bool checked = false)
+ {
+ return new vs_checkbutton(bracketr, mark, bracketl,
+ label, checked);
+ }
+
+ static ref_ptr<vs_checkbutton>
+ create(char bracketr, char mark, char bracketl,
+ const std::string &label, bool checked = false)
+ {
+ return new vs_checkbutton(bracketr, mark, bracketl,
+ label, checked);
+ }
+};
+
+class vs_radiobutton:public vs_togglebutton
+{
+protected:
+ vs_radiobutton(fragment *_label, bool _checked)
+ :vs_togglebutton('(', '*', ')', _label, _checked)
+ {
+ }
+
+ vs_radiobutton(const std::string &_label, bool _checked)
+ :vs_togglebutton('(', '*', ')', _label, _checked)
+ {
+ }
+
+public:
+ static ref_ptr<vs_radiobutton>
+ create(fragment *label, bool checked = false)
+ {
+ return new vs_radiobutton(label, checked);
+ }
+
+ static ref_ptr<vs_radiobutton>
+ create(const std::string &label, bool checked = false)
+ {
+ return new vs_radiobutton(label, checked);
+ }
+};
+
+typedef ref_ptr<vs_togglebutton> vs_togglebutton_ref;
+typedef ref_ptr<vs_checkbutton> vs_checkbutton_ref;
+typedef ref_ptr<vs_radiobutton> vs_radiobutton_ref;
+
+#endif
diff --git a/src/vscreen/vs_transient.cc b/src/vscreen/vs_transient.cc
new file mode 100644
index 00000000..be0336df
--- /dev/null
+++ b/src/vscreen/vs_transient.cc
@@ -0,0 +1,58 @@
+// vs_transient.cc
+//
+// Copyright 2005 Daniel Burrows
+
+#include "vs_transient.h"
+
+#include <sigc++/functors/mem_fun.h>
+
+vs_transient::vs_transient(const vs_widget_ref &w)
+{
+ set_subwidget(w);
+
+ do_layout.connect(sigc::mem_fun(*this, &vs_transient::layout_me));
+}
+
+void vs_transient::layout_me()
+{
+ vs_widget_ref w=get_subwidget();
+
+ if(w.valid())
+ {
+ if(w->get_visible())
+ w->alloc_size(0, 0, getmaxx(), getmaxy());
+ else
+ w->alloc_size(0, 0, 0, 0);
+ }
+}
+
+int vs_transient::width_request()
+{
+ vs_widget_ref w=get_subwidget();
+
+ if(w.valid())
+ return w->width_request();
+ else
+ return 0;
+}
+
+int vs_transient::height_request(int width)
+{
+ vs_widget_ref w=get_subwidget();
+
+ if(w.valid())
+ return w->height_request(width);
+ else
+ return 0;
+}
+
+bool vs_transient::focus_me()
+{
+ return true;
+}
+
+bool vs_transient::handle_char(chtype ch)
+{
+ destroy();
+ return true;
+}
diff --git a/src/vscreen/vs_transient.h b/src/vscreen/vs_transient.h
new file mode 100644
index 00000000..4f17b8d7
--- /dev/null
+++ b/src/vscreen/vs_transient.h
@@ -0,0 +1,59 @@
+// vs_transient.h -*-c++-*-
+//
+// Copyright 2005 Daniel Burrows
+
+#ifndef VS_TRANSIENT_H
+#define VS_TRANSIENT_H
+
+#include "vs_bin.h"
+
+/** This class is a visually transparent wrapper around another
+ * widget. It captures all keystrokes (preventing the subwidget from
+ * recieving them), and destroys itself upon receiving one.
+ */
+class vs_transient:public vs_bin
+{
+private:
+ /** Handle layout: the subwidget is assigned the entire area of this
+ * widget.
+ */
+ void layout_me();
+
+protected:
+ vs_transient(const vs_widget_ref &w);
+public:
+ /** Create a new vs_transient.
+ *
+ * \param w the widget to place inside the transient wrapper.
+ */
+ static ref_ptr<vs_transient>
+ create(const vs_widget_ref &w = NULL)
+ {
+ ref_ptr<vs_transient> rval(new vs_transient(w));
+ rval->decref();
+ return rval;
+ }
+
+ /** \return the desired width of the subwidget. */
+ int width_request();
+
+ /** Calculate the desired height of the subwidget.
+ *
+ * \param width the width of this widget
+ * \return the desired height
+ */
+ int height_request(int width);
+
+ /** \return \b true: vs_transients can always be focussed. */
+ bool focus_me();
+
+ /** Destroy the transient.
+ *
+ * \return \b true.
+ */
+ bool handle_char(chtype ch);
+};
+
+typedef ref_ptr<vs_transient> vs_transient_ref;
+
+#endif // VS_TRANSIENT_H
diff --git a/src/vscreen/vs_tree.cc b/src/vscreen/vs_tree.cc
new file mode 100644
index 00000000..3e4dc001
--- /dev/null
+++ b/src/vscreen/vs_tree.cc
@@ -0,0 +1,1001 @@
+// vs_tree.cc
+//
+// Copyright 1999-2002, 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Implementation of stuff in vs_tree.h
+
+#include "vs_tree.h"
+#include "vs_editline.h"
+#include "config/keybindings.h"
+#include "config/colors.h"
+#include "transcode.h"
+
+#include "../aptitude.h" // For _()
+
+#include <sigc++/functors/ptr_fun.h>
+
+using namespace std;
+
+keybindings *vs_tree::bindings=NULL;
+
+bool vs_tree_search_string::operator()(const vs_treeitem &item)
+{
+ return item.matches(s);
+}
+
+vs_tree::vs_tree()
+ :vscreen_widget(),
+ root(NULL),
+ begin(new vs_tree_root_iterator(NULL)),
+ end(begin),
+ top(begin),
+ selected(top),
+ hierarchical(true),
+ prev_level(NULL)
+{
+ focussed.connect(sigc::ptr_fun(vscreen_update));
+ unfocussed.connect(sigc::ptr_fun(vscreen_update));
+}
+
+vs_tree::vs_tree(vs_treeitem *_root, bool showroot)
+ :vscreen_widget(),
+ root(NULL),
+ begin(new vs_tree_root_iterator(NULL)),
+ end(begin),
+ top(begin),
+ selected(top),
+ hierarchical(true),
+ prev_level(NULL)
+{
+ set_root(_root, showroot);
+
+ focussed.connect(sigc::ptr_fun(vscreen_update));
+ unfocussed.connect(sigc::ptr_fun(vscreen_update));
+}
+
+vs_tree::~vs_tree()
+{
+ while(prev_level)
+ {
+ flat_frame *next=prev_level->next;
+ delete prev_level;
+ prev_level=next;
+ }
+
+ delete root; root=NULL;
+}
+
+void vs_tree::do_shown()
+{
+ if(selected!=end)
+ selected->highlighted(this);
+}
+
+int vs_tree::width_request()
+{
+ return 1;
+}
+
+int vs_tree::height_request(int w)
+{
+ return 1;
+}
+
+void vs_tree::set_root(vs_treeitem *_root, bool showroot)
+{
+ // Clear out the "history list"
+ while(prev_level)
+ {
+ flat_frame *next=prev_level->next;
+ delete prev_level;
+ prev_level=next;
+ }
+
+ if(selected!=end)
+ selected->unhighlighted(this);
+
+ if(root)
+ delete root;
+
+ root=_root;
+
+ if(root)
+ {
+ if(showroot)
+ {
+ vs_tree_root_iterator *realbegin=new vs_tree_root_iterator(_root);
+ // NOTE: realbegin will be DELETED when it is assigned to begin,
+ // because the temporary that wraps it will be destroyed.
+ // This is Just Plain Evil (probably conversion from vs_levelrefs
+ // to vs_treeiterators shouldn't be allowed) but the workaround is
+ // here: reference its end() routine *before* we assign it.
+
+ end=realbegin->end();
+ begin=realbegin; // now realbegin is INVALID!!
+ }
+ else
+ {
+ begin=_root->begin();
+ end=_root->end();
+ }
+
+ top=begin;
+ }
+ else
+ {
+ top=begin=end=new vs_tree_root_iterator(NULL);
+ }
+
+ selected=top;
+ while(selected!=end && !selected->get_selectable())
+ selected++;
+ if(selected!=end)
+ selected->highlighted(this);
+}
+
+void vs_tree::sync_bounds()
+ // As I said: yuck!
+{
+ begin=root->begin();
+ if(top==end)
+ top=begin;
+ if(selected==end)
+ selected=begin;
+ end=root->end();
+}
+
+int vs_tree::line_of(vs_treeiterator item)
+ // Returns the Y coordinate of the given item. (so we have to count
+ // from 1)
+{
+ int j;
+ vs_treeiterator i=top;
+ if(item==top)
+ return 1;
+
+ j=1;
+ do {
+ if(hierarchical)
+ ++i;
+ else
+ i.move_forward_level();
+
+ ++j;
+
+ if(i==item)
+ return j;
+ } while(i!=end);
+
+ i=top;
+ j=1;
+ do {
+ if(hierarchical)
+ --i;
+ else
+ i.move_backward_level();
+
+ --j;
+
+ if(i==item)
+ return j;
+ } while(i!=begin);
+
+ // Only happens if the iterator isn't in the visible range at all.
+ abort();
+}
+
+bool vs_tree::item_visible(vs_treeiterator pkg)
+{
+ int width,height;
+ vs_treeiterator i=top;
+
+ getmaxyx(height,width);
+
+ if(!hierarchical)
+ --height;
+
+ while(height>0 && i!=pkg && i!=end)
+ {
+ --height;
+ ++i;
+ }
+
+ return height>0 && i!=end;
+}
+
+void vs_tree::set_selection(vs_treeiterator to)
+{
+ // Expand all its parents so that it's possible to make it visible.
+ vs_treeiterator curr = to;
+ while(!curr.is_root())
+ {
+ curr = curr.get_up();
+ curr.expand();
+ }
+
+ // Expand the root as well if necessary.
+ if(curr != to)
+ curr.expand();
+
+ if(item_visible(to))
+ {
+ if(selected!=end)
+ selected->unhighlighted(this);
+ selected=to;
+ if(selected!=end)
+ selected->highlighted(this);
+
+ vscreen_update();
+ }
+ else
+ {
+ int height = getmaxy();
+ if(height == 0)
+ {
+ selected = top = to;
+ return;
+ }
+
+ // Give up and just directly determine the line of 'to'.
+ int l = line_of(to);
+
+ while(l < 1)
+ {
+ assert(top != end);
+
+ if(hierarchical)
+ --top;
+ else
+ top.move_backward_level();
+
+ ++l;
+ }
+
+ while(l > height)
+ {
+ assert(top != end);
+
+ if(hierarchical)
+ ++top;
+ else
+ top.move_forward_level();
+
+ --l;
+ }
+
+ if(selected != to)
+ {
+ if(selected != end)
+ selected->unhighlighted(this);
+
+ if(to != end)
+ to->highlighted(this);
+ }
+
+ selected = to;
+
+ vscreen_update();
+ }
+}
+
+bool vs_tree::get_cursorvisible()
+{
+ return (root != NULL && selected != end && selected->get_selectable());
+}
+
+point vs_tree::get_cursorloc()
+{
+ if(root == NULL)
+ return point(0, 0);
+ else if(selected==end || !selected->get_selectable())
+ return point(0,0);
+ else
+ return point(0, hierarchical?line_of(selected)-1:line_of(selected));
+}
+
+void vs_tree::line_down()
+{
+ if(root == NULL)
+ return;
+
+ int width,height;
+ getmaxyx(height,width);
+
+ if(!hierarchical)
+ --height;
+
+ vs_treeiterator orig = selected, prevtop = top;
+
+ int newline = line_of(selected);
+ int scrollcount = 0;
+ bool moved = false;
+
+ while(selected != end &&
+ scrollcount < 1 &&
+ (!moved || !selected->get_selectable()))
+ {
+ if(hierarchical)
+ ++selected;
+ else
+ selected.move_forward_level();
+
+ ++newline;
+ moved = true;
+
+ // If we fell off the end of the screen and not off the end of
+ // the list, scroll the screen forward.
+ if(newline > height && selected != end)
+ {
+ if(hierarchical)
+ ++top;
+ else
+ top.move_forward_level();
+
+ --newline;
+ ++scrollcount;
+ }
+ }
+
+ if(selected == end)
+ {
+ if(hierarchical)
+ --selected;
+ else
+ selected.move_backward_level();
+
+ --newline;
+ }
+
+ if(orig != selected)
+ {
+ if(orig != end)
+ orig->unhighlighted(this);
+
+ if(selected != end)
+ selected->highlighted(this);
+ }
+
+ vscreen_update();
+}
+
+void vs_tree::set_hierarchical(bool _hierarchical)
+{
+ if(_hierarchical!=hierarchical)
+ {
+ hierarchical=_hierarchical;
+
+ if(_hierarchical)
+ {
+ while(prev_level && prev_level->next)
+ {
+ flat_frame *next=prev_level->next;
+ delete prev_level;
+ prev_level=next;
+ }
+
+ if(prev_level)
+ {
+ top=prev_level->top;
+ begin=prev_level->begin;
+ end=prev_level->end;
+ selected=prev_level->selected;
+
+ delete prev_level;
+ prev_level=NULL;
+ }
+ }
+
+ vscreen_update();
+ }
+}
+
+void vs_tree::highlight_current()
+{
+ if(root != NULL && selected != end)
+ selected->highlighted(this);
+}
+
+void vs_tree::unhighlight_current()
+{
+ if(root != NULL && selected != end)
+ selected->unhighlighted(this);
+}
+
+void vs_tree::line_up()
+{
+ if(root == NULL)
+ return;
+
+ int width,height;
+ getmaxyx(height,width);
+
+ if(!hierarchical)
+ --height;
+
+ vs_treeiterator orig=selected;
+
+ bool moved = false;
+ int scrollcount = 0;
+
+ while(selected != begin &&
+ scrollcount < 1 &&
+ (!moved || !selected->get_selectable()))
+ {
+ if(selected == top)
+ {
+ if(hierarchical)
+ --top;
+ else
+ top.move_backward_level();
+
+ ++scrollcount;
+ }
+
+ if(hierarchical)
+ --selected;
+ else
+ selected.move_backward_level();
+ moved = true;
+ }
+
+ // Handle the special case where the first element of the tree is
+ // non-selectable.
+ if(selected == begin && !selected->get_selectable())
+ {
+ while(selected != end && !selected->get_selectable())
+ ++selected;
+
+ if(line_of(selected) >= height)
+ selected = begin;
+ }
+
+ if(orig != selected)
+ {
+ if(orig != end)
+ orig->unhighlighted(this);
+
+ if(selected != end)
+ selected->highlighted(this);
+ }
+
+ vscreen_update();
+}
+
+void vs_tree::page_down()
+{
+ if(root == NULL)
+ return;
+
+ int width,height;
+ getmaxyx(height,width);
+
+ if(!hierarchical)
+ --height;
+
+ int count=height;
+ vs_treeiterator newtop=top;
+ while(count>0 && newtop!=end)
+ {
+ if(hierarchical)
+ ++newtop;
+ else
+ newtop.move_forward_level();
+ count--;
+ }
+
+ if(count==0 && newtop!=end)
+ {
+ int l=0;
+ (*selected).unhighlighted(this);
+ selected=top=newtop;
+ while(l<height && selected!=end && !selected->get_selectable())
+ if(hierarchical)
+ ++selected;
+ else
+ selected.move_forward_level();
+ if(l==height || selected==end)
+ selected=top;
+ (*selected).highlighted(this);
+ vscreen_update();
+ }
+}
+
+void vs_tree::page_up()
+{
+ if(root == NULL)
+ return;
+
+ int width,height;
+ getmaxyx(height,width);
+
+ if(!hierarchical)
+ --height;
+
+ int count=height;
+ vs_treeiterator newtop=top;
+ while(count>0 && newtop!=begin)
+ {
+ if(hierarchical)
+ --newtop;
+ else
+ newtop.move_backward_level();
+ count--;
+ }
+
+ if(newtop!=top)
+ {
+ int l=0;
+ if(selected!=end)
+ (*selected).unhighlighted(this);
+ selected=top=newtop;
+ while(l<height && selected!=end && !selected->get_selectable())
+ if(hierarchical)
+ ++selected;
+ else
+ selected.move_forward_level();
+ if(l==height || selected==end)
+ selected=top;
+
+ if(selected!=end)
+ (*selected).unhighlighted(this);
+ vscreen_update();
+ }
+}
+
+void vs_tree::jump_to_begin()
+{
+ if(root == NULL)
+ return;
+
+ int width,height;
+ getmaxyx(height,width);
+
+ if(!hierarchical)
+ --height;
+
+ int l=0;
+ vs_treeiterator prev=selected;
+
+ if(selected!=end)
+ selected->unhighlighted(this);
+
+ selected=begin;
+ while(l<height && selected!=end && !selected->get_selectable())
+ if(hierarchical)
+ ++selected;
+ else
+ selected.move_forward_level();
+ if(l==height || selected==end)
+ selected=begin;
+
+ if(selected!=end)
+ selected->highlighted(this);
+
+ if(top!=begin)
+ top=begin;
+
+ vscreen_update();
+}
+
+void vs_tree::jump_to_end()
+{
+ if(root == NULL)
+ return;
+
+ int width,height;
+ getmaxyx(height,width);
+
+ if(!hierarchical)
+ --height;
+
+ int l=-1;
+ vs_treeiterator last=end,newtop=end,prev=selected;
+ if(hierarchical)
+ --last;
+ else
+ last.move_backward_level();
+ while(newtop!=begin && newtop!=top && height>0)
+ {
+ if(hierarchical)
+ --newtop;
+ else
+ newtop.move_backward_level();
+ --height;
+ l++;
+ }
+
+ if(selected!=end)
+ selected->unhighlighted(this);
+
+ selected=last;
+ while(l>=0 && selected!=end && !selected->get_selectable())
+ {
+ if(hierarchical)
+ --selected;
+ else
+ selected.move_backward_level();
+ l--;
+ }
+ if(selected==end && l<0)
+ selected=last;
+
+ if(selected!=end)
+ selected->highlighted(this);
+
+ if(newtop!=top)
+ top=newtop;
+
+ vscreen_update();
+}
+
+void vs_tree::level_line_up()
+{
+ if(root == NULL)
+ return;
+
+ vs_treeiterator tmp=selected;
+ tmp.move_backward_level();
+ if(tmp!=end)
+ set_selection(tmp);
+}
+
+void vs_tree::level_line_down()
+{
+ if(root == NULL)
+ return;
+
+ vs_treeiterator tmp=selected;
+ tmp.move_forward_level();
+ if(tmp!=end)
+ set_selection(tmp);
+}
+
+bool vs_tree::handle_key(const key &k)
+{
+ // umm...
+ //width++;
+ //height++;
+
+ if(selected!=vs_treeiterator(NULL))
+ {
+ if(root != NULL && hierarchical && bindings->key_matches(k, "Parent"))
+ {
+ if(!selected.is_root())
+ set_selection(selected.get_up());
+ }
+ else if(root != NULL && !hierarchical && prev_level && bindings->key_matches(k, "Left"))
+ {
+ selected->unhighlighted(this);
+
+ top=prev_level->top;
+ begin=prev_level->begin;
+ end=prev_level->end;
+ selected=prev_level->selected;
+
+ flat_frame *next=prev_level->next;
+ delete prev_level;
+ prev_level=next;
+
+ selected->highlighted(this);
+
+ vscreen_update();
+ }
+ else if(root != NULL && !hierarchical &&
+ selected!=end && selected->get_selectable() &&
+ selected->begin()!=selected->end() &&
+ (bindings->key_matches(k, "Right") ||
+ bindings->key_matches(k, "Confirm")))
+ {
+ selected->unhighlighted(this);
+ prev_level=new flat_frame(begin, end, top, selected, prev_level);
+
+ begin=selected->begin();
+ end=selected->end();
+ top=begin;
+ selected=begin;
+
+ selected->highlighted(this);
+
+ vscreen_update();
+ }
+ else if(bindings->key_matches(k, "Down"))
+ line_down();
+ else if(bindings->key_matches(k, "Up"))
+ line_up();
+ else if(bindings->key_matches(k, "NextPage"))
+ page_down();
+ else if(bindings->key_matches(k, "PrevPage"))
+ page_up();
+ else if(bindings->key_matches(k, "Begin"))
+ jump_to_begin();
+ else if(bindings->key_matches(k, "End"))
+ jump_to_end();
+ else if(bindings->key_matches(k, "LevelUp"))
+ level_line_up();
+ else if(bindings->key_matches(k, "LevelDown"))
+ level_line_down();
+ /*else if(bindings->key_matches(ch, "Search"))
+ {
+ vs_statusedit *ed=new vs_statusedit("Search for: ");
+ ed->entered.connect(sigc::mem_fun(this, &vs_tree::search_for));
+ add_widget(ed);
+ vscreen_update();
+ }
+ else if(bindings->key_matches(ch, "ReSearch"))
+ search_for("");*/
+ else
+ {
+ if(root != NULL && selected!=end && selected->get_selectable() &&
+ selected->dispatch_key(k, this))
+ vscreen_update();
+ else
+ return vscreen_widget::handle_key(k);
+ }
+ return true;
+ }
+ return false;
+}
+
+void vs_tree::search_for(vs_tree_search_func &matches)
+{
+ if(root == NULL)
+ return;
+
+ vs_treeiterator curr((selected==vs_treeiterator(NULL))?begin:selected, hierarchical),
+ start(curr);
+ // Make an iterator that ignores all the rules >=)
+
+ if(curr!=end)
+ {
+ if(hierarchical)
+ ++curr;
+ else
+ curr.move_forward_level();
+
+ // Don't forget this case!
+ if(curr==end)
+ curr=begin;
+ }
+
+ while(curr!=start && !matches(*curr))
+ {
+ if(hierarchical)
+ ++curr;
+ else
+ curr.move_forward_level();
+
+ if(curr==end)
+ curr=begin;
+ }
+
+ if(curr==start)
+ beep();
+ else
+ {
+ set_selection(curr);
+ vscreen_update();
+ }
+}
+
+void vs_tree::search_back_for(vs_tree_search_func &matches)
+{
+ if(root == NULL)
+ return;
+
+ vs_treeiterator curr((selected == vs_treeiterator(NULL))
+ ? begin : selected, hierarchical),
+ start(curr);
+
+ // Skip the starting location, cycling to the end.
+ if(curr != begin)
+ {
+ if(hierarchical)
+ --curr;
+ else
+ curr.move_backward_level();
+ }
+ else
+ {
+ if(hierarchical)
+ {
+ curr = end;
+ --curr;
+ }
+ else
+ {
+ vs_treeiterator curr2 = curr;
+ curr2.move_forward_level();
+
+ while(curr2 != curr)
+ {
+ curr = curr2;
+ curr2.move_forward_level();
+ }
+ }
+ }
+
+ while(curr != start && !matches(*curr))
+ {
+ // Code duplication alert
+ if(curr != begin)
+ {
+ if(hierarchical)
+ --curr;
+ else
+ curr.move_backward_level();
+ }
+ else
+ {
+ if(hierarchical)
+ {
+ curr = end;
+ --curr;
+ }
+ else
+ {
+ vs_treeiterator curr2 = curr;
+ curr2.move_forward_level();
+
+ while(curr2 != curr)
+ {
+ curr = curr2;
+ curr2.move_forward_level();
+ }
+ }
+ }
+ }
+
+ if(curr == start)
+ beep();
+ else
+ {
+ set_selection(curr);
+ vscreen_update();
+ }
+}
+
+void vs_tree::paint(const style &st)
+{
+ if(root == NULL)
+ return;
+
+ int width,height;
+ int selectedln=line_of(selected);
+
+ getmaxyx(height,width);
+
+ if(selectedln>height)
+ {
+ while(selected!=top && selectedln>height)
+ {
+ if(hierarchical)
+ ++top;
+ else
+ top.move_forward_level();
+ selectedln--;
+ }
+ }
+ else
+ {
+ while(selected!=top && selectedln<0)
+ {
+ if(hierarchical)
+ --top;
+ else
+ top.move_backward_level();
+ selectedln++;
+ }
+ }
+
+ //if(selected!=end && selected->get_selectable())
+ //selected->highlighted(this);
+ // Some classes need this to update display stuff properly. For instance,
+ // when a new pkg_tree is created, its 'update the status line' signal
+ // won't be properly called without this.
+
+ vs_treeiterator i=top;
+ int y=0;
+
+ if(!hierarchical && y<height)
+ {
+ wstring todisp;
+
+ // Uh...I'd rather use the iterators to do this..
+ flat_frame *curr=prev_level;
+ while(curr)
+ {
+ if(todisp.empty())
+ todisp=curr->selected->label()+todisp;
+ else
+ todisp=curr->selected->label()+(L"::"+todisp);
+ curr=curr->next;
+ }
+
+ if(todisp.empty())
+ todisp=transcode(_("TOP LEVEL"));
+
+ while(todisp.size()<(unsigned) width)
+ todisp+=L" ";
+
+ apply_style(st+get_style("Header"));
+ mvaddnstr(y, 0, todisp.c_str(), width);
+
+ ++y;
+ }
+
+ // FIXME: this is a hack around nasty edge cases. All the tree code needs
+ // a rewrite.
+ vs_treeiterator prev=i;
+ while(y<height && i!=end)
+ {
+ vs_treeitem *curr=&*i;
+
+ style curr_st;
+
+ if(get_isfocussed() && i==selected && i->get_selectable())
+ curr_st = st+curr->get_highlight_style();
+ else
+ curr_st = st+curr->get_normal_style();
+
+ apply_style(curr_st);
+ curr->paint(this, y, hierarchical, curr_st);
+
+ if(hierarchical)
+ ++i;
+ else
+ i.move_forward_level();
+ y++;
+
+ // FIXME: this is a hack.
+ if(i==prev) // If we hit the end, it will refuse to advance.
+ break;
+ prev=i;
+ }
+}
+
+void vs_tree::dispatch_mouse(short id, int x, int y, int z, mmask_t bstate)
+{
+ if(root == NULL)
+ return;
+
+ if(!hierarchical)
+ --y;
+
+ vs_treeiterator i=top;
+ while(y>0 && i!=end)
+ {
+ if(hierarchical)
+ ++i;
+ else
+ i.move_forward_level();
+
+ --y;
+ }
+
+ if(y==0 && i!=end)
+ {
+ set_selection(i);
+
+ i->dispatch_mouse(id, x, bstate, this);
+ }
+}
+
+void vs_tree::init_bindings()
+{
+ bindings=new keybindings(&global_bindings);
+}
diff --git a/src/vscreen/vs_tree.h b/src/vscreen/vs_tree.h
new file mode 100644
index 00000000..46acaca3
--- /dev/null
+++ b/src/vscreen/vs_tree.h
@@ -0,0 +1,197 @@
+// vs_tree.h (this is -*-c++-*-)
+//
+// Copyright 1999-2001, 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// A simple tree displayer that is a vscreen by itself. Note that
+// you can't modify a tree's contents once it has been created (although you
+// can change the display order), and that everything put into the tree until
+// the first subtree will display as a flat list.
+//
+// Displaying the tree-ness is up to the derived classes (a generic
+// indentation policy probably won't work)
+//
+// One more point: after much consideration, I have decided that
+// *HETEROGENOUS TREES ARE NOT ALLOWED*. There's just no good way to flexibly
+// sort such a beast. I don't anticipate this being a major problem, though.
+// (note that while not allowed in general, they can sort of be done -- just
+// sort with the key item. A default class that does just this is provided)
+
+#ifndef VS_TREE_H
+#define VS_TREE_H
+
+#include "vscreen_widget.h"
+#include "vs_treeitem.h"
+
+#include <list>
+#include <assert.h>
+
+class keybindings;
+
+// A predicate on vs_treeitems:
+class vs_tree_search_func
+{
+public:
+ virtual bool operator()(const vs_treeitem &item)=0;
+ virtual ~vs_tree_search_func() {}
+};
+
+class vs_tree_search_string:public vs_tree_search_func
+{
+ std::wstring s;
+public:
+ vs_tree_search_string(const std::wstring &_s):s(_s) {}
+
+ virtual bool operator()(const vs_treeitem &item);
+};
+
+class vs_tree:public vscreen_widget
+{
+ vs_treeitem *root;
+ vs_treeiterator begin, end;
+
+ vs_treeiterator top;
+ vs_treeiterator selected;
+ // The top item on the current page and the currently selected item.
+ // NOTE: it's implicitly assumed in many places in the code that the
+ // currently selected item is visible (ie, on the screen).
+
+ bool hierarchical;
+ // If not true, display the tree as a series of "flat thingies".
+ // Must be seen to be described :)
+
+ // This structure is used to easily retrace our steps in flat-mode.
+ // (it could probably be done without this, but this makes it MUCH simpler)
+ // Note that we don't even bother with an STL list here; it's
+ // just not worth it.
+ struct flat_frame
+ {
+ vs_treeiterator begin, end, top, selected;
+
+ flat_frame *next;
+ flat_frame(vs_treeiterator _begin,
+ vs_treeiterator _end,
+ vs_treeiterator _top,
+ vs_treeiterator _selected,
+ flat_frame *_next)
+ :begin(_begin), end(_end), top(_top), selected(_selected), next(_next) {}
+ };
+ flat_frame *prev_level;
+
+ int line_of(vs_treeiterator item);
+ bool item_visible(vs_treeiterator item);
+
+ void do_shown();
+protected:
+ vs_treeiterator get_selected() {return selected;}
+ vs_treeiterator get_begin() {return begin;}
+ vs_treeiterator get_end() {return end;}
+
+ void sync_bounds();
+ // This is an awful hack; I've been thinking about an alternate design of
+ // the tree code for a while, and this just confirms it. Yuck! :)
+ // It'll be the first thing to be removed in the next version..
+ // -- well, it wasn't.
+
+ virtual bool handle_key(const key &k);
+
+protected:
+ vs_tree();
+ vs_tree(vs_treeitem *_root, bool showroot);
+
+public:
+ static ref_ptr<vs_tree>
+ create()
+ {
+ ref_ptr<vs_tree> rval(new vs_tree);
+ rval->decref();
+ return rval;
+ }
+
+ static ref_ptr<vs_tree>
+ create(vs_treeitem *root, bool showroot = false)
+ {
+ ref_ptr<vs_tree> rval(new vs_tree(root, showroot));
+ rval->decref();
+ return rval;
+ }
+
+ void set_root(vs_treeitem *_root, bool showroot=false);
+
+ /** \return the desired width of the widget. */
+ int width_request();
+
+ /** \param w the width of the widget.
+ *
+ * \return the desired height of the widget for the given width.
+ */
+ int height_request(int w);
+
+ bool vs_tree::get_cursorvisible();
+ point vs_tree::get_cursorloc();
+ virtual bool focus_me() {return true;}
+ virtual void paint(const style &st);
+ virtual void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+ void set_selection(vs_treeiterator to);
+ // Directly sets the selection to a given item. [ the item must be
+ // visible -- ie, all its parents must be expanded ]
+ // Causes a redraw.
+ virtual ~vs_tree();
+
+ void search_for(vs_tree_search_func &matches);
+ void search_for(const std::wstring &s)
+ {
+ vs_tree_search_string matches(s);
+ search_for(matches);
+ }
+
+ void search_back_for(vs_tree_search_func &matches);
+ void search_back_for(const std::wstring &s)
+ {
+ vs_tree_search_string matches(s);
+ search_back_for(matches);
+ }
+
+ void set_hierarchical(bool _hierarchical);
+ bool get_hierarchical() {return hierarchical;}
+
+ /** Send a 'highlighted' message to the currently selected item. */
+ void highlight_current();
+
+ /** Send an 'unhighlighted' message to the currently selected item. */
+ void unhighlight_current();
+
+
+ // Execute the given command
+ void line_up();
+ void line_down();
+ void page_up();
+ void page_down();
+ void jump_to_begin();
+ void jump_to_end();
+ void level_line_up();
+ void level_line_down();
+
+ static keybindings *bindings;
+ static void init_bindings();
+ // Sets up the bindings..
+};
+
+typedef ref_ptr<vs_tree> vs_tree_ref;
+
+#endif
diff --git a/src/vscreen/vs_treeitem.cc b/src/vscreen/vs_treeitem.cc
new file mode 100644
index 00000000..fc7bb458
--- /dev/null
+++ b/src/vscreen/vs_treeitem.cc
@@ -0,0 +1,50 @@
+// vs_treeitem.cc
+//
+// Copyright 1999 Daniel Burrows
+//
+// Implementation of stuff in vs_treeitem.h
+
+#include "vs_treeitem.h"
+#include "vs_tree.h"
+
+using namespace std;
+
+void vs_treeitem::highlighted(vs_tree *win)
+{
+}
+
+void vs_treeitem::paint(vs_tree *win, int y, bool hierarchical,
+ const wstring &str, int depth_shift)
+{
+ int width, height;
+ int basex=hierarchical?depth_shift*get_depth():0;
+ win->getmaxyx(height,width);
+
+ win->move(y,0);
+ int x=0;
+
+ while(x<basex && x<width)
+ {
+ win->add_wch(L' ');
+ x+=wcwidth(L' ');
+ }
+
+ if(x>=width)
+ return;
+
+ size_t i=0;
+ while(i<str.size())
+ {
+ wchar_t ch=str[i];
+
+ win->add_wch(ch);
+ x+=wcwidth(ch);
+ ++i;
+ }
+
+ while(x<width)
+ {
+ win->add_wch(L' ');
+ x+=wcwidth(L' ');
+ }
+}
diff --git a/src/vscreen/vs_treeitem.h b/src/vscreen/vs_treeitem.h
new file mode 100644
index 00000000..5906afe3
--- /dev/null
+++ b/src/vscreen/vs_treeitem.h
@@ -0,0 +1,398 @@
+// vs_treeitem.h (this is -*-c++-*-)
+//
+// Copyright 1999-2001, 2004-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// The basic tree item declaration (moved here to reduce the size of
+// vs_tree.h)
+//
+// I think this is *TOO* general. A simplified version will probably be
+// introduced at some point -- but the API should be fairly stable and nothing
+// outside this file is likely to change significantly. Yeah, right.
+
+#ifndef VS_TREEITEM_H
+#define VS_TREEITEM_H
+
+#include <stdlib.h>
+#include "curses++.h"
+#include "config/style.h"
+
+#include "vs_minibuf_win.h"
+
+class vs_tree;
+class vs_treeitem;
+class sortpolicy;
+
+class vs_tree_levelref
+// A generic way to iterate over a *single level* of a tree. Not really an
+// "iterator" since it doesn't do the operators and so on.
+{
+ vs_tree_levelref *parent;
+public:
+ vs_tree_levelref():parent(NULL) {}
+ vs_tree_levelref(const vs_tree_levelref &x)
+ :parent(x.parent?x.parent->clone():NULL) {}
+ virtual ~vs_tree_levelref() {}
+
+ virtual vs_treeitem *get_item()=0;
+ virtual void advance_next()=0;
+ virtual void return_prev()=0;
+ // Like ++ and --, sort of..
+
+ virtual bool is_begin()=0;
+ // Should return true if this iterator is at the front of a list (so going
+ // back will do Really Bad Things) -- many STL containers need this..
+ virtual bool is_end()=0;
+ // Should return true if this iterator no longer refers to something valid.
+
+ virtual vs_tree_levelref *clone() const=0;
+
+ friend class vs_treeiterator;
+};
+
+class vs_tree_root_iterator:public vs_tree_levelref
+// A dummy iterator for use on parent-less trees (all other iterators
+// assume that they have a valid parent -- er?)
+{
+ vs_treeitem *val;
+ vs_treeitem *prevval;
+
+ vs_treeitem *get_item() {return val;}
+ public:
+ vs_tree_root_iterator(vs_treeitem *_val):val(_val), prevval(NULL) {}
+ vs_tree_root_iterator(const vs_tree_root_iterator &x):vs_tree_levelref(x), val(x.val), prevval(x.prevval) {}
+
+ void advance_next() {if(val) { prevval=val; val=NULL;} }
+ void return_prev() {if(prevval) {val=prevval; prevval=NULL;} }
+
+ vs_tree_root_iterator *clone() const {return new vs_tree_root_iterator(*this);}
+
+ bool is_end() {return !val;}
+ bool is_begin() {return prevval==NULL;}
+
+ // Returns an "end iterator" for this tree
+ vs_tree_root_iterator *end()
+ {
+ vs_tree_root_iterator *rval=new vs_tree_root_iterator(*this);
+ rval->advance_next();
+ return rval;
+ }
+};
+
+class vs_treeitem
+// An abstracted item (could be a leaf node or a subtree) -- this isn't really
+// meant to be that useful in general, derive from the specialized children
+// instead.
+{
+ int depth;
+ bool selectable;
+ protected:
+ virtual void set_depth(int _depth) {depth=_depth;}
+ virtual void set_selectable(bool _selectable) {selectable=_selectable;}
+ public:
+ vs_treeitem(bool _selectable=true):depth(0),selectable(_selectable) {}
+
+ /** Display this item and this item only (does not descend to the
+ * children of the item, if any). The current style of the
+ * corresponding tree widget will be initialized using
+ * get_normal_style() and/or get_highlight_style() prior to the
+ * invocation of this method.
+ *
+ * \param win the tree in which to paint this item
+ * \param y the y location at which to paint this item
+ * \param hierarchical if \b true, paint this item as an
+ * entry in a 'hierarchical' tree.
+ * \param st the style with which this item is being displayed.
+ */
+ virtual void paint(vs_tree *win, int y, bool hierarchical,
+ const style &st)=0;
+
+ /** Display the given text as the label of this item at the given
+ * shifted depth.
+ *
+ * \param win the tree in which to paint this item
+ * \param y the y location at which to paint this item
+ * \param hierarchical if \b true, paint this item as an
+ * entry in a 'hierarchical' tree
+ * \param st the style with which this item is to be displayed.
+ */
+ void paint(vs_tree *win, int y, bool hierarchical,
+ const std::wstring &str, int depth_shift=2);
+
+ virtual const wchar_t *tag()=0;
+ // The tag that this item should be sorted by [for the trivial version of
+ // the subtree object]
+ virtual const wchar_t *label()=0;
+ // The label to display when this item is "active" in non-hierarchical mode.
+
+ int get_depth() {return depth;}
+ bool get_selectable() {return selectable;}
+
+ virtual style get_normal_style() {return style();}
+ virtual style get_highlight_style() {return get_normal_style()+style_attrs_flip(A_REVERSE);}
+
+ virtual void sort(sortpolicy &sort_method) {}
+ // Sorts an item's subtree using the given method.
+
+ virtual void sort() {}
+ // Sorts an item's subtree (NOP for most items) -- provided to make it easy
+ // to recursively sort the list.
+
+ virtual void highlighted(vs_tree *win);
+ // Called when an item is highlighted
+ virtual void unhighlighted(vs_tree *win) {}
+ // Called when the highlight leaves an item.
+ virtual bool dispatch_key(const key &k, vs_tree *owner) {return false;}
+ // Called when a key is pressed while the item is highlighted. The return
+ // value indicates whether a redraw of the screen is needed.
+ // (not any more; now it indicates whether the item consumed the keystroke
+ // -- this also triggers a screen repainting, but is a more useful semantic)
+ virtual void dispatch_mouse(short id, int x, mmask_t bstate, vs_tree *owner)
+ {
+ }
+ // Called when a mouse event occurs at the y location assigned to this
+ // item.
+
+ virtual vs_tree_levelref *begin() {return NULL;}
+ virtual vs_tree_levelref *end() {return NULL;}
+ // These can act like containers; these routines return a NEWLY ALLOCATED
+ // POINTER, though.
+
+ virtual bool has_visible_children() {return false;}
+ virtual bool has_children() {return false;}
+
+ virtual bool matches(const std::wstring &s) const {return false;}
+ // Returns true if this item matches the given string.
+
+ // More hackery: do what's needed to expand the children of this item.
+ virtual void expand() {}
+ virtual void expand_all() {}
+ virtual void collapse_all() {}
+
+ template<class childtype, class sorter>
+ friend class vs_subtree;
+
+ virtual ~vs_treeitem() {}
+};
+
+class vs_treeiterator
+{
+ vs_tree_levelref *curr;
+ bool ignore_collapsed;
+
+public:
+ vs_treeiterator(vs_tree_levelref *_curr, bool _ignore_collapsed=false):curr(_curr),ignore_collapsed(_ignore_collapsed) {}
+ vs_treeiterator(const vs_treeiterator &x):curr(x.curr?x.curr->clone():NULL),ignore_collapsed(x.ignore_collapsed) {}
+ vs_treeiterator(const vs_treeiterator &x, bool _ignore_collapsed):curr(x.curr?x.curr->clone():NULL),ignore_collapsed(_ignore_collapsed) {}
+
+ bool is_root() {return curr->parent==NULL;}
+ // Ugh, a really nasty hack.
+ vs_treeiterator get_up()
+ {
+ return vs_treeiterator(curr->parent->clone());
+ }
+
+ void expand()
+ {
+ if(curr && curr->get_item())
+ curr->get_item()->expand();
+ }
+
+ vs_treeitem &operator*() {return *curr->get_item();}
+ const vs_treeitem &operator*() const {return *curr->get_item();}
+ vs_treeitem *operator->() {return curr->get_item();}
+ const vs_treeitem *operator->() const {return curr->get_item();}
+
+ bool operator==(const vs_treeiterator &x)
+ {
+ if(!curr)
+ return !x.curr;
+ else if(!x.curr)
+ return false;
+ else if(curr->is_end())
+ return x.curr->is_end() && curr->parent == x.curr->parent;
+ else if(x.curr->is_end())
+ return false;
+ else
+ return curr->get_item()==x.curr->get_item();
+ }
+ bool operator!=(const vs_treeiterator &x)
+ {return !(*this==x);}
+
+ vs_treeiterator &operator=(const vs_treeiterator &x)
+ {
+ while(curr)
+ {
+ vs_tree_levelref *old=curr;
+ curr=curr->parent;
+ delete old;
+ }
+
+ curr=x.curr?x.curr->clone():NULL;
+
+ return *this;
+ }
+
+ vs_treeiterator &operator++()
+ {
+ if(curr->get_item() &&
+ (ignore_collapsed?curr->get_item()->has_children():curr->get_item()->has_visible_children()))
+ {
+ vs_tree_levelref *newref=curr->get_item()->begin();
+ newref->parent=curr;
+ curr=newref;
+ }
+ else
+ {
+ curr->advance_next();
+
+ while(curr->is_end() && curr->parent)
+ {
+ vs_tree_levelref *old=curr;
+ curr=curr->parent;
+ curr->advance_next();
+ delete old;
+ }
+ }
+
+ return *this;
+ }
+ vs_treeiterator operator++(int)
+ {
+ vs_treeiterator oldval(curr?curr->clone():NULL);
+
+ ++*this;
+
+ return oldval;
+ }
+
+ vs_treeiterator &operator--()
+ {
+ if(curr->is_begin())
+ {
+ if(curr->parent)
+ {
+ vs_tree_levelref *old=curr;
+ curr=curr->parent;
+ delete old;
+ }
+ }
+ else
+ {
+ curr->return_prev();
+ while(curr->get_item() &&
+ (ignore_collapsed?curr->get_item()->has_children():curr->get_item()->has_visible_children()))
+ {
+ vs_tree_levelref *newref=curr->get_item()->end();
+
+ newref->parent=curr;
+ newref->return_prev();
+ curr=newref;
+ }
+ }
+ return *this;
+ }
+
+ vs_treeiterator operator--(int)
+ {
+ vs_treeiterator oldval(curr?curr->clone():NULL);
+
+ --*this;
+
+ return oldval;
+ }
+
+ void move_forward_level()
+ {
+ if(!curr->is_end())
+ {
+ vs_tree_levelref *old=curr->clone();
+ curr->advance_next();
+
+ if(curr->is_end())
+ {
+ delete curr;
+ curr=old;
+ }
+ else
+ delete old;
+ }
+ }
+
+ void move_backward_level()
+ {
+ if(!curr->is_begin())
+ curr->return_prev();
+ }
+
+ ~vs_treeiterator()
+ {
+ while(curr)
+ {
+ vs_tree_levelref *old=curr;
+ curr=curr->parent;
+ delete old;
+ }
+ }
+};
+
+// A generic sorter.
+//
+// Making operator() virtual is a bit icky.
+class sortpolicy
+{
+public:
+ sortpolicy() {}
+
+ virtual bool operator()(vs_treeitem *item1,
+ vs_treeitem *item2)=0;
+
+ virtual ~sortpolicy() {}
+};
+
+// ack! How dare this be templated?
+//template<class childtype>
+class tag_sort_policy:public sortpolicy
+{
+public:
+ bool operator()(vs_treeitem *item1, vs_treeitem *item2)
+ {
+ return (wcscmp(item1->tag(), item2->tag())<0);
+ }
+};
+
+// Hack? hmm..
+//
+// Here's the situation: STL sort passes the sort method by value, probably
+// to keep people from being horrendously inefficient like I'm about to. But
+// we can get around that with an inline wrapper:
+class sortpolicy_wrapper
+{
+ sortpolicy &real_policy;
+public:
+ sortpolicy_wrapper(sortpolicy &_real_policy):real_policy(_real_policy)
+ {
+ }
+
+ inline bool operator()(vs_treeitem *item1,
+ vs_treeitem *item2) const
+ {
+ return real_policy(item1, item2);
+ }
+};
+
+#endif
diff --git a/src/vscreen/vs_util.cc b/src/vscreen/vs_util.cc
new file mode 100644
index 00000000..7a7db915
--- /dev/null
+++ b/src/vscreen/vs_util.cc
@@ -0,0 +1,396 @@
+// vs_util.cc
+
+#include "transcode.h"
+#include "vs_button.h"
+#include "vs_center.h"
+#include "vs_editline.h"
+#include "vs_frame.h"
+#include "vs_label.h"
+#include "vs_pager.h"
+#include "vs_util.h"
+#include "vs_scrollbar.h"
+#include "vs_table.h"
+#include "vs_text_layout.h"
+
+#include <config/colors.h>
+#include <config/keybindings.h>
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+#include <aptitude.h>
+
+using namespace std;
+
+vs_widget_ref vs_dialog_ok(const vs_widget_ref &w,
+ slot0arg okslot,
+ const wstring &label,
+ const style &st)
+{
+ vs_center_ref center = vs_center::create();
+
+ vs_table_ref table = vs_table::create();
+
+ vs_button_ref okbutton = vs_button::create(label);
+
+ okbutton->pressed.connect(sigc::mem_fun(*center.unsafe_get_ref(), &vscreen_widget::destroy));
+ if(okslot)
+ okbutton->pressed.connect(*okslot);
+
+ table->add_widget(w, 0, 0, 1, 1, true, true);
+ table->add_widget(vs_center::create(okbutton), 1, 0, 1, 1, false, false);
+ table->connect_key("Confirm", &global_bindings, okbutton->pressed.make_slot());
+
+ vs_frame_ref frame = vs_frame::create(table);
+
+ center->add_widget(frame);
+ frame->set_bg_style(st);
+ return center;
+}
+
+vs_widget_ref vs_dialog_ok(fragment *msg, slot0arg okslot,
+ const wstring &label,
+ const style &st, bool scrollbar)
+{
+ vs_widget_ref w;
+
+ if(scrollbar)
+ {
+ vs_table_ref t = vs_table::create();
+ w=t;
+
+ vs_text_layout_ref l = vs_text_layout::create(msg);
+ vs_scrollbar_ref s = vs_scrollbar::create(vs_scrollbar::VERTICAL);
+
+ t->add_widget(l, 0, 0, 1, 1, true, true);
+ t->add_widget_opts(s, 0, 1, 1, 1,
+ vs_table::ALIGN_RIGHT,
+ vs_table::ALIGN_CENTER | vs_table::FILL);
+
+ l->location_changed.connect(sigc::mem_fun(*s.unsafe_get_ref(), &vs_scrollbar::set_slider));
+ s->scrollbar_interaction.connect(sigc::mem_fun(*l.unsafe_get_ref(), &vs_text_layout::scroll));
+ }
+ else
+ w=vs_text_layout::create(msg);
+
+ return vs_dialog_ok(w, okslot, label, st);
+}
+
+vs_widget_ref vs_dialog_ok(fragment *msg, slot0arg okslot, const style &st, bool scrollbar)
+{
+ return vs_dialog_ok(msg, okslot, transcode(_("Ok")), st, scrollbar);
+}
+
+vs_widget_ref vs_dialog_ok(fragment *msg, slot0arg okslot, bool scrollbar)
+{
+ return vs_dialog_ok(msg, okslot, style_attrs_flip(A_REVERSE), scrollbar);
+}
+
+vs_widget_ref vs_dialog_ok(const wstring &msg, slot0arg okslot,
+ const style &st)
+{
+ vs_widget_ref l=vs_label::create (msg);
+
+ return vs_dialog_ok(l, okslot, transcode(_("Ok")), st);
+}
+
+vs_widget_ref vs_dialog_ok(const wstring &msg, slot0arg okslot)
+{
+ return vs_dialog_ok(msg, okslot, style_attrs_flip(A_REVERSE));
+}
+
+vs_widget_ref vs_dialog_yesno(const vs_widget_ref &widget,
+ slot0arg yesslot,
+ const wstring &yeslabel,
+ slot0arg noslot,
+ const wstring &nolabel,
+ const style &st,
+ bool deflt)
+{
+ vs_center_ref center = vs_center::create();
+
+ vs_table_ref table = vs_table::create();
+
+ vs_button_ref yesbutton = vs_button::create(yeslabel);
+ vs_button_ref nobutton = vs_button::create(nolabel);
+
+ yesbutton->pressed.connect(sigc::mem_fun(*center.unsafe_get_ref(), &vscreen_widget::destroy));
+ nobutton->pressed.connect(sigc::mem_fun(*center.unsafe_get_ref(), &vscreen_widget::destroy));
+
+ if(yesslot)
+ yesbutton->pressed.connect(*yesslot);
+ if(noslot)
+ nobutton->pressed.connect(*noslot);
+
+ table->connect_key("Yes", &global_bindings, yesbutton->pressed.make_slot());
+ table->connect_key("No", &global_bindings, nobutton->pressed.make_slot());
+ table->connect_key("Cancel", &global_bindings, nobutton->pressed.make_slot());
+
+ table->add_widget(widget, 0, 0, 1, 2, true, true);
+ table->add_widget_opts(yesbutton, 1, 0, 1, 1, vs_table::SHRINK|vs_table::ALIGN_CENTER, 0);
+ table->add_widget_opts(nobutton, 1, 1, 1, 1, vs_table::SHRINK|vs_table::ALIGN_CENTER, 0);
+
+ widget->show();
+ yesbutton->show();
+ nobutton->show();
+
+ if(deflt)
+ table->focus_widget(yesbutton);
+ else
+ table->focus_widget(nobutton);
+
+ vs_frame_ref frame = vs_frame::create(table);
+ frame->set_bg_style(st);
+
+ center->add_widget(frame);
+
+ return center;
+}
+
+vs_widget_ref vs_dialog_yesno(const wstring &msg,
+ slot0arg yesslot,
+ const wstring &yeslabel,
+ slot0arg noslot,
+ const wstring &nolabel,
+ const style &st,
+ bool deflt)
+{
+ vs_widget_ref txt=vs_label::create(msg);
+
+ return vs_dialog_yesno(txt, yesslot, yeslabel, noslot, nolabel, st, deflt);
+}
+
+vs_widget_ref vs_dialog_yesno(const wstring &msg,
+ slot0arg yesslot,
+ slot0arg noslot,
+ const style &st,
+ bool deflt)
+{
+ return vs_dialog_yesno(msg, yesslot, transcode(_("Yes")),
+ noslot, transcode(_("No")), st, deflt);
+}
+
+vs_widget_ref vs_dialog_yesno(const wstring &msg,
+ slot0arg yesslot,
+ slot0arg noslot,
+ bool deflt)
+{
+ return vs_dialog_yesno(msg,
+ yesslot,
+ noslot,
+ style_attrs_flip(A_REVERSE),
+ deflt);
+}
+
+
+vs_widget_ref vs_dialog_yesno(fragment *msg,
+ slot0arg yesslot,
+ slot0arg noslot,
+ bool scrollbar,
+ bool deflt)
+{
+ return vs_dialog_yesno(msg,
+ yesslot,
+ noslot,
+ style_attrs_flip(A_REVERSE),
+ scrollbar,
+ deflt);
+}
+
+vs_widget_ref vs_dialog_yesno(fragment *msg,
+ slot0arg yesslot,
+ slot0arg noslot,
+ const style &st,
+ bool scrollbar,
+ bool deflt)
+{
+ return vs_dialog_yesno(msg, yesslot, transcode(_("Yes")),
+ noslot, transcode(_("No")), st,
+ scrollbar, deflt);
+}
+
+vs_widget_ref vs_dialog_yesno(fragment *msg,
+ slot0arg yesslot,
+ const std::wstring &yeslabel,
+ slot0arg noslot,
+ const std::wstring &nolabel,
+ const style &st,
+ bool scrollbar,
+ bool deflt)
+{
+ vs_widget_ref w;
+
+ if(scrollbar)
+ {
+ vs_table_ref t = vs_table::create();
+ w=t;
+
+ vs_text_layout_ref l = vs_text_layout::create(msg);
+ vs_scrollbar_ref s = vs_scrollbar::create(vs_scrollbar::VERTICAL);
+
+ t->add_widget(l, 0, 0, 1, 1, true, true);
+ t->add_widget_opts(s, 0, 1, 1, 1,
+ vs_table::ALIGN_RIGHT,
+ vs_table::ALIGN_CENTER | vs_table::FILL);
+
+ l->location_changed.connect(sigc::mem_fun(*s.unsafe_get_ref(), &vs_scrollbar::set_slider));
+ s->scrollbar_interaction.connect(sigc::mem_fun(*l.unsafe_get_ref(), &vs_text_layout::scroll));
+ }
+ else
+ w=vs_text_layout::create(msg);
+
+ return vs_dialog_yesno(w, yesslot, yeslabel, noslot, nolabel, st, deflt);
+}
+
+vs_widget_ref vs_dialog_fileview(const string &fn,
+ slot0arg okslot,
+ slotarg<sigc::slot1<void, vs_pager &> > search_slot,
+ slotarg<sigc::slot1<void, vs_pager &> > repeat_search_slot,
+ const style &st,
+ const char *encoding)
+{
+ vs_file_pager_ref p = vs_file_pager::create(fn, encoding);
+ vs_scrollbar_ref scrollbar = vs_scrollbar::create(vs_scrollbar::VERTICAL, 0, 0);
+ vs_table_ref t = vs_table::create();
+
+ t->add_widget_opts(p, 0, 0, 1, 1,
+ vs_table::EXPAND | vs_table::FILL | vs_table::SHRINK | vs_table::ALIGN_CENTER,
+ vs_table::FILL | vs_table::SHRINK | vs_table::ALIGN_CENTER);
+ t->add_widget_opts(scrollbar, 0, 1, 1, 1,
+ vs_table::ALIGN_CENTER,
+ vs_table::EXPAND | vs_table::FILL);
+
+ //t->set_bg_style(style_attrs_off(A_REVERSE));
+
+ p->line_changed.connect(sigc::mem_fun(*scrollbar.unsafe_get_ref(), &vs_scrollbar::set_slider));
+ p->do_line_signal();
+ scrollbar->scrollbar_interaction.connect(sigc::mem_fun(*p.unsafe_get_ref(), &vs_pager::scroll_page));
+
+ if(search_slot)
+ p->connect_key("Search", &global_bindings, sigc::bind(*search_slot, p.weak_ref()));
+
+ if(repeat_search_slot)
+ p->connect_key("ReSearch", &global_bindings, sigc::bind(*repeat_search_slot, p.weak_ref()));
+
+ return vs_dialog_ok(t, okslot, transcode(_("Ok")), st);
+}
+
+vs_widget_ref vs_dialog_fileview(const string &fn,
+ slot0arg okslot,
+ slotarg<sigc::slot1<void, vs_pager &> > search_slot,
+ slotarg<sigc::slot1<void, vs_pager &> > repeat_search_slot,
+ const char *encoding)
+{
+ return vs_dialog_fileview(fn, okslot, search_slot, repeat_search_slot,
+ style_attrs_flip(A_REVERSE),
+ encoding);
+}
+
+static void do_dialog_string(wstring s,
+ sigc::slot0<void> realslot)
+{
+ realslot();
+}
+
+static void also_do_dialog_string(vs_editline &e,
+ sigc::slot1<void, wstring> thestrslot)
+{
+ e.add_to_history(e.get_text());
+ thestrslot(e.get_text());
+}
+
+vs_widget_ref vs_dialog_string(const vs_widget_ref &msg,
+ wstring deflt,
+ slotarg<sigc::slot1<void, wstring> > slot,
+ slotarg<sigc::slot0<void> > cancel_slot,
+ slotarg<sigc::slot1<void, wstring> > changed_slot,
+ vs_editline::history_list *history,
+ const style &st)
+{
+ vs_table_ref t = vs_table::create();
+ vs_editline_ref e = vs_editline::create(rootwin.getmaxx()-6, L"", deflt, history);
+ vs_button_ref bok = vs_button::create(_("Ok"));
+ vs_button_ref bcancel = vs_button::create(_("Cancel"));
+ vs_frame_ref f = vs_frame::create(t);
+ vs_center_ref c = vs_center::create(f);
+
+ f->set_bg_style(st);
+
+ t->add_widget(msg, 0, 0, 1, 2);
+ t->add_widget(e, 1, 0, 1, 2);
+ t->add_widget_opts(bok, 2, 0, 1, 1,
+ vs_table::ALIGN_CENTER|vs_table::SHRINK,
+ vs_table::ALIGN_CENTER);
+ t->add_widget_opts(bcancel, 2, 1, 1, 1,
+ vs_table::ALIGN_CENTER|vs_table::SHRINK,
+ vs_table::ALIGN_CENTER);
+
+ e->entered.connect(sigc::bind(sigc::ptr_fun(do_dialog_string),
+ bok->pressed.make_slot()));
+ if(changed_slot)
+ e->text_changed.connect(*changed_slot);
+
+ t->connect_key_post("Cancel", &global_bindings,
+ bcancel->pressed.make_slot());
+
+ bok->pressed.connect(sigc::mem_fun(*c.unsafe_get_ref(), &vscreen_widget::destroy));
+ if(slot)
+ bok->pressed.connect(sigc::bind(sigc::ptr_fun(also_do_dialog_string),
+ e.weak_ref(), *slot));
+
+ bcancel->pressed.connect(sigc::mem_fun(*c.unsafe_get_ref(), &vscreen_widget::destroy));
+ if(cancel_slot)
+ bcancel->pressed.connect(*cancel_slot);
+
+ return c;
+}
+
+vs_widget_ref vs_dialog_string(fragment *msg,
+ const wstring &deflt,
+ slotarg<sigc::slot1<void, wstring> > slot,
+ slotarg<sigc::slot0<void> > cancel_slot,
+ slotarg<sigc::slot1<void, wstring> > changed_slot,
+ vs_editline::history_list *history,
+ const style &st)
+{
+ return vs_dialog_string(vs_label::create(msg),
+ deflt,
+ slot,
+ cancel_slot,
+ changed_slot,
+ history,
+ st);
+}
+
+vs_widget_ref vs_dialog_string(const wstring &msg,
+ const wstring &deflt,
+ slotarg<sigc::slot1<void, wstring> > slot,
+ slotarg<sigc::slot0<void> > cancel_slot,
+ slotarg<sigc::slot1<void, wstring> > changed_slot,
+ vs_editline::history_list *history,
+ const style &st)
+{
+ return vs_dialog_string(vs_label::create(msg),
+ deflt,
+ slot,
+ cancel_slot,
+ changed_slot,
+ history,
+ st);
+}
+
+vs_widget_ref vs_dialog_string(const wstring &msg,
+ const wstring &deflt,
+ slotarg<sigc::slot1<void, wstring> > slot,
+ slotarg<sigc::slot0<void> > cancel_slot,
+ slotarg<sigc::slot1<void, wstring> > changed_slot,
+ vs_editline::history_list *history)
+{
+ return vs_dialog_string(msg,
+ deflt,
+ slot,
+ cancel_slot,
+ changed_slot,
+ history,
+ style_attrs_flip(A_REVERSE));
+}
diff --git a/src/vscreen/vs_util.h b/src/vscreen/vs_util.h
new file mode 100644
index 00000000..a32caae2
--- /dev/null
+++ b/src/vscreen/vs_util.h
@@ -0,0 +1,153 @@
+// vs_util.h -*-c++-*-
+//
+// Copyright 2000 Daniel Burrows
+//
+// Provides a bunch of utility functions to construct prefabricated
+// widget trees (for instance, handy message boxes)
+
+#ifndef VS_UTIL_H
+#define VS_UTIL_H
+
+#include "vs_editline.h"
+
+#include <generic/util/slotarg.h>
+
+#include <string>
+
+class fragment;
+class style;
+class vscreen_widget;
+class vs_pager;
+
+template<class T>
+class ref_ptr;
+
+typedef ref_ptr<vscreen_widget> vs_widget_ref;
+
+// Canned dialog-boxes:
+
+/** Create a dialog box with a single button.
+ *
+ * \param widget the widget to place above the button.
+ *
+ * \param okslot the slot to be triggered when the button is pressed.
+ *
+ * \param label the label of the button
+ *
+ * \param attr the attributes to use for the background of the dialog
+ * box, defaults to reverse-video of DefaultWidgetBackground.
+ */
+vs_widget_ref vs_dialog_ok(const vs_widget_ref &widget,
+ slot0arg okslot, const std::wstring &label,
+ const style &st);
+
+vs_widget_ref vs_dialog_ok(fragment *msg, slot0arg okslot=NULL, bool scrollbar=false);
+vs_widget_ref vs_dialog_ok(fragment *msg, slot0arg okslot, const style &st, bool scrollbar=false);
+vs_widget_ref vs_dialog_ok(fragment *msg, slot0arg okslot, const std::wstring &label,
+ const style &st, bool scrollbar=false);
+
+vs_widget_ref vs_dialog_ok(const std::wstring &msg, slot0arg okslot=NULL);
+vs_widget_ref vs_dialog_ok(const std::wstring &msg, slot0arg okslot,
+ const style &st);
+vs_widget_ref vs_dialog_ok(const std::wstring &msg, slot0arg okslot, const std::wstring &label,
+ const style &st);
+
+/** Create a dialog box with two buttons, labelled "yes" and "no".
+ *
+ * \param widget the widget to place above the buttons
+ *
+ * \param yesslot the callback to be triggered when "yes" is selected
+ *
+ * \param yeslabel the label of the "yes" button
+ *
+ * \param noslot the callback to be triggered when "no" is selected
+ *
+ * \param yeslabel the label of the "no" button
+ *
+ * \param attr the attribute to use as the background of widgets
+ * created by this routine
+ *
+ * \param deflt if \b true, the "yes" button will be selected by default;
+ * otherwise, the "no" button will be selected by default.
+ */
+vs_widget_ref vs_dialog_yesno(const vs_widget_ref &widget,
+ slot0arg yesslot,
+ const std::wstring &yeslabel,
+ slot0arg noslot,
+ const std::wstring &nolabel,
+ const style &st,
+ bool deflt=true);
+
+vs_widget_ref vs_dialog_yesno(fragment *msg,
+ slot0arg yesslot,
+ slot0arg noslot,
+ bool scrollbar=false,
+ bool deflt=true);
+vs_widget_ref vs_dialog_yesno(fragment *msg,
+ slot0arg yesslot,
+ slot0arg noslot,
+ const style &st,
+ bool scrollbar=false,
+ bool deflt=true);
+vs_widget_ref vs_dialog_yesno(fragment *msg,
+ slot0arg yesslot,
+ const std::wstring &yeslabel,
+ slot0arg noslot,
+ const std::wstring &nolabel,
+ const style &st,
+ bool scrollbar=false,
+ bool deflt=true);
+
+vs_widget_ref vs_dialog_yesno(const std::wstring &msg,
+ slot0arg yesslot,
+ slot0arg noslot,
+ bool deflt=true);
+vs_widget_ref vs_dialog_yesno(const std::wstring &msg,
+ slot0arg yesslot,
+ slot0arg noslot,
+ const style &st,
+ bool deflt=true);
+vs_widget_ref vs_dialog_yesno(const std::wstring &msg,
+ slot0arg yesslot,
+ const std::wstring &yeslabel,
+ slot0arg noslot,
+ const std::wstring &nolabel,
+ const style &st,
+ bool deflt=true);
+
+vs_widget_ref vs_dialog_fileview(const std::string &fn,
+ slot0arg okslot=NULL,
+ slotarg<sigc::slot1<void, vs_pager &> > search_slot=NULL,
+ slotarg<sigc::slot1<void, vs_pager &> > repeat_search_slot=NULL,
+ const char *encoding=NULL);
+vs_widget_ref vs_dialog_fileview(const std::string &fn,
+ slot0arg okslot,
+ slotarg<sigc::slot1<void, vs_pager &> > search_slot,
+ slotarg<sigc::slot1<void, vs_pager &> > repeat_search_slot,
+ const style &st,
+ const char *encoding=NULL);
+
+vs_widget_ref vs_dialog_string(fragment *msg,
+ const std::wstring &deflt,
+ slotarg<sigc::slot1<void, std::wstring> > okslot,
+ slotarg<sigc::slot0<void> > cancel_slot,
+ slotarg<sigc::slot1<void, std::wstring> > changed_slot,
+ vs_editline::history_list *history,
+ const style &st);
+
+vs_widget_ref vs_dialog_string(const std::wstring &msg,
+ const std::wstring &deflt,
+ slotarg<sigc::slot1<void, std::wstring> > okslot,
+ slotarg<sigc::slot0<void> > cancel_slot,
+ slotarg<sigc::slot1<void, std::wstring> > changed_slot,
+ vs_editline::history_list *history,
+ const style &st);
+
+vs_widget_ref vs_dialog_string(const std::wstring &msg,
+ const std::wstring &deflt,
+ slotarg<sigc::slot1<void, std::wstring> > slot,
+ slotarg<sigc::slot0<void> > cancel_slot,
+ slotarg<sigc::slot1<void, std::wstring> > changed_slot,
+ vs_editline::history_list *history);
+
+#endif
diff --git a/src/vscreen/vscreen.cc b/src/vscreen/vscreen.cc
new file mode 100644
index 00000000..2bc78613
--- /dev/null
+++ b/src/vscreen/vscreen.cc
@@ -0,0 +1,1053 @@
+// vscreen.cc
+//
+// Copyright 1999-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// Implementation of vscreen stuff.
+
+// We need this for recursive mutexes. Should all files be compiled
+// with this defined??
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "vscreen.h"
+#include "curses++.h"
+#include "vscreen_widget.h"
+
+#include "transcode.h"
+#include "vs_editline.h"
+#include "vs_menu.h"
+#include "vs_menubar.h"
+#include "vs_pager.h"
+#include "vs_statuschoice.h"
+#include "vs_table.h"
+#include "vs_text_layout.h"
+#include "vs_tree.h"
+
+#include "config/keybindings.h"
+#include "config/style.h"
+
+#include <generic/util/event_queue.h>
+#include <generic/util/threads.h>
+
+// For _()
+#include "../aptitude.h"
+
+#include <signal.h>
+
+#include <assert.h>
+#include <sys/time.h>
+
+#include <map>
+
+threads::recursive_mutex vscreen_mutex;
+
+inline
+threads::mutex &vscreen_get_mutex()
+{
+ return vscreen_mutex;
+}
+
+sigc::signal0<void> main_hook;
+
+
+static threads::event_queue<vscreen_event *> eventq;
+
+using namespace std;
+
+bool curses_avail=false;
+bool should_exit=false;
+
+static bool suspended_with_signals = false;
+static struct sigaction oldsigcont, oldsigtstp;
+
+// Used to queue and merge update requests
+//
+// The global event queue isn't used for this so that
+// vscreen_update(); vscreen_tryupdate() works as desired. However,
+// threading magic is used to ensure that background threads can post
+// update requests.
+struct update_state
+{
+ bool layout;
+ bool update;
+ bool cursorupdate;
+
+ update_state()
+ :layout(false), update(false), cursorupdate(false)
+ {
+ }
+};
+
+threads::recursive_mutex pending_updates_mutex;
+update_state pending_updates;
+
+
+vscreen_event::~vscreen_event()
+{
+}
+
+void slot_event::dispatch()
+{
+ the_slot();
+}
+
+static vs_widget_ref toplevel = NULL;
+// The widget which is displayed as the root of everything
+
+// Cleanly shutdown (eg, restore screen settings if possible)
+//
+// Called on SIGTERM, SIGINT, SIGSEGV, SIGABRT, and SIGQUIT
+//
+// FIXME: revert to the /previous/ handler, not just SIG_DFL?
+static void sigkilled(int sig)
+{
+ endwin();
+
+ switch(sig)
+ {
+ case SIGTERM:
+ fprintf(stderr, _("Ouch! Got SIGTERM, dying..\n"));
+ break;
+ case SIGSEGV:
+ fprintf(stderr, _("Ouch! Got SIGSEGV, dying..\n"));
+ break;
+ case SIGABRT:
+ fprintf(stderr, _("Ouch! Got SIGABRT, dying..\n"));
+ break;
+ case SIGQUIT:
+ fprintf(stderr, _("Ouch! Got SIGQUIT, dying..\n"));
+ break;
+ }
+
+ signal(sig, SIG_DFL);
+ raise(sig);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// The following function comes from the glibc documentation.
+// Subtract the `struct timeval' values X and Y,
+// storing the result in RESULT.
+// Return 1 if the difference is negative, otherwise 0.
+
+static int
+timeval_subtract (timeval *result, timeval *x, timeval *y)
+{
+ /* Perform the carry for the later subtraction by updating Y. */
+ if (x->tv_usec < y->tv_usec)
+ {
+ int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
+ y->tv_usec -= 1000000 * nsec;
+ y->tv_sec += nsec;
+ }
+ if (x->tv_usec - y->tv_usec > 1000000)
+ {
+ int nsec = (x->tv_usec - y->tv_usec) / 1000000;
+ y->tv_usec += 1000000 * nsec;
+ y->tv_sec -= nsec;
+ }
+
+ /* Compute the time remaining to wait.
+ `tv_usec' is certainly positive. */
+ result->tv_sec = x->tv_sec - y->tv_sec;
+ result->tv_usec = x->tv_usec - y->tv_usec;
+
+ /* Return 1 if result is negative. */
+ return x->tv_sec < y->tv_sec;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+vs_widget_ref vscreen_settoplevel(const vs_widget_ref &w)
+{
+ if(toplevel.valid())
+ toplevel->unfocussed();
+
+ vs_widget_ref oldw = toplevel;
+
+ toplevel = w;
+
+ if(curses_avail)
+ {
+ toplevel->set_owner_window(rootwin, 0, 0, rootwin.getmaxx(), rootwin.getmaxy());
+ toplevel->show_all();
+ toplevel->focussed();
+ vscreen_redraw();
+ }
+
+ return oldw;
+}
+
+//////////////////////////////////////////////////////////////////////
+void vscreen_post_event(vscreen_event *ev)
+{
+ eventq.put(ev);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Event management threads
+
+/** This thread is responsible for posting wget_wch() calls. */
+class input_thread
+{
+ class key_input_event : public vscreen_event
+ {
+ key k;
+ public:
+ key_input_event (const key &_k)
+ :k(_k)
+ {
+ }
+
+ void dispatch()
+ {
+ if(global_bindings.key_matches(k, "Refresh"))
+ vscreen_redraw();
+ else
+ toplevel->dispatch_key(k);
+ }
+ };
+
+ class mouse_input_event : public vscreen_event
+ {
+ MEVENT ev;
+
+ public:
+ mouse_input_event(const MEVENT &_ev)
+ :ev(_ev)
+ {
+ }
+
+ void dispatch()
+ {
+ if(toplevel.valid())
+ toplevel->dispatch_mouse(ev.id, ev.x, ev.y, ev.z, ev.bstate);
+ }
+ };
+
+ static input_thread instance;
+
+ static threads::mutex instance_mutex;
+ static threads::thread *instancet;
+public:
+ static void start()
+ {
+ threads::mutex::lock l(instance_mutex);
+
+ if(instancet == NULL)
+ instancet = new threads::thread(instance);
+ }
+
+ static void stop()
+ {
+ threads::mutex::lock l(instance_mutex);
+
+ if(instancet != NULL)
+ {
+ instancet->cancel();
+ instancet->join();
+ delete instancet;
+ instancet = NULL;
+ }
+ }
+
+ void operator()() const
+ {
+ // NB: use the GLOBAL getch function to avoid weirdness
+ // referencing the state of toplevel.
+ while(1)
+ {
+ wint_t wch = 0;
+ int status;
+
+ do
+ {
+ status = get_wch(&wch);
+ } while(status == KEY_CODE_YES && wch == KEY_RESIZE);
+
+ key k(wch, status == KEY_CODE_YES);
+
+ if(status == ERR)
+ return; // ???
+
+ if(wch == KEY_MOUSE)
+ {
+ MEVENT ev;
+ getmouse(&ev);
+
+ vscreen_post_event(new mouse_input_event(ev));
+ }
+ else
+ vscreen_post_event(new key_input_event(k));
+ }
+ }
+};
+
+threads::mutex input_thread::instance_mutex;
+threads::thread *input_thread::instancet = NULL;
+input_thread input_thread::instance;
+
+class signal_thread
+{
+ class signal_event : public vscreen_event
+ {
+ int signal;
+ public:
+ signal_event(int _signal)
+ :signal(_signal)
+ {
+ }
+
+ void dispatch()
+ {
+ switch(signal)
+ {
+ case SIGWINCH:
+ vscreen_handleresize();
+ break;
+ default:
+ vscreen_exitmain();
+ break;
+ }
+ }
+ };
+
+ static signal_thread instance;
+ static threads::thread *t;
+public:
+ static void start()
+ {
+ if(t == NULL)
+ t = new threads::thread(instance);
+ }
+
+ static void stop()
+ {
+ if(t != NULL)
+ {
+ t->cancel();
+ t->join();
+ delete t;
+ t = NULL;
+ }
+ }
+
+ void operator()() const
+ {
+ sigset_t s;
+
+ sigemptyset(&s);
+ sigaddset(&s, SIGWINCH);
+
+ while(1)
+ {
+ int signum;
+
+ int result = sigwait(&s, &signum);
+
+ if(result == 0)
+ vscreen_post_event(new signal_event(signum));
+ }
+ }
+};
+
+signal_thread signal_thread::instance;
+threads::thread *signal_thread::t = NULL;
+
+class timeout_thread
+{
+ class SingletonViolationException
+ {
+ public:
+ string errmsg() const
+ {
+ return "Attempt to run a singleton thread twice!";
+ }
+ };
+
+
+ /** Information about a single time-out. */
+ struct timeout_info
+ {
+ vscreen_event *ev;
+ timeval activate_time; // tells when this timeout should be triggered
+ timeout_info(vscreen_event *_ev,
+ const timeval &_activate_time)
+ :ev(_ev), activate_time(_activate_time)
+ {
+ }
+
+ timeout_info()
+ {
+ activate_time.tv_sec = 0;
+ activate_time.tv_usec = 0;
+ }
+ };
+
+
+ // The set of active timeouts.
+ map<int, timeout_info> timeouts;
+
+ /** If \b true, the thread should stop. */
+ bool cancelled;
+
+ // A lock for the set of timeouts and the cancelled flag.
+ threads::mutex timeouts_mutex;
+
+ // A condition to be broadcast when the set of timeouts is expanded
+ // by add_timeout.
+ threads::condition timeout_added;
+
+ /** The thread that is currently executing in this object. */
+ threads::box<threads::thread *> running_thread;
+
+
+
+ /** Post messages about any timeouts that occurred. Should be called
+ * with timeouts_mutex locked.
+ */
+ void check_timeouts()
+ {
+ map<int, timeout_info>::iterator i,j;
+ for(i = timeouts.begin(); i != timeouts.end(); i = j)
+ {
+ j = i;
+ j++;
+ timeval result,curtime;
+ gettimeofday(&curtime, 0);
+
+ if(timeval_subtract(&result, &i->second.activate_time, &curtime) == 1 ||
+ result.tv_sec == 0 && result.tv_usec <= 10)
+ {
+ vscreen_post_event(i->second.ev);
+ timeouts.erase(i);
+ }
+ }
+ }
+
+ /** timeouts_mutex should be locked when this is called.
+ *
+ * \param tv_out the time at which the first active timeout should trigger.
+ * \return \b true if a timeout was found.
+ */
+ bool first_timeout(timeval &tv_out)
+ {
+ bool found_one = false;
+ timeval mintime;
+ mintime.tv_sec = INT_MAX/1000;
+ mintime.tv_usec = (INT_MAX % 1000) * 1000;
+
+ timeval curtime;
+ gettimeofday(&curtime, 0);
+
+ map<int, timeout_info>::iterator i,j;
+ for(i = timeouts.begin(); i != timeouts.end(); i = j)
+ {
+ j = i;
+ j++;
+ timeval diff;
+ if(timeval_subtract(&diff, &i->second.activate_time, &curtime) == 1 ||
+ diff.tv_sec == 0 && diff.tv_usec <= 10)
+ {
+ tv_out = curtime;
+ return true;
+ }
+ else
+ {
+ if(diff.tv_sec < mintime.tv_sec ||
+ (diff.tv_sec == mintime.tv_sec && diff.tv_usec < mintime.tv_usec))
+ {
+ found_one = true;
+ mintime = i->second.activate_time;
+ }
+ }
+ }
+ if(found_one)
+ {
+ tv_out = mintime;
+ return true;
+ }
+ else
+ return false;
+ }
+
+
+ timeout_thread(const timeout_thread &other);
+ timeout_thread &operator=(const timeout_thread &other);
+
+ timeout_thread()
+ : cancelled(false), running_thread(NULL)
+ {
+ }
+
+ // The global instance; this is a Singleton.
+ static timeout_thread instance;
+
+ // Unfortunately, technical considerations in the threading code
+ // mean that the actual thread object is expected to be copyable.
+ // Hence this proxy:
+ class timeout_proxy
+ {
+ timeout_thread &real_thread;
+ public:
+ timeout_proxy(timeout_thread &_real_thread)
+ : real_thread(_real_thread)
+ {
+ }
+
+ void operator()() const
+ {
+ real_thread();
+ }
+ };
+public:
+ static timeout_thread &get_instance()
+ {
+ return instance;
+ }
+
+ static void start()
+ {
+ timeout_thread &instance = get_instance();
+
+ threads::thread *running = instance.running_thread.take();
+ if(running != NULL)
+ {
+ instance.running_thread.put(running);
+ throw SingletonViolationException();
+ }
+
+ instance.running_thread.put(new threads::thread(timeout_proxy(instance)));
+ }
+
+ static void stop()
+ {
+ timeout_thread &instance = get_instance();
+
+ threads::thread *running = instance.running_thread.take();
+ threads::mutex::lock l(instance.timeouts_mutex);
+
+ instance.cancelled = true;
+ instance.timeout_added.wake_all();
+
+ l.release();
+
+ running->join();
+
+ instance.running_thread.put(NULL);
+ }
+
+ void operator()()
+ {
+ threads::mutex::lock l(timeouts_mutex);
+
+ while(!cancelled)
+ {
+ timeval next_timeout;
+
+ if(first_timeout(next_timeout))
+ {
+ timespec until;
+ until.tv_sec = next_timeout.tv_sec;
+ until.tv_nsec = next_timeout.tv_usec * 1000;
+
+ timeout_added.timed_wait(l, until);
+
+ // Probably don't need to do this, but it won't hurt.
+ check_timeouts();
+ }
+ else
+ timeout_added.wait(l);
+ }
+ }
+
+ /** Add a timeout to the set of active timeouts.
+ *
+ * \param slot a callback to activate when the timeout triggers
+ * \param msecs the number of milliseconds in which to activate the
+ * timeout.
+ * \return the ID number of the new timeout; can be used later to
+ * remove the timeout before it triggers.
+ */
+ int add_timeout(vscreen_event *ev, int msecs)
+ {
+ threads::mutex::lock l(timeouts_mutex);
+
+ timeval activate_time;
+ gettimeofday(&activate_time, 0);
+ activate_time.tv_sec += msecs/1000;
+ activate_time.tv_usec += (msecs%1000)*1000;
+ while(activate_time.tv_usec > 1000 * 1000)
+ // Should only run through once
+ {
+ activate_time.tv_sec++;
+ activate_time.tv_usec -= 1000 * 1000;
+ }
+
+ // Since the timeouts are sorted by ID, the back element is the
+ // maximum ID in use.
+ int rval;
+
+ if(timeouts.empty())
+ rval = 0;
+ else
+ rval = timeouts.rbegin()->first + 1;
+
+ timeouts[rval] = timeout_info(ev, activate_time);
+
+ timeout_added.wake_all();
+
+ return rval;
+ }
+
+ void del_timeout(int id)
+ {
+ threads::mutex::lock l(timeouts_mutex);
+
+ timeouts.erase(id);
+ }
+};
+
+timeout_thread timeout_thread::instance;
+
+void vscreen_init()
+{
+ keybinding upkey, downkey, leftkey, rightkey, quitkey, homekey, endkey;
+ keybinding historynextkey, historyprevkey;
+ keybinding delfkey, delbkey, ppagekey, npagekey;
+ keybinding undokey, helpkey, researchkey;
+ keybinding menutogglekey, cancelkey;
+
+ upkey.push_back(key(KEY_UP, true));
+ upkey.push_back(key(L'k', false));
+ downkey.push_back(key(KEY_DOWN, true));
+ downkey.push_back(key(L'j', false));
+ leftkey.push_back(key(KEY_LEFT, true));
+ leftkey.push_back(key(L'h', false));
+ rightkey.push_back(key(KEY_RIGHT, true));
+ rightkey.push_back(key(L'l', false));
+ quitkey.push_back(key(L'q', false));
+
+ historyprevkey.push_back(key(KEY_UP, true));
+ historyprevkey.push_back(KEY_CTRL(L'p'));
+ historynextkey.push_back(key(KEY_DOWN, true));
+ historynextkey.push_back(KEY_CTRL(L'n'));
+
+ homekey.push_back(key(KEY_HOME, true));
+ homekey.push_back(KEY_CTRL(L'a'));
+ endkey.push_back(key(KEY_END, true));
+ endkey.push_back(KEY_CTRL(L'e'));
+
+ delfkey.push_back(key(KEY_DC, true));
+ delfkey.push_back(KEY_CTRL(L'd'));
+
+ delbkey.push_back(key(KEY_BACKSPACE, true));
+ delbkey.push_back(KEY_CTRL(L'h'));
+
+ ppagekey.push_back(key(KEY_PPAGE, true));
+ ppagekey.push_back(KEY_CTRL(L'b'));
+
+ npagekey.push_back(key(KEY_NPAGE, true));
+ npagekey.push_back(KEY_CTRL(L'f'));
+
+ undokey.push_back(KEY_CTRL(L'u'));
+ undokey.push_back(KEY_CTRL(L'_'));
+
+ helpkey.push_back(key(L'?', false));
+ helpkey.push_back(KEY_CTRL(L'h'));
+ helpkey.push_back(key(KEY_F(1), true));
+
+ menutogglekey.push_back(key(KEY_F(10), true));
+ menutogglekey.push_back(KEY_CTRL(L' '));
+
+ cancelkey.push_back(KEY_CTRL(L'g'));
+ cancelkey.push_back(key(L'\e', true));
+ cancelkey.push_back(KEY_CTRL(L'['));
+
+ researchkey.push_back(key(L'n', false));
+
+ init_curses();
+
+ mousemask(ALL_MOUSE_EVENTS, NULL);
+
+ curses_avail=true;
+ cbreak();
+ rootwin.keypad(true);
+
+ global_bindings.set("Quit", quitkey);
+ global_bindings.set("Cycle", key(L'\t', false));
+ global_bindings.set("Refresh", KEY_CTRL(L'l'));
+
+ global_bindings.set("Up", upkey);
+ global_bindings.set("Down", downkey);
+ global_bindings.set("LevelDown", key(L'J', false));
+ global_bindings.set("LevelUp", key(L'K', false));
+ global_bindings.set("Left", leftkey);
+ global_bindings.set("Right", rightkey);
+ global_bindings.set("HistoryNext", historynextkey);
+ global_bindings.set("HistoryPrev", historyprevkey);
+ global_bindings.set("Parent", key(L'^', false));
+ global_bindings.set("PrevPage", ppagekey);
+ global_bindings.set("NextPage", npagekey);
+ global_bindings.set("Begin", homekey);
+ global_bindings.set("End", endkey);
+ global_bindings.set("Search", key(L'/', false));
+ global_bindings.set("SearchBack", key(L'\\', false));
+ global_bindings.set("ReSearch", researchkey);
+ global_bindings.set("DelBack", delbkey);
+ global_bindings.set("DelForward", delfkey);
+
+ global_bindings.set("DelEOL", KEY_CTRL(L'k'));
+ global_bindings.set("DelBOL", KEY_CTRL(L'u'));
+
+ global_bindings.set("Confirm", key(KEY_ENTER, true));
+ global_bindings.set("Cancel", cancelkey);
+ global_bindings.set("Undo", undokey);
+ global_bindings.set("Help", helpkey);
+ global_bindings.set("ToggleMenuActive", menutogglekey);
+ global_bindings.set("PushButton", key(L' ', false));
+ global_bindings.set("Yes", key(transcode(_("yes_key"))[0], false));
+ global_bindings.set("No", key(transcode(_("no_key"))[0], false));
+
+ global_bindings.set("ToggleExpanded", key(KEY_ENTER, true));
+ global_bindings.set("ExpandAll", key(L'[', false));
+ global_bindings.set("CollapseAll", key(L']', false));
+ global_bindings.set("SelectParent", key(L'^', false));
+
+ vs_editline::init_bindings();
+ vs_menu::init_bindings();
+ vs_menubar::init_bindings();
+ vs_pager::init_bindings();
+ vs_statuschoice::init_bindings();
+ vs_table::init_bindings();
+ vs_text_layout::init_bindings();
+ vs_tree::init_bindings();
+
+ set_style("Error",
+ style_fg(COLOR_WHITE)+style_bg(COLOR_RED)+style_attrs_on(A_BOLD));
+
+ // The 'base' style for the display.
+ set_style("Default",
+ style_fg(COLOR_WHITE)+style_bg(COLOR_BLACK));
+
+ set_style("Header",
+ style_fg(COLOR_WHITE)+style_bg(COLOR_BLUE)+style_attrs_on(A_BOLD));
+ set_style("Status",
+ style_fg(COLOR_WHITE)+style_bg(COLOR_BLUE)+style_attrs_on(A_BOLD));
+
+ set_style("MenuEntry", style_fg(COLOR_WHITE)+style_bg(COLOR_BLUE));
+ set_style("MenuBorder", style_fg(COLOR_WHITE)+style_bg(COLOR_BLUE)+style_attrs_on(A_BOLD));
+ set_style("HighlightedMenuEntry", style_bg(COLOR_BLUE)+style_fg(COLOR_WHITE)+style_attrs_on(A_BOLD|A_REVERSE));
+ set_style("DisabledMenuEntry",
+ style_fg(COLOR_BLACK)+style_bg(COLOR_BLUE)+style_attrs_on(A_DIM));
+ set_style("MenuBar", style_fg(COLOR_WHITE)+style_bg(COLOR_BLUE)+style_attrs_on(A_BOLD));
+ set_style("HighlightedMenuBar", style_fg(COLOR_BLUE)+style_bg(COLOR_WHITE));
+
+ set_style("MultiplexTab", style_fg(COLOR_WHITE)+style_bg(COLOR_BLUE));
+ set_style("MultiplexTabHighlighted", style_fg(COLOR_BLUE)+style_bg(COLOR_WHITE));
+
+ // Edit lines will *always* appear white-on-black.
+ set_style("EditLine", style_fg(COLOR_WHITE)+style_bg(COLOR_BLACK)+style_attrs_off(A_REVERSE));
+
+ set_style("TreeBackground", style());
+
+ if(toplevel.valid())
+ vscreen_settoplevel(toplevel);
+
+
+ vscreen_install_sighandlers();
+
+
+ // Block WINCH so the signal_thread can pick it up.
+ sigset_t signals;
+ sigemptyset(&signals);
+ sigaddset(&signals, SIGWINCH);
+ sigprocmask(SIG_BLOCK, &signals, NULL);
+
+
+
+ input_thread::start();
+ signal_thread::start();
+ timeout_thread::start();
+}
+
+void vscreen_install_sighandlers()
+{
+ signal(SIGTERM, sigkilled);
+ signal(SIGINT, sigkilled);
+ signal(SIGSEGV, sigkilled);
+ signal(SIGQUIT, sigkilled);
+ signal(SIGABRT, sigkilled);
+}
+
+void vscreen_handleresize()
+{
+ toplevel->set_owner_window(NULL, 0, 0, 0, 0);
+ resize();
+ toplevel->set_owner_window(rootwin, 0, 0, rootwin.getmaxx(), rootwin.getmaxy());
+ vscreen_redraw();
+}
+
+class try_update_event : public vscreen_event
+{
+public:
+ void dispatch()
+ {
+ vscreen_tryupdate();
+ }
+};
+
+void vscreen_updatecursor()
+{
+ threads::mutex::lock l(pending_updates_mutex);
+
+ pending_updates.cursorupdate=true;
+}
+
+void vscreen_updatecursornow()
+{
+ if(toplevel->get_cursorvisible())
+ {
+ point p=toplevel->get_cursorloc();
+ toplevel->win.leaveok(false);
+ toplevel->win.move(p.y, p.x);
+ toplevel->win.noutrefresh();
+ }
+ else
+ toplevel->win.leaveok(true);
+}
+
+void vscreen_update()
+{
+ threads::mutex::lock l(pending_updates_mutex);
+
+ pending_updates.update=true;
+ pending_updates.cursorupdate=true;
+
+ vscreen_post_event(new try_update_event);
+}
+
+void vscreen_updatenow()
+{
+ if(toplevel.valid())
+ {
+ toplevel->display(get_style("Default"));
+ toplevel->sync();
+ }
+}
+
+void vscreen_queuelayout()
+{
+ threads::mutex::lock l(pending_updates_mutex);
+
+ pending_updates.layout = true;
+ pending_updates.update = true;
+ pending_updates.cursorupdate = true;
+
+ vscreen_post_event(new try_update_event);
+}
+
+void vscreen_layoutnow()
+{
+ toplevel->do_layout();
+}
+
+void vscreen_tryupdate()
+{
+ threads::mutex::lock l(pending_updates_mutex);
+
+ update_state needs = pending_updates;
+
+ if(needs.layout)
+ vscreen_layoutnow();
+
+ if(needs.update)
+ vscreen_updatenow();
+
+ if(needs.update || needs.cursorupdate)
+ vscreen_updatecursornow();
+
+ doupdate();
+
+ // \todo This appears to just paper over sloppiness -- screen update
+ // routines shouldn't be queuing more updates!
+ pending_updates = update_state();
+}
+
+bool vscreen_poll()
+{
+ bool rval=false;
+
+ vscreen_event *ev = NULL;
+
+ while(eventq.try_get(ev))
+ {
+ rval = true;
+ ev->dispatch();
+ delete ev;
+ }
+
+ main_hook();
+
+ return rval;
+}
+
+void vscreen_mainloop(int synch)
+{
+ static int main_level=0;
+
+ main_level++;
+
+ threads::mutex::lock l(vscreen_get_mutex());
+
+ while(!should_exit && toplevel.valid())
+ {
+ l.release();
+
+ vscreen_event *ev = eventq.get();
+
+ l.acquire();
+
+ ev->dispatch();
+ delete ev;
+
+ while(eventq.try_get(ev))
+ {
+ ev->dispatch();
+ delete ev;
+ }
+
+ main_hook();
+ }
+
+ should_exit=false;
+
+ main_level--;
+}
+
+void vscreen_exitmain()
+{
+ should_exit=1;
+}
+
+void vscreen_suspend_without_signals()
+{
+ input_thread::stop();
+ signal_thread::stop();
+ timeout_thread::stop();
+
+ if(toplevel.valid())
+ toplevel->set_owner_window(NULL, 0, 0, 0, 0);
+
+ rootwin.bkgdset(' ');
+ rootwin.clear();
+ rootwin.refresh();
+ endwin();
+ curses_avail=false;
+}
+
+void vscreen_suspend()
+{
+ suspended_with_signals = true;
+
+ struct sigaction act;
+ act.sa_handler = SIG_IGN;
+ act.sa_sigaction = 0;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_restorer = 0;
+
+ sigaction(SIGCONT, &act, &oldsigcont);
+ sigaction(SIGTSTP, &act, &oldsigtstp);
+
+ vscreen_suspend_without_signals();
+}
+
+void vscreen_shutdown()
+{
+ toplevel->destroy();
+ toplevel = NULL;
+
+ vscreen_suspend();
+
+ // Discard all remaining events.
+ vscreen_event *ev = NULL;
+ while(eventq.try_get(ev))
+ delete ev;
+}
+
+void vscreen_resume()
+{
+ if(suspended_with_signals)
+ {
+ sigaction(SIGCONT, &oldsigcont, NULL);
+ sigaction(SIGTSTP, &oldsigtstp, NULL);
+ suspended_with_signals = false;
+ }
+
+ curses_avail=true;
+ if(toplevel.valid())
+ {
+ toplevel->set_owner_window(rootwin, 0, 0, rootwin.getmaxx(), rootwin.getmaxy());
+ toplevel->display(get_style("Default"));
+ toplevel->sync();
+ doupdate();
+ }
+ else
+ refresh();
+
+ input_thread::start();
+ signal_thread::start();
+ timeout_thread::start();
+}
+
+void vscreen_redraw()
+{
+ threads::mutex::lock l(pending_updates_mutex);
+
+ if(toplevel.valid())
+ {
+ toplevel->focussed();
+ toplevel->get_win().touch();
+ toplevel->get_win().clearok(true);
+ toplevel->do_layout();
+ toplevel->display(get_style("Default"));
+ vscreen_updatecursornow();
+ toplevel->sync();
+ doupdate();
+ toplevel->get_win().clearok(false);
+ }
+
+ // For reasons that aren't entirely clear, running a tryupdate()
+ // right after a resize causes stuff to break -- but since resizing
+ // triggers a redraw, the tryupdate() shouldn't be necessary anyway.
+ // Suppress any pending updates after redrawing:
+ pending_updates = update_state();
+}
+
+int vscreen_addtimeout(vscreen_event *ev, int msecs)
+{
+ if(msecs < 0)
+ return -1;
+
+ return timeout_thread::get_instance().add_timeout(ev, msecs);
+}
+
+void vscreen_deltimeout(int num)
+{
+ timeout_thread::get_instance().del_timeout(num);
+}
diff --git a/src/vscreen/vscreen.h b/src/vscreen/vscreen.h
new file mode 100644
index 00000000..ba578361
--- /dev/null
+++ b/src/vscreen/vscreen.h
@@ -0,0 +1,183 @@
+// vscreen.h -*-c++-*-
+//
+// Copyright 2000, 2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// This file declares general routines and interfaces for the vscreen
+// code. Formerly this declared a vscreen class, but with the coming
+// of the Great Widget Revolution, things changed..a LOT.
+//
+// Basically, you can set the toplevel widget here and run a main loop.
+// The toplevel widget is responsible for almost everything else.
+
+#ifndef VSCREEN_H
+#define VSCREEN_H
+
+#include <sigc++/signal.h>
+
+#include "ref_ptr.h"
+
+/** An event in the global event queue. Events are dispatched
+ * serially by the main loop.
+ */
+class vscreen_event
+{
+public:
+ virtual void dispatch() = 0;
+ virtual ~vscreen_event();
+};
+
+/** An event based on sigc++ slots. This is important because it can
+ * avoid some timing issues that occur when an object is destroyed
+ * before its callback triggers. However, you cannot construct one
+ * of these threadsafely unless you are EXTREMELY careful, because it
+ * involves a slot copy; as with other sigc++ stuff, I recommend that
+ * you only create these in the 'main thread'. (of course, passing
+ * around pointers to the resulting object is just fine, as long as
+ * the final invocation is also in the main thread)
+ */
+class slot_event : public vscreen_event
+{
+ sigc::slot0<void> the_slot;
+public:
+ slot_event(const sigc::slot0<void> &_the_slot)
+ : the_slot(_the_slot)
+ {
+ }
+
+ void dispatch();
+};
+
+class vscreen_widget;
+
+void vscreen_init();
+// Performs initialization tasks (including calling init_curses())
+
+void vscreen_install_sighandlers();
+// Installs signal handlers for TERM, INT, QUIT, SEGV, and ABRT which
+// cleanly shut the program down. This can be called after the
+// program starts to re-initialize the display code.
+
+/** Sets the top-level widget to the new value, returning the old
+ * top-level widget. If the top-level widget is to be destroyed,
+ * IT IS THE CALLER'S RESPONSIBILITY TO CALL destroy() BEFORE
+ * DISCARDING THE REFERENCE!
+ */
+ref_ptr<vscreen_widget> vscreen_settoplevel(const ref_ptr<vscreen_widget> &widget);
+
+/** Posts a request to recalculate every widget's layout and update
+ * the screen. May be called from any thread.
+ */
+void vscreen_queuelayout();
+
+void vscreen_layoutnow();
+// Lays out all widgets immediately.
+
+// Main loop handlers:
+
+void vscreen_mainloop(int synch=0);
+// Enters a loop, calling getch() over and over and over again..
+// A valid vscreen must be currently displayed.
+
+/** Post the given event to the main event queue. When the event
+ * comes off the queue, its dispatch method will be invoked and it
+ * will immediately be destroyed.
+ *
+ * This method is thread-safe and is the main mechanism by which
+ * other threads should communicate with the main thread.
+ */
+void vscreen_post_event(vscreen_event *ev);
+
+/** Dispatch any events in the event queue. This is deprecated in
+ * favor of the more reliable approach of using threads and
+ * post_event.
+ *
+ * \return \b true if pending input was found.
+ */
+bool vscreen_poll();
+
+void vscreen_exitmain();
+// Exits the main loop.
+
+void vscreen_redraw();
+// Redraws the screen completely from scratch
+
+/** Posts a request to redraw the screen; may be called from any thread. */
+void vscreen_update();
+
+/** Executes any pending draws or redraws. */
+void vscreen_tryupdate();
+
+/** Posts a request to update the cursor location; may be called from
+ * any thread.
+ */
+void vscreen_updatecursor();
+
+/** Hides all widgets and suspends Curses operation until
+ * vscreen_resume() is called. In addition, sets SIGCONT and SIGTSTP
+ * to SIG_DFL (appropriate if you'll be running subprocesses);
+ * vscreen_resume() will restore these handlers.
+ */
+void vscreen_suspend();
+
+/** Hides all widgets and suspends Curses operation until
+ * vscreen_resume() is called, but does NOT reset the SIGCONT and
+ * SIGTSTP handlers.
+ */
+void vscreen_suspend_without_signals();
+
+/** Does the same thing as vscreen_suspend, except that it also
+ * destroys the top-level widget. Call this when the program is
+ * exiting.
+ */
+void vscreen_shutdown();
+
+/** Returns to Curses mode after a vscreen_suspend*, restoring any
+ * signal handlers that were modified by the suspend routine.
+ */
+void vscreen_resume();
+
+/** Invoke the given vscreen_event in at least msencs from the current
+ * time.
+ *
+ * \return a numerical identifier of the new event; you can use this
+ * number to delete it.
+ */
+int vscreen_addtimeout(vscreen_event *ev, int msecs);
+
+/** Delete the event with the given identifier. */
+void vscreen_deltimeout(int id);
+
+void vscreen_handleresize();
+// Does anything needed to handle a window resize event.
+// FIXME: I --think-- that this is now redundant
+
+// Return a mutex that is held whenever the main loop is processing
+// events or drawing the screen. The mutex can be held by other
+// threads that want to do their own drawing or processing, although
+// this is highly discouraged (use the event posting system instead
+// to run code in the main loop).
+namespace threads { class mutex; }
+threads::mutex &vscreen_get_mutex();
+
+extern sigc::signal0<void> main_hook;
+// Called right after we finish handling input in the mainloop. Can
+// be used (eg) to insert extra actions to be performed after all
+// user-input (aptitude uses this to check for apt errors and pop up a
+// message about them)
+
+#endif
diff --git a/src/vscreen/vscreen_widget.cc b/src/vscreen/vscreen_widget.cc
new file mode 100644
index 00000000..7070f20f
--- /dev/null
+++ b/src/vscreen/vscreen_widget.cc
@@ -0,0 +1,303 @@
+// vscreen_widget.cc
+
+#include "vscreen_widget.h"
+#include "vs_container.h"
+
+// needed for queuelayout:
+#include "vscreen.h"
+
+#include <set>
+
+#include <sigc++/adaptors/bind.h>
+#include <sigc++/functors/mem_fun.h>
+
+#include <config/colors.h>
+#include <config/keybindings.h>
+
+#include <assert.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+using namespace std;
+
+// Queues of things that Need To Be Done
+static list<vscreen_widget *> toresize;
+
+vscreen_widget::vscreen_widget()
+ :win(NULL),
+ timeout_value(0),
+ owner(NULL),
+ geom(0,0,0,0),
+ refcount(1),
+ visible(false),
+ isfocussed(false),
+ pre_display_erase(true),
+ is_destroyed(false)
+{
+ focussed.connect(sigc::bind(sigc::mem_fun(*this, &vscreen_widget::set_isfocussed), true));
+ unfocussed.connect(sigc::bind(sigc::mem_fun(*this, &vscreen_widget::set_isfocussed), false));
+}
+
+vscreen_widget::~vscreen_widget()
+{
+ assert(!owner);
+ assert(is_destroyed);
+}
+
+void vscreen_widget::set_bg_style(const style &new_style)
+{
+ bg_style=new_style;
+}
+
+void vscreen_widget::apply_style(const style &st)
+{
+ bkgdset(st.get_attrs());
+ attrset(st.get_attrs());
+}
+
+void vscreen_widget::set_isfocussed(bool _isfocussed)
+{
+ isfocussed=_isfocussed;
+}
+
+void vscreen_widget::set_owner_window(cwindow _win, int x, int y, int w, int h)
+{
+ vs_widget_ref tmpref(this);
+
+ if(_win)
+ {
+ geom.x=x;
+ geom.y=y;
+ geom.w=w;
+ geom.h=h;
+
+ if(geom.h==0 || geom.w==0)
+ win=NULL;
+ else
+ {
+ assert(!is_destroyed);
+
+ win=_win.derwin(geom.h,
+ geom.w,
+ geom.y,
+ geom.x);
+ win.keypad(true);
+ }
+ }
+ else
+ win=NULL;
+
+ // NOT queue_layout()! (that would be just Dumb[tm])
+ do_layout();
+}
+
+void vscreen_widget::alloc_size(int x, int y, int w, int h)
+{
+ if(owner)
+ set_owner_window(owner->win, x, y, w, h);
+ else
+ set_owner_window(NULL, x, y, w, h);
+}
+
+void vscreen_widget::set_owner(vs_container *_owner)
+{
+ owner=_owner;
+ alloc_size(0,0,0,0);
+}
+
+int vscreen_widget::timeout(int msecs)
+{
+ int prevval=timeout_value;
+ timeout_value=msecs;
+ if(win)
+ win.timeout(msecs);
+ return prevval;
+}
+
+void vscreen_widget::cleanup()
+{
+ assert(is_destroyed);
+ delete this;
+}
+
+void vscreen_widget::destroy()
+{
+ assert(refcount > 0);
+
+ if(is_destroyed)
+ return;
+
+ // Make sure we don't die during the destroy routine.
+ vs_widget_ref this_ref = this;
+
+ hide();
+
+ if(owner)
+ {
+ owner->rem_widget(this);
+ assert(!win);
+ }
+
+ // This must be done after hide() and rem_widget(), because
+ // otherwise some of the vscreen_widget manipulators become NOPs,
+ // which causes confusion and problems for code that accesses them.
+ // For instance, the multiplex rem_widget code expects hide_widget
+ // to do something sensible. I could try to fix this, but it's much
+ // more straightforward to just leave the widget in a non-destroyed
+ // state until after it's disconnected from everything.
+ is_destroyed = true;
+
+ destroyed();
+}
+
+ref_ptr<vs_container> vscreen_widget::get_owner()
+{
+ return owner;
+}
+
+void vscreen_widget::show()
+{
+ vs_widget_ref tmpref(this);
+
+ if(is_destroyed)
+ return;
+
+ visible=true;
+
+ shown_sig();
+}
+
+void vscreen_widget::show_all()
+{
+ vs_widget_ref tmpref(this);
+
+ if(is_destroyed)
+ return;
+
+ show();
+}
+
+void vscreen_widget::hide()
+{
+ vs_widget_ref tmpref(this);
+
+ if(is_destroyed)
+ return;
+
+ visible=false;
+
+ hidden_sig();
+}
+
+void vscreen_widget::display(const style &st)
+{
+ vs_widget_ref tmpref(this);
+
+ if(is_destroyed)
+ return;
+
+ // Erase our window, using the composition of the surrounding style
+ // and our background style.
+ style basic_st=st+bg_style;
+ int bgattr=basic_st.get_attrs();
+
+ if(pre_display_erase)
+ {
+ bkgd(bgattr);
+ erase();
+ }
+
+ attrset(bgattr);
+ paint(basic_st);
+}
+
+bool vscreen_widget::focus_me()
+{
+ if(is_destroyed)
+ return false;
+
+ return !auxillary_bindings.empty();
+}
+
+bool vscreen_widget::handle_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ if(is_destroyed)
+ return false;
+
+ bool rval=false;
+
+ for(key_connection i=auxillary_post_bindings.begin();
+ i!=auxillary_post_bindings.end();
+ i++)
+ if(i->bindings->key_matches(k, i->keyname))
+ {
+ i->slot();
+ rval=true;
+ }
+
+ return rval;
+}
+
+bool vscreen_widget::dispatch_key(const key &k)
+{
+ vs_widget_ref tmpref(this);
+
+ if(is_destroyed)
+ return false;
+
+ bool rval=false;
+
+ for(key_connection i=auxillary_bindings.begin();
+ i!=auxillary_bindings.end();
+ i++)
+ if(i->bindings->key_matches(k, i->keyname))
+ {
+ i->slot();
+ rval=true;
+ }
+
+ return rval || handle_key(k);
+}
+
+void vscreen_widget::dispatch_mouse(short id, int x, int y, int z,
+ mmask_t bmask)
+{
+}
+
+vscreen_widget::key_connection vscreen_widget::connect_key(const string &key,
+ keybindings *bindings,
+ const sigc::slot0<void> &slot)
+{
+ auxillary_bindings.push_back(binding_connection(key, bindings, slot));
+
+ key_connection rval=auxillary_bindings.end();
+ --rval;
+
+ return rval;
+}
+
+vscreen_widget::key_connection vscreen_widget::connect_key_post(const string &key,
+ keybindings *bindings,
+ const sigc::slot0<void> &slot)
+{
+ auxillary_post_bindings.push_back(binding_connection(key, bindings, slot));
+
+ key_connection rval=auxillary_bindings.end();
+ --rval;
+
+ return rval;
+}
+
+void vscreen_widget::disconnect_key(key_connection key)
+{
+ auxillary_bindings.erase(key);
+}
+
+void vscreen_widget::disconnect_key_post(key_connection key)
+{
+ auxillary_post_bindings.erase(key);
+}
diff --git a/src/vscreen/vscreen_widget.h b/src/vscreen/vscreen_widget.h
new file mode 100644
index 00000000..6dac4280
--- /dev/null
+++ b/src/vscreen/vscreen_widget.h
@@ -0,0 +1,503 @@
+// vscreen_widget.h -*-c++-*-
+//
+// Copyright (C) 2000-2005 Daniel Burrows
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; see the file COPYING. If not, write to
+// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+// Boston, MA 02111-1307, USA.
+//
+// "widgets" are sort of what they sound like -- entities that get
+// drawn on the screen in some stacked order and can grab keyboard
+// input. By default, the widget currently on the top of the stack
+// has keyboard "focus". (overriding this may be an option eventually
+// but isn't right now) (the widget with keyboard focus gets to
+// determine cursor information)
+//
+//
+// Lifetime of a widget: a widget is potentially active and visible
+// until it is destroyed (via the destroy() method). Once a widget
+// has been destroyed, you cannot un-destroy it, but further calls to
+// destroy() are allowed until the widget is deleted for good.
+// Widgets should generally be referred to using ref_ptr objects; to
+// enforce this, widgets can only be allocated via W::create(...). Of
+// course, as with any refcounting scheme, it's the user's
+// responsibility to prevent cycles by using unsafe_get_ref()
+// appropriately. For instance, pointers to the widget's parent have
+// to be created this way.
+
+#ifndef VSCREEN_WIDGET_H
+#define VSCREEN_WIDGET_H
+
+#include <list>
+#include <vector>
+
+#include <sigc++/signal.h>
+#include <sigc++/trackable.h>
+
+#include "curses++.h"
+#include "config/style.h"
+#include "ref_ptr.h"
+
+#include <assert.h>
+
+class vs_container;
+class key;
+
+struct size
+{
+ int w, h;
+ size(int _w, int _h):w(_w), h(_h) {}
+};
+
+struct point
+{
+ int x, y;
+ point(int _x, int _y):x(_x), y(_y) {}
+};
+
+struct rect
+{
+ int x, y, w, h;
+ rect(int _x, int _y, int _w, int _h):x(_x), y(_y), w(_w), h(_h) {}
+ void set_size(const size &s) {w=s.w; h=s.h;}
+ size get_size() {return size(w, h);}
+};
+
+class keybindings;
+
+/** The basic widget interface. Note that due to the current
+ * reference counting implementation, this is not presently
+ * threadsafe.
+ */
+class vscreen_widget:virtual public sigc::trackable
+{
+ friend class vs_container;
+
+ // Too many friends..
+ friend bool vscreen_poll();
+ friend void vscreen_mainloop(int);
+ friend void vscreen_redraw();
+ friend ref_ptr<vscreen_widget> vscreen_settoplevel(const ref_ptr<vscreen_widget> &);
+ friend void vscreen_suspend_without_signals();
+ friend void vscreen_resume();
+ friend void vscreen_updatecursornow();
+ friend void vscreen_handleresize();
+
+ // Used to store info on externally/explicitly set bindings.
+ struct binding_connection
+ {
+ std::string keyname;
+
+ keybindings *bindings;
+
+ sigc::slot0<void> slot;
+
+ binding_connection():bindings(NULL) {}
+ binding_connection(const std::string &_keyname, keybindings *_bindings, const sigc::slot0<void> &_slot)
+ :keyname(_keyname), bindings(_bindings), slot(_slot) {}
+ };
+
+ // Bindings set via connect_key() and connect_key_post()
+ std::list<binding_connection> auxillary_bindings, auxillary_post_bindings;
+
+ cwindow win;
+
+ int timeout_value;
+
+ vs_container *owner;
+
+ // Needed for recreating the window when the vscreen's window gets switched.
+ // This stores the CURRENT size of the widget.
+ rect geom;
+
+ /** The basic style attached to this widget. */
+ style bg_style;
+
+ /** The number of live references to this object. This is initially
+ * 1, so that it's safe to take references in the constructor. It
+ * is expected that subclasses will only permit construction via
+ * static methods that remove this initial reference.
+ */
+ mutable int refcount;
+
+ // Whether the widget is visible (distinct from whether it has a window;
+ // answers the question "should this widget have a window?")
+ bool visible:1;
+
+ // Tracks whether or not we have the focus.
+ bool isfocussed:1;
+
+ /** If \b true (the default setting), clear this widget with the
+ * background color before displaying it.
+ */
+ bool pre_display_erase:1;
+
+ bool is_destroyed:1;
+
+ // Used to set the owner-window without setting the owner. Used only
+ // to handle the toplevel widget (which has a window but no owner)
+ // Like alloc_size
+ void set_owner_window(cwindow _win, int x, int y, int w, int h);
+
+ // Used to update the "focussed" state
+ void set_isfocussed(bool _isfocussed);
+protected:
+ cwindow get_win() {return win;}
+
+ /** Display this widget.
+ *
+ * \param st the style environment in which the widget is to be
+ * displayed.
+ */
+ virtual void paint(const style &st)=0;
+
+ /** Handles a keypress in this widget.
+ *
+ * \param k the key that was pressed (see keybindings.h).
+ *
+ * \return \b true if the key was consumed; if \b false is
+ * returned, further processing of the key will be performed.
+ */
+ virtual bool handle_key(const key &k);
+
+ /** Handle cleanup when the reference count goes to 0. */
+ void cleanup();
+protected:
+ vscreen_widget();
+
+public:
+ void incref()
+ {
+ ++refcount;
+ }
+
+ void decref()
+ {
+ assert(refcount > 0);
+
+ --refcount;
+ if(refcount == 0)
+ cleanup();
+ }
+
+ static void handle_pending_deletes();
+
+ // show() and hide() do the expected. show_all() makes a container show
+ // all of its "children". (err..it doesn't make sense for menubars to show
+ // their menus, but aside from that..)
+ void show();
+ virtual void show_all();
+ void hide();
+ void toggle_visible()
+ {
+ if(visible)
+ hide();
+ else
+ show();
+ }
+ void set_visible(bool _visible)
+ {
+ if(visible!=_visible)
+ {
+ if(_visible)
+ show();
+ else
+ hide();
+ }
+ }
+
+ virtual ~vscreen_widget();
+
+ // This should be called when an arbitrary widget is to have a
+ // keypress sent to it.
+ bool dispatch_key(const key & k);
+
+ // This should be called when an arbitrary widget is to have a mouse event
+ // sent to it. Override it to change mousing behavior.
+ virtual void dispatch_mouse(short id, int x, int y, int z, mmask_t bstate);
+
+
+
+ // The following methods deal with handling widget layout. Widget
+ // layout is a two-shot affair: first, all widgets are allocated
+ // space in the X dimension; then, all widgets are allocated space
+ // in the Y dimension. Doing allocation in this asymmetrical way
+ // allows widgets with complex interdependencies between their
+ // dimensions to be handled (for instance: a widget that wraps the
+ // text it displays, and will have to request more height if its
+ // width decreases).
+ //
+ // You can assume that the widget's state is unchanged between a
+ // call to width_request() and a call to height_request().
+
+ /** \return the desired width of the widget. */
+ virtual int width_request()=0;
+
+ /** Calculate the desired height of the widget, given its width.
+ *
+ * \param width the width of this widget
+ * \return the desired height
+ */
+ virtual int height_request(int width)=0;
+
+ /** Set the size and location in the parent of this widget. This
+ * routine should be called by the parent to actually resize and/or
+ * move the widget around. There is no guarantee that the new
+ * width and height bear any relation to what the _request
+ * functions asked for, although the parent is expected to make a
+ * best-effort attempt to give a widget its desired size.
+ *
+ * As a special case, calling alloc_size(0, 0, 0, 0) indicates that
+ * the widget has no allocation at all.
+ *
+ * \param x the new x location within the parent
+ * \param y the new y location within the parent
+ * \param w the new width of the widget
+ * \param h the new height of the widget
+ */
+ void alloc_size(int x, int y, int w, int h);
+
+
+
+
+ virtual bool focus_me();
+ // Returns true if this widet wants the keyboard focus (used in, eg, dialog
+ // boxes)
+
+ bool get_isfocussed() {return isfocussed;}
+
+ void set_owner(vs_container *w);
+ // Makes this widget a child of the given widget (incidentally will delete
+ // any allocated size; setting the owner to NULL hides the widget for now)
+
+ /** Display this widget in the given style environment. This is an
+ * interface function only; subclasses should override paint() to
+ * control behavior (but should call display() on subwidgets).
+ *
+ * \param st the style environment in which this widget should be
+ * displayed.
+ */
+ void display(const style &st);
+
+ int timeout(int msecs);
+
+ /** Destroys the visible representation of this widget and
+ * disconnects it from any children that it may have.
+ */
+ virtual void destroy();
+
+ ref_ptr<vs_container> get_owner();
+
+ virtual bool get_cursorvisible()=0;
+ virtual point get_cursorloc()=0;
+
+ int get_startx() {return geom.x;}
+ int get_starty() {return geom.y;}
+ int get_width() {return geom.w;}
+ int get_height() {return geom.h;}
+
+ bool get_visible() {return visible;}
+
+ // Should NOT be overridden -- that was a thinko
+ void sync() {if(win) {win.touch(); win.noutrefresh();}}
+
+ // from the original vscreen
+ int scroll(int n=1) {return win?win.scroll(n):0;}
+
+ int addch(chtype ch) {return win?win.addch(ch):0;}
+ int mvaddch(int y, int x, chtype ch) {return win?win.mvaddch(y,x,ch):0;}
+
+ int add_wch(wchar_t wch)
+ {
+ return win?win.add_wch(wch):0;
+ }
+
+ int add_wch(const cchar_t *cch)
+ {
+ return win?win.add_wch(cch):0;
+ }
+
+ int mvadd_wch(int y, int x, wchar_t wch)
+ {
+ return win?win.mvadd_wch(y, x, wch):0;
+ }
+
+ int mvadd_wch(int y, int x, const cchar_t *cch)
+ {
+ return win?win.mvadd_wch(y, x, cch):0;
+ }
+
+ int addstr(const char *str) {return win?win.addstr(str):0;}
+ int addnstr(const char *str, int n) {return win?win.addnstr(str, n):0;}
+ int mvaddstr(int y, int x, const char *str) {return win?win.mvaddstr(y, x, str):0;}
+ int mvaddnstr(int y, int x, const char *str, int n) {return win?win.mvaddnstr(y, x, str, n):0;}
+
+ int addstr(const wchar_t *str) {return win?win.addstr(str):0;}
+ int addnstr(const wchar_t *str, int n) {return win?win.addnstr(str, n):0;}
+ int mvaddstr(int y, int x, const wchar_t *str) {return win?win.mvaddstr(y, x, str):0;}
+ int mvaddnstr(int y, int x, const wchar_t *str, int n) {return win?win.mvaddnstr(y, x, str, n):0;}
+
+ int addstr(const std::wstring &str) {return win?win.addstr(str):0;}
+ int addnstr(const std::wstring &str, int n) {return win?win.addnstr(str, n):0;}
+ int mvaddstr(int y, int x, const std::wstring &str) {return win?win.mvaddstr(y, x, str):0;}
+ int mvaddnstr(int y, int x, const std::wstring &str, int n) {return win?win.mvaddnstr(y, x, str, n):0;}
+
+ int addstr(const wchstring &str) {return win?win.addstr(str):0;}
+ int addnstr(const wchstring &str, int n) {return win?win.addnstr(str, n):0;}
+ int mvaddstr(int y, int x, const wchstring &str) {return win?win.mvaddstr(y, x, str):0;}
+ int mvaddnstr(int y, int x, const wchstring &str, int n) {return win?win.mvaddnstr(y, x, str, n):0;}
+
+ int addstr(const chstring &str) {return win?win.addstr(str):0;}
+ int addnstr(const chstring &str, int n) {return win?win.addnstr(str, n):0;}
+ int mvaddstr(int y, int x, const chstring &str) {return win?win.mvaddstr(y, x, str):0;}
+ int mvaddnstr(int y, int x, const chstring &str, int n) {return win?win.mvaddnstr(y, x, str, n):0;}
+
+ int attroff(int attrs) {return win?win.attroff(attrs):0;}
+ int attron(int attrs) {return win?win.attron(attrs):0;}
+ int attrset(int attrs) {return win?win.attrset(attrs):0;}
+
+ void bkgdset(const chtype ch) {if(win) win.bkgdset(ch);}
+ int bkgd(const chtype ch) {return win?win.bkgd(ch):0;}
+ chtype getbkgd() {return win?win.getbkgd():0;}
+
+ int border(chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br)
+ {return win?win.border(ls,rs,ts,bs,tl,tr,bl,br):0;}
+ int box(chtype verch, chtype horch) {return win?win.box(verch,horch):0;}
+ int hline(chtype ch, int n) {return win?win.hline(ch,n):0;}
+ int vline(chtype ch, int n) {return win?win.vline(ch,n):0;}
+ int mvhline(int y, int x, chtype ch, int n) {return win?win.mvhline(y, x, ch, n):0;}
+ int mvvline(int y, int x, chtype ch, int n) {return win?win.mvvline(y,x,ch,n):0;}
+
+ int delch() {return win?win.delch():0;}
+ int mvdelch(int y, int x) {return win?win.mvdelch(y, x):0;}
+
+ int deleteln() {return win?win.deleteln():0;}
+ int insdelln(int n) {return win?win.insdelln(n):0;}
+ int insertln() {return win?win.insertln():0;}
+
+ int echochar(chtype ch) {return win?win.echochar(ch):0;}
+
+ int move(int y, int x) {return win?win.move(y,x):0;}
+ void getyx(int &y, int &x) {if(win) win.getyx(y,x); else y=x=0;}
+ void getbegyx(int &y, int &x) {if(win) win.getbegyx(y,x); else y=x=0;}
+ void getmaxyx(int &y, int &x) {if(win) win.getmaxyx(y,x); else y=x=0;}
+ int getmaxy() {return win?win.getmaxy():0;}
+ int getmaxx() {return win?win.getmaxx():0;}
+
+ void show_string_as_progbar(int x, int y, const std::wstring &s,
+ const style &st1, const style &st2,
+ int size1, int totalsize)
+ {
+ if(win)
+ win.show_string_as_progbar(x, y, s,
+ st1.get_attrs(),
+ st2.get_attrs(),
+ size1, totalsize);
+ }
+
+ void display_header(std::wstring s, const style &st) {if(win) win.display_header(s, st.get_attrs());}
+ void display_status(std::wstring s, const style &st) {if(win) win.display_status(s, st.get_attrs());}
+
+ int erase() {return win?win.erase():0;}
+ int clear() {return win?win.clear():0;}
+ int clrtobot() {return win?win.clrtobot():0;}
+ int clrtoeol() {return win?win.clrtoeol():0;}
+
+ // FIXME: we should preserve these settings ourselves and restore them on
+ // set_win(). ?
+ int keypad(bool bf) {return win?win.keypad(bf):0;}
+ int meta(bool bf) {return win?win.meta(bf):0;}
+
+ bool enclose(int y, int x)
+ {
+ if(win)
+ return y>=geom.y && y<geom.y+geom.h && x>=geom.x && x<geom.x+geom.w;
+ else
+ return false;
+ }
+
+ /** Enable or disable clearing the background before displaying the
+ * widget.
+ *
+ * \param opaque if \b true (the default setting), the widget's
+ * entire area will be overwritten with its background style prior
+ * to displaying it.
+ */
+ void set_opaque(bool opaque)
+ {
+ pre_display_erase=opaque;
+ }
+
+ /** Update this widget's basic style to the given value. The style
+ * stack must be empty.
+ */
+ void set_bg_style(const style &new_style);
+
+ /** Set the display attributes of our associated window directly
+ * from the given style. (it is expected that subclasses will use
+ * this to control what is output to the window)
+ */
+ void apply_style(const style &st);
+
+ typedef std::list<binding_connection>::iterator key_connection;
+ // This can be used to connect to a pseudo-signal for keypresses.
+ // Most useful for stuff like setting up hotkeys and keyboard accelerators..
+ key_connection connect_key(const std::string &key,
+ keybindings *bindings,
+ const sigc::slot0<void> &slot);
+ // Same, but the key is tested for after all other possibilities are
+ // exhausted.
+ key_connection connect_key_post(const std::string &key,
+ keybindings *bindings,
+ const sigc::slot0<void> &slot);
+
+ // The opposite..
+ void disconnect_key(key_connection c);
+ // Eww, do I really need two of these?
+ void disconnect_key_post(key_connection c);
+
+ // Signals:
+ //
+ // I use signals for events that an external object (eg,
+ // a container) might want to act on. For instance, when an object is
+ // hidden, its parent might want to rearrange its children.
+ //
+ // In contrast, virtual methods are used for specific behaviors of this
+ // class: for instance, displaying the widget itself.
+
+ sigc::signal0<void> shown_sig;
+ // Emitted when the object is shown. (not to be confused with the obsolete
+ // show_sig, which was a request by the object to be shown)
+
+ sigc::signal0<void> hidden_sig;
+ // similarly
+
+ sigc::signal0<void> destroyed;
+ // Sent before a widget is destroyed.
+ // A widget is always hidden before being destroyed
+
+ sigc::signal0<void> do_layout;
+ // Sent when the widget's layout needs to be recalculated and child windows
+ // need to be re-updated (mainly when the size is altered)
+ // This should not be called directly by the user. Use vscreen_queuelayout()
+ // instead.
+
+ sigc::signal0<void> focussed;
+ sigc::signal0<void> unfocussed;
+ // Sent when we gain or lose the keyboard focus.
+};
+
+typedef ref_ptr<vscreen_widget> vs_widget_ref;
+
+#endif