2009-08-26 Chris Toshok <toshok@ximian.com>
[moon.git] / src / textbox.cpp
blobc641f509287d6fede3dbf81f2233bec59554c90a
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 // AsyncEventClosure
295 class AsyncEventClosure : public EventObject {
296 public:
297 TextBoxBase *textbox;
298 int generation;
300 AsyncEventClosure (int generation, TextBoxBase *textbox)
302 this->generation = generation;
303 this->textbox = textbox;
305 textbox->ref ();
308 virtual ~AsyncEventClosure ()
310 textbox->unref ();
316 // TextBoxUndoActions
319 enum TextBoxUndoActionType {
320 TextBoxUndoActionTypeInsert,
321 TextBoxUndoActionTypeDelete,
322 TextBoxUndoActionTypeReplace,
325 class TextBoxUndoAction : public List::Node {
326 public:
327 TextBoxUndoActionType type;
328 int selection_anchor;
329 int selection_cursor;
330 int length;
331 int start;
334 class TextBoxUndoActionInsert : public TextBoxUndoAction {
335 public:
336 TextBuffer *buffer;
337 bool growable;
339 TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, gunichar c);
340 TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, const gunichar *inserted, int length, bool atomic = false);
341 virtual ~TextBoxUndoActionInsert ();
343 bool Insert (int start, const gunichar *text, int len);
344 bool Insert (int start, gunichar c);
347 class TextBoxUndoActionDelete : public TextBoxUndoAction {
348 public:
349 gunichar *text;
351 TextBoxUndoActionDelete (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length);
352 virtual ~TextBoxUndoActionDelete ();
355 class TextBoxUndoActionReplace : public TextBoxUndoAction {
356 public:
357 gunichar *inserted;
358 gunichar *deleted;
359 int inlen;
361 TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, const gunichar *inserted, int inlen);
362 TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, gunichar c);
363 virtual ~TextBoxUndoActionReplace ();
366 class TextBoxUndoStack {
367 int max_count;
368 List *list;
370 public:
371 TextBoxUndoStack (int max_count);
372 ~TextBoxUndoStack ();
374 bool IsEmpty ();
375 void Clear ();
377 void Push (TextBoxUndoAction *action);
378 TextBoxUndoAction *Peek ();
379 TextBoxUndoAction *Pop ();
382 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, gunichar c)
384 this->type = TextBoxUndoActionTypeInsert;
385 this->selection_anchor = selection_anchor;
386 this->selection_cursor = selection_cursor;
387 this->start = start;
388 this->length = 1;
390 this->buffer = new TextBuffer ();
391 this->buffer->Append (c);
392 this->growable = true;
395 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, const gunichar *inserted, int length, bool atomic)
397 this->type = TextBoxUndoActionTypeInsert;
398 this->selection_anchor = selection_anchor;
399 this->selection_cursor = selection_cursor;
400 this->length = length;
401 this->start = start;
403 this->buffer = new TextBuffer (inserted, length);
404 this->growable = !atomic;
407 TextBoxUndoActionInsert::~TextBoxUndoActionInsert ()
409 delete buffer;
412 bool
413 TextBoxUndoActionInsert::Insert (int start, const gunichar *text, int len)
415 if (!growable || start != (this->start + length))
416 return false;
418 buffer->Append (text, len);
419 length += len;
421 return true;
424 bool
425 TextBoxUndoActionInsert::Insert (int start, gunichar c)
427 if (!growable || start != (this->start + length))
428 return false;
430 buffer->Append (c);
431 length++;
433 return true;
436 TextBoxUndoActionDelete::TextBoxUndoActionDelete (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length)
438 this->type = TextBoxUndoActionTypeDelete;
439 this->selection_anchor = selection_anchor;
440 this->selection_cursor = selection_cursor;
441 this->length = length;
442 this->start = start;
444 this->text = buffer->Substring (start, length);
447 TextBoxUndoActionDelete::~TextBoxUndoActionDelete ()
449 g_free (text);
452 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, const gunichar *inserted, int inlen)
454 this->type = TextBoxUndoActionTypeReplace;
455 this->selection_anchor = selection_anchor;
456 this->selection_cursor = selection_cursor;
457 this->length = length;
458 this->start = start;
460 this->deleted = buffer->Substring (start, length);
461 this->inserted = (gunichar *) g_malloc (UNICODE_LEN (inlen + 1));
462 memcpy (this->inserted, inserted, UNICODE_LEN (inlen + 1));
463 this->inlen = inlen;
466 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, gunichar c)
468 this->type = TextBoxUndoActionTypeReplace;
469 this->selection_anchor = selection_anchor;
470 this->selection_cursor = selection_cursor;
471 this->length = length;
472 this->start = start;
474 this->deleted = buffer->Substring (start, length);
475 this->inserted = g_new (gunichar, 2);
476 memcpy (inserted, &c, sizeof (gunichar));
477 inserted[1] = 0;
478 this->inlen = 1;
481 TextBoxUndoActionReplace::~TextBoxUndoActionReplace ()
483 g_free (inserted);
484 g_free (deleted);
488 TextBoxUndoStack::TextBoxUndoStack (int max_count)
490 this->max_count = max_count;
491 this->list = new List ();
494 TextBoxUndoStack::~TextBoxUndoStack ()
496 delete list;
499 bool
500 TextBoxUndoStack::IsEmpty ()
502 return list->IsEmpty ();
505 void
506 TextBoxUndoStack::Clear ()
508 list->Clear (true);
511 void
512 TextBoxUndoStack::Push (TextBoxUndoAction *action)
514 if (list->Length () == max_count) {
515 List::Node *node = list->Last ();
516 list->Unlink (node);
517 delete node;
520 list->Prepend (action);
523 TextBoxUndoAction *
524 TextBoxUndoStack::Pop ()
526 List::Node *node = list->First ();
528 if (node)
529 list->Unlink (node);
531 return (TextBoxUndoAction *) node;
534 TextBoxUndoAction *
535 TextBoxUndoStack::Peek ()
537 return (TextBoxUndoAction *) list->First ();
542 // TextBoxBase
545 // emit state, also doubles as available event mask
546 #define NOTHING_CHANGED (0)
547 #define SELECTION_CHANGED (1 << 0)
548 #define TEXT_CHANGED (1 << 1)
550 #define CONTROL_MASK GDK_CONTROL_MASK
551 #define SHIFT_MASK GDK_SHIFT_MASK
552 #define ALT_MASK GDK_MOD1_MASK
554 #define IsEOL(c) ((c) == '\r' || (c) == '\n')
556 static GdkWindow *
557 GetGdkWindow (TextBoxBase *textbox)
559 MoonWindow *window;
560 Surface *surface;
562 if (!(surface = textbox->GetSurface ()))
563 return NULL;
565 if (!(window = surface->GetWindow ()))
566 return NULL;
568 return window->GetGdkWindow ();
571 static GtkClipboard *
572 GetClipboard (TextBoxBase *textbox, GdkAtom atom)
574 GdkDisplay *display;
575 GdkWindow *window;
577 if (!(window = GetGdkWindow (textbox)))
578 return NULL;
580 if (!(display = gdk_drawable_get_display ((GdkDrawable *) window)))
581 return NULL;
583 return gtk_clipboard_get_for_display (display, atom);
586 void
587 TextBoxBase::Initialize (Type::Kind type, const char *type_name)
589 ManagedTypeInfo *type_info = new ManagedTypeInfo ("System.Windows", type_name);
591 SetObjectType (type);
592 SetDefaultStyleKey (type_info);
594 AddHandler (UIElement::MouseLeftButtonMultiClickEvent, TextBoxBase::mouse_left_button_multi_click, this);
595 AddHandler (UIElement::MouseLeftButtonDownEvent, TextBoxBase::mouse_left_button_down, this);
596 AddHandler (UIElement::MouseLeftButtonUpEvent, TextBoxBase::mouse_left_button_up, this);
597 AddHandler (UIElement::MouseMoveEvent, TextBoxBase::mouse_move, this);
598 AddHandler (UIElement::LostFocusEvent, TextBoxBase::focus_out, this);
599 AddHandler (UIElement::GotFocusEvent, TextBoxBase::focus_in, this);
601 font = new TextFontDescription ();
602 font->SetFamily (GetFontFamily ()->source);
603 font->SetStretch (GetFontStretch ()->stretch);
604 font->SetWeight (GetFontWeight ()->weight);
605 font->SetStyle (GetFontStyle ()->style);
606 font->SetSize (GetFontSize ());
608 downloaders = g_ptr_array_new ();
609 font_source = NULL;
611 contentElement = NULL;
613 im_ctx = gtk_im_multicontext_new ();
614 gtk_im_context_set_use_preedit (im_ctx, false);
616 g_signal_connect (im_ctx, "retrieve-surrounding", G_CALLBACK (TextBoxBase::retrieve_surrounding), this);
617 g_signal_connect (im_ctx, "delete-surrounding", G_CALLBACK (TextBoxBase::delete_surrounding), this);
618 g_signal_connect (im_ctx, "commit", G_CALLBACK (TextBoxBase::commit), this);
620 undo = new TextBoxUndoStack (10);
621 redo = new TextBoxUndoStack (10);
622 buffer = new TextBuffer ();
623 max_length = 0;
625 emit = NOTHING_CHANGED;
626 events_mask = 0;
628 selection_anchor = 0;
629 selection_cursor = 0;
630 cursor_offset = 0.0;
631 batch = 0;
633 accepts_return = false;
634 need_im_reset = false;
635 is_read_only = false;
636 have_offset = false;
637 multiline = false;
638 selecting = false;
639 setvalue = true;
640 captured = false;
641 focused = false;
642 secret = false;
643 view = NULL;
646 TextBoxBase::~TextBoxBase ()
648 RemoveHandler (UIElement::MouseLeftButtonMultiClickEvent, TextBoxBase::mouse_left_button_multi_click, this);
649 RemoveHandler (UIElement::MouseLeftButtonDownEvent, TextBoxBase::mouse_left_button_down, this);
650 RemoveHandler (UIElement::MouseLeftButtonUpEvent, TextBoxBase::mouse_left_button_up, this);
651 RemoveHandler (UIElement::MouseMoveEvent, TextBoxBase::mouse_move, this);
652 RemoveHandler (UIElement::LostFocusEvent, TextBoxBase::focus_out, this);
653 RemoveHandler (UIElement::GotFocusEvent, TextBoxBase::focus_in, this);
655 ResetIMContext ();
656 g_object_unref (im_ctx);
658 CleanupDownloaders ();
659 g_ptr_array_free (downloaders, true);
660 g_free (font_source);
662 delete buffer;
663 delete undo;
664 delete redo;
665 delete font;
668 void
669 TextBoxBase::SetSurface (Surface *surface)
671 Control::SetSurface (surface);
673 if (surface)
674 gtk_im_context_set_client_window (im_ctx, GetGdkWindow (this));
677 void
678 TextBoxBase::CleanupDownloaders ()
680 Downloader *downloader;
681 guint i;
683 for (i = 0; i < downloaders->len; i++) {
684 downloader = (Downloader *) downloaders->pdata[i];
685 downloader->RemoveHandler (Downloader::CompletedEvent, downloader_complete, this);
686 downloader->Abort ();
687 downloader->unref ();
690 g_ptr_array_set_size (downloaders, 0);
693 double
694 TextBoxBase::GetCursorOffset ()
696 if (!have_offset && view) {
697 cursor_offset = view->GetCursor ().x;
698 have_offset = true;
701 return cursor_offset;
705 TextBoxBase::CursorDown (int cursor, bool page)
707 double y = view->GetCursor ().y;
708 double x = GetCursorOffset ();
709 TextLayoutLine *line;
710 TextLayoutRun *run;
711 int index, cur, n;
712 guint i;
714 if (!(line = view->GetLineFromY (y, &index)))
715 return cursor;
717 if (page) {
718 // calculate the number of lines to skip over
719 n = GetActualHeight () / line->height;
720 } else {
721 n = 1;
724 if (index + n >= view->GetLineCount ()) {
725 // go to the end of the last line
726 line = view->GetLineFromIndex (view->GetLineCount () - 1);
728 for (cur = line->offset, i = 0; i < line->runs->len; i++) {
729 run = (TextLayoutRun *) line->runs->pdata[i];
730 cur += run->count;
733 have_offset = false;
735 return cur;
738 line = view->GetLineFromIndex (index + n);
740 return line->GetCursorFromX (Point (), x);
744 TextBoxBase::CursorUp (int cursor, bool page)
746 double y = view->GetCursor ().y;
747 double x = GetCursorOffset ();
748 TextLayoutLine *line;
749 int index, n;
751 if (!(line = view->GetLineFromY (y, &index)))
752 return cursor;
754 if (page) {
755 // calculate the number of lines to skip over
756 n = GetActualHeight () / line->height;
757 } else {
758 n = 1;
761 if (index < n) {
762 // go to the beginning of the first line
763 have_offset = false;
764 return 0;
767 line = view->GetLineFromIndex (index - n);
769 return line->GetCursorFromX (Point (), x);
772 #ifdef EMULATE_GTK
773 enum CharClass {
774 CharClassUnknown,
775 CharClassWhitespace,
776 CharClassAlphaNumeric
779 static inline CharClass
780 char_class (gunichar c)
782 if (g_unichar_isspace (c))
783 return CharClassWhitespace;
785 if (g_unichar_isalnum (c))
786 return CharClassAlphaNumeric;
788 return CharClassUnknown;
790 #else
791 static bool
792 is_start_of_word (TextBuffer *buffer, int index)
794 // A 'word' starts with an AlphaNumeric immediately preceeded by lwsp
795 if (index > 0 && !g_unichar_isspace (buffer->text[index - 1]))
796 return false;
798 return g_unichar_isalnum (buffer->text[index]);
800 #endif
803 TextBoxBase::CursorNextWord (int cursor)
805 int i, lf, cr;
807 // find the end of the current line
808 cr = CursorLineEnd (cursor);
809 if (buffer->text[cr] == '\r' && buffer->text[cr + 1] == '\n')
810 lf = cr + 1;
811 else
812 lf = cr;
814 // if the cursor is at the end of the line, return the starting offset of the next line
815 if (cursor == cr || cursor == lf) {
816 if (lf < buffer->len)
817 return lf + 1;
819 return cursor;
822 #ifdef EMULATE_GTK
823 CharClass cc = char_class (buffer->text[cursor]);
824 i = cursor;
826 // skip over the word, punctuation, or run of whitespace
827 while (i < cr && char_class (buffer->text[i]) == cc)
828 i++;
830 // skip any whitespace after the word/punct
831 while (i < cr && g_unichar_isspace (buffer->text[i]))
832 i++;
833 #else
834 i = cursor;
836 // skip to the end of the current word
837 while (i < cr && !g_unichar_isspace (buffer->text[i]))
838 i++;
840 // skip any whitespace after the word
841 while (i < cr && g_unichar_isspace (buffer->text[i]))
842 i++;
844 // find the start of the next word
845 while (i < cr && !is_start_of_word (buffer, i))
846 i++;
847 #endif
849 return i;
853 TextBoxBase::CursorPrevWord (int cursor)
855 int begin, i, cr, lf;
857 // find the beginning of the current line
858 lf = CursorLineBegin (cursor) - 1;
860 if (lf > 0 && buffer->text[lf] == '\n' && buffer->text[lf - 1] == '\r')
861 cr = lf - 1;
862 else
863 cr = lf;
865 // if the cursor is at the beginning of the line, return the end of the prev line
866 if (cursor - 1 == lf) {
867 if (cr > 0)
868 return cr;
870 return 0;
873 #ifdef EMULATE_GTK
874 CharClass cc = char_class (buffer->text[cursor - 1]);
875 begin = lf + 1;
876 i = cursor;
878 // skip over the word, punctuation, or run of whitespace
879 while (i > begin && char_class (buffer->text[i - 1]) == cc)
880 i--;
882 // if the cursor was at whitespace, skip back a word too
883 if (cc == CharClassWhitespace && i > begin) {
884 cc = char_class (buffer->text[i - 1]);
885 while (i > begin && char_class (buffer->text[i - 1]) == cc)
886 i--;
888 #else
889 begin = lf + 1;
890 i = cursor;
892 if (cursor < buffer->len) {
893 // skip to the beginning of this word
894 while (i > begin && !g_unichar_isspace (buffer->text[i - 1]))
895 i--;
897 if (i < cursor && is_start_of_word (buffer, i))
898 return i;
901 // skip to the start of the lwsp
902 while (i > begin && g_unichar_isspace (buffer->text[i - 1]))
903 i--;
905 if (i > begin)
906 i--;
908 // skip to the beginning of the word
909 while (i > begin && !is_start_of_word (buffer, i))
910 i--;
911 #endif
913 return i;
917 TextBoxBase::CursorLineBegin (int cursor)
919 int cur = cursor;
921 // find the beginning of the line
922 while (cur > 0 && !IsEOL (buffer->text[cur - 1]))
923 cur--;
925 return cur;
929 TextBoxBase::CursorLineEnd (int cursor, bool include)
931 int cur = cursor;
933 // find the end of the line
934 while (cur < buffer->len && !IsEOL (buffer->text[cur]))
935 cur++;
937 if (include && cur < buffer->len) {
938 if (buffer->text[cur] == '\r' && buffer->text[cur + 1] == '\n')
939 cur += 2;
940 else
941 cur++;
944 return cur;
947 bool
948 TextBoxBase::KeyPressBackSpace (GdkModifierType modifiers)
950 int anchor = selection_anchor;
951 int cursor = selection_cursor;
952 TextBoxUndoAction *action;
953 int start = 0, length = 0;
954 bool handled = false;
956 if ((modifiers & (ALT_MASK | SHIFT_MASK)) != 0)
957 return false;
959 if (cursor != anchor) {
960 // BackSpace w/ active selection: delete the selected text
961 length = abs (cursor - anchor);
962 start = MIN (anchor, cursor);
963 } else if ((modifiers & CONTROL_MASK) != 0) {
964 // Ctrl+BackSpace: delete the word ending at the cursor
965 start = CursorPrevWord (cursor);
966 length = cursor - start;
967 } else if (cursor > 0) {
968 // BackSpace: delete the char before the cursor position
969 if (cursor >= 2 && buffer->text[cursor - 1] == '\n' && buffer->text[cursor - 2] == '\r') {
970 start = cursor - 2;
971 length = 2;
972 } else {
973 start = cursor - 1;
974 length = 1;
978 if (length > 0) {
979 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
980 undo->Push (action);
981 redo->Clear ();
983 buffer->Cut (start, length);
984 emit |= TEXT_CHANGED;
985 anchor = start;
986 cursor = start;
987 handled = true;
990 // check to see if selection has changed
991 if (selection_anchor != anchor || selection_cursor != cursor) {
992 SetSelectionStart (MIN (anchor, cursor));
993 SetSelectionLength (abs (cursor - anchor));
994 selection_anchor = anchor;
995 selection_cursor = cursor;
996 emit |= SELECTION_CHANGED;
997 handled = true;
1000 return handled;
1003 bool
1004 TextBoxBase::KeyPressDelete (GdkModifierType modifiers)
1006 int anchor = selection_anchor;
1007 int cursor = selection_cursor;
1008 TextBoxUndoAction *action;
1009 int start = 0, length = 0;
1010 bool handled = false;
1012 if ((modifiers & (ALT_MASK | SHIFT_MASK)) != 0)
1013 return false;
1015 if (cursor != anchor) {
1016 // Delete w/ active selection: delete the selected text
1017 length = abs (cursor - anchor);
1018 start = MIN (anchor, cursor);
1019 } else if ((modifiers & CONTROL_MASK) != 0) {
1020 // Ctrl+Delete: delete the word starting at the cursor
1021 length = CursorNextWord (cursor) - cursor;
1022 start = cursor;
1023 } else if (cursor < buffer->len) {
1024 // Delete: delete the char after the cursor position
1025 if (buffer->text[cursor] == '\r' && buffer->text[cursor + 1] == '\n')
1026 length = 2;
1027 else
1028 length = 1;
1030 start = cursor;
1033 if (length > 0) {
1034 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1035 undo->Push (action);
1036 redo->Clear ();
1038 buffer->Cut (start, length);
1039 emit |= TEXT_CHANGED;
1040 handled = true;
1043 // check to see if selection has changed
1044 if (selection_anchor != anchor || selection_cursor != cursor) {
1045 SetSelectionStart (MIN (anchor, cursor));
1046 SetSelectionLength (abs (cursor - anchor));
1047 selection_anchor = anchor;
1048 selection_cursor = cursor;
1049 emit |= SELECTION_CHANGED;
1050 handled = true;
1053 return handled;
1056 bool
1057 TextBoxBase::KeyPressPageDown (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 down one page from its current position
1068 cursor = CursorDown (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::KeyPressPageUp (GdkModifierType modifiers)
1093 int anchor = selection_anchor;
1094 int cursor = selection_cursor;
1095 bool handled = false;
1096 bool have;
1098 if ((modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1099 return false;
1101 // move the cursor up one page from its current position
1102 cursor = CursorUp (cursor, true);
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::KeyPressDown (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 down by one line from its current position
1136 cursor = CursorDown (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::KeyPressUp (GdkModifierType modifiers)
1161 int anchor = selection_anchor;
1162 int cursor = selection_cursor;
1163 bool handled = false;
1164 bool have;
1166 if (!accepts_return || (modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1167 return false;
1169 // move the cursor up by one line from its current position
1170 cursor = CursorUp (cursor, false);
1171 have = have_offset;
1173 if ((modifiers & SHIFT_MASK) == 0) {
1174 // clobber the selection
1175 anchor = cursor;
1178 // check to see if selection has changed
1179 if (selection_anchor != anchor || selection_cursor != cursor) {
1180 SetSelectionStart (MIN (anchor, cursor));
1181 SetSelectionLength (abs (cursor - anchor));
1182 selection_anchor = anchor;
1183 selection_cursor = cursor;
1184 emit |= SELECTION_CHANGED;
1185 have_offset = have;
1186 handled = true;
1189 return handled;
1192 bool
1193 TextBoxBase::KeyPressHome (GdkModifierType modifiers)
1195 int anchor = selection_anchor;
1196 int cursor = selection_cursor;
1197 bool handled = false;
1199 if ((modifiers & ALT_MASK) != 0)
1200 return false;
1202 if ((modifiers & CONTROL_MASK) != 0) {
1203 // move the cursor to the beginning of the buffer
1204 cursor = 0;
1205 } else {
1206 // move the cursor to the beginning of the line
1207 cursor = CursorLineBegin (cursor);
1210 if ((modifiers & SHIFT_MASK) == 0) {
1211 // clobber the selection
1212 anchor = cursor;
1215 // check to see if selection has changed
1216 if (selection_anchor != anchor || selection_cursor != cursor) {
1217 SetSelectionStart (MIN (anchor, cursor));
1218 SetSelectionLength (abs (cursor - anchor));
1219 selection_anchor = anchor;
1220 selection_cursor = cursor;
1221 emit |= SELECTION_CHANGED;
1222 have_offset = false;
1223 handled = true;
1226 return handled;
1229 bool
1230 TextBoxBase::KeyPressEnd (GdkModifierType modifiers)
1232 int anchor = selection_anchor;
1233 int cursor = selection_cursor;
1234 bool handled = false;
1236 if ((modifiers & ALT_MASK) != 0)
1237 return false;
1239 if ((modifiers & CONTROL_MASK) != 0) {
1240 // move the cursor to the end of the buffer
1241 cursor = buffer->len;
1242 } else {
1243 // move the cursor to the end of the line
1244 cursor = CursorLineEnd (cursor);
1247 if ((modifiers & SHIFT_MASK) == 0) {
1248 // clobber the selection
1249 anchor = cursor;
1252 // check to see if selection has changed
1253 if (selection_anchor != anchor || selection_cursor != cursor) {
1254 SetSelectionStart (MIN (anchor, cursor));
1255 SetSelectionLength (abs (cursor - anchor));
1256 selection_anchor = anchor;
1257 selection_cursor = cursor;
1258 emit |= SELECTION_CHANGED;
1259 handled = true;
1262 return handled;
1265 bool
1266 TextBoxBase::KeyPressRight (GdkModifierType modifiers)
1268 int anchor = selection_anchor;
1269 int cursor = selection_cursor;
1270 bool handled = false;
1272 if ((modifiers & ALT_MASK) != 0)
1273 return false;
1275 if ((modifiers & CONTROL_MASK) != 0) {
1276 // move the cursor to beginning of the next word
1277 cursor = CursorNextWord (cursor);
1278 } else if ((modifiers & SHIFT_MASK) == 0 && anchor != cursor) {
1279 // set cursor at end of selection
1280 cursor = MAX (anchor, cursor);
1281 } else {
1282 // move the cursor forward one character
1283 if (buffer->text[cursor] == '\r' && buffer->text[cursor + 1] == '\n')
1284 cursor += 2;
1285 else if (cursor < buffer->len)
1286 cursor++;
1289 if ((modifiers & SHIFT_MASK) == 0) {
1290 // clobber the selection
1291 anchor = cursor;
1294 // check to see if selection has changed
1295 if (selection_anchor != anchor || selection_cursor != cursor) {
1296 SetSelectionStart (MIN (anchor, cursor));
1297 SetSelectionLength (abs (cursor - anchor));
1298 selection_anchor = anchor;
1299 selection_cursor = cursor;
1300 emit |= SELECTION_CHANGED;
1301 handled = true;
1304 return handled;
1307 bool
1308 TextBoxBase::KeyPressLeft (GdkModifierType modifiers)
1310 int anchor = selection_anchor;
1311 int cursor = selection_cursor;
1312 bool handled = false;
1314 if ((modifiers & ALT_MASK) != 0)
1315 return false;
1317 if ((modifiers & CONTROL_MASK) != 0) {
1318 // move the cursor to the beginning of the previous word
1319 cursor = CursorPrevWord (cursor);
1320 } else if ((modifiers & SHIFT_MASK) == 0 && anchor != cursor) {
1321 // set cursor at start of selection
1322 cursor = MIN (anchor, cursor);
1323 } else {
1324 // move the cursor backward one character
1325 if (cursor >= 2 && buffer->text[cursor - 2] == '\r' && buffer->text[cursor - 1] == '\n')
1326 cursor -= 2;
1327 else if (cursor > 0)
1328 cursor--;
1331 if ((modifiers & SHIFT_MASK) == 0) {
1332 // clobber the selection
1333 anchor = cursor;
1336 // check to see if selection has changed
1337 if (selection_anchor != anchor || selection_cursor != cursor) {
1338 SetSelectionStart (MIN (anchor, cursor));
1339 SetSelectionLength (abs (cursor - anchor));
1340 selection_anchor = anchor;
1341 selection_cursor = cursor;
1342 emit |= SELECTION_CHANGED;
1343 handled = true;
1346 return handled;
1349 bool
1350 TextBoxBase::KeyPressUnichar (gunichar c)
1352 int length = abs (selection_cursor - selection_anchor);
1353 int start = MIN (selection_anchor, selection_cursor);
1354 int anchor = selection_anchor;
1355 int cursor = selection_cursor;
1356 TextBoxUndoAction *action;
1358 if ((max_length > 0 && buffer->len >= max_length) || ((c == '\r') && !accepts_return))
1359 return false;
1361 if (length > 0) {
1362 // replace the currently selected text
1363 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, c);
1364 undo->Push (action);
1365 redo->Clear ();
1367 buffer->Replace (start, length, &c, 1);
1368 } else {
1369 // insert the text at the cursor position
1370 TextBoxUndoActionInsert *insert = NULL;
1372 if ((action = undo->Peek ()) && action->type == TextBoxUndoActionTypeInsert) {
1373 insert = (TextBoxUndoActionInsert *) action;
1375 if (!insert->Insert (start, c))
1376 insert = NULL;
1379 if (!insert) {
1380 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, c);
1381 undo->Push (insert);
1384 redo->Clear ();
1386 buffer->Insert (start, c);
1389 emit |= TEXT_CHANGED;
1390 cursor = start + 1;
1391 anchor = cursor;
1393 // check to see if selection has changed
1394 if (selection_anchor != anchor || selection_cursor != cursor) {
1395 SetSelectionStart (MIN (anchor, cursor));
1396 SetSelectionLength (abs (cursor - anchor));
1397 selection_anchor = anchor;
1398 selection_cursor = cursor;
1399 emit |= SELECTION_CHANGED;
1401 return true;
1404 void
1405 TextBoxBase::BatchPush ()
1407 batch++;
1410 void
1411 TextBoxBase::BatchPop ()
1413 if (batch == 0) {
1414 g_warning ("TextBoxBase batch underflow");
1415 return;
1418 batch--;
1421 void
1422 TextBoxBase::SyncAndEmit (bool sync_text)
1424 if (batch != 0 || emit == NOTHING_CHANGED)
1425 return;
1427 if (sync_text && (emit & TEXT_CHANGED))
1428 SyncText ();
1430 if (emit & SELECTION_CHANGED)
1431 SyncSelectedText ();
1433 if (IsLoaded ()) {
1434 // eliminate events that we can't emit
1435 emit &= events_mask;
1437 if (emit & TEXT_CHANGED)
1438 EmitTextChanged ();
1440 if (emit & SELECTION_CHANGED)
1441 EmitSelectionChanged ();
1444 emit = NOTHING_CHANGED;
1447 void
1448 TextBoxBase::Paste (GtkClipboard *clipboard, const char *str)
1450 int length = abs (selection_cursor - selection_anchor);
1451 int start = MIN (selection_anchor, selection_cursor);
1452 TextBoxUndoAction *action;
1453 gunichar *text;
1454 glong len, i;
1456 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1457 return;
1459 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1460 // paste cannot exceed MaxLength
1461 len = max_length - (buffer->len - length);
1462 if (len > 0)
1463 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1464 else
1465 len = 0;
1466 text[len] = '\0';
1469 if (!multiline) {
1470 // only paste the content of the first line
1471 for (i = 0; i < len; i++) {
1472 if (text[i] == '\r' || text[i] == '\n' || text[i] == 0x2028) {
1473 text = (gunichar *) g_realloc (text, UNICODE_LEN (i + 1));
1474 text[i] = '\0';
1475 len = i;
1476 break;
1481 ResetIMContext ();
1483 if (length > 0) {
1484 // replace the currently selected text
1485 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, len);
1487 buffer->Replace (start, length, text, len);
1488 } else if (len > 0) {
1489 // insert the text at the cursor position
1490 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, len, true);
1492 buffer->Insert (start, text, len);
1493 } else {
1494 g_free (text);
1495 return;
1498 undo->Push (action);
1499 redo->Clear ();
1500 g_free (text);
1502 emit |= TEXT_CHANGED;
1503 start += len;
1505 BatchPush ();
1506 SetSelectionStart (start);
1507 SetSelectionLength (0);
1508 BatchPop ();
1510 SyncAndEmit ();
1513 void
1514 TextBoxBase::paste (GtkClipboard *clipboard, const char *text, gpointer closure)
1516 ((TextBoxBase *) closure)->Paste (clipboard, text);
1519 void
1520 TextBoxBase::OnKeyDown (KeyEventArgs *args)
1522 GdkModifierType modifiers = (GdkModifierType) args->GetModifiers ();
1523 guint key = args->GetKeyVal ();
1524 GtkClipboard *clipboard;
1525 bool handled = false;
1527 if (args->IsModifier ())
1528 return;
1530 // set 'emit' to NOTHING_CHANGED so that we can figure out
1531 // what has chanegd after applying the changes that this
1532 // keypress will cause.
1533 emit = NOTHING_CHANGED;
1534 BatchPush ();
1536 switch (key) {
1537 case GDK_BackSpace:
1538 if (is_read_only)
1539 break;
1541 handled = KeyPressBackSpace (modifiers);
1542 break;
1543 case GDK_Delete:
1544 if (is_read_only)
1545 break;
1547 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == SHIFT_MASK) {
1548 // Shift+Delete => Cut
1549 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1550 if (selection_cursor != selection_anchor) {
1551 // copy selection to the clipboard and then cut
1552 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1556 SetSelectedText ("");
1557 handled = true;
1558 } else {
1559 handled = KeyPressDelete (modifiers);
1561 break;
1562 case GDK_Insert:
1563 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == SHIFT_MASK) {
1564 // Shift+Insert => Paste
1565 if (is_read_only)
1566 break;
1568 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1569 // paste clipboard contents to the buffer
1570 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1573 handled = true;
1574 } else if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == CONTROL_MASK) {
1575 // Control+Insert => Copy
1576 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1577 if (selection_cursor != selection_anchor) {
1578 // copy selection to the clipboard
1579 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1583 handled = true;
1585 break;
1586 case GDK_KP_Page_Down:
1587 case GDK_Page_Down:
1588 handled = KeyPressPageDown (modifiers);
1589 break;
1590 case GDK_KP_Page_Up:
1591 case GDK_Page_Up:
1592 handled = KeyPressPageUp (modifiers);
1593 break;
1594 case GDK_KP_Home:
1595 case GDK_Home:
1596 handled = KeyPressHome (modifiers);
1597 break;
1598 case GDK_KP_End:
1599 case GDK_End:
1600 handled = KeyPressEnd (modifiers);
1601 break;
1602 case GDK_KP_Right:
1603 case GDK_Right:
1604 handled = KeyPressRight (modifiers);
1605 break;
1606 case GDK_KP_Left:
1607 case GDK_Left:
1608 handled = KeyPressLeft (modifiers);
1609 break;
1610 case GDK_KP_Down:
1611 case GDK_Down:
1612 handled = KeyPressDown (modifiers);
1613 break;
1614 case GDK_KP_Up:
1615 case GDK_Up:
1616 handled = KeyPressUp (modifiers);
1617 break;
1618 default:
1619 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == CONTROL_MASK) {
1620 switch (key) {
1621 case GDK_A:
1622 case GDK_a:
1623 // Ctrl+A => Select All
1624 handled = true;
1625 SelectAll ();
1626 break;
1627 case GDK_C:
1628 case GDK_c:
1629 // Ctrl+C => Copy
1630 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1631 if (selection_cursor != selection_anchor) {
1632 // copy selection to the clipboard
1633 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1637 handled = true;
1638 break;
1639 case GDK_X:
1640 case GDK_x:
1641 // Ctrl+X => Cut
1642 if (is_read_only)
1643 break;
1645 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1646 if (selection_cursor != selection_anchor) {
1647 // copy selection to the clipboard and then cut
1648 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1652 SetSelectedText ("");
1653 handled = true;
1654 break;
1655 case GDK_V:
1656 case GDK_v:
1657 // Ctrl+V => Paste
1658 if (is_read_only)
1659 break;
1661 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1662 // paste clipboard contents to the buffer
1663 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1666 handled = true;
1667 break;
1668 case GDK_Y:
1669 case GDK_y:
1670 // Ctrl+Y => Redo
1671 if (!is_read_only) {
1672 handled = true;
1673 Redo ();
1675 break;
1676 case GDK_Z:
1677 case GDK_z:
1678 // Ctrl+Z => Undo
1679 if (!is_read_only) {
1680 handled = true;
1681 Undo ();
1683 break;
1684 default:
1685 // unhandled Control commands
1686 break;
1689 break;
1692 if (handled) {
1693 args->SetHandled (handled);
1694 ResetIMContext ();
1697 BatchPop ();
1699 SyncAndEmit ();
1702 void
1703 TextBoxBase::OnCharacterKeyDown (KeyEventArgs *args)
1705 guint key = args->GetKeyVal ();
1706 bool handled = false;
1707 gunichar c;
1709 if (!is_read_only && gtk_im_context_filter_keypress (im_ctx, args->GetEvent ())) {
1710 args->SetHandled (true);
1711 need_im_reset = true;
1712 return;
1715 if (is_read_only || args->IsModifier ())
1716 return;
1718 // set 'emit' to NOTHING_CHANGED so that we can figure out
1719 // what has chanegd after applying the changes that this
1720 // keypress will cause.
1721 emit = NOTHING_CHANGED;
1722 BatchPush ();
1724 switch (key) {
1725 case GDK_Return:
1726 handled = KeyPressUnichar ('\r');
1727 break;
1728 default:
1729 if ((args->GetModifiers () & (CONTROL_MASK | ALT_MASK)) == 0) {
1730 // normal character input
1731 if ((c = args->GetUnicode ()))
1732 handled = KeyPressUnichar (c);
1734 break;
1737 if (handled)
1738 args->SetHandled (handled);
1740 BatchPop ();
1742 SyncAndEmit ();
1745 void
1746 TextBoxBase::OnKeyUp (KeyEventArgs *args)
1748 if (!is_read_only) {
1749 if (gtk_im_context_filter_keypress (im_ctx, args->GetEvent ()))
1750 need_im_reset = true;
1752 args->SetHandled (true);
1756 bool
1757 TextBoxBase::DeleteSurrounding (int offset, int n_chars)
1759 const char *delete_start, *delete_end;
1760 const char *text = GetActualText ();
1761 int anchor = selection_anchor;
1762 int cursor = selection_cursor;
1763 TextBoxUndoAction *action;
1764 int start, length;
1766 if (is_read_only)
1767 return true;
1769 // get the utf-8 pointers so that we can use them to get gunichar offsets
1770 delete_start = g_utf8_offset_to_pointer (text, selection_cursor) + offset;
1771 delete_end = delete_start + n_chars;
1773 // get the character length/start index
1774 length = g_utf8_pointer_to_offset (delete_start, delete_end);
1775 start = g_utf8_pointer_to_offset (text, delete_start);
1777 if (length > 0) {
1778 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1779 undo->Push (action);
1780 redo->Clear ();
1782 buffer->Cut (start, length);
1783 emit |= TEXT_CHANGED;
1784 anchor = start;
1785 cursor = start;
1788 BatchPush ();
1790 // check to see if selection has changed
1791 if (selection_anchor != anchor || selection_cursor != cursor) {
1792 SetSelectionStart (MIN (anchor, cursor));
1793 SetSelectionLength (abs (cursor - anchor));
1794 selection_anchor = anchor;
1795 selection_cursor = cursor;
1796 emit |= SELECTION_CHANGED;
1799 BatchPop ();
1801 SyncAndEmit ();
1803 return true;
1806 gboolean
1807 TextBoxBase::delete_surrounding (GtkIMContext *context, int offset, int n_chars, gpointer user_data)
1809 return ((TextBoxBase *) user_data)->DeleteSurrounding (offset, n_chars);
1812 bool
1813 TextBoxBase::RetrieveSurrounding ()
1815 const char *text = GetActualText ();
1816 const char *cursor = g_utf8_offset_to_pointer (text, selection_cursor);
1818 gtk_im_context_set_surrounding (im_ctx, text, -1, cursor - text);
1820 return true;
1823 gboolean
1824 TextBoxBase::retrieve_surrounding (GtkIMContext *context, gpointer user_data)
1826 return ((TextBoxBase *) user_data)->RetrieveSurrounding ();
1829 void
1830 TextBoxBase::Commit (const char *str)
1832 int length = abs (selection_cursor - selection_anchor);
1833 int start = MIN (selection_anchor, selection_cursor);
1834 TextBoxUndoAction *action;
1835 int anchor, cursor;
1836 gunichar *text;
1837 glong len, i;
1839 if (is_read_only)
1840 return;
1842 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1843 return;
1845 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1846 // paste cannot exceed MaxLength
1847 len = max_length - (buffer->len - length);
1848 if (len > 0)
1849 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1850 else
1851 len = 0;
1852 text[len] = '\0';
1855 if (!multiline) {
1856 // only paste the content of the first line
1857 for (i = 0; i < len; i++) {
1858 if (g_unichar_type (text[i]) == G_UNICODE_LINE_SEPARATOR) {
1859 text = (gunichar *) g_realloc (text, UNICODE_LEN (i + 1));
1860 text[i] = '\0';
1861 len = i;
1862 break;
1867 if (length > 0) {
1868 // replace the currently selected text
1869 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, len);
1870 undo->Push (action);
1871 redo->Clear ();
1873 buffer->Replace (start, length, text, len);
1874 } else if (len > 0) {
1875 // insert the text at the cursor position
1876 TextBoxUndoActionInsert *insert = NULL;
1878 buffer->Insert (start, text, len);
1880 if ((action = undo->Peek ()) && action->type == TextBoxUndoActionTypeInsert) {
1881 insert = (TextBoxUndoActionInsert *) action;
1883 if (!insert->Insert (start, text, len))
1884 insert = NULL;
1887 if (!insert) {
1888 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, len);
1889 undo->Push (insert);
1892 redo->Clear ();
1893 } else {
1894 g_free (text);
1895 return;
1898 emit = TEXT_CHANGED;
1899 cursor = start + len;
1900 anchor = cursor;
1901 g_free (text);
1903 BatchPush ();
1905 // check to see if selection has changed
1906 if (selection_anchor != anchor || selection_cursor != cursor) {
1907 SetSelectionStart (MIN (anchor, cursor));
1908 SetSelectionLength (abs (cursor - anchor));
1909 selection_anchor = anchor;
1910 selection_cursor = cursor;
1911 emit |= SELECTION_CHANGED;
1914 BatchPop ();
1916 SyncAndEmit ();
1919 void
1920 TextBoxBase::commit (GtkIMContext *context, const char *str, gpointer user_data)
1922 ((TextBoxBase *) user_data)->Commit (str);
1925 void
1926 TextBoxBase::ResetIMContext ()
1928 if (need_im_reset) {
1929 gtk_im_context_reset (im_ctx);
1930 need_im_reset = false;
1934 void
1935 TextBoxBase::OnMouseLeftButtonDown (MouseEventArgs *args)
1937 double x, y;
1938 int cursor;
1940 args->SetHandled (true);
1941 Focus ();
1943 if (view) {
1944 args->GetPosition (view, &x, &y);
1946 cursor = view->GetCursorFromXY (x, y);
1948 ResetIMContext ();
1950 // Single-Click: cursor placement
1951 captured = CaptureMouse ();
1952 selecting = true;
1954 BatchPush ();
1955 emit = NOTHING_CHANGED;
1956 SetSelectionStart (cursor);
1957 SetSelectionLength (0);
1958 BatchPop ();
1960 SyncAndEmit ();
1964 void
1965 TextBoxBase::mouse_left_button_down (EventObject *sender, EventArgs *args, gpointer closure)
1967 ((TextBoxBase *) closure)->OnMouseLeftButtonDown ((MouseEventArgs *) args);
1970 void
1971 TextBoxBase::OnMouseLeftButtonMultiClick (MouseEventArgs *args)
1973 int cursor, start, end;
1974 double x, y;
1976 args->SetHandled (true);
1978 if (view) {
1979 args->GetPosition (view, &x, &y);
1981 cursor = view->GetCursorFromXY (x, y);
1983 ResetIMContext ();
1985 if (args->GetClickCount () == 3) {
1986 // Note: Silverlight doesn't implement this, but to
1987 // be consistent with other TextEntry-type
1988 // widgets in Gtk+, we will.
1990 // Triple-Click: select the line
1991 if (captured)
1992 ReleaseMouseCapture ();
1993 start = CursorLineBegin (cursor);
1994 end = CursorLineEnd (cursor, true);
1995 selecting = false;
1996 captured = false;
1997 } else {
1998 // Double-Click: select the word
1999 if (captured)
2000 ReleaseMouseCapture ();
2001 start = CursorPrevWord (cursor);
2002 end = CursorNextWord (cursor);
2003 selecting = false;
2004 captured = false;
2007 BatchPush ();
2008 emit = NOTHING_CHANGED;
2009 SetSelectionStart (start);
2010 SetSelectionLength (end - start);
2011 BatchPop ();
2013 SyncAndEmit ();
2017 void
2018 TextBoxBase::mouse_left_button_multi_click (EventObject *sender, EventArgs *args, gpointer closure)
2020 ((TextBoxBase *) closure)->OnMouseLeftButtonMultiClick ((MouseEventArgs *) args);
2023 void
2024 TextBoxBase::OnMouseLeftButtonUp (MouseEventArgs *args)
2026 if (captured)
2027 ReleaseMouseCapture ();
2029 args->SetHandled (true);
2030 selecting = false;
2031 captured = false;
2034 void
2035 TextBoxBase::mouse_left_button_up (EventObject *sender, EventArgs *args, gpointer closure)
2037 ((TextBoxBase *) closure)->OnMouseLeftButtonUp ((MouseEventArgs *) args);
2040 void
2041 TextBoxBase::OnMouseMove (MouseEventArgs *args)
2043 int anchor = selection_anchor;
2044 int cursor = selection_cursor;
2045 double x, y;
2047 if (selecting) {
2048 args->GetPosition (view, &x, &y);
2049 args->SetHandled (true);
2051 cursor = view->GetCursorFromXY (x, y);
2053 BatchPush ();
2054 emit = NOTHING_CHANGED;
2055 SetSelectionStart (MIN (anchor, cursor));
2056 SetSelectionLength (abs (cursor - anchor));
2057 selection_anchor = anchor;
2058 selection_cursor = cursor;
2059 BatchPop ();
2061 SyncAndEmit ();
2065 void
2066 TextBoxBase::mouse_move (EventObject *sender, EventArgs *args, gpointer closure)
2068 ((TextBoxBase *) closure)->OnMouseMove ((MouseEventArgs *) args);
2071 void
2072 TextBoxBase::OnFocusOut (EventArgs *args)
2074 BatchPush ();
2075 emit = NOTHING_CHANGED;
2076 SetSelectionStart (selection_cursor);
2077 SetSelectionLength (0);
2078 BatchPop ();
2080 SyncAndEmit ();
2082 focused = false;
2084 if (view)
2085 view->OnFocusOut ();
2087 if (!is_read_only) {
2088 gtk_im_context_focus_out (im_ctx);
2089 need_im_reset = true;
2093 void
2094 TextBoxBase::focus_out (EventObject *sender, EventArgs *args, gpointer closure)
2096 ((TextBoxBase *) closure)->OnFocusOut (args);
2099 void
2100 TextBoxBase::OnFocusIn (EventArgs *args)
2102 focused = true;
2104 if (view)
2105 view->OnFocusIn ();
2107 if (!is_read_only) {
2108 gtk_im_context_focus_in (im_ctx);
2109 need_im_reset = true;
2113 void
2114 TextBoxBase::focus_in (EventObject *sender, EventArgs *args, gpointer closure)
2116 ((TextBoxBase *) closure)->OnFocusIn (args);
2119 void
2120 TextBoxBase::EmitCursorPositionChanged (double height, double x, double y)
2122 Emit (TextBoxBase::CursorPositionChangedEvent, new CursorPositionChangedEventArgs (height, x, y));
2125 void
2126 TextBoxBase::DownloaderComplete (Downloader *downloader)
2128 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2129 char *resource, *filename;
2130 InternalDownloader *idl;
2131 const char *path;
2132 Uri *uri;
2134 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2135 if (!(filename = downloader->GetDownloadedFilename (NULL)))
2136 return;
2138 g_free (filename);
2140 if (!(idl = downloader->GetInternalDownloader ()))
2141 return;
2143 if (!(idl->GetObjectType () == Type::FILEDOWNLOADER))
2144 return;
2146 uri = downloader->GetUri ();
2148 // If the downloaded file was a zip file, this'll get the path to the
2149 // extracted zip directory, else it will simply be the path to the
2150 // downloaded file.
2151 if (!(path = ((FileDownloader *) idl)->GetUnzippedPath ()))
2152 return;
2154 resource = uri->ToString ((UriToStringFlags) (UriHidePasswd | UriHideQuery | UriHideFragment));
2155 manager->AddResource (resource, path);
2156 g_free (resource);
2158 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont, NULL));
2161 void
2162 TextBoxBase::downloader_complete (EventObject *sender, EventArgs *calldata, gpointer closure)
2164 ((TextBoxBase *) closure)->DownloaderComplete ((Downloader *) sender);
2167 void
2168 TextBoxBase::AddFontSource (Downloader *downloader)
2170 downloader->AddHandler (downloader->CompletedEvent, downloader_complete, this);
2171 g_ptr_array_add (downloaders, downloader);
2172 downloader->ref ();
2174 if (downloader->Started () || downloader->Completed ()) {
2175 if (downloader->Completed ())
2176 DownloaderComplete (downloader);
2177 } else {
2178 // This is what actually triggers the download
2179 downloader->Send ();
2183 void
2184 TextBoxBase::AddFontResource (const char *resource)
2186 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2187 Application *application = Application::GetCurrent ();
2188 Downloader *downloader;
2189 Surface *surface;
2190 char *path;
2191 Uri *uri;
2193 uri = new Uri ();
2195 if (!application || !uri->Parse (resource) || !(path = application->GetResourceAsPath (GetResourceBase(), uri))) {
2196 if ((surface = GetSurface ()) && (downloader = surface->CreateDownloader ())) {
2197 downloader->Open ("GET", resource, FontPolicy);
2198 AddFontSource (downloader);
2199 downloader->unref ();
2202 delete uri;
2204 return;
2207 manager->AddResource (resource, path);
2208 g_free (path);
2209 delete uri;
2212 void
2213 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2215 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2217 if (args->GetId () == Control::FontFamilyProperty) {
2218 FontFamily *family = args->GetNewValue () ? args->GetNewValue ()->AsFontFamily () : NULL;
2219 char **families, *fragment;
2220 int i;
2222 CleanupDownloaders ();
2224 if (family && family->source) {
2225 families = g_strsplit (family->source, ",", -1);
2226 for (i = 0; families[i]; i++) {
2227 g_strstrip (families[i]);
2228 if ((fragment = strchr (families[i], '#'))) {
2229 // the first portion of this string is the resource name...
2230 *fragment = '\0';
2231 AddFontResource (families[i]);
2234 g_strfreev (families);
2237 font->SetFamily (family ? family->source : NULL);
2238 changed = TextBoxModelChangedFont;
2239 } else if (args->GetId () == Control::FontSizeProperty) {
2240 double size = args->GetNewValue()->AsDouble ();
2241 changed = TextBoxModelChangedFont;
2242 font->SetSize (size);
2243 } else if (args->GetId () == Control::FontStretchProperty) {
2244 FontStretches stretch = args->GetNewValue()->AsFontStretch()->stretch;
2245 changed = TextBoxModelChangedFont;
2246 font->SetStretch (stretch);
2247 } else if (args->GetId () == Control::FontStyleProperty) {
2248 FontStyles style = args->GetNewValue()->AsFontStyle ()->style;
2249 changed = TextBoxModelChangedFont;
2250 font->SetStyle (style);
2251 } else if (args->GetId () == Control::FontWeightProperty) {
2252 FontWeights weight = args->GetNewValue()->AsFontWeight ()->weight;
2253 changed = TextBoxModelChangedFont;
2254 font->SetWeight (weight);
2255 } else if (args->GetId () == FrameworkElement::MinHeightProperty) {
2256 // pass this along to our TextBoxView
2257 if (view)
2258 view->SetMinHeight (args->GetNewValue ()->AsDouble ());
2259 } else if (args->GetId () == FrameworkElement::MaxHeightProperty) {
2260 // pass this along to our TextBoxView
2261 if (view)
2262 view->SetMaxHeight (args->GetNewValue ()->AsDouble ());
2263 } else if (args->GetId () == FrameworkElement::MinWidthProperty) {
2264 // pass this along to our TextBoxView
2265 if (view)
2266 view->SetMinWidth (args->GetNewValue ()->AsDouble ());
2267 } else if (args->GetId () == FrameworkElement::MaxWidthProperty) {
2268 // pass this along to our TextBoxView
2269 if (view)
2270 view->SetMaxWidth (args->GetNewValue ()->AsDouble ());
2271 } else if (args->GetId () == FrameworkElement::HeightProperty) {
2272 // pass this along to our TextBoxView
2273 if (view)
2274 view->SetHeight (args->GetNewValue ()->AsDouble ());
2275 } else if (args->GetId () == FrameworkElement::WidthProperty) {
2276 // pass this along to our TextBoxView
2277 if (view)
2278 view->SetWidth (args->GetNewValue ()->AsDouble ());
2281 if (changed != TextBoxModelChangedNothing)
2282 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2284 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE) {
2285 Control::OnPropertyChanged (args, error);
2286 return;
2289 NotifyListenersOfPropertyChange (args, error);
2292 void
2293 TextBoxBase::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2295 if (prop && (prop->GetId () == Control::BackgroundProperty ||
2296 prop->GetId () == Control::ForegroundProperty)) {
2297 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
2298 Invalidate ();
2301 if (prop->GetOwnerType () != Type::TEXTBOXBASE)
2302 Control::OnSubPropertyChanged (prop, obj, subobj_args);
2305 void
2306 TextBoxBase::OnApplyTemplate ()
2308 contentElement = GetTemplateChild ("ContentElement");
2310 if (contentElement == NULL) {
2311 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2312 Control::OnApplyTemplate ();
2313 return;
2316 view = new TextBoxView ();
2317 view->SetTextBox (this);
2319 view->SetMinHeight (GetMinHeight ());
2320 view->SetMaxHeight (GetMaxHeight ());
2321 view->SetMinWidth (GetMinWidth ());
2322 view->SetMaxWidth (GetMaxWidth ());
2323 view->SetHeight (GetHeight ());
2324 view->SetWidth (GetWidth ());
2326 // Insert our TextBoxView
2327 if (contentElement->Is (Type::CONTENTCONTROL)) {
2328 ContentControl *control = (ContentControl *) contentElement;
2330 control->SetValue (ContentControl::ContentProperty, Value (view));
2331 } else if (contentElement->Is (Type::BORDER)) {
2332 Border *border = (Border *) contentElement;
2334 border->SetValue (Border::ChildProperty, Value (view));
2335 } else if (contentElement->Is (Type::PANEL)) {
2336 DependencyObjectCollection *children = ((Panel *) contentElement)->GetChildren ();
2338 children->Add (view);
2339 } else {
2340 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentELement of type %s",
2341 contentElement->GetType ()->GetName ());
2342 view->unref ();
2343 view = NULL;
2346 Control::OnApplyTemplate ();
2349 void
2350 TextBoxBase::ClearSelection (int start)
2352 BatchPush ();
2353 SetSelectionStart (start);
2354 SetSelectionLength (0);
2355 BatchPop ();
2358 bool
2359 TextBoxBase::SelectWithError (int start, int length, MoonError *error)
2361 if (start < 0) {
2362 MoonError::FillIn (error, MoonError::ARGUMENT, "selection start must be >= 0");
2363 return false;
2366 if (length < 0) {
2367 MoonError::FillIn (error, MoonError::ARGUMENT, "selection length must be >= 0");
2368 return false;
2371 if (start > buffer->len)
2372 start = buffer->len;
2374 if (length > (buffer->len - start))
2375 length = (buffer->len - start);
2377 BatchPush ();
2378 SetSelectionStart (start);
2379 SetSelectionLength (length);
2380 BatchPop ();
2382 ResetIMContext ();
2384 SyncAndEmit ();
2386 return true;
2389 void
2390 TextBoxBase::SelectAll ()
2392 SelectWithError (0, buffer->len, NULL);
2395 bool
2396 TextBoxBase::CanUndo ()
2398 return !undo->IsEmpty ();
2401 bool
2402 TextBoxBase::CanRedo ()
2404 return !redo->IsEmpty ();
2407 void
2408 TextBoxBase::Undo ()
2410 TextBoxUndoActionReplace *replace;
2411 TextBoxUndoActionInsert *insert;
2412 TextBoxUndoActionDelete *dele;
2413 TextBoxUndoAction *action;
2414 int anchor, cursor;
2416 if (undo->IsEmpty ())
2417 return;
2419 action = undo->Pop ();
2420 redo->Push (action);
2422 switch (action->type) {
2423 case TextBoxUndoActionTypeInsert:
2424 insert = (TextBoxUndoActionInsert *) action;
2426 buffer->Cut (insert->start, insert->length);
2427 anchor = action->selection_anchor;
2428 cursor = action->selection_cursor;
2429 break;
2430 case TextBoxUndoActionTypeDelete:
2431 dele = (TextBoxUndoActionDelete *) action;
2433 buffer->Insert (dele->start, dele->text, dele->length);
2434 anchor = action->selection_anchor;
2435 cursor = action->selection_cursor;
2436 break;
2437 case TextBoxUndoActionTypeReplace:
2438 replace = (TextBoxUndoActionReplace *) action;
2440 buffer->Cut (replace->start, replace->inlen);
2441 buffer->Insert (replace->start, replace->deleted, replace->length);
2442 anchor = action->selection_anchor;
2443 cursor = action->selection_cursor;
2444 break;
2447 BatchPush ();
2448 SetSelectionStart (MIN (anchor, cursor));
2449 SetSelectionLength (abs (cursor - anchor));
2450 emit = TEXT_CHANGED | SELECTION_CHANGED;
2451 selection_anchor = anchor;
2452 selection_cursor = cursor;
2453 BatchPop ();
2455 SyncAndEmit ();
2458 void
2459 TextBoxBase::Redo ()
2461 TextBoxUndoActionReplace *replace;
2462 TextBoxUndoActionInsert *insert;
2463 TextBoxUndoActionDelete *dele;
2464 TextBoxUndoAction *action;
2465 int anchor, cursor;
2467 if (redo->IsEmpty ())
2468 return;
2470 action = redo->Pop ();
2471 undo->Push (action);
2473 switch (action->type) {
2474 case TextBoxUndoActionTypeInsert:
2475 insert = (TextBoxUndoActionInsert *) action;
2477 buffer->Insert (insert->start, insert->buffer->text, insert->buffer->len);
2478 anchor = cursor = insert->start + insert->buffer->len;
2479 break;
2480 case TextBoxUndoActionTypeDelete:
2481 dele = (TextBoxUndoActionDelete *) action;
2483 buffer->Cut (dele->start, dele->length);
2484 anchor = cursor = dele->start;
2485 break;
2486 case TextBoxUndoActionTypeReplace:
2487 replace = (TextBoxUndoActionReplace *) action;
2489 buffer->Cut (replace->start, replace->length);
2490 buffer->Insert (replace->start, replace->inserted, replace->inlen);
2491 anchor = cursor = replace->start + replace->inlen;
2492 break;
2495 BatchPush ();
2496 SetSelectionStart (MIN (anchor, cursor));
2497 SetSelectionLength (abs (cursor - anchor));
2498 emit = TEXT_CHANGED | SELECTION_CHANGED;
2499 selection_anchor = anchor;
2500 selection_cursor = cursor;
2501 BatchPop ();
2503 SyncAndEmit ();
2508 // TextBoxDynamicPropertyValueProvider
2511 class TextBoxDynamicPropertyValueProvider : public PropertyValueProvider {
2512 Value *selection_background;
2513 Value *selection_foreground;
2515 public:
2516 TextBoxDynamicPropertyValueProvider (DependencyObject *obj, PropertyPrecedence precedence) : PropertyValueProvider (obj, precedence)
2518 selection_background = NULL;
2519 selection_foreground = NULL;
2522 virtual ~TextBoxDynamicPropertyValueProvider ()
2524 delete selection_background;
2525 delete selection_foreground;
2528 virtual Value *GetPropertyValue (DependencyProperty *property)
2530 if (property->GetId () == TextBox::SelectionBackgroundProperty) {
2531 return selection_background;
2532 } else if (property->GetId () == TextBox::SelectionForegroundProperty) {
2533 return selection_foreground;
2536 return NULL;
2539 void InitializeSelectionBrushes ()
2541 if (!selection_background)
2542 selection_background = Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2544 if (!selection_foreground)
2545 selection_foreground = Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2551 // TextBox
2554 TextBox::TextBox ()
2556 providers[PropertyPrecedence_DynamicValue] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2558 Initialize (Type::TEXTBOX, "System.Windows.Controls.TextBox");
2559 events_mask = TEXT_CHANGED | SELECTION_CHANGED;
2560 multiline = true;
2563 void
2564 TextBox::EmitSelectionChanged ()
2566 EmitAsync (TextBox::SelectionChangedEvent, new RoutedEventArgs ());
2569 void
2570 TextBox::EmitTextChanged ()
2572 EmitAsync (TextBox::TextChangedEvent, new TextChangedEventArgs ());
2575 void
2576 TextBox::SyncSelectedText ()
2578 if (selection_cursor != selection_anchor) {
2579 int length = abs (selection_cursor - selection_anchor);
2580 int start = MIN (selection_anchor, selection_cursor);
2581 char *text;
2583 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2585 setvalue = false;
2586 SetValue (TextBox::SelectedTextProperty, Value (text, true));
2587 setvalue = true;
2588 } else {
2589 setvalue = false;
2590 SetValue (TextBox::SelectedTextProperty, Value (""));
2591 setvalue = true;
2595 void
2596 TextBox::SyncText ()
2598 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
2600 setvalue = false;
2601 SetValue (TextBox::TextProperty, Value (text, true));
2602 setvalue = true;
2605 void
2606 TextBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2608 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2609 DependencyProperty *prop;
2610 int start, length;
2612 if (args->GetId () == TextBox::AcceptsReturnProperty) {
2613 // update accepts_return state
2614 accepts_return = args->GetNewValue ()->AsBool ();
2615 } else if (args->GetId () == TextBox::CaretBrushProperty) {
2616 // FIXME: if we want to be perfect, we could invalidate the
2617 // blinking cursor rect if it is active... but is it that
2618 // important?
2619 } else if (args->GetId () == TextBox::FontSourceProperty) {
2620 FontSource *source = args->GetNewValue () ? args->GetNewValue ()->AsFontSource () : NULL;
2621 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2623 // FIXME: ideally we'd remove the old item from the cache (or,
2624 // rather, 'unref' it since some other textblocks/boxes might
2625 // still be using it).
2627 g_free (font_source);
2629 if (source && source->stream)
2630 font_source = manager->AddResource (source->stream);
2631 else
2632 font_source = NULL;
2634 changed = TextBoxModelChangedFont;
2635 font->SetSource (font_source);
2636 } else if (args->GetId () == TextBox::IsReadOnlyProperty) {
2637 // update is_read_only state
2638 is_read_only = args->GetNewValue ()->AsBool ();
2640 if (focused) {
2641 if (is_read_only) {
2642 ResetIMContext ();
2643 gtk_im_context_focus_out (im_ctx);
2644 } else {
2645 gtk_im_context_focus_in (im_ctx);
2648 } else if (args->GetId () == TextBox::MaxLengthProperty) {
2649 // update max_length state
2650 max_length = args->GetNewValue ()->AsInt32 ();
2651 } else if (args->GetId () == TextBox::SelectedTextProperty) {
2652 if (setvalue) {
2653 Value *value = args->GetNewValue ();
2654 const char *str = value && value->AsString () ? value->AsString () : "";
2655 TextBoxUndoAction *action = NULL;
2656 gunichar *text;
2657 glong textlen;
2659 length = abs (selection_cursor - selection_anchor);
2660 start = MIN (selection_anchor, selection_cursor);
2662 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2663 if (length > 0) {
2664 // replace the currently selected text
2665 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, textlen);
2667 buffer->Replace (start, length, text, textlen);
2668 } else if (textlen > 0) {
2669 // insert the text at the cursor
2670 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, textlen);
2672 buffer->Insert (start, text, textlen);
2675 g_free (text);
2677 if (action != NULL) {
2678 emit |= TEXT_CHANGED;
2679 undo->Push (action);
2680 redo->Clear ();
2682 ClearSelection (start + textlen);
2683 ResetIMContext ();
2685 SyncAndEmit ();
2687 } else {
2688 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str);
2691 } else if (args->GetId () == TextBox::SelectionStartProperty) {
2692 length = abs (selection_cursor - selection_anchor);
2693 start = args->GetNewValue ()->AsInt32 ();
2695 if (start > buffer->len) {
2696 // clamp the selection start offset to a valid value
2697 SetSelectionStart (buffer->len);
2698 return;
2701 if (start + length > buffer->len) {
2702 // clamp the selection length to a valid value
2703 BatchPush ();
2704 length = buffer->len - start;
2705 SetSelectionLength (length);
2706 BatchPop ();
2709 // SelectionStartProperty is marked as AlwaysChange -
2710 // if the value hasn't actually changed, then we do
2711 // not want to emit the TextBoxModelChanged event.
2712 if (selection_anchor != start) {
2713 changed = TextBoxModelChangedSelection;
2714 have_offset = false;
2717 // When set programatically, anchor is always the
2718 // start and cursor is always the end.
2719 selection_cursor = start + length;
2720 selection_anchor = start;
2722 emit |= SELECTION_CHANGED;
2724 SyncAndEmit ();
2725 } else if (args->GetId () == TextBox::SelectionLengthProperty) {
2726 start = MIN (selection_anchor, selection_cursor);
2727 length = args->GetNewValue ()->AsInt32 ();
2729 if (start + length > buffer->len) {
2730 // clamp the selection length to a valid value
2731 length = buffer->len - start;
2732 SetSelectionLength (length);
2733 return;
2736 // SelectionLengthProperty is marked as AlwaysChange -
2737 // if the value hasn't actually changed, then we do
2738 // not want to emit the TextBoxModelChanged event.
2739 if (selection_cursor != start + length) {
2740 changed = TextBoxModelChangedSelection;
2741 have_offset = false;
2744 // When set programatically, anchor is always the
2745 // start and cursor is always the end.
2746 selection_cursor = start + length;
2747 selection_anchor = start;
2749 emit |= SELECTION_CHANGED;
2751 SyncAndEmit ();
2752 } else if (args->GetId () == TextBox::SelectionBackgroundProperty) {
2753 changed = TextBoxModelChangedBrush;
2754 } else if (args->GetId () == TextBox::SelectionForegroundProperty) {
2755 changed = TextBoxModelChangedBrush;
2756 } else if (args->GetId () == TextBox::TextProperty) {
2757 if (setvalue) {
2758 Value *value = args->GetNewValue ();
2759 const char *str = value && value->AsString () ? value->AsString () : "";
2760 TextBoxUndoAction *action;
2761 gunichar *text;
2762 glong textlen;
2764 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2765 if (buffer->len > 0) {
2766 // replace the current text
2767 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, 0, buffer->len, text, textlen);
2769 buffer->Replace (0, buffer->len, text, textlen);
2770 } else {
2771 // insert the text
2772 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
2774 buffer->Insert (0, text, textlen);
2777 undo->Push (action);
2778 redo->Clear ();
2779 g_free (text);
2781 emit |= TEXT_CHANGED;
2782 ClearSelection (0);
2783 ResetIMContext ();
2785 SyncAndEmit (value && !value->GetIsNull ());
2786 } else {
2787 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str);
2791 changed = TextBoxModelChangedText;
2792 } else if (args->GetId () == TextBox::TextAlignmentProperty) {
2793 changed = TextBoxModelChangedTextAlignment;
2794 } else if (args->GetId () == TextBox::TextWrappingProperty) {
2795 changed = TextBoxModelChangedTextWrapping;
2796 } else if (args->GetId () == TextBox::HorizontalScrollBarVisibilityProperty) {
2797 // XXX more crap because these aren't templatebound.
2798 if (contentElement) {
2799 if ((prop = contentElement->GetDependencyProperty ("VerticalScrollBarVisibility")))
2800 contentElement->SetValue (prop, GetValue (TextBox::VerticalScrollBarVisibilityProperty));
2803 Invalidate ();
2804 } else if (args->GetId () == TextBox::VerticalScrollBarVisibilityProperty) {
2805 // XXX more crap because these aren't templatebound.
2806 if (contentElement) {
2807 if ((prop = contentElement->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2808 contentElement->SetValue (prop, GetValue (TextBox::HorizontalScrollBarVisibilityProperty));
2811 Invalidate ();
2814 if (changed != TextBoxModelChangedNothing)
2815 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2817 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
2818 TextBoxBase::OnPropertyChanged (args, error);
2819 return;
2822 NotifyListenersOfPropertyChange (args, error);
2825 void
2826 TextBox::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2828 if (prop && (prop->GetId () == TextBox::SelectionBackgroundProperty ||
2829 prop->GetId () == TextBox::SelectionForegroundProperty)) {
2830 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
2831 Invalidate ();
2834 if (prop->GetOwnerType () != Type::TEXTBOX)
2835 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
2838 void
2839 TextBox::OnApplyTemplate ()
2841 DependencyProperty *prop;
2843 TextBoxBase::OnApplyTemplate ();
2845 if (!contentElement)
2846 return;
2848 // XXX LAME these should be template bindings in the textbox template.
2849 if ((prop = contentElement->GetDependencyProperty ("VerticalScrollBarVisibility")))
2850 contentElement->SetValue (prop, GetValue (TextBox::VerticalScrollBarVisibilityProperty));
2852 if ((prop = contentElement->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2853 contentElement->SetValue (prop, GetValue (TextBox::HorizontalScrollBarVisibilityProperty));
2858 // PasswordBox
2862 // PasswordBoxDynamicPropertyValueProvider
2865 class PasswordBoxDynamicPropertyValueProvider : public PropertyValueProvider {
2866 Value *selection_background;
2867 Value *selection_foreground;
2869 public:
2870 PasswordBoxDynamicPropertyValueProvider (DependencyObject *obj, PropertyPrecedence precedence) : PropertyValueProvider (obj, precedence)
2872 selection_background = NULL;
2873 selection_foreground = NULL;
2876 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2878 delete selection_background;
2879 delete selection_foreground;
2882 virtual Value *GetPropertyValue (DependencyProperty *property)
2884 if (property->GetId () == PasswordBox::SelectionBackgroundProperty) {
2885 return selection_background;
2886 } else if (property->GetId () == PasswordBox::SelectionForegroundProperty) {
2887 return selection_foreground;
2890 return NULL;
2893 void InitializeSelectionBrushes ()
2895 if (!selection_background)
2896 selection_background = Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2898 if (!selection_foreground)
2899 selection_foreground = Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2905 // PasswordBox
2908 PasswordBox::PasswordBox ()
2910 providers[PropertyPrecedence_DynamicValue] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2912 Initialize (Type::PASSWORDBOX, "System.Windows.Controls.PasswordBox");
2913 events_mask = TEXT_CHANGED;
2914 secret = true;
2916 display = g_string_new ("");
2919 PasswordBox::~PasswordBox ()
2921 g_string_free (display, true);
2925 PasswordBox::CursorDown (int cursor, bool page)
2927 return GetBuffer ()->len;
2931 PasswordBox::CursorUp (int cursor, bool page)
2933 return 0;
2937 PasswordBox::CursorLineBegin (int cursor)
2939 return 0;
2943 PasswordBox::CursorLineEnd (int cursor, bool include)
2945 return GetBuffer ()->len;
2949 PasswordBox::CursorNextWord (int cursor)
2951 return GetBuffer ()->len;
2955 PasswordBox::CursorPrevWord (int cursor)
2957 return 0;
2960 void
2961 PasswordBox::EmitTextChanged ()
2963 EmitAsync (PasswordBox::PasswordChangedEvent, new RoutedEventArgs ());
2966 void
2967 PasswordBox::SyncSelectedText ()
2969 if (selection_cursor != selection_anchor) {
2970 int length = abs (selection_cursor - selection_anchor);
2971 int start = MIN (selection_anchor, selection_cursor);
2972 char *text;
2974 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2976 setvalue = false;
2977 SetValue (PasswordBox::SelectedTextProperty, Value (text, true));
2978 setvalue = true;
2979 } else {
2980 setvalue = false;
2981 SetValue (PasswordBox::SelectedTextProperty, Value (""));
2982 setvalue = true;
2986 void
2987 PasswordBox::SyncDisplayText ()
2989 gunichar c = GetPasswordChar ();
2991 g_string_truncate (display, 0);
2993 for (int i = 0; i < buffer->len; i++)
2994 g_string_append_unichar (display, c);
2997 void
2998 PasswordBox::SyncText ()
3000 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
3002 setvalue = false;
3003 SetValue (PasswordBox::PasswordProperty, Value (text, true));
3004 setvalue = true;
3006 SyncDisplayText ();
3009 const char *
3010 PasswordBox::GetDisplayText ()
3012 return display->str;
3015 void
3016 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
3018 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
3019 int length, start;
3021 if (args->GetId () == PasswordBox::CaretBrushProperty) {
3022 // FIXME: if we want to be perfect, we could invalidate the
3023 // blinking cursor rect if it is active... but is it that
3024 // important?
3025 } else if (args->GetId () == PasswordBox::FontSourceProperty) {
3026 FontSource *source = args->GetNewValue () ? args->GetNewValue ()->AsFontSource () : NULL;
3027 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
3029 // FIXME: ideally we'd remove the old item from the cache (or,
3030 // rather, 'unref' it since some other textblocks/boxes might
3031 // still be using it).
3033 g_free (font_source);
3035 if (source && source->stream)
3036 font_source = manager->AddResource (source->stream);
3037 else
3038 font_source = NULL;
3040 changed = TextBoxModelChangedFont;
3041 font->SetSource (font_source);
3042 } else if (args->GetId () == PasswordBox::MaxLengthProperty) {
3043 // update max_length state
3044 max_length = args->GetNewValue()->AsInt32 ();
3045 } else if (args->GetId () == PasswordBox::PasswordCharProperty) {
3046 changed = TextBoxModelChangedText;
3047 } else if (args->GetId () == PasswordBox::PasswordProperty) {
3048 if (setvalue) {
3049 Value *value = args->GetNewValue ();
3050 const char *str = value && value->AsString () ? value->AsString () : "";
3051 TextBoxUndoAction *action;
3052 gunichar *text;
3053 glong textlen;
3055 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
3056 if (buffer->len > 0) {
3057 // replace the current text
3058 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, 0, buffer->len, text, textlen);
3060 buffer->Replace (0, buffer->len, text, textlen);
3061 } else {
3062 // insert the text
3063 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
3065 buffer->Insert (0, text, textlen);
3068 undo->Push (action);
3069 redo->Clear ();
3070 g_free (text);
3072 emit |= TEXT_CHANGED;
3073 SyncDisplayText ();
3074 ClearSelection (0);
3075 ResetIMContext ();
3077 SyncAndEmit ();
3081 changed = TextBoxModelChangedText;
3082 } else if (args->GetId () == PasswordBox::SelectedTextProperty) {
3083 if (setvalue) {
3084 Value *value = args->GetNewValue ();
3085 const char *str = value && value->AsString () ? value->AsString () : "";
3086 TextBoxUndoAction *action = NULL;
3087 gunichar *text;
3088 glong textlen;
3090 length = abs (selection_cursor - selection_anchor);
3091 start = MIN (selection_anchor, selection_cursor);
3093 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
3094 if (length > 0) {
3095 // replace the currently selected text
3096 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, textlen);
3098 buffer->Replace (start, length, text, textlen);
3099 } else if (textlen > 0) {
3100 // insert the text at the cursor
3101 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, textlen);
3103 buffer->Insert (start, text, textlen);
3106 g_free (text);
3108 if (action != NULL) {
3109 undo->Push (action);
3110 redo->Clear ();
3112 ClearSelection (start + textlen);
3113 emit |= TEXT_CHANGED;
3114 SyncDisplayText ();
3115 ResetIMContext ();
3117 SyncAndEmit ();
3121 } else if (args->GetId () == PasswordBox::SelectionStartProperty) {
3122 length = abs (selection_cursor - selection_anchor);
3123 start = args->GetNewValue ()->AsInt32 ();
3125 if (start > buffer->len) {
3126 // clamp the selection start offset to a valid value
3127 SetSelectionStart (buffer->len);
3128 return;
3131 if (start + length > buffer->len) {
3132 // clamp the selection length to a valid value
3133 BatchPush ();
3134 length = buffer->len - start;
3135 SetSelectionLength (length);
3136 BatchPop ();
3139 // SelectionStartProperty is marked as AlwaysChange -
3140 // if the value hasn't actually changed, then we do
3141 // not want to emit the TextBoxModelChanged event.
3142 if (selection_anchor != start) {
3143 changed = TextBoxModelChangedSelection;
3144 have_offset = false;
3147 // When set programatically, anchor is always the
3148 // start and cursor is always the end.
3149 selection_cursor = start + length;
3150 selection_anchor = start;
3152 emit |= SELECTION_CHANGED;
3154 SyncAndEmit ();
3155 } else if (args->GetId () == PasswordBox::SelectionLengthProperty) {
3156 start = MIN (selection_anchor, selection_cursor);
3157 length = args->GetNewValue ()->AsInt32 ();
3159 if (start + length > buffer->len) {
3160 // clamp the selection length to a valid value
3161 length = buffer->len - start;
3162 SetSelectionLength (length);
3163 return;
3166 // SelectionLengthProperty is marked as AlwaysChange -
3167 // if the value hasn't actually changed, then we do
3168 // not want to emit the TextBoxModelChanged event.
3169 if (selection_cursor != start + length) {
3170 changed = TextBoxModelChangedSelection;
3171 have_offset = false;
3174 // When set programatically, anchor is always the
3175 // start and cursor is always the end.
3176 selection_cursor = start + length;
3177 selection_anchor = start;
3179 emit |= SELECTION_CHANGED;
3181 SyncAndEmit ();
3182 } else if (args->GetId () == PasswordBox::SelectionBackgroundProperty) {
3183 changed = TextBoxModelChangedBrush;
3184 } else if (args->GetId () == PasswordBox::SelectionForegroundProperty) {
3185 changed = TextBoxModelChangedBrush;
3188 if (changed != TextBoxModelChangedNothing)
3189 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
3191 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
3192 TextBoxBase::OnPropertyChanged (args, error);
3193 return;
3196 NotifyListenersOfPropertyChange (args, error);
3199 void
3200 PasswordBox::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
3202 if (prop && (prop->GetId () == PasswordBox::SelectionBackgroundProperty ||
3203 prop->GetId () == PasswordBox::SelectionForegroundProperty)) {
3204 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
3205 Invalidate ();
3208 if (prop->GetOwnerType () != Type::TEXTBOX)
3209 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
3214 // TextBoxView
3217 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3218 #define CURSOR_BLINK_ON_MULTIPLIER 2
3219 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3220 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3221 #define CURSOR_BLINK_DIVIDER 3
3223 TextBoxView::TextBoxView ()
3225 SetObjectType (Type::TEXTBOXVIEW);
3227 AddHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3228 AddHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3230 SetCursor (MouseCursorIBeam);
3232 cursor = Rect (0, 0, 0, 0);
3233 layout = new TextLayout ();
3234 selection_changed = false;
3235 had_selected_text = false;
3236 cursor_visible = false;
3237 blink_timeout = 0;
3238 textbox = NULL;
3239 dirty = false;
3242 TextBoxView::~TextBoxView ()
3244 RemoveHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3245 RemoveHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3247 if (textbox) {
3248 textbox->RemoveHandler (TextBox::ModelChangedEvent, TextBoxView::model_changed, this);
3249 textbox->view = NULL;
3252 DisconnectBlinkTimeout ();
3254 delete layout;
3257 TextLayoutLine *
3258 TextBoxView::GetLineFromY (double y, int *index)
3260 return layout->GetLineFromY (Point (), y, index);
3263 TextLayoutLine *
3264 TextBoxView::GetLineFromIndex (int index)
3266 return layout->GetLineFromIndex (index);
3270 TextBoxView::GetCursorFromXY (double x, double y)
3272 return layout->GetCursorFromXY (Point (), x, y);
3275 gboolean
3276 TextBoxView::blink (void *user_data)
3278 return ((TextBoxView *) user_data)->Blink ();
3281 static guint
3282 GetCursorBlinkTimeout (TextBoxView *view)
3284 GtkSettings *settings;
3285 MoonWindow *window;
3286 GdkScreen *screen;
3287 GdkWindow *widget;
3288 Surface *surface;
3289 guint timeout;
3291 if (!(surface = view->GetSurface ()))
3292 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3294 if (!(window = surface->GetWindow ()))
3295 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3297 if (!(widget = window->GetGdkWindow ()))
3298 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3300 if (!(screen = gdk_drawable_get_screen ((GdkDrawable *) widget)))
3301 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3303 if (!(settings = gtk_settings_get_for_screen (screen)))
3304 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3306 g_object_get (settings, "gtk-cursor-blink-time", &timeout, NULL);
3308 return timeout;
3311 void
3312 TextBoxView::ConnectBlinkTimeout (guint multiplier)
3314 guint timeout = GetCursorBlinkTimeout (this) * multiplier / CURSOR_BLINK_DIVIDER;
3315 Surface *surface = GetSurface ();
3316 TimeManager *manager;
3318 if (!surface || !(manager = surface->GetTimeManager ()))
3319 return;
3321 blink_timeout = manager->AddTimeout (MOON_PRIORITY_DEFAULT, timeout, TextBoxView::blink, this);
3324 void
3325 TextBoxView::DisconnectBlinkTimeout ()
3327 TimeManager *manager;
3328 Surface *surface;
3330 if (blink_timeout != 0) {
3331 if (!(surface = GetSurface ()) || !(manager = surface->GetTimeManager ()))
3332 return;
3334 manager->RemoveTimeout (blink_timeout);
3335 blink_timeout = 0;
3339 bool
3340 TextBoxView::Blink ()
3342 guint multiplier;
3344 SetCurrentDeployment (true);
3346 if (cursor_visible) {
3347 multiplier = CURSOR_BLINK_OFF_MULTIPLIER;
3348 HideCursor ();
3349 } else {
3350 multiplier = CURSOR_BLINK_ON_MULTIPLIER;
3351 ShowCursor ();
3354 ConnectBlinkTimeout (multiplier);
3356 return false;
3359 void
3360 TextBoxView::DelayCursorBlink ()
3362 DisconnectBlinkTimeout ();
3363 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER);
3364 UpdateCursor (true);
3365 ShowCursor ();
3368 void
3369 TextBoxView::BeginCursorBlink ()
3371 if (blink_timeout == 0) {
3372 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER);
3373 UpdateCursor (true);
3374 ShowCursor ();
3378 void
3379 TextBoxView::EndCursorBlink ()
3381 DisconnectBlinkTimeout ();
3383 if (cursor_visible)
3384 HideCursor ();
3387 void
3388 TextBoxView::ResetCursorBlink (bool delay)
3390 if (textbox->IsFocused () && !textbox->HasSelectedText ()) {
3391 // cursor is blinkable... proceed with blinkage
3392 if (delay)
3393 DelayCursorBlink ();
3394 else
3395 BeginCursorBlink ();
3396 } else {
3397 // cursor not blinkable... stop all blinkage
3398 EndCursorBlink ();
3402 void
3403 TextBoxView::InvalidateCursor ()
3405 Invalidate (cursor.Transform (&absolute_xform));
3408 void
3409 TextBoxView::ShowCursor ()
3411 cursor_visible = true;
3412 InvalidateCursor ();
3415 void
3416 TextBoxView::HideCursor ()
3418 cursor_visible = false;
3419 InvalidateCursor ();
3422 void
3423 TextBoxView::UpdateCursor (bool invalidate)
3425 int cur = textbox->GetCursor ();
3426 GdkRectangle area;
3427 Rect rect;
3429 // invalidate current cursor rect
3430 if (invalidate && cursor_visible)
3431 InvalidateCursor ();
3433 // calculate the new cursor rect
3434 cursor = layout->GetCursor (Point (), cur);
3436 // transform the cursor rect into absolute coordinates for the IM context
3437 rect = cursor.Transform (&absolute_xform);
3438 area = rect.ToGdkRectangle ();
3440 gtk_im_context_set_cursor_location (textbox->im_ctx, &area);
3442 textbox->EmitCursorPositionChanged (cursor.height, cursor.x, cursor.y);
3444 // invalidate the new cursor rect
3445 if (invalidate && cursor_visible)
3446 InvalidateCursor ();
3449 void
3450 TextBoxView::UpdateText ()
3452 const char *text = textbox->GetDisplayText ();
3454 layout->SetText (text ? text : "", -1);
3457 void
3458 TextBoxView::GetSizeForBrush (cairo_t *cr, double *width, double *height)
3460 *height = GetActualHeight ();
3461 *width = GetActualWidth ();
3464 Size
3465 TextBoxView::ComputeActualSize ()
3467 Layout (Size (INFINITY, INFINITY));
3469 Size actual (0,0);
3470 layout->GetActualExtents (&actual.width, &actual.height);
3472 return actual;
3475 Size
3476 TextBoxView::MeasureOverride (Size availableSize)
3478 Size desired = Size ();
3481 Layout (availableSize);
3483 layout->GetActualExtents (&desired.width, &desired.height);
3485 if (GetUseLayoutRounding ())
3486 desired.width = ceil (desired.width);
3488 return desired.Min (availableSize);
3491 Size
3492 TextBoxView::ArrangeOverride (Size finalSize)
3494 Size arranged = Size ();
3497 Layout (finalSize);
3499 layout->GetActualExtents (&arranged.width, &arranged.height);
3501 arranged = arranged.Max (finalSize);
3503 return arranged;
3506 void
3507 TextBoxView::Layout (Size constraint)
3509 layout->SetMaxWidth (constraint.width);
3511 layout->Layout ();
3514 void
3515 TextBoxView::Paint (cairo_t *cr)
3517 layout->Render (cr, GetOriginPoint (), Point ());
3519 if (cursor_visible) {
3520 cairo_antialias_t alias = cairo_get_antialias (cr);
3521 Brush *caret = textbox->GetCaretBrush ();
3522 double h = round (cursor.height);
3523 double x = cursor.x;
3524 double y = cursor.y;
3526 // disable antialiasing
3527 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
3529 // snap 'x' to the half-pixel grid (to try and get a sharp 1-pixel-wide line)
3530 cairo_user_to_device (cr, &x, &y);
3531 x = trunc (x) + 0.5; y = trunc (y);
3532 cairo_device_to_user (cr, &x, &y);
3534 // set the cursor color
3535 caret->SetupBrush (cr, cursor);
3537 // draw the cursor
3538 cairo_set_line_width (cr, 1.0);
3539 cairo_move_to (cr, x, y);
3540 cairo_line_to (cr, x, y + h);
3542 // stroke the caret
3543 caret->Stroke (cr);
3545 // restore antialiasing
3546 cairo_set_antialias (cr, alias);
3550 void
3551 TextBoxView::Render (cairo_t *cr, Region *region, bool path_only)
3553 TextBoxDynamicPropertyValueProvider *dynamic = (TextBoxDynamicPropertyValueProvider *) textbox->providers[PropertyPrecedence_DynamicValue];
3554 Size renderSize = GetRenderSize ();
3556 dynamic->InitializeSelectionBrushes ();
3558 if (dirty) {
3559 Layout (renderSize);
3560 UpdateCursor (false);
3561 dirty = false;
3564 if (selection_changed) {
3565 layout->Select (textbox->GetSelectionStart (), textbox->GetSelectionLength ());
3566 selection_changed = false;
3569 cairo_save (cr);
3570 cairo_set_matrix (cr, &absolute_xform);
3572 if (!path_only)
3573 RenderLayoutClip (cr);
3575 layout->SetAvailableWidth (renderSize.width);
3576 Paint (cr);
3577 cairo_restore (cr);
3580 void
3581 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs *args)
3583 switch (args->changed) {
3584 case TextBoxModelChangedTextAlignment:
3585 // text alignment changed, update our layout
3586 if (layout->SetTextAlignment ((TextAlignment) args->property->GetNewValue()->AsInt32 ()))
3587 dirty = true;
3588 break;
3589 case TextBoxModelChangedTextWrapping:
3590 // text wrapping changed, update our layout
3591 if (layout->SetTextWrapping ((TextWrapping) args->property->GetNewValue()->AsInt32 ()))
3592 dirty = true;
3593 break;
3594 case TextBoxModelChangedSelection:
3595 if (had_selected_text || textbox->HasSelectedText ()) {
3596 // the selection has changed, update the layout's selection
3597 had_selected_text = textbox->HasSelectedText ();
3598 selection_changed = true;
3599 ResetCursorBlink (false);
3600 } else {
3601 // cursor position changed
3602 ResetCursorBlink (true);
3603 return;
3605 break;
3606 case TextBoxModelChangedBrush:
3607 // a brush has changed, no layout updates needed, we just need to re-render
3608 break;
3609 case TextBoxModelChangedFont:
3610 // font changed, need to recalculate layout/bounds
3611 layout->ResetState ();
3612 dirty = true;
3613 break;
3614 case TextBoxModelChangedText:
3615 // the text has changed, need to recalculate layout/bounds
3616 UpdateText ();
3617 dirty = true;
3618 break;
3619 default:
3620 // nothing changed??
3621 return;
3624 if (dirty) {
3625 InvalidateMeasure ();
3626 UpdateBounds (true);
3629 Invalidate ();
3632 void
3633 TextBoxView::model_changed (EventObject *sender, EventArgs *args, gpointer closure)
3635 ((TextBoxView *) closure)->OnModelChanged ((TextBoxModelChangedEventArgs *) args);
3638 void
3639 TextBoxView::OnFocusOut ()
3641 EndCursorBlink ();
3644 void
3645 TextBoxView::OnFocusIn ()
3647 ResetCursorBlink (false);
3650 void
3651 TextBoxView::OnMouseLeftButtonDown (MouseEventArgs *args)
3653 // proxy to our parent TextBox control
3654 textbox->OnMouseLeftButtonDown (args);
3657 void
3658 TextBoxView::mouse_left_button_down (EventObject *sender, EventArgs *args, gpointer closure)
3660 ((TextBoxView *) closure)->OnMouseLeftButtonDown ((MouseEventArgs *) args);
3663 void
3664 TextBoxView::OnMouseLeftButtonUp (MouseEventArgs *args)
3666 // proxy to our parent TextBox control
3667 textbox->OnMouseLeftButtonUp (args);
3670 void
3671 TextBoxView::mouse_left_button_up (EventObject *sender, EventArgs *args, gpointer closure)
3673 ((TextBoxView *) closure)->OnMouseLeftButtonUp ((MouseEventArgs *) args);
3676 void
3677 TextBoxView::SetTextBox (TextBoxBase *textbox)
3679 TextLayoutAttributes *attrs;
3681 if (this->textbox == textbox)
3682 return;
3684 if (this->textbox) {
3685 // remove the event handlers from the old textbox
3686 this->textbox->RemoveHandler (TextBoxBase::ModelChangedEvent, TextBoxView::model_changed, this);
3689 this->textbox = textbox;
3691 if (textbox) {
3692 textbox->AddHandler (TextBoxBase::ModelChangedEvent, TextBoxView::model_changed, this);
3694 // sync our state with the textbox
3695 layout->SetTextAttributes (new List ());
3696 attrs = new TextLayoutAttributes ((ITextAttributes *) textbox, 0);
3697 layout->GetTextAttributes ()->Append (attrs);
3699 layout->SetTextAlignment (textbox->GetTextAlignment ());
3700 layout->SetTextWrapping (textbox->GetTextWrapping ());
3701 had_selected_text = textbox->HasSelectedText ();
3702 selection_changed = true;
3703 UpdateText ();
3704 } else {
3705 layout->SetTextAttributes (NULL);
3706 layout->SetText (NULL, -1);
3709 UpdateBounds (true);
3710 InvalidateMeasure ();
3711 Invalidate ();
3712 dirty = true;