File: uifield.cpp

package info (click to toggle)
openmohaa 0.81.1%2Bdfsg-2
  • links: PTS, VCS
  • area: contrib
  • in suites: trixie
  • size: 29,124 kB
  • sloc: ansic: 270,865; cpp: 250,173; sh: 234; asm: 141; xml: 64; makefile: 7
file content (326 lines) | stat: -rw-r--r-- 10,187 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/*
===========================================================================
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;
}