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