File indexing completed on 2022-09-27 12:31:09

0001 /*
0002     SPDX-FileCopyrightText: 2007 Niels Slot <nielsslot AT gmail DOT com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "directiondialog.h"
0008 
0009 #include <cmath>
0010 using std::atan;
0011 
0012 #include <QApplication>
0013 #include <QBoxLayout>
0014 #include <QClipboard>
0015 #include <QComboBox>
0016 #include <QDialogButtonBox>
0017 #include <QFontDatabase>
0018 #include <QLabel>
0019 #include <QLineEdit>
0020 #include <QMouseEvent>
0021 #include <QPainter>
0022 #include <QPaintEvent>
0023 #include <QPushButton>
0024 #include <QSpinBox>
0025 #include <QtMath>
0026 
0027 #include <KGuiItem>
0028 #include <KLocalizedString>
0029 #include <KStandardGuiItem>
0030 
0031 
0032 
0033 //BEGIN DirectionCanvas widget
0034 
0035 DirectionCanvas::DirectionCanvas(QWidget* parent)
0036     : QWidget(parent)
0037 {
0038     setFocusPolicy(Qt::ClickFocus);
0039     setMinimumSize(230, 200);
0040     setBackgroundRole(QPalette::Base);
0041     setAutoFillBackground(true);
0042     turtle.load(QStringLiteral(":turtle.svg"));
0043     greyTurtle.load(QStringLiteral(":turtle_grey.svg"));
0044 
0045     deg = 0;
0046     previousDeg = 0;
0047     greyTurtleEnabled = true;
0048 }
0049 
0050 void DirectionCanvas::enableGreyTurtle(bool enable)
0051 {
0052     greyTurtleEnabled = enable;
0053     update();
0054 }
0055 
0056 void DirectionCanvas::paintEvent(QPaintEvent *event)
0057 {
0058     Q_UNUSED(event);
0059     int side = qMin(width(), height());
0060 
0061     QPainter painter(this);
0062     painter.setRenderHint(QPainter::Antialiasing);
0063 
0064     painter.save();
0065 
0066     // Place us in the middle of the widget
0067     painter.translate(width() / 2, height() / 2);
0068 
0069     // Scale the widget to a square of 200 by 200
0070     painter.scale(side / 200.0, side / 200.0);
0071 
0072     // Draw the ellipse. With a nice border of 10
0073     painter.drawEllipse(-80, -80, 160, 160);
0074 
0075     // Draw the lines in the circle
0076     painter.save();
0077     for (int i = 0; i < 4; i++) {
0078         painter.drawLine(0, -80, 0, 80);
0079         painter.rotate(45);
0080     }
0081     painter.restore();
0082 
0083     painter.drawText(-100, -98, 200, 200, Qt::AlignHCenter|Qt::AlignTop, QStringLiteral("0"));
0084     painter.drawText(-100, -100, 200, 200, Qt::AlignHCenter|Qt::AlignBottom, QStringLiteral("180"));
0085     painter.drawText(-100, -100, 203, 200, Qt::AlignRight|Qt::AlignVCenter, QStringLiteral("90"));
0086     painter.drawText(-109, -100, 200, 200, Qt::AlignLeft|Qt::AlignVCenter, QStringLiteral("270"));
0087 
0088     painter.save();
0089 
0090     // the gray turtle
0091     if (greyTurtleEnabled) {
0092         painter.rotate(previousDeg);
0093         painter.setPen(Qt::blue);
0094         painter.drawLine(0, -80, 0, 0);
0095         QRectF greyTurtleRect(-25, -25, 50, 50);
0096         greyTurtle.render(&painter, greyTurtleRect);
0097         painter.restore();
0098         painter.save();
0099     }
0100 
0101     // the more healthy looking one
0102     painter.rotate(deg);
0103     painter.setPen(Qt::red);
0104     painter.drawLine(0, -80, 0, 0);
0105     QRectF turtleRect(-25, -25, 50, 50);
0106     turtle.render(&painter, turtleRect);
0107 
0108     painter.restore();
0109     painter.restore();
0110 
0111     // Draw the widget's border
0112     painter.setPen(palette().dark().color());
0113     painter.setBrush(Qt::NoBrush);
0114     painter.drawRect(QRect(0, 0, width() - 1, height() - 1));
0115 }
0116 
0117 void DirectionCanvas::mouseMoveEvent(QMouseEvent *event)
0118 {
0119     mousePressEvent(event);
0120 }
0121 
0122 void DirectionCanvas::mousePressEvent(QMouseEvent *event)
0123 {
0124     // Only act upon left and right mouse button clicks,
0125     // then translate the X and Y coordinates so that
0126     // (0, 0) is in the middle of the widget, sent the
0127     // signals and update the widget.
0128     if (event->buttons() & Qt::LeftButton) {
0129         deg = translateMouseCoords(event->x() - (width() / 2), event->y() - (height() / 2));
0130         update();
0131         Q_EMIT degreeChanged(deg);
0132     } else if (event->buttons() & Qt::RightButton) {
0133         previousDeg = translateMouseCoords(event->x() - (width() / 2), event->y() - (height() / 2));
0134         Q_EMIT previousDegreeChanged(previousDeg);
0135         update();
0136     }
0137 }
0138 
0139 void DirectionCanvas::updateDirections(double previousDeg, double deg)
0140 {
0141     this->deg = deg;
0142     this->previousDeg = previousDeg;
0143     update();
0144 }
0145 
0146 double DirectionCanvas::translateMouseCoords(double trans_x, double trans_y)
0147 {
0148     // We now have 4 squares. One four every corner.
0149     // With a cross in the middle.
0150     // For every square we calculate a different tangent
0151     // therefore we have to add of subtract a number of degrees
0152     double result = 0;
0153     if (trans_x >= 0 && trans_y >= 0) {
0154         // Right down
0155         double arc_tan = trans_y / trans_x;
0156         result = 90 + (atan(arc_tan)) * (180/M_PI);
0157     } else if (trans_x <= 0 && trans_y >= 0) {
0158         // Left down
0159         trans_x = trans_x * -1;
0160         double arc_tan = trans_y / trans_x;
0161         result = 270 - (atan(arc_tan)) * (180/M_PI);
0162     } else if (trans_x >= 0 && trans_y <= 0) {
0163         // Right up
0164         trans_y = trans_y * -1;
0165         double arc_tan = trans_y / trans_x;
0166         result = 90 - (atan(arc_tan)) * (180/M_PI);
0167     } else if (trans_x <= 0 && trans_y <= 0) {
0168         // Left up
0169         trans_x = trans_x * -1;
0170         trans_y = trans_y * -1;
0171         double arc_tan = trans_y / trans_x;
0172         result = 270 + (atan(arc_tan)) * (180/M_PI);
0173     }
0174     return result;
0175 }
0176 
0177 //END DirectionCanvas widget
0178 
0179 
0180 DirectionDialog::DirectionDialog(double deg, QWidget* parent)
0181     : QDialog(parent)
0182 {
0183     skipValueChangedEvent = false;
0184 
0185     while (deg < 0 || deg > 359) {
0186         if (deg < 0)
0187             deg = deg + 360;
0188         else if (deg > 359)
0189             deg = deg - 360;
0190     }
0191 
0192     translator = Translator::instance();
0193 
0194     setWindowTitle(i18nc("@title:window", "Direction Chooser"));
0195     setModal(false);
0196     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0197 
0198     QWidget *mainWidget = new QWidget(this);
0199     mainLayout->addWidget(mainWidget);
0200 
0201     QWidget* baseWidget = new QWidget(this);
0202     mainLayout->addWidget(baseWidget);
0203 
0204     QVBoxLayout* baseLayout = new QVBoxLayout;
0205     baseLayout->setContentsMargins(0, 0, 0, 0);
0206     baseWidget->setLayout( baseLayout );
0207     QHBoxLayout* degreeChooserLayout = new QHBoxLayout;
0208     baseLayout->addLayout(degreeChooserLayout);
0209 
0210     canvas = new DirectionCanvas(baseWidget);
0211     mainLayout->addWidget(canvas);
0212     connect(canvas, &DirectionCanvas::degreeChanged, this, &DirectionDialog::updateDegrees);
0213     connect(canvas, &DirectionCanvas::previousDegreeChanged, this, &DirectionDialog::updatePreviousDegrees);
0214 
0215     degreeChooserLayout->addWidget(canvas);
0216 
0217     QWidget* rightWidget = new QWidget(baseWidget);
0218     mainLayout->addWidget(rightWidget);
0219     degreeChooserLayout->addWidget(rightWidget);
0220 
0221     QVBoxLayout* rightLayout = new QVBoxLayout(rightWidget);
0222 
0223     // command picker
0224     QLabel* commandPickerLabel = new QLabel(rightWidget);
0225     commandPickerLabel->setText(i18n("Command &type:"));
0226     commandPickerLabel->setScaledContents(true);
0227     rightLayout->addWidget(commandPickerLabel);
0228     commandPicker = new QComboBox(rightWidget);
0229     commandPicker->insertItem(Turnleft, translator->default2localized(QStringLiteral("turnleft")));
0230     commandPicker->insertItem(Turnright, translator->default2localized(QStringLiteral("turnright")));
0231     commandPicker->insertItem(Direction, translator->default2localized(QStringLiteral("direction")));
0232     rightLayout->addWidget(commandPicker);
0233     commandPickerLabel->setBuddy(commandPicker);
0234     connect(commandPicker, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &DirectionDialog::changeCommand);
0235 
0236     rightLayout->addStretch();
0237 
0238     // direction
0239     QLabel* previousDirectionLabel = new QLabel(rightWidget);
0240     previousDirectionLabel->setText(i18n("&Previous direction:"));
0241     previousDirectionLabel->setScaledContents(true);
0242     rightLayout->addWidget(previousDirectionLabel);
0243     previousDirectionSpin = new QSpinBox(rightWidget);
0244     // Use -360 to 720 instead of 0 to 360
0245     // If 0 to 360 is used, then wrap-around goes from 360 to 0 (which isn't really a step at all)
0246     // Instead use larger range and then convert it into the 0 to 359 range whenever it is changed.
0247     previousDirectionSpin->setRange(-360, 720);
0248     previousDirectionSpin->setWrapping(true);
0249     previousDirectionSpin->setSingleStep(10);
0250     previousDirectionSpin->setValue(static_cast<int>(deg));
0251     rightLayout->addWidget(previousDirectionSpin);
0252     previousDirectionLabel->setBuddy(previousDirectionSpin);
0253     connect(previousDirectionSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &DirectionDialog::directionChanged);
0254 
0255     // previous direction
0256     QLabel* directionLabel = new QLabel(rightWidget);
0257     directionLabel->setText(i18n("&New direction:"));
0258     rightLayout->addWidget(directionLabel);
0259     directionSpin = new QSpinBox(rightWidget);
0260     // Use -360 to 720 instead of 0 to 360
0261     // If 0 to 360 is used, then wrap-around goes from 360 to 0 (which isn't really a step at all)
0262     // Instead use larger range and then convert it into the 0 to 359 range whenever it is changed.
0263     directionSpin->setRange(-360, 720);
0264     directionSpin->setWrapping(true);
0265     directionSpin->setSingleStep(10);
0266     directionSpin->setValue(static_cast<int>(deg));
0267     rightLayout->addWidget(directionSpin);
0268     directionLabel->setBuddy(directionSpin);
0269     connect(directionSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &DirectionDialog::directionChanged);
0270 
0271     baseLayout->addSpacing(20);
0272 
0273     // commandBox and copy/paste buttons
0274     QHBoxLayout *pasteRowLayout = new QHBoxLayout;
0275     baseLayout->addLayout(pasteRowLayout);
0276     pasteRowLayout->addStretch();
0277     commandBox = new QLineEdit(rightWidget);
0278     commandBox->setReadOnly(true);
0279     commandBox->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0280     commandBox->setMinimumWidth(commandBox->fontMetrics().boundingRect(QStringLiteral("000000000_360")).width());
0281     pasteRowLayout->addWidget(commandBox);
0282     QPushButton* copyButton = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy to clipboard"), baseWidget);
0283     mainLayout->addWidget(copyButton);
0284     pasteRowLayout->addWidget(copyButton);
0285     connect(copyButton, &QPushButton::clicked, this, &DirectionDialog::copyProxy);
0286     QPushButton* pasteButton = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("&Paste to editor"), baseWidget);
0287     mainLayout->addWidget(pasteButton);
0288     pasteRowLayout->addWidget(pasteButton);
0289     connect(pasteButton, &QPushButton::clicked, this, &DirectionDialog::pasteProxy);
0290     pasteRowLayout->addStretch();
0291 
0292     baseLayout->addSpacing(10);
0293 
0294     QDialogButtonBox *buttonBox = new QDialogButtonBox();
0295     QPushButton *user1Button = new QPushButton;
0296     buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
0297     connect(buttonBox, &QDialogButtonBox::accepted, this, &DirectionDialog::accept);
0298     connect(buttonBox, &QDialogButtonBox::rejected, this, &DirectionDialog::reject);
0299     mainLayout->addWidget(buttonBox);
0300     user1Button->setDefault(true);
0301     KGuiItem::assign(user1Button, KStandardGuiItem::close());
0302     connect(user1Button, &QPushButton::clicked, this, &DirectionDialog::close);
0303 
0304     changeCommand(0);
0305     show();
0306 }
0307 
0308 void DirectionDialog::directionChanged(int value)
0309 {
0310     Q_UNUSED(value);
0311 
0312     // if value is outside of the 0 to 359 range, then move it into that range
0313     if (previousDirectionSpin->value() < 0) {
0314         previousDirectionSpin->setValue(previousDirectionSpin->value() + 360);
0315     } else if (previousDirectionSpin->value() >= 360) {
0316         previousDirectionSpin->setValue(previousDirectionSpin->value() - 360);
0317     }
0318 
0319     // if value is outside of the 0 to 359 range, then move it into that range
0320     if (directionSpin->value() < 0) {
0321         directionSpin->setValue(directionSpin->value() + 360);
0322     } else if (directionSpin->value() >= 360) {
0323         directionSpin->setValue(directionSpin->value() - 360);
0324     }
0325 
0326     // Don't update the canvas when we just updated the direction spinbox.
0327     // (only update when the users changes the spinbox)
0328     if (skipValueChangedEvent) {
0329         skipValueChangedEvent = false;
0330         return;
0331     }
0332     updateCanvas();
0333     updateCommandBox();
0334 }
0335 
0336 void DirectionDialog::updateCanvas()
0337 {
0338     // Get the things we need, then update the canvas.
0339     int previousDir = previousDirectionSpin->value();
0340     int dir = directionSpin->value();
0341     canvas->updateDirections(previousDir, dir);
0342 }
0343 
0344 void DirectionDialog::changeCommand(int command)
0345 {
0346     currentCommand = command;
0347     if (currentCommand == Direction) {
0348         previousDirectionSpin->setEnabled(false);
0349         canvas->enableGreyTurtle(false);
0350     } else {
0351         previousDirectionSpin->setEnabled(true);
0352         canvas->enableGreyTurtle(true);
0353     }
0354     updateCanvas();
0355     updateCommandBox();
0356 }
0357 
0358 void DirectionDialog::updateDegrees(double deg)
0359 {
0360     // The canvas has changed, update the spinbox and command-LineEdit
0361     skipValueChangedEvent = true;
0362     directionSpin->setValue(static_cast<int>(round(deg)));
0363     updateCommandBox();
0364 }
0365 
0366 void DirectionDialog::updatePreviousDegrees(double deg)
0367 {
0368     // The canvas has changed, update the spinbox and commandBox
0369     skipValueChangedEvent = true;
0370     previousDirectionSpin->setValue(static_cast<int>(round(deg)));
0371     updateCommandBox();
0372 }
0373 
0374 void DirectionDialog::updateCommandBox()
0375 {
0376     // Generate a new value for the commandBox.
0377     QString output;
0378     int degree = 0;
0379     switch (currentCommand) {
0380         case Turnleft:
0381             output.append(translator->default2localized(QStringLiteral("turnleft")));
0382             degree = 360 - (directionSpin->value() - previousDirectionSpin->value());
0383             break;
0384         case Turnright:
0385             output.append(translator->default2localized(QStringLiteral("turnright")));
0386             degree = directionSpin->value() - previousDirectionSpin->value();
0387             break;
0388         case Direction:
0389             output.append(translator->default2localized(QStringLiteral("direction")));
0390             degree = directionSpin->value();
0391             break;
0392     }
0393     if (degree < 0) {
0394         degree += 360;
0395     } else if (degree >= 360) {
0396         degree -= 360;
0397     }
0398     output.append(QStringLiteral(" %1\n").arg(degree));
0399     commandBox->setText(output);
0400 }
0401 
0402 void DirectionDialog::copyProxy()
0403 {
0404     QApplication::clipboard()->setText(commandBox->text());
0405 }
0406 
0407 void DirectionDialog::pasteProxy()
0408 {
0409     Q_EMIT pasteText(commandBox->text());
0410 }