2009-11-05 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / textbox.cpp
blob8ddc041a91c0a88f449c5718aa7b2946de9a38b1
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * textbox.cpp:
5 * Contact:
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.
13 #include <config.h>
15 #include <gdk/gdkkeysyms.h>
16 #include <cairo.h>
18 #include <string.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 #include <errno.h>
24 #include "dependencyproperty.h"
25 #include "file-downloader.h"
26 #include "contentcontrol.h"
27 #include "timemanager.h"
28 #include "runtime.h"
29 #include "textbox.h"
30 #include "border.h"
31 #include "panel.h"
32 #include "utils.h"
33 #include "uri.h"
35 #include "geometry.h"
38 // TextBuffer
41 #define UNICODE_LEN(size) (sizeof (gunichar) * (size))
42 #define UNICODE_OFFSET(buf,offset) (((char *) buf) + UNICODE_LEN (offset))
44 class TextBuffer {
45 int allocated;
47 bool Resize (int needed)
49 int new_size = allocated;
50 bool resize = false;
51 void *buf;
53 if (allocated >= needed + 128) {
54 while (new_size >= needed + 128)
55 new_size -= 128;
56 resize = true;
57 } else if (allocated < needed) {
58 while (new_size < needed)
59 new_size += 128;
60 resize = true;
63 if (resize) {
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;
70 allocated = new_size;
73 return true;
76 public:
77 gunichar *text;
78 int len;
80 TextBuffer (const gunichar *text, int len)
82 this->allocated = 0;
83 this->text = NULL;
84 this->len = 0;
86 Append (text, len);
89 TextBuffer ()
91 text = NULL;
92 Reset ();
95 void Reset ()
97 text = (gunichar *) g_realloc (text, UNICODE_LEN (128));
98 allocated = 128;
99 text[0] = '\0';
100 len = 0;
103 void Print ()
105 printf ("TextBuffer::text = \"");
107 for (int i = 0; i < len; i++) {
108 switch (text[i]) {
109 case '\r':
110 fputs ("\\r", stdout);
111 break;
112 case '\n':
113 fputs ("\\n", stdout);
114 break;
115 case '\0':
116 fputs ("\\0", stdout);
117 break;
118 case '\\':
119 fputc ('\\', stdout);
120 // fall thru
121 default:
122 fputc ((char) text[i], stdout);
123 break;
127 printf ("\";\n");
130 void Append (gunichar c)
132 if (!Resize (len + 2))
133 return;
135 text[len++] = c;
136 text[len] = 0;
139 void Append (const gunichar *str, int count)
141 if (!Resize (len + count + 1))
142 return;
144 memcpy (UNICODE_OFFSET (text, len), str, UNICODE_LEN (count));
145 len += count;
146 text[len] = 0;
149 void Cut (int start, int length)
151 char *dest, *src;
152 int beyond;
154 if (length == 0 || start >= len)
155 return;
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));
165 len -= length;
168 void Insert (int index, gunichar c)
170 if (!Resize (len + 2))
171 return;
173 if (index < len) {
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));
176 text[index] = c;
177 len++;
178 } else {
179 text[len++] = c;
180 text[len] = 0;
184 void Insert (int index, const gunichar *str, int count)
186 if (!Resize (len + count + 1))
187 return;
189 if (index < len) {
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));
195 len += count;
196 } else {
197 // simply append @count chars of @str onto the end of our buffer
198 memcpy (UNICODE_OFFSET (text, len), str, UNICODE_LEN (count));
199 len += count;
200 text[len] = 0;
204 void Prepend (gunichar c)
206 if (!Resize (len + 2))
207 return;
209 // shift the entire buffer down by 1 char
210 memmove (UNICODE_OFFSET (text, 1), text, UNICODE_LEN (len + 1));
211 text[0] = c;
212 len++;
215 void Prepend (const gunichar *str, int count)
217 if (!Resize (len + count + 1))
218 return;
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));
225 len += count;
228 void Replace (int start, int length, const gunichar *str, int count)
230 char *dest, *src;
231 int beyond;
233 if (start > len)
234 return;
236 if (start + length > len)
237 length = len - start;
239 // Check for the easy cases first...
240 if (length == 0) {
241 Insert (start, str, count);
242 return;
243 } else if (count == 0) {
244 Cut (start, length);
245 return;
246 } else if (count == length) {
247 memcpy (UNICODE_OFFSET (text, start), str, UNICODE_LEN (count));
248 return;
251 if (count > length && !Resize (len + (count - length) + 1))
252 return;
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)
270 gunichar *substr;
271 size_t n_bytes;
273 if (start < 0 || start > len || length == 0)
274 return NULL;
276 if (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);
284 substr[length] = 0;
286 return substr;
292 // TextBoxUndoActions
295 enum TextBoxUndoActionType {
296 TextBoxUndoActionTypeInsert,
297 TextBoxUndoActionTypeDelete,
298 TextBoxUndoActionTypeReplace,
301 class TextBoxUndoAction : public List::Node {
302 public:
303 TextBoxUndoActionType type;
304 int selection_anchor;
305 int selection_cursor;
306 int length;
307 int start;
310 class TextBoxUndoActionInsert : public TextBoxUndoAction {
311 public:
312 TextBuffer *buffer;
313 bool growable;
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 {
324 public:
325 gunichar *text;
327 TextBoxUndoActionDelete (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length);
328 virtual ~TextBoxUndoActionDelete ();
331 class TextBoxUndoActionReplace : public TextBoxUndoAction {
332 public:
333 gunichar *inserted;
334 gunichar *deleted;
335 int inlen;
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 {
343 int max_count;
344 List *list;
346 public:
347 TextBoxUndoStack (int max_count);
348 ~TextBoxUndoStack ();
350 bool IsEmpty ();
351 void Clear ();
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;
363 this->start = start;
364 this->length = 1;
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;
377 this->start = start;
379 this->buffer = new TextBuffer (inserted, length);
380 this->growable = !atomic;
383 TextBoxUndoActionInsert::~TextBoxUndoActionInsert ()
385 delete buffer;
388 bool
389 TextBoxUndoActionInsert::Insert (int start, const gunichar *text, int len)
391 if (!growable || start != (this->start + length))
392 return false;
394 buffer->Append (text, len);
395 length += len;
397 return true;
400 bool
401 TextBoxUndoActionInsert::Insert (int start, gunichar c)
403 if (!growable || start != (this->start + length))
404 return false;
406 buffer->Append (c);
407 length++;
409 return true;
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;
418 this->start = start;
420 this->text = buffer->Substring (start, length);
423 TextBoxUndoActionDelete::~TextBoxUndoActionDelete ()
425 g_free (text);
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;
434 this->start = start;
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));
439 this->inlen = inlen;
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;
448 this->start = start;
450 this->deleted = buffer->Substring (start, length);
451 this->inserted = g_new (gunichar, 2);
452 memcpy (inserted, &c, sizeof (gunichar));
453 inserted[1] = 0;
454 this->inlen = 1;
457 TextBoxUndoActionReplace::~TextBoxUndoActionReplace ()
459 g_free (inserted);
460 g_free (deleted);
464 TextBoxUndoStack::TextBoxUndoStack (int max_count)
466 this->max_count = max_count;
467 this->list = new List ();
470 TextBoxUndoStack::~TextBoxUndoStack ()
472 delete list;
475 bool
476 TextBoxUndoStack::IsEmpty ()
478 return list->IsEmpty ();
481 void
482 TextBoxUndoStack::Clear ()
484 list->Clear (true);
487 void
488 TextBoxUndoStack::Push (TextBoxUndoAction *action)
490 if (list->Length () == max_count) {
491 List::Node *node = list->Last ();
492 list->Unlink (node);
493 delete node;
496 list->Prepend (action);
499 TextBoxUndoAction *
500 TextBoxUndoStack::Pop ()
502 List::Node *node = list->First ();
504 if (node)
505 list->Unlink (node);
507 return (TextBoxUndoAction *) node;
510 TextBoxUndoAction *
511 TextBoxUndoStack::Peek ()
513 return (TextBoxUndoAction *) list->First ();
518 // TextBoxBase
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')
532 static GdkWindow *
533 GetGdkWindow (TextBoxBase *textbox)
535 MoonWindow *window;
536 Surface *surface;
538 if (!(surface = textbox->GetSurface ()))
539 return NULL;
541 if (!(window = surface->GetWindow ()))
542 return NULL;
544 return window->GetGdkWindow ();
547 static GtkClipboard *
548 GetClipboard (TextBoxBase *textbox, GdkAtom atom)
550 GdkDisplay *display;
551 GdkWindow *window;
553 if (!(window = GetGdkWindow (textbox)))
554 return NULL;
556 if (!(display = gdk_drawable_get_display ((GdkDrawable *) window)))
557 return NULL;
559 return gtk_clipboard_get_for_display (display, atom);
562 void
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 ();
580 font_source = NULL;
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 ();
594 max_length = 0;
596 emit = NOTHING_CHANGED;
597 events_mask = 0;
599 selection_anchor = 0;
600 selection_cursor = 0;
601 cursor_offset = 0.0;
602 batch = 0;
604 accepts_return = false;
605 need_im_reset = false;
606 is_read_only = false;
607 have_offset = false;
608 multiline = false;
609 selecting = false;
610 setvalue = true;
611 captured = false;
612 focused = false;
613 secret = false;
614 view = NULL;
617 TextBoxBase::~TextBoxBase ()
619 RemoveHandler (UIElement::MouseLeftButtonMultiClickEvent, TextBoxBase::mouse_left_button_multi_click, this);
621 ResetIMContext ();
622 g_object_unref (im_ctx);
624 CleanupDownloaders ();
625 g_ptr_array_free (downloaders, true);
626 g_free (font_source);
628 delete buffer;
629 delete undo;
630 delete redo;
631 delete font;
634 void
635 TextBoxBase::SetSurface (Surface *surface)
637 Control::SetSurface (surface);
639 if (surface)
640 gtk_im_context_set_client_window (im_ctx, GetGdkWindow (this));
643 void
644 TextBoxBase::CleanupDownloaders ()
646 Downloader *downloader;
647 guint i;
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);
659 double
660 TextBoxBase::GetCursorOffset ()
662 if (!have_offset && view) {
663 cursor_offset = view->GetCursor ().x;
664 have_offset = true;
667 return cursor_offset;
671 TextBoxBase::CursorDown (int cursor, bool page)
673 double y = view->GetCursor ().y;
674 double x = GetCursorOffset ();
675 TextLayoutLine *line;
676 TextLayoutRun *run;
677 int index, cur, n;
678 guint i;
680 if (!(line = view->GetLineFromY (y, &index)))
681 return cursor;
683 if (page) {
684 // calculate the number of lines to skip over
685 n = GetActualHeight () / line->height;
686 } else {
687 n = 1;
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];
696 cur += run->count;
699 have_offset = false;
701 return cur;
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;
715 int index, n;
717 if (!(line = view->GetLineFromY (y, &index)))
718 return cursor;
720 if (page) {
721 // calculate the number of lines to skip over
722 n = GetActualHeight () / line->height;
723 } else {
724 n = 1;
727 if (index < n) {
728 // go to the beginning of the first line
729 have_offset = false;
730 return 0;
733 line = view->GetLineFromIndex (index - n);
735 return line->GetCursorFromX (Point (), x);
738 #ifdef EMULATE_GTK
739 enum CharClass {
740 CharClassUnknown,
741 CharClassWhitespace,
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;
756 #else
757 static bool
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]))
762 return false;
764 return g_unichar_isalnum (buffer->text[index]);
766 #endif
769 TextBoxBase::CursorNextWord (int cursor)
771 int i, lf, cr;
773 // find the end of the current line
774 cr = CursorLineEnd (cursor);
775 if (buffer->text[cr] == '\r' && buffer->text[cr + 1] == '\n')
776 lf = cr + 1;
777 else
778 lf = cr;
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)
783 return lf + 1;
785 return cursor;
788 #ifdef EMULATE_GTK
789 CharClass cc = char_class (buffer->text[cursor]);
790 i = cursor;
792 // skip over the word, punctuation, or run of whitespace
793 while (i < cr && char_class (buffer->text[i]) == cc)
794 i++;
796 // skip any whitespace after the word/punct
797 while (i < cr && g_unichar_isspace (buffer->text[i]))
798 i++;
799 #else
800 i = cursor;
802 // skip to the end of the current word
803 while (i < cr && !g_unichar_isspace (buffer->text[i]))
804 i++;
806 // skip any whitespace after the word
807 while (i < cr && g_unichar_isspace (buffer->text[i]))
808 i++;
810 // find the start of the next word
811 while (i < cr && !is_start_of_word (buffer, i))
812 i++;
813 #endif
815 return 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')
827 cr = lf - 1;
828 else
829 cr = lf;
831 // if the cursor is at the beginning of the line, return the end of the prev line
832 if (cursor - 1 == lf) {
833 if (cr > 0)
834 return cr;
836 return 0;
839 #ifdef EMULATE_GTK
840 CharClass cc = char_class (buffer->text[cursor - 1]);
841 begin = lf + 1;
842 i = cursor;
844 // skip over the word, punctuation, or run of whitespace
845 while (i > begin && char_class (buffer->text[i - 1]) == cc)
846 i--;
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)
852 i--;
854 #else
855 begin = lf + 1;
856 i = cursor;
858 if (cursor < buffer->len) {
859 // skip to the beginning of this word
860 while (i > begin && !g_unichar_isspace (buffer->text[i - 1]))
861 i--;
863 if (i < cursor && is_start_of_word (buffer, i))
864 return i;
867 // skip to the start of the lwsp
868 while (i > begin && g_unichar_isspace (buffer->text[i - 1]))
869 i--;
871 if (i > begin)
872 i--;
874 // skip to the beginning of the word
875 while (i > begin && !is_start_of_word (buffer, i))
876 i--;
877 #endif
879 return i;
883 TextBoxBase::CursorLineBegin (int cursor)
885 int cur = cursor;
887 // find the beginning of the line
888 while (cur > 0 && !IsEOL (buffer->text[cur - 1]))
889 cur--;
891 return cur;
895 TextBoxBase::CursorLineEnd (int cursor, bool include)
897 int cur = cursor;
899 // find the end of the line
900 while (cur < buffer->len && !IsEOL (buffer->text[cur]))
901 cur++;
903 if (include && cur < buffer->len) {
904 if (buffer->text[cur] == '\r' && buffer->text[cur + 1] == '\n')
905 cur += 2;
906 else
907 cur++;
910 return cur;
913 bool
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)
923 return false;
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') {
936 start = cursor - 2;
937 length = 2;
938 } else {
939 start = cursor - 1;
940 length = 1;
944 if (length > 0) {
945 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
946 undo->Push (action);
947 redo->Clear ();
949 buffer->Cut (start, length);
950 emit |= TEXT_CHANGED;
951 anchor = start;
952 cursor = start;
953 handled = true;
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;
963 handled = true;
966 return handled;
969 bool
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)
979 return false;
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;
988 start = 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')
992 length = 2;
993 else
994 length = 1;
996 start = cursor;
999 if (length > 0) {
1000 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1001 undo->Push (action);
1002 redo->Clear ();
1004 buffer->Cut (start, length);
1005 emit |= TEXT_CHANGED;
1006 handled = true;
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;
1016 handled = true;
1019 return handled;
1022 bool
1023 TextBoxBase::KeyPressPageDown (GdkModifierType modifiers)
1025 int anchor = selection_anchor;
1026 int cursor = selection_cursor;
1027 bool handled = false;
1028 bool have;
1030 if ((modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1031 return false;
1033 // move the cursor down one page from its current position
1034 cursor = CursorDown (cursor, true);
1035 have = have_offset;
1037 if ((modifiers & SHIFT_MASK) == 0) {
1038 // clobber the selection
1039 anchor = cursor;
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;
1049 have_offset = have;
1050 handled = true;
1053 return handled;
1056 bool
1057 TextBoxBase::KeyPressPageUp (GdkModifierType modifiers)
1059 int anchor = selection_anchor;
1060 int cursor = selection_cursor;
1061 bool handled = false;
1062 bool have;
1064 if ((modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1065 return false;
1067 // move the cursor up one page from its current position
1068 cursor = CursorUp (cursor, true);
1069 have = have_offset;
1071 if ((modifiers & SHIFT_MASK) == 0) {
1072 // clobber the selection
1073 anchor = cursor;
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;
1083 have_offset = have;
1084 handled = true;
1087 return handled;
1090 bool
1091 TextBoxBase::KeyPressDown (GdkModifierType modifiers)
1093 int anchor = selection_anchor;
1094 int cursor = selection_cursor;
1095 bool handled = false;
1096 bool have;
1098 if (!accepts_return || (modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1099 return false;
1101 // move the cursor down by one line from its current position
1102 cursor = CursorDown (cursor, false);
1103 have = have_offset;
1105 if ((modifiers & SHIFT_MASK) == 0) {
1106 // clobber the selection
1107 anchor = cursor;
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;
1117 have_offset = have;
1118 handled = true;
1121 return handled;
1124 bool
1125 TextBoxBase::KeyPressUp (GdkModifierType modifiers)
1127 int anchor = selection_anchor;
1128 int cursor = selection_cursor;
1129 bool handled = false;
1130 bool have;
1132 if (!accepts_return || (modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1133 return false;
1135 // move the cursor up by one line from its current position
1136 cursor = CursorUp (cursor, false);
1137 have = have_offset;
1139 if ((modifiers & SHIFT_MASK) == 0) {
1140 // clobber the selection
1141 anchor = cursor;
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;
1151 have_offset = have;
1152 handled = true;
1155 return handled;
1158 bool
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)
1166 return false;
1168 if ((modifiers & CONTROL_MASK) != 0) {
1169 // move the cursor to the beginning of the buffer
1170 cursor = 0;
1171 } else {
1172 // move the cursor to the beginning of the line
1173 cursor = CursorLineBegin (cursor);
1176 if ((modifiers & SHIFT_MASK) == 0) {
1177 // clobber the selection
1178 anchor = cursor;
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;
1189 handled = true;
1192 return handled;
1195 bool
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)
1203 return false;
1205 if ((modifiers & CONTROL_MASK) != 0) {
1206 // move the cursor to the end of the buffer
1207 cursor = buffer->len;
1208 } else {
1209 // move the cursor to the end of the line
1210 cursor = CursorLineEnd (cursor);
1213 if ((modifiers & SHIFT_MASK) == 0) {
1214 // clobber the selection
1215 anchor = cursor;
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;
1225 handled = true;
1228 return handled;
1231 bool
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)
1239 return false;
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);
1247 } else {
1248 // move the cursor forward one character
1249 if (buffer->text[cursor] == '\r' && buffer->text[cursor + 1] == '\n')
1250 cursor += 2;
1251 else if (cursor < buffer->len)
1252 cursor++;
1255 if ((modifiers & SHIFT_MASK) == 0) {
1256 // clobber the selection
1257 anchor = cursor;
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;
1267 handled = true;
1270 return handled;
1273 bool
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)
1281 return false;
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);
1289 } else {
1290 // move the cursor backward one character
1291 if (cursor >= 2 && buffer->text[cursor - 2] == '\r' && buffer->text[cursor - 1] == '\n')
1292 cursor -= 2;
1293 else if (cursor > 0)
1294 cursor--;
1297 if ((modifiers & SHIFT_MASK) == 0) {
1298 // clobber the selection
1299 anchor = cursor;
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;
1309 handled = true;
1312 return handled;
1315 bool
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))
1325 return false;
1327 if (length > 0) {
1328 // replace the currently selected text
1329 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, c);
1330 undo->Push (action);
1331 redo->Clear ();
1333 buffer->Replace (start, length, &c, 1);
1334 } else {
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))
1342 insert = NULL;
1345 if (!insert) {
1346 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, c);
1347 undo->Push (insert);
1350 redo->Clear ();
1352 buffer->Insert (start, c);
1355 emit |= TEXT_CHANGED;
1356 cursor = start + 1;
1357 anchor = cursor;
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;
1368 return true;
1371 void
1372 TextBoxBase::BatchPush ()
1374 batch++;
1377 void
1378 TextBoxBase::BatchPop ()
1380 if (batch == 0) {
1381 g_warning ("TextBoxBase batch underflow");
1382 return;
1385 batch--;
1388 void
1389 TextBoxBase::SyncAndEmit (bool sync_text)
1391 if (batch != 0 || emit == NOTHING_CHANGED)
1392 return;
1394 if (sync_text && (emit & TEXT_CHANGED))
1395 SyncText ();
1397 if (emit & SELECTION_CHANGED)
1398 SyncSelectedText ();
1400 if (IsLoaded ()) {
1401 // eliminate events that we can't emit
1402 emit &= events_mask;
1404 if (emit & TEXT_CHANGED)
1405 EmitTextChanged ();
1407 if (emit & SELECTION_CHANGED)
1408 EmitSelectionChanged ();
1411 emit = NOTHING_CHANGED;
1414 void
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;
1420 gunichar *text;
1421 glong len, i;
1423 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1424 return;
1426 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1427 // paste cannot exceed MaxLength
1428 len = max_length - (buffer->len - length);
1429 if (len > 0)
1430 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1431 else
1432 len = 0;
1433 text[len] = '\0';
1436 if (!multiline) {
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));
1441 text[i] = '\0';
1442 len = i;
1443 break;
1448 ResetIMContext ();
1450 if (length > 0) {
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);
1460 } else {
1461 g_free (text);
1462 return;
1465 undo->Push (action);
1466 redo->Clear ();
1467 g_free (text);
1469 emit |= TEXT_CHANGED;
1470 start += len;
1472 BatchPush ();
1473 SetSelectionStart (start);
1474 SetSelectionLength (0);
1475 BatchPop ();
1477 SyncAndEmit ();
1480 void
1481 TextBoxBase::paste (GtkClipboard *clipboard, const char *text, gpointer closure)
1483 ((TextBoxBase *) closure)->Paste (clipboard, text);
1486 void
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 ())
1495 return;
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;
1501 BatchPush ();
1503 switch (key) {
1504 case GDK_BackSpace:
1505 if (is_read_only)
1506 break;
1508 handled = KeyPressBackSpace (modifiers);
1509 break;
1510 case GDK_Delete:
1511 if (is_read_only)
1512 break;
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 ("");
1524 handled = true;
1525 } else {
1526 handled = KeyPressDelete (modifiers);
1528 break;
1529 case GDK_Insert:
1530 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == SHIFT_MASK) {
1531 // Shift+Insert => Paste
1532 if (is_read_only)
1533 break;
1535 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1536 // paste clipboard contents to the buffer
1537 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1540 handled = true;
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);
1550 handled = true;
1552 break;
1553 case GDK_KP_Page_Down:
1554 case GDK_Page_Down:
1555 handled = KeyPressPageDown (modifiers);
1556 break;
1557 case GDK_KP_Page_Up:
1558 case GDK_Page_Up:
1559 handled = KeyPressPageUp (modifiers);
1560 break;
1561 case GDK_KP_Home:
1562 case GDK_Home:
1563 handled = KeyPressHome (modifiers);
1564 break;
1565 case GDK_KP_End:
1566 case GDK_End:
1567 handled = KeyPressEnd (modifiers);
1568 break;
1569 case GDK_KP_Right:
1570 case GDK_Right:
1571 handled = KeyPressRight (modifiers);
1572 break;
1573 case GDK_KP_Left:
1574 case GDK_Left:
1575 handled = KeyPressLeft (modifiers);
1576 break;
1577 case GDK_KP_Down:
1578 case GDK_Down:
1579 handled = KeyPressDown (modifiers);
1580 break;
1581 case GDK_KP_Up:
1582 case GDK_Up:
1583 handled = KeyPressUp (modifiers);
1584 break;
1585 default:
1586 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == CONTROL_MASK) {
1587 switch (key) {
1588 case GDK_A:
1589 case GDK_a:
1590 // Ctrl+A => Select All
1591 handled = true;
1592 SelectAll ();
1593 break;
1594 case GDK_C:
1595 case GDK_c:
1596 // Ctrl+C => Copy
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);
1604 handled = true;
1605 break;
1606 case GDK_X:
1607 case GDK_x:
1608 // Ctrl+X => Cut
1609 if (is_read_only)
1610 break;
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 ("");
1620 handled = true;
1621 break;
1622 case GDK_V:
1623 case GDK_v:
1624 // Ctrl+V => Paste
1625 if (is_read_only)
1626 break;
1628 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1629 // paste clipboard contents to the buffer
1630 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1633 handled = true;
1634 break;
1635 case GDK_Y:
1636 case GDK_y:
1637 // Ctrl+Y => Redo
1638 if (!is_read_only) {
1639 handled = true;
1640 Redo ();
1642 break;
1643 case GDK_Z:
1644 case GDK_z:
1645 // Ctrl+Z => Undo
1646 if (!is_read_only) {
1647 handled = true;
1648 Undo ();
1650 break;
1651 default:
1652 // unhandled Control commands
1653 break;
1656 break;
1659 if (handled) {
1660 args->SetHandled (handled);
1661 ResetIMContext ();
1664 BatchPop ();
1666 SyncAndEmit ();
1669 void
1670 TextBoxBase::PostOnKeyDown (KeyEventArgs *args)
1672 guint key = args->GetKeyVal ();
1673 gunichar c;
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;
1680 return;
1683 if (is_read_only || args->IsModifier ())
1684 return;
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;
1690 BatchPush ();
1692 switch (key) {
1693 case GDK_Return:
1694 KeyPressUnichar ('\r');
1695 break;
1696 default:
1697 if ((args->GetModifiers () & (CONTROL_MASK | ALT_MASK)) == 0) {
1698 // normal character input
1699 if ((c = args->GetUnicode ()))
1700 KeyPressUnichar (c);
1702 break;
1705 BatchPop ();
1707 SyncAndEmit ();
1710 void
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;
1719 bool
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;
1727 int start, length;
1729 if (is_read_only)
1730 return true;
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);
1740 if (length > 0) {
1741 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1742 undo->Push (action);
1743 redo->Clear ();
1745 buffer->Cut (start, length);
1746 emit |= TEXT_CHANGED;
1747 anchor = start;
1748 cursor = start;
1751 BatchPush ();
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;
1762 BatchPop ();
1764 SyncAndEmit ();
1766 return true;
1769 gboolean
1770 TextBoxBase::delete_surrounding (GtkIMContext *context, int offset, int n_chars, gpointer user_data)
1772 return ((TextBoxBase *) user_data)->DeleteSurrounding (offset, n_chars);
1775 bool
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);
1783 return true;
1786 gboolean
1787 TextBoxBase::retrieve_surrounding (GtkIMContext *context, gpointer user_data)
1789 return ((TextBoxBase *) user_data)->RetrieveSurrounding ();
1792 void
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;
1798 int anchor, cursor;
1799 gunichar *text;
1800 glong len, i;
1802 if (is_read_only)
1803 return;
1805 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1806 return;
1808 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1809 // paste cannot exceed MaxLength
1810 len = max_length - (buffer->len - length);
1811 if (len > 0)
1812 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1813 else
1814 len = 0;
1815 text[len] = '\0';
1818 if (!multiline) {
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));
1823 text[i] = '\0';
1824 len = i;
1825 break;
1830 if (length > 0) {
1831 // replace the currently selected text
1832 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, len);
1833 undo->Push (action);
1834 redo->Clear ();
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))
1847 insert = NULL;
1850 if (!insert) {
1851 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, len);
1852 undo->Push (insert);
1855 redo->Clear ();
1856 } else {
1857 g_free (text);
1858 return;
1861 emit = TEXT_CHANGED;
1862 cursor = start + len;
1863 anchor = cursor;
1864 g_free (text);
1866 BatchPush ();
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;
1877 BatchPop ();
1879 SyncAndEmit ();
1882 void
1883 TextBoxBase::commit (GtkIMContext *context, const char *str, gpointer user_data)
1885 ((TextBoxBase *) user_data)->Commit (str);
1888 void
1889 TextBoxBase::ResetIMContext ()
1891 if (need_im_reset) {
1892 gtk_im_context_reset (im_ctx);
1893 need_im_reset = false;
1897 void
1898 TextBoxBase::OnMouseLeftButtonDown (MouseButtonEventArgs *args)
1900 double x, y;
1901 int cursor;
1903 args->SetHandled (true);
1904 Focus ();
1906 if (view) {
1907 args->GetPosition (view, &x, &y);
1909 cursor = view->GetCursorFromXY (x, y);
1911 ResetIMContext ();
1913 // Single-Click: cursor placement
1914 captured = CaptureMouse ();
1915 selecting = true;
1917 BatchPush ();
1918 emit = NOTHING_CHANGED;
1919 SetSelectionStart (cursor);
1920 SetSelectionLength (0);
1921 BatchPop ();
1923 SyncAndEmit ();
1927 void
1928 TextBoxBase::OnMouseLeftButtonMultiClick (MouseButtonEventArgs *args)
1930 int cursor, start, end;
1931 double x, y;
1933 args->SetHandled (true);
1935 if (view) {
1936 args->GetPosition (view, &x, &y);
1938 cursor = view->GetCursorFromXY (x, y);
1940 ResetIMContext ();
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
1948 if (captured)
1949 ReleaseMouseCapture ();
1950 start = CursorLineBegin (cursor);
1951 end = CursorLineEnd (cursor, true);
1952 selecting = false;
1953 captured = false;
1954 } else {
1955 // Double-Click: select the word
1956 if (captured)
1957 ReleaseMouseCapture ();
1958 start = CursorPrevWord (cursor);
1959 end = CursorNextWord (cursor);
1960 selecting = false;
1961 captured = false;
1964 BatchPush ();
1965 emit = NOTHING_CHANGED;
1966 SetSelectionStart (start);
1967 SetSelectionLength (end - start);
1968 BatchPop ();
1970 SyncAndEmit ();
1974 void
1975 TextBoxBase::mouse_left_button_multi_click (EventObject *sender, EventArgs *args, gpointer closure)
1977 ((TextBoxBase *) closure)->OnMouseLeftButtonMultiClick ((MouseButtonEventArgs *) args);
1980 void
1981 TextBoxBase::OnMouseLeftButtonUp (MouseButtonEventArgs *args)
1983 if (captured)
1984 ReleaseMouseCapture ();
1986 args->SetHandled (true);
1987 selecting = false;
1988 captured = false;
1991 void
1992 TextBoxBase::OnMouseMove (MouseEventArgs *args)
1994 int anchor = selection_anchor;
1995 int cursor = selection_cursor;
1996 double x, y;
1998 if (selecting) {
1999 args->GetPosition (view, &x, &y);
2000 args->SetHandled (true);
2002 cursor = view->GetCursorFromXY (x, y);
2004 BatchPush ();
2005 emit = NOTHING_CHANGED;
2006 SetSelectionStart (MIN (anchor, cursor));
2007 SetSelectionLength (abs (cursor - anchor));
2008 selection_anchor = anchor;
2009 selection_cursor = cursor;
2010 BatchPop ();
2012 SyncAndEmit ();
2016 void
2017 TextBoxBase::OnLostFocus (RoutedEventArgs *args)
2019 BatchPush ();
2020 emit = NOTHING_CHANGED;
2021 SetSelectionStart (selection_cursor);
2022 SetSelectionLength (0);
2023 BatchPop ();
2025 SyncAndEmit ();
2027 focused = false;
2029 if (view)
2030 view->OnLostFocus ();
2032 if (!is_read_only) {
2033 gtk_im_context_focus_out (im_ctx);
2034 need_im_reset = true;
2038 void
2039 TextBoxBase::OnGotFocus (RoutedEventArgs *args)
2041 focused = true;
2043 if (view)
2044 view->OnGotFocus ();
2046 if (!is_read_only) {
2047 gtk_im_context_focus_in (im_ctx);
2048 need_im_reset = true;
2052 void
2053 TextBoxBase::EmitCursorPositionChanged (double height, double x, double y)
2055 Emit (TextBoxBase::CursorPositionChangedEvent, new CursorPositionChangedEventArgs (height, x, y));
2058 void
2059 TextBoxBase::DownloaderComplete (Downloader *downloader)
2061 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2062 char *resource, *filename;
2063 InternalDownloader *idl;
2064 const char *path;
2065 Uri *uri;
2067 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2068 if (!(filename = downloader->GetDownloadedFilename (NULL)))
2069 return;
2071 g_free (filename);
2073 if (!(idl = downloader->GetInternalDownloader ()))
2074 return;
2076 if (!(idl->GetObjectType () == Type::FILEDOWNLOADER))
2077 return;
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
2083 // downloaded file.
2084 if (!(path = ((FileDownloader *) idl)->GetUnzippedPath ()))
2085 return;
2087 resource = uri->ToString ((UriToStringFlags) (UriHidePasswd | UriHideQuery | UriHideFragment));
2088 manager->AddResource (resource, path);
2089 g_free (resource);
2091 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont, NULL));
2094 void
2095 TextBoxBase::downloader_complete (EventObject *sender, EventArgs *calldata, gpointer closure)
2097 ((TextBoxBase *) closure)->DownloaderComplete ((Downloader *) sender);
2100 void
2101 TextBoxBase::AddFontSource (Downloader *downloader)
2103 downloader->AddHandler (downloader->CompletedEvent, downloader_complete, this);
2104 g_ptr_array_add (downloaders, downloader);
2105 downloader->ref ();
2107 if (downloader->Started () || downloader->Completed ()) {
2108 if (downloader->Completed ())
2109 DownloaderComplete (downloader);
2110 } else {
2111 // This is what actually triggers the download
2112 downloader->Send ();
2116 void
2117 TextBoxBase::AddFontResource (const char *resource)
2119 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2120 Application *application = Application::GetCurrent ();
2121 Downloader *downloader;
2122 Surface *surface;
2123 char *path;
2124 Uri *uri;
2126 uri = new Uri ();
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 ();
2135 delete uri;
2137 return;
2140 manager->AddResource (resource, path);
2141 g_free (path);
2142 delete uri;
2145 void
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;
2153 int i;
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...
2163 *fragment = '\0';
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
2190 if (view)
2191 view->SetMinHeight (args->GetNewValue ()->AsDouble ());
2192 } else if (args->GetId () == FrameworkElement::MaxHeightProperty) {
2193 // pass this along to our TextBoxView
2194 if (view)
2195 view->SetMaxHeight (args->GetNewValue ()->AsDouble ());
2196 } else if (args->GetId () == FrameworkElement::MinWidthProperty) {
2197 // pass this along to our TextBoxView
2198 if (view)
2199 view->SetMinWidth (args->GetNewValue ()->AsDouble ());
2200 } else if (args->GetId () == FrameworkElement::MaxWidthProperty) {
2201 // pass this along to our TextBoxView
2202 if (view)
2203 view->SetMaxWidth (args->GetNewValue ()->AsDouble ());
2204 } else if (args->GetId () == FrameworkElement::HeightProperty) {
2205 // pass this along to our TextBoxView
2206 if (view)
2207 view->SetHeight (args->GetNewValue ()->AsDouble ());
2208 } else if (args->GetId () == FrameworkElement::WidthProperty) {
2209 // pass this along to our TextBoxView
2210 if (view)
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);
2219 return;
2222 NotifyListenersOfPropertyChange (args, error);
2225 void
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));
2231 Invalidate ();
2234 if (prop->GetOwnerType () != Type::TEXTBOXBASE)
2235 Control::OnSubPropertyChanged (prop, obj, subobj_args);
2238 void
2239 TextBoxBase::OnApplyTemplate ()
2241 contentElement = GetTemplateChild ("ContentElement");
2243 if (contentElement == NULL) {
2244 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2245 Control::OnApplyTemplate ();
2246 return;
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);
2272 } else {
2273 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentElement of type %s",
2274 contentElement->GetType ()->GetName ());
2275 view->unref ();
2276 view = NULL;
2279 Control::OnApplyTemplate ();
2282 void
2283 TextBoxBase::ClearSelection (int start)
2285 BatchPush ();
2286 SetSelectionStart (start);
2287 SetSelectionLength (0);
2288 BatchPop ();
2291 bool
2292 TextBoxBase::SelectWithError (int start, int length, MoonError *error)
2294 if (start < 0) {
2295 MoonError::FillIn (error, MoonError::ARGUMENT, "selection start must be >= 0");
2296 return false;
2299 if (length < 0) {
2300 MoonError::FillIn (error, MoonError::ARGUMENT, "selection length must be >= 0");
2301 return false;
2304 if (start > buffer->len)
2305 start = buffer->len;
2307 if (length > (buffer->len - start))
2308 length = (buffer->len - start);
2310 BatchPush ();
2311 SetSelectionStart (start);
2312 SetSelectionLength (length);
2313 BatchPop ();
2315 ResetIMContext ();
2317 SyncAndEmit ();
2319 return true;
2322 void
2323 TextBoxBase::SelectAll ()
2325 SelectWithError (0, buffer->len, NULL);
2328 bool
2329 TextBoxBase::CanUndo ()
2331 return !undo->IsEmpty ();
2334 bool
2335 TextBoxBase::CanRedo ()
2337 return !redo->IsEmpty ();
2340 void
2341 TextBoxBase::Undo ()
2343 TextBoxUndoActionReplace *replace;
2344 TextBoxUndoActionInsert *insert;
2345 TextBoxUndoActionDelete *dele;
2346 TextBoxUndoAction *action;
2347 int anchor, cursor;
2349 if (undo->IsEmpty ())
2350 return;
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;
2362 break;
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;
2369 break;
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;
2377 break;
2380 BatchPush ();
2381 SetSelectionStart (MIN (anchor, cursor));
2382 SetSelectionLength (abs (cursor - anchor));
2383 emit = TEXT_CHANGED | SELECTION_CHANGED;
2384 selection_anchor = anchor;
2385 selection_cursor = cursor;
2386 BatchPop ();
2388 SyncAndEmit ();
2391 void
2392 TextBoxBase::Redo ()
2394 TextBoxUndoActionReplace *replace;
2395 TextBoxUndoActionInsert *insert;
2396 TextBoxUndoActionDelete *dele;
2397 TextBoxUndoAction *action;
2398 int anchor, cursor;
2400 if (redo->IsEmpty ())
2401 return;
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;
2412 break;
2413 case TextBoxUndoActionTypeDelete:
2414 dele = (TextBoxUndoActionDelete *) action;
2416 buffer->Cut (dele->start, dele->length);
2417 anchor = cursor = dele->start;
2418 break;
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;
2425 break;
2428 BatchPush ();
2429 SetSelectionStart (MIN (anchor, cursor));
2430 SetSelectionLength (abs (cursor - anchor));
2431 emit = TEXT_CHANGED | SELECTION_CHANGED;
2432 selection_anchor = anchor;
2433 selection_cursor = cursor;
2434 BatchPop ();
2436 SyncAndEmit ();
2441 // TextBoxDynamicPropertyValueProvider
2444 class TextBoxDynamicPropertyValueProvider : public PropertyValueProvider {
2445 Value *selection_background;
2446 Value *selection_foreground;
2448 public:
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;
2469 return NULL;
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"));
2484 // TextBox
2487 TextBox::TextBox ()
2489 providers[PropertyPrecedence_DynamicValue] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2491 Initialize (Type::TEXTBOX, "System.Windows.Controls.TextBox");
2492 events_mask = TEXT_CHANGED | SELECTION_CHANGED;
2493 multiline = true;
2496 void
2497 TextBox::EmitSelectionChanged ()
2499 EmitAsync (TextBox::SelectionChangedEvent, new RoutedEventArgs ());
2502 void
2503 TextBox::EmitTextChanged ()
2505 EmitAsync (TextBox::TextChangedEvent, new TextChangedEventArgs ());
2508 void
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);
2514 char *text;
2516 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2518 setvalue = false;
2519 SetValue (TextBox::SelectedTextProperty, Value (text, true));
2520 setvalue = true;
2521 } else {
2522 setvalue = false;
2523 SetValue (TextBox::SelectedTextProperty, Value (""));
2524 setvalue = true;
2528 void
2529 TextBox::SyncText ()
2531 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
2533 setvalue = false;
2534 SetValue (TextBox::TextProperty, Value (text, true));
2535 setvalue = true;
2538 void
2539 TextBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2541 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2542 DependencyProperty *prop;
2543 int start, length;
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
2551 // important?
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);
2564 else
2565 font_source = NULL;
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 ();
2573 if (focused) {
2574 if (is_read_only) {
2575 ResetIMContext ();
2576 gtk_im_context_focus_out (im_ctx);
2577 } else {
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) {
2585 if (setvalue) {
2586 Value *value = args->GetNewValue ();
2587 const char *str = value && value->AsString () ? value->AsString () : "";
2588 TextBoxUndoAction *action = NULL;
2589 gunichar *text;
2590 glong textlen;
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))) {
2596 if (length > 0) {
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);
2608 g_free (text);
2610 if (action != NULL) {
2611 emit |= TEXT_CHANGED;
2612 undo->Push (action);
2613 redo->Clear ();
2615 ClearSelection (start + textlen);
2616 ResetIMContext ();
2618 SyncAndEmit ();
2620 } else {
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);
2631 return;
2634 if (start + length > buffer->len) {
2635 // clamp the selection length to a valid value
2636 BatchPush ();
2637 length = buffer->len - start;
2638 SetSelectionLength (length);
2639 BatchPop ();
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;
2657 SyncAndEmit ();
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);
2666 return;
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;
2684 SyncAndEmit ();
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) {
2690 if (setvalue) {
2691 Value *value = args->GetNewValue ();
2692 const char *str = value && value->AsString () ? value->AsString () : "";
2693 TextBoxUndoAction *action;
2694 gunichar *text;
2695 glong textlen;
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);
2703 } else {
2704 // insert the text
2705 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
2707 buffer->Insert (0, text, textlen);
2710 undo->Push (action);
2711 redo->Clear ();
2712 g_free (text);
2714 emit |= TEXT_CHANGED;
2715 ClearSelection (0);
2716 ResetIMContext ();
2718 SyncAndEmit (value && !value->GetIsNull ());
2719 } else {
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));
2736 Invalidate ();
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));
2744 Invalidate ();
2747 if (changed != TextBoxModelChangedNothing)
2748 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2750 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
2751 TextBoxBase::OnPropertyChanged (args, error);
2752 return;
2755 NotifyListenersOfPropertyChange (args, error);
2758 void
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));
2764 Invalidate ();
2767 if (prop->GetOwnerType () != Type::TEXTBOX)
2768 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
2771 void
2772 TextBox::OnApplyTemplate ()
2774 DependencyProperty *prop;
2776 TextBoxBase::OnApplyTemplate ();
2778 if (!contentElement)
2779 return;
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));
2791 // PasswordBox
2795 // PasswordBoxDynamicPropertyValueProvider
2798 class PasswordBoxDynamicPropertyValueProvider : public PropertyValueProvider {
2799 Value *selection_background;
2800 Value *selection_foreground;
2802 public:
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;
2823 return NULL;
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"));
2838 // PasswordBox
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;
2847 secret = true;
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)
2866 return 0;
2870 PasswordBox::CursorLineBegin (int cursor)
2872 return 0;
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)
2890 return 0;
2893 void
2894 PasswordBox::EmitTextChanged ()
2896 EmitAsync (PasswordBox::PasswordChangedEvent, new RoutedEventArgs ());
2899 void
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);
2905 char *text;
2907 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2909 setvalue = false;
2910 SetValue (PasswordBox::SelectedTextProperty, Value (text, true));
2911 setvalue = true;
2912 } else {
2913 setvalue = false;
2914 SetValue (PasswordBox::SelectedTextProperty, Value (""));
2915 setvalue = true;
2919 void
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);
2930 void
2931 PasswordBox::SyncText ()
2933 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
2935 setvalue = false;
2936 SetValue (PasswordBox::PasswordProperty, Value (text, true));
2937 setvalue = true;
2939 SyncDisplayText ();
2942 const char *
2943 PasswordBox::GetDisplayText ()
2945 return display->str;
2948 void
2949 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2951 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2952 int length, start;
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
2957 // important?
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);
2970 else
2971 font_source = NULL;
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) {
2981 if (setvalue) {
2982 Value *value = args->GetNewValue ();
2983 const char *str = value && value->AsString () ? value->AsString () : "";
2984 TextBoxUndoAction *action;
2985 gunichar *text;
2986 glong textlen;
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);
2994 } else {
2995 // insert the text
2996 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
2998 buffer->Insert (0, text, textlen);
3001 undo->Push (action);
3002 redo->Clear ();
3003 g_free (text);
3005 emit |= TEXT_CHANGED;
3006 SyncDisplayText ();
3007 ClearSelection (0);
3008 ResetIMContext ();
3010 SyncAndEmit ();
3014 changed = TextBoxModelChangedText;
3015 } else if (args->GetId () == PasswordBox::SelectedTextProperty) {
3016 if (setvalue) {
3017 Value *value = args->GetNewValue ();
3018 const char *str = value && value->AsString () ? value->AsString () : "";
3019 TextBoxUndoAction *action = NULL;
3020 gunichar *text;
3021 glong textlen;
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))) {
3027 if (length > 0) {
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);
3039 g_free (text);
3041 if (action != NULL) {
3042 undo->Push (action);
3043 redo->Clear ();
3045 ClearSelection (start + textlen);
3046 emit |= TEXT_CHANGED;
3047 SyncDisplayText ();
3048 ResetIMContext ();
3050 SyncAndEmit ();
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);
3061 return;
3064 if (start + length > buffer->len) {
3065 // clamp the selection length to a valid value
3066 BatchPush ();
3067 length = buffer->len - start;
3068 SetSelectionLength (length);
3069 BatchPop ();
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;
3087 SyncAndEmit ();
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);
3096 return;
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;
3114 SyncAndEmit ();
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);
3126 return;
3129 NotifyListenersOfPropertyChange (args, error);
3132 void
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));
3138 Invalidate ();
3141 if (prop->GetOwnerType () != Type::TEXTBOX)
3142 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
3147 // TextBoxView
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;
3170 blink_timeout = 0;
3171 textbox = NULL;
3172 dirty = false;
3175 TextBoxView::~TextBoxView ()
3177 RemoveHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3178 RemoveHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3180 if (textbox) {
3181 textbox->RemoveHandler (TextBox::ModelChangedEvent, TextBoxView::model_changed, this);
3182 textbox->view = NULL;
3185 DisconnectBlinkTimeout ();
3187 delete layout;
3190 TextLayoutLine *
3191 TextBoxView::GetLineFromY (double y, int *index)
3193 return layout->GetLineFromY (Point (), y, index);
3196 TextLayoutLine *
3197 TextBoxView::GetLineFromIndex (int index)
3199 return layout->GetLineFromIndex (index);
3203 TextBoxView::GetCursorFromXY (double x, double y)
3205 return layout->GetCursorFromXY (Point (), x, y);
3208 gboolean
3209 TextBoxView::blink (void *user_data)
3211 return ((TextBoxView *) user_data)->Blink ();
3214 static guint
3215 GetCursorBlinkTimeout (TextBoxView *view)
3217 GtkSettings *settings;
3218 MoonWindow *window;
3219 GdkScreen *screen;
3220 GdkWindow *widget;
3221 Surface *surface;
3222 guint timeout;
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);
3241 return timeout;
3244 void
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 ()))
3252 return;
3254 blink_timeout = manager->AddTimeout (MOON_PRIORITY_DEFAULT, timeout, TextBoxView::blink, this);
3257 void
3258 TextBoxView::DisconnectBlinkTimeout ()
3260 TimeManager *manager;
3261 Surface *surface;
3263 if (blink_timeout != 0) {
3264 if (!(surface = GetSurface ()) || !(manager = surface->GetTimeManager ()))
3265 return;
3267 manager->RemoveTimeout (blink_timeout);
3268 blink_timeout = 0;
3272 bool
3273 TextBoxView::Blink ()
3275 guint multiplier;
3277 SetCurrentDeployment (true);
3279 if (cursor_visible) {
3280 multiplier = CURSOR_BLINK_OFF_MULTIPLIER;
3281 HideCursor ();
3282 } else {
3283 multiplier = CURSOR_BLINK_ON_MULTIPLIER;
3284 ShowCursor ();
3287 ConnectBlinkTimeout (multiplier);
3289 return false;
3292 void
3293 TextBoxView::DelayCursorBlink ()
3295 DisconnectBlinkTimeout ();
3296 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER);
3297 UpdateCursor (true);
3298 ShowCursor ();
3301 void
3302 TextBoxView::BeginCursorBlink ()
3304 if (blink_timeout == 0) {
3305 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER);
3306 UpdateCursor (true);
3307 ShowCursor ();
3311 void
3312 TextBoxView::EndCursorBlink ()
3314 DisconnectBlinkTimeout ();
3316 if (cursor_visible)
3317 HideCursor ();
3320 void
3321 TextBoxView::ResetCursorBlink (bool delay)
3323 if (textbox->IsFocused () && !textbox->HasSelectedText ()) {
3324 // cursor is blinkable... proceed with blinkage
3325 if (delay)
3326 DelayCursorBlink ();
3327 else
3328 BeginCursorBlink ();
3329 } else {
3330 // cursor not blinkable... stop all blinkage
3331 EndCursorBlink ();
3335 void
3336 TextBoxView::InvalidateCursor ()
3338 Invalidate (cursor.Transform (&absolute_xform));
3341 void
3342 TextBoxView::ShowCursor ()
3344 cursor_visible = true;
3345 InvalidateCursor ();
3348 void
3349 TextBoxView::HideCursor ()
3351 cursor_visible = false;
3352 InvalidateCursor ();
3355 void
3356 TextBoxView::UpdateCursor (bool invalidate)
3358 int cur = textbox->GetCursor ();
3359 GdkRectangle area;
3360 Rect rect;
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 ();
3382 void
3383 TextBoxView::UpdateText ()
3385 const char *text = textbox->GetDisplayText ();
3387 layout->SetText (text ? text : "", -1);
3390 void
3391 TextBoxView::GetSizeForBrush (cairo_t *cr, double *width, double *height)
3393 *height = GetActualHeight ();
3394 *width = GetActualWidth ();
3397 Size
3398 TextBoxView::ComputeActualSize ()
3400 Layout (Size (INFINITY, INFINITY));
3402 Size actual (0,0);
3403 layout->GetActualExtents (&actual.width, &actual.height);
3405 return actual;
3408 Size
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);
3423 Size
3424 TextBoxView::ArrangeOverride (Size finalSize)
3426 Size arranged = Size ();
3428 Layout (finalSize);
3430 layout->GetActualExtents (&arranged.width, &arranged.height);
3432 arranged = arranged.Max (finalSize);
3434 return arranged;
3437 void
3438 TextBoxView::Layout (Size constraint)
3440 layout->SetMaxWidth (constraint.width);
3442 layout->Layout ();
3443 dirty = false;
3446 void
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);
3469 // draw the cursor
3470 cairo_set_line_width (cr, 1.0);
3471 cairo_move_to (cr, x, y);
3472 cairo_line_to (cr, x, y + h);
3474 // stroke the caret
3475 caret->Stroke (cr);
3477 // restore antialiasing
3478 cairo_set_antialias (cr, alias);
3482 void
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;
3497 cairo_save (cr);
3498 cairo_set_matrix (cr, &absolute_xform);
3500 if (!path_only)
3501 RenderLayoutClip (cr);
3503 layout->SetAvailableWidth (renderSize.width);
3504 Paint (cr);
3505 cairo_restore (cr);
3508 void
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 ()))
3515 dirty = true;
3516 break;
3517 case TextBoxModelChangedTextWrapping:
3518 // text wrapping changed, update our layout
3519 if (layout->SetTextWrapping ((TextWrapping) args->property->GetNewValue()->AsInt32 ()))
3520 dirty = true;
3521 break;
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);
3528 } else {
3529 // cursor position changed
3530 ResetCursorBlink (true);
3531 return;
3533 break;
3534 case TextBoxModelChangedBrush:
3535 // a brush has changed, no layout updates needed, we just need to re-render
3536 break;
3537 case TextBoxModelChangedFont:
3538 // font changed, need to recalculate layout/bounds
3539 layout->ResetState ();
3540 dirty = true;
3541 break;
3542 case TextBoxModelChangedText:
3543 // the text has changed, need to recalculate layout/bounds
3544 UpdateText ();
3545 dirty = true;
3546 break;
3547 default:
3548 // nothing changed??
3549 return;
3552 if (dirty) {
3553 InvalidateMeasure ();
3554 UpdateBounds (true);
3557 Invalidate ();
3560 void
3561 TextBoxView::model_changed (EventObject *sender, EventArgs *args, gpointer closure)
3563 ((TextBoxView *) closure)->OnModelChanged ((TextBoxModelChangedEventArgs *) args);
3566 void
3567 TextBoxView::OnLostFocus ()
3569 EndCursorBlink ();
3572 void
3573 TextBoxView::OnGotFocus ()
3575 ResetCursorBlink (false);
3578 void
3579 TextBoxView::OnMouseLeftButtonDown (MouseButtonEventArgs *args)
3581 // proxy to our parent TextBox control
3582 textbox->OnMouseLeftButtonDown (args);
3585 void
3586 TextBoxView::mouse_left_button_down (EventObject *sender, EventArgs *args, gpointer closure)
3588 ((TextBoxView *) closure)->OnMouseLeftButtonDown ((MouseButtonEventArgs *) args);
3591 void
3592 TextBoxView::OnMouseLeftButtonUp (MouseButtonEventArgs *args)
3594 // proxy to our parent TextBox control
3595 textbox->OnMouseLeftButtonUp (args);
3598 void
3599 TextBoxView::mouse_left_button_up (EventObject *sender, EventArgs *args, gpointer closure)
3601 ((TextBoxView *) closure)->OnMouseLeftButtonUp ((MouseButtonEventArgs *) args);
3604 void
3605 TextBoxView::SetTextBox (TextBoxBase *textbox)
3607 TextLayoutAttributes *attrs;
3609 if (this->textbox == textbox)
3610 return;
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;
3619 if (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;
3631 UpdateText ();
3632 } else {
3633 layout->SetTextAttributes (NULL);
3634 layout->SetText (NULL, -1);
3637 UpdateBounds (true);
3638 InvalidateMeasure ();
3639 Invalidate ();
3640 dirty = true;