Merge branch 'plotrefactoring'

This commit is contained in:
Martin Kleusberg
2017-01-16 12:46:52 +01:00
8 changed files with 999 additions and 879 deletions
+3
View File
@@ -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
+9 -502
View File
@@ -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<QKeySequence> 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<QString, QColor> 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<QString, PlotSettings> 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;i<model->columnCount();++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<double> 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<QString, PlotSettings>& 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<QString, PlotSettings>& 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
+55 -92
View File
@@ -4,12 +4,14 @@
#include "sqltextedit.h"
#include "sqlitedb.h"
#include "RemoteDatabase.h"
#include "PlotDock.h"
#include <QMainWindow>
#include <QMap>
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<int, int> columnWidths;
QMap<int, QString> filterValues;
QMap<int, QString> displayFormats;
bool showRowid;
QString encoding;
QString plotXAxis;
QMap<QString, PlotDock::PlotSettings> plotYAxes;
friend QDataStream& operator<<(QDataStream& stream, const BrowseDataTableSettings& object)
{
stream << object.sortOrderIndex;
stream << static_cast<int>(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<Qt::SortOrder>(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<int, int> columnWidths;
QMap<int, QString> filterValues;
QMap<int, QString> displayFormats;
bool showRowid;
QString encoding;
QString plotXAxis;
QMap<QString, PlotSettings> plotYAxes;
friend QDataStream& operator<<(QDataStream& stream, const MainWindow::BrowseDataTableSettings& object)
{
stream << object.sortOrderIndex;
stream << static_cast<int>(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<Qt::SortOrder>(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<QString, BrowseDataTableSettings> 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();
};
+1 -282
View File
@@ -1077,261 +1077,7 @@
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_2">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QSplitter" name="splitterForPlot">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTreeWidget" name="treePlotColumns">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="columnCount">
<number>4</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>100</number>
</attribute>
<column>
<property name="text">
<string>Columns</string>
</property>
</column>
<column>
<property name="text">
<string>X</string>
</property>
</column>
<column>
<property name="text">
<string>Y</string>
</property>
</column>
<column>
<property name="text">
<string>_</string>
</property>
</column>
</widget>
<widget class="QCustomPlot" name="plotWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>8</verstretch>
</sizepolicy>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Line type:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboLineType">
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Line</string>
</property>
</item>
<item>
<property name="text">
<string>StepLeft</string>
</property>
</item>
<item>
<property name="text">
<string>StepRight</string>
</property>
</item>
<item>
<property name="text">
<string>StepCenter</string>
</property>
</item>
<item>
<property name="text">
<string>Impulse</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Point shape:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboPointShape">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Cross</string>
</property>
</item>
<item>
<property name="text">
<string>Plus</string>
</property>
</item>
<item>
<property name="text">
<string>Circle</string>
</property>
</item>
<item>
<property name="text">
<string>Disc</string>
</property>
</item>
<item>
<property name="text">
<string>Square</string>
</property>
</item>
<item>
<property name="text">
<string>Diamond</string>
</property>
</item>
<item>
<property name="text">
<string>Star</string>
</property>
</item>
<item>
<property name="text">
<string>Triangle</string>
</property>
</item>
<item>
<property name="text">
<string>TriangleInverted</string>
</property>
</item>
<item>
<property name="text">
<string>CrossSquare</string>
</property>
</item>
<item>
<property name="text">
<string>PlusSquare</string>
</property>
</item>
<item>
<property name="text">
<string>CrossCircle</string>
</property>
</item>
<item>
<property name="text">
<string>PlusCircle</string>
</property>
</item>
<item>
<property name="text">
<string>Peace</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="butSavePlot">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Save current plot...&lt;/p&gt;&lt;p&gt;File format chosen by extension (png, jpg, pdf, bmp)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="statusTip">
<string>Save current plot...</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/save_table</normaloff>:/icons/save_table</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonLoadAllData">
<property name="toolTip">
<string>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.</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/keyword</normaloff>:/icons/keyword</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
<layout class="QVBoxLayout" name="verticalLayout_6"/>
</widget>
</widget>
<widget class="QDockWidget" name="dockSchema">
@@ -2031,23 +1777,12 @@
<signal>foreignKeyClicked(QString,QString,QByteArray)</signal>
</slots>
</customwidget>
<customwidget>
<class>QCustomPlot</class>
<extends>QWidget</extends>
<header>qcustomplot.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mainTab</tabstop>
<tabstop>dbTreeWidget</tabstop>
<tabstop>comboLogSubmittedBy</tabstop>
<tabstop>buttonLogClear</tabstop>
<tabstop>treePlotColumns</tabstop>
<tabstop>comboLineType</tabstop>
<tabstop>comboPointShape</tabstop>
<tabstop>butSavePlot</tabstop>
<tabstop>buttonLoadAllData</tabstop>
<tabstop>treeSchemaDock</tabstop>
<tabstop>comboBrowseTable</tabstop>
<tabstop>buttonRefresh</tabstop>
@@ -3013,22 +2748,6 @@
</hint>
</hints>
</connection>
<connection>
<sender>buttonLoadAllData</sender>
<signal>clicked()</signal>
<receiver>MainWindow</receiver>
<slot>browseDataFetchAllData()</slot>
<hints>
<hint type="sourcelabel">
<x>1003</x>
<y>389</y>
</hint>
<hint type="destinationlabel">
<x>1005</x>
<y>412</y>
</hint>
</hints>
</connection>
<connection>
<sender>fileExportJsonAction</sender>
<signal>triggered()</signal>
+530
View File
@@ -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<QString, QColor> 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<QString, PlotSettings> 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;i<model->columnCount();++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<double> 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<QString, PlotSettings>& 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<QString, PlotSettings>& 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);
}
}
+81
View File
@@ -0,0 +1,81 @@
#ifndef PLOTDOCK_H
#define PLOTDOCK_H
#include <QDialog>
#include <QVariant>
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
+314
View File
@@ -0,0 +1,314 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PlotDock</class>
<widget class="QDialog" name="PlotDock">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>478</width>
<height>553</height>
</rect>
</property>
<property name="windowTitle">
<string>Plot</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="splitterForPlot">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTreeWidget" name="treePlotColumns">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="columnCount">
<number>4</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>100</number>
</attribute>
<column>
<property name="text">
<string>Columns</string>
</property>
</column>
<column>
<property name="text">
<string>X</string>
</property>
</column>
<column>
<property name="text">
<string>Y</string>
</property>
</column>
<column>
<property name="text">
<string>_</string>
</property>
</column>
</widget>
<widget class="QCustomPlot" name="plotWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>8</verstretch>
</sizepolicy>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Line type:</string>
</property>
<property name="buddy">
<cstring>comboLineType</cstring>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="comboLineType">
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Line</string>
</property>
</item>
<item>
<property name="text">
<string>StepLeft</string>
</property>
</item>
<item>
<property name="text">
<string>StepRight</string>
</property>
</item>
<item>
<property name="text">
<string>StepCenter</string>
</property>
</item>
<item>
<property name="text">
<string>Impulse</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Point shape:</string>
</property>
<property name="buddy">
<cstring>comboPointShape</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboPointShape">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Cross</string>
</property>
</item>
<item>
<property name="text">
<string>Plus</string>
</property>
</item>
<item>
<property name="text">
<string>Circle</string>
</property>
</item>
<item>
<property name="text">
<string>Disc</string>
</property>
</item>
<item>
<property name="text">
<string>Square</string>
</property>
</item>
<item>
<property name="text">
<string>Diamond</string>
</property>
</item>
<item>
<property name="text">
<string>Star</string>
</property>
</item>
<item>
<property name="text">
<string>Triangle</string>
</property>
</item>
<item>
<property name="text">
<string>TriangleInverted</string>
</property>
</item>
<item>
<property name="text">
<string>CrossSquare</string>
</property>
</item>
<item>
<property name="text">
<string>PlusSquare</string>
</property>
</item>
<item>
<property name="text">
<string>CrossCircle</string>
</property>
</item>
<item>
<property name="text">
<string>PlusCircle</string>
</property>
</item>
<item>
<property name="text">
<string>Peace</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="butSavePlot">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Save current plot...&lt;/p&gt;&lt;p&gt;File format chosen by extension (png, jpg, pdf, bmp)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="statusTip">
<string>Save current plot...</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/save_table</normaloff>:/icons/save_table</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonLoadAllData">
<property name="toolTip">
<string>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.</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/keyword</normaloff>:/icons/keyword</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QCustomPlot</class>
<extends>QWidget</extends>
<header>qcustomplot.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="icons/icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonLoadAllData</sender>
<signal>clicked()</signal>
<receiver>PlotDock</receiver>
<slot>fetchAllData()</slot>
<hints>
<hint type="sourcelabel">
<x>463</x>
<y>526</y>
</hint>
<hint type="destinationlabel">
<x>477</x>
<y>495</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>fetchAllData()</slot>
</slots>
</ui>
+6 -3
View File
@@ -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 \