OSDN Git Service

2520fbc60ed3cdf8fd313139cfa298466d495643
[hengbandforosx/hengbandosx.git] / src / main-win.cpp
1 /*!
2  * @file main-win.cpp
3  * @brief Windows版固有実装(メインエントリポイント含む)
4  * @date 2018/03/16
5  * @author Hengband Team
6  * @details
7  *
8  * <h3>概要</h3>
9  * Windows98かその前後の頃を起点としたAPI実装。
10  * 各種のゲームエンジンは無論、
11  * DirectXといった昨今描画に標準的となったライブラリも用いていない。
12  * タイルの描画処理などについては、現在動作の詳細を検証中。
13  *
14  * <h3>フォーク元の概要</h3>
15  * <p>
16  * Copyright (c) 1997 Ben Harrison, Skirmantas Kligys, and others
17  *
18  * This software may be copied and distributed for educational, research,
19  * and not for profit purposes provided that this copyright and statement
20  * are included in all such copies.
21  * </p>
22  * <p>
23  * This file helps Angband work with Windows computers.
24  *
25  * To use this file, use an appropriate "Makefile" or "Project File",
26  * make sure that "WINDOWS" and/or "WIN32" are defined somewhere, and
27  * make sure to obtain various extra files as described below.
28  *
29  * The official compilation uses the CodeWarrior Pro compiler, which
30  * includes a special project file and precompilable header file.
31  * </p>
32  *
33  * <p>
34  * The "lib/user/pref-win.prf" file contains keymaps, macro definitions,
35  * and/or color redefinitions.
36  * </p>
37  *
38  * <p>
39  * The "lib/user/font-win.prf" contains attr/char mappings for wall.bmp.
40  * </p>
41  *
42  * <p>
43  * The "lib/user/graf-win.prf" contains attr/char mappings for use with the
44  * special bitmap files in "lib/xtra/graf", which are activated by a menu
45  * item.
46  * </p>
47  *
48  * <p>
49  * Compiling this file, and using the resulting executable, requires
50  * several extra files not distributed with the standard Angband code.
51  * All of these extra files can be found in the "ext-win" archive.
52  * </p>
53  *
54  * <p>
55  * The "term_xtra_win_clear()" function should probably do a low-level
56  * clear of the current window, and redraw the borders and other things,
57  * if only for efficiency.
58  * </p>
59  *
60  * <p>
61  * A simpler method is needed for selecting the "tile size" for windows.
62  * </p>
63  *
64  * <p>
65  * The various "warning" messages assume the existance of the "screen.w"
66  * window, I think, and only a few calls actually check for its existance,
67  * this may be okay since "nullptr" means "on top of all windows". (?)  The
68  * user must never be allowed to "hide" the main window, or the "menubar"
69  * will disappear.
70  * </p>
71  *
72  * <p>
73  * Initial framework (and most code) by Ben Harrison (benh@phial.com).
74  *
75  * Original code by Skirmantas Kligys (kligys@scf.usc.edu).
76  *
77  * Additional code by Ross E Becker (beckerr@cis.ohio-state.edu),
78  * and Chris R. Martin (crm7479@tam2000.tamu.edu).
79  * </p>
80  */
81
82 #ifdef WINDOWS
83
84 #include "cmd-io/cmd-save.h"
85 #include "cmd-visual/cmd-draw.h"
86 #include "core/game-play.h"
87 #include "core/player-processor.h"
88 #include "core/score-util.h"
89 #include "core/scores.h"
90 #include "core/special-internal-keys.h"
91 #include "core/stuff-handler.h"
92 #include "core/visuals-reseter.h"
93 #include "core/window-redrawer.h"
94 #include "floor/floor-events.h"
95 #include "game-option/runtime-arguments.h"
96 #include "game-option/special-options.h"
97 #include "io/files-util.h"
98 #include "io/input-key-acceptor.h"
99 #include "io/record-play-movie.h"
100 #include "io/signal-handlers.h"
101 #include "io/write-diary.h"
102 #include "main-win/commandline-win.h"
103 #include "main-win/graphics-win.h"
104 #include "main-win/main-win-bg.h"
105 #include "main-win/main-win-file-utils.h"
106 #include "main-win/main-win-mci.h"
107 #include "main-win/main-win-menuitem.h"
108 #include "main-win/main-win-music.h"
109 #include "main-win/main-win-sound.h"
110 #include "main-win/main-win-term.h"
111 #include "main-win/main-win-utils.h"
112 #include "main/angband-initializer.h"
113 #include "main/sound-of-music.h"
114 #include "monster-floor/monster-lite.h"
115 #include "save/save.h"
116 #include "system/angband.h"
117 #include "system/player-type-definition.h"
118 #include "system/system-variables.h"
119 #include "term/gameterm.h"
120 #include "term/screen-processor.h"
121 #include "term/term-color-types.h"
122 #include "util/angband-files.h"
123 #include "util/bit-flags-calculator.h"
124 #include "util/enum-converter.h"
125 #include "util/int-char-converter.h"
126 #include "util/string-processor.h"
127 #include "view/display-messages.h"
128 #include "view/display-scores.h"
129 #include "wizard/spoiler-util.h"
130 #include "wizard/wizard-spoiler.h"
131 #include "world/world.h"
132 #include <algorithm>
133 #include <commdlg.h>
134 #include <cstdlib>
135 #include <direct.h>
136 #include <locale>
137 #include <string>
138 #include <string_view>
139 #include <vector>
140
141 /*
142  * Window names
143  */
144 LPCWSTR win_term_name[] = { L"Hengband", L"Term-1", L"Term-2", L"Term-3", L"Term-4", L"Term-5", L"Term-6", L"Term-7" };
145
146 #define MAX_TERM_DATA 8 //!< Maximum number of windows
147
148 static term_data data[MAX_TERM_DATA]; //!< An array of term_data's
149 static bool is_main_term(term_data *td)
150 {
151     return td == &data[0];
152 }
153 static term_data *my_td; //!< Hack -- global "window creation" pointer
154 POINT normsize; //!< Remember normal size of main window when maxmized
155
156 /*
157  * was main window maximized on previous playing
158  */
159 bool win_maximized = false;
160
161 /*
162  * game in progress
163  */
164 bool game_in_progress = false;
165
166 /*
167  * movie in progress
168  */
169 bool movie_in_progress = false;
170
171 /*
172  * note when "open"/"new" become valid
173  */
174 bool initialized = false;
175
176 /*
177  * Saved instance handle
178  */
179 static HINSTANCE hInstance;
180
181 /*
182  * Yellow brush for the cursor
183  */
184 static HBRUSH hbrYellow;
185
186 /*
187  * An icon
188  */
189 static HICON hIcon;
190
191 /* bg */
192 bg_mode current_bg_mode = bg_mode::BG_NONE;
193 #define DEFAULT_BG_FILENAME "bg.bmp"
194 std::filesystem::path wallpaper_path = ""; //!< 壁紙ファイル名。
195
196 /*
197  * Show sub-windows even when Hengband is not in focus
198  */
199 static bool keep_subwindows = true;
200
201 /*
202  * Full path to ANGBAND.INI
203  */
204 static concptr ini_file = nullptr;
205
206 /*
207  * Name of application
208  */
209 static LPCWSTR AppName = L"ANGBAND";
210
211 /*
212  * Name of sub-window type
213  */
214 static LPCWSTR AngList = L"AngList";
215
216 /*
217  * The "complex" color values
218  */
219 static COLORREF win_clr[256];
220
221 /*
222  * Flag for macro trigger with dump ASCII
223  */
224 static bool term_no_press = false;
225
226 /*
227  * Copy and paste
228  */
229 static bool mouse_down = false;
230 static bool paint_rect = false;
231 static TERM_LEN mousex = 0, mousey = 0;
232 static TERM_LEN oldx, oldy;
233
234 /*
235  * Hack -- define which keys are "special"
236  */
237 static bool special_key[256];
238 static bool ignore_key[256];
239
240 /*
241  * Hack -- initialization list for "special_key"
242  */
243 static byte special_key_list[] = {
244     VK_CLEAR, VK_PAUSE, VK_CAPITAL, VK_KANA, VK_JUNJA, VK_FINAL, VK_KANJI, VK_CONVERT, VK_NONCONVERT, VK_ACCEPT, VK_MODECHANGE, VK_PRIOR, VK_NEXT, VK_END,
245     VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN, VK_SELECT, VK_PRINT, VK_EXECUTE, VK_SNAPSHOT, VK_INSERT, VK_DELETE, VK_HELP, VK_APPS, VK_NUMPAD0, VK_NUMPAD1,
246     VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_MULTIPLY, VK_ADD, VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL,
247     VK_DIVIDE, VK_F1, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19,
248     VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_NUMLOCK, VK_SCROLL, VK_ATTN, VK_CRSEL, VK_EXSEL, VK_EREOF, VK_PLAY, VK_ZOOM, VK_NONAME, VK_PA1,
249     0 /* End of List */
250 };
251
252 static byte ignore_key_list[] = {
253     VK_ESCAPE, VK_TAB, VK_SPACE, 'F', 'W', 'O', /*'H',*/ /* these are menu characters.*/
254     VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU, 0 /* End of List */
255 };
256
257 /*!
258  * @brief Validate a file
259  */
260 static void validate_file(const std::filesystem::path &s)
261 {
262     if (std::filesystem::is_regular_file(s)) {
263         return;
264     }
265
266     const auto &file = s.string();
267     quit_fmt(_("必要なファイル[%s]が見あたりません。", "Cannot find required file:\n%s"), file.data());
268 }
269
270 /*!
271  * @brief Validate a directory
272  */
273 static void validate_dir(const std::filesystem::path &s, bool vital)
274 {
275     if (std::filesystem::is_directory(s)) {
276         return;
277     }
278
279     const auto &dir = s.string();
280     if (vital) {
281         quit_fmt(_("必要なディレクトリ[%s]が見あたりません。", "Cannot find required directory:\n%s"), dir.data());
282     } else if (mkdir(dir.data())) {
283         quit_fmt("Unable to create directory:\n%s", dir.data());
284     }
285 }
286
287 /*!
288  * @brief (Windows版固有実装)Get the "size" for a window
289  */
290 static void term_getsize(term_data *td)
291 {
292     if (td->cols < 1) {
293         td->cols = 1;
294     }
295     if (td->rows < 1) {
296         td->rows = 1;
297     }
298
299     TERM_LEN wid = td->cols * td->tile_wid + td->size_ow1 + td->size_ow2;
300     TERM_LEN hgt = td->rows * td->tile_hgt + td->size_oh1 + td->size_oh2;
301
302     RECT rw, rc;
303     if (td->w) {
304         GetWindowRect(td->w, &rw);
305         GetClientRect(td->w, &rc);
306
307         td->size_wid = (rw.right - rw.left) - (rc.right - rc.left) + wid;
308         td->size_hgt = (rw.bottom - rw.top) - (rc.bottom - rc.top) + hgt;
309
310         td->pos_x = rw.left;
311         td->pos_y = rw.top;
312     } else {
313         /* Tempolary calculation */
314         rc.left = 0;
315         rc.right = wid;
316         rc.top = 0;
317         rc.bottom = hgt;
318         AdjustWindowRectEx(&rc, td->dwStyle, TRUE, td->dwExStyle);
319         td->size_wid = rc.right - rc.left;
320         td->size_hgt = rc.bottom - rc.top;
321     }
322 }
323
324 /*!
325  * @brief Write the "prefs" for a single term
326  */
327 static void save_prefs_aux(int i)
328 {
329     term_data *td = &data[i];
330     GAME_TEXT sec_name[128];
331     char buf[1024];
332
333     if (!td->w) {
334         return;
335     }
336
337     wsprintfA(sec_name, "Term-%d", i);
338
339     if (i > 0) {
340         strcpy(buf, td->visible ? "1" : "0");
341         WritePrivateProfileStringA(sec_name, "Visible", buf, ini_file);
342     }
343
344     auto pwchar = td->lf.lfFaceName[0] != '\0' ? td->lf.lfFaceName : _(L"MS ゴシック", L"Courier");
345     WritePrivateProfileStringA(sec_name, "Font", to_multibyte(pwchar).c_str(), ini_file);
346
347     wsprintfA(buf, "%d", td->lf.lfWidth);
348     WritePrivateProfileStringA(sec_name, "FontWid", buf, ini_file);
349     wsprintfA(buf, "%d", td->lf.lfHeight);
350     WritePrivateProfileStringA(sec_name, "FontHgt", buf, ini_file);
351     wsprintfA(buf, "%d", td->lf.lfWeight);
352     WritePrivateProfileStringA(sec_name, "FontWgt", buf, ini_file);
353
354     wsprintfA(buf, "%d", td->tile_wid);
355     WritePrivateProfileStringA(sec_name, "TileWid", buf, ini_file);
356
357     wsprintfA(buf, "%d", td->tile_hgt);
358     WritePrivateProfileStringA(sec_name, "TileHgt", buf, ini_file);
359
360     WINDOWPLACEMENT lpwndpl;
361     lpwndpl.length = sizeof(WINDOWPLACEMENT);
362     GetWindowPlacement(td->w, &lpwndpl);
363
364     RECT rc = lpwndpl.rcNormalPosition;
365     if (i == 0) {
366         wsprintfA(buf, "%d", normsize.x);
367     } else {
368         wsprintfA(buf, "%d", td->cols);
369     }
370
371     WritePrivateProfileStringA(sec_name, "NumCols", buf, ini_file);
372
373     if (i == 0) {
374         wsprintfA(buf, "%d", normsize.y);
375     } else {
376         wsprintfA(buf, "%d", td->rows);
377     }
378
379     WritePrivateProfileStringA(sec_name, "NumRows", buf, ini_file);
380     if (i == 0) {
381         strcpy(buf, IsZoomed(td->w) ? "1" : "0");
382         WritePrivateProfileStringA(sec_name, "Maximized", buf, ini_file);
383     }
384
385     GetWindowRect(td->w, &rc);
386     wsprintfA(buf, "%d", rc.left);
387     WritePrivateProfileStringA(sec_name, "PositionX", buf, ini_file);
388
389     wsprintfA(buf, "%d", rc.top);
390     WritePrivateProfileStringA(sec_name, "PositionY", buf, ini_file);
391     if (i > 0) {
392         strcpy(buf, td->posfix ? "1" : "0");
393         WritePrivateProfileStringA(sec_name, "PositionFix", buf, ini_file);
394     }
395 }
396
397 /*!
398  * @brief Write the "prefs"
399  * We assume that the windows have all been initialized
400  */
401 static void save_prefs(void)
402 {
403     char buf[128];
404     wsprintfA(buf, "%d", arg_graphics);
405     WritePrivateProfileStringA("Angband", "Graphics", buf, ini_file);
406
407     strcpy(buf, arg_bigtile ? "1" : "0");
408     WritePrivateProfileStringA("Angband", "Bigtile", buf, ini_file);
409
410     strcpy(buf, arg_sound ? "1" : "0");
411     WritePrivateProfileStringA("Angband", "Sound", buf, ini_file);
412     WritePrivateProfileStringA("Angband", "SoundVolumeTableIndex", std::to_string(arg_sound_volume_table_index).data(), ini_file);
413
414     strcpy(buf, arg_music ? "1" : "0");
415     WritePrivateProfileStringA("Angband", "Music", buf, ini_file);
416     strcpy(buf, use_pause_music_inactive ? "1" : "0");
417     WritePrivateProfileStringA("Angband", "MusicVolumeTableIndex", std::to_string(arg_music_volume_table_index).data(), ini_file);
418     WritePrivateProfileStringA("Angband", "MusicPauseInactive", buf, ini_file);
419
420     wsprintfA(buf, "%d", current_bg_mode);
421     WritePrivateProfileStringA("Angband", "BackGround", buf, ini_file);
422     const auto &wallpaper_filename = wallpaper_path.string();
423     WritePrivateProfileStringA("Angband", "BackGroundBitmap", !wallpaper_path.empty() ? wallpaper_filename.data() : DEFAULT_BG_FILENAME, ini_file);
424
425     auto angband_dir_str = ANGBAND_DIR.string();
426     const auto path_length = angband_dir_str.length() - 4; // "\lib" を除く.
427     angband_dir_str = angband_dir_str.substr(0, path_length);
428     const auto savefile_str = savefile.string();
429     if (angband_dir_str == savefile_str) {
430         const auto relative_path = format(".\\%s", (savefile_str.data() + path_length));
431         WritePrivateProfileStringA("Angband", "SaveFile", relative_path.data(), ini_file);
432     } else {
433         WritePrivateProfileStringA("Angband", "SaveFile", savefile_str.data(), ini_file);
434     }
435
436     strcpy(buf, keep_subwindows ? "1" : "0");
437     WritePrivateProfileStringA("Angband", "KeepSubwindows", buf, ini_file);
438
439     for (int i = 0; i < MAX_TERM_DATA; ++i) {
440         save_prefs_aux(i);
441     }
442 }
443
444 /*!
445  * @brief callback for EnumDisplayMonitors API
446  */
447 BOOL CALLBACK monitor_enum_procedure([[maybe_unused]] HMONITOR hMon, [[maybe_unused]] HDC hdcMon, [[maybe_unused]] LPRECT lpMon, LPARAM dwDate)
448 {
449     bool *result = (bool *)dwDate;
450     *result = true;
451     return false;
452 }
453
454 /*!
455  * @brief Load the "prefs" for a single term
456  */
457 static void load_prefs_aux(int i)
458 {
459     term_data *td = &data[i];
460     GAME_TEXT sec_name[128];
461     char tmp[1024];
462
463     wsprintfA(sec_name, "Term-%d", i);
464     if (i > 0) {
465         td->visible = (GetPrivateProfileIntA(sec_name, "Visible", td->visible, ini_file) != 0);
466     }
467
468     GetPrivateProfileStringA(sec_name, "Font", _("MS ゴシック", "Courier"), tmp, 127, ini_file);
469
470     td->font_want = string_make(tmp);
471     int hgt = 15;
472     int wid = 0;
473     td->lf.lfWidth = GetPrivateProfileIntA(sec_name, "FontWid", wid, ini_file);
474     td->lf.lfHeight = GetPrivateProfileIntA(sec_name, "FontHgt", hgt, ini_file);
475     td->lf.lfWeight = GetPrivateProfileIntA(sec_name, "FontWgt", 0, ini_file);
476
477     td->tile_wid = GetPrivateProfileIntA(sec_name, "TileWid", td->lf.lfWidth, ini_file);
478     td->tile_hgt = GetPrivateProfileIntA(sec_name, "TileHgt", td->lf.lfHeight, ini_file);
479
480     td->cols = GetPrivateProfileIntA(sec_name, "NumCols", td->cols, ini_file);
481     td->rows = GetPrivateProfileIntA(sec_name, "NumRows", td->rows, ini_file);
482     normsize.x = td->cols;
483     normsize.y = td->rows;
484
485     if (i == 0) {
486         win_maximized = (GetPrivateProfileIntA(sec_name, "Maximized", win_maximized, ini_file) != 0);
487     }
488
489     int posx = GetPrivateProfileIntA(sec_name, "PositionX", 0, ini_file);
490     int posy = GetPrivateProfileIntA(sec_name, "PositionY", 0, ini_file);
491     // 保存座標がモニタ内の領域にあるかチェック
492     RECT rect = { posx, posy, posx + 128, posy + 128 };
493     bool in_any_monitor = false;
494     ::EnumDisplayMonitors(NULL, &rect, monitor_enum_procedure, (LPARAM)&in_any_monitor);
495     if (in_any_monitor) {
496         // いずれかのモニタに表示可能、ウインドウ位置を復元
497         td->pos_x = posx;
498         td->pos_y = posy;
499     }
500
501     if (i > 0) {
502         td->posfix = (GetPrivateProfileIntA(sec_name, "PositionFix", td->posfix, ini_file) != 0);
503     }
504 }
505
506 /*!
507  * @brief Load the "prefs"
508  */
509 static void load_prefs(void)
510 {
511     arg_graphics = (byte)GetPrivateProfileIntA("Angband", "Graphics", enum2i(graphics_mode::GRAPHICS_NONE), ini_file);
512     arg_bigtile = (GetPrivateProfileIntA("Angband", "Bigtile", false, ini_file) != 0);
513     use_bigtile = arg_bigtile;
514     arg_sound = (GetPrivateProfileIntA("Angband", "Sound", 0, ini_file) != 0);
515     arg_sound_volume_table_index = std::clamp<int>(GetPrivateProfileIntA("Angband", "SoundVolumeTableIndex", 0, ini_file), 0, SOUND_VOLUME_TABLE.size() - 1);
516     arg_music = (GetPrivateProfileIntA("Angband", "Music", 0, ini_file) != 0);
517     arg_music_volume_table_index = std::clamp<int>(GetPrivateProfileIntA("Angband", "MusicVolumeTableIndex", 0, ini_file), 0, main_win_music::VOLUME_TABLE.size() - 1);
518     use_pause_music_inactive = (GetPrivateProfileIntA("Angband", "MusicPauseInactive", 0, ini_file) != 0);
519     current_bg_mode = static_cast<bg_mode>(GetPrivateProfileIntA("Angband", "BackGround", 0, ini_file));
520     char wallpaper_buf[1024]{};
521     GetPrivateProfileStringA("Angband", "BackGroundBitmap", DEFAULT_BG_FILENAME, wallpaper_buf, 1023, ini_file);
522     wallpaper_path = wallpaper_buf;
523     char savefile_buf[1024]{};
524     GetPrivateProfileStringA("Angband", "SaveFile", "", savefile_buf, 1023, ini_file);
525     if (strncmp(".\\", savefile_buf, 2) == 0) {
526         std::string angband_dir_str(ANGBAND_DIR.string());
527         const auto path_length = angband_dir_str.length() - 4; // "\lib" を除く.
528         angband_dir_str = angband_dir_str.substr(0, path_length);
529         char tmp[1024] = "";
530         strncat(tmp, angband_dir_str.data(), path_length);
531         strncat(tmp, savefile_buf + 2, std::string_view(savefile_buf).length() - 2 + path_length);
532         savefile = tmp;
533     } else {
534         savefile = savefile_buf;
535     }
536
537     keep_subwindows = (GetPrivateProfileIntA("Angband", "KeepSubwindows", 0, ini_file) != 0);
538     for (int i = 0; i < MAX_TERM_DATA; ++i) {
539         load_prefs_aux(i);
540     }
541 }
542
543 /*!
544  * @brief Initialize music
545  */
546 static void init_music(void)
547 {
548     // Flag set once "music" has been initialized
549     static bool can_use_music = false;
550
551     if (!can_use_music) {
552         main_win_music::load_music_prefs();
553         can_use_music = true;
554     }
555 }
556
557 /*!
558  * @brief Initialize sound
559  */
560 static void init_sound(void)
561 {
562     // Flag set once "sound" has been initialized
563     static bool can_use_sound = false;
564
565     if (!can_use_sound) {
566         load_sound_prefs();
567         can_use_sound = true;
568     }
569 }
570
571 /*!
572  * @brief Change sound mode
573  * @param new_mode bool
574  */
575 static void change_sound_mode(bool new_mode)
576 {
577     use_sound = new_mode;
578     if (use_sound) {
579         init_sound();
580     }
581 }
582
583 /*!
584  * @brief Initialize background
585  */
586 static void init_background(void)
587 {
588     // Flag set once "background" has been initialized
589     static bool can_use_background = false;
590
591     if (!can_use_background) {
592         load_bg_prefs();
593         can_use_background = true;
594     }
595 }
596
597 /*!
598  * @brief Change background mode
599  * @param new_mode bg_mode
600  * @param show_error trueに設定した場合のみ、エラーダイアログを表示する
601  * @param force_redraw trueの場合、モード変更に関わらずウインドウを再描画する
602  * @retval true success
603  * @retval false failed
604  */
605 static bool change_bg_mode(bg_mode new_mode, bool show_error = false, bool force_redraw = false)
606 {
607     bg_mode old_bg_mode = current_bg_mode;
608     current_bg_mode = new_mode;
609     if (current_bg_mode != bg_mode::BG_NONE) {
610         init_background();
611         if (!load_bg(wallpaper_path)) {
612             current_bg_mode = bg_mode::BG_NONE;
613             if (show_error) {
614                 const auto &wallaper_filename = wallpaper_path.string();
615                 plog_fmt(_("壁紙用ファイル '%s' を読み込めません。", "Can't load the image file '%s'."), wallaper_filename.data());
616             }
617         }
618     } else {
619         delete_bg();
620     }
621
622     const bool mode_changed = (current_bg_mode != old_bg_mode);
623     if (mode_changed || force_redraw) {
624         // 全ウインドウ再描画
625         term_type *old = game_term;
626         for (int i = 0; i < MAX_TERM_DATA; i++) {
627             term_data *td = &data[i];
628             if (td->visible) {
629                 term_activate(&td->t);
630                 term_redraw();
631             }
632         }
633         term_activate(old);
634     }
635
636     return current_bg_mode == new_mode;
637 }
638
639 /*!
640  * @brief Resize a window
641  */
642 static void term_window_resize(term_data *td)
643 {
644     if (!td->w) {
645         return;
646     }
647
648     SetWindowPos(td->w, 0, 0, 0, td->size_wid, td->size_hgt, SWP_NOMOVE | SWP_NOZORDER);
649     if (!td->size_hack) {
650         td->dispose_offscreen();
651         term_activate(&td->t);
652         term_redraw();
653     }
654 }
655
656 /*!
657  * @brief Force the use of a new font for a term_data.
658  * This function may be called before the "window" is ready.
659  * This function returns zero only if everything succeeds.
660  * @note that the "font name" must be capitalized!!!
661  */
662 static errr term_force_font(term_data *td)
663 {
664     if (td->font_id) {
665         DeleteObject(td->font_id);
666     }
667
668     td->font_id = CreateFontIndirectW(&(td->lf));
669     int wid = td->lf.lfWidth;
670     int hgt = td->lf.lfHeight;
671     if (!td->font_id) {
672         return 1;
673     }
674
675     if (!wid || !hgt) {
676         HDC hdcDesktop;
677         HFONT hfOld;
678         TEXTMETRIC tm;
679
680         hdcDesktop = GetDC(HWND_DESKTOP);
681         hfOld = static_cast<HFONT>(SelectObject(hdcDesktop, td->font_id));
682         GetTextMetrics(hdcDesktop, &tm);
683         SelectObject(hdcDesktop, hfOld);
684         ReleaseDC(HWND_DESKTOP, hdcDesktop);
685
686         wid = tm.tmAveCharWidth;
687         hgt = tm.tmHeight;
688     }
689
690     td->font_wid = wid;
691     td->font_hgt = hgt;
692
693     return 0;
694 }
695
696 /*!
697  * @brief Allow the user to change the font for this window.
698  */
699 static void term_change_font(term_data *td)
700 {
701     CHOOSEFONTW cf;
702     memset(&cf, 0, sizeof(cf));
703     cf.lStructSize = sizeof(cf);
704     cf.Flags = CF_SCREENFONTS | CF_FIXEDPITCHONLY | CF_NOVERTFONTS | CF_INITTOLOGFONTSTRUCT;
705     cf.lpLogFont = &(td->lf);
706
707     if (!ChooseFontW(&cf)) {
708         return;
709     }
710
711     term_force_font(td);
712     td->tile_wid = td->font_wid;
713     td->tile_hgt = td->font_hgt;
714     term_getsize(td);
715     term_window_resize(td);
716 }
717
718 /*!
719  * @brief Allow the user to lock this window.
720  */
721 static void term_window_pos(term_data *td, HWND hWnd)
722 {
723     SetWindowPos(td->w, hWnd, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
724 }
725
726 /*!
727  * @brief Hack -- redraw a term_data
728  */
729 static void term_data_redraw(term_data *td)
730 {
731     term_activate(&td->t);
732     term_redraw();
733     term_activate(term_screen);
734 }
735
736 /*!
737  * @brief termの反転色表示
738  */
739 void term_inversed_area(HWND hWnd, int x, int y, int w, int h)
740 {
741     term_data *td = (term_data *)GetWindowLong(hWnd, 0);
742     int tx = td->size_ow1 + x * td->tile_wid;
743     int ty = td->size_oh1 + y * td->tile_hgt;
744     int tw = w * td->tile_wid - 1;
745     int th = h * td->tile_hgt - 1;
746
747     HDC hdc = td->get_hdc();
748     HBRUSH myBrush = CreateSolidBrush(RGB(255, 255, 255));
749     HBRUSH oldBrush = static_cast<HBRUSH>(SelectObject(hdc, myBrush));
750     HPEN oldPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
751
752     PatBlt(hdc, tx, ty, tw, th, PATINVERT);
753
754     SelectObject(hdc, oldBrush);
755     SelectObject(hdc, oldPen);
756
757     RECT rect{ tx, ty, tx + tw, ty + th };
758     td->refresh(&rect);
759 }
760
761 /*!
762  * @brief Windows版ユーザ設定項目実装部(実装必須) /Interact with the User
763  */
764 static errr term_user_win(int n)
765 {
766     (void)n;
767     return 0;
768 }
769
770 /*!
771  * @brief カラーパレットの変更?
772  */
773 static void refresh_color_table()
774 {
775     for (int i = 0; i < 256; i++) {
776         byte rv = angband_color_table[i][1];
777         byte gv = angband_color_table[i][2];
778         byte bv = angband_color_table[i][3];
779         win_clr[i] = PALETTERGB(rv, gv, bv);
780     }
781 }
782
783 /*!
784  * @brief グラフィクスのモード変更
785  */
786 static void change_graphics_mode(graphics_mode mode)
787 {
788     graphics_mode ret = graphic.change_graphics(mode);
789     if (ret != mode) {
790         plog(_("グラフィクスを初期化できません!", "Cannot initialize graphics!"));
791     }
792     arg_graphics = static_cast<byte>(ret);
793     use_graphics = (arg_graphics > 0);
794 }
795
796 /*!
797  * @brief ターミナルのサイズ更新
798  * @details 行数、列数の変更に対応する。
799  * @param td term_dataのポインタ
800  * @param resize_window trueの場合に再計算されたウインドウサイズにリサイズする
801  */
802 static void rebuild_term(term_data *td, bool resize_window = true)
803 {
804     term_type *old = game_term;
805     td->size_hack = true;
806     term_activate(&td->t);
807     term_getsize(td);
808     if (resize_window) {
809         term_window_resize(td);
810     }
811     td->dispose_offscreen();
812     term_resize(td->cols, td->rows);
813     td->size_hack = false;
814     term_activate(old);
815 }
816
817 /*!
818  * @brief React to global changes
819  */
820 static errr term_xtra_win_react(PlayerType *player_ptr)
821 {
822     refresh_color_table();
823
824     const byte current_mode = static_cast<byte>(graphic.get_mode());
825     if (current_mode != arg_graphics) {
826         change_graphics_mode(static_cast<graphics_mode>(arg_graphics));
827         reset_visuals(player_ptr);
828     }
829
830     for (int i = 0; i < MAX_TERM_DATA; i++) {
831         term_data *td = &data[i];
832         if ((td->cols != td->t.wid) || (td->rows != td->t.hgt)) {
833             rebuild_term(td);
834         }
835     }
836
837     return 0;
838 }
839
840 /*!
841  * @brief Process at least one event
842  */
843 static errr term_xtra_win_event(int v)
844 {
845     MSG msg;
846     if (v) {
847         if (GetMessage(&msg, NULL, 0, 0)) {
848             TranslateMessage(&msg);
849             DispatchMessage(&msg);
850         }
851     } else {
852         if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
853             TranslateMessage(&msg);
854             DispatchMessage(&msg);
855         }
856     }
857
858     return 0;
859 }
860
861 /*!
862  * @brief Process all pending events
863  */
864 static errr term_xtra_win_flush(void)
865 {
866     MSG msg;
867     while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
868         TranslateMessage(&msg);
869         DispatchMessage(&msg);
870     }
871
872     return 0;
873 }
874
875 /*!
876  * @brief Hack -- clear the screen
877  * @details
878  * Make this more efficient
879  */
880 static errr term_xtra_win_clear(void)
881 {
882     term_data *td = (term_data *)(game_term->data);
883
884     RECT rc;
885     GetClientRect(td->w, &rc);
886
887     HDC hdc = td->get_hdc();
888     SetBkColor(hdc, RGB(0, 0, 0));
889     SelectObject(hdc, td->font_id);
890     ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
891
892     if (current_bg_mode != bg_mode::BG_NONE) {
893         rc.left = 0;
894         rc.top = 0;
895         draw_bg(hdc, &rc);
896     }
897
898     td->refresh();
899     return 0;
900 }
901
902 /*!
903  * @brief Hack -- make a noise
904  */
905 static errr term_xtra_win_noise(void)
906 {
907     MessageBeep(MB_ICONASTERISK);
908     return 0;
909 }
910
911 /*!
912  * @brief Hack -- make a sound
913  */
914 static errr term_xtra_win_sound(int v)
915 {
916     if (!use_sound) {
917         return 1;
918     }
919     return play_sound(v, SOUND_VOLUME_TABLE[arg_sound_volume_table_index]);
920 }
921
922 /*!
923  * @brief Hack -- play a music
924  */
925 static errr term_xtra_win_music(int n, int v)
926 {
927     if (!use_music) {
928         return 1;
929     }
930
931     return main_win_music::play_music(n, v);
932 }
933
934 /*!
935  * @brief Hack -- play a music matches a situation
936  */
937 static errr term_xtra_win_scene(int v)
938 {
939     // TODO 場面に合った壁紙変更対応
940     if (!use_music) {
941         return 1;
942     }
943
944     return main_win_music::play_music_scene(v);
945 }
946
947 /*!
948  * @brief Delay for "x" milliseconds
949  */
950 static int term_xtra_win_delay(int v)
951 {
952     Sleep(v);
953     return 0;
954 }
955
956 /*!
957  * @brief Do a "special thing"
958  * @todo z-termに影響があるのでPlayerTypeの追加は保留
959  */
960 static errr term_xtra_win(int n, int v)
961 {
962     switch (n) {
963     case TERM_XTRA_NOISE: {
964         return term_xtra_win_noise();
965     }
966     case TERM_XTRA_FRESH: {
967         term_data *td = (term_data *)(game_term->data);
968         if (td->w) {
969             UpdateWindow(td->w);
970         }
971         return 0;
972     }
973     case TERM_XTRA_MUSIC_BASIC:
974     case TERM_XTRA_MUSIC_DUNGEON:
975     case TERM_XTRA_MUSIC_QUEST:
976     case TERM_XTRA_MUSIC_TOWN:
977     case TERM_XTRA_MUSIC_MONSTER: {
978         return term_xtra_win_music(n, v);
979     }
980     case TERM_XTRA_MUSIC_MUTE: {
981         return main_win_music::stop_music();
982     }
983     case TERM_XTRA_SCENE: {
984         return term_xtra_win_scene(v);
985     }
986     case TERM_XTRA_SOUND: {
987         return term_xtra_win_sound(v);
988     }
989     case TERM_XTRA_BORED: {
990         return term_xtra_win_event(0);
991     }
992     case TERM_XTRA_EVENT: {
993         return term_xtra_win_event(v);
994     }
995     case TERM_XTRA_FLUSH: {
996         return term_xtra_win_flush();
997     }
998     case TERM_XTRA_CLEAR: {
999         return term_xtra_win_clear();
1000     }
1001     case TERM_XTRA_REACT: {
1002         return term_xtra_win_react(p_ptr);
1003     }
1004     case TERM_XTRA_DELAY: {
1005         return term_xtra_win_delay(v);
1006     }
1007     }
1008
1009     return 1;
1010 }
1011
1012 /*!
1013  * @brief Low level graphics (Assumes valid input).
1014  * @details
1015  * Draw a "cursor" at (x,y), using a "yellow box".
1016  */
1017 static errr term_curs_win(int x, int y)
1018 {
1019     term_data *td = (term_data *)(game_term->data);
1020     int tile_wid, tile_hgt;
1021     tile_wid = td->tile_wid;
1022     tile_hgt = td->tile_hgt;
1023
1024     RECT rc;
1025     rc.left = x * tile_wid + td->size_ow1;
1026     rc.right = rc.left + tile_wid;
1027     rc.top = y * tile_hgt + td->size_oh1;
1028     rc.bottom = rc.top + tile_hgt;
1029
1030     HDC hdc = td->get_hdc();
1031     FrameRect(hdc, &rc, hbrYellow);
1032     td->refresh(&rc);
1033     return 0;
1034 }
1035
1036 /*!
1037  * @brief Low level graphics (Assumes valid input).
1038  * @details
1039  * Draw a "big cursor" at (x,y), using a "yellow box".
1040  */
1041 static errr term_bigcurs_win(int x, int y)
1042 {
1043     term_data *td = (term_data *)(game_term->data);
1044     int tile_wid, tile_hgt;
1045     tile_wid = td->tile_wid;
1046     tile_hgt = td->tile_hgt;
1047
1048     RECT rc;
1049     rc.left = x * tile_wid + td->size_ow1;
1050     rc.right = rc.left + 2 * tile_wid;
1051     rc.top = y * tile_hgt + td->size_oh1;
1052     rc.bottom = rc.top + tile_hgt;
1053
1054     HDC hdc = td->get_hdc();
1055     FrameRect(hdc, &rc, hbrYellow);
1056     td->refresh(&rc);
1057     return 0;
1058 }
1059
1060 /*!
1061  * @brief Low level graphics (Assumes valid input).
1062  * @details
1063  * Erase a "block" of "n" characters starting at (x,y).
1064  */
1065 static errr term_wipe_win(int x, int y, int n)
1066 {
1067     term_data *td = (term_data *)(game_term->data);
1068     RECT rc;
1069     rc.left = x * td->tile_wid + td->size_ow1;
1070     rc.right = rc.left + n * td->tile_wid;
1071     rc.top = y * td->tile_hgt + td->size_oh1;
1072     rc.bottom = rc.top + td->tile_hgt;
1073
1074     HDC hdc = td->get_hdc();
1075     SetBkColor(hdc, RGB(0, 0, 0));
1076     SelectObject(hdc, td->font_id);
1077     if (current_bg_mode != bg_mode::BG_NONE) {
1078         draw_bg(hdc, &rc);
1079     } else {
1080         ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
1081     }
1082
1083     td->refresh(&rc);
1084     return 0;
1085 }
1086
1087 /*!
1088  * @brief Low level graphics.  Assumes valid input.
1089  * @details
1090  * Draw several ("n") chars, with an attr, at a given location.
1091  *
1092  * All "graphic" data is handled by "term_pict_win()", below.
1093  *
1094  * One would think there is a more efficient method for telling a window
1095  * what color it should be using to draw with, but perhaps simply changing
1096  * it every time is not too inefficient.
1097  */
1098 static errr term_text_win(int x, int y, int n, TERM_COLOR a, concptr s)
1099 {
1100     term_data *td = (term_data *)(game_term->data);
1101     static HBITMAP WALL;
1102     static HBRUSH myBrush, oldBrush;
1103     static HPEN oldPen;
1104     static bool init_done = false;
1105
1106     if (!init_done) {
1107         WALL = LoadBitmapW(hInstance, AppName);
1108         myBrush = CreatePatternBrush(WALL);
1109         init_done = true;
1110     }
1111
1112     RECT rc{ static_cast<LONG>(x * td->tile_wid + td->size_ow1), static_cast<LONG>(y * td->tile_hgt + td->size_oh1),
1113         static_cast<LONG>(rc.left + n * td->tile_wid), static_cast<LONG>(rc.top + td->tile_hgt) };
1114     RECT rc_start = rc;
1115
1116     HDC hdc = td->get_hdc();
1117     SetBkColor(hdc, RGB(0, 0, 0));
1118     SetTextColor(hdc, win_clr[a]);
1119
1120     SelectObject(hdc, td->font_id);
1121     if (current_bg_mode != bg_mode::BG_NONE) {
1122         SetBkMode(hdc, TRANSPARENT);
1123     }
1124
1125     ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
1126     if (current_bg_mode != bg_mode::BG_NONE) {
1127         draw_bg(hdc, &rc);
1128     }
1129
1130     rc.left += ((td->tile_wid - td->font_wid) / 2);
1131     rc.right = rc.left + td->font_wid;
1132     rc.top += ((td->tile_hgt - td->font_hgt) / 2);
1133     rc.bottom = rc.top + td->font_hgt;
1134
1135     for (int i = 0; i < n; i++) {
1136 #ifdef JP
1137         if (use_bigtile && *(s + i) == "■"[0] && *(s + i + 1) == "■"[1]) {
1138             rc.right += td->font_wid;
1139             oldBrush = static_cast<HBRUSH>(SelectObject(hdc, myBrush));
1140             oldPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
1141             Rectangle(hdc, rc.left, rc.top, rc.right + 1, rc.bottom + 1);
1142             SelectObject(hdc, oldBrush);
1143             SelectObject(hdc, oldPen);
1144             rc.right -= td->font_wid;
1145             i++;
1146             rc.left += 2 * td->tile_wid;
1147             rc.right += 2 * td->tile_wid;
1148         } else if (iskanji(*(s + i))) /* 2バイト文字 */
1149         {
1150             char tmp[] = { *(s + i), *(s + i + 1), '\0' };
1151             to_wchar wc(tmp);
1152             const auto *buf = wc.wc_str();
1153             rc.right += td->font_wid;
1154             if (buf == NULL) {
1155                 ExtTextOutA(hdc, rc.left, rc.top, ETO_CLIPPED, &rc, s + i, 2, NULL);
1156             } else {
1157                 ExtTextOutW(hdc, rc.left, rc.top, ETO_CLIPPED, &rc, buf, wcslen(buf), NULL);
1158             }
1159             rc.right -= td->font_wid;
1160             i++;
1161             rc.left += 2 * td->tile_wid;
1162             rc.right += 2 * td->tile_wid;
1163         } else if (*(s + i) == 127) {
1164             oldBrush = static_cast<HBRUSH>(SelectObject(hdc, myBrush));
1165             oldPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
1166             Rectangle(hdc, rc.left, rc.top, rc.right + 1, rc.bottom + 1);
1167             SelectObject(hdc, oldBrush);
1168             SelectObject(hdc, oldPen);
1169             rc.left += td->tile_wid;
1170             rc.right += td->tile_wid;
1171         } else {
1172             ExtTextOutA(hdc, rc.left, rc.top, ETO_CLIPPED, &rc, s + i, 1, NULL);
1173             rc.left += td->tile_wid;
1174             rc.right += td->tile_wid;
1175         }
1176 #else
1177         if (*(s + i) == 127) {
1178             oldBrush = static_cast<HBRUSH>(SelectObject(hdc, myBrush));
1179             oldPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
1180             Rectangle(hdc, rc.left, rc.top, rc.right + 1, rc.bottom + 1);
1181             SelectObject(hdc, oldBrush);
1182             SelectObject(hdc, oldPen);
1183             rc.left += td->tile_wid;
1184             rc.right += td->tile_wid;
1185         } else {
1186             ExtTextOutA(hdc, rc.left, rc.top, ETO_CLIPPED, &rc, s + i, 1, NULL);
1187             rc.left += td->tile_wid;
1188             rc.right += td->tile_wid;
1189         }
1190 #endif
1191     }
1192
1193     rc.left = rc_start.left;
1194     rc.top = rc_start.top;
1195     td->refresh(&rc);
1196     return 0;
1197 }
1198
1199 /*!
1200  * @brief Low level graphics.  Assumes valid input.
1201  * @details
1202  * Draw an array of "special" attr/char pairs at the given location.
1203  *
1204  * We use the "term_pict_win()" function for "graphic" data, which are
1205  * encoded by setting the "high-bits" of both the "attr" and the "char"
1206  * data.  We use the "attr" to represent the "row" of the main bitmap,
1207  * and the "char" to represent the "col" of the main bitmap.  The use
1208  * of this function is induced by the "higher_pict" flag.
1209  *
1210  * If "graphics" is not available, we simply "wipe" the given grids.
1211  */
1212 static errr term_pict_win(TERM_LEN x, TERM_LEN y, int n, const TERM_COLOR *ap, concptr cp, const TERM_COLOR *tap, concptr tcp)
1213 {
1214     term_data *td = (term_data *)(game_term->data);
1215     int i;
1216     HDC hdcMask = NULL;
1217     if (!use_graphics) {
1218         return term_wipe_win(x, y, n);
1219     }
1220
1221     const tile_info &infGraph = graphic.get_tile_info();
1222     const bool has_mask = (infGraph.hBitmapMask != NULL);
1223     TERM_LEN w1 = infGraph.CellWidth;
1224     TERM_LEN h1 = infGraph.CellHeight;
1225     TERM_LEN tw1 = infGraph.TileWidth;
1226     TERM_LEN th1 = infGraph.TileHeight;
1227     TERM_LEN w2, h2, tw2 = 0;
1228     w2 = td->tile_wid;
1229     h2 = td->tile_hgt;
1230     tw2 = w2;
1231     if (use_bigtile) {
1232         tw2 *= 2;
1233     }
1234
1235     TERM_LEN x2 = x * w2 + td->size_ow1 + infGraph.OffsetX;
1236     TERM_LEN y2 = y * h2 + td->size_oh1 + infGraph.OffsetY;
1237     HDC hdc = td->get_hdc();
1238     HDC hdcSrc = CreateCompatibleDC(hdc);
1239     HBITMAP hbmSrcOld = static_cast<HBITMAP>(SelectObject(hdcSrc, infGraph.hBitmap));
1240
1241     if (has_mask) {
1242         hdcMask = CreateCompatibleDC(hdc);
1243         SelectObject(hdcMask, infGraph.hBitmapMask);
1244     }
1245
1246     for (i = 0; i < n; i++, x2 += w2) {
1247         TERM_COLOR a = ap[i];
1248         char c = cp[i];
1249         int row = (a & 0x7F);
1250         int col = (c & 0x7F);
1251         TERM_LEN x1 = col * w1;
1252         TERM_LEN y1 = row * h1;
1253
1254         if (has_mask) {
1255             TERM_LEN x3 = (tcp[i] & 0x7F) * w1;
1256             TERM_LEN y3 = (tap[i] & 0x7F) * h1;
1257             tw2 = tw2 * w1 / tw1;
1258             h2 = h2 * h1 / th1;
1259             if ((tw1 == tw2) && (th1 == h2)) {
1260                 BitBlt(hdc, x2, y2, tw2, h2, hdcSrc, x3, y3, SRCCOPY);
1261                 BitBlt(hdc, x2, y2, tw2, h2, hdcMask, x1, y1, SRCAND);
1262                 BitBlt(hdc, x2, y2, tw2, h2, hdcSrc, x1, y1, SRCPAINT);
1263                 continue;
1264             }
1265
1266             SetStretchBltMode(hdc, COLORONCOLOR);
1267             StretchBlt(hdc, x2, y2, tw2, h2, hdcMask, x3, y3, w1, h1, SRCAND);
1268             StretchBlt(hdc, x2, y2, tw2, h2, hdcSrc, x3, y3, w1, h1, SRCPAINT);
1269             if ((x1 != x3) || (y1 != y3)) {
1270                 StretchBlt(hdc, x2, y2, tw2, h2, hdcMask, x1, y1, w1, h1, SRCAND);
1271                 StretchBlt(hdc, x2, y2, tw2, h2, hdcSrc, x1, y1, w1, h1, SRCPAINT);
1272             }
1273
1274             continue;
1275         }
1276
1277         if ((w1 == tw2) && (h1 == h2)) {
1278             BitBlt(hdc, x2, y2, tw2, h2, hdcSrc, x1, y1, SRCCOPY);
1279             continue;
1280         }
1281
1282         SetStretchBltMode(hdc, COLORONCOLOR);
1283         StretchBlt(hdc, x2, y2, tw2, h2, hdcSrc, x1, y1, w1, h1, SRCCOPY);
1284     }
1285
1286     SelectObject(hdcSrc, hbmSrcOld);
1287     DeleteDC(hdcSrc);
1288     if (has_mask) {
1289         SelectObject(hdcMask, hbmSrcOld);
1290         DeleteDC(hdcMask);
1291     }
1292
1293     td->refresh();
1294     return 0;
1295 }
1296
1297 /*!
1298  * @brief Create and initialize a "term_data" given a title
1299  */
1300 static void term_data_link(term_data *td)
1301 {
1302     term_type *t = &td->t;
1303     term_init(t, td->cols, td->rows, FILE_READ_BUFF_SIZE);
1304     t->soft_cursor = true;
1305     t->higher_pict = true;
1306     t->attr_blank = TERM_WHITE;
1307     t->char_blank = ' ';
1308     t->user_hook = term_user_win;
1309     t->xtra_hook = term_xtra_win;
1310     t->curs_hook = term_curs_win;
1311     t->bigcurs_hook = term_bigcurs_win;
1312     t->wipe_hook = term_wipe_win;
1313     t->text_hook = term_text_win;
1314     t->pict_hook = term_pict_win;
1315     t->data = (vptr)(td);
1316 }
1317
1318 /*!
1319  * @brief Create the windows
1320  * @details
1321  * First, instantiate the "default" values, then read the "ini_file"
1322  * to over-ride selected values, then create the windows, and fonts.
1323  *
1324  * Must use SW_SHOW not SW_SHOWNA, since on 256 color display
1325  * must make active to realize the palette.
1326  */
1327 static void init_windows(void)
1328 {
1329     term_data *td;
1330     td = &data[0];
1331     *td = {};
1332     td->name = win_term_name[0];
1333
1334     td->rows = MAIN_TERM_MIN_ROWS;
1335     td->cols = MAIN_TERM_MIN_COLS;
1336     td->visible = true;
1337     td->size_ow1 = 2;
1338     td->size_ow2 = 2;
1339     td->size_oh1 = 2;
1340     td->size_oh2 = 2;
1341     td->pos_x = 7 * 30;
1342     td->pos_y = 7 * 20;
1343     td->posfix = false;
1344
1345     for (int i = 1; i < MAX_TERM_DATA; i++) {
1346         td = &data[i];
1347         *td = {};
1348         td->name = win_term_name[i];
1349         td->rows = TERM_DEFAULT_ROWS;
1350         td->cols = TERM_DEFAULT_COLS;
1351         td->visible = false;
1352         td->size_ow1 = 1;
1353         td->size_ow2 = 1;
1354         td->size_oh1 = 1;
1355         td->size_oh2 = 1;
1356         td->pos_x = (7 - i) * 30;
1357         td->pos_y = (7 - i) * 20;
1358         td->posfix = false;
1359     }
1360
1361     load_prefs();
1362
1363     /* Atrributes of main window */
1364     td = &data[0];
1365     td->dwStyle = (WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CAPTION | WS_VISIBLE);
1366     td->dwExStyle = 0;
1367     td->visible = true;
1368
1369     /* Attributes of sub windows */
1370     for (int i = 1; i < MAX_TERM_DATA; i++) {
1371         td = &data[i];
1372         td->dwStyle = (WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU);
1373         td->dwExStyle = (WS_EX_TOOLWINDOW);
1374     }
1375
1376     /* Font of each window */
1377     for (int i = 0; i < MAX_TERM_DATA; i++) {
1378         td = &data[i];
1379         wcsncpy(td->lf.lfFaceName, to_wchar(td->font_want).wc_str(), LF_FACESIZE);
1380         td->lf.lfCharSet = _(SHIFTJIS_CHARSET, DEFAULT_CHARSET);
1381         td->lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1382         term_force_font(td);
1383         if (!td->tile_wid) {
1384             td->tile_wid = td->font_wid;
1385         }
1386         if (!td->tile_hgt) {
1387             td->tile_hgt = td->font_hgt;
1388         }
1389         term_getsize(td);
1390         term_window_resize(td);
1391     }
1392
1393     /* Create sub windows */
1394     for (int i = MAX_TERM_DATA - 1; i >= 1; --i) {
1395         td = &data[i];
1396
1397         my_td = td;
1398         td->w = CreateWindowExW(
1399             td->dwExStyle, AngList, td->name, td->dwStyle, td->pos_x, td->pos_y, td->size_wid, td->size_hgt, HWND_DESKTOP, NULL, hInstance, NULL);
1400         my_td = NULL;
1401
1402         if (!td->w) {
1403             quit(_("サブウィンドウに作成に失敗しました", "Failed to create sub-window"));
1404         }
1405
1406         td->size_hack = true;
1407         term_getsize(td);
1408         term_window_resize(td);
1409
1410         if (td->visible) {
1411             ShowWindow(td->w, SW_SHOW);
1412         }
1413         td->size_hack = false;
1414
1415         term_data_link(td);
1416         angband_terms[i] = &td->t;
1417
1418         if (td->visible) {
1419             /* Activate the window */
1420             SetActiveWindow(td->w);
1421         }
1422
1423         if (td->posfix) {
1424             term_window_pos(td, HWND_TOPMOST);
1425         } else {
1426             term_window_pos(td, td->w);
1427         }
1428     }
1429
1430     /* Create main window */
1431     td = &data[0];
1432     my_td = td;
1433     td->w = CreateWindowExW(
1434         td->dwExStyle, AppName, _(L"変愚蛮怒", td->name), td->dwStyle,
1435         td->pos_x, td->pos_y, td->size_wid, td->size_hgt, HWND_DESKTOP, NULL, hInstance, NULL);
1436     my_td = NULL;
1437
1438     if (!td->w) {
1439         quit(_("メインウィンドウの作成に失敗しました", "Failed to create main window"));
1440     }
1441
1442     /* Resize */
1443     td->size_hack = true;
1444     term_getsize(td);
1445     term_window_resize(td);
1446     td->size_hack = false;
1447
1448     term_data_link(td);
1449     angband_terms[0] = &td->t;
1450     normsize.x = td->cols;
1451     normsize.y = td->rows;
1452
1453     if (win_maximized) {
1454         ShowWindow(td->w, SW_SHOWMAXIMIZED);
1455     } else {
1456         ShowWindow(td->w, SW_SHOW);
1457     }
1458
1459     SetWindowPos(td->w, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
1460     hbrYellow = CreateSolidBrush(win_clr[TERM_YELLOW]);
1461     (void)term_xtra_win_flush();
1462 }
1463
1464 /*!
1465  * @brief Prepare the menus
1466  */
1467 static void setup_menus(void)
1468 {
1469     HMENU hm = GetMenu(data[0].w);
1470
1471     if (w_ptr->character_generated) {
1472         EnableMenuItem(hm, IDM_FILE_NEW, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1473         EnableMenuItem(hm, IDM_FILE_OPEN, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1474         EnableMenuItem(hm, IDM_FILE_SAVE, MF_BYCOMMAND | MF_ENABLED);
1475     } else {
1476         EnableMenuItem(hm, IDM_FILE_NEW, MF_BYCOMMAND | MF_ENABLED);
1477         EnableMenuItem(hm, IDM_FILE_OPEN, MF_BYCOMMAND | MF_ENABLED);
1478         EnableMenuItem(hm, IDM_FILE_SAVE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1479     }
1480
1481     for (int i = 0; i < MAX_TERM_DATA; i++) {
1482         EnableMenuItem(hm, IDM_WINDOW_VIS_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1483         CheckMenuItem(hm, IDM_WINDOW_VIS_0 + i, (data[i].visible ? MF_CHECKED : MF_UNCHECKED));
1484         EnableMenuItem(hm, IDM_WINDOW_VIS_0 + i, MF_BYCOMMAND | MF_ENABLED);
1485     }
1486
1487     for (int i = 0; i < MAX_TERM_DATA; i++) {
1488         EnableMenuItem(hm, IDM_WINDOW_FONT_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1489
1490         if (data[i].visible) {
1491             EnableMenuItem(hm, IDM_WINDOW_FONT_0 + i, MF_BYCOMMAND | MF_ENABLED);
1492         }
1493     }
1494
1495     for (int i = 0; i < MAX_TERM_DATA; i++) {
1496         EnableMenuItem(hm, IDM_WINDOW_POS_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1497         CheckMenuItem(hm, IDM_WINDOW_POS_0 + i, (data[i].posfix ? MF_CHECKED : MF_UNCHECKED));
1498         if (data[i].visible) {
1499             EnableMenuItem(hm, IDM_WINDOW_POS_0 + i, MF_BYCOMMAND | MF_ENABLED);
1500         }
1501     }
1502
1503     for (int i = 0; i < MAX_TERM_DATA; i++) {
1504         EnableMenuItem(hm, IDM_WINDOW_I_WID_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1505         if (data[i].visible) {
1506             EnableMenuItem(hm, IDM_WINDOW_I_WID_0 + i, MF_BYCOMMAND | MF_ENABLED);
1507         }
1508     }
1509
1510     for (int i = 0; i < MAX_TERM_DATA; i++) {
1511         EnableMenuItem(hm, IDM_WINDOW_D_WID_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1512         if (data[i].visible) {
1513             EnableMenuItem(hm, IDM_WINDOW_D_WID_0 + i, MF_BYCOMMAND | MF_ENABLED);
1514         }
1515     }
1516
1517     for (int i = 0; i < MAX_TERM_DATA; i++) {
1518         EnableMenuItem(hm, IDM_WINDOW_I_HGT_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1519         if (data[i].visible) {
1520             EnableMenuItem(hm, IDM_WINDOW_I_HGT_0 + i, MF_BYCOMMAND | MF_ENABLED);
1521         }
1522     }
1523
1524     for (int i = 0; i < MAX_TERM_DATA; i++) {
1525         EnableMenuItem(hm, IDM_WINDOW_D_HGT_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1526
1527         if (data[i].visible) {
1528             EnableMenuItem(hm, IDM_WINDOW_D_HGT_0 + i, MF_BYCOMMAND | MF_ENABLED);
1529         }
1530     }
1531     CheckMenuItem(hm, IDM_WINDOW_KEEP_SUBWINDOWS, (keep_subwindows ? MF_CHECKED : MF_UNCHECKED));
1532
1533     CheckMenuItem(hm, IDM_OPTIONS_NO_GRAPHICS, (arg_graphics == enum2i(graphics_mode::GRAPHICS_NONE) ? MF_CHECKED : MF_UNCHECKED));
1534     CheckMenuItem(hm, IDM_OPTIONS_OLD_GRAPHICS, (arg_graphics == enum2i(graphics_mode::GRAPHICS_ORIGINAL) ? MF_CHECKED : MF_UNCHECKED));
1535     CheckMenuItem(hm, IDM_OPTIONS_NEW_GRAPHICS, (arg_graphics == enum2i(graphics_mode::GRAPHICS_ADAM_BOLT) ? MF_CHECKED : MF_UNCHECKED));
1536     CheckMenuItem(hm, IDM_OPTIONS_NEW2_GRAPHICS, (arg_graphics == enum2i(graphics_mode::GRAPHICS_HENGBAND) ? MF_CHECKED : MF_UNCHECKED));
1537     CheckMenuItem(hm, IDM_OPTIONS_BIGTILE, (arg_bigtile ? MF_CHECKED : MF_UNCHECKED));
1538     CheckMenuItem(hm, IDM_OPTIONS_MUSIC, (arg_music ? MF_CHECKED : MF_UNCHECKED));
1539     CheckMenuRadioItem(hm, IDM_OPTIONS_MUSIC_VOLUME_100, IDM_OPTIONS_MUSIC_VOLUME_010,
1540         IDM_OPTIONS_MUSIC_VOLUME_100 + arg_music_volume_table_index, MF_BYCOMMAND);
1541     CheckMenuItem(hm, IDM_OPTIONS_MUSIC_PAUSE_INACTIVE, (use_pause_music_inactive ? MF_CHECKED : MF_UNCHECKED));
1542     CheckMenuItem(hm, IDM_OPTIONS_SOUND, (arg_sound ? MF_CHECKED : MF_UNCHECKED));
1543     CheckMenuRadioItem(hm, IDM_OPTIONS_SOUND_VOLUME_100, IDM_OPTIONS_SOUND_VOLUME_010,
1544         IDM_OPTIONS_SOUND_VOLUME_100 + arg_sound_volume_table_index, MF_BYCOMMAND);
1545     CheckMenuItem(hm, IDM_OPTIONS_NO_BG, ((current_bg_mode == bg_mode::BG_NONE) ? MF_CHECKED : MF_UNCHECKED));
1546     CheckMenuItem(hm, IDM_OPTIONS_BG, ((current_bg_mode == bg_mode::BG_ONE) ? MF_CHECKED : MF_UNCHECKED));
1547     CheckMenuItem(hm, IDM_OPTIONS_PRESET_BG, ((current_bg_mode == bg_mode::BG_PRESET) ? MF_CHECKED : MF_UNCHECKED));
1548     // TODO IDM_OPTIONS_PRESET_BG を有効にする
1549     EnableMenuItem(hm, IDM_OPTIONS_PRESET_BG, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1550 }
1551
1552 /*!
1553  * @brief Check for double clicked (or dragged) savefile
1554  * @details
1555  * Apparently, Windows copies the entire filename into the first
1556  * piece of the "command line string".  Perhaps we should extract
1557  * the "basename" of that filename and append it to the "save" dir.
1558  * @param savefile_option savefile path
1559  */
1560 static void check_for_save_file(const std::string &savefile_option)
1561 {
1562     if (savefile_option.empty()) {
1563         return;
1564     }
1565
1566     savefile = savefile_option;
1567     validate_file(savefile);
1568     game_in_progress = true;
1569 }
1570
1571 /*!
1572  * @brief Process a menu command
1573  */
1574 static void process_menus(PlayerType *player_ptr, WORD wCmd)
1575 {
1576     if (!initialized) {
1577         plog(_("まだ初期化中です...", "You cannot do that yet..."));
1578         return;
1579     }
1580
1581     term_data *td;
1582     OPENFILENAMEW ofn;
1583     switch (wCmd) {
1584     case IDM_FILE_NEW: {
1585         if (game_in_progress || movie_in_progress) {
1586             plog(_("プレイ中は新しいゲームを始めることができません!", "You can't start a new game while you're still playing!"));
1587         } else {
1588             game_in_progress = true;
1589             savefile = "";
1590         }
1591
1592         break;
1593     }
1594     case IDM_FILE_OPEN: {
1595         if (game_in_progress || movie_in_progress) {
1596             plog(_("プレイ中はゲームをロードすることができません!", "You can't open a new game while you're still playing!"));
1597         } else {
1598             memset(&ofn, 0, sizeof(ofn));
1599             ofn.lStructSize = sizeof(ofn);
1600             ofn.hwndOwner = data[0].w;
1601             ofn.lpstrFilter = L"Save Files (*.)\0*\0";
1602             ofn.nFilterIndex = 1;
1603             ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
1604             const auto &filename = get_open_filename(&ofn, ANGBAND_DIR_SAVE, savefile, MAIN_WIN_MAX_PATH);
1605             if (filename.has_value()) {
1606                 savefile = filename.value();
1607                 validate_file(savefile);
1608                 game_in_progress = true;
1609             }
1610         }
1611
1612         break;
1613     }
1614     case IDM_FILE_SAVE: {
1615         if (game_in_progress && w_ptr->character_generated) {
1616             if (!can_save) {
1617                 plog(_("今はセーブすることは出来ません。", "You may not do that right now."));
1618                 break;
1619             }
1620
1621             msg_flag = false;
1622             do_cmd_save_game(player_ptr, false);
1623         } else {
1624             plog(_("今、セーブすることは出来ません。", "You may not do that right now."));
1625         }
1626
1627         break;
1628     }
1629     case IDM_FILE_EXIT: {
1630         if (game_in_progress && w_ptr->character_generated) {
1631             if (!can_save) {
1632                 plog(_("今は終了できません。", "You may not do that right now."));
1633                 break;
1634             }
1635
1636             msg_flag = false;
1637             forget_lite(player_ptr->current_floor_ptr);
1638             forget_view(player_ptr->current_floor_ptr);
1639             clear_mon_lite(player_ptr->current_floor_ptr);
1640
1641             term_key_push(SPECIAL_KEY_QUIT);
1642             break;
1643         }
1644
1645         quit(nullptr);
1646         break;
1647     }
1648     case IDM_FILE_SCORE: {
1649         const auto &path = path_build(ANGBAND_DIR_APEX, "scores.raw");
1650         highscore_fd = fd_open(path, O_RDONLY);
1651         if (highscore_fd < 0) {
1652             msg_print("Score file unavailable.");
1653         } else {
1654             screen_save();
1655             term_clear();
1656             display_scores(0, MAX_HISCORES, -1, nullptr);
1657             (void)fd_close(highscore_fd);
1658             highscore_fd = -1;
1659             screen_load();
1660             term_fresh();
1661         }
1662
1663         break;
1664     }
1665     case IDM_FILE_MOVIE: {
1666         if (game_in_progress || movie_in_progress) {
1667             plog(_("プレイ中はムービーをロードすることができません!", "You can't open a movie while you're playing!"));
1668         } else {
1669             memset(&ofn, 0, sizeof(ofn));
1670             ofn.lStructSize = sizeof(ofn);
1671             ofn.hwndOwner = data[0].w;
1672             ofn.lpstrFilter = L"Angband Movie Files (*.amv)\0*.amv\0";
1673             ofn.nFilterIndex = 1;
1674             ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
1675             const auto &filename = get_open_filename(&ofn, ANGBAND_DIR_USER, savefile, MAIN_WIN_MAX_PATH);
1676             if (filename.has_value()) {
1677                 savefile = filename.value();
1678                 prepare_browse_movie_without_path_build(savefile.string());
1679                 movie_in_progress = true;
1680             }
1681         }
1682
1683         break;
1684     }
1685     case IDM_WINDOW_VIS_0: {
1686         plog(_("メインウィンドウは非表示にできません!", "You are not allowed to do that!"));
1687         break;
1688     }
1689     case IDM_WINDOW_VIS_1:
1690     case IDM_WINDOW_VIS_2:
1691     case IDM_WINDOW_VIS_3:
1692     case IDM_WINDOW_VIS_4:
1693     case IDM_WINDOW_VIS_5:
1694     case IDM_WINDOW_VIS_6:
1695     case IDM_WINDOW_VIS_7: {
1696         int i = wCmd - IDM_WINDOW_VIS_0;
1697         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1698             break;
1699         }
1700
1701         td = &data[i];
1702         if (!td->visible) {
1703             td->visible = true;
1704             ShowWindow(td->w, SW_SHOW);
1705             term_data_redraw(td);
1706         } else {
1707             td->visible = false;
1708             td->posfix = false;
1709             ShowWindow(td->w, SW_HIDE);
1710         }
1711
1712         break;
1713     }
1714     case IDM_WINDOW_FONT_0:
1715     case IDM_WINDOW_FONT_1:
1716     case IDM_WINDOW_FONT_2:
1717     case IDM_WINDOW_FONT_3:
1718     case IDM_WINDOW_FONT_4:
1719     case IDM_WINDOW_FONT_5:
1720     case IDM_WINDOW_FONT_6:
1721     case IDM_WINDOW_FONT_7: {
1722         int i = wCmd - IDM_WINDOW_FONT_0;
1723         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1724             break;
1725         }
1726
1727         td = &data[i];
1728         term_change_font(td);
1729         break;
1730     }
1731     case IDM_WINDOW_POS_1:
1732     case IDM_WINDOW_POS_2:
1733     case IDM_WINDOW_POS_3:
1734     case IDM_WINDOW_POS_4:
1735     case IDM_WINDOW_POS_5:
1736     case IDM_WINDOW_POS_6:
1737     case IDM_WINDOW_POS_7: {
1738         int i = wCmd - IDM_WINDOW_POS_0;
1739         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1740             break;
1741         }
1742
1743         td = &data[i];
1744         if (!td->posfix && td->visible) {
1745             td->posfix = true;
1746             term_window_pos(td, HWND_TOPMOST);
1747         } else {
1748             td->posfix = false;
1749             term_window_pos(td, data[0].w);
1750         }
1751
1752         break;
1753     }
1754     case IDM_WINDOW_I_WID_0:
1755     case IDM_WINDOW_I_WID_1:
1756     case IDM_WINDOW_I_WID_2:
1757     case IDM_WINDOW_I_WID_3:
1758     case IDM_WINDOW_I_WID_4:
1759     case IDM_WINDOW_I_WID_5:
1760     case IDM_WINDOW_I_WID_6:
1761     case IDM_WINDOW_I_WID_7: {
1762         int i = wCmd - IDM_WINDOW_I_WID_0;
1763         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1764             break;
1765         }
1766
1767         td = &data[i];
1768         td->tile_wid += 1;
1769         term_getsize(td);
1770         term_window_resize(td);
1771         break;
1772     }
1773     case IDM_WINDOW_D_WID_0:
1774     case IDM_WINDOW_D_WID_1:
1775     case IDM_WINDOW_D_WID_2:
1776     case IDM_WINDOW_D_WID_3:
1777     case IDM_WINDOW_D_WID_4:
1778     case IDM_WINDOW_D_WID_5:
1779     case IDM_WINDOW_D_WID_6:
1780     case IDM_WINDOW_D_WID_7: {
1781         int i = wCmd - IDM_WINDOW_D_WID_0;
1782         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1783             break;
1784         }
1785
1786         td = &data[i];
1787         td->tile_wid -= 1;
1788         term_getsize(td);
1789         term_window_resize(td);
1790         break;
1791     }
1792     case IDM_WINDOW_I_HGT_0:
1793     case IDM_WINDOW_I_HGT_1:
1794     case IDM_WINDOW_I_HGT_2:
1795     case IDM_WINDOW_I_HGT_3:
1796     case IDM_WINDOW_I_HGT_4:
1797     case IDM_WINDOW_I_HGT_5:
1798     case IDM_WINDOW_I_HGT_6:
1799     case IDM_WINDOW_I_HGT_7: {
1800         int i = wCmd - IDM_WINDOW_I_HGT_0;
1801         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1802             break;
1803         }
1804
1805         td = &data[i];
1806         td->tile_hgt += 1;
1807         term_getsize(td);
1808         term_window_resize(td);
1809         break;
1810     }
1811     case IDM_WINDOW_D_HGT_0:
1812     case IDM_WINDOW_D_HGT_1:
1813     case IDM_WINDOW_D_HGT_2:
1814     case IDM_WINDOW_D_HGT_3:
1815     case IDM_WINDOW_D_HGT_4:
1816     case IDM_WINDOW_D_HGT_5:
1817     case IDM_WINDOW_D_HGT_6:
1818     case IDM_WINDOW_D_HGT_7: {
1819         int i = wCmd - IDM_WINDOW_D_HGT_0;
1820         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1821             break;
1822         }
1823
1824         td = &data[i];
1825         td->tile_hgt -= 1;
1826         term_getsize(td);
1827         term_window_resize(td);
1828         break;
1829     }
1830     case IDM_WINDOW_KEEP_SUBWINDOWS: {
1831         keep_subwindows = !keep_subwindows;
1832         break;
1833     }
1834     case IDM_OPTIONS_NO_GRAPHICS: {
1835         if (arg_graphics != enum2i(graphics_mode::GRAPHICS_NONE)) {
1836             arg_graphics = enum2i(graphics_mode::GRAPHICS_NONE);
1837             if (game_in_progress) {
1838                 do_cmd_redraw(player_ptr);
1839             }
1840         }
1841         break;
1842     }
1843     case IDM_OPTIONS_OLD_GRAPHICS: {
1844         if (arg_graphics != enum2i(graphics_mode::GRAPHICS_ORIGINAL)) {
1845             arg_graphics = enum2i(graphics_mode::GRAPHICS_ORIGINAL);
1846             if (game_in_progress) {
1847                 do_cmd_redraw(player_ptr);
1848             }
1849         }
1850
1851         break;
1852     }
1853     case IDM_OPTIONS_NEW_GRAPHICS: {
1854         if (arg_graphics != enum2i(graphics_mode::GRAPHICS_ADAM_BOLT)) {
1855             arg_graphics = enum2i(graphics_mode::GRAPHICS_ADAM_BOLT);
1856             if (game_in_progress) {
1857                 do_cmd_redraw(player_ptr);
1858             }
1859         }
1860
1861         break;
1862     }
1863     case IDM_OPTIONS_NEW2_GRAPHICS: {
1864         if (arg_graphics != enum2i(graphics_mode::GRAPHICS_HENGBAND)) {
1865             arg_graphics = enum2i(graphics_mode::GRAPHICS_HENGBAND);
1866             if (game_in_progress) {
1867                 do_cmd_redraw(player_ptr);
1868             }
1869         }
1870
1871         break;
1872     }
1873     case IDM_OPTIONS_BIGTILE: {
1874         td = &data[0];
1875         arg_bigtile = !arg_bigtile;
1876         rebuild_term(td);
1877         break;
1878     }
1879     case IDM_OPTIONS_MUSIC: {
1880         arg_music = !arg_music;
1881         use_music = arg_music;
1882         if (use_music) {
1883             init_music();
1884             if (game_in_progress) {
1885                 select_floor_music(player_ptr);
1886             }
1887         } else {
1888             main_win_music::stop_music();
1889         }
1890         break;
1891     }
1892     case IDM_OPTIONS_MUSIC_VOLUME_100:
1893     case IDM_OPTIONS_MUSIC_VOLUME_090:
1894     case IDM_OPTIONS_MUSIC_VOLUME_080:
1895     case IDM_OPTIONS_MUSIC_VOLUME_070:
1896     case IDM_OPTIONS_MUSIC_VOLUME_060:
1897     case IDM_OPTIONS_MUSIC_VOLUME_050:
1898     case IDM_OPTIONS_MUSIC_VOLUME_040:
1899     case IDM_OPTIONS_MUSIC_VOLUME_030:
1900     case IDM_OPTIONS_MUSIC_VOLUME_020:
1901     case IDM_OPTIONS_MUSIC_VOLUME_010: {
1902         arg_music_volume_table_index = wCmd - IDM_OPTIONS_MUSIC_VOLUME_100;
1903         if (use_music) {
1904             main_win_music::set_music_volume(main_win_music::VOLUME_TABLE[arg_music_volume_table_index]);
1905         }
1906         break;
1907     }
1908     case IDM_OPTIONS_MUSIC_PAUSE_INACTIVE: {
1909         use_pause_music_inactive = !use_pause_music_inactive;
1910         break;
1911     }
1912     case IDM_OPTIONS_OPEN_MUSIC_DIR: {
1913         const auto &path = path_build(ANGBAND_DIR_XTRA_MUSIC, "music.cfg");
1914         open_dir_in_explorer(path.string());
1915         break;
1916     }
1917     case IDM_OPTIONS_SOUND: {
1918         arg_sound = !arg_sound;
1919         change_sound_mode(arg_sound);
1920         break;
1921     }
1922     case IDM_OPTIONS_SOUND_VOLUME_100:
1923     case IDM_OPTIONS_SOUND_VOLUME_090:
1924     case IDM_OPTIONS_SOUND_VOLUME_080:
1925     case IDM_OPTIONS_SOUND_VOLUME_070:
1926     case IDM_OPTIONS_SOUND_VOLUME_060:
1927     case IDM_OPTIONS_SOUND_VOLUME_050:
1928     case IDM_OPTIONS_SOUND_VOLUME_040:
1929     case IDM_OPTIONS_SOUND_VOLUME_030:
1930     case IDM_OPTIONS_SOUND_VOLUME_020:
1931     case IDM_OPTIONS_SOUND_VOLUME_010: {
1932         arg_sound_volume_table_index = wCmd - IDM_OPTIONS_SOUND_VOLUME_100;
1933         break;
1934     }
1935     case IDM_OPTIONS_OPEN_SOUND_DIR: {
1936         const auto &path = path_build(ANGBAND_DIR_XTRA_SOUND, "sound.cfg");
1937         open_dir_in_explorer(path.string());
1938         break;
1939     }
1940     case IDM_OPTIONS_NO_BG: {
1941         change_bg_mode(bg_mode::BG_NONE);
1942         break;
1943     }
1944     case IDM_OPTIONS_PRESET_BG: {
1945         change_bg_mode(bg_mode::BG_PRESET);
1946         break;
1947     }
1948     case IDM_OPTIONS_BG: {
1949         if (change_bg_mode(bg_mode::BG_ONE)) {
1950             break;
1951         }
1952         // 壁紙の設定に失敗した(ファイルが存在しない等)場合、壁紙に使うファイルを選択させる
1953     }
1954         [[fallthrough]];
1955     case IDM_OPTIONS_OPEN_BG: {
1956         memset(&ofn, 0, sizeof(ofn));
1957         ofn.lStructSize = sizeof(ofn);
1958         ofn.hwndOwner = data[0].w;
1959         ofn.lpstrFilter = L"Image Files (*.bmp;*.png;*.jpg;*.jpeg;)\0*.bmp;*.png;*.jpg;*.jpeg;\0";
1960         ofn.nFilterIndex = 1;
1961         ofn.lpstrTitle = _(L"壁紙を選んでね。", L"Choose wall paper.");
1962         ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
1963         const auto &filename = get_open_filename(&ofn, "", wallpaper_path, MAIN_WIN_MAX_PATH);
1964         if (filename.has_value()) {
1965             wallpaper_path = filename.value();
1966             change_bg_mode(bg_mode::BG_ONE, true, true);
1967         }
1968         break;
1969     }
1970     case IDM_DUMP_SCREEN_HTML: {
1971         save_screen_as_html(data[0].w);
1972         break;
1973     }
1974     }
1975 }
1976
1977 /*!
1978  * @brief Add a keypress to the "queue"
1979  */
1980 static errr term_keypress(int k)
1981 {
1982     /* Refuse to enqueue non-keys */
1983     if (!k) {
1984         return -1;
1985     }
1986
1987     /* Store the char, advance the queue */
1988     game_term->key_queue[game_term->key_head++] = (char)k;
1989
1990     /* Circular queue, handle wrap */
1991     if (game_term->key_head == game_term->key_size) {
1992         game_term->key_head = 0;
1993     }
1994
1995     if (game_term->key_head != game_term->key_tail) {
1996         return 0;
1997     }
1998
1999     return 1;
2000 }
2001
2002 /*!
2003  * @brief Add a keypress to the "queue"
2004  * @details マルチバイト文字をkey_queueに追加する。
2005  * @param str マルチバイト文字列
2006  */
2007 static void term_keypress(char *str)
2008 {
2009     if (str) {
2010         char *psrc = str;
2011         while (*psrc) {
2012             term_keypress(*psrc);
2013             ++psrc;
2014         }
2015     }
2016 }
2017
2018 /*!
2019  * @brief キーダウンのハンドラ
2020  */
2021 static bool process_keydown(WPARAM wParam, LPARAM lParam)
2022 {
2023     auto mc = any_bits(static_cast<ushort>(GetKeyState(VK_CONTROL)), 0x8000);
2024     auto ms = any_bits(static_cast<ushort>(GetKeyState(VK_SHIFT)), 0x8000);
2025     auto ma = any_bits(static_cast<ushort>(GetKeyState(VK_MENU)), 0x8000);
2026     term_no_press = ma;
2027     if (special_key[(byte)(wParam)] || (ma && !ignore_key[(byte)(wParam)])) {
2028         bool ext_key = any_bits(static_cast<ulong>(lParam), 0x1000000UL);
2029         bool numpad = false;
2030
2031         term_keypress(31);
2032         if (mc) {
2033             term_keypress('C');
2034         }
2035         if (ms) {
2036             term_keypress('S');
2037         }
2038         if (ma) {
2039             term_keypress('A');
2040         }
2041
2042         int i = LOBYTE(HIWORD(lParam));
2043         term_keypress('x');
2044         switch (wParam) {
2045         case VK_DIVIDE:
2046             term_no_press = true;
2047             [[fallthrough]];
2048         case VK_RETURN:
2049             numpad = ext_key;
2050             break;
2051         case VK_NUMPAD0:
2052         case VK_NUMPAD1:
2053         case VK_NUMPAD2:
2054         case VK_NUMPAD3:
2055         case VK_NUMPAD4:
2056         case VK_NUMPAD5:
2057         case VK_NUMPAD6:
2058         case VK_NUMPAD7:
2059         case VK_NUMPAD8:
2060         case VK_NUMPAD9:
2061         case VK_ADD:
2062         case VK_MULTIPLY:
2063         case VK_SUBTRACT:
2064         case VK_SEPARATOR:
2065         case VK_DECIMAL:
2066             term_no_press = true;
2067             [[fallthrough]];
2068         case VK_CLEAR:
2069         case VK_HOME:
2070         case VK_END:
2071         case VK_PRIOR:
2072         case VK_NEXT:
2073         case VK_INSERT:
2074         case VK_DELETE:
2075         case VK_UP:
2076         case VK_DOWN:
2077         case VK_LEFT:
2078         case VK_RIGHT:
2079             numpad = !ext_key;
2080         }
2081
2082         if (numpad) {
2083             term_keypress('K');
2084         }
2085
2086         term_keypress(hexsym[i / 16]);
2087         term_keypress(hexsym[i % 16]);
2088         term_keypress(13);
2089
2090         return 1;
2091     }
2092
2093     return 0;
2094 }
2095
2096 /*!
2097  * @brief ウィンドウのアクティブ/非アクティブのハンドラ
2098  */
2099 static void handle_app_active(HWND hWnd, UINT uMsg, WPARAM wParam, [[maybe_unused]] LPARAM lParam)
2100 {
2101     switch (uMsg) {
2102     case WM_ACTIVATEAPP: {
2103         if (wParam) {
2104             if (use_pause_music_inactive) {
2105                 main_win_music::resume_music();
2106             }
2107         } else {
2108             if (use_pause_music_inactive) {
2109                 main_win_music::pause_music();
2110             }
2111         }
2112         break;
2113     }
2114     case WM_WINDOWPOSCHANGING: {
2115         if (!IsIconic(hWnd)) {
2116             if (use_pause_music_inactive) {
2117                 main_win_music::resume_music();
2118             }
2119         }
2120         break;
2121     }
2122     }
2123 }
2124
2125 /*!
2126  * @brief ターミナルのサイズをウインドウのサイズに合わせる
2127  * @param td term_dataのポインタ
2128  * @param recalc_window_size trueの場合に行列数からウインドウサイズを再計算し設定する
2129  */
2130 static void fit_term_size_to_window(term_data *td, bool recalc_window_size = false)
2131 {
2132     RECT rc;
2133     ::GetClientRect(td->w, &rc);
2134     int width = rc.right - rc.left;
2135     int height = rc.bottom - rc.top;
2136
2137     TERM_LEN cols = (width - td->size_ow1 - td->size_ow2) / td->tile_wid;
2138     TERM_LEN rows = (height - td->size_oh1 - td->size_oh2) / td->tile_hgt;
2139     if ((td->cols != cols) || (td->rows != rows)) {
2140         td->cols = cols;
2141         td->rows = rows;
2142         if (is_main_term(td) && !IsZoomed(td->w) && !IsIconic(td->w)) {
2143             normsize.x = td->cols;
2144             normsize.y = td->rows;
2145         }
2146
2147         rebuild_term(td, recalc_window_size);
2148
2149         if (!is_main_term(td)) {
2150             p_ptr->window_flags = PW_ALL;
2151             handle_stuff(p_ptr);
2152         }
2153     }
2154 }
2155
2156 /*!
2157  * @brief Windowのリサイズをハンドリング
2158  * @retval true ウインドウメッセージを処理した
2159  * @retval false ウインドウメッセージを処理していない
2160  */
2161 static bool handle_window_resize(term_data *td, UINT uMsg, WPARAM wParam, LPARAM lParam)
2162 {
2163     if (!td) {
2164         return false;
2165     }
2166     if (!td->w) {
2167         return false;
2168     }
2169
2170     switch (uMsg) {
2171     case WM_GETMINMAXINFO: {
2172         const bool is_main = is_main_term(td);
2173         const int min_cols = (is_main) ? MAIN_TERM_MIN_COLS : 20;
2174         const int min_rows = (is_main) ? MAIN_TERM_MIN_ROWS : 3;
2175         const LONG w = min_cols * td->tile_wid + td->size_ow1 + td->size_ow2;
2176         const LONG h = min_rows * td->tile_hgt + td->size_oh1 + td->size_oh2 + 1;
2177         RECT rc{ 0, 0, w, h };
2178         AdjustWindowRectEx(&rc, td->dwStyle, TRUE, td->dwExStyle);
2179
2180         MINMAXINFO *lpmmi = (MINMAXINFO *)lParam;
2181         lpmmi->ptMinTrackSize.x = rc.right - rc.left;
2182         lpmmi->ptMinTrackSize.y = rc.bottom - rc.top;
2183
2184         return true;
2185     }
2186     case WM_EXITSIZEMOVE: {
2187         fit_term_size_to_window(td, true);
2188         return true;
2189     }
2190     case WM_WINDOWPOSCHANGED: {
2191         if (!td->size_hack) {
2192             WINDOWPOS *pos = (WINDOWPOS *)lParam;
2193             if ((pos->flags & (SWP_NOCOPYBITS | SWP_NOSIZE)) == 0) {
2194                 fit_term_size_to_window(td);
2195                 return true;
2196             }
2197         }
2198         break;
2199     }
2200     case WM_SIZE: {
2201         if (td->size_hack) {
2202             break;
2203         }
2204
2205         //!< @todo 二重のswitch文。後で分割する.
2206         switch (wParam) {
2207         case SIZE_MINIMIZED: {
2208             for (int i = 1; i < MAX_TERM_DATA; i++) {
2209                 if (data[i].visible) {
2210                     ShowWindow(data[i].w, SW_HIDE);
2211                 }
2212             }
2213
2214             return true;
2215         }
2216         case SIZE_MAXIMIZED:
2217         case SIZE_RESTORED: {
2218             fit_term_size_to_window(td);
2219
2220             td->size_hack = true;
2221             for (int i = 1; i < MAX_TERM_DATA; i++) {
2222                 if (data[i].visible) {
2223                     ShowWindow(data[i].w, SW_SHOWNA);
2224                 }
2225             }
2226
2227             td->size_hack = false;
2228
2229             return true;
2230         }
2231         }
2232
2233         break;
2234     }
2235     }
2236
2237     return false;
2238 }
2239
2240 /*!
2241  * @brief メインウインドウ用ウインドウプロシージャ
2242  */
2243 LRESULT PASCAL angband_window_procedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2244 {
2245     term_data *td = (term_data *)GetWindowLong(hWnd, 0);
2246
2247     handle_app_active(hWnd, uMsg, wParam, lParam);
2248     if (handle_window_resize(td, uMsg, wParam, lParam)) {
2249         return 0;
2250     }
2251
2252     switch (uMsg) {
2253     case WM_NCCREATE: {
2254         SetWindowLong(hWnd, 0, (LONG)(my_td));
2255         break;
2256     }
2257     case WM_CREATE: {
2258         setup_mci(hWnd);
2259         return 0;
2260     }
2261     case WM_ERASEBKGND: {
2262         return 1;
2263     }
2264     case WM_PAINT: {
2265         PAINTSTRUCT ps;
2266         HDC hdc = BeginPaint(hWnd, &ps);
2267         if (td) {
2268             if (!td->render(ps.rcPaint)) {
2269                 SetBkColor(hdc, RGB(0, 0, 0));
2270                 ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, NULL, 0, NULL);
2271             }
2272         }
2273         EndPaint(hWnd, &ps);
2274         ValidateRect(hWnd, NULL);
2275         return 0;
2276     }
2277     case MM_MCINOTIFY: {
2278         main_win_music::on_mci_notify(wParam, lParam, main_win_music::VOLUME_TABLE[arg_music_volume_table_index]);
2279
2280         return 0;
2281     }
2282     case WM_SYSKEYDOWN:
2283     case WM_KEYDOWN: {
2284         if (process_keydown(wParam, lParam)) {
2285             return 0;
2286         }
2287         break;
2288     }
2289     case WM_CHAR: {
2290         // wParam is WCHAR because using RegisterClassW
2291         if (term_no_press) {
2292             term_no_press = false;
2293         } else {
2294             WCHAR wc[2] = { (WCHAR)wParam, '\0' };
2295             term_keypress(to_multibyte(wc).c_str());
2296         }
2297         return 0;
2298     }
2299     case WM_LBUTTONDOWN: {
2300         if (macro_running()) {
2301             return 0;
2302         }
2303         mousex = std::min(LOWORD(lParam) / td->tile_wid, td->cols - 1);
2304         mousey = std::min(HIWORD(lParam) / td->tile_hgt, td->rows - 1);
2305         mouse_down = true;
2306         oldx = mousex;
2307         oldy = mousey;
2308         return 0;
2309     }
2310     case WM_LBUTTONUP: {
2311         if (!mouse_down) {
2312             return 0;
2313         }
2314         HGLOBAL hGlobal;
2315         LPSTR lpStr;
2316         TERM_LEN dx = abs(oldx - mousex) + 1;
2317         TERM_LEN dy = abs(oldy - mousey) + 1;
2318         TERM_LEN ox = (oldx > mousex) ? mousex : oldx;
2319         TERM_LEN oy = (oldy > mousey) ? mousey : oldy;
2320
2321         mouse_down = false;
2322         paint_rect = false;
2323
2324 #ifdef JP
2325         int sz = (dx + 3) * dy;
2326 #else
2327         int sz = (dx + 2) * dy;
2328 #endif
2329         hGlobal = GlobalAlloc(GHND, sz + 1);
2330         if (hGlobal == NULL) {
2331             return 0;
2332         }
2333         lpStr = (LPSTR)GlobalLock(hGlobal);
2334
2335         for (int i = 0; i < dy; i++) {
2336 #ifdef JP
2337             const auto &scr = data[0].t.scr->c;
2338
2339             std::vector<char> s(dx + 1);
2340             strncpy(s.data(), &scr[oy + i][ox], dx);
2341
2342             if (ox > 0) {
2343                 if (iskanji(scr[oy + i][ox - 1])) {
2344                     s[0] = ' ';
2345                 }
2346             }
2347
2348             if (ox + dx < data[0].cols) {
2349                 if (iskanji(scr[oy + i][ox + dx - 1])) {
2350                     s[dx - 1] = ' ';
2351                 }
2352             }
2353
2354             for (int j = 0; j < dx; j++) {
2355                 if (s[j] == 127) {
2356                     s[j] = '#';
2357                 }
2358                 *lpStr++ = s[j];
2359             }
2360 #else
2361             for (int j = 0; j < dx; j++) {
2362                 *lpStr++ = data[0].t.scr->c[oy + i][ox + j];
2363             }
2364 #endif
2365             if (dy > 1) {
2366                 *lpStr++ = '\r';
2367                 *lpStr++ = '\n';
2368             }
2369         }
2370
2371         GlobalUnlock(hGlobal);
2372         if (OpenClipboard(hWnd) == 0) {
2373             GlobalFree(hGlobal);
2374             return 0;
2375         }
2376
2377         EmptyClipboard();
2378         SetClipboardData(CF_TEXT, hGlobal);
2379         CloseClipboard();
2380         term_redraw();
2381         return 0;
2382     }
2383     case WM_MOUSEMOVE: {
2384         if (!mouse_down) {
2385             return 0;
2386         }
2387
2388         int dx, dy;
2389         int cx = std::min(LOWORD(lParam) / td->tile_wid, td->cols - 1);
2390         int cy = std::min(HIWORD(lParam) / td->tile_hgt, td->rows - 1);
2391         int ox, oy;
2392
2393         if (paint_rect) {
2394             dx = abs(oldx - mousex) + 1;
2395             dy = abs(oldy - mousey) + 1;
2396             ox = (oldx > mousex) ? mousex : oldx;
2397             oy = (oldy > mousey) ? mousey : oldy;
2398             term_inversed_area(hWnd, ox, oy, dx, dy);
2399         } else {
2400             paint_rect = true;
2401         }
2402
2403         dx = abs(cx - mousex) + 1;
2404         dy = abs(cy - mousey) + 1;
2405         ox = (cx > mousex) ? mousex : cx;
2406         oy = (cy > mousey) ? mousey : cy;
2407         term_inversed_area(hWnd, ox, oy, dx, dy);
2408
2409         oldx = cx;
2410         oldy = cy;
2411         return 0;
2412     }
2413     case WM_INITMENU: {
2414         setup_menus();
2415         return 0;
2416     }
2417     case WM_CLOSE: {
2418         if (!game_in_progress || !w_ptr->character_generated) {
2419             quit(nullptr);
2420             return 0;
2421         }
2422
2423         if (!can_save) {
2424             plog(_("今は終了できません。", "You may not do that right now."));
2425             return 0;
2426         }
2427
2428         msg_flag = false;
2429         forget_lite(p_ptr->current_floor_ptr);
2430         forget_view(p_ptr->current_floor_ptr);
2431         clear_mon_lite(p_ptr->current_floor_ptr);
2432         term_key_push(SPECIAL_KEY_QUIT);
2433         return 0;
2434     }
2435     case WM_QUERYENDSESSION: {
2436         if (!game_in_progress || !w_ptr->character_generated) {
2437             quit(nullptr);
2438             return 0;
2439         }
2440
2441         msg_flag = false;
2442         if (p_ptr->chp < 0) {
2443             p_ptr->is_dead = false;
2444         }
2445         exe_write_diary(p_ptr, DIARY_GAMESTART, 0, _("----ゲーム中断----", "---- Save and Exit Game ----"));
2446
2447         p_ptr->panic_save = 1;
2448         signals_ignore_tstp();
2449         p_ptr->died_from = _("(緊急セーブ)", "(panic save)");
2450         (void)save_player(p_ptr, SaveType::CLOSE_GAME);
2451         quit(nullptr);
2452         return 0;
2453     }
2454     case WM_QUIT: {
2455         quit(nullptr);
2456         return 0;
2457     }
2458     case WM_COMMAND: {
2459         process_menus(p_ptr, LOWORD(wParam));
2460         return 0;
2461     }
2462     case WM_ACTIVATE: {
2463         if (!wParam || HIWORD(lParam)) {
2464             break;
2465         }
2466
2467         for (int i = 1; i < MAX_TERM_DATA; i++) {
2468             if (!data[i].posfix) {
2469                 term_window_pos(&data[i], hWnd);
2470             }
2471         }
2472
2473         SetFocus(hWnd);
2474         return 0;
2475     }
2476     case WM_ACTIVATEAPP: {
2477         if (IsIconic(td->w)) {
2478             break;
2479         }
2480
2481         for (int i = 1; i < MAX_TERM_DATA; i++) {
2482             if (data[i].visible) {
2483                 if (wParam == 1) {
2484                     ShowWindow(data[i].w, SW_SHOWNA);
2485                 } else {
2486                     ShowWindow(data[i].w, SW_HIDE);
2487                 }
2488             }
2489         }
2490     }
2491         [[fallthrough]];
2492     case WM_ENABLE: {
2493         if (wParam == FALSE && keep_subwindows) {
2494             for (int i = 1; i < MAX_TERM_DATA; i++) {
2495                 if (data[i].visible) {
2496                     ShowWindow(data[i].w, SW_SHOWNA);
2497                 }
2498             }
2499         }
2500     }
2501     }
2502
2503     return DefWindowProc(hWnd, uMsg, wParam, lParam);
2504 }
2505
2506 /*!
2507  * @brief サブウインドウ用ウインドウプロシージャ
2508  */
2509 LRESULT PASCAL AngbandListProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2510 {
2511     term_data *td = (term_data *)GetWindowLong(hWnd, 0);
2512     if (handle_window_resize(td, uMsg, wParam, lParam)) {
2513         return 0;
2514     }
2515
2516     switch (uMsg) {
2517     case WM_NCCREATE: {
2518         SetWindowLong(hWnd, 0, (LONG)(my_td));
2519         break;
2520     }
2521     case WM_CREATE: {
2522         return 0;
2523     }
2524     case WM_ERASEBKGND: {
2525         return 1;
2526     }
2527     case WM_PAINT: {
2528         PAINTSTRUCT ps;
2529         HDC hdc = BeginPaint(hWnd, &ps);
2530         if (td) {
2531             if (!td->render(ps.rcPaint)) {
2532                 SetBkColor(hdc, RGB(0, 0, 0));
2533                 ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, NULL, 0, NULL);
2534             }
2535         }
2536         EndPaint(hWnd, &ps);
2537         return 0;
2538     }
2539     case WM_SYSKEYDOWN:
2540     case WM_KEYDOWN: {
2541         if (process_keydown(wParam, lParam)) {
2542             return 0;
2543         }
2544
2545         break;
2546     }
2547     case WM_CHAR: {
2548         // wParam is WCHAR because using RegisterClassW
2549         if (term_no_press) {
2550             term_no_press = false;
2551         } else {
2552             WCHAR wc[2] = { (WCHAR)wParam, '\0' };
2553             term_keypress(to_multibyte(wc).c_str());
2554         }
2555         return 0;
2556     }
2557     case WM_NCLBUTTONDOWN: {
2558         if (wParam == HTCLOSE) {
2559             wParam = HTSYSMENU;
2560         }
2561
2562         if (wParam == HTSYSMENU) {
2563             if (td->visible) {
2564                 td->visible = false;
2565                 ShowWindow(td->w, SW_HIDE);
2566             }
2567
2568             return 0;
2569         }
2570
2571         break;
2572     }
2573     }
2574
2575     return DefWindowProc(hWnd, uMsg, wParam, lParam);
2576 }
2577
2578 /*!
2579  * @brief Display warning message (see "z-util.c")
2580  */
2581 static void hook_plog(concptr str)
2582 {
2583     if (str) {
2584         MessageBoxW(data[0].w, to_wchar(str).wc_str(), _(L"警告!", L"Warning"), MB_ICONEXCLAMATION | MB_OK);
2585     }
2586 }
2587
2588 /*!
2589  * @brief Display error message and quit (see "z-util.c")
2590  */
2591 static void hook_quit(concptr str)
2592 {
2593     if (str) {
2594         MessageBoxW(data[0].w, to_wchar(str).wc_str(), _(L"エラー!", L"Error"), MB_ICONEXCLAMATION | MB_OK | MB_ICONSTOP);
2595     }
2596
2597     save_prefs();
2598     for (int i = MAX_TERM_DATA - 1; i >= 0; --i) {
2599         term_force_font(&data[i]);
2600         if (data[i].font_want) {
2601             string_free(data[i].font_want);
2602         }
2603         if (data[i].w) {
2604             DestroyWindow(data[i].w);
2605         }
2606         data[i].w = 0;
2607     }
2608
2609     DeleteObject(hbrYellow);
2610     finalize_bg();
2611     graphic.finalize();
2612     finalize_sound();
2613
2614     UnregisterClassW(AppName, hInstance);
2615     if (hIcon) {
2616         DestroyIcon(hIcon);
2617     }
2618
2619     exit(0);
2620 }
2621
2622 /*!
2623  * @brief Init some stuff
2624  */
2625 static void init_stuff()
2626 {
2627     char path[MAIN_WIN_MAX_PATH];
2628     DWORD path_len = GetModuleFileNameA(hInstance, path, MAIN_WIN_MAX_PATH);
2629     strcpy(path + path_len - 4, ".INI");
2630     ini_file = string_make(path);
2631
2632     int i = path_len;
2633     for (; i > 0; i--) {
2634         if (path[i] == '\\') {
2635             break;
2636         }
2637     }
2638
2639     strcpy(path + i + 1, "lib\\");
2640     validate_dir(path, true);
2641     init_file_paths(path);
2642     validate_dir(ANGBAND_DIR_APEX, false);
2643     validate_dir(ANGBAND_DIR_BONE, false);
2644     if (!check_dir(ANGBAND_DIR_EDIT)) {
2645         validate_dir(ANGBAND_DIR_DATA, true);
2646     } else {
2647         validate_dir(ANGBAND_DIR_DATA, false);
2648     }
2649
2650     validate_dir(ANGBAND_DIR_FILE, true);
2651     validate_dir(ANGBAND_DIR_HELP, false);
2652     validate_dir(ANGBAND_DIR_INFO, false);
2653     validate_dir(ANGBAND_DIR_PREF, true);
2654     validate_dir(ANGBAND_DIR_SAVE, false);
2655     validate_dir(ANGBAND_DIR_DEBUG_SAVE, false);
2656     validate_dir(ANGBAND_DIR_USER, true);
2657     validate_dir(ANGBAND_DIR_XTRA, true);
2658     const auto &path_news = path_build(ANGBAND_DIR_FILE, _("news_j.txt", "news.txt"));
2659     validate_file(path_news);
2660
2661     ANGBAND_DIR_XTRA_GRAF = path_build(ANGBAND_DIR_XTRA, "graf");
2662     validate_dir(ANGBAND_DIR_XTRA_GRAF, true);
2663
2664     ANGBAND_DIR_XTRA_SOUND = path_build(ANGBAND_DIR_XTRA, "sound");
2665     validate_dir(ANGBAND_DIR_XTRA_SOUND, false);
2666
2667     ANGBAND_DIR_XTRA_MUSIC = path_build(ANGBAND_DIR_XTRA, "music");
2668     validate_dir(ANGBAND_DIR_XTRA_MUSIC, false);
2669
2670     for (i = 0; special_key_list[i]; ++i) {
2671         special_key[special_key_list[i]] = true;
2672     }
2673
2674     for (i = 0; ignore_key_list[i]; ++i) {
2675         ignore_key[ignore_key_list[i]] = true;
2676     }
2677
2678     ANGBAND_SYS = "win";
2679     if (7 != GetKeyboardType(0)) {
2680         ANGBAND_KEYBOARD = "0";
2681     } else {
2682         switch (GetKeyboardType(1)) {
2683         case 0x0D01:
2684         case 0x0D02:
2685         case 0x0D03:
2686         case 0x0D04:
2687         case 0x0D05:
2688         case 0x0D06:
2689             /* NEC PC-98x1 */
2690             ANGBAND_KEYBOARD = "NEC98";
2691             break;
2692         default:
2693             /* PC/AT */
2694             ANGBAND_KEYBOARD = "JAPAN";
2695         }
2696     }
2697 }
2698
2699 /*!
2700  * @brief 全スポイラー出力を行う
2701  * Create Spoiler files
2702  * @details スポイラー出力処理の成功、失敗に関わらずプロセスを終了する。
2703  */
2704 void create_debug_spoiler(void)
2705 {
2706     init_stuff();
2707     init_angband(p_ptr, true);
2708
2709     switch (output_all_spoilers()) {
2710     case SpoilerOutputResultType::SUCCESSFUL:
2711         fprintf(stdout, "Successfully created a spoiler file.");
2712         break;
2713     case SpoilerOutputResultType::FILE_OPEN_FAILED:
2714         fprintf(stderr, "Cannot create spoiler file.");
2715         break;
2716     case SpoilerOutputResultType::FILE_CLOSE_FAILED:
2717         fprintf(stderr, "Cannot close spoiler file.");
2718         break;
2719     default:
2720         break;
2721     }
2722
2723     quit(nullptr);
2724 }
2725
2726 /*!
2727  * @brief メインウインドウ、サブウインドウのウインドウクラス登録
2728  */
2729 static void register_wndclass(void)
2730 {
2731     WNDCLASSW wc{};
2732     wc.style = CS_CLASSDC;
2733     wc.lpfnWndProc = angband_window_procedure;
2734     wc.cbClsExtra = 0;
2735     wc.cbWndExtra = 4;
2736     wc.hInstance = hInstance;
2737     wc.hIcon = hIcon = LoadIconW(hInstance, AppName);
2738     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
2739     wc.hbrBackground = NULL;
2740     wc.lpszMenuName = AppName;
2741     wc.lpszClassName = AppName;
2742
2743     if (!RegisterClassW(&wc)) {
2744         exit(1);
2745     }
2746
2747     wc.lpfnWndProc = AngbandListProc;
2748     wc.lpszMenuName = NULL;
2749     wc.lpszClassName = AngList;
2750
2751     if (!RegisterClassW(&wc)) {
2752         exit(2);
2753     }
2754 }
2755
2756 /*!
2757  * @brief (Windows固有)Windowsアプリケーションとしてのエントリポイント
2758  */
2759 int WINAPI WinMain(
2760     _In_ HINSTANCE hInst, [[maybe_unused]] _In_opt_ HINSTANCE hPrevInst, [[maybe_unused]] _In_ LPSTR lpCmdLine, [[maybe_unused]] _In_ int nCmdShow)
2761 {
2762     setlocale(LC_ALL, "ja_JP");
2763     hInstance = hInst;
2764     if (is_already_running()) {
2765         constexpr auto mes = _(L"変愚蛮怒はすでに起動しています。", L"Hengband is already running.");
2766         constexpr auto caption = _(L"エラー!", L"Error");
2767         MessageBoxW(NULL, mes, caption, MB_ICONEXCLAMATION | MB_OK | MB_ICONSTOP);
2768         return 0;
2769     }
2770
2771     command_line.handle();
2772     register_wndclass();
2773
2774     // before term_data initialize
2775     plog_aux = [](concptr str) {
2776         if (str) {
2777             MessageBoxW(NULL, to_wchar(str).wc_str(), _(L"警告!", L"Warning"), MB_ICONEXCLAMATION | MB_OK);
2778         }
2779     };
2780     quit_aux = [](concptr str) {
2781         if (str) {
2782             MessageBoxW(NULL, to_wchar(str).wc_str(), _(L"エラー!", L"Error"), MB_ICONEXCLAMATION | MB_OK | MB_ICONSTOP);
2783         }
2784
2785         UnregisterClassW(AppName, hInstance);
2786         if (hIcon) {
2787             DestroyIcon(hIcon);
2788         }
2789
2790         exit(0);
2791     };
2792     core_aux = quit_aux;
2793
2794     init_stuff();
2795     refresh_color_table();
2796     init_windows();
2797     change_graphics_mode(static_cast<graphics_mode>(arg_graphics));
2798     change_bg_mode(current_bg_mode, true);
2799
2800     // after term_data initialize
2801     plog_aux = hook_plog;
2802     quit_aux = hook_quit;
2803     core_aux = hook_quit;
2804
2805     signals_init();
2806     term_activate(term_screen);
2807     {
2808         TermCenteredOffsetSetter tcos(MAIN_TERM_MIN_COLS, MAIN_TERM_MIN_ROWS);
2809
2810         init_angband(p_ptr, false);
2811         initialized = true;
2812
2813         check_for_save_file(command_line.get_savefile_option());
2814         prt(_("[ファイル] メニューの [新規] または [開く] を選択してください。", "[Choose 'New' or 'Open' from the 'File' menu]"), 23, _(8, 17));
2815         term_fresh();
2816     }
2817
2818     change_sound_mode(arg_sound);
2819     use_music = arg_music;
2820     if (use_music) {
2821         init_music();
2822     }
2823
2824     // ユーザーがゲーム開始を選択するまで待つループ
2825     MSG msg;
2826     while (GetMessage(&msg, NULL, 0, 0)) {
2827         TranslateMessage(&msg);
2828         DispatchMessage(&msg);
2829         if (game_in_progress || movie_in_progress) {
2830             break;
2831         }
2832     }
2833
2834     term_flush();
2835     if (movie_in_progress) {
2836         // selected movie
2837         play_game(p_ptr, false, true);
2838     } else if (savefile.empty()) {
2839         // new game
2840         play_game(p_ptr, true, false);
2841     } else {
2842         // selected savefile
2843         play_game(p_ptr, false, false);
2844     }
2845
2846     quit(nullptr);
2847     return 0;
2848 }
2849 #endif /* WINDOWS */