OSSP CVS Repository

ossp - ossp-pkg/as/as-gui/as_dataop.cpp
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/as/as-gui/as_dataop.cpp
//
//  OSSP asgui - Accounting system graphical user interface
//  Copyright (c) 2002-2004 The OSSP Project (http://www.ossp.org/)
//  Copyright (c) 2002-2004 Ralf S. Engelschall <rse@engelschall.com>
//  Copyright (c) 2002-2004 Michael Schloh von Bennewitz <michael@schloh.com>
//  Copyright (c) 2002-2004 Cable & Wireless Telecommunications Services GmbH
//
//  This file is part of OSSP asgui, an accounting system graphical user
//  interface which can be found at http://www.ossp.org/pkg/tool/asgui/.
//
//  Permission to use, copy, modify, and distribute this software for
//  any purpose with or without fee is hereby granted, provided that
//  the above copyright notice and this permission notice appear in all
//  copies.
//
//  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
//  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
//  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
//  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
//  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
//  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
//  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
//  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
//  SUCH DAMAGE.
//
//  as_dataops.cpp: ISO C++ implementation
//

// System headers
#include <map>
#include <string>

// Qt general headers
#include <qregexp.h>            // Portable regular expressions
#include <qdatetime.h>
#include <qmessagebox.h>
#include <qtextstream.h>
#include <qpopupmenu.h>
#include <qfile.h>

// User interface
#include "as_const.h"           // Application constants
#include "as_tableitem.h"       // For class RtTableItem
#include "as_crc.h"             // For quality strings
#include "as_uuid.h"            // UUID classes
#include "as_table.h"           // TiTable class

// Icon pixel maps
#include "as_gfx/statok.xpm"    // static const char *s_kpcStatokay_xpm[]
#include "as_gfx/staterr.xpm"   // static const char *s_kpcStaterror_xpm[]
#include "as_gfx/statwrn.xpm"   // static const char *s_kpcStatwarn_xpm[]


//
// Convenience method to load accounts from a file
//
void Titraqform::loadAccounts(QFile &Fileobj)
{
    if (Fileobj.isOpen()) {             // Check state of file
        Fileobj.flush();                // Begin processing file cleanly
        QTextStream Account(&Fileobj);  // Convert data to stream
        this->loadAccounts(Account);    // Pass off to do the real work
    }
    else {
        if (!Fileobj.open(IO_ReadOnly)) {   // Try to open file
            QString Readerrstr;
            Readerrstr = trUtf8(TITRAQ_READAFILFAIL).arg(Fileobj.name());
            throw Genexcept(Readerrstr.ascii());
        }
        else
            Fileobj.flush();                // Begin processing file cleanly
            QTextStream Account(&Fileobj);  // Convert data to stream
            this->loadAccounts(Account);    // Pass off to do the real work
            Fileobj.close();                // Finish fileop by closing
    }
}

//
// Load accounts themselves data from a stream
//
void Titraqform::loadAccounts(QTextStream &Tstream)
{
    using namespace std;                // Needed for hash tables with hmap
    map<string, int> Hashnames;         // Hashtable for storing names
    map<int, string> Hashnums;          // Hashtable for storing repetitions
    map<string, int>::iterator Nameiter; // The hashtable name iterator
    map<int, string>::iterator Numiter;  // The hashtable number iterator

    QString Line;           // Used for linewise editing and whitespace eating

    // Eat lines until reading the start accounts token
    while (!Line.startsWith(trUtf8("%!AS-ACCOUNTS")) && !Tstream.atEnd()) {
        Line = QString("");         // Empty line for later inspection
        Line = Tstream.readLine();  // Get a new line to examine
    }

    // Strip out extra line feeds in stream
    while (Line.isEmpty() && !Tstream.atEnd()) {
        Tstream.skipWhiteSpace();       // Strip and get
        Line = Tstream.readLine();      // the new line
        if (Line.at(0) == QChar('#'))   // Remove comments
            Line = QString("");
    }

    // Set the accounts choices by linewise reading from the input
    // stream and parsing the corresponding account fields out of it
    while (!Line.isEmpty()) {
        QString Temp;                           // For reading from stream
        QTextStream Asline(&Line, IO_ReadOnly); // Convert a single line

        Asline.skipWhiteSpace();            // Remove whitespaces
        Asline >> Temp;                     // Copy revision indicator

        if (Temp == QString(QChar('R'))) {  // Copy the account field
            Asline >> Temp;                 // to temporary for transfer
            string Convstring = Temp;       // Convert to string (can't cast?)
            Hashnames[Convstring] += Hashnames[Convstring];
        }

        Line = QString(""); // Clear line for next round
        while (Line.isEmpty() && !Tstream.atEnd()) {
            Tstream.skipWhiteSpace();       // Strip and get
            Line = Tstream.readLine();      // the new line
            if (Line.at(0) == QChar('#'))   // Remove comments
                Line = QString("");
        }
    }

//    Hashnames.insert(map<string, int>::value_type((string)QString, int));
    for (Nameiter = Hashnames.begin(); Nameiter != Hashnames.end(); Nameiter++)
        Hashnums[Nameiter->second] += Nameiter->first + '\n';

// FIXME: Put this in loadData, to load custom and most used task names before
// FIXME: default listings are sorted and inserted
    for (Numiter = Hashnums.begin(); Numiter != Hashnums.end(); Numiter++) {

        // Count the number of lines of sorted task names
        int nNumlines = QString(Numiter->second).contains('\n');

        // Iterate through the lines of task names, feeding them into the menu
        for (int nIter = 0; nIter < nNumlines; nIter++)
            *m_pTaskentries << QString(Numiter->second).section('\n', nIter, nIter);
    }
}

//
// Convenience method to load personal data from a file
//
void Titraqform::loadData(QFile &Fileobj)
{
    if (Fileobj.isOpen()) {             // Check state of file
        Fileobj.flush();                // Begin processing file cleanly
        QTextStream Asentry(&Fileobj);  // Convert data to stream
        this->loadData(Asentry);        // Pass off to do the real work
    }
    else {
        if (!Fileobj.open(IO_ReadOnly)) // Try to open file
            throw Genexcept(trUtf8(TITRAQ_READPFILFAIL));
        else
            Fileobj.flush();                // Begin processing file cleanly
            QTextStream Asentry(&Fileobj);  // Convert data to stream
            this->loadData(Asentry);        // Pass off to do the real work
            Fileobj.close();                // Finish fileop by closing
    }
}

//
// Load personal data from a stream
//
void Titraqform::loadData(QTextStream &Tstream)
{
    bool bValid = true; // Used to warn on globally invalid accounting data
    int nIter = 0;      // Iterator used in loop and also as a count
    QString Line;       // Used for linewise editing and whitespace eating
    QString Bitbucket;  // Used for null device until we find a better way

    QPixmap Statokay(s_kpcStatokay_xpm);
    QPixmap Statwarn(s_kpcStatwarn_xpm);
    QPixmap Staterror(s_kpcStaterror_xpm);

    // Strip out extra line feeds at stream start
    while (Line.isEmpty() && !Tstream.atEnd()) {
        Tstream.skipWhiteSpace();       // Strip and get
        Line = Tstream.readLine();      // the new line
        if (Line.at(0) == QChar('#'))   // Remove comments
            Line = QString("");
    }

    // Strip out extra line feeds after reading the data symbol
    Line = QString("");                 // Reset our line
    while (Line.isEmpty() && !Tstream.atEnd()) {
        Tstream.skipWhiteSpace();       // Strip and get
        Line = Tstream.readLine();      // the new line
        if (Line.at(0) == QChar('#'))   // Remove comments
            Line = QString("");
    }

//    // Going into data churning, so prepare date and time parsing and conversion
//    Converdate.setSeparator(trUtf8("."));
//    Convertime.setSeparator(trUtf8(":"));

    // Optimize viewing by repainting cells only once after processing
    m_pMaintable->setUpdatesEnabled(false);

    // Set the table text by linewise reading from the input stream
    // and parsing date, time, account, and other columns out of it
    while (!Line.isEmpty()) {
        bool bValid = true; // Warns on linewise invalid accounting data
        QString User, Guid, Crc, Rev;                           // Valid admin fields
        QString Date, Start, Finish, Account, Amount, Remark;   // Valid user fields
        QTextStream Asline(&Line, IO_ReadOnly); // Convert a single line now

        if (nIter % g_knBlocks == 0) // Add blocks of rows to optimize loading
            m_pMaintable->setNumRows(m_pMaintable->numRows() + g_knBlocks);

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> User;             // Copy the user field
        if (!User.isEmpty())
            m_pMaintable->setText(nIter, TITRAQ_IDXUSER, User);
        else
            bValid = false;

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> Guid;             // Copy the GUID field
        // Postpone actual text delivery, to autoinsert GUIDs!

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> Crc;              // Copy the CRC field
        // Postpone actual text delivery, to autoinsert CRCs!

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> Rev;              // Copy the rev field
        if (!Rev.isEmpty())
            m_pMaintable->setText(nIter, TITRAQ_IDXREV, Rev);
        else
            bValid = false;

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> Date;             // Copy the date field
        if (!Date.isEmpty())
            m_pMaintable->setText(nIter, TITRAQ_IDXDATE, Date);
        else
            bValid = false;

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> Start;            // Copy the start field
        if (!Start.isEmpty())
            m_pMaintable->setText(nIter, TITRAQ_IDXSTART, Start);
        else
            bValid = false;

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> Finish;           // Copy the finish field
        if (!Finish.isEmpty())
            m_pMaintable->setText(nIter, TITRAQ_IDXFINISH, Finish);
        else
            bValid = false;

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> Amount;           // Copy the amount field
        if (!Amount.isEmpty())
            m_pMaintable->setText(nIter, TITRAQ_IDXAMOUNT, Amount);
        else
            bValid = false;

        Asline.skipWhiteSpace();    // Remove whitespaces
        Asline >> Account;          // Copy the account field
        if (!Account.isEmpty()) {
            QTableItem *pOld = m_pMaintable->item(nIter, TITRAQ_IDXTASK);
            delete(pOld);   // Get rid of the old before going with the new
            RtTableItem *pSwapitem = new RtTableItem(m_pMaintable, QTableItem::WhenCurrent, Account);
            pSwapitem->setAlignment(AlignLeft | AlignVCenter);
            m_pMaintable->setItem(nIter, TITRAQ_IDXTASK, pSwapitem);
        }
        else
            bValid = false;

        Asline.skipWhiteSpace();    // Remove whitespaces
        Remark = Asline.read();     // Copy the remark field

        QRegExp Quoted("\"(.*[^\\\\])\"");                  // Get rid of
        Quoted.search(Remark);                              // surrounding double
        Remark = Quoted.cap(Quoted.numCaptures());          // quotes, and
        Remark.replace(QRegExp("\\\\(.)"), QString("\\1")); // escape backslashes

        if (!Remark.isEmpty())
            m_pMaintable->setText(nIter, TITRAQ_IDXREMARK, Remark);

        if (bValid) { // Show a bitmap to signal valid or error state
            m_pMaintable->setText(nIter, TITRAQ_IDXSTATUS, QString(QChar('O')));
            m_pMaintable->setPixmap(nIter, TITRAQ_IDXSTATUS, Statokay);
        }
        else {
            m_pMaintable->setText(nIter, TITRAQ_IDXSTATUS, QString(QChar('E')));
            m_pMaintable->setPixmap(nIter, TITRAQ_IDXSTATUS, Staterror);
        }

        // Set the GUID text here, in case we need to generate a fresh value
        if (!Guid.isEmpty()) {
            if (Guid != ".")    // This means, generate a fresh GUID value
                m_pMaintable->setText(nIter, TITRAQ_IDXGUID, Guid);
            else {
                AS::Uuid Guidi; // For GUID production
                Guidi.genId();
                m_pMaintable->setText(nIter, TITRAQ_IDXGUID, Guidi.getString());
            }
        }
        else // if isEmpty()
            bValid = false;

        // Set the CRC text here, in case we need to generate a fresh value
        if (!Crc.isEmpty()) {
            if (Crc != ".") // This means, generate a fresh CRC value
                m_pMaintable->setText(nIter, TITRAQ_IDXCRC, "0x" + Crc);
            else {
                // Make our long tuple to run through the CRC32 generator
                Qualistring Testuple = User + Guid + Rev + Date + Start;
                            Testuple += Finish + Amount + Account + Remark;
                U32 Valcrc = Testuple.getCrc(); // Finally set the checksum to its new value
                QString Crcstr = QString::number(Valcrc, 16).rightJustify(8, '0');
                m_pMaintable->setText(nIter, TITRAQ_IDXCRC, "0x" + Crcstr);
            }
        }
        else // if isEmpty()
            bValid = false;

        nIter++;            // The big increment
        Line = QString(""); // Clear line for next round

        while (Line.isEmpty() && !Tstream.atEnd()) {    // Strip and get
            Tstream.skipWhiteSpace();                   // Remove white
            Line = Tstream.readLine();                  // the new line
            if (Line.at(0) == QChar('#'))               // Remove comments
                Line = QString("");
        }
    }

//    // Start sorting order and direction correctly according to user preferences
//    int nSortcol = (int)m_pPrefs->getNumber(TITRAQ_PREFSORTCOL, TITRAQ_DEFSORTCOL);
//    bool bSortdir = m_pPrefs->getBool(TITRAQ_PREFSORTDIR, TITRAQ_DEFSORTDIR);
//    m_pMaintable->setSortdir(!bSortdir); // Hack! Fake sortdir so we start right
//    m_pMaintable->sortColumn(nSortcol, bSortdir);

    // Write nonsaving line numbers for all rows
    for (int nIter = m_pMaintable->numRows() - 1; nIter >= 0; nIter--)
        m_pMaintable->setText(nIter, TITRAQ_IDXLINE, QString::number(nIter).rightJustify(4, QChar('0')));

    m_pMaintable->setNumRows(nIter);            // No excess rows
    m_pMaintable->setCurrentCell(nIter - 1, 0); // Move focus to last row
    m_pMaintable->setUpdatesEnabled(true);      // Update
    m_pMaintable->ensureCellVisible(nIter - 1, 0); // Scroll please

    if (!bValid)
        throw Genexcept("Warning: invalid accounting data.");
}

//
// Convenience method to save accounting data to a file
//
void Titraqform::saveData(QFile &Fileobj)
{
    if (Fileobj.isOpen()) {             // Check state of file
        Fileobj.flush();                // Begin processing file cleanly
        QTextStream Asentry(&Fileobj);  // Convert data to stream
        this->saveData(Asentry);        // Pass off to do the real work
    }
    else {
        if (!Fileobj.open(IO_WriteOnly)) // Try to open file
            throw Genexcept(trUtf8(TITRAQ_READPFILFAIL));
        QTextStream Asentry(&Fileobj);  // Convert data to stream
        this->saveData(Asentry);        // Pass off to do the real work
        Fileobj.close();                // Finish fileop by closing
    }
}

//
// Save accounting data to a stream
//
void Titraqform::saveData(QTextStream &Tstream)
{
    const int nRows = m_pMaintable->numRows();      // Max rows used in loop
    QString Tempfield;                              // Current field string
    QString Strsearch;                              // String to strip search
    QRegExp Stripper("\\s*$");                      // Pattern to strip off

    // Start by prepending the AS data format version symbol
    Tstream << TITRAQ_DATAPATTERN << TITRAQ_DATAVERSIONMAJ
            << QChar('.') << TITRAQ_DATAVERSIONMIN << endl;

    // Linewise save from the main table date, time, account, and others
    for (int nIter = 0; nIter < nRows; nIter++) {
        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXUSER);      // Load user field text
        if (Tempfield != NULL)
            Tstream << Tempfield;                                   // Save user field text

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXGUID);      // Load GUID field text
        if (Tempfield != NULL)
            Tstream << trUtf8(" ") << Tempfield;                    // Save GUID field text

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXCRC);       // Load CRC field text
        Tempfield.remove("0x");
        if (Tempfield != NULL)
            Tstream << trUtf8(" ") << Tempfield;                    // Save CRC field text

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXREV);       // Load rev field text
        if (Tempfield != NULL)
            Tstream << trUtf8(" ") << Tempfield;                    // Save rev field text

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXDATE);      // Load date field text
        if (Tempfield != NULL)
            Tstream << trUtf8(" ") << Tempfield;                    // Save date field text

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXSTART);     // Load start field text
        if (Tempfield != NULL)
            Tstream << trUtf8(" ") << Tempfield;                    // Save start field text

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXFINISH);    // Load end field text
        if (Tempfield != NULL)
            Tstream << trUtf8(" ") << Tempfield;                    // Save end field text

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXAMOUNT);    // Load amount field text
        if (Tempfield != NULL)
            Tstream << trUtf8(" ") << Tempfield;                    // Save amount

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXTASK);      // Load acct field text
        if (Tempfield != NULL)
            Tstream << trUtf8(" ") << Tempfield;                    // Save acct field text

        Tempfield = m_pMaintable->text(nIter, TITRAQ_IDXREMARK);    // Load remark field text
        Tstream << trUtf8(" \"");                                   // Save beginning double quote
        if (Tempfield != NULL) {
            Strsearch = QRegExp::escape(Tempfield);                 // Incoming string escaped
            Stripper.search(Strsearch);
            Tempfield.truncate(Stripper.pos());                     // Cut off whitespace
            Tempfield.replace(QChar('\\'), QString("\\\\"));        // Escape back slashes
            Tempfield.replace(QChar('\"'), QString("\\\""));        // Escape double quotes
            Tstream << Tempfield;                                   // Save remark field text
        }
        Tstream << trUtf8("\"");                                    // Save ending double quote

        Tstream << endl;                                            // Append a newline
    }
}

//
// Convenience method to validate AS data in a file
//
const bool Titraqform::validateData(QFile &Filin) const
{
    QString Firstline;      // Will contain the first line of text
    bool bRet = false;      // Set the initial return value

    if (Filin.isOpen()) {   // Check open state of file
        Filin.flush();      // Not sure if this is needed
        Filin.reset();      // Set the file index position to 0
        Filin.readLine(Firstline, QString(TITRAQ_DATAPATTERN).length() + 2L);
    }
    else {
        if (!Filin.open(IO_ReadOnly))   // Try to open file
            throw Genexcept(trUtf8(TITRAQ_READPFILFAIL));
        else {                          // File is now open
            Filin.readLine(Firstline, QString(TITRAQ_DATAPATTERN).length() + 2L);
            Filin.close();              // Remember to close
        }
    }

    try {   // Pass off to worker method
        bRet = this->validateData(Firstline);
    }
    catch (Genexcept &) {
        throw;  // Rethrow onwards
    }
    return bRet;
}

//
// Validate the AS data pattern in a line
//
const bool Titraqform::validateData(QString &Linin) const
{
    bool bRet = false;  // Initial return value

    // Ensure that the right data version pattern precedes the data
    QString Datapattern = QString(TITRAQ_DATAPATTERN);
    if (!Linin.startsWith(Datapattern)) { // Incompatible data format
        QMessageBox Problema(QString(TITRAQ_APPTITLE) + ' ' + asgui_version.v_short,
            TITRAQ_NOPATTERNFOUND + QString(TITRAQ_DATAPATTERN) + TITRAQ_WASNOTFOUNDIN,
            QMessageBox::Critical, QMessageBox::Ok | QMessageBox::Escape,
            QMessageBox::NoButton, QMessageBox::NoButton);
        Problema.exec(); // Give the user the bad news
        throw Genexcept(TITRAQ_INVALIDDATA);
    }
    else if (Linin.section(Datapattern, 1).section('.', 0, 0).toInt() != TITRAQ_DATAVERSIONMAJ) {
        QMessageBox Problema(QString(TITRAQ_APPTITLE) + ' ' + asgui_version.v_short,
            TITRAQ_BADVERSIONMAJ, QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Escape,
            QMessageBox::NoButton, QMessageBox::NoButton);
        Problema.exec(); // Give the user the bad news
        throw Genexcept(TITRAQ_INCOMPATDATA);
    }
    else if (Linin.section(Datapattern, 1).section('.', 1, 1).toInt() > TITRAQ_DATAVERSIONMIN) {
        QMessageBox Problema(QString(TITRAQ_APPTITLE) + ' ' + asgui_version.v_short,
            TITRAQ_BADVERSIONMIN, QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Escape,
            QMessageBox::NoButton, QMessageBox::NoButton);
        Problema.exec(); // Give the user the bad news
        throw Genexcept(TITRAQ_INCOMPATDATA);
    }
    else
        bRet = true;

    return bRet;    // Not reached in case of failure
}

//
// Get a whole row of data
//
const QString Titraqform::getRowdata(void) const
{
    QString Rowdata, Tempstring;                            // For output string
    QTableSelection Select = m_pMaintable->selection(0);    // Highlighted text
    int nTotal = Select.bottomRow() - Select.topRow() + 1;  // Total row select

    // Calculate rows to delete from selection highlight
    for (int nIter = 0; nIter < nTotal; ++nIter) {
        // Build the row data string one field at a time, adding seps inbetween
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXLINE);
        if (!Tempstring.isEmpty())
            Rowdata += Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXUSER);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXGUID);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXCRC);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXREV);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXDATE);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXSTART);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXFINISH);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXAMOUNT);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXTASK);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Tempstring = m_pMaintable->text(Select.topRow() + nIter, TITRAQ_IDXREMARK);
        if (!Tempstring.isEmpty())
            Rowdata += TITRAQ_SEPARATORTOK + Tempstring;
        Rowdata += trUtf8("\n");    // Finish off line
    }

    return Rowdata;
}

//
// Set a whole row of data
//
void Titraqform::setRowdata(QString &Rowdata) const
{
    int nRows = Rowdata.contains(QChar('\n'));          // Set so many rows
    int nCurrentrow = m_pMaintable->currentRow();       // Current table row
    QTableItem *pItem = NULL;                           // Old item to change out
    QString Line, User, Guid, Crc, Rev;                 // Admin fields in table
    QString Date, Start, Finish, Amount, Task, Remark;  // Viewable fields in table
    QTextStream Datastream(&Rowdata, IO_ReadOnly);      // Convert data to stream

    for (int nIter = 0; nIter < nRows; ++nIter) {
        QString Singlerow = Datastream.readLine();              // For linewise operation
        QTextStream Rowstream(&Singlerow, IO_ReadOnly);         // Convert row to stream

        Rowstream >> Line >> User >> Guid >> Crc >> Rev;        // Stream data fields
        Rowstream >> Date >> Start >> Finish >> Amount >> Task; // to corresponding vars
        Remark = Rowstream.readLine();                          // Remark is a whole line

        // Set the table row data one field at a time, skipping seps inbetween
        // Importantly, do not copy over the GUID, which must be unique each row
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXLINE, Line);
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXUSER, User);
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXCRC, Crc);
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXREV, Rev);
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXDATE, Date);
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXSTART, Start);
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXFINISH, Finish);
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXAMOUNT, Amount);

//        // FIXME: Why doesn't this code change the RtTableItem text in place?
//        RtTableItem *pTask = NULL;                      // Derived and special
//        pItem = m_pMaintable->item(nCurrentrow + nIter, TITRAQ_IDXTASK);
//        pTask = static_cast<RtTableItem *>(pItem);
//        pTask->setText(Task);

        // Change out old item and replace with new task data
        pItem = m_pMaintable->item(nCurrentrow + nIter, TITRAQ_IDXTASK);
        delete(pItem);  // Get rid of the old before going with the new
        RtTableItem *pSwapitem = new RtTableItem(m_pMaintable, QTableItem::WhenCurrent, Task);
        pSwapitem->setAlignment(AlignLeft | AlignVCenter);
        m_pMaintable->setItem(nCurrentrow + nIter, TITRAQ_IDXTASK, pSwapitem);

        // Continue with field processing business as usual
        m_pMaintable->setText(nCurrentrow + nIter, TITRAQ_IDXREMARK, Remark.simplifyWhiteSpace());
    }
}

//
// Discover which column is the first in view
//
const int Titraqform::getFirstcol(void) const
{
    int nIter = 0;  // Is both iterator in loop and column number returned

    // Use column selector menu popup as sole key to determining column state
    while (nIter < TITRAQ_IDXTAIL && !m_pColspopup->isItemChecked(m_pColspopup->idAt(++nIter)))
        TITRAQ_NOP;

    return nIter - 1; // Return one less to account for start of item offset
}

CVSTrac 2.0.1