mirror of
https://github.com/jmarshall23/msword.git
synced 2026-08-12 08:01:05 +02:00
File Save/Open now works correctly.
This commit is contained in:
@@ -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
@@ -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
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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> */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user