FaceAccess/Linguist/keyboard/keyboard.cpp

595 lines
18 KiB
C++
Raw Normal View History

2024-07-11 11:27:12 +08:00
#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;
}