File indexing completed on 2024-04-21 03:41:51

0001 /***************************************************************************
0002  *   Copyright (C) 2004-2007 by Albert Astals Cid                          *
0003  *   aacid@kde.org                                                         *
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 "boxasker.h"
0012 
0013 #include <KAcceleratorManager>
0014 #include <KLocalizedString>
0015 
0016 #include <QButtonGroup>
0017 #include <QGroupBox>
0018 #include <QEvent>
0019 #include <QLabel>
0020 #include <QLayout>
0021 #include <QRadioButton>
0022 #include <QRandomGenerator>
0023 #include <QPushButton>
0024 #include <QKeyEvent>
0025 
0026 #include "map.h"
0027 #include "settings.h"
0028 
0029 static const int NB_CHOICES = 4;
0030 
0031 boxAsker::boxAsker(QWidget *parent, KGmap *m, QWidget *w, uint count) : askWidget(parent, m, w, count)
0032 {
0033     p_headWidget = nullptr;
0034     p_lay = new QVBoxLayout(this);
0035 
0036     p_groupBox = new QGroupBox(this);
0037     p_groupLayout = new QGridLayout(p_groupBox);
0038     p_label = new QLabel(this);
0039 
0040     p_radioButtons.resize(NB_CHOICES);
0041     p_answerLabels.resize(NB_CHOICES);
0042     for(int i = 0; i < NB_CHOICES; i++)
0043     {
0044         p_answerLabels[i] = new QLabel(QString::number(i +1));
0045         p_radioButtons[i] = new QRadioButton(p_groupBox);
0046 
0047         p_radioButtons[i] -> installEventFilter(this);
0048         connect(p_radioButtons[i], &QAbstractButton::toggled, this, &boxAsker::atLeastOneSelected);
0049     }
0050     p_accept = new QPushButton();
0051 
0052     layoutGroupBox();
0053     layoutAligned();
0054 
0055     KAcceleratorManager::setNoAccel(this);
0056 
0057     p_groupBox -> setFocus();
0058 }
0059 
0060 boxAsker::~boxAsker()
0061 {
0062     if ( p_accept->parent() == nullptr )
0063         delete p_accept;
0064 }
0065 
0066 void boxAsker::updateLayout()
0067 {
0068     layoutGroupBox();
0069     layoutAligned();
0070 }
0071 
0072 void boxAsker::layoutGroupBox()
0073 {
0074     while ( p_groupLayout->takeAt(0) != nullptr ) { }
0075 
0076     int horizAlignCode = kgeographySettings::self() -> questionPlacingScheme() % 3;
0077     Qt::Alignment horizAlignment = horizAlignCode == 0 ? Qt::AlignLeft : horizAlignCode == 1 ? Qt::AlignHCenter : Qt::AlignRight;
0078     p_groupLayout->setColumnStretch(0, horizAlignment == Qt::AlignLeft ? 0 : 1);
0079     p_groupLayout->setColumnStretch(3, horizAlignment == Qt::AlignRight ? 0 : 1);
0080 
0081     p_groupLayout -> setHorizontalSpacing(6);
0082     for(int i = 0; i < NB_CHOICES; i++)
0083     {
0084         p_answerLabels[i]->setAlignment(Qt::AlignRight);
0085         p_groupLayout -> addWidget(p_answerLabels[i], i, 0);
0086         p_groupLayout -> addWidget(p_radioButtons[i], i, 1);
0087     }
0088 }
0089 
0090 void boxAsker::layoutAligned()
0091 {
0092     while ( p_lay->takeAt(0) != nullptr ) { }
0093 
0094     if ( p_headWidget != nullptr )
0095         p_lay->addWidget(p_headWidget);
0096 
0097     int horizAlignCode = kgeographySettings::self() -> questionPlacingScheme() % 3;
0098     Qt::Alignment horizAlignment = horizAlignCode == 0 ? Qt::AlignLeft : horizAlignCode == 1 ? Qt::AlignHCenter : Qt::AlignRight;
0099     int vertAlignCode = kgeographySettings::self() -> questionPlacingScheme() / 3 % 3;
0100     Qt::Alignment vertAlignment = vertAlignCode == 0 ? Qt::AlignTop : vertAlignCode == 1 ? Qt::AlignVCenter : Qt::AlignBottom;
0101 
0102     p_label -> setAlignment(horizAlignment);
0103     p_groupBox -> setAlignment(horizAlignment);
0104 
0105     p_lay -> addWidget(p_label);
0106 
0107     if ( vertAlignment != Qt::AlignTop ) {
0108         p_lay -> addStretch(1);
0109     }
0110 
0111     p_lay -> addWidget(p_groupBox, 0);
0112 
0113     if ( vertAlignment != Qt::AlignBottom ) {
0114         p_lay -> addStretch(1);
0115     }
0116 
0117     if ( kgeographySettings::self() -> waitsForValidation() ) {
0118         p_lay->addWidget(p_accept);
0119         p_accept->show();
0120     }
0121     else {
0122         if ( p_accept->isVisible() )
0123             checkAnswer();
0124         p_accept -> hide();
0125     }
0126 }
0127 
0128 bool boxAsker::eventFilter(QObject *obj, QEvent *event)
0129 {
0130     if ( kgeographySettings::self() -> focusFollowsMouse() && event -> type() == QEvent::Enter) {
0131         if (obj == p_accept)
0132             p_accept -> setFocus();
0133         else
0134             ((QRadioButton*)obj) -> setFocus();
0135         return true;
0136     } else {
0137         // pass the event on to the parent class
0138         return QWidget::eventFilter(obj, event);
0139     }
0140 }
0141 
0142 void boxAsker::setQuestion(const QString &q)
0143 {
0144     p_label -> setText(q);
0145 }
0146 
0147 void boxAsker::keyPressEvent(QKeyEvent *e)
0148 {
0149     // we do this on press because it is done so for 0->1-2->3 and 3->2->1->0 movements
0150     // (those keys are subject to repeat, they have to be treated as press)
0151     if ( e -> key() == Qt::Key_Down && p_radioButtons[NB_CHOICES -1] -> hasFocus() )
0152     {
0153         if ( p_radioButtons[NB_CHOICES -1] -> isChecked() ) p_radioButtons[0] -> setChecked(true);
0154         p_radioButtons[0] -> setFocus();
0155     }
0156     else if ( e -> key() == Qt::Key_Up && p_radioButtons[0] -> hasFocus() )
0157     {
0158         if ( p_radioButtons[0] -> isChecked() ) p_radioButtons[NB_CHOICES -1] -> setChecked(true);
0159         p_radioButtons[NB_CHOICES -1] -> setFocus();
0160     }
0161 }
0162 
0163 void boxAsker::keyReleaseEvent(QKeyEvent *e)
0164 {
0165     if (e -> key() == Qt::Key_Return || e -> key() == Qt::Key_Enter) checkAnswer();
0166     else if ( e -> key() >= Qt::Key_1 && e -> key() <= (Qt::Key_1 + NB_CHOICES -1) )
0167     {
0168         p_radioButtons[e -> key() - Qt::Key_1] -> setFocus();
0169         // we check the box after the focus because the check can trigger immediate destruction of the asker at last question
0170         p_radioButtons[e -> key() - Qt::Key_1] -> setChecked(true);
0171         // next line triggered by previous, no need to go this way, crashes at end.
0172         //if ( ! kgeographySettings::self() -> waitsForValidation() )           checkAnswer();
0173     }
0174     else askWidget::keyReleaseEvent(e);
0175 }
0176 
0177 void boxAsker::nextQuestionHook(const division *div)
0178 {
0179     QString otherDivision;
0180     QStringList auxList;
0181     int j;
0182 
0183     for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setAutoExclusive(false);
0184     for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setChecked(false);
0185     for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setText(QString());
0186     for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setIcon(QIcon());
0187     for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setAutoExclusive(true);
0188 
0189     p_accept -> setEnabled(false);
0190 
0191     auxList << div -> getUntranslatedName ();
0192 
0193     // we put the division in a random place
0194     p_position = QRandomGenerator::global()->bounded(NB_CHOICES);
0195     nextBoxAskerQuestionHook(div, p_position, true);
0196 
0197     // fill the other names
0198     j = 0;
0199     while (j < NB_CHOICES)
0200     {
0201         if (p_radioButtons[j] -> text().isNull() && p_radioButtons[j] -> icon().isNull())
0202         {
0203             division *otherDiv;
0204             do
0205             {
0206                 otherDiv = p_map -> getRandomDivision(askMode());
0207                 otherDivision = otherDiv -> getUntranslatedName();
0208             } while (auxList.contains(otherDivision));
0209             if (nextBoxAskerQuestionHook(otherDiv, j, false))
0210                 ++j;
0211             auxList << otherDivision;
0212         }
0213         else ++j;
0214     }
0215 }
0216 
0217 void boxAsker::atLeastOneSelected()
0218 {
0219     if ( ! kgeographySettings::self() -> waitsForValidation() )
0220         QMetaObject::invokeMethod(this, &boxAsker::checkAnswer, Qt::QueuedConnection);
0221     else
0222         p_accept -> setEnabled(true);
0223 }
0224 
0225 void boxAsker::checkAnswer()
0226 {
0227     bool any, correct;
0228     int i;
0229 
0230     correct = false;
0231     any = false;
0232     i = 0;
0233     while(!any && i < NB_CHOICES)
0234     {
0235         if (p_radioButtons[i] -> isChecked())
0236         {
0237             any = true;
0238             correct = (i == p_position);
0239         }
0240         else i++;
0241     }
0242 
0243     if (any)
0244     {
0245         setAnswerHook(i);
0246         questionAnswered(correct);
0247         nextQuestion();
0248     }
0249 }
0250 
0251 void boxAsker::init()
0252 {
0253     p_accept -> setText(i18n("&Accept"));
0254 
0255     resetAnswers();
0256     clearAsked();
0257     nextQuestion();
0258 
0259     p_accept -> disconnect();
0260     connect(p_accept, &QPushButton::clicked, this, &boxAsker::checkAnswer);
0261 }
0262 
0263 void boxAsker::setHeadWidget(QWidget *headWidget)
0264 {
0265     p_headWidget = headWidget;
0266     p_lay -> insertWidget(0, headWidget);
0267 }
0268 
0269 #include "moc_boxasker.cpp"