1005 lines
27 KiB
C++
1005 lines
27 KiB
C++
/***********************************************************
|
|
WinTN3270
|
|
Copyright © 2007 Bob Carroll (bob.carroll@alum.rit.edu)
|
|
|
|
This software is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public
|
|
License as published by the Free Software Foundation;
|
|
either version 2, or (at your option) any later version.
|
|
|
|
This software is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
PURPOSE. See the GNU General Public License for more
|
|
details.
|
|
|
|
You should have received a copy of the GNU General Public
|
|
License along with this software; if not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
Boston, MA 02110-1301 USA
|
|
***********************************************************/
|
|
|
|
#include "stdafx.h"
|
|
#include "CClient3270.h"
|
|
#include "IBM3270.h"
|
|
#include "C3270Char.h"
|
|
#include "CEbcdicTranslator.h"
|
|
#include "stdio.h"
|
|
|
|
using namespace WinTN3270;
|
|
using namespace System;
|
|
using namespace System::Drawing;
|
|
using namespace System::Windows::Forms;
|
|
|
|
/***********************************************************
|
|
Creates a new TN3270 client instance.
|
|
|
|
@param cchIPAddr the IP address of the remote host
|
|
@param nPort the remote TCP port
|
|
***********************************************************/
|
|
CClient3270::CClient3270(String^ cchIPAddr, int nPort)
|
|
{
|
|
this->__Constructor(cchIPAddr, nPort, 0);
|
|
}
|
|
|
|
/***********************************************************
|
|
Creates a new TN3270 client instance.
|
|
|
|
@param cchIPAddr the IP address of the remote host
|
|
@param nPort the remote TCP port
|
|
@param nModel the terminal model number
|
|
***********************************************************/
|
|
CClient3270::CClient3270(String^ cchIPAddr, int nPort, int nModel)
|
|
{
|
|
this->__Constructor(cchIPAddr, nPort, nModel);
|
|
}
|
|
|
|
/***********************************************************
|
|
Disconnects from the remote host.
|
|
***********************************************************/
|
|
CClient3270::~CClient3270()
|
|
{
|
|
this->Disconnect();
|
|
}
|
|
|
|
/***********************************************************
|
|
Creates a new TN3270 client instance. Unlike the above two
|
|
functions, this is the real constructor.
|
|
|
|
@param cchIPAddr the IP address of the remote host
|
|
@param nPort the remote TCP port
|
|
@param nModel the terminal model number
|
|
***********************************************************/
|
|
void CClient3270::__Constructor(System::String^ cchIPAddr, int nPort, int nModel)
|
|
{
|
|
m_mpClient = gcnew CTelnetClient(cchIPAddr, nPort);
|
|
|
|
/* Set the screen dimensions */
|
|
switch (nModel) {
|
|
|
|
/* 24x80 */
|
|
case 2:
|
|
m_nRows = 24;
|
|
m_nCols = 80;
|
|
break;
|
|
|
|
/* 32x80 */
|
|
case 3:
|
|
m_nRows = 32;
|
|
m_nCols = 80;
|
|
break;
|
|
|
|
/* 43x80 */
|
|
case 4:
|
|
m_nRows = 43;
|
|
m_nCols = 80;
|
|
break;
|
|
|
|
/* 27x132 */
|
|
case 5:
|
|
m_nRows = 27;
|
|
m_nCols = 132;
|
|
break;
|
|
|
|
/* Default to model 2 */
|
|
default:
|
|
nModel = 2;
|
|
m_nRows = 24;
|
|
m_nCols = 80;
|
|
}
|
|
|
|
/* Set the model and term type */
|
|
char* pchModel = (char*)malloc(sizeof(char) + 1);
|
|
sprintf_s(pchModel, sizeof(char) + 1, "%d", nModel);
|
|
array<Byte>^ mpTermType =
|
|
{ 'I', 'B', 'M', '-', '3', '2', '7', '8', '-', *pchModel, '-', 'E' };
|
|
free(pchModel);
|
|
m_nModel = nModel;
|
|
|
|
/* Set default 3270 options */
|
|
m_mpClient->TerminalType = mpTermType;
|
|
m_mpClient->BinaryTransmission = true;
|
|
m_mpClient->EndOfRecord = true;
|
|
|
|
/* Register callbacks */
|
|
m_mpClient->ReceiveCallbackBinary =
|
|
gcnew CTelnetClient::OnReceiveBinary(this, &CClient3270::ReceiveCallback);
|
|
m_mpClient->SendCallback =
|
|
gcnew CTelnetClient::OnSend(this, &CClient3270::SendCallback);
|
|
|
|
m_mpCharBuffer =
|
|
gcnew array<C3270Char^>(this->Size.Height * this->Size.Width);
|
|
m_mpOnPaintCbk = nullptr;
|
|
m_fBaseColor = true;
|
|
m_fKeybLocked = true;
|
|
m_fSoundAlaram = false;
|
|
m_fStartPrinter = false;
|
|
m_nBufferPos = 0;
|
|
m_nCursorPos = 0;
|
|
m_nActionId = 0;
|
|
m_nLineLength = 0;
|
|
m_nLastField = 0;
|
|
|
|
this->EraseScreen();
|
|
}
|
|
|
|
/***********************************************************
|
|
Opens a new connection to the remote host.
|
|
***********************************************************/
|
|
void CClient3270::Connect()
|
|
{
|
|
this->Connect(false);
|
|
}
|
|
|
|
/***********************************************************
|
|
Opens a new connection to the remote host.
|
|
|
|
@param fSecure enable SSL
|
|
***********************************************************/
|
|
void CClient3270::Connect(bool fSecure)
|
|
{
|
|
m_mpClient->Connect(fSecure);
|
|
}
|
|
|
|
/***********************************************************
|
|
Converts the 6-bit buffer address to an integer.
|
|
|
|
@param chFirst the first byte of the address
|
|
@param chSecond the second byte of the address
|
|
|
|
@return the buffer offset as an integer
|
|
***********************************************************/
|
|
int CClient3270::ConvertBufferAddress(Byte chFirst, Byte chSecond)
|
|
{
|
|
/* Do some bit-shifting voodoo to get the offset */
|
|
return ((chFirst & 0xC0) == 0x00 ?
|
|
((chFirst & 0x3F) << 8) + chSecond :
|
|
((chFirst & 0x3F) << 6) + (chSecond & 0x3F));
|
|
}
|
|
|
|
/***********************************************************
|
|
Creates a key entry action for transmission to the
|
|
mainframe.
|
|
|
|
@param chActionId the action id byte
|
|
|
|
@return an action array
|
|
***********************************************************/
|
|
array<Byte>^ CClient3270::CreateActionArray(Byte chActionId)
|
|
{
|
|
array<Byte>^ mpAction = gcnew array<Byte>(3);
|
|
mpAction[0] = chActionId;
|
|
mpAction[1] = m_mpAddrTbl[(m_nCursorPos >> 6) & 0x3F];
|
|
mpAction[2] = m_mpAddrTbl[m_nCursorPos & 0x3F];
|
|
|
|
return mpAction;
|
|
}
|
|
|
|
/***********************************************************
|
|
Terminates the active stream connection.
|
|
***********************************************************/
|
|
void CClient3270::Disconnect()
|
|
{
|
|
m_mpClient->Disconnect();
|
|
}
|
|
|
|
/***********************************************************
|
|
Performs a terminal input action.
|
|
|
|
@param chKeyChar the key character
|
|
|
|
@return TRUE for success, FALSE otherwise
|
|
***********************************************************/
|
|
bool CClient3270::DoInputAction(wchar_t chKeyChar)
|
|
{
|
|
array<Byte>^ mpResp;
|
|
|
|
switch (chKeyChar) {
|
|
|
|
/* Enter */
|
|
case ConsoleKey::Enter:
|
|
/* Send the action */
|
|
mpResp = this->CreateActionArray(IBM3270DS_AID_ENTER);
|
|
mpResp = this->MergeModifiedFields(mpResp);
|
|
m_mpClient->Send(mpResp);
|
|
return true;
|
|
|
|
/* Clear Screen */
|
|
case ConsoleKey::F12:
|
|
/* Send the action */
|
|
mpResp = gcnew array<Byte>(1);
|
|
mpResp[0] = IBM3270DS_AID_CLEAR;
|
|
m_mpClient->Send(mpResp);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/***********************************************************
|
|
Resets all unprotected fields and jumps to the first.
|
|
***********************************************************/
|
|
void CClient3270::EraseAllUnprotected()
|
|
{
|
|
bool fInField = false;
|
|
|
|
for (int i = 0; i < m_mpCharBuffer->Length; i++) {
|
|
/* Look for the start of field character */
|
|
if (m_mpCharBuffer[i]->StartField) {
|
|
/* Reset the MDT of unprotected fields */
|
|
fInField = !m_mpCharBuffer[i]->Protected;
|
|
if (fInField)
|
|
m_mpCharBuffer[i]->Modified = false;
|
|
continue;
|
|
}
|
|
|
|
if (!fInField)
|
|
continue;
|
|
|
|
/* Null out the character */
|
|
m_mpCharBuffer[i]->Character = 0;
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
Clears the character buffer.
|
|
***********************************************************/
|
|
void CClient3270::EraseScreen()
|
|
{
|
|
for (int i = 0; i < m_mpCharBuffer->Length; i++)
|
|
m_mpCharBuffer[i] = gcnew C3270Char();
|
|
|
|
m_nBufferPos = 0;
|
|
m_nCursorPos = 0;
|
|
|
|
this->RepaintScreen();
|
|
}
|
|
|
|
/***********************************************************
|
|
Finds the field start byte from a given offset.
|
|
|
|
@param nOffset the starting offset position
|
|
|
|
@return the start-of-field offset, or -1 if it can't find
|
|
the SOF byte
|
|
***********************************************************/
|
|
int CClient3270::FindStartOfField(int nOffset)
|
|
{
|
|
for (int i = nOffset; i >= 0; i--) {
|
|
if (m_mpCharBuffer[i]->StartField)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/***********************************************************
|
|
Reads the order byte and any attribute bytes and then
|
|
changes the client state accordingly.
|
|
|
|
@param mpData the incoming data buffer
|
|
@param nOffset the starting offset position
|
|
|
|
@return the new starting offset
|
|
***********************************************************/
|
|
int CClient3270::InterpretOrder(array<Byte>^ mpData, int nOffset)
|
|
{
|
|
int nBuffAddr;
|
|
|
|
/* Bounds check */
|
|
if (mpData == nullptr || mpData->Length == 0 || nOffset >= mpData->Length)
|
|
return nOffset;
|
|
|
|
while (true) {
|
|
switch (mpData[nOffset]) {
|
|
|
|
/* Start Field */
|
|
case IBM3270DS_ORDER_SF:
|
|
/* The first byte is a field attribute */
|
|
m_mpCharBuffer[m_nBufferPos] = gcnew C3270Char(mpData[nOffset + 1], true);
|
|
m_nLastField = m_nBufferPos;
|
|
m_nBufferPos++;
|
|
|
|
/* Skip to the data */
|
|
nOffset += 2;
|
|
break;
|
|
|
|
/* Start Field (Extended) */
|
|
case IBM3270DS_ORDER_SFE:
|
|
/* Not implemented */
|
|
nOffset += 3;
|
|
break;
|
|
|
|
/* Set Buffer Address */
|
|
case IBM3270DS_ORDER_SBA:
|
|
m_nBufferPos = this->ConvertBufferAddress(
|
|
mpData[nOffset + 1],
|
|
mpData[nOffset + 2]
|
|
);
|
|
nOffset += 3;
|
|
|
|
break;
|
|
|
|
/* Insert Cursor */
|
|
case IBM3270DS_ORDER_IC:
|
|
/* Set the visible cursor position */
|
|
m_nCursorPos = m_nBufferPos;
|
|
m_nBufferPos++;
|
|
nOffset++;
|
|
break;
|
|
|
|
/* Erase Unprotected Fields */
|
|
case IBM3270DS_ORDER_EUA:
|
|
this->EraseAllUnprotected();
|
|
|
|
/* Jump to the first unprotected field */
|
|
m_nCursorPos = 0;
|
|
this->JumpToNextUnprotectedField();
|
|
nOffset += 3;
|
|
break;
|
|
|
|
/* Modify Field */
|
|
case IBM3270DS_ORDER_MF:
|
|
/* Not implemented */
|
|
nOffset += 6;
|
|
break;
|
|
|
|
/* Repeat to Address */
|
|
case IBM3270DS_ORDER_RA:
|
|
nBuffAddr = this->ConvertBufferAddress(
|
|
mpData[nOffset + 1],
|
|
mpData[nOffset + 2]
|
|
);
|
|
this->RepeatToAddress(mpData[nOffset + 3], nBuffAddr);
|
|
nOffset += 4;
|
|
break;
|
|
|
|
/* Set Attribute */
|
|
case IBM3270DS_ORDER_SA:
|
|
/* Not implemented */
|
|
nOffset += 3;
|
|
break;
|
|
|
|
/* Program Tab */
|
|
case IBM3270DS_ORDER_PT:
|
|
this->JumpToNextUnprotectedField();
|
|
nOffset++;
|
|
break;
|
|
|
|
/* Graphic Escape */
|
|
case IBM3270DS_ORDER_GE:
|
|
/* Not implemented */
|
|
nOffset += 2;
|
|
break;
|
|
|
|
/* Not an order byte */
|
|
default:
|
|
return nOffset;
|
|
}
|
|
|
|
if (nOffset >= mpData->Length)
|
|
return nOffset;
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
Determines if the given field is protected.
|
|
|
|
@param nOffset the offset position in the buffer
|
|
|
|
@return TRUE if protected, FALSE otherwise
|
|
***********************************************************/
|
|
bool CClient3270::IsProtectedField(int nOffset)
|
|
{
|
|
/* If we're at the start of the field... */
|
|
if (m_mpCharBuffer[nOffset]->StartField)
|
|
return m_mpCharBuffer[nOffset]->Protected;
|
|
|
|
/* ... we're not, so find the SOF first */
|
|
int nFieldStart = this->FindStartOfField(nOffset);
|
|
if (nFieldStart == -1)
|
|
return false;
|
|
|
|
return m_mpCharBuffer[nFieldStart]->Protected;
|
|
}
|
|
|
|
/***********************************************************
|
|
Moves the cursor to the next unprotected field.
|
|
***********************************************************/
|
|
void CClient3270::JumpToNextUnprotectedField()
|
|
{
|
|
bool fWrap = false;
|
|
|
|
for (int i = m_nCursorPos + 1; i <= m_mpCharBuffer->Length; i++) {
|
|
/* Wrap to the front if we're past the end */
|
|
if (i == m_mpCharBuffer->Length) {
|
|
fWrap = true;
|
|
i = 0;
|
|
}
|
|
|
|
/* We're looking for start of fields */
|
|
if (fWrap && i >= m_nCursorPos)
|
|
break;
|
|
if (!m_mpCharBuffer[i]->StartField)
|
|
continue;
|
|
|
|
/* Skip protected fields */
|
|
if (m_mpCharBuffer[i]->Protected || m_mpCharBuffer[i]->AutoSkip)
|
|
continue;
|
|
|
|
this->MoveCursor(i - m_nCursorPos + 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
Moves the cursor to the previous unprotected field.
|
|
***********************************************************/
|
|
void CClient3270::JumpToPrevUnprotectedField()
|
|
{
|
|
bool fWrap = false;
|
|
|
|
for (int i = m_nCursorPos - 2; i >= -1; i--) {
|
|
/* Wrap to the end if we're past the front */
|
|
if (i == -1) {
|
|
fWrap = true;
|
|
i = m_mpCharBuffer->Length -1;
|
|
}
|
|
|
|
/* We're looking for start of fields */
|
|
if (fWrap && i <= m_nCursorPos)
|
|
break;
|
|
if (!m_mpCharBuffer[i]->StartField)
|
|
continue;
|
|
|
|
/* Skip protected fields */
|
|
if (m_mpCharBuffer[i]->Protected || m_mpCharBuffer[i]->AutoSkip)
|
|
continue;
|
|
|
|
this->MoveCursor(-1 * (m_nCursorPos - i) + 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
Simulates a key press on the terminal.
|
|
|
|
@param mpArgs the keyboard event arguments
|
|
***********************************************************/
|
|
void CClient3270::KeyPress(KeyPressEventArgs^ mpArgs)
|
|
{
|
|
this->KeyPress(mpArgs, Keys::None);
|
|
}
|
|
|
|
/***********************************************************
|
|
Simulates a key press on the terminal.
|
|
|
|
@param mpArgs the keyboard event arguments
|
|
@param nModifiers bitwise combination of modifier keys
|
|
***********************************************************/
|
|
void CClient3270::KeyPress(KeyPressEventArgs^ mpArgs, Keys nModifiers)
|
|
{
|
|
C3270Char^ mpChar = m_mpCharBuffer[m_nCursorPos];
|
|
int nFieldStart;
|
|
|
|
/* Get the previous cursor position */
|
|
int nCursPrev = (m_nCursorPos == 0 ? m_mpCharBuffer->Length - 1 : m_nCursorPos - 1);
|
|
|
|
/* Handle input actions */
|
|
if (DoInputAction(mpArgs->KeyChar))
|
|
return;
|
|
|
|
/* Handle Tab */
|
|
if (mpArgs->KeyChar == (wchar_t)ConsoleKey::Tab) {
|
|
if (nModifiers == Keys::Shift)
|
|
return this->JumpToPrevUnprotectedField();
|
|
else
|
|
return this->JumpToNextUnprotectedField();
|
|
}
|
|
|
|
/* Handle backspace */
|
|
if (mpArgs->KeyChar == (wchar_t)ConsoleKey::Backspace
|
|
&& !this->IsProtectedField(nCursPrev)
|
|
&& !(nCursPrev < m_nCursorPos && m_mpCharBuffer[nCursPrev]->StartField))
|
|
{
|
|
/* Erase the last character */
|
|
m_mpCharBuffer[nCursPrev]->Character = 0;
|
|
nFieldStart = this->FindStartOfField(nCursPrev);
|
|
if (nFieldStart >= 0)
|
|
m_mpCharBuffer[nFieldStart]->Modified = true;
|
|
return this->MoveCursor(-1);
|
|
}
|
|
|
|
/* Ignore non-characters */
|
|
if (((int)mpArgs->KeyChar) < 32 || ((int)mpArgs->KeyChar) > 126)
|
|
return;
|
|
|
|
/* Check for the numeric-only flag */
|
|
nFieldStart = this->FindStartOfField(m_nCursorPos);
|
|
if (nFieldStart > -1 && m_mpCharBuffer[nFieldStart]->NumericOnly &&
|
|
(((int)mpArgs->KeyChar) < 48 || ((int)mpArgs->KeyChar) > 57))
|
|
return;
|
|
|
|
/* Everything else */
|
|
if (!mpChar->StartField && !this->IsProtectedField(m_nCursorPos)) {
|
|
/* Write the character to the buffer */
|
|
m_mpCharBuffer[m_nCursorPos]->Character = (wchar_t)mpArgs->KeyChar;
|
|
if (nFieldStart != -1)
|
|
m_mpCharBuffer[nFieldStart]->Modified = true;
|
|
this->MoveCursor(1);
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
Builds an array of modified fields for transmission to
|
|
the mainframe.
|
|
|
|
@param mpData the input data array
|
|
***********************************************************/
|
|
array<Byte>^ CClient3270::MergeModifiedFields(array<Byte>^ mpData)
|
|
{
|
|
bool fInField = false;
|
|
bool fCopy = false;
|
|
|
|
for (int i = 0; i < m_mpCharBuffer->Length; i++) {
|
|
/* Mark when we hit a field */
|
|
if (m_mpCharBuffer[i]->StartField)
|
|
fInField = true;
|
|
|
|
/* Look for modified fields */
|
|
if (m_mpCharBuffer[i]->StartField && m_mpCharBuffer[i]->Modified) {
|
|
array<Byte>::Resize(mpData, mpData->Length + 3);
|
|
mpData[mpData->Length - 3] = IBM3270DS_ORDER_SBA;
|
|
mpData[mpData->Length - 2] = m_mpAddrTbl[((i + 1) >> 6) & 0x3F];
|
|
mpData[mpData->Length - 1] = m_mpAddrTbl[(i + 1) & 0x3F];
|
|
fCopy = true;
|
|
|
|
if (fInField)
|
|
continue;
|
|
}
|
|
|
|
/* If we're not in a field, then copy the character, otherwise
|
|
only copy when the copy flag is set. */
|
|
if (fInField && !fCopy)
|
|
continue;
|
|
|
|
/* Skip null characters */
|
|
if (m_mpCharBuffer[i]->Character == NULL) {
|
|
fCopy = false;
|
|
continue;
|
|
}
|
|
|
|
array<Byte>::Resize(mpData, mpData->Length + 1);
|
|
mpData[mpData->Length - 1] =
|
|
CEbcdicTranslator::CharToEBCDIC(m_mpCharBuffer[i]->Character);
|
|
}
|
|
|
|
return mpData;
|
|
}
|
|
|
|
/***********************************************************
|
|
Sets the cursor position.
|
|
|
|
@param nStep the increment value
|
|
***********************************************************/
|
|
void CClient3270::MoveCursor(int nStep)
|
|
{
|
|
/* Move the cursor */
|
|
int nNewPos = m_nCursorPos + nStep;
|
|
if (nNewPos < 0)
|
|
nNewPos = m_mpCharBuffer->Length - 1;
|
|
if (nNewPos >= m_mpCharBuffer->Length)
|
|
nNewPos = 0;
|
|
|
|
/* Find the coordinates */
|
|
int nRow = (int)Math::Floor(nNewPos / this->Size.Width);
|
|
int nCol = nNewPos - (nRow * this->Size.Width);
|
|
|
|
/* Paint the cursor */
|
|
this->MoveCursor(nCol, nRow);
|
|
}
|
|
|
|
/***********************************************************
|
|
Sets the cursor position.
|
|
|
|
@param x the x coordinate (abscissa)
|
|
@param y the y coordinate (ordinate)
|
|
***********************************************************/
|
|
void CClient3270::MoveCursor(int x, int y)
|
|
{
|
|
this->MoveCursor(x, y, false);
|
|
}
|
|
|
|
/***********************************************************
|
|
Sets the cursor position.
|
|
|
|
@param x the x coordinate (abscissa)
|
|
@param y the y coordinate (ordinate)
|
|
@param fRelative the flag to indicate relative position
|
|
***********************************************************/
|
|
void CClient3270::MoveCursor(int x, int y, bool fRelative)
|
|
{
|
|
if (fRelative) {
|
|
int nRow = (int)Math::Floor( m_nCursorPos / this->Size.Width);
|
|
int nCol = m_nCursorPos - (nRow * this->Size.Width);
|
|
|
|
x += nCol;
|
|
if (x < 0)
|
|
x += this->Size.Width;
|
|
if (x >= this->Size.Width)
|
|
x -= this->Size.Width;
|
|
|
|
y += nRow;
|
|
if (y < 0)
|
|
y += this->Size.Height;
|
|
if (y >= this->Size.Height)
|
|
y -= this->Size.Height;
|
|
}
|
|
|
|
int nOldPos = m_nCursorPos;
|
|
m_nCursorPos = (y * this->Size.Width) + x;
|
|
|
|
this->RepaintScreen(nOldPos, nOldPos, false);
|
|
this->RepaintScreen(m_nCursorPos, m_nCursorPos, false);
|
|
}
|
|
|
|
/***********************************************************
|
|
Interprets the 3270 data stream.
|
|
|
|
@param mpData -- the input buffer
|
|
@param nLength -- the length of the stream
|
|
***********************************************************/
|
|
void CClient3270::ParseStream(array<Byte>^ mpData, int nLength)
|
|
{
|
|
/* Is there anything to do? */
|
|
if (mpData->Length == 0)
|
|
return;
|
|
|
|
/* Process the 3270 command */
|
|
switch (mpData[0]) {
|
|
|
|
/* Erase/Write */
|
|
case IBM3270DS_CMD_EW:
|
|
case IBM3270DS_CMD_EW_EBCDIC:
|
|
this->EraseScreen();
|
|
|
|
/* Write */
|
|
case IBM3270DS_CMD_W:
|
|
case IBM3270DS_CMD_W_EBCDIC:
|
|
if (!this->UnserializeWCC(mpData, 1))
|
|
break;
|
|
this->WriteBuffer(mpData, 2, mpData->Length - 2);
|
|
|
|
break;
|
|
|
|
/* Write Structured Field */
|
|
case IBM3270DS_CMD_WSF:
|
|
case IBM3270DS_CMD_WSF_EBCDIC:
|
|
this->WriteStructuredField(mpData, 1);
|
|
break;
|
|
|
|
/* No Operation */
|
|
case IBM3270DS_CMD_NOP:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
Handles a read request for the given screen partition.
|
|
|
|
@param nPartId id of the partition to read
|
|
@param nOpCode the read operation code
|
|
***********************************************************/
|
|
void CClient3270::ReadPartition(int nPartId, int nOpCode)
|
|
{
|
|
switch (nOpCode) {
|
|
|
|
/* Query */
|
|
case IBM3270DS_SF_RP_QUERY:
|
|
/* The partition must be FF */
|
|
if (nPartId != 0xFF)
|
|
break;
|
|
|
|
/* We don't support anything :( */
|
|
array<Byte>^ mpReply = {
|
|
IBM3270DS_AID_SF,
|
|
0x00, /* Length byte */
|
|
0x04, /* Length byte */
|
|
IBM3270DS_SF_QUERY_REPLY,
|
|
IBM3270DS_SF_QR_NULL
|
|
};
|
|
m_mpClient->Send(mpReply);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
The callback function for receiving data.
|
|
|
|
@param mpData the data received
|
|
***********************************************************/
|
|
void CClient3270::ReceiveCallback(array<Byte>^ mpData)
|
|
{
|
|
this->ParseStream(mpData, mpData->Length);
|
|
}
|
|
|
|
/***********************************************************
|
|
Repeats a character until given address.
|
|
|
|
@param mpData the data received
|
|
***********************************************************/
|
|
void CClient3270::RepeatToAddress(Byte chChar, int nStopAddr)
|
|
{
|
|
bool fWrap = (nStopAddr <= m_nBufferPos);
|
|
|
|
for (int i = m_nBufferPos; i < m_mpCharBuffer->Length; i++) {
|
|
/* Write to the character buffer */
|
|
C3270Char^ mpChar = gcnew C3270Char(chChar);
|
|
m_mpCharBuffer[i] = mpChar;
|
|
m_nBufferPos = i + 1;
|
|
|
|
/* Wrap to the top of the buffer */
|
|
if (i + 1 >= m_mpCharBuffer->Length && fWrap) {
|
|
m_nBufferPos = 0;
|
|
i = 0;
|
|
}
|
|
|
|
/* Break out if we're at the stop address */
|
|
if ((nStopAddr > 0 && i == nStopAddr - 1) || (nStopAddr == 0 && i == 0))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
Resets the modified flag on all characters in the buffer.
|
|
***********************************************************/
|
|
void CClient3270::ResetAllFieldMDTs()
|
|
{
|
|
for (int i = 0; i < m_mpCharBuffer->Length; i++)
|
|
m_mpCharBuffer[i]->Modified = false;
|
|
}
|
|
|
|
/***********************************************************
|
|
Draws the character buffer on the canvas.
|
|
***********************************************************/
|
|
void CClient3270::RepaintScreen()
|
|
{
|
|
this->RepaintScreen(0, m_mpCharBuffer->Length - 1, true);
|
|
}
|
|
|
|
/***********************************************************
|
|
Draws the character buffer on the canvas.
|
|
|
|
@param nStartPos the starting position of the pen
|
|
@param nEndPos the last position to redraw
|
|
@param fErase flag to erase the canvas
|
|
***********************************************************/
|
|
void CClient3270::RepaintScreen(int nStartPos, int nEndPos, bool fErase)
|
|
{
|
|
Brush^ mpBrush;
|
|
int nFieldStart;
|
|
bool fMasked;
|
|
|
|
/* Bounds check */
|
|
if (nStartPos < 0 || nEndPos >= m_mpCharBuffer->Length)
|
|
return;
|
|
|
|
/* Notify the client that we're repainting the screen */
|
|
if (m_mpOnPaintCbk != nullptr)
|
|
m_mpOnPaintCbk->Invoke(nStartPos, nEndPos, fErase);
|
|
|
|
if (m_pnlCanvas == nullptr)
|
|
return;
|
|
|
|
Graphics^ mpCanvas = m_pnlCanvas->CreateGraphics();
|
|
if (fErase)
|
|
mpCanvas->Clear(m_pnlCanvas->BackColor);
|
|
|
|
/* Calculate block size */
|
|
float nColWidth =
|
|
(float)m_pnlCanvas->Bounds.Width / (this->Size.Width + 5);
|
|
float nRowHeight =
|
|
(float)m_pnlCanvas->Bounds.Height / (this->Size.Height + 1);
|
|
|
|
/* Calculate the font size */
|
|
Font^ mpFont = gcnew Font(FontFamily::GenericMonospace, 24);
|
|
SizeF^ mpFontSize = mpCanvas->MeasureString("W", mpFont);
|
|
float nNewFontSize = (24 * (nColWidth / mpFontSize->Width)) + 2;
|
|
mpFont = gcnew Font(mpFont->FontFamily, nNewFontSize);
|
|
|
|
/* Draw each character on the canvas */
|
|
for (int i = nStartPos; i <= nEndPos; i++) {
|
|
/* Get the current block */
|
|
int nRow = (int)Math::Floor(i / this->Size.Width);
|
|
int nCol = i - (nRow * this->Size.Width);
|
|
RectangleF^ mpBlock = gcnew RectangleF(
|
|
(float) nCol * nColWidth,
|
|
(float) nRow * nRowHeight,
|
|
(float) nColWidth,
|
|
(float) nRowHeight
|
|
);
|
|
|
|
/* Draw the cursor */
|
|
if (i == m_nCursorPos)
|
|
mpCanvas->FillRectangle(Brushes::Gray, *mpBlock);
|
|
else
|
|
mpCanvas->FillRectangle(Brushes::Black, *mpBlock);
|
|
|
|
/* Get the brush */
|
|
if (m_mpCharBuffer[i]->StartField) {
|
|
mpBrush = m_mpCharBuffer[i]->GetPaintBrush(m_fBaseColor);
|
|
fMasked = false;
|
|
} else {
|
|
nFieldStart = this->FindStartOfField(i);
|
|
mpBrush = (nFieldStart == -1 ? Brushes::Green :
|
|
m_mpCharBuffer[nFieldStart]->GetPaintBrush(m_fBaseColor));
|
|
fMasked = (nFieldStart == -1 ? false :
|
|
!m_mpCharBuffer[nFieldStart]->Protected &&
|
|
m_mpCharBuffer[nFieldStart]->Display == C3270Char::DisplayOptions::Dark);
|
|
}
|
|
|
|
/* And finally, draw the character */
|
|
mpCanvas->DrawString(
|
|
m_mpCharBuffer[i]->ToString(fMasked),
|
|
mpFont,
|
|
mpBrush,
|
|
*mpBlock
|
|
);
|
|
}
|
|
}
|
|
|
|
/***********************************************************
|
|
The callback function for sending data.
|
|
***********************************************************/
|
|
void CClient3270::SendCallback()
|
|
{
|
|
|
|
}
|
|
|
|
/***********************************************************
|
|
Unserializes the flags in the write control character.
|
|
|
|
@param mpData the data buffer to examine
|
|
@param nOffset the offset position to start reading
|
|
|
|
@return TRUE for WCC found, FALSE otherwise
|
|
***********************************************************/
|
|
bool CClient3270::UnserializeWCC(array<Byte>^ mpData, int nOffset)
|
|
{
|
|
/* Check for WCC */
|
|
if (nOffset + 1 >= mpData->Length)
|
|
return false;
|
|
|
|
Byte chWCC = mpData[nOffset];
|
|
|
|
/* Bit 7: Reset MDT */
|
|
if (chWCC & 0x01)
|
|
this->ResetAllFieldMDTs();
|
|
|
|
/* Bit 6: Unlock Keyboard */
|
|
if (chWCC >> 1 & 0x01)
|
|
m_fKeybLocked = false;
|
|
|
|
/* Bit 5: Sound Alarm */
|
|
m_fSoundAlaram = (chWCC >> 2 & 0x01);
|
|
|
|
/* Bit 4: Start Printer */
|
|
m_fStartPrinter = (chWCC >> 3 & 0x01);
|
|
|
|
/* Bits 2-3: Print Line Length */
|
|
if ((chWCC >> 4 & 0x03) == 0x00)
|
|
m_nLineLength = 132;
|
|
else if ((chWCC >> 4 & 0x03) == 0x01)
|
|
m_nLineLength = 40;
|
|
else if ((chWCC >> 4 & 0x03) == 0x02)
|
|
m_nLineLength = 64;
|
|
else if ((chWCC >> 4 & 0x03) == 0x03)
|
|
m_nLineLength = 80;
|
|
|
|
/* Bit 1: WCC Reset (Ignored) */
|
|
|
|
/* Bit 0: Unknown (Ignored) */
|
|
|
|
return true;
|
|
}
|
|
|
|
/***********************************************************
|
|
Writes data to the character buffer.
|
|
|
|
@param mpData the data buffer to examine
|
|
@param nOffset the offset position to start reading
|
|
@param nLength the length of the data to write
|
|
***********************************************************/
|
|
void CClient3270::WriteBuffer(array<Byte>^ mpData, int nOffset, int nLength)
|
|
{
|
|
C3270Char^ mpChar;
|
|
|
|
for (int i = nOffset; i < mpData->Length; i++) {
|
|
/* Make sure we don't exceed the data length */
|
|
if (i >= nLength)
|
|
break;
|
|
|
|
/* Get the order and adjust the position accordingly */
|
|
i = this->InterpretOrder(mpData, i);
|
|
if (i >= mpData->Length)
|
|
break;
|
|
|
|
/* Write to the character buffer */
|
|
mpChar = gcnew C3270Char(mpData[i]);
|
|
m_mpCharBuffer[m_nBufferPos] = mpChar;
|
|
m_nBufferPos++;
|
|
|
|
/* Wrap so we don't go off the screen */
|
|
if (m_nBufferPos >= m_mpCharBuffer->Length)
|
|
m_nBufferPos = 0;
|
|
}
|
|
|
|
this->RepaintScreen();
|
|
}
|
|
|
|
/***********************************************************
|
|
Writes an output structured field.
|
|
|
|
@param mpData the data buffer to examine
|
|
@param nOffset the offset position to start reading
|
|
***********************************************************/
|
|
void CClient3270::WriteStructuredField(array<Byte>^ mpData, int nOffset)
|
|
{
|
|
int nPId;
|
|
int nType;
|
|
|
|
for (int i = nOffset; i < mpData->Length; i++) {
|
|
/* Get the field length */
|
|
if (i + 1 >= mpData->Length)
|
|
break;
|
|
int nLength = (mpData[i] << 8) + mpData[i + 1];
|
|
|
|
/* Check for an invalid field length */
|
|
if (nLength > 0 && nLength < 3)
|
|
break;
|
|
|
|
/* Get the structured field id (one byte first) */
|
|
int nSFId = mpData[i + 2];
|
|
|
|
switch (nSFId) {
|
|
|
|
/* Read Partition */
|
|
case IBM3270DS_SF_READ_PART:
|
|
/* Bounds check */
|
|
if (nLength < 5 || (i + 5) > mpData->Length)
|
|
break;
|
|
|
|
/* Get the partition id and type id */
|
|
nPId = mpData[i + 3];
|
|
nType = mpData[i + 4];
|
|
|
|
this->ReadPartition(nPId, nType);
|
|
break;
|
|
|
|
default:
|
|
/* Unsupported SFID */
|
|
i += nLength;
|
|
continue;
|
|
}
|
|
}
|
|
}
|