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
);
295 class AsyncEventClosure
: public EventObject
{
297 TextBoxBase
*textbox
;
300 AsyncEventClosure (int generation
, TextBoxBase
*textbox
)
302 this->generation
= generation
;
303 this->textbox
= textbox
;
308 virtual ~AsyncEventClosure ()
316 // TextBoxUndoActions
319 enum TextBoxUndoActionType
{
320 TextBoxUndoActionTypeInsert
,
321 TextBoxUndoActionTypeDelete
,
322 TextBoxUndoActionTypeReplace
,
325 class TextBoxUndoAction
: public List::Node
{
327 TextBoxUndoActionType type
;
328 int selection_anchor
;
329 int selection_cursor
;
334 class TextBoxUndoActionInsert
: public TextBoxUndoAction
{
339 TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, gunichar c
);
340 TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, const gunichar
*inserted
, int length
, bool atomic
= false);
341 virtual ~TextBoxUndoActionInsert ();
343 bool Insert (int start
, const gunichar
*text
, int len
);
344 bool Insert (int start
, gunichar c
);
347 class TextBoxUndoActionDelete
: public TextBoxUndoAction
{
351 TextBoxUndoActionDelete (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
);
352 virtual ~TextBoxUndoActionDelete ();
355 class TextBoxUndoActionReplace
: public TextBoxUndoAction
{
361 TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, const gunichar
*inserted
, int inlen
);
362 TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, gunichar c
);
363 virtual ~TextBoxUndoActionReplace ();
366 class TextBoxUndoStack
{
371 TextBoxUndoStack (int max_count
);
372 ~TextBoxUndoStack ();
377 void Push (TextBoxUndoAction
*action
);
378 TextBoxUndoAction
*Peek ();
379 TextBoxUndoAction
*Pop ();
382 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, gunichar c
)
384 this->type
= TextBoxUndoActionTypeInsert
;
385 this->selection_anchor
= selection_anchor
;
386 this->selection_cursor
= selection_cursor
;
390 this->buffer
= new TextBuffer ();
391 this->buffer
->Append (c
);
392 this->growable
= true;
395 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, const gunichar
*inserted
, int length
, bool atomic
)
397 this->type
= TextBoxUndoActionTypeInsert
;
398 this->selection_anchor
= selection_anchor
;
399 this->selection_cursor
= selection_cursor
;
400 this->length
= length
;
403 this->buffer
= new TextBuffer (inserted
, length
);
404 this->growable
= !atomic
;
407 TextBoxUndoActionInsert::~TextBoxUndoActionInsert ()
413 TextBoxUndoActionInsert::Insert (int start
, const gunichar
*text
, int len
)
415 if (!growable
|| start
!= (this->start
+ length
))
418 buffer
->Append (text
, len
);
425 TextBoxUndoActionInsert::Insert (int start
, gunichar c
)
427 if (!growable
|| start
!= (this->start
+ length
))
436 TextBoxUndoActionDelete::TextBoxUndoActionDelete (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
)
438 this->type
= TextBoxUndoActionTypeDelete
;
439 this->selection_anchor
= selection_anchor
;
440 this->selection_cursor
= selection_cursor
;
441 this->length
= length
;
444 this->text
= buffer
->Substring (start
, length
);
447 TextBoxUndoActionDelete::~TextBoxUndoActionDelete ()
452 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, const gunichar
*inserted
, int inlen
)
454 this->type
= TextBoxUndoActionTypeReplace
;
455 this->selection_anchor
= selection_anchor
;
456 this->selection_cursor
= selection_cursor
;
457 this->length
= length
;
460 this->deleted
= buffer
->Substring (start
, length
);
461 this->inserted
= (gunichar
*) g_malloc (UNICODE_LEN (inlen
+ 1));
462 memcpy (this->inserted
, inserted
, UNICODE_LEN (inlen
+ 1));
466 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, gunichar c
)
468 this->type
= TextBoxUndoActionTypeReplace
;
469 this->selection_anchor
= selection_anchor
;
470 this->selection_cursor
= selection_cursor
;
471 this->length
= length
;
474 this->deleted
= buffer
->Substring (start
, length
);
475 this->inserted
= g_new (gunichar
, 2);
476 memcpy (inserted
, &c
, sizeof (gunichar
));
481 TextBoxUndoActionReplace::~TextBoxUndoActionReplace ()
488 TextBoxUndoStack::TextBoxUndoStack (int max_count
)
490 this->max_count
= max_count
;
491 this->list
= new List ();
494 TextBoxUndoStack::~TextBoxUndoStack ()
500 TextBoxUndoStack::IsEmpty ()
502 return list
->IsEmpty ();
506 TextBoxUndoStack::Clear ()
512 TextBoxUndoStack::Push (TextBoxUndoAction
*action
)
514 if (list
->Length () == max_count
) {
515 List::Node
*node
= list
->Last ();
520 list
->Prepend (action
);
524 TextBoxUndoStack::Pop ()
526 List::Node
*node
= list
->First ();
531 return (TextBoxUndoAction
*) node
;
535 TextBoxUndoStack::Peek ()
537 return (TextBoxUndoAction
*) list
->First ();
545 // emit state, also doubles as available event mask
546 #define NOTHING_CHANGED (0)
547 #define SELECTION_CHANGED (1 << 0)
548 #define TEXT_CHANGED (1 << 1)
550 #define CONTROL_MASK GDK_CONTROL_MASK
551 #define SHIFT_MASK GDK_SHIFT_MASK
552 #define ALT_MASK GDK_MOD1_MASK
554 #define IsEOL(c) ((c) == '\r' || (c) == '\n')
557 GetGdkWindow (TextBoxBase
*textbox
)
562 if (!(surface
= textbox
->GetSurface ()))
565 if (!(window
= surface
->GetWindow ()))
568 return window
->GetGdkWindow ();
571 static GtkClipboard
*
572 GetClipboard (TextBoxBase
*textbox
, GdkAtom atom
)
577 if (!(window
= GetGdkWindow (textbox
)))
580 if (!(display
= gdk_drawable_get_display ((GdkDrawable
*) window
)))
583 return gtk_clipboard_get_for_display (display
, atom
);
587 TextBoxBase::Initialize (Type::Kind type
, const char *type_name
)
589 ManagedTypeInfo
*type_info
= new ManagedTypeInfo ("System.Windows", type_name
);
591 SetObjectType (type
);
592 SetDefaultStyleKey (type_info
);
594 AddHandler (UIElement::MouseLeftButtonMultiClickEvent
, TextBoxBase::mouse_left_button_multi_click
, this);
595 AddHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxBase::mouse_left_button_down
, this);
596 AddHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxBase::mouse_left_button_up
, this);
597 AddHandler (UIElement::MouseMoveEvent
, TextBoxBase::mouse_move
, this);
598 AddHandler (UIElement::LostFocusEvent
, TextBoxBase::focus_out
, this);
599 AddHandler (UIElement::GotFocusEvent
, TextBoxBase::focus_in
, this);
601 font
= new TextFontDescription ();
602 font
->SetFamily (GetFontFamily ()->source
);
603 font
->SetStretch (GetFontStretch ()->stretch
);
604 font
->SetWeight (GetFontWeight ()->weight
);
605 font
->SetStyle (GetFontStyle ()->style
);
606 font
->SetSize (GetFontSize ());
608 downloaders
= g_ptr_array_new ();
611 contentElement
= NULL
;
613 im_ctx
= gtk_im_multicontext_new ();
614 gtk_im_context_set_use_preedit (im_ctx
, false);
616 g_signal_connect (im_ctx
, "retrieve-surrounding", G_CALLBACK (TextBoxBase::retrieve_surrounding
), this);
617 g_signal_connect (im_ctx
, "delete-surrounding", G_CALLBACK (TextBoxBase::delete_surrounding
), this);
618 g_signal_connect (im_ctx
, "commit", G_CALLBACK (TextBoxBase::commit
), this);
620 undo
= new TextBoxUndoStack (10);
621 redo
= new TextBoxUndoStack (10);
622 buffer
= new TextBuffer ();
625 emit
= NOTHING_CHANGED
;
628 selection_anchor
= 0;
629 selection_cursor
= 0;
633 accepts_return
= false;
634 need_im_reset
= false;
635 is_read_only
= false;
646 TextBoxBase::~TextBoxBase ()
648 RemoveHandler (UIElement::MouseLeftButtonMultiClickEvent
, TextBoxBase::mouse_left_button_multi_click
, this);
649 RemoveHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxBase::mouse_left_button_down
, this);
650 RemoveHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxBase::mouse_left_button_up
, this);
651 RemoveHandler (UIElement::MouseMoveEvent
, TextBoxBase::mouse_move
, this);
652 RemoveHandler (UIElement::LostFocusEvent
, TextBoxBase::focus_out
, this);
653 RemoveHandler (UIElement::GotFocusEvent
, TextBoxBase::focus_in
, this);
656 g_object_unref (im_ctx
);
658 CleanupDownloaders ();
659 g_ptr_array_free (downloaders
, true);
660 g_free (font_source
);
669 TextBoxBase::SetSurface (Surface
*surface
)
671 Control::SetSurface (surface
);
674 gtk_im_context_set_client_window (im_ctx
, GetGdkWindow (this));
678 TextBoxBase::CleanupDownloaders ()
680 Downloader
*downloader
;
683 for (i
= 0; i
< downloaders
->len
; i
++) {
684 downloader
= (Downloader
*) downloaders
->pdata
[i
];
685 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
686 downloader
->Abort ();
687 downloader
->unref ();
690 g_ptr_array_set_size (downloaders
, 0);
694 TextBoxBase::GetCursorOffset ()
696 if (!have_offset
&& view
) {
697 cursor_offset
= view
->GetCursor ().x
;
701 return cursor_offset
;
705 TextBoxBase::CursorDown (int cursor
, bool page
)
707 double y
= view
->GetCursor ().y
;
708 double x
= GetCursorOffset ();
709 TextLayoutLine
*line
;
714 if (!(line
= view
->GetLineFromY (y
, &index
)))
718 // calculate the number of lines to skip over
719 n
= GetActualHeight () / line
->height
;
724 if (index
+ n
>= view
->GetLineCount ()) {
725 // go to the end of the last line
726 line
= view
->GetLineFromIndex (view
->GetLineCount () - 1);
728 for (cur
= line
->offset
, i
= 0; i
< line
->runs
->len
; i
++) {
729 run
= (TextLayoutRun
*) line
->runs
->pdata
[i
];
738 line
= view
->GetLineFromIndex (index
+ n
);
740 return line
->GetCursorFromX (Point (), x
);
744 TextBoxBase::CursorUp (int cursor
, bool page
)
746 double y
= view
->GetCursor ().y
;
747 double x
= GetCursorOffset ();
748 TextLayoutLine
*line
;
751 if (!(line
= view
->GetLineFromY (y
, &index
)))
755 // calculate the number of lines to skip over
756 n
= GetActualHeight () / line
->height
;
762 // go to the beginning of the first line
767 line
= view
->GetLineFromIndex (index
- n
);
769 return line
->GetCursorFromX (Point (), x
);
776 CharClassAlphaNumeric
779 static inline CharClass
780 char_class (gunichar c
)
782 if (g_unichar_isspace (c
))
783 return CharClassWhitespace
;
785 if (g_unichar_isalnum (c
))
786 return CharClassAlphaNumeric
;
788 return CharClassUnknown
;
792 is_start_of_word (TextBuffer
*buffer
, int index
)
794 // A 'word' starts with an AlphaNumeric immediately preceeded by lwsp
795 if (index
> 0 && !g_unichar_isspace (buffer
->text
[index
- 1]))
798 return g_unichar_isalnum (buffer
->text
[index
]);
803 TextBoxBase::CursorNextWord (int cursor
)
807 // find the end of the current line
808 cr
= CursorLineEnd (cursor
);
809 if (buffer
->text
[cr
] == '\r' && buffer
->text
[cr
+ 1] == '\n')
814 // if the cursor is at the end of the line, return the starting offset of the next line
815 if (cursor
== cr
|| cursor
== lf
) {
816 if (lf
< buffer
->len
)
823 CharClass cc
= char_class (buffer
->text
[cursor
]);
826 // skip over the word, punctuation, or run of whitespace
827 while (i
< cr
&& char_class (buffer
->text
[i
]) == cc
)
830 // skip any whitespace after the word/punct
831 while (i
< cr
&& g_unichar_isspace (buffer
->text
[i
]))
836 // skip to the end of the current word
837 while (i
< cr
&& !g_unichar_isspace (buffer
->text
[i
]))
840 // skip any whitespace after the word
841 while (i
< cr
&& g_unichar_isspace (buffer
->text
[i
]))
844 // find the start of the next word
845 while (i
< cr
&& !is_start_of_word (buffer
, i
))
853 TextBoxBase::CursorPrevWord (int cursor
)
855 int begin
, i
, cr
, lf
;
857 // find the beginning of the current line
858 lf
= CursorLineBegin (cursor
) - 1;
860 if (lf
> 0 && buffer
->text
[lf
] == '\n' && buffer
->text
[lf
- 1] == '\r')
865 // if the cursor is at the beginning of the line, return the end of the prev line
866 if (cursor
- 1 == lf
) {
874 CharClass cc
= char_class (buffer
->text
[cursor
- 1]);
878 // skip over the word, punctuation, or run of whitespace
879 while (i
> begin
&& char_class (buffer
->text
[i
- 1]) == cc
)
882 // if the cursor was at whitespace, skip back a word too
883 if (cc
== CharClassWhitespace
&& i
> begin
) {
884 cc
= char_class (buffer
->text
[i
- 1]);
885 while (i
> begin
&& char_class (buffer
->text
[i
- 1]) == cc
)
892 if (cursor
< buffer
->len
) {
893 // skip to the beginning of this word
894 while (i
> begin
&& !g_unichar_isspace (buffer
->text
[i
- 1]))
897 if (i
< cursor
&& is_start_of_word (buffer
, i
))
901 // skip to the start of the lwsp
902 while (i
> begin
&& g_unichar_isspace (buffer
->text
[i
- 1]))
908 // skip to the beginning of the word
909 while (i
> begin
&& !is_start_of_word (buffer
, i
))
917 TextBoxBase::CursorLineBegin (int cursor
)
921 // find the beginning of the line
922 while (cur
> 0 && !IsEOL (buffer
->text
[cur
- 1]))
929 TextBoxBase::CursorLineEnd (int cursor
, bool include
)
933 // find the end of the line
934 while (cur
< buffer
->len
&& !IsEOL (buffer
->text
[cur
]))
937 if (include
&& cur
< buffer
->len
) {
938 if (buffer
->text
[cur
] == '\r' && buffer
->text
[cur
+ 1] == '\n')
948 TextBoxBase::KeyPressBackSpace (GdkModifierType modifiers
)
950 int anchor
= selection_anchor
;
951 int cursor
= selection_cursor
;
952 TextBoxUndoAction
*action
;
953 int start
= 0, length
= 0;
954 bool handled
= false;
956 if ((modifiers
& (ALT_MASK
| SHIFT_MASK
)) != 0)
959 if (cursor
!= anchor
) {
960 // BackSpace w/ active selection: delete the selected text
961 length
= abs (cursor
- anchor
);
962 start
= MIN (anchor
, cursor
);
963 } else if ((modifiers
& CONTROL_MASK
) != 0) {
964 // Ctrl+BackSpace: delete the word ending at the cursor
965 start
= CursorPrevWord (cursor
);
966 length
= cursor
- start
;
967 } else if (cursor
> 0) {
968 // BackSpace: delete the char before the cursor position
969 if (cursor
>= 2 && buffer
->text
[cursor
- 1] == '\n' && buffer
->text
[cursor
- 2] == '\r') {
979 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
983 buffer
->Cut (start
, length
);
984 emit
|= TEXT_CHANGED
;
990 // check to see if selection has changed
991 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
992 SetSelectionStart (MIN (anchor
, cursor
));
993 SetSelectionLength (abs (cursor
- anchor
));
994 selection_anchor
= anchor
;
995 selection_cursor
= cursor
;
996 emit
|= SELECTION_CHANGED
;
1004 TextBoxBase::KeyPressDelete (GdkModifierType modifiers
)
1006 int anchor
= selection_anchor
;
1007 int cursor
= selection_cursor
;
1008 TextBoxUndoAction
*action
;
1009 int start
= 0, length
= 0;
1010 bool handled
= false;
1012 if ((modifiers
& (ALT_MASK
| SHIFT_MASK
)) != 0)
1015 if (cursor
!= anchor
) {
1016 // Delete w/ active selection: delete the selected text
1017 length
= abs (cursor
- anchor
);
1018 start
= MIN (anchor
, cursor
);
1019 } else if ((modifiers
& CONTROL_MASK
) != 0) {
1020 // Ctrl+Delete: delete the word starting at the cursor
1021 length
= CursorNextWord (cursor
) - cursor
;
1023 } else if (cursor
< buffer
->len
) {
1024 // Delete: delete the char after the cursor position
1025 if (buffer
->text
[cursor
] == '\r' && buffer
->text
[cursor
+ 1] == '\n')
1034 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
1035 undo
->Push (action
);
1038 buffer
->Cut (start
, length
);
1039 emit
|= TEXT_CHANGED
;
1043 // check to see if selection has changed
1044 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1045 SetSelectionStart (MIN (anchor
, cursor
));
1046 SetSelectionLength (abs (cursor
- anchor
));
1047 selection_anchor
= anchor
;
1048 selection_cursor
= cursor
;
1049 emit
|= SELECTION_CHANGED
;
1057 TextBoxBase::KeyPressPageDown (GdkModifierType modifiers
)
1059 int anchor
= selection_anchor
;
1060 int cursor
= selection_cursor
;
1061 bool handled
= false;
1064 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1067 // move the cursor down one page from its current position
1068 cursor
= CursorDown (cursor
, true);
1071 if ((modifiers
& SHIFT_MASK
) == 0) {
1072 // clobber the selection
1076 // check to see if selection has changed
1077 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1078 SetSelectionStart (MIN (anchor
, cursor
));
1079 SetSelectionLength (abs (cursor
- anchor
));
1080 selection_anchor
= anchor
;
1081 selection_cursor
= cursor
;
1082 emit
|= SELECTION_CHANGED
;
1091 TextBoxBase::KeyPressPageUp (GdkModifierType modifiers
)
1093 int anchor
= selection_anchor
;
1094 int cursor
= selection_cursor
;
1095 bool handled
= false;
1098 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1101 // move the cursor up one page from its current position
1102 cursor
= CursorUp (cursor
, true);
1105 if ((modifiers
& SHIFT_MASK
) == 0) {
1106 // clobber the selection
1110 // check to see if selection has changed
1111 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1112 SetSelectionStart (MIN (anchor
, cursor
));
1113 SetSelectionLength (abs (cursor
- anchor
));
1114 selection_anchor
= anchor
;
1115 selection_cursor
= cursor
;
1116 emit
|= SELECTION_CHANGED
;
1125 TextBoxBase::KeyPressDown (GdkModifierType modifiers
)
1127 int anchor
= selection_anchor
;
1128 int cursor
= selection_cursor
;
1129 bool handled
= false;
1132 if (!accepts_return
|| (modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1135 // move the cursor down by one line from its current position
1136 cursor
= CursorDown (cursor
, false);
1139 if ((modifiers
& SHIFT_MASK
) == 0) {
1140 // clobber the selection
1144 // check to see if selection has changed
1145 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1146 SetSelectionStart (MIN (anchor
, cursor
));
1147 SetSelectionLength (abs (cursor
- anchor
));
1148 selection_anchor
= anchor
;
1149 selection_cursor
= cursor
;
1150 emit
|= SELECTION_CHANGED
;
1159 TextBoxBase::KeyPressUp (GdkModifierType modifiers
)
1161 int anchor
= selection_anchor
;
1162 int cursor
= selection_cursor
;
1163 bool handled
= false;
1166 if (!accepts_return
|| (modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1169 // move the cursor up by one line from its current position
1170 cursor
= CursorUp (cursor
, false);
1173 if ((modifiers
& SHIFT_MASK
) == 0) {
1174 // clobber the selection
1178 // check to see if selection has changed
1179 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1180 SetSelectionStart (MIN (anchor
, cursor
));
1181 SetSelectionLength (abs (cursor
- anchor
));
1182 selection_anchor
= anchor
;
1183 selection_cursor
= cursor
;
1184 emit
|= SELECTION_CHANGED
;
1193 TextBoxBase::KeyPressHome (GdkModifierType modifiers
)
1195 int anchor
= selection_anchor
;
1196 int cursor
= selection_cursor
;
1197 bool handled
= false;
1199 if ((modifiers
& ALT_MASK
) != 0)
1202 if ((modifiers
& CONTROL_MASK
) != 0) {
1203 // move the cursor to the beginning of the buffer
1206 // move the cursor to the beginning of the line
1207 cursor
= CursorLineBegin (cursor
);
1210 if ((modifiers
& SHIFT_MASK
) == 0) {
1211 // clobber the selection
1215 // check to see if selection has changed
1216 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1217 SetSelectionStart (MIN (anchor
, cursor
));
1218 SetSelectionLength (abs (cursor
- anchor
));
1219 selection_anchor
= anchor
;
1220 selection_cursor
= cursor
;
1221 emit
|= SELECTION_CHANGED
;
1222 have_offset
= false;
1230 TextBoxBase::KeyPressEnd (GdkModifierType modifiers
)
1232 int anchor
= selection_anchor
;
1233 int cursor
= selection_cursor
;
1234 bool handled
= false;
1236 if ((modifiers
& ALT_MASK
) != 0)
1239 if ((modifiers
& CONTROL_MASK
) != 0) {
1240 // move the cursor to the end of the buffer
1241 cursor
= buffer
->len
;
1243 // move the cursor to the end of the line
1244 cursor
= CursorLineEnd (cursor
);
1247 if ((modifiers
& SHIFT_MASK
) == 0) {
1248 // clobber the selection
1252 // check to see if selection has changed
1253 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1254 SetSelectionStart (MIN (anchor
, cursor
));
1255 SetSelectionLength (abs (cursor
- anchor
));
1256 selection_anchor
= anchor
;
1257 selection_cursor
= cursor
;
1258 emit
|= SELECTION_CHANGED
;
1266 TextBoxBase::KeyPressRight (GdkModifierType modifiers
)
1268 int anchor
= selection_anchor
;
1269 int cursor
= selection_cursor
;
1270 bool handled
= false;
1272 if ((modifiers
& ALT_MASK
) != 0)
1275 if ((modifiers
& CONTROL_MASK
) != 0) {
1276 // move the cursor to beginning of the next word
1277 cursor
= CursorNextWord (cursor
);
1278 } else if ((modifiers
& SHIFT_MASK
) == 0 && anchor
!= cursor
) {
1279 // set cursor at end of selection
1280 cursor
= MAX (anchor
, cursor
);
1282 // move the cursor forward one character
1283 if (buffer
->text
[cursor
] == '\r' && buffer
->text
[cursor
+ 1] == '\n')
1285 else if (cursor
< buffer
->len
)
1289 if ((modifiers
& SHIFT_MASK
) == 0) {
1290 // clobber the selection
1294 // check to see if selection has changed
1295 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1296 SetSelectionStart (MIN (anchor
, cursor
));
1297 SetSelectionLength (abs (cursor
- anchor
));
1298 selection_anchor
= anchor
;
1299 selection_cursor
= cursor
;
1300 emit
|= SELECTION_CHANGED
;
1308 TextBoxBase::KeyPressLeft (GdkModifierType modifiers
)
1310 int anchor
= selection_anchor
;
1311 int cursor
= selection_cursor
;
1312 bool handled
= false;
1314 if ((modifiers
& ALT_MASK
) != 0)
1317 if ((modifiers
& CONTROL_MASK
) != 0) {
1318 // move the cursor to the beginning of the previous word
1319 cursor
= CursorPrevWord (cursor
);
1320 } else if ((modifiers
& SHIFT_MASK
) == 0 && anchor
!= cursor
) {
1321 // set cursor at start of selection
1322 cursor
= MIN (anchor
, cursor
);
1324 // move the cursor backward one character
1325 if (cursor
>= 2 && buffer
->text
[cursor
- 2] == '\r' && buffer
->text
[cursor
- 1] == '\n')
1327 else if (cursor
> 0)
1331 if ((modifiers
& SHIFT_MASK
) == 0) {
1332 // clobber the selection
1336 // check to see if selection has changed
1337 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1338 SetSelectionStart (MIN (anchor
, cursor
));
1339 SetSelectionLength (abs (cursor
- anchor
));
1340 selection_anchor
= anchor
;
1341 selection_cursor
= cursor
;
1342 emit
|= SELECTION_CHANGED
;
1350 TextBoxBase::KeyPressUnichar (gunichar c
)
1352 int length
= abs (selection_cursor
- selection_anchor
);
1353 int start
= MIN (selection_anchor
, selection_cursor
);
1354 int anchor
= selection_anchor
;
1355 int cursor
= selection_cursor
;
1356 TextBoxUndoAction
*action
;
1358 if ((max_length
> 0 && buffer
->len
>= max_length
) || ((c
== '\r') && !accepts_return
))
1362 // replace the currently selected text
1363 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, c
);
1364 undo
->Push (action
);
1367 buffer
->Replace (start
, length
, &c
, 1);
1369 // insert the text at the cursor position
1370 TextBoxUndoActionInsert
*insert
= NULL
;
1372 if ((action
= undo
->Peek ()) && action
->type
== TextBoxUndoActionTypeInsert
) {
1373 insert
= (TextBoxUndoActionInsert
*) action
;
1375 if (!insert
->Insert (start
, c
))
1380 insert
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, c
);
1381 undo
->Push (insert
);
1386 buffer
->Insert (start
, c
);
1389 emit
|= TEXT_CHANGED
;
1393 // check to see if selection has changed
1394 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1395 SetSelectionStart (MIN (anchor
, cursor
));
1396 SetSelectionLength (abs (cursor
- anchor
));
1397 selection_anchor
= anchor
;
1398 selection_cursor
= cursor
;
1399 emit
|= SELECTION_CHANGED
;
1405 TextBoxBase::BatchPush ()
1411 TextBoxBase::BatchPop ()
1414 g_warning ("TextBoxBase batch underflow");
1422 TextBoxBase::SyncAndEmit (bool sync_text
)
1424 if (batch
!= 0 || emit
== NOTHING_CHANGED
)
1427 if (sync_text
&& (emit
& TEXT_CHANGED
))
1430 if (emit
& SELECTION_CHANGED
)
1431 SyncSelectedText ();
1434 // eliminate events that we can't emit
1435 emit
&= events_mask
;
1437 if (emit
& TEXT_CHANGED
)
1440 if (emit
& SELECTION_CHANGED
)
1441 EmitSelectionChanged ();
1444 emit
= NOTHING_CHANGED
;
1448 TextBoxBase::Paste (GtkClipboard
*clipboard
, const char *str
)
1450 int length
= abs (selection_cursor
- selection_anchor
);
1451 int start
= MIN (selection_anchor
, selection_cursor
);
1452 TextBoxUndoAction
*action
;
1456 if (!(text
= g_utf8_to_ucs4_fast (str
? str
: "", -1, &len
)))
1459 if (max_length
> 0 && ((buffer
->len
- length
) + len
> max_length
)) {
1460 // paste cannot exceed MaxLength
1461 len
= max_length
- (buffer
->len
- length
);
1463 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (len
+ 1));
1470 // only paste the content of the first line
1471 for (i
= 0; i
< len
; i
++) {
1472 if (text
[i
] == '\r' || text
[i
] == '\n' || text
[i
] == 0x2028) {
1473 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (i
+ 1));
1484 // replace the currently selected text
1485 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, len
);
1487 buffer
->Replace (start
, length
, text
, len
);
1488 } else if (len
> 0) {
1489 // insert the text at the cursor position
1490 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, len
, true);
1492 buffer
->Insert (start
, text
, len
);
1498 undo
->Push (action
);
1502 emit
|= TEXT_CHANGED
;
1506 SetSelectionStart (start
);
1507 SetSelectionLength (0);
1514 TextBoxBase::paste (GtkClipboard
*clipboard
, const char *text
, gpointer closure
)
1516 ((TextBoxBase
*) closure
)->Paste (clipboard
, text
);
1520 TextBoxBase::OnKeyDown (KeyEventArgs
*args
)
1522 GdkModifierType modifiers
= (GdkModifierType
) args
->GetModifiers ();
1523 guint key
= args
->GetKeyVal ();
1524 GtkClipboard
*clipboard
;
1525 bool handled
= false;
1527 if (args
->IsModifier ())
1530 // set 'emit' to NOTHING_CHANGED so that we can figure out
1531 // what has chanegd after applying the changes that this
1532 // keypress will cause.
1533 emit
= NOTHING_CHANGED
;
1541 handled
= KeyPressBackSpace (modifiers
);
1547 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == SHIFT_MASK
) {
1548 // Shift+Delete => Cut
1549 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1550 if (selection_cursor
!= selection_anchor
) {
1551 // copy selection to the clipboard and then cut
1552 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1556 SetSelectedText ("");
1559 handled
= KeyPressDelete (modifiers
);
1563 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == SHIFT_MASK
) {
1564 // Shift+Insert => Paste
1568 if ((clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1569 // paste clipboard contents to the buffer
1570 gtk_clipboard_request_text (clipboard
, TextBoxBase::paste
, this);
1574 } else if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == CONTROL_MASK
) {
1575 // Control+Insert => Copy
1576 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1577 if (selection_cursor
!= selection_anchor
) {
1578 // copy selection to the clipboard
1579 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1586 case GDK_KP_Page_Down
:
1588 handled
= KeyPressPageDown (modifiers
);
1590 case GDK_KP_Page_Up
:
1592 handled
= KeyPressPageUp (modifiers
);
1596 handled
= KeyPressHome (modifiers
);
1600 handled
= KeyPressEnd (modifiers
);
1604 handled
= KeyPressRight (modifiers
);
1608 handled
= KeyPressLeft (modifiers
);
1612 handled
= KeyPressDown (modifiers
);
1616 handled
= KeyPressUp (modifiers
);
1619 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == CONTROL_MASK
) {
1623 // Ctrl+A => Select All
1630 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1631 if (selection_cursor
!= selection_anchor
) {
1632 // copy selection to the clipboard
1633 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1645 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1646 if (selection_cursor
!= selection_anchor
) {
1647 // copy selection to the clipboard and then cut
1648 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1652 SetSelectedText ("");
1661 if ((clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1662 // paste clipboard contents to the buffer
1663 gtk_clipboard_request_text (clipboard
, TextBoxBase::paste
, this);
1671 if (!is_read_only
) {
1679 if (!is_read_only
) {
1685 // unhandled Control commands
1693 args
->SetHandled (handled
);
1703 TextBoxBase::OnCharacterKeyDown (KeyEventArgs
*args
)
1705 guint key
= args
->GetKeyVal ();
1706 bool handled
= false;
1709 if (!is_read_only
&& gtk_im_context_filter_keypress (im_ctx
, args
->GetEvent ())) {
1710 args
->SetHandled (true);
1711 need_im_reset
= true;
1715 if (is_read_only
|| args
->IsModifier ())
1718 // set 'emit' to NOTHING_CHANGED so that we can figure out
1719 // what has chanegd after applying the changes that this
1720 // keypress will cause.
1721 emit
= NOTHING_CHANGED
;
1726 handled
= KeyPressUnichar ('\r');
1729 if ((args
->GetModifiers () & (CONTROL_MASK
| ALT_MASK
)) == 0) {
1730 // normal character input
1731 if ((c
= args
->GetUnicode ()))
1732 handled
= KeyPressUnichar (c
);
1738 args
->SetHandled (handled
);
1746 TextBoxBase::OnKeyUp (KeyEventArgs
*args
)
1748 if (!is_read_only
) {
1749 if (gtk_im_context_filter_keypress (im_ctx
, args
->GetEvent ()))
1750 need_im_reset
= true;
1752 args
->SetHandled (true);
1757 TextBoxBase::DeleteSurrounding (int offset
, int n_chars
)
1759 const char *delete_start
, *delete_end
;
1760 const char *text
= GetActualText ();
1761 int anchor
= selection_anchor
;
1762 int cursor
= selection_cursor
;
1763 TextBoxUndoAction
*action
;
1769 // get the utf-8 pointers so that we can use them to get gunichar offsets
1770 delete_start
= g_utf8_offset_to_pointer (text
, selection_cursor
) + offset
;
1771 delete_end
= delete_start
+ n_chars
;
1773 // get the character length/start index
1774 length
= g_utf8_pointer_to_offset (delete_start
, delete_end
);
1775 start
= g_utf8_pointer_to_offset (text
, delete_start
);
1778 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
1779 undo
->Push (action
);
1782 buffer
->Cut (start
, length
);
1783 emit
|= TEXT_CHANGED
;
1790 // check to see if selection has changed
1791 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1792 SetSelectionStart (MIN (anchor
, cursor
));
1793 SetSelectionLength (abs (cursor
- anchor
));
1794 selection_anchor
= anchor
;
1795 selection_cursor
= cursor
;
1796 emit
|= SELECTION_CHANGED
;
1807 TextBoxBase::delete_surrounding (GtkIMContext
*context
, int offset
, int n_chars
, gpointer user_data
)
1809 return ((TextBoxBase
*) user_data
)->DeleteSurrounding (offset
, n_chars
);
1813 TextBoxBase::RetrieveSurrounding ()
1815 const char *text
= GetActualText ();
1816 const char *cursor
= g_utf8_offset_to_pointer (text
, selection_cursor
);
1818 gtk_im_context_set_surrounding (im_ctx
, text
, -1, cursor
- text
);
1824 TextBoxBase::retrieve_surrounding (GtkIMContext
*context
, gpointer user_data
)
1826 return ((TextBoxBase
*) user_data
)->RetrieveSurrounding ();
1830 TextBoxBase::Commit (const char *str
)
1832 int length
= abs (selection_cursor
- selection_anchor
);
1833 int start
= MIN (selection_anchor
, selection_cursor
);
1834 TextBoxUndoAction
*action
;
1842 if (!(text
= g_utf8_to_ucs4_fast (str
? str
: "", -1, &len
)))
1845 if (max_length
> 0 && ((buffer
->len
- length
) + len
> max_length
)) {
1846 // paste cannot exceed MaxLength
1847 len
= max_length
- (buffer
->len
- length
);
1849 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (len
+ 1));
1856 // only paste the content of the first line
1857 for (i
= 0; i
< len
; i
++) {
1858 if (g_unichar_type (text
[i
]) == G_UNICODE_LINE_SEPARATOR
) {
1859 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (i
+ 1));
1868 // replace the currently selected text
1869 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, len
);
1870 undo
->Push (action
);
1873 buffer
->Replace (start
, length
, text
, len
);
1874 } else if (len
> 0) {
1875 // insert the text at the cursor position
1876 TextBoxUndoActionInsert
*insert
= NULL
;
1878 buffer
->Insert (start
, text
, len
);
1880 if ((action
= undo
->Peek ()) && action
->type
== TextBoxUndoActionTypeInsert
) {
1881 insert
= (TextBoxUndoActionInsert
*) action
;
1883 if (!insert
->Insert (start
, text
, len
))
1888 insert
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, len
);
1889 undo
->Push (insert
);
1898 emit
= TEXT_CHANGED
;
1899 cursor
= start
+ len
;
1905 // check to see if selection has changed
1906 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1907 SetSelectionStart (MIN (anchor
, cursor
));
1908 SetSelectionLength (abs (cursor
- anchor
));
1909 selection_anchor
= anchor
;
1910 selection_cursor
= cursor
;
1911 emit
|= SELECTION_CHANGED
;
1920 TextBoxBase::commit (GtkIMContext
*context
, const char *str
, gpointer user_data
)
1922 ((TextBoxBase
*) user_data
)->Commit (str
);
1926 TextBoxBase::ResetIMContext ()
1928 if (need_im_reset
) {
1929 gtk_im_context_reset (im_ctx
);
1930 need_im_reset
= false;
1935 TextBoxBase::OnMouseLeftButtonDown (MouseEventArgs
*args
)
1940 args
->SetHandled (true);
1944 args
->GetPosition (view
, &x
, &y
);
1946 cursor
= view
->GetCursorFromXY (x
, y
);
1950 // Single-Click: cursor placement
1951 captured
= CaptureMouse ();
1955 emit
= NOTHING_CHANGED
;
1956 SetSelectionStart (cursor
);
1957 SetSelectionLength (0);
1965 TextBoxBase::mouse_left_button_down (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
1967 ((TextBoxBase
*) closure
)->OnMouseLeftButtonDown ((MouseEventArgs
*) args
);
1971 TextBoxBase::OnMouseLeftButtonMultiClick (MouseEventArgs
*args
)
1973 int cursor
, start
, end
;
1976 args
->SetHandled (true);
1979 args
->GetPosition (view
, &x
, &y
);
1981 cursor
= view
->GetCursorFromXY (x
, y
);
1985 if (args
->GetClickCount () == 3) {
1986 // Note: Silverlight doesn't implement this, but to
1987 // be consistent with other TextEntry-type
1988 // widgets in Gtk+, we will.
1990 // Triple-Click: select the line
1992 ReleaseMouseCapture ();
1993 start
= CursorLineBegin (cursor
);
1994 end
= CursorLineEnd (cursor
, true);
1998 // Double-Click: select the word
2000 ReleaseMouseCapture ();
2001 start
= CursorPrevWord (cursor
);
2002 end
= CursorNextWord (cursor
);
2008 emit
= NOTHING_CHANGED
;
2009 SetSelectionStart (start
);
2010 SetSelectionLength (end
- start
);
2018 TextBoxBase::mouse_left_button_multi_click (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2020 ((TextBoxBase
*) closure
)->OnMouseLeftButtonMultiClick ((MouseEventArgs
*) args
);
2024 TextBoxBase::OnMouseLeftButtonUp (MouseEventArgs
*args
)
2027 ReleaseMouseCapture ();
2029 args
->SetHandled (true);
2035 TextBoxBase::mouse_left_button_up (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2037 ((TextBoxBase
*) closure
)->OnMouseLeftButtonUp ((MouseEventArgs
*) args
);
2041 TextBoxBase::OnMouseMove (MouseEventArgs
*args
)
2043 int anchor
= selection_anchor
;
2044 int cursor
= selection_cursor
;
2048 args
->GetPosition (view
, &x
, &y
);
2049 args
->SetHandled (true);
2051 cursor
= view
->GetCursorFromXY (x
, y
);
2054 emit
= NOTHING_CHANGED
;
2055 SetSelectionStart (MIN (anchor
, cursor
));
2056 SetSelectionLength (abs (cursor
- anchor
));
2057 selection_anchor
= anchor
;
2058 selection_cursor
= cursor
;
2066 TextBoxBase::mouse_move (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2068 ((TextBoxBase
*) closure
)->OnMouseMove ((MouseEventArgs
*) args
);
2072 TextBoxBase::OnFocusOut (EventArgs
*args
)
2075 emit
= NOTHING_CHANGED
;
2076 SetSelectionStart (selection_cursor
);
2077 SetSelectionLength (0);
2085 view
->OnFocusOut ();
2087 if (!is_read_only
) {
2088 gtk_im_context_focus_out (im_ctx
);
2089 need_im_reset
= true;
2094 TextBoxBase::focus_out (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2096 ((TextBoxBase
*) closure
)->OnFocusOut (args
);
2100 TextBoxBase::OnFocusIn (EventArgs
*args
)
2107 if (!is_read_only
) {
2108 gtk_im_context_focus_in (im_ctx
);
2109 need_im_reset
= true;
2114 TextBoxBase::focus_in (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
2116 ((TextBoxBase
*) closure
)->OnFocusIn (args
);
2120 TextBoxBase::EmitCursorPositionChanged (double height
, double x
, double y
)
2122 Emit (TextBoxBase::CursorPositionChangedEvent
, new CursorPositionChangedEventArgs (height
, x
, y
));
2126 TextBoxBase::DownloaderComplete (Downloader
*downloader
)
2128 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2129 char *resource
, *filename
;
2130 InternalDownloader
*idl
;
2134 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2135 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
2140 if (!(idl
= downloader
->GetInternalDownloader ()))
2143 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
2146 uri
= downloader
->GetUri ();
2148 // If the downloaded file was a zip file, this'll get the path to the
2149 // extracted zip directory, else it will simply be the path to the
2151 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
2154 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
2155 manager
->AddResource (resource
, path
);
2158 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont
, NULL
));
2162 TextBoxBase::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
2164 ((TextBoxBase
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
2168 TextBoxBase::AddFontSource (Downloader
*downloader
)
2170 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
2171 g_ptr_array_add (downloaders
, downloader
);
2174 if (downloader
->Started () || downloader
->Completed ()) {
2175 if (downloader
->Completed ())
2176 DownloaderComplete (downloader
);
2178 // This is what actually triggers the download
2179 downloader
->Send ();
2184 TextBoxBase::AddFontResource (const char *resource
)
2186 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2187 Application
*application
= Application::GetCurrent ();
2188 Downloader
*downloader
;
2195 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
2196 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
2197 downloader
->Open ("GET", resource
, FontPolicy
);
2198 AddFontSource (downloader
);
2199 downloader
->unref ();
2207 manager
->AddResource (resource
, path
);
2213 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2215 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2217 if (args
->GetId () == Control::FontFamilyProperty
) {
2218 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
2219 char **families
, *fragment
;
2222 CleanupDownloaders ();
2224 if (family
&& family
->source
) {
2225 families
= g_strsplit (family
->source
, ",", -1);
2226 for (i
= 0; families
[i
]; i
++) {
2227 g_strstrip (families
[i
]);
2228 if ((fragment
= strchr (families
[i
], '#'))) {
2229 // the first portion of this string is the resource name...
2231 AddFontResource (families
[i
]);
2234 g_strfreev (families
);
2237 font
->SetFamily (family
? family
->source
: NULL
);
2238 changed
= TextBoxModelChangedFont
;
2239 } else if (args
->GetId () == Control::FontSizeProperty
) {
2240 double size
= args
->GetNewValue()->AsDouble ();
2241 changed
= TextBoxModelChangedFont
;
2242 font
->SetSize (size
);
2243 } else if (args
->GetId () == Control::FontStretchProperty
) {
2244 FontStretches stretch
= args
->GetNewValue()->AsFontStretch()->stretch
;
2245 changed
= TextBoxModelChangedFont
;
2246 font
->SetStretch (stretch
);
2247 } else if (args
->GetId () == Control::FontStyleProperty
) {
2248 FontStyles style
= args
->GetNewValue()->AsFontStyle ()->style
;
2249 changed
= TextBoxModelChangedFont
;
2250 font
->SetStyle (style
);
2251 } else if (args
->GetId () == Control::FontWeightProperty
) {
2252 FontWeights weight
= args
->GetNewValue()->AsFontWeight ()->weight
;
2253 changed
= TextBoxModelChangedFont
;
2254 font
->SetWeight (weight
);
2255 } else if (args
->GetId () == FrameworkElement::MinHeightProperty
) {
2256 // pass this along to our TextBoxView
2258 view
->SetMinHeight (args
->GetNewValue ()->AsDouble ());
2259 } else if (args
->GetId () == FrameworkElement::MaxHeightProperty
) {
2260 // pass this along to our TextBoxView
2262 view
->SetMaxHeight (args
->GetNewValue ()->AsDouble ());
2263 } else if (args
->GetId () == FrameworkElement::MinWidthProperty
) {
2264 // pass this along to our TextBoxView
2266 view
->SetMinWidth (args
->GetNewValue ()->AsDouble ());
2267 } else if (args
->GetId () == FrameworkElement::MaxWidthProperty
) {
2268 // pass this along to our TextBoxView
2270 view
->SetMaxWidth (args
->GetNewValue ()->AsDouble ());
2271 } else if (args
->GetId () == FrameworkElement::HeightProperty
) {
2272 // pass this along to our TextBoxView
2274 view
->SetHeight (args
->GetNewValue ()->AsDouble ());
2275 } else if (args
->GetId () == FrameworkElement::WidthProperty
) {
2276 // pass this along to our TextBoxView
2278 view
->SetWidth (args
->GetNewValue ()->AsDouble ());
2281 if (changed
!= TextBoxModelChangedNothing
)
2282 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2284 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE
) {
2285 Control::OnPropertyChanged (args
, error
);
2289 NotifyListenersOfPropertyChange (args
, error
);
2293 TextBoxBase::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2295 if (prop
&& (prop
->GetId () == Control::BackgroundProperty
||
2296 prop
->GetId () == Control::ForegroundProperty
)) {
2297 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2301 if (prop
->GetOwnerType () != Type::TEXTBOXBASE
)
2302 Control::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2306 TextBoxBase::OnApplyTemplate ()
2308 contentElement
= GetTemplateChild ("ContentElement");
2310 if (contentElement
== NULL
) {
2311 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2312 Control::OnApplyTemplate ();
2316 view
= new TextBoxView ();
2317 view
->SetTextBox (this);
2319 view
->SetMinHeight (GetMinHeight ());
2320 view
->SetMaxHeight (GetMaxHeight ());
2321 view
->SetMinWidth (GetMinWidth ());
2322 view
->SetMaxWidth (GetMaxWidth ());
2323 view
->SetHeight (GetHeight ());
2324 view
->SetWidth (GetWidth ());
2326 // Insert our TextBoxView
2327 if (contentElement
->Is (Type::CONTENTCONTROL
)) {
2328 ContentControl
*control
= (ContentControl
*) contentElement
;
2330 control
->SetValue (ContentControl::ContentProperty
, Value (view
));
2331 } else if (contentElement
->Is (Type::BORDER
)) {
2332 Border
*border
= (Border
*) contentElement
;
2334 border
->SetValue (Border::ChildProperty
, Value (view
));
2335 } else if (contentElement
->Is (Type::PANEL
)) {
2336 DependencyObjectCollection
*children
= ((Panel
*) contentElement
)->GetChildren ();
2338 children
->Add (view
);
2340 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentELement of type %s",
2341 contentElement
->GetType ()->GetName ());
2346 Control::OnApplyTemplate ();
2350 TextBoxBase::ClearSelection (int start
)
2353 SetSelectionStart (start
);
2354 SetSelectionLength (0);
2359 TextBoxBase::SelectWithError (int start
, int length
, MoonError
*error
)
2362 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection start must be >= 0");
2367 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection length must be >= 0");
2371 if (start
> buffer
->len
)
2372 start
= buffer
->len
;
2374 if (length
> (buffer
->len
- start
))
2375 length
= (buffer
->len
- start
);
2378 SetSelectionStart (start
);
2379 SetSelectionLength (length
);
2390 TextBoxBase::SelectAll ()
2392 SelectWithError (0, buffer
->len
, NULL
);
2396 TextBoxBase::CanUndo ()
2398 return !undo
->IsEmpty ();
2402 TextBoxBase::CanRedo ()
2404 return !redo
->IsEmpty ();
2408 TextBoxBase::Undo ()
2410 TextBoxUndoActionReplace
*replace
;
2411 TextBoxUndoActionInsert
*insert
;
2412 TextBoxUndoActionDelete
*dele
;
2413 TextBoxUndoAction
*action
;
2416 if (undo
->IsEmpty ())
2419 action
= undo
->Pop ();
2420 redo
->Push (action
);
2422 switch (action
->type
) {
2423 case TextBoxUndoActionTypeInsert
:
2424 insert
= (TextBoxUndoActionInsert
*) action
;
2426 buffer
->Cut (insert
->start
, insert
->length
);
2427 anchor
= action
->selection_anchor
;
2428 cursor
= action
->selection_cursor
;
2430 case TextBoxUndoActionTypeDelete
:
2431 dele
= (TextBoxUndoActionDelete
*) action
;
2433 buffer
->Insert (dele
->start
, dele
->text
, dele
->length
);
2434 anchor
= action
->selection_anchor
;
2435 cursor
= action
->selection_cursor
;
2437 case TextBoxUndoActionTypeReplace
:
2438 replace
= (TextBoxUndoActionReplace
*) action
;
2440 buffer
->Cut (replace
->start
, replace
->inlen
);
2441 buffer
->Insert (replace
->start
, replace
->deleted
, replace
->length
);
2442 anchor
= action
->selection_anchor
;
2443 cursor
= action
->selection_cursor
;
2448 SetSelectionStart (MIN (anchor
, cursor
));
2449 SetSelectionLength (abs (cursor
- anchor
));
2450 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2451 selection_anchor
= anchor
;
2452 selection_cursor
= cursor
;
2459 TextBoxBase::Redo ()
2461 TextBoxUndoActionReplace
*replace
;
2462 TextBoxUndoActionInsert
*insert
;
2463 TextBoxUndoActionDelete
*dele
;
2464 TextBoxUndoAction
*action
;
2467 if (redo
->IsEmpty ())
2470 action
= redo
->Pop ();
2471 undo
->Push (action
);
2473 switch (action
->type
) {
2474 case TextBoxUndoActionTypeInsert
:
2475 insert
= (TextBoxUndoActionInsert
*) action
;
2477 buffer
->Insert (insert
->start
, insert
->buffer
->text
, insert
->buffer
->len
);
2478 anchor
= cursor
= insert
->start
+ insert
->buffer
->len
;
2480 case TextBoxUndoActionTypeDelete
:
2481 dele
= (TextBoxUndoActionDelete
*) action
;
2483 buffer
->Cut (dele
->start
, dele
->length
);
2484 anchor
= cursor
= dele
->start
;
2486 case TextBoxUndoActionTypeReplace
:
2487 replace
= (TextBoxUndoActionReplace
*) action
;
2489 buffer
->Cut (replace
->start
, replace
->length
);
2490 buffer
->Insert (replace
->start
, replace
->inserted
, replace
->inlen
);
2491 anchor
= cursor
= replace
->start
+ replace
->inlen
;
2496 SetSelectionStart (MIN (anchor
, cursor
));
2497 SetSelectionLength (abs (cursor
- anchor
));
2498 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2499 selection_anchor
= anchor
;
2500 selection_cursor
= cursor
;
2508 // TextBoxDynamicPropertyValueProvider
2511 class TextBoxDynamicPropertyValueProvider
: public PropertyValueProvider
{
2512 Value
*selection_background
;
2513 Value
*selection_foreground
;
2516 TextBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : PropertyValueProvider (obj
, precedence
)
2518 selection_background
= NULL
;
2519 selection_foreground
= NULL
;
2522 virtual ~TextBoxDynamicPropertyValueProvider ()
2524 delete selection_background
;
2525 delete selection_foreground
;
2528 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2530 if (property
->GetId () == TextBox::SelectionBackgroundProperty
) {
2531 return selection_background
;
2532 } else if (property
->GetId () == TextBox::SelectionForegroundProperty
) {
2533 return selection_foreground
;
2539 void InitializeSelectionBrushes ()
2541 if (!selection_background
)
2542 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2544 if (!selection_foreground
)
2545 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2556 providers
[PropertyPrecedence_DynamicValue
] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2558 Initialize (Type::TEXTBOX
, "System.Windows.Controls.TextBox");
2559 events_mask
= TEXT_CHANGED
| SELECTION_CHANGED
;
2564 TextBox::EmitSelectionChanged ()
2566 EmitAsync (TextBox::SelectionChangedEvent
, new RoutedEventArgs ());
2570 TextBox::EmitTextChanged ()
2572 EmitAsync (TextBox::TextChangedEvent
, new TextChangedEventArgs ());
2576 TextBox::SyncSelectedText ()
2578 if (selection_cursor
!= selection_anchor
) {
2579 int length
= abs (selection_cursor
- selection_anchor
);
2580 int start
= MIN (selection_anchor
, selection_cursor
);
2583 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2586 SetValue (TextBox::SelectedTextProperty
, Value (text
, true));
2590 SetValue (TextBox::SelectedTextProperty
, Value (""));
2596 TextBox::SyncText ()
2598 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
2601 SetValue (TextBox::TextProperty
, Value (text
, true));
2606 TextBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2608 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2609 DependencyProperty
*prop
;
2612 if (args
->GetId () == TextBox::AcceptsReturnProperty
) {
2613 // update accepts_return state
2614 accepts_return
= args
->GetNewValue ()->AsBool ();
2615 } else if (args
->GetId () == TextBox::CaretBrushProperty
) {
2616 // FIXME: if we want to be perfect, we could invalidate the
2617 // blinking cursor rect if it is active... but is it that
2619 } else if (args
->GetId () == TextBox::FontSourceProperty
) {
2620 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
2621 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2623 // FIXME: ideally we'd remove the old item from the cache (or,
2624 // rather, 'unref' it since some other textblocks/boxes might
2625 // still be using it).
2627 g_free (font_source
);
2629 if (source
&& source
->stream
)
2630 font_source
= manager
->AddResource (source
->stream
);
2634 changed
= TextBoxModelChangedFont
;
2635 font
->SetSource (font_source
);
2636 } else if (args
->GetId () == TextBox::IsReadOnlyProperty
) {
2637 // update is_read_only state
2638 is_read_only
= args
->GetNewValue ()->AsBool ();
2643 gtk_im_context_focus_out (im_ctx
);
2645 gtk_im_context_focus_in (im_ctx
);
2648 } else if (args
->GetId () == TextBox::MaxLengthProperty
) {
2649 // update max_length state
2650 max_length
= args
->GetNewValue ()->AsInt32 ();
2651 } else if (args
->GetId () == TextBox::SelectedTextProperty
) {
2653 Value
*value
= args
->GetNewValue ();
2654 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2655 TextBoxUndoAction
*action
= NULL
;
2659 length
= abs (selection_cursor
- selection_anchor
);
2660 start
= MIN (selection_anchor
, selection_cursor
);
2662 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2664 // replace the currently selected text
2665 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
2667 buffer
->Replace (start
, length
, text
, textlen
);
2668 } else if (textlen
> 0) {
2669 // insert the text at the cursor
2670 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
2672 buffer
->Insert (start
, text
, textlen
);
2677 if (action
!= NULL
) {
2678 emit
|= TEXT_CHANGED
;
2679 undo
->Push (action
);
2682 ClearSelection (start
+ textlen
);
2688 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2691 } else if (args
->GetId () == TextBox::SelectionStartProperty
) {
2692 length
= abs (selection_cursor
- selection_anchor
);
2693 start
= args
->GetNewValue ()->AsInt32 ();
2695 if (start
> buffer
->len
) {
2696 // clamp the selection start offset to a valid value
2697 SetSelectionStart (buffer
->len
);
2701 if (start
+ length
> buffer
->len
) {
2702 // clamp the selection length to a valid value
2704 length
= buffer
->len
- start
;
2705 SetSelectionLength (length
);
2709 // SelectionStartProperty is marked as AlwaysChange -
2710 // if the value hasn't actually changed, then we do
2711 // not want to emit the TextBoxModelChanged event.
2712 if (selection_anchor
!= start
) {
2713 changed
= TextBoxModelChangedSelection
;
2714 have_offset
= false;
2717 // When set programatically, anchor is always the
2718 // start and cursor is always the end.
2719 selection_cursor
= start
+ length
;
2720 selection_anchor
= start
;
2722 emit
|= SELECTION_CHANGED
;
2725 } else if (args
->GetId () == TextBox::SelectionLengthProperty
) {
2726 start
= MIN (selection_anchor
, selection_cursor
);
2727 length
= args
->GetNewValue ()->AsInt32 ();
2729 if (start
+ length
> buffer
->len
) {
2730 // clamp the selection length to a valid value
2731 length
= buffer
->len
- start
;
2732 SetSelectionLength (length
);
2736 // SelectionLengthProperty is marked as AlwaysChange -
2737 // if the value hasn't actually changed, then we do
2738 // not want to emit the TextBoxModelChanged event.
2739 if (selection_cursor
!= start
+ length
) {
2740 changed
= TextBoxModelChangedSelection
;
2741 have_offset
= false;
2744 // When set programatically, anchor is always the
2745 // start and cursor is always the end.
2746 selection_cursor
= start
+ length
;
2747 selection_anchor
= start
;
2749 emit
|= SELECTION_CHANGED
;
2752 } else if (args
->GetId () == TextBox::SelectionBackgroundProperty
) {
2753 changed
= TextBoxModelChangedBrush
;
2754 } else if (args
->GetId () == TextBox::SelectionForegroundProperty
) {
2755 changed
= TextBoxModelChangedBrush
;
2756 } else if (args
->GetId () == TextBox::TextProperty
) {
2758 Value
*value
= args
->GetNewValue ();
2759 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2760 TextBoxUndoAction
*action
;
2764 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2765 if (buffer
->len
> 0) {
2766 // replace the current text
2767 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
2769 buffer
->Replace (0, buffer
->len
, text
, textlen
);
2772 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
2774 buffer
->Insert (0, text
, textlen
);
2777 undo
->Push (action
);
2781 emit
|= TEXT_CHANGED
;
2785 SyncAndEmit (value
&& !value
->GetIsNull ());
2787 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2791 changed
= TextBoxModelChangedText
;
2792 } else if (args
->GetId () == TextBox::TextAlignmentProperty
) {
2793 changed
= TextBoxModelChangedTextAlignment
;
2794 } else if (args
->GetId () == TextBox::TextWrappingProperty
) {
2795 changed
= TextBoxModelChangedTextWrapping
;
2796 } else if (args
->GetId () == TextBox::HorizontalScrollBarVisibilityProperty
) {
2797 // XXX more crap because these aren't templatebound.
2798 if (contentElement
) {
2799 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2800 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2804 } else if (args
->GetId () == TextBox::VerticalScrollBarVisibilityProperty
) {
2805 // XXX more crap because these aren't templatebound.
2806 if (contentElement
) {
2807 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2808 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2814 if (changed
!= TextBoxModelChangedNothing
)
2815 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2817 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
2818 TextBoxBase::OnPropertyChanged (args
, error
);
2822 NotifyListenersOfPropertyChange (args
, error
);
2826 TextBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2828 if (prop
&& (prop
->GetId () == TextBox::SelectionBackgroundProperty
||
2829 prop
->GetId () == TextBox::SelectionForegroundProperty
)) {
2830 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2834 if (prop
->GetOwnerType () != Type::TEXTBOX
)
2835 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2839 TextBox::OnApplyTemplate ()
2841 DependencyProperty
*prop
;
2843 TextBoxBase::OnApplyTemplate ();
2845 if (!contentElement
)
2848 // XXX LAME these should be template bindings in the textbox template.
2849 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2850 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2852 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2853 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2862 // PasswordBoxDynamicPropertyValueProvider
2865 class PasswordBoxDynamicPropertyValueProvider
: public PropertyValueProvider
{
2866 Value
*selection_background
;
2867 Value
*selection_foreground
;
2870 PasswordBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : PropertyValueProvider (obj
, precedence
)
2872 selection_background
= NULL
;
2873 selection_foreground
= NULL
;
2876 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2878 delete selection_background
;
2879 delete selection_foreground
;
2882 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2884 if (property
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
2885 return selection_background
;
2886 } else if (property
->GetId () == PasswordBox::SelectionForegroundProperty
) {
2887 return selection_foreground
;
2893 void InitializeSelectionBrushes ()
2895 if (!selection_background
)
2896 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2898 if (!selection_foreground
)
2899 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2908 PasswordBox::PasswordBox ()
2910 providers
[PropertyPrecedence_DynamicValue
] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2912 Initialize (Type::PASSWORDBOX
, "System.Windows.Controls.PasswordBox");
2913 events_mask
= TEXT_CHANGED
;
2916 display
= g_string_new ("");
2919 PasswordBox::~PasswordBox ()
2921 g_string_free (display
, true);
2925 PasswordBox::CursorDown (int cursor
, bool page
)
2927 return GetBuffer ()->len
;
2931 PasswordBox::CursorUp (int cursor
, bool page
)
2937 PasswordBox::CursorLineBegin (int cursor
)
2943 PasswordBox::CursorLineEnd (int cursor
, bool include
)
2945 return GetBuffer ()->len
;
2949 PasswordBox::CursorNextWord (int cursor
)
2951 return GetBuffer ()->len
;
2955 PasswordBox::CursorPrevWord (int cursor
)
2961 PasswordBox::EmitTextChanged ()
2963 EmitAsync (PasswordBox::PasswordChangedEvent
, new RoutedEventArgs ());
2967 PasswordBox::SyncSelectedText ()
2969 if (selection_cursor
!= selection_anchor
) {
2970 int length
= abs (selection_cursor
- selection_anchor
);
2971 int start
= MIN (selection_anchor
, selection_cursor
);
2974 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2977 SetValue (PasswordBox::SelectedTextProperty
, Value (text
, true));
2981 SetValue (PasswordBox::SelectedTextProperty
, Value (""));
2987 PasswordBox::SyncDisplayText ()
2989 gunichar c
= GetPasswordChar ();
2991 g_string_truncate (display
, 0);
2993 for (int i
= 0; i
< buffer
->len
; i
++)
2994 g_string_append_unichar (display
, c
);
2998 PasswordBox::SyncText ()
3000 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
3003 SetValue (PasswordBox::PasswordProperty
, Value (text
, true));
3010 PasswordBox::GetDisplayText ()
3012 return display
->str
;
3016 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
3018 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
3021 if (args
->GetId () == PasswordBox::CaretBrushProperty
) {
3022 // FIXME: if we want to be perfect, we could invalidate the
3023 // blinking cursor rect if it is active... but is it that
3025 } else if (args
->GetId () == PasswordBox::FontSourceProperty
) {
3026 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
3027 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
3029 // FIXME: ideally we'd remove the old item from the cache (or,
3030 // rather, 'unref' it since some other textblocks/boxes might
3031 // still be using it).
3033 g_free (font_source
);
3035 if (source
&& source
->stream
)
3036 font_source
= manager
->AddResource (source
->stream
);
3040 changed
= TextBoxModelChangedFont
;
3041 font
->SetSource (font_source
);
3042 } else if (args
->GetId () == PasswordBox::MaxLengthProperty
) {
3043 // update max_length state
3044 max_length
= args
->GetNewValue()->AsInt32 ();
3045 } else if (args
->GetId () == PasswordBox::PasswordCharProperty
) {
3046 changed
= TextBoxModelChangedText
;
3047 } else if (args
->GetId () == PasswordBox::PasswordProperty
) {
3049 Value
*value
= args
->GetNewValue ();
3050 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
3051 TextBoxUndoAction
*action
;
3055 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
3056 if (buffer
->len
> 0) {
3057 // replace the current text
3058 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
3060 buffer
->Replace (0, buffer
->len
, text
, textlen
);
3063 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
3065 buffer
->Insert (0, text
, textlen
);
3068 undo
->Push (action
);
3072 emit
|= TEXT_CHANGED
;
3081 changed
= TextBoxModelChangedText
;
3082 } else if (args
->GetId () == PasswordBox::SelectedTextProperty
) {
3084 Value
*value
= args
->GetNewValue ();
3085 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
3086 TextBoxUndoAction
*action
= NULL
;
3090 length
= abs (selection_cursor
- selection_anchor
);
3091 start
= MIN (selection_anchor
, selection_cursor
);
3093 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
3095 // replace the currently selected text
3096 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
3098 buffer
->Replace (start
, length
, text
, textlen
);
3099 } else if (textlen
> 0) {
3100 // insert the text at the cursor
3101 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
3103 buffer
->Insert (start
, text
, textlen
);
3108 if (action
!= NULL
) {
3109 undo
->Push (action
);
3112 ClearSelection (start
+ textlen
);
3113 emit
|= TEXT_CHANGED
;
3121 } else if (args
->GetId () == PasswordBox::SelectionStartProperty
) {
3122 length
= abs (selection_cursor
- selection_anchor
);
3123 start
= args
->GetNewValue ()->AsInt32 ();
3125 if (start
> buffer
->len
) {
3126 // clamp the selection start offset to a valid value
3127 SetSelectionStart (buffer
->len
);
3131 if (start
+ length
> buffer
->len
) {
3132 // clamp the selection length to a valid value
3134 length
= buffer
->len
- start
;
3135 SetSelectionLength (length
);
3139 // SelectionStartProperty is marked as AlwaysChange -
3140 // if the value hasn't actually changed, then we do
3141 // not want to emit the TextBoxModelChanged event.
3142 if (selection_anchor
!= start
) {
3143 changed
= TextBoxModelChangedSelection
;
3144 have_offset
= false;
3147 // When set programatically, anchor is always the
3148 // start and cursor is always the end.
3149 selection_cursor
= start
+ length
;
3150 selection_anchor
= start
;
3152 emit
|= SELECTION_CHANGED
;
3155 } else if (args
->GetId () == PasswordBox::SelectionLengthProperty
) {
3156 start
= MIN (selection_anchor
, selection_cursor
);
3157 length
= args
->GetNewValue ()->AsInt32 ();
3159 if (start
+ length
> buffer
->len
) {
3160 // clamp the selection length to a valid value
3161 length
= buffer
->len
- start
;
3162 SetSelectionLength (length
);
3166 // SelectionLengthProperty is marked as AlwaysChange -
3167 // if the value hasn't actually changed, then we do
3168 // not want to emit the TextBoxModelChanged event.
3169 if (selection_cursor
!= start
+ length
) {
3170 changed
= TextBoxModelChangedSelection
;
3171 have_offset
= false;
3174 // When set programatically, anchor is always the
3175 // start and cursor is always the end.
3176 selection_cursor
= start
+ length
;
3177 selection_anchor
= start
;
3179 emit
|= SELECTION_CHANGED
;
3182 } else if (args
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
3183 changed
= TextBoxModelChangedBrush
;
3184 } else if (args
->GetId () == PasswordBox::SelectionForegroundProperty
) {
3185 changed
= TextBoxModelChangedBrush
;
3188 if (changed
!= TextBoxModelChangedNothing
)
3189 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
3191 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
3192 TextBoxBase::OnPropertyChanged (args
, error
);
3196 NotifyListenersOfPropertyChange (args
, error
);
3200 PasswordBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
3202 if (prop
&& (prop
->GetId () == PasswordBox::SelectionBackgroundProperty
||
3203 prop
->GetId () == PasswordBox::SelectionForegroundProperty
)) {
3204 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
3208 if (prop
->GetOwnerType () != Type::TEXTBOX
)
3209 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
3217 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3218 #define CURSOR_BLINK_ON_MULTIPLIER 2
3219 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3220 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3221 #define CURSOR_BLINK_DIVIDER 3
3223 TextBoxView::TextBoxView ()
3225 SetObjectType (Type::TEXTBOXVIEW
);
3227 AddHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3228 AddHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3230 SetCursor (MouseCursorIBeam
);
3232 cursor
= Rect (0, 0, 0, 0);
3233 layout
= new TextLayout ();
3234 selection_changed
= false;
3235 had_selected_text
= false;
3236 cursor_visible
= false;
3242 TextBoxView::~TextBoxView ()
3244 RemoveHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3245 RemoveHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3248 textbox
->RemoveHandler (TextBox::ModelChangedEvent
, TextBoxView::model_changed
, this);
3249 textbox
->view
= NULL
;
3252 DisconnectBlinkTimeout ();
3258 TextBoxView::GetLineFromY (double y
, int *index
)
3260 return layout
->GetLineFromY (Point (), y
, index
);
3264 TextBoxView::GetLineFromIndex (int index
)
3266 return layout
->GetLineFromIndex (index
);
3270 TextBoxView::GetCursorFromXY (double x
, double y
)
3272 return layout
->GetCursorFromXY (Point (), x
, y
);
3276 TextBoxView::blink (void *user_data
)
3278 return ((TextBoxView
*) user_data
)->Blink ();
3282 GetCursorBlinkTimeout (TextBoxView
*view
)
3284 GtkSettings
*settings
;
3291 if (!(surface
= view
->GetSurface ()))
3292 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3294 if (!(window
= surface
->GetWindow ()))
3295 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3297 if (!(widget
= window
->GetGdkWindow ()))
3298 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3300 if (!(screen
= gdk_drawable_get_screen ((GdkDrawable
*) widget
)))
3301 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3303 if (!(settings
= gtk_settings_get_for_screen (screen
)))
3304 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3306 g_object_get (settings
, "gtk-cursor-blink-time", &timeout
, NULL
);
3312 TextBoxView::ConnectBlinkTimeout (guint multiplier
)
3314 guint timeout
= GetCursorBlinkTimeout (this) * multiplier
/ CURSOR_BLINK_DIVIDER
;
3315 Surface
*surface
= GetSurface ();
3316 TimeManager
*manager
;
3318 if (!surface
|| !(manager
= surface
->GetTimeManager ()))
3321 blink_timeout
= manager
->AddTimeout (MOON_PRIORITY_DEFAULT
, timeout
, TextBoxView::blink
, this);
3325 TextBoxView::DisconnectBlinkTimeout ()
3327 TimeManager
*manager
;
3330 if (blink_timeout
!= 0) {
3331 if (!(surface
= GetSurface ()) || !(manager
= surface
->GetTimeManager ()))
3334 manager
->RemoveTimeout (blink_timeout
);
3340 TextBoxView::Blink ()
3344 SetCurrentDeployment (true);
3346 if (cursor_visible
) {
3347 multiplier
= CURSOR_BLINK_OFF_MULTIPLIER
;
3350 multiplier
= CURSOR_BLINK_ON_MULTIPLIER
;
3354 ConnectBlinkTimeout (multiplier
);
3360 TextBoxView::DelayCursorBlink ()
3362 DisconnectBlinkTimeout ();
3363 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER
);
3364 UpdateCursor (true);
3369 TextBoxView::BeginCursorBlink ()
3371 if (blink_timeout
== 0) {
3372 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER
);
3373 UpdateCursor (true);
3379 TextBoxView::EndCursorBlink ()
3381 DisconnectBlinkTimeout ();
3388 TextBoxView::ResetCursorBlink (bool delay
)
3390 if (textbox
->IsFocused () && !textbox
->HasSelectedText ()) {
3391 // cursor is blinkable... proceed with blinkage
3393 DelayCursorBlink ();
3395 BeginCursorBlink ();
3397 // cursor not blinkable... stop all blinkage
3403 TextBoxView::InvalidateCursor ()
3405 Invalidate (cursor
.Transform (&absolute_xform
));
3409 TextBoxView::ShowCursor ()
3411 cursor_visible
= true;
3412 InvalidateCursor ();
3416 TextBoxView::HideCursor ()
3418 cursor_visible
= false;
3419 InvalidateCursor ();
3423 TextBoxView::UpdateCursor (bool invalidate
)
3425 int cur
= textbox
->GetCursor ();
3429 // invalidate current cursor rect
3430 if (invalidate
&& cursor_visible
)
3431 InvalidateCursor ();
3433 // calculate the new cursor rect
3434 cursor
= layout
->GetCursor (Point (), cur
);
3436 // transform the cursor rect into absolute coordinates for the IM context
3437 rect
= cursor
.Transform (&absolute_xform
);
3438 area
= rect
.ToGdkRectangle ();
3440 gtk_im_context_set_cursor_location (textbox
->im_ctx
, &area
);
3442 textbox
->EmitCursorPositionChanged (cursor
.height
, cursor
.x
, cursor
.y
);
3444 // invalidate the new cursor rect
3445 if (invalidate
&& cursor_visible
)
3446 InvalidateCursor ();
3450 TextBoxView::UpdateText ()
3452 const char *text
= textbox
->GetDisplayText ();
3454 layout
->SetText (text
? text
: "", -1);
3458 TextBoxView::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
3460 *height
= GetActualHeight ();
3461 *width
= GetActualWidth ();
3465 TextBoxView::ComputeActualSize ()
3467 Layout (Size (INFINITY
, INFINITY
));
3470 layout
->GetActualExtents (&actual
.width
, &actual
.height
);
3476 TextBoxView::MeasureOverride (Size availableSize
)
3478 Size desired
= Size ();
3481 Layout (availableSize
);
3483 layout
->GetActualExtents (&desired
.width
, &desired
.height
);
3485 if (GetUseLayoutRounding ())
3486 desired
.width
= ceil (desired
.width
);
3488 return desired
.Min (availableSize
);
3492 TextBoxView::ArrangeOverride (Size finalSize
)
3494 Size arranged
= Size ();
3499 layout
->GetActualExtents (&arranged
.width
, &arranged
.height
);
3501 arranged
= arranged
.Max (finalSize
);
3507 TextBoxView::Layout (Size constraint
)
3509 layout
->SetMaxWidth (constraint
.width
);
3515 TextBoxView::Paint (cairo_t
*cr
)
3517 layout
->Render (cr
, GetOriginPoint (), Point ());
3519 if (cursor_visible
) {
3520 cairo_antialias_t alias
= cairo_get_antialias (cr
);
3521 Brush
*caret
= textbox
->GetCaretBrush ();
3522 double h
= round (cursor
.height
);
3523 double x
= cursor
.x
;
3524 double y
= cursor
.y
;
3526 // disable antialiasing
3527 cairo_set_antialias (cr
, CAIRO_ANTIALIAS_NONE
);
3529 // snap 'x' to the half-pixel grid (to try and get a sharp 1-pixel-wide line)
3530 cairo_user_to_device (cr
, &x
, &y
);
3531 x
= trunc (x
) + 0.5; y
= trunc (y
);
3532 cairo_device_to_user (cr
, &x
, &y
);
3534 // set the cursor color
3535 caret
->SetupBrush (cr
, cursor
);
3538 cairo_set_line_width (cr
, 1.0);
3539 cairo_move_to (cr
, x
, y
);
3540 cairo_line_to (cr
, x
, y
+ h
);
3545 // restore antialiasing
3546 cairo_set_antialias (cr
, alias
);
3551 TextBoxView::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
3553 TextBoxDynamicPropertyValueProvider
*dynamic
= (TextBoxDynamicPropertyValueProvider
*) textbox
->providers
[PropertyPrecedence_DynamicValue
];
3554 Size renderSize
= GetRenderSize ();
3556 dynamic
->InitializeSelectionBrushes ();
3559 Layout (renderSize
);
3560 UpdateCursor (false);
3564 if (selection_changed
) {
3565 layout
->Select (textbox
->GetSelectionStart (), textbox
->GetSelectionLength ());
3566 selection_changed
= false;
3570 cairo_set_matrix (cr
, &absolute_xform
);
3573 RenderLayoutClip (cr
);
3575 layout
->SetAvailableWidth (renderSize
.width
);
3581 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs
*args
)
3583 switch (args
->changed
) {
3584 case TextBoxModelChangedTextAlignment
:
3585 // text alignment changed, update our layout
3586 if (layout
->SetTextAlignment ((TextAlignment
) args
->property
->GetNewValue()->AsInt32 ()))
3589 case TextBoxModelChangedTextWrapping
:
3590 // text wrapping changed, update our layout
3591 if (layout
->SetTextWrapping ((TextWrapping
) args
->property
->GetNewValue()->AsInt32 ()))
3594 case TextBoxModelChangedSelection
:
3595 if (had_selected_text
|| textbox
->HasSelectedText ()) {
3596 // the selection has changed, update the layout's selection
3597 had_selected_text
= textbox
->HasSelectedText ();
3598 selection_changed
= true;
3599 ResetCursorBlink (false);
3601 // cursor position changed
3602 ResetCursorBlink (true);
3606 case TextBoxModelChangedBrush
:
3607 // a brush has changed, no layout updates needed, we just need to re-render
3609 case TextBoxModelChangedFont
:
3610 // font changed, need to recalculate layout/bounds
3611 layout
->ResetState ();
3614 case TextBoxModelChangedText
:
3615 // the text has changed, need to recalculate layout/bounds
3620 // nothing changed??
3625 InvalidateMeasure ();
3626 UpdateBounds (true);
3633 TextBoxView::model_changed (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3635 ((TextBoxView
*) closure
)->OnModelChanged ((TextBoxModelChangedEventArgs
*) args
);
3639 TextBoxView::OnFocusOut ()
3645 TextBoxView::OnFocusIn ()
3647 ResetCursorBlink (false);
3651 TextBoxView::OnMouseLeftButtonDown (MouseEventArgs
*args
)
3653 // proxy to our parent TextBox control
3654 textbox
->OnMouseLeftButtonDown (args
);
3658 TextBoxView::mouse_left_button_down (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3660 ((TextBoxView
*) closure
)->OnMouseLeftButtonDown ((MouseEventArgs
*) args
);
3664 TextBoxView::OnMouseLeftButtonUp (MouseEventArgs
*args
)
3666 // proxy to our parent TextBox control
3667 textbox
->OnMouseLeftButtonUp (args
);
3671 TextBoxView::mouse_left_button_up (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3673 ((TextBoxView
*) closure
)->OnMouseLeftButtonUp ((MouseEventArgs
*) args
);
3677 TextBoxView::SetTextBox (TextBoxBase
*textbox
)
3679 TextLayoutAttributes
*attrs
;
3681 if (this->textbox
== textbox
)
3684 if (this->textbox
) {
3685 // remove the event handlers from the old textbox
3686 this->textbox
->RemoveHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3689 this->textbox
= textbox
;
3692 textbox
->AddHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3694 // sync our state with the textbox
3695 layout
->SetTextAttributes (new List ());
3696 attrs
= new TextLayoutAttributes ((ITextAttributes
*) textbox
, 0);
3697 layout
->GetTextAttributes ()->Append (attrs
);
3699 layout
->SetTextAlignment (textbox
->GetTextAlignment ());
3700 layout
->SetTextWrapping (textbox
->GetTextWrapping ());
3701 had_selected_text
= textbox
->HasSelectedText ();
3702 selection_changed
= true;
3705 layout
->SetTextAttributes (NULL
);
3706 layout
->SetText (NULL
, -1);
3709 UpdateBounds (true);
3710 InvalidateMeasure ();