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"
36 #include "managedtypeinfo.h"
42 #define UNICODE_LEN(size) (sizeof (gunichar) * (size))
43 #define UNICODE_OFFSET(buf,offset) (((char *) buf) + UNICODE_LEN (offset))
48 bool Resize (int needed
)
50 int new_size
= allocated
;
54 if (allocated
>= needed
+ 128) {
55 while (new_size
>= needed
+ 128)
58 } else if (allocated
< needed
) {
59 while (new_size
< needed
)
65 if (!(buf
= g_try_realloc (text
, UNICODE_LEN (new_size
)))) {
66 // if new_size is < allocated, then we can pretend like we succeeded
67 return new_size
< allocated
;
70 text
= (gunichar
*) buf
;
81 TextBuffer (const gunichar
*text
, int len
)
98 text
= (gunichar
*) g_realloc (text
, UNICODE_LEN (128));
106 printf ("TextBuffer::text = \"");
108 for (int i
= 0; i
< len
; i
++) {
111 fputs ("\\r", stdout
);
114 fputs ("\\n", stdout
);
117 fputs ("\\0", stdout
);
120 fputc ('\\', stdout
);
123 fputc ((char) text
[i
], stdout
);
131 void Append (gunichar c
)
133 if (!Resize (len
+ 2))
140 void Append (const gunichar
*str
, int count
)
142 if (!Resize (len
+ count
+ 1))
145 memcpy (UNICODE_OFFSET (text
, len
), str
, UNICODE_LEN (count
));
150 void Cut (int start
, int length
)
155 if (length
== 0 || start
>= len
)
158 if (start
+ length
> len
)
159 length
= len
- start
;
161 src
= UNICODE_OFFSET (text
, start
+ length
);
162 dest
= UNICODE_OFFSET (text
, start
);
163 beyond
= len
- (start
+ length
);
165 memmove (dest
, src
, UNICODE_LEN (beyond
+ 1));
169 void Insert (int index
, gunichar c
)
171 if (!Resize (len
+ 2))
175 // shift all chars beyond position @index down by 1 char
176 memmove (UNICODE_OFFSET (text
, index
+ 1), UNICODE_OFFSET (text
, index
), UNICODE_LEN ((len
- index
) + 1));
185 void Insert (int index
, const gunichar
*str
, int count
)
187 if (!Resize (len
+ count
+ 1))
191 // shift all chars beyond position @index down by @count chars
192 memmove (UNICODE_OFFSET (text
, index
+ count
), UNICODE_OFFSET (text
, index
), UNICODE_LEN ((len
- index
) + 1));
194 // insert @count chars of @str into our buffer at position @index
195 memcpy (UNICODE_OFFSET (text
, index
), str
, UNICODE_LEN (count
));
198 // simply append @count chars of @str onto the end of our buffer
199 memcpy (UNICODE_OFFSET (text
, len
), str
, UNICODE_LEN (count
));
205 void Prepend (gunichar c
)
207 if (!Resize (len
+ 2))
210 // shift the entire buffer down by 1 char
211 memmove (UNICODE_OFFSET (text
, 1), text
, UNICODE_LEN (len
+ 1));
216 void Prepend (const gunichar
*str
, int count
)
218 if (!Resize (len
+ count
+ 1))
221 // shift the endtire buffer down by @count chars
222 memmove (UNICODE_OFFSET (text
, count
), text
, UNICODE_LEN (len
+ 1));
224 // copy @count chars of @str into the beginning of our buffer
225 memcpy (text
, str
, UNICODE_LEN (count
));
229 void Replace (int start
, int length
, const gunichar
*str
, int count
)
237 if (start
+ length
> len
)
238 length
= len
- start
;
240 // Check for the easy cases first...
242 Insert (start
, str
, count
);
244 } else if (count
== 0) {
247 } else if (count
== length
) {
248 memcpy (UNICODE_OFFSET (text
, start
), str
, UNICODE_LEN (count
));
252 if (count
> length
&& !Resize (len
+ (count
- length
) + 1))
255 // calculate the number of chars beyond @start that won't be cut
256 beyond
= len
- (start
+ length
);
258 // shift all chars beyond position (@start + length) into position...
259 dest
= UNICODE_OFFSET (text
, start
+ count
);
260 src
= UNICODE_OFFSET (text
, start
+ length
);
261 memmove (dest
, src
, UNICODE_LEN (beyond
+ 1));
263 // copy @count chars of @str into our buffer at position @start
264 memcpy (UNICODE_OFFSET (text
, start
), str
, UNICODE_LEN (count
));
266 len
= (len
- length
) + count
;
269 gunichar
*Substring (int start
, int length
= -1)
274 if (start
< 0 || start
> len
|| length
== 0)
278 length
= len
- start
;
280 n_bytes
= sizeof (gunichar
) * (length
+ 1);
281 substr
= (gunichar
*) g_malloc (n_bytes
);
282 n_bytes
-= sizeof (gunichar
);
284 memcpy (substr
, text
+ start
, n_bytes
);
293 // TextBoxUndoActions
296 enum TextBoxUndoActionType
{
297 TextBoxUndoActionTypeInsert
,
298 TextBoxUndoActionTypeDelete
,
299 TextBoxUndoActionTypeReplace
,
302 class TextBoxUndoAction
: public List::Node
{
304 TextBoxUndoActionType type
;
305 int selection_anchor
;
306 int selection_cursor
;
311 class TextBoxUndoActionInsert
: public TextBoxUndoAction
{
316 TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, gunichar c
);
317 TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, const gunichar
*inserted
, int length
, bool atomic
= false);
318 virtual ~TextBoxUndoActionInsert ();
320 bool Insert (int start
, const gunichar
*text
, int len
);
321 bool Insert (int start
, gunichar c
);
324 class TextBoxUndoActionDelete
: public TextBoxUndoAction
{
328 TextBoxUndoActionDelete (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
);
329 virtual ~TextBoxUndoActionDelete ();
332 class TextBoxUndoActionReplace
: public TextBoxUndoAction
{
338 TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, const gunichar
*inserted
, int inlen
);
339 TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, gunichar c
);
340 virtual ~TextBoxUndoActionReplace ();
343 class TextBoxUndoStack
{
348 TextBoxUndoStack (int max_count
);
349 ~TextBoxUndoStack ();
354 void Push (TextBoxUndoAction
*action
);
355 TextBoxUndoAction
*Peek ();
356 TextBoxUndoAction
*Pop ();
359 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, gunichar c
)
361 this->type
= TextBoxUndoActionTypeInsert
;
362 this->selection_anchor
= selection_anchor
;
363 this->selection_cursor
= selection_cursor
;
367 this->buffer
= new TextBuffer ();
368 this->buffer
->Append (c
);
369 this->growable
= true;
372 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor
, int selection_cursor
, int start
, const gunichar
*inserted
, int length
, bool atomic
)
374 this->type
= TextBoxUndoActionTypeInsert
;
375 this->selection_anchor
= selection_anchor
;
376 this->selection_cursor
= selection_cursor
;
377 this->length
= length
;
380 this->buffer
= new TextBuffer (inserted
, length
);
381 this->growable
= !atomic
;
384 TextBoxUndoActionInsert::~TextBoxUndoActionInsert ()
390 TextBoxUndoActionInsert::Insert (int start
, const gunichar
*text
, int len
)
392 if (!growable
|| start
!= (this->start
+ length
))
395 buffer
->Append (text
, len
);
402 TextBoxUndoActionInsert::Insert (int start
, gunichar c
)
404 if (!growable
|| start
!= (this->start
+ length
))
413 TextBoxUndoActionDelete::TextBoxUndoActionDelete (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
)
415 this->type
= TextBoxUndoActionTypeDelete
;
416 this->selection_anchor
= selection_anchor
;
417 this->selection_cursor
= selection_cursor
;
418 this->length
= length
;
421 this->text
= buffer
->Substring (start
, length
);
424 TextBoxUndoActionDelete::~TextBoxUndoActionDelete ()
429 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, const gunichar
*inserted
, int inlen
)
431 this->type
= TextBoxUndoActionTypeReplace
;
432 this->selection_anchor
= selection_anchor
;
433 this->selection_cursor
= selection_cursor
;
434 this->length
= length
;
437 this->deleted
= buffer
->Substring (start
, length
);
438 this->inserted
= (gunichar
*) g_malloc (UNICODE_LEN (inlen
+ 1));
439 memcpy (this->inserted
, inserted
, UNICODE_LEN (inlen
+ 1));
443 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor
, int selection_cursor
, TextBuffer
*buffer
, int start
, int length
, gunichar c
)
445 this->type
= TextBoxUndoActionTypeReplace
;
446 this->selection_anchor
= selection_anchor
;
447 this->selection_cursor
= selection_cursor
;
448 this->length
= length
;
451 this->deleted
= buffer
->Substring (start
, length
);
452 this->inserted
= g_new (gunichar
, 2);
453 memcpy (inserted
, &c
, sizeof (gunichar
));
458 TextBoxUndoActionReplace::~TextBoxUndoActionReplace ()
465 TextBoxUndoStack::TextBoxUndoStack (int max_count
)
467 this->max_count
= max_count
;
468 this->list
= new List ();
471 TextBoxUndoStack::~TextBoxUndoStack ()
477 TextBoxUndoStack::IsEmpty ()
479 return list
->IsEmpty ();
483 TextBoxUndoStack::Clear ()
489 TextBoxUndoStack::Push (TextBoxUndoAction
*action
)
491 if (list
->Length () == max_count
) {
492 List::Node
*node
= list
->Last ();
497 list
->Prepend (action
);
501 TextBoxUndoStack::Pop ()
503 List::Node
*node
= list
->First ();
508 return (TextBoxUndoAction
*) node
;
512 TextBoxUndoStack::Peek ()
514 return (TextBoxUndoAction
*) list
->First ();
522 // emit state, also doubles as available event mask
523 #define NOTHING_CHANGED (0)
524 #define SELECTION_CHANGED (1 << 0)
525 #define TEXT_CHANGED (1 << 1)
527 #define CONTROL_MASK GDK_CONTROL_MASK
528 #define SHIFT_MASK GDK_SHIFT_MASK
529 #define ALT_MASK GDK_MOD1_MASK
531 #define IsEOL(c) ((c) == '\r' || (c) == '\n')
534 GetGdkWindow (TextBoxBase
*textbox
)
538 if (!textbox
->IsAttached ())
541 if (!(window
= textbox
->GetDeployment ()->GetSurface ()->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
= g_new (ManagedTypeInfo
, 1);
566 type_info
->Initialize ("System.Windows", type_name
);
568 SetObjectType (type
);
569 SetDefaultStyleKey (type_info
);
570 ManagedTypeInfo::Free (type_info
);
572 AddHandler (UIElement::MouseLeftButtonMultiClickEvent
, TextBoxBase::mouse_left_button_multi_click
, this);
574 font
= new TextFontDescription ();
575 font
->SetFamily (GetFontFamily ()->source
);
576 font
->SetStretch (GetFontStretch ()->stretch
);
577 font
->SetWeight (GetFontWeight ()->weight
);
578 font
->SetStyle (GetFontStyle ()->style
);
579 font
->SetSize (GetFontSize ());
581 downloaders
= g_ptr_array_new ();
584 contentElement
= NULL
;
586 im_ctx
= gtk_im_multicontext_new ();
587 gtk_im_context_set_use_preedit (im_ctx
, false);
589 g_signal_connect (im_ctx
, "retrieve-surrounding", G_CALLBACK (TextBoxBase::retrieve_surrounding
), this);
590 g_signal_connect (im_ctx
, "delete-surrounding", G_CALLBACK (TextBoxBase::delete_surrounding
), this);
591 g_signal_connect (im_ctx
, "commit", G_CALLBACK (TextBoxBase::commit
), this);
593 undo
= new TextBoxUndoStack (10);
594 redo
= new TextBoxUndoStack (10);
595 buffer
= new TextBuffer ();
598 emit
= NOTHING_CHANGED
;
601 selection_anchor
= 0;
602 selection_cursor
= 0;
606 accepts_return
= false;
607 need_im_reset
= false;
608 is_read_only
= false;
619 TextBoxBase::~TextBoxBase ()
621 RemoveHandler (UIElement::MouseLeftButtonMultiClickEvent
, TextBoxBase::mouse_left_button_multi_click
, this);
624 g_object_unref (im_ctx
);
626 CleanupDownloaders ();
627 g_ptr_array_free (downloaders
, true);
628 g_free (font_source
);
637 TextBoxBase::SetIsAttached (bool value
)
639 Control::SetIsAttached (value
);
642 gtk_im_context_set_client_window (im_ctx
, GetGdkWindow (this));
646 TextBoxBase::CleanupDownloaders ()
648 Downloader
*downloader
;
651 for (i
= 0; i
< downloaders
->len
; i
++) {
652 downloader
= (Downloader
*) downloaders
->pdata
[i
];
653 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
654 downloader
->Abort ();
655 downloader
->unref ();
658 g_ptr_array_set_size (downloaders
, 0);
662 TextBoxBase::GetCursorOffset ()
664 if (!have_offset
&& view
) {
665 cursor_offset
= view
->GetCursor ().x
;
669 return cursor_offset
;
673 TextBoxBase::CursorDown (int cursor
, bool page
)
675 double y
= view
->GetCursor ().y
;
676 double x
= GetCursorOffset ();
677 TextLayoutLine
*line
;
682 if (!(line
= view
->GetLineFromY (y
, &index
)))
686 // calculate the number of lines to skip over
687 n
= GetActualHeight () / line
->height
;
692 if (index
+ n
>= view
->GetLineCount ()) {
693 // go to the end of the last line
694 line
= view
->GetLineFromIndex (view
->GetLineCount () - 1);
696 for (cur
= line
->offset
, i
= 0; i
< line
->runs
->len
; i
++) {
697 run
= (TextLayoutRun
*) line
->runs
->pdata
[i
];
706 line
= view
->GetLineFromIndex (index
+ n
);
708 return line
->GetCursorFromX (Point (), x
);
712 TextBoxBase::CursorUp (int cursor
, bool page
)
714 double y
= view
->GetCursor ().y
;
715 double x
= GetCursorOffset ();
716 TextLayoutLine
*line
;
719 if (!(line
= view
->GetLineFromY (y
, &index
)))
723 // calculate the number of lines to skip over
724 n
= GetActualHeight () / line
->height
;
730 // go to the beginning of the first line
735 line
= view
->GetLineFromIndex (index
- n
);
737 return line
->GetCursorFromX (Point (), x
);
744 CharClassAlphaNumeric
747 static inline CharClass
748 char_class (gunichar c
)
750 if (g_unichar_isspace (c
))
751 return CharClassWhitespace
;
753 if (g_unichar_isalnum (c
))
754 return CharClassAlphaNumeric
;
756 return CharClassUnknown
;
760 is_start_of_word (TextBuffer
*buffer
, int index
)
762 // A 'word' starts with an AlphaNumeric immediately preceeded by lwsp
763 if (index
> 0 && !g_unichar_isspace (buffer
->text
[index
- 1]))
766 return g_unichar_isalnum (buffer
->text
[index
]);
771 TextBoxBase::CursorNextWord (int cursor
)
775 // find the end of the current line
776 cr
= CursorLineEnd (cursor
);
777 if (buffer
->text
[cr
] == '\r' && buffer
->text
[cr
+ 1] == '\n')
782 // if the cursor is at the end of the line, return the starting offset of the next line
783 if (cursor
== cr
|| cursor
== lf
) {
784 if (lf
< buffer
->len
)
791 CharClass cc
= char_class (buffer
->text
[cursor
]);
794 // skip over the word, punctuation, or run of whitespace
795 while (i
< cr
&& char_class (buffer
->text
[i
]) == cc
)
798 // skip any whitespace after the word/punct
799 while (i
< cr
&& g_unichar_isspace (buffer
->text
[i
]))
804 // skip to the end of the current word
805 while (i
< cr
&& !g_unichar_isspace (buffer
->text
[i
]))
808 // skip any whitespace after the word
809 while (i
< cr
&& g_unichar_isspace (buffer
->text
[i
]))
812 // find the start of the next word
813 while (i
< cr
&& !is_start_of_word (buffer
, i
))
821 TextBoxBase::CursorPrevWord (int cursor
)
823 int begin
, i
, cr
, lf
;
825 // find the beginning of the current line
826 lf
= CursorLineBegin (cursor
) - 1;
828 if (lf
> 0 && buffer
->text
[lf
] == '\n' && buffer
->text
[lf
- 1] == '\r')
833 // if the cursor is at the beginning of the line, return the end of the prev line
834 if (cursor
- 1 == lf
) {
842 CharClass cc
= char_class (buffer
->text
[cursor
- 1]);
846 // skip over the word, punctuation, or run of whitespace
847 while (i
> begin
&& char_class (buffer
->text
[i
- 1]) == cc
)
850 // if the cursor was at whitespace, skip back a word too
851 if (cc
== CharClassWhitespace
&& i
> begin
) {
852 cc
= char_class (buffer
->text
[i
- 1]);
853 while (i
> begin
&& char_class (buffer
->text
[i
- 1]) == cc
)
860 if (cursor
< buffer
->len
) {
861 // skip to the beginning of this word
862 while (i
> begin
&& !g_unichar_isspace (buffer
->text
[i
- 1]))
865 if (i
< cursor
&& is_start_of_word (buffer
, i
))
869 // skip to the start of the lwsp
870 while (i
> begin
&& g_unichar_isspace (buffer
->text
[i
- 1]))
876 // skip to the beginning of the word
877 while (i
> begin
&& !is_start_of_word (buffer
, i
))
885 TextBoxBase::CursorLineBegin (int cursor
)
889 // find the beginning of the line
890 while (cur
> 0 && !IsEOL (buffer
->text
[cur
- 1]))
897 TextBoxBase::CursorLineEnd (int cursor
, bool include
)
901 // find the end of the line
902 while (cur
< buffer
->len
&& !IsEOL (buffer
->text
[cur
]))
905 if (include
&& cur
< buffer
->len
) {
906 if (buffer
->text
[cur
] == '\r' && buffer
->text
[cur
+ 1] == '\n')
916 TextBoxBase::KeyPressBackSpace (GdkModifierType modifiers
)
918 int anchor
= selection_anchor
;
919 int cursor
= selection_cursor
;
920 TextBoxUndoAction
*action
;
921 int start
= 0, length
= 0;
922 bool handled
= false;
924 if ((modifiers
& (ALT_MASK
| SHIFT_MASK
)) != 0)
927 if (cursor
!= anchor
) {
928 // BackSpace w/ active selection: delete the selected text
929 length
= abs (cursor
- anchor
);
930 start
= MIN (anchor
, cursor
);
931 } else if ((modifiers
& CONTROL_MASK
) != 0) {
932 // Ctrl+BackSpace: delete the word ending at the cursor
933 start
= CursorPrevWord (cursor
);
934 length
= cursor
- start
;
935 } else if (cursor
> 0) {
936 // BackSpace: delete the char before the cursor position
937 if (cursor
>= 2 && buffer
->text
[cursor
- 1] == '\n' && buffer
->text
[cursor
- 2] == '\r') {
947 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
951 buffer
->Cut (start
, length
);
952 emit
|= TEXT_CHANGED
;
958 // check to see if selection has changed
959 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
960 SetSelectionStart (MIN (anchor
, cursor
));
961 SetSelectionLength (abs (cursor
- anchor
));
962 selection_anchor
= anchor
;
963 selection_cursor
= cursor
;
964 emit
|= SELECTION_CHANGED
;
972 TextBoxBase::KeyPressDelete (GdkModifierType modifiers
)
974 int anchor
= selection_anchor
;
975 int cursor
= selection_cursor
;
976 TextBoxUndoAction
*action
;
977 int start
= 0, length
= 0;
978 bool handled
= false;
980 if ((modifiers
& (ALT_MASK
| SHIFT_MASK
)) != 0)
983 if (cursor
!= anchor
) {
984 // Delete w/ active selection: delete the selected text
985 length
= abs (cursor
- anchor
);
986 start
= MIN (anchor
, cursor
);
987 } else if ((modifiers
& CONTROL_MASK
) != 0) {
988 // Ctrl+Delete: delete the word starting at the cursor
989 length
= CursorNextWord (cursor
) - cursor
;
991 } else if (cursor
< buffer
->len
) {
992 // Delete: delete the char after the cursor position
993 if (buffer
->text
[cursor
] == '\r' && buffer
->text
[cursor
+ 1] == '\n')
1002 action
= new TextBoxUndoActionDelete (selection_anchor
, selection_cursor
, buffer
, start
, length
);
1003 undo
->Push (action
);
1006 buffer
->Cut (start
, length
);
1007 emit
|= TEXT_CHANGED
;
1011 // check to see if selection has changed
1012 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1013 SetSelectionStart (MIN (anchor
, cursor
));
1014 SetSelectionLength (abs (cursor
- anchor
));
1015 selection_anchor
= anchor
;
1016 selection_cursor
= cursor
;
1017 emit
|= SELECTION_CHANGED
;
1025 TextBoxBase::KeyPressPageDown (GdkModifierType modifiers
)
1027 int anchor
= selection_anchor
;
1028 int cursor
= selection_cursor
;
1031 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1034 // move the cursor down one page from its current position
1035 cursor
= CursorDown (cursor
, true);
1038 if ((modifiers
& SHIFT_MASK
) == 0) {
1039 // clobber the selection
1043 // check to see if selection has changed
1044 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1045 SetSelectionStart (MIN (anchor
, cursor
));
1046 SetSelectionLength (abs (cursor
- anchor
));
1047 selection_anchor
= anchor
;
1048 selection_cursor
= cursor
;
1049 emit
|= SELECTION_CHANGED
;
1057 TextBoxBase::KeyPressPageUp (GdkModifierType modifiers
)
1059 int anchor
= selection_anchor
;
1060 int cursor
= selection_cursor
;
1063 if ((modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1066 // move the cursor up one page from its current position
1067 cursor
= CursorUp (cursor
, true);
1070 if ((modifiers
& SHIFT_MASK
) == 0) {
1071 // clobber the selection
1075 // check to see if selection has changed
1076 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1077 SetSelectionStart (MIN (anchor
, cursor
));
1078 SetSelectionLength (abs (cursor
- anchor
));
1079 selection_anchor
= anchor
;
1080 selection_cursor
= cursor
;
1081 emit
|= SELECTION_CHANGED
;
1089 TextBoxBase::KeyPressDown (GdkModifierType modifiers
)
1091 int anchor
= selection_anchor
;
1092 int cursor
= selection_cursor
;
1093 bool handled
= false;
1096 if (!accepts_return
|| (modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1099 // move the cursor down by one line from its current position
1100 cursor
= CursorDown (cursor
, false);
1103 if ((modifiers
& SHIFT_MASK
) == 0) {
1104 // clobber the selection
1108 // check to see if selection has changed
1109 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1110 SetSelectionStart (MIN (anchor
, cursor
));
1111 SetSelectionLength (abs (cursor
- anchor
));
1112 selection_anchor
= anchor
;
1113 selection_cursor
= cursor
;
1114 emit
|= SELECTION_CHANGED
;
1123 TextBoxBase::KeyPressUp (GdkModifierType modifiers
)
1125 int anchor
= selection_anchor
;
1126 int cursor
= selection_cursor
;
1127 bool handled
= false;
1130 if (!accepts_return
|| (modifiers
& (CONTROL_MASK
| ALT_MASK
)) != 0)
1133 // move the cursor up by one line from its current position
1134 cursor
= CursorUp (cursor
, false);
1137 if ((modifiers
& SHIFT_MASK
) == 0) {
1138 // clobber the selection
1142 // check to see if selection has changed
1143 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1144 SetSelectionStart (MIN (anchor
, cursor
));
1145 SetSelectionLength (abs (cursor
- anchor
));
1146 selection_anchor
= anchor
;
1147 selection_cursor
= cursor
;
1148 emit
|= SELECTION_CHANGED
;
1157 TextBoxBase::KeyPressHome (GdkModifierType modifiers
)
1159 int anchor
= selection_anchor
;
1160 int cursor
= selection_cursor
;
1161 bool handled
= false;
1163 if ((modifiers
& ALT_MASK
) != 0)
1166 if ((modifiers
& CONTROL_MASK
) != 0) {
1167 // move the cursor to the beginning of the buffer
1170 // move the cursor to the beginning of the line
1171 cursor
= CursorLineBegin (cursor
);
1174 if ((modifiers
& SHIFT_MASK
) == 0) {
1175 // clobber the selection
1179 // check to see if selection has changed
1180 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1181 SetSelectionStart (MIN (anchor
, cursor
));
1182 SetSelectionLength (abs (cursor
- anchor
));
1183 selection_anchor
= anchor
;
1184 selection_cursor
= cursor
;
1185 emit
|= SELECTION_CHANGED
;
1186 have_offset
= false;
1194 TextBoxBase::KeyPressEnd (GdkModifierType modifiers
)
1196 int anchor
= selection_anchor
;
1197 int cursor
= selection_cursor
;
1198 bool handled
= false;
1200 if ((modifiers
& ALT_MASK
) != 0)
1203 if ((modifiers
& CONTROL_MASK
) != 0) {
1204 // move the cursor to the end of the buffer
1205 cursor
= buffer
->len
;
1207 // move the cursor to the end of the line
1208 cursor
= CursorLineEnd (cursor
);
1211 if ((modifiers
& SHIFT_MASK
) == 0) {
1212 // clobber the selection
1216 // check to see if selection has changed
1217 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1218 SetSelectionStart (MIN (anchor
, cursor
));
1219 SetSelectionLength (abs (cursor
- anchor
));
1220 selection_anchor
= anchor
;
1221 selection_cursor
= cursor
;
1222 emit
|= SELECTION_CHANGED
;
1223 have_offset
= false;
1231 TextBoxBase::KeyPressRight (GdkModifierType modifiers
)
1233 int anchor
= selection_anchor
;
1234 int cursor
= selection_cursor
;
1235 bool handled
= false;
1237 if ((modifiers
& ALT_MASK
) != 0)
1240 if ((modifiers
& CONTROL_MASK
) != 0) {
1241 // move the cursor to beginning of the next word
1242 cursor
= CursorNextWord (cursor
);
1243 } else if ((modifiers
& SHIFT_MASK
) == 0 && anchor
!= cursor
) {
1244 // set cursor at end of selection
1245 cursor
= MAX (anchor
, cursor
);
1247 // move the cursor forward one character
1248 if (buffer
->text
[cursor
] == '\r' && buffer
->text
[cursor
+ 1] == '\n')
1250 else if (cursor
< buffer
->len
)
1254 if ((modifiers
& SHIFT_MASK
) == 0) {
1255 // clobber the selection
1259 // check to see if selection has changed
1260 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1261 SetSelectionStart (MIN (anchor
, cursor
));
1262 SetSelectionLength (abs (cursor
- anchor
));
1263 selection_anchor
= anchor
;
1264 selection_cursor
= cursor
;
1265 emit
|= SELECTION_CHANGED
;
1273 TextBoxBase::KeyPressLeft (GdkModifierType modifiers
)
1275 int anchor
= selection_anchor
;
1276 int cursor
= selection_cursor
;
1277 bool handled
= false;
1279 if ((modifiers
& ALT_MASK
) != 0)
1282 if ((modifiers
& CONTROL_MASK
) != 0) {
1283 // move the cursor to the beginning of the previous word
1284 cursor
= CursorPrevWord (cursor
);
1285 } else if ((modifiers
& SHIFT_MASK
) == 0 && anchor
!= cursor
) {
1286 // set cursor at start of selection
1287 cursor
= MIN (anchor
, cursor
);
1289 // move the cursor backward one character
1290 if (cursor
>= 2 && buffer
->text
[cursor
- 2] == '\r' && buffer
->text
[cursor
- 1] == '\n')
1292 else if (cursor
> 0)
1296 if ((modifiers
& SHIFT_MASK
) == 0) {
1297 // clobber the selection
1301 // check to see if selection has changed
1302 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1303 SetSelectionStart (MIN (anchor
, cursor
));
1304 SetSelectionLength (abs (cursor
- anchor
));
1305 selection_anchor
= anchor
;
1306 selection_cursor
= cursor
;
1307 emit
|= SELECTION_CHANGED
;
1315 TextBoxBase::KeyPressUnichar (gunichar c
)
1317 int length
= abs (selection_cursor
- selection_anchor
);
1318 int start
= MIN (selection_anchor
, selection_cursor
);
1319 int anchor
= selection_anchor
;
1320 int cursor
= selection_cursor
;
1321 TextBoxUndoAction
*action
;
1323 if ((max_length
> 0 && buffer
->len
>= max_length
) || ((c
== '\r') && !accepts_return
))
1327 // replace the currently selected text
1328 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, c
);
1329 undo
->Push (action
);
1332 buffer
->Replace (start
, length
, &c
, 1);
1334 // insert the text at the cursor position
1335 TextBoxUndoActionInsert
*insert
= NULL
;
1337 if ((action
= undo
->Peek ()) && action
->type
== TextBoxUndoActionTypeInsert
) {
1338 insert
= (TextBoxUndoActionInsert
*) action
;
1340 if (!insert
->Insert (start
, c
))
1345 insert
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, c
);
1346 undo
->Push (insert
);
1351 buffer
->Insert (start
, c
);
1354 emit
|= TEXT_CHANGED
;
1358 // check to see if selection has changed
1359 if (selection_anchor
!= anchor
|| selection_cursor
!= cursor
) {
1360 SetSelectionStart (MIN (anchor
, cursor
));
1361 SetSelectionLength (abs (cursor
- anchor
));
1362 selection_anchor
= anchor
;
1363 selection_cursor
= cursor
;
1364 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
;
1995 GtkClipboard
*clipboard
;
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
;
2014 if (!secret
&& (clipboard
= GetClipboard (this, GDK_SELECTION_PRIMARY
))) {
2015 // copy the selection to the primary clipboard
2016 gtk_clipboard_set_text (clipboard
, GetSelectedText (), -1);
2022 TextBoxBase::OnLostFocus (RoutedEventArgs
*args
)
2025 emit
= NOTHING_CHANGED
;
2026 SetSelectionStart (selection_cursor
);
2027 SetSelectionLength (0);
2035 view
->OnLostFocus ();
2037 if (!is_read_only
) {
2038 gtk_im_context_focus_out (im_ctx
);
2039 need_im_reset
= true;
2044 TextBoxBase::OnGotFocus (RoutedEventArgs
*args
)
2049 view
->OnGotFocus ();
2051 if (!is_read_only
) {
2052 gtk_im_context_focus_in (im_ctx
);
2053 need_im_reset
= true;
2058 TextBoxBase::EmitCursorPositionChanged (double height
, double x
, double y
)
2060 Emit (TextBoxBase::CursorPositionChangedEvent
, new CursorPositionChangedEventArgs (height
, x
, y
));
2064 TextBoxBase::DownloaderComplete (Downloader
*downloader
)
2066 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2067 char *resource
, *filename
;
2068 InternalDownloader
*idl
;
2072 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2073 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
2078 if (!(idl
= downloader
->GetInternalDownloader ()))
2081 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
2084 uri
= downloader
->GetUri ();
2086 // If the downloaded file was a zip file, this'll get the path to the
2087 // extracted zip directory, else it will simply be the path to the
2089 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
2092 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
2093 manager
->AddResource (resource
, path
);
2096 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont
, NULL
));
2100 TextBoxBase::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
2102 ((TextBoxBase
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
2106 TextBoxBase::AddFontSource (Downloader
*downloader
)
2108 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
2109 g_ptr_array_add (downloaders
, downloader
);
2112 if (downloader
->Started () || downloader
->Completed ()) {
2113 if (downloader
->Completed ())
2114 DownloaderComplete (downloader
);
2116 // This is what actually triggers the download
2117 downloader
->Send ();
2122 TextBoxBase::AddFontResource (const char *resource
)
2124 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2125 Application
*application
= Application::GetCurrent ();
2126 Downloader
*downloader
;
2132 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
2133 if (IsAttached () && (downloader
= GetDeployment ()->GetSurface ()->CreateDownloader ())) {
2134 downloader
->Open ("GET", resource
, FontPolicy
);
2135 AddFontSource (downloader
);
2136 downloader
->unref ();
2144 manager
->AddResource (resource
, path
);
2150 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2152 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2154 if (args
->GetId () == Control::FontFamilyProperty
) {
2155 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
2156 char **families
, *fragment
;
2159 CleanupDownloaders ();
2161 if (family
&& family
->source
) {
2162 families
= g_strsplit (family
->source
, ",", -1);
2163 for (i
= 0; families
[i
]; i
++) {
2164 g_strstrip (families
[i
]);
2165 if ((fragment
= strchr (families
[i
], '#'))) {
2166 // the first portion of this string is the resource name...
2168 AddFontResource (families
[i
]);
2171 g_strfreev (families
);
2174 font
->SetFamily (family
? family
->source
: NULL
);
2175 changed
= TextBoxModelChangedFont
;
2176 } else if (args
->GetId () == Control::FontSizeProperty
) {
2177 double size
= args
->GetNewValue()->AsDouble ();
2178 changed
= TextBoxModelChangedFont
;
2179 font
->SetSize (size
);
2180 } else if (args
->GetId () == Control::FontStretchProperty
) {
2181 FontStretches stretch
= args
->GetNewValue()->AsFontStretch()->stretch
;
2182 changed
= TextBoxModelChangedFont
;
2183 font
->SetStretch (stretch
);
2184 } else if (args
->GetId () == Control::FontStyleProperty
) {
2185 FontStyles style
= args
->GetNewValue()->AsFontStyle ()->style
;
2186 changed
= TextBoxModelChangedFont
;
2187 font
->SetStyle (style
);
2188 } else if (args
->GetId () == Control::FontWeightProperty
) {
2189 FontWeights weight
= args
->GetNewValue()->AsFontWeight ()->weight
;
2190 changed
= TextBoxModelChangedFont
;
2191 font
->SetWeight (weight
);
2192 } else if (args
->GetId () == FrameworkElement::MinHeightProperty
) {
2193 // pass this along to our TextBoxView
2195 view
->SetMinHeight (args
->GetNewValue ()->AsDouble ());
2196 } else if (args
->GetId () == FrameworkElement::MaxHeightProperty
) {
2197 // pass this along to our TextBoxView
2199 view
->SetMaxHeight (args
->GetNewValue ()->AsDouble ());
2200 } else if (args
->GetId () == FrameworkElement::MinWidthProperty
) {
2201 // pass this along to our TextBoxView
2203 view
->SetMinWidth (args
->GetNewValue ()->AsDouble ());
2204 } else if (args
->GetId () == FrameworkElement::MaxWidthProperty
) {
2205 // pass this along to our TextBoxView
2207 view
->SetMaxWidth (args
->GetNewValue ()->AsDouble ());
2208 } else if (args
->GetId () == FrameworkElement::HeightProperty
) {
2209 // pass this along to our TextBoxView
2211 view
->SetHeight (args
->GetNewValue ()->AsDouble ());
2212 } else if (args
->GetId () == FrameworkElement::WidthProperty
) {
2213 // pass this along to our TextBoxView
2215 view
->SetWidth (args
->GetNewValue ()->AsDouble ());
2218 if (changed
!= TextBoxModelChangedNothing
)
2219 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2221 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE
) {
2222 Control::OnPropertyChanged (args
, error
);
2226 NotifyListenersOfPropertyChange (args
, error
);
2230 TextBoxBase::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2232 if (prop
&& (prop
->GetId () == Control::BackgroundProperty
||
2233 prop
->GetId () == Control::ForegroundProperty
)) {
2234 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2238 if (prop
->GetOwnerType () != Type::TEXTBOXBASE
)
2239 Control::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2243 TextBoxBase::OnApplyTemplate ()
2245 contentElement
= GetTemplateChild ("ContentElement");
2247 if (contentElement
== NULL
) {
2248 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2249 Control::OnApplyTemplate ();
2253 view
= new TextBoxView ();
2254 view
->SetTextBox (this);
2256 view
->SetMinHeight (GetMinHeight ());
2257 view
->SetMaxHeight (GetMaxHeight ());
2258 view
->SetMinWidth (GetMinWidth ());
2259 view
->SetMaxWidth (GetMaxWidth ());
2260 view
->SetHeight (GetHeight ());
2261 view
->SetWidth (GetWidth ());
2263 // Insert our TextBoxView
2264 if (contentElement
->Is (Type::CONTENTCONTROL
)) {
2265 ContentControl
*control
= (ContentControl
*) contentElement
;
2267 control
->SetValue (ContentControl::ContentProperty
, Value (view
));
2268 } else if (contentElement
->Is (Type::BORDER
)) {
2269 Border
*border
= (Border
*) contentElement
;
2271 border
->SetValue (Border::ChildProperty
, Value (view
));
2272 } else if (contentElement
->Is (Type::PANEL
)) {
2273 DependencyObjectCollection
*children
= ((Panel
*) contentElement
)->GetChildren ();
2275 children
->Add (view
);
2277 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentElement of type %s",
2278 contentElement
->GetType ()->GetName ());
2283 Control::OnApplyTemplate ();
2287 TextBoxBase::ClearSelection (int start
)
2290 SetSelectionStart (start
);
2291 SetSelectionLength (0);
2296 TextBoxBase::SelectWithError (int start
, int length
, MoonError
*error
)
2299 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection start must be >= 0");
2304 MoonError::FillIn (error
, MoonError::ARGUMENT
, "selection length must be >= 0");
2308 if (start
> buffer
->len
)
2309 start
= buffer
->len
;
2311 if (length
> (buffer
->len
- start
))
2312 length
= (buffer
->len
- start
);
2315 SetSelectionStart (start
);
2316 SetSelectionLength (length
);
2327 TextBoxBase::SelectAll ()
2329 SelectWithError (0, buffer
->len
, NULL
);
2333 TextBoxBase::CanUndo ()
2335 return !undo
->IsEmpty ();
2339 TextBoxBase::CanRedo ()
2341 return !redo
->IsEmpty ();
2345 TextBoxBase::Undo ()
2347 TextBoxUndoActionReplace
*replace
;
2348 TextBoxUndoActionInsert
*insert
;
2349 TextBoxUndoActionDelete
*dele
;
2350 TextBoxUndoAction
*action
;
2351 int anchor
= 0, cursor
= 0;
2353 if (undo
->IsEmpty ())
2356 action
= undo
->Pop ();
2357 redo
->Push (action
);
2359 switch (action
->type
) {
2360 case TextBoxUndoActionTypeInsert
:
2361 insert
= (TextBoxUndoActionInsert
*) action
;
2363 buffer
->Cut (insert
->start
, insert
->length
);
2364 anchor
= action
->selection_anchor
;
2365 cursor
= action
->selection_cursor
;
2367 case TextBoxUndoActionTypeDelete
:
2368 dele
= (TextBoxUndoActionDelete
*) action
;
2370 buffer
->Insert (dele
->start
, dele
->text
, dele
->length
);
2371 anchor
= action
->selection_anchor
;
2372 cursor
= action
->selection_cursor
;
2374 case TextBoxUndoActionTypeReplace
:
2375 replace
= (TextBoxUndoActionReplace
*) action
;
2377 buffer
->Cut (replace
->start
, replace
->inlen
);
2378 buffer
->Insert (replace
->start
, replace
->deleted
, replace
->length
);
2379 anchor
= action
->selection_anchor
;
2380 cursor
= action
->selection_cursor
;
2385 SetSelectionStart (MIN (anchor
, cursor
));
2386 SetSelectionLength (abs (cursor
- anchor
));
2387 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2388 selection_anchor
= anchor
;
2389 selection_cursor
= cursor
;
2396 TextBoxBase::Redo ()
2398 TextBoxUndoActionReplace
*replace
;
2399 TextBoxUndoActionInsert
*insert
;
2400 TextBoxUndoActionDelete
*dele
;
2401 TextBoxUndoAction
*action
;
2402 int anchor
= 0, cursor
= 0;
2404 if (redo
->IsEmpty ())
2407 action
= redo
->Pop ();
2408 undo
->Push (action
);
2410 switch (action
->type
) {
2411 case TextBoxUndoActionTypeInsert
:
2412 insert
= (TextBoxUndoActionInsert
*) action
;
2414 buffer
->Insert (insert
->start
, insert
->buffer
->text
, insert
->buffer
->len
);
2415 anchor
= cursor
= insert
->start
+ insert
->buffer
->len
;
2417 case TextBoxUndoActionTypeDelete
:
2418 dele
= (TextBoxUndoActionDelete
*) action
;
2420 buffer
->Cut (dele
->start
, dele
->length
);
2421 anchor
= cursor
= dele
->start
;
2423 case TextBoxUndoActionTypeReplace
:
2424 replace
= (TextBoxUndoActionReplace
*) action
;
2426 buffer
->Cut (replace
->start
, replace
->length
);
2427 buffer
->Insert (replace
->start
, replace
->inserted
, replace
->inlen
);
2428 anchor
= cursor
= replace
->start
+ replace
->inlen
;
2433 SetSelectionStart (MIN (anchor
, cursor
));
2434 SetSelectionLength (abs (cursor
- anchor
));
2435 emit
= TEXT_CHANGED
| SELECTION_CHANGED
;
2436 selection_anchor
= anchor
;
2437 selection_cursor
= cursor
;
2445 // TextBoxDynamicPropertyValueProvider
2448 class TextBoxDynamicPropertyValueProvider
: public FrameworkElementProvider
{
2449 Value
*selection_background
;
2450 Value
*selection_foreground
;
2453 TextBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : FrameworkElementProvider (obj
, precedence
)
2455 selection_background
= NULL
;
2456 selection_foreground
= NULL
;
2459 virtual ~TextBoxDynamicPropertyValueProvider ()
2461 delete selection_background
;
2462 delete selection_foreground
;
2465 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2467 if (property
->GetId () == TextBox::SelectionBackgroundProperty
) {
2468 return selection_background
;
2469 } else if (property
->GetId () == TextBox::SelectionForegroundProperty
) {
2470 return selection_foreground
;
2473 return FrameworkElementProvider::GetPropertyValue (property
);
2476 void InitializeSelectionBrushes ()
2478 if (!selection_background
)
2479 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2481 if (!selection_foreground
)
2482 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2493 providers
[PropertyPrecedence_DynamicValue
] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2495 Initialize (Type::TEXTBOX
, "System.Windows.Controls.TextBox");
2496 events_mask
= TEXT_CHANGED
| SELECTION_CHANGED
;
2501 TextBox::EmitSelectionChanged ()
2503 EmitAsync (TextBox::SelectionChangedEvent
, new RoutedEventArgs ());
2507 TextBox::EmitTextChanged ()
2509 EmitAsync (TextBox::TextChangedEvent
, new TextChangedEventArgs ());
2513 TextBox::SyncSelectedText ()
2515 if (selection_cursor
!= selection_anchor
) {
2516 int length
= abs (selection_cursor
- selection_anchor
);
2517 int start
= MIN (selection_anchor
, selection_cursor
);
2520 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2523 SetValue (TextBox::SelectedTextProperty
, Value (text
, true));
2527 SetValue (TextBox::SelectedTextProperty
, Value (""));
2533 TextBox::SyncText ()
2535 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
2538 SetValue (TextBox::TextProperty
, Value (text
, true));
2543 TextBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2545 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2546 DependencyProperty
*prop
;
2549 if (args
->GetId () == TextBox::AcceptsReturnProperty
) {
2550 // update accepts_return state
2551 accepts_return
= args
->GetNewValue ()->AsBool ();
2552 } else if (args
->GetId () == TextBox::CaretBrushProperty
) {
2553 // FIXME: if we want to be perfect, we could invalidate the
2554 // blinking cursor rect if it is active... but is it that
2556 } else if (args
->GetId () == TextBox::FontSourceProperty
) {
2557 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
2558 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2560 // FIXME: ideally we'd remove the old item from the cache (or,
2561 // rather, 'unref' it since some other textblocks/boxes might
2562 // still be using it).
2564 g_free (font_source
);
2566 if (source
&& source
->stream
)
2567 font_source
= manager
->AddResource (source
->stream
);
2571 changed
= TextBoxModelChangedFont
;
2572 font
->SetSource (font_source
);
2573 } else if (args
->GetId () == TextBox::IsReadOnlyProperty
) {
2574 // update is_read_only state
2575 is_read_only
= args
->GetNewValue ()->AsBool ();
2580 gtk_im_context_focus_out (im_ctx
);
2582 gtk_im_context_focus_in (im_ctx
);
2585 } else if (args
->GetId () == TextBox::MaxLengthProperty
) {
2586 // update max_length state
2587 max_length
= args
->GetNewValue ()->AsInt32 ();
2588 } else if (args
->GetId () == TextBox::SelectedTextProperty
) {
2590 Value
*value
= args
->GetNewValue ();
2591 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2592 TextBoxUndoAction
*action
= NULL
;
2596 length
= abs (selection_cursor
- selection_anchor
);
2597 start
= MIN (selection_anchor
, selection_cursor
);
2599 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2601 // replace the currently selected text
2602 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
2604 buffer
->Replace (start
, length
, text
, textlen
);
2605 } else if (textlen
> 0) {
2606 // insert the text at the cursor
2607 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
2609 buffer
->Insert (start
, text
, textlen
);
2614 if (action
!= NULL
) {
2615 emit
|= TEXT_CHANGED
;
2616 undo
->Push (action
);
2619 ClearSelection (start
+ textlen
);
2625 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2628 } else if (args
->GetId () == TextBox::SelectionStartProperty
) {
2629 length
= abs (selection_cursor
- selection_anchor
);
2630 start
= args
->GetNewValue ()->AsInt32 ();
2632 if (start
> buffer
->len
) {
2633 // clamp the selection start offset to a valid value
2634 SetSelectionStart (buffer
->len
);
2638 if (start
+ length
> buffer
->len
) {
2639 // clamp the selection length to a valid value
2641 length
= buffer
->len
- start
;
2642 SetSelectionLength (length
);
2646 // SelectionStartProperty is marked as AlwaysChange -
2647 // if the value hasn't actually changed, then we do
2648 // not want to emit the TextBoxModelChanged event.
2649 if (selection_anchor
!= start
) {
2650 changed
= TextBoxModelChangedSelection
;
2651 have_offset
= false;
2654 // When set programatically, anchor is always the
2655 // start and cursor is always the end.
2656 selection_cursor
= start
+ length
;
2657 selection_anchor
= start
;
2659 emit
|= SELECTION_CHANGED
;
2662 } else if (args
->GetId () == TextBox::SelectionLengthProperty
) {
2663 start
= MIN (selection_anchor
, selection_cursor
);
2664 length
= args
->GetNewValue ()->AsInt32 ();
2666 if (start
+ length
> buffer
->len
) {
2667 // clamp the selection length to a valid value
2668 length
= buffer
->len
- start
;
2669 SetSelectionLength (length
);
2673 // SelectionLengthProperty is marked as AlwaysChange -
2674 // if the value hasn't actually changed, then we do
2675 // not want to emit the TextBoxModelChanged event.
2676 if (selection_cursor
!= start
+ length
) {
2677 changed
= TextBoxModelChangedSelection
;
2678 have_offset
= false;
2681 // When set programatically, anchor is always the
2682 // start and cursor is always the end.
2683 selection_cursor
= start
+ length
;
2684 selection_anchor
= start
;
2686 emit
|= SELECTION_CHANGED
;
2689 } else if (args
->GetId () == TextBox::SelectionBackgroundProperty
) {
2690 changed
= TextBoxModelChangedBrush
;
2691 } else if (args
->GetId () == TextBox::SelectionForegroundProperty
) {
2692 changed
= TextBoxModelChangedBrush
;
2693 } else if (args
->GetId () == TextBox::TextProperty
) {
2695 Value
*value
= args
->GetNewValue ();
2696 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2697 TextBoxUndoAction
*action
;
2701 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2702 if (buffer
->len
> 0) {
2703 // replace the current text
2704 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
2706 buffer
->Replace (0, buffer
->len
, text
, textlen
);
2709 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
2711 buffer
->Insert (0, text
, textlen
);
2714 undo
->Push (action
);
2718 emit
|= TEXT_CHANGED
;
2722 SyncAndEmit (value
&& !value
->GetIsNull ());
2724 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str
);
2728 changed
= TextBoxModelChangedText
;
2729 } else if (args
->GetId () == TextBox::TextAlignmentProperty
) {
2730 changed
= TextBoxModelChangedTextAlignment
;
2731 } else if (args
->GetId () == TextBox::TextWrappingProperty
) {
2732 changed
= TextBoxModelChangedTextWrapping
;
2733 } else if (args
->GetId () == TextBox::HorizontalScrollBarVisibilityProperty
) {
2734 // XXX more crap because these aren't templatebound.
2735 if (contentElement
) {
2736 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2737 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2741 } else if (args
->GetId () == TextBox::VerticalScrollBarVisibilityProperty
) {
2742 // XXX more crap because these aren't templatebound.
2743 if (contentElement
) {
2744 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2745 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2751 if (changed
!= TextBoxModelChangedNothing
)
2752 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
2754 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
2755 TextBoxBase::OnPropertyChanged (args
, error
);
2759 NotifyListenersOfPropertyChange (args
, error
);
2763 TextBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
2765 if (prop
&& (prop
->GetId () == TextBox::SelectionBackgroundProperty
||
2766 prop
->GetId () == TextBox::SelectionForegroundProperty
)) {
2767 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
2771 if (prop
->GetOwnerType () != Type::TEXTBOX
)
2772 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
2776 TextBox::OnApplyTemplate ()
2778 DependencyProperty
*prop
;
2780 TextBoxBase::OnApplyTemplate ();
2782 if (!contentElement
)
2785 // XXX LAME these should be template bindings in the textbox template.
2786 if ((prop
= contentElement
->GetDependencyProperty ("VerticalScrollBarVisibility")))
2787 contentElement
->SetValue (prop
, GetValue (TextBox::VerticalScrollBarVisibilityProperty
));
2789 if ((prop
= contentElement
->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2790 contentElement
->SetValue (prop
, GetValue (TextBox::HorizontalScrollBarVisibilityProperty
));
2799 // PasswordBoxDynamicPropertyValueProvider
2802 class PasswordBoxDynamicPropertyValueProvider
: public FrameworkElementProvider
{
2803 Value
*selection_background
;
2804 Value
*selection_foreground
;
2807 PasswordBoxDynamicPropertyValueProvider (DependencyObject
*obj
, PropertyPrecedence precedence
) : FrameworkElementProvider (obj
, precedence
)
2809 selection_background
= NULL
;
2810 selection_foreground
= NULL
;
2813 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2815 delete selection_background
;
2816 delete selection_foreground
;
2819 virtual Value
*GetPropertyValue (DependencyProperty
*property
)
2821 if (property
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
2822 return selection_background
;
2823 } else if (property
->GetId () == PasswordBox::SelectionForegroundProperty
) {
2824 return selection_foreground
;
2827 return FrameworkElementProvider::GetPropertyValue (property
);
2830 void InitializeSelectionBrushes ()
2832 if (!selection_background
)
2833 selection_background
= Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2835 if (!selection_foreground
)
2836 selection_foreground
= Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2845 PasswordBox::PasswordBox ()
2847 providers
[PropertyPrecedence_DynamicValue
] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue
);
2849 Initialize (Type::PASSWORDBOX
, "System.Windows.Controls.PasswordBox");
2850 events_mask
= TEXT_CHANGED
;
2853 display
= g_string_new ("");
2856 PasswordBox::~PasswordBox ()
2858 g_string_free (display
, true);
2862 PasswordBox::CursorDown (int cursor
, bool page
)
2864 return GetBuffer ()->len
;
2868 PasswordBox::CursorUp (int cursor
, bool page
)
2874 PasswordBox::CursorLineBegin (int cursor
)
2880 PasswordBox::CursorLineEnd (int cursor
, bool include
)
2882 return GetBuffer ()->len
;
2886 PasswordBox::CursorNextWord (int cursor
)
2888 return GetBuffer ()->len
;
2892 PasswordBox::CursorPrevWord (int cursor
)
2898 PasswordBox::EmitTextChanged ()
2900 EmitAsync (PasswordBox::PasswordChangedEvent
, new RoutedEventArgs ());
2904 PasswordBox::SyncSelectedText ()
2906 if (selection_cursor
!= selection_anchor
) {
2907 int length
= abs (selection_cursor
- selection_anchor
);
2908 int start
= MIN (selection_anchor
, selection_cursor
);
2911 text
= g_ucs4_to_utf8 (buffer
->text
+ start
, length
, NULL
, NULL
, NULL
);
2914 SetValue (PasswordBox::SelectedTextProperty
, Value (text
, true));
2918 SetValue (PasswordBox::SelectedTextProperty
, Value (""));
2924 PasswordBox::SyncDisplayText ()
2926 gunichar c
= GetPasswordChar ();
2928 g_string_truncate (display
, 0);
2930 for (int i
= 0; i
< buffer
->len
; i
++)
2931 g_string_append_unichar (display
, c
);
2935 PasswordBox::SyncText ()
2937 char *text
= g_ucs4_to_utf8 (buffer
->text
, buffer
->len
, NULL
, NULL
, NULL
);
2940 SetValue (PasswordBox::PasswordProperty
, Value (text
, true));
2947 PasswordBox::GetDisplayText ()
2949 return display
->str
;
2953 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
2955 TextBoxModelChangeType changed
= TextBoxModelChangedNothing
;
2958 if (args
->GetId () == PasswordBox::CaretBrushProperty
) {
2959 // FIXME: if we want to be perfect, we could invalidate the
2960 // blinking cursor rect if it is active... but is it that
2962 } else if (args
->GetId () == PasswordBox::FontSourceProperty
) {
2963 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
2964 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
2966 // FIXME: ideally we'd remove the old item from the cache (or,
2967 // rather, 'unref' it since some other textblocks/boxes might
2968 // still be using it).
2970 g_free (font_source
);
2972 if (source
&& source
->stream
)
2973 font_source
= manager
->AddResource (source
->stream
);
2977 changed
= TextBoxModelChangedFont
;
2978 font
->SetSource (font_source
);
2979 } else if (args
->GetId () == PasswordBox::MaxLengthProperty
) {
2980 // update max_length state
2981 max_length
= args
->GetNewValue()->AsInt32 ();
2982 } else if (args
->GetId () == PasswordBox::PasswordCharProperty
) {
2983 changed
= TextBoxModelChangedText
;
2984 } else if (args
->GetId () == PasswordBox::PasswordProperty
) {
2986 Value
*value
= args
->GetNewValue ();
2987 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
2988 TextBoxUndoAction
*action
;
2992 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
2993 if (buffer
->len
> 0) {
2994 // replace the current text
2995 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, 0, buffer
->len
, text
, textlen
);
2997 buffer
->Replace (0, buffer
->len
, text
, textlen
);
3000 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, 0, text
, textlen
);
3002 buffer
->Insert (0, text
, textlen
);
3005 undo
->Push (action
);
3009 emit
|= TEXT_CHANGED
;
3018 changed
= TextBoxModelChangedText
;
3019 } else if (args
->GetId () == PasswordBox::SelectedTextProperty
) {
3021 Value
*value
= args
->GetNewValue ();
3022 const char *str
= value
&& value
->AsString () ? value
->AsString () : "";
3023 TextBoxUndoAction
*action
= NULL
;
3027 length
= abs (selection_cursor
- selection_anchor
);
3028 start
= MIN (selection_anchor
, selection_cursor
);
3030 if ((text
= g_utf8_to_ucs4_fast (str
, -1, &textlen
))) {
3032 // replace the currently selected text
3033 action
= new TextBoxUndoActionReplace (selection_anchor
, selection_cursor
, buffer
, start
, length
, text
, textlen
);
3035 buffer
->Replace (start
, length
, text
, textlen
);
3036 } else if (textlen
> 0) {
3037 // insert the text at the cursor
3038 action
= new TextBoxUndoActionInsert (selection_anchor
, selection_cursor
, start
, text
, textlen
);
3040 buffer
->Insert (start
, text
, textlen
);
3045 if (action
!= NULL
) {
3046 undo
->Push (action
);
3049 ClearSelection (start
+ textlen
);
3050 emit
|= TEXT_CHANGED
;
3058 } else if (args
->GetId () == PasswordBox::SelectionStartProperty
) {
3059 length
= abs (selection_cursor
- selection_anchor
);
3060 start
= args
->GetNewValue ()->AsInt32 ();
3062 if (start
> buffer
->len
) {
3063 // clamp the selection start offset to a valid value
3064 SetSelectionStart (buffer
->len
);
3068 if (start
+ length
> buffer
->len
) {
3069 // clamp the selection length to a valid value
3071 length
= buffer
->len
- start
;
3072 SetSelectionLength (length
);
3076 // SelectionStartProperty is marked as AlwaysChange -
3077 // if the value hasn't actually changed, then we do
3078 // not want to emit the TextBoxModelChanged event.
3079 if (selection_anchor
!= start
) {
3080 changed
= TextBoxModelChangedSelection
;
3081 have_offset
= false;
3084 // When set programatically, anchor is always the
3085 // start and cursor is always the end.
3086 selection_cursor
= start
+ length
;
3087 selection_anchor
= start
;
3089 emit
|= SELECTION_CHANGED
;
3092 } else if (args
->GetId () == PasswordBox::SelectionLengthProperty
) {
3093 start
= MIN (selection_anchor
, selection_cursor
);
3094 length
= args
->GetNewValue ()->AsInt32 ();
3096 if (start
+ length
> buffer
->len
) {
3097 // clamp the selection length to a valid value
3098 length
= buffer
->len
- start
;
3099 SetSelectionLength (length
);
3103 // SelectionLengthProperty is marked as AlwaysChange -
3104 // if the value hasn't actually changed, then we do
3105 // not want to emit the TextBoxModelChanged event.
3106 if (selection_cursor
!= start
+ length
) {
3107 changed
= TextBoxModelChangedSelection
;
3108 have_offset
= false;
3111 // When set programatically, anchor is always the
3112 // start and cursor is always the end.
3113 selection_cursor
= start
+ length
;
3114 selection_anchor
= start
;
3116 emit
|= SELECTION_CHANGED
;
3119 } else if (args
->GetId () == PasswordBox::SelectionBackgroundProperty
) {
3120 changed
= TextBoxModelChangedBrush
;
3121 } else if (args
->GetId () == PasswordBox::SelectionForegroundProperty
) {
3122 changed
= TextBoxModelChangedBrush
;
3125 if (changed
!= TextBoxModelChangedNothing
)
3126 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (changed
, args
));
3128 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBOX
) {
3129 TextBoxBase::OnPropertyChanged (args
, error
);
3133 NotifyListenersOfPropertyChange (args
, error
);
3137 PasswordBox::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
3139 if (prop
&& (prop
->GetId () == PasswordBox::SelectionBackgroundProperty
||
3140 prop
->GetId () == PasswordBox::SelectionForegroundProperty
)) {
3141 Emit (ModelChangedEvent
, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush
));
3145 if (prop
->GetOwnerType () != Type::TEXTBOX
)
3146 TextBoxBase::OnSubPropertyChanged (prop
, obj
, subobj_args
);
3154 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3155 #define CURSOR_BLINK_ON_MULTIPLIER 2
3156 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3157 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3158 #define CURSOR_BLINK_DIVIDER 3
3160 TextBoxView::TextBoxView ()
3162 SetObjectType (Type::TEXTBOXVIEW
);
3164 AddHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3165 AddHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3167 SetCursor (MouseCursorIBeam
);
3169 cursor
= Rect (0, 0, 0, 0);
3170 layout
= new TextLayout ();
3171 selection_changed
= false;
3172 had_selected_text
= false;
3173 cursor_visible
= false;
3179 TextBoxView::~TextBoxView ()
3181 RemoveHandler (UIElement::MouseLeftButtonDownEvent
, TextBoxView::mouse_left_button_down
, this);
3182 RemoveHandler (UIElement::MouseLeftButtonUpEvent
, TextBoxView::mouse_left_button_up
, this);
3185 textbox
->RemoveHandler (TextBox::ModelChangedEvent
, TextBoxView::model_changed
, this);
3186 textbox
->view
= NULL
;
3189 DisconnectBlinkTimeout ();
3195 TextBoxView::GetLineFromY (double y
, int *index
)
3197 return layout
->GetLineFromY (Point (), y
, index
);
3201 TextBoxView::GetLineFromIndex (int index
)
3203 return layout
->GetLineFromIndex (index
);
3207 TextBoxView::GetCursorFromXY (double x
, double y
)
3209 return layout
->GetCursorFromXY (Point (), x
, y
);
3213 TextBoxView::blink (void *user_data
)
3215 return ((TextBoxView
*) user_data
)->Blink ();
3219 GetCursorBlinkTimeout (TextBoxView
*view
)
3221 GtkSettings
*settings
;
3227 if (!view
->IsAttached ())
3228 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3230 if (!(window
= view
->GetDeployment ()->GetSurface ()->GetWindow ()))
3231 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3233 if (!(widget
= window
->GetGdkWindow ()))
3234 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3236 if (!(screen
= gdk_drawable_get_screen ((GdkDrawable
*) widget
)))
3237 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3239 if (!(settings
= gtk_settings_get_for_screen (screen
)))
3240 return CURSOR_BLINK_TIMEOUT_DEFAULT
;
3242 g_object_get (settings
, "gtk-cursor-blink-time", &timeout
, NULL
);
3248 TextBoxView::ConnectBlinkTimeout (guint multiplier
)
3250 guint timeout
= GetCursorBlinkTimeout (this) * multiplier
/ CURSOR_BLINK_DIVIDER
;
3251 TimeManager
*manager
;
3253 if (!IsAttached () || !(manager
= GetDeployment ()->GetSurface ()->GetTimeManager ()))
3256 blink_timeout
= manager
->AddTimeout (MOON_PRIORITY_DEFAULT
, timeout
, TextBoxView::blink
, this);
3260 TextBoxView::DisconnectBlinkTimeout ()
3262 TimeManager
*manager
;
3264 if (blink_timeout
!= 0) {
3265 if (!IsAttached () || !(manager
= GetDeployment ()->GetSurface ()->GetTimeManager ()))
3268 manager
->RemoveTimeout (blink_timeout
);
3274 TextBoxView::Blink ()
3278 SetCurrentDeployment (true);
3280 if (cursor_visible
) {
3281 multiplier
= CURSOR_BLINK_OFF_MULTIPLIER
;
3284 multiplier
= CURSOR_BLINK_ON_MULTIPLIER
;
3288 ConnectBlinkTimeout (multiplier
);
3294 TextBoxView::DelayCursorBlink ()
3296 DisconnectBlinkTimeout ();
3297 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER
);
3298 UpdateCursor (true);
3303 TextBoxView::BeginCursorBlink ()
3305 if (blink_timeout
== 0) {
3306 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER
);
3307 UpdateCursor (true);
3313 TextBoxView::EndCursorBlink ()
3315 DisconnectBlinkTimeout ();
3322 TextBoxView::ResetCursorBlink (bool delay
)
3324 if (textbox
->IsFocused () && !textbox
->HasSelectedText ()) {
3325 // cursor is blinkable... proceed with blinkage
3327 DelayCursorBlink ();
3329 BeginCursorBlink ();
3331 // cursor not blinkable... stop all blinkage
3337 TextBoxView::InvalidateCursor ()
3339 Invalidate (cursor
.Transform (&absolute_xform
));
3343 TextBoxView::ShowCursor ()
3345 cursor_visible
= true;
3346 InvalidateCursor ();
3350 TextBoxView::HideCursor ()
3352 cursor_visible
= false;
3353 InvalidateCursor ();
3357 TextBoxView::UpdateCursor (bool invalidate
)
3359 int cur
= textbox
->GetCursor ();
3363 // invalidate current cursor rect
3364 if (invalidate
&& cursor_visible
)
3365 InvalidateCursor ();
3367 // calculate the new cursor rect
3368 cursor
= layout
->GetCursor (Point (), cur
);
3370 // transform the cursor rect into absolute coordinates for the IM context
3371 rect
= cursor
.Transform (&absolute_xform
);
3372 area
= rect
.ToGdkRectangle ();
3374 gtk_im_context_set_cursor_location (textbox
->im_ctx
, &area
);
3376 textbox
->EmitCursorPositionChanged (cursor
.height
, cursor
.x
, cursor
.y
);
3378 // invalidate the new cursor rect
3379 if (invalidate
&& cursor_visible
)
3380 InvalidateCursor ();
3384 TextBoxView::UpdateText ()
3386 const char *text
= textbox
->GetDisplayText ();
3388 layout
->SetText (text
? text
: "", -1);
3392 TextBoxView::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
3394 *height
= GetActualHeight ();
3395 *width
= GetActualWidth ();
3399 TextBoxView::ComputeActualSize ()
3401 UIElement
*parent
= GetVisualParent ();
3403 if (LayoutInformation::GetLayoutSlot (this))
3404 return FrameworkElement::ComputeActualSize ();
3406 Layout (Size (INFINITY
, INFINITY
));
3409 layout
->GetActualExtents (&actual
.width
, &actual
.height
);
3415 TextBoxView::MeasureOverride (Size availableSize
)
3417 Size desired
= Size ();
3419 Layout (availableSize
);
3421 layout
->GetActualExtents (&desired
.width
, &desired
.height
);
3423 /* FIXME using a magic number for minumum width here */
3424 if (isinf (availableSize
.width
))
3425 desired
.width
= MAX (desired
.width
, 11);
3427 return desired
.Min (availableSize
);
3431 TextBoxView::ArrangeOverride (Size finalSize
)
3433 Size arranged
= Size ();
3437 layout
->GetActualExtents (&arranged
.width
, &arranged
.height
);
3439 arranged
= arranged
.Max (finalSize
);
3445 TextBoxView::Layout (Size constraint
)
3447 layout
->SetMaxWidth (constraint
.width
);
3454 TextBoxView::Paint (cairo_t
*cr
)
3456 layout
->Render (cr
, GetOriginPoint (), Point ());
3458 if (cursor_visible
) {
3459 cairo_antialias_t alias
= cairo_get_antialias (cr
);
3460 Brush
*caret
= textbox
->GetCaretBrush ();
3461 double h
= round (cursor
.height
);
3462 double x
= cursor
.x
;
3463 double y
= cursor
.y
;
3465 // disable antialiasing
3466 cairo_set_antialias (cr
, CAIRO_ANTIALIAS_NONE
);
3468 // snap 'x' to the half-pixel grid (to try and get a sharp 1-pixel-wide line)
3469 cairo_user_to_device (cr
, &x
, &y
);
3470 x
= trunc (x
) + 0.5; y
= trunc (y
);
3471 cairo_device_to_user (cr
, &x
, &y
);
3473 // set the cursor color
3474 caret
->SetupBrush (cr
, cursor
);
3477 cairo_set_line_width (cr
, 1.0);
3478 cairo_move_to (cr
, x
, y
);
3479 cairo_line_to (cr
, x
, y
+ h
);
3484 // restore antialiasing
3485 cairo_set_antialias (cr
, alias
);
3490 TextBoxView::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
3492 TextBoxDynamicPropertyValueProvider
*dynamic
= (TextBoxDynamicPropertyValueProvider
*) textbox
->providers
[PropertyPrecedence_DynamicValue
];
3493 Size renderSize
= GetRenderSize ();
3495 dynamic
->InitializeSelectionBrushes ();
3497 UpdateCursor (false);
3499 if (selection_changed
) {
3500 layout
->Select (textbox
->GetSelectionStart (), textbox
->GetSelectionLength ());
3501 selection_changed
= false;
3505 cairo_set_matrix (cr
, &absolute_xform
);
3508 RenderLayoutClip (cr
);
3510 layout
->SetAvailableWidth (renderSize
.width
);
3516 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs
*args
)
3518 switch (args
->changed
) {
3519 case TextBoxModelChangedTextAlignment
:
3520 // text alignment changed, update our layout
3521 if (layout
->SetTextAlignment ((TextAlignment
) args
->property
->GetNewValue()->AsInt32 ()))
3524 case TextBoxModelChangedTextWrapping
:
3525 // text wrapping changed, update our layout
3526 if (layout
->SetTextWrapping ((TextWrapping
) args
->property
->GetNewValue()->AsInt32 ()))
3529 case TextBoxModelChangedSelection
:
3530 if (had_selected_text
|| textbox
->HasSelectedText ()) {
3531 // the selection has changed, update the layout's selection
3532 had_selected_text
= textbox
->HasSelectedText ();
3533 selection_changed
= true;
3534 ResetCursorBlink (false);
3536 // cursor position changed
3537 ResetCursorBlink (true);
3541 case TextBoxModelChangedBrush
:
3542 // a brush has changed, no layout updates needed, we just need to re-render
3544 case TextBoxModelChangedFont
:
3545 // font changed, need to recalculate layout/bounds
3546 layout
->ResetState ();
3549 case TextBoxModelChangedText
:
3550 // the text has changed, need to recalculate layout/bounds
3555 // nothing changed??
3560 InvalidateMeasure ();
3561 UpdateBounds (true);
3568 TextBoxView::model_changed (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3570 ((TextBoxView
*) closure
)->OnModelChanged ((TextBoxModelChangedEventArgs
*) args
);
3574 TextBoxView::OnLostFocus ()
3580 TextBoxView::OnGotFocus ()
3582 ResetCursorBlink (false);
3586 TextBoxView::OnMouseLeftButtonDown (MouseButtonEventArgs
*args
)
3588 // proxy to our parent TextBox control
3589 textbox
->OnMouseLeftButtonDown (args
);
3593 TextBoxView::mouse_left_button_down (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3595 ((TextBoxView
*) closure
)->OnMouseLeftButtonDown ((MouseButtonEventArgs
*) args
);
3599 TextBoxView::OnMouseLeftButtonUp (MouseButtonEventArgs
*args
)
3601 // proxy to our parent TextBox control
3602 textbox
->OnMouseLeftButtonUp (args
);
3606 TextBoxView::mouse_left_button_up (EventObject
*sender
, EventArgs
*args
, gpointer closure
)
3608 ((TextBoxView
*) closure
)->OnMouseLeftButtonUp ((MouseButtonEventArgs
*) args
);
3612 TextBoxView::SetTextBox (TextBoxBase
*textbox
)
3614 TextLayoutAttributes
*attrs
;
3616 if (this->textbox
== textbox
)
3619 if (this->textbox
) {
3620 // remove the event handlers from the old textbox
3621 this->textbox
->RemoveHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3624 this->textbox
= textbox
;
3627 textbox
->AddHandler (TextBoxBase::ModelChangedEvent
, TextBoxView::model_changed
, this);
3629 // sync our state with the textbox
3630 layout
->SetTextAttributes (new List ());
3631 attrs
= new TextLayoutAttributes ((ITextAttributes
*) textbox
, 0);
3632 layout
->GetTextAttributes ()->Append (attrs
);
3634 layout
->SetTextAlignment (textbox
->GetTextAlignment ());
3635 layout
->SetTextWrapping (textbox
->GetTextWrapping ());
3636 had_selected_text
= textbox
->HasSelectedText ();
3637 selection_changed
= true;
3640 layout
->SetTextAttributes (NULL
);
3641 layout
->SetText (NULL
, -1);
3644 UpdateBounds (true);
3645 InvalidateMeasure ();