| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design) |
| 5 | * Copyright (c) 2017-2022, The LegacyClonk Team and contributors |
| 6 | * |
| 7 | * Distributed under the terms of the ISC license; see accompanying file |
| 8 | * "COPYING" for details. |
| 9 | * |
| 10 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 11 | * See accompanying file "TRADEMARK" for details. |
| 12 | * |
| 13 | * To redistribute this file separately, substitute the full license texts |
| 14 | * for the above references. |
| 15 | */ |
| 16 | |
| 17 | /* Holds a single text file component from a group */ |
| 18 | |
| 19 | #include <C4ComponentHost.h> |
| 20 | #include "C4Config.h" |
| 21 | #include <C4Application.h> |
| 22 | #include <C4Language.h> |
| 23 | |
| 24 | #ifdef _WIN32 |
| 25 | #include "C4Console.h" |
| 26 | #include "StdRegistry.h" |
| 27 | #include "StdStringEncodingConverter.h" |
| 28 | #include "res/engine_resource.h" |
| 29 | #endif |
| 30 | |
| 31 | C4ComponentHost::C4ComponentHost() |
| 32 | { |
| 33 | Default(); |
| 34 | } |
| 35 | |
| 36 | C4ComponentHost::~C4ComponentHost() |
| 37 | { |
| 38 | Clear(); |
| 39 | } |
| 40 | |
| 41 | void C4ComponentHost::Default() |
| 42 | { |
| 43 | Data.Clear(); |
| 44 | Modified = false; |
| 45 | Name[0] = 0; |
| 46 | Filename[0] = 0; |
| 47 | FilePath[0] = 0; |
| 48 | } |
| 49 | |
| 50 | void C4ComponentHost::Clear() |
| 51 | { |
| 52 | Data.Clear(); |
| 53 | } |
| 54 | |
| 55 | bool C4ComponentHost::Load(const char *szName, |
| 56 | C4Group &hGroup, |
| 57 | const char *szFilename, |
| 58 | const char *szLanguage) |
| 59 | { |
| 60 | // Clear any old stuff |
| 61 | Clear(); |
| 62 | // Store name & filename |
| 63 | SCopy(szSource: szName, sTarget: Name); |
| 64 | SCopy(szSource: szFilename, sTarget: Filename); |
| 65 | // Load component - try all segmented filenames |
| 66 | char strEntry[_MAX_FNAME + 1]; |
| 67 | std::string entryWithLanguage; |
| 68 | for (int iFilename = 0; SCopySegment(fstr: Filename, segn: iFilename, tstr: strEntry, sepa: '|', _MAX_FNAME); iFilename++) |
| 69 | { |
| 70 | // Try to insert all language codes provided into the filename |
| 71 | char strCode[3] = "" ; |
| 72 | const char *const strCodePtr{strCode}; |
| 73 | for (int iLang = 0; SCopySegment(fstr: szLanguage ? szLanguage : "" , segn: iLang, tstr: strCode, sepa: ',', iMaxL: 2); iLang++) |
| 74 | { |
| 75 | // Insert language code |
| 76 | entryWithLanguage = std::vformat(fmt: strEntry, args: std::make_format_args(fmt_args: strCodePtr)); |
| 77 | if (hGroup.LoadEntryString(szEntryName: entryWithLanguage.c_str(), Buf&: Data)) |
| 78 | { |
| 79 | if (Config.General.fUTF8) Data.EnsureUnicode(); |
| 80 | // Store actual filename |
| 81 | hGroup.FindEntry(szWildCard: entryWithLanguage.c_str(), sFileName: Filename); |
| 82 | CopyFilePathFromGroup(hGroup); |
| 83 | // Got it |
| 84 | return true; |
| 85 | } |
| 86 | // Couldn't insert language code anyway - no point in trying other languages |
| 87 | if (!SSearch(szString: strEntry, szIndex: "{}" )) break; |
| 88 | } |
| 89 | } |
| 90 | // Truncate any additional segments from stored filename |
| 91 | SReplaceChar(str: Filename, fc: '|', tc: 0); |
| 92 | CopyFilePathFromGroup(hGroup); |
| 93 | // Not loaded |
| 94 | return false; |
| 95 | } |
| 96 | |
| 97 | bool C4ComponentHost::Load(const char *szName, |
| 98 | C4GroupSet &hGroupSet, |
| 99 | const char *szFilename, |
| 100 | const char *szLanguage) |
| 101 | { |
| 102 | // Clear any old stuff |
| 103 | Clear(); |
| 104 | // Store name & filename |
| 105 | SCopy(szSource: szName, sTarget: Name); |
| 106 | SCopy(szSource: szFilename, sTarget: Filename); |
| 107 | // Load component - try all segmented filenames |
| 108 | char strEntry[_MAX_FNAME + 1]; |
| 109 | std::string entryWithLanguage; |
| 110 | for (int iFilename = 0; SCopySegment(fstr: Filename, segn: iFilename, tstr: strEntry, sepa: '|', _MAX_FNAME); iFilename++) |
| 111 | { |
| 112 | // Try to insert all language codes provided into the filename |
| 113 | char strCode[3] = "" ; |
| 114 | const char *const strCodePtr{strCode}; |
| 115 | for (int iLang = 0; SCopySegment(fstr: szLanguage ? szLanguage : "" , segn: iLang, tstr: strCode, sepa: ',', iMaxL: 2); iLang++) |
| 116 | { |
| 117 | // Insert language code |
| 118 | entryWithLanguage = std::vformat(fmt: strEntry, args: std::make_format_args(fmt_args: strCodePtr)); |
| 119 | if (hGroupSet.LoadEntryString(szEntryName: entryWithLanguage.c_str(), rBuf&: Data)) |
| 120 | { |
| 121 | if (Config.General.fUTF8) Data.EnsureUnicode(); |
| 122 | // Store actual filename |
| 123 | C4Group *pGroup = hGroupSet.FindEntry(szWildcard: entryWithLanguage.c_str()); |
| 124 | pGroup->FindEntry(szWildCard: entryWithLanguage.c_str(), sFileName: Filename); |
| 125 | CopyFilePathFromGroup(hGroup: *pGroup); |
| 126 | // Got it |
| 127 | return true; |
| 128 | } |
| 129 | // Couldn't insert language code anyway - no point in trying other languages |
| 130 | if (!SSearch(szString: strEntry, szIndex: "{}" )) break; |
| 131 | } |
| 132 | } |
| 133 | // Truncate any additional segments from stored filename |
| 134 | SReplaceChar(str: Filename, fc: '|', tc: 0); |
| 135 | // skip full path (unknown) |
| 136 | FilePath[0] = 0; |
| 137 | // Not loaded |
| 138 | return false; |
| 139 | } |
| 140 | |
| 141 | bool C4ComponentHost::LoadEx(const char *szName, |
| 142 | C4Group &hGroup, |
| 143 | const char *szFilename, |
| 144 | const char *szLanguage) |
| 145 | { |
| 146 | // Load from a group set containing the provided group and |
| 147 | // alternative groups for cross-loading from a language pack |
| 148 | C4GroupSet hGroups; |
| 149 | hGroups.RegisterGroup(rGroup&: hGroup, fOwnGrp: false, Priority: 1000, C4GSCnt_Component); // Provided group gets highest priority |
| 150 | hGroups.RegisterGroups(rCopy&: Languages.GetPackGroups(strRelativePath: Config.AtExeRelativePath(szFilename: hGroup.GetFullName().getData())), C4GSCnt_Language); |
| 151 | // Load from group set |
| 152 | return Load(szName, hGroupSet&: hGroups, szFilename, szLanguage); |
| 153 | } |
| 154 | |
| 155 | bool C4ComponentHost::LoadAppend(const char *szName, |
| 156 | C4Group &hGroup, const char *szFilename, |
| 157 | const char *szLanguage) |
| 158 | { |
| 159 | Clear(); |
| 160 | |
| 161 | // Store name & filename |
| 162 | SCopy(szSource: szName, sTarget: Name); |
| 163 | SCopy(szSource: szFilename, sTarget: Filename); |
| 164 | |
| 165 | // Load component (segmented filename) |
| 166 | char str1[_MAX_FNAME + 1]; |
| 167 | std::string entry; |
| 168 | size_t iFileCnt = 0, iFileSizeSum = 0; |
| 169 | for (size_t cseg = 0; SCopySegment(fstr: Filename, segn: cseg, tstr: str1, sepa: '|', _MAX_FNAME); cseg++) |
| 170 | { |
| 171 | char szLang[3] = "" ; |
| 172 | const char *const szLangPtr{szLang}; |
| 173 | for (size_t clseg = 0; SCopySegment(fstr: szLanguage ? szLanguage : "" , segn: clseg, tstr: szLang, sepa: ',', iMaxL: 2); clseg++) |
| 174 | { |
| 175 | entry = std::vformat(fmt: str1, args: std::make_format_args(fmt_args: szLangPtr)); |
| 176 | // Check existance |
| 177 | size_t iFileSize; |
| 178 | if (hGroup.FindEntry(szWildCard: entry.c_str(), sFileName: nullptr, iSize: &iFileSize)) |
| 179 | { |
| 180 | iFileCnt++; |
| 181 | iFileSizeSum += 1 + iFileSize; |
| 182 | break; |
| 183 | } |
| 184 | if (!SSearch(szString: str1, szIndex: "{}" )) break; |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | // No matching files found? |
| 189 | if (!iFileCnt) return false; |
| 190 | |
| 191 | // Allocate memory |
| 192 | Data.SetLength(iFileSizeSum); |
| 193 | |
| 194 | // Search files to read contents |
| 195 | char *pPos = Data.getMData(); *pPos = 0; |
| 196 | for (size_t cseg = 0; SCopySegment(fstr: Filename, segn: cseg, tstr: str1, sepa: '|', _MAX_FNAME); cseg++) |
| 197 | { |
| 198 | char szLang[3] = "" ; |
| 199 | const char *const szLangPtr{szLang}; |
| 200 | for (size_t clseg = 0; SCopySegment(fstr: szLanguage ? szLanguage : "" , segn: clseg, tstr: szLang, sepa: ',', iMaxL: 2); clseg++) |
| 201 | { |
| 202 | entry = std::vformat(fmt: str1, args: std::make_format_args(fmt_args: szLangPtr)); |
| 203 | // Load data |
| 204 | char *pTemp; |
| 205 | if (hGroup.LoadEntry(szEntryName: entry.c_str(), lpbpBuf: &pTemp, ipSize: nullptr, iAppendZeros: 1)) |
| 206 | { |
| 207 | *pPos++ = '\n'; |
| 208 | SCopy(szSource: pTemp, sTarget: pPos, iMaxL: Data.getPtr(i: Data.getLength()) - pPos); |
| 209 | pPos += SLen(sptr: pPos); |
| 210 | delete[] pTemp; |
| 211 | break; |
| 212 | } |
| 213 | delete[] pTemp; |
| 214 | if (!SSearch(szString: str1, szIndex: "{}" )) break; |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | SReplaceChar(str: Filename, fc: '|', tc: 0); |
| 219 | CopyFilePathFromGroup(hGroup); |
| 220 | return !!iFileCnt; |
| 221 | } |
| 222 | |
| 223 | // Construct full path |
| 224 | void C4ComponentHost::CopyFilePathFromGroup(const C4Group &hGroup) |
| 225 | { |
| 226 | SCopy(szSource: Config.AtExeRelativePath(szFilename: hGroup.GetFullName().getData()), sTarget: FilePath, _MAX_PATH - 1); |
| 227 | SAppendChar(DirectorySeparator, szStr: FilePath); |
| 228 | SAppend(szSource: Filename, szTarget: FilePath, _MAX_PATH); |
| 229 | } |
| 230 | |
| 231 | bool C4ComponentHost::Save(C4Group &hGroup) |
| 232 | { |
| 233 | if (!Modified) return true; |
| 234 | if (!Data) return hGroup.Delete(szFiles: Filename); |
| 235 | return hGroup.Add(szName: Filename, pBuffer&: Data); |
| 236 | } |
| 237 | |
| 238 | bool C4ComponentHost::GetLanguageString(const char *szLanguage, StdStrBuf &rTarget) |
| 239 | { |
| 240 | const char *cptr; |
| 241 | // No good parameters |
| 242 | if (!szLanguage || !Data) return false; |
| 243 | // Search for two-letter language identifier in text body, i.e. "DE:" |
| 244 | char langindex[4] = "" ; |
| 245 | for (int clseg = 0; SCopySegment(fstr: szLanguage ? szLanguage : "" , segn: clseg, tstr: langindex, sepa: ',', iMaxL: 2); clseg++) |
| 246 | { |
| 247 | SAppend(szSource: ":" , szTarget: langindex); |
| 248 | if (cptr = SSearch(szString: Data.getData(), szIndex: langindex)) |
| 249 | { |
| 250 | // Return the according string |
| 251 | auto iEndPos = SCharPos(cTarget: '\r', szInStr: cptr); |
| 252 | if (iEndPos < 0) iEndPos = SCharPos(cTarget: '\n', szInStr: cptr); |
| 253 | if (iEndPos < 0) iEndPos = strlen(s: cptr); |
| 254 | rTarget.Copy(pnData: cptr, iChars: iEndPos); |
| 255 | return true; |
| 256 | } |
| 257 | } |
| 258 | // Language string not found |
| 259 | return false; |
| 260 | } |
| 261 | |
| 262 | void C4ComponentHost::Close() |
| 263 | { |
| 264 | } |
| 265 | |
| 266 | void C4ComponentHost::TrimSpaces() |
| 267 | { |
| 268 | Data.TrimSpaces(); |
| 269 | Modified = true; |
| 270 | } |
| 271 | |
| 272 | #ifdef _WIN32 |
| 273 | INT_PTR CALLBACK C4ComponentHost::ComponentDlgProc(const HWND hDlg, const UINT msg, const WPARAM wParam, const LPARAM lParam) |
| 274 | { |
| 275 | const auto close = [hDlg](C4ComponentHost &host) |
| 276 | { |
| 277 | host.Close(); |
| 278 | EndDialog(hDlg, 1); |
| 279 | }; |
| 280 | |
| 281 | switch (msg) |
| 282 | { |
| 283 | case WM_INITDIALOG: |
| 284 | { |
| 285 | const auto &componentHost = *reinterpret_cast<C4ComponentHost *>(lParam); |
| 286 | |
| 287 | SetWindowLongPtr(hDlg, DWLP_USER, lParam); |
| 288 | SetWindowText(hDlg, StdStringEncodingConverter::WinAcpToUtf16(componentHost.Name).c_str()); |
| 289 | SetDlgItemText(hDlg, IDOK, StdStringEncodingConverter::WinAcpToUtf16(LoadResStr(C4ResStrTableKey::IDS_BTN_OK)).c_str()); |
| 290 | SetDlgItemText(hDlg, IDCANCEL, StdStringEncodingConverter::WinAcpToUtf16(LoadResStr(C4ResStrTableKey::IDS_BTN_CANCEL)).c_str()); |
| 291 | |
| 292 | if (const char *const data{componentHost.GetData()}; data) |
| 293 | { |
| 294 | SetDlgItemText(hDlg, IDC_EDITDATA, StdStringEncodingConverter::WinAcpToUtf16(data).c_str()); |
| 295 | } |
| 296 | |
| 297 | RestoreWindowPosition(hDlg, "Component" , Config.GetSubkeyPath("Console" )); |
| 298 | |
| 299 | return TRUE; |
| 300 | } |
| 301 | |
| 302 | case WM_CLOSE: |
| 303 | close(*reinterpret_cast<C4ComponentHost *>(GetWindowLongPtr(hDlg, DWLP_USER))); |
| 304 | return 0; |
| 305 | |
| 306 | case WM_DESTROY: |
| 307 | StoreWindowPosition(hDlg, "Component" , Config.GetSubkeyPath("Console" ), false); |
| 308 | return 0; |
| 309 | |
| 310 | case WM_COMMAND: |
| 311 | { |
| 312 | auto &componentHost = *reinterpret_cast<C4ComponentHost *>(GetWindowLongPtr(hDlg, DWLP_USER)); |
| 313 | switch (LOWORD(wParam)) |
| 314 | { |
| 315 | case IDCANCEL: |
| 316 | close(componentHost); |
| 317 | return TRUE; |
| 318 | |
| 319 | case IDOK: |
| 320 | { |
| 321 | StdStrBuf &data{componentHost.Data}; |
| 322 | const auto size = static_cast<std::size_t>(SendDlgItemMessage(hDlg, IDC_EDITDATA, WM_GETTEXTLENGTH, 0, 0)); |
| 323 | if (size == 0) |
| 324 | { |
| 325 | data.Clear(); |
| 326 | } |
| 327 | else |
| 328 | { |
| 329 | const std::string text{StdStringEncodingConverter::Utf16ToWinAcp(C4Console::GetDialogItemText(hDlg, IDC_EDITDATA))}; |
| 330 | componentHost.Data.Copy(text.c_str(), text.size()); |
| 331 | } |
| 332 | |
| 333 | componentHost.Modified = true; |
| 334 | close(componentHost); |
| 335 | |
| 336 | return TRUE; |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | return FALSE; |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | return FALSE; |
| 345 | } |
| 346 | |
| 347 | void C4ComponentHost::ShowDialog(const HWND parent) |
| 348 | { |
| 349 | DialogBoxParam(Application.hInstance, MAKEINTRESOURCE(IDD_COMPONENT), parent, &ComponentDlgProc, reinterpret_cast<LPARAM>(this)); |
| 350 | } |
| 351 | #endif |
| 352 | |