OSDN Git Service

[Refactor] #3406 input_value() をテンプレート化してキャストを減らした
[hengbandforosx/hengbandosx.git] / src / core / show-file.cpp
1 #include "core/show-file.h"
2 #include "core/asking-player.h"
3 #include "io/files-util.h"
4 #include "io/input-key-acceptor.h"
5 #include "main/sound-of-music.h"
6 #include "system/angband-exceptions.h"
7 #include "system/angband-version.h"
8 #include "system/player-type-definition.h"
9 #include "term/gameterm.h"
10 #include "term/screen-processor.h"
11 #include "term/term-color-types.h"
12 #include "term/z-form.h"
13 #include "util/angband-files.h"
14 #include "util/int-char-converter.h"
15 #include "util/string-processor.h"
16 #include "view/display-messages.h"
17 #include <sstream>
18 #include <string>
19
20 /*!
21  * @brief ファイル内容の一行をコンソールに出力する
22  * Display single line of on-line help file
23  * @param str 出力する文字列
24  * @param cy コンソールの行
25  * @param shower 確認中
26  * @details
27  * <pre>
28  * You can insert some special color tag to change text color.
29  * Such as...
30  * WHITETEXT [[[[y|SOME TEXT WHICH IS DISPLAYED IN YELLOW| WHITETEXT
31  * A colored segment is between "[[[[y|" and the last "|".
32  * You can use any single character in place of the "|".
33  * </pre>
34  * @todo 表示とそれ以外を分割する
35  */
36 static void show_file_aux_line(concptr str, int cy, concptr shower)
37 {
38     char lcstr[1024];
39     concptr ptr;
40     byte textcolor = TERM_WHITE;
41     byte focuscolor = TERM_YELLOW;
42
43     if (shower) {
44         strcpy(lcstr, str);
45         str_tolower(lcstr);
46
47         ptr = angband_strstr(lcstr, shower);
48         textcolor = (ptr == nullptr) ? TERM_L_DARK : TERM_WHITE;
49     }
50
51     int cx = 0;
52     term_gotoxy(cx, cy);
53
54     static const char tag_str[] = "[[[[";
55     byte color = textcolor;
56     char in_tag = '\0';
57     for (int i = 0; str[i];) {
58         int len = strlen(&str[i]);
59         int showercol = len + 1;
60         int bracketcol = len + 1;
61         int endcol = len;
62         if (shower) {
63             ptr = angband_strstr(&lcstr[i], shower);
64             if (ptr) {
65                 showercol = ptr - &lcstr[i];
66             }
67         }
68
69         ptr = in_tag ? angband_strchr(&str[i], in_tag) : angband_strstr(&str[i], tag_str);
70         if (ptr) {
71             bracketcol = ptr - &str[i];
72         }
73         if (bracketcol < endcol) {
74             endcol = bracketcol;
75         }
76         if (showercol < endcol) {
77             endcol = showercol;
78         }
79
80         term_addstr(endcol, color, &str[i]);
81         cx += endcol;
82         i += endcol;
83
84         if (shower && endcol == showercol) {
85             int showerlen = strlen(shower);
86             term_addstr(showerlen, focuscolor, &str[i]);
87             cx += showerlen;
88             i += showerlen;
89             continue;
90         }
91
92         if (endcol != bracketcol) {
93             continue;
94         }
95
96         if (in_tag) {
97             i++;
98             in_tag = '\0';
99             color = textcolor;
100             continue;
101         }
102
103         i += sizeof(tag_str) - 1;
104         color = color_char_to_attr(str[i]);
105         if (color == 255 || str[i + 1] == '\0') {
106             color = textcolor;
107             term_addstr(-1, color, tag_str);
108             cx += sizeof(tag_str) - 1;
109             continue;
110         }
111
112         i++;
113         in_tag = str[i];
114         i++;
115     }
116
117     term_erase(cx, cy, 255);
118 }
119
120 /*!
121  * @brief ファイル内容をコンソールに出力する
122  * Recursive file perusal.
123  * @param player_ptr プレイヤーへの参照ポインタ
124  * @param show_version TRUEならばコンソール上にゲームのバージョンを表示する
125  * @param name ファイル名の文字列
126  * @param what 内容キャプションの文字列
127  * @param line 表示の現在行
128  * @param mode オプション
129  * @details
130  * <pre>
131  * Process various special text in the input file, including
132  * the "menu" structures used by the "help file" system.
133  * Return FALSE on 'q' to exit from a deep, otherwise TRUE.
134  * </pre>
135  * @todo 表示とそれ以外を分割する
136  */
137 bool show_file(PlayerType *player_ptr, bool show_version, std::string_view name_with_tag, int initial_line, uint32_t mode, std::string_view what)
138 {
139     TermCenteredOffsetSetter tcos(MAIN_TERM_MIN_COLS, std::nullopt);
140
141     int wid, hgt;
142     term_get_size(&wid, &hgt);
143
144     char finder_str[81] = "";
145     char shower_str[81] = "";
146     char hook[68][32]{};
147     auto stripped_names = str_split(name_with_tag, '#');
148     auto &name = stripped_names[0];
149     auto tag = stripped_names.size() > 1 ? stripped_names[1] : "";
150     std::filesystem::path path_reopen("");
151     FILE *fff = nullptr;
152     std::stringstream caption;
153     if (!what.empty()) {
154         caption << what;
155         path_reopen = name;
156         fff = angband_fopen(path_reopen, FileOpenMode::READ);
157     }
158
159     if (!fff) {
160         caption.clear();
161         caption << _("ヘルプ・ファイル'", "Help file '");
162         caption << name << "'";
163         path_reopen = path_build(ANGBAND_DIR_HELP, name);
164         fff = angband_fopen(path_reopen, FileOpenMode::READ);
165     }
166
167     if (!fff) {
168         caption.clear();
169         caption << _("スポイラー・ファイル'", "Info file '");
170         caption << name << "'";
171         path_reopen = path_build(ANGBAND_DIR_INFO, name);
172         fff = angband_fopen(path_reopen, FileOpenMode::READ);
173     }
174
175     if (!fff) {
176         caption.clear();
177         path_reopen = path_build(ANGBAND_DIR, name);
178         caption << _("スポイラー・ファイル'", "Info file '");
179         caption << name << "'";
180         fff = angband_fopen(path_reopen, FileOpenMode::READ);
181     }
182
183     const auto open_error_mes = format(_("'%s'をオープンできません。", "Cannot open '%s'."), name.data());
184     if (!fff) {
185         THROW_EXCEPTION(std::runtime_error, open_error_mes);
186     }
187
188     const auto caption_str = caption.str();
189     int skey;
190     auto next = 0;
191     auto back = 0;
192     auto menu = false;
193     char buf[1024]{};
194     auto reverse = initial_line < 0;
195     auto line = initial_line;
196     while (true) {
197         char *str = buf;
198         if (angband_fgets(fff, buf, sizeof(buf))) {
199             break;
200         }
201         if (!prefix(str, "***** ")) {
202             next++;
203             continue;
204         }
205
206         if ((str[6] == '[') && isalpha(str[7])) {
207             int k = str[7] - 'A';
208             menu = true;
209             if ((str[8] == ']') && (str[9] == ' ')) {
210                 angband_strcpy(hook[k], str + 10, sizeof(hook[k]));
211             }
212
213             continue;
214         }
215
216         if (str[6] != '<') {
217             continue;
218         }
219
220         size_t len = strlen(str);
221         if (str[len - 1] == '>') {
222             str[len - 1] = '\0';
223             if (!tag.empty() && streq(str + 7, tag)) {
224                 line = next;
225             }
226         }
227     }
228
229     auto size = next;
230     int rows = hgt - 4;
231     if (line == -1) {
232         line = ((size - 1) / rows) * rows;
233     }
234
235     term_clear();
236
237     concptr find = nullptr;
238     concptr shower = nullptr;
239     while (true) {
240         if (line >= size - rows) {
241             line = size - rows;
242         }
243         if (line < 0) {
244             line = 0;
245         }
246
247         if (next > line) {
248             angband_fclose(fff);
249             fff = angband_fopen(path_reopen, FileOpenMode::READ);
250             if (!fff) {
251                 THROW_EXCEPTION(std::runtime_error, open_error_mes);
252             }
253
254             next = 0;
255         }
256
257         while (next < line) {
258             if (angband_fgets(fff, buf, sizeof(buf))) {
259                 break;
260             }
261             if (prefix(buf, "***** ")) {
262                 continue;
263             }
264             next++;
265         }
266
267         int row_count;
268         for (row_count = 0; row_count < rows;) {
269             concptr str = buf;
270             if (!row_count) {
271                 line = next;
272             }
273             if (angband_fgets(fff, buf, sizeof(buf))) {
274                 break;
275             }
276             if (prefix(buf, "***** ")) {
277                 continue;
278             }
279             next++;
280             if (find && !row_count) {
281                 char lc_buf[1024];
282                 strcpy(lc_buf, str);
283                 str_tolower(lc_buf);
284                 if (!angband_strstr(lc_buf, find)) {
285                     continue;
286                 }
287             }
288
289             find = nullptr;
290             show_file_aux_line(str, row_count + 2, shower);
291             row_count++;
292         }
293
294         while (row_count < rows) {
295             term_erase(0, row_count + 2, 255);
296             row_count++;
297         }
298
299         if (find) {
300             bell();
301             line = back;
302             find = nullptr;
303             continue;
304         }
305
306         if (show_version) {
307             constexpr auto title = _("[%s, %s, %d/%d]", "[%s, %s, Line %d/%d]");
308             prt(format(title, get_version().data(), caption_str.data(), line, size), 0, 0);
309         } else {
310             constexpr auto title = _("[%s, %d/%d]", "[%s, Line %d/%d]");
311             prt(format(title, caption_str.data(), line, size), 0, 0);
312         }
313
314         if (size <= rows) {
315             prt(_("[キー:(?)ヘルプ (ESC)終了]", "[Press ESC to exit.]"), hgt - 1, 0);
316         } else {
317 #ifdef JP
318             if (reverse) {
319                 prt("[キー:(RET/スペース)↑ (-)↓ (?)ヘルプ (ESC)終了]", hgt - 1, 0);
320             } else {
321                 prt("[キー:(RET/スペース)↓ (-)↑ (?)ヘルプ (ESC)終了]", hgt - 1, 0);
322             }
323 #else
324             prt("[Press Return, Space, -, =, /, |, or ESC to exit.]", hgt - 1, 0);
325 #endif
326         }
327
328         skey = inkey_special(true);
329         switch (skey) {
330         case '?':
331             if (name != _("jhelpinfo.txt", "helpinfo.txt")) {
332                 show_file(player_ptr, true, _("jhelpinfo.txt", "helpinfo.txt"), 0, mode);
333             }
334             break;
335         case '=':
336             prt(_("強調: ", "Show: "), hgt - 1, 0);
337
338             char back_str[81];
339             strcpy(back_str, shower_str);
340             if (askfor(shower_str, 80)) {
341                 if (shower_str[0]) {
342                     str_tolower(shower_str);
343                     shower = shower_str;
344                 } else {
345                     shower = nullptr;
346                 }
347             } else {
348                 strcpy(shower_str, back_str);
349             }
350             break;
351
352         case '/':
353         case KTRL('s'):
354             prt(_("検索: ", "Find: "), hgt - 1, 0);
355             strcpy(back_str, finder_str);
356             if (askfor(finder_str, 80)) {
357                 if (finder_str[0]) {
358                     find = finder_str;
359                     back = line;
360                     line = line + 1;
361                     str_tolower(finder_str);
362                     shower = finder_str;
363                 } else {
364                     shower = nullptr;
365                 }
366             } else {
367                 strcpy(finder_str, back_str);
368             }
369             break;
370
371         case '#': {
372             char tmp[81];
373             prt(_("行: ", "Goto Line: "), hgt - 1, 0);
374             strcpy(tmp, "0");
375
376             if (askfor(tmp, 80)) {
377                 line = atoi(tmp);
378             }
379             break;
380         }
381
382         case SKEY_TOP:
383             line = 0;
384             break;
385
386         case SKEY_BOTTOM:
387             line = ((size - 1) / rows) * rows;
388             break;
389
390         case '%': {
391             char tmp[81];
392             prt(_("ファイル・ネーム: ", "Goto File: "), hgt - 1, 0);
393             strcpy(tmp, _("jhelp.hlp", "help.hlp"));
394
395             if (askfor(tmp, 80)) {
396                 if (!show_file(player_ptr, true, tmp, 0, mode)) {
397                     skey = 'q';
398                 }
399             }
400
401             break;
402         }
403
404         case '-':
405             line = line + (reverse ? rows : -rows);
406             if (line < 0) {
407                 line = 0;
408             }
409             break;
410
411         case SKEY_PGUP:
412             line = line - rows;
413             if (line < 0) {
414                 line = 0;
415             }
416             break;
417
418         case '\n':
419         case '\r':
420             line = line + (reverse ? -1 : 1);
421             if (line < 0) {
422                 line = 0;
423             }
424             break;
425
426         case '8':
427         case SKEY_UP:
428             line--;
429             if (line < 0) {
430                 line = 0;
431             }
432             break;
433
434         case '2':
435         case SKEY_DOWN:
436             line++;
437             break;
438
439         case ' ':
440             line = line + (reverse ? -rows : rows);
441             if (line < 0) {
442                 line = 0;
443             }
444             break;
445
446         case SKEY_PGDOWN:
447             line = line + rows;
448             break;
449         }
450
451         if (menu) {
452             int key = -1;
453             if (!(skey & SKEY_MASK) && isalpha(skey)) {
454                 key = skey - 'A';
455             }
456
457             if ((key > -1) && hook[key][0]) {
458                 /* Recurse on that file */
459                 if (!show_file(player_ptr, true, hook[key], 0, mode)) {
460                     skey = 'q';
461                 }
462             }
463         }
464
465         if (skey == '|') {
466             FILE *ffp;
467             char xtmp[81] = "";
468
469             if (!get_string(_("ファイル名: ", "File name: "), xtmp, 80)) {
470                 continue;
471             }
472
473             angband_fclose(fff);
474             fff = angband_fopen(path_reopen, FileOpenMode::READ);
475             const auto &path_xtemp = path_build(ANGBAND_DIR_USER, xtmp);
476             ffp = angband_fopen(path_xtemp, FileOpenMode::WRITE);
477
478             if (!(fff && ffp)) {
479                 msg_print(_("ファイルを開けません。", "Failed to open file."));
480                 skey = ESCAPE;
481                 break;
482             }
483
484             fprintf(ffp, "%s: %s\n", player_ptr->name, !what.empty() ? what.data() : caption_str.data());
485             char buff[1024]{};
486             while (!angband_fgets(fff, buff, sizeof(buff))) {
487                 angband_fputs(ffp, buff, 80);
488             }
489             angband_fclose(fff);
490             angband_fclose(ffp);
491             fff = angband_fopen(path_reopen, FileOpenMode::READ);
492         }
493
494         if ((skey == ESCAPE) || (skey == '<')) {
495             break;
496         }
497
498         if (skey == KTRL('q')) {
499             skey = 'q';
500         }
501
502         if (skey == 'q') {
503             break;
504         }
505     }
506
507     angband_fclose(fff);
508     return skey != 'q';
509 }
510
511 /*
512  * Convert string to lower case
513  */
514 void str_tolower(char *str)
515 {
516     for (; *str; str++) {
517 #ifdef JP
518         if (iskanji(*str)) {
519             str++;
520             continue;
521         }
522 #endif
523         *str = (char)tolower(*str);
524     }
525 }