1 /**
2  * @file
3  * @todo add getter/setter to cleanup access to extradata from cl_*.c files (check "u.text.")
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "../ui_main.h"
27 #include "../ui_internal.h"
28 #include "../ui_font.h"
29 #include "../ui_actions.h"
30 #include "../ui_parse.h"
31 #include "../ui_behaviour.h"
32 #include "../ui_render.h"
33 #include "ui_node_text.h"
34 #include "ui_node_abstractnode.h"
35 
36 #include "../../client.h"
37 #include "../../cl_language.h"
38 #include "../../../shared/parse.h"
39 
40 #define EXTRADATA_TYPE textExtraData_t
41 #define EXTRADATA(node) UI_EXTRADATA(node, EXTRADATA_TYPE)
42 #define EXTRADATACONST(node) UI_EXTRADATACONST(node, EXTRADATA_TYPE)
43 
44 /* Used for drag&drop-like scrolling */
45 static int mouseScrollX;
46 static int mouseScrollY;
47 
validateCache(uiNode_t * node)48 void uiTextNode::validateCache (uiNode_t* node)
49 {
50 	int v;
51 	if (EXTRADATA(node).dataID == TEXT_NULL || node->text != nullptr)
52 		return;
53 
54 	v = UI_GetDataVersion(EXTRADATA(node).dataID);
55 	if (v != EXTRADATA(node).versionId) {
56 		updateCache(node);
57 	}
58 }
59 
UI_TextNodeGetSelectedText(uiNode_t * node,int num)60 const char* UI_TextNodeGetSelectedText (uiNode_t* node, int num)
61 {
62 	const char* text = UI_GetTextFromList(EXTRADATA(node).dataID, num);
63 	if (text == nullptr)
64 		return "";
65 	return text;
66 }
67 
68 /**
69  * @brief Change the selected line
70  */
UI_TextNodeSelectLine(uiNode_t * node,int num)71 void UI_TextNodeSelectLine (uiNode_t* node, int num)
72 {
73 	if (EXTRADATA(node).textLineSelected == num)
74 		return;
75 	EXTRADATA(node).textLineSelected = num;
76 	EXTRADATA(node).textSelected = UI_TextNodeGetSelectedText(node, num);
77 	if (node->onChange)
78 		UI_ExecuteEventActions(node, node->onChange);
79 }
80 
81 /**
82  * @brief Scroll to the bottom
83  * @param[in] nodePath absolute path
84  */
UI_TextScrollEnd(const char * nodePath)85 void UI_TextScrollEnd (const char* nodePath)
86 {
87 	uiNode_t* node = UI_GetNodeByPath(nodePath);
88 	if (!node) {
89 		Com_DPrintf(DEBUG_CLIENT, "Node '%s' could not be found\n", nodePath);
90 		return;
91 	}
92 
93 	if (!UI_NodeInstanceOf(node, "text")) {
94 		Com_Printf("UI_TextScrollBottom: '%s' node is not an 'text'.\n", Cmd_Argv(1));
95 		return;
96 	}
97 
98 	uiTextNode *b = dynamic_cast<uiTextNode*>(node->behaviour->manager.get());
99 	b->validateCache(node);
100 
101 	if (EXTRADATA(node).super.scrollY.fullSize > EXTRADATA(node).super.scrollY.viewSize) {
102 		EXTRADATA(node).super.scrollY.viewPos = EXTRADATA(node).super.scrollY.fullSize - EXTRADATA(node).super.scrollY.viewSize;
103 		UI_ExecuteEventActions(node, EXTRADATA(node).super.onViewChange);
104 	}
105 }
106 
107 /**
108  * @brief Get the line number under an absolute position
109  * @param[in] node a text node
110  * @param[in] x position x on the screen
111  * @param[in] y position y on the screen
112  * @return The line number under the position (0 = first line)
113  */
UI_TextNodeGetLine(const uiNode_t * node,int x,int y)114 static int UI_TextNodeGetLine (const uiNode_t* node, int x, int y)
115 {
116 	int lineHeight;
117 	int line;
118 	assert(UI_NodeInstanceOf(node, "text"));
119 
120 	lineHeight = EXTRADATACONST(node).lineHeight;
121 	if (lineHeight == 0) {
122 		const char* font = UI_GetFontFromNode(node);
123 		lineHeight = UI_FontGetHeight(font);
124 	}
125 
126 	UI_NodeAbsoluteToRelativePos(node, &x, &y);
127 	y -= node->padding;
128 
129 	/* skip position over the first line */
130 	if (y < 0)
131 		 return -1;
132 	line = (int) (y / lineHeight) + EXTRADATACONST(node).super.scrollY.viewPos;
133 
134 	/* skip position under the last line */
135 	if (line >= EXTRADATACONST(node).super.scrollY.fullSize)
136 		return -1;
137 
138 	return line;
139 }
140 
onMouseMove(uiNode_t * node,int x,int y)141 void uiTextNode::onMouseMove (uiNode_t* node, int x, int y)
142 {
143 	EXTRADATA(node).lineUnderMouse = UI_TextNodeGetLine(node, x, y);
144 }
145 
146 #define UI_TEXTNODE_BUFFERSIZE		32768
147 
148 /**
149  * @brief Handles line breaks and drawing for shared data id
150  * @param[in] node The context node
151  * @param[in] text The test to draw else nullptr
152  * @param[in] list The test to draw else nullptr
153  * @param[in] noDraw If true, calling of this function only update the cache (real number of lines)
154  * @note text or list but be used, not both
155  */
drawText(uiNode_t * node,const char * text,const linkedList_t * list,bool noDraw)156 void uiTextNode::drawText (uiNode_t* node, const char* text, const linkedList_t* list, bool noDraw)
157 {
158 	static char textCopy[UI_TEXTNODE_BUFFERSIZE];
159 	char newFont[MAX_VAR];
160 	const char* oldFont = nullptr;
161 	vec4_t colorHover;
162 	vec4_t colorSelectedHover;
163 	char* cur, *tab, *end;
164 	int fullSizeY;
165 	const char* font = UI_GetFontFromNode(node);
166 	vec2_t pos;
167 	int x, y, width;
168 	int viewSizeY;
169 
170 	UI_GetNodeAbsPos(node, pos);
171 
172 	if (isSizeChange(node)) {
173 		int lineHeight = EXTRADATA(node).lineHeight;
174 		if (lineHeight == 0) {
175 			const char* font = UI_GetFontFromNode(node);
176 			lineHeight = UI_FontGetHeight(font);
177 		}
178 		viewSizeY = node->box.size[1] / lineHeight;
179 	} else {
180 		viewSizeY = EXTRADATA(node).super.scrollY.viewSize;
181 	}
182 
183 	/* text box */
184 	x = pos[0] + node->padding;
185 	y = pos[1] + node->padding;
186 	width = node->box.size[0] - node->padding - node->padding;
187 
188 	if (text) {
189 		Q_strncpyz(textCopy, text, sizeof(textCopy));
190 	} else if (list) {
191 		Q_strncpyz(textCopy, CL_Translate((const char*)list->data), sizeof(textCopy));
192 	} else
193 		return;	/**< Nothing to draw */
194 
195 	cur = textCopy;
196 
197 	/* Hover darkening effect for normal text lines. */
198 	VectorScale(node->color, 0.8, colorHover);
199 	colorHover[3] = node->color[3];
200 
201 	/* Hover darkening effect for selected text lines. */
202 	VectorScale(node->selectedColor, 0.8, colorSelectedHover);
203 	colorSelectedHover[3] = node->selectedColor[3];
204 
205 	/* fix position of the start of the draw according to the align */
206 	switch (node->contentAlign % 3) {
207 	case 0:	/* left */
208 		break;
209 	case 1:	/* middle */
210 		x += width / 2;
211 		break;
212 	case 2:	/* right */
213 		x += width;
214 		break;
215 	}
216 
217 	R_Color(node->color);
218 
219 	fullSizeY = 0;
220 	do {
221 		bool haveTab;
222 		int x1; /* variable x position */
223 		/* new line starts from node x position */
224 		x1 = x;
225 		if (oldFont) {
226 			font = oldFont;
227 			oldFont = nullptr;
228 		}
229 
230 		/* text styles and inline images */
231 		if (cur[0] == '^') {
232 			switch (toupper(cur[1])) {
233 			case 'B':
234 				Com_sprintf(newFont, sizeof(newFont), "%s_bold", font);
235 				oldFont = font;
236 				font = newFont;
237 				cur += 2; /* don't print the format string */
238 				break;
239 			}
240 		}
241 
242 		/* get the position of the next newline - otherwise end will be null */
243 		end = strchr(cur, '\n');
244 		if (end)
245 			/* set the \n to \0 to draw only this part (before the \n) with our font renderer */
246 			/* let end point to the next char after the \n (or \0 now) */
247 			*end++ = '\0';
248 
249 		/* highlighting */
250 		if (fullSizeY == EXTRADATA(node).textLineSelected && EXTRADATA(node).textLineSelected >= 0) {
251 			/* Draw current line in "selected" color (if the linenumber is stored). */
252 			R_Color(node->selectedColor);
253 		} else {
254 			R_Color(node->color);
255 		}
256 
257 		if (node->state && EXTRADATA(node).mousefx && fullSizeY == EXTRADATA(node).lineUnderMouse) {
258 			/* Highlight line if mousefx is true. */
259 			/** @todo what about multiline text that should be highlighted completely? */
260 			if (fullSizeY == EXTRADATA(node).textLineSelected && EXTRADATA(node).textLineSelected >= 0) {
261 				R_Color(colorSelectedHover);
262 			} else {
263 				R_Color(colorHover);
264 			}
265 		}
266 
267 		/* tabulation, we assume all the tabs fit on a single line */
268 		haveTab = strchr(cur, '\t') != nullptr;
269 		if (haveTab) {
270 			while (cur && *cur) {
271 				int tabwidth;
272 
273 				tab = strchr(cur, '\t');
274 
275 				/* use tab stop as given via property definition
276 				 * or use 1/3 of the node size (width) */
277 				if (!EXTRADATA(node).tabWidth)
278 					tabwidth = width / 3;
279 				else
280 					tabwidth = EXTRADATA(node).tabWidth;
281 
282 				if (tab) {
283 					int numtabs = strspn(tab, "\t");
284 					tabwidth *= numtabs;
285 					while (*tab == '\t')
286 						*tab++ = '\0';
287 				} else {
288 					/* maximize width for the last element */
289 					tabwidth = width - (x1 - x);
290 					if (tabwidth < 0)
291 						tabwidth = 0;
292 				}
293 
294 				/* minimize width for element outside node */
295 				if ((x1 - x) + tabwidth > width)
296 					tabwidth = width - (x1 - x);
297 
298 				/* make sure it is positive */
299 				if (tabwidth < 0)
300 					tabwidth = 0;
301 
302 				if (tabwidth != 0)
303 					UI_DrawString(font, (align_t)node->contentAlign, x1, y, x1, tabwidth - 1, EXTRADATA(node).lineHeight, cur, viewSizeY, EXTRADATA(node).super.scrollY.viewPos, &fullSizeY, false, LONGLINES_PRETTYCHOP);
304 
305 				/* next */
306 				x1 += tabwidth;
307 				cur = tab;
308 			}
309 			fullSizeY++;
310 		}
311 
312 		/*Com_Printf("until newline - lines: %i\n", lines);*/
313 		/* the conditional expression at the end is a hack to draw "/n/n" as a blank line */
314 		/* prevent line from being drawn if there is nothing that should be drawn after it */
315 		if (cur && (cur[0] || end || list)) {
316 			/* is it a white line? */
317 			if (!cur) {
318 				fullSizeY++;
319 			} else {
320 				if (noDraw) {
321 					int lines = 0;
322 					R_FontTextSize(font, cur, width, (longlines_t)EXTRADATA(node).longlines, nullptr, nullptr, &lines, nullptr);
323 					fullSizeY += lines;
324 				} else
325 					UI_DrawString(font, (align_t)node->contentAlign, x1, y, x, width, EXTRADATA(node).lineHeight, cur, viewSizeY, EXTRADATA(node).super.scrollY.viewPos, &fullSizeY, true, (longlines_t)EXTRADATA(node).longlines);
326 			}
327 		}
328 
329 		if (EXTRADATA(node).mousefx)
330 			R_Color(node->color); /* restore original color */
331 
332 		/* now set cur to the next char after the \n (see above) */
333 		cur = end;
334 		if (!cur && list) {
335 			list = list->next;
336 			if (list) {
337 				Q_strncpyz(textCopy, CL_Translate((const char*)list->data), sizeof(textCopy));
338 				cur = textCopy;
339 			}
340 		}
341 	} while (cur);
342 
343 	/* update scroll status */
344 	setScrollY(node, -1, viewSizeY, fullSizeY);
345 
346 	R_Color(nullptr);
347 }
348 
updateCache(uiNode_t * node)349 void uiTextNode::updateCache (uiNode_t* node)
350 {
351 	const uiSharedData_t* shared;
352 
353 	if (EXTRADATA(node).dataID == TEXT_NULL && node->text != nullptr)
354 		return;
355 
356 	shared = &ui_global.sharedData[EXTRADATA(node).dataID];
357 
358 	switch (shared->type) {
359 	case UI_SHARED_TEXT:
360 		{
361 			const char* t = CL_Translate(shared->data.text);
362 			drawText(node, t, nullptr, true);
363 		}
364 		break;
365 	case UI_SHARED_LINKEDLISTTEXT:
366 		drawText(node, nullptr, shared->data.linkedListText, true);
367 		break;
368 	default:
369 		break;
370 	}
371 
372 	EXTRADATA(node).versionId = shared->versionId;
373 }
374 
375 /**
376  * @brief Draw a text node
377  */
draw(uiNode_t * node)378 void uiTextNode::draw (uiNode_t* node)
379 {
380 	const uiSharedData_t* shared;
381 
382 	if (EXTRADATA(node).dataID == TEXT_NULL && node->text != nullptr) {
383 		const char* t = CL_Translate(UI_GetReferenceString(node, node->text));
384 		drawText(node, t, nullptr, false);
385 		return;
386 	}
387 
388 	shared = &ui_global.sharedData[EXTRADATA(node).dataID];
389 
390 	switch (shared->type) {
391 	case UI_SHARED_TEXT:
392 	{
393 		const char* t = CL_Translate(shared->data.text);
394 		drawText(node, t, nullptr, false);
395 		break;
396 	}
397 	case UI_SHARED_LINKEDLISTTEXT:
398 		drawText(node, nullptr, shared->data.linkedListText, false);
399 		break;
400 	default:
401 		break;
402 	}
403 
404 	EXTRADATA(node).versionId = shared->versionId;
405 }
406 
407 /**
408  * @brief Calls the script command for a text node that is clickable
409  * @sa UI_TextNodeRightClick
410  */
onLeftClick(uiNode_t * node,int x,int y)411 void uiTextNode::onLeftClick (uiNode_t* node, int x, int y)
412 {
413 	int line = UI_TextNodeGetLine(node, x, y);
414 
415 	if (line < 0 || line >= EXTRADATA(node).super.scrollY.fullSize)
416 		return;
417 
418 	UI_TextNodeSelectLine(node, line);
419 
420 	if (node->onClick)
421 		UI_ExecuteEventActions(node, node->onClick);
422 }
423 
424 /**
425  * @brief Calls the script command for a text node that is clickable via right mouse button
426  * @sa UI_TextNodeClick
427  */
onRightClick(uiNode_t * node,int x,int y)428 void uiTextNode::onRightClick (uiNode_t* node, int x, int y)
429 {
430 	int line = UI_TextNodeGetLine(node, x, y);
431 
432 	if (line < 0 || line >= EXTRADATA(node).super.scrollY.fullSize)
433 		return;
434 
435 	UI_TextNodeSelectLine(node, line);
436 
437 	if (node->onRightClick)
438 		UI_ExecuteEventActions(node, node->onRightClick);
439 }
440 
441 /**
442  */
onScroll(uiNode_t * node,int deltaX,int deltaY)443 bool uiTextNode::onScroll (uiNode_t* node, int deltaX, int deltaY)
444 {
445 	bool updated;
446 	bool down = deltaY > 0;
447 	if (deltaY == 0)
448 		return false;
449 	updated = scrollY(node, (down ? 1 : -1));
450 
451 	/* @todo use super behaviour */
452 	if (node->onWheelUp && !down) {
453 		UI_ExecuteEventActions(node, node->onWheelUp);
454 		updated = true;
455 	}
456 	if (node->onWheelDown && down) {
457 		UI_ExecuteEventActions(node, node->onWheelDown);
458 		updated = true;
459 	}
460 	if (node->onWheel) {
461 		UI_ExecuteEventActions(node, node->onWheel);
462 		updated = true;
463 	}
464 	return updated;
465 }
466 
onLoading(uiNode_t * node)467 void uiTextNode::onLoading (uiNode_t* node)
468 {
469 	EXTRADATA(node).textLineSelected = -1; /**< Invalid/no line selected per default. */
470 	EXTRADATA(node).textSelected = "";
471 	Vector4Set(node->selectedColor, 1.0, 1.0, 1.0, 1.0);
472 	Vector4Set(node->color, 1.0, 1.0, 1.0, 1.0);
473 }
474 
onLoaded(uiNode_t * node)475 void uiTextNode::onLoaded (uiNode_t* node)
476 {
477 	int lineheight = EXTRADATA(node).lineHeight;
478 	/* auto compute lineheight */
479 	/* we don't overwrite EXTRADATA(node).lineHeight, because "0" is dynamically replaced by font height on draw function */
480 	if (lineheight == 0) {
481 		/* the font is used */
482 		const char* font = UI_GetFontFromNode(node);
483 		lineheight = UI_FontGetHeight(font);
484 	}
485 
486 	/* auto compute rows (super.viewSizeY) */
487 	if (EXTRADATA(node).super.scrollY.viewSize == 0) {
488 		if (node->box.size[1] != 0 && lineheight != 0) {
489 			EXTRADATA(node).super.scrollY.viewSize = node->box.size[1] / lineheight;
490 		} else {
491 			EXTRADATA(node).super.scrollY.viewSize = 1;
492 			Com_Printf("UI_TextNodeLoaded: node '%s' has no rows value\n", UI_GetPath(node));
493 		}
494 	}
495 
496 	/* auto compute height */
497 	if (node->box.size[1] == 0) {
498 		node->box.size[1] = EXTRADATA(node).super.scrollY.viewSize * lineheight;
499 	}
500 
501 	/* is text slot exists */
502 	if (EXTRADATA(node).dataID >= UI_MAX_DATAID)
503 		Com_Error(ERR_DROP, "Error in node %s - max shared data id num exceeded (num: %i, max: %i)", UI_GetPath(node), EXTRADATA(node).dataID, UI_MAX_DATAID);
504 
505 #ifdef DEBUG
506 	if (EXTRADATA(node).super.scrollY.viewSize != (int)(node->box.size[1] / lineheight)) {
507 		Com_Printf("UI_TextNodeLoaded: rows value (%i) of node '%s' differs from size (%.0f) and format (%i) values\n",
508 			EXTRADATA(node).super.scrollY.viewSize, UI_GetPath(node), node->box.size[1], lineheight);
509 	}
510 #endif
511 
512 	if (node->text == nullptr && EXTRADATA(node).dataID == TEXT_NULL)
513 		Com_Printf("UI_TextNodeLoaded: 'textid' property of node '%s' is not set\n", UI_GetPath(node));
514 }
515 
516 /**
517  * @brief Track mouse down/up events to implement drag&drop-like scrolling, for touchscreen devices
518  * @sa UI_TextNodeMouseUp, UI_TextNodeCapturedMouseMove
519 */
onMouseDown(uiNode_t * node,int x,int y,int button)520 void uiTextNode::onMouseDown (uiNode_t* node, int x, int y, int button)
521 {
522 	if (button == K_MOUSE1 && !UI_GetMouseCapture() &&
523 		EXTRADATA(node).super.scrollY.fullSize > EXTRADATA(node).super.scrollY.viewSize) {
524 		UI_SetMouseCapture(node);
525 		mouseScrollX = x;
526 		mouseScrollY = y;
527 	}
528 }
529 
onMouseUp(uiNode_t * node,int x,int y,int button)530 void uiTextNode::onMouseUp (uiNode_t* node, int x, int y, int button)
531 {
532 	if (UI_GetMouseCapture() == node)  /* More checks can never hurt */
533 		UI_MouseRelease();
534 }
535 
onCapturedMouseMove(uiNode_t * node,int x,int y)536 void uiTextNode::onCapturedMouseMove (uiNode_t* node, int x, int y)
537 {
538 	const int lineHeight = getCellHeight(node);
539 	const int deltaY = (mouseScrollY - y) / lineHeight;
540 	/* We're doing only vertical scroll, that's enough for the most instances */
541 	if (abs(mouseScrollY - y) >= lineHeight) {
542 		scrollY(node, deltaY);
543 		/* @todo not accurate */
544 		mouseScrollX = x;
545 		mouseScrollY = y;
546 	}
547 	onMouseMove(node, x, y);
548 }
549 
550 /**
551  * @brief Return size of the cell, which is the size (in virtual "pixel") which represent 1 in the scroll values.
552  * Here we guess the widget can scroll pixel per pixel.
553  * @return Size in pixel.
554  */
getCellHeight(uiNode_t * node)555 int uiTextNode::getCellHeight (uiNode_t* node)
556 {
557 	int lineHeight = EXTRADATA(node).lineHeight;
558 	if (lineHeight == 0)
559 		lineHeight = UI_FontGetHeight(UI_GetFontFromNode(node));
560 	return lineHeight;
561 }
562 
UI_RegisterTextNode(uiBehaviour_t * behaviour)563 void UI_RegisterTextNode (uiBehaviour_t* behaviour)
564 {
565 	behaviour->name = "text";
566 	behaviour->extends = "abstractscrollable";
567 	behaviour->manager = UINodePtr(new uiTextNode());
568 	behaviour->extraDataSize = sizeof(EXTRADATA_TYPE);
569 
570 	/* Current selected line  */
571 	UI_RegisterExtradataNodeProperty(behaviour, "lineselected", V_INT, textExtraData_t, textLineSelected);
572 
573 	/* Text of the current selected line */
574 	UI_RegisterExtradataNodeProperty(behaviour, "textselected", V_CVAR_OR_STRING, textExtraData_t, textSelected);
575 
576 	/* One of the list TEXT_STANDARD, TEXT_LIST, TEXT_UFOPEDIA, TEXT_BUILDINGS,
577 	 * TEXT_BUILDING_INFO, TEXT_RESEARCH, TEXT_RESEARCH_INFO, TEXT_POPUP,
578 	 * TEXT_POPUP_INFO, TEXT_AIRCRAFT_LIST, TEXT_AIRCRAFT, TEXT_AIRCRAFT_INFO,
579 	 * TEXT_MESSAGESYSTEM, TEXT_CAMPAIGN_LIST, TEXT_MULTISELECTION.
580 	 * There are more IDs in use - see ui_data.h for an up-to-date list.
581 	 * Display a shared content registered by the client code.
582 	 */
583 	UI_RegisterExtradataNodeProperty(behaviour, "dataid", V_UI_DATAID, textExtraData_t, dataID);
584 	/* Size between two lines. Default value is 0, in this case it use a line height according to the font size. */
585 	UI_RegisterExtradataNodeProperty(behaviour, "lineheight", V_INT, textExtraData_t, lineHeight);
586 	/* Bigger size of the width replacing a tab character. */
587 	UI_RegisterExtradataNodeProperty(behaviour, "tabwidth", V_INT, textExtraData_t, tabWidth);
588 	/* What to do with text lines longer than node width. Default is to wordwrap them to make multiple lines.
589 	 * It can be LONGLINES_WRAP, LONGLINES_CHOP, LONGLINES_PRETTYCHOP
590 	 */
591 	UI_RegisterExtradataNodeProperty(behaviour, "longlines", V_INT, textExtraData_t, longlines);
592 
593 	/* Number of visible line we can display into the node height.
594 	 * Currently, it translate the scrollable property <code>viewSize</code>
595 	 * @todo For a smooth scroll we should split that
596 	 */
597 	UI_RegisterExtradataNodeProperty(behaviour, "rows", V_INT, textExtraData_t, super.scrollY.viewSize);
598 	/* Number of lines contained into the node.
599 	 * Currently, it translate the scrollable property <code>fullSize</code>
600 	 * @todo For a smooth scroll we should split that
601 	 */
602 	UI_RegisterExtradataNodeProperty(behaviour, "lines", V_INT, textExtraData_t, super.scrollY.fullSize);
603 
604 	/** Highlight each node elements when the mouse move over the node.
605 	 * @todo delete it when its possible (need to create a textlist...)
606 	 */
607 	UI_RegisterExtradataNodeProperty(behaviour, "mousefx", V_BOOL, textExtraData_t, mousefx);
608 
609 	Com_RegisterConstInt("LONGLINES_WRAP", LONGLINES_WRAP);
610 	Com_RegisterConstInt("LONGLINES_CHOP", LONGLINES_CHOP);
611 	Com_RegisterConstInt("LONGLINES_PRETTYCHOP", LONGLINES_PRETTYCHOP);
612 }
613