From 9830a0d15cf7ed76acc51a293d788136670890ea Mon Sep 17 00:00:00 2001 From: Charles Lechasseur Date: Sat, 18 Jan 2020 19:47:06 -0500 Subject: [PATCH] #92 : Settings UI can now display / handle base custom commands --- PathCopyCopy/plugins/prihdr/PipelinePlugin.h | 8 ++- PathCopyCopy/plugins/src/PipelinePlugin.cpp | 35 +++++++--- PathCopyCopy/prihdr/PluginPipeline.h | 20 ++++-- PathCopyCopy/prihdr/PluginPipelineDecoder.h | 21 ------ PathCopyCopy/prihdr/PluginPipelineElements.h | 4 +- PathCopyCopy/rsrc/PathCopyCopy.rc | 5 ++ PathCopyCopy/rsrc/resource.h | 3 + PathCopyCopy/src/PluginPipeline.cpp | 44 +++++++++---- PathCopyCopy/src/PluginPipelineDecoder.cpp | 66 +++---------------- PathCopyCopy/src/PluginPipelineElements.cpp | 31 +++++---- .../Core/Plugins/PipelinePlugins.cs | 3 +- .../UI/Forms/PipelinePluginForm.cs | 40 ++++++++--- .../PipelineElementWithPluginIDUserControl.cs | 26 +++++++- .../UI/Utils/PipelinePluginEditor.cs | 8 ++- 14 files changed, 180 insertions(+), 134 deletions(-) diff --git a/PathCopyCopy/plugins/prihdr/PipelinePlugin.h b/PathCopyCopy/plugins/prihdr/PipelinePlugin.h index be6bf15..599bb76 100644 --- a/PathCopyCopy/plugins/prihdr/PipelinePlugin.h +++ b/PathCopyCopy/plugins/prihdr/PipelinePlugin.h @@ -24,6 +24,8 @@ #include #include +#include + namespace PCC { @@ -47,6 +49,7 @@ namespace PCC PipelinePlugin& operator=(const PipelinePlugin&) = delete; const Pipeline* GetPipeline(GUIDS* p_psSeenPluginIds = nullptr) const; + std::string GetPipelineError() const; const GUID& Id() const noexcept(false) override; @@ -69,8 +72,9 @@ namespace PCC const std::wstring m_IconFile; // Plugin icon file. const bool m_UseDefaultIcon; // Whether to use default icon for plugin. const std::wstring m_EncodedElements; // Pipeline encoded elements. - mutable PipelineSP m_spPipeline; // Pipeline to execute on each path received. - mutable bool m_PipelineCreated; // Whether m_spPipeline has been created. + mutable std::optional + m_spPipeline; // Pipeline to execute on each path received. + mutable std::string m_PipelineError; // If pipeline failed to load, can contain the error message. }; } // namespace Plugins diff --git a/PathCopyCopy/plugins/src/PipelinePlugin.cpp b/PathCopyCopy/plugins/src/PipelinePlugin.cpp index 220269d..6249ad5 100644 --- a/PathCopyCopy/plugins/src/PipelinePlugin.cpp +++ b/PathCopyCopy/plugins/src/PipelinePlugin.cpp @@ -53,7 +53,7 @@ namespace PCC m_UseDefaultIcon(p_UseDefaultIcon), m_EncodedElements(p_EncodedElements), m_spPipeline(), - m_PipelineCreated(false) + m_PipelineError() { } @@ -68,21 +68,38 @@ namespace PCC // const Pipeline* PipelinePlugin::GetPipeline(GUIDS* const p_psSeenPluginIds /*= nullptr*/) const { - if (!m_PipelineCreated) { + if (!m_spPipeline.has_value()) { try { - m_spPipeline = std::make_shared(m_EncodedElements); + PipelineSP spPipeline = std::make_shared(m_EncodedElements); // Make sure pipeline is valid. GUIDS sSeenPluginIds; GUIDS& rsSeenPluginIds = p_psSeenPluginIds != nullptr ? *p_psSeenPluginIds : sSeenPluginIds; - if (!rsSeenPluginIds.emplace(Id()).second || !m_spPipeline->Valid(m_pPluginProvider, rsSeenPluginIds)) { - m_spPipeline = nullptr; + if (!rsSeenPluginIds.emplace(Id()).second) { + throw InvalidPipelineException(ATL::CStringA(MAKEINTRESOURCEA(IDS_INVALIDPIPELINE_LOOP_DETECTED))); } - } catch (const InvalidPipelineException&) { + spPipeline->Validate(m_pPluginProvider, rsSeenPluginIds); + + m_spPipeline = spPipeline; + m_PipelineError.clear(); + } catch (const std::exception& e) { + m_spPipeline = std::make_optional(nullptr); + m_PipelineError = e.what(); } - m_PipelineCreated = true; } - return m_spPipeline.get(); + return m_spPipeline.value().get(); + } + + // + // Returns any error message retrieved when this plugin's pipeline + // has been loaded. Call this if GetPipeline returns nullptr to + // perhaps get more information. + // + // @return Error message. May be empty even if pipeline failed to load. + // + std::string PipelinePlugin::GetPipelineError() const + { + return m_PipelineError; } // @@ -154,6 +171,8 @@ namespace PCC const Pipeline* pPipeline = GetPipeline(); if (pPipeline != nullptr) { pPipeline->ModifyPath(modifiedPath, m_pPluginProvider); + } else if (!m_PipelineError.empty()) { + modifiedPath = ATL::CStringW(m_PipelineError.c_str()); } return modifiedPath; } diff --git a/PathCopyCopy/prihdr/PluginPipeline.h b/PathCopyCopy/prihdr/PluginPipeline.h index 3c95f03..9fa5ee6 100644 --- a/PathCopyCopy/prihdr/PluginPipeline.h +++ b/PathCopyCopy/prihdr/PluginPipeline.h @@ -24,6 +24,7 @@ #include "PathCopyCopyPrivateTypes.h" #include "PluginProvider.h" +#include #include @@ -70,8 +71,8 @@ namespace PCC Pipeline(const Pipeline&) = delete; Pipeline& operator=(const Pipeline&) = delete; - bool Valid(const PluginProvider* p_pPluginProvider, - GUIDS& p_rsSeenPluginIds) const; + void Validate(const PluginProvider* p_pPluginProvider, + GUIDS& p_rsSeenPluginIds) const; void ModifyPath(std::wstring& p_rPath, const PluginProvider* p_pPluginProvider) const; @@ -100,8 +101,8 @@ namespace PCC PipelineElement& operator=(PipelineElement&&) = delete; virtual ~PipelineElement() = default; - virtual bool Valid(const PluginProvider* p_pPluginProvider, - GUIDS& p_rsSeenPluginIds) const noexcept(false); + virtual void Validate(const PluginProvider* p_pPluginProvider, + GUIDS& p_rsSeenPluginIds) const noexcept(false); virtual void ModifyPath(std::wstring& p_rPath, const PluginProvider* p_pPluginProvider) const = 0; @@ -111,4 +112,15 @@ namespace PCC const PluginProvider* p_pPluginProvider) const noexcept(false); }; + // + // Exception type thrown when a pipeline is invalid. + // + class InvalidPipelineException : public std::runtime_error + { + public: + [[gsl::suppress(f.6)]] + InvalidPipelineException(); + explicit InvalidPipelineException(const char* p_pWhat); + }; + } // namespace PCC diff --git a/PathCopyCopy/prihdr/PluginPipelineDecoder.h b/PathCopyCopy/prihdr/PluginPipelineDecoder.h index ce2d0b2..44f71d5 100644 --- a/PathCopyCopy/prihdr/PluginPipelineDecoder.h +++ b/PathCopyCopy/prihdr/PluginPipelineDecoder.h @@ -23,7 +23,6 @@ #include "PathCopyCopyPrivateTypes.h" -#include #include @@ -56,8 +55,6 @@ namespace PCC EncodedElementsStream& operator=(const EncodedElementsStream&) = delete; - auto GetEncodedElements() const noexcept -> const std::wstring&; - auto ReadData(std::wstring::size_type p_DataSize) -> std::wstring; auto ReadElementCount() -> size_t; auto ReadLong() -> long; @@ -82,22 +79,4 @@ namespace PCC EncodedElementsStream& p_rStream) -> PipelineElementSP; }; - // - // Exception type thrown when a encoded pipeline string is invalid. - // - class InvalidPipelineException : public std::exception - { - public: - InvalidPipelineException() noexcept; - explicit InvalidPipelineException(const std::wstring& p_EncodedElements); - - const std::wstring& - EncodedElements() const noexcept; - - const char* what() const noexcept override; - - private: - std::wstring m_EncodedElements; // The pipeline's encoded string. - }; - } // namespace PCC diff --git a/PathCopyCopy/prihdr/PluginPipelineElements.h b/PathCopyCopy/prihdr/PluginPipelineElements.h index 4025038..adecd20 100644 --- a/PathCopyCopy/prihdr/PluginPipelineElements.h +++ b/PathCopyCopy/prihdr/PluginPipelineElements.h @@ -308,8 +308,8 @@ namespace PCC ApplyPipelinePluginPipelineElement& operator=(const ApplyPipelinePluginPipelineElement&) = delete; - bool Valid(const PluginProvider* p_pPluginProvider, - GUIDS& p_rsSeenPluginIds) const override; + void Validate(const PluginProvider* p_pPluginProvider, + GUIDS& p_rsSeenPluginIds) const override; }; // diff --git a/PathCopyCopy/rsrc/PathCopyCopy.rc b/PathCopyCopy/rsrc/PathCopyCopy.rc index bb6c7db..3d6fafd 100644 --- a/PathCopyCopy/rsrc/PathCopyCopy.rc +++ b/PathCopyCopy/rsrc/PathCopyCopy.rc @@ -193,6 +193,11 @@ BEGIN IDS_MSYS_PATH_PLUGIN_DESCRIPTION "Copy MS&YS/MSYS2 Path" IDS_MSYS_PATH_PLUGIN_HINT "Copies the path of the file/folder to the clipboard in MSYS/MSYS2 format." + IDS_INVALIDPIPELINE "<< Invalid custom command >>" + IDS_INVALIDPIPELINE_LOOP_DETECTED + "<< Loop detected in custom command elements >>" + IDS_INVALIDPIPELINE_POSSIBLE_DOWNGRADE + "<< Invalid custom command element (possible downgrade) >>" END #endif // English (United States) resources diff --git a/PathCopyCopy/rsrc/resource.h b/PathCopyCopy/rsrc/resource.h index 8d1ad18..3672cfa 100644 --- a/PathCopyCopy/rsrc/resource.h +++ b/PathCopyCopy/rsrc/resource.h @@ -54,6 +54,9 @@ #define IDS_SAMBA_PATH_PLUGIN_HINT 148 #define IDS_MSYS_PATH_PLUGIN_DESCRIPTION 149 #define IDS_MSYS_PATH_PLUGIN_HINT 150 +#define IDS_INVALIDPIPELINE 151 +#define IDS_INVALIDPIPELINE_LOOP_DETECTED 152 +#define IDS_INVALIDPIPELINE_POSSIBLE_DOWNGRADE 153 #define IDR_PATHCOPYCOPYCONTEXTMENUEXT 201 #define IDR_PATHCOPYCOPYDATAHANDLER 202 #define IDR_PATHCOPYCOPYCONFIGHELPER 203 diff --git a/PathCopyCopy/src/PluginPipeline.cpp b/PathCopyCopy/src/PluginPipeline.cpp index 406fd92..3b3c8aa 100644 --- a/PathCopyCopy/src/PluginPipeline.cpp +++ b/PathCopyCopy/src/PluginPipeline.cpp @@ -117,19 +117,20 @@ namespace PCC // Validates the pipeline. A pipeline is valid if all its elements // are considered valid. // + // When the pipeline is invalid, an InvalidPipelineException is thrown. + // // @param p_pPluginProvider Optional plugin provider that can be used // during validation. // @param p_rsSeenPluginIds Set that should be used to store seen plugin IDs. // Any collision means a loop is detected and // pipeline should be considered invalid. - // @return true if the pipeline is valid. // - bool Pipeline::Valid(const PluginProvider* const p_pPluginProvider, - GUIDS& p_rsSeenPluginIds) const + void Pipeline::Validate(const PluginProvider* const p_pPluginProvider, + GUIDS& p_rsSeenPluginIds) const { - return std::all_of(m_vspElements.cbegin(), m_vspElements.cend(), [&](const auto& spElement) { - return spElement->Valid(p_pPluginProvider, p_rsSeenPluginIds); - }); + for (const auto& spElement : m_vspElements) { + spElement->Validate(p_pPluginProvider, p_rsSeenPluginIds); + } } @@ -143,7 +144,7 @@ namespace PCC void Pipeline::ModifyPath(std::wstring& p_rPath, const PluginProvider* const p_pPluginProvider) const { - for (const PipelineElementSP& spElement : m_vspElements) { + for (const auto& spElement : m_vspElements) { spElement->ModifyPath(p_rPath, p_pPluginProvider); } } @@ -156,7 +157,7 @@ namespace PCC // void Pipeline::ModifyOptions(PipelineOptions& p_rOptions) const { - for (const PipelineElementSP& spElement : m_vspElements) { + for (const auto& spElement : m_vspElements) { spElement->ModifyOptions(p_rOptions); } } @@ -184,18 +185,19 @@ namespace PCC // is implementation-specific, but should be used to detect recursion // in plugin usage, for instance. // + // When the pipeline element is invalid, an InvalidPipelineException + // should be thrown. + // // @param p_pPluginProvider Optional plugin provider that can be used // during validation. // @param p_rsSeenPluginIds Set that should be used to store seen plugin IDs. // Any collision means a loop is detected and // pipeline element should be considered invalid. - // @return true if pipeline element is valid. // - bool PipelineElement::Valid(const PluginProvider* const /*p_pPluginProvider*/, - GUIDS& /*p_rsSeenPluginIds*/) const noexcept(false) + void PipelineElement::Validate(const PluginProvider* const /*p_pPluginProvider*/, + GUIDS& /*p_rsSeenPluginIds*/) const noexcept(false) { // All pipeline elements are considered valid by default. - return true; } // @@ -224,4 +226,22 @@ namespace PCC return true; } + // + // Default constructor. + // + InvalidPipelineException::InvalidPipelineException() + : std::runtime_error(ATL::CStringA(MAKEINTRESOURCEA(IDS_INVALIDPIPELINE))) + { + } + + // + // Constructor with explanation string. + // + // @param p_pWhat Explanation string. + // + InvalidPipelineException::InvalidPipelineException(const char* const p_pWhat) + : std::runtime_error(p_pWhat) + { + } + } // namespace PCC diff --git a/PathCopyCopy/src/PluginPipelineDecoder.cpp b/PathCopyCopy/src/PluginPipelineDecoder.cpp index 2602619..e468e6f 100644 --- a/PathCopyCopy/src/PluginPipelineDecoder.cpp +++ b/PathCopyCopy/src/PluginPipelineDecoder.cpp @@ -158,12 +158,12 @@ namespace PCC default: // Unknown element type, we can't add it and don't know // how to skip it. Possibly due to a downgrade of PCC? - throw InvalidPipelineException(p_rStream.GetEncodedElements()); + throw InvalidPipelineException(ATL::CStringA(MAKEINTRESOURCEA(IDS_INVALIDPIPELINE_POSSIBLE_DOWNGRADE))); } // Add new element to the pipeline. if (spElement == nullptr) { - throw InvalidPipelineException(p_rStream.GetEncodedElements()); + throw InvalidPipelineException(); } return spElement; } @@ -196,7 +196,7 @@ namespace PCC // Make sure it's a version we can support. if (version > REGEX_ELEMENT_MAX_VERSION) { - throw InvalidPipelineException(p_rStream.GetEncodedElements()); + throw InvalidPipelineException(ATL::CStringA(MAKEINTRESOURCEA(IDS_INVALIDPIPELINE_POSSIBLE_DOWNGRADE))); } // Initial version: regex, format string and whether we should ignore case. @@ -228,7 +228,7 @@ namespace PCC CLSID pluginGuid; if (FAILED(::CLSIDFromString(guidString.c_str(), &pluginGuid))) { // Invalid GUID format. - throw InvalidPipelineException(p_rStream.GetEncodedElements()); + throw InvalidPipelineException(); } // We have the plugin GUID, return it. @@ -237,7 +237,7 @@ namespace PCC } else if (p_Code == ELEMENT_CODE_APPLY_PIPELINE_PLUGIN) { return std::make_shared(pluginGuid); } else { - throw InvalidPipelineException(p_rStream.GetEncodedElements()); + throw InvalidPipelineException(); } } @@ -272,7 +272,7 @@ namespace PCC } else if (p_Code == ELEMENT_CODE_EXECUTABLE_WITH_FILELIST) { return std::make_shared(executable); } else { - throw InvalidPipelineException(p_rStream.GetEncodedElements()); + throw InvalidPipelineException(); } } @@ -287,16 +287,6 @@ namespace PCC { } - // - // Returns the string containing encoded elements backing this stream. - // - // @return String containing all encoded elements. - // - auto PipelineDecoder::EncodedElementsStream::GetEncodedElements() const noexcept -> const std::wstring& - { - return m_EncodedElements; - } - // // Reads a number of characters from the stream. // @@ -306,7 +296,7 @@ namespace PCC auto PipelineDecoder::EncodedElementsStream::ReadData(const std::wstring::size_type p_DataSize) -> std::wstring { if (m_EncodedElements.size() - m_CurIndex < p_DataSize) { - throw InvalidPipelineException(m_EncodedElements); + throw InvalidPipelineException(); } std::wstring data = m_EncodedElements.substr(m_CurIndex, p_DataSize); m_CurIndex += p_DataSize; @@ -372,49 +362,9 @@ namespace PCC // Boolean values are merely encoded as 0 or 1. const wchar_t boolChar = ReadData(1).front(); if (boolChar != L'0' && boolChar != L'1') { - throw InvalidPipelineException(m_EncodedElements); + throw InvalidPipelineException(); } return boolChar == L'1'; } - // - // Default constructor. Does not set the encoded pipeline string. - // - InvalidPipelineException::InvalidPipelineException() noexcept - : std::exception(), - m_EncodedElements() - { - } - - // - // Constructor with encoded pipeline string. - // - // @param p_EncodedElements Encoded pipeline string. - // - InvalidPipelineException::InvalidPipelineException(const std::wstring& p_EncodedElements) - : std::exception(), - m_EncodedElements(p_EncodedElements) - { - } - - // - // Returns a reference to the encoded pipeline string. - // - // @return Encoded pipeline string reference. - // - const std::wstring& InvalidPipelineException::EncodedElements() const noexcept - { - return m_EncodedElements; - } - - // - // Standardized method returning an exception description. - // - // @return Exception description. - // - const char* InvalidPipelineException::what() const noexcept - { - return "PCC::InvalidPipelineException"; - } - } // namespace PCC diff --git a/PathCopyCopy/src/PluginPipelineElements.cpp b/PathCopyCopy/src/PluginPipelineElements.cpp index 5ab9423..b0bf6cb 100644 --- a/PathCopyCopy/src/PluginPipelineElements.cpp +++ b/PathCopyCopy/src/PluginPipelineElements.cpp @@ -355,23 +355,26 @@ namespace PCC // @param p_rsSeenPluginIds Set used to store seen plugin IDs. Any // collision means a loop is detected and // pipeline element is invalid. - // @return true if pipeline element is valid. // - bool ApplyPipelinePluginPipelineElement::Valid(const PluginProvider* const p_pPluginProvider, - GUIDS& p_rsSeenPluginIds) const + void ApplyPipelinePluginPipelineElement::Validate(const PluginProvider* const p_pPluginProvider, + GUIDS& p_rsSeenPluginIds) const { - bool valid = false; - if (p_pPluginProvider != nullptr) { - // Try finding the plugin we need. - const auto spPlugin = p_pPluginProvider->GetPlugin(m_PluginId); - if (spPlugin != nullptr) { - // To be valid, plugin either has to not be a pipeline plugin - // OR it needs to be one and have a valid pipeline. - const auto* const pPipelinePlugin = dynamic_cast(spPlugin.get()); - valid = pPipelinePlugin == nullptr || pPipelinePlugin->GetPipeline(&p_rsSeenPluginIds) != nullptr; - } + if (p_pPluginProvider == nullptr) { + throw InvalidPipelineException(); + } + + // Try finding the plugin we need. + const auto spPlugin = p_pPluginProvider->GetPlugin(m_PluginId); + if (spPlugin == nullptr) { + throw InvalidPipelineException(); + } + + // To be valid, plugin either has to not be a pipeline plugin + // OR it needs to be one and have a valid pipeline. + const auto* const pPipelinePlugin = dynamic_cast(spPlugin.get()); + if (pPipelinePlugin != nullptr && pPipelinePlugin->GetPipeline(&p_rsSeenPluginIds) == nullptr) { + throw InvalidPipelineException(pPipelinePlugin->GetPipelineError().c_str()); } - return valid; } // diff --git a/PathCopyCopySettings/Core/Plugins/PipelinePlugins.cs b/PathCopyCopySettings/Core/Plugins/PipelinePlugins.cs index 6484a9d..61a1658 100644 --- a/PathCopyCopySettings/Core/Plugins/PipelinePlugins.cs +++ b/PathCopyCopySettings/Core/Plugins/PipelinePlugins.cs @@ -2192,7 +2192,8 @@ private static PipelineElement DecodeElement(string encodedElements, ref int cur element = new UnexpandEnvironmentStringsPipelineElement(); break; } - case ApplyPluginPipelineElement.CODE: { + case ApplyPluginPipelineElement.CODE: + case ApplyPipelinePluginPipelineElement.CODE: { element = DecodeApplyPluginElement(elementCode, encodedElements, ref curChar); break; } diff --git a/PathCopyCopySettings/UI/Forms/PipelinePluginForm.cs b/PathCopyCopySettings/UI/Forms/PipelinePluginForm.cs index 8fa4e77..a645815 100644 --- a/PathCopyCopySettings/UI/Forms/PipelinePluginForm.cs +++ b/PathCopyCopySettings/UI/Forms/PipelinePluginForm.cs @@ -24,6 +24,7 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Linq; using System.Windows.Forms; using PathCopyCopy.Settings.Core.Plugins; using PathCopyCopy.Settings.Properties; @@ -113,10 +114,27 @@ public PipelinePluginInfo EditPlugin(IWin32Window owner, PipelinePluginInfo oldI /// Event arguments. private void PipelinePluginForm_Load(object sender, EventArgs e) { - // First load list of plugins to display in the listbox for the base - // plugin. We only load default and COM plugins for this since we - // don't want a pipeline plugin to be based off another (for now at least). - List plugins = PluginsRegistry.GetPluginsInDefaultOrder(Settings, false); + // First load list of plugins in default order for the base plugin. + List pluginsInDefaultOrder = PluginsRegistry.GetPluginsInDefaultOrder(Settings); + + // Create sorted dictionary of all plugins from the list above, to be able to perform lookups. + SortedDictionary dictionaryOfAllPlugins = new SortedDictionary(); + foreach (Plugin plugin in pluginsInDefaultOrder) { + if (!dictionaryOfAllPlugins.ContainsKey(plugin.Id)) { + dictionaryOfAllPlugins.Add(plugin.Id, plugin); + } + } + + // Use UI display order from settings to order the plugins. + // (See MainForm.LoadSettings for some more details on this process) + List uiDisplayOrder = Settings.UIDisplayOrder; + if (uiDisplayOrder == null) { + // No display order, just use all plugins in default order + uiDisplayOrder = pluginsInDefaultOrder.Select(plugin => plugin.Id).ToList(); + } + SortedSet uiDisplayOrderAsSet = new SortedSet(uiDisplayOrder); + List plugins = PluginsRegistry.OrderPluginsToDisplay(dictionaryOfAllPlugins, + uiDisplayOrder, uiDisplayOrderAsSet, pluginsInDefaultOrder); // Add all plugins to the list box. BasePluginLst.Items.AddRange(plugins.ToArray()); @@ -128,9 +146,9 @@ private void PipelinePluginForm_Load(object sender, EventArgs e) // Populate our controls. NameTxt.Text = oldPluginInfo.Description; - PipelineElement element = oldPipeline.Elements.Find(el => el is ApplyPluginPipelineElement); + PipelineElement element = oldPipeline.Elements.Find(el => el is PipelineElementWithPluginID); if (element != null) { - basePluginId = ((ApplyPluginPipelineElement) element).PluginID; + basePluginId = ((PipelineElementWithPluginID) element).PluginID; } if (oldPipeline.Elements.Find(el => el is OptionalQuotesPipelineElement) != null) { QuotesChk.Checked = true; @@ -257,8 +275,14 @@ private void UpdatePluginInfo() pipeline.Elements.Add(new PathsSeparatorPipelineElement(oldPathsSeparator)); } if (BasePluginLst.SelectedIndex != -1) { - pipeline.Elements.Add(new ApplyPluginPipelineElement( - ((Plugin) BasePluginLst.SelectedItem).Id)); + // If user selected a pipeline plugin, we need to use a different + // kind of pipeline element. + Plugin selectedPlugin = (Plugin) BasePluginLst.SelectedItem; + if (selectedPlugin is PipelinePlugin) { + pipeline.Elements.Add(new ApplyPipelinePluginPipelineElement(selectedPlugin.Id)); + } else { + pipeline.Elements.Add(new ApplyPluginPipelineElement(selectedPlugin.Id)); + } } if (UnexpandEnvStringsChk.Checked) { pipeline.Elements.Add(new UnexpandEnvironmentStringsPipelineElement()); diff --git a/PathCopyCopySettings/UI/UserControls/PipelineElementWithPluginIDUserControl.cs b/PathCopyCopySettings/UI/UserControls/PipelineElementWithPluginIDUserControl.cs index b024af9..adca9e7 100644 --- a/PathCopyCopySettings/UI/UserControls/PipelineElementWithPluginIDUserControl.cs +++ b/PathCopyCopySettings/UI/UserControls/PipelineElementWithPluginIDUserControl.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using PathCopyCopy.Settings.Core; using PathCopyCopy.Settings.Core.Plugins; using PathCopyCopy.Settings.Properties; @@ -65,7 +66,30 @@ protected override void OnLoad(EventArgs e) // First load list of plugins to display in the listbox for the base plugin. List plugins; using (UserSettings settings = new UserSettings()) { - plugins = PluginsRegistry.GetPluginsInDefaultOrder(settings, includePipelinePlugins); + List pluginsInDefaultOrder = PluginsRegistry.GetPluginsInDefaultOrder(settings, includePipelinePlugins); + if (!includePipelinePlugins) { + // Sufficient when not using pipeline plugins. + plugins = pluginsInDefaultOrder; + } else { + // Create sorted dictionary of all plugins from the list above, to be able to perform lookups. + SortedDictionary dictionaryOfAllPlugins = new SortedDictionary(); + foreach (Plugin plugin in pluginsInDefaultOrder) { + if (!dictionaryOfAllPlugins.ContainsKey(plugin.Id)) { + dictionaryOfAllPlugins.Add(plugin.Id, plugin); + } + } + + // Use UI display order from settings to order the plugins. + // (See MainForm.LoadSettings for some more details on this process) + List uiDisplayOrder = settings.UIDisplayOrder; + if (uiDisplayOrder == null) { + // No display order, just use all plugins in default order + uiDisplayOrder = pluginsInDefaultOrder.Select(plugin => plugin.Id).ToList(); + } + SortedSet uiDisplayOrderAsSet = new SortedSet(uiDisplayOrder); + plugins = PluginsRegistry.OrderPluginsToDisplay(dictionaryOfAllPlugins, + uiDisplayOrder, uiDisplayOrderAsSet, pluginsInDefaultOrder); + } } // Add all plugins to the list box. diff --git a/PathCopyCopySettings/UI/Utils/PipelinePluginEditor.cs b/PathCopyCopySettings/UI/Utils/PipelinePluginEditor.cs index debe93d..f857f7f 100644 --- a/PathCopyCopySettings/UI/Utils/PipelinePluginEditor.cs +++ b/PathCopyCopySettings/UI/Utils/PipelinePluginEditor.cs @@ -75,10 +75,10 @@ internal static bool IsPipelineSimple(Pipeline pipeline) } // All elements must be of different types, pipeline must not contain any of - // the expert-only types and must contain an ApplyPlugin element. + // the expert-only types and must contain an ApplyPlugin element (of some kind). return pipeline.Elements.Distinct(new PipelineElementEqualityComparerByClassType()).Count() == pipeline.Elements.Count && pipeline.Elements.All(el => IsElementSimple(el)) && - pipeline.Elements.Any(el => el is ApplyPluginPipelineElement); + pipeline.Elements.Any(el => el is PipelineElementWithPluginID); } /// @@ -206,7 +206,9 @@ private Type GetElementType(PipelineElement obj) // Some elements are mutually exclusive, so we'll consider them // the same type so that using Distinct can detect duplicates. Type type = obj.GetType(); - if (obj is OptionalQuotesPipelineElement) { + if (obj is ApplyPipelinePluginPipelineElement) { + type = typeof(ApplyPluginPipelineElement); + } else if (obj is OptionalQuotesPipelineElement) { type = typeof(QuotesPipelineElement); } else if (obj is EncodeURIWhitespacePipelineElement) { type = typeof(EncodeURICharsPipelineElement);