// Modified by:
// Created: 04/01/98
// RCS-ID: $Id$
-// Copyright: (c) Julian Smart and Markus Holzem
-// Licence: wxWindows license
+// Copyright: (c) Julian Smart
+// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ============================================================================
#include "wx/intl.h"
#include "wx/log.h"
#include "wx/app.h"
+ #include "wx/menu.h"
#endif
#include "wx/module.h"
#include <stdlib.h>
#include <sys/types.h>
-#if wxUSE_RICHEDIT && (!defined(__GNUWIN32_OLD__) || defined(__CYGWIN10__))
+#if wxUSE_RICHEDIT
+
+// old mingw32 has richedit stuff directly in windows.h and doesn't have
+// richedit.h at all
+#if !defined(__GNUWIN32_OLD__) || defined(__CYGWIN10__)
#include <richedit.h>
#endif
// Watcom C++ doesn't define this
#ifndef SCF_ALL
-#define SCF_ALL 0x0004
+ #define SCF_ALL 0x0004
#endif
+#endif // wxUSE_RICHEDIT
+
// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------
EVT_CHAR(wxTextCtrl::OnChar)
EVT_DROP_FILES(wxTextCtrl::OnDropFiles)
+#if wxUSE_RICHEDIT
+ EVT_RIGHT_UP(wxTextCtrl::OnRightClick)
+#endif
+
EVT_MENU(wxID_CUT, wxTextCtrl::OnCut)
EVT_MENU(wxID_COPY, wxTextCtrl::OnCopy)
EVT_MENU(wxID_PASTE, wxTextCtrl::OnPaste)
EVT_MENU(wxID_UNDO, wxTextCtrl::OnUndo)
EVT_MENU(wxID_REDO, wxTextCtrl::OnRedo)
+ EVT_MENU(wxID_CLEAR, wxTextCtrl::OnDelete)
+ EVT_MENU(wxID_SELECTALL, wxTextCtrl::OnSelectAll)
EVT_UPDATE_UI(wxID_CUT, wxTextCtrl::OnUpdateCut)
EVT_UPDATE_UI(wxID_COPY, wxTextCtrl::OnUpdateCopy)
EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste)
EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo)
EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo)
+ EVT_UPDATE_UI(wxID_CLEAR, wxTextCtrl::OnUpdateDelete)
+ EVT_UPDATE_UI(wxID_SELECTALL, wxTextCtrl::OnUpdateSelectAll)
#ifdef __WIN16__
EVT_ERASE_BACKGROUND(wxTextCtrl::OnEraseBackground)
#endif
{
#if wxUSE_RICHEDIT
m_verRichEdit = 0;
-#endif
+#endif // wxUSE_RICHEDIT
+
+ m_privateContextMenu = NULL;
+ m_suppressNextUpdate = FALSE;
+}
+
+wxTextCtrl::~wxTextCtrl()
+{
+ if (m_privateContextMenu)
+ {
+ delete m_privateContextMenu;
+ m_privateContextMenu = NULL;
+ }
}
bool wxTextCtrl::Create(wxWindow *parent, wxWindowID id,
// have we managed to load any richedit version?
if ( !s_errorGiven )
{
- msStyle |= ES_AUTOVSCROLL;
-
m_verRichEdit = verRichEdit;
if ( m_verRichEdit == 1 )
{
#if wxUSE_RICHEDIT
if ( IsRich() )
{
- // enable the events we're interested in: we want to get EN_CHANGE and
- // EN_UPDATE as for the normal controls
- LPARAM mask = ENM_CHANGE | ENM_UPDATE;
+ // enable the events we're interested in: we want to get EN_CHANGE as
+ // for the normal controls
+ LPARAM mask = ENM_CHANGE;
if ( GetRichVersion() == 1 )
{
m_windowStyle |= wxTE_READONLY;
if (style & ES_WANTRETURN)
m_windowStyle |= wxTE_PROCESS_ENTER;
+ if (style & ES_CENTER)
+ m_windowStyle |= wxTE_CENTRE;
+ if (style & ES_RIGHT)
+ m_windowStyle |= wxTE_RIGHT;
}
WXDWORD wxTextCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
long msStyle = wxControl::MSWGetStyle(style, exstyle);
- // default styles
- msStyle |= ES_LEFT;
-
+ // styles which we alaways add by default
if ( style & wxTE_MULTILINE )
{
wxASSERT_MSG( !(style & wxTE_PROCESS_ENTER),
msStyle |= ES_MULTILINE | ES_WANTRETURN;
if ( !(style & wxTE_NO_VSCROLL) )
- msStyle |= WS_VSCROLL;
+ {
+ // always adjust the vertical scrollbar automatically if we have it
+ msStyle |= WS_VSCROLL | ES_AUTOVSCROLL;
+
+ // we have to use this style for the rich edit controls because
+ // without it the vertical scrollbar never appears at all in
+ // richedit 3.0 because of our ECO_NOHIDESEL hack (search for it)
+ if ( style & wxTE_RICH2 )
+ {
+ msStyle |= ES_DISABLENOSCROLL;
+ }
+ }
style |= wxTE_PROCESS_ENTER;
}
msStyle |= ES_AUTOHSCROLL;
}
+ // styles which we add depending on the specified wxWindows styles
if ( style & wxHSCROLL )
- msStyle |= WS_HSCROLL | ES_AUTOHSCROLL;
+ {
+ // automatically scroll the control horizontally as necessary
+ msStyle |= WS_HSCROLL;// | ES_AUTOHSCROLL;
+ }
if ( style & wxTE_READONLY )
msStyle |= ES_READONLY;
if ( style & wxTE_PASSWORD )
msStyle |= ES_PASSWORD;
- if ( style & wxTE_AUTO_SCROLL )
- msStyle |= ES_AUTOHSCROLL;
-
if ( style & wxTE_NOHIDESEL )
msStyle |= ES_NOHIDESEL;
+ // note that we can't do do "& wxTE_LEFT" as wxTE_LEFT == 0
+ if ( style & wxTE_CENTRE )
+ msStyle |= ES_CENTER;
+ else if ( style & wxTE_RIGHT )
+ msStyle |= ES_RIGHT;
+ else
+ msStyle |= ES_LEFT; // ES_LEFT if 0 as well but for consistency...
+
return msStyle;
}
void wxTextCtrl::SetWindowStyleFlag(long style)
{
+ if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT )
+ style |= wxBORDER_SUNKEN;
+
#if wxUSE_RICHEDIT
// we have to deal with some styles separately because they can't be
// changed by simply calling SetWindowLong(GWL_STYLE) but can be changed
if ( (value.length() > 0x400) || (value != GetValue()) )
{
DoWriteText(value, FALSE /* not selection only */);
+ }
- // mark the control as being not dirty - we changed its text, not the
- // user
- DiscardEdits();
+ // we should reset the modified flag even if the value didn't really change
- // for compatibility, don't move the cursor when doing SetValue()
- SetInsertionPoint(0);
- }
+ // mark the control as being not dirty - we changed its text, not the
+ // user
+ DiscardEdits();
+
+ // for compatibility, don't move the cursor when doing SetValue()
+ SetInsertionPoint(0);
}
#if wxUSE_RICHEDIT && (!wxUSE_UNICODE || wxUSE_UNICODE_MSLU)
bool selectionOnly)
{
// we have to use EM_STREAMIN to force richedit control 2.0+ to show any
- // text in the non default charset - otherwise it thinks it knows better
+ // text in the non default charset -- otherwise it thinks it knows better
// than we do and always shows it in the default one
// first get the Windows code page for this encoding
// the cast below is needed for broken (very) old mingw32 headers
eds.pfnCallback = (EDITSTREAMCALLBACK)wxRichEditStreamIn;
- if ( !::SendMessage(GetHwnd(), EM_STREAMIN,
- SF_TEXT |
- SF_UNICODE |
- (selectionOnly ? SFF_SELECTION : 0),
- (LPARAM)&eds) || eds.dwError )
+ // we're going to receive 2 EN_CHANGE notifications if we got any selection
+ // (same problem as in DoWriteText())
+ if ( selectionOnly && HasSelection() )
+ {
+ // so suppress one of them
+ m_suppressNextUpdate = TRUE;
+ }
+
+ ::SendMessage(GetHwnd(), EM_STREAMIN,
+ SF_TEXT |
+ SF_UNICODE |
+ (selectionOnly ? SFF_SELECTION : 0),
+ (LPARAM)&eds);
+
+ if ( eds.dwError )
{
wxLogLastError(_T("EM_STREAMIN"));
}
if ( !done )
#endif // wxUSE_RICHEDIT
{
- if ( !selectionOnly )
+ // in some cases we get 2 EN_CHANGE notifications after the SendMessage
+ // call below which is confusing for the client code and so should be
+ // avoided
+ //
+ // these cases are: (a) plain EDIT controls if EM_REPLACESEL is used
+ // and there is a non empty selection currently and (b) rich text
+ // controls in any case
+ if (
+#if wxUSE_RICHEDIT
+ IsRich() ||
+#endif // wxUSE_RICHEDIT
+ (selectionOnly && HasSelection()) )
{
- SetSelection(-1, -1);
+ m_suppressNextUpdate = TRUE;
}
- ::SendMessage(GetHwnd(), EM_REPLACESEL, 0, (LPARAM)valueDos.c_str());
+ ::SendMessage(GetHwnd(), selectionOnly ? EM_REPLACESEL : WM_SETTEXT,
+ 0, (LPARAM)valueDos.c_str());
+
+ // OTOH, non rich text controls don't generate any events at all when
+ // we use WM_SETTEXT -- have to emulate them here
+ if ( !selectionOnly
+#if wxUSE_RICHEDIT
+ && !IsRich()
+#endif // wxUSE_RICHEDIT
+ )
+ {
+ // Windows already sends an update event for single-line
+ // controls.
+ if ( m_windowStyle & wxTE_MULTILINE )
+ SendUpdateEvent();
+ }
}
AdjustSpaceLimit();
SetInsertionPointEnd();
WriteText(text);
+
+#if wxUSE_RICHEDIT
+ if ( IsMultiLine() && GetRichVersion() > 1 )
+ {
+ // setting the caret to the end and showing it simply doesn't work for
+ // RichEdit 2.0 -- force it to still do what we want
+ ::SendMessage(GetHwnd(), EM_LINESCROLL, 0, GetNumberOfLines());
+ }
+#endif // wxUSE_RICHEDIT
}
void wxTextCtrl::Clear()
{
::SetWindowText(GetHwnd(), wxT(""));
+
+#if wxUSE_RICHEDIT
+ if ( !IsRich() )
+#endif // wxUSE_RICHEDIT
+ {
+ // rich edit controls send EN_UPDATE from WM_SETTEXT handler themselves
+ // but the normal ones don't -- make Clear() behaviour consistent by
+ // always sending this event
+
+ // Windows already sends an update event for single-line
+ // controls.
+ if ( m_windowStyle & wxTE_MULTILINE )
+ SendUpdateEvent();
+ }
}
#ifdef __WIN32__
}
}
-bool wxTextCtrl::CanCopy() const
+bool wxTextCtrl::HasSelection() const
{
- // Can copy if there's a selection
long from, to;
GetSelection(&from, &to);
return from != to;
}
+bool wxTextCtrl::CanCopy() const
+{
+ // Can copy if there's a selection
+ return HasSelection();
+}
+
bool wxTextCtrl::CanCut() const
{
return CanCopy() && IsEditable();
void wxTextCtrl::SetInsertionPointEnd()
{
+ // we must not do anything if the caret is already there because calling
+ // SetInsertionPoint() thaws the controls if Freeze() had been called even
+ // if it doesn't actually move the caret anywhere and so the simple fact of
+ // doing it results in horrible flicker when appending big amounts of text
+ // to the control in a few chunks (see DoAddText() test in the text sample)
+ if ( GetInsertionPoint() == GetLastPosition() )
+ return;
+
long pos;
#if wxUSE_RICHEDIT
if ( scrollCaret )
{
+#if wxUSE_RICHEDIT
+ // richedit 3.0 (i.e. the version living in riched20.dll distributed
+ // with Windows 2000 and beyond) doesn't honour EM_SCROLLCARET when
+ // emulating richedit 2.0 unless the control has focus or ECO_NOHIDESEL
+ // option is set (but it does work ok in richedit 1.0 mode...)
+ //
+ // so to make it work we either need to give focus to it here which
+ // will probably create many problems (dummy focus events; window
+ // containing the text control being brought to foreground
+ // unexpectedly; ...) or to temporarily set ECO_NOHIDESEL which may
+ // create other problems too -- and in fact it does because if we turn
+ // on/off this style while appending the text to the control, the
+ // vertical scrollbar never appears in it even if we append tons of
+ // text and to work around this the only solution I found was to use
+ // ES_DISABLENOSCROLL
+ //
+ // this is very ugly but I don't see any other way to make this work
+ if ( GetRichVersion() > 1 )
+ {
+ if ( !HasFlag(wxTE_NOHIDESEL) )
+ {
+ ::SendMessage(GetHwnd(), EM_SETOPTIONS,
+ ECOOP_OR, ECO_NOHIDESEL);
+ }
+ //else: everything is already ok
+ }
+#endif // wxUSE_RICHEDIT
+
SendMessage(hWnd, EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
+
+#if wxUSE_RICHEDIT
+ // restore ECO_NOHIDESEL if we changed it
+ if ( GetRichVersion() > 1 && !HasFlag(wxTE_NOHIDESEL) )
+ {
+ ::SendMessage(GetHwnd(), EM_SETOPTIONS,
+ ECOOP_AND, ~ECO_NOHIDESEL);
+ }
+#endif // wxUSE_RICHEDIT
}
#else // Win16
// WPARAM is 0: selection is scrolled into view
}
else // no Alt
{
- if ( wxIsCtrlDown() )
+ // we want to process some Ctrl-foo and Shift-bar but no key
+ // combinations without either Ctrl or Shift nor with both of them
+ // pressed
+ const int ctrl = wxIsCtrlDown(),
+ shift = wxIsShiftDown();
+ switch ( ctrl + shift )
{
- switch ( vkey )
- {
- case 'C':
- case 'V':
- case 'X':
- case VK_INSERT:
- case VK_DELETE:
- case VK_HOME:
- case VK_END:
- return FALSE;
- }
- }
- else if ( wxIsShiftDown() )
- {
- if ( vkey == VK_INSERT || vkey == VK_DELETE )
- return FALSE;
+ default:
+ wxFAIL_MSG( _T("how many modifiers have we got?") );
+ // fall through
+
+ case 0:
+ case 2:
+ break;
+
+ case 1:
+ // either Ctrl or Shift pressed
+ if ( ctrl )
+ {
+ switch ( vkey )
+ {
+ case 'C':
+ case 'V':
+ case 'X':
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_HOME:
+ case VK_END:
+ return FALSE;
+ }
+ }
+ else // Shift is pressed
+ {
+ if ( vkey == VK_INSERT || vkey == VK_DELETE )
+ return FALSE;
+ }
}
}
}
void wxTextCtrl::OnChar(wxKeyEvent& event)
{
- switch ( event.KeyCode() )
+ switch ( event.GetKeyCode() )
{
case WXK_RETURN:
if ( !(m_windowStyle & wxTE_MULTILINE) )
if ( nMsg == WM_GETDLGCODE )
{
- // we always want the chars and the arrows
+ // we always want the chars and the arrows: the arrows for navigation
+ // and the chars because we want Ctrl-C to work even in a read only
+ // control
long lDlgCode = DLGC_WANTCHARS | DLGC_WANTARROWS;
- // we may have several different cases:
- // 1. normal case: both TAB and ENTER are used for dialog navigation
- // 2. ctrl which wants TAB for itself: ENTER is used to pass to the
- // next control in the dialog
- // 3. ctrl which wants ENTER for itself: TAB is used for dialog
- // navigation
- // 4. ctrl which wants both TAB and ENTER: Ctrl-ENTER is used to pass
- // to the next control
-
- // the multiline edit control should always get <Return> for itself
- if ( HasFlag(wxTE_PROCESS_ENTER) || HasFlag(wxTE_MULTILINE) )
- lDlgCode |= DLGC_WANTMESSAGE;
-
- if ( HasFlag(wxTE_PROCESS_TAB) )
- lDlgCode |= DLGC_WANTTAB;
-
- lRc |= lDlgCode;
+ if ( IsEditable() )
+ {
+ // we may have several different cases:
+ // 1. normal case: both TAB and ENTER are used for dlg navigation
+ // 2. ctrl which wants TAB for itself: ENTER is used to pass to the
+ // next control in the dialog
+ // 3. ctrl which wants ENTER for itself: TAB is used for dialog
+ // navigation
+ // 4. ctrl which wants both TAB and ENTER: Ctrl-ENTER is used to go
+ // to the next control
+
+ // the multiline edit control should always get <Return> for itself
+ if ( HasFlag(wxTE_PROCESS_ENTER) || HasFlag(wxTE_MULTILINE) )
+ lDlgCode |= DLGC_WANTMESSAGE;
+
+ if ( HasFlag(wxTE_PROCESS_TAB) )
+ lDlgCode |= DLGC_WANTTAB;
+
+ lRc |= lDlgCode;
+ }
+ else // !editable
+ {
+ // NB: use "=", not "|=" as the base class version returns the
+ // same flags is this state as usual (i.e. including
+ // DLGC_WANTMESSAGE). This is strange (how does it work in the
+ // native Win32 apps?) but for now live with it.
+ lRc = lDlgCode;
+ }
}
return lRc;
// text control event processing
// ----------------------------------------------------------------------------
+bool wxTextCtrl::SendUpdateEvent()
+{
+ // is event reporting suspended?
+ if ( m_suppressNextUpdate )
+ {
+ // do process the next one
+ m_suppressNextUpdate = FALSE;
+
+ return FALSE;
+ }
+
+ wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
+ InitCommandEvent(event);
+ event.SetString(GetValue());
+
+ return ProcessCommand(event);
+}
+
bool wxTextCtrl::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
{
- switch (param)
+ switch ( param )
{
case EN_SETFOCUS:
case EN_KILLFOCUS:
wxFocusEvent event(param == EN_KILLFOCUS ? wxEVT_KILL_FOCUS
: wxEVT_SET_FOCUS,
m_windowId);
- event.SetEventObject( this );
+ event.SetEventObject(this);
GetEventHandler()->ProcessEvent(event);
}
break;
case EN_CHANGE:
- {
- wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, m_windowId);
- InitCommandEvent(event);
- event.SetString(GetValue());
- ProcessCommand(event);
- }
+ SendUpdateEvent();
break;
case EN_MAXTEXT:
- // the text size limit has been hit - increase it
+ // the text size limit has been hit -- try to increase it
if ( !AdjustSpaceLimit() )
{
wxCommandEvent event(wxEVT_COMMAND_TEXT_MAXLEN, m_windowId);
}
break;
- // the other notification messages are not processed
- case EN_UPDATE:
- case EN_ERRSPACE:
- case EN_HSCROLL:
- case EN_VSCROLL:
- return FALSE;
-
+ // the other edit notification messages are not processed
default:
return FALSE;
}
bool wxTextCtrl::AcceptsFocus() const
{
- // we don't want focus if we can't be edited
- return IsEditable() && wxControl::AcceptsFocus();
+ // we don't want focus if we can't be edited unless we're a multiline
+ // control because then it might be still nice to get focus from keyboard
+ // to be able to scroll it without mouse
+ return (IsEditable() || IsMultiLine()) && wxControl::AcceptsFocus();
}
wxSize wxTextCtrl::DoGetBestSize() const
Redo();
}
+void wxTextCtrl::OnDelete(wxCommandEvent& event)
+{
+ long from, to;
+ GetSelection(& from, & to);
+ if (from != -1 && to != -1)
+ Remove(from, to);
+}
+
+void wxTextCtrl::OnSelectAll(wxCommandEvent& event)
+{
+ SetSelection(-1, -1);
+}
+
void wxTextCtrl::OnUpdateCut(wxUpdateUIEvent& event)
{
event.Enable( CanCut() );
event.Enable( CanRedo() );
}
+void wxTextCtrl::OnUpdateDelete(wxUpdateUIEvent& event)
+{
+ long from, to;
+ GetSelection(& from, & to);
+ event.Enable(from != -1 && to != -1 && from != to && IsEditable()) ;
+}
+
+void wxTextCtrl::OnUpdateSelectAll(wxUpdateUIEvent& event)
+{
+ event.Enable(GetLastPosition() > 0);
+}
+
+void wxTextCtrl::OnRightClick(wxMouseEvent& event)
+{
+#if wxUSE_RICHEDIT
+ if (IsRich())
+ {
+ if (!m_privateContextMenu)
+ {
+ m_privateContextMenu = new wxMenu;
+ m_privateContextMenu->Append(wxID_UNDO, _("&Undo"));
+ m_privateContextMenu->Append(wxID_REDO, _("&Redo"));
+ m_privateContextMenu->AppendSeparator();
+ m_privateContextMenu->Append(wxID_CUT, _("Cu&t"));
+ m_privateContextMenu->Append(wxID_COPY, _("&Copy"));
+ m_privateContextMenu->Append(wxID_PASTE, _("&Paste"));
+ m_privateContextMenu->Append(wxID_CLEAR, _("&Delete"));
+ m_privateContextMenu->AppendSeparator();
+ m_privateContextMenu->Append(wxID_SELECTALL, _("Select &All"));
+ }
+ PopupMenu(m_privateContextMenu, event.GetPosition());
+ return;
+ }
+ else
+#endif
+ event.Skip();
+}
+
// the rest of the file only deals with the rich edit controls
#if wxUSE_RICHEDIT
NMHDR *hdr = (NMHDR* )lParam;
switch ( hdr->code )
{
- case EN_MSGFILTER:
+ case EN_MSGFILTER:
{
const MSGFILTER *msgf = (MSGFILTER *)lParam;
UINT msg = msgf->msg;
}
return TRUE;
}
-
+
// not processed, leave it to the base class
return wxTextCtrlBase::MSWOnNotify(idCtrl, lParam, result);
}
// styling support for rich edit controls
// ----------------------------------------------------------------------------
+#if wxUSE_RICHEDIT
+
bool wxTextCtrl::SetStyle(long start, long end, const wxTextAttr& style)
{
if ( !IsRich() )
return ok;
}
+bool wxTextCtrl::SetDefaultStyle(const wxTextAttr& style)
+{
+ if ( !wxTextCtrlBase::SetDefaultStyle(style) )
+ return FALSE;
+
+ // we have to do this or the style wouldn't apply for the text typed by the
+ // user
+ long posLast = GetLastPosition();
+ SetStyle(posLast, posLast, m_defaultStyle);
+
+ return TRUE;
+}
+
+#endif
+
// ----------------------------------------------------------------------------
// wxRichEditModule
// ----------------------------------------------------------------------------