diff --git a/CMakeLists.txt b/CMakeLists.txt index c8ecc6d6..0f300d96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ ENDIF () # More General Settings ############################## -set (BOSS_GUI_SRC ${BOSS_SRC} "${CMAKE_SOURCE_DIR}/src/gui/ids.cpp" "${CMAKE_SOURCE_DIR}/src/gui/main.cpp" "${CMAKE_SOURCE_DIR}/src/gui/settings.cpp") +set (BOSS_GUI_SRC ${BOSS_SRC} "${CMAKE_SOURCE_DIR}/src/gui/ids.cpp" "${CMAKE_SOURCE_DIR}/src/gui/main.cpp" "${CMAKE_SOURCE_DIR}/src/gui/settings.cpp" "${CMAKE_SOURCE_DIR}/src/gui/editor.cpp") set (BOSS_API_SRC ${BOSS_SRC} "${CMAKE_SOURCE_DIR}/src/api.cpp") diff --git a/README b/README index 21e916a5..86df5ced 100644 --- a/README +++ b/README @@ -54,10 +54,10 @@ Roadmap/To Do - Remove game detection override. - Checks for cyclic dependencies and incompatibilities. - Setting load order. -- Include libespm initialisation into Game constructor. +- Include libespm initialisation into Game constructor. DONE - Optimisations to load ordering. - Implement logging. -- Implement reading of plugin versions from their description field. +- Implement reading of plugin versions from their description field. DONE Sorting Algorithm diff --git a/src/gui/editor.cpp b/src/gui/editor.cpp new file mode 100644 index 00000000..4b0d45a9 --- /dev/null +++ b/src/gui/editor.cpp @@ -0,0 +1,182 @@ +/* BOSS + + A plugin load order optimiser for games that use the esp/esm plugin system. + + Copyright (C) 2013 WrinklyNinja + + This file is part of BOSS. + + BOSS is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + BOSS is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BOSS. If not, see + . +*/ + +#include "editor.h" + +#include + +Editor::Editor(const wxString title, wxFrame *parent) : wxFrame(parent, wxID_ANY, title) { + + //Initialise child windows. + filesBook = new wxNotebook(this, wxID_ANY); + wxNotebook * messagesBook = new wxNotebook(this, wxID_ANY); + + wxPanel * reqsTab = new wxPanel(filesBook); + wxPanel * incsTab = new wxPanel(filesBook); + wxPanel * loadAfterTab = new wxPanel(filesBook); + wxPanel * messagesTab = new wxPanel(messagesBook); + wxPanel * tagsTab = new wxPanel(messagesBook); + + //Tie together notebooks and panels. + filesBook->AddPage(reqsTab, translate("Requirements"), true); + filesBook->AddPage(incsTab, translate("Incompatibilities")); + filesBook->AddPage(loadAfterTab, translate("Load After")); + + messagesBook->AddPage(messagesTab, translate("Messages"), true); + messagesBook->AddPage(tagsTab, translate("Bash Tags")); + + //Initialise controls. + pluginList = new wxListBox(this, LIST_Plugins); + pluginText = new wxStaticText(this, wxID_ANY, ""); + prioritySpin = new wxSpinCtrl(this, SPIN_Priority, "0"); + enableUserEditsBox = new wxCheckBox(this, wxID_ANY, translate("Enable User Changes")); + + addFileBtn = new wxButton(this, BUTTON_AddFile, translate("Add File")); + editFileBtn = new wxButton(this, BUTTON_EditFile, translate("Edit File")); + removeFileBtn = new wxButton(this, BUTTON_RemoveFile, translate("Remove File")); + addMsgBtn = new wxButton(messagesTab, BUTTON_AddMessage, translate("Add Message")); + editMsgBtn = new wxButton(messagesTab, BUTTON_EditMessage, translate("Edit Message")); + removeMsgBtn = new wxButton(messagesTab, BUTTON_RemoveMessage, translate("Remove Message")); + addTagBtn = new wxButton(tagsTab, BUTTON_AddTag, translate("Add Tag")); + editTagBtn = new wxButton(tagsTab, BUTTON_EditTag, translate("Edit Tag")); + removeTagBtn = new wxButton(tagsTab, BUTTON_RemoveTag, translate("Remove Tag")); + saveEditsBtn = new wxButton(this, BUTTON_SaveEdits, translate("Save Edits")); + undoEditsBtn = new wxButton(this, BUTTON_UndoEdits, translate("Undo Edits")); + recalcBtn = new wxButton(this, BUTTON_Recalc, translate("Recalculate Load Order")); + applyBtn = new wxButton(this, BUTTON_Apply, translate("Apply Load Order")); + cancelBtn = new wxButton(this, BUTTON_Cancel, translate("Cancel")); + + reqsList = new wxListCtrl(reqsTab, LIST_Reqs, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); + incsList = new wxListCtrl(incsTab, LIST_Incs, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); + loadAfterList = new wxListCtrl(loadAfterTab, LIST_LoadAfter, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); + messageList = new wxListCtrl(messagesTab, LIST_Messages, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); + tagsList = new wxListCtrl(tagsTab, LIST_BashTags, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); + + //Set up list columns. + reqsList->AppendColumn(translate("Filename")); + reqsList->AppendColumn(translate("Display Name")); + reqsList->AppendColumn(translate("Condition")); + + incsList->AppendColumn(translate("Filename")); + incsList->AppendColumn(translate("Display Name")); + incsList->AppendColumn(translate("Condition")); + + loadAfterList->AppendColumn(translate("Filename")); + loadAfterList->AppendColumn(translate("Display Name")); + loadAfterList->AppendColumn(translate("Condition")); + + messageList->AppendColumn(translate("Type")); + messageList->AppendColumn(translate("Content")); + messageList->AppendColumn(translate("Condition")); + messageList->AppendColumn(translate("Language")); + + tagsList->AppendColumn(translate("Add/Remove")); + tagsList->AppendColumn(translate("Bash Tag")); + tagsList->AppendColumn(translate("Condition")); + + //Set up layout. + wxBoxSizer * bigBox = new wxBoxSizer(wxHORIZONTAL); + + wxBoxSizer *vbox1 = new wxBoxSizer(wxVERTICAL); + vbox1->Add(new wxStaticText(this, wxID_ANY, translate("Plugins")), 0, wxALL, 5); + vbox1->Add(pluginList, 1, wxEXPAND|wxALL, 5); + + bigBox->Add(vbox1, 0, wxEXPAND|wxALL, 5); + + wxBoxSizer * mainBox = new wxBoxSizer(wxVERTICAL); + + mainBox->Add(pluginText, 0, wxALL, 5); + + wxBoxSizer * hbox1 = new wxBoxSizer(wxHORIZONTAL); + + hbox1->Add(new wxStaticText(this, wxID_ANY, translate("Priority: ")), 0, wxALL, 5); + hbox1->Add(prioritySpin, 0, wxALL, 5); + + mainBox->Add(hbox1); + + wxBoxSizer * tabBox1 = new wxBoxSizer(wxVERTICAL); + tabBox1->Add(reqsList, 1, wxEXPAND); + reqsTab->SetSizer(tabBox1); + + wxBoxSizer * tabBox2 = new wxBoxSizer(wxVERTICAL); + tabBox2->Add(incsList, 1, wxEXPAND); + incsTab->SetSizer(tabBox2); + + wxBoxSizer * tabBox3 = new wxBoxSizer(wxVERTICAL); + tabBox3->Add(loadAfterList, 1, wxEXPAND); + loadAfterTab->SetSizer(tabBox3); + + mainBox->Add(filesBook, 1, wxEXPAND); + + wxBoxSizer * hbox2 = new wxBoxSizer(wxHORIZONTAL); + hbox2->Add(addFileBtn, 0, wxALL, 10); + hbox2->Add(editFileBtn, 0, wxALL, 10); + hbox2->Add(removeFileBtn, 0, wxALL, 10); + mainBox->Add(hbox2); + + wxBoxSizer * tabBox4 = new wxBoxSizer(wxVERTICAL); + tabBox4->Add(messageList, 0, wxEXPAND|wxALL, 5); + + wxBoxSizer * hbox3 = new wxBoxSizer(wxHORIZONTAL); + hbox3->Add(addMsgBtn, 0, wxALL, 10); + hbox3->Add(editMsgBtn, 0, wxALL, 10); + hbox3->Add(removeMsgBtn, 0, wxALL, 10); + tabBox4->Add(hbox3); + + messagesTab->SetSizer(tabBox4); + + wxBoxSizer * tabBox5 = new wxBoxSizer(wxVERTICAL); + tabBox5->Add(tagsList, 0, wxEXPAND|wxALL, 5); + + wxBoxSizer * hbox4 = new wxBoxSizer(wxHORIZONTAL); + hbox4->Add(addTagBtn, 0, wxTOP|wxLEFT|wxRIGHT, 10); + hbox4->Add(editTagBtn, 0, wxTOP|wxLEFT|wxRIGHT, 10); + hbox4->Add(removeTagBtn, 0, wxTOP|wxLEFT|wxRIGHT, 10); + tabBox5->Add(hbox4); + + tagsTab->SetSizer(tabBox5); + + mainBox->Add(messagesBook, 1, wxEXPAND); + + mainBox->AddSpacer(20); + + wxBoxSizer * hbox5 = new wxBoxSizer(wxHORIZONTAL); + hbox5->Add(enableUserEditsBox, 0, wxALL, 5); + hbox5->Add(saveEditsBtn, 0, wxALL, 5); + hbox5->Add(undoEditsBtn, 0, wxALL, 5); + mainBox->Add(hbox5); + + mainBox->AddSpacer(20); + + wxBoxSizer * hbox6 = new wxBoxSizer(wxHORIZONTAL); + hbox6->Add(recalcBtn, 0, wxALL, 5); + hbox6->Add(applyBtn, 0, wxALL, 5); + hbox6->Add(cancelBtn, 0, wxALL, 5); + mainBox->Add(hbox6); + + bigBox->Add(mainBox, 1, wxEXPAND|wxALL, 5); + + SetBackgroundColour(wxColour(255,255,255)); + + SetSizerAndFit(bigBox); +} diff --git a/src/gui/editor.h b/src/gui/editor.h new file mode 100644 index 00000000..a2d28094 --- /dev/null +++ b/src/gui/editor.h @@ -0,0 +1,72 @@ +/* BOSS + + A plugin load order optimiser for games that use the esp/esm plugin system. + + Copyright (C) 2013 WrinklyNinja + + This file is part of BOSS. + + BOSS is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + BOSS is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BOSS. If not, see + . +*/ +#ifndef __BOSS_GUI_EDITOR__ +#define __BOSS_GUI_EDITOR__ + +#include "ids.h" +#include "../metadata.h" + +#include +#include +#include +#include + +class Editor : public wxFrame { +public: + Editor(const wxString title, wxFrame *parent); + +private: + wxListBox * pluginList; + + wxButton * addFileBtn; + wxButton * editFileBtn; + wxButton * removeFileBtn; + + wxButton * addMsgBtn; + wxButton * editMsgBtn; + wxButton * removeMsgBtn; + + wxButton * addTagBtn; + wxButton * editTagBtn; + wxButton * removeTagBtn; + + wxButton * saveEditsBtn; + wxButton * undoEditsBtn; + wxButton * recalcBtn; + wxButton * applyBtn; + wxButton * cancelBtn; + + wxCheckBox * enableUserEditsBox; + wxSpinCtrl * prioritySpin; + wxStaticText * pluginText; + wxNotebook * filesBook; + wxListCtrl * reqsList; + wxListCtrl * incsList; + wxListCtrl * loadAfterList; + wxListCtrl * messageList; + wxListCtrl * tagsList; + + std::vector plugins; +}; + +#endif diff --git a/src/gui/ids.h b/src/gui/ids.h index 66ea7d43..8e1ed1c9 100644 --- a/src/gui/ids.h +++ b/src/gui/ids.h @@ -53,7 +53,29 @@ enum { MENU_FalloutNewVegas, //Settings window. OPTION_OKExitSettings, - OPTION_CancelExitSettings + OPTION_CancelExitSettings, + //Editor window. + LIST_Plugins, + LIST_Reqs, + LIST_Incs, + LIST_LoadAfter, + LIST_Messages, + LIST_BashTags, + SPIN_Priority, + BUTTON_AddFile, + BUTTON_EditFile, + BUTTON_RemoveFile, + BUTTON_AddMessage, + BUTTON_EditMessage, + BUTTON_RemoveMessage, + BUTTON_AddTag, + BUTTON_EditTag, + BUTTON_RemoveTag, + BUTTON_SaveEdits, + BUTTON_UndoEdits, + BUTTON_Recalc, + BUTTON_Apply, + BUTTON_Cancel }; wxString translate(const std::string& str); diff --git a/src/gui/main.cpp b/src/gui/main.cpp index d85adb85..19ad44ef 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -26,6 +26,7 @@ #include "../parsers.h" #include "main.h" #include "settings.h" +#include "editor.h" #include #include @@ -490,7 +491,54 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { } void Launcher::OnEditMetadata(wxCommandEvent& event) { + //Parse the userlist and masterlist and get a list of installed plugins. + list plugins; + YAML::Node mlist, ulist; + list mlist_plugins, ulist_plugins; + + for (fs::directory_iterator it(_game.DataPath()); it != fs::directory_iterator(); ++it) { + if (fs::is_regular_file(it->status()) && (it->path().extension().string() == ".esp" || it->path().extension().string() == ".esm")) { + + boss::Plugin plugin(it->path().filename().string()); + plugins.push_back(plugin); + } + } + + if (fs::exists(_game.MasterlistPath())) { + try { + mlist = YAML::LoadFile(_game.MasterlistPath().string()); + } catch (YAML::ParserException& e) { + //LOG_ERROR("Error: %s", e.getString().c_str()); + wxMessageBox( + FromUTF8(format(loc::translate("Error: Masterlist parsing failed. %1%")) % e.what()), + translate("BOSS: Error"), + wxOK | wxICON_ERROR, + NULL); + } + if (mlist["plugins"]) + mlist_plugins = mlist["plugins"].as< list >(); + } + + if (fs::exists(_game.UserlistPath())) { + try { + ulist = YAML::LoadFile(_game.UserlistPath().string()); + } catch (YAML::ParserException& e) { + //LOG_ERROR("Error: %s", e.getString().c_str()); + wxMessageBox( + FromUTF8(format(loc::translate("Error: Userlist parsing failed. %1%")) % e.what()), + translate("BOSS: Error"), + wxOK | wxICON_ERROR, + NULL); + } + if (ulist["plugins"]) + ulist_plugins = ulist["plugins"].as< list >(); + } + + + Editor *editor = new Editor(translate("BOSS: Metadata Editor"), this); + editor->SetIcon(wxIconLocation("BOSS.exe")); + editor->Show(); } void Launcher::OnViewLastReport(wxCommandEvent& event) { @@ -498,7 +546,6 @@ void Launcher::OnViewLastReport(wxCommandEvent& event) { } void Launcher::OnOpenSettings(wxCommandEvent& event) { - //Tell the user that stuff is happenining. SettingsFrame *settings = new SettingsFrame(translate("BOSS: Settings"), this, _settings); settings->SetIcon(wxIconLocation("BOSS.exe")); settings->Show(); diff --git a/src/helpers.cpp b/src/helpers.cpp index 4e91ab0e..f6ba8890 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -106,15 +106,14 @@ namespace boss { /// Array used to try each of the expressions defined above using /// an iteration for each of them. - boost::regex* version_checks[] = { - new boost::regex(regex1, boost::regex::icase), - new boost::regex(regex2, boost::regex::icase), - new boost::regex(regex3, boost::regex::icase), - new boost::regex(regex4, boost::regex::icase), - new boost::regex(regex5, boost::regex::icase), //This incorrectly identifies "OBSE v19" where 19 is any integer. - new boost::regex(regex6, boost::regex::icase), //This is responsible for metallicow's false positive. - new boost::regex(regex7, boost::regex::icase), - 0 + boost::regex version_checks[7] = { + boost::regex(regex1, boost::regex::icase), + boost::regex(regex2, boost::regex::icase), + boost::regex(regex3, boost::regex::icase), + boost::regex(regex4, boost::regex::icase), + boost::regex(regex5, boost::regex::icase), //This incorrectly identifies "OBSE v19" where 19 is any integer. + boost::regex(regex6, boost::regex::icase), //This is responsible for metallicow's false positive. + boost::regex(regex7, boost::regex::icase) }; ////////////////////////////////////////////////////////////////////////// diff --git a/src/helpers.h b/src/helpers.h index 45af487c..4a72872a 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -39,7 +39,7 @@ namespace boss { /// Array used to try each of the expressions defined using /// an iteration for each of them. - extern boost::regex* version_checks[]; + extern boost::regex version_checks[7]; ////////////////////////////////////////////////////////////////////////// // Helper functions diff --git a/src/metadata.cpp b/src/metadata.cpp index 4a0d8fdd..cf50298e 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -29,7 +29,7 @@ #include #include -#include +#include using namespace std; @@ -217,9 +217,9 @@ namespace boss { begin = text.begin(); end = text.end(); - /* for(int j = 0; boost::regex* re = version_checks[j]; j++) { + for(int j = 0; j < 7 && version.empty(); j++) { boost::smatch what; - while (boost::regex_search(begin, end, what, *re)) { + while (boost::regex_search(begin, end, what, version_checks[j])) { if (what.empty()) continue; @@ -228,9 +228,9 @@ namespace boss { continue; version = boost::trim_copy(string(match.first, match.second)); + break; } - cout << "Checking regex: " << j << endl; - }*/ + } size_t pos1 = text.find("{{BASH:"); if (pos1 == string::npos)