//----------------------------------------------------------------------------- // 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)); }