File Save/Open now works correctly.

This commit is contained in:
Justin Marshall
2026-08-06 23:57:43 -07:00
parent 467f7f712b
commit 031b19a9c8
16 changed files with 655 additions and 8 deletions
+6
View File
@@ -473,7 +473,13 @@ int fDelete; /* delete the file */
/* the sad truth is that we couldn't care less whether the file is deleted;
better to let the user see temp files than warn him the delete failed */
if (fDelete)
{
#ifdef OPUS_X64
EcOsDelete(st, 0); /* volume is unused by the Windows implementation */
#else
EcOsDelete(st, vol);
#endif
}
} /* end DeleteFn */
+2 -2
View File
@@ -1692,7 +1692,7 @@ uns cbTotal;
/* %%Function:HplReadPlf %%Owner:peterj */
/* H P L R E A D P L F */
HplReadPlf(fn, fcFirst, cb)
struct PL **HplReadPlf(fn, fcFirst, cb)
int fn;
FC fcFirst;
int cb;
@@ -1714,7 +1714,7 @@ int cb;
/* %%Function:HsttbReadSttbFromFile %%Owner:peterj */
/* H S T T B R E A D S T T B F F R O M F I L E */
HsttbReadSttbfFromFile(fn, fc, fExternal, fStyleRules, cbExtra)
struct STTB **HsttbReadSttbfFromFile(fn, fc, fExternal, fStyleRules, cbExtra)
int fn;
FC fc;
BOOL fExternal, fStyleRules;
+1 -1
View File
@@ -587,7 +587,7 @@ PN pn;
Assert (vbptbExt.ibpMax < 32768 / 4);
ispnMax = ((vbptbExt.ibpMax << 2) / vbptbExt.cqbpspn);
Debug(ispnOldest = ispnSecondOldest = -1);
ispnOldest = ispnSecondOldest = -1;
tsOldest = tsSecondOldest = 0;
for (ispn = 0; ispn < ispnMax; ispn++)
{
+4
View File
@@ -29,6 +29,10 @@ typedef struct
} MST;
#ifdef OPUS_X64
struct PPR;
struct PPR **HpprStartProgressReport(MST mst, unsigned *rgw, int nIncr,
int fAbortCheck);
static __inline MST OpusMakeMst(cxt, st)
int cxt;
char *st;
+10
View File
@@ -92,7 +92,17 @@ struct PPR
#define MmoFromCmg(cmg) ((cmg) & mmoNewestCmg)
#ifndef OPUS_X64
struct PPR **HpprStartProgressReport();
#endif
#ifdef OPUS_X64
/* The AMD64 prompt implementation lives in opus_asm_prompt.cpp. A complete
prototype is required here: with an unprototyped call, a literal NULL in
the fifth argument only initializes part of the native pointer stack slot. */
void ProgressReportPercent(struct PPR **hppr, long lLow, long lHigh,
long l, long *plNext);
void ChangeProgressReport(struct PPR **hppr, unsigned nNew);
#endif
NATIVE FQueryAbortCheckProc();
EXPORT AdjustPrompt();
+18
View File
@@ -103,6 +103,24 @@ static int vWin95ZoomPercent = 100;
static int vWin95BaseDxsInch = 0;
static int vWin95BaseDysInch = 0;
/* The Word 1.x Color command opens a keyboard prompt. The Win95 chrome
supplies a palette instead, but still applies the original character
property so document storage and formatting behavior remain unchanged. */
int OpusApplyWin95TextColor(hwnd, ico)
HWND hwnd;
int ico;
{
int ww = WwFromHwnd(hwnd);
if (ww < wwDocMin || ww >= wwMac || selCur.ww != ww ||
ico < icoAuto || ico >= icoMax)
return fFalse;
if (!FApplyOneProp(sprmCIco, ico))
return fFalse;
selCur.fUpdateRuler = fTrue;
return fTrue;
}
/* Read-only layout metrics used by the Win95 vertical ruler overlay. */
int OpusGetWin95VerticalRulerMetrics(hwnd, pypPageTop, pypPageBottom,
pdypTopMargin, pdypBottomMargin, pdypInch)
+34
View File
@@ -86,6 +86,14 @@ DEBUGASSERTSZ /* WIN - bogus macro for assert string */
#include "saveas.hs"
#include "saveas.sdm"
#ifdef OPUS_X64
extern int OpusWin95SaveAliasMatches();
extern int OpusFinishWin95SaveAlias();
#define FWin95SaveAliasMatches(st) OpusWin95SaveAliasMatches(st)
#else
#define FWin95SaveAliasMatches(st) fFalse
#endif
#ifdef PROTOTYPE
#include "save.cpt"
@@ -548,6 +556,13 @@ LSaveAs:
AssertDo(FNormalizeStFile(stT, stFile,
(*hcab)->iFmt == ISaveFmtDff(dffSaveDocType) ?
nfoDot : nfoNormal));
#ifdef OPUS_X64
/* The restored SDM summary-properties dialog is not part of the
Win95 common-dialog path. Do not leave Save As waiting on that
hidden secondary modal; properties remain available from File. */
fPromptSI = fFalse;
PdodDoc(doc)->fPromptSI = fFalse;
#endif
}
if (pcmb->fCheck)
@@ -677,6 +692,14 @@ LSaveAs:
#endif /* DCORELOAD */
LCleanup:
#ifdef OPUS_X64
if (cmd == cmdOK && pcmb->fAction &&
FWin95SaveAliasMatches(stFile) && PdodDoc(doc)->fn != fnNil)
FCloseFn(PdodDoc(doc)->fn);
if (!OpusFinishWin95SaveAlias(stFile,
cmd == cmdOK && pcmb->fAction) && cmd == cmdOK)
cmd = cmdError;
#endif
if (cmd == cmdCancelled)
fGuaranteeSave = fFalse;
@@ -960,6 +983,7 @@ LStayIn:
on the currently-on-line floppy */
if (FExistsStFile(stFile, fFalse /* fAnywhere */) &&
!FWin95SaveAliasMatches(stFile) &&
((fn = PdodDoc(pcesa->doc)->fn) == fnNil ||
FnFromOnlineSt(stFile) != fn))
{
@@ -989,6 +1013,7 @@ LStayIn:
}
/* check for overwritting foreign file */
if (!FWin95SaveAliasMatches(stFile))
switch (IdOverwriteForeign (pcesa->doc, stFile, (*hcab)->iFmt))
{
case IDYES:
@@ -2889,12 +2914,21 @@ CHAR *stFile;
int doc;
{
#ifdef OPUS_X64
/* The original progress-report handle can be retired by nested prompt
activity while the save writer is still holding vhpprSave. Leave the
save progress field inactive on the flat port; the normal completion
prompt below still reports the saved document and character count. */
vhpprSave = hNil;
return 0;
#else
int nIncr = NIncrFromL(CpMacDoc(doc));
Assert(vhpprSave == hNil);
if ((vhpprSave = HpprStartProgressReport (mst, &stFile, nIncr, fFalse))
== hNil)
/* no biggie */
vmerr.mat = matNil;
#endif
}
+8
View File
@@ -1023,6 +1023,14 @@ struct PLB
int fNoTsNonDest; /* don't update tsMru on fns != fnDest */
};
#ifdef OPUS_X64
/* These readers return movable-heap handles. The original K&R sources
* relied on implicit-int declarations, which truncate the handles on AMD64. */
struct STTB **HsttbReadSttbfFromFile(int fn, FC fc, int fExternal,
int fStyleRules, int cbExtra);
struct PL **HplReadPlf(int fn, FC fcFirst, int cb);
#endif
/* fast io block buffer */
#ifdef OPUS_X64
+1 -1
View File
@@ -1369,7 +1369,7 @@ int *pfPieceTableItem;
{
vpqsib->ipcd = IInPlc(hplcpcd, vpqsib->cpPicFetch - 1);
vpqsib->cpPicFetchLim = CpPlc(hplcpcd, vpqsib->ipcd + 1);
if (ipcd + 1 == IMacPlc(hplcpcd))
if (vpqsib->ipcd + 1 == IMacPlc(hplcpcd))
vpqsib->cpPicFetchLim--;
}
}
+9
View File
@@ -601,8 +601,13 @@ rather than before the first char of line dl (same cp position!) */
struct MERR
{
#ifdef OPUS_X64
void **hrgwEmerg1; /* native movable handle; hNil means stage 1 emergency */
void **hrgwEmerg2; /* native movable handle reserved for Save */
#else
int hrgwEmerg1; /* if hNil, we have stage 1 heap emergency */
int hrgwEmerg2; /* for use by Save */
#endif
int fDocFull; /* maximum number of docs has been allocated */
int fFnFull; /* maximum number of fns has been allocated */
int fWwFull; /* maximum number of wws has been allocated */
@@ -646,7 +651,11 @@ struct MERR
int fPrintEmerg; /* Set if printer DC is unavailable */
int fDclFull; /* maximum number of dcls (dde channels) allocated */
#endif
#ifdef OPUS_X64
void **hrgwEmerg3; /* native movable handle reserved for EmergSave */
#else
int hrgwEmerg3; /* for use by EmergSave */
#endif
int fWarnDocTooBig; /* set if some doc has grown beyond cpWarnTooBig */
#ifdef MAC
long qqrgwEmergSave; /* Mac - to make sure we are able to save */
+2
View File
@@ -1753,6 +1753,8 @@ LONG lParam;
return (LRESULT)PwwdWw(WwFromHwnd(hwnd))->rcePage.yeBottom;
case 78:
return (LRESULT)OpusGetWin95ZoomPercent(hwnd);
case 79:
return (LRESULT)selCur.chp.ico;
}
return (LRESULT) -1;
#endif
+6 -1
View File
@@ -113,6 +113,9 @@ extern int vfTableKeymap;
extern HWND vhwndMsgBoxParent;
extern struct UAB vuab;
extern int dclMac;
#ifdef OPUS_X64
extern int OpusWin95DisplayAlias();
#endif
extern CHAR szApp[];
extern HBRUSH vhbrGray;
extern int mwMac;
@@ -964,7 +967,6 @@ int nfo;
*
*/
/* %%Function:FNormalizeSzFile %%Owner:chic */
FNormalizeSzFile( szFile, szNormal, nfo )
CHAR *szFile;
@@ -2015,6 +2017,9 @@ int rgmw[], imwMac;
Assert(doc != docNil);
GetDocSt(doc, st, gdsoRelative);
#ifdef OPUS_X64
OpusWin95DisplayAlias(st);
#endif
if (imwMac > 1)
/* compute imw of mw and append :<imw+1> */
+41
View File
@@ -1,7 +1,12 @@
#include "opus_x64_compat.h"
#ifdef OpenFile
#undef OpenFile
#endif
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <limits>
/* AMD64 translation of FILEWINN.ASM. Word continues to open files through
@@ -46,6 +51,42 @@ extern "C" {
extern NativeBptbPrefix vbptbExt;
int OpusOpenFile(const LPCSTR file_name, void* const legacy_ofs,
const UINT style) {
struct LegacyOfStruct {
int byte_count : 8;
int fixed_disk : 8;
WORD error_code;
BYTE reserved[4];
CHAR path[120];
};
if (legacy_ofs == nullptr) {
SetLastError(ERROR_INVALID_PARAMETER);
return HFILE_ERROR;
}
auto* const legacy = static_cast<LegacyOfStruct*>(legacy_ofs);
OFSTRUCT native_ofs{};
native_ofs.cBytes = sizeof(native_ofs);
native_ofs.fFixedDisk = legacy->fixed_disk;
native_ofs.nErrCode = legacy->error_code;
std::memcpy(&native_ofs.Reserved1, legacy->reserved,
sizeof(legacy->reserved));
lstrcpynA(native_ofs.szPathName, legacy->path,
static_cast<int>(std::size(native_ofs.szPathName)));
const HFILE result = OpenFile(file_name, &native_ofs, style);
legacy->byte_count = native_ofs.cBytes;
legacy->fixed_disk = native_ofs.fFixedDisk;
legacy->error_code = native_ofs.nErrCode;
std::memcpy(legacy->reserved, &native_ofs.Reserved1,
sizeof(legacy->reserved));
lstrcpynA(legacy->path, native_ofs.szPathName,
static_cast<int>(std::size(legacy->path)));
return result;
}
int FCloseDoshnd(const HFILE file) {
if (file == HFILE_ERROR) {
return 0;
+354
View File
@@ -1,10 +1,12 @@
#include "opus_x64_compat.h"
#include "opus_x64_heap.h"
#include <commdlg.h>
extern "C" {
#include "dac.h"
}
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdint>
#include <cstring>
@@ -117,6 +119,82 @@ FontValueProc g_font_name_to_value = nullptr;
FontValueProc g_font_size_to_value = nullptr;
FontNameFromValueProc g_font_name_from_value = nullptr;
struct Win95SaveAlias {
bool active = false;
bool created = false;
std::string selected_path;
std::string legacy_path;
};
Win95SaveAlias g_win95_save_alias;
std::unordered_map<std::string, std::string> g_win95_saved_aliases;
struct Win95AliasCleanup {
~Win95AliasCleanup() {
if (g_win95_save_alias.created &&
!g_win95_save_alias.legacy_path.empty()) {
DeleteFileA(g_win95_save_alias.legacy_path.c_str());
}
for (const auto& saved : g_win95_saved_aliases) {
DeleteFileA(saved.first.c_str());
}
}
};
Win95AliasCleanup g_win95_alias_cleanup;
std::string win95_alias_key(std::string path) {
std::transform(path.begin(), path.end(), path.begin(),
[](const unsigned char character) {
return static_cast<char>(std::tolower(character));
});
return path;
}
bool make_win95_staging_path(std::string& path) {
char module_path[32768]{};
const DWORD module_length = GetModuleFileNameA(
nullptr, module_path, static_cast<DWORD>(std::size(module_path)));
if (module_length == 0 || module_length >= std::size(module_path)) {
return false;
}
char* separator = std::strrchr(module_path, '\\');
if (separator == nullptr) {
return false;
}
*separator = '\0';
std::string directory = std::string(module_path) + "\\W95TEMP";
if (!CreateDirectoryA(directory.c_str(), nullptr) &&
GetLastError() != ERROR_ALREADY_EXISTS) {
return false;
}
for (int attempt = 0; attempt < 32; ++attempt) {
char temporary[MAX_PATH]{};
if (GetTempFileNameA(directory.c_str(), "W95", 0, temporary) == 0) {
return false;
}
DeleteFileA(temporary);
char* extension = std::strrchr(temporary, '.');
if (extension != nullptr) {
lstrcpyA(extension, ".DOC");
}
if (GetFileAttributesA(temporary) == INVALID_FILE_ATTRIBUTES &&
std::strlen(temporary) < 120) {
path = temporary;
return true;
}
}
return false;
}
std::string counted_path(const unsigned char* st_file) {
if (st_file == nullptr || st_file[0] == 0 || st_file[0] >= 120) {
return {};
}
return std::string(reinterpret_cast<const char*>(st_file + 1),
st_file[0]);
}
constexpr Word kDlmInit = 0x0001;
constexpr Word kDlmTerm = 0x0003;
constexpr Word kDlmExit = 0x0004;
@@ -2022,6 +2100,181 @@ void handle_dialog_command(const Hdlg handle, const WPARAM w_param,
}
}
Tmc run_word95_common_file_dialog(DialogState& dialog) {
const bool opening = dialog.hid == kIddOpen;
const bool saving = dialog.hid == kIddSaveAs;
if (!opening && !saving) {
return static_cast<Tmc>(-1);
}
invoke_dialog_proc(dialog, kDlmInit, dialog.focus);
dialog.commands_active = true;
if (opening) {
read_open_cab(dialog);
} else {
read_save_cab(dialog);
}
HWND owner = dialog.window != nullptr ?
GetWindow(dialog.window, GW_OWNER) : nullptr;
if (owner == nullptr || !IsWindow(owner)) {
owner = vhWndMsgBoxParent;
}
std::vector<char> file_buffer(32768, '\0');
std::string initial = opening ?
dialog.controls[kTmcOpenFileName].text :
dialog.controls[kTmcSaveFile].text;
const auto initial_alias =
g_win95_saved_aliases.find(win95_alias_key(initial));
if (initial_alias != g_win95_saved_aliases.end()) {
initial = initial_alias->second;
}
establish_open_directory(dialog, initial.empty() ? "*.*" : initial);
if (initial.find_first_of("*?") == std::string::npos) {
lstrcpynA(file_buffer.data(), initial.c_str(),
static_cast<int>(file_buffer.size()));
}
std::string default_extension = "doc";
const std::size_t slash = initial.find_last_of("\\/");
const std::size_t dot = initial.find_last_of('.');
if (dot != std::string::npos &&
(slash == std::string::npos || dot > slash) &&
dot + 1 < initial.size()) {
default_extension = initial.substr(dot + 1);
}
static const char open_filter[] =
"Word Documents (*.doc;*.dot)\0*.doc;*.dot\0"
"Rich Text Format (*.rtf)\0*.rtf\0"
"Text Files (*.txt)\0*.txt\0"
"All Files (*.*)\0*.*\0\0";
static const char save_filter[] =
"Word Documents (*.doc)\0*.doc\0"
"Document Templates (*.dot)\0*.dot\0"
"All Files (*.*)\0*.*\0\0";
Tmc result = kTmcCancel;
for (;;) {
OPENFILENAMEA file_dialog{};
file_dialog.lStructSize = sizeof(file_dialog);
file_dialog.hwndOwner = owner;
file_dialog.lpstrFilter = opening ? open_filter : save_filter;
file_dialog.nFilterIndex = 1;
file_dialog.lpstrFile = file_buffer.data();
file_dialog.nMaxFile = static_cast<DWORD>(file_buffer.size());
file_dialog.lpstrInitialDir = dialog.current_directory.empty() ?
nullptr : dialog.current_directory.c_str();
file_dialog.lpstrTitle = opening ? "Open" : "Save As";
file_dialog.lpstrDefExt = default_extension.c_str();
file_dialog.Flags = OFN_EXPLORER | OFN_ENABLESIZING |
OFN_LONGNAMES | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST;
if (opening) {
file_dialog.Flags |= OFN_FILEMUSTEXIST;
if (dialog.controls[kTmcOpenReadOnly].value != 0) {
file_dialog.Flags |= OFN_READONLY;
}
} else {
file_dialog.Flags |= OFN_NOREADONLYRETURN |
OFN_OVERWRITEPROMPT;
}
const BOOL accepted = opening ?
GetOpenFileNameA(&file_dialog) : GetSaveFileNameA(&file_dialog);
if (!accepted) {
if (CommDlgExtendedError() != 0) {
MessageBoxA(owner,
"The Windows file dialog could not be opened.",
opening ? "Open" : "Save As",
MB_OK | MB_ICONEXCLAMATION);
result = static_cast<Tmc>(-1);
} else {
invoke_dialog_proc(dialog, kDlmTerm, kTmcCancel);
result = kTmcCancel;
}
break;
}
const std::string selected_path = file_buffer.data();
std::string legacy_path;
if (!make_win95_staging_path(legacy_path)) {
MessageBoxA(owner,
"Word could not prepare a temporary document file.",
opening ? "Open" : "Save As",
MB_OK | MB_ICONEXCLAMATION);
continue;
}
if (opening &&
!CopyFileA(selected_path.c_str(), legacy_path.c_str(), FALSE)) {
MessageBoxA(owner, "Word could not open the selected file.",
"Open", MB_OK | MB_ICONEXCLAMATION);
continue;
}
establish_open_directory(dialog, selected_path);
if (opening) {
g_win95_saved_aliases[win95_alias_key(legacy_path)] =
selected_path;
auto& edit = dialog.controls[kTmcOpenFileName];
edit.text = legacy_path;
if (edit.window != nullptr) {
SetWindowTextA(edit.window, edit.text.c_str());
}
auto& read_only = dialog.controls[kTmcOpenReadOnly];
read_only.value =
(file_dialog.Flags & OFN_READONLY) != 0;
if (read_only.window != nullptr) {
SendMessageA(read_only.window, BM_SETCHECK,
read_only.value ? BST_CHECKED : BST_UNCHECKED, 0);
}
sync_open_cab(dialog);
} else {
g_win95_save_alias.active = true;
g_win95_save_alias.created = true;
g_win95_save_alias.selected_path = selected_path;
g_win95_save_alias.legacy_path = legacy_path;
auto& edit = dialog.controls[kTmcSaveFile];
edit.text = legacy_path;
if (edit.window != nullptr) {
SetWindowTextA(edit.window, edit.text.c_str());
}
sync_save_cab(dialog);
}
if (invoke_dialog_proc(dialog, kDlmTerm, kTmcOk)) {
result = dialog.dying ? dialog.result_tmc : kTmcOk;
if (saving && result != kTmcOk) {
if (g_win95_save_alias.created) {
DeleteFileA(g_win95_save_alias.legacy_path.c_str());
}
g_win95_save_alias = {};
}
break;
}
if (dialog.dying) {
result = dialog.result_tmc;
if (saving) {
if (g_win95_save_alias.created) {
DeleteFileA(g_win95_save_alias.legacy_path.c_str());
}
g_win95_save_alias = {};
}
break;
}
if (saving) {
if (g_win95_save_alias.created) {
DeleteFileA(g_win95_save_alias.legacy_path.c_str());
}
g_win95_save_alias = {};
}
}
dialog.commands_active = false;
invoke_dialog_proc(dialog, kDlmExit, result);
return result;
}
LRESULT CALLBACK native_dialog_window_proc(const HWND window,
const UINT message,
const WPARAM w_param,
@@ -2062,6 +2315,90 @@ LRESULT CALLBACK native_dialog_window_proc(const HWND window,
extern "C" {
int OpusWin95SaveAliasMatches(const unsigned char* st_file) {
const std::string path = counted_path(st_file);
if (path.empty()) {
return false;
}
if (g_win95_save_alias.active &&
_stricmp(path.c_str(),
g_win95_save_alias.legacy_path.c_str()) == 0) {
return true;
}
return g_win95_saved_aliases.find(win95_alias_key(path)) !=
g_win95_saved_aliases.end();
}
int OpusWin95DisplayAlias(unsigned char* const st_file) {
const std::string path = counted_path(st_file);
if (path.empty()) {
return false;
}
auto saved = g_win95_saved_aliases.find(win95_alias_key(path));
if (saved == g_win95_saved_aliases.end()) {
char full_path[32768]{};
const DWORD length = GetFullPathNameA(
path.c_str(), static_cast<DWORD>(std::size(full_path)),
full_path, nullptr);
if (length == 0 || length >= std::size(full_path)) {
return false;
}
saved = g_win95_saved_aliases.find(win95_alias_key(full_path));
if (saved == g_win95_saved_aliases.end()) {
return false;
}
}
const char* name = saved->second.c_str();
if (const char* separator = std::strrchr(name, '\\');
separator != nullptr) {
name = separator + 1;
}
const std::size_t length = (std::min)(
std::strlen(name), static_cast<std::size_t>(119));
st_file[0] = static_cast<unsigned char>(length);
std::memcpy(st_file + 1, name, length);
return true;
}
int OpusFinishWin95SaveAlias(const unsigned char* st_file,
const int success) {
const std::string path = counted_path(st_file);
if (path.empty()) {
return !g_win95_save_alias.active;
}
const std::string key = win95_alias_key(path);
if (g_win95_save_alias.active &&
_stricmp(path.c_str(),
g_win95_save_alias.legacy_path.c_str()) == 0) {
bool copied = success != 0;
if (copied) {
copied = CopyFileA(
g_win95_save_alias.legacy_path.c_str(),
g_win95_save_alias.selected_path.c_str(),
FALSE) != 0;
}
if (!success) {
DeleteFileA(g_win95_save_alias.legacy_path.c_str());
} else if (copied) {
g_win95_saved_aliases[key] =
g_win95_save_alias.selected_path;
}
g_win95_save_alias = {};
return copied;
}
const auto saved = g_win95_saved_aliases.find(key);
if (saved == g_win95_saved_aliases.end()) {
return true;
}
if (!success) {
return false;
}
return CopyFileA(path.c_str(), saved->second.c_str(), FALSE) != 0;
}
struct OpusSdsCompat {
std::uintptr_t current_segment;
std::uintptr_t focus_segment;
@@ -2182,6 +2519,23 @@ Tmc TmcDoDlgDli(DltHeader** dialog_template, Hcab cab, Dli* initializer) {
return static_cast<Tmc>(-1);
}
if (dialog->modal &&
(dialog->hid == kIddOpen || dialog->hid == kIddSaveAs)) {
const Tmc common_result = run_word95_common_file_dialog(*dialog);
dialog = find_dialog(handle);
if (dialog != nullptr && dialog->window != nullptr &&
IsWindow(dialog->window)) {
DestroyWindow(dialog->window);
}
g_dialogs.erase(handle);
g_current_dialog = find_dialog(previous_current) == nullptr ?
0 : previous_current;
g_focus_dialog = find_dialog(previous_focus) == nullptr ?
0 : previous_focus;
sync_current_dialog_globals();
return common_result;
}
Tmc result = kTmcCancel;
HWND owner = nullptr;
bool restore_owner = false;
+145 -3
View File
@@ -24,6 +24,7 @@ extern "C" int OpusAdjustWin95Zoom(HWND pane, int percent_delta);
extern "C" int OpusRecenterWin95PageView(HWND pane);
extern "C" int OpusSetWin95VerticalMargin(
HWND pane, int top_margin, int margin_pixels);
extern "C" int OpusApplyWin95TextColor(HWND pane, int color_index);
extern "C" int OpusGetWin95HorizontalRulerMetrics(
HWND ruler, int* zero, int* active_left, int* active_right,
int* pixels_per_inch, int* left_indent, int* first_indent,
@@ -42,6 +43,7 @@ constexpr wchar_t kRulerOverlayClass[] = L"OpusWin95RulerOverlay";
constexpr int kToolbarBitmap = 201;
constexpr int kSpriteCell = 20;
constexpr COLORREF kButtonFace = RGB(192, 192, 192);
constexpr UINT_PTR kMenuRepaintTimer = 0x7f51;
constexpr COLORREF kButtonShadow = RGB(128, 128, 128);
constexpr COLORREF kButtonDarkShadow = RGB(0, 0, 0);
constexpr COLORREF kButtonHighlight = RGB(255, 255, 255);
@@ -52,6 +54,7 @@ constexpr UINT kComboSize = 0x7503;
constexpr UINT kComboZoom = 0x7504;
constexpr UINT kCmdToggleStandardToolbar = 0x7101;
constexpr UINT kCmdToggleFormattingToolbar = 0x7102;
constexpr UINT kCmdTextColorBase = 0x7200;
constexpr UINT_PTR kSyncTimer = 0x951;
constexpr wchar_t kOriginalPaneProcProperty[] = L"OpusWord95OriginalPaneProc";
@@ -153,6 +156,8 @@ struct ToolbarState {
bool formatting_visible = true;
bool startup_page_view_requested = false;
int ruler_refreshes_remaining = 0;
int text_color_index = 0;
COLORREF text_color = RGB(0, 0, 0);
HitResult pressed{};
std::array<bool, kStandardButtons.size()> standard_latched{};
std::array<bool, kFormatButtons.size()> format_latched{};
@@ -244,6 +249,18 @@ void style_menu_tree(HMENU menu) {
}
}
BOOL CALLBACK repaint_menu_popup(HWND window, LPARAM) {
wchar_t class_name[32]{};
GetClassNameW(window, class_name,
static_cast<int>(std::size(class_name)));
if (lstrcmpW(class_name, L"#32768") == 0 && IsWindowVisible(window)) {
RedrawWindow(window, nullptr, nullptr,
RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW |
RDW_ALLCHILDREN | RDW_FRAME);
}
return TRUE;
}
void configure_word95_menus(HWND window) {
HMENU root = GetMenu(window);
if (root == nullptr || GetMenuItemCount(root) < 7) {
@@ -591,7 +608,8 @@ void draw_alignment(HDC dc, RECT area, FormatGlyph glyph) {
}
void draw_format_glyph(HWND toolbar, HDC dc, const FormatButton& spec,
RECT rect, bool sunken, HDC sprite_dc) {
RECT rect, bool sunken, HDC sprite_dc,
COLORREF text_color) {
const int offset = sunken ? scale(toolbar, 1) : 0;
RECT area{rect.left + scale(toolbar, 4) + offset,
rect.top + scale(toolbar, 4) + offset,
@@ -623,7 +641,7 @@ void draw_format_glyph(HWND toolbar, HDC dc, const FormatButton& spec,
SelectObject(dc, old_font);
DeleteObject(font);
if (spec.glyph == FormatGlyph::color) {
HBRUSH swatch = CreateSolidBrush(RGB(255, 255, 0));
HBRUSH swatch = CreateSolidBrush(text_color);
RECT swatch_rect{area.left + 1, area.bottom - 3,
area.right - 1, area.bottom};
FillRect(dc, &swatch_rect, swatch);
@@ -740,7 +758,8 @@ void paint_toolbar(HWND toolbar, ToolbarState& state) {
state.pressed.index == index) || state.format_latched[index];
draw_classic_edge(dc, rect, down);
draw_format_glyph(toolbar, dc, kFormatButtons[index], rect, down,
state.sprite != nullptr ? sprite_dc : nullptr);
state.sprite != nullptr ? sprite_dc : nullptr,
state.text_color);
}
}
if (old_bitmap != nullptr) {
@@ -934,6 +953,116 @@ void restore_document_focus_from_root(HWND root) {
}
}
HBITMAP create_color_menu_swatch(HWND owner, COLORREF color,
bool automatic) {
HDC screen = GetDC(owner);
if (screen == nullptr) {
return nullptr;
}
const int size = scale(owner, 14);
HBITMAP bitmap = CreateCompatibleBitmap(screen, size, size);
HDC memory = CreateCompatibleDC(screen);
HBITMAP previous = bitmap != nullptr ? static_cast<HBITMAP>(
SelectObject(memory, bitmap)) : nullptr;
if (bitmap != nullptr) {
RECT bounds{0, 0, size, size};
FillRect(memory, &bounds, GetSysColorBrush(COLOR_MENU));
RECT swatch{scale(owner, 2), scale(owner, 2),
size - scale(owner, 1), size - scale(owner, 1)};
HBRUSH fill = CreateSolidBrush(color);
FillRect(memory, &swatch, fill);
DeleteObject(fill);
FrameRect(memory, &swatch,
static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
if (automatic) {
HPEN pen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
HPEN old_pen = static_cast<HPEN>(SelectObject(memory, pen));
MoveToEx(memory, swatch.left + 1, swatch.bottom - 2, nullptr);
LineTo(memory, swatch.right - 1, swatch.top + 1);
SelectObject(memory, old_pen);
DeleteObject(pen);
}
SelectObject(memory, previous);
}
DeleteDC(memory);
ReleaseDC(owner, screen);
return bitmap;
}
void show_text_color_palette(HWND app, HWND toolbar) {
ToolbarState* state = toolbar_state(toolbar);
if (state == nullptr) {
return;
}
struct Choice {
int index;
COLORREF color;
const wchar_t* name;
};
const std::array<Choice, 9> choices{{
{0, RGB(0, 0, 0), L"Automatic"},
{1, RGB(0, 0, 0), L"Black"},
{2, RGB(0, 0, 255), L"Blue"},
{3, RGB(0, 255, 255), L"Cyan"},
{4, RGB(0, 128, 0), L"Green"},
{5, RGB(255, 0, 255), L"Magenta"},
{6, RGB(255, 0, 0), L"Red"},
{7, RGB(255, 255, 0), L"Yellow"},
{8, RGB(255, 255, 255), L"White"},
}};
HMENU popup = CreatePopupMenu();
if (popup == nullptr) {
return;
}
std::vector<HBITMAP> swatches;
swatches.reserve(choices.size());
for (const auto& choice : choices) {
const UINT command = kCmdTextColorBase + choice.index;
AppendMenuW(popup, MF_STRING, command, choice.name);
HBITMAP swatch = create_color_menu_swatch(
toolbar, choice.color, choice.index == 0);
swatches.push_back(swatch);
if (swatch != nullptr) {
SetMenuItemBitmaps(popup, command, MF_BYCOMMAND, swatch, swatch);
}
if (choice.index == state->text_color_index) {
CheckMenuItem(popup, command, MF_BYCOMMAND | MF_CHECKED);
}
}
style_menu_tree(popup);
RECT anchor = format_button_rect(toolbar, *state, 3);
POINT point{anchor.left, anchor.bottom};
ClientToScreen(toolbar, &point);
const UINT selected = TrackPopupMenu(
popup, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,
point.x, point.y, 0, app, nullptr);
if (selected >= kCmdTextColorBase &&
selected < kCmdTextColorBase + choices.size()) {
const int index = static_cast<int>(selected - kCmdTextColorBase);
HWND pane = nullptr;
EnumChildWindows(app, find_document_pane,
reinterpret_cast<LPARAM>(&pane));
if (pane != nullptr &&
OpusApplyWin95TextColor(pane, choices[index].index)) {
state->text_color_index = choices[index].index;
state->text_color = choices[index].color;
InvalidateRect(toolbar, nullptr, FALSE);
UpdateWindow(toolbar);
} else {
MessageBeep(MB_ICONEXCLAMATION);
}
}
DestroyMenu(popup);
for (HBITMAP swatch : swatches) {
if (swatch != nullptr) {
DeleteObject(swatch);
}
}
restore_document_focus_from_root(app);
}
void forward_combo(HWND mirror, HWND source, int notification,
bool& edit_dirty) {
if (mirror == nullptr || source == nullptr || !IsWindow(source)) {
@@ -2371,6 +2500,10 @@ LRESULT CALLBACK app_window_proc(HWND app, UINT message,
}
g_word95_page_view_active = true;
}
if (command == bcmColor) {
show_text_color_palette(app, toolbar);
return 0;
}
if (command == kCmdToggleStandardToolbar) {
toggle_toolbar_row(app, toolbar, true);
return 0;
@@ -2385,6 +2518,15 @@ LRESULT CALLBACK app_window_proc(HWND app, UINT message,
if (state != nullptr) {
update_toolbar_menu_checks(app, *state);
}
/* On current Windows versions a popup using a classic background
brush can receive its initial erase before the menu items have
been invalidated. Repaint once the #32768 popup exists so the
complete File menu is visible without requiring mouse hover. */
SetTimer(app, kMenuRepaintTimer, 15, nullptr);
} else if (message == WM_TIMER && w_param == kMenuRepaintTimer) {
KillTimer(app, kMenuRepaintTimer);
EnumThreadWindows(GetCurrentThreadId(), repaint_menu_popup, 0);
return 0;
} else if (message == WM_NCDESTROY) {
g_original_app_proc = nullptr;
if (original != nullptr) {
+14
View File
@@ -59,6 +59,20 @@ BOOL OpusChangeMenu(HMENU menu, UINT item, const void *new_text,
#ifdef OPUS_X64
#define DeleteAtom OpusDeleteAtom
void OpusDeleteAtom(ATOM atom);
/* Win16 OFSTRUCT reserved 120 bytes for its path, while the current SDK's
* OFSTRUCT reserves 128. Passing Word's original structure directly to
* OpenFileA lets the API overwrite eight bytes beyond the caller's buffer.
* Marshal through a native OFSTRUCT so legacy file open/reopen behavior is
* preserved without corrupting the stack. */
#ifdef __cplusplus
extern "C" {
#endif
HFILE OpusOpenFile(LPCSTR file_name, void *legacy_ofs, UINT style);
#ifdef __cplusplus
}
#endif
#define OpenFile OpusOpenFile
#endif
/* Several original translation units define NOGDI to trim the Win16 header,