1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/textctrl.cpp
3 // Purpose: wxTextCtrl
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 04/01/98
7 // Copyright: (c) Julian Smart
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22
23 #if wxUSE_TEXTCTRL
24
25 #ifndef WX_PRECOMP
26 #include "wx/textctrl.h"
27 #include "wx/settings.h"
28 #include "wx/brush.h"
29 #include "wx/dcclient.h"
30 #include "wx/utils.h"
31 #include "wx/intl.h"
32 #include "wx/log.h"
33 #include "wx/app.h"
34 #include "wx/menu.h"
35 #include "wx/math.h"
36 #include "wx/module.h"
37 #include "wx/wxcrtvararg.h"
38 #endif
39
40 #include "wx/fontutil.h"
41 #include "wx/scopedptr.h"
42 #include "wx/stack.h"
43 #include "wx/sysopt.h"
44
45 #if wxUSE_CLIPBOARD
46 #include "wx/clipbrd.h"
47 #include "wx/dataobj.h"
48 #endif
49
50 #include "wx/textfile.h"
51
52 #include <windowsx.h>
53
54 #include "wx/msw/private.h"
55 #include "wx/msw/winundef.h"
56
57 #include <string.h>
58 #include <stdlib.h>
59
60 #include <sys/types.h>
61
62 #if wxUSE_RICHEDIT
63 #include <richedit.h>
64 #include <richole.h>
65
66 // MinGW32 doesn't have tom.h and doesn't define the interfaces and the
67 // constants we need, so we can't use ITextDocument::Undo() with it. All
68 // the other compilers do have this header.
69 #ifndef __MINGW32_TOOLCHAIN__
70 #define wxHAS_TOM_H
71 #endif
72
73 #ifdef wxHAS_TOM_H
74 #include <tom.h>
75 #endif
76
77 #include "wx/msw/ole/oleutils.h"
78
79 #include "wx/msw/private/comptr.h"
80 #endif // wxUSE_RICHEDIT
81
82 #if wxUSE_INKEDIT
83 #include <wx/dynlib.h>
84 #endif
85
86 #include "wx/msw/missing.h"
87
88 #ifndef CFM_BACKCOLOR
89 #define CFM_BACKCOLOR 0x04000000
90 #endif
91
92 #ifndef CFE_AUTOBACKCOLOR
93 #define CFE_AUTOBACKCOLOR 0x04000000
94 #endif
95
96 // missing defines for MinGW build
97 #ifndef CFM_UNDERLINETYPE
98 #define CFM_UNDERLINETYPE 0x00800000
99 #endif
100
101 #ifndef CFU_UNDERLINENONE
102 #define CFU_UNDERLINENONE 0
103 #endif
104
105 #ifndef CFU_UNDERLINE
106 #define CFU_UNDERLINE 1
107 #endif
108
109 #ifndef CFU_UNDERLINEDOUBLE
110 #define CFU_UNDERLINEDOUBLE 3
111 #endif
112
113 #ifndef CFU_UNDERLINEWAVE
114 #define CFU_UNDERLINEWAVE 8
115 #endif
116
117 #if wxUSE_DRAG_AND_DROP && wxUSE_RICHEDIT
118
119 // dummy value used for m_dropTarget, different from any valid pointer value
120 // (which are all even under Windows) and NULL
121 static wxDropTarget *
122 wxRICHTEXT_DEFAULT_DROPTARGET = reinterpret_cast<wxDropTarget *>(1);
123
124 #endif // wxUSE_DRAG_AND_DROP && wxUSE_RICHEDIT
125
126 #if wxUSE_OLE
127 // This must be the last header included to only affect the DEFINE_GUID()
128 // occurrences below but not any GUIDs declared in the standard files included
129 // above.
130 #include <initguid.h>
131
132 namespace
133 {
134
135 // Normally the IRichEditOleCallback interface and its IID are defined in
136 // richole.h header file included in the platform SDK but MinGW doesn't
137 // have the IID symbol (but does have the interface). Work around it by
138 // defining it ourselves.
139 DEFINE_GUID(wxIID_IRichEditOleCallback,
140 0x00020d03, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
141
142 } // anonymous namespace
143
144 #ifdef wxHAS_TOM_H
145
146 // This one is not defined in the standard libraries at all and MSDN just says
147 // to define it explicitly, so we do it for IID_XXX constant itself and not our
148 // own wxIID_XXX.
149 DEFINE_GUID(IID_ITextDocument,
150 0x8cc497c0, 0xa1df, 0x11ce, 0x80, 0x98, 0x00, 0xaa, 0x00, 0x47, 0xbe, 0x5d);
151
152 #endif // wxHAS_TOM_H
153
154 #endif // wxUSE_OLE
155
156 // ----------------------------------------------------------------------------
157 // private classes
158 // ----------------------------------------------------------------------------
159
160 #if wxUSE_RICHEDIT
161
162 // this module initializes RichEdit DLL(s) if needed
163 class wxRichEditModule : public wxModule
164 {
165 public:
166 enum Version
167 {
168 Version_1, // riched32.dll
169 Version_2or3, // both use riched20.dll
170 Version_41, // msftedit.dll (XP SP1 and Windows 2003)
171 Version_Max
172 };
173
174 virtual bool OnInit() wxOVERRIDE;
175 virtual void OnExit() wxOVERRIDE;
176
177 // load the richedit DLL for the specified version of rich edit
178 static bool Load(Version version);
179
180 #if wxUSE_INKEDIT
181 // load the InkEdit library
182 static bool LoadInkEdit();
183 #endif
184
185 private:
186 // the handles to richedit 1.0 and 2.0 (or 3.0) DLLs
187 static HINSTANCE ms_hRichEdit[Version_Max];
188
189 #if wxUSE_INKEDIT
190 static wxDynamicLibrary ms_inkEditLib;
191 static bool ms_inkEditLibLoadAttemped;
192 #endif
193
194 wxDECLARE_DYNAMIC_CLASS(wxRichEditModule);
195 };
196
197 HINSTANCE wxRichEditModule::ms_hRichEdit[Version_Max] = { NULL, NULL, NULL };
198
199 #if wxUSE_INKEDIT
200 wxDynamicLibrary wxRichEditModule::ms_inkEditLib;
201 bool wxRichEditModule::ms_inkEditLibLoadAttemped = false;
202 #endif
203
204 wxIMPLEMENT_DYNAMIC_CLASS(wxRichEditModule, wxModule);
205
206 #if wxUSE_OLE
207
208 extern wxMenu *wxCurrentPopupMenu;
209
210 class wxTextCtrlOleCallback : public IRichEditOleCallback
211 {
212 public:
wxTextCtrlOleCallback(wxTextCtrl * text)213 wxTextCtrlOleCallback(wxTextCtrl *text) : m_textCtrl(text), m_menu(NULL) {}
~wxTextCtrlOleCallback()214 virtual ~wxTextCtrlOleCallback() { DeleteContextMenuObject(); }
215
ContextSensitiveHelp(BOOL WXUNUSED (enterMode))216 wxSTDMETHODIMP ContextSensitiveHelp(BOOL WXUNUSED(enterMode)) wxOVERRIDE { return E_NOTIMPL; }
DeleteObject(LPOLEOBJECT WXUNUSED (oleobj))217 wxSTDMETHODIMP DeleteObject(LPOLEOBJECT WXUNUSED(oleobj)) wxOVERRIDE { return E_NOTIMPL; }
GetClipboardData(CHARRANGE * WXUNUSED (chrg),DWORD WXUNUSED (reco),LPDATAOBJECT * WXUNUSED (dataobj))218 wxSTDMETHODIMP GetClipboardData(CHARRANGE* WXUNUSED(chrg), DWORD WXUNUSED(reco), LPDATAOBJECT* WXUNUSED(dataobj)) wxOVERRIDE { return E_NOTIMPL; }
GetDragDropEffect(BOOL WXUNUSED (drag),DWORD WXUNUSED (grfKeyState),LPDWORD WXUNUSED (effect))219 wxSTDMETHODIMP GetDragDropEffect(BOOL WXUNUSED(drag), DWORD WXUNUSED(grfKeyState), LPDWORD WXUNUSED(effect)) wxOVERRIDE { return E_NOTIMPL; }
GetInPlaceContext(LPOLEINPLACEFRAME * WXUNUSED (frame),LPOLEINPLACEUIWINDOW * WXUNUSED (doc),LPOLEINPLACEFRAMEINFO WXUNUSED (frameInfo))220 wxSTDMETHODIMP GetInPlaceContext(LPOLEINPLACEFRAME* WXUNUSED(frame), LPOLEINPLACEUIWINDOW* WXUNUSED(doc), LPOLEINPLACEFRAMEINFO WXUNUSED(frameInfo)) wxOVERRIDE { return E_NOTIMPL; }
GetNewStorage(LPSTORAGE * WXUNUSED (stg))221 wxSTDMETHODIMP GetNewStorage(LPSTORAGE *WXUNUSED(stg)) wxOVERRIDE { return E_NOTIMPL; }
QueryAcceptData(LPDATAOBJECT WXUNUSED (dataobj),CLIPFORMAT * WXUNUSED (format),DWORD WXUNUSED (reco),BOOL WXUNUSED (really),HGLOBAL WXUNUSED (hMetaPict))222 wxSTDMETHODIMP QueryAcceptData(LPDATAOBJECT WXUNUSED(dataobj), CLIPFORMAT* WXUNUSED(format), DWORD WXUNUSED(reco), BOOL WXUNUSED(really), HGLOBAL WXUNUSED(hMetaPict)) wxOVERRIDE { return E_NOTIMPL; }
QueryInsertObject(LPCLSID WXUNUSED (clsid),LPSTORAGE WXUNUSED (stg),LONG WXUNUSED (cp))223 wxSTDMETHODIMP QueryInsertObject(LPCLSID WXUNUSED(clsid), LPSTORAGE WXUNUSED(stg), LONG WXUNUSED(cp)) wxOVERRIDE { return E_NOTIMPL; }
ShowContainerUI(BOOL WXUNUSED (show))224 wxSTDMETHODIMP ShowContainerUI(BOOL WXUNUSED(show)) wxOVERRIDE { return E_NOTIMPL; }
225
GetContextMenu(WORD WXUNUSED (seltype),LPOLEOBJECT WXUNUSED (oleobj),CHARRANGE * WXUNUSED (chrg),HMENU * menu)226 wxSTDMETHODIMP GetContextMenu(WORD WXUNUSED(seltype), LPOLEOBJECT WXUNUSED(oleobj), CHARRANGE* WXUNUSED(chrg), HMENU *menu) wxOVERRIDE
227 {
228 // 'menu' will be shown and destroyed by the caller. We need to keep
229 // its wx counterpart, the wxMenu instance, around until it is
230 // dismissed, though, so store it in m_menu and destroy sometime later.
231 DeleteContextMenuObject();
232 m_menu = m_textCtrl->MSWCreateContextMenu();
233 *menu = m_menu->GetHMenu();
234
235 // Make wx handle events from the popup menu correctly:
236 m_menu->SetInvokingWindow(m_textCtrl);
237 wxCurrentPopupMenu = m_menu;
238
239 m_menu->UpdateUI();
240
241 return S_OK;
242 }
243
244 DECLARE_IUNKNOWN_METHODS;
245
246 private:
DeleteContextMenuObject()247 void DeleteContextMenuObject()
248 {
249 if ( m_menu )
250 {
251 m_menu->MSWDetachHMENU();
252 if ( wxCurrentPopupMenu == m_menu )
253 wxCurrentPopupMenu = NULL;
254 wxDELETE(m_menu);
255 }
256 }
257
258 wxTextCtrl *m_textCtrl;
259 wxMenu *m_menu;
260
261 wxDECLARE_NO_COPY_CLASS(wxTextCtrlOleCallback);
262 };
263
264 BEGIN_IID_TABLE(wxTextCtrlOleCallback)
265 ADD_IID(Unknown)
266 ADD_RAW_IID(wxIID_IRichEditOleCallback)
267 END_IID_TABLE;
268
269 IMPLEMENT_IUNKNOWN_METHODS(wxTextCtrlOleCallback)
270
271 #endif // wxUSE_OLE
272
273 #endif // wxUSE_RICHEDIT
274
275 // a small class used to set m_updatesCount to 0 (to filter duplicate events if
276 // necessary) and to reset it back to -1 afterwards
277 class UpdatesCountFilter
278 {
279 public:
UpdatesCountFilter(int & count)280 UpdatesCountFilter(int& count)
281 : m_count(count)
282 {
283 wxASSERT_MSG( m_count == -1 || m_count == -2,
284 wxT("wrong initial m_updatesCount value") );
285
286 if (m_count != -2)
287 m_count = 0;
288 //else: we don't want to count how many update events we get as we're going
289 // to ignore all of them
290 }
291
~UpdatesCountFilter()292 ~UpdatesCountFilter()
293 {
294 m_count = -1;
295 }
296
297 // return true if an event has been received
GotUpdate() const298 bool GotUpdate() const
299 {
300 return m_count == 1;
301 }
302
303 private:
304 int& m_count;
305
306 wxDECLARE_NO_COPY_CLASS(UpdatesCountFilter);
307 };
308
309 namespace
310 {
311
312 // This stack stores the length of the text being currently inserted into the
313 // current control.
314 //
315 // It is used to pass information from DoWriteText() to AdjustSpaceLimit()
316 // and is global as text can only be inserted into a few text controls at a
317 // time (but possibly more than into one, if wxEVT_TEXT event handler does
318 // something that results in another text control update), and we don't want to
319 // waste space in every wxTextCtrl object for this field unnecessarily.
320 wxStack<int> gs_lenOfInsertedText;
321
322 } // anonymous namespace
323
324 // ----------------------------------------------------------------------------
325 // event tables and other macros
326 // ----------------------------------------------------------------------------
327
wxBEGIN_EVENT_TABLE(wxTextCtrl,wxTextCtrlBase)328 wxBEGIN_EVENT_TABLE(wxTextCtrl, wxTextCtrlBase)
329 EVT_CHAR(wxTextCtrl::OnChar)
330 EVT_KEY_DOWN(wxTextCtrl::OnKeyDown)
331 EVT_DROP_FILES(wxTextCtrl::OnDropFiles)
332
333 EVT_MENU(wxID_CUT, wxTextCtrl::OnCut)
334 EVT_MENU(wxID_COPY, wxTextCtrl::OnCopy)
335 EVT_MENU(wxID_PASTE, wxTextCtrl::OnPaste)
336 EVT_MENU(wxID_UNDO, wxTextCtrl::OnUndo)
337 EVT_MENU(wxID_REDO, wxTextCtrl::OnRedo)
338 EVT_MENU(wxID_CLEAR, wxTextCtrl::OnDelete)
339 EVT_MENU(wxID_SELECTALL, wxTextCtrl::OnSelectAll)
340
341 EVT_UPDATE_UI(wxID_CUT, wxTextCtrl::OnUpdateCut)
342 EVT_UPDATE_UI(wxID_COPY, wxTextCtrl::OnUpdateCopy)
343 EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste)
344 EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo)
345 EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo)
346 EVT_UPDATE_UI(wxID_CLEAR, wxTextCtrl::OnUpdateDelete)
347 EVT_UPDATE_UI(wxID_SELECTALL, wxTextCtrl::OnUpdateSelectAll)
348
349 EVT_SET_FOCUS(wxTextCtrl::OnSetFocus)
350 wxEND_EVENT_TABLE()
351
352 // ============================================================================
353 // implementation
354 // ============================================================================
355
356 // ----------------------------------------------------------------------------
357 // creation
358 // ----------------------------------------------------------------------------
359
360 void wxTextCtrl::Init()
361 {
362 #if wxUSE_RICHEDIT
363 m_verRichEdit = 0;
364 m_richDPIscale = 1;
365 #endif // wxUSE_RICHEDIT
366
367 #if wxUSE_INKEDIT && wxUSE_RICHEDIT
368 m_isInkEdit = 0;
369 #endif
370
371 m_privateContextMenu = NULL;
372 m_updatesCount = -1;
373 m_isNativeCaretShown = true;
374 }
375
~wxTextCtrl()376 wxTextCtrl::~wxTextCtrl()
377 {
378 #if wxUSE_DRAG_AND_DROP && wxUSE_RICHEDIT
379 if ( m_dropTarget == wxRICHTEXT_DEFAULT_DROPTARGET )
380 {
381 // don't try to destroy this dummy pointer in the base class dtor
382 m_dropTarget = NULL;
383 }
384 #endif // wxUSE_DRAG_AND_DROP && wxUSE_RICHEDIT
385
386 delete m_privateContextMenu;
387 }
388
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)389 bool wxTextCtrl::Create(wxWindow *parent,
390 wxWindowID id,
391 const wxString& value,
392 const wxPoint& pos,
393 const wxSize& size,
394 long style,
395 const wxValidator& validator,
396 const wxString& name)
397 {
398 // base initialization
399 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
400 return false;
401
402 if ( !MSWCreateText(value, pos, size) )
403 return false;
404
405 #if wxUSE_DRAG_AND_DROP && wxUSE_RICHEDIT
406 if ( IsRich() )
407 {
408 // rich text controls have a default associated drop target which
409 // allows them to receive (rich) text dropped on them, which is nice,
410 // but prevents us from associating a user-defined drop target with
411 // them as we need to unregister the old one first
412 //
413 // to make it work, we set m_dropTarget to this special value initially
414 // and check for it in our SetDropTarget()
415 m_dropTarget = wxRICHTEXT_DEFAULT_DROPTARGET;
416 }
417 #endif // wxUSE_DRAG_AND_DROP && wxUSE_RICHEDIT
418
419 return true;
420 }
421
422 // returns true if the platform should explicitly apply a theme border
CanApplyThemeBorder() const423 bool wxTextCtrl::CanApplyThemeBorder() const
424 {
425 // Standard text control already handles theming
426 return ((GetWindowStyle() & (wxTE_RICH|wxTE_RICH2)) != 0);
427 }
428
MSWCreateText(const wxString & value,const wxPoint & pos,const wxSize & size)429 bool wxTextCtrl::MSWCreateText(const wxString& value,
430 const wxPoint& pos,
431 const wxSize& size)
432 {
433 // translate wxWin style flags to MSW ones
434 WXDWORD msStyle = MSWGetCreateWindowFlags();
435
436 // do create the control - either an EDIT or RICHEDIT
437 wxString windowClass = wxT("EDIT");
438
439 #if wxUSE_RICHEDIT
440 if ( m_windowStyle & wxTE_AUTO_URL )
441 {
442 // automatic URL detection only works in RichEdit 2.0+
443 m_windowStyle |= wxTE_RICH2;
444 }
445
446 if ( m_windowStyle & wxTE_RICH2 )
447 {
448 // using richedit 2.0 implies using wxTE_RICH
449 m_windowStyle |= wxTE_RICH;
450 }
451
452 // we need to load the richedit DLL before creating the rich edit control
453 if ( m_windowStyle & wxTE_RICH )
454 {
455 // versions 2.0, 3.0 and 4.1 of rich edit are mostly compatible with
456 // each other but not with version 1.0, so we have separate flags for
457 // the version 1.0 and the others (and so m_verRichEdit may be 0 (plain
458 // EDIT control), 1 for version 1.0 or 2 for any higher version)
459 //
460 // notice that 1.0 has no Unicode support at all so in Unicode build we
461 // must use another version
462
463 #if wxUSE_UNICODE
464 m_verRichEdit = 2;
465 #else // !wxUSE_UNICODE
466 m_verRichEdit = m_windowStyle & wxTE_RICH2 ? 2 : 1;
467 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
468
469 #if wxUSE_INKEDIT
470 // First test if we can load an ink edit control. Normally, all edit
471 // controls will be made ink edit controls if a tablet environment is
472 // found (or if msw.inkedit != 0 and the InkEd.dll is present).
473 // However, an application can veto ink edit controls by either specifying
474 // msw.inkedit = 0 or by removing wxTE_RICH[2] from the style.
475 //
476 if ((wxSystemSettings::HasFeature(wxSYS_TABLET_PRESENT) || wxSystemOptions::GetOptionInt(wxT("msw.inkedit")) != 0) &&
477 !(wxSystemOptions::HasOption(wxT("msw.inkedit")) && wxSystemOptions::GetOptionInt(wxT("msw.inkedit")) == 0))
478 {
479 if (wxRichEditModule::LoadInkEdit())
480 {
481 windowClass = INKEDIT_CLASS;
482
483 #if wxUSE_INKEDIT && wxUSE_RICHEDIT
484 m_isInkEdit = 1;
485 #endif
486
487 // Fake rich text version for other calls
488 m_verRichEdit = 2;
489 }
490 }
491 #endif
492
493 #if wxUSE_INKEDIT
494 if (!IsInkEdit())
495 #endif // wxUSE_INKEDIT
496 {
497 if ( m_verRichEdit == 2 )
498 {
499 if ( wxRichEditModule::Load(wxRichEditModule::Version_41) )
500 {
501 // yes, class name for version 4.1 really is 5.0
502 windowClass = wxT("RICHEDIT50W");
503
504 m_verRichEdit = 4;
505 }
506 else if ( wxRichEditModule::Load(wxRichEditModule::Version_2or3) )
507 {
508 windowClass = wxT("RichEdit20")
509 #if wxUSE_UNICODE
510 wxT("W");
511 #else // ANSI
512 wxT("A");
513 #endif // Unicode/ANSI
514 }
515 else // failed to load msftedit.dll and riched20.dll
516 {
517 m_verRichEdit = 1;
518 }
519 }
520
521 if ( m_verRichEdit == 1 )
522 {
523 if ( wxRichEditModule::Load(wxRichEditModule::Version_1) )
524 {
525 windowClass = wxT("RICHEDIT");
526 }
527 else // failed to load any richedit control DLL
528 {
529 // only give the error msg once if the DLL can't be loaded
530 static bool s_errorGiven = false; // MT ok as only used by GUI
531
532 if ( !s_errorGiven )
533 {
534 wxLogError(_("Impossible to create a rich edit control, using simple text control instead. Please reinstall riched32.dll"));
535
536 s_errorGiven = true;
537 }
538
539 m_verRichEdit = 0;
540 }
541 }
542 } // !useInkEdit
543 }
544 #endif // wxUSE_RICHEDIT
545
546 // we need to turn '\n's into "\r\n"s for the multiline controls
547 wxString valueWin;
548 if ( m_windowStyle & wxTE_MULTILINE )
549 {
550 valueWin = wxTextFile::Translate(value, wxTextFileType_Dos);
551 }
552 else // single line
553 {
554 valueWin = value;
555 }
556
557 // suppress events sent during control creation: we're called either from
558 // the ctor and then we shouldn't generate any events for compatibility
559 // with the other ports, or from SetWindowStyleFlag() and then we shouldn't
560 // generate the events because our text doesn't really change, the fact
561 // that we (sometimes) need to recreate the control is just an
562 // implementation detail
563 m_updatesCount = -2;
564
565 if ( !MSWCreateControl(windowClass.t_str(), msStyle, pos, size, valueWin) )
566 return false;
567
568 m_updatesCount = -1;
569
570 #if wxUSE_RICHEDIT
571 if (IsRich())
572 {
573 #if wxUSE_INKEDIT
574 if (IsInkEdit())
575 {
576 // Pass IEM_InsertText (0) as wParam, in order to have the ink always
577 // converted to text.
578 ::SendMessage(GetHwnd(), EM_SETINKINSERTMODE, 0, 0);
579
580 // Make sure the mouse can be used for input
581 ::SendMessage(GetHwnd(), EM_SETUSEMOUSEFORINPUT, 1, 0);
582 }
583 #endif
584
585 // enable the events we're interested in: we want to get EN_CHANGE as
586 // for the normal controls
587 LPARAM mask = ENM_CHANGE;
588
589 if (GetRichVersion() == 1 && !IsInkEdit())
590 {
591 // we also need EN_MSGFILTER for richedit 1.0 for the reasons
592 // explained in its handler
593 mask |= ENM_MOUSEEVENTS;
594
595 // we also need to force the appearance of the vertical scrollbar
596 // initially as otherwise the control doesn't refresh correctly
597 // after resize: but once the vertical scrollbar had been shown
598 // (even if it's subsequently hidden) it does
599 //
600 // this is clearly a bug and for now it has been only noticed under
601 // Windows XP, so if we're sure it works correctly under other
602 // systems we could do this only for XP
603 SetSize(-1, 1); // 1 is small enough to force vert scrollbar
604 SetInitialSize(size);
605 }
606 else if ( m_windowStyle & wxTE_AUTO_URL )
607 {
608 mask |= ENM_LINK;
609
610 ::SendMessage(GetHwnd(), EM_AUTOURLDETECT, TRUE, 0);
611 }
612
613 ::SendMessage(GetHwnd(), EM_SETEVENTMASK, 0, mask);
614
615 bool contextMenuConnected = false;
616 #if wxUSE_OLE
617 if ( m_verRichEdit >= 4 )
618 {
619 wxTextCtrlOleCallback *cb = new wxTextCtrlOleCallback(this);
620 if ( ::SendMessage(GetHwnd(), EM_SETOLECALLBACK, 0, (LPARAM)cb) )
621 {
622 // If we succeeded in setting up the callback, we don't need to
623 // connect to wxEVT_CONTEXT_MENU to show the menu ourselves,
624 // but we do need to connect to wxEVT_RIGHT_UP to generate
625 // wxContextMenuEvent ourselves as we're not going to get it
626 // from the control which consumes it.
627 contextMenuConnected = true;
628 Bind(wxEVT_RIGHT_UP, &wxTextCtrl::OnRightUp, this);
629 }
630 }
631 #endif
632 if ( !contextMenuConnected )
633 Bind(wxEVT_CONTEXT_MENU, &wxTextCtrl::OnContextMenu, this);
634
635 // Determine the system DPI and the DPI of the display the rich control
636 // is shown on, and calculate and apply the scaling factor.
637 // When this control is created in a (wxFrame) constructor the zoom is
638 // not correctly applied, use CallAfter to delay setting the zoom.
639 m_richDPIscale = GetDPI().y / (float)::GetDeviceCaps(ScreenHDC(), LOGPIXELSY);
640 CallAfter(&wxTextCtrl::MSWSetRichZoom);
641 }
642 else
643 #endif // wxUSE_RICHEDIT
644 if ( HasFlag(wxTE_MULTILINE) && HasFlag(wxTE_READONLY) )
645 {
646 // non-rich read-only multiline controls have grey background by
647 // default under MSW but this is not always appropriate, so forcefully
648 // reset the background colour to normal default
649 //
650 // this is not ideal but, after a long discussion on wx-dev (see
651 // http://thread.gmane.org/gmane.comp.lib.wxwidgets.devel/116360/) it
652 // was finally deemed to be the best behaviour by default (and ideally
653 // we'd have a way to change this, see #11521)
654 SetBackgroundColour(GetClassDefaultAttributes().colBg);
655 }
656
657 // Without this, if we pass the size in the constructor and then don't change it,
658 // the themed borders will be drawn incorrectly.
659 SetWindowPos(GetHwnd(), NULL, 0, 0, 0, 0,
660 SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|
661 SWP_FRAMECHANGED);
662
663 if ( IsSingleLine() )
664 {
665 // If we don't set the margins explicitly, their size depends on the
666 // control initial size, see #2438. So explicitly set them to something
667 // consistent. And for this we have 2 candidates: EC_USEFONTINFO (which
668 // sets the left margin to 3 pixels, at least under Windows 7) or 0. We
669 // use the former because it looks like it was meant to be used as the
670 // default (what else would it be there for?) and 0 looks bad in
671 // classic mode, i.e. without themes. Also, the margin can be reset to
672 // 0 easily by calling SetMargins() explicitly but setting it to the
673 // default value is not currently supported.
674 //
675 // Finally, notice that EC_USEFONTINFO is used differently for plain
676 // and rich text controls.
677 WPARAM wParam;
678 LPARAM lParam;
679 if ( IsRich() )
680 {
681 wParam = EC_USEFONTINFO;
682 lParam = 0;
683 }
684 else // plain EDIT, EC_USEFONTINFO is used in lParam with them.
685 {
686 wParam = EC_LEFTMARGIN | EC_RIGHTMARGIN;
687 lParam = MAKELPARAM(EC_USEFONTINFO, EC_USEFONTINFO);
688 }
689
690 ::SendMessage(GetHwnd(), EM_SETMARGINS, wParam, lParam);
691 }
692
693 #if wxUSE_RICHEDIT && wxUSE_OLE && defined(wxHAS_TOM_H)
694 // For RichEdit >= 4, SetFont(), called above from MSWCreateControl(), uses
695 // EM_SETCHARFORMAT which affects the undo buffer, meaning that CanUndo()
696 // for a newly created control returns true, which is unexpected. To avoid
697 // this, we explicitly use Undo(tomFalse) here to clear the undo buffer.
698 // And since Undo(tomFalse) also disables the undo buffer, we need to
699 // enable it again immediately after clearing by calling Undo(tomTrue).
700 if ( GetRichVersion() >= 4 )
701 {
702 wxCOMPtr<IRichEditOle> pRichEditOle;
703 if ( SendMessage(GetHwnd(), EM_GETOLEINTERFACE,
704 0, (LPARAM)&pRichEditOle) && pRichEditOle )
705 {
706 wxCOMPtr<ITextDocument> pDoc;
707 HRESULT hr = pRichEditOle->QueryInterface
708 (
709 wxIID_PPV_ARGS(ITextDocument, &pDoc)
710 );
711 if ( SUCCEEDED(hr) )
712 {
713 hr = pDoc->Undo(tomFalse, NULL);
714 if ( SUCCEEDED(hr) )
715 pDoc->Undo(tomTrue, NULL);
716 }
717 }
718 }
719 #endif // wxUSE_RICHEDIT && wxHAS_TOM_H
720
721 return true;
722 }
723
724 // Make sure the window style (etc.) reflects the HWND style (roughly)
AdoptAttributesFromHWND()725 void wxTextCtrl::AdoptAttributesFromHWND()
726 {
727 wxWindow::AdoptAttributesFromHWND();
728
729 HWND hWnd = GetHwnd();
730 long style = ::GetWindowLong(hWnd, GWL_STYLE);
731
732 // retrieve the style to see whether this is an edit or richedit ctrl
733 #if wxUSE_RICHEDIT
734 wxString classname = wxGetWindowClass(GetHWND());
735
736 if ( classname.IsSameAs(wxT("EDIT"), false /* no case */) )
737 {
738 m_verRichEdit = 0;
739 }
740 else // rich edit?
741 {
742 wxChar c;
743 if ( wxSscanf(classname, wxT("RichEdit%d0%c"), &m_verRichEdit, &c) != 2 )
744 {
745 wxLogDebug(wxT("Unknown edit control '%s'."), classname.c_str());
746
747 m_verRichEdit = 0;
748 }
749 }
750 #endif // wxUSE_RICHEDIT
751
752 if (style & ES_MULTILINE)
753 m_windowStyle |= wxTE_MULTILINE;
754 if (style & ES_PASSWORD)
755 m_windowStyle |= wxTE_PASSWORD;
756 if (style & ES_READONLY)
757 m_windowStyle |= wxTE_READONLY;
758 if (style & ES_WANTRETURN)
759 m_windowStyle |= wxTE_PROCESS_ENTER;
760 if (style & ES_CENTER)
761 m_windowStyle |= wxTE_CENTRE;
762 if (style & ES_RIGHT)
763 m_windowStyle |= wxTE_RIGHT;
764 }
765
MSWGetStyle(long style,WXDWORD * exstyle) const766 WXDWORD wxTextCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
767 {
768 long msStyle = wxControl::MSWGetStyle(style, exstyle);
769
770 // styles which we always add by default
771 if ( style & wxTE_MULTILINE )
772 {
773 msStyle |= ES_MULTILINE | ES_WANTRETURN;
774 if ( !(style & wxTE_NO_VSCROLL) )
775 {
776 // always adjust the vertical scrollbar automatically if we have it
777 msStyle |= WS_VSCROLL | ES_AUTOVSCROLL;
778
779 #if wxUSE_RICHEDIT
780 // we have to use this style for the rich edit controls because
781 // without it the vertical scrollbar never appears at all in
782 // richedit 3.0 because of our ECO_NOHIDESEL hack (search for it)
783 if ( style & wxTE_RICH2 )
784 {
785 msStyle |= ES_DISABLENOSCROLL;
786 }
787 #endif // wxUSE_RICHEDIT
788 }
789
790 style |= wxTE_PROCESS_ENTER;
791 }
792 else // !multiline
793 {
794 // there is really no reason to not have this style for single line
795 // text controls
796 msStyle |= ES_AUTOHSCROLL;
797 }
798
799 // note that wxTE_DONTWRAP is the same as wxHSCROLL so if we have a horz
800 // scrollbar, there is no wrapping -- which makes sense
801 if ( style & wxTE_DONTWRAP )
802 {
803 // automatically scroll the control horizontally as necessary
804 //
805 // NB: ES_AUTOHSCROLL is needed for richedit controls or they don't
806 // show horz scrollbar at all, even in spite of WS_HSCROLL, and as
807 // it doesn't seem to do any harm for plain edit controls, add it
808 // always
809 msStyle |= WS_HSCROLL | ES_AUTOHSCROLL;
810 }
811
812 if ( style & wxTE_READONLY )
813 msStyle |= ES_READONLY;
814
815 if ( style & wxTE_PASSWORD )
816 msStyle |= ES_PASSWORD;
817
818 if ( style & wxTE_NOHIDESEL )
819 msStyle |= ES_NOHIDESEL;
820
821 // note that we can't do do "& wxTE_LEFT" as wxTE_LEFT == 0
822 if ( style & wxTE_CENTRE )
823 msStyle |= ES_CENTER;
824 else if ( style & wxTE_RIGHT )
825 msStyle |= ES_RIGHT;
826 else
827 msStyle |= ES_LEFT; // ES_LEFT is 0 as well but for consistency...
828
829 return msStyle;
830 }
831
SetWindowStyleFlag(long style)832 void wxTextCtrl::SetWindowStyleFlag(long style)
833 {
834 // changing the alignment of the control dynamically works under Win2003
835 // (but not older Windows version: it seems to work under some versions of
836 // XP but not other ones, and we have no way to determine it so be
837 // conservative here) and only for plain EDIT controls (not RICH ones) and
838 // we have to recreate the control to make it always work
839 if ( IsRich() || wxGetWinVersion() < wxWinVersion_2003 )
840 {
841 const long alignMask = wxTE_LEFT | wxTE_CENTRE | wxTE_RIGHT;
842 if ( (style & alignMask) != (GetWindowStyle() & alignMask) )
843 {
844 const wxString value = GetValue();
845 const wxPoint pos = GetPosition();
846 const wxSize size = GetSize();
847
848 // delete the old window
849 HWND hwnd = GetHwnd();
850 DissociateHandle();
851 ::DestroyWindow(hwnd);
852
853 // create the new one with the updated flags
854 m_windowStyle = style;
855 MSWCreateText(value, pos, size);
856
857 // and make sure it has the same attributes as before
858 if ( m_hasFont )
859 {
860 // calling SetFont(m_font) would do nothing as the code would
861 // notice that the font didn't change, so force it to believe
862 // that it did
863 wxFont font = m_font;
864 m_font = wxNullFont;
865 SetFont(font);
866 }
867
868 if ( m_hasFgCol )
869 {
870 wxColour colFg = m_foregroundColour;
871 m_foregroundColour = wxNullColour;
872 SetForegroundColour(colFg);
873 }
874
875 if ( m_hasBgCol )
876 {
877 wxColour colBg = m_backgroundColour;
878 m_backgroundColour = wxNullColour;
879 SetBackgroundColour(colBg);
880 }
881
882 // note that text styles are lost but this is probably not a big
883 // problem: if you use styles, you probably don't use nor change
884 // alignment flags anyhow
885
886 return;
887 }
888 }
889
890 #if wxUSE_RICHEDIT
891 // we have to deal with some styles separately because they can't be
892 // changed by simply calling SetWindowLong(GWL_STYLE) but can be changed
893 // using richedit-specific EM_SETOPTIONS
894 if ( IsRich() &&
895 ((style & wxTE_NOHIDESEL) != (GetWindowStyle() & wxTE_NOHIDESEL)) )
896 {
897 bool set = (style & wxTE_NOHIDESEL) != 0;
898
899 ::SendMessage(GetHwnd(), EM_SETOPTIONS, set ? ECOOP_OR : ECOOP_AND,
900 set ? ECO_NOHIDESEL : ~ECO_NOHIDESEL);
901 }
902 #endif // wxUSE_RICHEDIT
903
904 wxControl::SetWindowStyleFlag(style);
905 }
906
907 // ----------------------------------------------------------------------------
908 // set/get the controls text
909 // ----------------------------------------------------------------------------
910
IsEmpty() const911 bool wxTextCtrl::IsEmpty() const
912 {
913 // this is an optimization for multiline controls containing a lot of text
914 if ( IsMultiLine() && GetNumberOfLines() != 1 )
915 return false;
916
917 return wxTextCtrlBase::IsEmpty();
918 }
919
GetValue() const920 wxString wxTextCtrl::GetValue() const
921 {
922 // range 0..-1 is special for GetRange() and means to retrieve all text
923 return GetRange(0, -1);
924 }
925
GetRange(long from,long to) const926 wxString wxTextCtrl::GetRange(long from, long to) const
927 {
928 wxString str;
929
930 if ( from >= to && to != -1 )
931 {
932 // nothing to retrieve
933 return str;
934 }
935
936 #if wxUSE_RICHEDIT
937 if ( IsRich() )
938 {
939 int len = GetWindowTextLength(GetHwnd());
940 if ( len > from )
941 {
942 if ( to == -1 )
943 to = len;
944
945 #if !wxUSE_UNICODE
946 // we must use EM_STREAMOUT if we don't want to lose all characters
947 // not representable in the current character set (EM_GETTEXTRANGE
948 // simply replaces them with question marks...)
949 if ( GetRichVersion() > 1 )
950 {
951 // we must have some encoding, otherwise any 8bit chars in the
952 // control are simply *lost* (replaced by '?')
953 wxFontEncoding encoding = wxFONTENCODING_SYSTEM;
954
955 wxFont font = m_defaultStyle.GetFont();
956 if ( !font.IsOk() )
957 font = GetFont();
958
959 if ( font.IsOk() )
960 {
961 encoding = font.GetEncoding();
962 }
963
964 #if wxUSE_INTL
965 if ( encoding == wxFONTENCODING_SYSTEM )
966 {
967 encoding = wxLocale::GetSystemEncoding();
968 }
969 #endif // wxUSE_INTL
970
971 if ( encoding == wxFONTENCODING_SYSTEM )
972 {
973 encoding = wxFONTENCODING_ISO8859_1;
974 }
975
976 str = StreamOut(encoding);
977
978 if ( !str.empty() )
979 {
980 // we have to manually extract the required part, luckily
981 // this is easy in this case as EOL characters in str are
982 // just LFs because we remove CRs in wxRichEditStreamOut
983 str = str.Mid(from, to - from);
984 }
985 }
986
987 // StreamOut() wasn't used or failed, try to do it in normal way
988 if ( str.empty() )
989 #endif // !wxUSE_UNICODE
990 {
991 // alloc one extra WORD as needed by the control
992 wxStringBuffer tmp(str, ++len);
993 wxChar *p = tmp;
994
995 TEXTRANGE textRange;
996 textRange.chrg.cpMin = from;
997 textRange.chrg.cpMax = to;
998 textRange.lpstrText = p;
999
1000 (void)::SendMessage(GetHwnd(), EM_GETTEXTRANGE,
1001 0, (LPARAM)&textRange);
1002
1003 if ( m_verRichEdit > 1 )
1004 {
1005 // RichEdit 2.0 uses just CR ('\r') for the
1006 // newlines which is neither Unix nor Windows
1007 // style - convert it to something reasonable
1008 for ( ; *p; p++ )
1009 {
1010 if ( *p == wxT('\r') )
1011 *p = wxT('\n');
1012 }
1013 }
1014 }
1015
1016 if ( m_verRichEdit == 1 )
1017 {
1018 // convert to the canonical form - see comment below
1019 str = wxTextFile::Translate(str, wxTextFileType_Unix);
1020 }
1021 }
1022 //else: no text at all, leave the string empty
1023 }
1024 else
1025 #endif // wxUSE_RICHEDIT
1026 {
1027 // retrieve all text: wxTextEntry method works even for multiline
1028 // controls and must be used for single line ones to account for hints
1029 str = wxTextEntry::GetValue();
1030
1031 // need only a range?
1032 if ( from < to )
1033 {
1034 str = str.Mid(from, to - from);
1035 }
1036
1037 // WM_GETTEXT uses standard DOS CR+LF (\r\n) convention - convert to the
1038 // canonical one (same one as above) for consistency with the other kinds
1039 // of controls and, more importantly, with the other ports
1040 str = wxTextFile::Translate(str, wxTextFileType_Unix);
1041 }
1042
1043 return str;
1044 }
1045
DoSetValue(const wxString & value,int flags)1046 void wxTextCtrl::DoSetValue(const wxString& value, int flags)
1047 {
1048 // if the text is long enough, it's faster to just set it instead of first
1049 // comparing it with the old one (chances are that it will be different
1050 // anyhow, this comparison is there to avoid flicker for small single-line
1051 // edit controls mostly)
1052 if ( (value.length() > 0x400) || (value != DoGetValue()) )
1053 {
1054 DoWriteText(value, flags /* doesn't include SelectionOnly here */);
1055
1056 // mark the control as being not dirty - we changed its text, not the
1057 // user
1058 DiscardEdits();
1059
1060 // for compatibility, don't move the cursor when doing SetValue()
1061 SetInsertionPoint(0);
1062 }
1063 else // same text
1064 {
1065 // still reset the modified flag even if the value didn't really change
1066 // because now it comes from the program and not the user (and do it
1067 // before generating the event so that the event handler could get the
1068 // expected value from IsModified())
1069 DiscardEdits();
1070
1071 // still send an event for consistency
1072 if (flags & SetValue_SendEvent)
1073 SendUpdateEvent();
1074 }
1075 }
1076
1077 #if wxUSE_RICHEDIT && !wxUSE_UNICODE
1078
1079 // TODO: using memcpy() would improve performance a lot for big amounts of text
1080
1081 DWORD CALLBACK
wxRichEditStreamIn(DWORD_PTR dwCookie,BYTE * buf,LONG cb,LONG * pcb)1082 wxRichEditStreamIn(DWORD_PTR dwCookie, BYTE *buf, LONG cb, LONG *pcb)
1083 {
1084 *pcb = 0;
1085
1086 const wchar_t ** const ppws = (const wchar_t **)dwCookie;
1087
1088 wchar_t *wbuf = (wchar_t *)buf;
1089 const wchar_t *wpc = *ppws;
1090 while ( cb && *wpc )
1091 {
1092 *wbuf++ = *wpc++;
1093
1094 cb -= sizeof(wchar_t);
1095 (*pcb) += sizeof(wchar_t);
1096 }
1097
1098 *ppws = wpc;
1099
1100 return 0;
1101 }
1102
1103 // helper struct used to pass parameters from wxTextCtrl to wxRichEditStreamOut
1104 struct wxStreamOutData
1105 {
1106 wchar_t *wpc;
1107 size_t len;
1108 };
1109
1110 DWORD CALLBACK
wxRichEditStreamOut(DWORD_PTR dwCookie,BYTE * buf,LONG cb,LONG * pcb)1111 wxRichEditStreamOut(DWORD_PTR dwCookie, BYTE *buf, LONG cb, LONG *pcb)
1112 {
1113 *pcb = 0;
1114
1115 wxStreamOutData *data = (wxStreamOutData *)dwCookie;
1116
1117 const wchar_t *wbuf = (const wchar_t *)buf;
1118 wchar_t *wpc = data->wpc;
1119 while ( cb )
1120 {
1121 wchar_t wch = *wbuf++;
1122
1123 // turn "\r\n" into "\n" on the fly
1124 if ( wch != L'\r' )
1125 *wpc++ = wch;
1126 else
1127 data->len--;
1128
1129 cb -= sizeof(wchar_t);
1130 (*pcb) += sizeof(wchar_t);
1131 }
1132
1133 data->wpc = wpc;
1134
1135 return 0;
1136 }
1137
1138
1139 bool
StreamIn(const wxString & value,wxFontEncoding encoding,bool selectionOnly)1140 wxTextCtrl::StreamIn(const wxString& value,
1141 wxFontEncoding encoding,
1142 bool selectionOnly)
1143 {
1144 wxCSConv conv(encoding);
1145
1146 const size_t len = conv.MB2WC(NULL, value.mb_str(), value.length());
1147
1148 if (len == wxCONV_FAILED)
1149 return false;
1150
1151 wxWCharBuffer wchBuf(len); // allocates one extra character
1152 wchar_t *wpc = wchBuf.data();
1153
1154 conv.MB2WC(wpc, value.mb_str(), len + 1);
1155
1156 // finally, stream it in the control
1157 EDITSTREAM eds;
1158 wxZeroMemory(eds);
1159 eds.dwCookie = (DWORD_PTR)&wpc;
1160 // the cast below is needed for broken (very) old mingw32 headers
1161 eds.pfnCallback = (EDITSTREAMCALLBACK)wxRichEditStreamIn;
1162
1163 // same problem as in DoWriteText(): we can get multiple events here
1164 UpdatesCountFilter ucf(m_updatesCount);
1165
1166 ::SendMessage(GetHwnd(), EM_STREAMIN,
1167 SF_TEXT |
1168 SF_UNICODE |
1169 (selectionOnly ? SFF_SELECTION : 0),
1170 (LPARAM)&eds);
1171
1172 // It's okay for EN_UPDATE to not be sent if the selection is empty and
1173 // the text is empty, otherwise warn the programmer about it.
1174 wxASSERT_MSG( ucf.GotUpdate() || ( !HasSelection() && value.empty() ),
1175 wxT("EM_STREAMIN didn't send EN_UPDATE?") );
1176
1177 if ( eds.dwError )
1178 {
1179 wxLogLastError(wxT("EM_STREAMIN"));
1180 }
1181
1182 return true;
1183 }
1184
1185 wxString
StreamOut(wxFontEncoding encoding,bool selectionOnly) const1186 wxTextCtrl::StreamOut(wxFontEncoding encoding, bool selectionOnly) const
1187 {
1188 wxString out;
1189
1190 const int len = GetWindowTextLength(GetHwnd());
1191
1192 wxWCharBuffer wchBuf(len);
1193 wchar_t *wpc = wchBuf.data();
1194
1195 wxStreamOutData data;
1196 data.wpc = wpc;
1197 data.len = len;
1198
1199 EDITSTREAM eds;
1200 wxZeroMemory(eds);
1201 eds.dwCookie = (DWORD_PTR)&data;
1202 eds.pfnCallback = wxRichEditStreamOut;
1203
1204 ::SendMessage
1205 (
1206 GetHwnd(),
1207 EM_STREAMOUT,
1208 SF_TEXT | SF_UNICODE | (selectionOnly ? SFF_SELECTION : 0),
1209 (LPARAM)&eds
1210 );
1211
1212 if ( eds.dwError )
1213 {
1214 wxLogLastError(wxT("EM_STREAMOUT"));
1215 }
1216 else // streamed out ok
1217 {
1218 // NUL-terminate the string because its length could have been
1219 // decreased by wxRichEditStreamOut
1220 *(wchBuf.data() + data.len) = L'\0';
1221
1222 // now convert to the given encoding (this is a possibly lossful
1223 // conversion but what else can we do)
1224 wxCSConv conv(encoding);
1225 size_t lenNeeded = conv.WC2MB(NULL, wchBuf, 0);
1226
1227 if ( lenNeeded != wxCONV_FAILED && lenNeeded++ )
1228 {
1229 conv.WC2MB(wxStringBuffer(out, lenNeeded), wchBuf, lenNeeded);
1230 }
1231 }
1232
1233 return out;
1234 }
1235
1236 #endif // wxUSE_RICHEDIT
1237
WriteText(const wxString & value)1238 void wxTextCtrl::WriteText(const wxString& value)
1239 {
1240 DoWriteText(value);
1241 }
1242
DoWriteText(const wxString & value,int flags)1243 void wxTextCtrl::DoWriteText(const wxString& value, int flags)
1244 {
1245 bool selectionOnly = (flags & SetValue_SelectionOnly) != 0;
1246 wxString valueDos;
1247 if ( m_windowStyle & wxTE_MULTILINE )
1248 valueDos = wxTextFile::Translate(value, wxTextFileType_Dos);
1249 else
1250 valueDos = value;
1251
1252 #if wxUSE_RICHEDIT
1253 // there are several complications with the rich edit controls here
1254 bool done = false;
1255 if ( IsRich() )
1256 {
1257 // first, ensure that the new text will be in the default style
1258 if ( !m_defaultStyle.IsDefault() )
1259 {
1260 long start, end;
1261 GetSelection(&start, &end);
1262 SetStyle(start, end, m_defaultStyle);
1263 }
1264
1265 #if !wxUSE_UNICODE
1266 // next check if the text we're inserting must be shown in a non
1267 // default charset -- this only works for RichEdit > 1.0
1268 if ( GetRichVersion() > 1 )
1269 {
1270 wxFont font = m_defaultStyle.GetFont();
1271 if ( !font.IsOk() )
1272 font = GetFont();
1273
1274 if ( font.IsOk() )
1275 {
1276 wxFontEncoding encoding = font.GetEncoding();
1277 if ( encoding != wxFONTENCODING_SYSTEM )
1278 {
1279 // we have to use EM_STREAMIN to force richedit control 2.0+
1280 // to show any text in the non default charset -- otherwise
1281 // it thinks it knows better than we do and always shows it
1282 // in the default one
1283 done = StreamIn(valueDos, encoding, selectionOnly);
1284 }
1285 }
1286 }
1287 #endif // !wxUSE_UNICODE
1288 }
1289
1290 if ( !done )
1291 #endif // wxUSE_RICHEDIT
1292 {
1293 // in some cases we get 2 EN_CHANGE notifications after the SendMessage
1294 // call (this happens for plain EDITs with EM_REPLACESEL and under some
1295 // -- undetermined -- conditions with rich edit) and sometimes we don't
1296 // get any events at all (plain EDIT with WM_SETTEXT), so ensure that
1297 // we generate exactly one of them by ignoring all but the first one in
1298 // SendUpdateEvent() and generating one ourselves if we hadn't got any
1299 // notifications from Windows
1300 if ( !(flags & SetValue_SendEvent) )
1301 m_updatesCount = -2; // suppress any update event
1302
1303 UpdatesCountFilter ucf(m_updatesCount);
1304
1305 // Remember the length of the text we're inserting so that
1306 // AdjustSpaceLimit() could adjust the limit to be big enough for it:
1307 // and also signal us whether it did it by resetting it to 0.
1308 gs_lenOfInsertedText.push(valueDos.length());
1309
1310 ::SendMessage(GetHwnd(), selectionOnly ? EM_REPLACESEL : WM_SETTEXT,
1311 // EM_REPLACESEL takes 1 to indicate the operation should be redoable
1312 selectionOnly ? 1 : 0, wxMSW_CONV_LPARAM(valueDos));
1313
1314 const int lenActuallyInserted = gs_lenOfInsertedText.top();
1315 gs_lenOfInsertedText.pop();
1316
1317 if ( lenActuallyInserted == -1 )
1318 {
1319 // Text size limit has been hit and added text has been truncated.
1320 // But the max length has been increased by the EN_MAXTEXT message
1321 // handler, which also reset the top of the lengths stack to -1),
1322 // so we should be able to set it successfully now if we try again.
1323 if ( selectionOnly )
1324 Undo();
1325
1326 ::SendMessage(GetHwnd(), selectionOnly ? EM_REPLACESEL : WM_SETTEXT,
1327 selectionOnly ? 1 : 0, wxMSW_CONV_LPARAM(valueDos));
1328 }
1329
1330 if ( !ucf.GotUpdate() && (flags & SetValue_SendEvent) )
1331 {
1332 SendUpdateEvent();
1333 }
1334 }
1335 }
1336
AppendText(const wxString & text)1337 void wxTextCtrl::AppendText(const wxString& text)
1338 {
1339 wxTextEntry::AppendText(text);
1340
1341 #if wxUSE_RICHEDIT
1342 // don't do this if we're frozen, saves some time
1343 if ( !IsFrozen() && IsMultiLine() && GetRichVersion() > 1 )
1344 {
1345 ::SendMessage(GetHwnd(), WM_VSCROLL, SB_BOTTOM, (LPARAM)NULL);
1346 }
1347 #endif // wxUSE_RICHEDIT
1348 }
1349
Clear()1350 void wxTextCtrl::Clear()
1351 {
1352 ::SetWindowText(GetHwnd(), wxEmptyString);
1353
1354 if ( IsMultiLine() && !IsRich() )
1355 {
1356 // rich edit controls send EN_UPDATE from WM_SETTEXT handler themselves
1357 // but the normal ones don't -- make Clear() behaviour consistent by
1358 // always sending this event
1359 SendUpdateEvent();
1360 }
1361 }
1362
EmulateKeyPress(const wxKeyEvent & event)1363 bool wxTextCtrl::EmulateKeyPress(const wxKeyEvent& event)
1364 {
1365 SetFocus();
1366
1367 size_t lenOld = GetValue().length();
1368
1369 wxUint32 code = event.GetRawKeyCode();
1370 ::keybd_event((BYTE)code, 0, 0 /* key press */, 0);
1371 ::keybd_event((BYTE)code, 0, KEYEVENTF_KEYUP, 0);
1372
1373 // assume that any alphanumeric key changes the total number of characters
1374 // in the control - this should work in 99% of cases
1375 return GetValue().length() != lenOld;
1376 }
1377
1378 // ----------------------------------------------------------------------------
1379 // Accessors
1380 // ----------------------------------------------------------------------------
1381
SetInsertionPointEnd()1382 void wxTextCtrl::SetInsertionPointEnd()
1383 {
1384 // we must not do anything if the caret is already there because calling
1385 // SetInsertionPoint() thaws the controls if Freeze() had been called even
1386 // if it doesn't actually move the caret anywhere and so the simple fact of
1387 // doing it results in horrible flicker when appending big amounts of text
1388 // to the control in a few chunks (see DoAddText() test in the text sample)
1389 const wxTextPos lastPosition = GetLastPosition();
1390 if ( GetInsertionPoint() == lastPosition )
1391 {
1392 return;
1393 }
1394
1395 SetInsertionPoint(lastPosition);
1396 }
1397
GetInsertionPoint() const1398 long wxTextCtrl::GetInsertionPoint() const
1399 {
1400 #if wxUSE_RICHEDIT
1401 if ( IsRich() )
1402 {
1403 CHARRANGE range;
1404 range.cpMin = 0;
1405 range.cpMax = 0;
1406 ::SendMessage(GetHwnd(), EM_EXGETSEL, 0, (LPARAM) &range);
1407 return range.cpMin;
1408 }
1409 #endif // wxUSE_RICHEDIT
1410
1411 return wxTextEntry::GetInsertionPoint();
1412 }
1413
GetLastPosition() const1414 wxTextPos wxTextCtrl::GetLastPosition() const
1415 {
1416 if ( IsMultiLine() )
1417 {
1418 #if wxUSE_RICHEDIT
1419 if ( IsRich() )
1420 {
1421 GETTEXTLENGTHEX gtl;
1422 gtl.flags = GTL_NUMCHARS | GTL_PRECISE;
1423 gtl.codepage = GetRichVersion() > 1 ? 1200 : CP_ACP;
1424 return ::SendMessage(GetHwnd(), EM_GETTEXTLENGTHEX, (WPARAM)>l, 0);
1425 }
1426 else
1427 #endif // wxUSE_RICHEDIT
1428 {
1429 return ::GetWindowTextLength(GetHwnd());
1430 }
1431 }
1432
1433 return wxTextEntry::GetLastPosition();
1434 }
1435
1436 // If the return values from and to are the same, there is no
1437 // selection.
GetSelection(long * from,long * to) const1438 void wxTextCtrl::GetSelection(long *from, long *to) const
1439 {
1440 #if wxUSE_RICHEDIT
1441 if ( IsRich() )
1442 {
1443 CHARRANGE charRange;
1444 ::SendMessage(GetHwnd(), EM_EXGETSEL, 0, (LPARAM) &charRange);
1445
1446 *from = charRange.cpMin;
1447 *to = charRange.cpMax;
1448 }
1449 else
1450 #endif // !wxUSE_RICHEDIT
1451 {
1452 wxTextEntry::GetSelection(from, to);
1453 }
1454 }
1455
1456 // ----------------------------------------------------------------------------
1457 // selection
1458 // ----------------------------------------------------------------------------
1459
DoSetSelection(long from,long to,int flags)1460 void wxTextCtrl::DoSetSelection(long from, long to, int flags)
1461 {
1462 HWND hWnd = GetHwnd();
1463
1464 #if wxUSE_RICHEDIT
1465 if ( IsRich() )
1466 {
1467 // if from and to are both -1, it means (in wxWidgets) that all text
1468 // should be selected, translate this into Windows convention
1469 if ( (from == -1) && (to == -1) )
1470 {
1471 from = 0;
1472 }
1473
1474 CHARRANGE range;
1475 range.cpMin = from;
1476 range.cpMax = to;
1477 ::SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&range);
1478 }
1479 else
1480 #endif // wxUSE_RICHEDIT
1481 {
1482 wxTextEntry::DoSetSelection(from, to, flags);
1483 }
1484
1485 if ( (flags & SetSel_Scroll) && !IsFrozen() )
1486 {
1487 #if wxUSE_RICHEDIT
1488 // richedit 3.0 (i.e. the version living in riched20.dll distributed
1489 // with Windows 2000 and beyond) doesn't honour EM_SCROLLCARET when
1490 // emulating richedit 2.0 unless the control has focus or ECO_NOHIDESEL
1491 // option is set (but it does work ok in richedit 1.0 mode...)
1492 //
1493 // so to make it work we either need to give focus to it here which
1494 // will probably create many problems (dummy focus events; window
1495 // containing the text control being brought to foreground
1496 // unexpectedly; ...) or to temporarily set ECO_NOHIDESEL which may
1497 // create other problems too -- and in fact it does because if we turn
1498 // on/off this style while appending the text to the control, the
1499 // vertical scrollbar never appears in it even if we append tons of
1500 // text and to work around this the only solution I found was to use
1501 // ES_DISABLENOSCROLL
1502 //
1503 // this is very ugly but I don't see any other way to make this work
1504 long style = 0;
1505 if ( GetRichVersion() > 1 )
1506 {
1507 if ( !HasFlag(wxTE_NOHIDESEL) )
1508 {
1509 // setting ECO_NOHIDESEL also sets WS_VISIBLE and possibly
1510 // others, remember the style so we can reset it later if needed
1511 style = ::GetWindowLong(GetHwnd(), GWL_STYLE);
1512 ::SendMessage(GetHwnd(), EM_SETOPTIONS,
1513 ECOOP_OR, ECO_NOHIDESEL);
1514 }
1515 //else: everything is already ok
1516 }
1517 #endif // wxUSE_RICHEDIT
1518
1519 ::SendMessage(hWnd, EM_SCROLLCARET, 0, (LPARAM)0);
1520
1521 #if wxUSE_RICHEDIT
1522 // restore ECO_NOHIDESEL if we changed it
1523 if ( GetRichVersion() > 1 && !HasFlag(wxTE_NOHIDESEL) )
1524 {
1525 ::SendMessage(GetHwnd(), EM_SETOPTIONS,
1526 ECOOP_AND, ~ECO_NOHIDESEL);
1527 if ( style != ::GetWindowLong(GetHwnd(), GWL_STYLE) )
1528 ::SetWindowLong(GetHwnd(), GWL_STYLE, style);
1529 }
1530 #endif // wxUSE_RICHEDIT
1531 }
1532 }
1533
1534 // ----------------------------------------------------------------------------
1535 // dirty status
1536 // ----------------------------------------------------------------------------
1537
IsModified() const1538 bool wxTextCtrl::IsModified() const
1539 {
1540 return ::SendMessage(GetHwnd(), EM_GETMODIFY, 0, 0) != 0;
1541 }
1542
MarkDirty()1543 void wxTextCtrl::MarkDirty()
1544 {
1545 ::SendMessage(GetHwnd(), EM_SETMODIFY, TRUE, 0);
1546 }
1547
DiscardEdits()1548 void wxTextCtrl::DiscardEdits()
1549 {
1550 ::SendMessage(GetHwnd(), EM_SETMODIFY, FALSE, 0);
1551 }
1552
1553 // ----------------------------------------------------------------------------
1554 // Positions <-> coords
1555 // ----------------------------------------------------------------------------
1556
GetNumberOfLines() const1557 int wxTextCtrl::GetNumberOfLines() const
1558 {
1559 return (int)::SendMessage(GetHwnd(), EM_GETLINECOUNT, 0, 0);
1560 }
1561
XYToPosition(long x,long y) const1562 long wxTextCtrl::XYToPosition(long x, long y) const
1563 {
1564 // This gets the char index for the _beginning_ of this line
1565 long charIndex;
1566 if ( IsMultiLine() )
1567 {
1568 charIndex = ::SendMessage(GetHwnd(), EM_LINEINDEX, y, 0);
1569 if ( charIndex == -1 )
1570 return -1;
1571 }
1572 else
1573 {
1574 if ( y != 0 )
1575 return -1;
1576
1577 charIndex = 0;
1578 }
1579
1580 // Line is identified by a character position!
1581 long lineLength = ::SendMessage(GetHwnd(), EM_LINELENGTH, charIndex, 0);
1582
1583 // Notice that x == lineLength is still valid because it corresponds either
1584 // to the position of the LF at the end of any line except the last one or
1585 // to the last position, which is the position after the last character,
1586 // for the last line.
1587 if ( x > lineLength )
1588 return -1;
1589
1590 return charIndex + x;
1591 }
1592
PositionToXY(long pos,long * x,long * y) const1593 bool wxTextCtrl::PositionToXY(long pos, long *x, long *y) const
1594 {
1595 HWND hWnd = GetHwnd();
1596
1597 // This gets the line number containing the character
1598 long lineNo;
1599 #if wxUSE_RICHEDIT
1600 if ( IsRich() )
1601 {
1602 lineNo = ::SendMessage(hWnd, EM_EXLINEFROMCHAR, 0, pos);
1603 }
1604 else
1605 #endif // wxUSE_RICHEDIT
1606 {
1607 lineNo = ::SendMessage(hWnd, EM_LINEFROMCHAR, pos, 0);
1608 }
1609
1610 if ( lineNo == -1 )
1611 {
1612 // no such line
1613 return false;
1614 }
1615
1616 // This gets the char index for the _beginning_ of this line
1617 long charIndex = ::SendMessage(hWnd, EM_LINEINDEX, lineNo, 0);
1618 if ( charIndex == -1 )
1619 {
1620 return false;
1621 }
1622
1623 // Line is identified by a character position!
1624 // New lines characters are not included.
1625 long lineLength = ::SendMessage(hWnd, EM_LINELENGTH, charIndex, 0);
1626
1627 // To simplify further calculations, make position relative
1628 // to the beginning of the line.
1629 pos -= charIndex;
1630
1631 // We need to apply different approach for the position referring
1632 // to the last line so check if the next line exists.
1633 long charIndexNextLn = IsMultiLine() ?
1634 ::SendMessage(hWnd, EM_LINEINDEX, lineNo + 1, 0)
1635 : -1;
1636 if ( charIndexNextLn == -1 )
1637 {
1638 // No next line. Char position refers to the last line so
1639 // the length of the line obtained with EM_LINELENGTH is
1640 // correct because there are no new line characters at the end.
1641 if ( pos > lineLength )
1642 {
1643 return false;
1644 }
1645 }
1646 else
1647 {
1648 // Next line found. Char position doesn't refer to the last line
1649 // so we need to take into account new line characters which were
1650 // not counted by EM_LINELENGTH.
1651 long lineLengthFull = charIndexNextLn - charIndex;
1652 // (lineLengthFull - lineLength) can be 0 (for wrapped line),
1653 // 1 (for \r new line mark) or 2 (for \r\n new line mark).
1654 if ( pos > lineLengthFull )
1655 {
1656 return false;
1657 }
1658 if ( pos > lineLength )
1659 {
1660 // Char position refers to the second character of the CR/LF mark
1661 // and its physical X-Y position is the same as the position
1662 // of the first one.
1663 pos = lineLength;
1664 }
1665 }
1666
1667 // The X position is therefore a char position in the line.
1668 if ( x )
1669 *x = pos;
1670 if ( y )
1671 *y = lineNo;
1672
1673 return true;
1674 }
1675
1676 wxTextCtrlHitTestResult
HitTest(const wxPoint & pt,long * posOut) const1677 wxTextCtrl::HitTest(const wxPoint& pt, long *posOut) const
1678 {
1679 // first get the position from Windows
1680 LPARAM lParam;
1681
1682 #if wxUSE_RICHEDIT
1683 POINTL ptl;
1684 if ( IsRich() )
1685 {
1686 // for rich edit controls the position is passed iva the struct fields
1687 ptl.x = pt.x;
1688 ptl.y = pt.y;
1689 lParam = (LPARAM)&ptl;
1690 }
1691 else
1692 #endif // wxUSE_RICHEDIT
1693 {
1694 // for the plain ones, we are limited to 16 bit positions which are
1695 // combined in a single 32 bit value
1696 lParam = MAKELPARAM(pt.x, pt.y);
1697 }
1698
1699 LRESULT pos = ::SendMessage(GetHwnd(), EM_CHARFROMPOS, 0, lParam);
1700
1701 if ( pos == -1 )
1702 {
1703 // this seems to indicate an error...
1704 return wxTE_HT_UNKNOWN;
1705 }
1706
1707 #if wxUSE_RICHEDIT
1708 if ( !IsRich() )
1709 #endif // wxUSE_RICHEDIT
1710 {
1711 // for plain EDIT controls the higher word contains something else
1712 pos = LOWORD(pos);
1713 }
1714
1715
1716 // next determine where it is relatively to our point: EM_CHARFROMPOS
1717 // always returns the closest character but we need to be more precise, so
1718 // double check that we really are where it pretends
1719 POINTL ptReal;
1720
1721 #if wxUSE_RICHEDIT
1722 // FIXME: we need to distinguish between richedit 2 and 3 here somehow but
1723 // we don't know how to do it
1724 if ( IsRich() )
1725 {
1726 ::SendMessage(GetHwnd(), EM_POSFROMCHAR, (WPARAM)&ptReal, pos);
1727 }
1728 else
1729 #endif // wxUSE_RICHEDIT
1730 {
1731 LRESULT lRc = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, pos, 0);
1732
1733 if ( lRc == -1 )
1734 {
1735 // this is apparently returned when pos corresponds to the last
1736 // position
1737 ptReal.x =
1738 ptReal.y = 0;
1739 }
1740 else
1741 {
1742 ptReal.x = LOWORD(lRc);
1743 ptReal.y = HIWORD(lRc);
1744 }
1745 }
1746
1747 wxTextCtrlHitTestResult rc;
1748
1749 if ( pt.y > ptReal.y + GetCharHeight() )
1750 rc = wxTE_HT_BELOW;
1751 else if ( pt.x > ptReal.x + GetCharWidth() )
1752 rc = wxTE_HT_BEYOND;
1753 else
1754 rc = wxTE_HT_ON_TEXT;
1755
1756 if ( posOut )
1757 *posOut = pos;
1758
1759 return rc;
1760 }
1761
DoPositionToCoords(long pos) const1762 wxPoint wxTextCtrl::DoPositionToCoords(long pos) const
1763 {
1764 // FIXME: This code is broken for rich edit version 2.0 as it uses the same
1765 // API as plain edit i.e. the coordinates are returned directly instead of
1766 // filling the POINT passed as WPARAM with them but we can't distinguish
1767 // between 2.0 and 3.0 unfortunately (see also the use of EM_POSFROMCHAR
1768 // above).
1769 #if wxUSE_RICHEDIT
1770 if ( IsRich() )
1771 {
1772 POINT pt;
1773 LRESULT rc = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, (WPARAM)&pt, pos);
1774 if ( rc != -1 )
1775 return wxPoint(pt.x, pt.y);
1776 }
1777 else
1778 #endif // wxUSE_RICHEDIT
1779 {
1780 LRESULT rc = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, pos, 0);
1781 if ( rc == -1 )
1782 {
1783 // Finding coordinates for the last position of the control fails
1784 // in plain EDIT control, try to compensate for it by finding it
1785 // ourselves from the position of the previous character.
1786 if ( pos < GetLastPosition() )
1787 {
1788 // It's not the expected correctable failure case so just fail.
1789 return wxDefaultPosition;
1790 }
1791
1792 if ( pos == 0 )
1793 {
1794 // We're being asked the coordinates of the first (and last and
1795 // only) position in an empty control. There is no way to get
1796 // it directly with EM_POSFROMCHAR but EM_GETMARGINS returns
1797 // the correct value for at least the horizontal offset.
1798 rc = ::SendMessage(GetHwnd(), EM_GETMARGINS, 0, 0);
1799
1800 // Text control seems to effectively add 1 to margin.
1801 return wxPoint(LOWORD(rc) + 1, 1);
1802 }
1803
1804 // We do have a previous character, try to get its coordinates.
1805 rc = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, pos - 1, 0);
1806 if ( rc == -1 )
1807 {
1808 // If getting coordinates of the previous character failed as
1809 // well, just give up.
1810 return wxDefaultPosition;
1811 }
1812
1813 wxString prevChar = GetRange(pos - 1, pos);
1814 wxSize prevCharSize = GetTextExtent(prevChar);
1815
1816 if ( prevChar == wxT("\n" ))
1817 {
1818 // 'pos' is at the beginning of a new line so its X coordinate
1819 // should be the same as X coordinate of the first character of
1820 // any other line while its Y coordinate will be approximately
1821 // (but we can't compute it exactly...) one character height
1822 // more than that of the previous character.
1823 LRESULT coords0 = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, 0, 0);
1824 if ( coords0 == -1 )
1825 return wxDefaultPosition;
1826
1827 rc = MAKELPARAM(LOWORD(coords0), HIWORD(rc) + prevCharSize.y);
1828 }
1829 else
1830 {
1831 // Simple case: previous character is in the same line so this
1832 // one is just after it.
1833 rc += MAKELPARAM(prevCharSize.x, 0);
1834 }
1835 }
1836
1837 // Notice that {LO,HI}WORD macros return WORDs, i.e. unsigned shorts,
1838 // while we want to have signed values here (the y coordinate of any
1839 // position above the first currently visible line is negative, for
1840 // example), hence the need for casts.
1841 return wxPoint(static_cast<short>(LOWORD(rc)),
1842 static_cast<short>(HIWORD(rc)));
1843 }
1844
1845 return wxDefaultPosition;
1846 }
1847
1848
1849 // ----------------------------------------------------------------------------
1850 //
1851 // ----------------------------------------------------------------------------
1852
ShowPosition(long pos)1853 void wxTextCtrl::ShowPosition(long pos)
1854 {
1855 HWND hWnd = GetHwnd();
1856
1857 // To scroll to a position, we pass the number of lines and characters
1858 // to scroll *by*. This means that we need to:
1859 // (1) Find the line position of the current line.
1860 // (2) Find the line position of pos.
1861 // (3) Scroll by (pos - current).
1862 // For now, ignore the horizontal scrolling.
1863
1864 // Is this where scrolling is relative to - the line containing the caret?
1865 // Or is the first visible line??? Try first visible line.
1866 // int currentLineLineNo1 = (int)::SendMessage(hWnd, EM_LINEFROMCHAR, -1, 0L);
1867
1868 int currentLineLineNo = (int)::SendMessage(hWnd, EM_GETFIRSTVISIBLELINE, 0, 0);
1869
1870 int specifiedLineLineNo = (int)::SendMessage(hWnd, EM_LINEFROMCHAR, pos, 0);
1871
1872 int linesToScroll = specifiedLineLineNo - currentLineLineNo;
1873
1874 if (linesToScroll != 0)
1875 ::SendMessage(hWnd, EM_LINESCROLL, 0, linesToScroll);
1876 }
1877
GetLengthOfLineContainingPos(long pos) const1878 long wxTextCtrl::GetLengthOfLineContainingPos(long pos) const
1879 {
1880 return ::SendMessage(GetHwnd(), EM_LINELENGTH, pos, 0);
1881 }
1882
GetLineLength(long lineNo) const1883 int wxTextCtrl::GetLineLength(long lineNo) const
1884 {
1885 long pos = XYToPosition(0, lineNo);
1886
1887 if ( pos == -1 )
1888 return -1;
1889
1890 return GetLengthOfLineContainingPos(pos);
1891 }
1892
GetLineText(long lineNo) const1893 wxString wxTextCtrl::GetLineText(long lineNo) const
1894 {
1895 size_t len = (size_t)GetLineLength(lineNo) + 1;
1896
1897 // there must be at least enough place for the length WORD in the
1898 // buffer
1899 len += sizeof(WORD);
1900
1901 wxString str;
1902 {
1903 wxStringBufferLength tmp(str, len);
1904 wxChar *buf = tmp;
1905
1906 *(WORD *)buf = (WORD)len;
1907 len = (size_t)::SendMessage(GetHwnd(), EM_GETLINE, lineNo, (LPARAM)buf);
1908
1909 #if wxUSE_RICHEDIT
1910 if ( IsRich() )
1911 {
1912 // remove the '\r' returned by the rich edit control, the user code
1913 // should never see it
1914 if ( buf[len - 2] == wxT('\r') && buf[len - 1] == wxT('\n') )
1915 {
1916 // richedit 1.0 uses "\r\n" as line terminator, so remove "\r"
1917 // here and "\n" below
1918 buf[len - 2] = wxT('\n');
1919 len--;
1920 }
1921 else if ( buf[len - 1] == wxT('\r') )
1922 {
1923 // richedit 2.0+ uses only "\r", replace it with "\n"
1924 buf[len - 1] = wxT('\n');
1925 }
1926 }
1927 #endif // wxUSE_RICHEDIT
1928
1929 // remove the '\n' at the end, if any (this is how this function is
1930 // supposed to work according to the docs)
1931 if ( len && buf[len - 1] == wxT('\n') )
1932 {
1933 len--;
1934 }
1935
1936 buf[len] = 0;
1937 tmp.SetLength(len);
1938 }
1939
1940 return str;
1941 }
1942
SetMaxLength(unsigned long len)1943 void wxTextCtrl::SetMaxLength(unsigned long len)
1944 {
1945 #if wxUSE_RICHEDIT
1946 if ( IsRich() )
1947 {
1948 ::SendMessage(GetHwnd(), EM_EXLIMITTEXT, 0, len ? len : 0x7fffffff);
1949 }
1950 else
1951 #endif // wxUSE_RICHEDIT
1952 {
1953 wxTextEntry::SetMaxLength(len);
1954 }
1955 }
1956
1957 // ----------------------------------------------------------------------------
1958 // RTL support
1959 // ----------------------------------------------------------------------------
1960
SetLayoutDirection(wxLayoutDirection dir)1961 void wxTextCtrl::SetLayoutDirection(wxLayoutDirection dir)
1962 {
1963 // We only need to handle this specifically for plain EDIT controls, rich
1964 // edit ones behave like any other window.
1965 if ( IsRich() )
1966 {
1967 wxTextCtrlBase::SetLayoutDirection(dir);
1968 }
1969 else
1970 {
1971 if ( wxUpdateEditLayoutDirection(GetHwnd(), dir) )
1972 {
1973 // Update text layout by forcing the control to redo it, a simple
1974 // Refresh() is not enough.
1975 SendSizeEvent();
1976 Refresh();
1977 }
1978 }
1979 }
1980
GetLayoutDirection() const1981 wxLayoutDirection wxTextCtrl::GetLayoutDirection() const
1982 {
1983 // Just as above, we need to handle plain EDIT controls specially.
1984 return IsRich() ? wxTextCtrlBase::GetLayoutDirection()
1985 : wxGetEditLayoutDirection(GetHwnd());
1986 }
1987
1988 // ----------------------------------------------------------------------------
1989 // Undo/redo
1990 // ----------------------------------------------------------------------------
1991
Redo()1992 void wxTextCtrl::Redo()
1993 {
1994 #if wxUSE_RICHEDIT
1995 if ( GetRichVersion() > 1 )
1996 {
1997 ::SendMessage(GetHwnd(), EM_REDO, 0, 0);
1998 return;
1999 }
2000 #endif // wxUSE_RICHEDIT
2001
2002 wxTextEntry::Redo();
2003 }
2004
CanRedo() const2005 bool wxTextCtrl::CanRedo() const
2006 {
2007 #if wxUSE_RICHEDIT
2008 if ( GetRichVersion() > 1 )
2009 return ::SendMessage(GetHwnd(), EM_CANREDO, 0, 0) != 0;
2010 #endif // wxUSE_RICHEDIT
2011
2012 return wxTextEntry::CanRedo();
2013 }
2014
2015 // ----------------------------------------------------------------------------
2016 // caret handling (Windows only)
2017 // ----------------------------------------------------------------------------
2018
ShowNativeCaret(bool show)2019 bool wxTextCtrl::ShowNativeCaret(bool show)
2020 {
2021 if ( show != m_isNativeCaretShown )
2022 {
2023 if ( !(show ? ::ShowCaret(GetHwnd()) : ::HideCaret(GetHwnd())) )
2024 {
2025 // not an error, may simply indicate that it's not shown/hidden
2026 // yet (i.e. it had been hidden/shown 2 times before)
2027 return false;
2028 }
2029
2030 m_isNativeCaretShown = show;
2031 }
2032
2033 return true;
2034 }
2035
2036 // ----------------------------------------------------------------------------
2037 // implementation details
2038 // ----------------------------------------------------------------------------
2039
Command(wxCommandEvent & event)2040 void wxTextCtrl::Command(wxCommandEvent & event)
2041 {
2042 SetValue(event.GetString());
2043 ProcessCommand (event);
2044 }
2045
OnDropFiles(wxDropFilesEvent & event)2046 void wxTextCtrl::OnDropFiles(wxDropFilesEvent& event)
2047 {
2048 // By default, load the first file into the text window.
2049 if (event.GetNumberOfFiles() > 0)
2050 {
2051 LoadFile(event.GetFiles()[0]);
2052 }
2053 }
2054
2055 // ----------------------------------------------------------------------------
2056 // kbd input processing
2057 // ----------------------------------------------------------------------------
2058
MSWShouldPreProcessMessage(WXMSG * msg)2059 bool wxTextCtrl::MSWShouldPreProcessMessage(WXMSG* msg)
2060 {
2061 // check for our special keys here: if we don't do it and the parent frame
2062 // uses them as accelerators, they wouldn't work at all, so we disable
2063 // usual preprocessing for them
2064 if ( msg->message == WM_KEYDOWN )
2065 {
2066 const WPARAM vkey = msg->wParam;
2067 if ( HIWORD(msg->lParam) & KF_ALTDOWN )
2068 {
2069 // Alt-Backspace is accelerator for "Undo"
2070 if ( vkey == VK_BACK )
2071 return false;
2072 }
2073 else // no Alt
2074 {
2075 // we want to process some Ctrl-foo and Shift-bar but no key
2076 // combinations without either Ctrl or Shift nor with both of them
2077 // pressed
2078 const int ctrl = wxIsCtrlDown(),
2079 shift = wxIsShiftDown();
2080 switch ( ctrl + shift )
2081 {
2082 default:
2083 wxFAIL_MSG( wxT("how many modifiers have we got?") );
2084 wxFALLTHROUGH;
2085
2086 case 0:
2087 switch ( vkey )
2088 {
2089 case VK_RETURN:
2090 // This one is only special for multi line controls.
2091 if ( !IsMultiLine() )
2092 break;
2093 wxFALLTHROUGH;
2094
2095 case VK_DELETE:
2096 case VK_HOME:
2097 case VK_END:
2098 return false;
2099 }
2100 wxFALLTHROUGH;
2101 case 2:
2102 break;
2103
2104 case 1:
2105 // either Ctrl or Shift pressed
2106 if ( ctrl )
2107 {
2108 switch ( vkey )
2109 {
2110 case 'A':
2111 case 'C':
2112 case 'V':
2113 case 'X':
2114 case VK_INSERT:
2115 case VK_DELETE:
2116 case VK_HOME:
2117 case VK_END:
2118 return false;
2119
2120 case VK_BACK:
2121 if ( MSWNeedsToHandleCtrlBackspace() )
2122 return false;
2123 }
2124 }
2125 else // Shift is pressed
2126 {
2127 if ( vkey == VK_INSERT || vkey == VK_DELETE )
2128 return false;
2129 }
2130 }
2131 }
2132 }
2133
2134 return wxControl::MSWShouldPreProcessMessage(msg);
2135 }
2136
OnChar(wxKeyEvent & event)2137 void wxTextCtrl::OnChar(wxKeyEvent& event)
2138 {
2139 switch ( event.GetKeyCode() )
2140 {
2141 case WXK_RETURN:
2142 case WXK_NUMPAD_ENTER:
2143 // Single line controls only get this key code if they have
2144 // wxTE_PROCESS_ENTER style, but multiline ones always get it
2145 // because they need it for themselves. However we shouldn't
2146 // generate wxEVT_TEXT_ENTER for the controls without this style,
2147 // so test for it explicitly.
2148 if ( HasFlag(wxTE_PROCESS_ENTER) )
2149 {
2150 wxCommandEvent evt(wxEVT_TEXT_ENTER, m_windowId);
2151 InitCommandEvent(evt);
2152 evt.SetString(GetValue());
2153 if ( HandleWindowEvent(evt) )
2154 return;
2155 }
2156 break;
2157
2158 case WXK_TAB:
2159 // ok, so this is getting absolutely ridiculous but I don't see
2160 // any other way to fix this bug: when a multiline text control is
2161 // inside a wxFrame, we need to generate the navigation event as
2162 // otherwise nothing happens at all, but when the same control is
2163 // created inside a dialog, IsDialogMessage() *does* switch focus
2164 // all by itself and so if we do it here as well, it is advanced
2165 // twice and goes to the next control... to prevent this from
2166 // happening we're doing this ugly check, the logic being that if
2167 // we don't have focus then it had been already changed to the next
2168 // control
2169 //
2170 // the right thing to do would, of course, be to understand what
2171 // the hell is IsDialogMessage() doing but this is beyond my feeble
2172 // forces at the moment unfortunately
2173 if ( !(m_windowStyle & wxTE_PROCESS_TAB) || !IsEditable() )
2174 {
2175 if ( ::GetFocus() == GetHwnd() )
2176 {
2177 int flags = wxNavigationKeyEvent::FromTab;
2178 if (!event.ShiftDown())
2179 flags |= wxNavigationKeyEvent::IsForward ;
2180 if (event.ControlDown())
2181 flags |= wxNavigationKeyEvent::WinChange ;
2182 if (Navigate(flags))
2183 return;
2184 }
2185 }
2186 else
2187 {
2188 // Insert tab since calling the default Windows handler
2189 // doesn't seem to do it
2190 WriteText(wxT("\t"));
2191 return;
2192 }
2193 break;
2194 }
2195
2196 // no, we didn't process it
2197 event.Skip();
2198 }
2199
2200 #if wxUSE_OLE
2201
MSWProcessSpecialKey(wxKeyEvent & event)2202 void wxTextCtrl::MSWProcessSpecialKey(wxKeyEvent& event)
2203 {
2204 // It is not a good idea, in general, to manually call another event
2205 // handler, but here we need to do exactly the same thing as in OnChar()
2206 // above, so it doesn't seem to make much sense to add another function to
2207 // forward to when we can just call it directly.
2208 OnChar(event);
2209 }
2210
2211 #endif // wxUSE_OLE
2212
MSWDeleteWordBack()2213 void wxTextCtrl::MSWDeleteWordBack()
2214 {
2215 // Surprisingly the behaviour of Ctrl+Backspace is different in all three
2216 // cases where it's supported by MSW itself:
2217 //
2218 // 1. Rich edit controls simply ignore selection and handle it as usual.
2219 // 2. Plain edit controls don't do anything when there is selection.
2220 // 3. Notepad in Windows 10 1809 and later deletes just the selection.
2221 //
2222 // The latter behaviour seems the most useful, so do it like this here too.
2223 if ( HasSelection() )
2224 {
2225 RemoveSelection();
2226 return;
2227 }
2228
2229 // This variable contains one end of the range to delete, the rest of this
2230 // function is concerned with finding the starting end of this range.
2231 const long end = GetInsertionPoint();
2232
2233 long col, line;
2234 if ( !PositionToXY(end, &col, &line) )
2235 return;
2236
2237 // We stop at the start of line, so remember it.
2238 const long start = XYToPosition(0, line);
2239
2240 const wxString& text = GetLineText(line);
2241
2242 // The way it works in rich text controls or when SHAutoComplete() is used
2243 // is that it deletes everything until the first span of alphanumeric
2244 // characters it finds (moving backwards). But the implementation of the
2245 // same shortcut in in notepad in Windows 10 versions 1809 and later
2246 // doesn't behave in quite the same way and doesn't handle alphanumeric
2247 // characters specially, i.e. it just stops on space. This seems more
2248 // useful and simpler to implement, and it probably will become standard in
2249 // the future, so do it like this here too.
2250
2251 // First skip all space starting from the character to the left of the
2252 // current one.
2253 long current = end;
2254 for ( ;; )
2255 {
2256 if ( current == start )
2257 {
2258 // When there is nothing but spaces to the left until the start of
2259 // line, we need to delete these spaces (if any) as well as the new
2260 // line separating this line from the previous one (if any).
2261 if ( line > 0 )
2262 {
2263 // This function is only used with plain EDITs which use "\r\n"
2264 // and so we need to subtract 2 to account for the new line.
2265 current -= 2;
2266 }
2267
2268 break;
2269 }
2270
2271 // We start from the previous character.
2272 --current;
2273
2274 // Did we find the end of the previous "word"?
2275 if ( text[current - start] != ' ' )
2276 {
2277 for ( ;; )
2278 {
2279 if ( current == start )
2280 {
2281 // We don't delete the new line in this case, as we're going to
2282 // delete some non-spaces in this line.
2283 break;
2284 }
2285
2286 --current;
2287
2288 if ( text[current - start] == ' ' )
2289 {
2290 // Don't delete the space itself.
2291 ++current;
2292 break;
2293 }
2294 }
2295
2296 break;
2297 }
2298 }
2299
2300 Remove(current, end);
2301 }
2302
MSWNeedsToHandleCtrlBackspace() const2303 bool wxTextCtrl::MSWNeedsToHandleCtrlBackspace() const
2304 {
2305 // We want to handle the undocumented Ctrl+Backspace shortcut only if it's
2306 // not handled by the control itself, which is a bit tricky because it's
2307 // normally only handled by rich edit controls, but plain EDIT ones may
2308 // also handle it if they use SHAutoComplete().
2309 return !HasFlag(wxTE_READONLY) &&
2310 !IsRich() &&
2311 !MSWUsesStandardAutoComplete();
2312 }
2313
OnKeyDown(wxKeyEvent & event)2314 void wxTextCtrl::OnKeyDown(wxKeyEvent& event)
2315 {
2316 // Handle Ctrl+Backspace if necessary: this is not a documented standard
2317 // shortcut, but it's a de facto standard and people expect it to work.
2318 if ( MSWNeedsToHandleCtrlBackspace() &&
2319 event.GetModifiers() == wxMOD_CONTROL &&
2320 event.GetKeyCode() == WXK_BACK )
2321 {
2322 MSWDeleteWordBack();
2323 return;
2324 }
2325
2326 // richedit control doesn't send WM_PASTE, WM_CUT and WM_COPY messages
2327 // when Ctrl-V, X or C is pressed and this prevents wxClipboardTextEvent
2328 // from working. So we work around it by intercepting these shortcuts
2329 // ourselves and emitting clipboard events (which richedit will handle,
2330 // so everything works as before, including pasting of rich text):
2331 if ( IsRich() )
2332 {
2333 if ( event.GetModifiers() == wxMOD_CONTROL )
2334 {
2335 switch ( event.GetKeyCode() )
2336 {
2337 case 'C':
2338 case WXK_INSERT:
2339 Copy();
2340 return;
2341 case 'X':
2342 Cut();
2343 return;
2344 case 'V':
2345 Paste();
2346 return;
2347 default:
2348 break;
2349 }
2350 }
2351 else if ( event.GetModifiers() == wxMOD_SHIFT )
2352 {
2353 switch ( event.GetKeyCode() )
2354 {
2355 case WXK_INSERT:
2356 Paste();
2357 return;
2358 case WXK_DELETE:
2359 Cut();
2360 return;
2361 }
2362 }
2363 }
2364
2365 if ( IsMultiLine() )
2366 {
2367 // Default window procedure of multiline edit controls posts WM_CLOSE to
2368 // the parent window when it gets Escape key press for some reason, prevent
2369 // it from doing this as this resulted in dialog boxes being closed on
2370 // Escape even when they shouldn't be (we do handle Escape ourselves
2371 // correctly in the situations when it should close them).
2372 if ( event.GetKeyCode() == WXK_ESCAPE )
2373 return;
2374
2375 // We also handle Ctrl-A as the native EDIT control doesn't do it by
2376 // default (but RICHEDIT one does, so there is no need to check for it
2377 // in the switch above), however it's a de facto standard accelerator
2378 // and people expect it to work.
2379 if ( event.GetModifiers() == wxMOD_CONTROL && event.GetKeyCode() == 'A' )
2380 {
2381 SelectAll();
2382 return;
2383 }
2384 }
2385
2386 // no, we didn't process it
2387 event.Skip();
2388 }
2389
Paste()2390 void wxTextCtrl::Paste()
2391 {
2392 // Before pasting, check that the pasted text will fit, unless an explicit
2393 // maximum length was set, to avoid only pasting some part of it.
2394 //
2395 // Note that rich text controls do not send WM_PASTE, so we can't do it in
2396 // response to it, but we could handle EN_PROTECTED (after requesting it by
2397 // specifying ENM_PROTECTED in EM_SETEVENTMASK argument) and check for the
2398 // message being WM_PASTE there, but this doesn't seem to be better than
2399 // the simpler approach used here.
2400 if ( IsRich() )
2401 {
2402 AdjustMaxLengthBeforePaste();
2403 }
2404
2405 wxTextCtrlBase::Paste();
2406 }
2407
2408 bool
MSWHandleMessage(WXLRESULT * rc,WXUINT nMsg,WXWPARAM wParam,WXLPARAM lParam)2409 wxTextCtrl::MSWHandleMessage(WXLRESULT *rc,
2410 WXUINT nMsg,
2411 WXWPARAM wParam,
2412 WXLPARAM lParam)
2413 {
2414 bool processed = wxTextCtrlBase::MSWHandleMessage(rc, nMsg, wParam, lParam);
2415
2416 switch ( nMsg )
2417 {
2418 case WM_CHAR:
2419 // Handle the special case of "Enter" key: the user code needs to specify
2420 // wxTE_PROCESS_ENTER style to get it in the first place, but if this flag
2421 // is used, then even if the wxEVT_TEXT_ENTER handler skips the event, the
2422 // normal action of this key is not performed because IsDialogMessage() is
2423 // not called and, also, an annoying beep is generated by EDIT default
2424 // WndProc.
2425 //
2426 // Fix these problems by explicitly performing the default function of this
2427 // key (which would be done by MSWProcessMessage() if we didn't have
2428 // wxTE_PROCESS_ENTER) and preventing the default WndProc from getting it.
2429 if ( !processed && wParam == VK_RETURN && IsSingleLine() )
2430 {
2431 if ( ClickDefaultButtonIfPossible() )
2432 processed = true;
2433 }
2434 break;
2435
2436 case WM_GETDLGCODE:
2437 {
2438 // Ensure that the result value is initialized even if the base
2439 // class didn't handle WM_GETDLGCODE but just update the value
2440 // returned by it if it did handle it.
2441 if ( !processed )
2442 {
2443 *rc = MSWDefWindowProc(nMsg, wParam, lParam);
2444 processed = true;
2445 }
2446
2447 // we always want the chars and the arrows: the arrows for
2448 // navigation and the chars because we want Ctrl-C to work even
2449 // in a read only control
2450 long lDlgCode = DLGC_WANTCHARS | DLGC_WANTARROWS;
2451
2452 // we may have several different cases:
2453 // 1. normal: both TAB and ENTER are used for navigation
2454 // 2. ctrl wants TAB for itself: ENTER is used to pass to
2455 // the next control in the dialog
2456 // 3. ctrl wants ENTER for itself: TAB is used for dialog
2457 // navigation
2458 // 4. ctrl wants both TAB and ENTER: Ctrl-ENTER is used to
2459 // go to the next control (we need some way to do it)
2460
2461 // multiline controls should always get ENTER for themselves
2462 if ( HasFlag(wxTE_PROCESS_ENTER) || HasFlag(wxTE_MULTILINE) )
2463 lDlgCode |= DLGC_WANTMESSAGE;
2464
2465 if ( HasFlag(wxTE_PROCESS_TAB) )
2466 lDlgCode |= DLGC_WANTTAB;
2467
2468 *rc |= lDlgCode;
2469
2470 if ( IsMultiLine() )
2471 {
2472 // The presence of this style, coming from the default EDIT
2473 // WndProc, indicates that the control contents should be
2474 // selected when it gets focus, but we don't want this to
2475 // happen for the multiline controls, so clear it.
2476 *rc &= ~DLGC_HASSETSEL;
2477 }
2478 }
2479 break;
2480
2481 #if wxUSE_MENUS
2482 case WM_SETCURSOR:
2483 // rich text controls seem to have a bug and don't change the
2484 // cursor to the standard arrow one from the I-beam cursor usually
2485 // used by them even when a popup menu is shown (this works fine
2486 // for plain EDIT controls though), so explicitly work around this
2487 if ( IsRich() )
2488 {
2489 // wxCurrentPopupMenu stores the popup menu that will receive
2490 // WM_COMMAND, but it may be non-NULL even when the underlying
2491 // native menu is no longer shown. Use ::IsMenu() to check whether
2492 // the menu still exists.
2493 extern wxMenu *wxCurrentPopupMenu;
2494 if ( wxCurrentPopupMenu &&
2495 wxCurrentPopupMenu->GetInvokingWindow() == this &&
2496 ::IsMenu(GetHmenuOf(wxCurrentPopupMenu)) )
2497 ::SetCursor(GetHcursorOf(*wxSTANDARD_CURSOR));
2498 }
2499 break;
2500 #endif // wxUSE_MENUS
2501
2502 case WM_PASTE:
2503 // Note that we get this message for plain EDIT controls only, rich
2504 // controls are dealt with in our own Paste().
2505 AdjustMaxLengthBeforePaste();
2506 break;
2507 }
2508
2509 return processed;
2510 }
2511
2512 // ----------------------------------------------------------------------------
2513 // text control event processing
2514 // ----------------------------------------------------------------------------
2515
SendUpdateEvent()2516 bool wxTextCtrl::SendUpdateEvent()
2517 {
2518 switch ( m_updatesCount )
2519 {
2520 case 0:
2521 // remember that we've got an update
2522 m_updatesCount++;
2523 break;
2524
2525 case 1:
2526 // we had already sent one event since the last control modification
2527 return false;
2528
2529 default:
2530 wxFAIL_MSG( wxT("unexpected wxTextCtrl::m_updatesCount value") );
2531 wxFALLTHROUGH;
2532
2533 case -1:
2534 // we hadn't updated the control ourselves, this event comes from
2535 // the user, don't need to ignore it nor update the count
2536 break;
2537
2538 case -2:
2539 // the control was updated programmatically and we do NOT want to
2540 // send events
2541 return false;
2542 }
2543
2544 return SendTextUpdatedEvent();
2545 }
2546
MSWCommand(WXUINT param,WXWORD WXUNUSED (id))2547 bool wxTextCtrl::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
2548 {
2549 switch ( param )
2550 {
2551 case EN_CHANGE:
2552 SendUpdateEvent();
2553 break;
2554
2555 case EN_MAXTEXT:
2556 // the text size limit has been hit -- try to increase it
2557 if ( !AdjustSpaceLimit() )
2558 {
2559 wxCommandEvent event(wxEVT_TEXT_MAXLEN, m_windowId);
2560 InitCommandEvent(event);
2561 event.SetString(GetValue());
2562 ProcessCommand(event);
2563 }
2564 break;
2565
2566 // the other edit notification messages are not processed (or, in
2567 // the case of EN_{SET/KILL}FOCUS were already handled at WM_SET/
2568 // KILLFOCUS level)
2569 default:
2570 return false;
2571 }
2572
2573 // processed
2574 return true;
2575 }
2576
MSWControlColor(WXHDC hDC,WXHWND hWnd)2577 WXHBRUSH wxTextCtrl::MSWControlColor(WXHDC hDC, WXHWND hWnd)
2578 {
2579 if ( !IsThisEnabled() && !HasFlag(wxTE_MULTILINE) )
2580 return MSWControlColorDisabled(hDC);
2581
2582 return wxTextCtrlBase::MSWControlColor(hDC, hWnd);
2583 }
2584
HasSpaceLimit(unsigned int * len) const2585 bool wxTextCtrl::HasSpaceLimit(unsigned int *len) const
2586 {
2587 // HACK: we try to automatically extend the limit for the amount of text
2588 // to allow (interactively) entering more than 64Kb of text under
2589 // Win9x but we shouldn't reset the text limit which was previously
2590 // set explicitly with SetMaxLength()
2591 //
2592 // Unfortunately there is no EM_GETLIMITTEXTSETBYUSER and so we don't
2593 // know the limit we set (if any). We could solve this by storing the
2594 // limit we set in wxTextCtrl but to save space we prefer to simply
2595 // test here the actual limit value: we consider that SetMaxLength()
2596 // can only be called for small values while EN_MAXTEXT is only sent
2597 // for large values (in practice the default limit seems to be 30000
2598 // but make it smaller just to be on the safe side)
2599 *len = ::SendMessage(GetHwnd(), EM_GETLIMITTEXT, 0, 0);
2600 return *len < 10001;
2601
2602 }
2603
AdjustSpaceLimit()2604 bool wxTextCtrl::AdjustSpaceLimit()
2605 {
2606 unsigned int limit;
2607 if ( HasSpaceLimit(&limit) )
2608 return false;
2609
2610 unsigned int len = ::GetWindowTextLength(GetHwnd());
2611 if ( len >= limit )
2612 {
2613 unsigned long increaseBy;
2614
2615 // We need to increase the size of the buffer and to avoid increasing
2616 // it too many times make sure that we make it at least big enough to
2617 // fit all the text we are currently inserting into the control, if
2618 // we're inserting any, i.e. if we're called from DoWriteText().
2619 if ( !gs_lenOfInsertedText.empty() )
2620 {
2621 increaseBy = gs_lenOfInsertedText.top();
2622
2623 // Indicate to the caller that we increased the limit.
2624 gs_lenOfInsertedText.top() = -1;
2625 }
2626 else // Not inserting text, must be text actually typed by user.
2627 {
2628 increaseBy = 0;
2629 }
2630
2631 // But also increase it by at least 32KB chunks -- again, to avoid
2632 // doing it too often -- and round it up to 32KB in any case.
2633 if ( increaseBy < 0x8000 )
2634 increaseBy = 0x8000;
2635 else
2636 increaseBy = (increaseBy + 0x7fff) & ~0x7fff;
2637
2638 SetMaxLength(len + increaseBy);
2639 }
2640
2641 // we changed the limit
2642 return true;
2643 }
2644
AdjustMaxLengthBeforePaste()2645 void wxTextCtrl::AdjustMaxLengthBeforePaste()
2646 {
2647 #if wxUSE_CLIPBOARD
2648 // We only need to do this for multi line controls, single lines should
2649 // never receive more text than fits into them by default anyhow.
2650 if ( IsSingleLine() )
2651 return;
2652
2653 // Also don't override an explicitly set limit.
2654 unsigned int limit;
2655 if ( HasSpaceLimit(&limit) )
2656 return;
2657
2658 // Otherwise check if we have enough space for clipboard data. We only do
2659 // it for plain text because this is all we know how to handle here.
2660 wxClipboardLocker lock;
2661 wxTextDataObject textData;
2662 if ( !wxTheClipboard->GetData(textData) )
2663 return;
2664
2665 // Unfortunately we can't just get the length directly because we need to
2666 // convert EOLs, otherwise our calculation of the required length could be
2667 // way off when there are many lines.
2668 const unsigned long lenPasted =
2669 wxTextFile::Translate(textData.GetText(), wxTextFileType_Dos).length();
2670
2671 long from, to;
2672 GetSelection(&from, &to);
2673 const unsigned long lenSel = to - from;
2674
2675 const unsigned long lenCurrent = GetLastPosition();
2676
2677 // We need enough space for all the current text and all the new
2678 // text, but the selection will be replaced.
2679 const unsigned long lenNeeded = lenCurrent - lenSel + lenPasted;
2680 if ( lenNeeded >= limit )
2681 {
2682 SetMaxLength(lenNeeded);
2683 }
2684 #endif // wxUSE_CLIPBOARD
2685 }
2686
AcceptsFocusFromKeyboard() const2687 bool wxTextCtrl::AcceptsFocusFromKeyboard() const
2688 {
2689 // we don't want focus if we can't be edited unless we're a multiline
2690 // control because then it might be still nice to get focus from keyboard
2691 // to be able to scroll it without mouse
2692 return (IsEditable() || IsMultiLine()) && wxControl::AcceptsFocus();
2693 }
2694
DoGetBestSize() const2695 wxSize wxTextCtrl::DoGetBestSize() const
2696 {
2697 return DoGetSizeFromTextSize( FromDIP(DEFAULT_ITEM_WIDTH) );
2698 }
2699
DoGetSizeFromTextSize(int xlen,int ylen) const2700 wxSize wxTextCtrl::DoGetSizeFromTextSize(int xlen, int ylen) const
2701 {
2702 int cy;
2703 wxFont font = GetFont();
2704 font.WXAdjustToPPI(GetDPI());
2705 wxGetCharSize(GetHWND(), NULL, &cy, font);
2706
2707 DWORD wText = FromDIP(1);
2708 ::SystemParametersInfo(SPI_GETCARETWIDTH, 0, &wText, 0);
2709 wText += xlen;
2710
2711 int hText = cy;
2712 if ( m_windowStyle & wxTE_MULTILINE )
2713 {
2714 // add space for vertical scrollbar
2715 if ( !(m_windowStyle & wxTE_NO_VSCROLL) )
2716 wText += wxGetSystemMetrics(SM_CXVSCROLL, m_parent);
2717
2718 if ( ylen <= 0 )
2719 {
2720 hText *= wxMax(wxMin(GetNumberOfLines(), 10), 2);
2721 // add space for horizontal scrollbar
2722 if ( m_windowStyle & wxHSCROLL )
2723 hText += wxGetSystemMetrics(SM_CYHSCROLL, m_parent);
2724 }
2725 }
2726 // for single line control cy (height + external leading) is ok
2727 else
2728 {
2729 // Add the margins we have previously set
2730 wxPoint marg( GetMargins() );
2731 wText += wxMax(0, marg.x);
2732 hText += wxMax(0, marg.y);
2733 }
2734
2735 // Text controls without border are special and have the same height as
2736 // static labels (they also have the same appearance when they're disable
2737 // and are often used as a sort of copyable to the clipboard label so it's
2738 // important that they have the same height as the normal labels to not
2739 // stand out).
2740 if ( !HasFlag(wxBORDER_NONE) )
2741 {
2742 wText += FromDIP(9); // borders and inner margins
2743
2744 // we have to add the adjustments for the control height only once, not
2745 // once per line, so do it after multiplication above
2746 hText += EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy) - cy;
2747 }
2748
2749 // Perhaps the user wants something different from CharHeight, or ylen
2750 // is used as the height of a multiline text.
2751 if ( ylen > 0 )
2752 hText += ylen - GetCharHeight();
2753
2754 return wxSize(wText, hText);
2755 }
2756
DoMoveWindow(int x,int y,int width,int height)2757 void wxTextCtrl::DoMoveWindow(int x, int y, int width, int height)
2758 {
2759 // We reset the text of single line controls each time their width changes
2760 // because they don't adjust their horizontal offset on their own and there
2761 // doesn't seem to be any way to convince them to do it other than by just
2762 // setting the text again, see #18268.
2763 const bool resetText = IsSingleLine() && !IsShownOnScreen();
2764 int oldWidth = -1;
2765 if ( resetText )
2766 {
2767 oldWidth = GetSize().x;
2768 }
2769
2770 wxTextCtrlBase::DoMoveWindow(x, y, width, height);
2771
2772 if ( resetText && GetSize().x != oldWidth )
2773 {
2774 // We need to use DoWriteText() to avoid our own optimization in
2775 // ChangeValue() which does nothing when the text doesn't really
2776 // change.
2777 DoWriteText(DoGetValue(), 0 /* no flags for no events */);
2778 }
2779 }
2780
2781 // ----------------------------------------------------------------------------
2782 // standard handlers for standard edit menu events
2783 // ----------------------------------------------------------------------------
2784
OnCut(wxCommandEvent & WXUNUSED (event))2785 void wxTextCtrl::OnCut(wxCommandEvent& WXUNUSED(event))
2786 {
2787 Cut();
2788 }
2789
OnCopy(wxCommandEvent & WXUNUSED (event))2790 void wxTextCtrl::OnCopy(wxCommandEvent& WXUNUSED(event))
2791 {
2792 Copy();
2793 }
2794
OnPaste(wxCommandEvent & WXUNUSED (event))2795 void wxTextCtrl::OnPaste(wxCommandEvent& WXUNUSED(event))
2796 {
2797 Paste();
2798 }
2799
OnUndo(wxCommandEvent & WXUNUSED (event))2800 void wxTextCtrl::OnUndo(wxCommandEvent& WXUNUSED(event))
2801 {
2802 Undo();
2803 }
2804
OnRedo(wxCommandEvent & WXUNUSED (event))2805 void wxTextCtrl::OnRedo(wxCommandEvent& WXUNUSED(event))
2806 {
2807 Redo();
2808 }
2809
OnDelete(wxCommandEvent & WXUNUSED (event))2810 void wxTextCtrl::OnDelete(wxCommandEvent& WXUNUSED(event))
2811 {
2812 RemoveSelection();
2813 }
2814
OnSelectAll(wxCommandEvent & WXUNUSED (event))2815 void wxTextCtrl::OnSelectAll(wxCommandEvent& WXUNUSED(event))
2816 {
2817 SelectAll();
2818 }
2819
OnUpdateCut(wxUpdateUIEvent & event)2820 void wxTextCtrl::OnUpdateCut(wxUpdateUIEvent& event)
2821 {
2822 event.Enable( CanCut() );
2823 }
2824
OnUpdateCopy(wxUpdateUIEvent & event)2825 void wxTextCtrl::OnUpdateCopy(wxUpdateUIEvent& event)
2826 {
2827 event.Enable( CanCopy() );
2828 }
2829
OnUpdatePaste(wxUpdateUIEvent & event)2830 void wxTextCtrl::OnUpdatePaste(wxUpdateUIEvent& event)
2831 {
2832 event.Enable( CanPaste() );
2833 }
2834
OnUpdateUndo(wxUpdateUIEvent & event)2835 void wxTextCtrl::OnUpdateUndo(wxUpdateUIEvent& event)
2836 {
2837 event.Enable( CanUndo() );
2838 }
2839
OnUpdateRedo(wxUpdateUIEvent & event)2840 void wxTextCtrl::OnUpdateRedo(wxUpdateUIEvent& event)
2841 {
2842 event.Enable( CanRedo() );
2843 }
2844
OnUpdateDelete(wxUpdateUIEvent & event)2845 void wxTextCtrl::OnUpdateDelete(wxUpdateUIEvent& event)
2846 {
2847 event.Enable( HasSelection() && IsEditable() );
2848 }
2849
OnUpdateSelectAll(wxUpdateUIEvent & event)2850 void wxTextCtrl::OnUpdateSelectAll(wxUpdateUIEvent& event)
2851 {
2852 event.Enable( !IsEmpty() );
2853 }
2854
OnSetFocus(wxFocusEvent & event)2855 void wxTextCtrl::OnSetFocus(wxFocusEvent& event)
2856 {
2857 // be sure the caret remains invisible if the user had hidden it
2858 if ( !m_isNativeCaretShown )
2859 {
2860 ::HideCaret(GetHwnd());
2861 }
2862
2863 event.Skip();
2864 }
2865
2866 // the rest of the file only deals with the rich edit controls
2867 #if wxUSE_RICHEDIT
2868
OnRightUp(wxMouseEvent & eventMouse)2869 void wxTextCtrl::OnRightUp(wxMouseEvent& eventMouse)
2870 {
2871 wxContextMenuEvent eventMenu(wxEVT_CONTEXT_MENU,
2872 GetId(),
2873 ClientToScreen(eventMouse.GetPosition()));
2874 eventMenu.SetEventObject(this);
2875
2876 if ( !ProcessWindowEvent(eventMenu) )
2877 eventMouse.Skip();
2878 }
2879
OnContextMenu(wxContextMenuEvent & event)2880 void wxTextCtrl::OnContextMenu(wxContextMenuEvent& event)
2881 {
2882 if (IsRich())
2883 {
2884 if (!m_privateContextMenu)
2885 m_privateContextMenu = MSWCreateContextMenu();
2886 PopupMenu(m_privateContextMenu);
2887 return;
2888 }
2889 else
2890 event.Skip();
2891 }
2892
MSWCreateContextMenu()2893 wxMenu *wxTextCtrl::MSWCreateContextMenu()
2894 {
2895 wxMenu *m = new wxMenu;
2896 m->Append(wxID_UNDO, _("&Undo"));
2897 m->Append(wxID_REDO, _("&Redo"));
2898 m->AppendSeparator();
2899 m->Append(wxID_CUT, _("Cu&t"));
2900 m->Append(wxID_COPY, _("&Copy"));
2901 m->Append(wxID_PASTE, _("&Paste"));
2902 m->Append(wxID_CLEAR, _("&Delete"));
2903 m->AppendSeparator();
2904 m->Append(wxID_SELECTALL, _("Select &All"));
2905 return m;
2906 }
2907
MSWSetRichZoom()2908 void wxTextCtrl::MSWSetRichZoom()
2909 {
2910 // nothing to scale
2911 if ( m_richDPIscale == 1 )
2912 return;
2913
2914 // get the current zoom ratio
2915 UINT num = 1;
2916 UINT denom = 1;
2917 ::SendMessage(GetHWND(), EM_GETZOOM, (WPARAM)&num, (LPARAM)&denom);
2918
2919 // combine the zoom ratio with the DPI scale factor
2920 float ratio = m_richDPIscale;
2921 if ( denom > 0 )
2922 ratio = ratio * (num / (float)denom);
2923
2924 // apply the new zoom ratio, Windows uses a default denominator of 100, so
2925 // do it here as well
2926 num = 100 * ratio;
2927 denom = 100;
2928 ::SendMessage(GetHWND(), EM_SETZOOM, (WPARAM)num, (LPARAM)denom);
2929 }
2930
MSWUpdateFontOnDPIChange(const wxSize & newDPI)2931 void wxTextCtrl::MSWUpdateFontOnDPIChange(const wxSize& newDPI)
2932 {
2933 // Don't use MSWUpdateFontOnDPIChange for the rich edit controls, they
2934 // (somehow?) update their appearance on their own and changing their
2935 // HFONT, as the base class version does, would reset all the styles used
2936 // by them when the DPI changes, which is unwanted.
2937 if ( !IsRich() )
2938 {
2939 wxTextCtrlBase::MSWUpdateFontOnDPIChange(newDPI);
2940 }
2941 // If the rich control is created on a screen with non-system DPI, an
2942 // initial zoom factor was applied. This needs to be reset after the first
2943 // DPI change. First invert the scale, then set it to 1 so it is not
2944 // applied again.
2945 else if ( m_richDPIscale != 1 )
2946 {
2947 m_richDPIscale = 1 / m_richDPIscale;
2948 MSWSetRichZoom();
2949 m_richDPIscale = 1;
2950 }
2951 }
2952
2953 // ----------------------------------------------------------------------------
2954 // EN_LINK processing
2955 // ----------------------------------------------------------------------------
2956
MSWOnNotify(int idCtrl,WXLPARAM lParam,WXLPARAM * result)2957 bool wxTextCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
2958 {
2959 NMHDR *hdr = (NMHDR* )lParam;
2960 switch ( hdr->code )
2961 {
2962 case EN_MSGFILTER:
2963 {
2964 const MSGFILTER *msgf = (MSGFILTER *)lParam;
2965 UINT msg = msgf->msg;
2966
2967 // this is a bit crazy but richedit 1.0 sends us all mouse
2968 // events _except_ WM_LBUTTONUP (don't ask me why) so we have
2969 // generate the wxWin events for this message manually
2970 //
2971 // NB: in fact, this is still not totally correct as it does
2972 // send us WM_LBUTTONUP if the selection was cleared by the
2973 // last click -- so currently we get 2 events in this case,
2974 // but as I don't see any obvious way to check for this I
2975 // leave this code in place because it's still better than
2976 // not getting left up events at all
2977 if ( msg == WM_LBUTTONUP )
2978 {
2979 WXUINT flags = msgf->wParam;
2980 int x = GET_X_LPARAM(msgf->lParam),
2981 y = GET_Y_LPARAM(msgf->lParam);
2982
2983 HandleMouseEvent(msg, x, y, flags);
2984 }
2985 }
2986
2987 // return true to process the event (and false to ignore it)
2988 return true;
2989
2990 case EN_LINK:
2991 {
2992 const ENLINK *enlink = (ENLINK *)hdr;
2993
2994 switch ( enlink->msg )
2995 {
2996 case WM_SETCURSOR:
2997 // ok, so it is hardcoded - do we really nee to
2998 // customize it?
2999 {
3000 wxCursor cur(wxCURSOR_HAND);
3001 ::SetCursor(GetHcursorOf(cur));
3002 *result = TRUE;
3003 break;
3004 }
3005
3006 case WM_MOUSEMOVE:
3007 case WM_LBUTTONDOWN:
3008 case WM_LBUTTONUP:
3009 case WM_LBUTTONDBLCLK:
3010 case WM_RBUTTONDOWN:
3011 case WM_RBUTTONUP:
3012 case WM_RBUTTONDBLCLK:
3013 // send a mouse event
3014 {
3015 static const wxEventType eventsMouse[] =
3016 {
3017 wxEVT_MOTION,
3018 wxEVT_LEFT_DOWN,
3019 wxEVT_LEFT_UP,
3020 wxEVT_LEFT_DCLICK,
3021 wxEVT_RIGHT_DOWN,
3022 wxEVT_RIGHT_UP,
3023 wxEVT_RIGHT_DCLICK,
3024 };
3025
3026 // the event ids are consecutive
3027 wxMouseEvent
3028 evtMouse(eventsMouse[enlink->msg - WM_MOUSEMOVE]);
3029
3030 InitMouseEvent(evtMouse,
3031 GET_X_LPARAM(enlink->lParam),
3032 GET_Y_LPARAM(enlink->lParam),
3033 enlink->wParam);
3034
3035 wxTextUrlEvent event(m_windowId, evtMouse,
3036 enlink->chrg.cpMin,
3037 enlink->chrg.cpMax);
3038
3039 InitCommandEvent(event);
3040
3041 *result = ProcessCommand(event);
3042 }
3043 break;
3044 }
3045 }
3046 return true;
3047 }
3048
3049 // not processed, leave it to the base class
3050 return wxTextCtrlBase::MSWOnNotify(idCtrl, lParam, result);
3051 }
3052
3053 #if wxUSE_DRAG_AND_DROP
3054
SetDropTarget(wxDropTarget * dropTarget)3055 void wxTextCtrl::SetDropTarget(wxDropTarget *dropTarget)
3056 {
3057 if ( m_dropTarget == wxRICHTEXT_DEFAULT_DROPTARGET )
3058 {
3059 // get rid of the built-in drop target
3060 ::RevokeDragDrop(GetHwnd());
3061 m_dropTarget = NULL;
3062 }
3063
3064 wxTextCtrlBase::SetDropTarget(dropTarget);
3065 }
3066
3067 #endif // wxUSE_DRAG_AND_DROP
3068
3069 // ----------------------------------------------------------------------------
3070 // colour setting for the rich edit controls
3071 // ----------------------------------------------------------------------------
3072
SetBackgroundColour(const wxColour & colour)3073 bool wxTextCtrl::SetBackgroundColour(const wxColour& colour)
3074 {
3075 if ( !wxTextCtrlBase::SetBackgroundColour(colour) )
3076 {
3077 // colour didn't really change
3078 return false;
3079 }
3080
3081 if ( IsRich() )
3082 {
3083 // rich edit doesn't use WM_CTLCOLOR, hence we need to send
3084 // EM_SETBKGNDCOLOR additionally
3085 ::SendMessage(GetHwnd(), EM_SETBKGNDCOLOR, 0, wxColourToRGB(colour));
3086 }
3087
3088 return true;
3089 }
3090
SetForegroundColour(const wxColour & colour)3091 bool wxTextCtrl::SetForegroundColour(const wxColour& colour)
3092 {
3093 if ( !wxTextCtrlBase::SetForegroundColour(colour) )
3094 {
3095 // colour didn't really change
3096 return false;
3097 }
3098
3099 if ( IsRich() )
3100 {
3101 // change the colour of everything
3102 WinStruct<CHARFORMAT> cf;
3103 cf.dwMask = CFM_COLOR;
3104 cf.crTextColor = wxColourToRGB(colour);
3105 ::SendMessage(GetHwnd(), EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&cf);
3106 }
3107
3108 return true;
3109 }
3110
SetFont(const wxFont & font)3111 bool wxTextCtrl::SetFont(const wxFont& font)
3112 {
3113 // Native text control sends EN_CHANGE when the font changes, producing
3114 // a wxEVT_TEXT event as if the user changed the value. This is not
3115 // the case, so suppress the event.
3116 wxEventBlocker block(this, wxEVT_TEXT);
3117
3118 if ( !wxTextCtrlBase::SetFont(font) )
3119 return false;
3120
3121 if ( GetRichVersion() >= 4 )
3122 {
3123 // Using WM_SETFONT is not enough with RichEdit 4.1: it does work but
3124 // for ASCII characters only and inserting a non-ASCII one into it
3125 // later reverts to the default font so use EM_SETCHARFORMAT to change
3126 // the default font for it.
3127 wxTextAttr attr;
3128 attr.SetFont(font);
3129 SetStyle(-1, -1, attr);
3130 }
3131
3132 return true;
3133 }
3134
3135 // ----------------------------------------------------------------------------
3136 // styling support for rich edit controls
3137 // ----------------------------------------------------------------------------
3138
3139 #if _RICHEDIT_VER >= 0x0800
3140 static const wxColour gs_underlineColourMap[] =
3141 {
3142 // The colours are coming from https://docs.microsoft.com/en-us/windows/desktop/api/tom/nf-tom-itextdocument2-geteffectcolor.
3143 wxNullColour, // text colour
3144 wxColour(0, 0, 0 ), // black
3145 wxColour(0, 0, 255), // blue
3146 wxColour(0, 255, 255), // cyan
3147 wxColour(0, 255, 0 ), // green
3148 wxColour(255, 0, 255), // magenta
3149 wxColour(255, 0, 0 ), // red
3150 wxColour(255, 255, 0 ), // yellow
3151 wxColour(255, 255, 255), // white
3152 wxColour(0, 0, 128), // navy
3153 wxColour(0, 128, 128), // teal
3154 wxColour(0, 128, 0 ), // light green
3155 wxColour(128, 0, 128), // purple
3156 wxColour(128, 0, 0 ), // maroon
3157 wxColour(128, 128, 0 ), // olive
3158 wxColour(128, 128, 128), // grey
3159 wxColour(192, 192, 192), // light grey
3160 };
3161 #endif
3162
MSWSetCharFormat(const wxTextAttr & style,long start,long end)3163 bool wxTextCtrl::MSWSetCharFormat(const wxTextAttr& style, long start, long end)
3164 {
3165 // initialize CHARFORMAT struct
3166 #if wxUSE_RICHEDIT2
3167 CHARFORMAT2 cf;
3168 #else
3169 CHARFORMAT cf;
3170 #endif
3171
3172 wxZeroMemory(cf);
3173
3174 // we can't use CHARFORMAT2 with RichEdit 1.0, so pretend it is a simple
3175 // CHARFORMAT in that case
3176 #if wxUSE_RICHEDIT2
3177 if ( m_verRichEdit == 1 )
3178 {
3179 // this is the only thing the control is going to grok
3180 cf.cbSize = sizeof(CHARFORMAT);
3181 }
3182 else
3183 #endif
3184 {
3185 // CHARFORMAT or CHARFORMAT2
3186 cf.cbSize = sizeof(cf);
3187 }
3188
3189 if ( style.HasFont() )
3190 {
3191 // VZ: CFM_CHARSET doesn't seem to do anything at all in RichEdit 2.0
3192 // but using it doesn't seem to hurt neither so leaving it for now
3193
3194 cf.dwMask |= CFM_FACE | CFM_SIZE | CFM_CHARSET |
3195 CFM_ITALIC | CFM_BOLD | CFM_UNDERLINE | CFM_STRIKEOUT;
3196
3197 // fill in data from LOGFONT but recalculate lfHeight because we need
3198 // the real height in twips and not the negative number used inside
3199 // LOGFONT returns (this is correct in general and works with
3200 // the Windows font mapper, but not here)
3201
3202 wxFont font(style.GetFont());
3203
3204 LOGFONT lf = font.GetNativeFontInfo()->lf;
3205 cf.yHeight = 20 * font.GetFractionalPointSize(); // 1 pt = 20 twips
3206 cf.bCharSet = lf.lfCharSet;
3207 cf.bPitchAndFamily = lf.lfPitchAndFamily;
3208 wxStrlcpy(cf.szFaceName, lf.lfFaceName, WXSIZEOF(cf.szFaceName));
3209
3210 // also deal with underline/italic/bold attributes: note that we must
3211 // always set CFM_ITALIC &c bits in dwMask, even if we don't set the
3212 // style to allow clearing it
3213 if ( lf.lfItalic )
3214 {
3215 cf.dwEffects |= CFE_ITALIC;
3216 }
3217
3218 if ( lf.lfWeight == FW_BOLD )
3219 {
3220 cf.dwEffects |= CFE_BOLD;
3221 }
3222
3223 if ( lf.lfUnderline )
3224 {
3225 cf.dwEffects |= CFE_UNDERLINE;
3226 }
3227 if ( lf.lfStrikeOut )
3228 {
3229 cf.dwEffects |= CFE_STRIKEOUT;
3230 }
3231 }
3232
3233 if ( style.HasFontUnderlined() )
3234 {
3235 cf.dwMask |= CFM_UNDERLINETYPE;
3236 BYTE underlineType = CFU_UNDERLINENONE;
3237 switch ( style.GetUnderlineType() )
3238 {
3239 case wxTEXT_ATTR_UNDERLINE_SOLID:
3240 underlineType = CFU_UNDERLINE;
3241 break;
3242 case wxTEXT_ATTR_UNDERLINE_DOUBLE:
3243 underlineType = CFU_UNDERLINEDOUBLE;
3244 break;
3245 case wxTEXT_ATTR_UNDERLINE_SPECIAL:
3246 underlineType = CFU_UNDERLINEWAVE;
3247 break;
3248 default:
3249 underlineType = CFU_UNDERLINENONE;
3250 break;
3251 }
3252 cf.bUnderlineType = underlineType;
3253
3254 #if _RICHEDIT_VER >= 0x0800
3255 BYTE colour = 0;
3256 const wxColour& col = style.GetUnderlineColour();
3257 for ( size_t c = 0; c < WXSIZEOF(gs_underlineColourMap); ++c )
3258 {
3259 if ( col == gs_underlineColourMap[c] )
3260 {
3261 colour = static_cast<BYTE>(c);
3262 break;
3263 }
3264 }
3265 cf.bUnderlineColor = colour;
3266 #endif
3267 }
3268
3269 if ( style.HasTextColour() )
3270 {
3271 cf.dwMask |= CFM_COLOR;
3272 cf.crTextColor = wxColourToRGB(style.GetTextColour());
3273 }
3274
3275 #if wxUSE_RICHEDIT2
3276 if ( m_verRichEdit != 1 && style.HasBackgroundColour() )
3277 {
3278 cf.dwMask |= CFM_BACKCOLOR;
3279 cf.crBackColor = wxColourToRGB(style.GetBackgroundColour());
3280 }
3281 #endif // wxUSE_RICHEDIT2
3282
3283 // Apply the style either to the selection or to the entire control.
3284 WPARAM selMode;
3285 if ( start != -1 || end != -1 )
3286 {
3287 DoSetSelection(start, end, SetSel_NoScroll);
3288 selMode = SCF_SELECTION;
3289 }
3290 else
3291 {
3292 selMode = SCF_ALL;
3293 }
3294
3295 if ( !::SendMessage(GetHwnd(), EM_SETCHARFORMAT, selMode, (LPARAM)&cf) )
3296 {
3297 wxLogLastError(wxT("SendMessage(EM_SETCHARFORMAT)"));
3298 return false;
3299 }
3300
3301 return true;
3302 }
3303
MSWSetParaFormat(const wxTextAttr & style,long start,long end)3304 bool wxTextCtrl::MSWSetParaFormat(const wxTextAttr& style, long start, long end)
3305 {
3306 #if wxUSE_RICHEDIT2
3307 PARAFORMAT2 pf;
3308 #else
3309 PARAFORMAT pf;
3310 #endif
3311
3312 wxZeroMemory(pf);
3313
3314 // we can't use PARAFORMAT2 with RichEdit 1.0, so pretend it is a simple
3315 // PARAFORMAT in that case
3316 #if wxUSE_RICHEDIT2
3317 if ( m_verRichEdit == 1 )
3318 {
3319 // this is the only thing the control is going to grok
3320 pf.cbSize = sizeof(PARAFORMAT);
3321 }
3322 else
3323 #endif
3324 {
3325 // PARAFORMAT or PARAFORMAT2
3326 pf.cbSize = sizeof(pf);
3327 }
3328
3329 if (style.HasAlignment())
3330 {
3331 pf.dwMask |= PFM_ALIGNMENT;
3332 if (style.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT)
3333 pf.wAlignment = PFA_RIGHT;
3334 else if (style.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE)
3335 pf.wAlignment = PFA_CENTER;
3336 else if (style.GetAlignment() == wxTEXT_ALIGNMENT_JUSTIFIED)
3337 pf.wAlignment = PFA_JUSTIFY;
3338 else
3339 pf.wAlignment = PFA_LEFT;
3340 }
3341
3342 if (style.HasLeftIndent())
3343 {
3344 pf.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
3345
3346 // Convert from 1/10 mm to TWIPS
3347 pf.dxStartIndent = (int) (((double) style.GetLeftIndent()) * mm2twips / 10.0) ;
3348 pf.dxOffset = (int) (((double) style.GetLeftSubIndent()) * mm2twips / 10.0) ;
3349 }
3350
3351 if (style.HasRightIndent())
3352 {
3353 pf.dwMask |= PFM_RIGHTINDENT;
3354
3355 // Convert from 1/10 mm to TWIPS
3356 pf.dxRightIndent = (int) (((double) style.GetRightIndent()) * mm2twips / 10.0) ;
3357 }
3358
3359 if (style.HasTabs())
3360 {
3361 pf.dwMask |= PFM_TABSTOPS;
3362
3363 const wxArrayInt& tabs = style.GetTabs();
3364
3365 pf.cTabCount = (SHORT)wxMin(tabs.GetCount(), MAX_TAB_STOPS);
3366 size_t i;
3367 for (i = 0; i < (size_t) pf.cTabCount; i++)
3368 {
3369 // Convert from 1/10 mm to TWIPS
3370 pf.rgxTabs[i] = (int) (((double) tabs[i]) * mm2twips / 10.0) ;
3371 }
3372 }
3373
3374 #if wxUSE_RICHEDIT2
3375 if ( style.HasParagraphSpacingAfter() )
3376 {
3377 pf.dwMask |= PFM_SPACEAFTER;
3378
3379 // Convert from 1/10 mm to TWIPS
3380 pf.dySpaceAfter = (int) (((double) style.GetParagraphSpacingAfter()) * mm2twips / 10.0) ;
3381 }
3382
3383 if ( style.HasParagraphSpacingBefore() )
3384 {
3385 pf.dwMask |= PFM_SPACEBEFORE;
3386
3387 // Convert from 1/10 mm to TWIPS
3388 pf.dySpaceBefore = (int) (((double) style.GetParagraphSpacingBefore()) * mm2twips / 10.0) ;
3389 }
3390 #endif // wxUSE_RICHEDIT2
3391
3392 #if wxUSE_RICHEDIT2
3393 if ( m_verRichEdit > 1 )
3394 {
3395 if ( GetLayoutDirection() == wxLayout_RightToLeft )
3396 {
3397 // Use RTL paragraphs in RTL mode to get proper layout
3398 pf.dwMask |= PFM_RTLPARA;
3399 pf.wEffects |= PFE_RTLPARA;
3400 }
3401 }
3402 #endif // wxUSE_RICHEDIT2
3403
3404 if ( pf.dwMask )
3405 {
3406 // Do format the selection.
3407 DoSetSelection(start, end, SetSel_NoScroll);
3408
3409 if ( !::SendMessage(GetHwnd(), EM_SETPARAFORMAT, 0, (LPARAM) &pf) )
3410 {
3411 wxLogLastError(wxT("SendMessage(EM_SETPARAFORMAT)"));
3412
3413 return false;
3414 }
3415 }
3416
3417 return true;
3418 }
3419
SetStyle(long start,long end,const wxTextAttr & style)3420 bool wxTextCtrl::SetStyle(long start, long end, const wxTextAttr& style)
3421 {
3422 if ( !IsRich() )
3423 {
3424 // can't do it with normal text control
3425 return false;
3426 }
3427
3428 // the richedit 1.0 doesn't handle setting background colour, so don't
3429 // even try to do anything if it's the only thing we want to change
3430 if ( m_verRichEdit == 1 && !style.HasFont() && !style.HasTextColour() &&
3431 !style.HasLeftIndent() && !style.HasRightIndent() && !style.HasAlignment() &&
3432 !style.HasTabs() && !style.GetFontUnderlined() )
3433 {
3434 // nothing to do: return true if there was really nothing to do and
3435 // false if we failed to set bg colour
3436 return !style.HasBackgroundColour();
3437 }
3438
3439 // order the range if needed
3440 if ( start > end )
3441 wxSwap(start, end);
3442
3443 // we can only change the format of the selection, so select the range we
3444 // want and restore the old selection later, after MSWSetXXXFormat()
3445 // functions (possibly) change it.
3446 long startOld, endOld;
3447 GetSelection(&startOld, &endOld);
3448
3449 bool ok = MSWSetCharFormat(style, start, end);
3450 if ( !MSWSetParaFormat(style, start, end) )
3451 ok = false;
3452
3453 if ( start != startOld || end != endOld )
3454 {
3455 // restore the original selection
3456 DoSetSelection(startOld, endOld, SetSel_NoScroll);
3457 }
3458
3459 return ok;
3460 }
3461
SetDefaultStyle(const wxTextAttr & style)3462 bool wxTextCtrl::SetDefaultStyle(const wxTextAttr& style)
3463 {
3464 if ( !wxTextCtrlBase::SetDefaultStyle(style) )
3465 return false;
3466
3467 if ( IsEditable() )
3468 {
3469 // we have to do this or the style wouldn't apply for the text typed by
3470 // the user
3471 wxTextPos posLast = GetLastPosition();
3472 SetStyle(posLast, posLast, m_defaultStyle);
3473 }
3474
3475 return true;
3476 }
3477
GetStyle(long position,wxTextAttr & style)3478 bool wxTextCtrl::GetStyle(long position, wxTextAttr& style)
3479 {
3480 if ( !IsRich() )
3481 {
3482 // can't do it with normal text control
3483 return false;
3484 }
3485
3486 // initialize CHARFORMAT struct
3487 #if wxUSE_RICHEDIT2
3488 CHARFORMAT2 cf;
3489 #else
3490 CHARFORMAT cf;
3491 #endif
3492
3493 wxZeroMemory(cf);
3494
3495 // we can't use CHARFORMAT2 with RichEdit 1.0, so pretend it is a simple
3496 // CHARFORMAT in that case
3497 #if wxUSE_RICHEDIT2
3498 if ( m_verRichEdit == 1 )
3499 {
3500 // this is the only thing the control is going to grok
3501 cf.cbSize = sizeof(CHARFORMAT);
3502 }
3503 else
3504 #endif
3505 {
3506 // CHARFORMAT or CHARFORMAT2
3507 cf.cbSize = sizeof(cf);
3508 }
3509 // we can only change the format of the selection, so select the range we
3510 // want and restore the old selection later
3511 long startOld, endOld;
3512 GetSelection(&startOld, &endOld);
3513
3514 // but do we really have to change the selection?
3515 const bool changeSel = position != startOld;
3516
3517 if ( changeSel )
3518 {
3519 DoSetSelection(position, position + 1, SetSel_NoScroll);
3520 }
3521
3522 // get the selection formatting
3523 (void) ::SendMessage(GetHwnd(), EM_GETCHARFORMAT,
3524 SCF_SELECTION, (LPARAM)&cf) ;
3525
3526
3527 LOGFONT lf;
3528 lf.lfWidth = 0;
3529 lf.lfCharSet = ANSI_CHARSET; // FIXME: how to get correct charset?
3530 lf.lfClipPrecision = 0;
3531 lf.lfEscapement = 0;
3532 wxStrcpy(lf.lfFaceName, cf.szFaceName);
3533
3534 //NOTE: we _MUST_ set each of these values to _something_ since we
3535 //do not call wxZeroMemory on the LOGFONT lf
3536 if (cf.dwEffects & CFE_ITALIC)
3537 lf.lfItalic = TRUE;
3538 else
3539 lf.lfItalic = FALSE;
3540
3541 lf.lfOrientation = 0;
3542 lf.lfPitchAndFamily = cf.bPitchAndFamily;
3543 lf.lfQuality = 0;
3544
3545 if (cf.dwEffects & CFE_STRIKEOUT)
3546 lf.lfStrikeOut = TRUE;
3547 else
3548 lf.lfStrikeOut = FALSE;
3549
3550 if (cf.dwEffects & CFE_UNDERLINE)
3551 lf.lfUnderline = TRUE;
3552 else
3553 lf.lfUnderline = FALSE;
3554
3555 if (cf.dwEffects & CFE_BOLD)
3556 lf.lfWeight = FW_BOLD;
3557 else
3558 lf.lfWeight = FW_NORMAL;
3559
3560 // Determine the pointSize that was used in SetStyle. Don't worry about
3561 // lfHeight or PPI, style.SetFont() will lose this information anyway.
3562 wxFont font(wxNativeFontInfo(lf, this));
3563 font.SetFractionalPointSize(cf.yHeight / 20.0); // 1 pt = 20 twips
3564 if (font.IsOk())
3565 {
3566 style.SetFont(font);
3567 }
3568 style.SetTextColour(wxColour(cf.crTextColor));
3569
3570 #if wxUSE_RICHEDIT2
3571 if ( m_verRichEdit != 1 )
3572 {
3573 // Notice that, surprisingly, CFM_BACKCOLOR is still set in the mask
3574 // even when CFE_AUTOBACKCOLOR is set in the effects, indicating that
3575 // the background colour is not used.
3576 if ( !(cf.dwEffects & CFE_AUTOBACKCOLOR) && (cf.dwMask & CFM_BACKCOLOR) )
3577 {
3578 style.SetBackgroundColour(wxColour(cf.crBackColor));
3579 }
3580 }
3581 #endif // wxUSE_RICHEDIT2
3582
3583 wxTextAttrUnderlineType underlineType = wxTEXT_ATTR_UNDERLINE_NONE;
3584 switch ( cf.bUnderlineType )
3585 {
3586 case CFU_UNDERLINE:
3587 underlineType = wxTEXT_ATTR_UNDERLINE_SOLID;
3588 break;
3589 case CFU_UNDERLINEDOUBLE:
3590 underlineType = wxTEXT_ATTR_UNDERLINE_DOUBLE;
3591 break;
3592 case CFU_UNDERLINEWAVE:
3593 underlineType = wxTEXT_ATTR_UNDERLINE_SPECIAL;
3594 break;
3595 default:
3596 underlineType = wxTEXT_ATTR_UNDERLINE_NONE;
3597 break;
3598 }
3599
3600 wxColour underlineColour;
3601 #if _RICHEDIT_VER >= 0x0800
3602 if ( cf.bUnderlineColor < WXSIZEOF(gs_underlineColourMap) )
3603 underlineColour = gs_underlineColourMap[cf.bUnderlineColor];
3604 #endif
3605
3606 if ( underlineType != wxTEXT_ATTR_UNDERLINE_NONE )
3607 style.SetFontUnderlined(underlineType, underlineColour);
3608
3609 // now get the paragraph formatting
3610 PARAFORMAT2 pf;
3611 wxZeroMemory(pf);
3612 // we can't use PARAFORMAT2 with RichEdit 1.0, so pretend it is a simple
3613 // PARAFORMAT in that case
3614 #if wxUSE_RICHEDIT2
3615 if ( m_verRichEdit == 1 )
3616 {
3617 // this is the only thing the control is going to grok
3618 pf.cbSize = sizeof(PARAFORMAT);
3619 }
3620 else
3621 #endif
3622 {
3623 // PARAFORMAT or PARAFORMAT2
3624 pf.cbSize = sizeof(pf);
3625 }
3626
3627 // do format the selection
3628 (void) ::SendMessage(GetHwnd(), EM_GETPARAFORMAT, 0, (LPARAM) &pf) ;
3629
3630 style.SetLeftIndent( (int) ((double) pf.dxStartIndent * twips2mm * 10.0), (int) ((double) pf.dxOffset * twips2mm * 10.0) );
3631 style.SetRightIndent( (int) ((double) pf.dxRightIndent * twips2mm * 10.0) );
3632
3633 if (pf.wAlignment == PFA_CENTER)
3634 style.SetAlignment(wxTEXT_ALIGNMENT_CENTRE);
3635 else if (pf.wAlignment == PFA_RIGHT)
3636 style.SetAlignment(wxTEXT_ALIGNMENT_RIGHT);
3637 else if (pf.wAlignment == PFA_JUSTIFY)
3638 style.SetAlignment(wxTEXT_ALIGNMENT_JUSTIFIED);
3639 else
3640 style.SetAlignment(wxTEXT_ALIGNMENT_LEFT);
3641
3642 wxArrayInt tabStops;
3643 size_t i;
3644 for (i = 0; i < (size_t) pf.cTabCount; i++)
3645 {
3646 tabStops.Add( (int) ((double) (pf.rgxTabs[i] & 0xFFFF) * twips2mm * 10.0) );
3647 }
3648
3649 if ( changeSel )
3650 {
3651 // restore the original selection
3652 DoSetSelection(startOld, endOld, SetSel_NoScroll);
3653 }
3654
3655 return true;
3656 }
3657
3658 // ----------------------------------------------------------------------------
3659 // wxRichEditModule
3660 // ----------------------------------------------------------------------------
3661
3662 static const HINSTANCE INVALID_HINSTANCE = (HINSTANCE)-1;
3663
OnInit()3664 bool wxRichEditModule::OnInit()
3665 {
3666 // don't do anything - we will load it when needed
3667 return true;
3668 }
3669
OnExit()3670 void wxRichEditModule::OnExit()
3671 {
3672 for ( size_t i = 0; i < WXSIZEOF(ms_hRichEdit); i++ )
3673 {
3674 if ( ms_hRichEdit[i] && ms_hRichEdit[i] != INVALID_HINSTANCE )
3675 {
3676 ::FreeLibrary(ms_hRichEdit[i]);
3677 ms_hRichEdit[i] = NULL;
3678 }
3679 }
3680 #if wxUSE_INKEDIT
3681 if (ms_inkEditLib.IsLoaded())
3682 ms_inkEditLib.Unload();
3683 #endif
3684 }
3685
3686 /* static */
Load(Version version)3687 bool wxRichEditModule::Load(Version version)
3688 {
3689 if ( ms_hRichEdit[version] == INVALID_HINSTANCE )
3690 {
3691 // we had already tried to load it and failed
3692 return false;
3693 }
3694
3695 if ( ms_hRichEdit[version] )
3696 {
3697 // we've already got this one
3698 return true;
3699 }
3700
3701 static const wxChar *const dllnames[] =
3702 {
3703 wxT("riched32"),
3704 wxT("riched20"),
3705 wxT("msftedit"),
3706 };
3707
3708 wxCOMPILE_TIME_ASSERT( WXSIZEOF(dllnames) == Version_Max,
3709 RichEditDllNamesVersionsMismatch );
3710
3711 ms_hRichEdit[version] = ::LoadLibrary(dllnames[version]);
3712
3713 if ( !ms_hRichEdit[version] )
3714 {
3715 ms_hRichEdit[version] = INVALID_HINSTANCE;
3716
3717 return false;
3718 }
3719
3720 return true;
3721 }
3722
3723 #if wxUSE_INKEDIT
3724 // load the InkEdit library
LoadInkEdit()3725 bool wxRichEditModule::LoadInkEdit()
3726 {
3727 if (ms_inkEditLibLoadAttemped)
3728 return ms_inkEditLib.IsLoaded();
3729
3730 ms_inkEditLibLoadAttemped = true;
3731
3732 wxLogNull logNull;
3733 return ms_inkEditLib.Load(wxT("inked"));
3734 }
3735 #endif // wxUSE_INKEDIT
3736
3737
3738 #endif // wxUSE_RICHEDIT
3739
3740 #endif // wxUSE_TEXTCTRL
3741