// Copyright (C) 2014 Michael McMaster // // This file is part of SCSI2SD. // // SCSI2SD is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // SCSI2SD is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with SCSI2SD. If not, see . // For compilers that support precompilation, includes "wx/wx.h". #include #ifndef WX_PRECOMP #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ConfigUtil.hh" #include "BoardPanel.hh" #include "TargetPanel.hh" #include "SCSI2SD_Bootloader.hh" #include "SCSI2SD_HID.hh" #include "Firmware.hh" #include #include #include #include #include #if __cplusplus >= 201103L #include #include using std::shared_ptr; #else #include #include using std::tr1::shared_ptr; #endif #define MIN_FIRMWARE_VERSION 0x0400 using namespace SCSI2SD; class ProgressWrapper { public: void setProgressDialog( const wxWindowPtr& dlg, size_t maxRows) { myProgressDialog = dlg; myMaxRows = maxRows; myNumRows = 0; } void clearProgressDialog() { myProgressDialog->Show(false); myProgressDialog.reset(); } void update(unsigned char arrayId, unsigned short rowNum) { if (!myProgressDialog) return; myNumRows++; std::stringstream ss; ss << "Writing flash array " << static_cast(arrayId) << " row " << static_cast(rowNum); wxLogMessage("%s", ss.str()); myProgressDialog->Update(myNumRows, ss.str()); } private: wxWindowPtr myProgressDialog; size_t myMaxRows; size_t myNumRows; }; static ProgressWrapper TheProgressWrapper; extern "C" void ProgressUpdate(unsigned char arrayId, unsigned short rowNum) { TheProgressWrapper.update(arrayId, rowNum); } namespace { static uint8_t sdCrc7(uint8_t* chr, uint8_t cnt, uint8_t crc) { uint8_t a; for(a = 0; a < cnt; a++) { uint8_t data = chr[a]; uint8_t i; for(i = 0; i < 8; i++) { crc <<= 1; if ((data & 0x80) ^ (crc & 0x80)) { crc ^= 0x09; } data <<= 1; } } return crc & 0x7F; } class TimerLock { public: TimerLock(wxTimer* timer) : myTimer(timer), myInterval(myTimer->GetInterval()) { myTimer->Stop(); }; virtual ~TimerLock() { if (myTimer && myInterval > 0) { myTimer->Start(myInterval); } } private: wxTimer* myTimer; int myInterval; }; class AppFrame : public wxFrame { public: AppFrame() : wxFrame(NULL, wxID_ANY, "scsi2sd-util", wxPoint(50, 50), wxSize(600, 650)), myInitialConfig(false), myTickCounter(0), myLastPollTime(0) { wxMenu *menuFile = new wxMenu(); menuFile->Append( ID_SaveFile, _("&Save to file..."), _("Save settings to local file.")); menuFile->Append( ID_OpenFile, _("&Open file..."), _("Load settings from local file.")); menuFile->AppendSeparator(); menuFile->Append( ID_ConfigDefaults, _("Load &Defaults"), _("Load default configuration options.")); menuFile->Append( ID_Firmware, _("&Upgrade Firmware..."), _("Upgrade or inspect device firmware version.")); menuFile->AppendSeparator(); menuFile->Append(wxID_EXIT); wxMenu *menuWindow= new wxMenu(); menuWindow->Append( ID_LogWindow, _("Show &Log"), _("Show debug log window")); wxMenu *menuDebug = new wxMenu(); mySCSILogChk = menuDebug->AppendCheckItem( ID_SCSILog, _("Log SCSI data"), _("Log SCSI commands")); mySelfTestChk = menuDebug->AppendCheckItem( ID_SelfTest, _("SCSI Standalone Self-Test"), _("SCSI Standalone Self-Test")); wxMenu *menuHelp = new wxMenu(); menuHelp->Append(wxID_ABOUT); wxMenuBar *menuBar = new wxMenuBar(); menuBar->Append( menuFile, _("&File") ); menuBar->Append( menuDebug, _("&Debug") ); menuBar->Append( menuWindow, _("&Window") ); menuBar->Append( menuHelp, _("&Help") ); SetMenuBar( menuBar ); CreateStatusBar(); { wxPanel* cfgPanel = new wxPanel(this); wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15); cfgPanel->SetSizer(fgs); // Empty space below menu bar. fgs->Add(5, 5, wxALL); wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook); myBoardPanel = new BoardPanel(tabs, ConfigUtil::DefaultBoardConfig()); tabs->AddPage(myBoardPanel, _("General Settings")); for (int i = 0; i < MAX_SCSI_TARGETS; ++i) { TargetPanel* target = new TargetPanel(tabs, ConfigUtil::Default(i)); myTargets.push_back(target); std::stringstream ss; ss << "Device " << (i + 1); tabs->AddPage(target, ss.str()); target->Fit(); } tabs->Fit(); fgs->Add(tabs); wxPanel* btnPanel = new wxPanel(cfgPanel); wxFlexGridSizer *btnFgs = new wxFlexGridSizer(1, 2, 5, 5); btnPanel->SetSizer(btnFgs); myLoadButton = new wxButton(btnPanel, ID_BtnLoad, _("Load from device")); btnFgs->Add(myLoadButton); mySaveButton = new wxButton(btnPanel, ID_BtnSave, _("Save to device")); btnFgs->Add(mySaveButton); fgs->Add(btnPanel); btnPanel->Fit(); cfgPanel->Fit(); } //Fit(); // Needed to reduce window size on Windows FitInside(); // Needed on Linux to prevent status bar overlap myLogWindow = new wxLogWindow(this, _("scsi2sd-util debug log"), true); myLogWindow->PassMessages(false); // Prevent messagebox popups myTimer = new wxTimer(this, ID_Timer); myTimer->Start(16); //ms, suitable for scsi debug logging } private: wxLogWindow* myLogWindow; BoardPanel* myBoardPanel; std::vector myTargets; wxButton* myLoadButton; wxButton* mySaveButton; wxMenuItem* mySCSILogChk; wxMenuItem* mySelfTestChk; wxTimer* myTimer; shared_ptr myHID; shared_ptr myBootloader; bool myInitialConfig; uint8_t myTickCounter; time_t myLastPollTime; void mmLogStatus(const std::string& msg) { // We set PassMessages to false on our log window to prevent popups, but // this also prevents wxLogStatus from updating the status bar. SetStatusText(msg); wxLogMessage(this, "%s", msg.c_str()); } void onConfigChanged(wxCommandEvent& event) { evaluate(); } void evaluate() { bool valid = true; // Check for duplicate SCSI IDs std::set enabledID; // Check for overlapping SD sectors. std::vector > sdSectors; bool isTargetEnabled = false; // Need at least one enabled uint32_t autoStartSector = 0; for (size_t i = 0; i < myTargets.size(); ++i) { myTargets[i]->setAutoStartSector(autoStartSector); valid = myTargets[i]->evaluate() && valid; if (myTargets[i]->isEnabled()) { isTargetEnabled = true; uint8_t scsiID = myTargets[i]->getSCSIId(); if (enabledID.find(scsiID) != enabledID.end()) { myTargets[i]->setDuplicateID(true); valid = false; } else { enabledID.insert(scsiID); myTargets[i]->setDuplicateID(false); } auto sdSectorRange = myTargets[i]->getSDSectorRange(); for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it) { if (sdSectorRange.first < it->second && sdSectorRange.second > it->first) { valid = false; myTargets[i]->setSDSectorOverlap(true); } else { myTargets[i]->setSDSectorOverlap(false); } } sdSectors.push_back(sdSectorRange); autoStartSector = sdSectorRange.second; } else { myTargets[i]->setDuplicateID(false); myTargets[i]->setSDSectorOverlap(false); } } valid = valid && isTargetEnabled; // Need at least one. mySaveButton->Enable( valid && myHID && (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION)); myLoadButton->Enable( myHID && (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION)); } enum { ID_ConfigDefaults = wxID_HIGHEST + 1, ID_Firmware, ID_Timer, ID_Notebook, ID_BtnLoad, ID_BtnSave, ID_LogWindow, ID_SCSILog, ID_SelfTest, ID_SaveFile, ID_OpenFile }; void OnID_ConfigDefaults(wxCommandEvent& event) { myBoardPanel->setConfig(ConfigUtil::DefaultBoardConfig()); for (size_t i = 0; i < myTargets.size(); ++i) { myTargets[i]->setConfig(ConfigUtil::Default(i)); } } void OnID_SaveFile(wxCommandEvent& event) { TimerLock lock(myTimer); wxFileDialog dlg( this, "Save config settings", "", "", "XML files (*.xml)|*.xml", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg.ShowModal() == wxID_CANCEL) return; wxFileOutputStream file(dlg.GetPath()); if (!file.IsOk()) { wxLogError("Cannot save settings to file '%s'.", dlg.GetPath()); return; } wxTextOutputStream s(file); s << "\n"; s << ConfigUtil::toXML(myBoardPanel->getConfig()); for (size_t i = 0; i < myTargets.size(); ++i) { s << ConfigUtil::toXML(myTargets[i]->getConfig()); } s << "\n"; } void OnID_OpenFile(wxCommandEvent& event) { TimerLock lock(myTimer); wxFileDialog dlg( this, "Load config settings", "", "", "XML files (*.xml)|*.xml", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dlg.ShowModal() == wxID_CANCEL) return; try { std::pair> configs( ConfigUtil::fromXML(std::string(dlg.GetPath()))); myBoardPanel->setConfig(configs.first); size_t i; for (i = 0; i < configs.second.size() && i < myTargets.size(); ++i) { myTargets[i]->setConfig(configs.second[i]); } for (; i < myTargets.size(); ++i) { myTargets[i]->setConfig(ConfigUtil::Default(i)); } } catch (std::exception& e) { wxLogError( "Cannot load settings from file '%s'.\n%s", dlg.GetPath(), e.what()); wxMessageBox( e.what(), "Load error", wxOK | wxICON_ERROR); } } void OnID_Firmware(wxCommandEvent& event) { TimerLock lock(myTimer); doFirmwareUpdate(); } void OnID_LogWindow(wxCommandEvent& event) { myLogWindow->Show(); } void doFirmwareUpdate() { wxFileDialog dlg( this, "Load firmware file", "", "", "SCSI2SD Firmware files (*.scsi2sd;*.cyacd)|*.cyacd;*.scsi2sd", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dlg.ShowModal() == wxID_CANCEL) return; std::string filename(dlg.GetPath()); wxWindowPtr progress( new wxGenericProgressDialog( "Searching for bootloader", "Searching for bootloader", 100, this, wxPD_AUTO_HIDE | wxPD_CAN_ABORT) ); mmLogStatus("Searching for bootloader"); while (true) { try { if (!myHID) myHID.reset(HID::Open()); if (myHID) { mmLogStatus("Resetting SCSI2SD into bootloader"); myHID->enterBootloader(); myHID.reset(); } if (!myBootloader) { myBootloader.reset(Bootloader::Open()); if (myBootloader) { mmLogStatus("Bootloader found"); break; } } else if (myBootloader) { // Verify the USB HID connection is valid if (!myBootloader->ping()) { mmLogStatus("Bootloader ping failed"); myBootloader.reset(); } else { mmLogStatus("Bootloader found"); break; } } } catch (std::exception& e) { mmLogStatus(e.what()); myHID.reset(); myBootloader.reset(); } wxMilliSleep(100); if (!progress->Pulse()) { return; // user cancelled. } } int totalFlashRows = 0; std::string tmpFile; try { zipper::ReaderPtr reader(new zipper::FileReader(filename)); zipper::Decompressor decomp(reader); std::vector files(decomp.getEntries()); for (auto it(files.begin()); it != files.end(); it++) { if (myBootloader->isCorrectFirmware((*it)->getPath())) { std::stringstream msg; msg << "Found firmware entry " << (*it)->getPath() << " within archive " << filename; mmLogStatus(msg.str()); tmpFile = wxFileName::CreateTempFileName( _("SCSI2SD_Firmware"), static_cast(NULL) ); zipper::FileWriter out(tmpFile); (*it)->decompress(out); msg.clear(); msg << "Firmware extracted to " << tmpFile; mmLogStatus(msg.str()); break; } } if (tmpFile.empty()) { // TODO allow "force" option wxMessageBox( "Wrong filename", "Wrong filename", wxOK | wxICON_ERROR); return; } Firmware firmware(tmpFile); totalFlashRows = firmware.totalFlashRows(); } catch (std::exception& e) { mmLogStatus(e.what()); std::stringstream msg; msg << "Could not open firmware file: " << e.what(); wxMessageBox( msg.str(), "Bad file", wxOK | wxICON_ERROR); wxRemoveFile(tmpFile); return; } { wxWindowPtr progress( new wxGenericProgressDialog( "Loading firmware", "Loading firmware", totalFlashRows, this, wxPD_AUTO_HIDE | wxPD_REMAINING_TIME) ); TheProgressWrapper.setProgressDialog(progress, totalFlashRows); } std::stringstream msg; msg << "Upgrading firmware from file: " << tmpFile; mmLogStatus(msg.str()); try { myBootloader->load(tmpFile, &ProgressUpdate); TheProgressWrapper.clearProgressDialog(); wxMessageBox( "Firmware update successful", "Firmware OK", wxOK); mmLogStatus("Firmware update successful"); myHID.reset(); myBootloader.reset(); } catch (std::exception& e) { TheProgressWrapper.clearProgressDialog(); mmLogStatus(e.what()); myHID.reset(); myBootloader.reset(); wxMessageBox( "Firmware Update Failed", e.what(), wxOK | wxICON_ERROR); wxRemoveFile(tmpFile); } } void dumpSCSICommand(std::vector buf) { std::stringstream msg; msg << std::hex; for (size_t i = 0; i < 32 && i < buf.size(); ++i) { msg << std::setfill('0') << std::setw(2) << static_cast(buf[i]) << ' '; } wxLogMessage(this, msg.str().c_str()); } void logSCSI() { if (!mySCSILogChk->IsChecked() || !myHID) { return; } try { std::vector info(HID::HID_PACKET_SIZE); if (myHID->readSCSIDebugInfo(info)) { dumpSCSICommand(info); } } catch (std::exception& e) { wxLogWarning(this, e.what()); myHID.reset(); } } void OnID_Timer(wxTimerEvent& event) { logSCSI(); time_t now = time(NULL); if (now == myLastPollTime) return; myLastPollTime = now; // Check if we are connected to the HID device. // AND/or bootloader device. try { if (myBootloader) { // Verify the USB HID connection is valid if (!myBootloader->ping()) { myBootloader.reset(); } } if (!myBootloader) { myBootloader.reset(Bootloader::Open()); if (myBootloader) { mmLogStatus("SCSI2SD Bootloader Ready"); } } int supressLog = 0; if (myHID && myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION) { // No method to check connection is still valid. // So assume it isn't. myHID.reset(); supressLog = 1; } else if (myHID && !myHID->ping()) { // Verify the USB HID connection is valid myHID.reset(); } if (!myHID) { myHID.reset(HID::Open()); if (myHID) { if (myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION) { if (!supressLog) { // Oh dear, old firmware std::stringstream msg; msg << "Firmware update required. Version " << myHID->getFirmwareVersionStr(); mmLogStatus(msg.str()); } } else { std::stringstream msg; msg << "SCSI2SD Ready, firmware version " << myHID->getFirmwareVersionStr(); mmLogStatus(msg.str()); std::vector csd(myHID->getSD_CSD()); std::vector cid(myHID->getSD_CID()); std::stringstream sdinfo; sdinfo << "SD Capacity (512-byte sectors): " << myHID->getSDCapacity() << std::endl; sdinfo << "SD CSD Register: "; if (sdCrc7(&csd[0], 15, 0) != (csd[15] >> 1)) { sdinfo << "BADCRC "; } for (size_t i = 0; i < csd.size(); ++i) { sdinfo << std::hex << std::setfill('0') << std::setw(2) << static_cast(csd[i]); } sdinfo << std::endl; sdinfo << "SD CID Register: "; if (sdCrc7(&cid[0], 15, 0) != (cid[15] >> 1)) { sdinfo << "BADCRC "; } for (size_t i = 0; i < cid.size(); ++i) { sdinfo << std::hex << std::setfill('0') << std::setw(2) << static_cast(cid[i]); } wxLogMessage(this, "%s", sdinfo.str()); if (mySelfTestChk->IsChecked()) { std::stringstream scsiInfo; scsiInfo << "SCSI Self-Test: " << (myHID->scsiSelfTest() ? "Passed" : "FAIL"); wxLogMessage(this, "%s", scsiInfo.str()); } if (!myInitialConfig) { /* This doesn't work properly, and causes crashes. wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad); GetEventHandler()->AddPendingEvent(loadEvent); */ } } } else { char ticks[] = {'/', '-', '\\', '|'}; std::stringstream ss; ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)]; myTickCounter++; SetStatusText(ss.str()); } } } catch (std::runtime_error& e) { std::cerr << e.what() << std::endl; mmLogStatus(e.what()); } evaluate(); } void doLoad(wxCommandEvent& event) { TimerLock lock(myTimer); if (!myHID) return; mmLogStatus("Loading configuration"); wxWindowPtr progress( new wxGenericProgressDialog( "Load config settings", "Loading config settings", 100, this, wxPD_CAN_ABORT | wxPD_REMAINING_TIME) ); int currentProgress = 0; int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS + 1; // Read board config first. std::vector boardCfgFlashData; int flashRow = SCSI_CONFIG_BOARD_ROW; { std::stringstream ss; ss << "Reading flash array " << SCSI_CONFIG_ARRAY << " row " << flashRow; mmLogStatus(ss.str()); currentProgress += 1; if (!progress->Update( (100 * currentProgress) / totalProgress, ss.str() ) ) { goto abort; } try { myHID->readFlashRow( SCSI_CONFIG_ARRAY, flashRow, boardCfgFlashData); myBoardPanel->setConfig( ConfigUtil::boardConfigFromBytes(&boardCfgFlashData[0])); } catch (std::runtime_error& e) { mmLogStatus(e.what()); goto err; } } flashRow = SCSI_CONFIG_0_ROW; for (size_t i = 0; i < myTargets.size(); ++i, flashRow += SCSI_CONFIG_ROWS) { std::vector raw(sizeof(TargetConfig)); for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j) { std::stringstream ss; ss << "Reading flash array " << SCSI_CONFIG_ARRAY << " row " << (flashRow + j); mmLogStatus(ss.str()); currentProgress += 1; if (currentProgress == totalProgress) { ss.str("Load Complete."); mmLogStatus("Load Complete."); } if (!progress->Update( (100 * currentProgress) / totalProgress, ss.str() ) ) { goto abort; } std::vector flashData; try { myHID->readFlashRow( SCSI_CONFIG_ARRAY, flashRow + j, flashData); } catch (std::runtime_error& e) { mmLogStatus(e.what()); goto err; } std::copy( flashData.begin(), flashData.end(), &raw[j * SCSI_CONFIG_ROW_SIZE]); } myTargets[i]->setConfig(ConfigUtil::fromBytes(&raw[0])); } // Support old boards without board config set if (memcmp(&boardCfgFlashData[0], "BCFG", 4)) { BoardConfig defCfg = ConfigUtil::DefaultBoardConfig(); defCfg.flags = myTargets[0]->getConfig().flagsDEPRECATED; myBoardPanel->setConfig(defCfg); } myInitialConfig = true; goto out; err: mmLogStatus("Load failed"); progress->Update(100, "Load failed"); goto out; abort: mmLogStatus("Load Aborted"); out: return; } void doSave(wxCommandEvent& event) { TimerLock lock(myTimer); if (!myHID) return; mmLogStatus("Saving configuration"); wxWindowPtr progress( new wxGenericProgressDialog( "Save config settings", "Saving config settings", 100, this, wxPD_CAN_ABORT | wxPD_REMAINING_TIME) ); int currentProgress = 0; int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS + 1; // Write board config first. int flashRow = SCSI_CONFIG_BOARD_ROW; { std::stringstream ss; ss << "Programming flash array " << SCSI_CONFIG_ARRAY << " row " << flashRow; mmLogStatus(ss.str()); currentProgress += 1; if (!progress->Update( (100 * currentProgress) / totalProgress, ss.str() ) ) { goto abort; } std::vector flashData = ConfigUtil::boardConfigToBytes(myBoardPanel->getConfig()); try { myHID->writeFlashRow( SCSI_CONFIG_ARRAY, flashRow, flashData); } catch (std::runtime_error& e) { mmLogStatus(e.what()); goto err; } } flashRow = SCSI_CONFIG_0_ROW; for (size_t i = 0; i < myTargets.size(); ++i, flashRow += SCSI_CONFIG_ROWS) { TargetConfig config(myTargets[i]->getConfig()); std::vector raw(ConfigUtil::toBytes(config)); for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j) { std::stringstream ss; ss << "Programming flash array " << SCSI_CONFIG_ARRAY << " row " << (flashRow + j); mmLogStatus(ss.str()); currentProgress += 1; if (currentProgress == totalProgress) { ss.str("Save Complete."); mmLogStatus("Save Complete."); } if (!progress->Update( (100 * currentProgress) / totalProgress, ss.str() ) ) { goto abort; } std::vector flashData(SCSI_CONFIG_ROW_SIZE, 0); std::copy( &raw[j * SCSI_CONFIG_ROW_SIZE], &raw[(1+j) * SCSI_CONFIG_ROW_SIZE], flashData.begin()); try { myHID->writeFlashRow( SCSI_CONFIG_ARRAY, flashRow + j, flashData); } catch (std::runtime_error& e) { mmLogStatus(e.what()); goto err; } } } // Reboot so new settings take effect. myHID->enterBootloader(); myHID.reset(); goto out; err: mmLogStatus("Save failed"); progress->Update(100, "Save failed"); goto out; abort: mmLogStatus("Save Aborted"); out: return; } // Note: Don't confuse this with the wxApp::OnExit virtual method void OnExitEvt(wxCommandEvent& event); void OnCloseEvt(wxCloseEvent& event); void OnAbout(wxCommandEvent& event) { wxMessageBox( "SCSI2SD (scsi2sd-util)\n" "Copyright (C) 2014 Michael McMaster \n" "\n" "This program is free software: you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation, either version 3 of the License, or\n" "(at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program. If not, see .\n", "About scsi2sd-util", wxOK | wxICON_INFORMATION ); } wxDECLARE_EVENT_TABLE(); }; wxBEGIN_EVENT_TABLE(AppFrame, wxFrame) EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults) EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware) EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow) EVT_MENU(AppFrame::ID_SaveFile, AppFrame::OnID_SaveFile) EVT_MENU(AppFrame::ID_OpenFile, AppFrame::OnID_OpenFile) EVT_MENU(wxID_EXIT, AppFrame::OnExitEvt) EVT_MENU(wxID_ABOUT, AppFrame::OnAbout) EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer) EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged) EVT_BUTTON(ID_BtnSave, AppFrame::doSave) EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad) EVT_CLOSE(AppFrame::OnCloseEvt) wxEND_EVENT_TABLE() class App : public wxApp { public: virtual bool OnInit() { AppFrame* frame = new AppFrame(); frame->Show(true); SetTopWindow(frame); return true; } }; } // namespace // Main Method wxIMPLEMENT_APP(App); void AppFrame::OnExitEvt(wxCommandEvent& event) { wxGetApp().ExitMainLoop(); } void AppFrame::OnCloseEvt(wxCloseEvent& event) { wxGetApp().ExitMainLoop(); }