Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
++
++ #include "mainwindow.h"
++ #include "objectinspectorwidget.h"
++ #include "penwidget.h"
++ #include "brushwidget.h"
++ #include "engine.h"
++ #include <QtCore/QSettings>
++ #include <QtCharts/QChartView>
++ #include <QtWidgets/QDockWidget>
++ #include <QtWidgets/QMenuBar>
++ #include <QtWidgets/QTableWidget>
++ #include <QtWidgets/QSplitter>
++ #include <QtCore/QMetaEnum>
++ #include <QtGui/QStandardItemModel>
++ #include <QtWidgets/QHeaderView>
++ #include <QtCore/QDebug>
++ #include <QtWidgets/QMessageBox>
++
++ static const QString FILENAME_SETTING("filename");
++ static const QString GEOMETRY_SETTING("geometry");
++
++ MainWindow::MainWindow() :
++ m_engine(new Engine(this)),
++ m_view(new QChartView(m_engine->chart())),
++ m_scene(m_view->scene()),
++ m_table(new QTableView()),
++ m_addSeriesMenu(0),
++ m_seriesMenu(0),
++ m_chartMenu(0),
++ m_themeMenu(0),
++ m_animationMenu(0),
++ m_zoomMenu(0),
++ m_removeAllAction(0),
++ m_legendAction(0),
++ m_antialiasingAction(0)
++ {
++ createMenus();
++ createDockWidgets();
++ createTable();
++ createLayout();
++
++ QSettings settings;
++ restoreGeometry(settings.value(GEOMETRY_SETTING).toByteArray());
++ m_filename = settings.value(FILENAME_SETTING).toString();
++ if (m_filename.isEmpty())
++ m_filename = "untitled";
++
++ setWindowTitle(m_filename);
++ updateUI();
++ }
++
++ MainWindow::~MainWindow()
++ {
++ delete m_engine;
++ }
++
++ void MainWindow::createMenus()
++ {
++ QMenu *file = menuBar()->addMenu(tr("File"));
++ QMenu *edit = menuBar()->addMenu(tr("Edit"));
++ m_seriesMenu = menuBar()->addMenu(tr("Series"));
++ m_chartMenu = menuBar()->addMenu(tr("Chart"));
++
++ m_addSeriesMenu = new QMenu(tr("Add series"));
++ m_themeMenu = new QMenu(tr("Apply theme"));
++ m_animationMenu = new QMenu(tr("Animations"));
++ m_zoomMenu = new QMenu(tr("Zoom"));
++
++ file->addAction(tr("New"), this, SLOT(handleNewAction()));
++ file->addAction(tr("Load"), this, SLOT(handleLoadAction()));
++ file->addAction(tr("Save"), this, SLOT(handleSaveAction()));
++ file->addAction(tr("Save As"), this, SLOT(handleSaveAsAction()));
++
++ //seriesMenu
++ m_seriesMenu->addMenu(m_addSeriesMenu);
++ m_removeAllAction = new QAction(tr("Remove all series"), this);
++ QObject::connect(m_removeAllAction, SIGNAL(triggered()), this, SLOT(handleRemoveAllSeriesAction()));
++ m_seriesMenu->addAction(m_removeAllAction);
++ m_seriesMenu->addSeparator();
++
++ //seriesMenu /addSeriesMenu
++ {
++ int index = QAbstractSeries::staticMetaObject.indexOfEnumerator("SeriesType");
++ QMetaEnum metaEnum = QAbstractSeries::staticMetaObject.enumerator(index);
++
++ int count = metaEnum.keyCount();
++
++ for (int i = 0; i < count; ++i) {
++ QAction* action = new QAction(metaEnum.key(i), this);
++ action->setData(metaEnum.value(i));
++ m_addSeriesMenu->addAction(action);
++ QObject::connect(action, SIGNAL(triggered()), this, SLOT(handleAddSeriesMenu()));
++ }
++ }
++
++ //chartMenu / themeMenu
++ {
++ m_chartMenu->addMenu(m_themeMenu);
++ int index = QChart::staticMetaObject.indexOfEnumerator("ChartTheme");
++ QMetaEnum metaEnum = QChart::staticMetaObject.enumerator(index);
++
++ int count = metaEnum.keyCount();
++
++ for (int i = 0; i < count; ++i) {
++ QAction* action = new QAction(metaEnum.key(i), this);
++ action->setData(metaEnum.value(i));
++ action->setCheckable(true);
++ m_themeMenu->addAction(action);
++ QObject::connect(action, SIGNAL(triggered()), this, SLOT(handleThemeMenu()));
++ }
++ }
++
++ //chartMenu / animationMenu
++ {
++ m_chartMenu->addMenu(m_animationMenu);
++ int index = QChart::staticMetaObject.indexOfEnumerator("AnimationOption");
++ QMetaEnum metaEnum = QChart::staticMetaObject.enumerator(index);
++
++ int count = metaEnum.keyCount();
++
++ for (int i = 0; i < count; ++i) {
++ QAction* action = new QAction(metaEnum.key(i), this);
++ action->setData(metaEnum.value(i));
++ action->setCheckable(true);
++ m_animationMenu->addAction(action);
++ QObject::connect(action, SIGNAL(triggered()), this, SLOT(handleAnimationMenu()));
++ }
++ }
++
++ //chartMenu / zoomMenu
++ {
++ m_chartMenu->addMenu(m_zoomMenu);
++ int index = QChartView::staticMetaObject.indexOfEnumerator("RubberBand");
++ QMetaEnum metaEnum = QChartView::staticMetaObject.enumerator(index);
++
++ int count = metaEnum.keyCount();
++
++ for (int i = 0; i < count; ++i) {
++ QAction* action = new QAction(metaEnum.key(i), this);
++ action->setData(metaEnum.value(i));
++ action->setCheckable(true);
++ m_zoomMenu->addAction(action);
++ QObject::connect(action, SIGNAL(triggered()), this, SLOT(handleZoomMenu()));
++ }
++ }
++
++ //chartMenu / legend
++ m_legendAction = new QAction(tr("Legend"), this);
++ m_legendAction->setCheckable(true);
++ m_chartMenu->addAction(m_legendAction);
++ QObject::connect(m_legendAction, SIGNAL(triggered()), this, SLOT(handleLegendAction()));
++
++ //chartMenu / Anti-aliasing
++ m_antialiasingAction = new QAction(tr("Anti-aliasing"), this);
++ m_antialiasingAction->setCheckable(true);
++ m_chartMenu->addAction(m_antialiasingAction);
++ QObject::connect(m_antialiasingAction, SIGNAL(triggered()), this, SLOT(handleAntialiasingAction()));
++
++ }
++
++ void MainWindow::createDockWidgets()
++ {
++ m_brushWidget = new BrushWidget();
++ QDockWidget *brushDockWidget = new QDockWidget(tr("Brush"), this);
++ brushDockWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
++ brushDockWidget->setWidget(m_brushWidget);
++ addDockWidget(Qt::RightDockWidgetArea, brushDockWidget);
++
++ m_penWidget = new PenWidget();
++ QDockWidget *penDockWidget = new QDockWidget(tr("Pen"), this);
++ penDockWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
++ penDockWidget->setWidget(m_penWidget);
++ addDockWidget(Qt::RightDockWidgetArea, penDockWidget);
++
++ m_inspectorWidget = new InspectorWidget();
++ QDockWidget *inspectorDockWidget = new QDockWidget(tr("Object Inspector"), this);
++ inspectorDockWidget->setFeatures(
++ QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
++ inspectorDockWidget->setWidget(m_inspectorWidget);
++ addDockWidget(Qt::RightDockWidgetArea, inspectorDockWidget);
++
++ setDockOptions(QMainWindow::AnimatedDocks);
++ }
++
++ void MainWindow::createLayout()
++ {
++ m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
++
++ int rowHeight = m_table->rowHeight(0);
++ int tableHeight = (m_engine->modelCount() * rowHeight) + m_table->horizontalHeader()->height() + 2 * m_table->frameWidth();
++
++ m_table->setMinimumHeight(tableHeight);
++ m_table->setMaximumHeight(tableHeight);
++
++ QSplitter *splitter = new QSplitter(this);
++ splitter->setOrientation(Qt::Vertical);
++ splitter->addWidget(m_table);
++ splitter->addWidget(m_view);
++ setCentralWidget(splitter);
++ m_view->hide();
++ }
++
++ void MainWindow::createTable()
++ {
++ m_table->setModel(m_engine->model());
++ m_table->setSelectionModel(m_engine->selectionModel());
++ QObject::connect(m_table->selectionModel(),SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this,SLOT(updateUI()));
++ }
++
++ void MainWindow::updateUI()
++ {
++
++ QItemSelectionModel* selection = m_table->selectionModel();
++ const QModelIndexList& list = selection->selectedIndexes();
++
++ QMultiMap<int, QModelIndex> columns;
++
++ for (const auto index : list) {
++ columns.insert(index.column(), index);
++ }
++
++ QList<int> keys = columns.uniqueKeys();
++
++ bool seriesEnabled = false;
++
++ for (auto action: m_addSeriesMenu->actions()) {
++ switch (action->data().toInt()) {
++ case QAbstractSeries::SeriesTypeLine:
++ case QAbstractSeries::SeriesTypeSpline:
++ case QAbstractSeries::SeriesTypeScatter:
++ action->setEnabled(list.count() > 0 && keys.count() >= 2);
++ seriesEnabled |= action->isEnabled();
++ break;
++ case QAbstractSeries::SeriesTypeBar:
++ case QAbstractSeries::SeriesTypePercentBar:
++ case QAbstractSeries::SeriesTypeStackedBar:
++ action->setEnabled(list.count() > 0 && keys.count() >= 2);
++ seriesEnabled |= action->isEnabled();
++ break;
++ case QAbstractSeries::SeriesTypePie:
++ action->setEnabled(list.count() > 0 && keys.count() == 2);
++ seriesEnabled |= action->isEnabled();
++ break;
++ case QAbstractSeries::SeriesTypeArea:
++ action->setEnabled(list.count() > 0 && keys.count() == 3);
++ seriesEnabled |= action->isEnabled();
++ break;
++ }
++ }
++
++ m_chartMenu->setEnabled(m_engine->chart()->series().count() > 0);
++ m_seriesMenu->setEnabled(seriesEnabled || m_engine->chart()->series().count() > 0);
++ m_removeAllAction->setEnabled(m_engine->chart()->series().count() > 0);
++
++ int theme = m_engine->chart()->theme();
++ for (auto action : m_themeMenu->actions()) {
++ action->setChecked(action->data().toInt() == theme);
++ }
++
++ int animation = m_engine->chart()->animationOptions();
++ for (auto action : m_animationMenu->actions()) {
++ action->setChecked(action->data().toInt() == animation);
++ }
++
++ int zoom = m_view->rubberBand();
++ for (auto action : m_zoomMenu->actions()) {
++ action->setChecked(action->data().toInt() == zoom);
++ }
++
++ m_legendAction->setChecked(m_engine->chart()->legend()->isVisible());
++ m_antialiasingAction->setChecked(m_view->renderHints().testFlag(QPainter::Antialiasing));
++
++ for (auto action : m_seriesMenu->actions()) {
++ //TODO: visibility handling
++ //if (m_series.value(action->text()))
++ // ;
++ //action->setChecked(false);
++ }
++ }
++
++ void MainWindow::closeEvent(QCloseEvent *event)
++ {
++ QSettings settings;
++ settings.setValue(GEOMETRY_SETTING, saveGeometry());
++ settings.setValue(FILENAME_SETTING, m_filename);
++ QMainWindow::closeEvent(event);
++ }
++
++ //handlers
++
++ void MainWindow::handleNewAction()
++ {
++ m_engine->chart()->removeAllSeries();
++ m_view->hide();
++ m_engine->clearModels();
++ createTable();
++ m_filename = "untitled";
++ setWindowTitle(m_filename);
++ updateUI();
++ }
++
++ void MainWindow::handleAddSeriesMenu()
++ {
++ m_view->show();
++ QAction* action = qobject_cast<QAction*>(sender());
++ QList<QAbstractSeries*> series = m_engine->addSeries(QAbstractSeries::SeriesType(action->data().toInt()));
++
++ for (auto s : series) {
++ QAction *newAction = new QAction(s->name(),this);
++ //newAction->setCheckable(true);
++ m_series.insert(s->name(),s);
++ m_seriesMenu->addAction(newAction);
++ }
++
++ updateUI();
++ }
++
++ void MainWindow::handleRemoveAllSeriesAction()
++ {
++ for (auto action : m_seriesMenu->actions()) {
++ if(m_series.contains(action->text())){
++ m_seriesMenu->removeAction(action);
++ m_engine->removeSeries(m_series.value(action->text()));
++ delete action;
++ }
++ }
++
++ m_series.clear();
++
++ m_view->hide();
++ updateUI();
++ }
++
++ void MainWindow::handleThemeMenu()
++ {
++ QAction* action = qobject_cast<QAction*>(sender());
++ m_engine->chart()->setTheme(QChart::ChartTheme(action->data().toInt()));
++ updateUI();
++ }
++
++ void MainWindow::handleAnimationMenu()
++ {
++ QAction* action = qobject_cast<QAction*>(sender());
++ m_engine->chart()->setAnimationOptions(QChart::AnimationOption(action->data().toInt()));
++ updateUI();
++ }
++
++ void MainWindow::handleZoomMenu()
++ {
++ QAction* action = qobject_cast<QAction*>(sender());
++ m_view->setRubberBand(QChartView::RubberBand(action->data().toInt()));
++ updateUI();
++ }
++
++ void MainWindow::handleAntialiasingAction()
++ {
++ QAction* action = qobject_cast<QAction*>(sender());
++ if (action->isChecked()) {
++ m_view->setRenderHint(QPainter::Antialiasing, true);
++ }
++ else {
++ m_view->setRenderHint(QPainter::Antialiasing, false);
++ }
++ }
++
++ void MainWindow::handleLegendAction()
++ {
++ QAction* action = qobject_cast<QAction*>(sender());
++ if (action->isChecked()) {
++ m_engine->chart()->legend()->setVisible(true);
++ }
++ else {
++ m_engine->chart()->legend()->setVisible(false);
++ }
++ }
++
++ void MainWindow::handleSaveAction()
++ {
++ if(!m_engine->save(m_filename)) {
++
++ QScopedPointer<QMessageBox> messageBox(new QMessageBox(this));
++ messageBox->setIcon(QMessageBox::Warning);
++ messageBox->setWindowModality(Qt::WindowModal);
++ messageBox->setWindowTitle(QString(tr("Error")));
++ messageBox->setText(tr("Could not write to ") + m_filename);
++ messageBox->exec();
++ }
++ }
++
++ void MainWindow::handleLoadAction()
++ {
++ if(!m_engine->load(m_filename)) {
++
++ QScopedPointer<QMessageBox> messageBox(new QMessageBox(this));
++ messageBox->setIcon(QMessageBox::Warning);
++ messageBox->setWindowModality(Qt::WindowModal);
++ messageBox->setWindowTitle(QString(tr("Error")));
++ messageBox->setText(tr("Could not open ") + m_filename);
++ messageBox->exec();
++
++ }else createTable();
++ }