OSDN Git Service

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