revert jeff's last commit since it breaks the build
[moon.git] / src / textbox.cpp
blob95a59d1df0b02ae0238cff6d4061d2e72d1c9503
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"
36 #include "managedtypeinfo.h"
39 // TextBuffer
42 #define UNICODE_LEN(size) (sizeof (gunichar) * (size))
43 #define UNICODE_OFFSET(buf,offset) (((char *) buf) + UNICODE_LEN (offset))
45 class TextBuffer {
46 int allocated;
48 bool Resize (int needed)
50 int new_size = allocated;
51 bool resize = false;
52 void *buf;
54 if (allocated >= needed + 128) {
55 while (new_size >= needed + 128)
56 new_size -= 128;
57 resize = true;
58 } else if (allocated < needed) {
59 while (new_size < needed)
60 new_size += 128;
61 resize = true;
64 if (resize) {
65 if (!(buf = g_try_realloc (text, UNICODE_LEN (new_size)))) {
66 // if new_size is < allocated, then we can pretend like we succeeded
67 return new_size < allocated;
70 text = (gunichar *) buf;
71 allocated = new_size;
74 return true;
77 public:
78 gunichar *text;
79 int len;
81 TextBuffer (const gunichar *text, int len)
83 this->allocated = 0;
84 this->text = NULL;
85 this->len = 0;
87 Append (text, len);
90 TextBuffer ()
92 text = NULL;
93 Reset ();
96 void Reset ()
98 text = (gunichar *) g_realloc (text, UNICODE_LEN (128));
99 allocated = 128;
100 text[0] = '\0';
101 len = 0;
104 void Print ()
106 printf ("TextBuffer::text = \"");
108 for (int i = 0; i < len; i++) {
109 switch (text[i]) {
110 case '\r':
111 fputs ("\\r", stdout);
112 break;
113 case '\n':
114 fputs ("\\n", stdout);
115 break;
116 case '\0':
117 fputs ("\\0", stdout);
118 break;
119 case '\\':
120 fputc ('\\', stdout);
121 // fall thru
122 default:
123 fputc ((char) text[i], stdout);
124 break;
128 printf ("\";\n");
131 void Append (gunichar c)
133 if (!Resize (len + 2))
134 return;
136 text[len++] = c;
137 text[len] = 0;
140 void Append (const gunichar *str, int count)
142 if (!Resize (len + count + 1))
143 return;
145 memcpy (UNICODE_OFFSET (text, len), str, UNICODE_LEN (count));
146 len += count;
147 text[len] = 0;
150 void Cut (int start, int length)
152 char *dest, *src;
153 int beyond;
155 if (length == 0 || start >= len)
156 return;
158 if (start + length > len)
159 length = len - start;
161 src = UNICODE_OFFSET (text, start + length);
162 dest = UNICODE_OFFSET (text, start);
163 beyond = len - (start + length);
165 memmove (dest, src, UNICODE_LEN (beyond + 1));
166 len -= length;
169 void Insert (int index, gunichar c)
171 if (!Resize (len + 2))
172 return;
174 if (index < len) {
175 // shift all chars beyond position @index down by 1 char
176 memmove (UNICODE_OFFSET (text, index + 1), UNICODE_OFFSET (text, index), UNICODE_LEN ((len - index) + 1));
177 text[index] = c;
178 len++;
179 } else {
180 text[len++] = c;
181 text[len] = 0;
185 void Insert (int index, const gunichar *str, int count)
187 if (!Resize (len + count + 1))
188 return;
190 if (index < len) {
191 // shift all chars beyond position @index down by @count chars
192 memmove (UNICODE_OFFSET (text, index + count), UNICODE_OFFSET (text, index), UNICODE_LEN ((len - index) + 1));
194 // insert @count chars of @str into our buffer at position @index
195 memcpy (UNICODE_OFFSET (text, index), str, UNICODE_LEN (count));
196 len += count;
197 } else {
198 // simply append @count chars of @str onto the end of our buffer
199 memcpy (UNICODE_OFFSET (text, len), str, UNICODE_LEN (count));
200 len += count;
201 text[len] = 0;
205 void Prepend (gunichar c)
207 if (!Resize (len + 2))
208 return;
210 // shift the entire buffer down by 1 char
211 memmove (UNICODE_OFFSET (text, 1), text, UNICODE_LEN (len + 1));
212 text[0] = c;
213 len++;
216 void Prepend (const gunichar *str, int count)
218 if (!Resize (len + count + 1))
219 return;
221 // shift the endtire buffer down by @count chars
222 memmove (UNICODE_OFFSET (text, count), text, UNICODE_LEN (len + 1));
224 // copy @count chars of @str into the beginning of our buffer
225 memcpy (text, str, UNICODE_LEN (count));
226 len += count;
229 void Replace (int start, int length, const gunichar *str, int count)
231 char *dest, *src;
232 int beyond;
234 if (start > len)
235 return;
237 if (start + length > len)
238 length = len - start;
240 // Check for the easy cases first...
241 if (length == 0) {
242 Insert (start, str, count);
243 return;
244 } else if (count == 0) {
245 Cut (start, length);
246 return;
247 } else if (count == length) {
248 memcpy (UNICODE_OFFSET (text, start), str, UNICODE_LEN (count));
249 return;
252 if (count > length && !Resize (len + (count - length) + 1))
253 return;
255 // calculate the number of chars beyond @start that won't be cut
256 beyond = len - (start + length);
258 // shift all chars beyond position (@start + length) into position...
259 dest = UNICODE_OFFSET (text, start + count);
260 src = UNICODE_OFFSET (text, start + length);
261 memmove (dest, src, UNICODE_LEN (beyond + 1));
263 // copy @count chars of @str into our buffer at position @start
264 memcpy (UNICODE_OFFSET (text, start), str, UNICODE_LEN (count));
266 len = (len - length) + count;
269 gunichar *Substring (int start, int length = -1)
271 gunichar *substr;
272 size_t n_bytes;
274 if (start < 0 || start > len || length == 0)
275 return NULL;
277 if (length < 0)
278 length = len - start;
280 n_bytes = sizeof (gunichar) * (length + 1);
281 substr = (gunichar *) g_malloc (n_bytes);
282 n_bytes -= sizeof (gunichar);
284 memcpy (substr, text + start, n_bytes);
285 substr[length] = 0;
287 return substr;
293 // TextBoxUndoActions
296 enum TextBoxUndoActionType {
297 TextBoxUndoActionTypeInsert,
298 TextBoxUndoActionTypeDelete,
299 TextBoxUndoActionTypeReplace,
302 class TextBoxUndoAction : public List::Node {
303 public:
304 TextBoxUndoActionType type;
305 int selection_anchor;
306 int selection_cursor;
307 int length;
308 int start;
311 class TextBoxUndoActionInsert : public TextBoxUndoAction {
312 public:
313 TextBuffer *buffer;
314 bool growable;
316 TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, gunichar c);
317 TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, const gunichar *inserted, int length, bool atomic = false);
318 virtual ~TextBoxUndoActionInsert ();
320 bool Insert (int start, const gunichar *text, int len);
321 bool Insert (int start, gunichar c);
324 class TextBoxUndoActionDelete : public TextBoxUndoAction {
325 public:
326 gunichar *text;
328 TextBoxUndoActionDelete (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length);
329 virtual ~TextBoxUndoActionDelete ();
332 class TextBoxUndoActionReplace : public TextBoxUndoAction {
333 public:
334 gunichar *inserted;
335 gunichar *deleted;
336 int inlen;
338 TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, const gunichar *inserted, int inlen);
339 TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, gunichar c);
340 virtual ~TextBoxUndoActionReplace ();
343 class TextBoxUndoStack {
344 int max_count;
345 List *list;
347 public:
348 TextBoxUndoStack (int max_count);
349 ~TextBoxUndoStack ();
351 bool IsEmpty ();
352 void Clear ();
354 void Push (TextBoxUndoAction *action);
355 TextBoxUndoAction *Peek ();
356 TextBoxUndoAction *Pop ();
359 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, gunichar c)
361 this->type = TextBoxUndoActionTypeInsert;
362 this->selection_anchor = selection_anchor;
363 this->selection_cursor = selection_cursor;
364 this->start = start;
365 this->length = 1;
367 this->buffer = new TextBuffer ();
368 this->buffer->Append (c);
369 this->growable = true;
372 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, const gunichar *inserted, int length, bool atomic)
374 this->type = TextBoxUndoActionTypeInsert;
375 this->selection_anchor = selection_anchor;
376 this->selection_cursor = selection_cursor;
377 this->length = length;
378 this->start = start;
380 this->buffer = new TextBuffer (inserted, length);
381 this->growable = !atomic;
384 TextBoxUndoActionInsert::~TextBoxUndoActionInsert ()
386 delete buffer;
389 bool
390 TextBoxUndoActionInsert::Insert (int start, const gunichar *text, int len)
392 if (!growable || start != (this->start + length))
393 return false;
395 buffer->Append (text, len);
396 length += len;
398 return true;
401 bool
402 TextBoxUndoActionInsert::Insert (int start, gunichar c)
404 if (!growable || start != (this->start + length))
405 return false;
407 buffer->Append (c);
408 length++;
410 return true;
413 TextBoxUndoActionDelete::TextBoxUndoActionDelete (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length)
415 this->type = TextBoxUndoActionTypeDelete;
416 this->selection_anchor = selection_anchor;
417 this->selection_cursor = selection_cursor;
418 this->length = length;
419 this->start = start;
421 this->text = buffer->Substring (start, length);
424 TextBoxUndoActionDelete::~TextBoxUndoActionDelete ()
426 g_free (text);
429 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, const gunichar *inserted, int inlen)
431 this->type = TextBoxUndoActionTypeReplace;
432 this->selection_anchor = selection_anchor;
433 this->selection_cursor = selection_cursor;
434 this->length = length;
435 this->start = start;
437 this->deleted = buffer->Substring (start, length);
438 this->inserted = (gunichar *) g_malloc (UNICODE_LEN (inlen + 1));
439 memcpy (this->inserted, inserted, UNICODE_LEN (inlen + 1));
440 this->inlen = inlen;
443 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, gunichar c)
445 this->type = TextBoxUndoActionTypeReplace;
446 this->selection_anchor = selection_anchor;
447 this->selection_cursor = selection_cursor;
448 this->length = length;
449 this->start = start;
451 this->deleted = buffer->Substring (start, length);
452 this->inserted = g_new (gunichar, 2);
453 memcpy (inserted, &c, sizeof (gunichar));
454 inserted[1] = 0;
455 this->inlen = 1;
458 TextBoxUndoActionReplace::~TextBoxUndoActionReplace ()
460 g_free (inserted);
461 g_free (deleted);
465 TextBoxUndoStack::TextBoxUndoStack (int max_count)
467 this->max_count = max_count;
468 this->list = new List ();
471 TextBoxUndoStack::~TextBoxUndoStack ()
473 delete list;
476 bool
477 TextBoxUndoStack::IsEmpty ()
479 return list->IsEmpty ();
482 void
483 TextBoxUndoStack::Clear ()
485 list->Clear (true);
488 void
489 TextBoxUndoStack::Push (TextBoxUndoAction *action)
491 if (list->Length () == max_count) {
492 List::Node *node = list->Last ();
493 list->Unlink (node);
494 delete node;
497 list->Prepend (action);
500 TextBoxUndoAction *
501 TextBoxUndoStack::Pop ()
503 List::Node *node = list->First ();
505 if (node)
506 list->Unlink (node);
508 return (TextBoxUndoAction *) node;
511 TextBoxUndoAction *
512 TextBoxUndoStack::Peek ()
514 return (TextBoxUndoAction *) list->First ();
519 // TextBoxBase
522 // emit state, also doubles as available event mask
523 #define NOTHING_CHANGED (0)
524 #define SELECTION_CHANGED (1 << 0)
525 #define TEXT_CHANGED (1 << 1)
527 #define CONTROL_MASK GDK_CONTROL_MASK
528 #define SHIFT_MASK GDK_SHIFT_MASK
529 #define ALT_MASK GDK_MOD1_MASK
531 #define IsEOL(c) ((c) == '\r' || (c) == '\n')
533 static GdkWindow *
534 GetGdkWindow (TextBoxBase *textbox)
536 MoonWindow *window;
538 if (!textbox->IsAttached ())
539 return NULL;
541 if (!(window = textbox->GetDeployment ()->GetSurface ()->GetWindow ()))
542 return NULL;
544 return window->GetGdkWindow ();
547 static GtkClipboard *
548 GetClipboard (TextBoxBase *textbox, GdkAtom atom)
550 GdkDisplay *display;
551 GdkWindow *window;
553 if (!(window = GetGdkWindow (textbox)))
554 return NULL;
556 if (!(display = gdk_drawable_get_display ((GdkDrawable *) window)))
557 return NULL;
559 return gtk_clipboard_get_for_display (display, atom);
562 void
563 TextBoxBase::Initialize (Type::Kind type, const char *type_name)
565 ManagedTypeInfo *type_info = g_new (ManagedTypeInfo, 1);
566 type_info->Initialize ("System.Windows", type_name);
568 SetObjectType (type);
569 SetDefaultStyleKey (type_info);
570 ManagedTypeInfo::Free (type_info);
572 AddHandler (UIElement::MouseLeftButtonMultiClickEvent, TextBoxBase::mouse_left_button_multi_click, this);
574 font = new TextFontDescription ();
575 font->SetFamily (GetFontFamily ()->source);
576 font->SetStretch (GetFontStretch ()->stretch);
577 font->SetWeight (GetFontWeight ()->weight);
578 font->SetStyle (GetFontStyle ()->style);
579 font->SetSize (GetFontSize ());
581 downloaders = g_ptr_array_new ();
582 font_source = NULL;
584 contentElement = NULL;
586 im_ctx = gtk_im_multicontext_new ();
587 gtk_im_context_set_use_preedit (im_ctx, false);
589 g_signal_connect (im_ctx, "retrieve-surrounding", G_CALLBACK (TextBoxBase::retrieve_surrounding), this);
590 g_signal_connect (im_ctx, "delete-surrounding", G_CALLBACK (TextBoxBase::delete_surrounding), this);
591 g_signal_connect (im_ctx, "commit", G_CALLBACK (TextBoxBase::commit), this);
593 undo = new TextBoxUndoStack (10);
594 redo = new TextBoxUndoStack (10);
595 buffer = new TextBuffer ();
596 max_length = 0;
598 emit = NOTHING_CHANGED;
599 events_mask = 0;
601 selection_anchor = 0;
602 selection_cursor = 0;
603 cursor_offset = 0.0;
604 batch = 0;
606 accepts_return = false;
607 need_im_reset = false;
608 is_read_only = false;
609 have_offset = false;
610 multiline = false;
611 selecting = false;
612 setvalue = true;
613 captured = false;
614 focused = false;
615 secret = false;
616 view = NULL;
619 TextBoxBase::~TextBoxBase ()
621 RemoveHandler (UIElement::MouseLeftButtonMultiClickEvent, TextBoxBase::mouse_left_button_multi_click, this);
623 ResetIMContext ();
624 g_object_unref (im_ctx);
626 CleanupDownloaders ();
627 g_ptr_array_free (downloaders, true);
628 g_free (font_source);
630 delete buffer;
631 delete undo;
632 delete redo;
633 delete font;
636 void
637 TextBoxBase::SetIsAttached (bool value)
639 Control::SetIsAttached (value);
641 if (value)
642 gtk_im_context_set_client_window (im_ctx, GetGdkWindow (this));
645 void
646 TextBoxBase::CleanupDownloaders ()
648 Downloader *downloader;
649 guint i;
651 for (i = 0; i < downloaders->len; i++) {
652 downloader = (Downloader *) downloaders->pdata[i];
653 downloader->RemoveHandler (Downloader::CompletedEvent, downloader_complete, this);
654 downloader->Abort ();
655 downloader->unref ();
658 g_ptr_array_set_size (downloaders, 0);
661 double
662 TextBoxBase::GetCursorOffset ()
664 if (!have_offset && view) {
665 cursor_offset = view->GetCursor ().x;
666 have_offset = true;
669 return cursor_offset;
673 TextBoxBase::CursorDown (int cursor, bool page)
675 double y = view->GetCursor ().y;
676 double x = GetCursorOffset ();
677 TextLayoutLine *line;
678 TextLayoutRun *run;
679 int index, cur, n;
680 guint i;
682 if (!(line = view->GetLineFromY (y, &index)))
683 return cursor;
685 if (page) {
686 // calculate the number of lines to skip over
687 n = GetActualHeight () / line->height;
688 } else {
689 n = 1;
692 if (index + n >= view->GetLineCount ()) {
693 // go to the end of the last line
694 line = view->GetLineFromIndex (view->GetLineCount () - 1);
696 for (cur = line->offset, i = 0; i < line->runs->len; i++) {
697 run = (TextLayoutRun *) line->runs->pdata[i];
698 cur += run->count;
701 have_offset = false;
703 return cur;
706 line = view->GetLineFromIndex (index + n);
708 return line->GetCursorFromX (Point (), x);
712 TextBoxBase::CursorUp (int cursor, bool page)
714 double y = view->GetCursor ().y;
715 double x = GetCursorOffset ();
716 TextLayoutLine *line;
717 int index, n;
719 if (!(line = view->GetLineFromY (y, &index)))
720 return cursor;
722 if (page) {
723 // calculate the number of lines to skip over
724 n = GetActualHeight () / line->height;
725 } else {
726 n = 1;
729 if (index < n) {
730 // go to the beginning of the first line
731 have_offset = false;
732 return 0;
735 line = view->GetLineFromIndex (index - n);
737 return line->GetCursorFromX (Point (), x);
740 #ifdef EMULATE_GTK
741 enum CharClass {
742 CharClassUnknown,
743 CharClassWhitespace,
744 CharClassAlphaNumeric
747 static inline CharClass
748 char_class (gunichar c)
750 if (g_unichar_isspace (c))
751 return CharClassWhitespace;
753 if (g_unichar_isalnum (c))
754 return CharClassAlphaNumeric;
756 return CharClassUnknown;
758 #else
759 static bool
760 is_start_of_word (TextBuffer *buffer, int index)
762 // A 'word' starts with an AlphaNumeric immediately preceeded by lwsp
763 if (index > 0 && !g_unichar_isspace (buffer->text[index - 1]))
764 return false;
766 return g_unichar_isalnum (buffer->text[index]);
768 #endif
771 TextBoxBase::CursorNextWord (int cursor)
773 int i, lf, cr;
775 // find the end of the current line
776 cr = CursorLineEnd (cursor);
777 if (buffer->text[cr] == '\r' && buffer->text[cr + 1] == '\n')
778 lf = cr + 1;
779 else
780 lf = cr;
782 // if the cursor is at the end of the line, return the starting offset of the next line
783 if (cursor == cr || cursor == lf) {
784 if (lf < buffer->len)
785 return lf + 1;
787 return cursor;
790 #ifdef EMULATE_GTK
791 CharClass cc = char_class (buffer->text[cursor]);
792 i = cursor;
794 // skip over the word, punctuation, or run of whitespace
795 while (i < cr && char_class (buffer->text[i]) == cc)
796 i++;
798 // skip any whitespace after the word/punct
799 while (i < cr && g_unichar_isspace (buffer->text[i]))
800 i++;
801 #else
802 i = cursor;
804 // skip to the end of the current word
805 while (i < cr && !g_unichar_isspace (buffer->text[i]))
806 i++;
808 // skip any whitespace after the word
809 while (i < cr && g_unichar_isspace (buffer->text[i]))
810 i++;
812 // find the start of the next word
813 while (i < cr && !is_start_of_word (buffer, i))
814 i++;
815 #endif
817 return i;
821 TextBoxBase::CursorPrevWord (int cursor)
823 int begin, i, cr, lf;
825 // find the beginning of the current line
826 lf = CursorLineBegin (cursor) - 1;
828 if (lf > 0 && buffer->text[lf] == '\n' && buffer->text[lf - 1] == '\r')
829 cr = lf - 1;
830 else
831 cr = lf;
833 // if the cursor is at the beginning of the line, return the end of the prev line
834 if (cursor - 1 == lf) {
835 if (cr > 0)
836 return cr;
838 return 0;
841 #ifdef EMULATE_GTK
842 CharClass cc = char_class (buffer->text[cursor - 1]);
843 begin = lf + 1;
844 i = cursor;
846 // skip over the word, punctuation, or run of whitespace
847 while (i > begin && char_class (buffer->text[i - 1]) == cc)
848 i--;
850 // if the cursor was at whitespace, skip back a word too
851 if (cc == CharClassWhitespace && i > begin) {
852 cc = char_class (buffer->text[i - 1]);
853 while (i > begin && char_class (buffer->text[i - 1]) == cc)
854 i--;
856 #else
857 begin = lf + 1;
858 i = cursor;
860 if (cursor < buffer->len) {
861 // skip to the beginning of this word
862 while (i > begin && !g_unichar_isspace (buffer->text[i - 1]))
863 i--;
865 if (i < cursor && is_start_of_word (buffer, i))
866 return i;
869 // skip to the start of the lwsp
870 while (i > begin && g_unichar_isspace (buffer->text[i - 1]))
871 i--;
873 if (i > begin)
874 i--;
876 // skip to the beginning of the word
877 while (i > begin && !is_start_of_word (buffer, i))
878 i--;
879 #endif
881 return i;
885 TextBoxBase::CursorLineBegin (int cursor)
887 int cur = cursor;
889 // find the beginning of the line
890 while (cur > 0 && !IsEOL (buffer->text[cur - 1]))
891 cur--;
893 return cur;
897 TextBoxBase::CursorLineEnd (int cursor, bool include)
899 int cur = cursor;
901 // find the end of the line
902 while (cur < buffer->len && !IsEOL (buffer->text[cur]))
903 cur++;
905 if (include && cur < buffer->len) {
906 if (buffer->text[cur] == '\r' && buffer->text[cur + 1] == '\n')
907 cur += 2;
908 else
909 cur++;
912 return cur;
915 bool
916 TextBoxBase::KeyPressBackSpace (GdkModifierType modifiers)
918 int anchor = selection_anchor;
919 int cursor = selection_cursor;
920 TextBoxUndoAction *action;
921 int start = 0, length = 0;
922 bool handled = false;
924 if ((modifiers & (ALT_MASK | SHIFT_MASK)) != 0)
925 return false;
927 if (cursor != anchor) {
928 // BackSpace w/ active selection: delete the selected text
929 length = abs (cursor - anchor);
930 start = MIN (anchor, cursor);
931 } else if ((modifiers & CONTROL_MASK) != 0) {
932 // Ctrl+BackSpace: delete the word ending at the cursor
933 start = CursorPrevWord (cursor);
934 length = cursor - start;
935 } else if (cursor > 0) {
936 // BackSpace: delete the char before the cursor position
937 if (cursor >= 2 && buffer->text[cursor - 1] == '\n' && buffer->text[cursor - 2] == '\r') {
938 start = cursor - 2;
939 length = 2;
940 } else {
941 start = cursor - 1;
942 length = 1;
946 if (length > 0) {
947 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
948 undo->Push (action);
949 redo->Clear ();
951 buffer->Cut (start, length);
952 emit |= TEXT_CHANGED;
953 anchor = start;
954 cursor = start;
955 handled = true;
958 // check to see if selection has changed
959 if (selection_anchor != anchor || selection_cursor != cursor) {
960 SetSelectionStart (MIN (anchor, cursor));
961 SetSelectionLength (abs (cursor - anchor));
962 selection_anchor = anchor;
963 selection_cursor = cursor;
964 emit |= SELECTION_CHANGED;
965 handled = true;
968 return handled;
971 bool
972 TextBoxBase::KeyPressDelete (GdkModifierType modifiers)
974 int anchor = selection_anchor;
975 int cursor = selection_cursor;
976 TextBoxUndoAction *action;
977 int start = 0, length = 0;
978 bool handled = false;
980 if ((modifiers & (ALT_MASK | SHIFT_MASK)) != 0)
981 return false;
983 if (cursor != anchor) {
984 // Delete w/ active selection: delete the selected text
985 length = abs (cursor - anchor);
986 start = MIN (anchor, cursor);
987 } else if ((modifiers & CONTROL_MASK) != 0) {
988 // Ctrl+Delete: delete the word starting at the cursor
989 length = CursorNextWord (cursor) - cursor;
990 start = cursor;
991 } else if (cursor < buffer->len) {
992 // Delete: delete the char after the cursor position
993 if (buffer->text[cursor] == '\r' && buffer->text[cursor + 1] == '\n')
994 length = 2;
995 else
996 length = 1;
998 start = cursor;
1001 if (length > 0) {
1002 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1003 undo->Push (action);
1004 redo->Clear ();
1006 buffer->Cut (start, length);
1007 emit |= TEXT_CHANGED;
1008 handled = true;
1011 // check to see if selection has changed
1012 if (selection_anchor != anchor || selection_cursor != cursor) {
1013 SetSelectionStart (MIN (anchor, cursor));
1014 SetSelectionLength (abs (cursor - anchor));
1015 selection_anchor = anchor;
1016 selection_cursor = cursor;
1017 emit |= SELECTION_CHANGED;
1018 handled = true;
1021 return handled;
1024 bool
1025 TextBoxBase::KeyPressPageDown (GdkModifierType modifiers)
1027 int anchor = selection_anchor;
1028 int cursor = selection_cursor;
1029 bool have;
1031 if ((modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1032 return false;
1034 // move the cursor down one page from its current position
1035 cursor = CursorDown (cursor, true);
1036 have = have_offset;
1038 if ((modifiers & SHIFT_MASK) == 0) {
1039 // clobber the selection
1040 anchor = cursor;
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 have_offset = have;
1053 return true;
1056 bool
1057 TextBoxBase::KeyPressPageUp (GdkModifierType modifiers)
1059 int anchor = selection_anchor;
1060 int cursor = selection_cursor;
1061 bool have;
1063 if ((modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1064 return false;
1066 // move the cursor up one page from its current position
1067 cursor = CursorUp (cursor, true);
1068 have = have_offset;
1070 if ((modifiers & SHIFT_MASK) == 0) {
1071 // clobber the selection
1072 anchor = cursor;
1075 // check to see if selection has changed
1076 if (selection_anchor != anchor || selection_cursor != cursor) {
1077 SetSelectionStart (MIN (anchor, cursor));
1078 SetSelectionLength (abs (cursor - anchor));
1079 selection_anchor = anchor;
1080 selection_cursor = cursor;
1081 emit |= SELECTION_CHANGED;
1082 have_offset = have;
1085 return true;
1088 bool
1089 TextBoxBase::KeyPressDown (GdkModifierType modifiers)
1091 int anchor = selection_anchor;
1092 int cursor = selection_cursor;
1093 bool handled = false;
1094 bool have;
1096 if (!accepts_return || (modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1097 return false;
1099 // move the cursor down by one line from its current position
1100 cursor = CursorDown (cursor, false);
1101 have = have_offset;
1103 if ((modifiers & SHIFT_MASK) == 0) {
1104 // clobber the selection
1105 anchor = cursor;
1108 // check to see if selection has changed
1109 if (selection_anchor != anchor || selection_cursor != cursor) {
1110 SetSelectionStart (MIN (anchor, cursor));
1111 SetSelectionLength (abs (cursor - anchor));
1112 selection_anchor = anchor;
1113 selection_cursor = cursor;
1114 emit |= SELECTION_CHANGED;
1115 have_offset = have;
1116 handled = true;
1119 return handled;
1122 bool
1123 TextBoxBase::KeyPressUp (GdkModifierType modifiers)
1125 int anchor = selection_anchor;
1126 int cursor = selection_cursor;
1127 bool handled = false;
1128 bool have;
1130 if (!accepts_return || (modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1131 return false;
1133 // move the cursor up by one line from its current position
1134 cursor = CursorUp (cursor, false);
1135 have = have_offset;
1137 if ((modifiers & SHIFT_MASK) == 0) {
1138 // clobber the selection
1139 anchor = cursor;
1142 // check to see if selection has changed
1143 if (selection_anchor != anchor || selection_cursor != cursor) {
1144 SetSelectionStart (MIN (anchor, cursor));
1145 SetSelectionLength (abs (cursor - anchor));
1146 selection_anchor = anchor;
1147 selection_cursor = cursor;
1148 emit |= SELECTION_CHANGED;
1149 have_offset = have;
1150 handled = true;
1153 return handled;
1156 bool
1157 TextBoxBase::KeyPressHome (GdkModifierType modifiers)
1159 int anchor = selection_anchor;
1160 int cursor = selection_cursor;
1161 bool handled = false;
1163 if ((modifiers & ALT_MASK) != 0)
1164 return false;
1166 if ((modifiers & CONTROL_MASK) != 0) {
1167 // move the cursor to the beginning of the buffer
1168 cursor = 0;
1169 } else {
1170 // move the cursor to the beginning of the line
1171 cursor = CursorLineBegin (cursor);
1174 if ((modifiers & SHIFT_MASK) == 0) {
1175 // clobber the selection
1176 anchor = cursor;
1179 // check to see if selection has changed
1180 if (selection_anchor != anchor || selection_cursor != cursor) {
1181 SetSelectionStart (MIN (anchor, cursor));
1182 SetSelectionLength (abs (cursor - anchor));
1183 selection_anchor = anchor;
1184 selection_cursor = cursor;
1185 emit |= SELECTION_CHANGED;
1186 have_offset = false;
1187 handled = true;
1190 return handled;
1193 bool
1194 TextBoxBase::KeyPressEnd (GdkModifierType modifiers)
1196 int anchor = selection_anchor;
1197 int cursor = selection_cursor;
1198 bool handled = false;
1200 if ((modifiers & ALT_MASK) != 0)
1201 return false;
1203 if ((modifiers & CONTROL_MASK) != 0) {
1204 // move the cursor to the end of the buffer
1205 cursor = buffer->len;
1206 } else {
1207 // move the cursor to the end of the line
1208 cursor = CursorLineEnd (cursor);
1211 if ((modifiers & SHIFT_MASK) == 0) {
1212 // clobber the selection
1213 anchor = cursor;
1216 // check to see if selection has changed
1217 if (selection_anchor != anchor || selection_cursor != cursor) {
1218 SetSelectionStart (MIN (anchor, cursor));
1219 SetSelectionLength (abs (cursor - anchor));
1220 selection_anchor = anchor;
1221 selection_cursor = cursor;
1222 emit |= SELECTION_CHANGED;
1223 have_offset = false;
1224 handled = true;
1227 return handled;
1230 bool
1231 TextBoxBase::KeyPressRight (GdkModifierType modifiers)
1233 int anchor = selection_anchor;
1234 int cursor = selection_cursor;
1235 bool handled = false;
1237 if ((modifiers & ALT_MASK) != 0)
1238 return false;
1240 if ((modifiers & CONTROL_MASK) != 0) {
1241 // move the cursor to beginning of the next word
1242 cursor = CursorNextWord (cursor);
1243 } else if ((modifiers & SHIFT_MASK) == 0 && anchor != cursor) {
1244 // set cursor at end of selection
1245 cursor = MAX (anchor, cursor);
1246 } else {
1247 // move the cursor forward one character
1248 if (buffer->text[cursor] == '\r' && buffer->text[cursor + 1] == '\n')
1249 cursor += 2;
1250 else if (cursor < buffer->len)
1251 cursor++;
1254 if ((modifiers & SHIFT_MASK) == 0) {
1255 // clobber the selection
1256 anchor = cursor;
1259 // check to see if selection has changed
1260 if (selection_anchor != anchor || selection_cursor != cursor) {
1261 SetSelectionStart (MIN (anchor, cursor));
1262 SetSelectionLength (abs (cursor - anchor));
1263 selection_anchor = anchor;
1264 selection_cursor = cursor;
1265 emit |= SELECTION_CHANGED;
1266 handled = true;
1269 return handled;
1272 bool
1273 TextBoxBase::KeyPressLeft (GdkModifierType modifiers)
1275 int anchor = selection_anchor;
1276 int cursor = selection_cursor;
1277 bool handled = false;
1279 if ((modifiers & ALT_MASK) != 0)
1280 return false;
1282 if ((modifiers & CONTROL_MASK) != 0) {
1283 // move the cursor to the beginning of the previous word
1284 cursor = CursorPrevWord (cursor);
1285 } else if ((modifiers & SHIFT_MASK) == 0 && anchor != cursor) {
1286 // set cursor at start of selection
1287 cursor = MIN (anchor, cursor);
1288 } else {
1289 // move the cursor backward one character
1290 if (cursor >= 2 && buffer->text[cursor - 2] == '\r' && buffer->text[cursor - 1] == '\n')
1291 cursor -= 2;
1292 else if (cursor > 0)
1293 cursor--;
1296 if ((modifiers & SHIFT_MASK) == 0) {
1297 // clobber the selection
1298 anchor = cursor;
1301 // check to see if selection has changed
1302 if (selection_anchor != anchor || selection_cursor != cursor) {
1303 SetSelectionStart (MIN (anchor, cursor));
1304 SetSelectionLength (abs (cursor - anchor));
1305 selection_anchor = anchor;
1306 selection_cursor = cursor;
1307 emit |= SELECTION_CHANGED;
1308 handled = true;
1311 return handled;
1314 bool
1315 TextBoxBase::KeyPressUnichar (gunichar c)
1317 int length = abs (selection_cursor - selection_anchor);
1318 int start = MIN (selection_anchor, selection_cursor);
1319 int anchor = selection_anchor;
1320 int cursor = selection_cursor;
1321 TextBoxUndoAction *action;
1323 if ((max_length > 0 && buffer->len >= max_length) || ((c == '\r') && !accepts_return))
1324 return false;
1326 if (length > 0) {
1327 // replace the currently selected text
1328 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, c);
1329 undo->Push (action);
1330 redo->Clear ();
1332 buffer->Replace (start, length, &c, 1);
1333 } else {
1334 // insert the text at the cursor position
1335 TextBoxUndoActionInsert *insert = NULL;
1337 if ((action = undo->Peek ()) && action->type == TextBoxUndoActionTypeInsert) {
1338 insert = (TextBoxUndoActionInsert *) action;
1340 if (!insert->Insert (start, c))
1341 insert = NULL;
1344 if (!insert) {
1345 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, c);
1346 undo->Push (insert);
1349 redo->Clear ();
1351 buffer->Insert (start, c);
1354 emit |= TEXT_CHANGED;
1355 cursor = start + 1;
1356 anchor = cursor;
1358 // check to see if selection has changed
1359 if (selection_anchor != anchor || selection_cursor != cursor) {
1360 SetSelectionStart (MIN (anchor, cursor));
1361 SetSelectionLength (abs (cursor - anchor));
1362 selection_anchor = anchor;
1363 selection_cursor = cursor;
1364 emit |= SELECTION_CHANGED;
1367 return true;
1370 void
1371 TextBoxBase::BatchPush ()
1373 batch++;
1376 void
1377 TextBoxBase::BatchPop ()
1379 if (batch == 0) {
1380 g_warning ("TextBoxBase batch underflow");
1381 return;
1384 batch--;
1387 void
1388 TextBoxBase::SyncAndEmit (bool sync_text)
1390 if (batch != 0 || emit == NOTHING_CHANGED)
1391 return;
1393 if (sync_text && (emit & TEXT_CHANGED))
1394 SyncText ();
1396 if (emit & SELECTION_CHANGED)
1397 SyncSelectedText ();
1399 if (IsLoaded ()) {
1400 // eliminate events that we can't emit
1401 emit &= events_mask;
1403 if (emit & TEXT_CHANGED)
1404 EmitTextChanged ();
1406 if (emit & SELECTION_CHANGED)
1407 EmitSelectionChanged ();
1410 emit = NOTHING_CHANGED;
1413 void
1414 TextBoxBase::Paste (GtkClipboard *clipboard, const char *str)
1416 int length = abs (selection_cursor - selection_anchor);
1417 int start = MIN (selection_anchor, selection_cursor);
1418 TextBoxUndoAction *action;
1419 gunichar *text;
1420 glong len, i;
1422 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1423 return;
1425 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1426 // paste cannot exceed MaxLength
1427 len = max_length - (buffer->len - length);
1428 if (len > 0)
1429 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1430 else
1431 len = 0;
1432 text[len] = '\0';
1435 if (!multiline) {
1436 // only paste the content of the first line
1437 for (i = 0; i < len; i++) {
1438 if (text[i] == '\r' || text[i] == '\n' || text[i] == 0x2028) {
1439 text = (gunichar *) g_realloc (text, UNICODE_LEN (i + 1));
1440 text[i] = '\0';
1441 len = i;
1442 break;
1447 ResetIMContext ();
1449 if (length > 0) {
1450 // replace the currently selected text
1451 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, len);
1453 buffer->Replace (start, length, text, len);
1454 } else if (len > 0) {
1455 // insert the text at the cursor position
1456 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, len, true);
1458 buffer->Insert (start, text, len);
1459 } else {
1460 g_free (text);
1461 return;
1464 undo->Push (action);
1465 redo->Clear ();
1466 g_free (text);
1468 emit |= TEXT_CHANGED;
1469 start += len;
1471 BatchPush ();
1472 SetSelectionStart (start);
1473 SetSelectionLength (0);
1474 BatchPop ();
1476 SyncAndEmit ();
1479 void
1480 TextBoxBase::paste (GtkClipboard *clipboard, const char *text, gpointer closure)
1482 ((TextBoxBase *) closure)->Paste (clipboard, text);
1485 void
1486 TextBoxBase::OnKeyDown (KeyEventArgs *args)
1488 GdkModifierType modifiers = (GdkModifierType) args->GetModifiers ();
1489 guint key = args->GetKeyVal ();
1490 GtkClipboard *clipboard;
1491 bool handled = false;
1493 if (args->IsModifier ())
1494 return;
1496 // set 'emit' to NOTHING_CHANGED so that we can figure out
1497 // what has chanegd after applying the changes that this
1498 // keypress will cause.
1499 emit = NOTHING_CHANGED;
1500 BatchPush ();
1502 switch (key) {
1503 case GDK_BackSpace:
1504 if (is_read_only)
1505 break;
1507 handled = KeyPressBackSpace (modifiers);
1508 break;
1509 case GDK_Delete:
1510 if (is_read_only)
1511 break;
1513 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == SHIFT_MASK) {
1514 // Shift+Delete => Cut
1515 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1516 if (selection_cursor != selection_anchor) {
1517 // copy selection to the clipboard and then cut
1518 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1522 SetSelectedText ("");
1523 handled = true;
1524 } else {
1525 handled = KeyPressDelete (modifiers);
1527 break;
1528 case GDK_Insert:
1529 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == SHIFT_MASK) {
1530 // Shift+Insert => Paste
1531 if (is_read_only)
1532 break;
1534 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1535 // paste clipboard contents to the buffer
1536 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1539 handled = true;
1540 } else if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == CONTROL_MASK) {
1541 // Control+Insert => Copy
1542 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1543 if (selection_cursor != selection_anchor) {
1544 // copy selection to the clipboard
1545 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1549 handled = true;
1551 break;
1552 case GDK_KP_Page_Down:
1553 case GDK_Page_Down:
1554 handled = KeyPressPageDown (modifiers);
1555 break;
1556 case GDK_KP_Page_Up:
1557 case GDK_Page_Up:
1558 handled = KeyPressPageUp (modifiers);
1559 break;
1560 case GDK_KP_Home:
1561 case GDK_Home:
1562 handled = KeyPressHome (modifiers);
1563 break;
1564 case GDK_KP_End:
1565 case GDK_End:
1566 handled = KeyPressEnd (modifiers);
1567 break;
1568 case GDK_KP_Right:
1569 case GDK_Right:
1570 handled = KeyPressRight (modifiers);
1571 break;
1572 case GDK_KP_Left:
1573 case GDK_Left:
1574 handled = KeyPressLeft (modifiers);
1575 break;
1576 case GDK_KP_Down:
1577 case GDK_Down:
1578 handled = KeyPressDown (modifiers);
1579 break;
1580 case GDK_KP_Up:
1581 case GDK_Up:
1582 handled = KeyPressUp (modifiers);
1583 break;
1584 default:
1585 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == CONTROL_MASK) {
1586 switch (key) {
1587 case GDK_A:
1588 case GDK_a:
1589 // Ctrl+A => Select All
1590 handled = true;
1591 SelectAll ();
1592 break;
1593 case GDK_C:
1594 case GDK_c:
1595 // Ctrl+C => Copy
1596 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1597 if (selection_cursor != selection_anchor) {
1598 // copy selection to the clipboard
1599 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1603 handled = true;
1604 break;
1605 case GDK_X:
1606 case GDK_x:
1607 // Ctrl+X => Cut
1608 if (is_read_only)
1609 break;
1611 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1612 if (selection_cursor != selection_anchor) {
1613 // copy selection to the clipboard and then cut
1614 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1618 SetSelectedText ("");
1619 handled = true;
1620 break;
1621 case GDK_V:
1622 case GDK_v:
1623 // Ctrl+V => Paste
1624 if (is_read_only)
1625 break;
1627 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1628 // paste clipboard contents to the buffer
1629 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1632 handled = true;
1633 break;
1634 case GDK_Y:
1635 case GDK_y:
1636 // Ctrl+Y => Redo
1637 if (!is_read_only) {
1638 handled = true;
1639 Redo ();
1641 break;
1642 case GDK_Z:
1643 case GDK_z:
1644 // Ctrl+Z => Undo
1645 if (!is_read_only) {
1646 handled = true;
1647 Undo ();
1649 break;
1650 default:
1651 // unhandled Control commands
1652 break;
1655 break;
1658 if (handled) {
1659 args->SetHandled (handled);
1660 ResetIMContext ();
1663 BatchPop ();
1665 SyncAndEmit ();
1668 void
1669 TextBoxBase::PostOnKeyDown (KeyEventArgs *args)
1671 guint key = args->GetKeyVal ();
1672 gunichar c;
1674 // Note: we don't set Handled=true because anything we handle here, we
1675 // want to bubble up.
1677 if (!is_read_only && gtk_im_context_filter_keypress (im_ctx, args->GetEvent ())) {
1678 need_im_reset = true;
1679 return;
1682 if (is_read_only || args->IsModifier ())
1683 return;
1685 // set 'emit' to NOTHING_CHANGED so that we can figure out
1686 // what has chanegd after applying the changes that this
1687 // keypress will cause.
1688 emit = NOTHING_CHANGED;
1689 BatchPush ();
1691 switch (key) {
1692 case GDK_Return:
1693 KeyPressUnichar ('\r');
1694 break;
1695 default:
1696 if ((args->GetModifiers () & (CONTROL_MASK | ALT_MASK)) == 0) {
1697 // normal character input
1698 if ((c = args->GetUnicode ()))
1699 KeyPressUnichar (c);
1701 break;
1704 BatchPop ();
1706 SyncAndEmit ();
1709 void
1710 TextBoxBase::OnKeyUp (KeyEventArgs *args)
1712 if (!is_read_only) {
1713 if (gtk_im_context_filter_keypress (im_ctx, args->GetEvent ()))
1714 need_im_reset = true;
1718 bool
1719 TextBoxBase::DeleteSurrounding (int offset, int n_chars)
1721 const char *delete_start, *delete_end;
1722 const char *text = GetActualText ();
1723 int anchor = selection_anchor;
1724 int cursor = selection_cursor;
1725 TextBoxUndoAction *action;
1726 int start, length;
1728 if (is_read_only)
1729 return true;
1731 // get the utf-8 pointers so that we can use them to get gunichar offsets
1732 delete_start = g_utf8_offset_to_pointer (text, selection_cursor) + offset;
1733 delete_end = delete_start + n_chars;
1735 // get the character length/start index
1736 length = g_utf8_pointer_to_offset (delete_start, delete_end);
1737 start = g_utf8_pointer_to_offset (text, delete_start);
1739 if (length > 0) {
1740 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1741 undo->Push (action);
1742 redo->Clear ();
1744 buffer->Cut (start, length);
1745 emit |= TEXT_CHANGED;
1746 anchor = start;
1747 cursor = start;
1750 BatchPush ();
1752 // check to see if selection has changed
1753 if (selection_anchor != anchor || selection_cursor != cursor) {
1754 SetSelectionStart (MIN (anchor, cursor));
1755 SetSelectionLength (abs (cursor - anchor));
1756 selection_anchor = anchor;
1757 selection_cursor = cursor;
1758 emit |= SELECTION_CHANGED;
1761 BatchPop ();
1763 SyncAndEmit ();
1765 return true;
1768 gboolean
1769 TextBoxBase::delete_surrounding (GtkIMContext *context, int offset, int n_chars, gpointer user_data)
1771 return ((TextBoxBase *) user_data)->DeleteSurrounding (offset, n_chars);
1774 bool
1775 TextBoxBase::RetrieveSurrounding ()
1777 const char *text = GetActualText ();
1778 const char *cursor = g_utf8_offset_to_pointer (text, selection_cursor);
1780 gtk_im_context_set_surrounding (im_ctx, text, -1, cursor - text);
1782 return true;
1785 gboolean
1786 TextBoxBase::retrieve_surrounding (GtkIMContext *context, gpointer user_data)
1788 return ((TextBoxBase *) user_data)->RetrieveSurrounding ();
1791 void
1792 TextBoxBase::Commit (const char *str)
1794 int length = abs (selection_cursor - selection_anchor);
1795 int start = MIN (selection_anchor, selection_cursor);
1796 TextBoxUndoAction *action;
1797 int anchor, cursor;
1798 gunichar *text;
1799 glong len, i;
1801 if (is_read_only)
1802 return;
1804 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1805 return;
1807 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1808 // paste cannot exceed MaxLength
1809 len = max_length - (buffer->len - length);
1810 if (len > 0)
1811 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1812 else
1813 len = 0;
1814 text[len] = '\0';
1817 if (!multiline) {
1818 // only paste the content of the first line
1819 for (i = 0; i < len; i++) {
1820 if (g_unichar_type (text[i]) == G_UNICODE_LINE_SEPARATOR) {
1821 text = (gunichar *) g_realloc (text, UNICODE_LEN (i + 1));
1822 text[i] = '\0';
1823 len = i;
1824 break;
1829 if (length > 0) {
1830 // replace the currently selected text
1831 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, len);
1832 undo->Push (action);
1833 redo->Clear ();
1835 buffer->Replace (start, length, text, len);
1836 } else if (len > 0) {
1837 // insert the text at the cursor position
1838 TextBoxUndoActionInsert *insert = NULL;
1840 buffer->Insert (start, text, len);
1842 if ((action = undo->Peek ()) && action->type == TextBoxUndoActionTypeInsert) {
1843 insert = (TextBoxUndoActionInsert *) action;
1845 if (!insert->Insert (start, text, len))
1846 insert = NULL;
1849 if (!insert) {
1850 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, len);
1851 undo->Push (insert);
1854 redo->Clear ();
1855 } else {
1856 g_free (text);
1857 return;
1860 emit = TEXT_CHANGED;
1861 cursor = start + len;
1862 anchor = cursor;
1863 g_free (text);
1865 BatchPush ();
1867 // check to see if selection has changed
1868 if (selection_anchor != anchor || selection_cursor != cursor) {
1869 SetSelectionStart (MIN (anchor, cursor));
1870 SetSelectionLength (abs (cursor - anchor));
1871 selection_anchor = anchor;
1872 selection_cursor = cursor;
1873 emit |= SELECTION_CHANGED;
1876 BatchPop ();
1878 SyncAndEmit ();
1881 void
1882 TextBoxBase::commit (GtkIMContext *context, const char *str, gpointer user_data)
1884 ((TextBoxBase *) user_data)->Commit (str);
1887 void
1888 TextBoxBase::ResetIMContext ()
1890 if (need_im_reset) {
1891 gtk_im_context_reset (im_ctx);
1892 need_im_reset = false;
1896 void
1897 TextBoxBase::OnMouseLeftButtonDown (MouseButtonEventArgs *args)
1899 double x, y;
1900 int cursor;
1902 args->SetHandled (true);
1903 Focus ();
1905 if (view) {
1906 args->GetPosition (view, &x, &y);
1908 cursor = view->GetCursorFromXY (x, y);
1910 ResetIMContext ();
1912 // Single-Click: cursor placement
1913 captured = CaptureMouse ();
1914 selecting = true;
1916 BatchPush ();
1917 emit = NOTHING_CHANGED;
1918 SetSelectionStart (cursor);
1919 SetSelectionLength (0);
1920 BatchPop ();
1922 SyncAndEmit ();
1926 void
1927 TextBoxBase::OnMouseLeftButtonMultiClick (MouseButtonEventArgs *args)
1929 int cursor, start, end;
1930 double x, y;
1932 args->SetHandled (true);
1934 if (view) {
1935 args->GetPosition (view, &x, &y);
1937 cursor = view->GetCursorFromXY (x, y);
1939 ResetIMContext ();
1941 if (args->GetClickCount () == 3) {
1942 // Note: Silverlight doesn't implement this, but to
1943 // be consistent with other TextEntry-type
1944 // widgets in Gtk+, we will.
1946 // Triple-Click: select the line
1947 if (captured)
1948 ReleaseMouseCapture ();
1949 start = CursorLineBegin (cursor);
1950 end = CursorLineEnd (cursor, true);
1951 selecting = false;
1952 captured = false;
1953 } else {
1954 // Double-Click: select the word
1955 if (captured)
1956 ReleaseMouseCapture ();
1957 start = CursorPrevWord (cursor);
1958 end = CursorNextWord (cursor);
1959 selecting = false;
1960 captured = false;
1963 BatchPush ();
1964 emit = NOTHING_CHANGED;
1965 SetSelectionStart (start);
1966 SetSelectionLength (end - start);
1967 BatchPop ();
1969 SyncAndEmit ();
1973 void
1974 TextBoxBase::mouse_left_button_multi_click (EventObject *sender, EventArgs *args, gpointer closure)
1976 ((TextBoxBase *) closure)->OnMouseLeftButtonMultiClick ((MouseButtonEventArgs *) args);
1979 void
1980 TextBoxBase::OnMouseLeftButtonUp (MouseButtonEventArgs *args)
1982 if (captured)
1983 ReleaseMouseCapture ();
1985 args->SetHandled (true);
1986 selecting = false;
1987 captured = false;
1990 void
1991 TextBoxBase::OnMouseMove (MouseEventArgs *args)
1993 int anchor = selection_anchor;
1994 int cursor = selection_cursor;
1995 GtkClipboard *clipboard;
1996 double x, y;
1998 if (selecting) {
1999 args->GetPosition (view, &x, &y);
2000 args->SetHandled (true);
2002 cursor = view->GetCursorFromXY (x, y);
2004 BatchPush ();
2005 emit = NOTHING_CHANGED;
2006 SetSelectionStart (MIN (anchor, cursor));
2007 SetSelectionLength (abs (cursor - anchor));
2008 selection_anchor = anchor;
2009 selection_cursor = cursor;
2010 BatchPop ();
2012 SyncAndEmit ();
2014 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_PRIMARY))) {
2015 // copy the selection to the primary clipboard
2016 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
2021 void
2022 TextBoxBase::OnLostFocus (RoutedEventArgs *args)
2024 BatchPush ();
2025 emit = NOTHING_CHANGED;
2026 SetSelectionStart (selection_cursor);
2027 SetSelectionLength (0);
2028 BatchPop ();
2030 SyncAndEmit ();
2032 focused = false;
2034 if (view)
2035 view->OnLostFocus ();
2037 if (!is_read_only) {
2038 gtk_im_context_focus_out (im_ctx);
2039 need_im_reset = true;
2043 void
2044 TextBoxBase::OnGotFocus (RoutedEventArgs *args)
2046 focused = true;
2048 if (view)
2049 view->OnGotFocus ();
2051 if (!is_read_only) {
2052 gtk_im_context_focus_in (im_ctx);
2053 need_im_reset = true;
2057 void
2058 TextBoxBase::EmitCursorPositionChanged (double height, double x, double y)
2060 Emit (TextBoxBase::CursorPositionChangedEvent, new CursorPositionChangedEventArgs (height, x, y));
2063 void
2064 TextBoxBase::DownloaderComplete (Downloader *downloader)
2066 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2067 char *resource, *filename;
2068 InternalDownloader *idl;
2069 const char *path;
2070 Uri *uri;
2072 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2073 if (!(filename = downloader->GetDownloadedFilename (NULL)))
2074 return;
2076 g_free (filename);
2078 if (!(idl = downloader->GetInternalDownloader ()))
2079 return;
2081 if (!(idl->GetObjectType () == Type::FILEDOWNLOADER))
2082 return;
2084 uri = downloader->GetUri ();
2086 // If the downloaded file was a zip file, this'll get the path to the
2087 // extracted zip directory, else it will simply be the path to the
2088 // downloaded file.
2089 if (!(path = ((FileDownloader *) idl)->GetUnzippedPath ()))
2090 return;
2092 resource = uri->ToString ((UriToStringFlags) (UriHidePasswd | UriHideQuery | UriHideFragment));
2093 manager->AddResource (resource, path);
2094 g_free (resource);
2096 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont, NULL));
2099 void
2100 TextBoxBase::downloader_complete (EventObject *sender, EventArgs *calldata, gpointer closure)
2102 ((TextBoxBase *) closure)->DownloaderComplete ((Downloader *) sender);
2105 void
2106 TextBoxBase::AddFontSource (Downloader *downloader)
2108 downloader->AddHandler (downloader->CompletedEvent, downloader_complete, this);
2109 g_ptr_array_add (downloaders, downloader);
2110 downloader->ref ();
2112 if (downloader->Started () || downloader->Completed ()) {
2113 if (downloader->Completed ())
2114 DownloaderComplete (downloader);
2115 } else {
2116 // This is what actually triggers the download
2117 downloader->Send ();
2121 void
2122 TextBoxBase::AddFontResource (const char *resource)
2124 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2125 Application *application = Application::GetCurrent ();
2126 Downloader *downloader;
2127 char *path;
2128 Uri *uri;
2130 uri = new Uri ();
2132 if (!application || !uri->Parse (resource) || !(path = application->GetResourceAsPath (GetResourceBase(), uri))) {
2133 if (IsAttached () && (downloader = GetDeployment ()->GetSurface ()->CreateDownloader ())) {
2134 downloader->Open ("GET", resource, FontPolicy);
2135 AddFontSource (downloader);
2136 downloader->unref ();
2139 delete uri;
2141 return;
2144 manager->AddResource (resource, path);
2145 g_free (path);
2146 delete uri;
2149 void
2150 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2152 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2154 if (args->GetId () == Control::FontFamilyProperty) {
2155 FontFamily *family = args->GetNewValue () ? args->GetNewValue ()->AsFontFamily () : NULL;
2156 char **families, *fragment;
2157 int i;
2159 CleanupDownloaders ();
2161 if (family && family->source) {
2162 families = g_strsplit (family->source, ",", -1);
2163 for (i = 0; families[i]; i++) {
2164 g_strstrip (families[i]);
2165 if ((fragment = strchr (families[i], '#'))) {
2166 // the first portion of this string is the resource name...
2167 *fragment = '\0';
2168 AddFontResource (families[i]);
2171 g_strfreev (families);
2174 font->SetFamily (family ? family->source : NULL);
2175 changed = TextBoxModelChangedFont;
2176 } else if (args->GetId () == Control::FontSizeProperty) {
2177 double size = args->GetNewValue()->AsDouble ();
2178 changed = TextBoxModelChangedFont;
2179 font->SetSize (size);
2180 } else if (args->GetId () == Control::FontStretchProperty) {
2181 FontStretches stretch = args->GetNewValue()->AsFontStretch()->stretch;
2182 changed = TextBoxModelChangedFont;
2183 font->SetStretch (stretch);
2184 } else if (args->GetId () == Control::FontStyleProperty) {
2185 FontStyles style = args->GetNewValue()->AsFontStyle ()->style;
2186 changed = TextBoxModelChangedFont;
2187 font->SetStyle (style);
2188 } else if (args->GetId () == Control::FontWeightProperty) {
2189 FontWeights weight = args->GetNewValue()->AsFontWeight ()->weight;
2190 changed = TextBoxModelChangedFont;
2191 font->SetWeight (weight);
2192 } else if (args->GetId () == FrameworkElement::MinHeightProperty) {
2193 // pass this along to our TextBoxView
2194 if (view)
2195 view->SetMinHeight (args->GetNewValue ()->AsDouble ());
2196 } else if (args->GetId () == FrameworkElement::MaxHeightProperty) {
2197 // pass this along to our TextBoxView
2198 if (view)
2199 view->SetMaxHeight (args->GetNewValue ()->AsDouble ());
2200 } else if (args->GetId () == FrameworkElement::MinWidthProperty) {
2201 // pass this along to our TextBoxView
2202 if (view)
2203 view->SetMinWidth (args->GetNewValue ()->AsDouble ());
2204 } else if (args->GetId () == FrameworkElement::MaxWidthProperty) {
2205 // pass this along to our TextBoxView
2206 if (view)
2207 view->SetMaxWidth (args->GetNewValue ()->AsDouble ());
2208 } else if (args->GetId () == FrameworkElement::HeightProperty) {
2209 // pass this along to our TextBoxView
2210 if (view)
2211 view->SetHeight (args->GetNewValue ()->AsDouble ());
2212 } else if (args->GetId () == FrameworkElement::WidthProperty) {
2213 // pass this along to our TextBoxView
2214 if (view)
2215 view->SetWidth (args->GetNewValue ()->AsDouble ());
2218 if (changed != TextBoxModelChangedNothing)
2219 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2221 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE) {
2222 Control::OnPropertyChanged (args, error);
2223 return;
2226 NotifyListenersOfPropertyChange (args, error);
2229 void
2230 TextBoxBase::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2232 if (prop && (prop->GetId () == Control::BackgroundProperty ||
2233 prop->GetId () == Control::ForegroundProperty)) {
2234 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
2235 Invalidate ();
2238 if (prop->GetOwnerType () != Type::TEXTBOXBASE)
2239 Control::OnSubPropertyChanged (prop, obj, subobj_args);
2242 void
2243 TextBoxBase::OnApplyTemplate ()
2245 contentElement = GetTemplateChild ("ContentElement");
2247 if (contentElement == NULL) {
2248 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2249 Control::OnApplyTemplate ();
2250 return;
2253 view = new TextBoxView ();
2254 view->SetTextBox (this);
2256 view->SetMinHeight (GetMinHeight ());
2257 view->SetMaxHeight (GetMaxHeight ());
2258 view->SetMinWidth (GetMinWidth ());
2259 view->SetMaxWidth (GetMaxWidth ());
2260 view->SetHeight (GetHeight ());
2261 view->SetWidth (GetWidth ());
2263 // Insert our TextBoxView
2264 if (contentElement->Is (Type::CONTENTCONTROL)) {
2265 ContentControl *control = (ContentControl *) contentElement;
2267 control->SetValue (ContentControl::ContentProperty, Value (view));
2268 } else if (contentElement->Is (Type::BORDER)) {
2269 Border *border = (Border *) contentElement;
2271 border->SetValue (Border::ChildProperty, Value (view));
2272 } else if (contentElement->Is (Type::PANEL)) {
2273 DependencyObjectCollection *children = ((Panel *) contentElement)->GetChildren ();
2275 children->Add (view);
2276 } else {
2277 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentElement of type %s",
2278 contentElement->GetType ()->GetName ());
2279 view->unref ();
2280 view = NULL;
2283 Control::OnApplyTemplate ();
2286 void
2287 TextBoxBase::ClearSelection (int start)
2289 BatchPush ();
2290 SetSelectionStart (start);
2291 SetSelectionLength (0);
2292 BatchPop ();
2295 bool
2296 TextBoxBase::SelectWithError (int start, int length, MoonError *error)
2298 if (start < 0) {
2299 MoonError::FillIn (error, MoonError::ARGUMENT, "selection start must be >= 0");
2300 return false;
2303 if (length < 0) {
2304 MoonError::FillIn (error, MoonError::ARGUMENT, "selection length must be >= 0");
2305 return false;
2308 if (start > buffer->len)
2309 start = buffer->len;
2311 if (length > (buffer->len - start))
2312 length = (buffer->len - start);
2314 BatchPush ();
2315 SetSelectionStart (start);
2316 SetSelectionLength (length);
2317 BatchPop ();
2319 ResetIMContext ();
2321 SyncAndEmit ();
2323 return true;
2326 void
2327 TextBoxBase::SelectAll ()
2329 SelectWithError (0, buffer->len, NULL);
2332 bool
2333 TextBoxBase::CanUndo ()
2335 return !undo->IsEmpty ();
2338 bool
2339 TextBoxBase::CanRedo ()
2341 return !redo->IsEmpty ();
2344 void
2345 TextBoxBase::Undo ()
2347 TextBoxUndoActionReplace *replace;
2348 TextBoxUndoActionInsert *insert;
2349 TextBoxUndoActionDelete *dele;
2350 TextBoxUndoAction *action;
2351 int anchor = 0, cursor = 0;
2353 if (undo->IsEmpty ())
2354 return;
2356 action = undo->Pop ();
2357 redo->Push (action);
2359 switch (action->type) {
2360 case TextBoxUndoActionTypeInsert:
2361 insert = (TextBoxUndoActionInsert *) action;
2363 buffer->Cut (insert->start, insert->length);
2364 anchor = action->selection_anchor;
2365 cursor = action->selection_cursor;
2366 break;
2367 case TextBoxUndoActionTypeDelete:
2368 dele = (TextBoxUndoActionDelete *) action;
2370 buffer->Insert (dele->start, dele->text, dele->length);
2371 anchor = action->selection_anchor;
2372 cursor = action->selection_cursor;
2373 break;
2374 case TextBoxUndoActionTypeReplace:
2375 replace = (TextBoxUndoActionReplace *) action;
2377 buffer->Cut (replace->start, replace->inlen);
2378 buffer->Insert (replace->start, replace->deleted, replace->length);
2379 anchor = action->selection_anchor;
2380 cursor = action->selection_cursor;
2381 break;
2384 BatchPush ();
2385 SetSelectionStart (MIN (anchor, cursor));
2386 SetSelectionLength (abs (cursor - anchor));
2387 emit = TEXT_CHANGED | SELECTION_CHANGED;
2388 selection_anchor = anchor;
2389 selection_cursor = cursor;
2390 BatchPop ();
2392 SyncAndEmit ();
2395 void
2396 TextBoxBase::Redo ()
2398 TextBoxUndoActionReplace *replace;
2399 TextBoxUndoActionInsert *insert;
2400 TextBoxUndoActionDelete *dele;
2401 TextBoxUndoAction *action;
2402 int anchor = 0, cursor = 0;
2404 if (redo->IsEmpty ())
2405 return;
2407 action = redo->Pop ();
2408 undo->Push (action);
2410 switch (action->type) {
2411 case TextBoxUndoActionTypeInsert:
2412 insert = (TextBoxUndoActionInsert *) action;
2414 buffer->Insert (insert->start, insert->buffer->text, insert->buffer->len);
2415 anchor = cursor = insert->start + insert->buffer->len;
2416 break;
2417 case TextBoxUndoActionTypeDelete:
2418 dele = (TextBoxUndoActionDelete *) action;
2420 buffer->Cut (dele->start, dele->length);
2421 anchor = cursor = dele->start;
2422 break;
2423 case TextBoxUndoActionTypeReplace:
2424 replace = (TextBoxUndoActionReplace *) action;
2426 buffer->Cut (replace->start, replace->length);
2427 buffer->Insert (replace->start, replace->inserted, replace->inlen);
2428 anchor = cursor = replace->start + replace->inlen;
2429 break;
2432 BatchPush ();
2433 SetSelectionStart (MIN (anchor, cursor));
2434 SetSelectionLength (abs (cursor - anchor));
2435 emit = TEXT_CHANGED | SELECTION_CHANGED;
2436 selection_anchor = anchor;
2437 selection_cursor = cursor;
2438 BatchPop ();
2440 SyncAndEmit ();
2445 // TextBoxDynamicPropertyValueProvider
2448 class TextBoxDynamicPropertyValueProvider : public FrameworkElementProvider {
2449 Value *selection_background;
2450 Value *selection_foreground;
2452 public:
2453 TextBoxDynamicPropertyValueProvider (DependencyObject *obj, PropertyPrecedence precedence) : FrameworkElementProvider (obj, precedence)
2455 selection_background = NULL;
2456 selection_foreground = NULL;
2459 virtual ~TextBoxDynamicPropertyValueProvider ()
2461 delete selection_background;
2462 delete selection_foreground;
2465 virtual Value *GetPropertyValue (DependencyProperty *property)
2467 if (property->GetId () == TextBox::SelectionBackgroundProperty) {
2468 return selection_background;
2469 } else if (property->GetId () == TextBox::SelectionForegroundProperty) {
2470 return selection_foreground;
2473 return FrameworkElementProvider::GetPropertyValue (property);
2476 void InitializeSelectionBrushes ()
2478 if (!selection_background)
2479 selection_background = Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2481 if (!selection_foreground)
2482 selection_foreground = Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2488 // TextBox
2491 TextBox::TextBox ()
2493 providers[PropertyPrecedence_DynamicValue] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2495 Initialize (Type::TEXTBOX, "System.Windows.Controls.TextBox");
2496 events_mask = TEXT_CHANGED | SELECTION_CHANGED;
2497 multiline = true;
2500 void
2501 TextBox::EmitSelectionChanged ()
2503 EmitAsync (TextBox::SelectionChangedEvent, new RoutedEventArgs ());
2506 void
2507 TextBox::EmitTextChanged ()
2509 EmitAsync (TextBox::TextChangedEvent, new TextChangedEventArgs ());
2512 void
2513 TextBox::SyncSelectedText ()
2515 if (selection_cursor != selection_anchor) {
2516 int length = abs (selection_cursor - selection_anchor);
2517 int start = MIN (selection_anchor, selection_cursor);
2518 char *text;
2520 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2522 setvalue = false;
2523 SetValue (TextBox::SelectedTextProperty, Value (text, true));
2524 setvalue = true;
2525 } else {
2526 setvalue = false;
2527 SetValue (TextBox::SelectedTextProperty, Value (""));
2528 setvalue = true;
2532 void
2533 TextBox::SyncText ()
2535 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
2537 setvalue = false;
2538 SetValue (TextBox::TextProperty, Value (text, true));
2539 setvalue = true;
2542 void
2543 TextBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2545 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2546 DependencyProperty *prop;
2547 int start, length;
2549 if (args->GetId () == TextBox::AcceptsReturnProperty) {
2550 // update accepts_return state
2551 accepts_return = args->GetNewValue ()->AsBool ();
2552 } else if (args->GetId () == TextBox::CaretBrushProperty) {
2553 // FIXME: if we want to be perfect, we could invalidate the
2554 // blinking cursor rect if it is active... but is it that
2555 // important?
2556 } else if (args->GetId () == TextBox::FontSourceProperty) {
2557 FontSource *source = args->GetNewValue () ? args->GetNewValue ()->AsFontSource () : NULL;
2558 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2560 // FIXME: ideally we'd remove the old item from the cache (or,
2561 // rather, 'unref' it since some other textblocks/boxes might
2562 // still be using it).
2564 g_free (font_source);
2566 if (source && source->stream)
2567 font_source = manager->AddResource (source->stream);
2568 else
2569 font_source = NULL;
2571 changed = TextBoxModelChangedFont;
2572 font->SetSource (font_source);
2573 } else if (args->GetId () == TextBox::IsReadOnlyProperty) {
2574 // update is_read_only state
2575 is_read_only = args->GetNewValue ()->AsBool ();
2577 if (focused) {
2578 if (is_read_only) {
2579 ResetIMContext ();
2580 gtk_im_context_focus_out (im_ctx);
2581 } else {
2582 gtk_im_context_focus_in (im_ctx);
2585 } else if (args->GetId () == TextBox::MaxLengthProperty) {
2586 // update max_length state
2587 max_length = args->GetNewValue ()->AsInt32 ();
2588 } else if (args->GetId () == TextBox::SelectedTextProperty) {
2589 if (setvalue) {
2590 Value *value = args->GetNewValue ();
2591 const char *str = value && value->AsString () ? value->AsString () : "";
2592 TextBoxUndoAction *action = NULL;
2593 gunichar *text;
2594 glong textlen;
2596 length = abs (selection_cursor - selection_anchor);
2597 start = MIN (selection_anchor, selection_cursor);
2599 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2600 if (length > 0) {
2601 // replace the currently selected text
2602 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, textlen);
2604 buffer->Replace (start, length, text, textlen);
2605 } else if (textlen > 0) {
2606 // insert the text at the cursor
2607 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, textlen);
2609 buffer->Insert (start, text, textlen);
2612 g_free (text);
2614 if (action != NULL) {
2615 emit |= TEXT_CHANGED;
2616 undo->Push (action);
2617 redo->Clear ();
2619 ClearSelection (start + textlen);
2620 ResetIMContext ();
2622 SyncAndEmit ();
2624 } else {
2625 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str);
2628 } else if (args->GetId () == TextBox::SelectionStartProperty) {
2629 length = abs (selection_cursor - selection_anchor);
2630 start = args->GetNewValue ()->AsInt32 ();
2632 if (start > buffer->len) {
2633 // clamp the selection start offset to a valid value
2634 SetSelectionStart (buffer->len);
2635 return;
2638 if (start + length > buffer->len) {
2639 // clamp the selection length to a valid value
2640 BatchPush ();
2641 length = buffer->len - start;
2642 SetSelectionLength (length);
2643 BatchPop ();
2646 // SelectionStartProperty is marked as AlwaysChange -
2647 // if the value hasn't actually changed, then we do
2648 // not want to emit the TextBoxModelChanged event.
2649 if (selection_anchor != start) {
2650 changed = TextBoxModelChangedSelection;
2651 have_offset = false;
2654 // When set programatically, anchor is always the
2655 // start and cursor is always the end.
2656 selection_cursor = start + length;
2657 selection_anchor = start;
2659 emit |= SELECTION_CHANGED;
2661 SyncAndEmit ();
2662 } else if (args->GetId () == TextBox::SelectionLengthProperty) {
2663 start = MIN (selection_anchor, selection_cursor);
2664 length = args->GetNewValue ()->AsInt32 ();
2666 if (start + length > buffer->len) {
2667 // clamp the selection length to a valid value
2668 length = buffer->len - start;
2669 SetSelectionLength (length);
2670 return;
2673 // SelectionLengthProperty is marked as AlwaysChange -
2674 // if the value hasn't actually changed, then we do
2675 // not want to emit the TextBoxModelChanged event.
2676 if (selection_cursor != start + length) {
2677 changed = TextBoxModelChangedSelection;
2678 have_offset = false;
2681 // When set programatically, anchor is always the
2682 // start and cursor is always the end.
2683 selection_cursor = start + length;
2684 selection_anchor = start;
2686 emit |= SELECTION_CHANGED;
2688 SyncAndEmit ();
2689 } else if (args->GetId () == TextBox::SelectionBackgroundProperty) {
2690 changed = TextBoxModelChangedBrush;
2691 } else if (args->GetId () == TextBox::SelectionForegroundProperty) {
2692 changed = TextBoxModelChangedBrush;
2693 } else if (args->GetId () == TextBox::TextProperty) {
2694 if (setvalue) {
2695 Value *value = args->GetNewValue ();
2696 const char *str = value && value->AsString () ? value->AsString () : "";
2697 TextBoxUndoAction *action;
2698 gunichar *text;
2699 glong textlen;
2701 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2702 if (buffer->len > 0) {
2703 // replace the current text
2704 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, 0, buffer->len, text, textlen);
2706 buffer->Replace (0, buffer->len, text, textlen);
2707 } else {
2708 // insert the text
2709 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
2711 buffer->Insert (0, text, textlen);
2714 undo->Push (action);
2715 redo->Clear ();
2716 g_free (text);
2718 emit |= TEXT_CHANGED;
2719 ClearSelection (0);
2720 ResetIMContext ();
2722 SyncAndEmit (value && !value->GetIsNull ());
2723 } else {
2724 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str);
2728 changed = TextBoxModelChangedText;
2729 } else if (args->GetId () == TextBox::TextAlignmentProperty) {
2730 changed = TextBoxModelChangedTextAlignment;
2731 } else if (args->GetId () == TextBox::TextWrappingProperty) {
2732 changed = TextBoxModelChangedTextWrapping;
2733 } else if (args->GetId () == TextBox::HorizontalScrollBarVisibilityProperty) {
2734 // XXX more crap because these aren't templatebound.
2735 if (contentElement) {
2736 if ((prop = contentElement->GetDependencyProperty ("VerticalScrollBarVisibility")))
2737 contentElement->SetValue (prop, GetValue (TextBox::VerticalScrollBarVisibilityProperty));
2740 Invalidate ();
2741 } else if (args->GetId () == TextBox::VerticalScrollBarVisibilityProperty) {
2742 // XXX more crap because these aren't templatebound.
2743 if (contentElement) {
2744 if ((prop = contentElement->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2745 contentElement->SetValue (prop, GetValue (TextBox::HorizontalScrollBarVisibilityProperty));
2748 Invalidate ();
2751 if (changed != TextBoxModelChangedNothing)
2752 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2754 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
2755 TextBoxBase::OnPropertyChanged (args, error);
2756 return;
2759 NotifyListenersOfPropertyChange (args, error);
2762 void
2763 TextBox::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2765 if (prop && (prop->GetId () == TextBox::SelectionBackgroundProperty ||
2766 prop->GetId () == TextBox::SelectionForegroundProperty)) {
2767 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
2768 Invalidate ();
2771 if (prop->GetOwnerType () != Type::TEXTBOX)
2772 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
2775 void
2776 TextBox::OnApplyTemplate ()
2778 DependencyProperty *prop;
2780 TextBoxBase::OnApplyTemplate ();
2782 if (!contentElement)
2783 return;
2785 // XXX LAME these should be template bindings in the textbox template.
2786 if ((prop = contentElement->GetDependencyProperty ("VerticalScrollBarVisibility")))
2787 contentElement->SetValue (prop, GetValue (TextBox::VerticalScrollBarVisibilityProperty));
2789 if ((prop = contentElement->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2790 contentElement->SetValue (prop, GetValue (TextBox::HorizontalScrollBarVisibilityProperty));
2795 // PasswordBox
2799 // PasswordBoxDynamicPropertyValueProvider
2802 class PasswordBoxDynamicPropertyValueProvider : public FrameworkElementProvider {
2803 Value *selection_background;
2804 Value *selection_foreground;
2806 public:
2807 PasswordBoxDynamicPropertyValueProvider (DependencyObject *obj, PropertyPrecedence precedence) : FrameworkElementProvider (obj, precedence)
2809 selection_background = NULL;
2810 selection_foreground = NULL;
2813 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2815 delete selection_background;
2816 delete selection_foreground;
2819 virtual Value *GetPropertyValue (DependencyProperty *property)
2821 if (property->GetId () == PasswordBox::SelectionBackgroundProperty) {
2822 return selection_background;
2823 } else if (property->GetId () == PasswordBox::SelectionForegroundProperty) {
2824 return selection_foreground;
2827 return FrameworkElementProvider::GetPropertyValue (property);
2830 void InitializeSelectionBrushes ()
2832 if (!selection_background)
2833 selection_background = Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2835 if (!selection_foreground)
2836 selection_foreground = Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2842 // PasswordBox
2845 PasswordBox::PasswordBox ()
2847 providers[PropertyPrecedence_DynamicValue] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2849 Initialize (Type::PASSWORDBOX, "System.Windows.Controls.PasswordBox");
2850 events_mask = TEXT_CHANGED;
2851 secret = true;
2853 display = g_string_new ("");
2856 PasswordBox::~PasswordBox ()
2858 g_string_free (display, true);
2862 PasswordBox::CursorDown (int cursor, bool page)
2864 return GetBuffer ()->len;
2868 PasswordBox::CursorUp (int cursor, bool page)
2870 return 0;
2874 PasswordBox::CursorLineBegin (int cursor)
2876 return 0;
2880 PasswordBox::CursorLineEnd (int cursor, bool include)
2882 return GetBuffer ()->len;
2886 PasswordBox::CursorNextWord (int cursor)
2888 return GetBuffer ()->len;
2892 PasswordBox::CursorPrevWord (int cursor)
2894 return 0;
2897 void
2898 PasswordBox::EmitTextChanged ()
2900 EmitAsync (PasswordBox::PasswordChangedEvent, new RoutedEventArgs ());
2903 void
2904 PasswordBox::SyncSelectedText ()
2906 if (selection_cursor != selection_anchor) {
2907 int length = abs (selection_cursor - selection_anchor);
2908 int start = MIN (selection_anchor, selection_cursor);
2909 char *text;
2911 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2913 setvalue = false;
2914 SetValue (PasswordBox::SelectedTextProperty, Value (text, true));
2915 setvalue = true;
2916 } else {
2917 setvalue = false;
2918 SetValue (PasswordBox::SelectedTextProperty, Value (""));
2919 setvalue = true;
2923 void
2924 PasswordBox::SyncDisplayText ()
2926 gunichar c = GetPasswordChar ();
2928 g_string_truncate (display, 0);
2930 for (int i = 0; i < buffer->len; i++)
2931 g_string_append_unichar (display, c);
2934 void
2935 PasswordBox::SyncText ()
2937 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
2939 setvalue = false;
2940 SetValue (PasswordBox::PasswordProperty, Value (text, true));
2941 setvalue = true;
2943 SyncDisplayText ();
2946 const char *
2947 PasswordBox::GetDisplayText ()
2949 return display->str;
2952 void
2953 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2955 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2956 int length, start;
2958 if (args->GetId () == PasswordBox::CaretBrushProperty) {
2959 // FIXME: if we want to be perfect, we could invalidate the
2960 // blinking cursor rect if it is active... but is it that
2961 // important?
2962 } else if (args->GetId () == PasswordBox::FontSourceProperty) {
2963 FontSource *source = args->GetNewValue () ? args->GetNewValue ()->AsFontSource () : NULL;
2964 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2966 // FIXME: ideally we'd remove the old item from the cache (or,
2967 // rather, 'unref' it since some other textblocks/boxes might
2968 // still be using it).
2970 g_free (font_source);
2972 if (source && source->stream)
2973 font_source = manager->AddResource (source->stream);
2974 else
2975 font_source = NULL;
2977 changed = TextBoxModelChangedFont;
2978 font->SetSource (font_source);
2979 } else if (args->GetId () == PasswordBox::MaxLengthProperty) {
2980 // update max_length state
2981 max_length = args->GetNewValue()->AsInt32 ();
2982 } else if (args->GetId () == PasswordBox::PasswordCharProperty) {
2983 changed = TextBoxModelChangedText;
2984 } else if (args->GetId () == PasswordBox::PasswordProperty) {
2985 if (setvalue) {
2986 Value *value = args->GetNewValue ();
2987 const char *str = value && value->AsString () ? value->AsString () : "";
2988 TextBoxUndoAction *action;
2989 gunichar *text;
2990 glong textlen;
2992 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2993 if (buffer->len > 0) {
2994 // replace the current text
2995 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, 0, buffer->len, text, textlen);
2997 buffer->Replace (0, buffer->len, text, textlen);
2998 } else {
2999 // insert the text
3000 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
3002 buffer->Insert (0, text, textlen);
3005 undo->Push (action);
3006 redo->Clear ();
3007 g_free (text);
3009 emit |= TEXT_CHANGED;
3010 SyncDisplayText ();
3011 ClearSelection (0);
3012 ResetIMContext ();
3014 SyncAndEmit ();
3018 changed = TextBoxModelChangedText;
3019 } else if (args->GetId () == PasswordBox::SelectedTextProperty) {
3020 if (setvalue) {
3021 Value *value = args->GetNewValue ();
3022 const char *str = value && value->AsString () ? value->AsString () : "";
3023 TextBoxUndoAction *action = NULL;
3024 gunichar *text;
3025 glong textlen;
3027 length = abs (selection_cursor - selection_anchor);
3028 start = MIN (selection_anchor, selection_cursor);
3030 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
3031 if (length > 0) {
3032 // replace the currently selected text
3033 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, textlen);
3035 buffer->Replace (start, length, text, textlen);
3036 } else if (textlen > 0) {
3037 // insert the text at the cursor
3038 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, textlen);
3040 buffer->Insert (start, text, textlen);
3043 g_free (text);
3045 if (action != NULL) {
3046 undo->Push (action);
3047 redo->Clear ();
3049 ClearSelection (start + textlen);
3050 emit |= TEXT_CHANGED;
3051 SyncDisplayText ();
3052 ResetIMContext ();
3054 SyncAndEmit ();
3058 } else if (args->GetId () == PasswordBox::SelectionStartProperty) {
3059 length = abs (selection_cursor - selection_anchor);
3060 start = args->GetNewValue ()->AsInt32 ();
3062 if (start > buffer->len) {
3063 // clamp the selection start offset to a valid value
3064 SetSelectionStart (buffer->len);
3065 return;
3068 if (start + length > buffer->len) {
3069 // clamp the selection length to a valid value
3070 BatchPush ();
3071 length = buffer->len - start;
3072 SetSelectionLength (length);
3073 BatchPop ();
3076 // SelectionStartProperty is marked as AlwaysChange -
3077 // if the value hasn't actually changed, then we do
3078 // not want to emit the TextBoxModelChanged event.
3079 if (selection_anchor != start) {
3080 changed = TextBoxModelChangedSelection;
3081 have_offset = false;
3084 // When set programatically, anchor is always the
3085 // start and cursor is always the end.
3086 selection_cursor = start + length;
3087 selection_anchor = start;
3089 emit |= SELECTION_CHANGED;
3091 SyncAndEmit ();
3092 } else if (args->GetId () == PasswordBox::SelectionLengthProperty) {
3093 start = MIN (selection_anchor, selection_cursor);
3094 length = args->GetNewValue ()->AsInt32 ();
3096 if (start + length > buffer->len) {
3097 // clamp the selection length to a valid value
3098 length = buffer->len - start;
3099 SetSelectionLength (length);
3100 return;
3103 // SelectionLengthProperty is marked as AlwaysChange -
3104 // if the value hasn't actually changed, then we do
3105 // not want to emit the TextBoxModelChanged event.
3106 if (selection_cursor != start + length) {
3107 changed = TextBoxModelChangedSelection;
3108 have_offset = false;
3111 // When set programatically, anchor is always the
3112 // start and cursor is always the end.
3113 selection_cursor = start + length;
3114 selection_anchor = start;
3116 emit |= SELECTION_CHANGED;
3118 SyncAndEmit ();
3119 } else if (args->GetId () == PasswordBox::SelectionBackgroundProperty) {
3120 changed = TextBoxModelChangedBrush;
3121 } else if (args->GetId () == PasswordBox::SelectionForegroundProperty) {
3122 changed = TextBoxModelChangedBrush;
3125 if (changed != TextBoxModelChangedNothing)
3126 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
3128 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
3129 TextBoxBase::OnPropertyChanged (args, error);
3130 return;
3133 NotifyListenersOfPropertyChange (args, error);
3136 void
3137 PasswordBox::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
3139 if (prop && (prop->GetId () == PasswordBox::SelectionBackgroundProperty ||
3140 prop->GetId () == PasswordBox::SelectionForegroundProperty)) {
3141 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
3142 Invalidate ();
3145 if (prop->GetOwnerType () != Type::TEXTBOX)
3146 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
3151 // TextBoxView
3154 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3155 #define CURSOR_BLINK_ON_MULTIPLIER 2
3156 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3157 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3158 #define CURSOR_BLINK_DIVIDER 3
3160 TextBoxView::TextBoxView ()
3162 SetObjectType (Type::TEXTBOXVIEW);
3164 AddHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3165 AddHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3167 SetCursor (MouseCursorIBeam);
3169 cursor = Rect (0, 0, 0, 0);
3170 layout = new TextLayout ();
3171 selection_changed = false;
3172 had_selected_text = false;
3173 cursor_visible = false;
3174 blink_timeout = 0;
3175 textbox = NULL;
3176 dirty = false;
3179 TextBoxView::~TextBoxView ()
3181 RemoveHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3182 RemoveHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3184 if (textbox) {
3185 textbox->RemoveHandler (TextBox::ModelChangedEvent, TextBoxView::model_changed, this);
3186 textbox->view = NULL;
3189 DisconnectBlinkTimeout ();
3191 delete layout;
3194 TextLayoutLine *
3195 TextBoxView::GetLineFromY (double y, int *index)
3197 return layout->GetLineFromY (Point (), y, index);
3200 TextLayoutLine *
3201 TextBoxView::GetLineFromIndex (int index)
3203 return layout->GetLineFromIndex (index);
3207 TextBoxView::GetCursorFromXY (double x, double y)
3209 return layout->GetCursorFromXY (Point (), x, y);
3212 gboolean
3213 TextBoxView::blink (void *user_data)
3215 return ((TextBoxView *) user_data)->Blink ();
3218 static guint
3219 GetCursorBlinkTimeout (TextBoxView *view)
3221 GtkSettings *settings;
3222 MoonWindow *window;
3223 GdkScreen *screen;
3224 GdkWindow *widget;
3225 guint timeout;
3227 if (!view->IsAttached ())
3228 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3230 if (!(window = view->GetDeployment ()->GetSurface ()->GetWindow ()))
3231 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3233 if (!(widget = window->GetGdkWindow ()))
3234 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3236 if (!(screen = gdk_drawable_get_screen ((GdkDrawable *) widget)))
3237 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3239 if (!(settings = gtk_settings_get_for_screen (screen)))
3240 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3242 g_object_get (settings, "gtk-cursor-blink-time", &timeout, NULL);
3244 return timeout;
3247 void
3248 TextBoxView::ConnectBlinkTimeout (guint multiplier)
3250 guint timeout = GetCursorBlinkTimeout (this) * multiplier / CURSOR_BLINK_DIVIDER;
3251 TimeManager *manager;
3253 if (!IsAttached () || !(manager = GetDeployment ()->GetSurface ()->GetTimeManager ()))
3254 return;
3256 blink_timeout = manager->AddTimeout (MOON_PRIORITY_DEFAULT, timeout, TextBoxView::blink, this);
3259 void
3260 TextBoxView::DisconnectBlinkTimeout ()
3262 TimeManager *manager;
3264 if (blink_timeout != 0) {
3265 if (!IsAttached () || !(manager = GetDeployment ()->GetSurface ()->GetTimeManager ()))
3266 return;
3268 manager->RemoveTimeout (blink_timeout);
3269 blink_timeout = 0;
3273 bool
3274 TextBoxView::Blink ()
3276 guint multiplier;
3278 SetCurrentDeployment (true);
3280 if (cursor_visible) {
3281 multiplier = CURSOR_BLINK_OFF_MULTIPLIER;
3282 HideCursor ();
3283 } else {
3284 multiplier = CURSOR_BLINK_ON_MULTIPLIER;
3285 ShowCursor ();
3288 ConnectBlinkTimeout (multiplier);
3290 return false;
3293 void
3294 TextBoxView::DelayCursorBlink ()
3296 DisconnectBlinkTimeout ();
3297 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER);
3298 UpdateCursor (true);
3299 ShowCursor ();
3302 void
3303 TextBoxView::BeginCursorBlink ()
3305 if (blink_timeout == 0) {
3306 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER);
3307 UpdateCursor (true);
3308 ShowCursor ();
3312 void
3313 TextBoxView::EndCursorBlink ()
3315 DisconnectBlinkTimeout ();
3317 if (cursor_visible)
3318 HideCursor ();
3321 void
3322 TextBoxView::ResetCursorBlink (bool delay)
3324 if (textbox->IsFocused () && !textbox->HasSelectedText ()) {
3325 // cursor is blinkable... proceed with blinkage
3326 if (delay)
3327 DelayCursorBlink ();
3328 else
3329 BeginCursorBlink ();
3330 } else {
3331 // cursor not blinkable... stop all blinkage
3332 EndCursorBlink ();
3336 void
3337 TextBoxView::InvalidateCursor ()
3339 Invalidate (cursor.Transform (&absolute_xform));
3342 void
3343 TextBoxView::ShowCursor ()
3345 cursor_visible = true;
3346 InvalidateCursor ();
3349 void
3350 TextBoxView::HideCursor ()
3352 cursor_visible = false;
3353 InvalidateCursor ();
3356 void
3357 TextBoxView::UpdateCursor (bool invalidate)
3359 int cur = textbox->GetCursor ();
3360 GdkRectangle area;
3361 Rect rect;
3363 // invalidate current cursor rect
3364 if (invalidate && cursor_visible)
3365 InvalidateCursor ();
3367 // calculate the new cursor rect
3368 cursor = layout->GetCursor (Point (), cur);
3370 // transform the cursor rect into absolute coordinates for the IM context
3371 rect = cursor.Transform (&absolute_xform);
3372 area = rect.ToGdkRectangle ();
3374 gtk_im_context_set_cursor_location (textbox->im_ctx, &area);
3376 textbox->EmitCursorPositionChanged (cursor.height, cursor.x, cursor.y);
3378 // invalidate the new cursor rect
3379 if (invalidate && cursor_visible)
3380 InvalidateCursor ();
3383 void
3384 TextBoxView::UpdateText ()
3386 const char *text = textbox->GetDisplayText ();
3388 layout->SetText (text ? text : "", -1);
3391 void
3392 TextBoxView::GetSizeForBrush (cairo_t *cr, double *width, double *height)
3394 *height = GetActualHeight ();
3395 *width = GetActualWidth ();
3398 Size
3399 TextBoxView::ComputeActualSize ()
3401 UIElement *parent = GetVisualParent ();
3403 if (LayoutInformation::GetLayoutSlot (this))
3404 return FrameworkElement::ComputeActualSize ();
3406 Layout (Size (INFINITY, INFINITY));
3408 Size actual (0,0);
3409 layout->GetActualExtents (&actual.width, &actual.height);
3411 return actual;
3414 Size
3415 TextBoxView::MeasureOverride (Size availableSize)
3417 Size desired = Size ();
3419 Layout (availableSize);
3421 layout->GetActualExtents (&desired.width, &desired.height);
3423 /* FIXME using a magic number for minumum width here */
3424 if (isinf (availableSize.width))
3425 desired.width = MAX (desired.width, 11);
3427 return desired.Min (availableSize);
3430 Size
3431 TextBoxView::ArrangeOverride (Size finalSize)
3433 Size arranged = Size ();
3435 Layout (finalSize);
3437 layout->GetActualExtents (&arranged.width, &arranged.height);
3439 arranged = arranged.Max (finalSize);
3441 return arranged;
3444 void
3445 TextBoxView::Layout (Size constraint)
3447 layout->SetMaxWidth (constraint.width);
3449 layout->Layout ();
3450 dirty = false;
3453 void
3454 TextBoxView::Paint (cairo_t *cr)
3456 layout->Render (cr, GetOriginPoint (), Point ());
3458 if (cursor_visible) {
3459 cairo_antialias_t alias = cairo_get_antialias (cr);
3460 Brush *caret = textbox->GetCaretBrush ();
3461 double h = round (cursor.height);
3462 double x = cursor.x;
3463 double y = cursor.y;
3465 // disable antialiasing
3466 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
3468 // snap 'x' to the half-pixel grid (to try and get a sharp 1-pixel-wide line)
3469 cairo_user_to_device (cr, &x, &y);
3470 x = trunc (x) + 0.5; y = trunc (y);
3471 cairo_device_to_user (cr, &x, &y);
3473 // set the cursor color
3474 caret->SetupBrush (cr, cursor);
3476 // draw the cursor
3477 cairo_set_line_width (cr, 1.0);
3478 cairo_move_to (cr, x, y);
3479 cairo_line_to (cr, x, y + h);
3481 // stroke the caret
3482 caret->Stroke (cr);
3484 // restore antialiasing
3485 cairo_set_antialias (cr, alias);
3489 void
3490 TextBoxView::Render (cairo_t *cr, Region *region, bool path_only)
3492 TextBoxDynamicPropertyValueProvider *dynamic = (TextBoxDynamicPropertyValueProvider *) textbox->providers[PropertyPrecedence_DynamicValue];
3493 Size renderSize = GetRenderSize ();
3495 dynamic->InitializeSelectionBrushes ();
3497 UpdateCursor (false);
3499 if (selection_changed) {
3500 layout->Select (textbox->GetSelectionStart (), textbox->GetSelectionLength ());
3501 selection_changed = false;
3504 cairo_save (cr);
3505 cairo_set_matrix (cr, &absolute_xform);
3507 if (!path_only)
3508 RenderLayoutClip (cr);
3510 layout->SetAvailableWidth (renderSize.width);
3511 Paint (cr);
3512 cairo_restore (cr);
3515 void
3516 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs *args)
3518 switch (args->changed) {
3519 case TextBoxModelChangedTextAlignment:
3520 // text alignment changed, update our layout
3521 if (layout->SetTextAlignment ((TextAlignment) args->property->GetNewValue()->AsInt32 ()))
3522 dirty = true;
3523 break;
3524 case TextBoxModelChangedTextWrapping:
3525 // text wrapping changed, update our layout
3526 if (layout->SetTextWrapping ((TextWrapping) args->property->GetNewValue()->AsInt32 ()))
3527 dirty = true;
3528 break;
3529 case TextBoxModelChangedSelection:
3530 if (had_selected_text || textbox->HasSelectedText ()) {
3531 // the selection has changed, update the layout's selection
3532 had_selected_text = textbox->HasSelectedText ();
3533 selection_changed = true;
3534 ResetCursorBlink (false);
3535 } else {
3536 // cursor position changed
3537 ResetCursorBlink (true);
3538 return;
3540 break;
3541 case TextBoxModelChangedBrush:
3542 // a brush has changed, no layout updates needed, we just need to re-render
3543 break;
3544 case TextBoxModelChangedFont:
3545 // font changed, need to recalculate layout/bounds
3546 layout->ResetState ();
3547 dirty = true;
3548 break;
3549 case TextBoxModelChangedText:
3550 // the text has changed, need to recalculate layout/bounds
3551 UpdateText ();
3552 dirty = true;
3553 break;
3554 default:
3555 // nothing changed??
3556 return;
3559 if (dirty) {
3560 InvalidateMeasure ();
3561 UpdateBounds (true);
3564 Invalidate ();
3567 void
3568 TextBoxView::model_changed (EventObject *sender, EventArgs *args, gpointer closure)
3570 ((TextBoxView *) closure)->OnModelChanged ((TextBoxModelChangedEventArgs *) args);
3573 void
3574 TextBoxView::OnLostFocus ()
3576 EndCursorBlink ();
3579 void
3580 TextBoxView::OnGotFocus ()
3582 ResetCursorBlink (false);
3585 void
3586 TextBoxView::OnMouseLeftButtonDown (MouseButtonEventArgs *args)
3588 // proxy to our parent TextBox control
3589 textbox->OnMouseLeftButtonDown (args);
3592 void
3593 TextBoxView::mouse_left_button_down (EventObject *sender, EventArgs *args, gpointer closure)
3595 ((TextBoxView *) closure)->OnMouseLeftButtonDown ((MouseButtonEventArgs *) args);
3598 void
3599 TextBoxView::OnMouseLeftButtonUp (MouseButtonEventArgs *args)
3601 // proxy to our parent TextBox control
3602 textbox->OnMouseLeftButtonUp (args);
3605 void
3606 TextBoxView::mouse_left_button_up (EventObject *sender, EventArgs *args, gpointer closure)
3608 ((TextBoxView *) closure)->OnMouseLeftButtonUp ((MouseButtonEventArgs *) args);
3611 void
3612 TextBoxView::SetTextBox (TextBoxBase *textbox)
3614 TextLayoutAttributes *attrs;
3616 if (this->textbox == textbox)
3617 return;
3619 if (this->textbox) {
3620 // remove the event handlers from the old textbox
3621 this->textbox->RemoveHandler (TextBoxBase::ModelChangedEvent, TextBoxView::model_changed, this);
3624 this->textbox = textbox;
3626 if (textbox) {
3627 textbox->AddHandler (TextBoxBase::ModelChangedEvent, TextBoxView::model_changed, this);
3629 // sync our state with the textbox
3630 layout->SetTextAttributes (new List ());
3631 attrs = new TextLayoutAttributes ((ITextAttributes *) textbox, 0);
3632 layout->GetTextAttributes ()->Append (attrs);
3634 layout->SetTextAlignment (textbox->GetTextAlignment ());
3635 layout->SetTextWrapping (textbox->GetTextWrapping ());
3636 had_selected_text = textbox->HasSelectedText ();
3637 selection_changed = true;
3638 UpdateText ();
3639 } else {
3640 layout->SetTextAttributes (NULL);
3641 layout->SetText (NULL, -1);
3644 UpdateBounds (true);
3645 InvalidateMeasure ();
3646 Invalidate ();
3647 dirty = true;