File indexing completed on 2024-04-21 03:45:27

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 <QClipboard>
0014 #include <QComboBox>
0015 #include <QDialogButtonBox>
0016 #include <QFontDatabase>
0017 #include <QLabel>
0018 #include <QLineEdit>
0019 #include <QMouseEvent>
0020 #include <QPainter>
0021 #include <QPaintEvent>
0022 #include <QPushButton>
0023 #include <QSpinBox>
0024 #include <QHBoxLayout>
0025 #include <QVBoxLayout>
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     auto position = event->position();
0129     if (event->buttons() & Qt::LeftButton) {
0130         deg = translateMouseCoords(position.x() - (width() / 2), position.y() - (height() / 2));
0131         update();
0132         Q_EMIT degreeChanged(deg);
0133     } else if (event->buttons() & Qt::RightButton) {
0134         previousDeg = translateMouseCoords(position.x() - (width() / 2), position.y() - (height() / 2));
0135         Q_EMIT previousDegreeChanged(previousDeg);
0136         update();
0137     }
0138 }
0139 
0140 void DirectionCanvas::updateDirections(double previousDeg, double deg)
0141 {
0142     this->deg = deg;
0143     this->previousDeg = previousDeg;
0144     update();
0145 }
0146 
0147 double DirectionCanvas::translateMouseCoords(double trans_x, double trans_y)
0148 {
0149     // We now have 4 squares. One four every corner.
0150     // With a cross in the middle.
0151     // For every square we calculate a different tangent
0152     // therefore we have to add of subtract a number of degrees
0153     double result = 0;
0154     if (trans_x >= 0 && trans_y >= 0) {
0155         // Right down
0156         double arc_tan = trans_y / trans_x;
0157         result = 90 + (atan(arc_tan)) * (180/M_PI);
0158     } else if (trans_x <= 0 && trans_y >= 0) {
0159         // Left down
0160         trans_x = trans_x * -1;
0161         double arc_tan = trans_y / trans_x;
0162         result = 270 - (atan(arc_tan)) * (180/M_PI);
0163     } else if (trans_x >= 0 && trans_y <= 0) {
0164         // Right up
0165         trans_y = trans_y * -1;
0166         double arc_tan = trans_y / trans_x;
0167         result = 90 - (atan(arc_tan)) * (180/M_PI);
0168     } else if (trans_x <= 0 && trans_y <= 0) {
0169         // Left up
0170         trans_x = trans_x * -1;
0171         trans_y = trans_y * -1;
0172         double arc_tan = trans_y / trans_x;
0173         result = 270 + (atan(arc_tan)) * (180/M_PI);
0174     }
0175     return result;
0176 }
0177 
0178 //END DirectionCanvas widget
0179 
0180 
0181 DirectionDialog::DirectionDialog(double deg, QWidget* parent)
0182     : QDialog(parent)
0183 {
0184     skipValueChangedEvent = false;
0185 
0186     while (deg < 0 || deg > 359) {
0187         if (deg < 0)
0188             deg = deg + 360;
0189         else if (deg > 359)
0190             deg = deg - 360;
0191     }
0192 
0193     translator = Translator::instance();
0194 
0195     setWindowTitle(i18nc("@title:window", "Direction Chooser"));
0196     setModal(false);
0197     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0198 
0199     QWidget *mainWidget = new QWidget(this);
0200     mainLayout->addWidget(mainWidget);
0201 
0202     QWidget* baseWidget = new QWidget(this);
0203     mainLayout->addWidget(baseWidget);
0204 
0205     QVBoxLayout* baseLayout = new QVBoxLayout;
0206     baseLayout->setContentsMargins(0, 0, 0, 0);
0207     baseWidget->setLayout( baseLayout );
0208     QHBoxLayout* degreeChooserLayout = new QHBoxLayout;
0209     baseLayout->addLayout(degreeChooserLayout);
0210 
0211     canvas = new DirectionCanvas(baseWidget);
0212     mainLayout->addWidget(canvas);
0213     connect(canvas, &DirectionCanvas::degreeChanged, this, &DirectionDialog::updateDegrees);
0214     connect(canvas, &DirectionCanvas::previousDegreeChanged, this, &DirectionDialog::updatePreviousDegrees);
0215 
0216     degreeChooserLayout->addWidget(canvas);
0217 
0218     QWidget* rightWidget = new QWidget(baseWidget);
0219     mainLayout->addWidget(rightWidget);
0220     degreeChooserLayout->addWidget(rightWidget);
0221 
0222     QVBoxLayout* rightLayout = new QVBoxLayout(rightWidget);
0223 
0224     // command picker
0225     QLabel* commandPickerLabel = new QLabel(rightWidget);
0226     commandPickerLabel->setText(i18n("Command &type:"));
0227     commandPickerLabel->setScaledContents(true);
0228     rightLayout->addWidget(commandPickerLabel);
0229     commandPicker = new QComboBox(rightWidget);
0230     commandPicker->insertItem(Turnleft, translator->default2localized(QStringLiteral("turnleft")));
0231     commandPicker->insertItem(Turnright, translator->default2localized(QStringLiteral("turnright")));
0232     commandPicker->insertItem(Direction, translator->default2localized(QStringLiteral("direction")));
0233     rightLayout->addWidget(commandPicker);
0234     commandPickerLabel->setBuddy(commandPicker);
0235     connect(commandPicker, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &DirectionDialog::changeCommand);
0236 
0237     rightLayout->addStretch();
0238 
0239     // direction
0240     QLabel* previousDirectionLabel = new QLabel(rightWidget);
0241     previousDirectionLabel->setText(i18n("&Previous direction:"));
0242     previousDirectionLabel->setScaledContents(true);
0243     rightLayout->addWidget(previousDirectionLabel);
0244     previousDirectionSpin = new QSpinBox(rightWidget);
0245     // Use -360 to 720 instead of 0 to 360
0246     // If 0 to 360 is used, then wrap-around goes from 360 to 0 (which isn't really a step at all)
0247     // Instead use larger range and then convert it into the 0 to 359 range whenever it is changed.
0248     previousDirectionSpin->setRange(-360, 720);
0249     previousDirectionSpin->setWrapping(true);
0250     previousDirectionSpin->setSingleStep(10);
0251     previousDirectionSpin->setValue(static_cast<int>(deg));
0252     rightLayout->addWidget(previousDirectionSpin);
0253     previousDirectionLabel->setBuddy(previousDirectionSpin);
0254     connect(previousDirectionSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &DirectionDialog::directionChanged);
0255 
0256     // previous direction
0257     QLabel* directionLabel = new QLabel(rightWidget);
0258     directionLabel->setText(i18n("&New direction:"));
0259     rightLayout->addWidget(directionLabel);
0260     directionSpin = new QSpinBox(rightWidget);
0261     // Use -360 to 720 instead of 0 to 360
0262     // If 0 to 360 is used, then wrap-around goes from 360 to 0 (which isn't really a step at all)
0263     // Instead use larger range and then convert it into the 0 to 359 range whenever it is changed.
0264     directionSpin->setRange(-360, 720);
0265     directionSpin->setWrapping(true);
0266     directionSpin->setSingleStep(10);
0267     directionSpin->setValue(static_cast<int>(deg));
0268     rightLayout->addWidget(directionSpin);
0269     directionLabel->setBuddy(directionSpin);
0270     connect(directionSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &DirectionDialog::directionChanged);
0271 
0272     baseLayout->addSpacing(20);
0273 
0274     // commandBox and copy/paste buttons
0275     QHBoxLayout *pasteRowLayout = new QHBoxLayout;
0276     baseLayout->addLayout(pasteRowLayout);
0277     pasteRowLayout->addStretch();
0278     commandBox = new QLineEdit(rightWidget);
0279     commandBox->setReadOnly(true);
0280     commandBox->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0281     commandBox->setMinimumWidth(commandBox->fontMetrics().boundingRect(QStringLiteral("000000000_360")).width());
0282     pasteRowLayout->addWidget(commandBox);
0283     QPushButton* copyButton = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy to clipboard"), baseWidget);
0284     mainLayout->addWidget(copyButton);
0285     pasteRowLayout->addWidget(copyButton);
0286     connect(copyButton, &QPushButton::clicked, this, &DirectionDialog::copyProxy);
0287     QPushButton* pasteButton = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("&Paste to editor"), baseWidget);
0288     mainLayout->addWidget(pasteButton);
0289     pasteRowLayout->addWidget(pasteButton);
0290     connect(pasteButton, &QPushButton::clicked, this, &DirectionDialog::pasteProxy);
0291     pasteRowLayout->addStretch();
0292 
0293     baseLayout->addSpacing(10);
0294 
0295     QDialogButtonBox *buttonBox = new QDialogButtonBox();
0296     QPushButton *user1Button = new QPushButton;
0297     buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
0298     connect(buttonBox, &QDialogButtonBox::accepted, this, &DirectionDialog::accept);
0299     connect(buttonBox, &QDialogButtonBox::rejected, this, &DirectionDialog::reject);
0300     mainLayout->addWidget(buttonBox);
0301     user1Button->setDefault(true);
0302     KGuiItem::assign(user1Button, KStandardGuiItem::close());
0303     connect(user1Button, &QPushButton::clicked, this, &DirectionDialog::close);
0304 
0305     changeCommand(0);
0306     show();
0307 }
0308 
0309 void DirectionDialog::directionChanged(int value)
0310 {
0311     Q_UNUSED(value);
0312 
0313     // if value is outside of the 0 to 359 range, then move it into that range
0314     if (previousDirectionSpin->value() < 0) {
0315         previousDirectionSpin->setValue(previousDirectionSpin->value() + 360);
0316     } else if (previousDirectionSpin->value() >= 360) {
0317         previousDirectionSpin->setValue(previousDirectionSpin->value() - 360);
0318     }
0319 
0320     // if value is outside of the 0 to 359 range, then move it into that range
0321     if (directionSpin->value() < 0) {
0322         directionSpin->setValue(directionSpin->value() + 360);
0323     } else if (directionSpin->value() >= 360) {
0324         directionSpin->setValue(directionSpin->value() - 360);
0325     }
0326 
0327     // Don't update the canvas when we just updated the direction spinbox.
0328     // (only update when the users changes the spinbox)
0329     if (skipValueChangedEvent) {
0330         skipValueChangedEvent = false;
0331         return;
0332     }
0333     updateCanvas();
0334     updateCommandBox();
0335 }
0336 
0337 void DirectionDialog::updateCanvas()
0338 {
0339     // Get the things we need, then update the canvas.
0340     int previousDir = previousDirectionSpin->value();
0341     int dir = directionSpin->value();
0342     canvas->updateDirections(previousDir, dir);
0343 }
0344 
0345 void DirectionDialog::changeCommand(int command)
0346 {
0347     currentCommand = command;
0348     if (currentCommand == Direction) {
0349         previousDirectionSpin->setEnabled(false);
0350         canvas->enableGreyTurtle(false);
0351     } else {
0352         previousDirectionSpin->setEnabled(true);
0353         canvas->enableGreyTurtle(true);
0354     }
0355     updateCanvas();
0356     updateCommandBox();
0357 }
0358 
0359 void DirectionDialog::updateDegrees(double deg)
0360 {
0361     // The canvas has changed, update the spinbox and command-LineEdit
0362     skipValueChangedEvent = true;
0363     directionSpin->setValue(static_cast<int>(round(deg)));
0364     updateCommandBox();
0365 }
0366 
0367 void DirectionDialog::updatePreviousDegrees(double deg)
0368 {
0369     // The canvas has changed, update the spinbox and commandBox
0370     skipValueChangedEvent = true;
0371     previousDirectionSpin->setValue(static_cast<int>(round(deg)));
0372     updateCommandBox();
0373 }
0374 
0375 void DirectionDialog::updateCommandBox()
0376 {
0377     // Generate a new value for the commandBox.
0378     QString output;
0379     int degree = 0;
0380     switch (currentCommand) {
0381         case Turnleft:
0382             output.append(translator->default2localized(QStringLiteral("turnleft")));
0383             degree = 360 - (directionSpin->value() - previousDirectionSpin->value());
0384             break;
0385         case Turnright:
0386             output.append(translator->default2localized(QStringLiteral("turnright")));
0387             degree = directionSpin->value() - previousDirectionSpin->value();
0388             break;
0389         case Direction:
0390             output.append(translator->default2localized(QStringLiteral("direction")));
0391             degree = directionSpin->value();
0392             break;
0393     }
0394     if (degree < 0) {
0395         degree += 360;
0396     } else if (degree >= 360) {
0397         degree -= 360;
0398     }
0399     output.append(QStringLiteral(" %1\n").arg(degree));
0400     commandBox->setText(output);
0401 }
0402 
0403 void DirectionDialog::copyProxy()
0404 {
0405     QApplication::clipboard()->setText(commandBox->text());
0406 }
0407 
0408 void DirectionDialog::pasteProxy()
0409 {
0410     Q_EMIT pasteText(commandBox->text());
0411 }
0412 
0413 #include "moc_directiondialog.cpp"