OSDN Git Service

Merge pull request #3569 from sikabane-works/release/3.0.0.88-alpha
[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 #include <string_view>
20
21 /*!
22  * @brief ファイル内容の一行をコンソールに出力する
23  * Display single line of on-line help file
24  * @param str 出力する文字列
25  * @param cy コンソールの行
26  * @param shower 確認中
27  * @details
28  * <pre>
29  * You can insert some special color tag to change text color.
30  * Such as...
31  * WHITETEXT [[[[y|SOME TEXT WHICH IS DISPLAYED IN YELLOW| WHITETEXT
32  * A colored segment is between "[[[[y|" and the last "|".
33  * You can use any single character in place of the "|".
34  * </pre>
35  * @todo 表示とそれ以外を分割する
36  */
37 static void show_file_aux_line(std::string_view str, int cy, std::string_view shower)
38 {
39     char lcstr[1024];
40     concptr ptr;
41     byte textcolor = TERM_WHITE;
42     byte focuscolor = TERM_YELLOW;
43
44     if (!shower.empty()) {
45         strcpy(lcstr, str.data());
46         str_tolower(lcstr);
47
48         ptr = angband_strstr(lcstr, shower);
49         textcolor = (ptr == nullptr) ? TERM_L_DARK : TERM_WHITE;
50     }
51
52     int cx = 0;
53     term_gotoxy(cx, cy);
54
55     static const char tag_str[] = "[[[[";
56     byte color = textcolor;
57     char in_tag = '\0';
58     for (size_t i = 0; i < str.length();) {
59         int len = strlen(&str[i]);
60         int showercol = len + 1;
61         int bracketcol = len + 1;
62         int endcol = len;
63         if (!shower.empty()) {
64             ptr = angband_strstr(&lcstr[i], shower);
65             if (ptr) {
66                 showercol = ptr - &lcstr[i];
67             }
68         }
69
70         ptr = in_tag ? angband_strchr(&str[i], in_tag) : angband_strstr(&str[i], tag_str);
71         if (ptr) {
72             bracketcol = ptr - &str[i];
73         }
74         if (bracketcol < endcol) {
75             endcol = bracketcol;
76         }
77         if (showercol < endcol) {
78             endcol = showercol;
79         }
80
81         term_addstr(endcol, color, &str[i]);
82         cx += endcol;
83         i += endcol;
84
85         if (!shower.empty() && (endcol == showercol)) {
86             const auto showerlen = shower.length();
87             term_addstr(showerlen, focuscolor, &str[i]);
88             cx += showerlen;
89             i += showerlen;
90             continue;
91         }
92
93         if (endcol != bracketcol) {
94             continue;
95         }
96
97         if (in_tag) {
98             i++;
99             in_tag = '\0';
100             color = textcolor;
101             continue;
102         }
103
104         i += sizeof(tag_str) - 1;
105         color = color_char_to_attr(str[i]);
106         if (color == 255 || str[i + 1] == '\0') {
107             color = textcolor;
108             term_addstr(-1, color, tag_str);
109             cx += sizeof(tag_str) - 1;
110             continue;
111         }
112
113         i++;
114         in_tag = str[i];
115         i++;
116     }
117
118     term_erase(cx, cy);
119 }
120
121 /*!
122  * @brief ファイル内容をコンソールに出力する
123  * Recursive file perusal.
124  * @param player_ptr プレイヤーへの参照ポインタ
125  * @param show_version TRUEならばコンソール上にゲームのバージョンを表示する
126  * @param name ファイル名の文字列
127  * @param what 内容キャプションの文字列
128  * @param line 表示の現在行
129  * @param mode オプション
130  * @details
131  * <pre>
132  * Process various special text in the input file, including
133  * the "menu" structures used by the "help file" system.
134  * Return FALSE on 'q' to exit from a deep, otherwise TRUE.
135  * </pre>
136  * @todo 表示とそれ以外を分割する
137  */
138 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)
139 {
140     TermCenteredOffsetSetter tcos(MAIN_TERM_MIN_COLS, std::nullopt);
141
142     const auto [wid, hgt] = term_get_size();
143
144     char hook[68][32]{};
145     auto stripped_names = str_split(name_with_tag, '#');
146     auto &name = stripped_names[0];
147     auto tag = stripped_names.size() > 1 ? stripped_names[1] : "";
148     std::filesystem::path path_reopen("");
149     FILE *fff = nullptr;
150     std::stringstream caption;
151     if (!what.empty()) {
152         caption << what;
153         path_reopen = name;
154         fff = angband_fopen(path_reopen, FileOpenMode::READ);
155     }
156
157     if (!fff) {
158         caption.clear();
159         caption << _("ヘルプ・ファイル'", "Help file '");
160         caption << name << "'";
161         path_reopen = path_build(ANGBAND_DIR_HELP, name);
162         fff = angband_fopen(path_reopen, FileOpenMode::READ);
163     }
164
165     if (!fff) {
166         caption.clear();
167         caption << _("スポイラー・ファイル'", "Info file '");
168         caption << name << "'";
169         path_reopen = path_build(ANGBAND_DIR_INFO, name);
170         fff = angband_fopen(path_reopen, FileOpenMode::READ);
171     }
172
173     if (!fff) {
174         caption.clear();
175         path_reopen = path_build(ANGBAND_DIR, name);
176         caption << _("スポイラー・ファイル'", "Info file '");
177         caption << name << "'";
178         fff = angband_fopen(path_reopen, FileOpenMode::READ);
179     }
180
181     const auto open_error_mes = format(_("'%s'をオープンできません。", "Cannot open '%s'."), name.data());
182     if (!fff) {
183         THROW_EXCEPTION(std::runtime_error, open_error_mes);
184     }
185
186     const auto caption_str = caption.str();
187     int skey;
188     auto next = 0;
189     auto back = 0;
190     auto menu = false;
191     char buf[1024]{};
192     auto reverse = initial_line < 0;
193     auto line = initial_line;
194     while (true) {
195         char *str = buf;
196         if (angband_fgets(fff, buf, sizeof(buf))) {
197             break;
198         }
199         if (!prefix(str, "***** ")) {
200             next++;
201             continue;
202         }
203
204         if ((str[6] == '[') && isalpha(str[7])) {
205             int k = str[7] - 'A';
206             menu = true;
207             if ((str[8] == ']') && (str[9] == ' ')) {
208                 angband_strcpy(hook[k], str + 10, sizeof(hook[k]));
209             }
210
211             continue;
212         }
213
214         if (str[6] != '<') {
215             continue;
216         }
217
218         size_t len = strlen(str);
219         if (str[len - 1] == '>') {
220             str[len - 1] = '\0';
221             if (!tag.empty() && streq(str + 7, tag)) {
222                 line = next;
223             }
224         }
225     }
226
227     auto size = next;
228     int rows = hgt - 4;
229     if (line == -1) {
230         line = ((size - 1) / rows) * rows;
231     }
232
233     term_clear();
234
235     std::string find;
236     std::string finder_str;
237     std::string shower;
238     std::string shower_str;
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.empty() && !row_count) {
281                 char lc_buf[1024];
282                 strcpy(lc_buf, str);
283                 str_tolower(lc_buf);
284                 if (!str_find(lc_buf, find)) {
285                     continue;
286                 }
287             }
288
289             find.clear();
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);
296             row_count++;
297         }
298
299         if (!find.empty()) {
300             bell();
301             line = back;
302             find.clear();
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
335             break;
336         case '=': {
337             prt(_("強調: ", "Show: "), hgt - 1, 0);
338             auto ask_result = askfor(80, shower_str);
339             if (!ask_result) {
340                 break;
341             }
342
343             shower_str = *ask_result;
344             if (shower_str.empty()) {
345                 shower.clear();
346                 break;
347             }
348
349             str_tolower(shower_str.data());
350             shower = shower_str;
351             break;
352         }
353         case '/':
354         case KTRL('s'): {
355             prt(_("検索: ", "Find: "), hgt - 1, 0);
356             auto ask_result = askfor(80, finder_str);
357             if (!ask_result) {
358                 break;
359             }
360
361             finder_str = *ask_result;
362             if (finder_str.empty()) {
363                 shower.clear();
364                 break;
365             }
366
367             find = finder_str;
368             back = line;
369             line = line + 1;
370             str_tolower(finder_str.data());
371             shower = finder_str;
372             break;
373         }
374         case '#': {
375             prt(_("行: ", "Goto Line: "), hgt - 1, 0);
376             constexpr auto initial_goto = "0";
377             while (true) {
378                 const auto ask_result = askfor(10, initial_goto);
379                 if (!ask_result) {
380                     break;
381                 }
382
383                 try {
384                     line = std::stoi(*ask_result);
385                     break;
386                 } catch (std::invalid_argument const &) {
387                     prt(_("数値を入力して下さい。", "Please input numeric value."), hgt - 1, 0);
388                 } catch (std::out_of_range const &) {
389                     prt(_("入力可能な数値の範囲を超えています。", "Input value overflows the maximum number."), hgt - 1, 0);
390                 }
391             }
392
393             break;
394         }
395         case SKEY_TOP:
396             line = 0;
397             break;
398         case SKEY_BOTTOM:
399             line = ((size - 1) / rows) * rows;
400             break;
401         case '%': {
402             prt(_("ファイル・ネーム: ", "Goto File: "), hgt - 1, 0);
403             const auto ask_result = askfor(80, _("jhelp.hlp", "help.hlp"));
404             if (!ask_result) {
405                 break;
406             }
407
408             if (!show_file(player_ptr, true, *ask_result, 0, mode)) {
409                 skey = 'q';
410             }
411
412             break;
413         }
414         case '-':
415             line = line + (reverse ? rows : -rows);
416             if (line < 0) {
417                 line = 0;
418             }
419
420             break;
421         case SKEY_PGUP:
422             line = line - rows;
423             if (line < 0) {
424                 line = 0;
425             }
426
427             break;
428         case '\n':
429         case '\r':
430             line = line + (reverse ? -1 : 1);
431             if (line < 0) {
432                 line = 0;
433             }
434
435             break;
436         case '8':
437         case SKEY_UP:
438             line--;
439             if (line < 0) {
440                 line = 0;
441             }
442
443             break;
444         case '2':
445         case SKEY_DOWN:
446             line++;
447             break;
448         case ' ':
449             line = line + (reverse ? -rows : rows);
450             if (line < 0) {
451                 line = 0;
452             }
453
454             break;
455         case SKEY_PGDOWN:
456             line = line + rows;
457             break;
458         default:
459             break;
460         }
461
462         if (menu) {
463             auto key = -1;
464             if (!(skey & SKEY_MASK) && isalpha(skey)) {
465                 key = skey - 'A';
466             }
467
468             if ((key > -1) && hook[key][0]) {
469                 /* Recurse on that file */
470                 if (!show_file(player_ptr, true, hook[key], 0, mode)) {
471                     skey = 'q';
472                 }
473             }
474         }
475
476         if (skey == '|') {
477             const auto xtmp = input_string(_("ファイル名: ", "File name: "), 80);
478             if (!xtmp.has_value()) {
479                 continue;
480             }
481
482             angband_fclose(fff);
483             fff = angband_fopen(path_reopen, FileOpenMode::READ);
484             const auto &path_xtemp = path_build(ANGBAND_DIR_USER, xtmp.value());
485             auto *ffp = angband_fopen(path_xtemp, FileOpenMode::WRITE);
486
487             if (!(fff && ffp)) {
488                 msg_print(_("ファイルを開けません。", "Failed to open file."));
489                 skey = ESCAPE;
490                 break;
491             }
492
493             fprintf(ffp, "%s: %s\n", player_ptr->name, !what.empty() ? what.data() : caption_str.data());
494             char buff[1024]{};
495             while (!angband_fgets(fff, buff, sizeof(buff))) {
496                 angband_fputs(ffp, buff, 80);
497             }
498             angband_fclose(fff);
499             angband_fclose(ffp);
500             fff = angband_fopen(path_reopen, FileOpenMode::READ);
501         }
502
503         if ((skey == ESCAPE) || (skey == '<')) {
504             break;
505         }
506
507         if (skey == KTRL('q')) {
508             skey = 'q';
509         }
510
511         if (skey == 'q') {
512             break;
513         }
514     }
515
516     angband_fclose(fff);
517     return skey != 'q';
518 }
519
520 /*
521  * Convert string to lower case
522  */
523 void str_tolower(char *str)
524 {
525     for (; *str; str++) {
526 #ifdef JP
527         if (iskanji(*str)) {
528             str++;
529             continue;
530         }
531 #endif
532         *str = (char)tolower(*str);
533     }
534 }