From b11e142e1164ba9ec01bef5c2dae9317db598033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B5=B7?= Date: Mon, 2 Mar 2026 18:56:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=8D=A2csv=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E4=B8=BAcsv.hpp=E4=B8=BAcsv.h=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdlib/csv/csv.h | 1199 ++++++++++++++++++++++++++ src/DataProcessWorkPool.cpp | 200 +++-- src/DataProcessWorkPool.h | 2 +- src/MainWindow.cpp | 63 +- src/MainWindow.h | 1 + src/MeasureAnalysisDataTableView.cpp | 2 + src/MeasureAnalysisTree.cpp | 17 +- src/MeasureAnalysisTree.h | 2 +- src/main.cpp | 2 +- src/src.pro | 2 +- 10 files changed, 1403 insertions(+), 87 deletions(-) create mode 100644 3rdlib/csv/csv.h diff --git a/3rdlib/csv/csv.h b/3rdlib/csv/csv.h new file mode 100644 index 0000000..9a1919a --- /dev/null +++ b/3rdlib/csv/csv.h @@ -0,0 +1,1199 @@ +// Copyright: (2012-2015) Ben Strasser +// License: BSD-3 +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef CSV_H +#define CSV_H + +#include +#include +#include +#include +#include +#include +#include +#ifndef CSV_IO_NO_THREAD +#include +#include +#include +#endif +#include +#include +#include +#include +#include + +namespace io { +//////////////////////////////////////////////////////////////////////////// +// LineReader // +//////////////////////////////////////////////////////////////////////////// + +namespace error { +struct base : std::exception { + virtual void format_error_message() const = 0; + + const char *what() const noexcept override { + format_error_message(); + return error_message_buffer; + } + + mutable char error_message_buffer[2048]; +}; + +// this only affects the file name in the error message +const int max_file_name_length = 1024; + +struct with_file_name { + with_file_name() { std::memset(file_name, 0, sizeof(file_name)); } + + void set_file_name(const char *file_name) { + if (file_name != nullptr) { + // This call to strncpy has parenthesis around it + // to silence the GCC -Wstringop-truncation warning + (strncpy(this->file_name, file_name, sizeof(this->file_name))); + this->file_name[sizeof(this->file_name) - 1] = '\0'; + } else { + this->file_name[0] = '\0'; + } + } + + char file_name[max_file_name_length + 1]; +}; + +struct with_file_line { + with_file_line() { file_line = -1; } + + void set_file_line(int file_line) { this->file_line = file_line; } + + int file_line; +}; + +struct with_errno { + with_errno() { errno_value = 0; } + + void set_errno(int errno_value) { this->errno_value = errno_value; } + + int errno_value; +}; + +struct can_not_open_file : base, with_file_name, with_errno { + void format_error_message() const override { + if (errno_value != 0) + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + "Can not open file \"%s\" because \"%s\".", file_name, + std::strerror(errno_value)); + else + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + "Can not open file \"%s\".", file_name); + } +}; + +struct line_length_limit_exceeded : base, with_file_name, with_file_line { + void format_error_message() const override { + std::snprintf( + error_message_buffer, sizeof(error_message_buffer), + "Line number %d in file \"%s\" exceeds the maximum length of 2^24-1.", + file_line, file_name); + } +}; +} // namespace error + +class ByteSourceBase { +public: + virtual int read(char *buffer, int size) = 0; + virtual ~ByteSourceBase() {} +}; + +namespace detail { + +class OwningStdIOByteSourceBase : public ByteSourceBase { +public: + explicit OwningStdIOByteSourceBase(FILE *file) : file(file) { + // Tell the std library that we want to do the buffering ourself. + std::setvbuf(file, 0, _IONBF, 0); + } + + int read(char *buffer, int size) { return std::fread(buffer, 1, size, file); } + + ~OwningStdIOByteSourceBase() { std::fclose(file); } + +private: + FILE *file; +}; + +class NonOwningIStreamByteSource : public ByteSourceBase { +public: + explicit NonOwningIStreamByteSource(std::istream &in) : in(in) {} + + int read(char *buffer, int size) { + in.read(buffer, size); + return in.gcount(); + } + + ~NonOwningIStreamByteSource() {} + +private: + std::istream ∈ +}; + +class NonOwningStringByteSource : public ByteSourceBase { +public: + NonOwningStringByteSource(const char *str, long long size) + : str(str), remaining_byte_count(size) {} + + int read(char *buffer, int desired_byte_count) { + int to_copy_byte_count = desired_byte_count; + if (remaining_byte_count < to_copy_byte_count) + to_copy_byte_count = remaining_byte_count; + std::memcpy(buffer, str, to_copy_byte_count); + remaining_byte_count -= to_copy_byte_count; + str += to_copy_byte_count; + return to_copy_byte_count; + } + + ~NonOwningStringByteSource() {} + +private: + const char *str; + long long remaining_byte_count; +}; + +#ifndef CSV_IO_NO_THREAD +class AsynchronousReader { +public: + void init(std::unique_ptr arg_byte_source) { + std::unique_lock guard(lock); + byte_source = std::move(arg_byte_source); + desired_byte_count = -1; + termination_requested = false; + worker = std::thread([&] { + std::unique_lock guard(lock); + try { + for (;;) { + read_requested_condition.wait(guard, [&] { + return desired_byte_count != -1 || termination_requested; + }); + if (termination_requested) + return; + + read_byte_count = byte_source->read(buffer, desired_byte_count); + desired_byte_count = -1; + if (read_byte_count == 0) + break; + read_finished_condition.notify_one(); + } + } catch (...) { + read_error = std::current_exception(); + } + read_finished_condition.notify_one(); + }); + } + + bool is_valid() const { return byte_source != nullptr; } + + void start_read(char *arg_buffer, int arg_desired_byte_count) { + std::unique_lock guard(lock); + buffer = arg_buffer; + desired_byte_count = arg_desired_byte_count; + read_byte_count = -1; + read_requested_condition.notify_one(); + } + + int finish_read() { + std::unique_lock guard(lock); + read_finished_condition.wait( + guard, [&] { return read_byte_count != -1 || read_error; }); + if (read_error) + std::rethrow_exception(read_error); + else + return read_byte_count; + } + + ~AsynchronousReader() { + if (byte_source != nullptr) { + { + std::unique_lock guard(lock); + termination_requested = true; + } + read_requested_condition.notify_one(); + worker.join(); + } + } + +private: + std::unique_ptr byte_source; + + std::thread worker; + + bool termination_requested; + std::exception_ptr read_error; + char *buffer; + int desired_byte_count; + int read_byte_count; + + std::mutex lock; + std::condition_variable read_finished_condition; + std::condition_variable read_requested_condition; +}; +#endif + +class SynchronousReader { +public: + void init(std::unique_ptr arg_byte_source) { + byte_source = std::move(arg_byte_source); + } + + bool is_valid() const { return byte_source != nullptr; } + + void start_read(char *arg_buffer, int arg_desired_byte_count) { + buffer = arg_buffer; + desired_byte_count = arg_desired_byte_count; + } + + int finish_read() { return byte_source->read(buffer, desired_byte_count); } + +private: + std::unique_ptr byte_source; + char *buffer; + int desired_byte_count; +}; +} // namespace detail + +class LineReader { +private: + static const int block_len = 1 << 20; + std::unique_ptr buffer; // must be constructed before (and thus + // destructed after) the reader! +#ifdef CSV_IO_NO_THREAD + detail::SynchronousReader reader; +#else + detail::AsynchronousReader reader; +#endif + int data_begin; + int data_end; + + char file_name[error::max_file_name_length + 1]; + unsigned file_line; + + static std::unique_ptr open_file(const char *file_name) { + // We open the file in binary mode as it makes no difference under *nix + // and under Windows we handle \r\n newlines ourself. + FILE *file = std::fopen(file_name, "rb"); + if (file == 0) { + int x = errno; // store errno as soon as possible, doing it after + // constructor call can fail. + error::can_not_open_file err; + err.set_errno(x); + err.set_file_name(file_name); + throw err; + } + return std::unique_ptr( + new detail::OwningStdIOByteSourceBase(file)); + } + + void init(std::unique_ptr byte_source) { + file_line = 0; + + buffer = std::unique_ptr(new char[3 * block_len]); + data_begin = 0; + data_end = byte_source->read(buffer.get(), 2 * block_len); + + // Ignore UTF-8 BOM + if (data_end >= 3 && buffer[0] == '\xEF' && buffer[1] == '\xBB' && + buffer[2] == '\xBF') + data_begin = 3; + + if (data_end == 2 * block_len) { + reader.init(std::move(byte_source)); + reader.start_read(buffer.get() + 2 * block_len, block_len); + } + } + +public: + LineReader() = delete; + LineReader(const LineReader &) = delete; + LineReader &operator=(const LineReader &) = delete; + + explicit LineReader(const char *file_name) { + set_file_name(file_name); + init(open_file(file_name)); + } + + explicit LineReader(const std::string &file_name) { + set_file_name(file_name.c_str()); + init(open_file(file_name.c_str())); + } + + LineReader(const char *file_name, + std::unique_ptr byte_source) { + set_file_name(file_name); + init(std::move(byte_source)); + } + + LineReader(const std::string &file_name, + std::unique_ptr byte_source) { + set_file_name(file_name.c_str()); + init(std::move(byte_source)); + } + + LineReader(const char *file_name, const char *data_begin, + const char *data_end) { + set_file_name(file_name); + init(std::unique_ptr(new detail::NonOwningStringByteSource( + data_begin, data_end - data_begin))); + } + + LineReader(const std::string &file_name, const char *data_begin, + const char *data_end) { + set_file_name(file_name.c_str()); + init(std::unique_ptr(new detail::NonOwningStringByteSource( + data_begin, data_end - data_begin))); + } + + LineReader(const char *file_name, FILE *file) { + set_file_name(file_name); + init(std::unique_ptr( + new detail::OwningStdIOByteSourceBase(file))); + } + + LineReader(const std::string &file_name, FILE *file) { + set_file_name(file_name.c_str()); + init(std::unique_ptr( + new detail::OwningStdIOByteSourceBase(file))); + } + + LineReader(const char *file_name, std::istream &in) { + set_file_name(file_name); + init(std::unique_ptr( + new detail::NonOwningIStreamByteSource(in))); + } + + LineReader(const std::string &file_name, std::istream &in) { + set_file_name(file_name.c_str()); + init(std::unique_ptr( + new detail::NonOwningIStreamByteSource(in))); + } + + void set_file_name(const std::string &file_name) { + set_file_name(file_name.c_str()); + } + + void set_file_name(const char *file_name) { + if (file_name != nullptr) { + strncpy(this->file_name, file_name, sizeof(this->file_name) - 1); + this->file_name[sizeof(this->file_name) - 1] = '\0'; + } else { + this->file_name[0] = '\0'; + } + } + + const char *get_truncated_file_name() const { return file_name; } + + void set_file_line(unsigned file_line) { this->file_line = file_line; } + + unsigned get_file_line() const { return file_line; } + + char *next_line() { + if (data_begin == data_end) + return nullptr; + + ++file_line; + + assert(data_begin < data_end); + assert(data_end <= block_len * 2); + + if (data_begin >= block_len) { + std::memcpy(buffer.get(), buffer.get() + block_len, block_len); + data_begin -= block_len; + data_end -= block_len; + if (reader.is_valid()) { + data_end += reader.finish_read(); + std::memcpy(buffer.get() + block_len, buffer.get() + 2 * block_len, + block_len); + reader.start_read(buffer.get() + 2 * block_len, block_len); + } + } + + int line_end = data_begin; + while (line_end != data_end && buffer[line_end] != '\n') { + ++line_end; + } + + if (line_end - data_begin + 1 > block_len) { + error::line_length_limit_exceeded err; + err.set_file_name(file_name); + err.set_file_line(file_line); + throw err; + } + + if (line_end != data_end && buffer[line_end] == '\n') { + buffer[line_end] = '\0'; + } else { + // some files are missing the newline at the end of the + // last line + ++data_end; + buffer[line_end] = '\0'; + } + + // handle windows \r\n-line breaks + if (line_end != data_begin && buffer[line_end - 1] == '\r') + buffer[line_end - 1] = '\0'; + + char *ret = buffer.get() + data_begin; + data_begin = line_end + 1; + return ret; + } +}; + +//////////////////////////////////////////////////////////////////////////// +// CSV // +//////////////////////////////////////////////////////////////////////////// + +namespace error { +const int max_column_name_length = 63; +struct with_column_name { + with_column_name() { + std::memset(column_name, 0, max_column_name_length + 1); + } + + void set_column_name(const char *column_name) { + if (column_name != nullptr) { + std::strncpy(this->column_name, column_name, max_column_name_length); + this->column_name[max_column_name_length] = '\0'; + } else { + this->column_name[0] = '\0'; + } + } + + char column_name[max_column_name_length + 1]; +}; + +const int max_column_content_length = 63; + +struct with_column_content { + with_column_content() { + std::memset(column_content, 0, max_column_content_length + 1); + } + + void set_column_content(const char *column_content) { + if (column_content != nullptr) { + std::strncpy(this->column_content, column_content, + max_column_content_length); + this->column_content[max_column_content_length] = '\0'; + } else { + this->column_content[0] = '\0'; + } + } + + char column_content[max_column_content_length + 1]; +}; + +struct extra_column_in_header : base, with_file_name, with_column_name { + void format_error_message() const override { + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + R"(Extra column "%s" in header of file "%s".)", column_name, + file_name); + } +}; + +struct missing_column_in_header : base, with_file_name, with_column_name { + void format_error_message() const override { + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + R"(Missing column "%s" in header of file "%s".)", column_name, + file_name); + } +}; + +struct duplicated_column_in_header : base, with_file_name, with_column_name { + void format_error_message() const override { + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + R"(Duplicated column "%s" in header of file "%s".)", + column_name, file_name); + } +}; + +struct header_missing : base, with_file_name { + void format_error_message() const override { + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + "Header missing in file \"%s\".", file_name); + } +}; + +struct too_few_columns : base, with_file_name, with_file_line { + void format_error_message() const override { + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + "Too few columns in line %d in file \"%s\".", file_line, + file_name); + } +}; + +struct too_many_columns : base, with_file_name, with_file_line { + void format_error_message() const override { + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + "Too many columns in line %d in file \"%s\".", file_line, + file_name); + } +}; + +struct escaped_string_not_closed : base, with_file_name, with_file_line { + void format_error_message() const override { + std::snprintf(error_message_buffer, sizeof(error_message_buffer), + "Escaped string was not closed in line %d in file \"%s\".", + file_line, file_name); + } +}; + +struct integer_must_be_positive : base, + with_file_name, + with_file_line, + with_column_name, + with_column_content { + void format_error_message() const override { + std::snprintf( + error_message_buffer, sizeof(error_message_buffer), + R"(The integer "%s" must be positive or 0 in column "%s" in file "%s" in line "%d".)", + column_content, column_name, file_name, file_line); + } +}; + +struct no_digit : base, + with_file_name, + with_file_line, + with_column_name, + with_column_content { + void format_error_message() const override { + std::snprintf( + error_message_buffer, sizeof(error_message_buffer), + R"(The integer "%s" contains an invalid digit in column "%s" in file "%s" in line "%d".)", + column_content, column_name, file_name, file_line); + } +}; + +struct integer_overflow : base, + with_file_name, + with_file_line, + with_column_name, + with_column_content { + void format_error_message() const override { + std::snprintf( + error_message_buffer, sizeof(error_message_buffer), + R"(The integer "%s" overflows in column "%s" in file "%s" in line "%d".)", + column_content, column_name, file_name, file_line); + } +}; + +struct integer_underflow : base, + with_file_name, + with_file_line, + with_column_name, + with_column_content { + void format_error_message() const override { + std::snprintf( + error_message_buffer, sizeof(error_message_buffer), + R"(The integer "%s" underflows in column "%s" in file "%s" in line "%d".)", + column_content, column_name, file_name, file_line); + } +}; + +struct invalid_single_character : base, + with_file_name, + with_file_line, + with_column_name, + with_column_content { + void format_error_message() const override { + std::snprintf( + error_message_buffer, sizeof(error_message_buffer), + R"(The content "%s" of column "%s" in file "%s" in line "%d" is not a single character.)", + column_content, column_name, file_name, file_line); + } +}; +} // namespace error + +using ignore_column = unsigned int; +static const ignore_column ignore_no_column = 0; +static const ignore_column ignore_extra_column = 1; +static const ignore_column ignore_missing_column = 2; + +template struct trim_chars { +private: + constexpr static bool is_trim_char(char) { return false; } + + template + constexpr static bool is_trim_char(char c, char trim_char, + OtherTrimChars... other_trim_chars) { + return c == trim_char || is_trim_char(c, other_trim_chars...); + } + +public: + static void trim(char *&str_begin, char *&str_end) { + while (str_begin != str_end && is_trim_char(*str_begin, trim_char_list...)) + ++str_begin; + while (str_begin != str_end && + is_trim_char(*(str_end - 1), trim_char_list...)) + --str_end; + *str_end = '\0'; + } +}; + +struct no_comment { + static bool is_comment(const char *) { return false; } +}; + +template struct single_line_comment { +private: + constexpr static bool is_comment_start_char(char) { return false; } + + template + constexpr static bool + is_comment_start_char(char c, char comment_start_char, + OtherCommentStartChars... other_comment_start_chars) { + return c == comment_start_char || + is_comment_start_char(c, other_comment_start_chars...); + } + +public: + static bool is_comment(const char *line) { + return is_comment_start_char(*line, comment_start_char_list...); + } +}; + +struct empty_line_comment { + static bool is_comment(const char *line) { + if (*line == '\0') + return true; + while (*line == ' ' || *line == '\t') { + ++line; + if (*line == 0) + return true; + } + return false; + } +}; + +template +struct single_and_empty_line_comment { + static bool is_comment(const char *line) { + return single_line_comment::is_comment(line) || + empty_line_comment::is_comment(line); + } +}; + +template struct no_quote_escape { + static const char *find_next_column_end(const char *col_begin) { + while (*col_begin != sep && *col_begin != '\0') + ++col_begin; + return col_begin; + } + + static void unescape(char *&, char *&) {} +}; + +template struct double_quote_escape { + static const char *find_next_column_end(const char *col_begin) { + while (*col_begin != sep && *col_begin != '\0') + if (*col_begin != quote) + ++col_begin; + else { + do { + ++col_begin; + while (*col_begin != quote) { + if (*col_begin == '\0') + throw error::escaped_string_not_closed(); + ++col_begin; + } + ++col_begin; + } while (*col_begin == quote); + } + return col_begin; + } + + static void unescape(char *&col_begin, char *&col_end) { + if (col_end - col_begin >= 2) { + if (*col_begin == quote && *(col_end - 1) == quote) { + ++col_begin; + --col_end; + char *out = col_begin; + for (char *in = col_begin; in != col_end; ++in) { + if (*in == quote && (in + 1) != col_end && *(in + 1) == quote) { + ++in; + } + *out = *in; + ++out; + } + col_end = out; + *col_end = '\0'; + } + } + } +}; + +struct throw_on_overflow { + template static void on_overflow(T &) { + throw error::integer_overflow(); + } + + template static void on_underflow(T &) { + throw error::integer_underflow(); + } +}; + +struct ignore_overflow { + template static void on_overflow(T &) {} + + template static void on_underflow(T &) {} +}; + +struct set_to_max_on_overflow { + template static void on_overflow(T &x) { + // using (std::numeric_limits::max) instead of + // std::numeric_limits::max to make code including windows.h with its max + // macro happy + x = (std::numeric_limits::max)(); + } + + template static void on_underflow(T &x) { + x = (std::numeric_limits::min)(); + } +}; + +namespace detail { +template +void chop_next_column(char *&line, char *&col_begin, char *&col_end) { + assert(line != nullptr); + + col_begin = line; + // the col_begin + (... - col_begin) removes the constness + col_end = + col_begin + (quote_policy::find_next_column_end(col_begin) - col_begin); + + if (*col_end == '\0') { + line = nullptr; + } else { + *col_end = '\0'; + line = col_end + 1; + } +} + +template +void parse_line(char *line, char **sorted_col, + const std::vector &col_order) { + for (int i : col_order) { + if (line == nullptr) + throw ::io::error::too_few_columns(); + char *col_begin, *col_end; + chop_next_column(line, col_begin, col_end); + + if (i != -1) { + trim_policy::trim(col_begin, col_end); + quote_policy::unescape(col_begin, col_end); + + sorted_col[i] = col_begin; + } + } + if (line != nullptr) + throw ::io::error::too_many_columns(); +} + +template +void parse_header_line(char *line, std::vector &col_order, + const std::string *col_name, + ignore_column ignore_policy) { + col_order.clear(); + + bool found[column_count]; + std::fill(found, found + column_count, false); + while (line) { + char *col_begin, *col_end; + chop_next_column(line, col_begin, col_end); + + trim_policy::trim(col_begin, col_end); + quote_policy::unescape(col_begin, col_end); + + for (unsigned i = 0; i < column_count; ++i) + if (col_begin == col_name[i]) { + if (found[i]) { + error::duplicated_column_in_header err; + err.set_column_name(col_begin); + throw err; + } + found[i] = true; + col_order.push_back(i); + col_begin = 0; + break; + } + if (col_begin) { + if (ignore_policy & ::io::ignore_extra_column) + col_order.push_back(-1); + else { + error::extra_column_in_header err; + err.set_column_name(col_begin); + throw err; + } + } + } + if (!(ignore_policy & ::io::ignore_missing_column)) { + for (unsigned i = 0; i < column_count; ++i) { + if (!found[i]) { + error::missing_column_in_header err; + err.set_column_name(col_name[i].c_str()); + throw err; + } + } + } +} + +template void parse(char *col, char &x) { + if (!*col) + throw error::invalid_single_character(); + x = *col; + ++col; + if (*col) + throw error::invalid_single_character(); +} + +template void parse(char *col, std::string &x) { + x = col; +} + +template void parse(char *col, const char *&x) { + x = col; +} + +template void parse(char *col, char *&x) { x = col; } + +template +void parse_unsigned_integer(const char *col, T &x) { + x = 0; + while (*col != '\0') { + if ('0' <= *col && *col <= '9') { + T y = *col - '0'; + if (x > ((std::numeric_limits::max)() - y) / 10) { + overflow_policy::on_overflow(x); + return; + } + x = 10 * x + y; + } else + throw error::no_digit(); + ++col; + } +} + +template void parse(char *col, unsigned char &x) { + parse_unsigned_integer(col, x); +} +template void parse(char *col, unsigned short &x) { + parse_unsigned_integer(col, x); +} +template void parse(char *col, unsigned int &x) { + parse_unsigned_integer(col, x); +} +template void parse(char *col, unsigned long &x) { + parse_unsigned_integer(col, x); +} +template void parse(char *col, unsigned long long &x) { + parse_unsigned_integer(col, x); +} + +template +void parse_signed_integer(const char *col, T &x) { + if (*col == '-') { + ++col; + + x = 0; + while (*col != '\0') { + if ('0' <= *col && *col <= '9') { + T y = *col - '0'; + if (x < ((std::numeric_limits::min)() + y) / 10) { + overflow_policy::on_underflow(x); + return; + } + x = 10 * x - y; + } else + throw error::no_digit(); + ++col; + } + return; + } else if (*col == '+') + ++col; + parse_unsigned_integer(col, x); +} + +template void parse(char *col, signed char &x) { + parse_signed_integer(col, x); +} +template void parse(char *col, signed short &x) { + parse_signed_integer(col, x); +} +template void parse(char *col, signed int &x) { + parse_signed_integer(col, x); +} +template void parse(char *col, signed long &x) { + parse_signed_integer(col, x); +} +template void parse(char *col, signed long long &x) { + parse_signed_integer(col, x); +} + +template void parse_float(const char *col, T &x) { + bool is_neg = false; + if (*col == '-') { + is_neg = true; + ++col; + } else if (*col == '+') + ++col; + + x = 0; + while ('0' <= *col && *col <= '9') { + int y = *col - '0'; + x *= 10; + x += y; + ++col; + } + + if (*col == '.' || *col == ',') { + ++col; + T pos = 1; + while ('0' <= *col && *col <= '9') { + pos /= 10; + int y = *col - '0'; + ++col; + x += y * pos; + } + } + + if (*col == 'e' || *col == 'E') { + ++col; + int e; + + parse_signed_integer(col, e); + + if (e != 0) { + T base; + if (e < 0) { + base = T(0.1); + e = -e; + } else { + base = T(10); + } + + while (e != 1) { + if ((e & 1) == 0) { + base = base * base; + e >>= 1; + } else { + x *= base; + --e; + } + } + x *= base; + } + } else { + if (*col != '\0') + throw error::no_digit(); + } + + if (is_neg) + x = -x; +} + +template void parse(char *col, float &x) { + parse_float(col, x); +} +template void parse(char *col, double &x) { + parse_float(col, x); +} +template void parse(char *col, long double &x) { + parse_float(col, x); +} + +template void parse(char *col, T &x) { + // Mute unused variable compiler warning + (void)col; + (void)x; + // GCC evaluates "false" when reading the template and + // "sizeof(T)!=sizeof(T)" only when instantiating it. This is why + // this strange construct is used. + static_assert(sizeof(T) != sizeof(T), + "Can not parse this type. Only builtin integrals, floats, " + "char, char*, const char* and std::string are supported"); +} + +} // namespace detail + +template , + class quote_policy = no_quote_escape<','>, + class overflow_policy = throw_on_overflow, + class comment_policy = no_comment> +class CSVReader { +private: + LineReader in; + + char *row[column_count]; + std::string column_names[column_count]; + + std::vector col_order; + + template + void set_column_names(std::string s, ColNames... cols) { + column_names[column_count - sizeof...(ColNames) - 1] = std::move(s); + set_column_names(std::forward(cols)...); + } + + void set_column_names() {} + +public: + CSVReader() = delete; + CSVReader(const CSVReader &) = delete; + CSVReader &operator=(const CSVReader &); + + template + explicit CSVReader(Args &&... args) : in(std::forward(args)...) { + std::fill(row, row + column_count, nullptr); + col_order.resize(column_count); + for (unsigned i = 0; i < column_count; ++i) + col_order[i] = i; + for (unsigned i = 1; i <= column_count; ++i) + column_names[i - 1] = "col" + std::to_string(i); + } + + char *next_line() { return in.next_line(); } + + template + void read_header(ignore_column ignore_policy, ColNames... cols) { + static_assert(sizeof...(ColNames) >= column_count, + "not enough column names specified"); + static_assert(sizeof...(ColNames) <= column_count, + "too many column names specified"); + try { + set_column_names(std::forward(cols)...); + + char *line; + do { + line = in.next_line(); + if (!line) + throw error::header_missing(); + } while (comment_policy::is_comment(line)); + + detail::parse_header_line( + line, col_order, column_names, ignore_policy); + } catch (error::with_file_name &err) { + err.set_file_name(in.get_truncated_file_name()); + throw; + } + } + + template void set_header(ColNames... cols) { + static_assert(sizeof...(ColNames) >= column_count, + "not enough column names specified"); + static_assert(sizeof...(ColNames) <= column_count, + "too many column names specified"); + set_column_names(std::forward(cols)...); + std::fill(row, row + column_count, nullptr); + col_order.resize(column_count); + for (unsigned i = 0; i < column_count; ++i) + col_order[i] = i; + } + + bool has_column(const std::string &name) const { + return col_order.end() != + std::find(col_order.begin(), col_order.end(), + std::find(std::begin(column_names), std::end(column_names), + name) - + std::begin(column_names)); + } + + void set_file_name(const std::string &file_name) { + in.set_file_name(file_name); + } + + void set_file_name(const char *file_name) { in.set_file_name(file_name); } + + const char *get_truncated_file_name() const { + return in.get_truncated_file_name(); + } + + void set_file_line(unsigned file_line) { in.set_file_line(file_line); } + + unsigned get_file_line() const { return in.get_file_line(); } + +private: + void parse_helper(std::size_t) {} + + template + void parse_helper(std::size_t r, T &t, ColType &... cols) { + if (row[r]) { + try { + try { + ::io::detail::parse(row[r], t); + } catch (error::with_column_content &err) { + err.set_column_content(row[r]); + throw; + } + } catch (error::with_column_name &err) { + err.set_column_name(column_names[r].c_str()); + throw; + } + } + parse_helper(r + 1, cols...); + } + +public: + template bool read_row(ColType &... cols) { + static_assert(sizeof...(ColType) >= column_count, + "not enough columns specified"); + static_assert(sizeof...(ColType) <= column_count, + "too many columns specified"); + try { + try { + + char *line; + do { + line = in.next_line(); + if (!line) + return false; + } while (comment_policy::is_comment(line)); + + detail::parse_line(line, row, col_order); + + parse_helper(0, cols...); + } catch (error::with_file_name &err) { + err.set_file_name(in.get_truncated_file_name()); + throw; + } + } catch (error::with_file_line &err) { + err.set_file_line(in.get_file_line()); + throw; + } + + return true; + } +}; +} // namespace io +#endif diff --git a/src/DataProcessWorkPool.cpp b/src/DataProcessWorkPool.cpp index 44c3b97..82c633e 100644 --- a/src/DataProcessWorkPool.cpp +++ b/src/DataProcessWorkPool.cpp @@ -1,11 +1,13 @@ #include "DataProcessWorkPool.h" #include #include -#include "csv.hpp" +#include +#include "csv.h" #include "MeasureAnalysisProjectModel.h" #include "OutputInfoDefine.h" using namespace DataProcessWorkPool; +using namespace io; void EveryChannelParticleDataTask::SetAllChannelParticleDataFilename(const QString& all_channel_particle_data_filename) { @@ -66,6 +68,21 @@ void EveryChannelParticleDataTask::run() QMetaObject::invokeMethod(_finished_notifier, _finished_notifier_process, Qt::QueuedConnection, Q_ARG(QString, _project_name)); } +void EveryChannelParticleDataSeparateTask::SetResultDataDir(const QString& result_data_dir) +{ + this->_result_data_dir = result_data_dir; +} + +const QString& EveryChannelParticleDataSeparateTask::GetResultDataDir() const +{ + return this->_result_data_dir; +} + +bool EveryChannelParticleDataSeparateTask::IsValidSetWorkParameters() const +{ + return (!GetResultDataDir().isEmpty()) && EveryChannelParticleDataTask::IsValidSetWorkParameters(); +} + bool EveryChannelParticleDataSeparateTask::processEveryChannelParticleData() { bool ret_ok = true; @@ -79,18 +96,18 @@ bool EveryChannelParticleDataSeparateTask::processEveryChannelParticleData() try { QMap> ch_particle_data_of_list; - csv::CSVFormat format; - format.delimiter(',').quote('"').trim({' ', '\t'}).variable_columns(csv::VariableColumnPolicy::THROW); - csv::CSVReader reader(all_channel_particle_data_filename.toStdString()); - for (auto& row : reader) { - if (row.size() != 4) { - ret_ok = false; - break; - } - uint board_id = row[0].get(); - uint channel_id = row[1].get(); - uint address = row[2].get(); - unsigned long long time = row[3].get(); + std::string board_id_str = QString(QStringLiteral(u"板卡号")).toStdString(); + std::string channel_id_str = QString(QStringLiteral(u"通道号")).toStdString(); + std::string address_str = QString(QStringLiteral(u"道址")).toStdString(); + std::string time_str = QString(QStringLiteral(u"时间计数")).toStdString(); + + io::CSVReader<4> reader(all_channel_particle_data_filename.toStdString()); + reader.read_header(io::ignore_no_column, board_id_str, channel_id_str, address_str, time_str); + uint board_id; + uint channel_id; + uint address; + unsigned long long time; + while (reader.read_row(board_id, channel_id, address, time)) { // 板卡和通道号计算,通道号 = 板卡号 * 4 + 通道号 int channel_num = (board_id) * 4 + (channel_id + 1); @@ -104,12 +121,11 @@ bool EveryChannelParticleDataSeparateTask::processEveryChannelParticleData() new std::ofstream(particle_data_filename.toStdString(), std::ios::out | std::ios::app), [](std::ofstream* p){p->close();} ); - *out << QStringLiteral(u"板卡号,通道号,地址,时间计数") << std::endl; + *out << QString(QStringLiteral(u"板卡号,通道号,道址,时间计数")).toStdString() << std::endl; ch_particle_data_of_list.insert(channel_num, out); } auto ch_particle_data_of = ch_particle_data_of_list.value(channel_num); - auto writer = csv::make_csv_writer(*ch_particle_data_of); - writer << std::vector{std::to_string(board_id), std::to_string(channel_id), std::to_string(address), std::to_string(time)}; + *ch_particle_data_of << board_id << "," << channel_id << "," << address << "," << time << std::endl; } } catch (const std::runtime_error& e) { QString error = QString(QStringLiteral(u"处理%1发生运行时异常:%2")).arg(all_channel_particle_data_filename).arg(e.what()); @@ -137,50 +153,124 @@ bool EveryChannelParticleDataSeparateTask::processEveryChannelParticleData() return ret_ok; } +void EveryChannelParticleCountDataTask::SetAllChannelCountResultDir(const QString &dir_path) +{ + this->_all_ch_count_dir = dir_path; +} + +const QString &EveryChannelParticleCountDataTask::GetAllChannelCountResultDir() const +{ + return this->_all_ch_count_dir; +} + +void EveryChannelParticleCountDataTask::SetEveryChannelCountResultDir(const QString &dir_path) +{ + this->_every_ch_count_dir = dir_path; +} + +const QString &EveryChannelParticleCountDataTask::GetEveryChannelCountResultDir() const +{ + return this->_every_ch_count_dir; +} + +bool EveryChannelParticleCountDataTask::IsValidSetWorkParameters() const +{ + return (!GetAllChannelCountResultDir().isEmpty()) && + (!GetEveryChannelCountResultDir().isEmpty()) && + EveryChannelParticleDataTask::IsValidSetWorkParameters(); +} + bool EveryChannelParticleCountDataTask::processEveryChannelParticleData() { bool ret_ok = true; - const QString& result_data_output_dir_path = GetResultDataDir(); - QDir result_data_output_dir(result_data_output_dir_path); - result_data_output_dir.mkpath(result_data_output_dir_path); + const QString& all_ch_count_dir = GetAllChannelCountResultDir(); + const QString& every_ch_count_dir = GetEveryChannelCountResultDir(); + + QDir all_ch_count_output_dir(all_ch_count_dir); + all_ch_count_output_dir.mkpath(all_ch_count_dir); + + QDir every_ch_count_output_dir(every_ch_count_dir); + every_ch_count_output_dir.mkpath(every_ch_count_dir); const QString& all_channel_particle_data_filename = GetAllChannelParticleDataFilename(); - QMap particle_data_filename_list; + QMap particle_count_filename_list; + QString all_channel_total_count_filename; try { - QMap> ch_particle_data_of_list; - - csv::CSVFormat format; - format.delimiter(',').quote('"').trim({' ', '\t'}).variable_columns(csv::VariableColumnPolicy::THROW); - csv::CSVReader reader(all_channel_particle_data_filename.toStdString()); - for (auto& row : reader) { - if (row.size() != 4) { - ret_ok = false; - break; - } - uint board_id = row[0].get(); - uint channel_id = row[1].get(); - uint address = row[2].get(); - unsigned long long time = row[3].get(); - + // 统计每个通道的粒子计数(相同板卡号通道号相同道址) + QMap> channel_address_counts; // 通道号 -> 地址 -> 计数 + + // 统计所有通道的粒子计数(不同板卡号通道号相同道址) + QMap all_channel_address_counts; // 地址 -> 计数 + + std::string board_id_str = QString(QStringLiteral(u"板卡号")).toStdString(); + std::string channel_id_str = QString(QStringLiteral(u"通道号")).toStdString(); + std::string address_str = QString(QStringLiteral(u"道址")).toStdString(); + std::string time_str = QString(QStringLiteral(u"时间计数")).toStdString(); + + io::CSVReader<4> reader(all_channel_particle_data_filename.toStdString()); + reader.read_header(io::ignore_no_column, board_id_str, channel_id_str, address_str, time_str); + uint board_id; + uint channel_id; + uint address; + unsigned long long time; + while (reader.read_row(board_id, channel_id, address, time)) { // 板卡和通道号计算,通道号 = 板卡号 * 4 + 通道号 int channel_num = (board_id) * 4 + (channel_id + 1); - QString particle_data_filename = result_data_output_dir.filePath(QString("ParticleCountData_ch_%1.csv").arg(channel_num)); - if (!particle_data_filename_list.contains(channel_num)) { - particle_data_filename_list.insert(channel_num, particle_data_filename); + + // 统计每个通道的粒子计数 + if (!channel_address_counts.contains(channel_num)) { + channel_address_counts[channel_num] = QMap(); } - - if ( !ch_particle_data_of_list.contains(channel_num) ) { - std::shared_ptr out( - new std::ofstream(particle_data_filename.toStdString(), std::ios::out | std::ios::app), - [](std::ofstream* p){p->close();} - ); - ch_particle_data_of_list.insert(channel_num, out); - } - auto ch_particle_data_of = ch_particle_data_of_list.value(channel_num); - auto writer = csv::make_csv_writer(*ch_particle_data_of); - writer << std::vector{std::to_string(board_id), std::to_string(channel_id), std::to_string(address), std::to_string(time)}; + channel_address_counts[channel_num][address]++; + + // 统计所有通道的粒子计数 + all_channel_address_counts[address]++; } + + // 写入每个通道的粒子计数数据(优化:使用一次打开文件,批量写入) + QMap> channel_file_streams; + + // 预创建所有通道的文件流 + for (auto channel_it = channel_address_counts.begin(); channel_it != channel_address_counts.end(); ++channel_it) { + uint channel_num = channel_it.key(); + QString count_data_filename = every_ch_count_output_dir.filePath(QString("ParticleCountData_ch_%1.csv").arg(channel_num)); + particle_count_filename_list.insert(channel_num, count_data_filename); + + // 创建文件流 + std::shared_ptr out(new std::ofstream(count_data_filename.toStdString())); + channel_file_streams[channel_num] = out; + *out << QString(QStringLiteral(u"道址")).toStdString() << "," << QString(QStringLiteral(u"计数")).toStdString() << std::endl; + } + + // 批量写入数据 + for (auto channel_it = channel_address_counts.begin(); channel_it != channel_address_counts.end(); ++channel_it) { + uint channel_num = channel_it.key(); + const QMap& address_counts = channel_it.value(); + auto out_stream = channel_file_streams[channel_num]; + + for (auto address_it = address_counts.begin(); address_it != address_counts.end(); ++address_it) { + uint address = address_it.key(); + uint count = address_it.value(); + *out_stream << address << "," << count << std::endl; + } + } + + // 文件流会在shared_ptr析构时自动关闭 + channel_file_streams.clear(); + + // 写入所有通道的粒子计数数据 + all_channel_total_count_filename = all_ch_count_output_dir.filePath("AllChannelParticleTotalCountData.csv"); + std::ofstream all_channel_out(all_channel_total_count_filename.toStdString()); + all_channel_out << QString(QStringLiteral(u"道址")).toStdString() << "," << QString(QStringLiteral(u"计数")).toStdString() << std::endl; + + for (auto address_it = all_channel_address_counts.begin(); address_it != all_channel_address_counts.end(); ++address_it) { + uint address = address_it.key(); + uint count = address_it.value(); + all_channel_out << address << "," << count << std::endl; + } + all_channel_out.close(); + } catch (const std::runtime_error& e) { QString error = QString(QStringLiteral(u"处理%1发生运行时异常:%2")).arg(all_channel_particle_data_filename).arg(e.what()); LOG_ERROR(error) @@ -190,7 +280,7 @@ bool EveryChannelParticleCountDataTask::processEveryChannelParticleData() LOG_ERROR(error) ret_ok = false; } catch (...) { - QString error = QString(QStringLiteral(u"处理%1未知异常.")).arg(all_channel_particle_data_filename); + QString error = QString(QStringLiteral(u"处理%1未知异常.").arg(all_channel_particle_data_filename)); LOG_ERROR(error) ret_ok = false; } @@ -199,9 +289,13 @@ bool EveryChannelParticleCountDataTask::processEveryChannelParticleData() MeasureAnalysisProjectModel* project_model = MeasureAnalysisProjectModelList::GetProjectModel(project_name); if (project_model == nullptr) { ret_ok = false; - } - for (auto it = particle_data_filename_list.begin(); it != particle_data_filename_list.end(); ++it) { - project_model->SetChannelParticleDataFilename(it.key(), it.value()); + } else { + // 更新项目模型中的通道粒子计数数据文件名 + for (auto it = particle_count_filename_list.begin(); it != particle_count_filename_list.end(); ++it) { + project_model->SetChannelParticleCountDataFilename(it.key(), it.value()); + } + // 更新项目模型中的所有通道粒子总计数数据文件名 + project_model->SetAllChannelParticleTotalCountDataFilename(all_channel_total_count_filename); } return ret_ok; diff --git a/src/DataProcessWorkPool.h b/src/DataProcessWorkPool.h index 59c13d5..58a38e1 100644 --- a/src/DataProcessWorkPool.h +++ b/src/DataProcessWorkPool.h @@ -49,7 +49,7 @@ namespace DataProcessWorkPool class EveryChannelParticleCountDataTask : public EveryChannelParticleDataTask { public: - void SetAllChannelCountResultDir(const QString& filename); + void SetAllChannelCountResultDir(const QString& dir_path); const QString& GetAllChannelCountResultDir() const; void SetEveryChannelCountResultDir(const QString&dir_path); const QString& GetEveryChannelCountResultDir() const; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index f064f1b..efd54a4 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -87,16 +87,6 @@ MainWindow::MainWindow(QWidget* parent) MainWindow::~MainWindow() { - QList dock_widget_list = this->_dock_manager->dockWidgetsMap().values(); - for (auto it = dock_widget_list.constBegin(); it != dock_widget_list.constEnd(); ++it) { - CDockWidget* dock_widget = *it; - if (dock_widget) { - bool need_take_widget = dock_widget->property("TakeWidget").toBool(); - if (need_take_widget) { - dock_widget->takeWidget(); - } - } - } delete ui; } @@ -127,11 +117,11 @@ void MainWindow::initMainWindow() // 构建测量分析工程树视图 _tree_measure_analysis = new MeasureAnalysisTree::TreeWidget; - ads::CDockWidget* dockw_measure_analysis_tree = new ads::CDockWidget(QStringLiteral(u"测量分析工作空间")); - dockw_measure_analysis_tree->setWidget(_tree_measure_analysis); - dockw_measure_analysis_tree->setMinimumSizeHintMode(ads::CDockWidget::MinimumSizeHintFromContentMinimumSize); - _dock_manager->addDockWidget(ads::DockWidgetArea::LeftDockWidgetArea, dockw_measure_analysis_tree); - ui->menu_view->addAction(dockw_measure_analysis_tree->toggleViewAction()); + _dockw_measure_analysis_tree = new ads::CDockWidget(QStringLiteral(u"测量分析工作空间")); + _dockw_measure_analysis_tree->setWidget(_tree_measure_analysis); + _dockw_measure_analysis_tree->setMinimumSizeHintMode(ads::CDockWidget::MinimumSizeHintFromContentMinimumSize); + _dock_manager->addDockWidget(ads::DockWidgetArea::LeftDockWidgetArea, _dockw_measure_analysis_tree); + ui->menu_view->addAction(_dockw_measure_analysis_tree->toggleViewAction()); _menu_view_data_table_list = ui->menu_view->addMenu(QStringLiteral(u"查看数据列表")); _menu_view_analysis_view_list = ui->menu_view->addMenu(QStringLiteral(u"分析视图列表")); @@ -155,12 +145,12 @@ void MainWindow::initAction() this->_tree_measure_analysis->AddProjectModel(project_model); if (project_model->GetIsMeasureComplete()) { const QString& project_name = project_model->GetProjectName(); - const QString& result_data_dir = QDir(project_model->GetProjectDir()).filePath("EveryChannelParticleData"); - auto separate_task = new DataProcessWorkPool::EveryChannelParticleDataSeparateTask; - separate_task->SetAllChannelParticleDataFilename(project_model->GetAllChannelParticleDataFilename()); - separate_task->SetResultDataDir(result_data_dir); - separate_task->SetFinishedNotifier(this->_tree_measure_analysis, "onFinishedSeparateEveryChannelParticleData", project_name); - separate_task->StartTask(); + // const QString& result_data_dir = QDir(project_model->GetProjectDir()).filePath("EveryChannelParticleData"); + // auto separate_task = new DataProcessWorkPool::EveryChannelParticleDataSeparateTask; + // separate_task->SetAllChannelParticleDataFilename(project_model->GetAllChannelParticleDataFilename()); + // separate_task->SetResultDataDir(result_data_dir); + // separate_task->SetFinishedNotifier(this->_tree_measure_analysis, "onFinishedSeparateEveryChannelParticleData", project_name); + // separate_task->StartTask(); const QString& all_ch_count_dir = project_model->GetProjectDir(); const QString& every_ch_count_dir = QDir(project_model->GetProjectDir()).filePath("EveryChannelParticleCountData"); @@ -202,7 +192,7 @@ void MainWindow::initAction() }); connect(_tree_measure_analysis, &MeasureAnalysisTree::TreeWidget::currentItemViewWidget, [this](MeasureAnalysisView* view) { - if (view) { + if (view && this->_dock_manager) { bool view_exist = false; QList dock_widget_list = this->_dock_manager->dockWidgetsMap().values(); for (auto it = dock_widget_list.constBegin(); it != dock_widget_list.constEnd(); ++it) { @@ -244,13 +234,15 @@ void MainWindow::initAction() }); connect(_tree_measure_analysis, &MeasureAnalysisTree::TreeWidget::removeItemViewFromStack, [this](MeasureAnalysisView* view) { - QList dock_widget_list = this->_dock_manager->dockWidgetsMap().values(); - for (auto it = dock_widget_list.constBegin(); it != dock_widget_list.constEnd(); ++it) { - CDockWidget* dock_widget = *it; - if (dock_widget) { - if ( dock_widget->widget() == view ) { - dock_widget->takeWidget(); - dock_widget->close(); + if (this->_dock_manager) { + QList dock_widget_list = this->_dock_manager->dockWidgetsMap().values(); + for (auto it = dock_widget_list.constBegin(); it != dock_widget_list.constEnd(); ++it) { + CDockWidget* dock_widget = *it; + if (dock_widget) { + if ( dock_widget->widget() == view ) { + QWidget* content = dock_widget->takeWidget(); + dock_widget->deleteDockWidget(); + } } } } @@ -280,6 +272,19 @@ void MainWindow::closeEvent(QCloseEvent* event) { // Delete dock manager here to delete all floating widgets. This ensures // that all top level windows of the dock manager are properly closed + if (this->_dock_manager) { + QList dock_widget_list = this->_dock_manager->dockWidgetsMap().values(); + for (auto it = dock_widget_list.constBegin(); it != dock_widget_list.constEnd(); ++it) { + CDockWidget* dock_widget = *it; + if (dock_widget) { + bool need_take_widget = dock_widget->property("TakeWidget").toBool(); + if (need_take_widget) { + dock_widget->takeWidget(); + } + } + } + } _dock_manager->deleteLater(); + _dock_manager = nullptr; QMainWindow::closeEvent(event); } diff --git a/src/MainWindow.h b/src/MainWindow.h index a0d57b2..f6d98bd 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -59,6 +59,7 @@ private: ads::CDockAreaWidget* _central_dock_area { nullptr }; QAction* _action_central_dock_widget { nullptr }; MeasureAnalysisTree::TreeWidget* _tree_measure_analysis { nullptr }; + ads::CDockWidget* _dockw_measure_analysis_tree { nullptr }; QMenu* _menu_view_data_table_list { nullptr }; QMenu* _menu_view_analysis_view_list { nullptr }; }; diff --git a/src/MeasureAnalysisDataTableView.cpp b/src/MeasureAnalysisDataTableView.cpp index a0f12e9..840b3e0 100644 --- a/src/MeasureAnalysisDataTableView.cpp +++ b/src/MeasureAnalysisDataTableView.cpp @@ -12,6 +12,8 @@ MeasureAnalysisDataTableView::MeasureAnalysisDataTableView(): // 创建表格视图 _tableView = new VirtualTableView(this); _tableView->setFixedRowHeight(25); // 设置固定行高 + _tableView->setShowGrid(true); + _tableView->setAlternatingRowColors(false); layout->addWidget(_tableView); } diff --git a/src/MeasureAnalysisTree.cpp b/src/MeasureAnalysisTree.cpp index 15e1a2e..1ba47fc 100644 --- a/src/MeasureAnalysisTree.cpp +++ b/src/MeasureAnalysisTree.cpp @@ -19,7 +19,7 @@ TreeWidget::TreeWidget(QWidget* parent) this->setDragDropMode(QTreeWidget::NoDragDrop); this->setDefaultDropAction(Qt::IgnoreAction); this->setDropIndicatorShown(true); - this->setAlternatingRowColors(true); + this->setAlternatingRowColors(false); // 设置鼠标落在项上,显示项的详细信息 this->setMouseTracking(true); @@ -156,6 +156,19 @@ void TreeWidget::AddProjectModel(MeasureAnalysisProjectModel* model) { if (model) { auto new_item = new TreeMeasureAnalysisProjectItem(model->GetProjectName(), model->GetDescriptionInfo()); + if (model) { + TreeItem* tree_item_analyze_data_group = new_item->GetAnalyzeDataGroupItem(); + if (tree_item_analyze_data_group) { + const QString& all_ch_particle_data_filename = model->GetAllChannelParticleDataFilename(); + TreeItem* new_item_particle_data = new TreeItem; + new_item_particle_data->SetName(QStringLiteral(u"测量粒子数据")); + new_item_particle_data->SetType(TreeItem::TreeItemType::ParticleData); + new_item_particle_data->SetDescription(all_ch_particle_data_filename); + new_item_particle_data->setData(0, Qt::UserRole, all_ch_particle_data_filename); + tree_item_analyze_data_group->insertChild(0, new_item_particle_data); + this->expandItem(tree_item_analyze_data_group); + } + } this->addTopLevelItem(new_item); this->setCurrentItem(new_item); this->expandItem(new_item); @@ -163,6 +176,7 @@ void TreeWidget::AddProjectModel(MeasureAnalysisProjectModel* model) } } +/* void TreeWidget::onFinishedSeparateEveryChannelParticleData(const QString& project_name) { for (int i = 0; i < this->topLevelItemCount(); i++) { @@ -198,6 +212,7 @@ void TreeWidget::onFinishedSeparateEveryChannelParticleData(const QString& proje } } } +*/ void TreeWidget::onFinishedParticleCountData(const QString& project_name) { diff --git a/src/MeasureAnalysisTree.h b/src/MeasureAnalysisTree.h index 6c0f42b..a64007a 100644 --- a/src/MeasureAnalysisTree.h +++ b/src/MeasureAnalysisTree.h @@ -24,7 +24,7 @@ public: void AddProjectModel(MeasureAnalysisProjectModel* model); private slots: - void onFinishedSeparateEveryChannelParticleData(const QString& project_name); + // void onFinishedSeparateEveryChannelParticleData(const QString& project_name); void onFinishedParticleCountData(const QString& project_name); signals: diff --git a/src/main.cpp b/src/main.cpp index 8ba51f9..71c620b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,7 +50,7 @@ int main(int argc, char *argv[]) // 设置样式 app.setStyle(QStyleFactory::create("Fusion")); app.setWindowIcon(QIcon(":/logo/256.png")); - // app.setQuitOnLastWindowClosed(false); + app.setQuitOnLastWindowClosed(true); // 在应用程序可执行文件所在目录检查并创建Projects目录 QString projects_dir_path = QDir(app.applicationDirPath()).filePath("Projects"); diff --git a/src/src.pro b/src/src.pro index f483b71..ac43f46 100644 --- a/src/src.pro +++ b/src/src.pro @@ -6,7 +6,7 @@ QT += core gui widgets concurrent # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 -CONFIG += c++17 release +CONFIG += c++17 #release msvc { QMAKE_CFLAGS += /utf-8 QMAKE_CXXFLAGS += /utf-8