From 25b2825ee9272561ab5b9199d6401c52acb13c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Czerpak?= Date: Fri, 13 Feb 2015 17:08:09 +0100 Subject: [PATCH] 2015-02-13 17:08 UTC+0100 Przemyslaw Czerpak (druzus/at/poczta.onet.pl) * src/rtl/mlcfunc.c ! fixed MPosToLC() results for position in last line which have to be moved after analyzing rest of line due to word wrapping. * src/rtl/teditor.prg * rewritten whole internal code critical for basic functionality. + resolved the problem with keycode conflicts for GTs which support extended keycodes. + added support missing MemoEdit() editor functionality. ; Number of bugs and mistakes was to big to try to update it. New code is smaller and simpler. I tried to keep compatibility with previous version and added to new version most of "helper" methods which are not used in MemoEdit() at all anyhow I cannot guaranty that all existing code using TEditor will work without modifications, i.e. I had to remove :SetPos() and all logic bound with it because it was to hard for my brain to understand this idea and/or functionality. If someone needs strict compatibility with previous version then he should add to his source code copy of old code. Current code should addressed most of MemoEdit() problems with text formatting and editing reported in the past though probably not all. I have no spare time for precise tests of Cl*pper's MemoEdit(). * src/rtl/memoedit.prg * overload :InsertState() method to show SCOREBOARD message * changed "Abort Edit? (Y/N)" message position to be Clipper compatible * removed redundant keycode comparison * do not ::SetPos HBEditor method - this method has been removed --- ChangeLog.txt | 31 ++ src/rtl/memoedit.prg | 30 +- src/rtl/mlcfunc.c | 62 +-- src/rtl/teditor.prg | 1156 ++++++++++++++++-------------------------- 4 files changed, 521 insertions(+), 758 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 441a292d60..3d0b7e7036 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -10,6 +10,37 @@ * Change, ! Fix, % Optimization, + Addition, - Removal, ; Comment */ +2015-02-13 17:08 UTC+0100 Przemyslaw Czerpak (druzus/at/poczta.onet.pl) + * src/rtl/mlcfunc.c + ! fixed MPosToLC() results for position in last line which have to + be moved after analyzing rest of line due to word wrapping. + + * src/rtl/teditor.prg + * rewritten whole internal code critical for basic functionality. + + resolved the problem with keycode conflicts for GTs which + support extended keycodes. + + added support missing MemoEdit() editor functionality. + ; Number of bugs and mistakes was to big to try to update it. + New code is smaller and simpler. I tried to keep compatibility + with previous version and added to new version most of "helper" + methods which are not used in MemoEdit() at all anyhow I cannot + guaranty that all existing code using TEditor will work without + modifications, i.e. I had to remove :SetPos() and all logic bound + with it because it was to hard for my brain to understand this + idea and/or functionality. + If someone needs strict compatibility with previous version then + he should add to his source code copy of old code. + Current code should addressed most of MemoEdit() problems with + text formatting and editing reported in the past though probably + not all. I have no spare time for precise tests of Cl*pper's + MemoEdit(). + + * src/rtl/memoedit.prg + * overload :InsertState() method to show SCOREBOARD message + * changed "Abort Edit? (Y/N)" message position to be Clipper compatible + * removed redundant keycode comparison + * do not ::SetPos HBEditor method - this method has been removed + 2015-02-11 19:59 UTC+0100 Przemyslaw Czerpak (druzus/at/poczta.onet.pl) * ChangeLog.txt ! typo in issue number diff --git a/src/rtl/memoedit.prg b/src/rtl/memoedit.prg index 5789c15506..6dce6a40f6 100644 --- a/src/rtl/memoedit.prg +++ b/src/rtl/memoedit.prg @@ -69,6 +69,7 @@ CREATE CLASS HBMemoEditor INHERIT HBEditor METHOD xDo( nStatus ) // Calls xUserFunction saving and restoring cursor position and shape METHOD MoveCursor( nKey ) // Redefined to properly managed CTRL-W + METHOD InsertState( lInsState ) // Redefined for _SET_SCOREBOARD messages PROTECTED: @@ -170,16 +171,16 @@ METHOD KeyboardHook( nKey ) CLASS HBMemoEditor ELSEIF nKey == K_ESC IF ::lDirty .AND. Set( _SET_SCOREBOARD ) - cBackScr := SaveScreen( 0, MaxCol() - 18, 0, MaxCol() ) + cBackScr := SaveScreen( 0, MaxCol() - 19, 0, MaxCol() ) nRow := Row() nCol := Col() - hb_DispOutAt( 0, MaxCol() - 18, "Abort Edit? (Y/N)" ) - SetPos( 0, MaxCol() - 1 ) + hb_DispOutAt( 0, MaxCol() - 19, "Abort Edit? (Y/N)" ) + SetPos( 0, MaxCol() - 2 ) nYesNoKey := Inkey( 0 ) - RestScreen( 0, MaxCol() - 18, 0, MaxCol(), cBackScr ) + RestScreen( 0, MaxCol() - 19, 0, MaxCol(), cBackScr ) SetPos( nRow, nCol ) IF Upper( hb_keyChar( nYesNoKey ) ) == "Y" @@ -282,16 +283,14 @@ METHOD xDo( nStatus ) CLASS HBMemoEditor LOCAL xResult := Do( ::xUserFunction, nStatus, ::nRow, ::nCol - 1 ) - ::SetPos( nOldRow, nOldCol ) + SetPos( nOldRow, nOldCol ) SetCursor( nOldCur ) RETURN hb_defaultValue( xResult, ME_DEFAULT ) METHOD MoveCursor( nKey ) CLASS HBMemoEditor - IF nKey == K_CTRL_END .OR. ; - nKey == K_CTRL_W - + IF nKey == K_CTRL_W ::lSaved := .T. ::lExitEdit := .T. ELSE @@ -300,6 +299,18 @@ METHOD MoveCursor( nKey ) CLASS HBMemoEditor RETURN .F. +METHOD InsertState( lInsState ) CLASS HBMemoEditor + + IF HB_ISLOGICAL( lInsState ) .AND. ::lEditAllow + Set( _SET_INSERT, lInsState ) + SetCursor( iif( lInsState, SC_INSERT, SC_NORMAL ) ) + IF SET( _SET_SCOREBOARD ) + hb_dispOutAt( 0, MaxCol() - 19, iif( lInsState, "", " " ) ) + ENDIF + ENDIF + + RETURN Self + /* ------------------------------------------ */ FUNCTION MemoEdit( ; @@ -346,7 +357,8 @@ FUNCTION MemoEdit( ; /* Contrary to what the NG says, any logical value will make it pass through without any editing. */ IF ! HB_ISLOGICAL( xUserFunction ) - nOldCursor := SetCursor( iif( Set( _SET_INSERT ), SC_INSERT, SC_NORMAL ) ) + nOldCursor := SetCursor() + oEd:InsertState( Set( _SET_INSERT ) ) oEd:Edit() IF oEd:Changed() .AND. oEd:Saved() cString := oEd:GetText( .T. ) diff --git a/src/rtl/mlcfunc.c b/src/rtl/mlcfunc.c index ca4b332378..767f8a1e89 100644 --- a/src/rtl/mlcfunc.c +++ b/src/rtl/mlcfunc.c @@ -66,6 +66,7 @@ typedef struct HB_SIZE nLineLength; HB_SIZE nTabSize; HB_BOOL fWordWrap; + HB_BOOL fPos; int iEOLs; PHB_EOL_INFO pEOLs; PHB_CODEPAGE cdp; @@ -73,9 +74,7 @@ typedef struct HB_SIZE nOffset; HB_SIZE nMaxCol; HB_SIZE nMaxPos; - HB_SIZE nLine; HB_SIZE nCol; - HB_SIZE nEOL; HB_EOL_INFO EOL_buffer[ HB_EOL_BUFFER_SIZE ]; } @@ -154,8 +153,8 @@ static HB_BOOL hb_mlInit( PHB_MLC_INFO pMLC, int iParAdd ) pMLC->pszString = hb_parc( 1 ); if( pMLC->pszString && nSize > 0 ) { - pMLC->nOffset = pMLC->nMaxCol = pMLC->nMaxPos = pMLC->nLine = - pMLC->nCol = pMLC->nEOL = 0; + pMLC->nOffset = pMLC->nMaxCol = pMLC->nMaxPos = pMLC->nCol = 0; + pMLC->fPos = HB_FALSE; pMLC->nLineLength = nSize; pMLC->nLen = hb_parclen( 1 ); @@ -209,17 +208,15 @@ static int hb_mlEol( PHB_MLC_INFO pMLC ) static HB_SIZE hb_mlGetLine( PHB_MLC_INFO pMLC ) { - HB_SIZE nBlankCol = 0, nBlankPos = 0, nLastPos; + HB_SIZE nBlankCol = 0, nBlankPos = 0, nLastCol = 0, nLastPos; int i; - pMLC->nCol = pMLC->nEOL = 0; + pMLC->nCol = 0; - if( pMLC->nOffset >= pMLC->nLen || - ( pMLC->nMaxPos > 0 && pMLC->nOffset >= pMLC->nMaxPos ) ) + if( pMLC->nOffset >= pMLC->nLen ) return HB_FALSE; - while( pMLC->nOffset < pMLC->nLen && - ( pMLC->nMaxPos == 0 || pMLC->nOffset < pMLC->nMaxPos ) ) + while( pMLC->nOffset < pMLC->nLen ) { HB_WCHAR ch; @@ -239,9 +236,8 @@ static HB_SIZE hb_mlGetLine( PHB_MLC_INFO pMLC ) i = hb_mlEol( pMLC ); if( i >= 0 ) { - pMLC->nEOL = pMLC->pEOLs[ i ].nLen; if( pMLC->nMaxCol == 0 ) - pMLC->nOffset += pMLC->nEOL; + pMLC->nOffset += pMLC->pEOLs[ i ].nLen; break; } else if( ! pMLC->fWordWrap && pMLC->nCol >= pMLC->nLineLength ) @@ -256,12 +252,8 @@ static HB_SIZE hb_mlGetLine( PHB_MLC_INFO pMLC ) else ch = pMLC->pszString[ pMLC->nOffset++ ]; - if( ch == ' ' || ch == HB_CHAR_HT ) - { - nBlankCol = pMLC->nCol; - nBlankPos = pMLC->nOffset; - } - + if( pMLC->nOffset <= pMLC->nMaxPos ) + nLastCol = pMLC->nCol; pMLC->nCol += ch == HB_CHAR_HT ? pMLC->nTabSize - ( pMLC->nCol % pMLC->nTabSize ) : 1; @@ -275,27 +267,31 @@ static HB_SIZE hb_mlGetLine( PHB_MLC_INFO pMLC ) { if( pMLC->fWordWrap ) { - if( nBlankCol != 0 ) + if( pMLC->fPos && ( ch == ' ' || ch == HB_CHAR_HT ) ) + break; + else if( nBlankCol != 0 ) { - if( pMLC->nMaxPos ) - pMLC->nCol = nBlankCol + 1; - else - pMLC->nCol = nBlankCol; + pMLC->nCol = nBlankCol; pMLC->nOffset = nBlankPos; } else pMLC->nOffset = nLastPos; } - else if( pMLC->nCol > pMLC->nLineLength ) + else pMLC->nOffset = nLastPos; break; } + if( ch == ' ' || ch == HB_CHAR_HT ) + { + nBlankCol = pMLC->nCol; + nBlankPos = pMLC->nOffset; + } } - if( pMLC->nCol > pMLC->nLineLength ) + if( pMLC->nMaxPos && pMLC->nCol > nLastCol ) + pMLC->nCol = nLastCol; + else if( pMLC->nCol > pMLC->nLineLength ) pMLC->nCol = pMLC->nLineLength; - else if( pMLC->nMaxPos && pMLC->nCol ) - pMLC->nCol--; return HB_TRUE; } @@ -440,6 +436,8 @@ HB_FUNC( MLPOS ) { if( hb_mlInit( &MLC, 1 ) ) { + MLC.fPos = HB_TRUE; + while( --nLine && hb_mlGetLine( &MLC ) ) ; nOffset = MLC.nOffset; @@ -471,6 +469,8 @@ HB_FUNC( MLCTOPOS ) { if( MLC.nLineLength > 4 ) { + MLC.fPos = HB_TRUE; + while( --nLine && hb_mlGetLine( &MLC ) ) ; if( nCol && nLine == 0 ) @@ -512,12 +512,16 @@ HB_FUNC( MPOSTOLC ) nPos += nRest; } MLC.nMaxPos = nPos; - if( MLC.nMaxPos <= MLC.nLen ) + if( MLC.nMaxPos <= MLC.nLen + 1 ) { - while( hb_mlGetLine( &MLC ) ) + for( ;; ) { + HB_SIZE nOffset = MLC.nOffset; + hb_mlGetLine( &MLC ); nCol = MLC.nCol; ++nLine; + if( MLC.nOffset == nOffset || MLC.nOffset >= MLC.nMaxPos ) + break; } } hb_mlExit( &MLC ); diff --git a/src/rtl/teditor.prg b/src/rtl/teditor.prg index 081d9a66c5..34310c6aa1 100644 --- a/src/rtl/teditor.prg +++ b/src/rtl/teditor.prg @@ -3,6 +3,8 @@ * Editor Class (base for MemoEdit(), debugger, etc.) * * Copyright 2000 Maurilio Longo + * Copyright 2015 Przemyslaw Czerpak + * rewritten whole internal code critical for basic functionality. * www - http://harbour-project.org * * This program is free software; you can redistribute it and/or modify @@ -46,8 +48,6 @@ * */ -/* TODO: add support for soft-newlines where missing */ - #include "hbclass.ch" #include "button.ch" @@ -60,12 +60,17 @@ /* TOFIX: Leave this here, until this code is cleaned off of RTEs */ #pragma linenumber=on +#define _REFRESH_NONE 0 +#define _REFRESH_LINE 1 +#define _REFRESH_ALL 2 + + CREATE CLASS HBEditor EXPORTED: METHOD LoadFile( cFileName ) // Load cFileName into active editor - METHOD LoadText( cString ) // Load cString into active editor + METHOD LoadText( cText ) // Load cText 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 @@ -73,17 +78,17 @@ CREATE CLASS HBEditor METHOD RemoveLine( nRow ) // Remove a line of text METHOD GetLine( nRow ) // Return line n of text METHOD LineLen( nRow ) // Return text length of line n - METHOD SplitLine( nRow ) // If a line of text is longer than nWordWrapCol divides it into multiple lines + METHOD ReformParagraph() // Reform paragraph METHOD GotoLine( nRow ) // Put line nRow at cursor position METHOD LineCount() // Returns number of lines in text. METHOD GetText( lSoftCR ) // Returns aText as a string (for MemoEdit()) - METHOD display() // Redraw a window + METHOD Display() // Redraw a window METHOD RefreshLine() // 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 GoTo( nRow, nCol, nRefreshMode ) // Set current Column and Row in Edited Text METHOD MoveCursor( nKey ) // Move cursor inside text / window (needs a movement key) METHOD InsertState( lInsState ) // Changes insert state 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) @@ -97,9 +102,8 @@ CREATE CLASS HBEditor 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() // Same as Cl*pper ones, returns ::nPhysRow - METHOD Col() // Same as Cl*pper ones, returns ::nPhysCol + METHOD Row() // Returns current line position on the screen + METHOD Col() // Returns current column position on the screen METHOD RowPos() // Returns ::nRow METHOD ColPos() // Returns ::nCol value METHOD Saved() // Returns ::lSaved @@ -108,149 +112,118 @@ CREATE CLASS HBEditor METHOD WordWrapCol() // Returns ::nWordWrapCol METHOD hitTest( nMRow, nMCol ) // UI control compatible method - MESSAGE RefreshWindow() METHOD display() // for compatibility + MESSAGE RefreshWindow() METHOD Display() // for compatibility - - 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 New( cText, nTop, nLeft, nBottom, ; // Constructor + nRight, lEditMode, nLineLength, nTabSize, ; + nTextRow, nTextCol, nWndRow, nWndCol ) PROTECTED: VAR cFile AS STRING INIT "" // name of file being edited VAR aText AS ARRAY INIT {} // array with lines of text being edited - VAR naTextLen AS NUMERIC INIT 0 // number of lines of text inside aText. - VAR nTop AS NUMERIC // boundaries of editor window, without box around - VAR nLeft AS NUMERIC - VAR nBottom AS NUMERIC - VAR nRight AS NUMERIC + VAR nTop AS INTEGER // boundaries of editor window, without box around + VAR nLeft AS INTEGER + VAR nBottom AS INTEGER + VAR nRight AS INTEGER - VAR nFirstCol AS NUMERIC INIT 1 // FirstCol/Row of current text visible inside editor window - VAR nFirstRow AS NUMERIC INIT 1 - VAR nRow AS NUMERIC INIT 1 // Cursor position inside aText (nRow) and inside current line of text (nCol) - VAR nCol AS NUMERIC INIT 1 + VAR nFirstCol AS INTEGER // FirstCol/Row of current text visible inside editor window + VAR nFirstRow AS INTEGER + VAR nRow AS INTEGER // Cursor position inside aText (nRow) and inside current line of text (nCol) + VAR nCol AS INTEGER - VAR nPhysRow AS NUMERIC INIT 0 // Hardware cursor position, I cannot rely on Row()/Col() because I could be inside another - VAR nPhysCol AS NUMERIC 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() + VAR nNumCols AS INTEGER // How many columns / rows can be displayed inside editor window + VAR nNumRows AS INTEGER - VAR nNumCols AS NUMERIC INIT 1 // How many columns / rows can be displayed inside editor window - VAR nNumRows AS NUMERIC INIT 1 - - VAR nTabWidth AS NUMERIC INIT 4 // Size of Tab chars + VAR nTabWidth AS INTEGER INIT 4 // Size of Tab chars VAR lEditAllow AS LOGICAL INIT .T. // Are changes to text allowed? VAR lSaved AS LOGICAL INIT .F. // True if user exited editor with K_CTRL_W VAR lWordWrap AS LOGICAL INIT .F. // True if word wrapping is active - VAR nWordWrapCol AS NUMERIC INIT 0 // At which column word wrapping occurs + VAR nWordWrapCol AS INTEGER INIT 0 // At which column word wrapping occurs VAR lDirty AS LOGICAL INIT .F. // .T. if there are changes not saved VAR lExitEdit AS LOGICAL INIT .F. // .T. if user requested to end Edit() method VAR cColorSpec AS CHARACTER // Color string used for screen writes - METHOD GetParagraph( nRow ) METHOD BrowseText( nPassedKey ) ENDCLASS -/* -------------------------------------------- */ + +METHOD New( cText, nTop, nLeft, nBottom, nRight, lEditMode, nLineLength, ; + nTabSize, nTextRow, nTextCol, nWndRow, nWndCol ) CLASS HBEditor + + ::cColorSpec := SetColor() + + ::lEditAllow := hb_defaultValue( lEditMode, .T. ) + + IF HB_ISNUMERIC( nLineLength ) .AND. nLineLength >= 1 + ::lWordWrap := .T. + ::nWordWrapCol := nLineLength + ENDIF + + IF HB_ISNUMERIC( nTabSize ) .AND. nTabSize >= 1 + ::nTabWidth := Max( nTabSize, 2 ) + ENDIF + + ::LoadText( hb_defaultValue( cText, "" ) ) + ::InsertState( Set( _SET_INSERT ) ) + + ::nRow := hb_defaultValue( nTextRow, 1 ) + ::nCol := hb_defaultValue( nTextCol, 0 ) + 1 + ::nFirstRow := ::nRow - hb_defaultValue( nWndRow, 0 ) + ::nFirstCol := ::nCol - hb_defaultValue( nWndCol, 0 ) + ::Resize( hb_defaultValue( nTop, 0 ), ; + hb_defaultValue( nLeft, 0 ), ; + hb_defaultValue( nBottom, MaxRow() ), ; + hb_defaultValue( nRight, MaxCol() ) ) + + RETURN Self // Redefines editor window size and refreshes it METHOD Resize( nTop, nLeft, nBottom, nRight ) CLASS HBEditor // don't change coordinates not given - IF ! HB_ISNUMERIC( nTop ) - nTop := ::nTop + IF HB_ISNUMERIC( nTop ) + ::nTop := nTop ENDIF - IF ! HB_ISNUMERIC( nLeft ) - nLeft := ::nLeft + IF HB_ISNUMERIC( nLeft ) + ::nLeft := nLeft ENDIF - IF ! HB_ISNUMERIC( nBottom ) - nBottom := ::nBottom + IF HB_ISNUMERIC( nBottom ) + ::nBottom := nBottom ENDIF - IF ! HB_ISNUMERIC( nRight ) - nRight := ::nRight + IF HB_ISNUMERIC( nRight ) + ::nRight := nRight ENDIF - ::nTop := nTop - ::nLeft := nLeft - ::nBottom := nBottom - ::nRight := nRight - // How many cols and rows are available ::nNumCols := ::nRight - ::nLeft + 1 ::nNumRows := ::nBottom - ::nTop + 1 - IF ( ::nRow - ::nFirstRow ) > ::nNumRows - // current row is outide the editor window - display it at the top - ::nFirstRow := ::nRow - ENDIF - // FirstCol/Row of current text visible inside editor window - ::nFirstCol := 1 - // Cursor position inside aText (nRow) and inside current line of text (nCol) - ::nCol := 1 - - // Set cursor upper left corner - ::SetPos( ::nTop + ::nRow - ::nFirstRow, ::nLeft ) - - ::display() - - RETURN Self + RETURN ::Goto( ::nRow, ::nCol ) METHOD LoadFile( cFileName ) CLASS HBEditor + RETURN ::LoadText( hb_MemoRead( ::cFile := cFileName ) ) - LOCAL cString - - IF hb_FileExists( cFileName ) - ::cFile := cFileName - cString := hb_MemoRead( cFileName ) - ELSE - cString := "" - ENDIF - - ::aText := Text2Array( cString, iif( ::lWordWrap, ::nNumCols, ), ::nTabWidth ) - ::naTextLen := Len( ::aText ) - - IF ::naTextLen == 0 - AAdd( ::aText, HBTextLine():New() ) - ::naTextLen++ - ENDIF +METHOD LoadText( cText ) CLASS HBEditor + ::aText := Text2Array( cText, iif( ::lWordWrap, ::nWordWrapCol, ), ::nTabWidth ) ::lDirty := .F. - ::MoveCursor( K_CTRL_PGUP ) - RETURN Self - -METHOD LoadText( cString ) CLASS HBEditor - - ::aText := Text2Array( cString, iif( ::lWordWrap, ::nNumCols, ), ::nTabWidth ) - ::naTextLen := Len( ::aText ) - - IF ::naTextLen == 0 - AAdd( ::aText, HBTextLine():New() ) - ::naTextLen++ - ENDIF - - ::lDirty := .F. - ::MoveCursor( K_CTRL_PGUP ) - - RETURN Self + RETURN iif( ::nNumCols > 0, ::GoTo( 1, 1 ), Self ) // Saves file being edited, if there is no file name does nothing, returns .T. if OK METHOD SaveFile() CLASS HBEditor - - IF Empty( ::cFile ) - RETURN .F. - ENDIF - - RETURN ! ::lDirty := ! hb_MemoWrit( ::cFile, ::GetText() ) + RETURN ! Empty( ::cFile ) .AND. ; + ! ::lDirty := ! hb_MemoWrit( ::cFile, ::GetText() ) // Add a new Line of text at end of current text METHOD AddLine( cLine, lSoftCR ) CLASS HBEditor AAdd( ::aText, HBTextLine():New( cLine, lSoftCR ) ) - ::naTextLen++ RETURN Self @@ -258,7 +231,6 @@ METHOD AddLine( cLine, lSoftCR ) CLASS HBEditor METHOD InsertLine( cLine, lSoftCR, nRow ) CLASS HBEditor hb_AIns( ::aText, nRow, HBTextLine():New( cLine, lSoftCR ), .T. ) - ::naTextLen++ RETURN Self @@ -266,190 +238,59 @@ METHOD InsertLine( cLine, lSoftCR, nRow ) CLASS HBEditor METHOD RemoveLine( nRow ) CLASS HBEditor hb_ADel( ::aText, nRow, .T. ) - ::naTextLen-- RETURN Self // Return line n of text METHOD GetLine( nRow ) CLASS HBEditor - RETURN iif( nRow <= ::naTextLen .AND. nRow > 0, ::aText[ nRow ]:cText, "" ) + RETURN iif( nRow >=1 .AND. nRow <= ::LineCount, ::aText[ nRow ]:cText, "" ) // Return text length of line n METHOD LineLen( nRow ) CLASS HBEditor - /* TOFIX: bounds checking as a workaround for RTE in: - HBEDITOR:LINELEN < HBEDITOR:MOVECURSOR < HBEDITOR:SPLITLINE < HBEDITOR:EDIT */ - RETURN iif( nRow >= 1 .AND. nRow <= Len( ::aText ), Len( ::aText[ nRow ]:cText ), 0 ) + RETURN Len( ::GetLine( nRow ) ) // Converts an array of text lines to a String METHOD GetText( lSoftCR ) CLASS HBEditor - LOCAL cString := "" - LOCAL cEOL := hb_eol() - LOCAL cSoftCR + LOCAL cText, cEOL, cSoftCR, oLine - IF ::lWordWrap - cSoftCR := iif( hb_defaultValue( lSoftCR, .F. ), Chr( 141 ) + Chr( 10 ), "" ) - AEval( ::aText, {| cItem | cString += cItem:cText + iif( cItem:lSoftCR, cSoftCR, cEOL ) },, ::naTextLen - 1 ) - ELSE - AEval( ::aText, {| cItem | cString += cItem:cText + cEOL },, ::naTextLen - 1 ) - ENDIF + cEOL := hb_eol() + cSoftCR := iif( ::lWordWrap, iif( hb_defaultValue( lSoftCR, .F. ), ; + Chr( 141 ) + Chr( 10 ), "" ), cEOL ) + cText := "" + FOR EACH oLine IN ::aText + cText += oLine:cText + IF ! oLine:__enumIsLast() + cText += iif( oLine:lSoftCR, cSoftCR, cEOL ) + ENDIF + NEXT - // Last line does not need a cEOL delimiter - cString += ::aText[ ::naTextLen ]:cText - - RETURN cString + RETURN cText METHOD GotoLine( nRow ) CLASS HBEditor - - IF nRow <= ::naTextLen .AND. nRow > 0 - - SWITCH ::nRow - nRow - CASE 1 - ::MoveCursor( K_UP ) // Back one line - EXIT - CASE -1 - ::MoveCursor( K_DOWN ) - EXIT - OTHERWISE - // 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 ::nFirstRow > 1 - IF nRow < ::nNumRows .AND. ( ::nTop + nRow ) < ::Row() - ::SetPos( ::nTop + nRow, ::Col() ) - ENDIF - ELSE - IF nRow <= ::nNumRows - ::SetPos( ::nTop + nRow - 1, ::Col() ) - ENDIF - ENDIF - - ::nRow := nRow - - IF !( ::nFirstRow == 1 .AND. nRow <= ::nNumRows ) - ::nFirstRow := Max( 1, nRow - ( ::Row() - ::nTop ) ) - ENDIF - - ::display() - ENDSWITCH - ENDIF - - RETURN Self + RETURN ::Goto( nRow, ::nCol ) METHOD LineCount() CLASS HBEditor - RETURN ::naTextLen + RETURN Len( ::aText ) -// 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 HBEditor +METHOD Display() CLASS HBEditor - LOCAL nFirstSpace - LOCAL cLine - LOCAL cSplitLine - LOCAL nStartRow - LOCAL nOCol - LOCAL nORow - LOCAL lMoveToNextLine - LOCAL nPosInWord - LOCAL nI - - // Do something only if Word Wrapping is on - IF ::lWordWrap .AND. ::LineLen( nRow ) > ::nWordWrapCol - - nOCol := ::Col() - nORow := ::Row() - - // 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 - lMoveToNextLine := RAt( " ", RTrim( ::GetLine( nRow ) ) ) < ::nCol - nPosInWord := Len( ::GetLine( nRow ) ) - ::nCol - - nStartRow := nRow - cLine := ::GetParagraph( nRow ) - - DO WHILE ! Empty( cLine ) - - IF Len( cLine ) > ::nWordWrapCol - nFirstSpace := ::nWordWrapCol - - // Split line at fist space before current position - DO WHILE !( SubStr( cLine, --nFirstSpace, 1 ) == " " ) .AND. nFirstSpace > 1 - ENDDO - - ::InsertLine( cSplitLine := ; - Left( cLine, iif( nFirstSpace > 1, nFirstSpace, ::nCol - 1 ) ), .T., nStartRow++ ) - ELSE - // remainder of line - ::InsertLine( cSplitLine := cLine, .F., nStartRow++ ) - ENDIF - - cLine := SubStr( cLine, Len( cSplitLine ) + 1 ) - ENDDO - - IF lMoveToNextLine - ::MoveCursor( K_DOWN ) - ::MoveCursor( K_HOME ) - ::MoveCursor( K_CTRL_RIGHT ) - IF nPosInWord > 0 - // from 0 since I have to take into account previous K_CTRL_RIGHT which moves me past end of word - FOR nI := 0 TO nPosInWord - ::MoveCursor( K_LEFT ) - NEXT - ELSE - IF Set( _SET_INSERT ) - ::MoveCursor( K_LEFT ) - ENDIF - ENDIF - ELSE - ::SetPos( nORow, nOCol ) - ENDIF - ::display() - ENDIF - - RETURN Self - -// Redraws a screenfull of text -METHOD display() CLASS HBEditor - - LOCAL i - LOCAL nOCol := ::Col() - LOCAL nORow := ::Row() + LOCAL nRow, nLine, nCount DispBegin() - - FOR i := 0 TO Min( ::nNumRows - 1, ::naTextLen - 1 ) - hb_DispOutAt( ::nTop + i, ::nLeft, PadR( SubStr( ::GetLine( ::nFirstRow + i ), ::nFirstCol, ::nNumCols ), ::nNumCols, " " ), ::LineColor( ::nFirstRow + i ) ) - NEXT - - // Clear rest of editor window (needed when deleting lines of text) - IF ::naTextLen < ::nNumRows - hb_Scroll( ::nTop + ::naTextLen, ::nLeft, ::nBottom, ::nRight,,, ::cColorSpec ) - ENDIF - - ::SetPos( nORow, nOCol ) - + nRow := ::nTop + nLine := ::nFirstRow + nCount := ::nNumRows + DO WHILE --nCount >= 0 + hb_DispOutAt( nRow++, ::nLeft, SubStrPad( ::GetLine( nLine ), ::nFirstCol, ::nNumCols ), ::LineColor( nLine++ ) ) + ENDDO DispEnd() RETURN Self -// Redraws current screen line METHOD RefreshLine() CLASS HBEditor - hb_DispOutAt( ::Row(), ::nLeft, PadR( SubStr( ::GetLine( ::nRow ), ::nFirstCol, ::nNumCols ), ::nNumCols, " " ), ::LineColor( ::nRow ) ) - - RETURN Self - -// Refreshes only one screen column of text (for Left() and Right() movements) -METHOD RefreshColumn() CLASS HBEditor - - LOCAL i - - DispBegin() - - FOR i := 0 TO Min( ::nNumRows - 1, ::naTextLen - 1 ) - hb_DispOutAt( ::nTop + i, ::Col(), SubStr( ::GetLine( ::nFirstRow + i ), ::nCol, 1 ), ::LineColor( ::nFirstRow + i ) ) - NEXT - - DispEnd() + hb_DispOutAt( ::Row(), ::nLeft, SubStrPad( ::GetLine( ::nRow ), ::nFirstCol, ::nNumCols ), ::LineColor( ::nRow ) ) RETURN Self @@ -460,182 +301,149 @@ METHOD LineColor( nRow ) CLASS HBEditor RETURN ::cColorSpec +// Set current column and row in edited text +METHOD GoTo( nRow, nCol, nRefreshMode ) + + LOCAL nFirstRow := ::nFirstRow, nFirstCol := ::nFirstCol + + hb_default( @nRefreshMode, _REFRESH_NONE ) + + IF nRow < 1 + nRow := 1 + ELSEIF nRow > ::LineCount + nRow := ::LineCount + ENDIF + IF nFirstRow < 1 + nFirstRow := 1 + ELSEIF nRow < nFirstRow + nFirstRow := nRow + ELSEIF nRow > nFirstRow + ::nNumRows - 1 + nFirstRow := nRow - ::nNumRows + 1 + ENDIF + + IF nCol == -1 + nCol := ::LineLen( nRow ) + 1 + ENDIF + IF nCol < 1 + nCol := 1 + ELSEIF ::lWordWrap .AND. nCol > ::nWordWrapCol + 1 + nCol := ::nWordWrapCol + 1 + ENDIF + IF nFirstCol < 1 + nFirstCol := 1 + ELSEIF nCol < nFirstCol + nFirstCol := nCol + ELSEIF nCol > nFirstCol + ::nNumCols - 1 + nFirstCol := nCol - ::nNumCols + 1 + ENDIF + + ::nRow := nRow + ::nCol := nCol + + IF nRefreshMode == _REFRESH_ALL .OR. ; + nFirstRow != ::nFirstRow .OR. nFirstCol != ::nFirstCol + + ::nFirstRow := nFirstRow + ::nFirstCol := nFirstCol + ::Display() + ELSEIF nRefreshMode == _REFRESH_LINE + ::RefreshLine() + ENDIF + SetPos( ::Row(), ::Col() ) + + RETURN Self + +// Returns current line position on the screen +METHOD Row() CLASS HBEditor + RETURN ::nTop + ::nRow - ::nFirstRow + +// Returns current column position on the screen +METHOD Col() CLASS HBEditor + RETURN ::nLeft + ::nCol - ::nFirstCol + // Handles cursor movements inside text array METHOD MoveCursor( nKey ) CLASS HBEditor - SWITCH nKey + SWITCH hb_keyStd( nKey ) CASE K_DOWN IF ! ::lEditAllow - DO WHILE ::Row() < ::nBottom .AND. ::nRow < ::naTextLen - ::nRow++ - ::SetPos( ::Row() + 1, ::Col() ) - ENDDO - ENDIF - IF ::Row() == ::nBottom - IF ::nRow < ::naTextLen - hb_Scroll( ::nTop, ::nLeft, ::nBottom, ::nRight, 1,, ::cColorSpec ) - ::nFirstRow++ - ::nRow++ - ::RefreshLine() - ENDIF + ::Goto( ::nFirstRow + ::nNumRows, ::nCol ) ELSE - IF ::nRow < ::naTextLen - ::nRow++ - ::SetPos( ::Row() + 1, ::Col() ) - ENDIF + ::Goto( ::nRow + 1, ::nCol ) ENDIF EXIT CASE K_PGDN - IF ::nRow + ::nNumRows < ::naTextLen - ::nRow += ::nNumRows - IF ::nFirstRow + ::nNumRows > ::naTextLen - ::nFirstRow -= ( ( ::nFirstRow + ::nNumRows ) - ::naTextLen ) + 1 - ELSE - ::nFirstRow += ::nNumRows - ENDIF - ELSE - ::nFirstRow := Max( ::naTextLen - ::nNumRows + 1, 1 ) - ::nRow := ::naTextLen - ::SetPos( Min( ::nTop + ::naTextLen - 1, ::nBottom ), ::Col() ) - ENDIF - ::display() + ::Goto( ::nRow + ::nNumRows - 1, ::nCol ) EXIT CASE K_CTRL_PGDN - ::nRow := ::naTextLen - ::nCol := Max( ::LineLen( ::nRow ) + 1, 1 ) - ::nFirstRow := Max( ::naTextLen - ::nNumRows + 1, 1 ) - ::nFirstCol := Max( ::nCol - ::nNumCols + 1, 1 ) - ::SetPos( Min( ::nTop + ::naTextLen - 1, ::nBottom ), Min( ::nLeft + ::nCol - 1, ::nRight ) ) - ::display() + ::Goto( ::LineCount, -1 ) EXIT CASE K_UP IF ! ::lEditAllow - DO WHILE ::Row() > ::nTop .AND. ::nRow > 1 - ::nRow-- - ::SetPos( ::Row() - 1, ::Col() ) - ENDDO - ENDIF - IF ::Row() == ::nTop - IF ::nRow > 1 - hb_Scroll( ::nTop, ::nLeft, ::nBottom, ::nRight, -1,, ::cColorSpec ) - ::nFirstRow-- - ::nRow-- - ::RefreshLine() - ENDIF + ::Goto( ::nFirstRow - 1, ::nCol ) ELSE - ::nRow-- - ::SetPos( ::Row() - 1, ::Col() ) + ::Goto( ::nRow - 1, ::nCol ) ENDIF EXIT CASE K_PGUP - IF ( ::nRow - ::nNumRows ) > 1 - ::nRow -= ::nNumRows - ::nFirstRow -= ::nNumRows - IF ::nFirstRow < 1 - ::nFirstRow := 1 - ::nRow := 1 - ::SetPos( ::nTop, ::Col() ) - ENDIF - ELSE - ::nFirstRow := 1 - ::nRow := 1 - ::SetPos( ::nTop, ::Col() ) - ENDIF - ::display() + ::Goto( ::nRow - ::nNumRows + 1, ::nCol ) EXIT CASE K_CTRL_PGUP - ::nRow := 1 - ::nCol := 1 - ::nFirstCol := 1 - ::nFirstRow := 1 - ::SetPos( ::nTop, ::nLeft ) - ::display() + ::Goto( 1, 1 ) EXIT CASE K_RIGHT - IF ::Col() == ::nRight - IF ::nCol <= iif( ::lWordWrap, ::nWordWrapCol, ::LineLen( ::nRow ) ) - hb_Scroll( ::nTop, ::nLeft, ::nBottom, ::nRight,, 1, ::cColorSpec ) - ::nFirstCol++ - ::nCol++ - ::RefreshColumn() - ENDIF - ELSE - ::nCol++ - ::SetPos( ::Row(), ::Col() + 1 ) - ENDIF + ::Goto( ::nRow, ::nCol + 1 ) EXIT CASE K_CTRL_RIGHT - // NOTE: should be faster without call to ::GetLine() - DO WHILE ::nCol <= iif( ::lWordWrap, Min( ::nWordWrapCol, ::LineLen( ::nRow ) ), ::LineLen( ::nRow ) ) .AND. !( SubStr( ::aText[ ::nRow ]:cText, ::nCol, 1 ) == " " ) - ::MoveCursor( K_RIGHT ) - ENDDO - DO WHILE ::nCol <= iif( ::lWordWrap, Min( ::nWordWrapCol, ::LineLen( ::nRow ) ), ::LineLen( ::nRow ) ) .AND. SubStr( ::aText[ ::nRow ]:cText, ::nCol, 1 ) == " " - ::MoveCursor( K_RIGHT ) - ENDDO + /* Resolve K_CTRL_B and K_CTRL_RIGHT Cl*pper keycode collision */ + IF nKey != K_CTRL_RIGHT .AND. hb_keyVal( nKey ) != HB_KX_RIGHT + RETURN .F. + ENDIF + ::Goto( ::nRow, NextWord( ::GetLine( ::nRow ), ::nCol ) ) EXIT CASE K_LEFT - IF ::Col() == ::nLeft - IF ::nCol > 1 - hb_Scroll( ::nTop, ::nLeft, ::nBottom, ::nRight,, -1, ::cColorSpec ) - ::nFirstCol-- - ::nCol-- - ::RefreshColumn() - ENDIF - ELSE - ::nCol-- - ::SetPos( ::Row(), ::Col() - 1 ) - ENDIF + CASE K_BS + ::Goto( ::nRow, ::nCol - 1 ) EXIT CASE K_CTRL_LEFT - DO WHILE ::nCol > 1 .AND. !( SubStr( ::aText[ ::nRow ]:cText, ::nCol, 1 ) == " " ) - ::MoveCursor( K_LEFT ) - ENDDO - DO WHILE ::nCol > 1 .AND. SubStr( ::aText[ ::nRow ]:cText, ::nCol, 1 ) == " " - ::MoveCursor( K_LEFT ) - ENDDO + ::Goto( ::nRow, PrevWord( ::GetLine( ::nRow ), ::nCol ) ) EXIT CASE K_HOME - ::nCol := 1 - ::nFirstCol := 1 - ::SetPos( ::Row(), ::nLeft ) - ::display() + ::Goto( ::nRow, 1 ) EXIT CASE K_CTRL_HOME - ::nCol := 1 - ::nFirstCol := 1 - ::nRow -= ( ::Row() - ::nTop ) - ::SetPos( ::nTop, ::nLeft ) - ::display() + ::Goto( ::nFirstRow, 1 ) EXIT CASE K_END - // Empty lines have 0 len - ::nCol := Max( ::LineLen( ::nRow ) + 1, 1 ) - ::nFirstCol := Max( ::nCol - ::nNumCols + 1, 1 ) - ::SetPos( ::Row(), Min( ::nLeft + ::nCol - 1, ::nRight ) ) - ::display() + ::Goto( ::nRow, -1 ) EXIT CASE K_CTRL_END - ::nRow += ::nBottom - ::Row() - IF ::nRow > ::naTextLen - ::nRow := ::naTextLen + /* Resolve K_CTRL_W and K_CTRL_END Cl*pper keycode collision */ + IF nKey != K_CTRL_END .AND. hb_keyVal( nKey ) != HB_KX_END + RETURN .F. ENDIF - ::nCol := Max( ::LineLen( ::nRow ) + 1, 1 ) - ::nFirstCol := Max( ::nCol - ::nNumCols + 1, 1 ) - ::SetPos( Min( ::nTop + ::naTextLen - 1, ::nBottom ), Min( ::nLeft + ::nCol - 1, ::nRight ) ) - ::display() + ::Goto( ::nFirstRow + ::nNumRows - 1, -1 ) + EXIT + + CASE K_ENTER + ::Goto( ::nRow + 1, 1 ) + EXIT + + CASE K_TAB + ::Goto( ::nRow, ::nCol + TabCount( ::nTabWidth, ::nCol ) ) EXIT OTHERWISE @@ -644,194 +452,184 @@ METHOD MoveCursor( nKey ) CLASS HBEditor RETURN .T. -// Changes insert state and insertion / overstrike mode of editor -METHOD InsertState( lInsState ) CLASS HBEditor - - IF HB_ISLOGICAL( lInsState ) - Set( _SET_INSERT, lInsState ) - IF ::lEditAllow - SetCursor( iif( lInsState, SC_INSERT, SC_NORMAL ) ) - ENDIF - ENDIF - - RETURN Self - // Edits text METHOD Edit( nPassedKey ) CLASS HBEditor - LOCAL i - LOCAL nKey + LOCAL nKey, nKeyStd, nPos LOCAL cKey - LOCAL lDelAppend LOCAL bKeyBlock - LOCAL lSingleKeyProcess := .F. // .T. if I have to process passed key and then exit + LOCAL oLine - IF ::lEditAllow - - // 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 ( nKey := Inkey() ) == 0 - ::IdleHook() - nKey := Inkey( 0 ) - ENDIF - ELSE - lSingleKeyProcess := .T. - nKey := nPassedKey - ENDIF - - // 2002-09-03 - maurilio.longo@libero.it - // NOTE: I think this code should only be present on classes derived from TEditor which is - // a low level "editing engine".. For now I leave it here... - IF ( bKeyBlock := SetKey( nKey ) ) != NIL - Eval( bKeyBlock ) - LOOP - ENDIF - - DO CASE - CASE Len( cKey := hb_keyChar( nKey ) ) > 0 - ::lDirty := .T. - // If I'm past EOL I need to add as much spaces as I need to reach ::nCol - IF ::nCol > ::LineLen( ::nRow ) - ::aText[ ::nRow ]:cText += Space( ::nCol - ::LineLen( ::nRow ) ) - ENDIF - // insert char if in insert mode or at end of current line - IF Set( _SET_INSERT ) .OR. ::nCol > ::LineLen( ::nRow ) - ::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, 0, cKey ) - ELSE - ::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, 1, cKey ) - ENDIF - ::MoveCursor( K_RIGHT ) - ::RefreshLine() - ::SplitLine( ::nRow ) - - CASE nKey == K_ENTER - ::lDirty := .T. - IF Set( _SET_INSERT ) .OR. ::nRow == ::naTextLen - IF ::LineLen( ::nRow ) > 0 - // Split current line at cursor position - ::InsertLine( Right( ::aText[ ::nRow ]:cText, ::LineLen( ::nRow ) - ::nCol + 1 ), ::aText[ ::nRow ]:lSoftCR, ::nRow + 1 ) - ::aText[ ::nRow ]:cText := Left( ::aText[ ::nRow ]:cText, ::nCol - 1 ) - IF ::lWordWrap - ::aText[ ::nRow ]:lSoftCR := .F. - ENDIF - ELSE - ::InsertLine( "", .F., ::nRow + 1 ) - ENDIF - ENDIF - ::MoveCursor( K_DOWN ) - ::MoveCursor( K_HOME ) - - CASE nKey == K_INS - Set( _SET_INSERT, ! Set( _SET_INSERT ) ) - SetCursor( iif( Set( _SET_INSERT ), SC_INSERT, SC_NORMAL ) ) - - CASE nKey == K_DEL - // If there is a wordwrapping limit and I'm past it - IF ::lWordWrap .AND. ::nCol > ::nWordWrapCol - ::MoveCursor( K_DOWN ) - ::MoveCursor( K_HOME ) - - ELSE - ::lDirty := .T. - // If I'm on last char of a line and there are more lines, append next line to current one - lDelAppend := ::nCol > ::LineLen( ::nRow ) - ::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, 1, "" ) - IF lDelAppend - IF ::nRow < ::naTextLen - ::aText[ ::nRow ]:cText += ::GetLine( ::nRow + 1 ) - ::RemoveLine( ::nRow + 1 ) - ::SplitLine( ::nRow ) - ::display() - ELSE - ::RefreshLine() - ENDIF - ELSE - ::RefreshLine() - ENDIF - ENDIF - - CASE nKey == K_TAB - // insert char if in insert mode or at end of current line - IF Set( _SET_INSERT ) .OR. ::nCol == ::LineLen( ::nRow ) - ::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, 0, Space( ::nTabWidth ) ) - ::lDirty := .T. - ENDIF - FOR i := 1 TO ::nTabWidth - ::MoveCursor( K_RIGHT ) - NEXT - ::RefreshLine() - - CASE nKey == K_BS - IF ::nCol > 1 - ::lDirty := .T. - // delete previous character - ::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, --::nCol, 1, "" ) - // correct column position for next call to MoveCursor() - ::nCol++ - ::MoveCursor( K_LEFT ) - ::RefreshLine() - ENDIF - - CASE nKey == K_CTRL_Y - ::lDirty := .T. - IF ::naTextLen > 1 - ::RemoveLine( ::nRow ) - // if we have less lines of text than our current position, up one line - IF ::nRow > ::naTextLen - ::nRow := Max( ::nRow - 1, 1 ) - // if our position on screen exceeds text length, up one row - IF ( ::nFirstRow + ::nNumRows - 1 ) > ::naTextLen - ::SetPos( Max( ::Row() - 1, ::nTop ), ::Col() ) - ENDIF - // if first line of displayed text is less than length of text - IF ::nFirstRow > ::naTextLen - ::nFirstRow := Max( ::nFirstRow - 1, 1 ) - ENDIF - ENDIF - ::display() - ELSE - ::aText[ ::nRow ]:cText := "" - ::RefreshLine() - ENDIF - - CASE ::MoveCursor( nKey ) - // if it's a movement key ::MoveCursor() handles it - - CASE nKey == K_CTRL_B - /* TODO: Resolve keycode collision with K_CTRL_RIGHT */ - /* TODO: Implement reform paragraph */ - - CASE nKey == K_CTRL_T - /* TODO: Implement delete word right */ - - CASE nKey == K_ALT_W - /* TOFIX: Not Cl*pper compatible */ - ::lSaved := .T. - ::lExitEdit := .T. - - OTHERWISE - /* 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 */ - ::KeyboardHook( nKey ) - ENDCASE - ENDDO - ELSE - ::BrowseText( nPassedKey ) + IF ! ::lEditAllow + RETURN ::BrowseText( nPassedKey ) ENDIF + DO WHILE ! ::lExitEdit + + IF nPassedKey == NIL + IF ( nKey := Inkey(, hb_bitOr( Set( _SET_EVENTMASK ), HB_INKEY_EXT ) ) ) == 0 + ::IdleHook() + nKey := Inkey( 0, hb_bitOr( Set( _SET_EVENTMASK ), HB_INKEY_EXT ) ) + ENDIF + ELSE + nKey := nPassedKey + ENDIF + nKeyStd := hb_keyStd( nKey ) + + DO CASE + CASE ( bKeyBlock := SetKey( nKeyStd ) ) != NIL + Eval( bKeyBlock ) + + CASE Len( cKey := iif( nKeyStd == K_TAB .AND. Set( _SET_INSERT ), ; + Space( TabCount( ::nTabWidth, ::nCol ) ), ; + hb_keyChar( nKey ) ) ) > 0 + ::lDirty := .T. + oLine := ::aText[ ::nRow ] + IF ::nCol > Len( oLine:cText ) + 1 + oLine:cText += Space( ::nCol - Len( oLine:cText ) - 1 ) + ENDIF + oLine:cText := Stuff( oLine:cText, ::nCol, ; + iif( Set( _SET_INSERT ), 0, 1 ), cKey ) + ::nCol += Len( cKey ) + IF ::lWordWrap .AND. Len( oLine:cText ) > ::nWordWrapCol + ::ReformParagraph() + ELSE + ::GoTo( ::nRow, ::nCol, _REFRESH_LINE ) + ENDIF + + CASE nKeyStd == K_ENTER + IF Set( _SET_INSERT ) + ::lDirty := .T. + oLine := ::aText[ ::nRow ] + ::InsertLine( SubStr( oLine:cText, ::nCol ), oLine:lSoftCR, ::nRow + 1 ) + oLine:cText := Left( oLine:cText, ::nCol - 1 ) + oLine:lSoftCR := .F. + ::Goto( ::nRow + 1, 1, _REFRESH_ALL ) + ELSE + IF ::nRow == ::LineCount + ::lDirty := .T. + ::AddLine() + ENDIF + ::Goto( ::nRow + 1, 1 ) + ENDIF + + CASE nKeyStd == K_INS + ::InsertState( ! Set( _SET_INSERT ) ) + + CASE nKeyStd == K_BS + IF ::nCol > 1 + ::lDirty := .T. + ::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, --::nCol, 1, "" ) + ::GoTo( ::nRow, ::nCol, _REFRESH_LINE ) + ENDIF + + CASE nKeyStd == K_DEL + IF ::nRow < ::LineCount .OR. ::nCol <= ::LineLen( ::nRow ) + ::lDirty := .T. + oLine := ::aText[ ::nRow ] + IF ::nCol <= Len( oLine:cText ) + oLine:cText := Stuff( oLine:cText, ::nCol, 1, "" ) + ::GoTo( ::nRow, ::nCol, _REFRESH_LINE ) + ELSE + IF ::nCol > Len( oLine:cText ) + 1 + oLine:cText += Space( ::nCol - Len( oLine:cText ) - 1 ) + ENDIF + oLine:cText += ::aText[ ::nRow + 1 ]:cText + oLine:lSoftCR := ::aText[ ::nRow + 1 ]:lSoftCR + ::RemoveLine( ::nRow + 1 ) + IF ::lWordWrap .AND. Len( oLine:cText ) > ::nWordWrapCol + ::ReformParagraph() + ELSE + ::GoTo( ::nRow, ::nCol, _REFRESH_ALL ) + ENDIF + ENDIF + ENDIF + + CASE nKeyStd == K_CTRL_Y + ::lDirty := .T. + IF ::nRow == ::LineCount + ::aText[ ::nRow ]:cText := "" + ::GoTo( ::nRow, ::nCol, _REFRESH_LINE ) + ELSE + ::RemoveLine( ::nRow ) + ::GoTo( ::nRow, ::nCol, _REFRESH_ALL ) + ENDIF + + CASE nKeyStd == K_CTRL_T + IF ( nPos := SkipWord( ::GetLine( ::nRow ), ::nCol ) - ::nCol ) > 0 + ::lDirty := .T. + ::aText[ ::nRow ]:cText := Stuff( ::aText[ ::nRow ]:cText, ::nCol, nPos, "" ) + ::GoTo( ::nRow, ::nCol, _REFRESH_LINE ) + ENDIF + + CASE ::MoveCursor( nKey ) + // if it's a movement key ::MoveCursor() handles it + + CASE nKeyStd == K_CTRL_B .OR. nKeyStd == K_ALT_B + /* TOFIX: K_ALT_B is not Cl*pper compatible, added as workaround + for missing in some GTs extended keycodes which are + necessary to resolve K_CTRL_B and K_CTRL_RIGHT keycode + conflict */ + ::ReformParagraph() + + CASE nKeyStd == K_CTRL_W .OR. nKeyStd == K_ALT_W + /* TOFIX: K_ALT_W is not Cl*pper compatible, added as workaround + for missing in some GTs extended keycodes which are + necessary to resolve K_CTRL_W and K_CTRL_END keycode + conflict */ + ::lSaved := .T. + ::lExitEdit := .T. + + OTHERWISE + /* 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 */ + ::KeyboardHook( nKeyStd ) + ENDCASE + + IF nPassedKey != NIL + EXIT + ENDIF + ENDDO + RETURN Self -METHOD ExitState() CLASS HBEditor - RETURN ::lExitEdit +// browse text without editing +METHOD BrowseText( nPassedKey ) CLASS HBEditor -// This in an empty method which can be used by classes subclassing HBEditor to be able -// to handle particular keys. + LOCAL nKey, nKeyStd + LOCAL bKeyBlock + + DO WHILE ! ::lExitEdit + IF nPassedKey == NIL + IF ( nKey := Inkey() ) == 0 + ::IdleHook() + nKey := Inkey( 0 ) + ENDIF + ELSE + nKey := nPassedKey + ENDIF + + nKeyStd := hb_keyStd( nKey ) + IF ( bKeyBlock := SetKey( nKeyStd ) ) != NIL + Eval( bKeyBlock ) + ELSEIF nKeyStd == K_ESC + ::lExitEdit := .T. + ELSEIF ! ::MoveCursor( nKey ) + ::KeyboardHook( nKey ) + ENDIF + + IF nPassedKey != NIL + EXIT + ENDIF + ENDDO + + RETURN Self + +// This method can be overloaded by HBEditor descendants to handle custom keys. METHOD KeyboardHook( nKey ) CLASS HBEditor - IF nKey == K_ESC + IF hb_keyStd( nKey ) == K_ESC ::lSaved := .F. ::lExitEdit := .T. ENDIF @@ -842,6 +640,42 @@ METHOD KeyboardHook( nKey ) CLASS HBEditor METHOD IdleHook() CLASS HBEditor RETURN Self +// Reform paragraph +METHOD ReformParagraph() CLASS HBEditor + + LOCAL lNext := .T. + LOCAL cLine := "" + LOCAL nLine + LOCAL nLines + LOCAL aPos + + DO WHILE lNext .AND. ::nRow <= Len( ::aText ) + cLine += ::aText[ ::nRow ]:cText + lNext := ::aText[ ::nRow ]:lSoftCR + ::RemoveLine( ::nRow ) + ENDDO + nLines := MLCount( cLine, ::nWordWrapCol + 1, ::nTabWidth ) + FOR nLine := 1 TO nLines + ::InsertLine( MemoLine( cLine, ::nWordWrapCol + 1, nLine, ::nTabWidth,,, .F. ), ; + nLine < nLines, ::nRow + nLine - 1 ) + NEXT + aPos := MPosToLC( cLine, ::nWordWrapCol + 1, ::nCol, ::nTabWidth ) + + RETURN ::GoTo( ::nRow + aPos[ 1 ] - 1, aPos[ 2 ] + 1, _REFRESH_ALL ) + +// Changes insert state and insertion / overstrike mode of editor +METHOD InsertState( lInsState ) CLASS HBEditor + + IF HB_ISLOGICAL( lInsState ) .AND. ::lEditAllow + Set( _SET_INSERT, lInsState ) + SetCursor( iif( lInsState, SC_INSERT, SC_NORMAL ) ) + ENDIF + + RETURN Self + +METHOD ExitState() CLASS HBEditor + RETURN ::lExitEdit + METHOD SetColor( cColorString ) CLASS HBEditor LOCAL cOldColor := ::cColorSpec @@ -874,30 +708,6 @@ METHOD DeHilite() CLASS HBEditor RETURN Self -METHOD SetPos( nRow, nCol ) CLASS HBEditor - - IF ! HB_ISNUMERIC( nRow ) - nRow := ::nPhysRow - ENDIF - IF ! HB_ISNUMERIC( nCol ) - nCol := ::nPhysCol - ENDIF - - ::nPhysRow := nRow - ::nPhysCol := nCol - - SetPos( ::nPhysRow, ::nPhysCol ) - - RETURN ::nPhysRow - -// Same as Cl*pper ones, returns ::nPhysRow value -METHOD Row() CLASS HBEditor - RETURN ::nPhysRow - -// Same as Cl*pper ones, returns ::nPhysCol value -METHOD Col() CLASS HBEditor - RETURN ::nPhysCol - METHOD RowPos() CLASS HBEditor RETURN ::nRow @@ -927,150 +737,15 @@ METHOD hitTest( nMRow, nMCol ) CLASS HBEditor RETURN HTNOWHERE -/* -------------------------------------------- */ -// Rebuild a long line from multiple short ones (wrapped at soft CR) -METHOD GetParagraph( nRow ) CLASS HBEditor - - LOCAL cLine := "" - - DO WHILE nRow <= Len( ::aText ) .AND. ::aText[ nRow ]:lSoftCR - cLine += ::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 - ::RemoveLine( nRow ) - ENDDO - - IF nRow <= Len( ::aText ) - // Last line, or only one line - cLine += ::aText[ nRow ]:cText - ::RemoveLine( nRow ) - ENDIF - - RETURN cLine - -// if editing isn't allowed we enter this loop which -// handles only movement keys and discards all the others -METHOD BrowseText( nPassedKey ) CLASS HBEditor - - LOCAL nKey - LOCAL bKeyBlock - - DO WHILE ! ::lExitEdit - - // If I haven't been called with a key already preset, evaluate this key and then exit - IF nPassedKey == NIL - IF ( nKey := Inkey() ) == 0 - ::IdleHook() - nKey := Inkey( 0 ) - ENDIF - ELSE - nKey := nPassedKey - ENDIF - - IF ( bKeyBlock := SetKey( nKey ) ) != NIL - Eval( bKeyBlock ) - LOOP - ENDIF - - IF nKey == K_ESC - ::lExitEdit := .T. - ELSEIF ! ::MoveCursor( nKey ) - ::KeyboardHook( nKey ) - ENDIF - - IF nPassedKey != NIL - EXIT - ENDIF - ENDDO - - RETURN Self - -/* -------------------------------------------- */ - -METHOD New( cString, nTop, nLeft, nBottom, nRight, lEditMode, nLineLength, nTabSize, nTextRow, nTextCol, nWndRow, nWndCol ) CLASS HBEditor - - // is word wrap required? - IF HB_ISNUMERIC( nLineLength ) - ::lWordWrap := .T. - ::nWordWrapCol := nLineLength - ELSE - nLineLength := NIL - ENDIF - - ::aText := Text2Array( hb_defaultValue( cString, "" ), nLineLength, ::nTabWidth ) - ::naTextLen := Len( ::aText ) - - IF ::naTextLen == 0 - AAdd( ::aText, HBTextLine():New() ) - ::naTextLen++ - ENDIF - - // editor window boundaries - ::nTop := nTop := hb_defaultValue( nTop, 0 ) - ::nLeft := nLeft := hb_defaultValue( nLeft, 0 ) - ::nBottom := nBottom := hb_defaultValue( nBottom, MaxRow() ) - ::nRight := nRight := hb_defaultValue( nRight, MaxCol() ) - - ::cColorSpec := SetColor() - - // How many cols and rows are available - ::nNumCols := nRight - nLeft + 1 - ::nNumRows := nBottom - nTop + 1 - - ::lEditAllow := hb_defaultValue( lEditMode, .T. ) - - // how many spaces for each tab? - IF HB_ISNUMERIC( nTabSize ) - ::nTabWidth := nTabSize - ENDIF - - // textrow/col, wndrow/col management - nTextRow := Max( 1, hb_defaultValue( nTextRow, 1 ) ) - nTextCol := Max( 0, hb_defaultValue( nTextCol, 0 ) ) - nWndRow := Max( 0, hb_defaultValue( nWndRow, 0 ) ) - nWndCol := Max( 0, hb_defaultValue( nWndCol, 0 ) ) - - ::nFirstRow := Max( 1, nTextRow - nWndRow ) - ::nFirstCol := nTextCol - nWndCol + 1 - IF ::nFirstCol < 1 - nTextCol -= ::nFirstCol - 1 - ::nFirstCol := 1 - ENDIF - - ::nRow := Max( 1, Min( nTextRow, ::naTextLen ) ) - ::nCol := Max( 1, nTextCol + 1 ) - - // extra sanitization over max bounds - IF ::nFirstRow > ::naTextLen - ::nFirstRow := ::naTextLen - ENDIF - - IF ::nFirstRow + nWndRow > ::naTextLen - DO WHILE ::nFirstRow + --nWndRow > ::naTextLen - ENDDO - ENDIF - - // Empty area of screen which will hold editor window - hb_Scroll( nTop, nLeft, nBottom, nRight ) - - // Set cursor upper left corner - ::SetPos( ::nTop + nWndRow, ::nLeft + nWndCol ) - - RETURN Self - -/* -------------------------------------------- */ - -// Converts a string to an array of strings splitting input string at EOL boundaries -STATIC FUNCTION Text2Array( cString, nWordWrapCol, nTabWidth ) +STATIC FUNCTION Text2Array( cText, nWordWrapCol, nTabWidth ) LOCAL aArray := {} LOCAL cLine LOCAL nLines LOCAL nLine - FOR EACH cLine IN hb_ATokens( cString, .T. ) - + FOR EACH cLine IN hb_ATokens( cText, .T. ) IF nWordWrapCol != NIL .AND. Len( cLine ) > nWordWrapCol nLines := MLCount( cLine, nWordWrapCol + 1, nTabWidth ) FOR nLine := 1 TO nLines @@ -1081,5 +756,46 @@ STATIC FUNCTION Text2Array( cString, nWordWrapCol, nTabWidth ) AAdd( aArray, HBTextLine():New( cLine, .F. ) ) ENDIF NEXT + IF Empty( aArray ) + AAdd( aArray, HBTextLine():New() ) + ENDIF RETURN aArray + +STATIC FUNCTION SubStrPad( cText, nFrom, nLen ) + RETURN PadR( SubStr( cText, nFrom, nLen ), nLen ) + +STATIC FUNCTION TabCount( nTabWidth, nCol ) + RETURN Int( nTabWidth - ( nCol - 1 ) % nTabWidth ) + +STATIC FUNCTION SkipWord( cText, nPos ) + + DO WHILE nPos < Len( cText ) .AND. SubStr( cText, nPos, 1 ) == " " + ++nPos + ENDDO + IF ( nPos := hb_At( " ", cText, nPos ) ) == 0 + nPos := Len( cText ) + 1 + ENDIF + + RETURN nPos + +STATIC FUNCTION NextWord( cText, nPos ) + + IF ( nPos := hb_At( " ", cText, nPos ) ) == 0 + nPos := Len( cText ) + 1 + ELSE + DO WHILE SubStr( cText, ++nPos, 1 ) == " " + ENDDO + ENDIF + + RETURN nPos + +STATIC FUNCTION PrevWord( cText, nPos ) + + DO WHILE nPos > 1 .AND. SubStr( cText, --nPos, 1 ) == " " + ENDDO + DO WHILE nPos > 1 .AND. ! SubStr( cText, nPos - 1, 1 ) == " " + --nPos + ENDDO + + RETURN nPos