#
# Created by Vadim Belman <voland@plab.ku.dk>
#
package Prima::Terminals;
use strict;
use warnings;
use Prima::Const;
use Prima::Classes;
use Prima::ScrollBar;
use Prima::Utils;
# Terminal specific constants
package
tm;
use constant WordBegin => 0x0000;
use constant WordEnd => 0x0001;
package Prima::AbstractTerminal;
use vars qw( @ISA);
@ISA = qw( Prima::Widget);
{
my %RNT = (
%{Prima::Widget-> notification_types()},
ExecCommand => nt::Action,
);
sub notification_types {
return \%RNT;
}
}
my $defaultAccelItems = [
['Line begin' => 'Home' => kb::Home, 'Home'],
['Line end' => 'End' => kb::End, 'End'],
['Cursor left' => 'Left' => kb::Left, 'cursorLeft'],
['Cursor right' => 'Right' => kb::Right, 'cursorRight'],
['Cursor up' => 'Up' => kb::Up, 'cursorUp'],
['Cursor down' => 'Down' => kb::Down, 'cursorDown'],
['Cursor left by word' => 'Ctrl-Left' => ( km::Ctrl | kb::Left), 'wordLeft'],
['Cursor right by word' => 'Ctrl-Right' => ( km::Ctrl | kb::Right), 'wordRight'],
['Ins' => 'Ins' => kb::Insert, 'toggleInsMode'],
['Delete char' => 'Del' => kb::Delete, 'deleteChar'],
['Delete left char' => 'Backspace' => kb::Backspace, 'deleteLeftChar'],
['Delete word right' => 'Alt-W' => '@W' => 'deleteWordRight'],
['Delete word left' => 'Ctrl-Backspace' => ( km::Ctrl | kb::Backspace), 'deleteWordLeft'],
['Delete word inplace' => 'Ctrl-Del' => ( km::Ctrl | kb::Delete), 'deleteWord'],
['Delete up to line begin' => 'Ctrl-U' => '^U' => 'deleteToLineBegin'],
['Delete up to line end' => 'Ctrl-K' => '^K' => 'deleteToLineEnd'],
['Execute command' => 'Enter' => kb::Enter, 'enterPressed'],
['Previous prompt' => 'Ctrl-Up' => ( km::Ctrl | kb::Up), 'previousPrompt'],
['Next prompt' => 'Ctrl-Down' => ( km::Ctrl | kb::Down), 'nextPrompt'],
];
#\subsection{profile\_default}
sub profile_default {
my $def = $_[ 0]-> SUPER::profile_default;
my %prf = (
borderWidth => 2,
hScroll => 0,
vScroll => 0,
pointerType => cr::Text,
lineWrap => 0,
prompt => '$ ',
topItem => undef,
bottomItem => undef,
vAlign => undef,
items => [], # Items array
syncPaint => 0,
accelItems => $defaultAccelItems,
autoAdjustPos => 1,
insertMode => 1, # Insert or replace mode for typing.
overrideMode => 1, # Insert a new item if inserting in the middle
# of items array or override old one.
reusePrompts => 1,
mousePromptSelection => 1,
wordRightMode => tm::WordEnd,
syncPromptChange => 0,
);
@$def{keys %prf} = values %prf;
return $def;
}
#\subsection{profile\_check\_in}
sub profile_check_in {
my ( $self, $prf, $default) = @_;
$self-> SUPER::profile_check_in( $prf, $default);
#my @scrlWidth = Prima::Application->get_default_scrollbar_metrics();
$prf-> { topItem} = undef if ! exists( $prf-> { topItem}) &&
exists( $prf-> { bottomItem});
$prf-> { borderWidth} = 0 unless $prf-> { borderWidth} >= 0;
}
#\subsection{calculate\_top\_bottom}
sub calculate_top_bottom {
my $self = $_[0];
if ( ( $self-> { vAlign} == ta::Bottom) ||
( !defined( $self-> topItem) && defined( $self-> { bottomItem}))) {
$self-> { bottomItem} = $#{ $self-> { items}} unless defined( $self-> { bottomItem});
$self-> { yOffset} = $self-> { itemRow}-> [ $self-> { bottomItem}] +
$self-> { itemHeights}-> [ $self-> { bottomItem}] - 1 - $self-> { textRows};
$self-> { topItem} = $self-> row2Item( $self-> { yOffset});
$self-> { vAlign} = ta::Bottom;
}
else {
$self-> { topItem} = 0 unless defined( $self-> { topItem});
$self-> { yOffset} = $self-> { itemRow}-> [ $self-> { topItem}];
$self-> { bottomItem} = $self-> row2Item( $self-> { yOffset} + $self-> { textRows} - 1);
}
}
#\subsection{calculate\_text\_preferences}
sub calculate_text_preferences {
my $self = $_[0];
$self-> { charHeight} = $self-> { termView}-> font-> { height};
$self-> { charWidth} = $self-> { termView}-> font-> { width};
my $textHeight = $self-> { termView}-> height;
$self-> { textRows} = Prima::Utils::ceil( $textHeight / $self-> { charHeight});
$self-> { nonIntRows} = ( $self-> { textRows} * $self-> { charHeight}) != $textHeight;
my $textWidth = $self-> { termView}-> width;
$self-> { textCols} = Prima::Utils::ceil( $textWidth / $self-> { charWidth} );
$self-> { nonIntCols} = ( $self-> { textCols} * $self-> { charWidth}) != $textWidth;
$self-> { vShift} = ( $self-> { textRows} - 1) * $self-> { charHeight};
}
#\subsection{row2Item}
sub row2Item {
my ( $self, $row) = @_;
return 0 if $row < 0;
my $items = $self-> { items};
return $#{ $items} if $row >= $self-> { totalRows};
my $itemRow = $self-> { itemRow};
my $itemHeights = $self-> { itemHeights};
my ( $min, $max) = ( 0, $#{ $items});
my $i;
for (;;) {
$i = int ( ( $min + $max) / 2);
if ( $itemRow-> [ $i] < $row) {
$min = $i;
}
else {
$max = $i;
}
last if ( ( $itemRow-> [ $i] <= $row) && ( ( $itemRow-> [ $i] + $itemHeights-> [ $i]) > $row));
if ( ( ( $max - $min)) == 1 && ( $i == int( ( $min + $max) / 2))) {
$i++;
last if ( $i == $#{ $items}) ||
( ( $itemRow-> [ $i] <= $row) && ( ( $itemRow-> [ $i] + $itemHeights-> [ $i]) > $row));
}
}
return $i;
}
#\subsection{trect2prect}
sub trect2prect {
my ( $self, $xLeft, $yTop, $xRight, $yBottom) = @_;
my ( $cW, $cH, $wH) = ( $self-> { charWidth},
$self-> { charHeight},
$self-> { termView}-> height,
);
my $textRows = $self-> { textRows};
#print "Translating $xLeft,$yTop $xRight,$yBottom";
return [
$cW * $xLeft,
( $textRows - $yBottom - 1) * $cH,
$cW * ( $xRight + 1),
( $textRows - $yTop) * $cH,
];
}
#\subsection{prect2trect}
sub prect2trect {
my ( $self, $xLeft, $yBottom, $xRight, $yTop) = @_;
my ( $cW, $cH, $tH) =
( $self-> { charWidth},
$self-> { charHeight},
$self-> { textRows} * $self-> { charHeight},
);
#print "wH: $wH, cH: $cH, yTop: $yTop";
return (
Prima::Utils::floor( $xLeft / $cW),
Prima::Utils::floor( ( $tH - 1 - $yTop) / $cH),
Prima::Utils::floor( $xRight / $cW),
Prima::Utils::floor( ( $tH - 1 - $yBottom) / $cH),
);
}
#\subsection{point2item}
sub pos2item {
my ( $self, $x, $y) = @_;
return -1 if ( $y < 0) || ( $y > ( $self-> height - 1));
my ( $row) = $self-> { yOffset} + $self-> { textRows} - int( $y / $self-> { charHeight}) - 1;
my ( $idx) = $self-> row2Item( $row);
my ( $ilen) = length( $self-> { items}-> [ $idx]);
my ( $cP) = int( $x / $self-> { charWidth}) + $self-> { xOffset} +
( $row - $self-> { itemRow}-> [ $idx]) * $self-> { textCols};
$cP = $ilen if $cP > $ilen;
return ( $idx, $cP);
}
#\subsection{set\_view\_item\_pos}
sub set_view_item_pos {
my ( $self, $i) = @_;
my $item = $self-> { items}-> [ $i];
my $itemRow = $self-> { itemRow}-> [ $i] - $self-> { yOffset};
my $itemBottomRow = $itemRow + $self-> { itemHeights}-> [ $i];
if ( ( $itemRow >= $self-> { textRows}) || ( $itemBottomRow < 0)) {
#print "Hiding ", $item->name;
$item-> hide;
return;
}
#print "Setting view pos for $i ($self->{ items}->[ $i])";
my ( $itemHeight) = $self-> { items}-> [ $i]-> height;
$itemHeight ||= 1;
my $itemBottomY = ( $self-> { textRows} - $itemBottomRow) * $self-> { charHeight} +
int( ( ( $self-> { itemHeights}-> [ $i] * $self-> { charHeight}) %
$itemHeight) / 2);
my $itemLeftX = ( $self-> { itemXShifts}-> [ $i] - $self-> { xOffset} * $self-> { charWidth});
#print "Setting view ", $self->{ items}->[ $i]->name, " to $itemLeftX,$itemBottomY";
$item-> origin( $itemLeftX, $itemBottomY);
$item-> show unless $item-> visible;
}
#\subsection{VScroll\_Change}
sub VScroll_Change {
my ( $self, $scr) = @_;
return if $self-> { ignoreScrollChanges};
$self-> set_offsets( undef, $scr-> value);
}
#\subsection{HScroll\_Change}
sub HScroll_Change {
my ( $self, $scr) = @_;
return if $self-> { ignoreScrollChanges};
$self-> set_offsets( $scr-> value, undef);
}
#\subsection{init}
sub init {
my $self = shift;
$self-> { IAmInitializing} = 1; # Could be changed to alive method!!!
for ( qw( charHeight charWidth cursorPos textRows textCols vScroll hScroll
totalRows rightBorder leftBorder borderWidth autoAdjustPos
prompt vAlign lineWrap xOffset yOffset xOffsetOld yOffsetOld
ignoreScrollChanges vShift maxWidth clientResize clientWidth
IAmResetting topItem insertMode overrideMode wordRightMode
syncPromptChange reusePrompts mousePromptSelection
)) {
$self-> { $_} = 0;
}
for ( qw( itemsChanged)) {
$self-> { $_} = 1;
}
for ( qw( viewItems itemHeights itemWidths itemRow prompts)) {
$self-> { $_} = [];
}
my %profile = $self-> SUPER::init( @_);
$self-> { items} = $profile{ items} if defined( $profile{ items});
@{ $self-> { prompts}} = map( 0, 0 .. $#{ $self-> { items}});
for ( qw( borderWidth hScroll vScroll lineWrap bottomItem topItem vAlign
prompt autoAdjustPos insertMode overrideMode wordRightMode
syncPromptChange reusePrompts mousePromptSelection)
) {
$self-> $_( $profile{ $_});
}
$self-> add_empty_command_line;
my ( $popup, $popupItems) = ( $self-> { popup}, $self-> { popupItems});
$self-> { popup} = undef;
$self-> { termView} =
$self-> insert( 'Widget',
name => 'TermView',
rect => [
$self-> { borderWidth},
$self-> { borderWidth} + ( $self-> { hScroll} ? $self-> { hScrollBar}-> height : 0),
$self-> width - $self-> { borderWidth} - ( $self-> { vScroll} ? $self-> { vScrollBar}-> width : 0),
$self-> height - $self-> { borderWidth},
],
syncPaint => 0,
growMode => gm::Client,
cursorVisible => 1,
selectable => 1,
ownerColor => 1,
ownerBackColor => 1,
);
$self-> { updateTimer} =
$self-> insert( 'Timer',
owner => $self,
name => 'UpdateTimer',
timeout => 500,
);
$self-> { updateTimer}-> stop;
$self-> { charsPrinted} = $self-> { linesPrinted} = 0; # puts uses this counters.
$self-> { __DYNAS__}-> { onExecCommand} = $profile{ onExecCommand};
$self-> { IAmInitializing} = 0; # This must be placed only and only just before reset call;
$self-> reset_view_items;
$self-> reset;
$self-> set_cursor_shape;
return %profile;
}
#\subsection{set}
sub set {
my ( $self, %profile) = @_;
$self-> { __DYNAS__}-> { onExecCommand} = $profile{ onExecCommand},
delete $profile{ onExecCommand} if defined( $profile{ onExecCommand});
$self-> SUPER::set( %profile);
}
#sub reset {
# use Benchmark;
# my $self = $_[0];
# my $t = timeit(1, sub {$self->_reset}); print "Reset: ", timestr($t);
#}
#\subsection{reset}
sub reset {
my ( $self) = @_;
my $i;
my ( $topView, $bottomView) = ( $self-> { topItem}, $self-> { bottomItem});
return if $self-> { IAmInitializing} || $self-> { IAmResetting};
$self-> { IAmResetting} = 1;
#print "resetting";
if ( $self-> { termView}-> font-> pitch != fp::Fixed) {
$self-> { termView}-> font-> pitch( fp::Fixed);
return;
}
$self-> { vAlign} = ( defined $self-> { topItem} ? ta::Top : ta::Bottom)
unless defined( $self-> { vAlign});
$self-> calculate_text_preferences;
if ( $self-> { clientResize}) {
my @tmvrect = (
$self-> { borderWidth},
$self-> { borderWidth} + ( $self-> { hScroll} ? $self-> { hScrollBar}-> height : 0),
$self-> width - $self-> { borderWidth} - ( $self-> { vScroll} ? $self-> { vScrollBar}-> width : 0),
$self-> height - $self-> { borderWidth},
);
#print "tmvrect : @tmvrect\n";
$self-> { termView}-> rect( @tmvrect);
if ( $self-> { hScroll}) {
$self-> HScroll-> origin( $self-> { borderWidth}, $self-> { borderWidth});
$self-> HScroll-> width( $self-> { termView}-> width + 1);
}
if ( $self-> { vScroll}) {
$self-> VScroll-> origin( $tmvrect[2], $tmvrect[1]);
$self-> VScroll-> height( $self-> { termView}-> height);
}
$self-> insert_bone;
$self-> { clientResize} = 0;
}
if ( ( ! defined( $self-> { oldWidth})) || ( $self-> { oldWidth} != $self-> { width})) {
$#{ $self-> { itemXRight}} = $#{ $self-> { itemXLeft}} = $#{ $self-> { itemXShifts}} =
$#{ $self-> { itemRow}} = $#{ $self-> { itemWidths}} = $#{ $self-> { itemHeights}} = $#{ $self-> { items}};
$self-> { totalRows} = 0;
$self-> { rightBorder} = 0;
$self-> { leftBorder} = 0;
$self-> { maxWidth} = 0;
my ( $width, $item);
my ( $items, $itemsChanged, $itemWidths, $itemXRight, $itemXLeft, $itemHeights, $itemXShifts, $itemRow) =
( $self-> { items}, $self-> { itemsChanged}, $self-> { itemWidths}, $self-> { itemXRight}, $self-> { itemXLeft},
$self-> { itemHeights}, $self-> { itemXShifts}, $self-> { itemRow});
my ( $cW, $cH, $lineWrap, $textCols, $focused) =
( $self-> { charWidth}, $self-> { charHeight}, $self-> { lineWrap}, $self-> { textCols}, $self-> { focused});
my ( $rightBorder, $leftBorder, $maxWidth, $totalRows) =
( $self-> { rightBorder}, $self-> { leftBorder}, $self-> { maxWidth}, $self-> { totalRows});
my ( $itemXShift, $iXRight, $iXLeft);
my $itemLastN = $#{ $self-> { items}};
if ( $lineWrap) {
for ( $i = 0; $i <= $itemLastN; $i++) {
$item = $items-> [ $i];
$itemXShift = $itemXShifts-> [ $i];
if ( ref $item) {
$itemWidths-> [ $i] = Prima::Utils::ceil( ( $item-> width + $itemXShift) / $cW);
$itemXRight-> [ $i] = Prima::Utils::floor( $item-> right / $cW);
$itemXLeft-> [ $i] = Prima::Utils::floor( $itemXShift / $cW);
$itemHeights-> [ $i] = Prima::Utils::ceil( $item-> height / $cH);
$itemHeights-> [ $i] ||= 1;
}
else {
$width = length( $item) + ( $i == $focused ? 1 : 0);
$itemWidths-> [ $i] = ( $width > $textCols) ? $textCols : $width;
$itemXRight-> [ $i] = $itemWidths-> [ $i] - 1;
$itemXLeft-> [ $i] = 0;
$itemHeights-> [ $i] = Prima::Utils::ceil( $width / $textCols);
}
$itemHeights-> [ $i] ||= 1; # to avoid zero heights
$iXRight = $itemXRight-> [ $i];
$iXLeft = $itemXLeft-> [ $i];
$rightBorder = $iXRight if $iXRight > $rightBorder;
$leftBorder = $iXLeft if $iXLeft < $leftBorder;
$width = $rightBorder - $leftBorder + 1;
$maxWidth = $width if $width > $maxWidth;
$itemRow-> [ $i] = $totalRows;
$totalRows += $itemHeights-> [ $i];
}
}
else {
for ( $i = 0; $i <= $itemLastN; $i++) {
$item = $items-> [ $i];
$itemXShift = $itemXShifts-> [ $i];
if ( ref $item) {
$itemWidths-> [ $i] = Prima::Utils::ceil( ( $item-> width + $itemXShift) / $cW);
$itemXRight-> [ $i] = Prima::Utils::floor( $item-> right / $cW);
$itemXLeft-> [ $i] = Prima::Utils::floor( $itemXShift / $cW);
$itemHeights-> [ $i] = Prima::Utils::ceil( $item-> height / $cH);
$itemHeights-> [ $i] ||= 1; # to avoid zero heights
}
else {
$width = length( $item) + ( $i == $focused ? 1 : 0);
$itemWidths-> [ $i] = $width;
$itemXRight-> [ $i] = $itemWidths-> [ $i] - 1;
$itemXLeft-> [ $i] = 0;
$itemHeights-> [ $i] = 1;
}
$iXRight = $itemXRight-> [ $i];
$iXLeft = $itemXLeft-> [ $i];
$rightBorder = $iXRight if $iXRight > $rightBorder;
$leftBorder = $iXLeft if $iXLeft < $leftBorder;
$width = $rightBorder - $leftBorder + 1;
$maxWidth = $width if $width > $maxWidth;
$itemRow-> [ $i] = $totalRows;
$totalRows += $itemHeights-> [ $i];
}
}
$self-> { rightBorder} = $rightBorder;
$self-> { leftBorder} = $leftBorder;
$self-> { maxWidth} = $maxWidth;
$self-> { totalRows} = $totalRows;
$self-> calculate_top_bottom;
$self-> { oldWidth} = $self-> { width};
}
$self-> { xOffset} = $self-> { rightBorder} - $self-> { textCols} + 1
if ( $self-> { xOffset} + $self-> { textCols}) > $self-> { rightBorder};
$self-> { xOffset} = $self-> { leftBorder} if $self-> { xOffset} < $self-> { leftBorder};
$self-> { yOffset} = ( $self-> { nonIntRows} ? -1 : 0) if $self-> { yOffset} == 0;
for ( $i = 0; $i <= $#{ $self-> { viewItems}}; $i++ ) {
$self-> set_view_item_pos( $self-> { viewItems}-> [ $i]);
}
$self-> reset_scrolls;
$self-> reset_cursor;
$self-> { termView}-> focus unless $self-> { termView}-> focused;
$self-> { IAmResetting} = 0;
#$self->repaint;
}
#\subsection{reset\_scrolls}
sub reset_scrolls {
my $self = shift;
return if $self-> { IAmInitializing} || $self-> { ignoreScrollChanges};
$self-> { ignoreScrollChanges} = 1;
my $maxWidth = $self-> { maxWidth};
my $hpartial = $self-> { textCols} < $maxWidth ? $self-> { textCols} : $maxWidth;
my $hmax = $self-> { rightBorder} - $self-> { textCols} + 1;
my $hmin = $self-> { leftBorder};
$hmax = $hmin if $hmax < $hmin;
my $hval = $self-> { xOffset} > $hmax ? $hmax : ( $self-> { xOffset} < $hmin ? $hmin : $self-> { xOffset}) ;
my $vval = $self-> { yOffset};
my $vmax = $self-> { totalRows} - $self-> { textRows};
$vmax = 0 if $vmax < 0;
my $vmin = $self-> { nonIntRows} ?
( $self-> { totalRows} < $self-> { textRows} ?
( $self-> { vAlign} == ta::Bottom ?
0 :
$vval) :
-1) :
0;
#print "vval : $vval";
$self-> { hScrollBar}-> set(
min => $hmin,
max => $hmax,
step => 1,
pageStep => Prima::Utils::ceil( $self-> { textCols} / 3),
whole => $maxWidth,
partial => $hpartial,
value => $hval,
) if $self-> { hScroll};
#print "vScrollBar: min: $vmin, max: $vmax, whole: ", $self->{ totalRows} - $vmin, ", value: $vval";
$self-> { vScrollBar}-> set(
min => $vmin,
max => $vmax,
step => 1,
pageStep => $self-> { textRows},
whole => $self-> { totalRows} - $vmin,
partial => $self-> { textRows} < $self-> { totalRows} ?
$self-> { textRows} : $self-> { totalRows},
value => $vval,
) if $self-> { vScroll};
$self-> { ignoreScrollChanges} = 0;
}
#\subsection{buildinItem}
sub buildinItem {
my ( $self, $item) = @_;
if ( ! defined( $item-> { __DYNAS__}-> { onSize}) || $item-> { __DYNAS__}-> { onSize} != \&itemOnSize) {
$item-> { termView}-> { onSize} = $item-> { __DYNAS__}-> { onSize};
$item-> set( onSize => \&itemOnSize);
}
if ( ! defined( $item-> { __DYNAS__}-> { onMove}) || $item-> { __DYNAS__}-> { onMove} != \&itemOnMove) {
$item-> { termView}-> { onMove} = $item-> { __DYNAS__}-> { onMove};
$item-> set( onMove => \&itemOnMove);
}
$item-> notify( 'TerminalInsert', $self);
}
#\subsection{insert\_view}
sub insert_view {
my ( $self, $item, $i) = @_;
if ( ref( $item) eq 'ARRAY') {
my ( $view);
$view = $self-> { termView}-> insert( @{ $item});
( print "Item creation failed for $item->[ 0]\n", return) if ! $view;
$item = $view;
}
elsif ( $item-> isa('Prima::Widget')) {
$item-> owner( $self-> TermView);
}
$self-> buildinItem( $item);
if ( ! $item-> isa( 'Prima::Widget')) {
croak( "Item #$i not a Widget descendant") unless $item-> isa( 'Prima::Widget');
}
if ( defined( $i)) {
$self-> { items}-> [ $i] = $item;
$self-> { itemXShifts}-> [ $i] = $item-> left;
}
return $item;
}
#\subsection{reset\_view\_items}
sub reset_view_items {
my ( $self) = @_;
my $i;
$#{ $self-> { itemXShifts}} = $#{ $self-> { items}};
for ( $i=0; $i <= $#{ $self-> { items}}; $i++) {
my $item = $self-> { items}-> [ $i];
if ( ref $item) {
$item = $self-> insert_view( $item, $i);
push @{ $self-> { viewItems}}, $i;
}
else {
$self-> { itemXShifts}-> [ $i] = 0;
}
}
}
#\subsection{set\_cursor\_shape}
sub set_cursor_shape {
my ( $self) = @_;
return unless ( ! $self-> { IAmInitializing});
$self-> { termView}-> cursorSize( $self-> { charWidth}, $self-> { insertMode} ? 2 : $self-> { charHeight});
}
#\subsection{update\_cursor}
sub reset_cursor {
my ( $self) = @_;
my ( $cursorCol, $cursorRow) = $self-> get_cursor_coordinates;
if ( ( $cursorRow >= $self-> { textRows}) ||
( $cursorCol >= $self-> { textCols}) ||
( $cursorRow < 0) || ( $cursorCol < 0)) {
$self-> { termView}-> cursorVisible(0);
}
else {
#print "setting cursor to ",$self->{ charWidth} * $cursorCol, "x", $self->{ charHeight} * ( $self->{ textRows} - $cursorRow - 1);
$self-> { termView}-> cursorVisible(1);
$self-> { termView}-> set_cursor_pos( $self-> { charWidth} * $cursorCol, $self->{ charHeight} * ( $self->{textRows} - $cursorRow - 1));
}
}
#\subsection{adjust\_terminal\_window}
sub adjust_terminal_window {
my ( $self, $forced) = @_;
return unless ( ! ( $self-> { inCommandExecution} && ! $forced)) || $self-> { autoAdjustPos};
my ( $cPos, $focused, $newXOffset, $newYOffset, $textRows, $textCols) =
( $self-> { cursorPos}, $self-> { focused}, $self-> { xOffset}, $self-> { yOffset},
$self-> { textRows}, $self-> { textCols});
my ( $cursorCol, $cursorRow) = $self-> get_cursor_coordinates;
if ( $cursorCol < 0) {
$newXOffset += $cursorCol;
}
elsif ( $cursorCol >= $textCols) {
$newXOffset += $cursorCol - $textCols + 1;
}
if ( $cursorRow < 0) {
$newYOffset += $cursorRow;
}
elsif ( $cursorRow >= $textRows) {
$newYOffset += $cursorRow - $textRows + 1;
}
#print "Setting x,y to ", $newX || 'undef', ",", $newY || 'undef';
if ( ( $newXOffset != $self-> { xOffset}) || ( $newYOffset != $self-> { yOffset})) {
$self-> set_offsets( $newXOffset, $newYOffset);
$self-> reset_scrolls;
}
}
#\subsection{update\_cursor}
sub update_cursor {
my ( $self, $forced) = @_;
my ( $cursorCol, $cursorRow) = $self-> get_cursor_coordinates;
if ( ( ( $cursorRow >= $self-> { textRows}) ||
( $cursorCol >= $self-> { textCols}) ||
( $cursorRow < 0) ||
( $cursorCol < 0)
)
) {
$self-> adjust_terminal_window( $forced);
}
else {
$self-> reset_cursor;
}
}
#\subsection{set\_offsets}
sub set_offsets {
my ( $self, $newX, $newY) = @_;
return unless ! $self-> { IAmInitializing};
my ( $xOffset, $yOffset, $textRows, $textCols) =
( $self-> { xOffset}, $self-> { yOffset}, $self-> { textRows}, $self-> { textCols});
my ( $hRectTop, $hRectBottom); # Top and Bottom of the rectange which should be
# redrawn for a horizontal shift.
my ( $hs, $vs) = ( 0, 0);
my ( @rect2Scroll) = ( 0, 0, $self-> { termView}-> width, $self-> { termView}-> height);
my ( $topView, $bottomView) = ( $self-> { topItem}, $self-> { bottomItem});
my $i;
my $yOffsetLimit = $self-> { totalRows} < $self-> { textRows} ? 0 : ( $self-> { totalRows} - $self-> { textRows});
#print "Setting offsets to $newX,$newY (1)";
$newX = defined( $newX) ?
( $newX < $self-> { leftBorder} ?
$self-> { leftBorder} :
( ( $newX + $self-> { textCols} - 1) > $self-> { rightBorder} ?
$self-> { rightBorder} - $self-> { textCols} :
$newX)) :
$self-> { xOffset};
$newY = defined( $newY) ? ( $newY > $yOffsetLimit ? $yOffsetLimit : $newY) : $self-> { yOffset};
#print "Setting offsets to $newX,$newY (2)";
$self-> { yOffsetOld} = $yOffset;
$self-> { xOffsetOld} = $xOffset;
if ( defined( $newY) && ( $newY != $yOffset)) {
$vs = ( $newY - $yOffset) * $self-> { charHeight};
$self-> { yOffset} = $newY;
$topView = $self-> { topItem} if ( $self-> { topItem} = $self-> row2Item( $newY)) < $topView;
$bottomView = $self-> { bottomItem}
if ( $self-> { bottomItem} = $self-> row2Item( $newY + $self-> { textRows} - 1)) > $bottomView;
}
if ( defined( $newX) && ( $newX != $xOffset)) {
$hs = ( $xOffset - $newX) * $self-> { charWidth};
$self-> { xOffset} = $newX;
}
$self-> { ignoreViewsMove} = 1;
$self-> { termView}-> scroll( $hs, $vs, withChildren => 1);
$self-> { termView}-> update_view if ! $self-> syncPaint;
$self-> { ignoreViewsMove} = 0;
if ( $self-> { yOffset} != $self-> { yOffsetOld}) {
my $items = $self-> { items};
my $visibleTop = $self-> { yOffset};
my $visibleBottom = $visibleTop + $self-> { textRows} - 1;
my $hiddenTop = $self-> { yOffsetOld} < $visibleTop ? $self-> { yOffsetOld} : ( $visibleBottom + 1);
my $hiddenBottom = $hiddenTop + $self-> { textRows} - 1;
$hiddenBottom = $visibleTop - 1 if $hiddenBottom < $visibleBottom;
my $visItemTop = $self-> row2Item( $visibleTop);
my $visItemBottom = $self-> row2Item( $visibleBottom);
my $hidItemTop = $self-> row2Item( $hiddenTop);
my $hidItemBottom = $self-> row2Item( $hiddenBottom);
if ( $hidItemTop == $visItemBottom) {
$hidItemTop++;
}
elsif ( $hidItemBottom == $visItemTop) {
$hidItemBottom--;
}
#print "Hiding from $hidItemTop to $hidItemBottom ($hiddenTop - $hiddenBottom)";
#print "Show from $visItemTop to $visItemBottom ($visibleTop - $visibleBottom)";
for ( $i=$hidItemTop; $i<=$hidItemBottom; $i++) {
$self-> set_view_item_pos( $i) if ref $items-> [ $i];
}
for ( $i=$visItemTop; $i<=$visItemBottom; $i++) {
$self-> set_view_item_pos( $i) if ref $items-> [ $i];
}
}
$self-> reset_cursor;
if ( $self-> { vScroll}) {
$self-> { vScrollBar}-> value( $newY);
}
if ( $self-> { hScroll}) {
$self-> { hScrollBar}-> value( $newX);
}
}
#\subsection{drawItem}
sub drawItem {
my ( $self, $term, $item) = @_;
my $itemRow = $self-> { itemRow}-> [ $item] - $self-> { yOffset};
my $vShift = $self-> { vShift};
my $itemY = $vShift - $itemRow * $self-> { charHeight};
my ( $xLeft, $xRight) = @{ $self-> { updateCols}};
my $xStrOffset = $self-> { xOffset} + $xLeft;
my $xOutOffset = $xStrOffset < 0 ? ( $xLeft - $self-> { xOffset}) : ( $xLeft);
$xOutOffset = 0 if $xOutOffset < 0;
$xStrOffset = 0 if $xStrOffset < 0;
if ( ! ref $self-> { items}-> [ $item]) {
if ( $self-> { lineWrap}) {
my $i;
for ( $i = 0; $i < $self-> { itemHeights}-> [ $item] && $itemY >= 0; $i++) {
my $outLine = substr( $self-> { items}-> [ $item], $i*$self-> { textCols}, $self-> { textCols});
#print "updating cols $xLeft,$xRight for $item, row $itemRow, y:$itemY, xOffset:$self->{ xOffset}";
$term-> text_out( substr( $outLine, $xStrOffset, $xRight - $xLeft + 1),
$xOutOffset * $self-> { charWidth}, $itemY)
if $xStrOffset <= length( $outLine);
$itemY -= $self-> { charHeight};
$itemRow++;
}
}
else {
#print "updating cols $xLeft,$xRight for $item, row $itemRow, y:$itemY, xOffset:$xOffset at ",$xLeft * $self->{ charWidth} if $xOffset <= length( $self->{ items}->[ $item]);
$term-> text_out( substr( $self-> { items}-> [ $item], $xStrOffset, $xRight - $xLeft + 1),
$xOutOffset * $self-> { charWidth}, $itemY)
if $xStrOffset <= length( $self-> { items}-> [ $item]);
}
}
}
#\subsection{TermView\_Size}
sub TermView_Size {
#print "TermView_Size->reset";
$_[0]-> reset;
}
#\subsection{TermView\_Popup}
sub TermView_Popup {
my ( $self, $term, $mouseDriven, $x, $y) = @_;
my $popup = $self-> popup;
if ( $popup && $popup-> autoPopup) {
$self-> { mousePos} = $mouseDriven ? [ $x, $y] : undef;
$popup-> popup( $x, $y);
$term-> clear_event;
}
}
#\subsection{TermView\_FontChanged}
sub TermView_FontChanged {
my ( $self, $term) = @_;
#$self-> SUPER::on_fontchanged( @_);
if ( $term-> font-> pitch == fp::Fixed) {
#print "TermView_FontChanged->reset";
$self-> reset;
$self-> set_cursor_shape;
}
else {
$term-> font-> pitch( fp::Fixed);
}
}
#\subsection{TermView\_Paint}
sub TermView_Paint {
my ( $self, $term) = @_;
my $clr = $term-> color;
my @clipRect = $term-> clipRect;
my @textClip = $self-> prect2trect( @clipRect);
#print "Clipping ", ($term->size)[0], "x", ($term->size)[1]," by @clipRect, @textClip, $self->{ textCols} x $self->{ textRows}, cH: ",$self->{ charHeight};
my $topItem = $self-> row2Item( $textClip[1] + $self-> { yOffset});
my $bottomItem = $self-> row2Item( $textClip[3] + $self-> { yOffset});
$term-> color( $term-> backColor);
$term-> bar( @clipRect);
$term-> color( $clr);
my $i;
#print "from $topItem to $bottomItem (rows: $self->{ textRows})";
$self-> { updateCols} = [ $textClip[0], $textClip[2]];
for ( $i=$topItem; $i<=$bottomItem; $i++) {
$self-> drawItem( $term, $i);
}
#$term->color( cl::Green);
#$term->rectangle( @clipRect);
#$term->color( $clr);
$self-> { updateCols} = $self-> { updateLines} = undef;
@textClip = @clipRect = undef;
}
#\subsection{TermView\_KeyDown}
sub TermView_KeyDown {
my ( $self, $term, $code, $key, $mod) = @_;
if ( (( $code & 0xFF) >= ord(' ')) &&
( ( $mod & ( km::Alt | km::Ctrl)) == 0) &&
( ( $key == kb::NoKey) || ( $key == kb::Space))
) {
$self-> put_str( chr( $code & 0xFF));
$term-> clear_event();
}
}
#\subsection{TermView\_MouseClick}
sub TermView_MouseClick {
my ( $self, $term, $btn, $shft, $x, $y) = @_;
if ( ( $btn == mb::Left) && $self-> { mousePromptSelection}) {
my ( $i, $cursorPos) = $self-> pos2item( $x, $y);
if ( $self-> { prompts}-> [ $i] > 0) {
$self-> { focused} = $i;
$self-> { cursorPos} = $cursorPos < $self-> { prompts}-> [ $i] ?
$self-> { prompts}-> [ $i] :
$cursorPos;
$self-> update_cursor;
}
}
}
#\subsection{UpdateTimer\_Tick}
sub UpdateTimer_Tick {
my ( $self, $timer) = @_;
$self-> adjust_terminal_window( 1);
}
#\subsection{on\_paint}
sub on_paint {
my ( $self, $canvas) = @_;
$self-> draw_border( $canvas, $self-> backColor, $self-> size );
}
#\subsection{on\_postmessage}
sub on_postmessage {
my ( $self, $cmd, $data) = @_;
if ( $cmd eq 'viewChanged') {
$self-> viewChanged( $data);
}
}
#\subsection{enterPressed}
sub enterPressed {
my ( $self) = @_;
my ( $commandString) = substr( $self-> { items}-> [ $self-> { focused}], $self-> { prompts}-> [ $self-> { focused}]);
my ( $cursorCol, $cursorRow) = $self-> get_cursor_coordinates;
if ( $cursorRow == ( $self-> { textRows} - 1)) {
$self-> { totalRows}++;
$self-> set_offsets( undef, $self-> { yOffset} + 2);
$self-> { totalRows}--;
}
$self-> { ignoreScrollChanges} = 1;
$self-> { termView}-> hide_cursor;
$self-> { updateTimer}-> start;
$self-> { charsPrinted} = $self-> { linesPrinted} = 0;
$self-> { focused}++;
$self-> { cursorPos} = 0;
$self-> { commandExecutionStarted} = $self-> { inCommandExecution} = 1;
$self-> notify( 'ExecCommand', $commandString);
$self-> { commandExecutionStarted} = $self-> { inCommandExecution} = 0;
$self-> { updateTimer}-> stop;
$self-> { termView}-> show_cursor;
$self-> { ignoreScrollChanges} = 0;
$self-> set_offsets( 0, undef);
$self-> add_empty_command_line;
}
#\subsection{add\_empty\_command\_line}
sub add_empty_command_line {
my $self = $_[0];
my ( $idx);
if ( $self-> { IAmInitializing}) {
push @{$self-> { items}}, $self-> { prompt};
$idx = $#{$self-> { items}};
$self-> { itemHeights}-> [ $idx] = 1;
$self-> { cursorPos} = $self-> { itemWidths}-> [ $idx] =
$self-> { prompts}-> [ $idx] = length( $self-> { prompt});
$self-> { focused} = $idx;
}
else {
my $focused = $self-> { focused};
while ( $focused <= $#{ $self-> { items}} && $self-> { prompts}-> [ $focused] == 0) {
$focused++;
}
$self-> { focused} = $focused;
if ( ( $focused > $#{ $self-> { items}}) || ( ! $self-> { reusePrompts})) {
$self-> insert_item( '', $self-> { prompt});
}
else {
$self-> prompt( $self-> { prompt}) if $self-> { syncPromptChange};
}
$self-> { cursorPos} = $self-> { prompts}-> [ $self-> { focused}];
$self-> update_cursor;
}
}
#\subsection{insert\_bone}
sub insert_bone
{
my $self = $_[0];
my $bw = $self-> {borderWidth};
$self-> {bone}-> destroy if defined $self-> {bone};
return unless $self-> { hScroll} && $self-> { vScroll};
$self-> {bone} = $self-> insert( q(Widget),
name => q(Bone),
origin => [ $bw + $self-> { hScrollBar}-> width, $bw],
size => [ $self-> {vScrollBar}-> width, $self-> {hScrollBar}-> height],
ownerBackColor => 1,
growMode => gm::GrowLoX,
pointerType=> cr::Arrow,
onPaint => sub {
my ( $self, $owner, $w, $h) = ($_[0], $_[0]-> owner, $_[0]-> size);
$self-> color( $self-> backColor);
$self-> bar( 0, 1, $w - 2, $h - 1);
$self-> color( $owner-> light3DColor);
$self-> line( 0, 0, $w - 1, 0);
$self-> line( $w - 1, 0, $w - 1, $h - 1);
},
);
}
#\subsection{set\_h\_scroll}
sub set_h_scroll
{
my ( $self, $hs) = @_;
return if $hs == $self-> {hScroll};
my $bw = $self-> {borderWidth};
if ( $self-> {hScroll} = $hs) {
$self-> {hScrollBar} = $self-> insert( q(ScrollBar),
name => q(HScroll),
vertical => 0,
origin => [ $bw, $bw],
growMode => gm::GrowHiX,
width => $self-> width - 2 * $bw - ( $self-> {vScroll} ? $self-> {vScrollBar}-> width : 0),
pointerType => cr::Arrow,
firstClick => 1,
);
} else {
$self-> {hScrollBar}-> destroy;
}
$self-> { clientResize} = 1;
$self-> reset;
$self-> repaint;
}
#\subsection{set\_v\_scroll}
sub set_v_scroll
{
my ( $self, $vs) = @_;
return if $vs == $self-> {vScroll};
my $bw = $self-> {borderWidth};
my @size = $self-> size;
if ( $self-> {vScroll} = $vs) {
$self-> {vScrollBar} = $self-> insert( q(ScrollBar),
name => q(VScroll),
vertical => 1,
left => $size[0] - $bw - $Prima::ScrollBar::stdMetrics[0],
top => $size[1] - $bw,
bottom => $bw + ( $self-> {hScroll} ? $self-> {hScrollBar}-> height : 0),
growMode => gm::GrowLoX | gm::GrowHiY,
pointerType => cr::Arrow,
firstClick => 1,
);
} else {
$self-> { vScrollBar}-> destroy;
}
$self-> { clientResize} = 1;
$self-> reset;
$self-> repaint;
}
#\subsection{set\_border\_width}
sub set_border_width {
my ( $self, $bw) = @_;
return unless defined( $bw) && ( $bw != $self-> { borderWidth});
$self-> { borderWidth} = $bw;
$self-> { clientResize} = 1 unless $self-> { IAmInitializing};
#print "set_border_width->reset";
$self-> reset;
$self-> repaint;
}
#\subsection{set\_top\_item}
sub set_top_item {
my ( $self, $ti) = @_;
if ( defined( $ti)) {
$self-> { vAlign} = ta::Top;
if ( $self-> { IAmInitializing}) {
$self-> { topItem} = $ti;
}
else {
$self-> set_offsets( undef, $self-> { itemRow}-> [ $ti]) unless $ti > $#{ $self-> { itemRow}};
}
}
}
#\subsection{set\_bottom\_item}
sub set_bottom_item {
my ( $self, $bi) = @_;
if ( defined( $bi)) {
$self-> { vAlign} = ta::Bottom;
if ( $self-> { IAmInitializing}) {
$self-> { bottomItem} = $bi;
}
elsif ( $bi <= $#{ $self-> { itemRow}}) {
my $brow = $self-> { itemRow}-> [ $bi] + $self-> { itemHeights}-> [ $bi] - $self-> { textRows};
$self-> set_offsets( undef, $brow > 0 ? $brow : 0);
}
}
}
#\subsection{set\_alignment}
sub set_alignment {
my ( $self, $al) = @_;
return unless defined( $al) && ( $al != $self-> { vAlign});
$self-> { vAlign} = $al;
if ( $al == ta::Top) {
my $newY = $self-> { itemRow}-> [ $self-> { topItem}];
$newY ||= -1 if $self-> { nonIntRows};
#print "Setting yOffset into ", $newY;
$self-> set_offsets( undef, $newY);
}
elsif ( $al == ta::Bottom) {
#print "Setting yOffset into ", $self->{ itemRow}->[ $self->{ bottomItem}] + $self->{ itemHeights}->[ $self->{ bottomItem}] - $self->{ textRows};
$self-> set_offsets( undef,
$self-> { itemRow}-> [ $self-> { bottomItem}] +
$self-> { itemHeights}-> [ $self-> { bottomItem}] -
$self-> { textRows}
);
}
#$self->reset;
}
#\subsection{set\_line\_wrapping}
sub set_line_wrapping {
my ( $self, $lw) = @_;
return unless defined( $lw) && ( $lw != $self-> { lineWrap});
$self-> { lineWrap} = $lw;
$self-> { xOffsetOld} = $self-> { xOffset} = 0;
#print "set_line_wrapping->reset";
$self-> reset;
$self-> repaint;
}
#\subsection{set\_prompt}
sub set_prompt {
my ( $self, $prompt) = @_;
return unless defined( $prompt);
my ( $focused) = $self-> { focused};
if ( defined( $focused)) {
my ( $item) = $self-> { items}-> [ $focused];
if ( $self-> { syncPromptChange} && ! ref $item) {
my ( $ilen, $plen) =
( length( $item || ''), $self-> { prompts}-> [ $focused] || 0);
if ( $plen > 0) {
$self-> { cursorPos} += length( $prompt) - $plen;
substr( $self-> { items}-> [ $focused], 0, $plen) = $prompt;
$self-> { prompts}-> [ $focused] = length( $prompt);
$self-> item_changed( $focused);
if ( length( $prompt) != $plen) {
$ilen = length( $self-> { items}-> [ $focused]) if length( $self-> { items}-> [ $focused]) > $ilen;
}
else {
$ilen = $plen;
}
$self-> invalidate_line( $focused, 0, $ilen);
}
}
}
$self-> { prompt} = $prompt;
}
#\subsection{set\_sync\_prompt\_change}
sub set_sync_prompt_change {
my ( $self, $spc) = @_;
return unless defined $spc;
$self-> { syncPromptChange} = $spc != 0;
}
#\subsection{set\_mouse\_prompt\_selection}
sub set_mouse_prompt_selection {
my ( $self, $mps) = @_;
return unless defined $mps;
$self-> { mousePromptSelection} = $mps != 0;
}
#\subsection{set\_auto\_adjust\_pos}
sub set_auto_adjust_pos {
my ( $self, $aap) = @_;
return unless defined( $aap);
$self-> { autoAdjustPos} = $aap;
}
#\subsection{set\_insert\_mode}
sub set_insert_mode {
my ( $self, $insert_mode) = @_;
return unless defined( $insert_mode);
$self-> { insertMode} = ( $insert_mode != 0);
$self-> set_cursor_shape;
}
#\subsection{set\_override\_mode}
sub set_override_mode {
my ( $self, $override_mode) = @_;
return unless defined( $override_mode);
$self-> { overrideMode} = ( $override_mode != 0);
}
#\subsection{set\_reuse\_prompts}
sub set_reuse_prompts {
my ( $self, $reuse_prompts) = @_;
return unless defined( $reuse_prompts);
$self-> { reusePrompts} = ( $reuse_prompts != 0);
}
#\subsection{set\_word\_right\_mode}
sub set_word_right_mode {
my ( $self, $wrm) = @_;
croak("Wrong wordRightMode passed")
unless defined( $wrm) && ( ( $wrm == tm::WordEnd) || ( $wrm == tm::WordBegin));
$self-> { wordRightMode} = $wrm;
}
#\subsection{get\_integral\_size}
sub get_integral_size {
my ( $self) = @_;
return [
$self-> { textCols} * $self-> { charWidth} +
( $self-> vScroll ? $self-> { vScrollBar}-> width : 0) + $self-> { borderWidth} * 2,
$self-> { textRows} * $self-> { charHeight} +
( $self-> hScroll ? $self-> { hScrollBar}-> height : 0) + $self-> { borderWidth} * 2
];
}
#\subsection{get\_cursor\_coordinates}
sub get_cursor_coordinates {
my ( $self) = @_;
my ( $cursorPos, $focused) = ( $self-> { cursorPos}, $self-> { focused});
my ( $itemRow, $cursorX, $cursorY);
if ( ! defined( $self-> { itemRow}-> [ $focused])) {
$focused--;
$itemRow = $self-> { itemRow}-> [ $focused] + $self-> { itemHeights}-> [ $focused];
}
else {
$itemRow = $self-> { itemRow}-> [ $focused];
}
if ( $self-> { lineWrap}) {
$cursorX = $cursorPos % $self-> { textCols};
$cursorY = $self-> { itemRow}-> [ $focused] + int( $cursorPos / $self-> { textCols});
}
else {
$cursorX = $cursorPos;
$cursorY = $self-> { itemRow}-> [ $focused];
}
return ( $cursorX - $self-> { xOffset}, $cursorY - $self-> { yOffset});
}
#\subsection{item\_changed}
sub item_changed {
my ( $self, $i) = @_;
my ( $textCols, $textRows) = ( $self-> { textCols}, $self-> { textRows});
my ( $items, $itemsChanged, $itemWidths, $itemXRight, $itemXLeft, $itemHeights,
$itemXShifts, $itemRow, $viewItems, $cursorPos) =
( $self-> { items}, $self-> { itemsChanged}, $self-> { itemWidths}, $self-> { itemXRight},
$self-> { itemXLeft}, $self-> { itemHeights}, $self-> { itemXShifts}, $self-> { itemRow},
$self-> { viewItems}, $self-> { cursorPos});
my $item = $items-> [ $i];
my ( $cW, $cH, $lineWrap, $focused, $xOffset, $yOffset) =
( $self-> { charWidth}, $self-> { charHeight}, $self-> { lineWrap}, $self-> { focused},
$self-> { xOffset}, $self-> { yOffset});
my ( $rightBorder, $leftBorder, $maxWidth, $totalRows) =
( $self-> { rightBorder}, $self-> { leftBorder}, $self-> { maxWidth}, $self-> { totalRows});
my $itemLastN = $#{ $items};
my $itemXShift = $itemXShifts-> [ $i]; # Remember! This is one of the changable parameters for a inserted views!
my ( $newHeight, $newWidth, $newXRight, $newXLeft, $newXOffset, $newYOffset) =
( $itemHeights-> [ $i], $itemWidths-> [ $i], $itemXRight-> [ $i], $itemXLeft-> [ $i],
$self-> { xOffset}, $self-> { yOffset});
if ( ref $item) {
$newWidth = Prima::Utils::ceil( ( $item-> width + $itemXShift) / $cW);
$newXRight = Prima::Utils::floor( ( $item-> right + $xOffset * $cW) / $cW);
$newXLeft = Prima::Utils::floor( $itemXShift / $cW);
$newHeight = Prima::Utils::ceil( $item-> height / $cH);
$newHeight ||= 1;
}
else {
my $width = length( $item) + ( $i == $focused ? 1 : 0);
if ( $lineWrap) {
$newHeight = Prima::Utils::ceil( $width / $textCols);
$newWidth = ( $width > $textCols) ? $textCols : $width;
}
else {
$newHeight = 1;
$newWidth = $width;
}
$newHeight ||= 1;
$newXRight = $newWidth - 1;
$newXLeft = 0;
}
my $vShiftVal = ( $newHeight - $itemHeights-> [ $i]);
my ( $resetScrolls, $needRectScroll) = ( $vShiftVal != 0, 0);
my ( $scrollRect, $vS);
#print "vShiftVal: $vShiftVal; newHeight: $newHeight";
if ( $newXRight > $rightBorder) {
$self-> { rightBorder} = $newXRight;
$resetScrolls = 1;
}
if ( $newXLeft < $leftBorder) {
$self-> { leftBorder} = $newXLeft;
$resetScrolls = 1;
}
if ( ( $self-> { rightBorder} - $self-> { leftBorder} + 1) > $maxWidth) {
$self-> { maxWidth} = ( $self-> { rightBorder} - $self-> { leftBorder} + 1);
$resetScrolls = 1;
}
my $itemBottom = $itemRow-> [ $i] + $itemHeights-> [ $i] - 1;
$itemBottom = 0 if $itemBottom < 0; # To fix -1 itemBottom problem for newly inserted
# items;
if ( $vShiftVal != 0) {
my $j;
#tex Lets change all \textbf{itemRow} values starting from the next item up to
#tex the end of items list.
#tex Actually, it's a good idea to move this loop outside of the condition
#tex and make it calculate \textbf{maxWidth / leftBorder / rightBorder} as well.
for ( $j = ( $i + 1); $j <= $itemLastN; $j++) {
$itemRow-> [ $j] += $vShiftVal;
}
$self-> { totalRows} += $vShiftVal;
# Now we must shift the client window region below the changed
# item if necessary.
if ( $itemBottom < $yOffset) {
#print "Changing yOffset";
$self-> { yOffset} = $newYOffset += $vShiftVal;
}
elsif ( $itemRow-> [ $i] < ( $yOffset + $textRows) && $itemBottom >= $yOffset &&
( $i <= $itemLastN) && $newHeight != $itemHeights-> [ $i]) {
#print "Calculating scrollRect";
$scrollRect = $self-> trect2prect( 0,
( $vShiftVal > 0 ?
$itemBottom + 1 :
$itemRow-> [ $i] + $itemHeights-> [ $i]) - $yOffset,
$textCols - 1, $textRows - 1);
$needRectScroll = 1;
}
}
my ( $cursorX, $cursorY) = $self-> get_cursor_coordinates;
#print "Cursor now at $cursorX,$cursorY";
if ( $self-> { autoAdjustPos} && ! $self-> { inCommandExecution}) {
if ( $i == $self-> { focused}) {
#print "Adjusting for changed focused item";
if ( ( $cursorX >= $textCols) || ( $cursorX < 0)) {
$newXOffset += $cursorX < 0 ? $cursorX : $cursorX - $textCols + 1;
}
if ( ( ( $cursorY >= $textRows) || ( $cursorY < 0))) {
$newYOffset += $cursorY < 0 ? $cursorY : $cursorY - $textRows + 1;
}
}
elsif ( ( $itemRow-> [ $i] + $newHeight - 1) >= ( $yOffset + $textRows) &&
$itemRow-> [ $i] < ( $yOffset + $textRows)) {
#print "Adjusting for visible not focused item";
$newYOffset = $itemRow-> [ $i] + $newHeight - $textRows;
}
}
$itemHeights-> [ $i] = $newHeight;
$itemWidths-> [ $i] = $newWidth;
$itemXRight-> [ $i] = $newXRight;
$itemXLeft-> [ $i] = $newXLeft;
if ( $needRectScroll) {
#$vS = ( $newYOffset - $self->{ yOffset} - $vShiftVal) * $cH;
$vS = ( - $vShiftVal) * $cH;
my ( @r) = $self-> { termView}-> rect;
#print "Scrolling rect by $vShiftVal => $vS (@{ $scrollRect}), (@r)";
$self-> { termView}-> scroll( 0, $vS, confineRect => $scrollRect);
my ( $j);
for ( $j = 0; $j <= $#{ $viewItems}; $j++) {
my ( $vi) = $viewItems-> [ $j];
$self-> set_view_item_pos( $vi) if $vi > $i;
}
}
$self-> set_view_item_pos( $i) if ref $item;
#print $itemRow->[ $i] - $newYOffset, "-", $itemRow->[ $i] - $newYOffset + $itemHeights->[ $i] - 1;
#print "1.Now xOffset is $self->{ xOffset}; $newXOffset; leftBorder: $self->{ leftBorder}; rightBorder = $self->{ rightBorder}; maxWidth: $self->{ maxWidth}";
$self-> reset_scrolls if $resetScrolls;
#print "VScroll after: ", $self->{ vScrollBar}->value;
if ( ( $newXOffset != $self-> { xOffset}) || ( $newYOffset != $self-> { yOffset})) {
#print "Offsets: $newXOffset, $newYOffset";
$self-> set_offsets( $newXOffset, $newYOffset);
}
else {
# $self->{ termView}->update_view;
$self-> reset_cursor;
}
#print "2.Now xOffset is $self->{ xOffset}";
}
#\subsection{invalidate\_line}
sub invalidate_line {
my ( $self, $i, $sPos, $len) = @_;
my @clipRect;
if ( $self-> { lineWrap}) {
my ( $textCols, $textRows) =
( $self-> { textCols}, $self-> { textRows});
my $sRow = int( $sPos / $textCols) + $self-> { itemRow}-> [ $i] - $self-> { yOffset};
my $sCol = $sPos % $textCols - $self-> { xOffset};
my $sWidth = ( $sCol + $len) > $textCols ? $textCols - $sCol : $len;
$len -= $sWidth;
$self-> { termView}-> invalidate_rect( @{ $self-> trect2prect( $sCol, $sRow, $sCol + $sWidth - 1, $sRow)});
my $wholeLines = int( $len / $textCols);
$sRow++;
if ( $wholeLines > 0) {
$len -= $textCols * $wholeLines;
$self-> { termView}-> invalidate_rect( @{ $self-> trect2prect( 0, $sRow, $textCols - 1, $sRow + $wholeLines - 1)});
$sRow += $wholeLines;
}
$self-> { termView}-> invalidate_rect( @{ $self-> trect2prect( 0, $sRow, $len - 1, $sRow)}) unless $len == 0;
}
else {
my $leftX = $sPos - $self-> { xOffset};
my $rightX = $leftX + $len - 1;
my $Y = $self-> { itemRow}-> [ $i] - $self-> { yOffset};
$self-> { termView}-> invalidate_rect( @{ $self-> trect2prect( $leftX, $Y, $rightX, $Y)});
}
}
#stex
#\subsection{set\_line}
#
#Modes supported by \texttt{\bf set\_line}:
#
#\begin{tabular}{|l|l|}
#\hline\texttt{\it\bf add} & Method adds a string to the end of line. \\
#\hline\texttt{\it\bf insert} & Method inserts a string into the line. \\
#\hline\texttt{\it\bf set} & Method changes entire line. \\
#\hline\texttt{\it\bf change} & Method changes a part of line.\\\hline
#\end{tabular}\\
#etex
sub set_line {
my ( $self, $i, $str, $p) = @_;
croak("Wrong parameters passed to the method set_line")
unless defined( $str) && defined( $i) && ( ( $i >= 0) && ( $i <= ( $#{ $self-> { items}} + 1)));
if ( defined( $self-> { items}-> [ $i])) {
return if ref $self-> { items}-> [ $i];
}
else {
$self-> { items}-> [ $i] = '';
}
my ( $itemChanged, $cpos, $clen) = ( 1, 0, 0);
my ( $mode) = defined( $p-> { mode}) ? $p-> { mode} : 'set';
if ( $mode eq 'set') {
my ( $oldlen) = length( $self-> { items}-> [ $i]);
$self-> { items}-> [ $i] = $str;
$clen = length( $str) > $oldlen ? length( $str) : $oldlen;
}
elsif ( $mode eq 'add') {
$cpos = length( $self-> { items}-> [ $i]);
$self-> { items}-> [ $i] .= $str;
$clen = length( $str);
}
elsif ( $mode eq 'insert') {
$cpos = defined( $p-> { spos}) ? $p-> { spos} : 0;
substr( $self-> { items}-> [ $i], $cpos, 0) = $str;
$clen = length( $self-> { items}-> [ $i]) - $cpos;
$itemChanged = length( $str) > 0;
}
elsif ( $mode eq 'change') {
my( $oldlen, $slen) =
( length( $self-> { items}-> [ $i]),
defined( $p-> { slen}) ? $p-> { slen} : length( $str));
$cpos = defined( $p-> { spos}) ? $p-> { spos} : 0;
substr( $self-> { items}-> [ $i], $cpos, $slen) = $str;
my ( $newlen) = length( $self-> { items}-> [ $i]);
$itemChanged = $oldlen != $newlen;
$clen = $itemChanged ? ( $oldlen > $newlen ? $oldlen : $newlen) - $cpos : $slen;
}
else {
croak("Unknown mode passed for method set_line");
}
if ( $itemChanged) {
#print "Item $i changed";
$self-> item_changed( $i);
}
else {
$self-> reset_cursor;
}
#print "Invalidating from $cpos to $clen";
$self-> invalidate_line( $i, $cpos, $clen);
}
#\subsection{viewChanged}
sub viewChanged {
my ( $self, $idx) = @_;
#print "Item #$idx changed its size";
if ( $idx != -1) {
$self-> item_changed( $idx);
}
}
#\subsection{itemOnSize}
sub itemOnSize {
my ( $itemSelf) = @_;
# This counter should prevent from recursive notification processing.
$itemSelf-> { termView}-> { onSize_processing}++;
$itemSelf-> { termView}-> { onSize}-> ( @_) if defined( $itemSelf-> { termView}-> { onSize});
if ( $itemSelf-> { termView}-> { onSize_processing} < 2) {
my $self = $itemSelf-> owner-> owner;
my $i;
my $idx = -1;
for ( $i = 0; $i <= $#{ $self-> { viewItems}}; $i++) {
#print $self->{ items}->[ $self->{ viewItems}->[ $i]], " eq ", $itemSelf;
if ( $self-> { items}-> [ $self-> { viewItems}-> [ $i]] eq $itemSelf) {
$idx = $self-> { viewItems}-> [ $i];
last;
}
}
$self-> post_message( 'viewChanged', $idx) unless $idx == -1;
}
$itemSelf-> { termView}-> { onSize_processing}--;
}
#\subsection{itemOnSize}
sub itemOnMove {
my ( $itemSelf) = @_;
#print "itemOnMove";
# This counter should prevent from recursive notification processing.
$itemSelf-> { termView}-> { onMove_processing}++;
$itemSelf-> { termView}-> { onMove}-> ( @_) if defined( $itemSelf-> { termView}-> { onMove});
if ( $itemSelf-> { termView}-> { onMove_processing} < 2) {
my $self = $itemSelf-> owner-> owner;
my $i;
my $idx = -1;
for ( $i = 0; $i <= $#{ $self-> { viewItems}}; $i++) {
#print $self->{ items}->[ $self->{ viewItems}->[ $i]], " eq ", $itemSelf;
if ( $self-> { items}-> [ $self-> { viewItems}-> [ $i]] eq $itemSelf) {
$idx = $self-> { viewItems}-> [ $i];
last;
}
}
if ( ( $idx != -1) && ( ! $self-> { ignoreViewsMove}) &&
( $self-> { itemXShifts}-> [ $idx] - $self-> { xOffset} * $self-> { charWidth}) != $self-> { items}-> [ $idx]-> left) {
#print "Should be:", $self->{ itemXShifts}->[ $idx] - $self->{ xOffset} * $self->{ charWidth}, ", is: ", $self->{ items}->[ $idx]->left;
#print "Processing position change for $idx";
$self-> { itemXShifts}-> [ $idx] = $self-> { items}-> [ $idx]-> left + $self-> { xOffset} * $self-> { charWidth};
$self-> post_message( 'viewChanged', $idx);
}
}
$itemSelf-> { termView}-> { onMove_processing}--;
}
#\subsection{delete\_view\_item}
sub remove_view_item {
my ( $self, $i) = @_;
return if ! ref $self-> { items}-> [ $i];
my ( $item) = $self-> { items}-> [ $i];
croak( "Not a reference to a Widget descendant") unless $item-> isa( "Prima::Widget");
$item-> notify( 'TerminalRemove', $self);
$item-> destroy, return 1
if $item-> notify( 'Close');
return 0;
}
#\subsection{insert\_item}
sub insert_item {
my ( $self, $item, $prompt, $i) = @_;
my ( $immutableFocused) = 0;
my ( $items, $itemWidths, $itemXRight, $itemXLeft, $itemHeights,
$itemXShifts, $itemRow) =
( $self-> { items}, $self-> { itemWidths}, $self-> { itemXRight}, $self-> { itemXLeft},
$self-> { itemHeights}, $self-> { itemXShifts}, $self-> { itemRow});
if ( ! defined( $i)) {
$immutableFocused = 1;
$i = $self-> { focused};
}
if ( $i < ( - $#{ $items} - 1)) {
croak("Incorrect index passed to insert_item");
}
my ( $idx) = $i < 0 ? scalar( @{ $items}) - $i : $i;
return if ( $idx <= $#{ $items}) && ( ! defined( $items-> [ $idx]));
if ( ref $item) {
$item = $self-> insert_view( $item);
}
splice( @{ $items}, $idx, 0, ( ( defined( $item) && ref $item) ?
$item :
( defined( $prompt) ?
( $prompt . $item) :
undef)));
$item ||= '';
$prompt ||= '';
splice( @{ $itemWidths}, $idx, 0, 0);
splice( @{ $itemXRight}, $idx, 0, 0);
splice( @{ $itemXLeft}, $idx, 0, 0);
splice( @{ $itemHeights}, $idx, 0, 0);
splice( @{ $itemXShifts}, $idx, 0, ref( $item) ? $item-> left : 0);
splice( @{ $itemRow}, $idx, 0, ( $idx == 0 ? 0 : $itemRow-> [ $idx - 1] + $itemHeights-> [ $idx - 1]));
splice( @{ $self-> { prompts}}, $idx, 0, length( $prompt));
my $j;
my $viewInsPos = 0;
for ( $j = 0; $j <= $#{ $self-> { viewItems}}; $j++) {
if ( $self-> { viewItems}-> [ $j] >= $idx) {
#print "Shifting down view: ", $self->{ viewItems}->[ $j];
$self-> { viewItems}-> [ $j]++;
#print "Shifted down view: ", $self->{ viewItems}->[ $j];
}
else {
$viewInsPos = $j;
}
}
$self-> { focused}++ if ( ! $immutableFocused) && ( $self-> { focused} >= $idx);
$self-> item_changed( $idx);
if ( ref $item) {
splice( @{ $self-> { viewItems}}, $viewInsPos, 0, $idx);
$self-> set_view_item_pos( $idx);
$self-> repaint;
}
else {
$self-> invalidate_line( $idx, 0, ( $self-> { textCols} < length( $items-> [ $idx])) ?
length( $items-> [ $idx]) :
$self-> { textCols})
}
}
#\subsection{delete_item}
sub delete_item {
my ( $self, $i) = @_;
my ( $immutableFocused) = 0;
my ( $items, $itemWidths, $itemXRight, $itemXLeft, $itemHeights,
$itemXShifts, $itemRow) =
( $self-> { items}, $self-> { itemWidths}, $self-> { itemXRight}, $self-> { itemXLeft},
$self-> { itemHeights}, $self-> { itemXShifts}, $self-> { itemRow});
if ( ! defined( $i)) {
$immutableFocused = 1;
$i = $self-> { focused}
}
if ( $i < ( - $#{ $items} - 1)) {
croak("Incorrect index passed to insert_item");
}
my ( $idx) = $i < 0 ? scalar( @{ $items}) - $i : $i;
if ( ( $idx == $self-> { focused}) && ( $self-> { prompts}-> [ $idx] > 0)) {
return 0;
}
my ( $item) = $items-> [ $idx];
if ( defined( $item) && ( ref $item)) {
return 0 unless $self-> remove_view_item( $idx);
}
if ( ! ref $item) {
$self-> invalidate_line( $idx, 0, length( $item));
}
$self-> { totalRows} -= $self-> { itemHeights}-> [ $idx];
if ( $idx < $#{ $self-> { items}}) {
# Tricky way to simulate changing of the next item for the item_changed
$self-> { itemRow}-> [ $idx + 1] = $self-> { itemRow}-> [ $idx];
$self-> { itemHeights}-> [ $idx + 1] += $self-> { itemHeights}-> [ $idx];
}
splice( @{ $items}, $idx, 1);
splice( @{ $itemWidths}, $idx, 1);
splice( @{ $itemXRight}, $idx, 1);
splice( @{ $itemXLeft}, $idx, 1);
splice( @{ $itemHeights}, $idx, 1);
splice( @{ $itemXShifts}, $idx, 1);
splice( @{ $itemRow}, $idx, 1);
splice( @{ $self-> { prompts}}, $idx, 1);
my ( $j, $viewDelPos);
for ( $j = 0; $j <= $#{ $self-> { viewItems}}; $j++) {
if ( $self-> { viewItems}-> [ $j] > $idx) {
$self-> { viewItems}-> [ $j]--;
}
elsif ( $self-> { viewItems}-> [ $j] == $idx) {
$viewDelPos = $j;
}
}
if ( defined $viewDelPos) {
splice( @{ $self-> { viewItems}}, $viewDelPos, 1);
}
$self-> { focused}-- unless $immutableFocused || $self-> { focused} < $idx;
my ( $ilen) = 0;
if ( $idx <= $#{ $self-> { items}}) {
$self-> item_changed( $idx);
if ( ! ref $self-> { items}-> [ $idx]) {
$self-> invalidate_line( $idx, 0, int( length( $self-> { items}-> [ $idx]) /
$self-> { textCols} + 1
) * $self-> { textCols});
}
}
$self-> update_view;
return 1;
}
#\subsection{put\_prepare\_item}
sub put_prepare_item {
my ( $self, $newLine, $init_item) = @_;
$init_item = '' unless defined( $init_item);
my ( $focused, $items) = ( $self-> { focused}, $self-> { items});
if ( $focused > $#{ $items}) {
$focused = $self-> { focused} = $#{ $items} + 1;
$self-> insert_item( $init_item, '');
$self-> { cursorPos} = 0;
}
else {
my ( $item) = $items-> [ $focused];
if ( defined( $item)) {
if ( ( $self-> { commandExecutionStarted}) && ( ! $self-> { overrideMode})) {
$self-> { commandExecutionStarted} = 0;
$self-> insert_item( $init_item, '');
}
elsif ( ref $item) {
if ( ! $self-> remove_view_item( $focused)) {
$self-> insert_item( $init_item, '');
}
else {
$items-> [ $focused] = ref $init_item ?
$self-> insert_view( $init_item) :
$init_item;
}
}
elsif ( ( $newLine && ( ! $self-> { overrideMode})) || ( $self-> { prompts}-> [ $focused] > 0)) {
$self-> insert_item( $init_item, '');
}
}
else {
$items-> [ $focused] = ref $init_item ?
$self-> insert_view( $init_item, $focused) :
$init_item;
}
}
$self-> { focused} = $focused;
}
#\subsection{putv}
sub putv {
my ( $self, $view) = @_;
return unless defined $view;
my ( $focused) = $self-> { focused};
if ( ref $view) {
$self-> put_prepare_item( 0, $view);
}
else {
$self-> put_prepare_item( 0, [ @_[ 1.. $#_]]);
}
$self-> item_changed( $focused);
}
#\subsection{print}
sub print {
my ( $self, @strs) = @_;
foreach ( @strs) {
$self-> puts( $_);
}
}
#\subsection{puts}
sub puts {
my ( $self, $str) = @_;
my $focused = $self-> { focused};
$self-> put_prepare_item;
my ( @strs) = split /((?:\n)|(?:\r)|(?:\t)|(?:\010)|(?:\007))/, $str;
foreach ( @strs) {
my ( $cP) = ( $self-> { cursorPos});
if ( $_ eq "\n") {
$self-> { linesPrinted}++;
$self-> { charsPrinted} = 0;
$self-> { focused} = ++$focused;
$self-> { cursorPos} = 0;
$self-> put_prepare_item( 1);
}
elsif ( $_ eq "\r") {
$self-> { cursorPos} = 0;
$self-> update_cursor( 1);
}
elsif ( $_ eq "\b") {
$self-> { cursorPos}-- if $self-> { cursorPos} > 0;
$self-> update_cursor( 1);
}
elsif ( $_ eq "\007") {
$self-> adjust_terminal_window( 1);
Prima::Utils::sound( 500, 100);
}
else {
if ( $_ eq "\t") {
$_ = ' ' x ( int( $cP / 8 + 1) * 8 - $cP);
}
my $slen = length( $_);
$self-> { charsPrinted} += $slen;
$self-> set_line( $focused, $_, { mode => 'change', spos => $cP, slen => $slen});
$self-> { cursorPos} += $slen;
if ( $self-> { lineWrap}) {
$self-> { linesPrinted} += int( $self-> { charsPrinted} / $self-> { textCols});
$self-> { charsPrinted} %= $self-> { textCols};
}
elsif ( $self-> { charsPrinted} > ( $self-> { textCols} * 3 / 4)) {
$self-> { charsPrinted} = 0;
$self-> adjust_terminal_window( 1);
}
$self-> { termView}-> update_view;
}
if ( $self-> { linesPrinted} > ( $self-> { textCols} / 2)) {
$self-> { linesPrinted} = 0;
$self-> adjust_terminal_window( 1);
}
}
}
#\subsection{put\_str}
sub put_str {
my ( $self, $str) = @_;
my $focused = $self-> { focused};
my ( $cP, $slen) = ( $self-> { cursorPos}, length( $str));
croak("Can't print into a empty item") unless defined( $self-> { items}-> [ $focused]);
croak("Can't print into a Widget item") if ref $self-> { items}-> [ $focused];
$self-> { cursorPos} += $slen;
$self-> set_line( $focused, $str, { mode => ( $self-> { insertMode} ? 'insert' : 'change'), spos => $cP, slen => $slen});
}
#\subsection{set\_cursor\_pos}
sub set_cursor {
my ( $self, $cP) = @_;
my ( $items, $prompts, $focused) =
( $self-> { items}, $self-> { prompts}, $self-> { focused});
return if ( ref $items-> [ $focused]) || ( $cP < $prompts-> [ $focused]) || ( $cP > length( $items-> [ $focused]));
$self-> { cursorPos} = $cP;
$self-> update_cursor;
}
#\subsection{hScroll}
sub hScroll {
$#_ ? $_[0]-> set_h_scroll( $_[1]) : return $_[0]-> { hScroll};
}
#\subsection{vScroll}
sub vScroll {
$#_ ? $_[0]-> set_v_scroll( $_[1]) : return $_[0]-> { vScroll};
}
#\subsection{borderWidth}
sub borderWidth {
$#_ ? $_[0]-> set_border_width( $_[1]) : return $_[0]-> { borderWidth};
}
#\subsection{topItem}
sub topItem {
$#_ ? $_[0]-> set_top_item( $_[1]) : return $_[0]-> { topItem};
}
#\subsection{bottomItem}
sub bottomItem {
$#_ ? $_[0]-> set_bottom_item( $_[1]) : return $_[0]-> { bottomItem};
}
#\subsection{vAlign}
sub vAlign {
$#_ ? $_[0]-> set_alignment( $_[1]) : return $_[0]-> { vAlign};
}
#\subsection{lineWrap}
sub lineWrap {
$#_ ? $_[0]-> set_line_wrapping( $_[1]) : return $_[0]-> { lineWrap};
}
#\subsection{prompt}
sub prompt {
$#_ ? $_[ 0]-> set_prompt( $_[ 1]) : return $_[ 0]-> { prompt};
}
#\subsection{syncPromptChange}
sub syncPromptChange {
$#_ ? $_[ 0]-> set_sync_prompt_change( $_[ 1]) : return $_[ 0]-> { syncPromptChange};
}
#\subsection{mousePromptSelection}
sub mousePromptSelection {
$#_ ? $_[ 0]-> set_mouse_prompt_selection( $_[ 1]) : return $_[ 0]-> { mousePromptSelection};
}
#\subsection{autoAdjustPos}
sub autoAdjustPos {
$#_ ? $_[0]-> set_auto_adjust_pos( $_[1]) : return $_[0]-> { autoAdjustPos};
}
#\subsection{cursor}
sub cursor {
$#_ ? $_[ 0]-> set_cursor( $_[ 1]) : return $_[ 0]-> { cursorPos};
}
#\subsection{insertMode}
sub insertMode {
$#_ ? $_[ 0]-> set_insert_mode( $_[ 1]) : return $_[ 0]-> { insertMode};
}
#\subsection{overrideMode}
sub overrideMode {
$#_ ? $_[ 0]-> set_override_mode( $_[ 1]) : return $_[ 0]-> { overrideMode};
}
#\subsection{reusePrompts}
sub reusePrompts {
$#_ ? $_[ 0]-> set_reuse_prompts( $_[ 1]) : return $_[ 0]-> { reusePrompts};
}
#\subsection{wordRightMode}
sub wordRightMode {
$#_ ? $_[ 0]-> set_word_right_mode( $_[ 1]) : return $_[ 0]-> { wordRightMode};
}
# Primitives
#\subsection{leftmostPos}
sub leftmostPos {
my ( $self) = @_;
return ( ref $self-> { items}-> [ $self-> { focused}]) || ( $self-> { cursorPos} <= $self-> { prompts}-> [ $self-> { focused}]);
}
#\subsection{rightmostPos}
sub rightmostPos {
my ( $self) = @_;
return ( ref $self-> { items}-> [ $self-> { focused}]) || ( $self-> { cursorPos} >= length( $self-> { items}-> [ $self-> { focused}]));
}
#\subsection{Home}
sub Home {
my ( $self) = @_;
$self-> { cursorPos} = $self-> { prompts}-> [ $self-> { focused}];
my ( $cursorCol, $cursorRow) = $self-> get_cursor_coordinates;
if ( $cursorCol < 0) {
$self-> set_offsets( 0, undef);
}
$self-> update_cursor;
}
#\subsection{End}
sub End {
my ( $self) = @_;
$self-> { cursorPos} = length( $self-> { items}-> [ $self-> { focused}]);
$self-> update_cursor;
}
#\subsection{cursorLeft}
sub cursorLeft {
my ( $self) = @_;
return if $self-> leftmostPos;
$self-> { cursorPos}--;
$self-> update_cursor;
}
#\subsection{cursorRight}
sub cursorRight {
my ( $self) = @_;
return if $self-> rightmostPos;
$self-> { cursorPos}++;
$self-> update_cursor;
}
#\subsection{wordLeft}
sub wordLeft {
my ( $self) = @_;
return if $self-> leftmostPos;
my ( $plen, $cP) =
( $self-> { prompts}-> [ $self-> { focused}],
$self-> { cursorPos},
);
my ( $str) = substr( $self-> { items}-> [ $self-> { focused}], $plen, $cP - $plen);
$str =~ s/\w+\W*$//;
$self-> { cursorPos} = $plen + length( $str);
$self-> update_cursor;
}
#\subsection{wordRight}
sub wordRight {
my ( $self) = @_;
return if $self-> rightmostPos;
my ( $plen, $cP) =
( $self-> { prompts}-> [ $self-> { focused}],
$self-> { cursorPos},
);
my ( $str) = substr( $self-> { items}-> [ $self-> { focused}], $cP);
if ( $self-> { 'wordRightMode'} == tm::WordEnd) {
$str =~ s/^(\W*\w+).*$/$1/;
}
else {
$str =~ s/^(\w*\W+).*$/$1/;
}
$self-> { cursorPos} += length( $str);
$self-> update_cursor;
}
#\subsection{toggleInsMode}
sub toggleInsMode {
my ( $self) = @_;
$self-> insertMode( !$self-> insertMode);
}
#\subsection{deleteChar}
sub deleteChar {
my ( $self) = @_;
return if $self-> rightmostPos;
$self-> set_line( $self-> { focused}, '', { mode => 'change', spos => $self-> { cursorPos}, slen => 1});
}
#\subsection{deleteLeftChar}
sub deleteLeftChar {
my ( $self) = @_;
return if $self-> leftmostPos;
$self-> { cursorPos}--;
$self-> set_line( $self-> { focused}, '', { mode => 'change', spos => $self-> { cursorPos}, slen => 1});
}
#\subsection{deleteWordRight}
sub deleteWordRight {
my ( $self) = @_;
return if $self-> rightmostPos;
my ( $str) = substr( $self-> { items}-> [ $self-> { focused}], $self-> { cursorPos});
$str =~ s/^((\w+)|(\W+)).*$/$1/;
$self-> set_line( $self-> { focused}, '', { mode => 'change', spos => $self-> { cursorPos}, slen => length( $str)});
}
#\subsection{deleteWordLeft}
sub deleteWordLeft {
my ( $self) = @_;
return if $self-> leftmostPos;
my ( $plen, $cP) =
( $self-> { prompts}-> [ $self-> { focused}],$self-> { cursorPos});
my ( $str) = substr( $self-> { items}-> [ $self-> { focused}], $plen, $cP - $plen);
$str =~ s/^(?:.*?)((\w+)|(\W+))$/$1/;
$cP = ( $self-> { cursorPos} -= length( $str));
$self-> set_line( $self-> { focused}, '', { mode => 'change', spos => $cP, slen => length( $str)});
$self-> update_cursor;
}
#\subsection{deleteWord}
sub deleteWord {
my ( $self) = @_;
my ( $str, $cP) = ( $self-> { items}-> [ $self-> { focused}], $self-> { cursorPos});
return if ( ref $str) || ( substr( $str, $cP, 1) !~ /\w/);
substr( $str, $cP) =~ s/^\w+\W*//;
substr( $str, 0, $cP) =~ s/(\w+)$//;
$self-> { cursorPos} -= length( $1);
$self-> set_line( $self-> { focused}, $str);
$self-> update_cursor;
}
#\subsection{deleteToLineBegin}
sub deleteToLineBegin {
my ( $self) = @_;
return if $self-> leftmostPos;
my ( $plen, $cP) =
( $self-> { prompts}-> [ $self-> { focused}], $self-> { cursorPos});
$self-> { cursorPos} = $plen;
$self-> set_line( $self-> { focused}, '', { mode => 'change', spos => $plen, slen => ( $cP - $plen)});
$self-> update_cursor;
}
#\subsection{deleteToLineEnd}
sub deleteToLineEnd {
my ( $self) = @_;
return if $self-> rightmostPos;
my ( $cP, $slen) =
( $self-> { cursorPos}, length( $self-> { items}-> [ $self-> { focused}]));
$self-> set_line( $self-> { focused}, '', { mode => 'change', spos => $cP, slen => $slen});
}
#\subsection{cursorUp}
sub cursorUp {
my ( $self) = @_;
return if ( ! $self-> { 'lineWrap'}) || ( ref $self-> { items}-> [ $self-> { focused}]) ||
( ( $self-> { cursorPos} - $self-> { prompts}-> [ $self-> { focused}]) < $self-> { textCols});
$self-> { cursorPos} -= $self-> { textCols};
$self-> update_cursor;
}
#\subsection{cursorDown}
sub cursorDown {
my ( $self) = @_;
return if ( ! $self-> { 'lineWrap'}) || ( ref $self-> { items}-> [ $self-> { focused}]) ||
( ( $self-> { cursorPos} + $self-> { textCols}) > length( $self-> { items}-> [ $self-> { focused}]));
$self-> { cursorPos} += $self-> { textCols};
$self-> update_cursor;
}
#\subsection{previousPrompt}
sub previousPrompt {
my ( $self) = @_;
my ( $focused, $items) =
( $self-> { focused} - 1, $self-> { items});
while ( ( $focused >= 0) && ( $self-> { prompts}-> [ $focused]) == 0) {
$focused--;
}
if ( $focused >= 0) {
$self-> { focused} = $focused;
$self-> { cursorPos} = $self-> { prompts}-> [ $focused];
$self-> update_cursor;
}
}
#\subsection{nextPrompt}
sub nextPrompt {
my ( $self) = @_;
my ( $focused, $items) =
( $self-> { focused} + 1, $self-> { items});
while ( ( $focused <= $#{ $self-> { items}}) && ( $self-> { prompts}-> [ $focused]) == 0) {
$focused++;
}
if ( $focused <= $#{ $self-> { items}}) {
$self-> { focused} = $focused;
$self-> { cursorPos} = $self-> { prompts}-> [ $focused];
$self-> update_cursor;
}
}
1;