開發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(); // 清理編輯器所有的樣式// - OtherSetIndent(4); // 縮進4字符SetIndentationGuides(wxSTC_IV_LOOKBOTH); // 顯示或隱藏縮進參考線UsePopUpEx(true); // 設置當用戶在某些區域上按錯鼠標按鈕時是否自動顯示彈出菜單// Error markerMarkerDefine(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); // 定義子折疊線:VLINEMarkerDefine(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 KeywordswxString keywords(sqlKeyWords);SetKeyWords(0, keywords);// Color Of KeywordStyleSetForeground(wxSTC_SQL_WORD, wxColour(0x00ff9966)); // 0x00CF8E6DStyleSetForeground(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 4SetTabWidth(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
Github - 類QueryPageEditor代碼https://github.com/CuteBitSoft/CuteMySQL/tree/master/src/ui/database/rightview/page/editor