Subversion Repositories svn1-original

Rev

Rev 382 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include "qmconfclass.h"
#include "qmconfig.h"
#include <QtCore/QVariant>
#include <QtGui/QAction>
#include <QApplication>
#include <QButtonGroup>
#include <QGroupBox>
#include <QHeaderView>
#include <QPushButton>
#include <QTableWidget>
#include <QVBoxLayout>
#include <QWidget>
#include    "consts.h"
#include    "structs.h"
#include    "proto.h"
#include "mainwindow.h"
#include "qmTableWidgetItem.h"

#include <QDialogButtonBox>
#include "QTableWidgetItem"
#include "spinboxdelegate.h"
#include "timedelegate.h"
#include "textdelegate.h"
#include <QMenu>

#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;
    }
}