1 ////////////////////////////////////////////////////////////////////////////////
2 //    Scorched3D (c) 2000-2011
3 //
4 //    This file is part of Scorched3D.
5 //
6 //    Scorched3D is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    Scorched3D is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License along
17 //    with this program; if not, write to the Free Software Foundation, Inc.,
18 //    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 ////////////////////////////////////////////////////////////////////////////////
20 
21 #include <GLW/GLWListView.h>
22 #include <GLW/GLWFont.h>
23 #include <GLW/GLWTranslate.h>
24 #include <common/Defines.h>
25 #include <client/ScorchedClient.h>
26 #include <engine/GameState.h>
27 #include <stdio.h>
28 
29 static const float BorderWidth = 20.0f;
30 
31 REGISTER_CLASS_SOURCE(GLWListView);
32 
33 unsigned GLWListView::WordEntry::wordRefCount_ = 1;
34 
GLWListView(float x,float y,float w,float h,int maxLen,float textSize,float scrollSpeed)35 GLWListView::GLWListView(float x, float y, float w, float h,
36 	int maxLen, float textSize, float scrollSpeed) :
37 	GLWidget(x, y, w, h),
38 	scroll_(x + w - 17, y, h - 1, 0, 1),
39 	maxLen_(maxLen), textSize_(textSize),
40 	scrollSpeed_(scrollSpeed),
41 	handler_(0), currentPosition_(0.0f)
42 {
43 	color_ = GLWFont::widgetFontColor;
44 	scroll_.setMax((int) lines_.size());
45 	scroll_.setSee((int) (h_ / 12.0f));
46 	scroll_.setCurrent(scroll_.getMax());
47 }
48 
~GLWListView()49 GLWListView::~GLWListView()
50 {
51 }
52 
draw()53 void GLWListView::draw()
54 {
55 	glBegin(GL_LINE_LOOP);
56 		drawShadedRoundBox(x_, y_, w_, h_, 6.0f, false);
57 	glEnd();
58 
59 	{
60 		// Stops each font draw from changing state every time
61 		GLState currentState(GLState::TEXTURE_ON | GLState::BLEND_ON);
62 		GLFont2d *font = GLWFont::instance()->getGameFont();
63 
64 		// Clear the currently stored urls
65 		urls_.clear();
66 
67 		// Calculate current position
68 		float posY = y_ + h_ - (textSize_ + 4.0f);
69 		int pos = (scroll_.getMax() - scroll_.getSee()) - scroll_.getCurrent();
70 
71 		// Figure out how many characters we are not drawing because
72 		// they are off the top of the scrolling window
73 		int charCount = 0;
74 		for (int i=0; i<pos; i++)
75 		{
76 			LineEntry &lineEntry = lines_[i];
77 
78 			for (int j=0; j<(int) lineEntry.words_.size(); j++)
79 			{
80 				WordEntry &wordEntry = lineEntry.words_[j];
81 				charCount += (int) wordEntry.word_.size();
82 			}
83 		}
84 
85 		// For each line
86 		bool newWords = false;
87 		for (int i=pos; i<(int) scroll_.getMax(); i++)
88 		{
89 			// Check the line is valid
90 			if (i >= 0 && i < (int) lines_.size())
91 			{
92 				float width = w_ - BorderWidth;
93 				float widthUsed = 0.0f;
94 
95 				// For each word
96 				LineEntry &lineEntry = lines_[i];
97 				for (int j=0; j<(int) lineEntry.words_.size(); j++)
98 				{
99 					WordEntry &wordEntry = lineEntry.words_[j];
100 
101 					// Check if there is space left to draw this word
102 					int possibleChars = font->getChars(textSize_, wordEntry.word_.c_str(), width - widthUsed);
103 					int drawChars = possibleChars;
104 					if (scrollSpeed_ > 0.0f)
105 					{
106 						if (possibleChars + charCount > (int) currentPosition_)
107 						{
108 							drawChars = int(currentPosition_) - charCount;
109 						}
110 					}
111 
112 					if (drawChars > 0)
113 					{
114 						// Draw this word
115 						font->drawSubStr(0, drawChars,
116 							wordEntry.color_,
117 							textSize_,
118 							x_ + 5.0f + widthUsed, posY, 0.0f,
119 							S3D::formatStringBuffer("%s", wordEntry.word_.c_str()));
120 
121 						// Send the event (if any)
122 						if (wordEntry.wordRef_ != wordEntry.wordRefCount_)
123 						{
124 							newWords = true;
125 							wordEntry.wordRef_ = wordEntry.wordRefCount_;
126 							if (!wordEntry.event_.empty() && handler_)
127 							{
128 								handler_->event(wordEntry.event_);
129 							}
130 						}
131 
132 						// Draw the url (if any)
133 						if (wordEntry.href_.size() > 0)
134 						{
135 							drawUrl(wordEntry, drawChars, widthUsed, posY);
136 						}
137 					}
138 
139 					// Add this word to the space used
140 					float wordWidth = font->getWidth(
141 						textSize_, wordEntry.word_.c_str());
142 					widthUsed += wordWidth;
143 					charCount += (int) wordEntry.word_.size();
144 				}
145 
146 				// Move down a line
147 				posY -= (textSize_ + 2.0f);
148 			}
149 
150 			// Check if we should scroll down
151 			if (posY < y_)
152 			{
153 				if (scrollSpeed_ > 0.0f && newWords)
154 				{
155 					scroll_.setCurrent(scroll_.getCurrent() - 1);
156 				}
157 				break;
158 			}
159 
160 			// Check if we have drawn more than we should see
161 			if ((scrollSpeed_ > 0.0f) && (charCount > int(currentPosition_))) break;
162 		}
163 	}
164 
165 	scroll_.draw();
166 }
167 
simulate(float frameTime)168 void GLWListView::simulate(float frameTime)
169 {
170 	currentPosition_ += (frameTime * scrollSpeed_);
171 	scroll_.simulate(frameTime);
172 }
173 
mouseDown(int button,float x,float y,bool & skipRest)174 void GLWListView::mouseDown(int button, float x, float y, bool &skipRest)
175 {
176 	std::vector<UrlEntry>::iterator itor;
177 	for (itor = urls_.begin();
178 		itor != urls_.end();
179 		++itor)
180 	{
181 		UrlEntry &entry = *itor;
182 		if (inBox(x, y, entry.x_, entry.y_, entry.w_, entry.h_))
183 		{
184 			if (handler_)
185 			{
186 				const char *url = entry.entry_->href_.c_str();
187 				handler_->url(url);
188 			}
189 			skipRest = true;
190 			return;
191 		}
192 	}
193 
194 	scroll_.mouseDown(button, x, y, skipRest);
195 }
196 
mouseUp(int button,float x,float y,bool & skipRest)197 void GLWListView::mouseUp(int button, float x, float y, bool &skipRest)
198 {
199 	scroll_.mouseUp(button, x, y, skipRest);
200 }
201 
mouseDrag(int button,float mx,float my,float x,float y,bool & skipRest)202 void GLWListView::mouseDrag(int button, float mx, float my, float x, float y, bool &skipRest)
203 {
204 	scroll_.mouseDrag(button, mx, my, x, y, skipRest);
205 }
206 
mouseWheel(float x,float y,float z,bool & skipRest)207 void GLWListView::mouseWheel(float x, float y, float z, bool &skipRest)
208 {
209 	if (inBox(x, y, x_, y_, w_, h_))
210 	{
211 		skipRest = true;
212 
213 		if (z < 0.0f) scroll_.setCurrent(scroll_.getCurrent() + 1);
214 		else scroll_.setCurrent(scroll_.getCurrent() - 1);
215 	}
216 }
217 
clear()218 void GLWListView::clear()
219 {
220 	lines_.clear();
221 	scroll_.setCurrent(0);
222 	resetPosition();
223 }
224 
endPosition()225 void GLWListView::endPosition()
226 {
227 	scroll_.setCurrent(scroll_.getMin());
228 }
229 
resetPosition()230 void GLWListView::resetPosition()
231 {
232 	GLWListView::WordEntry::wordRefCount_++;
233 	currentPosition_ = 0.0f;
234 }
235 
addWordEntry(std::list<WordEntry> & words,std::string & word,XMLNode * parentNode)236 bool GLWListView::addWordEntry(std::list<WordEntry> &words,
237 	std::string &word, XMLNode *parentNode)
238 {
239 	WordEntry wordEntry(word.c_str(), color_);
240 	word = "";
241 
242 	if (0 == strcmp("event", parentNode->getName()))
243 	{
244 		wordEntry.color_ = Vector(0.4f, 0.0f, 0.0f);
245 
246 		std::list<XMLNode *> &parameters = parentNode->getParameters();
247 		std::list<XMLNode *>::iterator itor;
248 		for (itor = parameters.begin();
249 			itor != parameters.end();
250 			++itor)
251 		{
252 			XMLNode *node = *itor;
253 			wordEntry.event_[node->getName()] = node->getContent();
254 		}
255 	}
256 	else if (0 == strcmp("a", parentNode->getName()))
257 	{
258 		wordEntry.color_ = Vector(0.4f, 0.0f, 0.0f);
259 		if (!parentNode->getNamedParameter("href", wordEntry.href_, true, false))
260 		{
261 			return false;
262 		}
263 	}
264 
265 	words.push_back(wordEntry);
266 	return true;
267 }
268 
getLines(std::list<WordEntry> & words,float & lineLen)269 bool GLWListView::getLines(std::list<WordEntry> &words, float &lineLen)
270 {
271 	std::list<WordEntry>::iterator itor;
272 	for (itor = words.begin();
273 		itor != words.end();
274 		++itor)
275 	{
276 		WordEntry &wordEntry = *itor;
277 
278 		if (wordEntry.word_.c_str()[0] == '\n')
279 		{
280 			// Add a new line
281 			LineEntry lineEntry;
282 			lines_.push_back(lineEntry);
283 			lineLen = 0.0f;
284 		}
285 		else
286 		{
287 			// Check if weve run out of space on the current line
288 			float wordLen =
289 				GLWFont::instance()->getGameFont()->
290 				getWidth(textSize_, wordEntry.word_.c_str());
291 			if (wordLen + lineLen >= w_ - BorderWidth)
292 			{
293 				// Add a new line
294 				LineEntry lineEntry;
295 				lines_.push_back(lineEntry);
296 				lineLen = 0.0f;
297 			}
298 
299 			lines_.back().words_.push_back(wordEntry);
300 			lineLen += wordLen;
301 		}
302 	}
303 
304 	return true;
305 }
306 
getWords(XMLNode * node,std::list<WordEntry> & words)307 bool GLWListView::getWords(XMLNode *node, std::list<WordEntry> &words)
308 {
309 	// For each child XML node
310 	std::list<XMLNode *>::iterator childrenItor;
311 	std::list<XMLNode *> children = node->getChildren();
312 	for (childrenItor = children.begin();
313 		childrenItor != children.end();
314 		++childrenItor)
315 	{
316 		XMLNode *child = (*childrenItor);
317 
318 		// Get the child node type
319 		if (child->getType() == XMLNode::XMLNodeType)
320 		{
321 			// Its another node, recurse over its children too
322 			if (!getWords(child, words)) return false;
323 		}
324 		else
325 		{
326 			// Its a text type, add the words from the text
327 			std::string word;
328 			for (const char *t=child->getContent(); *t; t++)
329 			{
330 				if (*t == '\n')
331 				{
332 					// Add the current word
333 					if (!addWordEntry(words, word, node)) return false;
334 
335 					word = "\n";
336 					if (!addWordEntry(words, word, node)) return false;
337 				}
338 				else
339 				{
340 					word += *t;
341 
342 					// A word break
343 					if (*t == ' ')
344 					{
345 						// Add a new word
346 						if (!addWordEntry(words, word, node)) return false;
347 					}
348 				}
349 			}
350 
351 			// Add any words we've got left over
352 			if (!addWordEntry(words, word, node)) return false;
353 		}
354 	}
355 
356 	return true;
357 }
358 
addXML(XMLNode * node)359 bool GLWListView::addXML(XMLNode *node)
360 {
361 	// Recurse over the document adding the words
362 	std::list<WordEntry> words;
363 	if (!getWords(node, words)) return false;
364 
365 	// Add a blank line to start with
366 	LineEntry lineEntry;
367 	lines_.push_back(lineEntry);
368 	float lineLen = 0.0f;
369 	getLines(words, lineLen);
370 
371 	// Setup the current scroll position
372 	setScroll();
373 
374 	return true;
375 }
376 
addLine(const std::string & text)377 void GLWListView::addLine(const std::string &text)
378 {
379 	// Remove extra lines
380 	if (maxLen_ > 0)
381 	{
382 		if (lines_.size() > (unsigned int) maxLen_) lines_.clear();
383 	}
384 
385 	// Generate the line to add (add a single word)
386 	WordEntry wordEntry(text.c_str(), color_);
387 	LineEntry lineEntry;
388 	lineEntry.words_.push_back(wordEntry);
389 	lines_.push_back(lineEntry);
390 
391 	// Setup the current scroll position
392 	setScroll();
393 }
394 
setScroll()395 void GLWListView::setScroll()
396 {
397 	int view = (int) (h_ / (textSize_ + 2.0f));
398 	scroll_.setMax((int) lines_.size());
399 	scroll_.setSee(view);
400 	scroll_.setCurrent(scroll_.getMax() - scroll_.getSee());
401 }
402 
drawUrl(WordEntry & wordEntry,int drawChars,float x,float y)403 void GLWListView::drawUrl(WordEntry &wordEntry, int drawChars, float x, float y)
404 {
405 	GLFont2d *font = GLWFont::instance()->getGameFont();
406 	float partWordWidth = font->getWidth(
407 		textSize_, wordEntry.word_.c_str(), drawChars);
408 
409 	// Add the new url entry
410 	UrlEntry urlEntry;
411 	urlEntry.x_ = x_ + 5.0f + x;
412 	urlEntry.y_ = y - 2.0f;
413 	urlEntry.w_ = partWordWidth;
414 	urlEntry.h_ = textSize_ + 2.0f;
415 	urlEntry.entry_ = &wordEntry;
416 	urls_.push_back(urlEntry);
417 
418 	// Draw the underline
419 	GLState noTexState(GLState::TEXTURE_OFF);
420 	glLineWidth(2.0f);
421 	glColor3fv(wordEntry.color_);
422 	glBegin(GL_LINES);
423 		glVertex2f(x_ + 5.0f + x, y - 2.0f);
424 		glVertex2f(x_ + 5.0f + x + partWordWidth, y - 2.0f);
425 	glEnd();
426 	glLineWidth(1.0f);
427 }
428