606 lines
19 KiB
C++
606 lines
19 KiB
C++
#include "mainwindow.h"
|
||
#include "ui_mainwindow.h"
|
||
#include "Version.h"
|
||
#include "widgets/ImageGridWidget.h"
|
||
#include "widgets/DeviceStatusWidget.h"
|
||
#include "widgets/MeasureResultListWidget.h"
|
||
#include "dialogcameralevel.h"
|
||
#include "dialogalgoarg.h"
|
||
#include "dialogcamera.h"
|
||
|
||
#include <QImage>
|
||
#include <QStringList>
|
||
#include <QDebug>
|
||
#include <QVBoxLayout>
|
||
#include <QMessageBox>
|
||
#include <QStatusBar>
|
||
#include <QLabel>
|
||
#include <QDateTime>
|
||
#include <QFileDialog>
|
||
#include <thread>
|
||
|
||
#include "VrLog.h"
|
||
#include "IVrUtils.h"
|
||
#include "StyledMessageBox.h"
|
||
|
||
MainWindow::MainWindow(QWidget *parent)
|
||
: QMainWindow(parent)
|
||
, ui(new Ui::MainWindow)
|
||
{
|
||
ui->setupUi(this);
|
||
|
||
// 设置窗口图标
|
||
this->setWindowIcon(QIcon(":/common/resource/logo.png"));
|
||
|
||
// 设置状态栏字体
|
||
QFont statusFont = statusBar()->font();
|
||
statusFont.setPointSize(12);
|
||
statusBar()->setFont(statusFont);
|
||
|
||
// 设置状态栏颜色和padding
|
||
statusBar()->setStyleSheet("QStatusBar { color: rgb(239, 241, 245); padding: 20px; }");
|
||
|
||
// 隐藏标题栏
|
||
setWindowFlags(Qt::FramelessWindowHint);
|
||
|
||
// 启动后自动最大化显示
|
||
this->showMaximized();
|
||
|
||
// 初始化时隐藏工作状态标签
|
||
ui->label_work->setVisible(false);
|
||
|
||
// 创建组合控件
|
||
m_gridView = new ImageGridWidget();
|
||
m_deviceStatusWidget = new DeviceStatusWidget();
|
||
m_measureResultWidget = new MeasureResultListWidget();
|
||
|
||
// 将设备状态widget添加到frame_dev中
|
||
QVBoxLayout* frameResultImageLayout = new QVBoxLayout(ui->detect_result);
|
||
frameResultImageLayout->setContentsMargins(0, 0, 0, 0);
|
||
frameResultImageLayout->addWidget(m_gridView);
|
||
|
||
// 将MeasureResultListWidget添加到detect_data中
|
||
QVBoxLayout* frameDataLayout = new QVBoxLayout(ui->detect_data);
|
||
frameDataLayout->setContentsMargins(0, 0, 0, 0);
|
||
frameDataLayout->addWidget(m_measureResultWidget);
|
||
|
||
QVBoxLayout* frameDevLayout = new QVBoxLayout(ui->device_status);
|
||
frameDevLayout->setContentsMargins(0, 0, 0, 0);
|
||
frameDevLayout->addWidget(m_deviceStatusWidget);
|
||
|
||
// 初始化Presenter
|
||
m_presenter = new WheelMeasurePresenter();
|
||
|
||
// 将设备状态控件与Presenter关联
|
||
m_presenter->setStatusUpdate(this);
|
||
|
||
// 连接配置更新信号
|
||
connect(m_presenter, &WheelMeasurePresenter::configUpdated, this, &MainWindow::onConfigSaved);
|
||
|
||
// 连接设备点击信号到重新检测槽函数
|
||
connect(m_deviceStatusWidget, &DeviceStatusWidget::deviceClicked, this, &MainWindow::onDeviceClicked);
|
||
|
||
// 连接图像右键点击信号到保存数据槽函数
|
||
connect(m_gridView, &ImageGridWidget::tileRightClicked, this, &MainWindow::onTileRightClicked);
|
||
|
||
// 设置版本信息显示
|
||
setupVersionDisplay();
|
||
|
||
// 初始化日志模型
|
||
m_logModel = new QStringListModel(this);
|
||
ui->detect_log->setModel(m_logModel);
|
||
|
||
// 连接日志更新信号
|
||
connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog);
|
||
|
||
m_presenter->Init();
|
||
}
|
||
|
||
MainWindow::~MainWindow()
|
||
{
|
||
// 先清除回调,防止后台线程继续调用
|
||
if (m_presenter) {
|
||
m_presenter->setStatusUpdate(nullptr);
|
||
}
|
||
|
||
if (m_gridView) {
|
||
delete m_gridView;
|
||
m_gridView = nullptr;
|
||
}
|
||
|
||
if (m_deviceStatusWidget) {
|
||
delete m_deviceStatusWidget;
|
||
m_deviceStatusWidget = nullptr;
|
||
}
|
||
|
||
if (m_measureResultWidget) {
|
||
delete m_measureResultWidget;
|
||
m_measureResultWidget = nullptr;
|
||
}
|
||
|
||
if (m_versionLabel) {
|
||
delete m_versionLabel;
|
||
m_versionLabel = nullptr;
|
||
}
|
||
|
||
if (m_dialogAlgoArg) {
|
||
delete m_dialogAlgoArg;
|
||
m_dialogAlgoArg = nullptr;
|
||
}
|
||
|
||
if (m_dialogCamera) {
|
||
delete m_dialogCamera;
|
||
m_dialogCamera = nullptr;
|
||
}
|
||
|
||
if (m_dialogCameraLevel) {
|
||
delete m_dialogCameraLevel;
|
||
m_dialogCameraLevel = nullptr;
|
||
}
|
||
|
||
if (m_presenter) {
|
||
delete m_presenter;
|
||
m_presenter = nullptr;
|
||
}
|
||
|
||
delete ui;
|
||
LOG_DEBUG("~MainWindow finish \n");
|
||
}
|
||
|
||
void MainWindow::resizeEvent(QResizeEvent* event)
|
||
{
|
||
QMainWindow::resizeEvent(event);
|
||
}
|
||
|
||
void MainWindow::on_btn_hide_clicked()
|
||
{
|
||
this->showMinimized();
|
||
}
|
||
|
||
void MainWindow::on_btn_close_clicked()
|
||
{
|
||
this->close();
|
||
}
|
||
|
||
void MainWindow::on_btn_test_clicked()
|
||
{
|
||
if (!m_presenter) {
|
||
QMessageBox::warning(this, "错误", "系统未初始化完成!");
|
||
return;
|
||
}
|
||
|
||
// 设置选中状态
|
||
ui->btn_test->setStyleSheet(
|
||
"QPushButton { image: url(:/common/resource/config_data_test_s.png); background-color: rgb(38, 40, 47); border: none; }"
|
||
);
|
||
|
||
// 打开文件选择对话框
|
||
QString fileName = QFileDialog::getOpenFileName(
|
||
this,
|
||
tr("选择调试数据文件"),
|
||
QString(),
|
||
tr("激光数据文件 (*.txt);;所有文件 (*.*)")
|
||
);
|
||
|
||
// 恢复未选中状态
|
||
ui->btn_test->setStyleSheet(
|
||
"QPushButton { image: url(:/common/resource/config_data_test.png); background-color: rgb(38, 40, 47); border: none; }"
|
||
"QPushButton:pressed { image: url(:/common/resource/config_data_test_s.png); }"
|
||
);
|
||
|
||
if (fileName.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
// 清空当前检测数据列表
|
||
if (m_measureResultWidget) {
|
||
m_measureResultWidget->clearAllResults();
|
||
}
|
||
|
||
// emit logUpdateRequested(QString("正在加载调试数据: %1").arg(fileName));
|
||
|
||
// 在后台线程中执行加载和检测
|
||
std::thread t([this, fileName]() {
|
||
int result = m_presenter->LoadDebugDataAndDetect(fileName.toStdString());
|
||
if (result == 0) {
|
||
QMetaObject::invokeMethod(this, [this]() {
|
||
emit logUpdateRequested("调试数据加载和检测成功");
|
||
}, Qt::QueuedConnection);
|
||
} else {
|
||
QMetaObject::invokeMethod(this, [this, result]() {
|
||
emit logUpdateRequested(QString("调试数据复检失败: %1").arg(result));
|
||
}, Qt::QueuedConnection);
|
||
}
|
||
});
|
||
t.detach();
|
||
}
|
||
|
||
void MainWindow::on_btn_start_clicked()
|
||
{
|
||
if (!m_presenter) {
|
||
QMessageBox::warning(this, "错误", "系统未初始化完成!");
|
||
return;
|
||
}
|
||
|
||
// 启动所有相机的检测
|
||
m_presenter->StartAllDetection();
|
||
emit logUpdateRequested("已启动所有相机检测");
|
||
}
|
||
|
||
void MainWindow::on_btn_stop_clicked()
|
||
{
|
||
if (!m_presenter) {
|
||
return;
|
||
}
|
||
|
||
// 停止所有相机的检测
|
||
m_presenter->StopAllDetection();
|
||
emit logUpdateRequested("已停止所有相机检测");
|
||
}
|
||
|
||
void MainWindow::on_btn_camera_config_clicked()
|
||
{
|
||
if (!m_presenter) {
|
||
QMessageBox::warning(this, "错误", "系统未初始化完成!");
|
||
return;
|
||
}
|
||
|
||
if (!m_dialogCamera) {
|
||
m_dialogCamera = new DialogCamera(this);
|
||
m_dialogCamera->SetPresenter(m_presenter);
|
||
connect(m_dialogCamera, &DialogCamera::configSaved, this, &MainWindow::onConfigSaved);
|
||
// 连接关闭信号恢复按钮状态
|
||
connect(m_dialogCamera, &QDialog::finished, this, [this]() {
|
||
ui->btn_camera_config->setStyleSheet(
|
||
"QPushButton { image: url(:/common/resource/config_camera.png); background-color: rgb(38, 40, 47); border: none; }"
|
||
"QPushButton:pressed { image: url(:/common/resource/config_camera_s.png); }"
|
||
);
|
||
});
|
||
}
|
||
|
||
// 设置选中状态
|
||
ui->btn_camera_config->setStyleSheet(
|
||
"QPushButton { image: url(:/common/resource/config_camera_s.png); background-color: rgb(38, 40, 47); border: none; }"
|
||
);
|
||
m_dialogCamera->show();
|
||
}
|
||
|
||
void MainWindow::on_btn_algo_config_clicked()
|
||
{
|
||
if (!m_presenter) {
|
||
QMessageBox::warning(this, "错误", "系统未初始化完成!");
|
||
return;
|
||
}
|
||
|
||
if (!m_dialogAlgoArg) {
|
||
m_dialogAlgoArg = new DialogAlgoArg(m_presenter->GetConfig(),
|
||
m_presenter->GetConfigResult(), this);
|
||
// 连接关闭信号恢复按钮状态
|
||
connect(m_dialogAlgoArg, &QDialog::finished, this, [this]() {
|
||
ui->btn_algo_config->setStyleSheet(
|
||
"QPushButton { image: url(:/common/resource/config_algo.png); background-color: rgb(38, 40, 47); border: none; }"
|
||
"QPushButton:pressed { image: url(:/common/resource/config_algo_s.png); }"
|
||
);
|
||
});
|
||
}
|
||
|
||
// 设置选中状态
|
||
ui->btn_algo_config->setStyleSheet(
|
||
"QPushButton { image: url(:/common/resource/config_algo_s.png); background-color: rgb(38, 40, 47); border: none; }"
|
||
);
|
||
m_dialogAlgoArg->Init();
|
||
m_dialogAlgoArg->show();
|
||
}
|
||
|
||
void MainWindow::on_btn_camera_level_clicked()
|
||
{
|
||
if (!m_presenter) {
|
||
QMessageBox::warning(this, "错误", "系统未初始化完成!");
|
||
return;
|
||
}
|
||
|
||
if (!m_dialogCameraLevel) {
|
||
m_dialogCameraLevel = new DialogCameraLevel(this);
|
||
// 连接关闭信号恢复按钮状态
|
||
connect(m_dialogCameraLevel, &QDialog::finished, this, [this]() {
|
||
ui->btn_camera_level->setStyleSheet(
|
||
"QPushButton { image: url(:/common/resource/config_camera_level.png); background-color: rgb(38, 40, 47); border: none; }"
|
||
"QPushButton:pressed { image: url(:/common/resource/config_camera_level_s.png); }"
|
||
);
|
||
});
|
||
}
|
||
|
||
// 设置相机列表
|
||
m_dialogCameraLevel->setCameraList(m_presenter->GetCameraList(), m_presenter);
|
||
|
||
// 设置配置对象
|
||
m_dialogCameraLevel->setConfig(m_presenter->GetConfig(), m_presenter->GetConfigResult());
|
||
|
||
// 设置选中状态
|
||
ui->btn_camera_level->setStyleSheet(
|
||
"QPushButton { image: url(:/common/resource/config_camera_level_s.png); background-color: rgb(38, 40, 47); border: none; }"
|
||
);
|
||
m_dialogCameraLevel->show();
|
||
}
|
||
|
||
void MainWindow::onDeviceClicked(const QString& deviceName)
|
||
{
|
||
if (!m_presenter) {
|
||
QMessageBox::warning(this, "错误", "系统未初始化完成!");
|
||
return;
|
||
}
|
||
|
||
// 查找相机索引
|
||
auto cameraList = m_presenter->GetCameraList();
|
||
int foundIndex = -1;
|
||
for (int i = 0; i < static_cast<int>(cameraList.size()); ++i) {
|
||
if (QString::fromStdString(cameraList[i].first) == deviceName) {
|
||
foundIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (foundIndex < 0) {
|
||
emit logUpdateRequested(QString("未找到相机: %1").arg(deviceName));
|
||
return;
|
||
}
|
||
|
||
// 清空指定设备的检测结果
|
||
if (m_measureResultWidget) {
|
||
m_measureResultWidget->clearDeviceResult(deviceName);
|
||
}
|
||
|
||
// 直接开始检测
|
||
m_presenter->ResetDetect(foundIndex);
|
||
emit logUpdateRequested(QString("已启动相机 \"%1\" 的检测").arg(deviceName));
|
||
}
|
||
|
||
void MainWindow::onConfigSaved()
|
||
{
|
||
// 配置已在 WheelMeasurePresenter::OnConfigChanged 中更新
|
||
// 算法参数和调平参数的修改不需要重新初始化相机
|
||
LOG_INFO("Config saved, configuration updated in memory\n");
|
||
}
|
||
|
||
void MainWindow::onTileRightClicked(int index, const QString& alias)
|
||
{
|
||
if (!m_presenter) {
|
||
StyledMessageBox::warning(this, "错误", "系统未初始化完成!");
|
||
return;
|
||
}
|
||
|
||
// 获取当前检测的相机索引(0-based)
|
||
int currentDetectIndex = m_presenter->GetDetectIndex() - 1; // 转换为0-based
|
||
|
||
// 检查点击的是否是当前检测的相机
|
||
if (index != currentDetectIndex) {
|
||
emit logUpdateRequested(QString("设备 \"%1\" 无缓存数据").arg(alias));
|
||
StyledMessageBox::information(this, "提示",
|
||
QString("设备 \"%1\" 无缓存数据\n\n当前缓存的是设备 %2 的数据")
|
||
.arg(alias)
|
||
.arg(currentDetectIndex >= 0 ? m_gridView->getAlias(currentDetectIndex) : "无"));
|
||
return;
|
||
}
|
||
|
||
// 检查是否有缓存数据
|
||
int cacheSize = m_presenter->GetDetectionDataCacheSize();
|
||
if (cacheSize <= 0) {
|
||
emit logUpdateRequested(QString("设备 \"%1\" 无缓存数据").arg(alias));
|
||
StyledMessageBox::information(this, "提示", QString("设备 \"%1\" 无缓存数据").arg(alias));
|
||
return;
|
||
}
|
||
|
||
// 弹出文件保存对话框
|
||
QString defaultFileName = QString("%1_%2.txt")
|
||
.arg(alias)
|
||
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"));
|
||
|
||
QString filePath = QFileDialog::getSaveFileName(
|
||
this,
|
||
tr("保存检测数据"),
|
||
defaultFileName,
|
||
tr("文本文件 (*.txt);;所有文件 (*.*)")
|
||
);
|
||
|
||
if (filePath.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
// 调用Presenter保存数据
|
||
int result = m_presenter->SaveDetectionDataToFile(filePath.toStdString());
|
||
if (result == 0) {
|
||
emit logUpdateRequested(QString("检测数据已保存: %1").arg(filePath));
|
||
StyledMessageBox::information(this, "成功", QString("检测数据已保存到:\n%1").arg(filePath));
|
||
} else {
|
||
emit logUpdateRequested(QString("保存检测数据失败: %1").arg(result));
|
||
StyledMessageBox::warning(this, "失败", QString("保存检测数据失败,错误码: %1").arg(result));
|
||
}
|
||
}
|
||
|
||
// IWheelMeasureStatus接口方法实现
|
||
void MainWindow::OnStatusUpdate(const QString &statusMessage)
|
||
{
|
||
emit logUpdateRequested(statusMessage);
|
||
LOG_DEBUG("Status update: %s \n", statusMessage.toStdString().c_str());
|
||
}
|
||
|
||
void MainWindow::OnNeedShowImageCount(const QStringList &cameraNames)
|
||
{
|
||
if (cameraNames.isEmpty()) {
|
||
m_gridView->initImages(0);
|
||
LOG_DEBUG("No images to display\n");
|
||
} else {
|
||
m_gridView->initImages(cameraNames.size());
|
||
|
||
// 为每个tile设置别名
|
||
for (int i = 0; i < cameraNames.size(); ++i) {
|
||
m_gridView->setTileAlias(i, cameraNames.at(i));
|
||
}
|
||
|
||
// 初始化设备状态列表
|
||
if (m_deviceStatusWidget && m_presenter) {
|
||
QList<WheelDeviceDisplayInfo> devices;
|
||
for (const QString& name : cameraNames) {
|
||
devices.append(WheelDeviceDisplayInfo(name, name, "", DeviceStatus::Offline, true));
|
||
}
|
||
m_deviceStatusWidget->setDevices(devices);
|
||
}
|
||
|
||
// 初始化检测结果列表
|
||
if (m_measureResultWidget) {
|
||
m_measureResultWidget->setDeviceList(cameraNames);
|
||
}
|
||
}
|
||
}
|
||
|
||
void MainWindow::OnMeasureResult(const WheelMeasureResult &result)
|
||
{
|
||
// 直接调用处理方法(Presenter在后台线程,需要使用invokeMethod确保线程安全)
|
||
QMetaObject::invokeMethod(this, [this, result]() {
|
||
// 如果图像有效,显示在网格控件中
|
||
if (result.bImageValid && !result.image.isNull()) {
|
||
m_gridView->setImages(result.aliasName, result.image);
|
||
}
|
||
|
||
// 更新设备检测结果列表
|
||
if (m_measureResultWidget) {
|
||
// 获取第一个结果数据(如果有的话)
|
||
WheelMeasureData data;
|
||
if (!result.result.empty()) {
|
||
data = result.result[0];
|
||
}
|
||
m_measureResultWidget->updateDeviceResult(result.cameraName, data, result.bResultValid);
|
||
}
|
||
}, Qt::QueuedConnection);
|
||
}
|
||
|
||
void MainWindow::OnCameraConnected(const QString &cameraName)
|
||
{
|
||
QString message = QString("相机已连接: %1").arg(cameraName);
|
||
emit logUpdateRequested(message);
|
||
LOG_DEBUG("%s \n", message.toStdString().c_str());
|
||
}
|
||
|
||
void MainWindow::OnCameraDisconnected(const QString &cameraName)
|
||
{
|
||
QString message = QString("相机断开连接: %1").arg(cameraName);
|
||
emit logUpdateRequested(message);
|
||
LOG_DEBUG("%s \n", message.toStdString().c_str());
|
||
}
|
||
|
||
void MainWindow::OnWorkStatusChangedImpl(WorkStatus status)
|
||
{
|
||
QString statusStr;
|
||
switch (status) {
|
||
case WorkStatus::InitIng:
|
||
statusStr = "初始化中";
|
||
break;
|
||
case WorkStatus::Ready:
|
||
statusStr = "准备就绪";
|
||
break;
|
||
case WorkStatus::Working:
|
||
statusStr = "正在检测";
|
||
break;
|
||
case WorkStatus::Detecting:
|
||
statusStr = "正在检测";
|
||
break;
|
||
case WorkStatus::Completed:
|
||
statusStr = "检测完成";
|
||
break;
|
||
case WorkStatus::Error:
|
||
statusStr = "设备异常";
|
||
break;
|
||
default:
|
||
statusStr = "未知状态";
|
||
break;
|
||
}
|
||
|
||
QString message = QString("工作状态: %1").arg(statusStr);
|
||
emit logUpdateRequested(message);
|
||
LOG_DEBUG("%s \n", message.toStdString().c_str());
|
||
}
|
||
|
||
void MainWindow::OnErrorOccurred(const QString &errorMessage)
|
||
{
|
||
emit logUpdateRequested("错误: " + errorMessage);
|
||
LOG_ERROR("Error occurred: %s \n", errorMessage.toStdString().c_str());
|
||
}
|
||
|
||
void MainWindow::OnDeviceStatusChanged(const QString &deviceName, int deviceStatus)
|
||
{
|
||
if (m_deviceStatusWidget) {
|
||
m_deviceStatusWidget->updateDeviceStatus(deviceName, static_cast<DeviceStatus>(deviceStatus));
|
||
}
|
||
}
|
||
|
||
void MainWindow::OnClearMeasureData()
|
||
{
|
||
if (m_measureResultWidget) {
|
||
m_measureResultWidget->clearAllResults();
|
||
}
|
||
}
|
||
|
||
void MainWindow::setupVersionDisplay()
|
||
{
|
||
m_versionLabel = new QLabel(this);
|
||
|
||
// 使用与 Workpiece 一致的版本格式
|
||
QString versionText = QString("%1_%2%3%4%5%6%7")
|
||
.arg(GetWheelMeasureFullVersion())
|
||
.arg(YEAR)
|
||
.arg(MONTH, 2, 10, QChar('0'))
|
||
.arg(DAY, 2, 10, QChar('0'))
|
||
.arg(HOUR, 2, 10, QChar('0'))
|
||
.arg(MINUTE, 2, 10, QChar('0'))
|
||
.arg(SECOND, 2, 10, QChar('0'));
|
||
|
||
m_versionLabel->setText(versionText);
|
||
m_versionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
|
||
m_versionLabel->setStyleSheet("QLabel { color: rgb(239, 241, 245); font-size: 24px; margin-right: 10px; }");
|
||
|
||
statusBar()->addPermanentWidget(m_versionLabel);
|
||
|
||
LOG_INFO("Version display initialized: %s\n", versionText.toStdString().c_str());
|
||
}
|
||
|
||
void MainWindow::updateDetectionLog(const QString& message)
|
||
{
|
||
// 在UI线程中更新detect_log控件(QListView)
|
||
if (!m_logModel) return;
|
||
|
||
// 获取当前数据
|
||
QStringList logList = m_logModel->stringList();
|
||
|
||
// 获取当前时间
|
||
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss");
|
||
|
||
// 检查是否与最后一条消息相同
|
||
if (message == m_lastLogMessage && !logList.isEmpty()) {
|
||
// 相同消息,增加计数并替换最后一条
|
||
m_lastLogCount++;
|
||
QString lastEntry = QString("[%1] %2 (x%3)").arg(timestamp).arg(message).arg(m_lastLogCount);
|
||
logList.replace(logList.size() - 1, lastEntry);
|
||
} else {
|
||
// 新消息,重置计数
|
||
m_lastLogMessage = message;
|
||
m_lastLogCount = 1;
|
||
|
||
// 限制日志条数,最多保留200条
|
||
if (logList.size() >= 200) {
|
||
logList.removeFirst();
|
||
}
|
||
|
||
// 添加新的日志条目
|
||
QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);
|
||
logList.append(logEntry);
|
||
}
|
||
|
||
// 更新模型
|
||
m_logModel->setStringList(logList);
|
||
|
||
// 自动滚动到最底部
|
||
QModelIndex lastIndex = m_logModel->index(logList.size() - 1);
|
||
ui->detect_log->scrollTo(lastIndex);
|
||
}
|