diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d089852..6fd3a516 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,7 @@ set(SQLB_MOC_HDR src/FilterLineEdit.h src/RemoteDatabase.h src/ForeignKeyEditorDelegate.h + src/PlotDock.h ) set(SQLB_SRC @@ -139,6 +140,7 @@ set(SQLB_SRC src/FilterLineEdit.cpp src/RemoteDatabase.cpp src/ForeignKeyEditorDelegate.cpp + src/PlotDock.cpp ) set(SQLB_FORMS @@ -155,6 +157,7 @@ set(SQLB_FORMS src/CipherDialog.ui src/ExportSqlDialog.ui src/ColumnDisplayFormatDialog.ui + src/PlotDock.ui ) set(SQLB_RESOURCES diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 5bf5bbb5..59283bfc 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -55,6 +55,7 @@ MainWindow::MainWindow(QWidget* parent) m_browseTableModel(new SqliteTableModel(db, this, Settings::getSettingsValue("db", "prefetchsize").toInt())), m_currentTabTableModel(m_browseTableModel), editDock(new EditDialog(this)), + plotDock(new PlotDock(this)), gotoValidator(new QIntValidator(0, 0, this)) { ui->setupUi(this); @@ -102,18 +103,16 @@ void MainWindow::init() // Set up the table combo box in the Browse Data tab ui->comboBrowseTable->setModel(dbStructureModel); - // Edit dock + // Create docks ui->dockEdit->setWidget(editDock); + ui->dockPlot->setWidget(plotDock); // Restore window geometry restoreGeometry(Settings::getSettingsValue("MainWindow", "geometry").toByteArray()); restoreState(Settings::getSettingsValue("MainWindow", "windowState").toByteArray()); - // Restore various dock state settings + // Restore dock state settings ui->comboLogSubmittedBy->setCurrentIndex(ui->comboLogSubmittedBy->findText(Settings::getSettingsValue("SQLLogDock", "Log").toString())); - ui->splitterForPlot->restoreState(Settings::getSettingsValue("PlotDock", "splitterSize").toByteArray()); - ui->comboLineType->setCurrentIndex(Settings::getSettingsValue("PlotDock", "lineType").toInt()); - ui->comboPointShape->setCurrentIndex(Settings::getSettingsValue("PlotDock", "pointShape").toInt()); // Add keyboard shortcuts QList shortcuts = ui->actionExecuteSql->shortcuts(); @@ -225,9 +224,6 @@ void MainWindow::init() connect(ui->dockEdit, SIGNAL(visibilityChanged(bool)), this, SLOT(toggleEditDock(bool))); connect(&m_remoteDb, SIGNAL(openFile(QString)), this, SLOT(fileOpen(QString))); - // plot widgets - ui->treePlotColumns->setSelectionMode(QAbstractItemView::NoSelection); - // Set other window settings setAcceptDrops(true); setWindowTitle(QApplication::applicationName()); @@ -476,7 +472,7 @@ void MainWindow::populateTable() m_browseTableModel->setEncoding(tableIt.value().encoding); // Plot - updatePlot(m_browseTableModel, true, false); + plotDock->updatePlot(m_browseTableModel, &browseTableSettings[ui->comboBrowseTable->currentText()], true, false); } else { // There aren't any information stored for this table yet, so use some default values @@ -495,7 +491,7 @@ void MainWindow::populateTable() m_browseTableModel->setEncoding(defaultBrowseTableEncoding); // Plot - updatePlot(m_browseTableModel); + plotDock->updatePlot(m_browseTableModel, &browseTableSettings[ui->comboBrowseTable->currentText()]); // The filters can be left empty as they are } @@ -537,7 +533,7 @@ bool MainWindow::fileClose() setRecordsetLabel(); // Reset the plot dock model - updatePlot(0); + plotDock->updatePlot(0); activateFields(false); @@ -558,9 +554,6 @@ void MainWindow::closeEvent( QCloseEvent* event ) Settings::setSettingsValue("MainWindow", "geometry", saveGeometry()); Settings::setSettingsValue("MainWindow", "windowState", saveState()); Settings::setSettingsValue("SQLLogDock", "Log", ui->comboLogSubmittedBy->currentText()); - Settings::setSettingsValue("PlotDock", "splitterSize", ui->splitterForPlot->saveState()); - Settings::setSettingsValue("PlotDock", "lineType", ui->comboLineType->currentIndex()); - Settings::setSettingsValue("PlotDock", "pointShape", ui->comboPointShape->currentIndex()); QMainWindow::closeEvent(event); } else { event->ignore(); @@ -1008,7 +1001,7 @@ void MainWindow::executeQuery() execution_start_index = execution_end_index; } while( tail && *tail != 0 && (sql3status == SQLITE_OK || sql3status == SQLITE_DONE)); sqlWidget->finishExecution(statusMessage); - updatePlot(sqlWidget->getModel()); + plotDock->updatePlot(sqlWidget->getModel()); connect(sqlWidget->getTableResult(), SIGNAL(clicked(QModelIndex)), this, SLOT(dataTableSelectionChanged(QModelIndex))); @@ -1397,6 +1390,7 @@ void MainWindow::activateFields(bool enable) ui->actionEncryption->setEnabled(enable && write); ui->buttonClearFilters->setEnabled(enable); ui->dockEdit->setEnabled(enable && write); + ui->dockPlot->setEnabled(enable); ui->actionSave_Remote->setEnabled(enable); } @@ -1779,416 +1773,6 @@ void MainWindow::httpresponse(QNetworkReply *reply) reply->deleteLater(); } -namespace { -/*! - * \brief guessdatatype try to parse the first 10 rows and decide the datatype - * \param model model to check the data - * \param column index of the column to check - * \return the guessed datatype - */ -QVariant::Type guessdatatype(SqliteTableModel* model, int column) -{ - QVariant::Type type = QVariant::Invalid; - for(int i = 0; i < std::min(10, model->rowCount()) && type != QVariant::String; ++i) - { - QVariant data = model->data(model->index(i, column), Qt::EditRole); - if(data.isNull() || data.convert(QVariant::Double)) - { - type = QVariant::Double; - } else { - QString s = model->data(model->index(i, column)).toString(); - QDate d = QDate::fromString(s, Qt::ISODate); - if(d.isValid()) - type = QVariant::DateTime; - else - type = QVariant::String; - } - } - - return type; -} -} - -void MainWindow::updatePlot(SqliteTableModel *model, bool update, bool keepOrResetSelection) -{ - // add columns to x/y selection tree widget - if(update) - { - // disconnect treeplotcolumns item changed updates - disconnect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), - this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); - - m_currentPlotModel = model; - - // save current selected columns, so we can restore them after the update - QString sItemX; // selected X column - QMap mapItemsY; // selected Y columns with color - - if(keepOrResetSelection) - { - // Store the currently selected plot columns to restore them later - for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) - { - QTreeWidgetItem* item = ui->treePlotColumns->topLevelItem(i); - if(item->checkState(PlotColumnX) == Qt::Checked) - sItemX = item->text(PlotColumnField); - - if(item->checkState(PlotColumnY) == Qt::Checked) - mapItemsY[item->text(PlotColumnField)] = item->backgroundColor(PlotColumnY); - } - } else { - // Get the plot columns to select from the stored browse table information - sItemX = browseTableSettings[ui->comboBrowseTable->currentText()].plotXAxis; - - QMap axesY = browseTableSettings[ui->comboBrowseTable->currentText()].plotYAxes; - auto it = axesY.constBegin(); - while(it != axesY.constEnd()) - { - mapItemsY.insert(it.key(), it.value().colour); - ++it; - } - } - - ui->treePlotColumns->clear(); - - if(model) - { - // Add each column with a supported data type to the column selection view - for(int i=0;icolumnCount();++i) - { - QVariant::Type columntype = guessdatatype(model, i); - if(columntype != QVariant::String && columntype != QVariant::Invalid) - { - QTreeWidgetItem* columnitem = new QTreeWidgetItem(ui->treePlotColumns); - // maybe i make this more complicated than i should - // but store the model column index in the first 16 bit and the type - // in the other 16 bits - uint itemdata = 0; - itemdata = i << 16; - itemdata |= columntype; - columnitem->setData(PlotColumnField, Qt::UserRole, itemdata); - columnitem->setText(PlotColumnField, model->headerData(i, Qt::Horizontal).toString()); - - // restore previous check state - if(mapItemsY.contains(columnitem->text(PlotColumnField))) - { - columnitem->setCheckState(PlotColumnY, Qt::Checked); - columnitem->setBackgroundColor(PlotColumnY, mapItemsY[columnitem->text(PlotColumnField)]); - } - else - columnitem->setCheckState(PlotColumnY, Qt::Unchecked); - if(sItemX == columnitem->text(PlotColumnField)) - columnitem->setCheckState(PlotColumnX, Qt::Checked); - else - columnitem->setCheckState(PlotColumnX, Qt::Unchecked); - } - } - - // Add a row number column at the beginning of the column list, but only when there were (other) columns added - if(ui->treePlotColumns->topLevelItemCount()) - { - QTreeWidgetItem* columnitem = new QTreeWidgetItem(ui->treePlotColumns); - - // Just set all bits in the user role information field here to somehow indicate what column this is - uint itemdata = -1; - columnitem->setData(PlotColumnField, Qt::UserRole, itemdata); - columnitem->setText(PlotColumnField, tr("Row #")); - - // restore previous check state - if(mapItemsY.contains(columnitem->text(PlotColumnField))) - { - columnitem->setCheckState(PlotColumnY, Qt::Checked); - columnitem->setBackgroundColor(PlotColumnY, mapItemsY[columnitem->text(PlotColumnField)]); - } else { - columnitem->setCheckState(PlotColumnY, Qt::Unchecked); - } - if(sItemX == columnitem->text(PlotColumnField)) - columnitem->setCheckState(PlotColumnX, Qt::Checked); - else - columnitem->setCheckState(PlotColumnX, Qt::Unchecked); - - ui->treePlotColumns->takeTopLevelItem(ui->treePlotColumns->indexOfTopLevelItem(columnitem)); - ui->treePlotColumns->insertTopLevelItem(0, columnitem); - } - } - - ui->plotWidget->yAxis->setLabel("Y"); - ui->plotWidget->xAxis->setLabel("X"); - connect(ui->treePlotColumns, &QTreeWidget::itemChanged, this, &MainWindow::on_treePlotColumns_itemChanged); - } - - // search for the x axis select - QTreeWidgetItem* xitem = 0; - for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) - { - xitem = ui->treePlotColumns->topLevelItem(i); - if(xitem->checkState(PlotColumnX) == Qt::Checked) - break; - - xitem = 0; - } - - QStringList yAxisLabels; - - ui->plotWidget->clearGraphs(); - if(xitem) - { - // regain the model column index and the datatype - // leading 16 bit are column index, the other 16 bit are the datatype - // right now datatype is only important for X axis (date, non date) - uint xitemdata = xitem->data(PlotColumnField, Qt::UserRole).toUInt(); - int x = xitemdata >> 16; - int xtype = xitemdata & (uint)0xFF; - - // check if we have a x axis with datetime data - if(xtype == QVariant::DateTime) - { - ui->plotWidget->xAxis->setTickLabelType(QCPAxis::ltDateTime); - ui->plotWidget->xAxis->setDateTimeFormat("yyyy-MM-dd"); - } else { - ui->plotWidget->xAxis->setTickLabelType(QCPAxis::ltNumber); - } - - // add graph for each selected y axis - for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) - { - QTreeWidgetItem* item = ui->treePlotColumns->topLevelItem(i); - if(item->checkState((PlotColumnY)) == Qt::Checked) - { - // regain the model column index and the datatype - // leading 16 bit are column index - uint itemdata = item->data(0, Qt::UserRole).toUInt(); - int column = itemdata >> 16; - QCPGraph* graph = ui->plotWidget->addGraph(); - - graph->setPen(QPen(item->backgroundColor(PlotColumnY))); - - // prepare the data vectors for qcustomplot - // possible improvement might be a QVector subclass that directly - // access the model data, to save memory, we are copying here - QVector xdata(model->rowCount()), ydata(model->rowCount()); - for(int i = 0; i < model->rowCount(); ++i) - { - // convert x type axis if it's datetime - if(xtype == QVariant::DateTime) - { - QString s = model->data(model->index(i, x)).toString(); - QDateTime d = QDateTime::fromString(s, Qt::ISODate); - xdata[i] = d.toTime_t(); - } else { - // Get the x value for this point. If the selected column is -1, i.e. the row number, just use the current row number from the loop - // instead of retrieving some value from the model. - if(x == 0xFFFF) - xdata[i] = i+1; - - else - xdata[i] = model->data(model->index(i, x)).toDouble(); - } - - // Get the y value for this point. If the selected column is -1, i.e. the row number, just use the current row number from the loop - // instead of retrieving some value from the model. - QVariant pointdata; - if(column == 0xFFFF) - pointdata = i+1; - else - pointdata = model->data(model->index(i, column), Qt::EditRole); - - if(pointdata.isNull()) - ydata[i] = qQNaN(); - else - ydata[i] = pointdata.toDouble(); - } - - // set some graph styles - graph->setData(xdata, ydata); - graph->setLineStyle((QCPGraph::LineStyle) ui->comboLineType->currentIndex()); - // WARN: ssDot is removed - int shapeIdx = ui->comboPointShape->currentIndex(); - if (shapeIdx > 0) shapeIdx += 1; - graph->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)shapeIdx, 5)); - - // gather Y label column names - yAxisLabels << model->headerData(column, Qt::Horizontal).toString(); - } - } - - ui->plotWidget->rescaleAxes(true); - - // set axis labels - ui->plotWidget->xAxis->setLabel(model->headerData(x, Qt::Horizontal).toString()); - ui->plotWidget->yAxis->setLabel(yAxisLabels.join("|")); - } - ui->plotWidget->replot(); -} - -void MainWindow::on_treePlotColumns_itemChanged(QTreeWidgetItem *changeitem, int column) -{ - // disable change updates, or we get unwanted redrawing and weird behavior - disconnect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), - this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); - - // make sure only 1 X axis is selected - QString current_table = ui->comboBrowseTable->currentText(); - if(column == PlotColumnX) - { - for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) - { - QTreeWidgetItem* item = ui->treePlotColumns->topLevelItem(i); - if(item->checkState(column) == Qt::Checked && item != changeitem) - { - item->setCheckState(column, Qt::Unchecked); - } - } - - // Save settings for this table - if(changeitem->checkState(column) == Qt::Checked) - browseTableSettings[current_table].plotXAxis = changeitem->text(PlotColumnField); - else - browseTableSettings[current_table].plotXAxis = QString(); - } else if(column == PlotColumnY) { - if(changeitem->checkState(column) == Qt::Checked) - { - // Generate a default colour if none isn't set yet - QColor colour = changeitem->backgroundColor(column); - if(!colour.isValid()) - { - static int last_colour_index = 0; - switch(last_colour_index++) - { - case 0: - colour = QColor(0, 69, 134); - break; - case 1: - colour = QColor(255, 66, 14); - break; - case 2: - colour = QColor(255, 211, 32); - break; - case 3: - colour = QColor(87, 157, 28); - break; - case 4: - colour = QColor(126, 0, 33); - break; - case 5: - colour = QColor(131, 202, 255); - break; - case 6: - colour = QColor(49, 64, 4); - break; - case 7: - colour = QColor(174, 207, 0); - break; - case 8: - colour = QColor(75, 31, 111); - break; - case 9: - colour = QColor(255, 149, 14); - break; - case 10: - colour = QColor(197, 00, 11); - break; - case 11: - colour = QColor(0, 132, 209); - - // Since this is the last colour in our table, reset the counter back - // to the first colour - last_colour_index = 0; - break; - default: - // NOTE: This shouldn't happen! - colour = QColor(0, 0, 0); - break; - } - } - - // Set colour - changeitem->setBackgroundColor(column, colour); - - // Save settings for this table - PlotSettings& plot_settings = browseTableSettings[current_table].plotYAxes[changeitem->text(PlotColumnField)]; - plot_settings.colour = colour; - plot_settings.lineStyle = ui->comboLineType->currentIndex(); - plot_settings.pointShape = (ui->comboPointShape->currentIndex() > 0 ? (ui->comboPointShape->currentIndex()+1) : ui->comboPointShape->currentIndex()); - } - } - - connect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), - this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); - - updatePlot(m_currentPlotModel, false); -} - -void MainWindow::on_treePlotColumns_itemDoubleClicked(QTreeWidgetItem *item, int column) -{ - // disable change updates, or we get unwanted redrawing and weird behavior - disconnect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), - this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); - - if(column == PlotColumnY) - { - // On double click open the colordialog - QColorDialog colordialog(this); - QColor curbkcolor = item->backgroundColor(column); - QColor precolor = !curbkcolor.isValid() ? (Qt::GlobalColor)(qrand() % 13 + 5) : curbkcolor; - QColor color = colordialog.getColor(precolor, this, tr("Choose a axis color")); - QString current_table = ui->comboBrowseTable->currentText(); - if(color.isValid()) - { - item->setCheckState(column, Qt::Checked); - item->setBackgroundColor(column, color); - - // Save settings for this table - PlotSettings& plot_settings = browseTableSettings[current_table].plotYAxes[item->text(PlotColumnField)]; - plot_settings.colour = color; - plot_settings.lineStyle = ui->comboLineType->currentIndex(); - plot_settings.pointShape = (ui->comboPointShape->currentIndex() > 0 ? (ui->comboPointShape->currentIndex()+1) : ui->comboPointShape->currentIndex()); - } else { - item->setCheckState(column, Qt::Unchecked); - - // Save settings for this table - browseTableSettings[current_table].plotYAxes.remove(item->text(PlotColumnField)); - } - } - - connect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), - this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); - - updatePlot(m_currentPlotModel, false); -} - -void MainWindow::on_butSavePlot_clicked() -{ - QString fileName = FileDialog::getSaveFileName(this, - tr("Choose a filename to save under"), - tr("PNG(*.png);;JPG(*.jpg);;PDF(*.pdf);;BMP(*.bmp);;All Files(*)") - ); - if(!fileName.isEmpty()) - { - if(fileName.endsWith(".png", Qt::CaseInsensitive)) - { - ui->plotWidget->savePng(fileName); - } - else if(fileName.endsWith(".jpg", Qt::CaseInsensitive)) - { - ui->plotWidget->saveJpg(fileName); - } - else if(fileName.endsWith(".pdf", Qt::CaseInsensitive)) - { - ui->plotWidget->savePdf(fileName); - } - else if(fileName.endsWith(".bmp", Qt::CaseInsensitive)) - { - ui->plotWidget->saveBmp(fileName); - } - else - { - fileName += ".png"; - ui->plotWidget->savePng(fileName); - } - } -} - void MainWindow::on_actionOpen_Remote_triggered() { QString url = QInputDialog::getText(this, qApp->applicationName(), tr("Please enter the URL of the database file to open.")); @@ -2591,54 +2175,6 @@ void MainWindow::copyCurrentCreateStatement() QApplication::clipboard()->setText(stmt); } -void MainWindow::on_comboLineType_currentIndexChanged(int index) -{ - Q_ASSERT(index >= QCPGraph::lsNone && - index <= QCPGraph::lsImpulse); - QCPGraph::LineStyle lineStyle = (QCPGraph::LineStyle) index; - for (int i = 0, ie = ui->plotWidget->graphCount(); i < ie; ++i) - { - QCPGraph * graph = ui->plotWidget->graph(i); - if (graph) - graph->setLineStyle(lineStyle); - } - ui->plotWidget->replot(); - - // Save settings for this table - QMap& graphs = browseTableSettings[ui->comboBrowseTable->currentText()].plotYAxes; - auto it = graphs.begin(); - while(it != graphs.end()) - { - it.value().lineStyle = lineStyle; - ++it; - } -} - -void MainWindow::on_comboPointShape_currentIndexChanged(int index) -{ - // WARN: because ssDot point shape is removed - if (index > 0) index += 1; - Q_ASSERT(index >= QCPScatterStyle::ssNone && - index < QCPScatterStyle::ssPixmap); - QCPScatterStyle::ScatterShape shape = (QCPScatterStyle::ScatterShape) index; - for (int i = 0, ie = ui->plotWidget->graphCount(); i < ie; ++i) - { - QCPGraph * graph = ui->plotWidget->graph(i); - if (graph) - graph->setScatterStyle(QCPScatterStyle(shape, 5)); - } - ui->plotWidget->replot(); - - // Save settings for this table - QMap& graphs = browseTableSettings[ui->comboBrowseTable->currentText()].plotYAxes; - auto it = graphs.begin(); - while(it != graphs.end()) - { - it.value().pointShape = shape; - ++it; - } -} - void MainWindow::jumpToRow(const QString& table, QString column, const QByteArray& value) { // First check if table exists @@ -2800,35 +2336,6 @@ void MainWindow::browseDataSetDefaultTableEncoding() browseDataSetTableEncoding(true); } -void MainWindow::browseDataFetchAllData() -{ - if(m_currentPlotModel) - { - // Show progress dialog because fetching all data might take some time - QProgressDialog progress(tr("Fetching all data..."), - tr("Cancel"), m_browseTableModel->rowCount(), m_browseTableModel->totalRowCount()); - progress.setWindowModality(Qt::ApplicationModal); - progress.show(); - qApp->processEvents(); - - // Make sure all data is loaded - while(m_currentPlotModel->canFetchMore()) - { - // Fetch the next bunch of data - m_currentPlotModel->fetchMore(); - - // Update the progress dialog and stop loading data when the cancel button was pressed - progress.setValue(m_currentPlotModel->rowCount()); - qApp->processEvents(); - if(progress.wasCanceled()) - break; - } - - // Update plot - updatePlot(m_currentPlotModel); - } -} - void MainWindow::fileOpenReadOnly() { // Redirect to 'standard' fileOpen(), with the read only flag set diff --git a/src/MainWindow.h b/src/MainWindow.h index 9c669b2c..43467c78 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -4,12 +4,14 @@ #include "sqltextedit.h" #include "sqlitedb.h" #include "RemoteDatabase.h" +#include "PlotDock.h" #include #include class QDragEnterEvent; class EditDialog; +class PlotDock; class QIntValidator; class QLabel; class QModelIndex; @@ -18,12 +20,62 @@ class SqliteTableModel; class DbStructureModel; class QNetworkReply; class QNetworkAccessManager; -class QTreeWidgetItem; namespace Ui { class MainWindow; } +struct BrowseDataTableSettings +{ + int sortOrderIndex; + Qt::SortOrder sortOrderMode; + QMap columnWidths; + QMap filterValues; + QMap displayFormats; + bool showRowid; + QString encoding; + QString plotXAxis; + QMap plotYAxes; + + friend QDataStream& operator<<(QDataStream& stream, const BrowseDataTableSettings& object) + { + stream << object.sortOrderIndex; + stream << static_cast(object.sortOrderMode); + stream << object.columnWidths; + stream << object.filterValues; + stream << object.displayFormats; + stream << object.showRowid; + stream << object.encoding; + stream << object.plotXAxis; + stream << object.plotYAxes; + + return stream; + } + friend QDataStream& operator>>(QDataStream& stream, BrowseDataTableSettings& object) + { + stream >> object.sortOrderIndex; + int sortordermode; + stream >> sortordermode; + object.sortOrderMode = static_cast(sortordermode); + stream >> object.columnWidths; + stream >> object.filterValues; + stream >> object.displayFormats; + stream >> object.showRowid; + stream >> object.encoding; + + // Versions pre 3.10.0 didn't store the following information in their project files. + // To be absolutely sure that nothing strange happens when we read past the stream for + // those cases, check for the end of the stream here. + if(stream.atEnd()) + return stream; + + stream >> object.plotXAxis; + stream >> object.plotYAxes; + + return stream; + } +}; + class MainWindow : public QMainWindow { Q_OBJECT @@ -35,81 +87,6 @@ public: DBBrowserDB& getDb() { return db; } const RemoteDatabase& getRemote() const { return m_remoteDb; } - struct PlotSettings - { - int lineStyle; - int pointShape; - QColor colour; - - friend QDataStream& operator<<(QDataStream& stream, const MainWindow::PlotSettings& object) - { - stream << object.lineStyle; - stream << object.pointShape; - stream << object.colour; - - return stream; - } - friend QDataStream& operator>>(QDataStream& stream, MainWindow::PlotSettings& object) - { - stream >> object.lineStyle; - stream >> object.pointShape; - stream >> object.colour; - - return stream; - } - }; - - struct BrowseDataTableSettings - { - int sortOrderIndex; - Qt::SortOrder sortOrderMode; - QMap columnWidths; - QMap filterValues; - QMap displayFormats; - bool showRowid; - QString encoding; - QString plotXAxis; - QMap plotYAxes; - - friend QDataStream& operator<<(QDataStream& stream, const MainWindow::BrowseDataTableSettings& object) - { - stream << object.sortOrderIndex; - stream << static_cast(object.sortOrderMode); - stream << object.columnWidths; - stream << object.filterValues; - stream << object.displayFormats; - stream << object.showRowid; - stream << object.encoding; - stream << object.plotXAxis; - stream << object.plotYAxes; - - return stream; - } - friend QDataStream& operator>>(QDataStream& stream, MainWindow::BrowseDataTableSettings& object) - { - stream >> object.sortOrderIndex; - int sortordermode; - stream >> sortordermode; - object.sortOrderMode = static_cast(sortordermode); - stream >> object.columnWidths; - stream >> object.filterValues; - stream >> object.displayFormats; - stream >> object.showRowid; - stream >> object.encoding; - - // Versions pre 3.10.0 didn't store the following information in their project files. - // To be absolutely sure that nothing strange happens when we read past the stream for - // those cases, check for the end of the stream here. - if(stream.atEnd()) - return stream; - - stream >> object.plotXAxis; - stream >> object.plotYAxes; - - return stream; - } - }; - enum Tabs { StructureTab, @@ -140,19 +117,11 @@ private: int wal_autocheckpoint; } pragmaValues; - enum PlotColumns - { - PlotColumnField = 0, - PlotColumnX = 1, - PlotColumnY = 2, - PlotColumnDummy = 3 - }; - Ui::MainWindow* ui; SqliteTableModel* m_browseTableModel; SqliteTableModel* m_currentTabTableModel; - SqliteTableModel* m_currentPlotModel; + QMenu *popupTableMenu; QMenu *recentFilesMenu; QMenu *popupSaveSqlFileMenu; @@ -171,6 +140,7 @@ private: QMap browseTableSettings; EditDialog* editDock; + PlotDock* plotDock; QIntValidator* gotoValidator; DBBrowserDB db; @@ -256,10 +226,6 @@ private slots: void loadExtension(); void reloadSettings(); void httpresponse(QNetworkReply* reply); - void updatePlot(SqliteTableModel* model, bool update = true, bool keepOrResetSelection = true); - void on_treePlotColumns_itemChanged(QTreeWidgetItem *item, int column); - void on_treePlotColumns_itemDoubleClicked(QTreeWidgetItem *item, int column); - void on_butSavePlot_clicked(); void on_actionWiki_triggered(); void on_actionBug_report_triggered(); void on_actionSqlCipherFaq_triggered(); @@ -272,15 +238,12 @@ private slots: void editEncryption(); void on_buttonClearFilters_clicked(); void copyCurrentCreateStatement(); - void on_comboLineType_currentIndexChanged(int index); - void on_comboPointShape_currentIndexChanged(int index); void showDataColumnPopupMenu(const QPoint& pos); void showRecordPopupMenu(const QPoint& pos); void editDataColumnDisplayFormat(); void showRowidColumn(bool show); void browseDataSetTableEncoding(bool forAllTables = false); void browseDataSetDefaultTableEncoding(); - void browseDataFetchAllData(); void fileOpenReadOnly(); }; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 16ba4dc7..de945813 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -1077,261 +1077,7 @@ 2 - - - - - Qt::Vertical - - - - - 0 - 2 - - - - 4 - - - 100 - - - - Columns - - - - - X - - - - - Y - - - - - _ - - - - - - - 0 - 8 - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Line type: - - - - - - - 1 - - - - None - - - - - Line - - - - - StepLeft - - - - - StepRight - - - - - StepCenter - - - - - Impulse - - - - - - - - Point shape: - - - - - - - 0 - - - - None - - - - - Cross - - - - - Plus - - - - - Circle - - - - - Disc - - - - - Square - - - - - Diamond - - - - - Star - - - - - Triangle - - - - - TriangleInverted - - - - - CrossSquare - - - - - PlusSquare - - - - - CrossCircle - - - - - PlusCircle - - - - - Peace - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - <html><head/><body><p>Save current plot...</p><p>File format chosen by extension (png, jpg, pdf, bmp)</p></body></html> - - - Save current plot... - - - - - - - :/icons/save_table:/icons/save_table - - - false - - - false - - - false - - - false - - - - - - - Load all data. This has only an effect if not all data has been fetched from the table yet due to the partial fetch mechanism. - - - - :/icons/keyword:/icons/keyword - - - - - - - + @@ -2031,23 +1777,12 @@ foreignKeyClicked(QString,QString,QByteArray) - - QCustomPlot - QWidget -
qcustomplot.h
- 1 -
mainTab dbTreeWidget comboLogSubmittedBy buttonLogClear - treePlotColumns - comboLineType - comboPointShape - butSavePlot - buttonLoadAllData treeSchemaDock comboBrowseTable buttonRefresh @@ -3013,22 +2748,6 @@ - - buttonLoadAllData - clicked() - MainWindow - browseDataFetchAllData() - - - 1003 - 389 - - - 1005 - 412 - - - fileExportJsonAction triggered() diff --git a/src/PlotDock.cpp b/src/PlotDock.cpp new file mode 100644 index 00000000..7010a3a9 --- /dev/null +++ b/src/PlotDock.cpp @@ -0,0 +1,530 @@ +#include "PlotDock.h" +#include "ui_PlotDock.h" +#include "Settings.h" +#include "sqlitetablemodel.h" +#include "FileDialog.h" +#include "MainWindow.h" // Just for BrowseDataTableSettings, not for the actual main window class + +PlotDock::PlotDock(QWidget* parent) + : QDialog(parent), + ui(new Ui::PlotDock), + m_currentPlotModel(nullptr), + m_currentTableSettings(nullptr) +{ + ui->setupUi(this); + + // Init widgets + ui->treePlotColumns->setSelectionMode(QAbstractItemView::NoSelection); + + // Restore state + ui->splitterForPlot->restoreState(Settings::getSettingsValue("PlotDock", "splitterSize").toByteArray()); + ui->comboLineType->setCurrentIndex(Settings::getSettingsValue("PlotDock", "lineType").toInt()); + ui->comboPointShape->setCurrentIndex(Settings::getSettingsValue("PlotDock", "pointShape").toInt()); +} + +PlotDock::~PlotDock() +{ + // Save state + Settings::setSettingsValue("PlotDock", "splitterSize", ui->splitterForPlot->saveState()); + Settings::setSettingsValue("PlotDock", "lineType", ui->comboLineType->currentIndex()); + Settings::setSettingsValue("PlotDock", "pointShape", ui->comboPointShape->currentIndex()); + + // Finally, delete all widgets + delete ui; +} + +void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* settings, bool update, bool keepOrResetSelection) +{ + // add columns to x/y selection tree widget + if(update) + { + // Store pointer to the current browse table settings in the main window + m_currentTableSettings = settings; + + // disconnect treeplotcolumns item changed updates + disconnect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), + this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); + + m_currentPlotModel = model; + + // save current selected columns, so we can restore them after the update + QString sItemX; // selected X column + QMap mapItemsY; // selected Y columns with color + + if(keepOrResetSelection) + { + // Store the currently selected plot columns to restore them later + for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) + { + QTreeWidgetItem* item = ui->treePlotColumns->topLevelItem(i); + if(item->checkState(PlotColumnX) == Qt::Checked) + sItemX = item->text(PlotColumnField); + + if(item->checkState(PlotColumnY) == Qt::Checked) + mapItemsY[item->text(PlotColumnField)] = item->backgroundColor(PlotColumnY); + } + } else { + // Get the plot columns to select from the stored browse table information + sItemX = m_currentTableSettings->plotXAxis; + + QMap axesY = m_currentTableSettings->plotYAxes; + auto it = axesY.constBegin(); + while(it != axesY.constEnd()) + { + mapItemsY.insert(it.key(), it.value().colour); + ++it; + } + } + + ui->treePlotColumns->clear(); + + if(model) + { + // Add each column with a supported data type to the column selection view + for(int i=0;icolumnCount();++i) + { + QVariant::Type columntype = guessDataType(model, i); + if(columntype != QVariant::String && columntype != QVariant::Invalid) + { + QTreeWidgetItem* columnitem = new QTreeWidgetItem(ui->treePlotColumns); + // maybe i make this more complicated than i should + // but store the model column index in the first 16 bit and the type + // in the other 16 bits + uint itemdata = 0; + itemdata = i << 16; + itemdata |= columntype; + columnitem->setData(PlotColumnField, Qt::UserRole, itemdata); + columnitem->setText(PlotColumnField, model->headerData(i, Qt::Horizontal).toString()); + + // restore previous check state + if(mapItemsY.contains(columnitem->text(PlotColumnField))) + { + columnitem->setCheckState(PlotColumnY, Qt::Checked); + columnitem->setBackgroundColor(PlotColumnY, mapItemsY[columnitem->text(PlotColumnField)]); + } + else + columnitem->setCheckState(PlotColumnY, Qt::Unchecked); + if(sItemX == columnitem->text(PlotColumnField)) + columnitem->setCheckState(PlotColumnX, Qt::Checked); + else + columnitem->setCheckState(PlotColumnX, Qt::Unchecked); + } + } + + // Add a row number column at the beginning of the column list, but only when there were (other) columns added + if(ui->treePlotColumns->topLevelItemCount()) + { + QTreeWidgetItem* columnitem = new QTreeWidgetItem(ui->treePlotColumns); + + // Just set all bits in the user role information field here to somehow indicate what column this is + uint itemdata = -1; + columnitem->setData(PlotColumnField, Qt::UserRole, itemdata); + columnitem->setText(PlotColumnField, tr("Row #")); + + // restore previous check state + if(mapItemsY.contains(columnitem->text(PlotColumnField))) + { + columnitem->setCheckState(PlotColumnY, Qt::Checked); + columnitem->setBackgroundColor(PlotColumnY, mapItemsY[columnitem->text(PlotColumnField)]); + } else { + columnitem->setCheckState(PlotColumnY, Qt::Unchecked); + } + if(sItemX == columnitem->text(PlotColumnField)) + columnitem->setCheckState(PlotColumnX, Qt::Checked); + else + columnitem->setCheckState(PlotColumnX, Qt::Unchecked); + + ui->treePlotColumns->takeTopLevelItem(ui->treePlotColumns->indexOfTopLevelItem(columnitem)); + ui->treePlotColumns->insertTopLevelItem(0, columnitem); + } + } + + ui->plotWidget->yAxis->setLabel("Y"); + ui->plotWidget->xAxis->setLabel("X"); + connect(ui->treePlotColumns, &QTreeWidget::itemChanged, this, &PlotDock::on_treePlotColumns_itemChanged); + } + + // search for the x axis select + QTreeWidgetItem* xitem = 0; + for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) + { + xitem = ui->treePlotColumns->topLevelItem(i); + if(xitem->checkState(PlotColumnX) == Qt::Checked) + break; + + xitem = 0; + } + + QStringList yAxisLabels; + + ui->plotWidget->clearGraphs(); + if(xitem) + { + // regain the model column index and the datatype + // leading 16 bit are column index, the other 16 bit are the datatype + // right now datatype is only important for X axis (date, non date) + uint xitemdata = xitem->data(PlotColumnField, Qt::UserRole).toUInt(); + int x = xitemdata >> 16; + int xtype = xitemdata & (uint)0xFF; + + // check if we have a x axis with datetime data + if(xtype == QVariant::DateTime) + { + ui->plotWidget->xAxis->setTickLabelType(QCPAxis::ltDateTime); + ui->plotWidget->xAxis->setDateTimeFormat("yyyy-MM-dd"); + } else { + ui->plotWidget->xAxis->setTickLabelType(QCPAxis::ltNumber); + } + + // add graph for each selected y axis + for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) + { + QTreeWidgetItem* item = ui->treePlotColumns->topLevelItem(i); + if(item->checkState((PlotColumnY)) == Qt::Checked) + { + // regain the model column index and the datatype + // leading 16 bit are column index + uint itemdata = item->data(0, Qt::UserRole).toUInt(); + int column = itemdata >> 16; + QCPGraph* graph = ui->plotWidget->addGraph(); + + graph->setPen(QPen(item->backgroundColor(PlotColumnY))); + + // prepare the data vectors for qcustomplot + // possible improvement might be a QVector subclass that directly + // access the model data, to save memory, we are copying here + QVector xdata(model->rowCount()), ydata(model->rowCount()); + for(int i = 0; i < model->rowCount(); ++i) + { + // convert x type axis if it's datetime + if(xtype == QVariant::DateTime) + { + QString s = model->data(model->index(i, x)).toString(); + QDateTime d = QDateTime::fromString(s, Qt::ISODate); + xdata[i] = d.toTime_t(); + } else { + // Get the x value for this point. If the selected column is -1, i.e. the row number, just use the current row number from the loop + // instead of retrieving some value from the model. + if(x == 0xFFFF) + xdata[i] = i+1; + + else + xdata[i] = model->data(model->index(i, x)).toDouble(); + } + + // Get the y value for this point. If the selected column is -1, i.e. the row number, just use the current row number from the loop + // instead of retrieving some value from the model. + QVariant pointdata; + if(column == 0xFFFF) + pointdata = i+1; + else + pointdata = model->data(model->index(i, column), Qt::EditRole); + + if(pointdata.isNull()) + ydata[i] = qQNaN(); + else + ydata[i] = pointdata.toDouble(); + } + + // set some graph styles + graph->setData(xdata, ydata); + graph->setLineStyle((QCPGraph::LineStyle) ui->comboLineType->currentIndex()); + // WARN: ssDot is removed + int shapeIdx = ui->comboPointShape->currentIndex(); + if (shapeIdx > 0) shapeIdx += 1; + graph->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)shapeIdx, 5)); + + // gather Y label column names + yAxisLabels << model->headerData(column, Qt::Horizontal).toString(); + } + } + + ui->plotWidget->rescaleAxes(true); + + // set axis labels + ui->plotWidget->xAxis->setLabel(model->headerData(x, Qt::Horizontal).toString()); + ui->plotWidget->yAxis->setLabel(yAxisLabels.join("|")); + } + ui->plotWidget->replot(); +} + +void PlotDock::on_treePlotColumns_itemChanged(QTreeWidgetItem* changeitem, int column) +{ + // disable change updates, or we get unwanted redrawing and weird behavior + disconnect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), + this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); + + // make sure only 1 X axis is selected + if(column == PlotColumnX) + { + for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) + { + QTreeWidgetItem* item = ui->treePlotColumns->topLevelItem(i); + if(item->checkState(column) == Qt::Checked && item != changeitem) + { + item->setCheckState(column, Qt::Unchecked); + } + } + + // Save settings for this table + if(m_currentTableSettings) + { + if(changeitem->checkState(column) == Qt::Checked) + m_currentTableSettings->plotXAxis = changeitem->text(PlotColumnField); + else + m_currentTableSettings->plotXAxis = QString(); + } + } else if(column == PlotColumnY) { + if(changeitem->checkState(column) == Qt::Checked) + { + // Generate a default colour if none isn't set yet + QColor colour = changeitem->backgroundColor(column); + if(!colour.isValid()) + { + static int last_colour_index = 0; + switch(last_colour_index++) + { + case 0: + colour = QColor(0, 69, 134); + break; + case 1: + colour = QColor(255, 66, 14); + break; + case 2: + colour = QColor(255, 211, 32); + break; + case 3: + colour = QColor(87, 157, 28); + break; + case 4: + colour = QColor(126, 0, 33); + break; + case 5: + colour = QColor(131, 202, 255); + break; + case 6: + colour = QColor(49, 64, 4); + break; + case 7: + colour = QColor(174, 207, 0); + break; + case 8: + colour = QColor(75, 31, 111); + break; + case 9: + colour = QColor(255, 149, 14); + break; + case 10: + colour = QColor(197, 00, 11); + break; + case 11: + colour = QColor(0, 132, 209); + + // Since this is the last colour in our table, reset the counter back + // to the first colour + last_colour_index = 0; + break; + default: + // NOTE: This shouldn't happen! + colour = QColor(0, 0, 0); + break; + } + } + + // Set colour + changeitem->setBackgroundColor(column, colour); + + // Save settings for this table + if(m_currentTableSettings) + { + PlotSettings& plot_settings = m_currentTableSettings->plotYAxes[changeitem->text(PlotColumnField)]; + plot_settings.colour = colour; + plot_settings.lineStyle = ui->comboLineType->currentIndex(); + plot_settings.pointShape = (ui->comboPointShape->currentIndex() > 0 ? (ui->comboPointShape->currentIndex()+1) : ui->comboPointShape->currentIndex()); + } + } + } + + connect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), + this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); + + updatePlot(m_currentPlotModel, m_currentTableSettings, false); +} + +void PlotDock::on_treePlotColumns_itemDoubleClicked(QTreeWidgetItem* item, int column) +{ + // disable change updates, or we get unwanted redrawing and weird behavior + disconnect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), + this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); + + if(column == PlotColumnY) + { + // On double click open the colordialog + QColorDialog colordialog(this); + QColor curbkcolor = item->backgroundColor(column); + QColor precolor = !curbkcolor.isValid() ? (Qt::GlobalColor)(qrand() % 13 + 5) : curbkcolor; + QColor color = colordialog.getColor(precolor, this, tr("Choose a axis color")); + if(color.isValid()) + { + item->setCheckState(column, Qt::Checked); + item->setBackgroundColor(column, color); + + // Save settings for this table + if(m_currentTableSettings) + { + PlotSettings& plot_settings = m_currentTableSettings->plotYAxes[item->text(PlotColumnField)]; + plot_settings.colour = color; + plot_settings.lineStyle = ui->comboLineType->currentIndex(); + plot_settings.pointShape = (ui->comboPointShape->currentIndex() > 0 ? (ui->comboPointShape->currentIndex()+1) : ui->comboPointShape->currentIndex()); + } + } else { + item->setCheckState(column, Qt::Unchecked); + + // Save settings for this table + if(m_currentTableSettings) + m_currentTableSettings->plotYAxes.remove(item->text(PlotColumnField)); + } + } + + connect(ui->treePlotColumns, SIGNAL(itemChanged(QTreeWidgetItem*,int)), + this,SLOT(on_treePlotColumns_itemChanged(QTreeWidgetItem*,int))); + + updatePlot(m_currentPlotModel, m_currentTableSettings, false); +} + +void PlotDock::on_butSavePlot_clicked() +{ + QString fileName = FileDialog::getSaveFileName(this, + tr("Choose a filename to save under"), + tr("PNG(*.png);;JPG(*.jpg);;PDF(*.pdf);;BMP(*.bmp);;All Files(*)") + ); + if(!fileName.isEmpty()) + { + if(fileName.endsWith(".png", Qt::CaseInsensitive)) + { + ui->plotWidget->savePng(fileName); + } + else if(fileName.endsWith(".jpg", Qt::CaseInsensitive)) + { + ui->plotWidget->saveJpg(fileName); + } + else if(fileName.endsWith(".pdf", Qt::CaseInsensitive)) + { + ui->plotWidget->savePdf(fileName); + } + else if(fileName.endsWith(".bmp", Qt::CaseInsensitive)) + { + ui->plotWidget->saveBmp(fileName); + } + else + { + fileName += ".png"; + ui->plotWidget->savePng(fileName); + } + } +} + +void PlotDock::on_comboLineType_currentIndexChanged(int index) +{ + Q_ASSERT(index >= QCPGraph::lsNone && + index <= QCPGraph::lsImpulse); + QCPGraph::LineStyle lineStyle = (QCPGraph::LineStyle) index; + for (int i = 0, ie = ui->plotWidget->graphCount(); i < ie; ++i) + { + QCPGraph * graph = ui->plotWidget->graph(i); + if (graph) + graph->setLineStyle(lineStyle); + } + ui->plotWidget->replot(); + + // Save settings for this table + if(m_currentTableSettings) + { + QMap& graphs = m_currentTableSettings->plotYAxes; + auto it = graphs.begin(); + while(it != graphs.end()) + { + it.value().lineStyle = lineStyle; + ++it; + } + } +} + +void PlotDock::on_comboPointShape_currentIndexChanged(int index) +{ + // WARN: because ssDot point shape is removed + if (index > 0) index += 1; + Q_ASSERT(index >= QCPScatterStyle::ssNone && + index < QCPScatterStyle::ssPixmap); + QCPScatterStyle::ScatterShape shape = (QCPScatterStyle::ScatterShape) index; + for (int i = 0, ie = ui->plotWidget->graphCount(); i < ie; ++i) + { + QCPGraph * graph = ui->plotWidget->graph(i); + if (graph) + graph->setScatterStyle(QCPScatterStyle(shape, 5)); + } + ui->plotWidget->replot(); + + // Save settings for this table + if(m_currentTableSettings) + { + QMap& graphs = m_currentTableSettings->plotYAxes; + auto it = graphs.begin(); + while(it != graphs.end()) + { + it.value().pointShape = shape; + ++it; + } + } +} + +QVariant::Type PlotDock::guessDataType(SqliteTableModel* model, int column) +{ + QVariant::Type type = QVariant::Invalid; + for(int i = 0; i < std::min(10, model->rowCount()) && type != QVariant::String; ++i) + { + QVariant data = model->data(model->index(i, column), Qt::EditRole); + if(data.isNull() || data.convert(QVariant::Double)) + { + type = QVariant::Double; + } else { + QString s = model->data(model->index(i, column)).toString(); + QDate d = QDate::fromString(s, Qt::ISODate); + if(d.isValid()) + type = QVariant::DateTime; + else + type = QVariant::String; + } + } + + return type; +} + +void PlotDock::fetchAllData() +{ + if(m_currentPlotModel) + { + // Show progress dialog because fetching all data might take some time + QProgressDialog progress(tr("Fetching all data..."), + tr("Cancel"), m_currentPlotModel->rowCount(), m_currentPlotModel->totalRowCount()); + progress.setWindowModality(Qt::ApplicationModal); + progress.show(); + qApp->processEvents(); + + // Make sure all data is loaded + while(m_currentPlotModel->canFetchMore()) + { + // Fetch the next bunch of data + m_currentPlotModel->fetchMore(); + + // Update the progress dialog and stop loading data when the cancel button was pressed + progress.setValue(m_currentPlotModel->rowCount()); + qApp->processEvents(); + if(progress.wasCanceled()) + break; + } + + // Update plot + updatePlot(m_currentPlotModel, m_currentTableSettings); + } +} diff --git a/src/PlotDock.h b/src/PlotDock.h new file mode 100644 index 00000000..b1eab732 --- /dev/null +++ b/src/PlotDock.h @@ -0,0 +1,81 @@ +#ifndef PLOTDOCK_H +#define PLOTDOCK_H + +#include +#include + +class SqliteTableModel; +class QTreeWidgetItem; +class BrowseDataTableSettings; + +namespace Ui { +class PlotDock; +} + +class PlotDock : public QDialog +{ + Q_OBJECT + +public: + explicit PlotDock(QWidget* parent = 0); + ~PlotDock(); + + struct PlotSettings + { + int lineStyle; + int pointShape; + QColor colour; + + friend QDataStream& operator<<(QDataStream& stream, const PlotDock::PlotSettings& object) + { + stream << object.lineStyle; + stream << object.pointShape; + stream << object.colour; + + return stream; + } + friend QDataStream& operator>>(QDataStream& stream, PlotDock::PlotSettings& object) + { + stream >> object.lineStyle; + stream >> object.pointShape; + stream >> object.colour; + + return stream; + } + }; + +public slots: + void updatePlot(SqliteTableModel* model, BrowseDataTableSettings* settings = nullptr, bool update = true, bool keepOrResetSelection = true); + void fetchAllData(); + +private: + enum PlotColumns + { + PlotColumnField = 0, + PlotColumnX = 1, + PlotColumnY = 2, + PlotColumnDummy = 3 + }; + + Ui::PlotDock* ui; + + SqliteTableModel* m_currentPlotModel; + BrowseDataTableSettings* m_currentTableSettings; + + /*! + * \brief guessdatatype try to parse the first 10 rows and decide the datatype + * \param model model to check the data + * \param column index of the column to check + * \return the guessed datatype + */ + QVariant::Type guessDataType(SqliteTableModel* model, int column); + +private slots: + void on_treePlotColumns_itemChanged(QTreeWidgetItem* item, int column); + void on_treePlotColumns_itemDoubleClicked(QTreeWidgetItem* item, int column); + void on_butSavePlot_clicked(); + void on_comboLineType_currentIndexChanged(int index); + void on_comboPointShape_currentIndexChanged(int index); +}; + +#endif diff --git a/src/PlotDock.ui b/src/PlotDock.ui new file mode 100644 index 00000000..f4b76fe9 --- /dev/null +++ b/src/PlotDock.ui @@ -0,0 +1,314 @@ + + + PlotDock + + + + 0 + 0 + 478 + 553 + + + + Plot + + + + + + Qt::Vertical + + + + + 0 + 2 + + + + 4 + + + 100 + + + + Columns + + + + + X + + + + + Y + + + + + _ + + + + + + + 0 + 8 + + + + + + + + + + + Line type: + + + comboLineType + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + + None + + + + + Line + + + + + StepLeft + + + + + StepRight + + + + + StepCenter + + + + + Impulse + + + + + + + + Point shape: + + + comboPointShape + + + + + + + 0 + + + + None + + + + + Cross + + + + + Plus + + + + + Circle + + + + + Disc + + + + + Square + + + + + Diamond + + + + + Star + + + + + Triangle + + + + + TriangleInverted + + + + + CrossSquare + + + + + PlusSquare + + + + + CrossCircle + + + + + PlusCircle + + + + + Peace + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Save current plot...</p><p>File format chosen by extension (png, jpg, pdf, bmp)</p></body></html> + + + Save current plot... + + + + + + + :/icons/save_table:/icons/save_table + + + false + + + false + + + false + + + false + + + + + + + Load all data. This has only an effect if not all data has been fetched from the table yet due to the partial fetch mechanism. + + + + :/icons/keyword:/icons/keyword + + + + + + + + + + + + + QCustomPlot + QWidget +
qcustomplot.h
+ 1 +
+
+ + + + + + buttonLoadAllData + clicked() + PlotDock + fetchAllData() + + + 463 + 526 + + + 477 + 495 + + + + + + fetchAllData() + +
diff --git a/src/src.pro b/src/src.pro index 61c6dc5e..3c2897f4 100644 --- a/src/src.pro +++ b/src/src.pro @@ -53,7 +53,8 @@ HEADERS += \ ColumnDisplayFormatDialog.h \ FilterLineEdit.h \ RemoteDatabase.h \ - ForeignKeyEditorDelegate.h + ForeignKeyEditorDelegate.h \ + PlotDock.h SOURCES += \ sqlitedb.cpp \ @@ -85,7 +86,8 @@ SOURCES += \ ColumnDisplayFormatDialog.cpp \ FilterLineEdit.cpp \ RemoteDatabase.cpp \ - ForeignKeyEditorDelegate.cpp + ForeignKeyEditorDelegate.cpp \ + PlotDock.cpp RESOURCES += icons/icons.qrc \ translations/flags/flags.qrc \ @@ -105,7 +107,8 @@ FORMS += \ VacuumDialog.ui \ CipherDialog.ui \ ExportSqlDialog.ui \ - ColumnDisplayFormatDialog.ui + ColumnDisplayFormatDialog.ui \ + PlotDock.ui TRANSLATIONS += \ translations/sqlb_ar_SA.ts \