diff --git a/src/backend/datasources/LiveDataSource.cpp b/src/backend/datasources/LiveDataSource.cpp index 1ba8a0b0e..44dea50e3 100644 --- a/src/backend/datasources/LiveDataSource.cpp +++ b/src/backend/datasources/LiveDataSource.cpp @@ -1,1051 +1,1048 @@ /*************************************************************************** File : LiveDataSource.cpp Project : LabPlot Description : Represents live data source -------------------------------------------------------------------- Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program 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 2 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/core/Project.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class LiveDataSource \brief Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filters. \ingroup datasources */ LiveDataSource::LiveDataSource(AbstractScriptingEngine* engine, const QString& name, bool loading) : Spreadsheet(engine, name, loading), m_fileType(Ascii), m_fileWatched(false), m_fileLinked(false), m_paused(false), m_prepared(false), m_keepLastValues(false), m_sampleSize(1), m_updateInterval(1000), //TODO: m_keepNvalues, m_port, m_baudRate ? m_bytesRead(0), m_filter(nullptr), m_updateTimer(new QTimer(this)), m_fileSystemWatcher(nullptr), m_file(nullptr), m_localSocket(nullptr), m_tcpSocket(nullptr), m_udpSocket(nullptr), m_serialPort(nullptr), m_device(nullptr) { initActions(); connect(m_updateTimer, &QTimer::timeout, this, &LiveDataSource::read); } LiveDataSource::~LiveDataSource() { //stop reading before deleting the objects pauseReading(); if (m_filter) delete m_filter; if (m_fileSystemWatcher) delete m_fileSystemWatcher; if (m_file) delete m_file; if (m_localSocket) delete m_localSocket; if (m_tcpSocket) delete m_tcpSocket; if (m_serialPort) delete m_serialPort; delete m_updateTimer; } /*! * depending on the update type, periodically or on data changes, starts the timer or activates the file watchers, respectively. */ void LiveDataSource::ready() { - DEBUG("LiveDataSource::ready() update type = " << m_updateType << ", interval = " << m_updateInterval); + DEBUG("LiveDataSource::ready() update type = " << ENUM_TO_STRING(LiveDataSource,UpdateType,m_updateType) << ", interval = " << m_updateInterval); switch (m_updateType) { case TimeInterval: - DEBUG("START TIMER"); m_updateTimer->start(m_updateInterval); - DEBUG(" REMAINING TIME = " << m_updateTimer->remainingTime()); + DEBUG("STARTED TIMER. REMAINING TIME = " << m_updateTimer->remainingTime()); break; case NewData: - DEBUG("START WATCHER"); + DEBUG("STARTING WATCHER"); watch(); } } void LiveDataSource::initActions() { m_reloadAction = new QAction(QIcon::fromTheme("view-refresh"), i18n("Reload"), this); connect(m_reloadAction, &QAction::triggered, this, &LiveDataSource::read); m_toggleLinkAction = new QAction(i18n("Link the file"), this); m_toggleLinkAction->setCheckable(true); connect(m_toggleLinkAction, &QAction::triggered, this, &LiveDataSource::linkToggled); m_plotDataAction = new QAction(QIcon::fromTheme("office-chart-line"), i18n("Plot data"), this); connect(m_plotDataAction, &QAction::triggered, this, &LiveDataSource::plotData); } QWidget* LiveDataSource::view() const { if (!m_partView) m_partView = new SpreadsheetView(const_cast(this), true); return m_partView; } /*! * \brief Returns a list with the names of the available ports */ QStringList LiveDataSource::availablePorts() { QStringList ports; qDebug() << "available ports count:" << QSerialPortInfo::availablePorts().size(); for (const QSerialPortInfo& sp : QSerialPortInfo::availablePorts()) { ports.append(sp.portName()); qDebug() << sp.description(); qDebug() << sp.manufacturer(); qDebug() << sp.portName(); qDebug() << sp.serialNumber(); qDebug() << sp.systemLocation(); } return ports; } /*! * \brief Returns a list with the supported baud rates */ QStringList LiveDataSource::supportedBaudRates() { QStringList baudRates; for (const auto& baud : QSerialPortInfo::standardBaudRates()) baudRates.append(QString::number(baud)); return baudRates; } /*! * \brief Updates this data source at this moment */ void LiveDataSource::updateNow() { DEBUG("LiveDataSource::updateNow() update interval = " << m_updateInterval); m_updateTimer->stop(); read(); //restart the timer after update if (m_updateType == TimeInterval) m_updateTimer->start(m_updateInterval); } /*! * \brief Continue reading from the live data source after it was paused */ void LiveDataSource::continueReading() { m_paused = false; switch (m_updateType) { case TimeInterval: m_updateTimer->start(m_updateInterval); break; case NewData: connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); } } /*! * \brief Pause the reading of the live data source */ void LiveDataSource::pauseReading() { m_paused = true; switch (m_updateType) { case TimeInterval: m_updateTimer->stop(); break; case NewData: disconnect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); } } /*! returns the list of all supported data file formats */ QStringList LiveDataSource::fileTypes() { // see LiveDataSource::FileType return (QStringList() << i18n("ASCII data") << i18n("Binary data") << i18n("Image") << i18n("Hierarchical Data Format 5 (HDF5)") << i18n("Network Common Data Format (NetCDF)") << i18n("Flexible Image Transport System Data Format (FITS)") << i18n("ROOT (CERN) Histograms") << "Ngspice RAW ASCII" ); } void LiveDataSource::setFileName(const QString& name) { m_fileName = name; } QString LiveDataSource::fileName() const { return m_fileName; } /*! * \brief Sets the local socket's server name to name * \param name */ void LiveDataSource::setLocalSocketName(const QString& name) { m_localSocketName = name; } QString LiveDataSource::localSocketName() const { return m_localSocketName; } void LiveDataSource::setFileType(FileType type) { m_fileType = type; } LiveDataSource::FileType LiveDataSource::fileType() const { return m_fileType; } void LiveDataSource::setFilter(AbstractFileFilter* f) { m_filter = f; } AbstractFileFilter* LiveDataSource::filter() const { return m_filter; } /*! sets whether the file should be watched or not. In the first case the data source will be automatically updated on file changes. */ void LiveDataSource::setFileWatched(bool b) { m_fileWatched = b; } bool LiveDataSource::isFileWatched() const { return m_fileWatched; } /*! * \brief Sets whether we'll keep the last values or append it to the previous ones * \param keepLastValues */ void LiveDataSource::setKeepLastValues(bool keepLastValues) { m_keepLastValues = keepLastValues; } bool LiveDataSource::keepLastValues() const { return m_keepLastValues; } /*! * \brief Sets the serial port's baud rate * \param baudrate */ void LiveDataSource::setBaudRate(int baudrate) { m_baudRate = baudrate; } int LiveDataSource::baudRate() const { return m_baudRate; } /*! * \brief Sets the source's update interval to \c interval * \param interval */ void LiveDataSource::setUpdateInterval(int interval) { m_updateInterval = interval; m_updateTimer->start(m_updateInterval); } int LiveDataSource::updateInterval() const { return m_updateInterval; } /*! * \brief Sets how many values we should store * \param keepnvalues */ void LiveDataSource::setKeepNvalues(int keepnvalues) { m_keepNvalues = keepnvalues; } int LiveDataSource::keepNvalues() const { return m_keepNvalues; } /*! * \brief Sets the network socket's port to port * \param port */ void LiveDataSource::setPort(quint16 port) { m_port = port; } void LiveDataSource::setBytesRead(qint64 bytes) { m_bytesRead = bytes; } int LiveDataSource::bytesRead() const { return m_bytesRead; } int LiveDataSource::port() const { return m_port; } /*! * \brief Sets the serial port's name to name * \param name */ void LiveDataSource::setSerialPort(const QString& name) { m_serialPortName = name; } QString LiveDataSource::serialPortName() const { return m_serialPortName; } bool LiveDataSource::isPaused() const { return m_paused; } /*! * \brief Sets the sample size to size * \param size */ void LiveDataSource::setSampleSize(int size) { m_sampleSize = size; } int LiveDataSource::sampleSize() const { return m_sampleSize; } /*! * \brief Sets the source's type to sourcetype * \param sourcetype */ void LiveDataSource::setSourceType(SourceType sourcetype) { m_sourceType = sourcetype; } LiveDataSource::SourceType LiveDataSource::sourceType() const { return m_sourceType; } /*! * \brief Sets the source's reading type to readingType * \param readingType */ void LiveDataSource::setReadingType(ReadingType readingType) { m_readingType = readingType; } LiveDataSource::ReadingType LiveDataSource::readingType() const { return m_readingType; } /*! * \brief Sets the source's update type to updatetype and handles this change * \param updatetype */ void LiveDataSource::setUpdateType(UpdateType updatetype) { switch (updatetype) { case NewData: m_updateTimer->stop(); if (m_fileSystemWatcher == nullptr) watch(); else connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); break; case TimeInterval: if (m_fileSystemWatcher) disconnect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); } m_updateType = updatetype; } LiveDataSource::UpdateType LiveDataSource::updateType() const { return m_updateType; } /*! * \brief Sets the network socket's host * \param host */ void LiveDataSource::setHost(const QString& host) { m_host = host; } QString LiveDataSource::host() const { return m_host; } /*! sets whether only a link to the file is saved in the project file (\c b=true) or the whole content of the file (\c b=false). */ void LiveDataSource::setFileLinked(bool b) { m_fileLinked = b; } /*! returns \c true if only a link to the file is saved in the project file. \c false otherwise. */ bool LiveDataSource::isFileLinked() const { return m_fileLinked; } QIcon LiveDataSource::icon() const { QIcon icon; if (m_fileType == LiveDataSource::Ascii) icon = QIcon::fromTheme("text-plain"); else if (m_fileType == LiveDataSource::Binary) icon = QIcon::fromTheme("application-octet-stream"); else if (m_fileType == LiveDataSource::Image) icon = QIcon::fromTheme("image-x-generic"); // TODO: HDF5, NetCDF, FITS, etc. return icon; } QMenu* LiveDataSource::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); QAction* firstAction = 0; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size() > 1) firstAction = menu->actions().at(1); menu->insertAction(firstAction, m_plotDataAction); menu->insertSeparator(firstAction); //TODO: doesnt always make sense... // if (!m_fileWatched) // menu->insertAction(firstAction, m_reloadAction); // // m_toggleWatchAction->setChecked(m_fileWatched); // menu->insertAction(firstAction, m_toggleWatchAction); // // m_toggleLinkAction->setChecked(m_fileLinked); // menu->insertAction(firstAction, m_toggleLinkAction); return menu; } //############################################################################## //################################# SLOTS #################################### //############################################################################## /* * called periodically or on new data changes (file changed, new data in the socket, etc.) */ void LiveDataSource::read() { DEBUG("LiveDataSource::read()"); if (m_filter == nullptr) return; //initialize the device (file, socket, serial port), when calling this function for the first time if (!m_prepared) { - DEBUG(" preparing device. update type = " << m_updateType); + DEBUG(" preparing device. update type = " << ENUM_TO_STRING(LiveDataSource,UpdateType,m_updateType)); switch (m_sourceType) { case FileOrPipe: m_file = new QFile(m_fileName); m_device = m_file; break; case NetworkTcpSocket: m_tcpSocket = new QTcpSocket(this); m_device = m_tcpSocket; m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); connect(m_tcpSocket, &QTcpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_tcpSocket, static_cast(&QTcpSocket::error), this, &LiveDataSource::tcpSocketError); break; case NetworkUdpSocket: m_udpSocket = new QUdpSocket(this); m_device = m_udpSocket; m_udpSocket->bind(QHostAddress(m_host), m_port); m_udpSocket->connectToHost(m_host, 0, QUdpSocket::ReadOnly); // only connect to readyRead when update is on new data if (m_updateType == NewData) connect(m_udpSocket, &QUdpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_udpSocket, static_cast(&QUdpSocket::error), this, &LiveDataSource::tcpSocketError); break; case LocalSocket: m_localSocket = new QLocalSocket(this); m_device = m_localSocket; m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); connect(m_localSocket, &QLocalSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_localSocket, static_cast(&QLocalSocket::error), this, &LiveDataSource::localSocketError); break; case SerialPort: m_serialPort = new QSerialPort; m_device = m_serialPort; m_serialPort->setBaudRate(m_baudRate); m_serialPort->setPortName(m_serialPortName); connect(m_serialPort, static_cast(&QSerialPort::error), this, &LiveDataSource::serialPortError); connect(m_serialPort, &QSerialPort::readyRead, this, &LiveDataSource::readyRead); break; } m_prepared = true; } qint64 bytes = 0; switch (m_sourceType) { case FileOrPipe: - DEBUG("Reading FileOrPipe .."); + DEBUG("Reading FileOrPipe. type = " << ENUM_TO_STRING(LiveDataSource,FileType,m_fileType)); switch (m_fileType) { case Ascii: - DEBUG(" type ASCII"); if (m_readingType == LiveDataSource::ReadingType::WholeFile) { dynamic_cast(m_filter)->readFromLiveDevice(*m_file, this, 0); } else { bytes = dynamic_cast(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead); m_bytesRead += bytes; } DEBUG("Read " << bytes << " bytes, in total: " << m_bytesRead); break; case Binary: - DEBUG(" type Binary"); //TODO: bytes = dynamic_cast(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead); m_bytesRead += bytes; //TODO:? case Image: case HDF5: case NETCDF: case FITS: case ROOT: break; } break; case NetworkTcpSocket: DEBUG("reading from TCP socket. state before abort = " << m_tcpSocket->state()); m_tcpSocket->abort(); m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); DEBUG("reading from TCP socket. state after reconnect = " << m_tcpSocket->state()); break; case NetworkUdpSocket: - DEBUG("reading from UDP socket. state before abort = " << m_udpSocket->state()); + DEBUG("reading from UDP socket. state = " << m_udpSocket->state()); // reading data here - dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); - DEBUG("reading from UDP socket. state after reconnect = " << m_udpSocket->state()); + if (m_fileType == Ascii) + dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; case LocalSocket: DEBUG("reading from local socket. state before abort = " << m_localSocket->state()); m_localSocket->abort(); m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); DEBUG("reading from local socket. state after reconnect = " << m_localSocket->state()); break; case SerialPort: DEBUG("reading from the serial port"); m_serialPort->setBaudRate(m_baudRate); m_serialPort->setPortName(m_serialPortName); m_device = m_serialPort; //TODO break; } } /*! * Slot for the signal that is emitted once every time new data is available for reading from the device. * It will only be emitted again once new data is available, such as when a new payload of network data has arrived on the network socket, * or when a new block of data has been appended to your device. */ void LiveDataSource::readyRead() { - DEBUG("LiveDataSource::readyRead() update type = " << m_updateType); + DEBUG("LiveDataSource::readyRead() update type = " << ENUM_TO_STRING(LiveDataSource,UpdateType,m_updateType)); DEBUG(" REMAINING TIME = " << m_updateTimer->remainingTime()); if (m_fileType == Ascii) dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //TODO: else if (m_fileType == Binary) // dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //since we won't have the timer to call read() where we create new connections //for sequencial devices in read() we just request data/connect to servers if (m_updateType == NewData) read(); } void LiveDataSource::localSocketError(QLocalSocket::LocalSocketError socketError) { Q_UNUSED(socketError); /*disconnect(m_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(localSocketError(QLocalSocket::LocalSocketError))); disconnect(m_localSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));*/ /*switch (socketError) { case QLocalSocket::ServerNotFoundError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The socket was not found. Please check the socket name.")); break; case QLocalSocket::ConnectionRefusedError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The connection was refused by the peer")); break; case QLocalSocket::PeerClosedError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The socket has closed the connection.")); break; default: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The following error occurred: %1.", m_localSocket->errorString())); }*/ } void LiveDataSource::tcpSocketError(QAbstractSocket::SocketError socketError) { Q_UNUSED(socketError); /*switch (socketError) { case QAbstractSocket::ConnectionRefusedError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The connection was refused by the peer. Make sure the server is running and check the host name and port settings.")); break; case QAbstractSocket::RemoteHostClosedError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The remote host closed the connection.")); break; case QAbstractSocket::HostNotFoundError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The host was not found. Please check the host name and port settings.")); break; default: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The following error occurred: %1.", m_tcpSocket->errorString())); }*/ } void LiveDataSource::serialPortError(QSerialPort::SerialPortError serialPortError) { switch (serialPortError) { case QSerialPort::DeviceNotFoundError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Failed to open the device.")); break; case QSerialPort::PermissionError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Failed to open the device. Please check your permissions on this device.")); break; case QSerialPort::OpenError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Device already opened.")); break; case QSerialPort::NotOpenError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("The device is not opened.")); break; case QSerialPort::ReadError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Failed to read data.")); break; case QSerialPort::ResourceError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("Failed to read data. The device is removed.")); break; case QSerialPort::TimeoutError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("The device timed out.")); break; #ifndef _MSC_VER //MSVC complains about the usage of deprecated enums, g++ and clang complain about missing enums case QSerialPort::ParityError: case QSerialPort::FramingError: case QSerialPort::BreakConditionError: #endif case QSerialPort::WriteError: case QSerialPort::UnsupportedOperationError: case QSerialPort::UnknownError: QMessageBox::critical(0, i18n("Serial Port Error"), i18n("The following error occurred: %1.", m_serialPort->errorString())); break; case QSerialPort::NoError: break; } } void LiveDataSource::watchToggled() { m_fileWatched = !m_fileWatched; watch(); project()->setChanged(true); } void LiveDataSource::linkToggled() { m_fileLinked = !m_fileLinked; project()->setChanged(true); } //watch the file upon reading for changes if required void LiveDataSource::watch() { DEBUG("LiveDataSource::watch() file name = " << m_fileName.toStdString()); if (m_fileWatched) { if (!m_fileSystemWatcher) { m_fileSystemWatcher = new QFileSystemWatcher; connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &LiveDataSource::read); } if ( !m_fileSystemWatcher->files().contains(m_fileName) ) m_fileSystemWatcher->addPath(m_fileName); } else { if (m_fileSystemWatcher) m_fileSystemWatcher->removePath(m_fileName); } } /*! returns a string containing the general information about the file \c name and some content specific information (number of columns and lines for ASCII, color-depth for images etc.). */ QString LiveDataSource::fileInfoString(const QString &name) { QString infoString; QFileInfo fileInfo; QString fileTypeString; QIODevice *file = new QFile(name); QString fileName; #ifdef Q_OS_WIN if (name.at(1) != QLatin1Char(':')) fileName = QDir::homePath() + name; else fileName = name; #else if (name.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + name; else fileName = name; #endif if(file==0) file = new QFile(fileName); if (file->open(QIODevice::ReadOnly)) { QStringList infoStrings; //general information about the file infoStrings << "" + fileName + "
"; fileInfo.setFile(fileName); infoStrings << i18n("Readable: %1", fileInfo.isReadable() ? i18n("yes") : i18n("no")); infoStrings << i18n("Writable: %1", fileInfo.isWritable() ? i18n("yes") : i18n("no")); infoStrings << i18n("Executable: %1", fileInfo.isExecutable() ? i18n("yes") : i18n("no")); infoStrings << i18n("Created: %1", fileInfo.created().toString()); infoStrings << i18n("Last modified: %1", fileInfo.lastModified().toString()); infoStrings << i18n("Last read: %1", fileInfo.lastRead().toString()); infoStrings << i18n("Owner: %1", fileInfo.owner()); infoStrings << i18n("Group: %1", fileInfo.group()); infoStrings << i18n("Size: %1", i18np("%1 cByte", "%1 cBytes", fileInfo.size())); #ifdef HAVE_FITS if (fileName.endsWith(QLatin1String(".fits"))) { infoStrings << i18n("Images: %1", QString::number(FITSFilter::imagesCount(fileName) )); infoStrings << i18n("Tables: %1", QString::number(FITSFilter::tablesCount(fileName) )); } #endif // file type and type specific information about the file #ifdef Q_OS_LINUX QProcess *proc = new QProcess(); QStringList args; args<<"-b"<start( "file", args); if(proc->waitForReadyRead(1000) == false) infoStrings << i18n("Could not open file %1 for reading.", fileName); else { fileTypeString = proc->readLine(); if( fileTypeString.contains(i18n("cannot open")) ) fileTypeString=""; else { fileTypeString.remove(fileTypeString.length()-1,1); // remove '\n' } } infoStrings << i18n("File type: %1", fileTypeString); #endif //TODO depending on the file type, generate additional information about the file: //Number of lines for ASCII, color-depth for images etc. Use the specific filters here. // port the old labplot1.6 code. if( fileTypeString.contains("ASCII")) { infoStrings << "
"; //TODO: consider choosen separator infoStrings << i18n("Number of columns: %1", AsciiFilter::columnNumber(fileName)); infoStrings << i18n("Number of lines: %1", AsciiFilter::lineNumber(fileName)); } infoString += infoStrings.join("
"); } else infoString += i18n("Could not open file %1 for reading.", fileName); return infoString; } void LiveDataSource::plotData() { PlotDataDialog* dlg = new PlotDataDialog(this); dlg->exec(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void LiveDataSource::save(QXmlStreamWriter* writer) const { writer->writeStartElement("LiveDataSource"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); writer->writeAttribute("fileName", m_fileName); writer->writeAttribute("fileType", QString::number(m_fileType)); writer->writeAttribute("fileWatched", QString::number(m_fileWatched)); writer->writeAttribute("fileLinked", QString::number(m_fileLinked)); writer->writeAttribute("updateType", QString::number(m_updateType)); writer->writeAttribute("readingType", QString::number(m_readingType)); writer->writeAttribute("sourceType", QString::number(m_sourceType)); writer->writeAttribute("keepValues", QString::number(m_keepNvalues)); if (m_updateType == TimeInterval) writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); if (m_readingType != TillEnd) writer->writeAttribute("sampleSize", QString::number(m_sampleSize)); switch (m_sourceType) { case SerialPort: writer->writeAttribute("baudRate", QString::number(m_baudRate)); writer->writeAttribute("serialPortName", m_serialPortName); break; case NetworkTcpSocket: case NetworkUdpSocket: writer->writeAttribute("host", m_host); writer->writeAttribute("port", QString::number(m_port)); break; case FileOrPipe: break; case LocalSocket: break; default: break; } writer->writeEndElement(); //filter m_filter->save(writer); //columns if (!m_fileLinked) { for (auto* col : children(IncludeHidden)) col->save(writer); } writer->writeEndElement(); // "LiveDataSource" } /*! Loads from XML. */ bool LiveDataSource::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "LiveDataSource") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("fileName").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileName").toString()); else m_fileName = str; str = attribs.value("fileType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileType").toString()); else m_fileType = (FileType)str.toInt(); str = attribs.value("fileWatched").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileWatched").toString()); else m_fileWatched = str.toInt(); str = attribs.value("fileLinked").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileLinked").toString()); else m_fileLinked = str.toInt(); str = attribs.value("updateType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateType").toString()); else m_updateType = static_cast(str.toInt()); str = attribs.value("sourceType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("sourceType").toString()); else m_sourceType = static_cast(str.toInt()); str = attribs.value("readingType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("readingType").toString()); else m_readingType = static_cast(str.toInt()); if (m_updateType == TimeInterval) { str = attribs.value("updateInterval").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateInterval").toString()); else m_updateInterval = str.toInt(); } if (m_readingType != TillEnd) { str = attribs.value("sampleSize").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("sampleSize").toString()); else m_sampleSize = str.toInt(); } switch (m_sourceType) { case SerialPort: str = attribs.value("baudRate").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("baudRate").toString()); else m_baudRate = str.toInt(); str = attribs.value("serialPortName").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("serialPortName").toString()); else m_serialPortName = str; break; case NetworkTcpSocket: case NetworkUdpSocket: str = attribs.value("host").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("host").toString()); else m_host = str; str = attribs.value("port").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.subs("port").toString()); else m_host = str; break; case FileOrPipe: break; case LocalSocket: break; default: break; } } else if (reader->name() == "asciiFilter") { m_filter = new AsciiFilter(); if (!m_filter->load(reader)) return false; } else if(reader->name() == "column") { Column* column = new Column("", AbstractColumn::Text); if (!column->load(reader, preview)) { delete column; setColumnCount(0); return false; } addChild(column); } else {// unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } //read the content of the file if it was only linked if (m_fileLinked) this->read(); return !reader->hasError(); } diff --git a/src/backend/datasources/LiveDataSource.h b/src/backend/datasources/LiveDataSource.h index c579692a3..b9bee9f37 100644 --- a/src/backend/datasources/LiveDataSource.h +++ b/src/backend/datasources/LiveDataSource.h @@ -1,212 +1,215 @@ /*************************************************************************** File : LiveDataSource.h Project : LabPlot Description : File data source -------------------------------------------------------------------- Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2017-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program 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 2 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef LIVEDATASOURCE_H #define LIVEDATASOURCE_H #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include #include #include class QString; class AbstractFileFilter; class QFileSystemWatcher; class QAction; class QTcpSocket; class QUdpSocket; class QFile; class LiveDataSource : public Spreadsheet { Q_OBJECT Q_ENUMS(FileType) + Q_ENUMS(SourceType) + Q_ENUMS(UpdateType) + Q_ENUMS(ReadingType) public: enum FileType {Ascii, Binary, Image, HDF5, NETCDF, FITS, ROOT, NgspiceRawAscii}; enum SourceType { FileOrPipe = 0, // regular file or pipe NetworkTcpSocket, // TCP socket NetworkUdpSocket, // UDP socket LocalSocket, // local socket SerialPort // serial port }; enum UpdateType { TimeInterval = 0, // update periodically using given interval NewData // update when new data is available }; enum ReadingType { ContinuousFixed = 0, // read fixed number of samples (aka lines) using given sample size FromEnd, // ? TillEnd, // read until the end WholeFile // reread whole file }; LiveDataSource(AbstractScriptingEngine*, const QString& name, bool loading = false); ~LiveDataSource() override; void ready(); static QStringList supportedBaudRates(); static QStringList availablePorts(); static QStringList fileTypes(); static QString fileInfoString(const QString&); void setFileType(const FileType); FileType fileType() const; UpdateType updateType() const; void setUpdateType(UpdateType); SourceType sourceType() const; void setSourceType(SourceType); ReadingType readingType() const; void setReadingType(ReadingType); int sampleSize() const; void setSampleSize(int); void setBytesRead(qint64 bytes); int bytesRead() const; int port() const; void setPort(quint16); bool isPaused() const; void setSerialPort(const QString& name); QString serialPortName() const; QString host() const; void setHost(const QString&); int baudRate() const; void setBaudRate(int); void setUpdateInterval(int); int updateInterval() const; void setKeepNvalues(int); int keepNvalues() const; void setKeepLastValues(bool); bool keepLastValues() const; void setFileWatched(bool); bool isFileWatched() const; void setFileLinked(bool); bool isFileLinked() const; void setFileName(const QString&); QString fileName() const; void setLocalSocketName(const QString&); QString localSocketName() const; void updateNow(); void pauseReading(); void continueReading(); void setFilter(AbstractFileFilter*); AbstractFileFilter* filter() const; QIcon icon() const override; QMenu* createContextMenu() override; QWidget* view() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; private: void initActions(); void watch(); QString m_fileName; QString m_serialPortName; QString m_localSocketName; QString m_host; FileType m_fileType; UpdateType m_updateType; SourceType m_sourceType; ReadingType m_readingType; bool m_fileWatched; bool m_fileLinked; bool m_paused; bool m_prepared; bool m_keepLastValues; int m_sampleSize; int m_keepNvalues; int m_updateInterval; quint16 m_port; int m_baudRate; qint64 m_bytesRead; AbstractFileFilter* m_filter; QTimer* m_updateTimer; QFileSystemWatcher* m_fileSystemWatcher; QFile* m_file; QLocalSocket* m_localSocket; QTcpSocket* m_tcpSocket; QUdpSocket* m_udpSocket; QSerialPort* m_serialPort; QIODevice* m_device; QAction* m_reloadAction; QAction* m_toggleLinkAction; QAction* m_showEditorAction; QAction* m_showSpreadsheetAction; QAction* m_plotDataAction; public slots: void read(); private slots: void watchToggled(); void linkToggled(); void plotData(); void readyRead(); void localSocketError(QLocalSocket::LocalSocketError); void tcpSocketError(QAbstractSocket::SocketError); void serialPortError(QSerialPort::SerialPortError); }; #endif diff --git a/src/doc/coding_style.dox b/src/doc/coding_style.dox index 4e1495032..111ebbf12 100644 --- a/src/doc/coding_style.dox +++ b/src/doc/coding_style.dox @@ -1,134 +1,134 @@ /**\page coding_style Coding Style The following rules are not used everywhere (yet), but are intended as guidelines for new code and eventually old code should be adapted as well. They apply to C++ and C code. The standards are C++11 and C99. \section files Files - Files use Unix-style line endings ('\\n'). - C++ source files use “.cpp” as extension, C source code use "*.c" and header files use “.h”. - The code is documented using Doxygen comments which are placed in the source files, not the header files. - Every file should be named exactly like the class inside and there should be only one class per file, with the exception of really short classes. Very short classes can be bundled in one file which then is named using all lower case letters. \section identifier Identifier names - Class names start with a capital letter and use CamelCase, acronyms in class names are use like normal words. Example: MySuperHtmlToPdfConverter - Function/method names start with a lower case letter and use CamelCase Example: doSomethingImportant() - Variable/object names start with a lower case letter and use CamelCase, underscores are used for special prefixes only. - Only private class member variables are prefixed with “m_” to distinguish them easily. d-pointer and UI-widgets are called d and ui, respectively, i.e. without prefix. - Property access methods use Qt style: property() and setProperty(), except for boolean properties (isVisible(), hasChanged()). Accessor functions (getter/setter) can be done using macros. - Avoid abbreviations, except for local counters and temporaries whose purpose is obvious. \section indent Indentation, spacing and line breaks - Tabs are used for indentation because they allow everyone to choose the indentation depth for him/herself. - Try to keep lines shorter than 100 characters, inserting line breaks as necessary and indent the following lines to improved readability. - included headers should be in order: own header, local header, Qt/KDE header, system header, extern header - Opening braces (‘{‘) are placed behind the statement and are preceded by a space. This also goes for function implementations, class, struct and namespace declarations, which are exceptions in other coding styles. Example: @code void MyClass::doSomething() { if (condition) { ... } ... } @endcode - Opening brackets (‘(‘) are preceded by a space in for/switch/if/while statements, but not for function calls. Example: @code if (condition) { doSomething(myData); ... } @endcode - For pointers or references, use a single space after ‘*’ or ‘&’ (i.e. specifier is bound to the data type not the name). Example: @code void doSomething(int* dataPointer, const QString& name); ... = static_cast(...) @endcode “public” and namespace enclosures are not indented. Example: @code class MyClass: public QObject { public: void doSomething(); @endcode - “case” of switch is not indented. “default” should be present if type of condition is not an enum. Example: + “case” of switch is not indented. “default” should be present only if data type is not an enum. Example: @code switch (condition) { case 1: handleCaseOne(); break; case 2: { int i=0; ... break; } ... default: ... } @endcode - Each comma in a function call or semicolon in a for statement is followed by a space character; no space before the first and after the last argument. Example: @code for (int i = 0; i < 10; i++) { ... doSomething(arg1, arg2, arg3); } @endcode "else" (and "catch" if it is ever used) is put after the closing brace like this: "} else {" - Use as many brackets in conditions/math terms as you see fit for optimum readability. All operators ('=', '==', '<', '+', '-', '<<', etc.) and castings should always be surrounded by spaces. Examples: @code foo/2 + bar/4 + baz/3 for (int i = 0; i < bar+1; i++) var = (foo - 1) + (bar - 2) + (baz - 3) char *s = (char*) malloc(LENGTH * sizeof(char)); @endcode - enum and structs should be defined first in a class - parameter names in a method definition should only be used to explains the usage of the parameter - In SIGNAL() and SLOT() macros, use as little whitespace as possible. This gives a little speed up since Qt does not have to normalize the signal/slot name. \section constructs Usage of specific constructs * Use C++ casting (static_cast, const_cast, dynamic_cast) in C++ and qobject_cast in Qt classes since they include checks see https://en.wikibooks.org/wiki/C%2B%2B_Programming/Programming_Languages/C%2B%2B/Code/Statements/Variables/Type_Casting * In C++ use Qt container instead of STL container https://marcmutz.wordpress.com/effective-qt/containers/ * In C++ use range-based loops instead of foreach/Q_FOREACH https://www.kdab.com/goodbye-q_foreach/ * For integer data types int is preferred for small numbers and size_t for big, unsigned values. Use double as floating point type. * The keyword 'auto' can be used in range-based loops but should be avoided else to maintain readability * use smart pointers unique_ptr when possible and shared_ptr otherwise. * Avoid const pass-by-value parameters in function declarations. Still make the parameter const in the same function's definition if it won't be modified. * Use the 'override' specifier when overriding virtual functions from the base class http://en.cppreference.com/w/cpp/language/override * Use braces to enclose a single statement only for readability * In C++ nullptr should be used instead of bug-prone NULL and 0. * #include <...> vs. #include "...": Include headers from external libraries using angle brackets (as in #include ) and headers from LabPlot/SciDAVis using double quotes (as in #include "core/AbstractAspect.h"). Rationale: Headers of external libraries are never in the same directory as the including file, so it makes sense to use the angle bracket form (which searches only in directories specified using -I). If you work with a build system that does not include the current source directory, or disable CMAKE_INCLUDE_CURRENT_DIR, then all angle-bracket-includes referencing LabPlot/SciDAVis headers will break. Excluding the current directory from -I dirs may be desirable one day when using a new library or a new version of a library containing a header file with the same name as one of our headers. * Use DEBUG() macro for debugging code when possible and QDEBUG() only for special Qt data types. DEBUG() works on all supported systems. @code QString string; DEBUG(" string : " << string.toStdString()); @endcode * Use Qt functions for user messages: qDebug(), qWarning(), qCritical(), qFatal(). Check conditions with Q_ASSERT(cond) or Q_ASSERT_X(cond, where, what) and pointers with Q_CHECK_PTR(ptr). * Import C header (from GSL etc.) with extern statement. We use "std::" prefix (C++11) and try to avoid C header like cmath, cfloat etc. by using corresponding C++ constructs (fabs() -> std::abs(), DBL_MAX -> std::numeric_limits::max(), round() -> qRound()). Example: @code extern "C" { #include } if (std::isnan(x)) { ... } @endcode \section links Links Apart from that, the following links are recommended as guidelines for the coding style: http://techbase.kde.org/index.php?title=Policies/Library_Code_Policy http://doc.trolltech.com/qq/qq13-apis.html http://techbase.kde.org/Policies/Kdelibs_Coding_Style */