boxasker.cpp 7.37 KB
Newer Older
1
/***************************************************************************
2
3
 *   Copyright (C) 2004-2007 by Albert Astals Cid                          *
 *   aacid@kde.org                                                         *
4
5
6
7
8
9
10
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

Albert Astals Cid's avatar
Albert Astals Cid committed
11
12
#include "boxasker.h"

13
14
15
16
17
18
19
20
21
#include <KAcceleratorManager>
#include <KLocalizedString>

#include <QButtonGroup>
#include <QGroupBox>
#include <QEvent>
#include <QLabel>
#include <QLayout>
#include <QRadioButton>
Albert Astals Cid's avatar
Albert Astals Cid committed
22
#include <QRandomGenerator>
23
24
#include <QPushButton>
#include <QKeyEvent>
25
26

#include "map.h"
27
#include "settings.h"
28

29
30
static const int NB_CHOICES = 4;

Albert Astals Cid's avatar
Albert Astals Cid committed
31
boxAsker::boxAsker(QWidget *parent, KGmap *m, QWidget *w, uint count) : askWidget(parent, m, w, count)
32
{
Laurent G's avatar
Laurent G committed
33
	p_headWidget = NULL;
34
	p_lay = new QVBoxLayout(this);
35

Laurent G's avatar
Laurent G committed
36
37
	p_groupBox = new QGroupBox(this);
	p_groupLayout = new QGridLayout(p_groupBox);
Albert Astals Cid's avatar
Albert Astals Cid committed
38
	p_label = new QLabel(this);
Laurent G's avatar
Laurent G committed
39

40
	p_radioButtons.resize(NB_CHOICES);
Laurent G's avatar
Laurent G committed
41
	p_answerLabels.resize(NB_CHOICES);
42
	for(int i = 0; i < NB_CHOICES; i++)
43
	{
Laurent G's avatar
Laurent G committed
44
45
		p_answerLabels[i] = new QLabel(QString::number(i +1));
		p_radioButtons[i] = new QRadioButton(p_groupBox);
46

47
		p_radioButtons[i] -> installEventFilter(this);
Laurent Montel's avatar
Laurent Montel committed
48
		connect(p_radioButtons[i], &QAbstractButton::toggled, this, &boxAsker::atLeastOneSelected);
49
	}
David Gil's avatar
David Gil committed
50
	p_accept = new QPushButton();
51

Laurent G's avatar
Laurent G committed
52
53
	layoutGroupBox();
	layoutAligned();
54
55
56

	KAcceleratorManager::setNoAccel(this);

Laurent G's avatar
Laurent G committed
57
	p_groupBox -> setFocus();
58
59
}

60
61
62
63
64
boxAsker::~boxAsker()
{
	if ( p_accept->parent() == NULL )
		delete p_accept;
}
65

Laurent G's avatar
Laurent G committed
66
void boxAsker::updateLayout()
67
{
Laurent G's avatar
Laurent G committed
68
69
	layoutGroupBox();
	layoutAligned();
70
}
Laurent G's avatar
Laurent G committed
71

Laurent G's avatar
Laurent G committed
72
void boxAsker::layoutGroupBox()
73
{
Laurent G's avatar
Laurent G committed
74
75
76
77
78
79
	while ( p_groupLayout->takeAt(0) != NULL ) { }

	int horizAlignCode = kgeographySettings::self() -> questionPlacingScheme() % 3;
	Qt::Alignment horizAlignment = horizAlignCode == 0 ? Qt::AlignLeft : horizAlignCode == 1 ? Qt::AlignHCenter : Qt::AlignRight;
	p_groupLayout->setColumnStretch(0, horizAlignment == Qt::AlignLeft ? 0 : 1);
	p_groupLayout->setColumnStretch(3, horizAlignment == Qt::AlignRight ? 0 : 1);
80

Laurent G's avatar
Laurent G committed
81
	p_groupLayout -> setHorizontalSpacing(6);
82
83
	for(int i = 0; i < NB_CHOICES; i++)
	{
Laurent G's avatar
Laurent G committed
84
85
86
		p_answerLabels[i]->setAlignment(Qt::AlignRight);
		p_groupLayout -> addWidget(p_answerLabels[i], i, 0);
		p_groupLayout -> addWidget(p_radioButtons[i], i, 1);
87
	}
Laurent G's avatar
Laurent G committed
88
}
89

Laurent G's avatar
Laurent G committed
90
91
void boxAsker::layoutAligned()
{
92
	while ( p_lay->takeAt(0) != NULL ) { }
93

Laurent G's avatar
Laurent G committed
94
95
96
97
98
99
100
101
102
103
104
	if ( p_headWidget != NULL )
		p_lay->addWidget(p_headWidget);

	int horizAlignCode = kgeographySettings::self() -> questionPlacingScheme() % 3;
	Qt::Alignment horizAlignment = horizAlignCode == 0 ? Qt::AlignLeft : horizAlignCode == 1 ? Qt::AlignHCenter : Qt::AlignRight;
	int vertAlignCode = kgeographySettings::self() -> questionPlacingScheme() / 3 % 3;
	Qt::Alignment vertAlignment = vertAlignCode == 0 ? Qt::AlignTop : vertAlignCode == 1 ? Qt::AlignVCenter : Qt::AlignBottom;

	p_label -> setAlignment(horizAlignment);
	p_groupBox -> setAlignment(horizAlignment);

105
	p_lay -> addWidget(p_label);
106

Laurent G's avatar
Laurent G committed
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
	if ( vertAlignment != Qt::AlignTop ) {
		p_lay -> addStretch(1);
	}

	p_lay -> addWidget(p_groupBox, 0);

	if ( vertAlignment != Qt::AlignBottom ) {
		p_lay -> addStretch(1);
	}

	if ( kgeographySettings::self() -> waitsForValidation() ) {
		p_lay->addWidget(p_accept);
		p_accept->show();
	}
	else {
		if ( p_accept->isVisible() )
			checkAnswer();
		p_accept -> hide();
	}
126
127
}

Laurent G's avatar
Laurent G committed
128
129
bool boxAsker::eventFilter(QObject *obj, QEvent *event)
{
130
	if ( kgeographySettings::self() -> focusFollowsMouse() && event -> type() == QEvent::Enter) {
Laurent G's avatar
Laurent G committed
131
132
133
134
135
136
137
138
139
140
141
		if (obj == p_accept)
			p_accept -> setFocus();
		else
			((QRadioButton*)obj) -> setFocus();
		return true;
	} else {
		// pass the event on to the parent class
		return QWidget::eventFilter(obj, event);
	}
}

Albert Astals Cid's avatar
Albert Astals Cid committed
142
void boxAsker::setQuestion(const QString &q)
Albert Astals Cid's avatar
Albert Astals Cid committed
143
144
145
146
{
	p_label -> setText(q);
}

147
148
149
150
void boxAsker::keyPressEvent(QKeyEvent *e)
{
	// we do this on press because it is done so for 0->1-2->3 and 3->2->1->0 movements
	// (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
154
		if ( p_radioButtons[NB_CHOICES -1] -> isChecked() ) p_radioButtons[0] -> setChecked(true);
		p_radioButtons[0] -> setFocus();
155
	}
156
	else if ( e -> key() == Qt::Key_Up && p_radioButtons[0] -> hasFocus() )
157
	{
158
159
		if ( p_radioButtons[0] -> isChecked() ) p_radioButtons[NB_CHOICES -1] -> setChecked(true);
		p_radioButtons[NB_CHOICES -1] -> setFocus();
160
161
162
	}
}

Albert Astals Cid's avatar
Albert Astals Cid committed
163
164
165
void boxAsker::keyReleaseEvent(QKeyEvent *e)
{
	if (e -> key() == Qt::Key_Return || e -> key() == Qt::Key_Enter) checkAnswer();
166
167
	else if ( e -> key() >= Qt::Key_1 && e -> key() <= (Qt::Key_1 + NB_CHOICES -1) )
	{
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
172
		// next line triggered by previous, no need to go this way, crashes at end.
		//if ( ! kgeographySettings::self() -> waitsForValidation() )			checkAnswer();
173
	}
Albert Astals Cid's avatar
Albert Astals Cid committed
174
175
176
	else askWidget::keyReleaseEvent(e);
}

177
void boxAsker::nextQuestionHook(const division *div)
178
{
Albert Astals Cid's avatar
Albert Astals Cid committed
179
	QString otherDivision;
180
	QStringList auxList;
181
	int j;
182

183
184
185
186
187
	for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setAutoExclusive(false);
	for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setChecked(false);
	for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setText(QString());
	for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setIcon(QIcon());
	for(int i = 0; i < NB_CHOICES; i++) p_radioButtons[i] -> setAutoExclusive(true);
188

189
190
	p_accept -> setEnabled(false);

191
	auxList << div -> getUntranslatedName ();
192

193
	// we put the division in a random place
Albert Astals Cid's avatar
Albert Astals Cid committed
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
204
205
206
207
208
			division *otherDiv;
			do
			{
				otherDiv = p_map -> getRandomDivision(askMode());
				otherDivision = otherDiv -> getUntranslatedName();
			} while (auxList.contains(otherDivision));
209
			if (nextBoxAskerQuestionHook(otherDiv, j, false))
210
				++j;
211
212
			auxList << otherDivision;
		}
213
		else ++j;
214
215
216
	}
}

217
218
void boxAsker::atLeastOneSelected()
{
219
	if ( ! kgeographySettings::self() -> waitsForValidation() )
220
		QMetaObject::invokeMethod(this, &boxAsker::checkAnswer, Qt::QueuedConnection);
221
222
	else
		p_accept -> setEnabled(true);
223
224
}

225
226
227
void boxAsker::checkAnswer()
{
	bool any, correct;
Albert Astals Cid's avatar
Albert Astals Cid committed
228
	int i;
229

230
	correct = false;
231
	any = false;
Albert Astals Cid's avatar
Albert Astals Cid committed
232
	i = 0;
233
	while(!any && i < NB_CHOICES)
234
	{
235
		if (p_radioButtons[i] -> isChecked())
236
237
238
239
		{
			any = true;
			correct = (i == p_position);
		}
Albert Astals Cid's avatar
Albert Astals Cid committed
240
		else i++;
241
	}
242

243
244
	if (any)
	{
Albert Astals Cid's avatar
Albert Astals Cid committed
245
		setAnswerHook(i);
246
		questionAnswered(correct);
247
248
249
250
251
252
253
254
		nextQuestion();
	}
}

void boxAsker::init()
{
	p_accept -> setText(i18n("&Accept"));

255
256
	resetAnswers();
	clearAsked();
257
	nextQuestion();
258

259
	p_accept -> disconnect();
260
	connect(p_accept, &QPushButton::clicked, this, &boxAsker::checkAnswer);
261
262
}

263
264
void boxAsker::setHeadWidget(QWidget *headWidget)
{
Laurent G's avatar
Laurent G committed
265
	p_headWidget = headWidget;
266
267
268
	p_lay -> insertWidget(0, headWidget);
}

269