From 69b66e9e2e2b8e49e3816acdde079686ce9b0da1 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 Nov 2015 03:40:40 +0100 Subject: [PATCH] Add wxTextEntry::ForceUpper() Allow automatically converting lower-case letters entered into wxTextCtrl to upper-case equivalents. Provide generic fallback and implement the method natively for all the major platforms. Also update the text sample to show it in action. --- docs/changes.txt | 1 + include/wx/gtk/textentry.h | 9 +++- include/wx/msw/textentry.h | 4 ++ include/wx/osx/cocoa/private/textimpl.h | 10 ++++- include/wx/osx/core/private.h | 5 ++- include/wx/osx/textentry.h | 2 + include/wx/textentry.h | 10 +++++ interface/wx/textentry.h | 11 +++++ samples/text/text.cpp | 5 +++ src/common/textentrycmn.cpp | 60 +++++++++++++++++++++++++ src/gtk/textentry.cpp | 51 +++++++++++++++++++-- src/msw/textentry.cpp | 11 ++++- src/osx/cocoa/textctrl.mm | 60 ++++++++++++++++++++----- src/osx/textentry_osx.cpp | 17 +++++++ 14 files changed, 238 insertions(+), 18 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 54528efea6..ad38eb027b 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -90,6 +90,7 @@ All (GUI): - Allow customizing window shown by wxBusyInfo. - Add wxAddRemoveCtrl. - Add wxAppProgressIndicator for MSW (Chaobin Zhang) and OS X (Tobias Taschner). +- Add wxTextEntry::ForceUpper(). - Add wxEVT_MAGNIFY mouse event (Joost Nieuwenhuijse). - Add wxProcess::Activate(). - Fix setting colours of labels in wxSlider. diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index 89177673c3..f78dc7b0dd 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -21,7 +21,7 @@ typedef struct _GtkEntry GtkEntry; class WXDLLIMPEXP_CORE wxTextEntry : public wxTextEntryBase { public: - wxTextEntry() { } + wxTextEntry() { m_isUpperCase = false; } // implement wxTextEntryBase pure virtual methods virtual void WriteText(const wxString& text) wxOVERRIDE; @@ -47,6 +47,7 @@ public: virtual void SetEditable(bool editable) wxOVERRIDE; virtual void SetMaxLength(unsigned long len) wxOVERRIDE; + virtual void ForceUpper() wxOVERRIDE; #ifdef __WXGTK3__ virtual bool SetHint(const wxString& hint) wxOVERRIDE; @@ -56,6 +57,7 @@ public: // implementation only from now on void SendMaxLenEvent(); bool GTKEntryOnInsertText(const char* text); + bool GTKIsUpperCase() const { return m_isUpperCase; } protected: // This method must be called from the derived class Create() to connect @@ -85,7 +87,12 @@ private: // implement this to return the associated GtkEntry virtual GtkEntry *GetEntry() const = 0; + + bool m_isUpperCase; }; +// We don't need the generic version. +#define wxHAS_NATIVE_TEXT_FORCEUPPER + #endif // _WX_GTK_TEXTENTRY_H_ diff --git a/include/wx/msw/textentry.h b/include/wx/msw/textentry.h index 80b9e23966..2590b83191 100644 --- a/include/wx/msw/textentry.h +++ b/include/wx/msw/textentry.h @@ -47,6 +47,7 @@ public: virtual void SetEditable(bool editable); virtual void SetMaxLength(unsigned long len); + virtual void ForceUpper(); #if wxUSE_UXTHEME virtual bool SetHint(const wxString& hint); @@ -98,5 +99,8 @@ private: #endif // wxUSE_OLE }; +// We don't need the generic version. +#define wxHAS_NATIVE_TEXT_FORCEUPPER + #endif // _WX_MSW_TEXTENTRY_H_ diff --git a/include/wx/osx/cocoa/private/textimpl.h b/include/wx/osx/cocoa/private/textimpl.h index e377c7027c..24b4f8cff5 100644 --- a/include/wx/osx/cocoa/private/textimpl.h +++ b/include/wx/osx/cocoa/private/textimpl.h @@ -14,6 +14,8 @@ #include "wx/combobox.h" #include "wx/osx/private.h" +@class wxTextEntryFormatter; + // implementation exposed, so that search control can pull it class wxNSTextFieldControl : public wxWidgetCocoaImpl, public wxTextWidgetImpl @@ -29,7 +31,10 @@ public : virtual bool CanClipMaxLength() const { return true; } virtual void SetMaxLength(unsigned long len); - + + virtual bool CanForceUpper() { return true; } + virtual void ForceUpper(); + virtual wxString GetStringValue() const ; virtual void SetStringValue( const wxString &str) ; virtual void Copy() ; @@ -57,6 +62,9 @@ protected : private: // Common part of both ctors. void Init(WXWidget w); + + // Get our formatter, creating it if necessary. + wxTextEntryFormatter* GetFormatter(); }; class wxNSTextViewControl : public wxWidgetCocoaImpl, public wxTextWidgetImpl diff --git a/include/wx/osx/core/private.h b/include/wx/osx/core/private.h index ba19e45960..55f1c4e9fa 100644 --- a/include/wx/osx/core/private.h +++ b/include/wx/osx/core/private.h @@ -655,7 +655,10 @@ public : virtual bool CanClipMaxLength() const { return false; } virtual void SetMaxLength(unsigned long WXUNUSED(len)) {} - + + virtual bool CanForceUpper() { return false; } + virtual void ForceUpper() {} + virtual bool GetStyle( long position, wxTextAttr& style); virtual void SetStyle( long start, long end, const wxTextAttr& style ) ; virtual void Copy() ; diff --git a/include/wx/osx/textentry.h b/include/wx/osx/textentry.h index 12d780778a..29939d8383 100644 --- a/include/wx/osx/textentry.h +++ b/include/wx/osx/textentry.h @@ -50,6 +50,8 @@ public: // in a single line text control virtual void SetMaxLength(unsigned long len); + virtual void ForceUpper(); + // writing text inserts it at the current position; // appending always inserts it at the end virtual void WriteText(const wxString& text); diff --git a/include/wx/textentry.h b/include/wx/textentry.h index f3ab5398a2..542dc14cc3 100644 --- a/include/wx/textentry.h +++ b/include/wx/textentry.h @@ -138,10 +138,16 @@ public: virtual void SetEditable(bool editable) = 0; + // input restrictions + // ------------------ + // set the max number of characters which may be entered in a single line // text control virtual void SetMaxLength(unsigned long WXUNUSED(len)) { } + // convert any lower-case characters to upper-case on the fly in this entry + virtual void ForceUpper(); + // hints // ----- @@ -208,6 +214,10 @@ public: SuppressTextChangedEvents(); } + // change the entry value to be in upper case only, if needed (i.e. if it's + // not already the case) + void ConvertToUpperCase(); + protected: // flags for DoSetValue(): common part of SetValue() and ChangeValue() and // also used to implement WriteText() in wxMSW diff --git a/interface/wx/textentry.h b/interface/wx/textentry.h index a8c518f18f..20bb7e238f 100644 --- a/interface/wx/textentry.h +++ b/interface/wx/textentry.h @@ -212,6 +212,17 @@ public: */ virtual void Cut(); + /** + Convert all text entered into the control to upper case. + + Call this method to ensure that all text entered into the control is + converted on the fly to upper case. If the control is not empty, its + existing contents is also converted to upper case. + + @since 3.1.0 + */ + void ForceUpper(); + /** Returns the insertion point, or cursor, position. diff --git a/samples/text/text.cpp b/samples/text/text.cpp index 0e00a8d6e4..24fee72cad 100644 --- a/samples/text/text.cpp +++ b/samples/text/text.cpp @@ -1093,6 +1093,10 @@ MyPanel::MyPanel( wxFrame *frame, int x, int y, int w, int h ) wxSize size2 = m_limited->GetSizeFromTextSize(m_limited->GetTextExtent("WWWWWWWW")); m_limited->SetSizeHints(size2, size2); + wxTextCtrl* upperOnly = new MyTextCtrl(this, wxID_ANY, "Only upper case", + wxDefaultPosition, wxDefaultSize); + upperOnly->ForceUpper(); + // multi line text controls wxString string3L("Read only\nMultiline\nFitted size"); @@ -1194,6 +1198,7 @@ MyPanel::MyPanel( wxFrame *frame, int x, int y, int w, int h ) column1->Add( m_password, 0, wxALL | wxEXPAND, 10 ); column1->Add( m_readonly, 0, wxALL, 10 ); column1->Add( m_limited, 0, wxALL, 10 ); + column1->Add( upperOnly, 0, wxALL, 10 ); column1->Add( m_horizontal, 1, wxALL | wxEXPAND, 10 ); wxBoxSizer *column2 = new wxBoxSizer(wxVERTICAL); diff --git a/src/common/textentrycmn.cpp b/src/common/textentrycmn.cpp index f6c7db5b3c..298eb408a4 100644 --- a/src/common/textentrycmn.cpp +++ b/src/common/textentrycmn.cpp @@ -310,6 +310,66 @@ bool wxTextEntryBase::CanPaste() const return false; } +// ---------------------------------------------------------------------------- +// input restrictions +// ---------------------------------------------------------------------------- + +#ifndef wxHAS_NATIVE_TEXT_FORCEUPPER + +namespace +{ + +// Poor man's lambda: helper for binding ConvertToUpperCase() to the event +struct ForceUpperFunctor +{ + explicit ForceUpperFunctor(wxTextEntryBase* entry) + : m_entry(entry) + { + } + + void operator()(wxCommandEvent& event) + { + event.Skip(); + m_entry->ConvertToUpperCase(); + } + + wxTextEntryBase* const m_entry; +}; + +} // anonymous namespace + +#endif // !wxHAS_NATIVE_TEXT_FORCEUPPER + +void wxTextEntryBase::ConvertToUpperCase() +{ + const wxString& valueOld = GetValue(); + const wxString& valueNew = valueOld.Upper(); + + if ( valueNew != valueOld ) + { + long from, to; + GetSelection(&from, &to); + ChangeValue(valueNew); + SetSelection(from, to); + } +} + +void wxTextEntryBase::ForceUpper() +{ + // Do nothing if this method is never called because a native override is + // provided: this is just a tiny size-saving optimization, nothing else. +#ifndef wxHAS_NATIVE_TEXT_FORCEUPPER + wxWindow* const win = GetEditableWindow(); + wxCHECK_RET( win, wxS("can't be called before creating the window") ); + + // Convert the current control contents to upper case + ConvertToUpperCase(); + + // And ensure that any text entered in the future is converted too + win->Bind(wxEVT_TEXT, ForceUpperFunctor(this)); +#endif // !wxHAS_NATIVE_TEXT_FORCEUPPER +} + // ---------------------------------------------------------------------------- // hints support // ---------------------------------------------------------------------------- diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 2de7e069f7..f031844925 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -33,6 +33,7 @@ #include #include "wx/gtk/private.h" #include "wx/gtk/private/gtk2-compat.h" +#include "wx/gtk/private/string.h" // ============================================================================ // signal handlers implementation @@ -43,8 +44,8 @@ extern "C" void wx_gtk_insert_text_callback(GtkEditable *editable, const gchar * new_text, - gint WXUNUSED(new_text_length), - gint * WXUNUSED(position), + gint new_text_length, + gint * position, wxTextEntry *text) { GtkEntry *entry = GTK_ENTRY (editable); @@ -78,6 +79,40 @@ wx_gtk_insert_text_callback(GtkEditable *editable, } } + // Check if we have to convert all input to upper-case + if ( !handled && text->GTKIsUpperCase() ) + { + const wxGtkString upper(g_utf8_strup(new_text, new_text_length)); + + // Use the converted text to generate events + if ( !text->GTKEntryOnInsertText(upper) ) + { + // Event not handled, so do insert the text: we have to do it + // ourselves to use the upper-case version of it + + // Prevent recursive call to this handler again + g_signal_handlers_block_by_func + ( + editable, + (gpointer)wx_gtk_insert_text_callback, + text + ); + + gtk_editable_insert_text(editable, upper, strlen(upper), position); + + g_signal_handlers_unblock_by_func + ( + editable, + (gpointer)wx_gtk_insert_text_callback, + text + ); + } + + // Don't call the default handler in any case, either the event was + // handled in the user code or we've already inserted the text. + handled = true; + } + if ( !handled && text->GTKEntryOnInsertText(new_text) ) { // If we already handled the new text insertion, don't do it again. @@ -386,7 +421,7 @@ void wxTextEntry::SetEditable(bool editable) } // ---------------------------------------------------------------------------- -// max text length +// input restrictions // ---------------------------------------------------------------------------- void wxTextEntry::SetMaxLength(unsigned long len) @@ -411,6 +446,16 @@ void wxTextEntry::SendMaxLenEvent() win->HandleWindowEvent(event); } +void wxTextEntry::ForceUpper() +{ + if ( !m_isUpperCase ) + { + ConvertToUpperCase(); + + m_isUpperCase = true; + } +} + // ---------------------------------------------------------------------------- // IM handling // ---------------------------------------------------------------------------- diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index ed83993928..bf5a2da4ff 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -898,7 +898,7 @@ void wxTextEntry::SetEditable(bool editable) } // ---------------------------------------------------------------------------- -// max length +// input restrictions // ---------------------------------------------------------------------------- void wxTextEntry::SetMaxLength(unsigned long len) @@ -913,6 +913,15 @@ void wxTextEntry::SetMaxLength(unsigned long len) ::SendMessage(GetEditHwnd(), EM_LIMITTEXT, len, 0); } +void wxTextEntry::ForceUpper() +{ + ConvertToUpperCase(); + + const HWND hwnd = GetEditHwnd(); + const LONG styleOld = ::GetWindowLong(hwnd, GWL_STYLE); + ::SetWindowLong(hwnd, GWL_STYLE, styleOld | ES_UPPERCASE); +} + // ---------------------------------------------------------------------------- // hints // ---------------------------------------------------------------------------- diff --git a/src/osx/cocoa/textctrl.mm b/src/osx/cocoa/textctrl.mm index a0cc33b445..0605e8fb3f 100644 --- a/src/osx/cocoa/textctrl.mm +++ b/src/osx/cocoa/textctrl.mm @@ -116,21 +116,23 @@ protected : NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil; -// a minimal NSFormatter that just avoids getting too long entries -@interface wxMaximumLengthFormatter : NSFormatter +// an NSFormatter used to implement SetMaxLength() and ForceUpper() methods +@interface wxTextEntryFormatter : NSFormatter { int maxLength; wxTextEntry* field; + bool forceUpper; } @end -@implementation wxMaximumLengthFormatter +@implementation wxTextEntryFormatter - (id)init { self = [super init]; maxLength = 0; + forceUpper = false; return self; } @@ -139,6 +141,11 @@ NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil; maxLength = maxlen; } +- (void) forceUpper +{ + forceUpper = true; +} + - (NSString *)stringForObjectValue:(id)anObject { if(![anObject isKindOfClass:[NSString class]]) @@ -155,12 +162,25 @@ NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil; - (BOOL)isPartialStringValid:(NSString **)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString **)error { - int len = [*partialStringPtr length]; - if ( maxLength > 0 && len > maxLength ) + if ( maxLength > 0 ) { - field->SendMaxLenEvent(); - return NO; + if ( [*partialStringPtr length] > maxLength ) + { + field->SendMaxLenEvent(); + return NO; + } } + + if ( forceUpper ) + { + NSString* upper = [*partialStringPtr uppercaseString]; + if ( ![*partialStringPtr isEqual:upper] ) + { + *partialStringPtr = upper; + return NO; + } + } + return YES; } @@ -869,12 +889,30 @@ void wxNSTextFieldControl::SetStringValue( const wxString &str) [m_textField setStringValue: wxCFStringRef( str , m_wxPeer->GetFont().GetEncoding() ).AsNSString()]; } +wxTextEntryFormatter* wxNSTextFieldControl::GetFormatter() +{ + // We only ever call setFormatter with wxTextEntryFormatter, so the cast is + // safe. + wxTextEntryFormatter* + formatter = (wxTextEntryFormatter*)[m_textField formatter]; + if ( !formatter ) + { + formatter = [[[wxTextEntryFormatter alloc] init] autorelease]; + [formatter setTextEntry:GetTextEntry()]; + [m_textField setFormatter:formatter]; + } + + return formatter; +} + void wxNSTextFieldControl::SetMaxLength(unsigned long len) { - wxMaximumLengthFormatter* formatter = [[[wxMaximumLengthFormatter alloc] init] autorelease]; - [formatter setMaxLength:len]; - [formatter setTextEntry:GetTextEntry()]; - [m_textField setFormatter:formatter]; + [GetFormatter() setMaxLength:len]; +} + +void wxNSTextFieldControl::ForceUpper() +{ + [GetFormatter() forceUpper]; } void wxNSTextFieldControl::Copy() diff --git a/src/osx/textentry_osx.cpp b/src/osx/textentry_osx.cpp index 8a1466ba83..54421bb598 100644 --- a/src/osx/textentry_osx.cpp +++ b/src/osx/textentry_osx.cpp @@ -81,6 +81,23 @@ void wxTextEntry::SetMaxLength(unsigned long len) m_maxLength = len ; } +void wxTextEntry::ForceUpper() +{ + wxTextWidgetImpl* const textPeer = GetTextPeer(); + + wxCHECK_RET( textPeer, "Must create the control first" ); + + if ( textPeer->CanForceUpper() ) + { + ConvertToUpperCase(); + textPeer->ForceUpper(); + } + else + { + wxTextEntryBase::ForceUpper(); + } +} + // Clipboard operations void wxTextEntry::Copy()