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