FreeRDP
Loading...
Searching...
No Matches
sdl_input_widget_pair_list.cpp
1
20#include <cassert>
21#include <algorithm>
22#include <optional>
23
24#include <winpr/cast.h>
25
26#include "../sdl_utils.hpp"
27#include "sdl_widget_list.hpp"
28#include "sdl_input_widget_pair_list.hpp"
29
30static const Uint32 vpadding = 5;
31
32SdlInputWidgetPairList::SdlInputWidgetPairList(const std::string& title,
33 const std::vector<std::string>& labels,
34 const std::vector<std::string>& initial,
35 const std::vector<Uint32>& flags, ssize_t selected)
36{
37 assert(labels.size() == initial.size());
38 assert(labels.size() == flags.size());
39 const std::vector<int> buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL };
40 const std::vector<std::string> buttonlabels = { "accept", "cancel" };
41
42 const size_t widget_width = 300;
43 const size_t widget_height = 50;
44
45 const size_t total_width = widget_width + widget_width;
46 const size_t input_height = labels.size() * (widget_height + vpadding) + vpadding;
47 const size_t total_height = input_height + widget_height;
48 assert(total_width <= INT32_MAX);
49 assert(total_height <= INT32_MAX);
50
51 if (reset(title, total_width, total_height))
52 {
53 for (size_t x = 0; x < labels.size(); x++)
54 {
55 auto widget =
56 std::make_shared<SdlInputWidgetPair>(_renderer, labels.at(x), initial.at(x),
57 flags.at(x), x, widget_width, widget_height);
58 m_list.emplace_back(widget);
59 }
60
61 std::ignore = _buttons.populate(
62 _renderer, buttonlabels, buttonids, total_width, static_cast<Sint32>(input_height),
63 static_cast<Sint32>(widget_width), static_cast<Sint32>(widget_height));
64 _buttons.set_highlight(0);
65 m_currentActiveTextInput = selected;
66 }
67}
68
69ssize_t SdlInputWidgetPairList::next(ssize_t current)
70{
71 size_t iteration = 0;
72 auto val = static_cast<size_t>(current);
73
74 do
75 {
76 if (iteration >= m_list.size())
77 return -1;
78
79 if (iteration == 0)
80 {
81 if (current < 0)
82 val = 0;
83 else
84 val++;
85 }
86 else
87 val++;
88 iteration++;
89 val %= m_list.size();
90 } while (!valid(static_cast<ssize_t>(val)));
91 return static_cast<ssize_t>(val);
92}
93
94bool SdlInputWidgetPairList::valid(ssize_t current) const
95{
96 if (current < 0)
97 return false;
98 auto s = static_cast<size_t>(current);
99 if (s >= m_list.size())
100 return false;
101 return !m_list.at(s)->readonly();
102}
103
104std::shared_ptr<SdlInputWidgetPair> SdlInputWidgetPairList::get(ssize_t index)
105{
106 if (index < 0)
107 return nullptr;
108 auto s = static_cast<size_t>(index);
109 if (s >= m_list.size())
110 return nullptr;
111 return m_list.at(s);
112}
113
114SdlInputWidgetPairList::~SdlInputWidgetPairList()
115{
116 m_list.clear();
117 _buttons.clear();
118}
119
120bool SdlInputWidgetPairList::updateInternal()
121{
122 for (auto& btn : m_list)
123 {
124 if (!btn->update())
125 return false;
126 }
127
128 return true;
129}
130
131ssize_t SdlInputWidgetPairList::get_index(const SDL_MouseButtonEvent& button)
132{
133 const auto x = button.x;
134 const auto y = button.y;
135 for (size_t i = 0; i < m_list.size(); i++)
136 {
137 auto& cur = m_list.at(i);
138 auto r = cur->input_rect();
139
140 if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h))
141 return WINPR_ASSERTING_INT_CAST(ssize_t, i);
142 }
143 return -1;
144}
145
146int SdlInputWidgetPairList::run(std::vector<std::string>& result)
147{
148 int res = -1;
149 ssize_t LastActiveTextInput = -1;
150 m_currentActiveTextInput = next(m_currentActiveTextInput);
151
152 if (!_window || !_renderer)
153 return -2;
154
155 if (!SDL_StartTextInput(_window.get()))
156 return -3;
157
158 std::optional<SDL_Event> forward;
159 try
160 {
161 bool running = true;
162 while (running)
163 {
164 if (!update())
165 throw std::exception();
166
167 SDL_Event event = {};
168 if (!SDL_WaitEventTimeout(&event, 30))
169 continue;
170 do
171 {
172 switch (event.type)
173 {
174 case SDL_EVENT_KEY_UP:
175 {
176 switch (event.key.key)
177 {
178 case SDLK_BACKSPACE:
179 {
180 auto cur = get(m_currentActiveTextInput);
181 if (cur)
182 {
183 if ((event.key.mod & SDL_KMOD_CTRL) != 0)
184 {
185 if (!cur->set_str(""))
186 throw std::exception();
187 }
188 else
189 {
190 if (!cur->remove_str(1))
191 throw std::exception();
192 }
193 }
194 }
195 break;
196 case SDLK_TAB:
197 m_currentActiveTextInput = next(m_currentActiveTextInput);
198 break;
199 case SDLK_RETURN:
200 case SDLK_RETURN2:
201 case SDLK_KP_ENTER:
202 running = false;
203 res = INPUT_BUTTON_ACCEPT;
204 break;
205 case SDLK_ESCAPE:
206 running = false;
207 res = INPUT_BUTTON_CANCEL;
208 break;
209 case SDLK_V:
210 if ((event.key.mod & SDL_KMOD_CTRL) != 0)
211 {
212 auto cur = get(m_currentActiveTextInput);
213 if (cur)
214 {
215 auto text = SDL_GetClipboardText();
216 if (!cur->set_str(text))
217 throw std::exception();
218 }
219 }
220 break;
221 default:
222 break;
223 }
224 }
225 break;
226 case SDL_EVENT_TEXT_INPUT:
227 {
228 auto cur = get(m_currentActiveTextInput);
229 if (cur)
230 {
231 if (!cur->append_str(event.text.text))
232 throw std::exception();
233 }
234 }
235 break;
236 case SDL_EVENT_MOUSE_MOTION:
237 {
238 auto TextInputIndex = get_index(event.button);
239 for (auto& cur : m_list)
240 {
241 cur->set_mouseover(false);
242 }
243 if (TextInputIndex >= 0)
244 {
245 auto& cur = m_list.at(static_cast<size_t>(TextInputIndex));
246 cur->set_mouseover(true);
247 }
248
249 _buttons.set_mouseover(event.button.x, event.button.y);
250 }
251 break;
252 case SDL_EVENT_MOUSE_BUTTON_DOWN:
253 {
254 auto val = get_index(event.button);
255 if (valid(val))
256 m_currentActiveTextInput = val;
257
258 auto button = _buttons.get_selected(event.button);
259 if (button)
260 {
261 running = false;
262 if (button->id() == INPUT_BUTTON_CANCEL)
263 res = INPUT_BUTTON_CANCEL;
264 else
265 res = INPUT_BUTTON_ACCEPT;
266 }
267 }
268 break;
269 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
270 /* Closing any window cancels the dialog. The event of another
271 * window is queued again once the dialog is done, so the main
272 * loop still handles it. */
273 if (event.window.windowID != SDL_GetWindowID(_window.get()))
274 forward = event;
275 res = INPUT_BUTTON_CANCEL;
276 running = false;
277 break;
278 case SDL_EVENT_WINDOW_OCCLUDED:
279 case SDL_EVENT_WINDOW_MINIMIZED:
280 case SDL_EVENT_WINDOW_DESTROYED:
281 /* Only events of our own window cancel the dialog. Hiding the
282 * connection dialog for this prompt destroys its window, and that
283 * event is still queued when this loop starts. */
284 if (event.window.windowID != SDL_GetWindowID(_window.get()))
285 break;
286 res = INPUT_BUTTON_CANCEL;
287 running = false;
288 break;
289 case SDL_EVENT_TERMINATING:
290 case SDL_EVENT_QUIT:
291 res = INPUT_BUTTON_CANCEL;
292 running = false;
293 break;
294 default:
295 break;
296 }
297 } while (running && SDL_PollEvent(&event));
298
299 if (LastActiveTextInput != m_currentActiveTextInput)
300 {
301 LastActiveTextInput = m_currentActiveTextInput;
302 }
303
304 for (auto& cur : m_list)
305 {
306 if (!cur->set_highlight(false))
307 throw std::exception();
308 }
309 auto cur = get(m_currentActiveTextInput);
310 if (cur)
311 {
312 if (!cur->set_highlight(true))
313 throw std::exception();
314 }
315 }
316
317 for (auto& cur : m_list)
318 result.push_back(cur->value());
319 }
320 catch (...)
321 {
322 res = -2;
323 }
324
325 /* Drop the input that was meant for the dialog, so it does not reach the session.
326 * Other events, such as window and clipboard events, are still handled. */
327 SDL_PumpEvents();
328 SDL_FlushEvents(SDL_EVENT_KEY_DOWN, SDL_EVENT_MOUSE_REMOVED);
329 SDL_FlushEvents(SDL_EVENT_FINGER_DOWN, SDL_EVENT_FINGER_CANCELED);
330 SDL_FlushEvents(SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_AXIS);
331 if (forward)
332 std::ignore = SDL_PushEvent(&*forward);
333
334 if (!SDL_StopTextInput(_window.get()))
335 return -4;
336
337 return res;
338}
339
340void SdlInputWidgetPairList::parent(SDL_Window* parent)
341{
342 if (!parent)
343 return;
344 SDL_SetWindowParent(_window.get(), parent);
345 SDL_SetWindowModal(_window.get(), true);
346}