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 *> ¶meters = 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