more work on horz textctrl scrolling

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/wxUNIVERSAL@8412 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2000-09-25 00:20:56 +00:00
parent b5366def43
commit dd27aca4ee
6 changed files with 275 additions and 151 deletions

1
TODO
View File

@@ -7,6 +7,7 @@ All
* write sample testing all listbox styles/events
+ text ctrl horz scrolling
* text ctrl wxTE_XXX styles support
* listbox inc kbd search
MSW

View File

@@ -56,6 +56,17 @@ class WXDLLEXPORT wxCaret;
#define wxACTION_TEXT_SEL_WORD _T("wordsel")
#define wxACTION_TEXT_SEL_LINE _T("linesel")
// ----------------------------------------------------------------------------
// wxTextCtrl::HitTest return values
// ----------------------------------------------------------------------------
enum wxTextCtrlHitTestResult
{
wxTE_HT_BEFORE = -1,
wxTE_HT_ON_TEXT,
wxTE_HT_AFTER
};
// ----------------------------------------------------------------------------
// wxTextCtrl
// ----------------------------------------------------------------------------
@@ -170,13 +181,31 @@ public:
void RemoveSelection();
wxString GetSelectionText() const;
// find the character at this position, return TRUE if the character is
// really there or FALSE if it the position is beyond the end of line/text
// and the returned character is the last one
bool HitTest(const wxPoint& pt, long *col, long *row) const;
// find the character at this position, return 0 if the character is
// really there, -1 if the point is before the beginning of the text/line
// and the returned character is the first one to follow it or +1 if it the
// position is beyond the end of line/text and the returned character is
// the last one
//
// NB: pt is in device coords (not adjusted for the client area origin nor
// for the scrolling)
wxTextCtrlHitTestResult HitTest(const wxPoint& pt,
long *col, long *row) const;
// scroll the window horizontally
void ScrollText(wxCoord x);
// find the character at this position in the given line, return value as
// for HitTest()
//
// NB: x is the logical coord (client and unscrolled)
wxTextCtrlHitTestResult HitTestLine(const wxString& line,
wxCoord x,
long *colOut) const;
// bring the given position into view
void ShowHorzPosition(wxCoord pos);
// scroll the window horizontally so that the first character shown is in
// position pos
void ScrollText(long col);
// adjust the DC for horz text control scrolling too
virtual void DoPrepareDC(wxDC& dc);
@@ -220,8 +249,8 @@ protected:
// get the extent (width) of the text
wxCoord GetTextWidth(const wxString& text) const;
// refresh the text in the given range (in client coords) of this line
void RefreshLine(long line, wxCoord from, wxCoord to);
// refresh the text in the given range (in text coords) of this line
void RefreshLine(long line, long from, long to);
// get the text to show: either the text itself or the text replaced with
// starts for wxTE_PASSWORD control
@@ -261,7 +290,8 @@ private:
wxRect m_rectText;
// for the controls without horz scrollbar: the offset by which the window
// is scrolled to the right
// is scrolled to the right and the first visible position
long m_colStart;
wxCoord m_ofsHorz;
DECLARE_EVENT_TABLE()

View File

@@ -224,7 +224,14 @@ bool MyUnivApp::OnInit()
// ----------------------------------------------------------------------------
MyUnivFrame::MyUnivFrame(const wxString& title)
: wxFrame(NULL, -1, title, wxDefaultPosition, wxSize(700, 700))
: wxFrame(NULL, -1, title,
wxDefaultPosition,
#if 0
wxSize(700, 700)
#else
wxSize(240, 150)
#endif
)
{
SetBackgroundColour(wxGetApp().GetBgColour());

View File

@@ -70,6 +70,7 @@ void wxTextCtrl::Init()
m_isModified = FALSE;
m_isEditable = TRUE;
m_colStart = 0;
m_ofsHorz = 0;
m_curPos =
@@ -136,10 +137,6 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
wxCHECK_RET( from >= 0 && to >= 0 && from <= to && to <= GetLastPosition(),
_T("invalid range in wxTextCtrl::Replace") );
// remember the line width as we may need to refresh beyond the end of the
// new text if the text shrinks
wxCoord widthOld = GetTextWidth(m_value);
// replace the part of the text with the new value
wxString valueNew(m_value, (size_t)from);
valueNew += text;
@@ -157,9 +154,7 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text)
ClearSelection();
// refresh to the end of the line
wxCoord start = GetTextWidth(m_value.Left((size_t)from));
wxCoord end = GetTextWidth(m_value);
RefreshLine(0, start, wxMax(end, widthOld));
RefreshLine(0, from, -1);
// and the affected parts of the next line (TODO)
}
@@ -214,31 +209,7 @@ void wxTextCtrl::SetInsertionPoint(long pos)
// the current position should always be shown, scroll the window
// for this if necessary
wxCoord x = GetCaretPosition();
static const wxCoord MARGIN = 3;
if ( x < wxMax(0, m_ofsHorz + MARGIN) )
{
// scroll backwards
ScrollText(x - MARGIN);
}
else
{
wxCoord width = m_rectText.width;
if ( !width )
{
// if we are called from the ctor, m_rectText is not
// initialized yet, so do it now
UpdateTextRect();
width = m_rectText.width;
}
if ( x > m_ofsHorz + width - MARGIN )
{
// scroll forward
ScrollText(x - width + MARGIN);
}
}
ShowHorzPosition(GetCaretPosition());
}
else // multi line
{
@@ -732,36 +703,12 @@ wxCoord wxTextCtrl::GetTextWidth(const wxString& text) const
return w;
}
bool wxTextCtrl::HitTest(const wxPoint& pos, long *colOut, long *rowOut) const
wxTextCtrlHitTestResult wxTextCtrl::HitTestLine(const wxString& line,
wxCoord x,
long *colOut) const
{
// is the point in the text area or to the right or below it?
bool inside = TRUE;
wxTextCtrlHitTestResult res = wxTE_HT_ON_TEXT;
int x, y;
wxPoint pt = GetClientAreaOrigin();
pt += m_rectText.GetPosition();
CalcUnscrolledPosition(pos.x - pt.x, pos.y - pt.y, &x, &y);
// row calculation is simple as we assume that all lines have the same
// height
int row = y / GetCharHeight();
int rowMax = GetNumberOfLines() - 1;
if ( row > rowMax )
{
// clicking below the text is the same as clicking on the last line
row = rowMax;
inside = FALSE;
}
else if ( row < 0 )
{
row = 0;
inside = FALSE;
}
// if the line is empty, the column can only be the first one
wxString line = GetTextToShow(GetLineText(row));
int col;
wxTextCtrl *self = wxConstCast(this, wxTextCtrl);
wxClientDC dc(self);
@@ -776,13 +723,13 @@ bool wxTextCtrl::HitTest(const wxPoint& pos, long *colOut, long *rowOut) const
// the end of it
col = line.length();
inside = FALSE;
res = wxTE_HT_AFTER;
}
else if ( x < 0 )
{
col = 0;
inside = FALSE;
res = wxTE_HT_BEFORE;
}
else // we're inside the line
{
@@ -818,13 +765,11 @@ bool wxTextCtrl::HitTest(const wxPoint& pos, long *colOut, long *rowOut) const
if ( matchDir == 1 )
{
// we were going to the right and, finally, moved beyond
// the original position: so the current is the next after
// the correct one
col--;
// the original position - stop here
break;
}
else if ( matchDir == 0 )
if ( matchDir == 0 )
{
// we just started iterating, now we know that we should
// move to the left
@@ -834,13 +779,13 @@ bool wxTextCtrl::HitTest(const wxPoint& pos, long *colOut, long *rowOut) const
}
else // width < x
{
// same logic as above ...
// same logic as above
if ( matchDir == -1 )
{
// ... except that we don't need to back track
break;
}
else if ( matchDir == 0 )
if ( matchDir == 0 )
{
// go to the right
matchDir = 1;
@@ -859,20 +804,121 @@ bool wxTextCtrl::HitTest(const wxPoint& pos, long *colOut, long *rowOut) const
if ( colOut )
*colOut = col;
return res;
}
wxTextCtrlHitTestResult wxTextCtrl::HitTest(const wxPoint& pos,
long *colOut, long *rowOut) const
{
// is the point in the text area or to the right or below it?
wxTextCtrlHitTestResult res = wxTE_HT_ON_TEXT;
// we accept the window coords and adjust for both the client and text area
// offsets ourselves
int x, y;
wxPoint pt = GetClientAreaOrigin();
pt += m_rectText.GetPosition();
CalcUnscrolledPosition(pos.x - pt.x, pos.y - pt.y, &x, &y);
// row calculation is simple as we assume that all lines have the same
// height
int row = y / GetCharHeight();
int rowMax = GetNumberOfLines() - 1;
if ( row > rowMax )
{
// clicking below the text is the same as clicking on the last line
row = rowMax;
res = wxTE_HT_AFTER;
}
else if ( row < 0 )
{
row = 0;
res = wxTE_HT_BEFORE;
}
// now find the position in the line
wxTextCtrlHitTestResult res2 =
HitTestLine(GetTextToShow(GetLineText(row)), x, colOut);
if ( res == wxTE_HT_ON_TEXT )
{
res = res2;
}
if ( rowOut )
*rowOut = row;
return inside;
return res;
}
// ----------------------------------------------------------------------------
// scrolling
// ----------------------------------------------------------------------------
void wxTextCtrl::ScrollText(wxCoord x)
/*
wxTextCtrl has not one but two scrolling mechanisms: one is semi-automatic
scrolling in both horizontal and vertical direction implemented using
wxScrollHelper and the second one is manual scrolling implemented using
m_ofsHorz and used by the single line controls without scroll bar.
The first version (the standard one) always scrolls by fixed amount which is
fine for vertical scrolling as all lines have the same height but is rather
ugly for horizontal scrolling if proportional font is used. This is why we
manually update and use m_ofsHorz which contains the length of the string
which is hidden beyond the left borde. An important property of text
controls using this kind of scrolling is that an entire number of characters
is always shown and that parts of characters never appear on display -
neither in the leftmost nor rightmost positions.
Once again, for multi line controls m_ofsHorz is always 0 and scrolling is
done as usual for wxScrollWindow.
*/
void wxTextCtrl::ShowHorzPosition(wxCoord pos)
{
if ( pos < m_ofsHorz )
{
// scroll backwards
long col;
HitTestLine(GetValue(), pos, &col);
ScrollText(col);
}
else
{
wxCoord width = m_rectText.width;
if ( !width )
{
// if we are called from the ctor, m_rectText is not initialized
// yet, so do it now
UpdateTextRect();
width = m_rectText.width;
}
if ( pos > width )
{
// scroll forward
long col;
HitTestLine(GetValue(), pos - width, &col);
ScrollText(col + 1);
}
}
}
// scroll the window horizontally so that the first visible character becomes
// the one at this position
void wxTextCtrl::ScrollText(long col)
{
wxASSERT_MSG( IsSingleLine(),
_T("ScrollText() is for single line controls only") );
// never scroll beyond the left border
wxCoord ofsHorz = x > 0 ? x : 0;
if ( col < 0 )
col = 0;
wxCoord ofsHorz = GetTextWidth(GetLineText(0).Left(col));
if ( ofsHorz != m_ofsHorz )
{
// NB1: to scroll to the right, offset must be negative, hence the
@@ -882,6 +928,7 @@ void wxTextCtrl::ScrollText(wxCoord x)
// NB2: ScrollWindow() calls Refresh() which results in a call to
// DoDraw(), so we must update m_ofsHorz before calling it
m_ofsHorz = ofsHorz;
m_colStart = col;
ScrollWindow(dx, 0, &m_rectText);
}
@@ -906,16 +953,33 @@ void wxTextCtrl::DoPrepareDC(wxDC& dc)
// refresh
// ----------------------------------------------------------------------------
void wxTextCtrl::RefreshLine(long line, wxCoord from, wxCoord to)
void wxTextCtrl::RefreshLine(long line, long from, long to)
{
wxString text = GetLineText(line);
wxCoord h = GetCharHeight();
wxRect rect;
wxPoint pt = GetClientAreaOrigin() + m_rectText.GetPosition();
rect.x = pt.x + from;
rect.y = pt.y + line*h;
rect.width = to - from + 1;
rect.x = GetTextWidth(text.Left((size_t)from)) - m_ofsHorz;
rect.y = line*h;
if ( to == -1 )
{
// till the end of line
rect.width = m_rectText.width - rect.x;
}
else
{
// only part of line
rect.width = GetTextWidth(text.Mid((size_t)from, (size_t)(to - from + 1)));
}
rect.height = h;
// account for the origin offset
wxPoint pt = GetClientAreaOrigin() + m_rectText.GetPosition();
rect.x += pt.x;
rect.y += pt.y;
wxLogTrace(_T("text"), _T("Refreshing (%d, %d)-(%d, %d)"),
rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
@@ -987,92 +1051,104 @@ void wxTextCtrl::DrawTextLine(wxDC& dc, const wxRect& rect,
}
}
// TODO: don't redraw the parts of line which are not visible because they're
// outside the window (because of horz scrolling)
void wxTextCtrl::DoDraw(wxControlRenderer *renderer)
{
// hide the caret while we're redrawing the window and show it after we are
// done with it
wxCaretSuspend cs(this);
// prepare the DC
wxDC& dc = renderer->GetDC();
dc.SetFont(GetFont());
dc.SetTextForeground(GetForegroundColour());
DoPrepareDC(dc);
// calculate the lines which must be redrawn
// get the intersection of the update region with the text area: note that
// the update region is in window coord and text area is in the client
// ones, so it must be shifted before computing intesection
wxRegion rgnUpdate = GetUpdateRegion();
wxRect rectTextArea = m_rectText;
wxPoint pt = GetClientAreaOrigin();
wxPoint pt = GetClientAreaOrigin();
rectTextArea.x += pt.x;
rectTextArea.y += pt.y;
rgnUpdate.Intersect(rectTextArea);
wxRect rectUpdate = rgnUpdate.GetBox();
pt = rectUpdate.GetPosition();
// FIXME: why is this needed (at least under MSW)?
rectUpdate.Inflate(2, 1);
// debugging trick to see the update rect visually
#if 0
static int s_count = 0;
dc.SetBrush(*(++s_count % 2 ? wxRED_BRUSH : wxGREEN_BRUSH));
dc.SetPen(*wxTRANSPARENT_PEN);
dc.DrawRectangle(rectUpdate);
#endif
// calculate the range of lines to refresh
wxPoint pt1 = rectUpdate.GetPosition();
long colStart, lineStart;
(void)HitTest(pt, &colStart, &lineStart);
(void)HitTest(pt1, &colStart, &lineStart);
wxPoint pt2 = pt1;
pt2.x += rectUpdate.width;
pt2.y += rectUpdate.height;
long colEnd, lineEnd;
pt.x += rectUpdate.width;
pt.y += rectUpdate.height;
(void)HitTest(pt, &colEnd, &lineEnd);
(void)HitTest(pt2, &colEnd, &lineEnd);
wxLogTrace(_T("text"), _T("Redrawing positions (%ld, %ld)-(%ld, %ld)"),
colStart, lineStart, colEnd, lineEnd);
// pt1 and pt2 will run along the left and right update rect borders
// respectively from top to bottom (NB: they're in device coords)
pt2.y = pt1.y;
// prepare for drawing
wxRect rectText;
rectText.x = m_rectText.x;
rectText.width = m_rectText.width;
rectText.height = GetCharHeight();
rectText.y = m_rectText.y + lineStart*rectText.height;
wxString text;
int selStart, selEnd;
// draw the part of the first line
GetSelectedPartOfLine(lineStart, &selStart, &selEnd);
wxString textLine = GetLineText(lineStart);
text = textLine.c_str() + colStart;
if ( lineEnd == lineStart )
// do draw the invalidated parts of each line
// shouldn't be needed: dc.SetClippingRegion(rectUpdate);
DoPrepareDC(dc); // adjust for scrolling
for ( long line = lineStart; line <= lineEnd; line++ )
{
text.Truncate(colEnd - colStart + 1);
}
// calculate the update rect in text positions for this line
if ( HitTest(pt1, &colStart, NULL) == wxTE_HT_AFTER )
{
// the update rect is beyond the end of line, no need to redraw
// anything
continue;
}
rectText.x += GetTextWidth(textLine.Left(colStart));
rectText.width = GetTextWidth(text);
DrawTextLine(dc, rectText, GetTextToShow(text), selStart, selEnd);
rectText.y += rectText.height;
(void)HitTest(pt2, &colEnd, NULL);
wxLogTrace(_T("text"), _T("Start line: positions %ld-%ld redrawn."),
colStart,
lineEnd == lineStart ? colEnd : GetLineLength(lineStart));
// extract the part of line we need to redraw
wxString textLine = GetLineText(line);
wxString text = textLine.Mid(colStart, colEnd - colStart + 1);
// all intermediate lines must be redrawn completely
rectText.x = m_rectText.x;
rectText.width = m_rectText.width;
for ( long line = lineStart + 1; line < lineEnd; line++ )
{
// now deal with the selection
int selStart, selEnd;
GetSelectedPartOfLine(line, &selStart, &selEnd);
DrawTextLine(dc, rectText, GetTextToShow(GetLineText(line)),
selStart, selEnd);
if ( selStart != -1 )
{
// these values are relative to the start of the line while the
// string passed to DrawTextLine() is only part of it, so adjust
// the selection range accordingly
selStart += colStart;
selEnd += colStart;
}
// calculate the logical text coords
rectText.x = m_rectText.x + GetTextWidth(textLine.Left(colStart));
rectText.width = GetTextWidth(text);
// do draw the text
DrawTextLine(dc, rectText, text, selStart, selEnd);
wxLogTrace(_T("text"), _T("Line %ld: positions %ld-%ld redrawn."),
line, colStart, colEnd);
// adjust for the next line
rectText.y += rectText.height;
wxLogTrace(_T("text"), _T("Middle line %ld entirely redrawn."), line);
}
// and draw the part of the last line if it hadn't been drawn yet
if ( lineEnd != lineStart )
{
GetSelectedPartOfLine(lineEnd, &selStart, &selEnd);
text = GetLineText(lineEnd).Left(colEnd);
rectText.y += rectText.height;
DrawTextLine(dc, rectText, GetTextToShow(text),
selStart, selEnd);
wxLogTrace(_T("text"), _T("End line: positions 0-%ld redrawn."),
colEnd);
pt1.y += rectText.height;
pt2.y += rectText.height;
}
}
@@ -1088,11 +1164,12 @@ void wxTextCtrl::ShowCaret(bool show)
wxCaret *caret = GetCaret();
if ( caret )
{
caret->Show(show);
// position it correctly
caret->Move(m_rectText.x + GetCaretPosition() - m_ofsHorz,
m_rectText.y + m_curLine*GetCharHeight());
// and show it there
caret->Show(show);
}
}

View File

@@ -170,10 +170,10 @@ public:
virtual wxRect GetTextTotalArea(const wxTextCtrl *text,
const wxRect& rect)
{ wxRect rectTotal = rect; rectTotal.Inflate(1); return rectTotal; }
{ wxRect rectTotal = rect; rectTotal.Inflate(10); return rectTotal; }
virtual wxRect GetTextClientArea(const wxTextCtrl *text,
const wxRect& rect)
{ wxRect rectText = rect; rectText.Inflate(-1); return rectText; }
{ wxRect rectText = rect; rectText.Inflate(-10); return rectText; }
protected:
// common part of DrawLabel() and DrawItem()

View File

@@ -204,8 +204,13 @@ void wxWindow::OnPaint(wxPaintEvent& event)
bool wxWindow::DoDrawBackground(wxDC& dc)
{
wxRect rect = GetUpdateRegion().GetBox();
// FIXME: leaving this code in leads to partial bg redraws sometimes under
// MSW
wxRect rect;
#ifndef __WXMSW__
rect = GetUpdateRegion().GetBox();
if ( !rect.width && !rect.height )
#endif
{
wxSize size = GetSize();
rect.width = size.x;
@@ -584,9 +589,15 @@ void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
wxLogTrace(_T("scroll"), _T("rect is %dx%d, scroll by %d, %d"),
sizeTotal.x, sizeTotal.y, dx, dy);
// the initial and end point of the region we move in client coords
wxPoint ptSource, ptDest;
if ( rect )
{
ptSource = rect->GetPosition();
ptDest = rect->GetPosition();
}
// the size of this region
wxSize size;
size.x = sizeTotal.x - abs(dx);
size.y = sizeTotal.y - abs(dy);
@@ -600,8 +611,6 @@ void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
else // move the part which doesn't change to the new location
{
wxPoint ptOrigin = GetClientAreaOrigin();
if ( rect )
ptOrigin += rect->GetPosition();
// note that when we scroll the canvas in some direction we move the
// block which doesn't need to be refreshed in the opposite direction
@@ -653,8 +662,8 @@ void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
// it bad?
wxRect rect;
rect.x = ptOrigin.x;
rect.y = ptOrigin.y;
rect.x = ptOrigin.x + ptSource.x;
rect.y = ptOrigin.y + ptSource.y;
if ( dx )
{