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