Correl Roush
a3dfc6d2b8
git-svn-id: file:///srv/svn/scanner/trunk@18 a0501263-5b7a-4423-a8ba-1edf086583e7
425 lines
11 KiB
PHP
425 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* PHP ncurses library
|
|
*
|
|
* Provides a clean, object-oriented interface to the PHP ncurses functions to ease
|
|
* the creation of elaborate, functional CLI applications.
|
|
*
|
|
* @author Correl J. Roush <correl@gmail.com>
|
|
* @copyright Copyright (c) 2008, Correl J. Roush
|
|
* @version 0.1
|
|
*
|
|
* @package Nc
|
|
*/
|
|
|
|
require_once('signals.php');
|
|
|
|
/**
|
|
* Ncurses window
|
|
*
|
|
* Provides a basic window object. A border can be optionally enabled if the window
|
|
* is large enough to accomodate it, and is enabled by default. If a title is set,
|
|
* it will be visible at the top of the border if one exists.
|
|
*
|
|
* If a parent window is provided, the window will be registered as a child of the
|
|
* parent, and will be relatively positioned to the parent.
|
|
*
|
|
* @package Nc
|
|
*/
|
|
class NcWindow extends Signaler {
|
|
protected static $max_id = 0;
|
|
protected $id = 0;
|
|
/**
|
|
* @var resource nc_window resource for the border window, if one exists
|
|
*/
|
|
protected $frame;
|
|
/**
|
|
* @var resource nc_window resource for the container window
|
|
*/
|
|
protected $window;
|
|
/**
|
|
* @var array nc_panel resources for the window and its container
|
|
*/
|
|
protected $panels = array();
|
|
/**
|
|
* @var array children array of child NcWindow references
|
|
*/
|
|
protected $children = array();
|
|
/**
|
|
* @var NcWindow reference to this object's direct parent, if one exists
|
|
*/
|
|
protected $parent = false;
|
|
protected $app = false;
|
|
/**
|
|
* @var int ID of the current active object
|
|
*/
|
|
public static $active_window = false;
|
|
public static $windows = array();
|
|
/**
|
|
* @var integer Absolute column position of the window
|
|
*/
|
|
protected $x;
|
|
/**
|
|
* @var integer Absolute row position of the window
|
|
*/
|
|
protected $y;
|
|
/**
|
|
* @var integer Width of contained window
|
|
*/
|
|
protected $width;
|
|
/**
|
|
* @var integer Height of contained window
|
|
*/
|
|
protected $height;
|
|
/**
|
|
* @var string Window title
|
|
*/
|
|
protected $title;
|
|
protected $enabled = true;
|
|
protected $visible = true;
|
|
protected $shortcuts = array();
|
|
protected $emit_log_filter = array('keypress');
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* Builds the requested ncurses window.
|
|
*
|
|
* @param NcWindow $parent Parent window
|
|
* @param integer $height Height
|
|
* @param integer $width Width
|
|
* @param integer $y Row
|
|
* @param integer $x Column
|
|
* @param boolean $framed Include a frame (border). Required for window titles.
|
|
*/
|
|
public function __construct( &$parent, $height, $width, $y, $x, $framed = true ) {
|
|
$this->id = count(self::$windows);
|
|
self::$windows[] = &$this;
|
|
if( self::$active_window === false ) {
|
|
self::$active_window = $this->id;
|
|
}
|
|
if( $parent instanceof NcWindow ) {
|
|
$this->parent = &$parent;
|
|
$this->app = &$parent->app;
|
|
$x += $parent->x;
|
|
$y += $parent->y;
|
|
$width += $width <= 0 ? $parent->width : 0;
|
|
$height += $height <= 0 ? $parent->height : 0;
|
|
} elseif( $parent instanceof NcApp ) {
|
|
$this->app = &$parent;
|
|
}
|
|
if( $framed ) {
|
|
$this->frame = ncurses_newwin( $height, $width, $y, $x );
|
|
ncurses_getmaxyx( $this->frame, $max_y, $max_x );
|
|
if( $max_y >= 2 && $max_x >= 2 ) {
|
|
$this->window = ncurses_newwin( $max_y - 2, $max_x - 2, $y + 1, $x + 1 );
|
|
ncurses_wborder( $this->frame, 0,0, 0,0, 0,0, 0,0 );
|
|
ncurses_wrefresh( $this->frame );
|
|
$this->panels[] = ncurses_new_panel( $this->frame );
|
|
} else {
|
|
$this->window = $this->frame;
|
|
$this->frame = false;
|
|
}
|
|
} else {
|
|
$this->frame = false;
|
|
$this->window = ncurses_newwin( $height, $width, $y, $x );
|
|
ncurses_getmaxyx( $this->window, $max_y, $max_x );
|
|
}
|
|
$this->width = $this->frame ? $max_x - 2 : $max_x;
|
|
$this->height = $this->frame ? $max_y - 2 : $max_y;
|
|
$this->x = $this->frame ? $x + 1 : $x;
|
|
$this->y = $this->frame ? $y + 1 : $y;
|
|
ncurses_wrefresh( $this->window );
|
|
$this->panels[] = ncurses_new_panel( $this->window );
|
|
ncurses_update_panels();
|
|
if( $this->parent instanceof NcWindow ) {
|
|
$this->parent->add_child( $this );
|
|
}
|
|
|
|
self::connect($this, '__emit', $this, '__emit');
|
|
}
|
|
/**
|
|
* Destructor
|
|
*/
|
|
public function __destruct() {
|
|
foreach( $this->panels as $id => $panel ) {
|
|
ncurses_del_panel( $this->panels[$id] );
|
|
}
|
|
ncurses_delwin( $this->window );
|
|
if( $this->frame !== false ) {
|
|
ncurses_delwin( $this->frame );
|
|
}
|
|
}
|
|
|
|
static public function get_window($id) {
|
|
return self::$windows[$id];
|
|
}
|
|
/**
|
|
* Set or retrieve a window title
|
|
*
|
|
* Sets window title to the value provided, returns the current window title
|
|
* if one is not.
|
|
*
|
|
* @param string $value New window title
|
|
* @return string Window title
|
|
*/
|
|
public function title( $value = false ) {
|
|
if( $value === false ) {
|
|
return $this->title;
|
|
} elseif( $this->frame !== false ) {
|
|
$this->title = $value;
|
|
ncurses_wborder( $this->frame, 0,0, 0,0, 0,0, 0,0 );
|
|
ncurses_mvwaddstr( $this->frame, 0, 1, $this->title );
|
|
ncurses_update_panels();
|
|
ncurses_doupdate();
|
|
}
|
|
}
|
|
public function height() { return $this->height; }
|
|
public function width() { return $this->width; }
|
|
public function enabled() { return (bool)$this->enabled; }
|
|
public function active() { return (bool)($this->id === self::$active_window); }
|
|
/**
|
|
* Activate an ncurses output attribute
|
|
*
|
|
* @param integer NCURSES_A_* constant
|
|
*/
|
|
public function attribute_on( $attribute ) {
|
|
ncurses_wattron( $this->window, $attribute );
|
|
}
|
|
/**
|
|
* Deactivate an ncurses output attribute
|
|
*
|
|
* @param integer NCURSES_A_* constant
|
|
*/
|
|
public function attribute_off( $attribute ) {
|
|
ncurses_wattroff( $this->window, $attribute );
|
|
}
|
|
/**
|
|
* Write text to the window
|
|
*
|
|
* Writes text to the window at the specified coordinates.
|
|
* Text is truncated to prevent it overflowing off the side of the window. If
|
|
* the text contains newlines, it is split and each new line will continue from
|
|
* the same x position as the first, rather than wrapping back to the far left
|
|
* side of the window as ncurses normally would.
|
|
*
|
|
* @param integer $y Row
|
|
* @param integer $x Column
|
|
* @param string $text Text to write
|
|
* @param boolean $update Update the window upon completion
|
|
*/
|
|
public function write( $y, $x, $text, $update = true ) {
|
|
$y = $y < 0 ? $this->height + $y : $y;
|
|
$x = $x < 0 ? $this->width + $x : $x;
|
|
$lines = preg_split( '/\r?(\n|\r)/', $text );
|
|
foreach( $lines as $id => $line ) {
|
|
$line = substr( $line, 0, $this->width - $x );
|
|
ncurses_mvwaddstr( $this->window, $y + $id, $x, $line );
|
|
}
|
|
if( $update ) {
|
|
ncurses_update_panels();
|
|
ncurses_doupdate();
|
|
}
|
|
}
|
|
/**
|
|
* Write centered text
|
|
*
|
|
* Writes text centered horizontally within the window on the specified line.
|
|
*
|
|
* @param integer $y Row
|
|
* @param string $text Text to write
|
|
* @param boolean $update Update the window upon completion
|
|
*/
|
|
public function write_centered( $y, $text, $update = true ) {
|
|
$this->write(
|
|
$y,
|
|
floor( $this->width / 2 ) - floor( strlen( $text ) / 2 ),
|
|
$text,
|
|
$update
|
|
);
|
|
}
|
|
/**
|
|
* Write fixed-width text
|
|
*
|
|
* Writes a string truncated or padded with spaces to fill the requested width.
|
|
* If the width is unspecified or zero, the width is defined as the length from
|
|
* the starting coordinate to the right edge of the window.
|
|
*
|
|
* @param integer $y Row
|
|
* @param integer $x Column
|
|
* @param string $text Text to write
|
|
* @param integer $width Field length
|
|
* @param boolean $update Update the window upon completion
|
|
*/
|
|
public function write_fixed( $y, $x, $text, $width = 0, $update = true ) {
|
|
$width = $width > 0 ? $width : $this->width - $x;
|
|
$this->write( $y, $x, $this->_string_fixed( $text, $width ), $update );
|
|
}
|
|
/**
|
|
* Build a fixed-width string
|
|
*
|
|
* Truncates a string if it exceeds the specified width, pads it with spaces
|
|
* if it is too short.
|
|
*
|
|
* @param string $text Text to format
|
|
* @param integer $width Field length
|
|
* @return string Fixed-width string
|
|
*/
|
|
protected function _string_fixed( $text, $width = 0 ) {
|
|
$width = $width < 0 ? 0 : $width;
|
|
$text = substr( $text, 0, $width );
|
|
$text .= str_repeat( ' ', $width - strlen( $text ) );
|
|
return $text;
|
|
}
|
|
/**
|
|
* Assign a window as a child of this window
|
|
*
|
|
* Children of a window are hidden and shown along with their parent widget.
|
|
*
|
|
* @param NcWindow $child Child window
|
|
*/
|
|
public function add_child( $child ) {
|
|
$this->children[$child->id] = $child;
|
|
if( count($this->children) == 1 ) {
|
|
// Make the first child active
|
|
self::$active_window = $child->id;
|
|
$child->focus();
|
|
}
|
|
}
|
|
/**
|
|
* Hide the window
|
|
*
|
|
* @param boolean $update Update the window upon completion
|
|
*/
|
|
public function hide( $update = true ) {
|
|
foreach( $this->panels as $panel ) {
|
|
ncurses_hide_panel( $panel );
|
|
}
|
|
foreach( $this->children as $child ) {
|
|
$child->hide( false );
|
|
}
|
|
if( $update ) {
|
|
ncurses_update_panels();
|
|
ncurses_doupdate();
|
|
}
|
|
$this->visible = false;
|
|
}
|
|
/**
|
|
* Show the window
|
|
*
|
|
* @param boolean $update Update the window upon completion
|
|
*/
|
|
public function show( $update = true ) {
|
|
foreach( $this->panels as $panel ) {
|
|
ncurses_show_panel( $panel );
|
|
}
|
|
foreach( $this->children as $child ) {
|
|
$child->show( false );
|
|
}
|
|
if( $update ) {
|
|
ncurses_update_panels();
|
|
ncurses_doupdate();
|
|
}
|
|
$this->visible = true;
|
|
}
|
|
/**
|
|
* Clear the window's contents
|
|
*
|
|
* @param boolean $update Update the window upon completion
|
|
*/
|
|
public function erase( $update = true ) {
|
|
ncurses_werase( $this->window );
|
|
if( $update ) {
|
|
ncurses_wrefresh( $this->window );
|
|
ncurses_doupdate();
|
|
}
|
|
}
|
|
/**
|
|
* Read a character from the keyboard and return it
|
|
*
|
|
* @param boolean $flush Flush the input buffer before requesting a key
|
|
* @return integer Keycode
|
|
*/
|
|
public function get_char( $flush = true ) {
|
|
if( $flush ) {
|
|
ncurses_flushinp();
|
|
}
|
|
return ncurses_getch();
|
|
//return ncurses_wgetch( $this->window );
|
|
}
|
|
|
|
public function shortcut($key, $object = null) {
|
|
$key = ord(strtolower(chr($key)));
|
|
if( $object !== null ) {
|
|
$this->shortcuts[$key] = $object;
|
|
} elseif( in_array($key, array_keys($this->shortcuts)) ) {
|
|
unset($this->shortcuts[$key]);
|
|
}
|
|
}
|
|
/* Event slots */
|
|
public function log($message) {
|
|
if( $this->app ) {
|
|
$this->app->log($this, "[{$this->id}] $message");
|
|
}
|
|
}
|
|
public function __emit($signal, $args) {
|
|
if( !in_array($signal, $this->emit_log_filter) ) {
|
|
foreach($args as $key => $value) {
|
|
$args[$key] = (string)$value;
|
|
}
|
|
$args = implode(',', $args);
|
|
$this->log("Emitted {$signal}({$args})");
|
|
}
|
|
}
|
|
public function focus() {
|
|
self::$active_window = $this->id;
|
|
$this->emit('focus');
|
|
}
|
|
public function blur() {
|
|
$this->emit('blur');
|
|
if( $this->parent instanceof NcWindow ) {
|
|
$this->parent->focus_next();
|
|
}
|
|
}
|
|
public function disable() {
|
|
$this->enabled = false;
|
|
$this->blur();
|
|
}
|
|
public function focus_next() {
|
|
$next = false;
|
|
$first = false;
|
|
foreach( $this->children as $child ) {
|
|
if( $child->visible && $child->enabled ) {
|
|
if( $first === false ) {
|
|
$first = $child;
|
|
}
|
|
$next = &$child;
|
|
if( self::$active_window && $next->id > self::$active_window ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( $next === false ) {
|
|
$next = &$this;
|
|
} elseif( $next->id == self::$active_window ) {
|
|
$next = &$first;
|
|
}
|
|
$next->focus();
|
|
}
|
|
public function on_key_press($key) {
|
|
if( self::$active_window === $this->id ) {
|
|
// This is the active window, handle input
|
|
switch($key) {
|
|
case 9:
|
|
// TAB
|
|
$this->blur();
|
|
break;
|
|
}
|
|
} else {
|
|
// Pass it along to the active child widget
|
|
self::get_window(self::$active_window)->on_key_press($key);
|
|
}
|
|
$this->emit('keypress', $key);
|
|
}
|
|
}
|
|
?>
|