1 /***************************************************************************
2 * Copyright (C) 2004-2007 by Albert Astals Cid *
3 * aacid@kde.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
10
11 #include "boxasker.h"
12
13 #include <KAcceleratorManager>
14 #include <KLocalizedString>
15
16 #include <QButtonGroup>
17 #include <QGroupBox>
18 #include <QEvent>
19 #include <QLabel>
20 #include <QLayout>
21 #include <QRadioButton>
22 #include <QRandomGenerator>
23 #include <QPushButton>
24 #include <QKeyEvent>
25
26 #include "map.h"
27 #include "settings.h"
28
29 static const int NB_CHOICES = 4;
30
boxAsker(QWidget * parent,KGmap * m,QWidget * w,uint count)31 boxAsker::boxAsker(QWidget *parent, KGmap *m, QWidget *w, uint count) : askWidget(parent, m, w, count)
32 {
33 p_headWidget = NULL;
34 p_lay = new QVBoxLayout(this);
35
36 p_groupBox = new QGroupBox(this);
37 p_groupLayout = new QGridLayout(p_groupBox);
38 p_label = new QLabel(this);
39
40 p_radioButtons.resize(NB_CHOICES);
41 p_answerLabels.resize(NB_CHOICES);
42 for(int i = 0; i < NB_CHOICES; i++)
43 {
44 p_answerLabels[i] = new QLabel(QString::number(i +1));
45 p_radioButtons[i] = new QRadioButton(p_groupBox);
46
47 p_radioButtons[i] -> installEventFilter(this);
48 connect(p_radioButtons[i], &QAbstractButton::toggled, this, &boxAsker::atLeastOneSelected);
49 }
50 p_accept = new QPushButton();
51
52 layoutGroupBox();
53 layoutAligned();
54
55 KAcceleratorManager::setNoAccel(this);
56
57 p_groupBox -> setFocus();
58 }
59
~boxAsker()60 boxAsker::~boxAsker()
61 {
62 if ( p_accept->parent() == NULL )
63 delete p_accept;
64 }
65
updateLayout()66 void boxAsker::updateLayout()
67 {
68 layoutGroupBox();
69 layoutAligned();
70 }
71
layoutGroupBox()72 void boxAsker::layoutGroupBox()
73 {
74 while ( p_groupLayout->takeAt(0) != NULL ) { }
75
76 int horizAlignCode = kgeographySettings::self() -> questionPlacingScheme() % 3;
77 Qt::Alignment horizAlignment = horizAlignCode == 0 ? Qt::AlignLeft : horizAlignCode == 1 ? Qt::AlignHCenter : Qt::AlignRight;
78 p_groupLayout->setColumnStretch(0, horizAlignment == Qt::AlignLeft ? 0 : 1);
79 p_groupLayout->setColumnStretch(3, horizAlignment == Qt::AlignRight ? 0 : 1);
80
81 p_groupLayout -> setHorizontalSpacing(6);
82 for(int i = 0; i < NB_CHOICES; i++)
83 {
84 p_answerLabels[i]->setAlignment(Qt::AlignRight);
85 p_groupLayout -> addWidget(p_answerLabels[i], i, 0);
86 p_groupLayout -> addWidget(p_radioButtons[i], i, 1);
87 }
88 }
89
layoutAligned()90 void boxAsker::layoutAligned()
91 {
92 while ( p_lay->takeAt(0) != NULL ) { }
93
94 if ( p_headWidget != NULL )
95 p_lay->addWidget(p_headWidget);
96
97 int horizAlignCode = kgeographySettings::self() -> questionPlacingScheme() % 3;
98 Qt::Alignment horizAlignment = horizAlignCode == 0 ? Qt::AlignLeft : horizAlignCode == 1 ? Qt::AlignHCenter : Qt::AlignRight;
99 int vertAlignCode = kgeographySettings::self() -> questionPlacingScheme() / 3 % 3;
100 Qt::Alignment vertAlignment = vertAlignCode == 0 ? Qt::AlignTop : vertAlignCode == 1 ? Qt::AlignVCenter : Qt::AlignBottom;
101
102 p_label -> setAlignment(horizAlignment);
103 p_groupBox -> setAlignment(horizAlignment);
104
105 p_lay -> addWidget(p_label);
106
107 if ( vertAlignment != Qt::AlignTop ) {
108 p_lay -> addStretch(1);
109 }
110
111 p_lay -> addWidget(p_groupBox, 0);
112
113 if ( vertAlignment != Qt::AlignBottom ) {
114 p_lay -> addStretch(1);
115 }
116
117 if ( kgeographySettings::self() -> waitsForValidation() ) {
118 p_lay->addWidget(p_accept);
119 p_accept->show();
120 }
121 else {
122 if ( p_accept->isVisible() )
123 checkAnswer();
124 p_accept -> hide();
125 }
126 }
127
eventFilter(QObject * obj,QEvent * event)128 bool boxAsker::eventFilter(QObject *obj, QEvent *event)
129 {
130 if ( kgeographySettings::self() -> focusFollowsMouse() && event -> type() == QEvent::Enter) {
131 if (obj == p_accept)
132 p_accept -> setFocus();
133 else
134 ((QRadioButton*)obj) -> setFocus();
135 return true;
136 } else {
137 // pass the event on to the parent class
138 return QWidget::eventFilter(obj, event);
139 }
140 }
141
setQuestion(const QString & q)142 void boxAsker::setQuestion(const QString &q)
143 {
144 p_label -> setText(q);
145 }
146
keyPressEvent(QKeyEvent * e)147 void boxAsker::keyPressEvent(QKeyEvent *e)
148 {
149 // we do this on press because it is done so for 0->1-2->3 and 3->2->1->0 movements
150 // (those keys are subject to repeat, they have to be treated as press)
151 if ( e -> key() == Qt::Key_Down && p_radioButtons[NB_CHOICES -1] -> hasFocus() )
152 {
153 if ( p_radioButtons[NB_CHOICES -1] -> isChecked() ) p_radioButtons[0] -> setChecked(true);
154 p_radioButtons[0] -> setFocus();
155 }
156 else if ( e -> key() == Qt::Key_Up && p_radioButtons[0] -> hasFocus() )
157 {
158 if ( p_radioButtons[0] -> isChecked() ) p_radioButtons[NB_CHOICES -1] -> setChecked(true);
159 p_radioButtons[NB_CHOICES -1] -> setFocus();
160 }
161 }
162
keyReleaseEvent(QKeyEvent * e)163 void boxAsker::keyReleaseEvent(QKeyEvent *e)
164 {
165 if (e -> key() == Qt::Key_Return || e -> key() == Qt::Key_Enter) checkAnswer();
166 else if ( e -> key() >= Qt::Key_1 && e -> key() <= (Qt::Key_1 + NB_CHOICES -1) )
167 {
168 p_radioButtons[e -> key() - Qt::Key_1] -> setFocus();
169 // we check the box after the focus because the check can trigger immediate destruction of the asker at last question
170 p_radioButtons[e -> key() - Qt::Key_1] -> setChecked(true);
171 // next line triggered by previous, no need to go this way, crashes at end.
172 //if ( ! kgeographySettings::self() -> waitsForValidation() ) checkAnswer();
173 }
174 else askWidget::keyReleaseEvent(e);
175 }
176
nextQuestionHook(const division * div)177 void boxAsker::nextQuestionHook(const division *div)
178 {
179 QString otherDivision;
180 QStringList auxList;
181 int j;
182
183 for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setAutoExclusive(false);
184 for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setChecked(false);
185 for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setText(QString());
186 for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setIcon(QIcon());
187 for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setAutoExclusive(true);
188
189 p_accept -> setEnabled(false);
190
191 auxList << div -> getUntranslatedName ();
192
193 // we put the division in a random place
194 p_position = QRandomGenerator::global()->bounded(NB_CHOICES);
195 nextBoxAskerQuestionHook(div, p_position, true);
196
197 // fill the other names
198 j = 0;
199 while (j < NB_CHOICES)
200 {
201 if (p_radioButtons[j] -> text().isNull() && p_radioButtons[j] -> icon().isNull())
202 {
203 division *otherDiv;
204 do
205 {
206 otherDiv = p_map -> getRandomDivision(askMode());
207 otherDivision = otherDiv -> getUntranslatedName();
208 } while (auxList.contains(otherDivision));
209 if (nextBoxAskerQuestionHook(otherDiv, j, false))
210 ++j;
211 auxList << otherDivision;
212 }
213 else ++j;
214 }
215 }
216
atLeastOneSelected()217 void boxAsker::atLeastOneSelected()
218 {
219 if ( ! kgeographySettings::self() -> waitsForValidation() )
220 QMetaObject::invokeMethod(this, &boxAsker::checkAnswer, Qt::QueuedConnection);
221 else
222 p_accept -> setEnabled(true);
223 }
224
checkAnswer()225 void boxAsker::checkAnswer()
226 {
227 bool any, correct;
228 int i;
229
230 correct = false;
231 any = false;
232 i = 0;
233 while(!any && i < NB_CHOICES)
234 {
235 if (p_radioButtons[i] -> isChecked())
236 {
237 any = true;
238 correct = (i == p_position);
239 }
240 else i++;
241 }
242
243 if (any)
244 {
245 setAnswerHook(i);
246 questionAnswered(correct);
247 nextQuestion();
248 }
249 }
250
init()251 void boxAsker::init()
252 {
253 p_accept -> setText(i18n("&Accept"));
254
255 resetAnswers();
256 clearAsked();
257 nextQuestion();
258
259 p_accept -> disconnect();
260 connect(p_accept, &QPushButton::clicked, this, &boxAsker::checkAnswer);
261 }
262
setHeadWidget(QWidget * headWidget)263 void boxAsker::setHeadWidget(QWidget *headWidget)
264 {
265 p_headWidget = headWidget;
266 p_lay -> insertWidget(0, headWidget);
267 }
268
269
270