andre@1: andre@1: /**************************************************************************** andre@1: ** Copyright (c) 2006 - 2011, the LibQxt project. andre@1: ** See the Qxt AUTHORS file for a list of authors and copyright holders. andre@1: ** All rights reserved. andre@1: ** andre@1: ** Redistribution and use in source and binary forms, with or without andre@1: ** modification, are permitted provided that the following conditions are met: andre@1: ** * Redistributions of source code must retain the above copyright andre@1: ** notice, this list of conditions and the following disclaimer. andre@1: ** * Redistributions in binary form must reproduce the above copyright andre@1: ** notice, this list of conditions and the following disclaimer in the andre@1: ** documentation and/or other materials provided with the distribution. andre@1: ** * Neither the name of the LibQxt project nor the andre@1: ** names of its contributors may be used to endorse or promote products andre@1: ** derived from this software without specific prior written permission. andre@1: ** andre@1: ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND andre@1: ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED andre@1: ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE andre@1: ** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY andre@1: ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES andre@1: ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; andre@1: ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND andre@1: ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT andre@1: ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS andre@1: ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. andre@1: ** andre@1: ** andre@1: *****************************************************************************/ andre@1: andre@1: /*! andre@1: \class QxtCsvModel andre@1: \inmodule QxtCore andre@1: \brief The QxtCsvModel class provides a QAbstractTableModel for CSV Files andre@1: */ andre@1: andre@1: andre@1: andre@1: #include "qxtcsvmodel.h" andre@1: #include andre@1: #include andre@1: #include andre@1: andre@1: class QxtCsvModelPrivate : public QxtPrivate andre@1: { andre@1: public: andre@81: QxtCsvModelPrivate() : csvData(), header(), header_tt(), maxColumn(0), quoteMode(QxtCsvModel::DefaultQuoteMode) andre@1: {} andre@1: QXT_DECLARE_PUBLIC(QxtCsvModel) andre@1: andre@1: QList csvData; andre@1: QStringList header; andre@81: QStringList header_tt; andre@1: int maxColumn; andre@1: QxtCsvModel::QuoteMode quoteMode; andre@1: }; andre@1: andre@1: /*! andre@1: Creates an empty QxtCsvModel with parent \a parent. andre@1: */ andre@1: QxtCsvModel::QxtCsvModel(QObject *parent) : QAbstractTableModel(parent) andre@1: { andre@1: QXT_INIT_PRIVATE(QxtCsvModel); andre@1: } andre@1: andre@1: /*! andre@1: Creates a QxtCsvModel with the parent \a parent and content loaded from \a file. andre@1: andre@1: See \a setSource for information on the \a withHeader and \a separator properties, or andre@1: if you need control over the quoting method or codec used to parse the file. andre@1: andre@1: \sa setSource andre@1: */ andre@1: QxtCsvModel::QxtCsvModel(QIODevice *file, QObject *parent, bool withHeader, QChar separator) : QAbstractTableModel(parent) andre@1: { andre@1: QXT_INIT_PRIVATE(QxtCsvModel); andre@1: setSource(file, withHeader, separator); andre@1: } andre@1: andre@1: /*! andre@1: \overload andre@1: andre@1: Creates a QxtCsvModel with the parent \a parent and content loaded from \a file. andre@1: andre@1: See \a setSource for information on the \a withHeader and \a separator properties, or andre@1: if you need control over the quoting method or codec used to parse the file. andre@1: andre@1: \sa setSource andre@1: */ andre@1: QxtCsvModel::QxtCsvModel(const QString filename, QObject *parent, bool withHeader, QChar separator) : QAbstractTableModel(parent) andre@1: { andre@1: QXT_INIT_PRIVATE(QxtCsvModel); andre@1: QFile src(filename); andre@1: setSource(&src, withHeader, separator); andre@1: } andre@1: andre@1: QxtCsvModel::~QxtCsvModel() andre@1: {} andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: int QxtCsvModel::rowCount(const QModelIndex& parent) const andre@1: { andre@1: if (parent.row() != -1 && parent.column() != -1) return 0; andre@1: return qxt_d().csvData.count(); andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: int QxtCsvModel::columnCount(const QModelIndex& parent) const andre@1: { andre@1: if (parent.row() != -1 && parent.column() != -1) return 0; andre@1: return qxt_d().maxColumn; andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: QVariant QxtCsvModel::data(const QModelIndex& index, int role) const andre@1: { andre@1: if(index.parent() != QModelIndex()) return QVariant(); andre@1: if(role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) { andre@1: if(index.row() < 0 || index.column() < 0 || index.row() >= rowCount()) andre@1: return QVariant(); andre@1: const QStringList& row = qxt_d().csvData[index.row()]; andre@1: if(index.column() >= row.length()) andre@1: return QVariant(); andre@1: return row[index.column()]; andre@1: } andre@1: return QVariant(); andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: QVariant QxtCsvModel::headerData(int section, Qt::Orientation orientation, int role) const andre@1: { andre@1: if(section < qxt_d().header.count() && orientation == Qt::Horizontal && (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole)) andre@1: return qxt_d().header[section]; andre@81: else if (section < qxt_d().header_tt.count() && role == Qt::ToolTipRole) { andre@81: return qxt_d().header_tt[section]; andre@81: } else andre@1: return QAbstractTableModel::headerData(section, orientation, role); andre@1: } andre@1: andre@1: /*! andre@1: \overload andre@1: andre@1: Reads in a CSV file from the provided \a file using \a codec. andre@1: */ andre@1: void QxtCsvModel::setSource(const QString filename, bool withHeader, QChar separator, QTextCodec* codec) andre@1: { andre@1: QFile src(filename); andre@1: setSource(&src, withHeader, separator, codec); andre@1: } andre@1: andre@1: /*! andre@1: Reads in a CSV file from the provided \a file using \a codec. andre@1: andre@1: The value of \a separator will be used to delimit fields, subject to the specified \a quoteMode. andre@1: If \a withHeader is set to true, the first line of the file will be used to populate the model's andre@1: horizontal header. andre@1: andre@1: \sa quoteMode andre@1: */ andre@1: void QxtCsvModel::setSource(QIODevice *file, bool withHeader, QChar separator, QTextCodec* codec) andre@1: { andre@1: QxtCsvModelPrivate* d_ptr = &qxt_d(); andre@1: bool headerSet = !withHeader; andre@1: if(!file->isOpen()) andre@1: file->open(QIODevice::ReadOnly); andre@1: if(withHeader) andre@1: d_ptr->maxColumn = 0; andre@1: else andre@1: d_ptr->maxColumn = d_ptr->header.size(); andre@1: d_ptr->csvData.clear(); andre@1: QStringList row; andre@1: QString field; andre@1: QChar quote; andre@1: QChar ch, buffer(0); andre@1: bool readCR = false; andre@37: int idx_nr = 1; // XXX added for retraceit andre@1: QTextStream stream(file); andre@1: if(codec) { andre@1: stream.setCodec(codec); andre@1: } else { andre@1: stream.setAutoDetectUnicode(true); andre@1: } andre@1: while(!stream.atEnd()) { andre@1: if(buffer != QChar(0)) { andre@1: ch = buffer; andre@1: buffer = QChar(0); andre@1: } else { andre@1: stream >> ch; andre@1: } andre@1: if(ch == '\n' && readCR) andre@1: continue; andre@1: else if(ch == '\r') andre@1: readCR = true; andre@1: else andre@1: readCR = false; andre@1: if(ch != separator && (ch.category() == QChar::Separator_Line || ch.category() == QChar::Separator_Paragraph || ch.category() == QChar::Other_Control)) { andre@1: row << field; andre@1: field.clear(); andre@1: if(!row.isEmpty()) { andre@40: row.insert(0, headerSet ? QString::number(idx_nr++) : QString::fromLatin1("#Index Nr.")); // XXX added for retraceit andre@1: if(!headerSet) { andre@1: d_ptr->header = row; andre@1: headerSet = true; andre@1: } else { andre@1: d_ptr->csvData.append(row); andre@1: } andre@1: if(row.length() > d_ptr->maxColumn) { andre@1: d_ptr->maxColumn = row.length(); andre@1: } andre@1: } andre@1: row.clear(); andre@1: } else if((d_ptr->quoteMode & DoubleQuote && ch == '"') || (d_ptr->quoteMode & SingleQuote && ch == '\'')) { andre@1: quote = ch; andre@1: do { andre@1: stream >> ch; andre@1: if(ch == '\\' && d_ptr->quoteMode & BackslashEscape) { andre@1: stream >> ch; andre@1: } else if(ch == quote) { andre@1: if(d_ptr->quoteMode & TwoQuoteEscape) { andre@1: stream >> buffer; andre@1: if(buffer == quote) { andre@1: buffer = QChar(0); andre@1: field.append(ch); andre@1: continue; andre@1: } andre@1: } andre@1: break; andre@1: } andre@1: field.append(ch); andre@1: } while(!stream.atEnd()); andre@1: } else if(ch == separator) { andre@1: row << field; andre@1: field.clear(); andre@1: } else { andre@1: field.append(ch); andre@1: } andre@1: } andre@1: if(!field.isEmpty()) andre@1: row << field; andre@1: if(!row.isEmpty()) { andre@37: row.insert(0, headerSet ? QString::number(idx_nr++) : QString::fromLatin1("#Index Nr.")); // XXX added for relayit andre@1: if(!headerSet) andre@1: d_ptr->header = row; andre@1: else andre@1: d_ptr->csvData.append(row); andre@1: } andre@1: file->close(); andre@1: } andre@1: andre@1: /*! andre@1: Sets the horizontal headers of the model to the values provided in \a data. andre@1: */ andre@1: void QxtCsvModel::setHeaderData(const QStringList& data) andre@1: { andre@1: qxt_d().header = data; andre@1: emit headerDataChanged(Qt::Horizontal, 0, data.count()); andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) andre@1: { andre@1: if(section < 0) return false; // Bogus input andre@1: while(section > qxt_d().header.size()) { andre@1: qxt_d().header << QString(); andre@1: } andre@81: while(section >= qxt_d().header_tt.size()) { andre@81: qxt_d().header_tt << QString(); andre@81: } andre@81: qDebug() << "Sect " << section << " size " << qxt_d().header_tt.size(); andre@81: if(orientation != Qt::Horizontal) return false; // We don't support the vertical header andre@81: if(role == Qt::ToolTipRole) { andre@81: qxt_d().header_tt[section] = value.toString(); andre@81: emit headerDataChanged(Qt::Horizontal, section, section); andre@81: return true; andre@81: } andre@81: if(role != Qt::DisplayRole && role != Qt::EditRole) return false; // We don't support any other roles andre@1: qxt_d().header[section] = value.toString(); andre@1: emit headerDataChanged(Qt::Horizontal, section, section); andre@1: return true; andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::setData(const QModelIndex& index, const QVariant& data, int role) andre@1: { andre@1: if (index.parent() != QModelIndex()) return false; andre@1: andre@1: if(role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) { andre@1: if(index.row() >= rowCount() || index.column() >= columnCount() || index.row() < 0 || index.column() < 0) return false; andre@1: QStringList& row = qxt_d().csvData[index.row()]; andre@1: while(row.length() <= index.column()) andre@1: row << QString(); andre@1: row[index.column()] = data.toString(); andre@1: emit dataChanged(index, index); andre@1: return true; andre@1: } andre@1: return false; andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::insertRow(int row, const QModelIndex& parent) andre@1: { andre@1: return insertRows(row, 1, parent); andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::insertRows(int row, int count, const QModelIndex& parent) andre@1: { andre@1: if (parent != QModelIndex() || row < 0) return false; andre@1: emit beginInsertRows(parent, row, row + count); andre@1: QxtCsvModelPrivate& d_ptr = qxt_d(); andre@1: if(row >= rowCount()) { andre@1: for(int i = 0; i < count; i++) d_ptr.csvData << QStringList(); andre@1: } else { andre@1: for(int i = 0; i < count; i++) d_ptr.csvData.insert(row, QStringList()); andre@1: } andre@1: emit endInsertRows(); andre@1: return true; andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::removeRow(int row, const QModelIndex& parent) andre@1: { andre@1: return removeRows(row, 1, parent); andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::removeRows(int row, int count, const QModelIndex& parent) andre@1: { andre@1: if (parent != QModelIndex() || row < 0) return false; andre@1: if (row >= rowCount()) return false; andre@1: if (row + count >= rowCount()) count = rowCount() - row; andre@1: emit beginRemoveRows(parent, row, row + count); andre@1: QxtCsvModelPrivate& d_ptr = qxt_d(); andre@1: for (int i = 0;i < count;i++) andre@1: d_ptr.csvData.removeAt(row); andre@1: emit endRemoveRows(); andre@1: return true; andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::insertColumn(int col, const QModelIndex& parent) andre@1: { andre@1: return insertColumns(col, 1, parent); andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::insertColumns(int col, int count, const QModelIndex& parent) andre@1: { andre@1: if (parent != QModelIndex() || col < 0) return false; andre@1: beginInsertColumns(parent, col, col + count - 1); andre@1: QxtCsvModelPrivate& d_ptr = qxt_d(); andre@1: for(int i = 0; i < rowCount(); i++) { andre@1: QStringList& row = d_ptr.csvData[i]; andre@1: while(col >= row.length()) row.append(QString()); andre@1: for(int j = 0; j < count; j++) { andre@1: row.insert(col, QString()); andre@1: } andre@1: } andre@81: for(int i = 0; i < count ;i++) { andre@1: d_ptr.header.insert(col, QString()); andre@81: d_ptr.header_tt.insert(col, QString()); andre@81: } andre@1: d_ptr.maxColumn += count; andre@1: endInsertColumns(); andre@1: return true; andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::removeColumn(int col, const QModelIndex& parent) andre@1: { andre@1: return removeColumns(col, 1, parent); andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: bool QxtCsvModel::removeColumns(int col, int count, const QModelIndex& parent) andre@1: { andre@1: if (parent != QModelIndex() || col < 0) return false; andre@1: if (col >= columnCount()) return false; andre@1: if (col + count >= columnCount()) count = columnCount() - col; andre@1: emit beginRemoveColumns(parent, col, col + count); andre@1: QxtCsvModelPrivate& d_ptr = qxt_d(); andre@1: QString before, after; andre@1: for(int i = 0; i < rowCount(); i++) { andre@1: for(int j = 0; j < count; j++) { andre@1: d_ptr.csvData[i].removeAt(col); andre@1: } andre@1: } andre@81: for(int i = 0; i < count; i++) { andre@1: d_ptr.header.removeAt(col); andre@81: d_ptr.header_tt.removeAt(col); andre@81: } andre@1: emit endRemoveColumns(); andre@1: return true; andre@1: } andre@1: andre@1: static QString qxt_addCsvQuotes(QxtCsvModel::QuoteMode mode, QString field) andre@1: { andre@1: bool addDoubleQuotes = ((mode & QxtCsvModel::DoubleQuote) && field.contains('"')); andre@1: bool addSingleQuotes = ((mode & QxtCsvModel::SingleQuote) && field.contains('\'')); andre@1: bool quoteField = (mode & QxtCsvModel::AlwaysQuoteOutput) || addDoubleQuotes || addSingleQuotes; andre@1: if(quoteField && !addDoubleQuotes && !addSingleQuotes) { andre@1: if(mode & QxtCsvModel::DoubleQuote) andre@1: addDoubleQuotes = true; andre@1: else if(mode & QxtCsvModel::SingleQuote) andre@1: addSingleQuotes = true; andre@1: } andre@1: if(mode & QxtCsvModel::BackslashEscape) { andre@1: if(addDoubleQuotes) andre@1: return '"' + field.replace("\\", "\\\\").replace("\"", "\\\"") + '"'; andre@1: if(addSingleQuotes) andre@1: return '\'' + field.replace("\\", "\\\\").replace("'", "\\'") + '\''; andre@1: } else { andre@1: if(addDoubleQuotes) andre@1: return '"' + field.replace("\"", "\"\"") + '"'; andre@1: if(addSingleQuotes) andre@1: return '\'' + field.replace("'", "''") + '\''; andre@1: } andre@1: return field; andre@1: } andre@1: andre@1: /*! andre@1: Outputs the content of the model as a CSV file to the device \a dest using \a codec. andre@1: andre@1: Fields in the output file will be separated by \a separator. Set \a withHeader to true andre@1: to output a row of headers at the top of the file. andre@1: */ andre@1: void QxtCsvModel::toCSV(QIODevice* dest, bool withHeader, QChar separator, QTextCodec* codec) const andre@1: { andre@1: const QxtCsvModelPrivate& d_ptr = qxt_d(); andre@1: int row, col, rows, cols; andre@1: rows = rowCount(); andre@1: cols = columnCount(); andre@1: QString data; andre@1: if(!dest->isOpen()) dest->open(QIODevice::WriteOnly | QIODevice::Truncate); andre@1: QTextStream stream(dest); andre@1: if(codec) stream.setCodec(codec); andre@1: if(withHeader) { andre@1: data = ""; andre@1: for(col = 0; col < cols; ++col) { andre@1: if(col > 0) data += separator; andre@1: data += qxt_addCsvQuotes(d_ptr.quoteMode, d_ptr.header.at(col)); andre@1: } andre@1: stream << data << endl; andre@1: } andre@1: for(row = 0; row < rows; ++row) andre@1: { andre@1: const QStringList& rowData = d_ptr.csvData[row]; andre@1: data = ""; andre@1: for(col = 0; col < cols; ++col) { andre@1: if(col > 0) data += separator; andre@1: if(col < rowData.length()) andre@1: data += qxt_addCsvQuotes(d_ptr.quoteMode, rowData.at(col)); andre@1: else andre@1: data += qxt_addCsvQuotes(d_ptr.quoteMode, QString());; andre@1: } andre@1: stream << data << endl; andre@1: } andre@1: stream << flush; andre@1: dest->close(); andre@1: } andre@1: andre@1: /*! andre@1: \overload andre@1: andre@1: Outputs the content of the model as a CSV file to the file specified by \a filename using \a codec. andre@1: andre@1: Fields in the output file will be separated by \a separator. Set \a withHeader to true andre@1: to output a row of headers at the top of the file. andre@1: */ andre@1: void QxtCsvModel::toCSV(const QString filename, bool withHeader, QChar separator, QTextCodec* codec) const andre@1: { andre@1: QFile dest(filename); andre@1: toCSV(&dest, withHeader, separator, codec); andre@1: } andre@1: andre@1: /*! andre@1: \reimp andre@1: */ andre@1: Qt::ItemFlags QxtCsvModel::flags(const QModelIndex& index) const andre@1: { andre@1: return Qt::ItemIsEditable | QAbstractTableModel::flags(index); andre@1: } andre@1: andre@1: /*! andre@1: * Returns the current quoting mode. andre@1: * \sa setQuoteMode andre@1: */ andre@1: QxtCsvModel::QuoteMode QxtCsvModel::quoteMode() const andre@1: { andre@1: return qxt_d().quoteMode; andre@1: } andre@1: andre@1: /*! andre@1: * Sets the current quoting mode. The default quoting mode is BothQuotes | BackslashEscape. andre@1: * andre@1: * The quoting mode determines what kinds of quoting is used for reading and writing CSV files. andre@1: * \sa quoteMode andre@1: * \sa QuoteOption andre@1: */ andre@1: void QxtCsvModel::setQuoteMode(QuoteMode mode) andre@1: { andre@1: qxt_d().quoteMode = mode; andre@1: } andre@1: andre@1: /*! andre@1: Sets the content of the cell at row \a row and column \a column to \a value. andre@1: andre@1: \sa text andre@1: */ andre@1: void QxtCsvModel::setText(int row, int column, const QString& value) andre@1: { andre@1: setData(index(row, column), value); andre@1: } andre@1: andre@1: /*! andre@1: Fetches the content of the cell at row \a row and column \a column. andre@1: andre@1: \sa setText andre@1: */ andre@1: QString QxtCsvModel::text(int row, int column) const andre@1: { andre@1: return data(index(row, column)).toString(); andre@1: } andre@1: andre@1: /*! andre@1: Sets the content of the header for column \a column to \a value. andre@1: andre@1: \sa headerText andre@1: */ andre@1: void QxtCsvModel::setHeaderText(int column, const QString& value) andre@1: { andre@1: setHeaderData(column, Qt::Horizontal, value); andre@1: } andre@1: andre@1: /*! andre@1: Fetches the content of the cell at row \a row and column \a column. andre@1: andre@1: \sa setText andre@1: */ andre@1: QString QxtCsvModel::headerText(int column) const andre@1: { andre@1: return headerData(column, Qt::Horizontal).toString(); andre@1: }