502 lines
13 KiB
C++
502 lines
13 KiB
C++
#pragma execution_character_set("utf-8")
|
||
|
||
#include "QMainwindow.h"
|
||
#include <QSettings>
|
||
#include <QFile>
|
||
#include <QTextStream>
|
||
|
||
#include <QTimer>
|
||
//#include <QMessageBox>
|
||
|
||
#include <QDebug>
|
||
|
||
QMainwindow::QMainwindow(QWidget *parent)
|
||
: QMainWindow(parent)
|
||
{
|
||
ui.setupUi(this);
|
||
|
||
// 初始化日志管理器
|
||
QLogManager::Instance()->setLogLevel(QLogManager::SQ_INFO);;
|
||
|
||
m_nReplyCount = 0;
|
||
|
||
m_dataAcquisition = new QDataAcquisition;
|
||
|
||
m_netCheckTimer = new QTimer(this);
|
||
m_netCheckTimer->start(1000); // 启动定时器,间隔1000毫秒(1秒)
|
||
connect(m_netCheckTimer, &QTimer::timeout, this, [=]() {
|
||
if (m_dataAcquisition->m_InterfaceFutu->bConnected)
|
||
ui.lblNetStatu->setStyleSheet("background-color: green;");
|
||
else
|
||
ui.lblNetStatu->setStyleSheet("background-color: red;");
|
||
});
|
||
|
||
initWidget();
|
||
initReplyManage();
|
||
|
||
// 日志查看器标签页
|
||
logViewer = new QLogViewer;
|
||
//tabWidget->addTab(logViewer, "System Logs");
|
||
|
||
ui.verticalLayout_9->addWidget(logViewer);
|
||
|
||
LOG_INFO("Start Programm!");
|
||
|
||
|
||
// 创建查看器
|
||
QBigOrderViewer* viewer = new QBigOrderViewer();
|
||
ui.layoutBigorderViewer->addWidget(viewer);
|
||
QObject::connect(
|
||
viewer,
|
||
&QBigOrderViewer::sendStockCodeToMainForm,
|
||
this,
|
||
[this](const QString& code) {
|
||
int nIdex = 0;
|
||
for (const auto& item : m_replyCodes) {
|
||
if (item->stockCode == code)
|
||
break;
|
||
nIdex++;
|
||
}
|
||
|
||
if (nIdex < 0 || nIdex >= m_model->rowCount()) return;
|
||
|
||
// 清除当前选择
|
||
ui.tableReplylist->clearSelection();
|
||
|
||
// 创建选择范围(整行)
|
||
QModelIndex topLeft = m_model->index(nIdex, 0);
|
||
QModelIndex bottomRight = m_model->index(nIdex, m_model->columnCount() - 1);
|
||
QItemSelection selection(topLeft, bottomRight);
|
||
|
||
// 应用选择
|
||
ui.tableReplylist->selectionModel()->select(selection, QItemSelectionModel::Select);
|
||
|
||
// 滚动到选中行
|
||
ui.tableReplylist->scrollTo(m_model->index(nIdex, 0), QAbstractItemView::PositionAtTop);
|
||
//ui.tableReplylist->scrollTo(topLeft, QAbstractItemView::PositionAtCenter);
|
||
ui.tableReplylist->update();
|
||
|
||
}, Qt::QueuedConnection // 必须使用跨线程连接
|
||
);
|
||
|
||
|
||
auto manager = QBigOrderManager::instance();
|
||
QObject::connect(
|
||
manager,
|
||
&QBigOrderManager::markBigOrderSignal,
|
||
this,
|
||
[this]() {
|
||
m_lightWidget->triggerSignal();
|
||
|
||
}, Qt::QueuedConnection // 必须使用跨线程连接
|
||
);
|
||
|
||
// 检查对象有效性
|
||
if (!m_dataAcquisition || !m_dataAcquisition->m_InterfaceFutu) {
|
||
qCritical() << "Invalid data acquisition or interface";
|
||
return;
|
||
}
|
||
// 获取处理器指针
|
||
auto* processor = &m_dataAcquisition->m_InterfaceFutu->processor;
|
||
// 连接 maxAskOrderReady 信号
|
||
bool askConnected = QObject::connect(
|
||
processor,
|
||
&QOrderProcessor::maxOrderReady,
|
||
this,
|
||
[this](BigOrderInfo bigOrderInfo) {
|
||
|
||
if (auto manager = QBigOrderManager::instance()) {
|
||
if (!manager->addBigOrder(bigOrderInfo))
|
||
;// LOG_WARNING("Failed to add order");
|
||
}
|
||
},Qt::QueuedConnection // 必须使用跨线程连接
|
||
);
|
||
|
||
//bool bidConnected = QObject::connect(
|
||
// processor,
|
||
// &QOrderProcessor::maxBidOrderReady,
|
||
// this,
|
||
// [this](BigOrderInfo bigOrderInfo) {
|
||
|
||
// BigOrderItem order;
|
||
// order.stockCode = bigOrderInfo.code;
|
||
// order.stockName = bigOrderInfo.name;
|
||
// order.orderPrice = bigOrderInfo.price;
|
||
// order.stockQuantity = bigOrderInfo.volume;
|
||
// order.orderCount = 1;
|
||
// order.orderType = "BID";
|
||
// order.orderTime = QDateTime::currentDateTime(); // 这里时间还不准确,应该是接收到的系统时间
|
||
|
||
// if (auto manager = QBigOrderManager::instance()) {
|
||
// if (!manager->addBigOrder(order))
|
||
// LOG_WARNING("Failed to add order");
|
||
// }
|
||
// }, Qt::QueuedConnection // 必须使用跨线程连接
|
||
//);
|
||
|
||
|
||
|
||
/*
|
||
执行订阅
|
||
- 发送订阅信号
|
||
*/
|
||
QObject::connect(ui.btnDoReply, &QPushButton::clicked, [this]() {
|
||
m_nReplyCount = m_setReplayCode.size();
|
||
LOG_INFO("Start Reply!");
|
||
|
||
ui.btnDoReply->setText("正在检测 ...");
|
||
|
||
// 后面封装起来,通过信号的方式进行交互
|
||
|
||
// 筛选参数,这个放到外面去,修改之后及时更新
|
||
m_dataAcquisition->m_InterfaceFutu->processor.setreplyCodeQuantity(m_replyCodeQuantity);
|
||
m_dataAcquisition->m_InterfaceFutu->subStock();
|
||
});
|
||
|
||
/*
|
||
手动连接 OpenD
|
||
*/
|
||
QObject::connect(ui.btnConnOpend, &QPushButton::clicked, [this]() {
|
||
if (!m_dataAcquisition->m_InterfaceFutu->bConnected)
|
||
m_dataAcquisition->m_InterfaceFutu->InitConn();
|
||
});
|
||
|
||
}
|
||
|
||
QMainwindow::~QMainwindow()
|
||
{
|
||
if (m_dataAcquisition)
|
||
{
|
||
delete m_dataAcquisition;
|
||
m_dataAcquisition = NULL;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
void QMainwindow::initWidget()
|
||
{
|
||
m_model = new QStandardItemModel(0, 2, this);
|
||
//m_model->setEditable(true);
|
||
m_proxyModel = new QSortFilterProxyModel(this);
|
||
|
||
// 表格视图配置
|
||
m_proxyModel->setSourceModel(m_model);
|
||
ui.tableReplylist->setModel(m_proxyModel);
|
||
//ui.tableReplylist->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||
|
||
ui.tableReplylist->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
ui.tableReplylist->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||
//ui.tableReplylist->setEditTriggers(QAbstractItemView::EditTriggers);
|
||
// 设置表头
|
||
m_model->setHorizontalHeaderLabels(
|
||
QStringList() << "股票代码" << "检测阈值");
|
||
|
||
/*
|
||
用户更改数量阈值时,同时修改列表
|
||
*/
|
||
QObject::connect(m_model, &QStandardItemModel::dataChanged,
|
||
[&](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) {
|
||
// 遍历变化区域
|
||
for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
|
||
for (int col = topLeft.column(); col <= bottomRight.column(); ++col) {
|
||
QModelIndex index = m_model->index(row, col);
|
||
QString newData = m_model->data(index, Qt::DisplayRole).toString();
|
||
|
||
m_replyCodes.at(row)->stockQuantityTh = newData.mid(0, newData.size() - 2).toFloat() * 1000;
|
||
}
|
||
}
|
||
});
|
||
|
||
QObject::connect(ui.tableReplylist->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||
|
||
[&](const QItemSelection &selected, const QItemSelection &deselected) {
|
||
|
||
Q_UNUSED(deselected); // 未使用deselected,避免警告
|
||
|
||
// 获取当前选中的所有行
|
||
QModelIndexList selectedIndexes = ui.tableReplylist->selectionModel()->selectedIndexes();
|
||
|
||
// 如果没有选中任何项
|
||
if (selectedIndexes.isEmpty()) {
|
||
return;
|
||
}
|
||
// 提取选中的行号(去重)
|
||
int selectedRow = selectedIndexes.at(0).row();
|
||
ui.editInputCode->setText(m_replyCodes.at(selectedRow)->stockCode);
|
||
ui.editQuantityTh->setText(QString::number(m_replyCodes.at(selectedRow)->stockQuantityTh / 1000,'f',1));
|
||
});
|
||
//ui.listAsk->setAlternatingRowColors(true);
|
||
//ui.listAsk->setStyleSheet(
|
||
// "QListWidget::item[latest=true] {"
|
||
// " background-color: #ffcccc;" // 最新行的颜色
|
||
// "}"
|
||
// "QListWidget::item {"
|
||
// " background-color: white;" // 默认行的颜色
|
||
// "}"
|
||
//);
|
||
//ui.listBid->setAlternatingRowColors(true);
|
||
//ui.listBid->setStyleSheet(
|
||
// "QListWidget::item[latest=true] {"
|
||
// " background-color: #ffcccc;" // 最新行的颜色
|
||
// "}"
|
||
// "QListWidget::item {"
|
||
// " background-color: white;" // 默认行的颜色
|
||
// "}"
|
||
//);
|
||
//// 添加项目后更新属性
|
||
//connect(ui.listAsk, &QListWidget::itemChanged, [=]() {
|
||
// if (ui.listAsk->count() > 0) {
|
||
// // 清除之前最新项的标记
|
||
// for (int i = 0; i < ui.listAsk->count(); ++i) {
|
||
// ui.listAsk->item(i)->setData(Qt::UserRole, QVariant());
|
||
// }
|
||
// // 标记最新项
|
||
// ui.listAsk->item(ui.listAsk->count() - 1)->setData(Qt::UserRole, "latest");
|
||
// }
|
||
//});
|
||
|
||
//connect(ui.listBid, &QListWidget::itemChanged, [=]() {
|
||
// if (ui.listAsk->count() > 0) {
|
||
// // 清除之前最新项的标记
|
||
// for (int i = 0; i < ui.listAsk->count(); ++i) {
|
||
// ui.listBid->item(i)->setData(Qt::UserRole, QVariant());
|
||
// }
|
||
// // 标记最新项
|
||
// ui.listAsk->item(ui.listBid->count() - 1)->setData(Qt::UserRole, "latest");
|
||
// }
|
||
//});
|
||
|
||
// 创建呼吸灯控件
|
||
m_lightWidget = new QBreathingLight();
|
||
ui.layoutBreathing->addWidget(m_lightWidget, 0, Qt::AlignCenter);
|
||
|
||
//// 创建自动测试定时器
|
||
//QTimer *autoTestTimer = new QTimer(this);
|
||
//connect(autoTestTimer, &QTimer::timeout, m_lightWidget, &QBreathingLight::triggerSignal);
|
||
//autoTestTimer->start(2000); // 每2秒自动触发一次
|
||
|
||
//connect(ui.btnConnOpend, &QPushButton::clicked, m_lightWidget, &QBreathingLight::triggerSignal);
|
||
|
||
}
|
||
|
||
void QMainwindow::initReplyManage()
|
||
{
|
||
// 读取默认订阅股票代码列表
|
||
readReplyCodefile(m_replyCodes);
|
||
LOG_INFO("订阅列表加载成功!");
|
||
|
||
m_dataAcquisition->m_InterfaceFutu->setReplyList(m_replyCodes);
|
||
|
||
// 设置代码输入规则
|
||
QIntValidator *intValidator = new QIntValidator(1, 99999, this);
|
||
ui.editInputCode->setValidator(intValidator);
|
||
|
||
// 添加/修改订阅项
|
||
QObject::connect(ui.btnAddCode, &QPushButton::clicked, [=]()
|
||
{
|
||
ReplyCodeItem order;
|
||
order.stockCode = toFixedDigitNumber(ui.editInputCode->text());
|
||
order.stockQuantityTh = ui.editQuantityTh->text().toFloat() * 1000;
|
||
|
||
bool bFinded = false;
|
||
for (const auto& item : m_replyCodes) {
|
||
if (item->stockCode == order.stockCode)
|
||
{
|
||
item->stockQuantityTh = order.stockQuantityTh;
|
||
m_replyCodeQuantity[item->stockCode] = order.stockQuantityTh;
|
||
bFinded = true;
|
||
|
||
LOG_INFO("更新完成,代码:" + order.stockCode + " ,阈值:"+QString::number(order.stockQuantityTh));
|
||
|
||
break;
|
||
}
|
||
}
|
||
if (!bFinded)
|
||
{
|
||
if (m_replyCodes.size() + 1 > 50)
|
||
{
|
||
LOG_WARNING("订阅列表添加失败,订阅数超过最大订阅数量!");
|
||
return;
|
||
}
|
||
|
||
auto newOrder = QSharedPointer<ReplyCodeItem>::create(order);
|
||
m_replyCodes.append(newOrder);
|
||
|
||
m_replyCodeQuantity.insert(order.stockCode, order.stockQuantityTh);
|
||
|
||
LOG_INFO("添加完成,代码:" + order.stockCode + " ,阈值:" + QString::number(order.stockQuantityTh));
|
||
}
|
||
updateCodeTable();
|
||
|
||
});
|
||
|
||
// 删除选订阅项
|
||
QObject::connect(ui.btnDeleteCode, &QPushButton::clicked, [=]() {
|
||
// 获取所有选中的行
|
||
QModelIndexList selected = ui.tableReplylist->selectionModel()->selectedRows();
|
||
// 删除选中行
|
||
for (int i = selected.size() - 1; i >= 0; --i) {
|
||
m_replyCodes.removeAt(selected[i].row());
|
||
m_replyCodeQuantity.remove(toFixedDigitNumber(ui.editInputCode->text()));
|
||
}
|
||
updateCodeTable();
|
||
});
|
||
|
||
|
||
/*
|
||
保存文件
|
||
*/
|
||
QObject::connect(ui.btnSaveCodefile, &QPushButton::clicked, [=]() {
|
||
saveReplyCodefile();
|
||
|
||
// 更新检测阈值
|
||
m_dataAcquisition->m_InterfaceFutu->processor.setreplyCodeQuantity(m_replyCodeQuantity);
|
||
});
|
||
|
||
}
|
||
|
||
void QMainwindow::updateCodeTable()
|
||
{
|
||
m_model->removeRows(0, m_model->rowCount());
|
||
for (const auto& order : m_replyCodes) {
|
||
QList<QStandardItem*> rowItems;
|
||
|
||
QStandardItem* codeItem = new QStandardItem(order->stockCode);
|
||
codeItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << codeItem;
|
||
|
||
//rowItems << new QStandardItem(order->stockCode);
|
||
|
||
QStandardItem* quanityItem = new QStandardItem(QString::number(order->stockQuantityTh / 1000, 'f', 1) + "K");
|
||
quanityItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << quanityItem;
|
||
|
||
m_model->appendRow(rowItems);
|
||
}
|
||
}
|
||
|
||
// 检查并格式化为5位数字
|
||
QString QMainwindow::toFixedDigitNumber(const QString& input) {
|
||
// 检查是否全为0-9数字
|
||
QRegularExpression re("^\\d+$");
|
||
if (!re.match(input).hasMatch()) {
|
||
return QString(); // 弹出提示框,code 输入错误,暂时先返回空
|
||
}
|
||
|
||
// 长度检查和处理
|
||
if (input.length() > 5) {
|
||
return input.right(5); // 超过5位:截取后5位
|
||
}
|
||
else if (input.length() < 5) {
|
||
return QString("%1").arg(input, 5, '0'); // 不足5位:左侧补0
|
||
}
|
||
return input; // 正好5位:直接返回
|
||
}
|
||
|
||
|
||
/*
|
||
加载订阅列表
|
||
*/
|
||
void QMainwindow::readReplyCodefile(QList<QSharedPointer<ReplyCodeItem>>& replyList)
|
||
{
|
||
QString appDir = QCoreApplication::applicationDirPath();
|
||
QString fileName = QDir(appDir).absoluteFilePath("../../config/replyCodeList.csv");
|
||
QFile file(fileName);
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
LOG_FATAL("无法打开文件。");
|
||
}
|
||
|
||
QTextStream in(&file);
|
||
in.setCodec("UTF-8"); // 设置编码,确保中文正常
|
||
|
||
replyList.clear(); // 清空原有数据
|
||
|
||
m_replyCodeQuantity.clear();
|
||
|
||
bool isFirstLine = true;
|
||
while (!in.atEnd()) {
|
||
QString line = in.readLine();
|
||
|
||
// 跳过空行
|
||
if (line.trimmed().isEmpty()) {
|
||
continue;
|
||
}
|
||
|
||
// 跳过标题行(假设第一行是标题)
|
||
if (isFirstLine) {
|
||
isFirstLine = false;
|
||
continue;
|
||
}
|
||
|
||
// 解析CSV行
|
||
QStringList fields = line.split(',');
|
||
|
||
|
||
// 确保有足够的字段(根据你的CSV结构调整)
|
||
if (/*fields.size() >= 3*/1) {
|
||
QString code = fields[0].trimmed();
|
||
QString description = fields[1].trimmed();
|
||
//bool isActive = fields[2].trimmed().toLower() == "true" || fields[2].trimmed() == "1";
|
||
ReplyCodeItem replyItem;
|
||
replyItem.stockCode = toFixedDigitNumber(code);
|
||
replyItem.stockQuantityTh = description.toFloat();
|
||
|
||
// 创建ReplyCodeItem并添加到列表
|
||
auto item = QSharedPointer<ReplyCodeItem>::create(replyItem);
|
||
m_replyCodes.append(item);
|
||
|
||
// 保存到映射表
|
||
m_replyCodeQuantity.insert(replyItem.stockCode, replyItem.stockQuantityTh);
|
||
}
|
||
else {
|
||
//qWarning() << "忽略格式不正确的行:" << line;
|
||
}
|
||
}
|
||
|
||
file.close();
|
||
updateCodeTable();
|
||
}
|
||
|
||
/*
|
||
保存订阅列表
|
||
*/
|
||
void QMainwindow::saveReplyCodefile()
|
||
{
|
||
//// 配置文件名
|
||
QString appDir = QCoreApplication::applicationDirPath();
|
||
QString fileName = QDir(appDir).absoluteFilePath("../../config/replyCodeList.csv");
|
||
|
||
QFile file(fileName);
|
||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||
LOG_FATAL("无法创建订阅文件。" );
|
||
return;
|
||
}
|
||
|
||
QTextStream out(&file);
|
||
out.setCodec("GBK"); // 确保使用UTF-8编码
|
||
|
||
// 写入表头
|
||
for (int col = 0; col < m_model->columnCount(); ++col) {
|
||
out << escapeCsv(m_model->headerData(col, Qt::Horizontal).toString());
|
||
if (col < m_model->columnCount() - 1) out << ",";
|
||
}
|
||
out << "\n";
|
||
// 写入数据
|
||
for (const auto &value : m_replyCodes) {
|
||
out << value->stockCode<<","<< value->stockQuantityTh << "\n";
|
||
}
|
||
|
||
file.close();
|
||
|
||
LOG_INFO("订阅列表保存成功!");
|
||
}
|
||
|
||
QString QMainwindow::escapeCsv(const QString &field) {
|
||
if (field.contains(',') || field.contains('"') || field.contains('\n')) {
|
||
QString escaped = field;
|
||
escaped.replace("\"", "\"\"");
|
||
return "\"" + escaped + "\"";
|
||
}
|
||
return field;
|
||
} |