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