add the 2.1-bootstrap dir to MONO_PATH when running smcs
[moon.git] / src / textbox.cpp
blobd7f609b871247c78792dac076792b4dad17e2891
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * textbox.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <gdk/gdkkeysyms.h>
16 #include <cairo.h>
18 #include <string.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 #include <errno.h>
24 #include "dependencyproperty.h"
25 #include "file-downloader.h"
26 #include "contentcontrol.h"
27 #include "timemanager.h"
28 #include "runtime.h"
29 #include "textbox.h"
30 #include "border.h"
31 #include "panel.h"
32 #include "utils.h"
33 #include "uri.h"
35 #include "geometry.h"
38 // TextBuffer
41 #define UNICODE_LEN(size) (sizeof (gunichar) * (size))
42 #define UNICODE_OFFSET(buf,offset) (((char *) buf) + UNICODE_LEN (offset))
44 class TextBuffer {
45 int allocated;
47 bool Resize (int needed)
49 int new_size = allocated;
50 bool resize = false;
51 void *buf;
53 if (allocated >= needed + 128) {
54 while (new_size >= needed + 128)
55 new_size -= 128;
56 resize = true;
57 } else if (allocated < needed) {
58 while (new_size < needed)
59 new_size += 128;
60 resize = true;
63 if (resize) {
64 if (!(buf = g_try_realloc (text, UNICODE_LEN (new_size)))) {
65 // if new_size is < allocated, then we can pretend like we succeeded
66 return new_size < allocated;
69 text = (gunichar *) buf;
70 allocated = new_size;
73 return true;
76 public:
77 gunichar *text;
78 int len;
80 TextBuffer (const gunichar *text, int len)
82 this->allocated = 0;
83 this->text = NULL;
84 this->len = 0;
86 Append (text, len);
89 TextBuffer ()
91 text = NULL;
92 Reset ();
95 void Reset ()
97 text = (gunichar *) g_realloc (text, UNICODE_LEN (128));
98 allocated = 128;
99 text[0] = '\0';
100 len = 0;
103 void Print ()
105 printf ("TextBuffer::text = \"");
107 for (int i = 0; i < len; i++) {
108 switch (text[i]) {
109 case '\r':
110 fputs ("\\r", stdout);
111 break;
112 case '\n':
113 fputs ("\\n", stdout);
114 break;
115 case '\0':
116 fputs ("\\0", stdout);
117 break;
118 case '\\':
119 fputc ('\\', stdout);
120 // fall thru
121 default:
122 fputc ((char) text[i], stdout);
123 break;
127 printf ("\";\n");
130 void Append (gunichar c)
132 if (!Resize (len + 2))
133 return;
135 text[len++] = c;
136 text[len] = 0;
139 void Append (const gunichar *str, int count)
141 if (!Resize (len + count + 1))
142 return;
144 memcpy (UNICODE_OFFSET (text, len), str, UNICODE_LEN (count));
145 len += count;
146 text[len] = 0;
149 void Cut (int start, int length)
151 char *dest, *src;
152 int beyond;
154 if (length == 0 || start >= len)
155 return;
157 if (start + length > len)
158 length = len - start;
160 src = UNICODE_OFFSET (text, start + length);
161 dest = UNICODE_OFFSET (text, start);
162 beyond = len - (start + length);
164 memmove (dest, src, UNICODE_LEN (beyond + 1));
165 len -= length;
168 void Insert (int index, gunichar c)
170 if (!Resize (len + 2))
171 return;
173 if (index < len) {
174 // shift all chars beyond position @index down by 1 char
175 memmove (UNICODE_OFFSET (text, index + 1), UNICODE_OFFSET (text, index), UNICODE_LEN ((len - index) + 1));
176 text[index] = c;
177 len++;
178 } else {
179 text[len++] = c;
180 text[len] = 0;
184 void Insert (int index, const gunichar *str, int count)
186 if (!Resize (len + count + 1))
187 return;
189 if (index < len) {
190 // shift all chars beyond position @index down by @count chars
191 memmove (UNICODE_OFFSET (text, index + count), UNICODE_OFFSET (text, index), UNICODE_LEN ((len - index) + 1));
193 // insert @count chars of @str into our buffer at position @index
194 memcpy (UNICODE_OFFSET (text, index), str, UNICODE_LEN (count));
195 len += count;
196 } else {
197 // simply append @count chars of @str onto the end of our buffer
198 memcpy (UNICODE_OFFSET (text, len), str, UNICODE_LEN (count));
199 len += count;
200 text[len] = 0;
204 void Prepend (gunichar c)
206 if (!Resize (len + 2))
207 return;
209 // shift the entire buffer down by 1 char
210 memmove (UNICODE_OFFSET (text, 1), text, UNICODE_LEN (len + 1));
211 text[0] = c;
212 len++;
215 void Prepend (const gunichar *str, int count)
217 if (!Resize (len + count + 1))
218 return;
220 // shift the endtire buffer down by @count chars
221 memmove (UNICODE_OFFSET (text, count), text, UNICODE_LEN (len + 1));
223 // copy @count chars of @str into the beginning of our buffer
224 memcpy (text, str, UNICODE_LEN (count));
225 len += count;
228 void Replace (int start, int length, const gunichar *str, int count)
230 char *dest, *src;
231 int beyond;
233 if (start > len)
234 return;
236 if (start + length > len)
237 length = len - start;
239 // Check for the easy cases first...
240 if (length == 0) {
241 Insert (start, str, count);
242 return;
243 } else if (count == 0) {
244 Cut (start, length);
245 return;
246 } else if (count == length) {
247 memcpy (UNICODE_OFFSET (text, start), str, UNICODE_LEN (count));
248 return;
251 if (count > length && !Resize (len + (count - length) + 1))
252 return;
254 // calculate the number of chars beyond @start that won't be cut
255 beyond = len - (start + length);
257 // shift all chars beyond position (@start + length) into position...
258 dest = UNICODE_OFFSET (text, start + count);
259 src = UNICODE_OFFSET (text, start + length);
260 memmove (dest, src, UNICODE_LEN (beyond + 1));
262 // copy @count chars of @str into our buffer at position @start
263 memcpy (UNICODE_OFFSET (text, start), str, UNICODE_LEN (count));
265 len = (len - length) + count;
268 gunichar *Substring (int start, int length = -1)
270 gunichar *substr;
271 size_t n_bytes;
273 if (start < 0 || start > len || length == 0)
274 return NULL;
276 if (length < 0)
277 length = len - start;
279 n_bytes = sizeof (gunichar) * (length + 1);
280 substr = (gunichar *) g_malloc (n_bytes);
281 n_bytes -= sizeof (gunichar);
283 memcpy (substr, text + start, n_bytes);
284 substr[length] = 0;
286 return substr;
292 // TextBoxUndoActions
295 enum TextBoxUndoActionType {
296 TextBoxUndoActionTypeInsert,
297 TextBoxUndoActionTypeDelete,
298 TextBoxUndoActionTypeReplace,
301 class TextBoxUndoAction : public List::Node {
302 public:
303 TextBoxUndoActionType type;
304 int selection_anchor;
305 int selection_cursor;
306 int length;
307 int start;
310 class TextBoxUndoActionInsert : public TextBoxUndoAction {
311 public:
312 TextBuffer *buffer;
313 bool growable;
315 TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, gunichar c);
316 TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, const gunichar *inserted, int length, bool atomic = false);
317 virtual ~TextBoxUndoActionInsert ();
319 bool Insert (int start, const gunichar *text, int len);
320 bool Insert (int start, gunichar c);
323 class TextBoxUndoActionDelete : public TextBoxUndoAction {
324 public:
325 gunichar *text;
327 TextBoxUndoActionDelete (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length);
328 virtual ~TextBoxUndoActionDelete ();
331 class TextBoxUndoActionReplace : public TextBoxUndoAction {
332 public:
333 gunichar *inserted;
334 gunichar *deleted;
335 int inlen;
337 TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, const gunichar *inserted, int inlen);
338 TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, gunichar c);
339 virtual ~TextBoxUndoActionReplace ();
342 class TextBoxUndoStack {
343 int max_count;
344 List *list;
346 public:
347 TextBoxUndoStack (int max_count);
348 ~TextBoxUndoStack ();
350 bool IsEmpty ();
351 void Clear ();
353 void Push (TextBoxUndoAction *action);
354 TextBoxUndoAction *Peek ();
355 TextBoxUndoAction *Pop ();
358 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, gunichar c)
360 this->type = TextBoxUndoActionTypeInsert;
361 this->selection_anchor = selection_anchor;
362 this->selection_cursor = selection_cursor;
363 this->start = start;
364 this->length = 1;
366 this->buffer = new TextBuffer ();
367 this->buffer->Append (c);
368 this->growable = true;
371 TextBoxUndoActionInsert::TextBoxUndoActionInsert (int selection_anchor, int selection_cursor, int start, const gunichar *inserted, int length, bool atomic)
373 this->type = TextBoxUndoActionTypeInsert;
374 this->selection_anchor = selection_anchor;
375 this->selection_cursor = selection_cursor;
376 this->length = length;
377 this->start = start;
379 this->buffer = new TextBuffer (inserted, length);
380 this->growable = !atomic;
383 TextBoxUndoActionInsert::~TextBoxUndoActionInsert ()
385 delete buffer;
388 bool
389 TextBoxUndoActionInsert::Insert (int start, const gunichar *text, int len)
391 if (!growable || start != (this->start + length))
392 return false;
394 buffer->Append (text, len);
395 length += len;
397 return true;
400 bool
401 TextBoxUndoActionInsert::Insert (int start, gunichar c)
403 if (!growable || start != (this->start + length))
404 return false;
406 buffer->Append (c);
407 length++;
409 return true;
412 TextBoxUndoActionDelete::TextBoxUndoActionDelete (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length)
414 this->type = TextBoxUndoActionTypeDelete;
415 this->selection_anchor = selection_anchor;
416 this->selection_cursor = selection_cursor;
417 this->length = length;
418 this->start = start;
420 this->text = buffer->Substring (start, length);
423 TextBoxUndoActionDelete::~TextBoxUndoActionDelete ()
425 g_free (text);
428 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, const gunichar *inserted, int inlen)
430 this->type = TextBoxUndoActionTypeReplace;
431 this->selection_anchor = selection_anchor;
432 this->selection_cursor = selection_cursor;
433 this->length = length;
434 this->start = start;
436 this->deleted = buffer->Substring (start, length);
437 this->inserted = (gunichar *) g_malloc (UNICODE_LEN (inlen + 1));
438 memcpy (this->inserted, inserted, UNICODE_LEN (inlen + 1));
439 this->inlen = inlen;
442 TextBoxUndoActionReplace::TextBoxUndoActionReplace (int selection_anchor, int selection_cursor, TextBuffer *buffer, int start, int length, gunichar c)
444 this->type = TextBoxUndoActionTypeReplace;
445 this->selection_anchor = selection_anchor;
446 this->selection_cursor = selection_cursor;
447 this->length = length;
448 this->start = start;
450 this->deleted = buffer->Substring (start, length);
451 this->inserted = g_new (gunichar, 2);
452 memcpy (inserted, &c, sizeof (gunichar));
453 inserted[1] = 0;
454 this->inlen = 1;
457 TextBoxUndoActionReplace::~TextBoxUndoActionReplace ()
459 g_free (inserted);
460 g_free (deleted);
464 TextBoxUndoStack::TextBoxUndoStack (int max_count)
466 this->max_count = max_count;
467 this->list = new List ();
470 TextBoxUndoStack::~TextBoxUndoStack ()
472 delete list;
475 bool
476 TextBoxUndoStack::IsEmpty ()
478 return list->IsEmpty ();
481 void
482 TextBoxUndoStack::Clear ()
484 list->Clear (true);
487 void
488 TextBoxUndoStack::Push (TextBoxUndoAction *action)
490 if (list->Length () == max_count) {
491 List::Node *node = list->Last ();
492 list->Unlink (node);
493 delete node;
496 list->Prepend (action);
499 TextBoxUndoAction *
500 TextBoxUndoStack::Pop ()
502 List::Node *node = list->First ();
504 if (node)
505 list->Unlink (node);
507 return (TextBoxUndoAction *) node;
510 TextBoxUndoAction *
511 TextBoxUndoStack::Peek ()
513 return (TextBoxUndoAction *) list->First ();
518 // TextBoxBase
521 // emit state, also doubles as available event mask
522 #define NOTHING_CHANGED (0)
523 #define SELECTION_CHANGED (1 << 0)
524 #define TEXT_CHANGED (1 << 1)
526 #define CONTROL_MASK GDK_CONTROL_MASK
527 #define SHIFT_MASK GDK_SHIFT_MASK
528 #define ALT_MASK GDK_MOD1_MASK
530 #define IsEOL(c) ((c) == '\r' || (c) == '\n')
532 static GdkWindow *
533 GetGdkWindow (TextBoxBase *textbox)
535 MoonWindow *window;
536 Surface *surface;
538 if (!(surface = textbox->GetSurface ()))
539 return NULL;
541 if (!(window = surface->GetWindow ()))
542 return NULL;
544 return window->GetGdkWindow ();
547 static GtkClipboard *
548 GetClipboard (TextBoxBase *textbox, GdkAtom atom)
550 GdkDisplay *display;
551 GdkWindow *window;
553 if (!(window = GetGdkWindow (textbox)))
554 return NULL;
556 if (!(display = gdk_drawable_get_display ((GdkDrawable *) window)))
557 return NULL;
559 return gtk_clipboard_get_for_display (display, atom);
562 void
563 TextBoxBase::Initialize (Type::Kind type, const char *type_name)
565 ManagedTypeInfo *type_info = new ManagedTypeInfo ("System.Windows", type_name);
567 SetObjectType (type);
568 SetDefaultStyleKey (type_info);
570 AddHandler (UIElement::MouseLeftButtonMultiClickEvent, TextBoxBase::mouse_left_button_multi_click, this);
571 AddHandler (UIElement::MouseLeftButtonDownEvent, TextBoxBase::mouse_left_button_down, this);
572 AddHandler (UIElement::MouseLeftButtonUpEvent, TextBoxBase::mouse_left_button_up, this);
573 AddHandler (UIElement::MouseMoveEvent, TextBoxBase::mouse_move, this);
574 AddHandler (UIElement::LostFocusEvent, TextBoxBase::focus_out, this);
575 AddHandler (UIElement::GotFocusEvent, TextBoxBase::focus_in, this);
577 font = new TextFontDescription ();
578 font->SetFamily (GetFontFamily ()->source);
579 font->SetStretch (GetFontStretch ()->stretch);
580 font->SetWeight (GetFontWeight ()->weight);
581 font->SetStyle (GetFontStyle ()->style);
582 font->SetSize (GetFontSize ());
584 downloaders = g_ptr_array_new ();
585 font_source = NULL;
587 contentElement = NULL;
589 im_ctx = gtk_im_multicontext_new ();
590 gtk_im_context_set_use_preedit (im_ctx, false);
592 g_signal_connect (im_ctx, "retrieve-surrounding", G_CALLBACK (TextBoxBase::retrieve_surrounding), this);
593 g_signal_connect (im_ctx, "delete-surrounding", G_CALLBACK (TextBoxBase::delete_surrounding), this);
594 g_signal_connect (im_ctx, "commit", G_CALLBACK (TextBoxBase::commit), this);
596 undo = new TextBoxUndoStack (10);
597 redo = new TextBoxUndoStack (10);
598 buffer = new TextBuffer ();
599 max_length = 0;
601 emit = NOTHING_CHANGED;
602 events_mask = 0;
604 selection_anchor = 0;
605 selection_cursor = 0;
606 cursor_offset = 0.0;
607 batch = 0;
609 accepts_return = false;
610 need_im_reset = false;
611 is_read_only = false;
612 have_offset = false;
613 multiline = false;
614 selecting = false;
615 setvalue = true;
616 captured = false;
617 focused = false;
618 secret = false;
619 view = NULL;
622 TextBoxBase::~TextBoxBase ()
624 RemoveHandler (UIElement::MouseLeftButtonMultiClickEvent, TextBoxBase::mouse_left_button_multi_click, this);
625 RemoveHandler (UIElement::MouseLeftButtonDownEvent, TextBoxBase::mouse_left_button_down, this);
626 RemoveHandler (UIElement::MouseLeftButtonUpEvent, TextBoxBase::mouse_left_button_up, this);
627 RemoveHandler (UIElement::MouseMoveEvent, TextBoxBase::mouse_move, this);
628 RemoveHandler (UIElement::LostFocusEvent, TextBoxBase::focus_out, this);
629 RemoveHandler (UIElement::GotFocusEvent, TextBoxBase::focus_in, this);
631 ResetIMContext ();
632 g_object_unref (im_ctx);
634 CleanupDownloaders ();
635 g_ptr_array_free (downloaders, true);
636 g_free (font_source);
638 delete buffer;
639 delete undo;
640 delete redo;
641 delete font;
644 void
645 TextBoxBase::SetSurface (Surface *surface)
647 Control::SetSurface (surface);
649 gtk_im_context_set_client_window (im_ctx, GetGdkWindow (this));
652 void
653 TextBoxBase::CleanupDownloaders ()
655 Downloader *downloader;
656 guint i;
658 for (i = 0; i < downloaders->len; i++) {
659 downloader = (Downloader *) downloaders->pdata[i];
660 downloader->RemoveHandler (Downloader::CompletedEvent, downloader_complete, this);
661 downloader->Abort ();
662 downloader->unref ();
665 g_ptr_array_set_size (downloaders, 0);
668 double
669 TextBoxBase::GetCursorOffset ()
671 if (!have_offset && view) {
672 cursor_offset = view->GetCursor ().x;
673 have_offset = true;
676 return cursor_offset;
680 TextBoxBase::CursorDown (int cursor, bool page)
682 double y = view->GetCursor ().y;
683 double x = GetCursorOffset ();
684 TextLayoutLine *line;
685 TextLayoutRun *run;
686 int index, cur, n;
687 guint i;
689 if (!(line = view->GetLineFromY (y, &index)))
690 return cursor;
692 if (page) {
693 // calculate the number of lines to skip over
694 n = GetActualHeight () / line->height;
695 } else {
696 n = 1;
699 if (index + n >= view->GetLineCount ()) {
700 // go to the end of the last line
701 line = view->GetLineFromIndex (view->GetLineCount () - 1);
703 for (cur = line->offset, i = 0; i < line->runs->len; i++) {
704 run = (TextLayoutRun *) line->runs->pdata[i];
705 cur += run->count;
708 have_offset = false;
710 return cur;
713 line = view->GetLineFromIndex (index + n);
715 return line->GetCursorFromX (Point (), x);
719 TextBoxBase::CursorUp (int cursor, bool page)
721 double y = view->GetCursor ().y;
722 double x = GetCursorOffset ();
723 TextLayoutLine *line;
724 int index, n;
726 if (!(line = view->GetLineFromY (y, &index)))
727 return cursor;
729 if (page) {
730 // calculate the number of lines to skip over
731 n = GetActualHeight () / line->height;
732 } else {
733 n = 1;
736 if (index < n) {
737 // go to the beginning of the first line
738 have_offset = false;
739 return 0;
742 line = view->GetLineFromIndex (index - n);
744 return line->GetCursorFromX (Point (), x);
747 #ifdef EMULATE_GTK
748 enum CharClass {
749 CharClassUnknown,
750 CharClassWhitespace,
751 CharClassAlphaNumeric
754 static inline CharClass
755 char_class (gunichar c)
757 if (g_unichar_isspace (c))
758 return CharClassWhitespace;
760 if (g_unichar_isalnum (c))
761 return CharClassAlphaNumeric;
763 return CharClassUnknown;
765 #else
766 static bool
767 is_start_of_word (TextBuffer *buffer, int index)
769 // A 'word' starts with an AlphaNumeric immediately preceeded by lwsp
770 if (index > 0 && !g_unichar_isspace (buffer->text[index - 1]))
771 return false;
773 return g_unichar_isalnum (buffer->text[index]);
775 #endif
778 TextBoxBase::CursorNextWord (int cursor)
780 int i, lf, cr;
782 // find the end of the current line
783 cr = CursorLineEnd (cursor);
784 if (buffer->text[cr] == '\r' && buffer->text[cr + 1] == '\n')
785 lf = cr + 1;
786 else
787 lf = cr;
789 // if the cursor is at the end of the line, return the starting offset of the next line
790 if (cursor == cr || cursor == lf) {
791 if (lf < buffer->len)
792 return lf + 1;
794 return cursor;
797 #ifdef EMULATE_GTK
798 CharClass cc = char_class (buffer->text[cursor]);
799 i = cursor;
801 // skip over the word, punctuation, or run of whitespace
802 while (i < cr && char_class (buffer->text[i]) == cc)
803 i++;
805 // skip any whitespace after the word/punct
806 while (i < cr && g_unichar_isspace (buffer->text[i]))
807 i++;
808 #else
809 i = cursor;
811 // skip to the end of the current word
812 while (i < cr && !g_unichar_isspace (buffer->text[i]))
813 i++;
815 // skip any whitespace after the word
816 while (i < cr && g_unichar_isspace (buffer->text[i]))
817 i++;
819 // find the start of the next word
820 while (i < cr && !is_start_of_word (buffer, i))
821 i++;
822 #endif
824 return i;
828 TextBoxBase::CursorPrevWord (int cursor)
830 int begin, i, cr, lf;
832 // find the beginning of the current line
833 lf = CursorLineBegin (cursor) - 1;
835 if (lf > 0 && buffer->text[lf] == '\n' && buffer->text[lf - 1] == '\r')
836 cr = lf - 1;
837 else
838 cr = lf;
840 // if the cursor is at the beginning of the line, return the end of the prev line
841 if (cursor - 1 == lf) {
842 if (cr > 0)
843 return cr;
845 return 0;
848 #ifdef EMULATE_GTK
849 CharClass cc = char_class (buffer->text[cursor - 1]);
850 begin = lf + 1;
851 i = cursor;
853 // skip over the word, punctuation, or run of whitespace
854 while (i > begin && char_class (buffer->text[i - 1]) == cc)
855 i--;
857 // if the cursor was at whitespace, skip back a word too
858 if (cc == CharClassWhitespace && i > begin) {
859 cc = char_class (buffer->text[i - 1]);
860 while (i > begin && char_class (buffer->text[i - 1]) == cc)
861 i--;
863 #else
864 begin = lf + 1;
865 i = cursor;
867 if (cursor < buffer->len) {
868 // skip to the beginning of this word
869 while (i > begin && !g_unichar_isspace (buffer->text[i - 1]))
870 i--;
872 if (i < cursor && is_start_of_word (buffer, i))
873 return i;
876 // skip to the start of the lwsp
877 while (i > begin && g_unichar_isspace (buffer->text[i - 1]))
878 i--;
880 if (i > begin)
881 i--;
883 // skip to the beginning of the word
884 while (i > begin && !is_start_of_word (buffer, i))
885 i--;
886 #endif
888 return i;
892 TextBoxBase::CursorLineBegin (int cursor)
894 int cur = cursor;
896 // find the beginning of the line
897 while (cur > 0 && !IsEOL (buffer->text[cur - 1]))
898 cur--;
900 return cur;
904 TextBoxBase::CursorLineEnd (int cursor, bool include)
906 int cur = cursor;
908 // find the end of the line
909 while (cur < buffer->len && !IsEOL (buffer->text[cur]))
910 cur++;
912 if (include && cur < buffer->len) {
913 if (buffer->text[cur] == '\r' && buffer->text[cur + 1] == '\n')
914 cur += 2;
915 else
916 cur++;
919 return cur;
922 bool
923 TextBoxBase::KeyPressBackSpace (GdkModifierType modifiers)
925 int anchor = selection_anchor;
926 int cursor = selection_cursor;
927 TextBoxUndoAction *action;
928 int start = 0, length = 0;
929 bool handled = false;
931 if ((modifiers & (ALT_MASK | SHIFT_MASK)) != 0)
932 return false;
934 if (cursor != anchor) {
935 // BackSpace w/ active selection: delete the selected text
936 length = abs (cursor - anchor);
937 start = MIN (anchor, cursor);
938 } else if ((modifiers & CONTROL_MASK) != 0) {
939 // Ctrl+BackSpace: delete the word ending at the cursor
940 start = CursorPrevWord (cursor);
941 length = cursor - start;
942 } else if (cursor > 0) {
943 // BackSpace: delete the char before the cursor position
944 if (cursor >= 2 && buffer->text[cursor - 1] == '\n' && buffer->text[cursor - 2] == '\r') {
945 start = cursor - 2;
946 length = 2;
947 } else {
948 start = cursor - 1;
949 length = 1;
953 if (length > 0) {
954 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
955 undo->Push (action);
956 redo->Clear ();
958 buffer->Cut (start, length);
959 emit |= TEXT_CHANGED;
960 anchor = start;
961 cursor = start;
962 handled = true;
965 // check to see if selection has changed
966 if (selection_anchor != anchor || selection_cursor != cursor) {
967 SetSelectionStart (MIN (anchor, cursor));
968 SetSelectionLength (abs (cursor - anchor));
969 selection_anchor = anchor;
970 selection_cursor = cursor;
971 emit |= SELECTION_CHANGED;
972 handled = true;
975 return handled;
978 bool
979 TextBoxBase::KeyPressDelete (GdkModifierType modifiers)
981 int anchor = selection_anchor;
982 int cursor = selection_cursor;
983 TextBoxUndoAction *action;
984 int start = 0, length = 0;
985 bool handled = false;
987 if ((modifiers & (ALT_MASK | SHIFT_MASK)) != 0)
988 return false;
990 if (cursor != anchor) {
991 // Delete w/ active selection: delete the selected text
992 length = abs (cursor - anchor);
993 start = MIN (anchor, cursor);
994 } else if ((modifiers & CONTROL_MASK) != 0) {
995 // Ctrl+Delete: delete the word starting at the cursor
996 length = CursorNextWord (cursor) - cursor;
997 start = cursor;
998 } else if (cursor < buffer->len) {
999 // Delete: delete the char after the cursor position
1000 if (buffer->text[cursor] == '\r' && buffer->text[cursor + 1] == '\n')
1001 length = 2;
1002 else
1003 length = 1;
1005 start = cursor;
1008 if (length > 0) {
1009 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1010 undo->Push (action);
1011 redo->Clear ();
1013 buffer->Cut (start, length);
1014 emit |= TEXT_CHANGED;
1015 handled = true;
1018 // check to see if selection has changed
1019 if (selection_anchor != anchor || selection_cursor != cursor) {
1020 SetSelectionStart (MIN (anchor, cursor));
1021 SetSelectionLength (abs (cursor - anchor));
1022 selection_anchor = anchor;
1023 selection_cursor = cursor;
1024 emit |= SELECTION_CHANGED;
1025 handled = true;
1028 return handled;
1031 bool
1032 TextBoxBase::KeyPressPageDown (GdkModifierType modifiers)
1034 int anchor = selection_anchor;
1035 int cursor = selection_cursor;
1036 bool handled = false;
1037 bool have;
1039 if ((modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1040 return false;
1042 // move the cursor down one page from its current position
1043 cursor = CursorDown (cursor, true);
1044 have = have_offset;
1046 if ((modifiers & SHIFT_MASK) == 0) {
1047 // clobber the selection
1048 anchor = cursor;
1051 // check to see if selection has changed
1052 if (selection_anchor != anchor || selection_cursor != cursor) {
1053 SetSelectionStart (MIN (anchor, cursor));
1054 SetSelectionLength (abs (cursor - anchor));
1055 selection_anchor = anchor;
1056 selection_cursor = cursor;
1057 emit |= SELECTION_CHANGED;
1058 have_offset = have;
1059 handled = true;
1062 return handled;
1065 bool
1066 TextBoxBase::KeyPressPageUp (GdkModifierType modifiers)
1068 int anchor = selection_anchor;
1069 int cursor = selection_cursor;
1070 bool handled = false;
1071 bool have;
1073 if ((modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1074 return false;
1076 // move the cursor up one page from its current position
1077 cursor = CursorUp (cursor, true);
1078 have = have_offset;
1080 if ((modifiers & SHIFT_MASK) == 0) {
1081 // clobber the selection
1082 anchor = cursor;
1085 // check to see if selection has changed
1086 if (selection_anchor != anchor || selection_cursor != cursor) {
1087 SetSelectionStart (MIN (anchor, cursor));
1088 SetSelectionLength (abs (cursor - anchor));
1089 selection_anchor = anchor;
1090 selection_cursor = cursor;
1091 emit |= SELECTION_CHANGED;
1092 have_offset = have;
1093 handled = true;
1096 return handled;
1099 bool
1100 TextBoxBase::KeyPressDown (GdkModifierType modifiers)
1102 int anchor = selection_anchor;
1103 int cursor = selection_cursor;
1104 bool handled = false;
1105 bool have;
1107 if (!accepts_return || (modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1108 return false;
1110 // move the cursor down by one line from its current position
1111 cursor = CursorDown (cursor, false);
1112 have = have_offset;
1114 if ((modifiers & SHIFT_MASK) == 0) {
1115 // clobber the selection
1116 anchor = cursor;
1119 // check to see if selection has changed
1120 if (selection_anchor != anchor || selection_cursor != cursor) {
1121 SetSelectionStart (MIN (anchor, cursor));
1122 SetSelectionLength (abs (cursor - anchor));
1123 selection_anchor = anchor;
1124 selection_cursor = cursor;
1125 emit |= SELECTION_CHANGED;
1126 have_offset = have;
1127 handled = true;
1130 return handled;
1133 bool
1134 TextBoxBase::KeyPressUp (GdkModifierType modifiers)
1136 int anchor = selection_anchor;
1137 int cursor = selection_cursor;
1138 bool handled = false;
1139 bool have;
1141 if (!accepts_return || (modifiers & (CONTROL_MASK | ALT_MASK)) != 0)
1142 return false;
1144 // move the cursor up by one line from its current position
1145 cursor = CursorUp (cursor, false);
1146 have = have_offset;
1148 if ((modifiers & SHIFT_MASK) == 0) {
1149 // clobber the selection
1150 anchor = cursor;
1153 // check to see if selection has changed
1154 if (selection_anchor != anchor || selection_cursor != cursor) {
1155 SetSelectionStart (MIN (anchor, cursor));
1156 SetSelectionLength (abs (cursor - anchor));
1157 selection_anchor = anchor;
1158 selection_cursor = cursor;
1159 emit |= SELECTION_CHANGED;
1160 have_offset = have;
1161 handled = true;
1164 return handled;
1167 bool
1168 TextBoxBase::KeyPressHome (GdkModifierType modifiers)
1170 int anchor = selection_anchor;
1171 int cursor = selection_cursor;
1172 bool handled = false;
1174 if ((modifiers & ALT_MASK) != 0)
1175 return false;
1177 if ((modifiers & CONTROL_MASK) != 0) {
1178 // move the cursor to the beginning of the buffer
1179 cursor = 0;
1180 } else {
1181 // move the cursor to the beginning of the line
1182 cursor = CursorLineBegin (cursor);
1185 if ((modifiers & SHIFT_MASK) == 0) {
1186 // clobber the selection
1187 anchor = cursor;
1190 // check to see if selection has changed
1191 if (selection_anchor != anchor || selection_cursor != cursor) {
1192 SetSelectionStart (MIN (anchor, cursor));
1193 SetSelectionLength (abs (cursor - anchor));
1194 selection_anchor = anchor;
1195 selection_cursor = cursor;
1196 emit |= SELECTION_CHANGED;
1197 have_offset = false;
1198 handled = true;
1201 return handled;
1204 bool
1205 TextBoxBase::KeyPressEnd (GdkModifierType modifiers)
1207 int anchor = selection_anchor;
1208 int cursor = selection_cursor;
1209 bool handled = false;
1211 if ((modifiers & ALT_MASK) != 0)
1212 return false;
1214 if ((modifiers & CONTROL_MASK) != 0) {
1215 // move the cursor to the end of the buffer
1216 cursor = buffer->len;
1217 } else {
1218 // move the cursor to the end of the line
1219 cursor = CursorLineEnd (cursor);
1222 if ((modifiers & SHIFT_MASK) == 0) {
1223 // clobber the selection
1224 anchor = cursor;
1227 // check to see if selection has changed
1228 if (selection_anchor != anchor || selection_cursor != cursor) {
1229 SetSelectionStart (MIN (anchor, cursor));
1230 SetSelectionLength (abs (cursor - anchor));
1231 selection_anchor = anchor;
1232 selection_cursor = cursor;
1233 emit |= SELECTION_CHANGED;
1234 handled = true;
1237 return handled;
1240 bool
1241 TextBoxBase::KeyPressRight (GdkModifierType modifiers)
1243 int anchor = selection_anchor;
1244 int cursor = selection_cursor;
1245 bool handled = false;
1247 if ((modifiers & ALT_MASK) != 0)
1248 return false;
1250 if ((modifiers & CONTROL_MASK) != 0) {
1251 // move the cursor to beginning of the next word
1252 cursor = CursorNextWord (cursor);
1253 } else if ((modifiers & SHIFT_MASK) == 0 && anchor != cursor) {
1254 // set cursor at end of selection
1255 cursor = MAX (anchor, cursor);
1256 } else {
1257 // move the cursor forward one character
1258 if (buffer->text[cursor] == '\r' && buffer->text[cursor + 1] == '\n')
1259 cursor += 2;
1260 else if (cursor < buffer->len)
1261 cursor++;
1264 if ((modifiers & SHIFT_MASK) == 0) {
1265 // clobber the selection
1266 anchor = cursor;
1269 // check to see if selection has changed
1270 if (selection_anchor != anchor || selection_cursor != cursor) {
1271 SetSelectionStart (MIN (anchor, cursor));
1272 SetSelectionLength (abs (cursor - anchor));
1273 selection_anchor = anchor;
1274 selection_cursor = cursor;
1275 emit |= SELECTION_CHANGED;
1276 handled = true;
1279 return handled;
1282 bool
1283 TextBoxBase::KeyPressLeft (GdkModifierType modifiers)
1285 int anchor = selection_anchor;
1286 int cursor = selection_cursor;
1287 bool handled = false;
1289 if ((modifiers & ALT_MASK) != 0)
1290 return false;
1292 if ((modifiers & CONTROL_MASK) != 0) {
1293 // move the cursor to the beginning of the previous word
1294 cursor = CursorPrevWord (cursor);
1295 } else if ((modifiers & SHIFT_MASK) == 0 && anchor != cursor) {
1296 // set cursor at start of selection
1297 cursor = MIN (anchor, cursor);
1298 } else {
1299 // move the cursor backward one character
1300 if (cursor >= 2 && buffer->text[cursor - 2] == '\r' && buffer->text[cursor - 1] == '\n')
1301 cursor -= 2;
1302 else if (cursor > 0)
1303 cursor--;
1306 if ((modifiers & SHIFT_MASK) == 0) {
1307 // clobber the selection
1308 anchor = cursor;
1311 // check to see if selection has changed
1312 if (selection_anchor != anchor || selection_cursor != cursor) {
1313 SetSelectionStart (MIN (anchor, cursor));
1314 SetSelectionLength (abs (cursor - anchor));
1315 selection_anchor = anchor;
1316 selection_cursor = cursor;
1317 emit |= SELECTION_CHANGED;
1318 handled = true;
1321 return handled;
1324 bool
1325 TextBoxBase::KeyPressUnichar (gunichar c)
1327 int length = abs (selection_cursor - selection_anchor);
1328 int start = MIN (selection_anchor, selection_cursor);
1329 int anchor = selection_anchor;
1330 int cursor = selection_cursor;
1331 TextBoxUndoAction *action;
1333 if ((max_length > 0 && buffer->len >= max_length) || ((c == '\r') && !accepts_return))
1334 return false;
1336 if (length > 0) {
1337 // replace the currently selected text
1338 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, c);
1339 undo->Push (action);
1340 redo->Clear ();
1342 buffer->Replace (start, length, &c, 1);
1343 } else {
1344 // insert the text at the cursor position
1345 TextBoxUndoActionInsert *insert = NULL;
1347 if ((action = undo->Peek ()) && action->type == TextBoxUndoActionTypeInsert) {
1348 insert = (TextBoxUndoActionInsert *) action;
1350 if (!insert->Insert (start, c))
1351 insert = NULL;
1354 if (!insert) {
1355 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, c);
1356 undo->Push (insert);
1359 redo->Clear ();
1361 buffer->Insert (start, c);
1364 emit |= TEXT_CHANGED;
1365 cursor = start + 1;
1366 anchor = cursor;
1368 // check to see if selection has changed
1369 if (selection_anchor != anchor || selection_cursor != cursor) {
1370 SetSelectionStart (MIN (anchor, cursor));
1371 SetSelectionLength (abs (cursor - anchor));
1372 selection_anchor = anchor;
1373 selection_cursor = cursor;
1374 emit |= SELECTION_CHANGED;
1376 return true;
1379 void
1380 TextBoxBase::BatchPush ()
1382 batch++;
1385 void
1386 TextBoxBase::BatchPop ()
1388 if (batch == 0) {
1389 g_warning ("TextBoxBase batch underflow");
1390 return;
1393 batch--;
1396 void
1397 TextBoxBase::emit_selection_changed (EventObject *sender)
1399 if (((TextBoxBase *) sender)->IsLoaded ())
1400 ((TextBoxBase *) sender)->EmitSelectionChanged ();
1403 void
1404 TextBoxBase::EmitSelectionChangedAsync ()
1406 if (IsLoaded () && (events_mask & SELECTION_CHANGED))
1407 AddTickCall (TextBoxBase::emit_selection_changed);
1409 emit &= ~SELECTION_CHANGED;
1412 void
1413 TextBoxBase::emit_text_changed (EventObject *sender)
1415 if (((TextBoxBase *) sender)->IsLoaded ())
1416 ((TextBoxBase *) sender)->EmitTextChanged ();
1419 void
1420 TextBoxBase::EmitTextChangedAsync ()
1422 if (IsLoaded () && (events_mask & TEXT_CHANGED))
1423 AddTickCall (TextBoxBase::emit_text_changed);
1425 emit &= ~TEXT_CHANGED;
1428 void
1429 TextBoxBase::SyncAndEmit (bool sync_text)
1431 if (batch != 0 || emit == NOTHING_CHANGED)
1432 return;
1434 if (sync_text && (emit & TEXT_CHANGED))
1435 SyncText ();
1437 if (emit & SELECTION_CHANGED)
1438 SyncSelectedText ();
1440 if (emit & TEXT_CHANGED)
1441 EmitTextChangedAsync ();
1443 if (emit & SELECTION_CHANGED)
1444 EmitSelectionChangedAsync ();
1446 emit = NOTHING_CHANGED;
1449 void
1450 TextBoxBase::Paste (GtkClipboard *clipboard, const char *str)
1452 int length = abs (selection_cursor - selection_anchor);
1453 int start = MIN (selection_anchor, selection_cursor);
1454 TextBoxUndoAction *action;
1455 gunichar *text;
1456 glong len, i;
1458 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1459 return;
1461 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1462 // paste cannot exceed MaxLength
1463 len = max_length - (buffer->len - length);
1464 if (len > 0)
1465 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1466 else
1467 len = 0;
1468 text[len] = '\0';
1471 if (!multiline) {
1472 // only paste the content of the first line
1473 for (i = 0; i < len; i++) {
1474 if (g_unichar_type (text[i]) == G_UNICODE_LINE_SEPARATOR) {
1475 text = (gunichar *) g_realloc (text, UNICODE_LEN (i + 1));
1476 text[i] = '\0';
1477 len = i;
1478 break;
1483 ResetIMContext ();
1485 if (length > 0) {
1486 // replace the currently selected text
1487 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, len);
1489 buffer->Replace (start, length, text, len);
1490 } else if (len > 0) {
1491 // insert the text at the cursor position
1492 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, len, true);
1494 buffer->Insert (start, text, len);
1495 } else {
1496 g_free (text);
1497 return;
1500 undo->Push (action);
1501 redo->Clear ();
1502 g_free (text);
1504 emit |= TEXT_CHANGED;
1505 start += len;
1507 BatchPush ();
1508 SetSelectionStart (start);
1509 SetSelectionLength (0);
1510 BatchPop ();
1512 SyncAndEmit ();
1515 void
1516 TextBoxBase::paste (GtkClipboard *clipboard, const char *text, gpointer closure)
1518 ((TextBoxBase *) closure)->Paste (clipboard, text);
1521 void
1522 TextBoxBase::OnKeyDown (KeyEventArgs *args)
1524 GdkModifierType modifiers = (GdkModifierType) args->GetModifiers ();
1525 guint key = args->GetKeyVal ();
1526 GtkClipboard *clipboard;
1527 bool handled = false;
1529 if (args->IsModifier ())
1530 return;
1532 // set 'emit' to NOTHING_CHANGED so that we can figure out
1533 // what has chanegd after applying the changes that this
1534 // keypress will cause.
1535 emit = NOTHING_CHANGED;
1536 BatchPush ();
1538 switch (key) {
1539 case GDK_BackSpace:
1540 if (is_read_only)
1541 break;
1543 handled = KeyPressBackSpace (modifiers);
1544 break;
1545 case GDK_Delete:
1546 if (is_read_only)
1547 break;
1549 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == SHIFT_MASK) {
1550 // Shift+Delete => Cut
1551 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1552 if (selection_cursor != selection_anchor) {
1553 // copy selection to the clipboard and then cut
1554 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1558 SetSelectedText ("");
1559 handled = true;
1560 } else {
1561 handled = KeyPressDelete (modifiers);
1563 break;
1564 case GDK_Insert:
1565 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == SHIFT_MASK) {
1566 // Shift+Insert => Paste
1567 if (is_read_only)
1568 break;
1570 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1571 // paste clipboard contents to the buffer
1572 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1575 handled = true;
1576 } else if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == CONTROL_MASK) {
1577 // Control+Insert => Copy
1578 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1579 if (selection_cursor != selection_anchor) {
1580 // copy selection to the clipboard
1581 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1585 handled = true;
1587 break;
1588 case GDK_KP_Page_Down:
1589 case GDK_Page_Down:
1590 handled = KeyPressPageDown (modifiers);
1591 break;
1592 case GDK_KP_Page_Up:
1593 case GDK_Page_Up:
1594 handled = KeyPressPageUp (modifiers);
1595 break;
1596 case GDK_KP_Home:
1597 case GDK_Home:
1598 handled = KeyPressHome (modifiers);
1599 break;
1600 case GDK_KP_End:
1601 case GDK_End:
1602 handled = KeyPressEnd (modifiers);
1603 break;
1604 case GDK_KP_Right:
1605 case GDK_Right:
1606 handled = KeyPressRight (modifiers);
1607 break;
1608 case GDK_KP_Left:
1609 case GDK_Left:
1610 handled = KeyPressLeft (modifiers);
1611 break;
1612 case GDK_KP_Down:
1613 case GDK_Down:
1614 handled = KeyPressDown (modifiers);
1615 break;
1616 case GDK_KP_Up:
1617 case GDK_Up:
1618 handled = KeyPressUp (modifiers);
1619 break;
1620 default:
1621 if ((modifiers & (CONTROL_MASK | ALT_MASK | SHIFT_MASK)) == CONTROL_MASK) {
1622 switch (key) {
1623 case GDK_A:
1624 case GDK_a:
1625 // Ctrl+A => Select All
1626 handled = true;
1627 SelectAll ();
1628 break;
1629 case GDK_C:
1630 case GDK_c:
1631 // Ctrl+C => Copy
1632 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1633 if (selection_cursor != selection_anchor) {
1634 // copy selection to the clipboard
1635 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1639 handled = true;
1640 break;
1641 case GDK_X:
1642 case GDK_x:
1643 // Ctrl+X => Cut
1644 if (is_read_only)
1645 break;
1647 if (!secret && (clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1648 if (selection_cursor != selection_anchor) {
1649 // copy selection to the clipboard and then cut
1650 gtk_clipboard_set_text (clipboard, GetSelectedText (), -1);
1654 SetSelectedText ("");
1655 handled = true;
1656 break;
1657 case GDK_V:
1658 case GDK_v:
1659 // Ctrl+V => Paste
1660 if (is_read_only)
1661 break;
1663 if ((clipboard = GetClipboard (this, GDK_SELECTION_CLIPBOARD))) {
1664 // paste clipboard contents to the buffer
1665 gtk_clipboard_request_text (clipboard, TextBoxBase::paste, this);
1668 handled = true;
1669 break;
1670 case GDK_Y:
1671 case GDK_y:
1672 // Ctrl+Y => Redo
1673 if (!is_read_only) {
1674 handled = true;
1675 Redo ();
1677 break;
1678 case GDK_Z:
1679 case GDK_z:
1680 // Ctrl+Z => Undo
1681 if (!is_read_only) {
1682 handled = true;
1683 Undo ();
1685 break;
1686 default:
1687 // unhandled Control commands
1688 break;
1691 break;
1694 if (handled) {
1695 args->SetHandled (handled);
1696 ResetIMContext ();
1699 BatchPop ();
1701 SyncAndEmit ();
1704 void
1705 TextBoxBase::OnCharacterKeyDown (KeyEventArgs *args)
1707 guint key = args->GetKeyVal ();
1708 bool handled = false;
1709 gunichar c;
1711 if (!is_read_only && gtk_im_context_filter_keypress (im_ctx, args->GetEvent ())) {
1712 args->SetHandled (true);
1713 need_im_reset = true;
1714 return;
1717 if (is_read_only || args->IsModifier ())
1718 return;
1720 // set 'emit' to NOTHING_CHANGED so that we can figure out
1721 // what has chanegd after applying the changes that this
1722 // keypress will cause.
1723 emit = NOTHING_CHANGED;
1724 BatchPush ();
1726 switch (key) {
1727 case GDK_Return:
1728 handled = KeyPressUnichar ('\r');
1729 break;
1730 default:
1731 if ((args->GetModifiers () & (CONTROL_MASK | ALT_MASK)) == 0) {
1732 // normal character input
1733 if ((c = args->GetUnicode ()))
1734 handled = KeyPressUnichar (c);
1736 break;
1739 if (handled)
1740 args->SetHandled (handled);
1742 BatchPop ();
1744 SyncAndEmit ();
1747 void
1748 TextBoxBase::OnKeyUp (KeyEventArgs *args)
1750 if (!is_read_only) {
1751 if (gtk_im_context_filter_keypress (im_ctx, args->GetEvent ()))
1752 need_im_reset = true;
1754 args->SetHandled (true);
1758 bool
1759 TextBoxBase::DeleteSurrounding (int offset, int n_chars)
1761 const char *delete_start, *delete_end;
1762 const char *text = GetActualText ();
1763 int anchor = selection_anchor;
1764 int cursor = selection_cursor;
1765 TextBoxUndoAction *action;
1766 int start, length;
1768 if (is_read_only)
1769 return true;
1771 // get the utf-8 pointers so that we can use them to get gunichar offsets
1772 delete_start = g_utf8_offset_to_pointer (text, selection_cursor) + offset;
1773 delete_end = delete_start + n_chars;
1775 // get the character length/start index
1776 length = g_utf8_pointer_to_offset (delete_start, delete_end);
1777 start = g_utf8_pointer_to_offset (text, delete_start);
1779 if (length > 0) {
1780 action = new TextBoxUndoActionDelete (selection_anchor, selection_cursor, buffer, start, length);
1781 undo->Push (action);
1782 redo->Clear ();
1784 buffer->Cut (start, length);
1785 emit |= TEXT_CHANGED;
1786 anchor = start;
1787 cursor = start;
1790 BatchPush ();
1792 // check to see if selection has changed
1793 if (selection_anchor != anchor || selection_cursor != cursor) {
1794 SetSelectionStart (MIN (anchor, cursor));
1795 SetSelectionLength (abs (cursor - anchor));
1796 selection_anchor = anchor;
1797 selection_cursor = cursor;
1798 emit |= SELECTION_CHANGED;
1801 BatchPop ();
1803 SyncAndEmit ();
1805 return true;
1808 gboolean
1809 TextBoxBase::delete_surrounding (GtkIMContext *context, int offset, int n_chars, gpointer user_data)
1811 return ((TextBoxBase *) user_data)->DeleteSurrounding (offset, n_chars);
1814 bool
1815 TextBoxBase::RetrieveSurrounding ()
1817 const char *text = GetActualText ();
1818 const char *cursor = g_utf8_offset_to_pointer (text, selection_cursor);
1820 gtk_im_context_set_surrounding (im_ctx, text, -1, cursor - text);
1822 return true;
1825 gboolean
1826 TextBoxBase::retrieve_surrounding (GtkIMContext *context, gpointer user_data)
1828 return ((TextBoxBase *) user_data)->RetrieveSurrounding ();
1831 void
1832 TextBoxBase::Commit (const char *str)
1834 int length = abs (selection_cursor - selection_anchor);
1835 int start = MIN (selection_anchor, selection_cursor);
1836 TextBoxUndoAction *action;
1837 int anchor, cursor;
1838 gunichar *text;
1839 glong len, i;
1841 if (is_read_only)
1842 return;
1844 if (!(text = g_utf8_to_ucs4_fast (str ? str : "", -1, &len)))
1845 return;
1847 if (max_length > 0 && ((buffer->len - length) + len > max_length)) {
1848 // paste cannot exceed MaxLength
1849 len = max_length - (buffer->len - length);
1850 if (len > 0)
1851 text = (gunichar *) g_realloc (text, UNICODE_LEN (len + 1));
1852 else
1853 len = 0;
1854 text[len] = '\0';
1857 if (!multiline) {
1858 // only paste the content of the first line
1859 for (i = 0; i < len; i++) {
1860 if (g_unichar_type (text[i]) == G_UNICODE_LINE_SEPARATOR) {
1861 text = (gunichar *) g_realloc (text, UNICODE_LEN (i + 1));
1862 text[i] = '\0';
1863 len = i;
1864 break;
1869 if (length > 0) {
1870 // replace the currently selected text
1871 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, len);
1872 undo->Push (action);
1873 redo->Clear ();
1875 buffer->Replace (start, length, text, len);
1876 } else if (len > 0) {
1877 // insert the text at the cursor position
1878 TextBoxUndoActionInsert *insert = NULL;
1880 buffer->Insert (start, text, len);
1882 if ((action = undo->Peek ()) && action->type == TextBoxUndoActionTypeInsert) {
1883 insert = (TextBoxUndoActionInsert *) action;
1885 if (!insert->Insert (start, text, len))
1886 insert = NULL;
1889 if (!insert) {
1890 insert = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, len);
1891 undo->Push (insert);
1894 redo->Clear ();
1895 } else {
1896 g_free (text);
1897 return;
1900 emit = TEXT_CHANGED;
1901 cursor = start + len;
1902 anchor = cursor;
1903 g_free (text);
1905 BatchPush ();
1907 // check to see if selection has changed
1908 if (selection_anchor != anchor || selection_cursor != cursor) {
1909 SetSelectionStart (MIN (anchor, cursor));
1910 SetSelectionLength (abs (cursor - anchor));
1911 selection_anchor = anchor;
1912 selection_cursor = cursor;
1913 emit |= SELECTION_CHANGED;
1916 BatchPop ();
1918 SyncAndEmit ();
1921 void
1922 TextBoxBase::commit (GtkIMContext *context, const char *str, gpointer user_data)
1924 ((TextBoxBase *) user_data)->Commit (str);
1927 void
1928 TextBoxBase::ResetIMContext ()
1930 if (need_im_reset) {
1931 gtk_im_context_reset (im_ctx);
1932 need_im_reset = false;
1936 void
1937 TextBoxBase::OnMouseLeftButtonDown (MouseEventArgs *args)
1939 double x, y;
1940 int cursor;
1942 args->SetHandled (true);
1943 Focus ();
1945 if (view) {
1946 args->GetPosition (view, &x, &y);
1948 cursor = view->GetCursorFromXY (x, y);
1950 ResetIMContext ();
1952 // Single-Click: cursor placement
1953 captured = CaptureMouse ();
1954 selecting = true;
1956 BatchPush ();
1957 emit = NOTHING_CHANGED;
1958 SetSelectionStart (cursor);
1959 SetSelectionLength (0);
1960 BatchPop ();
1962 SyncAndEmit ();
1966 void
1967 TextBoxBase::mouse_left_button_down (EventObject *sender, EventArgs *args, gpointer closure)
1969 ((TextBoxBase *) closure)->OnMouseLeftButtonDown ((MouseEventArgs *) args);
1972 void
1973 TextBoxBase::OnMouseLeftButtonMultiClick (MouseEventArgs *args)
1975 int cursor, start, end;
1976 double x, y;
1978 args->SetHandled (true);
1980 if (view) {
1981 args->GetPosition (view, &x, &y);
1983 cursor = view->GetCursorFromXY (x, y);
1985 ResetIMContext ();
1987 if (args->GetClickCount () == 3) {
1988 // Note: Silverlight doesn't implement this, but to
1989 // be consistent with other TextEntry-type
1990 // widgets in Gtk+, we will.
1992 // Triple-Click: select the line
1993 if (captured)
1994 ReleaseMouseCapture ();
1995 start = CursorLineBegin (cursor);
1996 end = CursorLineEnd (cursor, true);
1997 selecting = false;
1998 captured = false;
1999 } else {
2000 // Double-Click: select the word
2001 if (captured)
2002 ReleaseMouseCapture ();
2003 start = CursorPrevWord (cursor);
2004 end = CursorNextWord (cursor);
2005 selecting = false;
2006 captured = false;
2009 BatchPush ();
2010 emit = NOTHING_CHANGED;
2011 SetSelectionStart (start);
2012 SetSelectionLength (end - start);
2013 BatchPop ();
2015 SyncAndEmit ();
2019 void
2020 TextBoxBase::mouse_left_button_multi_click (EventObject *sender, EventArgs *args, gpointer closure)
2022 ((TextBoxBase *) closure)->OnMouseLeftButtonMultiClick ((MouseEventArgs *) args);
2025 void
2026 TextBoxBase::OnMouseLeftButtonUp (MouseEventArgs *args)
2028 if (captured)
2029 ReleaseMouseCapture ();
2031 args->SetHandled (true);
2032 selecting = false;
2033 captured = false;
2036 void
2037 TextBoxBase::mouse_left_button_up (EventObject *sender, EventArgs *args, gpointer closure)
2039 ((TextBoxBase *) closure)->OnMouseLeftButtonUp ((MouseEventArgs *) args);
2042 void
2043 TextBoxBase::OnMouseMove (MouseEventArgs *args)
2045 int anchor = selection_anchor;
2046 int cursor = selection_cursor;
2047 double x, y;
2049 if (selecting) {
2050 args->GetPosition (view, &x, &y);
2051 args->SetHandled (true);
2053 cursor = view->GetCursorFromXY (x, y);
2055 BatchPush ();
2056 emit = NOTHING_CHANGED;
2057 SetSelectionStart (MIN (anchor, cursor));
2058 SetSelectionLength (abs (cursor - anchor));
2059 selection_anchor = anchor;
2060 selection_cursor = cursor;
2061 BatchPop ();
2063 SyncAndEmit ();
2067 void
2068 TextBoxBase::mouse_move (EventObject *sender, EventArgs *args, gpointer closure)
2070 ((TextBoxBase *) closure)->OnMouseMove ((MouseEventArgs *) args);
2073 void
2074 TextBoxBase::OnFocusOut (EventArgs *args)
2076 BatchPush ();
2077 emit = NOTHING_CHANGED;
2078 SetSelectionStart (selection_cursor);
2079 SetSelectionLength (0);
2080 BatchPop ();
2082 SyncAndEmit ();
2084 focused = false;
2086 if (view)
2087 view->OnFocusOut ();
2089 if (!is_read_only) {
2090 gtk_im_context_focus_out (im_ctx);
2091 need_im_reset = true;
2095 void
2096 TextBoxBase::focus_out (EventObject *sender, EventArgs *args, gpointer closure)
2098 ((TextBoxBase *) closure)->OnFocusOut (args);
2101 void
2102 TextBoxBase::OnFocusIn (EventArgs *args)
2104 focused = true;
2106 if (view)
2107 view->OnFocusIn ();
2109 if (!is_read_only) {
2110 gtk_im_context_focus_in (im_ctx);
2111 need_im_reset = true;
2115 void
2116 TextBoxBase::focus_in (EventObject *sender, EventArgs *args, gpointer closure)
2118 ((TextBoxBase *) closure)->OnFocusIn (args);
2121 void
2122 TextBoxBase::EmitCursorPositionChanged (double height, double x, double y)
2124 Emit (TextBoxBase::CursorPositionChangedEvent, new CursorPositionChangedEventArgs (height, x, y));
2127 void
2128 TextBoxBase::DownloaderComplete (Downloader *downloader)
2130 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2131 char *resource, *filename;
2132 InternalDownloader *idl;
2133 const char *path;
2134 Uri *uri;
2136 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
2137 if (!(filename = downloader->GetDownloadedFilename (NULL)))
2138 return;
2140 g_free (filename);
2142 if (!(idl = downloader->GetInternalDownloader ()))
2143 return;
2145 if (!(idl->GetObjectType () == Type::FILEDOWNLOADER))
2146 return;
2148 uri = downloader->GetUri ();
2150 // If the downloaded file was a zip file, this'll get the path to the
2151 // extracted zip directory, else it will simply be the path to the
2152 // downloaded file.
2153 if (!(path = ((FileDownloader *) idl)->GetUnzippedPath ()))
2154 return;
2156 resource = uri->ToString ((UriToStringFlags) (UriHidePasswd | UriHideQuery | UriHideFragment));
2157 manager->AddResource (resource, path);
2158 g_free (resource);
2160 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedFont, NULL));
2163 void
2164 TextBoxBase::downloader_complete (EventObject *sender, EventArgs *calldata, gpointer closure)
2166 ((TextBoxBase *) closure)->DownloaderComplete ((Downloader *) sender);
2169 void
2170 TextBoxBase::AddFontSource (Downloader *downloader)
2172 downloader->AddHandler (downloader->CompletedEvent, downloader_complete, this);
2173 g_ptr_array_add (downloaders, downloader);
2174 downloader->ref ();
2176 if (downloader->Started () || downloader->Completed ()) {
2177 if (downloader->Completed ())
2178 DownloaderComplete (downloader);
2179 } else {
2180 // This is what actually triggers the download
2181 downloader->Send ();
2185 void
2186 TextBoxBase::AddFontResource (const char *resource)
2188 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2189 Application *application = Application::GetCurrent ();
2190 Downloader *downloader;
2191 Surface *surface;
2192 char *path;
2193 Uri *uri;
2195 uri = new Uri ();
2197 if (!application || !uri->Parse (resource) || !(path = application->GetResourceAsPath (GetResourceBase(), uri))) {
2198 if ((surface = GetSurface ()) && (downloader = surface->CreateDownloader ())) {
2199 downloader->Open ("GET", resource, FontPolicy);
2200 AddFontSource (downloader);
2201 downloader->unref ();
2204 delete uri;
2206 return;
2209 manager->AddResource (resource, path);
2210 g_free (path);
2211 delete uri;
2214 void
2215 TextBoxBase::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2217 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2219 if (args->GetId () == Control::FontFamilyProperty) {
2220 FontFamily *family = args->GetNewValue () ? args->GetNewValue ()->AsFontFamily () : NULL;
2221 char **families, *fragment;
2222 int i;
2224 CleanupDownloaders ();
2226 if (family && family->source) {
2227 families = g_strsplit (family->source, ",", -1);
2228 for (i = 0; families[i]; i++) {
2229 g_strstrip (families[i]);
2230 if ((fragment = strchr (families[i], '#'))) {
2231 // the first portion of this string is the resource name...
2232 *fragment = '\0';
2233 AddFontResource (families[i]);
2236 g_strfreev (families);
2239 font->SetFamily (family ? family->source : NULL);
2240 changed = TextBoxModelChangedFont;
2241 } else if (args->GetId () == Control::FontSizeProperty) {
2242 double size = args->GetNewValue()->AsDouble ();
2243 changed = TextBoxModelChangedFont;
2244 font->SetSize (size);
2245 } else if (args->GetId () == Control::FontStretchProperty) {
2246 FontStretches stretch = args->GetNewValue()->AsFontStretch()->stretch;
2247 changed = TextBoxModelChangedFont;
2248 font->SetStretch (stretch);
2249 } else if (args->GetId () == Control::FontStyleProperty) {
2250 FontStyles style = args->GetNewValue()->AsFontStyle ()->style;
2251 changed = TextBoxModelChangedFont;
2252 font->SetStyle (style);
2253 } else if (args->GetId () == Control::FontWeightProperty) {
2254 FontWeights weight = args->GetNewValue()->AsFontWeight ()->weight;
2255 changed = TextBoxModelChangedFont;
2256 font->SetWeight (weight);
2257 } else if (args->GetId () == FrameworkElement::MinHeightProperty) {
2258 // pass this along to our TextBoxView
2259 if (view)
2260 view->SetMinHeight (args->GetNewValue ()->AsDouble ());
2261 } else if (args->GetId () == FrameworkElement::MaxHeightProperty) {
2262 // pass this along to our TextBoxView
2263 if (view)
2264 view->SetMaxHeight (args->GetNewValue ()->AsDouble ());
2265 } else if (args->GetId () == FrameworkElement::MinWidthProperty) {
2266 // pass this along to our TextBoxView
2267 if (view)
2268 view->SetMinWidth (args->GetNewValue ()->AsDouble ());
2269 } else if (args->GetId () == FrameworkElement::MaxWidthProperty) {
2270 // pass this along to our TextBoxView
2271 if (view)
2272 view->SetMaxWidth (args->GetNewValue ()->AsDouble ());
2273 } else if (args->GetId () == FrameworkElement::HeightProperty) {
2274 // pass this along to our TextBoxView
2275 if (view)
2276 view->SetHeight (args->GetNewValue ()->AsDouble ());
2277 } else if (args->GetId () == FrameworkElement::WidthProperty) {
2278 // pass this along to our TextBoxView
2279 if (view)
2280 view->SetWidth (args->GetNewValue ()->AsDouble ());
2283 if (changed != TextBoxModelChangedNothing)
2284 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2286 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOXBASE) {
2287 Control::OnPropertyChanged (args, error);
2288 return;
2291 NotifyListenersOfPropertyChange (args, error);
2294 void
2295 TextBoxBase::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2297 if (prop && (prop->GetId () == Control::BackgroundProperty ||
2298 prop->GetId () == Control::ForegroundProperty)) {
2299 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
2300 Invalidate ();
2303 if (prop->GetOwnerType () != Type::TEXTBOXBASE)
2304 Control::OnSubPropertyChanged (prop, obj, subobj_args);
2307 void
2308 TextBoxBase::OnApplyTemplate ()
2310 contentElement = GetTemplateChild ("ContentElement");
2312 if (contentElement == NULL) {
2313 g_warning ("TextBoxBase::OnApplyTemplate: no ContentElement found");
2314 Control::OnApplyTemplate ();
2315 return;
2318 view = new TextBoxView ();
2319 view->SetTextBox (this);
2321 view->SetMinHeight (GetMinHeight ());
2322 view->SetMaxHeight (GetMaxHeight ());
2323 view->SetMinWidth (GetMinWidth ());
2324 view->SetMaxWidth (GetMaxWidth ());
2325 view->SetHeight (GetHeight ());
2326 view->SetWidth (GetWidth ());
2328 // Insert our TextBoxView
2329 if (contentElement->Is (Type::CONTENTCONTROL)) {
2330 ContentControl *control = (ContentControl *) contentElement;
2332 control->SetValue (ContentControl::ContentProperty, Value (view));
2333 } else if (contentElement->Is (Type::BORDER)) {
2334 Border *border = (Border *) contentElement;
2336 border->SetValue (Border::ChildProperty, Value (view));
2337 } else if (contentElement->Is (Type::PANEL)) {
2338 DependencyObjectCollection *children = ((Panel *) contentElement)->GetChildren ();
2340 children->Add (view);
2341 } else {
2342 g_warning ("TextBoxBase::OnApplyTemplate: don't know how to handle a ContentELement of type %s",
2343 contentElement->GetType ()->GetName ());
2344 view->unref ();
2345 view = NULL;
2348 Control::OnApplyTemplate ();
2351 void
2352 TextBoxBase::ClearSelection (int start)
2354 BatchPush ();
2355 SetSelectionStart (start);
2356 SetSelectionLength (0);
2357 BatchPop ();
2360 bool
2361 TextBoxBase::SelectWithError (int start, int length, MoonError *error)
2363 if (start < 0) {
2364 MoonError::FillIn (error, MoonError::ARGUMENT, "selection start must be >= 0");
2365 return false;
2368 if (length < 0) {
2369 MoonError::FillIn (error, MoonError::ARGUMENT, "selection length must be >= 0");
2370 return false;
2373 if (start > buffer->len)
2374 start = buffer->len;
2376 if (length > (buffer->len - start))
2377 length = (buffer->len - start);
2379 BatchPush ();
2380 SetSelectionStart (start);
2381 SetSelectionLength (length);
2382 BatchPop ();
2384 ResetIMContext ();
2386 SyncAndEmit ();
2388 return true;
2391 void
2392 TextBoxBase::SelectAll ()
2394 SelectWithError (0, buffer->len, NULL);
2397 bool
2398 TextBoxBase::CanUndo ()
2400 return !undo->IsEmpty ();
2403 bool
2404 TextBoxBase::CanRedo ()
2406 return !redo->IsEmpty ();
2409 void
2410 TextBoxBase::Undo ()
2412 TextBoxUndoActionReplace *replace;
2413 TextBoxUndoActionInsert *insert;
2414 TextBoxUndoActionDelete *dele;
2415 TextBoxUndoAction *action;
2416 int anchor, cursor;
2418 if (undo->IsEmpty ())
2419 return;
2421 action = undo->Pop ();
2422 redo->Push (action);
2424 switch (action->type) {
2425 case TextBoxUndoActionTypeInsert:
2426 insert = (TextBoxUndoActionInsert *) action;
2428 buffer->Cut (insert->start, insert->length);
2429 anchor = action->selection_anchor;
2430 cursor = action->selection_cursor;
2431 break;
2432 case TextBoxUndoActionTypeDelete:
2433 dele = (TextBoxUndoActionDelete *) action;
2435 buffer->Insert (dele->start, dele->text, dele->length);
2436 anchor = action->selection_anchor;
2437 cursor = action->selection_cursor;
2438 break;
2439 case TextBoxUndoActionTypeReplace:
2440 replace = (TextBoxUndoActionReplace *) action;
2442 buffer->Cut (replace->start, replace->inlen);
2443 buffer->Insert (replace->start, replace->deleted, replace->length);
2444 anchor = action->selection_anchor;
2445 cursor = action->selection_cursor;
2446 break;
2449 BatchPush ();
2450 SetSelectionStart (MIN (anchor, cursor));
2451 SetSelectionLength (abs (cursor - anchor));
2452 emit = TEXT_CHANGED | SELECTION_CHANGED;
2453 selection_anchor = anchor;
2454 selection_cursor = cursor;
2455 BatchPop ();
2457 SyncAndEmit ();
2460 void
2461 TextBoxBase::Redo ()
2463 TextBoxUndoActionReplace *replace;
2464 TextBoxUndoActionInsert *insert;
2465 TextBoxUndoActionDelete *dele;
2466 TextBoxUndoAction *action;
2467 int anchor, cursor;
2469 if (redo->IsEmpty ())
2470 return;
2472 action = redo->Pop ();
2473 undo->Push (action);
2475 switch (action->type) {
2476 case TextBoxUndoActionTypeInsert:
2477 insert = (TextBoxUndoActionInsert *) action;
2479 buffer->Insert (insert->start, insert->buffer->text, insert->buffer->len);
2480 anchor = cursor = insert->start + insert->buffer->len;
2481 break;
2482 case TextBoxUndoActionTypeDelete:
2483 dele = (TextBoxUndoActionDelete *) action;
2485 buffer->Cut (dele->start, dele->length);
2486 anchor = cursor = dele->start;
2487 break;
2488 case TextBoxUndoActionTypeReplace:
2489 replace = (TextBoxUndoActionReplace *) action;
2491 buffer->Cut (replace->start, replace->length);
2492 buffer->Insert (replace->start, replace->inserted, replace->inlen);
2493 anchor = cursor = replace->start + replace->inlen;
2494 break;
2497 BatchPush ();
2498 SetSelectionStart (MIN (anchor, cursor));
2499 SetSelectionLength (abs (cursor - anchor));
2500 emit = TEXT_CHANGED | SELECTION_CHANGED;
2501 selection_anchor = anchor;
2502 selection_cursor = cursor;
2503 BatchPop ();
2505 SyncAndEmit ();
2510 // TextBoxDynamicPropertyValueProvider
2513 class TextBoxDynamicPropertyValueProvider : public PropertyValueProvider {
2514 Value *selection_background;
2515 Value *selection_foreground;
2517 public:
2518 TextBoxDynamicPropertyValueProvider (DependencyObject *obj, PropertyPrecedence precedence) : PropertyValueProvider (obj, precedence)
2520 selection_background = NULL;
2521 selection_foreground = NULL;
2524 virtual ~TextBoxDynamicPropertyValueProvider ()
2526 delete selection_background;
2527 delete selection_foreground;
2530 virtual Value *GetPropertyValue (DependencyProperty *property)
2532 if (property->GetId () == TextBox::SelectionBackgroundProperty) {
2533 return selection_background;
2534 } else if (property->GetId () == TextBox::SelectionForegroundProperty) {
2535 return selection_foreground;
2538 return NULL;
2541 void InitializeSelectionBrushes ()
2543 if (!selection_background)
2544 selection_background = Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2546 if (!selection_foreground)
2547 selection_foreground = Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2553 // TextBox
2556 TextBox::TextBox ()
2558 providers[PropertyPrecedence_DynamicValue] = new TextBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2560 Initialize (Type::TEXTBOX, "System.Windows.Controls.TextBox");
2561 events_mask = TEXT_CHANGED | SELECTION_CHANGED;
2562 multiline = true;
2565 void
2566 TextBox::EmitSelectionChanged ()
2568 Emit (TextBox::SelectionChangedEvent, new RoutedEventArgs ());
2571 void
2572 TextBox::EmitTextChanged ()
2574 Emit (TextBox::TextChangedEvent, new TextChangedEventArgs ());
2577 void
2578 TextBox::SyncSelectedText ()
2580 if (selection_cursor != selection_anchor) {
2581 int length = abs (selection_cursor - selection_anchor);
2582 int start = MIN (selection_anchor, selection_cursor);
2583 char *text;
2585 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2587 setvalue = false;
2588 SetValue (TextBox::SelectedTextProperty, Value (text, true));
2589 setvalue = true;
2590 } else {
2591 setvalue = false;
2592 SetValue (TextBox::SelectedTextProperty, Value (""));
2593 setvalue = true;
2597 void
2598 TextBox::SyncText ()
2600 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
2602 setvalue = false;
2603 SetValue (TextBox::TextProperty, Value (text, true));
2604 setvalue = true;
2607 void
2608 TextBox::ClearFontSource ()
2610 ClearValue (TextBox::FontSourceProperty);
2613 void
2614 TextBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
2616 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
2617 DependencyProperty *prop;
2618 int start, length;
2620 if (args->GetId () == TextBox::AcceptsReturnProperty) {
2621 // update accepts_return state
2622 accepts_return = args->GetNewValue ()->AsBool ();
2623 } else if (args->GetId () == TextBox::CaretBrushProperty) {
2624 // FIXME: if we want to be perfect, we could invalidate the
2625 // blinking cursor rect if it is active... but is it that
2626 // important?
2627 } else if (args->GetId () == TextBox::FontSourceProperty) {
2628 FontSource *source = args->GetNewValue () ? args->GetNewValue ()->AsFontSource () : NULL;
2629 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
2631 g_free (font_source);
2633 if (source && source->stream) {
2634 font_source = g_strdup_printf ("font-source://%p.%p", this, source);
2635 manager->AddResource (font_source, source->stream);
2636 } else {
2637 font_source = NULL;
2639 } else if (args->GetId () == TextBox::IsReadOnlyProperty) {
2640 // update is_read_only state
2641 is_read_only = args->GetNewValue ()->AsBool ();
2643 if (focused) {
2644 if (is_read_only) {
2645 ResetIMContext ();
2646 gtk_im_context_focus_out (im_ctx);
2647 } else {
2648 gtk_im_context_focus_in (im_ctx);
2651 } else if (args->GetId () == TextBox::MaxLengthProperty) {
2652 // update max_length state
2653 max_length = args->GetNewValue ()->AsInt32 ();
2654 } else if (args->GetId () == TextBox::SelectedTextProperty) {
2655 if (setvalue) {
2656 Value *value = args->GetNewValue ();
2657 const char *str = value && value->AsString () ? value->AsString () : "";
2658 TextBoxUndoAction *action = NULL;
2659 gunichar *text;
2660 glong textlen;
2662 length = abs (selection_cursor - selection_anchor);
2663 start = MIN (selection_anchor, selection_cursor);
2665 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2666 if (length > 0) {
2667 // replace the currently selected text
2668 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, textlen);
2670 buffer->Replace (start, length, text, textlen);
2671 } else if (textlen > 0) {
2672 // insert the text at the cursor
2673 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, textlen);
2675 buffer->Insert (start, text, textlen);
2678 g_free (text);
2680 if (action != NULL) {
2681 emit |= TEXT_CHANGED;
2682 undo->Push (action);
2683 redo->Clear ();
2685 ClearSelection (start + textlen);
2686 ResetIMContext ();
2688 SyncAndEmit ();
2690 } else {
2691 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str);
2694 } else if (args->GetId () == TextBox::SelectionStartProperty) {
2695 length = abs (selection_cursor - selection_anchor);
2696 start = args->GetNewValue ()->AsInt32 ();
2698 if (start > buffer->len) {
2699 // clamp the selection start offset to a valid value
2700 SetSelectionStart (buffer->len);
2701 return;
2704 if (start + length > buffer->len) {
2705 // clamp the selection length to a valid value
2706 BatchPush ();
2707 length = buffer->len - start;
2708 SetSelectionLength (length);
2709 BatchPop ();
2712 // SelectionStartProperty is marked as AlwaysChange -
2713 // if the value hasn't actually changed, then we do
2714 // not want to emit the TextBoxModelChanged event.
2715 if (selection_anchor != start) {
2716 changed = TextBoxModelChangedSelection;
2717 have_offset = false;
2720 // When set programatically, anchor is always the
2721 // start and cursor is always the end.
2722 selection_cursor = start + length;
2723 selection_anchor = start;
2725 emit |= SELECTION_CHANGED;
2727 SyncAndEmit ();
2728 } else if (args->GetId () == TextBox::SelectionLengthProperty) {
2729 start = MIN (selection_anchor, selection_cursor);
2730 length = args->GetNewValue ()->AsInt32 ();
2732 if (start + length > buffer->len) {
2733 // clamp the selection length to a valid value
2734 length = buffer->len - start;
2735 SetSelectionLength (length);
2736 return;
2739 // SelectionLengthProperty is marked as AlwaysChange -
2740 // if the value hasn't actually changed, then we do
2741 // not want to emit the TextBoxModelChanged event.
2742 if (selection_cursor != start + length) {
2743 changed = TextBoxModelChangedSelection;
2744 have_offset = false;
2747 // When set programatically, anchor is always the
2748 // start and cursor is always the end.
2749 selection_cursor = start + length;
2750 selection_anchor = start;
2752 emit |= SELECTION_CHANGED;
2754 SyncAndEmit ();
2755 } else if (args->GetId () == TextBox::SelectionBackgroundProperty) {
2756 changed = TextBoxModelChangedBrush;
2757 } else if (args->GetId () == TextBox::SelectionForegroundProperty) {
2758 changed = TextBoxModelChangedBrush;
2759 } else if (args->GetId () == TextBox::TextProperty) {
2760 if (setvalue) {
2761 Value *value = args->GetNewValue ();
2762 const char *str = value && value->AsString () ? value->AsString () : "";
2763 TextBoxUndoAction *action;
2764 gunichar *text;
2765 glong textlen;
2767 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
2768 if (buffer->len > 0) {
2769 // replace the current text
2770 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, 0, buffer->len, text, textlen);
2772 buffer->Replace (0, buffer->len, text, textlen);
2773 } else {
2774 // insert the text
2775 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
2777 buffer->Insert (0, text, textlen);
2780 undo->Push (action);
2781 redo->Clear ();
2782 g_free (text);
2784 emit |= TEXT_CHANGED;
2785 ClearSelection (0);
2786 ResetIMContext ();
2788 SyncAndEmit (value && !value->GetIsNull ());
2789 } else {
2790 g_warning ("g_utf8_to_ucs4_fast failed for string '%s'", str);
2794 changed = TextBoxModelChangedText;
2795 } else if (args->GetId () == TextBox::TextAlignmentProperty) {
2796 changed = TextBoxModelChangedTextAlignment;
2797 } else if (args->GetId () == TextBox::TextWrappingProperty) {
2798 changed = TextBoxModelChangedTextWrapping;
2799 } else if (args->GetId () == TextBox::HorizontalScrollBarVisibilityProperty) {
2800 // XXX more crap because these aren't templatebound.
2801 if (contentElement) {
2802 if ((prop = contentElement->GetDependencyProperty ("VerticalScrollBarVisibility")))
2803 contentElement->SetValue (prop, GetValue (TextBox::VerticalScrollBarVisibilityProperty));
2806 Invalidate ();
2807 } else if (args->GetId () == TextBox::VerticalScrollBarVisibilityProperty) {
2808 // XXX more crap because these aren't templatebound.
2809 if (contentElement) {
2810 if ((prop = contentElement->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2811 contentElement->SetValue (prop, GetValue (TextBox::HorizontalScrollBarVisibilityProperty));
2814 Invalidate ();
2817 if (changed != TextBoxModelChangedNothing)
2818 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
2820 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
2821 TextBoxBase::OnPropertyChanged (args, error);
2822 return;
2825 NotifyListenersOfPropertyChange (args, error);
2828 void
2829 TextBox::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
2831 if (prop && (prop->GetId () == TextBox::SelectionBackgroundProperty ||
2832 prop->GetId () == TextBox::SelectionForegroundProperty)) {
2833 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
2834 Invalidate ();
2837 if (prop->GetOwnerType () != Type::TEXTBOX)
2838 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
2841 void
2842 TextBox::OnApplyTemplate ()
2844 DependencyProperty *prop;
2846 TextBoxBase::OnApplyTemplate ();
2848 if (!contentElement)
2849 return;
2851 // XXX LAME these should be template bindings in the textbox template.
2852 if ((prop = contentElement->GetDependencyProperty ("VerticalScrollBarVisibility")))
2853 contentElement->SetValue (prop, GetValue (TextBox::VerticalScrollBarVisibilityProperty));
2855 if ((prop = contentElement->GetDependencyProperty ("HorizontalScrollBarVisibility")))
2856 contentElement->SetValue (prop, GetValue (TextBox::HorizontalScrollBarVisibilityProperty));
2861 // PasswordBox
2865 // PasswordBoxDynamicPropertyValueProvider
2868 class PasswordBoxDynamicPropertyValueProvider : public PropertyValueProvider {
2869 Value *selection_background;
2870 Value *selection_foreground;
2872 public:
2873 PasswordBoxDynamicPropertyValueProvider (DependencyObject *obj, PropertyPrecedence precedence) : PropertyValueProvider (obj, precedence)
2875 selection_background = NULL;
2876 selection_foreground = NULL;
2879 virtual ~PasswordBoxDynamicPropertyValueProvider ()
2881 delete selection_background;
2882 delete selection_foreground;
2885 virtual Value *GetPropertyValue (DependencyProperty *property)
2887 if (property->GetId () == PasswordBox::SelectionBackgroundProperty) {
2888 return selection_background;
2889 } else if (property->GetId () == PasswordBox::SelectionForegroundProperty) {
2890 return selection_foreground;
2893 return NULL;
2896 void InitializeSelectionBrushes ()
2898 if (!selection_background)
2899 selection_background = Value::CreateUnrefPtr (new SolidColorBrush ("#FF444444"));
2901 if (!selection_foreground)
2902 selection_foreground = Value::CreateUnrefPtr (new SolidColorBrush ("#FFFFFFFF"));
2908 // PasswordBox
2911 PasswordBox::PasswordBox ()
2913 providers[PropertyPrecedence_DynamicValue] = new PasswordBoxDynamicPropertyValueProvider (this, PropertyPrecedence_DynamicValue);
2915 Initialize (Type::PASSWORDBOX, "System.Windows.Controls.PasswordBox");
2916 events_mask = TEXT_CHANGED;
2917 secret = true;
2919 display = g_string_new ("");
2922 PasswordBox::~PasswordBox ()
2924 g_string_free (display, true);
2928 PasswordBox::CursorDown (int cursor, bool page)
2930 return GetBuffer ()->len;
2934 PasswordBox::CursorUp (int cursor, bool page)
2936 return 0;
2940 PasswordBox::CursorLineBegin (int cursor)
2942 return 0;
2946 PasswordBox::CursorLineEnd (int cursor, bool include)
2948 return GetBuffer ()->len;
2952 PasswordBox::CursorNextWord (int cursor)
2954 return GetBuffer ()->len;
2958 PasswordBox::CursorPrevWord (int cursor)
2960 return 0;
2963 void
2964 PasswordBox::EmitTextChanged ()
2966 Emit (PasswordBox::PasswordChangedEvent, new RoutedEventArgs ());
2969 void
2970 PasswordBox::SyncSelectedText ()
2972 if (selection_cursor != selection_anchor) {
2973 int length = abs (selection_cursor - selection_anchor);
2974 int start = MIN (selection_anchor, selection_cursor);
2975 char *text;
2977 text = g_ucs4_to_utf8 (buffer->text + start, length, NULL, NULL, NULL);
2979 setvalue = false;
2980 SetValue (PasswordBox::SelectedTextProperty, Value (text, true));
2981 setvalue = true;
2982 } else {
2983 setvalue = false;
2984 SetValue (PasswordBox::SelectedTextProperty, Value (""));
2985 setvalue = true;
2989 void
2990 PasswordBox::SyncDisplayText ()
2992 gunichar c = GetPasswordChar ();
2994 g_string_truncate (display, 0);
2996 for (int i = 0; i < buffer->len; i++)
2997 g_string_append_unichar (display, c);
3000 void
3001 PasswordBox::SyncText ()
3003 char *text = g_ucs4_to_utf8 (buffer->text, buffer->len, NULL, NULL, NULL);
3005 setvalue = false;
3006 SetValue (PasswordBox::PasswordProperty, Value (text, true));
3007 setvalue = true;
3009 SyncDisplayText ();
3012 const char *
3013 PasswordBox::GetDisplayText ()
3015 return display->str;
3018 void
3019 PasswordBox::ClearFontSource ()
3021 ClearValue (PasswordBox::FontSourceProperty);
3024 void
3025 PasswordBox::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
3027 TextBoxModelChangeType changed = TextBoxModelChangedNothing;
3028 int length, start;
3030 if (args->GetId () == PasswordBox::CaretBrushProperty) {
3031 // FIXME: if we want to be perfect, we could invalidate the
3032 // blinking cursor rect if it is active... but is it that
3033 // important?
3034 } else if (args->GetId () == PasswordBox::FontSourceProperty) {
3035 FontSource *source = args->GetNewValue () ? args->GetNewValue ()->AsFontSource () : NULL;
3036 FontManager *manager = Deployment::GetCurrent ()->GetFontManager ();
3038 g_free (font_source);
3040 if (source && source->stream) {
3041 font_source = g_strdup_printf ("font-source://%p.%p", this, source);
3042 manager->AddResource (font_source, source->stream);
3043 } else {
3044 font_source = NULL;
3046 } else if (args->GetId () == PasswordBox::MaxLengthProperty) {
3047 // update max_length state
3048 max_length = args->GetNewValue()->AsInt32 ();
3049 } else if (args->GetId () == PasswordBox::PasswordCharProperty) {
3050 changed = TextBoxModelChangedText;
3051 } else if (args->GetId () == PasswordBox::PasswordProperty) {
3052 if (setvalue) {
3053 Value *value = args->GetNewValue ();
3054 const char *str = value && value->AsString () ? value->AsString () : "";
3055 TextBoxUndoAction *action;
3056 gunichar *text;
3057 glong textlen;
3059 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
3060 if (buffer->len > 0) {
3061 // replace the current text
3062 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, 0, buffer->len, text, textlen);
3064 buffer->Replace (0, buffer->len, text, textlen);
3065 } else {
3066 // insert the text
3067 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, 0, text, textlen);
3069 buffer->Insert (0, text, textlen);
3072 undo->Push (action);
3073 redo->Clear ();
3074 g_free (text);
3076 emit |= TEXT_CHANGED;
3077 SyncDisplayText ();
3078 ClearSelection (0);
3079 ResetIMContext ();
3081 SyncAndEmit ();
3085 changed = TextBoxModelChangedText;
3086 } else if (args->GetId () == PasswordBox::SelectedTextProperty) {
3087 if (setvalue) {
3088 Value *value = args->GetNewValue ();
3089 const char *str = value && value->AsString () ? value->AsString () : "";
3090 TextBoxUndoAction *action = NULL;
3091 gunichar *text;
3092 glong textlen;
3094 length = abs (selection_cursor - selection_anchor);
3095 start = MIN (selection_anchor, selection_cursor);
3097 if ((text = g_utf8_to_ucs4_fast (str, -1, &textlen))) {
3098 if (length > 0) {
3099 // replace the currently selected text
3100 action = new TextBoxUndoActionReplace (selection_anchor, selection_cursor, buffer, start, length, text, textlen);
3102 buffer->Replace (start, length, text, textlen);
3103 } else if (textlen > 0) {
3104 // insert the text at the cursor
3105 action = new TextBoxUndoActionInsert (selection_anchor, selection_cursor, start, text, textlen);
3107 buffer->Insert (start, text, textlen);
3110 g_free (text);
3112 if (action != NULL) {
3113 undo->Push (action);
3114 redo->Clear ();
3116 ClearSelection (start + textlen);
3117 emit |= TEXT_CHANGED;
3118 SyncDisplayText ();
3119 ResetIMContext ();
3121 SyncAndEmit ();
3125 } else if (args->GetId () == PasswordBox::SelectionStartProperty) {
3126 length = abs (selection_cursor - selection_anchor);
3127 start = args->GetNewValue ()->AsInt32 ();
3129 if (start > buffer->len) {
3130 // clamp the selection start offset to a valid value
3131 SetSelectionStart (buffer->len);
3132 return;
3135 if (start + length > buffer->len) {
3136 // clamp the selection length to a valid value
3137 BatchPush ();
3138 length = buffer->len - start;
3139 SetSelectionLength (length);
3140 BatchPop ();
3143 // SelectionStartProperty is marked as AlwaysChange -
3144 // if the value hasn't actually changed, then we do
3145 // not want to emit the TextBoxModelChanged event.
3146 if (selection_anchor != start) {
3147 changed = TextBoxModelChangedSelection;
3148 have_offset = false;
3151 // When set programatically, anchor is always the
3152 // start and cursor is always the end.
3153 selection_cursor = start + length;
3154 selection_anchor = start;
3156 emit |= SELECTION_CHANGED;
3158 SyncAndEmit ();
3159 } else if (args->GetId () == PasswordBox::SelectionLengthProperty) {
3160 start = MIN (selection_anchor, selection_cursor);
3161 length = args->GetNewValue ()->AsInt32 ();
3163 if (start + length > buffer->len) {
3164 // clamp the selection length to a valid value
3165 length = buffer->len - start;
3166 SetSelectionLength (length);
3167 return;
3170 // SelectionLengthProperty is marked as AlwaysChange -
3171 // if the value hasn't actually changed, then we do
3172 // not want to emit the TextBoxModelChanged event.
3173 if (selection_cursor != start + length) {
3174 changed = TextBoxModelChangedSelection;
3175 have_offset = false;
3178 // When set programatically, anchor is always the
3179 // start and cursor is always the end.
3180 selection_cursor = start + length;
3181 selection_anchor = start;
3183 emit |= SELECTION_CHANGED;
3185 SyncAndEmit ();
3186 } else if (args->GetId () == PasswordBox::SelectionBackgroundProperty) {
3187 changed = TextBoxModelChangedBrush;
3188 } else if (args->GetId () == PasswordBox::SelectionForegroundProperty) {
3189 changed = TextBoxModelChangedBrush;
3192 if (changed != TextBoxModelChangedNothing)
3193 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (changed, args));
3195 if (args->GetProperty ()->GetOwnerType () != Type::TEXTBOX) {
3196 TextBoxBase::OnPropertyChanged (args, error);
3197 return;
3200 NotifyListenersOfPropertyChange (args, error);
3203 void
3204 PasswordBox::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
3206 if (prop && (prop->GetId () == PasswordBox::SelectionBackgroundProperty ||
3207 prop->GetId () == PasswordBox::SelectionForegroundProperty)) {
3208 Emit (ModelChangedEvent, new TextBoxModelChangedEventArgs (TextBoxModelChangedBrush));
3209 Invalidate ();
3212 if (prop->GetOwnerType () != Type::TEXTBOX)
3213 TextBoxBase::OnSubPropertyChanged (prop, obj, subobj_args);
3218 // TextBoxView
3221 #define CURSOR_BLINK_TIMEOUT_DEFAULT 900
3222 #define CURSOR_BLINK_ON_MULTIPLIER 2
3223 #define CURSOR_BLINK_OFF_MULTIPLIER 1
3224 #define CURSOR_BLINK_DELAY_MULTIPLIER 3
3225 #define CURSOR_BLINK_DIVIDER 3
3227 TextBoxView::TextBoxView ()
3229 SetObjectType (Type::TEXTBOXVIEW);
3231 AddHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3232 AddHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3234 SetCursor (MouseCursorIBeam);
3236 cursor = Rect (0, 0, 0, 0);
3237 layout = new TextLayout ();
3238 selection_changed = false;
3239 had_selected_text = false;
3240 cursor_visible = false;
3241 blink_timeout = 0;
3242 textbox = NULL;
3243 dirty = false;
3246 TextBoxView::~TextBoxView ()
3248 RemoveHandler (UIElement::MouseLeftButtonDownEvent, TextBoxView::mouse_left_button_down, this);
3249 RemoveHandler (UIElement::MouseLeftButtonUpEvent, TextBoxView::mouse_left_button_up, this);
3251 if (textbox) {
3252 textbox->RemoveHandler (TextBox::ModelChangedEvent, TextBoxView::model_changed, this);
3253 textbox->view = NULL;
3256 DisconnectBlinkTimeout ();
3258 delete layout;
3261 TextLayoutLine *
3262 TextBoxView::GetLineFromY (double y, int *index)
3264 return layout->GetLineFromY (Point (), y, index);
3267 TextLayoutLine *
3268 TextBoxView::GetLineFromIndex (int index)
3270 return layout->GetLineFromIndex (index);
3274 TextBoxView::GetCursorFromXY (double x, double y)
3276 return layout->GetCursorFromXY (Point (), x, y);
3279 gboolean
3280 TextBoxView::blink (void *user_data)
3282 return ((TextBoxView *) user_data)->Blink ();
3285 static guint
3286 GetCursorBlinkTimeout (TextBoxView *view)
3288 GtkSettings *settings;
3289 MoonWindow *window;
3290 GdkScreen *screen;
3291 GdkWindow *widget;
3292 Surface *surface;
3293 guint timeout;
3295 if (!(surface = view->GetSurface ()))
3296 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3298 if (!(window = surface->GetWindow ()))
3299 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3301 if (!(widget = window->GetGdkWindow ()))
3302 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3304 if (!(screen = gdk_drawable_get_screen ((GdkDrawable *) widget)))
3305 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3307 if (!(settings = gtk_settings_get_for_screen (screen)))
3308 return CURSOR_BLINK_TIMEOUT_DEFAULT;
3310 g_object_get (settings, "gtk-cursor-blink-time", &timeout, NULL);
3312 return timeout;
3315 void
3316 TextBoxView::ConnectBlinkTimeout (guint multiplier)
3318 guint timeout = GetCursorBlinkTimeout (this) * multiplier / CURSOR_BLINK_DIVIDER;
3319 Surface *surface = GetSurface ();
3320 TimeManager *manager;
3322 if (!surface || !(manager = surface->GetTimeManager ()))
3323 return;
3325 blink_timeout = manager->AddTimeout (MOON_PRIORITY_DEFAULT, timeout, TextBoxView::blink, this);
3328 void
3329 TextBoxView::DisconnectBlinkTimeout ()
3331 TimeManager *manager;
3332 Surface *surface;
3334 if (blink_timeout != 0) {
3335 if (!(surface = GetSurface ()) || !(manager = surface->GetTimeManager ()))
3336 return;
3338 manager->RemoveTimeout (blink_timeout);
3339 blink_timeout = 0;
3343 bool
3344 TextBoxView::Blink ()
3346 guint multiplier;
3348 SetCurrentDeployment (true);
3350 if (cursor_visible) {
3351 multiplier = CURSOR_BLINK_OFF_MULTIPLIER;
3352 HideCursor ();
3353 } else {
3354 multiplier = CURSOR_BLINK_ON_MULTIPLIER;
3355 ShowCursor ();
3358 ConnectBlinkTimeout (multiplier);
3360 return false;
3363 void
3364 TextBoxView::DelayCursorBlink ()
3366 DisconnectBlinkTimeout ();
3367 ConnectBlinkTimeout (CURSOR_BLINK_DELAY_MULTIPLIER);
3368 UpdateCursor (true);
3369 ShowCursor ();
3372 void
3373 TextBoxView::BeginCursorBlink ()
3375 if (blink_timeout == 0) {
3376 ConnectBlinkTimeout (CURSOR_BLINK_ON_MULTIPLIER);
3377 UpdateCursor (true);
3378 ShowCursor ();
3382 void
3383 TextBoxView::EndCursorBlink ()
3385 DisconnectBlinkTimeout ();
3387 if (cursor_visible)
3388 HideCursor ();
3391 void
3392 TextBoxView::ResetCursorBlink (bool delay)
3394 if (textbox->IsFocused () && !textbox->HasSelectedText ()) {
3395 // cursor is blinkable... proceed with blinkage
3396 if (delay)
3397 DelayCursorBlink ();
3398 else
3399 BeginCursorBlink ();
3400 } else {
3401 // cursor not blinkable... stop all blinkage
3402 EndCursorBlink ();
3406 void
3407 TextBoxView::InvalidateCursor ()
3409 Invalidate (cursor.Transform (&absolute_xform));
3412 void
3413 TextBoxView::ShowCursor ()
3415 cursor_visible = true;
3416 InvalidateCursor ();
3419 void
3420 TextBoxView::HideCursor ()
3422 cursor_visible = false;
3423 InvalidateCursor ();
3426 void
3427 TextBoxView::UpdateCursor (bool invalidate)
3429 int cur = textbox->GetCursor ();
3430 GdkRectangle area;
3431 Rect rect;
3433 // invalidate current cursor rect
3434 if (invalidate && cursor_visible)
3435 InvalidateCursor ();
3437 // calculate the new cursor rect
3438 cursor = layout->GetCursor (Point (), cur);
3440 // transform the cursor rect into absolute coordinates for the IM context
3441 rect = cursor.Transform (&absolute_xform);
3442 area = rect.ToGdkRectangle ();
3444 gtk_im_context_set_cursor_location (textbox->im_ctx, &area);
3446 textbox->EmitCursorPositionChanged (cursor.height, cursor.x, cursor.y);
3448 // invalidate the new cursor rect
3449 if (invalidate && cursor_visible)
3450 InvalidateCursor ();
3453 void
3454 TextBoxView::UpdateText ()
3456 const char *text = textbox->GetDisplayText ();
3458 layout->SetText (text ? text : "", -1);
3461 void
3462 TextBoxView::GetSizeForBrush (cairo_t *cr, double *width, double *height)
3464 *height = GetActualHeight ();
3465 *width = GetActualWidth ();
3468 Size
3469 TextBoxView::ComputeActualSize ()
3471 Layout (Size (INFINITY, INFINITY));
3473 Size actual (0,0);
3474 layout->GetActualExtents (&actual.width, &actual.height);
3476 return actual;
3479 Size
3480 TextBoxView::MeasureOverride (Size availableSize)
3482 Size desired = Size ();
3485 Layout (availableSize);
3487 layout->GetActualExtents (&desired.width, &desired.height);
3489 return desired.Min (availableSize);
3492 Size
3493 TextBoxView::ArrangeOverride (Size finalSize)
3495 Size arranged = Size ();
3498 Layout (finalSize);
3500 layout->GetActualExtents (&arranged.width, &arranged.height);
3502 arranged = arranged.Max (finalSize);
3504 return arranged;
3507 void
3508 TextBoxView::Layout (Size constraint)
3510 layout->SetMaxWidth (constraint.width);
3512 layout->Layout ();
3515 void
3516 TextBoxView::Paint (cairo_t *cr)
3518 layout->Render (cr, GetOriginPoint (), Point ());
3520 if (cursor_visible) {
3521 cairo_antialias_t alias = cairo_get_antialias (cr);
3522 Brush *caret = textbox->GetCaretBrush ();
3523 double h = round (cursor.height);
3524 double x = cursor.x;
3525 double y = cursor.y;
3527 // disable antialiasing
3528 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
3530 // snap 'x' to the half-pixel grid (to try and get a sharp 1-pixel-wide line)
3531 cairo_user_to_device (cr, &x, &y);
3532 x = trunc (x) + 0.5; y = trunc (y);
3533 cairo_device_to_user (cr, &x, &y);
3535 // set the cursor color
3536 caret->SetupBrush (cr, cursor);
3538 // draw the cursor
3539 cairo_set_line_width (cr, 1.0);
3540 cairo_move_to (cr, x, y);
3541 cairo_line_to (cr, x, y + h);
3543 // stroke the caret
3544 caret->Stroke (cr);
3546 // restore antialiasing
3547 cairo_set_antialias (cr, alias);
3551 void
3552 TextBoxView::Render (cairo_t *cr, Region *region, bool path_only)
3554 TextBoxDynamicPropertyValueProvider *dynamic = (TextBoxDynamicPropertyValueProvider *) textbox->providers[PropertyPrecedence_DynamicValue];
3555 Size renderSize = GetRenderSize ();
3557 dynamic->InitializeSelectionBrushes ();
3559 if (dirty) {
3560 Layout (renderSize);
3561 UpdateCursor (false);
3562 dirty = false;
3565 if (selection_changed) {
3566 layout->Select (textbox->GetSelectionStart (), textbox->GetSelectionLength ());
3567 selection_changed = false;
3570 cairo_save (cr);
3571 cairo_set_matrix (cr, &absolute_xform);
3573 if (!path_only)
3574 RenderLayoutClip (cr);
3576 layout->SetAvailableWidth (renderSize.width);
3577 Paint (cr);
3578 cairo_restore (cr);
3581 void
3582 TextBoxView::OnModelChanged (TextBoxModelChangedEventArgs *args)
3584 switch (args->changed) {
3585 case TextBoxModelChangedTextAlignment:
3586 // text alignment changed, update our layout
3587 if (layout->SetTextAlignment ((TextAlignment) args->property->GetNewValue()->AsInt32 ()))
3588 dirty = true;
3589 break;
3590 case TextBoxModelChangedTextWrapping:
3591 // text wrapping changed, update our layout
3592 if (layout->SetTextWrapping ((TextWrapping) args->property->GetNewValue()->AsInt32 ()))
3593 dirty = true;
3594 break;
3595 case TextBoxModelChangedSelection:
3596 if (had_selected_text || textbox->HasSelectedText ()) {
3597 // the selection has changed, update the layout's selection
3598 had_selected_text = textbox->HasSelectedText ();
3599 selection_changed = true;
3600 ResetCursorBlink (false);
3601 } else {
3602 // cursor position changed
3603 ResetCursorBlink (true);
3604 return;
3606 break;
3607 case TextBoxModelChangedBrush:
3608 // a brush has changed, no layout updates needed, we just need to re-render
3609 break;
3610 case TextBoxModelChangedFont:
3611 // font changed, need to recalculate layout/bounds
3612 layout->ResetState ();
3613 dirty = true;
3614 break;
3615 case TextBoxModelChangedText:
3616 // the text has changed, need to recalculate layout/bounds
3617 UpdateText ();
3618 dirty = true;
3619 break;
3620 default:
3621 // nothing changed??
3622 return;
3625 if (dirty) {
3626 InvalidateMeasure ();
3627 UpdateBounds (true);
3630 Invalidate ();
3633 void
3634 TextBoxView::model_changed (EventObject *sender, EventArgs *args, gpointer closure)
3636 ((TextBoxView *) closure)->OnModelChanged ((TextBoxModelChangedEventArgs *) args);
3639 void
3640 TextBoxView::OnFocusOut ()
3642 EndCursorBlink ();
3645 void
3646 TextBoxView::OnFocusIn ()
3648 ResetCursorBlink (false);
3651 void
3652 TextBoxView::OnMouseLeftButtonDown (MouseEventArgs *args)
3654 // proxy to our parent TextBox control
3655 textbox->OnMouseLeftButtonDown (args);
3658 void
3659 TextBoxView::mouse_left_button_down (EventObject *sender, EventArgs *args, gpointer closure)
3661 ((TextBoxView *) closure)->OnMouseLeftButtonDown ((MouseEventArgs *) args);
3664 void
3665 TextBoxView::OnMouseLeftButtonUp (MouseEventArgs *args)
3667 // proxy to our parent TextBox control
3668 textbox->OnMouseLeftButtonUp (args);
3671 void
3672 TextBoxView::mouse_left_button_up (EventObject *sender, EventArgs *args, gpointer closure)
3674 ((TextBoxView *) closure)->OnMouseLeftButtonUp ((MouseEventArgs *) args);
3677 void
3678 TextBoxView::SetTextBox (TextBoxBase *textbox)
3680 TextLayoutAttributes *attrs;
3682 if (this->textbox == textbox)
3683 return;
3685 if (this->textbox) {
3686 // remove the event handlers from the old textbox
3687 this->textbox->RemoveHandler (TextBoxBase::ModelChangedEvent, TextBoxView::model_changed, this);
3690 this->textbox = textbox;
3692 if (textbox) {
3693 textbox->AddHandler (TextBoxBase::ModelChangedEvent, TextBoxView::model_changed, this);
3695 // sync our state with the textbox
3696 layout->SetTextAttributes (new List ());
3697 attrs = new TextLayoutAttributes ((ITextAttributes *) textbox, 0);
3698 layout->GetTextAttributes ()->Append (attrs);
3700 layout->SetTextAlignment (textbox->GetTextAlignment ());
3701 layout->SetTextWrapping (textbox->GetTextWrapping ());
3702 had_selected_text = textbox->HasSelectedText ();
3703 selection_changed = true;
3704 UpdateText ();
3705 } else {
3706 layout->SetTextAttributes (NULL);
3707 layout->SetText (NULL, -1);
3710 UpdateBounds (true);
3711 InvalidateMeasure ();
3712 Invalidate ();
3713 dirty = true;