diff --git a/.hgignore b/.hgignore index 4539299..2e155dd 100644 --- a/.hgignore +++ b/.hgignore @@ -1,2 +1,11 @@ syntax: glob -Build/ \ No newline at end of file +*.orig +*.suo +.git +Build/ +Documentation/Documentation/* +Documentation/Artifacts/* +Documentation/Manual/_build/* +Configuration/Configuration/bin/** +Configuration/Configuration/obj/** + diff --git a/Assets/Environment/Common/Layouts/Default 16x9/Layout.xml b/Assets/Environment/Common/Layouts/Default 16x9/Layout.xml deleted file mode 100644 index 4a1a58c..0000000 --- a/Assets/Environment/Common/Layouts/Default 16x9/Layout.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Assets/Environment/Common/Layouts/Default 4x3/Age.otf b/Assets/Environment/Common/Layouts/Default 4x3/Age.otf deleted file mode 100644 index 5d76781..0000000 Binary files a/Assets/Environment/Common/Layouts/Default 4x3/Age.otf and /dev/null differ diff --git a/Assets/Environment/Common/Layouts/Default 4x3/Layout.xml b/Assets/Environment/Common/Layouts/Default 4x3/Layout.xml deleted file mode 100644 index 471c965..0000000 --- a/Assets/Environment/Common/Layouts/Default 4x3/Layout.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Assets/Environment/Common/Layouts/Default 4x3/background.png b/Assets/Environment/Common/Layouts/Default 4x3/background.png deleted file mode 100644 index ce0e1a0..0000000 Binary files a/Assets/Environment/Common/Layouts/Default 4x3/background.png and /dev/null differ diff --git a/Assets/Environment/Common/Layouts/Default 4x3/highlight.wav b/Assets/Environment/Common/Layouts/Default 4x3/highlight.wav deleted file mode 100644 index 589d167..0000000 Binary files a/Assets/Environment/Common/Layouts/Default 4x3/highlight.wav and /dev/null differ diff --git a/Assets/Environment/Common/Layouts/Default 4x3/load.wav b/Assets/Environment/Common/Layouts/Default 4x3/load.wav deleted file mode 100644 index 077480e..0000000 Binary files a/Assets/Environment/Common/Layouts/Default 4x3/load.wav and /dev/null differ diff --git a/Assets/Environment/Common/Layouts/Default 4x3/logo.png b/Assets/Environment/Common/Layouts/Default 4x3/logo.png deleted file mode 100644 index 54bd5ac..0000000 Binary files a/Assets/Environment/Common/Layouts/Default 4x3/logo.png and /dev/null differ diff --git a/Assets/Environment/Common/Layouts/Default 4x3/select.wav b/Assets/Environment/Common/Layouts/Default 4x3/select.wav deleted file mode 100644 index 6678b93..0000000 Binary files a/Assets/Environment/Common/Layouts/Default 4x3/select.wav and /dev/null differ diff --git a/Assets/Environment/Common/Layouts/Default 4x3/square.png b/Assets/Environment/Common/Layouts/Default 4x3/square.png deleted file mode 100644 index de7cb82..0000000 Binary files a/Assets/Environment/Common/Layouts/Default 4x3/square.png and /dev/null differ diff --git a/Assets/Environment/Common/Layouts/Default 4x3/unload.wav b/Assets/Environment/Common/Layouts/Default 4x3/unload.wav deleted file mode 100644 index 65f069a..0000000 Binary files a/Assets/Environment/Common/Layouts/Default 4x3/unload.wav and /dev/null differ diff --git a/Configuration/Configuration.sln b/Configuration/Configuration.sln new file mode 100644 index 0000000..8011aba --- /dev/null +++ b/Configuration/Configuration.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Configuration", "Configuration\Configuration.csproj", "{90F163C8-2147-46C9-8BF5-C51116856F62}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {90F163C8-2147-46C9-8BF5-C51116856F62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90F163C8-2147-46C9-8BF5-C51116856F62}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90F163C8-2147-46C9-8BF5-C51116856F62}.Debug|x86.ActiveCfg = Debug|x86 + {90F163C8-2147-46C9-8BF5-C51116856F62}.Debug|x86.Build.0 = Debug|x86 + {90F163C8-2147-46C9-8BF5-C51116856F62}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90F163C8-2147-46C9-8BF5-C51116856F62}.Release|Any CPU.Build.0 = Release|Any CPU + {90F163C8-2147-46C9-8BF5-C51116856F62}.Release|x86.ActiveCfg = Release|x86 + {90F163C8-2147-46C9-8BF5-C51116856F62}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Configuration/Configuration/App.config b/Configuration/Configuration/App.config new file mode 100644 index 0000000..fad249e --- /dev/null +++ b/Configuration/Configuration/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Configuration/Configuration/App.xaml b/Configuration/Configuration/App.xaml new file mode 100644 index 0000000..a07c142 --- /dev/null +++ b/Configuration/Configuration/App.xaml @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/Configuration/Configuration/App.xaml.cs b/Configuration/Configuration/App.xaml.cs new file mode 100644 index 0000000..ce657d8 --- /dev/null +++ b/Configuration/Configuration/App.xaml.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace Configuration +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + + } +} diff --git a/Configuration/Configuration/Assets/Icons/Add.png b/Configuration/Configuration/Assets/Icons/Add.png new file mode 100644 index 0000000..cf2e43e Binary files /dev/null and b/Configuration/Configuration/Assets/Icons/Add.png differ diff --git a/Configuration/Configuration/Assets/Icons/Delete.png b/Configuration/Configuration/Assets/Icons/Delete.png new file mode 100644 index 0000000..27a3623 Binary files /dev/null and b/Configuration/Configuration/Assets/Icons/Delete.png differ diff --git a/Configuration/Configuration/Builder.cs b/Configuration/Configuration/Builder.cs new file mode 100644 index 0000000..0684668 --- /dev/null +++ b/Configuration/Configuration/Builder.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Configuration.ViewModel; +using System.IO; +namespace Configuration +{ + public class Builder + { + public void LoadMain(ref MainVM main, ObservableCollectionlayouts, ObservableCollection collections) + { + //todo :make paths relative + ConfFileParser ini = new ConfFileParser(RetroFE.GetAbsolutePath() + "/Settings.conf"); + + main.IsFullscreen = ToBool(ini.GetSetting("fullscreen")); + main.IsHorizontalStretch = ToBool(ini.GetSetting("horizontal")); + main.IsVerticalStretch = ToBool(ini.GetSetting("vertical")); + if (!main.IsHorizontalStretch) + { + main.HorizontalResolution = Convert.ToInt32(ini.GetSetting("horizontal")); + } + if (!main.IsVerticalStretch) + { + main.VerticalResolution = Convert.ToInt32(ini.GetSetting("vertical")); + } + + main.Layout = layouts.FirstOrDefault(row => row == ini.GetSetting("layout")); + + main.IsMouseHidden = ToBool(ini.GetSetting("hideMouse")); + main.IsParenthesisVisible = !ToBool(ini.GetSetting("showParenthesis")); + main.IsBracesVisible = !ToBool(ini.GetSetting("showSquareBrackets")); + string firstCollection = ini.GetSetting("firstCollection"); + if(firstCollection == "") + { + firstCollection = "Main"; + } + main.FirstCollection = collections.FirstOrDefault(row => row.Name == firstCollection); + main.IsVideoEnabled = ToBool(ini.GetSetting("videoEnable")); + main.VideoLoop = Convert.ToInt32(ini.GetSetting("videoLoop")); + main.IsInfiniteLoop = (main.VideoLoop == 0); + main.IsExitOnFirstBack = ToBool(ini.GetSetting("exitOnFirstPageBack")); + main.AttractModeTime = Convert.ToInt32(ini.GetSetting("attractModeTime")); + main.IsAttractModeEnabled = (main.AttractModeTime != 0); + } + + public void LoadController(ref ControllerVM vm) + { + //todo :make paths relative + ConfFileParser ini = new ConfFileParser(RetroFE.GetAbsolutePath() + "/Controls.conf"); + vm.ScrollNext = ini.GetSetting("nextItem"); + vm.ScrollPrevious = ini.GetSetting("previousItem"); + vm.PageUp = ini.GetSetting("pageUp"); + vm.PageDown = ini.GetSetting("pageDown"); + vm.SelectItem = ini.GetSetting("select"); + vm.Back = ini.GetSetting("back"); + vm.Quit = ini.GetSetting("quit"); + } + + public ObservableCollection LoadLaunchers() + { + //todo :make paths relative + ObservableCollection launchers = new ObservableCollection(); + + string[] files = Directory.GetFiles(RetroFE.GetAbsolutePath() + "/Launchers", "*.conf"); + + foreach (string file in files) + { + LauncherVM vm = new LauncherVM(); + ConfFileParser ini = new ConfFileParser(file); + + vm.Name = System.IO.Path.GetFileNameWithoutExtension(file); + vm.ExecutablePath = ini.GetSetting("executable"); + vm.Arguments = ini.GetSetting("arguments"); + launchers.Add(vm); + } + + return launchers; + } + + public ObservableCollection LoadCollections(ObservableCollection launchers) + { + //todo :make paths relative + ObservableCollection collections = new ObservableCollection(); + + string[] dirs = Directory.GetDirectories(RetroFE.GetAbsolutePath() + "/Collections"); + + foreach (string dir in dirs) + { + string settingsFile = Path.Combine(dir, "Settings.conf"); + string menuFile = Path.Combine(dir, "Menu.xml"); + CollectionVM vm = new CollectionVM(); + ConfFileParser ini = new ConfFileParser(settingsFile); + MenuParser mp = new MenuParser(); + string launcher = ini.GetSetting("launcher"); + vm.Name = System.IO.Path.GetFileNameWithoutExtension(dir); + vm.Launcher = launchers.FirstOrDefault(row => row.Name == launcher); + vm.ListPath = ini.GetSetting("list.path"); + vm.Layout = ini.GetSetting("layout"); + + if (vm.Layout == "") + { + vm.IsDefaultLayout = true; + } + vm.FileExtensions = ini.GetSetting("list.extensions"); + vm.MediaPathVideo = ini.GetSetting("media.video"); + vm.MediaPathTitle = ini.GetSetting("media.title"); + vm.MediaPathLogo = ini.GetSetting("media.logo"); + vm.MediaPathTitle = ini.GetSetting("media.title"); + vm.MediaPathSnap = ini.GetSetting("media.snap"); + vm.MediaPathBox = ini.GetSetting("media.box"); + vm.MediaPathCart = ini.GetSetting("media.cart"); + + //todo: read submenus + + vm.Submenus = mp.ReadCollections(menuFile); + collections.Add(vm); + } + + return collections; + } + public ObservableCollection LoadLayouts() + { + //todo :make paths relative + ObservableCollection layouts = new ObservableCollection(); + + string[] dirs = Directory.GetDirectories(RetroFE.GetAbsolutePath() + "/Layouts"); + + foreach (string dir in dirs) + { + string layout = System.IO.Path.GetFileNameWithoutExtension(dir); + layouts.Add(layout); + } + + return layouts; + } + + private bool ToBool(string value) + { + value = value.Trim().ToLower(); + + return (value == "yes" || value == "true" || value == "stretch"); + } + } +} diff --git a/Configuration/Configuration/ConfFileParser.cs b/Configuration/Configuration/ConfFileParser.cs new file mode 100644 index 0000000..2f7e90d --- /dev/null +++ b/Configuration/Configuration/ConfFileParser.cs @@ -0,0 +1,146 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; + +public class ConfFileParser +{ + private Dictionary keyPairs = new Dictionary(); + private String FilePath; + + /// + /// Opens the INI file at the given path and enumerates the values in the IniParser. + /// + /// Full path to INI file. + public ConfFileParser(String filePath) + { + TextReader iniFile = null; + String strLine = null; + + FilePath = filePath; + + if (File.Exists(filePath)) + { + try + { + iniFile = new StreamReader(filePath); + + strLine = iniFile.ReadLine(); + + while (strLine != null) + { + strLine = strLine.Trim(); + + if (strLine != "") + { + int commentStart = strLine.IndexOf("#"); + if(commentStart > 0) + { + strLine = strLine.Substring(0, commentStart-1); + } + + string[] propertyPair = strLine.Split(new char[] { '=' }, 2); + + if (propertyPair.Length > 1) + { + string key = propertyPair[0].Trim(); + string value = propertyPair[1].Trim(); + keyPairs.Add(key, value); + } + } + + strLine = iniFile.ReadLine(); + } + + } + catch (Exception ex) + { + throw ex; + } + finally + { + if (iniFile != null) + iniFile.Close(); + } + } + else + throw new FileNotFoundException("Unable to locate " + filePath); + + } + + /// + /// Returns the value for the given section, key pair. + /// + /// Section name. + /// Key name. + public String GetSetting(String settingName) + { + if(keyPairs.ContainsKey(settingName)) + return (String)keyPairs[settingName]; + + return ""; + } + + /// + /// Adds or replaces a setting to the table to be saved. + /// + /// Section to add under. + /// Key name to add. + /// Value of key. + public void AddSetting(String settingName, String settingValue) + { + keyPairs[settingName] = settingValue; + } + + /// + /// Remove a setting. + /// + /// Section to add under. + /// Key name to add. + public void DeleteSetting(String settingName) + { + if (keyPairs.ContainsKey(settingName)) + keyPairs.Remove(settingName); + } + + /// + /// Save settings to new file. + /// + /// New file path. + public void SaveSettings(String newFilePath) + { + String tmpValue = ""; + String strToSave = ""; + + foreach (string property in keyPairs.Keys) + { + tmpValue = (String)keyPairs[property]; + + if (tmpValue != null) + tmpValue = "=" + tmpValue; + + strToSave += (property + tmpValue + "\r\n"); + } + + strToSave += "\r\n"; + + try + { + TextWriter tw = new StreamWriter(newFilePath); + tw.Write(strToSave); + tw.Close(); + } + catch (Exception ex) + { + throw ex; + } + } + + /// + /// Save settings back to ini file. + /// + public void SaveSettings() + { + SaveSettings(FilePath); + } +} \ No newline at end of file diff --git a/Configuration/Configuration/ConfFileSaver.cs b/Configuration/Configuration/ConfFileSaver.cs new file mode 100644 index 0000000..bf9fbbd --- /dev/null +++ b/Configuration/Configuration/ConfFileSaver.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Configuration +{ + class ConfFileSaver + { + public void AddOption(string key, string value) + { + Options.Add(key, value); + } + + public void AddOption(string key, bool value) + { + string strValue = (value) ? "yes" : "no"; + Options.Add(key, strValue); + } + + + public void AddOption(string key, int value) + { + string strValue = Convert.ToString(value); + + Options.Add(key, strValue); + } + + public void Save(string filePath) + { + TextWriter iniFile = null; + + try + { + iniFile = new StreamWriter(filePath); + + foreach (KeyValuePair option in Options) + { + iniFile.Write(option.Key + " = " + option.Value + Environment.NewLine); + } + + } + catch (Exception ex) + { + throw ex; + } + finally + { + if (iniFile != null) + iniFile.Close(); + } + } + + private Dictionary Options = new Dictionary(); + } +} diff --git a/Configuration/Configuration/Configuration.csproj b/Configuration/Configuration/Configuration.csproj new file mode 100644 index 0000000..537d00a --- /dev/null +++ b/Configuration/Configuration/Configuration.csproj @@ -0,0 +1,182 @@ + + + + + Debug + AnyCPU + {90F163C8-2147-46C9-8BF5-C51116856F62} + WinExe + Properties + Configuration + Configuration + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + ManagedMinimumRules.ruleset + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + ManagedMinimumRules.ruleset + true + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + + + + + + + + + + + + + + + AddRemoveList.xaml + + + Collection.xaml + + + ControlInput.xaml + + + Launcher.xaml + + + MainSettings.xaml + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Configuration/Configuration/Converter/CollectionExistsConverter.cs b/Configuration/Configuration/Converter/CollectionExistsConverter.cs new file mode 100644 index 0000000..a24f94f --- /dev/null +++ b/Configuration/Configuration/Converter/CollectionExistsConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Windows.Data; + +namespace Configuration.Converter +{ + public class CollectionExistsConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { +// if (targetType != typeof(bool)) +// throw new InvalidOperationException("The target is not a bool"); + return true; + // return !(bool)value; + } + + public object ConvertBack(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/Configuration/Configuration/Converter/InverseBooleanConverter.cs b/Configuration/Configuration/Converter/InverseBooleanConverter.cs new file mode 100644 index 0000000..67f6c23 --- /dev/null +++ b/Configuration/Configuration/Converter/InverseBooleanConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.Windows.Data; + +namespace Configuration.Converter +{ + [ValueConversion(typeof(bool), typeof(bool))] + public class InverseBooleanConverter: IValueConverter + { + public object Convert(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + if (targetType != typeof(bool)) + throw new InvalidOperationException("The target is not a bool"); + + return !(bool)value; + } + + public object ConvertBack(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/Configuration/Configuration/Converter/InverseBooleanToVisibilityConverter.cs b/Configuration/Configuration/Converter/InverseBooleanToVisibilityConverter.cs new file mode 100644 index 0000000..595cd59 --- /dev/null +++ b/Configuration/Configuration/Converter/InverseBooleanToVisibilityConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Windows; +using System.Windows.Data; + +namespace Configuration.Converter +{ + public class InverseBooleanToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + if (targetType != typeof(Visibility)) + throw new InvalidOperationException("The target is not a visibility type"); + + return (!(bool)value) ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + + } +} diff --git a/Configuration/Configuration/Converter/NullToVisibilityConverter.cs b/Configuration/Configuration/Converter/NullToVisibilityConverter.cs new file mode 100644 index 0000000..d255838 --- /dev/null +++ b/Configuration/Configuration/Converter/NullToVisibilityConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Windows; +using System.Windows.Data; + +namespace Configuration.Converter +{ + public class NullToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + if (targetType != typeof(Visibility)) + throw new InvalidOperationException("The target is not of type bool"); + + return ((object)value != null) ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + + } +} diff --git a/Configuration/Configuration/MainWindow.xaml b/Configuration/Configuration/MainWindow.xaml new file mode 100644 index 0000000..8c04d08 --- /dev/null +++ b/Configuration/Configuration/MainWindow.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Configuration/Configuration/MainWindow.xaml.cs b/Configuration/Configuration/MainWindow.xaml.cs new file mode 100644 index 0000000..4ed668c --- /dev/null +++ b/Configuration/Configuration/MainWindow.xaml.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Configuration.ViewModel; +namespace Configuration +{ + /// + /// Interaction logic for MainWindow.xaml + /// + /// + + public partial class MainWindow : Window + { + private TabItem LastSelectedTabItem; + public MainWindow() + { + + InitializeComponent(); + if (!File.Exists(RetroFE.GetAbsolutePath() + "/Core/RetroFE.exe")) + { + MessageBox.Show("Could not find RetroFE executable. Exiting."); + Close(); + } + else + { + MessageBox.Show("This tool has not had a lot of testing. " + Environment.NewLine + Environment.NewLine + "Back up your files and use at your own risk before using this tool."); + ObservableCollection layouts = new ObservableCollection(); + LauncherListVM launcher = this.TryFindResource("LauncherConfig") as LauncherListVM; + CollectionListVM collection = this.TryFindResource("CollectionConfig") as CollectionListVM; + ControllerVM controller = this.TryFindResource("ControllerConfig") as ControllerVM; + MainVM main = this.TryFindResource("MainConfig") as MainVM; + Builder b = new Builder(); + + launcher.LauncherCollection = b.LoadLaunchers(); + collection.CollectionList = b.LoadCollections(launcher.LauncherCollection); + main.Layouts = b.LoadLayouts(); + b.LoadMain(ref main, main.Layouts, collection.CollectionList); + b.LoadController(ref controller); + } + } + + private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + TabControl control = sender as TabControl; + + if (LastSelectedTabItem != null) + { + LastSelectedTabItem.Focus(); + Save((string)LastSelectedTabItem.Header); + } + + if (control != null && control.SelectedValue != null) + { + LastSelectedTabItem = control.SelectedItem as TabItem; + } + } + + private void TabControl_FocusableChanged(object sender, DependencyPropertyChangedEventArgs e) + { + TabControl control = sender as TabControl; + + if(control.SelectedItem != null) + { + TabItem item = control.SelectedItem as TabItem; + item.Focus(); + Save((string)item.Header); + } + } + + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + if (ConfigurationTabControl.SelectedItem != null) + { + TabItem item = ConfigurationTabControl.SelectedItem as TabItem; + item.Focus(); + Save((string)item.Header); + } + + } + + private void Save(string tabItem) + { + if (tabItem == "General") + { + MainVM main = this.TryFindResource("MainConfig") as MainVM; + main.Save(); + } + else if (tabItem == "Controller") + { + ControllerVM vm = this.TryFindResource("ControllerConfig") as ControllerVM; + vm.Save(); + } + else if (tabItem == "Launchers") + { + LauncherListVM vm = this.TryFindResource("LauncherConfig") as LauncherListVM; + vm.Save(vm.SelectedLauncher); + } + else if (tabItem == "Collections") + { + CollectionListVM vm = this.TryFindResource("CollectionConfig") as CollectionListVM; + vm.Save(vm.SelectedCollection); + } + } + + } +} diff --git a/Configuration/Configuration/MenuParser.cs b/Configuration/Configuration/MenuParser.cs new file mode 100644 index 0000000..140608e --- /dev/null +++ b/Configuration/Configuration/MenuParser.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Xml; + +public class MenuParser +{ + public ObservableCollection ReadCollections(string filePath) + { + + ObservableCollection list = new ObservableCollection(); + if (File.Exists(filePath)) + { + try + { + XmlReader reader = XmlReader.Create(filePath); + XmlDocument doc = new XmlDocument(); + reader.Read(); + doc.Load(reader); + + XmlNodeList items = doc.GetElementsByTagName("item"); + foreach (XmlNode item in items) + { + XmlAttribute name = item.Attributes["collection"]; + + if(name != null) + { + list.Add(name.Value); + } + } + } + catch (Exception ex) + { + throw ex; + } + } + + return list; + } + + /// + /// Save settings back to ini file. + /// + public void Save(ObservableCollection list, string filePath) + { + try + { + XmlDocument doc = new XmlDocument(); + XmlElement menu = doc.CreateElement("menu"); + + doc.AppendChild(menu); + + foreach (string item in list) + { + XmlElement node = doc.CreateElement("item"); + XmlAttribute attrib = doc.CreateAttribute("collection"); + attrib.Value = item; + menu.AppendChild(node); + node.AppendChild(attrib); + } + + doc.Save(filePath); + } + catch (Exception ex) + { + throw ex; + } + + +// SaveSettings(_FilePath); + } +} \ No newline at end of file diff --git a/Configuration/Configuration/Properties/AssemblyInfo.cs b/Configuration/Configuration/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c893c5f --- /dev/null +++ b/Configuration/Configuration/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Configuration")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Deere & Company")] +[assembly: AssemblyProduct("Configuration")] +[assembly: AssemblyCopyright("Copyright © Deere & Company 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Configuration/Configuration/Properties/Resources.Designer.cs b/Configuration/Configuration/Properties/Resources.Designer.cs new file mode 100644 index 0000000..744b8ff --- /dev/null +++ b/Configuration/Configuration/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18444 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Configuration.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Configuration.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Configuration/Configuration/Properties/Resources.resx b/Configuration/Configuration/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Configuration/Configuration/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Configuration/Configuration/Properties/Settings.Designer.cs b/Configuration/Configuration/Properties/Settings.Designer.cs new file mode 100644 index 0000000..c17d0c2 --- /dev/null +++ b/Configuration/Configuration/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18444 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Configuration.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Configuration/Configuration/Properties/Settings.settings b/Configuration/Configuration/Properties/Settings.settings new file mode 100644 index 0000000..8f2fd95 --- /dev/null +++ b/Configuration/Configuration/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Configuration/Configuration/RelayCommand.cs b/Configuration/Configuration/RelayCommand.cs new file mode 100644 index 0000000..b85758b --- /dev/null +++ b/Configuration/Configuration/RelayCommand.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +namespace Configuration +{ + public class RelayCommand : ICommand + { + #region Fields + + readonly Action _execute; + readonly Predicate _canExecute; + + #endregion // Fields + + #region Constructors + + /// + /// Creates a new command that can always execute. + /// + /// The execution logic. + public RelayCommand(Action execute) + : this(execute, null) + { + } + + /// + /// Creates a new command. + /// + /// The execution logic. + /// The execution status logic. + public RelayCommand(Action execute, Predicate canExecute) + { + if (execute == null) + throw new ArgumentNullException("execute"); + + _execute = execute; + _canExecute = canExecute; + } + + #endregion // Constructors + + #region ICommand Members + + public bool CanExecute(object parameters) + { + return _canExecute == null ? true : _canExecute(parameters); + } + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public void Execute(object parameters) + { + _execute(parameters); + } + + #endregion // ICommand Members + } +} diff --git a/Configuration/Configuration/RetroFE.cs b/Configuration/Configuration/RetroFE.cs new file mode 100644 index 0000000..c84b041 --- /dev/null +++ b/Configuration/Configuration/RetroFE.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + +namespace Configuration +{ + class RetroFE + { + public static string GetAbsolutePath() + { + string path = Environment.GetEnvironmentVariable("RETROFE_PATH"); + + if (path == null) + { + path = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).Directory.FullName; + } + + return path; + } + } +} diff --git a/Configuration/Configuration/View/AddRemoveList.xaml b/Configuration/Configuration/View/AddRemoveList.xaml new file mode 100644 index 0000000..0dd0f88 --- /dev/null +++ b/Configuration/Configuration/View/AddRemoveList.xaml @@ -0,0 +1,75 @@ + + + /Assets/Icons/Add.png + /Assets/Icons/Delete.png + + + + + + + + + + + + + + + + + + + + + Name + + + + + + + + + + + + + + diff --git a/Configuration/Configuration/View/AddRemoveList.xaml.cs b/Configuration/Configuration/View/AddRemoveList.xaml.cs new file mode 100644 index 0000000..ddf2c89 --- /dev/null +++ b/Configuration/Configuration/View/AddRemoveList.xaml.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Configuration.ViewModel; + +namespace Configuration.View +{ + /// + /// Interaction logic for AddRemoveList.xaml + /// + public partial class AddRemoveList : UserControl + { + + public ICommand AddListItemCommand + { + get { return (ICommand)GetValue(AddListItemCommandProperty); } + set { SetValue(ListItemsSourceProperty, value); } + } + + public static DependencyProperty AddListItemCommandProperty = DependencyProperty.Register("AddListItemCommand", typeof(ICommand), typeof(AddRemoveList)); + + public ICommand RemoveListItemCommand + { + get { return (ICommand)GetValue(RemoveListItemCommandProperty); } + set { SetValue(ListItemsSourceProperty, value); } + } + + public static DependencyProperty RemoveListItemCommandProperty = DependencyProperty.Register("RemoveListItemCommand", typeof(ICommand), typeof(AddRemoveList)); + + public System.Collections.IEnumerable ListItemsSource + { + get { return (ObservableCollection)GetValue(ListItemsSourceProperty); } + set { SetValue(ListItemsSourceProperty, value); } + } + + public static DependencyProperty ListItemsSourceProperty = DependencyProperty.Register("ListItemsSource", typeof(System.Collections.IEnumerable), typeof(AddRemoveList)); + + + public object SelectedItem + { + get { return (object)GetValue(SelectedItemProperty); } + set { SetValue(SelectedItemProperty, value); } + } + + public static DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(AddRemoveList)); + + + public String ListDisplayMemberPath + { + get { return (String)GetValue(ListDisplayMemberPathProperty); } + set { SetValue(ListDisplayMemberPathProperty, value); } + } + + public static DependencyProperty ListDisplayMemberPathProperty = DependencyProperty.Register("ListDisplayMemberPath", typeof(String), typeof(AddRemoveList)); + + + public AddRemoveList() + { + InitializeComponent(); + } + + private void HideAddPopup(object sender, RoutedEventArgs e) + { + AddPopup.IsOpen = false; + // AddName.Text = ""; + } + } +} diff --git a/Configuration/Configuration/View/Collection.xaml b/Configuration/Configuration/View/Collection.xaml new file mode 100644 index 0000000..b38e71b --- /dev/null +++ b/Configuration/Configuration/View/Collection.xaml @@ -0,0 +1,161 @@ + + + + + + + /Assets/Icons/Add.png + /Assets/Icons/Delete.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Configuration/Configuration/View/Collection.xaml.cs b/Configuration/Configuration/View/Collection.xaml.cs new file mode 100644 index 0000000..e413fee --- /dev/null +++ b/Configuration/Configuration/View/Collection.xaml.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Configuration.ViewModel; + +namespace Configuration.View +{ + /// + /// Interaction logic for Collection.xaml + /// + public partial class Collection : UserControl + { + + public CollectionVM Data + { + get { return (CollectionVM)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + public static DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(CollectionVM), typeof(Collection)); + + public System.Collections.IEnumerable LauncherCollection + { + get { return (System.Collections.IEnumerable)GetValue(LauncherCollectionProperty); } + set { SetValue(LauncherCollectionProperty, value); } + } + + public static DependencyProperty LauncherCollectionProperty = DependencyProperty.Register("LauncherCollection", typeof(System.Collections.IEnumerable), typeof(Collection)); + + public System.Collections.IEnumerable Layouts + { + get { return (System.Collections.IEnumerable)GetValue(LayoutsProperty); } + set { SetValue(LayoutsProperty, value); } + } + + public static DependencyProperty LayoutsProperty = DependencyProperty.Register("Layouts", typeof(System.Collections.IEnumerable), typeof(Collection)); + + + public System.Collections.IEnumerable Collections + { + get { return (System.Collections.IEnumerable)GetValue(CollectionsProperty); } + set { SetValue(CollectionsProperty, value); } + } + + public static DependencyProperty CollectionsProperty = DependencyProperty.Register("Collections", typeof(System.Collections.IEnumerable), typeof(Collection)); + + public Collection() + { + InitializeComponent(); + } + + } +} diff --git a/Configuration/Configuration/View/ControlInput.xaml b/Configuration/Configuration/View/ControlInput.xaml new file mode 100644 index 0000000..2b8f02a --- /dev/null +++ b/Configuration/Configuration/View/ControlInput.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Configuration/Configuration/View/ControlInput.xaml.cs b/Configuration/Configuration/View/ControlInput.xaml.cs new file mode 100644 index 0000000..77057f3 --- /dev/null +++ b/Configuration/Configuration/View/ControlInput.xaml.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Configuration.ViewModel; + +namespace Configuration.View +{ + /// + /// Interaction logic for ControlInput.xaml + /// + public partial class ControlInput : UserControl + { + Dictionary SDLAsciiDescMap = new Dictionary(); + public ControlInput() + { + InitializeComponent(); + SDLAsciiDescMap[0x8] = "Backspace"; + SDLAsciiDescMap[0x9] = "Tab"; + SDLAsciiDescMap[0x0C] = "Clear"; + SDLAsciiDescMap[0x0D] = "Return"; + SDLAsciiDescMap[0x13] = "Pause"; + SDLAsciiDescMap[0x14] = "CapsLock"; + SDLAsciiDescMap[0x1B] = "Escape"; + SDLAsciiDescMap[0x20] = "Space"; + SDLAsciiDescMap[0x21] = "PageUp"; + SDLAsciiDescMap[0x22] = "PageDown"; + SDLAsciiDescMap[0x23] = "End"; + SDLAsciiDescMap[0x24] = "Home"; + SDLAsciiDescMap[0x25] = "Left"; + SDLAsciiDescMap[0x26] = "Up"; + SDLAsciiDescMap[0x27] = "Right"; + SDLAsciiDescMap[0x28] = "Down"; + SDLAsciiDescMap[0x29] = "Select"; + SDLAsciiDescMap[0x2B] = "Execute"; + SDLAsciiDescMap[0x2C] = "PrintScreen"; + SDLAsciiDescMap[0x2D] = "Insert"; + SDLAsciiDescMap[0x2E] = "Delete"; + SDLAsciiDescMap[0x2F] = "Help"; + SDLAsciiDescMap[0x30] = "0"; + SDLAsciiDescMap[0x31] = "1"; + SDLAsciiDescMap[0x32] = "2"; + SDLAsciiDescMap[0x33] = "3"; + SDLAsciiDescMap[0x34] = "4"; + SDLAsciiDescMap[0x35] = "5"; + SDLAsciiDescMap[0x36] = "6"; + SDLAsciiDescMap[0x37] = "7"; + SDLAsciiDescMap[0x38] = "8"; + SDLAsciiDescMap[0x39] = "9"; + SDLAsciiDescMap[0x41] = "A"; + SDLAsciiDescMap[0x42] = "B"; + SDLAsciiDescMap[0x43] = "C"; + SDLAsciiDescMap[0x44] = "D"; + SDLAsciiDescMap[0x45] = "E"; + SDLAsciiDescMap[0x46] = "F"; + SDLAsciiDescMap[0x47] = "G"; + SDLAsciiDescMap[0x48] = "H"; + SDLAsciiDescMap[0x49] = "I"; + SDLAsciiDescMap[0x4A] = "J"; + SDLAsciiDescMap[0x4B] = "K"; + SDLAsciiDescMap[0x4C] = "L"; + SDLAsciiDescMap[0x4D] = "M"; + SDLAsciiDescMap[0x4E] = "N"; + SDLAsciiDescMap[0x4F] = "O"; + SDLAsciiDescMap[0x50] = "P"; + SDLAsciiDescMap[0x51] = "Q"; + SDLAsciiDescMap[0x52] = "R"; + SDLAsciiDescMap[0x53] = "S"; + SDLAsciiDescMap[0x54] = "T"; + SDLAsciiDescMap[0x55] = "U"; + SDLAsciiDescMap[0x56] = "V"; + SDLAsciiDescMap[0x57] = "W"; + SDLAsciiDescMap[0x58] = "X"; + SDLAsciiDescMap[0x59] = "Y"; + SDLAsciiDescMap[0x5A] = "Z"; + SDLAsciiDescMap[0x5B] = "Left GUI"; + SDLAsciiDescMap[0x5C] = "Right GUI"; + SDLAsciiDescMap[0x5D] = "Application"; + SDLAsciiDescMap[0x5F] = "Sleep"; + SDLAsciiDescMap[0x60] = "Keypad 0"; + SDLAsciiDescMap[0x61] = "Keypad 1"; + SDLAsciiDescMap[0x62] = "Keypad 2"; + SDLAsciiDescMap[0x63] = "Keypad 3"; + SDLAsciiDescMap[0x64] = "Keypad 4"; + SDLAsciiDescMap[0x65] = "Keypad 5"; + SDLAsciiDescMap[0x66] = "Keypad 6"; + SDLAsciiDescMap[0x67] = "Keypad 7"; + SDLAsciiDescMap[0x68] = "Keypad 8"; + SDLAsciiDescMap[0x69] = "Keypad 9"; + SDLAsciiDescMap[0x6A] = "*"; + SDLAsciiDescMap[0x6B] = "+"; + SDLAsciiDescMap[0x6C] = "Separator"; + SDLAsciiDescMap[0x6D] = "-"; + SDLAsciiDescMap[0x6E] = "."; + SDLAsciiDescMap[0x6F] = "/"; + SDLAsciiDescMap[0x70] = "F1"; + SDLAsciiDescMap[0x71] = "F2"; + SDLAsciiDescMap[0x72] = "F3"; + SDLAsciiDescMap[0x73] = "F4"; + SDLAsciiDescMap[0x74] = "F5"; + SDLAsciiDescMap[0x75] = "F6"; + SDLAsciiDescMap[0x76] = "F7"; + SDLAsciiDescMap[0x77] = "F8"; + SDLAsciiDescMap[0x78] = "F9"; + SDLAsciiDescMap[0x79] = "F10"; + SDLAsciiDescMap[0x7A] = "F11"; + SDLAsciiDescMap[0x7B] = "F12"; + SDLAsciiDescMap[0x7C] = "F13"; + SDLAsciiDescMap[0x7D] = "F14"; + SDLAsciiDescMap[0x7E] = "F15"; + SDLAsciiDescMap[0x7F] = "F16"; + SDLAsciiDescMap[0x80] = "F17"; + SDLAsciiDescMap[0x81] = "F18"; + SDLAsciiDescMap[0x82] = "F19"; + SDLAsciiDescMap[0x83] = "F20"; + SDLAsciiDescMap[0x84] = "F21"; + SDLAsciiDescMap[0x85] = "F22"; + SDLAsciiDescMap[0x86] = "F23"; + SDLAsciiDescMap[0x87] = "F24"; + SDLAsciiDescMap[0x90] = "Numlock"; + SDLAsciiDescMap[0x91] = "ScrollLock"; + SDLAsciiDescMap[0xA0] = "Left Shift"; + SDLAsciiDescMap[0xA1] = "Right Shift"; + SDLAsciiDescMap[0xA2] = "Left Ctrl"; + SDLAsciiDescMap[0xA3] = "Right Ctrl"; + SDLAsciiDescMap[0xA4] = "Left Menu"; + SDLAsciiDescMap[0xA5] = "Right Menu"; + SDLAsciiDescMap[0xC0] = "`"; + + } + + public ControllerVM Data + { + get { return (ControllerVM)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + public static DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(ControllerVM), typeof(ControlInput)); + + + private void TextBlock_PreviewKeyDown(object sender, KeyEventArgs e) + { + TextBox tb = sender as TextBox; + System.Windows.Forms.KeysConverter kc = new System.Windows.Forms.KeysConverter(); + int key = System.Windows.Input.KeyInterop.VirtualKeyFromKey(e.Key); + int systemKey = System.Windows.Input.KeyInterop.VirtualKeyFromKey(e.SystemKey); + + if (SDLAsciiDescMap.ContainsKey(key)) + { + tb.Text = SDLAsciiDescMap[key]; + } + else + if (SDLAsciiDescMap.ContainsKey(systemKey)) + { + tb.Text = SDLAsciiDescMap[systemKey]; + } + else + { + tb.Text = key + " " + e.SystemKey + " (none)"; + } + //todo, implement a map + + e.Handled = true; + } + } + +} diff --git a/Configuration/Configuration/View/Launcher.xaml b/Configuration/Configuration/View/Launcher.xaml new file mode 100644 index 0000000..3e51184 --- /dev/null +++ b/Configuration/Configuration/View/Launcher.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Configuration/Configuration/View/Launcher.xaml.cs b/Configuration/Configuration/View/Launcher.xaml.cs new file mode 100644 index 0000000..668771c --- /dev/null +++ b/Configuration/Configuration/View/Launcher.xaml.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Configuration.ViewModel; + +namespace Configuration.View +{ + /// + /// Interaction logic for Launcher.xaml + /// + public partial class Launcher : UserControl + { + public Launcher() + { + InitializeComponent(); + } + + + public LauncherVM Data + { + get { return (LauncherVM)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + public static DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(LauncherVM), typeof(Launcher)); + + } +} diff --git a/Configuration/Configuration/View/MainSettings.xaml b/Configuration/Configuration/View/MainSettings.xaml new file mode 100644 index 0000000..2ccd2cb --- /dev/null +++ b/Configuration/Configuration/View/MainSettings.xaml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Configuration/Configuration/View/MainSettings.xaml.cs b/Configuration/Configuration/View/MainSettings.xaml.cs new file mode 100644 index 0000000..3ce7e4b --- /dev/null +++ b/Configuration/Configuration/View/MainSettings.xaml.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Configuration.ViewModel; + +namespace Configuration.View +{ + /// + /// Interaction logic for MainSettings.xaml + /// + public partial class MainSettings : UserControl + { + public MainVM Data + { + get { return (MainVM)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + public static DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(MainVM), typeof(MainSettings)); + + public System.Collections.IEnumerable Launchers + { + get { return (System.Collections.IEnumerable)GetValue(LaunchersProperty); } + set { SetValue(LaunchersProperty, value); } + } + public static DependencyProperty LaunchersProperty = DependencyProperty.Register("Launchers", typeof(System.Collections.IEnumerable), typeof(MainSettings)); + + public System.Collections.IEnumerable Collections + { + get { return (System.Collections.IEnumerable)GetValue(CollectionsProperty); } + set { SetValue(CollectionsProperty, value); } + } + public static DependencyProperty CollectionsProperty = DependencyProperty.Register("Collections", typeof(System.Collections.IEnumerable), typeof(MainSettings)); + + + public MainSettings() + { + InitializeComponent(); + } + } +} diff --git a/Configuration/Configuration/ViewModel/CollectionListVM.cs b/Configuration/Configuration/ViewModel/CollectionListVM.cs new file mode 100644 index 0000000..41aabe1 --- /dev/null +++ b/Configuration/Configuration/ViewModel/CollectionListVM.cs @@ -0,0 +1,160 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using Configuration; + +namespace Configuration.ViewModel +{ + class CollectionListVM : INotifyPropertyChanged + { + ObservableCollection _CollectionList = new ObservableCollection(); + public ObservableCollection CollectionList + { + get { return _CollectionList; } + set { _CollectionList = value; NotifyPropertyChanged("CollectionList"); } + } + + CollectionVM _SelectedCollection = null; + public CollectionVM SelectedCollection + { + get { return _SelectedCollection; } + set { + if (_SelectedCollection != null) + { + Save(_SelectedCollection); + } + _SelectedCollection = value; + NotifyPropertyChanged("SelectedCollection"); + } + } + + ICommand _AddListItemCommand; + public ICommand AddListItemCommand + { + get + { + if (_AddListItemCommand == null) + { + _AddListItemCommand = new RelayCommand(param => this.AddCollection(param), param => this.CanAdd()); + } + + return _AddListItemCommand; + } + } + ICommand _RemoveListItemCommand; + public ICommand RemoveListItemCommand + { + get + { + if (_RemoveListItemCommand == null) + { + _RemoveListItemCommand = new RelayCommand(param => this.RemoveCollection(), param => this.CanDelete()); + } + + return _RemoveListItemCommand; + } + } + + public event PropertyChangedEventHandler PropertyChanged; + private void NotifyPropertyChanged(String propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + + private bool CanAdd() + { + return true; + } + + private bool CanDelete() + { + return (SelectedCollection != null); + } + + private void AddCollection(object param) + { + + CollectionVM cvm = new CollectionVM(); + cvm.Name = param as String; + NotifyPropertyChanged("CollectionList"); + + ConfFileSaver settings = new ConfFileSaver(); + ConfFileSaver include = new ConfFileSaver(); + ConfFileSaver exclude = new ConfFileSaver(); + MenuParser menu = new MenuParser(); + + //todo change path + string path = RetroFE.GetAbsolutePath() + "/Collections/" + cvm.Name; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + if (!File.Exists(path + "/Settings.conf")) + { + CollectionList.Add(cvm); + settings.Save(path + "/Settings.conf"); + + if (!File.Exists(path + "/Include.txt")) + { + include.Save(path + "/Include.txt"); + } + if (!File.Exists(path + "/Exclude.txt")) + { + exclude.Save(path + "/Exclude.txt"); + } + + //menu.Save(path + "/Menu.xml"); + } + } + + public void Save(CollectionVM cvm) + { + if (cvm == null) return; + + ConfFileSaver s = new ConfFileSaver(); + + if (!cvm.IsDefaultLayout) + { + s.AddOption("layout", cvm.Layout); + } + s.AddOption("launcher", (cvm.Launcher == null) ? "" : cvm.Launcher.Name); + s.AddOption("list.path", cvm.ListPath); + s.AddOption("list.extensions", cvm.FileExtensions); + s.AddOption("media.box", cvm.MediaPathBox); + s.AddOption("media.cart", cvm.MediaPathCart); + s.AddOption("media.logo", cvm.MediaPathLogo); + s.AddOption("media.snap", cvm.MediaPathSnap); + s.AddOption("media.title", cvm.MediaPathTitle); + s.AddOption("media.video", cvm.MediaPathVideo); + + //todo: change serverPath + string path = RetroFE.GetAbsolutePath() + "/Collections/" + cvm.Name + "/Settings.conf"; + s.Save(path); + } + + private bool RemoveCollection() + { + //todo: change location + string path = RetroFE.GetAbsolutePath() + "/Launchers/" + SelectedCollection.Name + ".conf"; + if (File.Exists(path)) + { + File.Delete(path); + } + + CollectionList.Remove(SelectedCollection); + + return true; + } + } +} diff --git a/Configuration/Configuration/ViewModel/CollectionVM.cs b/Configuration/Configuration/ViewModel/CollectionVM.cs new file mode 100644 index 0000000..fcbdd14 --- /dev/null +++ b/Configuration/Configuration/ViewModel/CollectionVM.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows.Forms; +using Configuration; + +namespace Configuration.ViewModel +{ + public class CollectionVM : INotifyPropertyChanged + { + String _Name; + public String Name + { + get { return _Name; } + set { _Name = value; NotifyPropertyChanged("Name"); } + } + + String _ListPath; + public String ListPath + { + get { return _ListPath; } + set { _ListPath = value; NotifyPropertyChanged("ListPath"); } + } + + String _FileExtensions = null; + public String FileExtensions + { + get { return _FileExtensions; } + set { _FileExtensions = value; NotifyPropertyChanged("FileExtensions"); } + } + + LauncherVM _Launcher = null; + public LauncherVM Launcher + { + get { return _Launcher; } + set { _Launcher = value; NotifyPropertyChanged("Launcher"); } + } + + String _Layout = null; + public String Layout + { + get { return _Layout; } + set { _Layout = value; NotifyPropertyChanged("Layout"); } + } + + bool _IsDefaultLayout; + public bool IsDefaultLayout + { + get { return _IsDefaultLayout; } + set { _IsDefaultLayout = value; NotifyPropertyChanged("IsDefaultLayout"); } + } + + + String _MediaPathVideo; + public String MediaPathVideo + { + get { return _MediaPathVideo; } + set { _MediaPathVideo = value; NotifyPropertyChanged("MediaPathVideo"); } + } + + String _MediaPathTitle; + public String MediaPathTitle + { + get { return _MediaPathTitle; } + set { _MediaPathTitle = value; NotifyPropertyChanged("MediaPathTitle"); } + } + String _MediaPathLogo; + public String MediaPathLogo + { + get { return _MediaPathLogo; } + set { _MediaPathLogo = value; NotifyPropertyChanged("MediaPathLogo"); } + } + String _MediaPathBox; + public String MediaPathBox + { + get { return _MediaPathBox; } + set { _MediaPathBox = value; NotifyPropertyChanged("MediaPathBox"); } + } + String _MediaPathCart; + public String MediaPathCart + { + get { return _MediaPathCart; } + set { _MediaPathCart = value; NotifyPropertyChanged("MediaPathCart"); } + } + String _MediaPathSnap; + public String MediaPathSnap + { + get { return _MediaPathSnap; } + set { _MediaPathSnap = value; NotifyPropertyChanged("MediaPathSnap"); } + } + + ObservableCollection _Submenus; + public ObservableCollection Submenus + { + get { return _Submenus; } + set { _Submenus = value; NotifyPropertyChanged("Submenus"); } + } + + ICommand _DeleteSubmenuCommand; + public ICommand DeleteSubmenuCommand + { + get + { + if (_DeleteSubmenuCommand == null) + { + _DeleteSubmenuCommand = new RelayCommand(param => this.DeleteSubmenu((string)param), param => true); + } + + return _DeleteSubmenuCommand; + } + } + + + + ICommand _BrowseFolderCommand; + public ICommand BrowseFolderCommand + { + get + { + if (_BrowseFolderCommand == null) + { + _BrowseFolderCommand = new RelayCommand(param => this.BrowseFolder((string)param), param => true); + } + + return _BrowseFolderCommand; + } + } + + public void DeleteSubmenu(string reserved) + { + } + + public void BrowseFolder(string type) + { + FolderBrowserDialog dialog = new FolderBrowserDialog(); + + if(dialog.ShowDialog() == DialogResult.OK) + { + string selectedPath = dialog.SelectedPath; + + if (type == "Video") + { + MediaPathVideo = selectedPath; + } + else if (type == "Title") + { + MediaPathTitle = selectedPath; + } + else if (type == "Snap") + { + MediaPathSnap = selectedPath; + } + else if (type == "Logo") + { + MediaPathLogo = selectedPath; + } + else if (type == "Cart") + { + MediaPathCart = selectedPath; + } + else if (type == "Box") + { + MediaPathBox = selectedPath; + } + else if (type == "Item") + { + ListPath = selectedPath; + } + } + } + + + public event PropertyChangedEventHandler PropertyChanged; + private void NotifyPropertyChanged(String propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + } +} diff --git a/Configuration/Configuration/ViewModel/ControllerVM.cs b/Configuration/Configuration/ViewModel/ControllerVM.cs new file mode 100644 index 0000000..5782612 --- /dev/null +++ b/Configuration/Configuration/ViewModel/ControllerVM.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows.Forms; +using Configuration; + +namespace Configuration.ViewModel +{ + public class ControllerVM : INotifyPropertyChanged + { + String _ScrollPrevious; + public String ScrollPrevious + { + get { return _ScrollPrevious; } + set { _ScrollPrevious = value; NotifyPropertyChanged("ScrollPrevious"); } + } + + String _ScrollNext; + public String ScrollNext + { + get { return _ScrollNext; } + set { _ScrollNext = value; NotifyPropertyChanged("ScrollNext"); } + } + + String _PageUp; + public String PageUp + { + get { return _PageUp; } + set { _PageUp = value; NotifyPropertyChanged("PageUp"); } + } + + String _PageDown; + public String PageDown + { + get { return _PageDown; } + set { _PageDown = value; NotifyPropertyChanged("PageDown"); } + } + + String _SelectItem; + public String SelectItem + { + get { return _SelectItem; } + set { _SelectItem = value; NotifyPropertyChanged("SelectItem"); } + } + + String _Back; + public String Back + { + get { return _Back; } + set { _Back = value; NotifyPropertyChanged("Back"); } + } + + String _Quit; + public String Quit + { + get { return _Quit; } + set { _Quit = value; NotifyPropertyChanged("Quit"); } + } + + public void Save() + { + ConfFileSaver s = new ConfFileSaver(); + + s.AddOption("previousItem", ScrollPrevious); + s.AddOption("nextItem", ScrollNext); + s.AddOption("pageUp", PageUp); + s.AddOption("pageDown", PageDown); + + s.AddOption("select", SelectItem); + s.AddOption("back", Back); + s.AddOption("quit", Quit); + + //todo: change location + string path = RetroFE.GetAbsolutePath() + "/Controls.conf"; + s.Save(path); + } + + + public event PropertyChangedEventHandler PropertyChanged; + private void NotifyPropertyChanged(String propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/Configuration/Configuration/ViewModel/LauncherListVM.cs b/Configuration/Configuration/ViewModel/LauncherListVM.cs new file mode 100644 index 0000000..a6f2bb2 --- /dev/null +++ b/Configuration/Configuration/ViewModel/LauncherListVM.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using System.IO; +using Configuration; + +namespace Configuration.ViewModel +{ + class LauncherListVM : INotifyPropertyChanged + { + ObservableCollection _LauncherCollection = new ObservableCollection(); + public ObservableCollection LauncherCollection + { + get { return _LauncherCollection; } + set { _LauncherCollection = value; NotifyPropertyChanged("LauncherCollection"); } + } + + LauncherVM _SelectedLauncher = null; + public LauncherVM SelectedLauncher + { + get { return _SelectedLauncher; } + set + { + if (_SelectedLauncher != null) + { + Save(_SelectedLauncher); + } + _SelectedLauncher = value; + NotifyPropertyChanged("SelectedLauncher"); + } + } + + ICommand _AddListItemCommand; + public ICommand AddListItemCommand + { + get + { + if (_AddListItemCommand == null) + { + _AddListItemCommand = new RelayCommand(param => this.AddLauncher(param), param => this.CanAdd()); + } + + return _AddListItemCommand; + } + } + ICommand _RemoveListItemCommand; + public ICommand RemoveListItemCommand + { + get + { + if (_RemoveListItemCommand == null) + { + _RemoveListItemCommand = new RelayCommand(param => this.RemoveLauncher(), param => this.CanDelete()); + } + + return _RemoveListItemCommand; + } + } + + + public event PropertyChangedEventHandler PropertyChanged; + private void NotifyPropertyChanged(String propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + + private bool CanAdd() + { + return true; + } + + private bool CanDelete() + { + return (SelectedLauncher != null); + } + + private void AddLauncher(object param) + { + + LauncherVM l = new LauncherVM(); + l.Name = param as String; + NotifyPropertyChanged("LauncherCollection"); + ConfFileSaver saver = new ConfFileSaver(); + + //todo change path + if (!File.Exists(RetroFE.GetAbsolutePath() + "/Launchers/" + l.Name + ".conf")) + { + LauncherCollection.Add(l); + saver.Save(RetroFE.GetAbsolutePath() + "/Launchers/" + l.Name + ".conf"); + } + } + + public void Save(LauncherVM launcher) + { + if (launcher == null) return; + + ConfFileSaver s = new ConfFileSaver(); + s.AddOption("executable", launcher.ExecutablePath); + s.AddOption("arguments", launcher.Arguments); + + //todo: change location + string path = RetroFE.GetAbsolutePath() + "/Launchers/" + SelectedLauncher.Name + ".conf"; + s.Save(path); + } + + private bool RemoveLauncher() + { + //todo: change location + string path = RetroFE.GetAbsolutePath() + "/Launchers/" + SelectedLauncher.Name + ".conf"; + if (File.Exists(path)) + { + File.Delete(path); + } + + LauncherCollection.Remove(SelectedLauncher); + + return true; + } + } +} diff --git a/Configuration/Configuration/ViewModel/LauncherVM.cs b/Configuration/Configuration/ViewModel/LauncherVM.cs new file mode 100644 index 0000000..1e85f44 --- /dev/null +++ b/Configuration/Configuration/ViewModel/LauncherVM.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows.Forms; +using Configuration; + +namespace Configuration.ViewModel +{ + public class LauncherVM : INotifyPropertyChanged + { + String _Name; + public String Name + { + get { return _Name; } + set { _Name = value; NotifyPropertyChanged("Name"); } + } + + String _ExecutablePath; + public String ExecutablePath + { + get { return _ExecutablePath; } + set { _ExecutablePath = value; NotifyPropertyChanged("ExecutablePath"); } + } + + String _Arguments; + public String Arguments + { + get { return _Arguments; } + set { _Arguments = value; NotifyPropertyChanged("Arguments"); } + } + + public event PropertyChangedEventHandler PropertyChanged; + private void NotifyPropertyChanged(String propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + ICommand _BrowseFileCommand; + public ICommand BrowseFileCommand + { + get + { + if (_BrowseFileCommand == null) + { + _BrowseFileCommand = new RelayCommand(param => this.BrowseFile(), param => true); + } + + return _BrowseFileCommand; + } + } + + + public void BrowseFile() + { + OpenFileDialog dialog = new OpenFileDialog(); + dialog.Multiselect = false; + + if (dialog.ShowDialog() == DialogResult.OK) + { + ExecutablePath = dialog.FileName; + } + } + + } +} diff --git a/Configuration/Configuration/ViewModel/MainVM.cs b/Configuration/Configuration/ViewModel/MainVM.cs new file mode 100644 index 0000000..a6f77b7 --- /dev/null +++ b/Configuration/Configuration/ViewModel/MainVM.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using Configuration; + +namespace Configuration.ViewModel +{ + public class MainVM : INotifyPropertyChanged + { + bool _IsFullscreen; + public bool IsFullscreen + { + get { return _IsFullscreen; } + set { _IsFullscreen = value; NotifyPropertyChanged("IsFullscreen"); } + } + + int _HorizontalResolution; + public int HorizontalResolution + { + get { return _HorizontalResolution; } + set { _HorizontalResolution = value; NotifyPropertyChanged("HorizontalResolution"); } + } + + bool _IsHorizontalStretch; + public bool IsHorizontalStretch + { + get { return _IsHorizontalStretch; } + set { _IsHorizontalStretch = value; NotifyPropertyChanged("IsHorizontalStretch"); } + } + + bool _IsVerticalStretch; + public bool IsVerticalStretch + { + get { return _IsVerticalStretch; } + set { _IsVerticalStretch = value; NotifyPropertyChanged("IsVerticalStretch"); } + } + + int _VerticalResolution; + public int VerticalResolution + { + get { return _VerticalResolution; } + set { _VerticalResolution = value; NotifyPropertyChanged("VerticalResolution"); } + } + + String _Layout; + public String Layout + { + get { return _Layout; } + set { _Layout = value; NotifyPropertyChanged("Layout"); } + } + + ObservableCollection _Layouts; + public ObservableCollection Layouts + { + get { return _Layouts; } + set { _Layouts = value; NotifyPropertyChanged("Layouts"); } + } + + + bool _IsMouseHidden; + public bool IsMouseHidden + { + get { return _IsMouseHidden; } + set { _IsMouseHidden = value; NotifyPropertyChanged("IsMouseHidden"); } + } + + bool _IsParenthesisVisible; + public bool IsParenthesisVisible + { + get { return _IsParenthesisVisible; } + set { _IsParenthesisVisible = value; NotifyPropertyChanged("IsParenthesisVisible"); } + } + + bool _IsBracesVisible; + public bool IsBracesVisible + { + get { return _IsBracesVisible; } + set { _IsBracesVisible = value; NotifyPropertyChanged("IsBracesVisible"); } + } + + CollectionVM _FirstCollection; + public CollectionVM FirstCollection + { + get { return _FirstCollection; } + set { _FirstCollection = value; NotifyPropertyChanged("FirstCollection"); } + } + + bool _IsVideoEnabled; + public bool IsVideoEnabled + { + get { return _IsVideoEnabled; } + set { _IsVideoEnabled = value; NotifyPropertyChanged("IsVideoEnabled"); } + } + + int _VideoLoop; + public int VideoLoop + { + get { return _VideoLoop; } + set { _VideoLoop = value; NotifyPropertyChanged("VideoLoop"); } + } + + bool _IsInfiniteLoop; + public bool IsInfiniteLoop + { + get { return _IsInfiniteLoop; } + set { _IsInfiniteLoop = value; NotifyPropertyChanged("IsInfiniteLoop"); } + } + + bool _IsExitOnFirstBack; + public bool IsExitOnFirstBack + { + get { return _IsExitOnFirstBack; } + set { _IsExitOnFirstBack = value; NotifyPropertyChanged("IsExitOnFirstBack"); } + } + + int _AttractModeTime; + public int AttractModeTime + { + get { return _AttractModeTime; } + set { _AttractModeTime = value; NotifyPropertyChanged("AttractModeTime"); } + } + + bool _IsAttractModeEnabled; + public bool IsAttractModeEnabled + { + get { return _IsAttractModeEnabled; } + set { _IsAttractModeEnabled = value; NotifyPropertyChanged("IsAttractModeEnabled"); } + } + + + public event PropertyChangedEventHandler PropertyChanged; + private void NotifyPropertyChanged(String propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + public void Save() + { + ConfFileSaver s = new ConfFileSaver(); + + if(IsVerticalStretch) + { + s.AddOption("vertical", "stretch"); + } + else + { + s.AddOption("vertical", VerticalResolution); + } + + if(IsHorizontalStretch) + { + s.AddOption("horizontal", "stretch"); + } + else + { + s.AddOption("horizontal", HorizontalResolution); + } + + s.AddOption("fullscreen", IsFullscreen); + s.AddOption("layout", (Layout == null) ? "Default" : Layout); + s.AddOption("hideMouse", IsMouseHidden); + s.AddOption("showParenthesis", IsParenthesisVisible); + s.AddOption("showSquareBrackets", IsBracesVisible); + s.AddOption("firstCollection", (FirstCollection == null) ? "Main" : FirstCollection.Name); + + s.AddOption("videoEnable", IsVideoEnabled); + s.AddOption("videoLoop", VideoLoop); + s.AddOption("exitOnFirstPageBack", IsExitOnFirstBack); + s.AddOption("attractModeTime", (IsAttractModeEnabled) ? AttractModeTime : 0); + + + //todo: change location + string path = RetroFE.GetAbsolutePath() + "/Settings.conf"; + s.Save(path); + } + + } +} diff --git a/Documentation/Manual/Configuration.rst b/Documentation/Manual/Configuration.rst new file mode 100644 index 0000000..351805c --- /dev/null +++ b/Documentation/Manual/Configuration.rst @@ -0,0 +1,478 @@ +.. _Configuration: + +================================== +Configuration +================================== + +RetroFE splits up configuration files to optimize flexability and portability. The configuration is broken up into the following categories: + +=========================== ================================================================================================================================================================== +Configuration Role +=========================== ================================================================================================================================================================== +Main Settings Defines screen resolution, controls, etc. +Launchers Defines what executables to use for launching menu items (emulators, apps, etc.) +Collections Defines directories to scan for populating lists, media (image and video) paths, and additional information for an item (i.e controls, instructions, histrory) +Main Menu Defines the list of collections to pick from on the main page. +=========================== ================================================================================================================================================================== + +Main Configuration +################################################ + +**/Settings.conf** + +See below for a list of supported properties. + +================================== ======================================================================================================== +Property Description +================================== ======================================================================================================== +horizontal Horizontal screen width (in pixels) +vertical Vertical screen width (in pixels) +fullscreen Display in fullscreen or windowed mode (yes/no) +layout Name of the default layout to use (unless overridden in the themes) +exitOnFirstPageBack Exit the frontend when the back button is pressed on the first page +attractModeTime Number of seconds while idleing to wait before entering attract mode (0 to disable) +showParenthesis Set to no if you want to hide parenthesis (and anything inside) for menu lists +showSquareBrackets Set to no if you want to hide braces (and anything inside) for menu lists +showVideo Set to no if you want to not load video (for slower systems) +videoLoop Number of times to loop video playback (enter 0 to continuously loop) +firstCollection Defines the first collection to load. If not specified, the "Main" collection will be used. +controls.previousItem The key to press to scroll to the previous item in a list +controls.nextItem The key to press to scroll to the next item in a list +controls.pageUp The key to press to scroll page up in a list +controls.pageDown The key to press to scroll page down in a list +controls.select The key to press to select (or launch) the selected list item +controls.back The key to press to return to the previous menu +controls.quit The key to press to quit the frontend +================================== ======================================================================================================== + +Basic example (640x480 fullscreen with controls configured): + +.. code-block:: javascript + + horizontal = 640 + vertical = 480 + fullscreen = yes + + controls.previousItem = Up + controls.nextItem = Down + controls.pageUp = Left + controls.pageDown = Right + controls.select = Space + controls.back = Escape + controls.quit = q + +See :ref:`ControlKeycodes` for a list of valid key codes + +.. _ConfigurationLaunchers: + + +Launchers +################################################ + +**/Launchers/.conf** + +*(where is the name of the launcher)* + +A launcher is a program (i.e. emulator, application, or game) which gets executed when a menu item is selected. Parameters can also be passed +into a launcher. + +See below for a list of supported properties. + +=========================== ================================================================== +Property Description +=========================== ================================================================== +executable Path of where the executable exists +arguments Arguments to pass when executing the launcher (i.e. ROM name) +=========================== ================================================================== + +See below for a basic launcher for the Nestopia emulator (/Launchers/nestopia.conf) + +.. code-block:: javascript + + executable = D:/Emulators/Nestopia/nestopia.exe + arguments = "%ITEM_FILEPATH%" + +%ITEM_FILEPATH% is a reserved variable name. See the variables table below for other variables that may be used. +Also note the quotes around "%ITEM_FILEPATH%" to help not confuse the executable from thinking that an item with spaces as multiple arguments. + +Assuming that "Super Mario Bros" was the selected item, the frontend will attempt to execute: + +.. code-block:: javascript + + "D:/Emulators/Nestopia/nestopia.exe" "D:/ROMs/Nintendo/Super Mario Bros.nes". + + You can also use relative paths (relative to the root folder of RetroFE) +.. code-block:: javascript + + executable = ../Emulators/Nestopia/nestopia.exe + arguments = "%ITEM_FILEPATH%" + + + +Variables +----------- +=========================== =========================== =============================================== +Variable Description Translated Example +=========================== =========================== =============================================== +%ITEM_FILEPATH% Full item path D:/ROMs/Nintendo/Super Mario Bros.nes +%ITEM_NAME% The item name Super Mario Bros +%ITEM_FILENAME% Filename without path Super Mario Bros.nes +%ITEM_DIRECTORY% Folder where file exists D:/ROMs/Nintendo +%ITEM_COLLECTION_NAME% Name of collection for item Nintendo Entertainment System +%RETROFE_PATH% Folder location of Frontend D:/Frontends/RetroFE +%RETROFE_EXEC_PATH% Location of RetroFE D:/Frontends/RetroFE/RetroFE.exe +=========================== =========================== =============================================== + +More elaborate example: + +.. code-block:: javascript + + # Have fceux load a save state automatically for the ROM when started + executable = D:/Emulators/fceux/fceux.exe + arguments = "%ITEM_FILEPATH%" -loadstate "%ITEM_DIRECTORY%/%ITEM_NAME%.fcs" + +.. _ConfigurationCollections: + +Collections +################################################ + +**/Collections//** + +A collection is a list of items to display on a menu. A collection can be built by scanning a list of files in a folder. Each collection configuration is broken up into three separate +configuration files (for portability). + + +================================== ================================================================================================================== +Configuration File Description +================================== ================================================================================================================== +Settings.conf Defines which launcher to use, item (ROM) folders, extensions, media paths, layout/theme, etc... +Mamelist.xml If this is a mame collection, place your mamelist.xml file in this folder. +Include.txt List the filenames (without the extension) to show up in your list. If empty, all files will be shown in the list. +Include.xml HyperSpin HyperList XML file to show up in your list. Ignored if Include.txt exists. +Exclude.txt List the filenames (without the extension) to hide from being shown up in your list +================================== ================================================================================================================== + +General Settings +---------------------- +**/Collections//Settings.conf** + +================================== ================================================================================================================================ +Property Description +================================== ================================================================================================================================ +launcher Launcher to use when item is selected (will look up /Launchers/.conf +layout The name of the layout to load for the collection (will read layout from Layouts/ +path Location of where files to launch exist +extensions Adds only files with the given extension to a list (comma separated) +snap Snapshot image folder +title Title screen image folder +video Video folder +box Box artwork folder +================================== ================================================================================================================================ + +The following example will use the launcher configuration from "/Launchers/nestopia.conf" and will use the layout in "Layouts/Nintendo Entertainment System" + +.. code-block:: javascript + + launcher = nestopia + layout = Nintendo Entertainment System + path = D:/ROMs/Nintendo Entertainment System + extensions = nes,zip + snap = D:/Media/Nintendo Entertainment System/Snaps + title = D:/Media/Nintendo Entertainment System/Titles + video = D:/Media/Nintendo Entertainment System/Videos + box = D:/Media/Nintendo Entertainment System/Box + + +Showing and Hiding Collection Items +------------------------------------------ +**/Collections//Include.txt** and **/Collections//Exclude.txt** + + +By default, RetroFE will show all items scanned in your folder path (assuming the extension matches). If an Include.txt file exists (and Include.txt is not empty), only the +items in that file will show up in the menu (assuming the file exists). + +If an Exclude.xml file exists, the file will always be hidden from the list, regardless if the file item is specified in Include.txt. + +All items listed in Include.txt and Exclude.txt are the name of the file (without the file extension). + +Example Include.txt file: + +.. code-block:: javascript + + Super Mario Bros (USA) + Contra (USA) + +Example Exclude.txt file: + +.. code-block:: javascript + + E.T. (USA) + Bayou Billy (USA) + + +.. _ConfigurationMenu: + +Main Menu +################################################ +**/Collections/Main/Menu.xml** + +This file defines what menu items are displayed on the first page. See below for a basic example: + +.. code-block:: xml + + + + + + +Note that for each item specified, one with an identical name (case sensitive) must exist in your collections folder. For the example above, the collection configuration in +/Collections/Nintendo Entertainment System/ and /Collections/Arcade/ must exist. + +.. _ControlKeycodes: + +Valid Key Codes +################ + +These codes were taken from https://wiki.libsdl.org/SDL_Keycode + +See below for a list of key codes that can be used for configuring the controls: + +===================== ================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================ +Code Notes +===================== ================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================ +"0" +"1" +"2" +"3" +"4" +"5" +"6" +"7" +"8" +"9" +"A" +"AC Back" the Back key (application control keypad) +"AC Bookmarks" the Bookmarks key (application control keypad) +"AC Forward" the Forward key (application control keypad) +"AC Home" the Home key (application control keypad) +"AC Refresh" the Refresh key (application control keypad) +"AC Search" the Search key (application control keypad) +"AC Stop" the Stop key (application control keypad) +"Again" the Again key (Redo) +"AltErase" Erase-Eaze +"'" +"Application" the Application / Compose / Context Menu (Windows) key +"AudioMute" the Mute volume key +"AudioNext" the Next Track media key +"AudioPlay" the Play media key +"AudioPrev" the Previous Track media key +"AudioStop" the Stop media key) +"B" +"\" Located at the lower left of the return key on ISO keyboards and at the right end of the QWERTY row on ANSI keyboards. Produces REVERSE SOLIDUS (backslash) and VERTICAL LINE in a US layout, REVERSE SOLIDUS and VERTICAL LINE in a UK Mac layout, NUMBER SIGN and TILDE in a UK Windows layout, DOLLAR SIGN and POUND SIGN in a Swiss German layout, NUMBER SIGN and APOSTROPHE in a German layout, GRAVE ACCENT and POUND SIGN in a French Mac layout, and ASTERISK and MICRO SIGN in a French Windows layout. +"Backspace" +"BrightnessDown" the Brightness Down key +"BrightnessUp" the Brightness Up key +"C" +"Calculator" the Calculator key +"Cancel" +"CapsLock" +"Clear" +"Clear / Again" +"," +"Computer" the My Computer key +"Copy" +"CrSel" +"CurrencySubUnit" the Currency Subunit key +"CurrencyUnit" the Currency Unit key +"Cut" +"D" +"DecimalSeparator" the Decimal Separator key +"Delete" +"DisplaySwitch" display mirroring/dual display switch, video mode switch +"Down" the Down arrow key (navigation keypad) +"E" +"Eject" the Eject key) +"End" +"=" +"Escape" the Esc key) +"Execute" +"ExSel" +"F" +"F1" +"F10" +"F11" +"F12" +"F13" +"F14" +"F15" +"F16" +"F17" +"F18" +"F19" +"F2" +"F20" +"F21" +"F22" +"F23" +"F24" +"F3" +"F4" +"F5" +"F6" +"F7" +"F8" +"F9" +"Find" +"G" +"`" Located in the top left corner (on both ANSI and ISO keyboards). Produces GRAVE ACCENT and TILDE in a US Windows layout and in US and UK Mac layouts on ANSI keyboards, GRAVE ACCENT and NOT SIGN in a UK Windows layout, SECTION SIGN and PLUS-MINUS SIGN in US and UK Mac layouts on ISO keyboards, SECTION SIGN and DEGREE SIGN in a Swiss German layout (Mac: only on ISO keyboards), CIRCUMFLEX ACCENT and DEGREE SIGN in a German layout (Mac: only on ISO keyboards), SUPERSCRIPT TWO and TILDE in a French Windows layout, COMMERCIAL AT and NUMBER SIGN in a French Mac layout on ISO keyboards, and LESS-THAN SIGN and GREATER-THAN SIGN in a Swiss German, German, or French Mac layout on ANSI keyboards. +"H" +"Help" +"Home" +"I" +"Insert" insert on PC, help on some Mac keyboards (but does send code 73, not 117) +"J" +"K" +"KBDIllumDown" the Keyboard Illumination Down key +"KBDIllumToggle" the Keyboard Illumination Toggle key +"KBDIllumUp" the Keyboard Illumination Up key +"Keypad 0" the 0 key (numeric keypad) +"Keypad 00" the 00 key (numeric keypad) +"Keypad 000" the 000 key (numeric keypad) +"Keypad 1" the 1 key (numeric keypad) +"Keypad 2" the 2 key (numeric keypad) +"Keypad 3" the 3 key (numeric keypad) +"Keypad 4" the 4 key (numeric keypad) +"Keypad 5" the 5 key (numeric keypad) +"Keypad 6" the 6 key (numeric keypad) +"Keypad 7" the 7 key (numeric keypad) +"Keypad 8" the 8 key (numeric keypad) +"Keypad 9" the 9 key (numeric keypad) +"Keypad A" the A key (numeric keypad) +"Keypad &" the & key (numeric keypad) +"Keypad @" the @ key (numeric keypad) +"Keypad B" the B key (numeric keypad) +"Keypad Backspace" the Backspace key (numeric keypad) +"Keypad Binary" the Binary key (numeric keypad) +"Keypad C" the C key (numeric keypad) +"Keypad Clear" the Clear key (numeric keypad) +"Keypad ClearEntry" the Clear Entry key (numeric keypad) +"Keypad :" the : key (numeric keypad) +"Keypad ," the Comma key (numeric keypad) +"Keypad D" the D key (numeric keypad) +"Keypad &&" the && key (numeric keypad) +"Keypad ||" the || key (numeric keypad) +"Keypad Decimal" the Decimal key (numeric keypad) +"Keypad /" the / key (numeric keypad) +"Keypad E" the E key (numeric keypad) +"Keypad Enter" the Enter key (numeric keypad) +"Keypad =" the = key (numeric keypad) +"Keypad = (AS400)" the Equals AS400 key (numeric keypad) +"Keypad !" the ! key (numeric keypad) +"Keypad F" the F key (numeric keypad) +"Keypad >" the Greater key (numeric keypad) +"Keypad #" the # key (numeric keypad) +"Keypad Hexadecimal" the Hexadecimal key (numeric keypad) +"Keypad {" the Left Brace key (numeric keypad) +"Keypad (" the Left Parenthesis key (numeric keypad) +"Keypad <" the Less key (numeric keypad) +"Keypad MemAdd" the Mem Add key (numeric keypad) +"Keypad MemClear" the Mem Clear key (numeric keypad) +"Keypad MemDivide" the Mem Divide key (numeric keypad) +"Keypad MemMultiply" the Mem Multiply key (numeric keypad) +"Keypad MemRecall" the Mem Recall key (numeric keypad) +"Keypad MemStore" the Mem Store key (numeric keypad) +"Keypad MemSubtract" the Mem Subtract key (numeric keypad) +"Keypad -" the - key (numeric keypad) +"Keypad \*" the \* key (numeric keypad) +"Keypad Octal" the Octal key (numeric keypad) +"Keypad %" the Percent key (numeric keypad) +"Keypad ." the . key (numeric keypad) +"Keypad +" the + key (numeric keypad) +"Keypad +/-" the +/- key (numeric keypad) +"Keypad ^" the Power key (numeric keypad) +"Keypad }" the Right Brace key (numeric keypad) +"Keypad )" the Right Parenthesis key (numeric keypad) +"Keypad Space" the Space key (numeric keypad) +"Keypad Tab" the Tab key (numeric keypad) +"Keypad \|" the \| key (numeric keypad) +"Keypad XOR" the XOR key (numeric keypad) +"L" +"Left Alt" alt, option +"Left Ctrl" +"Left" the Left arrow key (navigation keypad) +"[" +"Left GUI" windows, command (apple), meta +"Left Shift" +"M" +"Mail" the Mail/eMail key +"MediaSelect" the Media Select key +"Menu" +"-" +"ModeSwitch" I'm not sure if this is really not covered by any of the above, but since there's a special KMOD_MODE for it I'm adding it here +"Mute" +"N" +"Numlock" the Num Lock key (PC) / the Clear key (Mac) +"O" +"Oper" +"Out" +"P" +"PageDown" +"PageUp" +"Paste" +"Pause" the Pause / Break key +"." +"Power" The USB document says this is a status flag, not a physical key - but some Mac keyboards do have a power key. +"PrintScreen" +"Prior" +"Q" +"R" +"Right Alt" alt gr, option +"Right Ctrl" +"Return" the Enter key (main keyboard) +"Return" +"Right GUI" windows, command (apple), meta +"Right" the Right arrow key (navigation keypad) +"]" +"Right Shift" +"S" +"ScrollLock" +"Select" +";" +"Separator" +"/" +"Sleep" the Sleep key +"Space" the Space Bar key(s) +"Stop" +"SysReq" the SysReq key +"T" +"Tab" the Tab key +"ThousandsSeparator" the Thousands Separator key +"U" +"Undo" +"Up" the Up arrow key (navigation keypad) +"V" +"VolumeDown" +"VolumeUp" +"W" +"WWW" the WWW/World Wide Web key +"X" +"Y" +"Z" +"#" ISO USB keyboards actually use this code instead of 49 for the same key, but all OSes I've seen treat the two codes identically. So, as an implementor, unless your keyboard generates both of those codes and your OS treats them differently, you should generate SDL_SCANCODE_BACKSLASH instead of this code. As a user, you should not rely on this code because SDL will never generate it with most (all?) keyboards. +"&" +"*" +"@" +"^" +":" +"$" +"!" +">" +"#" +"(" +"<" +"%" +"+" +"?" +")" +"_" +===================== ================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================ diff --git a/Documentation/Manual/Installation.rst b/Documentation/Manual/Installation.rst new file mode 100644 index 0000000..23a2128 --- /dev/null +++ b/Documentation/Manual/Installation.rst @@ -0,0 +1,26 @@ + +Installation +#################### + +Setting up a base installation can be done by performing the following steps: + +Overview +------------------------------------------ +#. Unzip RetroFE +#. Edit your Settings.conf +#. Setup your launchers (emulators) +#. Setup your collections (game lists) +#. Edit your Collections/Main/Menu.xml +#. Run RetroFE.exe + + +Details +------------------------------------------ +#. Extract the contents of the frontend to a folder of your choosing. +#. Edit your :ref:`Configuration` file (/Settings.conf) +#. Setup your launchers (emulators) (/Launchers/\*.conf) +#. Setup your collections (game lists) for your games. (/Collections//\*.conf) +#. Edit your main menu (Collections/Main/Menu.xml) to show what collections show up on the front page. +#. Run RetroFE.exe + +It is recommended that you organize your assets (games, artwork, videos, etc...) prior to configuring. After getting your system up and running you can download additional layouts or create your own layout (see :ref:`Layouts` for instructions on how to create your own layout). diff --git a/Documentation/Manual/Layouts.rst b/Documentation/Manual/Layouts.rst new file mode 100644 index 0000000..3379048 --- /dev/null +++ b/Documentation/Manual/Layouts.rst @@ -0,0 +1,561 @@ +.. _Layouts: + +********** +Layouts +********** +A layout defines your user experience for displaying a collection. The layout XML file defines where graphical components are displayed, what audio is played, and how to animate components when certain events occur. + +Each collection can have its own layout. Edit your :ref:`ConfigurationCollections` files to pick your layout. + +File format +#################### + +All XML tags must be inside the root layout tag. + +.. code-block:: xml + + + ... + + + + attributes +=================== + +=========================== ===================================================================================================================================== +Attribute Description +=========================== ===================================================================================================================================== +width The virtual width to use for this layout. This will be scaled automatically by the frontend if the screen resolution is different. +height The virtual height to use for this layout. This will be scaled automatically by the frontend if the screen resolution is different. +font Location of the font (relative to the layout folder) +fontColor RGB color of the font (in hex) +=========================== ===================================================================================================================================== + +The following example uses a layout that is setup for 1024x768 pixels (4:3 aspect ratio). The frontend will handle layout scaling if your monitor runs at a larger resolution +and is of the same aspect ratio (i.e 1600x1200). The frontend will still handle layout scaling if the aspect ratio is different, however, your image will look stretched +(i.e 1920x1080 resolution, 16:9 aspect ratio). + +.. code-block:: xml + + + ... + + +The following example sets the menu font to Age.otf with a color of Royal Blue Red=41(65) Green=69(105) Blue=E1(225) + +.. code-block:: xml + + + + + +Components +########## + +A component is a graphical item to be displayed on your screen (i.e. a menu, image, video, text). + +=========================== =========================================================================== +Top Level Components Description +=========================== =========================================================================== + Menu for layout (can only contain one menu per layout) + Display a single image (i.e. a background image) + An image that is reloaded with one for a particular selected item + Display a video for the selected item +=========================== =========================================================================== + +Component attributes +==================== + +Each component (listed above) supports the following attributes: + +=========================== ================================================================================ +Attribute Description +=========================== ================================================================================ +x X coordinate of where to place the component +y Y coordinate of where to place the component +xOffset Relative X offset of how many pixels to shift the object from x (x + xOffset) +yOffset Relative Y offset of how many pixels to shift the object from y (y + yOffset) +xOrigin X offset on image to use as the pin point for placement +yOrigin Y offset on image to use as the pin point for placement +transparency 0 = 0% visible, 0.5=50% visible, 1=100% visible +angle Angle to rotate image, in degrees +width Width of the component. Image will be scaled appropriately if not specified. +height Height of the component. Image will be scaled appropriately if not specified. +minWidth Minimum width to allow the image to be (if scaling is needed) +minHeight Minimum height to allow the image to be (if scaling is needed) +maxWidth Maximum width to allow the image to be (if scaling is needed) +maxHeight Maximum height to allow the image to be (if scaling is needed) +fontSize Size of font to display component is rendering text +=========================== ================================================================================ + +(As a reminder, all examples shown below must be inside a tag) + +Relative Positioning Diagram +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. image:: coordinates.png + :width: 400pt + +Attribute values (alignment) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Keywords can be passed into the values of some attributes for alignment. + +All horizontal based attributes (i.e. x, xOffset, xOrigin, width, minWidth, maxWidth) can support the following values: "left", "center", "right", "stretch" + +All vertical based attributes (i.e. x, yOffset, yOrigin, height, minHeight, maxHeight) can support the following values: "top", "center", "bottom", "stretch" + +Display an image stretched across the the screen +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: xml + + + +Display an image centered on the screen +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: xml + + + +Display an image aligned at the bottom right of the screen +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use the bottom rightmost pixel as the reference point when displaying on the screen and display at the bottom rightmost position. + +.. code-block:: xml + + + + +Display an image being offset 100 pixels from the right of the page (vertically centered) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use the rightmost (vertical center) pixel as a reference point when displaying on the screen. +Display at the right-center area of the page. +Offset the image to the left by 100 pixels. + +.. code-block:: xml + + + + + +Animations +########### + +The animation engine is very flexible. You can move, rotate, scale, and make images transparent. See the basic example XML for performing an animation. The sections below explain what each tag is responsible for. + +.. code-block:: xml + + + + + + + + + + + + + + + +===================================== ================================================================================================= +Tag (in example above) Description +===================================== ================================================================================================= + The component to animate. In this case it is an image. + When to trigger the event. In this case it will be triggered when the page is first loaded. + Groups component properties to animate. In this case the first group animates the x and y coordinates + together for one second and then later animates the y axis for half a second. + Defines the start, end, and algorithm to use for animating a property (in this case the X and Y position). + Event that is continuously looped when there is no active input (when the menu is idling) +===================================== ================================================================================================= + + +Events +=================== + +The following animations are supported by all component tags. + +===================================== ================================================================================================= +Tag Description +===================================== ================================================================================================= + Events that are triggered when the layout first starts + Events that are triggered when the layout exits + Events that are triggered when the currently highlighted item is no longer highlighted + Events that are triggered when the item is highlighted + Event that is continuously looped when there is no active input (when the menu is idling) +===================================== ================================================================================================= + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + animation tag +=================== +An animation is a collection of animation properties to change at the same time. You can "daisy chain" multiple animations. + +.. code-block:: xml + + + + + + + + + + + + + tag +=============== +An animation tag defines what property to animate. + +=========================== ================================================================================================================================== +Attribute Description +=========================== ================================================================================================================================== +type Component property to animate. Supported types are: x, y, width, height, transparency, angle, xOrigin, yOrigin, xOffset, yOffset +from Starting value +to Ending value +algorithm Motion (tweening) algorithm. Defaults to linear if not specified. See Animation algorithms for more information. +=========================== ================================================================================================================================== + +The following example animates an image on the x axis to move from the left to the right of the screen in 1 second. + +.. code-block:: xml + + + + + + + + + +The following example animates an image on the x axis to move from the top left to the bottom right of the screen in 1 second. + +.. code-block:: xml + + + + + + + + + + +Animation algorithms +^^^^^^^^^^^^^^^^^^^^^ +See http://gizma.com/easing/ for examples on how each animation operates. + +===================================== ================================================================================================= +Algorithm Description +===================================== ================================================================================================= +linear no easing, no acceleration (default if none is specified) +easeinquadratic accelerating from zero velocity +easeoutquadratic deaccelerating from zero velocity +easeinoutquadratic acceleration until halfway, then deceleration +easeincubic accelerating from zero velocity +easeoutcubic deaccelerating from zero velocity +easeinoutcubic acceleration until halfway, then deceleration +easeinquartic accelerating from zero velocity +easeoutquartic deaccelerating from zero velocity +easeinoutquartic acceleration until halfway, then deceleration +easeinquintic accelerating from zero velocity +easeoutquintic deaccelerating from zero velocity +easeinoutquintic acceleration until halfway, then deceleration +easeinsinusoidal accelerating from zero velocity +easeoutsinusoidal deaccelerating from zero velocity +easeinoutsinusoidal acceleration until halfway, then deceleration +easeinexponential accelerating from zero velocity +easeoutexponential deaccelerating from zero velocity +easeinoutexponential acceleration until halfway, then deceleration +easeincircular accelerating from zero velocity +easeoutcircular deaccelerating from zero velocity +easeinoutcircular acceleration until halfway, then deceleration +===================================== ================================================================================================= + +Daisy chained animation example +================================ + +Take an image and move it from the top left of the screen to the center in 1 second. After the animation completes move the image +from the center of the screen to the bottom center of the screen in 0.5 seconds. + +.. code-block:: xml + + + + + + + + + + + + + +Fully animating +================================ + +While this example may not be practical; it showcases all the properties that can be animated. + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Images +#################### + +Component attributes +==================== + +See below for a list of supported attributes (in addition to the standard component attributes listed above) + +=========================== ================================================================================ +Attribute Description +=========================== ================================================================================ +src location of the source image (relative to the layout folder) +=========================== ================================================================================ + +For example, if you want to display picture of an Nintendo console (named "NES Console.png" in your layout folder), you would do the following: + +.. code-block:: xml + + + + +Reloadable Images +############################## + +Displays image for the currently highlighted menu item. + +Component attributes +==================== + +See below for a list of supported attributes (in addition to the standard component attributes listed above). + +=========================== ===================================================================================================================================================================== +Attribute Description +=========================== ===================================================================================================================================================================== +type Type of image to display (using the selected item). +=========================== ===================================================================================================================================================================== + +For example, if you want to display a snap shot of the selected menu item, you would do the following in your Layout.xml file: + +.. code-block:: xml + + + +Your Settings.conf file will have the following line + +.. code-block:: xml + + media.snap = D:/Video Game Artwork/Nintendo/Snaps + +If an item titled "Tetris (USA)" was selected, the reloadable image component will try to load "D:/Video Game Artwork/Nintendo/Snaps/Tetris (USA).png". If no image could be found than nothing +will be displayed. + +Reloadable Videos +############################## + +Displays video for the currently highlighted menu item. + +Component attributes +==================== + +See below for a list of supported attributes (in addition to the standard component attributes listed above). + +=========================== ===================================================================================================================================================================== +Attribute Description +=========================== ===================================================================================================================================================================== +imageType Type of image to display if the video could not be found (using the selected item). +=========================== ===================================================================================================================================================================== + +For example, if you want to display a video of the selected menu item, you would do the following in your Layout.xml file: + +.. code-block:: xml + + + +Your Settings.conf file will have the following line + +.. code-block:: xml + + media.video = D:/Video Game Artwork/Nintendo/Videos + +If an item titled "Tetris (USA)" was selected, the reloadable image component will try to load "D:/Video Game Artwork/Nintendo/Videos/Tetris (USA).png". If no image could be found than nothing +will be displayed. + +If you do not want to display an image component if the video does not exist, simply do not specify an imageType attribute: + +.. code-block:: xml + + + +Rendering Text +############################## + +Displays static text on the screen + +Component attributes +==================== + +See below for a list of supported attributes (in addition to the standard component attributes listed above). + +=========================== ===================================================================================================================================================================== +Attribute Description +=========================== ===================================================================================================================================================================== +value Contents of the text to display +=========================== ===================================================================================================================================================================== + +For example, if you want to display a video of the selected menu item, you would do the following in your Layout.xml file: + +.. code-block:: xml + + + +Menu +########### + +The menu supports animations just like every other component. There can be only one menu per layout. + +See below for a list of supported attributes (in addition to the standard component attributes listed above). + + + +menu tag +==================== + +Each menu tag represents a point on where to display a scrolling list item in the menu. When scrolling, the items themselves will scroll/move from one item point to another. +If an attribute in is not specified, it will use the attribute specified in the . + +.. code-block:: xml + + + + + + + + + + + +Animating the menu and list items +================================== +Not only can the entire menu have an animation performed, the menu item at a particular point can also be animated. See below: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Sounds +####### +In addition to displaying graphical components, the frontend supports sound effects that are triggered when certain events occur. + +=============================== ================================================================================================= +Tag Description +=============================== ================================================================================================= + Sound triggered when the layout is started + Sound triggered when the layout is exited + Sound triggered when a new item is highlighted. This will not loop while actively scrolling. + Sound triggered when an item is selected2 +=============================== ================================================================================================= + +Each sound effect supports the following parameters: + +=========================== ===================================================================================================================================================================== +Attribute Description +=========================== ===================================================================================================================================================================== +src Location of the sound file (relative to the layout folder). +=========================== ===================================================================================================================================================================== + +.. code-block:: xml + + + + + + + diff --git a/Documentation/Manual/Makefile b/Documentation/Manual/Makefile new file mode 100644 index 0000000..db17d5a --- /dev/null +++ b/Documentation/Manual/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Puddle.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Puddle.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/Documentation/Manual/Overview.rst b/Documentation/Manual/Overview.rst new file mode 100644 index 0000000..d653938 --- /dev/null +++ b/Documentation/Manual/Overview.rst @@ -0,0 +1,69 @@ +.. _Overview: + +================================== +Overview +================================== + +About +#################### +RetroFE is a menu system frontend to use for navigating through your game collections. It is intended to be loaded on arcade cabinets or media PCs to hide the operating system. + +Operating Systems +#################### + +Currently RetroFE runs in both Windows and Linux on x86 architectures. +Future releases will include support for OSX, and the Raspberry Pi. + +Folder Structure +#################### + +Main +------------ + +The frontend contains several folders to help manage your experience. + +============================================= ======================================================================================================================================== +File or Folder Description +============================================= ======================================================================================================================================== +/Documentation Documentation for using the frontend (what you are currently reading) +/RetroFE.lnk Shortcut to RetroFE Executable (points to Core/RetroFE.exe) +/Layouts Folder containing all of your layouts/themes +/Launchers Folder containing all the launchers (emulators) +/Core Core libraries for RetroFE +/Core/RetroFE.exe Main Executable +/Collections Contains all your list information +/Collections/Main/Menu.xml List for the main menu +/Cache.db Used to help make the frontend run more quickly. This should not need to be touched or editied (unless you know what you are doing). +/Log.txt Log file from the last time you ran RetroFE. Used for troubleshooting issues. +/Settings.conf Main configuration. Set your screen resolution, controls, how to launch games, etc. +============================================= ======================================================================================================================================== + + +Terminology +#################### + +Collections +------------------- + +A collection is essentially a game list. Think of it as a "collection of games". + +See :ref:`ConfigurationCollections` for more information on how to setup and customize your collections. + + +Launchers +------------------- + +A launcher is an executable (i.e. an emulator) that is ran when a menu item is selected. + +See :ref:`ConfigurationLaunchers` for more information. + + +Menu +------------------- + +A menu defines how you navigate through the menu system. + +See :ref:`ConfigurationMenu` for more information. + + + diff --git a/Documentation/Manual/Troubleshooting.rst b/Documentation/Manual/Troubleshooting.rst new file mode 100644 index 0000000..62c2bb0 --- /dev/null +++ b/Documentation/Manual/Troubleshooting.rst @@ -0,0 +1,5 @@ +Troubleshooting +#################### + +Having troubles launching items? Lists not showing up correctly? Graphics not showing up properly? Is the frontend crashing? Open up Log.txt to help diagnose your problems. Also visit www.retrofe.com for any questions. + diff --git a/Documentation/Manual/conf.py b/Documentation/Manual/conf.py new file mode 100644 index 0000000..c2ada88 --- /dev/null +++ b/Documentation/Manual/conf.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +# +# Puddle documentation build configuration file, created by +# sphinx-quickstart on Thu Apr 17 14:08:27 2014. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'RetroFE' +copyright = u'2014, Don Honerbrink' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1.0.12 beta' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = [ "." ] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Puddledoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'RetroFE.tex', u'RetroFE Documentation', + u'Don Honerbrink', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/Documentation/Manual/coordinates.png b/Documentation/Manual/coordinates.png new file mode 100644 index 0000000..d07242b Binary files /dev/null and b/Documentation/Manual/coordinates.png differ diff --git a/Documentation/Manual/easing.swf b/Documentation/Manual/easing.swf new file mode 100644 index 0000000..06cd9bb Binary files /dev/null and b/Documentation/Manual/easing.swf differ diff --git a/Documentation/Manual/index.rst b/Documentation/Manual/index.rst new file mode 100644 index 0000000..0a21f7c --- /dev/null +++ b/Documentation/Manual/index.rst @@ -0,0 +1,17 @@ +RetroFE +====================================================== + +:Release: |release| +:Date: |today| + +User Documentation +------------------ + +.. toctree:: + :maxdepth: 2 + + Overview + Installation + Configuration + Layouts + Troubleshooting diff --git a/Documentation/Manual/make.bat b/Documentation/Manual/make.bat new file mode 100644 index 0000000..4754964 --- /dev/null +++ b/Documentation/Manual/make.bat @@ -0,0 +1,113 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Puddle.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Puddle.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/Documentation/Manual/sphinx_rtd_theme/__init__.py b/Documentation/Manual/sphinx_rtd_theme/__init__.py new file mode 100644 index 0000000..1440863 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/__init__.py @@ -0,0 +1,17 @@ +"""Sphinx ReadTheDocs theme. + +From https://github.com/ryan-roemer/sphinx-bootstrap-theme. + +""" +import os + +VERSION = (0, 1, 5) + +__version__ = ".".join(str(v) for v in VERSION) +__version_full__ = __version__ + + +def get_html_theme_path(): + """Return list of HTML theme paths.""" + cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + return cur_dir diff --git a/Documentation/Manual/sphinx_rtd_theme/breadcrumbs.html b/Documentation/Manual/sphinx_rtd_theme/breadcrumbs.html new file mode 100644 index 0000000..ff0938e --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/breadcrumbs.html @@ -0,0 +1,19 @@ + + + Docs » + {% for doc in parents %} + {{ doc.title }} » + {% endfor %} + {{ title }} + + {% if display_github %} + Edit on GitHub + {% elif display_bitbucket %} + Edit on Bitbucket + {% elif show_source and has_source and sourcename %} + View page source + {% endif %} + + + + diff --git a/Documentation/Manual/sphinx_rtd_theme/footer.html b/Documentation/Manual/sphinx_rtd_theme/footer.html new file mode 100644 index 0000000..3c7afed --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/footer.html @@ -0,0 +1,32 @@ + diff --git a/Documentation/Manual/sphinx_rtd_theme/layout.html b/Documentation/Manual/sphinx_rtd_theme/layout.html new file mode 100644 index 0000000..48b64f5 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/layout.html @@ -0,0 +1,160 @@ +{# TEMPLATE VAR SETTINGS #} +{%- set url_root = pathto('', 1) %} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} +{%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} +{%- else %} + {%- set titlesuffix = "" %} +{%- endif %} + + + + + + + + {% block htmltitle %} + {{ title|striptags|e }}{{ titlesuffix }} + {% endblock %} + + {# FAVICON #} + {% if favicon %} + + {% endif %} + + {# CSS #} + + + {# OPENSEARCH #} + {% if not embedded %} + {% if use_opensearch %} + + {% endif %} + + {% endif %} + + {# RTD hosts this file, so just load on non RTD builds #} + {% if not READTHEDOCS %} + + {% endif %} + + {% for cssfile in css_files %} + + {% endfor %} + + {%- block linktags %} + {%- if hasdoc('about') %} + + {%- endif %} + {%- if hasdoc('genindex') %} + + {%- endif %} + {%- if hasdoc('search') %} + + {%- endif %} + {%- if hasdoc('copyright') %} + + {%- endif %} + + {%- if parents %} + + {%- endif %} + {%- if next %} + + {%- endif %} + {%- if prev %} + + {%- endif %} + {%- endblock %} + {%- block extrahead %} {% endblock %} + + {# Keep modernizr in head - http://modernizr.com/docs/#installing #} + + + + + + + + + {# SIDE NAV, TOGGLES ON MOBILE #} + + + {{ project }} + {% include "searchbox.html" %} + + + + {% set toctree = toctree(maxdepth=2, collapse=False, includehidden=True) %} + {% if toctree %} + {{ toctree }} + {% else %} + + {{ toc }} + {% endif %} + + + + + + + {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #} + + + {{ project }} + + + + {# PAGE CONTENT #} + + + {% include "breadcrumbs.html" %} + + {% block body %}{% endblock %} + + {% include "footer.html" %} + + + + + + + {% include "versions.html" %} + + {% if not embedded %} + + + {%- for scriptfile in script_files %} + + {%- endfor %} + + {% endif %} + + {# RTD hosts this file, so just load on non RTD builds #} + {% if not READTHEDOCS %} + + {% endif %} + + {# STICKY NAVIGATION #} + {% if theme_sticky_navigation %} + + {% endif %} + + {%- block footer %} {% endblock %} + + + diff --git a/Documentation/Manual/sphinx_rtd_theme/layout_old.html b/Documentation/Manual/sphinx_rtd_theme/layout_old.html new file mode 100644 index 0000000..deb8df2 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/layout_old.html @@ -0,0 +1,205 @@ +{# + basic/layout.html + ~~~~~~~~~~~~~~~~~ + + Master layout template for Sphinx themes. + + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block doctype -%} + +{%- endblock %} +{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} +{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} +{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and + (sidebars != []) %} +{%- set url_root = pathto('', 1) %} +{# XXX necessary? #} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} +{%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} +{%- else %} + {%- set titlesuffix = "" %} +{%- endif %} + +{%- macro relbar() %} + + {{ _('Navigation') }} + + {%- for rellink in rellinks %} + + {{ rellink[3] }} + {%- if not loop.first %}{{ reldelim2 }}{% endif %} + {%- endfor %} + {%- block rootrellink %} + {{ shorttitle|e }}{{ reldelim1 }} + {%- endblock %} + {%- for parent in parents %} + {{ parent.title }}{{ reldelim1 }} + {%- endfor %} + {%- block relbaritems %} {% endblock %} + + +{%- endmacro %} + +{%- macro sidebar() %} + {%- if render_sidebar %} + + + {%- block sidebarlogo %} + {%- if logo %} + + + + {%- endif %} + {%- endblock %} + {%- if sidebars != None %} + {#- new style sidebar: explicitly include/exclude templates #} + {%- for sidebartemplate in sidebars %} + {%- include sidebartemplate %} + {%- endfor %} + {%- else %} + {#- old style sidebars: using blocks -- should be deprecated #} + {%- block sidebartoc %} + {%- include "localtoc.html" %} + {%- endblock %} + {%- block sidebarrel %} + {%- include "relations.html" %} + {%- endblock %} + {%- block sidebarsourcelink %} + {%- include "sourcelink.html" %} + {%- endblock %} + {%- if customsidebar %} + {%- include customsidebar %} + {%- endif %} + {%- block sidebarsearch %} + {%- include "searchbox.html" %} + {%- endblock %} + {%- endif %} + + + {%- endif %} +{%- endmacro %} + +{%- macro script() %} + + {%- for scriptfile in script_files %} + + {%- endfor %} +{%- endmacro %} + +{%- macro css() %} + + + {%- for cssfile in css_files %} + + {%- endfor %} +{%- endmacro %} + + + + + {{ metatags }} + {%- block htmltitle %} + {{ title|striptags|e }}{{ titlesuffix }} + {%- endblock %} + {{ css() }} + {%- if not embedded %} + {{ script() }} + {%- if use_opensearch %} + + {%- endif %} + {%- if favicon %} + + {%- endif %} + {%- endif %} +{%- block linktags %} + {%- if hasdoc('about') %} + + {%- endif %} + {%- if hasdoc('genindex') %} + + {%- endif %} + {%- if hasdoc('search') %} + + {%- endif %} + {%- if hasdoc('copyright') %} + + {%- endif %} + + {%- if parents %} + + {%- endif %} + {%- if next %} + + {%- endif %} + {%- if prev %} + + {%- endif %} +{%- endblock %} +{%- block extrahead %} {% endblock %} + + +{%- block header %}{% endblock %} + +{%- block relbar1 %}{{ relbar() }}{% endblock %} + +{%- block content %} + {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %} + + + {%- block document %} + + {%- if render_sidebar %} + + {%- endif %} + + {% block body %} {% endblock %} + + {%- if render_sidebar %} + + {%- endif %} + + {%- endblock %} + + {%- block sidebar2 %}{{ sidebar() }}{% endblock %} + + +{%- endblock %} + +{%- block relbar2 %}{{ relbar() }}{% endblock %} + +{%- block footer %} + + asdf asdf asdf asdf 22 +{%- endblock %} + + + diff --git a/Documentation/Manual/sphinx_rtd_theme/search.html b/Documentation/Manual/sphinx_rtd_theme/search.html new file mode 100644 index 0000000..e3aa9b5 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/search.html @@ -0,0 +1,50 @@ +{# + basic/search.html + ~~~~~~~~~~~~~~~~~ + + Template for the search page. + + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- extends "layout.html" %} +{% set title = _('Search') %} +{% set script_files = script_files + ['_static/searchtools.js'] %} +{% block footer %} + + {# this is used when loading the search index using $.ajax fails, + such as on Chrome for documents on localhost #} + + {{ super() }} +{% endblock %} +{% block body %} + + + + {% trans %}Please activate JavaScript to enable the search + functionality.{% endtrans %} + + + + + {% if search_performed %} + {{ _('Search Results') }} + {% if not search_results %} + {{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }} + {% endif %} + {% endif %} + + {% if search_results %} + + {% for href, caption, context in search_results %} + + {{ caption }} + {{ context|e }} + + {% endfor %} + + {% endif %} + +{% endblock %} diff --git a/Documentation/Manual/sphinx_rtd_theme/searchbox.html b/Documentation/Manual/sphinx_rtd_theme/searchbox.html new file mode 100644 index 0000000..24418d3 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/searchbox.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Documentation/Manual/sphinx_rtd_theme/static/css/badge_only.css b/Documentation/Manual/sphinx_rtd_theme/static/css/badge_only.css new file mode 100644 index 0000000..4868a00 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:"\f02d"}.icon-book:before{content:"\f02d"}.fa-caret-down:before{content:"\f0d7"}.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} diff --git a/Documentation/Manual/sphinx_rtd_theme/static/css/theme.css b/Documentation/Manual/sphinx_rtd_theme/static/css/theme.css new file mode 100644 index 0000000..5a5a481 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/static/css/theme.css @@ -0,0 +1,4 @@ +*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:20px 0;padding:0}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.0.3");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff?v=4.0.3") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.0.3") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.pull-left.icon{margin-right:.3em}.fa.pull-right,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before,.icon-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before,.icon-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:"\f057"}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before,.icon-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before,.icon-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:"\f0a8"}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before,.icon-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-asc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-desc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-reply-all:before{content:"\f122"}.fa-mail-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before,.icon-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .icon,.nav .fa,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .icon{display:inline}.btn .fa.fa-large,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a{color:#2980b9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:60px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:60px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27ae60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27ae60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#e74c3c !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#e67e22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980b9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#999;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{display:block;float:left;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{display:block;float:left;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{display:block;float:left;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:0.3125em;font-style:italic}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#f3f6f6;color:#cad2d3}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e74c3c}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fff;color:#cad2d3;border-color:transparent}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{padding:6px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #e74c3c}.wy-control-group.wy-control-group-error textarea{border:solid 1px #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px;margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980b9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27ae60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#e74c3c !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}code,.rst-content tt{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:"Consolas","Monaco",monospace;color:#e74c3c;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9b59b6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:"Consolas","Monaco",monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:"Consolas","Monaco",monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#eaf2f5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical header{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#2980b9;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:0.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-side-nav-search{z-index:200;background-color:#2980b9;text-align:center;padding:0.809em;display:block;color:#fcfcfc;margin-bottom:0.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto 0.809em auto;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:0.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:absolute;top:0;left:0;width:300px;overflow:hidden;min-height:100%;background:#343131;z-index:200}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:0.4045em 0.809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}nav.stickynav{position:fixed;top:0}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}.rst-content img{max-width:100%;height:auto !important}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after{visibility:visible;content:"\f0c1";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt{color:#000}.rst-content tt big,.rst-content tt em{font-size:100% !important;line-height:normal}.rst-content tt .xref,a .rst-content tt{font-weight:bold}.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:gray}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040} diff --git a/Documentation/Manual/sphinx_rtd_theme/static/fonts/FontAwesome.otf b/Documentation/Manual/sphinx_rtd_theme/static/fonts/FontAwesome.otf new file mode 100644 index 0000000..8b0f54e Binary files /dev/null and b/Documentation/Manual/sphinx_rtd_theme/static/fonts/FontAwesome.otf differ diff --git a/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot b/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..7c79c6a Binary files /dev/null and b/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot differ diff --git a/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg b/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..45fdf33 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf b/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..e89738d Binary files /dev/null and b/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf differ diff --git a/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff b/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..8c1748a Binary files /dev/null and b/Documentation/Manual/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff differ diff --git a/Documentation/Manual/sphinx_rtd_theme/static/js/theme.js b/Documentation/Manual/sphinx_rtd_theme/static/js/theme.js new file mode 100644 index 0000000..60520cc --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/static/js/theme.js @@ -0,0 +1,47 @@ +$( document ).ready(function() { + // Shift nav in mobile when clicking the menu. + $(document).on('click', "[data-toggle='wy-nav-top']", function() { + $("[data-toggle='wy-nav-shift']").toggleClass("shift"); + $("[data-toggle='rst-versions']").toggleClass("shift"); + }); + // Close menu when you click a link. + $(document).on('click', ".wy-menu-vertical .current ul li a", function() { + $("[data-toggle='wy-nav-shift']").removeClass("shift"); + $("[data-toggle='rst-versions']").toggleClass("shift"); + }); + $(document).on('click', "[data-toggle='rst-current-version']", function() { + $("[data-toggle='rst-versions']").toggleClass("shift-up"); + }); + // Make tables responsive + $("table.docutils:not(.field-list)").wrap(""); +}); + +window.SphinxRtdTheme = (function (jquery) { + var stickyNav = (function () { + var navBar, + win, + stickyNavCssClass = 'stickynav', + applyStickNav = function () { + if (navBar.height() <= win.height()) { + navBar.addClass(stickyNavCssClass); + } else { + navBar.removeClass(stickyNavCssClass); + } + }, + enable = function () { + applyStickNav(); + win.on('resize', applyStickNav); + }, + init = function () { + navBar = jquery('nav.wy-nav-side:first'); + win = jquery(window); + }; + jquery(init); + return { + enable : enable + }; + }()); + return { + StickyNav : stickyNav + }; +}($)); diff --git a/Documentation/Manual/sphinx_rtd_theme/theme.conf b/Documentation/Manual/sphinx_rtd_theme/theme.conf new file mode 100644 index 0000000..dcfbf8c --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/theme.conf @@ -0,0 +1,8 @@ +[theme] +inherit = basic +stylesheet = css/theme.css + +[options] +typekit_id = hiw1hhg +analytics_id = +sticky_navigation = False diff --git a/Documentation/Manual/sphinx_rtd_theme/versions.html b/Documentation/Manual/sphinx_rtd_theme/versions.html new file mode 100644 index 0000000..8b3eb79 --- /dev/null +++ b/Documentation/Manual/sphinx_rtd_theme/versions.html @@ -0,0 +1,37 @@ +{% if READTHEDOCS %} +{# Add rst-badge after rst-versions for small badge style. #} + + + Read the Docs + v: {{ current_version }} + + + + + Versions + {% for slug, url in versions %} + {{ slug }} + {% endfor %} + + + Downloads + {% for type, url in downloads %} + {{ type }} + {% endfor %} + + + On Read the Docs + + Project Home + + + Builds + + + + Free document hosting provided by Read the Docs. + + + +{% endif %} + diff --git a/LICENSE.txt b/LICENSE.txt index fbc457c..a737dcf 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,675 @@ -All information contained herein is, and remains -the property of Puddle Jump Creative. The intellectual and technical -concepts contained herein are proprietary to Puddle Jump Creative -may be covered by U.S. and Foreign Patents, patents in process, -and are protected by trade secret or copyright law. -Dissemination of this information or reproduction of this material -is strictly forbidden unless prior written permission is obtained -from Puddle Jump Creative. + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Assets/Environment/Common/Collections/Arcade/Exclude.txt b/Package/Environment/Common/Collections/Arcade/Exclude.txt similarity index 100% rename from Assets/Environment/Common/Collections/Arcade/Exclude.txt rename to Package/Environment/Common/Collections/Arcade/Exclude.txt diff --git a/Assets/Environment/Common/Collections/Arcade/Include.txt b/Package/Environment/Common/Collections/Arcade/Include.txt similarity index 100% rename from Assets/Environment/Common/Collections/Arcade/Include.txt rename to Package/Environment/Common/Collections/Arcade/Include.txt diff --git a/Assets/Environment/Common/Collections/Arcade/Include.xml b/Package/Environment/Common/Collections/Arcade/Include.xml similarity index 100% rename from Assets/Environment/Common/Collections/Arcade/Include.xml rename to Package/Environment/Common/Collections/Arcade/Include.xml diff --git a/Assets/Environment/Common/Collections/Arcade/Settings.conf b/Package/Environment/Common/Collections/Arcade/Settings.conf similarity index 100% rename from Assets/Environment/Common/Collections/Arcade/Settings.conf rename to Package/Environment/Common/Collections/Arcade/Settings.conf diff --git a/Assets/Environment/Common/Collections/Main/Menu.xml b/Package/Environment/Common/Collections/Main/Menu.xml similarity index 100% rename from Assets/Environment/Common/Collections/Main/Menu.xml rename to Package/Environment/Common/Collections/Main/Menu.xml diff --git a/Assets/Environment/Common/Collections/Main/Settings.conf b/Package/Environment/Common/Collections/Main/Settings.conf similarity index 100% rename from Assets/Environment/Common/Collections/Main/Settings.conf rename to Package/Environment/Common/Collections/Main/Settings.conf diff --git a/Assets/Environment/Common/Collections/NES and Arcade/Menu.xml b/Package/Environment/Common/Collections/NES and Arcade/Menu.xml similarity index 100% rename from Assets/Environment/Common/Collections/NES and Arcade/Menu.xml rename to Package/Environment/Common/Collections/NES and Arcade/Menu.xml diff --git a/Assets/Environment/Common/Collections/NES and Arcade/Settings.conf b/Package/Environment/Common/Collections/NES and Arcade/Settings.conf similarity index 100% rename from Assets/Environment/Common/Collections/NES and Arcade/Settings.conf rename to Package/Environment/Common/Collections/NES and Arcade/Settings.conf diff --git a/Assets/Environment/Common/Collections/Nintendo Entertainment System/Exclude.txt b/Package/Environment/Common/Collections/Nintendo Entertainment System/Exclude.txt similarity index 100% rename from Assets/Environment/Common/Collections/Nintendo Entertainment System/Exclude.txt rename to Package/Environment/Common/Collections/Nintendo Entertainment System/Exclude.txt diff --git a/Assets/Environment/Common/Collections/Nintendo Entertainment System/Include.txt b/Package/Environment/Common/Collections/Nintendo Entertainment System/Include.txt similarity index 100% rename from Assets/Environment/Common/Collections/Nintendo Entertainment System/Include.txt rename to Package/Environment/Common/Collections/Nintendo Entertainment System/Include.txt diff --git a/Assets/Environment/Common/Collections/Nintendo Entertainment System/Settings.conf b/Package/Environment/Common/Collections/Nintendo Entertainment System/Settings.conf similarity index 100% rename from Assets/Environment/Common/Collections/Nintendo Entertainment System/Settings.conf rename to Package/Environment/Common/Collections/Nintendo Entertainment System/Settings.conf diff --git a/Assets/Environment/Common/Controls.conf b/Package/Environment/Common/Controls.conf similarity index 100% rename from Assets/Environment/Common/Controls.conf rename to Package/Environment/Common/Controls.conf diff --git a/Assets/Environment/Common/Launchers/Fceux.conf b/Package/Environment/Common/Launchers/Fceux.conf similarity index 100% rename from Assets/Environment/Common/Launchers/Fceux.conf rename to Package/Environment/Common/Launchers/Fceux.conf diff --git a/Assets/Environment/Common/Launchers/HyperLaunch.conf b/Package/Environment/Common/Launchers/HyperLaunch.conf similarity index 100% rename from Assets/Environment/Common/Launchers/HyperLaunch.conf rename to Package/Environment/Common/Launchers/HyperLaunch.conf diff --git a/Assets/Environment/Common/Launchers/MAME.conf b/Package/Environment/Common/Launchers/MAME.conf similarity index 100% rename from Assets/Environment/Common/Launchers/MAME.conf rename to Package/Environment/Common/Launchers/MAME.conf diff --git a/Assets/Environment/Common/Launchers/Nestopia.conf b/Package/Environment/Common/Launchers/Nestopia.conf similarity index 100% rename from Assets/Environment/Common/Launchers/Nestopia.conf rename to Package/Environment/Common/Launchers/Nestopia.conf diff --git a/Assets/Environment/Common/Layouts/Default 16x9/Age.otf b/Package/Environment/Common/Layouts/Default 16x9/Age.otf similarity index 100% rename from Assets/Environment/Common/Layouts/Default 16x9/Age.otf rename to Package/Environment/Common/Layouts/Default 16x9/Age.otf diff --git a/Package/Environment/Common/Layouts/Default 16x9/Layout.xml b/Package/Environment/Common/Layouts/Default 16x9/Layout.xml new file mode 100644 index 0000000..ccaedaf --- /dev/null +++ b/Package/Environment/Common/Layouts/Default 16x9/Layout.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Assets/Environment/Common/Layouts/Default 16x9/background.png b/Package/Environment/Common/Layouts/Default 16x9/background.png similarity index 100% rename from Assets/Environment/Common/Layouts/Default 16x9/background.png rename to Package/Environment/Common/Layouts/Default 16x9/background.png diff --git a/Assets/Environment/Common/Layouts/Default 16x9/highlight.wav b/Package/Environment/Common/Layouts/Default 16x9/highlight.wav similarity index 100% rename from Assets/Environment/Common/Layouts/Default 16x9/highlight.wav rename to Package/Environment/Common/Layouts/Default 16x9/highlight.wav diff --git a/Assets/Environment/Common/Layouts/Default 16x9/load.wav b/Package/Environment/Common/Layouts/Default 16x9/load.wav similarity index 100% rename from Assets/Environment/Common/Layouts/Default 16x9/load.wav rename to Package/Environment/Common/Layouts/Default 16x9/load.wav diff --git a/Assets/Environment/Common/Layouts/Default 16x9/logo.png b/Package/Environment/Common/Layouts/Default 16x9/logo.png similarity index 100% rename from Assets/Environment/Common/Layouts/Default 16x9/logo.png rename to Package/Environment/Common/Layouts/Default 16x9/logo.png diff --git a/Assets/Environment/Common/Layouts/Default 16x9/select.wav b/Package/Environment/Common/Layouts/Default 16x9/select.wav similarity index 100% rename from Assets/Environment/Common/Layouts/Default 16x9/select.wav rename to Package/Environment/Common/Layouts/Default 16x9/select.wav diff --git a/Assets/Environment/Common/Layouts/Default 16x9/square.png b/Package/Environment/Common/Layouts/Default 16x9/square.png similarity index 100% rename from Assets/Environment/Common/Layouts/Default 16x9/square.png rename to Package/Environment/Common/Layouts/Default 16x9/square.png diff --git a/Assets/Environment/Common/Layouts/Default 16x9/unload.wav b/Package/Environment/Common/Layouts/Default 16x9/unload.wav similarity index 100% rename from Assets/Environment/Common/Layouts/Default 16x9/unload.wav rename to Package/Environment/Common/Layouts/Default 16x9/unload.wav diff --git a/Assets/Environment/Common/Settings.conf b/Package/Environment/Common/Settings.conf similarity index 100% rename from Assets/Environment/Common/Settings.conf rename to Package/Environment/Common/Settings.conf diff --git a/Assets/Environment/Linux/README-UBUNTU.txt b/Package/Environment/Linux/README-UBUNTU.txt similarity index 100% rename from Assets/Environment/Linux/README-UBUNTU.txt rename to Package/Environment/Linux/README-UBUNTU.txt diff --git a/Assets/Environment/Windows/RetroFE.lnk b/Package/Environment/Windows/RetroFE.lnk similarity index 100% rename from Assets/Environment/Windows/RetroFE.lnk rename to Package/Environment/Windows/RetroFE.lnk diff --git a/CMake/FindGStreamer.cmake b/RetroFE/CMake/FindGStreamer.cmake similarity index 100% rename from CMake/FindGStreamer.cmake rename to RetroFE/CMake/FindGStreamer.cmake diff --git a/CMake/FindGlib2.cmake b/RetroFE/CMake/FindGlib2.cmake similarity index 100% rename from CMake/FindGlib2.cmake rename to RetroFE/CMake/FindGlib2.cmake diff --git a/CMake/FindSDL2.cmake b/RetroFE/CMake/FindSDL2.cmake similarity index 100% rename from CMake/FindSDL2.cmake rename to RetroFE/CMake/FindSDL2.cmake diff --git a/CMake/FindSDL2_image.cmake b/RetroFE/CMake/FindSDL2_image.cmake similarity index 100% rename from CMake/FindSDL2_image.cmake rename to RetroFE/CMake/FindSDL2_image.cmake diff --git a/CMake/FindSDL2_mixer.cmake b/RetroFE/CMake/FindSDL2_mixer.cmake similarity index 100% rename from CMake/FindSDL2_mixer.cmake rename to RetroFE/CMake/FindSDL2_mixer.cmake diff --git a/CMake/FindSDL2_ttf.cmake b/RetroFE/CMake/FindSDL2_ttf.cmake similarity index 100% rename from CMake/FindSDL2_ttf.cmake rename to RetroFE/CMake/FindSDL2_ttf.cmake diff --git a/Scripts/SetupEnvironment.bat b/RetroFE/Scripts/SetupEnvironment.bat similarity index 100% rename from Scripts/SetupEnvironment.bat rename to RetroFE/Scripts/SetupEnvironment.bat diff --git a/Source/CMakeLists.txt b/RetroFE/Source/CMakeLists.txt similarity index 78% rename from Source/CMakeLists.txt rename to RetroFE/Source/CMakeLists.txt index 827a711..c1b5f1e 100644 --- a/Source/CMakeLists.txt +++ b/RetroFE/Source/CMakeLists.txt @@ -83,24 +83,26 @@ set(RETROFE_HEADERS "${RETROFE_DIR}/Source/Database/Configuration.h" "${RETROFE_DIR}/Source/Database/DB.h" "${RETROFE_DIR}/Source/Database/MamelistMetadata.h" + "${RETROFE_DIR}/Source/Execute/AttractMode.h" "${RETROFE_DIR}/Source/Execute/Launcher.h" "${RETROFE_DIR}/Source/Graphics/Animate/Tween.h" "${RETROFE_DIR}/Source/Graphics/Animate/TweenTypes.h" "${RETROFE_DIR}/Source/Graphics/ComponentItemBinding.h" + "${RETROFE_DIR}/Source/Graphics/Component/Container.h" "${RETROFE_DIR}/Source/Graphics/Component/Component.h" - "${RETROFE_DIR}/Source/Graphics/Font.h" - "${RETROFE_DIR}/Source/Graphics/FontCache.h" "${RETROFE_DIR}/Source/Graphics/Component/Image.h" - "${RETROFE_DIR}/Source/Graphics/Component/Text.h" - "${RETROFE_DIR}/Source/Graphics/PageBuilder.h" - "${RETROFE_DIR}/Source/Graphics/MenuNotifierInterface.h" - "${RETROFE_DIR}/Source/Graphics/Page.h" "${RETROFE_DIR}/Source/Graphics/Component/ImageBuilder.h" "${RETROFE_DIR}/Source/Graphics/Component/ReloadableMedia.h" "${RETROFE_DIR}/Source/Graphics/Component/ReloadableText.h" "${RETROFE_DIR}/Source/Graphics/Component/ScrollingList.h" + "${RETROFE_DIR}/Source/Graphics/Component/Text.h" "${RETROFE_DIR}/Source/Graphics/Component/VideoComponent.h" "${RETROFE_DIR}/Source/Graphics/Component/VideoBuilder.h" + "${RETROFE_DIR}/Source/Graphics/Font.h" + "${RETROFE_DIR}/Source/Graphics/FontCache.h" + "${RETROFE_DIR}/Source/Graphics/PageBuilder.h" + "${RETROFE_DIR}/Source/Graphics/MenuNotifierInterface.h" + "${RETROFE_DIR}/Source/Graphics/Page.h" "${RETROFE_DIR}/Source/Sound/Sound.h" "${RETROFE_DIR}/Source/Utility/Log.h" "${RETROFE_DIR}/Source/Utility/Utils.h" @@ -124,6 +126,7 @@ set(RETROFE_SOURCES "${RETROFE_DIR}/Source/Database/Configuration.cpp" "${RETROFE_DIR}/Source/Database/DB.cpp" "${RETROFE_DIR}/Source/Database/MamelistMetadata.cpp" + "${RETROFE_DIR}/Source/Execute/AttractMode.cpp" "${RETROFE_DIR}/Source/Execute/Launcher.cpp" "${RETROFE_DIR}/Source/Graphics/Animate/Tween.cpp" "${RETROFE_DIR}/Source/Graphics/Font.cpp" @@ -133,6 +136,7 @@ set(RETROFE_SOURCES "${RETROFE_DIR}/Source/Graphics/ViewInfo.cpp" "${RETROFE_DIR}/Source/Graphics/ComponentItemBindingBuilder.cpp" "${RETROFE_DIR}/Source/Graphics/ComponentItemBinding.cpp" + "${RETROFE_DIR}/Source/Graphics/Component/Container.cpp" "${RETROFE_DIR}/Source/Graphics/Component/Component.cpp" "${RETROFE_DIR}/Source/Graphics/Component/Image.cpp" "${RETROFE_DIR}/Source/Graphics/Component/ImageBuilder.cpp" @@ -175,50 +179,4 @@ if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /WX") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /WX") set_target_properties(RetroFE PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS") -endif() - - -set(RETROFE_OUTPUT_PATH "../Build/Artifacts/RetroFE") - - -ADD_CUSTOM_COMMAND(TARGET RetroFE POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "../Build/Artifacts/RetroFE" ) - -ADD_CUSTOM_COMMAND(TARGET RetroFE POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory "../Assets/Environment/Common" "${RETROFE_OUTPUT_PATH}" ) - -if(WIN32) -set(RETROFE_OUTPUT_CORE_PATH "${RETROFE_OUTPUT_PATH}/Core") - file(GLOB CORE_FILES - "${GSTREAMER_ROOT}/lib/*.dll" - "${GSTREAMER_ROOT}/lib/gstreamer-1.0/*.dll" - "${GSTREAMER_ROOT}/bin/*.dll" - "${SDL2_ROOT}/lib/x86/*.dll" - "${SDL2_IMAGE_ROOT}/lib/x86/*.dll" - "${SDL2_TTF_ROOT}/lib/x86/*.dll" - "${SDL2_MIXER_ROOT}/lib/x86/*.dll" - ) - - ADD_CUSTOM_COMMAND(TARGET RetroFE POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "${RETROFE_OUTPUT_CORE_PATH}" ) - - ADD_CUSTOM_COMMAND(TARGET RetroFE POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory "../Assets/Environment/Windows" "${RETROFE_OUTPUT_PATH}" ) - - - foreach(CORE_FILE ${CORE_FILES}) - ADD_CUSTOM_COMMAND(TARGET RetroFE POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CORE_FILE}" "${RETROFE_OUTPUT_CORE_PATH}" ) - endforeach() - - ADD_CUSTOM_COMMAND(TARGET RetroFE POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy "../Build/Debug/RetroFE.exe" "${RETROFE_OUTPUT_CORE_PATH}" ) - -else() - ADD_CUSTOM_COMMAND(TARGET RetroFE POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory "../Assets/Environment/Linux" "${RETROFE_OUTPUT_PATH}" ) - - ADD_CUSTOM_COMMAND(TARGET RetroFE POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy "../Build/RetroFE" "${RETROFE_OUTPUT_PATH}" ) -endif() - +endif() \ No newline at end of file diff --git a/RetroFE/Source/Collection/CollectionInfo.cpp b/RetroFE/Source/Collection/CollectionInfo.cpp new file mode 100644 index 0000000..b4fee86 --- /dev/null +++ b/RetroFE/Source/Collection/CollectionInfo.cpp @@ -0,0 +1,79 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "CollectionInfo.h" +#include "../Database/Configuration.h" +#include + +CollectionInfo::CollectionInfo(std::string name, + std::string listPath, + std::string extensions, + std::string metadataType, + std::string metadataPath) + : Name(name) + , ListPath(listPath) + , Extensions(extensions) + , MetadataType(metadataType) + , MetadataPath(metadataPath) +{ +} + +CollectionInfo::~CollectionInfo() +{ +} + +std::string CollectionInfo::GetName() const +{ + return Name; +} + +std::string CollectionInfo::GetSettingsPath() const +{ + return Configuration::GetAbsolutePath() + "/Collections/" + GetName(); +} + +std::string CollectionInfo::GetListPath() const +{ + return ListPath; +} + +std::string CollectionInfo::GetMetadataType() const +{ + return MetadataType; +} + +std::string CollectionInfo::GetMetadataPath() const +{ + return MetadataPath; +} + +std::string CollectionInfo::GetExtensions() const +{ + return Extensions; +} + +void CollectionInfo::GetExtensions(std::vector &extensions) +{ + std::istringstream ss(Extensions); + std::string token; + + while(std::getline(ss, token, ',')) + { + extensions.push_back(token); + } +} + + + diff --git a/RetroFE/Source/Collection/CollectionInfo.h b/RetroFE/Source/Collection/CollectionInfo.h new file mode 100644 index 0000000..9f5750e --- /dev/null +++ b/RetroFE/Source/Collection/CollectionInfo.h @@ -0,0 +1,28 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include +#include + +class CollectionInfo +{ +public: + CollectionInfo(std::string name, std::string listPath, std::string extensions, std::string metadataType, std::string metadataPath); + virtual ~CollectionInfo(); + std::string GetName() const; + std::string GetSettingsPath() const; + std::string GetListPath() const; + std::string GetMetadataType() const; + std::string GetMetadataPath() const; + std::string GetExtensions() const; + void GetExtensions(std::vector &extensions); + +private: + std::string Name; + std::string ListPath; + std::string Extensions; + std::string MetadataType; + std::string MetadataPath; +}; diff --git a/RetroFE/Source/Collection/CollectionInfoBuilder.cpp b/RetroFE/Source/Collection/CollectionInfoBuilder.cpp new file mode 100644 index 0000000..1be5f92 --- /dev/null +++ b/RetroFE/Source/Collection/CollectionInfoBuilder.cpp @@ -0,0 +1,141 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "CollectionInfoBuilder.h" +#include "CollectionInfo.h" +#include "../Database/Configuration.h" +#include "../Utility/Log.h" +#include +#include + +CollectionInfoBuilder::CollectionInfoBuilder(Configuration &c) + : Conf(c) +{ +} + +CollectionInfoBuilder::~CollectionInfoBuilder() +{ + std::map::iterator it = InfoMap.begin(); + + for(it == InfoMap.begin(); it != InfoMap.end(); ++it) + { + delete it->second; + } + + InfoMap.clear(); +} + +bool CollectionInfoBuilder::LoadAllCollections() +{ + std::vector collections; + + Conf.GetChildKeyCrumbs("collections", collections); + + if(collections.size() == 0) + { + Logger::Write(Logger::ZONE_ERROR, "Collections", "No collections were found. Please configure Settings.conf"); + return false; + } + + bool retVal = true; + std::vector::iterator it; + + for(it = collections.begin(); it != collections.end(); ++it) + { + // todo: There is nothing that should really stop us from creating a collection + // in the main folder. I just need to find some time to look at the impacts if + // I remove this conditional check. + if(*it != "Main") + { + if(ImportCollection(*it)) + { + Logger::Write(Logger::ZONE_INFO, "Collections", "Adding collection " + *it); + } + else + { + // Continue processing the rest of the collections if an error occurs during import. + // ImportCollection() will print out an error to the log file. + retVal = false; + } + } + } + + return retVal; +} + +void CollectionInfoBuilder::GetCollections(std::vector &collections) +{ + std::map::iterator InfoMapIt; + + for(InfoMapIt = InfoMap.begin(); InfoMapIt != InfoMap.end(); ++InfoMapIt) + { + collections.push_back(InfoMapIt->second); + } +} + +bool CollectionInfoBuilder::ImportCollection(std::string name) +{ + // create a new instance if one does not exist + if(InfoMap.find(name) != InfoMap.end()) + { + return true; + } + std::string listItemsPathKey = "collections." + name + ".list.path"; + std::string listFilterKey = "collections." + name + ".list.filter"; + std::string extensionsKey = "collections." + name + ".list.extensions"; + std::string launcherKey = "collections." + name + ".launcher"; + + //todo: metadata is not fully not implemented + std::string metadataTypeKey = "collections." + name + ".metadata.type"; + std::string metadataPathKey = "collections." + name + ".metadata.path"; + + std::string listItemsPath; + std::string launcherName; + std::string extensions; + std::string metadataType; + std::string metadataPath; + + if(!Conf.GetPropertyAbsolutePath(listItemsPathKey, listItemsPath)) + { + Logger::Write(Logger::ZONE_INFO, "Collections", "Property \"" + listItemsPathKey + "\" does not exist. Assuming \"" + name + "\" is a menu"); + return false; + } + + if(!Conf.GetProperty(extensionsKey, extensions)) + { + Logger::Write(Logger::ZONE_INFO, "Collections", "Property \"" + extensionsKey + "\" does not exist. Assuming \"" + name + "\" is a menu"); + return false; + } + + (void)Conf.GetProperty(metadataTypeKey, metadataType); + (void)Conf.GetProperty(metadataPathKey, metadataPath); + + if(!Conf.GetProperty(launcherKey, launcherName)) + { + std::stringstream ss; + ss << "Warning: launcher property \"" + << launcherKey + << "\" points to a launcher that is not configured (launchers." + << launcherName + << "). Your collection will be viewable, however you will not be able to " + << "launch any of the items in your collection."; + + Logger::Write(Logger::ZONE_WARNING, "Collections", ss.str()); + } + + InfoMap[name] = new CollectionInfo(name, listItemsPath, extensions, metadataType, metadataPath); + + return (InfoMap[name] != NULL); +} diff --git a/RetroFE/Source/Collection/CollectionInfoBuilder.h b/RetroFE/Source/Collection/CollectionInfoBuilder.h new file mode 100644 index 0000000..f512bcf --- /dev/null +++ b/RetroFE/Source/Collection/CollectionInfoBuilder.h @@ -0,0 +1,25 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include +#include +#include + +class Configuration; +class CollectionInfo; + +class CollectionInfoBuilder +{ +public: + CollectionInfoBuilder(Configuration &c); + virtual ~CollectionInfoBuilder(); + bool LoadAllCollections(); + void GetCollections(std::vector &keys); + +private: + bool ImportCollection(std::string name); + std::map InfoMap; + Configuration &Conf; +}; diff --git a/RetroFE/Source/Collection/Item.cpp b/RetroFE/Source/Collection/Item.cpp new file mode 100644 index 0000000..8f0aa32 --- /dev/null +++ b/RetroFE/Source/Collection/Item.cpp @@ -0,0 +1,179 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "Item.h" +#include "../Utility/Utils.h" +#include +#include + +Item::Item() + : NumberPlayers(0) + , NumberButtons(0) + , Leaf(true) +{ +} + +Item::~Item() +{ +} + +const std::string Item::GetFileName() const +{ + return Utils::GetFileName(FilePath); +} + +const std::string& Item::GetFilePath() const +{ + return FilePath; +} + +void Item::SetFilePath(const std::string& filepath) +{ + FilePath = filepath; +} + +const std::string& Item::GetLauncher() const +{ + return Launcher; +} + +void Item::SetLauncher(const std::string& launcher) +{ + Launcher = launcher; +} + +const std::string& Item::GetManufacturer() const +{ + return Manufacturer; +} + +void Item::SetManufacturer(const std::string& manufacturer) +{ + Manufacturer = manufacturer; +} + +const std::string& Item::GetName() const +{ + return Name; +} + +void Item::SetName(const std::string& name) +{ + Name = name; +} + +int Item::GetNumberButtons() const +{ + return NumberButtons; +} + +std::string Item::GetNumberButtonsString() +{ + std::stringstream ss; + ss << NumberButtons; + return ss.str(); +} + + +void Item::SetNumberButtons(int numberbuttons) +{ + NumberButtons = numberbuttons; +} + +int Item::GetNumberPlayers() const +{ + return NumberPlayers; +} + +std::string Item::GetNumberPlayersString() +{ + std::stringstream ss; + ss << NumberButtons; + return ss.str(); +} + + +void Item::SetNumberPlayers(int numberplayers) +{ + NumberPlayers = numberplayers; +} + +const std::string& Item::GetTitle() const +{ + return Title; +} + +const std::string& Item::GetLCTitle() const +{ + return LCTitle; +} + +void Item::SetTitle(const std::string& title) +{ + Title = title; + LCTitle = Title; + std::transform(LCTitle.begin(), LCTitle.end(), LCTitle.begin(), ::tolower); +} + +const std::string& Item::GetYear() const +{ + return Year; +} + +void Item::SetYear(const std::string& year) +{ + Year = year; +} + +bool Item::IsLeaf() const +{ + return Leaf; +} + +void Item::SetIsLeaf(bool leaf) +{ + Leaf = leaf; +} + +const std::string& Item::GetFullTitle() const +{ + return FullTitle; +} + +void Item::SetFullTitle(const std::string& fulltitle) +{ + FullTitle = fulltitle; +} + +const std::string& Item::GetCloneOf() const +{ + return CloneOf; +} + +void Item::SetCloneOf(const std::string& cloneOf) +{ + CloneOf = cloneOf; +} + +bool Item::operator<(const Item &rhs) +{ + return LCTitle < rhs.LCTitle; +} +bool Item::operator>(const Item &rhs) +{ + return LCTitle > rhs.LCTitle; +} + diff --git a/RetroFE/Source/Collection/Item.h b/RetroFE/Source/Collection/Item.h new file mode 100644 index 0000000..2109c8e --- /dev/null +++ b/RetroFE/Source/Collection/Item.h @@ -0,0 +1,56 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include + +class Item +{ +public: + Item(); + virtual ~Item(); + const std::string GetFileName() const; + const std::string& GetFilePath() const; + void SetFilePath(const std::string& filepath); + const std::string& GetLauncher() const; + void SetLauncher(const std::string& launcher); + const std::string& GetManufacturer() const; + void SetManufacturer(const std::string& manufacturer); + const std::string& GetName() const; + void SetName(const std::string& name); + int GetNumberButtons() const; + std::string GetNumberButtonsString(); + void SetNumberButtons(int numberbuttons); + void SetNumberPlayers(int numberplayers); + int GetNumberPlayers() const; + std::string GetNumberPlayersString(); + const std::string& GetTitle() const; + const std::string& GetLCTitle() const; + void SetTitle(const std::string& title); + const std::string& GetYear() const; + void SetYear(const std::string& year); + bool IsLeaf() const; + void SetIsLeaf(bool leaf); + const std::string& GetFullTitle() const; + void SetFullTitle(const std::string& fulltitle); + const std::string& GetCloneOf() const; + void SetCloneOf(const std::string& cloneOf); + bool operator<(const Item& rhs); + bool operator>(const Item& rhs); + +private: + std::string Launcher; + std::string FilePath; + std::string Name; + std::string Title; + std::string LCTitle; + std::string FullTitle; + std::string Year; + std::string Manufacturer; + std::string CloneOf; + int NumberPlayers; + int NumberButtons; + bool Leaf; +}; + diff --git a/RetroFE/Source/Collection/MenuParser.cpp b/RetroFE/Source/Collection/MenuParser.cpp new file mode 100644 index 0000000..0465128 --- /dev/null +++ b/RetroFE/Source/Collection/MenuParser.cpp @@ -0,0 +1,121 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "MenuParser.h" +#include "Item.h" +#include "../Utility/Log.h" +#include "../Database/Configuration.h" +#include "../Database/CollectionDatabase.h" +#include "../Database/DB.h" +#include +#include +#include +#include + +bool VectorSort(const Item *d1, const Item *d2) +{ + return d1->GetLCTitle() < d2->GetLCTitle(); +} + +MenuParser::MenuParser() +{ +} + +MenuParser::~MenuParser() +{ +} + +//todo: clean up this method, too much nesting +bool MenuParser::GetMenuItems(CollectionDatabase *cdb, std::string collectionName, std::vector &items) +{ + bool retVal = false; + //todo: magic string + std::string menuFilename = Configuration::GetAbsolutePath() + "/Collections/" + collectionName + "/Menu.xml"; + rapidxml::xml_document<> doc; + rapidxml::xml_node<> * rootNode; + + Logger::Write(Logger::ZONE_INFO, "Menu", "Checking if menu exists at \"" + menuFilename + "\""); + + try + { + std::ifstream file(menuFilename.c_str()); + + // gracefully exit if there is no menu file for the pa + if(file.good()) + { + Logger::Write(Logger::ZONE_INFO, "Menu", "Found menu"); + std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + buffer.push_back('\0'); + + doc.parse<0>(&buffer[0]); + + rootNode = doc.first_node("menu"); + + for (rapidxml::xml_node<> * itemNode = rootNode->first_node("item"); itemNode; itemNode = itemNode->next_sibling()) + { + rapidxml::xml_attribute<> *collectionAttribute = itemNode->first_attribute("collection"); + rapidxml::xml_attribute<> *importAttribute = itemNode->first_attribute("import"); + + if(!collectionAttribute) + { + retVal = false; + Logger::Write(Logger::ZONE_ERROR, "Menu", "Menu item tag is missing collection attribute"); + break; + } + else + { + //todo: too much nesting! Ack! + std::string import; + if(importAttribute) + { + import = importAttribute->value(); + } + if(import != "true") + { + //todo, check for empty string + std::string title = collectionAttribute->value(); + Item *item = new Item(); + item->SetTitle(title); + item->SetFullTitle(title); + item->SetName(collectionAttribute->value()); + item->SetIsLeaf(false); + items.push_back(item); + } + else + { + std::string collectionName = collectionAttribute->value(); + Logger::Write(Logger::ZONE_INFO, "Menu", "Loading collection into menu: " + collectionName); + cdb->GetCollection(collectionAttribute->value(), items); + } + } + } + + std::sort( items.begin(), items.end(), VectorSort); + + retVal = true; + } + } + catch(std::ifstream::failure &e) + { + std::stringstream ss; + ss << "Unable to open menu file \"" << menuFilename << "\": " << e.what(); + Logger::Write(Logger::ZONE_ERROR, "Menu", ss.str()); + } + + return retVal; + +} diff --git a/Source/Collection/MenuParser.h b/RetroFE/Source/Collection/MenuParser.h similarity index 61% rename from Source/Collection/MenuParser.h rename to RetroFE/Source/Collection/MenuParser.h index 4c15e31..a712149 100644 --- a/Source/Collection/MenuParser.h +++ b/RetroFE/Source/Collection/MenuParser.h @@ -1,5 +1,5 @@ -/* This file is subject to the terms and conditions defined in - * file 'LICENSE.txt', which is part of this source code package. +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. */ #pragma once #include "Item.h" @@ -10,8 +10,8 @@ class CollectionDatabase; class MenuParser { public: - MenuParser(); - virtual ~MenuParser(); - bool GetMenuItems(CollectionDatabase *cdb, std::string collectionName, std::vector &items); + MenuParser(); + virtual ~MenuParser(); + bool GetMenuItems(CollectionDatabase *cdb, std::string collectionName, std::vector &items); }; diff --git a/RetroFE/Source/Control/UserInput.cpp b/RetroFE/Source/Control/UserInput.cpp new file mode 100644 index 0000000..ea0ce4e --- /dev/null +++ b/RetroFE/Source/Control/UserInput.cpp @@ -0,0 +1,91 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "UserInput.h" +#include "../Database/Configuration.h" +#include "../Utility/Log.h" + +UserInput::UserInput(Configuration &c) + : Config(c) +{ +} + +UserInput::~UserInput() +{ +} + +bool UserInput::Initialize() +{ + bool retVal = true; + + retVal = MapKey("nextItem", KeyCodeNextItem) && retVal; + retVal = MapKey("previousItem", KeyCodePreviousItem) && retVal; + retVal = MapKey("pageDown", KeyCodePageDown) && retVal; + retVal = MapKey("pageUp", KeyCodePageUp) && retVal; + retVal = MapKey("select", KeyCodeSelect) && retVal; + retVal = MapKey("back", KeyCodeBack) && retVal; + retVal = MapKey("quit", KeyCodeQuit) && retVal; + // these features will need to be implemented at a later time +// retVal = MapKey("admin", KeyCodeAdminMode) && retVal; +// retVal = MapKey("remove", KeyCodeHideItem) && retVal; + + return retVal; +} + +SDL_Scancode UserInput::GetScancode(KeyCode_E key) +{ + SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN; + std::map::iterator it = KeyMap.find(key); + + if(it != KeyMap.end()) + { + scancode = it->second; + } + + return scancode; +} + + +bool UserInput::MapKey(std::string keyDescription, KeyCode_E key) +{ + bool retVal = false; + SDL_Scancode scanCode; + std::string description; + + std::string configKey = "controls." + keyDescription; + + if(!Config.GetProperty(configKey, description)) + { + Logger::Write(Logger::ZONE_ERROR, "Configuration", "Missing property " + configKey); + } + else + { + scanCode = SDL_GetScancodeFromName(description.c_str()); + + if(scanCode == SDL_SCANCODE_UNKNOWN) + { + Logger::Write(Logger::ZONE_ERROR, "Configuration", "Unsupported property value for " + configKey + "(" + description + "). See Documentation/Keycodes.txt for valid inputs"); + } + else + { + KeyMap[key] = scanCode; + retVal = true; + } + } + + return retVal; +} + diff --git a/RetroFE/Source/Control/UserInput.h b/RetroFE/Source/Control/UserInput.h new file mode 100644 index 0000000..e7eb51b --- /dev/null +++ b/RetroFE/Source/Control/UserInput.h @@ -0,0 +1,38 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once +#include +#include +#include + +class Configuration; + +class UserInput +{ +public: + enum KeyCode_E + { + KeyCodeNextItem, + KeyCodePreviousItem, + KeyCodeSelect, + KeyCodeBack, + KeyCodePageDown, + KeyCodePageUp, + KeyCodeAdminMode, + KeyCodeHideItem, + KeyCodeQuit + }; + + UserInput(Configuration &c); + virtual ~UserInput(); + bool Initialize(); + SDL_Scancode GetScancode(KeyCode_E key); + + +private: + bool MapKey(std::string keyDescription, KeyCode_E key); + std::map KeyMap; + + Configuration &Config; +}; diff --git a/RetroFE/Source/Database/CollectionDatabase.cpp b/RetroFE/Source/Database/CollectionDatabase.cpp new file mode 100644 index 0000000..d1a4098 --- /dev/null +++ b/RetroFE/Source/Database/CollectionDatabase.cpp @@ -0,0 +1,759 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "CollectionDatabase.h" +#include "../Collection/CollectionInfoBuilder.h" +#include "../Collection/CollectionInfo.h" +#include "../Collection/Item.h" +#include "../Utility/Log.h" +#include "../Utility/Utils.h" +#include "MamelistMetadata.h" +#include "Configuration.h" +#include "DB.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CollectionDatabase::CollectionDatabase(DB &db, Configuration &c) + : Config(c) + , DBInstance(db) +{ + +} + +CollectionDatabase::~CollectionDatabase() +{ +} + +bool CollectionDatabase::ResetDatabase() +{ + bool retVal = true; + int rc; + char *error = NULL; + sqlite3 *handle = DBInstance.GetHandle(); + + Logger::Write(Logger::ZONE_INFO, "Database", "Erasing"); + + std::string sql; + sql.append("DROP TABLE IF EXISTS CollectionItems;"); + sql.append("DROP TABLE IF EXISTS Meta;"); + sql.append("DROP TABLE IF EXISTS Collections;"); + + rc = sqlite3_exec(handle, sql.c_str(), NULL, 0, &error); + + if(rc != SQLITE_OK) + { + std::stringstream ss; + ss << "Unable to create Configurations table. Error: " << error; + Logger::Write(Logger::ZONE_ERROR, "Database", ss.str()); + retVal = false; + } + + return retVal; +} + +bool CollectionDatabase::Initialize() +{ + int rc; + char *error = NULL; + sqlite3 *handle = DBInstance.GetHandle(); + + std::string sql; + sql.append("CREATE TABLE IF NOT EXISTS CollectionItems("); + sql.append("collectionName TEXT KEY,"); + sql.append("filePath TEXT NOT NULL DEFAULT '',"); + sql.append("name TEXT NOT NULL DEFAULT '',"); + sql.append("hidden INT NOT NULL DEFAULT 0);"); + + sql.append("CREATE TABLE IF NOT EXISTS Meta("); + sql.append("collectionName TEXT KEY,"); + sql.append("name TEXT NOT NULL DEFAULT '',"); + sql.append("title TEXT NOT NULL DEFAULT '',"); + sql.append("year TEXT NOT NULL DEFAULT '',"); + sql.append("manufacturer TEXT NOT NULL DEFAULT '',"); + sql.append("cloneOf TEXT NOT NULL DEFAULT '',"); + sql.append("players INTEGER,"); + sql.append("buttons INTEGER);"); + sql.append("CREATE UNIQUE INDEX IF NOT EXISTS MetaUniqueId ON Meta(collectionName, name);"); + + sql.append("CREATE TABLE IF NOT EXISTS Collections("); + sql.append("collectionName TEXT KEY,"); + sql.append("crc32 UNSIGNED INTEGER NOT NULL DEFAULT 0);"); + sql.append("CREATE UNIQUE INDEX IF NOT EXISTS CollectionsUniqueId ON Collections(collectionName);"); + + rc = sqlite3_exec(handle, sql.c_str(), NULL, 0, &error); + + if(rc != SQLITE_OK) + { + std::stringstream ss; + ss << "Unable to create Configurations table. Error: " << error; + Logger::Write(Logger::ZONE_ERROR, "Database", ss.str()); + + return false; + } + + return true; +} + + +bool CollectionDatabase::Import() +{ + bool retVal = true; + + // should keep allocation here + CollectionInfoBuilder cib(Config); + + (void)cib.LoadAllCollections(); + + std::vector collections; + cib.GetCollections(collections); + + std::vector::iterator it; + for(it = collections.begin(); it != collections.end() && retVal; ++it) + { + CollectionInfo *info = *it; + std::string title = info->GetName(); + unsigned long crc32 = CalculateCollectionCrc32(info); + + std::stringstream crcStr; + crcStr << crc32; + + if(title != "Main") + { + if(CollectionChanged(info, crc32)) + { + std::string msg = "Detected collection \"" + title + "\" has changed (new CRC: " + crcStr.str() + "). Rebuilding database for this collection."; + Logger::Write(Logger::ZONE_INFO, "Database", msg); + + (void)ImportDirectory(info, crc32); + retVal = true; + } + else + { + std::stringstream ss; + std::string msg = "Collection \"" + title + "\" has not changed (CRC: " + crcStr.str() + "). Using existing database settings."; + Logger::Write(Logger::ZONE_INFO, "Database", msg); + + } + } + //std::cout << "Importing collection metadata for " << info->GetFullTitle() << " (collections." << info->GetName() << ")" << std::endl; + //ImportMetadata(info); + } + Logger::Write(Logger::ZONE_INFO, "Database", "COMPLETE"); + Sleep(1000); + return retVal; +} + +unsigned long CollectionDatabase::CalculateCollectionCrc32(CollectionInfo *info) +{ + unsigned long crc = crc32(0L, Z_NULL, 0); + + // start off by reading all of the contents in the collection configuration folders + std::string settingsFile = info->GetSettingsPath() + "/Settings.conf"; + crc = CrcFile(settingsFile, crc); + + + std::string includeFile = info->GetSettingsPath() + "/Include.txt"; + crc = CrcFile(includeFile, crc); + + std::string excludeFile = info->GetSettingsPath() + "/Exclude.txt"; + crc = CrcFile(excludeFile, crc); + + std::string mamelistFile = info->GetSettingsPath() + "/Mamelist.xml"; + crc = CrcFile(mamelistFile, crc); + + DIR *dp; + struct dirent *dirp; + std::string path = info->GetListPath(); + dp = opendir(path.c_str()); + + if(dp == NULL) + { + Logger::Write(Logger::ZONE_ERROR, "Database", "Could not read directory for caching \"" + info->GetListPath() + "\""); + return crc; + } + + std::vector extensions; + info->GetExtensions(extensions); + std::vector::iterator extensionsIt; + + // md5sum each filename for the matching extension + while((dirp = readdir(dp)) != NULL) + { + std::string file = dirp->d_name; + for(extensionsIt = extensions.begin(); extensionsIt != extensions.end(); ++extensionsIt) + { + std::string comparator = "." + *extensionsIt; + int start = file.length() - comparator.length() + 1; + + if(start >= 0 && file.compare(start, comparator.length(), *extensionsIt) == 0) + { + std::string filename = dirp->d_name; + filename.append("\n"); + crc = crc32(crc, (const unsigned char *)filename.c_str(), (unsigned int)filename.length()); + } + } + } + + return crc; +} + +unsigned long CollectionDatabase::CrcFile(std::string file, unsigned long crc) +{ + // CRC both the filename and its contents + crc = crc32(crc, (const unsigned char *)file.c_str(), (unsigned int)file.length()); + std::ifstream ifFile(file.c_str()); + if(ifFile.good()) + { + std::stringstream ss; + ss << ifFile.rdbuf(); + + crc = crc32(crc, (const unsigned char *)ss.str().c_str(), (unsigned int)ss.str().length()); + Logger::Write(Logger::ZONE_INFO, "Database", "Crcing \"" + file + "\""); + ifFile.close(); + } + + return crc; +} + +bool CollectionDatabase::CollectionChanged(CollectionInfo *info, unsigned long crc32) +{ + bool retVal = true; + + sqlite3 *handle = DBInstance.GetHandle(); + int rc; + sqlite3_stmt *stmt; + + sqlite3_prepare_v2(handle, + "SELECT crc32 " + "FROM Collections WHERE collectionName=? and crc32=?;", + -1, &stmt, 0); + + sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 2, crc32); + + rc = sqlite3_step(stmt); + + if(rc == SQLITE_ROW) + { + retVal = false; + } + + return retVal; +} + +bool CollectionDatabase::SetHidden(std::string collectionName, Item *item, bool hidden) +{ + bool retVal = true; + char *error = NULL; + sqlite3 *handle = DBInstance.GetHandle(); + std::string mode = (hidden) ? "hidden":"visible"; + int isHidden = (hidden)?1:0; + + Logger::Write(Logger::ZONE_DEBUG, "Database", "Marking \"" + item->GetFullTitle() + "\" " + mode); + + sqlite3_stmt *stmt; + sqlite3_prepare_v2(handle, + "UPDATE CollectionItems SET hidden=? WHERE collectionName=? AND name=?;", + -1, &stmt, 0); + + sqlite3_bind_int(stmt, 1, isHidden); + sqlite3_bind_text(stmt, 2, collectionName.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, item->GetFullTitle().c_str(), -1, SQLITE_TRANSIENT); + + sqlite3_step(stmt); + sqlite3_finalize(stmt); + + sqlite3_exec(handle, "COMMIT TRANSACTION;", NULL, NULL, &error); + + return retVal; +} + +//todo: This file needs MASSIVE REFACTORING! +bool CollectionDatabase::ImportDirectory(CollectionInfo *info, unsigned long crc32) +{ + DIR *dp; + struct dirent *dirp; + std::string path = info->GetListPath(); + std::map includeFilter; + std::map excludeFilter; + std::map includeList; + std::map metaList; + bool retVal = true; + char *error = NULL; + sqlite3 *handle = DBInstance.GetHandle(); + std::string includeFile = Configuration::GetAbsolutePath() + "/Collections/" + info->GetName() + "/Include.txt"; + std::string excludeFile = Configuration::GetAbsolutePath() + "/Collections/" + info->GetName() + "/Exclude.txt"; + std::string includeHyperListFile = Configuration::GetAbsolutePath() + "/Collections/" + info->GetName() + "/Include.xml"; + + if(!ImportBasicList(info, includeFile, includeFilter)) + { + ImportHyperList(info, includeHyperListFile, includeFilter); + + } + //todo: this shouldn't be read twice, perform a copy + ImportHyperList(info, includeHyperListFile, metaList); + + (void)ImportBasicList(info, excludeFile, excludeFilter); + + dp = opendir(path.c_str()); + std::vector extensions; + info->GetExtensions(extensions); + std::vector::iterator extensionsIt; + + if(dp == NULL) + { + Logger::Write(Logger::ZONE_ERROR, "Database", "Could not read directory \"" + info->GetListPath() + "\""); + //todo: store into a database + } + else + { + while((dirp = readdir(dp)) != NULL) + { + std::string file = dirp->d_name; + + Utils::NormalizeBackSlashes(file); + size_t position = file.find_last_of("."); + std::string basename = (std::string::npos == position)? file : file.substr(0, position); + + + if((includeFilter.size() == 0 || includeFilter.find(basename) != includeFilter.end()) && + (excludeFilter.size() == 0 || excludeFilter.find(basename) == excludeFilter.end())) + { + for(extensionsIt = extensions.begin(); extensionsIt != extensions.end(); ++extensionsIt) + { + std::string comparator = "." + *extensionsIt; + int start = file.length() - comparator.length() + 1; + + if(start >= 0) + { + if(file.compare(start, comparator.length(), *extensionsIt) == 0) + { + if(includeList.find(basename) == includeList.end()) + { + Item *i = new Item(); + i->SetFullTitle(file); + includeList[basename] = i; + } + if(metaList.find(basename) == metaList.end()) + { + Item *i = new Item(); + i->SetFullTitle(file); + metaList[basename] = i; + } + } + } + } + } + } + } + + while(includeFilter.size() > 0) + { + std::map::iterator it = includeFilter.begin(); + delete it->second; + includeFilter.erase(it); + } + while(excludeFilter.size() > 0) + { + std::map::iterator it = excludeFilter.begin(); + delete it->second; + excludeFilter.erase(it); + } + + + Logger::Write(Logger::ZONE_INFO, "Database", "Scanning to import \"" + path + "\""); + sqlite3_exec(handle, "BEGIN IMMEDIATE TRANSACTION;", NULL, NULL, &error); + + sqlite3_stmt *stmt; + sqlite3_prepare_v2(handle, + "DELETE FROM Collections WHERE collectionName=?;", + -1, &stmt, 0); + sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + + sqlite3_prepare_v2(handle, + "DELETE FROM CollectionItems WHERE collectionName=?;", + -1, &stmt, 0); + sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + + sqlite3_prepare_v2(handle, + "DELETE FROM Meta WHERE collectionName=?;", + -1, &stmt, 0); + sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + + if(sqlite3_exec(handle, "COMMIT TRANSACTION;", NULL, NULL, &error) != SQLITE_OK) + { + std::stringstream ss; + ss << "Updating cache collection failure " << error; + Logger::Write(Logger::ZONE_ERROR, "Database", ss.str()); + retVal = false; + } + + std::map::iterator it; + + if(sqlite3_exec(handle, "BEGIN IMMEDIATE TRANSACTION;", NULL, NULL, &error) != SQLITE_OK) + { + std::stringstream ss; + ss << "Delete cache collection failure " << error; + Logger::Write(Logger::ZONE_ERROR, "Database", ss.str()); + retVal = false; + } + + for(it = includeList.begin(); it != includeList.end(); it++) + { + std::string basename = it->first; + Item *file = it->second; + + std::string name = file->GetFullTitle(); + Utils::NormalizeBackSlashes(name); + file->SetFullTitle(name); + + sqlite3_prepare_v2(handle, + "INSERT OR REPLACE INTO CollectionItems (collectionName, filePath, name) VALUES (?,?,?);", + -1, &stmt, 0); + + sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, file->GetFullTitle().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, basename.c_str(), -1, SQLITE_TRANSIENT); + + //todo: better error handling for all of these messages + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + for(it = metaList.begin(); it != metaList.end(); it++) + { + std::string basename = it->first; + Item *file = it->second; + + sqlite3_prepare_v2(handle, + "INSERT OR REPLACE INTO Meta (collectionName, name, title, year, manufacturer, cloneOf, players, buttons) VALUES (?,?,?,?,?,?,?,?);", + -1, &stmt, 0); + + sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, basename.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, basename.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 4, file->GetYear().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 5, file->GetManufacturer().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 6, file->GetCloneOf().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 7, file->GetNumberPlayersString().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 8, file->GetNumberButtonsString().c_str(), -1, SQLITE_TRANSIENT); + + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + + sqlite3_prepare_v2(handle, + "INSERT OR REPLACE INTO Collections (collectionName, crc32) VALUES (?,?);", + -1, &stmt, 0); + + sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 2, crc32); + + sqlite3_step(stmt); + sqlite3_finalize(stmt); + + + if(sqlite3_exec(handle, "COMMIT TRANSACTION;", NULL, NULL, &error) != SQLITE_OK) + { + std::stringstream ss; + ss << "Updating cache collection failure " << error; + Logger::Write(Logger::ZONE_ERROR, "Database", ss.str()); + retVal = false; + } + + Logger::Write(Logger::ZONE_INFO, "Database", "Imported files from \"" + path + "\" into database"); + + //todo: create a helper method to get this file directly (copy paste hazard) + std::string mamelistFile = info->GetSettingsPath() + "/Mamelist.xml"; + std::ifstream infile(mamelistFile.c_str()); + + if(infile.good()) + { + Logger::Write(Logger::ZONE_INFO, "Database", "Updating Mamelist metadata for \"" + info->GetName() + "\" (\"" + mamelistFile + "\") into database. This will take a while..."); + MamelistMetadata mld(DBInstance); + mld.Import(mamelistFile, info->GetName()); + } + infile.close(); + + + while(includeList.size() > 0) + { + std::map::iterator it = includeList.begin(); + delete it->second; + includeList.erase(it); + } + while(metaList.size() > 0) + { + std::map::iterator it = metaList.begin(); + delete it->second; + metaList.erase(it); + } + return retVal; +} + +bool CollectionDatabase::ImportBasicList(CollectionInfo *info, std::string file, std::map &list) +{ + bool retVal = false; + + Logger::Write(Logger::ZONE_DEBUG, "Database", "Checking to see if \"" + file + "\" exists"); + + std::ifstream includeStream(file.c_str()); + + if (includeStream.good()) + { + Logger::Write(Logger::ZONE_DEBUG, "Database", "Importing \"" + file + "\""); + std::string line; + + while(std::getline(includeStream, line)) + { + if(list.find(line) == list.end()) + { + Item *i = new Item(); + line.erase( std::remove(line.begin(), line.end(), '\r'), line.end() ); + + i->SetFullTitle(line); + list[line] = i; + Logger::Write(Logger::ZONE_DEBUG, "Database", "Including \"" + line + "\" (if file exists)"); + } + } + + retVal = true; + } + + return retVal; +} +bool CollectionDatabase::ImportHyperList(CollectionInfo *info, std::string hyperlistFile, std::map &list) +{ + bool retVal = false; + rapidxml::xml_document<> doc; + std::ifstream file(hyperlistFile.c_str()); + std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + Logger::Write(Logger::ZONE_DEBUG, "Database", "Checking to see if \"" + hyperlistFile + "\" exists"); + + if(!file.good()) + { + Logger::Write(Logger::ZONE_INFO, "Database", "Could not find HyperList file: " + hyperlistFile); + return retVal; + } + + try + { + Logger::Write(Logger::ZONE_INFO, "Database", "Importing: " + hyperlistFile); + buffer.push_back('\0'); + + doc.parse<0>(&buffer[0]); + + rapidxml::xml_node<> *root = doc.first_node("menu"); + + + if(!root) + { + Logger::Write(Logger::ZONE_ERROR, "CollectionDatabase", "Does not appear to be a HyperList file (missing tag)"); + return NULL; + } + else + { + for(rapidxml::xml_node<> *game = root->first_node("game"); game; game = game->next_sibling("game")) + { + rapidxml::xml_attribute<> *nameXml = game->first_attribute("name"); + rapidxml::xml_node<> *descriptionXml = game->first_node("description"); + rapidxml::xml_node<> *cloneofXml = game->first_node("cloneof"); + rapidxml::xml_node<> *crcXml = game->first_node("crc"); + rapidxml::xml_node<> *manufacturerXml = game->first_node("manufacturer"); + rapidxml::xml_node<> *yearXml = game->first_node("year"); + rapidxml::xml_node<> *genreXml = game->first_node("genre"); + rapidxml::xml_node<> *ratingXml = game->first_node("rating"); + rapidxml::xml_node<> *enabledXml = game->first_node("enabled"); + std::string name = (nameXml) ? nameXml->value() : ""; + std::string description = (descriptionXml) ? descriptionXml->value() : ""; + std::string crc = (crcXml) ? crcXml->value() : ""; + std::string cloneOf = (cloneofXml) ? cloneofXml->value() : ""; + std::string manufacturer = (manufacturerXml) ? manufacturerXml->value() : ""; + std::string year = (yearXml) ? yearXml->value() : ""; + std::string genre = (genreXml) ? genreXml->value() : ""; + std::string rating = (ratingXml) ? ratingXml->value() : ""; + std::string enabled = (enabledXml) ? enabledXml->value() : ""; + + if(name.length() > 0 && list.find(name) == list.end()) + { + Item *i = new Item(); + i->SetFullTitle(name); + i->SetYear(year); + i->SetManufacturer(manufacturer); + i->SetCloneOf(cloneOf); + list[name] = i; + Logger::Write(Logger::ZONE_DEBUG, "Database", "Including \"" + name + "\" (if file exists)"); + } + + } + } + } + catch(rapidxml::parse_error &e) + { + std::string what = e.what(); + long line = static_cast(std::count(&buffer.front(), e.where(), char('\n')) + 1); + std::stringstream ss; + ss << "Could not parse layout file. [Line: " << line << "] Reason: " << e.what(); + + Logger::Write(Logger::ZONE_ERROR, "Layout", ss.str()); + } + catch(std::exception &e) + { + std::string what = e.what(); + Logger::Write(Logger::ZONE_ERROR, "Layout", "Could not parse layout file. Reason: " + what); + } + + + return retVal; +} + +/* +bool CollectionDatabase::ImportMetadata(CollectionInfo *info) +{ + bool retVal = true; + std::string type = info->GetMetadataType(); + + if(type.compare("mamelist") == 0) + { + MamelistMetadata meta; + //todo: pass in collectionName + retVal = meta.Import(info->GetMetadataPath(), "arcade"); + } + else if(!type.empty()) + { + std::stringstream ss; + ss << "Unsupported metadata type \"" << type << "\" for " << info->GetFullTitle() << " (collections." << info->GetName() << ".metadata.type)" << std::endl; + Log::Write(Log::ERROR, "Database", ss.str()); + + retVal = false; + } + + return retVal; +} +*/ + +bool CollectionDatabase::GetCollection(std::string collectionName, std::vector &list) +{ + bool retVal = true; + + sqlite3 *handle = DBInstance.GetHandle(); + int rc; + sqlite3_stmt *stmt; + + bool showParenthesis = true; + bool showSquareBrackets = true; + + (void)Config.GetProperty("showParenthesis", showParenthesis); + (void)Config.GetProperty("showSquareBrackets", showSquareBrackets); + + //todo: program crashes if this query fails + sqlite3_prepare_v2(handle, + "SELECT DISTINCT CollectionItems.filePath, CollectionItems.name, Meta.title, Meta.year, Meta.manufacturer, Meta.players, Meta.buttons, Meta.cloneOf " + "FROM CollectionItems, Meta WHERE CollectionItems.collectionName=? AND Meta.collectionName=? AND CollectionItems.name=Meta.name AND CollectionItems.hidden=0 ORDER BY title ASC;", + -1, &stmt, 0); + + sqlite3_bind_text(stmt, 1, collectionName.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, collectionName.c_str(), -1, SQLITE_TRANSIENT); + + rc = sqlite3_step(stmt); + + while(rc == SQLITE_ROW) + { + std::string filePath = (char *)sqlite3_column_text(stmt, 0); + std::string name = (char *)sqlite3_column_text(stmt, 1); + std::string fullTitle = (char *)sqlite3_column_text(stmt, 2); + std::string year = (char *)sqlite3_column_text(stmt, 3); + std::string manufacturer = (char *)sqlite3_column_text(stmt, 4); + int numberPlayers = (int)sqlite3_column_int(stmt, 5); + int numberButtons = (int)sqlite3_column_int(stmt, 6); + std::string cloneOf = (char *)sqlite3_column_text(stmt, 7); + std::string launcher; + std::string title = fullTitle; + + if(!showParenthesis) + { + std::string::size_type firstPos = title.find_first_of("("); + std::string::size_type secondPos = title.find_first_of(")", firstPos); + + while(firstPos != std::string::npos && secondPos != std::string::npos) + { + firstPos = title.find_first_of("("); + secondPos = title.find_first_of(")", firstPos); + + if (firstPos != std::string::npos) + { + title.erase(firstPos, (secondPos - firstPos) + 1); + } + } + } + if(!showSquareBrackets) + { + std::string::size_type firstPos = title.find_first_of("["); + std::string::size_type secondPos = title.find_first_of("]", firstPos); + + while(firstPos != std::string::npos && secondPos != std::string::npos) + { + firstPos = title.find_first_of("["); + secondPos = title.find_first_of("]", firstPos); + + if (firstPos != std::string::npos && secondPos != std::string::npos) + { + title.erase(firstPos, (secondPos - firstPos) + 1); + } + } + } + + Item *item = new Item(); + item->SetFilePath(filePath); + item->SetName(name); + item->SetTitle(title); + item->SetFullTitle(fullTitle); + item->SetYear(year); + item->SetManufacturer(manufacturer); + item->SetNumberPlayers(numberPlayers); + item->SetNumberButtons(numberButtons); + item->SetCloneOf(cloneOf); + + //std::cout << "loading " << title << std::endl; + if(Config.GetProperty("collections." + collectionName + ".launcher", launcher)) + { + item->SetLauncher(launcher); + } + + list.push_back(item); + + rc = sqlite3_step(stmt); + } + + //todo: query the metadata table to populate each item + + return retVal; +} diff --git a/RetroFE/Source/Database/CollectionDatabase.h b/RetroFE/Source/Database/CollectionDatabase.h new file mode 100644 index 0000000..ff656b2 --- /dev/null +++ b/RetroFE/Source/Database/CollectionDatabase.h @@ -0,0 +1,44 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include +#include +#include + +class DB; +class Configuration; +class CollectionInfo; +class Item; + +class CollectionDatabase +{ +public: + CollectionDatabase(DB &db, Configuration &c); + virtual ~CollectionDatabase(); + bool Initialize(); + bool Import(); + bool ResetDatabase(); + + + bool GetCollection(std::string collectionName, std::vector &list); + bool SetHidden(std::string collectionName, Item *item, bool hidden); + +private: + unsigned long CalculateCollectionCrc32(CollectionInfo *info); + bool CollectionChanged(CollectionInfo *info, unsigned long crc32); + unsigned long CrcFile(std::string file, unsigned long crc); + +// bool ImportMetadata(CollectionInfo *info); + bool ImportDirectory(CollectionInfo *info, unsigned long crc32); + bool ImportBasicList(CollectionInfo *info, + std::string file, + std::map &list); + bool ImportHyperList(CollectionInfo *info, + std::string file, + std::map &list); + std::map *ImportHyperList(CollectionInfo *info); + Configuration &Config; + DB &DBInstance; +}; diff --git a/RetroFE/Source/Database/Configuration.cpp b/RetroFE/Source/Database/Configuration.cpp new file mode 100644 index 0000000..d6ae685 --- /dev/null +++ b/RetroFE/Source/Database/Configuration.cpp @@ -0,0 +1,345 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "Configuration.h" +#include "../Utility/Log.h" +#include "../Utility/Utils.h" +#include +#include +#include +#include + +#ifdef WIN32 +#include +#else +#include +#include +#endif + +std::string Configuration::AbsolutePath; + +Configuration::Configuration() + : Verbose(false) +{ +} + +Configuration::~Configuration() +{ +} + +void Configuration::Initialize() +{ + const char *environment = std::getenv("RETROFE_PATH"); + std::string environmentStr; + if (environment != NULL) + { + environmentStr = environment; + Configuration::SetAbsolutePath(environment); + } + else + { +#ifdef WIN32 + HMODULE hModule = GetModuleHandle(NULL); + CHAR exe[MAX_PATH]; + GetModuleFileName(hModule, exe, MAX_PATH); + std::string sPath(exe); + sPath = Utils::GetDirectory(sPath); + sPath = Utils::GetParentDirectory(sPath); +#else + char exepath[1024]; + sprintf(exepath, "/proc/%d/exe", getpid()); + readlink(exepath, exepath, sizeof(exepath)); + std::string sPath(exepath); + sPath = Utils::GetDirectory(sPath); +#endif + + + Configuration::SetAbsolutePath(sPath); + } +} + +bool Configuration::Import(std::string keyPrefix, std::string file) +{ + bool retVal = true; + int lineCount = 0; + std::string line; + + Logger::Write(Logger::ZONE_INFO, "Configuration", "Importing " + file); + + std::ifstream ifs(file.c_str()); + + if (!ifs.is_open()) + { + Logger::Write(Logger::ZONE_ERROR, "Configuration", "Could not open " + file); + + return false; + } + + while (std::getline (ifs, line)) + { + lineCount++; + retVal = retVal && ParseLine(keyPrefix, line, lineCount); + } + + ifs.close(); + + return retVal; +} + +bool Configuration::ParseLine(std::string keyPrefix, std::string line, int lineCount) +{ + bool retVal = false; + std::string key; + std::string value; + size_t position; + std::string delimiter = "="; + + // strip out any comments + if((position = line.find("#")) != std::string::npos) + { + line = line.substr(0, position); + } + // unix only wants \n. Windows uses \r\n. Strip off the \r for unix. + line.erase( std::remove(line.begin(), line.end(), '\r'), line.end() ); + if(line.empty() || (line.find_first_not_of(" \t\r") == std::string::npos)) + { + retVal = true; + } + // all configuration fields must have an assignment operator + else if((position = line.find(delimiter)) != std::string::npos) + { + if(keyPrefix.size() != 0) + { + keyPrefix += "."; + } + + key = keyPrefix + line.substr(0, position); + + key = TrimEnds(key); + + + value = line.substr(position + delimiter.length(), line.length()); + value = TrimEnds(value); + + + Properties.insert(PropertiesPair(key, value)); + + std::stringstream ss; + ss << "Dump: " << "\"" << key << "\" = \"" << value << "\""; + + + Logger::Write(Logger::ZONE_INFO, "Configuration", ss.str()); + retVal = true; + } + else + { + std::stringstream ss; + ss << "Missing an assignment operator (=) on line " << lineCount; + Logger::Write(Logger::ZONE_ERROR, "Configuration", ss.str()); + } + + return retVal; +} + +std::string Configuration::TrimEnds(std::string str) +{ + // strip off any initial tabs or spaces + size_t trimStart = str.find_first_not_of(" \t"); + + if(trimStart != std::string::npos) + { + size_t trimEnd = str.find_last_not_of(" \t"); + + str = str.substr(trimStart, trimEnd - trimStart + 1); + } + + return str; +} + +bool Configuration::GetProperty(std::string key, std::string &value) +{ + bool retVal = false; + if(Properties.find(key) != Properties.end()) + { + value = Properties[key]; + + retVal = true; + } + else if(Verbose) + { + Logger::Write(Logger::ZONE_DEBUG, "Configuration", "Missing property " + key); + } + + + return retVal; +} + +bool Configuration::GetProperty(std::string key, int &value) +{ + std::string strValue; + + bool retVal = GetProperty(key, strValue); + + if(retVal) + { + std::stringstream ss; + ss << strValue; + ss >> value; + } + + return retVal; +} + +bool Configuration::GetProperty(std::string key, bool &value) +{ + std::string strValue; + + bool retVal = GetProperty(key, strValue); + + if(retVal) + { + std::stringstream ss; + ss << strValue; + + for(unsigned int i=0; i < strValue.length(); ++i) + { + std::locale loc; + strValue[i] = std::tolower(strValue[i], loc); + } + + if(!strValue.compare("yes") || !strValue.compare("true")) + { + value = true; + } + else + { + value = false; + } + } + + return retVal; +} + +void Configuration::SetProperty(std::string key, std::string value) +{ + Properties[key] = value; +} + +bool Configuration::PropertyExists(std::string key) +{ + return (Properties.find(key) != Properties.end()); +} + +bool Configuration::PropertyPrefixExists(std::string key) +{ + PropertiesType::iterator it; + + for(it = Properties.begin(); it != Properties.end(); ++it) + { + std::string search = key + "."; + if(it->first.compare(0, search.length(), search) == 0) + { + return true; + } + } + + return false; +} + +void Configuration::GetChildKeyCrumbs(std::string parent, std::vector &children) +{ + PropertiesType::iterator it; + + for(it = Properties.begin(); it != Properties.end(); ++it) + { + std::string search = parent + "."; + if(it->first.compare(0, search.length(), search) == 0) + { + std::string crumb = Utils::Replace(it->first, search, ""); + + std::size_t end = crumb.find_first_of("."); + + if(end != std::string::npos) + { + crumb = crumb.substr(0, end); + } + + if(std::find(children.begin(), children.end(), crumb) == children.end()) + { + children.push_back(crumb); + } + } + } +} + +std::string Configuration::ConvertToAbsolutePath(std::string prefix, std::string path) +{ + char first = ' '; + char second = ' '; + + if(path.length() >= 0) + { + first = path.c_str()[0]; + } + if(path.length() >= 1) + { + second = path.c_str()[1]; + } + + // check to see if it is already an absolute path + if((first != '/') && + (first != '\\') && + //(first != '.') && + (second != ':')) + { + path = prefix + "/" + path; + } + + return path; +} + +bool Configuration::GetPropertyAbsolutePath(std::string key, std::string &value) +{ + bool retVal = GetProperty(key, value); + + if(retVal) + { + value = ConvertToAbsolutePath(GetAbsolutePath(), value); + } + + return retVal; +} + +void Configuration::SetAbsolutePath(std::string absolutePath) +{ + AbsolutePath = absolutePath; +} + +std::string Configuration::GetAbsolutePath() +{ + return AbsolutePath; +} + +bool Configuration::IsVerbose() const +{ + return Verbose; +} + +void Configuration::SetVerbose(bool verbose) +{ + this->Verbose = verbose; +} + + diff --git a/RetroFE/Source/Database/Configuration.h b/RetroFE/Source/Database/Configuration.h new file mode 100644 index 0000000..2918ed5 --- /dev/null +++ b/RetroFE/Source/Database/Configuration.h @@ -0,0 +1,44 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include +#include +#include + +class Configuration +{ +public: + Configuration(); + virtual ~Configuration(); + static void Initialize(); + static void SetAbsolutePath(std::string absolutePath); + static std::string GetAbsolutePath(); + static std::string ConvertToAbsolutePath(std::string prefix, std::string path); + + // gets the global configuration + bool Import(std::string keyPrefix, std::string file); + + bool GetProperty(std::string key, std::string &value); + bool GetProperty(std::string key, int &value); + bool GetProperty(std::string key, bool &value); + void GetChildKeyCrumbs(std::string parent, std::vector &children); + void SetProperty(std::string key, std::string value); + bool PropertyExists(std::string key); + bool PropertyPrefixExists(std::string key); + bool GetPropertyAbsolutePath(std::string key, std::string &value); + bool IsVerbose() const; + void SetVerbose(bool verbose); + bool IsRequiredPropertiesSet(); + +private: + bool ParseLine(std::string keyPrefix, std::string line, int lineCount); + std::string TrimEnds(std::string str); + typedef std::map PropertiesType; + typedef std::pair PropertiesPair; + bool Verbose; + + static std::string AbsolutePath; + PropertiesType Properties; +}; diff --git a/RetroFE/Source/Database/DB.cpp b/RetroFE/Source/Database/DB.cpp new file mode 100644 index 0000000..48e4935 --- /dev/null +++ b/RetroFE/Source/Database/DB.cpp @@ -0,0 +1,61 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "DB.h" +#include "../Utility/Log.h" + +#include +#include + +DB::DB(std::string dbFile) + : Path(dbFile) + , Handle(NULL) +{ +} + +DB::~DB() +{ + DeInitialize(); +} + +bool DB::Initialize() +{ + bool retVal = false; + + if(sqlite3_open(Path.c_str(), &Handle) != 0) + { + std::stringstream ss; + ss << "Cannot open database: \"" << Path << "\"" << sqlite3_errmsg(Handle); + Logger::Write(Logger::ZONE_ERROR, "Database", ss.str()); + } + else + { + Logger::Write(Logger::ZONE_INFO, "Database", "Opened database \"" + Path + "\""); + retVal = true; + } + + return retVal; +} + + +void DB::DeInitialize() +{ + if(Handle != NULL) + { + sqlite3_close(Handle); + Handle = NULL; + } +} + diff --git a/Source/Database/DB.h b/RetroFE/Source/Database/DB.h similarity index 52% rename from Source/Database/DB.h rename to RetroFE/Source/Database/DB.h index f214b85..7db60d1 100644 --- a/Source/Database/DB.h +++ b/RetroFE/Source/Database/DB.h @@ -1,5 +1,5 @@ -/* This file is subject to the terms and conditions defined in - * file 'LICENSE.txt', which is part of this source code package. +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. */ #pragma once @@ -8,14 +8,17 @@ class DB { public: - DB(); - bool Initialize(); - void DeInitialize(); - virtual ~DB(); - sqlite3 *GetHandle() { return Handle; } + DB(std::string dbFile); + bool Initialize(); + void DeInitialize(); + virtual ~DB(); + sqlite3 *GetHandle() + { + return Handle; + } private: - sqlite3 *Handle; - std::string Path; + sqlite3 *Handle; + std::string Path; }; diff --git a/RetroFE/Source/Database/MamelistMetadata.cpp b/RetroFE/Source/Database/MamelistMetadata.cpp new file mode 100644 index 0000000..9dd7c03 --- /dev/null +++ b/RetroFE/Source/Database/MamelistMetadata.cpp @@ -0,0 +1,132 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "MamelistMetadata.h" +#include "DB.h" +#include "../Utility/Log.h" +#include "Metadata.h" +#include +#include +#include +#include +#include + + +MamelistMetadata::MamelistMetadata(DB &dbInstance) + : DBInstance(dbInstance) +{ +} + +MamelistMetadata::~MamelistMetadata() +{ +} + +bool MamelistMetadata::Import(std::string filename, std::string collection) +{ + bool retVal = true; + rapidxml::xml_document<> doc; + rapidxml::xml_node<> * rootNode; + char *error = NULL; + sqlite3 *handle = DBInstance.GetHandle(); + + std::ifstream f(filename.c_str()); + + if (!f.good()) + { + Logger::Write(Logger::ZONE_ERROR, "Mamelist", "Could not find mamelist metadata file at \"" + filename + "\""); + + retVal = false; + } + + f.close(); + + if(retVal) + { + Logger::Write(Logger::ZONE_INFO, "Mamelist", "Importing mamelist file \"" + filename + "\""); + std::ifstream file(filename.c_str()); + std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + buffer.push_back('\0'); + + doc.parse<0>(&buffer[0]); + + rootNode = doc.first_node("mame"); + + sqlite3_exec(handle, "BEGIN IMMEDIATE TRANSACTION;", NULL, NULL, &error); + for (rapidxml::xml_node<> * game = rootNode->first_node("game"); game; game = game->next_sibling()) + { + rapidxml::xml_attribute<> *nameNode = game->first_attribute("name"); + rapidxml::xml_attribute<> *cloneOfXml = game->first_attribute("cloneof"); + + if(nameNode != NULL) + { + std::string name = nameNode->value(); + rapidxml::xml_node<> *descriptionNode = game->first_node("description"); + rapidxml::xml_node<> *yearNode = game->first_node("year"); + rapidxml::xml_node<> *manufacturerNode = game->first_node("manufacturer"); + rapidxml::xml_node<> *inputNode = game->first_node("input"); + + std::string description = (descriptionNode == NULL) ? nameNode->value() : descriptionNode->value(); + std::string year = (yearNode == NULL) ? "" : yearNode->value(); + std::string manufacturer = (manufacturerNode == NULL) ? "" : manufacturerNode->value(); + std::string cloneOf = (cloneOfXml == NULL) ? "" : cloneOfXml->value(); + std::string players; + std::string buttons; + + if(inputNode != NULL) + { + rapidxml::xml_attribute<> *playersAttribute = inputNode->first_attribute("players"); + rapidxml::xml_attribute<> *buttonsAttribute = inputNode->first_attribute("buttons"); + + if(playersAttribute) + { + players = playersAttribute->value(); + } + + if(buttonsAttribute) + { + buttons = buttonsAttribute->value(); + } + + } + + sqlite3_stmt *stmt; + sqlite3_prepare_v2(handle, + "UPDATE OR REPLACE Meta SET title=?, year=?, manufacturer=?, players=?, buttons=?, cloneOf=? WHERE name=? AND collectionName=?;", + -1, &stmt, 0); + + sqlite3_bind_text(stmt, 1, description.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, year.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, manufacturer.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 4, players.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 5, buttons.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 6, cloneOf.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 7, name.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 8, collection.c_str(), -1, SQLITE_TRANSIENT); + + sqlite3_step(stmt); + sqlite3_finalize(stmt); + + } + } + sqlite3_exec(handle, "COMMIT TRANSACTION;", NULL, NULL, &error); + + } + + + + return retVal; +} diff --git a/Source/Database/MamelistMetadata.h b/RetroFE/Source/Database/MamelistMetadata.h similarity index 60% rename from Source/Database/MamelistMetadata.h rename to RetroFE/Source/Database/MamelistMetadata.h index 4cb0b66..cb328fb 100644 --- a/Source/Database/MamelistMetadata.h +++ b/RetroFE/Source/Database/MamelistMetadata.h @@ -1,5 +1,5 @@ -/* This file is subject to the terms and conditions defined in - * file 'LICENSE.txt', which is part of this source code package. +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. */ #pragma once @@ -10,9 +10,9 @@ class DB; class MamelistMetadata : Metadata { public: - MamelistMetadata(DB *dbInstance); - virtual ~MamelistMetadata(); - bool Import(std::string file, std::string collectionName); + MamelistMetadata(DB &dbInstance); + virtual ~MamelistMetadata(); + bool Import(std::string file, std::string collectionName); private: - DB *DBInstance; + DB &DBInstance; }; diff --git a/RetroFE/Source/Database/Metadata.h b/RetroFE/Source/Database/Metadata.h new file mode 100644 index 0000000..3ab03c1 --- /dev/null +++ b/RetroFE/Source/Database/Metadata.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class Metadata +{ +public: + virtual ~Metadata() {} + virtual bool Import(std::string file, std::string collectionName) = 0; +}; diff --git a/RetroFE/Source/Execute/AttractMode.cpp b/RetroFE/Source/Execute/AttractMode.cpp new file mode 100644 index 0000000..7f5d169 --- /dev/null +++ b/RetroFE/Source/Execute/AttractMode.cpp @@ -0,0 +1,61 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "AttractMode.h" +#include "../Graphics/Page.h" + +AttractMode::AttractMode() + : IsActive(false) + , ElapsedTime(0) + , ActiveTime(0) + , IdleTime(0) +{ +} + +void AttractMode::SetIdleTime(float time) +{ + IdleTime = time; +} +void AttractMode::Reset() +{ + ElapsedTime = 0; + IsActive = false; + ActiveTime = 0; +} + +void AttractMode::Update(float dt, Page &page) +{ + ElapsedTime += dt; + + // enable attract mode when idling for the expected time. Disable if idle time is set to 0. + if(!IsActive && ElapsedTime > IdleTime && IdleTime > 0) + { + IsActive = true; + ElapsedTime = 0; + ActiveTime = ((float)((1000+rand()) % 5000)) / 1000; + } + + if(IsActive) + { + page.SetScrolling(Page::ScrollDirectionForward); + + if(ElapsedTime > ActiveTime) + { + ElapsedTime = 0; + IsActive = false; + page.SetScrolling(Page::ScrollDirectionIdle); + } + } +} \ No newline at end of file diff --git a/RetroFE/Source/Execute/AttractMode.h b/RetroFE/Source/Execute/AttractMode.h new file mode 100644 index 0000000..5ed1465 --- /dev/null +++ b/RetroFE/Source/Execute/AttractMode.h @@ -0,0 +1,22 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +class Page; + +class AttractMode +{ +public: + AttractMode(); + void SetIdleTime(float time); + void Reset(); + void Update(float dt, Page &page); + +private: + bool IsActive; + float ElapsedTime; + float ActiveTime; + float IdleTime; + +}; diff --git a/RetroFE/Source/Execute/Launcher.cpp b/RetroFE/Source/Execute/Launcher.cpp new file mode 100644 index 0000000..2e42141 --- /dev/null +++ b/RetroFE/Source/Execute/Launcher.cpp @@ -0,0 +1,339 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "Launcher.h" +#include "../Collection/Item.h" +#include "../Utility/Log.h" +#include "../Database/Configuration.h" +#include "../Utility/Utils.h" +#include "../RetroFE.h" +#include "../SDL.h" +#include +#include +#include +#include +#ifdef WIN32 +#include +#include +#endif + +Launcher::Launcher(RetroFE &p, Configuration &c) + : Config(c) + , RetroFEInst(p) +{ +} + +bool Launcher::Run(std::string collection, Item *collectionItem) +{ + std::string launcherName = collectionItem->GetLauncher(); + std::string executablePath; + std::string selectedItemsDirectory; + std::string selectedItemsPath; + std::string currentDirectory; + std::string extensions; + std::string matchedExtension; + std::string args; + + if(!GetLauncherExecutable(executablePath, currentDirectory, launcherName)) + { + Logger::Write(Logger::ZONE_ERROR, "Launcher", "Failed to find launcher executable (launcher: " + launcherName + " executable: " + executablePath + ")"); + return false; + } + if(!GetExtensions(extensions, collection)) + { + Logger::Write(Logger::ZONE_ERROR, "Launcher", "No file extensions configured for collection \"" + collection + "\""); + return false; + } + if(!GetCollectionDirectory(selectedItemsDirectory, collection)) + { + Logger::Write(Logger::ZONE_ERROR, "Launcher", "Could not find files in directory \"" + selectedItemsDirectory + "\" for collection \"" + collection + "\""); + return false; + } + if(!GetLauncherArgs(args, launcherName)) + { + Logger::Write(Logger::ZONE_ERROR, "Launcher", "No launcher arguments specified for launcher " + launcherName); + return false; + } + if(!FindFile(selectedItemsPath, matchedExtension, selectedItemsDirectory, collectionItem->GetName(), extensions)) + { + // FindFile() prints out diagnostic messages for us, no need to print anything here + return false; + } + args = ReplaceVariables(args, + selectedItemsPath, + collectionItem->GetName(), + collectionItem->GetFileName(), + selectedItemsDirectory, + collection); + + executablePath = ReplaceVariables(executablePath, + selectedItemsPath, + collectionItem->GetName(), + collectionItem->GetFileName(), + selectedItemsDirectory, + collection); + + currentDirectory = ReplaceVariables(currentDirectory, + selectedItemsPath, + collectionItem->GetName(), + collectionItem->GetFileName(), + selectedItemsDirectory, + collection); + + if(!ExecuteCommand(executablePath, args, currentDirectory)) + { + Logger::Write(Logger::ZONE_ERROR, "Launcher", "Failed to launch."); + return false; + } + + return true; +} + +std::string Launcher::ReplaceVariables(std::string str, + std::string itemFilePath, + std::string itemName, + std::string itemFilename, + std::string itemDirectory, + std::string itemCollectionName) +{ + str = Utils::Replace(str, "%ITEM_FILEPATH%", itemFilePath); + str = Utils::Replace(str, "%ITEM_NAME%", itemName); + str = Utils::Replace(str, "%ITEM_FILENAME%", itemFilename); + str = Utils::Replace(str, "%ITEM_DIRECTORY%", itemDirectory); + str = Utils::Replace(str, "%ITEM_COLLECTION_NAME%", itemCollectionName); + str = Utils::Replace(str, "%RETROFE_PATH%", Configuration::GetAbsolutePath()); +#ifdef WIN32 + str = Utils::Replace(str, "%RETROFE_EXEC_PATH%", Configuration::GetAbsolutePath() + "/RetroFE.exe"); +#else + str = Utils::Replace(str, "%RETROFE_EXEC_PATH%", Configuration::GetAbsolutePath() + "/RetroFE"); +#endif + + return str; +} + +bool Launcher::ExecuteCommand(std::string executable, std::string args, std::string currentDirectory) +{ + bool retVal = false; + std::string executionString = "\"" + executable + "\" " + args; + + Logger::Write(Logger::ZONE_INFO, "Launcher", "Attempting to launch: " + executionString); + Logger::Write(Logger::ZONE_INFO, "Launcher", " from within folder: " + currentDirectory); + + //todo: use delegation instead of depending on knowing the RetroFE class (tie to an interface) + RetroFEInst.LaunchEnter(); + +#ifdef WIN32 + STARTUPINFO startupInfo; + PROCESS_INFORMATION processInfo; + char applicationName[256]; + char currDir[256]; + memset(&applicationName, 0, sizeof(applicationName)); + memset(&startupInfo, 0, sizeof(startupInfo)); + memset(&processInfo, 0, sizeof(processInfo)); + strncpy(applicationName, executionString.c_str(), sizeof(applicationName)); + strncpy(currDir, currentDirectory.c_str(), sizeof(currDir)); + startupInfo.dwFlags = STARTF_USESTDHANDLES; + startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + startupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startupInfo.wShowWindow = SW_SHOWDEFAULT; + + if(!CreateProcess(NULL, applicationName, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &processInfo)) +#else + if(system(executionString.c_str()) != 0) +#endif + { + Logger::Write(Logger::ZONE_ERROR, "Launcher", "Failed to run: " + executable); + } + + else + { +#ifdef WIN32 + while(WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1, &processInfo.hProcess, FALSE, INFINITE, QS_ALLINPUT)) + { + MSG msg; + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + DispatchMessage(&msg); + } + } + + // result = GetExitCodeProcess(processInfo.hProcess, &exitCode); + CloseHandle(processInfo.hProcess); +#endif + retVal = true; + } + + Logger::Write(Logger::ZONE_INFO, "Launcher", "Completed"); + RetroFEInst.LaunchExit(); + + return retVal; +} + +bool Launcher::GetLauncherName(std::string &launcherName, std::string collection) +{ + std::string launcherKey = "collections." + collection + ".launcher"; + + // find the launcher for the particular item + if(!Config.GetProperty(launcherKey, launcherName)) + { + std::stringstream ss; + + ss << "Launch failed. Could not find a configured launcher for collection \"" + << collection + << "\" (could not find a property for \"" + << launcherKey + << "\")"; + + Logger::Write(Logger::ZONE_ERROR, "Launcher", ss.str()); + + return false; + } + + std::stringstream ss; + ss << "collections." + << collection + << " is configured to use launchers." + << launcherName + << "\""; + + Logger::Write(Logger::ZONE_DEBUG, "Launcher", ss.str()); + + return true; +} + + + +bool Launcher::GetLauncherExecutable(std::string &executable, std::string ¤tDirectory, std::string launcherName) +{ + std::string executableKey = "launchers." + launcherName + ".executable"; + + if(!Config.GetProperty(executableKey, executable)) + { + return false; + } + + std::string currentDirectoryKey = "launchers." + launcherName + ".currentDirectory"; + currentDirectory = Utils::GetDirectory(executable); + + Config.GetProperty(currentDirectoryKey, currentDirectory); + + return true; +} + +bool Launcher::GetLauncherArgs(std::string &args, std::string launcherName) +{ + std::string argsKey = "launchers." + launcherName + ".arguments"; + + if(!Config.GetProperty(argsKey, args)) + { + Logger::Write(Logger::ZONE_ERROR, "Launcher", "No arguments specified for: " + argsKey); + + return false; + } + return true; +} + +bool Launcher::GetExtensions(std::string &extensions, std::string collection) +{ + std::string extensionsKey = "collections." + collection + ".list.extensions"; + + if(!Config.GetProperty(extensionsKey, extensions)) + { + Logger::Write(Logger::ZONE_ERROR, "Launcher", "No extensions specified for: " + extensionsKey); + return false; + } + + extensions = Utils::Replace(extensions, " ", ""); + extensions = Utils::Replace(extensions, ".", ""); + + return true; +} + +bool Launcher::GetCollectionDirectory(std::string &directory, std::string collection) +{ + std::string itemsPathKey = "collections." + collection + ".list.path"; + std::string itemsPathValue; + + // find the items path folder (i.e. ROM path) + if(!Config.GetPropertyAbsolutePath(itemsPathKey, itemsPathValue)) + { + directory = ""; + } + else + { + directory += itemsPathValue + "/"; + } + + return true; +} + +bool Launcher::FindFile(std::string &foundFilePath, std::string &foundFilename, std::string directory, std::string filenameWithoutExtension, std::string extensions) +{ + std::string extension; + bool fileFound = false; + std::stringstream ss; + ss << extensions; + + while(!fileFound && std::getline(ss, extension, ',') ) + { + std::string selectedItemsPath = directory + filenameWithoutExtension + "." + extension; + std::ifstream f(selectedItemsPath.c_str()); + + if (f.good()) + { + std::stringstream ss; + + ss <<"Checking to see if \"" + << selectedItemsPath << "\" exists [Yes]"; + + fileFound = true; + + Logger::Write(Logger::ZONE_INFO, "Launcher", ss.str()); + + foundFilePath = selectedItemsPath; + foundFilename = extension; + } + else + { + std::stringstream ss; + + ss << "Checking to see if \"" + << selectedItemsPath << "\" exists [No]"; + + Logger::Write(Logger::ZONE_WARNING, "Launcher", ss.str()); + } + + f.close(); + } + + // get the launchers executable + + if(!fileFound) + { + std::stringstream ss; + ss <<"Could not find any files with the name \"" + << filenameWithoutExtension << "\" in folder \"" + << directory; + + Logger::Write(Logger::ZONE_ERROR, "Launcher", ss.str()); + + } + + return fileFound; +} + + diff --git a/RetroFE/Source/Execute/Launcher.h b/RetroFE/Source/Execute/Launcher.h new file mode 100644 index 0000000..a7ae4b0 --- /dev/null +++ b/RetroFE/Source/Execute/Launcher.h @@ -0,0 +1,40 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include + +class Configuration; +class Item; +class RetroFE; + +class Launcher +{ +public: + Launcher(RetroFE &p, Configuration &c); + bool Run(std::string collection, Item *collectionItem); + +private: + std::string ReplaceString( + std::string subject, + const std::string &search, + const std::string &replace); + + bool GetLauncherName(std::string &launcherName, std::string collection); + bool GetLauncherExecutable(std::string &executable, std::string ¤tDirectory, std::string launcherName); + bool GetLauncherArgs(std::string &args, std::string launcherName); + bool GetExtensions(std::string &extensions, std::string launcherName); + bool GetCollectionDirectory(std::string &directory, std::string collection); + bool ExecuteCommand(std::string executable, std::string arguments, std::string currentDirectory); + bool FindFile(std::string &foundFilePath, std::string &foundFilename, std::string directory, std::string filenameWithoutExtension, std::string extensions); + std::string ReplaceVariables(std::string str, + std::string itemFilePath, + std::string itemName, + std::string itemFilename, + std::string itemDirectory, + std::string itemCollectionName); + + Configuration &Config; + RetroFE &RetroFEInst; +}; diff --git a/RetroFE/Source/Graphics/Animate/Tween.cpp b/RetroFE/Source/Graphics/Animate/Tween.cpp new file mode 100644 index 0000000..0cff078 --- /dev/null +++ b/RetroFE/Source/Graphics/Animate/Tween.cpp @@ -0,0 +1,383 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "Tween.h" +#include +#define _USE_MATH_DEFINES +#include +#include + +std::map Tween::TweenTypeMap; +std::map Tween::TweenPropertyMap; + +Tween::Tween(TweenProperty property, TweenAlgorithm type, double start, double end, double duration) + : Property(property) + , Type(type) + , Start(start) + , End(end) + , Duration(duration) +{ +} + +TweenProperty Tween::GetProperty() const +{ + return Property; +} + + +bool Tween::GetTweenProperty(std::string name, TweenProperty &property) +{ + bool retVal = false; + + if(TweenPropertyMap.size() == 0) + { + TweenPropertyMap["x"] = TWEEN_PROPERTY_X; + TweenPropertyMap["y"] = TWEEN_PROPERTY_Y; + TweenPropertyMap["angle"] = TWEEN_PROPERTY_ANGLE; + TweenPropertyMap["alpha"] = TWEEN_PROPERTY_ALPHA; + TweenPropertyMap["width"] = TWEEN_PROPERTY_WIDTH; + TweenPropertyMap["height"] = TWEEN_PROPERTY_HEIGHT; + TweenPropertyMap["xorigin"] = TWEEN_PROPERTY_X_ORIGIN; + TweenPropertyMap["yorigin"] = TWEEN_PROPERTY_Y_ORIGIN; + TweenPropertyMap["xoffset"] = TWEEN_PROPERTY_X_OFFSET; + TweenPropertyMap["yoffset"] = TWEEN_PROPERTY_Y_OFFSET; + TweenPropertyMap["fontSize"] = TWEEN_PROPERTY_FONT_SIZE; + } + + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + + if(TweenPropertyMap.find(name) != TweenPropertyMap.end()) + { + property = TweenPropertyMap[name]; + retVal = true; + } + + return retVal; +} + + +TweenAlgorithm Tween::GetTweenType(std::string name) +{ + if(TweenTypeMap.size() == 0) + { + TweenTypeMap["easeinquadratic"] = EASE_IN_QUADRATIC; + TweenTypeMap["easeoutquadratic"] = EASE_OUT_QUADRATIC; + TweenTypeMap["easeinoutquadratic"] = EASE_INOUT_QUADRATIC; + TweenTypeMap["easeincubic"] = EASE_IN_CUBIC; + TweenTypeMap["easeoutcubic"] = EASE_OUT_CUBIC; + TweenTypeMap["easeinoutcubic"] = EASE_INOUT_CUBIC; + TweenTypeMap["easeinquartic"] = EASE_IN_QUARTIC; + TweenTypeMap["easeoutquartic"] = EASE_OUT_QUARTIC; + TweenTypeMap["easeinoutquartic"] = EASE_INOUT_QUARTIC; + TweenTypeMap["easeinquintic"] = EASE_IN_QUINTIC; + TweenTypeMap["easeoutquintic"] = EASE_OUT_QUINTIC; + TweenTypeMap["easeinoutquintic"] = EASE_INOUT_QUINTIC; + TweenTypeMap["easeinsine"] = EASE_IN_SINE; + TweenTypeMap["easeoutsine"] = EASE_OUT_SINE; + TweenTypeMap["easeinoutsine"] = EASE_INOUT_SINE; + TweenTypeMap["easeinexponential"] = EASE_IN_EXPONENTIAL; + TweenTypeMap["easeoutexponential"] = EASE_OUT_EXPONENTIAL; + TweenTypeMap["easeinoutexponential"] = EASE_INOUT_EXPONENTIAL; + TweenTypeMap["easeincircular"] = EASE_IN_CIRCULAR; + TweenTypeMap["easeoutcircular"] = EASE_OUT_CIRCULAR; + TweenTypeMap["easeinoutcircular"] = EASE_INOUT_CIRCULAR; + TweenTypeMap["linear"] = LINEAR; + } + + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + + if(TweenTypeMap.find(name) != TweenTypeMap.end()) + { + return TweenTypeMap[name]; + } + else + { + return TweenTypeMap["linear"]; + } +} + + +float Tween::Animate(double elapsedTime) +{ + return AnimateSingle(Type, Start, End, Duration, elapsedTime); +} + +//todo: SDL likes floats, consider having casting being performed elsewhere +float Tween::AnimateSingle(TweenAlgorithm type, double start, double end, double duration, double elapsedTime) +{ + double a = start; + double b = end - start; + double result = 0; + + switch(type) + { + case EASE_IN_QUADRATIC: + result = EaseInQuadratic(elapsedTime, duration, a, b); + break; + + case EASE_OUT_QUADRATIC: + result = EaseOutQuadratic(elapsedTime, duration, a, b); + break; + + case EASE_INOUT_QUADRATIC: + result = EaseInOutQuadratic(elapsedTime, duration, a, b); + break; + + case EASE_IN_CUBIC: + result = EaseInCubic(elapsedTime, duration, a, b); + break; + + case EASE_OUT_CUBIC: + result = EaseOutCubic(elapsedTime, duration, a, b); + break; + + case EASE_INOUT_CUBIC: + result = EaseInOutCubic(elapsedTime, duration, a, b); + break; + + case EASE_IN_QUARTIC: + result = EaseInQuartic(elapsedTime, duration, a, b); + break; + + case EASE_OUT_QUARTIC: + result = EaseOutQuartic(elapsedTime, duration, a, b); + break; + + case EASE_INOUT_QUARTIC: + result = EaseInOutQuartic(elapsedTime, duration, a, b); + break; + + case EASE_IN_QUINTIC: + result = EaseInQuintic(elapsedTime, duration, a, b); + break; + + case EASE_OUT_QUINTIC: + result = EaseOutQuintic(elapsedTime, duration, a, b); + break; + + case EASE_INOUT_QUINTIC: + result = EaseInOutQuintic(elapsedTime, duration, a, b); + break; + + case EASE_IN_SINE: + result = EaseInSine(elapsedTime, duration, a, b); + break; + + case EASE_OUT_SINE: + result = EaseOutSine(elapsedTime, duration, a, b); + break; + + case EASE_INOUT_SINE: + result = EaseInOutSine(elapsedTime, duration, a, b); + break; + + case EASE_IN_EXPONENTIAL: + result = EaseInExponential(elapsedTime, duration, a, b); + break; + + case EASE_OUT_EXPONENTIAL: + result = EaseOutExponential(elapsedTime, duration, a, b); + break; + + case EASE_INOUT_EXPONENTIAL: + result = EaseInOutExponential(elapsedTime, duration, a, b); + break; + + case EASE_IN_CIRCULAR: + result = EaseInCircular(elapsedTime, duration, a, b); + break; + + case EASE_OUT_CIRCULAR: + result = EaseOutCircular(elapsedTime, duration, a, b); + break; + + case EASE_INOUT_CIRCULAR: + result = EaseInOutCircular(elapsedTime, duration, a, b); + break; + + case LINEAR: + default: + result = Linear(elapsedTime, duration, a, b); + break; + } + + return static_cast(result); + +} + +double Tween::Linear(double t, double d, double b, double c) +{ + if(d == 0) return b; + return c*t/d + b; +}; + +double Tween::EaseInQuadratic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d; + return c*t*t + b; +}; + +double Tween::EaseOutQuadratic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d; + return -c * t*(t-2) + b; +}; + +double Tween::EaseInOutQuadratic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d/2; + if (t < 1) return c/2*t*t + b; + t--; + return -c/2 * (t*(t-2) - 1) + b; +}; + +double Tween::EaseInCubic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d; + return c*t*t*t + b; +}; + +double Tween::EaseOutCubic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d; + t--; + return c*(t*t*t + 1) + b; +}; + +double Tween::EaseInOutCubic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d/2; + if (t < 1) return c/2*t*t*t + b; + t -= 2; + return c/2*(t*t*t + 2) + b; +}; + +double Tween::EaseInQuartic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d; + return c*t*t*t*t + b; +}; + +double Tween::EaseOutQuartic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d; + t--; + return -c * (t*t*t*t - 1) + b; +}; + +double Tween::EaseInOutQuartic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d/2; + if (t < 1) return c/2*t*t*t*t + b; + t -= 2; + return -c/2 * (t*t*t*t - 2) + b; +}; + +double Tween::EaseInQuintic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d; + return c*t*t*t*t*t + b; +}; + + +double Tween::EaseOutQuintic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d; + t--; + return c*(t*t*t*t*t + 1) + b; +}; + +double Tween::EaseInOutQuintic(double t, double d, double b, double c) +{ + if(d == 0) return b; + t /= d/2; + if (t < 1) return c/2*t*t*t*t*t + b; + t -= 2; + return c/2*(t*t*t*t*t + 2) + b; +}; + +double Tween::EaseInSine(double t, double d, double b, double c) +{ + return -c * cos(t/d * (M_PI/2)) + c + b; +}; + +double Tween::EaseOutSine(double t, double d, double b, double c) +{ + return c * sin(t/d * (M_PI/2)) + b; +}; + +double Tween::EaseInOutSine(double t, double d, double b, double c) +{ + return -c/2 * (cos( M_PI*t/d) - 1) + b; +}; + +double Tween::EaseInExponential(double t, double d, double b, double c) +{ + return c * pow( 2, 10 * (t/d - 1) ) + b; +}; + +double Tween::EaseOutExponential(double t, double d, double b, double c) +{ + return c * ( - pow( 2, -10 * t/d ) + 1 ) + b; +}; + +double Tween::EaseInOutExponential(double t, double d, double b, double c) +{ + t /= d/2; + if (t < 1) return c/2 * pow( 2, 10 * (t - 1) ) + b; + t--; + return c/2 * ( -1* pow( 2, -10 * t) + 2 ) + b; +}; + +double Tween::EaseInCircular(double t, double d, double b, double c) +{ + t /= d; + return -c * (sqrt(1 - t*t) - 1) + b; +}; + + +double Tween::EaseOutCircular(double t, double d, double b, double c) +{ + t /= d; + t--; + return c * sqrt(1 - t*t) + b; +}; + +double Tween::EaseInOutCircular(double t, double d, double b, double c) +{ + t /= d/2; + if (t < 1) return -c/2 * (sqrt(1 - t*t) - 1) + b; + t -= 2; + return c/2 * (sqrt(1 - t*t) + 1) + b; +} +; + +//todo: sdl requires floats, should the casting be done at this layer? +float Tween::GetDuration() const +{ + return static_cast(Duration); +} diff --git a/RetroFE/Source/Graphics/Animate/Tween.h b/RetroFE/Source/Graphics/Animate/Tween.h new file mode 100644 index 0000000..9afecec --- /dev/null +++ b/RetroFE/Source/Graphics/Animate/Tween.h @@ -0,0 +1,55 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include "TweenTypes.h" +#include +#include + +class ViewInfo; + +class Tween +{ +public: + + Tween(TweenProperty name, TweenAlgorithm type, double start, double end, double duration); + float Animate(double elapsedTime); + static float AnimateSingle(TweenAlgorithm type, double start, double end, double duration, double elapsedTime); + static TweenAlgorithm GetTweenType(std::string name); + static bool GetTweenProperty(std::string name, TweenProperty &property); + TweenProperty GetProperty() const; + float GetDuration() const; + +private: + static double EaseInQuadratic(double elapsedTime, double duration, double b, double c); + static double EaseOutQuadratic(double elapsedTime, double duration, double b, double c); + static double EaseInOutQuadratic(double elapsedTime, double duration, double b, double c); + static double EaseInCubic(double elapsedTime, double duration, double b, double c); + static double EaseOutCubic(double elapsedTime, double duration, double b, double c); + static double EaseInOutCubic(double elapsedTime, double duration, double b, double c); + static double EaseInQuartic(double elapsedTime, double duration, double b, double c); + static double EaseOutQuartic(double elapsedTime, double duration, double b, double c); + static double EaseInOutQuartic(double elapsedTime, double duration, double b, double c); + static double EaseInQuintic(double elapsedTime, double duration, double b, double c); + static double EaseOutQuintic(double elapsedTime, double duration, double b, double c); + static double EaseInOutQuintic(double elapsedTime, double duration, double b, double c); + static double EaseInSine(double elapsedTime, double duration, double b, double c); + static double EaseOutSine(double elapsedTime, double duration, double b, double c); + static double EaseInOutSine(double elapsedTime, double duration, double b, double c); + static double EaseInExponential(double elapsedTime, double duration, double b, double c); + static double EaseOutExponential(double elapsedTime, double duration, double b, double c); + static double EaseInOutExponential(double elapsedTime, double duration, double b, double c); + static double EaseInCircular(double elapsedTime, double duration, double b, double c); + static double EaseOutCircular(double elapsedTime, double duration, double b, double c); + static double EaseInOutCircular(double elapsedTime, double duration, double b, double c); + static double Linear(double elapsedTime, double duration, double b, double c); + + static std::map TweenTypeMap; + static std::map TweenPropertyMap; + TweenProperty Property; + TweenAlgorithm Type; + double Start; + double End; + double Duration; +}; diff --git a/RetroFE/Source/Graphics/Animate/TweenTypes.h b/RetroFE/Source/Graphics/Animate/TweenTypes.h new file mode 100644 index 0000000..074543a --- /dev/null +++ b/RetroFE/Source/Graphics/Animate/TweenTypes.h @@ -0,0 +1,45 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +enum TweenAlgorithm +{ + LINEAR, + EASE_IN_QUADRATIC, + EASE_OUT_QUADRATIC, + EASE_INOUT_QUADRATIC, + EASE_IN_CUBIC, + EASE_OUT_CUBIC, + EASE_INOUT_CUBIC, + EASE_IN_QUARTIC, + EASE_OUT_QUARTIC, + EASE_INOUT_QUARTIC, + EASE_IN_QUINTIC, + EASE_OUT_QUINTIC, + EASE_INOUT_QUINTIC, + EASE_IN_SINE, + EASE_OUT_SINE, + EASE_INOUT_SINE, + EASE_IN_EXPONENTIAL, + EASE_OUT_EXPONENTIAL, + EASE_INOUT_EXPONENTIAL, + EASE_IN_CIRCULAR, + EASE_OUT_CIRCULAR, + EASE_INOUT_CIRCULAR, +}; + +enum TweenProperty +{ + TWEEN_PROPERTY_HEIGHT, + TWEEN_PROPERTY_WIDTH, + TWEEN_PROPERTY_ANGLE, + TWEEN_PROPERTY_ALPHA, + TWEEN_PROPERTY_X, + TWEEN_PROPERTY_Y, + TWEEN_PROPERTY_X_ORIGIN, + TWEEN_PROPERTY_Y_ORIGIN, + TWEEN_PROPERTY_X_OFFSET, + TWEEN_PROPERTY_Y_OFFSET, + TWEEN_PROPERTY_FONT_SIZE +}; diff --git a/RetroFE/Source/Graphics/Component/Component.cpp b/RetroFE/Source/Graphics/Component/Component.cpp new file mode 100644 index 0000000..dfd3637 --- /dev/null +++ b/RetroFE/Source/Graphics/Component/Component.cpp @@ -0,0 +1,337 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "Component.h" +#include "../Animate/Tween.h" +#include "../../Graphics/ViewInfo.h" +#include "../../Utility/Log.h" +#include "../../SDL.h" + +Component::Component() +{ + OnEnterTweens = NULL; + OnExitTweens = NULL; + OnIdleTweens = NULL; + OnHighlightEnterTweens = NULL; + OnHighlightExitTweens = NULL; + SelectedItem = NULL; + NewItemSelectedSinceEnter = false; + BackgroundTexture = NULL; + FreeGraphicsMemory(); +} + +Component::~Component() +{ + FreeGraphicsMemory(); +} + +void Component::FreeGraphicsMemory() +{ + CurrentAnimationState = HIDDEN; + EnterRequested = false; + ExitRequested = false; + NewItemSelected = false; + HighlightExitComplete = false; + CurrentTweens = NULL; + CurrentTweenIndex = 0; + CurrentTweenComplete = false; + ElapsedTweenTime = 0; + ScrollActive = false; + + if(BackgroundTexture) + { + SDL_LockMutex(SDL::GetMutex()); + SDL_DestroyTexture(BackgroundTexture); + SDL_UnlockMutex(SDL::GetMutex()); + + BackgroundTexture = NULL; + } +} +void Component::AllocateGraphicsMemory() +{ + if(!BackgroundTexture) + { + // make a 4x4 pixel wide surface to be stretched during rendering, make it a white background so we can use + // color later + SDL_Surface *surface = SDL_CreateRGBSurface(0, 4, 4, 32, 0, 0, 0, 0); + SDL_FillRect(surface, NULL, SDL_MapRGB(surface->format, 255, 255, 255)); + + SDL_LockMutex(SDL::GetMutex()); + BackgroundTexture = SDL_CreateTextureFromSurface(SDL::GetRenderer(), surface); + SDL_UnlockMutex(SDL::GetMutex()); + + SDL_FreeSurface(surface); + SDL_SetTextureBlendMode(BackgroundTexture, SDL_BLENDMODE_BLEND); + } +} + +void Component::TriggerEnterEvent() +{ + EnterRequested = true; +} + +void Component::TriggerExitEvent() +{ + ExitRequested = true; +} + +void Component::TriggerHighlightEvent(Item *selectedItem) +{ + NewItemSelected = true; + this->SelectedItem = selectedItem; +} + + +bool Component::IsIdle() +{ + return (CurrentAnimationState == IDLE); +} + +bool Component::IsHidden() +{ + return (CurrentAnimationState == HIDDEN); +} +bool Component::IsWaiting() +{ + return (CurrentAnimationState == HIGHLIGHT_WAIT); +} + +void Component::Update(float dt) +{ + ElapsedTweenTime += dt; + HighlightExitComplete = false; + if(IsHidden() || IsWaiting() || (IsIdle() && ExitRequested)) + { + CurrentTweenComplete = true; + } + + if(CurrentTweenComplete) + { + CurrentTweens = NULL; + + // There was no request to override our state path. Continue on as normal. + switch(CurrentAnimationState) + { + case ENTER: + CurrentTweens = OnHighlightEnterTweens; + CurrentAnimationState = HIGHLIGHT_ENTER; + break; + + case EXIT: + CurrentTweens = NULL; + CurrentAnimationState = HIDDEN; + + break; + + case HIGHLIGHT_ENTER: + CurrentTweens = OnIdleTweens; + CurrentAnimationState = IDLE; + break; + + case IDLE: + // prevent us from automatically jumping to the exit tween upon enter + if(EnterRequested) + { + EnterRequested = false; + NewItemSelected = false; + } + else if(IsScrollActive() || NewItemSelected || ExitRequested) + { + CurrentTweens = OnHighlightExitTweens; + CurrentAnimationState = HIGHLIGHT_EXIT; + } + else + { + CurrentTweens = OnIdleTweens; + CurrentAnimationState = IDLE; + } + break; + + case HIGHLIGHT_EXIT: + + // intentionally break down + case HIGHLIGHT_WAIT: + + if(ExitRequested && (CurrentAnimationState == HIGHLIGHT_WAIT)) + { + CurrentTweens = OnHighlightExitTweens; + CurrentAnimationState = HIGHLIGHT_EXIT; + + } + else if(ExitRequested && (CurrentAnimationState == HIGHLIGHT_EXIT)) + { + + CurrentTweens = OnExitTweens; + CurrentAnimationState = EXIT; + ExitRequested = false; + } + else if(IsScrollActive()) + { + CurrentTweens = NULL; + CurrentAnimationState = HIGHLIGHT_WAIT; + } + else if(NewItemSelected) + { + CurrentTweens = OnHighlightEnterTweens; + CurrentAnimationState = HIGHLIGHT_ENTER; + HighlightExitComplete = true; + NewItemSelected = false; + } + else + { + CurrentTweens = NULL; + CurrentAnimationState = HIGHLIGHT_WAIT; + } + break; + + case HIDDEN: + if(EnterRequested || ExitRequested) + { + CurrentTweens = OnEnterTweens; + CurrentAnimationState = ENTER; + } + else + { + CurrentTweens = NULL; + CurrentAnimationState = HIDDEN; + } + } + + CurrentTweenIndex = 0; + CurrentTweenComplete = false; + + ElapsedTweenTime = 0; + } + + CurrentTweenComplete = Animate(IsIdle()); +} + +void Component::Draw() +{ + + if(BackgroundTexture) + { + ViewInfo *info = GetBaseViewInfo(); + SDL_Rect rect; + rect.h = static_cast(info->GetHeight()); + rect.w = static_cast(info->GetWidth()); + rect.x = static_cast(info->GetXRelativeToOrigin()); + rect.y = static_cast(info->GetYRelativeToOrigin()); + + + SDL_SetTextureColorMod(BackgroundTexture, + static_cast(info->GetBackgroundRed()*255), + static_cast(info->GetBackgroundGreen()*255), + static_cast(info->GetBackgroundBlue()*255)); + + SDL::RenderCopy(BackgroundTexture, static_cast(info->GetBackgroundAlpha()*255), NULL, &rect, info->GetAngle()); + } +} + +bool Component::Animate(bool loop) +{ + bool completeDone = false; + if(!CurrentTweens || CurrentTweenIndex >= CurrentTweens->size()) + { + completeDone = true; + } + else if(CurrentTweens) + { + bool currentDone = true; + std::vector *tweenSet = CurrentTweens->at(CurrentTweenIndex); + + for(unsigned int i = 0; i < tweenSet->size(); i++) + { + Tween *tween = tweenSet->at(i); + float elapsedTime = ElapsedTweenTime; + + //todo: too many levels of nesting + if(elapsedTime < tween->GetDuration()) + { + currentDone = false; + } + else + { + elapsedTime = tween->GetDuration(); + } + + float value = tween->Animate(elapsedTime); + + switch(tween->GetProperty()) + { + case TWEEN_PROPERTY_X: + GetBaseViewInfo()->SetX(value); + break; + + case TWEEN_PROPERTY_Y: + GetBaseViewInfo()->SetY(value); + break; + + case TWEEN_PROPERTY_HEIGHT: + GetBaseViewInfo()->SetHeight(value); + break; + + case TWEEN_PROPERTY_WIDTH: + GetBaseViewInfo()->SetWidth(value); + break; + + case TWEEN_PROPERTY_ANGLE: + GetBaseViewInfo()->SetAngle(value); + break; + + case TWEEN_PROPERTY_ALPHA: + GetBaseViewInfo()->SetAlpha(value); + break; + + case TWEEN_PROPERTY_X_ORIGIN: + GetBaseViewInfo()->SetXOrigin(value); + break; + + case TWEEN_PROPERTY_Y_ORIGIN: + GetBaseViewInfo()->SetYOrigin(value); + break; + + case TWEEN_PROPERTY_X_OFFSET: + GetBaseViewInfo()->SetXOffset(value); + break; + + case TWEEN_PROPERTY_Y_OFFSET: + GetBaseViewInfo()->SetYOffset(value); + break; + + case TWEEN_PROPERTY_FONT_SIZE: + GetBaseViewInfo()->SetFontSize(value); + break; + } + } + + if(currentDone) + { + CurrentTweenIndex++; + ElapsedTweenTime = 0; + } + } + + if(!CurrentTweens || CurrentTweenIndex >= CurrentTweens->size()) + { + if(loop) + { + CurrentTweenIndex = 0; + } + completeDone = true; + } + + return completeDone; +} diff --git a/RetroFE/Source/Graphics/Component/Component.h b/RetroFE/Source/Graphics/Component/Component.h new file mode 100644 index 0000000..24da67e --- /dev/null +++ b/RetroFE/Source/Graphics/Component/Component.h @@ -0,0 +1,125 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include + +#include "../../SDL.h" +#include "../MenuNotifierInterface.h" +#include "../ViewInfo.h" +#include "../Animate/Tween.h" +#include "../../Collection/Item.h" + +class Component +{ +public: + Component(); + virtual ~Component(); + virtual void FreeGraphicsMemory(); + virtual void AllocateGraphicsMemory(); + virtual void LaunchEnter() {} + virtual void LaunchExit() {} + void TriggerEnterEvent(); + void TriggerExitEvent(); + void TriggerHighlightEvent(Item *selectedItem); + bool IsIdle(); + bool IsHidden(); + bool IsWaiting(); + typedef std::vector *> TweenSets; + + void SetOnEnterTweens(TweenSets *tweens) + { + this->OnEnterTweens = tweens; + } + + void SetOnExitTweens(TweenSets *tweens) + { + this->OnExitTweens = tweens; + } + + void SetOnIdleTweens(TweenSets *tweens) + { + this->OnIdleTweens = tweens; + } + + void SetOnHighlightEnterTweens(TweenSets *tweens) + { + this->OnHighlightEnterTweens = tweens; + } + + void SetOnHighlightExitTweens(TweenSets *tweens) + { + this->OnHighlightExitTweens = tweens; + } + virtual void Update(float dt); + + virtual void Draw(); + + ViewInfo *GetBaseViewInfo() + { + return &BaseViewInfo; + } + void UpdateBaseViewInfo(ViewInfo &info) + { + BaseViewInfo = info; + } + + bool IsScrollActive() const + { + return ScrollActive; + } + + void SetScrollActive(bool scrollActive) + { + ScrollActive = scrollActive; + } + + + +protected: + Item *GetSelectedItem() + { + return SelectedItem; + } + enum AnimationState + { + IDLE, + ENTER, + HIGHLIGHT_EXIT, + HIGHLIGHT_WAIT, + HIGHLIGHT_ENTER, + EXIT, + HIDDEN + }; + + AnimationState CurrentAnimationState; + bool EnterRequested; + bool ExitRequested; + bool NewItemSelected; + bool HighlightExitComplete; + bool NewItemSelectedSinceEnter; +private: + bool Animate(bool loop); + bool IsTweenSequencingComplete(); + void ResetTweenSequence(std::vector *tweens); + + TweenSets *OnEnterTweens; + TweenSets *OnExitTweens; + TweenSets *OnIdleTweens; + TweenSets *OnHighlightEnterTweens; + TweenSets *OnHighlightExitTweens; + + TweenSets *CurrentTweens; + unsigned int CurrentTweenIndex; + + bool CurrentTweenComplete; + ViewInfo BaseViewInfo; + + float ElapsedTweenTime; + Tween *TweenInst; + Item *SelectedItem; + bool ScrollActive; + SDL_Texture *BackgroundTexture; + +}; diff --git a/RetroFE/Source/Graphics/Component/Container.cpp b/RetroFE/Source/Graphics/Component/Container.cpp new file mode 100644 index 0000000..e9b5405 --- /dev/null +++ b/RetroFE/Source/Graphics/Component/Container.cpp @@ -0,0 +1,44 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "Container.h" +#include "../ViewInfo.h" +#include "../../SDL.h" + +Container::Container() +{ + AllocateGraphicsMemory(); +} + +Container::~Container() +{ + FreeGraphicsMemory(); +} + +void Container::FreeGraphicsMemory() +{ + Component::FreeGraphicsMemory(); +} + +void Container::AllocateGraphicsMemory() +{ + Component::AllocateGraphicsMemory(); +} + +void Container::Draw() +{ + Component::Draw(); +} diff --git a/RetroFE/Source/Graphics/Component/Container.h b/RetroFE/Source/Graphics/Component/Container.h new file mode 100644 index 0000000..ddcf2fd --- /dev/null +++ b/RetroFE/Source/Graphics/Component/Container.h @@ -0,0 +1,18 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include "Component.h" +#include +#include + +class Container : public Component +{ +public: + Container(); + virtual ~Container(); + void FreeGraphicsMemory(); + void AllocateGraphicsMemory(); + void Draw(); +}; diff --git a/RetroFE/Source/Graphics/Component/Image.cpp b/RetroFE/Source/Graphics/Component/Image.cpp new file mode 100644 index 0000000..9fb268e --- /dev/null +++ b/RetroFE/Source/Graphics/Component/Image.cpp @@ -0,0 +1,89 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "Image.h" +#include "../ViewInfo.h" +#include "../../SDL.h" +#include "../../Utility/Log.h" +#include + +Image::Image(std::string file, float scaleX, float scaleY) + : Texture(NULL) + , File(file) + , ScaleX(scaleX) + , ScaleY(scaleY) +{ + AllocateGraphicsMemory(); +} + +Image::~Image() +{ + FreeGraphicsMemory(); +} + +void Image::FreeGraphicsMemory() +{ + Component::FreeGraphicsMemory(); + + SDL_LockMutex(SDL::GetMutex()); + if (Texture != NULL) + { + SDL_DestroyTexture(Texture); + Texture = NULL; + } + SDL_UnlockMutex(SDL::GetMutex()); +} + +void Image::AllocateGraphicsMemory() +{ + int width; + int height; + + Component::AllocateGraphicsMemory(); + + if(!Texture) + { + SDL_LockMutex(SDL::GetMutex()); + Texture = IMG_LoadTexture(SDL::GetRenderer(), File.c_str()); + + if (Texture != NULL) + { + SDL_SetTextureBlendMode(Texture, SDL_BLENDMODE_BLEND); + SDL_QueryTexture(Texture, NULL, NULL, &width, &height); + GetBaseViewInfo()->SetImageWidth(width * ScaleX); + GetBaseViewInfo()->SetImageHeight(height * ScaleY); + } + SDL_UnlockMutex(SDL::GetMutex()); + + } +} + +void Image::Draw() +{ + Component::Draw(); + + if(Texture) + { + ViewInfo *info = GetBaseViewInfo(); + SDL_Rect rect; + + rect.x = static_cast(info->GetXRelativeToOrigin()); + rect.y = static_cast(info->GetYRelativeToOrigin()); + rect.h = static_cast(info->GetHeight()); + rect.w = static_cast(info->GetWidth()); + + SDL::RenderCopy(Texture, static_cast((info->GetAlpha() * 255)), NULL, &rect, info->GetAngle()); + } +} diff --git a/RetroFE/Source/Graphics/Component/Image.h b/RetroFE/Source/Graphics/Component/Image.h new file mode 100644 index 0000000..593539a --- /dev/null +++ b/RetroFE/Source/Graphics/Component/Image.h @@ -0,0 +1,24 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once + +#include "Component.h" +#include +#include + +class Image : public Component +{ +public: + Image(std::string file, float scaleX, float scaleY); + virtual ~Image(); + void FreeGraphicsMemory(); + void AllocateGraphicsMemory(); + void Draw(); + +protected: + SDL_Texture *Texture; + std::string File; + float ScaleX; + float ScaleY; +}; diff --git a/RetroFE/Source/Graphics/Component/ImageBuilder.cpp b/RetroFE/Source/Graphics/Component/ImageBuilder.cpp new file mode 100644 index 0000000..e8b9268 --- /dev/null +++ b/RetroFE/Source/Graphics/Component/ImageBuilder.cpp @@ -0,0 +1,42 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#include "ImageBuilder.h" +#include "../../Utility/Utils.h" +#include "../../Utility/Log.h" +#include + +Image * ImageBuilder::CreateImage(std::string path, std::string name, float scaleX, float scaleY) +{ + Image *image = NULL; + std::vector extensions; + + extensions.push_back("png"); + extensions.push_back("PNG"); + extensions.push_back("jpg"); + extensions.push_back("JPG"); + extensions.push_back("jpeg"); + extensions.push_back("JPEG"); + + std::string prefix = path + "/" + name; + std::string file; + + if(Utils::FindMatchingFile(prefix, extensions, file)) + { + image = new Image(file, scaleX, scaleY); + } + + return image; +} diff --git a/Source/Graphics/Component/ImageBuilder.h b/RetroFE/Source/Graphics/Component/ImageBuilder.h similarity index 75% rename from Source/Graphics/Component/ImageBuilder.h rename to RetroFE/Source/Graphics/Component/ImageBuilder.h index 4f37732..bd4cb06 100644 --- a/Source/Graphics/Component/ImageBuilder.h +++ b/RetroFE/Source/Graphics/Component/ImageBuilder.h @@ -11,5 +11,5 @@ class ImageBuilder { public: - Image * CreateImage(std::string path, std::string name, float scaleX, float scaleY); + Image * CreateImage(std::string path, std::string name, float scaleX, float scaleY); }; diff --git a/RetroFE/Source/Graphics/Component/ReloadableMedia.cpp b/RetroFE/Source/Graphics/Component/ReloadableMedia.cpp new file mode 100644 index 0000000..f2f63d8 --- /dev/null +++ b/RetroFE/Source/Graphics/Component/ReloadableMedia.cpp @@ -0,0 +1,187 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "ReloadableMedia.h" +#include "ImageBuilder.h" +#include "VideoBuilder.h" +#include "../ViewInfo.h" +#include "../../Video/VideoFactory.h" +#include "../../Database/Configuration.h" +#include "../../Utility/Log.h" +#include "../../Utility/Utils.h" +#include "../../SDL.h" +#include +#include +#include + +ReloadableMedia::ReloadableMedia(std::string imagePath, std::string videoPath, bool isVideo, float scaleX, float scaleY) + : LoadedComponent(NULL) + , ImagePath(imagePath) + , VideoPath(videoPath) + , ReloadRequested(false) + , FirstLoad(true) + , IsVideo(isVideo) + , ScaleX(scaleX) + , ScaleY(scaleY) +{ + AllocateGraphicsMemory(); +} + +ReloadableMedia::~ReloadableMedia() +{ + if (LoadedComponent != NULL) + { + delete LoadedComponent; + } +} + +void ReloadableMedia::Update(float dt) +{ + if(NewItemSelected) + { + ReloadRequested = true; + } + // wait for the right moment to reload the image + if (ReloadRequested && (HighlightExitComplete || FirstLoad)) + { + ReloadTexture(); + ReloadRequested = false; + FirstLoad = false; + } + + if(LoadedComponent) + { + LoadedComponent->Update(dt); + } + + // needs to be ran at the end to prevent the NewItemSelected flag from being detected + Component::Update(dt); + +} + +void ReloadableMedia::AllocateGraphicsMemory() +{ + FirstLoad = true; + + if(LoadedComponent) + { + LoadedComponent->AllocateGraphicsMemory(); + } + + // NOTICE! needs to be done last to prevent flags from being missed + Component::AllocateGraphicsMemory(); +} + +void ReloadableMedia::LaunchEnter() +{ + if(LoadedComponent) + { + LoadedComponent->LaunchEnter(); + } +} + +void ReloadableMedia::LaunchExit() +{ + if(LoadedComponent) + { + LoadedComponent->LaunchExit(); + } +} + +void ReloadableMedia::FreeGraphicsMemory() +{ + Component::FreeGraphicsMemory(); + + if(LoadedComponent) + { + LoadedComponent->FreeGraphicsMemory(); + } +} +void ReloadableMedia::ReloadTexture() +{ + bool found = false; + + if(LoadedComponent) + { + delete LoadedComponent; + LoadedComponent = NULL; + } + + Item *selectedItem = GetSelectedItem(); + + if (selectedItem != NULL) + { + if(IsVideo) + { + std::vector names; + + names.push_back(selectedItem->GetName()); + + if(selectedItem->GetCloneOf().length() > 0) + { + names.push_back(selectedItem->GetCloneOf()); + } + + for(unsigned int n = 0; n < names.size() && !found; ++n) + { + std::string filePrefix; + filePrefix.append(VideoPath); + filePrefix.append("/"); + filePrefix.append(names[n]); + + std::string file; + + VideoBuilder videoBuild; + + LoadedComponent = videoBuild.CreateVideo(VideoPath, names[n], ScaleX, ScaleY); + + if(LoadedComponent) + { + LoadedComponent->AllocateGraphicsMemory(); + found = true; + } + } + } + + if(!LoadedComponent) + { + ImageBuilder imageBuild; + LoadedComponent = imageBuild.CreateImage(ImagePath, selectedItem->GetFullTitle(), ScaleX, ScaleY); + + if (LoadedComponent != NULL) + { + LoadedComponent->AllocateGraphicsMemory(); + GetBaseViewInfo()->SetImageWidth(LoadedComponent->GetBaseViewInfo()->GetImageWidth()); + GetBaseViewInfo()->SetImageHeight(LoadedComponent->GetBaseViewInfo()->GetImageHeight()); + } + } + } +} + +void ReloadableMedia::Draw() +{ + ViewInfo *info = GetBaseViewInfo(); + + Component::Draw(); + + if(LoadedComponent) + { + info->SetImageHeight(LoadedComponent->GetBaseViewInfo()->GetImageHeight()); + info->SetImageWidth(LoadedComponent->GetBaseViewInfo()->GetImageWidth()); + LoadedComponent->UpdateBaseViewInfo(*info); + LoadedComponent->Draw(); + } +} diff --git a/RetroFE/Source/Graphics/Component/ReloadableMedia.h b/RetroFE/Source/Graphics/Component/ReloadableMedia.h new file mode 100644 index 0000000..43d0d37 --- /dev/null +++ b/RetroFE/Source/Graphics/Component/ReloadableMedia.h @@ -0,0 +1,38 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once +#include "Component.h" +#include "../../Video/IVideo.h" +#include "../../Collection/Item.h" +#include +#include + +class Image; + +//todo: this class should aggregate Image, Text, and Video component classes +class ReloadableMedia : public Component +{ +public: + ReloadableMedia(std::string imagePath, std::string videoPath, bool isVideo, float scaleX, float scaleY); + virtual ~ReloadableMedia(); + void Update(float dt); + void Draw(); + void FreeGraphicsMemory(); + void AllocateGraphicsMemory(); + void LaunchEnter(); + void LaunchExit(); + +private: + void ReloadTexture(); + Component *LoadedComponent; + std::string ImagePath; + std::string VideoPath; + bool ReloadRequested; + bool FirstLoad; + IVideo *VideoInst; + + bool IsVideo; + float ScaleX; + float ScaleY; +}; diff --git a/RetroFE/Source/Graphics/Component/ReloadableText.cpp b/RetroFE/Source/Graphics/Component/ReloadableText.cpp new file mode 100644 index 0000000..8a2f5ad --- /dev/null +++ b/RetroFE/Source/Graphics/Component/ReloadableText.cpp @@ -0,0 +1,170 @@ +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "ReloadableText.h" +#include "../ViewInfo.h" +#include "../../Database/Configuration.h" +#include "../../Utility/Log.h" +#include "../../SDL.h" +#include +#include +#include + +ReloadableText::ReloadableText(std::string type, Font *font, SDL_Color color, std::string layoutKey, std::string collection, float scaleX, float scaleY) + : ImageInst(NULL) + , LayoutKey(layoutKey) + , Collection(collection) + , ReloadRequested(false) + , FirstLoad(true) + , FontInst(font) + , FontColor(color) + , ScaleX(scaleX) + , ScaleY(scaleY) +{ + + Type = TextTypeUnknown; + + if(type == "numberButtons") + { + Type = TextTypeNumberButtons; + } + else if(type == "numberPlayers") + { + Type = TextTypeNumberPlayers; + } + else if(type == "year") + { + Type = TextTypeYear; + } + else if(type == "title") + { + Type = TextTypeTitle; + } + else if(type == "manufacturer") + { + Type = TextTypeManufacturer; + } + + AllocateGraphicsMemory(); +} + + + +ReloadableText::~ReloadableText() +{ + if (ImageInst != NULL) + { + delete ImageInst; + } +} + +void ReloadableText::Update(float dt) +{ + if(NewItemSelected) + { + ReloadRequested = true; + } + // wait for the right moment to reload the image + if (ReloadRequested && (HighlightExitComplete || FirstLoad)) + { + ReloadTexture(); + ReloadRequested = false; + FirstLoad = false; + } + + // needs to be ran at the end to prevent the NewItemSelected flag from being detected + Component::Update(dt); + +} + +void ReloadableText::AllocateGraphicsMemory() +{ + FirstLoad = true; + + ReloadTexture(); + + // NOTICE! needs to be done last to prevent flags from being missed + Component::AllocateGraphicsMemory(); +} + +void ReloadableText::LaunchEnter() +{ +} + +void ReloadableText::LaunchExit() +{ +} + +void ReloadableText::FreeGraphicsMemory() +{ + Component::FreeGraphicsMemory(); + + if (ImageInst != NULL) + { + delete ImageInst; + ImageInst = NULL; + } +} +void ReloadableText::ReloadTexture() +{ + if (ImageInst != NULL) + { + delete ImageInst; + ImageInst = NULL; + } + + Item *selectedItem = GetSelectedItem(); + + if (selectedItem != NULL) + { + std::stringstream ss; + std::string text; + switch(Type) + { + case TextTypeNumberButtons: + ss << selectedItem->GetNumberButtons(); + break; + case TextTypeNumberPlayers: + ss << selectedItem->GetNumberPlayers(); + break; + case TextTypeYear: + ss << selectedItem->GetYear(); + break; + case TextTypeTitle: + ss << selectedItem->GetTitle(); + break; + case TextTypeManufacturer: + ss << selectedItem->GetManufacturer(); + break; + default: + break; + } + + ImageInst = new Text(ss.str(), FontInst, FontColor, ScaleX, ScaleY); + } +} + + +void ReloadableText::Draw() +{ + ViewInfo *info = GetBaseViewInfo(); + + if(ImageInst) + { + ImageInst->UpdateBaseViewInfo(*info); + ImageInst->Draw(); + } +} diff --git a/RetroFE/Source/Graphics/Component/ReloadableText.h b/RetroFE/Source/Graphics/Component/ReloadableText.h new file mode 100644 index 0000000..71e26c4 --- /dev/null +++ b/RetroFE/Source/Graphics/Component/ReloadableText.h @@ -0,0 +1,48 @@ +/* This file is subject to the terms and conditions defined in + * file 'LICENSE.txt', which is part of this source code package. + */ +#pragma once +#include "Component.h" +#include "Text.h" +#include "../Font.h" +#include "../../Collection/Item.h" +#include
+ +
asdf asdf asdf asdf 22
+ {% trans %}Please activate JavaScript to enable the search + functionality.{% endtrans %} +
{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}
{{ context|e }}