OSDN Git Service

Merge pull request #3475 from habu1010/feature/move-to-the-beginning-or-end-while...
[hengbandforosx/hengbandosx.git] / src / core / asking-player.cpp
1 #include "core/asking-player.h"
2 #include "cmd-io/macro-util.h"
3 #include "core/stuff-handler.h"
4 #include "core/window-redrawer.h"
5 #include "game-option/input-options.h"
6 #include "io/command-repeater.h"
7 #include "io/input-key-acceptor.h"
8 #include "io/input-key-requester.h" //!< @todo 相互依存している、後で何とかする.
9 #include "main/sound-of-music.h"
10 #include "system/player-type-definition.h"
11 #include "system/redrawing-flags-updater.h"
12 #include "term/gameterm.h"
13 #include "term/screen-processor.h"
14 #include "term/term-color-types.h"
15 #include "term/z-form.h"
16 #include "util/int-char-converter.h"
17 #include "util/string-processor.h"
18 #include "view/display-messages.h"
19 #include <algorithm>
20 #include <charconv>
21 #include <climits>
22 #include <iostream>
23 #include <sstream>
24 #include <stdexcept>
25 #include <string>
26
27 /*
28  * Get some string input at the cursor location.
29  * Assume the buffer is initialized to a default string.
30  *
31  * The default buffer is in Overwrite mode and displayed in yellow at
32  * first.  Normal chars clear the yellow text and append the char in
33  * white text.
34  *
35  * LEFT (^B) and RIGHT (^F) movement keys move the cursor position.
36  * If the text is still displayed in yellow (Overwite mode), it will
37  * turns into white (Insert mode) when cursor moves.
38  *
39  * DELETE (^D) deletes a char at the cursor position.
40  * BACKSPACE (^H) deletes a char at the left of cursor position.
41  * ESCAPE clears the buffer and the window and returns FALSE.
42  * RETURN accepts the current buffer contents and returns TRUE.
43  */
44 bool askfor(char *buf, int len, bool numpad_cursor)
45 {
46     /*
47      * Text color
48      * TERM_YELLOW : Overwrite mode
49      * TERM_WHITE : Insert mode
50      */
51     auto color = TERM_YELLOW;
52
53     int y, x;
54     term_locate(&x, &y);
55     if (len < 1) {
56         len = 1;
57     }
58
59     if ((x < 0) || (x >= MAIN_TERM_MIN_COLS)) {
60         x = 0;
61     }
62
63     if (x + len > MAIN_TERM_MIN_COLS) {
64         len = MAIN_TERM_MIN_COLS - x;
65     }
66
67     buf[len] = '\0';
68     auto pos = 0;
69     while (true) {
70         term_erase(x, y, len);
71         term_putstr(x, y, -1, color, buf);
72         term_gotoxy(x + pos, y);
73         const auto skey = inkey_special(numpad_cursor);
74         switch (skey) {
75         case SKEY_LEFT:
76         case KTRL('b'): {
77             auto i = 0;
78             color = TERM_WHITE;
79             if (0 == pos) {
80                 break;
81             }
82
83             while (true) {
84                 auto next_pos = i + 1;
85 #ifdef JP
86                 if (iskanji(buf[i])) {
87                     next_pos++;
88                 }
89 #endif
90                 if (next_pos >= pos) {
91                     break;
92                 }
93
94                 i = next_pos;
95             }
96
97             pos = i;
98             break;
99         }
100         case SKEY_RIGHT:
101         case KTRL('f'):
102             color = TERM_WHITE;
103             if ('\0' == buf[pos]) {
104                 break;
105             }
106
107 #ifdef JP
108             if (iskanji(buf[pos])) {
109                 pos += 2;
110             } else {
111                 pos++;
112             }
113 #else
114             pos++;
115 #endif
116             break;
117         case SKEY_TOP:
118         case KTRL('a'):
119             color = TERM_WHITE;
120             pos = 0;
121             break;
122         case SKEY_BOTTOM:
123         case KTRL('e'):
124             color = TERM_WHITE;
125             pos = std::string_view(buf).length();
126             break;
127         case ESCAPE:
128             buf[0] = '\0';
129             return false;
130         case '\n':
131         case '\r':
132             return true;
133         case '\010': {
134             auto i = 0;
135             color = TERM_WHITE;
136             if (pos == 0) {
137                 break;
138             }
139
140             while (true) {
141                 auto next_pos = i + 1;
142 #ifdef JP
143                 if (iskanji(buf[i])) {
144                     next_pos++;
145                 }
146 #endif
147                 if (next_pos >= pos) {
148                     break;
149                 }
150
151                 i = next_pos;
152             }
153
154             pos = i;
155         }
156             [[fallthrough]];
157         case 0x7F:
158         case KTRL('d'): {
159             color = TERM_WHITE;
160             if (buf[pos] == '\0') {
161                 break;
162             }
163
164             auto src = pos + 1;
165 #ifdef JP
166             if (iskanji(buf[pos])) {
167                 src++;
168             }
169 #endif
170             auto dst = pos;
171             while ('\0' != (buf[dst++] = buf[src++])) {
172                 ;
173             }
174
175             break;
176         }
177         default: {
178             char tmp[100];
179             if (skey & SKEY_MASK) {
180                 break;
181             }
182
183             const auto c = static_cast<char>(skey);
184             if (color == TERM_YELLOW) {
185                 buf[0] = '\0';
186                 color = TERM_WHITE;
187             }
188
189             strcpy(tmp, buf + pos);
190 #ifdef JP
191             if (iskanji(c)) {
192                 inkey_base = true;
193                 char next = inkey();
194                 if (pos + 1 < len) {
195                     buf[pos++] = c;
196                     buf[pos++] = next;
197                 } else {
198                     bell();
199                 }
200             } else
201 #endif
202             {
203                 const auto is_print = _(isprint(c) || iskana(c), isprint(c));
204                 if (pos < len && is_print) {
205                     buf[pos++] = c;
206                 } else {
207                     bell();
208                 }
209             }
210
211             buf[pos] = '\0';
212             angband_strcat(buf, tmp, len + 1);
213             break;
214         }
215         }
216     }
217 }
218
219 /*
220  * Get a string from the user
221  *
222  * The "prompt" should take the form "Prompt: "
223  *
224  * Note that the initial contents of the string is used as
225  * the default response, so be sure to "clear" it if needed.
226  *
227  * We clear the input, and return FALSE, on "ESCAPE".
228  */
229 bool get_string(std::string_view prompt, char *buf, int len)
230 {
231     bool res;
232     msg_print(nullptr);
233     prt(prompt, 0, 0);
234     res = askfor(buf, len);
235     prt("", 0, 0);
236     return res;
237 }
238
239 /*
240  * Verify something with the user
241  *
242  * The "prompt" should take the form "Query? "
243  *
244  * Note that "[y/n]" is appended to the prompt.
245  */
246 bool get_check(std::string_view prompt)
247 {
248     return get_check_strict(p_ptr, prompt, 0);
249 }
250
251 /*
252  * Verify something with the user strictly
253  *
254  * mode & CHECK_OKAY_CANCEL : force user to answer 'O'kay or 'C'ancel
255  * mode & CHECK_NO_ESCAPE   : don't allow ESCAPE key
256  * mode & CHECK_NO_HISTORY  : no message_add
257  * mode & CHECK_DEFAULT_Y   : accept any key as y, except n and Esc.
258  */
259 bool get_check_strict(PlayerType *player_ptr, std::string_view prompt, BIT_FLAGS mode)
260 {
261     if (!rogue_like_commands) {
262         mode &= ~CHECK_OKAY_CANCEL;
263     }
264
265     std::stringstream ss;
266     ss << prompt;
267     if (mode & CHECK_OKAY_CANCEL) {
268         ss << "[(O)k/(C)ancel]";
269     } else if (mode & CHECK_DEFAULT_Y) {
270         ss << "[Y/n]";
271     } else {
272         ss << "[y/n]";
273     }
274     const auto buf = ss.str();
275
276     auto &rfu = RedrawingFlagsUpdater::get_instance();
277     if (auto_more) {
278         rfu.set_flag(SubWindowRedrawingFlag::MESSAGE);
279         handle_stuff(player_ptr);
280         num_more = 0;
281     }
282
283     msg_print(nullptr);
284
285     prt(buf, 0, 0);
286     if (!(mode & CHECK_NO_HISTORY) && player_ptr->playing) {
287         message_add(buf);
288         rfu.set_flag(SubWindowRedrawingFlag::MESSAGE);
289         handle_stuff(player_ptr);
290     }
291
292     bool flag = false;
293     while (true) {
294         int i = inkey();
295
296         if (!(mode & CHECK_NO_ESCAPE)) {
297             if (i == ESCAPE) {
298                 flag = false;
299                 break;
300             }
301         }
302
303         if (mode & CHECK_OKAY_CANCEL) {
304             if (i == 'o' || i == 'O') {
305                 flag = true;
306                 break;
307             } else if (i == 'c' || i == 'C') {
308                 flag = false;
309                 break;
310             }
311         } else {
312             if (i == 'y' || i == 'Y') {
313                 flag = true;
314                 break;
315             } else if (i == 'n' || i == 'N') {
316                 flag = false;
317                 break;
318             }
319         }
320
321         if (mode & CHECK_DEFAULT_Y) {
322             flag = true;
323             break;
324         }
325
326         bell();
327     }
328
329     prt("", 0, 0);
330     return flag;
331 }
332
333 /*
334  * Prompts for a keypress
335  *
336  * The "prompt" should take the form "Command: "
337  *
338  * Returns TRUE unless the character is "Escape"
339  */
340 bool get_com(std::string_view prompt, char *command, bool z_escape)
341 {
342     msg_print(nullptr);
343     prt(prompt, 0, 0);
344     if (get_com_no_macros) {
345         *command = (char)inkey_special(false);
346     } else {
347         *command = inkey();
348     }
349
350     prt("", 0, 0);
351     if (*command == ESCAPE) {
352         return false;
353     }
354     if (z_escape && ((*command == 'z') || (*command == 'Z'))) {
355         return false;
356     }
357
358     return true;
359 }
360
361 /*
362  * Request a "quantity" from the user
363  *
364  * Hack -- allow "command_arg" to specify a quantity
365  */
366 QUANTITY get_quantity(std::optional<std::string_view> prompt_opt, QUANTITY max)
367 {
368     /*!
369      * @todo QUANTITY、COMMAND_CODE、その他の型サイズがまちまちな変数とのやり取りが多数ある.
370      * この処理での数の入力を0からSHRT_MAXに制限することで不整合の発生を回避する.
371      */
372     max = std::clamp<QUANTITY>(max, 0, SHRT_MAX);
373
374     bool res;
375     char tmp[80];
376     char buf[80];
377
378     QUANTITY amt;
379     if (command_arg) {
380         amt = command_arg;
381         command_arg = 0;
382         if (amt > max) {
383             amt = max;
384         }
385
386         return amt;
387     }
388
389     COMMAND_CODE code;
390     bool result = repeat_pull(&code);
391     amt = (QUANTITY)code;
392     if ((max != 1) && result) {
393         if (amt > max) {
394             amt = max;
395         }
396         if (amt < 0) {
397             amt = 0;
398         }
399
400         return amt;
401     }
402
403     std::string_view prompt;
404     if (prompt_opt.has_value()) {
405         prompt = prompt_opt.value();
406     } else {
407         strnfmt(tmp, sizeof(tmp), _("いくつですか (1-%d): ", "Quantity (1-%d): "), max);
408         prompt = tmp;
409     }
410
411     msg_print(nullptr);
412     prt(prompt, 0, 0);
413     amt = 1;
414     strnfmt(buf, sizeof(buf), "%d", amt);
415
416     /*
417      * Ask for a quantity
418      * Don't allow to use numpad as cursor key.
419      */
420     res = askfor(buf, 6, false);
421
422     prt("", 0, 0);
423     if (!res) {
424         return 0;
425     }
426
427     if (isalpha(buf[0])) {
428         amt = max;
429     } else {
430         amt = std::clamp<int>(atoi(buf), 0, max);
431     }
432
433     if (amt) {
434         repeat_push((COMMAND_CODE)amt);
435     }
436
437     return amt;
438 }
439
440 /*
441  * Pause for user response
442  */
443 void pause_line(int row)
444 {
445     prt("", row, 0);
446     put_str(_("[ 何かキーを押して下さい ]", "[Press any key to continue]"), row, _(26, 23));
447
448     (void)inkey();
449     prt("", row, 0);
450 }
451
452 std::optional<int> input_integer(std::string_view prompt, int min, int max, int initial_value)
453 {
454     std::stringstream ss;
455     char tmp_val[12] = "";
456     std::to_chars(std::begin(tmp_val), std::end(tmp_val) - 1, initial_value);
457     ss << prompt << "(" << min << "-" << max << "): ";
458     auto digit = std::max(std::to_string(min).length(), std::to_string(max).length());
459     while (true) {
460         if (!get_string(ss.str().data(), tmp_val, digit)) {
461             return std::nullopt;
462         }
463
464         try {
465             auto val = std::stoi(tmp_val);
466             if ((val < min) || (val > max)) {
467                 msg_format(_("%dから%dの間で指定して下さい。", "It must be between %d to %d."), min, max);
468                 continue;
469             }
470
471             return val;
472         } catch (std::invalid_argument const &) {
473             msg_print(_("数値を入力して下さい。", "Please input numeric value."));
474             continue;
475         } catch (std::out_of_range const &) {
476             msg_print(_("入力可能な数値の範囲を超えています。", "Input value overflows the maximum number."));
477             continue;
478         }
479     }
480 }