From 32d391c587c39a3168a79e443d7d161ddfa2cb8c Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Wed, 1 Jun 2016 16:37:42 +0300 Subject: [PATCH] Resolved issue #409. New feature: Export measurement file to Excel .csv. --HG-- branch : develop --- ChangeLog.txt | 1 + src/app/tape/dialogs/dialogabouttape.h | 2 +- src/app/tape/dialogs/dialogexporttocsv.cpp | 154 +++++ src/app/tape/dialogs/dialogexporttocsv.h | 62 ++ src/app/tape/dialogs/dialogexporttocsv.ui | 165 +++++ src/app/tape/tape.pri | 5 +- src/app/tape/tmainwindow.cpp | 92 ++- src/app/tape/tmainwindow.h | 1 + src/app/tape/tmainwindow.ui | 9 + src/libs/vmisc/def.h | 91 +++ src/libs/vmisc/qxtcsvmodel.cpp | 729 +++++++++++++++++++++ src/libs/vmisc/qxtcsvmodel.h | 113 ++++ src/libs/vmisc/vmisc.pri | 6 +- src/libs/vmisc/vtapesettings.cpp | 72 ++ src/libs/vmisc/vtapesettings.h | 12 + 15 files changed, 1508 insertions(+), 6 deletions(-) create mode 100644 src/app/tape/dialogs/dialogexporttocsv.cpp create mode 100644 src/app/tape/dialogs/dialogexporttocsv.h create mode 100644 src/app/tape/dialogs/dialogexporttocsv.ui create mode 100644 src/libs/vmisc/qxtcsvmodel.cpp create mode 100644 src/libs/vmisc/qxtcsvmodel.h diff --git a/ChangeLog.txt b/ChangeLog.txt index c1eef42df..16df511b4 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -24,6 +24,7 @@ - [#472] Add 'Full Name' column to Formula dialog. - [#487] True dart point always goes to origin when the label is moved. - [#128] New Tool: Slash and Spread. +- [#409] New feature: Export measurement file to Excel .csv. # Version 0.4.5 - [#435] Valentina doesn't change the cursor. diff --git a/src/app/tape/dialogs/dialogabouttape.h b/src/app/tape/dialogs/dialogabouttape.h index d3927dfd6..64039ed04 100644 --- a/src/app/tape/dialogs/dialogabouttape.h +++ b/src/app/tape/dialogs/dialogabouttape.h @@ -41,7 +41,7 @@ class DialogAboutTape : public QDialog public: explicit DialogAboutTape(QWidget *parent = 0); - ~DialogAboutTape(); + virtual ~DialogAboutTape(); protected: virtual void changeEvent(QEvent* event) Q_DECL_OVERRIDE; diff --git a/src/app/tape/dialogs/dialogexporttocsv.cpp b/src/app/tape/dialogs/dialogexporttocsv.cpp new file mode 100644 index 000000000..d1058598c --- /dev/null +++ b/src/app/tape/dialogs/dialogexporttocsv.cpp @@ -0,0 +1,154 @@ +/************************************************************************ + ** + ** @file dialogexporttocsv.cpp + ** @author Roman Telezhynskyi + ** @date 1 6, 2016 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentine project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2016 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ + +#include "dialogexporttocsv.h" +#include "ui_dialogexporttocsv.h" + +#include "../vmisc/vtapesettings.h" +#include "../mapplication.h" + +#include +#include + +//--------------------------------------------------------------------------------------------------------------------- +DialogExportToCSV::DialogExportToCSV(QWidget *parent) + : QDialog(parent), + ui(new Ui::DialogExportToCSV), + isInitialized(false) +{ + ui->setupUi(this); + + ui->checkBoxWithHeader->setChecked(qApp->TapeSettings()->GetCSVWithHeader()); + + foreach (int mib, QTextCodec::availableMibs()) + { + ui->comboBoxCodec->addItem(QTextCodec::codecForMib(mib)->name(), mib); + } + + ui->comboBoxCodec->setCurrentIndex(ui->comboBoxCodec->findData(qApp->TapeSettings()->GetCSVCodec())); + + SetSeparator(qApp->TapeSettings()->GetCSVSeparator()); +} + +//--------------------------------------------------------------------------------------------------------------------- +DialogExportToCSV::~DialogExportToCSV() +{ + delete ui; +} + +//--------------------------------------------------------------------------------------------------------------------- +bool DialogExportToCSV::WithHeader() const +{ + return ui->checkBoxWithHeader->isChecked(); +} + +//--------------------------------------------------------------------------------------------------------------------- +int DialogExportToCSV::SelectedMib() const +{ +#if QT_VERSION < QT_VERSION_CHECK(5, 2, 0) + return ui->comboBoxCodec->itemData(ui->comboBoxCodec->currentIndex()).toInt(); +#else + return ui->comboBoxCodec->currentData().toInt(); +#endif +} + +//--------------------------------------------------------------------------------------------------------------------- +QChar DialogExportToCSV::Separator() const +{ + if (ui->radioButtonTab->isChecked()) + { + return QChar('\t'); + } + else if (ui->radioButtonSemicolon->isChecked()) + { + return QChar(';'); + } + else if (ui->radioButtonSpace->isChecked()) + { + return QChar(' '); + } + else + { + return QChar(','); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void DialogExportToCSV::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::LanguageChange) + { + // retranslate designer form (single inheritance approach) + ui->retranslateUi(this); + } + + // remember to call base class implementation + QDialog::changeEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void DialogExportToCSV::showEvent(QShowEvent *event) +{ + QDialog::showEvent( event ); + if ( event->spontaneous() ) + { + return; + } + + if (isInitialized) + { + return; + } + // do your init stuff here + + setMaximumSize(size()); + setMinimumSize(size()); + + isInitialized = true;//first show windows are held +} + +//--------------------------------------------------------------------------------------------------------------------- +void DialogExportToCSV::SetSeparator(const QChar &separator) +{ + switch(separator.toLatin1()) + { + case '\t': + ui->radioButtonTab->setChecked(true); + break; + case ';': + ui->radioButtonSemicolon->setChecked(true); + break; + case ' ': + ui->radioButtonSpace->setChecked(true); + break; + case ',': + default: + ui->radioButtonComma->setChecked(true); + break; + } +} diff --git a/src/app/tape/dialogs/dialogexporttocsv.h b/src/app/tape/dialogs/dialogexporttocsv.h new file mode 100644 index 000000000..5c684d934 --- /dev/null +++ b/src/app/tape/dialogs/dialogexporttocsv.h @@ -0,0 +1,62 @@ +/************************************************************************ + ** + ** @file dialogexporttocsv.h + ** @author Roman Telezhynskyi + ** @date 1 6, 2016 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentine project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2016 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ + +#ifndef DIALOGEXPORTTOCSV_H +#define DIALOGEXPORTTOCSV_H + +#include + +namespace Ui { +class DialogExportToCSV; +} + +class DialogExportToCSV : public QDialog +{ + Q_OBJECT + +public: + explicit DialogExportToCSV(QWidget *parent = nullptr); + virtual ~DialogExportToCSV(); + + bool WithHeader() const; + int SelectedMib() const; + QChar Separator() const; + +protected: + virtual void changeEvent(QEvent* event) Q_DECL_OVERRIDE; + virtual void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(DialogExportToCSV) + Ui::DialogExportToCSV *ui; + bool isInitialized; + + void SetSeparator(const QChar &separator); +}; + +#endif // DIALOGEXPORTTOCSV_H diff --git a/src/app/tape/dialogs/dialogexporttocsv.ui b/src/app/tape/dialogs/dialogexporttocsv.ui new file mode 100644 index 000000000..786270467 --- /dev/null +++ b/src/app/tape/dialogs/dialogexporttocsv.ui @@ -0,0 +1,165 @@ + + + DialogExportToCSV + + + Qt::ApplicationModal + + + + 0 + 0 + 298 + 292 + + + + Export options + + + + :/tapeicon/64x64/logo.png:/tapeicon/64x64/logo.png + + + true + + + + + + Export + + + + + + With header + + + + + + + + + + 0 + 0 + + + + Codec: + + + + + + + + + + + + + + + Separator + + + + + + Tab + + + buttonGroup + + + + + + + Comma + + + true + + + buttonGroup + + + + + + + Semicolon + + + buttonGroup + + + + + + + Space + + + buttonGroup + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DialogExportToCSV + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogExportToCSV + reject() + + + 316 + 260 + + + 286 + 274 + + + + + + + + diff --git a/src/app/tape/tape.pri b/src/app/tape/tape.pri index 8ce98fcd7..a8671ed02 100644 --- a/src/app/tape/tape.pri +++ b/src/app/tape/tape.pri @@ -11,6 +11,7 @@ SOURCES += \ $$PWD/dialogs/tapeconfigdialog.cpp \ $$PWD/dialogs/configpages/tapeconfigurationpage.cpp \ $$PWD/dialogs/configpages/tapepathpage.cpp \ + $$PWD/dialogs/dialogexporttocsv.cpp \ $$PWD/vlitepattern.cpp \ $$PWD/vtablesearch.cpp @@ -27,6 +28,7 @@ HEADERS += \ $$PWD/dialogs/tapeconfigdialog.h \ $$PWD/dialogs/configpages/tapeconfigurationpage.h \ $$PWD/dialogs/configpages/tapepathpage.h \ + $$PWD/dialogs/dialogexporttocsv.h \ $$PWD/vlitepattern.h \ $$PWD/vtablesearch.h @@ -34,4 +36,5 @@ FORMS += \ $$PWD/tmainwindow.ui \ $$PWD/dialogs/dialogabouttape.ui \ $$PWD/dialogs/dialognewmeasurements.ui \ - $$PWD/dialogs/dialogmdatabase.ui + $$PWD/dialogs/dialogmdatabase.ui \ + $$PWD/dialogs/dialogexporttocsv.ui diff --git a/src/app/tape/tmainwindow.cpp b/src/app/tape/tmainwindow.cpp index 7df09786b..6ade33027 100644 --- a/src/app/tape/tmainwindow.cpp +++ b/src/app/tape/tmainwindow.cpp @@ -32,6 +32,7 @@ #include "dialogs/dialognewmeasurements.h" #include "dialogs/dialogmdatabase.h" #include "dialogs/tapeconfigdialog.h" +#include "dialogs/dialogexporttocsv.h" #include "../vpatterndb/calculator.h" #include "../ifc/ifcdef.h" #include "../ifc/xml/vvitconverter.h" @@ -39,6 +40,7 @@ #include "../ifc/xml/vpatternconverter.h" #include "../vmisc/vlockguard.h" #include "../vmisc/vsysexits.h" +#include "../vmisc/qxtcsvmodel.h" #include "vlitepattern.h" #include "../qmuparser/qmudef.h" #include "../vtools/dialogs/support/dialogeditwrongformula.h" @@ -51,6 +53,7 @@ #include #include #include +#include #if defined(Q_OS_MAC) #include @@ -679,6 +682,80 @@ void TMainWindow::FileSaveAs() } } +//--------------------------------------------------------------------------------------------------------------------- +void TMainWindow::ExportToCSV() +{ + const QString filters = tr("Comma-Separated Values (*.cvs)"); + const QString suffix("csv"); + const QString path = QDir::homePath() + "/" + tr("measurements"); + "." + suffix; + + QString fileName = QFileDialog::getSaveFileName(this, tr("Export to CSV"), path, filters); + + if (fileName.isEmpty()) + { + return; + } + + QFileInfo f( fileName ); + if (f.suffix().isEmpty() && f.suffix() != suffix) + { + fileName += "." + suffix; + } + + DialogExportToCSV dialog(this); + if (dialog.exec() == QDialog::Accepted) + { + QxtCsvModel csv; + const int columns = ui->tableWidget->columnCount(); + { + int colCount = 0; + for (int column = 0; column < columns; ++column) + { + if (not ui->tableWidget->isColumnHidden(column)) + { + csv.insertColumn(colCount++); + } + } + } + + if (dialog.WithHeader()) + { + int colCount = 0; + for (int column = 0; column < columns; ++column) + { + if (not ui->tableWidget->isColumnHidden(column)) + { + QTableWidgetItem *header = ui->tableWidget->horizontalHeaderItem(colCount); + csv.setHeaderText(colCount, header->text()); + ++colCount; + } + } + } + + const int rows = ui->tableWidget->rowCount(); + for (int row = 0; row < rows; ++row) + { + csv.insertRow(row); + int colCount = 0; + for (int column = 0; column < columns; ++column) + { + if (not ui->tableWidget->isColumnHidden(column)) + { + QTableWidgetItem *item = ui->tableWidget->item(row, column); + csv.setText(row, colCount, item->text()); + ++colCount; + } + } + } + + csv.toCSV(fileName, dialog.WithHeader(), dialog.Separator(), QTextCodec::codecForMib(dialog.SelectedMib())); + + qApp->TapeSettings()->SetCSVSeparator(dialog.Separator()); + qApp->TapeSettings()->SetCSVCodec(dialog.SelectedMib()); + qApp->TapeSettings()->SetCSVWithHeader(dialog.WithHeader()); + } +} + //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::AboutToShowWindowMenu() { @@ -863,6 +940,8 @@ void TMainWindow::Remove() { MFields(false); + ui->actionExportToCSV->setEnabled(false); + ui->lineEditName->blockSignals(true); ui->lineEditName->setText(""); ui->lineEditName->blockSignals(false); @@ -1060,13 +1139,15 @@ void TMainWindow::AddCustom() ui->tableWidget->selectRow(currentRow); + ui->actionExportToCSV->setEnabled(true); + MeasurementsWasSaved(false); } //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::AddKnown() { - DialogMDataBase *dialog = new DialogMDataBase(m->ListKnown(), this); + QScopedPointer dialog (new DialogMDataBase(m->ListKnown(), this)); if (dialog->exec() == QDialog::Accepted) { qint32 currentRow; @@ -1114,9 +1195,10 @@ void TMainWindow::AddKnown() ui->tableWidget->selectRow(currentRow); + ui->actionExportToCSV->setEnabled(true); + MeasurementsWasSaved(false); } - delete dialog; } //--------------------------------------------------------------------------------------------------------------------- @@ -1732,6 +1814,7 @@ void TMainWindow::SetupMenu() connect(ui->actionSaveAs, &QAction::triggered, this, &TMainWindow::FileSaveAs); ui->actionSaveAs->setShortcuts(QKeySequence::SaveAs); + connect(ui->actionExportToCSV, &QAction::triggered, this, &TMainWindow::ExportToCSV); connect(ui->actionReadOnly, &QAction::triggered, this, &TMainWindow::ReadOnly); connect(ui->actionPreferences, &QAction::triggered, this, &TMainWindow::Preferences); @@ -2302,6 +2385,11 @@ void TMainWindow::RefreshTable() ui->tableWidget->resizeRowsToContents(); ui->tableWidget->horizontalHeader()->setStretchLastSection(true); ui->tableWidget->blockSignals(false); + + if (ui->tableWidget->rowCount() > 0) + { + ui->actionExportToCSV->setEnabled(true); + } } //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/app/tape/tmainwindow.h b/src/app/tape/tmainwindow.h index 241ee05e3..587387ee7 100644 --- a/src/app/tape/tmainwindow.h +++ b/src/app/tape/tmainwindow.h @@ -79,6 +79,7 @@ protected: private slots: void FileSave(); void FileSaveAs(); + void ExportToCSV(); void AboutToShowWindowMenu(); void ShowWindow(); void AboutApplication(); diff --git a/src/app/tape/tmainwindow.ui b/src/app/tape/tmainwindow.ui index acf79fbc1..14bb38350 100644 --- a/src/app/tape/tmainwindow.ui +++ b/src/app/tape/tmainwindow.ui @@ -913,6 +913,7 @@ + @@ -1249,6 +1250,14 @@ QAction::NoRole + + + false + + + Export to CSV + + diff --git a/src/libs/vmisc/def.h b/src/libs/vmisc/def.h index 5b6ba2ca9..2353e421f 100644 --- a/src/libs/vmisc/def.h +++ b/src/libs/vmisc/def.h @@ -632,4 +632,95 @@ static inline bool VFuzzyComparePossibleNulls(double p1, double p2) } } +/**************************************************************************** +** This file is derived from code bearing the following notice: +** The sole author of this file, Adam Higerd, has explicitly disclaimed all +** copyright interest and protection for the content within. This file has +** been placed in the public domain according to United States copyright +** statute and case law. In jurisdictions where this public domain dedication +** is not legally recognized, anyone who receives a copy of this file is +** permitted to use, modify, duplicate, and redistribute this file, in whole +** or in part, with no restrictions or conditions. In these jurisdictions, +** this file shall be copyright (C) 2006-2008 by Adam Higerd. +****************************************************************************/ + +#define QXT_DECLARE_PRIVATE(PUB) friend class PUB##Private; QxtPrivateInterface qxt_d; +#define QXT_DECLARE_PUBLIC(PUB) friend class PUB; +#define QXT_INIT_PRIVATE(PUB) qxt_d.setPublic(this); +#define QXT_D(PUB) PUB##Private& d = qxt_d() +#define QXT_P(PUB) PUB& p = qxt_p() + +template +class QxtPrivate +{ +public: + QxtPrivate(): qxt_p_ptr(nullptr) + {} + virtual ~QxtPrivate() + {} + inline void QXT_setPublic(PUB* pub) + { + qxt_p_ptr = pub; + } + +protected: + inline PUB& qxt_p() + { + return *qxt_p_ptr; + } + inline const PUB& qxt_p() const + { + return *qxt_p_ptr; + } + inline PUB* qxt_ptr() + { + return qxt_p_ptr; + } + inline const PUB* qxt_ptr() const + { + return qxt_p_ptr; + } + +private: + Q_DISABLE_COPY(QxtPrivate) + PUB* qxt_p_ptr; +}; + +template +class QxtPrivateInterface +{ + friend class QxtPrivate; +public: + QxtPrivateInterface() : pvt(new PVT) + {} + ~QxtPrivateInterface() + { + delete pvt; + } + + inline void setPublic(PUB* pub) + { + pvt->QXT_setPublic(pub); + } + inline PVT& operator()() + { + return *static_cast(pvt); + } + inline const PVT& operator()() const + { + return *static_cast(pvt); + } + inline PVT * operator->() + { + return static_cast(pvt); + } + inline const PVT * operator->() const + { + return static_cast(pvt); + } +private: + Q_DISABLE_COPY(QxtPrivateInterface) + QxtPrivate* pvt; +}; + #endif // DEF_H diff --git a/src/libs/vmisc/qxtcsvmodel.cpp b/src/libs/vmisc/qxtcsvmodel.cpp new file mode 100644 index 000000000..a413a066c --- /dev/null +++ b/src/libs/vmisc/qxtcsvmodel.cpp @@ -0,0 +1,729 @@ +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS 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 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. +** +** +*****************************************************************************/ + +/*! +\class QxtCsvModel +\brief The QxtCsvModel class provides a QAbstractTableModel for CSV Files + */ + +#include "qxtcsvmodel.h" + +#include +#include +#include + +class QxtCsvModelPrivate : public QxtPrivate +{ +public: + QxtCsvModelPrivate() : csvData(), header(), maxColumn(0), quoteMode(QxtCsvModel::DefaultQuoteMode) + {} + QXT_DECLARE_PUBLIC(QxtCsvModel) + + QList csvData; + QStringList header; + int maxColumn; + QxtCsvModel::QuoteMode quoteMode; +}; + +#ifdef Q_CC_GNU + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Weffc++" +#endif + +/*! + Creates an empty QxtCsvModel with parent \a parent. + */ +QxtCsvModel::QxtCsvModel(QObject *parent) : QAbstractTableModel(parent) +{ + QXT_INIT_PRIVATE(QxtCsvModel); +} + +/*! + Creates a QxtCsvModel with the parent \a parent and content loaded from \a file. + + See \a setSource for information on the \a withHeader and \a separator properties, or + if you need control over the quoting method or codec used to parse the file. + + \sa setSource + */ +QxtCsvModel::QxtCsvModel(QIODevice *file, QObject *parent, bool withHeader, QChar separator) + : QAbstractTableModel(parent) +{ + QXT_INIT_PRIVATE(QxtCsvModel); + setSource(file, withHeader, separator); +} + +/*! + \overload + + Creates a QxtCsvModel with the parent \a parent and content loaded from \a file. + + See \a setSource for information on the \a withHeader and \a separator properties, or + if you need control over the quoting method or codec used to parse the file. + + \sa setSource + */ +QxtCsvModel::QxtCsvModel(const QString filename, QObject *parent, bool withHeader, QChar separator) + : QAbstractTableModel(parent) +{ + QXT_INIT_PRIVATE(QxtCsvModel); + QFile src(filename); + setSource(&src, withHeader, separator); +} + +#ifdef Q_CC_GNU +#pragma GCC diagnostic pop +#endif + +QxtCsvModel::~QxtCsvModel() +{} + +/*! + \reimp + */ +int QxtCsvModel::rowCount(const QModelIndex& parent) const +{ + if (parent.row() != -1 && parent.column() != -1) + { + return 0; + } + return qxt_d().csvData.count(); +} + +/*! + \reimp + */ +int QxtCsvModel::columnCount(const QModelIndex& parent) const +{ + if (parent.row() != -1 && parent.column() != -1) + { + return 0; + } + return qxt_d().maxColumn; +} + +/*! + \reimp + */ +QVariant QxtCsvModel::data(const QModelIndex& index, int role) const +{ + if (index.parent() != QModelIndex()) + { + return QVariant(); + } + if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) + { + if (index.row() < 0 || index.column() < 0 || index.row() >= rowCount()) + { + return QVariant(); + } + const QStringList& row = qxt_d().csvData[index.row()]; + if (index.column() >= row.length()) + { + return QVariant(); + } + return row[index.column()]; + } + return QVariant(); +} + +/*! + \reimp + */ +QVariant QxtCsvModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section < qxt_d().header.count() && orientation == Qt::Horizontal && (role == Qt::DisplayRole + || role == Qt::EditRole + || role == Qt::UserRole)) + { + return qxt_d().header[section]; + } + else + { + return QAbstractTableModel::headerData(section, orientation, role); + } +} + +/*! + \overload + + Reads in a CSV file from the provided \a file using \a codec. + */ +void QxtCsvModel::setSource(const QString filename, bool withHeader, QChar separator, QTextCodec* codec) +{ + QFile src(filename); + setSource(&src, withHeader, separator, codec); +} + +/*! + Reads in a CSV file from the provided \a file using \a codec. + + The value of \a separator will be used to delimit fields, subject to the specified \a quoteMode. + If \a withHeader is set to true, the first line of the file will be used to populate the model's + horizontal header. + + \sa quoteMode + */ +void QxtCsvModel::setSource(QIODevice *file, bool withHeader, QChar separator, QTextCodec* codec) +{ + QxtCsvModelPrivate* d_ptr = &qxt_d(); + bool headerSet = !withHeader; + if (not file->isOpen()) + { + file->open(QIODevice::ReadOnly); + } + if (withHeader) + { + d_ptr->maxColumn = 0; + } + else + { + d_ptr->maxColumn = d_ptr->header.size(); + } + d_ptr->csvData.clear(); + QStringList row; + QString field; + QChar quote; + QChar ch, buffer(0); + bool readCR = false; + QTextStream stream(file); + if (codec) + { + stream.setCodec(codec); + } + else + { + stream.setAutoDetectUnicode(true); + } + while (not stream.atEnd()) + { + if (buffer != QChar(0)) + { + ch = buffer; + buffer = QChar(0); + } + else + { + stream >> ch; + } + if (ch == '\n' && readCR) + { + continue; + } + else if (ch == '\r') + { + readCR = true; + } + else + { + readCR = false; + } + if (ch != separator && (ch.category() == QChar::Separator_Line || ch.category() == QChar::Separator_Paragraph + || ch.category() == QChar::Other_Control)) + { + row << field; + field.clear(); + if (not row.isEmpty()) + { + if (not headerSet) + { + d_ptr->header = row; + headerSet = true; + } + else + { + d_ptr->csvData.append(row); + } + if (row.length() > d_ptr->maxColumn) + { + d_ptr->maxColumn = row.length(); + } + } + row.clear(); + } + else if ((d_ptr->quoteMode & DoubleQuote && ch == '"') || (d_ptr->quoteMode & SingleQuote && ch == '\'')) + { + quote = ch; + do + { + stream >> ch; + if (ch == '\\' && d_ptr->quoteMode & BackslashEscape) + { + stream >> ch; + } + else if (ch == quote) + { + if (d_ptr->quoteMode & TwoQuoteEscape) + { + stream >> buffer; + if (buffer == quote) + { + buffer = QChar(0); + field.append(ch); + continue; + } + } + break; + } + field.append(ch); + } while (!stream.atEnd()); + } + else if (ch == separator) + { + row << field; + field.clear(); + } + else + { + field.append(ch); + } + } + if (not field.isEmpty()) + { + row << field; + } + if (not row.isEmpty()) + { + if (not headerSet) + { + d_ptr->header = row; + } + else + { + d_ptr->csvData.append(row); + } + } + file->close(); +} + +/*! + Sets the horizontal headers of the model to the values provided in \a data. + */ +void QxtCsvModel::setHeaderData(const QStringList& data) +{ + qxt_d().header = data; + emit headerDataChanged(Qt::Horizontal, 0, data.count()); +} + +/*! + \reimp + */ +bool QxtCsvModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) +{ + if (orientation != Qt::Horizontal) + { + return false; // We don't support the vertical header + } + + if (role != Qt::DisplayRole && role != Qt::EditRole) + { + return false; // We don't support any other roles + } + + if (section < 0) + { + return false; // Bogus input + } + + while (section > qxt_d().header.size()) + { + qxt_d().header << QString(); + } + qxt_d().header[section] = value.toString(); + emit headerDataChanged(Qt::Horizontal, section, section); + return true; +} + +/*! + \reimp + */ +bool QxtCsvModel::setData(const QModelIndex& index, const QVariant& data, int role) +{ + if (index.parent() != QModelIndex()) + { + return false; + } + + if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) + { + if (index.row() >= rowCount() || index.column() >= columnCount() || index.row() < 0 || index.column() < 0) + { + return false; + } + QStringList& row = qxt_d().csvData[index.row()]; + while (row.length() <= index.column()) + { + row << QString(); + } + row[index.column()] = data.toString(); + emit dataChanged(index, index); + return true; + } + return false; +} + +/*! + \reimp + */ +bool QxtCsvModel::insertRow(int row, const QModelIndex& parent) +{ + return insertRows(row, 1, parent); +} + +/*! + \reimp + */ +bool QxtCsvModel::insertRows(int row, int count, const QModelIndex& parent) +{ + if (parent != QModelIndex() || row < 0) + { + return false; + } + emit beginInsertRows(parent, row, row + count); + QxtCsvModelPrivate& d_ptr = qxt_d(); + if (row >= rowCount()) + { + for(int i = 0; i < count; i++) + { + d_ptr.csvData << QStringList(); + } + } + else + { + for(int i = 0; i < count; i++) + { + d_ptr.csvData.insert(row, QStringList()); + } + } + emit endInsertRows(); + return true; +} + +/*! + \reimp + */ +bool QxtCsvModel::removeRow(int row, const QModelIndex& parent) +{ + return removeRows(row, 1, parent); +} + +/*! + \reimp + */ +bool QxtCsvModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (parent != QModelIndex() || row < 0) + { + return false; + } + if (row >= rowCount()) + { + return false; + } + if (row + count >= rowCount()) + { + count = rowCount() - row; + } + emit beginRemoveRows(parent, row, row + count); + QxtCsvModelPrivate& d_ptr = qxt_d(); + for (int i = 0;i < count;i++) + { + d_ptr.csvData.removeAt(row); + } + emit endRemoveRows(); + return true; +} + +/*! + \reimp + */ +bool QxtCsvModel::insertColumn(int col, const QModelIndex& parent) +{ + return insertColumns(col, 1, parent); +} + +/*! + \reimp + */ +bool QxtCsvModel::insertColumns(int col, int count, const QModelIndex& parent) +{ + if (parent != QModelIndex() || col < 0) + { + return false; + } + beginInsertColumns(parent, col, col + count - 1); + QxtCsvModelPrivate& d_ptr = qxt_d(); + for (int i = 0; i < rowCount(); i++) + { + QStringList& row = d_ptr.csvData[i]; + while (col >= row.length()) + { + row.append(QString()); + } + for (int j = 0; j < count; j++) + { + row.insert(col, QString()); + } + } + for (int i = 0; i < count ;i++) + { + d_ptr.header.insert(col, QString()); + } + d_ptr.maxColumn += count; + endInsertColumns(); + return true; +} + +/*! + \reimp + */ +bool QxtCsvModel::removeColumn(int col, const QModelIndex& parent) +{ + return removeColumns(col, 1, parent); +} + +/*! + \reimp + */ +bool QxtCsvModel::removeColumns(int col, int count, const QModelIndex& parent) +{ + if (parent != QModelIndex() || col < 0) + { + return false; + } + if (col >= columnCount()) + { + return false; + } + if (col + count >= columnCount()) + { + count = columnCount() - col; + } + emit beginRemoveColumns(parent, col, col + count); + QxtCsvModelPrivate& d_ptr = qxt_d(); + QString before, after; + for (int i = 0; i < rowCount(); i++) + { + for (int j = 0; j < count; j++) + { + d_ptr.csvData[i].removeAt(col); + } + } + for (int i = 0; i < count; i++) + { + d_ptr.header.removeAt(col); + } + emit endRemoveColumns(); + return true; +} + +static QString qxt_addCsvQuotes(QxtCsvModel::QuoteMode mode, QString field) +{ + bool addDoubleQuotes = ((mode & QxtCsvModel::DoubleQuote) && field.contains('"')); + bool addSingleQuotes = ((mode & QxtCsvModel::SingleQuote) && field.contains('\'')); + bool quoteField = (mode & QxtCsvModel::AlwaysQuoteOutput) || addDoubleQuotes || addSingleQuotes; + if (quoteField && !addDoubleQuotes && !addSingleQuotes) + { + if (mode & QxtCsvModel::DoubleQuote) + { + addDoubleQuotes = true; + } + else if(mode & QxtCsvModel::SingleQuote) + { + addSingleQuotes = true; + } + } + if (mode & QxtCsvModel::BackslashEscape) + { + if (addDoubleQuotes) + { + return '"' + field.replace("\\", "\\\\").replace("\"", "\\\"") + '"'; + } + if (addSingleQuotes) + { + return '\'' + field.replace("\\", "\\\\").replace("'", "\\'") + '\''; + } + } + else + { + if (addDoubleQuotes) + { + return '"' + field.replace("\"", "\"\"") + '"'; + } + if (addSingleQuotes) + { + return '\'' + field.replace("'", "''") + '\''; + } + } + return field; +} + +/*! + Outputs the content of the model as a CSV file to the device \a dest using \a codec. + + Fields in the output file will be separated by \a separator. Set \a withHeader to true + to output a row of headers at the top of the file. + */ +void QxtCsvModel::toCSV(QIODevice* dest, bool withHeader, QChar separator, QTextCodec* codec) const +{ + const QxtCsvModelPrivate& d_ptr = qxt_d(); + int row, col, rows, cols; + rows = rowCount(); + cols = columnCount(); + QString data; + if (not dest->isOpen()) + { + dest->open(QIODevice::WriteOnly | QIODevice::Truncate); + } + QTextStream stream(dest); + if (codec) + { + stream.setCodec(codec); + } + if (withHeader) + { + data = ""; + for (col = 0; col < cols; ++col) + { + if (col > 0) + { + data += separator; + } + data += qxt_addCsvQuotes(d_ptr.quoteMode, d_ptr.header.at(col)); + } + stream << data << endl; + } + for (row = 0; row < rows; ++row) + { + const QStringList& rowData = d_ptr.csvData[row]; + data = ""; + for (col = 0; col < cols; ++col) + { + if (col > 0) + { + data += separator; + } + if (col < rowData.length()) + { + data += qxt_addCsvQuotes(d_ptr.quoteMode, rowData.at(col)); + } + else + { + data += qxt_addCsvQuotes(d_ptr.quoteMode, QString()); + } + } + stream << data << endl; + } + stream << flush; + dest->close(); +} + +/*! + \overload + + Outputs the content of the model as a CSV file to the file specified by \a filename using \a codec. + + Fields in the output file will be separated by \a separator. Set \a withHeader to true + to output a row of headers at the top of the file. + */ +void QxtCsvModel::toCSV(const QString filename, bool withHeader, QChar separator, QTextCodec* codec) const +{ + QFile dest(filename); + toCSV(&dest, withHeader, separator, codec); +} + +/*! + \reimp + */ +Qt::ItemFlags QxtCsvModel::flags(const QModelIndex& index) const +{ + return Qt::ItemIsEditable | QAbstractTableModel::flags(index); +} + +/*! + * Returns the current quoting mode. + * \sa setQuoteMode + */ +QxtCsvModel::QuoteMode QxtCsvModel::quoteMode() const +{ + return qxt_d().quoteMode; +} + +/*! + * Sets the current quoting mode. The default quoting mode is BothQuotes | BackslashEscape. + * + * The quoting mode determines what kinds of quoting is used for reading and writing CSV files. + * \sa quoteMode + * \sa QuoteOption + */ +void QxtCsvModel::setQuoteMode(QuoteMode mode) +{ + qxt_d().quoteMode = mode; +} + +/*! + Sets the content of the cell at row \a row and column \a column to \a value. + + \sa text + */ +void QxtCsvModel::setText(int row, int column, const QString& value) +{ + setData(index(row, column), value); +} + +/*! + Fetches the content of the cell at row \a row and column \a column. + + \sa setText + */ +QString QxtCsvModel::text(int row, int column) const +{ + return data(index(row, column)).toString(); +} + +/*! + Sets the content of the header for column \a column to \a value. + + \sa headerText + */ +void QxtCsvModel::setHeaderText(int column, const QString& value) +{ + setHeaderData(column, Qt::Horizontal, value); +} + +/*! + Fetches the content of the cell at row \a row and column \a column. + + \sa setText + */ +QString QxtCsvModel::headerText(int column) const +{ + return headerData(column, Qt::Horizontal).toString(); +} diff --git a/src/libs/vmisc/qxtcsvmodel.h b/src/libs/vmisc/qxtcsvmodel.h new file mode 100644 index 000000000..18869fd89 --- /dev/null +++ b/src/libs/vmisc/qxtcsvmodel.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** Copyright (c) 2006 - 2011, the LibQxt project. +** See the Qxt AUTHORS file for a list of authors and copyright holders. +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** * Neither the name of the LibQxt project nor the +** names of its contributors may be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +** ANY EXPRESS 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 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. +** +** +*****************************************************************************/ + +#ifndef QXTCSVMODEL_H +#define QXTCSVMODEL_H + +#include +#include +#include +#include +#include +#include +#include +#include +class QTextCodec; + +class QxtCsvModelPrivate; +class QxtCsvModel : public QAbstractTableModel +{ + Q_OBJECT +public: + QxtCsvModel(QObject *parent = nullptr); + explicit QxtCsvModel(QIODevice *file, QObject *parent = nullptr, bool withHeader = false, QChar separator = ','); + explicit QxtCsvModel(const QString filename, QObject *parent = nullptr, bool withHeader = false, + QChar separator = ','); + virtual ~QxtCsvModel(); + + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + virtual bool setData(const QModelIndex& index, const QVariant& data, int role = Qt::EditRole) Q_DECL_OVERRIDE; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, + int role = Qt::DisplayRole) Q_DECL_OVERRIDE; + void setHeaderData(const QStringList& data); + + QString text(int row, int column) const; + void setText(int row, int column, const QString& value); + + QString headerText(int column) const; + void setHeaderText(int column, const QString& value); + + + bool insertRow(int row, const QModelIndex& parent = QModelIndex()); + virtual bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()) Q_DECL_OVERRIDE; + + bool removeRow(int row, const QModelIndex& parent = QModelIndex()); + virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) Q_DECL_OVERRIDE; + + bool insertColumn(int col, const QModelIndex& parent = QModelIndex()); + virtual bool insertColumns(int col, int count, const QModelIndex& parent = QModelIndex()) Q_DECL_OVERRIDE; + + bool removeColumn(int col, const QModelIndex& parent = QModelIndex()); + virtual bool removeColumns(int col, int count, const QModelIndex& parent = QModelIndex()) Q_DECL_OVERRIDE; + + void setSource(QIODevice *file, bool withHeader = false, QChar separator = ',', QTextCodec* codec = nullptr); + void setSource(const QString filename, bool withHeader = false, QChar separator = ',', QTextCodec* codec = nullptr); + + void toCSV(QIODevice *file, bool withHeader = false, QChar separator = ',', QTextCodec* codec = nullptr) const; + void toCSV(const QString filename, bool withHeader = false, QChar separator = ',', + QTextCodec* codec = nullptr) const; + + enum QuoteOption { NoQuotes = 0, + SingleQuote = 1, + DoubleQuote = 2, + BothQuotes = 3, + NoEscape = 0, + TwoQuoteEscape = 4, + BackslashEscape = 8, + AlwaysQuoteOutput = 16, + DefaultQuoteMode = BothQuotes | BackslashEscape | AlwaysQuoteOutput }; + Q_DECLARE_FLAGS(QuoteMode, QuoteOption) + + QuoteMode quoteMode() const; + void setQuoteMode(QuoteMode mode); + + virtual Qt::ItemFlags flags(const QModelIndex& index) const Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QxtCsvModel) + QXT_DECLARE_PRIVATE(QxtCsvModel) +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QxtCsvModel::QuoteMode) + +#endif // QXTCSVMODEL_H diff --git a/src/libs/vmisc/vmisc.pri b/src/libs/vmisc/vmisc.pri index 4010be461..b75dfda45 100644 --- a/src/libs/vmisc/vmisc.pri +++ b/src/libs/vmisc/vmisc.pri @@ -9,7 +9,8 @@ SOURCES += \ $$PWD/projectversion.cpp \ $$PWD/vcommonsettings.cpp \ $$PWD/vtapesettings.cpp \ - $$PWD/commandoptions.cpp + $$PWD/commandoptions.cpp \ + $$PWD/qxtcsvmodel.cpp win32-msvc*:SOURCES += $$PWD/stable.cpp @@ -27,7 +28,8 @@ HEADERS += \ $$PWD/debugbreak.h \ $$PWD/vlockguard.h \ $$PWD/vsysexits.h \ - $$PWD/commandoptions.h + $$PWD/commandoptions.h \ + $$PWD/qxtcsvmodel.h # Qt's versions # 5.0.0, 5.0.1, 5.0.2 diff --git a/src/libs/vmisc/vtapesettings.cpp b/src/libs/vmisc/vtapesettings.cpp index f28a893cb..e98a5ed3c 100644 --- a/src/libs/vmisc/vtapesettings.cpp +++ b/src/libs/vmisc/vtapesettings.cpp @@ -30,12 +30,17 @@ #include #include +#include const QString VTapeSettings::SettingDataBaseGeometry = QStringLiteral("database/geometry"); const QString VTapeSettings::SettingDefHeight = QStringLiteral("gradation/defHeight"); const QString VTapeSettings::SettingDefSize = QStringLiteral("gradation/defHeight"); +const QString VTapeSettings::SettingCSVWithHeader = QStringLiteral("csv/withHeader"); +const QString VTapeSettings::SettingCSVCodec = QStringLiteral("csv/withCodec"); +const QString VTapeSettings::SettingCSVSeparator = QStringLiteral("csv/withSeparator"); + //--------------------------------------------------------------------------------------------------------------------- VTapeSettings::VTapeSettings(Format format, Scope scope, const QString &organization, const QString &application, QObject *parent) @@ -78,3 +83,70 @@ int VTapeSettings::GetDefSize() const { return value(SettingDefSize, 50).toInt(); } + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetCSVWithHeader(bool withHeader) +{ + setValue(SettingCSVWithHeader, withHeader); +} + +//--------------------------------------------------------------------------------------------------------------------- +bool VTapeSettings::GetCSVWithHeader() const +{ + return value(SettingCSVWithHeader, false).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetCSVCodec(int mib) +{ + setValue(SettingCSVCodec, mib); +} + +//--------------------------------------------------------------------------------------------------------------------- +int VTapeSettings::GetCSVCodec() const +{ + return value(SettingCSVCodec, QTextCodec::codecForLocale()->mibEnum()).toInt(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetCSVSeparator(const QChar &separator) +{ + switch(separator.toLatin1()) + { + case '\t': + setValue(SettingCSVSeparator, 0); + break; + case ';': + setValue(SettingCSVSeparator, 1); + break; + case ' ': + setValue(SettingCSVSeparator, 2); + break; + case ',': + default: + setValue(SettingCSVSeparator, 3); + break; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +QChar VTapeSettings::GetCSVSeparator() const +{ + const quint8 separator = static_cast(value(SettingCSVSeparator, 3).toUInt()); + switch(separator) + { + case 0: + return QChar('\t'); + break; + case 1: + return QChar(';'); + break; + case 2: + return QChar(' '); + break; + case 3: + default: + return QChar(','); + break; + } +} diff --git a/src/libs/vmisc/vtapesettings.h b/src/libs/vmisc/vtapesettings.h index ba5424921..aea93a15f 100644 --- a/src/libs/vmisc/vtapesettings.h +++ b/src/libs/vmisc/vtapesettings.h @@ -47,12 +47,24 @@ public: void SetDefSize(int value); int GetDefSize() const; + void SetCSVWithHeader(bool withHeader); + bool GetCSVWithHeader() const; + + void SetCSVCodec(int mib); + int GetCSVCodec() const; + + void SetCSVSeparator(const QChar &separator); + QChar GetCSVSeparator() const; + private: Q_DISABLE_COPY(VTapeSettings) static const QString SettingDataBaseGeometry; static const QString SettingDefHeight; static const QString SettingDefSize; + static const QString SettingCSVWithHeader; + static const QString SettingCSVCodec; + static const QString SettingCSVSeparator; }; #endif // VTAPESETTINGS_H