mirror of
https://github.com/jmarshall23/msword.git
synced 2026-08-12 08:01:05 +02:00
Initial commit
This commit is contained in:
@@ -0,0 +1,919 @@
|
||||
#include "legacy_asm_port.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <CommCtrl.h>
|
||||
#include <CommDlg.h>
|
||||
#include <Richedit.h>
|
||||
#include <Shellapi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cwctype>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr wchar_t kWindowClass[] = L"OpusWordX64Window";
|
||||
constexpr wchar_t kProductName[] = L"Microsoft Word x64 Port";
|
||||
constexpr UINT_PTR kStatusTimer = 1;
|
||||
|
||||
enum Command : UINT {
|
||||
cmd_file_new = 100,
|
||||
cmd_file_open,
|
||||
cmd_file_save,
|
||||
cmd_file_save_as,
|
||||
cmd_file_print,
|
||||
cmd_file_exit,
|
||||
cmd_edit_undo = 200,
|
||||
cmd_edit_redo,
|
||||
cmd_edit_cut,
|
||||
cmd_edit_copy,
|
||||
cmd_edit_paste,
|
||||
cmd_edit_select_all,
|
||||
cmd_edit_find,
|
||||
cmd_edit_find_next,
|
||||
cmd_format_bold = 300,
|
||||
cmd_format_italic,
|
||||
cmd_format_underline,
|
||||
cmd_format_font,
|
||||
cmd_format_left,
|
||||
cmd_format_center,
|
||||
cmd_format_right,
|
||||
cmd_format_justify,
|
||||
cmd_tools_word_count = 400,
|
||||
cmd_help_about = 500,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::wstring system_error_message(const DWORD error) {
|
||||
wchar_t* buffer = nullptr;
|
||||
const DWORD count = FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error, 0, reinterpret_cast<wchar_t*>(&buffer), 0, nullptr);
|
||||
std::wstring message = count != 0 && buffer != nullptr
|
||||
? std::wstring(buffer, count)
|
||||
: L"Unknown error (" + std::to_wstring(error) + L")";
|
||||
if (buffer != nullptr) {
|
||||
LocalFree(buffer);
|
||||
}
|
||||
while (!message.empty() && (message.back() == L'\r' || message.back() == L'\n')) {
|
||||
message.pop_back();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::wstring lower_extension(const std::wstring& path) {
|
||||
auto extension = std::filesystem::path(path).extension().wstring();
|
||||
std::ranges::transform(extension, extension.begin(),
|
||||
[](const wchar_t ch) { return static_cast<wchar_t>(std::towlower(ch)); });
|
||||
return extension;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<std::byte> read_file(const std::wstring& path) {
|
||||
const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (file == INVALID_HANDLE_VALUE) {
|
||||
throw std::runtime_error("CreateFileW failed: " + std::to_string(GetLastError()));
|
||||
}
|
||||
|
||||
LARGE_INTEGER size{};
|
||||
if (!GetFileSizeEx(file, &size) || size.QuadPart < 0 ||
|
||||
size.QuadPart > static_cast<LONGLONG>(std::numeric_limits<DWORD>::max())) {
|
||||
const DWORD error = GetLastError();
|
||||
CloseHandle(file);
|
||||
throw std::runtime_error("GetFileSizeEx failed: " + std::to_string(error));
|
||||
}
|
||||
|
||||
std::vector<std::byte> bytes(static_cast<std::size_t>(size.QuadPart));
|
||||
DWORD read = 0;
|
||||
const BOOL succeeded = bytes.empty() ||
|
||||
ReadFile(file, bytes.data(), static_cast<DWORD>(bytes.size()), &read, nullptr);
|
||||
const DWORD error = succeeded ? ERROR_SUCCESS : GetLastError();
|
||||
CloseHandle(file);
|
||||
if (!succeeded || read != bytes.size()) {
|
||||
throw std::runtime_error("ReadFile failed: " + std::to_string(error));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void write_file(const std::wstring& path, const std::vector<std::byte>& bytes) {
|
||||
const HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (file == INVALID_HANDLE_VALUE) {
|
||||
throw std::runtime_error("CreateFileW failed: " + std::to_string(GetLastError()));
|
||||
}
|
||||
DWORD written = 0;
|
||||
const BOOL succeeded = bytes.empty() ||
|
||||
WriteFile(file, bytes.data(), static_cast<DWORD>(bytes.size()), &written, nullptr);
|
||||
const DWORD error = succeeded ? ERROR_SUCCESS : GetLastError();
|
||||
CloseHandle(file);
|
||||
if (!succeeded || written != bytes.size()) {
|
||||
throw std::runtime_error("WriteFile failed: " + std::to_string(error));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::wstring decode_text(const std::vector<std::byte>& bytes) {
|
||||
if (bytes.size() >= 2 && bytes[0] == std::byte{0xff} && bytes[1] == std::byte{0xfe}) {
|
||||
std::wstring result((bytes.size() - 2) / sizeof(wchar_t), L'\0');
|
||||
std::memcpy(result.data(), bytes.data() + 2, result.size() * sizeof(wchar_t));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::size_t offset = 0;
|
||||
if (bytes.size() >= 3 && bytes[0] == std::byte{0xef} && bytes[1] == std::byte{0xbb} &&
|
||||
bytes[2] == std::byte{0xbf}) {
|
||||
offset = 3;
|
||||
}
|
||||
const auto* text = reinterpret_cast<const char*>(bytes.data() + offset);
|
||||
const int byte_count = static_cast<int>(bytes.size() - offset);
|
||||
if (byte_count == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
UINT code_page = CP_UTF8;
|
||||
DWORD flags = MB_ERR_INVALID_CHARS;
|
||||
int character_count = MultiByteToWideChar(code_page, flags, text, byte_count, nullptr, 0);
|
||||
if (character_count == 0) {
|
||||
code_page = CP_ACP;
|
||||
flags = 0;
|
||||
character_count = MultiByteToWideChar(code_page, flags, text, byte_count, nullptr, 0);
|
||||
}
|
||||
if (character_count == 0) {
|
||||
throw std::runtime_error("The text encoding could not be decoded");
|
||||
}
|
||||
std::wstring result(static_cast<std::size_t>(character_count), L'\0');
|
||||
MultiByteToWideChar(code_page, flags, text, byte_count, result.data(), character_count);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<std::byte> encode_utf8(const std::wstring_view text) {
|
||||
const int byte_count = WideCharToMultiByte(CP_UTF8, 0, text.data(),
|
||||
static_cast<int>(text.size()), nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
std::vector<std::byte> result{
|
||||
std::byte{0xef}, std::byte{0xbb}, std::byte{0xbf},
|
||||
};
|
||||
const auto initial_size = result.size();
|
||||
result.resize(initial_size + static_cast<std::size_t>(byte_count));
|
||||
if (byte_count != 0) {
|
||||
WideCharToMultiByte(CP_UTF8, 0, text.data(), static_cast<int>(text.size()),
|
||||
reinterpret_cast<char*>(result.data() + initial_size), byte_count,
|
||||
nullptr, nullptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] HMENU build_menu() {
|
||||
const HMENU menu = CreateMenu();
|
||||
const HMENU file = CreatePopupMenu();
|
||||
AppendMenuW(file, MF_STRING, cmd_file_new, L"&New\tCtrl+N");
|
||||
AppendMenuW(file, MF_STRING, cmd_file_open, L"&Open...\tCtrl+O");
|
||||
AppendMenuW(file, MF_STRING, cmd_file_save, L"&Save\tCtrl+S");
|
||||
AppendMenuW(file, MF_STRING, cmd_file_save_as, L"Save &As...");
|
||||
AppendMenuW(file, MF_SEPARATOR, 0, nullptr);
|
||||
AppendMenuW(file, MF_STRING, cmd_file_print, L"&Print...\tCtrl+P");
|
||||
AppendMenuW(file, MF_SEPARATOR, 0, nullptr);
|
||||
AppendMenuW(file, MF_STRING, cmd_file_exit, L"E&xit");
|
||||
AppendMenuW(menu, MF_POPUP, reinterpret_cast<UINT_PTR>(file), L"&File");
|
||||
|
||||
const HMENU edit = CreatePopupMenu();
|
||||
AppendMenuW(edit, MF_STRING, cmd_edit_undo, L"&Undo\tCtrl+Z");
|
||||
AppendMenuW(edit, MF_STRING, cmd_edit_redo, L"&Redo\tCtrl+Y");
|
||||
AppendMenuW(edit, MF_SEPARATOR, 0, nullptr);
|
||||
AppendMenuW(edit, MF_STRING, cmd_edit_cut, L"Cu&t\tCtrl+X");
|
||||
AppendMenuW(edit, MF_STRING, cmd_edit_copy, L"&Copy\tCtrl+C");
|
||||
AppendMenuW(edit, MF_STRING, cmd_edit_paste, L"&Paste\tCtrl+V");
|
||||
AppendMenuW(edit, MF_STRING, cmd_edit_select_all, L"Select &All\tCtrl+A");
|
||||
AppendMenuW(edit, MF_SEPARATOR, 0, nullptr);
|
||||
AppendMenuW(edit, MF_STRING, cmd_edit_find, L"&Find...\tCtrl+F");
|
||||
AppendMenuW(edit, MF_STRING, cmd_edit_find_next, L"Find &Next\tF3");
|
||||
AppendMenuW(menu, MF_POPUP, reinterpret_cast<UINT_PTR>(edit), L"&Edit");
|
||||
|
||||
const HMENU format = CreatePopupMenu();
|
||||
AppendMenuW(format, MF_STRING, cmd_format_bold, L"&Bold\tCtrl+B");
|
||||
AppendMenuW(format, MF_STRING, cmd_format_italic, L"&Italic\tCtrl+I");
|
||||
AppendMenuW(format, MF_STRING, cmd_format_underline, L"&Underline\tCtrl+U");
|
||||
AppendMenuW(format, MF_STRING, cmd_format_font, L"&Font...");
|
||||
AppendMenuW(format, MF_SEPARATOR, 0, nullptr);
|
||||
AppendMenuW(format, MF_STRING, cmd_format_left, L"Align &Left\tCtrl+L");
|
||||
AppendMenuW(format, MF_STRING, cmd_format_center, L"&Center\tCtrl+E");
|
||||
AppendMenuW(format, MF_STRING, cmd_format_right, L"Align &Right\tCtrl+R");
|
||||
AppendMenuW(format, MF_STRING, cmd_format_justify, L"&Justify\tCtrl+J");
|
||||
AppendMenuW(menu, MF_POPUP, reinterpret_cast<UINT_PTR>(format), L"F&ormat");
|
||||
|
||||
const HMENU tools = CreatePopupMenu();
|
||||
AppendMenuW(tools, MF_STRING, cmd_tools_word_count, L"&Word Count...");
|
||||
AppendMenuW(menu, MF_POPUP, reinterpret_cast<UINT_PTR>(tools), L"&Tools");
|
||||
|
||||
const HMENU help = CreatePopupMenu();
|
||||
AppendMenuW(help, MF_STRING, cmd_help_about, L"&About Microsoft Word x64 Port");
|
||||
AppendMenuW(menu, MF_POPUP, reinterpret_cast<UINT_PTR>(help), L"&Help");
|
||||
return menu;
|
||||
}
|
||||
|
||||
class WordApplication {
|
||||
public:
|
||||
explicit WordApplication(const HINSTANCE instance) : instance_(instance) {}
|
||||
|
||||
int run(const int show_command) {
|
||||
int argument_count = 0;
|
||||
wchar_t** arguments = CommandLineToArgvW(GetCommandLineW(), &argument_count);
|
||||
if (arguments != nullptr && argument_count > 1 &&
|
||||
std::wstring_view(arguments[1]) == L"--self-test") {
|
||||
std::array<std::uint8_t, 7> pattern_buffer{};
|
||||
constexpr std::array<std::uint8_t, 3> pattern{1, 2, 3};
|
||||
opus::x64::fill_byte_pattern(pattern_buffer.data(), pattern_buffer.size(),
|
||||
pattern.data(), pattern.size());
|
||||
const bool passed = opus::x64::assembly_manifest_complete() &&
|
||||
opus::x64::assembly_manifest_count() == 59 &&
|
||||
opus::x64::multiply_divide(10, 3, 2) == 15 &&
|
||||
opus::x64::legacy_square(4.0) == 16.0 &&
|
||||
pattern_buffer == std::array<std::uint8_t, 7>{1, 2, 3, 1, 2, 3, 1} &&
|
||||
opus::x64::is_ascii_alphanumeric('Z') &&
|
||||
opus::x64::ascii_upper('q') == 'Q';
|
||||
LocalFree(arguments);
|
||||
return passed ? 0 : 2;
|
||||
}
|
||||
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
INITCOMMONCONTROLSEX controls{sizeof(controls), ICC_BAR_CLASSES | ICC_STANDARD_CLASSES};
|
||||
InitCommonControlsEx(&controls);
|
||||
|
||||
rich_edit_library_ = LoadLibraryW(L"Msftedit.dll");
|
||||
if (rich_edit_library_ == nullptr) {
|
||||
show_error(L"Microsoft Rich Edit could not be loaded", GetLastError());
|
||||
if (arguments != nullptr) {
|
||||
LocalFree(arguments);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
WNDCLASSEXW window_class{sizeof(window_class)};
|
||||
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||
window_class.lpfnWndProc = &WordApplication::window_proc;
|
||||
window_class.hInstance = instance_;
|
||||
window_class.hIcon = LoadIconW(nullptr, IDI_APPLICATION);
|
||||
window_class.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
||||
window_class.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_APPWORKSPACE + 1);
|
||||
window_class.lpszClassName = kWindowClass;
|
||||
window_class.hIconSm = window_class.hIcon;
|
||||
if (RegisterClassExW(&window_class) == 0) {
|
||||
show_error(L"The application window could not be registered", GetLastError());
|
||||
FreeLibrary(rich_edit_library_);
|
||||
if (arguments != nullptr) {
|
||||
LocalFree(arguments);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
main_window_ = CreateWindowExW(
|
||||
0, kWindowClass, kProductName, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 1100, 800, nullptr, nullptr, instance_, this);
|
||||
if (main_window_ == nullptr) {
|
||||
show_error(L"The application window could not be created", GetLastError());
|
||||
FreeLibrary(rich_edit_library_);
|
||||
if (arguments != nullptr) {
|
||||
LocalFree(arguments);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (arguments != nullptr && argument_count > 1 &&
|
||||
std::wstring_view(arguments[1]) == L"--ui-self-test") {
|
||||
SetWindowTextW(editor_, L"Word x64 UI smoke test");
|
||||
const bool passed = IsWindow(main_window_) != FALSE && IsWindow(editor_) != FALSE &&
|
||||
IsWindow(status_) != FALSE && GetWindowTextLengthW(editor_) == 22;
|
||||
DestroyWindow(main_window_);
|
||||
LocalFree(arguments);
|
||||
FreeLibrary(rich_edit_library_);
|
||||
return passed ? 0 : 3;
|
||||
}
|
||||
|
||||
if (arguments != nullptr && argument_count > 1) {
|
||||
open_path(arguments[1]);
|
||||
}
|
||||
if (arguments != nullptr) {
|
||||
LocalFree(arguments);
|
||||
}
|
||||
|
||||
ShowWindow(main_window_, show_command);
|
||||
UpdateWindow(main_window_);
|
||||
|
||||
const std::array accelerators{
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'N', cmd_file_new},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'O', cmd_file_open},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'S', cmd_file_save},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'P', cmd_file_print},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'Z', cmd_edit_undo},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'Y', cmd_edit_redo},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'X', cmd_edit_cut},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'C', cmd_edit_copy},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'V', cmd_edit_paste},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'A', cmd_edit_select_all},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'F', cmd_edit_find},
|
||||
ACCEL{FVIRTKEY, VK_F3, cmd_edit_find_next},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'B', cmd_format_bold},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'I', cmd_format_italic},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'U', cmd_format_underline},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'L', cmd_format_left},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'E', cmd_format_center},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'R', cmd_format_right},
|
||||
ACCEL{FVIRTKEY | FCONTROL, 'J', cmd_format_justify},
|
||||
};
|
||||
const HACCEL accelerator_table = CreateAcceleratorTableW(
|
||||
const_cast<LPACCEL>(accelerators.data()), static_cast<int>(accelerators.size()));
|
||||
|
||||
MSG message{};
|
||||
while (GetMessageW(&message, nullptr, 0, 0) > 0) {
|
||||
if (find_dialog_ != nullptr && IsDialogMessageW(find_dialog_, &message)) {
|
||||
continue;
|
||||
}
|
||||
if (!TranslateAcceleratorW(main_window_, accelerator_table, &message)) {
|
||||
TranslateMessage(&message);
|
||||
DispatchMessageW(&message);
|
||||
}
|
||||
}
|
||||
DestroyAcceleratorTable(accelerator_table);
|
||||
FreeLibrary(rich_edit_library_);
|
||||
return static_cast<int>(message.wParam);
|
||||
}
|
||||
|
||||
private:
|
||||
static LRESULT CALLBACK window_proc(const HWND window, const UINT message,
|
||||
const WPARAM wparam, const LPARAM lparam) {
|
||||
WordApplication* app = reinterpret_cast<WordApplication*>(
|
||||
GetWindowLongPtrW(window, GWLP_USERDATA));
|
||||
if (message == WM_NCCREATE) {
|
||||
const auto* create = reinterpret_cast<const CREATESTRUCTW*>(lparam);
|
||||
app = static_cast<WordApplication*>(create->lpCreateParams);
|
||||
app->main_window_ = window;
|
||||
SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(app));
|
||||
}
|
||||
return app != nullptr ? app->dispatch(message, wparam, lparam)
|
||||
: DefWindowProcW(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
LRESULT dispatch(const UINT message, const WPARAM wparam, const LPARAM lparam) {
|
||||
if (message == find_message_) {
|
||||
on_find_message(*reinterpret_cast<const FINDREPLACEW*>(lparam));
|
||||
return 0;
|
||||
}
|
||||
switch (message) {
|
||||
case WM_CREATE:
|
||||
return create_children() ? 0 : -1;
|
||||
case WM_SIZE:
|
||||
layout(LOWORD(lparam), HIWORD(lparam));
|
||||
return 0;
|
||||
case WM_SETFOCUS:
|
||||
SetFocus(editor_);
|
||||
return 0;
|
||||
case WM_COMMAND:
|
||||
if (reinterpret_cast<HWND>(lparam) == editor_ && HIWORD(wparam) == EN_CHANGE) {
|
||||
status_dirty_ = true;
|
||||
update_title();
|
||||
return 0;
|
||||
}
|
||||
execute_command(LOWORD(wparam));
|
||||
return 0;
|
||||
case WM_NOTIFY:
|
||||
if (reinterpret_cast<const NMHDR*>(lparam)->hwndFrom == editor_) {
|
||||
status_dirty_ = true;
|
||||
}
|
||||
return 0;
|
||||
case WM_INITMENU:
|
||||
update_menu(reinterpret_cast<HMENU>(wparam));
|
||||
return 0;
|
||||
case WM_TIMER:
|
||||
if (wparam == kStatusTimer && status_dirty_) {
|
||||
update_status();
|
||||
}
|
||||
return 0;
|
||||
case WM_DROPFILES:
|
||||
open_drop(reinterpret_cast<HDROP>(wparam));
|
||||
return 0;
|
||||
case WM_CLOSE:
|
||||
if (prompt_save_changes()) {
|
||||
DestroyWindow(main_window_);
|
||||
}
|
||||
return 0;
|
||||
case WM_DESTROY:
|
||||
KillTimer(main_window_, kStatusTimer);
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
default:
|
||||
return DefWindowProcW(main_window_, message, wparam, lparam);
|
||||
}
|
||||
}
|
||||
|
||||
bool create_children() {
|
||||
SetMenu(main_window_, build_menu());
|
||||
editor_ = CreateWindowExW(
|
||||
WS_EX_CLIENTEDGE, MSFTEDIT_CLASS, L"",
|
||||
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_WANTRETURN |
|
||||
ES_NOHIDESEL | ES_AUTOVSCROLL,
|
||||
0, 0, 0, 0, main_window_, reinterpret_cast<HMENU>(1), instance_, nullptr);
|
||||
status_ = CreateWindowExW(0, STATUSCLASSNAMEW, nullptr, WS_CHILD | WS_VISIBLE,
|
||||
0, 0, 0, 0, main_window_, reinterpret_cast<HMENU>(2),
|
||||
instance_, nullptr);
|
||||
if (editor_ == nullptr || status_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SendMessageW(editor_, EM_SETEVENTMASK, 0, ENM_CHANGE | ENM_SELCHANGE);
|
||||
SendMessageW(editor_, EM_SETBKGNDCOLOR, 0, RGB(255, 255, 255));
|
||||
SendMessageW(editor_, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(16, 16));
|
||||
SendMessageW(editor_, EM_EXLIMITTEXT, 0, std::numeric_limits<LONG>::max());
|
||||
|
||||
CHARFORMAT2W format{sizeof(format)};
|
||||
format.dwMask = CFM_FACE | CFM_SIZE | CFM_CHARSET;
|
||||
format.yHeight = 220;
|
||||
format.bCharSet = DEFAULT_CHARSET;
|
||||
wcscpy_s(format.szFaceName, L"Segoe UI");
|
||||
SendMessageW(editor_, EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast<LPARAM>(&format));
|
||||
|
||||
find_message_ = RegisterWindowMessageW(FINDMSGSTRINGW);
|
||||
DragAcceptFiles(main_window_, TRUE);
|
||||
SetTimer(main_window_, kStatusTimer, 250, nullptr);
|
||||
set_clean();
|
||||
update_status();
|
||||
SetFocus(editor_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void layout(const int width, const int height) {
|
||||
SendMessageW(status_, WM_SIZE, 0, 0);
|
||||
RECT status_rect{};
|
||||
GetWindowRect(status_, &status_rect);
|
||||
const int status_height = status_rect.bottom - status_rect.top;
|
||||
MoveWindow(editor_, 0, 0, width, std::max(0, height - status_height), TRUE);
|
||||
const int parts[]{std::max(200, width - 330), std::max(320, width - 150), -1};
|
||||
SendMessageW(status_, SB_SETPARTS, std::size(parts), reinterpret_cast<LPARAM>(parts));
|
||||
status_dirty_ = true;
|
||||
}
|
||||
|
||||
void execute_command(const UINT command) {
|
||||
switch (command) {
|
||||
case cmd_file_new: new_document(); break;
|
||||
case cmd_file_open: open_dialog(); break;
|
||||
case cmd_file_save: save(false); break;
|
||||
case cmd_file_save_as: save(true); break;
|
||||
case cmd_file_print: print_document(); break;
|
||||
case cmd_file_exit: SendMessageW(main_window_, WM_CLOSE, 0, 0); break;
|
||||
case cmd_edit_undo: SendMessageW(editor_, EM_UNDO, 0, 0); break;
|
||||
case cmd_edit_redo: SendMessageW(editor_, EM_REDO, 0, 0); break;
|
||||
case cmd_edit_cut: SendMessageW(editor_, WM_CUT, 0, 0); break;
|
||||
case cmd_edit_copy: SendMessageW(editor_, WM_COPY, 0, 0); break;
|
||||
case cmd_edit_paste: SendMessageW(editor_, WM_PASTE, 0, 0); break;
|
||||
case cmd_edit_select_all: SendMessageW(editor_, EM_SETSEL, 0, -1); break;
|
||||
case cmd_edit_find: show_find(); break;
|
||||
case cmd_edit_find_next: find_next(find_flags_); break;
|
||||
case cmd_format_bold: toggle_effect(CFM_BOLD, CFE_BOLD); break;
|
||||
case cmd_format_italic: toggle_effect(CFM_ITALIC, CFE_ITALIC); break;
|
||||
case cmd_format_underline: toggle_effect(CFM_UNDERLINE, CFE_UNDERLINE); break;
|
||||
case cmd_format_font: choose_font(); break;
|
||||
case cmd_format_left: set_alignment(PFA_LEFT); break;
|
||||
case cmd_format_center: set_alignment(PFA_CENTER); break;
|
||||
case cmd_format_right: set_alignment(PFA_RIGHT); break;
|
||||
case cmd_format_justify: set_alignment(PFA_JUSTIFY); break;
|
||||
case cmd_tools_word_count: show_word_count(); break;
|
||||
case cmd_help_about: show_about(); break;
|
||||
default: break;
|
||||
}
|
||||
status_dirty_ = true;
|
||||
}
|
||||
|
||||
void update_menu(const HMENU menu) const {
|
||||
const bool can_undo = SendMessageW(editor_, EM_CANUNDO, 0, 0) != 0;
|
||||
const bool can_redo = SendMessageW(editor_, EM_CANREDO, 0, 0) != 0;
|
||||
CHARRANGE selection{};
|
||||
SendMessageW(editor_, EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&selection));
|
||||
EnableMenuItem(menu, cmd_edit_undo, MF_BYCOMMAND | (can_undo ? MF_ENABLED : MF_GRAYED));
|
||||
EnableMenuItem(menu, cmd_edit_redo, MF_BYCOMMAND | (can_redo ? MF_ENABLED : MF_GRAYED));
|
||||
EnableMenuItem(menu, cmd_edit_cut,
|
||||
MF_BYCOMMAND | (selection.cpMin != selection.cpMax ? MF_ENABLED : MF_GRAYED));
|
||||
EnableMenuItem(menu, cmd_edit_copy,
|
||||
MF_BYCOMMAND | (selection.cpMin != selection.cpMax ? MF_ENABLED : MF_GRAYED));
|
||||
EnableMenuItem(menu, cmd_edit_paste,
|
||||
MF_BYCOMMAND | (SendMessageW(editor_, EM_CANPASTE, 0, 0) ? MF_ENABLED : MF_GRAYED));
|
||||
}
|
||||
|
||||
void new_document() {
|
||||
if (!prompt_save_changes()) {
|
||||
return;
|
||||
}
|
||||
loading_ = true;
|
||||
SetWindowTextW(editor_, L"");
|
||||
current_path_.clear();
|
||||
loading_ = false;
|
||||
set_clean();
|
||||
}
|
||||
|
||||
void open_dialog() {
|
||||
if (!prompt_save_changes()) {
|
||||
return;
|
||||
}
|
||||
std::array<wchar_t, 32768> path{};
|
||||
OPENFILENAMEW dialog{sizeof(dialog)};
|
||||
dialog.hwndOwner = main_window_;
|
||||
dialog.lpstrFilter = L"Rich Text Format (*.rtf)\0*.rtf\0Text files (*.txt)\0*.txt\0All files (*.*)\0*.*\0";
|
||||
dialog.lpstrFile = path.data();
|
||||
dialog.nMaxFile = static_cast<DWORD>(path.size());
|
||||
dialog.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
|
||||
if (GetOpenFileNameW(&dialog)) {
|
||||
open_path(path.data());
|
||||
}
|
||||
}
|
||||
|
||||
void open_drop(const HDROP drop) {
|
||||
std::array<wchar_t, 32768> path{};
|
||||
if (DragQueryFileW(drop, 0, path.data(), static_cast<UINT>(path.size())) != 0 &&
|
||||
prompt_save_changes()) {
|
||||
open_path(path.data());
|
||||
}
|
||||
DragFinish(drop);
|
||||
}
|
||||
|
||||
void open_path(const std::wstring& path) {
|
||||
if (lower_extension(path) == L".doc") {
|
||||
MessageBoxW(main_window_,
|
||||
L"The native x64 document engine does not read binary Word 1.x .doc files yet. "
|
||||
L"Open an RTF or text document for this migration milestone.",
|
||||
kProductName, MB_OK | MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loading_ = true;
|
||||
if (lower_extension(path) == L".rtf") {
|
||||
const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (file == INVALID_HANDLE_VALUE) {
|
||||
throw std::runtime_error("CreateFileW failed: " + std::to_string(GetLastError()));
|
||||
}
|
||||
EDITSTREAM stream{reinterpret_cast<DWORD_PTR>(file), 0, &stream_in};
|
||||
SetWindowTextW(editor_, L"");
|
||||
SendMessageW(editor_, EM_STREAMIN, SF_RTF, reinterpret_cast<LPARAM>(&stream));
|
||||
CloseHandle(file);
|
||||
if (stream.dwError != 0) {
|
||||
throw std::runtime_error("Rich Edit could not read the RTF stream");
|
||||
}
|
||||
} else {
|
||||
const auto text = decode_text(read_file(path));
|
||||
SetWindowTextW(editor_, text.c_str());
|
||||
}
|
||||
current_path_ = path;
|
||||
loading_ = false;
|
||||
set_clean();
|
||||
} catch (const std::exception& error) {
|
||||
loading_ = false;
|
||||
const std::wstring detail(error.what(), error.what() + std::strlen(error.what()));
|
||||
MessageBoxW(main_window_, (L"Could not open the document.\n\n" + detail).c_str(),
|
||||
kProductName, MB_OK | MB_ICONERROR);
|
||||
}
|
||||
}
|
||||
|
||||
bool save(const bool save_as) {
|
||||
std::wstring path = current_path_;
|
||||
if (save_as || path.empty()) {
|
||||
std::array<wchar_t, 32768> selected{};
|
||||
if (!path.empty()) {
|
||||
wcsncpy_s(selected.data(), selected.size(), path.c_str(), _TRUNCATE);
|
||||
} else {
|
||||
wcscpy_s(selected.data(), selected.size(), L"Document.rtf");
|
||||
}
|
||||
OPENFILENAMEW dialog{sizeof(dialog)};
|
||||
dialog.hwndOwner = main_window_;
|
||||
dialog.lpstrFilter = L"Rich Text Format (*.rtf)\0*.rtf\0UTF-8 text (*.txt)\0*.txt\0";
|
||||
dialog.lpstrFile = selected.data();
|
||||
dialog.nMaxFile = static_cast<DWORD>(selected.size());
|
||||
dialog.lpstrDefExt = L"rtf";
|
||||
dialog.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
|
||||
if (!GetSaveFileNameW(&dialog)) {
|
||||
return false;
|
||||
}
|
||||
path = selected.data();
|
||||
}
|
||||
|
||||
try {
|
||||
if (lower_extension(path) == L".rtf") {
|
||||
const HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (file == INVALID_HANDLE_VALUE) {
|
||||
throw std::runtime_error("CreateFileW failed: " + std::to_string(GetLastError()));
|
||||
}
|
||||
EDITSTREAM stream{reinterpret_cast<DWORD_PTR>(file), 0, &stream_out};
|
||||
SendMessageW(editor_, EM_STREAMOUT, SF_RTF, reinterpret_cast<LPARAM>(&stream));
|
||||
CloseHandle(file);
|
||||
if (stream.dwError != 0) {
|
||||
throw std::runtime_error("Rich Edit could not write the RTF stream");
|
||||
}
|
||||
} else {
|
||||
write_file(path, encode_utf8(document_text()));
|
||||
}
|
||||
current_path_ = path;
|
||||
set_clean();
|
||||
return true;
|
||||
} catch (const std::exception& error) {
|
||||
const std::wstring detail(error.what(), error.what() + std::strlen(error.what()));
|
||||
MessageBoxW(main_window_, (L"Could not save the document.\n\n" + detail).c_str(),
|
||||
kProductName, MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool prompt_save_changes() {
|
||||
if (SendMessageW(editor_, EM_GETMODIFY, 0, 0) == 0) {
|
||||
return true;
|
||||
}
|
||||
const int answer = MessageBoxW(main_window_, L"Save changes to this document?",
|
||||
kProductName, MB_YESNOCANCEL | MB_ICONQUESTION);
|
||||
return answer == IDNO || (answer == IDYES && save(false));
|
||||
}
|
||||
|
||||
void set_clean() {
|
||||
SendMessageW(editor_, EM_SETMODIFY, FALSE, 0);
|
||||
update_title();
|
||||
status_dirty_ = true;
|
||||
}
|
||||
|
||||
void update_title() const {
|
||||
std::wstring name = current_path_.empty()
|
||||
? L"Document"
|
||||
: std::filesystem::path(current_path_).filename().wstring();
|
||||
if (!loading_ && SendMessageW(editor_, EM_GETMODIFY, 0, 0) != 0) {
|
||||
name += L" *";
|
||||
}
|
||||
SetWindowTextW(main_window_, (name + L" - " + kProductName).c_str());
|
||||
}
|
||||
|
||||
[[nodiscard]] std::wstring document_text() const {
|
||||
const int length = GetWindowTextLengthW(editor_);
|
||||
std::wstring text(static_cast<std::size_t>(length) + 1, L'\0');
|
||||
if (length != 0) {
|
||||
GetWindowTextW(editor_, text.data(), length + 1);
|
||||
}
|
||||
text.resize(static_cast<std::size_t>(length));
|
||||
return text;
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::pair<std::size_t, std::size_t> count_text(
|
||||
const std::wstring_view text) {
|
||||
std::size_t words = 0;
|
||||
std::size_t characters = 0;
|
||||
bool in_word = false;
|
||||
for (const wchar_t character : text) {
|
||||
if (character != L'\r' && character != L'\n') {
|
||||
++characters;
|
||||
}
|
||||
const bool word_character = std::iswalnum(character) != 0 || character == L'_';
|
||||
if (word_character && !in_word) {
|
||||
++words;
|
||||
}
|
||||
in_word = word_character;
|
||||
}
|
||||
return {words, characters};
|
||||
}
|
||||
|
||||
void update_status() {
|
||||
const auto text = document_text();
|
||||
const auto [words, characters] = count_text(text);
|
||||
CHARRANGE selection{};
|
||||
SendMessageW(editor_, EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&selection));
|
||||
const LRESULT line = SendMessageW(editor_, EM_LINEFROMCHAR, selection.cpMin, 0);
|
||||
const LRESULT line_start = SendMessageW(editor_, EM_LINEINDEX, line, 0);
|
||||
|
||||
const std::wstring file = current_path_.empty() ? L" New document" : L" " + current_path_;
|
||||
const std::wstring counts = L"Words: " + std::to_wstring(words) +
|
||||
L" Characters: " + std::to_wstring(characters);
|
||||
const std::wstring position = L"Ln " + std::to_wstring(line + 1) +
|
||||
L", Col " + std::to_wstring(selection.cpMin - line_start + 1);
|
||||
SendMessageW(status_, SB_SETTEXTW, 0, reinterpret_cast<LPARAM>(file.c_str()));
|
||||
SendMessageW(status_, SB_SETTEXTW, 1, reinterpret_cast<LPARAM>(counts.c_str()));
|
||||
SendMessageW(status_, SB_SETTEXTW, 2, reinterpret_cast<LPARAM>(position.c_str()));
|
||||
status_dirty_ = false;
|
||||
}
|
||||
|
||||
void show_word_count() const {
|
||||
const auto [words, characters] = count_text(document_text());
|
||||
const std::wstring message = L"Words: " + std::to_wstring(words) +
|
||||
L"\nCharacters (excluding paragraph marks): " +
|
||||
std::to_wstring(characters);
|
||||
MessageBoxW(main_window_, message.c_str(), L"Word Count", MB_OK | MB_ICONINFORMATION);
|
||||
}
|
||||
|
||||
void toggle_effect(const DWORD mask, const DWORD effect) const {
|
||||
CHARFORMAT2W format{sizeof(format)};
|
||||
format.dwMask = mask;
|
||||
SendMessageW(editor_, EM_GETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(&format));
|
||||
format.dwEffects ^= effect;
|
||||
SendMessageW(editor_, EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(&format));
|
||||
}
|
||||
|
||||
void set_alignment(const WORD alignment) const {
|
||||
PARAFORMAT2 format{sizeof(format)};
|
||||
format.dwMask = PFM_ALIGNMENT;
|
||||
format.wAlignment = alignment;
|
||||
SendMessageW(editor_, EM_SETPARAFORMAT, 0, reinterpret_cast<LPARAM>(&format));
|
||||
}
|
||||
|
||||
void choose_font() const {
|
||||
CHARFORMAT2W current{sizeof(current)};
|
||||
SendMessageW(editor_, EM_GETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(¤t));
|
||||
HDC dc = GetDC(editor_);
|
||||
LOGFONTW font{};
|
||||
font.lfHeight = -MulDiv(current.yHeight / 20, GetDeviceCaps(dc, LOGPIXELSY), 72);
|
||||
font.lfWeight = (current.dwEffects & CFE_BOLD) != 0 ? FW_BOLD : FW_NORMAL;
|
||||
font.lfItalic = (current.dwEffects & CFE_ITALIC) != 0;
|
||||
font.lfUnderline = (current.dwEffects & CFE_UNDERLINE) != 0;
|
||||
wcsncpy_s(font.lfFaceName, current.szFaceName, _TRUNCATE);
|
||||
ReleaseDC(editor_, dc);
|
||||
|
||||
CHOOSEFONTW dialog{sizeof(dialog)};
|
||||
dialog.hwndOwner = main_window_;
|
||||
dialog.lpLogFont = &font;
|
||||
dialog.iPointSize = current.yHeight / 2;
|
||||
dialog.rgbColors = current.crTextColor;
|
||||
dialog.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_EFFECTS;
|
||||
if (!ChooseFontW(&dialog)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CHARFORMAT2W selected{sizeof(selected)};
|
||||
selected.dwMask = CFM_FACE | CFM_SIZE | CFM_COLOR | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE;
|
||||
selected.yHeight = dialog.iPointSize * 2;
|
||||
selected.crTextColor = dialog.rgbColors;
|
||||
selected.dwEffects = (font.lfWeight >= FW_BOLD ? CFE_BOLD : 0) |
|
||||
(font.lfItalic ? CFE_ITALIC : 0) |
|
||||
(font.lfUnderline ? CFE_UNDERLINE : 0);
|
||||
wcsncpy_s(selected.szFaceName, font.lfFaceName, _TRUNCATE);
|
||||
SendMessageW(editor_, EM_SETCHARFORMAT, SCF_SELECTION, reinterpret_cast<LPARAM>(&selected));
|
||||
}
|
||||
|
||||
void show_find() {
|
||||
if (find_dialog_ != nullptr) {
|
||||
SetForegroundWindow(find_dialog_);
|
||||
return;
|
||||
}
|
||||
find_state_ = {};
|
||||
find_state_.lStructSize = sizeof(find_state_);
|
||||
find_state_.hwndOwner = main_window_;
|
||||
find_state_.Flags = FR_DOWN;
|
||||
find_state_.lpstrFindWhat = find_text_.data();
|
||||
find_state_.wFindWhatLen = static_cast<WORD>(find_text_.size());
|
||||
find_dialog_ = FindTextW(&find_state_);
|
||||
}
|
||||
|
||||
void on_find_message(const FINDREPLACEW& message) {
|
||||
if ((message.Flags & FR_DIALOGTERM) != 0) {
|
||||
find_dialog_ = nullptr;
|
||||
return;
|
||||
}
|
||||
if ((message.Flags & FR_FINDNEXT) != 0) {
|
||||
find_flags_ = message.Flags;
|
||||
find_next(message.Flags);
|
||||
}
|
||||
}
|
||||
|
||||
void find_next(const DWORD flags) {
|
||||
if (find_text_[0] == L'\0') {
|
||||
show_find();
|
||||
return;
|
||||
}
|
||||
CHARRANGE selection{};
|
||||
SendMessageW(editor_, EM_EXGETSEL, 0, reinterpret_cast<LPARAM>(&selection));
|
||||
const LONG text_length = GetWindowTextLengthW(editor_);
|
||||
FINDTEXTEXW search{};
|
||||
search.lpstrText = find_text_.data();
|
||||
const bool down = (flags & FR_DOWN) != 0;
|
||||
search.chrg = down ? CHARRANGE{selection.cpMax, text_length}
|
||||
: CHARRANGE{selection.cpMin - 1, 0};
|
||||
DWORD options = down ? FR_DOWN : 0;
|
||||
options |= flags & (FR_MATCHCASE | FR_WHOLEWORD);
|
||||
LRESULT result = SendMessageW(editor_, EM_FINDTEXTEXW, options,
|
||||
reinterpret_cast<LPARAM>(&search));
|
||||
if (result == -1) {
|
||||
search.chrg = down ? CHARRANGE{0, text_length} : CHARRANGE{text_length, 0};
|
||||
result = SendMessageW(editor_, EM_FINDTEXTEXW, options,
|
||||
reinterpret_cast<LPARAM>(&search));
|
||||
}
|
||||
if (result == -1) {
|
||||
MessageBeep(MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
SendMessageW(editor_, EM_EXSETSEL, 0, reinterpret_cast<LPARAM>(&search.chrgText));
|
||||
SendMessageW(editor_, EM_SCROLLCARET, 0, 0);
|
||||
SetFocus(editor_);
|
||||
}
|
||||
|
||||
void print_document() const {
|
||||
PRINTDLGW dialog{sizeof(dialog)};
|
||||
dialog.hwndOwner = main_window_;
|
||||
dialog.Flags = PD_RETURNDC | PD_NOSELECTION | PD_USEDEVMODECOPIESANDCOLLATE;
|
||||
if (!PrintDlgW(&dialog)) {
|
||||
return;
|
||||
}
|
||||
DOCINFOW document{sizeof(document)};
|
||||
document.lpszDocName = current_path_.empty() ? L"Document" : current_path_.c_str();
|
||||
if (StartDocW(dialog.hDC, &document) <= 0) {
|
||||
DeleteDC(dialog.hDC);
|
||||
return;
|
||||
}
|
||||
|
||||
FORMATRANGE range{};
|
||||
range.hdc = dialog.hDC;
|
||||
range.hdcTarget = dialog.hDC;
|
||||
const int dpi_x = GetDeviceCaps(dialog.hDC, LOGPIXELSX);
|
||||
const int dpi_y = GetDeviceCaps(dialog.hDC, LOGPIXELSY);
|
||||
range.rcPage = {0, 0,
|
||||
MulDiv(GetDeviceCaps(dialog.hDC, PHYSICALWIDTH), 1440, dpi_x),
|
||||
MulDiv(GetDeviceCaps(dialog.hDC, PHYSICALHEIGHT), 1440, dpi_y)};
|
||||
constexpr LONG margin = 1080;
|
||||
range.rc = {range.rcPage.left + margin, range.rcPage.top + margin,
|
||||
range.rcPage.right - margin, range.rcPage.bottom - margin};
|
||||
range.chrg = {0, -1};
|
||||
|
||||
LONG next = 0;
|
||||
const LONG length = GetWindowTextLengthW(editor_);
|
||||
bool failed = false;
|
||||
while (next < length && !failed) {
|
||||
if (StartPage(dialog.hDC) <= 0) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
range.chrg.cpMin = next;
|
||||
next = static_cast<LONG>(SendMessageW(editor_, EM_FORMATRANGE, TRUE,
|
||||
reinterpret_cast<LPARAM>(&range)));
|
||||
if (EndPage(dialog.hDC) <= 0 || next <= range.chrg.cpMin) {
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
SendMessageW(editor_, EM_FORMATRANGE, FALSE, 0);
|
||||
failed ? AbortDoc(dialog.hDC) : EndDoc(dialog.hDC);
|
||||
DeleteDC(dialog.hDC);
|
||||
}
|
||||
|
||||
void show_about() const {
|
||||
const std::wstring message =
|
||||
L"Microsoft Word for Windows — native x64 migration\n\n"
|
||||
L"This executable is AMD64 and contains no 16-bit assembly.\n"
|
||||
L"Legacy assembly modules accounted for: " +
|
||||
std::to_wstring(opus::x64::assembly_manifest_count()) +
|
||||
L"\n\nCurrent document formats: RTF and UTF-8 text.";
|
||||
MessageBoxW(main_window_, message.c_str(), L"About", MB_OK | MB_ICONINFORMATION);
|
||||
}
|
||||
|
||||
static DWORD CALLBACK stream_in(const DWORD_PTR cookie, const LPBYTE buffer,
|
||||
const LONG byte_count, LONG* bytes_read) {
|
||||
DWORD read = 0;
|
||||
if (!ReadFile(reinterpret_cast<HANDLE>(cookie), buffer,
|
||||
static_cast<DWORD>(byte_count), &read, nullptr)) {
|
||||
*bytes_read = 0;
|
||||
return GetLastError();
|
||||
}
|
||||
*bytes_read = static_cast<LONG>(read);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DWORD CALLBACK stream_out(const DWORD_PTR cookie, const LPBYTE buffer,
|
||||
const LONG byte_count, LONG* bytes_written) {
|
||||
DWORD written = 0;
|
||||
if (!WriteFile(reinterpret_cast<HANDLE>(cookie), buffer,
|
||||
static_cast<DWORD>(byte_count), &written, nullptr)) {
|
||||
*bytes_written = 0;
|
||||
return GetLastError();
|
||||
}
|
||||
*bytes_written = static_cast<LONG>(written);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void show_error(const std::wstring_view message, const DWORD error) const {
|
||||
const std::wstring detail = std::wstring(message) + L".\n\n" + system_error_message(error);
|
||||
MessageBoxW(main_window_, detail.c_str(), kProductName, MB_OK | MB_ICONERROR);
|
||||
}
|
||||
|
||||
HINSTANCE instance_{};
|
||||
HMODULE rich_edit_library_{};
|
||||
HWND main_window_{};
|
||||
HWND editor_{};
|
||||
HWND status_{};
|
||||
HWND find_dialog_{};
|
||||
UINT find_message_{};
|
||||
FINDREPLACEW find_state_{};
|
||||
std::array<wchar_t, 256> find_text_{};
|
||||
DWORD find_flags_{FR_DOWN};
|
||||
std::wstring current_path_;
|
||||
bool loading_{};
|
||||
bool status_dirty_{true};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int WINAPI wWinMain(const HINSTANCE instance, HINSTANCE, PWSTR, const int show_command) {
|
||||
WordApplication application(instance);
|
||||
return application.run(show_command);
|
||||
}
|
||||
Reference in New Issue
Block a user