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:
@@ -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).
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
@@ -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; }
|
||||
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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() )
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user