为Scintilla加入代码折叠功能
前面曾说过当编辑器有代码折叠功能时,25号到31号这7个标记是作为代码折叠专用标记的。在scintilla.h中,我们可以找到它们的定义:
#define SC_MARKNUM_FOLDEREND 25 //折叠状态(多级中间)
#define SC_MARKNUM_FOLDEROPENMID 26 //展开状态(多级中间)
#define SC_MARKNUM_FOLDERMIDTAIL 27 //被折叠代码块尾部(多级中间)
#define SC_MARKNUM_FOLDERTAIL 28 //被折叠代码块尾部
#define SC_MARKNUM_FOLDERSUB 29 //被折叠的代码块
#define SC_MARKNUM_FOLDER 30 //折叠状态
#define SC_MARKNUM_FOLDEROPEN 31 //展开状态
显示这些标记的掩码是0xFE000000,同样头文件里已经定义好了:
#define SC_MASK_FOLDERS 0xFE000000
要加入代码折叠功能,还有一个最最关键的事情,就是要得到语法解析器(Lexer)的支持,上面的这些标记都是由语法解析器自动添加删除的。一般来说,只要用下面这条命令就可以了让语法解析器支持代码折叠了:
SendEditor(SCI_SETPROPERTY,(sptr_t)"fold",(sptr_t)"1");
是时候上代码了:
- #define MARGIN_FOLD_INDEX 2
- void TForm1::setFold()
- {
- SendEditor(SCI_SETPROPERTY,(sptr_t)"fold",(sptr_t)"1");
- SendEditor(SCI_SETMARGINTYPEN, MARGIN_FOLD_INDEX, SC_MARGIN_SYMBOL);//页边类型
- SendEditor(SCI_SETMARGINMASKN, MARGIN_FOLD_INDEX, SC_MASK_FOLDERS); //页边掩码
- SendEditor(SCI_SETMARGINWIDTHN, MARGIN_FOLD_INDEX, 11); //页边宽度
- SendEditor(SCI_SETMARGINSENSITIVEN, MARGIN_FOLD_INDEX, TRUE); //响应鼠标消息
- // 折叠标签样式
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_CIRCLEPLUS);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_CIRCLEMINUS);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_CIRCLEPLUSCONNECTED);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_CIRCLEMINUSCONNECTED);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE);
- // 折叠标签颜色
- SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERSUB, 0xa0a0a0);
- SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERMIDTAIL, 0xa0a0a0);
- SendEditor(SCI_MARKERSETBACK, SC_MARKNUM_FOLDERTAIL, 0xa0a0a0);
- SendEditor(SCI_SETFOLDFLAGS, 16|4, 0); //如果折叠就在折叠行的上下各画一条横线
- }
- __fastcall TForm1::TForm1(TComponent* Owner)
- : TForm(Owner)
- {
- ...
- setFold();
- }
- void __fastcall TForm1::WndProc(Messages::TMessage &Message)
- {
- TForm::WndProc(Message);
- if(Message.Msg == WM_NOTIFY){
- SCNotification* notify = (SCNotification*)Message.LParam;
- if(notify->nmhdr.code == SCN_MARGINCLICK &&
- notify->nmhdr.idFrom == SCINT_ID){
- // 确定是页边点击事件
- const int line_number = SendEditor(SCI_LINEFROMPOSITION,notify->position);
- SendEditor(SCI_TOGGLEFOLD, line_number);
- }
- }
- }
现在的效果
这里TForm1::WndProc方法是Scintilla父窗体即我们的TForm1的窗口处理函数。
代码折叠以后我们要通过点击页边上的+和-标记来打开和折叠代码,所以需要页边接收鼠标点击事件:
SendEditor(SCI_SETMARGINSENSITIVEN, MARGIN_FOLD_INDEX, TRUE); //响应鼠标消息
这样,当有鼠标点击该页边后,Scintilla就会向它的父窗体发送代码为SCN_MARGINCLICK的WM_NOTIFY消息,其中的LParam为SCNotification*类型。关于SCNotification的详细信息请参考这里。SCNotification的position成员指出了点击位置对应的行号,最后我们用SCI_TOGGLEFOLD命令折叠或展开代码。
使用自定义图形
Scintilla自带的标记样式和VS比起来还有差距,反正偶是怎么调都觉得有点土。Scintilla允许我们自己定义标记的样式,方法是:
- 用SCI_MARKERDEFINE命令设置标记的样式为SC_MARK_PIXMAP
- 用SCI_MARKERDEFINEPIXMAP命令设置标记使用的图形,这里的图形要求是xpm格式。
怎样得到xpm格式图形
xpm在linux系统下用得比较多,它和BMP、jpg一样也是一种图片格式,有不少工具可以把图片转换成xpm格式的,比如我喜欢的免费看图软件XnView就可以,想必ACDSee也行吧。
xpm比较特殊的地方是它可以作为头文件直接被C语言调用,吃惊吧?用文本编辑器打开它看看,呵呵,其实它就是一个数组定义。
如下面这个数据(代码)就是后面马上就要用到的minus.xpm和plus.xpm图片文件的内容(从eclipse里挖出来的):
/* XPM */ static const char *minus_xpm[] = { /* width height num_colors chars_per_pixel */ " 9 9 16 1", /* colors */ "` c #8c96ac", ". c #c4d0da", "# c #daecf4", "a c #ccdeec", "b c #eceef4", "c c #e0e5eb", "d c #a7b7c7", "e c #e4ecf0", "f c #d0d8e2", "g c #b7c5d4", "h c #fafdfc", "i c #b4becc", "j c #d1e6f1", "k c #e4f2fb", "l c #ecf6fc", "m c #d4dfe7", /* pixels */ "hbc.i.cbh", "bffeheffb", "mfllkllfm", "gjkkkkkji", "da`````jd", "ga#j##jai", "f.k##k#.a", "#..jkj..#", "hemgdgc#h" };
/* XPM */ static const char *plus_xpm[] = { /* width height num_colors chars_per_pixel */ " 9 9 16 1", /* colors */ "` c #242e44", ". c #8ea0b5", "# c #b7d5e4", "a c #dcf2fc", "b c #a2b8c8", "c c #ccd2dc", "d c #b8c6d4", "e c #f4f4f4", "f c #accadc", "g c #798fa4", "h c #a4b0c0", "i c #cde5f0", "j c #bcdeec", "k c #ecf1f6", "l c #acbccc", "m c #fcfefc", /* pixels */ "mech.hcem", "eldikille", "dlaa`akld", ".#ii`ii#.", "g#`````fg", ".fjj`jjf.", "lbji`ijbd", "khb#idlhk", "mkd.ghdkm" };
演示,使用自定义图形
- #include "minus.xpm"
- #include "plus.xpm"
- void TForm1::setFold()
- {
- ...
- // 折叠标签样式
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_PIXMAP);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_PIXMAP);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_PIXMAP);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_PIXMAP);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_TCORNERCURVE);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_VLINE);
- SendEditor(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_LCORNERCURVE);
- //
- SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDER, (sptr_t)plus_xpm);
- SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEROPEN, (sptr_t)minus_xpm);
- SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEREND, (sptr_t)plus_xpm);
- SendEditor(SCI_MARKERDEFINEPIXMAP, SC_MARKNUM_FOLDEROPENMID, (sptr_t)minus_xpm);
- ...
- }
现在,我们的成果是这样的(与VS有一拼了吧^_^):