1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "GUI/TextArea.h"
23 #include "GUI/GameControl.h"
28 #include "DialogHandler.h"
31 #include "Interface.h"
33 #include "Variables.h"
35 #include "Scriptable/Actor.h"
40 TextArea::TextArea(Color hitextcolor
, Color initcolor
, Color lowtextcolor
)
51 ResetEventHandler( TextAreaOnChange
);
52 ResetEventHandler( TextAreaOutOfText
);
54 palette
= core
->CreatePalette( hitextcolor
, lowtextcolor
);
55 initpalette
= core
->CreatePalette( initcolor
, lowtextcolor
);
57 hitextcolor
.b
, hitextcolor
.g
, hitextcolor
.r
, 0
59 selected
= core
->CreatePalette( tmp
, lowtextcolor
);
63 lineselpal
= core
->CreatePalette( tmp
, lowtextcolor
);
65 //Drop Capitals means initials on!
66 core
->GetDictionary()->Lookup("Drop Capitals", InternalFlags
);
68 InternalFlags
= TA_INITIALS
;
72 TextArea::~TextArea(void)
74 gamedata
->FreePalette( palette
);
75 gamedata
->FreePalette( initpalette
);
76 gamedata
->FreePalette( selected
);
77 gamedata
->FreePalette( lineselpal
);
78 core
->GetVideoDriver()->FreeSprite( Cursor
);
79 for (size_t i
= 0; i
< lines
.size(); i
++) {
84 void TextArea::RefreshSprite(const char *portrait
)
87 if (!strnicmp(PortraitResRef
, portrait
, 8) ) {
92 strnlwrcpy(PortraitResRef
, portrait
, 8);
93 if (!strnicmp(PortraitResRef
, "none", 8) ) {
96 ResourceHolder
<ImageMgr
> im(PortraitResRef
);
101 SetAnimPicture ( im
->GetSprite2D() );
104 void TextArea::Draw(unsigned short x
, unsigned short y
)
106 /** Don't come back recursively */
107 if (InternalFlags
&TA_BITEMYTAIL
) {
112 Region
clip( tx
, ty
, Width
, Height
);
113 Video
*video
= core
->GetVideoDriver();
115 if (Flags
&IE_GUI_TEXTAREA_SPEAKER
) {
117 video
->BlitSprite(AnimPicture
, tx
,ty
, true, &clip
);
118 clip
.x
+=AnimPicture
->Width
;
119 clip
.w
-=AnimPicture
->Width
;
123 //this might look better in GlobalTimer
124 //or you might want to change the animated button to work like this
125 if (Flags
&IE_GUI_TEXTAREA_SMOOTHSCROLL
)
127 unsigned long thisTime
;
130 if (thisTime
>starttime
) {
131 starttime
= thisTime
+ticks
;
134 smooth
+=ftext
->maxHeight
;
140 /** Forcing redraw of whole screen before drawing text*/
142 InternalFlags
|= TA_BITEMYTAIL
;
144 InternalFlags
&= ~TA_BITEMYTAIL
;
148 if (!Changed
&& !(Owner
->Flags
&WF_FLOAT
) ) {
156 size_t linesize
= lines
.size();
161 //smooth vertical scrolling up
162 if (Flags
& IE_GUI_TEXTAREA_SMOOTHSCROLL
) {
167 //if textarea is 'selectable' it actually means, it is a listbox
168 //in this case the selected value equals the line number
169 //if it is 'not selectable' it can still have selectable lines
170 //but then it is like the dialog window in the main game screen:
171 //the selected value is encoded into the line
172 if (!(Flags
& IE_GUI_TEXTAREA_SELECTABLE
) ) {
173 char* Buffer
= (char *) malloc( 1 );
177 for (size_t i
= 0; i
< linesize
; i
++) {
178 if (strnicmp( "[s=", lines
[i
], 3 ) == 0) {
180 unsigned long idx
, acolor
, bcolor
;
182 idx
= strtoul( lines
[i
] + 3, &rest
, 0 );
185 acolor
= strtoul( rest
+ 1, &rest
, 16 );
188 bcolor
= strtoul( rest
+ 1, &rest
, 16 );
191 tlen
= (int)(strstr( rest
+ 1, "[/s]" ) - rest
- 1);
195 Buffer
= (char *) realloc( Buffer
, len
+ 2 );
196 if (seltext
== (int) i
) {
197 sprintf( Buffer
+ lastlen
, "[color=%6.6lX]%.*s[/color]",
198 acolor
, tlen
, rest
+ 1 );
200 sprintf( Buffer
+ lastlen
, "[color=%6.6lX]%.*s[/color]",
201 bcolor
, tlen
, rest
+ 1 );
205 len
+= ( int ) strlen( lines
[i
] ) + 1;
206 Buffer
= (char *) realloc( Buffer
, len
+ 2 );
207 memcpy( &Buffer
[lastlen
], lines
[i
], len
- lastlen
);
210 if (i
!= linesize
- 1) {
211 Buffer
[lastlen
- 1] = '\n';
215 video
->SetClipRect( &clip
);
219 if (startrow
==CurLine
) {
224 ftext
->PrintFromLine( startrow
, clip
,
225 ( unsigned char * ) Buffer
, palette
,
226 IE_FONT_ALIGN_LEFT
, finit
, Cursor
, pos
);
228 video
->SetClipRect( NULL
);
231 //the buffer is filled enough
234 if (core
->GetAudioDrv()->IsSpeaking() ) {
235 //the narrator is still talking
238 if (RunEventHandler( TextAreaOutOfText
)) {
241 if (linesize
==lines
.size()) {
242 ResetEventHandler( TextAreaOutOfText
);
248 // normal scrolling textarea
253 for (i
= 0; i
< linesize
; i
++) {
254 if (rc
+ lrows
[i
] <= sr
) {
260 if (seltext
== (int) i
)
266 ftext
->PrintFromLine( sr
, clip
,
267 ( unsigned char * ) lines
[i
], pal
,
268 IE_FONT_ALIGN_LEFT
, finit
, NULL
);
269 yl
= ftext
->size
[1].h
*(lrows
[i
]-sr
);
274 for (i
++; i
< linesize
; i
++) {
276 if (seltext
== (int) i
)
282 ftext
->Print( clip
, ( unsigned char * ) lines
[i
], pal
,
283 IE_FONT_ALIGN_LEFT
, true );
284 yl
= ftext
->size
[1].h
*lrows
[i
];
290 /** Sets the Scroll Bar Pointer. If 'ptr' is NULL no Scroll Bar will be linked
291 to this Text Area Control. */
292 int TextArea::SetScrollBar(Control
* ptr
)
294 int ret
= Control::SetScrollBar(ptr
);
299 /** Sets the Actual Text */
300 int TextArea::SetText(const char* text
, int pos
)
308 if (lines
.size() == 0) {
312 if (pos
>= ( int ) lines
.size()) {
315 int newlen
= ( int ) strlen( text
);
318 char* str
= (char *) malloc( newlen
+ 1 );
319 memcpy( str
, text
, newlen
+ 1 );
320 lines
.push_back( str
);
321 lrows
.push_back( 0 );
323 lines
[pos
] = (char *) realloc( lines
[pos
], newlen
+ 1 );
324 memcpy( lines
[pos
], text
, newlen
+ 1 );
327 CurLine
= lines
.size()-1;
332 void TextArea::SetMinRow(bool enable
)
335 minrow
= (int) lines
.size();
342 //drop lines scrolled out at the top.
343 //keeplines is the number of lines that should still be
344 //preserved (for scrollback history)
345 void TextArea::DiscardLines()
347 if (rows
<=keeplines
) {
350 int drop
= rows
-keeplines
;
351 PopLines(drop
, true);
354 static char *note_const
= NULL
;
355 static const char inserted_crap
[]="[/color][color=ffffff]";
356 #define CRAPLENGTH sizeof(inserted_crap)-1
358 void TextArea::SetNoteString(const char *s
)
362 note_const
= (char *) malloc(strlen(s
)+5);
363 sprintf(note_const
, "\r\n\r\n%s", s
);
367 /** Appends a String to the current Text */
368 int TextArea::AppendText(const char* text
, int pos
)
371 if (pos
>= ( int ) lines
.size()) {
374 int newlen
= ( int ) strlen( text
);
377 const char *note
= NULL
;
379 note
= strstr(text
,note_const
);
383 str
= (char *) malloc( newlen
+1 );
384 memcpy(str
, text
, newlen
+1);
387 unsigned int notepos
= (unsigned int) (note
- text
);
388 str
= (char *) malloc( newlen
+ CRAPLENGTH
+1 );
389 memcpy(str
,text
,notepos
);
390 memcpy(str
+notepos
,inserted_crap
,CRAPLENGTH
);
391 memcpy(str
+notepos
+CRAPLENGTH
, text
+notepos
, newlen
-notepos
+1);
393 lines
.push_back( str
);
394 lrows
.push_back( 0 );
395 ret
=(int) (lines
.size() - 1);
397 int mylen
= ( int ) strlen( lines
[pos
] );
399 lines
[pos
] = (char *) realloc( lines
[pos
], mylen
+ newlen
+ 1 );
400 memcpy( lines
[pos
]+mylen
, text
, newlen
+ 1 );
404 //if the textarea is not a listbox, then discard scrolled out
406 if (Flags
&IE_GUI_TEXTAREA_HISTORY
) {
414 /** Deletes last or first `count' lines */
415 /** Probably not too optimal for many lines, but it isn't used */
416 /** for many lines */
417 void TextArea::PopLines(unsigned int count
, bool top
)
419 if (count
> lines
.size()) {
420 count
= (unsigned int) lines
.size();
425 int tmp
= lrows
.front();
426 if (minrow
|| (startrow
<tmp
) )
429 free(lines
.front() );
430 lines
.erase(lines
.begin());
431 lrows
.erase(lrows
.begin());
442 void TextArea::UpdateControls()
449 ScrollBar
* bar
= ( ScrollBar
* ) sb
;
450 if (Flags
& IE_GUI_TEXTAREA_AUTOSCROLL
)
451 pos
= rows
- ( ( Height
- 5 ) / ftext
->maxHeight
);
458 if (Flags
& IE_GUI_TEXTAREA_AUTOSCROLL
) {
459 pos
= rows
- ( ( Height
- 5 ) / ftext
->maxHeight
);
466 /** Sets the Fonts */
467 void TextArea::SetFonts(Font
* init
, Font
* text
)
474 /** Key Press Event */
475 void TextArea::OnKeyPress(unsigned char Key
, unsigned short /*Mod*/)
477 if (Flags
& IE_GUI_TEXTAREA_EDITABLE
) {
481 int len
= GetRowLength(CurLine
);
482 //printf("len: %d Before: %s\n",len, lines[CurLine]);
483 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
+ 2 );
484 for (int i
= len
; i
> CurPos
; i
--) {
485 lines
[CurLine
][i
] = lines
[CurLine
][i
- 1];
487 lines
[CurLine
][CurPos
] = Key
;
488 lines
[CurLine
][len
+ 1] = 0;
490 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
492 RunEventHandler( TextAreaOnChange
);
497 //Selectable=false for dialogs, rather unintuitive, but fact
498 if ((Flags
& IE_GUI_TEXTAREA_SELECTABLE
) || ( Key
< '1' ) || ( Key
> '9' ))
500 GameControl
*gc
= core
->GetGameControl();
501 if (gc
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
) ) {
504 if ((unsigned int) seltext
>=lines
.size()) {
507 for(int i
=0;i
<Key
-'0';i
++) {
510 if ((unsigned int) seltext
>=lines
.size()) {
514 while (strnicmp( lines
[seltext
], "[s=", 3 ) != 0 );
517 sscanf( lines
[seltext
], "[s=%d,", &idx
);
519 //this kills this object, don't use any more data!
520 gc
->dialoghandler
->EndDialog();
523 gc
->dialoghandler
->DialogChoose( idx
);
527 /** Special Key Press */
528 void TextArea::OnSpecialKeyPress(unsigned char Key
)
533 if (!(Flags
&IE_GUI_TEXTAREA_EDITABLE
)) {
549 if (CurLine
<lines
.size()) {
554 CurLine
=lines
.size()-1;
555 CurPos
= GetRowLength((unsigned int) CurLine
);
563 CurPos
= GetRowLength(CurLine
);
568 len
= GetRowLength(CurLine
);
572 if(CurLine
<lines
.size()) {
579 len
= GetRowLength(CurLine
);
580 //printf("len: %d Before: %s\n",len, lines[CurLine]);
582 //TODO: merge next line
585 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
);
586 for (i
= CurPos
; i
< len
; i
++) {
587 lines
[CurLine
][i
] = lines
[CurLine
][i
+ 1];
589 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
592 len
= GetRowLength(CurLine
);
594 //printf("len: %d Before: %s\n",len, lines[CurLine]);
598 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
);
599 for (i
= CurPos
; i
< len
; i
++) {
600 lines
[CurLine
][i
- 1] = lines
[CurLine
][i
];
602 lines
[CurLine
][len
- 1] = 0;
604 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
608 int oldline
= CurLine
;
610 int old
= GetRowLength(CurLine
);
611 //printf("len: %d Before: %s\n",old, lines[CurLine]);
612 //printf("len: %d Before: %s\n",len, lines[oldline]);
613 lines
[CurLine
] = (char *) realloc (lines
[CurLine
], len
+old
);
614 memcpy(lines
[CurLine
]+old
, lines
[oldline
],len
);
615 free(lines
[oldline
]);
616 lines
[CurLine
][old
+len
]=0;
617 lines
.erase(lines
.begin()+oldline
);
618 lrows
.erase(lrows
.begin()+oldline
);
620 //printf("pos: %d len: %d After: %s\n",CurPos, GetRowLength(CurLine), lines[CurLine]);
625 //add an empty line after CurLine
626 //printf("pos: %d Before: %s\n",CurPos, lines[CurLine]);
627 lrows
.insert(lrows
.begin()+CurLine
, 0);
628 len
= GetRowLength(CurLine
);
629 //copy the text after the cursor into the new line
630 char *str
= (char *) malloc(len
-CurPos
+2);
631 memcpy(str
, lines
[CurLine
]+CurPos
, len
-CurPos
+1);
632 str
[len
-CurPos
+1] = 0;
633 lines
.insert(lines
.begin()+CurLine
+1, str
);
634 //truncate the current line
635 lines
[CurLine
] = (char *) realloc (lines
[CurLine
], CurPos
+1);
636 lines
[CurLine
][CurPos
]=0;
637 //move cursor to next line beginning
640 //printf("len: %d After: %s\n",GetRowLength(CurLine-1), lines[CurLine-1]);
641 //printf("len: %d After: %s\n",GetRowLength(CurLine), lines[CurLine]);
645 RunEventHandler( TextAreaOnChange
);
648 /** Returns Row count */
649 int TextArea::GetRowCount()
651 return ( int ) lines
.size();
654 int TextArea::GetRowLength(unsigned int row
)
656 if (lines
.size()<=row
) {
659 //this is just roughly the line size, escape sequences need to be removed
660 return strlen( lines
[row
] );
663 int TextArea::GetVisibleRowCount()
665 return (Height
-5) / ftext
->maxHeight
;
668 /** Returns top index */
669 int TextArea::GetTopIndex()
674 /** Set Starting Row */
675 void TextArea::SetRow(int row
)
683 void TextArea::CalcRowCount()
688 if (Flags
&IE_GUI_TEXTAREA_SPEAKER
) {
689 const char *portrait
= NULL
;
691 GameControl
*gc
= core
->GetGameControl();
693 Scriptable
*target
= gc
->dialoghandler
->GetTarget();
694 if (target
&& target
->Type
== ST_ACTOR
) {
695 actor
= (Actor
*)target
;
699 portrait
= actor
->GetPortrait(1);
702 RefreshSprite(portrait
);
705 w
-=AnimPicture
->Width
;
710 if (lines
.size() != 0) {
711 for (size_t i
= 0; i
< lines
.size(); i
++) {
714 int len
= ( int ) strlen( lines
[i
] );
715 char* tmp
= (char *) malloc( len
+ 1 );
716 memcpy( tmp
, lines
[i
], len
+ 1 );
717 ftext
->SetupString( tmp
, w
);
718 for (int p
= 0; p
<= len
; p
++) {
719 if (( ( unsigned char ) tmp
[p
] ) == '[') {
723 for (k
= 0; k
< 256; k
++) {
748 if (CurLine
>=lines
.size()) {
749 CurLine
=lines
.size()-1;
751 w
= strlen(lines
[CurLine
]);
763 ScrollBar
* bar
= ( ScrollBar
* ) sb
;
764 tr
= rows
- Height
/ftext
->size
[1].h
+ 1;
768 bar
->SetMax( (ieWord
) tr
);
770 /** Mouse Over Event */
771 void TextArea::OnMouseOver(unsigned short /*x*/, unsigned short y
)
773 int height
= ftext
->maxHeight
; //size[1].h;
777 for (size_t i
= 0; i
< lines
.size(); i
++) {
779 if (r
< ( row
- startrow
)) {
780 if (seltext
!= (int) i
)
783 //printf("CtrlId = 0x%08lx, seltext = %d, rows = %d, row = %d, r = %d\n", ControlID, i, rows, row, r);
791 //printf("CtrlId = 0x%08lx, seltext = %d, rows %d, row %d, r = %d\n", ControlID, seltext, rows, row, r);
794 /** Mouse Button Up */
795 void TextArea::OnMouseUp(unsigned short x
, unsigned short y
, unsigned short /*Button*/,
796 unsigned short /*Mod*/)
798 if (( x
<= Width
) && ( y
<= ( Height
- 5 ) ) && ( seltext
!= -1 )) {
799 Value
= (unsigned int) seltext
;
801 if (strnicmp( lines
[seltext
], "[s=", 3 ) == 0) {
802 if (minrow
> seltext
)
805 sscanf( lines
[seltext
], "[s=%d,", &idx
);
806 GameControl
* gc
= core
->GetGameControl();
807 if (gc
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
) ) {
809 //this kills this object, don't use any more data!
810 gc
->dialoghandler
->EndDialog();
813 gc
->dialoghandler
->DialogChoose( idx
);
819 if (VarName
[0] != 0) {
820 core
->GetDictionary()->SetAt( VarName
, Value
);
822 RunEventHandler( TextAreaOnChange
);
825 /** Copies the current TextArea content to another TextArea control */
826 void TextArea::CopyTo(TextArea
* ta
)
829 for (size_t i
= 0; i
< lines
.size(); i
++) {
830 ta
->SetText( lines
[i
], -1 );
834 void TextArea::RedrawTextArea(const char* VariableName
, unsigned int Sum
)
836 if (strnicmp( VarName
, VariableName
, MAX_VARIABLE_LENGTH
)) {
843 const char* TextArea::QueryText()
845 if ( Value
<lines
.size() ) {
846 return ( const char * ) lines
[Value
];
848 return ( const char *) "";
851 bool TextArea::SetEvent(int eventType
, EventHandler handler
)
856 case IE_GUI_TEXTAREA_ON_CHANGE
:
857 TextAreaOnChange
= handler
;
859 case IE_GUI_TEXTAREA_OUT_OF_TEXT
:
860 TextAreaOutOfText
= handler
;
869 void TextArea::PadMinRow()
872 int i
=(int) (lines
.size()-1);
874 //minrow -2 ->npc text
875 while (i
>=minrow
-2 && i
>=0) {
879 row
= GetVisibleRowCount()-row
;
886 void TextArea::SetPreservedRow(int arg
)
889 Flags
|= IE_GUI_TEXTAREA_HISTORY
;
892 void TextArea::Clear()
894 for (size_t i
= 0; i
< lines
.size(); i
++) {
902 //setting up the textarea for smooth scrolling, the first
903 //TEXTAREA_OUTOFTEXT callback is called automatically
904 void TextArea::SetupScroll(unsigned long tck
)
907 smooth
= ftext
->maxHeight
;
910 //clearing the textarea
912 unsigned int i
= (unsigned int) (Height
/smooth
);
914 char *str
= (char *) malloc(1);
916 lines
.push_back(str
);
919 i
= (unsigned int) lines
.size();
920 Flags
|= IE_GUI_TEXTAREA_SMOOTHSCROLL
;
921 GetTime( starttime
);
922 if (RunEventHandler( TextAreaOutOfText
)) {
923 //event handler destructed this object?
926 if (i
==lines
.size()) {
927 ResetEventHandler( TextAreaOutOfText
);
934 void TextArea::OnMouseDown(unsigned short /*x*/, unsigned short /*y*/, unsigned short Button
,
935 unsigned short /*Mod*/)
938 ScrollBar
* scrlbr
= (ScrollBar
*) sb
;
941 Control
*ctrl
= Owner
->GetScrollControl();
942 if (ctrl
&& (ctrl
->ControlType
== IE_GUI_SCROLLBAR
)) {
943 scrlbr
= (ScrollBar
*) ctrl
;
952 case GEM_MB_SCRLDOWN
:
953 scrlbr
->ScrollDown();