User Tools

Site Tools


wiki:stc

Using wxStyledTextControl (stc)

I use SciTE editor in my everyday work and it is natural to choose Scintilla for syntax highlighting component in my programs. There is a nice Scintilla interface control for wxWigdets (formerly wxWindows) called wxStyledTextControl. For those who don't know it yet, here are some features (important to me):

  • syntax highlighting for almost every language and filetype (C, C++, php, html, diff, python, sql, … and much more)
  • autocompletion which is easy to tweak to any need
  • brace matching with highlighting
  • line numbers (as trivial as it seems, many other components don't have it)
  • calltips (function parameters and/or descriptions)

Hints:
Whatever you want to do, read the stc.h file first.

Font size

Font size is funny. The setting of 10 (from example) looks very good on Windows, but on GTK it is just too small. Size of 14 looks ok, but it's kind of big for Windows. So I have two options, set it to 12 or make #ifdef. I still haven't chosen, perhaps I will leave it at 12, and let the user pick what (s)he wants. The size is very easy to change at runtime, using Ctrl+ and Ctrl- key combinations.

There seems to be the way to handle this, but I haven't tried it yet: For wx < 2.5.3 there is NormalizeFontSizes() and for wx >= 2.5.3 there is SetStandardFonts() function. It looks like both are yet undocumented. For wx < 2.5 there isn't any function available. If you test something and it works, please update this page (or contact me at: mbabuskov at yahoo dot com).

Syntax highlighting

My primary goal was developing a SQL editor. Of course I used wxSTC_LEX_SQL lexer, which can be set by SetLexer() function. After that, you have to supply the sql keywords like this:

sql_edit->SetKeyWords(0, wxT(
        "abs action active add admin after all alter and any as asc ascending at auto autoddl "
        "avg based basename base_name before begin between bigint blob blobedit boolean both "
        "break buffer by cache cascade case cast char character character_length char_length "
....
        "while with work write year yearday" )
    );

It worked great until I switched to latest (at this time) wxWin version 2.4.2, and it just stopped working. First I googled to find the answer, and finally I decided to diff the sources between 2.4.0 and 2.4.2. and I found it. The developers changed the game, so all keywords have to be given with lowercase letters. Of course, I read the entire “changes” files, between versions, and nothing like this was mentioned. It was a few seconds to fix, but it took me few hours to find it.

Autocomplete

The most important feature and the hardest to setup. I used AutoCompSetIgnoreCase(true) and AutoCompSetAutoHide(true), but you may want it to work differently. Autocomplete stuff is handled in EVT_STC_CHARADDED event handler. Here is a simple version (read the comments in code):

int pos = sql_edit->GetCurrentPos();
int start = sql_edit->WordStartPosition(pos, true);
if (pos - start > 2 && !sql_edit->AutoCompActive())    // require 3 characters to show auto-complete
{
    // GTK version crashes if nothing matches, so this check must be made for GTK
    // For MSW, it doesn't crash but it flashes on the screen (also not very nice)
    if (HasWord(sql_edit->GetTextRange(start, pos), keywordsM))
        sql_edit->AutoCompShow(pos-start, keywordsM);
}

HasWord is a very simple function:

//! returns true if there is a word in "wordlist" that starts with "word"
bool HasWord(wxString word, wxString &wordlist)
{
    word.MakeUpper();   // entire wordlist is uppercase
 
    wxStringTokenizer tkz(wordlist, wxT(" "));
    while (tkz.HasMoreTokens())
    {
        wxString token = tkz.GetNextToken();
        if (token.StartsWith(word))
            return true;
    }
    return false;
}

This works perfectly on wxMSW. Of course, it's very custom, since the application is SQL editor, but it can be easily extended. On GTK with version 2.4.0 it also works, but it crashes when you type text too fast. I guess it has got something to to with event handling in gtk+ itself, since the backtrace leads there.

So, after I tried many things to fix it, I finally gave up, and decided to install 2.4.2 and try (especially after I diff-ed the sources of stc between two versions). I'm delighted to say that it works :)

Brace matching

I needed to experiment a while until I got this one right. Version 2.4.0 has a bug and multiline brace highlighting (i.e. the open and close braces aren't in the same line of text) doesn't work properly. Sometimes it works when you click the mouse. I guess there could be a workaround, but it doesn't matter anymore since it is fixed and works propery in 2.4.2. Here's how I made it work…

// setup the colors and bold font
sql_edit->StyleSetBackground(wxSTC_STYLE_BRACELIGHT, wxColour(0xff, 0xcc, 0x00));        // brace highlight
sql_edit->StyleSetBackground(wxSTC_STYLE_BRACEBAD, wxColour(0xff, 0x33, 0x33));        // brace bad highlight
sql_edit->StyleSetBold(wxSTC_STYLE_BRACELIGHT, TRUE);
sql_edit->StyleSetBold(wxSTC_STYLE_BRACEBAD, TRUE);

You should use EVT_STC_UPDATEUI event to handle brace highlighting: Here's the code of event handler:

int p = sql_edit->GetCurrentPos();
int c1 = sql_edit->GetCharAt(p);
int c2 = (p > 1 ? sql_edit->GetCharAt(p-1) : 0);
 
if (c2=='(' || c2==')' || c1=='(' || c1==')')
{
    int sp = (c2=='(' || c2==')') ? p-1 : p;
 
    int q = sql_edit->BraceMatch(sp);
    if (q == wxSTC_INVALID_POSITION)
        sql_edit->BraceBadLight(sp);
    else
        sql_edit->BraceHighlight(sp, q);
}
else
    sql_edit->BraceBadLight(wxSTC_INVALID_POSITION);    // remove light

Perhaps it can be written nicer with less lines of code. I leave it to you for homework :)

Line numbers

Setting the STC to show line numbers was arcane undertaking too. I had to read the Scintilla html docs to understand how to get things done. Here is some relevant (and some irelevant) code:

sql_edit->SetMarginWidth(0, 30);        // turn on the linenumbers margin, set width to 30pixels
sql_edit->SetMarginWidth(1, 0);            // turn off the folding margin
sql_edit->SetMarginType(0, 1);            // set margin type to linenumbers

Calltips

Using calltips is easy. I'll extend the examples shown above. First, in CharAdded event handler, add something like this:

int pos = styled_text_ctrl_sql->GetCurrentPos();
if (pos > 0)
{
	int c = styled_text_ctrl_sql->GetCharAt(pos-1);
	if (c == '(')
	{
		int start = styled_text_ctrl_sql->WordStartPosition(pos-2, true);
		if (start != -1 && start != pos-2)
		{
			wxString word = styled_text_ctrl_sql->GetTextRange(start, pos-1).Upper();
			wxString calltip = some_function_to_get_calltip(word);
			styled_text_ctrl_sql->CallTipShow(start, calltip);
			styled_text_ctrl_sql->CallTipSetHighlight(0, pos-1-start);
		}
        }
}

This will show the calltip. Of course, you need to implement some_function_to_get_calltip. It takes function name as a parameter, and should return the calltip. You also might want to check if it didn't return anything (unknown function) and not show the calltip then.

Calltip will stay on the screen until we remove it. I did it in STC_UPDATE_UI event handler. This is relevant part of code:

int p = styled_text_ctrl_sql->GetCurrentPos();
int row = styled_text_ctrl_sql->GetCurrentLine();
int col = p - styled_text_ctrl_sql->PositionFromLine(row);
 
int c1 = styled_text_ctrl_sql->GetCharAt(p);
int c2 = (p > 1 ? styled_text_ctrl_sql->GetCharAt(p-1) : 0);
 
if (c2==')' || c1==')')
{
	int sp = ( c2==')' ? p-1 : p);
	int q = styled_text_ctrl_sql->BraceMatch(sp);
	if (styled_text_ctrl_sql->CallTipActive() && q == styled_text_ctrl_sql->CallTipPosAtStart() - 1)
		styled_text_ctrl_sql->CallTipCancel();
}

Now, we can cobmine brace matching and calltip code together. The final UPDATE_UI handler looks like this:

int p   = styled_text_ctrl_sql->GetCurrentPos();
int row = styled_text_ctrl_sql->GetCurrentLine();
int col = p - styled_text_ctrl_sql->PositionFromLine(row);
int c1  = styled_text_ctrl_sql->GetCharAt(p);
int c2  = (p > 1 ? styled_text_ctrl_sql->GetCharAt(p-1) : 0);
 
if (c2=='(' || c2==')' || c1=='(' || c1==')')
{
	int sp = (c2=='(' || c2==')') ? p-1 : p;
	int q = styled_text_ctrl_sql->BraceMatch(sp);
	if (q == wxSTC_INVALID_POSITION)
		styled_text_ctrl_sql->BraceBadLight(sp);
	else
		styled_text_ctrl_sql->BraceHighlight(sp, q);
 
	// remove calltip if needed
	if (styled_text_ctrl_sql->CallTipActive() && (c1==')' || c2==')') 
                && q == styled_text_ctrl_sql->CallTipPosAtStart() - 1)
		styled_text_ctrl_sql->CallTipCancel();
}
else
	styled_text_ctrl_sql->BraceBadLight(wxSTC_INVALID_POSITION);	// remove light

Well, that's about it. You may also want to disable autocomplete while calltip is active as it can be really annoying. Just check for styled_text_ctrl_sql→CallTipActive() before doing autocomplete stuff.

Copyright © Milan Babuskov 2004-2005.

wiki/stc.txt · Last modified: 2015/05/13 09:48 by mariuz