mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Started to implement XHTML report generator. Metadata.cpp edit is to speed up sorting for more rapid testing, will be reverted later. Dummy data is being fed in generators.h until the interface is implemented. Uses PugiXML for writing and (EventShim)[https://github.com/jwmcpeak/EventShim] for getting W3C events in IE8 (for now, at least).
This commit is contained in:
+4
-4
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+192
@@ -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();
|
||||
Executable
+124
@@ -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;
|
||||
}
|
||||
+2
-2
@@ -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() {
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
@@ -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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef __BOSS_GENERATORS__
|
||||
#define __BOSS_GENERATORS__
|
||||
|
||||
#include "helpers.h"
|
||||
#include "metadata.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
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<Message>& 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<Message>::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<Message>& 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<Message> 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
|
||||
+8
-12
@@ -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 <ostream>
|
||||
#include <algorithm>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "viewer.h"
|
||||
|
||||
#include <wx/webview.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user