The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.


PerlVision - Text-mode User Interface Widgets.


  use PV;

  init PV;

  my $foo = new PV::Static ("Text", $x1, $y1, $x2, $y2);


Once upon a time I needed a basic text-mode GUI framework to implement some nice-looking interfaces for the Linux console. Didn't find any around, so necessity became the mother of PerlVision, which kept growing as I kept adding more goodies, so now it's far from basic...

PV provides 90% of the features you'd want for a user interface, including checkboxes, radiobuttons, three different styles (!) of pushbuttons, single and multiple selection listboxes, an extensible editbox, a scrollable viewbox, single line text entry fields, a menubar with pulldown menus, and full popup dialog boxes with multiple controls.


The following object classes are defined within PV:


A static text region, trivial class.


A single 2-state checkbox.


A single 2-state radiobutton.


A group of connected radiobuttons.


A single selection list box.


A multiple selection list box.


A single line text entry field.


A single line text entry field that *'s out what's typed.


A top line menu bar with single-level pulldown submenus.


A combo box.


A multi line edit box.


A readonly viewer/pager for text files.


A push button that takes 3 lines of screen real estate.


A push button that takes 2 lines of screen real estate.


A no-frills button that fits on a single line.


A full dialog box with as many controls as you like.

  • Other classes are defined for internal use by PerlVision and should not be used from outside. Also, see below why use of the PV::Radio control is limited from outside.

  • Constructors for all the classes are called new().

  • All classes expect that you will not fiddle with the object's data yourself.

  • All nontrivial controls (except PV::RadioG, see below) have an activate() method. It makes the control active, and returns when any traditional shift focus key is pressed - see the section on PV::Dialog for more details.

  • All nontrivial controls have a stat() method, which returns the status of the control (checked, unchecked, text, whatever).


  my $foo = new PV::Static ("Text", $x1, $y1, $x2, $y2);

This is the trivial text region control. It's there mainly so you can put static text in dialog boxes. It doesn't have an activate() or stat() method.

  $foo->display;      # Displays the widget.  

If your text doesn't fit in the space you allocate, it'll be truncated. It's also your responsibility to provide line breaks if you don't want all the text to be thought of as a single line.


  my $foo = new PV::Checkbox ("Label", $x, $y, $stat);

Arguments $x and $y are the X and Y co-ordinates to place the control. $stat is 1 if the Checkbox is checked, 0 if not. "Label" is printed on the left of the checkbox.

  $foo->display;      # Displays checkbox.
  $foo->activate;     # Gives it focus. Exits on 1,2,3,4,5,6,7 codes.
  $foo->select;       # Toggles status.
  $foo->stat;         # Returns status. (1 checked, 0 unchecked)


  my $foo = new PV::Radio ("Label", $x, $y, $stat);

PV::Radio is a direct descendant of PV::Checkbox that just looks a bit different. All the methods defined for PV::Checkbox are defined for PV::Radio as well, But don't try to use PV::Radio as a different looking PV::Checkbox.

Because radio buttons are generally meant to be grouped and to affect the state of all other buttons in the group. So unless you include a radio button in a group with PV::RadioG (see below), you're liable to hurt something. Once it's in a group you can use all the methods outlined above for PV::Checkbox.


  my $foo = new PV::RadioG ($radio_1, $radio_2, $radio_3...);

Where $radio_* are PV::Radio objects. See? You take your PV::Radio objects, and feed them to the constructor for PV::RadioG, and out pops a radio button group.

  $foo->display;      # Displays all radio buttons in group.
  $foo->stat;         # To figure out which button is the one that's
                      # selected. This returns the Label of the selected
                      # button.

PV::RadioG has no 'activate' method. You activate the PV::Radio objects directly. This is much more flexible for use in dialog boxes.


  my $foo = new PV::Listbox ("Head", $x1, $y1, $x2, $y2, 
                             "Label1", 0, "Label2", 0...);

Yes, the element following each "Labeln" should be 0 for this to work right.

"Label*" are the strings that will be shown in the listbox. "Head" will be printed across above the top of the listbox.

  $foo->activate;     # Gives it focus. Exits on 5,6,7,8 exit codes.
  $foo->stat;         # Returns the label of the selected entry.


  my $foo = new PV::Mlistbox ("Head", $x1, $y1, $x2, $y2, 
                              "Label1", 0, "Label2", 0...);

Yes, the element following each "Labeln" should be 0 for this to work right.

"Label*" are the strings that will be shown in the listbox. "Head" will be printed across above the top of the listbox.

  $foo->activate;     # Gives it focus. Exits on 5,6,7,8 exit codes.
  $foo->stat;         # Returns a list of all selected labels.


  my $foo = new PV::Entryfield ($x, $y, $length, $max,
                                "Label", "Initial Value");

$length is the length of the text entry area. $max is the maximum length of the input. actually this is ignored. ;-) "Label" is printed to the left of the entry field. Can be "". The entryfield is pre-initialized to "Initial Value". Can be "".

  $foo->activate;     # Gives it focus. Exits on 1,2,5,6,7,8 exit codes.
                      # Changed text is always saved, regardless of
                      # how the loop exited.
  $foo->stat;         # Returns the text value of the entryfield.


Identical to PV::Entryfield except that it displays '*'s instead of what the user types.


The menu bar is a bit odd. The way you do it is set it up with just one pulldown, then add pulldowns to it till you have enough. Don't add too many (i.e. that there's not enough space for their heads on the menubar) or things will definitely get broken.

  my $foo = new PV::Menubar ("Head", $width, $depth, 
                             "label1", 0, "label2", 0...);

Just like with the listboxes, each list element is followed by a 0. This list becomes your first pulldown. Now to add more pulldowns, do:

  $foo->add("Head", $width, $depth, "label1", 0, "label2", 0...);

That's the second pulldown, and so on. Because of this step by step method of building up the menubar, you need to display it once you're finished adding pulldowns, it doesn't automatically display itself. Do a:


To activate:


It'll exit on 5, 7, and 8. On 8, it'll give you a second element in the return list of the form "Pulldown:Selection". The "Pulldown" is the head of the pulldown menu, the "Selection" is the label of the selection.

Help context does not come through on the 5 exit code. i.e. you can't tell which pulldown was active when help was requested, or which selection in which pulldown. C'est la vie.


Not implemented yet. I'll get around to it when I need it I guess. Actually it's a pretty trivial offspring of a listbox and an entryfield.


  my $foo = new PV::Editbox ($x1, $y1, $x2, $y2, $margin, 
                             "Text", $index, $start);

$margin is the word-wrap boundary. If it's bigger than the size of the box, that's your headache.

$text is a text string to be dumped into the editbox. it will be stripped of CRs (not LFs), TABs, and nulls, and justified the way the editbox does it (see below).

$index is the start position within the text to initially place the cursor at. First char is 0.

$start is the line number to position at the top of the editbox, if possible. First line is 0.

  $foo->activate;     # Gives it focus. Exits on 5,6,7 exit codes.
                      # Changed text is always saved, regardless of
                      # how the loop exited.
  $foo->stat;         # Returns the text value of the editbox.

There are some hooks in there to let you subclass it and do things. One is an empty 'sub statusbar' that's called every-time the display is refreshed. Another is an empty 'sub process_key' which is used extensively in rap to build a full editor out of the editbox control.

The editbox does automatic word-wrapping and reverse word-wrapping and other fancy stuff. The style of auto-wrapping I chose is what personally irritates me the least (all auto-wraps irritate me). Trying to change the wrap style is likely to be very hairy, and will probably break the editbox. It took a lot of tweaking of plenty of regexps to get it to work the way it does.


  my $foo = new PV::Viewbox ($x1, $y1, $x2, $y2, $text, $start);

Much like PV::Editbox but it's readonly and the arrow keys have different bindings. I will eventually implement hardware scrolling in viewboxes that extend the length of the display so that it's a fast browser.

PV::Pushbutton, PV::Cutebutton, PV::Plainbutton

  my $foo = new PV::Pushbutton ("Label", $x1, $y1);

Makes a simple push button.

  $foo->display();    # Displays it.
  $foo->activate();   # Activates it.

Exits on codes 1,2,3,4,5,6,7,8. On 8, it 'depresses' and it's up to you to 'undepress' it by calling the display method.

PV::Pushbutton is BIG. It takes 3 lines on the screen. PV::Cutebutton is my favorite - it takes only two lines, and actually pushes and pops around so it's fun to watch ;) PV::Plainbutton is a basic one-line button which does absolutely nothing fancy but is very useful in some situations (e.g. for hyper-text).


This is the guy that puts it all together and does all the work of managing how focus switches between multiple controls in a dialog box. Once you've created all the controls you need, you can feed them to PV::Dialog and out pops an object that you can trust to handle everything. Above you would have noticed that the activate loops for all controls return en exit code when focus is released. This is what these codes mean:

When an activate loop exits, it returns a code telling you the reason for exiting:

1 = Up Arrow (Traditional shift-focus key)

2 = Down Arrow (Traditional shift-focus key)

3 = Right Arrow (Traditional shift-focus key)

4 = Left Arrow (Traditional shift-focus key)

5 = M-h (For help)

6 = M-x (For menu)

7 = Tab (Traditional shift-focus key)

8 = Enter (Traditional 'Done here' key)

These codes are used by the PV::Dialog control to figure out how to switch focus between controls, and when to exit. Here's how to create a PV::Dialog object:

  $foo = new PV::Dialog ("Title", $x1, $y1, $x2, $y2, $style, $color,
                         $control1, 1, 2, 2, 1, 1, 1, 2, 0,
                         $control2, 1, 3, 3, 1, 2, 2, 3, 0,

"Title" is currently ignored.

$style: if 1, creates a popup that is 'raised'. if 0, creates a popup that is 'depressed'

$color is the background color for the dialog. I'd recommend 6 (cyan) because of the overall hardcoded buddha-nature of colors at present.

$control* are PV::* objects that you created beforehand (I think they can even be PV::Dialog types, though I haven't tested it. They can't be PV::Menubar types). Note that the controls must be positioned relative to the origin of the dialog box, not relative to the screen origin (dialog boxes are actually curses windows, and that's how curses likes it).

How the dialog box works is that the control+exitcode matrix tells PV::Dialog which control to switch focus to on each of the 8 exit codes listed above. So when you do a:


PV::Dialog starts off by displaying itself and giving focus to $control1. When $control1 exits, $foo looks in the list that follows $control1 in the constructor syntax above to figure out which control to give focus to next. The list is simply numbers that say which control. So 1 represents $control1, 2 represents $control2, and so on, strictly based on the order in which the controls appear in the constructor invocation.

The special value 0 is reserved to tell PV::Dialog that it's time to exit and hide the dialog box. I also use it as a place-holder for those exit-codes that a certain control never returns, for example of $control1 above was a PV::Editbox, I'd put 0's in the list following $control1 at positions 1,2,3 and 4 because the edit box object never exits on those codes (those keys have meaning within the editbox)

If you don't want focus to switch off a control when a certain exitcode is returned, simply put that control's own number in the corresponding position in the list.

Look in the rap code for an example of PV::Dialog use, the $options object. It's generally very easy and powerful.

When PV::Dialog's activate exits, it returns a two-element list. First element tells you which was the last control to be active (again numbered as they appear in the constructor invocation), and the second element tells you what exitcode that control returned.

After the dialog box has exited, you can call 'stat' on each control to find out what's up. Remember, don't put PV::RadioG controls in a dialog box, they don't have an activate method. Put the corresponding PV::Radio controls in. When you 'stat', you'll be 'stat'ing the PV::RadioG.

Also, don't ever put a PV::Static as the first control in a PV::Dialog. It doesn't have an activate method. If you just want a pop-up box with text and no other controls, either consider using a PV::Viewbox control, or write the text onto the popup box yourself with pv::pvprint.

Goodies: PV::PVD

PerlVision also defines two often needed dialog box styles:

  PV::PVD::message      (A simple message box with OK button)
  PV::PVD::yesno        (An option box with Yes/No buttons)

Both self-center, and make sure the box is big enough to hold the buttons. They don't bother to check if the screen will hold the dialog box, or the dialog box will hold your text. Both use the following syntax:

  $foo = new PV::PVD::message ("Text", $width, $depth);

PV::PVD::yesno returns 1 for yes and 0 for no.

Width and depth are how big you want the text part of the box to be (the buttons are separate).


  • $max in PV::Entryfield is a misnomer. It's actually used internally and should be set to 0 when you create a new entryfield object.

  • Colors are still more-or-less hardcoded.

  • Some vestigal crufts from v0.1 remain, as the rewrite to use curses was accomplished by blatant abuse of emacs's M-x replace-regexp. In particular, all controls still expect X co-ordinates before Y co-ordinates, which is the reverse of how curses likes it. In a future version I'll do away with positional arguments and use hash arguments.

  • should check if the terminal can support the minimum capabilities required, as well as eval uncertain curses calls.

  • Error checking needs work.

  • PV languished for many (4) years without any updates but I am hoping to get a chance to clean it up soon. A lot needs to be worked on - too much to list here.



March 1995. First release. Didn't use curses, did its own screen optimization, which was in Perl and very slow.


April 1995. Found Will Setzer's Curses interface for Perl and did a rewrite using curses/ncurses. Tremendously speeded up and much more portable. Turned into a real Perl 5 module. Many thanx to Tim Bunce for helping clear up my confusion about modules.


July 2000. Moved docs to pod format and put PV in CVS. Brought PV packaging up to date with a 2 digit version number and a Makefile.PL. Hopefully this means I am going to finally do some work on PV soon!


Curses(3), perl(1).


PerlVision is Copyright (c) 2000 Ashish Gulhati <>. All Rights Reserved.


Nick Cabatoff <>


Thanks to Barkha for inspiration and lots of good times; to Raj for the good old days when we hacked Unix and consumed insane quantities of alcohol; to Emily Saliers, Eddie Van Halen and Neil Peart for fantastic music; to William Setzer for Perl Curses, Larry Wall for Perl, and RMS for Emacs.


This code is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

It would be nice if you would mail your patches to me, and I would love to hear about projects that make use of this module.


This is free software. If it breaks, you own both parts.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 2299:

'=item' outside of any '=over'