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