1
0
forked from cheng/wallet
wallet/frame.cpp
2022-05-23 16:06:01 +10:00

379 lines
14 KiB
C++

#include "stdafx.h"
// ----------------------------------------------------------------------------
// frame
// ----------------------------------------------------------------------------
Frame* singletonFrame{nullptr};
void Frame::RestorePositionFromConfig(const wxSize& bestSize) {
// SetPath() understands ".." but you should probably never use it.
singletonApp->pConfig->SetPath(wxT("/MainFrame")); wxPoint scr{ wxSystemSettings::GetMetric(wxSYS_SCREEN_X), wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) };
// restore frame position and size
int x = singletonApp->pConfig->ReadLong(wxT("x"), scr.x / 4);
int y = singletonApp->pConfig->ReadLong(wxT("y"), scr.y / 4);
int w = singletonApp->pConfig->ReadLong(wxT("w"), scr.x / 2);
int h = singletonApp->pConfig->ReadLong(wxT("h"), scr.y / 2);
w = std::min(std::max(std::max(w, scr.x / 5), bestSize.GetWidth()), 8 * scr.x / 9);
h = std::min(std::max(std::max(h, scr.y / 9), bestSize.GetHeight()), 4 * scr.y / 5);
x = std::max(scr.x / 12, std::min(x, scr.x - w - scr.x / 12));
y = std::max(scr.y / 10, std::min(y, scr.y - h - scr.y / 10));
this->Move(x, y);
this->Maximize(singletonApp->pConfig->ReadBool(wxT("Maximized"), false));
this->SetSize(w, h);
singletonApp->pConfig->SetPath(wxT("/"));
if (singletonApp->m_display || m_pLogWindow != nullptr) {
m_pLogWindow->GetFrame()->SetSize(w, h);
if (singletonApp->m_display_in_front) {
m_pLogWindow->GetFrame()->Move(std::min(x + 46, scr.x - w), std::min(y + 46, scr.y - h));
}
else {
m_pLogWindow->GetFrame()->Move(std::max(x - 32, 0), std::max(y - 32, 0));
}
m_pLogWindow->GetFrame()->SetTitle(sz_unit_test_log);
m_pLogWindow->Show(true);
}
else {
m_pLogNull = std::make_unique<wxLogNull>();
}
}
void Frame::StorePositionToConfig() {
if (singletonApp->pConfig) {
singletonApp->pConfig->SetPath(wxT("/MainFrame"));
if (this->IsMaximized()) {
singletonApp->pConfig->Write(wxT("Maximized"), true);
}
else {
// save the frame position
int x, y, w, h;
this->GetSize(&w, &h);
this->GetPosition(&x, &y);
singletonApp->pConfig->Write(wxT("x"), (long)x);
singletonApp->pConfig->Write(wxT("y"), (long)y);
singletonApp->pConfig->Write(wxT("w"), (long)w);
singletonApp->pConfig->Write(wxT("h"), (long)h);
singletonApp->pConfig->Write(wxT("Maximized"), false);
}
singletonApp->pConfig->SetPath(wxT("/"));
}
}
// main frame ctor
Frame::Frame(wxString wxs)
: wxFrame(nullptr, myID_MAINFRAME, wxs, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, wxs),
m_panel(),
m_LastUsedSqlite()
{
try {
assert(singletonFrame == nullptr);
singletonFrame = this;
SetIcon(wxICON(AAArho));
sqlite3_init();
if (sodium_init() == -1) {
szError = "Fatal Error: Encryption library did not init.";
errorCode = 6;
// Cannot log the error, because logging not set up yet, so logging itself causes an exception
throw FatalException(szError.c_str());
}
wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED);
if (singletonApp->m_display || singletonApp->m_log_focus_events) {
m_pLogNull.reset(nullptr);
m_pLogWindow = new wxLogWindow(this, wxs, false, false);
wxLog::EnableLogging(true);
wxLog::SetActiveTarget(m_pLogWindow);
m_pLogWindow->GetFrame()->SetName(sz_unit_test_log);
m_pLogWindow->GetFrame()->SetIcon(wxICON(AAArho));
if (singletonApp->m_unit_test) {
wxLogMessage(wxT("Command line specified %s unit test with%s exit on completion of unit test."),
singletonApp->m_complete_unit_test?wxT("complete"): singletonApp->m_quick_unit_test?wxT("quick"):wxT(""),
singletonApp->m_display ? wxT("out") : wxT(""));
wxLogMessage(wxT("If an error occurs during unit test, the program will return a non zero "
"error number on exit."));
wxLogMessage(wxT(""));
}
}else {
wxLog::EnableLogging(false);
wxLog::SetActiveTarget(nullptr);
m_pLogNull.reset(new wxLogNull());
}
if (singletonApp->m_unit_test) singletonApp->Bind(
wxEVT_IDLE,
&UnitTest
);
if (singletonApp->m_log_focus_events) {
wxLogMessage(wxT("Logging focus events"));
wxLogMessage(wxT(""));
}
if (singletonApp->m_params.empty()) {
wxLogMessage(wxT("No wallet specified. Attempting to open last used wallet"));
}else {
wxString subcommands( wxT(""));
for (auto& str : singletonApp->m_params) {
subcommands += str + wxT(" ");
}
wxLogMessage(wxT("command argument%s %s"), singletonApp->m_params.size()==1?"":"s", subcommands);
wxLogMessage(wxT("attempting to open %s"), singletonApp->m_params[0]);
}
SetIcon(wxICON(AAArho)); //Does not appear to do anything. Maybe it does something in Unix.
//wxICON is a namestring on windows, and a symbol on Unix
Bind(wxEVT_CLOSE_WINDOW, &Frame::OnClose, this);
wxMenu* menuFile = new wxMenu;
menuFile->Append(wxID_NEW, menu_strings[0].tail[0][0], menu_strings[0].tail[0][1]);
menuFile->Bind(wxEVT_MENU, &Frame::OnSaveNew, this, wxID_NEW);
menuFile->Append(wxID_REFRESH, menu_strings[0].tail[1][0], menu_strings[0].tail[1][1]);
menuFile->Bind(wxEVT_MENU, &Frame::RecreateWalletFromExistingSecret, this, wxID_REFRESH);
menuFile->Append(wxID_OPEN, menu_strings[0].tail[2][0], menu_strings[0].tail[2][1]);
menuFile->Bind(wxEVT_MENU, &Frame::OnFileOpen, this, wxID_OPEN);
{ auto _ = new wxMenuItem(menuFile, wxID_CLOSE);
_->SetHelp(menu_strings[0].tail[3][1] + m_LastUsedSqlite.GetFullPath());
menuFile->Append(_);
menuFile->Bind(wxEVT_MENU, &Frame::OnMyCloseMPanel, this, wxID_CLOSE);
}
menuFile->Append(myID_DELETECONFIG, menu_strings[0].tail[4][0], menu_strings[0].tail[4][1] + m_LastUsedSqlite.GetFullPath());
menuFile->Bind(wxEVT_MENU, &Frame::OnDeleteConfiguration, this, myID_DELETECONFIG);
menuFile->Append(wxID_EXIT);
menuFile->Bind(wxEVT_MENU, &Frame::OnExit, this, wxID_EXIT);
wxMenu* menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT);
menuHelp->Bind(wxEVT_MENU, &Frame::OnAbout, this, wxID_ABOUT);
wxMenuBar* menuBar = new wxMenuBar;
menuBar->Append(menuFile, menu_strings[0].head);
menuBar->Append(new wxMenu, menu_strings[1].head); //Edit menu, initially empty and disabled
menuBar->Append(menuHelp, menu_strings[2].head);
SetMenuBar(menuBar);
CreateStatusBar();
menuBar->EnableTop(1, false); //disable edit menu.
// child controls
m_LastUsedSqlite.Assign(singletonApp->pConfig->Read(wxT("/Wallet/LastUsed"), wxT("")));
if (!m_LastUsedSqlite.IsOk() || !m_LastUsedSqlite.HasName() || !m_LastUsedSqlite.HasExt()) {
m_panel = new welcome_to_rhocoin(this); //Owner is "this", via the base class wxFrame. m_panel is a
// non owning pointer in the derived class that duplicates the owning pointer in the base class.
}
else {
display_wallet* panel = new display_wallet(this, m_LastUsedSqlite);
m_panel = panel;
}
this->RestorePositionFromConfig(ClientToWindowSize(m_panel->GetBestSize()));
SetClientSize(GetClientSize());
}
catch (const std::exception& e) {
// cannot throw when no window is available. Construction of the base frame has to be completed,
// come what may.
// if an exception propagated from the constructor of the derived frame, it would destruct the base frame
// and chaos would ensue as a windowing program attempts to handle an error with no main window.
// so exceptions in the constructor of the main frame have to be caught and not rethrown.
queue_error_message(e.what());
}
}
void Frame::OnExit(wxCommandEvent& event) {
if (m_panel) {
m_panel->Close(true);
m_panel = nullptr;
}
Close(true);
}
void Frame::OnClose(wxCloseEvent& event) {
// This event gives you the opportunity to clean up anything that needs explicit cleanup, albeit if you have done your work right nothing should need explicit cleanup,
// and to object to the closing in a "file not saved" type situation.
// https://docs.wxwidgets.org/trunk/classwx_close_event.html
if (sqlite3_shutdown())wxMessageBox(wxT(R"|(Sqlite3 shutdown error)|"), wsz_error, wxICON_ERROR);
DestroyChildren();
Destroy(); //Default handler will destroy the window. This is our handler for the user calling close, replacing the default handler.
}
void Frame::OnAbout(wxCommandEvent& event)
{
wxMessageBox(szAboutWallet,
szAboutTitle, wxOK | wxICON_INFORMATION);
}
void Frame::OnDeleteConfiguration(wxCommandEvent&)
{
std::unique_ptr<wxConfigBase>pConfig{ wxConfigBase::Set(nullptr) };
if (pConfig)
{
if (pConfig->DeleteAll())
{
wxLogMessage(wxT("Config file/registry key successfully deleted."));
wxConfigBase::DontCreateOnDemand();
pConfig.release();
}
else
{
wxLogError(wxT("Deleting config file/registry key failed."));
}
}
else {
wxLogError(wxT("No config to delete!"));
return;
}
}
using ro::bin2hex, ro::to_base64_string;
void Frame::NewWallet(wxFileName& filename, ristretto255::hash<256>& secret) {
/*If creation fails, abort with exception. If it succeeds, set LastUsed to default filename.
The exception in unit test should simply generate an error message, but if run during initialization,
should bring up the more complex UI for constructing or selecting your wallet file.*/
wxLogMessage(_wx("New wallet file %s"), filename.GetFullPath());
std::unique_ptr<ISqlite3> db{ nullptr };
try {
// Disk operations to create wallet, which may throw.
// This try/catch block exists to catch disk io issues.
db.reset(Sqlite3_create(filename.GetFullPath().ToUTF8()));
db->exec(R"|(
PRAGMA journal_mode = WAL;
PRAGMA synchronous = 1;
BEGIN TRANSACTION;
CREATE TABLE "Keys"(
"pubkey" BLOB NOT NULL UNIQUE PRIMARY KEY,
"id" integer NOT NULL,
"use" INTEGER NOT NULL);
CREATE TABLE "Names"(
"name" TEXT NOT NULL UNIQUE
);
CREATE TABLE "Misc"(
"index" INTEGER NOT NULL UNIQUE PRIMARY KEY,
"m" BLOB
);
COMMIT;)|");
wxLogMessage("\t\tConstructing default wallet %s", filename.GetFullPath());
// We now have a working wallet file with no valid data. Attempting to create a strong random secret, a name, and public and private keys for that name.
wxLogMessage("\t\tGenerating random 128 bit wallet secret");
auto text_secret{ DeriveTextSecret(ristretto255::scalar::random(), 1) };
ro::msec start_time{ ro::msec_since_epoch() };
ristretto255::CMasterSecret MasterSecret(DeriveStrongSecret(&text_secret[0]));
decltype(start_time) end_time{ ro::msec_since_epoch() };
wxLogMessage("\t\tStrong secret derivation took %d milliseconds", (end_time - start_time).count());
sql_update_to_misc update_to_misc(db);
update_to_misc(1, WALLET_FILE_IDENTIFIER);
update_to_misc(2, WALLET_FILE_SCHEMA_VERSION_0_0);
update_to_misc(3, &text_secret[0]);
update_to_misc(4, MasterSecret);
}
catch (const MyException& e) {
ILogError(R"|(Failed to create or failed to properly initialize wallet)|");
errorCode = 20;
szError = e.what();
ILogError(szError.c_str());
}
}
class hide_panel {
wxPanel* oldpanel;
public:
hide_panel(wxPanel* v): oldpanel(v){
v->Hide();
}
~hide_panel() {
if (oldpanel == singletonFrame->m_panel) oldpanel->Show();
}
};
void Frame::OnSaveNew(wxCommandEvent& WXUNUSED(event))
{
wxFileDialog dialog(this,
sz_new_wallet_new_secret,
wxStandardPaths::Get().GetUserLocalDataDir(),
sz_default_wallet_name,
sz_wallet_files_title,
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
dialog.SetFilterIndex(1);
if (dialog.ShowModal() == wxID_OK)
{
wxLogMessage("%s, filter %d",
dialog.GetPath(), dialog.GetFilterIndex());
hide_panel hid(m_panel);
wxString wxStrWallet{ dialog.GetDirectory() + "/" + dialog.GetFilename() };
wxFileName wxFileWallet(wxStrWallet);
ristretto255::hash<256> WalletSecret{ wxStrWallet.ToUTF8() };
NewWallet(wxFileWallet, WalletSecret);
wxLogMessage("new wallet created: %s", wxStrWallet);
display_wallet* panel = new display_wallet(this, wxFileWallet);
if (m_panel)m_panel->Destroy();
m_panel = panel;
m_panel->Show();
}
}
void Frame::OnFileOpen(wxCommandEvent&) {
wxString directory{ wxT("") };
wxString file{ wxT("") };
if (m_LastUsedSqlite.IsOk()) {
directory = m_LastUsedSqlite.GetPath();
file = m_LastUsedSqlite.GetFullName();
}
wxFileDialog
dialog(this, sz_open_wallet_file, directory, file,
sz_wallet_files_title, wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR);
if (dialog.ShowModal() == wxID_CANCEL)
return; // the user changed idea...
wxLogMessage("Opening %s", dialog.GetPath());
wxFileName walletfile(dialog.GetPath());
if (m_panel)m_panel->Destroy(); //Destroy somehow manages to execute
// the correct derived destructor regardless of what kind of object it is.
display_wallet* panel = new display_wallet(this, walletfile);
m_panel = panel;
m_panel->Show();
}
void Frame::RecreateWalletFromExistingSecret(wxCommandEvent&) {
wxMessageBox(wxT("new wallet existing secret event"), wxT(""));
auto standardpaths = wxStandardPaths::Get();
wxFileDialog dialog(this,
sz_new_wallet_existing_secret,
wxStandardPaths::Get().GetAppDocumentsDir(),
sz_default_wallet_name,
sz_wallet_files_title,
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
dialog.SetFilterIndex(1);
if (dialog.ShowModal() == wxID_OK)
{
wxLogMessage("%s, filter %d",
dialog.GetPath(), dialog.GetFilterIndex());
}
}
void Frame::OnMyCloseMPanel(wxCommandEvent& event) {
if (m_panel) {
if (!m_panel->Close(false)) throw MyException("Close cancelled");
}
assert(m_panel == nullptr);
singletonApp->pConfig->SetPath(wxT("/Wallet"));
if (singletonApp->pConfig->Read(wxT("LastUsed"), wxT("")) == m_LastUsedSqlite.GetFullPath()) {
singletonApp->pConfig->DeleteEntry(wxT("LastUsed"));
m_LastUsedSqlite.Clear();
}
assert(m_panel == nullptr);
}
void Frame::OnMenuOpen(wxMenuEvent& evt) {
auto pMenu(evt.GetMenu());
if (pMenu) {
auto label(pMenu->GetTitle());
wxLogMessage(wxT("Open menu \"%s\""), label);
}
}
Frame::~Frame() {
assert(singletonFrame == this);
singletonFrame = nullptr;
wxConfigBase* pConfig = wxConfigBase::Get();
if (pConfig == nullptr)return;
StorePositionToConfig();
if (singletonApp->pConfig->Read(wxT("/Wallet/LastUsed"), wxT("")) != m_LastUsedSqlite.GetFullPath()) {
pConfig->Write(wxT("/Wallet/LastUsed"), m_LastUsedSqlite.GetFullPath());
}
}