From 085e4c354aa82994cd15bb8f8a30394b7462df25 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 3 Jun 2018 16:32:00 +0200 Subject: [PATCH 1/3] Show HitTest() result on left click in "Text" widgets sample page Right clicking didn't work under wxGTK where it just showed the context menu, so use left click handler instead and check whether "Alt" is pressed. Also add a note to make this test more discoverable. --- samples/widgets/textctrl.cpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/samples/widgets/textctrl.cpp b/samples/widgets/textctrl.cpp index c89ba6ca93..96e5074954 100644 --- a/samples/widgets/textctrl.cpp +++ b/samples/widgets/textctrl.cpp @@ -276,11 +276,17 @@ public: int flags) : wxTextCtrl(parent, id, value, wxDefaultPosition, wxDefaultSize, flags) { + Bind(wxEVT_LEFT_DOWN, &WidgetsTextCtrl::OnLeftClick, this); } -protected: - void OnRightClick(wxMouseEvent& event) +private: + // Show the result of HitTest() at the mouse position if Alt is pressed. + void OnLeftClick(wxMouseEvent& event) { + event.Skip(); + if ( !event.AltDown() ) + return; + wxString where; wxTextCoord x, y; switch ( HitTest(event.GetPosition(), &x, &y) ) @@ -312,12 +318,7 @@ protected: } wxLogMessage(wxT("Mouse is %s (%ld, %ld)"), where.c_str(), x, y); - - event.Skip(); } - -private: - wxDECLARE_EVENT_TABLE(); }; // ---------------------------------------------------------------------------- @@ -353,10 +354,6 @@ wxBEGIN_EVENT_TABLE(TextWidgetsPage, WidgetsPage) EVT_RADIOBOX(wxID_ANY, TextWidgetsPage::OnCheckOrRadioBox) wxEND_EVENT_TABLE() -wxBEGIN_EVENT_TABLE(WidgetsTextCtrl, wxTextCtrl) - EVT_RIGHT_UP(WidgetsTextCtrl::OnRightClick) -wxEND_EVENT_TABLE() - // ============================================================================ // implementation // ============================================================================ @@ -593,6 +590,17 @@ void TextWidgetsPage::CreateContent() 0, wxALL, 5 ); + sizerMiddleDown->Add + ( + new wxStaticText + ( + this, + wxID_ANY, + "Alt-click in the text to see HitTest() result" + ), + wxSizerFlags().Border() + ); + wxSizer *sizerMiddle = new wxBoxSizer(wxVERTICAL); sizerMiddle->Add(sizerMiddleUp, 0, wxGROW); sizerMiddle->Add(sizerMiddleDown, 1, wxGROW | wxTOP, 5); From 690b95646bc68f3d2ca6502c618a065ed8955b79 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 3 Jun 2018 17:04:06 +0200 Subject: [PATCH 2/3] Implement wxTextCtrl::HitTest() for single line controls in wxGTK Use Pango API to find the character at the given position. Closes #18144. --- docs/changes.txt | 1 + interface/wx/textctrl.h | 4 ++-- src/gtk/textctrl.cpp | 49 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 41ecaf1d11..63ba58a58f 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -84,6 +84,7 @@ All (GUI): wxGTK: +- Implement wxTextCtrl::HitTest() for single line controls. - Fix the build with glib < 2.32 (e.g. CentOS 6). wxMSW: diff --git a/interface/wx/textctrl.h b/interface/wx/textctrl.h index 70b296c5b0..1bb50d9e90 100644 --- a/interface/wx/textctrl.h +++ b/interface/wx/textctrl.h @@ -1337,7 +1337,7 @@ public: parameter is not modified. Please note that this function is currently only implemented in wxUniv, - wxMSW and wxGTK2 ports and always returns @c wxTE_HT_UNKNOWN in the + wxMSW and wxGTK ports and always returns @c wxTE_HT_UNKNOWN in the other ports. @beginWxPerlOnly @@ -1363,7 +1363,7 @@ public: parameters are not modified. Please note that this function is currently only implemented in wxUniv, - wxMSW and wxGTK2 ports and always returns @c wxTE_HT_UNKNOWN in the + wxMSW and wxGTK ports and always returns @c wxTE_HT_UNKNOWN in the other ports. @beginWxPerlOnly diff --git a/src/gtk/textctrl.cpp b/src/gtk/textctrl.cpp index 281099dce9..b8551d37c7 100644 --- a/src/gtk/textctrl.cpp +++ b/src/gtk/textctrl.cpp @@ -1465,8 +1465,53 @@ wxTextCtrl::HitTest(const wxPoint& pt, long *pos) const { if ( !IsMultiLine() ) { - // not supported - return wxTE_HT_UNKNOWN; + // These variables will contain the position inside PangoLayout. + int x = pt.x, + y = pt.y; + + // Get the offsets of PangoLayout inside the control. + // + // Note that contrary to what GTK+ documentation implies, the + // horizontal offset already accounts for scrolling, i.e. it will be + // negative if text is scrolled. + gint ofsX = 0, + ofsY = 0; + gtk_entry_get_layout_offsets(GTK_ENTRY(m_text), &ofsX, &ofsY); + + x -= ofsX; + y -= ofsY; + + // And scale the coordinates for Pango. + x *= PANGO_SCALE; + y *= PANGO_SCALE; + + PangoLayout* const layout = gtk_entry_get_layout(GTK_ENTRY(m_text)); + + int idx = -1, + ofs = 0; + if ( !pango_layout_xy_to_index(layout, x, y, &idx, &ofs) ) + { + // Try to guess why did it fail. + if ( x < 0 || y < 0 ) + { + if ( pos ) + *pos = 0; + + return wxTE_HT_BEFORE; + } + else + { + if ( pos ) + *pos = wxTextEntry::GetLastPosition(); + + return wxTE_HT_BEYOND; + } + } + + if ( pos ) + *pos = idx; + + return wxTE_HT_ON_TEXT; } int x, y; From 06f554775cdb37a4a0477564653c87198b806903 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 3 Jun 2018 16:58:38 +0200 Subject: [PATCH 3/3] Add unit test for wxTextCtrl::HitTest() for single line controls Note that the control needs to be created with a reasonable size for HitTest() to work correctly (at least under MSW, but probably not only), so change the test set up function to use some fixed size for all kinds of controls instead of doing it only for the multi-line ones. --- tests/controls/textctrltest.cpp | 53 +++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/tests/controls/textctrltest.cpp b/tests/controls/textctrltest.cpp index 7264bc5d82..eceaee7e47 100644 --- a/tests/controls/textctrltest.cpp +++ b/tests/controls/textctrltest.cpp @@ -59,6 +59,7 @@ private: WXUISIM_TEST( MaxLength ); CPPUNIT_TEST( PositionToXYSingleLine ); CPPUNIT_TEST( XYToPositionSingleLine ); + CPPUNIT_TEST( HitTestSingleLine ); SINGLE_AND_MULTI_TESTS(); // Now switch to the multi-line text controls. @@ -112,6 +113,7 @@ private: void MaxLength(); void StreamInput(); void Redirector(); + void HitTestSingleLine(); //void ProcessEnter(); void Url(); void Style(); @@ -164,12 +166,8 @@ long TextCtrlTestCase::ms_style = 0; void TextCtrlTestCase::CreateText(long extraStyles) { - wxSize size; - if ( ms_style == wxTE_MULTILINE ) - size = wxSize(400, TEXT_HEIGHT); - m_text = new wxTextCtrl(wxTheApp->GetTopWindow(), wxID_ANY, "", - wxDefaultPosition, size, + wxDefaultPosition, wxSize(400, TEXT_HEIGHT), ms_style | extraStyles); } @@ -342,6 +340,51 @@ void TextCtrlTestCase::Redirector() #endif } +void TextCtrlTestCase::HitTestSingleLine() +{ + m_text->ChangeValue("Hit me"); + + // We don't know the size of the text borders, so we can't really do any + // exact tests, just try to verify that the results are roughly as + // expected. + const wxSize sizeChar = m_text->GetTextExtent("X"); + const int yMid = sizeChar.y / 2; + + long pos = -1; + + // Hitting a point near the left side of the control should find one of the + // first few characters under it. + SECTION("Normal") + { + REQUIRE( m_text->HitTest(wxPoint(2*sizeChar.x, yMid), &pos) == wxTE_HT_ON_TEXT ); + CHECK( pos >= 0 ); + CHECK( pos < 3 ); + } + + // Hitting a point well beyond the end of the text shouldn't find any valid + // character. + SECTION("Beyond") + { + REQUIRE( m_text->HitTest(wxPoint(20*sizeChar.x, yMid), &pos) == wxTE_HT_BEYOND ); + CHECK( pos == m_text->GetLastPosition() ); + } + + // Making the control scroll, by ensuring that its contents is too long to + // show inside its window, should change the hit test result for the same + // position as used above. + SECTION("Scrolled") + { + m_text->ChangeValue(wxString(200, 'X')); + m_text->SetInsertionPointEnd(); + + // wxGTK must be given an opportunity to lay the text out. + wxYield(); + + REQUIRE( m_text->HitTest(wxPoint(2*sizeChar.x, yMid), &pos) == wxTE_HT_ON_TEXT ); + CHECK( pos > 3 ); + } +} + #if 0 void TextCtrlTestCase::ProcessEnter() {