/* =========================================================================== Copyright (C) 2015-2023 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "ui_local.h" CLASS_DECLARATION(UIWidget, UIField, NULL) { {&W_LeftMouseDown, &UIField::Pressed}, {NULL, NULL } }; UIField::UIField() { AllowActivate(qtrue); m_iPreStep = 0; } void UIField::Draw(void) { UpdateData(); // the width of the widget that can be filled with valid text float fVirtualWidth = m_frame.size.width / getVirtualScale()[0] - m_font->getWidth(" ..", -1); float indentLength = m_indent; float cursorSize = m_font->getCharWidth('_'); // The total pixel length of the entire string in the editfield buffer float totalDrawLength = m_font->getWidth(m_edit.m_buffer, -1) + indentLength; // The index of the character that has to be shown first // when viewing the end of the string to properly fill out the field int iMaxPreStep; float fEndLen = totalDrawLength + cursorSize; int buflen = strlen(m_edit.m_buffer); // Calculate the max prestep for (iMaxPreStep = 0; fEndLen > fVirtualWidth && iMaxPreStep < buflen; iMaxPreStep++) { float fCharWidth = m_font->getCharWidth(m_edit.m_buffer[iMaxPreStep]); fEndLen -= fCharWidth; } // fEndLen is discarded // Index of the last currently visible character in the textfield int iLastChar; fEndLen = indentLength + cursorSize; for (iLastChar = m_iPreStep; fEndLen < fVirtualWidth; iLastChar++) { if (char c = m_edit.m_buffer[iLastChar]) { float fCharWidth = m_font->getCharWidth(c); fEndLen += fCharWidth; } else { // important to quit early! // otherwise iLastChar would get incremented one extra time break; } } if (IsActive()) { if (m_edit.m_cursor >= m_iPreStep) { // Fixed in OPM // m_iPreStep < iLastChar check added to avoid passing negative size to Q_strncpyz and crashing the game while (iLastChar <= m_edit.m_cursor && m_iPreStep < iMaxPreStep && m_iPreStep < iLastChar ) { // is text too long to fit? fEndLen -= m_font->getCharWidth(m_edit.m_buffer[m_iPreStep]); m_iPreStep++; // FIXME: repetitive logic if (char c = m_edit.m_buffer[iLastChar]) { float fCharWidth = m_font->getCharWidth(c); fEndLen += fCharWidth; } else { break; } while (fEndLen < fVirtualWidth) { iLastChar++; if (char c = m_edit.m_buffer[iLastChar]) { float fCharWidth = m_font->getCharWidth(c); fEndLen += fCharWidth; } else { break; } } } } else { m_iPreStep = m_edit.m_cursor; } m_iPreStep = Q_min(m_iPreStep, iMaxPreStep); } // Put the portion of the text intended for display into the temptext buffer char temptext[EDITFIELD_BUFSIZE]; Q_strncpyz(temptext, &m_edit.m_buffer[m_iPreStep], iLastChar - m_iPreStep + 1); // Calculate cursor position float cursorPos = indentLength - 1.0f; // Added in OPM // new i < EDITFIELD_BUFSIZE check for extra safety for (int i = m_iPreStep; i < m_edit.m_cursor && i < EDITFIELD_BUFSIZE; i++) { float fCharWidth = m_font->getCharWidth(m_edit.m_buffer[i]); cursorPos += fCharWidth; } cursorPos = Q_min(cursorPos, fVirtualWidth); // Calculate text height float y = m_frame.size.height / getVirtualScale()[1] - m_font->getHeight(); if (m_bottomindent * 2 <= y) // upper and lower margin { y -= m_bottomindent; } else { y /= 2; } // Show text m_font->setColor(m_foreground_color); m_font->Print(m_indent, y, temptext, -1, getVirtualScale()); // Display blinking cursor if field is in focus if (IsActive()) { const char *cursorChar = (uid.time / 250) & 1 ? "|" : "_"; m_font->Print(cursorPos, y, cursorChar, -1, getVirtualScale()); } Q_strncpyz(temptext, "..", EDITFIELD_BUFSIZE); if (m_iPreStep) { // Display leading dots if not starting from the beginning m_font->Print(2.0f, y, temptext, -1, getVirtualScale()); } // Added in OPM // new iLastChar < EDITFIELD_BUFSIZE check for extra safety if (iLastChar < EDITFIELD_BUFSIZE && m_edit.m_buffer[iLastChar]) { // Display trailing dots if not ending at the end fVirtualWidth = m_frame.size.width / getVirtualScale()[0] - m_font->getWidth(temptext, -1) - 2.0f; m_font->Print(fVirtualWidth, y, temptext, -1, getVirtualScale()); } } qboolean UIField::KeyEvent(int key, unsigned int time) { if (key == 'l' && uii.Sys_IsKeyDown(K_CTRL)) { return qtrue; } if (key == K_ENTER || key == K_KP_ENTER) { if (m_cvarname.length()) { uii.Cvar_Set(m_cvarname, m_edit.m_buffer); } if (m_commandhandler) { m_commandhandler(str(m_edit.m_buffer) + '\n', NULL); } if (m_command.length()) { str result = m_command + ' ' + m_edit.m_buffer + '\n'; Cbuf_AddText(result); } return qtrue; } if ((key == K_INS || key == K_KP_INS) && uii.Sys_IsKeyDown(K_SHIFT)) { return qtrue; } int len = strlen(m_edit.m_buffer); switch (key) { case K_DEL: // DELETE key, backspace is handled in CharEvent if (m_edit.m_cursor < len) { memmove(&m_edit.m_buffer[m_edit.m_cursor], &m_edit.m_buffer[m_edit.m_cursor + 1], len - m_edit.m_cursor); if (m_cvarname.length()) { uii.Cvar_Set(m_cvarname, m_edit.m_buffer); } } return qtrue; case K_RIGHTARROW: if (m_edit.m_cursor < len) { m_edit.m_cursor++; } return qtrue; case K_LEFTARROW: if (m_edit.m_cursor > 0) { m_edit.m_cursor--; } return qtrue; } // HOME or Ctrl + A if (key == K_HOME || (tolower(key) == 'a' && uii.Sys_IsKeyDown(K_CTRL))) { m_edit.m_cursor = 0; return qtrue; } // END or Ctrl + E if (key == K_END || (tolower(key) == 'e' && uii.Sys_IsKeyDown(K_CTRL))) { m_edit.m_cursor = len; return qtrue; } return key == K_INS; } void UIField::CharEvent(int ch) { uii.Snd_PlaySound("sound/menu/typekey.wav"); if (ch == 3 || ch == 22) // Ctrl + C (^C) or Ctrl + V (^V) { return; } int len = strlen(m_edit.m_buffer); switch (ch) { case '\b': // backspace or Ctrl + H (^H) - DEL is handled in KeyEvent if (m_edit.m_cursor <= 0) { // already at beginning of field return; } // Move all text after the cursor one position back memmove(&m_edit.m_buffer[m_edit.m_cursor - 1], &m_edit.m_buffer[m_edit.m_cursor], len + 1 - m_edit.m_cursor); // move back cursor by one m_edit.m_cursor--; if (m_cvarname.length()) { uii.Cvar_Set(m_cvarname, m_edit.m_buffer); } return; case 1: // Home (^A) m_edit.m_cursor = 0; return; case 5: // End (^E) m_edit.m_cursor = len; return; } // original check only does ch < 32, // this one excludes DEL too but it shouldn't be a problem, // DEL is handled in KeyEvent if (iscntrl(ch)) { return; } len++; // new printable character was typed // Fixed in OPM // originally this was len == EDITFIELD_BUFSIZE, just being extra safe if (len >= EDITFIELD_BUFSIZE) // is it out of bounds? { return; } // make room for the character in the buffer memmove(&m_edit.m_buffer[m_edit.m_cursor + 1], &m_edit.m_buffer[m_edit.m_cursor], len - m_edit.m_cursor); // insert the new character at the cursor's location m_edit.m_buffer[m_edit.m_cursor] = ch; // advance the cursor m_edit.m_cursor++; // if the character was inserted at the end of the string, // make sure it remains NULL-terminated if (m_edit.m_cursor == len) { m_edit.m_buffer[m_edit.m_cursor] = 0; } if (m_cvarname.length()) { uii.Cvar_Set(m_cvarname, m_edit.m_buffer); } } void UIField::UpdateData(void) { if (m_cvarname.length()) { str::snprintf(m_edit.m_buffer, EDITFIELD_BUFSIZE, "%s", CvarGetForUI(m_cvarname, "")); m_edit.m_cursor = Q_min(m_edit.m_cursor, strlen(m_edit.m_buffer)); } } void UIField::Pressed(Event *ev) { float xpos = ev->GetFloat(1); float point = (xpos - m_frame.pos.x) / getVirtualScale()[0]; point = Q_max(point, 0.0f); // added in 2.15 int iStep = m_iPreStep; float fStringWidth = m_indent; while (char c = m_edit.m_buffer[iStep]) { iStep++; fStringWidth += m_font->getCharWidth(c); if (point <= fStringWidth) { if (iStep > 0) { iStep--; // put the cursor BEFORE the clicked character } break; } } m_edit.m_cursor = iStep; }