Add wxTextCtrl::PositionToCoords() functions for wxMSW and wxGTK.

The new method allows to find the coordinates in pixels of the given character
in a text control.

Closes #13221.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@68450 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2011-07-29 15:11:54 +00:00
parent d298f18ffb
commit 6ce832135e
10 changed files with 304 additions and 0 deletions

View File

@@ -444,6 +444,7 @@ All:
All (GUI):
- Added documented, public wxNavigationEnabled<> class.
- Added wxTextCtrl::PositionToCoords() (Navaneeth).
- Support float, double and file name values in wxGenericValidator (troelsk).
- Fix keyboard navigation in wxGrid with hidden columns (ivan_14_32).
- Add wxDataViewEvent::IsEditCancelled() (Allonii).

View File

@@ -167,6 +167,8 @@ protected:
virtual void DoSetValue(const wxString &value, int flags = 0);
virtual wxPoint DoPositionToCoords(long pos) const;
// wrappers hiding the differences between functions doing the same thing
// for GtkTextView and GtkEntry (all of them use current window style to
// set the given characteristic)

View File

@@ -198,6 +198,8 @@ protected:
virtual void DoSetValue(const wxString &value, int flags = 0);
virtual wxPoint DoPositionToCoords(long pos) const;
// return true if this control has a user-set limit on amount of text (i.e.
// the limit is due to a previous call to SetMaxLength() and not built in)
bool HasSpaceLimit(unsigned int *len) const;

View File

@@ -574,6 +574,11 @@ public:
virtual long XYToPosition(long x, long y) const = 0;
virtual bool PositionToXY(long pos, long *x, long *y) const = 0;
// translate the given position (which is just an index in the text control)
// to client coordinates
wxPoint PositionToCoords(long pos) const;
virtual void ShowPosition(long pos) = 0;
// find the character at position given in pixels
@@ -592,6 +597,13 @@ protected:
virtual bool DoLoadFile(const wxString& file, int fileType);
virtual bool DoSaveFile(const wxString& file, int fileType);
// Return true if the given position is valid, i.e. positive and less than
// the last position.
virtual bool IsValidPosition(long pos) const = 0;
// Default stub implementation of PositionToCoords() always returns
// wxDefaultPosition.
virtual wxPoint DoPositionToCoords(long pos) const;
// the name of the last file loaded with LoadFile() which will be used by
// SaveFile() by default
@@ -625,6 +637,12 @@ public:
wxTextEntryBase::SetValue(value);
}
protected:
virtual bool IsValidPosition(long pos) const
{
return pos >= 0 && pos <= GetLastPosition();
}
private:
wxDECLARE_NO_COPY_CLASS(wxTextCtrlIface);
};
@@ -723,6 +741,12 @@ protected:
virtual bool DoLoadFile(const wxString& file, int fileType);
virtual bool DoSaveFile(const wxString& file, int fileType);
// Another wxTextAreaBase override.
virtual bool IsValidPosition(long pos) const
{
return pos >= 0 && pos <= GetLastPosition();
}
// implement the wxTextEntry pure virtual method
virtual wxWindow *GetEditableWindow() { return this; }

View File

@@ -1348,6 +1348,28 @@ public:
*/
virtual bool PositionToXY(long pos, long* x, long* y) const;
/**
Converts given text position to client coordinates in pixels.
This function allows to find where is the character at the given
position displayed in the text control.
@onlyfor{wxmsw,wxgtk}. Additionally, wxGTK only implements this method
for multiline controls and ::wxDefaultPosition is always returned for
the single line ones.
@param pos
Text position in 0 to GetLastPosition() range (inclusive).
@return
On success returns a wxPoint which contains client coordinates for
the given position in pixels, otherwise returns ::wxDefaultPosition.
@since 2.9.3
@see XYToPosition(), PositionToXY()
*/
wxPoint PositionToCoords(long pos) const;
/**
Saves the contents of the control in a text file.

View File

@@ -128,6 +128,7 @@ public:
void DoSelectText();
void DoMoveToEndOfText();
void DoMoveToEndOfEntry();
void DoGetWindowCoordinates();
// return true if currently text control has any selection
bool HasSelection() const
@@ -217,6 +218,10 @@ public:
void OnMoveToEndOfText( wxCommandEvent& WXUNUSED(event) )
{ m_panel->DoMoveToEndOfText(); }
void OnGetWindowCoordinates( wxCommandEvent& WXUNUSED(event) )
{ m_panel->DoGetWindowCoordinates(); }
void OnMoveToEndOfEntry( wxCommandEvent& WXUNUSED(event) )
{ m_panel->DoMoveToEndOfEntry(); }
@@ -415,6 +420,7 @@ enum
TEXT_ADD_FREEZE,
TEXT_ADD_LINE,
TEXT_MOVE_ENDTEXT,
TEXT_GET_WINDOW_COORD,
TEXT_MOVE_ENDENTRY,
TEXT_SET_EDITABLE,
TEXT_SET_ENABLED,
@@ -513,6 +519,7 @@ bool MyApp::OnInit()
menuText->Append(TEXT_LINE_UP, wxT("Scroll text one line up"));
menuText->Append(TEXT_PAGE_DOWN, wxT("Scroll text one page down"));
menuText->Append(TEXT_PAGE_UP, wxT("Scroll text one page up"));
menuText->Append(TEXT_GET_WINDOW_COORD, wxT("Get window coordinates"));
menuText->AppendSeparator();
menuText->Append(TEXT_GET_LINE, wxT("Get the text of a line of the tabbed multiline"));
menuText->Append(TEXT_GET_LINELENGTH, wxT("Get the length of a line of the tabbed multiline"));
@@ -1318,6 +1325,18 @@ void MyPanel::DoMoveToEndOfText()
m_multitext->SetFocus();
}
void MyPanel::DoGetWindowCoordinates()
{
wxTextCtrl * const text = GetFocusedText();
const wxPoint pt0 = text->PositionToCoords(0);
const wxPoint ptCur = text->PositionToCoords(text->GetInsertionPoint());
*m_log << "Current position coordinates: "
"(" << ptCur.x << ", " << ptCur.y << "), "
"first position coordinates: "
"(" << pt0.x << ", " << pt0.y << ")\n";
}
void MyPanel::DoMoveToEndOfEntry()
{
m_text->SetInsertionPointEnd();
@@ -1380,6 +1399,7 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(TEXT_ADD_FREEZE, MyFrame::OnAddTextFreeze)
EVT_MENU(TEXT_ADD_LINE, MyFrame::OnAddTextLine)
EVT_MENU(TEXT_MOVE_ENDTEXT, MyFrame::OnMoveToEndOfText)
EVT_MENU(TEXT_GET_WINDOW_COORD, MyFrame::OnGetWindowCoordinates)
EVT_MENU(TEXT_MOVE_ENDENTRY, MyFrame::OnMoveToEndOfEntry)
EVT_MENU(TEXT_SET_EDITABLE, MyFrame::OnSetEditable)

View File

@@ -1084,6 +1084,19 @@ wxTextAreaBase::HitTest(const wxPoint& WXUNUSED(pt), long * WXUNUSED(pos)) const
return wxTE_HT_UNKNOWN;
}
wxPoint wxTextAreaBase::PositionToCoords(long pos) const
{
wxCHECK_MSG( IsValidPosition(pos), wxDefaultPosition,
wxS("Position argument out of range.") );
return DoPositionToCoords(pos);
}
wxPoint wxTextAreaBase::DoPositionToCoords(long WXUNUSED(pos)) const
{
return wxDefaultPosition;
}
#else // !wxUSE_TEXTCTRL
// define this one even if !wxUSE_TEXTCTRL because it is also used by other

View File

@@ -1218,6 +1218,37 @@ int wxTextCtrl::GetLineLength(long lineNo) const
}
}
wxPoint wxTextCtrl::DoPositionToCoords(long pos) const
{
if ( !IsMultiLine() )
{
// Single line text entry (GtkTextEntry) doesn't have support for
// getting the coordinates for the given offset. Perhaps we could
// find them ourselves by using GetTextExtent() but for now just leave
// it unimplemented, this function is more useful for multiline
// controls anyhow.
return wxDefaultPosition;
}
// Window coordinates for the given position is calculated by getting
// the buffer coordinates and converting them to window coordinates.
GtkTextView *textview = GTK_TEXT_VIEW(m_text);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_offset(m_buffer, &iter, pos);
GdkRectangle bufferCoords;
gtk_text_view_get_iter_location(textview, &iter, &bufferCoords);
gint winCoordX = 0,
winCoordY = 0;
gtk_text_view_buffer_to_window_coords(textview, GTK_TEXT_WINDOW_WIDGET,
bufferCoords.x, bufferCoords.y,
&winCoordX, &winCoordY);
return wxPoint(winCoordX, winCoordY);
}
int wxTextCtrl::GetNumberOfLines() const
{
if ( IsMultiLine() )

View File

@@ -1489,6 +1489,93 @@ wxTextCtrl::HitTest(const wxPoint& pt, long *posOut) const
return rc;
}
wxPoint wxTextCtrl::DoPositionToCoords(long pos) const
{
// FIXME: This code is broken for rich edit version 2.0 as it uses the same
// API as plain edit i.e. the coordinates are returned directly instead of
// filling the POINT passed as WPARAM with them but we can't distinguish
// between 2.0 and 3.0 unfortunately (see also the use of EM_POSFROMCHAR
// above).
#if wxUSE_RICHEDIT
if ( IsRich() )
{
POINT pt;
LRESULT rc = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, (WPARAM)&pt, pos);
if ( rc != -1 )
return wxPoint(pt.x, pt.y);
}
else
#endif // wxUSE_RICHEDIT
{
LRESULT rc = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, pos, 0);
if ( rc == -1 )
{
// Finding coordinates for the last position of the control fails
// in plain EDIT control, try to compensate for it by finding it
// ourselves from the position of the previous character.
if ( pos < GetLastPosition() )
{
// It's not the expected correctable failure case so just fail.
return wxDefaultPosition;
}
if ( pos == 0 )
{
// We're being asked the coordinates of the first (and last and
// only) position in an empty control. There is no way to get
// it directly with EM_POSFROMCHAR but EM_GETMARGINS returns
// the correct value for at least the horizontal offset.
rc = ::SendMessage(GetHwnd(), EM_GETMARGINS, 0, 0);
// Text control seems to effectively add 1 to margin.
return wxPoint(LOWORD(rc) + 1, 1);
}
// We do have a previous character, try to get its coordinates.
rc = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, pos - 1, 0);
if ( rc == -1 )
{
// If getting coordinates of the previous character failed as
// well, just give up.
return wxDefaultPosition;
}
wxString prevChar = GetRange(pos - 1, pos);
wxSize prevCharSize = GetTextExtent(prevChar);
if ( prevChar == wxT("\n" ))
{
// 'pos' is at the beginning of a new line so its X coordinate
// should be the same as X coordinate of the first character of
// any other line while its Y coordinate will be approximately
// (but we can't compute it exactly...) one character height
// more than that of the previous character.
LRESULT coords0 = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, 0, 0);
if ( coords0 == -1 )
return wxDefaultPosition;
rc = MAKELPARAM(LOWORD(coords0), HIWORD(rc) + prevCharSize.y);
}
else
{
// Simple case: previous character is in the same line so this
// one is just after it.
rc += MAKELPARAM(prevCharSize.x, 0);
}
}
// Notice that {LO,HI}WORD macros return WORDs, i.e. unsigned shorts,
// while we want to have signed values here (the y coordinate of any
// position above the first currently visible line is negative, for
// example), hence the need for casts.
return wxPoint(static_cast<short>(LOWORD(rc)),
static_cast<short>(HIWORD(rc)));
}
return wxDefaultPosition;
}
// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------

View File

@@ -59,6 +59,9 @@ private:
CPPUNIT_TEST( Style );
CPPUNIT_TEST( Lines );
CPPUNIT_TEST( LogTextCtrl );
CPPUNIT_TEST( PositionToCoords );
CPPUNIT_TEST( PositionToCoordsRich );
CPPUNIT_TEST( PositionToCoordsRich2 );
CPPUNIT_TEST_SUITE_END();
void MultiLineReplace();
@@ -71,6 +74,11 @@ private:
void Style();
void Lines();
void LogTextCtrl();
void PositionToCoords();
void PositionToCoordsRich();
void PositionToCoordsRich2();
void DoPositionToCoordsTestWithStyle(long style);
wxTextCtrl *m_text;
@@ -422,4 +430,98 @@ void TextCtrlTestCase::LogTextCtrl()
CPPUNIT_ASSERT(!m_text->IsEmpty());
}
void TextCtrlTestCase::PositionToCoords()
{
DoPositionToCoordsTestWithStyle(0);
}
void TextCtrlTestCase::PositionToCoordsRich()
{
DoPositionToCoordsTestWithStyle(wxTE_RICH);
}
void TextCtrlTestCase::PositionToCoordsRich2()
{
DoPositionToCoordsTestWithStyle(wxTE_RICH2);
}
void TextCtrlTestCase::DoPositionToCoordsTestWithStyle(long style)
{
static const int TEXT_HEIGHT = 200;
delete m_text;
m_text = new wxTextCtrl(wxTheApp->GetTopWindow(), wxID_ANY, "",
wxDefaultPosition, wxSize(400, TEXT_HEIGHT),
wxTE_MULTILINE | style);
// Asking for invalid index should fail.
WX_ASSERT_FAILS_WITH_ASSERT( m_text->PositionToCoords(1) );
// Getting position shouldn't return wxDefaultPosition except if the method
// is not implemented at all in the current port.
const wxPoint pos0 = m_text->PositionToCoords(0);
if ( pos0 == wxDefaultPosition )
{
#if defined(__WXMSW__) || defined(__WXGTK20__)
CPPUNIT_FAIL( "PositionToCoords() unexpectedly failed." );
#endif
return;
}
CPPUNIT_ASSERT(pos0.x >= 0);
CPPUNIT_ASSERT(pos0.y >= 0);
m_text->SetValue("Hello");
wxYield(); // Let GTK layout the control correctly.
// Position of non-first character should be positive.
const long posHello4 = m_text->PositionToCoords(4).x;
CPPUNIT_ASSERT( posHello4 > 0 );
// Asking for position beyond the last character should succeed and return
// reasonable result.
CPPUNIT_ASSERT( m_text->PositionToCoords(5).x > posHello4 );
// But asking for the next position should fail.
WX_ASSERT_FAILS_WITH_ASSERT( m_text->PositionToCoords(6) );
// Test getting the coordinates of the last character when it is in the
// beginning of a new line to exercise MSW code which has specific logic
// for it.
m_text->AppendText("\n");
const wxPoint posLast = m_text->PositionToCoords(m_text->GetLastPosition());
CPPUNIT_ASSERT_EQUAL( pos0.x, posLast.x );
CPPUNIT_ASSERT( posLast.y > 0 );
// Add enough contents to the control to make sure it has a scrollbar.
m_text->SetValue("First line" + wxString(50, '\n') + "Last line");
m_text->SetInsertionPoint(0);
wxYield(); // Let GTK layout the control correctly.
// This shouldn't change anything for the first position coordinates.
CPPUNIT_ASSERT_EQUAL( pos0, m_text->PositionToCoords(0) );
// And the last one must be beyond the window boundary and so not be
// visible -- but getting its coordinate should still work.
CPPUNIT_ASSERT
(
m_text->PositionToCoords(m_text->GetLastPosition()).y > TEXT_HEIGHT
);
// Now make it scroll to the end and check that the first position now has
// negative offset as its above the visible part of the window while the
// last position is in its bounds.
m_text->SetInsertionPointEnd();
CPPUNIT_ASSERT( m_text->PositionToCoords(0).y < 0 );
CPPUNIT_ASSERT
(
m_text->PositionToCoords(m_text->GetInsertionPoint()).y <= TEXT_HEIGHT
);
}
#endif //wxUSE_TEXTCTRL