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.
27 #include "GameControl.h"
30 #include "Interface.h"
32 #include "Variables.h"
38 TextArea::TextArea(Color hitextcolor
, Color initcolor
, Color lowtextcolor
)
49 ResetEventHandler( TextAreaOnChange
);
50 ResetEventHandler( TextAreaOutOfText
);
52 palette
= core
->CreatePalette( hitextcolor
, lowtextcolor
);
53 initpalette
= core
->CreatePalette( initcolor
, lowtextcolor
);
55 hitextcolor
.b
, hitextcolor
.g
, hitextcolor
.r
, 0
57 selected
= core
->CreatePalette( tmp
, lowtextcolor
);
61 lineselpal
= core
->CreatePalette( tmp
, lowtextcolor
);
63 //Drop Capitals means initials on!
64 core
->GetDictionary()->Lookup("Drop Capitals", InternalFlags
);
66 InternalFlags
= TA_INITIALS
;
70 TextArea::~TextArea(void)
72 gamedata
->FreePalette( palette
);
73 gamedata
->FreePalette( initpalette
);
74 gamedata
->FreePalette( selected
);
75 gamedata
->FreePalette( lineselpal
);
76 core
->GetVideoDriver()->FreeSprite( Cursor
);
77 for (size_t i
= 0; i
< lines
.size(); i
++) {
82 void TextArea::RefreshSprite(const char *portrait
)
85 if (!strnicmp(PortraitResRef
, portrait
, 8) ) {
90 strnlwrcpy(PortraitResRef
, portrait
, 8);
91 if (!strnicmp(PortraitResRef
, "none", 8) ) {
94 ResourceHolder
<ImageMgr
> im(PortraitResRef
);
99 SetAnimPicture ( im
->GetSprite2D() );
102 void TextArea::Draw(unsigned short x
, unsigned short y
)
104 /** Don't come back recursively */
105 if (InternalFlags
&TA_BITEMYTAIL
) {
110 Region
clip( tx
, ty
, Width
, Height
);
111 Video
*video
= core
->GetVideoDriver();
113 if (Flags
&IE_GUI_TEXTAREA_SPEAKER
) {
115 video
->BlitSprite(AnimPicture
, tx
,ty
, true, &clip
);
116 clip
.x
+=AnimPicture
->Width
;
117 clip
.w
-=AnimPicture
->Width
;
121 //this might look better in GlobalTimer
122 //or you might want to change the animated button to work like this
123 if (Flags
&IE_GUI_TEXTAREA_SMOOTHSCROLL
)
125 unsigned long thisTime
;
128 if (thisTime
>starttime
) {
129 starttime
= thisTime
+ticks
;
132 smooth
+=ftext
->maxHeight
;
138 /** Forcing redraw of whole screen before drawing text*/
140 InternalFlags
|= TA_BITEMYTAIL
;
142 InternalFlags
&= ~TA_BITEMYTAIL
;
146 if (!Changed
&& !(Owner
->Flags
&WF_FLOAT
) ) {
154 size_t linesize
= lines
.size();
159 //smooth vertical scrolling up
160 if (Flags
& IE_GUI_TEXTAREA_SMOOTHSCROLL
) {
165 //if textarea is 'selectable' it actually means, it is a listbox
166 //in this case the selected value equals the line number
167 //if it is 'not selectable' it can still have selectable lines
168 //but then it is like the dialog window in the main game screen:
169 //the selected value is encoded into the line
170 if (!(Flags
& IE_GUI_TEXTAREA_SELECTABLE
) ) {
171 char* Buffer
= (char *) malloc( 1 );
175 for (size_t i
= 0; i
< linesize
; i
++) {
176 if (strnicmp( "[s=", lines
[i
], 3 ) == 0) {
178 unsigned long idx
, acolor
, bcolor
;
180 idx
= strtoul( lines
[i
] + 3, &rest
, 0 );
183 acolor
= strtoul( rest
+ 1, &rest
, 16 );
186 bcolor
= strtoul( rest
+ 1, &rest
, 16 );
189 tlen
= (int)(strstr( rest
+ 1, "[/s]" ) - rest
- 1);
193 Buffer
= (char *) realloc( Buffer
, len
+ 2 );
194 if (seltext
== (int) i
) {
195 sprintf( Buffer
+ lastlen
, "[color=%6.6lX]%.*s[/color]",
196 acolor
, tlen
, rest
+ 1 );
198 sprintf( Buffer
+ lastlen
, "[color=%6.6lX]%.*s[/color]",
199 bcolor
, tlen
, rest
+ 1 );
203 len
+= ( int ) strlen( lines
[i
] ) + 1;
204 Buffer
= (char *) realloc( Buffer
, len
+ 2 );
205 memcpy( &Buffer
[lastlen
], lines
[i
], len
- lastlen
);
208 if (i
!= linesize
- 1) {
209 Buffer
[lastlen
- 1] = '\n';
213 video
->SetClipRect( &clip
);
217 if (startrow
==CurLine
) {
222 ftext
->PrintFromLine( startrow
, clip
,
223 ( unsigned char * ) Buffer
, palette
,
224 IE_FONT_ALIGN_LEFT
, finit
, Cursor
, pos
);
226 video
->SetClipRect( NULL
);
229 //the buffer is filled enough
232 if (core
->GetAudioDrv()->IsSpeaking() ) {
233 //the narrator is still talking
236 if (RunEventHandler( TextAreaOutOfText
)) {
239 if (linesize
==lines
.size()) {
240 ResetEventHandler( TextAreaOutOfText
);
246 // normal scrolling textarea
251 for (i
= 0; i
< linesize
; i
++) {
252 if (rc
+ lrows
[i
] <= sr
) {
258 if (seltext
== (int) i
)
264 ftext
->PrintFromLine( sr
, clip
,
265 ( unsigned char * ) lines
[i
], pal
,
266 IE_FONT_ALIGN_LEFT
, finit
, NULL
);
267 yl
= ftext
->size
[1].h
*(lrows
[i
]-sr
);
272 for (i
++; i
< linesize
; i
++) {
274 if (seltext
== (int) i
)
280 ftext
->Print( clip
, ( unsigned char * ) lines
[i
], pal
,
281 IE_FONT_ALIGN_LEFT
, true );
282 yl
= ftext
->size
[1].h
*lrows
[i
];
288 /** Sets the Scroll Bar Pointer. If 'ptr' is NULL no Scroll Bar will be linked
289 to this Text Area Control. */
290 int TextArea::SetScrollBar(Control
* ptr
)
292 int ret
= Control::SetScrollBar(ptr
);
297 /** Sets the Actual Text */
298 int TextArea::SetText(const char* text
, int pos
)
306 if (lines
.size() == 0) {
310 if (pos
>= ( int ) lines
.size()) {
313 int newlen
= ( int ) strlen( text
);
316 char* str
= (char *) malloc( newlen
+ 1 );
317 memcpy( str
, text
, newlen
+ 1 );
318 lines
.push_back( str
);
319 lrows
.push_back( 0 );
321 lines
[pos
] = (char *) realloc( lines
[pos
], newlen
+ 1 );
322 memcpy( lines
[pos
], text
, newlen
+ 1 );
325 CurLine
= lines
.size()-1;
330 void TextArea::SetMinRow(bool enable
)
333 minrow
= (int) lines
.size();
340 //drop lines scrolled out at the top.
341 //keeplines is the number of lines that should still be
342 //preserved (for scrollback history)
343 void TextArea::DiscardLines()
345 if (rows
<=keeplines
) {
348 int drop
= rows
-keeplines
;
349 PopLines(drop
, true);
352 static const char inserted_crap
[]="[/color][color=ffffff]";
353 #define CRAPLENGTH sizeof(inserted_crap)-1
355 /** Appends a String to the current Text */
356 int TextArea::AppendText(const char* text
, int pos
)
359 if (pos
>= ( int ) lines
.size()) {
362 int newlen
= ( int ) strlen( text
);
365 const char *note
= strstr(text
,"\r\n\r\nNOTE:");
368 str
= (char *) malloc( newlen
+1 );
369 memcpy(str
,text
, newlen
+1);
372 unsigned int notepos
= (unsigned int) (note
- text
);
373 str
= (char *) malloc( newlen
+ CRAPLENGTH
+1 );
374 memcpy(str
,text
,notepos
);
375 memcpy(str
+notepos
,inserted_crap
,CRAPLENGTH
);
376 memcpy(str
+notepos
+CRAPLENGTH
, text
+notepos
, newlen
-notepos
+1);
378 lines
.push_back( str
);
379 lrows
.push_back( 0 );
380 ret
=(int) (lines
.size() - 1);
382 int mylen
= ( int ) strlen( lines
[pos
] );
384 lines
[pos
] = (char *) realloc( lines
[pos
], mylen
+ newlen
+ 1 );
385 memcpy( lines
[pos
]+mylen
, text
, newlen
+ 1 );
389 //if the textarea is not a listbox, then discard scrolled out
391 if (Flags
&IE_GUI_TEXTAREA_HISTORY
) {
399 /** Deletes last or first `count' lines */
400 /** Probably not too optimal for many lines, but it isn't used */
401 /** for many lines */
402 void TextArea::PopLines(unsigned int count
, bool top
)
404 if (count
> lines
.size()) {
405 count
= (unsigned int) lines
.size();
410 int tmp
= lrows
.front();
411 if (minrow
|| (startrow
<tmp
) )
414 free(lines
.front() );
415 lines
.erase(lines
.begin());
416 lrows
.erase(lrows
.begin());
427 void TextArea::UpdateControls()
434 ScrollBar
* bar
= ( ScrollBar
* ) sb
;
435 if (Flags
& IE_GUI_TEXTAREA_AUTOSCROLL
)
436 pos
= rows
- ( ( Height
- 5 ) / ftext
->maxHeight
);
443 if (Flags
& IE_GUI_TEXTAREA_AUTOSCROLL
) {
444 pos
= rows
- ( ( Height
- 5 ) / ftext
->maxHeight
);
451 /** Sets the Fonts */
452 void TextArea::SetFonts(Font
* init
, Font
* text
)
459 /** Key Press Event */
460 void TextArea::OnKeyPress(unsigned char Key
, unsigned short /*Mod*/)
462 if (Flags
& IE_GUI_TEXTAREA_EDITABLE
) {
466 int len
= GetRowLength(CurLine
);
467 //printf("len: %d Before: %s\n",len, lines[CurLine]);
468 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
+ 2 );
469 for (int i
= len
; i
> CurPos
; i
--) {
470 lines
[CurLine
][i
] = lines
[CurLine
][i
- 1];
472 lines
[CurLine
][CurPos
] = Key
;
473 lines
[CurLine
][len
+ 1] = 0;
475 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
477 RunEventHandler( TextAreaOnChange
);
482 //Selectable=false for dialogs, rather unintuitive, but fact
483 if ((Flags
& IE_GUI_TEXTAREA_SELECTABLE
) || ( Key
< '1' ) || ( Key
> '9' ))
485 GameControl
*gc
= core
->GetGameControl();
486 if (gc
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
) ) {
489 if ((unsigned int) seltext
>=lines
.size()) {
492 for(int i
=0;i
<Key
-'0';i
++) {
495 if ((unsigned int) seltext
>=lines
.size()) {
499 while (strnicmp( lines
[seltext
], "[s=", 3 ) != 0 );
502 sscanf( lines
[seltext
], "[s=%d,", &idx
);
504 //this kills this object, don't use any more data!
508 gc
->DialogChoose( idx
);
512 /** Special Key Press */
513 void TextArea::OnSpecialKeyPress(unsigned char Key
)
518 if (!(Flags
&IE_GUI_TEXTAREA_EDITABLE
)) {
534 if (CurLine
<lines
.size()) {
539 CurLine
=lines
.size()-1;
540 CurPos
= GetRowLength((unsigned int) CurLine
);
548 CurPos
= GetRowLength(CurLine
);
553 len
= GetRowLength(CurLine
);
557 if(CurLine
<lines
.size()) {
564 len
= GetRowLength(CurLine
);
565 //printf("len: %d Before: %s\n",len, lines[CurLine]);
567 //TODO: merge next line
570 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
);
571 for (i
= CurPos
; i
< len
; i
++) {
572 lines
[CurLine
][i
] = lines
[CurLine
][i
+ 1];
574 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
577 len
= GetRowLength(CurLine
);
579 //printf("len: %d Before: %s\n",len, lines[CurLine]);
583 lines
[CurLine
] = (char *) realloc( lines
[CurLine
], len
);
584 for (i
= CurPos
; i
< len
; i
++) {
585 lines
[CurLine
][i
- 1] = lines
[CurLine
][i
];
587 lines
[CurLine
][len
- 1] = 0;
589 //printf("pos: %d After: %s\n",CurPos, lines[CurLine]);
593 int oldline
= CurLine
;
595 int old
= GetRowLength(CurLine
);
596 //printf("len: %d Before: %s\n",old, lines[CurLine]);
597 //printf("len: %d Before: %s\n",len, lines[oldline]);
598 lines
[CurLine
] = (char *) realloc (lines
[CurLine
], len
+old
);
599 memcpy(lines
[CurLine
]+old
, lines
[oldline
],len
);
600 free(lines
[oldline
]);
601 lines
[CurLine
][old
+len
]=0;
602 lines
.erase(lines
.begin()+oldline
);
603 lrows
.erase(lrows
.begin()+oldline
);
605 //printf("pos: %d len: %d After: %s\n",CurPos, GetRowLength(CurLine), lines[CurLine]);
610 //add an empty line after CurLine
611 //printf("pos: %d Before: %s\n",CurPos, lines[CurLine]);
612 lrows
.insert(lrows
.begin()+CurLine
, 0);
613 len
= GetRowLength(CurLine
);
614 //copy the text after the cursor into the new line
615 char *str
= (char *) malloc(len
-CurPos
+2);
616 memcpy(str
, lines
[CurLine
]+CurPos
, len
-CurPos
+1);
617 str
[len
-CurPos
+1] = 0;
618 lines
.insert(lines
.begin()+CurLine
+1, str
);
619 //truncate the current line
620 lines
[CurLine
] = (char *) realloc (lines
[CurLine
], CurPos
+1);
621 lines
[CurLine
][CurPos
]=0;
622 //move cursor to next line beginning
625 //printf("len: %d After: %s\n",GetRowLength(CurLine-1), lines[CurLine-1]);
626 //printf("len: %d After: %s\n",GetRowLength(CurLine), lines[CurLine]);
630 RunEventHandler( TextAreaOnChange
);
633 /** Returns Row count */
634 int TextArea::GetRowCount()
636 return ( int ) lines
.size();
639 int TextArea::GetRowLength(unsigned int row
)
641 if (lines
.size()<=row
) {
644 //this is just roughly the line size, escape sequences need to be removed
645 return strlen( lines
[row
] );
648 int TextArea::GetVisibleRowCount()
650 return (Height
-5) / ftext
->maxHeight
;
653 /** Returns top index */
654 int TextArea::GetTopIndex()
659 /** Set Starting Row */
660 void TextArea::SetRow(int row
)
668 void TextArea::CalcRowCount()
673 if (Flags
&IE_GUI_TEXTAREA_SPEAKER
) {
674 const char *portrait
= NULL
;
676 GameControl
*gc
= core
->GetGameControl();
678 actor
= gc
->GetTarget();
681 portrait
= actor
->GetPortrait(1);
684 RefreshSprite(portrait
);
687 w
-=AnimPicture
->Width
;
692 if (lines
.size() != 0) {
693 for (size_t i
= 0; i
< lines
.size(); i
++) {
696 int len
= ( int ) strlen( lines
[i
] );
697 char* tmp
= (char *) malloc( len
+ 1 );
698 memcpy( tmp
, lines
[i
], len
+ 1 );
699 ftext
->SetupString( tmp
, w
);
700 for (int p
= 0; p
<= len
; p
++) {
701 if (( ( unsigned char ) tmp
[p
] ) == '[') {
705 for (k
= 0; k
< 256; k
++) {
730 if (CurLine
>=lines
.size()) {
731 CurLine
=lines
.size()-1;
733 w
= strlen(lines
[CurLine
]);
745 ScrollBar
* bar
= ( ScrollBar
* ) sb
;
746 tr
= rows
- Height
/ftext
->size
[1].h
+ 1;
750 bar
->SetMax( (ieWord
) tr
);
752 /** Mouse Over Event */
753 void TextArea::OnMouseOver(unsigned short /*x*/, unsigned short y
)
755 int height
= ftext
->maxHeight
; //size[1].h;
759 for (size_t i
= 0; i
< lines
.size(); i
++) {
761 if (r
< ( row
- startrow
)) {
762 if (seltext
!= (int) i
)
765 //printf("CtrlId = 0x%08lx, seltext = %d, rows = %d, row = %d, r = %d\n", ControlID, i, rows, row, r);
773 //printf("CtrlId = 0x%08lx, seltext = %d, rows %d, row %d, r = %d\n", ControlID, seltext, rows, row, r);
776 /** Mouse Button Up */
777 void TextArea::OnMouseUp(unsigned short x
, unsigned short y
, unsigned short /*Button*/,
778 unsigned short /*Mod*/)
780 if (( x
<= Width
) && ( y
<= ( Height
- 5 ) ) && ( seltext
!= -1 )) {
781 Value
= (unsigned int) seltext
;
783 if (strnicmp( lines
[seltext
], "[s=", 3 ) == 0) {
784 if (minrow
> seltext
)
787 sscanf( lines
[seltext
], "[s=%d,", &idx
);
788 GameControl
* gc
= core
->GetGameControl();
789 if (gc
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
) ) {
791 //this kills this object, don't use any more data!
795 gc
->DialogChoose( idx
);
801 if (VarName
[0] != 0) {
802 core
->GetDictionary()->SetAt( VarName
, Value
);
804 RunEventHandler( TextAreaOnChange
);
807 /** Copies the current TextArea content to another TextArea control */
808 void TextArea::CopyTo(TextArea
* ta
)
811 for (size_t i
= 0; i
< lines
.size(); i
++) {
812 ta
->SetText( lines
[i
], -1 );
816 void TextArea::RedrawTextArea(const char* VariableName
, unsigned int Sum
)
818 if (strnicmp( VarName
, VariableName
, MAX_VARIABLE_LENGTH
)) {
825 const char* TextArea::QueryText()
827 if ( Value
<lines
.size() ) {
828 return ( const char * ) lines
[Value
];
830 return ( const char *) "";
833 bool TextArea::SetEvent(int eventType
, const char *handler
)
838 case IE_GUI_TEXTAREA_ON_CHANGE
:
839 SetEventHandler( TextAreaOnChange
, handler
);
841 case IE_GUI_TEXTAREA_OUT_OF_TEXT
:
842 SetEventHandler( TextAreaOutOfText
, handler
);
851 void TextArea::PadMinRow()
854 int i
=(int) (lines
.size()-1);
856 //minrow -2 ->npc text
857 while (i
>=minrow
-2 && i
>=0) {
861 row
= GetVisibleRowCount()-row
;
868 void TextArea::SetPreservedRow(int arg
)
871 Flags
|= IE_GUI_TEXTAREA_HISTORY
;
874 void TextArea::Clear()
876 for (size_t i
= 0; i
< lines
.size(); i
++) {
884 //setting up the textarea for smooth scrolling, the first
885 //TEXTAREA_OUTOFTEXT callback is called automatically
886 void TextArea::SetupScroll(unsigned long tck
)
889 smooth
= ftext
->maxHeight
;
892 //clearing the textarea
894 unsigned int i
= (unsigned int) (Height
/smooth
);
896 char *str
= (char *) malloc(1);
898 lines
.push_back(str
);
901 i
= (unsigned int) lines
.size();
902 Flags
|= IE_GUI_TEXTAREA_SMOOTHSCROLL
;
903 GetTime( starttime
);
904 if (RunEventHandler( TextAreaOutOfText
)) {
905 //event handler destructed this object?
908 if (i
==lines
.size()) {
909 ResetEventHandler( TextAreaOutOfText
);
916 void TextArea::OnMouseDown(unsigned short /*x*/, unsigned short /*y*/, unsigned short Button
,
917 unsigned short /*Mod*/)
920 ScrollBar
* scrlbr
= (ScrollBar
*) sb
;
923 Control
*ctrl
= Owner
->GetScrollControl();
924 if (ctrl
&& (ctrl
->ControlType
== IE_GUI_SCROLLBAR
)) {
925 scrlbr
= (ScrollBar
*) ctrl
;
934 case GEM_MB_SCRLDOWN
:
935 scrlbr
->ScrollDown();