├── LICENSE ├── .gitmodules ├── docs ├── edit_cal.PMs.201604.pptx └── edit_cal.RAF.201311.pptx ├── .gitignore ├── polyfitgsl.h ├── ViewTextDialog.cc ├── ViewTextDialog.h ├── Jenkinsfile ├── PolyEval.h ├── pushBathCalsToRemotes.sh ├── README.md ├── SortFilterProxyModel.h ├── ViewTextDialog.ui ├── CalibrationPlot.ui ├── SConstruct ├── calTableHeaders.h ├── CalibrationPlot.h ├── polyfitgsl.cc ├── CalibrationForm.h ├── CalibrationPlot.cc ├── SortFilterProxyModel.cc ├── main.cc ├── MainWindow.h ├── CalibrationForm.cc ├── CalibrationForm.ui └── MainWindow.cc /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "site_scons"] 2 | path = site_scons/eol_scons 3 | url = ../eol_scons 4 | -------------------------------------------------------------------------------- /docs/edit_cal.PMs.201604.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NCAR/aircraft_caledit/master/docs/edit_cal.PMs.201604.pptx -------------------------------------------------------------------------------- /docs/edit_cal.RAF.201311.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NCAR/aircraft_caledit/master/docs/edit_cal.RAF.201311.pptx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.a 4 | ui_*.h 5 | moc_*.* 6 | .sconf_temp 7 | .sconsign.dblite 8 | config.log 9 | caledit 10 | -------------------------------------------------------------------------------- /polyfitgsl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @link http://rosettacode.org/wiki/Polynomial_regression 3 | */ 4 | #ifndef _POLYFITGSL_H 5 | #define _POLYFITGSL_H 6 | bool polynomialfit(int obs, int order, 7 | double *dx, double *dy, double *store, double *Rsq); /* n, p */ 8 | #endif 9 | -------------------------------------------------------------------------------- /ViewTextDialog.cc: -------------------------------------------------------------------------------- 1 | #include "ViewTextDialog.h" 2 | 3 | /* -------------------------------------------------------------------- */ 4 | 5 | ViewTextDialog::ViewTextDialog(QWidget *parent) : QDialog(parent) 6 | { 7 | setupUi(this); 8 | } 9 | 10 | /* -------------------------------------------------------------------- */ 11 | 12 | void ViewTextDialog::setContents(const QString *text) 13 | { 14 | textView->setFontFamily("Courier"); 15 | textView->clear(); 16 | textView->insertPlainText(*text); 17 | } 18 | -------------------------------------------------------------------------------- /ViewTextDialog.h: -------------------------------------------------------------------------------- 1 | #ifndef _ViewTextDialog_h_ 2 | #define _ViewTextDialog_h_ 3 | 4 | #include 5 | #include 6 | 7 | #include "ui_ViewTextDialog.h" 8 | 9 | /** 10 | * @class ViewTextDialog 11 | * Provides a monospaced view of text. 12 | */ 13 | class ViewTextDialog : public QDialog, public Ui::Ui_ViewTextDialog 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | ViewTextDialog(QWidget * parent = 0); 19 | ~ViewTextDialog() {} 20 | 21 | void setContents(const QString *text); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | node { 4 | label 'CentOS9_x86_64' 5 | } 6 | } 7 | triggers { 8 | pollSCM('H/30 8-18 * * * ') 9 | } 10 | stages { 11 | stage('Build') { 12 | steps { 13 | sh 'scons' 14 | } 15 | } 16 | } 17 | post { 18 | failure { 19 | mail(to: 'cjw@ucar.edu cdewerd@ucar.edu janine@ucar.edu', subject: 'aircraft_caledit Jenkins build failed', body: 'aircraft_caledit Jenkins build failed') 20 | } 21 | } 22 | options { 23 | buildDiscarder(logRotator(numToKeepStr: '5')) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PolyEval.h: -------------------------------------------------------------------------------- 1 | #ifndef _numeric_PolyEval_h_ 2 | #define _numeric_PolyEval_h_ 3 | 4 | #include 5 | 6 | namespace numeric 7 | { 8 | 9 | inline double PolyEval(double *cof, unsigned int order, double target) 10 | { 11 | if (order == 0) 12 | return 0.0; 13 | 14 | int corder = order - 1; 15 | 16 | double out = cof[corder]; 17 | 18 | for (unsigned int k = 1; k < order; k++) 19 | out = cof[corder-k] + target * out; 20 | 21 | return out; 22 | } 23 | 24 | inline double PolyEval(std::vector cof, double target) 25 | { 26 | return PolyEval(&cof[0], cof.size(), target); 27 | } 28 | 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /pushBathCalsToRemotes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | master="eol-rosetta" 4 | ping -c 1 $master 5 | if [[ $? -ne 0 ]] ; then 6 | echo "Master calibration database server '"$master"' is offline." 7 | exit 8 | fi 9 | 10 | for server in raf-acrouter.eol.ucar.edu hyper.eol.ucar.edu hercules.raf-guest.ucar.edu; do 11 | echo "Pushing bath cals to:" $server 12 | ping -c 1 $server 13 | if [[ $? -eq 0 ]] ; then 14 | pg_dump --insert -h $master -U ads calibrations | grep " 'TT_BATH', " | psql -h $server -U ads -d calibrations 15 | echo "Pushed bath cals to:" $server 16 | else 17 | echo "Failed to push bath cals to:" $server "(no connection)." 18 | fi 19 | done 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aircraft_caledit 2 | EOL/RAF aircraft calibration editor 3 | 4 | The calibration editor has a few functions. 5 | 1) sweep calibrations off the aircraft alibrations database and into a database inhouse (moved to eol-rosetta DEC 1, 2023). 6 | 2) view calibrations and export them to nidas cal files. 7 | 3) calibrations can be duplicated and then edited if need before exporting. Original cals from the aircraft can not be edited (paper trail). 8 | 9 | For EOL staff, detailed instructions on how to use caledit can be found in the SEW Wiki: https://wiki.ucar.edu/spaces/SEW/pages/364844232/CalDatabaseBackupSpecs. Instructions on how to sweep and export cals are at the bottom of that page. 10 | -------------------------------------------------------------------------------- /SortFilterProxyModel.h: -------------------------------------------------------------------------------- 1 | #ifndef SORTFILTERPROXYMODEL_H 2 | #define SORTFILTERPROXYMODEL_H 3 | 4 | #include 5 | 6 | /** 7 | * @class SortFilterProxyModel 8 | * This derived class allows for multiple column filters. 9 | */ 10 | class SortFilterProxyModel : public QSortFilterProxyModel 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit SortFilterProxyModel(QObject *parent = 0); 15 | void setFilter(int column, const QString &pattern); 16 | QString filterAt(int column); 17 | void clearFilters(); 18 | void refreshFilters(); 19 | 20 | protected: 21 | bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; 22 | 23 | private: 24 | QMap m_columnPatterns; 25 | }; 26 | 27 | #endif // SORTFILTERPROXYMODEL_H 28 | -------------------------------------------------------------------------------- /ViewTextDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | John Wasinger 4 | Ui::ViewTextDialog 5 | 6 | 7 | 8 | 0 9 | 0 10 | 900 11 | 454 12 | 13 | 14 | 15 | true 16 | 17 | 18 | false 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | qPixmapFromMimeSource 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /CalibrationPlot.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | CalibrationPlot 4 | 5 | 6 | 7 | 0 8 | 0 9 | 620 10 | 620 11 | 12 | 13 | 14 | Calibration Plot 15 | 16 | 17 | 18 | 19 | 0 20 | 20 21 | 600 22 | 600 23 | 24 | 25 | 26 | 27 | 28 | 29 | QwtPlot 30 | QFrame 31 |
qwt_plot.h
32 |
33 |
34 | 35 | 36 |
37 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | # -*- python -*- 2 | ## Copyright 2013 UCAR, NCAR, All Rights Reserved 3 | 4 | import eol_scons 5 | 6 | from SCons.Errors import StopError 7 | 8 | env = Environment(tools = ['default', 'qt5', 'qtsql', 'qtgui', 'qtwidgets', 'qtnetwork', 'qtcore', 'qwt', 'gsl']) 9 | 10 | # Compilation generates numerous warnings relative to the Qt4 code base itself when -Weffc++ is enabled 11 | #env['CXXFLAGS'] = ['-Weffc++','-Wall','-O2' ] 12 | env['CXXFLAGS'] = [ '-Wall','-std=c++11' ] 13 | 14 | uis = Split(""" 15 | ViewTextDialog.ui 16 | CalibrationPlot.ui 17 | CalibrationForm.ui 18 | """) 19 | 20 | env.Uic5(uis) 21 | 22 | sources = Split(""" 23 | main.cc 24 | MainWindow.cc 25 | CalibrationPlot.cc 26 | CalibrationForm.cc 27 | polyfitgsl.cc 28 | SortFilterProxyModel.cc 29 | ViewTextDialog.cc 30 | """) 31 | 32 | caledit = env.Program('caledit', sources) 33 | 34 | inode = env.Install('/opt/local/bin', caledit) 35 | env.Clean('install', inode) 36 | 37 | options = env.GlobalOptions() 38 | options.Update(env) 39 | Help(options.GenerateHelpText(env)) 40 | -------------------------------------------------------------------------------- /calTableHeaders.h: -------------------------------------------------------------------------------- 1 | #ifndef _calTableHeaders_h_ 2 | #define _calTableHeaders_h_ 3 | 4 | #define DB_DRIVER "QPSQL" 5 | #define DB_HOST "eol-rosetta.eol.ucar.edu" 6 | #define AC_SERV "acserver" 7 | //#define DB_HOST "localhost" 8 | #define DB_USER "ads" 9 | #define DB_NAME "calibrations" 10 | #define MAX_ORDER 3 11 | 12 | //#define SANDBOX 13 | 14 | #ifdef SANDBOX 15 | #define DB_TABLE "sandbox" 16 | #else 17 | #define DB_TABLE "calibrations" 18 | #endif 19 | 20 | enum calTableHeaders { 21 | clm_rid, 22 | clm_pid, 23 | clm_site, 24 | clm_pulled, 25 | clm_removed, 26 | clm_exported, 27 | clm_cal_date, 28 | clm_project_name, 29 | clm_username, 30 | clm_sensor_type, 31 | clm_serial_number, 32 | clm_var_name, 33 | clm_dsm_name, 34 | clm_cal_type, 35 | clm_channel, 36 | clm_gainbplr, 37 | clm_ads_file_name, 38 | clm_set_times, 39 | clm_set_points, 40 | clm_averages, 41 | clm_stddevs, 42 | clm_cal, 43 | clm_temperature, 44 | clm_comment, 45 | clm_COUNT 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /CalibrationPlot.h: -------------------------------------------------------------------------------- 1 | #ifndef _CalibrationPlot_h_ 2 | #define _CalibrationPlot_h_ 3 | 4 | #include 5 | 6 | class QColor; 7 | class QwtPlotCurve; 8 | 9 | #include 10 | 11 | using namespace std; 12 | 13 | #include "ui_CalibrationPlot.h" 14 | 15 | /** 16 | * @class CalibrationCurve 17 | * This is a simple class that associates a row of calibration data 18 | * to a pair of plotted curves. The 'actual' curve is rendered as 19 | * line segments from set point to set point. The 'fitted' is a 20 | * single straight line that is least square fitted to the set points 21 | * based upon the coefficients set in the calibration row. 22 | */ 23 | class CalibrationCurve 24 | { 25 | public: 26 | QString rid; 27 | QwtPlotCurve* actual; 28 | QwtPlotCurve* fitted; 29 | }; 30 | 31 | /** 32 | * @class CalibrationPlot 33 | * A container widget that stores the curves and maintains their colors. 34 | */ 35 | class CalibrationPlot : public QWidget, public Ui::CalibrationPlot 36 | { 37 | Q_OBJECT 38 | 39 | public: 40 | CalibrationPlot(QWidget* parent = 0); 41 | void dropColors(); 42 | void setupColors(); 43 | 44 | QList curves; 45 | list colors; 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /polyfitgsl.cc: -------------------------------------------------------------------------------- 1 | #include "polyfitgsl.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | bool polynomialfit(int obs, int order, 9 | double *dx, double *dy, double *store, double *Rsq) /* n, p */ 10 | { 11 | gsl_multifit_linear_workspace *ws; 12 | gsl_matrix *cov, *X; 13 | gsl_vector *y, *c; 14 | 15 | int i, j; 16 | 17 | X = gsl_matrix_alloc(obs, order); 18 | y = gsl_vector_alloc(obs); 19 | c = gsl_vector_alloc(order); 20 | cov = gsl_matrix_alloc(order, order); 21 | 22 | for(i=0; i < obs; i++) { 23 | gsl_matrix_set(X, i, 0, 1.0); 24 | for(j=0; j < order; j++) { 25 | gsl_matrix_set(X, i, j, pow(dx[i], j)); 26 | } 27 | gsl_vector_set(y, i, dy[i]); 28 | } 29 | 30 | double chisq; 31 | ws = gsl_multifit_linear_alloc(obs, order); 32 | gsl_multifit_linear(X, y, c, cov, &chisq, ws); 33 | 34 | *Rsq = 1.0 - chisq / gsl_stats_tss(dy, 1, obs); 35 | 36 | /* store result ... */ 37 | for(i=0; i < order; i++) 38 | store[i] = gsl_vector_get(c, i); 39 | 40 | gsl_multifit_linear_free(ws); 41 | gsl_matrix_free(X); 42 | gsl_matrix_free(cov); 43 | gsl_vector_free(y); 44 | gsl_vector_free(c); 45 | return true; /* we do not "analyse" the result (cov matrix mainly) 46 | to know if the fit is "good" */ 47 | } 48 | -------------------------------------------------------------------------------- /CalibrationForm.h: -------------------------------------------------------------------------------- 1 | #ifndef _CalibrationForm_h_ 2 | #define _CalibrationForm_h_ 3 | 4 | #include "ui_CalibrationForm.h" 5 | 6 | #define nRows 20 7 | 8 | class QStringListModel; 9 | class QAbstractItemModel; 10 | class QDataWidgetMapper; 11 | class QButtonGroup; 12 | class QPushButton; 13 | 14 | 15 | /** 16 | * @class CalibrationForm 17 | * This defines the form used when editing a single calibration entry. 18 | * Its layout UI is based upon the one used in the AEROS calibration tool. 19 | */ 20 | class CalibrationForm : public QWidget, public Ui::CalibrationForm 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | CalibrationForm(QWidget* parent = 0); 26 | 27 | void setModel(QAbstractItemModel *model); 28 | 29 | QStringListModel* setupComboModel(QString sql_column); 30 | 31 | void setupMapper(); 32 | 33 | void setRow(int row); 34 | int getRow() {return _row;}; 35 | 36 | void setEnabled(bool state); 37 | 38 | QList _setPointList; 39 | QList _delButtonList; 40 | QList _inputList; 41 | QList _stdDevList; 42 | QList _appliedList; 43 | QList _setDateTimeList; 44 | 45 | QList _currCalCList; 46 | QList _prevCalCList; 47 | 48 | QButtonGroup* _delButtonGroup; 49 | QButtonGroup* _curveFitGroup; 50 | 51 | public slots: 52 | void commitData(QWidget* widget); 53 | void removeSetPoint(int index); 54 | void changeFitButtonClicked(int order); 55 | void revert(); 56 | void submit(); 57 | 58 | signals: 59 | void removeSetPoint(int row, int index); 60 | void changeFitButtonClicked(int row, int order); 61 | void replot(int row); 62 | void initializeForm(int row); 63 | void submitForm(int row); 64 | 65 | private: 66 | QAbstractItemModel* _model; 67 | QDataWidgetMapper* _mapper; 68 | int _row; 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /CalibrationPlot.cc: -------------------------------------------------------------------------------- 1 | #include "CalibrationPlot.h" 2 | 3 | #include 4 | 5 | //#include "logx/Logging.h" 6 | 7 | //LOGGING("CalibrationPlot"); 8 | 9 | CalibrationPlot::CalibrationPlot(QWidget* parent) : QWidget(parent) 10 | { 11 | std::cout << __PRETTY_FUNCTION__ << std::endl; 12 | setupUi(parent); 13 | 14 | setupColors(); 15 | } 16 | 17 | /* -------------------------------------------------------------------- */ 18 | 19 | void CalibrationPlot::dropColors() 20 | { 21 | colors.clear(); 22 | } 23 | 24 | /* -------------------------------------------------------------------- */ 25 | 26 | void CalibrationPlot::setupColors() 27 | { 28 | colors.push_back( QColor(200, 0, 0) ); // 1 red 29 | colors.push_back( QColor( 0,200,200) ); // 7 skyblue 30 | colors.push_back( QColor(200,150, 0) ); // 2 orange 31 | colors.push_back( QColor( 0,150,200) ); // 8 lightblue 32 | colors.push_back( QColor(200,200, 0) ); // 3 yellow 33 | colors.push_back( QColor( 0, 0,200) ); // 9 blue 34 | colors.push_back( QColor(150,200, 0) ); // 4 yellowgreen 35 | colors.push_back( QColor(150, 0,200) ); // 10 purple 36 | colors.push_back( QColor( 0,200, 0) ); // 5 green 37 | colors.push_back( QColor(200, 0,200) ); // 11 fusha 38 | colors.push_back( QColor( 0,200,150) ); // 6 seagreen 39 | colors.push_back( QColor(200, 0,150) ); // 12 pink 40 | /* 41 | colors.push_back( QColor(200, 0, 0) ); // 1 red 42 | colors.push_back( QColor(200,150, 0) ); // 2 orange 43 | colors.push_back( QColor(200,200, 0) ); // 3 yellow 44 | colors.push_back( QColor(150,200, 0) ); // 4 yellowgreen 45 | colors.push_back( QColor( 0,200, 0) ); // 5 green 46 | colors.push_back( QColor( 0,200,150) ); // 6 seagreen 47 | colors.push_back( QColor( 0,200,200) ); // 7 skyblue 48 | colors.push_back( QColor( 0,150,200) ); // 8 lightblue 49 | colors.push_back( QColor( 0, 0,200) ); // 9 blue 50 | colors.push_back( QColor(150, 0,200) ); // 10 purple 51 | colors.push_back( QColor(200, 0,200) ); // 11 fusha 52 | colors.push_back( QColor(200, 0,150) ); // 12 pink 53 | */ 54 | } 55 | -------------------------------------------------------------------------------- /SortFilterProxyModel.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "SortFilterProxyModel.h" 4 | 5 | SortFilterProxyModel::SortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) 6 | { 7 | } 8 | 9 | /* -------------------------------------------------------------------- */ 10 | 11 | void SortFilterProxyModel::setFilter(int column, const QString &pattern) 12 | { 13 | qDebug() << __PRETTY_FUNCTION__ << "column:" << column << "pattern:" << pattern; 14 | 15 | if (pattern.isEmpty()) 16 | m_columnPatterns.remove(column); 17 | else 18 | m_columnPatterns[column] = QRegExp(pattern, Qt::CaseInsensitive); 19 | 20 | invalidateFilter(); 21 | } 22 | 23 | /* -------------------------------------------------------------------- */ 24 | 25 | QString SortFilterProxyModel::filterAt(int column) 26 | { 27 | if (m_columnPatterns.contains(column)) 28 | return m_columnPatterns[column].pattern(); 29 | else 30 | return ""; 31 | } 32 | 33 | /* -------------------------------------------------------------------- */ 34 | 35 | void SortFilterProxyModel::clearFilters() 36 | { 37 | if(m_columnPatterns.isEmpty()) 38 | return; 39 | 40 | qDebug() << __PRETTY_FUNCTION__; 41 | m_columnPatterns.clear(); 42 | invalidateFilter(); 43 | } 44 | 45 | /* -------------------------------------------------------------------- */ 46 | 47 | void SortFilterProxyModel::refreshFilters() 48 | { 49 | invalidateFilter(); 50 | } 51 | 52 | /* -------------------------------------------------------------------- */ 53 | 54 | bool SortFilterProxyModel::filterAcceptsRow(int sourceRow, 55 | const QModelIndex &sourceParent) const 56 | { 57 | if(m_columnPatterns.isEmpty()) 58 | return true; 59 | 60 | bool ret = false; 61 | 62 | for(QMap::const_iterator iter = m_columnPatterns.constBegin(); 63 | iter != m_columnPatterns.constEnd(); ++iter) 64 | { 65 | QModelIndex index = sourceModel()->index(sourceRow, iter.key(), sourceParent); 66 | ret = index.data().toString().contains(iter.value()); 67 | 68 | if(!ret) 69 | return ret; 70 | } 71 | 72 | return ret; 73 | } 74 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "MainWindow.h" 10 | 11 | //#include "logx/Logging.h" 12 | 13 | QString siteNameFilter; 14 | QString varNameFilter; 15 | 16 | int usage(const char* argv0) 17 | { 18 | std::cerr << "Usage: " << argv0 << " [options]\n"; 19 | std::cerr << " -s Filter table by site name.\n"; 20 | std::cerr << " -v Filter table by variable name.\n"; 21 | std::cerr << " -h This usage info.\n\n"; 22 | //logx::LogUsage(cerr); 23 | return 1; 24 | } 25 | 26 | int parseRunstring(int argc, char** argv) 27 | { 28 | extern char *optarg; /* set by getopt() */ 29 | // extern int optind; /* " " " */ 30 | int opt_char; /* option character */ 31 | 32 | while ((opt_char = getopt(argc, argv, "hs:v:")) != -1) { 33 | switch (opt_char) { 34 | case 's': 35 | siteNameFilter = optarg; 36 | qDebug() << "Filter table by site name:" << siteNameFilter; 37 | break; 38 | case 'v': 39 | varNameFilter = optarg; 40 | qDebug() << "Filter table by variable name:" << varNameFilter; 41 | break; 42 | case 'h': 43 | return usage(argv[0]); 44 | } 45 | } 46 | return 0; 47 | } 48 | 49 | /* -------------------------------------------------------------------- */ 50 | 51 | int main(int argc, char *argv[]) 52 | { 53 | // logx::ParseLogArgs (argc, argv, true/*skip usage*/); 54 | 55 | // TODO find out how '.qrc' files are processed by 'qt4.py' 56 | // Q_INIT_RESOURCE(CalibrationWizard); 57 | 58 | // Create the application so qt can extract its options. 59 | QApplication app(argc, argv); 60 | 61 | // Parse arguments list 62 | int res; 63 | if ((res = parseRunstring(argc,argv))) return res; 64 | 65 | // Install international language translator 66 | QString translatorFileName = QLatin1String("qt_"); 67 | translatorFileName += QLocale::system().name(); 68 | QTranslator *translator = new QTranslator(&app); 69 | if (translator->load(translatorFileName, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) 70 | app.installTranslator(translator); 71 | 72 | MainWindow window; 73 | window.setFilter( clm_site, siteNameFilter ); 74 | window.setFilter( clm_var_name, varNameFilter ); 75 | window.show(); 76 | res = app.exec(); 77 | std::cout << __PRETTY_FUNCTION__ << " EXITING" << std::endl; 78 | return res; 79 | } 80 | -------------------------------------------------------------------------------- /MainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef _MainWindow_h_ 2 | #define _MainWindow_h_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "calTableHeaders.h" 13 | #include "CalibrationPlot.h" 14 | #include "CalibrationForm.h" 15 | #include "SortFilterProxyModel.h" 16 | 17 | #include 18 | 19 | QT_BEGIN_NAMESPACE 20 | class QActionGroup; 21 | class QMenu; 22 | class QSqlTableModel; 23 | class QItemSelectionModel; 24 | class QStringList; 25 | class QTableView; 26 | QT_END_NAMESPACE 27 | 28 | class BackgroundColorDelegate; 29 | 30 | /** 31 | * @class MainWindow 32 | * Provides an interface to allow updating, viewing, QA, editing, 33 | * and exporting (individual calibrations) of the "master" calibration 34 | * database table. 35 | */ 36 | class MainWindow : public QMainWindow 37 | { 38 | Q_OBJECT 39 | 40 | public: 41 | MainWindow(); 42 | ~MainWindow(); 43 | 44 | void openDatabase(QString hostname); 45 | 46 | /// Set a column filter pattern for a given column in the table view. 47 | void setFilter(int column, const QString &pattern); 48 | 49 | signals: 50 | void submitForm(); 51 | void revertForm(); 52 | 53 | protected slots: 54 | 55 | /// Remember what the last item selected was. 56 | void tableItemPressed(const QModelIndex &); 57 | 58 | /// Toggle the row's hidden state selected by cal type. 59 | void toggleRow(int id); 60 | 61 | /// Applies hidden states to all rows. 62 | void hideRows(); 63 | 64 | /// Toggles column's hidden state. 65 | void toggleColumn(int id); 66 | 67 | /// Enable or disable hide and show options based upon column availability. 68 | void headerMenu_aboutToShow(); 69 | 70 | /// Detects changes to the database. 71 | void dataChanged(const QModelIndex& old, const QModelIndex& now); 72 | 73 | void onQuit(); 74 | 75 | /// Imports a calibration from an ISFF file. 76 | int importButtonClicked(); 77 | 78 | /// Saves changes to the local database. 79 | /// @returns 0 on success. 80 | int saveButtonClicked(); 81 | 82 | /// Scroll to the top of the table 83 | void scrollToHome(); 84 | 85 | /// Scroll to the bottom of the table 86 | void scrollToEnd(); 87 | 88 | /// Scroll to the last selected item. 89 | void scrollToLastClicked(); 90 | 91 | /// Scroll to the currently edited row 92 | void scrollToEditedRow(); 93 | 94 | /// Open this calibration line entry in the form view 95 | void editCalButtonClicked(); 96 | 97 | /// Plot this calibration line entry in the graph 98 | void plotCalButtonClicked(); 99 | 100 | /// Unplot this calibration line entry in the graph 101 | void unplotCalButtonClicked(); 102 | 103 | /// Unplots all calibrations and resets the color pallette sequence 104 | void unplotAllButtonClicked(); 105 | 106 | /// Create an entry in the corresponding NIDAS cal file. 107 | void exportCalButtonClicked(); 108 | 109 | /// Create a CSV file of set_points and avererages for an entry. 110 | void exportCsvButtonClicked(); 111 | 112 | /// View NIDAS cal file for an entry. 113 | void viewCalButtonClicked(); 114 | 115 | /// View CSV file for an entry. 116 | void viewCsvButtonClicked(); 117 | 118 | /// Clone an entry in the database. 119 | void cloneButtonClicked(); 120 | 121 | /// Marks an entry as deleted in the database. 122 | void removeButtonClicked(); 123 | 124 | /// Changes the polynominal fit 125 | void changeFitButtonClicked(int row, int order); 126 | 127 | /// Creates a popup menu for the table's header 128 | void showHeaderMenu( const QPoint &pos ); 129 | 130 | /// Filters a selected column by a user entered string 131 | void filterBy(); 132 | 133 | /// Unfilters all columns 134 | void unfilterAll(); 135 | 136 | /// hides a selected column 137 | void hideColumn(); 138 | 139 | /// shows a selected column choosen by the user 140 | void showColumn(); 141 | 142 | /// Creates a popup menu for the table 143 | void showTableMenu( const QPoint &pos ); 144 | 145 | /// Remove a set point (and all of its associated values) 146 | void removeSetPoint(int row, int index); 147 | 148 | /// Replot row 149 | void replot(int row); 150 | 151 | /// Initialize form entries for row 152 | void initializeForm(int row); 153 | 154 | /// Submit form entries for row to model 155 | void submitForm(int row); 156 | 157 | private: 158 | 159 | /// Allow for viewin DB on aircraft 160 | bool onAircraft; 161 | 162 | /// Create a unique RID (relative to this system). 163 | QString createRID(); 164 | 165 | /// Ask user to submit the current Form before editing a new line one or quiting 166 | void unsubmittedFormQuery(QString title); 167 | 168 | /// restrict allowable polynominal order radio buttons 169 | void restrictOrderChoice(QStringList list_set_points); 170 | 171 | /// Plot calibration for this row 172 | void plotCalButtonClicked(int row); 173 | 174 | /// Unplot calibration for this row 175 | void unplotCalButtonClicked(int row); 176 | 177 | /// Scroll to row for the given RID. 178 | void scrollToRid(QString rid); 179 | 180 | QSqlTableModel* _model; 181 | SortFilterProxyModel* _proxy; 182 | QTableView* _table; 183 | CalibrationPlot* _plot; 184 | CalibrationForm* _form; 185 | BackgroundColorDelegate* _delegate; 186 | QString _lastRid; 187 | QString _editRid; 188 | 189 | QActionGroup *colsGrp; 190 | 191 | /// Selected column when the table header is right clicked on for its context menu. 192 | int _column; 193 | 194 | QAction *hideColumnAction; 195 | QAction *showColumnAction; 196 | 197 | // A list of editable clone RIDs. 198 | QStringList _editable; 199 | 200 | void setupDatabase(); 201 | void setupModels(); 202 | void setupDelegates(); 203 | void setupTable(); 204 | void setupViews(); 205 | void setupMenus(); 206 | 207 | void exportInstrument(int currentRow); 208 | void exportAnalog(int currentRow); 209 | void exportBath(int currentRow); 210 | 211 | void exportCalFile(QString filename, std::string contents); 212 | void saveFileAs(QString filename, QString title, std::string contents); 213 | void viewFile(QString filename); 214 | void viewText(QString text, QString title); 215 | 216 | /// Imports a remote calibration table into the master database. 217 | void importRemoteCalibTable(QString remote); 218 | 219 | bool changeDetected; 220 | 221 | bool showAnalog; 222 | bool showBath; 223 | bool showInstrument; 224 | bool showCloned; 225 | bool showRemoved; 226 | bool showExported; 227 | 228 | void addRowAction(QMenu *menu, const QString &text, 229 | QActionGroup *group, QSignalMapper *mapper, 230 | int id, bool checked); 231 | 232 | void addColAction(QMenu *menu, const QString &text, 233 | QActionGroup *group, QSignalMapper *mapper, 234 | int id, bool checked); 235 | 236 | void addAction(QMenu *menu, const QString &text, 237 | QActionGroup *group, QSignalMapper *mapper, 238 | int id, bool checked); 239 | 240 | /// Returns a string from the model 241 | QString modelData(int row, int col); 242 | 243 | /// Returns a list of extracted values set within braces from the table 244 | QStringList extractListFromBracedCSV(int row, calTableHeaders key); 245 | 246 | /// Returns a list of extracted values set within braces 247 | QStringList extractListFromBracedCSV(QString string); 248 | 249 | /// Returns a string set within in braces 250 | QString extractStringWithinBraced(QString string); 251 | 252 | QMenu *headerMenu; 253 | QMenu *tableMenu; 254 | 255 | QString calfile_dir; 256 | QString csvfile_dir; 257 | 258 | std::map tailNumIdx; 259 | std::map showTailNum; 260 | 261 | /// Temporary values from an edited line in the model that allows initial 262 | /// changes in the form to be seen in the plot. 263 | QString form_set_times; 264 | QString form_set_points; 265 | QString form_averages; 266 | QString form_stddevs; 267 | QString form_cal; 268 | }; 269 | 270 | #endif 271 | -------------------------------------------------------------------------------- /CalibrationForm.cc: -------------------------------------------------------------------------------- 1 | #include "CalibrationForm.h" 2 | #include "calTableHeaders.h" 3 | 4 | #include 5 | 6 | // CentOS7 Qt5.9 pkg-config Qt5Widgets should define this, but is not. 7 | #ifndef QT_WIDGETS_LIB 8 | #define QT_WIDGETS_LIB 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | //#include "logx/Logging.h" 21 | 22 | //LOGGING("CalibrationForm"); 23 | 24 | /* -------------------------------------------------------------------- */ 25 | CalibrationForm::CalibrationForm(QWidget* parent) : QWidget(parent) 26 | { 27 | std::cout << __PRETTY_FUNCTION__ << std::endl; 28 | 29 | setupUi(this); 30 | 31 | _tableWidget->setRowCount(nRows); 32 | 33 | for (int r=0; rsetCheckable(true); 43 | // _delButtonList[r]->setChecked(false); 44 | 45 | _setPointList[r]->setReadOnly(true); 46 | _delButtonList[r]->setText("del"); 47 | _inputList[r]->setReadOnly(true); 48 | _stdDevList[r]->setReadOnly(true); 49 | _appliedList[r]->setReadOnly(true); 50 | _setDateTimeList[r]->setReadOnly(true); 51 | 52 | _setPointList[r]->setMinimumWidth(80); 53 | _delButtonList[r]->setFixedWidth(40); 54 | _inputList[r]->setMinimumWidth(90); 55 | _stdDevList[r]->setMinimumWidth(90); 56 | _appliedList[r]->setMinimumWidth(20); 57 | _setDateTimeList[r]->setMinimumWidth(50); 58 | 59 | _tableWidget->setCellWidget(r, 0, _setPointList[r] ); 60 | _tableWidget->setCellWidget(r, 1, _delButtonList[r] ); 61 | _tableWidget->setCellWidget(r, 2, _inputList[r] ); 62 | _tableWidget->setCellWidget(r, 3, _stdDevList[r] ); 63 | _tableWidget->setCellWidget(r, 4, _appliedList[r] ); 64 | _tableWidget->setCellWidget(r, 5, _setDateTimeList[r] ); 65 | } 66 | _tableWidget->resizeColumnsToContents(); 67 | _tableWidget->horizontalHeader()->setStretchLastSection( true ); 68 | _tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); 69 | 70 | _currCalCList.append(_currCalC0); 71 | _currCalCList.append(_currCalC1); 72 | _currCalCList.append(_currCalC2); 73 | _currCalCList.append(_currCalC3); 74 | 75 | _prevCalCList.append(_prevCalC0); 76 | _prevCalCList.append(_prevCalC1); 77 | _prevCalCList.append(_prevCalC2); 78 | _prevCalCList.append(_prevCalC3); 79 | 80 | _delButtonGroup = new QButtonGroup(this); 81 | _delButtonGroup->setObjectName(QString::fromUtf8("_delButtonGroup")); 82 | _delButtonGroup->setExclusive(false); 83 | for (int r=0; raddButton(_delButtonList[r], r); 85 | 86 | connect(_delButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(removeSetPoint(int))); 87 | 88 | _curveFitGroup = new QButtonGroup(this); 89 | _curveFitGroup->setObjectName(QString::fromUtf8("_curveFitGroup")); 90 | _curveFitGroup->addButton(_linearRB, 1); 91 | _curveFitGroup->addButton(_2ndOrderRB, 2); 92 | _curveFitGroup->addButton(_3rdOrderRB, 3); 93 | connect(_curveFitGroup, SIGNAL(buttonClicked(int)), this, SLOT(changeFitButtonClicked(int))); 94 | } 95 | 96 | /* -------------------------------------------------------------------- */ 97 | void CalibrationForm::setModel(QAbstractItemModel *model) 98 | { 99 | _model = model; 100 | setupMapper(); 101 | } 102 | 103 | /* -------------------------------------------------------------------- */ 104 | void CalibrationForm::commitData(QWidget* widget) 105 | { 106 | std::cout << __PRETTY_FUNCTION__ << std::endl; 107 | std::cout << widget << std::endl; 108 | 109 | _revertBtn->setEnabled(true); 110 | _submitBtn->setEnabled(true); 111 | 112 | // update Combo Box... 113 | QComboBox *combobox = qobject_cast(widget); 114 | if (!combobox) return; 115 | 116 | QString currentText = combobox->currentText(); 117 | 118 | QStringListModel *slm = qobject_cast(combobox->model()); 119 | 120 | QStringList list = slm->stringList(); 121 | 122 | // TODO resort the list after insertion? 123 | if (!list.contains(currentText)) { 124 | list.prepend(currentText); 125 | slm->setStringList(list); 126 | combobox->setCurrentIndex(0); 127 | } 128 | } 129 | 130 | /* -------------------------------------------------------------------- */ 131 | QStringListModel* CalibrationForm::setupComboModel(QString sql_column) 132 | { 133 | // std::cout << __PRETTY_FUNCTION__ << " : " << sql_column.toStdString() << std::endl; 134 | QStringList items; 135 | items << ""; 136 | 137 | // Extract a list of previously used values from the database 138 | QString sql = QString("SELECT DISTINCT %1 FROM " DB_TABLE).arg(sql_column); 139 | // std::cout << sql.toStdString() << std::endl; 140 | 141 | QSqlQuery query(sql, QSqlDatabase::database() ); 142 | while (query.next()) 143 | items << query.value(0).toString().trimmed(); 144 | query.finish(); 145 | 146 | if (sql_column == "cal_type") 147 | items << "bath" << "analog" << "instrument"; 148 | 149 | // remove duplicates and sort. 150 | #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) 151 | items = items.toSet().toList(); 152 | #else 153 | QSet set(items.begin(), items.end()); 154 | items = QStringList(set.begin(), set.end()); 155 | #endif 156 | items.sort(); 157 | 158 | // foreach(QString item, items) 159 | // std::cout << __PRETTY_FUNCTION__ << " > " << item.toStdString() << std::endl; 160 | 161 | QStringListModel* comboModel = new QStringListModel(items, this); 162 | return comboModel; 163 | } 164 | 165 | /* -------------------------------------------------------------------- */ 166 | void CalibrationForm::setupMapper() 167 | { 168 | std::cout << __PRETTY_FUNCTION__ << std::endl; 169 | 170 | _mapper = new QDataWidgetMapper(this); 171 | _mapper->setModel(_model); 172 | _mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); 173 | _mapper->setItemDelegate(new QSqlRelationalDelegate(_mapper)); 174 | connect(_mapper->itemDelegate(), SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); 175 | 176 | _projTxt->setModel( setupComboModel("project_name") ); 177 | _userTxt->setModel( setupComboModel("username") ); 178 | _sensorTypeTxt->setModel( setupComboModel("sensor_type") ); 179 | _serialNumTxt->setModel( setupComboModel("serial_number") ); 180 | _dsmTxt->setModel( setupComboModel("dsm_name") ); 181 | _calTypeTxt->setModel( setupComboModel("cal_type") ); 182 | _addrTxt->setModel( setupComboModel("channel") ); 183 | _gainbplrTxt->setModel( setupComboModel("gainbplr") ); 184 | 185 | _projTxt ->setEditText( "" ); 186 | _userTxt ->setEditText( "" ); 187 | _sensorTypeTxt->setEditText( "" ); 188 | _serialNumTxt ->setEditText( "" ); 189 | _dsmTxt ->setEditText( "" ); 190 | _calTypeTxt ->setEditText( "" ); 191 | _addrTxt ->setEditText( "" ); 192 | _gainbplrTxt ->setEditText( "" ); 193 | 194 | _mapper->addMapping( _projTxt, clm_project_name, "currentText"); 195 | _mapper->addMapping( _userTxt, clm_username, "currentText"); 196 | _mapper->addMapping( _sensorTypeTxt, clm_sensor_type, "currentText"); 197 | _mapper->addMapping( _serialNumTxt, clm_serial_number, "currentText"); 198 | _mapper->addMapping( _varNameTxt, clm_var_name, "text"); 199 | _mapper->addMapping( _dsmTxt, clm_dsm_name, "currentText"); 200 | _mapper->addMapping( _calTypeTxt, clm_cal_type, "currentText"); 201 | _mapper->addMapping( _addrTxt, clm_channel, "currentText"); 202 | _mapper->addMapping( _gainbplrTxt, clm_gainbplr, "currentText"); 203 | _mapper->addMapping( _adsFileName, clm_ads_file_name, "text"); 204 | _mapper->addMapping( _temperatureTxt, clm_temperature ); 205 | _mapper->addMapping( _commentTxt, clm_comment, "plainText"); 206 | _mapper->addMapping( _calDateTime, clm_cal_date, "dateTime"); 207 | } 208 | 209 | /* -------------------------------------------------------------------- */ 210 | void CalibrationForm::setRow(int row) 211 | { 212 | _row = row; 213 | _mapper->setCurrentIndex(row); 214 | } 215 | 216 | /* -------------------------------------------------------------------- */ 217 | void CalibrationForm::setEnabled(bool state) 218 | { 219 | std::cout << __PRETTY_FUNCTION__ << " state: " << state << std::endl; 220 | 221 | _projTxt->setEnabled(state); 222 | _userTxt->setEnabled(state); 223 | _sensorTypeTxt->setEnabled(state); 224 | _serialNumTxt->setEnabled(state); 225 | _varNameTxt->setEnabled(state); 226 | _dsmTxt->setEnabled(state); 227 | _calTypeTxt->setEnabled(state); 228 | _addrTxt->setEnabled(state); 229 | _gainbplrTxt->setEnabled(state); 230 | _adsFileName->setEnabled(state); 231 | _temperatureTxt->setEnabled(state); 232 | _commentTxt->setEnabled(state); 233 | _calDateTime->setEnabled(state); 234 | 235 | _platformTxt->setEnabled(state); 236 | _tableWidget->setEnabled(state); 237 | _linearRB->setEnabled(state); 238 | _2ndOrderRB->setEnabled(state); 239 | _3rdOrderRB->setEnabled(state); 240 | _currCalC0->setEnabled(state); 241 | _prevCalC0->setEnabled(state); 242 | _currCalC1->setEnabled(state); 243 | _prevCalC1->setEnabled(state); 244 | _currCalC2->setEnabled(state); 245 | _prevCalC2->setEnabled(state); 246 | _currCalC3->setEnabled(state); 247 | _prevCalC3->setEnabled(state); 248 | 249 | _revertBtn->setEnabled(false); 250 | _submitBtn->setEnabled(false); 251 | 252 | for (int r=0; rbutton(r)->setEnabled(state); 254 | } 255 | 256 | /* -------------------------------------------------------------------- */ 257 | void CalibrationForm::removeSetPoint( int index ) 258 | { 259 | std::cout << "removeSetPoint index: " << index << std::endl; 260 | 261 | // _deleted[index] != _deleted[index]; // TODO make button toggle-able 262 | 263 | // note, this just clears the displayed values. They are actually 264 | // removed by MainWindow::removeSetPoint. 265 | _setPointList [index]->clear(); 266 | _delButtonList [index]->setEnabled(false); 267 | _inputList [index]->clear(); 268 | _stdDevList [index]->clear(); 269 | _appliedList [index]->clear(); 270 | _setDateTimeList[index]->clear(); 271 | 272 | emit removeSetPoint(_row, index); 273 | emit replot(_row); 274 | 275 | _revertBtn->setEnabled(true); 276 | _submitBtn->setEnabled(true); 277 | } 278 | 279 | /* -------------------------------------------------------------------- */ 280 | void CalibrationForm::changeFitButtonClicked(int order) 281 | { 282 | std::cout << "changeFitButtonClicked order: " << order << std::endl; 283 | emit changeFitButtonClicked(_row, order); 284 | emit replot(_row); 285 | 286 | _revertBtn->setEnabled(true); 287 | _submitBtn->setEnabled(true); 288 | } 289 | 290 | /* -------------------------------------------------------------------- */ 291 | void CalibrationForm::revert() 292 | { 293 | std::cout << __PRETTY_FUNCTION__ << std::endl; 294 | emit initializeForm(_row); 295 | _mapper->revert(); 296 | 297 | _revertBtn->setEnabled(false); 298 | _submitBtn->setEnabled(false); 299 | } 300 | 301 | /* -------------------------------------------------------------------- */ 302 | void CalibrationForm::submit() 303 | { 304 | std::cout << __PRETTY_FUNCTION__ << std::endl; 305 | emit submitForm(_row); 306 | _mapper->submit(); 307 | 308 | _revertBtn->setEnabled(false); 309 | _submitBtn->setEnabled(false); 310 | } 311 | -------------------------------------------------------------------------------- /CalibrationForm.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | John Wasinger 4 | CalibrationForm 5 | 6 | 7 | 8 | 0 9 | 0 10 | 529 11 | 734 12 | 13 | 14 | 15 | 16 | 0 17 | 0 18 | 19 | 20 | 21 | 22 | 300 23 | 200 24 | 25 | 26 | 27 | 28 | 600 29 | 32767 30 | 31 | 32 | 33 | View and Edit a Calibration Entry 34 | 35 | 36 | 37 | 6 38 | 39 | 40 | 9 41 | 42 | 43 | 44 | 45 | 6 46 | 47 | 48 | 0 49 | 50 | 51 | 52 | 53 | 6 54 | 55 | 56 | 0 57 | 58 | 59 | 60 | 61 | Select RID 62 | 63 | 64 | 65 | 66 | 67 | 68 | Row ID: 69 | 70 | 71 | _ridTxt 72 | 73 | 74 | 75 | 76 | 77 | 78 | true 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 6 88 | 89 | 90 | 0 91 | 92 | 93 | 94 | 95 | ADS file name: 96 | 97 | 98 | _adsFileName 99 | 100 | 101 | 102 | 103 | 104 | 105 | true 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 6 115 | 116 | 117 | 0 118 | 119 | 120 | 121 | 122 | Variable Name: 123 | 124 | 125 | _varNameTxt 126 | 127 | 128 | 129 | 130 | 131 | 132 | false 133 | 134 | 135 | 136 | 137 | 138 | 139 | User: 140 | 141 | 142 | _userTxt 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 180 151 | 0 152 | 153 | 154 | 155 | true 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 6 165 | 166 | 167 | 0 168 | 169 | 170 | 171 | 172 | Site: 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 100 181 | 0 182 | 183 | 184 | 185 | true 186 | 187 | 188 | 189 | 190 | 191 | 192 | Project: 193 | 194 | 195 | _projTxt 196 | 197 | 198 | 199 | 200 | 201 | 202 | true 203 | 204 | 205 | 206 | 207 | 208 | 209 | Temperature: 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 50 218 | 16777215 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 6 229 | 230 | 231 | 0 232 | 233 | 234 | 235 | 236 | Sensor Type: 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 200 245 | 0 246 | 247 | 248 | 249 | true 250 | 251 | 252 | 253 | 254 | 255 | 256 | Calibration Type: 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 80 265 | 0 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 6 276 | 277 | 278 | 0 279 | 280 | 281 | 282 | 283 | DSM: 284 | 285 | 286 | _dsmTxt 287 | 288 | 289 | 290 | 291 | 292 | 293 | true 294 | 295 | 296 | 297 | 298 | 299 | 300 | Addr: 301 | 302 | 303 | _addrTxt 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | GB: 314 | 315 | 316 | _gainbplrTxt 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | Serial #: 327 | 328 | 329 | _serialNumTxt 330 | 331 | 332 | 333 | 334 | 335 | 336 | true 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 300 349 | 20 350 | 351 | 352 | 353 | true 354 | 355 | 356 | false 357 | 358 | 359 | false 360 | 361 | 362 | false 363 | 364 | 365 | false 366 | 367 | 368 | 369 | Set Point 370 | 371 | 372 | AlignHCenter|AlignVCenter|AlignCenter 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | AlignHCenter|AlignVCenter|AlignCenter 381 | 382 | 383 | 384 | 385 | Input 386 | 387 | 388 | AlignHCenter|AlignVCenter|AlignCenter 389 | 390 | 391 | 392 | 393 | Std Dev 394 | 395 | 396 | AlignHCenter|AlignVCenter|AlignCenter 397 | 398 | 399 | 400 | 401 | Applied 402 | 403 | 404 | AlignHCenter|AlignVCenter|AlignCenter 405 | 406 | 407 | 408 | 409 | Set Time 410 | 411 | 412 | AlignHCenter|AlignVCenter|AlignCenter 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 0 422 | 40 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 9 431 | 432 | 433 | 5 434 | 435 | 436 | 437 | 438 | Linear 439 | 440 | 441 | 442 | 443 | 444 | 445 | Second Order 446 | 447 | 448 | 449 | 450 | 451 | 452 | Third Order 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 0 463 | 464 | 465 | 6 466 | 467 | 468 | 469 | 470 | Current Cal: 471 | 472 | 473 | 474 | 475 | 476 | 477 | Previous Cal: 478 | 479 | 480 | 481 | 482 | 483 | 484 | true 485 | 486 | 487 | 488 | 489 | 490 | 491 | true 492 | 493 | 494 | 495 | 496 | 497 | 498 | true 499 | 500 | 501 | 502 | 503 | 504 | 505 | true 506 | 507 | 508 | 509 | 510 | 511 | 512 | true 513 | 514 | 515 | 516 | 517 | 518 | 519 | true 520 | 521 | 522 | 523 | 524 | 525 | 526 | true 527 | 528 | 529 | 530 | 531 | 532 | 533 | true 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | Comment: 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 16777215 555 | 50 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 6 564 | 565 | 566 | 11 567 | 568 | 569 | 570 | 571 | cal date time: 572 | 573 | 574 | 575 | 576 | 577 | 578 | yyyy MMM dd HH:mm:ss 579 | 580 | 581 | 582 | 583 | 584 | 585 | Qt::Horizontal 586 | 587 | 588 | QSizePolicy::Expanding 589 | 590 | 591 | 592 | 20 593 | 20 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | Revert 602 | 603 | 604 | 605 | 606 | 607 | 608 | Submit 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | _revertBtn 621 | clicked() 622 | CalibrationForm 623 | revert() 624 | 625 | 626 | 20 627 | 20 628 | 629 | 630 | 20 631 | 20 632 | 633 | 634 | 635 | 636 | _submitBtn 637 | clicked() 638 | CalibrationForm 639 | submit() 640 | 641 | 642 | 20 643 | 20 644 | 645 | 646 | 20 647 | 20 648 | 649 | 650 | 651 | 652 | 653 | -------------------------------------------------------------------------------- /MainWindow.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "MainWindow.h" 15 | #include "ViewTextDialog.h" 16 | #include "polyfitgsl.h" 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | //#include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include 50 | 51 | #include 52 | #include 53 | #include 54 | //#include 55 | #include 56 | 57 | #include "PolyEval.h" 58 | 59 | #define MAX_BATH_PROBES 10 60 | #define PATH_TO_BATH_CALS "/Configuration/cal_files/Bath" 61 | #define DECADE_BOX_WIRE_RESISTANCE 0.03 // Ohms 62 | //#define DECADE_BOX_WIRE_RESISTANCE 0 // Remove resistance when requested by Friesen 63 | 64 | 65 | /* -------------------------------------------------------------------- */ 66 | /* -------------------------------------------------------------------- */ 67 | /* -------------------------------------------------------------------- */ 68 | #include 69 | 70 | /** 71 | * @class BackgroundColorDelegate 72 | * A delgate that colors the lines in the table view. 73 | */ 74 | class BackgroundColorDelegate: public QItemDelegate 75 | { 76 | public: 77 | BackgroundColorDelegate(QAbstractItemModel *model, QObject *parent = 0) 78 | : QItemDelegate(parent) 79 | { 80 | std::cout << __PRETTY_FUNCTION__ << std::endl; 81 | _model = model; 82 | } 83 | 84 | public: 85 | void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 86 | { 87 | drawBackground(painter, option, index); 88 | QItemDelegate::paint(painter, option, index); 89 | } 90 | 91 | void highlightRow(QString rid, QColor color) 92 | { 93 | std::cout << __PRETTY_FUNCTION__ << std::endl; 94 | std::cout << "rid: " << rid.toStdString(); 95 | std::cout << " color: " << color.red() << " " << color.green() << " " << color.blue() << std::endl; 96 | m_colorMap[rid] = color; 97 | } 98 | void unhighlightRow(QString rid) 99 | { 100 | std::cout << "rid: " << rid.toStdString() << std::endl; 101 | m_colorMap.remove(rid); 102 | } 103 | 104 | protected: 105 | void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 106 | { 107 | QString rid = _model->index(index.row(), clm_rid).data().toString().trimmed(); 108 | if (m_colorMap.contains(rid)) 109 | painter->fillRect(option.rect, m_colorMap.value(rid)); 110 | } 111 | private: 112 | QAbstractItemModel *_model; 113 | 114 | /// model's row id (rid) to color mapping; temporary model data. 115 | QMap m_colorMap; 116 | }; 117 | /* -------------------------------------------------------------------- */ 118 | /* -------------------------------------------------------------------- */ 119 | /* -------------------------------------------------------------------- */ 120 | 121 | MainWindow::MainWindow() : onAircraft(false),_model(0), changeDetected(false) 122 | { 123 | std::cout << __PRETTY_FUNCTION__ << std::endl; 124 | 125 | // Seed the random number generator used to create UUIDs. 126 | srand(time(0)); 127 | qDebug() << QUuid::createUuid(); 128 | qDebug() << QUuid::createUuid(); 129 | qDebug() << QUuid::createUuid(); 130 | 131 | setupDatabase(); 132 | 133 | setupModels(); 134 | 135 | setupTable(); 136 | 137 | setupDelegates(); 138 | 139 | setupViews(); 140 | 141 | setupMenus(); 142 | 143 | setWindowTitle(tr("Edit Calibration Database")); 144 | QRect screenSize = QApplication::desktop()->availableGeometry(this); 145 | resize(QSize(screenSize.width() * 0.5f, screenSize.height() * 0.9f)); 146 | setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), screenSize)); 147 | scrollToEnd(); 148 | 149 | hideRows(); 150 | } 151 | 152 | /* -------------------------------------------------------------------- */ 153 | 154 | void MainWindow::setupDatabase() 155 | { 156 | std::cout << __PRETTY_FUNCTION__ << std::endl; 157 | 158 | // Ping the master DB server to see if it is active. 159 | QProcess process; 160 | QStringList params; 161 | params << DB_HOST << "-i" << "1" << "-w" << "1" <<"-c" << "1"; 162 | 163 | /* Comment out check for barolo - would like to at least be able to 164 | * view local databases... 165 | if (process.execute("ping", params)) { 166 | QMessageBox::information(0, tr("pinging calibration database"), 167 | tr("cannot contact:\n") + DB_HOST); 168 | exit(1); 169 | } 170 | */ 171 | // List of remote sites that fill a calibration database. 172 | QStringList siteList; 173 | siteList << "petajoules.eol.ucar.edu"; // Tech labstation 174 | siteList << "gigajoules.eol.ucar.edu"; // EE labstation 175 | siteList << "hyper.eol.ucar.edu"; // GV 176 | siteList << "hercules.eol.ucar.edu"; // C130 177 | 178 | cerr<<"Host is:"<setTable(DB_TABLE); 245 | _model->setSort(clm_cal_date, Qt::AscendingOrder); 246 | _model->setEditStrategy(QSqlTableModel::OnManualSubmit); 247 | _model->select(); 248 | 249 | _proxy = new SortFilterProxyModel; 250 | _proxy->setDynamicSortFilter(true); // NOTE - http://doc.qt.digia.com/4.7-snapshot/qsortfilterproxymodel.html#dynamicSortFilter-prop 251 | _proxy->setSourceModel(_model); 252 | } 253 | 254 | /* -------------------------------------------------------------------- */ 255 | 256 | void MainWindow::setupTable() 257 | { 258 | std::cout << __PRETTY_FUNCTION__ << std::endl; 259 | 260 | _table = new QTableView; 261 | // TODO use a custom delegate for displaying the clm_cal_date? 262 | // 263 | // http://www.qtcentre.org/archive/index.php/t-20277.html mentions this... 264 | // The proper way of handling such situations is to provide a custom QAbstractItemDelegate for the widget mapper with setModelData() and setEditorData() reimplemented. 265 | // 266 | // these are inherited members of QTableView: 267 | // void setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate); 268 | // QAbstractItemDelegate *itemDelegateForColumn(int column) const; 269 | 270 | // disable editing of table 271 | _table->setEditTriggers(QAbstractItemView::NoEditTriggers); 272 | 273 | /* TODO this doesn't show anything... yet the columns are still draggable. why? 274 | _table->setDragEnabled(true); 275 | _table->setAcceptDrops(true); 276 | _table->setDropIndicatorShown(true); 277 | */ 278 | _table->setModel(_proxy); 279 | 280 | _table->setContextMenuPolicy( Qt::CustomContextMenu ); 281 | 282 | QHeaderView *hH = _table->horizontalHeader(); 283 | hH->setContextMenuPolicy( Qt::CustomContextMenu ); 284 | 285 | _table->adjustSize(); 286 | 287 | connect(_table, SIGNAL( pressed(const QModelIndex &)), 288 | this, SLOT( tableItemPressed(const QModelIndex &))); 289 | 290 | connect(hH, SIGNAL( customContextMenuRequested( const QPoint & )), 291 | this, SLOT( showHeaderMenu( const QPoint & ))); 292 | 293 | connect(_table, SIGNAL( customContextMenuRequested( const QPoint & )), 294 | this, SLOT( showTableMenu( const QPoint & ))); 295 | 296 | _table->resizeColumnsToContents(); 297 | } 298 | 299 | /* -------------------------------------------------------------------- */ 300 | 301 | void MainWindow::setupDelegates() 302 | { 303 | std::cout << __PRETTY_FUNCTION__ << std::endl; 304 | 305 | _delegate = new BackgroundColorDelegate(_proxy); 306 | _table->setItemDelegate(_delegate); 307 | } 308 | 309 | /* -------------------------------------------------------------------- */ 310 | 311 | void MainWindow::setupViews() 312 | { 313 | std::cout << __PRETTY_FUNCTION__ << std::endl; 314 | 315 | QSplitter *splitHorz = new QSplitter; 316 | QSplitter *splitVert = new QSplitter; 317 | 318 | splitHorz->setOrientation(Qt::Horizontal); 319 | splitVert->setOrientation(Qt::Vertical); 320 | 321 | QWidget *container = new QWidget; 322 | QVBoxLayout *layoutHorz = new QVBoxLayout; 323 | 324 | _plot = new CalibrationPlot(splitHorz); 325 | _form = new CalibrationForm(splitHorz); 326 | 327 | splitHorz->addWidget(_plot); 328 | splitHorz->addWidget(_form); 329 | 330 | layoutHorz->addWidget(splitHorz); 331 | container->setLayout(layoutHorz); 332 | 333 | splitVert->addWidget(container); 334 | splitVert->addWidget(_table); 335 | 336 | QVBoxLayout *layout = new QVBoxLayout; 337 | 338 | layout->addWidget(splitVert); 339 | 340 | QWidget *wnd = new QWidget; 341 | wnd->setLayout(layout); 342 | setCentralWidget(wnd); 343 | 344 | _form->setModel(_proxy); 345 | 346 | _form->setEnabled(false); 347 | 348 | QList ratio; 349 | ratio << 1000 << 1000; 350 | splitVert->setSizes(ratio); 351 | 352 | // TODO is this needed? 353 | // QItemSelectionModel *selectionModel = new QItemSelectionModel(_proxy); 354 | // _table->setSelectionModel(selectionModel); 355 | // _form->setSelectionModel( _table->selectionModel() ); 356 | // _form->setSelectionModel(selectionModel); 357 | // 358 | connect(_form, SIGNAL(removeSetPoint(int ,int)), 359 | this, SLOT(removeSetPoint(int ,int))); 360 | 361 | connect(_form, SIGNAL(changeFitButtonClicked(int, int)), 362 | this, SLOT(changeFitButtonClicked(int, int))); 363 | 364 | connect(_form, SIGNAL(replot(int)), 365 | this, SLOT(replot(int))); 366 | 367 | connect(_form, SIGNAL(initializeForm(int)), 368 | this, SLOT(initializeForm(int))); 369 | 370 | connect(_form, SIGNAL(submitForm(int)), 371 | this, SLOT(submitForm(int))); 372 | 373 | connect(_form->_selectRid, SIGNAL(pressed()), 374 | this, SLOT(scrollToEditedRow())); 375 | 376 | connect(this, SIGNAL(submitForm()), 377 | _form, SLOT(submit())); 378 | 379 | connect(this, SIGNAL(revertForm()), 380 | _form, SLOT(revert())); 381 | 382 | for (int i=0; i < _proxy->columnCount(); i++) 383 | _table->resizeColumnToContents(i); 384 | 385 | QHeaderView *horizontalHeader = _table->horizontalHeader(); 386 | horizontalHeader->setSectionsMovable(true); 387 | horizontalHeader->setStretchLastSection(true); 388 | horizontalHeader->setSectionResizeMode(QHeaderView::Interactive); 389 | 390 | QHeaderView *verticalHeader = _table->verticalHeader(); 391 | verticalHeader->setSectionResizeMode(QHeaderView::Fixed); 392 | verticalHeader->hide(); 393 | } 394 | 395 | /* -------------------------------------------------------------------- */ 396 | 397 | MainWindow::~MainWindow() 398 | { 399 | std::cout << __PRETTY_FUNCTION__ << std::endl; 400 | 401 | onQuit(); 402 | 403 | delete _delegate; 404 | delete _table; 405 | delete _proxy; 406 | delete _model; 407 | 408 | // re-enforce uniqueness constraint (HACK - this gets dropped during 409 | // simple "open... no edit... close" situations). 410 | QString cmd = QString("ALTER TABLE ONLY %1 ADD CONSTRAINT %2_rid_key UNIQUE (rid)").arg(DB_TABLE).arg(DB_TABLE); 411 | qDebug() << "cmd:" << cmd; 412 | QSqlQuery query(cmd); 413 | query.finish(); 414 | 415 | QSqlDatabase::database().close(); 416 | 417 | std::cout << __PRETTY_FUNCTION__ << " EXITING" << std::endl; 418 | } 419 | 420 | /* -------------------------------------------------------------------- */ 421 | 422 | void MainWindow::showHeaderMenu( const QPoint &pos ) 423 | { 424 | // clear any multiple selections made by user 425 | _table->selectionModel()->clearSelection(); 426 | 427 | QHeaderView *hH = _table->horizontalHeader(); 428 | _column = hH->logicalIndexAt(pos); 429 | std::cout << __PRETTY_FUNCTION__ << " _column: " << _column << std::endl; 430 | 431 | // Popup table menu setup... (cannot use keyboard shortcuts here) 432 | QMenu *headerMenu = new QMenu; 433 | QString column = _model->headerData(_column, Qt::Horizontal).toString(); 434 | QString filterBy = tr("Filter '%1' by...").arg(column); 435 | QString unfilterAll = tr("Unfilter all columns"); 436 | QString hideColumn = tr("Hide '%1' column").arg(column); 437 | QString showColumn = tr("Show column..."); 438 | 439 | headerMenu->addAction(filterBy, this, SLOT(filterBy())); 440 | headerMenu->addAction(unfilterAll, this, SLOT(unfilterAll())); 441 | hideColumnAction = headerMenu->addAction(hideColumn, this, SLOT(hideColumn())); 442 | showColumnAction = headerMenu->addAction(showColumn, this, SLOT(showColumn())); 443 | 444 | connect(headerMenu, SIGNAL(aboutToShow()), this, SLOT(headerMenu_aboutToShow())); 445 | 446 | // show the popup menu 447 | headerMenu->exec( _table->mapToGlobal(pos) + QPoint(20,0) ); 448 | } 449 | 450 | /* -------------------------------------------------------------------- */ 451 | 452 | void MainWindow::filterBy() 453 | { 454 | QString column = _model->headerData(_column, Qt::Horizontal).toString(); 455 | QString filterBy = tr("Filter '%1' by...").arg(column); 456 | 457 | bool ok; 458 | QString pattern = QInputDialog::getText(this, filterBy, 459 | tr("Pressing OK with nothing entered unfilters this column."), 460 | QLineEdit::Normal, _proxy->filterAt(_column), &ok); 461 | 462 | if (!ok) return; 463 | 464 | setFilter( _column, pattern); 465 | } 466 | 467 | /* -------------------------------------------------------------------- */ 468 | 469 | void MainWindow::setFilter(int column, const QString &pattern) 470 | { 471 | _proxy->setFilter(column, pattern); 472 | } 473 | 474 | /* -------------------------------------------------------------------- */ 475 | 476 | void MainWindow::unfilterAll() 477 | { 478 | _proxy->clearFilters(); 479 | } 480 | 481 | /* -------------------------------------------------------------------- */ 482 | 483 | void MainWindow::headerMenu_aboutToShow() 484 | { 485 | int nChecked = 0; 486 | foreach (QAction *action, colsGrp->actions()) 487 | if (action->isChecked()) 488 | nChecked++; 489 | 490 | hideColumnAction->setEnabled(nChecked != 1); 491 | showColumnAction->setEnabled(nChecked != colsGrp->actions().length()); 492 | } 493 | 494 | /* -------------------------------------------------------------------- */ 495 | 496 | void MainWindow::hideColumn() 497 | { 498 | _table->setColumnHidden(_column, true); 499 | QList actions = colsGrp->actions(); 500 | actions[_column]->setChecked(false); 501 | } 502 | 503 | /* -------------------------------------------------------------------- */ 504 | 505 | void MainWindow::showColumn() 506 | { 507 | QStringList allColumns, columns; 508 | foreach (QAction *action, colsGrp->actions()) { 509 | allColumns << action->text(); 510 | if (!action->isChecked()) 511 | columns << action->text(); 512 | } 513 | bool ok; 514 | QString column = QInputDialog::getItem(this, tr("Show Column..."), 515 | tr("currently hidden columns:"), 516 | columns, 0, false, &ok); 517 | 518 | if (!ok) return; 519 | 520 | int sC = allColumns.indexOf(column); 521 | 522 | _table->setColumnHidden(sC, false); 523 | QList actions = colsGrp->actions(); 524 | actions[sC]->setChecked(true); 525 | } 526 | 527 | /* -------------------------------------------------------------------- */ 528 | 529 | void MainWindow::showTableMenu( const QPoint &pos ) 530 | { 531 | // clear any multiple selections made by user 532 | _table->selectionModel()->clearSelection(); 533 | 534 | // select the row 535 | int row = _table->indexAt(pos).row(); 536 | _table->selectionModel()->select(_proxy->index(row, 0), 537 | QItemSelectionModel::Select | QItemSelectionModel::Rows); 538 | 539 | // show the popup menu 540 | tableMenu->exec( _table->mapToGlobal(pos) + QPoint(20,0) ); 541 | } 542 | 543 | /* -------------------------------------------------------------------- */ 544 | 545 | void MainWindow::removeSetPoint(int row, int index) 546 | { 547 | std::cout << __PRETTY_FUNCTION__ << " row: " << row << " index: " << index << std::endl; 548 | 549 | QStringList list_set_times = extractListFromBracedCSV(form_set_times); 550 | QStringList list_set_points = extractListFromBracedCSV(form_set_points); 551 | QStringList list_averages = extractListFromBracedCSV(form_averages); 552 | QStringList list_stddevs = extractListFromBracedCSV(form_stddevs); 553 | 554 | // mitigate old data that predates the use of set_times 555 | if (list_set_times.count() > 1) 556 | list_set_times.replace(index, ""); 557 | list_set_points.replace(index, ""); 558 | list_averages.replace(index, ""); 559 | list_stddevs.replace(index, ""); 560 | 561 | // modify a set of temporary variables setup for the form representation 562 | form_set_times = "{" + list_set_times.join(",") + "}"; 563 | form_set_points = "{" + list_set_points.join(",") + "}"; 564 | form_averages = "{" + list_averages.join(",") + "}"; 565 | form_stddevs = "{" + list_stddevs.join(",") + "}"; 566 | 567 | restrictOrderChoice(list_set_points); 568 | 569 | std::cout << "form_set_times: " << form_set_times.toStdString() << std::endl; 570 | std::cout << "form_set_points: " << form_set_points.toStdString() << std::endl; 571 | std::cout << "form_averages: " << form_averages.toStdString() << std::endl; 572 | std::cout << "form_stddevs: " << form_stddevs.toStdString() << std::endl; 573 | } 574 | 575 | /* -------------------------------------------------------------------- */ 576 | 577 | void MainWindow::restrictOrderChoice(QStringList list_set_points) 578 | { 579 | // restrict allowable polynomial order radio buttons 580 | _form->_linearRB->setEnabled(false); 581 | _form->_2ndOrderRB->setEnabled(false); 582 | _form->_3rdOrderRB->setEnabled(false); 583 | int nSP = 0; 584 | foreach(QString sp, list_set_points) 585 | if (!sp.isEmpty()) nSP++; 586 | int max_order = nSP-1; 587 | std::cout << "max_order: " << max_order << std::endl; 588 | if (max_order > 2) _form->_3rdOrderRB->setEnabled(true); 589 | if (max_order > 1) _form->_2ndOrderRB->setEnabled(true); 590 | if (max_order > 0) _form->_linearRB->setEnabled(true); 591 | } 592 | 593 | /* -------------------------------------------------------------------- */ 594 | 595 | void MainWindow::replot(int row) 596 | { 597 | std::cout << __PRETTY_FUNCTION__ << " row: " << row << std::endl; 598 | 599 | // don't plot if not already plotted 600 | QString rid = modelData(row, clm_rid); 601 | foreach (CalibrationCurve *curve, _plot->curves) 602 | if (curve->rid == rid) { 603 | unplotCalButtonClicked(row); 604 | plotCalButtonClicked(row); 605 | return; 606 | } 607 | } 608 | 609 | /* -------------------------------------------------------------------- */ 610 | 611 | void MainWindow::addRowAction(QMenu *menu, const QString &text, 612 | QActionGroup *group, QSignalMapper *mapper, 613 | int id, bool checked) 614 | { 615 | int n = 0; 616 | if (id == n++) showAnalog = checked; 617 | else if (id == n++) showBath = checked; 618 | else if (id == n++) showInstrument = checked; 619 | else if (id == n++) showTailNum[0] = checked; 620 | else if (id == n++) showTailNum[1] = checked; 621 | else if (id == n++) showTailNum[2] = checked; 622 | else if (id == n++) showTailNum[3] = checked; 623 | else if (id == n++) showCloned = checked; 624 | else if (id == n++) showRemoved = checked; 625 | else if (id == n++) showExported = checked; 626 | 627 | addAction(menu, text, group, mapper, id, checked); 628 | } 629 | 630 | /* -------------------------------------------------------------------- */ 631 | 632 | void MainWindow::addColAction(QMenu *menu, const QString &text, 633 | QActionGroup *group, QSignalMapper *mapper, 634 | int id, bool checked) 635 | { 636 | _model->setHeaderData(id, Qt::Horizontal, text); 637 | _table->setColumnHidden(id, !checked); 638 | 639 | addAction(menu, text, group, mapper, id, checked); 640 | } 641 | 642 | /* -------------------------------------------------------------------- */ 643 | 644 | void MainWindow::addAction(QMenu *menu, const QString &text, 645 | QActionGroup *group, QSignalMapper *mapper, 646 | int id, bool checked) 647 | { 648 | QAction *result = menu->addAction(text); 649 | result->setCheckable(true); 650 | result->setChecked(checked); 651 | group->addAction(result); 652 | 653 | QObject::connect(result, SIGNAL(triggered()), mapper, SLOT(map())); 654 | mapper->setMapping(result, id); 655 | } 656 | 657 | /* -------------------------------------------------------------------- */ 658 | 659 | inline QString MainWindow::modelData(int row, int col) 660 | { 661 | return _proxy->index(row, col).data().toString().trimmed(); 662 | } 663 | 664 | /* -------------------------------------------------------------------- */ 665 | 666 | QStringList MainWindow::extractListFromBracedCSV(int row, calTableHeaders key) 667 | { 668 | QString string = modelData(row, key); 669 | return extractListFromBracedCSV(string); 670 | } 671 | 672 | /* -------------------------------------------------------------------- */ 673 | 674 | QStringList MainWindow::extractListFromBracedCSV(QString string) 675 | { 676 | QStringList list; 677 | 678 | list = extractStringWithinBraced(string).split(","); 679 | 680 | return list; 681 | } 682 | 683 | /* -------------------------------------------------------------------- */ 684 | 685 | QString MainWindow::extractStringWithinBraced(QString string) 686 | { 687 | QRegExp rxBraced("\\{(.*)\\}"); 688 | QString inside; 689 | 690 | if (rxBraced.indexIn(string) != -1) 691 | inside = rxBraced.cap(1); 692 | 693 | return inside; 694 | } 695 | 696 | /* -------------------------------------------------------------------- */ 697 | 698 | void MainWindow::tableItemPressed(const QModelIndex &) 699 | { 700 | // std::cout << __PRETTY_FUNCTION__ << std::endl; 701 | 702 | // get selected row number 703 | int row = _table->selectionModel()->currentIndex().row(); 704 | 705 | _lastRid = modelData(row, clm_rid); 706 | } 707 | 708 | /* -------------------------------------------------------------------- */ 709 | 710 | void MainWindow::toggleRow(int id) 711 | { 712 | std::cout << __PRETTY_FUNCTION__ << std::endl; 713 | _table->selectionModel()->clearSelection(); 714 | 715 | // Toggle the row's hidden state 716 | int n = 0; 717 | if (id == n++) showAnalog = !showAnalog; 718 | else if (id == n++) showBath = !showBath; 719 | else if (id == n++) showInstrument = !showInstrument; 720 | else if (id == n++) showTailNum[0] = !showTailNum[0]; 721 | else if (id == n++) showTailNum[1] = !showTailNum[1]; 722 | else if (id == n++) showTailNum[2] = !showTailNum[2]; 723 | else if (id == n++) showTailNum[3] = !showTailNum[3]; 724 | else if (id == n++) showCloned = !showCloned; 725 | else if (id == n++) showRemoved = !showRemoved; 726 | else if (id == n++) showExported = !showExported; 727 | 728 | hideRows(); 729 | } 730 | 731 | /* -------------------------------------------------------------------- */ 732 | 733 | void MainWindow::hideRows() 734 | { 735 | std::cout << __PRETTY_FUNCTION__ << std::endl; 736 | 737 | QStringList cal_type_filters; 738 | if (showAnalog) cal_type_filters << "analog"; 739 | if (showBath) cal_type_filters << "bath"; 740 | if (showInstrument) cal_type_filters << "instrument"; 741 | if (cal_type_filters.isEmpty()) 742 | setFilter(clm_cal_type, ""); 743 | else 744 | setFilter(clm_cal_type, cal_type_filters.join("|")); 745 | 746 | QStringList tailNumbers; 747 | if (showTailNum[0]) tailNumbers << tailNumIdx[0]; 748 | if (showTailNum[1]) tailNumbers << tailNumIdx[1]; 749 | if (showTailNum[2]) tailNumbers << tailNumIdx[2]; 750 | if (showTailNum[3]) tailNumbers << tailNumIdx[3]; 751 | if (tailNumbers.isEmpty()) 752 | setFilter(clm_site, ""); 753 | else 754 | setFilter(clm_site, tailNumbers.join("|")); 755 | 756 | if (showCloned) 757 | setFilter(clm_pid, ""); 758 | else 759 | setFilter(clm_pid, " "); 760 | 761 | if (showRemoved) 762 | setFilter(clm_removed, ""); 763 | else 764 | setFilter(clm_removed, "0"); 765 | 766 | if (showExported) 767 | setFilter(clm_exported, ""); 768 | else 769 | setFilter(clm_exported, "0"); 770 | 771 | _proxy->refreshFilters(); 772 | } 773 | 774 | /* -------------------------------------------------------------------- */ 775 | 776 | void MainWindow::toggleColumn(int id) 777 | { 778 | _table->setColumnHidden(id, !_table->isColumnHidden(id)); 779 | _table->resizeColumnToContents(id); 780 | 781 | // reduce the size of some of the larger columns 782 | if (id == clm_comment) 783 | _table->setColumnWidth(id, 300); 784 | } 785 | 786 | /* -------------------------------------------------------------------- */ 787 | 788 | void MainWindow::setupMenus() 789 | { 790 | std::cout << __PRETTY_FUNCTION__ << std::endl; 791 | 792 | // Popup table menu setup... (cannot use keyboard shortcuts here) 793 | tableMenu = new QMenu; 794 | tableMenu->addAction(tr("Edit Cal"), this, SLOT(editCalButtonClicked())); 795 | tableMenu->addAction(tr("Plot Cal"), this, SLOT(plotCalButtonClicked())); 796 | tableMenu->addAction(tr("Unplot Cal"), this, SLOT(unplotCalButtonClicked())); 797 | tableMenu->addAction(tr("Unplot All Cals"), this, SLOT(unplotAllButtonClicked())); 798 | tableMenu->addAction(tr("Export to Cal File"), this, SLOT(exportCalButtonClicked())); 799 | tableMenu->addAction(tr("Export to CSV File"), this, SLOT(exportCsvButtonClicked())); 800 | tableMenu->addAction(tr("View Cal File"), this, SLOT(viewCalButtonClicked())); 801 | tableMenu->addAction(tr("View CSV File"), this, SLOT(viewCsvButtonClicked())); 802 | tableMenu->addAction(tr("Clone Entry"), this, SLOT(cloneButtonClicked())); 803 | tableMenu->addAction(tr("Delete Entry"), this, SLOT(removeButtonClicked())); 804 | 805 | // File menu setup... 806 | QMenu *fileMenu = new QMenu(tr("&File"), this); 807 | fileMenu->addAction(tr("&Import"), this, SLOT(importButtonClicked()), Qt::CTRL + Qt::Key_I); 808 | fileMenu->addAction(tr("&Save"), this, SLOT(saveButtonClicked()), Qt::CTRL + Qt::Key_S); 809 | fileMenu->addAction(tr("&Quit"), this, SLOT(onQuit()), Qt::CTRL + Qt::Key_Q); 810 | // connect(this, SIGNAL(aboutToQuit()), this, SLOT(onQuit())); 811 | menuBar()->addMenu(fileMenu); 812 | 813 | QMenu *viewMenu = new QMenu(tr("&View")); 814 | 815 | // View->Rows menu setup... 816 | QSignalMapper *rowsMapper = new QSignalMapper(this); 817 | connect(rowsMapper, SIGNAL(mapped(int)), this, SLOT(toggleRow(int))); 818 | 819 | QActionGroup *rowsGrp = new QActionGroup(this); 820 | rowsGrp->setExclusive(false); 821 | 822 | QMenu *rowsMenu = new QMenu(tr("&Rows")); 823 | 824 | // true == unhidden 825 | int i = 0; 826 | rowsMenu->addAction(tr("select &start of table"), this, SLOT(scrollToHome()), Qt::Key_Home); 827 | rowsMenu->addAction(tr("select &end of table"), this, SLOT(scrollToEnd()), Qt::Key_End); 828 | rowsMenu->addAction(tr("select &last clicked"), this, SLOT(scrollToLastClicked()), Qt::Key_ScrollLock); 829 | rowsMenu->addSeparator(); 830 | addRowAction(rowsMenu, tr("analog"), rowsGrp, rowsMapper, i++, true); 831 | addRowAction(rowsMenu, tr("bath"), rowsGrp, rowsMapper, i++, true); 832 | addRowAction(rowsMenu, tr("instrument"), rowsGrp, rowsMapper, i++, true); 833 | rowsMenu->addSeparator(); 834 | addRowAction(rowsMenu, tailNumIdx[0], rowsGrp, rowsMapper, i++, true); 835 | addRowAction(rowsMenu, tailNumIdx[1], rowsGrp, rowsMapper, i++, true); 836 | addRowAction(rowsMenu, tailNumIdx[2], rowsGrp, rowsMapper, i++, true); 837 | addRowAction(rowsMenu, tailNumIdx[3], rowsGrp, rowsMapper, i++, true); 838 | rowsMenu->addSeparator(); 839 | addRowAction(rowsMenu, tr("cloned"), rowsGrp, rowsMapper, i++, true); 840 | addRowAction(rowsMenu, tr("removed"), rowsGrp, rowsMapper, i++, false); 841 | addRowAction(rowsMenu, tr("exported"), rowsGrp, rowsMapper, i++, true); 842 | 843 | viewMenu->addMenu(rowsMenu); 844 | 845 | // View->Columns menu setup... 846 | QSignalMapper *colsMapper = new QSignalMapper(this); 847 | connect(colsMapper, SIGNAL(mapped(int)), this, SLOT(toggleColumn(int))); 848 | 849 | colsGrp = new QActionGroup(this); 850 | colsGrp->setExclusive(false); 851 | 852 | QMenu *colsMenu = new QMenu(tr("&Columns")); 853 | 854 | // Set up default columns, true == visible 855 | i = 0; 856 | addColAction(colsMenu, tr("Row Id"), colsGrp, colsMapper, i++, false); // rid 857 | addColAction(colsMenu, tr("Parent Id"), colsGrp, colsMapper, i++, false); // pid 858 | addColAction(colsMenu, tr("Site"), colsGrp, colsMapper, i++, true); // Site 859 | addColAction(colsMenu, tr("Pulled"), colsGrp, colsMapper, i++, false); // pulled 860 | addColAction(colsMenu, tr("Removed"), colsGrp, colsMapper, i++, false); // removed 861 | addColAction(colsMenu, tr("Exported"), colsGrp, colsMapper, i++, false); // exported 862 | addColAction(colsMenu, tr("Date"), colsGrp, colsMapper, i++, true); // cal_date 863 | addColAction(colsMenu, tr("Project"), colsGrp, colsMapper, i++, true); // project_name 864 | addColAction(colsMenu, tr("User"), colsGrp, colsMapper, i++, true); // username 865 | addColAction(colsMenu, tr("Sensor Type"), colsGrp, colsMapper, i++, false); // sensor_type 866 | addColAction(colsMenu, tr("Serial #"), colsGrp, colsMapper, i++, true); // serial_number 867 | addColAction(colsMenu, tr("Variable"), colsGrp, colsMapper, i++, true); // var_name 868 | addColAction(colsMenu, tr("DSM"), colsGrp, colsMapper, i++, false); // dsm_name 869 | addColAction(colsMenu, tr("Cal Type"), colsGrp, colsMapper, i++, true); // cal_type 870 | addColAction(colsMenu, tr("Channel"), colsGrp, colsMapper, i++, false); // channel 871 | addColAction(colsMenu, tr("GainBplr"), colsGrp, colsMapper, i++, false); // gainbplr 872 | addColAction(colsMenu, tr("ADS file name"), colsGrp, colsMapper, i++, false); // ads_file_name 873 | addColAction(colsMenu, tr("Set Times"), colsGrp, colsMapper, i++, false); ///set_times 874 | addColAction(colsMenu, tr("Set Points"), colsGrp, colsMapper, i++, false); ///set_points 875 | addColAction(colsMenu, tr("Avg Values"), colsGrp, colsMapper, i++, false); ///averages 876 | addColAction(colsMenu, tr("StdDev Values"), colsGrp, colsMapper, i++, false); ///stddevs 877 | addColAction(colsMenu, tr("Calibration"), colsGrp, colsMapper, i++, true); // cal 878 | addColAction(colsMenu, tr("Temperature"), colsGrp, colsMapper, i++, false); // temperature 879 | addColAction(colsMenu, tr("Comment"), colsGrp, colsMapper, i++, true); // comment 880 | 881 | viewMenu->addMenu(colsMenu); 882 | 883 | menuBar()->addMenu(viewMenu); 884 | } 885 | 886 | /* -------------------------------------------------------------------- */ 887 | 888 | void MainWindow::openDatabase(QString hostname) 889 | { 890 | std::cout << __PRETTY_FUNCTION__ << " hostname: " << hostname.toStdString() << std::endl; 891 | 892 | QSqlDatabase db = QSqlDatabase::addDatabase(DB_DRIVER); 893 | db.setDatabaseName(DB_NAME); 894 | db.setHostName(hostname); 895 | db.setUserName(DB_USER); 896 | if (!db.open()) { 897 | 898 | std::ostringstream ostr; 899 | ostr << tr("Failed to open calibration database on:\n").toStdString(); 900 | ostr << hostname.toStdString() << std::endl; 901 | QString errMsg = db.lastError().text(); 902 | if (errMsg.contains("FATAL: too many connections for database ")) { 903 | 904 | // determine who is currently accessing it 905 | db.setDatabaseName("postgres"); 906 | if (db.open()) { 907 | 908 | QSqlQuery query(QSqlDatabase::database()); 909 | QString cmd("SELECT usename,client_addr from pg_stat_activity WHERE datname='calibrations'"); 910 | 911 | QString currentUser = errMsg; 912 | if (query.exec(cmd) && query.first()) 913 | currentUser = "\nCurrent user: " + query.value(0).toString() 914 | + "\nLogged in from: " + query.value(1).toString(); 915 | query.finish(); 916 | ostr << tr(currentUser.toStdString().c_str()).toStdString(); 917 | } else { 918 | ostr << tr("Cannot determine who has the database open.").toStdString(); 919 | } 920 | } else { 921 | // unknown error 922 | ostr << tr(errMsg.toLatin1().data()).toStdString(); 923 | } 924 | db = QSqlDatabase(); 925 | QSqlDatabase::removeDatabase(hostname); 926 | 927 | QMessageBox::critical(0, tr("open"), ostr.str().c_str()); 928 | exit(1); 929 | } 930 | } 931 | 932 | /* -------------------------------------------------------------------- */ 933 | 934 | void MainWindow::dataChanged(const QModelIndex& old, const QModelIndex& now) 935 | { 936 | std::cout << __PRETTY_FUNCTION__ << std::endl; 937 | changeDetected = true; 938 | } 939 | 940 | /* -------------------------------------------------------------------- */ 941 | 942 | QString MainWindow::createRID() 943 | { 944 | std::cout << __PRETTY_FUNCTION__ << std::endl; 945 | 946 | // initialize a unique RID 947 | QSqlQuery query(QSqlDatabase::database()); 948 | QUuid uuid_rid; 949 | QString rid; 950 | bool unique = false; 951 | QString chk; 952 | // HACK - loop until an actual unique one is found (relative to this system) 953 | while(not unique) { 954 | uuid_rid = QUuid::createUuid(); 955 | rid = extractStringWithinBraced(uuid_rid.toString()); 956 | chk = QString("SELECT rid FROM calibrations " 957 | "WHERE rid='%1'").arg(rid); //.c_str()); 958 | qDebug() << "chk: " << chk; 959 | unique = not (query.exec(chk) && query.last()); 960 | if (not unique) 961 | qDebug() << "not unique: " << rid; 962 | } 963 | return rid; 964 | } 965 | 966 | /* -------------------------------------------------------------------- */ 967 | 968 | void MainWindow::unsubmittedFormQuery(QString title) 969 | { 970 | if (_form->_revertBtn->isEnabled()) { 971 | QMessageBox::StandardButton reply; 972 | reply = QMessageBox::question(0, title, 973 | tr("Unsubmitted changes detected in previous edit.\nSubmit them?\n"), 974 | QMessageBox::Yes | QMessageBox::No); 975 | 976 | if (reply == QMessageBox::Yes) 977 | emit submitForm(); 978 | else 979 | emit revertForm(); 980 | replot(_form->getRow()); 981 | } 982 | } 983 | 984 | /* -------------------------------------------------------------------- */ 985 | 986 | void MainWindow::onQuit() 987 | { 988 | std::cout << __PRETTY_FUNCTION__ << std::endl; 989 | unsubmittedFormQuery(tr("Close")); 990 | if (changeDetected) { 991 | QMessageBox::StandardButton reply; 992 | reply = QMessageBox::question(0, tr("Close"), 993 | tr("Save changes to database?\n"), 994 | QMessageBox::Yes | QMessageBox::Discard); 995 | 996 | if (reply == QMessageBox::Yes) 997 | saveButtonClicked(); 998 | } 999 | close(); 1000 | } 1001 | 1002 | /* -------------------------------------------------------------------- */ 1003 | 1004 | void MainWindow::importRemoteCalibTable(QString remote) 1005 | { 1006 | std::cout << __PRETTY_FUNCTION__ << " remote: " << remote.toStdString() << std::endl; 1007 | 1008 | QProcess process; 1009 | QStringList params; 1010 | QSqlQuery query(QSqlDatabase::database()); 1011 | 1012 | // Ping the remote DB server to see if it is active. 1013 | params << remote << "-i" << "1" << "-w" << "1" <<"-c" << "1"; 1014 | 1015 | if (process.execute("ping", params)) { 1016 | QMessageBox::information(0, tr("pinging calibration database"), 1017 | tr("cannot contact:\n") + remote); 1018 | return; 1019 | } 1020 | QString connectStr = QString("'host=%1 user=ads " 1021 | "dbname=%2'").arg(remote).arg(DB_NAME); 1022 | qDebug() << "connectStr:" << connectStr; 1023 | 1024 | QString insertStr = QString("INSERT INTO %1 SELECT * FROM dblink(%2, " 1025 | "'SELECT * FROM %3 WHERE pulled=''0'' ORDER BY cal_date') AS (" 1026 | "rid character(36)," 1027 | "pid character(36)," 1028 | "site character varying(20)," 1029 | "pulled character(1)," 1030 | "removed character(1)," 1031 | "exported character(1)," 1032 | "cal_date timestamp without time zone," 1033 | "project_name character varying(32)," 1034 | "username character varying(32)," 1035 | "sensor_type character varying(20)," 1036 | "serial_number character varying(20)," 1037 | "var_name character varying(20)," 1038 | "dsm_name character varying(16)," 1039 | "cal_type character varying(16)," 1040 | "channel character(2)," 1041 | "gainbplr character(2)," 1042 | "ads_file_name character varying(200)," 1043 | "set_times timestamp without time zone[]," 1044 | "set_points double precision[]," 1045 | "averages double precision[]," 1046 | "stddevs double precision[]," 1047 | "cal double precision[]," 1048 | "temperature double precision," 1049 | "comment character varying(256));").arg(DB_TABLE).arg(connectStr).arg(DB_TABLE); 1050 | 1051 | QString pulledQueryStr = QString( 1052 | "SELECT cal_date, site, cal_type, serial_number, var_name FROM %1" 1053 | " WHERE pulled=\'0\' ORDER BY cal_date").arg(DB_TABLE); 1054 | 1055 | QString updateMasterStr = QString( 1056 | "UPDATE %1 SET pulled=1 WHERE pulled=''0''").arg(DB_TABLE); 1057 | 1058 | QString updateRemoteStr = QString("SELECT * FROM dblink_exec(%1, '%2')") 1059 | .arg(connectStr).arg(updateMasterStr); 1060 | 1061 | updateMasterStr = QString("UPDATE %1 SET pulled=1").arg(DB_TABLE); 1062 | 1063 | // insert unpulled rows from remote database 1064 | qDebug() << "insertStr:" << insertStr; 1065 | qDebug() << "insert unpulled rows from remote database"; 1066 | if (!query.exec(insertStr)) { 1067 | QMessageBox::information(0, tr("remote database query failed"), 1068 | query.lastError().text()); 1069 | qDebug() << query.lastError().text(); 1070 | return; 1071 | } 1072 | query.finish(); 1073 | 1074 | // briefly show what was just pulled in 1075 | qDebug() << "pulledQueryStr:" << pulledQueryStr; 1076 | qDebug() << "briefly show what was just pulled in"; 1077 | std::ostringstream brief; 1078 | QSqlQuery briefQuery(pulledQueryStr); 1079 | QSqlRecord rec = briefQuery.record(); 1080 | 1081 | brief << "Calibration Date Site Type S/N Var Name\n"; 1082 | brief << "------------------- -------- ----- ------ ------------\n"; 1083 | while (briefQuery.next()) { 1084 | for (int i=0; isetupComboModel("project_name"); 1216 | 1217 | qDebug() << "sensor_types: " << sensor_types; 1218 | qDebug() << "serial_numbers: " << serial_numbers; 1219 | bool ok; 1220 | 1221 | do { 1222 | comment = QInputDialog::getText(this, tr("edit comment?"), 1223 | tr("current comment:\n")+comment, QLineEdit::Normal, comment, &ok); 1224 | } while ((!ok) || (comment.length() == 0)); 1225 | 1226 | do { 1227 | project_name = QInputDialog::getItem(this, tr("project name"), 1228 | comment, projects->stringList(), 0, true, &ok); 1229 | } while ((!ok) || (project_name.length() == 0)); 1230 | 1231 | qDebug() << "project_name: " << project_name; 1232 | 1233 | // setup for generating calibration fits for storage 1234 | int nSetPoints = list_set_points.count(); 1235 | double setp[nSetPoints]; 1236 | double avrg[nSetPoints]; 1237 | 1238 | for (int i=0; irecord(); 1269 | 1270 | // stuff the new record 1271 | record.setValue(clm_rid, rid); 1272 | record.setValue(clm_pid, ""); 1273 | record.setValue(clm_site, "Lab_FL1"); 1274 | record.setValue(clm_pulled, "1"); 1275 | record.setValue(clm_removed, "0"); 1276 | record.setValue(clm_exported, "0"); 1277 | record.setValue(clm_cal_date, cal_date); 1278 | record.setValue(clm_project_name, project_name); 1279 | record.setValue(clm_username, getenv("USERNAME")); 1280 | record.setValue(clm_sensor_type, sensor_types[c]); 1281 | record.setValue(clm_serial_number, serial_numbers[c]); 1282 | record.setValue(clm_var_name, "TT_BATH"); 1283 | record.setValue(clm_dsm_name, ""); 1284 | record.setValue(clm_cal_type, "bath"); 1285 | record.setValue(clm_channel, QString::number(c)); 1286 | record.setValue(clm_gainbplr, ""); 1287 | record.setValue(clm_ads_file_name, filename); 1288 | record.setValue(clm_set_times, "{}"); 1289 | record.setValue(clm_set_points, set_points); 1290 | record.setValue(clm_averages, averages[c]); 1291 | record.setValue(clm_stddevs, stddevs[c]); 1292 | record.setValue(clm_cal, cals[c]); 1293 | record.setValue(clm_temperature, "nan"); 1294 | record.setValue(clm_comment, comment); 1295 | 1296 | // TODO find a row based upon cal_date 1297 | int row = -1; // HACK insert at top of table for now 1298 | _model->insertRow(row+1); 1299 | _model->setRecord(row+1, record); 1300 | 1301 | // re-apply any filtering after inserting a new row 1302 | _lastRid = rid; 1303 | qDebug() << "_lastRid:" << _lastRid; 1304 | _proxy->refreshFilters(); 1305 | scrollToLastClicked(); 1306 | } 1307 | // Adding a new row(s) does not trigger a dataChanged event 1308 | changeDetected = true; 1309 | 1310 | return 0; 1311 | } 1312 | 1313 | /* -------------------------------------------------------------------- */ 1314 | 1315 | int MainWindow::saveButtonClicked() 1316 | { 1317 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1318 | 1319 | // update calibration database 1320 | if (_model && !_model->submitAll()) { 1321 | QString lastError = _model->lastError().text(); 1322 | QSqlDatabase::database().rollback(); 1323 | QMessageBox::warning(0, tr("save"), 1324 | tr("The database reported an error: %1") .arg(lastError)); 1325 | return 1; 1326 | } 1327 | // resort table by cal_date 1328 | std::cout << "resort table by cal_date" << std::endl; 1329 | QSqlQuery query(QSqlDatabase::database()); 1330 | 1331 | QString script( 1332 | "CREATE TABLE resorted (LIKE "); script.append(DB_TABLE); 1333 | script += " INCLUDING INDEXES);"; 1334 | script += "INSERT INTO resorted SELECT * FROM "; script.append(DB_TABLE); 1335 | script += " ORDER BY cal_date;"; 1336 | script += "DROP TABLE "; script.append(DB_TABLE); script +=";"; 1337 | script += "CREATE TABLE "; script.append(DB_TABLE); 1338 | script += " (LIKE resorted INCLUDING INDEXES);"; 1339 | script += "INSERT INTO "; script.append(DB_TABLE); 1340 | script += " SELECT * FROM resorted;"; 1341 | script += "DROP TABLE resorted;"; 1342 | 1343 | QStringList scriptQueries = script.split(';'); 1344 | 1345 | // TODO create a progress bar for this loop? 1346 | foreach (QString queryTxt, scriptQueries) { 1347 | if (queryTxt.trimmed().isEmpty()) continue; 1348 | qDebug() << "queryTxt:" << queryTxt; 1349 | if (!query.exec(queryTxt)) { 1350 | QMessageBox::warning(0, tr("resort"), 1351 | query.lastError().text()); 1352 | return 1; 1353 | } 1354 | query.finish(); 1355 | } 1356 | changeDetected = false; 1357 | return 0; 1358 | } 1359 | 1360 | /* -------------------------------------------------------------------- */ 1361 | 1362 | void MainWindow::scrollToHome() 1363 | { 1364 | QModelIndex index = _proxy->index(0, 0); 1365 | _table->scrollTo(index, QAbstractItemView::EnsureVisible); 1366 | _table->selectRow(index.row()); 1367 | } 1368 | 1369 | /* -------------------------------------------------------------------- */ 1370 | 1371 | void MainWindow::scrollToEnd() 1372 | { 1373 | QModelIndex index = _proxy->index(_proxy->rowCount()-1, 0); 1374 | _table->scrollTo(index, QAbstractItemView::EnsureVisible); 1375 | _table->selectRow(index.row()); 1376 | } 1377 | 1378 | /* -------------------------------------------------------------------- */ 1379 | 1380 | void MainWindow::scrollToLastClicked() 1381 | { 1382 | scrollToRid(_lastRid); 1383 | } 1384 | 1385 | /* -------------------------------------------------------------------- */ 1386 | 1387 | void MainWindow::scrollToEditedRow() 1388 | { 1389 | scrollToRid(_editRid); 1390 | } 1391 | 1392 | /* -------------------------------------------------------------------- */ 1393 | 1394 | void MainWindow::scrollToRid(QString rid) 1395 | { 1396 | QModelIndex index; 1397 | for (int row = 0; row < _proxy->rowCount(); row++) 1398 | if (modelData(row, clm_rid) == rid) 1399 | index = _proxy->index(row, 0); 1400 | 1401 | if (!index.isValid()) { 1402 | QMessageBox::warning(0, tr("hidden"), 1403 | tr("Cannot reselect, row is hidden.")); 1404 | return; 1405 | } 1406 | _table->scrollTo(index, QAbstractItemView::EnsureVisible); 1407 | _table->selectRow(index.row()); 1408 | } 1409 | 1410 | /* -------------------------------------------------------------------- */ 1411 | 1412 | void MainWindow::editCalButtonClicked() 1413 | { 1414 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1415 | 1416 | unsubmittedFormQuery(tr("Edit")); 1417 | 1418 | // get selected row number 1419 | int row = _table->selectionModel()->currentIndex().row(); 1420 | 1421 | _editRid = modelData(row, clm_rid); 1422 | if (!_editable.contains(_editRid)) { 1423 | _form->setEnabled(false); 1424 | QMessageBox::warning(0, tr("edit"), 1425 | tr("Cannot edit content, clone it first then edit that line.")); 1426 | } 1427 | else 1428 | _form->setEnabled(true); 1429 | 1430 | // set the form's data widget mapper to the selected row 1431 | _form->setRow(row); 1432 | 1433 | initializeForm(row); 1434 | } 1435 | 1436 | /* -------------------------------------------------------------------- */ 1437 | 1438 | void MainWindow::initializeForm(int row) 1439 | { 1440 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1441 | 1442 | // populate the form view with elements from this row 1443 | QString rid = modelData(row, clm_rid); 1444 | QString pid = modelData(row, clm_pid); 1445 | QString site = modelData(row, clm_site); 1446 | QString var_name = modelData(row, clm_var_name); 1447 | QString cal_date = modelData(row, clm_cal_date); 1448 | 1449 | std::cout << "site: " << site.toStdString() << std::endl; 1450 | std::cout << "var_name: " << var_name.toStdString() << std::endl; 1451 | std::cout << "cal_date: " << cal_date.toStdString() << std::endl; 1452 | 1453 | QSqlQuery query(QSqlDatabase::database()); 1454 | QString cmd("SELECT cal FROM "); cmd.append(DB_TABLE); 1455 | cmd += " WHERE site='" + site + 1456 | "' AND var_name='" + var_name + 1457 | "' AND cal_date<'" + cal_date + 1458 | "' ORDER BY cal_date DESC LIMIT 1"; 1459 | qDebug() << "cmd:" << cmd; 1460 | 1461 | QString prevString = "{}"; 1462 | if (query.exec(cmd) && query.first()) 1463 | prevString = query.value(0).toString(); 1464 | query.finish(); 1465 | 1466 | form_set_times = modelData(row, clm_set_times); 1467 | form_set_points = modelData(row, clm_set_points); 1468 | form_averages = modelData(row, clm_averages); 1469 | form_stddevs = modelData(row, clm_stddevs); 1470 | form_cal = modelData(row, clm_cal); 1471 | 1472 | QStringList list_set_times = extractListFromBracedCSV(form_set_times); 1473 | QStringList list_set_points = extractListFromBracedCSV(form_set_points); 1474 | QStringList list_averages = extractListFromBracedCSV(form_averages); 1475 | QStringList list_stddevs = extractListFromBracedCSV(form_stddevs); 1476 | QStringList list_cal = extractListFromBracedCSV(form_cal); 1477 | QStringList list_old = extractListFromBracedCSV( prevString ); 1478 | 1479 | if (pid.length()) 1480 | restrictOrderChoice(list_set_points); 1481 | 1482 | int i = 0; 1483 | std::vector _cals; 1484 | foreach( QString coeff, list_cal) { 1485 | double iCd = coeff.toDouble(); 1486 | _cals.push_back(iCd); 1487 | _form->_currCalCList[i++]->setText(coeff); 1488 | } 1489 | QList curveFitButtons = _form->_curveFitGroup->buttons(); 1490 | foreach (QAbstractButton *button, curveFitButtons) 1491 | button->setChecked(false); 1492 | 1493 | curveFitButtons[i-2]->setChecked(true); 1494 | 1495 | for (; i<4;) 1496 | _form->_currCalCList[i++]->setText(""); 1497 | 1498 | i = 0; 1499 | foreach( QString coeff, list_old) 1500 | _form->_prevCalCList[i++]->setText(coeff); 1501 | 1502 | for (; i<4;) 1503 | _form->_prevCalCList[i++]->setText(""); 1504 | 1505 | i = 0; 1506 | QStringListIterator iT(list_set_times); 1507 | QStringListIterator iP(list_set_points); 1508 | QStringListIterator iA(list_averages); 1509 | QStringListIterator iD(list_stddevs); 1510 | while (iP.hasNext()) { 1511 | 1512 | // skip over removed set points 1513 | QString sp = iP.next(); 1514 | if ( sp.isEmpty() ) { 1515 | if (list_set_times.count() > 1) 1516 | iT.next(); 1517 | iA.next(); 1518 | iD.next(); 1519 | _form->_setDateTimeList[i]->setText(""); 1520 | _form->_setPointList[i]->setText( ""); 1521 | _form->_inputList[i]->setText( ""); 1522 | _form->_stdDevList[i]->setText( ""); 1523 | _form->_appliedList[i]->setText( ""); 1524 | _form->_delButtonList[i]->setEnabled(false); 1525 | i++; 1526 | continue; 1527 | } 1528 | // mitigate old data that predates the use of set_times 1529 | QString dt; 1530 | if (list_set_times.count() > 1) 1531 | dt = iT.next(); 1532 | 1533 | // calculate applied value 1534 | QString average = iA.next(); 1535 | double voltage = average.toDouble(); 1536 | QString applied = QString::number(numeric::PolyEval(_cals, voltage)); 1537 | 1538 | _form->_tableWidget->setRowHidden(i, false); 1539 | 1540 | _form->_setDateTimeList[i]->setText(dt.remove(QChar('"'))); 1541 | _form->_setPointList[i]->setText( sp); 1542 | _form->_inputList[i]->setText( average); 1543 | _form->_stdDevList[i]->setText( iD.next()); 1544 | _form->_appliedList[i]->setText( applied); 1545 | _form->_delButtonList[i]->setEnabled(true); 1546 | i++; 1547 | } 1548 | for (; i_tableWidget->setRowHidden(i, true); 1550 | 1551 | _form->_tableWidget->resizeColumnsToContents(); 1552 | 1553 | // stuff QLineEdit elements 1554 | _form-> _ridTxt->setText( rid ); 1555 | _form-> _platformTxt->setText( site ); 1556 | 1557 | // stuff QComboBox elements 1558 | _form-> _projTxt->setCurrentIndex( _form-> _projTxt->findText( modelData(row, clm_project_name ) ) ); 1559 | _form-> _userTxt->setCurrentIndex( _form-> _userTxt->findText( modelData(row, clm_username ) ) ); 1560 | _form->_sensorTypeTxt->setCurrentIndex( _form->_sensorTypeTxt->findText( modelData(row, clm_sensor_type ) ) ); 1561 | _form-> _serialNumTxt->setCurrentIndex( _form-> _serialNumTxt->findText( modelData(row, clm_serial_number ) ) ); 1562 | _form-> _dsmTxt->setCurrentIndex( _form-> _dsmTxt->findText( modelData(row, clm_dsm_name ) ) ); 1563 | _form-> _calTypeTxt->setCurrentIndex( _form-> _calTypeTxt->findText( modelData(row, clm_cal_type ) ) ); 1564 | _form-> _addrTxt->setCurrentIndex( _form-> _addrTxt->findText( modelData(row, clm_channel ) ) ); 1565 | _form-> _gainbplrTxt->setCurrentIndex( _form-> _gainbplrTxt->findText( modelData(row, clm_gainbplr ) ) ); 1566 | } 1567 | 1568 | /* -------------------------------------------------------------------- */ 1569 | 1570 | void MainWindow::submitForm(int row) 1571 | { 1572 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1573 | 1574 | _proxy->setData(_proxy->index(row, clm_set_times), form_set_times); 1575 | _proxy->setData(_proxy->index(row, clm_set_points), form_set_points); 1576 | _proxy->setData(_proxy->index(row, clm_averages), form_averages); 1577 | _proxy->setData(_proxy->index(row, clm_stddevs), form_stddevs); 1578 | _proxy->setData(_proxy->index(row, clm_cal), form_cal); 1579 | 1580 | changeDetected = true; 1581 | } 1582 | 1583 | /* -------------------------------------------------------------------- */ 1584 | 1585 | void MainWindow::plotCalButtonClicked() 1586 | { 1587 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1588 | 1589 | // get selected row number 1590 | int row = _table->selectionModel()->currentIndex().row(); 1591 | plotCalButtonClicked(row); 1592 | } 1593 | 1594 | /* -------------------------------------------------------------------- */ 1595 | 1596 | void MainWindow::plotCalButtonClicked(int row) 1597 | { 1598 | std::cout << __PRETTY_FUNCTION__ << " row: " << row << std::endl; 1599 | 1600 | // don't plot if already plotted 1601 | QString rid = modelData(row, clm_rid); 1602 | foreach (CalibrationCurve *curve, _plot->curves) 1603 | if (curve->rid == rid) return; 1604 | 1605 | QString label = modelData(row, clm_var_name) + " " + modelData(row, clm_cal_date); 1606 | std::cout << "label: " << label.toStdString() << std::endl; 1607 | 1608 | QwtLegend *legend = new QwtLegend; 1609 | 1610 | #if QWT_VERSION >= 0x060100 1611 | legend->setDefaultItemMode(QwtLegendData::Checkable); 1612 | #else 1613 | legend->setItemMode(QwtLegend::CheckableItem); 1614 | #endif 1615 | 1616 | _plot->qwtPlot->insertLegend(legend, QwtPlot::BottomLegend); 1617 | 1618 | QStringList list_set_points; 1619 | QStringList list_averages; 1620 | QStringList list_cal; 1621 | 1622 | // if row is currently being edited in the form then plot the unsubmitted 1623 | // data instead on the model data 1624 | if (rid == _editRid) { 1625 | list_set_points = extractListFromBracedCSV(form_set_points); 1626 | list_averages = extractListFromBracedCSV(form_averages); 1627 | list_cal = extractListFromBracedCSV(form_cal); 1628 | } else { 1629 | list_set_points = extractListFromBracedCSV(row, clm_set_points); 1630 | list_averages = extractListFromBracedCSV(row, clm_averages); 1631 | list_cal = extractListFromBracedCSV(row, clm_cal); 1632 | } 1633 | if (list_set_points.isEmpty()) return; 1634 | if (list_averages.isEmpty()) return; 1635 | if (list_cal.isEmpty()) return; 1636 | 1637 | // fetch a new color to plot in 1638 | if ( _plot->colors.empty()) { 1639 | QMessageBox::information(0, tr("notice"), 1640 | tr("Ran out of unique colors to plot in!\nCurrently only 12 colors are specified.")); 1641 | return; 1642 | } 1643 | QColor color = _plot->colors.front(); 1644 | std::cout << "pull color: " << color.red() << " " << color.green() << " " << color.blue() << std::endl; 1645 | _plot->colors.pop_front(); 1646 | 1647 | QColor actColor = color; 1648 | QColor fitColor = color.lighter(150); 1649 | QColor mrkColor = color.darker(150); 1650 | 1651 | std::cout << "actColor: " << actColor.red() << " " << actColor.green() << " " << actColor.blue() << std::endl; 1652 | std::cout << "fitColor: " << fitColor.red() << " " << fitColor.green() << " " << fitColor.blue() << std::endl; 1653 | std::cout << "mrkColor: " << mrkColor.red() << " " << mrkColor.green() << " " << mrkColor.blue() << std::endl; 1654 | 1655 | // highlight the row in the table to match the color 1656 | _delegate->highlightRow(rid, actColor); 1657 | 1658 | std::vector _cals; 1659 | foreach( QString coeff, list_cal) 1660 | _cals.push_back( coeff.toDouble() ); 1661 | 1662 | // Run 80 points from -10 to 10 Vdc. 1663 | int nPoints = 80; 1664 | /* 1665 | double step = 20.0 / nPoints, vdc = -10.0; 1666 | */ 1667 | QStringList aList; 1668 | if ( modelData(row, clm_cal_type) == "bath") 1669 | aList = list_set_points; 1670 | else 1671 | aList = list_averages; 1672 | QStringListIterator iX(aList); 1673 | 1674 | float max = -9999.9; 1675 | float min = 9999.9; 1676 | std::cout << "min: " << min << " max: " << max << std::endl; 1677 | while (iX.hasNext()) { 1678 | QString iPs = iX.next(); 1679 | if (!iPs.isEmpty()) { 1680 | float iPf = iPs.toFloat(); 1681 | std::cout << "val: " << iPf << std::endl; 1682 | if (max < iPf) max = iPf; 1683 | if (min > iPf) min = iPf; 1684 | } 1685 | } 1686 | std::cout << "min: " << min << " max: " << max << std::endl; 1687 | double step = (max-min) / nPoints, vdc = min; 1688 | 1689 | std::vector x, y; 1690 | x.resize(nPoints); 1691 | y.resize(nPoints); 1692 | 1693 | for (int i = 0; i < nPoints; ++i) 1694 | { 1695 | x[i] = vdc; 1696 | y[i] = numeric::PolyEval(_cals, x[i]); 1697 | vdc += step; 1698 | } 1699 | 1700 | QVector* actual = new QVector; 1701 | 1702 | QStringListIterator iA(list_averages); 1703 | QStringListIterator iP(list_set_points); 1704 | while (iA.hasNext() && iP.hasNext()) 1705 | { 1706 | // skip over removed set points 1707 | QString sp = iP.next(); 1708 | if ( sp.isEmpty() ) { 1709 | iA.next(); 1710 | continue; 1711 | } 1712 | float iAf = iA.next().toFloat(); 1713 | float iPf = sp .toFloat(); 1714 | std::cout << iAf << " " << iPf << std::endl; 1715 | actual->push_back(QPointF(iAf, iPf)); 1716 | } 1717 | QwtText actLabel("actual "+label); 1718 | QwtText fitLabel("fitted "+label); 1719 | 1720 | actLabel.setColor(actColor); 1721 | fitLabel.setColor(fitColor); 1722 | 1723 | CalibrationCurve* curve = new CalibrationCurve; 1724 | curve->rid = rid; 1725 | curve->actual = new QwtPlotCurve(actLabel); 1726 | curve->fitted = new QwtPlotCurve(fitLabel); 1727 | 1728 | QPen actPen(actColor); actPen.setWidth(1); 1729 | curve->actual->setPen(actPen); 1730 | 1731 | QPen fitPen(fitColor); fitPen.setWidth(1); 1732 | curve->fitted->setPen(fitPen); 1733 | 1734 | #if QWT_VERSION >= 0x060000 1735 | QwtSymbol *cross = new QwtSymbol(QwtSymbol::Cross, mrkColor, mrkColor, QSize(5, 5)); 1736 | curve->actual->setSymbol(cross); 1737 | #else 1738 | QwtSymbol cross(QwtSymbol::Cross, mrkColor, mrkColor, QSize(5, 5)); 1739 | curve->actual->setSymbol(cross); 1740 | #endif 1741 | 1742 | #if QWT_VERSION >= 0x060000 1743 | QwtPointSeriesData* actData = new QwtPointSeriesData; 1744 | actData->setSamples(*actual); 1745 | 1746 | curve->actual->setData(actData); 1747 | if ( modelData(row, clm_cal_type) == "bath") 1748 | curve->fitted->setSamples(&y[0], &x[0], nPoints); 1749 | else 1750 | curve->fitted->setSamples(&x[0], &y[0], nPoints); 1751 | #else 1752 | // TODO: figure out how to implement the above in version 5. Until then, issue a warning. 1753 | #warning "Source is not compatible with version 5 of QWT" 1754 | #endif 1755 | 1756 | curve->actual->attach(_plot->qwtPlot); 1757 | curve->fitted->attach(_plot->qwtPlot); 1758 | _plot->curves.append(curve); 1759 | 1760 | // TODO implement zooming via rubberband selection 1761 | // QRectF region = actData->boundingRect(); 1762 | QwtPlotZoomer *zoomer = new QwtPlotZoomer(_plot->qwtPlot->canvas()); 1763 | zoomer->zoom(0); 1764 | // _plot->qwtPlot->replot(); 1765 | } 1766 | 1767 | /* -------------------------------------------------------------------- */ 1768 | 1769 | void MainWindow::unplotCalButtonClicked() 1770 | { 1771 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1772 | 1773 | // get selected row number 1774 | int row = _table->selectionModel()->currentIndex().row(); 1775 | unplotCalButtonClicked(row); 1776 | } 1777 | 1778 | /* -------------------------------------------------------------------- */ 1779 | 1780 | void MainWindow::unplotAllButtonClicked() 1781 | { 1782 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1783 | 1784 | foreach (CalibrationCurve *curve, _plot->curves) { 1785 | curve->actual->detach(); 1786 | curve->fitted->detach(); 1787 | _plot->curves.removeOne(curve); 1788 | _delegate->unhighlightRow(curve->rid); 1789 | } 1790 | _plot->dropColors(); 1791 | _plot->setupColors(); 1792 | _plot->qwtPlot->replot(); 1793 | } 1794 | 1795 | /* -------------------------------------------------------------------- */ 1796 | 1797 | void MainWindow::unplotCalButtonClicked(int row) 1798 | { 1799 | std::cout << __PRETTY_FUNCTION__ << " row: " << row << std::endl; 1800 | 1801 | QString rid = modelData(row, clm_rid); 1802 | std::cout << "selected rid: " << rid.toStdString() << std::endl; 1803 | 1804 | foreach (CalibrationCurve *curve, _plot->curves) { 1805 | std::cout << "iterated rid: " << curve->rid.toStdString() << std::endl; 1806 | if (curve->rid == rid) { 1807 | QColor color = curve->actual->pen().color(); 1808 | std::cout << "push color: " << color.red() << " " << color.green() << " " << color.blue() << std::endl; 1809 | _plot->colors.push_front(color); 1810 | curve->actual->detach(); 1811 | curve->fitted->detach(); 1812 | _plot->curves.removeOne(curve); 1813 | } 1814 | } 1815 | _plot->qwtPlot->replot(); 1816 | _delegate->unhighlightRow(rid); 1817 | } 1818 | 1819 | /* -------------------------------------------------------------------- */ 1820 | 1821 | void MainWindow::exportCalButtonClicked() 1822 | { 1823 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1824 | 1825 | // get selected row number 1826 | int row = _table->selectionModel()->currentIndex().row(); 1827 | 1828 | // don't export anything that was removed 1829 | QString removed = modelData(row, clm_removed); 1830 | if (removed == "1") { 1831 | QMessageBox::information(0, tr("notice"), 1832 | tr("You cannot export a calibration from a removed row.")); 1833 | return; 1834 | } 1835 | // get the cal_type from the selected row 1836 | QString cal_type = modelData(row, clm_cal_type); 1837 | std::cout << "cal_type: " << cal_type.toStdString() << std::endl; 1838 | 1839 | if (cal_type == "instrument") 1840 | exportInstrument(row); 1841 | 1842 | if (cal_type == "analog") 1843 | exportAnalog(row); 1844 | 1845 | if (cal_type == "bath") 1846 | exportBath(row); 1847 | 1848 | // immediately view what's exported 1849 | viewCalButtonClicked(); 1850 | } 1851 | 1852 | /* -------------------------------------------------------------------- */ 1853 | 1854 | void MainWindow::exportCsvButtonClicked() 1855 | { 1856 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1857 | 1858 | // get selected row number 1859 | int row = _table->selectionModel()->currentIndex().row(); 1860 | 1861 | QStringList list_set_points = extractListFromBracedCSV(row, clm_set_points); 1862 | if (list_set_points.isEmpty()) return; 1863 | QStringList list_averages = extractListFromBracedCSV(row, clm_averages); 1864 | if (list_averages.isEmpty()) return; 1865 | 1866 | std::ostringstream ostr; 1867 | ostr << "setPoint,Average\n"; 1868 | 1869 | QStringListIterator iP(list_set_points); 1870 | QStringListIterator iA(list_averages); 1871 | while (iP.hasNext() && iA.hasNext()) 1872 | ostr << iP.next().toStdString() << "," 1873 | << iA.next().toStdString() << "\n"; 1874 | 1875 | QString site = modelData(row, clm_site); 1876 | QString var_name = modelData(row, clm_var_name); 1877 | 1878 | QString filename = csvfile_dir; 1879 | filename += site + "_" + var_name + ".csv"; 1880 | 1881 | saveFileAs(filename, tr("CSV files")+" (*.dat)", ostr.str()); 1882 | } 1883 | 1884 | /* -------------------------------------------------------------------- */ 1885 | 1886 | void MainWindow::viewCalButtonClicked() 1887 | { 1888 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1889 | 1890 | // get selected row number 1891 | int row = _table->selectionModel()->currentIndex().row(); 1892 | 1893 | // get the cal_type from the selected row 1894 | QString cal_type = modelData(row, clm_cal_type); 1895 | QString sensor_type = modelData(row, clm_sensor_type); 1896 | 1897 | QString filename = calfile_dir; 1898 | 1899 | if (cal_type == "instrument") { 1900 | QString var_name = modelData(row, clm_var_name); 1901 | 1902 | // get the site from the selected row 1903 | QString site = modelData(row, clm_site); 1904 | 1905 | #ifndef SANDBOX 1906 | filename += QString("Engineering/") + site + QString("/"); 1907 | #endif 1908 | filename += var_name + ".dat"; 1909 | } 1910 | else if (cal_type == "analog") { 1911 | // extract the serial_number of the A2D card from the current row 1912 | QString serial_number = modelData(row, clm_serial_number); 1913 | 1914 | #ifndef SANDBOX 1915 | filename += QString("A2D/"); 1916 | #endif 1917 | if (sensor_type == "analog_diamond"){ 1918 | filename += QString("DMMAT/"); 1919 | } 1920 | filename += QString("A2D") + QString(serial_number) + QString(".dat"); 1921 | } 1922 | else if (cal_type == "bath") { 1923 | QString project_name = modelData(row, clm_project_name); 1924 | QString serial_number = modelData(row, clm_serial_number); 1925 | 1926 | filename = csvfile_dir; 1927 | filename += project_name + QString("_") + sensor_type + QString("_") + serial_number + QString(".bath"); 1928 | } 1929 | else 1930 | return; 1931 | 1932 | viewFile(filename); 1933 | } 1934 | 1935 | /* -------------------------------------------------------------------- */ 1936 | 1937 | void MainWindow::viewCsvButtonClicked() 1938 | { 1939 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1940 | 1941 | // get selected row number 1942 | int row = _table->selectionModel()->currentIndex().row(); 1943 | 1944 | QString site = modelData(row, clm_site); 1945 | QString var_name = modelData(row, clm_var_name); 1946 | 1947 | QString filename = csvfile_dir; 1948 | filename += site + "_" + var_name + ".csv"; 1949 | 1950 | viewFile(filename); 1951 | } 1952 | 1953 | /* -------------------------------------------------------------------- */ 1954 | 1955 | void MainWindow::viewFile(QString filename) 1956 | { 1957 | qDebug() << "filename: " << filename; 1958 | QFile file(filename); 1959 | if (file.open(QFile::ReadOnly)) { 1960 | QTextStream in(&file); 1961 | const QString data = in.readAll(); 1962 | viewText(data, filename); 1963 | } 1964 | else 1965 | QMessageBox::information(0, tr("notice"), 1966 | tr("missing:\n") + filename + tr("\n\nNot found.")); 1967 | } 1968 | 1969 | /* -------------------------------------------------------------------- */ 1970 | 1971 | void MainWindow::viewText(QString text, QString title) 1972 | { 1973 | ViewTextDialog viewTextDialog; 1974 | viewTextDialog.setWindowTitle(title); 1975 | // viewTextDialog.setWindowTitle(QApplication::translate("Ui::ViewTextDialog", 1976 | // title.toStdString().c_str(), 0, QApplication::UnicodeUTF8)); 1977 | viewTextDialog.setContents(&text); 1978 | viewTextDialog.exec(); 1979 | } 1980 | 1981 | /* -------------------------------------------------------------------- */ 1982 | 1983 | void MainWindow::exportInstrument(int row) 1984 | { 1985 | std::cout << __PRETTY_FUNCTION__ << std::endl; 1986 | 1987 | QString var_name = modelData(row, clm_var_name); 1988 | std::cout << "var_name: " << var_name.toStdString() << std::endl; 1989 | 1990 | // extract the site of the instrument from the current row 1991 | QString site = modelData(row, clm_site); 1992 | std::cout << "site: " << site.toStdString() << std::endl; 1993 | 1994 | // extract the cal_date from the current row 1995 | QString cal_date = modelData(row, clm_cal_date); 1996 | QDateTime ct = QDateTime::fromString(cal_date, Qt::ISODate); 1997 | 1998 | // extract the cal coefficients from the selected row 1999 | QStringList list_cal = extractListFromBracedCSV(row, clm_cal); 2000 | if (list_cal.isEmpty()) return; 2001 | 2002 | // record results to the device's CalFile 2003 | std::ostringstream ostr; 2004 | ostr << std::endl; 2005 | 2006 | ostr << ct.toString("yyyy MMM dd HH:mm:ss").toStdString(); 2007 | qDebug() << "ct: " << ct.toString("yyyy MMM dd HH:mm:ss"); 2008 | 2009 | foreach (QString coeff, list_cal) 2010 | ostr << " " << std::setw(9) << coeff.toStdString(); 2011 | 2012 | ostr << " # " << QString(getenv("USER")).toStdString(); 2013 | QString sernum = modelData(row, clm_serial_number); 2014 | ostr << " S/N:" << sernum.toStdString(); 2015 | QString projname = modelData(row, clm_project_name); 2016 | ostr << " " << projname.toStdString(); 2017 | QString comment = modelData(row, clm_comment); 2018 | ostr << ":" << comment.toStdString(); 2019 | ostr << std::endl; 2020 | 2021 | QString filename = calfile_dir; 2022 | 2023 | #ifndef SANDBOX 2024 | filename += QString("Engineering/") + site + "/"; 2025 | #endif 2026 | filename += var_name + ".dat"; 2027 | 2028 | exportCalFile(filename, ostr.str()); 2029 | } 2030 | 2031 | /* -------------------------------------------------------------------- */ 2032 | 2033 | void MainWindow::exportAnalog(int row) 2034 | { 2035 | std::cout << __PRETTY_FUNCTION__ << std::endl; 2036 | 2037 | size_t nChannels = 8; // Default to NCAR A/D for now. 2038 | int Mask = 0x00ff; 2039 | QStringList cals[16]; 2040 | 2041 | /* Extract the sensor_type so we can determine which analog card. 2042 | * - NCAR A/D has 8 channels and cal file will have linear cal 2043 | * - gpDAQ has 4 channels and cal file will have 3rd order cal 2044 | * - Diamond has 16 channels and cal file will have 3rd order cal 2045 | */ 2046 | QString sensor_type = modelData(row, clm_sensor_type); 2047 | std::cout << "sensor_type: " << sensor_type.toStdString() << std::endl; 2048 | if (sensor_type == "analog_gpDAQ") { 2049 | Mask = 0x000f; 2050 | nChannels = 4; 2051 | } 2052 | else 2053 | if (sensor_type == "analog_diamond") { 2054 | Mask = 0xffff; 2055 | nChannels = 16; 2056 | } 2057 | 2058 | // extract the serial_number of the A2D card from the current row 2059 | QString serial_number = modelData(row, clm_serial_number); 2060 | std::cout << "serial_number: " << serial_number.toStdString() << std::endl; 2061 | 2062 | QString gainbplr = modelData(row, clm_gainbplr); 2063 | std::cout << "gainbplr: " << gainbplr.toStdString() << std::endl; 2064 | 2065 | // extract the cal_date from the current row 2066 | QString cal_date = modelData(row, clm_cal_date); 2067 | QDateTime ut, ct = QDateTime::fromString(cal_date, Qt::ISODate); 2068 | 2069 | int channel = modelData(row, clm_channel).toInt(); 2070 | cals[channel] = extractListFromBracedCSV(modelData(row, clm_cal)); 2071 | int chnMask = 1 << channel; 2072 | 2073 | // search for the other channels and continue extracting coefficients... 2074 | int topRow = row; 2075 | do { 2076 | if (--topRow < 0) break; 2077 | if ("1" == modelData(topRow, clm_removed)) continue; 2078 | if (serial_number != modelData(topRow, clm_serial_number)) break; 2079 | if (sensor_type != modelData(topRow, clm_sensor_type)) break; 2080 | if ("analog" != modelData(topRow, clm_cal_type)) break; 2081 | if (gainbplr != modelData(topRow, clm_gainbplr)) break; 2082 | 2083 | cal_date = modelData(topRow, clm_cal_date); 2084 | ut = QDateTime::fromString(cal_date, Qt::ISODate); 2085 | qDebug() << "| " << ut << " - " << ct << " | = " 2086 | << std::abs(ut.secsTo(ct)) 2087 | << " > " << 120*60*60; 2088 | // Calibrations should be within 5 days of one another 2089 | if (std::abs(ut.secsTo(ct)) > 120*60*60) break; 2090 | 2091 | channel = modelData(topRow, clm_channel).toInt(); 2092 | cals[channel] = extractListFromBracedCSV(modelData(topRow, clm_cal)); 2093 | chnMask |= 1 << channel; 2094 | 2095 | // select the rows of what's found 2096 | _table->selectionModel()->select(_proxy->index(topRow, 0), 2097 | QItemSelectionModel::Select | QItemSelectionModel::Rows); 2098 | } while (true); 2099 | topRow++; 2100 | 2101 | int numRows = _proxy->rowCount() - 1; 2102 | int btmRow = row; 2103 | do { 2104 | if (++btmRow > numRows) break; 2105 | if ("1" == modelData(btmRow, clm_removed)) continue; 2106 | if (serial_number != modelData(btmRow, clm_serial_number)) break; 2107 | if (sensor_type != modelData(topRow, clm_sensor_type)) break; 2108 | if ("analog" != modelData(btmRow, clm_cal_type)) break; 2109 | if (gainbplr != modelData(btmRow, clm_gainbplr)) break; 2110 | 2111 | cal_date = modelData(btmRow, clm_cal_date); 2112 | ut = QDateTime::fromString(cal_date, Qt::ISODate); 2113 | qDebug() << "| " << ut << " - " << ct << " | = " 2114 | << std::abs(ut.secsTo(ct)) 2115 | << " > " << 120*60*60; 2116 | // Calibrations should be within 3 days of one another 2117 | if (std::abs(ut.secsTo(ct)) > 120*60*60) break; 2118 | 2119 | channel = modelData(btmRow, clm_channel).toInt(); 2120 | cals[channel] = extractListFromBracedCSV(modelData(btmRow, clm_cal)); 2121 | chnMask |= 1 << channel; 2122 | 2123 | // select the rows of what's found 2124 | _table->selectionModel()->select(_proxy->index(btmRow, 0), 2125 | QItemSelectionModel::Select | QItemSelectionModel::Rows); 2126 | } while (true); 2127 | btmRow--; 2128 | 2129 | // complain if the found selection is discontiguous or an undistinct set of nChannels 2130 | size_t numFound = btmRow - topRow + 1; 2131 | std::cout << "chnMask: 0x" << std::hex << chnMask << std::dec << std::endl; 2132 | std::cout << "numFound: " << numFound << std::endl; 2133 | if ((chnMask != Mask) || (numFound != nChannels)) { 2134 | QString msg = QString("Discontiguous or an undistinct selection found.\n\nYou need %1 channels selected to generate a calibration dat file!").arg(nChannels); 2135 | QMessageBox::information(0, tr("notice"), msg); 2136 | return; 2137 | } 2138 | // extract temperature from the btmRow 2139 | QString temperature = modelData(btmRow, clm_temperature); 2140 | 2141 | // extract cal_date and comment from channel 0 2142 | int chn0idx = -1; 2143 | QModelIndexList rowList = _table->selectionModel()->selectedRows(); 2144 | foreach (QModelIndex rowIndex, rowList) { 2145 | chn0idx = rowIndex.row(); 2146 | if (modelData(chn0idx, clm_channel) == "0") 2147 | break; 2148 | } 2149 | cal_date = modelData(chn0idx, clm_cal_date); 2150 | ut = QDateTime::fromString(cal_date, Qt::ISODate); 2151 | QString comment = modelData(chn0idx, clm_comment); 2152 | 2153 | // record results to the device's CalFile 2154 | std::ostringstream ostr; 2155 | ostr << std::endl; 2156 | ostr << "# temperature: " << temperature.toStdString() << std::endl; 2157 | ostr << "# Comment: " << comment.toStdString() << std::endl; 2158 | ostr << "# Date Gain Bipolar"; 2159 | for (size_t ix = 0; ix < nChannels; ix++) 2160 | { 2161 | ostr << " CH" << ix << "-off CH" << ix << "-slope"; 2162 | if (sensor_type == "analog_gpDAQ" || sensor_type == "analog_diamond") 2163 | ostr << " C2 C3"; 2164 | } 2165 | ostr << std::endl; 2166 | 2167 | ostr << ut.toString("yyyy MMM dd HH:mm:ss").toStdString(); 2168 | qDebug() << "ut: " << ut.toString("yyyy MMM dd HH:mm:ss"); 2169 | 2170 | std::map gainbplr_out; 2171 | gainbplr_out["1T"] = " 1 1 "; 2172 | gainbplr_out["2F"] = " 2 0 "; 2173 | gainbplr_out["2T"] = " 2 1 "; 2174 | gainbplr_out["4F"] = " 4 0 "; 2175 | ostr << gainbplr_out[gainbplr]; 2176 | 2177 | for (size_t ix = 0; ix < nChannels; ix++) { 2178 | ostr << " " << cals[ix].at(0).leftJustified(9).toStdString() 2179 | << " " << cals[ix].at(1).leftJustified(8).toStdString(); 2180 | 2181 | if (sensor_type == "analog_gpDAQ" || sensor_type == "analog_diamond") 2182 | { 2183 | // Cal file is 3rd order, pad cals out if needed. 2184 | for (int j = cals[ix].size(); j < 4; ++j) 2185 | cals[ix] << "0.0"; 2186 | 2187 | ostr << " " << cals[ix].at(2).leftJustified(8).toStdString() 2188 | << " " << cals[ix].at(3).leftJustified(8).toStdString(); 2189 | } 2190 | } 2191 | ostr << std::endl; 2192 | 2193 | QString filename = calfile_dir; 2194 | 2195 | #ifndef SANDBOX 2196 | filename += QString("A2D/"); 2197 | #endif 2198 | filename += "A2D" + serial_number + ".dat"; 2199 | 2200 | exportCalFile(filename, ostr.str()); 2201 | } 2202 | 2203 | /* -------------------------------------------------------------------- */ 2204 | 2205 | void MainWindow::exportBath(int row) 2206 | { 2207 | std::cout << __PRETTY_FUNCTION__ << std::endl; 2208 | 2209 | QString cal_date = modelData(row, clm_cal_date); 2210 | QString project_name = modelData(row, clm_project_name); 2211 | QString sensor_type = modelData(row, clm_sensor_type); 2212 | QString serial_number = modelData(row, clm_serial_number); 2213 | 2214 | QDateTime ut = QDateTime::fromString(cal_date, Qt::ISODate); 2215 | 2216 | QStringList list_set_points = extractListFromBracedCSV(row, clm_set_points); 2217 | if (list_set_points.isEmpty()) return; 2218 | QStringList list_averages = extractListFromBracedCSV(row, clm_averages); 2219 | if (list_averages.isEmpty()) return; 2220 | QStringList list_stddevs = extractListFromBracedCSV(row, clm_stddevs); 2221 | if (list_stddevs.isEmpty()) return; 2222 | 2223 | int nSetPoints = list_set_points.count(); 2224 | double setp[nSetPoints]; 2225 | double avrg[nSetPoints]; 2226 | 2227 | for (int i=0; isetData(_proxy->index(row, clm_exported), "1"); 2277 | } 2278 | 2279 | /* -------------------------------------------------------------------- */ 2280 | 2281 | void MainWindow::exportCalFile(QString filename, std::string contents) 2282 | { 2283 | std::cout << __PRETTY_FUNCTION__ << std::endl; 2284 | 2285 | QMessageBox::information(0, tr("Exporting cal into file:"), filename); 2286 | qDebug() << "filename: " << filename; 2287 | qDebug() << "contents: " << contents.c_str(); 2288 | 2289 | // this matches date and time stamps like this: 2008 Jun 09 19:47:05 2290 | QRegExp rxDateTime("^([12][0-9][0-9][0-9] ... [ 0-3][0-9] " 2291 | "[ 0-2][0-9]:[0-5][0-9]:[0-5][0-9])"); 2292 | 2293 | QDateTime bt, ct, et(QDate(2999,1,1),QTime(0,0,0)); 2294 | 2295 | // Find the actual singular calibration line in the new contents. 2296 | QString calLine, qstr(contents.c_str()); 2297 | QStringList qsl = qstr.split("\n"); 2298 | foreach (calLine, qsl) { 2299 | qDebug() << "calLine: " << calLine; 2300 | if ( calLine.contains(rxDateTime) ) 2301 | break; 2302 | } 2303 | if ( not calLine.contains(rxDateTime) ) 2304 | return; 2305 | 2306 | ct = QDateTime::fromString(rxDateTime.cap(1), "yyyy MMM dd HH:mm:ss"); 2307 | qDebug() << "ct: " << ct.toString(); 2308 | 2309 | // Open the selected calfile (it's ok if it doesn't exist yet). 2310 | std::ifstream calfile ( filename.toStdString().c_str() ); 2311 | 2312 | // Open a unique temporary file. 2313 | char tmpfilename[] = "/tmp/MainWindow_XXXXXX"; 2314 | int tmpfile = mkstemp(tmpfilename); 2315 | if (tmpfile == -1) { 2316 | if (calfile) calfile.close(); 2317 | std::ostringstream ostr; 2318 | ostr << tr("failed to open temporary file:\n").toStdString(); 2319 | ostr << tmpfilename << std::endl; 2320 | ostr << tr(strerror(errno)).toStdString(); 2321 | QMessageBox::warning(0, tr("error"), ostr.str().c_str()); 2322 | return; 2323 | } 2324 | // Read the selected calfile and look for a place, chronologically, to insert 2325 | // the new calibration entry. 2326 | std::ostringstream buffer; 2327 | std::string line; 2328 | while ( std::getline(calfile, line) ) { 2329 | buffer << line << std::endl; 2330 | // std::cout << "-- buffer -------------------------------------------------------------\n" 2331 | // << buffer.str().c_str() 2332 | // << "-- ------ -------------------------------------------------------------\n"; 2333 | 2334 | // If line matches a previously inserted line then prompt before re-inserting it. 2335 | if ( QString(line.c_str()) == calLine) { 2336 | QMessageBox::StandardButton reply; 2337 | reply = QMessageBox::question(0, tr("Duplicate?"), 2338 | tr("Calibration was already exported to this file\n\nCreate a duplicate entry in file?\n"), 2339 | QMessageBox::Yes | QMessageBox::No); 2340 | 2341 | if (reply == QMessageBox::No) 2342 | ct = et; 2343 | } 2344 | // If successfully inserted then dump remainder of calfile into tmpfile. 2345 | if ( ct == et) { 2346 | // std::cout << "APPENDING REMAINDER | " << buffer.str().c_str(); 2347 | write(tmpfile, buffer.str().c_str(), buffer.str().length()); 2348 | buffer.str(""); 2349 | continue; 2350 | } 2351 | // Find datetime stamp from the latest line in the buffer. 2352 | if (rxDateTime.indexIn( QString(line.c_str()) ) == -1) 2353 | continue; 2354 | 2355 | // insert leading zeros to day and hour fields 2356 | // 01234567890123456789 2357 | // yyyy MMM dd HH:mm:ss 2358 | QString aTime = rxDateTime.cap(1); 2359 | if (aTime[9] == ' ') aTime[9] = '0'; 2360 | if (aTime[12] == ' ') aTime[12] = '0'; 2361 | bt = QDateTime::fromString(aTime, "yyyy MMM dd HH:mm:ss"); 2362 | // std::cout << " bt: " << bt.toString().toStdString() << " (" << rxDateTime.cap(1).toStdString() << ")" << std::endl; 2363 | if ( ct < bt) { 2364 | ct = et; 2365 | write(tmpfile, contents.c_str(), contents.length()); 2366 | } 2367 | write(tmpfile, buffer.str().c_str(), buffer.str().length()); 2368 | buffer.str(""); 2369 | } 2370 | // Insert the contents if not already done yet. 2371 | if ( ct != et) 2372 | write(tmpfile, contents.c_str(), contents.length()); 2373 | 2374 | if (calfile) calfile.close(); 2375 | ::close(tmpfile); 2376 | 2377 | chmod(tmpfilename, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); 2378 | 2379 | // Replace calfile with tmpfile. 2380 | QFile::remove(filename); 2381 | if ( !QFile::rename(QString(tmpfilename), filename) ) { 2382 | std::ostringstream ostr; 2383 | ostr << tr("failed to create cal file:\n").toStdString(); 2384 | ostr << filename.toStdString() << std::endl; 2385 | ostr << tr(strerror(errno)).toStdString(); 2386 | QMessageBox::warning(0, tr("error"), ostr.str().c_str()); 2387 | return; 2388 | } 2389 | // mark what's exported 2390 | QModelIndexList rowList = _table->selectionModel()->selectedRows(); 2391 | foreach (QModelIndex rowIndex, rowList) { 2392 | _proxy->setData(_proxy->index(rowIndex.row(), clm_exported), "1"); 2393 | } 2394 | } 2395 | 2396 | /* -------------------------------------------------------------------- */ 2397 | 2398 | void MainWindow::saveFileAs(QString filename, QString title, std::string contents) 2399 | { 2400 | std::cout << __PRETTY_FUNCTION__ << std::endl; 2401 | 2402 | // provide a dialog for the user to select what to save as... 2403 | filename = QFileDialog::getSaveFileName(this, tr("Save File"), 2404 | filename, title); 2405 | 2406 | if (filename.isEmpty()) return; 2407 | 2408 | std::cout << "Writing results to: "; 2409 | std::cout << filename.toStdString() << std::endl; 2410 | std::cout << contents << std::endl; 2411 | 2412 | // TODO - why isn't older files being completely replaced here? 2413 | // HACK remove previous file first 2414 | QFile::remove(filename); 2415 | int fd = ::open( filename.toStdString().c_str(), 2416 | O_WRONLY | O_CREAT, 0664); 2417 | 2418 | if (fd == -1) { 2419 | std::ostringstream ostr; 2420 | ostr << tr("failed to save results to:\n").toStdString(); 2421 | ostr << filename.toStdString() << std::endl; 2422 | ostr << tr(strerror(errno)).toStdString(); 2423 | QMessageBox::warning(0, tr("error"), ostr.str().c_str()); 2424 | return; 2425 | } 2426 | write(fd, contents.c_str(), contents.length()); 2427 | ::close(fd); 2428 | } 2429 | 2430 | /* -------------------------------------------------------------------- */ 2431 | 2432 | void MainWindow::cloneButtonClicked() 2433 | { 2434 | std::cout << __PRETTY_FUNCTION__ << std::endl; 2435 | QModelIndex index = _table->selectionModel()->currentIndex(); 2436 | int row = index.row(); 2437 | std::cout << "row = " << row << std::endl; 2438 | 2439 | // set clone's row ID 2440 | QString rid = createRID(); 2441 | 2442 | // set clone's parent ID 2443 | QString pid = modelData(row, clm_rid); 2444 | 2445 | // copy data from parent row 2446 | QString site = modelData(row, clm_site); 2447 | QString pulled = "0"; 2448 | QString removed = "0"; 2449 | QString exported = "0"; 2450 | QDateTime cal_date = _proxy->index(row, clm_cal_date).data().toDateTime(); 2451 | QString project_name = modelData(row, clm_project_name); 2452 | QString username = getenv("USERNAME"); 2453 | QString sensor_type = modelData(row, clm_sensor_type); 2454 | QString serial_number = modelData(row, clm_serial_number); 2455 | QString var_name = modelData(row, clm_var_name); 2456 | QString dsm_name = modelData(row, clm_dsm_name); 2457 | QString cal_type = modelData(row, clm_cal_type); 2458 | QString channel = modelData(row, clm_channel); 2459 | QString gainbplr = modelData(row, clm_gainbplr); 2460 | QString ads_file_name = modelData(row, clm_ads_file_name); 2461 | QString set_times = modelData(row, clm_set_times); 2462 | QString set_points = modelData(row, clm_set_points); 2463 | QString averages = modelData(row, clm_averages); 2464 | QString stddevs = modelData(row, clm_stddevs); 2465 | QString cal = modelData(row, clm_cal); 2466 | QString temperature = modelData(row, clm_temperature); 2467 | QString comment = modelData(row, clm_comment); 2468 | 2469 | // add new rid to list of editable clones 2470 | _editable.push_back(rid); 2471 | 2472 | // advance the clone's timestamp to be one second past the parent's 2473 | cal_date = cal_date.addSecs(1); 2474 | 2475 | QSqlRecord record = _model->record(); 2476 | 2477 | // paste the parent's data into its clone 2478 | record.setValue(clm_rid, rid); 2479 | record.setValue(clm_pid, pid); 2480 | record.setValue(clm_site, site); 2481 | record.setValue(clm_pulled, pulled); 2482 | record.setValue(clm_removed, removed); 2483 | record.setValue(clm_exported, exported); 2484 | record.setValue(clm_cal_date, cal_date); 2485 | record.setValue(clm_project_name, project_name); 2486 | record.setValue(clm_username, username); 2487 | record.setValue(clm_sensor_type, sensor_type); 2488 | record.setValue(clm_serial_number, serial_number); 2489 | record.setValue(clm_var_name, var_name); 2490 | record.setValue(clm_dsm_name, dsm_name); 2491 | record.setValue(clm_cal_type, cal_type); 2492 | record.setValue(clm_channel, channel); 2493 | record.setValue(clm_gainbplr, gainbplr); 2494 | record.setValue(clm_ads_file_name, ads_file_name); 2495 | record.setValue(clm_set_times, set_times); 2496 | record.setValue(clm_set_points, set_points); 2497 | record.setValue(clm_averages, averages); 2498 | record.setValue(clm_stddevs, stddevs); 2499 | record.setValue(clm_cal, cal); 2500 | record.setValue(clm_temperature, temperature); 2501 | record.setValue(clm_comment, comment); 2502 | 2503 | _model->insertRow(row+1); 2504 | _model->setRecord(row+1, record); 2505 | 2506 | // re-apply any filtering after inserting a new row 2507 | _lastRid = rid; 2508 | qDebug() << "_lastRid:" << _lastRid; 2509 | _proxy->refreshFilters(); 2510 | scrollToLastClicked(); 2511 | 2512 | // Adding a new row does not trigger a dataChanged event 2513 | changeDetected = true; 2514 | } 2515 | 2516 | /* -------------------------------------------------------------------- */ 2517 | 2518 | void MainWindow::removeButtonClicked() 2519 | { 2520 | bool ok = true; 2521 | int numEmptyText = 0; 2522 | QInputDialog* inputDialog = new QInputDialog(); 2523 | 2524 | std::cout << __PRETTY_FUNCTION__ << std::endl; 2525 | 2526 | // get selected row number 2527 | int row = _table->selectionModel()->currentIndex().row(); 2528 | 2529 | QString rid = modelData(row, clm_rid); 2530 | 2531 | QSqlQuery query(QSqlDatabase::database()); 2532 | QString cmd; 2533 | cmd = QString("SELECT comment from calibrations " 2534 | "WHERE rid='%1'").arg(rid); 2535 | 2536 | QString comment; 2537 | if (query.exec(cmd) && query.first()) 2538 | comment = query.value(0).toString(); 2539 | query.finish(); 2540 | 2541 | std::cout << "Comment: " << comment.toStdString() << "\n"; 2542 | 2543 | QString text; 2544 | 2545 | while (ok && text.isEmpty()) 2546 | if (numEmptyText == 0) { 2547 | text = inputDialog->getText(NULL, "DeleteRow Reason", 2548 | "Delete Comment:", QLineEdit::Normal, 2549 | "DELETED because", &ok); 2550 | numEmptyText++; 2551 | } 2552 | else 2553 | text = inputDialog->getText(NULL, "DeleteRow Reason", 2554 | "Please Enter Non-Blank Delete Comment:", 2555 | QLineEdit::Normal, "-DELETED because", &ok); 2556 | 2557 | // Bail out if user clicks Cancel 2558 | if (!ok) return; 2559 | 2560 | std::cout<setData(_proxy->index(row, clm_comment), comment); 2566 | 2567 | // mark what's removed 2568 | _proxy->setData(_proxy->index(row, clm_removed), "1"); 2569 | 2570 | } 2571 | 2572 | /* -------------------------------------------------------------------- */ 2573 | 2574 | void MainWindow::changeFitButtonClicked(int row, int order) 2575 | { 2576 | std::cout << __PRETTY_FUNCTION__ << " row: " << row << " order: " << order 2577 | << std::endl; 2578 | 2579 | QStringList list_averages = extractListFromBracedCSV(form_averages); 2580 | if (list_averages.isEmpty()) return; 2581 | QStringList list_set_points = extractListFromBracedCSV(form_set_points); 2582 | if (list_set_points.isEmpty()) return; 2583 | 2584 | // exit if array sizes don't match 2585 | if (list_set_points.count() != list_averages.count()) { 2586 | QMessageBox::warning(0, tr("error"), 2587 | tr("sizes of 'averages' and 'set_points' arrays don't match!")); 2588 | return; 2589 | } 2590 | 2591 | int nSetPoints = list_set_points.count(); 2592 | double setp[nSetPoints]; 2593 | double avrg[nSetPoints]; 2594 | 2595 | for (int i=0; i_currCalCList[i]->setText( QString::number(coeff[i]) ); 2613 | cals << coeff[i]; 2614 | if (i < order) 2615 | cals << ","; 2616 | } 2617 | for (; i<4; i++) 2618 | _form->_currCalCList[i]->setText(""); 2619 | 2620 | cals << "}"; 2621 | form_cal = QString(cals.str().c_str()); 2622 | 2623 | std::cout << "old cal: " << modelData(row, clm_cal).toStdString() << std::endl; 2624 | std::cout << "new cal: " << form_cal.toStdString() << std::endl; 2625 | } 2626 | --------------------------------------------------------------------------------