static void UI_AbstractNodeCallCreateChild (uiNode_t* node, const uiCallContext_t* context) { uiNode_t* child; uiNode_t* component; const char* name; const char* type; if (UI_GetParamNumber(context) != 2) { Com_Printf("UI_AbstractNodeCallCreateChild: Invalid number of parameters\n"); return; } name = UI_GetParam(context, 1); type = UI_GetParam(context, 2); uiNode_t* existingNode = UI_GetNode(node, name); if (existingNode != nullptr) { Com_Printf("UI_AbstractNodeCallCreateChild: Node with name '%s' already exists\n", name); } component = UI_GetComponent(type); if (component) { child = UI_CloneNode(component, node->root, true, name, true); } else { child = UI_AllocNode(name, type, true); } if (child == nullptr) { Com_Printf("UI_AbstractNodeCallCreateChild: Impossible to create the node\n"); return; } UI_AppendNode(node, child); }
/** * @brief Recursive searches for a child node by name in the entire subtree. * @return A uiNode_t* or nullptr if not found. */ uiNode_t* UI_FindNode(const uiNode_t* node, const char* name) { /* search current level */ uiNode_t* result = UI_GetNode(node, name); if (!result) { /* iterate child nodes and search next level */ for(uiNode_t* current = node->firstChild; current; current = current->next) { result = UI_FindNode(current, name); if (result) break; } } return result; }
/** * @brief Generates a popup that contains a list of selectable choices. * @param[in] title Title of the popup. * @param[in] headline First line of text written in the popup. * @param[in] entries List of the selectables choices. * @param[in] clickAction Action to perform when one clicked on the popup. */ uiNode_t *UI_PopupList (const char *title, const char *headline, linkedList_t* entries, const char *clickAction) { uiNode_t* window; uiNode_t* listNode; Cvar_Set("ui_sys_popup_title", title); UI_RegisterText(TEXT_POPUP_INFO, headline); /* make sure, that we are using the linked list */ UI_ResetData(TEXT_LIST); UI_RegisterLinkedListText(TEXT_LIST, entries); window = UI_GetWindow(POPUPLIST_WINDOW_NAME); if (!window) Com_Error(ERR_FATAL, "Could not get "POPUPLIST_WINDOW_NAME" window"); listNode = UI_GetNode(window, POPUPLIST_NODE_NAME); if (!listNode) Com_Error(ERR_FATAL, "Could not get "POPUPLIST_NODE_NAME" node in "POPUPLIST_WINDOW_NAME" window"); /* free previous actions */ if (listNode->onClick) { assert(listNode->onClick->d.terminal.d1.data); Mem_Free(listNode->onClick->d.terminal.d1.data); Mem_Free(listNode->onClick); listNode->onClick = NULL; } if (clickAction) { UI_PoolAllocAction(&listNode->onClick, EA_CMD, clickAction); } else { listNode->onClick = NULL; } if (!UI_IsWindowOnStack(window->name)) UI_PushWindow(window->name, NULL, NULL); return listNode; }
/** * @brief Set string and action button. * @param[in] window window where buttons are modified. * @param[in] button Name of the node of the button. * @param[in] clickAction Action to perform when button is clicked. * @note clickAction may be NULL if button is not needed. */ static void UI_SetOneButton (uiNode_t* window, const char *button, const char *clickAction) { uiNode_t* buttonNode; buttonNode = UI_GetNode(window, button); if (!buttonNode) Com_Error(ERR_FATAL, "Could not get %s node in %s window", button, window->name); /* free previous actions */ if (buttonNode->onClick) { assert(buttonNode->onClick->d.terminal.d1.data); Mem_Free(buttonNode->onClick->d.terminal.d1.data); Mem_Free(buttonNode->onClick); buttonNode->onClick = NULL; } if (clickAction) { UI_PoolAllocAction(&buttonNode->onClick, EA_CMD, clickAction); buttonNode->invis = qfalse; } else { buttonNode->onClick = NULL; buttonNode->invis = qtrue; } }
/** * @brief parse a node * @sa UI_ParseNodeProperties * @todo we can think about merging UI_ParseNodeProperties here * @note first token already read * @note dont read more than the need token (last right token is '}' of end of node) */ static uiNode_t* UI_ParseNode (uiNode_t* parent, const char** text, const char** token, const char* errhead) { uiNode_t* node = nullptr; uiBehaviour_t* behaviour; uiNode_t* component = nullptr; /* get the behaviour */ behaviour = UI_GetNodeBehaviour(*token); if (!behaviour) { component = UI_GetComponent(*token); } if (behaviour == nullptr && component == nullptr) { Com_Printf("UI_ParseNode: node behaviour/component '%s' doesn't exist (%s)\n", *token, UI_GetPath(parent)); return nullptr; } /* get the name */ *token = Com_EParse(text, errhead, ""); if (!*text) return nullptr; if (!UI_TokenIsName(*token, Com_GetType(text) == TT_QUOTED_WORD)) { Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", *token); return nullptr; } if (UI_TokenIsReserved(*token)) { Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", *token); return nullptr; } /* test if node already exists */ /* Already existing node should only come from inherited node, we should not have 2 definitions of the same node into the same window. */ if (parent) node = UI_GetNode(parent, *token); /* reuse a node */ if (node) { const uiBehaviour_t* test = (behaviour != nullptr) ? behaviour : (component != nullptr) ? component->behaviour : nullptr; if (node->behaviour != test) { Com_Printf("UI_ParseNode: we can't change node type (node \"%s\")\n", UI_GetPath(node)); return nullptr; } Com_DPrintf(DEBUG_CLIENT, "... over-riding node %s\n", UI_GetPath(node)); /* else initialize a component */ } else if (component) { node = UI_CloneNode(component, nullptr, true, *token, false); if (parent) { if (parent->root) UI_UpdateRoot(node, parent->root); UI_AppendNode(parent, node); } /* else initialize a new node */ } else { node = UI_AllocNode(*token, behaviour->name, false); node->parent = parent; if (parent) node->root = parent->root; /** @todo move it into caller */ if (parent) UI_AppendNode(parent, node); } /* get body */ const bool result = UI_ParseNodeBody(node, text, token, errhead); if (!result) return nullptr; /* validate properties */ UI_Node_Loaded(node); return node; }
/** * @brief Read a path and return every we can use (node and property) * @details The path token must be a window name, and then node child. * Reserved token 'root' and 'parent' can be used to navigate. * If relativeNode is set, the path can start with reserved token * 'this', 'root' and 'parent' (relative to this node). * The function can return a node property by using a '\@', * the path 'foo\@pos' will return the window foo and the property 'pos' * from the 'window' behaviour. * @param[in] path Path to read. Contain a node location with dot seprator and a facultative property * @param[in] relativeNode relative node where the path start. It allow to use facultative command to start the path (this, parent, root). * @param[out] resultNode Node found. Else NULL. * @param[out] resultProperty Property found. Else NULL. * TODO Speed up, evilly used function, use strncmp instead of using buffer copy (name[MAX_VAR]) */ void UI_ReadNodePath (const char* path, const uiNode_t *relativeNode, uiNode_t **resultNode, const value_t **resultProperty) { char name[MAX_VAR]; uiNode_t* node = NULL; const char* nextName; char nextCommand = '^'; *resultNode = NULL; if (resultProperty) *resultProperty = NULL; nextName = path; while (nextName && nextName[0] != '\0') { const char* begin = nextName; char command = nextCommand; nextName = strpbrk(begin, ".@#"); if (!nextName) { Q_strncpyz(name, begin, sizeof(name)); nextCommand = '\0'; } else { assert(nextName - begin + 1 <= sizeof(name)); Q_strncpyz(name, begin, nextName - begin + 1); nextCommand = *nextName; nextName++; } switch (command) { case '^': /* first string */ if (Q_streq(name, "this")) { if (relativeNode == NULL) return; /** @todo find a way to fix the bad cast. only here to remove "discards qualifiers" warning */ node = *(uiNode_t**) ((void*)&relativeNode); } else if (Q_streq(name, "parent")) { if (relativeNode == NULL) return; node = relativeNode->parent; } else if (Q_streq(name, "root")) { if (relativeNode == NULL) return; node = relativeNode->root; } else node = UI_GetWindow(name); break; case '.': /* child node */ if (Q_streq(name, "parent")) node = node->parent; else if (Q_streq(name, "root")) node = node->root; else node = UI_GetNode(node, name); break; case '#': /* window index */ /** @todo FIXME use a warning instead of an assert */ assert(node->behaviour == ui_windowBehaviour); node = UI_WindowNodeGetIndexedChild(node, name); break; case '@': /* property */ assert(nextCommand == '\0'); *resultProperty = UI_GetPropertyFromBehaviour(node->behaviour, name); *resultNode = node; return; } if (!node) return; } *resultNode = node; return; }
/** * @brief Read a path and return every we can use (node and property) * @details The path token must be a window name, and then node child. * Reserved token 'root' and 'parent' can be used to navigate. * If relativeNode is set, the path can start with reserved token * 'this', 'root', 'parent' and 'child' (relative to this node). * The function can return a node property by using a '\@', * the path 'foo\@pos' will return the window foo and the property 'pos' * from the 'window' behaviour. * @param[in] path Path to read. Contain a node location with dot seprator and a facultative property * @param[in] relativeNode relative node where the path start. It allow to use facultative command to start the path (this, parent, root). * @param[in] iterationNode relative node referencing child in 'forchildin' iteration, mapped to '*node:child', can be nullptr * @param[out] resultNode Node found. Else nullptr. * @param[out] resultProperty Property found. Else nullptr. * @param[in,out] luaMethod A pointer to a value_t structure that is filled with a lua based method identification, can be nullptr * @note If luaMethod is set, the method will scan the known lua methods defined on the behaviour. If luaMethod is not set, only * registered local properties are scanned. * @todo Speed up, evilly used function, use strncmp instead of using buffer copy (name[MAX_VAR]) * @note If luaMethod is set, make sure to release the allocated .name string. */ void UI_ReadNodePath (const char* path, const uiNode_t* relativeNode, const uiNode_t* iterationNode, uiNode_t** resultNode, const value_t** resultProperty, value_t *luaMethod) { char name[MAX_VAR]; uiNode_t* node = nullptr; uiNode_t* childnode = nullptr; const char* nextName; char nextCommand = '^'; *resultNode = nullptr; if (resultProperty) *resultProperty = nullptr; nextName = path; while (nextName && nextName[0] != '\0') { const char* begin = nextName; char command = nextCommand; nextName = strpbrk(begin, ".@#"); if (!nextName) { Q_strncpyz(name, begin, sizeof(name)); nextCommand = '\0'; } else { assert(nextName - begin + 1 <= sizeof(name)); Q_strncpyz(name, begin, nextName - begin + 1); nextCommand = *nextName; nextName++; } switch (command) { case '^': /* first string */ if (Q_streq(name, "this")) { if (relativeNode == nullptr) return; node = const_cast<uiNode_t *>(relativeNode); } else if (Q_streq(name, "parent")) { if (relativeNode == nullptr) return; node = relativeNode->parent; } else if (Q_streq(name, "root")) { if (relativeNode == nullptr) return; node = relativeNode->root; } else if (Q_streq(name, "child")) { if (iterationNode == nullptr) return; node = const_cast<uiNode_t *>(iterationNode); } else node = UI_GetWindow(name); break; case '.': /* child node */ if (Q_streq(name, "parent")) childnode = node->parent; else if (Q_streq(name, "root")) childnode = node->root; else { childnode = UI_GetNode(node, name); /* if no node found and if we need it, then it is possible we call a lua based function */ if (luaMethod && !childnode) { *resultProperty = UI_GetPropertyOrLuaMethod(node, name, luaMethod); if (luaMethod->type) { childnode = node; } } } node = childnode; break; case '#': /* window index */ /** @todo FIXME use a warning instead of an assert */ assert(UI_Node_IsWindow(node)); node = UI_WindowNodeGetIndexedChild(node, name); break; case '@': /* property */ assert(nextCommand == '\0'); *resultProperty = UI_GetPropertyOrLuaMethod(node, name, luaMethod); *resultNode = node; return; } if (!node) return; } *resultNode = node; return; }
/** * @brief Try to autoplace an item from a container. * @param[in] node The context node * @param[in] ic An item from the node container * @todo We should use an item ID, and get the item inside the function, to avoid wrong uses. */ void UI_ContainerNodeAutoPlaceItem (uiNode_t* node, invList_t *ic) { containerIndex_t target; uiNode_t *targetNode; bool ammoChanged = false; const invDef_t *container = EXTRADATA(node).container; /* Right click: automatic item assignment/removal. */ if (container->id != csi.idEquip) { if (ic->item.ammo && ic->item.ammo != ic->item.item && ic->item.ammoLeft) { /* Remove ammo on removing weapon from a soldier */ target = csi.idEquip; ammoChanged = INV_UnloadWeapon(ic, ui_inventory, INVDEF(target)); } else { /* Move back to idEquip (ground, floor) container. */ target = csi.idEquip; INV_MoveItem(ui_inventory, INVDEF(target), NONE, NONE, container, ic, NULL); } } else { bool packed = false; assert(ic->item.item); /* armour can only have one target */ if (INV_IsArmour(ic->item.item)) { target = csi.idArmour; packed = INV_MoveItem(ui_inventory, INVDEF(target), 0, 0, container, ic, NULL); /* ammo or item */ } else if (INV_IsAmmo(ic->item.item)) { /* Finally try left and right hand. There is no other place to put it now. */ const containerIndex_t idxArray[] = { csi.idBelt, csi.idHolster, csi.idBackpack, csi.idLeft, csi.idRight }; const size_t size = lengthof(idxArray); unsigned int i; for (i = 0; i < size; i++) { target = idxArray[i]; packed = UI_ContainerNodeAddItem(container, ic, target, NULL); if (packed) break; } } else { if (ic->item.item->headgear) { target = csi.idHeadgear; packed = UI_ContainerNodeAddItem(container, ic, target, NULL); } else { /* left and right are single containers, but this might change - it's cleaner to check * for available space here, too */ const containerIndex_t idxArray[] = { csi.idRight, csi.idBelt, csi.idHolster, csi.idBackpack, csi.idLeft }; const size_t size = lengthof(idxArray); invList_t *tItem = NULL; unsigned int i; for (i = 0; i < size; i++) { target = idxArray[i]; packed = UI_ContainerNodeAddItem(container, ic, target, &tItem); if (packed) { if ((ic->item.item->weapon && !ic->item.ammoLeft) || ic->item.item->oneshot) ammoChanged = INV_LoadWeapon(tItem, ui_inventory, container, INVDEF(target)); break; } } } } /* no need to continue here - placement wasn't successful at all */ if (!packed) return; } /* Run onChange events */ targetNode = UI_GetContainerNodeByContainerIDX(node->parent, target); if (node->onChange) UI_ExecuteEventActions(node, node->onChange); if (targetNode != NULL && node != targetNode && targetNode->onChange) UI_ExecuteEventActions(targetNode, targetNode->onChange); /* Also call onChange for equip_ammo if ammo moved * Maybe there's a better way to do this? */ if (INV_IsAmmo(ic->item.item) || ammoChanged) { /** @todo hard coded node name, remove it when it is possible */ uiNode_t *ammoNode = UI_GetNode(node->root, "equip_ammo"); if (ammoNode != NULL && node != ammoNode && ammoNode->onChange) UI_ExecuteEventActions(ammoNode, ammoNode->onChange); } }
/** * @brief Search a child container node by the given container id * @note Only search with one depth */ uiNode_t *UI_GetContainerNodeByContainerIDX (const uiNode_t* const parent, const int index) { const invDef_t* const container = INVDEF(index); uiNode_t *containerNode = UI_GetNode(parent, container->name); return containerNode; }