1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk1/textctrl.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Id:          $Id: textctrl.cpp 41739 2006-10-08 17:46:12Z VZ $
6 // Copyright:   (c) 1998 Robert Roebling, Vadim Zeitlin
7 // Licence:     wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9 
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12 
13 #include "wx/textctrl.h"
14 
15 #ifndef WX_PRECOMP
16     #include "wx/intl.h"
17     #include "wx/log.h"
18     #include "wx/utils.h"
19     #include "wx/panel.h"
20     #include "wx/settings.h"
21     #include "wx/math.h"
22 #endif
23 
24 #include "wx/strconv.h"
25 #include "wx/fontutil.h"        // for wxNativeFontInfo (GetNativeFontInfo())
26 
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <ctype.h>
30 
31 #include "wx/gtk1/private.h"
32 #include <gdk/gdkkeysyms.h>
33 
34 //-----------------------------------------------------------------------------
35 // idle system
36 //-----------------------------------------------------------------------------
37 
38 extern void wxapp_install_idle_handler();
39 extern bool g_isIdle;
40 
41 //-----------------------------------------------------------------------------
42 // data
43 //-----------------------------------------------------------------------------
44 
45 extern wxCursor   g_globalCursor;
46 extern wxWindowGTK *g_delayedFocus;
47 
48 // ----------------------------------------------------------------------------
49 // helpers
50 // ----------------------------------------------------------------------------
51 
52 extern "C" {
wxGtkTextInsert(GtkWidget * text,const wxTextAttr & attr,const char * txt,size_t len)53 static void wxGtkTextInsert(GtkWidget *text,
54                             const wxTextAttr& attr,
55                             const char *txt,
56                             size_t len)
57 {
58     GdkFont *font = attr.HasFont() ? attr.GetFont().GetInternalFont()
59                                    : NULL;
60 
61     GdkColor *colFg = attr.HasTextColour() ? attr.GetTextColour().GetColor()
62                                            : NULL;
63 
64     GdkColor *colBg = attr.HasBackgroundColour()
65                         ? attr.GetBackgroundColour().GetColor()
66                         : NULL;
67 
68     gtk_text_insert( GTK_TEXT(text), font, colFg, colBg, txt, len );
69 }
70 }
71 
72 // ----------------------------------------------------------------------------
73 // "insert_text" for GtkEntry
74 // ----------------------------------------------------------------------------
75 
76 extern "C" {
77 static void
gtk_insert_text_callback(GtkEditable * editable,const gchar * new_text,gint new_text_length,gint * position,wxTextCtrl * win)78 gtk_insert_text_callback(GtkEditable *editable,
79                          const gchar *new_text,
80                          gint new_text_length,
81                          gint *position,
82                          wxTextCtrl *win)
83 {
84     if (g_isIdle)
85         wxapp_install_idle_handler();
86 
87     // we should only be called if we have a max len limit at all
88     GtkEntry *entry = GTK_ENTRY (editable);
89 
90     wxCHECK_RET( entry->text_max_length, _T("shouldn't be called") );
91 
92     // check that we don't overflow the max length limit
93     //
94     // FIXME: this doesn't work when we paste a string which is going to be
95     //        truncated
96     if ( entry->text_length == entry->text_max_length )
97     {
98         // we don't need to run the base class version at all
99         gtk_signal_emit_stop_by_name(GTK_OBJECT(editable), "insert_text");
100 
101         // remember that the next changed signal is to be ignored to avoid
102         // generating a dummy wxEVT_COMMAND_TEXT_UPDATED event
103         win->IgnoreNextTextUpdate();
104 
105         // and generate the correct one ourselves
106         wxCommandEvent event(wxEVT_COMMAND_TEXT_MAXLEN, win->GetId());
107         event.SetEventObject(win);
108         event.SetString(win->GetValue());
109         win->GetEventHandler()->ProcessEvent( event );
110     }
111 }
112 }
113 
114 //-----------------------------------------------------------------------------
115 //  "changed"
116 //-----------------------------------------------------------------------------
117 
118 extern "C" {
119 static void
gtk_text_changed_callback(GtkWidget * widget,wxTextCtrl * win)120 gtk_text_changed_callback( GtkWidget *widget, wxTextCtrl *win )
121 {
122     if ( win->IgnoreTextUpdate() )
123         return;
124 
125     if (!win->m_hasVMT) return;
126 
127     if (g_isIdle)
128         wxapp_install_idle_handler();
129 
130     win->SetModified();
131     win->UpdateFontIfNeeded();
132 
133     wxCommandEvent event( wxEVT_COMMAND_TEXT_UPDATED, win->GetId() );
134     event.SetEventObject( win );
135     win->GetEventHandler()->ProcessEvent( event );
136 }
137 }
138 
139 //-----------------------------------------------------------------------------
140 // "changed" from vertical scrollbar
141 //-----------------------------------------------------------------------------
142 
143 extern "C" {
144 static void
gtk_scrollbar_changed_callback(GtkWidget * WXUNUSED (widget),wxTextCtrl * win)145 gtk_scrollbar_changed_callback( GtkWidget *WXUNUSED(widget), wxTextCtrl *win )
146 {
147     if (!win->m_hasVMT) return;
148 
149     if (g_isIdle)
150         wxapp_install_idle_handler();
151 
152     win->CalculateScrollbar();
153 }
154 }
155 
156 // ----------------------------------------------------------------------------
157 // redraw callback for multiline text
158 // ----------------------------------------------------------------------------
159 
160 // redrawing a GtkText from inside a wxYield() call results in crashes (the
161 // text sample shows it in its "Add lines" command which shows wxProgressDialog
162 // which implicitly calls wxYield()) so we override GtkText::draw() and simply
163 // don't do anything if we're inside wxYield()
164 
165 extern bool wxIsInsideYield;
166 
167 extern "C" {
168     typedef void (*GtkDrawCallback)(GtkWidget *widget, GdkRectangle *rect);
169 }
170 
171 static GtkDrawCallback gs_gtk_text_draw = NULL;
172 
173 extern "C" {
wxgtk_text_draw(GtkWidget * widget,GdkRectangle * rect)174 static void wxgtk_text_draw( GtkWidget *widget, GdkRectangle *rect)
175 {
176     if ( !wxIsInsideYield )
177     {
178         wxCHECK_RET( gs_gtk_text_draw != wxgtk_text_draw,
179                      _T("infinite recursion in wxgtk_text_draw aborted") );
180 
181         gs_gtk_text_draw(widget, rect);
182     }
183 }
184 }
185 
186 //-----------------------------------------------------------------------------
187 //  wxTextCtrl
188 //-----------------------------------------------------------------------------
189 
IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl,wxTextCtrlBase)190 IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl, wxTextCtrlBase)
191 
192 BEGIN_EVENT_TABLE(wxTextCtrl, wxTextCtrlBase)
193     EVT_CHAR(wxTextCtrl::OnChar)
194 
195     EVT_MENU(wxID_CUT, wxTextCtrl::OnCut)
196     EVT_MENU(wxID_COPY, wxTextCtrl::OnCopy)
197     EVT_MENU(wxID_PASTE, wxTextCtrl::OnPaste)
198     EVT_MENU(wxID_UNDO, wxTextCtrl::OnUndo)
199     EVT_MENU(wxID_REDO, wxTextCtrl::OnRedo)
200 
201     EVT_UPDATE_UI(wxID_CUT, wxTextCtrl::OnUpdateCut)
202     EVT_UPDATE_UI(wxID_COPY, wxTextCtrl::OnUpdateCopy)
203     EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste)
204     EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo)
205     EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo)
206 END_EVENT_TABLE()
207 
208 void wxTextCtrl::Init()
209 {
210     m_ignoreNextUpdate =
211     m_modified = false;
212     SetUpdateFont(false);
213     m_text =
214     m_vScrollbar = (GtkWidget *)NULL;
215 }
216 
~wxTextCtrl()217 wxTextCtrl::~wxTextCtrl()
218 {
219 }
220 
wxTextCtrl(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)221 wxTextCtrl::wxTextCtrl( wxWindow *parent,
222                         wxWindowID id,
223                         const wxString &value,
224                         const wxPoint &pos,
225                         const wxSize &size,
226                         long style,
227                         const wxValidator& validator,
228                         const wxString &name )
229 {
230     Init();
231 
232     Create( parent, id, value, pos, size, style, validator, name );
233 }
234 
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)235 bool wxTextCtrl::Create( wxWindow *parent,
236                          wxWindowID id,
237                          const wxString &value,
238                          const wxPoint &pos,
239                          const wxSize &size,
240                          long style,
241                          const wxValidator& validator,
242                          const wxString &name )
243 {
244     m_needParent = true;
245     m_acceptsFocus = true;
246 
247     if (!PreCreation( parent, pos, size ) ||
248         !CreateBase( parent, id, pos, size, style, validator, name ))
249     {
250         wxFAIL_MSG( wxT("wxTextCtrl creation failed") );
251         return false;
252     }
253 
254 
255     m_vScrollbarVisible = false;
256 
257     bool multi_line = (style & wxTE_MULTILINE) != 0;
258 
259     if (multi_line)
260     {
261         // create our control ...
262         m_text = gtk_text_new( (GtkAdjustment *) NULL, (GtkAdjustment *) NULL );
263 
264         // ... and put into the upper left hand corner of the table
265         bool bHasHScrollbar = false;
266         m_widget = gtk_table_new(bHasHScrollbar ? 2 : 1, 2, FALSE);
267         GTK_WIDGET_UNSET_FLAGS( m_widget, GTK_CAN_FOCUS );
268         gtk_table_attach( GTK_TABLE(m_widget), m_text, 0, 1, 0, 1,
269                       (GtkAttachOptions)(GTK_FILL | GTK_EXPAND | GTK_SHRINK),
270                       (GtkAttachOptions)(GTK_FILL | GTK_EXPAND | GTK_SHRINK),
271                        0, 0);
272 
273         // always wrap words
274         gtk_text_set_word_wrap( GTK_TEXT(m_text), TRUE );
275 
276         // finally, put the vertical scrollbar in the upper right corner
277         m_vScrollbar = gtk_vscrollbar_new( GTK_TEXT(m_text)->vadj );
278         GTK_WIDGET_UNSET_FLAGS( m_vScrollbar, GTK_CAN_FOCUS );
279         gtk_table_attach(GTK_TABLE(m_widget), m_vScrollbar, 1, 2, 0, 1,
280                      GTK_FILL,
281                      (GtkAttachOptions)(GTK_EXPAND | GTK_FILL | GTK_SHRINK),
282                      0, 0);
283     }
284     else
285     {
286         // a single-line text control: no need for scrollbars
287         m_widget =
288         m_text = gtk_entry_new();
289     }
290 
291     m_parent->DoAddChild( this );
292 
293     m_focusWidget = m_text;
294 
295     PostCreation(size);
296 
297     if (multi_line)
298         gtk_widget_show(m_text);
299 
300     if (multi_line)
301     {
302         gtk_signal_connect(GTK_OBJECT(GTK_TEXT(m_text)->vadj), "changed",
303           (GtkSignalFunc) gtk_scrollbar_changed_callback, (gpointer) this );
304 
305         // only initialize gs_gtk_text_draw once, starting from the next the
306         // klass::draw will already be wxgtk_text_draw
307         if ( !gs_gtk_text_draw )
308         {
309             GtkDrawCallback&
310                 draw = GTK_WIDGET_CLASS(GTK_OBJECT(m_text)->klass)->draw;
311 
312             gs_gtk_text_draw = draw;
313 
314             draw = wxgtk_text_draw;
315         }
316     }
317 
318     if (!value.empty())
319     {
320 #if !GTK_CHECK_VERSION(1, 2, 0)
321         // if we don't realize it, GTK 1.0.6 dies with a SIGSEGV in
322         // gtk_editable_insert_text()
323         gtk_widget_realize(m_text);
324 #endif // GTK 1.0
325 
326         gint tmp = 0;
327 #if wxUSE_UNICODE
328         wxWX2MBbuf val = value.mbc_str();
329         gtk_editable_insert_text( GTK_EDITABLE(m_text), val, strlen(val), &tmp );
330 #else
331         gtk_editable_insert_text( GTK_EDITABLE(m_text), value, value.length(), &tmp );
332 #endif
333 
334         if (multi_line)
335         {
336             // Bring editable's cursor uptodate. Bug in GTK.
337             SET_EDITABLE_POS(m_text, gtk_text_get_point( GTK_TEXT(m_text) ));
338         }
339     }
340 
341     if (style & wxTE_PASSWORD)
342     {
343         if (!multi_line)
344             gtk_entry_set_visibility( GTK_ENTRY(m_text), FALSE );
345     }
346 
347     if (style & wxTE_READONLY)
348     {
349         if (!multi_line)
350             gtk_entry_set_editable( GTK_ENTRY(m_text), FALSE );
351     }
352     else
353     {
354         if (multi_line)
355             gtk_text_set_editable( GTK_TEXT(m_text), 1 );
356     }
357 
358     // We want to be notified about text changes.
359     gtk_signal_connect( GTK_OBJECT(m_text), "changed",
360         GTK_SIGNAL_FUNC(gtk_text_changed_callback), (gpointer)this);
361 
362     m_cursor = wxCursor( wxCURSOR_IBEAM );
363 
364     wxTextAttr attrDef(GetForegroundColour(), GetBackgroundColour(), GetFont());
365     SetDefaultStyle( attrDef );
366 
367     return true;
368 }
369 
370 
CalculateScrollbar()371 void wxTextCtrl::CalculateScrollbar()
372 {
373     if ((m_windowStyle & wxTE_MULTILINE) == 0) return;
374 
375     GtkAdjustment *adj = GTK_TEXT(m_text)->vadj;
376 
377     if (adj->upper - adj->page_size < 0.8)
378     {
379         if (m_vScrollbarVisible)
380         {
381             gtk_widget_hide( m_vScrollbar );
382             m_vScrollbarVisible = false;
383         }
384     }
385     else
386     {
387         if (!m_vScrollbarVisible)
388         {
389             gtk_widget_show( m_vScrollbar );
390             m_vScrollbarVisible = true;
391         }
392     }
393 }
394 
GetValue() const395 wxString wxTextCtrl::GetValue() const
396 {
397     wxCHECK_MSG( m_text != NULL, wxEmptyString, wxT("invalid text ctrl") );
398 
399     wxString tmp;
400     if (m_windowStyle & wxTE_MULTILINE)
401     {
402         gint len = gtk_text_get_length( GTK_TEXT(m_text) );
403         char *text = gtk_editable_get_chars( GTK_EDITABLE(m_text), 0, len );
404         tmp = text;
405         g_free( text );
406     }
407     else
408     {
409         tmp = wxGTK_CONV_BACK( gtk_entry_get_text( GTK_ENTRY(m_text) ) );
410     }
411 
412     return tmp;
413 }
414 
DoSetValue(const wxString & value,int flags)415 void wxTextCtrl::DoSetValue( const wxString &value, int flags )
416 {
417     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
418 
419     if ( !(flags & SetValue_SendEvent) )
420     {
421         // do not generate events
422         IgnoreNextTextUpdate();
423     }
424 
425     if (m_windowStyle & wxTE_MULTILINE)
426     {
427         gint len = gtk_text_get_length( GTK_TEXT(m_text) );
428         gtk_editable_delete_text( GTK_EDITABLE(m_text), 0, len );
429         len = 0;
430         gtk_editable_insert_text( GTK_EDITABLE(m_text), value.mbc_str(), value.length(), &len );
431     }
432     else
433     {
434         gtk_entry_set_text( GTK_ENTRY(m_text), wxGTK_CONV( value ) );
435     }
436 
437     // GRG, Jun/2000: Changed this after a lot of discussion in
438     //   the lists. wxWidgets 2.2 will have a set of flags to
439     //   customize this behaviour.
440     SetInsertionPoint(0);
441 
442     m_modified = false;
443 }
444 
WriteText(const wxString & text)445 void wxTextCtrl::WriteText( const wxString &text )
446 {
447     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
448 
449     if ( text.empty() )
450         return;
451 
452     // gtk_text_changed_callback() will set m_modified to true but m_modified
453     // shouldn't be changed by the program writing to the text control itself,
454     // so save the old value and restore when we're done
455     bool oldModified = m_modified;
456 
457     if ( m_windowStyle & wxTE_MULTILINE )
458     {
459         // After cursor movements, gtk_text_get_point() is wrong by one.
460         gtk_text_set_point( GTK_TEXT(m_text), GET_EDITABLE_POS(m_text) );
461 
462         // always use m_defaultStyle, even if it is empty as otherwise
463         // resetting the style and appending some more text wouldn't work: if
464         // we don't specify the style explicitly, the old style would be used
465         gtk_editable_delete_selection( GTK_EDITABLE(m_text) );
466         wxGtkTextInsert(m_text, m_defaultStyle, text.c_str(), text.length());
467 
468         // we called wxGtkTextInsert with correct font, no need to do anything
469         // in UpdateFontIfNeeded() any longer
470         if ( !text.empty() )
471         {
472             SetUpdateFont(false);
473         }
474 
475         // Bring editable's cursor back uptodate.
476         SET_EDITABLE_POS(m_text, gtk_text_get_point( GTK_TEXT(m_text) ));
477     }
478     else // single line
479     {
480         // First remove the selection if there is one
481         gtk_editable_delete_selection( GTK_EDITABLE(m_text) );
482 
483         // This moves the cursor pos to behind the inserted text.
484         gint len = GET_EDITABLE_POS(m_text);
485 
486         gtk_editable_insert_text( GTK_EDITABLE(m_text), text.c_str(), text.length(), &len );
487 
488         // Bring entry's cursor uptodate.
489         gtk_entry_set_position( GTK_ENTRY(m_text), len );
490     }
491 
492     m_modified = oldModified;
493 }
494 
AppendText(const wxString & text)495 void wxTextCtrl::AppendText( const wxString &text )
496 {
497     SetInsertionPointEnd();
498     WriteText( text );
499 }
500 
GetLineText(long lineNo) const501 wxString wxTextCtrl::GetLineText( long lineNo ) const
502 {
503     if (m_windowStyle & wxTE_MULTILINE)
504     {
505         gint len = gtk_text_get_length( GTK_TEXT(m_text) );
506         char *text = gtk_editable_get_chars( GTK_EDITABLE(m_text), 0, len );
507 
508         if (text)
509         {
510             wxString buf;
511             long i;
512             int currentLine = 0;
513             for (i = 0; currentLine != lineNo && text[i]; i++ )
514                 if (text[i] == '\n')
515             currentLine++;
516             // Now get the text
517             int j;
518             for (j = 0; text[i] && text[i] != '\n'; i++, j++ )
519                 buf += text[i];
520 
521             g_free( text );
522             return buf;
523         }
524         else
525         {
526             return wxEmptyString;
527         }
528     }
529     else
530     {
531         if (lineNo == 0) return GetValue();
532         return wxEmptyString;
533     }
534 }
535 
OnDropFiles(wxDropFilesEvent & WXUNUSED (event))536 void wxTextCtrl::OnDropFiles( wxDropFilesEvent &WXUNUSED(event) )
537 {
538   /* If you implement this, don't forget to update the documentation!
539    * (file docs/latex/wx/text.tex) */
540     wxFAIL_MSG( wxT("wxTextCtrl::OnDropFiles not implemented") );
541 }
542 
PositionToXY(long pos,long * x,long * y) const543 bool wxTextCtrl::PositionToXY(long pos, long *x, long *y ) const
544 {
545     if ( m_windowStyle & wxTE_MULTILINE )
546     {
547         wxString text = GetValue();
548 
549         // cast to prevent warning. But pos really should've been unsigned.
550         if( (unsigned long)pos > text.length()  )
551             return false;
552 
553         *x=0;   // First Col
554         *y=0;   // First Line
555 
556         const wxChar* stop = text.c_str() + pos;
557         for ( const wxChar *p = text.c_str(); p < stop; p++ )
558         {
559             if (*p == wxT('\n'))
560             {
561                 (*y)++;
562                 *x=0;
563             }
564             else
565                 (*x)++;
566         }
567     }
568     else // single line control
569     {
570         if ( pos <= GTK_ENTRY(m_text)->text_length )
571         {
572             *y = 0;
573             *x = pos;
574         }
575         else
576         {
577             // index out of bounds
578             return false;
579         }
580     }
581 
582     return true;
583 }
584 
XYToPosition(long x,long y) const585 long wxTextCtrl::XYToPosition(long x, long y ) const
586 {
587     if (!(m_windowStyle & wxTE_MULTILINE)) return 0;
588 
589     long pos=0;
590     for( int i=0; i<y; i++ ) pos += GetLineLength(i) + 1; // one for '\n'
591 
592     pos += x;
593     return pos;
594 }
595 
GetLineLength(long lineNo) const596 int wxTextCtrl::GetLineLength(long lineNo) const
597 {
598     wxString str = GetLineText (lineNo);
599     return (int) str.length();
600 }
601 
GetNumberOfLines() const602 int wxTextCtrl::GetNumberOfLines() const
603 {
604     if (m_windowStyle & wxTE_MULTILINE)
605     {
606         gint len = gtk_text_get_length( GTK_TEXT(m_text) );
607         char *text = gtk_editable_get_chars( GTK_EDITABLE(m_text), 0, len );
608 
609         if (text)
610         {
611             int currentLine = 0;
612             for (int i = 0; i < len; i++ )
613             {
614                 if (text[i] == '\n')
615                     currentLine++;
616             }
617             g_free( text );
618 
619             // currentLine is 0 based, add 1 to get number of lines
620             return currentLine + 1;
621         }
622         else
623         {
624             return 0;
625         }
626     }
627     else
628     {
629         return 1;
630     }
631 }
632 
SetInsertionPoint(long pos)633 void wxTextCtrl::SetInsertionPoint( long pos )
634 {
635     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
636 
637     if ( IsMultiLine() )
638     {
639         gtk_signal_disconnect_by_func( GTK_OBJECT(m_text),
640           GTK_SIGNAL_FUNC(gtk_text_changed_callback), (gpointer)this);
641 
642         /* we fake a set_point by inserting and deleting. as the user
643            isn't supposed to get to know about this non-sense, we
644            disconnect so that no events are sent to the user program. */
645 
646         gint tmp = (gint)pos;
647         gtk_editable_insert_text( GTK_EDITABLE(m_text), " ", 1, &tmp );
648         gtk_editable_delete_text( GTK_EDITABLE(m_text), tmp-1, tmp );
649 
650         gtk_signal_connect( GTK_OBJECT(m_text), "changed",
651           GTK_SIGNAL_FUNC(gtk_text_changed_callback), (gpointer)this);
652 
653         // bring editable's cursor uptodate. Bug in GTK.
654         SET_EDITABLE_POS(m_text, gtk_text_get_point( GTK_TEXT(m_text) ));
655     }
656     else
657     {
658         gtk_entry_set_position( GTK_ENTRY(m_text), (int)pos );
659 
660         // Bring editable's cursor uptodate. Bug in GTK.
661         SET_EDITABLE_POS(m_text, (guint32)pos);
662     }
663 }
664 
SetInsertionPointEnd()665 void wxTextCtrl::SetInsertionPointEnd()
666 {
667     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
668 
669     if (m_windowStyle & wxTE_MULTILINE)
670     {
671         SetInsertionPoint(gtk_text_get_length(GTK_TEXT(m_text)));
672     }
673     else
674     {
675         gtk_entry_set_position( GTK_ENTRY(m_text), -1 );
676     }
677 }
678 
SetEditable(bool editable)679 void wxTextCtrl::SetEditable( bool editable )
680 {
681     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
682 
683     if (m_windowStyle & wxTE_MULTILINE)
684     {
685         gtk_text_set_editable( GTK_TEXT(m_text), editable );
686     }
687     else
688     {
689         gtk_entry_set_editable( GTK_ENTRY(m_text), editable );
690     }
691 }
692 
Enable(bool enable)693 bool wxTextCtrl::Enable( bool enable )
694 {
695     if (!wxWindowBase::Enable(enable))
696     {
697         // nothing to do
698         return false;
699     }
700 
701     if (m_windowStyle & wxTE_MULTILINE)
702     {
703         gtk_text_set_editable( GTK_TEXT(m_text), enable );
704         OnParentEnable(enable);
705     }
706     else
707     {
708         gtk_widget_set_sensitive( m_text, enable );
709     }
710 
711     return true;
712 }
713 
714 // wxGTK-specific: called recursively by Enable,
715 // to give widgets an oppprtunity to correct their colours after they
716 // have been changed by Enable
OnParentEnable(bool enable)717 void wxTextCtrl::OnParentEnable( bool enable )
718 {
719     // If we have a custom background colour, we use this colour in both
720     // disabled and enabled mode, or we end up with a different colour under the
721     // text.
722     wxColour oldColour = GetBackgroundColour();
723     if (oldColour.Ok())
724     {
725         // Need to set twice or it'll optimize the useful stuff out
726         if (oldColour == * wxWHITE)
727             SetBackgroundColour(*wxBLACK);
728         else
729             SetBackgroundColour(*wxWHITE);
730         SetBackgroundColour(oldColour);
731     }
732 }
733 
MarkDirty()734 void wxTextCtrl::MarkDirty()
735 {
736     m_modified = true;
737 }
738 
DiscardEdits()739 void wxTextCtrl::DiscardEdits()
740 {
741     m_modified = false;
742 }
743 
744 // ----------------------------------------------------------------------------
745 // max text length support
746 // ----------------------------------------------------------------------------
747 
IgnoreNextTextUpdate()748 void wxTextCtrl::IgnoreNextTextUpdate()
749 {
750     m_ignoreNextUpdate = true;
751 }
752 
IgnoreTextUpdate()753 bool wxTextCtrl::IgnoreTextUpdate()
754 {
755     if ( m_ignoreNextUpdate )
756     {
757         m_ignoreNextUpdate = false;
758 
759         return true;
760     }
761 
762     return false;
763 }
764 
SetMaxLength(unsigned long len)765 void wxTextCtrl::SetMaxLength(unsigned long len)
766 {
767     if ( !HasFlag(wxTE_MULTILINE) )
768     {
769         gtk_entry_set_max_length(GTK_ENTRY(m_text), len);
770 
771         // there is a bug in GTK+ 1.2.x: "changed" signal is emitted even if
772         // we had tried to enter more text than allowed by max text length and
773         // the text wasn't really changed
774         //
775         // to detect this and generate TEXT_MAXLEN event instead of
776         // TEXT_CHANGED one in this case we also catch "insert_text" signal
777         //
778         // when max len is set to 0 we disconnect our handler as it means that
779         // we shouldn't check anything any more
780         if ( len )
781         {
782             gtk_signal_connect( GTK_OBJECT(m_text),
783                                 "insert_text",
784                                 GTK_SIGNAL_FUNC(gtk_insert_text_callback),
785                                 (gpointer)this);
786         }
787         else // no checking
788         {
789             gtk_signal_disconnect_by_func
790             (
791                 GTK_OBJECT(m_text),
792                 GTK_SIGNAL_FUNC(gtk_insert_text_callback),
793                 (gpointer)this
794             );
795         }
796     }
797 }
798 
SetSelection(long from,long to)799 void wxTextCtrl::SetSelection( long from, long to )
800 {
801     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
802 
803     if (from == -1 && to == -1)
804     {
805         from = 0;
806         to = GetValue().length();
807     }
808 
809     if ( (m_windowStyle & wxTE_MULTILINE) &&
810          !GTK_TEXT(m_text)->line_start_cache )
811     {
812         // tell the programmer that it didn't work
813         wxLogDebug(_T("Can't call SetSelection() before realizing the control"));
814         return;
815     }
816 
817     if (m_windowStyle & wxTE_MULTILINE)
818     {
819         gtk_editable_select_region( GTK_EDITABLE(m_text), (gint)from, (gint)to );
820     }
821     else
822     {
823         gtk_editable_select_region( GTK_EDITABLE(m_text), (gint)from, (gint)to );
824     }
825 }
826 
ShowPosition(long pos)827 void wxTextCtrl::ShowPosition( long pos )
828 {
829     if (m_windowStyle & wxTE_MULTILINE)
830     {
831         GtkAdjustment *vp = GTK_TEXT(m_text)->vadj;
832         float totalLines =  (float) GetNumberOfLines();
833         long posX;
834         long posY;
835         PositionToXY(pos, &posX, &posY);
836         float posLine = (float) posY;
837         float p = (posLine/totalLines)*(vp->upper - vp->lower) + vp->lower;
838         gtk_adjustment_set_value(GTK_TEXT(m_text)->vadj, p);
839     }
840 }
841 
GetInsertionPoint() const842 long wxTextCtrl::GetInsertionPoint() const
843 {
844     wxCHECK_MSG( m_text != NULL, 0, wxT("invalid text ctrl") );
845     return (long) GET_EDITABLE_POS(m_text);
846 }
847 
GetLastPosition() const848 wxTextPos wxTextCtrl::GetLastPosition() const
849 {
850     wxCHECK_MSG( m_text != NULL, 0, wxT("invalid text ctrl") );
851 
852     int pos = 0;
853 
854     if (m_windowStyle & wxTE_MULTILINE)
855     {
856         pos = gtk_text_get_length( GTK_TEXT(m_text) );
857     }
858     else
859     {
860         pos = GTK_ENTRY(m_text)->text_length;
861     }
862 
863     return (long)pos;
864 }
865 
Remove(long from,long to)866 void wxTextCtrl::Remove( long from, long to )
867 {
868     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
869     gtk_editable_delete_text( GTK_EDITABLE(m_text), (gint)from, (gint)to );
870 }
871 
Replace(long from,long to,const wxString & value)872 void wxTextCtrl::Replace( long from, long to, const wxString &value )
873 {
874     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
875 
876     Remove( from, to );
877 
878     if (!value.empty())
879     {
880         gint pos = (gint)from;
881 #if wxUSE_UNICODE
882         wxWX2MBbuf buf = value.mbc_str();
883         gtk_editable_insert_text( GTK_EDITABLE(m_text), buf, strlen(buf), &pos );
884 #else
885         gtk_editable_insert_text( GTK_EDITABLE(m_text), value, value.length(), &pos );
886 #endif // wxUSE_UNICODE
887     }
888 }
889 
Cut()890 void wxTextCtrl::Cut()
891 {
892     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
893     gtk_editable_cut_clipboard(GTK_EDITABLE(m_text) DUMMY_CLIPBOARD_ARG);
894 }
895 
Copy()896 void wxTextCtrl::Copy()
897 {
898     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
899     gtk_editable_copy_clipboard(GTK_EDITABLE(m_text) DUMMY_CLIPBOARD_ARG);
900 }
901 
Paste()902 void wxTextCtrl::Paste()
903 {
904     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
905     gtk_editable_paste_clipboard(GTK_EDITABLE(m_text) DUMMY_CLIPBOARD_ARG);
906 }
907 
908 // Undo/redo
Undo()909 void wxTextCtrl::Undo()
910 {
911     // TODO
912     wxFAIL_MSG( wxT("wxTextCtrl::Undo not implemented") );
913 }
914 
Redo()915 void wxTextCtrl::Redo()
916 {
917     // TODO
918     wxFAIL_MSG( wxT("wxTextCtrl::Redo not implemented") );
919 }
920 
CanUndo() const921 bool wxTextCtrl::CanUndo() const
922 {
923     // TODO
924     //wxFAIL_MSG( wxT("wxTextCtrl::CanUndo not implemented") );
925     return false;
926 }
927 
CanRedo() const928 bool wxTextCtrl::CanRedo() const
929 {
930     // TODO
931     //wxFAIL_MSG( wxT("wxTextCtrl::CanRedo not implemented") );
932     return false;
933 }
934 
935 // If the return values from and to are the same, there is no
936 // selection.
GetSelection(long * fromOut,long * toOut) const937 void wxTextCtrl::GetSelection(long* fromOut, long* toOut) const
938 {
939     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
940 
941     gint from = -1;
942     gint to = -1;
943     bool haveSelection = false;
944 
945      if ( (GTK_EDITABLE(m_text)->has_selection) )
946      {
947          haveSelection = true;
948          from = (long) GTK_EDITABLE(m_text)->selection_start_pos;
949          to = (long) GTK_EDITABLE(m_text)->selection_end_pos;
950      }
951 
952      if (! haveSelection )
953           from = to = GetInsertionPoint();
954 
955      if ( from > to )
956      {
957          // exchange them to be compatible with wxMSW
958          gint tmp = from;
959          from = to;
960          to = tmp;
961      }
962 
963     if ( fromOut )
964         *fromOut = from;
965     if ( toOut )
966         *toOut = to;
967 }
968 
969 
IsEditable() const970 bool wxTextCtrl::IsEditable() const
971 {
972     wxCHECK_MSG( m_text != NULL, false, wxT("invalid text ctrl") );
973     return GTK_EDITABLE(m_text)->editable;
974 }
975 
IsModified() const976 bool wxTextCtrl::IsModified() const
977 {
978     return m_modified;
979 }
980 
Clear()981 void wxTextCtrl::Clear()
982 {
983     SetValue( wxEmptyString );
984 }
985 
OnChar(wxKeyEvent & key_event)986 void wxTextCtrl::OnChar( wxKeyEvent &key_event )
987 {
988     wxCHECK_RET( m_text != NULL, wxT("invalid text ctrl") );
989 
990     if ((key_event.GetKeyCode() == WXK_RETURN) && (m_windowStyle & wxTE_PROCESS_ENTER))
991     {
992         wxCommandEvent event(wxEVT_COMMAND_TEXT_ENTER, m_windowId);
993         event.SetEventObject(this);
994         event.SetString(GetValue());
995         if (GetEventHandler()->ProcessEvent(event)) return;
996     }
997 
998     if ((key_event.GetKeyCode() == WXK_RETURN) && !(m_windowStyle & wxTE_MULTILINE))
999     {
1000         // This will invoke the dialog default action, such
1001         // as the clicking the default button.
1002 
1003         wxWindow *top_frame = m_parent;
1004         while (top_frame->GetParent() && !(top_frame->IsTopLevel()))
1005             top_frame = top_frame->GetParent();
1006 
1007         if (top_frame && GTK_IS_WINDOW(top_frame->m_widget))
1008         {
1009             GtkWindow *window = GTK_WINDOW(top_frame->m_widget);
1010 
1011             if (window->default_widget)
1012             {
1013                 gtk_widget_activate (window->default_widget);
1014                 return;
1015             }
1016         }
1017     }
1018 
1019     key_event.Skip();
1020 }
1021 
GetConnectWidget()1022 GtkWidget* wxTextCtrl::GetConnectWidget()
1023 {
1024     return GTK_WIDGET(m_text);
1025 }
1026 
IsOwnGtkWindow(GdkWindow * window)1027 bool wxTextCtrl::IsOwnGtkWindow( GdkWindow *window )
1028 {
1029     if (m_windowStyle & wxTE_MULTILINE)
1030     {
1031         return (window == GTK_TEXT(m_text)->text_area);
1032     }
1033     else
1034     {
1035         return (window == GTK_ENTRY(m_text)->text_area);
1036     }
1037 }
1038 
1039 // the font will change for subsequent text insertiongs
SetFont(const wxFont & font)1040 bool wxTextCtrl::SetFont( const wxFont &font )
1041 {
1042     wxCHECK_MSG( m_text != NULL, false, wxT("invalid text ctrl") );
1043 
1044     if ( !wxTextCtrlBase::SetFont(font) )
1045     {
1046         // font didn't change, nothing to do
1047         return false;
1048     }
1049 
1050     if ( m_windowStyle & wxTE_MULTILINE )
1051     {
1052         SetUpdateFont(true);
1053 
1054         m_defaultStyle.SetFont(font);
1055 
1056         ChangeFontGlobally();
1057     }
1058 
1059     return true;
1060 }
1061 
ChangeFontGlobally()1062 void wxTextCtrl::ChangeFontGlobally()
1063 {
1064     // this method is very inefficient and hence should be called as rarely as
1065     // possible!
1066     wxASSERT_MSG( (m_windowStyle & wxTE_MULTILINE) && m_updateFont,
1067 
1068                   _T("shouldn't be called for single line controls") );
1069 
1070     wxString value = GetValue();
1071     if ( !value.empty() )
1072     {
1073         SetUpdateFont(false);
1074 
1075         Clear();
1076         AppendText(value);
1077     }
1078 }
1079 
UpdateFontIfNeeded()1080 void wxTextCtrl::UpdateFontIfNeeded()
1081 {
1082     if ( m_updateFont )
1083         ChangeFontGlobally();
1084 }
1085 
SetForegroundColour(const wxColour & colour)1086 bool wxTextCtrl::SetForegroundColour(const wxColour& colour)
1087 {
1088     if ( !wxControl::SetForegroundColour(colour) )
1089         return false;
1090 
1091     // update default fg colour too
1092     m_defaultStyle.SetTextColour(colour);
1093 
1094     return true;
1095 }
1096 
SetBackgroundColour(const wxColour & colour)1097 bool wxTextCtrl::SetBackgroundColour( const wxColour &colour )
1098 {
1099     wxCHECK_MSG( m_text != NULL, false, wxT("invalid text ctrl") );
1100 
1101     if ( !wxControl::SetBackgroundColour( colour ) )
1102         return false;
1103 
1104     if (!m_widget->window)
1105         return false;
1106 
1107     if (!m_backgroundColour.Ok())
1108         return false;
1109 
1110     if (m_windowStyle & wxTE_MULTILINE)
1111     {
1112         GdkWindow *window = GTK_TEXT(m_text)->text_area;
1113         if (!window)
1114             return false;
1115         m_backgroundColour.CalcPixel( gdk_window_get_colormap( window ) );
1116         gdk_window_set_background( window, m_backgroundColour.GetColor() );
1117         gdk_window_clear( window );
1118     }
1119 
1120     // change active background color too
1121     m_defaultStyle.SetBackgroundColour( colour );
1122 
1123     return true;
1124 }
1125 
SetStyle(long start,long end,const wxTextAttr & style)1126 bool wxTextCtrl::SetStyle( long start, long end, const wxTextAttr& style )
1127 {
1128     if ( m_windowStyle & wxTE_MULTILINE )
1129     {
1130         if ( style.IsDefault() )
1131         {
1132             // nothing to do
1133             return true;
1134         }
1135 
1136         // VERY dirty way to do that - removes the required text and re-adds it
1137         // with styling (FIXME)
1138 
1139         gint l = gtk_text_get_length( GTK_TEXT(m_text) );
1140 
1141         wxCHECK_MSG( start >= 0 && end <= l, false,
1142                      _T("invalid range in wxTextCtrl::SetStyle") );
1143 
1144         gint old_pos = gtk_editable_get_position( GTK_EDITABLE(m_text) );
1145         char *text = gtk_editable_get_chars( GTK_EDITABLE(m_text), start, end );
1146         wxString tmp(text,*wxConvCurrent);
1147         g_free( text );
1148 
1149         gtk_editable_delete_text( GTK_EDITABLE(m_text), start, end );
1150         gtk_editable_set_position( GTK_EDITABLE(m_text), start );
1151 
1152     #if wxUSE_UNICODE
1153         wxWX2MBbuf buf = tmp.mbc_str();
1154         const char *txt = buf;
1155         size_t txtlen = strlen(buf);
1156     #else
1157         const char *txt = tmp;
1158         size_t txtlen = tmp.length();
1159     #endif
1160 
1161         // use the attributes from style which are set in it and fall back
1162         // first to the default style and then to the text control default
1163         // colours for the others
1164         wxGtkTextInsert(m_text,
1165                         wxTextAttr::Combine(style, m_defaultStyle, this),
1166                         txt,
1167                         txtlen);
1168 
1169         /* does not seem to help under GTK+ 1.2 !!!
1170         gtk_editable_set_position( GTK_EDITABLE(m_text), old_pos ); */
1171         SetInsertionPoint( old_pos );
1172 
1173         return true;
1174     }
1175 
1176     // else single line
1177     // cannot do this for GTK+'s Entry widget
1178     return false;
1179 }
1180 
DoApplyWidgetStyle(GtkRcStyle * style)1181 void wxTextCtrl::DoApplyWidgetStyle(GtkRcStyle *style)
1182 {
1183     gtk_widget_modify_style(m_text, style);
1184 }
1185 
OnCut(wxCommandEvent & WXUNUSED (event))1186 void wxTextCtrl::OnCut(wxCommandEvent& WXUNUSED(event))
1187 {
1188     Cut();
1189 }
1190 
OnCopy(wxCommandEvent & WXUNUSED (event))1191 void wxTextCtrl::OnCopy(wxCommandEvent& WXUNUSED(event))
1192 {
1193     Copy();
1194 }
1195 
OnPaste(wxCommandEvent & WXUNUSED (event))1196 void wxTextCtrl::OnPaste(wxCommandEvent& WXUNUSED(event))
1197 {
1198     Paste();
1199 }
1200 
OnUndo(wxCommandEvent & WXUNUSED (event))1201 void wxTextCtrl::OnUndo(wxCommandEvent& WXUNUSED(event))
1202 {
1203     Undo();
1204 }
1205 
OnRedo(wxCommandEvent & WXUNUSED (event))1206 void wxTextCtrl::OnRedo(wxCommandEvent& WXUNUSED(event))
1207 {
1208     Redo();
1209 }
1210 
OnUpdateCut(wxUpdateUIEvent & event)1211 void wxTextCtrl::OnUpdateCut(wxUpdateUIEvent& event)
1212 {
1213     event.Enable( CanCut() );
1214 }
1215 
OnUpdateCopy(wxUpdateUIEvent & event)1216 void wxTextCtrl::OnUpdateCopy(wxUpdateUIEvent& event)
1217 {
1218     event.Enable( CanCopy() );
1219 }
1220 
OnUpdatePaste(wxUpdateUIEvent & event)1221 void wxTextCtrl::OnUpdatePaste(wxUpdateUIEvent& event)
1222 {
1223     event.Enable( CanPaste() );
1224 }
1225 
OnUpdateUndo(wxUpdateUIEvent & event)1226 void wxTextCtrl::OnUpdateUndo(wxUpdateUIEvent& event)
1227 {
1228     event.Enable( CanUndo() );
1229 }
1230 
OnUpdateRedo(wxUpdateUIEvent & event)1231 void wxTextCtrl::OnUpdateRedo(wxUpdateUIEvent& event)
1232 {
1233     event.Enable( CanRedo() );
1234 }
1235 
OnInternalIdle()1236 void wxTextCtrl::OnInternalIdle()
1237 {
1238     wxCursor cursor = m_cursor;
1239     if (g_globalCursor.Ok()) cursor = g_globalCursor;
1240 
1241     if (cursor.Ok())
1242     {
1243         GdkWindow *window = (GdkWindow*) NULL;
1244         if (HasFlag(wxTE_MULTILINE))
1245             window = GTK_TEXT(m_text)->text_area;
1246         else
1247             window = GTK_ENTRY(m_text)->text_area;
1248 
1249         if (window)
1250             gdk_window_set_cursor( window, cursor.GetCursor() );
1251 
1252         if (!g_globalCursor.Ok())
1253             cursor = *wxSTANDARD_CURSOR;
1254 
1255         window = m_widget->window;
1256         if ((window) && !(GTK_WIDGET_NO_WINDOW(m_widget)))
1257             gdk_window_set_cursor( window, cursor.GetCursor() );
1258     }
1259 
1260     if (g_delayedFocus == this)
1261     {
1262         if (GTK_WIDGET_REALIZED(m_widget))
1263         {
1264             gtk_widget_grab_focus( m_widget );
1265             g_delayedFocus = NULL;
1266         }
1267     }
1268 
1269     if (wxUpdateUIEvent::CanUpdate(this))
1270         UpdateWindowUI(wxUPDATE_UI_FROMIDLE);
1271 }
1272 
DoGetBestSize() const1273 wxSize wxTextCtrl::DoGetBestSize() const
1274 {
1275     // FIXME should be different for multi-line controls...
1276     wxSize ret( wxControl::DoGetBestSize() );
1277     wxSize best(80, ret.y);
1278     CacheBestSize(best);
1279     return best;
1280 }
1281 
1282 // ----------------------------------------------------------------------------
1283 // freeze/thaw
1284 // ----------------------------------------------------------------------------
1285 
Freeze()1286 void wxTextCtrl::Freeze()
1287 {
1288     if ( HasFlag(wxTE_MULTILINE) )
1289     {
1290         gtk_text_freeze(GTK_TEXT(m_text));
1291     }
1292 }
1293 
Thaw()1294 void wxTextCtrl::Thaw()
1295 {
1296     if ( HasFlag(wxTE_MULTILINE) )
1297     {
1298         GTK_TEXT(m_text)->vadj->value = 0.0;
1299 
1300         gtk_text_thaw(GTK_TEXT(m_text));
1301     }
1302 }
1303 
1304 // ----------------------------------------------------------------------------
1305 // scrolling
1306 // ----------------------------------------------------------------------------
1307 
GetVAdj() const1308 GtkAdjustment *wxTextCtrl::GetVAdj() const
1309 {
1310     if ( !IsMultiLine() )
1311         return NULL;
1312 
1313     return GTK_TEXT(m_text)->vadj;
1314 }
1315 
DoScroll(GtkAdjustment * adj,int diff)1316 bool wxTextCtrl::DoScroll(GtkAdjustment *adj, int diff)
1317 {
1318     float value = adj->value + diff;
1319 
1320     if ( value < 0 )
1321         value = 0;
1322 
1323     float upper = adj->upper - adj->page_size;
1324     if ( value > upper )
1325         value = upper;
1326 
1327     // did we noticeably change the scroll position?
1328     if ( fabs(adj->value - value) < 0.2 )
1329     {
1330         // well, this is what Robert does in wxScrollBar, so it must be good...
1331         return false;
1332     }
1333 
1334     adj->value = value;
1335     gtk_signal_emit_by_name(GTK_OBJECT(adj), "value_changed");
1336 
1337     return true;
1338 }
1339 
ScrollLines(int lines)1340 bool wxTextCtrl::ScrollLines(int lines)
1341 {
1342     GtkAdjustment *adj = GetVAdj();
1343     if ( !adj )
1344         return false;
1345 
1346     // this is hardcoded to 10 in GTK+ 1.2 (great idea)
1347     int diff = 10*lines;
1348 
1349     return DoScroll(adj, diff);
1350 }
1351 
ScrollPages(int pages)1352 bool wxTextCtrl::ScrollPages(int pages)
1353 {
1354     GtkAdjustment *adj = GetVAdj();
1355     if ( !adj )
1356         return false;
1357 
1358     return DoScroll(adj, (int)ceil(pages*adj->page_increment));
1359 }
1360 
1361 
1362 // static
1363 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))1364 wxTextCtrl::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
1365 {
1366     return GetDefaultAttributesFromGTKWidget(gtk_entry_new, true);
1367 }
1368