#include "qmconfclass.h" #include "qmconfig.h" #include #include #include #include #include #include #include #include #include #include #include "consts.h" #include "structs.h" #include "proto.h" #include "mainwindow.h" #include "qmTableWidgetItem.h" #include #include "QTableWidgetItem" #include "spinboxdelegate.h" #include "timedelegate.h" #include "textdelegate.h" #include #define COL_ABR 0 #define COL_CLASS 1 #define COL_TIME 2 #define COL_WINNERS 3 #define COL_NE 4 #define COL_NE_WINNERS 5 #define COL_ST_TOTAL 6 #define COL_ST_VALID 7 #define COL_ST_DISQ 8 #define COL_ST_NONEQ 9 #define COL_ST_VET 10 #define COL_ST_CEV 11 #define COL_ST_CNE 12 #define COL_INDEX 13 #define COL_COUNT 14 QmConfClass::QmConfClass(QWidget *parent) : QWidget(parent) { populating = false; dirty = false; QVBoxLayout *verticalLayout = new QVBoxLayout(this); verticalLayout->setContentsMargins(0, 0, 0, 0); QGroupBox *groupBox = new QGroupBox("Class"); verticalLayout->addWidget(groupBox); QVBoxLayout *verticalLayout2 = new QVBoxLayout(groupBox); tableWidget = new QTableWidget(groupBox); tableWidget->setAlternatingRowColors(true); tableWidget->setRowCount(config.num_class + 1); tableWidget->setColumnCount(COL_COUNT); tableWidget->setContextMenuPolicy(Qt::CustomContextMenu); tableWidget->horizontalHeader()->setVisible(true); tableWidget->horizontalHeader()->setDefaultSectionSize(70); tableWidget->horizontalHeader()->setHighlightSections(true); //tableWidget->horizontalHeader()->setStretchLastSection(true); tableWidget->verticalHeader()->setVisible(true); tableWidget->verticalHeader()->setDefaultSectionSize(20); //tableWidget->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding ); //tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); verticalLayout2->addWidget(tableWidget); //QSpacerItem *verticalSpacer1; //verticalSpacer1 = new QSpacerItem(0, 10, QSizePolicy::Expanding, QSizePolicy::Expanding); //verticalLayout2->addItem(verticalSpacer1); QHBoxLayout *horizontalLayout; horizontalLayout = new QHBoxLayout(); horizontalLayout->setContentsMargins(0, 0, 5, 5); verticalLayout->addLayout(horizontalLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(); pushButtonRestore = buttonBox->addButton("Restore",QDialogButtonBox::ActionRole ); pushButtonSave = buttonBox->addButton("Save",QDialogButtonBox::ActionRole ); horizontalLayout->addWidget(buttonBox); connect(pushButtonSave, SIGNAL(clicked(bool)), this, SLOT(save()) ); connect(pushButtonRestore, SIGNAL(clicked(bool)), this, SLOT(cancel()) ); QStringList labels; labels << "Abr" << "Full Name" << "Start Time" << "Winners" << "NE" << "NE Winners"; labels << "Total" << "Valid" <<"Disqual" << "NonEq" << "VetCheck" << "Comp Ev" << " Comp NE"; labels << "Index"; tableWidget->setHorizontalHeaderLabels(labels); connect(tableWidget, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(ctxMenu(const QPoint &))); connect(tableWidget, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int,int))); /* ** Setup delegated for specialised editing */ tableWidget->setItemDelegateForColumn(COL_ABR, new textDelegate(2)); tableWidget->setItemDelegateForColumn(COL_CLASS, new textDelegate(LEN_CLASS_NAME)); tableWidget->setItemDelegateForColumn(COL_TIME, new timeDelegate()); tableWidget->setItemDelegateForColumn(COL_WINNERS, new SpinBoxDelegate(0,50)); tableWidget->setItemDelegateForColumn(COL_NE_WINNERS, new SpinBoxDelegate(0,50)); } /*---------------------------------------------------------------------------- ** FUNCTION : showEvent ** ** DESCRIPTION : Called when the page is shown ** Used to refresh the contents of the display as well ** as show them for the first time ** ----------------------------------------------------------------------------*/ void QmConfClass::showEvent ( QShowEvent * event ) { //qDebug("populate::showEvent"); if ( ! event->spontaneous() && !dirty ) { populate(); } } /*---------------------------------------------------------------------------- ** FUNCTION : populate ** ** DESCRIPTION : Populate the display with information ** ----------------------------------------------------------------------------*/ void QmConfClass::populate(void) { bool reportDataLoaded = false; t_class_summary data; populating = true; tableWidget->clearContents(); tableWidget->setRowCount(0); tableWidget->setSortingEnabled(false); /* ** Load Report Data if we can */ if( load_report_data() ) { calc_class_summary( & data ); reportDataLoaded = true; } /* ** Add all the information */ int ii = 0; int entryIndex = 0; for ( ii = 0; ii < MAX_CLASS; ii++) { if ( config.team_class[ii].abr[0] ) { // Keep the config item index entry // Will need it when we write stiff out tableWidget->setRowCount(tableWidget->rowCount() + 1); tableWidget->setItem(entryIndex, COL_INDEX, new qmTwiNumber(ii)); tableWidget->setItem(entryIndex, COL_ABR, new qmTwiEditString(config.team_class[ii].abr )); tableWidget->setItem(entryIndex, COL_CLASS, new qmTwiEditString(config.team_class[ii].full_name )); QTableWidgetItem *item; item = new QTableWidgetItem(); item->setData(Qt::EditRole,QTime().addSecs(config.team_class[ii].start) ); tableWidget->setItem(entryIndex, COL_TIME, item); tableWidget->setItem(entryIndex, COL_WINNERS, new qmTwiEditNumber(config.class_winners[ii])); tableWidget->setItem(entryIndex, COL_NE, new qmTwiEditFlag("",config.nonequestrian_class == 1 + ii)); tableWidget->setItem(entryIndex, COL_NE_WINNERS, new qmTwiEditNumber(config.class_ne_winners[ii])); if (reportDataLoaded) { tableWidget->setItem(entryIndex,COL_ST_TOTAL, new qmTwiNumber(data.teamclass[ii+1].total) ); tableWidget->setItem(entryIndex,COL_ST_VALID, new qmTwiNumber(data.teamclass[ii+1].valid) ); tableWidget->setItem(entryIndex,COL_ST_DISQ, new qmTwiNumber(data.teamclass[ii+1].disqualified)); tableWidget->setItem(entryIndex,COL_ST_NONEQ, new qmTwiNumber(data.teamclass[ii+1].non_equestrian)); tableWidget->setItem(entryIndex,COL_ST_VET, new qmTwiNumber(data.teamclass[ii+1].vet_check)); tableWidget->setItem(entryIndex,COL_ST_CEV, new qmTwiNumber(data.teamclass[ii+1].valid_ev)); tableWidget->setItem(entryIndex,COL_ST_CNE, new qmTwiNumber(data.teamclass[ii+1].valid_ne)); } entryIndex++; } } // Insert Totals // Flag that items on this this row is not to be sorted - or at least // sorted at the end // if (reportDataLoaded) { tableWidget->setRowCount(tableWidget->rowCount() + 1); tableWidget->setItem(entryIndex,COL_ABR, new qmTwiString("Totals", 1)); tableWidget->setItem(entryIndex, COL_CLASS, new qmTwiString("",1)); tableWidget->setItem(entryIndex, COL_TIME, new qmTwiString("",1)); tableWidget->setItem(entryIndex, COL_WINNERS, new qmTwiString("",1)); tableWidget->setItem(entryIndex, COL_NE, new qmTwiString("",1)); tableWidget->setItem(entryIndex, COL_NE_WINNERS, new qmTwiString("",1)); tableWidget->setItem(entryIndex, COL_INDEX, new qmTwiString("",1)); tableWidget->setItem(entryIndex,COL_ST_TOTAL, new qmTwiNumber(data.total.total, 1)); tableWidget->setItem(entryIndex,COL_ST_VALID, new qmTwiNumber(data.total.valid, 1)); tableWidget->setItem(entryIndex,COL_ST_DISQ, new qmTwiNumber(data.total.disqualified, 1)); tableWidget->setItem(entryIndex,COL_ST_NONEQ, new qmTwiNumber(data.total.non_equestrian, 1)); tableWidget->setItem(entryIndex,COL_ST_VET, new qmTwiNumber(data.total.vet_check, 1)); tableWidget->setItem(entryIndex,COL_ST_CEV, new qmTwiNumber(data.total.valid_ev, 1)); tableWidget->setItem(entryIndex,COL_ST_CNE, new qmTwiNumber(data.total.valid_ne, 1)); // Debug // tableWidget->setItem(entryIndex, COL_INDEX, new qmTwiNumber(5,2)); } checkNeClass(); tableWidget->sortByColumn ( COL_INDEX, Qt::AscendingOrder ); tableWidget->setSortingEnabled(true); tableWidget->resizeColumnsToContents(); tableWidget->resizeRowsToContents(); updateChanged(false); populating = false; } void QmConfClass::checkNeClass(void) { QString tip = "Unexpected value. The NE winners are configured on a per-class basis"; for ( int ii = 0; ii < MAX_CLASS; ii++) { int indexEntry = 0; QTableWidgetItem *item = tableWidget->item ( ii, COL_INDEX ); const QBrush *goldBrush = new QBrush(QColor(255,204,203)); if (!item) { //qDebug("Ignore row:%d", entryIndex); continue; } if ( item->text().length() == 0) { //qDebug("Ignore empty row:%d", entryIndex); continue; } indexEntry = item->text().toInt(); //qDebug("Processing: %d", ii); item = tableWidget->item( ii, COL_NE ); if ( item ) { if ( item->checkState() == Qt::Checked) { item = tableWidget->item( ii, COL_WINNERS ); if (item && item->text().length() && item->text().toInt() ) { tableWidget->item(ii,COL_WINNERS)->setBackground(*goldBrush); tableWidget->item(ii,COL_WINNERS)->setToolTip(tip); MainWindow::showMessage(tip); } else { tableWidget->item(ii,COL_WINNERS)->setBackground(Qt::transparent); tableWidget->item(ii,COL_WINNERS)->setToolTip(""); } item = tableWidget->item( ii, COL_NE_WINNERS ); if (item && item->text().length() && item->text().toInt() ) { tableWidget->item(ii,COL_NE_WINNERS)->setBackground(*goldBrush); tableWidget->item(ii,COL_NE_WINNERS)->setToolTip(tip); MainWindow::showMessage(tip); } else { tableWidget->item(ii,COL_NE_WINNERS)->setBackground(Qt::transparent); tableWidget->item(ii,COL_NE_WINNERS)->setToolTip(""); } } else { tableWidget->item(ii,COL_WINNERS)->setBackground(Qt::transparent); tableWidget->item(ii,COL_WINNERS)->setToolTip(""); tableWidget->item(ii,COL_NE_WINNERS)->setBackground(Qt::transparent); tableWidget->item(ii,COL_NE_WINNERS)->setToolTip(""); } } } } void QmConfClass::save(void) { /* ** Copy original data */ QmConfig newcfg(config); /* ** Extract the data from the Widgets ** Trap: The Class info is cross ref by index into the table ** Thus we need to maintain the order in the config. */ for (int entryIndex = 0; entryIndex < tableWidget->rowCount(); entryIndex++) { // Check that this is a real Entry or the 'Totals' header // Real rows have a COL_INDEX entry that is not empty // QTableWidgetItem *item = tableWidget->item ( entryIndex, COL_INDEX ); if (! item) { //qDebug("Ignore row:%d", entryIndex); continue; } if ( item->text().length() == 0) { //qDebug("Ignore empty row:%d", entryIndex); continue; } int ii = item->text().toInt(); //qDebug("Processing: %d", ii); /* ** This is a new entry - allocate a new index ** Scan the entire available space looking for the first empty slot */ if (ii < 0) { for (ii = 0; ii < MAX_CLASS ; ii++) { if (newcfg.team_class[ii].abr[0] == 0) { break; } } } if (ii >= MAX_CLASS ) { qDebug("Ignore row:%d. Class out of range", entryIndex); continue; } item = tableWidget->item ( entryIndex, COL_ABR ); if ( item ) { strncpy(newcfg.team_class[ii].abr, qPrintable(item->text()), sizeof(newcfg.team_class[ii].abr)-1); } else { *newcfg.team_class[ii].abr = 0; } item = tableWidget->item ( entryIndex, COL_CLASS ); if ( item ) { strncpy(newcfg.team_class[ii].full_name, qPrintable(item->text()), sizeof(newcfg.team_class[ii].full_name)-1); } else { *newcfg.team_class[ii].full_name = 0; } item = tableWidget->item( entryIndex, COL_TIME ); if ( item ) { QVariant data = item->data(Qt::EditRole); if (data.isValid()) { newcfg.team_class[ii].start = QTime(0,0,0).secsTo(item->data(Qt::EditRole).toTime()); } else { newcfg.team_class[ii].start = -1; } } else { newcfg.team_class[ii].start = -1; } item = tableWidget->item( entryIndex, COL_WINNERS ); if ( item ) { newcfg.class_winners[ii] = item->data(Qt::EditRole).toInt(); } else { newcfg.class_winners[ii] = 0; } item = tableWidget->item( entryIndex, COL_NE ); if ( item ) { if ( item->checkState() == Qt::Checked) { newcfg.nonequestrian_class = ii; strncpy(newcfg.nonequestrian_class_abr, newcfg.team_class[ii].abr, sizeof(newcfg.team_class[ii].abr)-1); } } item = tableWidget->item( entryIndex, COL_NE_WINNERS ); if ( item ) { newcfg.class_ne_winners[ii] = item->data(Qt::EditRole).toInt(); } else { newcfg.class_ne_winners[ii] = 0; } } // Validate the data try { MainWindow::showMessage( "Saving Config"); /* ** Now do the Class definitions */ for( int i = 0; i < MAX_CLASS; i++ ) { compact( newcfg.team_class[i].abr ); compact( newcfg.team_class[i].full_name ); } for( int i = 0; i < MAX_CLASS; i++ ) { if( ( newcfg.team_class[i].abr[0] == '\0' ) != ( newcfg.team_class[i].full_name[0] == '\0' ) ) { throw( "Configuration error. Class without description" ); } if( newcfg.team_class[i].abr[0] != '\0' && newcfg.team_class[i].start < 0L ) { throw( "Configuration error. Bad start time on class" ); } } // Calculate new max number of classes // Scan from the end looking for the last one used // for (int i = MAX_CLASS-1; i >= 0; i--) { if (newcfg.team_class[i].abr[0]) { //qDebug("num_class:%d -> %d", newcfg.num_class, 1+i); newcfg.num_class = 1 + i; break; } } for( int i = newcfg.num_class; i < MAX_CLASS; i++ ) if( newcfg.team_class[i].full_name[0] ) { qDebug( "Configuration error: Missing Class name. Gaps not allowed" ); } if( newcfg.num_class == 0 ) { throw( "Error: No categories defined" ); } /* ** Test for duplicate abbreviations ** I know its a slow agorithm, but its only a small set */ for( int i = 0; i < MAX_CLASS; i++ ) { if (newcfg.team_class[i].abr[0] != '\0') { for (int j = i+1; j < MAX_CLASS; j++) { if (newcfg.team_class[j].abr[0] != '\0') { if ( newcfg.team_class[i].abr[0] == newcfg.team_class[j].abr[0] && newcfg.team_class[i].abr[1] == newcfg.team_class[j].abr[1] ) { qDebug("Duplicate abr: %s", newcfg.team_class[i].abr); throw( "Error: Duplicate abbreviations detected"); } } } } } /* ** Sanity Test */ newcfg.nonequestrian_class = newcfg.lookup_class( newcfg.nonequestrian_class_abr ); if( newcfg.equestrian_leg && newcfg.nonequestrian_class == 0 ) MainWindow::showMessage( "WARNING: Non-equestrian class not found" ); //qDebug("NE Index is: %d", newcfg.nonequestrian_class ); /* ** Sanity test of the data */ for( int i = 0; i < MAX_CLASS; i++ ) { if( newcfg.team_class[i].abr[0] != '\0' && newcfg.class_winners[i] == 0 ) { if (newcfg.nonequestrian_class != i+1) { MainWindow::showMessage(QString("Warning: Class without winners:")+ newcfg.team_class[i].abr); } } } /* ** Cannot mix winners for NE class and NE Winners for each class */ if (newcfg.nonequestrian_class) { newcfg.class_ne_winners_by_class = false; for( int i = 0; i < MAX_CLASS; i++ ) { if ( newcfg.class_ne_winners[i]) { newcfg.class_ne_winners_by_class = true; break; } } if (newcfg.class_winners[newcfg.nonequestrian_class - 1] && newcfg.class_ne_winners_by_class ) { MainWindow::showMessage( QString("Should not mix NE winners by each class and by NE Class")); //throw( "Error: Cannot mix NE winners by each class and by NE Class" ); } } config = newcfg; config.write_config(); updateChanged(false); // Force re-populate so that the index field is now correct populate(); } catch (const char * str ) { MainWindow::showMessage(str); } } void QmConfClass::cancel(void) { populate(); } QmConfClass::~QmConfClass() { } void QmConfClass::ctxMenu(const QPoint & pos) { //qDebug("Context Menu"); QMenu *menu = new QMenu; menu->addAction(tr("New Category"), this, SLOT(ctxMenuAddRow())); /* ** Determine if we can delete an item ** Can delete a category we added in this session ** Can delete one if it has no teams using it. */ QTableWidgetItem *item = tableWidget->itemAt(pos); if (item) { int row = item->row(); //qDebug("Item is at row:%d", row); QTableWidgetItem *indexItem = tableWidget->item ( row, COL_INDEX ); if (indexItem) { int index = indexItem->text().toInt(); //qDebug("Item is at row:%d, Index: %d", row, index); if (index < 0) { menu->addAction(tr("Delete Newly added Category"), this, SLOT(ctxMenuDeleteRow())); } else { QTableWidgetItem *itemTotalCount = tableWidget->item ( row, COL_ST_TOTAL ); if (itemTotalCount) { int count = itemTotalCount->text().toInt(); if (count == 0) { menu->addAction(tr("Delete Category - its not used"), this, SLOT(ctxMenuDeleteRow())); } } } } } menu->exec(tableWidget->mapToGlobal(pos)); } void QmConfClass::ctxMenuDeleteRow(void) { // // Assume that if the menu was shown, then we can delete it // //qDebug ("DELETE ROW: %d", tableWidget->currentRow () ); //tableWidget->removeCellWidget(tableWidget->currentRow (), COL_ABR); //tableWidget->removeRow(tableWidget->currentRow ()); tableWidget->item(tableWidget->currentRow (), COL_ABR)->setText(""); tableWidget->item(tableWidget->currentRow (), COL_CLASS)->setText(""); tableWidget->hideRow(tableWidget->currentRow ()); updateChanged(true); } void QmConfClass::ctxMenuAddRow(void) { tableWidget->setSortingEnabled(false); tableWidget->setRowCount( 1+ tableWidget->rowCount()); /* ** Insert non-editable fields in the status part of the table */ tableWidget->setItem(tableWidget->rowCount() - 1, COL_INDEX, new qmTwiNumber(-1)); for (int ii= COL_ST_TOTAL; ii < COL_INDEX; ii++) { tableWidget->setItem(tableWidget->rowCount() - 1, ii, new qmTwiString("")); } } void QmConfClass::cellChanged(int row,int col) { if ( populating ) return; if (col == COL_INDEX) return; updateChanged(true); populating = true; //qDebug("Cell changed: %d, %d", row, col); if (col == COL_NE) { for (int ii = 0; ii < tableWidget->rowCount(); ii++) { QTableWidgetItem *item = tableWidget->item(ii, COL_NE); if (item) { item->setCheckState(ii == row ? Qt::Checked : Qt::Unchecked); } } } // Highlight wonky config if (col == COL_NE || col == COL_NE_WINNERS || col == COL_WINNERS) { checkNeClass(); } populating = false; } void QmConfClass::updateChanged(bool newDirty) { if (newDirty != dirty) { dirty = newDirty; if (dirty) { pushButtonSave->setEnabled(true); pushButtonSave->setStyleSheet("background-color: rgb(255, 0, 0);"); } else { pushButtonSave->setEnabled(false); pushButtonSave->setStyleSheet(""); } } } void QmConfClass::changeEvent(QEvent *e) { QWidget::changeEvent(e); switch (e->type()) { default: break; } }