• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 12600250642

03 Jan 2025 03:50PM UTC coverage: 50.212% (-0.01%) from 50.224%
12600250642

Pull #1727

github

web-flow
Merge ebc01d500 into 938c7f23e
Pull Request #1727: Support non-default libc

0 of 4 new or added lines in 1 file covered. (0.0%)

9 existing lines in 3 files now uncovered.

22305 of 44422 relevant lines covered (50.21%)

36706.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.18
/libs/s25main/controls/ctrlEdit.cpp
1
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "ctrlEdit.h"
6
#include "CollisionDetection.h"
7
#include "ctrlTextDeepening.h"
8
#include "driver/MouseCoords.h"
9
#include "drivers/VideoDriverWrapper.h"
10
#include "helpers/containerUtils.h"
11
#include "ogl/FontStyle.h"
12
#include "ogl/glFont.h"
13
#include "s25util/StringConversion.h"
14
#include <s25util/utf8.h>
15
#include <boost/nowide/detail/utf.hpp>
16
#include <numeric>
17

18
ctrlEdit::ctrlEdit(Window* parent, unsigned id, const DrawPoint& pos, const Extent& size, TextureColor tc,
6✔
19
                   const glFont* font, unsigned short maxlength, bool password, bool disabled, bool notify)
6✔
20
    : Window(parent, id, pos, size), maxLength_(maxlength), isPassword_(password), isDisabled_(disabled),
21
      notify_(notify)
6✔
22
{
23
    txtCtrl = static_cast<ctrlTextDeepening*>(
6✔
24
      AddTextDeepening(0, DrawPoint(0, 0), size, tc, "", font, COLOR_YELLOW, FontStyle::LEFT | FontStyle::VCENTER));
6✔
25
    UpdateInternalText();
6✔
26
}
6✔
27

28
/**
29
 *  setzt den Text.
30
 *
31
 *  @param[in] text Der Text.
32
 */
33
void ctrlEdit::SetText(const std::string& text)
35✔
34
{
35
    text_ = s25util::utf8to32(text);
35✔
36
    if(numberOnly_)
35✔
37
        helpers::erase_if(text_, [](char32_t c) { return !(c >= '0' && c <= '9'); });
×
38
    if(maxLength_ > 0 && text_.size() > maxLength_)
35✔
39
        text_.resize(maxLength_);
×
40

41
    viewStart_ = 0;
35✔
42
    cursorPos_ = text_.length();
35✔
43
    UpdateInternalText();
35✔
44
    Notify();
35✔
45
}
35✔
46

47
void ctrlEdit::SetText(const unsigned text)
1✔
48
{
49
    SetText(s25util::toStringClassic(text));
1✔
50
}
1✔
51

52
std::string ctrlEdit::GetText() const
38✔
53
{
54
    return s25util::utf32to8(text_);
38✔
55
}
56

57
void ctrlEdit::SetFocus(bool focus)
38✔
58
{
59
    if(focus_ != focus)
38✔
60
    {
61
        focus_ = focus;
6✔
62
        txtCtrl->SetTextColor(focus_ ? 0xFFFFA000 : COLOR_YELLOW);
6✔
63
        if(focus && GetParent())
6✔
64
        {
65
            for(auto* edit : GetParent()->GetCtrls<ctrlEdit>())
10✔
66
            {
67
                if(edit != this)
7✔
68
                    edit->SetFocus(false);
4✔
69
            }
70
        }
71
    }
72
}
38✔
73

74
static void removeFirstCharFromString(std::string& str)
573✔
75
{
76
    auto it = str.begin();
573✔
77
    boost::nowide::detail::utf::utf_traits<char>::decode_valid(it);
573✔
78
    str.erase(str.begin(), it);
573✔
79
}
573✔
80

81
void ctrlEdit::UpdateInternalText()
309✔
82
{
83
    if(text_.empty())
309✔
84
    {
85
        viewStart_ = 0;
8✔
86
        cursorPos_ = 0;
8✔
87
        cursorOffsetX_ = 0;
8✔
88
        txtCtrl->SetText("");
8✔
89
    } else
90
    {
91
        std::u32string dtext = (isPassword_) ? std::u32string(text_.size(), '*') : text_;
602✔
92
        viewStart_ = std::min<unsigned>(viewStart_, dtext.length() - 1);
301✔
93
        const auto* font = txtCtrl->GetFont();
301✔
94
        const unsigned max_width = GetSize().x - ctrlTextDeepening::borderSize.x * 2;
301✔
95
        unsigned max;
96
        std::string curText = s25util::utf32to8(dtext.substr(viewStart_));
602✔
97
        font->getWidth(curText, max_width, &max);
301✔
98
        // Add chars at front as long as full text is shown
99
        while(viewStart_ > 0 && curText.length() <= max)
387✔
100
        {
101
            --viewStart_;
86✔
102
            curText = s25util::utf32to8(dtext.substr(viewStart_));
86✔
103
            font->getWidth(curText, max_width, &max);
86✔
104
        }
105
        // Remove chars from front until remaining string can be fully shown
106
        while(curText.length() > max)
874✔
107
        {
108
            ++viewStart_;
573✔
109
            removeFirstCharFromString(curText);
573✔
110
            font->getWidth(curText, max_width, &max);
573✔
111
        }
112

113
        // Show (up to) 5 chars before the cursor
114
        if(viewStart_ + 5 > cursorPos_)
301✔
115
        {
116
            viewStart_ = std::max(0, static_cast<int>(cursorPos_) - 5);
130✔
117
            curText = s25util::utf32to8(dtext.substr(viewStart_));
130✔
118
        }
119
        if(cursorPos_ > viewStart_)
301✔
120
            cursorOffsetX_ = font->getWidth(s25util::utf32to8(dtext.substr(viewStart_, cursorPos_ - viewStart_)));
293✔
121
        else
122
            cursorOffsetX_ = 0;
8✔
123
        txtCtrl->SetText(curText);
301✔
124
    }
125
}
309✔
126

127
inline void ctrlEdit::CursorLeft()
138✔
128
{
129
    if(cursorPos_ == 0)
138✔
130
        return;
4✔
131
    --cursorPos_;
134✔
132
    UpdateInternalText();
134✔
133
}
134

135
inline void ctrlEdit::CursorRight()
134✔
136
{
137
    if(cursorPos_ == text_.length())
134✔
UNCOV
138
        return;
×
139
    ++cursorPos_;
134✔
140
    UpdateInternalText();
134✔
141
}
142

143
/**
144
 *  zeichnet das Fenster.
145
 *
146
 *  @todo muss alles überarbeitet werden
147
 */
148
void ctrlEdit::Draw_()
1✔
149
{
150
    Window::Draw_();
1✔
151
    // Alle 500ms Cursor für 500ms anzeigen
152
    if(focus_ && !isDisabled_ && VIDEODRIVER.GetTickCount() % 1000 < 500)
1✔
153
    {
154
        const auto fontHeight = txtCtrl->GetFont()->getHeight();
1✔
155
        DrawPoint cursorDrawPos = GetDrawPos();
1✔
156
        cursorDrawPos.x += cursorOffsetX_ + ctrlTextDeepening::borderSize.x;
1✔
157
        cursorDrawPos.y += (GetSize().y - (fontHeight + 2)) / 2;
1✔
158

159
        DrawRectangle(Rect(cursorDrawPos, Extent(1, fontHeight + 2)), 0xFFFFA000);
1✔
160
    }
161
}
1✔
162

163
/**
164
 *  fügt ein Zeichen zum Text hinzu
165
 *
166
 *  @param[in] text Das Zeichen
167
 */
168
void ctrlEdit::AddChar(char32_t c)
30✔
169
{
170
    // Number-only text fields accept numbers only ;)
171
    if(numberOnly_ && !(c >= '0' && c <= '9'))
30✔
172
        return;
×
173

174
    if(maxLength_ > 0 && text_.size() >= maxLength_)
30✔
175
        return;
×
176

177
    text_.insert(cursorPos_, 1, c);
30✔
178
    CursorRight();
30✔
179
    Notify();
30✔
180
}
181

182
/**
183
 *  entfernt ein Zeichen
184
 */
185
void ctrlEdit::RemoveChar()
35✔
186
{
187
    if(cursorPos_ > 0 && text_.length() > 0)
35✔
188
    {
189
        text_.erase(cursorPos_ - 1, 1);
30✔
190

191
        // View verschieben
192
        while(text_.length() > 0 && text_.length() <= viewStart_)
30✔
193
            --viewStart_;
×
194

195
        CursorLeft();
30✔
196
        Notify();
30✔
197
    }
198
}
35✔
199

200
/**
201
 *  benachrichtigt das Parent ("OnChange")
202
 */
203
void ctrlEdit::Notify()
95✔
204
{
205
    if(!notify_ || !GetParent())
95✔
206
        return;
91✔
207

208
    GetParent()->Msg_EditChange(GetID());
4✔
209
}
210

211
void ctrlEdit::Resize(const Extent& newSize)
×
212
{
213
    Window::Resize(newSize);
×
214
    UpdateInternalText();
×
215
}
×
216

217
/**
218
 *  Maustaste-gedrückt Callback
219
 */
220
bool ctrlEdit::Msg_LeftDown(const MouseCoords& mc)
31✔
221
{
222
    SetFocus(IsPointInRect(mc.GetPos(), GetDrawRect()));
31✔
223
    return false; // "Unhandled" so other edits can handle this too and set their focus accordingly
31✔
224
}
225

226
/**
227
 *  Taste-gedrückt Callback
228
 */
229
bool ctrlEdit::Msg_KeyDown(const KeyEvent& ke)
277✔
230
{
231
    auto isDelimiter = [](char32_t c) {
×
232
        for(const auto cur : U" \t\n-+=")
×
233
        {
234
            if(cur == c)
×
235
                return true;
×
236
        }
237
        return false;
×
238
    };
239

240
    // hat das Steuerelement den Fokus?
241
    if(!focus_)
277✔
242
        return false;
×
243

244
    switch(ke.kt)
277✔
245
    {
246
        default: return false;
×
247
        // Wird bereits über Char geliefert !!
248
        case KeyType::Space: // Leertaste
×
249
            AddChar(0x20);
×
250
            break;
×
251

252
        case KeyType::Left: // Cursor nach Links
108✔
253
            // Blockweise nach links, falls Strg gedrückt
254
            if(ke.ctrl)
108✔
255
            {
256
                // Erst über alle Trennzeichen hinweg
257
                while(cursorPos_ > 0 && isDelimiter(text_[cursorPos_ - 1]))
×
258
                {
259
                    --cursorPos_;
×
260
                }
261

262
                // Und dann über alles, was kein Trenner ist
263
                while(cursorPos_ > 0 && !isDelimiter(text_[cursorPos_ - 1]))
×
264
                {
265
                    --cursorPos_;
×
266
                }
267
                UpdateInternalText();
×
268
            } else
269
                CursorLeft(); // just one step
108✔
270
            break;
108✔
271

272
        case KeyType::Right: // Cursor nach Rechts
104✔
273
            // Blockweise nach rechts, falls Strg gedrückt
274
            if(ke.ctrl)
104✔
275
            {
276
                // Erst über alle Trennzeichen hinweg
277
                while(cursorPos_ + 1 < text_.length() && isDelimiter(text_[cursorPos_ + 1]))
×
278
                {
279
                    ++cursorPos_;
×
280
                }
281
                // Und dann über alles, was kein Trenner ist
282
                while(cursorPos_ + 1 < text_.length() && !isDelimiter(text_[cursorPos_ + 1]))
×
283
                {
284
                    ++cursorPos_;
×
285
                }
286
                UpdateInternalText();
×
287
            } else
288
                CursorRight(); // just one step
104✔
289
            break;
104✔
290

291
        case KeyType::Char: // Zeichen eingegeben
30✔
292
            if(!isDisabled_ && txtCtrl->GetFont()->CharExist(ke.c))
30✔
293
                AddChar(ke.c);
30✔
294
            break;
30✔
295

296
        case KeyType::Backspace: // Backspace gedrückt
35✔
297
            if(!isDisabled_)
35✔
298
                RemoveChar();
35✔
299
            break;
35✔
300

301
        case KeyType::Delete: // Entfernen gedrückt
×
302
            if(!isDisabled_ && cursorPos_ < text_.length())
×
303
            {
304
                CursorRight();
×
305
                RemoveChar();
×
306
            }
307
            break;
×
308

309
        case KeyType::Return: // Enter gedrückt
×
310
            if(!isDisabled_ && GetParent())
×
311
                GetParent()->Msg_EditEnter(GetID());
×
312
            break;
×
313

314
        case KeyType::Home: // Pos1 gedrückt
×
315
            if(cursorPos_ > 0)
×
316
            {
317
                cursorPos_ = 0;
×
318
                UpdateInternalText();
×
319
            }
320
            break;
×
321

322
        case KeyType::End: // Ende gedrückt
×
323
            if(cursorPos_ < text_.length())
×
324
            {
325
                cursorPos_ = text_.length();
×
326
                UpdateInternalText();
×
327
            }
328
            break;
×
329
    }
330

331
    return true;
277✔
332
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc