570 lines
16 KiB
Plaintext
570 lines
16 KiB
Plaintext
![]() |
{
|
|||
|
Simple text editor
|
|||
|
2009
|
|||
|
March
|
|||
|
5 started; basic screen drawing; cursor movement
|
|||
|
6 InsertChar, InsertCR, DeleteLeft/Right
|
|||
|
6 filename edit field
|
|||
|
6 file load, save
|
|||
|
June
|
|||
|
9 Sphinxified
|
|||
|
30 Page up/down, minor cursor movement improvement
|
|||
|
2010
|
|||
|
January
|
|||
|
29 Bug: Was dropping last character when reading a file that didn't end with CR/LF. Fixed.
|
|||
|
|
|||
|
ENTER(CR) Carriage return
|
|||
|
BKSP Delete to the left
|
|||
|
DEL Delete to the right
|
|||
|
?, ?, ?, ? Cursor movement
|
|||
|
HOME Start of line
|
|||
|
CTRL-HOME Start of file
|
|||
|
END End of line
|
|||
|
CTRL-END End of file
|
|||
|
CTRL-O Open a file
|
|||
|
CTRL-S Save to a file
|
|||
|
CTRL-Q Quit to Sphinx
|
|||
|
PGUP Page up
|
|||
|
PGDN Page down
|
|||
|
|
|||
|
to do:
|
|||
|
block select, cut/copy/paste, find/replace, undo/redo
|
|||
|
|
|||
|
}
|
|||
|
obj
|
|||
|
kb: "isxkb"
|
|||
|
sxtv: "isxtv"
|
|||
|
term: "tvtexted"
|
|||
|
sd: "sxfile"
|
|||
|
|
|||
|
var
|
|||
|
byte x[10]
|
|||
|
|
|||
|
pub Main | e
|
|||
|
if e := \Try
|
|||
|
if e > 0
|
|||
|
sxtv.str( e )
|
|||
|
else
|
|||
|
sxtv.dec( e )
|
|||
|
sxtv.out( 13 )
|
|||
|
term.stop
|
|||
|
sxtv.Enable
|
|||
|
|
|||
|
pub Try | i, key, refresh, blink, t, done
|
|||
|
sxtv.Disable
|
|||
|
term.start( sxtv.GetBasepin )
|
|||
|
|
|||
|
Init
|
|||
|
|
|||
|
Args
|
|||
|
|
|||
|
done~
|
|||
|
blink~
|
|||
|
t := cnt
|
|||
|
repeat
|
|||
|
refresh~
|
|||
|
repeat while key := kb.key
|
|||
|
refresh~~
|
|||
|
blink~
|
|||
|
case key
|
|||
|
$20..$7f: InsertChar( key )
|
|||
|
CR: InsertCR
|
|||
|
BKSP: DeleteLeft
|
|||
|
DEL: DeleteRight
|
|||
|
LARROW: MoveCursorLeft
|
|||
|
RARROW: MoveCursorRight
|
|||
|
UARROW: MoveCursorUp( false )
|
|||
|
DARROW: MoveCursorDown( false )
|
|||
|
HOME: MoveToStartOfLine
|
|||
|
CTRL+HOME: MoveToTop
|
|||
|
END: MoveToEndOfLine
|
|||
|
CTRL+END: MoveToBottom
|
|||
|
CTRL+"o": FileOp( OPEN, FALSE )
|
|||
|
CTRL+"s": FileOp( SAVE, FALSE )
|
|||
|
CTRL+"q": FileOp( SAVE, FALSE )
|
|||
|
done~~
|
|||
|
PGUP: PageCursorUp
|
|||
|
PGDN: PageCursorDown
|
|||
|
other: refresh~
|
|||
|
if cnt - t > 0
|
|||
|
refresh~~
|
|||
|
if refresh
|
|||
|
RefreshScreen( blink ^= 1 )
|
|||
|
t := cnt + clkfreq/10
|
|||
|
until done
|
|||
|
|
|||
|
term.stop
|
|||
|
sxtv.Enable
|
|||
|
|
|||
|
sd.Close
|
|||
|
sd.Open( string("sphinx.bin"), "R" )
|
|||
|
sd.Execute( 0 )
|
|||
|
|
|||
|
pri Args
|
|||
|
if sd.Open( string("args.d8a"), "R" ) == 0
|
|||
|
if sd.ReadByte
|
|||
|
sd.ReadString( @filename, FILENAMEBUFFERSIZE-1 )
|
|||
|
sd.Close
|
|||
|
FileOp( OPEN, true )
|
|||
|
sd.Close
|
|||
|
|
|||
|
con
|
|||
|
VISIBLELINES = 13
|
|||
|
VISIBLECOLUMNS = 40
|
|||
|
con
|
|||
|
CR = $0d
|
|||
|
LARROW = $c0
|
|||
|
RARROW = $c1
|
|||
|
UARROW = $c2
|
|||
|
DARROW = $c3
|
|||
|
HOME = $c4
|
|||
|
END = $c5
|
|||
|
PGUP = $c6
|
|||
|
PGDN = $c7
|
|||
|
BKSP = $c8
|
|||
|
DEL = $c9
|
|||
|
ESC = $cb
|
|||
|
CTRL = $0200
|
|||
|
CTRL_ALT_DEL = $06c9
|
|||
|
|
|||
|
con
|
|||
|
BUFFERSIZE = 20000
|
|||
|
var
|
|||
|
long nLines
|
|||
|
long firstVisibleLine
|
|||
|
long firstVisibleColumn
|
|||
|
long bytesUsed
|
|||
|
long cursorLine
|
|||
|
long cursorColumn
|
|||
|
long desiredColumn
|
|||
|
long cp
|
|||
|
long currentLineLength
|
|||
|
long topPtr ' points to start of first visible line
|
|||
|
|
|||
|
pri InsertChar( ch ) | n
|
|||
|
if cursorLine < nLines
|
|||
|
if bytesUsed + 1 => BUFFERSIZE
|
|||
|
return
|
|||
|
else ' starting new line at end of file
|
|||
|
if bytesUsed + 2 => BUFFERSIZE
|
|||
|
return
|
|||
|
buffer[bytesUsed++]~ ' terminating null for the new line
|
|||
|
++nLines
|
|||
|
n := bytesUsed - (cp - @buffer)
|
|||
|
bytemove( cp+1, cp, n )
|
|||
|
byte[cp] := ch
|
|||
|
++bytesUsed
|
|||
|
++currentLineLength
|
|||
|
MoveCursorRight
|
|||
|
|
|||
|
pri InsertCR | n
|
|||
|
if cursorLine < nLines
|
|||
|
if bytesUsed + 1 => BUFFERSIZE
|
|||
|
return
|
|||
|
else ' starting new line at end of file
|
|||
|
if bytesUsed + 2 => BUFFERSIZE
|
|||
|
return
|
|||
|
buffer[bytesUsed++]~ ' terminating null for the new line
|
|||
|
++nLines
|
|||
|
n := bytesUsed - (cp - @buffer)
|
|||
|
bytemove( cp+1, cp, n )
|
|||
|
byte[cp]~
|
|||
|
++bytesUsed
|
|||
|
++nLines
|
|||
|
currentLineLength := cursorColumn
|
|||
|
MoveCursorRight
|
|||
|
|
|||
|
pri DeleteLeft
|
|||
|
if cursorLine or cursorColumn
|
|||
|
MoveCursorLeft
|
|||
|
DeleteRight
|
|||
|
|
|||
|
pri DeleteRight | n
|
|||
|
if cursorLine == nLines ' can't delete if we're on the phantom line
|
|||
|
return
|
|||
|
if byte[cp] == 0 and cursorLine == nLines - 1 and cursorColumn
|
|||
|
return ' can't delete if we're at the end of the last real line
|
|||
|
' unless we're at the start (start = end => empty line
|
|||
|
' which we can delete, making this line the new phantom line)
|
|||
|
n := bytesUsed - (cp - @buffer) - 1
|
|||
|
ifnot byte[cp]
|
|||
|
--nLines
|
|||
|
bytemove( cp, cp+1, n )
|
|||
|
--bytesUsed
|
|||
|
repeat while byte[--cp]
|
|||
|
currentLineLength := strsize( ++cp )
|
|||
|
cp += cursorColumn
|
|||
|
AdjustVisibleColumn
|
|||
|
|
|||
|
pri MoveCursorLeft
|
|||
|
if cursorColumn
|
|||
|
desiredColumn := --cursorColumn
|
|||
|
--cp
|
|||
|
AdjustVisibleColumn
|
|||
|
else
|
|||
|
ifnot cursorLine
|
|||
|
return
|
|||
|
MoveCursorUp( true )
|
|||
|
|
|||
|
pri MoveCursorRight
|
|||
|
if cursorColumn < currentLineLength
|
|||
|
desiredColumn := ++cursorColumn
|
|||
|
++cp
|
|||
|
AdjustVisibleColumn
|
|||
|
else
|
|||
|
if cursorLine == nLines
|
|||
|
return
|
|||
|
MoveCursorDown( true )
|
|||
|
|
|||
|
pri MoveCursorDown( f )
|
|||
|
' if f, also move cursor to start of line
|
|||
|
if cursorLine == nLines
|
|||
|
return
|
|||
|
if ++cursorLine - firstVisibleLine => VISIBLELINES
|
|||
|
++firstVisibleLine
|
|||
|
repeat while byte[topPtr++]
|
|||
|
MoveCpToNextLine
|
|||
|
if f
|
|||
|
cursorColumn~
|
|||
|
desiredColumn~
|
|||
|
elseif cursorLine < nLines
|
|||
|
cursorColumn := currentLineLength <# desiredColumn
|
|||
|
else
|
|||
|
cursorColumn~
|
|||
|
currentLineLength~
|
|||
|
cp += cursorColumn
|
|||
|
AdjustVisibleColumn
|
|||
|
|
|||
|
pri PageCursorDown
|
|||
|
if firstVisibleLine + VISIBLELINES - 1 => nLines
|
|||
|
return
|
|||
|
repeat VISIBLELINES - 1
|
|||
|
repeat while byte[topPtr++]
|
|||
|
++firstVisibleLine
|
|||
|
MoveCursorDown( false )
|
|||
|
|
|||
|
pri MoveCursorUp( f )
|
|||
|
' if f, also move cursor to end of line
|
|||
|
ifnot cursorLine
|
|||
|
return
|
|||
|
if --cursorLine < firstVisibleLine
|
|||
|
--firstVisibleLine
|
|||
|
repeat while byte[--topPtr]
|
|||
|
repeat while byte[--topPtr]
|
|||
|
++topPtr
|
|||
|
MoveCpToPreviousLine
|
|||
|
if f
|
|||
|
cursorColumn := desiredColumn := currentLineLength
|
|||
|
else
|
|||
|
cursorColumn := currentLineLength <# desiredColumn
|
|||
|
cp += cursorColumn
|
|||
|
AdjustVisibleColumn
|
|||
|
|
|||
|
pri PageCursorUp | n
|
|||
|
n := firstVisibleLine <# VISIBLELINES - 1
|
|||
|
repeat n
|
|||
|
repeat 2
|
|||
|
repeat while byte[--topPtr]
|
|||
|
++topPtr
|
|||
|
--firstVisibleLine
|
|||
|
MoveCursorUp( false )
|
|||
|
|
|||
|
pri MoveCpToNextLine
|
|||
|
repeat while byte[cp++]
|
|||
|
currentLineLength := strsize( cp )
|
|||
|
|
|||
|
pri MoveCpToPreviousLine
|
|||
|
repeat while byte[--cp]
|
|||
|
repeat while byte[--cp]
|
|||
|
++cp
|
|||
|
currentLineLength := strsize( cp )
|
|||
|
|
|||
|
pri MoveToStartOfLine
|
|||
|
cp -= cursorColumn
|
|||
|
cursorColumn~
|
|||
|
desiredColumn~
|
|||
|
AdjustVisibleColumn
|
|||
|
|
|||
|
pri MoveToEndOfLine
|
|||
|
cp -= cursorColumn
|
|||
|
cursorColumn := desiredColumn := currentLineLength
|
|||
|
cp += currentLineLength
|
|||
|
AdjustVisibleColumn
|
|||
|
|
|||
|
pri MoveToTop
|
|||
|
cp := topPtr := @buffer[1]
|
|||
|
currentLineLength := strsize( cp )
|
|||
|
cursorLine~
|
|||
|
cursorColumn~
|
|||
|
desiredColumn~
|
|||
|
firstVisibleLine~
|
|||
|
firstVisibleColumn~
|
|||
|
|
|||
|
pri MoveToBottom ' cheesy implementation
|
|||
|
MoveToTop
|
|||
|
if nLines
|
|||
|
repeat nLines-1
|
|||
|
MoveCursorDown( false )
|
|||
|
MoveToEndOfLine
|
|||
|
|
|||
|
pri AdjustVisibleColumn
|
|||
|
firstVisibleColumn~
|
|||
|
firstVisibleColumn #>= cursorColumn - VISIBLECOLUMNS + 1
|
|||
|
|
|||
|
var
|
|||
|
byte buffer[BUFFERSIZE]
|
|||
|
{ buffer contains nLines null-terminated strings, with sentinel nulls at the beginning and end:
|
|||
|
+-+---------+-+--------+-+- ---------+-+-+
|
|||
|
<20>0<EFBFBD> line0 0<> line1 0<> ... lineN-1 0<>0<EFBFBD>
|
|||
|
+-+---------+-+--------+-+- ---------+-+-+
|
|||
|
}
|
|||
|
pri Init
|
|||
|
nLines~
|
|||
|
firstVisibleLine~
|
|||
|
firstVisibleColumn~
|
|||
|
bytesUsed := 2
|
|||
|
buffer[0]~
|
|||
|
buffer[1]~
|
|||
|
cursorLine~
|
|||
|
cursorColumn~
|
|||
|
desiredColumn~
|
|||
|
|
|||
|
currentLineLength~
|
|||
|
cp := topPtr := @buffer[1]
|
|||
|
|
|||
|
eolLength := 2
|
|||
|
eol[0] := $0d
|
|||
|
eol[1] := $0a
|
|||
|
|
|||
|
pri RefreshScreen( drawCursor ) | p, len, m, n, l, beyondEnd
|
|||
|
p := topPtr
|
|||
|
beyondEnd~
|
|||
|
repeat l from 0 to VISIBLELINES-1
|
|||
|
term.setXY( 0, l )
|
|||
|
if firstVisibleLine + l => nLines
|
|||
|
beyondEnd~~
|
|||
|
len~
|
|||
|
else
|
|||
|
len := strsize( p )
|
|||
|
if len > firstVisibleColumn
|
|||
|
m := (len - firstVisibleColumn) <# VISIBLECOLUMNS
|
|||
|
p += firstVisibleColumn
|
|||
|
repeat m-1
|
|||
|
term.out( byte[p++] )
|
|||
|
term.printNoAdvance( byte[p++] )
|
|||
|
term.setX( m )
|
|||
|
n := VISIBLECOLUMNS - m
|
|||
|
if n
|
|||
|
repeat n-1
|
|||
|
term.out( " " )
|
|||
|
term.printNoAdvance( " " )
|
|||
|
else
|
|||
|
repeat VISIBLECOLUMNS-1
|
|||
|
term.out( " " )
|
|||
|
term.printNoAdvance( " " )
|
|||
|
ifnot beyondEnd
|
|||
|
repeat while byte[p++]
|
|||
|
if drawCursor
|
|||
|
term.setXY( cursorColumn-firstVisibleColumn, cursorLine-firstVisibleLine )
|
|||
|
term.printNoAdvance( "_" )
|
|||
|
|
|||
|
con
|
|||
|
#0, OPEN, SAVE ' File ops
|
|||
|
' File errors
|
|||
|
#1, FILE_TOO_BIG, CARD_FULL_MAYBE
|
|||
|
|
|||
|
FILENAMEBUFFERSIZE = 13 ' 8.3 + null
|
|||
|
|
|||
|
pri FileOp( op, t ) | e, i
|
|||
|
if e := \_FileOp( op, t )
|
|||
|
RefreshScreen( false ) ' cursor off
|
|||
|
term.setXY( 0, VISIBLELINES-1 )
|
|||
|
term.out( $0c ) ' set color
|
|||
|
term.out( 1 )
|
|||
|
repeat VISIBLECOLUMNS-1
|
|||
|
term.out( " " )
|
|||
|
term.printNoAdvance( " " )
|
|||
|
term.setX( 0 )
|
|||
|
case e
|
|||
|
FILE_TOO_BIG: term.str( string("File too large") )
|
|||
|
CARD_FULL_MAYBE: term.str( string("SD card full?") )
|
|||
|
-13: term.str( string("Unable to mount SD card") )
|
|||
|
other:
|
|||
|
term.str( string("Error code ") )
|
|||
|
term.dec( e )
|
|||
|
kb.getkey
|
|||
|
term.out( $0c ) ' set color
|
|||
|
term.out( 0 )
|
|||
|
|
|||
|
pri _FileOp( op, t ) | r
|
|||
|
case op
|
|||
|
OPEN:
|
|||
|
if t
|
|||
|
ReadFile
|
|||
|
elseif EditFilename
|
|||
|
ReadFile
|
|||
|
SAVE:
|
|||
|
WriteFile
|
|||
|
|
|||
|
pri ReadFile | r, p, pEnd
|
|||
|
sd.Close
|
|||
|
if r := sd.Open( @filename, "R" ) <> 0
|
|||
|
abort r
|
|||
|
|
|||
|
Init
|
|||
|
r := sd.Read( @buffer[1], BUFFERSIZE-2 )
|
|||
|
|
|||
|
if r < 0
|
|||
|
abort r
|
|||
|
p := @buffer[1]
|
|||
|
pEnd := @buffer[r+1]
|
|||
|
repeat while p <> pEnd
|
|||
|
if byte[p] == $0d or byte[p] == $0a ' change CR, LF, or CRLF
|
|||
|
if byte[p] == $0d and byte[p+1] == $0a '
|
|||
|
eolLength := 2 '
|
|||
|
bytemove( p, p + 1, pEnd - p - 1 ) '
|
|||
|
--pEnd '
|
|||
|
else '
|
|||
|
eolLength := 1 '
|
|||
|
byte[p]~ ' to null
|
|||
|
++nLines
|
|||
|
++p
|
|||
|
|
|||
|
if byte[pEnd-1] ' if byte[pEnd-1] isn't null, that means that the file didn't
|
|||
|
byte[pEnd++]~ ' end with CR, LF, or CRLF, so we have to fake it. (fixed 2010/01/29 mp)
|
|||
|
++nLines
|
|||
|
|
|||
|
byte[pEnd]~ ' add final null
|
|||
|
bytesUsed := pEnd + 1 - @buffer
|
|||
|
|
|||
|
currentLineLength := strsize( topPtr )
|
|||
|
|
|||
|
if r == BUFFERSIZE-2
|
|||
|
abort FILE_TOO_BIG ' technically we don't know that it's too big at this point, but it's a good guess.
|
|||
|
if r := sd.Close
|
|||
|
abort r
|
|||
|
|
|||
|
pri WriteFile | p, len, r
|
|||
|
ifnot EditFilename
|
|||
|
return
|
|||
|
sd.Close
|
|||
|
if r := sd.Open( @filename, "W" )
|
|||
|
abort r
|
|||
|
|
|||
|
p := @buffer[1]
|
|||
|
repeat nLines
|
|||
|
len := strsize( p )
|
|||
|
r := sd.Write( p, len )
|
|||
|
if r < 0
|
|||
|
abort r
|
|||
|
if r <> len ' Does this ever happen?
|
|||
|
abort CARD_FULL_MAYBE
|
|||
|
|
|||
|
r := sd.Write( @eol, eolLength )
|
|||
|
if r < 0
|
|||
|
abort r
|
|||
|
if r <> eolLength ' Does this ever happen?
|
|||
|
abort CARD_FULL_MAYBE
|
|||
|
p += len + 1
|
|||
|
|
|||
|
if (r := sd.Close) < 0
|
|||
|
abort r
|
|||
|
|
|||
|
var
|
|||
|
byte filename[FILENAMEBUFFERSIZE]
|
|||
|
long filenameCursorColumn
|
|||
|
long filenameLength
|
|||
|
byte eolLength
|
|||
|
byte eol[2]
|
|||
|
con
|
|||
|
EDITOFFSET = 10 ' filename edit field starts at column 10
|
|||
|
|
|||
|
pri EditFilename | t, key, blink, refresh
|
|||
|
RefreshScreen( false ) ' cursor off
|
|||
|
term.setXY( 0, VISIBLELINES-1 )
|
|||
|
term.out( $0c ) ' set color
|
|||
|
term.out( 2 )
|
|||
|
repeat VISIBLECOLUMNS-1
|
|||
|
term.out( " " )
|
|||
|
term.printNoAdvance( " " )
|
|||
|
term.setX( 0 )
|
|||
|
term.str( string("Filename: ") )
|
|||
|
|
|||
|
filenameCursorColumn := filenameLength := strsize( @filename )
|
|||
|
blink~
|
|||
|
t := cnt
|
|||
|
repeat
|
|||
|
refresh~
|
|||
|
repeat while key := kb.key
|
|||
|
refresh~~
|
|||
|
blink~
|
|||
|
case key
|
|||
|
$20..$7f: FilenameInsertChar( key )
|
|||
|
CR:
|
|||
|
term.out( $0c ) ' set color
|
|||
|
term.out( 0 ) ' back to normal
|
|||
|
return true
|
|||
|
BKSP:
|
|||
|
if filenameCursorColumn
|
|||
|
filenameCursorColumn := (filenameCursorColumn - 1) #> 0
|
|||
|
FilenameDeleteRight
|
|||
|
DEL: FilenameDeleteRight
|
|||
|
LARROW: filenameCursorColumn := (filenameCursorColumn - 1) #> 0
|
|||
|
RARROW: filenameCursorColumn := (filenameCursorColumn + 1) <# filenameLength
|
|||
|
ESC:
|
|||
|
term.out( $0c ) ' set color
|
|||
|
term.out( 0 ) ' back to normal
|
|||
|
return false
|
|||
|
other: refresh~
|
|||
|
if cnt - t > 0
|
|||
|
refresh~~
|
|||
|
if refresh
|
|||
|
RefreshFilename( blink ^= 1 )
|
|||
|
t := cnt + clkfreq/10
|
|||
|
|
|||
|
pri RefreshFilename( drawCursor ) | i
|
|||
|
term.setXY( EDITOFFSET, VISIBLELINES-1 )
|
|||
|
repeat i from 0 to FILENAMEBUFFERSIZE-1
|
|||
|
if drawCursor and i == filenameCursorColumn
|
|||
|
term.out( "_" )
|
|||
|
else
|
|||
|
if i < filenameLength
|
|||
|
term.out( filename[i] )
|
|||
|
else
|
|||
|
term.out( " " )
|
|||
|
|
|||
|
pri FilenameInsertChar( ch ) | n
|
|||
|
if filenameLength => FILENAMEBUFFERSIZE-1
|
|||
|
return
|
|||
|
n := filenameLength - filenameCursorColumn + 1
|
|||
|
bytemove( @filename[filenameCursorColumn+1], @filename[filenameCursorColumn], n )
|
|||
|
++filenameLength
|
|||
|
filename[filenameCursorColumn++] := ch
|
|||
|
|
|||
|
pri FilenameDeleteRight | n
|
|||
|
ifnot filenameLength
|
|||
|
return
|
|||
|
ifnot n := filenameLength - filenameCursorColumn
|
|||
|
return
|
|||
|
bytemove( @filename[filenameCursorColumn], @filename[filenameCursorColumn+1], n )
|
|||
|
--filenameLength
|
|||
|
|
|||
|
{{
|
|||
|
Copyright (c) 2009 Michael Park
|
|||
|
+------------------------------------------------------------------------------------------------------------------------------+
|
|||
|
| TERMS OF USE: MIT License |
|
|||
|
+------------------------------------------------------------------------------------------------------------------------------+
|
|||
|
|Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation |
|
|||
|
|files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, |
|
|||
|
|modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software|
|
|||
|
|is furnished to do so, subject to the following conditions: |
|
|||
|
| |
|
|||
|
|The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.|
|
|||
|
| |
|
|||
|
|THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
|
|||
|
|WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|
|||
|
|COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
|
|||
|
|ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|||
|
+------------------------------------------------------------------------------------------------------------------------------+
|
|||
|
}}
|