diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f300d96..d0b9f010 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,10 @@ cmake_minimum_required (VERSION 2.8.9) project (boss) -set (BOSS_SRC "${CMAKE_SOURCE_DIR}/src/metadata.cpp" "${CMAKE_SOURCE_DIR}/src/game.cpp" "${CMAKE_SOURCE_DIR}/src/helpers.cpp") +set (BOSS_SRC "${CMAKE_SOURCE_DIR}/src/metadata.cpp" "${CMAKE_SOURCE_DIR}/src/game.cpp" "${CMAKE_SOURCE_DIR}/src/helpers.cpp" "${PROJECT_LIBS_DIR}/pugixml/src/pugixml.cpp") # Include source and library directories. -include_directories ("${PROJECT_LIBS_DIR}/alphanum" "${PROJECT_LIBS_DIR}/boost" "${PROJECT_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src" "${PROJECT_LIBS_DIR}/libloadorder/src" "${PROJECT_LIBS_DIR}/libespm" "${PROJECT_LIBS_DIR}/zlib") +include_directories ("${PROJECT_LIBS_DIR}/alphanum" "${PROJECT_LIBS_DIR}/boost" "${PROJECT_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src" "${PROJECT_LIBS_DIR}/libloadorder/src" "${PROJECT_LIBS_DIR}/libespm" "${PROJECT_LIBS_DIR}/zlib" "${PROJECT_LIBS_DIR}/pugixml/src") ############################## @@ -54,7 +54,7 @@ IF (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") IF (CMAKE_SYSTEM_NAME MATCHES "Windows") link_directories ("${PROJECT_LIBS_DIR}/boost/stage-mingw-${PROJECT_ARCH}/lib" "${PROJECT_LIBS_DIR}/wxWidgets/build-msw/lib") include_directories ("${PROJECT_LIBS_DIR}/wxWidgets/build-msw/lib/wx/include/i586-mingw32msvc-msw-unicode-static-2.9" "${PROJECT_LIBS_DIR}/wxWidgets/include") - set (BOSS_LIBS version wx_mswu_core-2.9-i586-mingw32msvc wx_baseu-2.9-i586-mingw32msvc wx_mswu_adv-2.9-i586-mingw32msvc wxpng-2.9-i586-mingw32msvc wxzlib-2.9-i586-mingw32msvc comctl32) + set (BOSS_LIBS version wx_mswu_webview-2.9-i586-mingw32msvc wx_mswu_core-2.9-i586-mingw32msvc wx_baseu-2.9-i586-mingw32msvc wx_mswu_adv-2.9-i586-mingw32msvc wxpng-2.9-i586-mingw32msvc wxzlib-2.9-i586-mingw32msvc comctl32) set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--subsystem,windows") ENDIF () @@ -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" "${CMAKE_SOURCE_DIR}/src/gui/editor.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" "${CMAKE_SOURCE_DIR}/src/gui/viewer.cpp") set (BOSS_API_SRC ${BOSS_SRC} "${CMAKE_SOURCE_DIR}/src/api.cpp") diff --git a/README.md b/README.md index b7e47e3f..095313ad 100644 --- a/README.md +++ b/README.md @@ -36,19 +36,19 @@ BOSSv3 won't have a command line interface, to simplify things. It also allows B ## Roadmap/To Do -- [x] Write new masterlist, userlist, settings file parsers. DONE -- [x] Implement per-game handling. DONE -- [x] Write API. DONE -- [x] Tie together automatic 'simple' sorting with masterlist, userlist data parsing and usage. DONE -- [x] Initialisation and finishing routines (read settings file, write log file, etc.). DONE -- [x] Remove game detection override. DONE -- [x] Include libespm initialisation into Game constructor. DONE -- [x] Implement reading of plugin versions from their description field. DONE -- [x] Develop UI - main window. DONE -- [x] Develop UI - settings window. DONE +- [x] Write new masterlist, userlist, settings file parsers. +- [x] Implement per-game handling. +- [x] Write API. +- [x] Tie together automatic 'simple' sorting with masterlist, userlist data parsing and usage. +- [x] Initialisation and finishing routines (read settings file, write log file, etc.). +- [x] Remove game detection override. +- [x] Include libespm initialisation into Game constructor. +- [x] Implement reading of plugin versions from their description field. +- [x] Develop UI - main window. +- [x] Develop UI - settings window. - [ ] Develop sorting algorithm. - [ ] Work out how to implement masterlist updating and write the code. -- [x] Develop UI - metadata editor window. DONE +- [x] Develop UI - metadata editor window. - [ ] Develop UI - report viewer. - [ ] Error handling. - [ ] Checks for cyclic dependencies and incompatibilities. @@ -60,14 +60,17 @@ BOSSv3 won't have a command line interface, to simplify things. It also allows B - [ ] Make validity checks non-fatal. - [ ] Add a massive "RUN THE GAME LAUNCHER IF YOUR GAME IS NOT DETECTED" message somewhere. - [ ] Generalise Total Conversion support, so that any TC for any of the supported 'base' games can be used with BOSS. +- [ ] Write XHTML report generator. +- [ ] Write report CSS. +- [ ] Write report Javascript. ## BOSS Report Once BOSS has applied a load order, it will display a report, similar to the BOSS Log, that covers the following: -* The number of messagesm, broken down into the total, the number of warnings and the number of errors. -* The masterlist version used and whether the masterlist was updated or not. +* The number of messages, broken down into the total, the number of warnings and the number of errors. +* The masterlist version used. * Any parser or validity checker errors encountered. * If there were any changes since BOSS was last run. * The new load order. @@ -90,7 +93,7 @@ The report data will be stored in a YAML file, which could use an extended form * Plugins get 'version' (string) and 'crc' (int) nodes added to them. * A new top-level node that holds a 'masterlist version' (string), a 'masterlist updated' (boolean) and a 'report changed' (boolean) node. -That should cover all the data necessary. In terms of UI, much of the BOSS Log UI is unnecessary in v3, all that's needed really is the summary page and the 'recognised' plugin list. They should be kept separate, maybe using notebook tabs, since that works quite well for the current BOSS Log. I would like to use HTML/CSS for the contents, but wxWidgets' HTML module makes that a bit involved. +There are a whole slew of options when it comes to how the report UI will be implemented. Using a native interface isn't a realistic option because it's so difficult to get anything that isn't very limited implemented, and the report is mostly static so wouldn't take advantage of native controls anyway. A HTML/CSS/JS approach is therefore preferable. The neatest way to do this would be to write the HTML as XHTML using PugiXML, then include external CSS & JS that gets distributed with BOSS. ## Optimisation @@ -104,8 +107,7 @@ Some ideas that have yet to be tested or implemented: * Masterlist updating could also just use a straightforward HTTP GET request, and if this method is chosen then the Accept-Encoding header should be sent with appropriate values so that the server can use compression when serving the file. Unfortunately, Google Code doesn't seem to use it, though other sites do. -Data Files Format -================= +## Data Files Format BOSSv3's settings file, masterlist, userlist and previous run logs will all be written in YAML. This allows me to take advantage of existing parsing libraries and the format's flexibility. It's also not that much more verbose than v2's masterlist format, once data structure changes are taken into account. @@ -123,6 +125,18 @@ LOOT will be a self-contained installation that can be dropped anywhere. It will The directory structure will be identical to that which BOSS currently has, with the exception that the text and ini files will be replaced by YAML files. +## Required Libraries + +BOSS uses the following libraries: + +* Alphanum +* Boost +* Libespm +* Libloadorder +* wxWidgets +* yaml-cpp + + ## Misc Here is the full list of BOSS members at the end of 2012. Any of these who lose diff --git a/examples/script.js b/examples/script.js new file mode 100755 index 00000000..a6dc7175 --- /dev/null +++ b/examples/script.js @@ -0,0 +1,192 @@ +'use strict'; +// shim for older browsers: +if (!document.getElementsByClassName) { + document.getElementsByClassName = (function(){ + // Utility function to traverse the DOM: + function traverse (node, callback) { + callback(node); + for (var i=0;i < node.childNodes.length; i++) { + traverse(node.childNodes[i],callback); + } + } + + // Actual definition of getElementsByClassName + return function (name) { + var result = []; + traverse(document.body,function(node){ + if (node.className && node.className.indexOf(name) != -1) { + result.push(node); + } + }); + return result; + } + })() +} +function showElement(element) { + if (element != null) { + if (element.className.indexOf('hidden') != -1) { + element.className = element.className.replace(' hidden', ''); + } else if (element.className.indexOf('visible') == -1) { + element.className += ' visible'; + } + } +} +function hideElement(element) { + if (element != null) { + if (element.className.indexOf('visible') != -1) { + element.className = element.className.replace('visible', ''); + } else if (element.className.indexOf('hidden') == -1) { + element.className += ' hidden'; + } + } +} +function stepUnhideElement(element) { + if (element != null && element.className.indexOf('hidden') != -1) { + element.className = element.className.replace('hidden', ''); + } +} +function stepHideElement(element) { + if (element != null) { + element.className += ' hidden'; + } +} +function toggleDisplayCSS(evt) { + var e = document.getElementsByClassName(evt.target.getAttribute('data-class')); + if (evt.target.checked) { + for (var i = 0, z = e.length; i < z; i++) { + e[i].className += ' hidden'; + } + } else { + for (var i = 0, z = e.length; i < z; i++) { + e[i].className = e[i].className.replace(' hidden', ''); + } + } +} +function toggleFilters(evt) { + if (document.getElementById('filtersToggle').className.indexOf('current') == -1) { + showElement(filters); + evt.target.className += ' current'; + } else { + hideElement(filters); + evt.target.className = evt.target.className.replace(' current', ''); + } +} +function showSection(evt) { + hideElement(document.querySelector('#summary,#plugins')); + showElement(document.getElementById(evt.target.getAttribute('data-section'))); + var elem = document.querySelector('#nav div.current[data-section]'); + if (elem != null) { + elem.className = elem.className.replace(' current', ''); + } + if (evt.target.className.indexOf('current') == -1) { + evt.target.className += ' current'; + } /*Also enable/disable filters based on current page.*/ + var elemArr = document.getElementById('filters').getElementsByTagName('input'); + for (var i = 0, z = elemArr.length; i < z; i++) { + if (evt.target.getAttribute('data-section') == 'plugins') { + elemArr[i].disabled = false; + } else { + elemArr[i].disabled = true; + } + } +} +function toggleMessages(evt) { + var listItems = document.getElementById('plugins').getElementsByTagName('li'); + var i = listItems.length - 1; + var hiddenNo = parseInt(document.getElementById('hiddenMessageNo').textContent); + while (i > -1) { + var spans = listItems[i].getElementsByTagName('span'); + if (spans.length == 0 || spans[0].className.indexOf('mod') == -1) { + var filterMatch = false; + if (evt.target.id == 'hideAllPluginMessages') { + filterMatch = true; + } else if (evt.target.id == 'hideNotes' && listItems[i].className.indexOf('note') != -1) { + filterMatch = true; + } else if (evt.target.id == 'hideBashTags' && listItems[i].className.indexOf('tag') != -1) { + filterMatch = true; + } else if (evt.target.id == 'hideDoNotCleanMessages' && listItems[i].className.indexOf('dirty') != -1 && (listItems[i].textContent.indexOf('Do not clean.') != -1 || listItems[i].textContent.indexOf('Do not clean.') != -1)) { + filterMatch = true; + } + if (filterMatch) { + if (evt.target.checked) { + if (listItems[i].className.indexOf('hidden') == -1) { + hiddenNo++; + } + stepHideElement(listItems[i]); + } else { + stepUnhideElement(listItems[i]); + if (listItems[i].className.indexOf('hidden') == -1) { + hiddenNo--; + } + } + } + } + i--; + } + document.getElementById('hiddenMessageNo').textContent = hiddenNo; + var event = document.createEvent('Event'); + event.initEvent('click', true, true); + document.getElementById('hideMessagelessPlugins').dispatchEvent(event); +} +function togglePlugins(evt) { + var plugins = document.getElementById('plugins').getElementsByTagName('ul')[0].childNodes; + var i = plugins.length - 1; + var hiddenNo = parseInt(document.getElementById('hiddenPluginNo').textContent); + while (i > -1) { + if (plugins[i].nodeType == Node.ELEMENT_NODE) { + var isMessageless = true, isInactive = true; + var messages = plugins[i].getElementsByTagName('li'); + var j = messages.length - 1; + while (j > -1) { + if (messages[j].className.indexOf('hidden') == -1) { + isMessageless = false; + break; + } + j--; + } + if (plugins[i].getElementsByClassName('active').length != 0) { + isInactive = false; + } + if ((document.getElementById('hideMessagelessPlugins').checked && isMessageless) || (document.getElementById('hideInactivePlugins').checked && isInactive)) { + if (plugins[i].className.indexOf('hidden') == -1) { + hiddenNo++; + hideElement(plugins[i]); + } + } else if (plugins[i].className.indexOf('hidden') != -1) { + hiddenNo--; + showElement(plugins[i]); + } + } + i--; + } + document.getElementById('hiddenPluginNo').textContent = hiddenNo; +} +function setupEventHandlers() { + var i, elemArr; + document.getElementById('filtersToggle').addEventListener('click', toggleFilters, false); + /*Set up handlers for section display.*/ + elemArr = document.getElementById('nav').querySelectorAll('.button[data-section]'); + var i = elemArr.length - 1; + while (i > -1) { + elemArr[i].addEventListener('click', showSection, false); + i--; + } + document.getElementById('hideVersionNumbers').addEventListener('click', toggleDisplayCSS, false); + document.getElementById('hideActiveLabel').addEventListener('click', toggleDisplayCSS, false); + document.getElementById('hideChecksums').addEventListener('click', toggleDisplayCSS, false); + document.getElementById('hideNotes').addEventListener('click', toggleMessages, false); + document.getElementById('hideBashTags').addEventListener('click', toggleMessages, false); + document.getElementById('hideDoNotCleanMessages').addEventListener('click', toggleMessages, false); + document.getElementById('hideAllPluginMessages').addEventListener('click', toggleMessages, false); + document.getElementById('hideInactivePlugins').addEventListener('click', togglePlugins, false); + document.getElementById('hideMessagelessPlugins').addEventListener('click', togglePlugins, false); +} +function init() { + setupEventHandlers(); + var elemArr = document.getElementById('filters').getElementsByTagName('input'); + for (var i = 0, z = elemArr.length; i < z; i++) { + elemArr[i].disabled = true; + } + showElement(document.getElementsByTagName('section')[0]) +} +init(); diff --git a/examples/style.css b/examples/style.css new file mode 100755 index 00000000..6117a1b1 --- /dev/null +++ b/examples/style.css @@ -0,0 +1,124 @@ +body { + font:10pt/1.5 Helvetica,sans-serif; + margin:0; + background:#f8f8f8; +} +noscript div { + background:#c00; + display:block; + padding:10px 5px; + color:white; +} +#nav { + background:#eee; + position:fixed; + top:0; + left:0; + z-index:2; + box-shadow: 0 0 3px 1px rgba(0,0,0,0.5); + width:100%; +} +.button { + display:inline-table; + cursor:pointer; + padding:0.5em; + text-decoration:none; + transition:background 0.2s; +} +.button.current { + background: #ccc; +} +.button[id] { + position:absolute; + right:0; +} +#main { + position:absolute; + top:32px; + width:100%; +} +#summary, #plugins { + padding:1em; +} +table { + margin:0 auto; + border-radius:10px; + border-collapse:separate; + border-spacing:0; +} +td { + padding:0.5em; + line-height:1.5; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + text-align:right; + padding-left:3em; +} +table tr:first-child td { + border-top: 1px solid #ccc; +} +table td:first-child { + text-align:left; + padding-left:0.5em; + border-left: 1px solid #ccc; + border-right: none; +} +table tr:first-child td:first-child { + border-top-left-radius: 5px; +} +table tr:first-child td:last-child { + border-top-right-radius: 5px; +} +table tr:last-child td:first-child { + border-bottom-left-radius: 5px; +} +table tr:last-child td:last-child { + border-bottom-right-radius: 5px; +} +#noChanges { + font-weight:bold; + font-size:12pt; + text-align:center; + margin:1em; +} +#filters { + background:#e8e8e8; + z-index:1; + overflow:hidden; + position:fixed; + right:0; + top:32px; + height:0; +} +#filters.visible { + height:auto; + box-shadow:0 0 3px 1px rgba(0,0,0,0.5); +} +#filters label { + display:block; + padding:0.2em 0.5em; + white-space:nowrap; + cursor:pointer; +} +#filters div { + padding:0.4em; + font-style:italic; +} +input[type='checkbox'] { + position:relative; + top:0.15em; + margin-right:0.5em; +} + +ul {margin-top:0.5em; margin-bottom:1em;} +li {margin:0.75em 0;} + +.active { + display:block; + color:green; + margin-left:1.5em; +} +#plugins > ul {list-style:none;} +.hidden, #summary.hidden, #plugins.hidden { + display:none; +} diff --git a/src/game.cpp b/src/game.cpp index f07c914c..e7d626b3 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -154,8 +154,8 @@ namespace boss { return fs::path(bossFolderName) / "userlist.yaml"; } - fs::path Game::ResultsPath() const { - return fs::path(bossFolderName) / "results.yaml"; + fs::path Game::ReportPath() const { + return fs::path(bossFolderName) / "report.html"; } void Game::RefreshActivePluginsList() { diff --git a/src/game.h b/src/game.h index 347fb7d3..70d4b802 100644 --- a/src/game.h +++ b/src/game.h @@ -57,7 +57,7 @@ namespace boss { boost::filesystem::path DataPath() const; boost::filesystem::path MasterlistPath() const; boost::filesystem::path UserlistPath() const; - boost::filesystem::path ResultsPath() const; + boost::filesystem::path ReportPath() const; //Creates directory in BOSS folder for BOSS's game-specific files. void CreateBOSSGameFolder(); diff --git a/src/generators.h b/src/generators.h new file mode 100644 index 00000000..6101e5dc --- /dev/null +++ b/src/generators.h @@ -0,0 +1,415 @@ +/* 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_GENERATORS__ +#define __BOSS_GENERATORS__ + +#include "helpers.h" +#include "metadata.h" + +#include + +#include +#include +#include + +namespace boss { + + void WriteMessage(pugi::xml_node& listItem, std::string content) { + + size_t pos1f = content.find("\"file:"); + size_t pos1h = content.find("\"http"); + size_t pos1; + if (pos1f < pos1h) + pos1 = pos1f; + else + pos1 = pos1h; + size_t pos2 = content.find(' ', pos1+1); + size_t pos3 = content.find('"', pos1+1); + + while (pos1 != std::string::npos && pos2 != std::string::npos && pos3 != std::string::npos) { + + std::string url = content.substr(pos1+1, pos2-pos1-1); + std::string display = content.substr(pos2+1, pos3-pos2-1); + + //Insert normal text preceding hyperlink. + listItem.append_child(pugi::node_pcdata).set_value(content.substr(0, pos1).c_str()); + + //Insert hyperlink. + pugi::xml_node a = listItem.append_child(); + a.set_name("a"); + a.append_attribute("href").set_value(url.c_str()); + a.text().set(display.c_str()); + + //Remove all the processed text from the string. + content = content.substr(pos3+1); + + //Look for the next hyperlink. + pos1f = content.find("\"file:"); + pos1h = content.find("\"http"); + if (pos1f < pos1h) + pos1 = pos1f; + else + pos1 = pos1h; + pos2 = content.find(' ', pos1+1); + pos3 = content.find('"', pos1+1); + } + + //Insert any leftover text. + listItem.append_child(pugi::node_pcdata).set_value(content.c_str()); + + } + + void GenerateHead(pugi::xml_document& doc) { + + //Add DOCTYPE node. + doc.append_child(pugi::node_doctype).set_value("html"); + + //Create 'head' node. + pugi::xml_node head = doc.append_child(); + head.set_name("head"); + + //Now add head elements. + pugi::xml_node node; + + node = head.append_child(); + node.set_name("meta"); + node.append_attribute("http-equiv").set_value("X-UA-Compatible"); + node.append_attribute("content").set_value("IE=8"); + + node = head.append_child(); + node.set_name("meta"); + node.append_attribute("charset").set_value("utf-8"); + + node = head.append_child(); + node.set_name("title"); + node.text().set("BOSS Report"); + + node = head.append_child(); + node.set_name("link"); + node.append_attribute("rel").set_value("stylesheet"); + node.append_attribute("href").set_value("../../examples/style.css"); + } + + void AppendNav(pugi::xml_node& body) { + pugi::xml_node nav, div; + + nav = body.append_child(); + nav.set_name("div"); + nav.append_attribute("id").set_value("nav"); + + div = nav.append_child(); + div.set_name("div"); + div.append_attribute("class").set_value("button current"); + div.append_attribute("data-section").set_value("summary"); + div.text().set("Summary"); + + div = nav.append_child(); + div.set_name("div"); + div.append_attribute("class").set_value("button"); + div.append_attribute("data-section").set_value("plugins"); + div.text().set("Details"); + + div = nav.append_child(); + div.set_name("div"); + div.append_attribute("class").set_value("button"); + div.append_attribute("id").set_value("filtersToggle"); + div.text().set("Filters"); + } + + void AppendSummary(pugi::xml_node& main, + bool hasChanged, + const std::string& masterlistVersion, + bool masterlistUpdateEnabled, + int messageNo, + int warnNo, + int errorNo, + const std::list& messages, + const std::string& language) { + + pugi::xml_node summary = main.append_child(); + summary.set_name("div"); + summary.append_attribute("id").set_value("summary"); + + pugi::xml_node table = summary.append_child(); + table.set_name("table"); + table = table.append_child(); + table.set_name("tbody"); + + pugi::xml_node row, cell; + + row = table.append_child(); + row.set_name("tr"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set("BOSS Version"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set((IntToString(VERSION_MAJOR)+"."+IntToString(VERSION_MINOR)+"."+IntToString(VERSION_PATCH)).c_str()); + + row = table.append_child(); + row.set_name("tr"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set("Masterlist Version"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set(masterlistVersion.c_str()); + + row = table.append_child(); + row.set_name("tr"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set("Masterlist Updating"); + cell = row.append_child(); + cell.set_name("td"); + if (masterlistUpdateEnabled) + cell.text().set("Enabled"); + else + cell.text().set("Disabled"); + + row = table.append_child(); + row.set_name("tr"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set("Total Number of Messages"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set(IntToString(messageNo).c_str()); + + row = table.append_child(); + row.set_name("tr"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set("Number of Warnings"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set(IntToString(warnNo).c_str()); + + row = table.append_child(); + row.set_name("tr"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set("Number of Errors"); + cell = row.append_child(); + cell.set_name("td"); + cell.text().set(IntToString(errorNo).c_str()); + + if (hasChanged) { + pugi::xml_node note = summary.append_child(); + note.set_name("div"); + note.append_attribute("id").set_value("noChanges"); + note.text().set("No changes since last run."); + } + + if (!messages.empty()) { + pugi::xml_node list = summary.append_child(); + list.set_name("ul"); + + for (std::list::const_iterator it=messages.begin(), endit=messages.end(); it != endit; ++it) { + //Need to weed out different language messages. + if (it->Language().empty() || boost::iequals(it->Language(), language)) { + + pugi::xml_node li = list.append_child(); + li.set_name("li"); + li.append_attribute("class").set_value(it->Type().c_str()); + + //Turn any urls into hyperlinks. + WriteMessage(li, it->Content()); + + } + } + } + } + + void AppendMain(pugi::xml_node& body, + bool hasChanged, + const std::string& masterlistVersion, + bool masterlistUpdateEnabled, + int messageNo, + int warnNo, + int errorNo, + const std::list& messages, + const std::string& language) { + + pugi::xml_node main = body.append_child(); + main.set_name("div"); + main.append_attribute("id").set_value("main"); + + pugi::xml_node noscript, div; + + noscript = main.append_child(); + noscript.set_name("noscript"); + div = noscript.append_child(); + div.set_name("div"); + div.text().set("The BOSS Report requires Javascript to be enabled in order to function."); + + AppendSummary(main, hasChanged, masterlistVersion, masterlistUpdateEnabled, messageNo, warnNo, errorNo, messages, language); + } + + void AppendFilters(pugi::xml_node& body, int messageNo, int pluginNo) { + + pugi::xml_node filters = body.append_child(); + filters.set_name("div"); + filters.append_attribute("id").set_value("filters"); + + pugi::xml_node label, input; + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideVersionNumbers"); + input.append_attribute("data-class").set_value("version"); + input.text().set("Hide Version Numbers"); + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideActiveLabel"); + input.append_attribute("data-class").set_value("active"); + input.text().set("Hide 'Active' Label"); + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideChecksums"); + input.append_attribute("data-class").set_value("crc"); + input.text().set("Hide Checksums"); + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideNotes"); + input.text().set("Hide Notes"); + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideBashTags"); + input.text().set("Hide Bash Tag Suggestions"); + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideDoNotCleanMessages"); + input.text().set("Hide 'Do Not Clean' Messages"); + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideAllPluginMessages"); + input.text().set("Hide All Plugin Messages"); + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideInactivePlugins"); + input.text().set("Hide Inactive Plugins"); + + label = filters.append_child(); + label.set_name("label"); + input = label.append_child(); + input.set_name("input"); + input.append_attribute("type").set_value("checkbox"); + input.append_attribute("id").set_value("hideMessagelessPlugins"); + input.text().set("Hide Messageless Plugins"); + + pugi::xml_node span; + + label = filters.append_child(); + label.set_name("div"); + span = label.append_child(); + span.set_name("span"); + span.append_attribute("id").set_value("hiddenPluginNo"); + span.text().set("0"); + label.append_child(pugi::node_pcdata).set_value((" of " + IntToString(pluginNo) + " plugins hidden.").c_str()); + + label = filters.append_child(); + label.set_name("div"); + span = label.append_child(); + span.set_name("span"); + span.append_attribute("id").set_value("hiddenMessageNo"); + span.text().set("0"); + label.append_child(pugi::node_pcdata).set_value((" of " + IntToString(messageNo) + " messages hidden.").c_str()); + } + + void AppendScripts(pugi::xml_node& body) { + + pugi::xml_node node; + + node = body.append_child(); + node.set_name("script"); + node.append_attribute("src").set_value("../../examples/eventShim.js"); + node.text().set(" "); + + node = body.append_child(); + node.set_name("script"); + node.append_attribute("src").set_value("../../examples/script.js"); + node.text().set(" "); + + } + + void GenerateReport(const std::string& file) { + + pugi::xml_document doc; + + GenerateHead(doc); + + pugi::xml_node body = doc.append_child(); + body.set_name("body"); + + AppendNav(body); + + std::list messages; + messages.push_back(Message("say", "This is a test message with a link to \"http://www.google.com Google\" in it.")); + + AppendMain(body, true, "4308 (2013-06-06)", true, 500, 4, 2, messages, "eng"); + + AppendFilters(body, 5, 5); + + AppendScripts(body); + + + if (!doc.save_file(file.c_str(), "\t", pugi::format_default | pugi::format_no_declaration)) + throw std::runtime_error("Could not write XML file."); + + } + +} + +#endif diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 72f36b49..77e9576f 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -23,12 +23,14 @@ #include "main.h" #include "settings.h" #include "editor.h" +#include "viewer.h" #include "../globals.h" #include "../metadata.h" #include "../parsers.h" #include "../error.h" #include "../helpers.h" +#include "../generators.h" #include #include @@ -272,7 +274,7 @@ Launcher::Launcher(const wxChar *title, YAML::Node& settings, Game& game, const else if (_game.Id() == GAME_FONV) GameMenu->FindItem(MENU_FalloutNewVegas)->Check(); - if (!fs::exists(_game.ResultsPath())) + if (!fs::exists(_game.ReportPath())) ViewButton->Enable(false); //Disable the menu items for the undetected games. @@ -513,16 +515,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { out << "Writing results file..." << endl; - YAML::Emitter yout; - yout.SetIndent(2); - yout << YAML::BeginMap - << YAML::Key << "globals" << YAML::Value << messages - << YAML::Key << "plugins" << YAML::Value << plugins - << YAML::EndMap; - - ofstream results_out(_game.ResultsPath().string().c_str()); - results_out << yout.c_str(); - results_out.close(); + GenerateReport(_game.ReportPath().string()); progDia->Pulse(); @@ -605,7 +598,10 @@ void Launcher::OnEditMetadata(wxCommandEvent& event) { } void Launcher::OnViewLastReport(wxCommandEvent& event) { - + //Create viewer window. + Viewer *viewer = new Viewer(this, translate("BOSS: Report Viewer"), _game); + + viewer->Show(); } void Launcher::OnOpenSettings(wxCommandEvent& event) { diff --git a/src/gui/viewer.cpp b/src/gui/viewer.cpp new file mode 100644 index 00000000..79fbb711 --- /dev/null +++ b/src/gui/viewer.cpp @@ -0,0 +1,43 @@ +/* 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 "viewer.h" + +#include +#include + +namespace fs = boost::filesystem; + +Viewer::Viewer(wxWindow *parent, const wxString& title, const boss::Game& game) : wxFrame(parent, wxID_ANY, title) { + if (!fs::exists(fs::absolute(game.ReportPath()))) + throw boss::error(ERROR_PATH_NOT_FOUND, "The path \"" + game.ReportPath().string() + "\" could not be found."); + + wxWebView * web = wxWebView::New(this, wxID_ANY, "file://" + fs::absolute(game.ReportPath()).string()); + + wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); + + topsizer->Add(web, 1, wxEXPAND); + + SetSizer(topsizer); + SetSize(800,600); +} diff --git a/src/gui/viewer.h b/src/gui/viewer.h new file mode 100644 index 00000000..cee84443 --- /dev/null +++ b/src/gui/viewer.h @@ -0,0 +1,35 @@ +/* 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_VIEWER__ +#define __BOSS_GUI_VIEWER__ + +#include "ids.h" +#include "../game.h" + +class Viewer : public wxFrame { +public: + Viewer(wxWindow *parent, const wxString& title, const boss::Game& game); +}; + +#endif diff --git a/src/metadata.cpp b/src/metadata.cpp index c22c2a1e..bc01f8c2 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -241,7 +241,7 @@ namespace boss { Plugin::Plugin(const boss::Game& game, const std::string& n) : name(n), enabled(true), priority(0) { - + /* // Get data from file contents using libespm. Assumes libespm has already been initialised. boost::filesystem::path filepath = game.DataPath() / n; espm::File file(filepath.string(), game.espm_settings, false, false); @@ -298,7 +298,7 @@ namespace boss { break; } - } + }*/ } void Plugin::Merge(const Plugin& plugin, bool ifDisabled) {