1 /*
2 * Hedgewars, a free turn based strategy game
3 * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
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; version 2 of the License
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include <QGridLayout>
20 #include <QVBoxLayout>
21 #include <QLabel>
22 #include <QListWidget>
23 #include <QListWidgetItem>
24 #include <QPushButton>
25
26 #include <QTextStream>
27 #include <QFile>
28 #include <QLocale>
29 #include <QSettings>
30
31 #include "mission.h"
32 #include "hwconsts.h"
33 #include "DataManager.h"
34
35 #include "pagetraining.h"
36
bodyLayoutDefinition()37 QLayout * PageTraining::bodyLayoutDefinition()
38 {
39 QGridLayout * pageLayout = new QGridLayout();
40
41 // declare start button, caption and description
42 btnPreview = formattedButton(":/res/Trainings.png", true);
43
44 // tweak widget spacing
45 pageLayout->setRowStretch(0, 1);
46 pageLayout->setRowStretch(1, 1);
47 pageLayout->setRowStretch(2, 1);
48 pageLayout->setColumnStretch(0, 5);
49 pageLayout->setColumnStretch(1, 1);
50 pageLayout->setColumnStretch(2, 9);
51 pageLayout->setColumnStretch(3, 5);
52
53 QWidget * infoWidget = new QWidget();
54 QHBoxLayout * infoLayout = new QHBoxLayout();
55 // add preview, caption and description
56 infoWidget->setLayout(infoLayout);
57 infoLayout->addWidget(btnPreview);
58
59 // center preview
60 infoLayout->setAlignment(btnPreview, Qt::AlignRight | Qt::AlignVCenter);
61
62 // info area (caption on top, description below)
63 QWidget * infoTextWidget = new QWidget();
64 QVBoxLayout * infoTextLayout = new QVBoxLayout();
65 infoTextWidget->setObjectName("trainingInfo");
66 infoTextWidget->setLayout(infoTextLayout);
67
68 lblCaption = new QLabel();
69 lblCaption->setMinimumWidth(360);
70 lblCaption->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
71 lblCaption->setWordWrap(true);
72 lblDescription = new QLabel();
73 lblDescription->setMinimumWidth(360);
74 lblDescription->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
75 lblDescription->setWordWrap(true);
76 lblHighscores = new QLabel();
77 lblHighscores->setMinimumWidth(360);
78 lblHighscores->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
79
80 infoTextLayout->addWidget(lblCaption);
81 infoTextLayout->addWidget(lblDescription);
82 infoTextLayout->addWidget(lblHighscores);
83
84 infoLayout->addWidget(infoTextWidget);
85
86 pageLayout->addWidget(infoWidget, 0, 1, 1, 2); // span 2 columns
87 pageLayout->setAlignment(infoTextWidget, Qt::AlignLeft);
88
89
90 // tab widget containing all lists
91 tbw = new QTabWidget(this);
92 pageLayout->addWidget(tbw, 1, 0, 1, 4); // span 4 columns
93 // let's not make the tab widget use more space than needed
94 tbw->setFixedWidth(400);
95 pageLayout->setAlignment(tbw, Qt::AlignHCenter);
96
97 // training/challenge/scenario lists
98 lstTrainings = new QListWidget(this);
99 lstTrainings ->setWhatsThis(tr("Pick the training to play"));
100 lstTrainings ->setObjectName("trainingList");
101
102 lstChallenges = new QListWidget(this);
103 lstChallenges ->setWhatsThis(tr("Pick the challenge to play"));
104 lstChallenges ->setObjectName("trainingList");
105
106 lstScenarios= new QListWidget(this);
107 lstScenarios->setWhatsThis(tr("Pick the scenario to play"));
108 lstScenarios->setObjectName("trainingList");
109
110 tbw->addTab(lstTrainings, tr("Trainings"));
111 tbw->addTab(lstChallenges, tr("Challenges"));
112 tbw->addTab(lstScenarios, tr("Scenarios"));
113 tbw->setCurrentWidget(lstTrainings);
114
115 QLabel* lblteam = new QLabel(tr("Team"));
116 CBTeam = new QComboBox(this);
117 CBTeam->setMaxVisibleItems(30);
118 pageLayout->addWidget(lblteam, 2, 1);
119 pageLayout->addWidget(CBTeam, 2, 2);
120
121 return pageLayout;
122 }
123
footerLayoutDefinition()124 QLayout * PageTraining::footerLayoutDefinition()
125 {
126 QBoxLayout * bottomLayout = new QVBoxLayout();
127
128 const QIcon& lp = QIcon(":/res/Start.png");
129 QSize sz = lp.actualSize(QSize(65535, 65535));
130 btnStart = new QPushButton();
131 btnStart->setStyleSheet("padding: 5px 10px");
132 btnStart->setText(QPushButton::tr("Start"));
133 btnStart->setWhatsThis(tr("Start fighting"));
134 btnStart->setMinimumWidth(sz.width() + 60);
135 btnStart->setIcon(lp);
136 btnStart->setFixedHeight(50);
137 btnStart->setIconSize(sz);
138 btnStart->setFlat(true);
139 btnStart->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
140
141 bottomLayout->addWidget(btnStart);
142
143 bottomLayout->setAlignment(btnStart, Qt::AlignRight | Qt::AlignVCenter);
144
145 return bottomLayout;
146 }
147
148
connectSignals()149 void PageTraining::connectSignals()
150 {
151 connect(lstTrainings, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(updateInfo()));
152 connect(lstTrainings, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(updateInfo()));
153 connect(lstTrainings, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(startSelected()));
154
155 connect(lstChallenges, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(updateInfo()));
156 connect(lstChallenges, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(updateInfo()));
157 connect(lstChallenges, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(startSelected()));
158
159 connect(lstScenarios, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(updateInfo()));
160 connect(lstScenarios, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(updateInfo()));
161 connect(lstScenarios, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(startSelected()));
162
163 connect(tbw, SIGNAL(currentChanged(int)), this, SLOT(updateInfo()));
164
165 connect(btnPreview, SIGNAL(clicked()), this, SLOT(startSelected()));
166 connect(btnStart, SIGNAL(clicked()), this, SLOT(startSelected()));
167 }
168
169
PageTraining(QWidget * parent)170 PageTraining::PageTraining(QWidget* parent) : AbstractPage(parent)
171 {
172 initPage();
173
174 DataManager & dataMgr = DataManager::instance();
175
176 // get locale
177 QSettings settings(dataMgr.settingsFileName(),
178 QSettings::IniFormat);
179
180 QString loc = QLocale().name();
181
182 QString infoFile = QString("physfs://Locale/missions_" + loc + ".txt");
183
184 // if file is non-existant try with language only
185 if (!QFile::exists(infoFile))
186 infoFile = QString("physfs://Locale/missions_" + loc.remove(QRegExp("_.*$")) + ".txt");
187
188 // fallback if file for current locale is non-existant
189 if (!QFile::exists(infoFile))
190 infoFile = QString("physfs://Locale/missions_en.txt");
191
192
193 // preload mission info for current locale
194 m_info = new QSettings(infoFile, QSettings::IniFormat, this);
195 m_info->setIniCodec("UTF-8");
196
197 QStringList m_list;
198 QListWidget * m_widget;
199 QString subFolder;
200
201 for(int i=1; i<=3; i++) {
202 switch(i) {
203 case 1:
204 subFolder = "Training";
205 m_widget = lstTrainings;
206 break;
207 case 2:
208 subFolder = "Challenge";
209 m_widget = lstChallenges;
210 break;
211 case 3:
212 subFolder = "Scenario";
213 m_widget = lstScenarios;
214 break;
215 }
216 // scripts to load
217 // first, load scripts in order specified in order.cfg (if present)
218 QFile orderFile(QString("physfs://Missions/%1/order.cfg").arg(subFolder));
219 QStringList orderedMissions;
220
221 if (orderFile.open(QFile::ReadOnly))
222 {
223 QString m_id;
224 QTextStream input(&orderFile);
225 while(true)
226 {
227 m_id = input.readLine();
228 if(m_id.isNull() || m_id.isEmpty())
229 {
230 break;
231 }
232 QListWidgetItem * item = new QListWidgetItem(m_id);
233 QString name = item->text().replace("_", " ");
234 name = m_info->value(m_id + ".name", name).toString();
235 item->setText(name);
236 item->setData(Qt::UserRole, m_id);
237 m_widget->addItem(item);
238
239 orderedMissions << m_id;
240 }
241 }
242
243 // then, just load anything else in no particular order
244 m_list = dataMgr.entryList(
245 "Missions/" + subFolder,
246 QDir::Files, QStringList("*.lua")).
247 replaceInStrings(QRegExp("\\.lua$"), "");
248
249 foreach (const QString & m_id, m_list)
250 {
251 // Disallow duplicates from order.cfg
252 if (orderedMissions.contains(m_id))
253 {
254 continue;
255 }
256
257 QListWidgetItem * item = new QListWidgetItem(m_id);
258
259 // fallback name: replace underscores in mission name with spaces
260 QString name = item->text().replace("_", " ");
261
262 // see if we can get a prettier/translated name
263 name = m_info->value(m_id + ".name", name).toString();
264
265 item->setText(name);
266
267 // store original name in data
268 item->setData(Qt::UserRole, m_id);
269
270 m_widget->addItem(item);
271 }
272 }
273
274 updateInfo();
275
276 // pre-select first mission
277 if (lstTrainings->count() > 0)
278 lstTrainings->setCurrentRow(0);
279
280 if (lstChallenges->count() > 0)
281 lstChallenges->setCurrentRow(0);
282
283 if (lstScenarios->count() > 0)
284 lstScenarios->setCurrentRow(0);
285 }
286
getSubFolderOfSelected()287 QString PageTraining::getSubFolderOfSelected()
288 {
289 QString subFolder;
290 if (tbw->currentWidget() == lstTrainings) {
291 subFolder = "Training";
292 } else if (tbw->currentWidget() == lstChallenges) {
293 subFolder = "Challenge";
294 } else if (tbw->currentWidget() == lstScenarios) {
295 subFolder = "Scenario";
296 } else {
297 subFolder = "Training";
298 }
299 return subFolder;
300 }
301
startSelected()302 void PageTraining::startSelected()
303 {
304 QListWidget *list;
305 list = (QListWidget*) tbw->currentWidget();
306 QListWidgetItem * curItem = list->currentItem();
307
308 if ((curItem != NULL) && (CBTeam->currentIndex() != -1))
309 emit startMission(curItem->data(Qt::UserRole).toString(), getSubFolderOfSelected());
310 }
311
312
updateInfo()313 void PageTraining::updateInfo()
314 {
315 if (tbw->currentWidget())
316 {
317 QString subFolder;
318 QListWidget *list;
319 subFolder = getSubFolderOfSelected();
320 list = (QListWidget*) tbw->currentWidget();
321 if (list->currentItem())
322 {
323 QString missionName = list->currentItem()->data(Qt::UserRole).toString();
324 QString thumbFile = "physfs://Graphics/Missions/" +
325 subFolder + "/" +
326 missionName +
327 "@2x.png";
328
329 if (QFile::exists(thumbFile))
330 btnPreview->setIcon(QIcon(thumbFile));
331 else if (tbw->currentWidget() == lstChallenges)
332 btnPreview->setIcon(QIcon(":/res/Challenges.png"));
333 else if (tbw->currentWidget() == lstScenarios)
334 // TODO: Prettier scenario fallback image
335 btnPreview->setIcon(QIcon(":/res/Scenarios.png"));
336 else
337 btnPreview->setIcon(QIcon(":/res/Trainings.png"));
338
339 btnPreview->setWhatsThis(tr("Start fighting"));
340
341 QString caption = m_info->value(missionName + ".name",
342 list->currentItem()->text()).toString();
343
344 QString description = m_info->value(missionName + ".desc",
345 tr("No description available")).toString();
346
347 lblCaption->setText("<h2>" + caption +"</h2>");
348 lblDescription->setText(description);
349
350 // Challenge highscores
351 QString highscoreText = QString("");
352 QString teamName = CBTeam->currentText();
353 if (missionValueExists(missionName, teamName, "Highscore"))
354 highscoreText = highscoreText +
355 //: Highest score of a team
356 tr("Team highscore: %1")
357 .arg(getMissionValue(missionName, teamName, "Highscore").toString()) + "\n";
358 if (missionValueExists(missionName, teamName, "Lowscore"))
359 highscoreText = highscoreText +
360 //: Lowest score of a team
361 tr("Team lowscore: %1")
362 .arg(getMissionValue(missionName, teamName, "Lowscore").toString()) + "\n";
363 if (missionValueExists(missionName, teamName, "AccuracyRecord"))
364 highscoreText = highscoreText +
365 //: Best accuracy of a team (in a challenge)
366 tr("Team's top accuracy: %1%")
367 .arg(getMissionValue(missionName, teamName, "AccuracyRecord").toString()) + "\n";
368 if (missionValueExists(missionName, teamName, "TimeRecord"))
369 {
370 double time = ((double) getMissionValue(missionName, teamName, "TimeRecord").toInt()) / 1000.0;
371 highscoreText = highscoreText + tr("Team's best time: %L1 s").arg(time, 0, 'f', 3) + "\n";
372 }
373 if (missionValueExists(missionName, teamName, "TimeRecordHigh"))
374 {
375 double time = ((double) getMissionValue(missionName, teamName, "TimeRecordHigh").toInt()) / 1000.0;
376 highscoreText = highscoreText + tr("Team's longest time: %L1 s").arg(time, 0, 'f', 3) + "\n";
377 }
378
379 lblHighscores->setText(highscoreText);
380 }
381 else
382 {
383 btnPreview->setIcon(QIcon(":/res/Trainings.png"));
384 lblCaption->setText(tr("Select a mission!"));
385 lblDescription->setText("");
386 lblHighscores->setText("");
387 }
388 }
389 }
390