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)