//-----------------------------------------------------------------------------
// 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 .
//------
//
// 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
#include
#include "ldmicro.h"
// 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;
}