Files
harbour-core/harbour/contrib/xhb/xhbtedit.prg
Viktor Szakats bcede0dabb 2012-06-26 20:37 UTC+0200 Viktor Szakats (harbour syenar.net)
* contrib/xhb/tfile.prg
  * contrib/xhb/ttable.prg
  * contrib/xhb/xhberr.prg
  * contrib/xhb/xhbtedit.prg
    ! unicode compatibility patches. build tested only. review me 
      and continue.

  * examples/hbdoc/genhtml.prg
  * examples/hbdoc/hbdoc.prg
    ! fixed regression from 2012-06-11 15:31 UTC+0200
    + changed to generate HTML5 compliant HTML output
2012-06-26 18:39:04 +00:00

3163 lines
89 KiB
Plaintext

/*
* $Id$
*/
/*
* Harbour Project source code:
* Editor Class (base for Memoedit(), debugger, etc.)
*
* Copyright 2000 Maurilio Longo <maurilio.longo@libero.it>
* www - http://www.harbour-project.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
*
* As a special exception, the Harbour Project gives permission for
* additional uses of the text contained in its release of Harbour.
*
* The exception is that, if you link the Harbour libraries with other
* files to produce an executable, this does not by itself cause the
* resulting executable to be covered by the GNU General Public License.
* Your use of that executable is in no way restricted on account of
* linking the Harbour library code into it.
*
* This exception does not however invalidate any other reasons why
* the executable file might be covered by the GNU General Public License.
*
* This exception applies only to the code released by the Harbour
* Project under the name Harbour. If you copy code from other
* Harbour Project or Free Software Foundation releases into a copy of
* Harbour, as the General Public License permits, the exception does
* not apply to the code that you add in this way. To avoid misleading
* anyone as to the status of such modified files, you must delete
* this exception notice from them.
*
* If you write modifications of your own for Harbour, it is your choice
* whether to permit this exception to apply to your modifications.
* If you do not wish that, delete this exception notice.
*
*/
/*
* Pritpal Bedi <pritpal@vouchcac.com>
* 28Feb2004
*
* Suppor for Clipper's MemoEdit( ..., nTextBufferRow, nTextBufferCol, nWindowRow, nWindowCol )
* Rearrangement of code in logical sections.
* Reformatting of code to be more readable.
* Navigation code broken into small methods for easy mainainability on lines with TBrowse()
*
*/
/*
*
* Teditor Fix: teditorx.prg -- V 3.0beta 2004/04/17
* Copyright 2004 Giancarlo Niccolai <antispam /at/ niccolai /dot/ ws>
*
* Minimal revision for proper working (expecially with word warping).
* Fixed many funtions
* Added GotoCol() and GotoPos() to goto a logical column or position;
* they translate this movement in a adequate ::SetPos call.
*
* Modifications are based upon the following source file:
*/
/*
* Teditor Fix: teditorx.prg -- V 2.0 2003/11/17
* Copyright 2003 Lance Owens <servant@gnosis.org>
*
* This Revised Version has a completely rewritten edit method key commands, with dynamic line & paragraqph reformatting.
* Includes a fix for the bugs in Teditor key processing that previously caused array errors
*
* Note: --If using the paste function to enter text, increase size of keyboard buffer to 2048 or 4096!
* Otherwise buffer will overrun -- it takes some processor time to do all the dynamic reformatting
* --SetCursor() is used to change cursor between insert and overwrite. Modify if desired....
* This will need to be cleared to return to original cursor within Memoedit()!!
* --K_LEFT is set to exit Memoedit() in read-only mode, in addition to the standard exit keys ESC.
* --CHR(141)+CHR(10) "soft CR" inserted by Clipper memoedit() is automatically removed when encountered in text
* --Color persistence problems in previous version corrected by taking setcolor() at Method New file call.
*
* Modifications are based upon the following source file:
*/
#include "common.ch"
#include "hbclass.ch"
#include "error.ch"
#include "fileio.ch"
#include "inkey.ch"
#include "setcurs.ch"
#include "color.ch"
#include "hbgtinfo.ch"
//-------------------------------------------------------------------//
CREATE CLASS XHBEditor
DATA cFile INIT "" // name of file being edited
DATA aText INIT {} // array with lines of text being edited
DATA nTop // boundaries of editor window, without box around
DATA nLeft
DATA nBottom
DATA nRight
DATA nFirstCol INIT 1 // FirstCol/Row of current text visible inside editor window
DATA nFirstRow INIT 1
DATA nRow INIT 1 // Cursor position inside aText (nRow) and inside current line of text (nCol)
DATA nCol INIT 1
DATA nPhysRow INIT 0 // Hardware cursor position, I cannot rely on Row()/Col() because I could be inside another
DATA nPhysCol INIT 0 // application/object and this one could be moving real cursor. If I'm running full
// screen nPhysRow will always have the same value as Row() and nPhysCol as Col()
DATA nTextRow INIT 0 // Display position of the cursor whitin the text buffer.
DATA nTextCol INIT 0 // idem.
DATA nWndRow INIT 0 // Initial position of cursor whitin text window.
DATA nWndCol INIT 0 // idem.
DATA nNumCols INIT 1 // How many columns / rows can be displayed inside editor window
DATA nNumRows INIT 1
DATA nTabWidth INIT 5 // Size of Tab chars
DATA lEditAllow INIT .T. // Are changes to text allowed?
DATA lSaved INIT .F. // True if user exited editor with K_CTRL_W
DATA lWordWrap INIT .T. // .f. earlier, True if word wrapping is active
DATA nWordWrapCol INIT 0 // At which column word wrapping occurs
DATA lChanged INIT .F. // .T. if there are changes not saved
DATA lExitEdit INIT .F. // .T. if user requested to end Edit() method
DATA cColorSpec INIT SetColor() // Color string used for screen writes
DATA lRightScroll INIT .T. // MARKER TO SET LINE SCROLLING OF R_KEY
DATA nMarkPos // Mark proper new position of cursor when wrapping and splitting lines
DATA nMarkLen
DATA nOrigCursor INIT SetCursor() // Save to restore original cursor format on exit
DATA ProcName INIT ""
DATA ProcLine INIT 0
DATA nCurrentCursor INIT SetCursor()
DATA lSelActive INIT .F.
DATA nRowSelStart INIT 0 // First row selected
DATA nRowSelEnd INIT 0 // Last row selected
DATA nColSelRow INIT 0 // Row of col selected
DATA nColSelStart INIT 0 // First col selected
DATA nColSelEnd INIT 0 // Last col selected
// Class DATA can be faster, but since the user can change directly
// READINSERT(), ::lInsert must check in it.
// DATA lInsert INIT .F. // Is editor in Insert mode or in Overstrike one? Default : Overstrike - Clipper
METHOD lInsert() BLOCK {|| Set( _SET_INSERT ) }
METHOD _lInsert( lInsert ) BLOCK {| Self, lInsert | HB_SYMBOL_UNUSED( Self ), iif( HB_ISLOGICAL( lInsert ), Set( _SET_INSERT, lInsert ), Set( _SET_INSERT ) ) }
METHOD New( cString, nTop, nLeft, nBottom, ; // Converts a string to an array of strings splitting input string at EOL boundaries
nRight, lEditMode, nLineLength, nTabSize, nTextRow, nTextCol, nWndRow, nWndCol )
METHOD LoadFile( cFileName ) // Load cFileName into active editor
METHOD LoadText( cString ) // Load cString into active editor
METHOD SaveFile() // Save active file ( not for MemoEdit() emulation )
METHOD AddLine( cLine, lSoftCR ) // Add a new Line of text at end of current text
METHOD InsertLine( cLine, lSoftCR, nRow ) // Insert a line of text at a defined row
METHOD RemoveLine( nRow ) // Remove a line of text
METHOD GetLine( nRow ) // Return line n of text
METHOD LineLen( nRow ) INLINE iif( nRow == NIL, nRow := ::nRow, ), iif( nRow <= ::LastRow(), Len( ::aText[ nRow ]:cText ), 0 ) // Return text length of line n
METHOD SplitLine( nRow ) // If a line of text is longer than nWordWrapCol divides it into multiple lines
METHOD GotoLine( nRow ) // Put line nRow at cursor position
METHOD GotoCol( nCol ) // Put line nCol at cursor position
METHOD GotoPos( nRow, nCol, lRefresh )
METHOD GetText( lSoftCR ) // Returns aText as a string ( for MemoEdit() return )
METHOD DelText() // Clear aText
METHOD AddText( cString, lAtPos ) // Add text at the cursor
METHOD GetTextIndex() // Return current cursor position in text.
METHOD SetTextSelection( cAction, nCount ) // Start or modify the current selection.
METHOD GetTextSelection( lSoftCr ) // Return the current selection.
METHOD DelTextSelection() // Delete the current selection
METHOD ClrTextSelection() // Clear the current selection.
METHOD RefreshWindow() // Redraw a window
METHOD RefreshLine( lRefreshColSel ) // Redraw a line
METHOD RefreshColumn() // Redraw a column of text
METHOD LineColor( nRow ) // Returns color string to use to draw nRow ( current line if nRow is empty )
METHOD ColColor() // Returns color string to use to draw nCol ( current line if nCol is empty )
METHOD MoveCursor( nKey ) // Move cursor inside text / window ( needs a movement key )
METHOD InsertState( lInsState ) // Changes lInsert value and insertion / overstrike mode of editor
METHOD Edit( nPassedKey ) // Handles input ( can receive a key in which case handles only this key and then exits )
METHOD KeyboardHook() // Gets called every time there is a key not handled directly by HBEditor
METHOD IdleHook() // Gets called every time there are no more keys to hanlde just before HBEditor blocks itself waiting for a char
METHOD Resize( nTop, nLeft, nBottom, nRight ) // Redefines editor window size and refreshes it
METHOD SetColor( cColorString ) // Sets/retrieves color used for screen writes
METHOD Hilite() // Start Hilighting swapping first two color definitions inside cColorSpec
METHOD DeHilite() // Stop Hilighting
METHOD SetPos( nRow, nCol ) // Updates ::nPhysRow, ::nPhysCol and then calls SetPos() to move hardware cursor
METHOD Row() INLINE ::nPhysRow // Same as clipper ones, returns ::nPhysRow value
METHOD Col() INLINE ::nPhysCol // Same as clipper ones, returns ::nPhysCol value
METHOD Down()
METHOD PageDown()
METHOD Bottom()
METHOD GoBottom()
METHOD Up()
METHOD PageUp()
METHOD Top()
METHOD GoTop()
METHOD Right()
METHOD WordRight()
METHOD End()
METHOD Left()
METHOD WordLeft()
METHOD Home()
METHOD K_Ascii( nKey )
METHOD K_Return()
METHOD K_Del()
METHOD K_Bs()
METHOD K_Tab()
METHOD K_Mouse( nKey )
METHOD K_Esc()
// 2006/07/19 - E.F. - Added datas and methods.
DATA cInsLabel // <Insert> label to display at toggle insert.
DATA lVerticalScroll INIT .T. // True if vertical scrolling is active (default).
DATA bKeyBlock // To process set key codeblock
METHOD DisplayInsert( lInsert ) // Show <insert> message at top of screen.
METHOD LastRow() INLINE Len( ::aText ) // Replace old ::naTextLen
METHOD DelTextRight( nRow ) // Delete text right of cursor.
METHOD DelWordRight() // Delete word right <CTRL-T> key.
METHOD ReformParagraph() // Reformat paragraph. CTRL-B behaviour
/////////////////
PROTECTED:
METHOD BrowseText( nPassedKey, lHandleOneKey )
// 2006/07/25 - E.F. - Internal use only.
METHOD GetCol( nRow, nCol ) INLINE iif( nRow > 0 .AND. nRow <= ::LastRow(), iif( nCol > 0 .AND. nCol <= Min(::nWordWrapCol + 1,::LineLen(nRow ) ), SubStr( ::aText[ nRow ]:cText, nCol, 1 ), "" ), "" )
METHOD IsEmptyLine( nRow ) INLINE iif( nRow > 0 .AND. nRow <= ::LastRow(), Empty( ::aText[ nRow ]:cText ), .T. )
ENDCLASS
//-------------------------------------------------------------------//
METHOD New( cString, nTop, nLeft, nBottom, nRight, lEditMode, nLineLength, nTabSize, nTextRow, nTextCol, nWndRow, nWndCol ) CLASS XHBEditor
DEFAULT cString TO ""
DEFAULT nTop TO 0
DEFAULT nLeft TO 0
DEFAULT nBottom TO MaxRow()
DEFAULT nRight TO MaxCol()
DEFAULT lEditMode TO .T.
DEFAULT nLineLength TO NIL
DEFAULT nTabSize TO NIL
DEFAULT nTextRow TO 1
DEFAULT nTextCol TO 0 // 1 Clipper Documentations says it is 0
DEFAULT nWndRow TO 0 // 1 "
DEFAULT nWndCol TO 0 // 1 "
// 2006/JUL/22 - E.F. To avoid run time error.
IF nTop > nBottom .OR. nLeft > nRight
Throw( ErrorNew( "BASE", 0, 1127, "Argument error: <nTop,nRight,nLeft,nBottom>" , ProcName() ) )
ENDIF
// fix setcolor() to value at New() call
::cColorSpec := SetColor()
// Note original cursor to restore after editing
::nOrigCursor := SetCursor()
// 2006/JUL/21 - E.F. To avoid out of boundaries.
// Editor window boundaries
::nTop := Min( Max( 0,nTop ), MaxRow() )
::nLeft := Min( Max( 0,nLeft ), MaxCol() )
::nBottom := Max( 0, Min( MaxRow(),nBottom ) )
::nRight := Max( 0, Min( MaxCol(),nRight ) )
// How many cols and rows are available
::nNumCols := ::nRight - ::nLeft + 1
::nNumRows := ::nBottom - ::nTop + 1
// 2006/AUG/18 - E.F. Adjusted nLineLenght in accordance with Clipper's
// memoedit.
IF !HB_ISNUMERIC( nLineLength )
nLineLength := ::nNumCols
ELSE
IF nLineLength < 0
nLineLength := 255
::lWordWrap := .F.
ELSE
nLineLength := Max( 6, nLineLength )
ENDIF
ENDIF
::nWordWrapCol := nLineLength - 1 // please don't change it.
IF lEditMode != NIL
::lEditAllow := lEditMode
ENDIF
// set correct insert state
IF ::lEditAllow
// Force to redraw INS message
::InsertState( ! Set( _SET_INSERT ) )
::InsertState( ! Set( _SET_INSERT ) )
ENDIF
// No need to save
::lChanged := .F.
// how many spaces for each tab?
IF nTabSize != NIL
::nTabWidth := nTabSize
ENDIF
::nTextRow := Max( 1, nTextRow )
::nTextCol := Max( 0, nTextCol )
::nWndRow := Max( 0, nWndRow )
::nWndCol := Max( 0, nWndCol )
::nFirstRow := Max( 1, ::nTextRow - ::nWndRow )
::nFirstCol := Max( 1, ::nTextCol - ::nWndCol )
// If memofield was created with Clipper, it needs to have chr( 141 )+chr( 10 ) stripped
// 2006/JUL/20 - E.F. - We should not replace SoftCR with chr(32).
// See Text2Array function for more details.
/*
* IF chr( 141 ) $ cString
* acsn := chr( 32 ) + chr( 141 ) + chr( 10 )
* cString := STRTRAN( cString, acsn, " " )
* acsn := chr( 141 ) + chr( 10 )
* cString := STRTRAN( cString, acsn, " " )
* ENDIF
*/
// Load text to internal array.
// TODO: if at ME_INIT mode (when udf is called), the ::lWordWrap is toggled
// to .F. (default is .t.), the <cString> should not be splitted, but
// in the Text2Array function the <cString> will be splitted in
// accordance with nLineLength.
//
::aText := Text2Array( cString, nLineLength )
IF ::LastRow() == 0
AAdd( ::aText, HBTextLine():New() )
ENDIF
// Setting datas that depend of ::aText filled.
//
::nRow := Max( 1, Min( ::nTextRow, Len( ::aText ) ) )
::nCol := Max( 1, Min( Len( ::aText[ ::nRow ]:cText ) , ::nTextCol + 1 ) )
// extra sanitization over max bounds
IF ::nFirstRow > ::LastRow()
::nFirstRow := ::LastRow()
ENDIF
IF ::nFirstCol > ::LineLen( ::nRow ) + 1
::nFirstCol := ::LineLen( ::nRow ) + 1
ENDIF
// Set cursor position; also initializes phisical to virtual mapping
//::SetPos( ::nTop + ::nRow - ::nFirstRow, ::nLeft + ::nCol - ::nFirstCol )
::SetPos( ::nTop + ::nWndRow, ::nLeft + ::nWndCol )
::RefreshWindow()
RETURN Self
//-------------------------------------------------------------------//
//
// Redefines editor window size and refreshes it
//
METHOD Resize( nTop, nLeft, nBottom, nRight ) CLASS XHBEditor
// don't change coordinates not given
DEFAULT nTop to ::nTop
DEFAULT nLeft to ::nLeft
DEFAULT nBottom to ::nBottom
DEFAULT nRight to ::nRight
::nTop := nTop
::nLeft := nLeft
::nBottom := nBottom
::nRight := nRight
// How many cols and rows are available
//
::nNumCols := ::nRight - ::nLeft + 1
::nNumRows := ::nBottom - ::nTop + 1
// FirstCol/Row of current text visible inside editor window
//
::nFirstCol := 1
::nFirstRow := 1
// Cursor position inside aText ( nRow ) and inside current line of text ( nCol )
//
::nRow := 1
::nCol := 1
// Set cursor upper left corner
::SetPos( ::nTop, ::nLeft )
::RefreshWindow()
RETURN Self
//-------------------------------------------------------------------//
//
// Screen Output
//
//-------------------------------------------------------------------//
//
// Redraws a screenfull of text
//
METHOD RefreshWindow() CLASS XHBEditor
LOCAL i
LOCAL nOCol
LOCAL nORow
LOCAL nOCur
nOCol := ::Col()
nORow := ::Row()
nOCur := SetCursor( SC_NONE )
DispBegin()
// This breaks individual line coloring, so I restored the old version with
// a small optimization. -- Ph.Krylov
// CLEAR THE WHOLE WINDOW!!! previous version wished to spare some output, but
// C is faster than a VM loop!!
//
//ScrollFixed( ::nTop, ::nLeft, ::nBottom, ::nRight )
FOR i := 0 TO Min( ::nNumRows - 1, ::LastRow() - 1 )
// 2006/JUL/23 - E.F. Adjusted to avoid out of bound.
// Don't replace ::GetLine(nRow) by ::aText[nRow]:cText here,
// because getline return line number in tbrwtext.prg (debug).
DispOutAt( Min( ::nTop + i,::nBottom ), ::nLeft, ;
PadR( iif( ::nFirstRow + i <= ::LastRow(), SubStr( ::GetLine( ::nFirstRow + i ), ::nFirstCol, ::nNumCols ), Space(::nNumCols ) ), ::nNumCols ), ;
::LineColor( ::nFirstRow + i ) )
NEXT
ScrollFixed( ::nTop + i, ::nLeft, ::nBottom, ::nRight )
DispEnd()
SetCursor( nOCur )
::SetPos( nORow, nOCol )
RETURN Self
//-------------------------------------------------------------------//
//
// Return the color of the row
//
METHOD LineColor( nRow ) CLASS XHBEditor
LOCAL cColor
IF ::lSelActive .AND. ( ( nRow >= ::nRowSelStart ) .AND. ( nRow <= ::nRowSelEnd ) ) .AND. ;
::nRowSelStart > 0 .AND. ::nRowSelEnd > 0
cColor := hb_ColorIndex( ::cColorSpec, CLR_ENHANCED )
ELSE
cColor := hb_ColorIndex( ::cColorSpec, CLR_STANDARD )
ENDIF
RETURN cColor
//-------------------------------------------------------------------//
//
// Return the color of the Col
//
METHOD ColColor() CLASS XHBEditor
LOCAL cColor
IF ::lSelActive .AND. ::nColSelStart > 0 .AND. ::nColSelEnd > 0 .AND. ;
::nColSelStart <= ::nColSelEnd
cColor := hb_ColorIndex( ::cColorSpec, CLR_ENHANCED )
ELSE
cColor := hb_ColorIndex( ::cColorSpec, CLR_STANDARD )
ENDIF
RETURN cColor
//-------------------------------------------------------------------//
//
// Redraws current screen line
//
METHOD RefreshLine( lRefreshColSel ) CLASS XHBEditor
LOCAL nOCol
LOCAL nORow
LOCAL nCol, nFirstCol
DEFAULT lRefreshColSel TO .F.
IF ::nRow <= ::LastRow()
nOCol := ::Col()
nORow := ::Row()
DispBegin()
// 2006/AUG/02 - E.F.
// Don't replace ::GetLine(nRow) by ::aText[nRow]:cText here
// because getline return line number in tbrwtext.prg (debug).
DispOutAt( ::Row(), ::nLeft, PadR( SubStr( ::GetLine( ::nRow ), ::nFirstCol, ::nNumCols ), ::nNumCols, " " ), ::LineColor( ::nRow ) )
IF lRefreshColSel
nFirstCol := Max( ::nTextCol, ::nWndCol )
nFirstCol := Max( nFirstCol, ::nLeft )
/* 2006/SEP/20 - E.F. - Fine cursor adjustment. */
// nCol := nFirstCol + ::nColSelStart - 1
nCol := Max( ::nLeft, nFirstCol + ::nColSelStart - 1 )
DispOutAt( ::Row(), nCol, SubStr( ::GetLine( ::nRow ), Max(1,::nColSelStart ), (::nColSelEnd - ::nColSelStart + 1 ) ) , ::ColColor() )
ENDIF
DispEnd()
::SetPos( nORow, nOCol )
ENDIF
RETURN Self
//-------------------------------------------------------------------//
//
// Refreshes only one screen column of text ( for Left() and Right() movements )
//
METHOD RefreshColumn() CLASS XHBEditor
LOCAL i
LOCAL nOCol
LOCAL nORow
LOCAL nOCur
nOCol := ::Col()
nORow := ::Row()
nOCur := SetCursor( SC_NONE )
DispBegin()
// 2006/AUG/02 - E.F.
// Don't replace ::GetLine(nRow) by ::aText[nRow]:cText here
// because getline return line number in tbrwtext.prg (debug).
for i := 0 TO Min( ::nNumRows - 1, ::LastRow() - 1 )
DispOutAt( ::nTop + i, nOCol, SubStr( ::GetLine(::nFirstRow + i ), ::nCol, 1 ), ::LineColor( ::nFirstRow + i ) )
next
DispEnd()
SetCursor( nOCur )
::SetPos( nORow, nOCol )
RETURN Self
//-------------------------------------------------------------------//
//
// Wrapper for Cursor Movement to be used from Outside of This Class
//
//-------------------------------------------------------------------//
METHOD MoveCursor( nKey ) CLASS XHBEditor
// Modified to handle cursor movements inside text array without crashing!
// Modified to allow line wrapping, and to track cursor to line ends.
//
Switch nKey
// TODO: for optimization, change this with relativie GOTOCOL, GOTOPOS and GOTOROW
CASE K_DOWN
::ClrTextSelection()
::Down()
EXIT
CASE K_PGDN
::ClrTextSelection()
::PageDown()
EXIT
CASE K_CTRL_PGDN
::ClrTextSelection()
::GoBottom()
EXIT
CASE K_UP
::ClrTextSelection()
::Up()
EXIT
CASE K_PGUP
::ClrTextSelection()
::PageUp()
EXIT
CASE K_CTRL_PGUP
::ClrTextSelection()
::GoTop()
EXIT
CASE K_RIGHT
::ClrTextSelection()
::Right()
EXIT
CASE K_CTRL_RIGHT
::ClrTextSelection()
::WordRight()
EXIT
CASE K_LEFT
::ClrTextSelection()
::Left()
EXIT
CASE K_CTRL_LEFT
::ClrTextSelection()
::WordLeft()
EXIT
CASE K_HOME
::ClrTextSelection()
::Home()
EXIT
CASE K_CTRL_HOME
::ClrTextSelection()
::Top()
EXIT
CASE K_END
::ClrTextSelection()
::End()
EXIT
CASE K_CTRL_END
::ClrTextSelection()
::Bottom()
::End()
EXIT
OTHERWISE
RETURN .F.
endswitch
RETURN .T.
//-------------------------------------------------------------------//
//
// Editing
//
//-------------------------------------------------------------------//
METHOD Edit( nPassedKey ) CLASS XHBEditor
LOCAL nKey
LOCAL lSingleKeyProcess := .F.
// If user pressed an exiting key ( K_ESC or K_ALT_W ) or I've received
// a key to handle and then exit
DO WHILE ! ::lExitEdit .AND. ! lSingleKeyProcess
// If I haven't been called with a key already preset, evaluate
// this key and then exit.
//
IF nPassedKey == NIL
IF NextKey() == 0
::IdleHook()
ENDIF
nKey := Inkey( 0, INKEY_ALL )
ELSE
lSingleKeyProcess := .T.
nKey := nPassedKey
ENDIF
/*
* 2006/AUG/12 -E.F. Trap Set key only if nKey is nil.
*/
IF nPassedKey == NIL
IF ( ::bKeyBlock := SetKey( nKey ) ) != NIL
Eval( ::bKeyBlock, ::ProcName, ::ProcLine, "", Self )
LOOP
ENDIF
ENDIF
SWITCH nKey
CASE K_LBUTTONUP
CASE K_MWFORWARD
CASE K_MWBACKWARD
::K_Mouse( nKey )
EXIT
#ifdef HB_EXT_INKEY
CASE K_CTRL_A // Select all
::SetTextSelection( "ALL" )
EXIT
CASE K_CTRL_C // Copy
hb_gtInfo( HB_GTI_CLIPBOARDDATA, ::GetTextSelection() )
//::ClrTextSelection()
EXIT
CASE K_CTRL_X // Cut
hb_gtInfo( HB_GTI_CLIPBOARDDATA, ::GetTextSelection() )
IF ::lEditAllow
::DelTextSelection()
ENDIF
EXIT
CASE K_CTRL_V // Paste
IF ::lEditAllow
::AddText( StrTran( hb_gtInfo( HB_GTI_CLIPBOARDDATA ), Chr( 0 ), " " ), .T. )
::ClrTextSelection()
ENDIF
EXIT
CASE K_ALT_W // Exit and return text buffer content.
CASE K_CTRL_W // idem
::lSaved := .T.
::lExitEdit := .T.
SetCursor( ::nOrigCursor ) // restore original cursor saved at startup
EXIT
CASE K_CTRL_END
::ClrTextSelection()
::Bottom()
::End()
EXIT
CASE K_CTRL_B // Reformat paragraph
IF ::lEditAllow
::ClrTextSelection()
::ReformParagraph() // 2006/JUL/29 -E.F. Added.
ENDIF
EXIT
#else
CASE K_ALT_W
EXIT
CASE K_CTRL_W
::lSaved := .T.
::lExitEdit := .T.
SetCursor( ::nOrigCursor ) // restore original cursor saved at startup
EXIT
#endif
CASE K_CTRL_N
IF ::lEditAllow // Clipper compatibility
::ClrTextSelection()
::lChanged := .T.
::Home()
::InsertLine( "", .F. , ::nRow )
::RefreshLine()
::RefreshWindow()
ENDIF
EXIT
CASE K_CTRL_T
IF ::lEditAllow
::ClrTextSelection()
::lChanged := .T.
::DelWordRight()
::RefreshLine()
ENDIF
EXIT
CASE K_CTRL_Y
IF ::lEditAllow // Clipper compatibility
::lChanged := .T.
::ClrTextSelection()
IF ::LastRow() > 1 .AND. ::nRow < ::LastRow()
::RemoveLine( ::nRow )
::RefreshWindow()
IF ::LastRow() > 0
::Home()
::RefreshLine()
ENDIF
ELSE
::aText[ ::nRow ]:cText := ""
::RefreshLine()
::Home()
::RefreshLine()
ENDIF
ENDIF
EXIT
CASE K_DOWN
IF hb_bitAnd( hb_gtInfo( HB_GTI_KBDSHIFTS ), HB_GTI_KBD_SHIFT ) != 0
IF ::nRow <= ::LastRow()
::SetTextSelection( "ROW", +1 )
ENDIF
ELSE
::ClrTextSelection()
::Down()
ENDIF
EXIT
CASE K_PGDN
::ClrTextSelection()
::PageDown()
EXIT
CASE K_CTRL_PGDN
::ClrTextSelection()
::GoBottom()
EXIT
CASE K_UP
IF hb_bitAnd( hb_gtInfo( HB_GTI_KBDSHIFTS ), HB_GTI_KBD_SHIFT ) != 0
IF ::nRow > 1
::SetTextSelection( "ROW", -1 )
ENDIF
ELSE
::ClrTextSelection()
::Up()
ENDIF
EXIT
CASE K_PGUP
::ClrTextSelection()
::PageUp()
EXIT
CASE K_CTRL_PGUP
::ClrTextSelection()
::GoTop()
EXIT
CASE K_RIGHT
IF hb_bitAnd( hb_gtInfo( HB_GTI_KBDSHIFTS ), HB_GTI_KBD_SHIFT ) != 0
IF ::nCol < ::nWordWrapCol + 1
::SetTextSelection( "COL", +1 )
ENDIF
ELSE
::ClrTextSelection()
::Right()
ENDIF
EXIT
CASE K_CTRL_RIGHT
::ClrTextSelection()
::WordRight()
EXIT
CASE K_LEFT
IF hb_bitAnd( hb_gtInfo( HB_GTI_KBDSHIFTS ), HB_GTI_KBD_SHIFT ) != 0
IF ::nCol > 1
::SetTextSelection( "COL", -1 )
ENDIF
ELSE
::ClrTextSelection()
::Left()
ENDIF
EXIT
CASE K_CTRL_LEFT
::ClrTextSelection()
::WordLeft()
EXIT
CASE K_HOME
IF hb_bitAnd( hb_gtInfo( HB_GTI_KBDSHIFTS ), HB_GTI_KBD_SHIFT ) != 0
IF ::nCol > 1
::SetTextSelection( "HOME" )
ENDIF
ELSE
::ClrTextSelection()
::Home()
ENDIF
EXIT
CASE K_CTRL_HOME
::ClrTextSelection()
::Top()
EXIT
CASE K_END
IF hb_bitAnd( hb_gtInfo( HB_GTI_KBDSHIFTS ), HB_GTI_KBD_SHIFT ) != 0
IF ::nCol < ::nWordWrapCol + 1
::SetTextSelection( "END" )
ENDIF
ELSE
::ClrTextSelection()
::End()
ENDIF
EXIT
CASE K_ESC
::ClrTextSelection()
::K_Esc()
EXIT
CASE K_RETURN
::ClrTextSelection()
::K_Return()
EXIT
CASE K_INS
IF hb_bitAnd( hb_gtInfo( HB_GTI_KBDSHIFTS ), HB_GTI_KBD_SHIFT ) != 0
IF ::lEditAllow
::AddText( StrTran( hb_gtInfo( HB_GTI_CLIPBOARDDATA ), Chr( 0 ), " " ), .T. )
::ClrTextSelection()
ENDIF
ELSE
// 2006/JUL/22 - E.F. - Insert is allowed only in edit mode.
IF ::lEditAllow
::ClrTextSelection()
::InsertState( !::lInsert )
ENDIF
ENDIF
EXIT
#ifdef HB_EXT_INKEY
CASE K_DEL
IF ::lSelActive .AND. ::lEditAllow
::DelTextSelection()
ELSEIF ::lEditAllow // Clipper compatibility
::K_Del()
ENDIF
EXIT
#else
CASE K_DEL
IF hb_bitAnd( hb_gtInfo( HB_GTI_KBDSHIFTS ), HB_GTI_KBD_SHIFT ) != 0
hb_gtInfo( HB_GTI_CLIPBOARDDATA, ::GetTextSelection() )
IF ::lEditAllow
::DelTextSelection()
ENDIF
ELSE
IF ::lEditAllow // Clipper compatibility
::K_Del()
ENDIF
ENDIF
EXIT
#endif
CASE K_TAB
IF ::lEditAllow // Clipper compatibility
::ClrTextSelection()
::K_Tab()
ENDIF
EXIT
CASE K_BS
::ClrTextSelection()
IF ::lEditAllow // Clipper compatibility
::K_Bs()
ELSE
// 2006/JUL/22 - E.F. - Clipper backspace in read only is same as left movement.
::Left()
ENDIF
EXIT
CASE K_CTRL_BS // block chr( 127 ), a printable character in windows
::ClrTextSelection()
EXIT
OTHERWISE
IF Len( hb_KeyChar( nKey ) ) > 0
IF ::lEditAllow
::ClrTextSelection()
::K_Ascii( nKey )
ENDIF
ELSE
// NOTE: if you call ::Edit() with a key that is passed to ::KeyboardHook() and then
// ::KeyboardHook() calls ::Edit() with the same key you end up with an endless loop
::ClrTextSelection()
::KeyboardHook( nKey )
ENDIF
ENDSWITCH
ENDDO
RETURN Self
//-------------------------------------------------------------------//
//
// Vertical Navigation
//
//-------------------------------------------------------------------//
METHOD Down() CLASS XHBEditor
IF ::lVerticalScroll
IF ::nRow < ::LastRow()
::GotoLine( ::nRow + 1 )
ENDIF
ELSE
IF ::nFirstRow < ::LastRow() .AND. ::LastRow() > ::nNumRows
::nFirstRow ++
::nRow ++
IF ::nRow > ::LastRow()
::nRow --
ENDIF
::RefreshWindow()
ELSEIF ::nRow < ::LastRow()
::GotoLine( ::nRow + 1 )
ENDIF
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD PageDown() CLASS XHBEditor
LOCAL nJump
nJump := Min( ::nNumRows, ::LastRow() - ::nFirstRow - ( ::nPhysRow - ::nTop ) )
IF ::lVerticalScroll
IF nJump > ( ::LastRow() - ::nRow ) //nJump < ::nNumRows
::Bottom()
ELSE
::nFirstRow += nJump
IF ::nFirstRow > ::LastRow()
::nFirstRow := ::LastRow()
ENDIF
::nRow += nJump
IF ::nRow > ::LastRow()
::nRow := ::LastRow()
ENDIF
::RefreshWindow()
ENDIF
// ::GotoLine( min( ::nRow + ::nNumRows - 1, ::LastRow() ) )
ELSE
nJump := Min( nJump, ::LastRow() - ::nFirstRow + 1 )
::nFirstRow += nJump
::nRow += nJump
::RefreshWindow()
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD Bottom() CLASS XHBEditor
LOCAL nRowTo := Min( ::nFirstRow + ::nNumRows - 1, ::LastRow() )
::GotoLine( nRowTo )
RETURN Self
//-------------------------------------------------------------------//
METHOD GoBottom() CLASS XHBEditor
::GotoPos( ::LastRow(), ::LineLen( ::LastRow() ) + 1, .T. )
RETURN Self
//-------------------------------------------------------------------//
METHOD Up() CLASS XHBEditor
IF ::lVerticalScroll
IF ::nRow > 1
::GotoLine( ::nRow - 1 )
//::GotoPos( ::nRow - 1, ::nCol )
ENDIF
ELSE
IF ::nFirstRow > 1
::nFirstRow --
::nRow --
IF ::nRow < 1
::nRow := 1
ENDIF
::RefreshWindow()
ELSEIF ::nRow > 1
::GotoLine( ::nRow - 1 )
ENDIF
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD PageUp() CLASS XHBEditor
LOCAL nJump
nJump := Min( ::nNumRows, ::nFirstRow - 1 )
IF ::lVerticalScroll
IF nJump == 0
::GoToLine( 1 )
ELSE
::nFirstRow -= nJump
IF ::nFirstRow < 1
::nFirstRow := 1
ENDIF
::nRow -= nJump
IF ::nRow < 1
::nRow := 1
ENDIF
::RefreshWindow()
ENDIF
// ::GotoLine( Max( 1, ::nRow - ::nNumRows ) )
ELSE
nJump := Min( nJump, ::nNumRows - 1 )
::nFirstRow -= nJump
::nRow -= nJump
::RefreshWindow()
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD Top() CLASS XHBEditor
::GotoPos( ::nFirstRow, 1, .T. )
RETURN Self
//-------------------------------------------------------------------//
METHOD GoTop() CLASS XHBEditor
::GotoPos( 1, 1 )
RETURN Self
//-------------------------------------------------------------------//
//
// Horizontal Navigation
//
//-------------------------------------------------------------------//
METHOD Right() CLASS XHBEditor
IF ( ::lWordWrap )
// 2006/07/19 - E.F. Changed max right point to pos cursor to next.
//
IF ::nCol > ::nWordWrapCol .AND. ::nRow < ::LastRow()
::GotoPos( ::nRow + 1, 1, .T. )
ELSE
/* ::GotoCol( ::nCol + 1 ) does not correctly redraw the screen
* if K_RIGHT is stuffed into the keyboard buffer; use GotoPos()
* Gotocol checks for line bounds also; as the IF should check for a
* method too, theres no spare in IF here.
* ::GotoCol( ::nCol + 1 )
*/
::GotoPos( ::nRow, ::nCol + 1, .T. )
ENDIF
ELSE
IF ::nCol < Max( ::nNumCols, ::nWordWrapCol + 1 )
//::GotoCol( ::nCol + 1 )
::GotoPos( ::nRow, ::nCol + 1 , .T. )
ENDIF
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD WordRight() CLASS XHBEditor
LOCAL nMaxCol := Min( ::nWordWrapCol + 1, ::LineLen( ::nRow ) )
// NOTE: should be faster without call to ::GetLine()
//
IF !::lWordWrap .AND. ::IsEmptyLine( ::nRow ) .OR. ;
::LastRow() == 0 .OR. ;
( At( " ", ::aText[ ::nRow ]:cText ) == 0 .AND. ::LineLen( ::nRow ) >= ::nWordWrapCol )
RETURN self
ENDIF
DispBegin() // to minimize flicker.
// 2006/JUL/21 - E.F. Changed to verify empty character instead space.
// In any circunstancies wordright stop at space.
// Added verification in not wordwrap mode if reach
// rightmost position.
//
DO while ::nCol <= nMaxCol .AND. !Empty( ::GetCol( ::nRow, ::nCol ) )
::Right()
IF ::nCol > nMaxCol .OR. ;
( !::lWordWrap .AND. ::nCol >= nMaxCol )
EXIT
ENDIF
ENDDO
DO while ::nCol <= nMaxCol .AND. Empty( ::GetCol( ::nRow, ::nCol ) )
::Right()
IF ::nCol > nMaxCol .OR. ;
( !::lWordWrap .AND. ::nCol >= nMaxCol )
EXIT
ENDIF
ENDDO
IF !::lWordWrap
DO while ::nCol > 1 .AND. !Empty( ::GetCol( ::nRow, ::nCol ) )
::Left()
ENDDO
DO while ::nCol < nMaxCol .AND. Empty( ::GetCol( ::nRow,::nCol ) )
::Right()
ENDDO
ENDIF
// mod = move to next line
//
IF ::lRightScroll
IF ::lWordWrap
// 2006/JUL/21 - E.F. - If cursor reach rightmost position
// go to the next line.
IF ::nCol > nMaxCol .AND. ::nRow < ::LastRow()
::Down()
::Home()
IF Empty( ::GetCol( ::nRow, ::nCol ) )
::WordRight()
ENDIF
// 2006/JUL/21 - E.F. - If cursor stop at empty char and it is the
// last reachable position go back to the previous word.
ELSEIF ::nCol >= nMaxCol .AND. ::nRow == ::LastRow()
IF !Empty( ::GetCol( ::nRow, ::nCol ) )
::end()
ENDIF
::WordLeft()
ELSEIF ::nCol == 1 .AND. Empty( ::GetCol( ::nRow, ::nCol ) )
::WordRight()
ENDIF
ELSE
// 2006/JUL/21 - E.F. - If cursor reach rightmost position go to back to prior word.
IF ::nCol > nMaxCol
::Wordleft()
ENDIF
ENDIF
ENDIF
DispEnd()
RETURN Self
//-------------------------------------------------------------------//
METHOD End() CLASS XHBEditor
// 2006/07/19 - E.F. Changed to avoid the cursor out of line.
//
::GotoCol( Min( ::LineLen( ::nRow ) + 1, Max(::nNumCols,::nWordWrapCol + 1 ) ) )
RETURN Self
//-------------------------------------------------------------------//
METHOD Left() CLASS XHBEditor
// Gotocol checks for nCol > 1 also, but this saves a func call
IF ::nCol == 1
IF ( ::lWordWrap )
IF ::nRow > 1
// 2006/07/19 E.F. left should be at max in the leftmost column.
//
::GotoPos( ::nRow - 1, Max( ::nNumCols,::nWordWrapCol + 1 ) , .T. )
ENDIF
//else do nothing
ENDIF
ELSE
::GotoCol( ::nCol - 1 )
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD WordLeft() CLASS XHBEditor
// splitline() does not use this function
// modifed to wrap lines and position at first letter of word, not word end
//
IF !::lWordWrap .AND. ::IsEmptyLine( ::nRow ) .OR. ::LastRow() == 0
RETURN self
ENDIF
DispBegin() // to minimize flicker
IF ::lWordWrap .AND. ::nCol == 1 .AND. ::nRow > 1
::Up()
::End()
DO while ::nCol == 1 .AND. ::nRow > 1 .AND. Empty( ::GetCol( ::nRow,::nCol ) )
::Up()
IF !::IsEmptyLine( ::nRow )
::End()
EXIT
ENDIF
ENDDO
ENDIF
// 2006/JUL/21 - E.F. - Changed to verifiy empty char instead space. In any
// circunstancies wordleft stop at space.
//
DO while ::nCol > 1 .AND. !Empty( ::GetCol( ::nRow, ::nCol ) )
::Left()
ENDDO
DO while ::nCol > 1 .AND. Empty( ::GetCol( ::nRow, ::nCol ) )
::Left()
ENDDO
DO while ::nCol > 1 .AND. !Empty( ::GetCol( ::nRow, ::nCol - 1 ) ) // move to front of word
::Left()
ENDDO
// 2006/JUL/24 -E.F. - If cursor stoped at empty char, then
// go to the next word.
IF !::lWordWrap .AND. ;
::nCol < ::LineLen( ::nRow ) .AND. ;
Empty( ::GetCol( ::nRow,::nCol ) )
::WordRight()
ELSEIF ::lWordWrap .AND. ::nCol = 1 .AND. ::nRow = 1 .AND. ;
Empty( ::GetCol( ::nRow,::nCol ) )
::WordRight()
ELSEIF ::lWordWrap .AND. ::nCol = 1 .AND. ::nRow > 1
DO While ::nCol = 1 .AND. ::nRow > 1 .AND. Empty( ::GetCol( ::nRow, ::nCol ) )
::up()
IF !::IsEmptyLine( ::nRow )
::end()
::wordLeft()
EXIT
ENDIF
ENDDO
ENDIF
DispEnd()
RETURN Self
//-------------------------------------------------------------------//
METHOD Home() CLASS XHBEditor
::GotoCol( 1 )
RETURN Self
//-------------------------------------------------------------------//
METHOD K_Mouse( nKey ) CLASS XHBEditor
LOCAL nRow, nCol, nJump
Switch nKey
CASE K_LBUTTONUP
nRow := MRow()
nCol := MCol()
IF ( nRow >= ::nTop .AND. nRow <= ::nBottom )
IF nCol >= ::nLeft .AND. nCol <= ::nRight
IF ( ::nRow + ( nJump := nRow - ::nPhysRow ) ) <= ::LastRow()
::GotoPos( Max( 1, ::nRow + nJump ), Max( 1, ::nCol + ( nCol - ::nPhysCol ) ), .T. )
ENDIF
ENDIF
ENDIF
EXIT
CASE K_MWFORWARD
::ClrTextSelection()
::Up()
EXIT
CASE K_MWBACKWARD
::ClrTextSelection()
::Down()
EXIT
end
RETURN Self
//-------------------------------------------------------------------//
//
// Keystroke Handelling
//
//-------------------------------------------------------------------//
METHOD K_Ascii( nKey ) CLASS XHBEditor
IF !::lEditAllow .OR. ::nCol > ::nWordWrapCol + 1
RETURN Self
ENDIF
// 2006/JUL/22 - E.F. - IF there is no line into memo add a new one.
IF ::LastRow() == 0
::AddLine( "", .F. )
ENDIF
::nMarkPos := 0
// If I'm past EOL I need to add as much spaces as I need to reach ::nCol
// Always remeber the cursor position is always 1 ahead of buffer
// So adding 1 below - Pritpal Bedi
//
IF ::nCol > ::LineLen( ::nRow ) + 1 // At end of line, add room
::aText[ ::nRow ]:cText += Space( ::nCol - ::LineLen( ::nRow ) )
::lChanged := .T.
ENDIF
// insert char if in insert mode or at end of current line
//
IF ::lInsert .OR. ( ::nCol > ::LineLen( ::nRow ) )
::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, 0, hb_KeyChar( nKey ) )
::lChanged := .T.
ELSE
::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, 1, hb_KeyChar( nKey ) )
::lChanged := .T.
ENDIF
// eventually wordwrap
//
IF ::lWordWrap .AND. ::LineLen( ::nRow ) > ::nWordWrapCol
::SplitLine( ::nRow )
::Right()
ELSE
::RefreshLine()
::Right()
ENDIF
RETURN Self
//-------------------------------------------------------------------//
// Backspace
//
METHOD K_Bs() CLASS XHBEditor
IF !::lEditAllow
::Left()
RETURN Self
ENDIF
// xHarbour extension: If backspace reach first column, move cursor to up
// and go to last column. Allow to continue backspace in
// previous line. Clipper memoedit backspace act only at
// same line.
//
IF ::nCol == 1
IF ( ::lWordWrap )
IF ::nRow > 1 .AND. ::nRow <= ::LastRow()
// 2006/JUL/21 - E.F. - Determine new ::nCol position.
//
::nCol := Min( ::LineLen( ::nRow - 1 ) + 1, ::nWordWrapCol )
::nRow --
// inherit sibling line's soft CR setting.
::aText[ ::nRow ]:lSoftCR := ::aText[ ::nRow + 1 ]:lSoftCR
// remove a SINGLE trailing space, if it exists
IF Right( ::aText[ ::nRow ]:cText, 1 ) == " "
::aText[ ::nRow ]:cText := SubStr( ::aText[ ::nRow ]:cText, 1, ::LineLen( ::nRow ) - 1 )
ENDIF
::aText[ ::nRow ]:cText += ::aText[ ::nRow + 1 ]:cText
::RemoveLine( ::nRow + 1 )
// resplit the line.
IF ::LineLen( ::nRow ) > ::nWordWrapCol
// will also refresh
::SplitLine( ::nRow )
ENDIF
// 2006/JUL/21 - E.F. - Delete the rightmost char and pos the cursor on it.
IF ::LineLen( ::nRow ) >= ::nWordWrapCol
::aText[ ::nRow ]:cText := SubStr( ::aText[ ::nRow ]:cText, 1, ::LineLen( ::nRow ) - 1 ) + " "
::nCol := Min( ::nCol + 1, ::nWordWrapCol + 1 )
ENDIF
IF !Empty( ::aText[ ::nRow ]:cText )
::GotoPos( ::nRow, ::nCol, .T. ) // also refresh
ELSE
::GotoPos( ::nRow, 1, .T. )
ENDIF
ENDIF
ENDIF
// 2006/JUL/19 - E.F. When backspace reach column 1 and the line is
// empty and exist next line, we need set linelen to
// zero and set lSoftCR to true as Clipper does.
//
IF ::nCol == 1 .AND. Empty( ::aText[ ::nRow ]:cText ) .AND. ;
::nRow + 1 <= ::LastRow()
::aText[ ::nRow ]:cText := ""
::aText[ ::nRow ]:lSoftCR := .T.
ENDIF
ELSEIF ::nCol >= ::nFirstCol
// delete previous character
::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol - 1, 1, "" )
::GotoCol( ::nCol - 1 )
::RefreshLine()
ENDIF
::lChanged := .T.
RETURN Self
//-------------------------------------------------------------------//
// Process DEL key
//
METHOD K_Del() CLASS XHBEditor
LOCAL lMerge := .F.
LOCAL nCurRow, nCurCol
IF !::lEditAllow
RETURN Self
ENDIF
IF ::nCol > ::LineLen( ::nRow ) //.and. ::nRow < ::LastRow()
// eventually pad.
//
//IF ::nCol > ::LineLen( ::nRow ) + 1
// ::aText[ ::nRow ]:cText := Padr( ::aText[ ::nRow ]:cText, ::nCol - 1)
//ENDIF
lMerge := .T.
ELSEIF ::nCol <= ::LineLen( ::nRow )
// stuff the character
//
::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, 1, "" )
::lChanged := .T.
// in case of softcr, reparse the paragraph.
//
IF ::aText[ ::nRow ]:lSoftCR == .T.
IF !( Right( ::aText[ ::nRow ]:cText, 1 ) == " " )
::aText[ ::nRow ]:cText += " "
ENDIF
// 2006/JUL/21 - E.F. If current line is empty and cursor is in
// the first column, remove it after del.
//
IF ::IsEmptyLine( ::nRow ) .AND. ::nCol == 1
::RemoveLine( ::nRow )
ENDIF
lMerge := .T.
ELSE
::RefreshLine()
ENDIF
ENDIF
// have we to merge with the next line?
IF lMerge
::lChanged := .T.
nCurRow := ::nRow
nCurCol := ::nCol
// 2006/07/19 - E.F. Merge line only if ::nRow+1 is valid,
// to avoid bound error.
//
IF ::nRow + 1 <= ::LastRow()
// copy the other line
::aText[ ::nRow ]:cText += ::aText[ ::nRow + 1 ]:cText
// copy its softcr setting
::aText[ ::nRow ]:lSoftCr := ::aText[ ::nRow + 1 ]:lSoftCr
// remove it.
::RemoveLine( ::nRow + 1 )
// and finally split it
IF ::LineLen( ::nRow ) > ::nWordWrapCol
::SplitLine( ::nRow )
ENDIF
ENDIF
::GotoPos( nCurRow, nCurCol, .T. )
::RefreshWindow()
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD K_Tab() CLASS XHBEditor
LOCAL lHardCR, i
IF !::lEditAllow
RETURN Self
ENDIF
// insert char if in insert mode or at end of current line
IF ::nCol < ::nWordWrapCol - ::nTabWidth - ::nTabWidth
IF ::lInsert .OR. ( ::nCol == ::LineLen( ::nRow ) )
::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, 0, Space( ::nTabWidth ) )
ENDIF
::lChanged := .T.
::lRightScroll := .F. //prevent auto linewrap
for i := 1 to ::nTabWidth
IF ::nCol < ::nWordWrapCol - ::nTabWidth - ::nTabWidth
::Right()
::RefreshLine()
ELSE
i := ::nTabWidth // end of line, stop it!
ENDIF
next
::lRightScroll := .T.
// wrap lines
IF ::LineLen( ::nRow ) > ::nWordWrapCol
lHardCR := .F. // should already by .F., but just to be safe, and it is a tiny line of code...
IF ::aText[ ::nRow ]:lSoftCR
IF !::aText[ ::nRow+1 ]:lSoftCR // the next line has a hard return, keep it
lHardCR := .T.
ENDIF
IF ::nRow == ::LastRow() - 1 // if next to last line of array, last line MUST have HR
lHardCR := .T.
ENDIF
::aText[ ::nRow ]:cText := ::aText[ ::nRow ]:cText + ::GetLine( ::nRow + 1 )
::RemoveLine( ::nRow + 1 )
::aText[ ::nRow ]:lSoftCR := !lHardCR // .T. if lHardCR == .F.
::SplitLine( ::nRow )
::RefreshWindow()
ENDIF
ENDIF
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD K_Return() CLASS XHBEditor
IF ::LastRow() == 0 .AND. !::lInsert
RETURN Self
ENDIF
IF ::lEditAllow
// 2006/JUL/24 - E.F. - Fixed <Enter> at insert mode.
/*
* IF ::lInsert
* IF ::nRow == ::LastRow()
* IF ::nCol > ::LineLen( ::nRow )
* ::AddLine( "", .F. )
* else
* ::InsertLine( Substr( ::aText[ ::nRow ]:cText, ::nCol ), .F., ::nRow + 1 )
* ENDIF
* ELSEIF ::aText[ ::nRow ]:lSoftCR
* ::aText[ ::nRow + 1 ]:cText := Substr( ::aText[ ::nRow ]:cText, ::nCol ) +" "+ ::aText[ ::nRow + 1 ]:cText
* ::SplitLine( ::nRow + 1 )
* ELSE
* ::InsertLine( Substr( ::aText[ ::nRow ]:cText, ::nCol ), .F., ::nRow + 1 )
* ENDIF
* ::aText[ ::nRow ]:cText := Left( ::aText[ ::nRow ]:cText, ::nCol - 1 )
*
* ELSEIF ::nRow == ::LastRow()
* ::AddLine( "", .F. )
* ENDIF
*/
IF ::lInsert
IF ::LastRow() == 0
::AddLine( "", .F. )
ENDIF
IF ::nRow == ::LastRow() .AND. ;
::nCol > ::LineLen( ::nRow )
::AddLine( "", .F. )
ELSE
::InsertLine( SubStr( ::aText[ ::nRow ]:cText, ::nCol ), .F. , ::nRow + 1 )
ENDIF
::aText[ ::nRow ]:cText := Left( ::aText[ ::nRow ]:cText, ::nCol - 1 )
ELSEIF ::nRow == ::LastRow()
::AddLine( "", .F. )
ENDIF
// the current line should not have softcr.
//
::aText[ ::nRow ]:lSoftCR := .F.
ENDIF
// will also refresh
//
IF ::nRow < ::LastRow()
::GotoPos( ::nRow + 1, 1, .T. )
ENDIF
::lChanged := .T.
RETURN Self
//-------------------------------------------------------------------//
METHOD K_Esc() CLASS XHBEditor
LOCAL cScreenMsg, nCurRow, nCurCol, nCursor, nKey
::lExitEdit := .T.
IF ::lEditAllow .AND. ::lChanged .AND. Set( _SET_SCOREBOARD )
nCurCol := ::Col()
nCurRow := ::Row()
cScreenMsg := SaveScreen( 0, 60, 0, 77 )
nCursor := SetCursor( SC_NORMAL )
@ 0, 60 SAY '(Abort Edit? Y/N)'
nKey := Inkey( 0 )
Inkey()
RestScreen( 0, 60, 0, 77, cScreenMsg )
SetCursor( nCursor )
SetPos( nCurRow, nCurCol )
// 2006/JUL/21 - E.F - Exit only if "Y" is pressed.
//
::lExitEdit := ( Upper( hb_KeyChar( nKey ) ) == "Y" )
ENDIF
IF ::lExitEdit
SetCursor( ::nOrigCursor ) // restore original cursor saved at startup
ENDIF
RETURN Self
//-------------------------------------------------------------------//
//
// Data Retrieval Methods
//
//-------------------------------------------------------------------//
//
// Add a new Line of text at end of current text
//
METHOD AddLine( cLine, lSoftCR ) CLASS XHBEditor
DEFAULT cLine TO ""
DEFAULT lSoftCR TO .F.
AAdd( ::aText, HBTextLine():New( cLine, lSoftCR ) )
RETURN Self
//-------------------------------------------------------------------//
//
// Insert a line of text at a defined row
//
METHOD InsertLine( cLine, lSoftCR, nRow ) CLASS XHBEditor
DEFAULT nRow TO ::nRow
DEFAULT lSoftCR TO .F.
IF nRow > ::LastRow()
IF Len( cLine ) == 0
lSoftCR := .F.
ENDIF
::AddLine( cLine, lSoftCR )
ELSE
hb_AIns( ::aText, nRow, HBTextLine():New( cLine, lSoftCR ), .T. )
ENDIF
RETURN Self
//-------------------------------------------------------------------//
//
// Remove a line of text
//
METHOD RemoveLine( nRow ) CLASS XHBEditor
DEFAULT nRow TO ::nRow
hb_ADel( ::aText, nRow, .T. )
RETURN Self
//-------------------------------------------------------------------//
//
// Return line n of text
//
METHOD GetLine( nRow ) CLASS XHBEditor
DEFAULT nRow TO ::nRow
IF nRow <= ::LastRow() .AND. nRow > 0
IF ::lEditAllow .OR. Empty( ::nTabWidth )
RETURN ::aText[ nRow ]:cText
ELSE
RETURN HB_TabExpand( ::aText[ nRow ]:cText, ::nTabWidth )
ENDIF
ELSE
RETURN ""
ENDIF
RETURN Self
//-------------------------------------------------------------------//
//
// Delete text from cursor to end of line.
//
METHOD DelTextRight( nRow ) CLASS XHBEditor
DEFAULT nRow TO ::nRow
IF !::lEditAllow
RETURN Self
ENDIF
IF nRow > 0 .AND. nRow <= ::LastRow()
::lChanged := .T.
::aText[ nRow ]:cText := Stuff( ::aText[ nRow ]:cText, ::nCol, ::LineLen( nRow ) - ::nCol + 1, "" )
::RefreshLine()
ENDIF
RETURN Self
//-------------------------------------------------------------------//
//
// Delete a word to the right of cursor. <CTRL-T>
//
METHOD DelWordRight() CLASS XHBEditor
LOCAL nCol, nCutCol
LOCAL nSpacesPre, cText
nCutCol := 0
nCol := ::nCol
IF !::lEditAllow
RETURN Self
ENDIF
::lChanged := .T.
nSpacesPre := 0
cText := SubStr( ::aText[ ::nRow ]:cText, nCol )
DO WHILE .T.
IF Left( cText, 1 ) == " " .AND. Len( cText ) > 0
cText := SubStr( cText, 2 )
nSpacesPre++
ELSE
EXIT
ENDIF
ENDDO
DO WHILE nCutCol <= 1 .AND. nCol < ::LineLen( ::nRow ) - 1
nCutCol := At( " ", SubStr( ::aText[ ::nRow ]:cText, nCol ) )
IF nCutCol <= 1 .AND. nCol < ::LineLen( ::nRow ) - 1
nCol ++
ELSEIF nCutCol <= 1 .AND. nCol >= ::LineLen( ::nRow )
nCutCol := Len( SubStr( ::aText[::nRow]:cText, ::nCol, nCol - ::nCol ) )
EXIT
ENDIF
ENDDO
IF nCutCol == 0 .AND. ::LineLen( ::nRow ) >= ::nCol
nCutCol := ::LineLen( ::nRow ) - ::nCol + 1
ENDIF
IF nCutCol > 0
::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, + nSpacesPre + nCutCol, " " )
IF ::lWordWrap .AND. ::aText[ ::nRow ]:lSoftCR
::SplitLine( ::nRow )
ELSE
::aText[::nRow]:lSoftCR := .F.
ENDIF
ENDIF
RETURN Self
//-------------------------------------------------------------------//
// <CTRL-B> behaviour.
//
METHOD ReformParagraph() CLASS XHBEditor
LOCAL nRow
LOCAL cHardCR := hb_eol()
LOCAL cSoftCR := Chr( 141 ) + Chr( 10 )
IF !::lEditAllow
RETURN Self
ENDIF
IF ::LastRow() > 0
::lChanged := .T.
FOR nRow := 1 TO ::LastRow()
::aText[ nRow ]:cText := StrTran( ::aText[ nRow ]:cText, cSoftCR )
::aText[ nRow ]:lSoftCR := .F.
IF At( cHardCR, ::aText[ nRow ]:cText ) != 0
EXIT
ENDIF
NEXT
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD GotoLine( nRow ) CLASS XHBEditor
LOCAL lRefresh := .F.
IF nRow > 0 .AND. nRow <= ::LastRow()
// I need to move cursor if is past requested line number and if requested line is
// inside first screen of text otherwise ::nFirstRow would be wrong
//
IF nRow < ::nFirstRow
::nFirstRow := nRow
lRefresh := .T.
ELSEIF nRow - ::nFirstRow >= ::nNumRows
::nFirstRow := Max( 1, nRow - ::nNumRows + 1 )
lRefresh := .T.
ENDIF
::nRow := Max( 1, Min( nRow, ::LastRow() ) )
IF lRefresh
::RefreshWindow()
ENDIF
::SetPos( ::nTop + nRow - ::nFirstRow, ::nLeft + ::nCol - ::nFirstCol )
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD GotoCol( nCol ) CLASS XHBEditor
IF nCol >= 1
// 2006/JUL/21 E.F. - Clipper allow cursor movement to left/right into
// line, with or without chars.
// Note: ::nWordWrapCol can be different than ::nNumCols if user has
// informed nLineLength > 0.
nCol := Max( 1, Min( nCol, Max(::nNumCols,::nWordWrapCol + 1 ) ) )
// I need to move cursor if is past requested line number and if requested line is
// inside first screen of text otherwise ::nFirstRow would be wrong
//
IF nCol < ::nFirstCol
::nFirstCol := nCol
::RefreshWindow()
ELSEIF nCol - ::nFirstCol >= ::nNumCols
::nFirstCol := Max( 1, nCol - ::nNumCols + 1 )
::RefreshWindow()
ENDIF
::nCol := nCol
::SetPos( ::Row(), ::nLeft + nCol - ::nFirstCol )
ENDIF
RETURN Self
//-------------------------------------------------------------------//
METHOD GotoPos( nRow, nCol, lRefresh ) CLASS XHBEditor
DEFAULT lRefresh TO .F.
DispBegin() // to minimize flicker
IF nRow > 0 .AND. nRow <= ::LastRow()
// I need to move cursor if is past requested line number and if requested line is
// inside first screen of text otherwise ::nFirstRow would be wrong
//
IF nRow < ::nFirstRow
::nFirstRow := nRow
lRefresh := .T.
ELSEIF nRow - ::nFirstRow >= ::nNumRows
::nFirstRow := Max( 1, nRow - ::nNumRows + 1 )
lRefresh := .T.
ENDIF
::nRow := nRow
ENDIF
IF nCol >= 1
// 2006/JUL/21 E.F. - Clipper allow cursor movement to left/right into
// line, with or without chars.
// Note: ::nWordWrapCol can be different than ::nNumCols if user has
// informed nLineLength > 0
nCol := Max( 1, Min( nCol, Max(::nNumCols,::nWordWrapCol + 1 ) ) )
// I need to move cursor if is past requested line number and if requested line is
// inside first screen of text otherwise ::nFirstRow would be wrong
IF nCol < ::nFirstCol
::nFirstCol := nCol // to left scroll
lRefresh := .T.
ELSEIF nCol - ::nFirstCol >= ::nNumCols
::nFirstCol := Max( 1, nCol - ::nNumCols + 1 )
lRefresh := .T.
ENDIF
::nCol := nCol
ENDIF
DispEnd()
IF lRefresh
::RefreshWindow()
ENDIF
::SetPos( ::nTop + nRow - ::nFirstRow, ::nLeft + nCol - ::nFirstCol )
RETURN Self
//-------------------------------------------------------------------//
//
// Rebuild a long line from multiple short ones ( wrapped at soft CR )
//
//-------------------------------------------------------------------//
/*
*STATIC function GetParagraph( oSelf, nRow )
*
* LOCAL cLine := ""
*
* do while oSelf:aText[ nRow ]:lSoftCR
* cLine := cline + oSelf:aText[ nRow ]:cText
* // I don't need to increment nRow since I'm removing lines, ie line n is
* // a different line each time I add it to cLine
* oSelf:RemoveLine( nRow )
* IF Len( cLine ) > 0 .and. !( Right( cLine, 1 ) == " " )
* cLine += " "
* ENDIF
* enddo
*
* // Last line, or only one line
* //
* cLine += oSelf:aText[ nRow ]:cText
* oSelf:RemoveLine( nRow ) // this is where array error occurs IF final line of text is allowed to have :lSoftCR
*
*return cLine
*/
STATIC FUNCTION GetParagraph( oSelf, nRow )
LOCAL cLine := ""
// V@
DO WHILE nRow <= oSelf:LastRow() .AND. ValType( oSelf:aText[ nRow ]:lSoftCR ) == 'L' .AND. oSelf:aText[ nRow ]:lSoftCR
cLine += oSelf:aText[ nRow ]:cText
oSelf:RemoveLine( nRow )
IF oSelf:LastRow() <= 0 // V@
EXIT
ENDIF
//GAD This is not needed and will corrupt long lines that do not have any spaces with wordwrap on.
/* IF Len( cLine ) > 0 .and. !( Right( cLine, 1 ) == " " )
cLine += " "
ENDIF
*/
ENDDO
// Last line, or only one line
//
IF nRow <= oSelf:LastRow() .AND. oSelf:LastRow() > 0 // V@
cLine += oSelf:aText[ nRow ]:cText
oSelf:RemoveLine( nRow ) // this is where array error occurs IF final line of text is allowed to have :lSoftCR
ENDIF
RETURN cLine
//-------------------------------------------------------------------//
//
// If a line of text is longer than nWordWrapCol divides it into multiple lines,
// Used during text editing to reflow a paragraph
//
//-------------------------------------------------------------------//
METHOD SplitLine( nRow ) CLASS XHBEditor
LOCAL nFirstSpace, nCurSpace
LOCAL cLine
LOCAL cSplittedLine
LOCAL nStartRow
LOCAL nPosInWord
// Do something only if Word Wrapping is on
IF .NOT. ::lWordWrap // .OR. ( ::LineLen( nRow ) <= ::nWordWrapCol )
RETURN Self
ENDIF
::lRightScroll := .F. // must be .F. within this Method
// Move cursor to next line if you will move the word which I'm over to next line
// ie, since word wrapping happens at spaces if first space is behind cursor
// special case; if the character(s) at the end of the line are spaces, we must just
// create a blank line.
//
cLine := ::GetLine( nRow )
// count words up to the word containing the cursor.
//
nFirstSpace := 0
nCurSpace := At( " ", cLine )
DO WHILE nCurSpace <= ::nCol .AND. nCurSpace > 0
nFirstSpace := nCurSpace
nCurSpace := hb_At( " ", cLine, nCurSpace + 1 )
ENDDO
// and see at what point in that line the cursor is.
// remember that nFirstSpace is zero based, and pointing to one space
// before the current word.
//
nPosInWord := iif( ::nCol > nFirstSpace, ::nCol - nFirstSpace, 1 )
nStartRow := nRow
cLine := GetParagraph( Self, nRow )
DO WHILE Len( cLine ) >= ::nWordWrapCol
// Added + 1 because it is possible that line ends when there is a space
// next to nWordWrapCol
//
nFirstSpace := ::nWordWrapCol + 1
// Split line at fist space before current position
//
DO WHILE nFirstSpace > 1 .AND. !( cLine[nFirstSpace] == " " )
nFirstSpace --
ENDDO
// If there is a space before beginning of line split there
//
IF nFirstSpace > 1
cSplittedLine := Left( cLine, nFirstSpace )
ELSE
// Changed -- now splits line at the nWordWrapCol when no space! The cursor position is not reliable!
// This avoids error if the line has NO SPACES! Without this modif. code enters infinite loop on wrap
// Note that cursor postioning when wrapping lines that have NO space is funky due to MovetoNextLine() problems
//
// Old method was: else split at current cursor position
// cSplittedLine := Left( cLine, ::nCol - 1 )
// 2006/07/19 - E.F. - Changed cut point at witdh of line to maintain.
// amount of chars same as Clipper.
//
//cSplittedLine := Left( cLine, ::nWordWrapCol )
cSplittedLine := Left( cLine, Min( ::nWordWrapCol + 1,::nNumCols ) )
ENDIF
// A necessity because xHarbour does not insert the SoftCarriage and
// then we are unable to keep trace of where the line break was while
// reformatting
//
//GAD
IF !( Right( cSplittedLine, 1 ) == " " ) .AND. nFirstSpace > 1
// 2006/JUL/21 - E.F. - Added condition to not stay out of max columns.
IF Len( cSplittedLine ) < ::nNumCols
cSplittedLine += " "
ENDIF
ENDIF
// We must not trim the line as split occurs next to a space
//
::InsertLine( cSplittedLine, .T. , nStartRow ++ )
cLine := SubStr( cLine, Len( cSplittedLine ) + 1 )
ENDDO
// insert EVEN an empty row (it will be added at bottom)
// I have to recheck if trim is viable here ???
//
// 2006/JUL/21 - E.F. Only insert a line in any circunstancies.
//
IF nStartRow + 1 <= ::LastRow()
IF ::LineLen( nStartRow + 1 ) == 0 .OR. Len( AllTrim( cLine ) ) > 0
::InsertLine( Trim( cLine ), .F. , nStartRow )
ENDIF
ELSE
::InsertLine( Trim( cLine ), .F. , nStartRow )
ENDIF
// re-count words and see where current word has gone.
cLine := ::GetLine( nRow )
IF Len( cLine ) < ::nCol
nCurSpace := At( " ", cLine )
// stop when word count has matched OR when nCol is passed (all stay in current line).
DO WHILE nCurSpace > 0 .AND. nCurSpace <= ::nCol
nCurSpace := hb_At( " ", cLine, nCurSpace + 1 )
ENDDO
// next line?
IF nCurSpace == 0
nRow ++
//fake border new.
::nFirstCol := 1
::GotoPos( nRow, nPosInWord, .T. )
ELSEIF nCurSpace == ::nCol
nRow ++
::GotoPos( nRow, 1, .T. )
ELSE
::RefreshWindow()
ENDIF
ELSE
::RefreshWindow()
ENDIF
::lRightScroll := .T. // set at beginning of if/endif -- must be .F. in SplitScreen()
RETURN Self
//-------------------------------------------------------------------//
//
// Utility Methods
//
//-------------------------------------------------------------------//
//
// This in an empty method which can be used by classes subclassing HBEditor to be able
// to handle particular keys.
//
//-------------------------------------------------------------------//
METHOD KeyboardHook() CLASS XHBEditor
RETURN Self
//-------------------------------------------------------------------//
//
// There are no more keys to handle. Can I do something for you?
//
METHOD IdleHook() CLASS XHBEditor
RETURN Self
//-------------------------------------------------------------------//
METHOD SetColor( cColorString ) CLASS XHBEditor
LOCAL cOldColor := ::cColorSpec
IF cColorString != NIL
::cColorSpec := cColorString
ENDIF
RETURN cOldColor
//-------------------------------------------------------------------//
METHOD Hilite() CLASS XHBEditor
LOCAL cEnhanced := ""
// Swap CLR_STANDARD and CLR_ENHANCED
cEnhanced += hb_tokenGet( ::cColorSpec, 2, "," ) + ","
cEnhanced += hb_tokenGet( ::cColorSpec, 1, "," )
::SetColor( cEnhanced + Right( ::cColorSpec, Len( ::cColorSpec ) - Len( cEnhanced ) ) )
RETURN Self
//-------------------------------------------------------------------//
METHOD DeHilite() CLASS XHBEditor
LOCAL cStandard := ""
// Swap CLR_STANDARD and CLR_ENHANCED back to their original position inside cColorSpec
cStandard += hb_tokenGet( ::cColorSpec, 2, "," ) + ","
cStandard += hb_tokenGet( ::cColorSpec, 1, "," )
::SetColor( cStandard + Right( ::cColorSpec, Len( ::cColorSpec ) - Len( cStandard ) ) )
RETURN Self
//-------------------------------------------------------------------//
METHOD SetPos( nRow, nCol ) CLASS XHBEditor
DEFAULT nRow to ::nPhysRow
DEFAULT nCol to ::nPhysCol
::nPhysRow := nRow
::nPhysCol := nCol
SetPos( ::nPhysRow, ::nPhysCol )
return ::nPhysRow
//-------------------------------------------------------------------//
//
// Changes lInsert value and insertion / overstrike mode of editor
//
METHOD InsertState( lInsState ) CLASS XHBEditor
// 2006/JUL/22 - E.F. - Insert only in edit mode.
IF ::lEditAllow .AND. HB_ISLOGICAL( lInsState ) .AND. ::lInsert != lInsState
::lInsert := lInsState
// Redundant, but useful if ::lInsert is used as class DATA
SET( _SET_INSERT, lInsState )
IF lInsState
SetCursor( SC_INSERT )
ELSE
SetCursor( SC_NORMAL )
ENDIF
::DisplayInsert( lInsState )
ENDIF
RETURN Self
//-------------------------------------------------------------------//
// 2006/JUL/15 - E.F. - Display "<insert>" message
//
METHOD DisplayInsert( lInsert ) CLASS XHBEditor
LOCAL nCurRow, nCurCol, nCursor
IF SET( _SET_SCOREBOARD )
nCurCol := Col()
nCurRow := Row()
nCursor := SetCursor( SC_NONE )
IF lInsert
IF ::cInsLabel == NIL
::cInsLabel := SaveScreen( 0, 60, 0, 67 )
ENDIF
@ 0, 60 SAY "<insert>"
ELSE
IF ::cInsLabel != NIL
RestScreen( 0, 60, 0, 67, ::cInsLabel )
::cInsLabel := NIL
ENDIF
ENDIF
SetCursor( nCursor )
SetPos( nCurRow, nCurCol )
ENDIF
RETURN NIL
//-------------------------------------------------------------------//
//
// Converts an array of text lines to a String
//
METHOD GetText( lSoftCr ) CLASS XHBEditor
LOCAL cString := "", cSoft := ""
LOCAL cEOL := hb_eol()
DEFAULT lSoftCr TO .F.
IF ::LastRow() > 0
IF lSoftCr
cSoft := Chr( 141 ) + Chr( 10 )
ENDIF
IF ::lWordWrap
AEval( ::aText, {| cItem | cString += cItem:cText + iif( cItem:lSoftCR, cSoft, cEOL ) }, , ::LastRow() - 1 )
ELSE
AEval( ::aText, {| cItem | cString += cItem:cText + cEOL }, , ::LastRow() - 1 )
ENDIF
// Last line does not need a cEOL delimiter
cString += ::aText[ ::LastRow() ]:cText
ENDIF
RETURN cString
//-------------------------------------------------------------------//
//
// Returns the text selection in a string
//
METHOD GetTextSelection( lSoftCr ) CLASS XHBEditor
LOCAL cString := ""
LOCAL cSoft := ""
LOCAL cEOL := hb_eol()
LOCAL nRowSelStart
LOCAL nRowSelEnd
LOCAL nI
DEFAULT lSoftCr TO .F.
IF !::lSelActive
RETURN cString
ENDIF
IF lSoftCr
cSoft := Chr( 141 ) + Chr( 10 )
ENDIF
IF ::nRowSelStart > 0 .AND. ::nRowSelEnd > 0
IF ::nRowSelStart > ::nRowSelEnd
nRowSelStart := ::nRowSelEnd
nRowSelEnd := ::nRowSelStart
ELSE
nRowSelStart := ::nRowSelStart
nRowSelEnd := ::nRowSelEnd
ENDIF
FOR nI := nRowSelStart TO nRowSelEnd
cString += ::aText[ nI ]:cText + iif( ::lWordWrap .AND. ::aText[ nI ]:lSoftCR, cSoft, cEOL )
NEXT
ENDIF
IF ::nColSelStart > 0 .AND. ::nColSelEnd > 0
cString += SubStr( ::aText[ ::nRow ]:cText, ::nColSelStart, ::nColSelEnd - ::nColSelStart + 1 ) + iif( ::lWordWrap .AND. ::aText[ ::nRow ]:lSoftCR, cSoft, cEOL )
ENDIF
RETURN cString
//-------------------------------------------------------------------//
//
// Set current selection
//
METHOD SetTextSelection( cAction, nCount ) CLASS XHBEditor
IF !::lSelActive
::lSelActive := .T.
IF cAction == "ALL"
::nRowSelStart := 1
::nRowSelEnd := ::LastRow()
::nColSelStart := ::nColSelEnd := 0
::RefreshWindow()
ELSEIF cAction == "ROW"
::GotoCol( 1 )
IF nCount > 0 // Shift-Down
::nRowSelStart := ::nRow
::nRowSelEnd := ::nRowSelStart
::RefreshLine()
IF ::nRow < ::LastRow()
::GotoLine( ::nRow + 1 )
ENDIF
ELSEIF nCount < 0 // Shift-UP
IF ::nRow > 1
::GotoLine( ::nRow - 1 )
ENDIF
::nRowSelStart := ::nRow
::nRowSelEnd := ::nRowSelStart
::RefreshLine()
ENDIF
ELSEIF cAction == "COL"
IF nCount > 0 // Shift Right
IF ::nCol < ::nWordWrapCol + 1
::GotoCol( ::nCol + 1 )
IF ::nColSelStart == 0
::nColSelRow := ::nRow
::nColSelStart := Max( 1, ::nCol - 1 )
ENDIF
::nColSelEnd := Max( 1, ::nCol - 1 )
::RefreshLine( .T. )
ENDIF
ELSEIF nCount < 0 // Shift Left
IF ::nCol > 1
IF ::nColSelStart = 0 .AND. ::nColSelEnd = 0
::nColSelEnd := ::nColSelStart := ::nCol
ENDIF
::GotoCol( ::nCol - 1 )
::nColSelEnd := Max( ::nColSelEnd, ::nCol )
::nColSelRow := ::nRow
::nColSelStart := ::nCol
ELSE
::nColSelRow := ::nColSelStart := ::nColSelEnd := 0
::lSelActive := .F.
ENDIF
::RefreshLine( .T. )
ENDIF
ELSEIF cAction == "END"
IF ::nColSelStart == 0
::nColSelRow := ::nRow
::nColSelStart := Max( 1, ::nCol - 1 )
ENDIF
::End()
::nColSelEnd := Max( 1, ::nCol - 1 )
::RefreshLine( .T. )
ELSEIF cAction == "HOME"
::nColSelRow := ::nRow
::nColSelEnd := ::nCol
::nColSelStart := 1
::GotoCol( 1 )
::RefreshLine( .T. )
ENDIF
ELSE
IF cAction == "ALL"
::nRowSelStart := 1
::nRowSelEnd := ::LastRow()
::nColSelStart := ::nColSelEnd := 0
::RefreshWindow()
ELSEIF cAction == "ROW"
IF nCount > 0 // Shift-Down
IF ::nRowSelStart == 0
::nRowSelStart := ::nRow
ELSEIF ::nRowSelEnd > ::nRow
::Clrtextselection()
::lSelActive := .T.
::nRowSelEnd := ::nRowSelStart := ::nRow - 1
ENDIF
IF ::nRow >= ::nRowSelStart .AND. ::nRow <= ::nRowSelEnd
IF ::nColSelStart > 0 .AND. ::nColSelEnd > 0
::nRowSelStart := ::nRow
ELSE
::nRowSelStart := ::nRow + 1
ENDIF
ELSE
::nRowSelEnd := ::nRow
ENDIF
::nColSelStart := ::nColSelEnd := 0
IF ::nRowSelEnd == ::LastRow()
//::nRowSelEnd := ::LastRow()-1
/* 2006/SEP/17 - E.F. - At this point we need add a new line
to be able to select the last row.
*/
IF !Empty( ::aText[ ::nRowSelEnd ]:cText )
::AddLine()
ELSE
::nRowSelEnd := ::LastRow() - 1
ENDIF
ENDIF
::RefreshLine()
IF ::nRow < ::LastRow()
::GotoLine( ::nRow + 1 )
ENDIF
ELSEIF nCount < 0 // Shift-Up
// if columns was already selected before...
//
IF ( ::nRowSelStart == 0 .AND. ::nRowSelEnd == 0 ) .OR. ;
( ::nRowSelEnd - ::nRowSelStart == 1 .AND. ;
::nColSelStart > 0 .AND. ::nColSelEnd > 0 )
::nColSelStart := ::nColSelEnd := 0
::nRowSelStart := ::nRow
::nRowSelEnd := ::nRow
::RefreshLine()
ELSEIF ::nRowSelEnd - ::nRowSelStart > 1 .AND. ;
::nColSelStart > 0 .AND. ::nColSelEnd > 0
::Clrtextselection()
::lSelActive := .T.
::nRowSelEnd := ::nRow - 1
::nRowSelStart := ::nRowSelEnd
::RefreshLine()
ENDIF
IF ::nRow > 1
::GotoLine( ::nRow - 1 )
ENDIF
IF ::nRow >= ::nRowSelStart .AND. ::nRow <= ::nRowSelEnd
IF ::nRowSelEnd - ::nRowSelStart > 0
::nRowSelEnd := ::nRow - 1
ELSE
::nRowSelEnd := ::nRow
ENDIF
ELSE
::nRowSelStart := ::nRow
ENDIF
IF ::nRow == 1 .AND. ::nRowSelStart == 1 .AND. ::nRowSelEnd == 1
::Clrtextselection()
ENDIF
::RefreshLine()
ENDIF
ELSEIF cAction == "COL"
IF nCount > 0 // Shift-Right
IF ::nCol < ::nWordWrapCol + 1
::GotoCol( ::nCol + 1 )
IF ::nColSelStart == 0
::nColSelRow := ::nRow
::nColSelStart := Max( 1, ::nCol - 1 )
ENDIF
::nColSelEnd := Max( ::nColSelStart, ::nCol - 1 )
IF ::nColSelStart == ::nColSelEnd
::nColSelStart := ::nColSelEnd := Max( 1, ::nCol - 1 )
::nColSelRow := 0
ENDIF
::RefreshLine( .T. )
ENDIF
ELSEIF nCount < 0 // Shift-Left
IF ::nCol > 1
::GotoCol( ::nCol - 1 )
IF ::nColSelEnd == 0
::nColSelRow := ::nRow
::nColSelEnd := Max( ::nColSelEnd, ::nCol )
ENDIF
IF ::nColSelStart <= ::nCol - 1
::nColSelEnd := Min( ::nColSelEnd, ::nCol - 1 )
ELSE
::nColSelStart := Max( 1, ::nCol )
ENDIF
IF ::nCol = 1 .AND. ::nColSelStart == ::nColSelEnd
::lSelActive := .F.
ENDIF
::RefreshLine( .T. )
ELSE
IF ::nColSelEnd == ::nColSelStart
::nColSelStart := ::nColSelEnd := 0
::nColSelRow := 0
ENDIF
::lSelActive := .F.
ENDIF
ENDIF
ELSEIF cAction == "END"
IF ::nColSelStart == 0
::nColSelStart := Max( 1, ::nCol - 1 )
ENDIF
::End()
::nColSelEnd := Max( 1, ::nCol - 1 )
::RefreshLine( .T. )
ELSEIF cAction == "HOME"
::nColSelEnd := ::nCol
::nColSelStart := 1
::GotoCol( 1 )
::RefreshLine( .T. )
ENDIF
ENDIF
RETURN nil
//-------------------------------------------------------------------//
//
// Clear current selection
//
METHOD ClrTextSelection() CLASS XHBEditor
IF ::lSelActive
::lSelActive := .F.
::nRowSelStart := ::nRowSelEnd := 0
::nColSelRow := 0
::nColSelStart := ::nColSelEnd := 0
::RefreshWindow()
ENDIF
RETURN nil
METHOD DelText() CLASS XHBEditor
IF !::lEditAllow
RETURN self
ENDIF
// 2006/SEP/17 - E.F. - changed to ::top() to avoid cursor out of bound.
//::Gotop()
::Top()
::aText := {}
// 2006/JUL/22 - E.F. - There is no need to add line here.
// See K_ASCII() method.
// AAdd( ::aText, HBTextLine():New() )
::lChanged := .T.
::RefreshWindow()
RETURN Self
METHOD DelTextSelection() CLASS XHBEditor
LOCAL nRowSelStart
LOCAL nRowSelEnd
LOCAL nI
LOCAL cText
IF !::lEditAllow
RETURN Self
ENDIF
IF ::lSelActive
// if only rows are selected
IF ::nRowSelStart > 0 .AND. ::nRowSelEnd > 0
IF ::nRowSelStart > ::nRowSelEnd
nRowSelStart := ::nRowSelEnd
nRowSelEnd := ::nRowSelStart
ELSE
nRowSelStart := ::nRowSelStart
nRowSelEnd := ::nRowSelEnd
ENDIF
::lChanged := .T.
for nI := nRowSelStart TO nRowSelEnd
::RemoveLine( nRowSelStart )
next
::nRow := nRowSelStart
IF Empty( ::aText )
::DelText()
ELSE
::GoToPos( Max( 1,nRowSelStart ) , 1 )
ENDIF
::ClrTextSelection()
ELSE
IF ::nColSelStart > 0 .AND. ::nColSelEnd > 0
// IF empty( nRowSelStart )
// nRowSelStart := ::nColSelRow
// ENDIF
cText := ::aText[ ::nRow ]:cText
::aText[::nRow]:cText := Stuff( cText, ::nColSelStart, ::nColSelEnd - ::nColSelStart + 1, "" )
::RefreshLine()
::GoToPos( ::nRow, Max( 1,::nColSelStart ) )
::nColSelStart := ::nColSelEnd := 0
::lChanged := .T.
IF Empty( ::aText )
::DelText()
ENDIF
ENDIF
ENDIF
// 3/03/2008 8:26a.m. added next 4 lines to fix array out of bounds RTL
IF ::nRow > ::LastRow()
::Addline( "", .F. )
::GoBottom()
ENDIF
ENDIF
RETURN Self
METHOD AddText( cString, lAtPos ) CLASS XHBEditor
LOCAL aTmpText
LOCAL nLines
LOCAL i
LOCAL nAtRow
LOCAL lSaveIns
IF !::lEditAllow
RETURN Self
ENDIF
IF !Empty( cString )
aTmpText := Text2Array( cString, iif( ::lWordWrap, ::nNumCols, nil ) )
nLines := Len( aTmpText )
nAtRow := ::nRow
lSaveIns := ::lInsert
IF !lSaveIns
::InsertState( .T. )
ENDIF
DEFAULT lAtPos TO .F.
IF !lAtPos .OR. ( nAtRow > ::LastRow() )
for i := 1 TO nLines
AAdd( ::aText, aTmpText[ i ] )
next
ELSE
nAtRow --
for i := 1 TO nLines
hb_AIns( ::aText, nAtRow + i, aTmpText[ i ], .T. )
next
IF nLines > 0
// ::RemoveLine(nAtRow+nLines)
ENDIF
ENDIF
IF !lSaveIns
::InsertState( .F. )
ENDIF
::lChanged := .T.
::RefreshWindow()
ENDIF
RETURN Self
METHOD GetTextIndex() CLASS XHBEditor
LOCAL nPos := 0
LOCAL oItem, nCount
LOCAL nEol := Len( hb_eol() )
// Using outer IF strategy to be more fast
IF ::lWordWrap
FOR nCount := 1 TO ::nRow - 1
oItem := ::aText[ nCount ]
nPos += iif( oItem:lSoftCR, 0 , nEol ) + Len( oItem:cText )
NEXT
ELSE
FOR nCount := 1 TO ::nRow - 1
oItem := ::aText[ nCount ]
nPos += Len( oItem:cText ) + nEol
NEXT
ENDIF
nPos += ::nCol
RETURN nPos
//-------------------------------------------------------------------//
METHOD LoadText( cString ) CLASS XHBEditor
::aText := Text2Array( cString, iif( ::lWordWrap, ::nNumCols, nil ) )
IF ::LastRow() == 0
AAdd( ::aText, HBTextLine():New() )
ENDIF
::lChanged := .F.
::GoTop()
RETURN Self
//-------------------------------------------------------------------//
METHOD LoadFile( cFileName ) CLASS XHBEditor
LOCAL cString := ""
IF File( cFileName )
::cFile := cFileName
cString := MemoRead( cFileName )
ENDIF
::aText := Text2Array( cString, iif( ::lWordWrap, ::nNumCols, nil ) )
IF ::LastRow() == 0
AAdd( ::aText, HBTextLine():New() )
ENDIF
::lChanged := .F.
::GoTop()
RETURN Self
//-------------------------------------------------------------------//
//
// Saves file being edited, if there is no file name does nothing, returns .T. if OK
//
METHOD SaveFile() CLASS XHBEditor
LOCAL cString
IF !Empty( ::cFile )
cString := ::GetText()
::lChanged := !MemoWrit( ::cFile, cString )
RETURN !::lChanged
ENDIF
RETURN .F.
//-------------------------------------------------------------------//
//
// Utility Functions
//
//-------------------------------------------------------------------//
//
// Returns EOL char ( be it either CR or LF or both )
//
STATIC FUNCTION WhichEOL( cString )
LOCAL nCRPos := At( Chr( 13 ), cString )
LOCAL nLFPos := At( Chr( 10 ), cString )
IF nCRPos > 0 .AND. nLFPos == 0
RETURN Chr( 13 )
ELSEIF nCRPos == 0 .AND. nLFPos > 0
RETURN Chr( 10 )
ELSEIF nCRPos > 0 .AND. nLFPos == nCRPos + 1
RETURN Chr( 13 ) + Chr( 10 )
ENDIF
RETURN hb_eol()
//-------------------------------------------------------------------//
//
// Converts a string to an array of strings splitting input string at EOL boundaries
//
STATIC FUNCTION Text2Array( cString, nWordWrapCol )
LOCAL cLine
LOCAL aArray
LOCAL cEOL
LOCAL nEOLLen
LOCAL nRetLen
LOCAL ncSLen
LOCAL nFirstSpace
LOCAL cSplittedLine
LOCAL nTokPos := 0
LOCAL lTokenized := .F.
LOCAL cSoftCR := Chr( 141 ) + Chr( 10 )
// 2005/JUL/19 - E.F. - SoftCR must be removed before convert string to
// array. It will be treated by HBEditor.
IF cSoftCR $ cString
cString := StrTran( cString, cSoftCR )
ENDIF
aArray := {}
cEOL := WhichEOL( cString )
nEOLLen := Len( cEOL )
// hb_tokenPtr() needs that string to be tokenized be terminated with a token delimiter
IF ! Right( cString, Len( cEOL ) ) == cEOL
cString += cEOL
//GAD so we don't add a blank line by accident at the end of this.
lTokenized := .T.
ENDIF
nRetLen := 0
ncSLen := Len( cString )
// If cString starts with EOL delimiters I have to add empty lines since hb_tokenPtr()
// gives back _next_ token and would skip these first EOL delimiters
DO WHILE SubStr( cString, nTokPos + 1, nEOLLen ) == cEOL
AAdd( aArray, HBTextLine():New( cLine, .F. ) )
nTokPos += nEOLLen
nRetLen += nEOLLen
ENDDO
DO WHILE nRetLen < ncSLen
/* TOFIX: Note that hb_tokenGet() is not able to cope with delimiters longer than one char */
// Dos - OS/2 - Windows have CRLF as EOL
IF nEOLLen > 1
cLine := StrTran( hb_tokenPtr( @cString, @nTokPos, cEOL ), SubStr( cEOL, 2 ) )
ELSE
cLine := hb_tokenPtr( @cString, @nTokPos, cEOL )
ENDIF
nRetLen += Len( cLine ) + nEOLLen
IF HB_ISNUMERIC( nWordWrapCol ) .AND. Len( cLine ) > nWordWrapCol
DO WHILE .T.
// Split line at nWordWrapCol boundary
IF Len( cLine ) > nWordWrapCol
nFirstSpace := RAt( " ", Left( cLine, nWordWrapCol + 1 ) )
IF nFirstSpace > 1
cSplittedLine := Left( cLine, nFirstSpace )
cLine := SubStr( cLine, nFirstSpace + 1 )
ELSE
cSplittedLine := Left( cLine, nWordWrapCol )
// 2006/07/19 - E.F. Changed cut point of second split.
//cLine := SubStr( cLine, nWordWrapCol + 1 )
cLine := SubStr( cLine, Len( cSplittedLine ) + 1 )
ENDIF
AAdd( aArray, HBTextLine():New( cSplittedLine, .T. ) )
ELSE
// remainder of line is shorter than split point
// 2006/JUL/21 - E.F. Only add a new line if cLine is not empty.
//
IF Len( cLine ) > 0
AAdd( aArray, HBTextLine():New( cLine, .F. ) )
ENDIF
// Done.
EXIT
ENDIF
ENDDO
ELSE
AAdd( aArray, HBTextLine():New( cLine, .F. ) )
ENDIF
ENDDO
//If string ends with EOL delimeters we have to add it here.
IF !lTokenized .AND. Right( cString, nEOLLen ) == cEOL
AAdd( aArray, HBTextLine():New( , .F. ) )
ENDIF
RETURN aArray
//-------------------------------------------------------------------//
//
// if editing isn't allowed we enter this loop which
// handles only movement keys and discards all the others
//
METHOD BrowseText( nPassedKey, lHandleOneKey ) CLASS XHBEditor
LOCAL nKey, bKeyBlock
DEFAULT lHandleOneKey TO .F.
DO WHILE ! ::lExitEdit
// If I haven't been called with a key already preset, evaluate this key and then exit
IF nPassedKey == NIL
IF NextKey() == 0
::IdleHook()
ENDIF
nKey := Inkey( 0 )
ELSE
nKey := nPassedKey
ENDIF
IF ( bKeyBlock := SetKey( nKey ) ) != NIL
Eval( bKeyBlock, ::ProcName, ::ProcLine, "", Self )
LOOP
ENDIF
// ******* modified to add exit with K_LEFT when in non-edit mode
IF nKey == K_ESC .OR. nkey == K_CTRL_W
::lExitEdit := .T.
ELSE
::MoveCursor( nKey )
/* 02/09/2004 - <maurilio.longo@libero.it>
If I'm on a readonly editor don't call KeyboardHook() because
it calls HandleUserKey() which calls Edit() which sees this is
a readonly editor and calls again BrowseText() which..,
IF ! oSelf:MoveCursor( nKey )
oSelf:KeyboardHook( nKey )
ENDIF
*/
ENDIF
// If I want to handle only one key and then exit...
IF lHandleOneKey
EXIT
ENDIF
ENDDO
RETURN nil
//-------------------------------------------------------------------//