forked from Mirror/Ryujinx
Better TimeZone entry in System Settings (#1254)
* Better timezone labels in System TimeZone Replace with GtkEntry with auto-complete Also removed async task as now loading is fast Address Thog's comments self-nit: Remove string alias Address AcK's comments * Improve parsing * Optimize and fix string matching Address jD's comments * Also, make abbreviations searchable * Optimize EntryCompletion's MatchFunc * nit: Result.IsFailure() * Fix potential crash on opening Settings window w/o FW installed
This commit is contained in:
parent
21dfa4974a
commit
4aa47a66c6
3 changed files with 190 additions and 68 deletions
|
@ -10,6 +10,7 @@ using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.FileSystem.Content;
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||||
using Ryujinx.HLE.Utilities;
|
using Ryujinx.HLE.Utilities;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
@ -117,6 +118,73 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(int Offset, string Location, string Abbr)> ParseTzOffsets()
|
||||||
|
{
|
||||||
|
var tzBinaryContentPath = GetTimeZoneBinaryTitleContentPath();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(tzBinaryContentPath))
|
||||||
|
{
|
||||||
|
return new[] { (0, "UTC", "UTC") };
|
||||||
|
}
|
||||||
|
|
||||||
|
List<(int Offset, string Location, string Abbr)> outList = new List<(int Offset, string Location, string Abbr)>();
|
||||||
|
var now = System.DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||||
|
using (IStorage ncaStorage = new LocalStorage(_virtualFileSystem.SwitchPathToSystemPath(tzBinaryContentPath), FileAccess.Read, FileMode.Open))
|
||||||
|
using (IFileSystem romfs = new Nca(_virtualFileSystem.KeySet, ncaStorage).OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel))
|
||||||
|
{
|
||||||
|
foreach (string locName in LocationNameCache)
|
||||||
|
{
|
||||||
|
if (locName.StartsWith("Etc"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (romfs.OpenFile(out IFile tzif, $"/zoneinfo/{locName}".ToU8Span(), OpenMode.Read).IsFailure())
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.ServiceTime, $"Error opening /zoneinfo/{locName}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (tzif)
|
||||||
|
{
|
||||||
|
TimeZone.ParseTimeZoneBinary(out TimeZoneRule tzRule, tzif.AsStream());
|
||||||
|
|
||||||
|
TimeTypeInfo ttInfo;
|
||||||
|
if (tzRule.TimeCount > 0) // Find the current transition period
|
||||||
|
{
|
||||||
|
int fin = 0;
|
||||||
|
for (int i = 0; i < tzRule.TimeCount; ++i)
|
||||||
|
{
|
||||||
|
if (tzRule.Ats[i] <= now)
|
||||||
|
{
|
||||||
|
fin = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ttInfo = tzRule.Ttis[tzRule.Types[fin]];
|
||||||
|
}
|
||||||
|
else if (tzRule.TypeCount >= 1) // Otherwise, use the first offset in TTInfo
|
||||||
|
{
|
||||||
|
ttInfo = tzRule.Ttis[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.ServiceTime, $"Couldn't find UTC offset for zone {locName}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var abbrStart = tzRule.Chars.AsSpan(ttInfo.AbbreviationListIndex);
|
||||||
|
int abbrEnd = abbrStart.IndexOf('\0');
|
||||||
|
|
||||||
|
outList.Add((ttInfo.GmtOffset, locName, abbrStart.Slice(0, abbrEnd).ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outList.Sort();
|
||||||
|
|
||||||
|
return outList;
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsLocationNameValid(string locationName)
|
private bool IsLocationNameValid(string locationName)
|
||||||
{
|
{
|
||||||
foreach (string cachedLocationName in LocationNameCache)
|
foreach (string cachedLocationName in LocationNameCache)
|
||||||
|
|
|
@ -9,7 +9,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -22,6 +21,8 @@ namespace Ryujinx.Ui
|
||||||
private static ListStore _gameDirsBoxStore;
|
private static ListStore _gameDirsBoxStore;
|
||||||
private static VirtualFileSystem _virtualFileSystem;
|
private static VirtualFileSystem _virtualFileSystem;
|
||||||
|
|
||||||
|
private TimeZoneContentManager _timeZoneContentManager;
|
||||||
|
private HashSet<string> _validTzRegions;
|
||||||
private long _systemTimeOffset;
|
private long _systemTimeOffset;
|
||||||
|
|
||||||
#pragma warning disable CS0649, IDE0044
|
#pragma warning disable CS0649, IDE0044
|
||||||
|
@ -44,7 +45,8 @@ namespace Ryujinx.Ui
|
||||||
[GUI] CheckButton _directKeyboardAccess;
|
[GUI] CheckButton _directKeyboardAccess;
|
||||||
[GUI] ComboBoxText _systemLanguageSelect;
|
[GUI] ComboBoxText _systemLanguageSelect;
|
||||||
[GUI] ComboBoxText _systemRegionSelect;
|
[GUI] ComboBoxText _systemRegionSelect;
|
||||||
[GUI] ComboBoxText _systemTimeZoneSelect;
|
[GUI] Entry _systemTimeZoneEntry;
|
||||||
|
[GUI] EntryCompletion _systemTimeZoneCompletion;
|
||||||
[GUI] ComboBoxText _audioBackendSelect;
|
[GUI] ComboBoxText _audioBackendSelect;
|
||||||
[GUI] SpinButton _systemTimeYearSpin;
|
[GUI] SpinButton _systemTimeYearSpin;
|
||||||
[GUI] SpinButton _systemTimeMonthSpin;
|
[GUI] SpinButton _systemTimeMonthSpin;
|
||||||
|
@ -87,6 +89,11 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
|
_timeZoneContentManager = new TimeZoneContentManager();
|
||||||
|
_timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, LibHac.FsSystem.IntegrityCheckLevel.None);
|
||||||
|
|
||||||
|
_validTzRegions = new HashSet<string>(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly.
|
||||||
|
|
||||||
//Bind Events
|
//Bind Events
|
||||||
_configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player1);
|
_configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player1);
|
||||||
_configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player2);
|
_configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player2);
|
||||||
|
@ -97,6 +104,7 @@ namespace Ryujinx.Ui
|
||||||
_configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player7);
|
_configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player7);
|
||||||
_configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player8);
|
_configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player8);
|
||||||
_configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Handheld);
|
_configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Handheld);
|
||||||
|
_systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut;
|
||||||
|
|
||||||
_resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
|
_resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
|
||||||
|
|
||||||
|
@ -186,19 +194,6 @@ namespace Ryujinx.Ui
|
||||||
_custThemeToggle.Click();
|
_custThemeToggle.Click();
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeZoneContentManager timeZoneContentManager = new TimeZoneContentManager();
|
|
||||||
|
|
||||||
timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, LibHac.FsSystem.IntegrityCheckLevel.None);
|
|
||||||
|
|
||||||
List<string> locationNames = timeZoneContentManager.LocationNameCache.ToList();
|
|
||||||
|
|
||||||
locationNames.Sort();
|
|
||||||
|
|
||||||
foreach (string locationName in locationNames)
|
|
||||||
{
|
|
||||||
_systemTimeZoneSelect.Append(locationName, locationName);
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (SoundIoAudioOut.IsSupported)
|
if (SoundIoAudioOut.IsSupported)
|
||||||
|
@ -223,9 +218,41 @@ namespace Ryujinx.Ui
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Custom EntryCompletion Columns. If added to glade, need to override more signals
|
||||||
|
ListStore tzList = new ListStore(typeof(string), typeof(string), typeof(string));
|
||||||
|
_systemTimeZoneCompletion.Model = tzList;
|
||||||
|
|
||||||
|
CellRendererText offsetCol = new CellRendererText();
|
||||||
|
CellRendererText abbrevCol = new CellRendererText();
|
||||||
|
|
||||||
|
_systemTimeZoneCompletion.PackStart(offsetCol, false);
|
||||||
|
_systemTimeZoneCompletion.AddAttribute(offsetCol, "text", 0);
|
||||||
|
_systemTimeZoneCompletion.TextColumn = 1; // Regions Column
|
||||||
|
_systemTimeZoneCompletion.PackStart(abbrevCol, false);
|
||||||
|
_systemTimeZoneCompletion.AddAttribute(abbrevCol, "text", 2);
|
||||||
|
|
||||||
|
int maxLocationLength = 0;
|
||||||
|
|
||||||
|
foreach (var (offset, location, abbr) in _timeZoneContentManager.ParseTzOffsets())
|
||||||
|
{
|
||||||
|
var hours = Math.DivRem(offset, 3600, out int seconds);
|
||||||
|
var minutes = Math.Abs(seconds) / 60;
|
||||||
|
|
||||||
|
var abbr2 = (abbr.StartsWith('+') || abbr.StartsWith('-')) ? string.Empty : abbr;
|
||||||
|
|
||||||
|
tzList.AppendValues($"UTC{hours:+0#;-0#;+00}:{minutes:D2} ", location, abbr2);
|
||||||
|
_validTzRegions.Add(location);
|
||||||
|
|
||||||
|
maxLocationLength = Math.Max(maxLocationLength, location.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
_systemTimeZoneEntry.WidthChars = Math.Max(20, maxLocationLength + 1); // Ensure minimum Entry width
|
||||||
|
_systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName();
|
||||||
|
|
||||||
|
_systemTimeZoneCompletion.MatchFunc = TimeZoneMatchFunc;
|
||||||
|
|
||||||
_systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString());
|
_systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString());
|
||||||
_systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString());
|
_systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString());
|
||||||
_systemTimeZoneSelect.SetActiveId(timeZoneContentManager.SanityCheckDeviceLocationName());
|
|
||||||
_resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString());
|
_resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString());
|
||||||
_anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString());
|
_anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString());
|
||||||
|
|
||||||
|
@ -290,6 +317,23 @@ namespace Ryujinx.Ui
|
||||||
}
|
}
|
||||||
|
|
||||||
//Events
|
//Events
|
||||||
|
private void TimeZoneEntry_FocusOut(Object sender, FocusOutEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text))
|
||||||
|
{
|
||||||
|
_systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TimeZoneMatchFunc(EntryCompletion compl, string key, TreeIter iter)
|
||||||
|
{
|
||||||
|
key = key.Trim().Replace(' ', '_');
|
||||||
|
|
||||||
|
return ((string)compl.Model.GetValue(iter, 1)).Contains(key, StringComparison.OrdinalIgnoreCase) || // region
|
||||||
|
((string)compl.Model.GetValue(iter, 2)).StartsWith(key, StringComparison.OrdinalIgnoreCase) || // abbr
|
||||||
|
((string)compl.Model.GetValue(iter, 0)).Substring(3).StartsWith(key); // offset
|
||||||
|
}
|
||||||
|
|
||||||
private void SystemTimeSpin_ValueChanged(Object sender, EventArgs e)
|
private void SystemTimeSpin_ValueChanged(Object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
int year = _systemTimeYearSpin.ValueAsInt;
|
int year = _systemTimeYearSpin.ValueAsInt;
|
||||||
|
@ -439,6 +483,11 @@ namespace Ryujinx.Ui
|
||||||
resScaleCustom = 1.0f;
|
resScaleCustom = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_validTzRegions.Contains(_systemTimeZoneEntry.Text))
|
||||||
|
{
|
||||||
|
ConfigurationState.Instance.System.TimeZone.Value = _systemTimeZoneEntry.Text;
|
||||||
|
}
|
||||||
|
|
||||||
ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active;
|
ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active;
|
||||||
ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active;
|
ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active;
|
||||||
ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active;
|
ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active;
|
||||||
|
@ -459,7 +508,6 @@ namespace Ryujinx.Ui
|
||||||
ConfigurationState.Instance.System.Language.Value = Enum.Parse<Language>(_systemLanguageSelect.ActiveId);
|
ConfigurationState.Instance.System.Language.Value = Enum.Parse<Language>(_systemLanguageSelect.ActiveId);
|
||||||
ConfigurationState.Instance.System.Region.Value = Enum.Parse<Configuration.System.Region>(_systemRegionSelect.ActiveId);
|
ConfigurationState.Instance.System.Region.Value = Enum.Parse<Configuration.System.Region>(_systemRegionSelect.ActiveId);
|
||||||
ConfigurationState.Instance.System.AudioBackend.Value = Enum.Parse<AudioBackend>(_audioBackendSelect.ActiveId);
|
ConfigurationState.Instance.System.AudioBackend.Value = Enum.Parse<AudioBackend>(_audioBackendSelect.ActiveId);
|
||||||
ConfigurationState.Instance.System.TimeZone.Value = _systemTimeZoneSelect.ActiveId;
|
|
||||||
ConfigurationState.Instance.System.SystemTimeOffset.Value = _systemTimeOffset;
|
ConfigurationState.Instance.System.SystemTimeOffset.Value = _systemTimeOffset;
|
||||||
ConfigurationState.Instance.Ui.CustomThemePath.Value = _custThemePath.Buffer.Text;
|
ConfigurationState.Instance.Ui.CustomThemePath.Value = _custThemePath.Buffer.Text;
|
||||||
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text;
|
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text;
|
||||||
|
|
|
@ -7,6 +7,11 @@
|
||||||
<property name="step_increment">1</property>
|
<property name="step_increment">1</property>
|
||||||
<property name="page_increment">10</property>
|
<property name="page_increment">10</property>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkEntryCompletion" id="_systemTimeZoneCompletion">
|
||||||
|
<property name="inline-completion">True</property>
|
||||||
|
<property name="inline-selection">True</property>
|
||||||
|
<property name="minimum-key-length">0</property>
|
||||||
|
</object>
|
||||||
<object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment">
|
<object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment">
|
||||||
<property name="lower">1</property>
|
<property name="lower">1</property>
|
||||||
<property name="upper">31</property>
|
<property name="upper">31</property>
|
||||||
|
@ -1224,11 +1229,12 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkComboBoxText" id="_systemTimeZoneSelect">
|
<object class="GtkEntry" id="_systemTimeZoneEntry">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="tooltip_text" translatable="yes">Change System TimeZone</property>
|
<property name="tooltip_text" translatable="yes">Change System TimeZone</property>
|
||||||
<property name="margin_left">5</property>
|
<property name="margin_left">5</property>
|
||||||
|
<property name="completion">_systemTimeZoneCompletion</property>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
|
Loading…
Reference in a new issue