diff options
Diffstat (limited to 'ldmicro/schematic.cpp')
-rw-r--r-- | ldmicro/schematic.cpp | 729 |
1 files changed, 729 insertions, 0 deletions
diff --git a/ldmicro/schematic.cpp b/ldmicro/schematic.cpp new file mode 100644 index 0000000..9c5e9b3 --- /dev/null +++ b/ldmicro/schematic.cpp @@ -0,0 +1,729 @@ +//----------------------------------------------------------------------------- +// 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 <http://www.gnu.org/licenses/>. +//------ +// +// Stuff for editing the schematic, mostly tying in with the cursor somehow. +// Actual manipulation of circuit elements happens in circuit.cpp, though. +// Jonathan Westhues, Oct 2004 +//----------------------------------------------------------------------------- +#include "linuxUI.h" +#include <stdio.h> +#include <stdlib.h> +#include <iostream> + +#include "ldmicro.h" + +using namespace std; + +// Not all options all available e.g. can't delete the only relay coil in +// a rung, can't insert two coils in series, etc. Keep track of what is +// allowed so we don't corrupt our program. +BOOL CanInsertEnd; +BOOL CanInsertOther; +BOOL CanInsertComment; + +// Ladder logic program is laid out on a grid program; this matrix tells +// us which leaf element is in which box on the grid, which allows us +// to determine what element has just been selected when the user clicks +// on something, for example. +ElemLeaf *DisplayMatrix[DISPLAY_MATRIX_X_SIZE][DISPLAY_MATRIX_Y_SIZE]; +int DisplayMatrixWhich[DISPLAY_MATRIX_X_SIZE][DISPLAY_MATRIX_Y_SIZE]; +ElemLeaf *Selected; +int SelectedWhich; + +ElemLeaf DisplayMatrixFiller; + +// Cursor within the ladder logic program. The paint routines figure out +// where the cursor should go and calculate the coordinates (in pixels) +// of the rectangle that describes it; then BlinkCursor just has to XOR +// the requested rectangle at periodic intervals. +PlcCursor Cursor; + +//----------------------------------------------------------------------------- +// Find the address in the DisplayMatrix of the selected leaf element. Set +// *gx and *gy if we succeed and return TRUE, else return FALSE. +//----------------------------------------------------------------------------- +/*BOOL FindSelected(int *gx, int *gy) +{ + if(!Selected) return FALSE; + + int i, j; + for(i = 0; i < DISPLAY_MATRIX_X_SIZE; i++) { + for(j = 0; j < DISPLAY_MATRIX_Y_SIZE; j++) { + if(DisplayMatrix[i][j] == Selected) { + while(DisplayMatrix[i+1][j] == Selected) { + i++; + } + *gx = i; + *gy = j; + return TRUE; + } + } + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Select the item in DisplayMatrix at (gx, gy), and then update everything +// to reflect that. In particular change the enabled state of the menus so +// that those items that do not apply are grayed, and scroll the element in +// to view. +//----------------------------------------------------------------------------- +void SelectElement(int gx, int gy, int state) +{ + if(gx < 0 || gy < 0) { + if(!FindSelected(&gx, &gy)) { + return; + } + } + + if(Selected) Selected->selectedState = SELECTED_NONE; + + Selected = DisplayMatrix[gx][gy]; + SelectedWhich = DisplayMatrixWhich[gx][gy]; + + if(SelectedWhich == ELEM_PLACEHOLDER) { + state = SELECTED_LEFT; + } + + if((gy - ScrollYOffset) >= ScreenRowsAvailable()) { + ScrollYOffset = gy - ScreenRowsAvailable() + 1; + RefreshScrollbars(); + } + if((gy - ScrollYOffset) < 0) { + ScrollYOffset = gy; + RefreshScrollbars(); + } + if((gx - ScrollXOffset*POS_WIDTH*FONT_WIDTH) >= ScreenColsAvailable()) { + ScrollXOffset = gx*POS_WIDTH*FONT_WIDTH - ScreenColsAvailable(); + RefreshScrollbars(); + } + if((gx - ScrollXOffset*POS_WIDTH*FONT_WIDTH) < 0) { + ScrollXOffset = gx*POS_WIDTH*FONT_WIDTH; + RefreshScrollbars(); + } + + ok(); + Selected->selectedState = state; + ok(); + + WhatCanWeDoFromCursorAndTopology(); +} + +//----------------------------------------------------------------------------- +// Must be called every time the cursor moves or the cursor stays the same +// but the circuit topology changes under it. Determines what we are allowed +// to do: where coils can be added, etc. +//----------------------------------------------------------------------------- +void WhatCanWeDoFromCursorAndTopology(void) +{ + BOOL canNegate = FALSE, canNormal = FALSE; + BOOL canResetOnly = FALSE, canSetOnly = FALSE; + BOOL canPushUp = TRUE, canPushDown = TRUE; + + BOOL canDelete = TRUE; + + int i = RungContainingSelected(); + if(i >= 0) { + if(i == 0) canPushUp = FALSE; + if(i == (Prog.numRungs-1)) canPushDown = FALSE; + + if(Prog.rungs[i]->count == 1 && + Prog.rungs[i]->contents[0].which == ELEM_PLACEHOLDER) + { + canDelete = FALSE; + } + } + + CanInsertEnd = FALSE; + CanInsertOther = TRUE; + + if(Selected && + (SelectedWhich == ELEM_COIL || + SelectedWhich == ELEM_RES || + SelectedWhich == ELEM_ADD || + SelectedWhich == ELEM_SUB || + SelectedWhich == ELEM_MUL || + SelectedWhich == ELEM_DIV || + SelectedWhich == ELEM_CTC || + SelectedWhich == ELEM_READ_ADC || + SelectedWhich == ELEM_SET_PWM || + SelectedWhich == ELEM_MASTER_RELAY || + SelectedWhich == ELEM_SHIFT_REGISTER || + SelectedWhich == ELEM_LOOK_UP_TABLE || + SelectedWhich == ELEM_PIECEWISE_LINEAR || + SelectedWhich == ELEM_PERSIST || + SelectedWhich == ELEM_MOVE)) + { + if(SelectedWhich == ELEM_COIL) { + canNegate = TRUE; + canNormal = TRUE; + canResetOnly = TRUE; + canSetOnly = TRUE; + } + + if(Selected->selectedState == SELECTED_ABOVE || + Selected->selectedState == SELECTED_BELOW) + { + CanInsertEnd = TRUE; + CanInsertOther = FALSE; + } else if(Selected->selectedState == SELECTED_RIGHT) { + CanInsertEnd = FALSE; + CanInsertOther = FALSE; + } + } else if(Selected) { + if(Selected->selectedState == SELECTED_RIGHT || + SelectedWhich == ELEM_PLACEHOLDER) + { + CanInsertEnd = ItemIsLastInCircuit(Selected); + } + } + if(SelectedWhich == ELEM_CONTACTS) { + canNegate = TRUE; + canNormal = TRUE; + } + if(SelectedWhich == ELEM_PLACEHOLDER) { + // a comment must be the only element in its rung, and it will fill + // the rung entirely + CanInsertComment = TRUE; + } else { + CanInsertComment = FALSE; + } + if(SelectedWhich == ELEM_COMMENT) { + // if there's a comment there already then don't let anything else + // into the rung + CanInsertEnd = FALSE; + CanInsertOther = FALSE; + } + SetMenusEnabled(canNegate, canNormal, canResetOnly, canSetOnly, canDelete, + CanInsertEnd, CanInsertOther, canPushDown, canPushUp, CanInsertComment); +} +*/ +//----------------------------------------------------------------------------- +// Rub out freed element from the DisplayMatrix, just so we don't confuse +// ourselves too much (or access freed memory)... +//----------------------------------------------------------------------------- +void ForgetFromGrid(void *p) +{ + int i, j; + for(i = 0; i < DISPLAY_MATRIX_X_SIZE; i++) { + for(j = 0; j < DISPLAY_MATRIX_Y_SIZE; j++) { + if(DisplayMatrix[i][j] == p) { + DisplayMatrix[i][j] = NULL; + } + } + } + if(Selected == p) { + Selected = NULL; + } +} +//----------------------------------------------------------------------------- +// Rub out everything from DisplayMatrix. If we don't do that before freeing +// the program (e.g. when loading a new file) then there is a race condition +// when we repaint. +//----------------------------------------------------------------------------- +void ForgetEverything(void) +{ + memset(DisplayMatrix, 0, sizeof(DisplayMatrix)); + memset(DisplayMatrixWhich, 0, sizeof(DisplayMatrixWhich)); + Selected = NULL; + SelectedWhich = 0; +} + +//----------------------------------------------------------------------------- +// Select the top left element of the program. Returns TRUE if it was able +// to do so, FALSE if not. The latter occurs given a completely empty +// program. +//----------------------------------------------------------------------------- +/*BOOL MoveCursorTopLeft(void) +{ + int i, j; + // Let us first try to place it somewhere on-screen, so start at the + // vertical scroll offset, not the very top (to avoid placing the + // cursor in a position that would force us to scroll to put it in to + // view.) + for(i = 0; i < DISPLAY_MATRIX_X_SIZE; i++) { + for(j = ScrollYOffset; + j < DISPLAY_MATRIX_Y_SIZE && j < (ScrollYOffset+16); j++) + { + if(VALID_LEAF(DisplayMatrix[i][j])) { + SelectElement(i, j, SELECTED_LEFT); + return TRUE; + } + } + } + + // If that didn't work, then try anywhere on the diagram before giving + // up entirely. + for(i = 0; i < DISPLAY_MATRIX_X_SIZE; i++) { + for(j = 0; j < 16; j++) { + if(VALID_LEAF(DisplayMatrix[i][j])) { + SelectElement(i, j, SELECTED_LEFT); + return TRUE; + } + } + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Move the cursor in response to a keyboard command (arrow keys). Basically +// we move the cursor within the currently selected element until we hit +// the edge (e.g. left edge for VK_LEFT), and then we see if we can select +// a new item that would let us keep going. +//----------------------------------------------------------------------------- +void MoveCursorKeyboard(int keyCode) +{ + if(!Selected || Selected->selectedState == SELECTED_NONE) { + MoveCursorTopLeft(); + return; + } + + switch(keyCode) { + case VK_LEFT: { + if(!Selected || Selected->selectedState == SELECTED_NONE) { + break; + } + if(Selected->selectedState != SELECTED_LEFT) { + SelectElement(-1, -1, SELECTED_LEFT); + break; + } + if(SelectedWhich == ELEM_COMMENT) break; + int i, j; + if(FindSelected(&i, &j)) { + i--; + while(i >= 0 && (!VALID_LEAF(DisplayMatrix[i][j]) || + (DisplayMatrix[i][j] == Selected))) + { + i--; + } + if(i >= 0) { + SelectElement(i, j, SELECTED_RIGHT); + } + } + break; + } + case VK_RIGHT: { + if(!Selected || Selected->selectedState == SELECTED_NONE) { + break; + } + if(Selected->selectedState != SELECTED_RIGHT) { + SelectElement(-1, -1, SELECTED_RIGHT); + break; + } + if(SelectedWhich == ELEM_COMMENT) break; + int i, j; + if(FindSelected(&i, &j)) { + i++; + while(i < DISPLAY_MATRIX_X_SIZE && + !VALID_LEAF(DisplayMatrix[i][j])) + { + i++; + } + if(i != DISPLAY_MATRIX_X_SIZE) { + SelectElement(i, j, SELECTED_LEFT); + } + } + break; + } + case VK_UP: { + if(!Selected || Selected->selectedState == SELECTED_NONE) { + break; + } + if(Selected->selectedState != SELECTED_ABOVE && + SelectedWhich != ELEM_PLACEHOLDER) + { + SelectElement(-1, -1, SELECTED_ABOVE); + break; + } + int i, j; + if(FindSelected(&i, &j)) { + j--; + while(j >= 0 && !VALID_LEAF(DisplayMatrix[i][j])) + j--; + if(j >= 0) { + SelectElement(i, j, SELECTED_BELOW); + } + } + break; + } + case VK_DOWN: { + if(!Selected || Selected->selectedState == SELECTED_NONE) { + break; + } + if(Selected->selectedState != SELECTED_BELOW && + SelectedWhich != ELEM_PLACEHOLDER) + { + SelectElement(-1, -1, SELECTED_BELOW); + break; + } + int i, j; + if(FindSelected(&i, &j)) { + j++; + while(j < DISPLAY_MATRIX_Y_SIZE && + !VALID_LEAF(DisplayMatrix[i][j])) + { + j++; + } + if(j != DISPLAY_MATRIX_Y_SIZE) { + SelectElement(i, j, SELECTED_ABOVE); + } else if(ScrollYOffsetMax - ScrollYOffset < 3) { + // special case: scroll the end marker into view + ScrollYOffset = ScrollYOffsetMax; + RefreshScrollbars(); + } + } + break; + } + } +} + +//----------------------------------------------------------------------------- +// Edit the selected element. Pop up the appropriate modal dialog box to do +// this. +//----------------------------------------------------------------------------- + +void EditSelectedElement(void) +{ + if(!Selected || Selected->selectedState == SELECTED_NONE) return; + + switch(SelectedWhich) { + case ELEM_COMMENT: + ShowCommentDialog(Selected->d.comment.str); + break; + + case ELEM_CONTACTS: + ShowContactsDialog(&(Selected->d.contacts.negated), + Selected->d.contacts.name); + break; + + case ELEM_COIL: + ShowCoilDialog(&(Selected->d.coil.negated), + &(Selected->d.coil.setOnly), &(Selected->d.coil.resetOnly), + Selected->d.coil.name); + break; + + case ELEM_TON: + case ELEM_TOF: + case ELEM_RTO: + ShowTimerDialog(SelectedWhich, &(Selected->d.timer.delay), + Selected->d.timer.name); + break; + + case ELEM_CTU: + case ELEM_CTD: + case ELEM_CTC: + ShowCounterDialog(SelectedWhich, &(Selected->d.counter.max), + Selected->d.counter.name); + break; + + case ELEM_EQU: + case ELEM_NEQ: + case ELEM_GRT: + case ELEM_GEQ: + case ELEM_LES: + case ELEM_LEQ: + ShowCmpDialog(SelectedWhich, Selected->d.cmp.op1, + Selected->d.cmp.op2); + break; + + case ELEM_ADD: + case ELEM_SUB: + case ELEM_MUL: + case ELEM_DIV: + ShowMathDialog(SelectedWhich, Selected->d.math.dest, + Selected->d.math.op1, Selected->d.math.op2); + break; + + case ELEM_RES: + ShowResetDialog(Selected->d.reset.name); + break; + + case ELEM_MOVE: + ShowMoveDialog(Selected->d.move.dest, Selected->d.move.src); + break; + + case ELEM_SET_PWM: + ShowSetPwmDialog(Selected->d.setPwm.name, + &(Selected->d.setPwm.targetFreq)); + break; + + case ELEM_READ_ADC: + ShowReadAdcDialog(Selected->d.readAdc.name+1); + break; + + case ELEM_UART_RECV: + case ELEM_UART_SEND: + ShowUartDialog(SelectedWhich, Selected->d.uart.name); + break; + + case ELEM_PERSIST: + ShowPersistDialog(Selected->d.persist.var); + break; + + case ELEM_SHIFT_REGISTER: + ShowShiftRegisterDialog(Selected->d.shiftRegister.name, + &(Selected->d.shiftRegister.stages)); + break; + + case ELEM_FORMATTED_STRING: + ShowFormattedStringDialog(Selected->d.fmtdStr.var, + Selected->d.fmtdStr.string); + break; + + case ELEM_PIECEWISE_LINEAR: + ShowPiecewiseLinearDialog(Selected); + break; + + case ELEM_LOOK_UP_TABLE: + ShowLookUpTableDialog(Selected); + break; + } +} + +//----------------------------------------------------------------------------- +// Edit the element under the mouse cursor. This will typically be the +// selected element, since the first click would have selected it. If there +// is no element under the mouser cursor then do nothing; do not edit the +// selected element (which is elsewhere on screen), as that would be +// confusing. +//----------------------------------------------------------------------------- +void EditElementMouseDoubleclick(int x, int y) +{ + x += ScrollXOffset; + + y += FONT_HEIGHT/2; + + int gx = (x - X_PADDING)/(POS_WIDTH*FONT_WIDTH); + int gy = (y - Y_PADDING)/(POS_HEIGHT*FONT_HEIGHT); + + gy += ScrollYOffset; + + if(InSimulationMode) { + ElemLeaf *l = DisplayMatrix[gx][gy]; + if(l && DisplayMatrixWhich[gx][gy] == ELEM_CONTACTS) { + char *name = l->d.contacts.name; + if(name[0] == 'X') { + SimulationToggleContact(name); + } + } else if(l && DisplayMatrixWhich[gx][gy] == ELEM_READ_ADC) { + ShowAnalogSliderPopup(l->d.readAdc.name); + } + } else { + if(DisplayMatrix[gx][gy] == Selected) { + EditSelectedElement(); + } + } +} + +//----------------------------------------------------------------------------- +// Move the cursor in response to a mouse click. If they clicked in a leaf +// element box then figure out which edge they're closest too (with appropriate +// fudge factors to make all edges equally easy to click on) and put the +// cursor there. +//----------------------------------------------------------------------------- +void MoveCursorMouseClick(int x, int y) +{ + x += ScrollXOffset; + + y += FONT_HEIGHT/2; + + int gx0 = (x - X_PADDING)/(POS_WIDTH*FONT_WIDTH); + int gy0 = (y - Y_PADDING)/(POS_HEIGHT*FONT_HEIGHT); + + int gx = gx0; + int gy = gy0 + ScrollYOffset; + + if(VALID_LEAF(DisplayMatrix[gx][gy])) { + int i, j; + for(i = 0; i < DISPLAY_MATRIX_X_SIZE; i++) { + for(j = 0; j < DISPLAY_MATRIX_Y_SIZE; j++) { + if(DisplayMatrix[i][j]) + DisplayMatrix[i][j]->selectedState = SELECTED_NONE; + } + } + int dx = x - (gx0*POS_WIDTH*FONT_WIDTH + X_PADDING); + int dy = y - (gy0*POS_HEIGHT*FONT_HEIGHT + Y_PADDING); + + int dtop = dy; + int dbottom = POS_HEIGHT*FONT_HEIGHT - dy; + int dleft = dx; + int dright = POS_WIDTH*FONT_WIDTH - dx; + + int extra = 1; + if(DisplayMatrixWhich[gx][gy] == ELEM_COMMENT) { + dleft += gx*POS_WIDTH*FONT_WIDTH; + dright += (ColsAvailable - gx - 1)*POS_WIDTH*FONT_WIDTH; + extra = ColsAvailable; + } else { + if((gx > 0) && (DisplayMatrix[gx-1][gy] == DisplayMatrix[gx][gy])) { + dleft += POS_WIDTH*FONT_WIDTH; + extra = 2; + } + if((gx < (DISPLAY_MATRIX_X_SIZE-1)) && + (DisplayMatrix[gx+1][gy] == DisplayMatrix[gx][gy])) + { + dright += POS_WIDTH*FONT_WIDTH; + extra = 2; + } + } + + int decideX = (dright - dleft); + int decideY = (dtop - dbottom); + + decideY = decideY*3*extra; + + int state; + if(abs(decideY) > abs(decideX)) { + if(decideY > 0) { + state = SELECTED_BELOW; + } else { + state = SELECTED_ABOVE; + } + } else { + if(decideX > 0) { + state = SELECTED_LEFT; + } else { + state = SELECTED_RIGHT; + } + } + SelectElement(gx, gy, state); + } +} + +//----------------------------------------------------------------------------- +// Place the cursor as near to the given point on the grid as possible. Used +// after deleting an element, for example. +//----------------------------------------------------------------------------- +void MoveCursorNear(int gx, int gy) +{ + int out = 0; + + for(out = 0; out < 8; out++) { + if(gx - out >= 0) { + if(VALID_LEAF(DisplayMatrix[gx-out][gy])) { + SelectElement(gx-out, gy, SELECTED_RIGHT); + return; + } + } + if(gx + out < DISPLAY_MATRIX_X_SIZE) { + if(VALID_LEAF(DisplayMatrix[gx+out][gy])) { + SelectElement(gx+out, gy, SELECTED_LEFT); + return; + } + } + if(gy - out >= 0) { + if(VALID_LEAF(DisplayMatrix[gx][gy-out])) { + SelectElement(gx, gy-out, SELECTED_BELOW); + return; + } + } + if(gy + out < DISPLAY_MATRIX_Y_SIZE) { + if(VALID_LEAF(DisplayMatrix[gx][gy+out])) { + SelectElement(gx, gy+out, SELECTED_ABOVE); + return; + } + } + + if(out == 1) { + // Now see if we have a straight shot to the right; might be far + // if we have to go up to a coil or other end of line element. + int across; + for(across = 1; gx+across < DISPLAY_MATRIX_X_SIZE; across++) { + if(VALID_LEAF(DisplayMatrix[gx+across][gy])) { + SelectElement(gx+across, gy, SELECTED_LEFT); + return; + } + if(!DisplayMatrix[gx+across][gy]) break; + } + } + } + + MoveCursorTopLeft(); +} + +//----------------------------------------------------------------------------- +// Negate the selected item, if this is meaningful. +//----------------------------------------------------------------------------- +void NegateSelected(void) +{ + switch(SelectedWhich) { + case ELEM_CONTACTS: + Selected->d.contacts.negated = TRUE; + break; + + case ELEM_COIL: { + ElemCoil *c = &Selected->d.coil; + c->negated = TRUE; + c->resetOnly = FALSE; + c->setOnly = FALSE; + break; + } + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Make the item selected normal: not negated, not set/reset only. +//----------------------------------------------------------------------------- +void MakeNormalSelected(void) +{ + switch(SelectedWhich) { + case ELEM_CONTACTS: + Selected->d.contacts.negated = FALSE; + break; + + case ELEM_COIL: { + ElemCoil *c = &Selected->d.coil; + c->negated = FALSE; + c->setOnly = FALSE; + c->resetOnly = FALSE; + break; + } + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Make the selected item set-only, if it is a coil. +//----------------------------------------------------------------------------- +void MakeSetOnlySelected(void) +{ + if(SelectedWhich != ELEM_COIL) return; + + ElemCoil *c = &Selected->d.coil; + c->setOnly = TRUE; + c->resetOnly = FALSE; + c->negated = FALSE; +} + +//----------------------------------------------------------------------------- +// Make the selected item reset-only, if it is a coil. +//----------------------------------------------------------------------------- +void MakeResetOnlySelected(void) +{ + if(SelectedWhich != ELEM_COIL) return; + + ElemCoil *c = &Selected->d.coil; + c->resetOnly = TRUE; + c->setOnly = FALSE; + c->negated = FALSE; +} +*/
\ No newline at end of file |