diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,6 +138,7 @@ option(ENABLE_FITS "Build with FITS support" ON) option(ENABLE_LIBCERF "Build with libcerf support" ON) option(ENABLE_LIBORIGIN "Build with liborigin support" ON) +option(ENABLE_ROOT "Build with ROOT (CERN) support" ON) option(ENABLE_TESTS "Build with tests" ON) ### OS macros #################################### @@ -306,6 +307,32 @@ MESSAGE (STATUS "libcerf Library DISABLED.") ENDIF () +IF (ENABLE_ROOT) +FIND_PACKAGE(ZLIB) +FIND_LIBRARY(LZ4_LIBRARY lz4 + PATHS + /usr/lib + /usr/local/lib +) +FIND_PATH (LZ4_INCLUDE_DIR lz4.h + /usr/include + /usr/local/include +) +IF (LZ4_LIBRARY AND LZ4_INCLUDE_DIR) + SET (LZ4_FOUND TRUE) + MESSAGE (STATUS "Found LZ4 library: ${LZ4_INCLUDE_DIR} ${LZ4_LIBRARY}") +ELSE () + SET (LZ4_FOUND FALSE) +ENDIF () +IF (ZLIB_FOUND AND LZ4_FOUND) + add_definitions (-DHAVE_ZIP) +ELSE () + MESSAGE (STATUS "ZIP libraries not found.") +ENDIF () +ELSE () + MESSAGE (STATUS "ROOT (CERN) importer DISABLED.") +ENDIF () + ################################################# FIND_PATH (XLOCALE_INCLUDE_DIR xlocale.h /usr/include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,7 @@ ${KDEFRONTEND_DIR}/datasources/ImportSQLDatabaseDialog.cpp ${KDEFRONTEND_DIR}/datasources/ImportSQLDatabaseWidget.cpp ${KDEFRONTEND_DIR}/datasources/NetCDFOptionsWidget.cpp + ${KDEFRONTEND_DIR}/datasources/ROOTOptionsWidget.cpp ${KDEFRONTEND_DIR}/datasources/FITSOptionsWidget.cpp ${KDEFRONTEND_DIR}/dockwidgets/AxisDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/NoteDock.cpp @@ -106,6 +107,7 @@ ${KDEFRONTEND_DIR}/ui/datasources/importprojectwidget.ui ${KDEFRONTEND_DIR}/ui/datasources/importsqldatabasewidget.ui ${KDEFRONTEND_DIR}/ui/datasources/netcdfoptionswidget.ui + ${KDEFRONTEND_DIR}/ui/datasources/rootoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/fitsoptionswidget.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/axisdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/cartesianplotdock.ui @@ -200,6 +202,7 @@ ${BACKEND_DIR}/datasources/filters/ImageFilter.cpp ${BACKEND_DIR}/datasources/filters/NetCDFFilter.cpp ${BACKEND_DIR}/datasources/filters/FITSFilter.cpp + ${BACKEND_DIR}/datasources/filters/ROOTFilter.cpp ${BACKEND_DIR}/datasources/projects/ProjectParser.cpp ${BACKEND_DIR}/datasources/projects/LabPlotProjectParser.cpp ${BACKEND_DIR}/datasources/projects/OriginProjectParser.cpp @@ -360,6 +363,9 @@ IF (LIBCERF_FOUND) target_link_libraries( labplot2lib ${LIBCERF_LIBRARY} ) ENDIF () +IF (ZLIB_FOUND AND LZ4_FOUND) + target_link_libraries( labplot2lib ${ZLIB_LIBRARY} ${LZ4_LIBRARY} ) +ENDIF () IF (ENABLE_LIBORIGIN) target_link_libraries( labplot2lib liborigin-static ) ENDIF () diff --git a/src/backend/datasources/LiveDataSource.h b/src/backend/datasources/LiveDataSource.h --- a/src/backend/datasources/LiveDataSource.h +++ b/src/backend/datasources/LiveDataSource.h @@ -49,7 +49,7 @@ Q_ENUMS(FileType) public: - enum FileType {Ascii, Binary, Image, HDF5, NETCDF, FITS}; + enum FileType {Ascii, Binary, Image, HDF5, NETCDF, FITS, ROOT}; enum SourceType { FileOrPipe = 0, NetworkTcpSocket, diff --git a/src/backend/datasources/LiveDataSource.cpp b/src/backend/datasources/LiveDataSource.cpp --- a/src/backend/datasources/LiveDataSource.cpp +++ b/src/backend/datasources/LiveDataSource.cpp @@ -218,6 +218,7 @@ // << "CDF" << i18n("Flexible Image Transport System Data Format (FITS)") // << i18n("Sound") + << i18n("ROOT (CERN) Histograms") ); } @@ -562,6 +563,7 @@ case HDF5: case NETCDF: case FITS: + case ROOT: break; } break; diff --git a/src/backend/datasources/filters/ROOTFilter.h b/src/backend/datasources/filters/ROOTFilter.h new file mode 100644 --- /dev/null +++ b/src/backend/datasources/filters/ROOTFilter.h @@ -0,0 +1,112 @@ +/*************************************************************************** +File : ROOTFilter.h +Project : LabPlot +Description : ROOT(CERN) I/O-filter +-------------------------------------------------------------------- +Copyright : (C) 2018 Christoph Roick (chrisito@gmx.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 ROOTFILTER_H +#define ROOTFILTER_H + +#include "backend/datasources/filters/AbstractFileFilter.h" + +class ROOTFilterPrivate; +class QStringList; +class QIODevice; + + +/// Manages the importing of histograms from ROOT files +class ROOTFilter : public AbstractFileFilter { + Q_OBJECT + +public: + ROOTFilter(); + ~ROOTFilter() override; + + enum ColumnTypes {Center = 1, Low = 2, Content = 4, Error = 8}; + + /** + * @brief Read data from the currently selected histogram + * + * The ROOT file is kept open until the file name is changed + */ + QVector readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, + AbstractFileFilter::ImportMode importMode, int) override; + /// Currently writing to ROOT files is not supported + void write(const QString& fileName, AbstractDataSource*) override; + + void loadFilterSettings(const QString&) override; + void saveFilterSettings(const QString&) const override; + + /// List names of histograms contained in ROOT file + QStringList listHistograms(const QString& fileName); + + /// Set the current histograms, which is one out of listHistograms + void setCurrentHistogram(const QString&); + /// Get the name of the currently set histogram + const QString currentHistogram() const; + + /// Get preview data of the currently set histogram + QVector previewCurrentHistogram(const QString& fileName, + int first, int last); + + /// Get the number of bins in the current histogram + int binsInCurrentHistogram(const QString& fileName); + + /** + * @brief Set the first bin of the histogram to be read + * + * The default of -1 skips the underflow bin with index 0 + */ + void setStartBin(const int bin); + /// Get the index of the first bin to be read + int startBin() const; + /** + * @brief Set the last bin of the histogram to be read + * + * The default of -1 skips the overflow bin + */ + void setEndBin(const int bin); + /// Get the index of the last bin to be read + int endBin() const; + + /** + * @brief Set the first column of the histogram to be read + * + * The following columns are available: Bin Center, Content, Error + */ + void setColumns(const int columns); + /// Get the index of the first column to be read + int columns() const; + + /// Save bin limitation settings + void save(QXmlStreamWriter*) const override; + /// Load bin limitation settings + bool load(XmlStreamReader*) override; + +private: + std::unique_ptr const d; + friend class ROOTFilterPrivate; +}; + +#endif diff --git a/src/backend/datasources/filters/ROOTFilter.cpp b/src/backend/datasources/filters/ROOTFilter.cpp new file mode 100644 --- /dev/null +++ b/src/backend/datasources/filters/ROOTFilter.cpp @@ -0,0 +1,650 @@ +/*************************************************************************** +File : ROOTFilter.cpp +Project : LabPlot +Description : ROOT(CERN) I/O-filter +-------------------------------------------------------------------- +Copyright : (C) 2018 by Christoph Roick (chrisito@gmx.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 * +* * +***************************************************************************/ + +#include "backend/datasources/filters/ROOTFilter.h" +#include "backend/datasources/filters/ROOTFilterPrivate.h" +#include "backend/datasources/AbstractDataSource.h" +#include "backend/core/column/Column.h" + +#include + +#ifdef HAVE_ZIP + +#include +#include + +#endif + +#include +#include +#include +#include +#include +#include + +ROOTFilter::ROOTFilter():AbstractFileFilter(), d(new ROOTFilterPrivate) {} + +ROOTFilter::~ROOTFilter() {} + +QVector ROOTFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, + AbstractFileFilter::ImportMode importMode, int) { + d->readDataFromFile(fileName, dataSource, importMode); + return QVector(); //TODO: remove this later once all read*-functions in the filter classes don't return any preview strings anymore +} + +void ROOTFilter::write(const QString& fileName, AbstractDataSource* dataSource) { + d->write(fileName, dataSource); +} + +void ROOTFilter::loadFilterSettings(const QString& filterName) { + Q_UNUSED(filterName); +} + +void ROOTFilter::saveFilterSettings(const QString& filterName) const { + Q_UNUSED(filterName); +} + +void ROOTFilter::setCurrentHistogram(const QString& histogram) { + d->currentHistogram = histogram; +} + +const QString ROOTFilter::currentHistogram() const { + return d->currentHistogram; +} + +QStringList ROOTFilter::listHistograms(const QString& fileName) { + return d->listHistograms(fileName); +} + +QVector ROOTFilter::previewCurrentHistogram(const QString& fileName, + int first, int last) { + return d->previewCurrentHistogram(fileName, first, last); +} + +int ROOTFilter::binsInCurrentHistogram(const QString& fileName) { + return d->binsInCurrentHistogram(fileName); +} + +void ROOTFilter::setStartBin(const int s) { + d->startBin = s; +} + +int ROOTFilter::startBin() const { + return d->startBin; +} + +void ROOTFilter::setEndBin(const int e) { + d->endBin = e; +} + +int ROOTFilter::endBin() const { + return d->endBin; +} + +void ROOTFilter::setColumns(const int columns) { + d->columns = columns; +} + +int ROOTFilter::columns() const { + return d->columns; +} + +void ROOTFilter::save(QXmlStreamWriter* writer) const { + writer->writeStartElement("rootFilter"); + writer->writeAttribute("startBin", QString::number(d->startBin) ); + writer->writeAttribute("endBin", QString::number(d->endBin) ); + writer->writeAttribute("columns", QString::number(d->columns) ); + writer->writeEndElement(); +} + +bool ROOTFilter::load(XmlStreamReader* reader) { + if (!reader->isStartElement() || reader->name() != "rootFilter") { + reader->raiseError(i18n("no ROOT filter element found")); + return false; + } + + QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); + QXmlStreamAttributes attribs = reader->attributes(); + + // read attributes + QString str = attribs.value("startBin").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'startBin'")); + else + d->startBin = str.toInt(); + + str = attribs.value("endBin").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'endBin'")); + else + d->endBin = str.toInt(); + + str = attribs.value("columns").toString(); + if (str.isEmpty()) + reader->raiseWarning(attributeWarning.arg("'columns'")); + else + d->columns = str.toInt(); + + return true; +} + +/**************** ROOTFilterPrivate implementation *******************/ + +ROOTFilterPrivate::ROOTFilterPrivate() {} + +void ROOTFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, + AbstractFileFilter::ImportMode importMode) { + DEBUG("readDataFromFile()"); + + setFile(fileName); + + auto bins = readHistogram(); + const int nbins = bins.size(); + + // skip underflow and overflow bins by default + int first = qMax(startBin, 0); + int last = qMin(endBin, nbins - 1); + + QStringList colNames = createHeaders(); + + QVector dataContainer; + const int columnOffset = dataSource->prepareImport(dataContainer, importMode, last - first + 1, colNames.size(), + colNames, QVector( + colNames.size(), AbstractColumn::Numeric)); + + // read data + DEBUG("reading " << first - last + 1 << " lines"); + + size_t c = 0; + if (columns & ROOTFilter::Center) { + QVector& container = *static_cast*>(dataContainer[c++]); + for (int i = first; i <= last; ++i) + container[i - first] = (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge) + : i == 0 ? bins.front().lowedge // -infinity + : -bins.front().lowedge; // +infinity + } + if (columns & ROOTFilter::Low) { + QVector& container = *static_cast*>(dataContainer[c++]); + for (int i = first; i <= last; ++i) + container[i - first] = bins[i].lowedge; + } + if (columns & ROOTFilter::Content) { + QVector& container = *static_cast*>(dataContainer[c++]); + for (int i = first; i <= last; ++i) + container[i - first] = bins[i].content; + } + if (columns & ROOTFilter::Error) { + QVector& container = *static_cast*>(dataContainer[c++]); + for (int i = first; i <= last; ++i) + container[i - first] = std::sqrt(bins[i].sumw2); + } + + dataSource->finalizeImport(columnOffset, 0, colNames.size() - 1, QString(), importMode); +} + +void ROOTFilterPrivate::write(const QString& fileName, AbstractDataSource* dataSource) { + Q_UNUSED(fileName); + Q_UNUSED(dataSource); + //TODO +} + +QStringList ROOTFilterPrivate::listHistograms(const QString& fileName) { + setFile(fileName); + + QStringList histList; + for (const auto& hist : currentROOTHist->listHistograms()) { + histList << QString::fromStdString(hist); + } + + return histList; +} + +QVector ROOTFilterPrivate::previewCurrentHistogram(const QString& fileName, + int first, int last) { + DEBUG("previewCurrentHistogram()"); + + setFile(fileName); + + auto bins = readHistogram(); + const int nbins = bins.size(); + + last = qMin(nbins - 1, last); + + QVector preview(qMax(last - first + 2, 1)); + preview.last() = createHeaders(); + + // read data + DEBUG("reading " << preview.size() << " lines"); + + if (columns & ROOTFilter::Center) { + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number( + (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge) + : i == 0 ? bins.front().lowedge // -infinity + : -bins.front().lowedge); // +infinity + } + if (columns & ROOTFilter::Low) { + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number(bins[i].lowedge); + } + if (columns & ROOTFilter::Content) { + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number(bins[i].content); + } + if (columns & ROOTFilter::Error) { + for (int i = first; i <= last; ++i) + preview[i - first] << QString::number(std::sqrt(bins[i].sumw2)); + } + + return preview; +} + +int ROOTFilterPrivate::binsInCurrentHistogram(const QString& fileName) { + setFile(fileName); + + QStringList nameindex = currentHistogram.split(';'); + bool ok = nameindex.size() > 1; + int cycle = ok ? nameindex.last().toInt(&ok) : 1; + if (ok) { + nameindex.removeLast(); + } else { + cycle = 1; + } + + return currentROOTHist->histogramBins(nameindex.join(';').toStdString(), cycle); +} + +QStringList ROOTFilterPrivate::createHeaders() { + QStringList colNames; + if (columns & ROOTFilter::Center) + colNames << i18n("Bin Center"); + if (columns & ROOTFilter::Low) + colNames << i18n("Low Edge"); + if (columns & ROOTFilter::Content) + colNames << i18n("Content"); + if (columns & ROOTFilter::Error) + colNames << i18n("Error"); + return colNames; +} + +void ROOTFilterPrivate::setFile(const QString& fileName) { + if (!currentROOTHist || fileName != currentFile) { + currentFile = fileName; + currentROOTHist.reset(new ROOTHist(fileName.toStdString())); + } +} + +std::vector ROOTFilterPrivate::readHistogram() { + QStringList nameindex = currentHistogram.split(';'); + bool ok = nameindex.size() > 1; + int cycle = ok ? nameindex.last().toInt(&ok) : 1; + if (ok) { + nameindex.removeLast(); + } else { + cycle = 1; + } + + return currentROOTHist->readHistogram(nameindex.join(';').toStdString(), cycle); +} + +/******************** ROOTHist implementation ************************/ + +namespace ROOTHistHelpers { + +/// Read value from stream +template +T read(std::ifstream& is) { + union { + T val; + char buf[sizeof(T)]; + } r; + for (size_t i = 0; i < sizeof(T); ++i) { + r.buf[sizeof(T) - i - 1] = is.get(); + } + + return r.val; +} + +/// Read value from buffer +template +T read(char*& s) { + union { + T val; + char buf[sizeof(T)]; + } r; + for (size_t i = 0; i < sizeof(T); ++i) { + r.buf[sizeof(T) - i - 1] = *(s++); + } + + return r.val; +} + +/// Get version of ROOT object, obtain number of bytes in object +short Version(char*& buffer, size_t& count) { + count = read(buffer); + short version = (count & 0x40000000) ? read(buffer) : read(buffer -= 4); + count &= (count & 0x40000000) ? (~0x40000000) : 0; + return version; +} + +/// Get version of ROOT object +short Version(char*& buffer) { + size_t c; + return Version(buffer, c); +} + +/// Skip ROOT object +void Skip(char*& buffer, const size_t& n) { + for (size_t i = 0; i < n; ++i) { + char* nbuf = buffer + 4; + size_t count; + Version(buffer, count); + buffer = nbuf + count; + } +} + +/// Skip TObject header +void SkipObject(char*& buffer) { + Version(buffer); + buffer += 8; +} + +/// Get TString +std::string String(char*& buffer) { + size_t s = *(buffer++); + if (s == 0) + return std::string(); + else { + if (s == 0xFF) + s = read(buffer); + buffer += s; + return std::string(buffer - s, buffer); + } +} + +} + +using namespace ROOTHistHelpers; + +ROOTHist::ROOTHist(const std::string& filename) : filename(filename) { + // The file structure is described in root/io/io/src/TFile.cxx + std::ifstream is(filename, std::ifstream::binary); + std::string root(4, 0); + is.read(const_cast(root.data()), 4); + if (root != "root") + return; + + is.seekg(8); // skip version + int pos = read(is); + is.seekg(16); + int lastpos = read(is); + + is.seekg(33); + compression = read(is); + compression = compression > 0 ? compression : 0; + + while (is.good() && pos < lastpos) { + is.seekg(pos); + size_t lcdata = read(is); + is.seekg(2, is.cur); // short version = read(is); + size_t ldata = read(is); + is.seekg(4, is.cur); + size_t lkey = read(is); + short cycle = read(is); + is.seekg(8, is.cur); + std::string cname(read(is), 0); + is.read(&cname[0], cname.size()); + if (cname.size() == 4 && cname.substr(0, 3) == "TH1") { + KeyBuffer::ContentType type; + switch (cname[3]) { + case 'D': + type = KeyBuffer::ContentType::Double; + break; + case 'F': + type = KeyBuffer::ContentType::Float; + break; + case 'I': + type = KeyBuffer::ContentType::Int; + break; + case 'S': + type = KeyBuffer::ContentType::Short; + break; + case 'C': + type = KeyBuffer::ContentType::Byte; + break; + default: + type = KeyBuffer::ContentType::Invalid; + break; + } + if (type) { + std::string name(read(is), 0); + is.read(&name[0], name.size()); + std::string title(read(is), 0); + is.read(&title[0], title.size()); + + auto it = histkeys.end(); + // see root/io/io/src/TKey.cxx for reference + if (compression && ldata > 256) { +# ifdef HAVE_ZIP + if (compression / 100 <= 1) { + // see root/core/zip/src/RZip.cxx -> R__unzip + std::string lib(2, 0); + is.read(&lib[0], 2); + char method = is.get(); + size_t chcdata = is.get(); + chcdata |= (is.get() << 8); + chcdata |= (is.get() << 16); + size_t chdata = is.get(); + chdata |= (is.get() << 8); + chdata |= (is.get() << 16); + + if (chcdata == lcdata - lkey - 9 && chdata == ldata) { + if (lib == "ZL" && method == Z_DEFLATED) { + it = histkeys.emplace(name + ";" + std::to_string(cycle), + KeyBuffer{name, title, cycle, type, KeyBuffer::zlib, + pos + lkey + 9, chcdata, chdata, 0} + ).first; + } else if (lib == "L4" && method == LZ4_versionNumber() / (100 * 100)) { + it = histkeys.emplace(name + ";" + std::to_string(cycle), + KeyBuffer{name, title, cycle, type, KeyBuffer::lz4, + pos + lkey + 9 + 8, chcdata - 8, chdata, 0} + ).first; + } + } + } +# endif + } else { + it = histkeys.emplace(name + ";" + std::to_string(cycle), + KeyBuffer{name, title, cycle, type, KeyBuffer::none, + pos + lkey, ldata, ldata, 0} + ).first; + } + if (it != histkeys.end()) + readNBins(it->second); + } + } + pos += lcdata; + } +} + +std::vector ROOTHist::listHistograms() const { + std::vector l; + for (auto& n : histkeys) { + l.emplace_back(n.first); + } + return l; +} + +std::vector ROOTHist::readHistogram(const std::string& name, int cycle) { + auto it = histkeys.find(name + ";" + std::to_string(cycle)); + if (it == histkeys.end()) + return std::vector(); + + std::string buffer = data(it->second); + if (!buffer.empty()) { + // The object structure can be retrieved from TFile::GetStreamerInfoList(). + // Every ROOT object contains a version number which may include the byte count + // for the object. The latter is currently assumed to be present to skip unused + // objects. No checks are performed. The corresponding ROOT code is quite nested + // but the actual readout is straight forward. + // Files for reference: + // root/io/io/src/TBufferFile.cxx -> ReadVersion, ReadTString, ReadObjectAny + // root/core/cont/src/TArrayD.cxx -> Streamer + // root/hist/hist/src/TH1.cxx -> Streamer + char *buf = &buffer[0]; + size_t count; + Version(buf, count); // TH1(D/F/I/S/C) + char* dbuf = buf + 4; + Version(buf, count); // TH1 + dbuf += count; + Skip(buf, 4); // skip TNamed, TAttLine, TAttFill, TAttMarker + + std::vector r(read(buf)); // fNcells + if (r.size() < 3) + return std::vector(); + + r.front().lowedge = -std::numeric_limits::infinity(); + + // x-Axis + char* nbuf = buf + 4; + Version(buf, count); // TAxis + nbuf += count; + Skip(buf, 2); // skip TNamed, TAttAxis + + int nbins = read(buf); + double xmin = read(buf); + double xmax = read(buf); + const size_t nborders = read(buf); + + if (nborders == r.size() - 1) { + for (size_t i = 0; i < nborders; ++i) { + r[i + 1].lowedge = read(buf); + } + } else { + buf += sizeof(double) * nbins; + const double scale = (xmax - xmin) / static_cast(nbins); + for (size_t i = 0; i < r.size() - 1; ++i) { + r[i + 1].lowedge = static_cast(i) * scale + xmin; + } + } + buf = nbuf; + Skip(buf, 2); // skip y-Axis and z-Axis; + buf += 68; // skip 2 shorts and 8 doubles + buf += read(buf) * 8; // skip fContour array + + if (static_cast(read(buf)) == r.size()) { + for (auto& b : r) + b.sumw2 = read(buf); // always double + } + + buf = dbuf; // skip to contents of TH1(D/F/I/S/C) + + if (static_cast(read(buf)) == r.size()) { + switch (it->second.type) { + case KeyBuffer::ContentType::Double: + for (auto& b : r) + b.content = read(buf); + break; + case KeyBuffer::ContentType::Float: + for (auto& b : r) + b.content = read(buf); + break; + case KeyBuffer::ContentType::Int: + for (auto& b : r) + b.content = read(buf); + break; + case KeyBuffer::ContentType::Short: + for (auto& b : r) + b.content = read(buf); + break; + case KeyBuffer::ContentType::Byte: + for (auto& b : r) + b.content = read(buf); + break; + case KeyBuffer::ContentType::Invalid: // never reached + default: + break; + } + } + + return r; + } else + return std::vector(); +} + +void ROOTHist::readNBins(ROOTHist::KeyBuffer& kbuffer) { + std::string buffer = data(kbuffer); + if (!buffer.empty()) { + // The object structure can be retrieved from TFile::GetStreamerInfoList(). + // Every ROOT object contains a version number which may include the byte count + // for the object. The latter is currently assumed to be present to skip unused + // objects. No checks are performed. The corresponding ROOT code is quite nested + // but the actual readout is straight forward. + // Files for reference: + // root/io/io/src/TBufferFile.cxx -> ReadVersion, ReadTString, ReadObjectAny + // root/core/cont/src/TArrayD.cxx -> Streamer + // root/hist/hist/src/TH1.cxx -> Streamer + char *buf = &buffer[0]; + size_t count; + Version(buf, count); // TH1(D/F/I/S/C) + char* dbuf = buf + 4; + Version(buf, count); // TH1 + dbuf += count; + Skip(buf, 4); // skip TNamed, TAttLine, TAttFill, TAttMarker + + kbuffer.nbins = read(buf); // fNcells + } +} + +std::string ROOTHist::data(const ROOTHist::KeyBuffer& buffer) const { + std::ifstream is(filename, std::ifstream::binary); + return data(buffer, is); +} + +std::string ROOTHist::data(const ROOTHist::KeyBuffer& buffer, std::ifstream& is) const { + std::string data(buffer.count, 0); + is.seekg(buffer.start); + if (buffer.compression == KeyBuffer::none) { + is.read(&data[0], buffer.count); + return data; +#ifdef HAVE_ZIP + } else if (buffer.compression == KeyBuffer::zlib) { + std::string cdata(buffer.compressed_count, 0); + is.read(&cdata[0], buffer.compressed_count); + size_t luncomp = buffer.count; + if (uncompress((Bytef *)data.data(), &luncomp, (Bytef *)cdata.data(), cdata.size()) == Z_OK && data.size() == luncomp) + return data; + } else { + std::string cdata(buffer.compressed_count, 0); + is.read(&cdata[0], buffer.compressed_count); + if (LZ4_decompress_safe(cdata.data(), const_cast(data.data()), buffer.compressed_count, buffer.count) == static_cast(buffer.count)) + return data; +#endif + } + + return std::string(); +} diff --git a/src/backend/datasources/filters/ROOTFilterPrivate.h b/src/backend/datasources/filters/ROOTFilterPrivate.h new file mode 100644 --- /dev/null +++ b/src/backend/datasources/filters/ROOTFilterPrivate.h @@ -0,0 +1,188 @@ +/*************************************************************************** +File : ROOTFilterPrivate.h +Project : LabPlot +Description : Private implementation class for ROOTFilter. +-------------------------------------------------------------------- +Copyright : (C) 2018 Christoph Roick (chrisito@gmx.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 ROOTFILTERPRIVATE_H +#define ROOTFILTERPRIVATE_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +class ROOTFilter; + +class AbstractDataSource; +class AbstractColumn; + +/** + * @brief Read TH1 histograms from ROOT files without depending on ROOT libraries + */ +class ROOTHist { +public: + /** + * @brief Open ROOT file and save file positions of histograms + * + * Also checks for the compression level. Currently the default ZLIB and LZ4 compression + * types are supported. The TH1 object structure is hard coded, TStreamerInfo is not read. + * + * @param[in] filename ROOT file to be read + */ + ROOTHist(const std::string& filename); + + struct BinPars { + double content; + double sumw2; + double lowedge; + }; + + /** + * @brief List available histograms in the ROOT file + */ + std::vector listHistograms() const; + + /** + * @brief Read histogram from file + * + * Jumps to memoized file position, decompresses the object if required and analyzes + * the buffer. Overflow and underflow bins are included. + * + * @param[in] name Histogram name without cycle indicator + * @param[in] cycle Indicator for object cycle + */ + std::vector readHistogram(const std::string& name, int cycle = 1); + + /** + * @brief Get histogram title + * + * The title is stored in the buffer. No file access required. + * + * @param[in] name Histogram name without cycle indicator + * @param[in] cycle Indicator for object cycle + */ + std::string histogramTitle(const std::string& name, int cycle = 1) + { + auto it = histkeys.find(name + ";" + std::to_string(cycle)); + if (it != histkeys.end()) + return it->second.title; + else + return std::string(); + } + + /** + * @brief Get number of bins in histogram + * + * The number of bins is stored in the buffer. No file access required. + * + * @param[in] name Histogram name without cycle indicator + * @param[in] cycle Indicator for object cycle + */ + int histogramBins(const std::string& name, int cycle = 1) + { + auto it = histkeys.find(name + ";" + std::to_string(cycle)); + if (it != histkeys.end()) + return it->second.nbins; + else + return 0; + } +private: + struct KeyBuffer { + std::string name; + std::string title; + int cycle; + enum ContentType { Invalid = 0, Double, Float, Int, Short, Byte } type; + enum CompressionType { none, zlib, lz4 } compression; + size_t start; + size_t compressed_count; + size_t count; + int nbins; + }; + + /// Get the number of bins contained in the histogram + void readNBins(ROOTHist::KeyBuffer& buffer); + /// Get buffer from file content at histogram position + std::string data(const ROOTHist::KeyBuffer& buffer) const; + /// Get buffer from file content at histogram position, uses already opened stream + std::string data(const ROOTHist::KeyBuffer& buffer, std::ifstream& is) const; + + std::string filename; + std::map histkeys; + int compression; +}; + +class ROOTFilterPrivate { + +public: + ROOTFilterPrivate(); + + /** + * @brief Read data from the currently selected histogram + * + * The ROOT file is kept open until the file name is changed + */ + void readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, + AbstractFileFilter::ImportMode importMode); + /// Currently writing to ROOT files is not supported + void write(const QString& fileName, AbstractDataSource*); + + /// List names of histograms contained in ROOT file + QStringList listHistograms(const QString& fileName); + + /// Get preview data of the currently set histogram + QVector previewCurrentHistogram(const QString& fileName, + int first, int last); + + /// Get the number of bins in the current histogram + int binsInCurrentHistogram(const QString& fileName); + + /// Identifier of the current histogram + QString currentHistogram; + /// Start bin to read (can be -1, skips the underflow bin 0) + int startBin = -1; + /// End bin to read (can be -1, skips the overflow bin) + int endBin = -1; + /// Start column to read + int columns = 0; +private: + /// Create headers from set columns + QStringList createHeaders(); + /// Checks and updates the current ROOT file path + void setFile(const QString& fileName); + /// Calls ReadHistogram from ROOTHist + std::vector readHistogram(); + + /// Currently set ROOT file path + QString currentFile; + /// ROOTHist instance kept alive while currentFile does not change + std::unique_ptr currentROOTHist; +}; + +#endif diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -34,6 +34,7 @@ #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/datasources/filters/HDF5Filter.h" #include "backend/datasources/filters/NetCDFFilter.h" +#include "backend/datasources/filters/ROOTFilter.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/core/Workbook.h" @@ -205,17 +206,29 @@ Workbook* workbook = qobject_cast(aspect); QVector sheets = workbook->children(); - QStringList names; LiveDataSource::FileType fileType = m_importFileWidget->currentFileType(); - if (fileType == LiveDataSource::HDF5) - names = m_importFileWidget->selectedHDF5Names(); - else if (fileType == LiveDataSource::NETCDF) - names = m_importFileWidget->selectedNetCDFNames(); - - //multiple extensions selected + // multiple data sets/variables for HDF5, NetCDF and ROOT + if (fileType == LiveDataSource::HDF5 || + fileType == LiveDataSource::NETCDF || + fileType == LiveDataSource::ROOT) { + QStringList names; + switch (fileType) { + case LiveDataSource::HDF5: + names = m_importFileWidget->selectedHDF5Names(); + break; + case LiveDataSource::NETCDF: + names = m_importFileWidget->selectedNetCDFNames(); + break; + case LiveDataSource::ROOT: + names = m_importFileWidget->selectedROOTNames(); + break; + case LiveDataSource::Ascii: + case LiveDataSource::Binary: + case LiveDataSource::Image: + case LiveDataSource::FITS: + break; // never reached, omit warning + } - // multiple data sets/variables for HDF5/NetCDF - if (fileType == LiveDataSource::HDF5 || fileType == LiveDataSource::NETCDF) { int nrNames = names.size(), offset = sheets.size(); int start=0; @@ -237,10 +250,22 @@ // import to sheets sheets = workbook->children(); for (int i = 0; i < nrNames; ++i) { - if (fileType == LiveDataSource::HDF5) - ((HDF5Filter*) filter)->setCurrentDataSetName(names[i]); - else - ((NetCDFFilter*) filter)->setCurrentVarName(names[i]); + switch (fileType) { + case LiveDataSource::HDF5: + ((HDF5Filter*) filter)->setCurrentDataSetName(names[i]); + break; + case LiveDataSource::NETCDF: + ((NetCDFFilter*) filter)->setCurrentVarName(names[i]); + break; + case LiveDataSource::ROOT: + ((ROOTFilter*) filter)->setCurrentHistogram(names[i]); + break; + case LiveDataSource::Ascii: + case LiveDataSource::Binary: + case LiveDataSource::Image: + case LiveDataSource::FITS: + break; // never reached, omit warning + } if (sheets[i+offset]->inherits("Matrix")) filter->readDataFromFile(fileName, qobject_cast(sheets[i+offset])); diff --git a/src/kdefrontend/datasources/ImportFileWidget.h b/src/kdefrontend/datasources/ImportFileWidget.h --- a/src/kdefrontend/datasources/ImportFileWidget.h +++ b/src/kdefrontend/datasources/ImportFileWidget.h @@ -41,6 +41,7 @@ class ImageOptionsWidget; class NetCDFOptionsWidget; class FITSOptionsWidget; +class ROOTOptionsWidget; class QTableWidget; class ImportFileWidget : public QWidget { @@ -61,6 +62,7 @@ const QStringList selectedHDF5Names() const; const QStringList selectedNetCDFNames() const; const QStringList selectedFITSExtensions() const; + const QStringList selectedROOTNames() const; void hideDataSource(); void showAsciiHeaderOptions(bool); @@ -79,6 +81,7 @@ std::unique_ptr m_imageOptionsWidget; std::unique_ptr m_netcdfOptionsWidget; std::unique_ptr m_fitsOptionsWidget; + std::unique_ptr m_rootOptionsWidget; QTableWidget* m_twPreview; const QString& m_fileName; bool m_fileEmpty; @@ -112,6 +115,7 @@ friend class HDF5OptionsWidget; // to access refreshPreview() friend class NetCDFOptionsWidget; // to access refreshPreview() and others friend class FITSOptionsWidget; + friend class ROOTOptionsWidget; // to access refreshPreview() and others }; #endif diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -36,12 +36,14 @@ #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/ImageFilter.h" #include "backend/datasources/filters/FITSFilter.h" +#include "backend/datasources/filters/ROOTFilter.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" +#include "ROOTOptionsWidget.h" #include #include @@ -109,6 +111,10 @@ m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->insertWidget(LiveDataSource::FITS, fitsw); + QWidget* rootw = new QWidget(); + m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); + ui.swOptions->insertWidget(LiveDataSource::ROOT, rootw); + // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); @@ -120,7 +126,7 @@ // default filter ui.swOptions->setCurrentIndex(LiveDataSource::Ascii); -#if !defined(HAVE_HDF5) || !defined(HAVE_NETCDF) || !defined(HAVE_FITS) +#if !defined(HAVE_HDF5) || !defined(HAVE_NETCDF) || !defined(HAVE_FITS) || !defined(HAVE_ZIP) const QStandardItemModel* model = qobject_cast(ui.cbFileType->model()); #endif #ifndef HAVE_HDF5 @@ -138,6 +144,11 @@ QStandardItem* item3 = model->item(LiveDataSource::FITS); item3->setFlags(item3->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif +#ifndef HAVE_ZIP + // disable ROOT item + QStandardItem* item4 = model->item(LiveDataSource::ROOT); + item4->setFlags(item4->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); +#endif ui.cbReadType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); @@ -319,6 +330,10 @@ const QString& extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) name += QLatin1Char('/') + extensionName; + } else if (format == LiveDataSource::ROOT) { + const QStringList& names = m_rootOptionsWidget->selectedROOTNames(); + if (names.size()) + name += QLatin1Char('/') + names.first(); } return name; @@ -498,6 +513,18 @@ filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); + return filter; + } + case LiveDataSource::ROOT: { + ROOTFilter* filter = new ROOTFilter(); + QStringList names = selectedROOTNames(); + if (!names.isEmpty()) + filter->setCurrentHistogram(names.first()); + + filter->setStartBin( m_rootOptionsWidget->startBin() ); + filter->setEndBin( m_rootOptionsWidget->endBin() ); + filter->setColumns( m_rootOptionsWidget->columns() ); + return filter; } } @@ -565,6 +592,7 @@ m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); m_fitsOptionsWidget->clear(); + m_rootOptionsWidget->clear(); emit fileNameChanged(); return; @@ -610,6 +638,11 @@ // update FITS tree widget using current selected file m_fitsOptionsWidget->updateContent((FITSFilter*)this->currentFileFilter(), fileName); + } else if (fileInfo.contains(QLatin1String("ROOT Data Format")) || fileName.endsWith(QLatin1String("root"), Qt::CaseInsensitive)) { // TODO find out file description + ui.cbFileType->setCurrentIndex(LiveDataSource::ROOT); + + // update ROOT list widget using current selected file + m_rootOptionsWidget->updateContent((ROOTFilter*)this->currentFileFilter(), fileName); } else if (fileInfo.contains("image") || fileInfo.contains("bitmap") || !imageFormat.isEmpty()) ui.cbFileType->setCurrentIndex(LiveDataSource::Image); else @@ -651,6 +684,10 @@ ui.lFilter->show(); ui.cbFilter->show(); + //if we switch from ROOT format (only two tabs available), add the data portion-tab again + if (ui.tabWidget->count() == 1) { + ui.tabWidget->insertTab(1, ui.tabDataPortion, i18n("Data portion to read")); + } //if we switch from netCDF-format (only two tabs available), add the data preview-tab again if (ui.tabWidget->count() == 2) { ui.tabWidget->setTabText(0, i18n("Data format")); @@ -672,6 +709,9 @@ ui.lEndColumn->hide(); ui.sbEndColumn->hide(); break; + case LiveDataSource::ROOT: + ui.tabWidget->removeTab(1); + // falls through case LiveDataSource::HDF5: case LiveDataSource::NETCDF: ui.lFilter->hide(); @@ -700,6 +740,7 @@ m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); + m_rootOptionsWidget->clear(); int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); @@ -726,6 +767,10 @@ return m_fitsOptionsWidget->selectedFITSExtensions(); } +const QStringList ImportFileWidget::selectedROOTNames() const { + return m_rootOptionsWidget->selectedROOTNames(); +} + /*! shows the dialog with the information about the file(s) to be imported. */ @@ -741,8 +786,11 @@ */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats - if (ui.cbFileType->currentIndex() == LiveDataSource::HDF5 || ui.cbFileType->currentIndex() == LiveDataSource::NETCDF - || ui.cbFileType->currentIndex() == LiveDataSource::Image || ui.cbFileType->currentIndex() == LiveDataSource::FITS) { + if (ui.cbFileType->currentIndex() == LiveDataSource::HDF5 || + ui.cbFileType->currentIndex() == LiveDataSource::NETCDF || + ui.cbFileType->currentIndex() == LiveDataSource::Image || + ui.cbFileType->currentIndex() == LiveDataSource::FITS || + ui.cbFileType->currentIndex() == LiveDataSource::ROOT) { ui.swOptions->setEnabled(true); return; } @@ -934,6 +982,24 @@ tmpTableWidget = m_fitsOptionsWidget->previewWidget(); break; } + case LiveDataSource::ROOT: { + DEBUG(" ROOT"); + ROOTFilter *filter = (ROOTFilter *)this->currentFileFilter(); + lines = m_rootOptionsWidget->lines(); + m_rootOptionsWidget->setNBins(filter->binsInCurrentHistogram(fileName)); + importedStrings = filter->previewCurrentHistogram( + fileName, + m_rootOptionsWidget->startBin(), + qMin(m_rootOptionsWidget->startBin() + m_rootOptionsWidget->lines() - 1, + m_rootOptionsWidget->endBin()) + ); + tmpTableWidget = m_rootOptionsWidget->previewWidget(); + // the last vector element contains the column names + vectorNameList = importedStrings.last(); + importedStrings.removeLast(); + columnModes = QVector(vectorNameList.size(), AbstractColumn::Numeric); + break; + } } // fill the table widget diff --git a/src/kdefrontend/datasources/ROOTOptionsWidget.h b/src/kdefrontend/datasources/ROOTOptionsWidget.h new file mode 100644 --- /dev/null +++ b/src/kdefrontend/datasources/ROOTOptionsWidget.h @@ -0,0 +1,63 @@ +/*************************************************************************** +File : ROOTOptionsWidget.h +Project : LabPlot +Description : widget providing options for the import of ROOT data +-------------------------------------------------------------------- +Copyright : (C) 2018 Christoph Roick (chrisito@gmx.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 ROOTOPTIONSWIDGET_H +#define ROOTOPTIONSWIDGET_H + +#include "ui_rootoptionswidget.h" + +class ROOTFilter; +class ImportFileWidget; + +/// Widget providing options for the import of ROOT data +class ROOTOptionsWidget : public QWidget { + Q_OBJECT + +public: + explicit ROOTOptionsWidget(QWidget*, ImportFileWidget*); + void clear(); + /// Fill the list of available histograms + void updateContent(ROOTFilter* filter, QString fileName); + /// Return a list of selected histograms + const QStringList selectedROOTNames() const; + int lines() const { return ui.sbPreviewLines->value(); } + int startBin() const { return ui.sbFirst->value(); } + int endBin() const { return ui.sbLast->value(); } + int columns() const; + void setNBins(int nbins); + QTableWidget* previewWidget() const { return ui.twPreview; } + +private: + Ui::ROOTOptionsWidget ui; + ImportFileWidget* m_fileWidget; + +private slots: + /// Updates the selected data set of a ROOT file when a new item is selected + void rootListWidgetSelectionChanged(); +}; + +#endif diff --git a/src/kdefrontend/datasources/ROOTOptionsWidget.cpp b/src/kdefrontend/datasources/ROOTOptionsWidget.cpp new file mode 100644 --- /dev/null +++ b/src/kdefrontend/datasources/ROOTOptionsWidget.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** +File : ROOTOptionsWidget.cpp +Project : LabPlot +Description : widget providing options for the import of ROOT data +-------------------------------------------------------------------- +Copyright : (C) 2018 Christoph Roick (chrisito@gmx.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 * + * * + ***************************************************************************/ + +#include "ROOTOptionsWidget.h" + +#include "ImportFileWidget.h" + +#include "backend/datasources/filters/ROOTFilter.h" +#include "backend/lib/macros.h" + +ROOTOptionsWidget::ROOTOptionsWidget(QWidget* parent, ImportFileWidget* fileWidget) : QWidget(parent), m_fileWidget(fileWidget) { + ui.setupUi(parent); + + connect(ui.lwContent, &QListWidget::itemSelectionChanged, this, &ROOTOptionsWidget::rootListWidgetSelectionChanged); + connect(ui.bRefreshPreview, &QPushButton::clicked, fileWidget, &ImportFileWidget::refreshPreview); +} + +void ROOTOptionsWidget::clear() { + ui.lwContent->clear(); + ui.twPreview->clearContents(); +} + +void ROOTOptionsWidget::updateContent(ROOTFilter *filter, QString fileName) { + DEBUG("updateContent()"); + ui.lwContent->clear(); + ui.lwContent->addItems(filter->listHistograms(fileName)); +} + +void ROOTOptionsWidget::rootListWidgetSelectionChanged() { + DEBUG("rootListWidgetSelectionChanged()"); + auto items = ui.lwContent->selectedItems(); + QDEBUG("SELECTED ITEMS =" << items); + + if (items.isEmpty()) + return; + + m_fileWidget->refreshPreview(); +} + +const QStringList ROOTOptionsWidget::selectedROOTNames() const { + QStringList names; + + for (const QListWidgetItem* const item : ui.lwContent->selectedItems()) + names << item->text(); + + return names; +} + +int ROOTOptionsWidget::columns() const { + int cols = ui.checkCenter->isChecked() ? ROOTFilter::Center : 0; + cols |= ui.checkLow->isChecked() ? ROOTFilter::Low : 0; + cols |= ui.checkContent->isChecked() ? ROOTFilter::Content : 0; + cols |= ui.checkError->isChecked() ? ROOTFilter::Error : 0; + + return cols; +} + +void ROOTOptionsWidget::setNBins(int nbins) { + // try to retain the range settings: + // - if nbins was not 0, keep start bin, + // else set it to one after underflow + // - if nbins didn't change, keep end bin, + // else set it to one before overflow + const int max = qMax(nbins - 1, 0); + int firstval = ui.sbFirst->value(); + if (ui.sbFirst->maximum() == 0) + firstval = qMin(nbins - 1, 1); + ui.sbFirst->setMaximum(max); + ui.sbFirst->setValue(firstval); + + int lastval = max == ui.sbLast->maximum() ? ui.sbLast->value() : qMax(max - 1, 0); + ui.sbLast->setMaximum(max); + ui.sbLast->setValue(lastval); +} diff --git a/src/kdefrontend/ui/datasources/rootoptionswidget.ui b/src/kdefrontend/ui/datasources/rootoptionswidget.ui new file mode 100644 --- /dev/null +++ b/src/kdefrontend/ui/datasources/rootoptionswidget.ui @@ -0,0 +1,198 @@ + + + ROOTOptionsWidget + + + + 0 + 0 + 516 + 242 + + + + + + + + + Number of bins to preview: + + + + + + + 1 + + + 10000 + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Refresh + + + + + + + + + + + + + + Data to import: + + + + + + + + + Bin Center + + + true + + + + + + + Error + + + true + + + + + + + Low Edge + + + + + + + Content + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 100 + + + 1 + + + + + + + 100 + + + 99 + + + + + + + First Bin: + + + + + + + Last Bin: + + + + + + + + + + + + + Qt::Horizontal + + + + Shows the content of a HDF5 file + + + QAbstractItemView::ExtendedSelection + + + + + QAbstractItemView::NoEditTriggers + + + true + + + false + + + + + + + + + + +