e3ba96799c
First thing that is actually part of the final use case
379 lines
14 KiB
C++
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());
|
|
}
|
|
}
|