diff --git a/App/Interfaces/Controls/IBeatmapListingPresenter.cs b/App/Interfaces/Controls/IBeatmapListingPresenter.cs deleted file mode 100644 index f328831..0000000 --- a/App/Interfaces/Controls/IBeatmapListingPresenter.cs +++ /dev/null @@ -1,9 +0,0 @@ -using CollectionManager.DataTypes; - -namespace App.Interfaces -{ - public interface IBeatmapListingPresenter - { - Beatmaps Beatmaps { get; set; } - } -} \ No newline at end of file diff --git a/App/Models/Controls/BeatmapListingModel.cs b/App/Models/Controls/BeatmapListingModel.cs index 3539851..e1e685c 100644 --- a/App/Models/Controls/BeatmapListingModel.cs +++ b/App/Models/Controls/BeatmapListingModel.cs @@ -81,16 +81,17 @@ public void SetBeatmaps(Beatmaps beatmaps) public void SetCollection(ICollection collection) { + CurrentCollection = collection; if (collection == null) { SetBeatmaps(null); - CurrentCollection = collection; - return; } - CurrentCollection = collection; - var maps = new Beatmaps(); - maps.AddRange(collection.AllBeatmaps()); - SetBeatmaps(maps); + else + { + var maps = new Beatmaps(); + maps.AddRange(collection.AllBeatmaps()); + SetBeatmaps(maps); + } } public void FilterBeatmaps(string text) diff --git a/App/Presenters/Controls/BeatmapListingPresenter.cs b/App/Presenters/Controls/BeatmapListingPresenter.cs index 7ea5a78..cd1c3fb 100644 --- a/App/Presenters/Controls/BeatmapListingPresenter.cs +++ b/App/Presenters/Controls/BeatmapListingPresenter.cs @@ -6,25 +6,11 @@ namespace App.Presenters.Controls { - public class BeatmapListingPresenter: IBeatmapListingPresenter + public class BeatmapListingPresenter { readonly IBeatmapListingView _view; readonly IBeatmapListingModel _model; - private Beatmaps _beatmaps; - - public Beatmaps Beatmaps - { - get - { - return _beatmaps; - } - set - { - _beatmaps = value; - _view.SetBeatmaps(value); - } - } public BeatmapListingPresenter(IBeatmapListingView view, IBeatmapListingModel model) { _view = view; @@ -36,11 +22,11 @@ public BeatmapListingPresenter(IBeatmapListingView view, IBeatmapListingModel mo _view.BeatmapOperation += (s, a) => _model.EmitBeatmapOperation(a); _model = model; - _model.BeatmapsChanged += _model_BeatmapsChanged; + _model.BeatmapsChanged += (_, _) => RefreshBeatmapsInViewFromModel(); _model.FilteringStarted+=ModelOnFilteringStarted; _model.FilteringFinished += _model_FilteringFinished; _view.SetFilter(_model.GetFilter()); - Beatmaps = _model.GetBeatmaps(); + RefreshBeatmapsInViewFromModel(); } private void _model_FilteringFinished(object sender, EventArgs e) @@ -63,9 +49,15 @@ private void ViewOnSearchTextChanged(object sender, EventArgs eventArgs) _model.FilterBeatmaps(_view.SearchText); } - private void _model_BeatmapsChanged(object sender, System.EventArgs e) + private void RefreshBeatmapsInViewFromModel() { - Beatmaps = _model.GetBeatmaps(); + _view.SetBeatmaps(_model.GetBeatmaps()); + _view.ClearCustomFieldDefinitions(); + var curCol = _model.CurrentCollection; + if(curCol != null && curCol.CustomFieldDefinitions != null) + { + _view.SetCustomFieldDefinitions(curCol.CustomFieldDefinitions); + } } diff --git a/CollectionManagerDll/DataTypes/BeatmapExtension.cs b/CollectionManagerDll/DataTypes/BeatmapExtension.cs index 12d3cc8..feb4bd0 100644 --- a/CollectionManagerDll/DataTypes/BeatmapExtension.cs +++ b/CollectionManagerDll/DataTypes/BeatmapExtension.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace CollectionManager.DataTypes { @@ -13,5 +15,42 @@ public class BeatmapExtension : Beatmap public string UserComment { get; set; } = ""; #endregion + + #region Custom Field Stuff + + private Dictionary _customFields; + + public void SetCustomFieldValues(BeatmapExtension other) + { + _customFields = other._customFields == null ? null : new Dictionary(other._customFields); + } + + public void SetCustomFieldValue(string key, object value) + { + _customFields ??= new Dictionary(); + _customFields[key] = value; + } + + public object GetCustomFieldValue(string key) + { + if(_customFields == null ) return null; + return _customFields.TryGetValue(key, out var value) ? value : null; + } + + public IEnumerable GetStringCustomFieldValues() + { + if (_customFields == null) yield break; + foreach(var customField in _customFields) + { + if(customField.Value is string stringValue) yield return stringValue; + } + } + + public IEnumerable> GetCustomFields() + { + return _customFields ?? Enumerable.Empty>(); + } + + #endregion } } \ No newline at end of file diff --git a/CollectionManagerDll/DataTypes/Collection.cs b/CollectionManagerDll/DataTypes/Collection.cs index 4cb45f3..5349855 100644 --- a/CollectionManagerDll/DataTypes/Collection.cs +++ b/CollectionManagerDll/DataTypes/Collection.cs @@ -58,6 +58,8 @@ public virtual int NumberOfBeatmaps public int Id { get; set; } + public IReadOnlyCollection CustomFieldDefinitions { get; set; } + public void SetLoadedMaps(MapCacher instance) { if (instance == null) @@ -161,6 +163,7 @@ public void AddBeatmapByMapId(int mapId) private void ProcessAdditionalProps(BeatmapExtension src, BeatmapExtension dest) { dest.UserComment = src.UserComment; + dest.SetCustomFieldValues(src); } protected virtual void ProcessNewlyAddedMap(BeatmapExtension map) { @@ -236,6 +239,5 @@ public IEnumerator GetEnumerator() { return this.AllBeatmaps().GetEnumerator(); } - } } \ No newline at end of file diff --git a/CollectionManagerDll/DataTypes/CustomFieldDefinition.cs b/CollectionManagerDll/DataTypes/CustomFieldDefinition.cs new file mode 100644 index 0000000..f0a38f3 --- /dev/null +++ b/CollectionManagerDll/DataTypes/CustomFieldDefinition.cs @@ -0,0 +1,11 @@ +using CollectionManager.Enums; + +namespace CollectionManager.DataTypes +{ + public class CustomFieldDefinition + { + public string Key { get; set; } + public CustomFieldType Type { get; set; } + public string DisplayText { get; set; } + } +} diff --git a/CollectionManagerDll/DataTypes/ICollection.cs b/CollectionManagerDll/DataTypes/ICollection.cs index c02edc9..55ce232 100644 --- a/CollectionManagerDll/DataTypes/ICollection.cs +++ b/CollectionManagerDll/DataTypes/ICollection.cs @@ -53,6 +53,8 @@ public interface ICollection int Id { get; set; } + IReadOnlyCollection CustomFieldDefinitions { get; } + void SetLoadedMaps(MapCacher instance); IEnumerable AllBeatmaps(); IEnumerable NotKnownBeatmaps(); diff --git a/CollectionManagerDll/Enums/CustomFieldTypes.cs b/CollectionManagerDll/Enums/CustomFieldTypes.cs new file mode 100644 index 0000000..aa4aa8e --- /dev/null +++ b/CollectionManagerDll/Enums/CustomFieldTypes.cs @@ -0,0 +1,21 @@ +namespace CollectionManager.Enums +{ + public enum CustomFieldType + { + String, + Boolean, + GameMode, + Grade, + UInt8, + UInt16, + UInt32, + UInt64, + Int8, + Int16, + Int32, + Int64, + DateTime, + Single, + Double + } +} diff --git a/CollectionManagerDll/Modules/FileIO/FileCollections/OsdbCollectionHandler.cs b/CollectionManagerDll/Modules/FileIO/FileCollections/OsdbCollectionHandler.cs index cdbfec8..9c9eeb7 100644 --- a/CollectionManagerDll/Modules/FileIO/FileCollections/OsdbCollectionHandler.cs +++ b/CollectionManagerDll/Modules/FileIO/FileCollections/OsdbCollectionHandler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; @@ -34,43 +35,36 @@ public class OsdbCollectionHandler {"o!dm6", 6}, {"o!dm7", 7}, {"o!dm8", 8}, + {"o!dm9", 9}, {"o!dm7min", 1007}, {"o!dm8min", 1008}, }; + private const string CurrentVersion = "o!dm9"; + public OsdbCollectionHandler(ILogger logger) { _logger = logger; } - public string CurrentVersion(bool minimalWrite = false) - { - return "o!dm8" + (minimalWrite ? "min" : ""); - } - - private bool IsFullCollection(string versionString) - => !isMinimalCollection(versionString); - private bool isMinimalCollection(string versionString) - => versionString.EndsWith("min"); - - public void WriteOsdb(Collections collections, string fullFileDir, string editor, bool minimalWrite = false) + public void WriteOsdb(Collections collections, string fullFileDir, string editor) { using (var fileStream = new FileStream(fullFileDir, FileMode.Create, FileAccess.ReadWrite)) { - WriteOsdb(collections, fileStream, editor, minimalWrite); + WriteOsdb(collections, fileStream, editor); } } - public void WriteOsdb(Collections collections, Stream outputStream, string editor, bool minimalWrite = false) + public void WriteOsdb(Collections collections, Stream outputStream, string editor) { using (var osdbMemoryStream = new MemoryStream()) using (var osdbBinaryWriter = new BinaryWriter(osdbMemoryStream)) { - WriteOsdb(collections, osdbBinaryWriter, editor, minimalWrite); + WriteOsdb(collections, osdbBinaryWriter, editor); using (var outputBinaryWriter = new BinaryWriter(outputStream, Encoding.UTF8, true)) { - outputBinaryWriter.Write(CurrentVersion(minimalWrite)); + outputBinaryWriter.Write(CurrentVersion); CompressStream(osdbMemoryStream, outputStream); } } @@ -86,11 +80,10 @@ public void CompressStream(Stream inputStream, Stream outputStream) archive.SaveTo(outputStream, new WriterOptions(CompressionType.GZip)); } } - private void WriteOsdb(Collections collections, BinaryWriter _binWriter, string editor, - bool minimalWrite = false) + private void WriteOsdb(Collections collections, BinaryWriter _binWriter, string editor) { //header - _binWriter.Write(CurrentVersion(minimalWrite)); + _binWriter.Write(CurrentVersion); //save date _binWriter.Write(DateTime.Now.ToOADate()); //who saved given osdb @@ -127,23 +120,66 @@ private void WriteOsdb(Collections collections, BinaryWriter _binWriter, string _binWriter.Write(collection.Name); _binWriter.Write(collection.OnlineId); + + // Custom Fields + var customFieldDefLookup = new Dictionary(); + if (collection.CustomFieldDefinitions == null) + { + _binWriter.Write(0); + } + else + { + _binWriter.Write(collection.CustomFieldDefinitions.Count); + foreach (var def in collection.CustomFieldDefinitions) + { + customFieldDefLookup.Add(def.Key, def); + _binWriter.Write(def.Key); + _binWriter.Write((byte)def.Type); + _binWriter.Write(def.DisplayText); + } + } + _binWriter.Write(beatmapsPossibleToSave.Count); //Save beatmaps foreach (var beatmap in beatmapsPossibleToSave) { _binWriter.Write(beatmap.MapId); _binWriter.Write(beatmap.MapSetId); - if (!minimalWrite) - { - _binWriter.Write(beatmap.ArtistRoman); - _binWriter.Write(beatmap.TitleRoman); - _binWriter.Write(beatmap.DiffName); - } + _binWriter.Write(beatmap.ArtistRoman); + _binWriter.Write(beatmap.TitleRoman); + _binWriter.Write(beatmap.DiffName); _binWriter.Write(beatmap.Md5); _binWriter.Write(((BeatmapExtension)beatmap).UserComment); _binWriter.Write((byte)beatmap.PlayMode); _binWriter.Write(beatmap.StarsNomod); + + // completely skip the custom fields section if there are no definitions in the header of this collection + if(customFieldDefLookup.Count > 0) + { + var validCustomFieldWriteActions = new List(); + foreach (var kvp in ((BeatmapExtension)beatmap).GetCustomFields()) + { + if (!customFieldDefLookup.TryGetValue(kvp.Key, out var def) || + !CustomFieldWriters.TryGetValue(def.Type, out var writerAction)) + { + // skip value if definition or its stream writer doesn't exist + continue; + } + + validCustomFieldWriteActions.Add(() => + { + _binWriter.Write(kvp.Key); + writerAction(_binWriter, kvp.Value); + }); + } + + _binWriter.Write(validCustomFieldWriteActions.Count); + foreach(var writeAction in validCustomFieldWriteActions) + { + writeAction(); + } + } } _binWriter.Write(beatmapWithHashOnly.Count); @@ -183,6 +219,13 @@ public IEnumerable ReadOsdb(string fullFileDir, MapCacher mapCacher) } else { + bool minimalCollection = false; + if(fileVersion > 1000) + { + minimalCollection = true; + fileVersion -= 1000; + } + if (fileVersion >= 7) { using (var archiveReader = GZipArchive.Open(_memStream)) @@ -211,11 +254,42 @@ public IEnumerable ReadOsdb(string fullFileDir, MapCacher mapCacher) onlineId = _binReader.ReadInt32(); } + var collection = new Collection(mapCacher) + { + Name = name, + LastEditorUsername = lastEditor, + OnlineId = onlineId + }; + + var customFieldDefinitions = new Dictionary(); + if (fileVersion >= 9) + { + var numberOfCustomFieldDefinitions = _binReader.ReadInt32(); + for (var j = 0; j < numberOfCustomFieldDefinitions; j++) + { + var key = _binReader.ReadString(); + var def = new CustomFieldDefinition + { + Key = key, + Type = (CustomFieldType)_binReader.ReadByte(), + DisplayText = _binReader.ReadString() + }; + try + { + customFieldDefinitions.Add(key, def); + } + catch (ArgumentException) + { + // custom field key is already used + throw new CorruptedFileException($"Collection declared multiple custom fields with the same key"); + } + } + } + var numberOfBeatmaps = _binReader.ReadInt32(); _logger?.Log(">Number of maps in collection {0}: {1} named:{2}", i.ToString(), numberOfBeatmaps.ToString(), name); - var collection = new Collection(mapCacher) - { Name = name, LastEditorUsername = lastEditor, OnlineId = onlineId }; + var actuallyUsedCustomFieldDefinitions = new HashSet(); for (var j = 0; j < numberOfBeatmaps; j++) { var map = new BeatmapExtension(); @@ -225,7 +299,7 @@ public IEnumerable ReadOsdb(string fullFileDir, MapCacher mapCacher) map.MapSetId = _binReader.ReadInt32(); } - if (!isMinimalCollection(versionString)) + if (!minimalCollection) { map.ArtistRoman = _binReader.ReadString(); map.TitleRoman = _binReader.ReadString(); @@ -238,19 +312,47 @@ public IEnumerable ReadOsdb(string fullFileDir, MapCacher mapCacher) map.UserComment = _binReader.ReadString(); } - if (fileVersion >= 8 || (fileVersion >= 5 && IsFullCollection(versionString))) + // versions 5-7 field existed only in full collections, since version 8 field is always here + if (fileVersion >= 8 || (fileVersion >= 5 && !minimalCollection)) { map.PlayMode = (PlayMode)_binReader.ReadByte(); } - if (fileVersion >= 8 || (fileVersion >= 6 && IsFullCollection(versionString))) + // versions 6-7 field existed only in full collections, since version 8 field is always here + if (fileVersion >= 8 || (fileVersion >= 6 && !minimalCollection)) { map.ModPpStars.Add(map.PlayMode, new StarRating { { 0, _binReader.ReadDouble() } }); } + // completely skip the custom fields section regardless of version if there are no definitions in the header of this collection + if (fileVersion >= 9 && customFieldDefinitions.Count > 0) + { + var numberOfCustomFieldValues = _binReader.ReadInt32(); + for (var k = 0; k < numberOfCustomFieldValues; k++) + { + var key = _binReader.ReadString(); + if (!customFieldDefinitions.TryGetValue(key, out var customFieldDef) || + !CustomFieldReaders.TryGetValue(customFieldDef.Type, out var readerFunc)) + { + // skip value, it has no definition or we dont know how to handle this type + continue; + } + + var customFieldValue = readerFunc(_binReader); + map.SetCustomFieldValue(key, customFieldValue); + actuallyUsedCustomFieldDefinitions.Add(key); + } + } + collection.AddBeatmap(map); } + collection.CustomFieldDefinitions = customFieldDefinitions + .Where(x => actuallyUsedCustomFieldDefinitions.Contains(x.Key)) + .Select(x => x.Value) + .ToList() + .AsReadOnly(); + if (fileVersion >= 3) { var numberOfMapHashes = _binReader.ReadInt32(); @@ -273,5 +375,43 @@ public IEnumerable ReadOsdb(string fullFileDir, MapCacher mapCacher) _binReader.Close(); } + + private static readonly Dictionary> CustomFieldReaders = new() + { + { CustomFieldType.String, reader => reader.ReadString() }, + { CustomFieldType.Boolean, reader => reader.ReadBoolean() }, + { CustomFieldType.GameMode, reader => (PlayMode)reader.ReadByte() }, + { CustomFieldType.Grade, reader => (OsuGrade)reader.ReadByte() }, + { CustomFieldType.UInt8, reader => reader.ReadByte() }, + { CustomFieldType.UInt16, reader => reader.ReadUInt16() }, + { CustomFieldType.UInt32, reader => reader.ReadUInt32() }, + { CustomFieldType.UInt64, reader => reader.ReadUInt64() }, + { CustomFieldType.Int8, reader => reader.ReadSByte() }, + { CustomFieldType.Int16, reader => reader.ReadInt16() }, + { CustomFieldType.Int32, reader => reader.ReadInt32() }, + { CustomFieldType.Int64, reader => reader.ReadInt64() }, + { CustomFieldType.DateTime, reader => new DateTime(reader.ReadInt64()) }, + { CustomFieldType.Single, reader => reader.ReadSingle() }, + { CustomFieldType.Double, reader => reader.ReadDouble() }, + }; + + private static readonly Dictionary> CustomFieldWriters = new() + { + { CustomFieldType.String, (writer, value) => writer.Write((string)value) }, + { CustomFieldType.Boolean, (writer, value) => writer.Write((bool)value) }, + { CustomFieldType.GameMode, (writer, value) => writer.Write((byte)value) }, + { CustomFieldType.Grade, (writer, value) => writer.Write((byte)value) }, + { CustomFieldType.UInt8, (writer, value) => writer.Write((byte)value) }, + { CustomFieldType.UInt16, (writer, value) => writer.Write((ushort)value) }, + { CustomFieldType.UInt32, (writer, value) => writer.Write((uint)value) }, + { CustomFieldType.UInt64, (writer, value) => writer.Write((ulong)value) }, + { CustomFieldType.Int8, (writer, value) => writer.Write((sbyte)value) }, + { CustomFieldType.Int16, (writer, value) => writer.Write((short)value) }, + { CustomFieldType.Int32, (writer, value) => writer.Write((int)value) }, + { CustomFieldType.Int64, (writer, value) => writer.Write((long)value) }, + { CustomFieldType.DateTime, (writer, value) => writer.Write(((DateTime)value).Ticks) }, + { CustomFieldType.Single, (writer, value) => writer.Write((float)value) }, + { CustomFieldType.Double, (writer, value) => writer.Write((double)value) }, + }; } } \ No newline at end of file diff --git a/CollectionManagerExtensionsDll/Modules/API/osustats/OsuStatsApi.cs b/CollectionManagerExtensionsDll/Modules/API/osustats/OsuStatsApi.cs index 6c6de10..56d30e1 100644 --- a/CollectionManagerExtensionsDll/Modules/API/osustats/OsuStatsApi.cs +++ b/CollectionManagerExtensionsDll/Modules/API/osustats/OsuStatsApi.cs @@ -182,7 +182,7 @@ public async Task> SaveCollection(ICollection collect using (var memoryStream = new MemoryStream()) { collectionHandler.WriteOsdb(new Collections { collection }, memoryStream, - collection.LastEditorUsername ?? "", true); + collection.LastEditorUsername ?? ""); memoryStream.Position = 0; diff --git a/CollectionManagerExtensionsDll/Modules/BeatmapFilter/BeatmapFilter.cs b/CollectionManagerExtensionsDll/Modules/BeatmapFilter/BeatmapFilter.cs index ce5ed07..d50cc33 100644 --- a/CollectionManagerExtensionsDll/Modules/BeatmapFilter/BeatmapFilter.cs +++ b/CollectionManagerExtensionsDll/Modules/BeatmapFilter/BeatmapFilter.cs @@ -334,7 +334,13 @@ private bool isWordMatch(BeatmapExtension b, string word) { return isWordMatch((Beatmap)b, word) || (b.UserComment != null && - b.UserComment.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0); + b.UserComment.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0) || + isWordMatchInCustomField(b, word); + } + + private bool isWordMatchInCustomField(BeatmapExtension b, string word) + { + return b.GetStringCustomFieldValues().Any(stringValue => stringValue.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0); } private bool isArtistMatch(Beatmap b, string word) diff --git a/Common/Interfaces/Controls/IBeatmapListingView.cs b/Common/Interfaces/Controls/IBeatmapListingView.cs index 58bb16e..4dd3d5f 100644 --- a/Common/Interfaces/Controls/IBeatmapListingView.cs +++ b/Common/Interfaces/Controls/IBeatmapListingView.cs @@ -29,5 +29,7 @@ public interface IBeatmapListingView void FilteringFinished(); void ClearSelection(); void SelectNextOrFirst(); + void ClearCustomFieldDefinitions(); + void SetCustomFieldDefinitions(IEnumerable customFieldDefinitions); } } \ No newline at end of file diff --git a/GuiComponents/Controls/BeatmapListingView.cs b/GuiComponents/Controls/BeatmapListingView.cs index 06d2955..3da0d5d 100644 --- a/GuiComponents/Controls/BeatmapListingView.cs +++ b/GuiComponents/Controls/BeatmapListingView.cs @@ -110,9 +110,18 @@ private void UpdateResultsCount() label_resultsCount.Text = string.Format("{0} {1}", count, count == 1 ? "map" : "maps"); } public static DateTime d = new DateTime(2006, 1, 1); + private static readonly AspectToStringConverterDelegate GradeConverter = cellValue => + { + if (cellValue == null || (OsuGrade)cellValue == CollectionManager.Enums.OsuGrade.Null) + return ""; + + return cellValue.ToString(); + }; + private const string NumberAspectFormat = "{0:0.##}"; private Mods _currentMods = Mods.Nm; private PlayMode _currentPlayMode = PlayMode.Osu; private DifficultyCalculator _difficultyCalculator = new DifficultyCalculator(); + private List _customFieldColumns = new List(); private void InitListView() { //listview @@ -125,11 +134,10 @@ private void InitListView() ListViewBeatmaps.UseNotifyPropertyChanged = true; ListViewBeatmaps.ShowItemCountOnGroups = true; ListViewBeatmaps.CellEditActivation = ObjectListView.CellEditActivateMode.DoubleClick; - var format = "{0:0.##}"; - column_ar.AspectToStringFormat = format; - column_cs.AspectToStringFormat = format; - column_od.AspectToStringFormat = format; - column_hp.AspectToStringFormat = format; + column_ar.AspectToStringFormat = NumberAspectFormat; + column_cs.AspectToStringFormat = NumberAspectFormat; + column_od.AspectToStringFormat = NumberAspectFormat; + column_hp.AspectToStringFormat = NumberAspectFormat; column_stars.AspectGetter = rowObject => { @@ -186,11 +194,7 @@ private void InitListView() } return null; }; - column_bpm.AspectToStringConverter = delegate (object cellValue) - { - if (cellValue == null) return string.Empty; - return $"{cellValue:0.##}"; - }; + column_bpm.AspectToStringFormat = NumberAspectFormat; column_state.AspectGetter = rowObject => { @@ -202,17 +206,10 @@ private void InitListView() }; - var gradeConverter = new AspectToStringConverterDelegate(cellValue => - { - if (cellValue == null || (OsuGrade)cellValue == CollectionManager.Enums.OsuGrade.Null) - return ""; - - return cellValue.ToString(); - }); - OsuGrade.AspectToStringConverter = gradeConverter; - TaikoGrade.AspectToStringConverter = gradeConverter; - CatchGrade.AspectToStringConverter = gradeConverter; - ManiaGrade.AspectToStringConverter = gradeConverter; + OsuGrade.AspectToStringConverter = GradeConverter; + TaikoGrade.AspectToStringConverter = GradeConverter; + CatchGrade.AspectToStringConverter = GradeConverter; + ManiaGrade.AspectToStringConverter = GradeConverter; var dropsink = new RearrangingDropSink(); dropsink.CanDropBetween = false; @@ -224,6 +221,44 @@ private void InitListView() } + public void ClearCustomFieldDefinitions() + { + foreach(var col in _customFieldColumns) + { + ListViewBeatmaps.AllColumns.Remove(col); + } + _customFieldColumns.Clear(); + ListViewBeatmaps.RebuildColumns(); + } + + public void SetCustomFieldDefinitions(IEnumerable customFieldDefinitions) + { + foreach(var customFieldDef in customFieldDefinitions) + { + var col = new OLVColumn + { + IsEditable = false, + Text = customFieldDef.DisplayText, + AspectGetter = rowObject => rowObject is BeatmapExtension beatmap ? beatmap.GetCustomFieldValue(customFieldDef.Key) : null + }; + + switch (customFieldDef.Type) + { + case CustomFieldType.Grade: + col.AspectToStringConverter = GradeConverter; + break; + case (>=CustomFieldType.UInt8 and <= CustomFieldType.Int64) or CustomFieldType.Single or CustomFieldType.Double: + col.AspectToStringFormat = NumberAspectFormat; + break; + } + + _customFieldColumns.Add(col); + ListViewBeatmaps.AllColumns.Add(col); + } + + ListViewBeatmaps.RebuildColumns(); + } + private void DropsinkOnModelDropped(object sender, ModelDropEventArgs modelDropEventArgs) { modelDropEventArgs.Handled = true; @@ -313,6 +348,4 @@ private void ListViewBeatmaps_KeyUp(object sender, KeyEventArgs e) } } } - - }