Friday, April 15, 2022

26-Mouse & Keyboard Events

This post discusses the mouse and keyboard events that provide functionality to move, rotate, and delete components and wires.

 Overview

Users create circuit diagrams by selecting components from the component menus. Then using the mouse, a user can select and move components to represent the circuit of interest. When the user selects the Wire Mode button, wires can be drawn to interconnect components interactively using the mouse and "rubberband" lines.

Keyboard events will be implemented to rotate a component as it is moved using the "R" key. Also, during mouse move, a component can be deleted using the "D" key.

Keyboard Events

Add keypress event variables in MainForm.cs

        // Keypress event variables
        public bool rotate = false; // Rotate component flag
        public bool delete = false; // Delete component flag

Add a MainForm Key Press event handler as follows:

       private void MainForm_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == 114 || e.KeyChar == 82)    // 114 = r, 82 = R
                rotate = !rotate;
 
            if (e.KeyChar == 100 || e.KeyChar == 68)    // 100 = d, 68 = D
                delete = !delete;
        }

Mouse Events

Add the mouse event variables in MainForm.cs

        // Mouse event variables
        bool isMouseDown = false;
        bool lineDrawing = false;
        int offsetX, offsetY, startX, startY;
        Comp tempComp = new Comp();
 
        // The wire we are drawing
        private Wire NewWire = null;
 
        // Points for the new line.
        private Point NewPt1, NewPt2;

Modify the Paint() event in MainForm.cs to draw the component output wires

       private void schematicCanvas_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
 
            foreach (Comp comp in ckt.comps)
            {
                comp.Draw(e.Graphics);
                schematicCanvas.Invalidate();
 
                foreach (Wire wire in comp.wires)
                {
                    wire.Draw(e.Graphics);
                    schematicCanvas.Invalidate();
                }
            }
        }

Add the HitTest() method to MainForm.cs.

        private bool hitTest(Comp comp)
        {
            bool hit;
 
            hit = false;
            if ((startX >= comp.Loc.X && startX <= comp.Loc.X + comp.Width) &&
                (startY >= comp.Loc.Y && startY <= comp.Loc.Y + comp.Height))
            {
                Debug.WriteLine("Hit!");
                hit = true;
            }
            else
            {
                Debug.WriteLine("No Hit!");
                hit = false;
            }
 
            return hit;
        }

Add the SnapToGrid() helper method to MainForm

        // Snap XY point to the grid
        private void SnapToGrid(ref int x, ref int y)
        {
            //if (!chkSnapToGrid.Checked) return;
            x = grid_gap * (int)Math.Round((double)x / grid_gap);
            y = grid_gap * (int)Math.Round((double)y / grid_gap);
        }

Add the Wire Mode button handler in MainForm.cs

        private void btnWireMode_Click(object sender, EventArgs e)
        {
            lineDrawing = !lineDrawing;
        }

Add a list of wires to Comp.cs

       public List<Wire> wires = new List<Wire>();

Initialize width & height in Comp.cs to facilitate the HitTest

        protected int _width = 60;
        protected int _height = 40;

Add MouseDown, MouseMove & MouseUp events to the schematicCanvas.

        private void schematicCanvas_MouseDown(object sender, MouseEventArgs e)
        {
            switch (e.Button)
            {
                case MouseButtons.Left:
                    {
                        closeOtherPanels(null);
                        isMouseDown = true;
 
                        if (!lineDrawing)
                        {// Snap the start point to the Grid
                            int x = e.X;
                            int y = e.Y;
                            SnapToGrid(ref x, ref y);
                            startX = x;
                            startY = y;
 
                            // Iterate over the schematic component list & draw each component
                            foreach (Comp comp in ckt.comps)
                            {
                                if (hitTest(comp))
                                {
                                    tempComp = comp;
                                    offsetX = startX - comp.Loc.X;
                                    offsetY = startY - comp.Loc.Y;
                                }
                            }
                        }
                        if (lineDrawing)
                        {
                            // Snap the start point to the Grid
                            int x = e.X;
                            int y = e.Y;
                            SnapToGrid(ref x, ref y);
                            NewPt1 = new Point(x, y);
                            NewPt2 = new Point(x, y);
                            startX = x;
                            startY = y;
 
                            // Create a new wire and add it to the schematic wires list
                            NewWire = new Wire(); // Use the constructor with no parameters
                            NewWire.Pt1 = NewPt1;
                            NewWire.Pt2 = NewPt2;
                            NewWire.endcapsVisible = true;
                            //schematic.wires.Add(NewWire);
 
                            // Debugs to show the line points on the console
                            Debug.WriteLine("Line Start Points: (" + NewPt1.X + ", " +                                         NewPt1.Y + " )");
 
                            // Iterate over the schematic component list & draw each component
                            foreach (Comp comp in ckt.comps)
                            {
                                if (hitTest(comp))
                                {
                                    Debug.WriteLine("Start Component hit!" + comp.ToString());
                                    NewWire.Cin = comp;
                                    //NewWire.Cout = Pout;
                                    comp.wires.Add(NewWire);
                                }
                            }
                        }
                        break;
                    }
                case MouseButtons.Right:
                    {
                        int x = e.X;
                        int y = e.Y;
                        SnapToGrid(ref x, ref y);
                        startX = x;
                        startY = y;
                        Comp hitComp = new Comp();
 
                        foreach (Comp comp in ckt.comps)
                        {
 
                            if (hitTest(comp))
                            {
                                hitComp = comp;
                            }
                        }
 
                        // TODO: Add code for property grid here
 
                        break;
                    }
                default:
                    break;
            }
        }
 
        private void schematicCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (isMouseDown == true)
            {
                if (!lineDrawing)
                {
                    if (tempComp != null)
                    {
                        if (rotate)
                        {
                            if (tempComp.Orientation == "Series")
                                tempComp.Orientation = "Shunt";
                            else if (tempComp.Orientation == "Shunt")
                                tempComp.Orientation = "Series";
                            rotate = false;
                        }
 
                        // TODO: Add delete code here
 
                        int x = e.X;
                        int y = e.Y;
                        SnapToGrid(ref x, ref y);
                        tempComp.Loc = new Point(x - offsetX, y - offsetY);
                        schematicCanvas.Invalidate();
                    }
                }
                if (lineDrawing)
                {
                    if (NewWire == null) return;
                    // Snap the end point to the Grid
                    int x = e.X;
                    int y = e.Y;
                    SnapToGrid(ref x, ref y);
                    NewPt2 = new Point(x, y);
                    NewWire.Pt2 = NewPt2;
 
                    // Redraw, this creates the "rubber band" effect while drawing the wire
                    schematicCanvas.Invalidate(); // Refresh the drawing canvas pictureBox
                }
            }
        }
 
        private void schematicCanvas_MouseUp(object sender, MouseEventArgs e)
        {
            isMouseDown = false;
            tempComp = null;
 
            if (lineDrawing)
            {
                int x = e.X;
                int y = e.Y;
                SnapToGrid(ref x, ref y);
                startX = x;
                startY = y;
 
                // Iterate over the schematic component list and draw each component
                foreach (Comp comp in ckt.comps)
                {
                    if (hitTest(comp))
                    {
                        Debug.WriteLine("End Component hit! " + comp.ToString());
                        NewWire.Cout = comp;
                    }
                }
 
                // Update the new wire end points
                NewWire.Pt1 = NewPt1;
                NewWire.Pt2 = NewPt2;
                NewWire.endcapsVisible = false;
 
                // Terminate any further updates to the new wire, this makes it permanent in                     the schematic wires list
                NewWire = null;
 
                // Redraw.
                schematicCanvas.Invalidate(); // Refresh the drawing canvas pictureBox
 
                // Debugs to show the line points on the console
                Debug.WriteLine("Line End Points: (" + NewPt2.X + ", " + NewPt2.Y + " )");
            }
            //lineDrawing = false;
        }

Wire Mode LED

Next we will add a Wire Mode LED indicating when the tool is in Wire Mode.

Create a new class in the Components folder called LED.cs.

// C# Libraries
using System;
using System.Collections.Generic;
using System.Drawing;
 
namespace MicrowaveTools.Components
{
    public class LED : Comp
    {
        // LED specific variables
        SolidBrush onfillColor = new SolidBrush(Color.Green);
        SolidBrush offfillColor = new SolidBrush(Color.Maroon);
        Color offColor = Color.Black;
        public Pen offPen = new Pen(Color.DimGray, 2);
        public Pen onPen = new Pen(Color.Green, 2);
 
        private const int endcap_radius = 3;
        public bool hotSpotsVisible = false;
        public bool logicState = false;
 
        // Component text variables
        // Create font and brush.
        public Font drawFont = new Font("Arial", 10);
        public SolidBrush drawBrush = new SolidBrush(Color.White);
        public StringFormat drawFormat = new StringFormat();
 
        public LED()
        {
 
        }
 
        public LED(Point location)
        {
            Loc = location;
            Pin = Loc;
        }
 
        // Let the LED draw itself called from the canvas paint event
        public override void Draw(Graphics gr)
        {
            // Draw outer circle
            Rectangle rect = new Rectangle((int)Loc.X - 6, (int)Loc.Y - 6, 12, 12);
            gr.FillEllipse(new SolidBrush(Color.DarkGray), rect);
 
            // Draw inner circle = LED based on the LED state
            if (logicState)
            {
                //gr.DrawLine(this.onPen, new Point((int)Loc.X, (int)Loc.Y + 20), new                             Point((int)Loc.X + 10, (int)Loc.Y + 20));
                rect = new Rectangle((int)Loc.X - 4, (int)Loc.Y - 4, 8, 8);
                gr.FillEllipse(this.onfillColor, rect);
            }
            else
            {
                //gr.DrawLine(this.offPen, new Point((int)Loc.X, (int)Loc.Y + 20), new                             Point((int)Loc.X + 10, (int)Loc.Y + 20));
                rect = new Rectangle((int)Loc.X - 4, (int)Loc.Y - 4, 8, 8);
                gr.FillEllipse(this.offfillColor, rect);
            }
 
            // Add label
            drawCompText(gr, new Point(Loc.X - 75, Loc.Y - 8), "Wire Mode");
        }
 
        public virtual void drawCompText(Graphics gr, Point p1, String drawString)
        {
            // Convert Point ints to floats
            float x = p1.X;
            float y = p1.Y;
 
            // Draw string to screen.
            gr.DrawString(drawString, drawFont, drawBrush, x, y, drawFormat);
        }
    }
}

This class was derived from the LED.cs class in the DigitalSim project.

Add the code to create the LED in MainForm.cs as a new variable.

       // Create the wire mode LED
        LED led = new LED(new Point(300, 200));

Add the following code to the end of the Pain() event handler in MainForm.cs

            // Display permanent wire mode LED
            showWireModeLED(e.Graphics);

Add the showWireModeLED() helper function to MainForm.cs

       private void showWireModeLED(Graphics gr)
        {
            // Draw the wire mode LED in the lower left corner of the schematic canvas
            int x, y;
            x = 80;
            y = schematicCanvas.ClientSize.Height - 20; // Forces location to lower left corner if height changes
            led.Loc = new Point(x, y);
            led.Draw(gr);
        }

Finally, update the Wire Mode button handler

       private void btnWireMode_Click(object sender, EventArgs e)
        {
            lineDrawing = !lineDrawing;
            led.logicState = lineDrawing;
            schematicCanvas.Invalidate();
        }

Run the program and add components and confirm that they can be moved with the mouse. Also click the Wire Mode buton and draw wires between components. Also, the wire mode LED should appear on the schematicCanvas in the lower left corner and the LED color should toggle red and green, where red is off and green is on.

MainForm Source Code Organization

Comments and regions have been added to MainForm.cs to group source code for constructors, schematicCanvas event handlers, main menu button handlers, component menu button handlers, and helper methods. The reorganized source code with updates from this post can be seen on GitHub.


There remain things to implement:

  • Rotate
  • Delete
  • Component Text


No comments:

Post a Comment

34-Microwave Tools with Analysis (Series Final Post)

In this final blog post, I have integrated Y-Matrix analysis into the Microwave Tools project. This version has addition Lumped, Ideal, Micr...