From 9779df4f9512c60357f8e7e6a566cbc52b2dd7cf Mon Sep 17 00:00:00 2001 From: Keith Westley Date: Sat, 6 Jan 2018 23:46:28 +1100 Subject: [PATCH] Add xmodel download from vendor site capability --- include/models/ModelImages.h | 1 + include/models/download.xpm | 54 ++ xLights/CachedFileDownloader.cpp | 318 ++++++ xLights/CachedFileDownloader.h | 65 ++ xLights/LayoutPanel.cpp | 14 +- xLights/SeqFileUtilities.cpp | 8 +- xLights/VendorModelDialog.cpp | 1282 +++++++++++++++++++++++++ xLights/VendorModelDialog.h | 126 +++ xLights/Xlights.vcxproj | 4 + xLights/Xlights.vcxproj.filters | 12 + xLights/models/Model.cpp | 40 +- xLights/models/Model.h | 2 +- xLights/wxsmith/VendorModelDialog.wxs | 234 +++++ xLights/wxsmith/xLightsframe.wxs | 9 + xLights/xLights.cbp | 6 + xLights/xLightsMain.cpp | 34 +- xLights/xLightsMain.h | 11 +- 17 files changed, 2186 insertions(+), 34 deletions(-) create mode 100644 include/models/download.xpm create mode 100644 xLights/CachedFileDownloader.cpp create mode 100644 xLights/CachedFileDownloader.h create mode 100644 xLights/VendorModelDialog.cpp create mode 100644 xLights/VendorModelDialog.h create mode 100644 xLights/wxsmith/VendorModelDialog.wxs diff --git a/include/models/ModelImages.h b/include/models/ModelImages.h index 0b49df5aeb..3604659c9e 100644 --- a/include/models/ModelImages.h +++ b/include/models/ModelImages.h @@ -17,4 +17,5 @@ #include "icicles.xpm" #include "import.xpm" #include "polyline.xpm" +#include "download.xpm" #endif diff --git a/include/models/download.xpm b/include/models/download.xpm new file mode 100644 index 0000000000..09a3c67b3c --- /dev/null +++ b/include/models/download.xpm @@ -0,0 +1,54 @@ +/* XPM */ +static const char *download[] = { +"48 48 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"................................................", +"................................................", +"................................................", +"................................................", +"................................................", +"................+++++++++++++++++...............", +"...............++++++++++++++++++...............", +"...............++++++++++++++++++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"...............+++............+++...............", +"........++++++++++............+++++++++++.......", +"........++++++++++............+++++++++++.......", +"........++++++++++.............++++++++++.......", +".........++++.......................++++........", +"..........++++.....................++++.........", +"...........++++...................++++..........", +"............++++.................++++...........", +".............+++.................++++...........", +".............++++...............++++............", +"..............++++.............++++.............", +"...............++++...........++++..............", +"................++++.........++++...............", +".................++++.......++++................", +"..................+++.......++++................", +"..................++++.....++++.................", +"...................++++...++++..................", +"....................++++.++++...................", +".....................+++++++....................", +"......................+++++.....................", +".......................++++.....................", +".......................+++......................", +"................................................", +"................................................", +"................................................", +"................................................"}; diff --git a/xLights/CachedFileDownloader.cpp b/xLights/CachedFileDownloader.cpp new file mode 100644 index 0000000000..577baf9d7d --- /dev/null +++ b/xLights/CachedFileDownloader.cpp @@ -0,0 +1,318 @@ +#include "CachedFileDownloader.h" + +#include +#include +#include +#include +#include +#include + +#define LONGCACHEDAYS 5 + +FileCacheItem::FileCacheItem(wxXmlNode* n) +{ + _url = n->GetAttribute("URI", ""); + _fileName = n->GetAttribute("FileName", ""); + _cacheFor = (CACHEFOR)wxAtoi(n->GetAttribute("CacheFor", "0")); +} + +FileCacheItem::FileCacheItem(wxURI url, CACHEFOR cacheFor) +{ + _url = url; + _cacheFor = cacheFor; + Download(); +} + +void FileCacheItem::Save(wxFile& f) +{ + f.Write(" \n"); +} + +void FileCacheItem::Download() +{ + _fileName = DownloadURLToTemp(_url); +} + +// A major constraint of this function is that it does not support https +bool FileCacheItem::DownloadURL(wxURI url, wxFileName filename) const +{ + bool ok = true; + + static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); + + if (url.GetScheme() == "https") + { + logger_base.warn("Unable to retrieve '%s' as xLights cannot access https pages.", (const char *)url.GetPath().c_str()); + } + + wxHTTP http; + http.SetMethod("GET"); + int port = 80; + if (wxAtoi(url.GetPort()) != 0) + { + port = wxAtoi(url.GetPort()); + } + bool connected = http.Connect(url.GetServer(), port); + + if (connected) + { + logger_base.debug("Making request to '%s'.", (const char *)url.BuildURI().c_str()); + wxStopWatch sw; + wxInputStream *httpStream = http.GetInputStream(url.GetPath()); + + if (http.GetError() == wxPROTO_NOERR) + { + logger_base.debug(" Result %d.", http.GetResponse()); + + if (http.GetResponse() >= 300 && http.GetResponse() < 400) + { + wxDELETE(httpStream); + + wxURI redir = http.GetHeader("LOCATION"); + if (redir.GetPath() == url.GetPath()) + { + return false; + } + return DownloadURL(redir, filename); + } + + wxFile f; + long size = 0; + if (f.Open(filename.GetFullPath(), wxFile::write)) + { + wxByte buffer[65536]; + while (!httpStream->Eof() && httpStream->CanRead()) + { + httpStream->Read(buffer, sizeof(buffer)); + f.Write(buffer, httpStream->LastRead()); + size += httpStream->LastRead(); + } + f.Close(); + logger_base.debug(" File downloaded %.1f kbytes in %ldms.", (float)size / 1024.0, sw.Time()); + } + else + { + ok = false; + } + } + else + { + logger_base.error("Unable to connect to '%s' : %d.", (const char *)url.GetPath().c_str(), http.GetError()); + ok = false; + } + + wxDELETE(httpStream); + } + else + { + ok = false; + } + + return ok; +} + +std::string FileCacheItem::DownloadURLToTemp(wxURI url) +{ + wxString type = url.GetPath().AfterLast('.'); + wxString fn = wxFileName::CreateTempFileName("xl"); + wxRemoveFile(fn); + wxString filename = fn.BeforeLast('.') + "." + type; + if (fn.BeforeLast('.') == "") + { + filename = fn + "." + type; + } + + if (DownloadURL(url, filename)) + { + return filename.ToStdString(); + } + + return ""; +} + +void FileCacheItem::PurgeIfAged() const +{ + static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); + + if (!Exists()) return; + + wxDateTime modified = wxFileName(_fileName).GetModificationTime(); + switch(_cacheFor) + { + case CACHETIME_SESSION: + // do nothing + break; + case CACHETIME_DAY: + if (wxDateTime::Now().GetDateOnly() != modified.GetDateOnly()) + { + logger_base.debug("%s purged from file cache because it was not created today.", (const char *)_url.BuildURI().c_str()); + Delete(); + } + break; + case CACHETIME_FOREVER: + // do nothing + break; + case CACHETIME_LONG: + wxTimeSpan age = wxDateTime::Now() - modified; + if (age.GetDays() > LONGCACHEDAYS) + { + logger_base.debug("%s purged from file cache because it was more than %d days old: %d.", (const char *)_url.BuildURI().c_str(), LONGCACHEDAYS, age.GetDays()); + Delete(); + } + break; + } +} + +void CachedFileDownloader::SaveCache() +{ + static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); + + logger_base.debug("Saving File Cache %s.", (const char *)_cacheFile.c_str()); + + wxFile f; + if (f.Create(_cacheFile, true) && f.IsOpened()) + { + wxString lit("\n"); + f.Write(lit, lit.size()); + lit = "\n"; + f.Write(lit, lit.size()); + + int i = 0; + for (auto it = _cacheItems.begin(); it != _cacheItems.end(); ++it) + { + if ((*it)->ShouldSave()) + { + (*it)->Save(f); + i++; + } + } + + lit = ""; + f.Write(lit, lit.size()); + f.Close(); + logger_base.debug(" File Cache %d items saved.", i); + } + else + { + logger_base.warn(" Problem saving File Cache."); + } +} + +void CachedFileDownloader::LoadCache() +{ + static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); + + logger_base.debug("Loading File Cache %s.", (const char *)_cacheFile.c_str()); + _cacheItems.empty(); + + if (wxFile::Exists(_cacheFile) && wxFileName(_cacheFile).GetSize() > 0) + { + wxXmlDocument d; + d.Load(_cacheFile); + if (d.IsOk()) + { + wxXmlNode* root = d.GetRoot(); + if (root != nullptr && root->GetName().Lower() == "filecache") + { + logger_base.debug(" Cache opened."); + for (wxXmlNode* n = root->GetChildren(); n != nullptr; n = n->GetNext()) + { + if (n->GetName().Lower() == "item") + _cacheItems.push_back(new FileCacheItem(n)); + } + logger_base.debug(" %d items loaded.", _cacheItems.size()); + } + } + else + { + logger_base.warn("File Cache was invalid."); + } + } + else + { + logger_base.warn("File Cache does not exist."); + } +} + +FileCacheItem* CachedFileDownloader::Find(wxURI url) +{ + for (auto it = _cacheItems.begin(); it != _cacheItems.end(); ++it) + { + if (**it == url) + { + return *it; + } + } + + return nullptr; +} + +CachedFileDownloader::CachedFileDownloader(const std::string cacheDir) +{ + _cacheDir = cacheDir; + if (_cacheDir == "" || !wxDirExists(_cacheDir)) + { + _cacheDir = wxFileName::GetTempDir(); + } + + _cacheFile = _cacheDir + "/xLightsCache.xml"; + + LoadCache(); + PurgeAgedItems(); +} + +CachedFileDownloader::~CachedFileDownloader() +{ + // Dont save ... this is done when the dialog is exited. + //SaveCache(); +} + +void CachedFileDownloader::ClearCache() +{ + static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); + + logger_base.debug("File Cache cleared."); + for (auto it= _cacheItems.begin(); it != _cacheItems.end(); ++it) + { + (*it)->Delete(); + } +} + +void CachedFileDownloader::PurgeAgedItems() +{ + static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); + + logger_base.debug("File Cache purging aged items."); + for (auto it= _cacheItems.begin(); it != _cacheItems.end(); ++it) + { + (*it)->PurgeIfAged(); + } +} + +std::string CachedFileDownloader::GetFile(wxURI url, CACHEFOR cacheFor) +{ + static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); + + FileCacheItem* fci = Find(url); + if (fci == nullptr) + { + logger_base.debug("File Cache downloading file %s.", (const char *)url.BuildURI().c_str()); + fci = new FileCacheItem(url, cacheFor); + _cacheItems.push_back(fci); + } + else if (!fci->Exists()) + { + logger_base.debug("File Cache re-downloading file %s.", (const char *)url.BuildURI().c_str()); + fci->Download(); + } + + if (fci->GetFileName() == "") + { + logger_base.debug("File Cache file %s could not be retrieved.", (const char *)url.BuildURI().c_str()); + } + + return fci->GetFileName(); +} diff --git a/xLights/CachedFileDownloader.h b/xLights/CachedFileDownloader.h new file mode 100644 index 0000000000..6ecaa934b7 --- /dev/null +++ b/xLights/CachedFileDownloader.h @@ -0,0 +1,65 @@ +#ifndef CACHEDFILEDOWNLOADER_H +#define CACHEDFILEDOWNLOADER_H + +#include +#include +#include +#include + +class wxXmlNode; + +typedef enum +{ + CACHETIME_SESSION, + CACHETIME_DAY, + CACHETIME_LONG, + CACHETIME_FOREVER +} CACHEFOR; + +class FileCacheItem +{ + wxURI _url; + CACHEFOR _cacheFor; + std::string _fileName; + +public: + FileCacheItem(wxXmlNode* n); + FileCacheItem(wxURI url, CACHEFOR cacheFor); + void Save(wxFile& f); + virtual ~FileCacheItem() {} + void Download(); + bool Exists() const { return wxFile::Exists(_fileName); } + void Touch() const { if (Exists()) wxFileName(_fileName).Touch(); } + void Delete() const { if (Exists()) wxRemoveFile(_fileName); } + std::string GetFileName() const { if (Exists()) return _fileName; else return ""; } + bool operator==(const wxURI& url) const { return _url.BuildURI() == url.BuildURI(); } + bool DownloadURL(wxURI url, wxFileName filename) const; + std::string DownloadURLToTemp(wxURI url); + void PurgeIfAged() const; + bool ShouldSave() { PurgeIfAged(); return Exists() && (_cacheFor == CACHETIME_DAY || _cacheFor == CACHETIME_LONG); } +}; + +class CachedFileDownloader +{ + std::string _cacheDir; + std::list _cacheItems; + std::string _cacheFile; + + void PurgeAgedItems(); + void SaveCache(); + void LoadCache(); + FileCacheItem* Find(wxURI url); + +public: + + CachedFileDownloader(const std::string cacheDir = ""); + virtual ~CachedFileDownloader(); + // erase everything from cache + void ClearCache(); + void Save() { SaveCache(); } + // retrieve a file from cache … if not present filename will be “” + std::string GetFile(wxURI url, CACHEFOR cacheFor); + int size() const { return _cacheItems.size(); } +}; + +#endif diff --git a/xLights/LayoutPanel.cpp b/xLights/LayoutPanel.cpp index 113c03d5c5..deddf1738d 100644 --- a/xLights/LayoutPanel.cpp +++ b/xLights/LayoutPanel.cpp @@ -82,7 +82,6 @@ BEGIN_EVENT_TABLE(LayoutPanel,wxPanel) //EVT_TREELIST_ITEM_ACTIVATED(wxID_ANY, LayoutPanel::OnSelectionChanged) END_EVENT_TABLE() - const long LayoutPanel::ID_TREELISTVIEW_MODELS = wxNewId(); const long LayoutPanel::ID_PREVIEW_ALIGN = wxNewId(); const long LayoutPanel::ID_PREVIEW_RESIZE = wxNewId(); @@ -338,7 +337,6 @@ LayoutPanel::LayoutPanel(wxWindow* parent, xLightsFrame *xl, wxPanel* sequencer) logger_base.debug("LayoutPanel property grid created"); - wxConfigBase* config = wxConfigBase::Get(); int msp = config->Read("LayoutModelSplitterSash", -1); int sp = config->Read("LayoutMainSplitterSash", -1); @@ -351,7 +349,7 @@ LayoutPanel::LayoutPanel(wxWindow* parent, xLightsFrame *xl, wxPanel* sequencer) ModelSplitter->SetSashPosition(msp); } - ToolSizer->SetCols(16); + ToolSizer->SetCols(17); AddModelButton("Arches", arches); AddModelButton("Candy Canes", canes); AddModelButton("Channel Block", channelblock_xpm); @@ -368,10 +366,10 @@ LayoutPanel::LayoutPanel(wxWindow* parent, xLightsFrame *xl, wxPanel* sequencer) AddModelButton("Window Frame", frame); AddModelButton("Wreath", wreath); AddModelButton("Import Custom", import); + AddModelButton("Download", download); logger_base.debug("LayoutPanel model buttons created"); - modelPreview->Connect(wxID_CUT, wxEVT_MENU, (wxObjectEventFunction)&LayoutPanel::DoCut, nullptr,this); modelPreview->Connect(wxID_COPY, wxEVT_MENU, (wxObjectEventFunction)&LayoutPanel::DoCopy, nullptr,this); modelPreview->Connect(wxID_PASTE, wxEVT_MENU, (wxObjectEventFunction)&LayoutPanel::DoPaste, nullptr,this); @@ -1829,15 +1827,15 @@ void LayoutPanel::FinalizeModel() m_polyline_active = false; if (newModel != nullptr) { - if (selectedButton->GetModelType() == "Import Custom") + if (selectedButton->GetModelType() == "Import Custom" || selectedButton->GetModelType() == "Download") { float min_x = (float)(newModel->GetModelScreenLocation().GetLeft()) / (float)(newModel->GetModelScreenLocation().previewW); float max_x = (float)(newModel->GetModelScreenLocation().GetRight()) / (float)(newModel->GetModelScreenLocation().previewW); float min_y = (float)(newModel->GetModelScreenLocation().GetBottom()) / (float)(newModel->GetModelScreenLocation().previewH); float max_y = (float)(newModel->GetModelScreenLocation().GetTop()) / (float)(newModel->GetModelScreenLocation().previewH); bool cancelled = false; - newModel = Model::GetXlightsModel(newModel, _lastXlightsModel, xlights, cancelled); - if( cancelled ) { + newModel = Model::GetXlightsModel(newModel, _lastXlightsModel, xlights, cancelled, selectedButton->GetModelType() == "Download"); + if (cancelled) { newModel = nullptr; m_over_handle = -1; modelPreview->SetCursor(wxCURSOR_DEFAULT); @@ -2555,7 +2553,7 @@ void LayoutPanel::OnNewModelTypeButtonClicked(wxCommandEvent& event) { Model *LayoutPanel::CreateNewModel(const std::string &type) const { std::string t = type; - if (t == "Import Custom") + if (t == "Import Custom" || t == "Download") { t = "Custom"; } diff --git a/xLights/SeqFileUtilities.cpp b/xLights/SeqFileUtilities.cpp index 9b4fcf3c8d..3a04ff577c 100644 --- a/xLights/SeqFileUtilities.cpp +++ b/xLights/SeqFileUtilities.cpp @@ -117,7 +117,7 @@ void xLightsFrame::NewSequence() MenuItem_File_Save->Enable(true); MenuItem_File_SaveAs_Sequence->Enable(true); MenuItem_File_Close_Sequence->Enable(true); - MenuItem_File_Export_Video->Enable(true); + MenuItem_File_Export_Video->Enable(true); MenuItem_PackageSequence->Enable(true); MenuItem_GenerateLyrics->Enable(true); MenuItem_ExportEffects->Enable(true); @@ -129,7 +129,7 @@ void xLightsFrame::NewSequence() m *= max; m /= 1024; // ->kb m /= 1024; // ->mb - + wxMessageBox(wxString::Format("The setup requires a VERY large number of channels (%u) which will result in" " a very large amount of memory used (%lu MB).", max, m), "Warning", wxICON_WARNING | wxOK | wxCENTRE, this); @@ -411,7 +411,7 @@ void xLightsFrame::OpenSequence(const wxString passed_filename, ConvertLogDialog m *= numChan; m /= 1024; // ->kb m /= 1024; // ->mb - + wxMessageBox(wxString::Format("The setup requires a VERY large number of channels (%u) which will result in" " a very large amount of memory used (%lu MB).", numChan, m), "Warning", wxICON_WARNING | wxOK | wxCENTRE, this); @@ -1965,7 +1965,7 @@ void xLightsFrame::ImportSuperStar(const wxFileName &filename) for (size_t i=0;iGetType() == ELEMENT_TYPE_MODEL) { dlg.ChoiceSuperStarImportModel->Append(mSequenceElements.GetElement(i)->GetName()); - + ModelElement *model = dynamic_cast(mSequenceElements.GetElement(i)); for (int x = 0; x < model->GetSubModelCount(); x++) { std::string fname = model->GetSubModel(x)->GetFullName(); diff --git a/xLights/VendorModelDialog.cpp b/xLights/VendorModelDialog.cpp new file mode 100644 index 0000000000..8544768ed6 --- /dev/null +++ b/xLights/VendorModelDialog.cpp @@ -0,0 +1,1282 @@ +#include "VendorModelDialog.h" + +//(*InternalHeaders(VendorModelDialog) +#include +#include +//*) + +#include +#include +#include + +#include "CachedFileDownloader.h" + +CachedFileDownloader VendorModelDialog::_cache; + +class MModel; + +class MModelWiring +{ +public: + std::list _images; + std::list _imageFiles; + std::string _name; + std::string _wiringDescription; + wxURI _xmodelLink; + wxFileName _xmodelFile; + MModel* _model; + + std::string GetDescription(); + + ~MModelWiring() + { + } + + void DownloadImages() + { + if (_imageFiles.size() != _images.size()) + { + _imageFiles.clear(); + for (auto it = _images.begin(); it != _images.end(); ++it) + { + std::string fn = VendorModelDialog::GetCache().GetFile(*it, CACHEFOR::CACHETIME_LONG); + if (fn != "") + { + _imageFiles.push_back(wxFileName(fn)); + } + } + } + } + + void DownloadXModel() + { + if (!_xmodelFile.Exists()) + { + _xmodelFile = VendorModelDialog::GetCache().GetFile(_xmodelLink, CACHEFOR::CACHETIME_LONG); + } + } + + MModelWiring(wxXmlNode* n, MModel* m) + { + _model = m; + + for (wxXmlNode* l = n->GetChildren(); l != nullptr; l = l->GetNext()) + { + wxString nn = l->GetName().Lower().ToStdString(); + if (nn == "name") + { + _name = l->GetNodeContent().ToStdString(); + } + else if (nn == "description") + { + _wiringDescription = l->GetNodeContent().ToStdString(); + } + else if (nn == "xmodellink") + { + _xmodelLink = wxURI(l->GetNodeContent()); + } + else if (nn == "imagefile") + { + _images.push_back(wxURI(l->GetNodeContent())); + } + else + { + wxASSERT(false); + } + } + } + + void AddImages(std::list images) + { + for (auto it = images.begin(); it != images.end(); ++it) + { + _images.push_back(*it); + } + } +}; + +class MModel +{ +public: + std::string _id; + std::list _categoryIds; + std::string _name; + std::string _type; + std::string _material; + std::string _thickness; + std::string _width; + std::string _height; + std::string _pixelCount; + std::string _pixelDescription; + wxURI _webpage; + std::list _images; + std::list _imageFiles; + std::string _notes; + std::list _wiring; + + bool InCategory(std::string category) + { + for (auto it = _categoryIds.begin(); it != _categoryIds.end(); ++it) + { + if (*it == category) return true; + } + + return false; + } + + MModel(wxXmlNode* n) + { + for (wxXmlNode* l = n->GetChildren(); l != nullptr; l = l->GetNext()) + { + wxString nn = l->GetName().Lower().ToStdString(); + if (nn == "id") + { + _id = l->GetNodeContent().ToStdString(); + } + else if (nn == "categoryid") + { + _categoryIds.push_back(l->GetNodeContent().ToStdString()); + } + else if (nn == "name") + { + _name = l->GetNodeContent().ToStdString(); + } + else if (nn == "type") + { + _type = l->GetNodeContent().ToStdString(); + } + else if (nn == "material") + { + _material = l->GetNodeContent().ToStdString(); + } + else if (nn == "thickness") + { + _thickness = l->GetNodeContent().ToStdString(); + } + else if (nn == "width") + { + _width = l->GetNodeContent().ToStdString(); + } + else if (nn == "height") + { + _height = l->GetNodeContent().ToStdString(); + } + else if (nn == "pixelcount") + { + _pixelCount = l->GetNodeContent().ToStdString(); + } + else if (nn == "pixeldescription") + { + _pixelDescription = l->GetNodeContent().ToStdString(); + } + else if (nn == "notes") + { + _notes = l->GetNodeContent().ToStdString(); + } + else if (nn == "weblink") + { + _webpage = wxURI(l->GetNodeContent()); + } + else if (nn == "imagefile") + { + _images.push_back(wxURI(l->GetNodeContent())); + } + else if (nn == "wiring") + { + _wiring.push_back(new MModelWiring(l, this)); + } + else + { + wxASSERT(false); + } + } + + for (auto it = _wiring.begin(); it != _wiring.end(); ++it) + { + (*it)->AddImages(_images); + } + } + + virtual ~MModel() + { + for (auto it = _wiring.begin(); it != _wiring.end(); ++it) + { + delete *it; + } + } + + std::string PadTitle(std::string t) const + { + std::string res = t; + while (res.size() < 18) res += " "; + return res; + } + + std::string GetDescription() const + { + std::string desc; + + if (_name != "") + { + desc += PadTitle("Name:") + _name + "\n\n"; + } + if (_type != "") + { + desc += PadTitle("Type:") + _type + "\n"; + } + if (_material != "") + { + desc += PadTitle("Material:") + _material + "\n"; + } + if (_thickness != "") + { + desc += PadTitle("Thickness:") + _thickness + "\n"; + } + if (_width != "") + { + desc += PadTitle("Width:") + _width + "\n"; + } + if (_height != "") + { + desc += PadTitle("Height:") + _height + "\n"; + } + if (_pixelCount != "") + { + desc += PadTitle("Pixel Count:") + _pixelCount + "\n"; + } + if (_pixelDescription != "") + { + desc += PadTitle("Pixel Description:") + _pixelDescription + "\n"; + } + if (_notes != "") + { + desc += "\n" + _notes + "\n"; + } + + return desc; + } + + void DownloadImages() + { + if (_imageFiles.size() == _images.size()) + { + return; + } + _imageFiles.clear(); + + for (auto it = _images.begin(); it != _images.end(); ++it) + { + std::string fn = VendorModelDialog::GetCache().GetFile(*it, CACHEFOR::CACHETIME_LONG); + if (fn != "") + { + _imageFiles.push_back(wxFileName(fn)); + } + } + } +}; + +std::string MModelWiring::GetDescription() +{ + std::string desc = _model->GetDescription(); + + desc += "\n"; + + if (_name != "") + { + desc += "Wiring Option: " + _name + "\n\n"; + } + if (_wiringDescription != "") + { + desc += _wiringDescription; + } + + return desc; +} + + +class MVendorCategory +{ + void ParseCategories(wxXmlNode *n) + { + for (wxXmlNode* l = n->GetChildren(); l != nullptr; l = l->GetNext()) + { + wxString nn = l->GetName().Lower().ToStdString(); + if (nn == "category") + { + _categories.push_back(new MVendorCategory(l, this)); + } + } + } + + public: + std::string _id; + std::string _name; + MVendorCategory* _parent; + std::list _categories; + + std::string GetPath() const + { + if (_parent != nullptr) + { + return _parent->GetPath() + "/" + _name; + } + else + { + return _name; + } + } + + MVendorCategory(wxXmlNode* n, MVendorCategory* parent) + { + _parent = parent; + for (wxXmlNode* e = n->GetChildren(); e != nullptr; e = e->GetNext()) + { + wxString nn = e->GetName().Lower(); + if (nn == "id") + { + _id = e->GetNodeContent().ToStdString(); + } + else if (nn == "name") + { + _name = e->GetNodeContent().ToStdString(); + } + else if (nn == "categories") + { + ParseCategories(e); + } + else + { + wxASSERT(false); + } + } + } + virtual ~MVendorCategory() + { + for (auto it = _categories.begin(); it != _categories.end(); ++it) + { + delete *it; + } + } +}; + +class MVendor +{ +public: + std::string _name = ""; + std::string _contact = ""; + std::string _email = ""; + std::string _phone = ""; + wxURI _website; + wxURI _facebook; + std::string _twitter = ""; + std::string _notes = ""; + wxFileName _logoFile; + std::list _categories; + std::list _models; + int _maxModels; + + std::list GetModels(std::string categoryId) + { + std::list res; + + for (auto it = _models.begin(); it != _models.end(); ++it) + { + if ((*it)->InCategory(categoryId)) + { + res.push_back(*it); + } + } + + return res; + } + + void ParseCategories(wxXmlNode *n) + { + for (wxXmlNode* l = n->GetChildren(); l != nullptr; l = l->GetNext()) + { + wxString nn = l->GetName().Lower().ToStdString(); + if (nn == "category") + { + _categories.push_back(new MVendorCategory(l, nullptr)); + } + } + } + + std::string PadTitle(std::string t) const + { + std::string res = t; + while (res.size() < 9) res += " "; + return res; + } + + std::string GetDescription() const + { + std::string desc; + + if (_name != "") + { + desc += PadTitle("Name:") + _name + "\n\n"; + } + if (_contact != "") + { + desc += PadTitle("Contact:") + _contact + "\n"; + } + if (_phone != "") + { + desc += PadTitle("Phone:") + _phone + "\n"; + } + if (_email != "") + { + desc += PadTitle("Email:") + _email + "\n"; + } + if (_twitter != "") + { + desc += PadTitle("Twitter:") + _twitter + "\n"; + } + if (_notes != "") + { + desc += "\n" + _notes + "\n"; + } + + return desc; + } + + MVendor(wxXmlDocument* doc, int maxModels) + { + _maxModels = maxModels; + + if (doc->IsOk()) + { + wxXmlNode* root = doc->GetRoot(); + wxString nn = root->GetName().Lower(); + if (nn == "modelinventory") + { + for (wxXmlNode* e = root->GetChildren(); e != nullptr; e = e->GetNext()) + { + nn = e->GetName().Lower(); + if (nn == "vendor") + { + for (wxXmlNode* v = e->GetChildren(); v != nullptr; v = v->GetNext()) + { + nn = v->GetName().Lower(); + if (nn == "name") + { + _name = v->GetNodeContent().ToStdString(); + } + else if (nn == "contact") + { + _contact = v->GetNodeContent().ToStdString(); + } + else if (nn == "email") + { + _email = v->GetNodeContent().ToStdString(); + } + else if (nn == "phone") + { + _phone = v->GetNodeContent().ToStdString(); + } + else if (nn == "website") + { + _website = wxURI(v->GetNodeContent().ToStdString()); + } + else if (nn == "facebook") + { + _facebook = wxURI(v->GetNodeContent().ToStdString()); + } + else if (nn == "twitter") + { + _twitter = v->GetNodeContent().ToStdString(); + } + else if (nn == "notes") + { + _notes = v->GetNodeContent().ToStdString(); + } + else if (nn == "logolink") + { + wxURI logo(v->GetNodeContent().ToStdString()); + _logoFile = wxFileName(VendorModelDialog::GetCache().GetFile(logo, CACHEFOR::CACHETIME_LONG)); + } + else + { + wxASSERT(false); + } + } + } + else if (nn == "categories") + { + ParseCategories(e); + } + else if (nn == "models") + { + int models = 0; + for (wxXmlNode* m = e->GetChildren(); m != nullptr; m = m->GetNext()) + { + nn = m->GetName().Lower(); + if (nn == "model") + { + models++; + if (maxModels < 1 || models < _maxModels) + { + _models.push_back(new MModel(m)); + } + } + } + } + else + { + wxASSERT(false); + } + } + } + } + } + + virtual ~MVendor() + { + for (auto it = _categories.begin(); it != _categories.end(); ++it) + { + delete *it; + } + + for (auto it = _models.begin(); it != _models.end(); ++it) + { + delete *it; + } + } +}; + +class VendorBaseTreeItemData : public wxTreeItemData +{ +public: + VendorBaseTreeItemData(std::string type) : _type(type) { } + + std::string GetType() const { return _type; } + +private: + std::string _type; +}; + +class MVendorTreeItemData : public VendorBaseTreeItemData +{ +public: + MVendorTreeItemData(MVendor* vendor) : VendorBaseTreeItemData("Vendor"), _vendor(vendor) { } + + MVendor* GetVendor() const { return _vendor; } + +private: + MVendor * _vendor; +}; + +class MModelTreeItemData : public VendorBaseTreeItemData +{ +public: + MModelTreeItemData(MModel* model) : VendorBaseTreeItemData("Model"), _model(model) { } + + MModel* GetModel() const { return _model; } + +private: + MModel * _model; +}; + +class MWiringTreeItemData : public VendorBaseTreeItemData +{ +public: + MWiringTreeItemData(MModelWiring* wiring) : VendorBaseTreeItemData("Wiring"), _wiring(wiring) { } + + MModelWiring* GetWiring() const { return _wiring; } + +private: + MModelWiring* _wiring; +}; + +//(*IdInit(VendorModelDialog) +const long VendorModelDialog::ID_TREECTRL1 = wxNewId(); +const long VendorModelDialog::ID_PANEL3 = wxNewId(); +const long VendorModelDialog::ID_STATICBITMAP1 = wxNewId(); +const long VendorModelDialog::ID_TEXTCTRL1 = wxNewId(); +const long VendorModelDialog::ID_STATICTEXT8 = wxNewId(); +const long VendorModelDialog::ID_HYPERLINKCTRL4 = wxNewId(); +const long VendorModelDialog::ID_STATICTEXT4 = wxNewId(); +const long VendorModelDialog::ID_HYPERLINKCTRL2 = wxNewId(); +const long VendorModelDialog::ID_PANEL2 = wxNewId(); +const long VendorModelDialog::ID_BUTTON2 = wxNewId(); +const long VendorModelDialog::ID_STATICBITMAP2 = wxNewId(); +const long VendorModelDialog::ID_BUTTON3 = wxNewId(); +const long VendorModelDialog::ID_TEXTCTRL2 = wxNewId(); +const long VendorModelDialog::ID_STATICTEXT7 = wxNewId(); +const long VendorModelDialog::ID_HYPERLINKCTRL3 = wxNewId(); +const long VendorModelDialog::ID_BUTTON1 = wxNewId(); +const long VendorModelDialog::ID_PANEL4 = wxNewId(); +const long VendorModelDialog::ID_NOTEBOOK1 = wxNewId(); +const long VendorModelDialog::ID_PANEL1 = wxNewId(); +const long VendorModelDialog::ID_SPLITTERWINDOW1 = wxNewId(); +//*) + +BEGIN_EVENT_TABLE(VendorModelDialog,wxDialog) + //(*EventTable(VendorModelDialog) + //*) +END_EVENT_TABLE() + +VendorModelDialog::VendorModelDialog(wxWindow* parent,wxWindowID id,const wxPoint& pos,const wxSize& size) +{ + //(*Initialize(VendorModelDialog) + wxFlexGridSizer* FlexGridSizer4; + wxFlexGridSizer* FlexGridSizer3; + wxFlexGridSizer* FlexGridSizer5; + wxFlexGridSizer* FlexGridSizer2; + wxFlexGridSizer* FlexGridSizer7; + wxFlexGridSizer* FlexGridSizer8; + wxFlexGridSizer* FlexGridSizer6; + wxFlexGridSizer* FlexGridSizer1; + + Create(parent, id, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION|wxRESIZE_BORDER|wxCLOSE_BOX|wxMAXIMIZE_BOX, _T("id")); + SetClientSize(wxSize(800,600)); + Move(wxDefaultPosition); + SetMinSize(wxSize(800,400)); + FlexGridSizer1 = new wxFlexGridSizer(0, 1, 0, 0); + FlexGridSizer1->AddGrowableCol(0); + FlexGridSizer1->AddGrowableRow(0); + SplitterWindow1 = new wxSplitterWindow(this, ID_SPLITTERWINDOW1, wxDefaultPosition, wxDefaultSize, wxSP_3D, _T("ID_SPLITTERWINDOW1")); + SplitterWindow1->SetMinSize(wxSize(10,10)); + SplitterWindow1->SetMinimumPaneSize(10); + SplitterWindow1->SetSashGravity(0.5); + Panel3 = new wxPanel(SplitterWindow1, ID_PANEL3, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL3")); + FlexGridSizer2 = new wxFlexGridSizer(0, 1, 0, 0); + FlexGridSizer2->AddGrowableCol(0); + FlexGridSizer2->AddGrowableRow(0); + TreeCtrl_Navigator = new wxTreeCtrl(Panel3, ID_TREECTRL1, wxDefaultPosition, wxSize(200,-1), wxTR_FULL_ROW_HIGHLIGHT|wxTR_HIDE_ROOT|wxTR_ROW_LINES|wxTR_SINGLE|wxTR_DEFAULT_STYLE|wxVSCROLL|wxHSCROLL, wxDefaultValidator, _T("ID_TREECTRL1")); + FlexGridSizer2->Add(TreeCtrl_Navigator, 1, wxALL|wxEXPAND, 5); + Panel3->SetSizer(FlexGridSizer2); + FlexGridSizer2->Fit(Panel3); + FlexGridSizer2->SetSizeHints(Panel3); + Panel1 = new wxPanel(SplitterWindow1, ID_PANEL1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL1")); + FlexGridSizer3 = new wxFlexGridSizer(0, 1, 0, 0); + FlexGridSizer3->AddGrowableCol(0); + FlexGridSizer3->AddGrowableRow(0); + NotebookPanels = new wxNotebook(Panel1, ID_NOTEBOOK1, wxDefaultPosition, wxDefaultSize, 0, _T("ID_NOTEBOOK1")); + PanelVendor = new wxPanel(NotebookPanels, ID_PANEL2, wxPoint(43,60), wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL2")); + FlexGridSizer4 = new wxFlexGridSizer(0, 1, 0, 0); + FlexGridSizer4->AddGrowableCol(0); + FlexGridSizer4->AddGrowableRow(1); + StaticBitmap_VendorImage = new wxStaticBitmap(PanelVendor, ID_STATICBITMAP1, wxNullBitmap, wxDefaultPosition, wxSize(256,128), wxSIMPLE_BORDER, _T("ID_STATICBITMAP1")); + FlexGridSizer4->Add(StaticBitmap_VendorImage, 1, wxALL|wxEXPAND, 5); + TextCtrl_VendorDetails = new wxTextCtrl(PanelVendor, ID_TEXTCTRL1, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxTE_LEFT, wxDefaultValidator, _T("ID_TEXTCTRL1")); + FlexGridSizer4->Add(TextCtrl_VendorDetails, 1, wxALL|wxEXPAND, 5); + FlexGridSizer5 = new wxFlexGridSizer(0, 2, 0, 0); + FlexGridSizer5->AddGrowableCol(1); + FlexGridSizer5->AddGrowableRow(0); + StaticText6 = new wxStaticText(PanelVendor, ID_STATICTEXT8, _("Facebook:"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT8")); + FlexGridSizer5->Add(StaticText6, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); + HyperlinkCtrl_Facebook = new wxHyperlinkCtrl(PanelVendor, ID_HYPERLINKCTRL4, _("http://www.codeblocks.org"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_CONTEXTMENU|wxHL_ALIGN_LEFT|wxNO_BORDER, _T("ID_HYPERLINKCTRL4")); + FlexGridSizer5->Add(HyperlinkCtrl_Facebook, 1, wxALL|wxEXPAND, 5); + StaticText2 = new wxStaticText(PanelVendor, ID_STATICTEXT4, _("Website:"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT4")); + FlexGridSizer5->Add(StaticText2, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); + HyperlinkCtrl_Website = new wxHyperlinkCtrl(PanelVendor, ID_HYPERLINKCTRL2, _("http://www.codeblocks.org"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_CONTEXTMENU|wxHL_ALIGN_LEFT|wxNO_BORDER, _T("ID_HYPERLINKCTRL2")); + FlexGridSizer5->Add(HyperlinkCtrl_Website, 1, wxALL|wxEXPAND, 5); + FlexGridSizer4->Add(FlexGridSizer5, 1, wxALL|wxEXPAND, 5); + PanelVendor->SetSizer(FlexGridSizer4); + FlexGridSizer4->Fit(PanelVendor); + FlexGridSizer4->SetSizeHints(PanelVendor); + Panel_Item = new wxPanel(NotebookPanels, ID_PANEL4, wxPoint(41,9), wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL4")); + FlexGridSizer6 = new wxFlexGridSizer(0, 1, 0, 0); + FlexGridSizer6->AddGrowableCol(0); + FlexGridSizer6->AddGrowableRow(1); + FlexGridSizer7 = new wxFlexGridSizer(0, 3, 0, 0); + FlexGridSizer7->AddGrowableCol(1); + Button_Prior = new wxButton(Panel_Item, ID_BUTTON2, _("<"), wxDefaultPosition, wxSize(30,-1), 0, wxDefaultValidator, _T("ID_BUTTON2")); + FlexGridSizer7->Add(Button_Prior, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); + StaticBitmap_ModelImage = new wxStaticBitmap(Panel_Item, ID_STATICBITMAP2, wxNullBitmap, wxDefaultPosition, wxSize(256,256), wxSIMPLE_BORDER, _T("ID_STATICBITMAP2")); + FlexGridSizer7->Add(StaticBitmap_ModelImage, 1, wxALL|wxEXPAND, 5); + Button_Next = new wxButton(Panel_Item, ID_BUTTON3, _(">"), wxDefaultPosition, wxSize(30,-1), 0, wxDefaultValidator, _T("ID_BUTTON3")); + FlexGridSizer7->Add(Button_Next, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); + FlexGridSizer6->Add(FlexGridSizer7, 1, wxALL|wxEXPAND, 5); + TextCtrl_ModelDetails = new wxTextCtrl(Panel_Item, ID_TEXTCTRL2, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxTE_LEFT, wxDefaultValidator, _T("ID_TEXTCTRL2")); + FlexGridSizer6->Add(TextCtrl_ModelDetails, 1, wxALL|wxEXPAND, 5); + FlexGridSizer8 = new wxFlexGridSizer(0, 2, 0, 0); + FlexGridSizer8->AddGrowableCol(1); + StaticText5 = new wxStaticText(Panel_Item, ID_STATICTEXT7, _("Web Link:"), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT7")); + FlexGridSizer8->Add(StaticText5, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5); + HyperlinkCtrl_ModelWebLink = new wxHyperlinkCtrl(Panel_Item, ID_HYPERLINKCTRL3, _("http://www.codeblocks.org"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_CONTEXTMENU|wxHL_ALIGN_LEFT|wxNO_BORDER, _T("ID_HYPERLINKCTRL3")); + FlexGridSizer8->Add(HyperlinkCtrl_ModelWebLink, 1, wxALL|wxEXPAND, 5); + FlexGridSizer6->Add(FlexGridSizer8, 1, wxALL|wxEXPAND, 5); + Button_InsertModel = new wxButton(Panel_Item, ID_BUTTON1, _("Insert Model"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_BUTTON1")); + FlexGridSizer6->Add(Button_InsertModel, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5); + Panel_Item->SetSizer(FlexGridSizer6); + FlexGridSizer6->Fit(Panel_Item); + FlexGridSizer6->SetSizeHints(Panel_Item); + NotebookPanels->AddPage(PanelVendor, _("Vendor"), false); + NotebookPanels->AddPage(Panel_Item, _("Item"), false); + FlexGridSizer3->Add(NotebookPanels, 1, wxALL|wxEXPAND, 5); + Panel1->SetSizer(FlexGridSizer3); + FlexGridSizer3->Fit(Panel1); + FlexGridSizer3->SetSizeHints(Panel1); + SplitterWindow1->SplitVertically(Panel3, Panel1); + FlexGridSizer1->Add(SplitterWindow1, 1, wxALL|wxEXPAND, 5); + SetSizer(FlexGridSizer1); + SetSizer(FlexGridSizer1); + Layout(); + + Connect(ID_TREECTRL1,wxEVT_COMMAND_TREE_ITEM_ACTIVATED,(wxObjectEventFunction)&VendorModelDialog::OnTreeCtrl_NavigatorItemActivated); + Connect(ID_TREECTRL1,wxEVT_COMMAND_TREE_SEL_CHANGED,(wxObjectEventFunction)&VendorModelDialog::OnTreeCtrl_NavigatorSelectionChanged); + Connect(ID_HYPERLINKCTRL4,wxEVT_COMMAND_HYPERLINK,(wxObjectEventFunction)&VendorModelDialog::OnHyperlinkCtrl_FacebookClick); + Connect(ID_HYPERLINKCTRL2,wxEVT_COMMAND_HYPERLINK,(wxObjectEventFunction)&VendorModelDialog::OnHyperlinkCtrl_WebsiteClick); + Connect(ID_BUTTON2,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&VendorModelDialog::OnButton_PriorClick); + Connect(ID_BUTTON3,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&VendorModelDialog::OnButton_NextClick); + Connect(ID_HYPERLINKCTRL3,wxEVT_COMMAND_HYPERLINK,(wxObjectEventFunction)&VendorModelDialog::OnHyperlinkCtrl_ModelWebLinkClick); + Connect(ID_BUTTON1,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&VendorModelDialog::OnButton_InsertModelClick); + Connect(ID_NOTEBOOK1,wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED,(wxObjectEventFunction)&VendorModelDialog::OnNotebookPanelsPageChanged); + Connect(wxID_ANY,wxEVT_CLOSE_WINDOW,(wxObjectEventFunction)&VendorModelDialog::OnClose); + Connect(wxEVT_SIZE,(wxObjectEventFunction)&VendorModelDialog::OnResize); + //*) + + SetSize(800, 600); + + static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); + logger_base.debug("File cache size: %d", _cache.size()); + + PopulateModelPanel((MModel*)nullptr); + PopulateVendorPanel(nullptr); + + ValidateWindow(); +} + +bool VendorModelDialog::DlgInit() +{ + if (LoadTree()) + { + ValidateWindow(); + return true; + } + + return false; +} + +wxXmlDocument* VendorModelDialog::GetXMLFromURL(wxURI url, std::string& filename) const +{ + filename = ""; + wxFileName fn = wxFileName(VendorModelDialog::GetCache().GetFile(url, CACHEFOR::CACHETIME_SESSION)); + if (fn.Exists()) + { + filename = fn.GetFullPath(); + return new wxXmlDocument(fn.GetFullPath()); + } + + return nullptr; +} + +bool VendorModelDialog::LoadTree() +{ + const std::string vendorlink = "http://nutcracker123.com/xlights/vendors/xlights_vendors.xml"; + + std::string filename; + wxXmlDocument* vd = GetXMLFromURL(wxURI(vendorlink), filename); + if (vd != nullptr && vd->IsOk()) + { + wxXmlNode* root = vd->GetRoot(); + + for (auto v = root->GetChildren(); v != nullptr; v = v->GetNext()) + { + if (v->GetName().Lower() == "vendor") + { + int maxModels = -1; + std::string url = ""; + + for (auto link = v->GetChildren(); link != nullptr; link = link->GetNext()) + { + if (link->GetName().Lower() == "link") + { + url = link->GetNodeContent().ToStdString(); + } + else if (link->GetName().Lower() == "maxmodels") + { + maxModels = wxAtoi(link->GetNodeContent()); + } + } + + if (url != "") + { + std::string vfilename; + wxXmlDocument* d = GetXMLFromURL(wxURI(url), vfilename); + if (d != nullptr) + { + MVendor* mv = new MVendor(d, maxModels); + _vendors.push_back(mv); + delete d; + } + } + } + } + } + if (vd != nullptr) + { + delete vd; + } + + TreeCtrl_Navigator->DeleteAllItems(); + wxTreeItemId root = TreeCtrl_Navigator->AddRoot("Vendors"); + for (auto it = _vendors.begin(); it != _vendors.end(); ++it) + { + wxTreeItemId v = TreeCtrl_Navigator->AppendItem(root, (*it)->_name, -1, -1, new MVendorTreeItemData(*it)); + AddHierachy(v, *it, (*it)->_categories); + } + + if (_vendors.size() == 0) + { + wxMessageBox("Unable to retrieve any vendor information", "Error"); + return false; + } + + return true; +} + +void VendorModelDialog::AddHierachy(wxTreeItemId id, MVendor* vendor, std::list categories) +{ + for (auto it = categories.begin(); it != categories.end(); ++it) + { + wxTreeItemId tid = TreeCtrl_Navigator->AppendItem(id, (*it)->_name); + AddHierachy(tid, vendor, (*it)->_categories); + AddModels(tid, vendor, (*it)->_id); + } +} + +void VendorModelDialog::AddModels(wxTreeItemId v, MVendor* vendor, std::string categoryId) +{ + auto models = vendor->GetModels(categoryId); + + for (auto it = models.begin(); it != models.end(); ++it) + { + if ((*it)->_wiring.size() > 1) + { + wxTreeItemId tid = TreeCtrl_Navigator->AppendItem(v, (*it)->_name, -1, -1, new MModelTreeItemData(*it)); + for (auto it2 = (*it)->_wiring.begin(); it2 != (*it)->_wiring.end(); ++it2) + { + TreeCtrl_Navigator->AppendItem(tid, (*it2)->_name, -1, -1, new MWiringTreeItemData(*it2)); + } + } + else + { + if ((*it)->_wiring.size() == 0) + { + wxTreeItemId tid = TreeCtrl_Navigator->AppendItem(v, (*it)->_name, -1, -1, new MModelTreeItemData(*it)); + } + else + { + wxTreeItemId tid = TreeCtrl_Navigator->AppendItem(v, (*it)->_name, -1, -1, new MWiringTreeItemData((*it)->_wiring.front())); + } + } + } +} + +VendorModelDialog::~VendorModelDialog() +{ + //(*Destroy(VendorModelDialog) + //*) + + _cache.Save(); + + for (auto it = _vendors.begin(); it != _vendors.end(); ++it) + { + delete *it; + } +} + +void VendorModelDialog::OnHyperlinkCtrl_ModelWebLinkClick(wxCommandEvent& event) +{ + ::wxLaunchDefaultBrowser(HyperlinkCtrl_ModelWebLink->GetURL()); +} + +void VendorModelDialog::OnButton_PriorClick(wxCommandEvent& event) +{ + if (TreeCtrl_Navigator->GetSelection().IsOk()) + { + wxTreeItemData* tid = TreeCtrl_Navigator->GetItemData(TreeCtrl_Navigator->GetSelection()); + + if (tid != nullptr) + { + std::string type = ((VendorBaseTreeItemData*)tid)->GetType(); + + if (type == "Model") + { + _currImage--; + LoadModelImage(((MModelTreeItemData*)tid)->GetModel()->_imageFiles, _currImage); + } + else if (type == "Wiring") + { + _currImage--; + LoadModelImage(((MWiringTreeItemData*)tid)->GetWiring()->_imageFiles, _currImage); + } + ValidateWindow(); + } + } +} + +void VendorModelDialog::OnButton_NextClick(wxCommandEvent& event) +{ + if (TreeCtrl_Navigator->GetSelection().IsOk()) + { + wxTreeItemData* tid = TreeCtrl_Navigator->GetItemData(TreeCtrl_Navigator->GetSelection()); + + if (tid != nullptr) + { + std::string type = ((VendorBaseTreeItemData*)tid)->GetType(); + + if (type == "Model") + { + _currImage++; + LoadModelImage(((MModelTreeItemData*)tid)->GetModel()->_imageFiles, _currImage); + } + else if (type == "Wiring") + { + _currImage++; + LoadModelImage(((MWiringTreeItemData*)tid)->GetWiring()->_imageFiles, _currImage); + } + ValidateWindow(); + } + } +} + +void VendorModelDialog::OnButton_InsertModelClick(wxCommandEvent& event) +{ + if (TreeCtrl_Navigator->GetSelection().IsOk()) + { + wxTreeItemData* tid = TreeCtrl_Navigator->GetItemData(TreeCtrl_Navigator->GetSelection()); + + if (tid != nullptr && ((VendorBaseTreeItemData*)tid)->GetType() == "Wiring") + { + ((MWiringTreeItemData*)tid)->GetWiring()->DownloadXModel(); + _modelFile = ((MWiringTreeItemData*)tid)->GetWiring()->_xmodelFile.GetFullPath(); + } + } + + EndDialog(wxID_OK); +} + +void VendorModelDialog::OnNotebookPanelsPageChanged(wxNotebookEvent& event) +{ +} + +void VendorModelDialog::OnTreeCtrl_NavigatorItemActivated(wxTreeEvent& event) +{ + ValidateWindow(); +} + +void VendorModelDialog::OnTreeCtrl_NavigatorSelectionChanged(wxTreeEvent& event) +{ + if (TreeCtrl_Navigator->GetSelection().IsOk()) + { + wxTreeItemData* tid = TreeCtrl_Navigator->GetItemData(TreeCtrl_Navigator->GetSelection()); + + if (tid != nullptr) + { + std::string type = ((VendorBaseTreeItemData*)tid)->GetType(); + + if (type == "Vendor") + { + NotebookPanels->GetPage(0)->Show(); + NotebookPanels->GetPage(1)->Hide(); + NotebookPanels->SetSelection(0); + PopulateVendorPanel(((MVendorTreeItemData*)tid)->GetVendor()); + } + else if (type == "Model") + { + NotebookPanels->GetPage(0)->Hide(); + NotebookPanels->GetPage(1)->Show(); + NotebookPanels->SetSelection(1); + PopulateModelPanel(((MModelTreeItemData*)tid)->GetModel()); + } + else if (type == "Wiring") + { + NotebookPanels->GetPage(0)->Hide(); + NotebookPanels->GetPage(1)->Show(); + NotebookPanels->SetSelection(1); + PopulateModelPanel(((MWiringTreeItemData*)tid)->GetWiring()); + } + else + { + NotebookPanels->GetPage(0)->Hide(); + NotebookPanels->GetPage(1)->Hide(); + } + } + else + { + NotebookPanels->GetPage(0)->Hide(); + NotebookPanels->GetPage(1)->Hide(); + } + } + else + { + NotebookPanels->GetPage(0)->Hide(); + NotebookPanels->GetPage(1)->Hide(); + } + + ValidateWindow(); +} + +void VendorModelDialog::OnHyperlinkCtrl_WebsiteClick(wxCommandEvent& event) +{ + ::wxLaunchDefaultBrowser(HyperlinkCtrl_Website->GetURL()); +} + +void VendorModelDialog::OnHyperlinkCtrl_FacebookClick(wxCommandEvent& event) +{ + ::wxLaunchDefaultBrowser(HyperlinkCtrl_Facebook->GetURL()); +} + +void VendorModelDialog::ValidateWindow() +{ + if (TreeCtrl_Navigator->GetSelection().IsOk()) + { + wxTreeItemData* tid = TreeCtrl_Navigator->GetItemData(TreeCtrl_Navigator->GetSelection()); + + if (tid != nullptr) + { + int imageCount = 0; + std::string type = ((VendorBaseTreeItemData*)tid)->GetType(); + + if (type == "Wiring") + { + Button_InsertModel->Enable(); + imageCount = ((MWiringTreeItemData*)tid)->GetWiring()->_imageFiles.size(); + } + else + { + Button_InsertModel->Disable(); + } + + if (type == "Model") + { + imageCount = ((MModelTreeItemData*)tid)->GetModel()->_imageFiles.size(); + } + + if (_currImage > 0) + { + Button_Prior->Enable(); + } + else + { + Button_Prior->Disable(); + } + + if (_currImage < imageCount - 1) + { + Button_Next->Enable(); + } + else + { + Button_Next->Disable(); + } + } + } +} + +void VendorModelDialog::PopulateVendorPanel(MVendor* vendor) +{ + if (vendor == nullptr) + { + NotebookPanels->GetPage(0)->Hide(); + return; + } + + if (vendor->_logoFile.Exists()) + { + _vendorImage.LoadFile(vendor->_logoFile.GetFullPath()); + if (_vendorImage.IsOk()) + { + StaticBitmap_VendorImage->Show(); + LoadImage(StaticBitmap_VendorImage, &_vendorImage); + } + else + { + StaticBitmap_VendorImage->Hide(); + } + } + else + { + StaticBitmap_VendorImage->Hide(); + } + + TextCtrl_VendorDetails->SetValue(vendor->GetDescription()); + + if (vendor->_facebook.GetPath() != "") + { + StaticText6->Show(); + HyperlinkCtrl_Facebook->Show(); + HyperlinkCtrl_Facebook->SetURL(vendor->_facebook.BuildURI()); + HyperlinkCtrl_Facebook->SetLabel(vendor->_facebook.BuildURI()); + } + else + { + StaticText6->Hide(); + HyperlinkCtrl_Facebook->Hide(); + } + if (vendor->_website.GetPath() != "") + { + StaticText2->Show(); + HyperlinkCtrl_Website->Show(); + HyperlinkCtrl_Website->SetURL(vendor->_website.BuildURI()); + HyperlinkCtrl_Website->SetLabel(vendor->_website.BuildURI()); + } + else + { + StaticText2->Hide(); + HyperlinkCtrl_Website->Hide(); + } + PanelVendor->Layout(); +} + +void VendorModelDialog::LoadImage(wxStaticBitmap* sb, wxImage* image) const +{ + if (image->GetWidth() == 0 || image->GetHeight() == 0) return; + + float x = (float)sb->GetSize().GetWidth() / (float)image->GetWidth(); + float y = (float)sb->GetSize().GetHeight() / (float)image->GetHeight(); + float scale = std::min(x, y); + + sb->SetBitmap(image->Copy().Rescale((float)image->GetWidth() * scale, (float)image->GetHeight() * scale)); +} + +void VendorModelDialog::LoadModelImage(std::list imageFiles, int image) +{ + auto it = imageFiles.begin(); + for (int i = 0; i < image; i++) + { + ++it; + } + if (it->Exists()) + { + _modelImage.LoadFile(it->GetFullPath()); + if (_modelImage.IsOk()) + { + LoadImage(StaticBitmap_ModelImage, &_modelImage); + } + } +} + +void VendorModelDialog::PopulateModelPanel(MModel* model) +{ + if (model == nullptr) + { + StaticBitmap_ModelImage->Hide(); + Button_Prior->Hide(); + Button_Next->Hide(); + StaticText5->Hide(); + TextCtrl_ModelDetails->Hide(); + HyperlinkCtrl_ModelWebLink->Hide(); + NotebookPanels->GetPage(1)->Hide(); + return; + } + + model->DownloadImages(); + if (model->_imageFiles.size() > 0) + { + StaticBitmap_ModelImage->Show(); + Button_Prior->Show(); + Button_Next->Show(); + _currImage = 0; + LoadModelImage(model->_imageFiles, _currImage); + } + else + { + StaticBitmap_ModelImage->Hide(); + Button_Prior->Hide(); + Button_Next->Hide(); + } + + TextCtrl_ModelDetails->Show(); + TextCtrl_ModelDetails->SetValue(model->GetDescription()); + + if (model->_webpage.GetPath() != "") + { + StaticText5->Show(); + HyperlinkCtrl_ModelWebLink->Show(); + HyperlinkCtrl_ModelWebLink->SetURL(model->_webpage.BuildURI()); + HyperlinkCtrl_ModelWebLink->SetLabel(model->_webpage.BuildURI()); + } + else + { + StaticText5->Hide(); + HyperlinkCtrl_ModelWebLink->Hide(); + } + + Panel_Item->Layout(); +} + +void VendorModelDialog::PopulateModelPanel(MModelWiring* wiring) +{ + if (wiring == nullptr) + { + StaticBitmap_ModelImage->Hide(); + Button_Prior->Hide(); + Button_Next->Hide(); + StaticText5->Hide(); + TextCtrl_ModelDetails->Hide(); + HyperlinkCtrl_ModelWebLink->Hide(); + NotebookPanels->GetPage(1)->Hide(); + return; + } + + wiring->DownloadImages(); + if (wiring->_imageFiles.size() > 0) + { + StaticBitmap_ModelImage->Show(); + Button_Prior->Show(); + Button_Next->Show(); + _currImage = 0; + LoadModelImage(wiring->_imageFiles, _currImage); + } + else + { + StaticBitmap_ModelImage->Hide(); + Button_Prior->Hide(); + Button_Next->Hide(); + } + + TextCtrl_ModelDetails->Show(); + TextCtrl_ModelDetails->SetValue(wiring->GetDescription()); + + if (wiring->_model->_webpage.GetPath() != "") + { + StaticText5->Show(); + HyperlinkCtrl_ModelWebLink->Show(); + HyperlinkCtrl_ModelWebLink->SetURL(wiring->_model->_webpage.BuildURI()); + HyperlinkCtrl_ModelWebLink->SetLabel(wiring->_model->_webpage.BuildURI()); + } + else + { + StaticText5->Hide(); + HyperlinkCtrl_ModelWebLink->Hide(); + } + + Panel_Item->Layout(); +} + +void VendorModelDialog::OnClose(wxCloseEvent& event) +{ + EndDialog(wxID_CLOSE); +} + +void VendorModelDialog::OnResize(wxSizeEvent& event) +{ + wxDialog::OnSize(event); + + if (NotebookPanels->GetSelection() == 0) + { + if (StaticBitmap_VendorImage->IsShown() && _vendorImage.IsOk()) + { + if (_vendorImage.GetSize() != StaticBitmap_VendorImage->GetSize()) + { + LoadImage(StaticBitmap_VendorImage, &_vendorImage); + } + } + } + else + { + if (StaticBitmap_ModelImage->IsShown() && _modelImage.IsOk()) + { + if (_modelImage.GetSize() != StaticBitmap_ModelImage->GetSize()) + { + LoadImage(StaticBitmap_ModelImage, &_modelImage); + } + } + } +} diff --git a/xLights/VendorModelDialog.h b/xLights/VendorModelDialog.h new file mode 100644 index 0000000000..d956d071a0 --- /dev/null +++ b/xLights/VendorModelDialog.h @@ -0,0 +1,126 @@ +#ifndef VENDORMODELDIALOG_H +#define VENDORMODELDIALOG_H + +//(*Headers(VendorModelDialog) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//*) + +#include +#include +#include +#include +#include +#include "CachedFileDownloader.h" + +class MVendor; +class MModel; +class MModelWiring; +class MVendorCategory; + +class VendorModelDialog: public wxDialog +{ + std::list _vendors; + std::string _modelFile; + int _currImage = -1; + wxImage _vendorImage; + wxImage _modelImage; + static CachedFileDownloader _cache; + + wxXmlDocument* GetXMLFromURL(wxURI url, std::string& filename) const; + bool LoadTree(); + void AddHierachy(wxTreeItemId v, MVendor* vendor, std::list categories); + void AddModels(wxTreeItemId v, MVendor* vendor, std::string categoryId); + void ValidateWindow(); + void PopulateVendorPanel(MVendor* vendor); + void PopulateModelPanel(MModel* vendor); + void PopulateModelPanel(MModelWiring* vendor); + void LoadModelImage(std::list imageFiles, int image); + void LoadImage(wxStaticBitmap* sb, wxImage* img) const; + + public: + + VendorModelDialog(wxWindow* parent,wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize); + virtual ~VendorModelDialog(); + std::string GetModelFile() const { return _modelFile; } + bool DlgInit(); + static CachedFileDownloader& GetCache() { return _cache; } + + //(*Declarations(VendorModelDialog) + wxTextCtrl* TextCtrl_ModelDetails; + wxNotebook* NotebookPanels; + wxHyperlinkCtrl* HyperlinkCtrl_Website; + wxStaticBitmap* StaticBitmap_VendorImage; + wxHyperlinkCtrl* HyperlinkCtrl_Facebook; + wxTreeCtrl* TreeCtrl_Navigator; + wxStaticText* StaticText2; + wxPanel* PanelVendor; + wxStaticText* StaticText6; + wxPanel* Panel1; + wxButton* Button_Prior; + wxStaticBitmap* StaticBitmap_ModelImage; + wxPanel* Panel3; + wxButton* Button_Next; + wxStaticText* StaticText5; + wxPanel* Panel_Item; + wxTextCtrl* TextCtrl_VendorDetails; + wxHyperlinkCtrl* HyperlinkCtrl_ModelWebLink; + wxSplitterWindow* SplitterWindow1; + wxButton* Button_InsertModel; + //*) + + protected: + + //(*Identifiers(VendorModelDialog) + static const long ID_TREECTRL1; + static const long ID_PANEL3; + static const long ID_STATICBITMAP1; + static const long ID_TEXTCTRL1; + static const long ID_STATICTEXT8; + static const long ID_HYPERLINKCTRL4; + static const long ID_STATICTEXT4; + static const long ID_HYPERLINKCTRL2; + static const long ID_PANEL2; + static const long ID_BUTTON2; + static const long ID_STATICBITMAP2; + static const long ID_BUTTON3; + static const long ID_TEXTCTRL2; + static const long ID_STATICTEXT7; + static const long ID_HYPERLINKCTRL3; + static const long ID_BUTTON1; + static const long ID_PANEL4; + static const long ID_NOTEBOOK1; + static const long ID_PANEL1; + static const long ID_SPLITTERWINDOW1; + //*) + + private: + + //(*Handlers(VendorModelDialog) + void OnHyperlinkCtrl_ModelWebLinkClick(wxCommandEvent& event); + void OnButton_NextClick(wxCommandEvent& event); + void OnButton_PriorClick(wxCommandEvent& event); + void OnButton_InsertModelClick(wxCommandEvent& event); + void OnNotebookPanelsPageChanged(wxNotebookEvent& event); + void OnTreeCtrl_NavigatorItemActivated(wxTreeEvent& event); + void OnTreeCtrl_NavigatorSelectionChanged(wxTreeEvent& event); + void OnHyperlinkCtrl_eMailClick(wxCommandEvent& event); + void OnHyperlinkCtrl_WebsiteClick(wxCommandEvent& event); + void OnHyperlinkCtrl_FacebookClick(wxCommandEvent& event); + void OnClose(wxCloseEvent& event); + void OnResize(wxSizeEvent& event); + //*) + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/xLights/Xlights.vcxproj b/xLights/Xlights.vcxproj index 2c2245ce93..6b7279a0d1 100644 --- a/xLights/Xlights.vcxproj +++ b/xLights/Xlights.vcxproj @@ -163,6 +163,7 @@ + @@ -456,6 +457,7 @@ + @@ -480,6 +482,7 @@ + @@ -768,6 +771,7 @@ + diff --git a/xLights/Xlights.vcxproj.filters b/xLights/Xlights.vcxproj.filters index 70ed393964..52a459f1ae 100644 --- a/xLights/Xlights.vcxproj.filters +++ b/xLights/Xlights.vcxproj.filters @@ -993,6 +993,12 @@ Source Files + + Source Files + + + Source Files + @@ -1931,6 +1937,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/xLights/models/Model.cpp b/xLights/models/Model.cpp index 0ff49d280c..5b31e474d5 100644 --- a/xLights/models/Model.cpp +++ b/xLights/models/Model.cpp @@ -22,6 +22,7 @@ #include "../ControllerConnectionDialog.h" #include "../outputs/Output.h" #include "../outputs/IPOutput.h" +#include "../VendorModelDialog.h" #include "ModelScreenLocation.h" #include @@ -3120,16 +3121,43 @@ void Model::ExportXlightsModel() { } -Model* Model::GetXlightsModel(Model* model, std::string &last_model, xLightsFrame* xlights, bool &cancelled) +Model* Model::GetXlightsModel(Model* model, std::string &last_model, xLightsFrame* xlights, bool &cancelled, bool download) { if (last_model == "") { - wxString filename = wxFileSelector(_("Choose model file"), wxEmptyString, wxEmptyString, wxEmptyString, "xLights Model files (*.xmodel)|*.xmodel|LOR prop files (*.lff;*.lpf)|*.lff;*.lpf", wxFD_OPEN); - if (filename.IsEmpty()) { - cancelled = true; - return model; + if (download) + { + xlights->SuspendAutoSave(true); + VendorModelDialog dlg(xlights); + if (dlg.DlgInit() && dlg.ShowModal() == wxID_OK) + { + xlights->SuspendAutoSave(false); + last_model = dlg.GetModelFile(); + + if (last_model == "") + { + wxMessageBox("Failed to download model file."); + + cancelled = true; + return model; + } + } + else + { + xlights->SuspendAutoSave(false); + cancelled = true; + return model; + } + } + else + { + wxString filename = wxFileSelector(_("Choose model file"), wxEmptyString, wxEmptyString, wxEmptyString, "xLights Model files (*.xmodel)|*.xmodel|LOR prop files (*.lff;*.lpf)|*.lff;*.lpf", wxFD_OPEN); + if (filename.IsEmpty()) { + cancelled = true; + return model; + } + last_model = filename.ToStdString(); } - last_model = filename.ToStdString(); } // if it isnt an xmodel then it is custom diff --git a/xLights/models/Model.h b/xLights/models/Model.h index da73fea787..3b1c06ee11 100644 --- a/xLights/models/Model.h +++ b/xLights/models/Model.h @@ -97,7 +97,7 @@ class Model } virtual bool SupportsXlightsModel(); - static Model* GetXlightsModel(Model* model, std::string &last_model, xLightsFrame* xlights, bool &cancelled); + static Model* GetXlightsModel(Model* model, std::string &last_model, xLightsFrame* xlights, bool &cancelled, bool download); virtual void ImportXlightsModel(std::string filename, xLightsFrame* xlights, float& min_x, float& max_x, float& min_y, float& max_y); virtual void ExportXlightsModel(); diff --git a/xLights/wxsmith/VendorModelDialog.wxs b/xLights/wxsmith/VendorModelDialog.wxs new file mode 100644 index 0000000000..c6ae822091 --- /dev/null +++ b/xLights/wxsmith/VendorModelDialog.wxs @@ -0,0 +1,234 @@ + + + + 800,600 + 800,400 + 1 + 1 + + + + + 1 + 0 + 0 + + + 10 + vertical + 10,10 + + + 1 + 0 + 0 + + + <none> + + TreeCtrl_Navigator + <none> + + + TreeCtrl_Navigator + <none> + + 200,-1 + + + + + wxALL|wxEXPAND + 5 + + + + + + + 1 + 0 + 0 + + + + + + 43,60 + + 1 + 0 + 1 + + + 256,128 + + wxALL|wxEXPAND + 5 + + + + + + + wxALL|wxEXPAND + 5 + + + + + 2 + 1 + 0 + + + + + wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL + 5 + + + + + + + + + wxALL|wxEXPAND + 5 + + + + + + + wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL + 5 + + + + + + + + + wxALL|wxEXPAND + 5 + + + + wxALL|wxEXPAND + 5 + + + + + + + + + 41,9 + + 1 + 0 + 1 + + + 3 + 1 + + + + 30,-1 + + + wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL + 5 + + + + + 256,256 + + wxALL|wxEXPAND + 5 + + + + + + 30,-1 + + + wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL + 5 + + + + wxALL|wxEXPAND + 5 + + + + + + + wxALL|wxEXPAND + 5 + + + + + 2 + 1 + + + + + wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL + 5 + + + + + + + + + wxALL|wxEXPAND + 5 + + + + wxALL|wxEXPAND + 5 + + + + + + + + wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL + 5 + + + + + + + + wxALL|wxEXPAND + 5 + + + + + + wxALL|wxEXPAND + 5 + + + + + diff --git a/xLights/wxsmith/xLightsframe.wxs b/xLights/wxsmith/xLightsframe.wxs index cdaaa738cc..e6c4503729 100644 --- a/xLights/wxsmith/xLightsframe.wxs +++ b/xLights/wxsmith/xLightsframe.wxs @@ -1006,6 +1006,11 @@ + + + + + F9 @@ -1117,6 +1122,10 @@ + + + + diff --git a/xLights/xLights.cbp b/xLights/xLights.cbp index f117985237..fafc0fca8e 100644 --- a/xLights/xLights.cbp +++ b/xLights/xLights.cbp @@ -504,6 +504,8 @@ + + @@ -693,6 +695,8 @@ + + @@ -1218,6 +1222,7 @@ + @@ -1379,6 +1384,7 @@ + diff --git a/xLights/xLightsMain.cpp b/xLights/xLightsMain.cpp index a6fd6fbd2f..5c6ee3fe50 100644 --- a/xLights/xLightsMain.cpp +++ b/xLights/xLightsMain.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include "xLightsApp.h" //global app run-time flags #include "heartbeat.h" //DJ @@ -74,6 +73,7 @@ #include #include "outputs/IPOutput.h" #include "GenerateLyricsDialog.h" +#include "VendorModelDialog.h" //helper functions @@ -206,6 +206,7 @@ const long xLightsFrame::ID_MENU_FPP_CONNECT = wxNewId(); const long xLightsFrame::ID_MNU_PACKAGESEQUENCE = wxNewId(); const long xLightsFrame::ID_MENU_BATCH_RENDER = wxNewId(); const long xLightsFrame::ID_MNU_XSCHEDULE = wxNewId(); +const long xLightsFrame::iD_MNU_VENDORCACHEPURGE = wxNewId(); const long xLightsFrame::ID_MNU_CRASH = wxNewId(); const long xLightsFrame::ID_MNU_DUMPRENDERSTATE = wxNewId(); const long xLightsFrame::ID_MENUITEM5 = wxNewId(); @@ -447,8 +448,7 @@ void AddEffectToolbarButtons(EffectManager &manager, xlAuiToolBar *EffectsToolBa EffectsToolBar->Realize(); } - -xLightsFrame::xLightsFrame(wxWindow* parent,wxWindowID id) : mSequenceElements(this), AllModels(&_outputManager, this), +xLightsFrame::xLightsFrame(wxWindow* parent, wxWindowID id) : mSequenceElements(this), AllModels(&_outputManager, this), layoutPanel(nullptr), color_mgr(this) { static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); @@ -463,6 +463,7 @@ xLightsFrame::xLightsFrame(wxWindow* parent,wxWindowID id) : mSequenceElements(t mCurrentPerpective = nullptr; MenuItemPreviews = nullptr; _renderMode = false; + _suspendAutoSave = false; _sequenceViewManager.SetModelManager(&AllModels); Bind(EVT_SELECTED_EFFECT_CHANGED, &xLightsFrame::SelectedEffectChanged, this); @@ -747,11 +748,10 @@ xLightsFrame::xLightsFrame(wxWindow* parent,wxWindowID id) : mSequenceElements(t MenuItem_File_Close_Sequence = new wxMenuItem(MenuFile, ID_CLOSE_SEQ, _("Close Sequence"), wxEmptyString, wxITEM_NORMAL); MenuFile->Append(MenuItem_File_Close_Sequence); MenuItem_File_Close_Sequence->Enable(false); - MenuFile->AppendSeparator(); - MenuItem_File_Export_Video = new wxMenuItem(MenuFile, ID_EXPORT_VIDEO, _("Export House Preview Video"), wxEmptyString, wxITEM_NORMAL); - MenuFile->Append(MenuItem_File_Export_Video); - MenuItem_File_Export_Video->Enable(false); - MenuFile->AppendSeparator(); + MenuFile->AppendSeparator(); + MenuItem_File_Export_Video = new wxMenuItem(MenuFile, ID_EXPORT_VIDEO, _("Export House Preview Video"), wxEmptyString, wxITEM_NORMAL); + MenuFile->Append(MenuItem_File_Export_Video); + MenuFile->AppendSeparator(); MenuItem5 = new wxMenuItem(MenuFile, ID_MENUITEM2, _("Select Show Folder\tF9"), wxEmptyString, wxITEM_NORMAL); MenuItem5->SetBitmap(wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_FOLDER")),wxART_OTHER)); MenuFile->Append(MenuItem5); @@ -809,6 +809,8 @@ xLightsFrame::xLightsFrame(wxWindow* parent,wxWindowID id) : mSequenceElements(t Menu1->Append(MenuItemBatchRender); MenuItem_xSchedule = new wxMenuItem(Menu1, ID_MNU_XSCHEDULE, _("xSchedu&le"), wxEmptyString, wxITEM_NORMAL); Menu1->Append(MenuItem_xSchedule); + MenuItem_PurgeVendorCache = new wxMenuItem(Menu1, iD_MNU_VENDORCACHEPURGE, _("Purge Vendor Cache"), wxEmptyString, wxITEM_NORMAL); + Menu1->Append(MenuItem_PurgeVendorCache); MenuItem_CrashXLights = new wxMenuItem(Menu1, ID_MNU_CRASH, _("Crash xLights"), wxEmptyString, wxITEM_NORMAL); Menu1->Append(MenuItem_CrashXLights); MenuItem_LogRenderState = new wxMenuItem(Menu1, ID_MNU_DUMPRENDERSTATE, _("Log Render State"), wxEmptyString, wxITEM_NORMAL); @@ -1119,7 +1121,7 @@ xLightsFrame::xLightsFrame(wxWindow* parent,wxWindowID id) : mSequenceElements(t Connect(IS_SAVE_SEQ,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_File_Save_Selected); Connect(ID_SAVE_AS_SEQUENCE,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_File_SaveAs_SequenceSelected); Connect(ID_CLOSE_SEQ,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_File_Close_SequenceSelected); - Connect(ID_EXPORT_VIDEO, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xLightsFrame::OnMenuItem_File_Export_VideoSelected); + Connect(ID_EXPORT_VIDEO,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_File_Export_VideoSelected); Connect(ID_MENUITEM2,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuOpenFolderSelected); Connect(ID_FILE_BACKUP,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItemBackupSelected); Connect(ID_FILE_ALTBACKUP,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnmAltBackupMenuItemSelected); @@ -1138,6 +1140,7 @@ xLightsFrame::xLightsFrame(wxWindow* parent,wxWindowID id) : mSequenceElements(t Connect(ID_MNU_PACKAGESEQUENCE,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_PackageSequenceSelected); Connect(ID_MENU_BATCH_RENDER,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItemBatchRenderSelected); Connect(ID_MNU_XSCHEDULE,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_xScheduleSelected); + Connect(iD_MNU_VENDORCACHEPURGE,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_PurgeVendorCacheSelected); Connect(ID_MNU_CRASH,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_CrashXLightsSelected); Connect(ID_MNU_DUMPRENDERSTATE,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnMenuItem_LogRenderStateSelected); Connect(wxID_ZOOM_IN,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&xLightsFrame::OnAuiToolBarItemZoominClick); @@ -3668,7 +3671,7 @@ void xLightsFrame::OnTimer_AutoSaveTrigger(wxTimerEvent& event) { static log4cpp::Category &logger_base = log4cpp::Category::getInstance(std::string("log_base")); // dont save if currently playing or in render mode - if (playType != PLAY_TYPE_MODEL && !_renderMode) { + if (playType != PLAY_TYPE_MODEL && !_renderMode && !_suspendAutoSave) { logger_base.debug("Autosaving backup of sequence."); wxStopWatch sw; if (mSavedChangeCount != mSequenceElements.GetChangeCount()) @@ -3697,7 +3700,7 @@ void xLightsFrame::OnTimer_AutoSaveTrigger(wxTimerEvent& event) } else { - logger_base.debug("AutoSave skipped because sequence is playing or batch rendering."); + logger_base.debug("AutoSave skipped because sequence is playing or batch rendering or suspended."); } if (AutoSaveInterval > 0) { @@ -7196,7 +7199,9 @@ void xLightsFrame::OnMenuItemBatchRenderSelected(wxCommandEvent& event) _renderMode = false; } else + { logger_base.info("BatchRender: No Sequences Selected."); + } } } @@ -7327,8 +7332,13 @@ void xLightsFrame::OnMenuItem_File_Save_Selected(wxCommandEvent& event) } } - void xLightsFrame::OnMenuItem_SnapToTimingMarksSelected(wxCommandEvent& event) { _snapToTimingMarks = MenuItem_SnapToTimingMarks->IsChecked(); } + +void xLightsFrame::OnMenuItem_PurgeVendorCacheSelected(wxCommandEvent& event) +{ + VendorModelDialog::GetCache().ClearCache(); + VendorModelDialog::GetCache().Save(); +} diff --git a/xLights/xLightsMain.h b/xLights/xLightsMain.h index 0a7eee4ff2..2324966577 100644 --- a/xLights/xLightsMain.h +++ b/xLights/xLightsMain.h @@ -410,7 +410,7 @@ class xLightsFrame: public wxFrame void OnResize(wxSizeEvent& event); void OnAuiToolBarItemRenderAllClick(wxCommandEvent& event); void OnMenuItem_File_Close_SequenceSelected(wxCommandEvent& event); - void OnMenuItem_File_Export_VideoSelected(wxCommandEvent& event); + void OnMenuItem_File_Export_VideoSelected(wxCommandEvent& event); void OnAuiToolBarFirstFrameClick(wxCommandEvent& event); void OnAuiToolBarLastFrameClick(wxCommandEvent& event); void OnAuiToolBarItemReplaySectionClick(wxCommandEvent& event); @@ -526,6 +526,7 @@ class xLightsFrame: public wxFrame void OnMenuItem_ModelBlendDefaultOffSelected(wxCommandEvent& event); void OnMenuItem_File_Save_Selected(wxCommandEvent& event); void OnMenuItem_SnapToTimingMarksSelected(wxCommandEvent& event); + void OnMenuItem_PurgeVendorCacheSelected(wxCommandEvent& event); //*) void OnIdle(wxIdleEvent& event); @@ -622,7 +623,7 @@ class xLightsFrame: public wxFrame static const long IS_SAVE_SEQ; static const long ID_SAVE_AS_SEQUENCE; static const long ID_CLOSE_SEQ; - static const long ID_EXPORT_VIDEO; + static const long ID_EXPORT_VIDEO; static const long ID_MENUITEM2; static const long ID_FILE_BACKUP; static const long ID_FILE_ALTBACKUP; @@ -640,6 +641,7 @@ class xLightsFrame: public wxFrame static const long ID_MNU_PACKAGESEQUENCE; static const long ID_MENU_BATCH_RENDER; static const long ID_MNU_XSCHEDULE; + static const long iD_MNU_VENDORCACHEPURGE; static const long ID_MNU_CRASH; static const long ID_MNU_DUMPRENDERSTATE; static const long ID_MENUITEM5; @@ -773,6 +775,7 @@ class xLightsFrame: public wxFrame wxMenuItem* MenuItemGridNodeValuesOff; wxMenuItem* MenuItem_SD_40; wxMenuItem* MenuItem40; + wxMenuItem* MenuItem_PurgeVendorCache; wxBitmapButton* BitmapButtonMoveNetworkDown; wxMenu* ToolIconSizeMenu; wxMenuItem* MenuItem_File_Open_Sequence; @@ -794,6 +797,7 @@ class xLightsFrame: public wxFrame wxMenuItem* mAltBackupLocationMenuItem; wxMenuItem* MenuItemShiftEffects; wxMenuItem* MenuItem_Donate; + wxMenuItem* MenuItem_File_Export_Video; wxMenuItem* MenuItem36; wxMenuItem* MenuItem_ACLIghts; wxMenuItem* MenuItemCheckSequence; @@ -802,7 +806,6 @@ class xLightsFrame: public wxFrame wxMenuItem* MenuItemTimingEditMode; wxMenuItem* MenuItemGridIconBackgroundOn; wxMenuItem* MenuItem_File_Close_Sequence; - wxMenuItem* MenuItem_File_Export_Video; wxPanel* AUIStatusBar; xlAuiToolBar* ViewToolBar; wxMenuItem* MenuItem37; @@ -1032,6 +1035,7 @@ class xLightsFrame: public wxFrame bool mScaleBackgroundImage = false; std::string mStoredLayoutGroup; int _suppressDuplicateFrames; + bool _suspendAutoSave; // convert public: @@ -1039,6 +1043,7 @@ class xLightsFrame: public wxFrame unsigned int modelsChangeCount; bool _renderMode; + void SuspendAutoSave(bool dosuspend) { _suspendAutoSave = dosuspend; } void ClearLastPeriod(); void WriteVirFile(const wxString& filename, long numChans, long numPeriods, SeqDataType *dataBuf); // Vixen *.vir void WriteHLSFile(const wxString& filename, long numChans, long numPeriods, SeqDataType *dataBuf); // HLS *.hlsnc