一、前言
之前寫過的V2018版本的輸入法,本來已經很完善了,不打算更新升級了,最近有個朋友找我定制一個輸入法,需要高仿一個蘋果MAC電腦的輸入法,MAC操作系統的審美無疑是相當棒的,於是乎直接拿以前的輸入法高仿了一個,由於之前有做過輸入法這塊的開發,而且改進了四年,各種需求都遇到過,陸陸續續完善了很多年,所以這個高仿起來難度不大,而且要支持滑動選詞,直接擼代碼。
體驗地址:https://pan.baidu.com/s/1vIyEdB4QGo5OvxLYj7kq5g 提取碼:sysn
二、功能特點
- 未采用Qt系統層輸入法框架,獨創輸入切換機制。
- 純QWidget編寫,支持任何目標平台(親測windows、linux、嵌入式linux等),支持任意Qt版本(親測Qt4.6.0到Qt5.13),支持任意編譯器(親測mingw、gcc、msvc等),支持任意控件輸入包括網頁中的輸入控件。
- 調用極為方便,pri文件調用形式,只要改成文件包含即可,例如pro文件中寫 include($$PWD/input2019/input2019.pri)。
- 界面清晰簡潔,UI美觀友好,高仿IOS輸入法,非常適合觸摸設備。
- 頂部滑動選詞+彈出漢字面板選詞,支持滑動。
- 具有記憶功能,之前選中過的詞語首先顯示,支持單個拼音多個漢字,自動調整優先級。
- 具有造詞功能,可以直接打開文件文件寫入自定義詞組,最高級別顯示。
- 支持Qt程序嵌入的瀏覽器中的網頁中的文本框等控件的輸入。
- 界面大小隨意設置,采用布局自使用任何分辨率。
- 屬性控制數字輸入,例如需要文本框默認彈出的是數字則設置代碼 ui->txt->setProperty("flag", "number");
- 自由控制需要顯示輸入法和不需要顯示輸入法,當某些控件不需要彈出輸入法,只需要對應不需要彈出輸入法的控件設置屬性noinput為真即可。例如ui->txt->setProperty("noinput", true);
- 界面自適應屏幕大小,輸入法彈出位置為控件底部時,當超過桌面右邊或者底部時,自動調整位置。
- 實現了長按超過500毫秒重復執行按下的鍵的功能。例如長按退格鍵,不斷刪除。
- 英文、中文、數字字母、大小寫、特殊字符自由切換。
- 支持單拼、全拼、模糊拼音輸入,智能分頁算法,可任意翻頁查看漢字詞組。
- 默認自帶5種皮膚顏色,可隨意切換,用戶也可用QSS自定義皮膚。
- 谷歌內核的輸入法引擎,品質保證,字庫文件1MB,不依賴數據庫,資源占用低效率極高。支持模糊拼音,比如nh=你好。
- 可選windows專有版本,支持外部程序輸入,比如輸入到記事本、QQ聊天窗口等。
- 整個輸入法代碼行數1000行左右,非常小,不會對程序增加大小造成負擔。
- 代碼結構極為清晰,注釋詳細,非常容易閱讀和理解,同時也可以自行修改拓展自定義的需求。
三、效果圖
四、使用方法
- 將input2019整個目錄放到你的項目的pro同一級別目錄中。
- 在你的主程序的pro文件中加一行 include($$PWD/input2019/input2019.pri)
- 在你的程序的main函數中引入頭文件 #include "input2019/frminput2019.h"
- 在你的程序的main函數中加一行代碼 QApplication a(argc, argv);之后加 frmInput2019::Instance()->hide();
- 將源碼下的dict_pinyin.dat+dict_pinyin_user.dat字庫文件復制到可執行文件同一目錄。
五、其他說明
- 如果想設置更小的尺寸,可用setFixedSize。
- 源碼下的chinese_user.txt為自定義詞組文件,打開編輯即可,該文件放到可執行文件同一目錄即可。
- 如果是dialog窗體,請在dialog窗體exec前增加一行代碼,QDialog dialog;dialog.setWindowModality(Qt::WindowModal);否則會阻塞窗體消息。
- 在某些嵌入式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<QMouseEvent *>(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<QMouseEvent *>(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<QMouseEvent *>(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<QPushButton *> btns;
btns << ui->widgetLetter->findChildren<QPushButton *>();
btns << ui->widgetNumber->findChildren<QPushButton *>();
foreach (QPushButton *btn, btns) {
btn->setProperty("btnInput", true);
connect(btn, SIGNAL(clicked()), this, SLOT(btnClicked()));
}
//設置字母屬性
btns.clear();
btns << ui->widgetLetter1->findChildren<QPushButton *>();
btns << ui->widgetLetter2->findChildren<QPushButton *>();
foreach (QPushButton *btn, btns) {
btn->setProperty("btnLetter", true);
}
//設置所有按鈕輸入法不可用+長按自動重復事件
btns.clear();
btns << this->findChildren<QPushButton *>();
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();
}