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