OSDN Git Service

DetailsHtmlBuilderを追加
authorKimura Youichi <kim.upsilon@bucyou.net>
Sun, 7 Jan 2024 19:15:44 +0000 (04:15 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Sun, 7 Jan 2024 19:42:17 +0000 (04:42 +0900)
OpenTween.Tests/Models/DetailsHtmlBuilderTest.cs [new file with mode: 0644]
OpenTween/Models/DetailsHtmlBuilder.cs [new file with mode: 0644]
OpenTween/Tween.cs
OpenTween/TweetDetailsView.cs
OpenTween/UserInfoDialog.cs

diff --git a/OpenTween.Tests/Models/DetailsHtmlBuilderTest.cs b/OpenTween.Tests/Models/DetailsHtmlBuilderTest.cs
new file mode 100644 (file)
index 0000000..6ba0287
--- /dev/null
@@ -0,0 +1,45 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System.Xml.Linq;
+using System.Xml.XPath;
+using Xunit;
+
+namespace OpenTween.Models
+{
+    public class DetailsHtmlBuilderTest
+    {
+        [Fact]
+        public void Build_Test()
+        {
+            var settingCommon = new SettingCommon();
+            var settingLocal = new SettingLocal();
+            using var theme = new ThemeManager(settingLocal);
+
+            var builder = new DetailsHtmlBuilder();
+            builder.Prepare(settingCommon, theme);
+
+            var actualHtml = builder.Build("tetete");
+            var parsedDocument = XDocument.Parse(actualHtml);
+            Assert.Equal("tetete", parsedDocument.XPathSelectElement("/html/body/p").Value);
+        }
+    }
+}
diff --git a/OpenTween/Models/DetailsHtmlBuilder.cs b/OpenTween/Models/DetailsHtmlBuilder.cs
new file mode 100644 (file)
index 0000000..a0219b9
--- /dev/null
@@ -0,0 +1,77 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#nullable enable
+
+using System;
+using System.Drawing;
+
+namespace OpenTween.Models
+{
+    public class DetailsHtmlBuilder
+    {
+        private const string TemplateHead =
+            """<head><meta http-equiv="X-UA-Compatible" content="IE=8" />"""
+            + """<style type="text/css"><!-- """
+            + "body, p, pre {margin: 0;} "
+            + """body {font-family: "%FONT_FAMILY%", "Segoe UI Emoji", sans-serif; font-size: %FONT_SIZE%pt; background-color:rgb(%BG_COLOR%); word-wrap: break-word; color:rgb(%FONT_COLOR%);} """
+            + "pre {font-family: inherit;} "
+            + "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
+            + "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
+            + ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
+            + ".quote-tweet.reply {border-color: rgb(%BG_REPLY_COLOR%);} "
+            + ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
+            + "--></style>"
+            + "</head>";
+
+        private const string TemplateMonospaced =
+            $"<html>{TemplateHead}<body><pre>%CONTENT_HTML%</pre></body></html>";
+
+        private const string TemplateProportional =
+            $"<html>{TemplateHead}<body><p>%CONTENT_HTML%</p></body></html>";
+
+        private string? preparedTemplate = null;
+
+        public void Prepare(SettingCommon settingCommon, ThemeManager theme)
+        {
+            var htmlTemplate = settingCommon.IsMonospace ? TemplateMonospaced : TemplateProportional;
+
+            static string ColorToRGBString(Color color)
+                => $"{color.R},{color.G},{color.B}";
+
+            this.preparedTemplate = htmlTemplate
+                .Replace("%FONT_FAMILY%", theme.FontDetail.Name)
+                .Replace("%FONT_SIZE%", theme.FontDetail.Size.ToString())
+                .Replace("%FONT_COLOR%", ColorToRGBString(theme.ColorDetail))
+                .Replace("%LINK_COLOR%", ColorToRGBString(theme.ColorDetailLink))
+                .Replace("%BG_COLOR%", ColorToRGBString(theme.ColorDetailBackcolor))
+                .Replace("%BG_REPLY_COLOR%", ColorToRGBString(theme.ColorAtTo));
+        }
+
+        public string Build(string contentHtml)
+        {
+            if (this.preparedTemplate == null)
+                throw new InvalidOperationException("Template is not prepared.");
+
+            return this.preparedTemplate.Replace("%CONTENT_HTML%", contentHtml);
+        }
+    }
+}
index ae3c660..7124676 100644 (file)
@@ -98,27 +98,7 @@ namespace OpenTween
 
         private readonly object syncObject = new(); // ロック用
 
-        private const string DetailHtmlFormatHead =
-            """<head><meta http-equiv="X-UA-Compatible" content="IE=8">"""
-            + """<style type="text/css"><!-- """
-            + "body, p, pre {margin: 0;} "
-            + """body {font-family: "%FONT_FAMILY%", "Segoe UI Emoji", sans-serif; font-size: %FONT_SIZE%pt; background-color:rgb(%BG_COLOR%); word-wrap: break-word; color:rgb(%FONT_COLOR%);} """
-            + "pre {font-family: inherit;} "
-            + "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
-            + "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
-            + ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
-            + ".quote-tweet.reply {border-color: rgb(%BG_REPLY_COLOR%);} "
-            + ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
-            + "--></style>"
-            + "</head>";
-
-        private const string DetailHtmlFormatTemplateMono =
-            $"<html>{DetailHtmlFormatHead}<body><pre>%CONTENT_HTML%</pre></body></html>";
-
-        private const string DetailHtmlFormatTemplateNormal =
-            $"<html>{DetailHtmlFormatHead}<body><p>%CONTENT_HTML%</p></body></html>";
-
-        private string detailHtmlFormatPreparedTemplate = null!;
+        private readonly DetailsHtmlBuilder detailsHtmlBuilder = new();
 
         private bool myStatusError = false;
         private bool myStatusOnline = false;
@@ -356,13 +336,13 @@ namespace OpenTween
 
             // フォント&文字色&背景色保持
             this.themeManager = new(this.settings.Local);
-            this.tweetDetailsView.Initialize(this, this.iconCache, this.themeManager);
+            this.tweetDetailsView.Initialize(this, this.iconCache, this.themeManager, this.detailsHtmlBuilder);
 
             // StringFormatオブジェクトへの事前設定
             this.sfTab.Alignment = StringAlignment.Center;
             this.sfTab.LineAlignment = StringAlignment.Center;
 
-            this.InitDetailHtmlFormat();
+            this.detailsHtmlBuilder.Prepare(this.settings.Common, this.themeManager);
             this.tweetDetailsView.ClearPostBrowser();
 
             this.recommendedStatusFooter = " [TWNv" + Regex.Replace(MyCommon.FileVersion.Replace(".", ""), "^0*", "") + "]";
@@ -754,22 +734,6 @@ namespace OpenTween
             }
         }
 
-        private void InitDetailHtmlFormat()
-        {
-            var htmlTemplate = this.settings.Common.IsMonospace ? DetailHtmlFormatTemplateMono : DetailHtmlFormatTemplateNormal;
-
-            static string ColorToRGBString(Color color)
-                => $"{color.R},{color.G},{color.B}";
-
-            this.detailHtmlFormatPreparedTemplate = htmlTemplate
-                .Replace("%FONT_FAMILY%", this.themeManager.FontDetail.Name)
-                .Replace("%FONT_SIZE%", this.themeManager.FontDetail.Size.ToString())
-                .Replace("%FONT_COLOR%", ColorToRGBString(this.themeManager.ColorDetail))
-                .Replace("%LINK_COLOR%", ColorToRGBString(this.themeManager.ColorDetailLink))
-                .Replace("%BG_COLOR%", ColorToRGBString(this.themeManager.ColorDetailBackcolor))
-                .Replace("%BG_REPLY_COLOR%", ColorToRGBString(this.themeManager.ColorAtTo));
-        }
-
         private void ListTab_DrawItem(object sender, DrawItemEventArgs e)
         {
             string txt;
@@ -2652,7 +2616,7 @@ namespace OpenTween
 
                     try
                     {
-                        this.InitDetailHtmlFormat();
+                        this.detailsHtmlBuilder.Prepare(this.settings.Common, this.themeManager);
                     }
                     catch (Exception ex)
                     {
@@ -4162,9 +4126,6 @@ namespace OpenTween
             this.DispSelectedPost();
         }
 
-        public string CreateDetailHtml(string orgdata)
-            => this.detailHtmlFormatPreparedTemplate.Replace("%CONTENT_HTML%", orgdata);
-
         private void DispSelectedPost()
             => this.DispSelectedPost(false);
 
@@ -9006,7 +8967,7 @@ namespace OpenTween
 
         private async Task DoShowUserStatus(TwitterUser user)
         {
-            using var userDialog = new UserInfoDialog(this, this.tw.Api);
+            using var userDialog = new UserInfoDialog(this, this.tw.Api, this.detailsHtmlBuilder);
             var showUserTask = userDialog.ShowUserAsync(user);
             userDialog.ShowDialog(this);
 
index 4bbe618..d0d9097 100644 (file)
@@ -54,6 +54,9 @@ namespace OpenTween
         private ImageCache IconCache
             => this.iconCache ?? throw this.NotInitializedException();
 
+        private DetailsHtmlBuilder HtmlBuilder
+            => this.detailsHtmlBuilder ?? throw this.NotInitializedException();
+
         /// <summary><see cref="PostClass"/> のダンプを表示するか</summary>
         [Browsable(false)]
         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
@@ -86,6 +89,7 @@ namespace OpenTween
         private TweenMain? owner;
         private ImageCache? iconCache;
         private ThemeManager? themeManager;
+        private DetailsHtmlBuilder? detailsHtmlBuilder;
 
         public TweetDetailsView()
         {
@@ -102,18 +106,19 @@ namespace OpenTween
             this.PostBrowser.AllowWebBrowserDrop = false;  // COMException を回避するため、ActiveX の初期化が終わってから設定する
         }
 
-        public void Initialize(TweenMain owner, ImageCache iconCache, ThemeManager themeManager)
+        public void Initialize(TweenMain owner, ImageCache iconCache, ThemeManager themeManager, DetailsHtmlBuilder detailsHtmlBuilder)
         {
             this.owner = owner;
             this.iconCache = iconCache;
             this.themeManager = themeManager;
+            this.detailsHtmlBuilder = detailsHtmlBuilder;
         }
 
         private Exception NotInitializedException()
             => new InvalidOperationException("Cannot call before initialization");
 
         public void ClearPostBrowser()
-            => this.PostBrowser.DocumentText = this.Owner.CreateDetailHtml("");
+            => this.PostBrowser.DocumentText = this.HtmlBuilder.Build("");
 
         public async Task ShowPostDetails(PostClass post)
         {
@@ -208,14 +213,14 @@ namespace OpenTween
                 }
                 sb.Append("-----End PostClass Dump<br>");
 
-                this.PostBrowser.DocumentText = this.Owner.CreateDetailHtml(sb.ToString());
+                this.PostBrowser.DocumentText = this.HtmlBuilder.Build(sb.ToString());
                 return;
             }
 
             using (ControlTransaction.Update(this.PostBrowser))
             {
                 this.PostBrowser.DocumentText =
-                    this.Owner.CreateDetailHtml(post.IsDeleted ? "(DELETED)" : post.Text);
+                    this.HtmlBuilder.Build(post.IsDeleted ? "(DELETED)" : post.Text);
 
                 this.PostBrowser.Document.Window.ScrollTo(0, 0);
             }
@@ -331,7 +336,7 @@ namespace OpenTween
             var body = post.Text + string.Concat(loadingQuoteHtml) + loadingReplyHtml;
 
             using (ControlTransaction.Update(this.PostBrowser))
-                this.PostBrowser.DocumentText = this.Owner.CreateDetailHtml(body);
+                this.PostBrowser.DocumentText = this.HtmlBuilder.Build(body);
 
             // 引用ツイートを読み込み
             var loadTweetTasks = quoteStatusIds.Select(x => this.CreateQuoteTweetHtml(x, isReply: false)).ToList();
@@ -348,7 +353,7 @@ namespace OpenTween
             body = post.Text + string.Concat(quoteHtmls);
 
             using (ControlTransaction.Update(this.PostBrowser))
-                this.PostBrowser.DocumentText = this.Owner.CreateDetailHtml(body);
+                this.PostBrowser.DocumentText = this.HtmlBuilder.Build(body);
         }
 
         private async Task<string> CreateQuoteTweetHtml(PostId statusId, bool isReply)
@@ -422,7 +427,7 @@ namespace OpenTween
                     langFrom: null,
                     langTo: SettingManager.Instance.Common.TranslateLanguage);
 
-                this.PostBrowser.DocumentText = this.Owner.CreateDetailHtml(translatedText);
+                this.PostBrowser.DocumentText = this.HtmlBuilder.Build(translatedText);
             }
             catch (WebApiException e)
             {
index 3a98320..e8c06e9 100644 (file)
@@ -44,6 +44,7 @@ using System.Windows.Forms;
 using OpenTween.Api;
 using OpenTween.Api.DataModel;
 using OpenTween.Connection;
+using OpenTween.Models;
 
 namespace OpenTween
 {
@@ -54,11 +55,13 @@ namespace OpenTween
 
         private readonly TweenMain mainForm;
         private readonly TwitterApi twitterApi;
+        private readonly DetailsHtmlBuilder detailsHtmlBuilder;
 
-        public UserInfoDialog(TweenMain mainForm, TwitterApi twitterApi)
+        public UserInfoDialog(TweenMain mainForm, TwitterApi twitterApi, DetailsHtmlBuilder detailsHtmlBuilder)
         {
             this.mainForm = mainForm;
             this.twitterApi = twitterApi;
+            this.detailsHtmlBuilder = detailsHtmlBuilder;
 
             this.InitializeComponent();
 
@@ -183,7 +186,7 @@ namespace OpenTween
                     .Concat(TweetExtractor.ExtractEmojiEntities(descriptionText));
 
                 var html = TweetFormatter.AutoLinkHtml(descriptionText, mergedEntities);
-                html = this.mainForm.CreateDetailHtml(html);
+                html = this.detailsHtmlBuilder.Build(html);
 
                 if (cancellationToken.IsCancellationRequested)
                     return;
@@ -192,7 +195,7 @@ namespace OpenTween
             }
             else
             {
-                this.DescriptionBrowser.DocumentText = this.mainForm.CreateDetailHtml("");
+                this.DescriptionBrowser.DocumentText = this.detailsHtmlBuilder.Build("");
             }
         }
 
@@ -263,7 +266,7 @@ namespace OpenTween
                 var mergedEntities = entities.Concat(TweetExtractor.ExtractEmojiEntities(status.FullText));
 
                 var html = TweetFormatter.AutoLinkHtml(status.FullText, mergedEntities);
-                html = this.mainForm.CreateDetailHtml(html +
+                html = this.detailsHtmlBuilder.Build(html +
                     " Posted at " + MyCommon.DateTimeParse(status.CreatedAt).ToLocalTimeString() +
                     " via " + status.Source);
 
@@ -274,7 +277,7 @@ namespace OpenTween
             }
             else
             {
-                this.RecentPostBrowser.DocumentText = this.mainForm.CreateDetailHtml(Properties.Resources.ShowUserInfo2);
+                this.RecentPostBrowser.DocumentText = this.detailsHtmlBuilder.Build(Properties.Resources.ShowUserInfo2);
             }
         }