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