FaceAccess/Linguist/keyboard/keyboard.cpp
2024-07-11 11:27:12 +08:00

595 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "keyboard.h"
#include "LineEditWithKeyboard.h"
#include <QEvent>
#include <QKeyEvent>
#include <QApplication>
#include <QDebug>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPainter>
#include <QMessageBox>
#include <QLabel>
#include <QTimer>
#include "UiConfig.h"
#include "UiTools.h"
//∧ len:3
#define KEY_COL_UNIT 4
#define KEY_HIDE " ▼ "
#define KEY_UPPER "△"
#define KEY_LOWER "∧"
#define KEY_SYMBOL " !@# "
#define KEY_SYMBOL_BACK "Back"
#define KEY_CN "中"
#define KEY_EN "En"
#define KEY_SPACE " Space "
#define KEY_BACKSPACE " ← "
#define KEY_ENTER "Enter"
//添加按键时需要注意不要超出头文件中定义的按键个数
static const char* lower_letter[] = {KEY_HIDE,
"1","2","3","4","5","6","7","8","9","0",
"q","w","e","r","t","y","u","i","o","p",
"a","s","d","f","g","h","j","k","l",
KEY_UPPER,"z","x","c","v","b","n","m",KEY_BACKSPACE,
KEY_CN,KEY_SYMBOL,KEY_SPACE,".",KEY_ENTER};
static const char* upper_letter[] = {KEY_HIDE,
"1","2","3","4","5","6","7","8","9","0",
"Q","W","E","R","T","Y","U","I","O","P",
"A","S","D","F","G","H","J","K","L",
KEY_LOWER,"Z","X","C","V", "B","N","M",KEY_BACKSPACE,
KEY_CN,KEY_SYMBOL,KEY_SPACE,".",KEY_ENTER};
static const char* cn_letter[] = {KEY_HIDE,
"-","=","[","]","\\",";","\'",",","?","/",
"!","@","#","$","%","^","&&","*","(",")",
"_","+","{","}","|",":","\"","<",">",
"~","·","×","÷","","","","",KEY_BACKSPACE,
KEY_CN,KEY_SYMBOL_BACK,KEY_SPACE,".",KEY_ENTER};
static const int COL_NUM_EACH_ROW[] = {1, 10, 10, 9, 9, 5};
static const int KEY_NUM = sizeof(lower_letter) / sizeof(*lower_letter);
#if 0
QPushButton* keyboard::m_btn[KEYS_QUANTITY_MAX] = {nullptr};
//chinese support
QVector<QPushButton*>* keyboard::m_btnsCnCdd = nullptr;
QLineEdit* keyboard::m_editCn = nullptr;
QPushButton* keyboard::m_btnCnPrePage = nullptr;
QPushButton* keyboard::m_btnCnNextPage = nullptr;
#endif
QFile* keyboard::m_pinyinFile = nullptr;
QRegExp* keyboard::m_regExp = nullptr;
QMultiMap<QString, QString>* keyboard::m_pinyinMap = nullptr;
keyboard* keyboard::m_instance = nullptr;
keyboard::enKeyboardStatus keyboard::m_status = enKeyboardNotGen;
keyboard* keyboard::GetInstance(QWidget *parent)
{
if(nullptr == m_instance && nullptr != parent){
qDebug() << "create keyboard";
m_status = enKeyboardCreating;
m_instance = new keyboard(parent);
m_instance->popup();
m_status = enKeyboardCreated;
}
if(parent){
QObject* recv = static_cast<QObject*>(parent);
if(m_instance->receiver() != recv){
m_instance->setReceiver(static_cast<QObject*>(parent));
}
}
return m_instance;
}
void keyboard::close()
{
if(m_instance){
if(enKeyboardCreated == m_status){
qDebug() << "close keyboard";
m_instance->slotClose();
}
else {
qDebug() << "close keyboard later";
QTimer::singleShot(500, m_instance, SLOT(slotClose()));
}
}
}
void keyboard::preLoad()
{
if(nullptr == m_instance){
qDebug() << "keyboard pre load";
keyboard();
}
}
keyboard::keyboard(QWidget *parent) : QDialog(parent), m_receiver(nullptr)
{
m_status = enKeyboardNotGen;
m_editCn = nullptr;
m_btnCnPrePage = nullptr;
m_btnCnNextPage = nullptr;
setReceiver(static_cast<QObject*>(parent));
qDebug() << "keyboard()";
QPalette palette(this->palette());
palette.setColor(QPalette::ButtonText, Qt::white);
const int chooseBtnHeight = UiConfig::GetInstance()->getUiWidth() / 16;
m_btnsCnCdd.resize(8);
QHBoxLayout* hLayoutCdd = new QHBoxLayout();
for(auto &b : m_btnsCnCdd)
{
//if(nullptr == b){
b = new QPushButton();
b->setFixedWidth(static_cast<int>(UiConfig::GetInstance()->getUiWidth()) / m_btnsCnCdd.size());
b->setPalette(palette);
b->setStyleSheet("background-color: transparent;");
b->setFlat(true);
b->setFixedSize(chooseBtnHeight * 2, chooseBtnHeight);
connect(b, SIGNAL(clicked(bool)), this, SLOT(slotCandidateBtnClicked()));
//}
hLayoutCdd->addWidget(b, 1, Qt::AlignLeft);
}
hLayoutCdd->addWidget(new QLabel(this), 100, Qt::AlignRight);
if(nullptr == m_editCn){
m_editCn = new QLineEdit();
m_editCn->setReadOnly(true);
palette.setColor(QPalette::Text, Qt::white);
m_editCn->setPalette(palette);
m_editCn->setFixedSize(chooseBtnHeight * 4, chooseBtnHeight);
m_editCn->setStyleSheet("background:transparent;border-width:0;border-style:outset");
}
if(nullptr == m_btnCnPrePage){
m_btnCnPrePage = new QPushButton();
m_btnCnPrePage->setText("");
m_btnCnPrePage->setPalette(palette);
m_btnCnPrePage->setFixedSize(chooseBtnHeight * 2, chooseBtnHeight);
m_btnCnPrePage->setStyleSheet("background-color: rgb(57, 53, 50);");
connect(m_btnCnPrePage, SIGNAL(clicked(bool)), this, SLOT(slotPageTurnBtnClicked()));
}
if(nullptr == m_btnCnNextPage){
m_btnCnNextPage = new QPushButton();
m_btnCnNextPage->setText("");
m_btnCnNextPage->setPalette(palette);
m_btnCnNextPage->setFixedSize(chooseBtnHeight * 2, chooseBtnHeight);
m_btnCnNextPage->setStyleSheet("background-color: rgb(57, 53, 50);");
connect(m_btnCnNextPage, SIGNAL(clicked(bool)), this, SLOT(slotPageTurnBtnClicked()));
}
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->addLayout(hLayoutCdd);
QHBoxLayout* hLayoutLine[ROW_NUM] = {nullptr};
for(int i = 0; i < ROW_NUM; i ++)
{
hLayoutLine[i] = new QHBoxLayout();
hLayoutLine[i]->setMargin(0);
//hLayoutLine[i]->setSpacing(15);
if(i < ROW_NUM - 1){ //最后一列不需要
hLayoutLine[i]->addStretch(0);
}
}
hLayoutLine[0]->addWidget(m_editCn, 2, Qt::AlignLeft);
hLayoutLine[0]->addWidget(m_btnCnPrePage, 1, Qt::AlignLeft);
hLayoutLine[0]->addWidget(m_btnCnNextPage, 1, Qt::AlignLeft);
int col = 0;
int row = 0;
int colMax = 0; //最大列数
int colTaken = 0;
palette.setColor(QPalette::ButtonText, Qt::white);
QFont ft;
ft.setPointSize(UiConfig::GetInstance()->isRkDevice() ? 12 : 24);
for(int i = 0; i < KEY_NUM; i ++)
{
m_btn[i] = new QPushButton(this);
m_btn[i]->setPalette(palette);
m_btn[i]->setStyleSheet("background-color: rgb(57, 53, 50);");
m_btn[i]->setFocusPolicy(Qt::NoFocus);
m_btn[i]->setFont(ft);
//m_btn[i]->setFlat(true);
connect(m_btn[i], SIGNAL(clicked(bool)), this, SLOT(slotBtnClicked()));
connect(m_btn[i], SIGNAL(pressed()), this, SLOT(slotBtnPressed()));
connect(m_btn[i], SIGNAL(released()), this, SLOT(slotBtnReleased()));
int btnCol = (strlen(lower_letter[i]) / KEY_COL_UNIT) + 1;
hLayoutLine[row]->addWidget(m_btn[i]);
colTaken += btnCol;
col++;
if(COL_NUM_EACH_ROW[row] == col)
{
col = 0;
row++;
if(colTaken > colMax){
colMax = colTaken;
}
colTaken = 0;
}
}
int unit = (UiConfig::GetInstance()->getUiWidth() - ((colMax - 1) * SPACE)) / colMax;
for(int i = 0; i < KEY_NUM; i ++)
{
int btnCol = (strlen(lower_letter[i]) / KEY_COL_UNIT) + 1;
m_btn[i]->setFixedSize(unit * btnCol, static_cast<int>(unit));
}
for(int i = 0; i < ROW_NUM; i ++)
{
if(i > 0 && i < ROW_NUM - 1){ //首行和尾行不需要
hLayoutLine[i]->addStretch(0);
}
vLayout->addLayout(hLayoutLine[i]);
}
vLayout->setSpacing(5);//10
vLayout->setContentsMargins(0, 10, 0, 10);
setLayout(vLayout);
setFocusPolicy(Qt::NoFocus);
setWindowFlags( Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool | Qt::WindowDoesNotAcceptFocus);
if(nullptr == m_pinyinFile){
m_pinyinFile = new QFile();
m_pinyinFile->setFileName(":/res/inputCn/pinyin-utf8"); //设置QFile的名称
if(!m_pinyinFile->open(QIODevice::ReadOnly) ){ //只读模式,打开文件
QMessageBox::warning(nullptr, "pinyin file", "can't load"); //打开失败,则报错
}
}
if(nullptr == m_regExp){
m_regExp = new QRegExp();
m_regExp->setCaseSensitivity(Qt::CaseSensitive); //设置正则表达式的参数,Qt::CaseInsensitive,大小写敏感
m_regExp->setPattern(QString("([a-z]+)")); //获得正则本身,获取a-z
}
if(nullptr == m_pinyinMap){
m_pinyinMap = new QMultiMap<QString, QString>();
while(!m_pinyinFile->atEnd())
{
QByteArray lineData = m_pinyinFile->readLine(); //读取一行
//qDebug() << "line:" << QString(data.data());
int ret = m_regExp->indexIn(QString(lineData.data()), 0, QRegExp::CaretAtZero); //进行匹配如果成功则返回index不成功返回-1 data.data()是读取到的一行数据,返回值应该是匹配到的位置
//qDebug() << "ret =" << ret;
//qDebug() << "m_regExp.cap(1):" << m_regExp.cap(1) << "QString(lineData.data()).left(ret):" << QString(lineData.data()).left(ret);
if("\r\n" != QString(lineData.data()).left(ret)){
m_pinyinMap->insert(m_regExp->cap(1), QString(lineData.data()).left(ret)); //将mmap对象的成员初始化;key是字母value是行字
}
}
m_pinyinFile->close();
}
setInputMethodCn(false);
setAttribute(Qt::WA_DeleteOnClose);
setFixedWidth(UiConfig::GetInstance()->getUiWidth());
m_labelTip = new QLabel(this);
m_labelTip->setFixedSize(unit, static_cast<int>(unit * 1.5));
m_labelTip->setFont(ft);
m_labelTip->setStyleSheet("background-color: rgb(15, 116, 248);");
m_labelTip->setAlignment(Qt :: AlignCenter);
m_labelTip->hide();
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(slotTimer()));
shiftkeyboard(2);
}
keyboard::~keyboard()
{
qDebug() << "~keyboard()";
m_instance = nullptr;
}
void keyboard::setInputMethodCn(bool setCn)
{
if(setCn){
m_editCn->clear();
m_editCn->show();
}
else {
for(auto &b : m_btnsCnCdd){
b->hide();
}
m_editCn->hide();
m_btnCnPrePage->hide();
m_btnCnNextPage->hide();
}
isInputMethodCn = setCn;
}
void keyboard::shiftkeyboard(int caps)
{
const char** letter = nullptr;
if(0 == caps){
letter = upper_letter;
}
else if (1 == caps) {
letter = cn_letter;
}
else if (2 == caps) {
letter = lower_letter;
}
for(int i = 0; i < KEY_NUM; i ++){
if(m_btn[i]->text().isEmpty()){ //if has icon, clear first
m_btn[i]->setIcon(QIcon());
}
if(QString(letter[i]) == KEY_BACKSPACE){
m_btn[i]->setText("");
//m_btn[i]->setIcon(QIcon(":res/image/pwd_del_large.png"));
setButtonBackImage(m_btn[i], ":res/image/pwd_del_large_black.png", m_btn[i]->height() / 2 , static_cast<int>(m_btn[i]->height() * 0.34));//w/2*(24/35)
m_btn[i]->setStyleSheet("background-color: rgb(57, 53, 50);");
m_btn[i]->setProperty("key", letter[i]);
}
else if(QString(letter[i]) == KEY_UPPER){
m_btn[i]->setText("");
//m_btn[i]->setIcon(QIcon(":res/image/shift_large.png"));
setButtonBackImage(m_btn[i], ":res/image/shift_large_black.png", m_btn[i]->height() / 2, m_btn[i]->height() / 2);
m_btn[i]->setStyleSheet("background-color: rgb(57, 53, 50);");
m_btn[i]->setProperty("key", letter[i]);
}
else if(QString(letter[i]) == KEY_LOWER){
m_btn[i]->setText("");
//m_btn[i]->setIcon(QIcon(":res/image/shift_lock_large.png"));
setButtonBackImage(m_btn[i], ":res/image/shift_lock_large_black.png", m_btn[i]->height() / 2, m_btn[i]->height() / 2);
m_btn[i]->setStyleSheet("background-color: rgb(57, 53, 50);");
m_btn[i]->setProperty("key", letter[i]);
}
else{
m_btn[i]->setText(letter[i]);
}
}
}
void keyboard::slotBtnPressed()
{
//qDebug() << "unit:" << dynamic_cast<QPushButton*>(sender())->width();
QPushButton* btn = dynamic_cast<QPushButton*>(sender());
if(btn){
for(int i = 0; i < KEY_NUM - 4; i ++){
if(btn == m_btn[i]){
int len = strlen(btn->text().toStdString().c_str());
if(len > 0 && len <= 3){
m_labelTip->setText(btn->text());
m_labelTip->move(btn->x(), btn->y() - m_labelTip->height());
m_labelTip->show();
}
}
}
m_curPressedKey = btn;
m_timer->start(500);
}
}
void keyboard::slotBtnReleased()
{
m_labelTip->hide();
m_timer->stop();
m_curPressedKey = nullptr;
}
void keyboard::slotBtnClicked()
{
for(int i = 0; i < KEY_NUM; i ++){
if(sender() == m_btn[i]){
keyPress(m_btn[i]);
break;
}
}
}
void keyboard::slotPageTurnBtnClicked()
{
if(sender() == m_btnCnPrePage){
changePage(--m_iCddPageIndex);
}
else if (sender() == m_btnCnNextPage) {
changePage(++m_iCddPageIndex);
}
}
void keyboard::slotCandidateBtnClicked()
{
for(auto &b : m_btnsCnCdd){
if(sender() == b){
emit signalWordChoose(b->text());
m_editCn->clear();
for(auto &btn : m_btnsCnCdd){
btn->hide();
}
m_btnCnPrePage->hide();
m_btnCnNextPage->hide();
break;
}
}
}
void keyboard::slotClose()
{
done(0);
}
void keyboard::slotTimer()
{
if(m_curPressedKey){
m_timer->start(100);
keyPress(m_curPressedKey);
}
}
void keyboard::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.fillRect(this->rect(), QColor(0, 0, 0,/*0x1f, 0x28, 0x41,*/ 255)); //QColor最后一个参数代表背景的透明度
return QWidget::paintEvent(event);
}
void keyboard::keyPress(const QPushButton* btn)
{
if(!btn){
return;
}
QString str = btn->text();
if(str.isEmpty()){
str = btn->property("key").toString();
}
QKeyEvent* event = nullptr;
if(str == KEY_BACKSPACE){
event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
}else if(str == KEY_ENTER){
event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
}else if(str == KEY_UPPER){
shiftkeyboard(0);//upper_letter
}else if(str == KEY_LOWER){
shiftkeyboard(2);//lower_letter
}else if(str == KEY_CN){
shiftkeyboard(2);
for(int j = 0; j < KEY_NUM; j ++){
if(m_btn[j]->text() == KEY_CN){
m_btn[j]->setText(KEY_EN);
}
}
setInputMethodCn(true);
}else if(str == KEY_EN){
for(int j = 0; j < KEY_NUM; j ++){
if(m_btn[j]->text() == KEY_EN){
m_btn[j]->setText(KEY_CN);
}
}
setInputMethodCn(false);
}else if(str == KEY_HIDE){
slotClose();
}else if(str == KEY_SYMBOL){
shiftkeyboard(1);
}else if(str == KEY_SYMBOL_BACK){
shiftkeyboard(2);
}else{
if(str == KEY_SPACE)
str = " ";
else if (str == "&&") {
str = "&";
}
event = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, str);
}
if(isInputMethodCn){
if(str >= "a" && str <= "z"){ //qt5.5 does not have isLower() func
m_editCn->setText(m_editCn->text() + str);
matching(m_editCn->text());
}else if (str == KEY_BACKSPACE) {
if("" != m_editCn->text()){
m_editCn->setText(m_editCn->text().remove(m_editCn->text().length() - 1, 1));
matching(m_editCn->text());
}else{
QApplication::sendEvent(m_receiver, new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier));
}
}else {
if(nullptr != event){
QApplication::sendEvent(m_receiver, event);
}
}
}else{
if(nullptr != event){
QApplication::sendEvent(m_receiver, event);
}
}
static_cast<QWidget*>(m_receiver)->setFocus();
}
void keyboard::popup(const int posToBottom)
{
qDebug() << "keyboard popup";
show();
move(0, UiConfig::GetInstance()->getUiHeight() - height() - posToBottom);
#if 0
qDebug() << "child: " << children().size();
QObjectList& childList = const_cast<QObjectList&>(children());
childList.clear();
qDebug() << "****************child: " << children().size();
#endif
}
void keyboard::matching(const QString& userInput)
{
//qDebug() << "input:" << userInput;
QList<QString> list = m_pinyinMap->values(userInput);
int foundQty = list.size();
//qDebug() << "list:" << foundQty;
m_pinyinList.clear();
#if 0
for (QList<QString>::const_reverse_iterator itr = list.rbegin(); itr != list.rend(); ++itr){
m_pinyinList.append(*itr);
}
#else
for(int i=0; i<foundQty; i++){
m_pinyinList.append(list.takeLast());
}
#endif
//for(auto &str : m_pinyinList)
//qDebug() << "-------:" << str;
changePage(0);
m_iCddPageIndex = 0;
}
void keyboard::changePage(int index)
{
for(auto &b : m_btnsCnCdd){
b->hide();
}
const int matchQty = m_pinyinList.size();
//qDebug() << "matchQty:" << matchQty;
if(matchQty){
const int btnQty = m_btnsCnCdd.size();
const int showQty = matchQty - (index * btnQty) > btnQty ? btnQty : matchQty - (index * btnQty);
for(int i=0; i<showQty; i++)
{
if(m_btnsCnCdd.at(i)->isHidden()){
m_btnsCnCdd.at(i)->show();
}
m_btnsCnCdd.at(i)->setText(m_pinyinList.value(i + index * btnQty));
//qDebug() << "word:" << m_pinyinList.value(i + index * btnQty);
}
m_btnCnPrePage->show();
m_btnCnNextPage->show();
m_btnCnPrePage->setEnabled(0 != index);
m_btnCnNextPage->setEnabled( (showQty == btnQty) && (matchQty - ((index + 1) * btnQty)) );
}
else {
m_btnCnPrePage->hide();
m_btnCnNextPage->hide();
}
}
void keyboard::setReceiver(QObject* receiver)
{
if(nullptr != receiver){
if(m_receiver){
disconnect(this, SIGNAL(signalWordChoose(QString)), m_receiver, SLOT(slotWordChoose(QString)));
}
m_receiver = receiver;
connect(this, SIGNAL(signalWordChoose(QString)), m_receiver, SLOT(slotWordChoose(QString)));
}
}
QObject* keyboard::receiver() const
{
return m_receiver;
}