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