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"
30 #include "Interface.h"
32 #include "Variables.h"
34 #include "Scriptable/Actor.h"
39 TextArea::TextArea(Color hitextcolor
, Color initcolor
, Color lowtextcolor
)
50 ResetEventHandler( TextAreaOnChange
);
51 ResetEventHandler( TextAreaOutOfText
);
53 palette
= core
->CreatePalette( hitextcolor
, lowtextcolor
);
54 initpalette
= core
->CreatePalette( initcolor
, lowtextcolor
);
56 hitextcolor
.b
, hitextcolor
.g
, hitextcolor
.r
, 0
58 selected
= core
->CreatePalette( tmp
, lowtextcolor
);
62 lineselpal
= core
->CreatePalette( tmp
, lowtextcolor
);
64 //Drop Capitals means initials on!
65 core
->GetDictionary()->Lookup("Drop Capitals", InternalFlags
);
67 InternalFlags
= TA_INITIALS
;
71 TextArea::~TextArea(void)
73 gamedata
->FreePalette( palette
);
74 gamedata
->FreePalette( initpalette
);
75 gamedata
->FreePalette( selected
);
76 gamedata
->FreePalette( lineselpal
);
77 core
->GetVideoDriver()->FreeSprite( Cursor
);
78 for (size_t i
= 0; i
< lines
.size(); i
++) {
83 void TextArea::RefreshSprite(const char *portrait
)
86 if (!strnicmp(PortraitResRef
, portrait
, 8) ) {
91 strnlwrcpy(PortraitResRef
, portrait
, 8);
92 if (!strnicmp(PortraitResRef
, "none", 8) ) {
95 ResourceHolder
<ImageMgr
> im(PortraitResRef
);
100 SetAnimPicture ( im
->GetSprite2D() );
103 void TextArea::Draw(unsigned short x
, unsigned short y
)
105 /** Don't come back recursively */
106 if (InternalFlags
&TA_BITEMYTAIL
) {
111 Region
clip( tx
, ty
, Width
, Height
);
112 Video
*video
= core
->GetVideoDriver();
114 if (Flags
&IE_GUI_TEXTAREA_SPEAKER
) {
116 video
->BlitSprite(AnimPicture
, tx
,ty
, true, &clip
);
117 clip
.x
+=AnimPicture
->Width
;
118 clip
.w
-=AnimPicture
->Width
;
122 //this might look better in GlobalTimer
123 //or you might want to change the animated button to work like this
124 if (Flags
&IE_GUI_TEXTAREA_SMOOTHSCROLL
)
126 unsigned long thisTime
;
129 if (thisTime
>starttime
) {
130 starttime
= thisTime
+ticks
;
133 smooth
+=ftext
->maxHeight
;
139 /** Forcing redraw of whole screen before drawing text*/
141 InternalFlags
|= TA_BITEMYTAIL
;
143 InternalFlags
&= ~TA_BITEMYTAIL
;
147 if (!Changed
&& !(Owner
->Flags
&WF_FLOAT
) ) {
155 size_t linesize
= lines
.size();
160 //smooth vertical scrolling up
161 if (Flags
& IE_GUI_TEXTAREA_SMOOTHSCROLL
) {
166 //if textarea is 'selectable' it actually means, it is a listbox
167 //in this case the selected value equals the line number
168 //if it is 'not selectable' it can still have selectable lines
169 //but then it is like the dialog window in the main game screen:
170 //the selected value is encoded into the line
171 if (!(Flags
& IE_GUI_TEXTAREA_SELECTABLE
) ) {
172 char* Buffer
= (char *) malloc( 1 );
176 for (size_t i
= 0; i
< linesize
; i
++) {
177 if (strnicmp( "[s=", lines
[i
], 3 ) == 0) {
179 unsigned long idx
, acolor
, bcolor
;
181 idx
= strtoul( lines
[i
] + 3, &rest
, 0 );
184 acolor
= strtoul( rest
+ 1, &rest
, 16 );
187 bcolor
= strtoul( rest
+ 1, &rest
, 16 );
190 tlen
= (int)(strstr( rest
+ 1, "[/s]" ) - rest
- 1);
194 Buffer
= (char *) realloc( Buffer
, len
+ 2 );
195 if (seltext
== (int) i
) {
196 sprintf( Buffer
+ lastlen
, "[color=%6.6lX]%.*s[/color]",
197 acolor
, tlen
, rest
+ 1 );
199 sprintf( Buffer
+ lastlen
, "[color=%6.6lX]%.*s[/color]",
200 bcolor
, tlen
, rest
+ 1 );
204 len
+= ( int ) strlen( lines
[i
] ) + 1;
205 Buffer
= (char *) realloc( Buffer
, len
+ 2 );
206 memcpy( &Buffer
[lastlen
], lines
[i
], len
- lastlen
);
209 if (i
!= linesize
- 1) {
210 Buffer
[lastlen
- 1] = '\n';
214 video
->SetClipRect( &clip
);
218 if (startrow
==CurLine
) {
223 ftext
->PrintFromLine( startrow
, clip
,
224 ( unsigned char * ) Buffer
, palette
,
225 IE_FONT_ALIGN_LEFT
, finit
, Cursor
, pos
);
227 video
->SetClipRect( NULL
);
230 //the buffer is filled enough
233 if (core
->GetAudioDrv()->IsSpeaking() ) {
234 //the narrator is still talking
237 if (RunEventHandler( TextAreaOutOfText
)) {
240 if (linesize
==lines
.size()) {
241 ResetEventHandler( TextAreaOutOfText
);
247 // normal scrolling textarea
252 for (i
= 0; i
< linesize
; i
++) {
253 if (rc
+ lrows
[i
] <= sr
) {
259 if (seltext
== (int) i
)
265 ftext
->PrintFromLine( sr
, clip
,
266 ( unsigned char * ) lines
[i
], pal
,
267 IE_FONT_ALIGN_LEFT
, finit
, NULL
);
268 yl
= ftext
->size
[1].h
*(lrows
[i
]-sr
);
273 for (i
++; i
< linesize
; i
++) {
275 if (seltext
== (int) i
)
281 ftext
->Print( clip
, ( unsigned char * ) lines
[i
], pal
,
282 IE_FONT_ALIGN_LEFT
, true );
283 yl
= ftext
->size
[1].h
*lrows
[i
];
289 /** Sets the Scroll Bar Pointer. If 'ptr' is NULL no Scroll Bar will be linked
290 to this Text Area Control. */
291 int TextArea::SetScrollBar(Control
* ptr
)
293 int ret
= Control::SetScrollBar(ptr
);
298 /** Sets the Actual Text */
299 int TextArea::SetText(const char* text
, int pos
)
307 if (lines
.size() == 0) {
311 if (pos
>= ( int ) lines
.size()) {
314 int newlen
= ( int ) strlen( text
);
317 char* str
= (char *) malloc( newlen
+ 1 );
318 memcpy( str
, text
, newlen
+ 1 );
319 lines
.push_back( str
);
320 lrows
.push_back( 0 );
322 lines
[pos
] = (char *) realloc( lines
[pos
], newlen
+ 1 );
323 memcpy( lines
[pos
], text
, newlen
+ 1 );
326 CurLine
= lines
.size()-1;
331 void TextArea::SetMinRow(bool enable
)
334 minrow
= (int) lines
.size();
341 //drop lines scrolled out at the top.
342 //keeplines is the number of lines that should still be
343 //preserved (for scrollback history)
344 void TextArea::DiscardLines()
346 if (rows
<=keeplines
) {
349 int drop
= rows
-keeplines
;
350 PopLines(drop
, true);
353 static const char inserted_crap
[]="[/color][color=ffffff]";
354 #define CRAPLENGTH sizeof(inserted_crap)-1
356 /** Appends a String to the current Text */
357 int TextArea::AppendText(const char* text
, int pos
)
360 if (pos
>= ( int ) lines
.size()) {
363 int newlen
= ( int ) strlen( text
);
366 const char *note
= strstr(text
,"\r\n\r\nNOTE:");
369 str
= (char *) malloc( newlen
+1 );
370 memcpy(str
,text
, newlen
+1);
373 unsigned int notepos
= (unsigned int) (note
- text
);
374 str
= (char *) malloc( newlen
+ CRAPLENGTH
+1 );
375 memcpy(str
,text
,notepos
);
376 memcpy(str
+notepos
,inserted_crap
,CRAPLENGTH
);
377 memcpy(str
+notepos
+CRAPLENGTH
, text
+notepos
, newlen
-notepos
+1);
379 lines
.push_back( str
);
380 lrows
.push_back( 0 );
381 ret
=(int) (lines
.size() - 1);
383 int mylen
= ( int ) strlen( lines
[pos
] );
385 lines
[pos
] = (char *) realloc( lines
[pos
], mylen
+ newlen
+ 1 );
386 memcpy( lines
[pos
]+mylen
, text
, newlen
+ 1 );
390 //if the textarea is not a listbox, then discard scrolled out
392 if (Flags
&IE_GUI_TEXTAREA_HISTORY
) {
400 /** Deletes last or first `count' lines */
401 /** Probably not too optimal for many lines, but it isn't used */
402 /** for many lines */
403 void TextArea::PopLines(unsigned int count
, bool top
)
405 if (count
> lines
.size()) {
406 count
= (unsigned int) lines
.size();
411 int tmp
= lrows
.front();
412 if (minrow
|| (startrow
<tmp
) )
415 free(lines
.front() );
416 lines
.erase(lines
.begin());
417 lrows
.erase(lrows
.begin());
428 void TextArea::UpdateControls()
435 ScrollBar
* bar
= ( ScrollBar
* ) sb
;
436 if (Flags
& IE_GUI_TEXTAREA_AUTOSCROLL
)
437 pos
= rows
- ( ( Height
- 5 ) / ftext
->maxHeight
);
444 if (Flags
& IE_GUI_TEXTAREA_AUTOSCROLL
) {
445 pos
= rows
- ( ( Height
- 5 ) / ftext
->maxHeight
);
452 /** Sets the Fonts */
453 void TextArea::SetFonts(Font
* init
, Font
* text
)
460 /** Key Press Event */
461 void TextArea::OnKeyPress(unsigned char Key
, unsigned short /*Mod*/)
463 if (Flags
& IE_GUI_TEXTAREA_EDITABLE
) {
467 int len
= GetRowLength(CurLine
);
468 //printf("len: %d Before: %s\n",len, lines[CurLine]);
469 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
+ 2 );
470 for (int i
= len
; i
> CurPos
; i
--) {
471 lines
[CurLine
][i
] = lines
[CurLine
][i
- 1];
473 lines
[CurLine
][CurPos
] = Key
;
474 lines
[CurLine
][len
+ 1] = 0;
476 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
478 RunEventHandler( TextAreaOnChange
);
483 //Selectable=false for dialogs, rather unintuitive, but fact
484 if ((Flags
& IE_GUI_TEXTAREA_SELECTABLE
) || ( Key
< '1' ) || ( Key
> '9' ))
486 GameControl
*gc
= core
->GetGameControl();
487 if (gc
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
) ) {
490 if ((unsigned int) seltext
>=lines
.size()) {
493 for(int i
=0;i
<Key
-'0';i
++) {
496 if ((unsigned int) seltext
>=lines
.size()) {
500 while (strnicmp( lines
[seltext
], "[s=", 3 ) != 0 );
503 sscanf( lines
[seltext
], "[s=%d,", &idx
);
505 //this kills this object, don't use any more data!
509 gc
->DialogChoose( idx
);
513 /** Special Key Press */
514 void TextArea::OnSpecialKeyPress(unsigned char Key
)
519 if (!(Flags
&IE_GUI_TEXTAREA_EDITABLE
)) {
535 if (CurLine
<lines
.size()) {
540 CurLine
=lines
.size()-1;
541 CurPos
= GetRowLength((unsigned int) CurLine
);
549 CurPos
= GetRowLength(CurLine
);
554 len
= GetRowLength(CurLine
);
558 if(CurLine
<lines
.size()) {
565 len
= GetRowLength(CurLine
);
566 //printf("len: %d Before: %s\n",len, lines[CurLine]);
568 //TODO: merge next line
571 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
);
572 for (i
= CurPos
; i
< len
; i
++) {
573 lines
[CurLine
][i
] = lines
[CurLine
][i
+ 1];
575 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
578 len
= GetRowLength(CurLine
);
580 //printf("len: %d Before: %s\n",len, lines[CurLine]);
584 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
);
585 for (i
= CurPos
; i
< len
; i
++) {
586 lines
[CurLine
][i
- 1] = lines
[CurLine
][i
];
588 lines
[CurLine
][len
- 1] = 0;
590 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
594 int oldline
= CurLine
;
596 int old
= GetRowLength(CurLine
);
597 //printf("len: %d Before: %s\n",old, lines[CurLine]);
598 //printf("len: %d Before: %s\n",len, lines[oldline]);
599 lines
[CurLine
] = (char *) realloc (lines
[CurLine
], len
+old
);
600 memcpy(lines
[CurLine
]+old
, lines
[oldline
],len
);
601 free(lines
[oldline
]);
602 lines
[CurLine
][old
+len
]=0;
603 lines
.erase(lines
.begin()+oldline
);
604 lrows
.erase(lrows
.begin()+oldline
);
606 //printf("pos: %d len: %d After: %s\n",CurPos, GetRowLength(CurLine), lines[CurLine]);
611 //add an empty line after CurLine
612 //printf("pos: %d Before: %s\n",CurPos, lines[CurLine]);
613 lrows
.insert(lrows
.begin()+CurLine
, 0);
614 len
= GetRowLength(CurLine
);
615 //copy the text after the cursor into the new line
616 char *str
= (char *) malloc(len
-CurPos
+2);
617 memcpy(str
, lines
[CurLine
]+CurPos
, len
-CurPos
+1);
618 str
[len
-CurPos
+1] = 0;
619 lines
.insert(lines
.begin()+CurLine
+1, str
);
620 //truncate the current line
621 lines
[CurLine
] = (char *) realloc (lines
[CurLine
], CurPos
+1);
622 lines
[CurLine
][CurPos
]=0;
623 //move cursor to next line beginning
626 //printf("len: %d After: %s\n",GetRowLength(CurLine-1), lines[CurLine-1]);
627 //printf("len: %d After: %s\n",GetRowLength(CurLine), lines[CurLine]);
631 RunEventHandler( TextAreaOnChange
);
634 /** Returns Row count */
635 int TextArea::GetRowCount()
637 return ( int ) lines
.size();
640 int TextArea::GetRowLength(unsigned int row
)
642 if (lines
.size()<=row
) {
645 //this is just roughly the line size, escape sequences need to be removed
646 return strlen( lines
[row
] );
649 int TextArea::GetVisibleRowCount()
651 return (Height
-5) / ftext
->maxHeight
;
654 /** Returns top index */
655 int TextArea::GetTopIndex()
660 /** Set Starting Row */
661 void TextArea::SetRow(int row
)
669 void TextArea::CalcRowCount()
674 if (Flags
&IE_GUI_TEXTAREA_SPEAKER
) {
675 const char *portrait
= NULL
;
677 GameControl
*gc
= core
->GetGameControl();
679 actor
= gc
->GetTarget();
682 portrait
= actor
->GetPortrait(1);
685 RefreshSprite(portrait
);
688 w
-=AnimPicture
->Width
;
693 if (lines
.size() != 0) {
694 for (size_t i
= 0; i
< lines
.size(); i
++) {
697 int len
= ( int ) strlen( lines
[i
] );
698 char* tmp
= (char *) malloc( len
+ 1 );
699 memcpy( tmp
, lines
[i
], len
+ 1 );
700 ftext
->SetupString( tmp
, w
);
701 for (int p
= 0; p
<= len
; p
++) {
702 if (( ( unsigned char ) tmp
[p
] ) == '[') {
706 for (k
= 0; k
< 256; k
++) {
731 if (CurLine
>=lines
.size()) {
732 CurLine
=lines
.size()-1;
734 w
= strlen(lines
[CurLine
]);
746 ScrollBar
* bar
= ( ScrollBar
* ) sb
;
747 tr
= rows
- Height
/ftext
->size
[1].h
+ 1;
751 bar
->SetMax( (ieWord
) tr
);
753 /** Mouse Over Event */
754 void TextArea::OnMouseOver(unsigned short /*x*/, unsigned short y
)
756 int height
= ftext
->maxHeight
; //size[1].h;
760 for (size_t i
= 0; i
< lines
.size(); i
++) {
762 if (r
< ( row
- startrow
)) {
763 if (seltext
!= (int) i
)
766 //printf("CtrlId = 0x%08lx, seltext = %d, rows = %d, row = %d, r = %d\n", ControlID, i, rows, row, r);
774 //printf("CtrlId = 0x%08lx, seltext = %d, rows %d, row %d, r = %d\n", ControlID, seltext, rows, row, r);
777 /** Mouse Button Up */
778 void TextArea::OnMouseUp(unsigned short x
, unsigned short y
, unsigned short /*Button*/,
779 unsigned short /*Mod*/)
781 if (( x
<= Width
) && ( y
<= ( Height
- 5 ) ) && ( seltext
!= -1 )) {
782 Value
= (unsigned int) seltext
;
784 if (strnicmp( lines
[seltext
], "[s=", 3 ) == 0) {
785 if (minrow
> seltext
)
788 sscanf( lines
[seltext
], "[s=%d,", &idx
);
789 GameControl
* gc
= core
->GetGameControl();
790 if (gc
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
) ) {
792 //this kills this object, don't use any more data!
796 gc
->DialogChoose( idx
);
802 if (VarName
[0] != 0) {
803 core
->GetDictionary()->SetAt( VarName
, Value
);
805 RunEventHandler( TextAreaOnChange
);
808 /** Copies the current TextArea content to another TextArea control */
809 void TextArea::CopyTo(TextArea
* ta
)
812 for (size_t i
= 0; i
< lines
.size(); i
++) {
813 ta
->SetText( lines
[i
], -1 );
817 void TextArea::RedrawTextArea(const char* VariableName
, unsigned int Sum
)
819 if (strnicmp( VarName
, VariableName
, MAX_VARIABLE_LENGTH
)) {
826 const char* TextArea::QueryText()
828 if ( Value
<lines
.size() ) {
829 return ( const char * ) lines
[Value
];
831 return ( const char *) "";
834 bool TextArea::SetEvent(int eventType
, EventHandler handler
)
839 case IE_GUI_TEXTAREA_ON_CHANGE
:
840 TextAreaOnChange
= handler
;
842 case IE_GUI_TEXTAREA_OUT_OF_TEXT
:
843 TextAreaOutOfText
= handler
;
852 void TextArea::PadMinRow()
855 int i
=(int) (lines
.size()-1);
857 //minrow -2 ->npc text
858 while (i
>=minrow
-2 && i
>=0) {
862 row
= GetVisibleRowCount()-row
;
869 void TextArea::SetPreservedRow(int arg
)
872 Flags
|= IE_GUI_TEXTAREA_HISTORY
;
875 void TextArea::Clear()
877 for (size_t i
= 0; i
< lines
.size(); i
++) {
885 //setting up the textarea for smooth scrolling, the first
886 //TEXTAREA_OUTOFTEXT callback is called automatically
887 void TextArea::SetupScroll(unsigned long tck
)
890 smooth
= ftext
->maxHeight
;
893 //clearing the textarea
895 unsigned int i
= (unsigned int) (Height
/smooth
);
897 char *str
= (char *) malloc(1);
899 lines
.push_back(str
);
902 i
= (unsigned int) lines
.size();
903 Flags
|= IE_GUI_TEXTAREA_SMOOTHSCROLL
;
904 GetTime( starttime
);
905 if (RunEventHandler( TextAreaOutOfText
)) {
906 //event handler destructed this object?
909 if (i
==lines
.size()) {
910 ResetEventHandler( TextAreaOutOfText
);
917 void TextArea::OnMouseDown(unsigned short /*x*/, unsigned short /*y*/, unsigned short Button
,
918 unsigned short /*Mod*/)
921 ScrollBar
* scrlbr
= (ScrollBar
*) sb
;
924 Control
*ctrl
= Owner
->GetScrollControl();
925 if (ctrl
&& (ctrl
->ControlType
== IE_GUI_SCROLLBAR
)) {
926 scrlbr
= (ScrollBar
*) ctrl
;
935 case GEM_MB_SCRLDOWN
:
936 scrlbr
->ScrollDown();