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