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.HOS.Services.Time.Clock;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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)
|
||||
{
|
||||
foreach (string cachedLocationName in LocationNameCache)
|
||||
|
|
|
@ -9,7 +9,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -22,6 +21,8 @@ namespace Ryujinx.Ui
|
|||
private static ListStore _gameDirsBoxStore;
|
||||
private static VirtualFileSystem _virtualFileSystem;
|
||||
|
||||
private TimeZoneContentManager _timeZoneContentManager;
|
||||
private HashSet<string> _validTzRegions;
|
||||
private long _systemTimeOffset;
|
||||
|
||||
#pragma warning disable CS0649, IDE0044
|
||||
|
@ -44,7 +45,8 @@ namespace Ryujinx.Ui
|
|||
[GUI] CheckButton _directKeyboardAccess;
|
||||
[GUI] ComboBoxText _systemLanguageSelect;
|
||||
[GUI] ComboBoxText _systemRegionSelect;
|
||||
[GUI] ComboBoxText _systemTimeZoneSelect;
|
||||
[GUI] Entry _systemTimeZoneEntry;
|
||||
[GUI] EntryCompletion _systemTimeZoneCompletion;
|
||||
[GUI] ComboBoxText _audioBackendSelect;
|
||||
[GUI] SpinButton _systemTimeYearSpin;
|
||||
[GUI] SpinButton _systemTimeMonthSpin;
|
||||
|
@ -87,6 +89,11 @@ namespace Ryujinx.Ui
|
|||
|
||||
_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
|
||||
_configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player1);
|
||||
_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);
|
||||
_configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Player8);
|
||||
_configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, args, PlayerIndex.Handheld);
|
||||
_systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut;
|
||||
|
||||
_resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
|
||||
|
||||
|
@ -186,19 +194,6 @@ namespace Ryujinx.Ui
|
|||
_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(() =>
|
||||
{
|
||||
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());
|
||||
_systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString());
|
||||
_systemTimeZoneSelect.SetActiveId(timeZoneContentManager.SanityCheckDeviceLocationName());
|
||||
_resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString());
|
||||
_anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString());
|
||||
|
||||
|
@ -290,6 +317,23 @@ namespace Ryujinx.Ui
|
|||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
int year = _systemTimeYearSpin.ValueAsInt;
|
||||
|
@ -439,6 +483,11 @@ namespace Ryujinx.Ui
|
|||
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.EnableWarn.Value = _warningLogToggle.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.Region.Value = Enum.Parse<Configuration.System.Region>(_systemRegionSelect.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.Ui.CustomThemePath.Value = _custThemePath.Buffer.Text;
|
||||
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text;
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</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">
|
||||
<property name="lower">1</property>
|
||||
<property name="upper">31</property>
|
||||
|
@ -1224,11 +1229,12 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="_systemTimeZoneSelect">
|
||||
<object class="GtkEntry" id="_systemTimeZoneEntry">
|
||||
<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="margin_left">5</property>
|
||||
<property name="completion">_systemTimeZoneCompletion</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
|
Loading…
Reference in a new issue