* @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); } } ?>