OSDN Git Service

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