diff --git a/TVRename/Forms/ListViewActionItemSorter.cs b/TVRename/Forms/ListViewActionItemSorter.cs index 763617df..58bd7afe 100644 --- a/TVRename/Forms/ListViewActionItemSorter.cs +++ b/TVRename/Forms/ListViewActionItemSorter.cs @@ -1,6 +1,7 @@ using BrightIdeasSoftware; using System.Collections; using System.Collections.Generic; +using System.Windows.Forms; namespace TVRename; @@ -48,6 +49,150 @@ public int Compare(OLVListItem? x, OLVListItem? y) return xIsNull ? -1 : 1; } - return new ActionItemSorter().Compare(x1, y1); + return Polarity() * Sorter.Compare(x1, y1); + } + + /// + /// Class constructor. Initializes various elements + /// + public ListViewActionItemSorter() + { + // Initialize the column to '0' + SortColumn = 0; + + // Initialize the sort order to 'none' + Order = SortOrder.None; + + // Initialize the CaseInsensitiveComparer object + Sorter = new DefaultActionItemSorter(); + } + + private int Polarity() + { + return Order switch + { + // Calculate correct return value based on object comparison + SortOrder.Ascending => + // Ascending sort is selected, return normal result of compare operation + 1, + SortOrder.Descending => + // Descending sort is selected, return negative result of compare operation + -1, + SortOrder.None => + // Return '0' to indicate they are equal + 0, + _ => 0 + }; + } + + /// + /// Gets or sets the number of the column to which to apply the sorting operation (Defaults to '0'). + /// + public int SortColumn { set; get; } + + /// + /// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending'). + /// + public SortOrder Order { set; get; } + + /// + /// Case insensitive comparer object + /// + public ActionItemSorter Sorter { get; set; } + + public void ClickedOn(int col, ActionItemSorter sorter) + { + Sorter = sorter; + + // Determine if clicked column is already the column that is being sorted. + if (col == SortColumn) + { + // Reverse the current sort direction for this column. + Order = Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending; + } + else + { + // Set the column number that is to be sorted; default to ascending. + SortColumn = col; + Order = SortOrder.Ascending; + } + } +} + +internal class OlvActionGroupComparer : IComparer +{ + /// + /// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending'). + /// + public SortOrder Order { set; get; } + + /// + /// Case insensitive comparer object + /// + public ActionItemSorter Sorter { get; set; } + + /// + /// Class constructor. Initializes various elements + /// + public OlvActionGroupComparer(ActionItemSorter sorter, SortOrder order) + { + // Initialize the sort order to 'none' + Order = order; + + // Initialize the CaseInsensitiveComparer object + Sorter = sorter; + } + + /// + /// Compare two rows + /// + /// row1 + /// row2 + /// An ordering indication: -1, 0, 1 + public int Compare(object? x, object? y) => Compare(x as OLVListItem, y as OLVListItem); + + /// + /// Compare two rows + /// + /// row1 + /// row2 + /// An ordering indication: -1, 0, 1 + public int Compare(OLVListItem? x, OLVListItem? y) + { + Item? x1 = x?.RowObject as Item; + Item? y1 = y?.RowObject as Item; + + // Handle nulls. Null values come last + bool xIsNull = x1 == null; + bool yIsNull = y1 == null; + if (xIsNull || yIsNull) + { + if (xIsNull && yIsNull) + { + return 0; + } + + return xIsNull ? -1 : 1; + } + + return Polarity() * Sorter.Compare(x1, y1); + } + + private int Polarity() + { + return Order switch + { + // Calculate correct return value based on object comparison + SortOrder.Ascending => + // Ascending sort is selected, return normal result of compare operation + 1, + SortOrder.Descending => + // Descending sort is selected, return negative result of compare operation + -1, + SortOrder.None => + // Return '0' to indicate they are equal + 0, + _ => 0 + }; } } diff --git a/TVRename/Forms/SeasonGroupComparer.cs b/TVRename/Forms/SeasonGroupComparer.cs new file mode 100644 index 00000000..42776cc7 --- /dev/null +++ b/TVRename/Forms/SeasonGroupComparer.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; +using BrightIdeasSoftware; + +namespace TVRename.Forms; + +/// +/// This comparer sort list view specifically for sesaons so that they appear in the season order +/// OLVGroups have a "SortValue" property, +/// which is used if present. Otherwise, the titles of the groups will be compared. +/// +public class SeasonGroupComparer : IComparer +{ + /// + /// Create a group comparer + /// + /// The ordering for column values + public SeasonGroupComparer(SortOrder order) + { + sortOrder = order; + } + + /// + /// Compare the two groups. OLVGroups have a "SortValue" property, + /// which is used if present. Otherwise, the titles of the groups will be compared. + /// + /// group1 + /// group2 + /// An ordering indication: -1, 0, 1 + public int Compare(OLVGroup? x, OLVGroup? y) + { + if (x is null || y is null) + { + return 0; + } + + // If we can compare the sort values, do that. + // Otherwise do a case insensitive compare on the group header. + int result; + if (x.Items.Any() && y.Items.Any()) + { + result = CompareValue(x).CompareTo(CompareValue(y)); + } + else if (x.SortValue != null && y.SortValue != null) + { + result = x.SortValue.CompareTo(y.SortValue); + } + else + { + result = string.Compare(x.Header, y.Header, StringComparison.CurrentCultureIgnoreCase); + } + + if (sortOrder == SortOrder.Descending) + { + return 0 - result; + } + + return result; + } + + private static int CompareValue(OLVGroup x) => ((Item)x.Items.First().RowObject).SeasonNumberAsInt ?? 0; + + private readonly SortOrder sortOrder; +} diff --git a/TVRename/Forms/Tools/Recommendations/RecommendationView.cs b/TVRename/Forms/Tools/Recommendations/RecommendationView.cs index 1f40a515..af6f1b8f 100644 --- a/TVRename/Forms/Tools/Recommendations/RecommendationView.cs +++ b/TVRename/Forms/Tools/Recommendations/RecommendationView.cs @@ -276,11 +276,11 @@ private void lvRecommendations_ItemSelectionChanged(object sender, ListViewItemS } if (rr.Movie != null) { - UI.SetHtmlBody(chrRecommendationPreview, rr.Movie.GetMovieHtmlOverview(rr)); + chrRecommendationPreview.SetHtmlBody(rr.Movie.GetMovieHtmlOverview(rr)); } else if (rr.Series != null) { - UI.SetHtmlBody(chrRecommendationPreview, rr.Series.GetShowHtmlOverview(rr)); + chrRecommendationPreview.SetHtmlBody(rr.Series.GetShowHtmlOverview(rr)); } } private void this_FormClosing(object sender, FormClosingEventArgs e) diff --git a/TVRename/Forms/Tools/YTSRecomendations/YtsRecommendationView.cs b/TVRename/Forms/Tools/YTSRecomendations/YtsRecommendationView.cs index 8d3867e0..9a40119f 100644 --- a/TVRename/Forms/Tools/YTSRecomendations/YtsRecommendationView.cs +++ b/TVRename/Forms/Tools/YTSRecomendations/YtsRecommendationView.cs @@ -248,8 +248,7 @@ private void lvRecommendations_ItemSelectionChanged(object sender, ListViewItemS { if (e.Item is BrightIdeasSoftware.OLVListItem { RowObject: YtsRecommendationRow rr }) { - UI.SetHtmlBody(chrRecommendationPreview, - rr.Movie != null + chrRecommendationPreview.SetHtmlBody(rr.Movie != null ? rr.Movie.GetMovieHtmlOverview(false) : rr.YtsMovie.GetMovieHtmlOverview()); } diff --git a/TVRename/Forms/Tools/YTSRecomendations/YtsViewerView.cs b/TVRename/Forms/Tools/YTSRecomendations/YtsViewerView.cs index fce4e0c6..536b8448 100644 --- a/TVRename/Forms/Tools/YTSRecomendations/YtsViewerView.cs +++ b/TVRename/Forms/Tools/YTSRecomendations/YtsViewerView.cs @@ -203,8 +203,7 @@ private void lvRecommendations_ItemSelectionChanged(object sender, ListViewItemS object? rowObject = (e.Item as BrightIdeasSoftware.OLVListItem)?.RowObject; if (rowObject is YtsViewerRow rr) { - UI.SetHtmlBody(chrRecommendationPreview, - rr.Movie != null + chrRecommendationPreview.SetHtmlBody(rr.Movie != null ? rr.Movie.GetMovieHtmlOverview(false) : rr.YtsMovie.GetMovieHtmlOverview()); } diff --git a/TVRename/Forms/UI.Designer.cs b/TVRename/Forms/UI.Designer.cs index b0160366..6b3c5bcf 100644 --- a/TVRename/Forms/UI.Designer.cs +++ b/TVRename/Forms/UI.Designer.cs @@ -1451,12 +1451,10 @@ public void InitializeComponent() olvAction.UseFiltering = true; olvAction.UseNotifyPropertyChanged = true; olvAction.View = View.Details; - olvAction.BeforeSorting += olvAction_BeforeSorting; olvAction.BeforeCreatingGroups += olvAction_BeforeCreatingGroups; olvAction.CanDrop += OlvAction_CanDrop; olvAction.Dropped += OlvAction_Dropped; olvAction.FormatRow += olv1_FormatRow; - olvAction.ColumnClick += olvAction_ColumnClick; olvAction.ItemChecked += lvAction_ItemChecked; olvAction.SelectedIndexChanged += lvAction_SelectedIndexChanged; olvAction.KeyDown += lvAction_KeyDown; diff --git a/TVRename/Forms/UI.cs b/TVRename/Forms/UI.cs index 3aae4d53..f214d8e3 100644 --- a/TVRename/Forms/UI.cs +++ b/TVRename/Forms/UI.cs @@ -75,8 +75,7 @@ public partial class UI : Form, IDialogParent private MovieConfiguration? switchToWhenOpenMyMovies; private readonly ListViewColumnSorter lvwScheduleColumnSorter; - //private readonly ListViewColumnSorter lvwActionColumnSorter; - + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private bool IsBusy => busy != 0; @@ -125,7 +124,7 @@ public UI(TVDoc doc, TVRenameSplash splash, bool showUi) lvwScheduleColumnSorter = new ListViewColumnSorter(new DateSorterWtw(3)); lvWhenToWatch.ListViewItemSorter = lvwScheduleColumnSorter; - //lvwActionColumnSorter = new ListViewColumnSorter(new NumberAsTextSorter(1)); + //lvwActionColumnSorter = new ListViewActionItemSorter(); if (mDoc.Args.Hide || !showUi) { @@ -300,6 +299,8 @@ private void ShowChild(Form childForm) } } + #region olvAction Methods + private void SetupObjectListForScanResults() { olvAction.SetObjects(mDoc.TheActionList); @@ -312,6 +313,7 @@ private void SetupObjectListForScanResults() olvDate.GroupKeyGetter = GroupDateKeyDelegate; olvDate.GroupKeyToTitleConverter = GroupDateTitleDelegate; + olvDate.DataType = typeof(DateTime); olvSeason.GroupKeyGetter = GroupSeasonKeyDelegate; @@ -320,10 +322,140 @@ private void SetupObjectListForScanResults() SimpleDropSink currActionDropSink = (SimpleDropSink)olvAction.DropSink; currActionDropSink.FeedbackColor = Color.LightGray; - olvDate.DataType = typeof(DateTime); olvAction.SortGroupItemsByPrimaryColumn = false; + + olvAction.CustomSorter = delegate(OLVColumn column, SortOrder order) + { + olvAction.ListViewItemSorter = new ColumnComparer( + MapToSortColumn(column), order); + }; + olvAction.ShowSortIndicator(); + } + private void olvAction_BeforeCreatingGroups(object sender, CreateGroupsEventArgs e) + { + e.Parameters.ItemComparer = new OlvActionGroupComparer(MapColumnToSorter(e.Parameters.PrimarySort), e.Parameters.PrimarySortOrder); + + if (e.Parameters.PrimarySort == olvEpisode || e.Parameters.PrimarySort == olvSeason) + { + e.Parameters.GroupComparer = new SeasonGroupComparer(e.Parameters.GroupByOrder); + } + } + + private ActionItemSorter MapColumnToSorter(OLVColumn column) + { + if (column == olvDate) + { + return new ActionItemDateSorter(); + } + if (column == olvShowColumn) + { + return new ActionItemNameSorter(); + } + if (column == olvEpisode) + { + return new ActionItemEpisodeSorter(); + } + if (column == olvSeason) + { + return new ActionItemSeasonSorter(); + } + if (column == olvErrors) + { + return new ActionItemErrorsSorter(); + } + if (column == olvFolder) + { + return new ActionItemFolderSorter(); + } + if (column == olvFilename) + { + return new ActionItemFilenameSorter(); + } + if (column == olvSource) + { + return new ActionItemSourceSorter(); + } + if (column == olvType) + { + return new DefaultActionItemSorter(); + } + + return new DefaultActionItemSorter(); + } + + private void DefaultOlvView() + { + olvAction.BeginUpdate(); + olvAction.ShowGroups = true; + olvAction.AlwaysGroupByColumn = null; + olvAction.Sort(olvType, SortOrder.Ascending); + olvAction.BuildGroups(olvType, SortOrder.Ascending, olvShowColumn, SortOrder.Ascending, olvSeason, SortOrder.Ascending); + olvAction.ResetColumnFiltering(); + olvAction.EndUpdate(); + } + + private void OlvAction_Dropped(object sender, OlvDropEventArgs e) + { + // Get a list of filenames being dragged + string[] files = (string[])((DataObject)e.DataObject).GetData(DataFormats.FileDrop, false); + + // Establish item in list being dragged to, and exit if no item matched + // Check at least one file was being dragged, and that dragged-to item is a "Missing Item" item. + if (files.Length <= 0 || e.DropTargetItem.RowObject is not ItemMissing mi) + { + return; + } + + // Only want the first file if multiple files were dragged across. + ManuallyAddFileForItem(mi, files[0]); } + private void OlvAction_CanDrop(object sender, OlvDropEventArgs e) + { + if (e.DropSink?.DropTargetItem?.RowObject is not Item item) + { + e.Effect = DragDropEffects.None; + } + else + { + if (item is ItemMissing) + { + if (((DataObject)e.DataObject).GetDataPresent(DataFormats.FileDrop)) + { + e.Effect = DragDropEffects.All; + } + else + { + e.Effect = DragDropEffects.None; + e.InfoMessage = "Can only drag files onto a missing episode"; + } + } + else + { + e.Effect = DragDropEffects.None; + e.InfoMessage = "Can only drag onto a missing episode"; + } + } + } + + private OLVColumn MapToSortColumn(OLVColumn source) + { + if (source == olvDate) + { + return new OLVColumn("RawDate", "AirDate"); + } + if (source == olvEpisode) + { + return new OLVColumn("RawEp", "EpisodeString"); + } + if (source == olvSeason) + { + return new OLVColumn("RawSe", "SeasonNumberAsInt"); + } + + return source; + } + private static object GroupFolderTitleDelegate(object rowObject) { Item? ep = (Item?)rowObject; @@ -525,12 +657,14 @@ private static string ConvertShowNameDelegate(object x) private void olv1_FormatRow(object sender, FormatRowEventArgs e) { - if (e.Model is Action a && a.Outcome.Error) + if (e.Model is Action { Outcome.Error: true }) { e.Item.BackColor = UiHelpers.WarningColor(); } } + #endregion + private void ReceiveArgs(string[] args) { // Send command-line arguments to already running instance @@ -575,17 +709,17 @@ private static void UpdateSplashStatus(TVRenameSplash splashScreen, string text, private void ClearInfoWindows(string defaultText) { - SetHtmlBody(chrImages, ShowHtmlHelper.CreateOldPage(defaultText)); - SetHtmlBody(chrInformation, ShowHtmlHelper.CreateOldPage(defaultText)); - SetHtmlBody(chrSummary, ShowHtmlHelper.CreateOldPage(defaultText)); - SetHtmlBody(chrTvTrailer, ShowHtmlHelper.CreateOldPage(defaultText)); + chrImages.SetSimpleHtmlBody(defaultText); + chrInformation.SetSimpleHtmlBody(defaultText); + chrSummary.SetSimpleHtmlBody(defaultText); + chrTvTrailer.SetSimpleHtmlBody(defaultText); } private void ClearMovieInfoWindows(string defaultText) { - SetHtmlBody(chrMovieImages, ShowHtmlHelper.CreateOldPage(defaultText)); - SetHtmlBody(chrMovieInformation, ShowHtmlHelper.CreateOldPage(defaultText)); - SetHtmlBody(chrMovieTrailer, ShowHtmlHelper.CreateOldPage(defaultText)); + chrMovieImages.SetSimpleHtmlBody(defaultText); + chrMovieInformation.SetSimpleHtmlBody(defaultText); + chrMovieTrailer.SetSimpleHtmlBody(defaultText); } private void MoreBusy() @@ -929,6 +1063,8 @@ private void flushCacheToolStripMenuItem_Click(object sender, EventArgs e) } } + #region Save and Load window size XML + private bool LoadWidths(XElement xml) { string? forwho = xml.Attribute("For")?.Value; @@ -1132,6 +1268,8 @@ private void WriteColWidthsXml(string thingName, XmlWriter writer) writer.WriteEndElement(); // ColumnWidths } + #endregion + private void UI_FormClosing(object sender, FormClosingEventArgs e) { try @@ -1316,10 +1454,10 @@ private void ShowQuickStartGuide() { try { - SetHtmlEmbed(chrInformation, QuickStartGuide()); - SetHtmlEmbed(chrImages, QuickStartGuide()); - SetHtmlEmbed(chrSummary, QuickStartGuide()); - SetHtmlEmbed(chrTvTrailer, QuickStartGuide()); + chrInformation.SetHtmlEmbed(QuickStartGuide()); + chrImages.SetHtmlEmbed(QuickStartGuide()); + chrSummary.SetHtmlEmbed(QuickStartGuide()); + chrTvTrailer.SetHtmlEmbed( QuickStartGuide()); } catch (COMException ex) { @@ -1445,27 +1583,27 @@ private void FillEpGuideHtml(ShowConfiguration? si, int snum) { if (snum >= 0 && si.AppropriateSeasons().TryGetValue( snum, out ProcessedSeason? s)) { - SetHtmlBody(chrInformation, ShowHtmlHelper.CreateOldPage(si.GetSeasonHtmlOverviewOffline(s))); - SetHtmlBody(chrImages, ShowHtmlHelper.CreateOldPage(si.GetSeasonImagesHtmlOverview(s))); + chrInformation.SetSimpleHtmlBody(si.GetSeasonHtmlOverviewOffline(s)); + chrImages.SetSimpleHtmlBody(si.GetSeasonImagesHtmlOverview(s)); } else { // no epnum specified, just show an overview - SetHtmlBody(chrInformation, ShowHtmlHelper.CreateOldPage(si.GetShowHtmlOverviewOffline())); - SetHtmlBody(chrImages, ShowHtmlHelper.CreateOldPage(si.GetShowImagesHtmlOverview())); + chrInformation.SetSimpleHtmlBody(si.GetShowHtmlOverviewOffline()); + chrImages.SetSimpleHtmlBody(si.GetShowImagesHtmlOverview()); } - SetHtmlBody(chrSummary, ShowHtmlHelper.CreateOldPage("Not available offline")); - SetHtmlBody(chrTvTrailer, ShowHtmlHelper.CreateOldPage("Not available offline")); + chrSummary.SetSimpleHtmlBody("Not available offline"); + chrTvTrailer.SetSimpleHtmlBody("Not available offline"); return; } if (snum >= 0 && si.AppropriateSeasons().TryGetValue(snum, out ProcessedSeason? se)) { - SetHtmlBody(chrImages, se.GetSeasonImagesOverview()); - SetHtmlBody(chrInformation, si.GetSeasonHtmlOverview(se, false)); - SetHtmlBody(chrSummary, si.GetSeasonSummaryHtmlOverview(se, false)); - UpdateTvTrailer(si); + chrImages.SetHtmlBody(se.GetSeasonImagesOverview()); + chrInformation.SetHtmlBody(si.GetSeasonHtmlOverview(se, false)); + chrSummary.SetHtmlBody(si.GetSeasonSummaryHtmlOverview(se, false)); + chrTvTrailer.UpdateTvTrailer(si); ResetRunBackGroundWorker(bwSeasonHTMLGenerator, se); ResetRunBackGroundWorker(bwSeasonSummaryHTMLGenerator, se); @@ -1473,10 +1611,10 @@ private void FillEpGuideHtml(ShowConfiguration? si, int snum) else { // no epnum specified, just show an overview - SetHtmlBody(chrImages, si.GetShowImagesOverview()); - SetHtmlBody(chrInformation, si.GetShowHtmlOverview(false)); - SetHtmlBody(chrSummary, si.GetShowSummaryHtmlOverview(false)); - UpdateTvTrailer(si); + chrImages.SetHtmlBody(si.GetShowImagesOverview()); + chrInformation.SetHtmlBody(si.GetShowHtmlOverview(false)); + chrSummary.SetHtmlBody(si.GetShowSummaryHtmlOverview(false)); + chrTvTrailer.UpdateTvTrailer(si); ResetRunBackGroundWorker(bwShowHTMLGenerator, si); ResetRunBackGroundWorker(bwShowSummaryHTMLGenerator, si); @@ -1497,70 +1635,6 @@ private static void ResetRunBackGroundWorker(BackgroundWorker worker, object s) } } - private void UpdateTvTrailer(ShowConfiguration? si) - { - if (si?.CachedShow?.TrailerUrl?.HasValue() ?? false) - { - // ReSharper disable once AssignNullToNotNullAttribute - SetHtmlEmbed(chrTvTrailer, ShowHtmlHelper.YoutubeTrailer(si.CachedShow!)); - } - else - { - SetHtmlBody(chrTvTrailer, ShowHtmlHelper.CreateOldPage("Not available for this TV show")); - } - } - - private static void SetWeb(ChromiumWebBrowser web, System.Action a) - { - web.Visible = true; - if (web.IsDisposed) - { - return; - } - - if (!web.IsBrowserInitialized) - { - web.IsBrowserInitializedChanged += (_, _) => - { - web.BeginInvoke((MethodInvoker)delegate { SetWeb(web, a); }); - }; - } - - try - { - a(); - } - catch (COMException ex) - { - //Fail gracefully - no RHS episode guide is not too big of a problem. - Logger.Warn(ex, "Could not update UI for the show/cachedSeries/movie information pane"); - } - catch (Exception ex) - { - //Fail gracefully - no RHS episode guide is not too big of a problem. - Logger.Error(ex); - } - web.Update(); - } - - public static void SetHtmlBody(ChromiumWebBrowser web, string body) - { - SetWeb(web, - () => - { - web.Load("data:text/html;base64," + Convert.ToBase64String(Encoding.UTF8.GetBytes(body))); - }); - } - - private static void SetHtmlEmbed(ChromiumWebBrowser web, string? link) - { - SetWeb(web, - () => - { - web.Load(link ?? string.Empty); - }); - } - public static void TvSourceFor(ProcessedEpisode? e) { if (e?.WebsiteUrl != null && e.WebsiteUrl.HasValue()) @@ -1683,8 +1757,7 @@ private ListViewItem GenerateLvi(DirFilesCache dfc, ProcessedEpisode pe) private void lvWhenToWatch_ColumnClick(object sender, ColumnClickEventArgs e) { - int col = e.Column; - SortSchedule(col); + SortSchedule(e.Column); } private void SortSchedule(int col) @@ -3290,22 +3363,23 @@ private void FillMovieGuideHtml(MovieConfiguration? si) if (TVSettings.Instance.OfflineMode || TVSettings.Instance.ShowBasicShowDetails) { - SetHtmlBody(chrMovieInformation, ShowHtmlHelper.CreateOldPage(si.GetMovieHtmlOverviewOffline())); - SetHtmlBody(chrMovieImages, ShowHtmlHelper.CreateOldPage(si.GetMovieImagesHtmlOverview())); - SetHtmlBody(chrMovieTrailer, ShowHtmlHelper.CreateOldPage("Not available offline")); + chrMovieInformation.SetSimpleHtmlBody(si.GetMovieHtmlOverviewOffline()); + chrMovieImages.SetSimpleHtmlBody(si.GetMovieImagesHtmlOverview()); + chrMovieTrailer.SetSimpleHtmlBody("Not available offline"); return; } - //SetHtmlBody(chrMovieImages, ShowHtmlHelper.CreateOldPage(si.GetMovieImagesHtmlOverview())); - SetHtmlBody(chrMovieImages, si.GetMovieImagesOverview()); - SetHtmlBody(chrMovieInformation, si.GetMovieHtmlOverview(false)); + + chrMovieImages.SetHtmlBody(si.GetMovieImagesOverview()); + chrMovieInformation.SetHtmlBody(si.GetMovieHtmlOverview(false)); + if (si.CachedMovie?.TrailerUrl?.HasValue() ?? false) { // ReSharper disable once AssignNullToNotNullAttribute - SetHtmlEmbed(chrMovieTrailer, ShowHtmlHelper.YoutubeTrailer(si.CachedMovie)); + chrMovieTrailer.SetHtmlEmbed(ShowHtmlHelper.YoutubeTrailer(si.CachedMovie)); } else { - SetHtmlBody(chrMovieTrailer, ShowHtmlHelper.CreateOldPage("Not available for this Movie")); + chrMovieTrailer.SetSimpleHtmlBody("Not available for this Movie"); } ResetRunBackGroundWorker(bwMovieHTMLGenerator, si); @@ -3682,7 +3756,7 @@ private void AskUserAboutShowProblems(bool unattended) { string message = mDoc.ShowProblems.Count() > 1 ? $"Shows with Id {mDoc.ShowProblems.Select(exception => exception.Media.ToString()).ToCsv()} are not found on TVDB, TMDB and TVMaze. Please update them" - : $"Show with {StringFor(mDoc.ShowProblems.First().ShowIdProvider)} Id {mDoc.ShowProblems.First().Media.IdFor(mDoc.ShowProblems.First().Media.Provider)} is not found on {StringFor(mDoc.ShowProblems.First().ErrorProvider)}. Please Update"; + : $"Show with {mDoc.ShowProblems.First().ShowIdProvider.PrettyPrint()} Id {mDoc.ShowProblems.First().Media.IdFor(mDoc.ShowProblems.First().Media.Provider)} is not found on {(mDoc.ShowProblems.First().ErrorProvider.PrettyPrint())}. Please Update"; DialogResult result = MessageBox.Show(message, "Series/Show No Longer Found", MessageBoxButtons.OKCancel, MessageBoxIcon.Error); @@ -3714,7 +3788,7 @@ private void AskUserAboutShowProblems(bool unattended) { string message = mDoc.MovieProblems.Count() > 1 ? $"Movies with Id {string.Join(",", mDoc.MovieProblems.Select(exception => exception.Media.ToString()))} are not found on TVDB, TMDB and TVMaze. Please update them" - : $"Movie with {StringFor(mDoc.MovieProblems.First().ShowIdProvider)} Id {mDoc.MovieProblems.First().Media} is not found on {StringFor(mDoc.MovieProblems.First().ErrorProvider)}. Please Update"; + : $"Movie with {(mDoc.MovieProblems.First().ShowIdProvider.PrettyPrint())} Id {mDoc.MovieProblems.First().Media} is not found on {(mDoc.MovieProblems.First().ErrorProvider.PrettyPrint())}. Please Update"; DialogResult result = MessageBox.Show(message, "Movie No Longer Found", MessageBoxButtons.OKCancel, MessageBoxIcon.Error); @@ -3743,18 +3817,6 @@ private void AskUserAboutShowProblems(bool unattended) mDoc.ClearCacheUpdateProblems(); } - private static string StringFor(TVDoc.ProviderType i) - { - return i switch - { - TVDoc.ProviderType.TVmaze => "TV Maze", - TVDoc.ProviderType.TMDB => "TMDB", - TVDoc.ProviderType.TheTVDB => "The TVDB", - TVDoc.ProviderType.libraryDefault => throw new ArgumentOutOfRangeException(nameof(i), i, null), - _ => throw new ArgumentOutOfRangeException(nameof(i), i, null) - }; - } - private static object ActionImageGetter(object rowObject) { Item s = (Item)rowObject; @@ -4618,7 +4680,7 @@ private void UpdateWeb(object sender, RunWorkerCompletedEventArgs e) { if (UiHasContextFor(result.Argument)) { - SetHtmlBody(result.Web, result.Html); + result.Web.SetHtmlBody(result.Html); } } } @@ -4789,64 +4851,6 @@ private void BtnRevertView_Click(object sender, EventArgs e) DefaultOlvView(); } - private void DefaultOlvView() - { - olvAction.BeginUpdate(); - olvAction.ShowGroups = true; - olvAction.AlwaysGroupByColumn = null; - olvAction.CustomSorter = delegate { olvAction.ListViewItemSorter = new ListViewActionItemSorter(); }; - olvAction.Sort(olvType, SortOrder.Ascending); - olvAction.BuildGroups(olvType, SortOrder.Ascending, olvShowColumn, SortOrder.Ascending, olvSeason, SortOrder.Ascending); - //olvAction.Sort(); - //olvAction.BuildGroups(olvType,SortOrder.Ascending);//(olvType, SortOrder.Ascending,olvShowColumn,SortOrder.Ascending,olvSeason,SortOrder.Ascending); - olvAction.ResetColumnFiltering(); - olvAction.EndUpdate(); - } - - private void OlvAction_Dropped(object sender, OlvDropEventArgs e) - { - // Get a list of filenames being dragged - string[] files = (string[])((DataObject)e.DataObject).GetData(DataFormats.FileDrop, false); - - // Establish item in list being dragged to, and exit if no item matched - // Check at least one file was being dragged, and that dragged-to item is a "Missing Item" item. - if (files.Length <= 0 || e.DropTargetItem.RowObject is not ItemMissing mi) - { - return; - } - - // Only want the first file if multiple files were dragged across. - ManuallyAddFileForItem(mi, files[0]); - } - - private void OlvAction_CanDrop(object sender, OlvDropEventArgs e) - { - if (e.DropSink?.DropTargetItem?.RowObject is not Item item) - { - e.Effect = DragDropEffects.None; - } - else - { - if (item is ItemMissing) - { - if (((DataObject)e.DataObject).GetDataPresent(DataFormats.FileDrop)) - { - e.Effect = DragDropEffects.All; - } - else - { - e.Effect = DragDropEffects.None; - e.InfoMessage = "Can only drag files onto a missing episode"; - } - } - else - { - e.Effect = DragDropEffects.None; - e.InfoMessage = "Can only drag onto a missing episode"; - } - } - } - private void BwShowSummaryHTMLGenerator_DoWork(object sender, DoWorkEventArgs e) { Thread.CurrentThread.Name ??= "Show Summary HTML Creation Thread"; // Can only set it once @@ -4975,42 +4979,6 @@ private void tsbScheduleJackettSearch_Click(object sender, EventArgs e) } } - private void olvAction_BeforeCreatingGroups(object sender, CreateGroupsEventArgs e) - { - e.Parameters.ItemComparer = GetActionComparer(e.Parameters.GroupByColumn); - - if (e.Parameters.PrimarySort == olvDate) - { - e.Parameters.PrimarySort = new OLVColumn("RawDate",olvDate.AspectName); - } - - if (e.Parameters.PrimarySort == olvEpisode) - { - e.Parameters.PrimarySort = new OLVColumn("RawEp", olvEpisode.AspectName); - e.Parameters.GroupComparer = new SeasonGroupComparer(e.Parameters.GroupByOrder); - } - - if (e.Parameters.PrimarySort == olvSeason || e.Parameters.PrimarySort.AspectName == "SeasonNumberAsInt") - { - e.Parameters.PrimarySort = new OLVColumn("RawSe", "SeasonNumberAsInt"); - e.Parameters.GroupComparer = new SeasonGroupComparer(e.Parameters.GroupByOrder); - } - - e.Parameters.SecondarySort = new OLVColumn("key", "OrderKey"); - e.Parameters.SecondarySortOrder = SortOrder.Ascending; - } - - private static IComparer GetActionComparer(OLVColumn column) - { - return column.Index switch - { - 1 => new NumberAsTextActionComparer(column.Index), - 2 => new NumberAsTextActionComparer(column.Index), - 3 => new DateActionComparer(column.Index), - _ => new TextActionComparer(column.Index) - }; - } - public void ShowFgDownloadProgress(CacheUpdater cu, CancellationTokenSource cts) { if (!IsDisposed) @@ -5327,36 +5295,6 @@ private void PartialScan(PostScanActivity activity) FocusOnScanResults(); } - private void olvAction_ColumnClick(object sender, ColumnClickEventArgs e) - { - if (!olvAction.AllColumns[e.Column].Groupable && olvAction.AllColumns[e.Column].Sortable) - { - //TODO work out how to deal with this - } - } - - private void olvAction_BeforeSorting(object sender, BeforeSortingEventArgs e) - { - if (e.ColumnToSort == olvDate) - { - e.ColumnToSort = new OLVColumn("RawDate", "AirDate"); - } - - if (e.ColumnToSort == olvEpisode) - { - e.ColumnToSort = new OLVColumn("RawEp", "EpisodeString"); - } - - if (e.ColumnToSort == olvSeason) - { - e.ColumnToSort = new OLVColumn("RawSe", "SeasonNumberAsInt"); - } - - e.SecondaryColumnToSort = new OLVColumn("AbsoluteOrder", "OrderKey"); - e.SecondarySortOrder = SortOrder.Ascending; - olvAction.ShowSortIndicator(); - } - private void tVDBUPdateCheckerLogToolStripMenuItem_Click(object sender, EventArgs e) { TvdbUpdateChecker form = new(mDoc); @@ -5457,61 +5395,74 @@ private void removeShowsWithNoFoldersToolStripMenuItem_Click(object sender, Even } } -/// -/// This comparer sort list view specifically for sesaons so that they appear in the season order -/// OLVGroups have a "SortValue" property, -/// which is used if present. Otherwise, the titles of the groups will be compared. -/// -public class SeasonGroupComparer : IComparer +public static class TvWebExtensions { - /// - /// Create a group comparer - /// - /// The ordering for column values - public SeasonGroupComparer(SortOrder order) + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + public static void UpdateTvTrailer(this ChromiumWebBrowser web, ShowConfiguration? si) { - sortOrder = order; + if (si?.CachedShow?.TrailerUrl?.HasValue() ?? false) + { + // ReSharper disable once AssignNullToNotNullAttribute + SetHtmlEmbed(web, ShowHtmlHelper.YoutubeTrailer(si.CachedShow!)); + } + else + { + SetHtmlBody(web, ShowHtmlHelper.CreateOldPage("Not available for this TV show")); + } } - /// - /// Compare the two groups. OLVGroups have a "SortValue" property, - /// which is used if present. Otherwise, the titles of the groups will be compared. - /// - /// group1 - /// group2 - /// An ordering indication: -1, 0, 1 - public int Compare(OLVGroup? x, OLVGroup? y) + private static void SetWeb(this ChromiumWebBrowser web, System.Action a) { - if (x is null || y is null) + web.Visible = true; + if (web.IsDisposed) { - return 0; + return; } - // If we can compare the sort values, do that. - // Otherwise do a case insensitive compare on the group header. - int result; - if (x.Items.Any() && y.Items.Any()) + if (!web.IsBrowserInitialized) { - result = CompareValue(x).CompareTo(CompareValue(y)); + web.IsBrowserInitializedChanged += (_, _) => + { + web.BeginInvoke((MethodInvoker)delegate { SetWeb(web, a); }); + }; } - else if (x.SortValue != null && y.SortValue != null) + + try { - result = x.SortValue.CompareTo(y.SortValue); + a(); } - else + catch (COMException ex) { - result = string.Compare(x.Header, y.Header, StringComparison.CurrentCultureIgnoreCase); + //Fail gracefully - no RHS episode guide is not too big of a problem. + Logger.Warn(ex, "Could not update UI for the show/cachedSeries/movie information pane"); } - - if (sortOrder == SortOrder.Descending) + catch (Exception ex) { - return 0 - result; + //Fail gracefully - no RHS episode guide is not too big of a problem. + Logger.Error(ex); } - - return result; + web.Update(); } - private static int CompareValue(OLVGroup x) => ((Item)x.Items.First().RowObject).SeasonNumberAsInt ?? 0; + public static void SetSimpleHtmlBody(this ChromiumWebBrowser web, string message) + { + web.SetHtmlBody(ShowHtmlHelper.CreateOldPage(message)); + } + public static void SetHtmlBody(this ChromiumWebBrowser web, string body) + { + SetHtml(web,"data:text/html;base64," + Convert.ToBase64String(Encoding.UTF8.GetBytes(body))); + } + internal static void SetHtmlEmbed(this ChromiumWebBrowser web, string? link) + { + SetHtml(web,link ?? string.Empty); + } - private readonly SortOrder sortOrder; + private static void SetHtml(this ChromiumWebBrowser web, string value) + { + SetWeb(web, + () => + { + web.Load(value); + }); + } } diff --git a/TVRename/ItemsAndActions/ActionItemSorter.cs b/TVRename/ItemsAndActions/ActionItemSorter.cs index 80babfde..a0a93a74 100644 --- a/TVRename/ItemsAndActions/ActionItemSorter.cs +++ b/TVRename/ItemsAndActions/ActionItemSorter.cs @@ -10,10 +10,8 @@ namespace TVRename; -public class ActionItemSorter : System.Collections.Generic.IComparer +public abstract class ActionItemSorter : System.Collections.Generic.IComparer { - #region IComparer Members - public int Compare(Item? x, Item? y) { if (x is null) @@ -26,9 +24,18 @@ public int Compare(Item? x, Item? y) return 1; } - return TypeNumber(x) == TypeNumber(y) ? x.CompareTo(y) : TypeNumber(x) - TypeNumber(y); + return CompareItems(x,y); } + protected abstract int CompareItems(Item item, Item item1); +} +public class DefaultActionItemSorter:ActionItemSorter +{ + #region IComparer Members + + protected override int CompareItems(Item x, Item y) + => TypeNumber(x) == TypeNumber(y) ? x.CompareTo(y) : TypeNumber(x) - TypeNumber(y); + #endregion IComparer Members private static int TypeNumber(Item a) @@ -62,3 +69,43 @@ private static int TypeNumber(Item a) }; } } + +public abstract class ActionItemStringSorter : ActionItemSorter +{ + protected override int CompareItems(Item x, Item y) => string.Compare(GetString(x),GetString(y),StringComparison.CurrentCultureIgnoreCase); + + protected abstract string GetString(Item x); +} + +public class ActionItemNameSorter : ActionItemStringSorter +{ + protected override string GetString(Item x) => x.SeriesName; +} +public class ActionItemDateSorter : ActionItemSorter +{ + protected override int CompareItems(Item x, Item y) => DateTime.Compare(x.AirDate ?? DateTime.MinValue,y.AirDate ?? DateTime.MinValue); +} +public class ActionItemFilenameSorter : ActionItemStringSorter +{ + protected override string GetString(Item x) => x.DestinationFile ?? string.Empty; +} +public class ActionItemFolderSorter : ActionItemStringSorter +{ + protected override string GetString(Item x) => x.DestinationFolder ?? string.Empty; +} +public class ActionItemSourceSorter : ActionItemStringSorter +{ + protected override string GetString(Item x) => x.SourceDetails; +} +public class ActionItemErrorsSorter : ActionItemStringSorter +{ + protected override string GetString(Item x) =>x.ErrorText ?? string.Empty; +} +public class ActionItemSeasonSorter : ActionItemSorter +{ + protected override int CompareItems(Item x, Item y) => x.SeasonNumberAsInt ?? 0 - y.SeasonNumberAsInt ?? 0; +} +public class ActionItemEpisodeSorter : ActionItemSorter +{ + protected override int CompareItems(Item x, Item y) => x.EpisodeNumber ?? 0 - y.EpisodeNumber ?? 0 ; +} diff --git a/TVRename/ScanActivity/Finders/FileFinder.cs b/TVRename/ScanActivity/Finders/FileFinder.cs index b6f13e04..1882f126 100644 --- a/TVRename/ScanActivity/Finders/FileFinder.cs +++ b/TVRename/ScanActivity/Finders/FileFinder.cs @@ -513,7 +513,7 @@ protected static void ReorganiseToLeaveOriginals(ItemList newList) // ideally do that move within same filesystem // sort based on source file, and destination drive, putting last if destdrive == sourcedrive - newList.Sort(new ActionItemSorter()); + newList.Sort(new DefaultActionItemSorter()); // sort puts all the CopyMoveRenames together // then set the last of each source file to be a move diff --git a/TVRename/Sources/CacheHelper.cs b/TVRename/Sources/CacheHelper.cs index a8b0ed97..6d68fad0 100644 --- a/TVRename/Sources/CacheHelper.cs +++ b/TVRename/Sources/CacheHelper.cs @@ -291,14 +291,12 @@ public static Dictionary GetMoviesDictMatching(this T c { cache.Search(hint, showErrorMsgBox, MediaConfiguration.MediaType.movie, preferredLocale); - string showName = hint; - - if (string.IsNullOrEmpty(showName)) + if (string.IsNullOrEmpty(hint)) { return null; } - showName = showName.ToLower(); + string showName = hint.ToLower(); List matchingShows = cache.GetMoviesDictMatching(showName).Values.ToList(); diff --git a/TVRename/TVRename/TVDoc.cs b/TVRename/TVRename/TVDoc.cs index ba325143..b7bc311e 100644 --- a/TVRename/TVRename/TVDoc.cs +++ b/TVRename/TVRename/TVDoc.cs @@ -932,7 +932,7 @@ public void Scan(ScanSettings settings) } // sort Action list by type - TheActionList.Sort(new ActionItemSorter()); // was new ActionSorter() + TheActionList.Sort(new DefaultActionItemSorter()); // was new ActionSorter() Stats().FindAndOrganisesDone++; diff --git a/TVRename/Utility/Sorters/NumberAsTextSorter.cs b/TVRename/Utility/Sorters/NumberAsTextSorter.cs index b2a4a3ed..ab10de60 100644 --- a/TVRename/Utility/Sorters/NumberAsTextSorter.cs +++ b/TVRename/Utility/Sorters/NumberAsTextSorter.cs @@ -39,7 +39,7 @@ private int ParseAsInt(ListViewItem cellItem) } catch { - return 0; + return -1; } } }