diff --git a/CHANGELOG.md b/CHANGELOG.md index 3365295..88f98ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Change log * 🎁 Add "Update" / "Help" menu entries * 🎁 Add "Find ICAO airport codes" menu entries +* 🎁 Add marker for unsaved changes 1.1.0 ----- diff --git a/README.md b/README.md index 696e34c..7a314a2 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ _Note:_ The desktop application requires you to actually hit the "Fetch" button **Important:** Be sure to quit the application before starting Aerofly FS 2 to not accidentally change values while AFS2 is running and to free up memory. Also you may want to backup your `main.mcf` in case something goes wrong. +For troubleshooting look into the [Frequently Asked Questions](docs/faq.md). + HTTP services ------------- diff --git a/aerofly-wettergeraet.sln b/aerofly-wettergeraet.sln index d422891..413d7c9 100644 --- a/aerofly-wettergeraet.sln +++ b/aerofly-wettergeraet.sln @@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution CHANGELOG.md = CHANGELOG.md docs\configuration.md = docs\configuration.md CONTRIBUTING.md = CONTRIBUTING.md + docs\faq.md = docs\faq.md docs\favicon.ico = docs\favicon.ico docs\issue_template.md = docs\issue_template.md LICENSE.txt = LICENSE.txt diff --git a/docs/README.md b/docs/README.md index ee4e0c6..aa87195 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,7 @@ ![](./favicon-64x64.png) Documentation ====================================== +1. [Frequently Asked Questions](faq.md) 1. [Advanced configuration](configuration.md) 1. [A small guide to METAR information](metar.md) 1. [How these tools interact with Aerofly'S `main.mcf`](aerofly-config.md) diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..7f6cc34 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,16 @@ +![](./favicon-64x64.png) Frequently Asked Questions +====================================== + +Why does the application crash on startup? +------------------------------------------ + +Make sure to check these settings: + +* A `main.mcf` has to be located at `%USERPROFILE%\Documents\Aerofly FS2\main.mcf`. If this is not the case point the tool to the file location by setting the `--file ` parameter. +* An internet connection to the [AVWX REST API](http://avwx.rest/) is required. If there is an internet connection but AVWX is not reachable, start the tool with the `--url ` parameter set to a different METAR REST API. + +If these conditions are met but you are still experiencing crashes, please submit an [issue description](https://github.com/fboes/aerofly-wettergeraet/issues), we'll look into it. + +--- + +Return to [table of contents](README.md). diff --git a/src/WettergeraetDesktop/Frame.cpp b/src/WettergeraetDesktop/Frame.cpp index 57ee97d..153d9bc 100644 --- a/src/WettergeraetDesktop/Frame.cpp +++ b/src/WettergeraetDesktop/Frame.cpp @@ -6,11 +6,15 @@ #include #include #include +#include #include #include "Frame.h" #include "Argumentor.h" #include "AeroflyConfigFile.h" +const char* Frame::EL_BUTTON_SAVE_LABEL = "Save main.mcf"; +const char* Frame::EL_BUTTON_SAVE_LABEL_DIRTY = "Save main.mcf \u2022"; + Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxID_ANY, title, wxPoint(-1, -1), wxSize(640, 480)) { this->utcDateValue.SetToCurrent(); @@ -82,7 +86,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, hbox3->Add(utcTimeLabel, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, labelBorder); wxDateTime utcDateValue = wxDateTime::Now(); - this->utcTimeInput = new wxTimePickerCtrl(panel, wxID_ANY, utcDateValue); + this->utcTimeInput = new wxTimePickerCtrl(panel, Frame::EL_CTRL_DATETIME, utcDateValue); hbox3->Add(this->utcTimeInput, 1, wxALIGN_CENTER_VERTICAL); hbox3->Add(10, -1); @@ -90,7 +94,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *utcDateLabel = new wxStaticText(panel, wxID_ANY, wxT("Date (UTC)")); hbox3->Add(utcDateLabel, 1, wxALIGN_CENTER_VERTICAL, labelBorder); - this->utcDateInput = new wxDatePickerCtrl(panel, wxID_ANY, utcDateValue); + this->utcDateInput = new wxDatePickerCtrl(panel, Frame::EL_CTRL_DATETIME, utcDateValue); hbox3->Add(this->utcDateInput, 1, wxLEFT | wxALIGN_CENTER_VERTICAL); } vbox->Add(hbox3, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); @@ -115,7 +119,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *clouds0HeightLabel = new wxStaticText(panel, wxID_ANY, cloudName + " height (%)"); hbox8->Add(clouds0HeightLabel, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, labelBorder); - this->clouds[i].heightInput = new wxSlider(panel, wxID_ANY, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); + this->clouds[i].heightInput = new wxSlider(panel, Frame::EL_CTRL_SLIDER, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); hbox8->Add(this->clouds[i].heightInput, 1, wxALIGN_CENTER_VERTICAL); hbox8->Add(10, -1); @@ -123,7 +127,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *clouds0DensityLabel = new wxStaticText(panel, wxID_ANY, cloudName + " density (%)"); hbox8->Add(clouds0DensityLabel, 1, wxALIGN_CENTER_VERTICAL, labelBorder); - this->clouds[i].densityInput = new wxSlider(panel, wxID_ANY, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); + this->clouds[i].densityInput = new wxSlider(panel, Frame::EL_CTRL_SLIDER, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); hbox8->Add(this->clouds[i].densityInput, 1, wxLEFT | wxALIGN_CENTER_VERTICAL); } vbox->Add(hbox8, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); @@ -136,7 +140,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *windDirectionLabel = new wxStaticText(panel, wxID_ANY, wxT("Wind direction (\u00B0)")); hbox5->Add(windDirectionLabel, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, labelBorder); - this->windDirectionInput = new wxSlider(panel, wxID_ANY, 180, 0, 359, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); + this->windDirectionInput = new wxSlider(panel, Frame::EL_CTRL_SLIDER, 180, 0, 359, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); hbox5->Add(this->windDirectionInput, 1, wxALIGN_CENTER_VERTICAL); hbox5->Add(10, -1); @@ -144,7 +148,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *windStrengthLabel = new wxStaticText(panel, wxID_ANY, wxT("Wind strength (%)")); hbox5->Add(windStrengthLabel, 1, wxALIGN_CENTER_VERTICAL, labelBorder); - this->windStrengthInput = new wxSlider(panel, wxID_ANY, 50, 0, 200, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); + this->windStrengthInput = new wxSlider(panel, Frame::EL_CTRL_SLIDER, 50, 0, 200, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); hbox5->Add(this->windStrengthInput, 1, wxLEFT | wxALIGN_CENTER_VERTICAL); } vbox->Add(hbox5, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); @@ -156,7 +160,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *windTurbulenceLabel = new wxStaticText(panel, wxID_ANY, wxT("Turbulence (%)")); hbox6->Add(windTurbulenceLabel, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, labelBorder); - this->windTurbulenceInput = new wxSlider(panel, wxID_ANY, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); + this->windTurbulenceInput = new wxSlider(panel, Frame::EL_CTRL_SLIDER, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); hbox6->Add(this->windTurbulenceInput, 1, wxALIGN_CENTER_VERTICAL); hbox6->Add(10, -1); @@ -164,7 +168,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *thermalActivityLabel = new wxStaticText(panel, wxID_ANY, wxT("Thermal activity (%)")); hbox6->Add(thermalActivityLabel, 1, wxALIGN_CENTER_VERTICAL, labelBorder); - this->thermalActivityInput = new wxSlider(panel, wxID_ANY, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); + this->thermalActivityInput = new wxSlider(panel, Frame::EL_CTRL_SLIDER, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); hbox6->Add(this->thermalActivityInput, 1, wxLEFT | wxALIGN_CENTER_VERTICAL); } vbox->Add(hbox6, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); @@ -176,7 +180,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *visbilityLabel = new wxStaticText(panel, wxID_ANY, wxT("Visibility (%)")); hbox7->Add(visbilityLabel, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, labelBorder); - this->visbilityInput = new wxSlider(panel, wxID_ANY, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); + this->visbilityInput = new wxSlider(panel, Frame::EL_CTRL_SLIDER, 50, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL | wxSL_VALUE_LABEL); hbox7->Add(this->visbilityInput, 1, wxALIGN_CENTER_VERTICAL); hbox7->Add(10, -1); @@ -184,7 +188,7 @@ Frame::Frame(const wxString& title, int argc, char * argv[]) : wxFrame(nullptr, wxStaticText *noLabel = new wxStaticText(panel, wxID_ANY, wxT("")); hbox7->Add(noLabel, 1, wxLEFT | wxALIGN_CENTER_VERTICAL); - this->saveButton = new wxButton(panel, wxID_SAVE, wxT("Save main.mcf"), wxPoint(5, 5)); + this->saveButton = new wxButton(panel, wxID_SAVE, this->EL_BUTTON_SAVE_LABEL_DIRTY, wxPoint(5, 5)); hbox7->Add(this->saveButton, 1, wxLEFT | wxALIGN_CENTER_VERTICAL); } vbox->Add(hbox7, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); @@ -257,23 +261,40 @@ void Frame::fromInputToObject() } } +void Frame::markAsClean() +{ + this->saveButton->SetLabel(this->EL_BUTTON_SAVE_LABEL); +} + +void Frame::markAsDirty() +{ + this->saveButton->SetLabel(this->EL_BUTTON_SAVE_LABEL_DIRTY); +} + void Frame::loadMainMcf() { - this->mainConfig.setFilename(this->argumentor.filename); - this->mainConfig.load(); - this->mainConfig.getToAeroflyObject(this->aerofly); - this->utcDateValue.SetToCurrent(); - this->metarInput->SetValue(""); - std::string origin; - std::string destination; - std::tie(origin, destination) = this->mainConfig.getFlightplan(); - if (origin != "") { - strcpy(this->aerofly.nearestAirport, origin.c_str()); + try { + this->mainConfig.setFilename(this->argumentor.filename); + this->mainConfig.load(); + this->mainConfig.getToAeroflyObject(this->aerofly); + this->utcDateValue.SetToCurrent(); + this->metarInput->SetValue(""); + std::string origin; + std::string destination; + std::tie(origin, destination) = this->mainConfig.getFlightplan(); + if (origin != "") { + strcpy(this->aerofly.nearestAirport, origin.c_str()); + } + else { + strcpy(this->aerofly.nearestAirport, this->argumentor.icaoCode); + } + this->fromObjectToInput(); + this->markAsClean(); } - else { - strcpy(this->aerofly.nearestAirport, this->argumentor.icaoCode); + catch (std::invalid_argument& e) { + this->metarInput->SetValue(e.what()); + return; } - this->fromObjectToInput(); } // -------------------------------------------------------------------------------------- @@ -291,6 +312,7 @@ void Frame::actionFetch(wxCommandEvent& WXUNUSED(event)) auto metarString = urlFetcher.fetch(this->argumentor.url, icaoCode, this->argumentor.response, this->argumentor.apikey); this->metarInput->SetValue(metarString); this->saveButton->SetFocus(); + this->markAsDirty(); } catch (std::invalid_argument& e) { this->metarInput->SetValue(e.what()); @@ -315,10 +337,17 @@ void Frame::actionParse(wxCommandEvent& WXUNUSED(event)) void Frame::actionSave(wxCommandEvent& WXUNUSED(event)) { if (!this->argumentor.isDryRun) { - this->fromInputToObject(); - this->mainConfig.setFromAeroflyObject(this->aerofly); - this->mainConfig.save(); + try { + this->fromInputToObject(); + this->mainConfig.setFromAeroflyObject(this->aerofly); + this->mainConfig.save(); + } + catch (std::invalid_argument& e) { + this->metarInput->SetValue(e.what()); + return; + } } + this->markAsClean(); } void Frame::actionExit(wxCommandEvent& WXUNUSED(event)) @@ -361,14 +390,21 @@ void Frame::actionFindIcao(wxCommandEvent& WXUNUSED(event)) wxLaunchDefaultBrowser("https://www.world-airport-codes.com/"); } +void Frame::actionMarkAsDirty(wxCommandEvent& WXUNUSED(event)) +{ + this->markAsDirty(); +} + wxBEGIN_EVENT_TABLE(Frame, wxFrame) EVT_BUTTON(Frame::EL_BUTTON_FETCH, Frame::actionFetch) EVT_BUTTON(wxID_SAVE, Frame::actionSave) EVT_MENU(wxID_HELP, Frame::actionHelp) -EVT_MENU(EL_MENU_UPDATE, Frame::actionUpdate) -EVT_MENU(EL_MENU_FIND_ICAO, Frame::actionFindIcao) +EVT_MENU(Frame::EL_MENU_UPDATE, Frame::actionUpdate) +EVT_MENU(Frame::EL_MENU_FIND_ICAO, Frame::actionFindIcao) EVT_MENU(wxID_ABOUT, Frame::actionAbout) EVT_MENU(wxID_EXIT, Frame::actionExit) EVT_MENU(wxID_OPEN, Frame::actionLoadMainMcf) EVT_TEXT(Frame::EL_CTRL_METAR, Frame::actionParse) +EVT_SLIDER(Frame::EL_CTRL_SLIDER, Frame::actionMarkAsDirty) +//EVT_DATE_CHANGED(Frame::EL_CTRL_DATETIME, Frame::actionMarkAsDirty) wxEND_EVENT_TABLE() diff --git a/src/WettergeraetDesktop/Frame.h b/src/WettergeraetDesktop/Frame.h index 7ef1fc3..defd7ca 100644 --- a/src/WettergeraetDesktop/Frame.h +++ b/src/WettergeraetDesktop/Frame.h @@ -36,6 +36,9 @@ class Frame : public wxFrame cloudsInputs clouds[3]; // "cirrus", "cumulus", "cumulus_mediocris" wxButton *saveButton; + static const char* EL_BUTTON_SAVE_LABEL; + static const char* EL_BUTTON_SAVE_LABEL_DIRTY; + DECLARE_EVENT_TABLE() public: @@ -56,6 +59,12 @@ class Frame : public wxFrame // Copy state of form to objects virtual void fromInputToObject(); + // Show user that current state has been saved. + virtual void markAsClean(); + + // Show user that current state has not yet been saved. + virtual void markAsDirty(); + virtual void loadMainMcf(); // Fetch new METAR data @@ -79,4 +88,6 @@ class Frame : public wxFrame virtual void actionFindIcao(wxCommandEvent&); + virtual void actionMarkAsDirty(wxCommandEvent&); + };