280 lines
9.7 KiB
C++
280 lines
9.7 KiB
C++
#include "numkeydia.h"
|
||
#include "ui_numkeydia.h"
|
||
#include <QDebug>
|
||
#include <QFile>
|
||
#include <QKeyEvent>
|
||
#include <QMouseEvent>
|
||
#include <QPushButton>
|
||
#include <QShortcut>
|
||
#include <QTextStream>
|
||
|
||
// --- 私有类定义 ---
|
||
class NumKeyDiaPrivate {
|
||
Q_DECLARE_PUBLIC(NumKeyDia)
|
||
|
||
public:
|
||
NumKeyDiaPrivate(NumKeyDia *q) : q_ptr(q) {}
|
||
|
||
// 成员变量移入 D-pointer
|
||
Ui::NumKeyDia *ui = nullptr;
|
||
QString value;
|
||
QPoint m_point;
|
||
bool m_firstInputFlag = true;
|
||
|
||
enum class InputPage { NumberPage, SymbolPage };
|
||
InputPage m_inputPage = InputPage::NumberPage;
|
||
|
||
// 辅助函数
|
||
void setInputPage(InputPage page);
|
||
void refreshInputButtons();
|
||
void configureInputButton(QPushButton *button, const QString &label,
|
||
const QString &value, bool enabled, bool isModeSwitch = false);
|
||
void loadStyleSheet();
|
||
|
||
private:
|
||
NumKeyDia *q_ptr; // 反向指针
|
||
};
|
||
|
||
// --- 私有类逻辑实现 ---
|
||
|
||
void NumKeyDiaPrivate::loadStyleSheet() {
|
||
QFile file(QStringLiteral(":/commonWidget.qss"));
|
||
if (file.open(QFile::ReadOnly | QFile::Text)) {
|
||
QTextStream stream(&file);
|
||
q_ptr->setStyleSheet(stream.readAll());
|
||
}
|
||
}
|
||
|
||
void NumKeyDiaPrivate::setInputPage(InputPage page) {
|
||
m_inputPage = page;
|
||
refreshInputButtons();
|
||
}
|
||
|
||
void NumKeyDiaPrivate::refreshInputButtons() {
|
||
if (m_inputPage == InputPage::NumberPage) {
|
||
configureInputButton(ui->btn_1, "1", "1", true);
|
||
configureInputButton(ui->btn_2, "2", "2", true);
|
||
configureInputButton(ui->btn_3, "3", "3", true);
|
||
configureInputButton(ui->btn_4, "4", "4", true);
|
||
configureInputButton(ui->btn_5, "5", "5", true);
|
||
configureInputButton(ui->btn_6, "6", "6", true);
|
||
configureInputButton(ui->btn_7, "7", "7", true);
|
||
configureInputButton(ui->btn_8, "8", "8", true);
|
||
configureInputButton(ui->btn_9, "9", "9", true);
|
||
configureInputButton(ui->btn_sub, "#+=", "", true, true);
|
||
configureInputButton(ui->btn_0, "0", "0", true);
|
||
configureInputButton(ui->pushButton_dot, ".", ".", true);
|
||
} else {
|
||
configureInputButton(ui->btn_1, "/", "/", true);
|
||
configureInputButton(ui->btn_2, "-", "-", true);
|
||
configureInputButton(ui->btn_3, ":", ":", true);
|
||
QString deg = QChar(0x00B0);
|
||
configureInputButton(ui->btn_4, deg, deg, true);
|
||
configureInputButton(ui->btn_5, "'", "'", true);
|
||
configureInputButton(ui->btn_6, "\"", "\"", true);
|
||
configureInputButton(ui->btn_7, ".", ".", true);
|
||
configureInputButton(ui->btn_8, "_", "_", true);
|
||
configureInputButton(ui->btn_9, "@", "@", true);
|
||
configureInputButton(ui->btn_sub, "123", "", true, true);
|
||
configureInputButton(ui->btn_0, "+", "+", true);
|
||
configureInputButton(ui->pushButton_dot, ",", ",", true);
|
||
}
|
||
}
|
||
|
||
void NumKeyDiaPrivate::configureInputButton(QPushButton *button, const QString &label,
|
||
const QString &value, bool enabled, bool isModeSwitch) {
|
||
if (!button) return;
|
||
button->setText(label);
|
||
button->setEnabled(enabled);
|
||
button->setProperty("inputValue", value);
|
||
button->setProperty("isModeSwitch", isModeSwitch);
|
||
}
|
||
|
||
// --- 公共类实现 ---
|
||
|
||
NumKeyDia *NumKeyDia::singleton = nullptr;
|
||
|
||
NumKeyDia::NumKeyDia(QWidget *parent)
|
||
: QDialog(parent)
|
||
, d_ptr(new NumKeyDiaPrivate(this)) // 初始化 d_ptr
|
||
{
|
||
Q_D(NumKeyDia);
|
||
|
||
if (!singleton) singleton = this;
|
||
|
||
Q_INIT_RESOURCE(NumKeyBoard);
|
||
|
||
d->ui = new Ui::NumKeyDia;
|
||
d->ui->setupUi(this);
|
||
d->loadStyleSheet();
|
||
|
||
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||
d->ui->lineEdit_input->setFocusPolicy(Qt::StrongFocus);
|
||
|
||
auto btnList = d->ui->widget_input->findChildren<QPushButton *>();
|
||
for (auto *btn : btnList) {
|
||
connect(btn, &QPushButton::clicked, this, &NumKeyDia::btn_input_clicked);
|
||
btn->setFocusPolicy(Qt::NoFocus);
|
||
// 数字和符号按钮只负责屏幕点按输入,不参与对话框默认确认键竞争。
|
||
btn->setAutoDefault(false);
|
||
btn->setDefault(false);
|
||
}
|
||
|
||
// 物理键盘 Enter 应默认完成输入;其余操作按钮关闭 autoDefault,
|
||
// 避免 Qt 把确认键派发到退格/清空等最近获得焦点的按钮。
|
||
d->ui->btn_ok->setAutoDefault(true);
|
||
d->ui->btn_ok->setDefault(true);
|
||
d->ui->btn_back->setAutoDefault(false);
|
||
d->ui->btn_clear->setAutoDefault(false);
|
||
d->ui->btn_cancel->setAutoDefault(false);
|
||
|
||
auto *okReturnShortcut = new QShortcut(QKeySequence(Qt::Key_Return), this);
|
||
connect(okReturnShortcut, &QShortcut::activated, this, &NumKeyDia::on_btn_ok_clicked);
|
||
auto *okEnterShortcut = new QShortcut(QKeySequence(Qt::Key_Enter), this);
|
||
connect(okEnterShortcut, &QShortcut::activated, this, &NumKeyDia::on_btn_ok_clicked);
|
||
|
||
d->ui->toolButton_ico->installEventFilter(this);
|
||
d->ui->label_appInfo->installEventFilter(this);
|
||
d->ui->btn_clear->setText(QStringLiteral("<"));
|
||
|
||
d->setInputPage(NumKeyDiaPrivate::InputPage::NumberPage);
|
||
}
|
||
|
||
NumKeyDia::~NumKeyDia() {
|
||
if (singleton == this) singleton = nullptr;
|
||
// QScopedPointer 会自动删除 d_ptr,但 Ui 需手动销毁(如果没设置 parent)
|
||
Q_D(NumKeyDia);
|
||
delete d->ui;
|
||
}
|
||
|
||
NumKeyDia* NumKeyDia::instance() {
|
||
if(!singleton)
|
||
singleton = new NumKeyDia;
|
||
return singleton;
|
||
}
|
||
|
||
bool NumKeyDia::eventFilter(QObject *, QEvent *event) {
|
||
Q_D(NumKeyDia);
|
||
if (event->type() == QEvent::MouseButtonPress) {
|
||
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||
d->m_point = mouseEvent->globalPos() - frameGeometry().topLeft();
|
||
} else if (event->type() == QEvent::MouseMove) {
|
||
auto *mouseEvent = static_cast<QMouseEvent *>(event);
|
||
move(mouseEvent->globalPos() - d->m_point);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 数字键盘物理确认键入口:直接收口到 OK,避免 QLineEdit 或按钮默认行为把 Enter 解释成其它操作。
|
||
void NumKeyDia::keyPressEvent(QKeyEvent *event) {
|
||
if (event && (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)) {
|
||
on_btn_ok_clicked();
|
||
event->accept();
|
||
return;
|
||
}
|
||
|
||
QDialog::keyPressEvent(event);
|
||
}
|
||
|
||
void NumKeyDia::setValue(const QString &value) {
|
||
Q_D(NumKeyDia);
|
||
d->value = value;
|
||
d->setInputPage(NumKeyDiaPrivate::InputPage::NumberPage);
|
||
d->ui->lineEdit_input->setFocus();
|
||
d->ui->lineEdit_input->setText(value);
|
||
d->ui->lineEdit_input->setCursorPosition(d->ui->lineEdit_input->text().size());
|
||
d->m_firstInputFlag = true;
|
||
}
|
||
|
||
QString &NumKeyDia::getValue() {
|
||
Q_D(NumKeyDia);
|
||
return d->value;
|
||
}
|
||
|
||
void NumKeyDia::on_btn_ok_clicked() {
|
||
Q_D(NumKeyDia);
|
||
QString res = d->ui->lineEdit_input->text();
|
||
d->value = res.isEmpty() ? "0" : res;
|
||
closeKeyBoard();
|
||
}
|
||
|
||
void NumKeyDia::on_btn_cancel_clicked() {
|
||
closeKeyBoard();
|
||
}
|
||
|
||
void NumKeyDia::on_btn_clear_clicked() {
|
||
Q_D(NumKeyDia);
|
||
d->m_firstInputFlag = false;
|
||
int pos = d->ui->lineEdit_input->cursorPosition();
|
||
d->ui->lineEdit_input->setCursorPosition(qMax(0, pos - 1));
|
||
d->ui->lineEdit_input->setFocus();
|
||
}
|
||
|
||
void NumKeyDia::on_btn_back_clicked() {
|
||
Q_D(NumKeyDia);
|
||
d->m_firstInputFlag = false;
|
||
int index = d->ui->lineEdit_input->cursorPosition();
|
||
QString text = d->ui->lineEdit_input->text();
|
||
|
||
if (index > 0) {
|
||
text.remove(index - 1, 1);
|
||
d->ui->lineEdit_input->setText(text);
|
||
d->ui->lineEdit_input->setCursorPosition(index - 1);
|
||
}
|
||
d->ui->lineEdit_input->setFocus();
|
||
}
|
||
|
||
// 数字/符号输入按钮统一入口:根据 QLineEdit 当前光标和选区执行插入或替换。
|
||
// 设计意图:保持触屏软键盘与普通编辑框一致,首次输入不再隐式清空已有内容;
|
||
// 边界条件:无选区时在光标处插入,部分/全部选中时仅替换选区,按钮信号在 GUI 线程内处理。
|
||
void NumKeyDia::btn_input_clicked() {
|
||
Q_D(NumKeyDia);
|
||
auto *btn = qobject_cast<QPushButton *>(sender());
|
||
if (!btn) return;
|
||
|
||
if (btn->property("isModeSwitch").toBool()) {
|
||
auto nextPage = (d->m_inputPage == NumKeyDiaPrivate::InputPage::NumberPage)
|
||
? NumKeyDiaPrivate::InputPage::SymbolPage
|
||
: NumKeyDiaPrivate::InputPage::NumberPage;
|
||
d->setInputPage(nextPage);
|
||
d->ui->lineEdit_input->setFocus();
|
||
return;
|
||
}
|
||
|
||
const QString input = btn->property("inputValue").toString();
|
||
if (input.isEmpty()) return;
|
||
|
||
// 数字键盘按钮点击发生在 GUI 线程内,这里只同步读取当前 QLineEdit 的光标和选区状态,
|
||
// 不缓存 QObject 指针到异步流程,避免弹窗复用时选区状态被旧的首次输入标记误清空。
|
||
QString text = d->ui->lineEdit_input->text();
|
||
const int cursorIndex = d->ui->lineEdit_input->cursorPosition();
|
||
const bool hasSelection = d->ui->lineEdit_input->hasSelectedText();
|
||
const int selectionStart = d->ui->lineEdit_input->selectionStart();
|
||
const int selectedLength = d->ui->lineEdit_input->selectedText().size();
|
||
int insertIndex = cursorIndex;
|
||
|
||
if (hasSelection && selectionStart >= 0) {
|
||
// 有选区时仅替换选中范围;全选只是该规则的边界情况,不再依赖首次输入清空整行。
|
||
text.replace(selectionStart, selectedLength, input);
|
||
insertIndex = selectionStart;
|
||
} else {
|
||
// 无选区时保持普通编辑语义,在当前光标处插入,支持末尾追加和中间补字符。
|
||
text.insert(insertIndex, input);
|
||
}
|
||
|
||
d->m_firstInputFlag = false;
|
||
d->ui->lineEdit_input->setText(text);
|
||
d->ui->lineEdit_input->setFocus();
|
||
d->ui->lineEdit_input->setCursorPosition(insertIndex + input.size());
|
||
}
|
||
|
||
void NumKeyDia::closeKeyBoard() {
|
||
this->close();
|
||
}
|
||
|
||
namespace KeyBoard {
|
||
NumKeyDia *app() {
|
||
return NumKeyDia::instance();
|
||
}
|
||
}
|