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:
mageven 2020-07-21 09:44:42 +05:30 committed by GitHub
parent 21dfa4974a
commit 4aa47a66c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 190 additions and 68 deletions

View file

@ -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)

View file

@ -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,59 +21,62 @@ 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
[GUI] CheckButton _errorLogToggle;
[GUI] CheckButton _warningLogToggle;
[GUI] CheckButton _infoLogToggle;
[GUI] CheckButton _stubLogToggle;
[GUI] CheckButton _debugLogToggle;
[GUI] CheckButton _fileLogToggle;
[GUI] CheckButton _guestLogToggle;
[GUI] CheckButton _fsAccessLogToggle;
[GUI] Adjustment _fsLogSpinAdjustment;
[GUI] CheckButton _dockedModeToggle;
[GUI] CheckButton _discordToggle;
[GUI] CheckButton _vSyncToggle;
[GUI] CheckButton _multiSchedToggle;
[GUI] CheckButton _ptcToggle;
[GUI] CheckButton _fsicToggle;
[GUI] CheckButton _ignoreToggle;
[GUI] CheckButton _directKeyboardAccess;
[GUI] ComboBoxText _systemLanguageSelect;
[GUI] ComboBoxText _systemRegionSelect;
[GUI] ComboBoxText _systemTimeZoneSelect;
[GUI] ComboBoxText _audioBackendSelect;
[GUI] SpinButton _systemTimeYearSpin;
[GUI] SpinButton _systemTimeMonthSpin;
[GUI] SpinButton _systemTimeDaySpin;
[GUI] SpinButton _systemTimeHourSpin;
[GUI] SpinButton _systemTimeMinuteSpin;
[GUI] Adjustment _systemTimeYearSpinAdjustment;
[GUI] Adjustment _systemTimeMonthSpinAdjustment;
[GUI] Adjustment _systemTimeDaySpinAdjustment;
[GUI] Adjustment _systemTimeHourSpinAdjustment;
[GUI] Adjustment _systemTimeMinuteSpinAdjustment;
[GUI] CheckButton _custThemeToggle;
[GUI] Entry _custThemePath;
[GUI] ToggleButton _browseThemePath;
[GUI] Label _custThemePathLabel;
[GUI] TreeView _gameDirsBox;
[GUI] Entry _addGameDirBox;
[GUI] Entry _graphicsShadersDumpPath;
[GUI] ComboBoxText _anisotropy;
[GUI] ComboBoxText _resScaleCombo;
[GUI] Entry _resScaleText;
[GUI] ToggleButton _configureController1;
[GUI] ToggleButton _configureController2;
[GUI] ToggleButton _configureController3;
[GUI] ToggleButton _configureController4;
[GUI] ToggleButton _configureController5;
[GUI] ToggleButton _configureController6;
[GUI] ToggleButton _configureController7;
[GUI] ToggleButton _configureController8;
[GUI] ToggleButton _configureControllerH;
[GUI] CheckButton _errorLogToggle;
[GUI] CheckButton _warningLogToggle;
[GUI] CheckButton _infoLogToggle;
[GUI] CheckButton _stubLogToggle;
[GUI] CheckButton _debugLogToggle;
[GUI] CheckButton _fileLogToggle;
[GUI] CheckButton _guestLogToggle;
[GUI] CheckButton _fsAccessLogToggle;
[GUI] Adjustment _fsLogSpinAdjustment;
[GUI] CheckButton _dockedModeToggle;
[GUI] CheckButton _discordToggle;
[GUI] CheckButton _vSyncToggle;
[GUI] CheckButton _multiSchedToggle;
[GUI] CheckButton _ptcToggle;
[GUI] CheckButton _fsicToggle;
[GUI] CheckButton _ignoreToggle;
[GUI] CheckButton _directKeyboardAccess;
[GUI] ComboBoxText _systemLanguageSelect;
[GUI] ComboBoxText _systemRegionSelect;
[GUI] Entry _systemTimeZoneEntry;
[GUI] EntryCompletion _systemTimeZoneCompletion;
[GUI] ComboBoxText _audioBackendSelect;
[GUI] SpinButton _systemTimeYearSpin;
[GUI] SpinButton _systemTimeMonthSpin;
[GUI] SpinButton _systemTimeDaySpin;
[GUI] SpinButton _systemTimeHourSpin;
[GUI] SpinButton _systemTimeMinuteSpin;
[GUI] Adjustment _systemTimeYearSpinAdjustment;
[GUI] Adjustment _systemTimeMonthSpinAdjustment;
[GUI] Adjustment _systemTimeDaySpinAdjustment;
[GUI] Adjustment _systemTimeHourSpinAdjustment;
[GUI] Adjustment _systemTimeMinuteSpinAdjustment;
[GUI] CheckButton _custThemeToggle;
[GUI] Entry _custThemePath;
[GUI] ToggleButton _browseThemePath;
[GUI] Label _custThemePathLabel;
[GUI] TreeView _gameDirsBox;
[GUI] Entry _addGameDirBox;
[GUI] Entry _graphicsShadersDumpPath;
[GUI] ComboBoxText _anisotropy;
[GUI] ComboBoxText _resScaleCombo;
[GUI] Entry _resScaleText;
[GUI] ToggleButton _configureController1;
[GUI] ToggleButton _configureController2;
[GUI] ToggleButton _configureController3;
[GUI] ToggleButton _configureController4;
[GUI] ToggleButton _configureController5;
[GUI] ToggleButton _configureController6;
[GUI] ToggleButton _configureController7;
[GUI] ToggleButton _configureController8;
[GUI] ToggleButton _configureControllerH;
#pragma warning restore CS0649, IDE0044
public SettingsWindow(VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(new Builder("Ryujinx.Ui.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
@ -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;
@ -438,6 +482,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;
@ -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;

View file

@ -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>