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