1 /*
2 ** consolebuffer.cpp
3 **
4 ** manages the text for the console
5 **
6 **---------------------------------------------------------------------------
7 ** Copyright 2014 Christoph Oelckers
8 ** All rights reserved.
9 **
10 ** Redistribution and use in source and binary forms, with or without
11 ** modification, are permitted provided that the following conditions
12 ** are met:
13 **
14 ** 1. Redistributions of source code must retain the above copyright
15 **    notice, this list of conditions and the following disclaimer.
16 ** 2. Redistributions in binary form must reproduce the above copyright
17 **    notice, this list of conditions and the following disclaimer in the
18 **    documentation and/or other materials provided with the distribution.
19 ** 3. The name of the author may not be used to endorse or promote products
20 **    derived from this software without specific prior written permission.
21 **
22 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 **---------------------------------------------------------------------------
33 **
34 */
35 
36 #include "c_console.h"
37 #include "c_consolebuffer.h"
38 
39 
40 //==========================================================================
41 //
42 //
43 //
44 //==========================================================================
45 
FConsoleBuffer()46 FConsoleBuffer::FConsoleBuffer()
47 {
48 	mLogFile = NULL;
49 	mAddType = NEWLINE;
50 	mLastFont = NULL;
51 	mLastDisplayWidth = -1;
52 	mLastLineNeedsUpdate = false;
53 	mTextLines = 0;
54 	mBufferWasCleared = true;
55 	mBrokenStart.Push(0);
56 }
57 
58 //==========================================================================
59 //
60 //
61 //
62 //==========================================================================
63 
~FConsoleBuffer()64 FConsoleBuffer::~FConsoleBuffer()
65 {
66 	FreeBrokenText();
67 }
68 
69 //==========================================================================
70 //
71 //
72 //
73 //==========================================================================
74 
FreeBrokenText(unsigned start,unsigned end)75 void FConsoleBuffer::FreeBrokenText(unsigned start, unsigned end)
76 {
77 	if (end > mBrokenConsoleText.Size()) end = mBrokenConsoleText.Size();
78 	for (unsigned i = start; i < end; i++)
79 	{
80 		if (mBrokenConsoleText[i] != NULL) V_FreeBrokenLines(mBrokenConsoleText[i]);
81 		mBrokenConsoleText[i] = NULL;
82 	}
83 }
84 
85 //==========================================================================
86 //
87 // Adds a new line of text to the console
88 // This is kept as simple as possible. This function does not:
89 // - remove old text if the buffer gets larger than the specified size
90 // - format the text for the current screen layout
91 //
92 // These tasks will only be be performed once per frame because they are
93 // relatively expensive. The old console did them each time text was added
94 // resulting in extremely bad performance with a high output rate.
95 //
96 //==========================================================================
97 
AddText(int printlevel,const char * text,FILE * logfile)98 void FConsoleBuffer::AddText(int printlevel, const char *text, FILE *logfile)
99 {
100 	FString build = TEXTCOLOR_TAN;
101 
102 	if (mAddType == REPLACELINE)
103 	{
104 		// Just wondering: Do we actually need this case? If so, it may need some work.
105 		mConsoleText.Pop();	// remove the line to be replaced
106 		mLastLineNeedsUpdate = true;
107 	}
108 	else if (mAddType == APPENDLINE)
109 	{
110 		mConsoleText.Pop(build);
111 		printlevel = -1;
112 		mLastLineNeedsUpdate = true;
113 	}
114 
115 	if (printlevel >= 0 && printlevel != PRINT_HIGH)
116 	{
117 		if (printlevel == 200) build = TEXTCOLOR_GREEN;
118 		else if (printlevel < PRINTLEVELS) build.Format("%c%c", TEXTCOLOR_ESCAPE, PrintColors[printlevel]);
119 	}
120 
121 	size_t textsize = strlen(text);
122 
123 	if (text[textsize-1] == '\r')
124 	{
125 		textsize--;
126 		mAddType = REPLACELINE;
127 	}
128 	else if (text[textsize-1] == '\n')
129 	{
130 		textsize--;
131 		mAddType = NEWLINE;
132 	}
133 	else
134 	{
135 		mAddType = APPENDLINE;
136 	}
137 
138 	// don't bother about linefeeds etc. inside the text, we'll let the formatter sort this out later.
139 	build.AppendCStrPart(text, textsize);
140 	mConsoleText.Push(build);
141 	if (logfile != NULL) WriteLineToLog(logfile, text);
142 }
143 
144 //==========================================================================
145 //
146 //
147 //
148 //==========================================================================
149 
WriteLineToLog(FILE * LogFile,const char * outline)150 void FConsoleBuffer::WriteLineToLog(FILE *LogFile, const char *outline)
151 {
152 	// Strip out any color escape sequences before writing to the log file
153 	char * copy = new char[strlen(outline)+1];
154 	const char * srcp = outline;
155 	char * dstp = copy;
156 
157 	while (*srcp != 0)
158 	{
159 
160 		if (*srcp != TEXTCOLOR_ESCAPE)
161 		{
162 			switch (*srcp)
163 			{
164 				case '\35':
165 					*dstp++ = '<';
166 					break;
167 
168 				case '\36':
169 					*dstp++ = '-';
170 					break;
171 
172 				case '\37':
173 					*dstp++ = '>';
174 					break;
175 
176 				default:
177 					*dstp++=*srcp;
178 					break;
179 			}
180 			srcp++;
181 		}
182 		else if (srcp[1] == '[')
183 		{
184 			srcp+=2;
185 			while (*srcp != ']' && *srcp != 0) srcp++;
186 			if (*srcp == ']') srcp++;
187 		}
188 		else
189 		{
190 			if (srcp[1]!=0) srcp+=2;
191 			else break;
192 		}
193 	}
194 	*dstp=0;
195 
196 	fputs (copy, LogFile);
197 	delete [] copy;
198 	fflush (LogFile);
199 }
200 
201 //==========================================================================
202 //
203 //
204 //
205 //==========================================================================
206 
WriteContentToLog(FILE * LogFile)207 void FConsoleBuffer::WriteContentToLog(FILE *LogFile)
208 {
209 	if (LogFile != NULL)
210 	{
211 		for (unsigned i = 0; i < mConsoleText.Size(); i++)
212 		{
213 			WriteLineToLog(LogFile, mConsoleText[i]);
214 		}
215 	}
216 }
217 
218 //==========================================================================
219 //
220 // ensures that the following text is not appended to the current line.
221 //
222 //==========================================================================
223 
Linefeed(FILE * Logfile)224 void FConsoleBuffer::Linefeed(FILE *Logfile)
225 {
226 	if (mAddType != NEWLINE && Logfile != NULL) fputc('\n', Logfile);
227 	mAddType = NEWLINE;
228 }
229 
230 //==========================================================================
231 //
232 //
233 //
234 //==========================================================================
235 
236 static const char bar1[] = TEXTCOLOR_RED "\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
237 						  "\36\36\36\36\36\36\36\36\36\36\36\36\37" TEXTCOLOR_TAN "\n";
238 static const char bar2[] = TEXTCOLOR_RED "\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
239 						  "\36\36\36\36\36\36\36\36\36\36\36\36\37" TEXTCOLOR_GREEN "\n";
240 static const char bar3[] = TEXTCOLOR_RED "\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
241 						  "\36\36\36\36\36\36\36\36\36\36\36\36\37" TEXTCOLOR_NORMAL "\n";
242 static const char logbar[] = "\n<------------------------------->\n";
243 
AddMidText(const char * string,bool bold,FILE * Logfile)244 void FConsoleBuffer::AddMidText(const char *string, bool bold, FILE *Logfile)
245 {
246 	Linefeed(Logfile);
247 	AddText (-1, bold? bar2 : bar1, Logfile);
248 	AddText (-1, string, Logfile);
249 	Linefeed(Logfile);
250 	AddText(-1, bar3, Logfile);
251 }
252 
253 //==========================================================================
254 //
255 // Format the text for output
256 //
257 //==========================================================================
258 
FormatText(FFont * formatfont,int displaywidth)259 void FConsoleBuffer::FormatText(FFont *formatfont, int displaywidth)
260 {
261 	if (formatfont != mLastFont || displaywidth != mLastDisplayWidth || mBufferWasCleared)
262 	{
263 		FreeBrokenText();
264 		mBrokenConsoleText.Clear();
265 		mBrokenStart.Clear();
266 		mBrokenStart.Push(0);
267 		mBrokenLines.Clear();
268 		mLastFont = formatfont;
269 		mLastDisplayWidth = displaywidth;
270 		mBufferWasCleared = false;
271 	}
272 	unsigned brokensize = mBrokenConsoleText.Size();
273 	if (brokensize == mConsoleText.Size())
274 	{
275 		// The last line got text appended. We have to wait until here to format it because
276 		// it is possible that during display new text will be added from the NetUpdate calls in the software version of DrawTextureV.
277 		if (mLastLineNeedsUpdate)
278 		{
279 			brokensize--;
280 			V_FreeBrokenLines(mBrokenConsoleText[brokensize]);
281 			mBrokenConsoleText.Resize(brokensize);
282 		}
283 	}
284 	mBrokenLines.Resize(mBrokenStart[brokensize]);
285 	mBrokenStart.Resize(brokensize);
286 	for (unsigned i = brokensize; i < mConsoleText.Size(); i++)
287 	{
288 		FBrokenLines *bl = V_BreakLines(formatfont, displaywidth, mConsoleText[i], true);
289 		mBrokenConsoleText.Push(bl);
290 		mBrokenStart.Push(mBrokenLines.Size());
291 		while (bl->Width != -1)
292 		{
293 			mBrokenLines.Push(bl);
294 			bl++;
295 		}
296 	}
297 	mTextLines = mBrokenLines.Size();
298 	mBrokenStart.Push(mTextLines);
299 	mLastLineNeedsUpdate = false;
300 }
301 
302 //==========================================================================
303 //
304 // Delete old content if number of lines gets too large
305 //
306 //==========================================================================
307 
ResizeBuffer(unsigned newsize)308 void FConsoleBuffer::ResizeBuffer(unsigned newsize)
309 {
310 	if (mConsoleText.Size() > newsize)
311 	{
312 		unsigned todelete = mConsoleText.Size() - newsize;
313 		mConsoleText.Delete(0, todelete);
314 		mBufferWasCleared = true;
315 	}
316 }
317 
318