Implement WRITE BUFFER and WRITE WITH VERIFY commands
[SCSI2SD-V6.git] / software / scsi2sd-util / scsi2sd-util.cc
index e847707..cc26e8b 100644 (file)
 #endif
 
 #include <wx/filedlg.h>
+#include <wx/filefn.h>
+#include <wx/filename.h>
+#include <wx/log.h>
 #include <wx/notebook.h>
 #include <wx/progdlg.h>
 #include <wx/utils.h>
 #include <wx/windowptr.h>
 #include <wx/thread.h>
 
+#include <zipper.hh>
+
 #include "ConfigUtil.hh"
 #include "TargetPanel.hh"
 #include "SCSI2SD_Bootloader.hh"
@@ -36,6 +41,7 @@
 #include "Firmware.hh"
 
 #include <algorithm>
+#include <iomanip>
 #include <vector>
 #include <set>
 #include <sstream>
@@ -50,6 +56,7 @@ using std::shared_ptr;
 using std::tr1::shared_ptr;
 #endif
 
+#define MIN_FIRMWARE_VERSION 0x0400
 
 using namespace SCSI2SD;
 
@@ -67,6 +74,7 @@ public:
 
        void clearProgressDialog()
        {
+               myProgressDialog->Show(false);
                myProgressDialog.reset();
        }
 
@@ -80,7 +88,7 @@ public:
                ss << "Writing flash array " <<
                        static_cast<int>(arrayId) << " row " <<
                        static_cast<int>(rowNum);
-               std::clog << ss.str() << std::endl;
+               wxLogMessage("%s", ss.str());
                myProgressDialog->Update(myNumRows, ss.str());
        }
 
@@ -128,7 +136,8 @@ public:
        AppFrame() :
                wxFrame(NULL, wxID_ANY, "scsi2sd-util", wxPoint(50, 50), wxSize(600, 650)),
                myInitialConfig(false),
-               myTickCounter(0)
+               myTickCounter(0),
+               myLastPollTime(0)
        {
                wxMenu *menuFile = new wxMenu();
                menuFile->Append(
@@ -141,19 +150,39 @@ public:
                        "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");
+
                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();
-               SetStatusText( "Searching for SCSI2SD" );
 
                {
                        wxPanel* cfgPanel = new wxPanel(this);
-                       wxFlexGridSizer *fgs = new wxFlexGridSizer(2, 1, 5, 5);
+                       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);
 
                        for (int i = 0; i < MAX_SCSI_TARGETS; ++i)
@@ -164,37 +193,42 @@ public:
                                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);
-                       btnFgs->Add(new wxButton(btnPanel, ID_BtnLoad, wxT("Load from device")));
+                       btnPanel->SetSizer(btnFgs);
+                       myLoadButton =
+                               new wxButton(btnPanel, ID_BtnLoad, wxT("Load from device"));
+                       btnFgs->Add(myLoadButton);
                        mySaveButton =
                                new wxButton(btnPanel, ID_BtnSave, wxT("Save to device"));
                        btnFgs->Add(mySaveButton);
-                       {
-                               wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
-                               hbox->Add(btnFgs, 1, wxALL | wxEXPAND, 15);
-                               btnPanel->SetSizer(hbox);
-                               fgs->Add(btnPanel);
-                       }
+                       fgs->Add(btnPanel);
 
-                       {
-                               wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
-                               hbox->Add(fgs, 1, wxALL | wxEXPAND, 15);
-                               cfgPanel->SetSizer(hbox);
-                       }
+                       btnPanel->Fit();
+                       cfgPanel->Fit();
                }
+               //Fit(); // Needed to reduce window size on Windows
+               FitInside(); // Needed on Linux to prevent status bar overlap
 
-               myTimer = new wxTimer(this, ID_Timer);
-               myTimer->Start(1000); //ms
+               myLogWindow = new wxLogWindow(this, wxT("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;
        std::vector<TargetPanel*> myTargets;
+       wxButton* myLoadButton;
        wxButton* mySaveButton;
+       wxMenuItem* mySCSILogChk;
        wxTimer* myTimer;
        shared_ptr<HID> myHID;
        shared_ptr<Bootloader> myBootloader;
@@ -202,6 +236,16 @@ private:
 
        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();
@@ -218,9 +262,10 @@ private:
                std::vector<std::pair<uint32_t, uint64_t> > 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())
@@ -253,6 +298,7 @@ private:
                                        }
                                }
                                sdSectors.push_back(sdSectorRange);
+                               autoStartSector = sdSectorRange.second + 1;
                        }
                        else
                        {
@@ -263,7 +309,14 @@ private:
 
                valid = valid && isTargetEnabled; // Need at least one.
 
-               mySaveButton->Enable(valid && myHID);
+               mySaveButton->Enable(
+                       valid &&
+                       myHID &&
+                       (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));
+
+               myLoadButton->Enable(
+                       myHID &&
+                       (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));
        }
 
 
@@ -274,7 +327,9 @@ private:
                ID_Timer,
                ID_Notebook,
                ID_BtnLoad,
-               ID_BtnSave
+               ID_BtnSave,
+               ID_LogWindow,
+               ID_SCSILog
        };
 
        void OnID_ConfigDefaults(wxCommandEvent& event)
@@ -291,6 +346,11 @@ private:
                doFirmwareUpdate();
        }
 
+       void OnID_LogWindow(wxCommandEvent& event)
+       {
+               myLogWindow->Show();
+       }
+
        void doFirmwareUpdate()
        {
                wxFileDialog dlg(
@@ -298,83 +358,124 @@ private:
                        "Load firmware file",
                        "",
                        "",
-                       "SCSI2SD Firmware files (*.cyacd)|*.cyacd",
+                       "SCSI2SD Firmware files (*.scsi2sd;*.cyacd)|*.cyacd;*.scsi2sd",
                        wxFD_OPEN | wxFD_FILE_MUST_EXIST);
                if (dlg.ShowModal() == wxID_CANCEL) return;
 
                std::string filename(dlg.GetPath());
 
-               try
+               wxWindowPtr<wxGenericProgressDialog> progress(
+                       new wxGenericProgressDialog(
+                               "Searching for bootloader",
+                               "Searching for bootloader",
+                               100,
+                               this,
+                               wxPD_AUTO_HIDE | wxPD_CAN_ABORT)
+                               );
+               mmLogStatus("Searching for bootloader");
+               while (true)
                {
-                       if (!myHID) myHID.reset(HID::Open());
-                       if (myHID)
+                       try
                        {
-                               std::clog << "Resetting SCSI2SD into bootloader" << std::endl;
+                               if (!myHID) myHID.reset(HID::Open());
+                               if (myHID)
+                               {
+                                       mmLogStatus("Resetting SCSI2SD into bootloader");
 
-                               myHID->enterBootloader();
-                               myHID.reset();
-                       }
+                                       myHID->enterBootloader();
+                                       myHID.reset();
+                               }
 
-                       if (myBootloader)
-                       {
-                               // Verify the USB HID connection is valid
-                               if (!myBootloader->ping())
+
+                               if (!myBootloader)
                                {
-                                       myBootloader.reset();
+                                       myBootloader.reset(Bootloader::Open());
+                                       if (myBootloader)
+                                       {
+                                               mmLogStatus("Bootloader found");
+                                               break;
+                                       }
                                }
-                       }
 
-                       for (int i = 0; !myBootloader && (i < 10); ++i)
+                               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)
                        {
-                               std::clog << "Searching for bootloader" << std::endl;
-                               myBootloader.reset(Bootloader::Open());
-                               wxMilliSleep(100);
+                               mmLogStatus(e.what());
+                               myHID.reset();
+                               myBootloader.reset();
+                       }
+                       wxMilliSleep(100);
+                       if (!progress->Pulse())
+                       {
+                               return; // user cancelled.
                        }
-               }
-               catch (std::exception& e)
-               {
-                       std::clog << e.what() << std::endl;
-
-                       SetStatusText(e.what());
-                       myHID.reset();
-                       myBootloader.reset();
-               }
-
-               if (!myBootloader)
-               {
-                       wxMessageBox(
-                               "SCSI2SD not found",
-                               "Device not ready",
-                               wxOK | wxICON_ERROR);
-
-                       return;
-               }
-
-               if (!myBootloader->isCorrectFirmware(filename))
-               {
-                       // TODO allow "force" option
-                       wxMessageBox(
-                               "Wrong filename",
-                               "Wrong filename",
-                               wxOK | wxICON_ERROR);
-                       return;
                }
 
                int totalFlashRows = 0;
+               std::string tmpFile;
                try
                {
-                       Firmware firmware(filename);
+                       zipper::ReaderPtr reader(new zipper::FileReader(filename));
+                       zipper::Decompressor decomp(reader);
+                       std::vector<zipper::CompressedFilePtr> 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(
+                                                       wxT("SCSI2SD_Firmware"), static_cast<wxFile*>(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)
                {
-                       SetStatusText(e.what());
+                       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;
                }
 
@@ -385,23 +486,25 @@ private:
                                        "Loading firmware",
                                        totalFlashRows,
                                        this,
-                                       wxPD_APP_MODAL)
+                                       wxPD_AUTO_HIDE | wxPD_REMAINING_TIME)
                                        );
                        TheProgressWrapper.setProgressDialog(progress, totalFlashRows);
                }
 
-               std::clog << "Upgrading firmware from file: " << filename << std::endl;
+               std::stringstream msg;
+               msg << "Upgrading firmware from file: " << tmpFile;
+               mmLogStatus(msg.str());
 
                try
                {
-                       myBootloader->load(filename, &ProgressUpdate);
+                       myBootloader->load(tmpFile, &ProgressUpdate);
                        TheProgressWrapper.clearProgressDialog();
 
                        wxMessageBox(
                                "Firmware update successful",
                                "Firmware OK",
                                wxOK);
-                       SetStatusText("Firmware update successful");
+                       mmLogStatus("Firmware update successful");
 
 
                        myHID.reset();
@@ -410,7 +513,7 @@ private:
                catch (std::exception& e)
                {
                        TheProgressWrapper.clearProgressDialog();
-                       SetStatusText(e.what());
+                       mmLogStatus(e.what());
                        myHID.reset();
                        myBootloader.reset();
 
@@ -418,11 +521,47 @@ private:
                                "Firmware Update Failed",
                                e.what(),
                                wxOK | wxICON_ERROR);
+
+                       wxRemoveFile(tmpFile);
+               }
+       }
+
+       void logSCSI()
+       {
+               if (!mySCSILogChk->IsChecked() ||
+                       !myHID)
+               {
+                       return;
+               }
+               try
+               {
+                       std::vector<uint8_t> info(HID::HID_PACKET_SIZE);
+                       if (myHID->readSCSIDebugInfo(info))
+                       {
+                               std::stringstream msg;
+                               msg << std::hex;
+                               for (size_t i = 0; i < 32 && i < info.size(); ++i)
+                               {
+                                       msg << std::setfill('0') << std::setw(2) <<
+                                               static_cast<int>(info[i]) << ' ';
+                               }
+                               wxLogMessage(this, msg.str().c_str());
+                       }
+               }
+               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
@@ -442,17 +581,22 @@ private:
 
                                if (myBootloader)
                                {
-                                       SetStatusText(wxT("SCSI2SD Bootloader Ready"));
+                                       mmLogStatus("SCSI2SD Bootloader Ready");
                                }
                        }
 
-                       if (myHID)
+                       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
-                               if (!myHID->ping())
-                               {
-                                       myHID.reset();
-                               }
+                               myHID.reset();
                        }
 
                        if (!myHID)
@@ -460,22 +604,54 @@ private:
                                myHID.reset(HID::Open());
                                if (myHID)
                                {
-                                       uint16_t version = myHID->getFirmwareVersion();
-                                       if (version == 0)
+                                       if (myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)
                                        {
-                                               // Oh dear, old firmware
-                                               SetStatusText(wxT("Firmware update required"));
-                                               myHID.reset();
+                                               if (!supressLog)
+                                               {
+                                                       // Oh dear, old firmware
+                                                       std::stringstream msg;
+                                                       msg << "Firmware update required. Version " <<
+                                                               myHID->getFirmwareVersionStr();
+                                                       mmLogStatus(msg.str());
+                                               }
                                        }
                                        else
                                        {
-                                               SetStatusText(wxT("SCSI2SD Ready"));
+                                               std::stringstream msg;
+                                               msg << "SCSI2SD Ready, firmware version " <<
+                                                       myHID->getFirmwareVersionStr();
+                                               mmLogStatus(msg.str());
+
+                                               std::vector<uint8_t> csd(myHID->getSD_CSD());
+                                               std::vector<uint8_t> cid(myHID->getSD_CID());
+                                               std::stringstream sdinfo;
+                                               sdinfo << "SD Capacity (512-byte sectors): " <<
+                                                       myHID->getSDCapacity() << std::endl;
+
+                                               sdinfo << "SD CSD Register: ";
+                                               for (size_t i = 0; i < csd.size(); ++i)
+                                               {
+                                                       sdinfo <<
+                                                               std::hex << std::setfill('0') << std::setw(2) <<
+                                                               static_cast<int>(csd[i]);
+                                               }
+                                               sdinfo << std::endl;
+                                               sdinfo << "SD CID Register: ";
+                                               for (size_t i = 0; i < cid.size(); ++i)
+                                               {
+                                                       sdinfo <<
+                                                               std::hex << std::setfill('0') << std::setw(2) <<
+                                                               static_cast<int>(cid[i]);
+                                               }
+
+                                               wxLogMessage(this, "%s", sdinfo.str());
 
                                                if (!myInitialConfig)
                                                {
                                                        wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
                                                        GetEventHandler()->AddPendingEvent(loadEvent);
                                                }
+
                                        }
                                }
                                else
@@ -490,8 +666,8 @@ private:
                }
                catch (std::runtime_error& e)
                {
-                       std::clog << e.what() << std::endl;
-                       SetStatusText(e.what());
+                       std::cerr << e.what() << std::endl;
+                       mmLogStatus(e.what());
                }
 
                evaluate();
@@ -502,7 +678,7 @@ private:
                TimerLock lock(myTimer);
                if (!myHID) return;
 
-               std::clog << "Loading configuration" << std::endl;
+               mmLogStatus("Loading configuration");
 
                wxWindowPtr<wxGenericProgressDialog> progress(
                        new wxGenericProgressDialog(
@@ -510,7 +686,7 @@ private:
                                "Loading config settings",
                                100,
                                this,
-                               wxPD_APP_MODAL | wxPD_CAN_ABORT)
+                               wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
                                );
 
                int flashRow = SCSI_CONFIG_0_ROW;
@@ -527,8 +703,7 @@ private:
                                std::stringstream ss;
                                ss << "Reading flash array " << SCSI_CONFIG_ARRAY <<
                                        " row " << (flashRow + j);
-                               SetStatusText(ss.str());
-                               std::clog << ss.str() << std::endl;
+                               mmLogStatus(ss.str());
                                currentProgress += 1;
                                if (!progress->Update(
                                                (100 * currentProgress) / totalProgress,
@@ -549,7 +724,7 @@ private:
                                }
                                catch (std::runtime_error& e)
                                {
-                                       SetStatusText(e.what());
+                                       mmLogStatus(e.what());
                                        goto err;
                                }
 
@@ -562,8 +737,7 @@ private:
                }
 
                myInitialConfig = true;
-               SetStatusText("Load Complete");
-               std::clog << "Load Complete" << std::endl;
+               mmLogStatus("Load Complete");
                while (progress->Update(100, "Load Complete"))
                {
                        // Wait for the user to click "Close"
@@ -572,8 +746,7 @@ private:
                goto out;
 
        err:
-               SetStatusText("Load failed");
-               std::clog << "Load failed" << std::endl;
+               mmLogStatus("Load failed");
                while (progress->Update(100, "Load failed"))
                {
                        // Wait for the user to click "Close"
@@ -582,8 +755,7 @@ private:
                goto out;
 
        abort:
-               SetStatusText("Load Aborted");
-               std::clog << "Load Aborted" << std::endl;
+               mmLogStatus("Load Aborted");
 
        out:
                return;
@@ -594,7 +766,7 @@ private:
                TimerLock lock(myTimer);
                if (!myHID) return;
 
-               std::clog << "Saving configuration" << std::endl;
+               mmLogStatus("Saving configuration");
 
                wxWindowPtr<wxGenericProgressDialog> progress(
                        new wxGenericProgressDialog(
@@ -602,7 +774,7 @@ private:
                                "Saving config settings",
                                100,
                                this,
-                               wxPD_APP_MODAL | wxPD_CAN_ABORT)
+                               wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
                                );
 
                int flashRow = SCSI_CONFIG_0_ROW;
@@ -620,8 +792,7 @@ private:
                                std::stringstream ss;
                                ss << "Programming flash array " << SCSI_CONFIG_ARRAY <<
                                        " row " << (flashRow + j);
-                               SetStatusText(ss.str());
-                               std::clog << ss.str() << std::endl;
+                               mmLogStatus(ss.str());
                                currentProgress += 1;
                                if (!progress->Update(
                                                (100 * currentProgress) / totalProgress,
@@ -644,8 +815,7 @@ private:
                                }
                                catch (std::runtime_error& e)
                                {
-                                       std::clog << e.what() << std::endl;
-                                       SetStatusText(e.what());
+                                       mmLogStatus(e.what());
                                        goto err;
                                }
                        }
@@ -655,8 +825,7 @@ private:
                myHID->enterBootloader();
                myHID.reset();
 
-               SetStatusText("Save Complete");
-               std::clog << "Save Complete" << std::endl;
+               mmLogStatus("Save Complete");
                while (progress->Update(100, "Save Complete"))
                {
                        // Wait for the user to click "Close"
@@ -665,8 +834,7 @@ private:
                goto out;
 
        err:
-               SetStatusText("Save failed");
-               std::clog << "Save failed" << std::endl;
+               mmLogStatus("Save failed");
                while (progress->Update(100, "Save failed"))
                {
                        // Wait for the user to click "Close"
@@ -675,17 +843,18 @@ private:
                goto out;
 
        abort:
-               SetStatusText("Save Aborted");
-               std::clog << "Save Aborted" << std::endl;
+               mmLogStatus("Save Aborted");
 
        out:
-               (void) true; // empty statement.
+               return;
        }
 
-       void OnExit(wxCommandEvent& event)
+       // Note: Don't confuse this with the wxApp::OnExit virtual method
+       void OnExitEvt(wxCommandEvent& event)
        {
                Close(true);
        }
+
        void OnAbout(wxCommandEvent& event)
        {
                wxMessageBox(
@@ -714,7 +883,8 @@ private:
 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
        EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
        EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
-       EVT_MENU(wxID_EXIT, AppFrame::OnExit)
+       EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
+       EVT_MENU(wxID_EXIT, AppFrame::OnExitEvt)
        EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
 
        EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)