一、前言
之前写过的V2018版本的输入法,本来已经很完善了,不打算更新升级了,最近有个朋友找我定制一个输入法,需要高仿一个苹果MAC电脑的输入法,MAC操作系统的审美无疑是相当棒的,于是乎直接拿以前的输入法高仿了一个,由于之前有做过输入法这块的开发,而且改进了四年,各种需求都遇到过,陆陆续续完善了很多年,所以这个高仿起来难度不大,而且要支持滑动选词,直接撸代码。
二、功能特点
1. 未采用Qt系统层输入法框架,独创输入切换机制。
2. 纯QWidget编写,支持任何目标平台(亲测windows、linux、嵌入式linux等),支持任意Qt版本(亲测Qt4.6.0到Qt5.13),支持任意编译器(亲测mingw、gcc、msvc等),支持任意控件输入包括网页中的输入控件。
3. 调用极为方便,pri文件调用形式,只要改成文件包含即可,例如pro文件中写 include($$PWD/input2019/input2019.pri)。
4. 界面清晰简洁,UI美观友好,高仿IOS输入法,非常适合触摸设备。
5. 顶部滑动选词+弹出汉字面板选词,支持滑动。
6. 具有记忆功能,之前选中过的词语首先显示,支持单个拼音多个汉字,自动调整优先级。
7. 具有造词功能,可以直接打开文件文件写入自定义词组,最高级别显示。
8. 支持Qt程序嵌入的浏览器中的网页中的文本框等控件的输入。
9. 界面大小随意设置,采用布局自使用任何分辨率。
10. 属性控制数字输入,例如需要文本框默认弹出的是数字则设置代码 ui->txt->setProperty("flag", "number");
11. 自由控制需要显示输入法和不需要显示输入法,当某些控件不需要弹出输入法,只需要对应不需要弹出输入法的控件设置属性noinput为真即可。例如ui->txt->setProperty("noinput", true);
12. 界面自适应屏幕大小,输入法弹出位置为控件底部时,当超过桌面右边或者底部时,自动调整位置。
13. 实现了长按超过500毫秒重复执行按下的键的功能。例如长按退格键,不断删除。
14. 英文、中文、数字字母、大小写、特殊字符自由切换。
15. 支持单拼、全拼、模糊拼音输入,智能分页算法,可任意翻页查看汉字词组。
16. 默认自带5种皮肤颜色,可随意切换,用户也可用QSS自定义皮肤。
17. 谷歌内核的输入法引擎,品质保证,字库文件1MB,不依赖数据库,资源占用低效率极高。支持模糊拼音,比如nh=你好。
18. 可选windows专有版本,支持外部程序输入,比如输入到记事本、QQ聊天窗口等。
19. 整个输入法代码行数1000行左右,非常小,不会对程序增加大小造成负担。
20. 代码结构极为清晰,注释详细,非常容易阅读和理解,同时也可以自行修改拓展自定义的需求。
三、效果图
四、使用方法
1. 将input2019整个目录放到你的项目的pro同一级别目录中。
2. 在你的主程序的pro文件中加一行 include($$PWD/input2019/input2019.pri)
3. 在你的程序的main函数中引入头文件 #include "input2019/frminput2019.h"
4. 在你的程序的main函数中加一行代码 QApplication a(argc, argv);之后加 frmInput2019::Instance()->hide();
5. 将源码下的dict_pinyin.dat+dict_pinyin_user.dat字库文件复制到可执行文件同一目录。
五、其他说明
1. 如果想设置更小的尺寸,可用setFixedSize。
2. 源码下的chinese_user.txt为自定义词组文件,打开编辑即可,该文件放到可执行文件同一目录即可。
3. 如果是dialog窗体,请在dialog窗体exec前增加一行代码,QDialog dialog;dialog.setWindowModality(Qt::WindowModal);否则会阻塞窗体消息。
4. 在某些嵌入式linux系统中,如果没有带有XCB,则输入法需要先show再hide一次,然后输入法才能起作用,不然程序会崩溃。
六、核心代码
bool frmInput2019::eventFilter(QObject *watched, QEvent *event){ if (watched == this) { //处理自身拖动 static QPoint mousePoint; static bool mousePressed = false; QMouseEvent *mouseEvent = static_cast(event); //按下的时候记住坐标,移动到鼠标松开的位置 if (event->type() == QEvent::MouseButtonPress) { if (mouseEvent->button() == Qt::LeftButton) { mousePressed = true; mousePoint = mouseEvent->globalPos() - this->pos(); return true; } } else if (event->type() == QEvent::MouseButtonRelease) { mousePressed = false; return true; } else if (event->type() == QEvent::MouseMove) { if (mousePressed && (mouseEvent->buttons() && Qt::LeftButton && position != "bottom")) { this->move(mouseEvent->globalPos() - mousePoint); this->update(); return true; } } } else if (watched == ui->labMore) { if (event->type() == QEvent::MouseButtonPress) { if (inputType == "chinese" && !upper && labCn.first()->isEnabled()) { if (!ui->widgetChinese->isVisible()) { ui->widgetLetter->setVisible(false); ui->widgetNumber->setVisible(false); ui->widgetChinese->setVisible(true); } else { ui->widgetLetter->setVisible(true); ui->widgetNumber->setVisible(false); ui->widgetChinese->setVisible(false); } //重新设置图标 QString strMore = ui->widgetMore->isVisible() ? "up" : "down"; ui->labMore->setPixmap(QString(":/image/btn_%1_%2.png").arg(strMore).arg(iconType)); return true; } } } else if (watched == ui->labType) { if (event->type() == QEvent::MouseButtonPress) { if (inputType == "english") { setInputType("chinese"); } else if (inputType == "chinese") { setInputType("english"); } } } else if (watched == ui->labType2) { if (event->type() == QEvent::MouseButtonPress) { setInputType("english"); } } else if (watched == ui->widgetCn) { //没有汉字或者按下的地方没有汉字或者当前汉字标签个数过少都不用继续 if (!labCn.first()->isEnabled() || lastText.isEmpty()) { return false; } //记住最后按下拖动的时间,过短则认为是滑动,启动滑动动画 static bool pressed = false; static QPoint lastPos = QPoint(); static QDateTime lastTime = QDateTime::currentDateTime(); QMouseEvent *mouseEvent = static_cast(event); if (event->type() == QEvent::MouseButtonPress) { pressed = true; lastPos = mouseEvent->pos(); animationCn->stop(); lastTime = QDateTime::currentDateTime(); } else if (event->type() == QEvent::MouseButtonRelease) { pressed = false; if (lastPos != mouseEvent->pos()) { //判断当前时间和鼠标按下事件比较,时间短则说明是滑动 QDateTime now = QDateTime::currentDateTime(); if (lastTime.msecsTo(now) < 600) { //可以改变下面的值来调整幅度 bool moveleft = (mouseEvent->pos().x() - lastPos.x()) < 0; int offset = moveleft ? 350 : -350; int value = ui->scrollAreaCn->horizontalScrollBar()->value(); animationCn->setStartValue(value); animationCn->setEndValue(value + offset); animationCn->start(); } } } else if (event->type() == QEvent::MouseMove) { if (pressed && labCn.first()->isEnabled()) { //计算滑过的距离 bool moveleft = (mouseEvent->pos().x() - lastPos.x()) < 0; int offset = moveleft ? 5 : -5; int value = ui->scrollAreaCn->horizontalScrollBar()->value(); ui->scrollAreaCn->horizontalScrollBar()->setValue(value + offset); return true; } } } else if (watched == ui->widgetMore) { //没有汉字或者按下的地方没有汉字或者当前汉字标签个数过少都不用继续 if (!labMore.first()->isEnabled() || lastText.isEmpty()) { return false; } //记住最后按下拖动的时间,过短则认为是滑动,启动滑动动画 static bool pressed = false; static QPoint lastPos = QPoint(); static QDateTime lastTime = QDateTime::currentDateTime(); QMouseEvent *mouseEvent = static_cast(event); if (event->type() == QEvent::MouseButtonPress) { pressed = true; lastPos = mouseEvent->pos(); animationMore->stop(); lastTime = QDateTime::currentDateTime(); } else if (event->type() == QEvent::MouseButtonRelease) { pressed = false; if (lastPos != mouseEvent->pos()) { //判断当前时间和鼠标按下事件比较,时间短则说明是滑动 QDateTime now = QDateTime::currentDateTime(); if (lastTime.msecsTo(now) < 600) { //可以改变下面的值来调整幅度 bool movebottom = (mouseEvent->pos().y() - lastPos.y()) < 0; int offset = movebottom ? 150 : -150; int value = ui->scrollAreaMore->verticalScrollBar()->value(); animationMore->setStartValue(value); animationMore->setEndValue(value + offset); animationMore->start(); } } } else if (event->type() == QEvent::MouseMove) { if (pressed && labMore.first()->isEnabled()) { //计算滑过的距离 bool movebottom = (mouseEvent->pos().y() - lastPos.y()) < 0; int offset = movebottom ? 5 : -5; int value = ui->scrollAreaMore->verticalScrollBar()->value(); ui->scrollAreaMore->verticalScrollBar()->setValue(value + offset); return true; } } } else if (watched->inherits("QLabel")) { QLabel *lab = (QLabel *)watched; if (!upper && inputType == "chinese") { if (lab->property("labCn").toBool()) { //记住最后按下的滚动条位置,如果滚动条一直没有变化则认为单击了标签 static int lastPosition = 0; if (event->type() == QEvent::MouseButtonPress) { lastPosition = ui->scrollAreaCn->horizontalScrollBar()->value(); lastText = lab->text(); } else if (event->type() == QEvent::MouseButtonRelease) { if (lastPosition == ui->scrollAreaCn->horizontalScrollBar()->value() && !lastText.isEmpty()) { insertValue(lab->text()); clearChinese(); } } } else if (lab->property("labMore").toBool()) { //记住最后按下的滚动条位置,如果滚动条一直没有变化则认为单击了标签 static int lastPosition = 0; if (event->type() == QEvent::MouseButtonPress) { lastPosition = ui->scrollAreaMore->verticalScrollBar()->value(); lastText = lab->text(); } else if (event->type() == QEvent::MouseButtonRelease) { if (lastPosition == ui->scrollAreaMore->verticalScrollBar()->value() && !lastText.isEmpty()) { insertValue(lab->text()); clearChinese(); } } } } } else { if (event->type() == QEvent::MouseButtonPress) { if (currentWidget != 0) { if (!isVisible()) { showPanel(); } } else { if (isVisible()) { hidePanel(); } } } } return QWidget::eventFilter(watched, event);}void frmInput2019::initForm(){#if (QT_VERSION > QT_VERSION_CHECK(5,0,0)) setWindowFlags(Qt::Tool | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);#else setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);#endif currentWidget = 0; upper = false; number = false; onlyControl = false; autoHide = false; columnCount = 8; maxCount = 256; dbPath = qApp->applicationDirPath(); //绑定按钮到事件 QList btns; btns << ui->widgetLetter->findChildren(); btns << ui->widgetNumber->findChildren(); foreach (QPushButton *btn, btns) { btn->setProperty("btnInput", true); connect(btn, SIGNAL(clicked()), this, SLOT(btnClicked())); } //设置字母属性 btns.clear(); btns << ui->widgetLetter1->findChildren(); btns << ui->widgetLetter2->findChildren(); foreach (QPushButton *btn, btns) { btn->setProperty("btnLetter", true); } //设置所有按钮输入法不可用+长按自动重复事件 btns.clear(); btns << this->findChildren(); foreach (QPushButton *btn, btns) { btn->setFocusPolicy(Qt::NoFocus); btn->setProperty("noinput", true); btn->setAutoRepeat(true); btn->setAutoRepeatDelay(500); } //默认最大生成256个,添加到顶部滚动区域中 for (int i = 0; i < maxCount; i++) { QLabel *lab = new QLabel; lab->setProperty("labCn", true); lab->setEnabled(false); ui->layout->addWidget(lab); labCn << lab; } //默认最大生成256个,添加到更多滚动区域中 int row = 0; int column = 0; for (int i = 0; i < maxCount; i++) { QLabel *lab = new QLabel; lab->setProperty("labMore", true); lab->setEnabled(false); lab->setAlignment(Qt::AlignCenter); lab->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); ui->gridLayout->addWidget(lab, row, column); labMore << lab; column++; if (column >= columnCount) { row++; column = 0; } } ui->lab1->setEnabled(false); ui->lab2->setEnabled(false); ui->labPY->setEnabled(false); ui->labMore->setEnabled(false); //输入法面板的字体名称和按钮字体大小即汉字区域字体大小 setFontInfo(this->font().family(), 11, 10); //图标固定大小 setIconSize(20, 20); //按钮之间的间隔 setSpacing(6); //顶部汉字区域高度 setTopHeight(40); //输入法面板的显示位置 control--显示在对应输入框的正下方 bottom--填充显示在底部 center--窗体居中显示 setPosition("control"); //输入法模式 english--英文模式 chinese--中文模式 number--数字特殊字符模式 setInputType("english"); //输入法面板的样式 black--黑色 blue--淡蓝色 brown--灰黑色 gray--灰色 silvery--银色 setStyleName("black"); //定义动画产生平滑数值 animationCn = new QPropertyAnimation(ui->scrollAreaCn->horizontalScrollBar(), "value"); animationCn->setEasingCurve(QEasingCurve::OutCirc); animationCn->setDuration(500); animationMore = new QPropertyAnimation(ui->scrollAreaMore->verticalScrollBar(), "value"); animationMore->setEasingCurve(QEasingCurve::OutCirc); animationMore->setDuration(500);}void frmInput2019::init(){ if (onlyControl) { ui->labPY->setVisible(false); this->installEventFilter(this); ui->labType->installEventFilter(this); ui->labType2->installEventFilter(this); ui->labMore->installEventFilter(this); ui->widgetCn->installEventFilter(this); ui->widgetMore->installEventFilter(this); foreach (QLabel *lab, labCn) { lab->installEventFilter(this); } foreach (QLabel *lab, labMore) { lab->installEventFilter(this); } } else { //绑定全局改变焦点信号槽 connect(qApp, SIGNAL(focusChanged(QWidget *, QWidget *)), this, SLOT(focusChanged(QWidget *, QWidget *))); qApp->installEventFilter(this); } py.open(dbPath); readChinese();}