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
;
1372 TextBoxBase::BatchPush ()
1378 TextBoxBase::BatchPop ()
1381 g_warning ("TextBoxBase batch underflow");
1389 TextBoxBase::SyncAndEmit (bool sync_text
)
1391 if (batch
!= 0 || emit
== NOTHING_CHANGED
)
1394 if (sync_text
&& (emit
& TEXT_CHANGED
))
1397 if (emit
& SELECTION_CHANGED
)
1398 SyncSelectedText ();
1401 // eliminate events that we can't emit
1402 emit
&= events_mask
;
1404 if (emit
& TEXT_CHANGED
)
1407 if (emit
& SELECTION_CHANGED
)
1408 EmitSelectionChanged ();
1411 emit
= NOTHING_CHANGED
;
1415 TextBoxBase::Paste (GtkClipboard
*clipboard
, const char *str
)
1417 int length
= abs (selection_cursor
- selection_anchor
);
1418 int start
= MIN (selection_anchor
, selection_cursor
);
1419 TextBoxUndoAction
*action
;
1423 if (!(text
= g_utf8_to_ucs4_fast (str
? str
: "", -1, &len
)))
1426 if (max_length
> 0 && ((buffer
->len
- length
) + len
> max_length
)) {
1427 // paste cannot exceed MaxLength
1428 len
= max_length
- (buffer
->len
- length
);
1430 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (len
+ 1));
1437 // only paste the content of the first line
1438 for (i
= 0; i
< len
; i
++) {
1439 if (text
[i
] == '\r' || text
[i
] == '\n' || text
[i
] == 0x2028) {
1440 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (i
+ 1));
1451 // replace the currently selected text
1452 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, len
);
1454 buffer
->Replace (start
, length
, text
, len
);
1455 } else if (len
> 0) {
1456 // insert the text at the cursor position
1457 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, len
, true);
1459 buffer
->Insert (start
, text
, len
);
1465 undo
->Push (action
);
1469 emit
|= TEXT_CHANGED
;
1473 SetSelectionStart (start
);
1474 SetSelectionLength (0);
1481 TextBoxBase::paste (GtkClipboard
*clipboard
, const char *text
, gpointer closure
)
1483 ((TextBoxBase
*) closure
)->Paste (clipboard
, text
);
1487 TextBoxBase::OnKeyDown (KeyEventArgs
*args
)
1489 GdkModifierType modifiers
= (GdkModifierType
) args
->GetModifiers ();
1490 guint key
= args
->GetKeyVal ();
1491 GtkClipboard
*clipboard
;
1492 bool handled
= false;
1494 if (args
->IsModifier ())
1497 // set 'emit' to NOTHING_CHANGED so that we can figure out
1498 // what has chanegd after applying the changes that this
1499 // keypress will cause.
1500 emit
= NOTHING_CHANGED
;
1508 handled
= KeyPressBackSpace (modifiers
);
1514 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == SHIFT_MASK
) {
1515 // Shift+Delete => Cut
1516 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1517 if (selection_cursor
!= selection_anchor
) {
1518 // copy selection to the clipboard and then cut
1519 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1523 SetSelectedText ("");
1526 handled
= KeyPressDelete (modifiers
);
1530 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == SHIFT_MASK
) {
1531 // Shift+Insert => Paste
1535 if ((clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1536 // paste clipboard contents to the buffer
1537 gtk_clipboard_request_text (clipboard
, TextBoxBase::paste
, this);
1541 } else if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == CONTROL_MASK
) {
1542 // Control+Insert => Copy
1543 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1544 if (selection_cursor
!= selection_anchor
) {
1545 // copy selection to the clipboard
1546 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1553 case GDK_KP_Page_Down
:
1555 handled
= KeyPressPageDown (modifiers
);
1557 case GDK_KP_Page_Up
:
1559 handled
= KeyPressPageUp (modifiers
);
1563 handled
= KeyPressHome (modifiers
);
1567 handled
= KeyPressEnd (modifiers
);
1571 handled
= KeyPressRight (modifiers
);
1575 handled
= KeyPressLeft (modifiers
);
1579 handled
= KeyPressDown (modifiers
);
1583 handled
= KeyPressUp (modifiers
);
1586 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
| SHIFT_MASK
)) == CONTROL_MASK
) {
1590 // Ctrl+A => Select All
1597 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1598 if (selection_cursor
!= selection_anchor
) {
1599 // copy selection to the clipboard
1600 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1612 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1613 if (selection_cursor
!= selection_anchor
) {
1614 // copy selection to the clipboard and then cut
1615 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
1619 SetSelectedText ("");
1628 if ((clipboard
= GetClipboard (this, GDK_SELECTION_CLIPBOARD
))) {
1629 // paste clipboard contents to the buffer
1630 gtk_clipboard_request_text (clipboard
, TextBoxBase::paste
, this);
1638 if (!is_read_only
) {
1646 if (!is_read_only
) {
1652 // unhandled Control commands
1660 args
->SetHandled (handled
);
1670 TextBoxBase::PostOnKeyDown (KeyEventArgs
*args
)
1672 guint key
= args
->GetKeyVal ();
1675 // Note: we don't set Handled=true because anything we handle here, we
1676 // want to bubble up.
1678 if (!is_read_only
&& gtk_im_context_filter_keypress (im_ctx
, args
->GetEvent ())) {
1679 need_im_reset
= true;
1683 if (is_read_only
|| args
->IsModifier ())
1686 // set 'emit' to NOTHING_CHANGED so that we can figure out
1687 // what has chanegd after applying the changes that this
1688 // keypress will cause.
1689 emit
= NOTHING_CHANGED
;
1694 KeyPressUnichar ('\r');
1697 if ((args
->GetModifiers () & (CONTROL_MASK
| ALT_MASK
)) == 0) {
1698 // normal character input
1699 if ((c
= args
->GetUnicode ()))
1700 KeyPressUnichar (c
);
1711 TextBoxBase::OnKeyUp (KeyEventArgs
*args
)
1713 if (!is_read_only
) {
1714 if (gtk_im_context_filter_keypress (im_ctx
, args
->GetEvent ()))
1715 need_im_reset
= true;
1720 TextBoxBase::DeleteSurrounding (int offset
, int n_chars
)
1722 const char *delete_start
, *delete_end
;
1723 const char *text
= GetActualText ();
1724 int anchor
= selection_anchor
;
1725 int cursor
= selection_cursor
;
1726 TextBoxUndoAction
*action
;
1732 // get the utf-8 pointers so that we can use them to get gunichar offsets
1733 delete_start
= g_utf8_offset_to_pointer (text
, selection_cursor
) + offset
;
1734 delete_end
= delete_start
+ n_chars
;
1736 // get the character length/start index
1737 length
= g_utf8_pointer_to_offset (delete_start
, delete_end
);
1738 start
= g_utf8_pointer_to_offset (text
, delete_start
);
1741 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
1742 undo
->Push (action
);
1745 buffer
->Cut (start
, length
);
1746 emit
|= TEXT_CHANGED
;
1753 // check to see if selection has changed
1754 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1755 SetSelectionStart (MIN (anchor
, cursor
));
1756 SetSelectionLength (abs (cursor
- anchor
));
1757 selection_anchor
= anchor
;
1758 selection_cursor
= cursor
;
1759 emit
|= SELECTION_CHANGED
;
1770 TextBoxBase::delete_surrounding (GtkIMContext
*context
, int offset
, int n_chars
, gpointer user_data
)
1772 return ((TextBoxBase
*) user_data
)->DeleteSurrounding (offset
, n_chars
);
1776 TextBoxBase::RetrieveSurrounding ()
1778 const char *text
= GetActualText ();
1779 const char *cursor
= g_utf8_offset_to_pointer (text
, selection_cursor
);
1781 gtk_im_context_set_surrounding (im_ctx
, text
, -1, cursor
- text
);
1787 TextBoxBase::retrieve_surrounding (GtkIMContext
*context
, gpointer user_data
)
1789 return ((TextBoxBase
*) user_data
)->RetrieveSurrounding ();
1793 TextBoxBase::Commit (const char *str
)
1795 int length
= abs (selection_cursor
- selection_anchor
);
1796 int start
= MIN (selection_anchor
, selection_cursor
);
1797 TextBoxUndoAction
*action
;
1805 if (!(text
= g_utf8_to_ucs4_fast (str
? str
: "", -1, &len
)))
1808 if (max_length
> 0 && ((buffer
->len
- length
) + len
> max_length
)) {
1809 // paste cannot exceed MaxLength
1810 len
= max_length
- (buffer
->len
- length
);
1812 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (len
+ 1));
1819 // only paste the content of the first line
1820 for (i
= 0; i
< len
; i
++) {
1821 if (g_unichar_type (text
[i
]) == G_UNICODE_LINE_SEPARATOR
) {
1822 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (i
+ 1));
1831 // replace the currently selected text
1832 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, len
);
1833 undo
->Push (action
);
1836 buffer
->Replace (start
, length
, text
, len
);
1837 } else if (len
> 0) {
1838 // insert the text at the cursor position
1839 TextBoxUndoActionInsert
*insert
= NULL
;
1841 buffer
->Insert (start
, text
, len
);
1843 if ((action
= undo
->Peek ()) && action
->type
== TextBoxUndoActionTypeInsert
) {
1844 insert
= (TextBoxUndoActionInsert
*) action
;
1846 if (!insert
->Insert (start
, text
, len
))
1851 insert
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, len
);
1852 undo
->Push (insert
);
1861 emit
= TEXT_CHANGED
;
1862 cursor
= start
+ len
;
1868 // check to see if selection has changed
1869 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1870 SetSelectionStart (MIN (anchor
, cursor
));
1871 SetSelectionLength (abs (cursor
- anchor
));
1872 selection_anchor
= anchor
;
1873 selection_cursor
= cursor
;
1874 emit
|= SELECTION_CHANGED
;
1883 TextBoxBase::commit (GtkIMContext
*context
, const char *str
, gpointer user_data
)
1885 ((TextBoxBase
*) user_data
)->Commit (str
);
1889 TextBoxBase::ResetIMContext ()
1891 if (need_im_reset
) {
1892 gtk_im_context_reset (im_ctx
);
1893 need_im_reset
= false;
1898 TextBoxBase::OnMouseLeftButtonDown (MouseButtonEventArgs
*args
)
1903 args
->SetHandled (true);
1907 args
->GetPosition (view
, &x
, &y
);
1909 cursor
= view
->GetCursorFromXY (x
, y
);
1913 // Single-Click: cursor placement
1914 captured
= CaptureMouse ();
1918 emit
= NOTHING_CHANGED
;
1919 SetSelectionStart (cursor
);
1920 SetSelectionLength (0);
1928 TextBoxBase::OnMouseLeftButtonMultiClick (MouseButtonEventArgs
*args
)
1930 int cursor
, start
, end
;
1933 args
->SetHandled (true);
1936 args
->GetPosition (view
, &x
, &y
);
1938 cursor
= view
->GetCursorFromXY (x
, y
);
1942 if (args
->GetClickCount () == 3) {
1943 // Note: Silverlight doesn't implement this, but to
1944 // be consistent with other TextEntry-type
1945 // widgets in Gtk+, we will.
1947 // Triple-Click: select the line
1949 ReleaseMouseCapture ();
1950 start
= CursorLineBegin (cursor
);
1951 end
= CursorLineEnd (cursor
, true);
1955 // Double-Click: select the word
1957 ReleaseMouseCapture ();
1958 start
= CursorPrevWord (cursor
);
1959 end
= CursorNextWord (cursor
);
1965 emit
= NOTHING_CHANGED
;
1966 SetSelectionStart (start
);
1967 SetSelectionLength (end
- start
);
1975 TextBoxBase::mouse_left_button_multi_click (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
1977 ((TextBoxBase
*) closure
)->OnMouseLeftButtonMultiClick ((MouseButtonEventArgs
*) args
);
1981 TextBoxBase::OnMouseLeftButtonUp (MouseButtonEventArgs
*args
)
1984 ReleaseMouseCapture ();
1986 args
->SetHandled (true);
1992 TextBoxBase::OnMouseMove (MouseEventArgs
*args
)
1994 int anchor
= selection_anchor
;
1995 int cursor
= selection_cursor
;
1999 args
->GetPosition (view
, &x
, &y
);
2000 args
->SetHandled (true);
2002 cursor
= view
->GetCursorFromXY (x
, y
);
2005 emit
= NOTHING_CHANGED
;
2006 SetSelectionStart (MIN (anchor
, cursor
));
2007 SetSelectionLength (abs (cursor
- anchor
));
2008 selection_anchor
= anchor
;
2009 selection_cursor
= cursor
;
2017 TextBoxBase::OnLostFocus (RoutedEventArgs
*args
)
2020 emit
= NOTHING_CHANGED
;
2021 SetSelectionStart (selection_cursor
);
2022 SetSelectionLength (0);
2030 view
->OnLostFocus ();
2032 if (!is_read_only
) {
2033 gtk_im_context_focus_out (im_ctx
);
2034 need_im_reset
= true;
2039 TextBoxBase::OnGotFocus (RoutedEventArgs
*args
)
2044 view
->OnGotFocus ();
2046 if (!is_read_only
) {
2047 gtk_im_context_focus_in (im_ctx
);
2048 need_im_reset
= true;
2053 TextBoxBase::EmitCursorPositionChanged (double height
, double x
, double y
)
2055 Emit (TextBoxBase::CursorPositionChangedEvent
, new CursorPositionChangedEventArgs (height
, x
, y
));
2059 TextBoxBase::DownloaderComplete (Downloader
*downloader
)
2061 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2062 char *resource
, *filename
;
2063 InternalDownloader
*idl
;
2067 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2068 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
2073 if (!(idl
= downloader
->GetInternalDownloader ()))
2076 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
2079 uri
= downloader
->GetUri ();
2081 // If the downloaded file was a zip file, this'll get the path to the
2082 // extracted zip directory, else it will simply be the path to the
2084 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
2087 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
2088 manager
->AddResource (resource
, path
);
2091 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont
, NULL
));
2095 TextBoxBase::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
2097 ((TextBoxBase
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
2101 TextBoxBase::AddFontSource (Downloader
*downloader
)
2103 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
2104 g_ptr_array_add (downloaders
, downloader
);
2107 if (downloader
->Started () || downloader
->Completed ()) {
2108 if (downloader
->Completed ())
2109 DownloaderComplete (downloader
);
2111 // This is what actually triggers the download
2112 downloader
->Send ();
2117 TextBoxBase::AddFontResource (const char *resource
)
2119 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2120 Application
*application
= Application::GetCurrent ();
2121 Downloader
*downloader
;
2128 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
2129 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
2130 downloader
->Open ("GET", resource
, FontPolicy
);
2131 AddFontSource (downloader
);
2132 downloader
->unref ();
2140 manager
->AddResource (resource
, path
);
2146 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2148 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2150 if (args
->GetId () == Control::FontFamilyProperty
) {
2151 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
2152 char **families
, *fragment
;
2155 CleanupDownloaders ();
2157 if (family
&& family
->source
) {
2158 families
= g_strsplit (family
->source
, ",", -1);
2159 for (i
= 0; families
[i
]; i
++) {
2160 g_strstrip (families
[i
]);
2161 if ((fragment
= strchr (families
[i
], '#'))) {
2162 // the first portion of this string is the resource name...
2164 AddFontResource (families
[i
]);
2167 g_strfreev (families
);
2170 font
->SetFamily (family
? family
->source
: NULL
);
2171 changed
= TextBoxModelChangedFont
;
2172 } else if (args
->GetId () == Control::FontSizeProperty
) {
2173 double size
= args
->GetNewValue()->AsDouble ();
2174 changed
= TextBoxModelChangedFont
;
2175 font
->SetSize (size
);
2176 } else if (args
->GetId () == Control::FontStretchProperty
) {
2177 FontStretches stretch
= args
->GetNewValue()->AsFontStretch()->stretch
;
2178 changed
= TextBoxModelChangedFont
;
2179 font
->SetStretch (stretch
);
2180 } else if (args
->GetId () == Control::FontStyleProperty
) {
2181 FontStyles style
= args
->GetNewValue()->AsFontStyle ()->style
;
2182 changed
= TextBoxModelChangedFont
;
2183 font
->SetStyle (style
);
2184 } else if (args
->GetId () == Control::FontWeightProperty
) {
2185 FontWeights weight
= args
->GetNewValue()->AsFontWeight ()->weight
;
2186 changed
= TextBoxModelChangedFont
;
2187 font
->SetWeight (weight
);
2188 } else if (args
->GetId () == FrameworkElement::MinHeightProperty
) {
2189 // pass this along to our TextBoxView
2191 view
->SetMinHeight (args
->GetNewValue ()->AsDouble ());
2192 } else if (args
->GetId () == FrameworkElement::MaxHeightProperty
) {
2193 // pass this along to our TextBoxView
2195 view
->SetMaxHeight (args
->GetNewValue ()->AsDouble ());
2196 } else if (args
->GetId () == FrameworkElement::MinWidthProperty
) {
2197 // pass this along to our TextBoxView
2199 view
->SetMinWidth (args
->GetNewValue ()->AsDouble ());
2200 } else if (args
->GetId () == FrameworkElement::MaxWidthProperty
) {
2201 // pass this along to our TextBoxView
2203 view
->SetMaxWidth (args
->GetNewValue ()->AsDouble ());
2204 } else if (args
->GetId () == FrameworkElement::HeightProperty
) {
2205 // pass this along to our TextBoxView
2207 view
->SetHeight (args
->GetNewValue ()->AsDouble ());
2208 } else if (args
->GetId () == FrameworkElement::WidthProperty
) {
2209 // pass this along to our TextBoxView
2211 view
->SetWidth (args
->GetNewValue ()->AsDouble ());
2214 if (changed
!= TextBoxModelChangedNothing
)
2215 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2217 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE
) {
2218 Control::OnPropertyChanged (args
, error
);
2222 NotifyListenersOfPropertyChange (args
, error
);
2226 TextBoxBase::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2228 if (prop
&& (prop
->GetId () == Control::BackgroundProperty
||
2229 prop
->GetId () == Control::ForegroundProperty
)) {
2230 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2234 if (prop
->GetOwnerType () != Type::TEXTBOXBASE
)
2235 Control::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2239 TextBoxBase::OnApplyTemplate ()
2241 contentElement
= GetTemplateChild ("ContentElement");
2243 if (contentElement
== NULL
) {
2244 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2245 Control::OnApplyTemplate ();
2249 view
= new TextBoxView ();
2250 view
->SetTextBox (this);
2252 view
->SetMinHeight (GetMinHeight ());
2253 view
->SetMaxHeight (GetMaxHeight ());
2254 view
->SetMinWidth (GetMinWidth ());
2255 view
->SetMaxWidth (GetMaxWidth ());
2256 view
->SetHeight (GetHeight ());
2257 view
->SetWidth (GetWidth ());
2259 // Insert our TextBoxView
2260 if (contentElement
->Is (Type::CONTENTCONTROL
)) {
2261 ContentControl
*control
= (ContentControl
*) contentElement
;
2263 control
->SetValue (ContentControl::ContentProperty
, Value (view
));
2264 } else if (contentElement
->Is (Type::BORDER
)) {
2265 Border
*border
= (Border
*) contentElement
;
2267 border
->SetValue (Border::ChildProperty
, Value (view
));
2268 } else if (contentElement
->Is (Type::PANEL
)) {
2269 DependencyObjectCollection
*children
= ((Panel
*) contentElement
)->GetChildren ();
2271 children
->Add (view
);
2273 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentElement of type %s",
2274 contentElement
->GetType ()->GetName ());
2279 Control::OnApplyTemplate ();
2283 TextBoxBase::ClearSelection (int start
)
2286 SetSelectionStart (start
);
2287 SetSelectionLength (0);
2292 TextBoxBase::SelectWithError (int start
, int length
, MoonError
*error
)
2295 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection start must be >= 0");
2300 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection length must be >= 0");
2304 if (start
> buffer
->len
)
2305 start
= buffer
->len
;
2307 if (length
> (buffer
->len
- start
))
2308 length
= (buffer
->len
- start
);
2311 SetSelectionStart (start
);
2312 SetSelectionLength (length
);
2323 TextBoxBase::SelectAll ()
2325 SelectWithError (0, buffer
->len
, NULL
);
2329 TextBoxBase::CanUndo ()
2331 return !undo
->IsEmpty ();
2335 TextBoxBase::CanRedo ()
2337 return !redo
->IsEmpty ();
2341 TextBoxBase::Undo ()
2343 TextBoxUndoActionReplace
*replace
;
2344 TextBoxUndoActionInsert
*insert
;
2345 TextBoxUndoActionDelete
*dele
;
2346 TextBoxUndoAction
*action
;
2349 if (undo
->IsEmpty ())
2352 action
= undo
->Pop ();
2353 redo
->Push (action
);
2355 switch (action
->type
) {
2356 case TextBoxUndoActionTypeInsert
:
2357 insert
= (TextBoxUndoActionInsert
*) action
;
2359 buffer
->Cut (insert
->start
, insert
->length
);
2360 anchor
= action
->selection_anchor
;
2361 cursor
= action
->selection_cursor
;
2363 case TextBoxUndoActionTypeDelete
:
2364 dele
= (TextBoxUndoActionDelete
*) action
;
2366 buffer
->Insert (dele
->start
, dele
->text
, dele
->length
);
2367 anchor
= action
->selection_anchor
;
2368 cursor
= action
->selection_cursor
;
2370 case TextBoxUndoActionTypeReplace
:
2371 replace
= (TextBoxUndoActionReplace
*) action
;
2373 buffer
->Cut (replace
->start
, replace
->inlen
);
2374 buffer
->Insert (replace
->start
, replace
->deleted
, replace
->length
);
2375 anchor
= action
->selection_anchor
;
2376 cursor
= action
->selection_cursor
;
2381 SetSelectionStart (MIN (anchor
, cursor
));
2382 SetSelectionLength (abs (cursor
- anchor
));
2383 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2384 selection_anchor
= anchor
;
2385 selection_cursor
= cursor
;
2392 TextBoxBase::Redo ()
2394 TextBoxUndoActionReplace
*replace
;
2395 TextBoxUndoActionInsert
*insert
;
2396 TextBoxUndoActionDelete
*dele
;
2397 TextBoxUndoAction
*action
;
2400 if (redo
->IsEmpty ())
2403 action
= redo
->Pop ();
2404 undo
->Push (action
);
2406 switch (action
->type
) {
2407 case TextBoxUndoActionTypeInsert
:
2408 insert
= (TextBoxUndoActionInsert
*) action
;
2410 buffer
->Insert (insert
->start
, insert
->buffer
->text
, insert
->buffer
->len
);
2411 anchor
= cursor
= insert
->start
+ insert
->buffer
->len
;
2413 case TextBoxUndoActionTypeDelete
:
2414 dele
= (TextBoxUndoActionDelete
*) action
;
2416 buffer
->Cut (dele
->start
, dele
->length
);
2417 anchor
= cursor
= dele
->start
;
2419 case TextBoxUndoActionTypeReplace
:
2420 replace
= (TextBoxUndoActionReplace
*) action
;
2422 buffer
->Cut (replace
->start
, replace
->length
);
2423 buffer
->Insert (replace
->start
, replace
->inserted
, replace
->inlen
);
2424 anchor
= cursor
= replace
->start
+ replace
->inlen
;
2429 SetSelectionStart (MIN (anchor
, cursor
));
2430 SetSelectionLength (abs (cursor
- anchor
));
2431 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2432 selection_anchor
= anchor
;
2433 selection_cursor
= cursor
;
2441 // TextBoxDynamicPropertyValueProvider
2444 class TextBoxDynamicPropertyValueProvider
: public PropertyValueProvider
{
2445 Value
*selection_background
;
2446 Value
*selection_foreground
;
2449 TextBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : PropertyValueProvider (obj
, precedence
)
2451 selection_background
= NULL
;
2452 selection_foreground
= NULL
;
2455 virtual ~TextBoxDynamicPropertyValueProvider ()
2457 delete selection_background
;
2458 delete selection_foreground
;
2461 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2463 if (property
->GetId () == TextBox::SelectionBackgroundProperty
) {
2464 return selection_background
;
2465 } else if (property
->GetId () == TextBox::SelectionForegroundProperty
) {
2466 return selection_foreground
;
2472 void InitializeSelectionBrushes ()
2474 if (!selection_background
)
2475 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2477 if (!selection_foreground
)
2478 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2489 providers
[PropertyPrecedence_DynamicValue
] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2491 Initialize (Type::TEXTBOX
, "System.Windows.Controls.TextBox");
2492 events_mask
= TEXT_CHANGED
| SELECTION_CHANGED
;
2497 TextBox::EmitSelectionChanged ()
2499 EmitAsync (TextBox::SelectionChangedEvent
, new RoutedEventArgs ());
2503 TextBox::EmitTextChanged ()
2505 EmitAsync (TextBox::TextChangedEvent
, new TextChangedEventArgs ());
2509 TextBox::SyncSelectedText ()
2511 if (selection_cursor
!= selection_anchor
) {
2512 int length
= abs (selection_cursor
- selection_anchor
);
2513 int start
= MIN (selection_anchor
, selection_cursor
);
2516 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2519 SetValue (TextBox::SelectedTextProperty
, Value (text
, true));
2523 SetValue (TextBox::SelectedTextProperty
, Value (""));
2529 TextBox::SyncText ()
2531 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
2534 SetValue (TextBox::TextProperty
, Value (text
, true));
2539 TextBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2541 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2542 DependencyProperty
*prop
;
2545 if (args
->GetId () == TextBox::AcceptsReturnProperty
) {
2546 // update accepts_return state
2547 accepts_return
= args
->GetNewValue ()->AsBool ();
2548 } else if (args
->GetId () == TextBox::CaretBrushProperty
) {
2549 // FIXME: if we want to be perfect, we could invalidate the
2550 // blinking cursor rect if it is active... but is it that
2552 } else if (args
->GetId () == TextBox::FontSourceProperty
) {
2553 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
2554 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2556 // FIXME: ideally we'd remove the old item from the cache (or,
2557 // rather, 'unref' it since some other textblocks/boxes might
2558 // still be using it).
2560 g_free (font_source
);
2562 if (source
&& source
->stream
)
2563 font_source
= manager
->AddResource (source
->stream
);
2567 changed
= TextBoxModelChangedFont
;
2568 font
->SetSource (font_source
);
2569 } else if (args
->GetId () == TextBox::IsReadOnlyProperty
) {
2570 // update is_read_only state
2571 is_read_only
= args
->GetNewValue ()->AsBool ();
2576 gtk_im_context_focus_out (im_ctx
);
2578 gtk_im_context_focus_in (im_ctx
);
2581 } else if (args
->GetId () == TextBox::MaxLengthProperty
) {
2582 // update max_length state
2583 max_length
= args
->GetNewValue ()->AsInt32 ();
2584 } else if (args
->GetId () == TextBox::SelectedTextProperty
) {
2586 Value
*value
= args
->GetNewValue ();
2587 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2588 TextBoxUndoAction
*action
= NULL
;
2592 length
= abs (selection_cursor
- selection_anchor
);
2593 start
= MIN (selection_anchor
, selection_cursor
);
2595 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2597 // replace the currently selected text
2598 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
2600 buffer
->Replace (start
, length
, text
, textlen
);
2601 } else if (textlen
> 0) {
2602 // insert the text at the cursor
2603 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
2605 buffer
->Insert (start
, text
, textlen
);
2610 if (action
!= NULL
) {
2611 emit
|= TEXT_CHANGED
;
2612 undo
->Push (action
);
2615 ClearSelection (start
+ textlen
);
2621 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2624 } else if (args
->GetId () == TextBox::SelectionStartProperty
) {
2625 length
= abs (selection_cursor
- selection_anchor
);
2626 start
= args
->GetNewValue ()->AsInt32 ();
2628 if (start
> buffer
->len
) {
2629 // clamp the selection start offset to a valid value
2630 SetSelectionStart (buffer
->len
);
2634 if (start
+ length
> buffer
->len
) {
2635 // clamp the selection length to a valid value
2637 length
= buffer
->len
- start
;
2638 SetSelectionLength (length
);
2642 // SelectionStartProperty is marked as AlwaysChange -
2643 // if the value hasn't actually changed, then we do
2644 // not want to emit the TextBoxModelChanged event.
2645 if (selection_anchor
!= start
) {
2646 changed
= TextBoxModelChangedSelection
;
2647 have_offset
= false;
2650 // When set programatically, anchor is always the
2651 // start and cursor is always the end.
2652 selection_cursor
= start
+ length
;
2653 selection_anchor
= start
;
2655 emit
|= SELECTION_CHANGED
;
2658 } else if (args
->GetId () == TextBox::SelectionLengthProperty
) {
2659 start
= MIN (selection_anchor
, selection_cursor
);
2660 length
= args
->GetNewValue ()->AsInt32 ();
2662 if (start
+ length
> buffer
->len
) {
2663 // clamp the selection length to a valid value
2664 length
= buffer
->len
- start
;
2665 SetSelectionLength (length
);
2669 // SelectionLengthProperty is marked as AlwaysChange -
2670 // if the value hasn't actually changed, then we do
2671 // not want to emit the TextBoxModelChanged event.
2672 if (selection_cursor
!= start
+ length
) {
2673 changed
= TextBoxModelChangedSelection
;
2674 have_offset
= false;
2677 // When set programatically, anchor is always the
2678 // start and cursor is always the end.
2679 selection_cursor
= start
+ length
;
2680 selection_anchor
= start
;
2682 emit
|= SELECTION_CHANGED
;
2685 } else if (args
->GetId () == TextBox::SelectionBackgroundProperty
) {
2686 changed
= TextBoxModelChangedBrush
;
2687 } else if (args
->GetId () == TextBox::SelectionForegroundProperty
) {
2688 changed
= TextBoxModelChangedBrush
;
2689 } else if (args
->GetId () == TextBox::TextProperty
) {
2691 Value
*value
= args
->GetNewValue ();
2692 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2693 TextBoxUndoAction
*action
;
2697 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2698 if (buffer
->len
> 0) {
2699 // replace the current text
2700 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
2702 buffer
->Replace (0, buffer
->len
, text
, textlen
);
2705 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
2707 buffer
->Insert (0, text
, textlen
);
2710 undo
->Push (action
);
2714 emit
|= TEXT_CHANGED
;
2718 SyncAndEmit (value
&& !value
->GetIsNull ());
2720 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2724 changed
= TextBoxModelChangedText
;
2725 } else if (args
->GetId () == TextBox::TextAlignmentProperty
) {
2726 changed
= TextBoxModelChangedTextAlignment
;
2727 } else if (args
->GetId () == TextBox::TextWrappingProperty
) {
2728 changed
= TextBoxModelChangedTextWrapping
;
2729 } else if (args
->GetId () == TextBox::HorizontalScrollBarVisibilityProperty
) {
2730 // XXX more crap because these aren't templatebound.
2731 if (contentElement
) {
2732 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2733 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2737 } else if (args
->GetId () == TextBox::VerticalScrollBarVisibilityProperty
) {
2738 // XXX more crap because these aren't templatebound.
2739 if (contentElement
) {
2740 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2741 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2747 if (changed
!= TextBoxModelChangedNothing
)
2748 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2750 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
2751 TextBoxBase::OnPropertyChanged (args
, error
);
2755 NotifyListenersOfPropertyChange (args
, error
);
2759 TextBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2761 if (prop
&& (prop
->GetId () == TextBox::SelectionBackgroundProperty
||
2762 prop
->GetId () == TextBox::SelectionForegroundProperty
)) {
2763 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2767 if (prop
->GetOwnerType () != Type::TEXTBOX
)
2768 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2772 TextBox::OnApplyTemplate ()
2774 DependencyProperty
*prop
;
2776 TextBoxBase::OnApplyTemplate ();
2778 if (!contentElement
)
2781 // XXX LAME these should be template bindings in the textbox template.
2782 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2783 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2785 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2786 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2795 // PasswordBoxDynamicPropertyValueProvider
2798 class PasswordBoxDynamicPropertyValueProvider
: public PropertyValueProvider
{
2799 Value
*selection_background
;
2800 Value
*selection_foreground
;
2803 PasswordBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : PropertyValueProvider (obj
, precedence
)
2805 selection_background
= NULL
;
2806 selection_foreground
= NULL
;
2809 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2811 delete selection_background
;
2812 delete selection_foreground
;
2815 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2817 if (property
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
2818 return selection_background
;
2819 } else if (property
->GetId () == PasswordBox::SelectionForegroundProperty
) {
2820 return selection_foreground
;
2826 void InitializeSelectionBrushes ()
2828 if (!selection_background
)
2829 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2831 if (!selection_foreground
)
2832 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2841 PasswordBox::PasswordBox ()
2843 providers
[PropertyPrecedence_DynamicValue
] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2845 Initialize (Type::PASSWORDBOX
, "System.Windows.Controls.PasswordBox");
2846 events_mask
= TEXT_CHANGED
;
2849 display
= g_string_new ("");
2852 PasswordBox::~PasswordBox ()
2854 g_string_free (display
, true);
2858 PasswordBox::CursorDown (int cursor
, bool page
)
2860 return GetBuffer ()->len
;
2864 PasswordBox::CursorUp (int cursor
, bool page
)
2870 PasswordBox::CursorLineBegin (int cursor
)
2876 PasswordBox::CursorLineEnd (int cursor
, bool include
)
2878 return GetBuffer ()->len
;
2882 PasswordBox::CursorNextWord (int cursor
)
2884 return GetBuffer ()->len
;
2888 PasswordBox::CursorPrevWord (int cursor
)
2894 PasswordBox::EmitTextChanged ()
2896 EmitAsync (PasswordBox::PasswordChangedEvent
, new RoutedEventArgs ());
2900 PasswordBox::SyncSelectedText ()
2902 if (selection_cursor
!= selection_anchor
) {
2903 int length
= abs (selection_cursor
- selection_anchor
);
2904 int start
= MIN (selection_anchor
, selection_cursor
);
2907 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2910 SetValue (PasswordBox::SelectedTextProperty
, Value (text
, true));
2914 SetValue (PasswordBox::SelectedTextProperty
, Value (""));
2920 PasswordBox::SyncDisplayText ()
2922 gunichar c
= GetPasswordChar ();
2924 g_string_truncate (display
, 0);
2926 for (int i
= 0; i
< buffer
->len
; i
++)
2927 g_string_append_unichar (display
, c
);
2931 PasswordBox::SyncText ()
2933 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
2936 SetValue (PasswordBox::PasswordProperty
, Value (text
, true));
2943 PasswordBox::GetDisplayText ()
2945 return display
->str
;
2949 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2951 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2954 if (args
->GetId () == PasswordBox::CaretBrushProperty
) {
2955 // FIXME: if we want to be perfect, we could invalidate the
2956 // blinking cursor rect if it is active... but is it that
2958 } else if (args
->GetId () == PasswordBox::FontSourceProperty
) {
2959 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
2960 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2962 // FIXME: ideally we'd remove the old item from the cache (or,
2963 // rather, 'unref' it since some other textblocks/boxes might
2964 // still be using it).
2966 g_free (font_source
);
2968 if (source
&& source
->stream
)
2969 font_source
= manager
->AddResource (source
->stream
);
2973 changed
= TextBoxModelChangedFont
;
2974 font
->SetSource (font_source
);
2975 } else if (args
->GetId () == PasswordBox::MaxLengthProperty
) {
2976 // update max_length state
2977 max_length
= args
->GetNewValue()->AsInt32 ();
2978 } else if (args
->GetId () == PasswordBox::PasswordCharProperty
) {
2979 changed
= TextBoxModelChangedText
;
2980 } else if (args
->GetId () == PasswordBox::PasswordProperty
) {
2982 Value
*value
= args
->GetNewValue ();
2983 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2984 TextBoxUndoAction
*action
;
2988 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2989 if (buffer
->len
> 0) {
2990 // replace the current text
2991 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
2993 buffer
->Replace (0, buffer
->len
, text
, textlen
);
2996 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
2998 buffer
->Insert (0, text
, textlen
);
3001 undo
->Push (action
);
3005 emit
|= TEXT_CHANGED
;
3014 changed
= TextBoxModelChangedText
;
3015 } else if (args
->GetId () == PasswordBox::SelectedTextProperty
) {
3017 Value
*value
= args
->GetNewValue ();
3018 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
3019 TextBoxUndoAction
*action
= NULL
;
3023 length
= abs (selection_cursor
- selection_anchor
);
3024 start
= MIN (selection_anchor
, selection_cursor
);
3026 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
3028 // replace the currently selected text
3029 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
3031 buffer
->Replace (start
, length
, text
, textlen
);
3032 } else if (textlen
> 0) {
3033 // insert the text at the cursor
3034 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
3036 buffer
->Insert (start
, text
, textlen
);
3041 if (action
!= NULL
) {
3042 undo
->Push (action
);
3045 ClearSelection (start
+ textlen
);
3046 emit
|= TEXT_CHANGED
;
3054 } else if (args
->GetId () == PasswordBox::SelectionStartProperty
) {
3055 length
= abs (selection_cursor
- selection_anchor
);
3056 start
= args
->GetNewValue ()->AsInt32 ();
3058 if (start
> buffer
->len
) {
3059 // clamp the selection start offset to a valid value
3060 SetSelectionStart (buffer
->len
);
3064 if (start
+ length
> buffer
->len
) {
3065 // clamp the selection length to a valid value
3067 length
= buffer
->len
- start
;
3068 SetSelectionLength (length
);
3072 // SelectionStartProperty is marked as AlwaysChange -
3073 // if the value hasn't actually changed, then we do
3074 // not want to emit the TextBoxModelChanged event.
3075 if (selection_anchor
!= start
) {
3076 changed
= TextBoxModelChangedSelection
;
3077 have_offset
= false;
3080 // When set programatically, anchor is always the
3081 // start and cursor is always the end.
3082 selection_cursor
= start
+ length
;
3083 selection_anchor
= start
;
3085 emit
|= SELECTION_CHANGED
;
3088 } else if (args
->GetId () == PasswordBox::SelectionLengthProperty
) {
3089 start
= MIN (selection_anchor
, selection_cursor
);
3090 length
= args
->GetNewValue ()->AsInt32 ();
3092 if (start
+ length
> buffer
->len
) {
3093 // clamp the selection length to a valid value
3094 length
= buffer
->len
- start
;
3095 SetSelectionLength (length
);
3099 // SelectionLengthProperty is marked as AlwaysChange -
3100 // if the value hasn't actually changed, then we do
3101 // not want to emit the TextBoxModelChanged event.
3102 if (selection_cursor
!= start
+ length
) {
3103 changed
= TextBoxModelChangedSelection
;
3104 have_offset
= false;
3107 // When set programatically, anchor is always the
3108 // start and cursor is always the end.
3109 selection_cursor
= start
+ length
;
3110 selection_anchor
= start
;
3112 emit
|= SELECTION_CHANGED
;
3115 } else if (args
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
3116 changed
= TextBoxModelChangedBrush
;
3117 } else if (args
->GetId () == PasswordBox::SelectionForegroundProperty
) {
3118 changed
= TextBoxModelChangedBrush
;
3121 if (changed
!= TextBoxModelChangedNothing
)
3122 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
3124 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
3125 TextBoxBase::OnPropertyChanged (args
, error
);
3129 NotifyListenersOfPropertyChange (args
, error
);
3133 PasswordBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
3135 if (prop
&& (prop
->GetId () == PasswordBox::SelectionBackgroundProperty
||
3136 prop
->GetId () == PasswordBox::SelectionForegroundProperty
)) {
3137 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
3141 if (prop
->GetOwnerType () != Type::TEXTBOX
)
3142 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
3150 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3151 #define CURSOR_BLINK_ON_MULTIPLIER 2
3152 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3153 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3154 #define CURSOR_BLINK_DIVIDER 3
3156 TextBoxView::TextBoxView ()
3158 SetObjectType (Type::TEXTBOXVIEW
);
3160 AddHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3161 AddHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3163 SetCursor (MouseCursorIBeam
);
3165 cursor
= Rect (0, 0, 0, 0);
3166 layout
= new TextLayout ();
3167 selection_changed
= false;
3168 had_selected_text
= false;
3169 cursor_visible
= false;
3175 TextBoxView::~TextBoxView ()
3177 RemoveHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3178 RemoveHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3181 textbox
->RemoveHandler (TextBox::ModelChangedEvent
, TextBoxView::model_changed
, this);
3182 textbox
->view
= NULL
;
3185 DisconnectBlinkTimeout ();
3191 TextBoxView::GetLineFromY (double y
, int *index
)
3193 return layout
->GetLineFromY (Point (), y
, index
);
3197 TextBoxView::GetLineFromIndex (int index
)
3199 return layout
->GetLineFromIndex (index
);
3203 TextBoxView::GetCursorFromXY (double x
, double y
)
3205 return layout
->GetCursorFromXY (Point (), x
, y
);
3209 TextBoxView::blink (void *user_data
)
3211 return ((TextBoxView
*) user_data
)->Blink ();
3215 GetCursorBlinkTimeout (TextBoxView
*view
)
3217 GtkSettings
*settings
;
3224 if (!(surface
= view
->GetSurface ()))
3225 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3227 if (!(window
= surface
->GetWindow ()))
3228 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3230 if (!(widget
= window
->GetGdkWindow ()))
3231 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3233 if (!(screen
= gdk_drawable_get_screen ((GdkDrawable
*) widget
)))
3234 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3236 if (!(settings
= gtk_settings_get_for_screen (screen
)))
3237 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3239 g_object_get (settings
, "gtk-cursor-blink-time", &timeout
, NULL
);
3245 TextBoxView::ConnectBlinkTimeout (guint multiplier
)
3247 guint timeout
= GetCursorBlinkTimeout (this) * multiplier
/ CURSOR_BLINK_DIVIDER
;
3248 Surface
*surface
= GetSurface ();
3249 TimeManager
*manager
;
3251 if (!surface
|| !(manager
= surface
->GetTimeManager ()))
3254 blink_timeout
= manager
->AddTimeout (MOON_PRIORITY_DEFAULT
, timeout
, TextBoxView::blink
, this);
3258 TextBoxView::DisconnectBlinkTimeout ()
3260 TimeManager
*manager
;
3263 if (blink_timeout
!= 0) {
3264 if (!(surface
= GetSurface ()) || !(manager
= surface
->GetTimeManager ()))
3267 manager
->RemoveTimeout (blink_timeout
);
3273 TextBoxView::Blink ()
3277 SetCurrentDeployment (true);
3279 if (cursor_visible
) {
3280 multiplier
= CURSOR_BLINK_OFF_MULTIPLIER
;
3283 multiplier
= CURSOR_BLINK_ON_MULTIPLIER
;
3287 ConnectBlinkTimeout (multiplier
);
3293 TextBoxView::DelayCursorBlink ()
3295 DisconnectBlinkTimeout ();
3296 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER
);
3297 UpdateCursor (true);
3302 TextBoxView::BeginCursorBlink ()
3304 if (blink_timeout
== 0) {
3305 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER
);
3306 UpdateCursor (true);
3312 TextBoxView::EndCursorBlink ()
3314 DisconnectBlinkTimeout ();
3321 TextBoxView::ResetCursorBlink (bool delay
)
3323 if (textbox
->IsFocused () && !textbox
->HasSelectedText ()) {
3324 // cursor is blinkable... proceed with blinkage
3326 DelayCursorBlink ();
3328 BeginCursorBlink ();
3330 // cursor not blinkable... stop all blinkage
3336 TextBoxView::InvalidateCursor ()
3338 Invalidate (cursor
.Transform (&absolute_xform
));
3342 TextBoxView::ShowCursor ()
3344 cursor_visible
= true;
3345 InvalidateCursor ();
3349 TextBoxView::HideCursor ()
3351 cursor_visible
= false;
3352 InvalidateCursor ();
3356 TextBoxView::UpdateCursor (bool invalidate
)
3358 int cur
= textbox
->GetCursor ();
3362 // invalidate current cursor rect
3363 if (invalidate
&& cursor_visible
)
3364 InvalidateCursor ();
3366 // calculate the new cursor rect
3367 cursor
= layout
->GetCursor (Point (), cur
);
3369 // transform the cursor rect into absolute coordinates for the IM context
3370 rect
= cursor
.Transform (&absolute_xform
);
3371 area
= rect
.ToGdkRectangle ();
3373 gtk_im_context_set_cursor_location (textbox
->im_ctx
, &area
);
3375 textbox
->EmitCursorPositionChanged (cursor
.height
, cursor
.x
, cursor
.y
);
3377 // invalidate the new cursor rect
3378 if (invalidate
&& cursor_visible
)
3379 InvalidateCursor ();
3383 TextBoxView::UpdateText ()
3385 const char *text
= textbox
->GetDisplayText ();
3387 layout
->SetText (text
? text
: "", -1);
3391 TextBoxView::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
3393 *height
= GetActualHeight ();
3394 *width
= GetActualWidth ();
3398 TextBoxView::ComputeActualSize ()
3400 Layout (Size (INFINITY
, INFINITY
));
3403 layout
->GetActualExtents (&actual
.width
, &actual
.height
);
3409 TextBoxView::MeasureOverride (Size availableSize
)
3411 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 ();
3430 layout
->GetActualExtents (&arranged
.width
, &arranged
.height
);
3432 arranged
= arranged
.Max (finalSize
);
3438 TextBoxView::Layout (Size constraint
)
3440 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 ();
3490 UpdateCursor (false);
3492 if (selection_changed
) {
3493 layout
->Select (textbox
->GetSelectionStart (), textbox
->GetSelectionLength ());
3494 selection_changed
= false;
3498 cairo_set_matrix (cr
, &absolute_xform
);
3501 RenderLayoutClip (cr
);
3503 layout
->SetAvailableWidth (renderSize
.width
);
3509 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs
*args
)
3511 switch (args
->changed
) {
3512 case TextBoxModelChangedTextAlignment
:
3513 // text alignment changed, update our layout
3514 if (layout
->SetTextAlignment ((TextAlignment
) args
->property
->GetNewValue()->AsInt32 ()))
3517 case TextBoxModelChangedTextWrapping
:
3518 // text wrapping changed, update our layout
3519 if (layout
->SetTextWrapping ((TextWrapping
) args
->property
->GetNewValue()->AsInt32 ()))
3522 case TextBoxModelChangedSelection
:
3523 if (had_selected_text
|| textbox
->HasSelectedText ()) {
3524 // the selection has changed, update the layout's selection
3525 had_selected_text
= textbox
->HasSelectedText ();
3526 selection_changed
= true;
3527 ResetCursorBlink (false);
3529 // cursor position changed
3530 ResetCursorBlink (true);
3534 case TextBoxModelChangedBrush
:
3535 // a brush has changed, no layout updates needed, we just need to re-render
3537 case TextBoxModelChangedFont
:
3538 // font changed, need to recalculate layout/bounds
3539 layout
->ResetState ();
3542 case TextBoxModelChangedText
:
3543 // the text has changed, need to recalculate layout/bounds
3548 // nothing changed??
3553 InvalidateMeasure ();
3554 UpdateBounds (true);
3561 TextBoxView::model_changed (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3563 ((TextBoxView
*) closure
)->OnModelChanged ((TextBoxModelChangedEventArgs
*) args
);
3567 TextBoxView::OnLostFocus ()
3573 TextBoxView::OnGotFocus ()
3575 ResetCursorBlink (false);
3579 TextBoxView::OnMouseLeftButtonDown (MouseButtonEventArgs
*args
)
3581 // proxy to our parent TextBox control
3582 textbox
->OnMouseLeftButtonDown (args
);
3586 TextBoxView::mouse_left_button_down (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3588 ((TextBoxView
*) closure
)->OnMouseLeftButtonDown ((MouseButtonEventArgs
*) args
);
3592 TextBoxView::OnMouseLeftButtonUp (MouseButtonEventArgs
*args
)
3594 // proxy to our parent TextBox control
3595 textbox
->OnMouseLeftButtonUp (args
);
3599 TextBoxView::mouse_left_button_up (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3601 ((TextBoxView
*) closure
)->OnMouseLeftButtonUp ((MouseButtonEventArgs
*) args
);
3605 TextBoxView::SetTextBox (TextBoxBase
*textbox
)
3607 TextLayoutAttributes
*attrs
;
3609 if (this->textbox
== textbox
)
3612 if (this->textbox
) {
3613 // remove the event handlers from the old textbox
3614 this->textbox
->RemoveHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3617 this->textbox
= textbox
;
3620 textbox
->AddHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3622 // sync our state with the textbox
3623 layout
->SetTextAttributes (new List ());
3624 attrs
= new TextLayoutAttributes ((ITextAttributes
*) textbox
, 0);
3625 layout
->GetTextAttributes ()->Append (attrs
);
3627 layout
->SetTextAlignment (textbox
->GetTextAlignment ());
3628 layout
->SetTextWrapping (textbox
->GetTextWrapping ());
3629 had_selected_text
= textbox
->HasSelectedText ();
3630 selection_changed
= true;
3633 layout
->SetTextAttributes (NULL
);
3634 layout
->SetText (NULL
, -1);
3637 UpdateBounds (true);
3638 InvalidateMeasure ();