1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
15 #include <gdk/gdkkeysyms.h>
19 #include <sys/types.h>
24 #include "dependencyproperty.h"
25 #include "file-downloader.h"
26 #include "contentcontrol.h"
27 #include "timemanager.h"
41 #define UNICODE_LEN(size) (sizeof (gunichar) * (size))
42 #define UNICODE_OFFSET(buf,offset) (((char *) buf) + UNICODE_LEN (offset))
47 bool Resize (int needed
)
49 int new_size
= allocated
;
53 if (allocated
>= needed
+ 128) {
54 while (new_size
>= needed
+ 128)
57 } else if (allocated
< needed
) {
58 while (new_size
< needed
)
64 if (!(buf
= g_try_realloc (text
, UNICODE_LEN (new_size
)))) {
65 // if new_size is < allocated, then we can pretend like we succeeded
66 return new_size
< allocated
;
69 text
= (gunichar
*) buf
;
80 TextBuffer (const gunichar
*text
, int len
)
97 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (128));
105 printf ("TextBuffer::text = \"");
107 for (int i
= 0; i
< len
; i
++) {
110 fputs ("\\r", stdout
);
113 fputs ("\\n", stdout
);
116 fputs ("\\0", stdout
);
119 fputc ('\\', stdout
);
122 fputc ((char) text
[i
], stdout
);
130 void Append (gunichar c
)
132 if (!Resize (len
+ 2))
139 void Append (const gunichar
*str
, int count
)
141 if (!Resize (len
+ count
+ 1))
144 memcpy (UNICODE_OFFSET (text
, len
), str
, UNICODE_LEN (count
));
149 void Cut (int start
, int length
)
154 if (length
== 0 || start
>= len
)
157 if (start
+ length
> len
)
158 length
= len
- start
;
160 src
= UNICODE_OFFSET (text
, start
+ length
);
161 dest
= UNICODE_OFFSET (text
, start
);
162 beyond
= len
- (start
+ length
);
164 memmove (dest
, src
, UNICODE_LEN (beyond
+ 1));
168 void Insert (int index
, gunichar c
)
170 if (!Resize (len
+ 2))
174 // shift all chars beyond position @index down by 1 char
175 memmove (UNICODE_OFFSET (text
, index
+ 1), UNICODE_OFFSET (text
, index
), UNICODE_LEN ((len
- index
) + 1));
184 void Insert (int index
, const gunichar
*str
, int count
)
186 if (!Resize (len
+ count
+ 1))
190 // shift all chars beyond position @index down by @count chars
191 memmove (UNICODE_OFFSET (text
, index
+ count
), UNICODE_OFFSET (text
, index
), UNICODE_LEN ((len
- index
) + 1));
193 // insert @count chars of @str into our buffer at position @index
194 memcpy (UNICODE_OFFSET (text
, index
), str
, UNICODE_LEN (count
));
197 // simply append @count chars of @str onto the end of our buffer
198 memcpy (UNICODE_OFFSET (text
, len
), str
, UNICODE_LEN (count
));
204 void Prepend (gunichar c
)
206 if (!Resize (len
+ 2))
209 // shift the entire buffer down by 1 char
210 memmove (UNICODE_OFFSET (text
, 1), text
, UNICODE_LEN (len
+ 1));
215 void Prepend (const gunichar
*str
, int count
)
217 if (!Resize (len
+ count
+ 1))
220 // shift the endtire buffer down by @count chars
221 memmove (UNICODE_OFFSET (text
, count
), text
, UNICODE_LEN (len
+ 1));
223 // copy @count chars of @str into the beginning of our buffer
224 memcpy (text
, str
, UNICODE_LEN (count
));
228 void Replace (int start
, int length
, const gunichar
*str
, int count
)
236 if (start
+ length
> len
)
237 length
= len
- start
;
239 // Check for the easy cases first...
241 Insert (start
, str
, count
);
243 } else if (count
== 0) {
246 } else if (count
== length
) {
247 memcpy (UNICODE_OFFSET (text
, start
), str
, UNICODE_LEN (count
));
251 if (count
> length
&& !Resize (len
+ (count
- length
) + 1))
254 // calculate the number of chars beyond @start that won't be cut
255 beyond
= len
- (start
+ length
);
257 // shift all chars beyond position (@start + length) into position...
258 dest
= UNICODE_OFFSET (text
, start
+ count
);
259 src
= UNICODE_OFFSET (text
, start
+ length
);
260 memmove (dest
, src
, UNICODE_LEN (beyond
+ 1));
262 // copy @count chars of @str into our buffer at position @start
263 memcpy (UNICODE_OFFSET (text
, start
), str
, UNICODE_LEN (count
));
265 len
= (len
- length
) + count
;
268 gunichar
*Substring (int start
, int length
= -1)
273 if (start
< 0 || start
> len
|| length
== 0)
277 length
= len
- start
;
279 n_bytes
= sizeof (gunichar
) * (length
+ 1);
280 substr
= (gunichar
*) g_malloc (n_bytes
);
281 n_bytes
-= sizeof (gunichar
);
283 memcpy (substr
, text
+ start
, n_bytes
);
292 // TextBoxUndoActions
295 enum TextBoxUndoActionType
{
296 TextBoxUndoActionTypeInsert
,
297 TextBoxUndoActionTypeDelete
,
298 TextBoxUndoActionTypeReplace
,
301 class TextBoxUndoAction
: public List::Node
{
303 TextBoxUndoActionType type
;
304 int selection_anchor
;
305 int selection_cursor
;
310 class TextBoxUndoActionInsert
: public TextBoxUndoAction
{
315 TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, gunichar c
);
316 TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, const gunichar
*inserted
, int length
, bool atomic
= false);
317 virtual ~TextBoxUndoActionInsert ();
319 bool Insert (int start
, const gunichar
*text
, int len
);
320 bool Insert (int start
, gunichar c
);
323 class TextBoxUndoActionDelete
: public TextBoxUndoAction
{
327 TextBoxUndoActionDelete (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
);
328 virtual ~TextBoxUndoActionDelete ();
331 class TextBoxUndoActionReplace
: public TextBoxUndoAction
{
337 TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, const gunichar
*inserted
, int inlen
);
338 TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, gunichar c
);
339 virtual ~TextBoxUndoActionReplace ();
342 class TextBoxUndoStack
{
347 TextBoxUndoStack (int max_count
);
348 ~TextBoxUndoStack ();
353 void Push (TextBoxUndoAction
*action
);
354 TextBoxUndoAction
*Peek ();
355 TextBoxUndoAction
*Pop ();
358 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, gunichar c
)
360 this->type
= TextBoxUndoActionTypeInsert
;
361 this->selection_anchor
= selection_anchor
;
362 this->selection_cursor
= selection_cursor
;
366 this->buffer
= new TextBuffer ();
367 this->buffer
->Append (c
);
368 this->growable
= true;
371 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, const gunichar
*inserted
, int length
, bool atomic
)
373 this->type
= TextBoxUndoActionTypeInsert
;
374 this->selection_anchor
= selection_anchor
;
375 this->selection_cursor
= selection_cursor
;
376 this->length
= length
;
379 this->buffer
= new TextBuffer (inserted
, length
);
380 this->growable
= !atomic
;
383 TextBoxUndoActionInsert::~TextBoxUndoActionInsert ()
389 TextBoxUndoActionInsert::Insert (int start
, const gunichar
*text
, int len
)
391 if (!growable
|| start
!= (this->start
+ length
))
394 buffer
->Append (text
, len
);
401 TextBoxUndoActionInsert::Insert (int start
, gunichar c
)
403 if (!growable
|| start
!= (this->start
+ length
))
412 TextBoxUndoActionDelete::TextBoxUndoActionDelete (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
)
414 this->type
= TextBoxUndoActionTypeDelete
;
415 this->selection_anchor
= selection_anchor
;
416 this->selection_cursor
= selection_cursor
;
417 this->length
= length
;
420 this->text
= buffer
->Substring (start
, length
);
423 TextBoxUndoActionDelete::~TextBoxUndoActionDelete ()
428 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, const gunichar
*inserted
, int inlen
)
430 this->type
= TextBoxUndoActionTypeReplace
;
431 this->selection_anchor
= selection_anchor
;
432 this->selection_cursor
= selection_cursor
;
433 this->length
= length
;
436 this->deleted
= buffer
->Substring (start
, length
);
437 this->inserted
= (gunichar
*) g_malloc (UNICODE_LEN (inlen
+ 1));
438 memcpy (this->inserted
, inserted
, UNICODE_LEN (inlen
+ 1));
442 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, gunichar c
)
444 this->type
= TextBoxUndoActionTypeReplace
;
445 this->selection_anchor
= selection_anchor
;
446 this->selection_cursor
= selection_cursor
;
447 this->length
= length
;
450 this->deleted
= buffer
->Substring (start
, length
);
451 this->inserted
= g_new (gunichar
, 2);
452 memcpy (inserted
, &c
, sizeof (gunichar
));
457 TextBoxUndoActionReplace::~TextBoxUndoActionReplace ()
464 TextBoxUndoStack::TextBoxUndoStack (int max_count
)
466 this->max_count
= max_count
;
467 this->list
= new List ();
470 TextBoxUndoStack::~TextBoxUndoStack ()
476 TextBoxUndoStack::IsEmpty ()
478 return list
->IsEmpty ();
482 TextBoxUndoStack::Clear ()
488 TextBoxUndoStack::Push (TextBoxUndoAction
*action
)
490 if (list
->Length () == max_count
) {
491 List::Node
*node
= list
->Last ();
496 list
->Prepend (action
);
500 TextBoxUndoStack::Pop ()
502 List::Node
*node
= list
->First ();
507 return (TextBoxUndoAction
*) node
;
511 TextBoxUndoStack::Peek ()
513 return (TextBoxUndoAction
*) list
->First ();
521 // emit state, also doubles as available event mask
522 #define NOTHING_CHANGED (0)
523 #define SELECTION_CHANGED (1 << 0)
524 #define TEXT_CHANGED (1 << 1)
526 #define CONTROL_MASK GDK_CONTROL_MASK
527 #define SHIFT_MASK GDK_SHIFT_MASK
528 #define ALT_MASK GDK_MOD1_MASK
530 #define IsEOL(c) ((c) == '\r' || (c) == '\n')
533 GetGdkWindow (TextBoxBase
*textbox
)
538 if (!(surface
= textbox
->GetSurface ()))
541 if (!(window
= surface
->GetWindow ()))
544 return window
->GetGdkWindow ();
547 static GtkClipboard
*
548 GetClipboard (TextBoxBase
*textbox
, GdkAtom atom
)
553 if (!(window
= GetGdkWindow (textbox
)))
556 if (!(display
= gdk_drawable_get_display ((GdkDrawable
*) window
)))
559 return gtk_clipboard_get_for_display (display
, atom
);
563 TextBoxBase::Initialize (Type::Kind type
, const char *type_name
)
565 ManagedTypeInfo
*type_info
= new ManagedTypeInfo ("System.Windows", type_name
);
567 SetObjectType (type
);
568 SetDefaultStyleKey (type_info
);
570 AddHandler (UIElement::MouseLeftButtonMultiClickEvent
, TextBoxBase::mouse_left_button_multi_click
, this);
571 AddHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxBase::mouse_left_button_down
, this);
572 AddHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxBase::mouse_left_button_up
, this);
573 AddHandler (UIElement::MouseMoveEvent
, TextBoxBase::mouse_move
, this);
574 AddHandler (UIElement::LostFocusEvent
, TextBoxBase::focus_out
, this);
575 AddHandler (UIElement::GotFocusEvent
, TextBoxBase::focus_in
, this);
577 font
= new TextFontDescription ();
578 font
->SetFamily (GetFontFamily ()->source
);
579 font
->SetStretch (GetFontStretch ()->stretch
);
580 font
->SetWeight (GetFontWeight ()->weight
);
581 font
->SetStyle (GetFontStyle ()->style
);
582 font
->SetSize (GetFontSize ());
584 downloaders
= g_ptr_array_new ();
587 contentElement
= NULL
;
589 im_ctx
= gtk_im_multicontext_new ();
590 gtk_im_context_set_use_preedit (im_ctx
, false);
592 g_signal_connect (im_ctx
, "retrieve-surrounding", G_CALLBACK (TextBoxBase::retrieve_surrounding
), this);
593 g_signal_connect (im_ctx
, "delete-surrounding", G_CALLBACK (TextBoxBase::delete_surrounding
), this);
594 g_signal_connect (im_ctx
, "commit", G_CALLBACK (TextBoxBase::commit
), this);
596 undo
= new TextBoxUndoStack (10);
597 redo
= new TextBoxUndoStack (10);
598 buffer
= new TextBuffer ();
601 emit
= NOTHING_CHANGED
;
604 selection_anchor
= 0;
605 selection_cursor
= 0;
609 accepts_return
= false;
610 need_im_reset
= false;
611 is_read_only
= false;
622 TextBoxBase::~TextBoxBase ()
624 RemoveHandler (UIElement::MouseLeftButtonMultiClickEvent
, TextBoxBase::mouse_left_button_multi_click
, this);
625 RemoveHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxBase::mouse_left_button_down
, this);
626 RemoveHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxBase::mouse_left_button_up
, this);
627 RemoveHandler (UIElement::MouseMoveEvent
, TextBoxBase::mouse_move
, this);
628 RemoveHandler (UIElement::LostFocusEvent
, TextBoxBase::focus_out
, this);
629 RemoveHandler (UIElement::GotFocusEvent
, TextBoxBase::focus_in
, this);
632 g_object_unref (im_ctx
);
634 CleanupDownloaders ();
635 g_ptr_array_free (downloaders
, true);
636 g_free (font_source
);
645 TextBoxBase::SetSurface (Surface
*surface
)
647 Control::SetSurface (surface
);
649 gtk_im_context_set_client_window (im_ctx
, GetGdkWindow (this));
653 TextBoxBase::CleanupDownloaders ()
655 Downloader
*downloader
;
658 for (i
= 0; i
< downloaders
->len
; i
++) {
659 downloader
= (Downloader
*) downloaders
->pdata
[i
];
660 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
661 downloader
->Abort ();
662 downloader
->unref ();
665 g_ptr_array_set_size (downloaders
, 0);
669 TextBoxBase::GetCursorOffset ()
671 if (!have_offset
&& view
) {
672 cursor_offset
= view
->GetCursor ().x
;
676 return cursor_offset
;
680 TextBoxBase::CursorDown (int cursor
, bool page
)
682 double y
= view
->GetCursor ().y
;
683 double x
= GetCursorOffset ();
684 TextLayoutLine
*line
;
689 if (!(line
= view
->GetLineFromY (y
, &index
)))
693 // calculate the number of lines to skip over
694 n
= GetActualHeight () / line
->height
;
699 if (index
+ n
>= view
->GetLineCount ()) {
700 // go to the end of the last line
701 line
= view
->GetLineFromIndex (view
->GetLineCount () - 1);
703 for (cur
= line
->offset
, i
= 0; i
< line
->runs
->len
; i
++) {
704 run
= (TextLayoutRun
*) line
->runs
->pdata
[i
];
713 line
= view
->GetLineFromIndex (index
+ n
);
715 return line
->GetCursorFromX (Point (), x
);
719 TextBoxBase::CursorUp (int cursor
, bool page
)
721 double y
= view
->GetCursor ().y
;
722 double x
= GetCursorOffset ();
723 TextLayoutLine
*line
;
726 if (!(line
= view
->GetLineFromY (y
, &index
)))
730 // calculate the number of lines to skip over
731 n
= GetActualHeight () / line
->height
;
737 // go to the beginning of the first line
742 line
= view
->GetLineFromIndex (index
- n
);
744 return line
->GetCursorFromX (Point (), x
);
751 CharClassAlphaNumeric
754 static inline CharClass
755 char_class (gunichar c
)
757 if (g_unichar_isspace (c
))
758 return CharClassWhitespace
;
760 if (g_unichar_isalnum (c
))
761 return CharClassAlphaNumeric
;
763 return CharClassUnknown
;
767 is_start_of_word (TextBuffer
*buffer
, int index
)
769 // A 'word' starts with an AlphaNumeric immediately preceeded by lwsp
770 if (index
> 0 && !g_unichar_isspace (buffer
->text
[index
- 1]))
773 return g_unichar_isalnum (buffer
->text
[index
]);
778 TextBoxBase::CursorNextWord (int cursor
)
782 // find the end of the current line
783 cr
= CursorLineEnd (cursor
);
784 if (buffer
->text
[cr
] == '\r' && buffer
->text
[cr
+ 1] == '\n')
789 // if the cursor is at the end of the line, return the starting offset of the next line
790 if (cursor
== cr
|| cursor
== lf
) {
791 if (lf
< buffer
->len
)
798 CharClass cc
= char_class (buffer
->text
[cursor
]);
801 // skip over the word, punctuation, or run of whitespace
802 while (i
< cr
&& char_class (buffer
->text
[i
]) == cc
)
805 // skip any whitespace after the word/punct
806 while (i
< cr
&& g_unichar_isspace (buffer
->text
[i
]))
811 // skip to the end of the current word
812 while (i
< cr
&& !g_unichar_isspace (buffer
->text
[i
]))
815 // skip any whitespace after the word
816 while (i
< cr
&& g_unichar_isspace (buffer
->text
[i
]))
819 // find the start of the next word
820 while (i
< cr
&& !is_start_of_word (buffer
, i
))
828 TextBoxBase::CursorPrevWord (int cursor
)
830 int begin
, i
, cr
, lf
;
832 // find the beginning of the current line
833 lf
= CursorLineBegin (cursor
) - 1;
835 if (lf
> 0 && buffer
->text
[lf
] == '\n' && buffer
->text
[lf
- 1] == '\r')
840 // if the cursor is at the beginning of the line, return the end of the prev line
841 if (cursor
- 1 == lf
) {
849 CharClass cc
= char_class (buffer
->text
[cursor
- 1]);
853 // skip over the word, punctuation, or run of whitespace
854 while (i
> begin
&& char_class (buffer
->text
[i
- 1]) == cc
)
857 // if the cursor was at whitespace, skip back a word too
858 if (cc
== CharClassWhitespace
&& i
> begin
) {
859 cc
= char_class (buffer
->text
[i
- 1]);
860 while (i
> begin
&& char_class (buffer
->text
[i
- 1]) == cc
)
867 if (cursor
< buffer
->len
) {
868 // skip to the beginning of this word
869 while (i
> begin
&& !g_unichar_isspace (buffer
->text
[i
- 1]))
872 if (i
< cursor
&& is_start_of_word (buffer
, i
))
876 // skip to the start of the lwsp
877 while (i
> begin
&& g_unichar_isspace (buffer
->text
[i
- 1]))
883 // skip to the beginning of the word
884 while (i
> begin
&& !is_start_of_word (buffer
, i
))
892 TextBoxBase::CursorLineBegin (int cursor
)
896 // find the beginning of the line
897 while (cur
> 0 && !IsEOL (buffer
->text
[cur
- 1]))
904 TextBoxBase::CursorLineEnd (int cursor
, bool include
)
908 // find the end of the line
909 while (cur
< buffer
->len
&& !IsEOL (buffer
->text
[cur
]))
912 if (include
&& cur
< buffer
->len
) {
913 if (buffer
->text
[cur
] == '\r' && buffer
->text
[cur
+ 1] == '\n')
923 TextBoxBase::KeyPressBackSpace (GdkModifierType modifiers
)
925 int anchor
= selection_anchor
;
926 int cursor
= selection_cursor
;
927 TextBoxUndoAction
*action
;
928 int start
= 0, length
= 0;
929 bool handled
= false;
931 if ((modifiers
& (ALT_MASK
| SHIFT_MASK
)) != 0)
934 if (cursor
!= anchor
) {
935 // BackSpace w/ active selection: delete the selected text
936 length
= abs (cursor
- anchor
);
937 start
= MIN (anchor
, cursor
);
938 } else if ((modifiers
& CONTROL_MASK
) != 0) {
939 // Ctrl+BackSpace: delete the word ending at the cursor
940 start
= CursorPrevWord (cursor
);
941 length
= cursor
- start
;
942 } else if (cursor
> 0) {
943 // BackSpace: delete the char before the cursor position
944 if (cursor
>= 2 && buffer
->text
[cursor
- 1] == '\n' && buffer
->text
[cursor
- 2] == '\r') {
954 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
958 buffer
->Cut (start
, length
);
959 emit
|= TEXT_CHANGED
;
965 // check to see if selection has changed
966 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
967 SetSelectionStart (MIN (anchor
, cursor
));
968 SetSelectionLength (abs (cursor
- anchor
));
969 selection_anchor
= anchor
;
970 selection_cursor
= cursor
;
971 emit
|= SELECTION_CHANGED
;
979 TextBoxBase::KeyPressDelete (GdkModifierType modifiers
)
981 int anchor
= selection_anchor
;
982 int cursor
= selection_cursor
;
983 TextBoxUndoAction
*action
;
984 int start
= 0, length
= 0;
985 bool handled
= false;
987 if ((modifiers
& (ALT_MASK
| SHIFT_MASK
)) != 0)
990 if (cursor
!= anchor
) {
991 // Delete w/ active selection: delete the selected text
992 length
= abs (cursor
- anchor
);
993 start
= MIN (anchor
, cursor
);
994 } else if ((modifiers
& CONTROL_MASK
) != 0) {
995 // Ctrl+Delete: delete the word starting at the cursor
996 length
= CursorNextWord (cursor
) - cursor
;
998 } else if (cursor
< buffer
->len
) {
999 // Delete: delete the char after the cursor position
1000 if (buffer
->text
[cursor
] == '\r' && buffer
->text
[cursor
+ 1] == '\n')
1009 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
1010 undo
->Push (action
);
1013 buffer
->Cut (start
, length
);
1014 emit
|= TEXT_CHANGED
;
1018 // check to see if selection has changed
1019 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1020 SetSelectionStart (MIN (anchor
, cursor
));
1021 SetSelectionLength (abs (cursor
- anchor
));
1022 selection_anchor
= anchor
;
1023 selection_cursor
= cursor
;
1024 emit
|= SELECTION_CHANGED
;
1032 TextBoxBase::KeyPressPageDown (GdkModifierType modifiers
)
1034 int anchor
= selection_anchor
;
1035 int cursor
= selection_cursor
;
1036 bool handled
= false;
1039 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1042 // move the cursor down one page from its current position
1043 cursor
= CursorDown (cursor
, true);
1046 if ((modifiers
& SHIFT_MASK
) == 0) {
1047 // clobber the selection
1051 // check to see if selection has changed
1052 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1053 SetSelectionStart (MIN (anchor
, cursor
));
1054 SetSelectionLength (abs (cursor
- anchor
));
1055 selection_anchor
= anchor
;
1056 selection_cursor
= cursor
;
1057 emit
|= SELECTION_CHANGED
;
1066 TextBoxBase::KeyPressPageUp (GdkModifierType modifiers
)
1068 int anchor
= selection_anchor
;
1069 int cursor
= selection_cursor
;
1070 bool handled
= false;
1073 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1076 // move the cursor up one page from its current position
1077 cursor
= CursorUp (cursor
, true);
1080 if ((modifiers
& SHIFT_MASK
) == 0) {
1081 // clobber the selection
1085 // check to see if selection has changed
1086 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1087 SetSelectionStart (MIN (anchor
, cursor
));
1088 SetSelectionLength (abs (cursor
- anchor
));
1089 selection_anchor
= anchor
;
1090 selection_cursor
= cursor
;
1091 emit
|= SELECTION_CHANGED
;
1100 TextBoxBase::KeyPressDown (GdkModifierType modifiers
)
1102 int anchor
= selection_anchor
;
1103 int cursor
= selection_cursor
;
1104 bool handled
= false;
1107 if (!accepts_return
|| (modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1110 // move the cursor down by one line from its current position
1111 cursor
= CursorDown (cursor
, false);
1114 if ((modifiers
& SHIFT_MASK
) == 0) {
1115 // clobber the selection
1119 // check to see if selection has changed
1120 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1121 SetSelectionStart (MIN (anchor
, cursor
));
1122 SetSelectionLength (abs (cursor
- anchor
));
1123 selection_anchor
= anchor
;
1124 selection_cursor
= cursor
;
1125 emit
|= SELECTION_CHANGED
;
1134 TextBoxBase::KeyPressUp (GdkModifierType modifiers
)
1136 int anchor
= selection_anchor
;
1137 int cursor
= selection_cursor
;
1138 bool handled
= false;
1141 if (!accepts_return
|| (modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1144 // move the cursor up by one line from its current position
1145 cursor
= CursorUp (cursor
, false);
1148 if ((modifiers
& SHIFT_MASK
) == 0) {
1149 // clobber the selection
1153 // check to see if selection has changed
1154 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1155 SetSelectionStart (MIN (anchor
, cursor
));
1156 SetSelectionLength (abs (cursor
- anchor
));
1157 selection_anchor
= anchor
;
1158 selection_cursor
= cursor
;
1159 emit
|= SELECTION_CHANGED
;
1168 TextBoxBase::KeyPressHome (GdkModifierType modifiers
)
1170 int anchor
= selection_anchor
;
1171 int cursor
= selection_cursor
;
1172 bool handled
= false;
1174 if ((modifiers
& ALT_MASK
) != 0)
1177 if ((modifiers
& CONTROL_MASK
) != 0) {
1178 // move the cursor to the beginning of the buffer
1181 // move the cursor to the beginning of the line
1182 cursor
= CursorLineBegin (cursor
);
1185 if ((modifiers
& SHIFT_MASK
) == 0) {
1186 // clobber the selection
1190 // check to see if selection has changed
1191 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1192 SetSelectionStart (MIN (anchor
, cursor
));
1193 SetSelectionLength (abs (cursor
- anchor
));
1194 selection_anchor
= anchor
;
1195 selection_cursor
= cursor
;
1196 emit
|= SELECTION_CHANGED
;
1197 have_offset
= false;
1205 TextBoxBase::KeyPressEnd (GdkModifierType modifiers
)
1207 int anchor
= selection_anchor
;
1208 int cursor
= selection_cursor
;
1209 bool handled
= false;
1211 if ((modifiers
& ALT_MASK
) != 0)
1214 if ((modifiers
& CONTROL_MASK
) != 0) {
1215 // move the cursor to the end of the buffer
1216 cursor
= buffer
->len
;
1218 // move the cursor to the end of the line
1219 cursor
= CursorLineEnd (cursor
);
1222 if ((modifiers
& SHIFT_MASK
) == 0) {
1223 // clobber the selection
1227 // check to see if selection has changed
1228 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1229 SetSelectionStart (MIN (anchor
, cursor
));
1230 SetSelectionLength (abs (cursor
- anchor
));
1231 selection_anchor
= anchor
;
1232 selection_cursor
= cursor
;
1233 emit
|= SELECTION_CHANGED
;
1241 TextBoxBase::KeyPressRight (GdkModifierType modifiers
)
1243 int anchor
= selection_anchor
;
1244 int cursor
= selection_cursor
;
1245 bool handled
= false;
1247 if ((modifiers
& ALT_MASK
) != 0)
1250 if ((modifiers
& CONTROL_MASK
) != 0) {
1251 // move the cursor to beginning of the next word
1252 cursor
= CursorNextWord (cursor
);
1253 } else if ((modifiers
& SHIFT_MASK
) == 0 && anchor
!= cursor
) {
1254 // set cursor at end of selection
1255 cursor
= MAX (anchor
, cursor
);
1257 // move the cursor forward one character
1258 if (buffer
->text
[cursor
] == '\r' && buffer
->text
[cursor
+ 1] == '\n')
1260 else if (cursor
< buffer
->len
)
1264 if ((modifiers
& SHIFT_MASK
) == 0) {
1265 // clobber the selection
1269 // check to see if selection has changed
1270 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1271 SetSelectionStart (MIN (anchor
, cursor
));
1272 SetSelectionLength (abs (cursor
- anchor
));
1273 selection_anchor
= anchor
;
1274 selection_cursor
= cursor
;
1275 emit
|= SELECTION_CHANGED
;
1283 TextBoxBase::KeyPressLeft (GdkModifierType modifiers
)
1285 int anchor
= selection_anchor
;
1286 int cursor
= selection_cursor
;
1287 bool handled
= false;
1289 if ((modifiers
& ALT_MASK
) != 0)
1292 if ((modifiers
& CONTROL_MASK
) != 0) {
1293 // move the cursor to the beginning of the previous word
1294 cursor
= CursorPrevWord (cursor
);
1295 } else if ((modifiers
& SHIFT_MASK
) == 0 && anchor
!= cursor
) {
1296 // set cursor at start of selection
1297 cursor
= MIN (anchor
, cursor
);
1299 // move the cursor backward one character
1300 if (cursor
>= 2 && buffer
->text
[cursor
- 2] == '\r' && buffer
->text
[cursor
- 1] == '\n')
1302 else if (cursor
> 0)
1306 if ((modifiers
& SHIFT_MASK
) == 0) {
1307 // clobber the selection
1311 // check to see if selection has changed
1312 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1313 SetSelectionStart (MIN (anchor
, cursor
));
1314 SetSelectionLength (abs (cursor
- anchor
));
1315 selection_anchor
= anchor
;
1316 selection_cursor
= cursor
;
1317 emit
|= SELECTION_CHANGED
;
1325 TextBoxBase::KeyPressUnichar (gunichar c
)
1327 int length
= abs (selection_cursor
- selection_anchor
);
1328 int start
= MIN (selection_anchor
, selection_cursor
);
1329 int anchor
= selection_anchor
;
1330 int cursor
= selection_cursor
;
1331 TextBoxUndoAction
*action
;
1333 if ((max_length
> 0 && buffer
->len
>= max_length
) || ((c
== '\r') && !accepts_return
))
1337 // replace the currently selected text
1338 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, c
);
1339 undo
->Push (action
);
1342 buffer
->Replace (start
, length
, &c
, 1);
1344 // insert the text at the cursor position
1345 TextBoxUndoActionInsert
*insert
= NULL
;
1347 if ((action
= undo
->Peek ()) && action
->type
== TextBoxUndoActionTypeInsert
) {
1348 insert
= (TextBoxUndoActionInsert
*) action
;
1350 if (!insert
->Insert (start
, c
))
1355 insert
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, c
);
1356 undo
->Push (insert
);
1361 buffer
->Insert (start
, c
);
1364 emit
|= TEXT_CHANGED
;
1368 // check to see if selection has changed
1369 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1370 SetSelectionStart (MIN (anchor
, cursor
));
1371 SetSelectionLength (abs (cursor
- anchor
));
1372 selection_anchor
= anchor
;
1373 selection_cursor
= cursor
;
1374 emit
|= SELECTION_CHANGED
;
1380 TextBoxBase::BatchPush ()
1386 TextBoxBase::BatchPop ()
1389 g_warning ("TextBoxBase batch underflow");
1397 TextBoxBase::emit_selection_changed (EventObject
*sender
)
1399 if (((TextBoxBase
*) sender
)->IsLoaded ())
1400 ((TextBoxBase
*) sender
)->EmitSelectionChanged ();
1404 TextBoxBase::EmitSelectionChangedAsync ()
1406 if (IsLoaded () && (events_mask
& SELECTION_CHANGED
))
1407 AddTickCall (TextBoxBase::emit_selection_changed
);
1409 emit
&= ~SELECTION_CHANGED
;
1413 TextBoxBase::emit_text_changed (EventObject
*sender
)
1415 if (((TextBoxBase
*) sender
)->IsLoaded ())
1416 ((TextBoxBase
*) sender
)->EmitTextChanged ();
1420 TextBoxBase::EmitTextChangedAsync ()
1422 if (IsLoaded () && (events_mask
& TEXT_CHANGED
))
1423 AddTickCall (TextBoxBase::emit_text_changed
);
1425 emit
&= ~TEXT_CHANGED
;
1429 TextBoxBase::SyncAndEmit (bool sync_text
)
1431 if (batch
!= 0 || emit
== NOTHING_CHANGED
)
1434 if (sync_text
&& (emit
& TEXT_CHANGED
))
1437 if (emit
& SELECTION_CHANGED
)
1438 SyncSelectedText ();
1440 if (emit
& TEXT_CHANGED
)
1441 EmitTextChangedAsync ();
1443 if (emit
& SELECTION_CHANGED
)
1444 EmitSelectionChangedAsync ();
1446 emit
= NOTHING_CHANGED
;
1450 TextBoxBase::Paste (GtkClipboard
*clipboard
, const char *str
)
1452 int length
= abs (selection_cursor
- selection_anchor
);
1453 int start
= MIN (selection_anchor
, selection_cursor
);
1454 TextBoxUndoAction
*action
;
1458 if (!(text
= g_utf8_to_ucs4_fast (str
? str
: "", -1, &len
)))
1461 if (max_length
> 0 && ((buffer
->len
- length
) + len
> max_length
)) {
1462 // paste cannot exceed MaxLength
1463 len
= max_length
- (buffer
->len
- length
);
1465 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (len
+ 1));
1472 // only paste the content of the first line
1473 for (i
= 0; i
< len
; i
++) {
1474 if (g_unichar_type (text
[i
]) == G_UNICODE_LINE_SEPARATOR
) {
1475 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (i
+ 1));
1486 // replace the currently selected text
1487 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, len
);
1489 buffer
->Replace (start
, length
, text
, len
);
1490 } else if (len
> 0) {
1491 // insert the text at the cursor position
1492 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, len
, true);
1494 buffer
->Insert (start
, text
, len
);
1500 undo
->Push (action
);
1504 emit
|= TEXT_CHANGED
;
1508 SetSelectionStart (start
);
1509 SetSelectionLength (0);
1516 TextBoxBase::paste (GtkClipboard
*clipboard
, const char *text
, gpointer closure
)
1518 ((TextBoxBase
*) closure
)->Paste (clipboard
, text
);
1522 TextBoxBase::OnKeyDown (KeyEventArgs
*args
)
1524 GdkModifierType modifiers
= (GdkModifierType
) args
->GetModifiers ();
1525 guint key
= args
->GetKeyVal ();
1526 GtkClipboard
*clipboard
;
1527 bool handled
= false;
1529 if (args
->IsModifier ())
1532 // set 'emit' to NOTHING_CHANGED so that we can figure out
1533 // what has chanegd after applying the changes that this
1534 // keypress will cause.
1535 emit
= NOTHING_CHANGED
;
1543 handled
= KeyPressBackSpace (modifiers
);
1549 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == SHIFT_MASK
) {
1550 // Shift+Delete => Cut
1551 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1552 if (selection_cursor
!= selection_anchor
) {
1553 // copy selection to the clipboard and then cut
1554 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1558 SetSelectedText ("");
1561 handled
= KeyPressDelete (modifiers
);
1565 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == SHIFT_MASK
) {
1566 // Shift+Insert => Paste
1570 if ((clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1571 // paste clipboard contents to the buffer
1572 gtk_clipboard_request_text (clipboard
, TextBoxBase::paste
, this);
1576 } else if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == CONTROL_MASK
) {
1577 // Control+Insert => Copy
1578 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1579 if (selection_cursor
!= selection_anchor
) {
1580 // copy selection to the clipboard
1581 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1588 case GDK_KP_Page_Down
:
1590 handled
= KeyPressPageDown (modifiers
);
1592 case GDK_KP_Page_Up
:
1594 handled
= KeyPressPageUp (modifiers
);
1598 handled
= KeyPressHome (modifiers
);
1602 handled
= KeyPressEnd (modifiers
);
1606 handled
= KeyPressRight (modifiers
);
1610 handled
= KeyPressLeft (modifiers
);
1614 handled
= KeyPressDown (modifiers
);
1618 handled
= KeyPressUp (modifiers
);
1621 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == CONTROL_MASK
) {
1625 // Ctrl+A => Select All
1632 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1633 if (selection_cursor
!= selection_anchor
) {
1634 // copy selection to the clipboard
1635 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1647 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1648 if (selection_cursor
!= selection_anchor
) {
1649 // copy selection to the clipboard and then cut
1650 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1654 SetSelectedText ("");
1663 if ((clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1664 // paste clipboard contents to the buffer
1665 gtk_clipboard_request_text (clipboard
, TextBoxBase::paste
, this);
1673 if (!is_read_only
) {
1681 if (!is_read_only
) {
1687 // unhandled Control commands
1695 args
->SetHandled (handled
);
1705 TextBoxBase::OnCharacterKeyDown (KeyEventArgs
*args
)
1707 guint key
= args
->GetKeyVal ();
1708 bool handled
= false;
1711 if (!is_read_only
&& gtk_im_context_filter_keypress (im_ctx
, args
->GetEvent ())) {
1712 args
->SetHandled (true);
1713 need_im_reset
= true;
1717 if (is_read_only
|| args
->IsModifier ())
1720 // set 'emit' to NOTHING_CHANGED so that we can figure out
1721 // what has chanegd after applying the changes that this
1722 // keypress will cause.
1723 emit
= NOTHING_CHANGED
;
1728 handled
= KeyPressUnichar ('\r');
1731 if ((args
->GetModifiers () & (CONTROL_MASK
| ALT_MASK
)) == 0) {
1732 // normal character input
1733 if ((c
= args
->GetUnicode ()))
1734 handled
= KeyPressUnichar (c
);
1740 args
->SetHandled (handled
);
1748 TextBoxBase::OnKeyUp (KeyEventArgs
*args
)
1750 if (!is_read_only
) {
1751 if (gtk_im_context_filter_keypress (im_ctx
, args
->GetEvent ()))
1752 need_im_reset
= true;
1754 args
->SetHandled (true);
1759 TextBoxBase::DeleteSurrounding (int offset
, int n_chars
)
1761 const char *delete_start
, *delete_end
;
1762 const char *text
= GetActualText ();
1763 int anchor
= selection_anchor
;
1764 int cursor
= selection_cursor
;
1765 TextBoxUndoAction
*action
;
1771 // get the utf-8 pointers so that we can use them to get gunichar offsets
1772 delete_start
= g_utf8_offset_to_pointer (text
, selection_cursor
) + offset
;
1773 delete_end
= delete_start
+ n_chars
;
1775 // get the character length/start index
1776 length
= g_utf8_pointer_to_offset (delete_start
, delete_end
);
1777 start
= g_utf8_pointer_to_offset (text
, delete_start
);
1780 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
1781 undo
->Push (action
);
1784 buffer
->Cut (start
, length
);
1785 emit
|= TEXT_CHANGED
;
1792 // check to see if selection has changed
1793 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1794 SetSelectionStart (MIN (anchor
, cursor
));
1795 SetSelectionLength (abs (cursor
- anchor
));
1796 selection_anchor
= anchor
;
1797 selection_cursor
= cursor
;
1798 emit
|= SELECTION_CHANGED
;
1809 TextBoxBase::delete_surrounding (GtkIMContext
*context
, int offset
, int n_chars
, gpointer user_data
)
1811 return ((TextBoxBase
*) user_data
)->DeleteSurrounding (offset
, n_chars
);
1815 TextBoxBase::RetrieveSurrounding ()
1817 const char *text
= GetActualText ();
1818 const char *cursor
= g_utf8_offset_to_pointer (text
, selection_cursor
);
1820 gtk_im_context_set_surrounding (im_ctx
, text
, -1, cursor
- text
);
1826 TextBoxBase::retrieve_surrounding (GtkIMContext
*context
, gpointer user_data
)
1828 return ((TextBoxBase
*) user_data
)->RetrieveSurrounding ();
1832 TextBoxBase::Commit (const char *str
)
1834 int length
= abs (selection_cursor
- selection_anchor
);
1835 int start
= MIN (selection_anchor
, selection_cursor
);
1836 TextBoxUndoAction
*action
;
1844 if (!(text
= g_utf8_to_ucs4_fast (str
? str
: "", -1, &len
)))
1847 if (max_length
> 0 && ((buffer
->len
- length
) + len
> max_length
)) {
1848 // paste cannot exceed MaxLength
1849 len
= max_length
- (buffer
->len
- length
);
1851 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (len
+ 1));
1858 // only paste the content of the first line
1859 for (i
= 0; i
< len
; i
++) {
1860 if (g_unichar_type (text
[i
]) == G_UNICODE_LINE_SEPARATOR
) {
1861 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (i
+ 1));
1870 // replace the currently selected text
1871 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, len
);
1872 undo
->Push (action
);
1875 buffer
->Replace (start
, length
, text
, len
);
1876 } else if (len
> 0) {
1877 // insert the text at the cursor position
1878 TextBoxUndoActionInsert
*insert
= NULL
;
1880 buffer
->Insert (start
, text
, len
);
1882 if ((action
= undo
->Peek ()) && action
->type
== TextBoxUndoActionTypeInsert
) {
1883 insert
= (TextBoxUndoActionInsert
*) action
;
1885 if (!insert
->Insert (start
, text
, len
))
1890 insert
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, len
);
1891 undo
->Push (insert
);
1900 emit
= TEXT_CHANGED
;
1901 cursor
= start
+ len
;
1907 // check to see if selection has changed
1908 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1909 SetSelectionStart (MIN (anchor
, cursor
));
1910 SetSelectionLength (abs (cursor
- anchor
));
1911 selection_anchor
= anchor
;
1912 selection_cursor
= cursor
;
1913 emit
|= SELECTION_CHANGED
;
1922 TextBoxBase::commit (GtkIMContext
*context
, const char *str
, gpointer user_data
)
1924 ((TextBoxBase
*) user_data
)->Commit (str
);
1928 TextBoxBase::ResetIMContext ()
1930 if (need_im_reset
) {
1931 gtk_im_context_reset (im_ctx
);
1932 need_im_reset
= false;
1937 TextBoxBase::OnMouseLeftButtonDown (MouseEventArgs
*args
)
1942 args
->SetHandled (true);
1946 args
->GetPosition (view
, &x
, &y
);
1948 cursor
= view
->GetCursorFromXY (x
, y
);
1952 // Single-Click: cursor placement
1953 captured
= CaptureMouse ();
1957 emit
= NOTHING_CHANGED
;
1958 SetSelectionStart (cursor
);
1959 SetSelectionLength (0);
1967 TextBoxBase::mouse_left_button_down (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
1969 ((TextBoxBase
*) closure
)->OnMouseLeftButtonDown ((MouseEventArgs
*) args
);
1973 TextBoxBase::OnMouseLeftButtonMultiClick (MouseEventArgs
*args
)
1975 int cursor
, start
, end
;
1978 args
->SetHandled (true);
1981 args
->GetPosition (view
, &x
, &y
);
1983 cursor
= view
->GetCursorFromXY (x
, y
);
1987 if (args
->GetClickCount () == 3) {
1988 // Note: Silverlight doesn't implement this, but to
1989 // be consistent with other TextEntry-type
1990 // widgets in Gtk+, we will.
1992 // Triple-Click: select the line
1994 ReleaseMouseCapture ();
1995 start
= CursorLineBegin (cursor
);
1996 end
= CursorLineEnd (cursor
, true);
2000 // Double-Click: select the word
2002 ReleaseMouseCapture ();
2003 start
= CursorPrevWord (cursor
);
2004 end
= CursorNextWord (cursor
);
2010 emit
= NOTHING_CHANGED
;
2011 SetSelectionStart (start
);
2012 SetSelectionLength (end
- start
);
2020 TextBoxBase::mouse_left_button_multi_click (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2022 ((TextBoxBase
*) closure
)->OnMouseLeftButtonMultiClick ((MouseEventArgs
*) args
);
2026 TextBoxBase::OnMouseLeftButtonUp (MouseEventArgs
*args
)
2029 ReleaseMouseCapture ();
2031 args
->SetHandled (true);
2037 TextBoxBase::mouse_left_button_up (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2039 ((TextBoxBase
*) closure
)->OnMouseLeftButtonUp ((MouseEventArgs
*) args
);
2043 TextBoxBase::OnMouseMove (MouseEventArgs
*args
)
2045 int anchor
= selection_anchor
;
2046 int cursor
= selection_cursor
;
2050 args
->GetPosition (view
, &x
, &y
);
2051 args
->SetHandled (true);
2053 cursor
= view
->GetCursorFromXY (x
, y
);
2056 emit
= NOTHING_CHANGED
;
2057 SetSelectionStart (MIN (anchor
, cursor
));
2058 SetSelectionLength (abs (cursor
- anchor
));
2059 selection_anchor
= anchor
;
2060 selection_cursor
= cursor
;
2068 TextBoxBase::mouse_move (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2070 ((TextBoxBase
*) closure
)->OnMouseMove ((MouseEventArgs
*) args
);
2074 TextBoxBase::OnFocusOut (EventArgs
*args
)
2077 emit
= NOTHING_CHANGED
;
2078 SetSelectionStart (selection_cursor
);
2079 SetSelectionLength (0);
2087 view
->OnFocusOut ();
2089 if (!is_read_only
) {
2090 gtk_im_context_focus_out (im_ctx
);
2091 need_im_reset
= true;
2096 TextBoxBase::focus_out (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2098 ((TextBoxBase
*) closure
)->OnFocusOut (args
);
2102 TextBoxBase::OnFocusIn (EventArgs
*args
)
2109 if (!is_read_only
) {
2110 gtk_im_context_focus_in (im_ctx
);
2111 need_im_reset
= true;
2116 TextBoxBase::focus_in (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2118 ((TextBoxBase
*) closure
)->OnFocusIn (args
);
2122 TextBoxBase::EmitCursorPositionChanged (double height
, double x
, double y
)
2124 Emit (TextBoxBase::CursorPositionChangedEvent
, new CursorPositionChangedEventArgs (height
, x
, y
));
2128 TextBoxBase::DownloaderComplete (Downloader
*downloader
)
2130 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2131 char *resource
, *filename
;
2132 InternalDownloader
*idl
;
2136 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2137 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
2142 if (!(idl
= downloader
->GetInternalDownloader ()))
2145 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
2148 uri
= downloader
->GetUri ();
2150 // If the downloaded file was a zip file, this'll get the path to the
2151 // extracted zip directory, else it will simply be the path to the
2153 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
2156 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
2157 manager
->AddResource (resource
, path
);
2160 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont
, NULL
));
2164 TextBoxBase::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
2166 ((TextBoxBase
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
2170 TextBoxBase::AddFontSource (Downloader
*downloader
)
2172 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
2173 g_ptr_array_add (downloaders
, downloader
);
2176 if (downloader
->Started () || downloader
->Completed ()) {
2177 if (downloader
->Completed ())
2178 DownloaderComplete (downloader
);
2180 // This is what actually triggers the download
2181 downloader
->Send ();
2186 TextBoxBase::AddFontResource (const char *resource
)
2188 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2189 Application
*application
= Application::GetCurrent ();
2190 Downloader
*downloader
;
2197 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
2198 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
2199 downloader
->Open ("GET", resource
, FontPolicy
);
2200 AddFontSource (downloader
);
2201 downloader
->unref ();
2209 manager
->AddResource (resource
, path
);
2215 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2217 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2219 if (args
->GetId () == Control::FontFamilyProperty
) {
2220 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
2221 char **families
, *fragment
;
2224 CleanupDownloaders ();
2226 if (family
&& family
->source
) {
2227 families
= g_strsplit (family
->source
, ",", -1);
2228 for (i
= 0; families
[i
]; i
++) {
2229 g_strstrip (families
[i
]);
2230 if ((fragment
= strchr (families
[i
], '#'))) {
2231 // the first portion of this string is the resource name...
2233 AddFontResource (families
[i
]);
2236 g_strfreev (families
);
2239 font
->SetFamily (family
? family
->source
: NULL
);
2240 changed
= TextBoxModelChangedFont
;
2241 } else if (args
->GetId () == Control::FontSizeProperty
) {
2242 double size
= args
->GetNewValue()->AsDouble ();
2243 changed
= TextBoxModelChangedFont
;
2244 font
->SetSize (size
);
2245 } else if (args
->GetId () == Control::FontStretchProperty
) {
2246 FontStretches stretch
= args
->GetNewValue()->AsFontStretch()->stretch
;
2247 changed
= TextBoxModelChangedFont
;
2248 font
->SetStretch (stretch
);
2249 } else if (args
->GetId () == Control::FontStyleProperty
) {
2250 FontStyles style
= args
->GetNewValue()->AsFontStyle ()->style
;
2251 changed
= TextBoxModelChangedFont
;
2252 font
->SetStyle (style
);
2253 } else if (args
->GetId () == Control::FontWeightProperty
) {
2254 FontWeights weight
= args
->GetNewValue()->AsFontWeight ()->weight
;
2255 changed
= TextBoxModelChangedFont
;
2256 font
->SetWeight (weight
);
2257 } else if (args
->GetId () == FrameworkElement::MinHeightProperty
) {
2258 // pass this along to our TextBoxView
2260 view
->SetMinHeight (args
->GetNewValue ()->AsDouble ());
2261 } else if (args
->GetId () == FrameworkElement::MaxHeightProperty
) {
2262 // pass this along to our TextBoxView
2264 view
->SetMaxHeight (args
->GetNewValue ()->AsDouble ());
2265 } else if (args
->GetId () == FrameworkElement::MinWidthProperty
) {
2266 // pass this along to our TextBoxView
2268 view
->SetMinWidth (args
->GetNewValue ()->AsDouble ());
2269 } else if (args
->GetId () == FrameworkElement::MaxWidthProperty
) {
2270 // pass this along to our TextBoxView
2272 view
->SetMaxWidth (args
->GetNewValue ()->AsDouble ());
2273 } else if (args
->GetId () == FrameworkElement::HeightProperty
) {
2274 // pass this along to our TextBoxView
2276 view
->SetHeight (args
->GetNewValue ()->AsDouble ());
2277 } else if (args
->GetId () == FrameworkElement::WidthProperty
) {
2278 // pass this along to our TextBoxView
2280 view
->SetWidth (args
->GetNewValue ()->AsDouble ());
2283 if (changed
!= TextBoxModelChangedNothing
)
2284 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2286 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE
) {
2287 Control::OnPropertyChanged (args
, error
);
2291 NotifyListenersOfPropertyChange (args
, error
);
2295 TextBoxBase::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2297 if (prop
&& (prop
->GetId () == Control::BackgroundProperty
||
2298 prop
->GetId () == Control::ForegroundProperty
)) {
2299 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2303 if (prop
->GetOwnerType () != Type::TEXTBOXBASE
)
2304 Control::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2308 TextBoxBase::OnApplyTemplate ()
2310 contentElement
= GetTemplateChild ("ContentElement");
2312 if (contentElement
== NULL
) {
2313 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2314 Control::OnApplyTemplate ();
2318 view
= new TextBoxView ();
2319 view
->SetTextBox (this);
2321 view
->SetMinHeight (GetMinHeight ());
2322 view
->SetMaxHeight (GetMaxHeight ());
2323 view
->SetMinWidth (GetMinWidth ());
2324 view
->SetMaxWidth (GetMaxWidth ());
2325 view
->SetHeight (GetHeight ());
2326 view
->SetWidth (GetWidth ());
2328 // Insert our TextBoxView
2329 if (contentElement
->Is (Type::CONTENTCONTROL
)) {
2330 ContentControl
*control
= (ContentControl
*) contentElement
;
2332 control
->SetValue (ContentControl::ContentProperty
, Value (view
));
2333 } else if (contentElement
->Is (Type::BORDER
)) {
2334 Border
*border
= (Border
*) contentElement
;
2336 border
->SetValue (Border::ChildProperty
, Value (view
));
2337 } else if (contentElement
->Is (Type::PANEL
)) {
2338 DependencyObjectCollection
*children
= ((Panel
*) contentElement
)->GetChildren ();
2340 children
->Add (view
);
2342 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentELement of type %s",
2343 contentElement
->GetType ()->GetName ());
2348 Control::OnApplyTemplate ();
2352 TextBoxBase::ClearSelection (int start
)
2355 SetSelectionStart (start
);
2356 SetSelectionLength (0);
2361 TextBoxBase::SelectWithError (int start
, int length
, MoonError
*error
)
2364 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection start must be >= 0");
2369 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection length must be >= 0");
2373 if (start
> buffer
->len
)
2374 start
= buffer
->len
;
2376 if (length
> (buffer
->len
- start
))
2377 length
= (buffer
->len
- start
);
2380 SetSelectionStart (start
);
2381 SetSelectionLength (length
);
2392 TextBoxBase::SelectAll ()
2394 SelectWithError (0, buffer
->len
, NULL
);
2398 TextBoxBase::CanUndo ()
2400 return !undo
->IsEmpty ();
2404 TextBoxBase::CanRedo ()
2406 return !redo
->IsEmpty ();
2410 TextBoxBase::Undo ()
2412 TextBoxUndoActionReplace
*replace
;
2413 TextBoxUndoActionInsert
*insert
;
2414 TextBoxUndoActionDelete
*dele
;
2415 TextBoxUndoAction
*action
;
2418 if (undo
->IsEmpty ())
2421 action
= undo
->Pop ();
2422 redo
->Push (action
);
2424 switch (action
->type
) {
2425 case TextBoxUndoActionTypeInsert
:
2426 insert
= (TextBoxUndoActionInsert
*) action
;
2428 buffer
->Cut (insert
->start
, insert
->length
);
2429 anchor
= action
->selection_anchor
;
2430 cursor
= action
->selection_cursor
;
2432 case TextBoxUndoActionTypeDelete
:
2433 dele
= (TextBoxUndoActionDelete
*) action
;
2435 buffer
->Insert (dele
->start
, dele
->text
, dele
->length
);
2436 anchor
= action
->selection_anchor
;
2437 cursor
= action
->selection_cursor
;
2439 case TextBoxUndoActionTypeReplace
:
2440 replace
= (TextBoxUndoActionReplace
*) action
;
2442 buffer
->Cut (replace
->start
, replace
->inlen
);
2443 buffer
->Insert (replace
->start
, replace
->deleted
, replace
->length
);
2444 anchor
= action
->selection_anchor
;
2445 cursor
= action
->selection_cursor
;
2450 SetSelectionStart (MIN (anchor
, cursor
));
2451 SetSelectionLength (abs (cursor
- anchor
));
2452 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2453 selection_anchor
= anchor
;
2454 selection_cursor
= cursor
;
2461 TextBoxBase::Redo ()
2463 TextBoxUndoActionReplace
*replace
;
2464 TextBoxUndoActionInsert
*insert
;
2465 TextBoxUndoActionDelete
*dele
;
2466 TextBoxUndoAction
*action
;
2469 if (redo
->IsEmpty ())
2472 action
= redo
->Pop ();
2473 undo
->Push (action
);
2475 switch (action
->type
) {
2476 case TextBoxUndoActionTypeInsert
:
2477 insert
= (TextBoxUndoActionInsert
*) action
;
2479 buffer
->Insert (insert
->start
, insert
->buffer
->text
, insert
->buffer
->len
);
2480 anchor
= cursor
= insert
->start
+ insert
->buffer
->len
;
2482 case TextBoxUndoActionTypeDelete
:
2483 dele
= (TextBoxUndoActionDelete
*) action
;
2485 buffer
->Cut (dele
->start
, dele
->length
);
2486 anchor
= cursor
= dele
->start
;
2488 case TextBoxUndoActionTypeReplace
:
2489 replace
= (TextBoxUndoActionReplace
*) action
;
2491 buffer
->Cut (replace
->start
, replace
->length
);
2492 buffer
->Insert (replace
->start
, replace
->inserted
, replace
->inlen
);
2493 anchor
= cursor
= replace
->start
+ replace
->inlen
;
2498 SetSelectionStart (MIN (anchor
, cursor
));
2499 SetSelectionLength (abs (cursor
- anchor
));
2500 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2501 selection_anchor
= anchor
;
2502 selection_cursor
= cursor
;
2510 // TextBoxDynamicPropertyValueProvider
2513 class TextBoxDynamicPropertyValueProvider
: public PropertyValueProvider
{
2514 Value
*selection_background
;
2515 Value
*selection_foreground
;
2518 TextBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : PropertyValueProvider (obj
, precedence
)
2520 selection_background
= NULL
;
2521 selection_foreground
= NULL
;
2524 virtual ~TextBoxDynamicPropertyValueProvider ()
2526 delete selection_background
;
2527 delete selection_foreground
;
2530 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2532 if (property
->GetId () == TextBox::SelectionBackgroundProperty
) {
2533 return selection_background
;
2534 } else if (property
->GetId () == TextBox::SelectionForegroundProperty
) {
2535 return selection_foreground
;
2541 void InitializeSelectionBrushes ()
2543 if (!selection_background
)
2544 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2546 if (!selection_foreground
)
2547 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2558 providers
[PropertyPrecedence_DynamicValue
] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2560 Initialize (Type::TEXTBOX
, "System.Windows.Controls.TextBox");
2561 events_mask
= TEXT_CHANGED
| SELECTION_CHANGED
;
2566 TextBox::EmitSelectionChanged ()
2568 Emit (TextBox::SelectionChangedEvent
, new RoutedEventArgs ());
2572 TextBox::EmitTextChanged ()
2574 Emit (TextBox::TextChangedEvent
, new TextChangedEventArgs ());
2578 TextBox::SyncSelectedText ()
2580 if (selection_cursor
!= selection_anchor
) {
2581 int length
= abs (selection_cursor
- selection_anchor
);
2582 int start
= MIN (selection_anchor
, selection_cursor
);
2585 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2588 SetValue (TextBox::SelectedTextProperty
, Value (text
, true));
2592 SetValue (TextBox::SelectedTextProperty
, Value (""));
2598 TextBox::SyncText ()
2600 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
2603 SetValue (TextBox::TextProperty
, Value (text
, true));
2608 TextBox::ClearFontSource ()
2610 ClearValue (TextBox::FontSourceProperty
);
2614 TextBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2616 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2617 DependencyProperty
*prop
;
2620 if (args
->GetId () == TextBox::AcceptsReturnProperty
) {
2621 // update accepts_return state
2622 accepts_return
= args
->GetNewValue ()->AsBool ();
2623 } else if (args
->GetId () == TextBox::CaretBrushProperty
) {
2624 // FIXME: if we want to be perfect, we could invalidate the
2625 // blinking cursor rect if it is active... but is it that
2627 } else if (args
->GetId () == TextBox::FontSourceProperty
) {
2628 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
2629 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2631 g_free (font_source
);
2633 if (source
&& source
->stream
) {
2634 font_source
= g_strdup_printf ("font-source://%p.%p", this, source
);
2635 manager
->AddResource (font_source
, source
->stream
);
2639 } else if (args
->GetId () == TextBox::IsReadOnlyProperty
) {
2640 // update is_read_only state
2641 is_read_only
= args
->GetNewValue ()->AsBool ();
2646 gtk_im_context_focus_out (im_ctx
);
2648 gtk_im_context_focus_in (im_ctx
);
2651 } else if (args
->GetId () == TextBox::MaxLengthProperty
) {
2652 // update max_length state
2653 max_length
= args
->GetNewValue ()->AsInt32 ();
2654 } else if (args
->GetId () == TextBox::SelectedTextProperty
) {
2656 Value
*value
= args
->GetNewValue ();
2657 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2658 TextBoxUndoAction
*action
= NULL
;
2662 length
= abs (selection_cursor
- selection_anchor
);
2663 start
= MIN (selection_anchor
, selection_cursor
);
2665 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2667 // replace the currently selected text
2668 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
2670 buffer
->Replace (start
, length
, text
, textlen
);
2671 } else if (textlen
> 0) {
2672 // insert the text at the cursor
2673 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
2675 buffer
->Insert (start
, text
, textlen
);
2680 if (action
!= NULL
) {
2681 emit
|= TEXT_CHANGED
;
2682 undo
->Push (action
);
2685 ClearSelection (start
+ textlen
);
2691 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2694 } else if (args
->GetId () == TextBox::SelectionStartProperty
) {
2695 length
= abs (selection_cursor
- selection_anchor
);
2696 start
= args
->GetNewValue ()->AsInt32 ();
2698 if (start
> buffer
->len
) {
2699 // clamp the selection start offset to a valid value
2700 SetSelectionStart (buffer
->len
);
2704 if (start
+ length
> buffer
->len
) {
2705 // clamp the selection length to a valid value
2707 length
= buffer
->len
- start
;
2708 SetSelectionLength (length
);
2712 // SelectionStartProperty is marked as AlwaysChange -
2713 // if the value hasn't actually changed, then we do
2714 // not want to emit the TextBoxModelChanged event.
2715 if (selection_anchor
!= start
) {
2716 changed
= TextBoxModelChangedSelection
;
2717 have_offset
= false;
2720 // When set programatically, anchor is always the
2721 // start and cursor is always the end.
2722 selection_cursor
= start
+ length
;
2723 selection_anchor
= start
;
2725 emit
|= SELECTION_CHANGED
;
2728 } else if (args
->GetId () == TextBox::SelectionLengthProperty
) {
2729 start
= MIN (selection_anchor
, selection_cursor
);
2730 length
= args
->GetNewValue ()->AsInt32 ();
2732 if (start
+ length
> buffer
->len
) {
2733 // clamp the selection length to a valid value
2734 length
= buffer
->len
- start
;
2735 SetSelectionLength (length
);
2739 // SelectionLengthProperty is marked as AlwaysChange -
2740 // if the value hasn't actually changed, then we do
2741 // not want to emit the TextBoxModelChanged event.
2742 if (selection_cursor
!= start
+ length
) {
2743 changed
= TextBoxModelChangedSelection
;
2744 have_offset
= false;
2747 // When set programatically, anchor is always the
2748 // start and cursor is always the end.
2749 selection_cursor
= start
+ length
;
2750 selection_anchor
= start
;
2752 emit
|= SELECTION_CHANGED
;
2755 } else if (args
->GetId () == TextBox::SelectionBackgroundProperty
) {
2756 changed
= TextBoxModelChangedBrush
;
2757 } else if (args
->GetId () == TextBox::SelectionForegroundProperty
) {
2758 changed
= TextBoxModelChangedBrush
;
2759 } else if (args
->GetId () == TextBox::TextProperty
) {
2761 Value
*value
= args
->GetNewValue ();
2762 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2763 TextBoxUndoAction
*action
;
2767 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2768 if (buffer
->len
> 0) {
2769 // replace the current text
2770 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
2772 buffer
->Replace (0, buffer
->len
, text
, textlen
);
2775 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
2777 buffer
->Insert (0, text
, textlen
);
2780 undo
->Push (action
);
2784 emit
|= TEXT_CHANGED
;
2788 SyncAndEmit (value
&& !value
->GetIsNull ());
2790 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2794 changed
= TextBoxModelChangedText
;
2795 } else if (args
->GetId () == TextBox::TextAlignmentProperty
) {
2796 changed
= TextBoxModelChangedTextAlignment
;
2797 } else if (args
->GetId () == TextBox::TextWrappingProperty
) {
2798 changed
= TextBoxModelChangedTextWrapping
;
2799 } else if (args
->GetId () == TextBox::HorizontalScrollBarVisibilityProperty
) {
2800 // XXX more crap because these aren't templatebound.
2801 if (contentElement
) {
2802 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2803 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2807 } else if (args
->GetId () == TextBox::VerticalScrollBarVisibilityProperty
) {
2808 // XXX more crap because these aren't templatebound.
2809 if (contentElement
) {
2810 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2811 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2817 if (changed
!= TextBoxModelChangedNothing
)
2818 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2820 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
2821 TextBoxBase::OnPropertyChanged (args
, error
);
2825 NotifyListenersOfPropertyChange (args
, error
);
2829 TextBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2831 if (prop
&& (prop
->GetId () == TextBox::SelectionBackgroundProperty
||
2832 prop
->GetId () == TextBox::SelectionForegroundProperty
)) {
2833 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2837 if (prop
->GetOwnerType () != Type::TEXTBOX
)
2838 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2842 TextBox::OnApplyTemplate ()
2844 DependencyProperty
*prop
;
2846 TextBoxBase::OnApplyTemplate ();
2848 if (!contentElement
)
2851 // XXX LAME these should be template bindings in the textbox template.
2852 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2853 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2855 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2856 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2865 // PasswordBoxDynamicPropertyValueProvider
2868 class PasswordBoxDynamicPropertyValueProvider
: public PropertyValueProvider
{
2869 Value
*selection_background
;
2870 Value
*selection_foreground
;
2873 PasswordBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : PropertyValueProvider (obj
, precedence
)
2875 selection_background
= NULL
;
2876 selection_foreground
= NULL
;
2879 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2881 delete selection_background
;
2882 delete selection_foreground
;
2885 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2887 if (property
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
2888 return selection_background
;
2889 } else if (property
->GetId () == PasswordBox::SelectionForegroundProperty
) {
2890 return selection_foreground
;
2896 void InitializeSelectionBrushes ()
2898 if (!selection_background
)
2899 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2901 if (!selection_foreground
)
2902 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2911 PasswordBox::PasswordBox ()
2913 providers
[PropertyPrecedence_DynamicValue
] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2915 Initialize (Type::PASSWORDBOX
, "System.Windows.Controls.PasswordBox");
2916 events_mask
= TEXT_CHANGED
;
2919 display
= g_string_new ("");
2922 PasswordBox::~PasswordBox ()
2924 g_string_free (display
, true);
2928 PasswordBox::CursorDown (int cursor
, bool page
)
2930 return GetBuffer ()->len
;
2934 PasswordBox::CursorUp (int cursor
, bool page
)
2940 PasswordBox::CursorLineBegin (int cursor
)
2946 PasswordBox::CursorLineEnd (int cursor
, bool include
)
2948 return GetBuffer ()->len
;
2952 PasswordBox::CursorNextWord (int cursor
)
2954 return GetBuffer ()->len
;
2958 PasswordBox::CursorPrevWord (int cursor
)
2964 PasswordBox::EmitTextChanged ()
2966 Emit (PasswordBox::PasswordChangedEvent
, new RoutedEventArgs ());
2970 PasswordBox::SyncSelectedText ()
2972 if (selection_cursor
!= selection_anchor
) {
2973 int length
= abs (selection_cursor
- selection_anchor
);
2974 int start
= MIN (selection_anchor
, selection_cursor
);
2977 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2980 SetValue (PasswordBox::SelectedTextProperty
, Value (text
, true));
2984 SetValue (PasswordBox::SelectedTextProperty
, Value (""));
2990 PasswordBox::SyncDisplayText ()
2992 gunichar c
= GetPasswordChar ();
2994 g_string_truncate (display
, 0);
2996 for (int i
= 0; i
< buffer
->len
; i
++)
2997 g_string_append_unichar (display
, c
);
3001 PasswordBox::SyncText ()
3003 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
3006 SetValue (PasswordBox::PasswordProperty
, Value (text
, true));
3013 PasswordBox::GetDisplayText ()
3015 return display
->str
;
3019 PasswordBox::ClearFontSource ()
3021 ClearValue (PasswordBox::FontSourceProperty
);
3025 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
3027 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
3030 if (args
->GetId () == PasswordBox::CaretBrushProperty
) {
3031 // FIXME: if we want to be perfect, we could invalidate the
3032 // blinking cursor rect if it is active... but is it that
3034 } else if (args
->GetId () == PasswordBox::FontSourceProperty
) {
3035 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
3036 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
3038 g_free (font_source
);
3040 if (source
&& source
->stream
) {
3041 font_source
= g_strdup_printf ("font-source://%p.%p", this, source
);
3042 manager
->AddResource (font_source
, source
->stream
);
3046 } else if (args
->GetId () == PasswordBox::MaxLengthProperty
) {
3047 // update max_length state
3048 max_length
= args
->GetNewValue()->AsInt32 ();
3049 } else if (args
->GetId () == PasswordBox::PasswordCharProperty
) {
3050 changed
= TextBoxModelChangedText
;
3051 } else if (args
->GetId () == PasswordBox::PasswordProperty
) {
3053 Value
*value
= args
->GetNewValue ();
3054 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
3055 TextBoxUndoAction
*action
;
3059 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
3060 if (buffer
->len
> 0) {
3061 // replace the current text
3062 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
3064 buffer
->Replace (0, buffer
->len
, text
, textlen
);
3067 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
3069 buffer
->Insert (0, text
, textlen
);
3072 undo
->Push (action
);
3076 emit
|= TEXT_CHANGED
;
3085 changed
= TextBoxModelChangedText
;
3086 } else if (args
->GetId () == PasswordBox::SelectedTextProperty
) {
3088 Value
*value
= args
->GetNewValue ();
3089 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
3090 TextBoxUndoAction
*action
= NULL
;
3094 length
= abs (selection_cursor
- selection_anchor
);
3095 start
= MIN (selection_anchor
, selection_cursor
);
3097 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
3099 // replace the currently selected text
3100 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
3102 buffer
->Replace (start
, length
, text
, textlen
);
3103 } else if (textlen
> 0) {
3104 // insert the text at the cursor
3105 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
3107 buffer
->Insert (start
, text
, textlen
);
3112 if (action
!= NULL
) {
3113 undo
->Push (action
);
3116 ClearSelection (start
+ textlen
);
3117 emit
|= TEXT_CHANGED
;
3125 } else if (args
->GetId () == PasswordBox::SelectionStartProperty
) {
3126 length
= abs (selection_cursor
- selection_anchor
);
3127 start
= args
->GetNewValue ()->AsInt32 ();
3129 if (start
> buffer
->len
) {
3130 // clamp the selection start offset to a valid value
3131 SetSelectionStart (buffer
->len
);
3135 if (start
+ length
> buffer
->len
) {
3136 // clamp the selection length to a valid value
3138 length
= buffer
->len
- start
;
3139 SetSelectionLength (length
);
3143 // SelectionStartProperty is marked as AlwaysChange -
3144 // if the value hasn't actually changed, then we do
3145 // not want to emit the TextBoxModelChanged event.
3146 if (selection_anchor
!= start
) {
3147 changed
= TextBoxModelChangedSelection
;
3148 have_offset
= false;
3151 // When set programatically, anchor is always the
3152 // start and cursor is always the end.
3153 selection_cursor
= start
+ length
;
3154 selection_anchor
= start
;
3156 emit
|= SELECTION_CHANGED
;
3159 } else if (args
->GetId () == PasswordBox::SelectionLengthProperty
) {
3160 start
= MIN (selection_anchor
, selection_cursor
);
3161 length
= args
->GetNewValue ()->AsInt32 ();
3163 if (start
+ length
> buffer
->len
) {
3164 // clamp the selection length to a valid value
3165 length
= buffer
->len
- start
;
3166 SetSelectionLength (length
);
3170 // SelectionLengthProperty is marked as AlwaysChange -
3171 // if the value hasn't actually changed, then we do
3172 // not want to emit the TextBoxModelChanged event.
3173 if (selection_cursor
!= start
+ length
) {
3174 changed
= TextBoxModelChangedSelection
;
3175 have_offset
= false;
3178 // When set programatically, anchor is always the
3179 // start and cursor is always the end.
3180 selection_cursor
= start
+ length
;
3181 selection_anchor
= start
;
3183 emit
|= SELECTION_CHANGED
;
3186 } else if (args
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
3187 changed
= TextBoxModelChangedBrush
;
3188 } else if (args
->GetId () == PasswordBox::SelectionForegroundProperty
) {
3189 changed
= TextBoxModelChangedBrush
;
3192 if (changed
!= TextBoxModelChangedNothing
)
3193 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
3195 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
3196 TextBoxBase::OnPropertyChanged (args
, error
);
3200 NotifyListenersOfPropertyChange (args
, error
);
3204 PasswordBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
3206 if (prop
&& (prop
->GetId () == PasswordBox::SelectionBackgroundProperty
||
3207 prop
->GetId () == PasswordBox::SelectionForegroundProperty
)) {
3208 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
3212 if (prop
->GetOwnerType () != Type::TEXTBOX
)
3213 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
3221 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3222 #define CURSOR_BLINK_ON_MULTIPLIER 2
3223 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3224 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3225 #define CURSOR_BLINK_DIVIDER 3
3227 TextBoxView::TextBoxView ()
3229 SetObjectType (Type::TEXTBOXVIEW
);
3231 AddHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3232 AddHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3234 SetCursor (MouseCursorIBeam
);
3236 cursor
= Rect (0, 0, 0, 0);
3237 layout
= new TextLayout ();
3238 selection_changed
= false;
3239 had_selected_text
= false;
3240 cursor_visible
= false;
3246 TextBoxView::~TextBoxView ()
3248 RemoveHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3249 RemoveHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3252 textbox
->RemoveHandler (TextBox::ModelChangedEvent
, TextBoxView::model_changed
, this);
3253 textbox
->view
= NULL
;
3256 DisconnectBlinkTimeout ();
3262 TextBoxView::GetLineFromY (double y
, int *index
)
3264 return layout
->GetLineFromY (Point (), y
, index
);
3268 TextBoxView::GetLineFromIndex (int index
)
3270 return layout
->GetLineFromIndex (index
);
3274 TextBoxView::GetCursorFromXY (double x
, double y
)
3276 return layout
->GetCursorFromXY (Point (), x
, y
);
3280 TextBoxView::blink (void *user_data
)
3282 return ((TextBoxView
*) user_data
)->Blink ();
3286 GetCursorBlinkTimeout (TextBoxView
*view
)
3288 GtkSettings
*settings
;
3295 if (!(surface
= view
->GetSurface ()))
3296 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3298 if (!(window
= surface
->GetWindow ()))
3299 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3301 if (!(widget
= window
->GetGdkWindow ()))
3302 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3304 if (!(screen
= gdk_drawable_get_screen ((GdkDrawable
*) widget
)))
3305 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3307 if (!(settings
= gtk_settings_get_for_screen (screen
)))
3308 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3310 g_object_get (settings
, "gtk-cursor-blink-time", &timeout
, NULL
);
3316 TextBoxView::ConnectBlinkTimeout (guint multiplier
)
3318 guint timeout
= GetCursorBlinkTimeout (this) * multiplier
/ CURSOR_BLINK_DIVIDER
;
3319 Surface
*surface
= GetSurface ();
3320 TimeManager
*manager
;
3322 if (!surface
|| !(manager
= surface
->GetTimeManager ()))
3325 blink_timeout
= manager
->AddTimeout (MOON_PRIORITY_DEFAULT
, timeout
, TextBoxView::blink
, this);
3329 TextBoxView::DisconnectBlinkTimeout ()
3331 TimeManager
*manager
;
3334 if (blink_timeout
!= 0) {
3335 if (!(surface
= GetSurface ()) || !(manager
= surface
->GetTimeManager ()))
3338 manager
->RemoveTimeout (blink_timeout
);
3344 TextBoxView::Blink ()
3348 SetCurrentDeployment (true);
3350 if (cursor_visible
) {
3351 multiplier
= CURSOR_BLINK_OFF_MULTIPLIER
;
3354 multiplier
= CURSOR_BLINK_ON_MULTIPLIER
;
3358 ConnectBlinkTimeout (multiplier
);
3364 TextBoxView::DelayCursorBlink ()
3366 DisconnectBlinkTimeout ();
3367 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER
);
3368 UpdateCursor (true);
3373 TextBoxView::BeginCursorBlink ()
3375 if (blink_timeout
== 0) {
3376 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER
);
3377 UpdateCursor (true);
3383 TextBoxView::EndCursorBlink ()
3385 DisconnectBlinkTimeout ();
3392 TextBoxView::ResetCursorBlink (bool delay
)
3394 if (textbox
->IsFocused () && !textbox
->HasSelectedText ()) {
3395 // cursor is blinkable... proceed with blinkage
3397 DelayCursorBlink ();
3399 BeginCursorBlink ();
3401 // cursor not blinkable... stop all blinkage
3407 TextBoxView::InvalidateCursor ()
3409 Invalidate (cursor
.Transform (&absolute_xform
));
3413 TextBoxView::ShowCursor ()
3415 cursor_visible
= true;
3416 InvalidateCursor ();
3420 TextBoxView::HideCursor ()
3422 cursor_visible
= false;
3423 InvalidateCursor ();
3427 TextBoxView::UpdateCursor (bool invalidate
)
3429 int cur
= textbox
->GetCursor ();
3433 // invalidate current cursor rect
3434 if (invalidate
&& cursor_visible
)
3435 InvalidateCursor ();
3437 // calculate the new cursor rect
3438 cursor
= layout
->GetCursor (Point (), cur
);
3440 // transform the cursor rect into absolute coordinates for the IM context
3441 rect
= cursor
.Transform (&absolute_xform
);
3442 area
= rect
.ToGdkRectangle ();
3444 gtk_im_context_set_cursor_location (textbox
->im_ctx
, &area
);
3446 textbox
->EmitCursorPositionChanged (cursor
.height
, cursor
.x
, cursor
.y
);
3448 // invalidate the new cursor rect
3449 if (invalidate
&& cursor_visible
)
3450 InvalidateCursor ();
3454 TextBoxView::UpdateText ()
3456 const char *text
= textbox
->GetDisplayText ();
3458 layout
->SetText (text
? text
: "", -1);
3462 TextBoxView::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
3464 *height
= GetActualHeight ();
3465 *width
= GetActualWidth ();
3469 TextBoxView::ComputeActualSize ()
3471 Layout (Size (INFINITY
, INFINITY
));
3474 layout
->GetActualExtents (&actual
.width
, &actual
.height
);
3480 TextBoxView::MeasureOverride (Size availableSize
)
3482 Size desired
= Size ();
3485 Layout (availableSize
);
3487 layout
->GetActualExtents (&desired
.width
, &desired
.height
);
3489 return desired
.Min (availableSize
);
3493 TextBoxView::ArrangeOverride (Size finalSize
)
3495 Size arranged
= Size ();
3500 layout
->GetActualExtents (&arranged
.width
, &arranged
.height
);
3502 arranged
= arranged
.Max (finalSize
);
3508 TextBoxView::Layout (Size constraint
)
3510 layout
->SetMaxWidth (constraint
.width
);
3516 TextBoxView::Paint (cairo_t
*cr
)
3518 layout
->Render (cr
, GetOriginPoint (), Point ());
3520 if (cursor_visible
) {
3521 cairo_antialias_t alias
= cairo_get_antialias (cr
);
3522 Brush
*caret
= textbox
->GetCaretBrush ();
3523 double h
= round (cursor
.height
);
3524 double x
= cursor
.x
;
3525 double y
= cursor
.y
;
3527 // disable antialiasing
3528 cairo_set_antialias (cr
, CAIRO_ANTIALIAS_NONE
);
3530 // snap 'x' to the half-pixel grid (to try and get a sharp 1-pixel-wide line)
3531 cairo_user_to_device (cr
, &x
, &y
);
3532 x
= trunc (x
) + 0.5; y
= trunc (y
);
3533 cairo_device_to_user (cr
, &x
, &y
);
3535 // set the cursor color
3536 caret
->SetupBrush (cr
, cursor
);
3539 cairo_set_line_width (cr
, 1.0);
3540 cairo_move_to (cr
, x
, y
);
3541 cairo_line_to (cr
, x
, y
+ h
);
3546 // restore antialiasing
3547 cairo_set_antialias (cr
, alias
);
3552 TextBoxView::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
3554 TextBoxDynamicPropertyValueProvider
*dynamic
= (TextBoxDynamicPropertyValueProvider
*) textbox
->providers
[PropertyPrecedence_DynamicValue
];
3555 Size renderSize
= GetRenderSize ();
3557 dynamic
->InitializeSelectionBrushes ();
3560 Layout (renderSize
);
3561 UpdateCursor (false);
3565 if (selection_changed
) {
3566 layout
->Select (textbox
->GetSelectionStart (), textbox
->GetSelectionLength ());
3567 selection_changed
= false;
3571 cairo_set_matrix (cr
, &absolute_xform
);
3574 RenderLayoutClip (cr
);
3576 layout
->SetAvailableWidth (renderSize
.width
);
3582 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs
*args
)
3584 switch (args
->changed
) {
3585 case TextBoxModelChangedTextAlignment
:
3586 // text alignment changed, update our layout
3587 if (layout
->SetTextAlignment ((TextAlignment
) args
->property
->GetNewValue()->AsInt32 ()))
3590 case TextBoxModelChangedTextWrapping
:
3591 // text wrapping changed, update our layout
3592 if (layout
->SetTextWrapping ((TextWrapping
) args
->property
->GetNewValue()->AsInt32 ()))
3595 case TextBoxModelChangedSelection
:
3596 if (had_selected_text
|| textbox
->HasSelectedText ()) {
3597 // the selection has changed, update the layout's selection
3598 had_selected_text
= textbox
->HasSelectedText ();
3599 selection_changed
= true;
3600 ResetCursorBlink (false);
3602 // cursor position changed
3603 ResetCursorBlink (true);
3607 case TextBoxModelChangedBrush
:
3608 // a brush has changed, no layout updates needed, we just need to re-render
3610 case TextBoxModelChangedFont
:
3611 // font changed, need to recalculate layout/bounds
3612 layout
->ResetState ();
3615 case TextBoxModelChangedText
:
3616 // the text has changed, need to recalculate layout/bounds
3621 // nothing changed??
3626 InvalidateMeasure ();
3627 UpdateBounds (true);
3634 TextBoxView::model_changed (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3636 ((TextBoxView
*) closure
)->OnModelChanged ((TextBoxModelChangedEventArgs
*) args
);
3640 TextBoxView::OnFocusOut ()
3646 TextBoxView::OnFocusIn ()
3648 ResetCursorBlink (false);
3652 TextBoxView::OnMouseLeftButtonDown (MouseEventArgs
*args
)
3654 // proxy to our parent TextBox control
3655 textbox
->OnMouseLeftButtonDown (args
);
3659 TextBoxView::mouse_left_button_down (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3661 ((TextBoxView
*) closure
)->OnMouseLeftButtonDown ((MouseEventArgs
*) args
);
3665 TextBoxView::OnMouseLeftButtonUp (MouseEventArgs
*args
)
3667 // proxy to our parent TextBox control
3668 textbox
->OnMouseLeftButtonUp (args
);
3672 TextBoxView::mouse_left_button_up (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3674 ((TextBoxView
*) closure
)->OnMouseLeftButtonUp ((MouseEventArgs
*) args
);
3678 TextBoxView::SetTextBox (TextBoxBase
*textbox
)
3680 TextLayoutAttributes
*attrs
;
3682 if (this->textbox
== textbox
)
3685 if (this->textbox
) {
3686 // remove the event handlers from the old textbox
3687 this->textbox
->RemoveHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3690 this->textbox
= textbox
;
3693 textbox
->AddHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3695 // sync our state with the textbox
3696 layout
->SetTextAttributes (new List ());
3697 attrs
= new TextLayoutAttributes ((ITextAttributes
*) textbox
, 0);
3698 layout
->GetTextAttributes ()->Append (attrs
);
3700 layout
->SetTextAlignment (textbox
->GetTextAlignment ());
3701 layout
->SetTextWrapping (textbox
->GetTextWrapping ());
3702 had_selected_text
= textbox
->HasSelectedText ();
3703 selection_changed
= true;
3706 layout
->SetTextAttributes (NULL
);
3707 layout
->SetText (NULL
, -1);
3710 UpdateBounds (true);
3711 InvalidateMeasure ();