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"