OSDN Git Service

Merge branch 'master' of https://github.com/hengband/hengband
[hengbandforosx/hengbandosx.git] / src / cmd-io / cmd-process-screen.cpp
1 /*!
2  * @brief 記念撮影のセーブとロード
3  * @date 2020/04/22
4  * @Author Hourier
5  */
6
7 #include "cmd-io/cmd-process-screen.h"
8 #include "cmd-visual/cmd-draw.h"
9 #include "core/asking-player.h"
10 #include "core/stuff-handler.h"
11 #include "core/visuals-reseter.h"
12 #include "game-option/special-options.h"
13 #include "io/files-util.h"
14 #include "io/input-key-acceptor.h"
15 #include "system/angband-exceptions.h"
16 #include "system/player-type-definition.h"
17 #include "system/redrawing-flags-updater.h"
18 #include "term/gameterm.h"
19 #include "term/screen-processor.h"
20 #include "term/term-color-types.h"
21 #include "util/angband-files.h"
22 #include "view/display-messages.h"
23 #include <optional>
24 #include <string>
25 #include <string_view>
26
27 // Encode the screen colors
28 static char hack[17] = "dwsorgbuDWvyRGBU";
29
30 static concptr tags[4] = {
31     "HEADER_START:",
32     "HEADER_END:",
33     "FOOTER_START:",
34     "FOOTER_END:",
35 };
36 static concptr html_head[3] = {
37     "<html>\n<body text=\"#ffffff\" bgcolor=\"#000000\">\n",
38     "<pre>",
39     0,
40 };
41 static concptr html_foot[3] = {
42     "</pre>\n",
43     "</body>\n</html>\n",
44     0,
45 };
46
47 /*!
48  * @brief 一時ファイルを読み込み、ファイルに書き出す
49  * @param fff ファイルへの参照ポインタ
50  * @param tempfff 一時ファイルへの参照ポインタ
51  * @param num_tag タグ番号
52  * @todo io/ 以下に移したいところだが、このファイルの行数も大したことがないので一旦保留
53  */
54 static void read_temporary_file(FILE *fff, FILE *tmpfff, int num_tag)
55 {
56     bool is_first_line = true;
57     int next_tag = num_tag + 1;
58     while (true) {
59         const auto buf = angband_fgets(tmpfff);
60         if (!buf) {
61             break;
62         }
63         if (is_first_line) {
64             if (strncmp(buf->data(), tags[num_tag], strlen(tags[num_tag])) == 0) {
65                 is_first_line = false;
66             }
67
68             continue;
69         }
70
71         if (strncmp(buf->data(), tags[next_tag], strlen(tags[next_tag])) == 0) {
72             break;
73         }
74
75         fprintf(fff, "%s\n", buf->data());
76     }
77 }
78
79 /*!
80  * @brief 記念撮影を1行ダンプする
81  * @param wid 幅
82  * @param y 現在の行位置
83  * @param fff 記念撮影ファイルへの参照ポインタ
84  */
85 static void screen_dump_one_line(int wid, int y, FILE *fff)
86 {
87     TERM_COLOR a = 0, old_a = 0;
88     char c = ' ';
89     for (TERM_LEN x = 0; x < wid - 1; x++) {
90         concptr cc = nullptr;
91         (void)(term_what(x, y, &a, &c));
92         switch (c) {
93         case '&':
94             cc = "&amp;";
95             break;
96         case '<':
97             cc = "&lt;";
98             break;
99         case '>':
100             cc = "&gt;";
101             break;
102 #ifdef WINDOWS
103         case 0x1f:
104             c = '.';
105             break;
106         case 0x7f:
107             c = (a == 0x09) ? '%' : '#';
108             break;
109 #endif
110         }
111
112         a = a & 0x0F;
113         if ((y == 0 && x == 0) || a != old_a) {
114             int rv = angband_color_table[a][1];
115             int gv = angband_color_table[a][2];
116             int bv = angband_color_table[a][3];
117             fprintf(fff, "%s<font color=\"#%02x%02x%02x\">",
118                 ((y == 0 && x == 0) ? "" : "</font>"), rv, gv, bv);
119             old_a = a;
120         }
121
122         if (cc) {
123             fprintf(fff, "%s", cc);
124         } else {
125             fprintf(fff, "%c", c);
126         }
127     }
128 }
129
130 /*!
131  * @brief 記念撮影を行方向にスイープする
132  * @param wid 幅
133  * @param hgt 高さ
134  * @param fff 記念撮影ファイルへの参照ポインタ
135  */
136 static void screen_dump_lines(int wid, int hgt, FILE *fff)
137 {
138     for (TERM_LEN y = 0; y < hgt; y++) {
139         if (y != 0) {
140             fprintf(fff, "\n");
141         }
142
143         screen_dump_one_line(wid, y, fff);
144     }
145 }
146
147 /*!
148  * @brief ファイルへ書き込めない場合にエラーを表示する
149  * @param fff ダンプファイルへの参照ポインタ
150  * @param path 保存先HTMLファイルのパス
151  * @param need_message メッセージ表示の必要性
152  * @return メッセージを表示して後続処理を実行するか否か
153  */
154 static bool check_screen_html_can_open(FILE *fff, const std::filesystem::path &path, bool need_message)
155 {
156     if (fff) {
157         return true;
158     }
159
160     if (!need_message) {
161         return false;
162     }
163
164     const auto &path_str = path.string();
165     const auto mes = format(_("ファイル %s を開けませんでした。", "Failed to open file %s."), path_str.data());
166     THROW_EXCEPTION(std::runtime_error, mes);
167 }
168
169 /*!
170  * @brief HTMLヘッダを書き込む
171  * @param tmpfff 一時ファイルへの参照ポインタ
172  * @param fff 記念撮影ファイルへの参照ポインタ
173  */
174 static void write_html_header(FILE *tmpfff, FILE *fff)
175 {
176     if (tmpfff) {
177         read_temporary_file(fff, tmpfff, 0);
178         return;
179     }
180
181     for (int i = 0; html_head[i]; i++) {
182         fputs(html_head[i], fff);
183     }
184 }
185
186 /*!
187  * @brief HTMLフッタを書き込む
188  * @param tmpfff 一時ファイルへの参照ポインタ
189  * @param fff 記念撮影ファイルへの参照ポインタ
190  */
191 static void write_html_footer(FILE *tmpfff, FILE *fff)
192 {
193     fprintf(fff, "</font>");
194     if (!tmpfff) {
195         for (int i = 0; html_foot[i]; i++) {
196             fputs(html_foot[i], fff);
197         }
198     } else {
199         rewind(tmpfff);
200         read_temporary_file(fff, tmpfff, 2);
201         angband_fclose(tmpfff);
202     }
203
204     fprintf(fff, "\n");
205 }
206
207 void exe_cmd_save_screen_html(const std::filesystem::path &path, bool need_message)
208 {
209     const auto &[wid, hgt] = term_get_size();
210     auto *fff = angband_fopen(path, FileOpenMode::WRITE);
211     if (!check_screen_html_can_open(fff, path, need_message)) {
212         return;
213     }
214
215     if (need_message) {
216         screen_save();
217     }
218
219     const auto path_prf = path_build(ANGBAND_DIR_USER, "htmldump.prf");
220     auto *tmpfff = angband_fopen(path_prf, FileOpenMode::READ);
221     write_html_header(tmpfff, fff);
222     screen_dump_lines(wid, hgt, fff);
223     write_html_footer(tmpfff, fff);
224     angband_fclose(fff);
225     if (!need_message) {
226         return;
227     }
228
229     msg_print(_("画面(記念撮影)をファイルに書き出しました。", "Screen dump saved."));
230     msg_print(nullptr);
231     screen_load();
232 }
233
234 /*!
235  * @brief HTML方式で記念撮影する / Save a screen dump to a file
236  * @param なし
237  */
238 static void exe_cmd_save_screen_html_with_naming()
239 {
240     const auto filename = input_string(_("ファイル名: ", "File name: "), 80, "screen.html");
241     if (!filename) {
242         return;
243     }
244
245     const auto path = path_build(ANGBAND_DIR_USER, *filename);
246     msg_print(nullptr);
247     exe_cmd_save_screen_html(path, true);
248 }
249
250 /*!
251  * @brief 記念撮影の方式を問い合わせる
252  * @param html_dump HTMLダンプするか否か
253  * @return ダンプするならTRUE、キャンセルならFALSE
254  */
255 static bool ask_html_dump(bool *html_dump)
256 {
257     while (true) {
258         char c = inkey();
259         if (c == 'Y' || c == 'y') {
260             *html_dump = false;
261             return true;
262         }
263
264         if (c == 'H' || c == 'h') {
265             *html_dump = true;
266             return true;
267         }
268
269         prt("", 0, 0);
270         return false;
271     }
272
273     // コンパイル警告対応.
274     return false;
275 }
276
277 /*!
278  * @brief ファイルへ書き込めない場合にエラーを表示する
279  * @param fff ダンプファイルへの参照ポインタ
280  * @return ファイルへ書き込めるならTRUE、書き込めないならFALSE
281  */
282 static bool check_screen_text_can_open(FILE *fff, const std::string_view filename)
283 {
284     if (fff) {
285         return true;
286     }
287
288     msg_format(_("ファイル %s を開けませんでした。", "Failed to open file %s."), filename.data());
289     msg_print(nullptr);
290     return false;
291 }
292
293 /*!
294  * @brief テキスト方式で記念撮影する
295  * @param wid 幅
296  * @param hgt 高さ
297  * @return 記念撮影に成功したらTRUE、ファイルが開けなかったらFALSE
298  * @todo どこかバグっていて、(恐らく初期化されていない)変な文字列まで出力される
299  */
300 static bool do_cmd_save_screen_text(int wid, int hgt)
301 {
302     TERM_COLOR a = 0;
303     auto c = ' ';
304     const auto path = path_build(ANGBAND_DIR_USER, "dump.txt");
305     auto *fff = angband_fopen(path, FileOpenMode::WRITE);
306     if (!check_screen_text_can_open(fff, path.string())) {
307         return false;
308     }
309
310     screen_save();
311     for (TERM_LEN y = 0; y < hgt; y++) {
312         TERM_LEN x;
313         char buf[1024]{};
314         for (x = 0; x < wid - 1; x++) {
315             (void)(term_what(x, y, &a, &c));
316             buf[x] = c;
317         }
318
319         buf[x] = '\0';
320         fprintf(fff, "%s\n", buf);
321     }
322
323     fprintf(fff, "\n");
324     for (TERM_LEN y = 0; y < hgt; y++) {
325         TERM_LEN x;
326         char buf[1024]{};
327         for (x = 0; x < wid - 1; x++) {
328             (void)(term_what(x, y, &a, &c));
329             buf[x] = hack[a & 0x0F];
330         }
331
332         buf[x] = '\0';
333         fprintf(fff, "%s\n", buf);
334     }
335
336     fprintf(fff, "\n");
337     angband_fclose(fff);
338     msg_print(_("画面(記念撮影)をファイルに書き出しました。", "Screen dump saved."));
339     msg_print(nullptr);
340     screen_load();
341     return true;
342 }
343
344 /*!
345  * @brief 記念撮影のためにグラフィック使用をOFFにする
346  * @param player_ptr プレイヤーへの参照ポインタ
347  * @return 記念撮影直前のグラフィックオプション
348  */
349 static bool update_use_graphics(PlayerType *player_ptr)
350 {
351     if (!use_graphics) {
352         return true;
353     }
354
355     use_graphics = false;
356     reset_visuals(player_ptr);
357     static constexpr auto flags = {
358         MainWindowRedrawingFlag::WIPE,
359         MainWindowRedrawingFlag::BASIC,
360         MainWindowRedrawingFlag::EXTRA,
361         MainWindowRedrawingFlag::MAP,
362         MainWindowRedrawingFlag::EQUIPPY,
363     };
364     RedrawingFlagsUpdater::get_instance().set_flags(flags);
365     handle_stuff(player_ptr);
366     return false;
367 }
368
369 /*
370  * Save a screen dump to a file
371  * @param player_ptr プレイヤーへの参照ポインタ
372  */
373 void do_cmd_save_screen(PlayerType *player_ptr)
374 {
375     prt(_("記念撮影しますか? [(y)es/(h)tml/(n)o] ", "Save screen dump? [(y)es/(h)tml/(n)o] "), 0, 0);
376     bool html_dump;
377     if (!ask_html_dump(&html_dump)) {
378         return;
379     }
380
381     const auto &[wid, hgt] = term_get_size();
382     const auto old_use_graphics = update_use_graphics(player_ptr);
383
384     if (html_dump) {
385         exe_cmd_save_screen_html_with_naming();
386         do_cmd_redraw(player_ptr);
387     } else if (!do_cmd_save_screen_text(wid, hgt)) {
388         return;
389     }
390
391     if (old_use_graphics) {
392         return;
393     }
394
395     use_graphics = true;
396     reset_visuals(player_ptr);
397     static constexpr auto flags = {
398         MainWindowRedrawingFlag::WIPE,
399         MainWindowRedrawingFlag::BASIC,
400         MainWindowRedrawingFlag::EXTRA,
401         MainWindowRedrawingFlag::MAP,
402         MainWindowRedrawingFlag::EQUIPPY,
403     };
404     RedrawingFlagsUpdater::get_instance().set_flags(flags);
405     handle_stuff(player_ptr);
406 }
407
408 /*!
409  * @brief 白文字だけ画面に描画する
410  * @param buf 描画用バッファ
411  * @param fff 記念撮影ファイルへの参照ポインタ
412  * @param wid 幅
413  * @param hgt 高さ
414  * @todo 目的は不明瞭
415  * @return ファイルが読み込めなくなったらFALSEで抜ける
416  */
417 static bool draw_white_characters(FILE *fff, int wid, int hgt)
418 {
419     bool okay = true;
420     for (TERM_LEN y = 0; okay; y++) {
421         char buf[1024]{};
422         if (!fgets(buf, sizeof(buf), fff)) {
423             okay = false;
424         }
425
426         if (buf[0] == '\n' || buf[0] == '\0') {
427             break;
428         }
429         if (y >= hgt) {
430             continue;
431         }
432
433         for (TERM_LEN x = 0; x < wid - 1; x++) {
434             if (buf[x] == '\n' || buf[x] == '\0') {
435                 break;
436             }
437
438             term_draw(x, y, TERM_WHITE, buf[x]);
439         }
440     }
441
442     return okay;
443 }
444
445 /*!
446  * @brief 白以外の文字を画面に描画する
447  * @param fff 記念撮影ファイルへの参照ポインタ
448  * @param wid 幅
449  * @param hgt 高さ
450  * @param 白文字が途中で読み込めなくなっていたらTRUE
451  * @todo 目的は不明瞭
452  */
453 static void draw_colored_characters(FILE *fff, int wid, int hgt, bool okay)
454 {
455     TERM_COLOR a = TERM_DARK;
456     auto c = ' ';
457     for (TERM_LEN y = 0; okay; y++) {
458         char buf[1024]{};
459         if (!fgets(buf, sizeof(buf), fff)) {
460             okay = false;
461         }
462
463         if (buf[0] == '\n' || buf[0] == '\0') {
464             break;
465         }
466         if (y >= hgt) {
467             continue;
468         }
469
470         for (TERM_LEN x = 0; x < wid - 1; x++) {
471             if (buf[x] == '\n' || buf[x] == '\0') {
472                 break;
473             }
474
475             (void)(term_what(x, y, &a, &c));
476             for (int i = 0; i < 16; i++) {
477                 if (hack[i] == buf[x]) {
478                     a = (byte)i;
479                 }
480             }
481
482             term_draw(x, y, a, c);
483         }
484     }
485 }
486
487 /*
488  * @brief Load a screen dump from a file
489  * @param なし
490  */
491 void do_cmd_load_screen(void)
492 {
493     const auto &[wid, hgt] = term_get_size();
494     const auto path = path_build(ANGBAND_DIR_USER, "dump.txt");
495     auto *fff = angband_fopen(path, FileOpenMode::READ);
496     if (!fff) {
497         const auto filename = path.string();
498         msg_format(_("%s を開くことができませんでした。", "Failed to open %s."), filename.data());
499         msg_print(nullptr);
500         return;
501     }
502
503     screen_save();
504     term_clear();
505     bool okay = draw_white_characters(fff, wid, hgt);
506     draw_colored_characters(fff, wid, hgt, okay);
507
508     angband_fclose(fff);
509     prt(_("ファイルに書き出された画面(記念撮影)をロードしました。", "Screen dump loaded."), 0, 0);
510     flush();
511     inkey();
512     screen_load();
513 }