]> git.saurik.com Git - wxWidgets.git/commitdiff
Add wxTextCtrl::PositionToCoords() functions for wxMSW and wxGTK.
authorVadim Zeitlin <vadim@wxwidgets.org>
Fri, 29 Jul 2011 15:11:54 +0000 (15:11 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Fri, 29 Jul 2011 15:11:54 +0000 (15:11 +0000)
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

docs/changes.txt
include/wx/gtk/textctrl.h
include/wx/msw/textctrl.h
include/wx/textctrl.h
interface/wx/textctrl.h
samples/text/text.cpp
src/common/textcmn.cpp
src/gtk/textctrl.cpp
src/msw/textctrl.cpp
tests/controls/textctrltest.cpp

index a0ca39762d5d7ac41ab6e1b195f288a42bb61fe5..8187f093a644bc57df77a59eedb23d3bc3247503 100644 (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).
index 9db822c90b708f0e07dfa1eebfe6dd83afa4df51..336a5cfa2e44116a474da8afc2fa1ad5a8b809c5 100644 (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)
index 888ea4c76b8e0e461f334ba1060100f918857e62..f8fdf1f1a87fc017b1965b8d4977f380346bd44e 100644 (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;
index f26cb8aa10801f3d686a0ceead2cfdfdc2326740..d412a3dc437203ebe279799894049d23ea314b69 100644 (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; }
 
index 746954eca850c8f9740e5a050d445e1a80227d99..6dfd9143208c18c20686c838fcb0a1467cd662e0 100644 (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.
 
index 6c3ddf400ad1b3eaff82169ef2ea879c388ff7be..d25968ce9e08048113dc76278a181af9b80dfb58 100644 (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)
index 96f70e5d4725b02fed425415d8ecfa2e76888091..9e04aedcaa99882f38fa6b0acb5218f0a30a3b4f 100644 (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
index ee0c631afc6d479a065f5327612f1d9e9c2c8d4b..ae9f03cd169cf382219b3c30f3437857deead9bf 100644 (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() )
index 3cf85ba094ab52ad3268bf2541d33f2ca97d2979..850ea6ee9373dcb70428837b26f0064170bbea95 100644 (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;
+}
+
+
 // ----------------------------------------------------------------------------
 //
 // ----------------------------------------------------------------------------
index 24213ce94fea567141dc0343209ffea87ddefd08..86f0fbee7a5de5da5f99ba538e9c9441dcfce77c 100644 (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