Saved config, save state slots, MRU list

This commit is contained in:
Souryo 2014-06-29 16:51:58 -04:00
parent c40e207301
commit b80b0a0d02
15 changed files with 5266 additions and 36 deletions

View file

@ -140,9 +140,16 @@ void Console::SaveState()
_saveStateFilename.clear();
}
void Console::LoadState(wstring filename)
bool Console::LoadState(wstring filename)
{
_loadStateFilename = filename;
ifstream file(filename, ios::out | ios::binary);
if(file) {
file.close();
_loadStateFilename = filename;
return true;
}
return false;
}
void Console::LoadState()

View file

@ -50,7 +50,7 @@ class Console
void SaveTestResult();
void SaveState(wstring filename);
void LoadState(wstring filename);
bool LoadState(wstring filename);
static bool CheckFlag(int flag);
static void SetFlags(int flags);

Binary file not shown.

View file

@ -147,6 +147,9 @@
<ProjectReference Include="..\Core\Core.vcxproj">
<Project>{78fef1a1-6df1-4cbb-a373-ae6fa7ce5ce0}</Project>
</ProjectReference>
<ProjectReference Include="..\Utilities\Utilities.vcxproj">
<Project>{b5330148-e8c7-46ba-b54e-69be59ea337d}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="Calibri.30.spritefont">
@ -157,7 +160,7 @@
<ItemGroup>
<Content Include="Calibri.30.spritefont">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</Content>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View file

@ -2,7 +2,7 @@
#include "Resource.h"
#include "MainWindow.h"
#include "../Core/Console.h"
#include "../Utilities/Timer.h"
#include "../Utilities/ConfigManager.h"
#include "InputManager.h"
using namespace DirectX;
@ -144,35 +144,201 @@ namespace NES {
return (INT_PTR)FALSE;
}
void MainWindow::InitializeOptions()
INT_PTR CALLBACK MainWindow::ControllerSetup(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
Console::SetFlags(EmulationFlags::LimitFPS);
UNREFERENCED_PARAMETER(lParam);
static HWND _focusedControl;
switch(message) {
case WM_INITDIALOG:
HWND cboPort1;
cboPort1 = GetDlgItem(hDlg, IDC_CBOPORT1);
SendMessage(cboPort1, CB_ADDSTRING, 0, (LPARAM)_T("<none>"));
SendMessage(cboPort1, CB_ADDSTRING, 0, (LPARAM)_T("Gamepad"));
SendMessage(cboPort1, CB_SETCURSEL, 0, 0);
return (INT_PTR)TRUE;
case WM_SETFOCUS:
case WM_SETCURSOR:
_focusedControl = GetFocus();
break;
case WM_KEYDOWN:
break;
case WM_COMMAND:
if(LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
if(LOWORD(wParam) == IDOK) {
//Save settings
ConfigManager::SetValue(Config::Player1_ButtonA, "V");
}
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
wstring MainWindow::SelectROM()
INT_PTR CALLBACK MainWindow::InputConfig(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
wchar_t buffer[2000];
UNREFERENCED_PARAMETER(lParam);
switch(message) {
case WM_INITDIALOG:
HWND cboPort1;
cboPort1 = GetDlgItem(hDlg, IDC_CBOPORT1);
SendMessage(cboPort1, CB_ADDSTRING, 0, (LPARAM)_T("<none>"));
SendMessage(cboPort1, CB_ADDSTRING, 0, (LPARAM)_T("Gamepad"));
SendMessage(cboPort1, CB_SETCURSEL, 0, 0);
return (INT_PTR)TRUE;
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = nullptr;
ofn.lpstrFile = buffer;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(buffer);
ofn.lpstrFilter = L"NES Roms\0*.NES\0All\0*.*";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = nullptr;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = nullptr;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
case WM_COMMAND:
if(LOWORD(wParam) == IDC_BTNPORT1KEYS) {
DialogBox(nullptr, MAKEINTRESOURCE(IDD_CONTROLLERSETUP), hDlg, ControllerSetup);
} else if(LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
if(LOWORD(wParam) == IDOK) {
//Save settings
ConfigManager::SetValue(Config::Player1_ButtonA, "V");
}
GetOpenFileName(&ofn);
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
wstring filepath(buffer);
void MainWindow::InitializeOptions()
{
if(ConfigManager::GetValue<bool>(Config::LimitFPS)) {
Console::SetFlags(EmulationFlags::LimitFPS);
SetMenuCheck(ID_OPTIONS_LIMITFPS, true);
}
if(ConfigManager::GetValue<bool>(Config::ShowFPS)) {
_renderer->SetFlags(UIFlags::ShowFPS);
SetMenuCheck(ID_OPTIONS_SHOWFPS, true);
}
wstring filename = filepath.substr(filepath.find_last_of(L"/\\") + 1);
SetWindowText(_hWnd, (wstring(_windowName) + L": " + filename).c_str());
UpdateMRUMenu();
}
void MainWindow::AddToMRU(wstring romFilename)
{
wstring MRU0 = ConfigManager::GetValue<wstring>(Config::MRU0);
wstring MRU1 = ConfigManager::GetValue<wstring>(Config::MRU1);
wstring MRU2 = ConfigManager::GetValue<wstring>(Config::MRU2);
wstring MRU3 = ConfigManager::GetValue<wstring>(Config::MRU3);
wstring MRU4 = ConfigManager::GetValue<wstring>(Config::MRU4);
if(MRU0.compare(romFilename) == 0) {
return;
} else if(MRU1.compare(romFilename) == 0) {
MRU1 = MRU0;
MRU0 = romFilename;
} else if(MRU2.compare(romFilename) == 0) {
MRU2 = MRU1;
MRU1 = MRU0;
MRU0 = romFilename;
} else if(MRU3.compare(romFilename) == 0) {
MRU3 = MRU2;
MRU2 = MRU1;
MRU1 = MRU0;
MRU0 = romFilename;
} else {
MRU4 = MRU3;
MRU3 = MRU2;
MRU2 = MRU1;
MRU1 = MRU0;
MRU0 = romFilename;
}
ConfigManager::SetValue(Config::MRU0, MRU0);
ConfigManager::SetValue(Config::MRU1, MRU1);
ConfigManager::SetValue(Config::MRU2, MRU2);
ConfigManager::SetValue(Config::MRU3, MRU3);
ConfigManager::SetValue(Config::MRU4, MRU4);
UpdateMRUMenu();
}
void MainWindow::UpdateMRUMenu()
{
wstring MRU0 = ConfigManager::GetValue<wstring>(Config::MRU0);
wstring MRU1 = ConfigManager::GetValue<wstring>(Config::MRU1);
wstring MRU2 = ConfigManager::GetValue<wstring>(Config::MRU2);
wstring MRU3 = ConfigManager::GetValue<wstring>(Config::MRU3);
wstring MRU4 = ConfigManager::GetValue<wstring>(Config::MRU4);
HMENU hMenu = GetMenu(_hWnd);
MENUITEMINFOW info;
//Initialize MENUITEMINFO structure:
memset(&info, 0, sizeof(info));
info.cbSize = sizeof(info);
info.fMask = MIIM_TYPE;
info.fType = MFT_STRING;
info.cch = 256;
if(!MRU0.empty()) {
info.dwTypeData = (LPWSTR)MRU0.c_str();
SetMenuItemInfo(hMenu, ID_RECENTFILES_MRU1, false, &info);
}
if(!MRU1.empty()) {
info.dwTypeData = (LPWSTR)MRU1.c_str();
SetMenuItemInfo(hMenu, ID_RECENTFILES_MRU2, false, &info);
}
if(!MRU2.empty()) {
info.dwTypeData = (LPWSTR)MRU2.c_str();
SetMenuItemInfo(hMenu, ID_RECENTFILES_MRU3, false, &info);
}
if(!MRU3.empty()) {
info.dwTypeData = (LPWSTR)MRU3.c_str();
SetMenuItemInfo(hMenu, ID_RECENTFILES_MRU4, false, &info);
}
if(!MRU4.empty()) {
info.dwTypeData = (LPWSTR)MRU4.c_str();
SetMenuItemInfo(hMenu, ID_RECENTFILES_MRU5, false, &info);
}
}
wstring MainWindow::SelectROM(wstring filepath)
{
if(filepath.empty()) {
wchar_t buffer[2000];
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = nullptr;
ofn.lpstrFile = buffer;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(buffer);
ofn.lpstrFilter = L"NES Roms\0*.NES\0All\0*.*";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = nullptr;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = nullptr;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
GetOpenFileName(&ofn);
filepath = wstring(buffer);
}
if(!filepath.empty()) {
wstring filename = filepath.substr(filepath.find_last_of(L"/\\") + 1);
SetWindowText(_hWnd, (wstring(_windowName) + L": " + filename).c_str());
AddToMRU(filepath);
Start(filename);
}
return filepath;
}
@ -200,8 +366,7 @@ namespace NES {
SetMenuEnabled(ID_NES_STOP, true);
SetMenuEnabled(ID_NES_RESUME, false);
SetMenuEnabled(ID_FILE_QUICKLOAD, true);
SetMenuEnabled(ID_FILE_QUICKLOAD, true);
SetMenuEnabled(ID_FILE_QUICKSAVE, true);
_renderer->ClearFlags(UIFlags::ShowPauseScreen);
@ -275,8 +440,10 @@ namespace NES {
{
if(ToggleMenuCheck(ID_OPTIONS_LIMITFPS)) {
Console::SetFlags(EmulationFlags::LimitFPS);
ConfigManager::SetValue(Config::LimitFPS, true);
} else {
Console::ClearFlags(EmulationFlags::LimitFPS);
ConfigManager::SetValue(Config::LimitFPS, false);
}
}
@ -284,8 +451,10 @@ namespace NES {
{
if(ToggleMenuCheck(ID_OPTIONS_SHOWFPS)) {
_renderer->SetFlags(UIFlags::ShowFPS);
ConfigManager::SetValue(Config::ShowFPS, true);
} else {
_renderer->ClearFlags(UIFlags::ShowFPS);
ConfigManager::SetValue(Config::ShowFPS, false);
}
}
@ -388,6 +557,18 @@ namespace NES {
std::cout << "------------------------" << std::endl;
}
void MainWindow::SelectSaveSlot(int slot)
{
_currentSaveSlot = slot % 5;
_renderer->DisplayMessage(L"Savestate slot: " + std::to_wstring(_currentSaveSlot + 1), 3000);
SetMenuCheck(ID_SAVESTATESLOT_1, _currentSaveSlot == 0);
SetMenuCheck(ID_SAVESTATESLOT_2, _currentSaveSlot == 1);
SetMenuCheck(ID_SAVESTATESLOT_3, _currentSaveSlot == 2);
SetMenuCheck(ID_SAVESTATESLOT_4, _currentSaveSlot == 3);
SetMenuCheck(ID_SAVESTATESLOT_5, _currentSaveSlot == 4);
}
LRESULT CALLBACK MainWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static MainWindow *mainWindow = MainWindow::GetInstance();
@ -404,18 +585,54 @@ namespace NES {
switch (wmId) {
case ID_FILE_OPEN:
filename = mainWindow->SelectROM();
if(filename.length() > 0) {
mainWindow->Start(filename);
break;
case ID_SAVESTATESLOT_1:
mainWindow->SelectSaveSlot(0);
break;
case ID_SAVESTATESLOT_2:
mainWindow->SelectSaveSlot(1);
break;
case ID_SAVESTATESLOT_3:
mainWindow->SelectSaveSlot(2);
break;
case ID_SAVESTATESLOT_4:
mainWindow->SelectSaveSlot(3);
break;
case ID_SAVESTATESLOT_5:
mainWindow->SelectSaveSlot(4);
break;
case ID_RECENTFILES_MRU1:
mainWindow->SelectROM(ConfigManager::GetValue<wstring>(Config::MRU0));
break;
case ID_RECENTFILES_MRU2:
mainWindow->SelectROM(ConfigManager::GetValue<wstring>(Config::MRU1));
break;
case ID_RECENTFILES_MRU3:
mainWindow->SelectROM(ConfigManager::GetValue<wstring>(Config::MRU2));
break;
case ID_RECENTFILES_MRU4:
mainWindow->SelectROM(ConfigManager::GetValue<wstring>(Config::MRU3));
break;
case ID_RECENTFILES_MRU5:
mainWindow->SelectROM(ConfigManager::GetValue<wstring>(Config::MRU4));
break;
case ID_FILE_QUICKLOAD:
if(mainWindow->_console->LoadState(mainWindow->_currentROM + L".ss" + std::to_wstring(mainWindow->_currentSaveSlot + 1))) {
mainWindow->_renderer->DisplayMessage(L"State loaded.", 3000);
} else {
mainWindow->_renderer->DisplayMessage(L"Slot is empty.", 3000);
}
break;
case ID_FILE_QUICKLOAD:
mainWindow->_console->LoadState(mainWindow->_currentROM + L".svs");
mainWindow->_renderer->DisplayMessage(L"State loaded.", 3000);
break;
case ID_FILE_QUICKSAVE:
mainWindow->_console->SaveState(mainWindow->_currentROM + L".svs");
mainWindow->_console->SaveState(mainWindow->_currentROM + L".ss" + std::to_wstring(mainWindow->_currentSaveSlot + 1));
mainWindow->_renderer->DisplayMessage(L"State saved.", 3000);
break;
case ID_CHANGESLOT:
mainWindow->SelectSaveSlot(mainWindow->_currentSaveSlot + 1);
break;
case ID_FILE_EXIT:
DestroyWindow(hWnd);
break;
@ -439,6 +656,9 @@ namespace NES {
case ID_OPTIONS_SHOWFPS:
mainWindow->ShowFPS_Click();
break;
case ID_OPTIONS_INPUT:
DialogBox(nullptr, MAKEINTRESOURCE(IDD_INPUTCONFIG), hWnd, InputConfig);
break;
case ID_TESTS_RUNTESTS:
mainWindow->RunTests();

View file

@ -2,6 +2,7 @@
#include "Renderer.h"
#include "SoundManager.h"
#include "../Core/Console.h"
#include "../Utilities/ConfigManager.h"
namespace NES {
class MainWindow
@ -20,11 +21,17 @@ namespace NES {
unique_ptr<thread> _emuThread;
wstring _currentROM;
int _currentSaveSlot = 0;
private:
bool Initialize();
HRESULT InitWindow();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK InputConfig(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK ControllerSetup(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
static wstring MainWindow::VKToString(int vk);
static MainWindow* GetInstance() { return MainWindow::Instance; }
@ -43,13 +50,17 @@ namespace NES {
bool SetMenuCheck(int resourceID, bool checked);
bool ToggleMenuCheck(int resourceID);
wstring SelectROM();
wstring SelectROM(wstring filepath = L"");
void Start(wstring romFilename);
void Reset();
void Stop(bool powerOff);
void InitializeOptions();
void SelectSaveSlot(int slot);
void AddToMRU(wstring romFilename);
void UpdateMRUMenu();
public:
MainWindow(HINSTANCE hInstance, int nCmdShow) : _hInstance(hInstance), _nCmdShow(nCmdShow)
{

Binary file not shown.

View file

@ -7,6 +7,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Core", "Core\Core.vcxproj",
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GUI", "GUI\GUI.vcxproj", "{6675D943-322A-4045-B16C-4756FD32CAF2}"
ProjectSection(ProjectDependencies) = postProject
{B5330148-E8C7-46BA-B54E-69BE59EA337D} = {B5330148-E8C7-46BA-B54E-69BE59EA337D}
{78FEF1A1-6DF1-4CBB-A373-AE6FA7CE5CE0} = {78FEF1A1-6DF1-4CBB-A373-AE6FA7CE5CE0}
EndProjectSection
EndProject

View file

@ -0,0 +1,78 @@
#include "stdafx.h"
#include "ConfigManager.h"
#include <Shlobj.h>
string ConfigManager::ConfigTagNames[] = {
"ShowFPS",
"LimitFPS",
"MRU0",
"MRU1",
"MRU2",
"MRU3",
"MRU4",
"Player1_ButtonA",
"Player1_ButtonB",
"Player1_Select",
"Player1_Start",
"Player1_Up",
"Player1_Down",
"Player1_Left",
"Player1_Right",
};
shared_ptr<ConfigManager> ConfigManager::Instance = nullptr;
void ConfigManager::LoadConfigFile()
{
PWSTR pathName;
SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &pathName);
_configFilePath = wstring((wchar_t*)pathName) + L"\\NesEMU\\Settings.xml";
CreateDirectory((wstring((wchar_t*)pathName) + L"\\NesEMU").c_str(), nullptr);
CoTaskMemFree(pathName);
ifstream configFile(_configFilePath, ifstream::in);
if(configFile) {
configFile.close();
_xmlDocument.LoadFile(utf8util::UTF8FromUTF16(_configFilePath).c_str());
} else {
_xmlDocument.InsertFirstChild(_xmlDocument.NewDeclaration());
tinyxml2::XMLElement* mainNode = _xmlDocument.NewElement("Config");
_xmlDocument.InsertEndChild(mainNode);
}
}
tinyxml2::XMLElement* ConfigManager::GetConfigNode()
{
return _xmlDocument.LastChildElement();
}
tinyxml2::XMLElement* ConfigManager::GetNode(Config config)
{
if(!_loaded) {
LoadConfigFile();
_loaded = true;
}
tinyxml2::XMLElement* currentNode = GetConfigNode()->FirstChildElement();
tinyxml2::XMLElement* configNode = nullptr;
if(currentNode) {
do {
if(ConfigTagNames[(int)config].compare(currentNode->Name()) == 0) {
configNode = currentNode;
break;
}
}
while(currentNode = currentNode->NextSiblingElement());
}
if(!configNode) {
configNode = _xmlDocument.NewElement(ConfigTagNames[(int)config].c_str());
GetConfigNode()->InsertEndChild(configNode);
}
return configNode;
}

106
Utilities/ConfigManager.h Normal file
View file

@ -0,0 +1,106 @@
#pragma once
#include "stdafx.h"
#include "../Utilities/tinyxml2.h"
#include "utf8conv.h"
enum class Config
{
ShowFPS = 0,
LimitFPS,
MRU0,
MRU1,
MRU2,
MRU3,
MRU4,
Player1_ButtonA,
Player1_ButtonB,
Player1_Select,
Player1_Start,
Player1_Up,
Player1_Down,
Player1_Left,
Player1_Right,
};
class ConfigManager
{
private:
static shared_ptr<ConfigManager> Instance;
static string ConfigTagNames[256];
bool _loaded;
wstring _configFilePath;
tinyxml2::XMLDocument _xmlDocument;
ConfigManager() { }
void LoadConfigFile();
tinyxml2::XMLElement* GetNode(Config config);
tinyxml2::XMLElement* GetConfigNode();
template<typename T>
T GetValueAttribute(tinyxml2::XMLElement* element);
template<>
int GetValueAttribute<int>(tinyxml2::XMLElement* element)
{
return element->IntAttribute("value");
}
template<>
bool GetValueAttribute<bool>(tinyxml2::XMLElement* element)
{
return element->BoolAttribute("value");
}
template<>
double GetValueAttribute<double>(tinyxml2::XMLElement* element)
{
return element->DoubleAttribute("value");
}
template<>
wstring GetValueAttribute<wstring>(tinyxml2::XMLElement* element)
{
if(element->Attribute("value")) {
return utf8util::UTF16FromUTF8(string(element->Attribute("value")));
} else {
return L"";
}
}
template<typename T>
void SetValueAttribute(tinyxml2::XMLElement* element, T value)
{
element->SetAttribute("value", value);
}
template<>
void SetValueAttribute<std::wstring>(tinyxml2::XMLElement* element, std::wstring value)
{
element->SetAttribute("value", utf8util::UTF8FromUTF16(value).c_str());
}
public:
static shared_ptr<ConfigManager> GetInstance()
{
if(ConfigManager::Instance == nullptr) {
ConfigManager::Instance.reset(new ConfigManager());
}
return ConfigManager::Instance;
}
template<typename T>
static T GetValue(Config config)
{
return GetInstance()->GetValueAttribute<T>(GetInstance()->GetNode(config));
}
template<typename T>
static void SetValue(Config config, T value)
{
GetInstance()->SetValueAttribute(GetInstance()->GetNode(config), value);
GetInstance()->_xmlDocument.SaveFile(utf8util::UTF8FromUTF16(GetInstance()->_configFilePath).c_str(), false);
}
};

View file

@ -5,3 +5,13 @@
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <string>
#include <iostream>
#include <fstream>
#include <memory>
using std::shared_ptr;
using std::ifstream;
using std::string;
using std::wstring;

2188
Utilities/tinyxml2.cpp Normal file

File diff suppressed because it is too large Load diff

2076
Utilities/tinyxml2.h Normal file

File diff suppressed because it is too large Load diff

162
Utilities/utf8conv.h Normal file
View file

@ -0,0 +1,162 @@
//////////////////////////////////////////////////////////////////////////
//
// FILE: utf8conv.h
//
// Header file defining prototypes of helper functions for converting
// strings between Unicode UTF-8 and UTF-16.
// (The implementation file is "utf8conv_inl.h").
//
// UTF-8 text is stored in std::string;
// UTF-16 text is stored in std::wstring.
//
// This code just uses Win32 Platform SDK and C++ standard library;
// so it can be used also with the Express editions of Visual Studio.
//
//
// Original code: February 4th, 2011
// Last update: October 15th, 2011
//
// - Added more information to the utf8_conversion_error class
// (like the return code of ::GetLastError());
// moreover, the class now derives from std::runtime_error.
//
// - Added conversion function overloads taking raw C strings as input.
// (This is more efficient when there are raw C strings already
// available, because it avoids the creation of temporary
// new std::[w]string's.)
//
// - UTF-8 conversion functions now detect invalid UTF-8 sequences
// thanks to MB_ERR_INVALID_CHARS flag, and throw an exception
// in this case.
//
//
// by Giovanni Dicanio <gdicanio@mvps.org>
//
//////////////////////////////////////////////////////////////////////////
#pragma once
//------------------------------------------------------------------------
// INCLUDES
//------------------------------------------------------------------------
#include <stdexcept> // std::runtime_error
#include <string> // STL string classes
namespace utf8util {
//------------------------------------------------------------------------
// Exception class representing an error occurred during UTF-8 conversion.
//------------------------------------------------------------------------
class utf8_conversion_error
: public std::runtime_error
{
public:
//
// Naming convention note:
// -----------------------
//
// This exception class is derived from std::runtime_error class,
// so I chose to use the same naming convention of STL classes
// (e.g. do_something_intersting() instead of DoSomethingInteresting()).
//
// Error code type
// (a DWORD, as the return value type from ::GetLastError())
typedef unsigned long error_code_type;
// Type of conversion
enum conversion_type
{
conversion_utf8_from_utf16, // UTF-16 ---> UTF-8
conversion_utf16_from_utf8 // UTF-8 ---> UTF-16
};
// Constructs an UTF-8 conversion error exception
// with a raw C string message, conversion type and error code.
utf8_conversion_error(
const char * message,
conversion_type conversion,
error_code_type error_code
);
// Constructs an UTF-8 conversion error exception
// with a std::string message, conversion type and error code.
utf8_conversion_error(
const std::string & message,
conversion_type conversion,
error_code_type error_code
);
// Returns the type of conversion (UTF-8 from UTF-16, or vice versa)
conversion_type conversion() const;
// Returns the error code occurred during the conversion
// (which is typically the return value of ::GetLastError()).
error_code_type error_code() const;
//
// IMPLEMENTATION
//
private:
conversion_type m_conversion; // kind of conversion
error_code_type m_error_code; // error code
};
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// Converts a string from UTF-8 to UTF-16.
// On error, can throw an utf8_conversion_error exception.
//------------------------------------------------------------------------
std::wstring UTF16FromUTF8(const std::string & utf8);
//------------------------------------------------------------------------
// Converts a raw C string from UTF-8 to UTF-16.
// On error, can throw an utf8_conversion_error exception.
// If the input pointer is NULL, an empty string is returned.
//------------------------------------------------------------------------
std::wstring UTF16FromUTF8(const char * utf8);
//------------------------------------------------------------------------
// Converts a string from UTF-16 to UTF-8.
// On error, can throw an utf8_conversion_error exception.
//------------------------------------------------------------------------
std::string UTF8FromUTF16(const std::wstring & utf16);
//------------------------------------------------------------------------
// Converts a raw C string from UTF-16 to UTF-8.
// On error, can throw an utf8_conversion_error exception.
// If the input pointer is NULL, an empty string is returned.
//------------------------------------------------------------------------
std::string UTF8FromUTF16(const wchar_t * utf16);
} // namespace utf8util
#include "utf8conv_inl.h" // inline implementations
//////////////////////////////////////////////////////////////////////////

368
Utilities/utf8conv_inl.h Normal file
View file

@ -0,0 +1,368 @@
//////////////////////////////////////////////////////////////////////////
//
// FILE: utf8conv_inl.h
//
// by Giovanni Dicanio <gdicanio@mvps.org>
//
// Private header file containing implementations of inline functions.
// The public header file for this module is "utf8conv.h";
// users should *not* #include this private header file directly.
//
//////////////////////////////////////////////////////////////////////////
#pragma once
#include <string.h> // strlen()
#include <Windows.h> // Win32 Platform SDK main header
namespace utf8util {
//------------------------------------------------------------------------
// Implementation of utf8_conversion_error class methods
//------------------------------------------------------------------------
inline utf8_conversion_error::utf8_conversion_error(
const char * message,
conversion_type conversion,
error_code_type error_code
) :
std::runtime_error(message),
m_conversion(conversion),
m_error_code(error_code)
{
}
inline utf8_conversion_error::utf8_conversion_error(
const std::string & message,
conversion_type conversion,
error_code_type error_code
) :
std::runtime_error(message),
m_conversion(conversion),
m_error_code(error_code)
{
}
inline utf8_conversion_error::conversion_type utf8_conversion_error::conversion() const
{
return m_conversion;
}
inline utf8_conversion_error::error_code_type utf8_conversion_error::error_code() const
{
return m_error_code;
}
//------------------------------------------------------------------------
// Implementation of module functions
//------------------------------------------------------------------------
inline std::wstring UTF16FromUTF8(const std::string & utf8)
{
//
// Special case of empty input string
//
if (utf8.empty())
return std::wstring();
// Fail if an invalid input character is encountered
const DWORD conversionFlags = MB_ERR_INVALID_CHARS;
//
// Get length (in wchar_t's) of resulting UTF-16 string
//
const int utf16Length = ::MultiByteToWideChar(
CP_UTF8, // convert from UTF-8
conversionFlags, // flags
utf8.data(), // source UTF-8 string
utf8.length(), // length (in chars) of source UTF-8 string
NULL, // unused - no conversion done in this step
0 // request size of destination buffer, in wchar_t's
);
if (utf16Length == 0)
{
// Error
DWORD error = ::GetLastError();
throw utf8_conversion_error(
(error == ERROR_NO_UNICODE_TRANSLATION) ?
"Invalid UTF-8 sequence found in input string." :
"Can't get length of UTF-16 string (MultiByteToWideChar failed).",
utf8_conversion_error::conversion_utf16_from_utf8,
error);
}
//
// Allocate destination buffer for UTF-16 string
//
std::wstring utf16;
utf16.resize(utf16Length);
//
// Do the conversion from UTF-8 to UTF-16
//
if ( ! ::MultiByteToWideChar(
CP_UTF8, // convert from UTF-8
0, // validation was done in previous call,
// so speed up things with default flags
utf8.data(), // source UTF-8 string
utf8.length(), // length (in chars) of source UTF-8 string
&utf16[0], // destination buffer
utf16.length() // size of destination buffer, in wchar_t's
) )
{
// Error
DWORD error = ::GetLastError();
throw utf8_conversion_error(
"Can't convert string from UTF-8 to UTF-16 (MultiByteToWideChar failed).",
utf8_conversion_error::conversion_utf16_from_utf8,
error);
}
//
// Return resulting UTF-16 string
//
return utf16;
}
inline std::wstring UTF16FromUTF8(const char * utf8)
{
//
// Special case of empty input string
//
if (utf8 == NULL || *utf8 == '\0')
return std::wstring();
// Prefetch the length of the input UTF-8 string
const int utf8Length = static_cast<int>(strlen(utf8));
// Fail if an invalid input character is encountered
const DWORD conversionFlags = MB_ERR_INVALID_CHARS;
//
// Get length (in wchar_t's) of resulting UTF-16 string
//
const int utf16Length = ::MultiByteToWideChar(
CP_UTF8, // convert from UTF-8
conversionFlags, // flags
utf8, // source UTF-8 string
utf8Length, // length (in chars) of source UTF-8 string
NULL, // unused - no conversion done in this step
0 // request size of destination buffer, in wchar_t's
);
if (utf16Length == 0)
{
// Error
DWORD error = ::GetLastError();
throw utf8_conversion_error(
(error == ERROR_NO_UNICODE_TRANSLATION) ?
"Invalid UTF-8 sequence found in input string." :
"Can't get length of UTF-16 string (MultiByteToWideChar failed).",
utf8_conversion_error::conversion_utf16_from_utf8,
error);
}
//
// Allocate destination buffer for UTF-16 string
//
std::wstring utf16;
utf16.resize(utf16Length);
//
// Do the conversion from UTF-8 to UTF-16
//
if ( ! ::MultiByteToWideChar(
CP_UTF8, // convert from UTF-8
0, // validation was done in previous call,
// so speed up things with default flags
utf8, // source UTF-8 string
utf8Length, // length (in chars) of source UTF-8 string
&utf16[0], // destination buffer
utf16.length() // size of destination buffer, in wchar_t's
) )
{
// Error
DWORD error = ::GetLastError();
throw utf8_conversion_error(
"Can't convert string from UTF-8 to UTF-16 (MultiByteToWideChar failed).",
utf8_conversion_error::conversion_utf16_from_utf8,
error);
}
//
// Return resulting UTF-16 string
//
return utf16;
}
inline std::string UTF8FromUTF16(const std::wstring & utf16)
{
//
// Special case of empty input string
//
if (utf16.empty())
return std::string();
//
// Get length (in chars) of resulting UTF-8 string
//
const int utf8Length = ::WideCharToMultiByte(
CP_UTF8, // convert to UTF-8
0, // default flags
utf16.data(), // source UTF-16 string
utf16.length(), // source string length, in wchar_t's,
NULL, // unused - no conversion required in this step
0, // request buffer size
NULL, NULL // unused
);
if (utf8Length == 0)
{
// Error
DWORD error = ::GetLastError();
throw utf8_conversion_error(
"Can't get length of UTF-8 string (WideCharToMultiByte failed).",
utf8_conversion_error::conversion_utf8_from_utf16,
error);
}
//
// Allocate destination buffer for UTF-8 string
//
std::string utf8;
utf8.resize(utf8Length);
//
// Do the conversion from UTF-16 to UTF-8
//
if ( ! ::WideCharToMultiByte(
CP_UTF8, // convert to UTF-8
0, // default flags
utf16.data(), // source UTF-16 string
utf16.length(), // source string length, in wchar_t's,
&utf8[0], // destination buffer
utf8.length(), // destination buffer size, in chars
NULL, NULL // unused
) )
{
// Error
DWORD error = ::GetLastError();
throw utf8_conversion_error(
"Can't convert string from UTF-16 to UTF-8 (WideCharToMultiByte failed).",
utf8_conversion_error::conversion_utf8_from_utf16,
error);
}
//
// Return resulting UTF-8 string
//
return utf8;
}
inline std::string UTF8FromUTF16(const wchar_t * utf16)
{
//
// Special case of empty input string
//
if (utf16 == NULL || *utf16 == L'\0')
return std::string();
// Prefetch the length of the input UTF-16 string
const int utf16Length = static_cast<int>(wcslen(utf16));
//
// Get length (in chars) of resulting UTF-8 string
//
const int utf8Length = ::WideCharToMultiByte(
CP_UTF8, // convert to UTF-8
0, // default flags
utf16, // source UTF-16 string
utf16Length, // source string length, in wchar_t's,
NULL, // unused - no conversion required in this step
0, // request buffer size
NULL, NULL // unused
);
if (utf8Length == 0)
{
// Error
DWORD error = ::GetLastError();
throw utf8_conversion_error(
"Can't get length of UTF-8 string (WideCharToMultiByte failed).",
utf8_conversion_error::conversion_utf8_from_utf16,
error);
}
//
// Allocate destination buffer for UTF-8 string
//
std::string utf8;
utf8.resize(utf8Length);
//
// Do the conversion from UTF-16 to UTF-8
//
if ( ! ::WideCharToMultiByte(
CP_UTF8, // convert to UTF-8
0, // default flags
utf16, // source UTF-16 string
utf16Length, // source string length, in wchar_t's,
&utf8[0], // destination buffer
utf8.length(), // destination buffer size, in chars
NULL, NULL // unused
) )
{
// Error
DWORD error = ::GetLastError();
throw utf8_conversion_error(
"Can't convert string from UTF-16 to UTF-8 (WideCharToMultiByte failed).",
utf8_conversion_error::conversion_utf8_from_utf16,
error);
}
//
// Return resulting UTF-8 string
//
return utf8;
}
} // namespace utf8util
//////////////////////////////////////////////////////////////////////////