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