diff --git a/include/ServerMainComponent.hpp b/include/ServerMainComponent.hpp index 1ebff1b..09f92ff 100644 --- a/include/ServerMainComponent.hpp +++ b/include/ServerMainComponent.hpp @@ -11,6 +11,8 @@ class ServerMainComponent : public juce::Component , public isobus::VirtualTerminalServer , public Timer + , public ApplicationCommandTarget + , public MenuBarModel { public: ServerMainComponent(std::shared_ptr serverControlFunction); @@ -32,7 +34,7 @@ class ServerMainComponent : public juce::Component std::uint8_t &numberOfRanges, std::vector &wideCharRangeArray) override; - std::vector get_versions(isobus::NAME clientNAME) override; + std::vector> get_versions(isobus::NAME clientNAME) override; std::vector get_supported_objects() const override; /// @brief This function is called when the client wants the server to load a previously stored object pool. @@ -41,7 +43,7 @@ class ServerMainComponent : public juce::Component /// @param[in] versionLabel The object pool version to load for the given client NAME /// @param[in] clientNAME The client requesting the object pool /// @returns The requested object pool associated with the version label. - virtual std::vector load_version(const std::vector &versionLabel, isobus::NAME clientNAME) override; + std::vector load_version(const std::vector &versionLabel, isobus::NAME clientNAME) override; /// @brief This function is called when the client wants the server to save an object pool /// to the VT's non-volatile memory. @@ -52,35 +54,83 @@ class ServerMainComponent : public juce::Component /// @param[in] versionLabel The object pool version to save for the given client NAME /// @param[in] clientNAME The client requesting the object pool /// @returns The requested object pool associated with the version label. - virtual bool save_version(const std::vector &objectPool, const std::vector &versionLabel, isobus::NAME clientNAME) override; + bool save_version(const std::vector &objectPool, const std::vector &versionLabel, isobus::NAME clientNAME) override; + + /// @brief This function is called when the client wants the server to delete a stored object pool. + /// All object pool files matching the specified version label should then be deleted from the VT's + /// non-volatile storage. + /// @param[in] versionLabel The version label for the object pool(s) to delete + /// @param[in] clientNAME The NAME of the client that is requesting deletion + /// @returns True if the version was deleted from VT non-volatile storage, otherwise false. + bool delete_version(const std::vector &versionLabel, isobus::NAME clientNAME) override; + + /// @brief This function is called when the client wants the server to delete ALL stored object pools associated to it's NAME. + /// All object pool files matching the specified client NAME should then be deleted from the VT's + /// non-volatile storage. + /// @param[in] clientNAME The NAME of the client that is requesting deletion + /// @returns True if all relevant object pools were deleted from VT non-volatile storage, otherwise false. + bool delete_all_versions(isobus::NAME clientNAME) override; void timerCallback() override; void paint(juce::Graphics &g) override; void resized() override; + ApplicationCommandTarget *getNextCommandTarget() override; + void getAllCommands(juce::Array &allCommands) override; + void getCommandInfo(juce::CommandID commandID, ApplicationCommandInfo &result) override; + bool perform(const InvocationInfo &info) override; + StringArray getMenuBarNames() override; + PopupMenu getMenuForIndex(int, const juce::String &) override; + void menuItemSelected(int, int) override; + std::shared_ptr get_client_control_function_for_working_set(std::shared_ptr workingSet) const; + void change_selected_working_set(std::uint8_t index); + + void repaint_on_next_update(); + private: - // Your private member variables go here... + enum class CommandIDs : int + { + NoCommand = 0, /// 0 Is an invalid command ID + About, + ConfigureLanguageCommand, + ConfigureReportedVersion + }; + + class LanguageCommandConfigClosed + { + public: + void operator()(int result) const noexcept; + ServerMainComponent &mParent; + + private: + }; + friend class LanguageCommandConfigClosed; + std::size_t number_of_iop_files_in_directory(std::filesystem::path path); void on_change_active_mask_callback(std::shared_ptr affectedWorkingSet, std::uint16_t workingSet, std::uint16_t newMask); - void on_change_numeric_value_callback(std::shared_ptr affectedWorkingSet, std::uint16_t objectID, std::uint32_t value); - void on_change_string_value_callback(std::shared_ptr affectedWorkingSet, std::uint16_t objectID, std::string value); - void on_change_child_position_callback(std::shared_ptr affectedWorkingSet, std::uint16_t parentObjectID, std::uint16_t objectID, std::uint16_t newX, std::uint16_t newY); void repaint_data_and_soft_key_mask(); + void check_load_settings(); + void save_settings(); const std::string ISO_DATA_PATH = "iso_data"; + juce::ApplicationCommandManager mCommandManager; WorkingSetSelectorComponent workingSetSelector; DataMaskRenderAreaComponent dataMaskRenderer; SoftKeyMaskRenderAreaComponent softKeyMaskRenderer; + MenuBarComponent menuBar; LoggerComponent logger; Viewport loggerViewport; SoundPlayer mSoundPlayer; AudioDeviceManager mAudioDeviceManager; + std::unique_ptr popupMenu; std::uint8_t numberOfPoolsToRender = 0; + VTVersion versionToReport = VTVersion::Version5; + bool needToRepaint = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ServerMainComponent) }; diff --git a/include/WorkingSetSelectorComponent.hpp b/include/WorkingSetSelectorComponent.hpp index 77773e5..19eaeed 100644 --- a/include/WorkingSetSelectorComponent.hpp +++ b/include/WorkingSetSelectorComponent.hpp @@ -16,16 +16,19 @@ #include +class ServerMainComponent; + class WorkingSetSelectorComponent : public Component { public: - WorkingSetSelectorComponent(); + explicit WorkingSetSelectorComponent(ServerMainComponent &server); void add_working_set_to_draw(std::shared_ptr workingSet); void remove_working_set(std::shared_ptr workingSet); void paint(Graphics &g) override; void resized() override; + void mouseUp(const MouseEvent &event) override; void redraw(); @@ -36,6 +39,7 @@ class WorkingSetSelectorComponent : public Component std::vector> childComponents; }; std::vector children; + ServerMainComponent &parentServer; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WorkingSetSelectorComponent); }; diff --git a/src/OutputRectangleComponent.cpp b/src/OutputRectangleComponent.cpp index ab34543..c630f8c 100644 --- a/src/OutputRectangleComponent.cpp +++ b/src/OutputRectangleComponent.cpp @@ -36,9 +36,9 @@ void OutputRectangleComponent::paint(Graphics &g) case isobus::FillAttributes::FillType::FillWithLineColor: { - for (std::uint16_t i = 0; i < get_number_children(); i++) + for (std::uint16_t j = 0; j < get_number_children(); j++) { - auto childLineAttributes = get_object_by_id(get_child_id(i)); + auto childLineAttributes = get_object_by_id(get_child_id(j)); if ((nullptr != childLineAttributes) && (isobus::VirtualTerminalObjectType::LineAttributes == childLineAttributes->get_object_type())) { diff --git a/src/ServerMainComponent.cpp b/src/ServerMainComponent.cpp index 75ce1b3..7c66234 100644 --- a/src/ServerMainComponent.cpp +++ b/src/ServerMainComponent.cpp @@ -15,12 +15,23 @@ ServerMainComponent::ServerMainComponent(std::shared_ptr serverControlFunction) : VirtualTerminalServer(serverControlFunction), + workingSetSelector(*this), dataMaskRenderer(*this), softKeyMaskRenderer(*this) { VirtualTerminalServer::initialize(); - languageCommandInterface.set_country_code("US"); // TODO add a way to change these in the GUI, save to file - languageCommandInterface.set_language_code("en"); + check_load_settings(); + + if (languageCommandInterface.get_country_code().empty()) + { + languageCommandInterface.set_country_code("US"); + } + + if (languageCommandInterface.get_language_code().empty()) + { + languageCommandInterface.set_language_code("en"); + } + languageCommandInterface.initialize(); mAudioDeviceManager.initialise(0, 1, nullptr, true); mAudioDeviceManager.addAudioCallback(&mSoundPlayer); @@ -28,6 +39,10 @@ ServerMainComponent::ServerMainComponent(std::shared_ptr &wideCharRangeArray) +isobus::VirtualTerminalBase::SupportedWideCharsErrorCode ServerMainComponent::get_supported_wide_chars(std::uint8_t, + std::uint16_t, + std::uint16_t, + std::uint8_t &, + std::vector &) { return isobus::VirtualTerminalBase::SupportedWideCharsErrorCode::AnyOtherError; } -std::vector ServerMainComponent::get_versions(isobus::NAME clientNAME) +std::vector> ServerMainComponent::get_versions(isobus::NAME clientNAME) { - return std::vector(); + std::ostringstream nameString; + std::vector> retVal; + nameString << std::hex << std::setfill('0') << std::setw(16) << clientNAME.get_full_name(); + File isoDirectory = (std::filesystem::current_path().string() + "/" + ISO_DATA_PATH + "/" + nameString.str()); + + if (isoDirectory.exists() && isoDirectory.isDirectory()) + { + auto directoryFiles = isoDirectory.findChildFiles(File::TypesOfFileToFind::findFiles, false, "*.iopx"); + + for (auto &file : directoryFiles) + { + std::ifstream iopxFile(file.getFullPathName().toStdString(), std::ios::binary); + + if (iopxFile.is_open()) + { + iopxFile.unsetf(std::ios::skipws); + std::array versionLabel; + iopxFile.read(reinterpret_cast(versionLabel.data()), 7); + + // Only add the version label if it is not already in the list + bool versionAlreadyInList = false; + for (const auto &version : retVal) + { + if (version == versionLabel) + { + versionAlreadyInList = true; + break; + } + } + + if (!versionAlreadyInList) + { + retVal.push_back(versionLabel); + } + } + } + } + else + { + isobus::CANStackLogger::info("[VT Server]: No saved object pool data for client: " + nameString.str()); + } + return retVal; } std::vector ServerMainComponent::get_supported_objects() const @@ -119,7 +176,6 @@ std::vector ServerMainComponent::load_version(const std::vector loadedIOPData; std::vector loadedVersionLabel(7); - std::streampos fileSize; nameString << std::hex << std::setfill('0') << std::setw(16) << clientNAME.get_full_name(); if ((std::filesystem::is_directory(ISO_DATA_PATH + "/" + nameString.str()) || @@ -151,12 +207,8 @@ std::vector ServerMainComponent::load_version(const std::vector(iopxFile), std::istream_iterator()); + loadedIOPData.insert(loadedIOPData.end(), std::istream_iterator(iopxFile), std::istream_iterator()); } } } @@ -203,6 +255,87 @@ bool ServerMainComponent::save_version(const std::vector &objectPo return retVal; } +bool ServerMainComponent::delete_version(const std::vector &versionLabel, isobus::NAME clientNAME) +{ + bool retVal = false; + std::ostringstream nameString; + std::vector loadedVersionLabel(7); + std::vector filesToRemove; + nameString << std::hex << std::setfill('0') << std::setw(16) << clientNAME.get_full_name(); + + if ((std::filesystem::is_directory(ISO_DATA_PATH + "/" + nameString.str()) || + std::filesystem::exists(ISO_DATA_PATH + "/" + nameString.str())) && + (7 == versionLabel.size())) + { + for (const auto &entry : std::filesystem::directory_iterator(ISO_DATA_PATH + "/" + nameString.str())) + { + if (entry.path().has_extension() && entry.path().extension() == ".iopx") + { + std::ifstream iopxFile(entry.path(), std::ios::binary); + + if (iopxFile.is_open()) + { + iopxFile.unsetf(std::ios::skipws); + iopxFile.read(reinterpret_cast(loadedVersionLabel.data()), 7); + + if (7 == loadedVersionLabel.size()) + { + bool versionMatches = true; + for (std::uint8_t i = 0; i < 7; i++) + { + if (loadedVersionLabel.at(i) != versionLabel.at(i)) + { + versionMatches = false; + break; + } + } + + if (versionMatches) + { + iopxFile.close(); + filesToRemove.push_back(entry); + retVal = true; + } + } + } + } + } + + for (const auto &entry : filesToRemove) + { + retVal &= std::filesystem::remove(entry); + } + } + return retVal; +} + +bool ServerMainComponent::delete_all_versions(isobus::NAME clientNAME) +{ + bool retVal = false; + std::ostringstream nameString; + std::vector loadedVersionLabel(7); + std::vector filesToRemove; + nameString << std::hex << std::setfill('0') << std::setw(16) << clientNAME.get_full_name(); + + if ((std::filesystem::is_directory(ISO_DATA_PATH + "/" + nameString.str()) || + std::filesystem::exists(ISO_DATA_PATH + "/" + nameString.str()))) + { + for (const auto &entry : std::filesystem::directory_iterator(ISO_DATA_PATH + "/" + nameString.str())) + { + if (entry.path().has_extension() && entry.path().extension() == ".iopx") + { + filesToRemove.push_back(entry); + } + } + + for (const auto &entry : filesToRemove) + { + retVal &= std::filesystem::remove(entry); + } + } + return retVal; +} + void ServerMainComponent::timerCallback() { // This function is called at the frequency specified by the setFramesPerSecond() call @@ -222,19 +355,8 @@ void ServerMainComponent::timerCallback() workingSetSelector.add_working_set_to_draw(ws); if (isobus::NULL_CAN_ADDRESS == activeWorkingSetMasterAddress) { - activeWorkingSetMasterAddress = ws->get_control_function()->get_address(); - activeWorkingSetDataMaskObjectID = std::static_pointer_cast(ws->get_working_set_object())->get_active_mask(); ws->set_working_set_maintenance_message_timestamp_ms(isobus::SystemTiming::get_timestamp_ms()); - - dataMaskRenderer.on_change_active_mask(ws); - softKeyMaskRenderer.on_change_active_mask(ws); - activeWorkingSet = ws; - ws->save_callback_handle(get_on_repaint_event_dispatcher().add_listener([this](std::shared_ptr) {const MessageManagerLock mmLock; this->dataMaskRenderer.on_change_active_mask(activeWorkingSet); - softKeyMaskRenderer.on_change_active_mask(activeWorkingSet); })); // Todo only repaint if active WS is the one that changed - ws->save_callback_handle(get_on_change_active_mask_event_dispatcher().add_listener([this](std::shared_ptr affectedWorkingSet, std::uint16_t workingSet, std::uint16_t newMask) { this->on_change_active_mask_callback(affectedWorkingSet, workingSet, newMask); })); - ws->save_callback_handle(get_on_change_numeric_value_event_dispatcher().add_listener([this](std::shared_ptr affectedWorkingSet, std::uint16_t objectID, std::uint32_t value) { this->on_change_numeric_value_callback(affectedWorkingSet, objectID, value); })); - ws->save_callback_handle(get_on_change_string_value_event_dispatcher().add_listener([this](std::shared_ptr affectedWorkingSet, std::uint16_t objectID, std::string value) { this->on_change_string_value_callback(affectedWorkingSet, objectID, value); })); - ws->save_callback_handle(get_on_change_child_position_event_dispatcher().add_listener([this](std::shared_ptr affectedWorkingSet, std::uint16_t parentObjectID, std::uint16_t objectID, std::uint16_t newX, std::uint16_t newY) { this->on_change_child_position_callback(affectedWorkingSet, parentObjectID, objectID, newX, newY); })); + change_selected_working_set(0); } } else if (isobus::VirtualTerminalServerManagedWorkingSet::ObjectPoolProcessingThreadState::Fail == ws->get_object_pool_processing_state()) @@ -254,6 +376,12 @@ void ServerMainComponent::timerCallback() { repaint_data_and_soft_key_mask(); dataMaskRenderer.on_change_active_mask(ws); + needToRepaint = false; + } + else if (needToRepaint) + { + repaint_data_and_soft_key_mask(); + needToRepaint = false; } } } @@ -274,20 +402,157 @@ void ServerMainComponent::resized() // If you add any child components, this is where you should // update their positions. auto lMenuBarHeight = juce::LookAndFeel::getDefaultLookAndFeel().getDefaultMenuBarHeight(); + auto lBounds = getLocalBounds(); workingSetSelector.setBounds(0, lMenuBarHeight + 4, 100, 600); dataMaskRenderer.setBounds(100, lMenuBarHeight + 4, 500, 500); softKeyMaskRenderer.setBounds(580, lMenuBarHeight + 4, 100, 480); loggerViewport.setSize(getWidth(), getHeight() - 600); loggerViewport.setTopLeftPosition(0, 600); + menuBar.setBounds(lBounds.removeFromTop(lMenuBarHeight)); +} + +ApplicationCommandTarget *ServerMainComponent::getNextCommandTarget() +{ + return nullptr; +} + +void ServerMainComponent::getAllCommands(juce::Array &allCommands) +{ + allCommands.add(static_cast(CommandIDs::About)); + allCommands.add(static_cast(CommandIDs::ConfigureLanguageCommand)); + allCommands.add(static_cast(CommandIDs::ConfigureReportedVersion)); +} + +void ServerMainComponent::getCommandInfo(juce::CommandID commandID, ApplicationCommandInfo &result) +{ + switch (static_cast(commandID)) + { + case CommandIDs::About: + { + result.setInfo("About", "Displays information about this application", "About", 0); + } + break; + + case CommandIDs::ConfigureLanguageCommand: + { + result.setInfo("Language Command", "Change the commanded language, units or country code", "Configure", 0); + } + break; + + case CommandIDs::ConfigureReportedVersion: + { + result.setInfo("Version", "Change the VT server's reported version", "Configure", 0); + } + break; + + default: + break; + } +} + +bool ServerMainComponent::perform(const InvocationInfo &info) +{ + bool retVal = false; + + switch (info.commandID) + { + case static_cast(CommandIDs::About): + { + retVal = true; + } + break; + + case static_cast(CommandIDs::ConfigureLanguageCommand): + { + retVal = true; + popupMenu = std::make_unique("Configure Language Command", "Use the following options to configure the units, language, and country that the VT will command from its clients.", MessageBoxIconType::NoIcon); + popupMenu->addTextEditor("Language Code", languageCommandInterface.get_language_code(), "Language Code"); + popupMenu->addTextEditor("Country Code", languageCommandInterface.get_country_code(), "Country Code"); + popupMenu->addComboBox("Area Units", { "Metric", "Imperial/US" }, "Area Units"); + popupMenu->addComboBox("Date Format", { "ddmmyyyy", "ddyyyymm", "mmyyyydd", "mmddyyyy", "yyyymmdd", "yyyyddmm" }, "Date Format"); + popupMenu->addComboBox("Decimal Symbol", { "Comma", "Point" }, "Decimal Symbol"); + popupMenu->addComboBox("Distance Units", { "Metric", "Imperial/US" }, "Distance Units"); + popupMenu->addComboBox("Force Units", { "Metric", "Imperial/US" }, "Force Units"); + popupMenu->addComboBox("Generic Units", { "Metric", "Imperial", "US" }, "Generic Units"); + popupMenu->addComboBox("Mass Units", { "Metric", "Imperial", "US" }, "Mass Units"); + popupMenu->addComboBox("Pressure Units", { "Metric", "Imperial/US" }, "Pressure Units"); + popupMenu->addComboBox("Temperature Units", { "Metric", "Imperial/US" }, "Temperature Units"); + popupMenu->addComboBox("Time Format", { "24 hour", "12 hour" }, "Time Format"); + popupMenu->addComboBox("Volume Units", { "Metric", "Imperial", "US" }, "Volume Units "); + popupMenu->getComboBoxComponent("Area Units")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_area_units())); + popupMenu->getComboBoxComponent("Date Format")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_date_format())); + popupMenu->getComboBoxComponent("Decimal Symbol")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_decimal_symbol())); + popupMenu->getComboBoxComponent("Distance Units")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_distance_units())); + popupMenu->getComboBoxComponent("Force Units")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_force_units())); + popupMenu->getComboBoxComponent("Generic Units")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_generic_units())); + popupMenu->getComboBoxComponent("Mass Units")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_mass_units())); + popupMenu->getComboBoxComponent("Pressure Units")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_pressure_units())); + popupMenu->getComboBoxComponent("Temperature Units")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_temperature_units())); + popupMenu->getComboBoxComponent("Time Format")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_time_format())); + popupMenu->getComboBoxComponent("Volume Units")->setSelectedItemIndex(static_cast(languageCommandInterface.get_commanded_volume_units())); + popupMenu->addButton("OK", 1, KeyPress(KeyPress::returnKey, 0, 0)); + popupMenu->addButton("Cancel", 0, KeyPress(KeyPress::escapeKey, 0, 0)); + popupMenu->enterModalState(true, ModalCallbackFunction::create(LanguageCommandConfigClosed{ *this })); + } + break; + + case static_cast(CommandIDs::ConfigureReportedVersion): + { + popupMenu = std::make_unique("Configure Reported VT Server Version", "You can use this setting to change the version of the ISO11783-6 standard that this server will claim to support in its status messages.", MessageBoxIconType::NoIcon); + popupMenu->addComboBox("Version", { "Version 2 or Older", "Version 3", "Version 4", "Version 5", "Version 6" }); + popupMenu->addButton("OK", 2, KeyPress(KeyPress::returnKey, 0, 0)); + popupMenu->addButton("Cancel", 0, KeyPress(KeyPress::escapeKey, 0, 0)); + popupMenu->enterModalState(true, ModalCallbackFunction::create(LanguageCommandConfigClosed{ *this })); + retVal = true; + } + break; + + default: + break; + } + return retVal; +} + +StringArray ServerMainComponent::getMenuBarNames() +{ + return juce::StringArray("Configure", "About"); +} + +PopupMenu ServerMainComponent::getMenuForIndex(int index, const juce::String &) +{ + juce::PopupMenu retVal; + + switch (index) + { + case 0: + { + retVal.addCommandItem(&mCommandManager, static_cast(CommandIDs::ConfigureLanguageCommand)); + retVal.addCommandItem(&mCommandManager, static_cast(CommandIDs::ConfigureReportedVersion)); + } + break; + + case 1: + { + retVal.addCommandItem(&mCommandManager, static_cast(CommandIDs::About)); + } + break; + + default: + break; + } + return retVal; +} + +void ServerMainComponent::menuItemSelected(int, int) +{ + // Do nothing } std::shared_ptr ServerMainComponent::get_client_control_function_for_working_set(std::shared_ptr workingSet) const { std::shared_ptr retVal = nullptr; - uint64_t test = 0; - for (const auto &ws : managedWorkingSetList) { if (workingSet == ws) @@ -299,6 +564,61 @@ std::shared_ptr ServerMainComponent::get_client_control return retVal; } +void ServerMainComponent::change_selected_working_set(std::uint8_t index) +{ + if (index < managedWorkingSetList.size()) + { + for (auto &ws : managedWorkingSetList) + { + ws->clear_callback_handles(); + } + auto &ws = managedWorkingSetList.at(index); + activeWorkingSetMasterAddress = ws->get_control_function()->get_address(); + activeWorkingSetDataMaskObjectID = std::static_pointer_cast(ws->get_working_set_object())->get_active_mask(); + + dataMaskRenderer.on_change_active_mask(ws); + softKeyMaskRenderer.on_change_active_mask(ws); + activeWorkingSet = ws; + ws->save_callback_handle(get_on_repaint_event_dispatcher().add_listener([this](std::shared_ptr) { this->repaint_on_next_update(); })); + ws->save_callback_handle(get_on_change_active_mask_event_dispatcher().add_listener([this](std::shared_ptr affectedWorkingSet, std::uint16_t workingSet, std::uint16_t newMask) { this->on_change_active_mask_callback(affectedWorkingSet, workingSet, newMask); })); + + if (send_status_message()) + { + statusMessageTimestamp_ms = isobus::SystemTiming::get_timestamp_ms(); + } + else + { + statusMessageTimestamp_ms = 0; + } + } +} + +void ServerMainComponent::repaint_on_next_update() +{ + needToRepaint = true; +} + +void ServerMainComponent::LanguageCommandConfigClosed::operator()(int result) const noexcept +{ + switch (result) + { + case 1: // Save Language Command + case 2: // Save Version + { + mParent.save_settings(); + } + break; + + default: + { + // Cancel. Do nothing + } + break; + } + mParent.exitModalState(result); + mParent.popupMenu.reset(); +} + std::size_t ServerMainComponent::number_of_iop_files_in_directory(std::filesystem::path path) { std::size_t retVal = 0; @@ -313,7 +633,7 @@ std::size_t ServerMainComponent::number_of_iop_files_in_directory(std::filesyste return retVal; } -void ServerMainComponent::on_change_active_mask_callback(std::shared_ptr affectedWorkingSet, std::uint16_t workingSet, std::uint16_t newMask) +void ServerMainComponent::on_change_active_mask_callback(std::shared_ptr affectedWorkingSet, std::uint16_t, std::uint16_t newMask) { if (isobus::VirtualTerminalServerManagedWorkingSet::ObjectPoolProcessingThreadState::Joined == affectedWorkingSet->get_object_pool_processing_state()) { @@ -383,30 +703,110 @@ void ServerMainComponent::on_change_active_mask_callback(std::shared_ptr affectedWorkingSet, std::uint16_t objectID, std::uint32_t value) +void ServerMainComponent::repaint_data_and_soft_key_mask() { - if (isobus::VirtualTerminalServerManagedWorkingSet::ObjectPoolProcessingThreadState::Joined == affectedWorkingSet->get_object_pool_processing_state()) - { - const MessageManagerLock mmLock; - repaint_data_and_soft_key_mask(); - } + dataMaskRenderer.on_change_active_mask(activeWorkingSet); + softKeyMaskRenderer.on_change_active_mask(activeWorkingSet); + workingSetSelector.redraw(); } -void ServerMainComponent::on_change_string_value_callback(std::shared_ptr affectedWorkingSet, std::uint16_t objectID, std::string value) +void ServerMainComponent::check_load_settings() { - const MessageManagerLock mmLock; - repaint_data_and_soft_key_mask(); -} + auto lDefaultSaveLocation = File::getSpecialLocation(File::userApplicationDataDirectory); + String lDataDirectoryPath = (lDefaultSaveLocation.getFullPathName().toStdString() + "/Open-Agriculture"); + File dataDirectory(lDataDirectoryPath); + bool lCanLoadSettings = false; -void ServerMainComponent::on_change_child_position_callback(std::shared_ptr, std::uint16_t, std::uint16_t objectID, std::uint16_t, std::uint16_t) -{ - const MessageManagerLock mmLock; - repaint_data_and_soft_key_mask(); + if (dataDirectory.exists() && dataDirectory.isDirectory()) + { + lCanLoadSettings = true; + } + else + { + auto result = dataDirectory.createDirectory(); + } + + if (lCanLoadSettings) + { + String lFilePath = (lDefaultSaveLocation.getFullPathName().toStdString() + "/Open-Agriculture/" + "vt_settings.xml"); + File settingsFile = File(lFilePath); + + if (settingsFile.existsAsFile()) + { + auto xml(XmlDocument::parse(settingsFile)); + ValueTree settings("Settings"); + + if (nullptr != xml) + { + settings.copyPropertiesAndChildrenFrom(ValueTree::fromXml(*xml), nullptr); + + auto firstChild = settings.getChild(0); + auto secondChild = settings.getChild(1); + languageCommandInterface.set_commanded_area_units(static_cast(int(firstChild.getProperty("AreaUnits")))); + languageCommandInterface.set_commanded_date_format(static_cast(int(firstChild.getProperty("DateFormat")))); + languageCommandInterface.set_commanded_decimal_symbol(static_cast(int(firstChild.getProperty("DecimalSymbol")))); + languageCommandInterface.set_commanded_distance_units(static_cast(int(firstChild.getProperty("DistanceUnits")))); + languageCommandInterface.set_commanded_force_units(static_cast(int(firstChild.getProperty("ForceUnits")))); + languageCommandInterface.set_commanded_generic_units(static_cast(int(firstChild.getProperty("UnitSystem")))); + languageCommandInterface.set_commanded_mass_units(static_cast(int(firstChild.getProperty("MassUnits")))); + languageCommandInterface.set_commanded_pressure_units(static_cast(int(firstChild.getProperty("PressureUnits")))); + languageCommandInterface.set_commanded_temperature_units(static_cast(int(firstChild.getProperty("TemperatureUnits")))); + languageCommandInterface.set_commanded_time_format(static_cast(int(firstChild.getProperty("TimeFormat")))); + languageCommandInterface.set_commanded_volume_units(static_cast(int(firstChild.getProperty("VolumeUnits")))); + languageCommandInterface.set_country_code(String(firstChild.getProperty("CountryCode").toString()).toStdString()); + languageCommandInterface.set_language_code(String(firstChild.getProperty("LanguageCode").toString()).toStdString()); + versionToReport = static_cast(int(secondChild.getProperty("Version"))); + } + } + } } -void ServerMainComponent::repaint_data_and_soft_key_mask() +void ServerMainComponent::save_settings() { - dataMaskRenderer.on_change_active_mask(activeWorkingSet); - softKeyMaskRenderer.on_change_active_mask(activeWorkingSet); - workingSetSelector.redraw(); + auto lDefaultSaveLocation = File::getSpecialLocation(File::userApplicationDataDirectory); + String lDataDirectoryPath = (lDefaultSaveLocation.getFullPathName().toStdString() + "/Open-Agriculture"); + File dataDirectory(lDataDirectoryPath); + bool lCanSaveSettings = false; + + if (dataDirectory.exists() && dataDirectory.isDirectory()) + { + lCanSaveSettings = true; + } + else + { + auto result = dataDirectory.createDirectory(); + lCanSaveSettings = result.wasOk(); + } + + if (lCanSaveSettings) + { + String lFilePath = (lDefaultSaveLocation.getFullPathName().toStdString() + "/Open-Agriculture/" + "vt_settings.xml"); + File settingsFile = File(lFilePath); + ValueTree settings("Settings"); + ValueTree languageCommandSettings("LanguageCommand"); + ValueTree compatibilitySettings("Compatibility"); + + languageCommandSettings.setProperty("AreaUnits", static_cast(languageCommandInterface.get_commanded_area_units()), nullptr); + languageCommandSettings.setProperty("DateFormat", static_cast(languageCommandInterface.get_commanded_date_format()), nullptr); + languageCommandSettings.setProperty("DecimalSymbol", static_cast(languageCommandInterface.get_commanded_decimal_symbol()), nullptr); + languageCommandSettings.setProperty("DistanceUnits", static_cast(languageCommandInterface.get_commanded_distance_units()), nullptr); + languageCommandSettings.setProperty("ForceUnits", static_cast(languageCommandInterface.get_commanded_force_units()), nullptr); + languageCommandSettings.setProperty("UnitSystem", static_cast(languageCommandInterface.get_commanded_generic_units()), nullptr); + languageCommandSettings.setProperty("MassUnits", static_cast(languageCommandInterface.get_commanded_mass_units()), nullptr); + languageCommandSettings.setProperty("PressureUnits", static_cast(languageCommandInterface.get_commanded_pressure_units()), nullptr); + languageCommandSettings.setProperty("TemperatureUnits", static_cast(languageCommandInterface.get_commanded_temperature_units()), nullptr); + languageCommandSettings.setProperty("TimeFormat", static_cast(languageCommandInterface.get_commanded_time_format()), nullptr); + languageCommandSettings.setProperty("VolumeUnits", static_cast(languageCommandInterface.get_commanded_volume_units()), nullptr); + languageCommandSettings.setProperty("CountryCode", String(languageCommandInterface.get_country_code()), nullptr); + languageCommandSettings.setProperty("LanguageCode", String(languageCommandInterface.get_language_code()), nullptr); + compatibilitySettings.setProperty("Version", get_vt_version_byte(versionToReport), nullptr); + settings.appendChild(languageCommandSettings, nullptr); + settings.appendChild(compatibilitySettings, nullptr); + std::unique_ptr xml(settings.createXml()); + + if (nullptr != xml) + { + xml->writeTo(settingsFile); + } + } } diff --git a/src/WorkingSetSelectorComponent.cpp b/src/WorkingSetSelectorComponent.cpp index 40af6ba..cba5ace 100644 --- a/src/WorkingSetSelectorComponent.cpp +++ b/src/WorkingSetSelectorComponent.cpp @@ -5,10 +5,12 @@ *******************************************************************************/ #include "WorkingSetSelectorComponent.hpp" #include "JuceManagedWorkingSetCache.hpp" +#include "ServerMainComponent.hpp" -WorkingSetSelectorComponent::WorkingSetSelectorComponent() +WorkingSetSelectorComponent::WorkingSetSelectorComponent(ServerMainComponent &server) : + parentServer(server) { - setOpaque(true); + setOpaque(false); setBounds(0, 0, 100, 600); } @@ -22,7 +24,7 @@ void WorkingSetSelectorComponent::add_working_set_to_draw(std::shared_ptrget_object_by_id(workingSetObject->get_child_id(i))); children.back().childComponents.push_back(childObject); - childObject->setTopLeftPosition(4 + 15, 10 + 7); + childObject->setTopLeftPosition(4 + 15 + workingSetObject->get_child_x(i), (static_cast(children.size()) - 1) * 80 + 10 + 7 + workingSetObject->get_child_y(i)); addAndMakeVisible(*childObject); } repaint(); @@ -46,10 +48,13 @@ void WorkingSetSelectorComponent::paint(Graphics &g) g.setColour(getLookAndFeel().findColour(ResizableWindow::backgroundColourId)); g.fillAll(); - for (const auto &ws : children) + int numberOfSquares = 0; + + for (auto ws = children.begin(); ws != children.end(); ws++) { g.setColour(getLookAndFeel().findColour(ResizableWindow::backgroundColourId).brighter()); - g.drawRoundedRectangle(8.0, 8.0, 80, 80, 6, 1); + g.drawRoundedRectangle(8.0f, 8.0f + (numberOfSquares * 80), 80, 80, 6, 1); + numberOfSquares++; } } @@ -61,7 +66,8 @@ void WorkingSetSelectorComponent::resized() void WorkingSetSelectorComponent::redraw() { - for (auto& workingSet : children) + int workingSetIndex = 0; + for (auto &workingSet : children) { workingSet.childComponents.clear(); auto workingSetObject = workingSet.workingSet->get_working_set_object(); @@ -69,10 +75,26 @@ void WorkingSetSelectorComponent::redraw() for (std::uint16_t i = 0; i < workingSetObject->get_number_children(); i++) { auto childObject = JuceManagedWorkingSetCache::create_component(workingSet.workingSet, workingSetObject->get_object_by_id(workingSetObject->get_child_id(i))); - children.back().childComponents.push_back(childObject); - childObject->setTopLeftPosition(4 + 15 + workingSetObject->get_child_x(i), 10 + 7 + workingSetObject->get_child_y(i)); + workingSet.childComponents.push_back(childObject); + childObject->setTopLeftPosition(4 + 15 + workingSetObject->get_child_x(i), workingSetIndex * 80 + 10 + 7 + workingSetObject->get_child_y(i)); addAndMakeVisible(*childObject); } + workingSetIndex++; } repaint(); } + +void WorkingSetSelectorComponent::mouseUp(const MouseEvent &event) +{ + auto relativeEvent = event.getEventRelativeTo(this); + + if ((relativeEvent.getMouseDownX() >= 19) && (relativeEvent.getMouseDownX() < 99) && (relativeEvent.getMouseDownY() > 17) && (relativeEvent.getMouseDownY() < 17 + children.size() * 80)) + { + int workingSetIndex = (relativeEvent.getMouseDownY() - 17) / 80; + + if (workingSetIndex <= 255) + { + parentServer.change_selected_working_set(static_cast(workingSetIndex)); + } + } +}