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