|
// Implementation of the ParseLine() virtual function for Visual C++ highlighting
//
// Author: Egipko Nikolay
// E-mail: Egipko@Artis-Inventum.de
//
////////////////////////////////////////////////////////////////////////////
// Description: Some months ago I have written own realization ParseLine()
// function for C++ highlighting. On my opinion this realization is little
// bit better than existing in CrystalEditor. In this code I wanted to realize
// the greatest conformity with Visual C++ text editor highlighting.
//
// I also have the modified code of the editor supporting concept
// of the VirtualSpace (see Visual C++ Options/Compatibility - "Enable virtual space").
// Unfortunately, the changes were rather difficult and I can't here
// them describe.
// Visual C++ highlighting implementation:
// First: define color enums and C++ keywords
enum
{
// Base colors
COLORINDEX_WHITESPACE = 0,
COLORINDEX_BKGND,
COLORINDEX_NORMALTEXT,
COLORINDEX_SELMARGIN,
COLORINDEX_SELBKGND,
COLORINDEX_SELTEXT,
// Syntax colors
COLORINDEX_KEYWORD,
COLORINDEX_OPERATOR,
COLORINDEX_COMMENT,
COLORINDEX_NUMBER,
COLORINDEX_STRING,
COLORINDEX_PREPROCESSOR,
COLORINDEX_VARIABLE,
// Compiler/debugger colors
COLORINDEX_ERRORBKGND,
COLORINDEX_ERRORTEXT,
COLORINDEX_EXECUTIONBKGND,
COLORINDEX_EXECUTIONTEXT,
COLORINDEX_BREAKPOINTBKGND,
COLORINDEX_BREAKPOINTTEXT,
// ...
// Expandable: custom elements are allowed.
COLORINDEX_LASTINDEX,
};
// C++ keywords (MSVC6.0)
static LPTSTR s_apszCppKeywordList[] =
{
_T("__asm"),
_T("__assume"),
_T("auto"),
_T("__based"),
_T("bool"),
_T("break"),
_T("case"),
_T("catch"),
_T("__cdecl"),
_T("char"),
_T("class"),
_T("const"),
_T("const_cast"),
_T("continue"),
_T("__declspec"),
_T("default"),
_T("delete"),
_T("dllexport"),
_T("dllimport"),
_T("do"),
_T("double"),
_T("dynamic_cast"),
_T("else"),
_T("enum"),
_T("__except"),
_T("explicit"),
_T("extern"),
_T("false"),
_T("__fastcall"),
_T("__finally"),
_T("float"),
_T("for"),
_T("friend"),
_T("goto"),
_T("if"),
_T("inline"),
_T("__inline"),
_T("int"),
_T("__int8"),
_T("__int16"),
_T("__int32"),
_T("__int64"),
_T("interface"),
_T("__leave"),
_T("long"),
_T("main"),
_T("__multiple_inheritance"),
_T("__single_inheritance"),
_T("__virtual_inheritance"),
_T("mutable"),
_T("naked"),
_T("namespace"),
_T("new"),
_T("noreturn"),
_T("operator"),
_T("private"),
_T("protected"),
_T("public"),
_T("register"),
_T("reinterpret_cast"),
_T("return"),
_T("short"),
_T("signed"),
_T("sizeof"),
_T("static"),
_T("static_cast"),
_T("__stdcall"),
_T("struct"),
_T("switch"),
_T("template"),
_T("this"),
_T("thread"),
_T("throw"),
_T("true"),
_T("try"),
_T("__try"),
_T("typedef"),
_T("typeid"),
_T("typename"),
_T("union"),
_T("unsigned"),
_T("using"),
_T("uuid"),
_T("__uuidof"),
_T("virtual"),
_T("void"),
_T("volatile"),
_T("wmain"),
_T("while"),
NULL
};
#define PPD_PRAGMA_INDEX 0
#define PPD_PRAGMA_IF 1
#define PPD_PRAGMA_ELIF 2
#define PPD_INCLUDE_BRACKETS 3
// C++ preprocessor directives (MSVC6.0)
static LPTSTR s_apszPPDirective[] =
{
// preprocessor specific keywords must be first
// Indexes see above
_T("pragma"),
_T("if"),
_T("elif"),
_T("include"),
// preprocessor keywords
_T("define"),
_T("else"),
_T("endif"),
_T("error"),
_T("ifdef"),
_T("ifndef"),
_T("import"),
_T("line"),
_T("undef"),
NULL,
};
static LPTSTR s_apszPPDirective_pragma[] =
{
_T("alloc_text"),
_T("auto_inline"),
_T("bss_seg"),
_T("check_stack"),
_T("code_seg"),
_T("comment"),
_T("component"),
_T("const_seg"),
_T("data_seg"),
_T("function"),
_T("hdrstop"),
_T("include_alias"),
_T("init_seg"),
_T("inline_depth"),
_T("inline_recursion"),
_T("intrinsic"),
_T("message"),
_T("once"),
_T("optimize"),
_T("pack"),
_T("pointers_to_members"),
_T("setlocale"),
_T("vtordisp"),
_T("warning"),
_T("on"),
_T("off"),
_T("pop"),
_T("push"),
_T("disable"),
_T("error"),
NULL,
};
// Second: implement custom GetColor() function
COLORREF CMyCrystalEditView::GetColor(int nColorIndex)
{
switch (nColorIndex)
{
// Basic color
case COLORINDEX_WHITESPACE:
case COLORINDEX_BKGND:
return ::GetSysColor(COLOR_WINDOW);
case COLORINDEX_NORMALTEXT:
return ::GetSysColor(COLOR_WINDOWTEXT);
case COLORINDEX_SELMARGIN:
return ::GetSysColor(COLOR_SCROLLBAR);
case COLORINDEX_SELBKGND:
return RGB(0, 0, 0);
case COLORINDEX_SELTEXT:
return RGB(255, 255, 0);
// Syntax colors
case COLORINDEX_KEYWORD:
return RGB(0, 0, 255);
case COLORINDEX_OPERATOR:
return RGB(0, 0, 128);
case COLORINDEX_COMMENT:
return RGB(0, 128, 0);
case COLORINDEX_NUMBER:
return RGB(128, 0, 128);
case COLORINDEX_STRING:
return RGB(255, 0, 0);
case COLORINDEX_PREPROCESSOR:
return RGB(0, 0, 255);
case COLORINDEX_VARIABLE:
return RGB(0, 0, 0);
// Compiler/debugger colors
// None... (expandable: custom elements are allowed)
}
return RGB(255, 0, 0);
}
// Third: implement helper functions, classes and ParseLine()
class CMapKeywords : public CMapStringToPtr
{
public:
CMapKeywords() : CMapStringToPtr()
{
Init();
};
void Init()
{
for (int L = 0; s_apszCppKeywordList[L] != NULL; L ++)
SetAt(s_apszCppKeywordList[L], s_apszCppKeywordList[L]);
};
inline BOOL IsKeyword(LPCSTR sKeyword)
{
static LPCSTR str;
return Lookup(sKeyword, (void*&)str);
};
} g_MapKeywords;
class CMapPPD_pragma : public CMapStringToPtr
{
public:
CMapPPD_pragma() : CMapStringToPtr()
{
Init();
};
void Init()
{
for (int L = 0; s_apszPPDirective_pragma[L] != NULL; L ++)
SetAt(s_apszPPDirective_pragma[L], s_apszPPDirective_pragma[L]);
};
inline BOOL IsKeyword(LPCSTR sKeyword)
{
static LPCSTR str;
return Lookup(sKeyword, (void*&)str);
};
} g_MapPPD_pragma;
static LPTSTR s_apszPPDirective_if_elif[] =
{
_T("defined"),
NULL,
};
static BOOL IsCppKeyword(LPCTSTR pszChars, int nLength, int nIndexPPD)
{
// Base list
static CHAR chBuff[255];
strncpy(chBuff, pszChars, nLength);
chBuff[nLength] = 0;
if(g_MapKeywords.IsKeyword(chBuff))
return TRUE;
// Preprocessor specific
if (nIndexPPD == PPD_PRAGMA_INDEX)
{
if (g_MapPPD_pragma.IsKeyword(chBuff))
return TRUE;
}
if (nIndexPPD == PPD_PRAGMA_IF || nIndexPPD == PPD_PRAGMA_ELIF)
for (int L = 0; s_apszPPDirective_if_elif[L] != NULL; L ++)
{
if (strncmp(s_apszPPDirective_if_elif[L], pszChars, nLength) == 0
&& s_apszPPDirective_if_elif[L][nLength] == 0)
return TRUE;
}
return FALSE;
}
static BOOL IsCppNumber(LPCTSTR pszChars, int nLength)
{
if ( isdigit(pszChars[0])
|| (nLength >= 2 && pszChars[0] == '.' && isdigit(pszChars[1])))
return TRUE;
return FALSE;
}
static int IsPPDirective(LPCTSTR pszChars, int nLength)
{
// skip leading spaces
for(int S = 0; S < nLength && isspace(pszChars[S]); S ++);
// find preprocessor directive
for (int L = 0; s_apszPPDirective[L] != NULL; L ++)
{
if (strncmp(s_apszPPDirective[L], pszChars + S, nLength - S) == 0
&& s_apszPPDirective[L][nLength - S] == 0)
return L;
}
return -1;
}
#define DEFINE_BLOCK(pos, colorindex)\
ASSERT((pos) >= 0 && (pos) <= nLength);\
if (pBuf != NULL)\
{\
if (nActualItems == 0 || pBuf[nActualItems - 1].m_nCharPos <= (pos)){\
pBuf[nActualItems].m_nCharPos = (pos);\
pBuf[nActualItems].m_nColorIndex = (colorindex);\
nActualItems ++;}\
}
#define COOKIE_COMMENT 0x0001
#define COOKIE_PREPROCESSOR 0x0002
#define CO
|
|
|
|
|
To implement search for matching brace (VC++ style), add a command handler for CCrystalTextView::OnEditFindBrace() and insert the following three new functions:
void CCrystalTextView::OnEditFindBrace()
{
// TODO: Add your command handler code here
char cTarget = '\0'; // One of {}()[]
CString text;
int Direction; // +1: forward, -1: Backward, 0: No brace
CPoint pStart; // Row & column position of target character (1 based)
if (IsSelection())
{ // Take the current selection, if any
CPoint ptSelStart, ptSelEnd;
GetSelection(ptSelStart, ptSelEnd);
if(ptSelStart.y == ptSelEnd.y)
{
GetText(ptSelStart, ptSelEnd, text);
cTarget = text[0];
pStart = ptSelStart; // Place left of character
if(Direction = BraceDirection(cTarget, pStart)) {
FindClosingBrace(cTarget, Direction, pStart);
return;
}
}
}
// Character right of cursor. First priority
if(m_ptCursorPos.x < GetLineLength(m_ptCursorPos.y)) {
GetText(m_ptCursorPos, CPoint(m_ptCursorPos.x+1, m_ptCursorPos.y), text);
if(text.GetLength()) {
cTarget = text[0];
pStart = m_ptCursorPos;
if(Direction = BraceDirection(cTarget, pStart)) {
FindClosingBrace(cTarget, Direction, pStart);
return;
}
}
}
// Search left of cursor
if(m_ptCursorPos.x == 0) return;
// Place left of character
pStart.x = m_ptCursorPos.x-1;
pStart.y = m_ptCursorPos.y;
GetText(pStart, m_ptCursorPos, text);
if(text.GetLength()) {
cTarget = text[0];
if(Direction = BraceDirection(cTarget, pStart)) {
FindClosingBrace(cTarget, Direction, pStart);
return;
}
}
}
int CCrystalTextView::BraceDirection(char cTarget, CPoint &pStart)
{ // Find search direction and adjust starting search position
// pStart = Cursor position (0 based) at left of target character
// Input: pStart must contain the 0-based indices of target character
int nChars = GetLineLength(pStart.y); // Number of chars in line
int nLines = GetLineCount(); // Number of lines
// Backward direction
if(cTarget == '}' || cTarget == ')' || cTarget == ']') {
// Is this the first character?
if(pStart.x == 0 && pStart.y == 0) return 0;
return -1;
}
else // Forward search
if(cTarget == '{' || cTarget == '(' || cTarget == '['){
// Is this the last character?
if(pStart.x == nChars-1 && pStart.y == nLines-1) return 0;
return 1;
}
return 0;
}
BOOL CCrystalTextView::FindClosingBrace(char cTarget, int Direction,
const CPoint &ptStartPosition)
{
// cTarget: target brace whose closing brace is to be found
// CPoint* ptStartPosition: x:character index in line, y: line index (both 0-based)
// ptStartPosition already placed beyond the first target character.
int Count = 0; // Target character will start Count at 1
CPoint ptCurrentPos = ptStartPosition;
CPoint ptFoundPos;
char cClosing;
LPCTSTR pStart, pEnd;
if (cTarget == '}') cClosing = '{';
else if (cTarget == '{') cClosing = '}';
else if (cTarget == ')') cClosing = '(';
else if (cTarget == '(') cClosing = ')';
else if (cTarget == '[') cClosing = ']';
else if (cTarget == ']') cClosing = '[';
ASSERT_VALIDTEXTPOS(ptCurrentPos);
if(Direction < 0) { // Search backward
while (ptCurrentPos.y >= 0)
{
if(ptCurrentPos.x < 0) {
if(--ptCurrentPos.y >= 0)
ptCurrentPos.x = GetLineLength(ptCurrentPos.y)-1; // 0 base
continue;
}
pStart = GetLineChars(ptCurrentPos.y);
pEnd = pStart + ptCurrentPos.x;
int nPos = -1, i = ptCurrentPos.x;
while (pEnd >= pStart) {
if(*pEnd == cTarget) Count++;
else
if (*pEnd == cClosing) {
Count--;
if(Count == 0) {
nPos = i;
break;
}
}
i--; pEnd--;
}
if (nPos >= 0) // Found closing brace!
{
ptCurrentPos.x = nPos;
// High light closing brace here
HighlightText(ptCurrentPos,1);
return TRUE;
}
if(--ptCurrentPos.y >= 0)
ptCurrentPos.x = GetLineLength(ptCurrentPos.y)-1; // 0 base
else return FALSE;
}
// Begin of text reached
return FALSE;
}
else
{ // Search forward
while (ptCurrentPos.y <= GetLineCount() - 1)
{
pStart = (pEnd = GetLineChars(ptCurrentPos.y)) + ptCurrentPos.x;
int Len = GetLineLength(ptCurrentPos.y);
if (Len == 0 ) // Empty line
{
ptCurrentPos.x = 0;
ptCurrentPos.y ++;
continue;
}
pEnd += (Len - 1);
// Perform search in the line
int nPos = -1, i = ptCurrentPos.x;
while (pStart <= pEnd) {
if(*pStart == cTarget) Count++;
else
if (*pStart == cClosing) {
Count--;
if(Count == 0) {
nPos = i;
break;
}
}
if(pStart == pEnd) break;
i++; pStart++;
}
if (nPos >= 0) // Found closing brace!
{
ptCurrentPos.x = nPos;
// High light closing brace here
HighlightText(ptCurrentPos,1);
return TRUE;
}
// Go further, text was not found
ptCurrentPos.x = 0;
ptCurrentPos.y ++;
}
// End of text reached
return FALSE;
}
ASSERT(FALSE); // Unreachable
return FALSE;
|
|
|
|
|
Hello,
Nice work. Unfortunately, we have one in the upcoming update codes. But, I will study it to see if this implementation is better or if there is something that can be used from this code.
Sorry, for my inability to release the codes in time, but I am working around the clock to put all in place and release the codes for discussions soon.
Regards,
Paul.
|
|
|
|
|
Run the demo project and enter following line into the view:
dgdfddDGBsrdg{} DGB
Search for the string "DGB" with paramters 'match case' AND 'whole word' set. This will
result in highlighting " DG". Bug.
Andreas
|
|
|
|
|
Good work,
It is really a funny bug. It only happens with three or less characters. With four or more characters no problem
I will take a closer look.
Regards,
Paul.
|
|
|
|
|
Hello Andreas,
I have found a fix (probably!) but it needs more test.
Locate the following lines in the static FindStringHelper()
method and correct it as shown here...
if (pszPos > pszFindWhere && (_istalnum(pszPos[-1]) || pszPos[-1] == _T('_')))
{
nCur += (pszPos - pszFindWhere + 1);
pszFindWhere = pszPos + 1;
continue;
}
Please give it a little test and give me any feedback on adverse effects.
Best regards,
Paul.
|
|
|
|
|
Hello,
Is there a new version out there? Is there a new version in development? I and many others would like to know.
Have a nice day and thanks,
Jef
|
|
|
|
|
This message is directed at the author, Andrei. Andrei, your editor was published on the web a few years ago. I'm wondering why you have not updated it with more enhancements. This is one of the better sources on the net, and I know that I'm not alone in that belief. Is this source a part of a commercial product? Do you intend to enhance it? Many others have but I want to hear your thoughts on the future of your Crystal Edit project
|
|
|
|
|
I read in the limitations that the editor does not support word wrap. Yes, this would be a desirable feature. Since,
there is no word wrap is there anyway to specify the maximum column length? I would like to have a fixed line length of 80 characters
|
|
|
|
|
You can lookup at the MFC superpad sample provided in MSDN 6
It use a kind of Clip method avalaible in the CEdit class
It looks very simple and code will fit in few lines
En esperant que cela vous soit utile
|
|
|
|
|
Hi everyone,
I was wondering if anybody knows of a HTML parser that I can use in CrystalEdit, I know of http://codeguru.earthweb.com/mfc/comments/2006.shtml but if someone has someting a bit more advanced I would be very thankful if you could send it to me. My attempts to code one myself have not been sucessful.
Thanks for listening & regards,
Jef
|
|
|
|
|
Hello Jeff,
Please give this another attempt. It is a little bit fun to get it working for you. I have not devoted time to writing parsers yet, but planned to.
There is only one parsing entering, which the framework calls. You examine the text passed to you and tell the framework the color to use, simple eh?
You need to keep track of given syntax, like "<"? serve a cookie, the next time the frame work calls you it comes with the cookie. You examine it and take the necessary action. You finish with a cookie remove it. It is very interactive and fun.
Please play with this a bit. You may come out with something to give to others. You have the one at the codeguru site to study. A good HTML parser must support embedded languages like JavaScript, VBScript.
Good luck,
Paul.
|
|
|
|
|
Hi,
Alright I guess I will have to write on myself. I already have got a UI working for my HTML editor but it needs more features. If I’m going to take on HTML I might as well make a parser for CSS, JS, Java, ASP, CGI, PHP3, and HTML (which includes embedded JavaScript and VBScript) When I finish I’ll put them up on my website (which I haven’t made yet) another addition I am going to sometime add is a way to change parsers (i.e. if the user opens a *.html file they will have HTML colors, likewise if the open a *.css stylesheet it will use CSS colors. I am also going to add a way to have web projects, this is basically a list of files and where they are, kind of like DevStudio workspaces, along with this I am going to have a task manager, I also am going to have a way to quickly preview your work in the default browser. The W3C has a HTML Tidy program that I plan to modify and use in my program (http://www.w3.org/People/Raggett/tidy/). This is beginning to sound like the complete web developers suite! And oh yeah I also am going to hopefully include a FTP client, I know of a few open source ones.
Have a nice day!,
Jef
|
|
|
|
|
[dont' forget a mailing list i will implicitly subscribe of course. ( ?ugh ?h ?h ?ugh ?ugh (-?). a fervent supporter]
PCYacc distributes (freely an HTML) lex and Yacc for HTML 3.2 at least but i don't remember the exact version. i have also found a lex and yacc file for CSS.
i have the code if anyone is interested for them mel me
This could make cool stuff, i belive but i just get the crysparser so perharps more explanations|ideas next time.
thanks also to
www.bumblebeesoftware.com for Lex&Yacc add-on for VC++
Salu
|
|
|
|
|
CodeMax version 2.0 from WinMain software will be out the end of June 2000. The web site (www.winmain.com) promises free licensing along with the free source code for the new version. I have been using this control for over a year and have found it to be very easy to use and very robust
|
|
|
|
|
Hi Everybody,
I'd just like to let everyone know that CodeMax 2.0 will be free and can be downloaded free of charge starting 6/30/2000
Jef
|
|
|
|
|
Hello Jeff,
Nice and thank you.
I work on crystal editor and will welcome the competition. Will this include the complete source codes too?
The next update to crystal packs a lot of punch and the current CodeMax 2.0b is no march ^_^. We are in for the fun. I will definately use the CodeMax, where required--mostly in non-MFC applications. I love the SDK, and could help contribute some codes too. WTL applications will also benefits a lot from this offer.
To the success of open-sources.
Regards,
Paul.
|
|
|
|
|
Sorry but I am not the author of CodeMax, I assume that it does include the source code, I was just leting everyone know about it. I'm looking foward to the new Crystal edit, it's kind of easy to see why codemax is free
-Jef
|
|
|
|
|
Its now September - how is the new Crystal veriosn coming along? Need any testers - if so tell me where to email?
Is there a home page for Crystal Edit?
Cheers,
Chri
|
|
|
|
|
Hello Chris,
Work on crystal editor stopped for sometime. I had to work on another project with a friend. I am now back to work on
the crystal editor and just resolved the main headache, regular expressions. Most of the implementations, C-based are just short of what I needed. The best also turns up to add too much to the kernel resulting in a 216 KB of DLL of regular expression library alone.
After a careful considerations and a discussion with one of the mailing list member, I decided to link the library statically (a test performed by the member revealed that it adds only 30 KB if only the low-level classes are used). We can then build a wrapper to the low-level classes to expose the regular expression interface in the crystal editor DLL (in fact, this is the plan so that using the DLL you also have access to the regular expressions in your own application).
Please watch out for the news on the update soon. There is currently some problems with the static link when one builds a static library from the regular expression classes, But I do not want to just add the 16+ files regular expression classes to the crystal editor project. It could become a little difficult to handle.
Regards,
Paul.
|
|
|
|
|
Some body has told me about another code editor, I havent looked at the soucre that much but I think crystal is a lot more straight foward:
Scintilla and SciTE by Neil Hodgson, Look at:
http://www.scintilla.org
--Jef
|
|
|
|
|
This is a great tool. I am going to use it for my project. However I found a (font)resource leaking problem when trying to implement Crystal view with line number(from Paul D. Biolchini).
Here is my fixed:
void CCrystalTextView::DrawSingleLine(CDC *pdc, const CRect &rc, int nLineIndex)
{
.....
CFont *pOldFont = NULL; //rob
if (nBlocks > 0)
{
ASSERT(pBuf[0].m_nCharPos >= 0 && pBuf[0].m_nCharPos <= nLength);
if (crText == CLR_NONE)
pdc->SetTextColor(GetColor(COLORINDEX_NORMALTEXT));
//pdc->SelectObject(GetFont(GetItalic(COLORINDEX_NORMALTEXT), GetBold(COLORINDEX_NORMALTEXT))); //rob
pOldFont = pdc->SelectObject(GetFont(GetItalic(COLORINDEX_NORMALTEXT), GetBold(COLORINDEX_NORMALTEXT)));//rob
DrawLineHelper(....)
....
}
else
{
if (crText == CLR_NONE)
pdc->SetTextColor(GetColor(COLORINDEX_NORMALTEXT));
pdc->SelectObject(GetFont(GetItalic(COLORINDEX_NORMALTEXT), GetBold(COLORINDEX_NORMALTEXT))); //rob
pOldFont = pdc->SelectObject(GetFont(GetItalic(COLORINDEX_NORMALTEXT), GetBold(COLORINDEX_NORMALTEXT)));//rob
DrawLineHelper(pdc, origin, rc, COLORINDEX_NORMALTEXT, pszChars, 0, nLength, CPoint(0, nLineIndex));
}
if(pOldFont) //rob
pdc->SelectObject(pOldFont);//rob
.....
|
|
|
|
|
Can you explain just how your undo/redo works.
I am trying to do the following:
BeginUndoGroup()
..
InsertText();
..
DeleteText();
..
InsertText();
..
FlushUndoGroup();
where I want the undo/redo buttons to treat the three / n operations as one. It works, but I got an assertion in
void CCrystalTextBuffer::FlushUndoGroup(CCrystalTextView *pSource)
{
ASSERT(m_bUndoGroup);
if (pSource != NULL)
{
int nUndoBufSize = m_aUndoBuf.GetSize();
// ASSERT(m_nUndoPosition == m_aUndoBuf.GetSize()); // commented out. Got to figure out how to resolve this.
if (m_nUndoPosition > 0)
{
m_bUndoBeginGroup = TRUE;
pSource->OnEditOperation(m_aUndoBuf[m_nUndoPosition - 1].m_nAction, m_aUndoBuf[m_nUndoPosition - 1].GetText());
}
}
m_bUndoGroup = FALSE;
}
which I commented out. I got everything working now, but feel very uncomfortable not knowing whether I have introduced some serious error in the process.
Basically, why does m_nUndoPosition need to equal m_aUndoBuf.GetSize()?
Navi
|
|
|
|
|
You can get the lastest version of this and much more at
http://www.ff.cuni.cz/~prantl/welcome.ph
|
|
|
|
|
But if you try to download the file you will get a permission denied error
|
|
|
|
|