OSDN Git Service

Font, Color, Brushインスタンスの管理をThemeManagerクラスに分離
[opentween/open-tween.git] / OpenTween / TweetDetailsView.cs
index 024b790..cc0fc08 100644 (file)
@@ -47,10 +47,12 @@ namespace OpenTween
 {
     public partial class TweetDetailsView : UserControl
     {
-        public TweenMain Owner { get; set; } = null!;
+        private TweenMain Owner
+            => this.owner ?? throw this.NotInitializedException();
 
         /// <summary>プロフィール画像のキャッシュ</summary>
-        public ImageCache IconCache { get; set; } = null!;
+        private ImageCache IconCache
+            => this.iconCache ?? throw this.NotInitializedException();
 
         /// <summary><see cref="PostClass"/> のダンプを表示するか</summary>
         public bool DumpPostClass { get; set; }
@@ -58,6 +60,12 @@ namespace OpenTween
         /// <summary>現在表示中の発言</summary>
         public PostClass? CurrentPost { get; private set; }
 
+        public ThemeManager Theme
+        {
+            get => this.themeManager ?? throw this.NotInitializedException();
+            set => this.themeManager = value;
+        }
+
         [DefaultValue(false)]
         public new bool TabStop
         {
@@ -69,7 +77,11 @@ namespace OpenTween
         public event EventHandler<TweetDetailsViewStatusChengedEventArgs>? StatusChanged;
 
         /// <summary><see cref="ContextMenuPostBrowser"/> 展開時の <see cref="PostBrowser"/>.StatusText を保持するフィールド</summary>
-        private string _postBrowserStatusText = "";
+        private string postBrowserStatusText = "";
+
+        private TweenMain? owner;
+        private ImageCache? iconCache;
+        private ThemeManager? themeManager;
 
         public TweetDetailsView()
         {
@@ -78,7 +90,8 @@ namespace OpenTween
             this.TabStop = false;
 
             // 発言詳細部の初期化
-            this.NameLabel.Text = "";
+            this.AuthorNameLinkLabel.Text = "";
+            this.RetweetedByLinkLabel.Text = "";
             this.DateTimeLabel.Text = "";
             this.SourceLinkLabel.Text = "";
 
@@ -86,6 +99,19 @@ namespace OpenTween
             this.PostBrowser.AllowWebBrowserDrop = false;  // COMException を回避するため、ActiveX の初期化が終わってから設定する
         }
 
+        public void Initialize(TweenMain owner, ImageCache iconCache, ThemeManager themeManager)
+        {
+            this.owner = owner;
+            this.iconCache = iconCache;
+            this.themeManager = themeManager;
+        }
+
+        private Exception NotInitializedException()
+            => new InvalidOperationException("Cannot call before initialization");
+
+        public void ClearPostBrowser()
+            => this.PostBrowser.DocumentText = this.Owner.CreateDetailHtml("");
+
         public async Task ShowPostDetails(PostClass post)
         {
             this.CurrentPost = post;
@@ -110,19 +136,31 @@ namespace OpenTween
                     nameText = "";
                 }
                 nameText += post.ScreenName + "/" + post.Nickname;
-                if (post.RetweetedId != null)
-                    nameText += " (RT:" + post.RetweetedBy + ")";
+                this.AuthorNameLinkLabel.Text = nameText;
 
-                this.NameLabel.Text = nameText;
+                if (post.RetweetedId != null)
+                {
+                    this.RetweetedByLinkLabel.Visible = true;
+                    this.RetweetedByLinkLabel.Text = $"(RT:{post.RetweetedBy})";
+                }
+                else
+                {
+                    this.RetweetedByLinkLabel.Visible = false;
+                    this.RetweetedByLinkLabel.Text = "";
+                }
 
                 var nameForeColor = SystemColors.ControlText;
-                if (post.IsOwl && (SettingManager.Common.OneWayLove || post.IsDm))
-                    nameForeColor = SettingManager.Local.ColorOWL;
+                if (post.IsOwl && (SettingManager.Instance.Common.OneWayLove || post.IsDm))
+                    nameForeColor = this.Theme.ColorOWL;
                 if (post.RetweetedId != null)
-                    nameForeColor = SettingManager.Local.ColorRetweet;
+                    nameForeColor = this.Theme.ColorRetweet;
                 if (post.IsFav)
-                    nameForeColor = SettingManager.Local.ColorFav;
-                this.NameLabel.ForeColor = nameForeColor;
+                    nameForeColor = this.Theme.ColorFav;
+
+                this.AuthorNameLinkLabel.LinkColor = nameForeColor;
+                this.AuthorNameLinkLabel.ActiveLinkColor = nameForeColor;
+                this.RetweetedByLinkLabel.LinkColor = nameForeColor;
+                this.RetweetedByLinkLabel.ActiveLinkColor = nameForeColor;
 
                 loadTasks.Add(this.SetUserPictureAsync(post.ImageUrl));
 
@@ -203,9 +241,9 @@ namespace OpenTween
             if (tags.Count > 0)
             {
                 if (forward)
-                    tags[0].ScrollTop += SettingManager.Local.FontDetail.Height;
+                    tags[0].ScrollTop += this.Theme.FontDetail.Height;
                 else
-                    tags[0].ScrollTop -= SettingManager.Local.FontDetail.Height;
+                    tags[0].ScrollTop -= this.Theme.FontDetail.Height;
             }
         }
 
@@ -218,9 +256,9 @@ namespace OpenTween
             if (tags.Count > 0)
             {
                 if (forward)
-                    tags[0].ScrollTop += this.PostBrowser.ClientRectangle.Height - SettingManager.Local.FontDetail.Height;
+                    tags[0].ScrollTop += this.PostBrowser.ClientRectangle.Height - this.Theme.FontDetail.Height;
                 else
-                    tags[0].ScrollTop -= this.PostBrowser.ClientRectangle.Height - SettingManager.Local.FontDetail.Height;
+                    tags[0].ScrollTop -= this.PostBrowser.ClientRectangle.Height - this.Theme.FontDetail.Height;
             }
         }
 
@@ -231,9 +269,9 @@ namespace OpenTween
                 .ToArray();
         }
 
-        private async Task SetUserPictureAsync(string imageUrl, bool force = false)
+        private async Task SetUserPictureAsync(string normalImageUrl, bool force = false)
         {
-            if (MyCommon.IsNullOrEmpty(imageUrl))
+            if (MyCommon.IsNullOrEmpty(normalImageUrl))
                 return;
 
             if (this.IconCache == null)
@@ -241,14 +279,31 @@ namespace OpenTween
 
             this.ClearUserPicture();
 
-            await this.UserPicture.SetImageFromTask(async () =>
+            var imageSize = Twitter.DecideProfileImageSize(this.UserPicture.Width);
+            var cachedImage = this.IconCache.TryGetLargerOrSameSizeFromCache(normalImageUrl, imageSize);
+            if (cachedImage != null)
             {
-                var image = await this.IconCache.DownloadImageAsync(imageUrl, force)
-                    .ConfigureAwait(false);
+                // 既にキャッシュされていればそれを表示して終了
+                this.UserPicture.Image = cachedImage.Clone();
+                return;
+            }
+
+            // 小さいサイズの画像がキャッシュにある場合は高解像度の画像が取得できるまでの間表示する
+            var fallbackImage = this.IconCache.TryGetLargerOrSameSizeFromCache(normalImageUrl, "mini");
+            if (fallbackImage != null)
+                this.UserPicture.Image = fallbackImage.Clone();
+
+            await this.UserPicture.SetImageFromTask(
+                async () =>
+                {
+                    var imageUrl = Twitter.CreateProfileImageUrl(normalImageUrl, imageSize);
+                    var image = await this.IconCache.DownloadImageAsync(imageUrl, force)
+                        .ConfigureAwait(false);
 
-                return await image.CloneAsync()
-                    .ConfigureAwait(false);
-            });
+                    return image.Clone();
+                },
+                useStatusImage: false
+            );
         }
 
         /// <summary>
@@ -372,7 +427,7 @@ namespace OpenTween
             {
                 var translatedText = await bing.TranslateAsync(str,
                     langFrom: null,
-                    langTo: SettingManager.Common.TranslateLanguage);
+                    langTo: SettingManager.Instance.Common.TranslateLanguage);
 
                 this.PostBrowser.DocumentText = this.Owner.CreateDetailHtml(translatedText);
             }
@@ -389,25 +444,25 @@ namespace OpenTween
         private async Task DoSearchToolStrip(string url)
         {
             // 発言詳細で「選択文字列で検索」(選択文字列取得)
-            var _selText = this.PostBrowser.GetSelectedText();
+            var selText = this.PostBrowser.GetSelectedText();
 
-            if (_selText != null)
+            if (selText != null)
             {
                 if (url == Properties.Resources.SearchItem4Url)
                 {
                     // 公式検索
-                    this.Owner.AddNewTabForSearch(_selText);
+                    this.Owner.AddNewTabForSearch(selText);
                     return;
                 }
 
-                var tmp = string.Format(url, Uri.EscapeDataString(_selText));
+                var tmp = string.Format(url, Uri.EscapeDataString(selText));
                 await MyCommon.OpenInBrowserAsync(this, tmp);
             }
         }
 
         private string? GetUserId()
         {
-            var m = Regex.Match(this._postBrowserStatusText, @"^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)(/status(es)?/[0-9]+)?$");
+            var m = Regex.Match(this.postBrowserStatusText, @"^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)(/status(es)?/[0-9]+)?$");
             if (m.Success && this.Owner.IsTwitterId(m.Result("${ScreenName}")))
                 return m.Result("${ScreenName}");
             else
@@ -420,26 +475,20 @@ namespace OpenTween
         private void TweetDetailsView_FontChanged(object sender, EventArgs e)
         {
             // OTBaseForm.GlobalFont による UI フォントの変更に対応
-            var origFont = this.NameLabel.Font;
-            this.NameLabel.Font = new Font(this.Font.Name, origFont.Size, origFont.Style);
+            var origFont = this.AuthorNameLinkLabel.Font;
+            this.AuthorNameLinkLabel.Font = new Font(this.Font.Name, origFont.Size, origFont.Style);
+            this.RetweetedByLinkLabel.Font = new Font(this.Font.Name, origFont.Size, origFont.Style);
         }
 
         #region TableLayoutPanel1
 
-        private async void UserPicture_DoubleClick(object sender, EventArgs e)
+        private async void UserPicture_Click(object sender, EventArgs e)
         {
-            if (this.CurrentPost == null)
-                return;
-
-            await MyCommon.OpenInBrowserAsync(this, MyCommon.TwitterUrl + this.CurrentPost.ScreenName);
+            var screenName = this.CurrentPost?.ScreenName;
+            if (screenName != null)
+                await this.Owner.ShowUserStatus(screenName, showInputDialog: false);
         }
 
-        private void UserPicture_MouseEnter(object sender, EventArgs e)
-            => this.UserPicture.Cursor = Cursors.Hand;
-
-        private void UserPicture_MouseLeave(object sender, EventArgs e)
-            => this.UserPicture.Cursor = Cursors.Default;
-
         private async void PostBrowser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
         {
             if (e.Url.AbsoluteUri != "about:blank")
@@ -465,8 +514,8 @@ namespace OpenTween
 
         private async void PostBrowser_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
         {
-            var KeyRes = this.Owner.CommonKeyDown(e.KeyData, FocusedControl.PostBrowser, out var asyncTask);
-            if (KeyRes)
+            var keyRes = this.Owner.CommonKeyDown(e.KeyData, FocusedControl.PostBrowser, out var asyncTask);
+            if (keyRes)
             {
                 e.IsInputKey = true;
             }
@@ -691,11 +740,12 @@ namespace OpenTween
 
         private async void IconNameToolStripMenuItem_Click(object sender, EventArgs e)
         {
-            var imageUrl = this.CurrentPost?.ImageUrl;
-            if (MyCommon.IsNullOrEmpty(imageUrl))
+            var imageNormalUrl = this.CurrentPost?.ImageUrl;
+            if (MyCommon.IsNullOrEmpty(imageNormalUrl))
                 return;
 
-            await MyCommon.OpenInBrowserAsync(this, imageUrl.Remove(imageUrl.LastIndexOf("_normal", StringComparison.Ordinal), 7)); // "_normal".Length
+            var imageOriginalUrl = Twitter.CreateProfileImageUrl(imageNormalUrl, "original");
+            await MyCommon.OpenInBrowserAsync(this, imageOriginalUrl);
         }
 
         private async void ReloadIconToolStripMenuItem_Click(object sender, EventArgs e)
@@ -749,7 +799,7 @@ namespace OpenTween
             // URLコピーの項目の表示/非表示
             if (this.PostBrowser.StatusText.StartsWith("http", StringComparison.Ordinal))
             {
-                this._postBrowserStatusText = this.PostBrowser.StatusText;
+                this.postBrowserStatusText = this.PostBrowser.StatusText;
                 var name = this.GetUserId();
                 this.UrlCopyContextMenuItem.Enabled = true;
                 if (name != null)
@@ -775,14 +825,14 @@ namespace OpenTween
                     this.SearchAtPostsDetailToolStripMenuItem.Enabled = false;
                 }
 
-                if (Regex.IsMatch(this._postBrowserStatusText, @"^https?://twitter.com/search\?q=%23"))
+                if (Regex.IsMatch(this.postBrowserStatusText, @"^https?://twitter.com/search\?q=%23"))
                     this.UseHashtagMenuItem.Enabled = true;
                 else
                     this.UseHashtagMenuItem.Enabled = false;
             }
             else
             {
-                this._postBrowserStatusText = "";
+                this.postBrowserStatusText = "";
                 this.UrlCopyContextMenuItem.Enabled = false;
                 this.FollowContextMenuItem.Enabled = false;
                 this.RemoveContextMenuItem.Enabled = false;
@@ -795,8 +845,8 @@ namespace OpenTween
                 this.ListManageUserContextToolStripMenuItem.Enabled = false;
             }
             // 文字列選択されていないときは選択文字列関係の項目を非表示に
-            var _selText = this.PostBrowser.GetSelectedText();
-            if (_selText == null)
+            var selText = this.PostBrowser.GetSelectedText();
+            if (selText == null)
             {
                 this.SelectionSearchContextMenuItem.Enabled = false;
                 this.SelectionCopyContextMenuItem.Enabled = false;
@@ -841,16 +891,17 @@ namespace OpenTween
         private void CurrentTabToolStripMenuItem_Click(object sender, EventArgs e)
         {
             // 発言詳細の選択文字列で現在のタブを検索
-            var _selText = this.PostBrowser.GetSelectedText();
+            var selText = this.PostBrowser.GetSelectedText();
 
-            if (_selText != null)
+            if (selText != null)
             {
                 var searchOptions = new SearchWordDialog.SearchOptions(
                     SearchWordDialog.SearchType.Timeline,
-                    _selText,
-                    newTab: false,
-                    caseSensitive: false,
-                    useRegex: false);
+                    selText,
+                    NewTab: false,
+                    CaseSensitive: false,
+                    UseRegex: false
+                );
 
                 this.Owner.SearchDialog.ResultOptions = searchOptions;
 
@@ -865,10 +916,10 @@ namespace OpenTween
         private void SelectionCopyContextMenuItem_Click(object sender, EventArgs e)
         {
             // 発言詳細で「選択文字列をコピー」
-            var _selText = this.PostBrowser.GetSelectedText();
+            var selText = this.PostBrowser.GetSelectedText();
             try
             {
-                Clipboard.SetDataObject(_selText, false, 5, 100);
+                Clipboard.SetDataObject(selText, false, 5, 100);
             }
             catch (Exception ex)
             {
@@ -882,7 +933,7 @@ namespace OpenTween
             {
                 foreach (var link in this.PostBrowser.Document.Links.Cast<HtmlElement>())
                 {
-                    if (link.GetAttribute("href") == this._postBrowserStatusText)
+                    if (link.GetAttribute("href") == this.postBrowserStatusText)
                     {
                         var linkStr = link.GetAttribute("title");
                         if (MyCommon.IsNullOrEmpty(linkStr))
@@ -893,7 +944,7 @@ namespace OpenTween
                     }
                 }
 
-                Clipboard.SetDataObject(this._postBrowserStatusText, false, 5, 100);
+                Clipboard.SetDataObject(this.postBrowserStatusText, false, 5, 100);
             }
             catch (Exception ex)
             {
@@ -991,7 +1042,7 @@ namespace OpenTween
 
         private void UseHashtagMenuItem_Click(object sender, EventArgs e)
         {
-            var m = Regex.Match(this._postBrowserStatusText, @"^https?://twitter.com/search\?q=%23(?<hash>.+)$");
+            var m = Regex.Match(this.postBrowserStatusText, @"^https?://twitter.com/search\?q=%23(?<hash>.+)$");
             if (m.Success)
                 this.Owner.SetPermanentHashtag(Uri.UnescapeDataString(m.Groups["hash"].Value));
         }
@@ -1055,6 +1106,20 @@ namespace OpenTween
         }
 
         #endregion
+
+        private async void AuthorNameLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+        {
+            var screenName = this.CurrentPost?.ScreenName;
+            if (screenName != null)
+                await this.Owner.ShowUserStatus(screenName, showInputDialog: false);
+        }
+
+        private async void RetweetedByLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+        {
+            var screenName = this.CurrentPost?.RetweetedBy;
+            if (screenName != null)
+                await this.Owner.ShowUserStatus(screenName, showInputDialog: false);
+        }
     }
 
     public class TweetDetailsViewStatusChengedEventArgs : EventArgs