ui_node_text.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024 */
00025 
00026 #include "../ui_main.h"
00027 #include "../ui_internal.h"
00028 #include "../ui_font.h"
00029 #include "../ui_actions.h"
00030 #include "../ui_parse.h"
00031 #include "../ui_render.h"
00032 #include "ui_node_text.h"
00033 #include "ui_node_abstractnode.h"
00034 
00035 #include "../../client.h"
00036 #include "../../../shared/parse.h"
00037 
00038 #define EXTRADATA_TYPE textExtraData_t
00039 #define EXTRADATA(node) UI_EXTRADATA(node, EXTRADATA_TYPE)
00040 #define EXTRADATACONST(node) UI_EXTRADATACONST(node, EXTRADATA_TYPE)
00041 
00042 static void UI_TextUpdateCache(uiNode_t *node);
00043 
00044 static void UI_TextValidateCache (uiNode_t *node)
00045 {
00046     int v;
00047     if (EXTRADATA(node).dataID == TEXT_NULL || node->text != NULL)
00048         return;
00049 
00050     v = UI_GetDataVersion(EXTRADATA(node).dataID);
00051     if (v != EXTRADATA(node).versionId) {
00052         UI_TextUpdateCache(node);
00053     }
00054 }
00055 
00059 void UI_TextNodeSelectLine (uiNode_t *node, int num)
00060 {
00061     if (EXTRADATA(node).textLineSelected == num)
00062         return;
00063     EXTRADATA(node).textLineSelected = num;
00064     if (node->onChange)
00065         UI_ExecuteEventActions(node, node->onChange);
00066 }
00067 
00072 void UI_TextScrollEnd (const char* nodePath)
00073 {
00074     uiNode_t *node = UI_GetNodeByPath(nodePath);
00075     if (!node) {
00076         Com_DPrintf(DEBUG_CLIENT, "Node '%s' could not be found\n", nodePath);
00077         return;
00078     }
00079 
00080     if (!UI_NodeInstanceOf(node, "text")) {
00081         Com_Printf("UI_TextScrollBottom: '%s' node is not an 'text'.\n", Cmd_Argv(1));
00082         return;
00083     }
00084 
00085     UI_TextValidateCache(node);
00086 
00087     if (EXTRADATA(node).super.scrollY.fullSize > EXTRADATA(node).super.scrollY.viewSize) {
00088         EXTRADATA(node).super.scrollY.viewPos = EXTRADATA(node).super.scrollY.fullSize - EXTRADATA(node).super.scrollY.viewSize;
00089         UI_ExecuteEventActions(node, EXTRADATA(node).super.onViewChange);
00090     }
00091 }
00092 
00100 static int UI_TextNodeGetLine (const uiNode_t *node, int x, int y)
00101 {
00102     int lineHeight;
00103     int line;
00104     assert(UI_NodeInstanceOf(node, "text"));
00105 
00106     lineHeight = EXTRADATACONST(node).lineHeight;
00107     if (lineHeight == 0) {
00108         const char *font = UI_GetFontFromNode(node);
00109         lineHeight = UI_FontGetHeight(font);
00110     }
00111 
00112     UI_NodeAbsoluteToRelativePos(node, &x, &y);
00113     y -= node->padding;
00114 
00115     /* skip position over the first line */
00116     if (y < 0)
00117          return -1;
00118     line = (int) (y / lineHeight) + EXTRADATACONST(node).super.scrollY.viewPos;
00119 
00120     /* skip position under the last line */
00121     if (line >= EXTRADATACONST(node).super.scrollY.fullSize)
00122         return -1;
00123 
00124     return line;
00125 }
00126 
00127 static void UI_TextNodeMouseMove (uiNode_t *node, int x, int y)
00128 {
00129     EXTRADATA(node).lineUnderMouse = UI_TextNodeGetLine(node, x, y);
00130 }
00131 
00132 #define UI_TEXTNODE_BUFFERSIZE      32768
00133 
00142 static void UI_TextNodeDrawText (uiNode_t* node, const char *text, const linkedList_t* list, qboolean noDraw)
00143 {
00145     char textCopy[UI_TEXTNODE_BUFFERSIZE];
00146     char newFont[MAX_VAR];
00147     const char* oldFont = NULL;
00148     vec4_t colorHover;
00149     vec4_t colorSelectedHover;
00150     char *cur, *tab, *end;
00151     int fullSizeY;
00152     int x1; /* variable x position */
00153     const char *font = UI_GetFontFromNode(node);
00154     vec2_t pos;
00155     int x, y, width;
00156     int viewSizeY;
00157 
00158     UI_GetNodeAbsPos(node, pos);
00159 
00160     if (UI_AbstractScrollableNodeIsSizeChange(node)) {
00161         int lineHeight = EXTRADATA(node).lineHeight;
00162         if (lineHeight == 0) {
00163             const char *font = UI_GetFontFromNode(node);
00164             lineHeight = UI_FontGetHeight(font);
00165         }
00166         viewSizeY = node->size[1] / lineHeight;
00167     } else {
00168         viewSizeY = EXTRADATA(node).super.scrollY.viewSize;
00169     }
00170 
00171     /* text box */
00172     x = pos[0] + node->padding;
00173     y = pos[1] + node->padding;
00174     width = node->size[0] - node->padding - node->padding;
00175 
00176     if (text) {
00177         Q_strncpyz(textCopy, text, sizeof(textCopy));
00178     } else if (list) {
00179         Q_strncpyz(textCopy, (const char*)list->data, sizeof(textCopy));
00180     } else
00181         return; 
00183     cur = textCopy;
00184 
00185     /* Hover darkening effect for normal text lines. */
00186     VectorScale(node->color, 0.8, colorHover);
00187     colorHover[3] = node->color[3];
00188 
00189     /* Hover darkening effect for selected text lines. */
00190     VectorScale(node->selectedColor, 0.8, colorSelectedHover);
00191     colorSelectedHover[3] = node->selectedColor[3];
00192 
00193     /* fix position of the start of the draw according to the align */
00194     switch (node->textalign % 3) {
00195     case 0: /* left */
00196         break;
00197     case 1: /* middle */
00198         x += width / 2;
00199         break;
00200     case 2: /* right */
00201         x += width;
00202         break;
00203     }
00204 
00205     R_Color(node->color);
00206 
00207     fullSizeY = 0;
00208     do {
00209         qboolean haveTab;
00210         /* new line starts from node x position */
00211         x1 = x;
00212         if (oldFont) {
00213             font = oldFont;
00214             oldFont = NULL;
00215         }
00216 
00217         /* text styles and inline images */
00218         if (cur[0] == '^') {
00219             switch (toupper(cur[1])) {
00220             case 'B':
00221                 Com_sprintf(newFont, sizeof(newFont), "%s_bold", font);
00222                 oldFont = font;
00223                 font = newFont;
00224                 cur += 2; /* don't print the format string */
00225                 break;
00226             }
00227         }
00228 
00229         /* get the position of the next newline - otherwise end will be null */
00230         end = strchr(cur, '\n');
00231         if (end)
00232             /* set the \n to \0 to draw only this part (before the \n) with our font renderer */
00233             /* let end point to the next char after the \n (or \0 now) */
00234             *end++ = '\0';
00235 
00236         /* highlighting */
00237         if (fullSizeY == EXTRADATA(node).textLineSelected && EXTRADATA(node).textLineSelected >= 0) {
00238             /* Draw current line in "selected" color (if the linenumber is stored). */
00239             R_Color(node->selectedColor);
00240         } else {
00241             R_Color(node->color);
00242         }
00243 
00244         if (node->state && EXTRADATA(node).mousefx && fullSizeY == EXTRADATA(node).lineUnderMouse) {
00245             /* Highlight line if mousefx is true. */
00247             if (fullSizeY == EXTRADATA(node).textLineSelected && EXTRADATA(node).textLineSelected >= 0) {
00248                 R_Color(colorSelectedHover);
00249             } else {
00250                 R_Color(colorHover);
00251             }
00252         }
00253 
00254         /* tabulation, we assume all the tabs fit on a single line */
00255         haveTab = strchr(cur, '\t') != NULL;
00256         if (haveTab) {
00257             while (cur && *cur) {
00258                 int tabwidth;
00259 
00260                 tab = strchr(cur, '\t');
00261 
00262                 /* use tab stop as given via property definition
00263                  * or use 1/3 of the node size (width) */
00264                 if (!EXTRADATA(node).tabWidth)
00265                     tabwidth = width / 3;
00266                 else
00267                     tabwidth = EXTRADATA(node).tabWidth;
00268 
00269                 if (tab) {
00270                     int numtabs = strspn(tab, "\t");
00271                     tabwidth *= numtabs;
00272                     while (*tab == '\t')
00273                         *tab++ = '\0';
00274                 } else {
00275                     /* maximize width for the last element */
00276                     tabwidth = width - (x1 - x);
00277                     if (tabwidth < 0)
00278                         tabwidth = 0;
00279                 }
00280 
00281                 /* minimize width for element outside node */
00282                 if ((x1 - x) + tabwidth > width)
00283                     tabwidth = width - (x1 - x);
00284 
00285                 /* make sure it is positive */
00286                 if (tabwidth < 0)
00287                     tabwidth = 0;
00288 
00289                 if (tabwidth != 0)
00290                     UI_DrawString(font, node->textalign, x1, y, x1, tabwidth - 1, EXTRADATA(node).lineHeight, cur, viewSizeY, EXTRADATA(node).super.scrollY.viewPos, &fullSizeY, qfalse, LONGLINES_PRETTYCHOP);
00291 
00292                 /* next */
00293                 x1 += tabwidth;
00294                 cur = tab;
00295             }
00296             fullSizeY++;
00297         }
00298 
00299         /*Com_Printf("until newline - lines: %i\n", lines);*/
00300         /* the conditional expression at the end is a hack to draw "/n/n" as a blank line */
00301         /* prevent line from being drawn if there is nothing that should be drawn after it */
00302         if (cur && (cur[0] || end || list)) {
00303             /* is it a white line? */
00304             if (!cur) {
00305                 fullSizeY++;
00306             } else {
00307                 if (noDraw) {
00308                     int lines = 0;
00309                     R_FontTextSize (font, cur, width, EXTRADATA(node).longlines, NULL, NULL, &lines, NULL);
00310                     fullSizeY += lines;
00311                 } else
00312                     UI_DrawString(font, node->textalign, x1, y, x, width, EXTRADATA(node).lineHeight, cur, viewSizeY, EXTRADATA(node).super.scrollY.viewPos, &fullSizeY, qtrue, EXTRADATA(node).longlines);
00313             }
00314         }
00315 
00316         if (EXTRADATA(node).mousefx)
00317             R_Color(node->color); /* restore original color */
00318 
00319         /* now set cur to the next char after the \n (see above) */
00320         cur = end;
00321         if (!cur && list) {
00322             list = list->next;
00323             if (list) {
00324                 Q_strncpyz(textCopy, (const char*)list->data, sizeof(textCopy));
00325                 cur = textCopy;
00326             }
00327         }
00328     } while (cur);
00329 
00330     /* update scroll status */
00331     UI_AbstractScrollableNodeSetY(node, -1, viewSizeY, fullSizeY);
00332 
00333     R_Color(NULL);
00334 }
00335 
00336 static void UI_TextUpdateCache (uiNode_t *node)
00337 {
00338     const uiSharedData_t *shared;
00339 
00340     if (EXTRADATA(node).dataID == TEXT_NULL && node->text != NULL)
00341         return;
00342 
00343     shared = &ui_global.sharedData[EXTRADATA(node).dataID];
00344 
00345     switch (shared->type) {
00346     case UI_SHARED_TEXT:
00347         {
00348             const char* t = shared->data.text;
00349             if (t[0] == '_')
00350                 t = _(++t);
00351             UI_TextNodeDrawText(node, t, NULL, qtrue);
00352         }
00353         break;
00354     case UI_SHARED_LINKEDLISTTEXT:
00355         UI_TextNodeDrawText(node, NULL, shared->data.linkedListText, qtrue);
00356         break;
00357     default:
00358         break;
00359     }
00360 
00361     EXTRADATA(node).versionId = shared->versionId;
00362 }
00363 
00367 static void UI_TextNodeDraw (uiNode_t *node)
00368 {
00369     const uiSharedData_t *shared;
00370 
00371     if (EXTRADATA(node).dataID == TEXT_NULL && node->text != NULL) {
00372         const char* t = UI_GetReferenceString(node, node->text);
00373         if (t[0] == '_')
00374             t = _(++t);
00375         UI_TextNodeDrawText(node, t, NULL, qfalse);
00376         return;
00377     }
00378 
00379     shared = &ui_global.sharedData[EXTRADATA(node).dataID];
00380 
00381     switch (shared->type) {
00382     case UI_SHARED_TEXT:
00383     {
00384         const char* t = shared->data.text;
00385         if (t[0] == '_')
00386             t = _(++t);
00387         UI_TextNodeDrawText(node, t, NULL, qfalse);
00388         break;
00389     }
00390     case UI_SHARED_LINKEDLISTTEXT:
00391         UI_TextNodeDrawText(node, NULL, shared->data.linkedListText, qfalse);
00392         break;
00393     default:
00394         break;
00395     }
00396 
00397     EXTRADATA(node).versionId = shared->versionId;
00398 }
00399 
00404 static void UI_TextNodeClick (uiNode_t * node, int x, int y)
00405 {
00406     int line = UI_TextNodeGetLine(node, x, y);
00407 
00408     if (line < 0 || line >= EXTRADATA(node).super.scrollY.fullSize)
00409         return;
00410 
00411     UI_TextNodeSelectLine(node, line);
00412 
00413     if (node->onClick)
00414         UI_ExecuteEventActions(node, node->onClick);
00415 }
00416 
00421 static void UI_TextNodeRightClick (uiNode_t * node, int x, int y)
00422 {
00423     int line = UI_TextNodeGetLine(node, x, y);
00424 
00425     if (line < 0 || line >= EXTRADATA(node).super.scrollY.fullSize)
00426         return;
00427 
00428     UI_TextNodeSelectLine(node, line);
00429 
00430     if (node->onRightClick)
00431         UI_ExecuteEventActions(node, node->onRightClick);
00432 }
00433 
00436 static void UI_TextNodeMouseWheel (uiNode_t *node, qboolean down, int x, int y)
00437 {
00438     UI_AbstractScrollableNodeScrollY(node, (down ? 1 : -1));
00439     if (node->onWheelUp && !down)
00440         UI_ExecuteEventActions(node, node->onWheelUp);
00441     if (node->onWheelDown && down)
00442         UI_ExecuteEventActions(node, node->onWheelDown);
00443     if (node->onWheel)
00444         UI_ExecuteEventActions(node, node->onWheel);
00445 }
00446 
00447 static void UI_TextNodeLoading (uiNode_t *node)
00448 {
00449     EXTRADATA(node).textLineSelected = -1; 
00450     Vector4Set(node->selectedColor, 1.0, 1.0, 1.0, 1.0);
00451     Vector4Set(node->color, 1.0, 1.0, 1.0, 1.0);
00452 }
00453 
00454 static void UI_TextNodeLoaded (uiNode_t *node)
00455 {
00456     int lineheight = EXTRADATA(node).lineHeight;
00457     /* auto compute lineheight */
00458     /* we don't overwrite EXTRADATA(node).lineHeight, because "0" is dynamically replaced by font height on draw function */
00459     if (lineheight == 0) {
00460         /* the font is used */
00461         const char *font = UI_GetFontFromNode(node);
00462         lineheight = UI_FontGetHeight(font);
00463     }
00464 
00465     /* auto compute rows (super.viewSizeY) */
00466     if (EXTRADATA(node).super.scrollY.viewSize == 0) {
00467         if (node->size[1] != 0 && lineheight != 0) {
00468             EXTRADATA(node).super.scrollY.viewSize = node->size[1] / lineheight;
00469         } else {
00470             EXTRADATA(node).super.scrollY.viewSize = 1;
00471             Com_Printf("UI_TextNodeLoaded: node '%s' has no rows value\n", UI_GetPath(node));
00472         }
00473     }
00474 
00475     /* auto compute height */
00476     if (node->size[1] == 0) {
00477         node->size[1] = EXTRADATA(node).super.scrollY.viewSize * lineheight;
00478     }
00479 
00480     /* is text slot exists */
00481     if (EXTRADATA(node).dataID >= UI_MAX_DATAID)
00482         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);
00483 
00484 #ifdef DEBUG
00485     if (EXTRADATA(node).super.scrollY.viewSize != (int)(node->size[1] / lineheight)) {
00486         Com_Printf("UI_TextNodeLoaded: rows value (%i) of node '%s' differs from size (%.0f) and format (%i) values\n",
00487             EXTRADATA(node).super.scrollY.viewSize, UI_GetPath(node), node->size[1], lineheight);
00488     }
00489 #endif
00490 
00491     if (node->text == NULL && EXTRADATA(node).dataID == TEXT_NULL)
00492         Com_Printf("UI_TextNodeLoaded: 'textid' property of node '%s' is not set\n", UI_GetPath(node));
00493 }
00494 
00495 static const value_t properties[] = {
00496     /* Current selected line  */
00497     {"lineselected", V_INT, UI_EXTRADATA_OFFSETOF(textExtraData_t, textLineSelected), MEMBER_SIZEOF(textExtraData_t, textLineSelected)},
00498     /* One of the list TEXT_STANDARD, TEXT_LIST, TEXT_UFOPEDIA, TEXT_BUILDINGS,
00499      * TEXT_BUILDING_INFO, TEXT_RESEARCH, TEXT_RESEARCH_INFO, TEXT_POPUP,
00500      * TEXT_POPUP_INFO, TEXT_AIRCRAFT_LIST, TEXT_AIRCRAFT, TEXT_AIRCRAFT_INFO,
00501      * TEXT_MESSAGESYSTEM, TEXT_CAMPAIGN_LIST, TEXT_MULTISELECTION.
00502      * There are more IDs in use - see mn_data.h for and up-to-date list.
00503      * Display a shared content registered by the client code.
00504      */
00505     {"dataid", V_UI_DATAID, UI_EXTRADATA_OFFSETOF(textExtraData_t, dataID), MEMBER_SIZEOF(textExtraData_t, dataID)},
00506     /* Size between two lines. Default value is 0, in this case it use a line height according to the font size. */
00507     {"lineheight", V_INT, UI_EXTRADATA_OFFSETOF(textExtraData_t, lineHeight), MEMBER_SIZEOF(textExtraData_t, lineHeight)},
00508     /* Bigger size of the width replacing a tab character. */
00509     {"tabwidth", V_INT, UI_EXTRADATA_OFFSETOF(textExtraData_t, tabWidth), MEMBER_SIZEOF(textExtraData_t, tabWidth)},
00510     /* What to do with text lines longer than node width. Default is to wordwrap them to make multiple lines.
00511      * It can be LONGLINES_WRAP, LONGLINES_CHOP, LONGLINES_PRETTYCHOP
00512      */
00513     {"longlines", V_INT, UI_EXTRADATA_OFFSETOF(textExtraData_t, longlines), MEMBER_SIZEOF(textExtraData_t, longlines)},
00514 
00515     /* Number of visible line we can display into the node height.
00516      * Currently, it translate the scrollable property <code>viewSize</code>
00517      * @todo For a smooth scroll we should split that
00518      */
00519     {"rows", V_INT, UI_EXTRADATA_OFFSETOF(textExtraData_t, super.scrollY.viewSize), MEMBER_SIZEOF(textExtraData_t, super.scrollY.viewSize)},
00520     /* Number of lines contained into the node.
00521      * Currently, it translate the scrollable property <code>fullSize</code>
00522      * @todo For a smooth scroll we should split that
00523      */
00524     {"lines", V_INT, UI_EXTRADATA_OFFSETOF(textExtraData_t, super.scrollY.fullSize), MEMBER_SIZEOF(textExtraData_t, super.scrollY.fullSize)},
00525 
00529     {"mousefx", V_BOOL, UI_EXTRADATA_OFFSETOF(textExtraData_t, mousefx), MEMBER_SIZEOF(textExtraData_t, mousefx)},
00530     {NULL, V_NULL, 0, 0}
00531 };
00532 
00533 void UI_RegisterTextNode (uiBehaviour_t *behaviour)
00534 {
00535     behaviour->name = "text";
00536     behaviour->extends = "abstractscrollable";
00537     behaviour->draw = UI_TextNodeDraw;
00538     behaviour->leftClick = UI_TextNodeClick;
00539     behaviour->rightClick = UI_TextNodeRightClick;
00540     behaviour->mouseWheel = UI_TextNodeMouseWheel;
00541     behaviour->mouseMove = UI_TextNodeMouseMove;
00542     behaviour->loading = UI_TextNodeLoading;
00543     behaviour->loaded = UI_TextNodeLoaded;
00544     behaviour->properties = properties;
00545     behaviour->extraDataSize = sizeof(EXTRADATA_TYPE);
00546 
00547     Com_RegisterConstInt("LONGLINES_WRAP", LONGLINES_WRAP);
00548     Com_RegisterConstInt("LONGLINES_CHOP", LONGLINES_CHOP);
00549     Com_RegisterConstInt("LONGLINES_PRETTYCHOP", LONGLINES_PRETTYCHOP);
00550 }

Generated by  doxygen 1.6.2