diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index b6732109..540124f7 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -160,12 +160,14 @@ set(HDR_UTIL source/scwx/qt/util/color.hpp source/scwx/qt/util/font_buffer.hpp source/scwx/qt/util/json.hpp source/scwx/qt/util/streams.hpp - source/scwx/qt/util/texture_atlas.hpp) + source/scwx/qt/util/texture_atlas.hpp + source/scwx/qt/util/q_file_buffer.hpp) set(SRC_UTIL source/scwx/qt/util/color.cpp source/scwx/qt/util/font.cpp source/scwx/qt/util/font_buffer.cpp source/scwx/qt/util/json.cpp - source/scwx/qt/util/texture_atlas.cpp) + source/scwx/qt/util/texture_atlas.cpp + source/scwx/qt/util/q_file_buffer.cpp) set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp source/scwx/qt/view/level3_product_view.hpp source/scwx/qt/view/level3_radial_view.hpp diff --git a/scwx-qt/source/scwx/qt/util/q_file_buffer.cpp b/scwx-qt/source/scwx/qt/util/q_file_buffer.cpp new file mode 100644 index 00000000..28640717 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/q_file_buffer.cpp @@ -0,0 +1,306 @@ +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +static const std::string logPrefix_ = "scwx::qt::util::q_file_buffer"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +// Adapted from Microsoft filebuf reference implementation +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +class QFileBuffer::Impl +{ +public: + explicit Impl(QFileBuffer* self) : self_ {self} {}; + ~Impl() = default; + + void ResetPutback(); + void SetPutback(); + + QFileBuffer* self_; + QFile file_ {}; + char_type putbackChar_ {}; + char_type* putbackEback_ {nullptr}; + char_type* putbackEgptr_ {nullptr}; +}; + +void QFileBuffer::Impl::ResetPutback() +{ + if (self_->eback() == &putbackChar_) + { + // Restore get buffer after putback + self_->setg(putbackEback_, putbackEback_, putbackEgptr_); + } +} + +void QFileBuffer::Impl::SetPutback() +{ + if (self_->eback() != &putbackChar_) + { + // Save current get buffer + putbackEback_ = self_->eback(); + putbackEgptr_ = self_->egptr(); + } + self_->setg(&putbackChar_, &putbackChar_, &putbackChar_ + 1); +} + +QFileBuffer::QFileBuffer() : std::streambuf(), p(std::make_unique(this)) +{ + // Initialize read/write pointers + setg(0, 0, 0); + setp(0, 0); +} + +QFileBuffer::QFileBuffer(const std::string& filename, + std::ios_base::openmode mode) : + QFileBuffer() +{ + open(filename, mode); +} + +QFileBuffer::~QFileBuffer() = default; + +QFileBuffer::QFileBuffer(QFileBuffer&&) noexcept = default; +QFileBuffer& QFileBuffer::operator=(QFileBuffer&&) noexcept = default; + +bool QFileBuffer::is_open() const +{ + return p->file_.isOpen(); +} + +QFileBuffer* QFileBuffer::open(const std::string& filename, + std::ios_base::openmode mode) +{ + // If the associated file is already open, return a null pointer right away + if (is_open()) + { + return nullptr; + } + + // Validate supported modes + if (mode & std::ios_base::out || mode & std::ios_base::app || + mode & std::ios_base::trunc) + { + logger_->error("open(): write mode not supported"); + return nullptr; + } + + // Convert std iostream flags to Qt flags + QIODeviceBase::OpenMode flags {}; + if (mode & std::ios_base::in) + { + flags |= QIODeviceBase::OpenModeFlag::ReadOnly; + } + if ((mode & std::ios_base::binary) != std::ios_base::binary) + { + flags |= QIODeviceBase::OpenModeFlag::Text; + } + + // Set the filename and open the file + p->file_.setFileName(QString::fromStdString(filename)); + bool isOpen = p->file_.open(flags); + + if (isOpen) + { + // Seek to end if requested + if (mode & std::ios_base::ate) + { + // Seek to end + p->file_.seek(p->file_.size()); + } + + // Initialize read/write pointers + setg(0, 0, 0); + setp(0, 0); + } + + return isOpen ? this : nullptr; +} + +QFileBuffer* QFileBuffer::close() +{ + // If the associated file is already closed, return a null pointer right away + if (!p->file_.isOpen()) + { + return nullptr; + } + + // Close the file + p->file_.close(); + + return this; +} + +QFileBuffer::int_type QFileBuffer::pbackfail(int_type c) +{ + if (gptr() && eback() < gptr() && + (traits_type::eq_int_type(traits_type::eof(), c) || + traits_type::eq_int_type(traits_type::to_int_type(gptr()[-1]), c))) + { + // Just back up position + gbump(static_cast(sizeof(char_type)) * -1); + return traits_type::not_eof(c); + } + else if (!is_open() || traits_type::eq_int_type(traits_type::eof(), c)) + { + // No open QFile or EOF, fail + return traits_type::eof(); + } + else if (gptr() != &p->putbackChar_) + { + // Put back to buffer + p->putbackChar_ = traits_type::to_char_type(c); + p->SetPutback(); + return c; + } + else + { + // Nowhere to put back + return traits_type::eof(); + } +} + +QFileBuffer::pos_type QFileBuffer::seekoff(off_type off, + std::ios_base::seekdir dir, + std::ios_base::openmode /* which */) +{ + pos_type newPos {pos_type(off_type(-1))}; + + switch (dir) + { + case std::ios_base::beg: + // Seek using absolute position + newPos = seekpos(off, std::ios_base::in); + break; + + case std::ios_base::cur: + { + const pos_type currentPos {p->file_.pos() - (egptr() - gptr())}; + pos_type updatePos {currentPos + off}; + + // If the putback buffer is not empty, decrement the offset + if (gptr() == &p->putbackChar_) + { + updatePos -= static_cast(sizeof(char_type)); + } + + // Seek the file + if (p->file_.seek(updatePos)) + { + // Record updated position + newPos = updatePos; + } + + break; + } + + case std::ios_base::end: + { + const pos_type endPos {p->file_.size()}; + const pos_type updatePos {endPos + off}; + + // Seek the file + if (p->file_.seek(updatePos)) + { + // Record updated position + newPos = updatePos; + } + + break; + } + } + + if (newPos != static_cast(-1)) + { + p->ResetPutback(); + } + + return newPos; +} + +QFileBuffer::pos_type QFileBuffer::seekpos(pos_type pos, + std::ios_base::openmode /* which */) +{ + pos_type newPos {pos_type(off_type(-1))}; + + // Seek the file + if (p->file_.seek(pos)) + { + // Record updated position + newPos = pos; + + p->ResetPutback(); + } + + return newPos; +} + +QFileBuffer::int_type QFileBuffer::underflow() +{ + // This function is only called if gptr() == nullptr or gptr() > egptr() + // (i.e., all buffer data has been read) + int_type c; + + if (gptr() && gptr() < egptr()) + { + // Return buffered + return traits_type::to_int_type(*gptr()); + } + else if (traits_type::eq_int_type(traits_type::eof(), c = uflow())) + { + // uflow failed, return EOF + return c; + } + else + { + // Get a character, don't point past it + pbackfail(c); + return c; + } +} + +QFileBuffer::int_type QFileBuffer::uflow() +{ + if (gptr() && gptr() < egptr()) + { + // Return buffered + int_type c = traits_type::to_int_type(*gptr()); + gbump(sizeof(char_type)); + return c; + } + + if (!p->file_.isOpen()) + { + // No open QFile, fail + return traits_type::eof(); + } + + p->ResetPutback(); + + // Read the next character + char_type c; + return p->file_.read(reinterpret_cast(&c), sizeof(char_type)) > 0 ? + traits_type::to_int_type(c) : + traits_type::eof(); +} + +std::streamsize QFileBuffer::xsgetn(char_type* s, std::streamsize count) +{ + // Read up to count bytes, forwarding the return value from QFile::read + // (return negative values as zero) + return std::max( + p->file_.read(reinterpret_cast(s), count * sizeof(char_type)), 0); +} + +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/q_file_buffer.hpp b/scwx-qt/source/scwx/qt/util/q_file_buffer.hpp new file mode 100644 index 00000000..cb63ecd2 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/q_file_buffer.hpp @@ -0,0 +1,191 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +/** + * The QFileBuffer class is a std::streambuf interface to a QFile, allowing use + * with C++ stream-based I/O. + * + * QFileBuffer has read-only support. Locales are ignored, and no conversions + * are performed. + * + * Documentation for functions derived from + * https://en.cppreference.com/ SPDX-License-Identifier: CC BY-SA 3.0 + */ +class QFileBuffer : public std::streambuf +{ +public: + /** + * Constructs a new QFileBuffer object. The created object is not associated + * with a file, and is_open() returns false. + */ + explicit QFileBuffer(); + + /** + * Constructs a new QFileBuffer object, then associated the object with a + * file by calling open(filename, mode). If the open call is successful, + * is_open() returns true. + */ + explicit QFileBuffer(const std::string& filename, + std::ios_base::openmode mode = std::ios_base::in); + ~QFileBuffer(); + + QFileBuffer(const QFileBuffer&) = delete; + QFileBuffer& operator=(const QFileBuffer&) = delete; + + QFileBuffer(QFileBuffer&&) noexcept; + QFileBuffer& operator=(QFileBuffer&&) noexcept; + + /** + * @brief Checks if the associated file is open + * + * Returns true if the most recent call to open() succeeded and there has + * been no call to close() since then. + * + * @return true if the associated file is open, false otherwise + */ + bool is_open() const; + + /** + * @brief Opens a file and configures it as the associated character sequence + * + * Opens the file with the given name. If the associated file was already + * open, returns a null pointer right away. + * + * @param filename The file name to open + * @param openmode The file opening mode, a binary OR of the std::ios_base + * modes + * + * @return this on success, a null pointer on failure + */ + QFileBuffer* open(const std::string& filename, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * @brief Flushes the put area buffer and closes the associated file + * + * Closes the file, regardless of whether any of the preceding calls + * succeeded or failed. + * + * If any of the function calls made fails, returns a null pointer. If any of + * the function calls made throws an exception, the exception is caught and + * rethrown after closing. If the file is already closed, returns a null + * pointer right away. + * + * @return this on success, a null pointer on failure + */ + QFileBuffer* close(); + +protected: + /** + * @brief Backs out the input sequence to unget a character, not affecting + * the associated file + * + * @param c The character to put back, or Traits::eof() to indicate that + * backing up of the get area is requested + * + * @return c on success except if c was Traits::eof(), in which case + * Traits::not_eof(c) is returned. Traits::eof() on failure. + */ + virtual int_type pbackfail(int_type c = traits_type::eof()) override; + + /** + * @brief Repositions the file position, using relative addressing + * + * Sets the position indicator of the input and/or output sequence relative + * to some other position. + * + * @param off Relative position to set the position indicator to + * @param dir Defines base position to apply the relative offset to + * @param which Defines which of the input and/or output sequences to affect + * + * @return The resulting absolute position as defined by the position + * indicator. + */ + virtual pos_type + seekoff(off_type off, + std::ios_base::seekdir dir, + std::ios_base::openmode which = std::ios_base::in | + std::ios_base::out) override; + + /** + * @brief Repositions the file position, using absolute addressing + * + * Sets the position indicator of the input and/or output sequence to an + * absolute position. + * + * @param pos Absolute position to set the position indicator to + * @param which Defines which of the input and/or output sequences to affect + * + * @return The resulting absolute position as defined by the position + * indicator. + */ + virtual pos_type + seekpos(pos_type pos, + std::ios_base::openmode which = std::ios_base::in | + std::ios_base::out) override; + + /** + * @brief Reads from the associated file + * + * Ensures that at least one character is available in the input area by + * updating the pointers to the input area (if needed) and reading more data + * in from the input sequence (if applicable). Returns the value of that + * character (converted to int_type with Traits::to_int_type(c)) on success + * or Traits::eof() on failure. + * + * The function may update gptr, egptr and eback pointers to define the + * location of newly loaded data (if any). On failure, the function ensures + * that either gptr() == nullptr or gptr() == egptr. + * + * @return The value of the character pointed to by the get pointer after the + * call on success, or Traits::eof() otherwise. + */ + virtual int_type underflow() override; + + /** + * @brief Reads from the associated file and advances the next pointer in the + * get area + * + * Behaves like the underflow(), except that if underflow() succeeds (does + * not return Traits::eof()), then advances the next pointer for the get + * area. In orther words, consumes one of the characters obtained by + * underflow(). + * + * @return The value of the character that was read and consumed in case of + * success, or Traits::eof() in case of failure. + */ + virtual int_type uflow() override; + + /** + * @brief Reads multiple characters from the input sequence + * + * Reads count characters from the input sequence and stores them into a + * character array pointed to by s. The characters are read as if by repeated + * calls to sbumpc(). That is, if less than count characters are immediately + * available, the function calls uflow() to provide more until Traits::eof() + * is returned. + * + * @param s Pointer to the beginning of a char_type array + * @param count Maximum number of characters to read + * + * @return The number of characters successfully read. If it is less than + * count the input sequence has reached the end. + */ + virtual std::streamsize xsgetn(char_type* s, std::streamsize count) override; + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace util +} // namespace qt +} // namespace scwx