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