/* ±±±±±±±±±±±±±±±±±±±±±±±±±±±± Commfnc.prg ±±±±±±±±±±±±±±±±±±±±±±±±±±±

     Common communication protocol functions.
     Copyright: Bert Kerkhof, February 10, 1995.

     This Clipper source code is an original work of Bert Kerkhof.

     The functions in this module are called by the Ansi-terminal module
     and the Kermit and Zmodem file transfer protocol module.

     Compile with the parameters: /M /N /W.

     Address of the author:
             Zwartewater 2, 3514 BN Utrecht, the Netherlands.
             Phone: +31 30 715690
             Email: 100333.306@CompuServe.Com

     Common Communication routines
     ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
     ComOpen( <nPort> ) -> nResultCode.
         After a run-time error during an open RS232-channel, the
         computer will hang. This ComOpen() function installs a special
         error-handler. This handler closes the RS232-interface when an
         error occurs. Using this ComOpen() function will give you
         comfort during developing programs that use the modem. ComOpen
         also sets the DTR-line to 'Data Terminal Ready' status. The
         return value is the same as used with Fast.Lib function
         ComInst().

     ComClose() -> NIL
         Performs the same action as ComUnInst(). But the special
         errorhandler that was installed with ComOpen(), is de-
         installed. Also, the DTR-line is set to 'Terminal not ready'
         status.

     ComModem( <nPort>, <nBaudRate> ) -> nResultCode
         Opens the RS232 channel and seeks at which COM port a modem
         is attached. If a modem is found, several baudrates are
         tried. The output parameters Port and BaudRate will return
         the choosen port and the highest functioning baudrate. The
         ResultCode that is returned, is the same as in the ComUnInst()
         function. If no modem is attached, an error message in a
         window is displayed and ResultCode 4 is returned.

     Connect( <cPhoneNumber>, [<lToneDial>] ) -> cMessage
         Before using this function, first open the Rs232 channel.
         Connect() causes the modem to dial the phone number. If
         parameter ToneDial is .True., the tonedial method is used.
         During the dialing process, a message window is displayed,
         with a clock. A modem result-message is returned.

     DisConnect() -> cMessage
         Causes the modem to hang up. A message window is displays
         shortly. The modem result-message, usually 'OK', is returned.

     Function ComLanguage( [<bNewLanguage>] )  ->  bOldLanguage
         By default the language used for messages in the file
         transfer progression window is English. You also can choose
         to display other messages by providing a codeblock that
         evaluates to messages in a different language.

     ComSends( <cOutput> )
         Waits when the RS232 output buffer is full. If there is
         space, the Output string is sended.

     Pause( <nDuration> )
         Waits a number of seconds.

     The next routines are used both in the Kermit and Zmodem protocol,
     can be used for programming other file transfer protocols, but have
     limited importance for the end user:

     BeginProgres( <cProtocolName> ) -> aScreen
         Starts displaying a file transfer progression window with a
         clock. The ProtocolName is displayed at the screen.
         BeginProgres returns an array with screen information which
         later can be used to restore the termial screen with
         EndProgress().

     Messag( <nNumber> ) -> cMessage
         This message function interfaces with the language module.
         Called with numbers ranging from 1 to 23, the appropriate
         message string is returned.

     FileProgres( <cFileName>, <lDirection>, <nFilePos>, <nSize> )
         Called by file transfer programs to update the file transfer
         progression window when a new file starts to be received
         (Direction is .True.) or sended (Direction is .False.). The
         FilePos and Size parameters pass the starting location in
         the file and the total file size (in bytes).

     ShowProgres( [<Message>], [<FilePos>] )
         After each packet is received\sended, the file transfer
         routines call the ShowProgres procedure. An optional message
         is displayed in the Status field of the progression window.
         Also, an optional Filepos parameter will update the field
         'Bytes transferred'.

     ShowElapsed()
         Updates the clock in the progression window. Called
         regularly.

     EndProgres( <cMessage>, <aScreen>, <lOkFlag> )
         Displays a status message in the progression window. If the
         OkFlag is .False., the window will stay at the screen for a
         while so the error message can be read. If the OkFlag is
         .True., the window will close fast. The Screen parameter
         passes the information originating from by function
         BeginProgres() to restore the terminal screen.

     Within( <nSeconds> ) -> nDeadline
         Called with the number of seconds from now on to go, this
         function returns a number representing a Deadline, meassured
         in seconds from the last midnight.

     Remarks on the RS232 interface from Fast.Lib
     ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
     The RS232-functions in Fast.Lib do a great job. But keep the
     following notices in mind:
     1     The buffer-string that is needed as a parameter in ComInputS()
           has to be re-initialized before each usage. Because after the
           execution of ComInputS(), the length of this string may or may
           not be shorter. Especially users which are familiar with the
           Fread() function in Clipper should be aware of this different
           behaviour.
     2     After a run-time error during an open RS232-channel, the
           computer will hang. So specific error-handling is needed that
           closes the RS232-interface when an error occurs. In this
           module this error-handling is invoked when using the function
           ComOpen() and ComClose().
     3     Cominput() returns negative values for bytes with have bit 8
           set. These negative values however are allowed in functions
           Chr() and StrSetCar().
*/

// ±±±±±±±±±±±±±±±± Common communication functions ±±±±±±±±±±±±±±±±±±±±

#Include "Common.ch"           // For the command 'default'.
#Include "Inkey.ch"

#Define CR          13         // Carriage return.
#Define LF          10         // Linefeed.

Static OldErrh                 // To save and restore the old error handler.
Static Origin                  // Start position in file to be transferred.
Static StartTime := NIL        // Time the dialing or file transfer started.
Static ComLanguage := { |I| English(I) } // Language in proceedings windows.

// Open Rs232 channel: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function ComOpen(Port)
Local Error                    // Result code from ComInst().
OldErrh := ErrorBlock({ |E| Rs232Errh(E) }) // Install special error handler.
If (Error := ComInst(Port))==0
  ComHighDtr()                 // Set data terminal flag to 'Ready'.
Endif
Return (Error)

// Close Rs232 channel: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function ComClose()
ComLowDtr()                    // Set data terminal flag to 'not ready'.
ComUnInst()
ErrorBlock(OldErrh)            // Remove the Rs232 error handler.
Return (NIL)

// Rs232 error handler: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function Rs232Errh(E)
ComLowDtr()                    // Set data terminal flag to 'not ready'.
ComUnInst()                    // Close the RS232 channel.
Eval(Olderrh,E)                // Invoke further error handling.
Return (NIL)

// Make connection to the modem: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function ComModem(Port,Baud)
/* This function tries to open a channel to the modem via the specified
   Comport, at the specified baudrate. If the opening doesnot succeed, the
   function seeks if and where a modem is attached and what the maximum
   baudrate is. Then the Rs232 channel to the modem is opened.

   Parameters: Port     [Optional] COM-port to use initially. If the opening
                        succeeds, Port returns the choosen COM port number.
               Baud     [Optional] Baudrate to use initially. If the opening
                        succeeds, Baud returns the choosen baudrate.

   The function returns an error number, the same as used for function
   ComInst(). In addition, a return value of 4 signifies that there is no
   modem attached.
*/
Local Psearch := If(Ascan({0,1,10,11,12,13},CpuType())>0,{1,2},{1,3,2,4})
Local Bsearch := {2400,9600,19200,38400,57600}
Local OldCursor := SetCursor(0) // Cursor off.
Local PortSeq, BaudSeq         // Arrays with Port and Baud search sequences.
Local Error                    // Error-number received from function ComInst()

PortSeq := If(Port==NIL,Psearch,{Port})
BaudSeq := If(Baud==NIL,Bsearch,{Baud})
Error := ComModemTry(PortSeq,BaudSeq,@Port,@Baud) // First try.
If Error>0 .And. (Port<>NIL .Or. Baud<>NIL) // Second try.
  Error := ComModemTry(Psearch,Bsearch,@Port,@Baud)
Endif
If Error>0
  OutMess(Messag(19))          // 'No modem found'.
Else
  ComHighDtr()                 // Set data terminal flag to 'Ready'.
Endif
SetCursor(OldCursor)           // Restore old cursor mode.
Return (Error)

// Try to find a modem and test modem speed: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function ComModemTry(PortSeq,BaudSeq,Port,Baud)
Local Pcount, Bcount           // Temporary variables.
Local Ok := .F.                // Flag: True if 'OK' is received from modem.
Local Error                    // Error-number received from ComInst().

For Pcount := 1 To Len(PortSeq)
  If (Error := ComOpen(PortSeq[Pcount]))==0
    For Bcount := 1 To Len(BaudSeq)
      ComSpeed(BaudSeq[Bcount])
      If Hayes("AT",Within(.4))<>"OK" // No 'OK' received.
        Exit
      Endif
      Ok := .T.
      Port := PortSeq[Pcount]
      Baud := BaudSeq[Bcount]
    Next Bcount
    If Ok
      Hayes("AT S7=45",Within(1)) // Configurate modem.
      Exit
    Else
      Error := 4               // Addition to the return values of ComInst().
    Endif
  Endif
  ComClose()
Next Pcount
Return (Error)

// Making connection to a BBS: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function Connect(Phone,ToneDial)
// Parameters: Phone:    The phone number string.
//             ToneDial: Optional flag .T. if tonedial is to be used.
Local OldScreen                // To save and restore a part of the screen.
Local Answer                   // Answer string received from function Hayes.
Default ToneDial To .F.
If Empty(Phone)
  Return (NIL)
Endif

OldScreen := MakeWindow(9,9,15,70)
@ 11,11 Say Messag(20)+" "+AllTrim(Phone)+" .."
@ 12,39 Say Padl(Messag(7),20)+":" // 'Elapsed time'.
StartTime := Within(0)         // Save the dial start time.
// Building connection:
Answer := Hayes("ATD"+If(ToneDial,"T","")+Phone,StartTime+60)
Do Case
  Case "CONNECT"$Answer
    ComHighDtr()               // Data terminal ready.
    // Sequence that works fine with most bulletin boards:
    Pause(.5)                  // Wait a moment.
    ComSends(Chr(K_ESC)+Chr(K_ESC)) // Acknowledge carrier.
    Pause(.5)                  // Wait a moment.
    ComSends(Chr(K_ESC)+Chr(K_ESC)) // Start BBS.
  Case "NO DIALTONE"$Answer
    @ 13,11 Say Messag(21)     // 'No Dialtone'.
    EndPause(10)
  Case "BUSY"$Answer
    @ 13,11 Say Messag(22)     // 'Busy'.
    EndPause(10)
  Case Empty(Answer)           // Timeout or user interrupt.
                               // Do nothing.
  Otherwise
    @ 13,11 Say Capital(Answer) // 'Voice' or other error.
    EndPause(10)
Endcase
StartTime := NIL               // Stop displaying elapsed time.
AllRest(OldScreen)             // Remove dialing window.
Return (Answer)

// Close connection to the BBS: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function DisConnect()
Local OldScreen := MakeWindow(10,29,14,50)
Local Answer                   // Return value: Answer from the modem.
@ 12,37 Say Messag(23)         // 'Hangup'.
ComSends("+++")                // Hayes string.
Pause(1.1)                     // Wait more then 1 second.
Answer := Hayes("ATH",Within(2)) // Hang up.
While .Not. ComOutEmpt()       // Finish communication with the modem.
Enddo
ComLowDtr()                    // Set data terminal to 'not ready'.
AllRest(OldScreen)             // Remove dialing window.
Return (Answer)

* Send Hayes command and receive backmessage: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function Hayes(Command,Deadline)
Local Answer                   // Message from the modem.
ComInFlh()                     // Clear the input buffer.
ComSends(Command+Chr(CR)+Chr(LF))
Begin Sequence
  Answer := ReadLine(Deadline)
  If Answer==Command           // Skip the command echo
    Answer := ReadLine(Deadline) // by reading an extra line.
  Endif
Recover                        // Timeout or user interrupt.
  Answer := ""                 // Purge command echo.
End Sequence
Return (Answer)

// Read text from a modem up to CRLF, skips leading CRLF: ÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function ReadLine(Deadline)
Local Line                     // String with received modem-answer.
Local Byte                     // Ascii value of received byte.

* Time-limit and other errors are catched in function Hayes.
While .T.                      // Skip preceding CR and LF.
  If (Byte := ModemByte(Deadline))>31
    Line := Chr(Byte)
    Exit
  Endif
Enddo
While .T.                      // Now read the text.
  If (Byte := ModemByte(Deadline))<32 // Up to the first control-character.
    Exit
  Endif
  Line += Chr(Byte)
Enddo
ModemByte(Deadline)              // Skip the linefeed too.
Return (Line)

// Read a byte returned from the modem: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function ModemByte(Deadline)
Local Byte
While (Byte := ComInput())==NIL // Receive byte.
  If Within(0)>Deadline .Or. Inkey()==K_ESC .Or. IsAlt()
    ComSends(" ")              // Stop modem.
    Break                      // Timeout.
  Endif
  ShowElapsed()                // Show elpased time.
Enddo
Return (Asc(Chr(Byte)))        // Remove sign from Fast.Lib.

// Pause a while: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Procedure Pause(Duration)
Local Deadline := Within(Duration)
While Within(0)<Deadline
Enddo

// Pause, while updating a clock: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Procedure EndPause(Duration)
Local Deadline := Within(Duration)
While Within(0)<Deadline .And. Inkey()==0
  ShowElapsed()
Enddo

// Meassure time: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function Within(Plus)
// At midnight Seconds() falls back to 0. But function Within() counts further.
Static OldSec := 0             // Zero value at program start.
Local NewSec := Seconds()
If NewSec<OldSec               // After midnight.
  NewSec += 86400              // Add 24 hours.
Endif
OldSec := NewSec               // Remember the last returned value.
Return (NewSec+Plus)

// Wait until output buffer is empty, then send a string to RS232: ÄÄÄÄ
Procedure ComSends(String)
While .Not. ComReady()
Enddo
ComOutPuts(String)

// ±±±±±±±±±±±±±±±±±±±±±± Progression Window Part ±±±±±±±±±±±±±±±±±±±±±

// Start progression window: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function BeginProgres(Protocol)
Local OldScreen := MakeWindow(8,9,16,70)
DispBegin()
@  9,11 Say Protocol+" "+Messag(1) // 'Kermit\Zmodem file transfer'.
@ 12,11 Say Padl(Messag(5),20)+":" // 'File size'.
@ 13,11 Say Padl(Messag(6),20)+":" // 'Bytes transferred'.
@ 12,39 Say Padl(Messag(7),20)+":" // 'Elapsed time'.
@ 13,39 Say Padl(Messag(8),20)+":" // 'Bytes per second'.
@ 15,11 Say Messag(9)+":"      // 'Last status\error:'.
SetEnhanced()                  // Display variable parts in enhanced colour:
@ 10,11 Say Messag(2)          // 'Initial handshaking ..'.
@ 12,61 Say "00:00:00"         // Elapsed time.
@ 13,33 Say Space(8)           // Bytes transferred.
@ 13,61 Say Space(8)           // Bytes per second.
@ 15,13+Len(Messag(9)) Say Messag(10) // 'All OK'
DispEnd()
StartTime := Within(0)         // Start elapsed time display.
Return (OldScreen)

// Progression, start a new file: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Procedure FileProgres(FileName,Direction,FilePos,Size)
// FileName does not include a path.
Local DirMess                  // Window subtitle: 'Receiving\sending file '.
Local MaxLen                   // Maximum length of filename.
Local ColPos                   // Column position of status message.
StartTime := Within(0)         // Initialize static variable.
Origin := FilePos              // Start-position in file.
DispBegin()
DirMess:= If(Direction,Messag(3),Messag(4))+": " // 'Receiving\sending file: '
MaxLen := 52-Len(DirMess)
@ 10,11 Say DirMess+Padr(Upper(Right(FileName,Maxlen)),Maxlen)
@ 12,33 Say If(Size==NIL,Space(8),Ltrim(Str(Size,8))) // File size in bytes. 
@ 12,61 Say "00:00:00"         // Elapsed time.
@ 13,33 Say Space(8)           // Number of characters transferred.
@ 13,61 Say Space(8)           // Bytes per second.
ColPos := 13+Len(Messag(9))
@ 15,ColPos Say Padr(Ltrim(Messag(10)),69-ColPos)
DispEnd()

// Show progression on the screen: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Procedure ShowProgres(Message,FilePos)
Local Nbytes                   // # Bytes transferred in this transaction.
Local ColPos                   // Column position of status message.
Local Pos := "0"               // String representation of position in file.
DispBegin()
ShowElapsed()                  // Display elpased time.
If FilePos<>NIL                // Not for non-file packets.
  Nbytes := FilePos-Origin     // Current file-position minus startpos.
  @ 13,33 Say Ltrim(Str(FilePos,8)) // Total # of bytes transferred. 
  @ 13,61 Say Padr(Ltrim(Str(Nbytes/(Within(0)-StartTime),8)),8) // Cps.
  Pos := Ltrim(Str(FilePos,8))
Endif
If Message<>NIL
  ColPos := 13+Len(Messag(9))
  @ 15,ColPos Say Padr(Ltrim(Pos+" "+Message),69-ColPos)
Endif
DispEnd()

// Display elapsed time: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Procedure ShowElapsed()
Static NewDispTime := 0
Local Now
If StartTime<>NIL .And. (Now := Within(0))>NewDispTime
  @ 12,61 Say SecToTime(Within(0)-StartTime) // Display elapsed time.
  NewDispTime := Now+1
Endif

// Remove progression window: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Procedure EndProgres(Message,OldScreen,OkFlag)
Local ColPos := 13+Len(Messag(9))
@ 15,ColPos Say Padr(Message,69-ColPos)
EndPause(If(OkFlag,1,5))       // Wait to read the last message.
StartTime := NIL               // Stop displaying elapsed time.
AllRest(OldScreen)             // Restore terminal screen and cursor.

// Convert number of seconds in time-string: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function Sectotime(Seconds)
Local Minutes
Local Hours := Int(Seconds/3600)
Seconds := Seconds%3600
Minutes := Int(Seconds/60)
Seconds := Seconds%60
Return (StrZero(Hours,2)+":"+StrZero(Minutes,2)+":"+StrZero(Seconds,2))

// ±±±±±±±±±±±±±±±±±±±±±±±± Basic primitives ±±±±±±±±±±±±±±±±±±±±±±±±±±

// Request user for alphanumeric input: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function Request(Message,Length,InString)
Local DispLen                  // Inner length of the window.
Local Left                     // Left column position of window.
Local OldScreen                // To store a screen region.
Local GetList := {}

Default Message To ""
Default Length To 60
Default InString To ""

DispLen := Max(Len(Message),Length)
Left := 38-Int(DispLen/2)
OldScreen := MakeWindow(9,Left,14,Left+Displen+2)
InString := Padr(InString,Length)

@ 11,Left+2 Say Message
Set Confirm On
@ 12,Left+2 Get InString
SetCursor(3)                   // Block cursor on.
Read
AllRest(OldScreen)             // Remove window and cursor.

Return (InString)

// Display a message in a window: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Procedure OutMess(Message,Time)
Local OldScreen
Local DispLen := Len(Message)
Local Left := 38-Int(DispLen/2)

DispBegin()
OldScreen := MakeWindow(10,Left,14,Left+Displen+3)
@ 12,Left+2 Say Message
DispEnd()
Clear TypeAhead                // Clear keyboard buffer.
Inkey(Time)                    // Wait until a key is pressed.
AllRest(OldScreen)             // Remove window and cursor.

// Displays a window with shadow: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function MakeWindow(Top,Left,Bottom,Right)
Local OldScreen := AllSave(Top,Left,Bottom+1,Right+2,,0) // Save screen.
DispBegin()
Window(Top,Left,Bottom,Right,,"ÚÄ¿³ÙÄÀ³ ",.T.) // With shadow.
DispEnd()
Return (OldScreen)             // Return array with old screen data.

// Set current colour to enhanced: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function SetEnhanced(Color)
Local Enhcol
Default Color To SetColor()
EnhCol := Atoken(Color,",")[2] // Parse second element of the colour-string.
Return (SetColor(EnhCol))

// Save (a part of a) screen with attributes in an array: ÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Function AllSave(Top,Left,Bottom,Right,Color,Cursor,Blink)
// AllSave() saves the screen and all screen attributes.
Local Spart[10]                // Array to be returned.

Default Top To 0
Default Left To 0
Default Bottom To 24
Default Right To 79

Default Color To SetColor()
Default Cursor To SetCursor()
Default Blink To SetBlink()

Spart[1]  := Row()             // Save cursor-position.
Spart[2]  := Col()
Spart[3]  := SetColor(Color)   // Save colour and set the new window colour.
Spart[4]  := SetCursor(Cursor) // Save cursor type and set the cursor off.
Spart[5]  := SetBlink(Blink)   // Save current blinking mode.
Spart[6]  := Top
Spart[7]  := Left
Spart[8]  := Bottom
Spart[9]  := Right
Spart[10] := SaveScreen(Top,Left,Bottom,Right)
Return (Spart)                 // Return array with screen and attributes:

// Restore the old screen: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Static Procedure AllRest(Spart)
// AllRest() restores the screen and all screen attributes.
SetCursor(0)                   // Supress cursor display during RestScreen().
SetBlink(Spart[5])             // Restore old blink mode.
RestScreen(Spart[6],Spart[7],Spart[8],Spart[9],Spart[10])
DevPos(Spart[1],Spart[2])      // Restore old cursor position.
SetColor(Spart[3])             // Restore current screen colour.
SetCursor(Spart[4])            // Restore the cursor type.

// Convert (string of) filemasks to total file-directory: ÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function MaskDir(Masks)
Local MaskCount := 0           // Current Mask.
Local TotalDir := {}           // Return parameter.
Local FileCount                // Current file in current mask.
Local CurrentDir               // Directory of files from current mask.

If ValType(Masks)=="C"
  Masks := Atoken(Masks," ,;+") // Convert string to array.
Endif
While MaskCount<Len(Masks)
  If .Not. Empty(Masks[++MaskCount])
    CurrentDir := Directory(Masks[MaskCount])
    FileCount := 0
    While FileCount<Len(CurrentDir)
      Aadd(TotalDir,CurrentDir[++FileCount])
    Enddo
  Endif
Enddo
Return (TotalDir)

// ±±±±±±±±±±±±±±±± Transfer protocol language support ±±±±±±±±±±±±±±±±

// Set\read the language used in the proceedings window: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function ComLanguage(NewLanguage)
Local OldLanguage := ComLanguage
ComLanguage := If(NewLanguage==NIL,OldLanguage,NewLanguage)
Return (OldLanguage)

// Messages for file transfers in different languages: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function Messag(Number)
Return (Eval(ComLanguage,Number))

// Enlish language messages: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function English(Number)
Static Messag :=             ;
{ "data transfer",           ; //  1  Texts 1 to 4 are displayed in the
  "Initial handshaking ..",  ; //  2  top lines of the progression status
  "Sending file",            ; //  3  window.
  "Receiving file",          ; //  4
  "File size",               ; //  5  Texts 5 to 8 are displayed in the
  "Bytes transferred",       ; //  6  middle of the window. The maximum
  "Elapsed time",            ; //  7  length of this messages is 20
  "Bytes per second",        ; //  8  characters.
  "Last status\error",       ; //  9
  "All OK",                  ; // 10  Texts 10 to 18 are (parts of a)
  "Trying again",            ; // 11  message that can appear in the
  "Waiting",                 ; // 12  last status\error display area.
  "Transaction aborted",     ; // 13
  "Transaction complete",    ; // 14
  "Remote",                  ; // 15
  "Local",                   ; // 16
  "DOS error",               ; // 17
  "stop (Escape key)",       ; // 18
  "No modem found",          ; // 19  Used in function ComModem().
  "Dialing",                 ; // 20   ..              Connect().
  "No dial tone",            ; // 21   ..              Connect().
  "Busy",                    ; // 22   ..              Connect().
  "Hangup"}                    // 23   ..              DisConnect().
Return (Messag[Number])

// Dutch language messages: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Function Dutch(Number)
Static Messag :=             ;
{ "gegevens overdracht",     ; //  1  Texts 1 to 9 are displayed in the
  "Voorbereiding ..",        ; //  2  top lines of the progression status
  "Zenden bestand",          ; //  3  window.
  "Ontvangst bestand",       ; //  4
  "Omvang bestand",          ; //  5  Texts 5 to 8 are displayed in the
  "Tekens overgebracht",     ; //  6  middle of the window. The maximum
  "Verstreken tijd",         ; //  7  length of this messages is 20
  "Tekens/sekonde",          ; //  8  characters.
  "Laatste status\fout",     ; //  9
  "Alles OK",                ; // 10  Texts 10 to 18 are (parts of a)
  "Nieuwe poging",           ; // 11  message that can appear in the
  "Wachten",                 ; // 12  last status\error display area.
  "Overdracht mislukt",      ; // 13
  "Overdracht kompleet",     ; // 14
  "Overzijde",               ; // 15
  "Lokale",                  ; // 16
  "DOS fout",                ; // 17
  "stop (Escape toets)",     ; // 18
  "Geen modem aangesloten",  ; // 19  Used in function ComModem().
  "Opbouwen verbinding met", ; // 20   ..              Connect().
  "Geen kiestoon",           ; // 21   ..              Connect().
  "Nummer is bezet",         ; // 22   ..              Connect().
  "Ophangen"}                  // 23   ..              DisConnect().
Return (Messag[Number])

// ±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±
