WxStyledTextCtrl
From WxWiki
wxStyledTextCtrl is a code editor type control, supporting syntax highlighting, folding, margins with line numbers/etc. and more. This are generally seen in IDE's such as Code::Blocks.
Contents |
Documentation
wxStyledTextCtrl is just a wrapper around the Scintilla library, and as such the wx wrapper is not very documented so it's better to check the docs of the Scintilla toolkit. See http://www.yellowbrain.com/stc/styling.html for instance
Margins
Margins are generally used for things like break points and line numbering as well as code folding. Usually you just set the margin width and use styling and markers to achieve what you are looking for.
Styling
Styling is used to define your keyword lists and the coloring and fonts or just general style of delimiters, characters, keywords, strings, numbers, etc.
Lexers
Lexers are what determine what needs highlighted in your scintilla based control. Several default lexers come with the wxStyledTextCtrl such as HTML, XML, C++, and C# but custom lexers can be used to achieve custom syntax styling for other languages.
Markers
Markers are used for indicating parsing errors in the code, as well as special symbol margins such as those you would use to insert break points with bitmap images.
Auto Completion
NOTE: In early releases of 2.9 there was a bug cutting off the Autocomp images, it has been fixed in the latest SVN
Auto completion windows can be shown with bitmap graphics as well by first registering the image to an integer type then adding each keyword to the auto complete list followed by a question mark and then then the integer type for that keyword followed by a space. For instance, "test_case?1 test_parse?2", would have test_case use the integer type one and its image and have test_parse use integer type two and its image.
Using wxStyledTextCtrl
Find below a minimal sample showing how to highlight HTML code :
/** Not really needed here since this example uses a single margin */ enum { MARGIN_LINE_NUMBERS }; class SourceViewDialog : public wxDialog { public: void onClose(wxCloseEvent& evt) { EndModal( GetReturnCode() ); } SourceViewDialog(wxWindow* parent, wxString source) : wxDialog(parent, wxID_ANY, "Source Code", wxDefaultPosition, wxSize(700,500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { wxStyledTextCtrl* text = new wxStyledTextCtrl(this, wxID_ANY); text->SetMarginWidth (MARGIN_LINE_NUMBERS, 50); text->StyleSetForeground (wxSTC_STYLE_LINENUMBER, wxColour (75, 75, 75) ); text->StyleSetBackground (wxSTC_STYLE_LINENUMBER, wxColour (220, 220, 220)); text->SetMarginType (MARGIN_LINE_NUMBERS, wxSTC_MARGIN_NUMBER); text->SetWrapMode (wxSTC_WRAP_WORD); text->SetText(source); text->StyleClearAll(); text->SetLexer(wxSTC_LEX_HTML); text->StyleSetForeground (wxSTC_H_DOUBLESTRING, wxColour(255,0,0)); text->StyleSetForeground (wxSTC_H_SINGLESTRING, wxColour(255,0,0)); text->StyleSetForeground (wxSTC_H_ENTITY, wxColour(255,0,0)); text->StyleSetForeground (wxSTC_H_TAG, wxColour(0,150,0)); text->StyleSetForeground (wxSTC_H_TAGUNKNOWN, wxColour(0,150,0)); text->StyleSetForeground (wxSTC_H_ATTRIBUTE, wxColour(0,0,150)); text->StyleSetForeground (wxSTC_H_ATTRIBUTEUNKNOWN, wxColour(0,0,150)); text->StyleSetForeground (wxSTC_H_COMMENT, wxColour(150,150,150)); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(text, 1, wxEXPAND); SetSizer(sizer); Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(SourceViewDialog::onClose), NULL, this); } };
And here is a somewhat more elaborate example to highlight C++ :
#include <wx/wx.h> #include <wx/stc/stc.h> enum { MARGIN_LINE_NUMBERS, MARGIN_FOLD }; class SourceViewDialog : public wxDialog { wxStyledTextCtrl* text; public: void onClose(wxCloseEvent& evt) { EndModal( GetReturnCode() ); } SourceViewDialog(wxWindow* parent, wxString source) : wxDialog(parent, wxID_ANY, _("Source Code"), wxDefaultPosition, wxSize(700,500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { text = new wxStyledTextCtrl(this, wxID_ANY); text->SetMarginWidth (MARGIN_LINE_NUMBERS, 50); text->StyleSetForeground (wxSTC_STYLE_LINENUMBER, wxColour (75, 75, 75) ); text->StyleSetBackground (wxSTC_STYLE_LINENUMBER, wxColour (220, 220, 220)); text->SetMarginType (MARGIN_LINE_NUMBERS, wxSTC_MARGIN_NUMBER); // ---- Enable code folding text->SetMarginType (MARGIN_FOLD, wxSTC_MARGIN_SYMBOL); text->SetMarginWidth(MARGIN_FOLD, 15); text->SetMarginMask (MARGIN_FOLD, wxSTC_MASK_FOLDERS); text->StyleSetBackground(MARGIN_FOLD, wxColor(200, 200, 200) ); text->SetMarginSensitive(MARGIN_FOLD, true); // Properties found from http://www.scintilla.org/SciTEDoc.html text->SetProperty (wxT("fold"), wxT("1") ); text->SetProperty (wxT("fold.comment"), wxT("1") ); text->SetProperty (wxT("fold.compact"), wxT("1") ); wxColor grey( 100, 100, 100 ); text->MarkerDefine (wxSTC_MARKNUM_FOLDER, wxSTC_MARK_ARROW ); text->MarkerSetForeground (wxSTC_MARKNUM_FOLDER, grey); text->MarkerSetBackground (wxSTC_MARKNUM_FOLDER, grey); text->MarkerDefine (wxSTC_MARKNUM_FOLDEROPEN, wxSTC_MARK_ARROWDOWN); text->MarkerSetForeground (wxSTC_MARKNUM_FOLDEROPEN, grey); text->MarkerSetBackground (wxSTC_MARKNUM_FOLDEROPEN, grey); text->MarkerDefine (wxSTC_MARKNUM_FOLDERSUB, wxSTC_MARK_EMPTY); text->MarkerSetForeground (wxSTC_MARKNUM_FOLDERSUB, grey); text->MarkerSetBackground (wxSTC_MARKNUM_FOLDERSUB, grey); text->MarkerDefine (wxSTC_MARKNUM_FOLDEREND, wxSTC_MARK_ARROW); text->MarkerSetForeground (wxSTC_MARKNUM_FOLDEREND, grey); text->MarkerSetBackground (wxSTC_MARKNUM_FOLDEREND, _T("WHITE")); text->MarkerDefine (wxSTC_MARKNUM_FOLDEROPENMID, wxSTC_MARK_ARROWDOWN); text->MarkerSetForeground (wxSTC_MARKNUM_FOLDEROPENMID, grey); text->MarkerSetBackground (wxSTC_MARKNUM_FOLDEROPENMID, _T("WHITE")); text->MarkerDefine (wxSTC_MARKNUM_FOLDERMIDTAIL, wxSTC_MARK_EMPTY); text->MarkerSetForeground (wxSTC_MARKNUM_FOLDERMIDTAIL, grey); text->MarkerSetBackground (wxSTC_MARKNUM_FOLDERMIDTAIL, grey); text->MarkerDefine (wxSTC_MARKNUM_FOLDERTAIL, wxSTC_MARK_EMPTY); text->MarkerSetForeground (wxSTC_MARKNUM_FOLDERTAIL, grey); text->MarkerSetBackground (wxSTC_MARKNUM_FOLDERTAIL, grey); // ---- End of code folding part text->SetWrapMode (wxSTC_WRAP_WORD); // other choice is wxSCI_WRAP_NONE text->SetText(source); text->StyleClearAll(); text->SetLexer(wxSTC_LEX_CPP); text->StyleSetForeground (wxSTC_C_STRING, wxColour(150,0,0)); text->StyleSetForeground (wxSTC_C_PREPROCESSOR, wxColour(165,105,0)); text->StyleSetForeground (wxSTC_C_IDENTIFIER, wxColour(40,0,60)); text->StyleSetForeground (wxSTC_C_NUMBER, wxColour(0,150,0)); text->StyleSetForeground (wxSTC_C_CHARACTER, wxColour(150,0,0)); text->StyleSetForeground (wxSTC_C_WORD, wxColour(0,0,150)); text->StyleSetForeground (wxSTC_C_WORD2, wxColour(0,150,0)); text->StyleSetForeground (wxSTC_C_COMMENT, wxColour(150,150,150)); text->StyleSetForeground (wxSTC_C_COMMENTLINE, wxColour(150,150,150)); text->StyleSetForeground (wxSTC_C_COMMENTDOC, wxColour(150,150,150)); text->StyleSetForeground (wxSTC_C_COMMENTDOCKEYWORD, wxColour(0,0,200)); text->StyleSetForeground (wxSTC_C_COMMENTDOCKEYWORDERROR, wxColour(0,0,200)); text->StyleSetBold(wxSTC_C_WORD, true); text->StyleSetBold(wxSTC_C_WORD2, true); text->StyleSetBold(wxSTC_C_COMMENTDOCKEYWORD, true); // a sample list of keywords, I haven't included them all to keep it short... text->SetKeyWords(0, wxT("return for while break continue")); text->SetKeyWords(1, wxT("const int float void char double")); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(text, 1, wxEXPAND); SetSizer(sizer); Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(SourceViewDialog::onClose), NULL, this); text->Connect(wxEVT_STC_MARGINCLICK, wxStyledTextEventHandler(SourceViewDialog::OnMarginClick), NULL, this); } /** Event callback when a margin is clicked, used here for code folding */ void OnMarginClick(wxStyledTextEvent &event) { if (event.GetMargin() == MARGIN_FOLD) { int lineClick = text->LineFromPosition(event.GetPosition()); int levelClick = text->GetFoldLevel(lineClick); if ((levelClick & wxSTC_FOLDLEVELHEADERFLAG) > 0) { text->ToggleFold(lineClick); } } } }; const char* code = "// Some example\n" "#define MAX 0\n\n" "/** @brief The entry point */\n" "int main(int argc, char *argv[])\n" "{\n" " for (int n=0; n<MAX; n++)\n" " {\n" " printf(\"Hello World %i\\n\", n);\n" " }\n" " return 0;\n" "}\n"; class MyApp : public wxApp { public: bool OnInit() { SourceViewDialog* dlg = new SourceViewDialog(NULL, wxString::FromUTF8(code)); dlg->Show(); } }; IMPLEMENT_APP(MyApp)
For lengthier examples, make sure to also check the sample that comes with wxWidgets.
Synchronizing the scrolling of two STCs
This is a common need, for instance to create a diff viewer
#include <wx/wx.h> #include <wx/stc/stc.h> /** Not really needed here since this example uses a single margin */ enum { MARGIN_LINE_NUMBERS }; class SourceViewDialog : public wxDialog { wxStyledTextCtrl* m_text1; wxStyledTextCtrl* m_text2; bool m_changing_values; public: void onClose(wxCloseEvent& evt) { EndModal( GetReturnCode() ); } wxStyledTextCtrl* createSTC() { wxStyledTextCtrl* text = new wxStyledTextCtrl(this, wxID_ANY); text->SetMarginWidth (MARGIN_LINE_NUMBERS, 50); text->StyleSetForeground (wxSTC_STYLE_LINENUMBER, wxColour (75, 75, 75) ); text->StyleSetBackground (wxSTC_STYLE_LINENUMBER, wxColour (220, 220, 220)); text->SetMarginType (MARGIN_LINE_NUMBERS, wxSTC_MARGIN_NUMBER); text->SetWrapMode (wxSTC_WRAP_WORD); text->StyleClearAll(); text->SetLexer(wxSTC_LEX_HTML); text->StyleSetForeground (wxSTC_H_DOUBLESTRING, wxColour(255,0,0)); text->StyleSetForeground (wxSTC_H_SINGLESTRING, wxColour(255,0,0)); text->StyleSetForeground (wxSTC_H_ENTITY, wxColour(255,0,0)); text->StyleSetForeground (wxSTC_H_TAG, wxColour(0,150,0)); text->StyleSetForeground (wxSTC_H_TAGUNKNOWN, wxColour(0,150,0)); text->StyleSetForeground (wxSTC_H_ATTRIBUTE, wxColour(0,0,150)); text->StyleSetForeground (wxSTC_H_ATTRIBUTEUNKNOWN, wxColour(0,0,150)); text->StyleSetForeground (wxSTC_H_COMMENT, wxColour(150,150,150)); return text; } SourceViewDialog(wxWindow* parent, wxString source) : wxDialog(parent, wxID_ANY, "Source Code", wxDefaultPosition, wxSize(700,500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { m_changing_values = false; m_text1 = createSTC(); m_text1->SetText(source + source + source + source + source + source + source + source); m_text2 = createSTC(); m_text2->SetText(source + source + source + source + source + source + source + source); wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_text1, 1, wxEXPAND); sizer->Add(m_text2, 1, wxEXPAND); SetSizer(sizer); Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(SourceViewDialog::onClose), NULL, this); m_text1->Connect(wxEVT_STC_PAINTED, wxStyledTextEventHandler(SourceViewDialog::onScrollLeft), NULL, this); m_text2->Connect(wxEVT_STC_PAINTED, wxStyledTextEventHandler(SourceViewDialog::onScrollRight), NULL, this); } void onScrollLeft(wxStyledTextEvent& evt) { if (m_changing_values) return; m_changing_values = true; int fvl = m_text1->GetFirstVisibleLine(); if (m_text2->GetFirstVisibleLine() != fvl) { m_text2->ScrollToLine(fvl); // ShowLines } m_changing_values = false; } void onScrollRight(wxStyledTextEvent& evt) { if (m_changing_values) return; m_changing_values = true; int fvl = m_text2->GetFirstVisibleLine(); if (m_text1->GetFirstVisibleLine() != fvl) { m_text1->ScrollToLine(fvl); // ShowLines } m_changing_values = false; } }; const char* code = "<a>\n" "<b foo=\"y\">\n" "<c>\n" "<d bar=\"x\">\n" "<e>\n" "<f>\n" "<g foobar=\"z\">\n" "<h>\n" "<i>\n" "<j foo=\"w\">\n"; class MyApp : public wxApp { public: bool OnInit() { SourceViewDialog* dlg = new SourceViewDialog(NULL, wxString::FromUTF8(code)); dlg->Show(); } }; IMPLEMENT_APP(MyApp)
Adding a Lexer to wxStyledTextCtrl
wxSTC comes with several supported languages built-in (constants wxSTC_LEX_*). However, if you want to highlight code in a language that is currently not supported by wxStyledTextCtrl, or to make something highly custom, you will need to write a new lexer.
This is my 2 days work to create a new lexer for wxStyledTextCtrl (wxSTC). The first thing you should know is that wxSTC is a wrapper around a widely known text control named scintilla [1]. So if you want to add a lexer you really are adding it to the scintilla distribution that wxSTC comes with.
Here is this web page [2] where you can find how to add a lexer to scintilla and its text editing application called SciTE. The first step is basically the same.
You should have a $(wxDIR)/contrib/src/stc dir where you have all the things that go with the control; I not a expert in this just I saw what I needed to add the lexer, in the documentation you will find more information. For the moment you should go to that directory and follow these steps. We will suppose we're adding some language, call it foo.
1. So go to the scintilla/include dir and open the SciLexer.h file, there you will add a ID value:
- define SCLEX_FOO=79, (supposed the last ID was 78)
And any lexical class IDs:
- define SCE_FOO_DEFAULT=0
- define SCE_FOO_COMMENT=1
2. Now you should add the ID value to stc.h located in $(wxDIR)/contrib/include/wx/stc.
- define wxSTC_LEX_FOO 79 and
- define wxSTC_FOO_DEFAULT 0
- define wxSTC_FOO_COMMENT 1
I think you should be able to recognize an order in the file so you can do it that way.
3. In the scintilla/src/LexOthers.cxx write a ColouriseFooDoc function similar to one of the other functions such as ColouriseLatexDoc. For a more detailed guide open the file LexCpp.cxx and see the ColouriseCppDoc. What I did was to copy and paste that function and fit it to my needs.
LexerModule lmfoo(SCLEX_FOO, ColouriseFooDoc, "conf");
4. If this is a complex lexer then it may be better off in its own file, in which case clone one of the current files and then add the file to all of the make files where LexOthers is currently referenced
5. You should be able to compile wxSTC.
Those were the steps I followed to get the lexer working. You should see this page to understand what the Colourise function does: [3]
To get the lexer working we would modify the sample that comes with wxSTC, you should find it in $(wxDIR)/contrib/samples/stc, here what we need to do is to modify the prefs.cpp file, this contains all the preferences for the lexers:
1. If you have some keywords you want to add, just follow the way C++ and Python do it.
2. To the g_LanguagePrefs const add _T("FOO") with your definitions.
That's all, you should be able to compile the sample and select the Foo language from the View->Highlight Language->Foo
I hope that serves you.
Deriving from ScintillaWX
From: dhoward ~at- mountbromo.com (Daniel Howard)
Subject: Custom ScintillaWX for wxStyledTextCtrl
Date: 27 Jun 2003 18:05:28 -0700
To: wx-users@lists.wxwidgets.org
I just wanted to record for posterity how to make a ScintillaWX
derived class under wxWidgets when using the Scintilla Edit Control
from the contrib/stc directory. The Scintilla Edit Control is a full
service text editing control (with built-in auto-completion and source
code coloring). It is included with the wxWidgets distribution with
documentation located at http://www.scintilla.org/ . It took me the
better part of a day to figure out how hook in my own ScintillaWX
derived class without crashing.
ScintillaWX is only meant for internal use by the wxStyledTextCtrl
control. However, once you've started using wxStyledTextCtrl, you
often want to make customizations or fix bugs in Scintilla itself.
Since wxStyledTextCtrl is a wrapper around ScintillaWX and ScintillaWX
contains the bulk of the Scintilla code, it often becomes necessary to
subclass ScintillaWX to get control of Scintilla's behavior.
To get at ScintillaWX and other wxStyledTextCtrl internals, you've got
to add the following include directories to your project:
C:\wxWidgets_2.4.0\contrib\include
C:\wxWidgets_2.4.0\contrib\src\stc\scintilla\include
C:\wxWidgets_2.4.0\contrib\src\stc\scintilla\src
Also, you've got to build the Scintilla control and add the Scintilla
(debug) library to your project:
C:\wxWidgets_2.4.0\lib\stcd.lib
The code to demonstrate deriving from ScintillaWX is below. There are
a few gotchas. The first trick is do "delete m_swx" and "m_swx = new
CoolScintillaWX(this)" in order to replace wxStyledTextCtrl's generic
ScintillaWX. There is no easy way to intercept the creation of the
generic ScintillaWX by Scintilla so, instead, we replace it. The
second trick is to define __WX__ and LINK_LEXERS before including
ScintillaWX.h (preferably everywhere, though especially for the file
that implements the first trick). If you do not, your program will
compile and link without warnings but will crash after typing a few
characters into the control. This happens because you accidentally
use two different memory managers: wxWidgets memory manager and Visual
C++'s default memory manager. If this happens, the program gets all
confused, allocating memory from one manager and then trying to
deallocate it from the other and vice-versa.
Here's the code (based on wxWidgets 2.4.0):
//CoolScintillaWX.h
#ifndef CoolScintillaWX_h
#define CoolScintillaWX_h
#define __WX__
#define SCI_LEXER
#define LINK_LEXERS
#include <../src/stc/ScintillaWX.h>
class CoolScintillaWX : public ScintillaWX
{
public:
CoolScintillaWX(wxStyledTextCtrl* win);
};
#endif //CoolScintillaWX_h
//CoolScintillaWX.cpp
//#include "CoolScintillaWX.h"
CoolScintillaWX::CoolScintillaWX( wxStyledTextCtrl * win ) :
ScintillaWX(win)
{
}
//CoolStyledTextCtrl.h
#ifndef CoolStyledTextCtrl_h
#define CoolStyledTextCtrl_h
#include <wx/stc/stc.h>
class CoolStyledTextCtrl : public wxStyledTextCtrl
{
public:
CoolStyledTextCtrl(wxWindow * parent);
};
#endif //CoolStyledTextCtrl_h
//CoolStyledTextCtrl.cpp
//#include "CoolScintillaWX.h"
//#include "CoolStyledTextCtrl.h"
CoolStyledTextCtrl::CoolStyledTextCtrl( wxWindow * parent ) :
wxStyledTextCtrl(parent, wxID_ANY)
{
delete m_swx;
m_swx = new CoolScintillaWX(this);
}
//test.h
void test();
//test.cpp
//#include "CoolStyledTextCtrl.h"
void test()
{
wxFrame * frame = new wxFrame(NULL, wxID_ANY, "Scintilla Test");
CoolStyledTextCtrl * ed = new CoolStyledTextCtrl(frame);
frame->Show(TRUE);
}
Scintilla Internals
Scintilla is complex and, at least in my experience, is hard to use without looking at the source code. Ideally, Scintilla.org would have its own Wiki. Since it does not, this section describes Scintilla internals which is essentially the same as wxStyledTextControl internals with special attention to how those internals relate to wxWidgets.
Scintilla is written in C++ but many of its data structures are reminiscent of C.
Often, Scintilla objects will allocate an regular array of C++ objects or C++ object pointers and use a data member to track how much of the array is used. Then, when the array grows to large, a new array is manually allocated, contents are moved from the old array to the new one and data members are fixed up. In contrast, wxWidgets itself has dedicated, general purpose containers that do allocations, copying and deallocations internally so the object that uses these containers need concern itself with low-level array manipulation.
Also, Scintilla sometimes allocates a pointer inside of one object and passes the pointer to another object to hold and use. Scintilla uses various ways to track these pointers that are shared between multiple objects and takes care to delete them only once. In contrast, wxWidgets objects usually allocate and permanently store pointers to child objects in a single owning object. In this way, wxWidgets needs very little code to track pointers and avoiding deleting a pointer more than once is usually trivial.
CellBuffer.cxx
CellBuffer.cxx contains the Action class and the UndoHistory class among several others.
The '''UndoHistory''' class stores an array of actions. There are three types of actions: Start, Insert and Remove. The Start action indicates the start of a sequence of actions that represent a single user action; for example, if the user swaps two lines using Ctrl-T, the deletion of the lower line and its re-insertion above the upper line are two actions that represent a single user action. The Insert action indicates a string of text that is inserted as a single position. The Remove action indicates an uninterrupted range of text that was removed from the document.
'''UndoHistory::actions''' contains an array of actions. '''UndoHistory::lenActions''' and '''UndoHistory::maxAction''' indicate the total number of action objects and the index of the largest object being used.
'''UndoHistory::currentAction''' indicates where the user is in the history. Normally, the user would be at the end but, if the user has done an Undo, this variable would point further towards the front of the actions array. The actions after the position indicated by this variable are actions that are taken if the user does a Redo.
'''UndoHistory::undoSequenceDepth''' tracks the number of BeginUndoAction() calls that do not have a matching EndUndoAction() call. These calls allow Scintilla to mark a group of actions as a single user action. Normally, Scintilla tries to group actions together that appear to be the result of uninterrupted typing or single deletes at the same position (i.e. "coalesce" in Scintilla terminology). Similiarly, Scintilla tries to add Start actions to separate actions that appear to be unrelated. The BeginUndoAction() and EndUndoAction() override this separation and force all actions between these calls to be part of the same user action; UndoHistory::undoSequenceDepth tracks this state.
'''UndoHistory::EnsureUndoRoom()''' helps to manage the allocation, copying and deallocation of the actions array when it becomes too large.
'''UndoHistory::AppendAction()''' adds an action to the actions array and checks the state to decide whether this action can be lumped in with a previous user action and should be preceded by a Start action to indicate a new user action.
Tips, Tricks, and Pitfalls
- Look out for SetReadOnly(). Once set future calls like ClearAll(), SetText(), and even LoadFile() will look to successfully complete, but will not change the content of the control.
Resources
- There is a sample in contrib/samples/stc
- http://www.yellowbrain.com/stc/index.html
- http://webpages.charter.net/edreamleo/out2.htm