c3c3f0130e5bb7028fac332d515f95c9091b023a
[scorecard] / src / main-window.cpp
1 /*
2  * Copyright (C) 2009 Sakari Poussa
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, version 2.
7  */
8
9 #include <QtGui>
10 #ifdef Q_WS_MAEMO_5
11 #include <QMaemo5InformationBox>
12 #endif
13
14 #include "score-common.h"
15 #include "main-window.h"
16 #include "score-dialog.h"
17 #include "course-dialog.h"
18 #include "settings-dialog.h"
19 #include "stat-model.h"
20 #include "xml-dom-parser.h"
21
22 QString appName("scorecard");
23 QString topDir("/opt");
24 QString mmcDir("/media/mmc1");
25 QString dataDirName("data");
26 QString dataDir;
27 QString imgDir(topDir + "/pixmaps");
28 QString scoreFileName("score.xml");
29 QString scoreFile;
30 QString clubFileName("club.xml");
31 QString clubFile;
32 QString masterFileName("club-master.xml");
33 QString masterFile;
34 QString logFile("/tmp/scorecard.log");
35 QString titleScores("ScoreCard - Scores");
36 QString titleCourses("ScoreCard - Courses");
37
38 bool dateLessThan(const Score *s1, const Score *s2)
39 {
40   return (*s1) < (*s2);
41 }
42
43 bool dateMoreThan(const Score *s1, const Score *s2)
44 {
45   return (*s1) > (*s2);
46 }
47 // Find score based on club and course name
48 Score *MainWindow::findScore(QString & clubName, QString & courseName)
49 {
50     TRACE;
51     QListIterator<Score *> i(scoreList);
52     Score * s;
53
54     while (i.hasNext()) {
55         s = i.next();
56         if ((s->getClubName() == clubName) &&
57             (s->getCourseName() == courseName))
58             return s;
59     }
60     return 0;
61 }
62
63 // Find club based on name
64 Club *MainWindow::findClub(QString &name)
65 {
66     TRACE;
67     QListIterator<Club *> i(clubList);
68     Club *c;
69
70     while (i.hasNext()) {
71         c = i.next();
72         if (c->getName() == name)
73             return c;
74     }
75     return 0;
76 }
77
78 // Find course based on club & course name
79 Course *MainWindow::findCourse(const QString &clubName, 
80                                const QString &courseName)
81 {
82     TRACE;
83     QListIterator<Club *> i(clubList);
84     Club *c;
85
86     while (i.hasNext()) {
87         c = i.next();
88         if (c->getName() == clubName) {
89             return c->getCourse(courseName);
90         }
91     }
92     return 0;
93 }
94
95 // Find course based on current selection on the list
96 // TODO: make sure this is only called when course list is the model...
97 Course *MainWindow::currentCourse()
98 {
99     TRACE;
100     QModelIndex index = selectionModel->currentIndex();
101
102     if (!index.isValid())
103         return 0;
104
105     const QAbstractItemModel *model = selectionModel->model();
106     QString str = model->data(index, Qt::DisplayRole).toString();
107
108     QStringList strList = str.split(", ");
109     if (strList.count() != 2) {
110         showNote(tr("Invalid course selection"));
111         return 0;
112     }
113     return findCourse(strList[0], strList[1]);
114 }
115
116 // Find score based on current selection on the list
117 // TODO: make sure this is only called when score list is the model...
118 Score *MainWindow::currentScore()
119 {
120     TRACE;
121     QModelIndex index = selectionModel->currentIndex();
122
123     if (!index.isValid())
124         return 0;
125
126     return scoreList.at(index.row());
127 }
128
129 void MainWindow::flushReadOnlyItems()
130 {
131     TRACE;
132     QMutableListIterator<Club *> i(clubList);
133     Club *c;
134
135     while (i.hasNext()) {
136         c = i.next();
137         if (c->isReadOnly()) {
138             qDebug() << "Del:" << c->getName();
139             i.remove();
140         }
141     }
142 }
143
144
145 MainWindow::MainWindow(QMainWindow *parent): QMainWindow(parent)
146 {
147   resize(800, 480);
148
149 #ifdef Q_WS_MAEMO_5
150   setAttribute(Qt::WA_Maemo5StackedWindow);
151 #endif
152
153   loadSettings();
154
155   centralWidget = new QWidget(this);
156
157   setCentralWidget(centralWidget);
158
159   loadScoreFile(scoreFile, scoreList);
160   if (conf.defaultCourses == "Yes")
161       loadClubFile(masterFile, clubList, true);
162   loadClubFile(clubFile, clubList);
163
164   // Sort the scores based on dates
165   qSort(scoreList.begin(), scoreList.end(), dateMoreThan); 
166   createActions();
167   createMenus();
168
169   createListView(scoreList, clubList);
170
171   createLayoutList(centralWidget);
172
173   scoreWindow = new ScoreWindow(this);
174   courseWindow = new CourseWindow(this);
175 }
176
177 void MainWindow::loadSettings(void)
178 {
179     TRACE;
180   bool external = false;
181
182   QDir mmc(mmcDir);
183   if (mmc.exists())
184     external = true;
185
186   // TODO: make via user option, automatic will never work
187   external = false;
188
189 #ifndef Q_WS_MAEMO_5
190   dataDir = "./" + dataDirName;
191 #else
192   if (external) {
193     dataDir = mmcDir + "/" + appName + "/" + dataDirName;
194   }
195   else {
196     dataDir = topDir + "/" + appName + "/" + dataDirName;
197   }
198 #endif
199   scoreFile = dataDir + "/" + scoreFileName;
200   clubFile = dataDir + "/" + clubFileName;
201   masterFile = dataDir + "/" + masterFileName;
202
203   QDir dir(dataDir);
204   if (!dir.exists())
205     if (!dir.mkpath(dataDir)) {
206       qWarning() << "Unable to create: " + dataDir;
207       return;
208     }
209   qDebug() << "Data is at:" + dataDir;
210
211   settings.beginGroup(settingsGroup);
212   conf.hcp = settings.value(settingsHcp);
213   conf.homeClub = settings.value(settingsHomeClub);
214   conf.defaultCourses = settings.value(settingsDefaultCourses);
215   settings.endGroup();
216
217   // Use default courses if no settings for that
218   if (!conf.defaultCourses.isValid())
219       conf.defaultCourses = "Yes";
220
221   qDebug() << "Settings: " << conf.hcp << conf.homeClub << conf.defaultCourses;
222 }
223
224 void MainWindow::saveSettings(void)
225 {
226     TRACE;
227     settings.beginGroup(settingsGroup);
228     if (conf.hcp.isValid())
229         settings.setValue(settingsHcp, conf.hcp);
230     if (conf.homeClub.isValid())
231         settings.setValue(settingsHomeClub, conf.homeClub);
232     if (conf.defaultCourses.isValid())
233         settings.setValue(settingsDefaultCourses, conf.defaultCourses);
234     settings.endGroup();
235 }
236
237 void MainWindow::createLayoutList(QWidget *parent)
238 {
239     TRACE;
240     QVBoxLayout * tableLayout = new QVBoxLayout;
241     tableLayout->addWidget(list);
242
243     QHBoxLayout *mainLayout = new QHBoxLayout(parent);
244     mainLayout->addLayout(tableLayout);
245     parent->setLayout(mainLayout);
246 }
247
248 void MainWindow::createListView(QList<Score *> &scoreList, 
249                                 QList <Club *> &clubList)
250 {
251     TRACE;
252     list = new QListView(this);
253
254     scoreListModel = new ScoreListModel(scoreList, clubList);
255     courseListModel = new CourseListModel(clubList);
256
257     list->setStyleSheet(defaultStyleSheet);
258
259     list->setSelectionMode(QAbstractItemView::SingleSelection);
260     list->setProperty("FingerScrolling", true);
261
262     // Initial view
263     listScores();
264
265     connect(list, SIGNAL(clicked(QModelIndex)),
266             this, SLOT(clickedList(QModelIndex)));
267 }
268
269 void MainWindow::listScores()
270 {
271     TRACE;
272     list->setModel(scoreListModel);
273     selectionModel = list->selectionModel();
274     updateTitleBar(titleScores);
275 }
276
277 void MainWindow::listCourses()
278 {
279     TRACE;
280     list->setModel(courseListModel);
281     selectionModel = list->selectionModel();
282     updateTitleBar(titleCourses);
283 }
284
285 void MainWindow::createActions()
286 {
287     TRACE;
288     newScoreAction = new QAction(tr("New Score"), this);
289     connect(newScoreAction, SIGNAL(triggered()), this, SLOT(newScore()));
290
291     newCourseAction = new QAction(tr("New Course"), this);
292     connect(newCourseAction, SIGNAL(triggered()), this, SLOT(newCourse()));
293
294     statAction = new QAction(tr("Statistics"), this);
295     connect(statAction, SIGNAL(triggered()), this, SLOT(viewStatistics()));
296
297     settingsAction = new QAction(tr("Settings"), this);
298     connect(settingsAction, SIGNAL(triggered()), this, SLOT(viewSettings()));
299
300     // Maemo5 style menu filters
301     filterGroup = new QActionGroup(this);
302     filterGroup->setExclusive(true);
303
304     listScoreAction = new QAction(tr("Scores"), filterGroup);
305     listScoreAction->setCheckable(true);
306     listScoreAction->setChecked(true);
307     connect(listScoreAction, SIGNAL(triggered()), this, SLOT(listScores()));
308
309     listCourseAction = new QAction(tr("Courses"), filterGroup);
310     listCourseAction->setCheckable(true);
311     connect(listCourseAction, SIGNAL(triggered()), this, SLOT(listCourses()));
312 }
313
314 void MainWindow::createMenus()
315 {
316     TRACE;
317 #ifdef Q_WS_MAEMO_5
318     menu = menuBar()->addMenu("");
319 #else
320     menu = menuBar()->addMenu("Menu");
321 #endif
322
323     menu->addAction(newScoreAction);
324     menu->addAction(newCourseAction);
325     menu->addAction(statAction);
326     menu->addAction(settingsAction);
327     menu->addActions(filterGroup->actions());
328 }
329
330 void MainWindow::updateTitleBar(QString & msg)
331 {
332     TRACE;
333     setWindowTitle(msg);
334 }
335
336 void MainWindow::showNote(QString msg)
337 {
338 #ifdef Q_WS_MAEMO_5
339     QMaemo5InformationBox::information(this, 
340                                        msg,
341                                        QMaemo5InformationBox::DefaultTimeout);
342 #endif
343 }
344
345 void MainWindow::clickedList(const QModelIndex &index)
346 {
347     TRACE;
348     int row = index.row();
349
350     const QAbstractItemModel *m = index.model();
351     if (m == scoreListModel) {
352         if (row < scoreList.count()) {
353             Score * score = scoreList.at(row);
354             Course * course = findCourse(score->getClubName(), score->getCourseName());
355             viewScore(score, course);
356         }
357     }
358     else if (m == courseListModel) {
359         QString str = courseListModel->data(index, Qt::DisplayRole).toString();
360         QStringList strList = str.split(", ");
361
362         if (strList.count() != 2) {
363             showNote(QString("Invalid course selection"));
364             return;
365         }
366         Course * course = findCourse(strList.at(0), strList.at(1));
367         viewCourse(course);
368     }
369 }
370
371 void MainWindow::viewScore(Score * score, Course * course)
372 {
373     TRACE;
374     scoreWindow->setup(score, course);
375     scoreWindow->show();
376 }
377
378 void MainWindow::newScore()
379 {
380     TRACE;
381     SelectDialog *selectDialog = new SelectDialog(this);
382
383     selectDialog->init(clubList);
384
385     int result = selectDialog->exec();
386     if (result) {
387         QString clubName;
388         QString courseName;
389         QString date;
390
391         selectDialog->results(clubName, courseName, date);
392
393         ScoreDialog *scoreDialog = new ScoreDialog(this);
394         QString title = "New Score: " + courseName + ", " + date;
395         scoreDialog->setWindowTitle(title);
396
397         Club *club = findClub(clubName);
398         if (!club) {
399             showNote(tr("Error: no such club"));
400             return;
401         }
402         Course *course = club->getCourse(courseName);
403         if (!course) {
404             showNote(tr("Error: no such course:"));
405             return;
406         }
407         scoreDialog->init(course);
408         result = scoreDialog->exec();
409         if (result) {
410             QVector<QString> scores(18);
411
412             scoreDialog->results(scores);
413             Score *score = new Score(scores, clubName, courseName, date);
414             scoreList << score;
415
416             // Sort the scores based on dates
417             qSort(scoreList.begin(), scoreList.end(), dateMoreThan); 
418             // Save it
419             saveScoreFile(scoreFile, scoreList);
420             scoreListModel->update(scoreList);
421             list->update();
422         }
423     }
424 }
425
426 void MainWindow::editScore()
427 {
428     TRACE;
429     Course * course = 0;
430     Score *score = currentScore();
431
432     if (score) 
433         course = findCourse(score->getClubName(), score->getCourseName());
434
435     if (!course || !score) {
436         qDebug() << "No score/course to edit";
437         return;
438     }
439
440     QString date = score->getDate();
441
442     ScoreDialog *scoreDialog = new ScoreDialog(this);
443     scoreDialog->init(course, score);
444   
445     QString title = "Edit Score: " + course->getName() + ", " + date;
446     scoreDialog->setWindowTitle(title);
447
448     int result = scoreDialog->exec();
449     if (result) {
450         QVector<QString> scores(18);
451
452         scoreDialog->results(scores);
453     
454         score->update(scores);
455
456         // Sort the scores based on dates
457         qSort(scoreList.begin(), scoreList.end(), dateMoreThan); 
458         // Save it
459         saveScoreFile(scoreFile, scoreList);
460     }
461     if (scoreDialog)
462         delete scoreDialog;
463 }
464
465 void MainWindow::deleteScore()
466 {
467     TRACE;
468     if (scoreWindow)
469         scoreWindow->close();
470
471     QModelIndex index = selectionModel->currentIndex();
472     if (!index.isValid()) {
473         qDebug() << "Invalid index";
474         return;
475     }
476     
477     scoreList.removeAt(index.row());
478     // Save it
479     saveScoreFile(scoreFile, scoreList);
480     scoreListModel->update(scoreList);
481     list->update();
482 }
483
484 void MainWindow::viewCourse(Course * course)
485 {
486     TRACE;
487     courseWindow->setup(course);
488     courseWindow->show();
489 }
490
491 void MainWindow::newCourse()
492 {
493     TRACE;
494     CourseSelectDialog *selectDialog = new CourseSelectDialog(this);
495
496     int result = selectDialog->exec();
497     if (result) {
498         QString clubName;
499         QString courseName;
500         QString date;
501
502         selectDialog->results(clubName, courseName);
503
504         CourseDialog *courseDialog = new CourseDialog(this);
505         courseDialog->init();
506         QString title = "New Course: " + clubName + "," + courseName;
507         courseDialog->setWindowTitle(title);
508
509         int result = courseDialog->exec();
510         if (result) {
511             QVector<QString> par(18);
512             QVector<QString> hcp(18);
513             QVector<QString> len(18);
514
515             courseDialog->results(par, hcp, len);
516
517             Course *course = 0;
518             Club *club = findClub(clubName);
519             if (club) {
520                 course = club->getCourse(courseName);
521                 if (course) {
522                     showNote(tr("Club/course already in the database"));
523                     return;
524                 }
525                 else {
526                     course = new Course(courseName, par, hcp);
527                     club->addCourse(course);
528                 }
529             }
530             else {
531                 // New club and course
532                 club = new Club(clubName);
533                 course = new Course(courseName, par, hcp);
534                 club->addCourse(course);
535                 clubList << club;
536             }
537             // Save it
538             saveClubFile(clubFile, clubList);
539             courseListModel->update(clubList);
540             list->update();
541         }
542     }
543 }
544
545 void MainWindow::editCourse()
546 {
547     TRACE;
548     Course *course = currentCourse();
549
550     if (!course) {
551         showNote(tr("No course on edit"));
552         return;
553     }
554
555     CourseDialog *courseDialog = new CourseDialog(this);
556     courseDialog->init(course);
557
558     QString title = "Edit Course: " + course->getName();
559     courseDialog->setWindowTitle(title);
560   
561     int result = courseDialog->exec();
562     if (result) {
563         QVector<QString> par(18);
564         QVector<QString> hcp(18);
565         QVector<QString> len(18);
566     
567         courseDialog->results(par, hcp, len);
568     
569         course->update(par, hcp, len);
570         saveClubFile(clubFile, clubList);
571     }
572     if (courseDialog)
573         delete courseDialog;
574 }
575
576 void MainWindow::deleteCourse()
577 {
578     TRACE;
579     Club *club = 0;
580     Course * course = currentCourse();
581
582     if (!course) {
583         qDebug() << "Invalid course for deletion";
584         return;
585     }
586     club = course->parent();
587
588     // Can not delete course if it has scores -- check
589     if (findScore(club->getName(), course->getName()) != 0) {
590         showNote(tr("Can not delete course, delete scores on the course first"));
591         return;
592     }
593     // Close the window
594     if (courseWindow)
595         courseWindow->close();
596
597     club->delCourse(course);
598
599     if (club->isEmpty()) {
600         int index = clubList.indexOf(club);
601         if (index != -1)
602             clubList.removeAt(index);
603     }
604
605     // Save it
606     saveClubFile(clubFile, clubList);
607     courseListModel->update(clubList);
608     list->update();
609 }
610
611 void MainWindow::viewStatistics()
612 {
613     TRACE;
614     QMainWindow *win = new QMainWindow(this);
615     QString title = "Statistics";
616     win->setWindowTitle(title);
617 #ifdef Q_WS_MAEMO_5
618     win->setAttribute(Qt::WA_Maemo5StackedWindow);
619 #endif
620
621     StatModel *model = new StatModel(clubList, scoreList);
622
623     QTableView *table = new QTableView;
624     table->showGrid();
625     table->setSelectionMode(QAbstractItemView::NoSelection);
626     table->setStyleSheet(statStyleSheet);
627     table->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
628     table->verticalHeader()->setResizeMode(QHeaderView::Stretch);
629     table->verticalHeader()->setAutoFillBackground(true);
630     table->setModel(model);
631
632     QWidget *central = new QWidget(win);
633     win->setCentralWidget(central);
634
635     QTextEdit *textEdit = new QTextEdit;
636
637     textEdit->setReadOnly(true);
638
639     QVBoxLayout *infoLayout = new QVBoxLayout;
640     infoLayout->addWidget(table);
641
642     QHBoxLayout *mainLayout = new QHBoxLayout(central);
643     mainLayout->addLayout(infoLayout);
644     central->setLayout(mainLayout);
645
646     win->show();
647 }
648
649 void MainWindow::viewSettings()
650 {
651     TRACE;
652     SettingsDialog *dlg = new SettingsDialog(this);
653
654     dlg->init(conf, clubList);
655
656     int result = dlg->exec();
657     if (result) {
658         QString oldValue = conf.defaultCourses.toString();
659         dlg->results(conf);
660         QString newValue = conf.defaultCourses.toString();
661         saveSettings();
662
663         qDebug() << "Settings:" << oldValue << "->" << newValue;
664
665         // Reload club list, or drop r/o courses from list
666         if (oldValue == "Yes" && newValue == "No") {
667             flushReadOnlyItems();
668             courseListModel->update(clubList);
669             list->update();
670         }
671         else if ((oldValue == "No" || oldValue == "") && newValue == "Yes") {
672             loadClubFile(masterFile, clubList, true);
673             courseListModel->update(clubList);
674             list->update();
675         }
676     }
677 }
678
679 void MainWindow::loadScoreFile(QString &fileName, QList<Score *> &list)
680 {
681   ScoreXmlHandler handler(list);
682
683   if (handler.parse(fileName))
684     qDebug() << "File loaded:" << fileName << " entries:" << list.size();
685 }
686
687 void MainWindow::saveScoreFile(QString &fileName, QList<Score *> &list)
688 {
689   ScoreXmlHandler handler(list);
690
691   if (handler.save(fileName))
692     // TODO: banner
693     qDebug() << "File saved:" << fileName << " entries:" << list.size();
694   else
695     qWarning() << "Unable to save:" << fileName;
696 }
697
698 void MainWindow::loadClubFile(QString &fileName, QList<Club *> &list, bool readOnly)
699 {
700     ClubXmlHandler handler(list);
701
702     if (handler.parse(fileName, readOnly))
703         qDebug() << "File loaded:" << fileName << " entries:" << list.size();
704 }
705
706 void MainWindow::saveClubFile(QString &fileName, QList<Club *> &list)
707 {
708   ClubXmlHandler handler(list);
709
710   if (handler.save(fileName))
711     // TODO: banner
712     qDebug() << "File saved:" << fileName << " entries:" << list.size();
713   else
714     qWarning() << "Unable to save:" << fileName;
715
716 }