非専門的シンギュラリティー研究所

無限に動き続けるシステムを表す方法を AI なども使って考えていきます。

関数電卓コマンド版(5)

関数電卓コマンド版(1) - 非専門的シンギュラリティー研究所」で「関数電卓」のコマンド版を作ると書いていたのですが、「関数電卓コマンド版(4) - 非専門的シンギュラリティー研究所」で乗算・除算をインラインアセンブラで記述したものを作成したところで中断していました。

関数電卓」のコマンド版を Windows のデスクトップアプリケーションとして作成します。そのためにまずコンソールからの入力と同様に動作する Windows のデスクトップアプリケーションを作成しました。

#include <windows.h>
#include <string>
#include <tchar.h>

#define ID_INPUT 1001
#define ID_OUTPUT 1002

HWND hInput, hOutput;

WNDPROC oldEditProc = nullptr;

const int MAX_INPUT_LENGTH = 1000;
std::wstring prompt = L"input> ";

// サブクラス用プロシージャ
LRESULT CALLBACK EditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (msg == WM_KEYDOWN && wParam == VK_RETURN) {
        wchar_t buffer[MAX_INPUT_LENGTH];

        DWORD start, end;
        SendMessage(hInput, EM_GETSEL, (WPARAM)&start, (LPARAM)&end); // カーソル位置取得
        int currentLine = (int)SendMessage(hInput, EM_LINEFROMCHAR, (WPARAM)start, 0);
        // 最初の WORD に、バッファのサイズを入れる(重要!)
        *((WORD*)buffer) = sizeof(buffer) / sizeof(TCHAR);
        // EM_GETLINE を使って指定行を取得
        int copied = (int)SendMessage(hInput, EM_GETLINE, (WPARAM)currentLine, (LPARAM)buffer);
        // buffer に copied 文字分のテキストが格納される(終端の \0 は自動で付かないので注意)
        buffer[copied] = _T('\0'); // 明示的に終端すること

        int lineCount = (int)SendMessage(hInput, EM_GETLINECOUNT, 0, 0);
        if (currentLine < lineCount - 1) {
            // 次の行がある場合は、最後に移動する
            int len = GetWindowTextLength(hInput);
            SendMessage(hInput, EM_SETSEL, len, len);
        }
        else {
            // 最後の行の場合は、プロンプトを表示する
            int len = GetWindowTextLength(hInput);
            SendMessage(hInput, EM_SETSEL, len, len);
            SendMessage(hInput, EM_REPLACESEL, 0, (LPARAM)(L"\r\n" + prompt).c_str());
        }

        // 仮の出力処理
        std::wstring result = buffer;
        result = result.substr(prompt.size());
        result += L"\r\n";

        int len = GetWindowTextLength(hOutput);
        SendMessage(hOutput, EM_SETSEL, len, len);
        SendMessage(hOutput, EM_REPLACESEL, 0, (LPARAM)result.c_str());


        return 0; // エンターキーの既定動作(ビープ音など)を抑制
    }
    if (msg == WM_CHAR && wParam == VK_RETURN) {
        return 0; // エンターキーの既定動作(ビープ音など)を抑制
    }
    return CallWindowProc(oldEditProc, hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_CREATE:
        // 出力用(読み取り専用、複数行)
        hOutput = CreateWindow(L"EDIT", NULL,
            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_READONLY | WS_VSCROLL,
            10, 10, 460, 300,
            hwnd, (HMENU)ID_OUTPUT, NULL, NULL);

        // 入力用
        hInput = CreateWindow(L"EDIT", NULL,
            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | WS_VSCROLL,
            10, 320, 460, 300,
            hwnd, (HMENU)ID_INPUT, NULL, NULL);

        SetWindowText(hInput, prompt.c_str());
        {
            int len = GetWindowTextLength(hInput);
            SendMessage(hInput, EM_SETSEL, len, len);
        }
        SetFocus(hInput);

        // サブクラス化
        oldEditProc = (WNDPROC)SetWindowLongPtr(hInput, GWLP_WNDPROC, (LONG_PTR)EditProc);
        break;

    case WM_COMMAND:
        if (LOWORD(wParam) == ID_INPUT && HIWORD(wParam) == EN_CHANGE) {
            // Enterキーを検出するためにEN_CHANGEではなくWM_KEYDOWNを使う
        }
        break;

    case WM_KEYDOWN:
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
    const wchar_t CLASS_NAME[] = L"MyConsoleWindow";

    WNDCLASS wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0, CLASS_NAME, L"GUI Console",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 500, 700,
        NULL, NULL, hInstance, NULL
    );

    ShowWindow(hwnd, nCmdShow);

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}