diff --git a/TODO b/TODO index b92f3471a7..f7e67231f8 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,9 @@ All -* problem with horz scrolling: the focus rect isn't drawn entirely... +* problem with lbox horz scrolling: the focus rect isn't drawn entirely... * write sample testing all listbox styles/events +* text ctrl horz scrolling +* text ctrl wxTE_XXX styles support MSW diff --git a/include/wx/univ/inphand.h b/include/wx/univ/inphand.h index 3e92a93673..537917d046 100644 --- a/include/wx/univ/inphand.h +++ b/include/wx/univ/inphand.h @@ -25,6 +25,7 @@ class WXDLLEXPORT wxCheckListBox; class WXDLLEXPORT wxListBox; class WXDLLEXPORT wxRenderer; class WXDLLEXPORT wxScrollBar; +class WXDLLEXPORT wxTextCtrl; // ---------------------------------------------------------------------------- // types of the standard input handlers which can be passed to @@ -297,6 +298,13 @@ public: const wxMouseEvent& event); virtual bool HandleMouseMove(wxControl *control, const wxMouseEvent& event); + +protected: + // get the position of the mouse click + static long HitTest(const wxTextCtrl *text, const wxPoint& pos); + + // capture data + wxTextCtrl *m_winCapture; }; #endif // _WX_UNIV_INPHAND_H_ diff --git a/include/wx/univ/renderer.h b/include/wx/univ/renderer.h index 73a0a3a535..96af94c061 100644 --- a/include/wx/univ/renderer.h +++ b/include/wx/univ/renderer.h @@ -97,10 +97,13 @@ public: int indexAccel = -1, wxRect *rectBounds = NULL) = 0; - // draw a line of the text ctrl + // draw a line of the text ctrl optionally highlighting the characters in + // the given range virtual void DrawTextLine(wxDC& dc, const wxString& text, const wxRect &rect, + int selStart = -1, + int selEnd = -1, int flags = 0) = 0; // draw the border and optionally return the rectangle containing the @@ -300,6 +303,8 @@ public: virtual void DrawTextLine(wxDC& dc, const wxString& text, const wxRect &rect, + int selStart = -1, + int selEnd = -1, int flags = 0) { m_renderer->DrawTextLine(dc, text, rect, flags); } virtual void DrawBorder(wxDC& dc, @@ -423,7 +428,7 @@ public: // operations void DrawLabel(const wxBitmap& bitmap = wxNullBitmap, wxCoord marginX = 0, wxCoord marginY = 0); - void DrawTextLine(const wxString& text); + void DrawTextLine(const wxString& text, int selStart = -1, int selEnd = -1); void DrawItems(const wxListBox *listbox, size_t itemFirst, size_t itemLast); void DrawCheckItems(const wxCheckListBox *listbox, diff --git a/include/wx/univ/textctrl.h b/include/wx/univ/textctrl.h index 96d07ff008..21025ab817 100644 --- a/include/wx/univ/textctrl.h +++ b/include/wx/univ/textctrl.h @@ -18,6 +18,8 @@ class WXDLLEXPORT wxCaret; +#include "wx/scrolwin.h" // for wxScrollHelper + // ---------------------------------------------------------------------------- // wxTextCtrl actions // ---------------------------------------------------------------------------- @@ -48,11 +50,15 @@ class WXDLLEXPORT wxCaret; #define wxACTION_TEXT_PREFIX_SEL _T("sel") #define wxACTION_TEXT_PREFIX_DEL _T("del") +// mouse selection +#define wxACTION_TEXT_ANCHOR_SEL _T("anchorsel") +#define wxACTION_TEXT_EXTEND_SEL _T("extendsel") + // ---------------------------------------------------------------------------- // wxTextCtrl // ---------------------------------------------------------------------------- -class WXDLLEXPORT wxTextCtrl : public wxTextCtrlBase +class WXDLLEXPORT wxTextCtrl : public wxTextCtrlBase, public wxScrollHelper { public: // creation @@ -155,6 +161,11 @@ public: long GetWordStart() const; long GetWordEnd() const; + // selection helpers + bool HasSelection() const { return m_selStart != -1; } + void ClearSelection(); + void RemoveSelection(); + // implementation only from now on // ------------------------------- @@ -162,6 +173,11 @@ public: virtual bool IsContainerWindow() const { return TRUE; } virtual wxBorder GetDefaultBorder() const { return wxBORDER_SUNKEN; } + // perform an action + virtual bool PerformAction(const wxControlAction& action, + long numArg = -1, + const wxString& strArg = wxEmptyString); + protected: // draw the text virtual void DoDraw(wxControlRenderer *renderer); @@ -170,9 +186,6 @@ protected: virtual wxSize DoGetBestClientSize() const; // input support - virtual bool PerformAction(const wxControlAction& action, - long numArg = -1, - const wxString& strArg = wxEmptyString); virtual wxString GetInputHandlerType() const; // common part of all ctors @@ -194,7 +207,8 @@ private: m_curRow; // selection - long m_selStart, + long m_selAnchor, + m_selStart, m_selEnd; // flags diff --git a/samples/univ/univ.cpp b/samples/univ/univ.cpp index e1e76cfd0f..0c42c527ea 100644 --- a/samples/univ/univ.cpp +++ b/samples/univ/univ.cpp @@ -407,7 +407,8 @@ void MyUnivFrame::OnListBox(wxCommandEvent& event) void MyUnivFrame::OnTextChange(wxCommandEvent& event) { - wxLogDebug(_T("Text control value changed: now '%s'"), event.GetString()); + wxLogDebug(_T("Text control value changed: now '%s'"), + event.GetString().c_str()); } void MyUnivFrame::OnLeftUp(wxMouseEvent& event) diff --git a/src/common/quantize.cpp b/src/common/quantize.cpp index d78882f673..42378db210 100644 --- a/src/common/quantize.cpp +++ b/src/common/quantize.cpp @@ -42,7 +42,6 @@ #endif #ifndef WX_PRECOMP -#include "wx/wx.h" #endif #include "wx/image.h" diff --git a/src/univ/renderer.cpp b/src/univ/renderer.cpp index 6bae292186..715191e7d2 100644 --- a/src/univ/renderer.cpp +++ b/src/univ/renderer.cpp @@ -630,10 +630,12 @@ void wxControlRenderer::DoDrawItems(const wxListBox *lbox, } } -void wxControlRenderer::DrawTextLine(const wxString& text) +void wxControlRenderer::DrawTextLine(const wxString& text, + int selStart, int selEnd) { m_dc.SetFont(m_window->GetFont()); m_dc.SetTextForeground(m_window->GetForegroundColour()); - m_renderer->DrawTextLine(m_dc, text, m_rect, m_window->GetStateFlags()); + m_renderer->DrawTextLine(m_dc, text, m_rect, selStart, selEnd, + m_window->GetStateFlags()); } diff --git a/src/univ/textctrl.cpp b/src/univ/textctrl.cpp index f5be3682e8..fba555a6da 100644 --- a/src/univ/textctrl.cpp +++ b/src/univ/textctrl.cpp @@ -57,6 +57,7 @@ IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl, wxControl) void wxTextCtrl::Init() { + m_selAnchor = m_selStart = m_selEnd = -1; @@ -103,18 +104,7 @@ void wxTextCtrl::SetValue(const wxString& value) if ( m_value == value ) return; - m_value = value; - - if ( IsSingleLine() ) - { - SetInsertionPointEnd(); - } - else - { - SetInsertionPoint(0); - } - - Refresh(); + Replace(0, GetLastPosition(), value); } wxString wxTextCtrl::GetValue() const @@ -129,7 +119,7 @@ void wxTextCtrl::Clear() void wxTextCtrl::Replace(long from, long to, const wxString& text) { - wxCHECK_RET( from >= 0 && to >= 0 && from <= to, + wxCHECK_RET( from >= 0 && to >= 0 && from <= to && to <= GetLastPosition(), _T("invalid range in wxTextCtrl::Replace") ); // replace the part of the text with the new value @@ -141,9 +131,13 @@ void wxTextCtrl::Replace(long from, long to, const wxString& text) } m_value = valueNew; - // update current position + // update the current position SetInsertionPoint(from + text.length()); + // and the selection (do it after setting the cursor to have correct value + // for selection anchor) + ClearSelection(); + // FIXME shouldn't refresh everything of course Refresh(); } @@ -240,13 +234,49 @@ void wxTextCtrl::GetSelection(long* from, long* to) const void wxTextCtrl::SetSelection(long from, long to) { - if ( from != m_selStart || to != m_selEnd ) + if ( from == -1 || to == -1 ) { - m_selStart = from; - m_selEnd = to; - - // TODO: update display + ClearSelection(); } + else // valid sel range + { + if ( from >= to ) + { + long tmp = from; + from = to; + to = tmp; + } + + wxCHECK_RET( to <= GetLastPosition(), + _T("invalid range in wxTextCtrl::SetSelection") ); + + if ( from != m_selStart || to != m_selEnd ) + { + m_selStart = from; + m_selEnd = to; + + Refresh(); + } + //else: nothing to do + } +} + +void wxTextCtrl::ClearSelection() +{ + m_selStart = + m_selEnd = -1; + + m_selAnchor = m_curPos; + + Refresh(); +} + +void wxTextCtrl::RemoveSelection() +{ + if ( !HasSelection() ) + return; + + Remove(m_selStart, m_selEnd); } // ---------------------------------------------------------------------------- @@ -330,11 +360,11 @@ int wxTextCtrl::GetNumberOfLines() const long wxTextCtrl::XYToPosition(long x, long y) const { + // note that this method should accept any values of x and y and return -1 + // if they are out of range if ( IsSingleLine() ) { - wxASSERT_MSG( y == 0, _T("invalid XYToPosition() parameter") ); - - return x; + return x > GetLastPosition() || y > 0 ? -1 : x; } else // multiline { @@ -517,7 +547,7 @@ void wxTextCtrl::DoDraw(wxControlRenderer *renderer) if ( IsSingleLine() ) { // just redraw everything - renderer->DrawTextLine(m_value); + renderer->DrawTextLine(m_value, (int)m_selStart, (int)m_selEnd); } else { @@ -561,11 +591,16 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig, bool textChanged = FALSE; wxString action; - bool del = FALSE; + bool del = FALSE, + sel = FALSE; if ( actionOrig.StartsWith(wxACTION_TEXT_PREFIX_DEL, &action) ) { del = TRUE; } + else if ( actionOrig.StartsWith(wxACTION_TEXT_PREFIX_SEL, &action) ) + { + sel = TRUE; + } else // not selection nor delete action { action = actionOrig; @@ -600,11 +635,22 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig, { if ( !strArg.empty() ) { + // replace the selection with the new text + RemoveSelection(); + WriteText(strArg); textChanged = TRUE; } } + else if ( action == wxACTION_TEXT_ANCHOR_SEL ) + { + newPos = numArg; + } + else if ( action == wxACTION_TEXT_EXTEND_SEL ) + { + SetSelection(m_selAnchor, numArg); + } else { return wxControl::PerformAction(action, numArg, strArg); @@ -622,18 +668,37 @@ bool wxTextCtrl::PerformAction(const wxControlAction& actionOrig, if ( del ) { - // delete everything between current opsition and the new one - if ( m_curPos != newPos ) + // if we have the selection, remove just it + if ( HasSelection() ) { - Remove(m_curPos, newPos); + RemoveSelection(); + } + else + { + // otherwise delete everything between current position and + // the new one + if ( m_curPos != newPos ) + { + Remove(m_curPos, newPos); - textChanged = TRUE; + textChanged = TRUE; + } } } - else + else // cursor movement command { // just go there SetInsertionPoint(newPos); + + if ( sel ) + { + SetSelection(m_selAnchor, m_curPos); + } + else // simple movement + { + // clear the existing selection + ClearSelection(); + } } } @@ -673,6 +738,114 @@ void wxTextCtrl::OnChar(wxKeyEvent& event) wxStdTextCtrlInputHandler::wxStdTextCtrlInputHandler(wxInputHandler *inphand) : wxStdInputHandler(inphand) { + m_winCapture = (wxTextCtrl *)NULL; +} + +/* static */ +long wxStdTextCtrlInputHandler::HitTest(const wxTextCtrl *text, + const wxPoint& pos) +{ + int x, y; + text->CalcUnscrolledPosition(pos.x, pos.y, &x, &y); + + // row calculation is simple as we assume that all lines have the same + // height + int row = y / text->GetCharHeight(); + int rowMax = text->GetNumberOfLines() - 1; + if ( row > rowMax ) + { + // clicking below the text is the same as clicking on the last line + row = rowMax; + } + + // if the line is empty, the column can only be the first one + wxString line = text->GetLineText(row); + int col; + wxClientDC dc(wxConstCast(text, wxTextCtrl)); + dc.SetFont(text->GetFont()); + + wxCoord width; + dc.GetTextExtent(line, &width, NULL); + if ( x >= width ) + { + // clicking beyond the end of line is equivalent to clicking at + // the end of it + col = line.length(); + } + else // we're inside the line + { + // now calculate the column: first, approximate it with fixed-width + // value and then calculate the correct value iteratively: note that + // we use the first character of the line instead of (average) + // GetCharWidth(): it is common to have lines of dashes, for example, + // and this should give us much better approximation in such case + dc.GetTextExtent(line[0], &width, NULL); + col = x / width; + + // matchDir is -1 if we must move left, +1 to move right and 0 when + // we're exactly on the character we need + int matchDir = 0; + for ( ;; ) + { + // check that we didn't go beyond the line boundary + if ( col < 0 ) + { + col = 0; + break; + } + if ( col > line.length() ) + { + col = line.length(); + break; + } + + wxString strBefore(line, (size_t)col); + dc.GetTextExtent(strBefore, &width, NULL); + if ( width >= x ) + { + 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--; + + break; + } + else if ( matchDir == 0 ) + { + // we just started iterating, now we know that we should + // move to the left + matchDir = -1; + } + //else: we are still to the right of the target, continue + } + else // width < x + { + // same logic as above ... + if ( matchDir == -1 ) + { + // ... except that we don't need to back track + break; + } + else if ( matchDir == 0 ) + { + // go to the right + matchDir = 1; + } + } + + // this is not supposed to happen + wxASSERT_MSG( matchDir, _T("logic error in wxTextCtrl::HitTest") ); + + if ( matchDir == 1 ) + col++; + else + col--; + } + } + + return text->XYToPosition(col, row); } bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control, @@ -720,9 +893,15 @@ bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control, case WXK_BACK: action << wxACTION_TEXT_PREFIX_DEL << wxACTION_TEXT_LEFT; break; + + // something else + default: + // reset the action as it could be already set to one of the + // prefixes + action = wxACTION_NONE; } - if ( !!action ) + if ( action != wxACTION_NONE ) { control->PerformAction(action, -1, str); @@ -735,12 +914,55 @@ bool wxStdTextCtrlInputHandler::HandleKey(wxControl *control, bool wxStdTextCtrlInputHandler::HandleMouse(wxControl *control, const wxMouseEvent& event) { + if ( event.LeftDown() ) + { + wxASSERT_MSG( !m_winCapture, _T("left button going down twice?") ); + + wxTextCtrl *text = wxStaticCast(control, wxTextCtrl); + + m_winCapture = text; + m_winCapture->CaptureMouse(); + + text->HideCaret(); + + long pos = HitTest(text, event.GetPosition()); + if ( pos != -1 ) + { + text->PerformAction(wxACTION_TEXT_ANCHOR_SEL, pos); + } + } + else if ( event.LeftDClick() ) + { + // TODO: select the word the cursor is on + } + else if ( event.LeftUp() ) + { + if ( m_winCapture ) + { + m_winCapture->ShowCaret(); + + m_winCapture->ReleaseMouse(); + m_winCapture = (wxTextCtrl *)NULL; + } + } + return wxStdInputHandler::HandleMouse(control, event); } bool wxStdTextCtrlInputHandler::HandleMouseMove(wxControl *control, const wxMouseEvent& event) { + if ( m_winCapture ) + { + // track it + wxTextCtrl *text = wxStaticCast(m_winCapture, wxTextCtrl); + long pos = HitTest(text, event.GetPosition()); + if ( pos != -1 ) + { + text->PerformAction(wxACTION_TEXT_EXTEND_SEL, pos); + } + } + return wxStdInputHandler::HandleMouseMove(control, event); } diff --git a/src/univ/themes/gtk.cpp b/src/univ/themes/gtk.cpp index b190f27ec0..34d7747977 100644 --- a/src/univ/themes/gtk.cpp +++ b/src/univ/themes/gtk.cpp @@ -75,6 +75,8 @@ public: virtual void DrawTextLine(wxDC& dc, const wxString& text, const wxRect &rect, + int selStart = -1, + int selEnd = -1, int flags = 0); virtual void DrawBorder(wxDC& dc, wxBorder border, @@ -914,9 +916,54 @@ void wxGTKRenderer::DrawButtonLabel(wxDC& dc, void wxGTKRenderer::DrawTextLine(wxDC& dc, const wxString& text, const wxRect &rect, + int selStart, + int selEnd, int flags) { - dc.DrawText(text, rect.x, rect.y); + if ( selStart == -1 ) + { + // just draw it as is + dc.DrawText(text, rect.x, rect.y); + } + else // we have selection + { + wxCoord width, + x = rect.x; + + // draw the part before selection + wxString s(text, (size_t)selStart); + if ( !s.empty() ) + { + dc.DrawText(s, x, rect.y); + + dc.GetTextExtent(s, &width, NULL); + x += width; + } + + // draw the selection itself + s = wxString(text.c_str() + selStart, text.c_str() + selEnd); + if ( !s.empty() ) + { + wxColour colFg = dc.GetTextForeground(); + dc.SetTextForeground(wxSCHEME_COLOUR(m_scheme, HIGHLIGHT_TEXT)); + dc.SetTextBackground(wxSCHEME_COLOUR(m_scheme, HIGHLIGHT)); + dc.SetBackgroundMode(wxSOLID); + + dc.DrawText(s, x, rect.y); + dc.GetTextExtent(s, &width, NULL); + x += width; + + dc.SetBackgroundMode(wxTRANSPARENT); + dc.SetTextForeground(colFg); + } + + // draw the final part + s = text.c_str() + selEnd; + if ( !s.empty() ) + { + dc.DrawText(s, x, rect.y); + } + } } void wxGTKRenderer::DrawItem(wxDC& dc, diff --git a/src/univ/themes/win32.cpp b/src/univ/themes/win32.cpp index 5e4900cb43..b6dfdea898 100644 --- a/src/univ/themes/win32.cpp +++ b/src/univ/themes/win32.cpp @@ -92,6 +92,8 @@ public: virtual void DrawTextLine(wxDC& dc, const wxString& text, const wxRect &rect, + int selStart = -1, + int selEnd = -1, int flags = 0); virtual void DrawBorder(wxDC& dc, wxBorder border, @@ -1394,6 +1396,8 @@ void wxWin32Renderer::DrawButtonLabel(wxDC& dc, void wxWin32Renderer::DrawTextLine(wxDC& dc, const wxString& text, const wxRect &rect, + int selStart, + int selEnd, int flags) { dc.DrawText(text, rect.x + 2, rect.y + 1);