wallet/frame.cpp

379 lines
14 KiB
C++
Raw Normal View History

#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);
2022-05-07 00:40:17 -04:00
menuBar->Append(new wxMenu, menu_strings[1].head); //Edit menu, initially empty and disabled
menuBar->Append(menuHelp, menu_strings[2].head);
SetMenuBar(menuBar);
CreateStatusBar();
2022-05-07 00:40:17 -04:00
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;
2022-04-14 05:17:18 -04:00
void Frame::NewWallet(wxFileName& filename, ristretto255::hash<256>& secret) {
/*If creation fails, abort with exception. If it succeeds, set LastUsed to default filename.
2022-05-23 02:06:01 -04:00
The exception in unit test should simply generate an error message, but if run during initialization,
2022-04-14 05:17:18 -04:00
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;)|");
2022-04-14 05:17:18 -04:00
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());
}
}