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);
572 font
= new TextFontDescription ();
573 font
->SetFamily (GetFontFamily ()->source
);
574 font
->SetStretch (GetFontStretch ()->stretch
);
575 font
->SetWeight (GetFontWeight ()->weight
);
576 font
->SetStyle (GetFontStyle ()->style
);
577 font
->SetSize (GetFontSize ());
579 downloaders
= g_ptr_array_new ();
582 contentElement
= NULL
;
584 im_ctx
= gtk_im_multicontext_new ();
585 gtk_im_context_set_use_preedit (im_ctx
, false);
587 g_signal_connect (im_ctx
, "retrieve-surrounding", G_CALLBACK (TextBoxBase::retrieve_surrounding
), this);
588 g_signal_connect (im_ctx
, "delete-surrounding", G_CALLBACK (TextBoxBase::delete_surrounding
), this);
589 g_signal_connect (im_ctx
, "commit", G_CALLBACK (TextBoxBase::commit
), this);
591 undo
= new TextBoxUndoStack (10);
592 redo
= new TextBoxUndoStack (10);
593 buffer
= new TextBuffer ();
596 emit
= NOTHING_CHANGED
;
599 selection_anchor
= 0;
600 selection_cursor
= 0;
604 accepts_return
= false;
605 need_im_reset
= false;
606 is_read_only
= false;
617 TextBoxBase::~TextBoxBase ()
619 RemoveHandler (UIElement::MouseLeftButtonMultiClickEvent
, TextBoxBase::mouse_left_button_multi_click
, this);
622 g_object_unref (im_ctx
);
624 CleanupDownloaders ();
625 g_ptr_array_free (downloaders
, true);
626 g_free (font_source
);
635 TextBoxBase::SetSurface (Surface
*surface
)
637 Control::SetSurface (surface
);
640 gtk_im_context_set_client_window (im_ctx
, GetGdkWindow (this));
644 TextBoxBase::CleanupDownloaders ()
646 Downloader
*downloader
;
649 for (i
= 0; i
< downloaders
->len
; i
++) {
650 downloader
= (Downloader
*) downloaders
->pdata
[i
];
651 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
652 downloader
->Abort ();
653 downloader
->unref ();
656 g_ptr_array_set_size (downloaders
, 0);
660 TextBoxBase::GetCursorOffset ()
662 if (!have_offset
&& view
) {
663 cursor_offset
= view
->GetCursor ().x
;
667 return cursor_offset
;
671 TextBoxBase::CursorDown (int cursor
, bool page
)
673 double y
= view
->GetCursor ().y
;
674 double x
= GetCursorOffset ();
675 TextLayoutLine
*line
;
680 if (!(line
= view
->GetLineFromY (y
, &index
)))
684 // calculate the number of lines to skip over
685 n
= GetActualHeight () / line
->height
;
690 if (index
+ n
>= view
->GetLineCount ()) {
691 // go to the end of the last line
692 line
= view
->GetLineFromIndex (view
->GetLineCount () - 1);
694 for (cur
= line
->offset
, i
= 0; i
< line
->runs
->len
; i
++) {
695 run
= (TextLayoutRun
*) line
->runs
->pdata
[i
];
704 line
= view
->GetLineFromIndex (index
+ n
);
706 return line
->GetCursorFromX (Point (), x
);
710 TextBoxBase::CursorUp (int cursor
, bool page
)
712 double y
= view
->GetCursor ().y
;
713 double x
= GetCursorOffset ();
714 TextLayoutLine
*line
;
717 if (!(line
= view
->GetLineFromY (y
, &index
)))
721 // calculate the number of lines to skip over
722 n
= GetActualHeight () / line
->height
;
728 // go to the beginning of the first line
733 line
= view
->GetLineFromIndex (index
- n
);
735 return line
->GetCursorFromX (Point (), x
);
742 CharClassAlphaNumeric
745 static inline CharClass
746 char_class (gunichar c
)
748 if (g_unichar_isspace (c
))
749 return CharClassWhitespace
;
751 if (g_unichar_isalnum (c
))
752 return CharClassAlphaNumeric
;
754 return CharClassUnknown
;
758 is_start_of_word (TextBuffer
*buffer
, int index
)
760 // A 'word' starts with an AlphaNumeric immediately preceeded by lwsp
761 if (index
> 0 && !g_unichar_isspace (buffer
->text
[index
- 1]))
764 return g_unichar_isalnum (buffer
->text
[index
]);
769 TextBoxBase::CursorNextWord (int cursor
)
773 // find the end of the current line
774 cr
= CursorLineEnd (cursor
);
775 if (buffer
->text
[cr
] == '\r' && buffer
->text
[cr
+ 1] == '\n')
780 // if the cursor is at the end of the line, return the starting offset of the next line
781 if (cursor
== cr
|| cursor
== lf
) {
782 if (lf
< buffer
->len
)
789 CharClass cc
= char_class (buffer
->text
[cursor
]);
792 // skip over the word, punctuation, or run of whitespace
793 while (i
< cr
&& char_class (buffer
->text
[i
]) == cc
)
796 // skip any whitespace after the word/punct
797 while (i
< cr
&& g_unichar_isspace (buffer
->text
[i
]))
802 // skip to the end of the current word
803 while (i
< cr
&& !g_unichar_isspace (buffer
->text
[i
]))
806 // skip any whitespace after the word
807 while (i
< cr
&& g_unichar_isspace (buffer
->text
[i
]))
810 // find the start of the next word
811 while (i
< cr
&& !is_start_of_word (buffer
, i
))
819 TextBoxBase::CursorPrevWord (int cursor
)
821 int begin
, i
, cr
, lf
;
823 // find the beginning of the current line
824 lf
= CursorLineBegin (cursor
) - 1;
826 if (lf
> 0 && buffer
->text
[lf
] == '\n' && buffer
->text
[lf
- 1] == '\r')
831 // if the cursor is at the beginning of the line, return the end of the prev line
832 if (cursor
- 1 == lf
) {
840 CharClass cc
= char_class (buffer
->text
[cursor
- 1]);
844 // skip over the word, punctuation, or run of whitespace
845 while (i
> begin
&& char_class (buffer
->text
[i
- 1]) == cc
)
848 // if the cursor was at whitespace, skip back a word too
849 if (cc
== CharClassWhitespace
&& i
> begin
) {
850 cc
= char_class (buffer
->text
[i
- 1]);
851 while (i
> begin
&& char_class (buffer
->text
[i
- 1]) == cc
)
858 if (cursor
< buffer
->len
) {
859 // skip to the beginning of this word
860 while (i
> begin
&& !g_unichar_isspace (buffer
->text
[i
- 1]))
863 if (i
< cursor
&& is_start_of_word (buffer
, i
))
867 // skip to the start of the lwsp
868 while (i
> begin
&& g_unichar_isspace (buffer
->text
[i
- 1]))
874 // skip to the beginning of the word
875 while (i
> begin
&& !is_start_of_word (buffer
, i
))
883 TextBoxBase::CursorLineBegin (int cursor
)
887 // find the beginning of the line
888 while (cur
> 0 && !IsEOL (buffer
->text
[cur
- 1]))
895 TextBoxBase::CursorLineEnd (int cursor
, bool include
)
899 // find the end of the line
900 while (cur
< buffer
->len
&& !IsEOL (buffer
->text
[cur
]))
903 if (include
&& cur
< buffer
->len
) {
904 if (buffer
->text
[cur
] == '\r' && buffer
->text
[cur
+ 1] == '\n')
914 TextBoxBase::KeyPressBackSpace (GdkModifierType modifiers
)
916 int anchor
= selection_anchor
;
917 int cursor
= selection_cursor
;
918 TextBoxUndoAction
*action
;
919 int start
= 0, length
= 0;
920 bool handled
= false;
922 if ((modifiers
& (ALT_MASK
| SHIFT_MASK
)) != 0)
925 if (cursor
!= anchor
) {
926 // BackSpace w/ active selection: delete the selected text
927 length
= abs (cursor
- anchor
);
928 start
= MIN (anchor
, cursor
);
929 } else if ((modifiers
& CONTROL_MASK
) != 0) {
930 // Ctrl+BackSpace: delete the word ending at the cursor
931 start
= CursorPrevWord (cursor
);
932 length
= cursor
- start
;
933 } else if (cursor
> 0) {
934 // BackSpace: delete the char before the cursor position
935 if (cursor
>= 2 && buffer
->text
[cursor
- 1] == '\n' && buffer
->text
[cursor
- 2] == '\r') {
945 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
949 buffer
->Cut (start
, length
);
950 emit
|= TEXT_CHANGED
;
956 // check to see if selection has changed
957 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
958 SetSelectionStart (MIN (anchor
, cursor
));
959 SetSelectionLength (abs (cursor
- anchor
));
960 selection_anchor
= anchor
;
961 selection_cursor
= cursor
;
962 emit
|= SELECTION_CHANGED
;
970 TextBoxBase::KeyPressDelete (GdkModifierType modifiers
)
972 int anchor
= selection_anchor
;
973 int cursor
= selection_cursor
;
974 TextBoxUndoAction
*action
;
975 int start
= 0, length
= 0;
976 bool handled
= false;
978 if ((modifiers
& (ALT_MASK
| SHIFT_MASK
)) != 0)
981 if (cursor
!= anchor
) {
982 // Delete w/ active selection: delete the selected text
983 length
= abs (cursor
- anchor
);
984 start
= MIN (anchor
, cursor
);
985 } else if ((modifiers
& CONTROL_MASK
) != 0) {
986 // Ctrl+Delete: delete the word starting at the cursor
987 length
= CursorNextWord (cursor
) - cursor
;
989 } else if (cursor
< buffer
->len
) {
990 // Delete: delete the char after the cursor position
991 if (buffer
->text
[cursor
] == '\r' && buffer
->text
[cursor
+ 1] == '\n')
1000 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
1001 undo
->Push (action
);
1004 buffer
->Cut (start
, length
);
1005 emit
|= TEXT_CHANGED
;
1009 // check to see if selection has changed
1010 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1011 SetSelectionStart (MIN (anchor
, cursor
));
1012 SetSelectionLength (abs (cursor
- anchor
));
1013 selection_anchor
= anchor
;
1014 selection_cursor
= cursor
;
1015 emit
|= SELECTION_CHANGED
;
1023 TextBoxBase::KeyPressPageDown (GdkModifierType modifiers
)
1025 int anchor
= selection_anchor
;
1026 int cursor
= selection_cursor
;
1027 bool handled
= false;
1030 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1033 // move the cursor down one page from its current position
1034 cursor
= CursorDown (cursor
, true);
1037 if ((modifiers
& SHIFT_MASK
) == 0) {
1038 // clobber the selection
1042 // check to see if selection has changed
1043 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1044 SetSelectionStart (MIN (anchor
, cursor
));
1045 SetSelectionLength (abs (cursor
- anchor
));
1046 selection_anchor
= anchor
;
1047 selection_cursor
= cursor
;
1048 emit
|= SELECTION_CHANGED
;
1057 TextBoxBase::KeyPressPageUp (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 up one page from its current position
1068 cursor
= CursorUp (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::KeyPressDown (GdkModifierType modifiers
)
1093 int anchor
= selection_anchor
;
1094 int cursor
= selection_cursor
;
1095 bool handled
= false;
1098 if (!accepts_return
|| (modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1101 // move the cursor down by one line from its current position
1102 cursor
= CursorDown (cursor
, false);
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::KeyPressUp (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 up by one line from its current position
1136 cursor
= CursorUp (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::KeyPressHome (GdkModifierType modifiers
)
1161 int anchor
= selection_anchor
;
1162 int cursor
= selection_cursor
;
1163 bool handled
= false;
1165 if ((modifiers
& ALT_MASK
) != 0)
1168 if ((modifiers
& CONTROL_MASK
) != 0) {
1169 // move the cursor to the beginning of the buffer
1172 // move the cursor to the beginning of the line
1173 cursor
= CursorLineBegin (cursor
);
1176 if ((modifiers
& SHIFT_MASK
) == 0) {
1177 // clobber the selection
1181 // check to see if selection has changed
1182 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1183 SetSelectionStart (MIN (anchor
, cursor
));
1184 SetSelectionLength (abs (cursor
- anchor
));
1185 selection_anchor
= anchor
;
1186 selection_cursor
= cursor
;
1187 emit
|= SELECTION_CHANGED
;
1188 have_offset
= false;
1196 TextBoxBase::KeyPressEnd (GdkModifierType modifiers
)
1198 int anchor
= selection_anchor
;
1199 int cursor
= selection_cursor
;
1200 bool handled
= false;
1202 if ((modifiers
& ALT_MASK
) != 0)
1205 if ((modifiers
& CONTROL_MASK
) != 0) {
1206 // move the cursor to the end of the buffer
1207 cursor
= buffer
->len
;
1209 // move the cursor to the end of the line
1210 cursor
= CursorLineEnd (cursor
);
1213 if ((modifiers
& SHIFT_MASK
) == 0) {
1214 // clobber the selection
1218 // check to see if selection has changed
1219 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1220 SetSelectionStart (MIN (anchor
, cursor
));
1221 SetSelectionLength (abs (cursor
- anchor
));
1222 selection_anchor
= anchor
;
1223 selection_cursor
= cursor
;
1224 emit
|= SELECTION_CHANGED
;
1232 TextBoxBase::KeyPressRight (GdkModifierType modifiers
)
1234 int anchor
= selection_anchor
;
1235 int cursor
= selection_cursor
;
1236 bool handled
= false;
1238 if ((modifiers
& ALT_MASK
) != 0)
1241 if ((modifiers
& CONTROL_MASK
) != 0) {
1242 // move the cursor to beginning of the next word
1243 cursor
= CursorNextWord (cursor
);
1244 } else if ((modifiers
& SHIFT_MASK
) == 0 && anchor
!= cursor
) {
1245 // set cursor at end of selection
1246 cursor
= MAX (anchor
, cursor
);
1248 // move the cursor forward one character
1249 if (buffer
->text
[cursor
] == '\r' && buffer
->text
[cursor
+ 1] == '\n')
1251 else if (cursor
< buffer
->len
)
1255 if ((modifiers
& SHIFT_MASK
) == 0) {
1256 // clobber the selection
1260 // check to see if selection has changed
1261 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1262 SetSelectionStart (MIN (anchor
, cursor
));
1263 SetSelectionLength (abs (cursor
- anchor
));
1264 selection_anchor
= anchor
;
1265 selection_cursor
= cursor
;
1266 emit
|= SELECTION_CHANGED
;
1274 TextBoxBase::KeyPressLeft (GdkModifierType modifiers
)
1276 int anchor
= selection_anchor
;
1277 int cursor
= selection_cursor
;
1278 bool handled
= false;
1280 if ((modifiers
& ALT_MASK
) != 0)
1283 if ((modifiers
& CONTROL_MASK
) != 0) {
1284 // move the cursor to the beginning of the previous word
1285 cursor
= CursorPrevWord (cursor
);
1286 } else if ((modifiers
& SHIFT_MASK
) == 0 && anchor
!= cursor
) {
1287 // set cursor at start of selection
1288 cursor
= MIN (anchor
, cursor
);
1290 // move the cursor backward one character
1291 if (cursor
>= 2 && buffer
->text
[cursor
- 2] == '\r' && buffer
->text
[cursor
- 1] == '\n')
1293 else if (cursor
> 0)
1297 if ((modifiers
& SHIFT_MASK
) == 0) {
1298 // clobber the selection
1302 // check to see if selection has changed
1303 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1304 SetSelectionStart (MIN (anchor
, cursor
));
1305 SetSelectionLength (abs (cursor
- anchor
));
1306 selection_anchor
= anchor
;
1307 selection_cursor
= cursor
;
1308 emit
|= SELECTION_CHANGED
;
1316 TextBoxBase::KeyPressUnichar (gunichar c
)
1318 int length
= abs (selection_cursor
- selection_anchor
);
1319 int start
= MIN (selection_anchor
, selection_cursor
);
1320 int anchor
= selection_anchor
;
1321 int cursor
= selection_cursor
;
1322 TextBoxUndoAction
*action
;
1324 if ((max_length
> 0 && buffer
->len
>= max_length
) || ((c
== '\r') && !accepts_return
))
1328 // replace the currently selected text
1329 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, c
);
1330 undo
->Push (action
);
1333 buffer
->Replace (start
, length
, &c
, 1);
1335 // insert the text at the cursor position
1336 TextBoxUndoActionInsert
*insert
= NULL
;
1338 if ((action
= undo
->Peek ()) && action
->type
== TextBoxUndoActionTypeInsert
) {
1339 insert
= (TextBoxUndoActionInsert
*) action
;
1341 if (!insert
->Insert (start
, c
))
1346 insert
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, c
);
1347 undo
->Push (insert
);
1352 buffer
->Insert (start
, c
);
1355 emit
|= TEXT_CHANGED
;
1359 // check to see if selection has changed
1360 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1361 SetSelectionStart (MIN (anchor
, cursor
));
1362 SetSelectionLength (abs (cursor
- anchor
));
1363 selection_anchor
= anchor
;
1364 selection_cursor
= cursor
;
1365 emit
|= SELECTION_CHANGED
;
1371 TextBoxBase::BatchPush ()
1377 TextBoxBase::BatchPop ()
1380 g_warning ("TextBoxBase batch underflow");
1388 TextBoxBase::SyncAndEmit (bool sync_text
)
1390 if (batch
!= 0 || emit
== NOTHING_CHANGED
)
1393 if (sync_text
&& (emit
& TEXT_CHANGED
))
1396 if (emit
& SELECTION_CHANGED
)
1397 SyncSelectedText ();
1400 // eliminate events that we can't emit
1401 emit
&= events_mask
;
1403 if (emit
& TEXT_CHANGED
)
1406 if (emit
& SELECTION_CHANGED
)
1407 EmitSelectionChanged ();
1410 emit
= NOTHING_CHANGED
;
1414 TextBoxBase::Paste (GtkClipboard
*clipboard
, const char *str
)
1416 int length
= abs (selection_cursor
- selection_anchor
);
1417 int start
= MIN (selection_anchor
, selection_cursor
);
1418 TextBoxUndoAction
*action
;
1422 if (!(text
= g_utf8_to_ucs4_fast (str
? str
: "", -1, &len
)))
1425 if (max_length
> 0 && ((buffer
->len
- length
) + len
> max_length
)) {
1426 // paste cannot exceed MaxLength
1427 len
= max_length
- (buffer
->len
- length
);
1429 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (len
+ 1));
1436 // only paste the content of the first line
1437 for (i
= 0; i
< len
; i
++) {
1438 if (text
[i
] == '\r' || text
[i
] == '\n' || text
[i
] == 0x2028) {
1439 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (i
+ 1));
1450 // replace the currently selected text
1451 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, len
);
1453 buffer
->Replace (start
, length
, text
, len
);
1454 } else if (len
> 0) {
1455 // insert the text at the cursor position
1456 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, len
, true);
1458 buffer
->Insert (start
, text
, len
);
1464 undo
->Push (action
);
1468 emit
|= TEXT_CHANGED
;
1472 SetSelectionStart (start
);
1473 SetSelectionLength (0);
1480 TextBoxBase::paste (GtkClipboard
*clipboard
, const char *text
, gpointer closure
)
1482 ((TextBoxBase
*) closure
)->Paste (clipboard
, text
);
1486 TextBoxBase::OnKeyDown (KeyEventArgs
*args
)
1488 GdkModifierType modifiers
= (GdkModifierType
) args
->GetModifiers ();
1489 guint key
= args
->GetKeyVal ();
1490 GtkClipboard
*clipboard
;
1491 bool handled
= false;
1493 if (args
->IsModifier ())
1496 // set 'emit' to NOTHING_CHANGED so that we can figure out
1497 // what has chanegd after applying the changes that this
1498 // keypress will cause.
1499 emit
= NOTHING_CHANGED
;
1507 handled
= KeyPressBackSpace (modifiers
);
1513 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == SHIFT_MASK
) {
1514 // Shift+Delete => Cut
1515 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1516 if (selection_cursor
!= selection_anchor
) {
1517 // copy selection to the clipboard and then cut
1518 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1522 SetSelectedText ("");
1525 handled
= KeyPressDelete (modifiers
);
1529 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == SHIFT_MASK
) {
1530 // Shift+Insert => Paste
1534 if ((clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1535 // paste clipboard contents to the buffer
1536 gtk_clipboard_request_text (clipboard
, TextBoxBase::paste
, this);
1540 } else if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == CONTROL_MASK
) {
1541 // Control+Insert => Copy
1542 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1543 if (selection_cursor
!= selection_anchor
) {
1544 // copy selection to the clipboard
1545 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1552 case GDK_KP_Page_Down
:
1554 handled
= KeyPressPageDown (modifiers
);
1556 case GDK_KP_Page_Up
:
1558 handled
= KeyPressPageUp (modifiers
);
1562 handled
= KeyPressHome (modifiers
);
1566 handled
= KeyPressEnd (modifiers
);
1570 handled
= KeyPressRight (modifiers
);
1574 handled
= KeyPressLeft (modifiers
);
1578 handled
= KeyPressDown (modifiers
);
1582 handled
= KeyPressUp (modifiers
);
1585 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == CONTROL_MASK
) {
1589 // Ctrl+A => Select All
1596 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1597 if (selection_cursor
!= selection_anchor
) {
1598 // copy selection to the clipboard
1599 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1611 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1612 if (selection_cursor
!= selection_anchor
) {
1613 // copy selection to the clipboard and then cut
1614 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1618 SetSelectedText ("");
1627 if ((clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1628 // paste clipboard contents to the buffer
1629 gtk_clipboard_request_text (clipboard
, TextBoxBase::paste
, this);
1637 if (!is_read_only
) {
1645 if (!is_read_only
) {
1651 // unhandled Control commands
1659 args
->SetHandled (handled
);
1669 TextBoxBase::PostOnKeyDown (KeyEventArgs
*args
)
1671 guint key
= args
->GetKeyVal ();
1674 // Note: we don't set Handled=true because anything we handle here, we
1675 // want to bubble up.
1677 if (!is_read_only
&& gtk_im_context_filter_keypress (im_ctx
, args
->GetEvent ())) {
1678 need_im_reset
= true;
1682 if (is_read_only
|| args
->IsModifier ())
1685 // set 'emit' to NOTHING_CHANGED so that we can figure out
1686 // what has chanegd after applying the changes that this
1687 // keypress will cause.
1688 emit
= NOTHING_CHANGED
;
1693 KeyPressUnichar ('\r');
1696 if ((args
->GetModifiers () & (CONTROL_MASK
| ALT_MASK
)) == 0) {
1697 // normal character input
1698 if ((c
= args
->GetUnicode ()))
1699 KeyPressUnichar (c
);
1710 TextBoxBase::OnKeyUp (KeyEventArgs
*args
)
1712 if (!is_read_only
) {
1713 if (gtk_im_context_filter_keypress (im_ctx
, args
->GetEvent ()))
1714 need_im_reset
= true;
1719 TextBoxBase::DeleteSurrounding (int offset
, int n_chars
)
1721 const char *delete_start
, *delete_end
;
1722 const char *text
= GetActualText ();
1723 int anchor
= selection_anchor
;
1724 int cursor
= selection_cursor
;
1725 TextBoxUndoAction
*action
;
1731 // get the utf-8 pointers so that we can use them to get gunichar offsets
1732 delete_start
= g_utf8_offset_to_pointer (text
, selection_cursor
) + offset
;
1733 delete_end
= delete_start
+ n_chars
;
1735 // get the character length/start index
1736 length
= g_utf8_pointer_to_offset (delete_start
, delete_end
);
1737 start
= g_utf8_pointer_to_offset (text
, delete_start
);
1740 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
1741 undo
->Push (action
);
1744 buffer
->Cut (start
, length
);
1745 emit
|= TEXT_CHANGED
;
1752 // check to see if selection has changed
1753 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1754 SetSelectionStart (MIN (anchor
, cursor
));
1755 SetSelectionLength (abs (cursor
- anchor
));
1756 selection_anchor
= anchor
;
1757 selection_cursor
= cursor
;
1758 emit
|= SELECTION_CHANGED
;
1769 TextBoxBase::delete_surrounding (GtkIMContext
*context
, int offset
, int n_chars
, gpointer user_data
)
1771 return ((TextBoxBase
*) user_data
)->DeleteSurrounding (offset
, n_chars
);
1775 TextBoxBase::RetrieveSurrounding ()
1777 const char *text
= GetActualText ();
1778 const char *cursor
= g_utf8_offset_to_pointer (text
, selection_cursor
);
1780 gtk_im_context_set_surrounding (im_ctx
, text
, -1, cursor
- text
);
1786 TextBoxBase::retrieve_surrounding (GtkIMContext
*context
, gpointer user_data
)
1788 return ((TextBoxBase
*) user_data
)->RetrieveSurrounding ();
1792 TextBoxBase::Commit (const char *str
)
1794 int length
= abs (selection_cursor
- selection_anchor
);
1795 int start
= MIN (selection_anchor
, selection_cursor
);
1796 TextBoxUndoAction
*action
;
1804 if (!(text
= g_utf8_to_ucs4_fast (str
? str
: "", -1, &len
)))
1807 if (max_length
> 0 && ((buffer
->len
- length
) + len
> max_length
)) {
1808 // paste cannot exceed MaxLength
1809 len
= max_length
- (buffer
->len
- length
);
1811 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (len
+ 1));
1818 // only paste the content of the first line
1819 for (i
= 0; i
< len
; i
++) {
1820 if (g_unichar_type (text
[i
]) == G_UNICODE_LINE_SEPARATOR
) {
1821 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (i
+ 1));
1830 // replace the currently selected text
1831 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, len
);
1832 undo
->Push (action
);
1835 buffer
->Replace (start
, length
, text
, len
);
1836 } else if (len
> 0) {
1837 // insert the text at the cursor position
1838 TextBoxUndoActionInsert
*insert
= NULL
;
1840 buffer
->Insert (start
, text
, len
);
1842 if ((action
= undo
->Peek ()) && action
->type
== TextBoxUndoActionTypeInsert
) {
1843 insert
= (TextBoxUndoActionInsert
*) action
;
1845 if (!insert
->Insert (start
, text
, len
))
1850 insert
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, len
);
1851 undo
->Push (insert
);
1860 emit
= TEXT_CHANGED
;
1861 cursor
= start
+ len
;
1867 // check to see if selection has changed
1868 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1869 SetSelectionStart (MIN (anchor
, cursor
));
1870 SetSelectionLength (abs (cursor
- anchor
));
1871 selection_anchor
= anchor
;
1872 selection_cursor
= cursor
;
1873 emit
|= SELECTION_CHANGED
;
1882 TextBoxBase::commit (GtkIMContext
*context
, const char *str
, gpointer user_data
)
1884 ((TextBoxBase
*) user_data
)->Commit (str
);
1888 TextBoxBase::ResetIMContext ()
1890 if (need_im_reset
) {
1891 gtk_im_context_reset (im_ctx
);
1892 need_im_reset
= false;
1897 TextBoxBase::OnMouseLeftButtonDown (MouseButtonEventArgs
*args
)
1902 args
->SetHandled (true);
1906 args
->GetPosition (view
, &x
, &y
);
1908 cursor
= view
->GetCursorFromXY (x
, y
);
1912 // Single-Click: cursor placement
1913 captured
= CaptureMouse ();
1917 emit
= NOTHING_CHANGED
;
1918 SetSelectionStart (cursor
);
1919 SetSelectionLength (0);
1927 TextBoxBase::OnMouseLeftButtonMultiClick (MouseButtonEventArgs
*args
)
1929 int cursor
, start
, end
;
1932 args
->SetHandled (true);
1935 args
->GetPosition (view
, &x
, &y
);
1937 cursor
= view
->GetCursorFromXY (x
, y
);
1941 if (args
->GetClickCount () == 3) {
1942 // Note: Silverlight doesn't implement this, but to
1943 // be consistent with other TextEntry-type
1944 // widgets in Gtk+, we will.
1946 // Triple-Click: select the line
1948 ReleaseMouseCapture ();
1949 start
= CursorLineBegin (cursor
);
1950 end
= CursorLineEnd (cursor
, true);
1954 // Double-Click: select the word
1956 ReleaseMouseCapture ();
1957 start
= CursorPrevWord (cursor
);
1958 end
= CursorNextWord (cursor
);
1964 emit
= NOTHING_CHANGED
;
1965 SetSelectionStart (start
);
1966 SetSelectionLength (end
- start
);
1974 TextBoxBase::mouse_left_button_multi_click (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
1976 ((TextBoxBase
*) closure
)->OnMouseLeftButtonMultiClick ((MouseButtonEventArgs
*) args
);
1980 TextBoxBase::OnMouseLeftButtonUp (MouseButtonEventArgs
*args
)
1983 ReleaseMouseCapture ();
1985 args
->SetHandled (true);
1991 TextBoxBase::OnMouseMove (MouseEventArgs
*args
)
1993 int anchor
= selection_anchor
;
1994 int cursor
= selection_cursor
;
1998 args
->GetPosition (view
, &x
, &y
);
1999 args
->SetHandled (true);
2001 cursor
= view
->GetCursorFromXY (x
, y
);
2004 emit
= NOTHING_CHANGED
;
2005 SetSelectionStart (MIN (anchor
, cursor
));
2006 SetSelectionLength (abs (cursor
- anchor
));
2007 selection_anchor
= anchor
;
2008 selection_cursor
= cursor
;
2016 TextBoxBase::OnLostFocus (RoutedEventArgs
*args
)
2019 emit
= NOTHING_CHANGED
;
2020 SetSelectionStart (selection_cursor
);
2021 SetSelectionLength (0);
2029 view
->OnLostFocus ();
2031 if (!is_read_only
) {
2032 gtk_im_context_focus_out (im_ctx
);
2033 need_im_reset
= true;
2038 TextBoxBase::OnGotFocus (RoutedEventArgs
*args
)
2043 view
->OnGotFocus ();
2045 if (!is_read_only
) {
2046 gtk_im_context_focus_in (im_ctx
);
2047 need_im_reset
= true;
2052 TextBoxBase::EmitCursorPositionChanged (double height
, double x
, double y
)
2054 Emit (TextBoxBase::CursorPositionChangedEvent
, new CursorPositionChangedEventArgs (height
, x
, y
));
2058 TextBoxBase::DownloaderComplete (Downloader
*downloader
)
2060 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2061 char *resource
, *filename
;
2062 InternalDownloader
*idl
;
2066 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2067 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
2072 if (!(idl
= downloader
->GetInternalDownloader ()))
2075 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
2078 uri
= downloader
->GetUri ();
2080 // If the downloaded file was a zip file, this'll get the path to the
2081 // extracted zip directory, else it will simply be the path to the
2083 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
2086 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
2087 manager
->AddResource (resource
, path
);
2090 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont
, NULL
));
2094 TextBoxBase::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
2096 ((TextBoxBase
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
2100 TextBoxBase::AddFontSource (Downloader
*downloader
)
2102 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
2103 g_ptr_array_add (downloaders
, downloader
);
2106 if (downloader
->Started () || downloader
->Completed ()) {
2107 if (downloader
->Completed ())
2108 DownloaderComplete (downloader
);
2110 // This is what actually triggers the download
2111 downloader
->Send ();
2116 TextBoxBase::AddFontResource (const char *resource
)
2118 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2119 Application
*application
= Application::GetCurrent ();
2120 Downloader
*downloader
;
2127 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
2128 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
2129 downloader
->Open ("GET", resource
, FontPolicy
);
2130 AddFontSource (downloader
);
2131 downloader
->unref ();
2139 manager
->AddResource (resource
, path
);
2145 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2147 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2149 if (args
->GetId () == Control::FontFamilyProperty
) {
2150 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
2151 char **families
, *fragment
;
2154 CleanupDownloaders ();
2156 if (family
&& family
->source
) {
2157 families
= g_strsplit (family
->source
, ",", -1);
2158 for (i
= 0; families
[i
]; i
++) {
2159 g_strstrip (families
[i
]);
2160 if ((fragment
= strchr (families
[i
], '#'))) {
2161 // the first portion of this string is the resource name...
2163 AddFontResource (families
[i
]);
2166 g_strfreev (families
);
2169 font
->SetFamily (family
? family
->source
: NULL
);
2170 changed
= TextBoxModelChangedFont
;
2171 } else if (args
->GetId () == Control::FontSizeProperty
) {
2172 double size
= args
->GetNewValue()->AsDouble ();
2173 changed
= TextBoxModelChangedFont
;
2174 font
->SetSize (size
);
2175 } else if (args
->GetId () == Control::FontStretchProperty
) {
2176 FontStretches stretch
= args
->GetNewValue()->AsFontStretch()->stretch
;
2177 changed
= TextBoxModelChangedFont
;
2178 font
->SetStretch (stretch
);
2179 } else if (args
->GetId () == Control::FontStyleProperty
) {
2180 FontStyles style
= args
->GetNewValue()->AsFontStyle ()->style
;
2181 changed
= TextBoxModelChangedFont
;
2182 font
->SetStyle (style
);
2183 } else if (args
->GetId () == Control::FontWeightProperty
) {
2184 FontWeights weight
= args
->GetNewValue()->AsFontWeight ()->weight
;
2185 changed
= TextBoxModelChangedFont
;
2186 font
->SetWeight (weight
);
2187 } else if (args
->GetId () == FrameworkElement::MinHeightProperty
) {
2188 // pass this along to our TextBoxView
2190 view
->SetMinHeight (args
->GetNewValue ()->AsDouble ());
2191 } else if (args
->GetId () == FrameworkElement::MaxHeightProperty
) {
2192 // pass this along to our TextBoxView
2194 view
->SetMaxHeight (args
->GetNewValue ()->AsDouble ());
2195 } else if (args
->GetId () == FrameworkElement::MinWidthProperty
) {
2196 // pass this along to our TextBoxView
2198 view
->SetMinWidth (args
->GetNewValue ()->AsDouble ());
2199 } else if (args
->GetId () == FrameworkElement::MaxWidthProperty
) {
2200 // pass this along to our TextBoxView
2202 view
->SetMaxWidth (args
->GetNewValue ()->AsDouble ());
2203 } else if (args
->GetId () == FrameworkElement::HeightProperty
) {
2204 // pass this along to our TextBoxView
2206 view
->SetHeight (args
->GetNewValue ()->AsDouble ());
2207 } else if (args
->GetId () == FrameworkElement::WidthProperty
) {
2208 // pass this along to our TextBoxView
2210 view
->SetWidth (args
->GetNewValue ()->AsDouble ());
2213 if (changed
!= TextBoxModelChangedNothing
)
2214 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2216 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE
) {
2217 Control::OnPropertyChanged (args
, error
);
2221 NotifyListenersOfPropertyChange (args
, error
);
2225 TextBoxBase::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2227 if (prop
&& (prop
->GetId () == Control::BackgroundProperty
||
2228 prop
->GetId () == Control::ForegroundProperty
)) {
2229 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2233 if (prop
->GetOwnerType () != Type::TEXTBOXBASE
)
2234 Control::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2238 TextBoxBase::OnApplyTemplate ()
2240 contentElement
= GetTemplateChild ("ContentElement");
2242 if (contentElement
== NULL
) {
2243 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2244 Control::OnApplyTemplate ();
2248 view
= new TextBoxView ();
2249 view
->SetTextBox (this);
2251 view
->SetMinHeight (GetMinHeight ());
2252 view
->SetMaxHeight (GetMaxHeight ());
2253 view
->SetMinWidth (GetMinWidth ());
2254 view
->SetMaxWidth (GetMaxWidth ());
2255 view
->SetHeight (GetHeight ());
2256 view
->SetWidth (GetWidth ());
2258 // Insert our TextBoxView
2259 if (contentElement
->Is (Type::CONTENTCONTROL
)) {
2260 ContentControl
*control
= (ContentControl
*) contentElement
;
2262 control
->SetValue (ContentControl::ContentProperty
, Value (view
));
2263 } else if (contentElement
->Is (Type::BORDER
)) {
2264 Border
*border
= (Border
*) contentElement
;
2266 border
->SetValue (Border::ChildProperty
, Value (view
));
2267 } else if (contentElement
->Is (Type::PANEL
)) {
2268 DependencyObjectCollection
*children
= ((Panel
*) contentElement
)->GetChildren ();
2270 children
->Add (view
);
2272 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentELement of type %s",
2273 contentElement
->GetType ()->GetName ());
2278 Control::OnApplyTemplate ();
2282 TextBoxBase::ClearSelection (int start
)
2285 SetSelectionStart (start
);
2286 SetSelectionLength (0);
2291 TextBoxBase::SelectWithError (int start
, int length
, MoonError
*error
)
2294 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection start must be >= 0");
2299 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection length must be >= 0");
2303 if (start
> buffer
->len
)
2304 start
= buffer
->len
;
2306 if (length
> (buffer
->len
- start
))
2307 length
= (buffer
->len
- start
);
2310 SetSelectionStart (start
);
2311 SetSelectionLength (length
);
2322 TextBoxBase::SelectAll ()
2324 SelectWithError (0, buffer
->len
, NULL
);
2328 TextBoxBase::CanUndo ()
2330 return !undo
->IsEmpty ();
2334 TextBoxBase::CanRedo ()
2336 return !redo
->IsEmpty ();
2340 TextBoxBase::Undo ()
2342 TextBoxUndoActionReplace
*replace
;
2343 TextBoxUndoActionInsert
*insert
;
2344 TextBoxUndoActionDelete
*dele
;
2345 TextBoxUndoAction
*action
;
2348 if (undo
->IsEmpty ())
2351 action
= undo
->Pop ();
2352 redo
->Push (action
);
2354 switch (action
->type
) {
2355 case TextBoxUndoActionTypeInsert
:
2356 insert
= (TextBoxUndoActionInsert
*) action
;
2358 buffer
->Cut (insert
->start
, insert
->length
);
2359 anchor
= action
->selection_anchor
;
2360 cursor
= action
->selection_cursor
;
2362 case TextBoxUndoActionTypeDelete
:
2363 dele
= (TextBoxUndoActionDelete
*) action
;
2365 buffer
->Insert (dele
->start
, dele
->text
, dele
->length
);
2366 anchor
= action
->selection_anchor
;
2367 cursor
= action
->selection_cursor
;
2369 case TextBoxUndoActionTypeReplace
:
2370 replace
= (TextBoxUndoActionReplace
*) action
;
2372 buffer
->Cut (replace
->start
, replace
->inlen
);
2373 buffer
->Insert (replace
->start
, replace
->deleted
, replace
->length
);
2374 anchor
= action
->selection_anchor
;
2375 cursor
= action
->selection_cursor
;
2380 SetSelectionStart (MIN (anchor
, cursor
));
2381 SetSelectionLength (abs (cursor
- anchor
));
2382 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2383 selection_anchor
= anchor
;
2384 selection_cursor
= cursor
;
2391 TextBoxBase::Redo ()
2393 TextBoxUndoActionReplace
*replace
;
2394 TextBoxUndoActionInsert
*insert
;
2395 TextBoxUndoActionDelete
*dele
;
2396 TextBoxUndoAction
*action
;
2399 if (redo
->IsEmpty ())
2402 action
= redo
->Pop ();
2403 undo
->Push (action
);
2405 switch (action
->type
) {
2406 case TextBoxUndoActionTypeInsert
:
2407 insert
= (TextBoxUndoActionInsert
*) action
;
2409 buffer
->Insert (insert
->start
, insert
->buffer
->text
, insert
->buffer
->len
);
2410 anchor
= cursor
= insert
->start
+ insert
->buffer
->len
;
2412 case TextBoxUndoActionTypeDelete
:
2413 dele
= (TextBoxUndoActionDelete
*) action
;
2415 buffer
->Cut (dele
->start
, dele
->length
);
2416 anchor
= cursor
= dele
->start
;
2418 case TextBoxUndoActionTypeReplace
:
2419 replace
= (TextBoxUndoActionReplace
*) action
;
2421 buffer
->Cut (replace
->start
, replace
->length
);
2422 buffer
->Insert (replace
->start
, replace
->inserted
, replace
->inlen
);
2423 anchor
= cursor
= replace
->start
+ replace
->inlen
;
2428 SetSelectionStart (MIN (anchor
, cursor
));
2429 SetSelectionLength (abs (cursor
- anchor
));
2430 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2431 selection_anchor
= anchor
;
2432 selection_cursor
= cursor
;
2440 // TextBoxDynamicPropertyValueProvider
2443 class TextBoxDynamicPropertyValueProvider
: public PropertyValueProvider
{
2444 Value
*selection_background
;
2445 Value
*selection_foreground
;
2448 TextBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : PropertyValueProvider (obj
, precedence
)
2450 selection_background
= NULL
;
2451 selection_foreground
= NULL
;
2454 virtual ~TextBoxDynamicPropertyValueProvider ()
2456 delete selection_background
;
2457 delete selection_foreground
;
2460 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2462 if (property
->GetId () == TextBox::SelectionBackgroundProperty
) {
2463 return selection_background
;
2464 } else if (property
->GetId () == TextBox::SelectionForegroundProperty
) {
2465 return selection_foreground
;
2471 void InitializeSelectionBrushes ()
2473 if (!selection_background
)
2474 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2476 if (!selection_foreground
)
2477 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2488 providers
[PropertyPrecedence_DynamicValue
] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2490 Initialize (Type::TEXTBOX
, "System.Windows.Controls.TextBox");
2491 events_mask
= TEXT_CHANGED
| SELECTION_CHANGED
;
2496 TextBox::EmitSelectionChanged ()
2498 EmitAsync (TextBox::SelectionChangedEvent
, new RoutedEventArgs ());
2502 TextBox::EmitTextChanged ()
2504 EmitAsync (TextBox::TextChangedEvent
, new TextChangedEventArgs ());
2508 TextBox::SyncSelectedText ()
2510 if (selection_cursor
!= selection_anchor
) {
2511 int length
= abs (selection_cursor
- selection_anchor
);
2512 int start
= MIN (selection_anchor
, selection_cursor
);
2515 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2518 SetValue (TextBox::SelectedTextProperty
, Value (text
, true));
2522 SetValue (TextBox::SelectedTextProperty
, Value (""));
2528 TextBox::SyncText ()
2530 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
2533 SetValue (TextBox::TextProperty
, Value (text
, true));
2538 TextBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2540 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2541 DependencyProperty
*prop
;
2544 if (args
->GetId () == TextBox::AcceptsReturnProperty
) {
2545 // update accepts_return state
2546 accepts_return
= args
->GetNewValue ()->AsBool ();
2547 } else if (args
->GetId () == TextBox::CaretBrushProperty
) {
2548 // FIXME: if we want to be perfect, we could invalidate the
2549 // blinking cursor rect if it is active... but is it that
2551 } else if (args
->GetId () == TextBox::FontSourceProperty
) {
2552 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
2553 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2555 // FIXME: ideally we'd remove the old item from the cache (or,
2556 // rather, 'unref' it since some other textblocks/boxes might
2557 // still be using it).
2559 g_free (font_source
);
2561 if (source
&& source
->stream
)
2562 font_source
= manager
->AddResource (source
->stream
);
2566 changed
= TextBoxModelChangedFont
;
2567 font
->SetSource (font_source
);
2568 } else if (args
->GetId () == TextBox::IsReadOnlyProperty
) {
2569 // update is_read_only state
2570 is_read_only
= args
->GetNewValue ()->AsBool ();
2575 gtk_im_context_focus_out (im_ctx
);
2577 gtk_im_context_focus_in (im_ctx
);
2580 } else if (args
->GetId () == TextBox::MaxLengthProperty
) {
2581 // update max_length state
2582 max_length
= args
->GetNewValue ()->AsInt32 ();
2583 } else if (args
->GetId () == TextBox::SelectedTextProperty
) {
2585 Value
*value
= args
->GetNewValue ();
2586 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2587 TextBoxUndoAction
*action
= NULL
;
2591 length
= abs (selection_cursor
- selection_anchor
);
2592 start
= MIN (selection_anchor
, selection_cursor
);
2594 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2596 // replace the currently selected text
2597 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
2599 buffer
->Replace (start
, length
, text
, textlen
);
2600 } else if (textlen
> 0) {
2601 // insert the text at the cursor
2602 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
2604 buffer
->Insert (start
, text
, textlen
);
2609 if (action
!= NULL
) {
2610 emit
|= TEXT_CHANGED
;
2611 undo
->Push (action
);
2614 ClearSelection (start
+ textlen
);
2620 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2623 } else if (args
->GetId () == TextBox::SelectionStartProperty
) {
2624 length
= abs (selection_cursor
- selection_anchor
);
2625 start
= args
->GetNewValue ()->AsInt32 ();
2627 if (start
> buffer
->len
) {
2628 // clamp the selection start offset to a valid value
2629 SetSelectionStart (buffer
->len
);
2633 if (start
+ length
> buffer
->len
) {
2634 // clamp the selection length to a valid value
2636 length
= buffer
->len
- start
;
2637 SetSelectionLength (length
);
2641 // SelectionStartProperty is marked as AlwaysChange -
2642 // if the value hasn't actually changed, then we do
2643 // not want to emit the TextBoxModelChanged event.
2644 if (selection_anchor
!= start
) {
2645 changed
= TextBoxModelChangedSelection
;
2646 have_offset
= false;
2649 // When set programatically, anchor is always the
2650 // start and cursor is always the end.
2651 selection_cursor
= start
+ length
;
2652 selection_anchor
= start
;
2654 emit
|= SELECTION_CHANGED
;
2657 } else if (args
->GetId () == TextBox::SelectionLengthProperty
) {
2658 start
= MIN (selection_anchor
, selection_cursor
);
2659 length
= args
->GetNewValue ()->AsInt32 ();
2661 if (start
+ length
> buffer
->len
) {
2662 // clamp the selection length to a valid value
2663 length
= buffer
->len
- start
;
2664 SetSelectionLength (length
);
2668 // SelectionLengthProperty is marked as AlwaysChange -
2669 // if the value hasn't actually changed, then we do
2670 // not want to emit the TextBoxModelChanged event.
2671 if (selection_cursor
!= start
+ length
) {
2672 changed
= TextBoxModelChangedSelection
;
2673 have_offset
= false;
2676 // When set programatically, anchor is always the
2677 // start and cursor is always the end.
2678 selection_cursor
= start
+ length
;
2679 selection_anchor
= start
;
2681 emit
|= SELECTION_CHANGED
;
2684 } else if (args
->GetId () == TextBox::SelectionBackgroundProperty
) {
2685 changed
= TextBoxModelChangedBrush
;
2686 } else if (args
->GetId () == TextBox::SelectionForegroundProperty
) {
2687 changed
= TextBoxModelChangedBrush
;
2688 } else if (args
->GetId () == TextBox::TextProperty
) {
2690 Value
*value
= args
->GetNewValue ();
2691 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2692 TextBoxUndoAction
*action
;
2696 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2697 if (buffer
->len
> 0) {
2698 // replace the current text
2699 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
2701 buffer
->Replace (0, buffer
->len
, text
, textlen
);
2704 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
2706 buffer
->Insert (0, text
, textlen
);
2709 undo
->Push (action
);
2713 emit
|= TEXT_CHANGED
;
2717 SyncAndEmit (value
&& !value
->GetIsNull ());
2719 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2723 changed
= TextBoxModelChangedText
;
2724 } else if (args
->GetId () == TextBox::TextAlignmentProperty
) {
2725 changed
= TextBoxModelChangedTextAlignment
;
2726 } else if (args
->GetId () == TextBox::TextWrappingProperty
) {
2727 changed
= TextBoxModelChangedTextWrapping
;
2728 } else if (args
->GetId () == TextBox::HorizontalScrollBarVisibilityProperty
) {
2729 // XXX more crap because these aren't templatebound.
2730 if (contentElement
) {
2731 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2732 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2736 } else if (args
->GetId () == TextBox::VerticalScrollBarVisibilityProperty
) {
2737 // XXX more crap because these aren't templatebound.
2738 if (contentElement
) {
2739 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2740 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2746 if (changed
!= TextBoxModelChangedNothing
)
2747 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2749 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
2750 TextBoxBase::OnPropertyChanged (args
, error
);
2754 NotifyListenersOfPropertyChange (args
, error
);
2758 TextBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2760 if (prop
&& (prop
->GetId () == TextBox::SelectionBackgroundProperty
||
2761 prop
->GetId () == TextBox::SelectionForegroundProperty
)) {
2762 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2766 if (prop
->GetOwnerType () != Type::TEXTBOX
)
2767 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2771 TextBox::OnApplyTemplate ()
2773 DependencyProperty
*prop
;
2775 TextBoxBase::OnApplyTemplate ();
2777 if (!contentElement
)
2780 // XXX LAME these should be template bindings in the textbox template.
2781 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2782 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2784 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2785 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2794 // PasswordBoxDynamicPropertyValueProvider
2797 class PasswordBoxDynamicPropertyValueProvider
: public PropertyValueProvider
{
2798 Value
*selection_background
;
2799 Value
*selection_foreground
;
2802 PasswordBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : PropertyValueProvider (obj
, precedence
)
2804 selection_background
= NULL
;
2805 selection_foreground
= NULL
;
2808 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2810 delete selection_background
;
2811 delete selection_foreground
;
2814 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2816 if (property
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
2817 return selection_background
;
2818 } else if (property
->GetId () == PasswordBox::SelectionForegroundProperty
) {
2819 return selection_foreground
;
2825 void InitializeSelectionBrushes ()
2827 if (!selection_background
)
2828 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2830 if (!selection_foreground
)
2831 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2840 PasswordBox::PasswordBox ()
2842 providers
[PropertyPrecedence_DynamicValue
] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2844 Initialize (Type::PASSWORDBOX
, "System.Windows.Controls.PasswordBox");
2845 events_mask
= TEXT_CHANGED
;
2848 display
= g_string_new ("");
2851 PasswordBox::~PasswordBox ()
2853 g_string_free (display
, true);
2857 PasswordBox::CursorDown (int cursor
, bool page
)
2859 return GetBuffer ()->len
;
2863 PasswordBox::CursorUp (int cursor
, bool page
)
2869 PasswordBox::CursorLineBegin (int cursor
)
2875 PasswordBox::CursorLineEnd (int cursor
, bool include
)
2877 return GetBuffer ()->len
;
2881 PasswordBox::CursorNextWord (int cursor
)
2883 return GetBuffer ()->len
;
2887 PasswordBox::CursorPrevWord (int cursor
)
2893 PasswordBox::EmitTextChanged ()
2895 EmitAsync (PasswordBox::PasswordChangedEvent
, new RoutedEventArgs ());
2899 PasswordBox::SyncSelectedText ()
2901 if (selection_cursor
!= selection_anchor
) {
2902 int length
= abs (selection_cursor
- selection_anchor
);
2903 int start
= MIN (selection_anchor
, selection_cursor
);
2906 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2909 SetValue (PasswordBox::SelectedTextProperty
, Value (text
, true));
2913 SetValue (PasswordBox::SelectedTextProperty
, Value (""));
2919 PasswordBox::SyncDisplayText ()
2921 gunichar c
= GetPasswordChar ();
2923 g_string_truncate (display
, 0);
2925 for (int i
= 0; i
< buffer
->len
; i
++)
2926 g_string_append_unichar (display
, c
);
2930 PasswordBox::SyncText ()
2932 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
2935 SetValue (PasswordBox::PasswordProperty
, Value (text
, true));
2942 PasswordBox::GetDisplayText ()
2944 return display
->str
;
2948 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2950 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2953 if (args
->GetId () == PasswordBox::CaretBrushProperty
) {
2954 // FIXME: if we want to be perfect, we could invalidate the
2955 // blinking cursor rect if it is active... but is it that
2957 } else if (args
->GetId () == PasswordBox::FontSourceProperty
) {
2958 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
2959 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2961 // FIXME: ideally we'd remove the old item from the cache (or,
2962 // rather, 'unref' it since some other textblocks/boxes might
2963 // still be using it).
2965 g_free (font_source
);
2967 if (source
&& source
->stream
)
2968 font_source
= manager
->AddResource (source
->stream
);
2972 changed
= TextBoxModelChangedFont
;
2973 font
->SetSource (font_source
);
2974 } else if (args
->GetId () == PasswordBox::MaxLengthProperty
) {
2975 // update max_length state
2976 max_length
= args
->GetNewValue()->AsInt32 ();
2977 } else if (args
->GetId () == PasswordBox::PasswordCharProperty
) {
2978 changed
= TextBoxModelChangedText
;
2979 } else if (args
->GetId () == PasswordBox::PasswordProperty
) {
2981 Value
*value
= args
->GetNewValue ();
2982 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2983 TextBoxUndoAction
*action
;
2987 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2988 if (buffer
->len
> 0) {
2989 // replace the current text
2990 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
2992 buffer
->Replace (0, buffer
->len
, text
, textlen
);
2995 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
2997 buffer
->Insert (0, text
, textlen
);
3000 undo
->Push (action
);
3004 emit
|= TEXT_CHANGED
;
3013 changed
= TextBoxModelChangedText
;
3014 } else if (args
->GetId () == PasswordBox::SelectedTextProperty
) {
3016 Value
*value
= args
->GetNewValue ();
3017 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
3018 TextBoxUndoAction
*action
= NULL
;
3022 length
= abs (selection_cursor
- selection_anchor
);
3023 start
= MIN (selection_anchor
, selection_cursor
);
3025 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
3027 // replace the currently selected text
3028 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
3030 buffer
->Replace (start
, length
, text
, textlen
);
3031 } else if (textlen
> 0) {
3032 // insert the text at the cursor
3033 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
3035 buffer
->Insert (start
, text
, textlen
);
3040 if (action
!= NULL
) {
3041 undo
->Push (action
);
3044 ClearSelection (start
+ textlen
);
3045 emit
|= TEXT_CHANGED
;
3053 } else if (args
->GetId () == PasswordBox::SelectionStartProperty
) {
3054 length
= abs (selection_cursor
- selection_anchor
);
3055 start
= args
->GetNewValue ()->AsInt32 ();
3057 if (start
> buffer
->len
) {
3058 // clamp the selection start offset to a valid value
3059 SetSelectionStart (buffer
->len
);
3063 if (start
+ length
> buffer
->len
) {
3064 // clamp the selection length to a valid value
3066 length
= buffer
->len
- start
;
3067 SetSelectionLength (length
);
3071 // SelectionStartProperty is marked as AlwaysChange -
3072 // if the value hasn't actually changed, then we do
3073 // not want to emit the TextBoxModelChanged event.
3074 if (selection_anchor
!= start
) {
3075 changed
= TextBoxModelChangedSelection
;
3076 have_offset
= false;
3079 // When set programatically, anchor is always the
3080 // start and cursor is always the end.
3081 selection_cursor
= start
+ length
;
3082 selection_anchor
= start
;
3084 emit
|= SELECTION_CHANGED
;
3087 } else if (args
->GetId () == PasswordBox::SelectionLengthProperty
) {
3088 start
= MIN (selection_anchor
, selection_cursor
);
3089 length
= args
->GetNewValue ()->AsInt32 ();
3091 if (start
+ length
> buffer
->len
) {
3092 // clamp the selection length to a valid value
3093 length
= buffer
->len
- start
;
3094 SetSelectionLength (length
);
3098 // SelectionLengthProperty is marked as AlwaysChange -
3099 // if the value hasn't actually changed, then we do
3100 // not want to emit the TextBoxModelChanged event.
3101 if (selection_cursor
!= start
+ length
) {
3102 changed
= TextBoxModelChangedSelection
;
3103 have_offset
= false;
3106 // When set programatically, anchor is always the
3107 // start and cursor is always the end.
3108 selection_cursor
= start
+ length
;
3109 selection_anchor
= start
;
3111 emit
|= SELECTION_CHANGED
;
3114 } else if (args
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
3115 changed
= TextBoxModelChangedBrush
;
3116 } else if (args
->GetId () == PasswordBox::SelectionForegroundProperty
) {
3117 changed
= TextBoxModelChangedBrush
;
3120 if (changed
!= TextBoxModelChangedNothing
)
3121 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
3123 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
3124 TextBoxBase::OnPropertyChanged (args
, error
);
3128 NotifyListenersOfPropertyChange (args
, error
);
3132 PasswordBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
3134 if (prop
&& (prop
->GetId () == PasswordBox::SelectionBackgroundProperty
||
3135 prop
->GetId () == PasswordBox::SelectionForegroundProperty
)) {
3136 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
3140 if (prop
->GetOwnerType () != Type::TEXTBOX
)
3141 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
3149 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3150 #define CURSOR_BLINK_ON_MULTIPLIER 2
3151 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3152 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3153 #define CURSOR_BLINK_DIVIDER 3
3155 TextBoxView::TextBoxView ()
3157 SetObjectType (Type::TEXTBOXVIEW
);
3159 AddHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3160 AddHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3162 SetCursor (MouseCursorIBeam
);
3164 cursor
= Rect (0, 0, 0, 0);
3165 layout
= new TextLayout ();
3166 selection_changed
= false;
3167 had_selected_text
= false;
3168 cursor_visible
= false;
3174 TextBoxView::~TextBoxView ()
3176 RemoveHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3177 RemoveHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3180 textbox
->RemoveHandler (TextBox::ModelChangedEvent
, TextBoxView::model_changed
, this);
3181 textbox
->view
= NULL
;
3184 DisconnectBlinkTimeout ();
3190 TextBoxView::GetLineFromY (double y
, int *index
)
3192 return layout
->GetLineFromY (Point (), y
, index
);
3196 TextBoxView::GetLineFromIndex (int index
)
3198 return layout
->GetLineFromIndex (index
);
3202 TextBoxView::GetCursorFromXY (double x
, double y
)
3204 return layout
->GetCursorFromXY (Point (), x
, y
);
3208 TextBoxView::blink (void *user_data
)
3210 return ((TextBoxView
*) user_data
)->Blink ();
3214 GetCursorBlinkTimeout (TextBoxView
*view
)
3216 GtkSettings
*settings
;
3223 if (!(surface
= view
->GetSurface ()))
3224 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3226 if (!(window
= surface
->GetWindow ()))
3227 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3229 if (!(widget
= window
->GetGdkWindow ()))
3230 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3232 if (!(screen
= gdk_drawable_get_screen ((GdkDrawable
*) widget
)))
3233 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3235 if (!(settings
= gtk_settings_get_for_screen (screen
)))
3236 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3238 g_object_get (settings
, "gtk-cursor-blink-time", &timeout
, NULL
);
3244 TextBoxView::ConnectBlinkTimeout (guint multiplier
)
3246 guint timeout
= GetCursorBlinkTimeout (this) * multiplier
/ CURSOR_BLINK_DIVIDER
;
3247 Surface
*surface
= GetSurface ();
3248 TimeManager
*manager
;
3250 if (!surface
|| !(manager
= surface
->GetTimeManager ()))
3253 blink_timeout
= manager
->AddTimeout (MOON_PRIORITY_DEFAULT
, timeout
, TextBoxView::blink
, this);
3257 TextBoxView::DisconnectBlinkTimeout ()
3259 TimeManager
*manager
;
3262 if (blink_timeout
!= 0) {
3263 if (!(surface
= GetSurface ()) || !(manager
= surface
->GetTimeManager ()))
3266 manager
->RemoveTimeout (blink_timeout
);
3272 TextBoxView::Blink ()
3276 SetCurrentDeployment (true);
3278 if (cursor_visible
) {
3279 multiplier
= CURSOR_BLINK_OFF_MULTIPLIER
;
3282 multiplier
= CURSOR_BLINK_ON_MULTIPLIER
;
3286 ConnectBlinkTimeout (multiplier
);
3292 TextBoxView::DelayCursorBlink ()
3294 DisconnectBlinkTimeout ();
3295 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER
);
3296 UpdateCursor (true);
3301 TextBoxView::BeginCursorBlink ()
3303 if (blink_timeout
== 0) {
3304 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER
);
3305 UpdateCursor (true);
3311 TextBoxView::EndCursorBlink ()
3313 DisconnectBlinkTimeout ();
3320 TextBoxView::ResetCursorBlink (bool delay
)
3322 if (textbox
->IsFocused () && !textbox
->HasSelectedText ()) {
3323 // cursor is blinkable... proceed with blinkage
3325 DelayCursorBlink ();
3327 BeginCursorBlink ();
3329 // cursor not blinkable... stop all blinkage
3335 TextBoxView::InvalidateCursor ()
3337 Invalidate (cursor
.Transform (&absolute_xform
));
3341 TextBoxView::ShowCursor ()
3343 cursor_visible
= true;
3344 InvalidateCursor ();
3348 TextBoxView::HideCursor ()
3350 cursor_visible
= false;
3351 InvalidateCursor ();
3355 TextBoxView::UpdateCursor (bool invalidate
)
3357 int cur
= textbox
->GetCursor ();
3361 // invalidate current cursor rect
3362 if (invalidate
&& cursor_visible
)
3363 InvalidateCursor ();
3365 // calculate the new cursor rect
3366 cursor
= layout
->GetCursor (Point (), cur
);
3368 // transform the cursor rect into absolute coordinates for the IM context
3369 rect
= cursor
.Transform (&absolute_xform
);
3370 area
= rect
.ToGdkRectangle ();
3372 gtk_im_context_set_cursor_location (textbox
->im_ctx
, &area
);
3374 textbox
->EmitCursorPositionChanged (cursor
.height
, cursor
.x
, cursor
.y
);
3376 // invalidate the new cursor rect
3377 if (invalidate
&& cursor_visible
)
3378 InvalidateCursor ();
3382 TextBoxView::UpdateText ()
3384 const char *text
= textbox
->GetDisplayText ();
3386 layout
->SetText (text
? text
: "", -1);
3390 TextBoxView::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
3392 *height
= GetActualHeight ();
3393 *width
= GetActualWidth ();
3397 TextBoxView::ComputeActualSize ()
3399 Layout (Size (INFINITY
, INFINITY
));
3402 layout
->GetActualExtents (&actual
.width
, &actual
.height
);
3408 TextBoxView::MeasureOverride (Size availableSize
)
3410 Size desired
= Size ();
3413 Layout (availableSize
);
3415 layout
->GetActualExtents (&desired
.width
, &desired
.height
);
3417 if (GetUseLayoutRounding ())
3418 desired
.width
= ceil (desired
.width
);
3420 return desired
.Min (availableSize
);
3424 TextBoxView::ArrangeOverride (Size finalSize
)
3426 Size arranged
= Size ();
3431 layout
->GetActualExtents (&arranged
.width
, &arranged
.height
);
3433 arranged
= arranged
.Max (finalSize
);
3439 TextBoxView::Layout (Size constraint
)
3441 layout
->SetMaxWidth (constraint
.width
);
3447 TextBoxView::Paint (cairo_t
*cr
)
3449 layout
->Render (cr
, GetOriginPoint (), Point ());
3451 if (cursor_visible
) {
3452 cairo_antialias_t alias
= cairo_get_antialias (cr
);
3453 Brush
*caret
= textbox
->GetCaretBrush ();
3454 double h
= round (cursor
.height
);
3455 double x
= cursor
.x
;
3456 double y
= cursor
.y
;
3458 // disable antialiasing
3459 cairo_set_antialias (cr
, CAIRO_ANTIALIAS_NONE
);
3461 // snap 'x' to the half-pixel grid (to try and get a sharp 1-pixel-wide line)
3462 cairo_user_to_device (cr
, &x
, &y
);
3463 x
= trunc (x
) + 0.5; y
= trunc (y
);
3464 cairo_device_to_user (cr
, &x
, &y
);
3466 // set the cursor color
3467 caret
->SetupBrush (cr
, cursor
);
3470 cairo_set_line_width (cr
, 1.0);
3471 cairo_move_to (cr
, x
, y
);
3472 cairo_line_to (cr
, x
, y
+ h
);
3477 // restore antialiasing
3478 cairo_set_antialias (cr
, alias
);
3483 TextBoxView::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
3485 TextBoxDynamicPropertyValueProvider
*dynamic
= (TextBoxDynamicPropertyValueProvider
*) textbox
->providers
[PropertyPrecedence_DynamicValue
];
3486 Size renderSize
= GetRenderSize ();
3488 dynamic
->InitializeSelectionBrushes ();
3491 Layout (renderSize
);
3492 UpdateCursor (false);
3496 if (selection_changed
) {
3497 layout
->Select (textbox
->GetSelectionStart (), textbox
->GetSelectionLength ());
3498 selection_changed
= false;
3502 cairo_set_matrix (cr
, &absolute_xform
);
3505 RenderLayoutClip (cr
);
3507 layout
->SetAvailableWidth (renderSize
.width
);
3513 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs
*args
)
3515 switch (args
->changed
) {
3516 case TextBoxModelChangedTextAlignment
:
3517 // text alignment changed, update our layout
3518 if (layout
->SetTextAlignment ((TextAlignment
) args
->property
->GetNewValue()->AsInt32 ()))
3521 case TextBoxModelChangedTextWrapping
:
3522 // text wrapping changed, update our layout
3523 if (layout
->SetTextWrapping ((TextWrapping
) args
->property
->GetNewValue()->AsInt32 ()))
3526 case TextBoxModelChangedSelection
:
3527 if (had_selected_text
|| textbox
->HasSelectedText ()) {
3528 // the selection has changed, update the layout's selection
3529 had_selected_text
= textbox
->HasSelectedText ();
3530 selection_changed
= true;
3531 ResetCursorBlink (false);
3533 // cursor position changed
3534 ResetCursorBlink (true);
3538 case TextBoxModelChangedBrush
:
3539 // a brush has changed, no layout updates needed, we just need to re-render
3541 case TextBoxModelChangedFont
:
3542 // font changed, need to recalculate layout/bounds
3543 layout
->ResetState ();
3546 case TextBoxModelChangedText
:
3547 // the text has changed, need to recalculate layout/bounds
3552 // nothing changed??
3557 InvalidateMeasure ();
3558 UpdateBounds (true);
3565 TextBoxView::model_changed (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3567 ((TextBoxView
*) closure
)->OnModelChanged ((TextBoxModelChangedEventArgs
*) args
);
3571 TextBoxView::OnLostFocus ()
3577 TextBoxView::OnGotFocus ()
3579 ResetCursorBlink (false);
3583 TextBoxView::OnMouseLeftButtonDown (MouseButtonEventArgs
*args
)
3585 // proxy to our parent TextBox control
3586 textbox
->OnMouseLeftButtonDown (args
);
3590 TextBoxView::mouse_left_button_down (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3592 ((TextBoxView
*) closure
)->OnMouseLeftButtonDown ((MouseButtonEventArgs
*) args
);
3596 TextBoxView::OnMouseLeftButtonUp (MouseButtonEventArgs
*args
)
3598 // proxy to our parent TextBox control
3599 textbox
->OnMouseLeftButtonUp (args
);
3603 TextBoxView::mouse_left_button_up (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3605 ((TextBoxView
*) closure
)->OnMouseLeftButtonUp ((MouseButtonEventArgs
*) args
);
3609 TextBoxView::SetTextBox (TextBoxBase
*textbox
)
3611 TextLayoutAttributes
*attrs
;
3613 if (this->textbox
== textbox
)
3616 if (this->textbox
) {
3617 // remove the event handlers from the old textbox
3618 this->textbox
->RemoveHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3621 this->textbox
= textbox
;
3624 textbox
->AddHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3626 // sync our state with the textbox
3627 layout
->SetTextAttributes (new List ());
3628 attrs
= new TextLayoutAttributes ((ITextAttributes
*) textbox
, 0);
3629 layout
->GetTextAttributes ()->Append (attrs
);
3631 layout
->SetTextAlignment (textbox
->GetTextAlignment ());
3632 layout
->SetTextWrapping (textbox
->GetTextWrapping ());
3633 had_selected_text
= textbox
->HasSelectedText ();
3634 selection_changed
= true;
3637 layout
->SetTextAttributes (NULL
);
3638 layout
->SetText (NULL
, -1);
3641 UpdateBounds (true);
3642 InvalidateMeasure ();