//-----------------------------------------------------------------------------
// Copyright 2007 Jonathan Westhues
//
// This file is part of LDmicro.
//
// LDmicro 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 3 of the License, or
// (at your option) any later version.
//
// LDmicro 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 LDmicro. If not, see .
//------
//
// Routines to simulate the logic interactively, for testing purposes. We can
// simulate in real time, triggering off a Windows timer, or we can
// single-cycle it. The GUI acts differently in simulation mode, to show the
// status of all the signals graphically, show how much time is left on the
// timers, etc.
// Jonathan Westhues, Nov 2004
//-----------------------------------------------------------------------------
#include "linuxUI.h"
//#include
#include
#include
#include
#include "ldmicro.h"
#include "intcode.h"
#include "freezeLD.h"
static struct {
char name[MAX_NAME_LEN];
BOOL powered;
} SingleBitItems[MAX_IO];
static int SingleBitItemsCount;
static struct {
char name[MAX_NAME_LEN];
SWORD val;
DWORD usedFlags;
} Variables[MAX_IO];
static int VariablesCount;
static struct {
char name[MAX_NAME_LEN];
SWORD val;
} AdcShadows[MAX_IO];
static int AdcShadowsCount;
#define VAR_FLAG_TON 0x00000001
#define VAR_FLAG_TOF 0x00000002
#define VAR_FLAG_RTO 0x00000004
#define VAR_FLAG_CTU 0x00000008
#define VAR_FLAG_CTD 0x00000010
#define VAR_FLAG_CTC 0x00000020
#define VAR_FLAG_RES 0x00000040
#define VAR_FLAG_ANY 0x00000080
#define VAR_FLAG_OTHERWISE_FORGOTTEN 0x80000000
// Schematic-drawing code needs to know whether we're in simulation mode or
// note, as that changes how everything is drawn; also UI code, to disable
// editing during simulation.
BOOL InSimulationMode;
// Don't want to redraw the screen unless necessary; track whether a coil
// changed state or a timer output switched to see if anything could have
// changed (not just coil, as we show the intermediate steps too).
static BOOL NeedRedraw;
// Have to let the effects of a coil change in cycle k appear in cycle k+1,
// or set by the UI code to indicate that user manually changed an Xfoo
// input.
BOOL SimulateRedrawAfterNextCycle;
// Don't want to set a timer every 100 us to simulate a 100 us cycle
// time...but we can cycle multiple times per timer interrupt and it will
// be almost as good, as long as everything runs fast.
static int CyclesPerTimerTick;
// Program counter as we evaluate the intermediate code.
static int IntPc;
// A window to allow simulation with the UART stuff (insert keystrokes into
// the program, view the output, like a terminal window).
static HWID UartSimulationWindow;
static HWID UartSimulationTextControl;
static LONG_PTR PrevTextProc;
static int QueuedUartCharacter = -1;
static int SimulateUartTxCountdown = 0;
static void AppendToUartSimulationTextControl(BYTE b);
static void SimulateIntCode(void);
static char *MarkUsedVariable(char *name, DWORD flag);
//-----------------------------------------------------------------------------
// Query the state of a single-bit element (relay, digital in, digital out).
// Looks in the SingleBitItems list; if an item is not present then it is
// FALSE by default.
//-----------------------------------------------------------------------------
static BOOL SingleBitOn(char *name)
{
int i;
for(i = 0; i < SingleBitItemsCount; i++) {
if(strcmp(SingleBitItems[i].name, name)==0) {
return SingleBitItems[i].powered;
}
}
return FALSE;
}
//-----------------------------------------------------------------------------
// Set the state of a single-bit item. Adds it to the list if it is not there
// already.
//-----------------------------------------------------------------------------
static void SetSingleBit(char *name, BOOL state)
{
int i;
for(i = 0; i < SingleBitItemsCount; i++) {
if(strcmp(SingleBitItems[i].name, name)==0) {
SingleBitItems[i].powered = state;
return;
}
}
if(i < MAX_IO) {
strcpy(SingleBitItems[i].name, name);
SingleBitItems[i].powered = state;
SingleBitItemsCount++;
}
}
//-----------------------------------------------------------------------------
// Count a timer up (i.e. increment its associated count by 1). Must already
// exist in the table.
//-----------------------------------------------------------------------------
static void IncrementVariable(char *name)
{
int i;
for(i = 0; i < VariablesCount; i++) {
if(strcmp(Variables[i].name, name)==0) {
(Variables[i].val)++;
return;
}
}
oops();
}
//-----------------------------------------------------------------------------
// Set a variable to a value.
//-----------------------------------------------------------------------------
static void SetSimulationVariable(char *name, SWORD val)
{
int i;
for(i = 0; i < VariablesCount; i++) {
if(strcmp(Variables[i].name, name)==0) {
Variables[i].val = val;
return;
}
}
MarkUsedVariable(name, VAR_FLAG_OTHERWISE_FORGOTTEN);
SetSimulationVariable(name, val);
}
//-----------------------------------------------------------------------------
// Read a variable's value.
//-----------------------------------------------------------------------------
SWORD GetSimulationVariable(char *name)
{
int i;
for(i = 0; i < VariablesCount; i++) {
if(strcmp(Variables[i].name, name)==0) {
return Variables[i].val;
}
}
MarkUsedVariable(name, VAR_FLAG_OTHERWISE_FORGOTTEN);
return GetSimulationVariable(name);
}
//-----------------------------------------------------------------------------
// Set the shadow copy of a variable associated with a READ ADC operation. This
// will get committed to the real copy when the rung-in condition to the
// READ ADC is true.
//-----------------------------------------------------------------------------
void SetAdcShadow(char *name, SWORD val)
{
int i;
for(i = 0; i < AdcShadowsCount; i++) {
if(strcmp(AdcShadows[i].name, name)==0) {
AdcShadows[i].val = val;
return;
}
}
strcpy(AdcShadows[i].name, name);
AdcShadows[i].val = val;
AdcShadowsCount++;
}
//-----------------------------------------------------------------------------
// Return the shadow value of a variable associated with a READ ADC. This is
// what gets copied into the real variable when an ADC read is simulated.
//-----------------------------------------------------------------------------
SWORD GetAdcShadow(char *name)
{
int i;
for(i = 0; i < AdcShadowsCount; i++) {
if(strcmp(AdcShadows[i].name, name)==0) {
return AdcShadows[i].val;
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Mark how a variable is used; a series of flags that we can OR together,
// then we can check to make sure that only valid combinations have been used
// (e.g. just a TON, an RTO with its reset, etc.). Returns NULL for success,
// else an error string.
//-----------------------------------------------------------------------------
static char *MarkUsedVariable(char *name, DWORD flag)
{
int i;
for(i = 0; i < VariablesCount; i++) {
if(strcmp(Variables[i].name, name)==0) {
break;
}
}
if(i >= MAX_IO) return "";
if(i == VariablesCount) {
strcpy(Variables[i].name, name);
Variables[i].usedFlags = 0;
Variables[i].val = 0;
VariablesCount++;
}
switch(flag) {
case VAR_FLAG_TOF:
if(Variables[i].usedFlags != 0)
return _("TOF: variable cannot be used elsewhere");
break;
case VAR_FLAG_TON:
if(Variables[i].usedFlags != 0)
return _("TON: variable cannot be used elsewhere");
break;
case VAR_FLAG_RTO:
if(Variables[i].usedFlags & ~VAR_FLAG_RES)
return _("RTO: variable can only be used for RES elsewhere");
break;
case VAR_FLAG_CTU:
case VAR_FLAG_CTD:
case VAR_FLAG_CTC:
case VAR_FLAG_RES:
case VAR_FLAG_ANY:
break;
case VAR_FLAG_OTHERWISE_FORGOTTEN:
if(name[0] != '$') {
Error(_("Variable '%s' not assigned to, e.g. with a "
"MOV statement, an ADD statement, etc.\r\n\r\n"
"This is probably a programming error; now it "
"will always be zero."), name);
}
break;
default:
oops();
}
Variables[i].usedFlags |= flag;
return NULL;
}
//-----------------------------------------------------------------------------
// Check for duplicate uses of a single variable. For example, there should
// not be two TONs with the same name. On the other hand, it would be okay
// to have an RTO with the same name as its reset; in fact, verify that
// there must be a reset for each RTO.
//-----------------------------------------------------------------------------
static void MarkWithCheck(char *name, int flag)
{
char *s = MarkUsedVariable(name, flag);
if(s) {
Error(_("Variable for '%s' incorrectly assigned: %s."), name, s);
}
}
static void CheckVariableNamesCircuit(int which, void *elem)
{
ElemLeaf *l = (ElemLeaf *)elem;
char *name = NULL;
DWORD flag;
switch(which) {
case ELEM_SERIES_SUBCKT: {
int i;
ElemSubcktSeries *s = (ElemSubcktSeries *)elem;
for(i = 0; i < s->count; i++) {
CheckVariableNamesCircuit(s->contents[i].which,
s->contents[i].d.any);
}
break;
}
case ELEM_PARALLEL_SUBCKT: {
int i;
ElemSubcktParallel *p = (ElemSubcktParallel *)elem;
for(i = 0; i < p->count; i++) {
CheckVariableNamesCircuit(p->contents[i].which,
p->contents[i].d.any);
}
break;
}
case ELEM_RTO:
case ELEM_TOF:
case ELEM_TON:
if(which == ELEM_RTO)
flag = VAR_FLAG_RTO;
else if(which == ELEM_TOF)
flag = VAR_FLAG_TOF;
else if(which == ELEM_TON)
flag = VAR_FLAG_TON;
else oops();
MarkWithCheck(l->d.timer.name, flag);
break;
case ELEM_CTU:
case ELEM_CTD:
case ELEM_CTC:
if(which == ELEM_CTU)
flag = VAR_FLAG_CTU;
else if(which == ELEM_CTD)
flag = VAR_FLAG_CTD;
else if(which == ELEM_CTC)
flag = VAR_FLAG_CTC;
else oops();
MarkWithCheck(l->d.counter.name, flag);
break;
case ELEM_RES:
MarkWithCheck(l->d.reset.name, VAR_FLAG_RES);
break;
case ELEM_MOVE:
MarkWithCheck(l->d.move.dest, VAR_FLAG_ANY);
break;
case ELEM_LOOK_UP_TABLE:
MarkWithCheck(l->d.lookUpTable.dest, VAR_FLAG_ANY);
break;
case ELEM_PIECEWISE_LINEAR:
MarkWithCheck(l->d.piecewiseLinear.dest, VAR_FLAG_ANY);
break;
case ELEM_READ_ADC:
MarkWithCheck(l->d.readAdc.name, VAR_FLAG_ANY);
break;
case ELEM_ADD:
case ELEM_SUB:
case ELEM_MUL:
case ELEM_DIV:
MarkWithCheck(l->d.math.dest, VAR_FLAG_ANY);
break;
case ELEM_UART_RECV:
MarkWithCheck(l->d.uart.name, VAR_FLAG_ANY);
break;
case ELEM_SHIFT_REGISTER: {
int i;
for(i = 1; i < l->d.shiftRegister.stages; i++) {
char str[MAX_NAME_LEN+10];
sprintf(str, "%s%d", l->d.shiftRegister.name, i);
MarkWithCheck(str, VAR_FLAG_ANY);
}
break;
}
case ELEM_PERSIST:
case ELEM_FORMATTED_STRING:
case ELEM_SET_PWM:
case ELEM_MASTER_RELAY:
case ELEM_UART_SEND:
case ELEM_PLACEHOLDER:
case ELEM_COMMENT:
case ELEM_OPEN:
case ELEM_SHORT:
case ELEM_COIL:
case ELEM_CONTACTS:
case ELEM_ONE_SHOT_RISING:
case ELEM_ONE_SHOT_FALLING:
case ELEM_EQU:
case ELEM_NEQ:
case ELEM_GRT:
case ELEM_GEQ:
case ELEM_LES:
case ELEM_LEQ:
break;
default:
oops();
}
}
static void CheckVariableNames(void)
{
int i;
for(i = 0; i < Prog.numRungs; i++) {
CheckVariableNamesCircuit(ELEM_SERIES_SUBCKT, Prog.rungs[i]);
}
}
//-----------------------------------------------------------------------------
// The IF condition is true. Execute the body, up until the ELSE or the
// END IF, and then skip the ELSE if it is present. Called with PC on the
// IF, returns with PC on the END IF.
//-----------------------------------------------------------------------------
static void IfConditionTrue(void)
{
IntPc++;
// now PC is on the first statement of the IF body
SimulateIntCode();
// now PC is on the ELSE or the END IF
if(IntCode[IntPc].op == INT_ELSE) {
int nesting = 1;
for(; ; IntPc++) {
if(IntPc >= IntCodeLen) oops();
if(IntCode[IntPc].op == INT_END_IF) {
nesting--;
} else if(INT_IF_GROUP(IntCode[IntPc].op)) {
nesting++;
}
if(nesting == 0) break;
}
} else if(IntCode[IntPc].op == INT_END_IF) {
return;
} else {
oops();
}
}
//-----------------------------------------------------------------------------
// The IF condition is false. Skip the body, up until the ELSE or the END
// IF, and then execute the ELSE if it is present. Called with PC on the IF,
// returns with PC on the END IF.
//-----------------------------------------------------------------------------
static void IfConditionFalse(void)
{
int nesting = 0;
for(; ; IntPc++) {
if(IntPc >= IntCodeLen) oops();
if(IntCode[IntPc].op == INT_END_IF) {
nesting--;
} else if(INT_IF_GROUP(IntCode[IntPc].op)) {
nesting++;
} else if(IntCode[IntPc].op == INT_ELSE && nesting == 1) {
break;
}
if(nesting == 0) break;
}
// now PC is on the ELSE or the END IF
if(IntCode[IntPc].op == INT_ELSE) {
IntPc++;
SimulateIntCode();
} else if(IntCode[IntPc].op == INT_END_IF) {
return;
} else {
oops();
}
}
//-----------------------------------------------------------------------------
// Evaluate a circuit, calling ourselves recursively to evaluate if/else
// constructs. Updates the on/off state of all the leaf elements in our
// internal tables. Returns when it reaches an end if or an else construct,
// or at the end of the program.
//-----------------------------------------------------------------------------
static void SimulateIntCode(void)
{
for(; IntPc < IntCodeLen; IntPc++) {
IntOp *a = &IntCode[IntPc];
switch(a->op) {
case INT_SIMULATE_NODE_STATE:
if(*(a->poweredAfter) != SingleBitOn(a->name1))
NeedRedraw = TRUE;
*(a->poweredAfter) = SingleBitOn(a->name1);
break;
case INT_SET_BIT:
SetSingleBit(a->name1, TRUE);
break;
case INT_CLEAR_BIT:
SetSingleBit(a->name1, FALSE);
break;
case INT_COPY_BIT_TO_BIT:
SetSingleBit(a->name1, SingleBitOn(a->name2));
break;
case INT_SET_VARIABLE_TO_LITERAL:
if(GetSimulationVariable(a->name1) !=
a->literal && a->name1[0] != '$')
{
NeedRedraw = TRUE;
}
SetSimulationVariable(a->name1, a->literal);
break;
case INT_SET_VARIABLE_TO_VARIABLE:
if(GetSimulationVariable(a->name1) !=
GetSimulationVariable(a->name2))
{
NeedRedraw = TRUE;
}
SetSimulationVariable(a->name1,
GetSimulationVariable(a->name2));
break;
case INT_INCREMENT_VARIABLE:
IncrementVariable(a->name1);
break;
{
SWORD v;
case INT_SET_VARIABLE_ADD:
v = GetSimulationVariable(a->name2) +
GetSimulationVariable(a->name3);
goto math;
case INT_SET_VARIABLE_SUBTRACT:
v = GetSimulationVariable(a->name2) -
GetSimulationVariable(a->name3);
goto math;
case INT_SET_VARIABLE_MULTIPLY:
v = GetSimulationVariable(a->name2) *
GetSimulationVariable(a->name3);
goto math;
case INT_SET_VARIABLE_DIVIDE:
if(GetSimulationVariable(a->name3) != 0) {
v = GetSimulationVariable(a->name2) /
GetSimulationVariable(a->name3);
} else {
v = 0;
Error(_("Division by zero; halting simulation"));
StopSimulation();
}
goto math;
math:
if(GetSimulationVariable(a->name1) != v) {
NeedRedraw = TRUE;
SetSimulationVariable(a->name1, v);
}
break;
}
#define IF_BODY \
{ \
IfConditionTrue(); \
} else { \
IfConditionFalse(); \
}
case INT_IF_BIT_SET:
if(SingleBitOn(a->name1))
IF_BODY
break;
case INT_IF_BIT_CLEAR:
if(!SingleBitOn(a->name1))
IF_BODY
break;
case INT_IF_VARIABLE_LES_LITERAL:
if(GetSimulationVariable(a->name1) < a->literal)
IF_BODY
break;
case INT_IF_VARIABLE_EQUALS_VARIABLE:
if(GetSimulationVariable(a->name1) ==
GetSimulationVariable(a->name2))
IF_BODY
break;
case INT_IF_VARIABLE_GRT_VARIABLE:
if(GetSimulationVariable(a->name1) >
GetSimulationVariable(a->name2))
IF_BODY
break;
case INT_SET_PWM:
// Dummy call will cause a warning if no one ever assigned
// to that variable.
(void)GetSimulationVariable(a->name1);
break;
// Don't try to simulate the EEPROM stuff: just hold the EEPROM
// busy all the time, so that the program never does anything
// with it.
case INT_EEPROM_BUSY_CHECK:
SetSingleBit(a->name1, TRUE);
break;
case INT_EEPROM_READ:
case INT_EEPROM_WRITE:
oops();
break;
case INT_READ_ADC:
// Keep the shadow copies of the ADC variables because in
// the real device they will not be updated until an actual
// read is performed, which occurs only for a true rung-in
// condition there.
SetSimulationVariable(a->name1, GetAdcShadow(a->name1));
break;
case INT_UART_SEND:
if(SingleBitOn(a->name2) && (SimulateUartTxCountdown == 0)) {
SimulateUartTxCountdown = 2;
AppendToUartSimulationTextControl(
(BYTE)GetSimulationVariable(a->name1));
}
if(SimulateUartTxCountdown == 0) {
SetSingleBit(a->name2, FALSE);
} else {
SetSingleBit(a->name2, TRUE);
}
break;
case INT_UART_RECV:
if(QueuedUartCharacter >= 0) {
SetSingleBit(a->name2, TRUE);
SetSimulationVariable(a->name1, (SWORD)QueuedUartCharacter);
QueuedUartCharacter = -1;
} else {
SetSingleBit(a->name2, FALSE);
}
break;
case INT_END_IF:
case INT_ELSE:
return;
case INT_COMMENT:
break;
default:
oops();
break;
}
}
}
//-----------------------------------------------------------------------------
// Called by the Windows timer that triggers cycles when we are running
// in real time.
//-----------------------------------------------------------------------------
BOOL PlcCycleTimer(BOOL kill = FALSE)
{
for(int i = 0; i < CyclesPerTimerTick; i++) {
SimulateOneCycle(FALSE);
}
return !kill;
}
//-----------------------------------------------------------------------------
// Simulate one cycle of the PLC. Update everything, and keep track of whether
// any outputs have changed. If so, force a screen refresh. If requested do
// a screen refresh regardless.
//-----------------------------------------------------------------------------
void SimulateOneCycle(BOOL forceRefresh)
{
// When there is an error message up, the modal dialog makes its own
// event loop, and there is risk that we would go recursive. So let
// us fix that. (Note that there are no concurrency issues; we really
// would get called recursively, not just reentrantly.)
static BOOL Simulating = FALSE;
if(Simulating) return;
Simulating = TRUE;
NeedRedraw = FALSE;
if(SimulateUartTxCountdown > 0) {
SimulateUartTxCountdown--;
} else {
SimulateUartTxCountdown = 0;
}
IntPc = 0;
SimulateIntCode();
if(NeedRedraw || SimulateRedrawAfterNextCycle || forceRefresh) {
InvalidateRect(DrawWindow, NULL, FALSE);
RefreshControlsToSettings();
gtk_widget_queue_draw(DrawWindow);
// ListView_RedrawItems(IoList, 0, Prog.io.count - 1);
}
SimulateRedrawAfterNextCycle = FALSE;
if(NeedRedraw) SimulateRedrawAfterNextCycle = TRUE;
Simulating = FALSE;
}
//-----------------------------------------------------------------------------
// Start the timer that we use to trigger PLC cycles in approximately real
// time. Independently of the given cycle time, just go at 40 Hz, since that
// is about as fast as anyone could follow by eye. Faster timers will just
// go instantly.
//-----------------------------------------------------------------------------
void StartSimulationTimer(void)
{
int p = Prog.cycleTime/1000;
if(p < 5) {
SetTimer(MainWindow, TIMER_SIMULATE, 10, PlcCycleTimer);
CyclesPerTimerTick = 10000 / Prog.cycleTime;
} else {
SetTimer(MainWindow, TIMER_SIMULATE, p, PlcCycleTimer);
CyclesPerTimerTick = 1;
}
}
//-----------------------------------------------------------------------------
// Clear out all the parameters relating to the previous simulation.
//-----------------------------------------------------------------------------
void ClearSimulationData(void)
{
VariablesCount = 0;
SingleBitItemsCount = 0;
AdcShadowsCount = 0;
QueuedUartCharacter = -1;
SimulateUartTxCountdown = 0;
CheckVariableNames();
SimulateRedrawAfterNextCycle = TRUE;
if(!GenerateIntermediateCode()) {
ToggleSimulationMode();
return;
}
SimulateOneCycle(TRUE);
}
//-----------------------------------------------------------------------------
// Provide a description for an item (Xcontacts, Ycoil, Rrelay, Ttimer,
// or other) in the I/O list.
//-----------------------------------------------------------------------------
void DescribeForIoList(char *name, char *out)
{
switch(name[0]) {
case 'R':
case 'X':
case 'Y':
sprintf(out, "%d", SingleBitOn(name));
break;
case 'T': {
double dtms = GetSimulationVariable(name) *
(Prog.cycleTime / 1000.0);
if(dtms < 1000) {
sprintf(out, "%.2f ms", dtms);
} else {
sprintf(out, "%.3f s", dtms / 1000);
}
break;
}
default: {
SWORD v = GetSimulationVariable(name);
sprintf(out, "%hd (0x%04hx)", v, v);
break;
}
}
}
//-----------------------------------------------------------------------------
// Toggle the state of a contact input; for simulation purposes, so that we
// can set the input state of the program.
//-----------------------------------------------------------------------------
void SimulationToggleContact(char *name)
{
SetSingleBit(name, !SingleBitOn(name));
// ListView_RedrawItems(IoList, 0, Prog.io.count - 1);
}
//-----------------------------------------------------------------------------
// Dialog proc for the popup that lets you interact with the UART stuff.
//-----------------------------------------------------------------------------
// static LRESULT CALLBACK UartSimulationProc(HWND hwnd, UINT msg,
// WPARAM wParam, LPARAM lParam)
// {
// switch (msg) {
// case WM_DESTROY:
// DestroyUartSimulationWindow();
// break;
// case WM_CLOSE:
// break;
// case WM_SIZE:
// MoveWindow(UartSimulationTextControl, 0, 0, LOWORD(lParam),
// HIWORD(lParam), TRUE);
// break;
// case WM_ACTIVATE:
// if(wParam != WA_INACTIVE) {
// SetFocus(UartSimulationTextControl);
// }
// break;
// default:
// return DefWindowProc(hwnd, msg, wParam, lParam);
// }
// return 1;
// }
//-----------------------------------------------------------------------------
// Intercept WM_CHAR messages that to the terminal simulation window so that
// we can redirect them to the PLC program.
//
// Ported: Read and write text fron the text view widget.
//-----------------------------------------------------------------------------
static void UartSimulationTextProc(HWID hwid, UINT umsg, char *text, UINT uszbuf)
{
switch(umsg)
{
case WM_SETTEXT:
{
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(hwid));
gtk_text_buffer_set_text (buffer, text, -1);
gtk_text_view_set_buffer (GTK_TEXT_VIEW(hwid), buffer);
GtkTextIter end;
gtk_text_buffer_get_end_iter (buffer, &end);
gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW(hwid), &end, 0.2, FALSE, 1, 1);
break;
}
case WM_SETTEXT_END:
{
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(hwid));
gtk_text_buffer_insert_at_cursor (buffer, text, -1);
gtk_text_view_set_buffer (GTK_TEXT_VIEW(hwid), buffer);
GtkTextIter end;
gtk_text_buffer_get_end_iter (buffer, &end);
gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW(hwid), &end, 0.2, FALSE, 1, 1);
break;
}
case WM_GETTEXT:
{
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(hwid));
GtkTextIter start, end;
gtk_text_buffer_get_start_iter (buffer, &start);
gtk_text_buffer_get_end_iter (buffer, &end);
char *txtBuf = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
strcpy(text, txtBuf);
strcat(text, "\0");
g_free(txtBuf);
break;
}
default:
break;
}
}
//-----------------------------------------------------------------------------
// Pop up the UART simulation window; like a terminal window where the
// characters that you type go into UART RECV instruction and whatever
// the program puts into UART SEND shows up as text.
//-----------------------------------------------------------------------------
void ShowUartSimulationWindow(void)
{
DWORD TerminalX = 200, TerminalY = 200, TerminalW = 300, TerminalH = 150;
ThawDWORD(TerminalX);
ThawDWORD(TerminalY);
ThawDWORD(TerminalW);
ThawDWORD(TerminalH);
if(TerminalW > 800) TerminalW = 100;
if(TerminalH > 800) TerminalH = 100;
UartSimulationWindow = CreateWindowClient(GTK_WINDOW_TOPLEVEL, GDK_WINDOW_TYPE_HINT_NORMAL,
"UART Simulation (Terminal)", TerminalX, TerminalY, TerminalW, TerminalH, NULL);
/// remove close button
gtk_window_set_deletable (GTK_WINDOW(UartSimulationWindow), FALSE);
UartSimulationTextControl = gtk_text_view_new();
gtk_widget_override_font(GTK_WIDGET(UartSimulationTextControl), pango_font_description_from_string("Lucida Console"));
/// Add text view into a scrolled window to enable scrolling functionality
HWID TextViewScroll = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (TextViewScroll),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_widget_set_hexpand(GTK_WIDGET(TextViewScroll), TRUE);
gtk_widget_set_vexpand(GTK_WIDGET(TextViewScroll), TRUE);
gtk_container_add (GTK_CONTAINER(TextViewScroll), UartSimulationTextControl);
gtk_container_add (GTK_CONTAINER(UartSimulationWindow), TextViewScroll);
gtk_widget_show_all(UartSimulationWindow);
gtk_window_set_keep_above (GTK_WINDOW(MainWindow), TRUE);
gtk_window_set_focus_visible (GTK_WINDOW(MainWindow), TRUE);
gtk_window_set_keep_above (GTK_WINDOW(MainWindow), FALSE);
}
//-----------------------------------------------------------------------------
// Get rid of the UART simulation terminal-type window.
//-----------------------------------------------------------------------------
void DestroyUartSimulationWindow(void)
{
// Try not to destroy the window if it is already destroyed; that is
// not for the sake of the window, but so that we don't trash the
// stored position.
if(UartSimulationWindow == NULL) return;
DWORD TerminalX, TerminalY, TerminalW, TerminalH;
RECT r;
GetClientRect(UartSimulationWindow, &r);
TerminalW = r.right - r.left;
TerminalH = r.bottom - r.top;
GetWindowRect(UartSimulationWindow, &r);
TerminalX = r.left;
TerminalY = r.top;
FreezeDWORD(TerminalX);
FreezeDWORD(TerminalY);
FreezeDWORD(TerminalW);
FreezeDWORD(TerminalH);
DestroyWindow(UartSimulationWindow);
UartSimulationWindow = NULL;
}
//-----------------------------------------------------------------------------
// Append a received character to the terminal buffer.
//-----------------------------------------------------------------------------
static void AppendToUartSimulationTextControl(BYTE b)
{
char append[5];
if((isalnum(b) || strchr("[]{};':\",.<>/?`~ !@#$%^&*()-=_+|", b) ||
b == '\r' || b == '\n') && b != '\0')
{
append[0] = b;
append[1] = '\0';
} else {
sprintf(append, "\\x%02x", b);
}
#define MAX_SCROLLBACK 256
char buf[MAX_SCROLLBACK];
UartSimulationTextProc(UartSimulationTextControl, WM_GETTEXT, buf, strlen(buf));
int overBy = (strlen(buf) + strlen(append) + 1) - sizeof(buf);
if(overBy > 0) {
memmove(buf, buf + overBy, strlen(buf));
}
strcat(buf, append);
UartSimulationTextProc(UartSimulationTextControl, WM_SETTEXT, buf, strlen(buf));
}