开发CuteMySQL/CuteSqlite开源客户端的时候,需要使用Scintilla编辑器,来高亮显示SQL语句,作为C/C++领域最成熟稳定又小巧的开源编辑器,Scintilla提供了强大的功能,wxWidgets对Scintilla进行包装后的是控件类:wxStyledTextCtrl。下面我们用正确的姿势来打开使用它。
先看下效果:
我们对该SQL编辑器的需求是:
1.显示行号
2.SQL语法高亮
3.输入SQL智能提示
4.适配DARK风格
好的,我们直接看下源码,程序员还是代码说话,其他都是废话,不多说了。
一、声明wxStyledTextCtrl的子类QSqlEditor
#pragma once
#include <wx/stc/stc.h>
#include <vector>
class QSqlEditor : public wxStyledTextCtrl
{
...
// 重要函数一:初始化编辑器,适配SQL语法,高亮,行号等
void setupSqlSyntax(int nSize, const char* face);
// 重要函数二:自动弹出智能提示,参数tags为表名,视图,字段名,函数,存储过程等
void autoShow(const std::vector<std::string> & tags);
// 重要函数三:自动替换光标当前单词
void autoReplaceWord();
private:
...
...
};
二、初始化编辑器,适配SQL语法
void QSqlEditor::setupSqlSyntax(int nSize, const char* face)
{
// - lex setup (lex语法解释器配置)
SetLexer(wxSTC_LEX_SQL); // 选择SQL解释器
// Divide each styling byte into lexical class bits (default: 5) and indicator
// bits (default: 3). If a lexer requires more than 32 lexical states, then this
// is used to expand the possible states.
// 将每个样式字节划分为词法类位(默认值:5)和指示符位(默认值:3)。
// 如果词法分析器需要超过 32 个词法状态,则用于扩展可能的状态。
// SetStyleBitsEx(5);
StyleSetForeground(wxSTC_STYLE_DEFAULT, wxColour(255, 0, 0)); // 编辑器的文本默认的前景色(文本默认的颜色)
StyleSetBackground(wxSTC_STYLE_DEFAULT, bkgColor); // 编辑器默认的背景色
StyleClearAll(); // 清理编辑器所有的样式
// - Other
SetIndent(4); // 缩进4字符
SetIndentationGuides(wxSTC_IV_LOOKBOTH); // 显示或隐藏缩进参考线
UsePopUpEx(true); // 设置当用户在某些区域上按错鼠标按钮时是否自动显示弹出菜单
// Error marker
MarkerDefine(0, wxSTC_MARK_ARROW); // 设置用于箭头标记编号的符号,以及(可选)前景色(第三参数)和背景色(第四参数)
MarkerSetBackground(0, wxColour(255, 255, 255)); // 设置第0个Marker的背景色
MarkerSetForeground(0, wxColour(0, 0, 0)); // 设置第0个Marker的前景色
// Font And Size 编辑器的字体和大小
wxFont font(wxSize(0, nSize), wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, face);
StyleSetFont(wxSTC_STYLE_DEFAULT, font);
StyleSetSize(wxSTC_STYLE_DEFAULT, nSize);
// - Margins
// number margin
SetMarginType(0, wxSTC_MARGIN_NUMBER); // 行号边距,将边距设置为数字。此处设置第0个边距是行号数字
SetMarginWidth(0, 37); // 设置边距宽度,第0个边距边宽
// SetMarginBackground(0, bkgColor); // 注意,这里不生效的原因是SetMarginBackground只对SC_MARGIN_COLOUR类型的margin生效,这里第0边距是wxSTC_MARGIN_NUMBER
// folding margin 折叠线边距
SetMarginMask(1, wxSTC_MASK_FOLDERS); // 设置第2个边距为折叠标记
SetMarginWidth(1, 12); // 设置边距宽度,第2个边距边宽
SetMarginSensitive(1, true); // 使第2个边距对鼠标单击敏感或不敏感。
// - Choose folding icons 选择折叠的小图标
MarkerDefine(wxSTC_MARKNUM_FOLDEROPEN, wxSTC_MARK_BOXMINUS); // 定义折叠线打开的图标:方框减号
MarkerDefine(wxSTC_MARKNUM_FOLDER, wxSTC_MARK_BOXPLUS); // 定义折叠线收缩的图标:方框加号
MarkerDefine(wxSTC_MARKNUM_FOLDERSUB, wxSTC_MARK_VLINE); // 定义子折叠线:VLINE
MarkerDefine(wxSTC_MARKNUM_FOLDERTAIL, wxSTC_MARK_LCORNER); // 定义折叠线结束的图标:L型拐角
MarkerDefine(wxSTC_MARKNUM_FOLDEREND, wxSTC_MARK_BOXPLUSCONNECTED); // 定义折叠线结束的图标:方框加号连接
MarkerDefine(wxSTC_MARKNUM_FOLDEROPENMID, wxSTC_MARK_BOXMINUSCONNECTED); // 定义折叠线打开中间的图标:方框减号连接
MarkerDefine(wxSTC_MARKNUM_FOLDERMIDTAIL, wxSTC_MARK_LCORNERCURVE); // 定义折叠线中间收尾的图标:T型拐角曲线
// - Choose folding icon colours 选择折叠的小图标颜色
MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPEN, textColor); // 设置折叠线打开小图标的前景色
MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPEN, bkgColor); // 设置折叠线打开小图标的背景色
MarkerSetForeground(wxSTC_MARKNUM_FOLDER, textColor); // 设置折叠线收缩小图标的前景色
MarkerSetBackground(wxSTC_MARKNUM_FOLDER, bkgColor); // 设置折叠线收缩小图标的背景色
MarkerSetForeground(wxSTC_MARKNUM_FOLDERSUB, textColor); // 设置子折叠线的背景色
MarkerSetBackground(wxSTC_MARKNUM_FOLDERSUB, textColor); // 设置子折叠线的背景色
MarkerSetForeground(wxSTC_MARKNUM_FOLDERTAIL, textColor); // 设置折叠线收缩结束小图标的背景色
MarkerSetBackground(wxSTC_MARKNUM_FOLDERTAIL, textColor); // 设置折叠线收缩结束小图标的背景色
MarkerSetForeground(wxSTC_MARKNUM_FOLDEREND, textColor); // 设置折叠线结束小图标的前景色
MarkerSetBackground(wxSTC_MARKNUM_FOLDEREND, bkgColor); // 设置折叠线结束小图标的背景色
MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPENMID, textColor); // 设置折叠线打开中间小图标的前景色
MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPENMID, bkgColor); // 设置折叠线打开中间小图标的背景色
MarkerSetForeground(wxSTC_MARKNUM_FOLDERMIDTAIL, textColor); // 设置折叠线中间收尾的小图标的前景色
MarkerSetBackground(wxSTC_MARKNUM_FOLDERMIDTAIL, textColor); // 设置折叠线中间收尾的小图标的背景色
// 行号文本颜色,仅仅对SetMarginType(0, wxSTC_MARGIN_NUMBER);起作用
StyleSetForeground(wxSTC_STYLE_LINENUMBER, textColor);
// 行号背景颜色
StyleSetBackground(wxSTC_STYLE_LINENUMBER, bkgColor2);
// 折叠边背景色
SetFoldMarginHiColour(true, bkgColor2);
SetFoldMarginColour(true, bkgColor2);
// - Set carlet 光标
// Color of Carlet 光标的颜色
SetCaretForeground(wxColour(0x00CEC0D6));
// Width of Carlet 光标大小
SetCaretWidth(2);
// set the caret blinking time to 400 milliseconds 光标闪烁间隔
SetCaretPeriod(400);
// - Set caret line colour (设置光标所处行的颜色)
SetCaretLineBackground(wxColour(38, 40, 46)); // 光标所处行的背景色
SetCaretLineVisible(true); // 显示光标所处行
// - Comment block (注释块)
StyleSetForeground(wxSTC_SQL_DEFAULT, wxColour(0, 0, 0)); // 默认SQL的前景色
StyleSetForeground(wxSTC_SQL_COMMENT, wxColour(32768)); // SQL注释的前景色
// - Single comment line (注释行)
StyleSetForeground(wxSTC_SQL_COMMENTLINE, wxColour(32768)); // 默认SQL注释的前景色
// - SQL number 数字
StyleSetForeground(wxSTC_SQL_NUMBER, wxColour(0x002AACB8)); // SQL数字的前景色
StyleSetBold(wxSTC_SQL_NUMBER, true); // SQL数字加粗
// - SQL string/operator/identifier (字符串/操作符/标识符)
StyleSetForeground(wxSTC_SQL_STRING, wxColour(0x00cc99ff)); // SQL字符串的前景色
StyleSetForeground(wxSTC_SQL_CHARACTER, wxColour(0x00cc99ff)); // SQL字符的前景色
StyleSetForeground(wxSTC_SQL_OPERATOR, wxColour(0x00BCBEC4)); // SQL操作符的前景色
StyleSetBold(wxSTC_SQL_OPERATOR, true); // SQL操作符加粗
StyleSetForeground(wxSTC_SQL_IDENTIFIER, wxColour(0x00BCBEC4));// SQL标识符的前景色
// - Color Of Selection (选中的颜色)
SetSelBackground(true, wxColour(49, 106, 197)); // 选中项启用并设置背景色
SetSelForeground(true, wxColour(255, 255, 255)); // 选中项启用并设置前景色
// Set Keywords
wxString keywords(sqlKeyWords);
SetKeyWords(0, keywords);
// Color Of Keyword
StyleSetForeground(wxSTC_SQL_WORD, wxColour(0x00ff9966)); // 0x00CF8E6D
StyleSetForeground(wxSTC_SQL_WORD2, wxColour(0x00ff9966));
StyleSetForeground(wxSTC_SQL_USER1, wxColour(0x00ff9966));
// 自动停顿的字符
AutoCompStops(autoStopChars);
// ignore the cmd key for CTRL+[key] 忽略CTRL+[key]
int n = static_cast<int>(sizeof(ignoreCtrlKey));
for (int i = 0; i < n; i++) {
CmdKeyClear(ignoreCtrlKey[i], wxSTC_KEYMOD_CTRL);
}
// Working Fold
SetProperty("fold", "1");
SetProperty("fold.compact", "1");
SetProperty("fold.html", "1");
SetProperty("fold.html.preprocessor", "1");
SetProperty("fold.comment", "1");
SetProperty("fold.at.else", "1");
SetProperty("fold.flags", "1");
SetProperty("fold.preprocessor", "1");
SetProperty("styling.within.preprocessor", "1");
// set tab width to 4
SetTabWidth(4);
}
上述初始化代码实现了SQL语法高亮,行号,折叠线,以及适配DARK风格的功能。因此我们的4个需求已经实现了3个,下面我们再用代码实现智能提示。
三、智能提示
智能提示,我们首先需要一个外层QSqlEditor的类QueryPageEditor,该类主要的作用:
1.创建编辑器,并显示到界面上。
2.捕捉QSqlEditor的各类事件。
3.调用QSqlEditor::autoShow函数,实现智能提示。
我们通过捕捉wxStyledTextCtrl的EVT_STC_AUTOCOMP_SELECTION事件,来实现智能提示。声明的代码简略如下:
class QueryPageEditor : public QPanel<DatabaseSupplier>
{
DECLARE_EVENT_TABLE()
public:
...
private:
...
// 编辑器
QSqlEditor* editor;
// 创建编辑器
void createEditor();
// 响应EVT_STC_CHARADDED字符输入,智能提示
void OnStcCharAdded(wxStyledTextEvent& event);
// 响应EVT_STC_AUTOCOMP_SELECTION,自动替换当前单词
void OnAutoCSelection(wxStyledTextEvent& event);
}
// 事件表
BEGIN_EVENT_TABLE(QueryPageEditor, wxPanel)
...
EVT_STC_CHARADDED(Config::DATABASE_QUERY_EDITOR_ID, OnStcCharAdded) // 字符输入
EVT_STC_AUTOCOMP_SELECTION(Config::DATABASE_QUERY_EDITOR_ID, OnAutoCSelection) // 提示选择
...
END_EVENT_TABLE()
// 创建创建编辑器
void QueryPageEditor::createEditor()
{
...
editor = new QSqlEditor();
editor->Create(this, Config::DATABASE_QUERY_EDITOR_ID, wxDefaultPosition, wxDefaultSize, wxNO_BORDER | wxCLIP_CHILDREN);
editor->setup(12, FN("Courier New").c_str());
editor->SetFocus();
...
}
// 响应EVT_STC_CHARADDED字符输入,智能提示
void QueryPageEditor::OnStcCharAdded(wxStyledTextEvent& event)
{
wxString line, preline, word;
line = editor->GetCurLine();
if (line.empty()) {
return ;
}
preline = editor->getPrePositionTextOfCurLine();
word = editor->getCurWord();
size_t curPosInLine = editor->getCurPosInLine();
std::vector<std::string> tags = delegate->getTags(line.ToStdString(), preline.ToStdString(), word.ToStdString(), curPosInLine);
editor->autoShow(tags);
}
// 响应EVT_STC_AUTOCOMP_SELECTION,自动替换当前单词
void QueryPageEditor::OnAutoCSelection(wxStyledTextEvent& event)
{
wxString selText = editor->GetSelectedText();
if (selText.empty()) {
editor->autoReplaceWord();
return;
}
if ((char)selText.at(0) == '<' && (char)selText.Last() == '>') {
editor->autoReplaceSelectTag();
return;
}
editor->autoReplaceWord();
}
然后我们再看下上述类调用两个编辑器实现的函数:
editor->autoShow(tags); // 参数tags : 要提示的单词,比如表名,函数,字段名等
editor->autoReplaceWord(); // 自动替换光标当前的单词
// 自动提示,参数tags : 要提示的单词,比如表名,函数,字段名等
void QSqlEditor::autoShow(const std::vector<std::string>& tags)
{
if (tags.empty()) {
return;
}
size_t n = tags.size();
size_t sum = 0;
std::for_each(tags.begin(), tags.end(), [&sum](const std::string& str) {
sum += str.size();
});
char* itemList = new char[tags.size() + sum];
memset(itemList, 0, tags.size() + sum);
char * ptr = itemList;
for (size_t i = 0; i < n; i++) {
std::string tag = tags.at(i);
if (i < n - 1) {
tag += separator;
}
memcpy(ptr, tag.c_str(), tag.size());
ptr += tag.size();
}
AutoCompSetSeparator(separator);
AutoCompSetIgnoreCase(true);
AutoCompSetCaseInsensitiveBehaviour(1);
AutoCompStops(autoStopChars);
AutoCompShow(0, itemList);
delete[] itemList;
}
// 自动替换光标当前的单词
void QSqlEditor::autoReplaceWord()
{
int curPos = GetCurrentPos();
int start = WordStartPosition(curPos, true);
int end = WordEndPosition(curPos, true);
SetSelection(start, end);
wxString text = AutoCompGetCurrentText();
replaceSelText(text);
AutoCompCancel();
}
好了,最后一个需求:输入SQL智能提示,也完成了。
四, 完整代码:
Github - 类QSqlEditor代码https://github.com/CuteBitSoft/CuteMySQL/tree/master/src/ui/common/editor