wxHTML: preserve TAB characters when copying <pre> content to clipboard (backport from trunk)

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@53318 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Václav Slavík
2008-04-23 11:54:05 +00:00
parent 555accf41f
commit c853b92bdb
5 changed files with 314 additions and 73 deletions

View File

@@ -132,6 +132,7 @@ All (GUI):
- Made wxSpinCtrl::Reparent() in MSW and generic versions (Angelo Mottola). - Made wxSpinCtrl::Reparent() in MSW and generic versions (Angelo Mottola).
- Fixed timing of malformed animated GIFs in wxHTML (Gennady Feller). - Fixed timing of malformed animated GIFs in wxHTML (Gennady Feller).
- Fixed incorrect layout width caching in wxHTML (Jeff Tupper). - Fixed incorrect layout width caching in wxHTML (Jeff Tupper).
- wxHTML: preserve TAB characters when copying <pre> content to clipboard.
All (Unix): All (Unix):

View File

@@ -145,11 +145,26 @@ public:
// creates font depending on m_Font* members. // creates font depending on m_Font* members.
virtual wxFont* CreateCurrentFont(); virtual wxFont* CreateCurrentFont();
#if wxABI_VERSION >= 20808
enum WhitespaceMode
{
Whitespace_Normal, // normal mode, collapse whitespace
Whitespace_Pre // inside <pre>, keep whitespace as-is
};
// change the current whitespace handling mode
void SetWhitespaceMode(WhitespaceMode mode);
WhitespaceMode GetWhitespaceMode() const;
#endif // wxABI_VERSION >= 20808
protected: protected:
virtual void AddText(const wxChar* txt); virtual void AddText(const wxChar* txt);
private: private:
void DoAddText(wxChar *temp, int& templen, wxChar nbsp); void FlushWordBuf(wxChar *temp, int& templen);
void AddWord(wxHtmlWordCell *c);
void AddWord(const wxString& word);
void AddPreBlock(const wxString& text);
bool m_tmpLastWasSpace; bool m_tmpLastWasSpace;
wxChar *m_tmpStrBuf; wxChar *m_tmpStrBuf;
@@ -206,7 +221,22 @@ private:
wxEncodingConverter *m_EncConv; wxEncodingConverter *m_EncConv;
#endif #endif
wxHtmlWordCell *m_lastWordCell; struct TextParsingState
{
// current whitespace handling mode
WhitespaceMode m_whitespaceMode;
wxHtmlWordCell *m_lastWordCell;
// current position on line, in num. of characters; used to properly
// expand TABs; only updated while inside <pre>
int m_posColumn;
};
// NB: this pointer replaces m_lastWordCell pointer in wx<=2.8.7; this
// way, wxHtmlWinParser remains ABI compatible with older versions
// despite addition of two fields in TextParsingState
TextParsingState *m_textParsingState;
DECLARE_NO_COPY_CLASS(wxHtmlWinParser) DECLARE_NO_COPY_CLASS(wxHtmlWinParser)
}; };

View File

@@ -631,6 +631,8 @@ wxString wxHtmlWordCell::ConvertToText(wxHtmlSelection *s) const
{ {
int part1 = priv.x; int part1 = priv.x;
int part2 = priv.y; int part2 = priv.y;
if ( part1 == part2 )
return wxEmptyString;
return m_Word.Mid(part1, part2-part1); return m_Word.Mid(part1, part2-part1);
} }
//else: return the whole word below //else: return the whole word below

View File

@@ -28,11 +28,13 @@
FORCE_LINK_ME(m_pre) FORCE_LINK_ME(m_pre)
// replaces '\t', ' ' and '\n' with HTML markup: // replaces '\t', ' ' and '\n' with HTML markup:
static wxString LINKAGEMODE HtmlizeWhitespaces(const wxString& str) static wxString LINKAGEMODE HtmlizeLinebreaks(const wxString& str)
{ {
wxString out; wxString out;
out.reserve(str.length()); // we'll certainly need at least that
size_t len = str.Len(); size_t len = str.Len();
size_t linepos = 0;
for (size_t i = 0; i < len; i++) for (size_t i = 0; i < len; i++)
{ {
switch (str[i]) switch (str[i])
@@ -44,24 +46,11 @@ static wxString LINKAGEMODE HtmlizeWhitespaces(const wxString& str)
} }
out << wxT('>'); out << wxT('>');
break; break;
case wxT(' '):
out << wxT("&nbsp;");
linepos++;
break;
case wxT('\n'): case wxT('\n'):
out << wxT("<br>"); out << wxT("<br>");
linepos = 0;
break;
case wxT('\t'):
{
for (size_t j = 8 - linepos % 8; j > 0; j--)
out << wxT("&nbsp;");
linepos += 8 - linepos % 8;
}
break; break;
default: default:
out << str[i]; out << str[i];
linepos++;
break; break;
} }
} }
@@ -81,13 +70,16 @@ TAG_HANDLER_BEGIN(PRE, "PRE")
{ {
wxHtmlContainerCell *c; wxHtmlContainerCell *c;
int fixed = m_WParser->GetFontFixed(), const int fixed = m_WParser->GetFontFixed();
italic = m_WParser->GetFontItalic(), const int italic = m_WParser->GetFontItalic();
underlined = m_WParser->GetFontUnderlined(), const int underlined = m_WParser->GetFontUnderlined();
bold = m_WParser->GetFontBold(), const int bold = m_WParser->GetFontBold();
fsize = m_WParser->GetFontSize(); const int fsize = m_WParser->GetFontSize();
const wxHtmlWinParser::WhitespaceMode whitespace =
m_WParser->GetWhitespaceMode();
c = m_WParser->GetContainer(); c = m_WParser->GetContainer();
m_WParser->SetWhitespaceMode(wxHtmlWinParser::Whitespace_Pre);
m_WParser->SetFontUnderlined(false); m_WParser->SetFontUnderlined(false);
m_WParser->SetFontBold(false); m_WParser->SetFontBold(false);
m_WParser->SetFontItalic(false); m_WParser->SetFontItalic(false);
@@ -103,12 +95,17 @@ TAG_HANDLER_BEGIN(PRE, "PRE")
c->SetIndent(m_WParser->GetCharHeight(), wxHTML_INDENT_TOP); c->SetIndent(m_WParser->GetCharHeight(), wxHTML_INDENT_TOP);
wxString srcMid = m_WParser->GetInnerSource(tag); wxString srcMid = m_WParser->GetInnerSource(tag);
ParseInnerSource(HtmlizeWhitespaces(srcMid));
// setting Whitespace_Pre mode takes care of spaces and TABs, but
// not linebreaks, so we have to translate them into <br> by
// calling HtmlizeLinebreaks() here
ParseInnerSource(HtmlizeLinebreaks(srcMid));
m_WParser->CloseContainer(); m_WParser->CloseContainer();
m_WParser->CloseContainer(); m_WParser->CloseContainer();
c = m_WParser->OpenContainer(); c = m_WParser->OpenContainer();
m_WParser->SetWhitespaceMode(whitespace);
m_WParser->SetFontUnderlined(underlined); m_WParser->SetFontUnderlined(underlined);
m_WParser->SetFontBold(bold); m_WParser->SetFontBold(bold);
m_WParser->SetFontItalic(italic); m_WParser->SetFontItalic(italic);

View File

@@ -28,6 +28,118 @@
#include "wx/fontmap.h" #include "wx/fontmap.h"
#include "wx/uri.h" #include "wx/uri.h"
//-----------------------------------------------------------------------------
// wxHtmlWordWithTabsCell
//-----------------------------------------------------------------------------
// NB: this is backported from wx-2.9 and moved to this file so that it
// stays private; trunk version is in htmlcell.h/cpp.
// wxHtmlWordCell specialization for storing text fragments with embedded
// '\t's; these differ from normal words in that the displayed text is
// different from the text copied to clipboard
class WXDLLIMPEXP_HTML wxHtmlWordWithTabsCell : public wxHtmlWordCell
{
public:
wxHtmlWordWithTabsCell(const wxString& word,
const wxString& wordOrig,
size_t linepos,
const wxDC& dc)
: wxHtmlWordCell(word, dc),
m_wordOrig(wordOrig),
m_linepos(linepos)
{}
virtual wxString ConvertToText(wxHtmlSelection *sel) const;
protected:
wxString GetPartAsText(int begin, int end) const;
wxString m_wordOrig;
size_t m_linepos;
};
wxString wxHtmlWordWithTabsCell::ConvertToText(wxHtmlSelection *s) const
{
if ( s && (this == s->GetFromCell() || this == s->GetToCell()) )
{
wxPoint priv = this == s->GetFromCell() ? s->GetFromPrivPos()
: s->GetToPrivPos();
// VZ: we may be called before we had a chance to re-render ourselves
// and in this case GetFrom/ToPrivPos() is not set yet -- assume
// that this only happens in case of a double/triple click (which
// seems to be the case now) and so it makes sense to select the
// entire contents of the cell in this case
//
// TODO: but this really needs to be fixed in some better way later...
if ( priv != wxDefaultPosition )
{
int part1 = priv.x;
int part2 = priv.y;
if ( part1 == part2 )
return wxEmptyString;
return GetPartAsText(part1, part2);
}
//else: return the whole word below
}
return m_wordOrig;
}
wxString wxHtmlWordWithTabsCell::GetPartAsText(int begin, int end) const
{
// NB: The 'begin' and 'end' positions are in the _displayed_ text
// (stored in m_Word) and not in the text with tabs that should
// be copied to clipboard (m_wordOrig).
//
// NB: Because selection is performed on displayed text, it's possible
// to select e.g. "half of TAB character" -- IOW, 'begin' and 'end'
// may be in the middle of TAB character expansion into ' 's. In this
// case, we copy the TAB character to clipboard once.
wxASSERT( begin < end );
const unsigned SPACES_PER_TAB = 8;
wxString sel;
int pos = 0;
wxString::const_iterator i = m_wordOrig.begin();
// find the beginning of text to copy:
for ( ; pos < begin; ++i )
{
if ( *i == '\t' )
{
pos += 8 - (m_linepos + pos) % SPACES_PER_TAB;
if ( pos >= begin )
{
sel += '\t';
}
}
else
{
++pos;
}
}
// copy the content until we reach 'end':
for ( ; pos < end; ++i )
{
const wxChar c = *i;
sel += c;
if ( c == '\t' )
pos += 8 - (m_linepos + pos) % SPACES_PER_TAB;
else
++pos;
}
return sel;
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// wxHtmlWinParser // wxHtmlWinParser
@@ -39,6 +151,11 @@ wxList wxHtmlWinParser::m_Modules;
wxHtmlWinParser::wxHtmlWinParser(wxHtmlWindowInterface *wndIface) wxHtmlWinParser::wxHtmlWinParser(wxHtmlWindowInterface *wndIface)
{ {
m_textParsingState = new TextParsingState;
m_textParsingState->m_whitespaceMode = Whitespace_Normal;
m_textParsingState->m_lastWordCell = NULL;
m_textParsingState->m_posColumn = 0;
m_tmpStrBuf = NULL; m_tmpStrBuf = NULL;
m_tmpStrBufSize = 0; m_tmpStrBufSize = 0;
m_windowInterface = wndIface; m_windowInterface = wndIface;
@@ -51,7 +168,6 @@ wxHtmlWinParser::wxHtmlWinParser(wxHtmlWindowInterface *wndIface)
m_InputEnc = wxFONTENCODING_ISO8859_1; m_InputEnc = wxFONTENCODING_ISO8859_1;
m_OutputEnc = wxFONTENCODING_DEFAULT; m_OutputEnc = wxFONTENCODING_DEFAULT;
#endif #endif
m_lastWordCell = NULL;
{ {
int i, j, k, l, m; int i, j, k, l, m;
@@ -98,6 +214,8 @@ wxHtmlWinParser::~wxHtmlWinParser()
delete m_EncConv; delete m_EncConv;
#endif #endif
delete[] m_tmpStrBuf; delete[] m_tmpStrBuf;
delete m_textParsingState;
} }
void wxHtmlWinParser::AddModule(wxHtmlTagsModule *module) void wxHtmlWinParser::AddModule(wxHtmlTagsModule *module)
@@ -212,7 +330,7 @@ void wxHtmlWinParser::InitParser(const wxString& source)
m_ScriptMode = wxHTML_SCRIPT_NORMAL; m_ScriptMode = wxHTML_SCRIPT_NORMAL;
m_ScriptBaseline = 0; m_ScriptBaseline = 0;
m_tmpLastWasSpace = false; m_tmpLastWasSpace = false;
m_lastWordCell = NULL; m_textParsingState->m_lastWordCell = NULL;
// open the toplevel container that contains everything else and that // open the toplevel container that contains everything else and that
// is never closed (this makes parser's life easier): // is never closed (this makes parser's life easier):
@@ -339,79 +457,171 @@ wxFSFile *wxHtmlWinParser::OpenURL(wxHtmlURLType type,
return GetFS()->OpenFile(myurl, flags); return GetFS()->OpenFile(myurl, flags);
} }
void wxHtmlWinParser::SetWhitespaceMode(wxHtmlWinParser::WhitespaceMode mode)
{
m_textParsingState->m_whitespaceMode = mode;
}
wxHtmlWinParser::WhitespaceMode wxHtmlWinParser::GetWhitespaceMode() const
{
return m_textParsingState->m_whitespaceMode;
}
void wxHtmlWinParser::AddText(const wxChar* txt) void wxHtmlWinParser::AddText(const wxChar* txt)
{ {
size_t i = 0, const wxChar nbsp = GetEntitiesParser()->GetCharForCode(160 /* nbsp */);
x,
lng = wxStrlen(txt);
register wxChar d;
int templen = 0;
wxChar nbsp = GetEntitiesParser()->GetCharForCode(160 /* nbsp */);
if (lng+1 > m_tmpStrBufSize) if ( m_textParsingState->m_whitespaceMode == Whitespace_Normal )
{ {
delete[] m_tmpStrBuf; size_t i = 0,
m_tmpStrBuf = new wxChar[lng+1]; x,
m_tmpStrBufSize = lng+1; lng = wxStrlen(txt);
} int templen = 0;
wxChar *temp = m_tmpStrBuf;
if (m_tmpLastWasSpace) if (lng+1 > m_tmpStrBufSize)
{
while ((i < lng) &&
((txt[i] == wxT('\n')) || (txt[i] == wxT('\r')) || (txt[i] == wxT(' ')) ||
(txt[i] == wxT('\t')))) i++;
}
while (i < lng)
{
x = 0;
d = temp[templen++] = txt[i];
if ((d == wxT('\n')) || (d == wxT('\r')) || (d == wxT(' ')) || (d == wxT('\t')))
{ {
i++, x++; delete[] m_tmpStrBuf;
while ((i < lng) && ((txt[i] == wxT('\n')) || (txt[i] == wxT('\r')) || m_tmpStrBuf = new wxChar[lng+1];
(txt[i] == wxT(' ')) || (txt[i] == wxT('\t')))) i++, x++; m_tmpStrBufSize = lng+1;
} }
else i++; wxChar *temp = m_tmpStrBuf;
if (x) if (m_tmpLastWasSpace)
{ {
temp[templen-1] = wxT(' '); while ((i < lng) &&
DoAddText(temp, templen, nbsp); ((txt[i] == wxT('\n')) || (txt[i] == wxT('\r')) || (txt[i] == wxT(' ')) ||
m_tmpLastWasSpace = true; (txt[i] == wxT('\t')))) i++;
}
while (i < lng)
{
x = 0;
wxChar d = txt[i];
if ((d == wxT('\n')) || (d == wxT('\r')) || (d == wxT(' ')) || (d == wxT('\t')))
{
i++, x++;
while ((i < lng) && ((txt[i] == wxT('\n')) || (txt[i] == wxT('\r')) ||
(txt[i] == wxT(' ')) || (txt[i] == wxT('\t')))) i++, x++;
}
else i++;
if ( d == nbsp )
d = wxT(' ');
temp[templen++] = d;
if (x)
{
temp[templen-1] = wxT(' ');
FlushWordBuf(temp, templen);
m_tmpLastWasSpace = true;
}
}
if (templen && (templen > 1 || temp[0] != wxT(' ')))
{
FlushWordBuf(temp, templen);
m_tmpLastWasSpace = false;
} }
} }
else // m_whitespaceMode == Whitespace_Pre
if (templen && (templen > 1 || temp[0] != wxT(' ')))
{ {
DoAddText(temp, templen, nbsp); if ( wxStrchr(txt, nbsp) != NULL )
{
// we need to substitute spaces for &nbsp; here just like we
// did in the Whitespace_Normal branch above
wxString txt2(txt);
wxChar nbsp_str[2];
nbsp_str[0] = nbsp;
nbsp_str[1] = 0;
txt2.Replace(nbsp_str, wxT(" "));
AddPreBlock(txt2);
}
else
{
AddPreBlock(txt);
}
// don't eat any whitespace in <pre> block
m_tmpLastWasSpace = false; m_tmpLastWasSpace = false;
} }
} }
void wxHtmlWinParser::DoAddText(wxChar *temp, int& templen, wxChar nbsp) void wxHtmlWinParser::FlushWordBuf(wxChar *buf, int& len)
{ {
temp[templen] = 0; buf[len] = 0;
templen = 0;
#if !wxUSE_UNICODE #if !wxUSE_UNICODE
if (m_EncConv) if (m_EncConv)
m_EncConv->Convert(temp); m_EncConv->Convert(buf);
#endif #endif
size_t len = wxStrlen(temp);
for (size_t j = 0; j < len; j++)
{
if (temp[j] == nbsp)
temp[j] = wxT(' ');
}
wxHtmlCell *c = new wxHtmlWordCell(temp, *(GetDC())); AddWord(wxString(buf, len));
len = 0;
}
void wxHtmlWinParser::AddWord(const wxString& word)
{
AddWord(new wxHtmlWordCell(word, *(GetDC())));
}
void wxHtmlWinParser::AddWord(wxHtmlWordCell *c)
{
ApplyStateToCell(c); ApplyStateToCell(c);
m_Container->InsertCell(c); m_Container->InsertCell(c);
((wxHtmlWordCell*)c)->SetPreviousWord(m_lastWordCell); c->SetPreviousWord(m_textParsingState->m_lastWordCell);
m_lastWordCell = (wxHtmlWordCell*)c; m_textParsingState->m_lastWordCell = c;
}
void wxHtmlWinParser::AddPreBlock(const wxString& text)
{
if ( text.find(wxT('\t')) != wxString::npos )
{
wxString text2;
text2.reserve(text.length());
const wxString::const_iterator end = text.end();
wxString::const_iterator copyFrom = text.begin();
size_t posFrom = 0;
size_t pos = 0;
int posColumn = m_textParsingState->m_posColumn;
for ( wxString::const_iterator i = copyFrom; i != end; ++i, ++pos )
{
if ( *i == wxT('\t') )
{
if ( copyFrom != i )
text2.append(copyFrom, i);
const unsigned SPACES_PER_TAB = 8;
const size_t expandTo = SPACES_PER_TAB - posColumn % SPACES_PER_TAB;
text2.append(expandTo, wxT(' '));
posColumn += expandTo;
copyFrom = i + 1;
posFrom = pos + 1;
}
else
{
++posColumn;
}
}
if ( copyFrom != text.end() )
text2.append(copyFrom, text.end());
AddWord(new wxHtmlWordWithTabsCell(text2, text,
m_textParsingState->m_posColumn,
*(GetDC())));
m_textParsingState->m_posColumn = posColumn;
}
else
{
// no special formatting needed
AddWord(text);
m_textParsingState->m_posColumn += text.length();
}
} }
@@ -420,6 +630,7 @@ wxHtmlContainerCell* wxHtmlWinParser::OpenContainer()
{ {
m_Container = new wxHtmlContainerCell(m_Container); m_Container = new wxHtmlContainerCell(m_Container);
m_Container->SetAlignHor(m_Align); m_Container->SetAlignHor(m_Align);
m_textParsingState->m_posColumn = 0;
m_tmpLastWasSpace = true; m_tmpLastWasSpace = true;
/* to avoid space being first character in paragraph */ /* to avoid space being first character in paragraph */
return m_Container; return m_Container;