2009-10-09 Chris Toshok <toshok@ximian.com>
[moon.git] / src / textbox.cpp
blobc3e1740798bdfbfe424fd2cbf08d9fb6b4238b8e
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;
1367 return true;
1370 void
1371 TextBoxBase::BatchPush ()
1373 batch++;
1376 void
1377 TextBoxBase::BatchPop ()
1379 if (batch == 0) {
1380 g_warning ("TextBoxBase batch underflow");
1381 return;
1384 batch--;
1387 void
1388 TextBoxBase::SyncAndEmit (bool sync_text)
1390 if (batch != 0 || emit == NOTHING_CHANGED)
1391 return;
1393 if (sync_text && (emit & TEXT_CHANGED))
1394 SyncText ();
1396 if (emit & SELECTION_CHANGED)
1397 SyncSelectedText ();
1399 if (IsLoaded ()) {
1400 // eliminate events that we can't emit
1401 emit &= events_mask;
1403 if (emit & TEXT_CHANGED)
1404 EmitTextChanged ();
1406 if (emit & SELECTION_CHANGED)
1407 EmitSelectionChanged ();
1410 emit = NOTHING_CHANGED;
1413 void
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;
1419 gunichar *text;
1420 glong len, i;
1422 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1423 return;
1425 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1426 // paste cannot exceed MaxLength
1427 len = max_length - (buffer->len - length);
1428 if (len > 0)
1429 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1430 else
1431 len = 0;
1432 text[len] = '\0';
1435 if (!multiline) {
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));
1440 text[i] = '\0';
1441 len = i;
1442 break;
1447 ResetIMContext ();
1449 if (length > 0) {
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);
1459 } else {
1460 g_free (text);
1461 return;
1464 undo->Push (action);
1465 redo->Clear ();
1466 g_free (text);
1468 emit |= TEXT_CHANGED;
1469 start += len;
1471 BatchPush ();
1472 SetSelectionStart (start);
1473 SetSelectionLength (0);
1474 BatchPop ();
1476 SyncAndEmit ();
1479 void
1480 TextBoxBase::paste (GtkClipboard *clipboard, const char *text, gpointer closure)
1482 ((TextBoxBase *) closure)->Paste (clipboard, text);
1485 void
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 ())
1494 return;
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;
1500 BatchPush ();
1502 switch (key) {
1503 case GDK_BackSpace:
1504 if (is_read_only)
1505 break;
1507 handled = KeyPressBackSpace (modifiers);
1508 break;
1509 case GDK_Delete:
1510 if (is_read_only)
1511 break;
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 ("");
1523 handled = true;
1524 } else {
1525 handled = KeyPressDelete (modifiers);
1527 break;
1528 case GDK_Insert:
1529 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == SHIFT_MASK) {
1530 // Shift+Insert => Paste
1531 if (is_read_only)
1532 break;
1534 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1535 // paste clipboard contents to the buffer
1536 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1539 handled = true;
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);
1549 handled = true;
1551 break;
1552 case GDK_KP_Page_Down:
1553 case GDK_Page_Down:
1554 handled = KeyPressPageDown (modifiers);
1555 break;
1556 case GDK_KP_Page_Up:
1557 case GDK_Page_Up:
1558 handled = KeyPressPageUp (modifiers);
1559 break;
1560 case GDK_KP_Home:
1561 case GDK_Home:
1562 handled = KeyPressHome (modifiers);
1563 break;
1564 case GDK_KP_End:
1565 case GDK_End:
1566 handled = KeyPressEnd (modifiers);
1567 break;
1568 case GDK_KP_Right:
1569 case GDK_Right:
1570 handled = KeyPressRight (modifiers);
1571 break;
1572 case GDK_KP_Left:
1573 case GDK_Left:
1574 handled = KeyPressLeft (modifiers);
1575 break;
1576 case GDK_KP_Down:
1577 case GDK_Down:
1578 handled = KeyPressDown (modifiers);
1579 break;
1580 case GDK_KP_Up:
1581 case GDK_Up:
1582 handled = KeyPressUp (modifiers);
1583 break;
1584 default:
1585 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == CONTROL_MASK) {
1586 switch (key) {
1587 case GDK_A:
1588 case GDK_a:
1589 // Ctrl+A => Select All
1590 handled = true;
1591 SelectAll ();
1592 break;
1593 case GDK_C:
1594 case GDK_c:
1595 // Ctrl+C => Copy
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);
1603 handled = true;
1604 break;
1605 case GDK_X:
1606 case GDK_x:
1607 // Ctrl+X => Cut
1608 if (is_read_only)
1609 break;
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 ("");
1619 handled = true;
1620 break;
1621 case GDK_V:
1622 case GDK_v:
1623 // Ctrl+V => Paste
1624 if (is_read_only)
1625 break;
1627 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1628 // paste clipboard contents to the buffer
1629 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1632 handled = true;
1633 break;
1634 case GDK_Y:
1635 case GDK_y:
1636 // Ctrl+Y => Redo
1637 if (!is_read_only) {
1638 handled = true;
1639 Redo ();
1641 break;
1642 case GDK_Z:
1643 case GDK_z:
1644 // Ctrl+Z => Undo
1645 if (!is_read_only) {
1646 handled = true;
1647 Undo ();
1649 break;
1650 default:
1651 // unhandled Control commands
1652 break;
1655 break;
1658 if (handled) {
1659 args->SetHandled (handled);
1660 ResetIMContext ();
1663 BatchPop ();
1665 SyncAndEmit ();
1668 void
1669 TextBoxBase::PostOnKeyDown (KeyEventArgs *args)
1671 guint key = args->GetKeyVal ();
1672 gunichar c;
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;
1679 return;
1682 if (is_read_only || args->IsModifier ())
1683 return;
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;
1689 BatchPush ();
1691 switch (key) {
1692 case GDK_Return:
1693 KeyPressUnichar ('\r');
1694 break;
1695 default:
1696 if ((args->GetModifiers () & (CONTROL_MASK | ALT_MASK)) == 0) {
1697 // normal character input
1698 if ((c = args->GetUnicode ()))
1699 KeyPressUnichar (c);
1701 break;
1704 BatchPop ();
1706 SyncAndEmit ();
1709 void
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;
1718 bool
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;
1726 int start, length;
1728 if (is_read_only)
1729 return true;
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);
1739 if (length > 0) {
1740 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1741 undo->Push (action);
1742 redo->Clear ();
1744 buffer->Cut (start, length);
1745 emit |= TEXT_CHANGED;
1746 anchor = start;
1747 cursor = start;
1750 BatchPush ();
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;
1761 BatchPop ();
1763 SyncAndEmit ();
1765 return true;
1768 gboolean
1769 TextBoxBase::delete_surrounding (GtkIMContext *context, int offset, int n_chars, gpointer user_data)
1771 return ((TextBoxBase *) user_data)->DeleteSurrounding (offset, n_chars);
1774 bool
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);
1782 return true;
1785 gboolean
1786 TextBoxBase::retrieve_surrounding (GtkIMContext *context, gpointer user_data)
1788 return ((TextBoxBase *) user_data)->RetrieveSurrounding ();
1791 void
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;
1797 int anchor, cursor;
1798 gunichar *text;
1799 glong len, i;
1801 if (is_read_only)
1802 return;
1804 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1805 return;
1807 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1808 // paste cannot exceed MaxLength
1809 len = max_length - (buffer->len - length);
1810 if (len > 0)
1811 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1812 else
1813 len = 0;
1814 text[len] = '\0';
1817 if (!multiline) {
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));
1822 text[i] = '\0';
1823 len = i;
1824 break;
1829 if (length > 0) {
1830 // replace the currently selected text
1831 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, len);
1832 undo->Push (action);
1833 redo->Clear ();
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))
1846 insert = NULL;
1849 if (!insert) {
1850 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, len);
1851 undo->Push (insert);
1854 redo->Clear ();
1855 } else {
1856 g_free (text);
1857 return;
1860 emit = TEXT_CHANGED;
1861 cursor = start + len;
1862 anchor = cursor;
1863 g_free (text);
1865 BatchPush ();
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;
1876 BatchPop ();
1878 SyncAndEmit ();
1881 void
1882 TextBoxBase::commit (GtkIMContext *context, const char *str, gpointer user_data)
1884 ((TextBoxBase *) user_data)->Commit (str);
1887 void
1888 TextBoxBase::ResetIMContext ()
1890 if (need_im_reset) {
1891 gtk_im_context_reset (im_ctx);
1892 need_im_reset = false;
1896 void
1897 TextBoxBase::OnMouseLeftButtonDown (MouseButtonEventArgs *args)
1899 double x, y;
1900 int cursor;
1902 args->SetHandled (true);
1903 Focus ();
1905 if (view) {
1906 args->GetPosition (view, &x, &y);
1908 cursor = view->GetCursorFromXY (x, y);
1910 ResetIMContext ();
1912 // Single-Click: cursor placement
1913 captured = CaptureMouse ();
1914 selecting = true;
1916 BatchPush ();
1917 emit = NOTHING_CHANGED;
1918 SetSelectionStart (cursor);
1919 SetSelectionLength (0);
1920 BatchPop ();
1922 SyncAndEmit ();
1926 void
1927 TextBoxBase::OnMouseLeftButtonMultiClick (MouseButtonEventArgs *args)
1929 int cursor, start, end;
1930 double x, y;
1932 args->SetHandled (true);
1934 if (view) {
1935 args->GetPosition (view, &x, &y);
1937 cursor = view->GetCursorFromXY (x, y);
1939 ResetIMContext ();
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
1947 if (captured)
1948 ReleaseMouseCapture ();
1949 start = CursorLineBegin (cursor);
1950 end = CursorLineEnd (cursor, true);
1951 selecting = false;
1952 captured = false;
1953 } else {
1954 // Double-Click: select the word
1955 if (captured)
1956 ReleaseMouseCapture ();
1957 start = CursorPrevWord (cursor);
1958 end = CursorNextWord (cursor);
1959 selecting = false;
1960 captured = false;
1963 BatchPush ();
1964 emit = NOTHING_CHANGED;
1965 SetSelectionStart (start);
1966 SetSelectionLength (end - start);
1967 BatchPop ();
1969 SyncAndEmit ();
1973 void
1974 TextBoxBase::mouse_left_button_multi_click (EventObject *sender, EventArgs *args, gpointer closure)
1976 ((TextBoxBase *) closure)->OnMouseLeftButtonMultiClick ((MouseButtonEventArgs *) args);
1979 void
1980 TextBoxBase::OnMouseLeftButtonUp (MouseButtonEventArgs *args)
1982 if (captured)
1983 ReleaseMouseCapture ();
1985 args->SetHandled (true);
1986 selecting = false;
1987 captured = false;
1990 void
1991 TextBoxBase::OnMouseMove (MouseEventArgs *args)
1993 int anchor = selection_anchor;
1994 int cursor = selection_cursor;
1995 double x, y;
1997 if (selecting) {
1998 args->GetPosition (view, &x, &y);
1999 args->SetHandled (true);
2001 cursor = view->GetCursorFromXY (x, y);
2003 BatchPush ();
2004 emit = NOTHING_CHANGED;
2005 SetSelectionStart (MIN (anchor, cursor));
2006 SetSelectionLength (abs (cursor - anchor));
2007 selection_anchor = anchor;
2008 selection_cursor = cursor;
2009 BatchPop ();
2011 SyncAndEmit ();
2015 void
2016 TextBoxBase::OnLostFocus (RoutedEventArgs *args)
2018 BatchPush ();
2019 emit = NOTHING_CHANGED;
2020 SetSelectionStart (selection_cursor);
2021 SetSelectionLength (0);
2022 BatchPop ();
2024 SyncAndEmit ();
2026 focused = false;
2028 if (view)
2029 view->OnLostFocus ();
2031 if (!is_read_only) {
2032 gtk_im_context_focus_out (im_ctx);
2033 need_im_reset = true;
2037 void
2038 TextBoxBase::OnGotFocus (RoutedEventArgs *args)
2040 focused = true;
2042 if (view)
2043 view->OnGotFocus ();
2045 if (!is_read_only) {
2046 gtk_im_context_focus_in (im_ctx);
2047 need_im_reset = true;
2051 void
2052 TextBoxBase::EmitCursorPositionChanged (double height, double x, double y)
2054 Emit (TextBoxBase::CursorPositionChangedEvent, new CursorPositionChangedEventArgs (height, x, y));
2057 void
2058 TextBoxBase::DownloaderComplete (Downloader *downloader)
2060 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2061 char *resource, *filename;
2062 InternalDownloader *idl;
2063 const char *path;
2064 Uri *uri;
2066 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2067 if (!(filename = downloader->GetDownloadedFilename (NULL)))
2068 return;
2070 g_free (filename);
2072 if (!(idl = downloader->GetInternalDownloader ()))
2073 return;
2075 if (!(idl->GetObjectType () == Type::FILEDOWNLOADER))
2076 return;
2078 uri = downloader->GetUri ();
2080 // If the downloaded file was a zip file, this'll get the path to the
2081 // extracted zip directory, else it will simply be the path to the
2082 // downloaded file.
2083 if (!(path = ((FileDownloader *) idl)->GetUnzippedPath ()))
2084 return;
2086 resource = uri->ToString ((UriToStringFlags) (UriHidePasswd | UriHideQuery | UriHideFragment));
2087 manager->AddResource (resource, path);
2088 g_free (resource);
2090 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont, NULL));
2093 void
2094 TextBoxBase::downloader_complete (EventObject *sender, EventArgs *calldata, gpointer closure)
2096 ((TextBoxBase *) closure)->DownloaderComplete ((Downloader *) sender);
2099 void
2100 TextBoxBase::AddFontSource (Downloader *downloader)
2102 downloader->AddHandler (downloader->CompletedEvent, downloader_complete, this);
2103 g_ptr_array_add (downloaders, downloader);
2104 downloader->ref ();
2106 if (downloader->Started () || downloader->Completed ()) {
2107 if (downloader->Completed ())
2108 DownloaderComplete (downloader);
2109 } else {
2110 // This is what actually triggers the download
2111 downloader->Send ();
2115 void
2116 TextBoxBase::AddFontResource (const char *resource)
2118 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2119 Application *application = Application::GetCurrent ();
2120 Downloader *downloader;
2121 Surface *surface;
2122 char *path;
2123 Uri *uri;
2125 uri = new Uri ();
2127 if (!application || !uri->Parse (resource) || !(path = application->GetResourceAsPath (GetResourceBase(), uri))) {
2128 if ((surface = GetSurface ()) && (downloader = surface->CreateDownloader ())) {
2129 downloader->Open ("GET", resource, FontPolicy);
2130 AddFontSource (downloader);
2131 downloader->unref ();
2134 delete uri;
2136 return;
2139 manager->AddResource (resource, path);
2140 g_free (path);
2141 delete uri;
2144 void
2145 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2147 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2149 if (args->GetId () == Control::FontFamilyProperty) {
2150 FontFamily *family = args->GetNewValue () ? args->GetNewValue ()->AsFontFamily () : NULL;
2151 char **families, *fragment;
2152 int i;
2154 CleanupDownloaders ();
2156 if (family && family->source) {
2157 families = g_strsplit (family->source, ",", -1);
2158 for (i = 0; families[i]; i++) {
2159 g_strstrip (families[i]);
2160 if ((fragment = strchr (families[i], '#'))) {
2161 // the first portion of this string is the resource name...
2162 *fragment = '\0';
2163 AddFontResource (families[i]);
2166 g_strfreev (families);
2169 font->SetFamily (family ? family->source : NULL);
2170 changed = TextBoxModelChangedFont;
2171 } else if (args->GetId () == Control::FontSizeProperty) {
2172 double size = args->GetNewValue()->AsDouble ();
2173 changed = TextBoxModelChangedFont;
2174 font->SetSize (size);
2175 } else if (args->GetId () == Control::FontStretchProperty) {
2176 FontStretches stretch = args->GetNewValue()->AsFontStretch()->stretch;
2177 changed = TextBoxModelChangedFont;
2178 font->SetStretch (stretch);
2179 } else if (args->GetId () == Control::FontStyleProperty) {
2180 FontStyles style = args->GetNewValue()->AsFontStyle ()->style;
2181 changed = TextBoxModelChangedFont;
2182 font->SetStyle (style);
2183 } else if (args->GetId () == Control::FontWeightProperty) {
2184 FontWeights weight = args->GetNewValue()->AsFontWeight ()->weight;
2185 changed = TextBoxModelChangedFont;
2186 font->SetWeight (weight);
2187 } else if (args->GetId () == FrameworkElement::MinHeightProperty) {
2188 // pass this along to our TextBoxView
2189 if (view)
2190 view->SetMinHeight (args->GetNewValue ()->AsDouble ());
2191 } else if (args->GetId () == FrameworkElement::MaxHeightProperty) {
2192 // pass this along to our TextBoxView
2193 if (view)
2194 view->SetMaxHeight (args->GetNewValue ()->AsDouble ());
2195 } else if (args->GetId () == FrameworkElement::MinWidthProperty) {
2196 // pass this along to our TextBoxView
2197 if (view)
2198 view->SetMinWidth (args->GetNewValue ()->AsDouble ());
2199 } else if (args->GetId () == FrameworkElement::MaxWidthProperty) {
2200 // pass this along to our TextBoxView
2201 if (view)
2202 view->SetMaxWidth (args->GetNewValue ()->AsDouble ());
2203 } else if (args->GetId () == FrameworkElement::HeightProperty) {
2204 // pass this along to our TextBoxView
2205 if (view)
2206 view->SetHeight (args->GetNewValue ()->AsDouble ());
2207 } else if (args->GetId () == FrameworkElement::WidthProperty) {
2208 // pass this along to our TextBoxView
2209 if (view)
2210 view->SetWidth (args->GetNewValue ()->AsDouble ());
2213 if (changed != TextBoxModelChangedNothing)
2214 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2216 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE) {
2217 Control::OnPropertyChanged (args, error);
2218 return;
2221 NotifyListenersOfPropertyChange (args, error);
2224 void
2225 TextBoxBase::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2227 if (prop && (prop->GetId () == Control::BackgroundProperty ||
2228 prop->GetId () == Control::ForegroundProperty)) {
2229 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
2230 Invalidate ();
2233 if (prop->GetOwnerType () != Type::TEXTBOXBASE)
2234 Control::OnSubPropertyChanged (prop, obj, subobj_args);
2237 void
2238 TextBoxBase::OnApplyTemplate ()
2240 contentElement = GetTemplateChild ("ContentElement");
2242 if (contentElement == NULL) {
2243 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2244 Control::OnApplyTemplate ();
2245 return;
2248 view = new TextBoxView ();
2249 view->SetTextBox (this);
2251 view->SetMinHeight (GetMinHeight ());
2252 view->SetMaxHeight (GetMaxHeight ());
2253 view->SetMinWidth (GetMinWidth ());
2254 view->SetMaxWidth (GetMaxWidth ());
2255 view->SetHeight (GetHeight ());
2256 view->SetWidth (GetWidth ());
2258 // Insert our TextBoxView
2259 if (contentElement->Is (Type::CONTENTCONTROL)) {
2260 ContentControl *control = (ContentControl *) contentElement;
2262 control->SetValue (ContentControl::ContentProperty, Value (view));
2263 } else if (contentElement->Is (Type::BORDER)) {
2264 Border *border = (Border *) contentElement;
2266 border->SetValue (Border::ChildProperty, Value (view));
2267 } else if (contentElement->Is (Type::PANEL)) {
2268 DependencyObjectCollection *children = ((Panel *) contentElement)->GetChildren ();
2270 children->Add (view);
2271 } else {
2272 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentELement of type %s",
2273 contentElement->GetType ()->GetName ());
2274 view->unref ();
2275 view = NULL;
2278 Control::OnApplyTemplate ();
2281 void
2282 TextBoxBase::ClearSelection (int start)
2284 BatchPush ();
2285 SetSelectionStart (start);
2286 SetSelectionLength (0);
2287 BatchPop ();
2290 bool
2291 TextBoxBase::SelectWithError (int start, int length, MoonError *error)
2293 if (start < 0) {
2294 MoonError::FillIn (error, MoonError::ARGUMENT, "selection start must be >= 0");
2295 return false;
2298 if (length < 0) {
2299 MoonError::FillIn (error, MoonError::ARGUMENT, "selection length must be >= 0");
2300 return false;
2303 if (start > buffer->len)
2304 start = buffer->len;
2306 if (length > (buffer->len - start))
2307 length = (buffer->len - start);
2309 BatchPush ();
2310 SetSelectionStart (start);
2311 SetSelectionLength (length);
2312 BatchPop ();
2314 ResetIMContext ();
2316 SyncAndEmit ();
2318 return true;
2321 void
2322 TextBoxBase::SelectAll ()
2324 SelectWithError (0, buffer->len, NULL);
2327 bool
2328 TextBoxBase::CanUndo ()
2330 return !undo->IsEmpty ();
2333 bool
2334 TextBoxBase::CanRedo ()
2336 return !redo->IsEmpty ();
2339 void
2340 TextBoxBase::Undo ()
2342 TextBoxUndoActionReplace *replace;
2343 TextBoxUndoActionInsert *insert;
2344 TextBoxUndoActionDelete *dele;
2345 TextBoxUndoAction *action;
2346 int anchor, cursor;
2348 if (undo->IsEmpty ())
2349 return;
2351 action = undo->Pop ();
2352 redo->Push (action);
2354 switch (action->type) {
2355 case TextBoxUndoActionTypeInsert:
2356 insert = (TextBoxUndoActionInsert *) action;
2358 buffer->Cut (insert->start, insert->length);
2359 anchor = action->selection_anchor;
2360 cursor = action->selection_cursor;
2361 break;
2362 case TextBoxUndoActionTypeDelete:
2363 dele = (TextBoxUndoActionDelete *) action;
2365 buffer->Insert (dele->start, dele->text, dele->length);
2366 anchor = action->selection_anchor;
2367 cursor = action->selection_cursor;
2368 break;
2369 case TextBoxUndoActionTypeReplace:
2370 replace = (TextBoxUndoActionReplace *) action;
2372 buffer->Cut (replace->start, replace->inlen);
2373 buffer->Insert (replace->start, replace->deleted, replace->length);
2374 anchor = action->selection_anchor;
2375 cursor = action->selection_cursor;
2376 break;
2379 BatchPush ();
2380 SetSelectionStart (MIN (anchor, cursor));
2381 SetSelectionLength (abs (cursor - anchor));
2382 emit = TEXT_CHANGED | SELECTION_CHANGED;
2383 selection_anchor = anchor;
2384 selection_cursor = cursor;
2385 BatchPop ();
2387 SyncAndEmit ();
2390 void
2391 TextBoxBase::Redo ()
2393 TextBoxUndoActionReplace *replace;
2394 TextBoxUndoActionInsert *insert;
2395 TextBoxUndoActionDelete *dele;
2396 TextBoxUndoAction *action;
2397 int anchor, cursor;
2399 if (redo->IsEmpty ())
2400 return;
2402 action = redo->Pop ();
2403 undo->Push (action);
2405 switch (action->type) {
2406 case TextBoxUndoActionTypeInsert:
2407 insert = (TextBoxUndoActionInsert *) action;
2409 buffer->Insert (insert->start, insert->buffer->text, insert->buffer->len);
2410 anchor = cursor = insert->start + insert->buffer->len;
2411 break;
2412 case TextBoxUndoActionTypeDelete:
2413 dele = (TextBoxUndoActionDelete *) action;
2415 buffer->Cut (dele->start, dele->length);
2416 anchor = cursor = dele->start;
2417 break;
2418 case TextBoxUndoActionTypeReplace:
2419 replace = (TextBoxUndoActionReplace *) action;
2421 buffer->Cut (replace->start, replace->length);
2422 buffer->Insert (replace->start, replace->inserted, replace->inlen);
2423 anchor = cursor = replace->start + replace->inlen;
2424 break;
2427 BatchPush ();
2428 SetSelectionStart (MIN (anchor, cursor));
2429 SetSelectionLength (abs (cursor - anchor));
2430 emit = TEXT_CHANGED | SELECTION_CHANGED;
2431 selection_anchor = anchor;
2432 selection_cursor = cursor;
2433 BatchPop ();
2435 SyncAndEmit ();
2440 // TextBoxDynamicPropertyValueProvider
2443 class TextBoxDynamicPropertyValueProvider : public PropertyValueProvider {
2444 Value *selection_background;
2445 Value *selection_foreground;
2447 public:
2448 TextBoxDynamicPropertyValueProvider (DependencyObject *obj, PropertyPrecedence precedence) : PropertyValueProvider (obj, precedence)
2450 selection_background = NULL;
2451 selection_foreground = NULL;
2454 virtual ~TextBoxDynamicPropertyValueProvider ()
2456 delete selection_background;
2457 delete selection_foreground;
2460 virtual Value *GetPropertyValue (DependencyProperty *property)
2462 if (property->GetId () == TextBox::SelectionBackgroundProperty) {
2463 return selection_background;
2464 } else if (property->GetId () == TextBox::SelectionForegroundProperty) {
2465 return selection_foreground;
2468 return NULL;
2471 void InitializeSelectionBrushes ()
2473 if (!selection_background)
2474 selection_background = Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2476 if (!selection_foreground)
2477 selection_foreground = Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2483 // TextBox
2486 TextBox::TextBox ()
2488 providers[PropertyPrecedence_DynamicValue] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2490 Initialize (Type::TEXTBOX, "System.Windows.Controls.TextBox");
2491 events_mask = TEXT_CHANGED | SELECTION_CHANGED;
2492 multiline = true;
2495 void
2496 TextBox::EmitSelectionChanged ()
2498 EmitAsync (TextBox::SelectionChangedEvent, new RoutedEventArgs ());
2501 void
2502 TextBox::EmitTextChanged ()
2504 EmitAsync (TextBox::TextChangedEvent, new TextChangedEventArgs ());
2507 void
2508 TextBox::SyncSelectedText ()
2510 if (selection_cursor != selection_anchor) {
2511 int length = abs (selection_cursor - selection_anchor);
2512 int start = MIN (selection_anchor, selection_cursor);
2513 char *text;
2515 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2517 setvalue = false;
2518 SetValue (TextBox::SelectedTextProperty, Value (text, true));
2519 setvalue = true;
2520 } else {
2521 setvalue = false;
2522 SetValue (TextBox::SelectedTextProperty, Value (""));
2523 setvalue = true;
2527 void
2528 TextBox::SyncText ()
2530 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
2532 setvalue = false;
2533 SetValue (TextBox::TextProperty, Value (text, true));
2534 setvalue = true;
2537 void
2538 TextBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2540 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2541 DependencyProperty *prop;
2542 int start, length;
2544 if (args->GetId () == TextBox::AcceptsReturnProperty) {
2545 // update accepts_return state
2546 accepts_return = args->GetNewValue ()->AsBool ();
2547 } else if (args->GetId () == TextBox::CaretBrushProperty) {
2548 // FIXME: if we want to be perfect, we could invalidate the
2549 // blinking cursor rect if it is active... but is it that
2550 // important?
2551 } else if (args->GetId () == TextBox::FontSourceProperty) {
2552 FontSource *source = args->GetNewValue () ? args->GetNewValue ()->AsFontSource () : NULL;
2553 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2555 // FIXME: ideally we'd remove the old item from the cache (or,
2556 // rather, 'unref' it since some other textblocks/boxes might
2557 // still be using it).
2559 g_free (font_source);
2561 if (source && source->stream)
2562 font_source = manager->AddResource (source->stream);
2563 else
2564 font_source = NULL;
2566 changed = TextBoxModelChangedFont;
2567 font->SetSource (font_source);
2568 } else if (args->GetId () == TextBox::IsReadOnlyProperty) {
2569 // update is_read_only state
2570 is_read_only = args->GetNewValue ()->AsBool ();
2572 if (focused) {
2573 if (is_read_only) {
2574 ResetIMContext ();
2575 gtk_im_context_focus_out (im_ctx);
2576 } else {
2577 gtk_im_context_focus_in (im_ctx);
2580 } else if (args->GetId () == TextBox::MaxLengthProperty) {
2581 // update max_length state
2582 max_length = args->GetNewValue ()->AsInt32 ();
2583 } else if (args->GetId () == TextBox::SelectedTextProperty) {
2584 if (setvalue) {
2585 Value *value = args->GetNewValue ();
2586 const char *str = value && value->AsString () ? value->AsString () : "";
2587 TextBoxUndoAction *action = NULL;
2588 gunichar *text;
2589 glong textlen;
2591 length = abs (selection_cursor - selection_anchor);
2592 start = MIN (selection_anchor, selection_cursor);
2594 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2595 if (length > 0) {
2596 // replace the currently selected text
2597 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, textlen);
2599 buffer->Replace (start, length, text, textlen);
2600 } else if (textlen > 0) {
2601 // insert the text at the cursor
2602 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, textlen);
2604 buffer->Insert (start, text, textlen);
2607 g_free (text);
2609 if (action != NULL) {
2610 emit |= TEXT_CHANGED;
2611 undo->Push (action);
2612 redo->Clear ();
2614 ClearSelection (start + textlen);
2615 ResetIMContext ();
2617 SyncAndEmit ();
2619 } else {
2620 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str);
2623 } else if (args->GetId () == TextBox::SelectionStartProperty) {
2624 length = abs (selection_cursor - selection_anchor);
2625 start = args->GetNewValue ()->AsInt32 ();
2627 if (start > buffer->len) {
2628 // clamp the selection start offset to a valid value
2629 SetSelectionStart (buffer->len);
2630 return;
2633 if (start + length > buffer->len) {
2634 // clamp the selection length to a valid value
2635 BatchPush ();
2636 length = buffer->len - start;
2637 SetSelectionLength (length);
2638 BatchPop ();
2641 // SelectionStartProperty is marked as AlwaysChange -
2642 // if the value hasn't actually changed, then we do
2643 // not want to emit the TextBoxModelChanged event.
2644 if (selection_anchor != start) {
2645 changed = TextBoxModelChangedSelection;
2646 have_offset = false;
2649 // When set programatically, anchor is always the
2650 // start and cursor is always the end.
2651 selection_cursor = start + length;
2652 selection_anchor = start;
2654 emit |= SELECTION_CHANGED;
2656 SyncAndEmit ();
2657 } else if (args->GetId () == TextBox::SelectionLengthProperty) {
2658 start = MIN (selection_anchor, selection_cursor);
2659 length = args->GetNewValue ()->AsInt32 ();
2661 if (start + length > buffer->len) {
2662 // clamp the selection length to a valid value
2663 length = buffer->len - start;
2664 SetSelectionLength (length);
2665 return;
2668 // SelectionLengthProperty is marked as AlwaysChange -
2669 // if the value hasn't actually changed, then we do
2670 // not want to emit the TextBoxModelChanged event.
2671 if (selection_cursor != start + length) {
2672 changed = TextBoxModelChangedSelection;
2673 have_offset = false;
2676 // When set programatically, anchor is always the
2677 // start and cursor is always the end.
2678 selection_cursor = start + length;
2679 selection_anchor = start;
2681 emit |= SELECTION_CHANGED;
2683 SyncAndEmit ();
2684 } else if (args->GetId () == TextBox::SelectionBackgroundProperty) {
2685 changed = TextBoxModelChangedBrush;
2686 } else if (args->GetId () == TextBox::SelectionForegroundProperty) {
2687 changed = TextBoxModelChangedBrush;
2688 } else if (args->GetId () == TextBox::TextProperty) {
2689 if (setvalue) {
2690 Value *value = args->GetNewValue ();
2691 const char *str = value && value->AsString () ? value->AsString () : "";
2692 TextBoxUndoAction *action;
2693 gunichar *text;
2694 glong textlen;
2696 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2697 if (buffer->len > 0) {
2698 // replace the current text
2699 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, 0, buffer->len, text, textlen);
2701 buffer->Replace (0, buffer->len, text, textlen);
2702 } else {
2703 // insert the text
2704 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
2706 buffer->Insert (0, text, textlen);
2709 undo->Push (action);
2710 redo->Clear ();
2711 g_free (text);
2713 emit |= TEXT_CHANGED;
2714 ClearSelection (0);
2715 ResetIMContext ();
2717 SyncAndEmit (value && !value->GetIsNull ());
2718 } else {
2719 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str);
2723 changed = TextBoxModelChangedText;
2724 } else if (args->GetId () == TextBox::TextAlignmentProperty) {
2725 changed = TextBoxModelChangedTextAlignment;
2726 } else if (args->GetId () == TextBox::TextWrappingProperty) {
2727 changed = TextBoxModelChangedTextWrapping;
2728 } else if (args->GetId () == TextBox::HorizontalScrollBarVisibilityProperty) {
2729 // XXX more crap because these aren't templatebound.
2730 if (contentElement) {
2731 if ((prop = contentElement->GetDependencyProperty ("VerticalScrollBarVisibility")))
2732 contentElement->SetValue (prop, GetValue (TextBox::VerticalScrollBarVisibilityProperty));
2735 Invalidate ();
2736 } else if (args->GetId () == TextBox::VerticalScrollBarVisibilityProperty) {
2737 // XXX more crap because these aren't templatebound.
2738 if (contentElement) {
2739 if ((prop = contentElement->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2740 contentElement->SetValue (prop, GetValue (TextBox::HorizontalScrollBarVisibilityProperty));
2743 Invalidate ();
2746 if (changed != TextBoxModelChangedNothing)
2747 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2749 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
2750 TextBoxBase::OnPropertyChanged (args, error);
2751 return;
2754 NotifyListenersOfPropertyChange (args, error);
2757 void
2758 TextBox::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2760 if (prop && (prop->GetId () == TextBox::SelectionBackgroundProperty ||
2761 prop->GetId () == TextBox::SelectionForegroundProperty)) {
2762 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
2763 Invalidate ();
2766 if (prop->GetOwnerType () != Type::TEXTBOX)
2767 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
2770 void
2771 TextBox::OnApplyTemplate ()
2773 DependencyProperty *prop;
2775 TextBoxBase::OnApplyTemplate ();
2777 if (!contentElement)
2778 return;
2780 // XXX LAME these should be template bindings in the textbox template.
2781 if ((prop = contentElement->GetDependencyProperty ("VerticalScrollBarVisibility")))
2782 contentElement->SetValue (prop, GetValue (TextBox::VerticalScrollBarVisibilityProperty));
2784 if ((prop = contentElement->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2785 contentElement->SetValue (prop, GetValue (TextBox::HorizontalScrollBarVisibilityProperty));
2790 // PasswordBox
2794 // PasswordBoxDynamicPropertyValueProvider
2797 class PasswordBoxDynamicPropertyValueProvider : public PropertyValueProvider {
2798 Value *selection_background;
2799 Value *selection_foreground;
2801 public:
2802 PasswordBoxDynamicPropertyValueProvider (DependencyObject *obj, PropertyPrecedence precedence) : PropertyValueProvider (obj, precedence)
2804 selection_background = NULL;
2805 selection_foreground = NULL;
2808 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2810 delete selection_background;
2811 delete selection_foreground;
2814 virtual Value *GetPropertyValue (DependencyProperty *property)
2816 if (property->GetId () == PasswordBox::SelectionBackgroundProperty) {
2817 return selection_background;
2818 } else if (property->GetId () == PasswordBox::SelectionForegroundProperty) {
2819 return selection_foreground;
2822 return NULL;
2825 void InitializeSelectionBrushes ()
2827 if (!selection_background)
2828 selection_background = Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2830 if (!selection_foreground)
2831 selection_foreground = Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2837 // PasswordBox
2840 PasswordBox::PasswordBox ()
2842 providers[PropertyPrecedence_DynamicValue] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2844 Initialize (Type::PASSWORDBOX, "System.Windows.Controls.PasswordBox");
2845 events_mask = TEXT_CHANGED;
2846 secret = true;
2848 display = g_string_new ("");
2851 PasswordBox::~PasswordBox ()
2853 g_string_free (display, true);
2857 PasswordBox::CursorDown (int cursor, bool page)
2859 return GetBuffer ()->len;
2863 PasswordBox::CursorUp (int cursor, bool page)
2865 return 0;
2869 PasswordBox::CursorLineBegin (int cursor)
2871 return 0;
2875 PasswordBox::CursorLineEnd (int cursor, bool include)
2877 return GetBuffer ()->len;
2881 PasswordBox::CursorNextWord (int cursor)
2883 return GetBuffer ()->len;
2887 PasswordBox::CursorPrevWord (int cursor)
2889 return 0;
2892 void
2893 PasswordBox::EmitTextChanged ()
2895 EmitAsync (PasswordBox::PasswordChangedEvent, new RoutedEventArgs ());
2898 void
2899 PasswordBox::SyncSelectedText ()
2901 if (selection_cursor != selection_anchor) {
2902 int length = abs (selection_cursor - selection_anchor);
2903 int start = MIN (selection_anchor, selection_cursor);
2904 char *text;
2906 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2908 setvalue = false;
2909 SetValue (PasswordBox::SelectedTextProperty, Value (text, true));
2910 setvalue = true;
2911 } else {
2912 setvalue = false;
2913 SetValue (PasswordBox::SelectedTextProperty, Value (""));
2914 setvalue = true;
2918 void
2919 PasswordBox::SyncDisplayText ()
2921 gunichar c = GetPasswordChar ();
2923 g_string_truncate (display, 0);
2925 for (int i = 0; i < buffer->len; i++)
2926 g_string_append_unichar (display, c);
2929 void
2930 PasswordBox::SyncText ()
2932 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
2934 setvalue = false;
2935 SetValue (PasswordBox::PasswordProperty, Value (text, true));
2936 setvalue = true;
2938 SyncDisplayText ();
2941 const char *
2942 PasswordBox::GetDisplayText ()
2944 return display->str;
2947 void
2948 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2950 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2951 int length, start;
2953 if (args->GetId () == PasswordBox::CaretBrushProperty) {
2954 // FIXME: if we want to be perfect, we could invalidate the
2955 // blinking cursor rect if it is active... but is it that
2956 // important?
2957 } else if (args->GetId () == PasswordBox::FontSourceProperty) {
2958 FontSource *source = args->GetNewValue () ? args->GetNewValue ()->AsFontSource () : NULL;
2959 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2961 // FIXME: ideally we'd remove the old item from the cache (or,
2962 // rather, 'unref' it since some other textblocks/boxes might
2963 // still be using it).
2965 g_free (font_source);
2967 if (source && source->stream)
2968 font_source = manager->AddResource (source->stream);
2969 else
2970 font_source = NULL;
2972 changed = TextBoxModelChangedFont;
2973 font->SetSource (font_source);
2974 } else if (args->GetId () == PasswordBox::MaxLengthProperty) {
2975 // update max_length state
2976 max_length = args->GetNewValue()->AsInt32 ();
2977 } else if (args->GetId () == PasswordBox::PasswordCharProperty) {
2978 changed = TextBoxModelChangedText;
2979 } else if (args->GetId () == PasswordBox::PasswordProperty) {
2980 if (setvalue) {
2981 Value *value = args->GetNewValue ();
2982 const char *str = value && value->AsString () ? value->AsString () : "";
2983 TextBoxUndoAction *action;
2984 gunichar *text;
2985 glong textlen;
2987 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2988 if (buffer->len > 0) {
2989 // replace the current text
2990 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, 0, buffer->len, text, textlen);
2992 buffer->Replace (0, buffer->len, text, textlen);
2993 } else {
2994 // insert the text
2995 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
2997 buffer->Insert (0, text, textlen);
3000 undo->Push (action);
3001 redo->Clear ();
3002 g_free (text);
3004 emit |= TEXT_CHANGED;
3005 SyncDisplayText ();
3006 ClearSelection (0);
3007 ResetIMContext ();
3009 SyncAndEmit ();
3013 changed = TextBoxModelChangedText;
3014 } else if (args->GetId () == PasswordBox::SelectedTextProperty) {
3015 if (setvalue) {
3016 Value *value = args->GetNewValue ();
3017 const char *str = value && value->AsString () ? value->AsString () : "";
3018 TextBoxUndoAction *action = NULL;
3019 gunichar *text;
3020 glong textlen;
3022 length = abs (selection_cursor - selection_anchor);
3023 start = MIN (selection_anchor, selection_cursor);
3025 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
3026 if (length > 0) {
3027 // replace the currently selected text
3028 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, textlen);
3030 buffer->Replace (start, length, text, textlen);
3031 } else if (textlen > 0) {
3032 // insert the text at the cursor
3033 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, textlen);
3035 buffer->Insert (start, text, textlen);
3038 g_free (text);
3040 if (action != NULL) {
3041 undo->Push (action);
3042 redo->Clear ();
3044 ClearSelection (start + textlen);
3045 emit |= TEXT_CHANGED;
3046 SyncDisplayText ();
3047 ResetIMContext ();
3049 SyncAndEmit ();
3053 } else if (args->GetId () == PasswordBox::SelectionStartProperty) {
3054 length = abs (selection_cursor - selection_anchor);
3055 start = args->GetNewValue ()->AsInt32 ();
3057 if (start > buffer->len) {
3058 // clamp the selection start offset to a valid value
3059 SetSelectionStart (buffer->len);
3060 return;
3063 if (start + length > buffer->len) {
3064 // clamp the selection length to a valid value
3065 BatchPush ();
3066 length = buffer->len - start;
3067 SetSelectionLength (length);
3068 BatchPop ();
3071 // SelectionStartProperty is marked as AlwaysChange -
3072 // if the value hasn't actually changed, then we do
3073 // not want to emit the TextBoxModelChanged event.
3074 if (selection_anchor != start) {
3075 changed = TextBoxModelChangedSelection;
3076 have_offset = false;
3079 // When set programatically, anchor is always the
3080 // start and cursor is always the end.
3081 selection_cursor = start + length;
3082 selection_anchor = start;
3084 emit |= SELECTION_CHANGED;
3086 SyncAndEmit ();
3087 } else if (args->GetId () == PasswordBox::SelectionLengthProperty) {
3088 start = MIN (selection_anchor, selection_cursor);
3089 length = args->GetNewValue ()->AsInt32 ();
3091 if (start + length > buffer->len) {
3092 // clamp the selection length to a valid value
3093 length = buffer->len - start;
3094 SetSelectionLength (length);
3095 return;
3098 // SelectionLengthProperty is marked as AlwaysChange -
3099 // if the value hasn't actually changed, then we do
3100 // not want to emit the TextBoxModelChanged event.
3101 if (selection_cursor != start + length) {
3102 changed = TextBoxModelChangedSelection;
3103 have_offset = false;
3106 // When set programatically, anchor is always the
3107 // start and cursor is always the end.
3108 selection_cursor = start + length;
3109 selection_anchor = start;
3111 emit |= SELECTION_CHANGED;
3113 SyncAndEmit ();
3114 } else if (args->GetId () == PasswordBox::SelectionBackgroundProperty) {
3115 changed = TextBoxModelChangedBrush;
3116 } else if (args->GetId () == PasswordBox::SelectionForegroundProperty) {
3117 changed = TextBoxModelChangedBrush;
3120 if (changed != TextBoxModelChangedNothing)
3121 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
3123 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
3124 TextBoxBase::OnPropertyChanged (args, error);
3125 return;
3128 NotifyListenersOfPropertyChange (args, error);
3131 void
3132 PasswordBox::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
3134 if (prop && (prop->GetId () == PasswordBox::SelectionBackgroundProperty ||
3135 prop->GetId () == PasswordBox::SelectionForegroundProperty)) {
3136 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
3137 Invalidate ();
3140 if (prop->GetOwnerType () != Type::TEXTBOX)
3141 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
3146 // TextBoxView
3149 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3150 #define CURSOR_BLINK_ON_MULTIPLIER 2
3151 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3152 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3153 #define CURSOR_BLINK_DIVIDER 3
3155 TextBoxView::TextBoxView ()
3157 SetObjectType (Type::TEXTBOXVIEW);
3159 AddHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3160 AddHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3162 SetCursor (MouseCursorIBeam);
3164 cursor = Rect (0, 0, 0, 0);
3165 layout = new TextLayout ();
3166 selection_changed = false;
3167 had_selected_text = false;
3168 cursor_visible = false;
3169 blink_timeout = 0;
3170 textbox = NULL;
3171 dirty = false;
3174 TextBoxView::~TextBoxView ()
3176 RemoveHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3177 RemoveHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3179 if (textbox) {
3180 textbox->RemoveHandler (TextBox::ModelChangedEvent, TextBoxView::model_changed, this);
3181 textbox->view = NULL;
3184 DisconnectBlinkTimeout ();
3186 delete layout;
3189 TextLayoutLine *
3190 TextBoxView::GetLineFromY (double y, int *index)
3192 return layout->GetLineFromY (Point (), y, index);
3195 TextLayoutLine *
3196 TextBoxView::GetLineFromIndex (int index)
3198 return layout->GetLineFromIndex (index);
3202 TextBoxView::GetCursorFromXY (double x, double y)
3204 return layout->GetCursorFromXY (Point (), x, y);
3207 gboolean
3208 TextBoxView::blink (void *user_data)
3210 return ((TextBoxView *) user_data)->Blink ();
3213 static guint
3214 GetCursorBlinkTimeout (TextBoxView *view)
3216 GtkSettings *settings;
3217 MoonWindow *window;
3218 GdkScreen *screen;
3219 GdkWindow *widget;
3220 Surface *surface;
3221 guint timeout;
3223 if (!(surface = view->GetSurface ()))
3224 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3226 if (!(window = surface->GetWindow ()))
3227 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3229 if (!(widget = window->GetGdkWindow ()))
3230 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3232 if (!(screen = gdk_drawable_get_screen ((GdkDrawable *) widget)))
3233 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3235 if (!(settings = gtk_settings_get_for_screen (screen)))
3236 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3238 g_object_get (settings, "gtk-cursor-blink-time", &timeout, NULL);
3240 return timeout;
3243 void
3244 TextBoxView::ConnectBlinkTimeout (guint multiplier)
3246 guint timeout = GetCursorBlinkTimeout (this) * multiplier / CURSOR_BLINK_DIVIDER;
3247 Surface *surface = GetSurface ();
3248 TimeManager *manager;
3250 if (!surface || !(manager = surface->GetTimeManager ()))
3251 return;
3253 blink_timeout = manager->AddTimeout (MOON_PRIORITY_DEFAULT, timeout, TextBoxView::blink, this);
3256 void
3257 TextBoxView::DisconnectBlinkTimeout ()
3259 TimeManager *manager;
3260 Surface *surface;
3262 if (blink_timeout != 0) {
3263 if (!(surface = GetSurface ()) || !(manager = surface->GetTimeManager ()))
3264 return;
3266 manager->RemoveTimeout (blink_timeout);
3267 blink_timeout = 0;
3271 bool
3272 TextBoxView::Blink ()
3274 guint multiplier;
3276 SetCurrentDeployment (true);
3278 if (cursor_visible) {
3279 multiplier = CURSOR_BLINK_OFF_MULTIPLIER;
3280 HideCursor ();
3281 } else {
3282 multiplier = CURSOR_BLINK_ON_MULTIPLIER;
3283 ShowCursor ();
3286 ConnectBlinkTimeout (multiplier);
3288 return false;
3291 void
3292 TextBoxView::DelayCursorBlink ()
3294 DisconnectBlinkTimeout ();
3295 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER);
3296 UpdateCursor (true);
3297 ShowCursor ();
3300 void
3301 TextBoxView::BeginCursorBlink ()
3303 if (blink_timeout == 0) {
3304 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER);
3305 UpdateCursor (true);
3306 ShowCursor ();
3310 void
3311 TextBoxView::EndCursorBlink ()
3313 DisconnectBlinkTimeout ();
3315 if (cursor_visible)
3316 HideCursor ();
3319 void
3320 TextBoxView::ResetCursorBlink (bool delay)
3322 if (textbox->IsFocused () && !textbox->HasSelectedText ()) {
3323 // cursor is blinkable... proceed with blinkage
3324 if (delay)
3325 DelayCursorBlink ();
3326 else
3327 BeginCursorBlink ();
3328 } else {
3329 // cursor not blinkable... stop all blinkage
3330 EndCursorBlink ();
3334 void
3335 TextBoxView::InvalidateCursor ()
3337 Invalidate (cursor.Transform (&absolute_xform));
3340 void
3341 TextBoxView::ShowCursor ()
3343 cursor_visible = true;
3344 InvalidateCursor ();
3347 void
3348 TextBoxView::HideCursor ()
3350 cursor_visible = false;
3351 InvalidateCursor ();
3354 void
3355 TextBoxView::UpdateCursor (bool invalidate)
3357 int cur = textbox->GetCursor ();
3358 GdkRectangle area;
3359 Rect rect;
3361 // invalidate current cursor rect
3362 if (invalidate && cursor_visible)
3363 InvalidateCursor ();
3365 // calculate the new cursor rect
3366 cursor = layout->GetCursor (Point (), cur);
3368 // transform the cursor rect into absolute coordinates for the IM context
3369 rect = cursor.Transform (&absolute_xform);
3370 area = rect.ToGdkRectangle ();
3372 gtk_im_context_set_cursor_location (textbox->im_ctx, &area);
3374 textbox->EmitCursorPositionChanged (cursor.height, cursor.x, cursor.y);
3376 // invalidate the new cursor rect
3377 if (invalidate && cursor_visible)
3378 InvalidateCursor ();
3381 void
3382 TextBoxView::UpdateText ()
3384 const char *text = textbox->GetDisplayText ();
3386 layout->SetText (text ? text : "", -1);
3389 void
3390 TextBoxView::GetSizeForBrush (cairo_t *cr, double *width, double *height)
3392 *height = GetActualHeight ();
3393 *width = GetActualWidth ();
3396 Size
3397 TextBoxView::ComputeActualSize ()
3399 Layout (Size (INFINITY, INFINITY));
3401 Size actual (0,0);
3402 layout->GetActualExtents (&actual.width, &actual.height);
3404 return actual;
3407 Size
3408 TextBoxView::MeasureOverride (Size availableSize)
3410 Size desired = Size ();
3413 Layout (availableSize);
3415 layout->GetActualExtents (&desired.width, &desired.height);
3417 if (GetUseLayoutRounding ())
3418 desired.width = ceil (desired.width);
3420 return desired.Min (availableSize);
3423 Size
3424 TextBoxView::ArrangeOverride (Size finalSize)
3426 Size arranged = Size ();
3429 Layout (finalSize);
3431 layout->GetActualExtents (&arranged.width, &arranged.height);
3433 arranged = arranged.Max (finalSize);
3435 return arranged;
3438 void
3439 TextBoxView::Layout (Size constraint)
3441 layout->SetMaxWidth (constraint.width);
3443 layout->Layout ();
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 if (dirty) {
3491 Layout (renderSize);
3492 UpdateCursor (false);
3493 dirty = false;
3496 if (selection_changed) {
3497 layout->Select (textbox->GetSelectionStart (), textbox->GetSelectionLength ());
3498 selection_changed = false;
3501 cairo_save (cr);
3502 cairo_set_matrix (cr, &absolute_xform);
3504 if (!path_only)
3505 RenderLayoutClip (cr);
3507 layout->SetAvailableWidth (renderSize.width);
3508 Paint (cr);
3509 cairo_restore (cr);
3512 void
3513 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs *args)
3515 switch (args->changed) {
3516 case TextBoxModelChangedTextAlignment:
3517 // text alignment changed, update our layout
3518 if (layout->SetTextAlignment ((TextAlignment) args->property->GetNewValue()->AsInt32 ()))
3519 dirty = true;
3520 break;
3521 case TextBoxModelChangedTextWrapping:
3522 // text wrapping changed, update our layout
3523 if (layout->SetTextWrapping ((TextWrapping) args->property->GetNewValue()->AsInt32 ()))
3524 dirty = true;
3525 break;
3526 case TextBoxModelChangedSelection:
3527 if (had_selected_text || textbox->HasSelectedText ()) {
3528 // the selection has changed, update the layout's selection
3529 had_selected_text = textbox->HasSelectedText ();
3530 selection_changed = true;
3531 ResetCursorBlink (false);
3532 } else {
3533 // cursor position changed
3534 ResetCursorBlink (true);
3535 return;
3537 break;
3538 case TextBoxModelChangedBrush:
3539 // a brush has changed, no layout updates needed, we just need to re-render
3540 break;
3541 case TextBoxModelChangedFont:
3542 // font changed, need to recalculate layout/bounds
3543 layout->ResetState ();
3544 dirty = true;
3545 break;
3546 case TextBoxModelChangedText:
3547 // the text has changed, need to recalculate layout/bounds
3548 UpdateText ();
3549 dirty = true;
3550 break;
3551 default:
3552 // nothing changed??
3553 return;
3556 if (dirty) {
3557 InvalidateMeasure ();
3558 UpdateBounds (true);
3561 Invalidate ();
3564 void
3565 TextBoxView::model_changed (EventObject *sender, EventArgs *args, gpointer closure)
3567 ((TextBoxView *) closure)->OnModelChanged ((TextBoxModelChangedEventArgs *) args);
3570 void
3571 TextBoxView::OnLostFocus ()
3573 EndCursorBlink ();
3576 void
3577 TextBoxView::OnGotFocus ()
3579 ResetCursorBlink (false);
3582 void
3583 TextBoxView::OnMouseLeftButtonDown (MouseButtonEventArgs *args)
3585 // proxy to our parent TextBox control
3586 textbox->OnMouseLeftButtonDown (args);
3589 void
3590 TextBoxView::mouse_left_button_down (EventObject *sender, EventArgs *args, gpointer closure)
3592 ((TextBoxView *) closure)->OnMouseLeftButtonDown ((MouseButtonEventArgs *) args);
3595 void
3596 TextBoxView::OnMouseLeftButtonUp (MouseButtonEventArgs *args)
3598 // proxy to our parent TextBox control
3599 textbox->OnMouseLeftButtonUp (args);
3602 void
3603 TextBoxView::mouse_left_button_up (EventObject *sender, EventArgs *args, gpointer closure)
3605 ((TextBoxView *) closure)->OnMouseLeftButtonUp ((MouseButtonEventArgs *) args);
3608 void
3609 TextBoxView::SetTextBox (TextBoxBase *textbox)
3611 TextLayoutAttributes *attrs;
3613 if (this->textbox == textbox)
3614 return;
3616 if (this->textbox) {
3617 // remove the event handlers from the old textbox
3618 this->textbox->RemoveHandler (TextBoxBase::ModelChangedEvent, TextBoxView::model_changed, this);
3621 this->textbox = textbox;
3623 if (textbox) {
3624 textbox->AddHandler (TextBoxBase::ModelChangedEvent, TextBoxView::model_changed, this);
3626 // sync our state with the textbox
3627 layout->SetTextAttributes (new List ());
3628 attrs = new TextLayoutAttributes ((ITextAttributes *) textbox, 0);
3629 layout->GetTextAttributes ()->Append (attrs);
3631 layout->SetTextAlignment (textbox->GetTextAlignment ());
3632 layout->SetTextWrapping (textbox->GetTextWrapping ());
3633 had_selected_text = textbox->HasSelectedText ();
3634 selection_changed = true;
3635 UpdateText ();
3636 } else {
3637 layout->SetTextAttributes (NULL);
3638 layout->SetText (NULL, -1);
3641 UpdateBounds (true);
3642 InvalidateMeasure ();
3643 Invalidate ();
3644 dirty = true;