File indexing completed on 2024-05-05 05:46:09

0001 /***************************************************************************
0002  *   Copyright (C) 2005 by John Myers                                      *
0003  *   electronerd@electronerdia.net                                         *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  ***************************************************************************/
0010 
0011 #include "rotoswitch.h"
0012 
0013 #include "canvasitemparts.h"
0014 #include "ecnode.h"
0015 #include "libraryitem.h"
0016 #include "switch.h"
0017 
0018 #include <KLocalizedString>
0019 #include <QPainter>
0020 #include <QPainterPath>
0021 #include <cassert>
0022 #include <cmath>
0023 
0024 #include <ktechlab_debug.h>
0025 
0026 // BEGIN class ECRotoSwitch
0027 Item *ECRotoSwitch::construct(ItemDocument *itemDocument, bool newItem, const char *id)
0028 {
0029     return new ECRotoSwitch(static_cast<ICNDocument *>(itemDocument), newItem, id);
0030 }
0031 
0032 LibraryItem *ECRotoSwitch::libraryItem()
0033 {
0034     return new LibraryItem(QStringList(QString("ec/roto_switch")), i18n("Rotary"), i18n("Switches"), "rotary.png", LibraryItem::lit_component, ECRotoSwitch::construct);
0035 }
0036 
0037 ECRotoSwitch::ECRotoSwitch(ICNDocument *icnDocument, bool newItem, const char *id)
0038     : Component(icnDocument, newItem, id ? id : "roto_switch")
0039     , m_numPositions(0)
0040 {
0041     m_name = i18n("Rotary Switch");
0042     // Q3PointArray pa;    // 2018.08.14 - see below
0043     // pa.makeArc( -_pinInnerRadius, -_pinInnerRadius, 2*_pinInnerRadius, 2*_pinInnerRadius , 0, 16*360 );
0044     QPainterPath path;
0045     path.addEllipse(-_pinInnerRadius, -_pinInnerRadius, 2 * _pinInnerRadius, 2 * _pinInnerRadius);
0046     QPolygon pa = path.toFillPolygon().toPolygon();
0047 
0048     setItemPoints(pa);
0049     // setSize( -64, -64, 128, 128 );
0050 
0051     // half the side length of the buttons
0052     int buttonRadius = 10;
0053     addButton("go_left", QRect(-_pinOuterRadius / 3 - buttonRadius, _pinOuterRadius - 3 * buttonRadius, 2 * buttonRadius, 2 * buttonRadius), "<", false);
0054 
0055     addButton("go_right", QRect(_pinOuterRadius / 3 - buttonRadius, _pinOuterRadius - 3 * buttonRadius, 2 * buttonRadius, 2 * buttonRadius), ">", false);
0056 
0057     /*Variant * v = createProperty( "button_map", Variant::Type::String );
0058     v->setCaption( i18n("Button Map") );
0059     v->setAdvanced(true);
0060     const QString defButtonMap("SSSSSSSSSSSM");
0061     v->setValue(defButtonMap);
0062     */
0063     Variant *v = createProperty("num_positions", Variant::Type::Int);
0064     v->setCaption(i18n("Number of Positions"));
0065     v->setAdvanced(false);
0066     v->setValue(6);
0067     v->setMinValue(3);
0068     m_inNode = createPin(0, height() / 2, 270, "in");
0069 
0070     v = createProperty("bounce", Variant::Type::Bool);
0071     v->setCaption("Bounce");
0072     v->setAdvanced(true);
0073     v->setValue(false);
0074 
0075     v = createProperty("bounce_period", Variant::Type::Double);
0076     v->setCaption("Bounce Period");
0077     v->setAdvanced(true);
0078     v->setUnit("s");
0079     v->setValue(5e-3);
0080 
0081     v = createProperty("cur_position", Variant::Type::Int);
0082     v->setHidden(true);
0083     v->setValue(0);
0084 
0085     // v = createProperty( "left_momentary", Variant::Type::Bool );
0086     // v->setCaption(i18n("Left Momentary" ) );
0087     // v->setValue(false);
0088 }
0089 
0090 ECRotoSwitch::~ECRotoSwitch()
0091 {
0092 }
0093 
0094 void ECRotoSwitch::dataChanged()
0095 {
0096     bool bounce = dataBool("bounce");
0097     int bouncePeriod_ms = int(dataDouble("bounce_period") * 1e3);
0098     m_curPosition = dataInt("cur_position");
0099     setUpSwitches();
0100 
0101     if (m_positions[m_curPosition].posSwitch->state() != Switch::Closed) {
0102         m_positions[m_curPosition].posSwitch->setState(Switch::Closed);
0103     }
0104 
0105     for (int i = 0; i < m_numPositions; i++) {
0106         m_positions[i].posSwitch->setBounce(bounce, bouncePeriod_ms);
0107     }
0108 }
0109 
0110 inline int roundTo10(int a)
0111 {
0112     return ((a / 10) + (a % 10 < 5 ? 0 : 1)) * 10;
0113 }
0114 void ECRotoSwitch::drawShape(QPainter &p)
0115 {
0116     initPainter(p);
0117 
0118     int cx = static_cast<int>(x());
0119     int cy = static_cast<int>(y());
0120 
0121     const int rotorRadius = 5;
0122 
0123     // draw the rotor
0124     p.drawEllipse(cx - rotorRadius, cy - rotorRadius, 2 * rotorRadius, 2 * rotorRadius);
0125 
0126     // and its connection
0127     p.drawLine(cx, cy + rotorRadius, cx, cy + _pinInnerRadius);
0128 
0129     // draw the output positions
0130     double angleBetweenPositions = (4 * M_PI / 3) / (m_numPositions - 1);
0131 
0132     /// \internal \brief Round to the nearest multiple of 8
0133 #define round_8(a) (((a) > 0) ? int(((a) + 4) / 8) * 8 : int(((a)-4) / 8) * 8)
0134     for (int i = 0; i < m_numPositions; i++) {
0135         double angle = (7 * M_PI / 6) - (i * angleBetweenPositions);
0136         int contactX = static_cast<int>(_contactRingRadius * cos(angle));
0137         int contactY = static_cast<int>(_contactRingRadius * sin(angle));
0138 
0139         p.drawEllipse(cx + contactX - _contactRadius, cy - contactY - _contactRadius, 2 * _contactRadius, 2 * _contactRadius);
0140 
0141         int pinX, pinY;
0142         switch (m_positions[i].pinAngle) {
0143         case 180:
0144             pinX = _pinInnerRadius;
0145             pinY = round_8(contactY);
0146             break;
0147         case 90:
0148             pinX = round_8(contactX);
0149             pinY = _pinInnerRadius;
0150             break;
0151         case 0:
0152             pinX = -_pinInnerRadius;
0153             pinY = round_8(contactY);
0154             break;
0155         default:
0156             assert(!"Bad pin angle");
0157         }
0158 
0159         p.drawLine(cx + contactX, cy - contactY, cx + pinX, cy - pinY);
0160     }
0161 #undef round_8
0162 
0163     // draw the connection to the selected position
0164     double angle = (7 * M_PI / 6) - (m_curPosition * angleBetweenPositions);
0165     int contactX = static_cast<int>(_contactRingRadius * cos(angle));
0166     int contactY = static_cast<int>(_contactRingRadius * sin(angle));
0167     int rotorX = static_cast<int>(rotorRadius * cos(angle));
0168     int rotorY = static_cast<int>(rotorRadius * sin(angle));
0169     p.drawLine(cx + rotorX, cy - rotorY, cx + contactX, cy - contactY);
0170 
0171     deinitPainter(p);
0172 }
0173 
0174 void ECRotoSwitch::buttonStateChanged(const QString &id, bool state)
0175 {
0176     SwitchPosition &curSP = m_positions[m_curPosition];
0177     int nextPos = m_curPosition;
0178     if (m_numPositions < 2) {
0179         return;
0180     }
0181 
0182     if (!state) // release
0183     {
0184         if (!curSP.isMomentary)
0185             return;
0186 
0187         if (m_curPosition == 0) {
0188             nextPos = m_curPosition + 1;
0189         } else if (m_curPosition == m_numPositions - 1) {
0190             nextPos = m_curPosition - 1;
0191         }
0192 
0193     } else // press
0194     {
0195         if (id == "go_left" && m_curPosition > 0) {
0196             nextPos = m_curPosition - 1;
0197         } else if (id == "go_right" && m_curPosition < m_numPositions - 1) {
0198             nextPos = m_curPosition + 1;
0199         }
0200     }
0201 
0202     if (nextPos != m_curPosition) {
0203         SwitchPosition &nextSP = m_positions[nextPos];
0204 
0205         curSP.posSwitch->setState(Switch::Open);
0206         nextSP.posSwitch->setState(Switch::Closed);
0207 
0208         m_curPosition = nextPos;
0209 
0210         property("cur_position")->setValue(m_curPosition);
0211     }
0212 }
0213 
0214 /*!
0215 Set up the switches according to the button_map
0216 *
0217 */
0218 void ECRotoSwitch::setUpSwitches()
0219 {
0220     if (dataInt("num_positions") == m_numPositions) {
0221         // number of positions didn't change, so we don't have to do anything.
0222         return;
0223     }
0224 
0225     // this uses the _old_ value of m_numPositions!
0226     for (int i = 0; i < m_numPositions; i++) {
0227         SwitchPosition &sp = m_positions[i];
0228         QString pinName = QString("pin_%1").arg(i);
0229         removeNode(pinName);
0230         removeSwitch(sp.posSwitch);
0231     }
0232 
0233     m_numPositions = dataInt("num_positions");
0234 
0235     if (m_curPosition >= m_numPositions) {
0236         setActivePosition(m_numPositions - 1);
0237     }
0238 
0239     m_positions.clear();
0240     m_positions.reserve(m_numPositions);
0241     double angleBetweenPositions = (4 * M_PI / 3) / (m_numPositions - 1);
0242 
0243     for (int i = 0; i < m_numPositions; i++) {
0244         double angle = (7 * M_PI / 6) - (i * angleBetweenPositions);
0245         int contactX = static_cast<int>(_contactRingRadius * cos(angle));
0246         int contactY = static_cast<int>(_contactRingRadius * sin(angle));
0247 
0248         SwitchPosition sp;
0249 
0250         if (angle > 3 * M_PI / 4) {
0251             sp.pinAngle = 0;
0252             contactX = -_pinOuterRadius;
0253         } else if (angle > M_PI / 4) {
0254             sp.pinAngle = 90;
0255             contactY = _pinOuterRadius;
0256         } else {
0257             sp.pinAngle = 180;
0258             contactX = _pinOuterRadius;
0259         }
0260         // qCDebug(KTL_LOG) << contactX <<", "<< contactY;
0261 
0262         sp.node = createPin(contactX, -contactY, sp.pinAngle, QString("pin_%1").arg(i));
0263         sp.posSwitch = createSwitch(m_inNode, sp.node, true);
0264         sp.isMomentary = false; //(map[i] == 'M');
0265         m_positions.push_back(sp);
0266     }
0267     updateAttachedPositioning();
0268 
0269     // redraw ourself
0270     setChanged();
0271 }
0272 
0273 /*!
0274  * Set the current position to \c newPosition updating the state of the switch.
0275  * \c m_curPosition must reference a valid position to switch away from
0276  *
0277  * \param newPosition the position to switch to
0278  */
0279 void ECRotoSwitch::setActivePosition(int newPosition)
0280 {
0281     SwitchPosition &curSP = m_positions[m_curPosition];
0282     SwitchPosition &nextSP = m_positions[newPosition];
0283 
0284     curSP.posSwitch->setState(Switch::Open);
0285     nextSP.posSwitch->setState(Switch::Closed);
0286 
0287     m_curPosition = newPosition;
0288 
0289     property("cur_position")->setValue(m_curPosition);
0290 }
0291 
0292 // END class ECRotoSwitch