# # Created by Vadim Belman # 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;