OSDN Git Service

タブ振り分けルールの処理を書き直し, PostFilterRule のテストコード追加
authorKimura Youichi <kim.upsilon@bucyou.net>
Fri, 20 Sep 2013 19:35:42 +0000 (04:35 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Tue, 1 Oct 2013 15:53:45 +0000 (00:53 +0900)
OpenTween.Tests/FiltersClassTest.cs [deleted file]
OpenTween.Tests/OpenTween.Tests.csproj
OpenTween.Tests/PostFilterRuleTest.cs [new file with mode: 0644]
OpenTween/FilterDialog.cs
OpenTween/OpenTween.csproj
OpenTween/PostFilterRule.cs [new file with mode: 0644]
OpenTween/Resources/ChangeLog.txt
OpenTween/StatusDictionary.cs
OpenTween/Tween.cs

diff --git a/OpenTween.Tests/FiltersClassTest.cs b/OpenTween.Tests/FiltersClassTest.cs
deleted file mode 100644 (file)
index 1b56c1f..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-// OpenTween - Client of Twitter
-// Copyright (c) 2013 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;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using NUnit.Framework;
-
-namespace OpenTween
-{
-    [TestFixture]
-    class FiltersClassTest
-    {
-        [Test]
-        public void NameTest()
-        {
-            var filter = new FiltersClass();
-            PostClass post;
-
-            filter.NameFilter = "hoge";
-            post = new PostClass { ScreenName = "hoge", Text = "test" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
-
-            filter.NameFilter = "hoge";
-            post = new PostClass { ScreenName = "foo", Text = "test" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None));
-
-            // NameFilter は RetweetedBy にもマッチする
-            filter.NameFilter = "hoge";
-            post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "hoge" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
-
-            filter.NameFilter = "hoge";
-            post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "bar" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None));
-
-            // NameFilter は部分一致ではない
-            filter.NameFilter = "hoge";
-            post = new PostClass { ScreenName = "hogehoge", Text = "test" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None));
-
-            // 大小文字を区別しないオプション
-            filter.NameFilter = "hoge";
-            filter.CaseSensitive = false;
-            post = new PostClass { ScreenName = "Hoge", Text = "test" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
-        }
-
-        [Test]
-        public void NameRegexTest()
-        {
-            var filter = new FiltersClass { UseRegex = true };
-            PostClass post;
-
-            filter.NameFilter = "hoge(hoge)+";
-            post = new PostClass { ScreenName = "hogehoge", Text = "test" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
-
-            filter.NameFilter = "hoge(hoge)+";
-            post = new PostClass { ScreenName = "hoge", Text = "test" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None));
-
-            // NameFilter は RetweetedBy にもマッチする
-            filter.NameFilter = "hoge(hoge)+";
-            post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "hogehoge" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
-
-            filter.NameFilter = "hoge(hoge)+";
-            post = new PostClass { ScreenName = "foo", Text = "test", RetweetedBy = "hoge2" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.None));
-
-            // 大小文字を区別しないオプション
-            filter.NameFilter = "hoge(hoge)+";
-            filter.CaseSensitive = false;
-            post = new PostClass { ScreenName = "HogeHogeHoge", Text = "test" };
-            Assert.That(filter.IsHit(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
-        }
-    }
-}
index d17ec2e..5e45dc6 100644 (file)
@@ -54,7 +54,7 @@
     <Compile Include="Api\ApiLimitTest.cs" />
     <Compile Include="Api\TwitterApiStatusTest.cs" />
     <Compile Include="BingTest.cs" />
-    <Compile Include="FiltersClassTest.cs" />
+    <Compile Include="PostFilterRuleTest.cs" />
     <Compile Include="FoursquareTest.cs" />
     <Compile Include="LRUCacheDictionaryTest.cs" />
     <Compile Include="MyCommonTest.cs" />
diff --git a/OpenTween.Tests/PostFilterRuleTest.cs b/OpenTween.Tests/PostFilterRuleTest.cs
new file mode 100644 (file)
index 0000000..ea211d4
--- /dev/null
@@ -0,0 +1,577 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2013 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+
+namespace OpenTween
+{
+    [TestFixture]
+    class PostFilterRuleTest
+    {
+        [TestFixtureSetUp]
+        public void FixtureSetUp()
+        {
+            PostFilterRule.AutoCompile = true;
+        }
+
+        [Test]
+        public void EmptyRuleTest()
+        {
+            var filter = new PostFilterRule { };
+            var post = new PostClass { ScreenName = "hogehoge" };
+
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+        }
+
+        [Test]
+        public void NullTest()
+        {
+            var filter = new PostFilterRule
+            {
+                FilterName = null,
+                FilterSource = null,
+                ExFilterName = null,
+                ExFilterSource = null,
+            };
+            var post = new PostClass { ScreenName = "hogehoge" };
+
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            Assert.That(() => filter.FilterBody = null, Throws.InstanceOf<ArgumentNullException>());
+            Assert.That(() => filter.ExFilterBody = null, Throws.InstanceOf<ArgumentNullException>());
+        }
+
+        [Test]
+        public void MatchOnlyTest()
+        {
+            var filter = new PostFilterRule { FilterName = "hogehoge" };
+            var post = new PostClass { ScreenName = "hogehoge" };
+
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
+        }
+
+        [Test]
+        public void ExcludeOnlyTest()
+        {
+            var filter = new PostFilterRule { ExFilterName = "hogehoge" };
+            var post = new PostClass { ScreenName = "hogehoge" };
+
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.Exclude));
+        }
+
+        [Test]
+        public void MatchAndExcludeTest()
+        {
+            var filter = new PostFilterRule { FilterName = "hogehoge", ExFilterSource = "tetete" };
+            var post = new PostClass { ScreenName = "hogehoge", Source = "tetete" };
+
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+        }
+
+        [Test]
+        public void PostMatchOptionsTest()
+        {
+            var filter = new PostFilterRule { FilterName = "hogehoge" };
+            var post = new PostClass { ScreenName = "hogehoge" };
+
+            filter.MoveMatches = false;
+            filter.MarkMatches = false;
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.Copy));
+
+            filter.MoveMatches = false;
+            filter.MarkMatches = true;
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.CopyAndMark));
+
+            filter.MoveMatches = true;
+            filter.MarkMatches = false; // 無視される
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.Move));
+
+            filter.MoveMatches = true;
+            filter.MarkMatches = true; // 無視される
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.Move));
+        }
+
+        [TestCase(false, "hogehoge", false)]
+        [TestCase(false, "hogehoge", true)]
+        [TestCase(true, "(hoge){2}", false)]
+        [TestCase(true, "(hoge){2}", true)]
+        public void NameTest(bool useRegex, string pattern, bool exclude)
+        {
+            var filter = new PostFilterRule();
+            PostClass post;
+
+            if (!exclude)
+            {
+                filter.UseRegex = useRegex;
+                filter.FilterName = pattern;
+            }
+            else
+            {
+                filter.ExUseRegex = useRegex;
+                filter.ExFilterName = pattern;
+            }
+
+            post = new PostClass { ScreenName = "hogehoge" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            post = new PostClass { ScreenName = "foo" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // FilterName は RetweetedBy にもマッチする
+            post = new PostClass { ScreenName = "foo", RetweetedBy = "hogehoge" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            post = new PostClass { ScreenName = "foo", RetweetedBy = "bar" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            if (!useRegex)
+            {
+                // FilterName は完全一致 (UseRegex = false の場合)
+                post = new PostClass { ScreenName = "_hogehoge_" };
+                Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+            }
+            else
+            {
+                // FilterName は部分一致 (UseRegex = true の場合)
+                post = new PostClass { ScreenName = "_hogehoge_" };
+                Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+            }
+
+            // 大小文字を区別する
+            if (!exclude)
+                filter.CaseSensitive = true;
+            else
+                filter.ExCaseSensitive = true;
+
+            post = new PostClass { ScreenName = "HogeHoge" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別しない
+            if (!exclude)
+                filter.CaseSensitive = false;
+            else
+                filter.ExCaseSensitive = false;
+
+            post = new PostClass { ScreenName = "HogeHoge" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+        }
+
+        [TestCase(false, new[] { "aaa", "bbb" }, false)]
+        [TestCase(false, new[] { "aaa", "bbb" }, true)]
+        [TestCase(true, new[] { "a{3}", "b{3}" }, false)]
+        [TestCase(true, new[] { "a{3}", "b{3}" }, true)]
+        public void BodyTest(bool useRegex, string[] pattern, bool exclude)
+        {
+            var filter = new PostFilterRule();
+            PostClass post;
+
+            if (!exclude)
+            {
+                filter.UseRegex = useRegex;
+                filter.FilterBody = pattern;
+            }
+            else
+            {
+                filter.ExUseRegex = useRegex;
+                filter.ExFilterBody = pattern;
+            }
+
+            post = new PostClass { TextFromApi = "test" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 片方だけではマッチしない
+            post = new PostClass { TextFromApi = "aaa" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // FilterBody の文字列が全て含まれている
+            post = new PostClass { TextFromApi = "123aaa456bbb" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            // ScreenName にはマッチしない (UseNameField = true の場合)
+            post = new PostClass { ScreenName = "aaabbb", TextFromApi = "test" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別する
+            if (!exclude)
+                filter.CaseSensitive = true;
+            else
+                filter.ExCaseSensitive = true;
+
+            post = new PostClass { TextFromApi = "AaaBbb" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別しない
+            if (!exclude)
+                filter.CaseSensitive = false;
+            else
+                filter.ExCaseSensitive = false;
+
+            post = new PostClass { TextFromApi = "AaaBbb" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+        }
+
+        [TestCase(false, new[] { "aaa", "bbb" }, false)]
+        [TestCase(false, new[] { "aaa", "bbb" }, true)]
+        [TestCase(true, new[] { "a{3}", "b{3}" }, false)]
+        [TestCase(true, new[] { "a{3}", "b{3}" }, true)]
+        public void BodyUrlTest(bool useRegex, string[] pattern, bool exclude)
+        {
+            var filter = new PostFilterRule();
+            PostClass post;
+
+            if (!exclude)
+            {
+                // FilterByUrl = true の場合は TextFromApi ではなく Text がマッチ対象になる
+                filter.FilterByUrl = true;
+
+                filter.UseRegex = useRegex;
+                filter.FilterBody = pattern;
+            }
+            else
+            {
+                // ExFilterByUrl = true の場合は TextFromApi ではなく Text がマッチ対象になる
+                filter.ExFilterByUrl = true;
+
+                filter.ExUseRegex = useRegex;
+                filter.ExFilterBody = pattern;
+            }
+
+            post = new PostClass { Text = "<a href='http://example.com/123'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 片方だけではマッチしない
+            post = new PostClass { Text = "<a href='http://example.com/aaa'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // FilterBody の文字列が全て含まれている
+            post = new PostClass { Text = "<a href='http://example.com/aaabbb'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            // ScreenName にはマッチしない (UseNameField = true の場合)
+            post = new PostClass { ScreenName = "aaabbb", Text = "<a href='http://example.com/123'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別する
+            if (!exclude)
+                filter.CaseSensitive = true;
+            else
+                filter.ExCaseSensitive = true;
+
+            post = new PostClass { Text = "<a href='http://example.com/AaaBbb'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別しない
+            if (!exclude)
+                filter.CaseSensitive = false;
+            else
+                filter.ExCaseSensitive = false;
+
+            post = new PostClass { Text = "<a href='http://example.com/AaaBbb'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+        }
+
+        [TestCase(false, new[] { "aaa", "bbb" }, false)]
+        [TestCase(false, new[] { "aaa", "bbb" }, true)]
+        [TestCase(true, new[] { "a{3}", "b{3}" }, false)]
+        [TestCase(true, new[] { "a{3}", "b{3}" }, true)]
+        public void BodyAndNameTest(bool useRegex, string[] pattern, bool exclude)
+        {
+            var filter = new PostFilterRule();
+            PostClass post;
+
+            if (!exclude)
+            {
+                // UseNameField = false の場合は FilterBody のマッチ対象が Text と ScreenName の両方になる
+                filter.UseNameField = false;
+
+                filter.UseRegex = useRegex;
+                filter.FilterBody = pattern;
+            }
+            else
+            {
+                // ExUseNameField = false の場合は ExFilterBody のマッチ対象が Text と ScreenName の両方になる
+                filter.ExUseNameField = false;
+
+                filter.ExUseRegex = useRegex;
+                filter.ExFilterBody = pattern;
+            }
+
+            post = new PostClass { ScreenName = "hoge", TextFromApi = "test" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // FilterBody の片方だけではマッチしない
+            post = new PostClass { ScreenName = "hoge", TextFromApi = "aaa" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // FilterBody の片方だけではマッチしない
+            post = new PostClass { ScreenName = "aaa", TextFromApi = "test" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // TextFromApi に FilterBody の文字列が全て含まれている
+            post = new PostClass { ScreenName = "hoge", TextFromApi = "123aaa456bbb" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            // ScreenName に FilterBody の文字列が全て含まれている
+            post = new PostClass { ScreenName = "aaabbb", TextFromApi = "test" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            // FilterBody と ScreenName に FilterBody の文字列がそれぞれ含まれている
+            post = new PostClass { ScreenName = "aaa", TextFromApi = "bbb" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            // 大小文字を区別する
+            if (!exclude)
+                filter.CaseSensitive = true;
+            else
+                filter.ExCaseSensitive = true;
+
+            post = new PostClass { ScreenName = "hoge", TextFromApi = "AaaBbb" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            post = new PostClass { ScreenName = "AaaBbb", TextFromApi = "test" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別しない
+            if (!exclude)
+                filter.CaseSensitive = false;
+            else
+                filter.ExCaseSensitive = false;
+
+            post = new PostClass { ScreenName = "hoge", TextFromApi = "AaaBbb" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            post = new PostClass { ScreenName = "AaaBbb", TextFromApi = "test" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+        }
+
+        [TestCase(false, new[] { "aaa", "bbb" }, false)]
+        [TestCase(false, new[] { "aaa", "bbb" }, true)]
+        [TestCase(true, new[] { "a{3}", "b{3}" }, false)]
+        [TestCase(true, new[] { "a{3}", "b{3}" }, true)]
+        public void BodyUrlAndNameTest(bool useRegex, string[] pattern, bool exclude)
+        {
+            var filter = new PostFilterRule();
+            PostClass post;
+
+            if (!exclude)
+            {
+                // FilterByUrl = true の場合は TextFromApi ではなく Text がマッチ対象になる
+                filter.FilterByUrl = true;
+
+                // UseNameField = false の場合は FilterBody のマッチ対象が Text と ScreenName の両方になる
+                filter.UseNameField = false;
+
+                filter.UseRegex = useRegex;
+                filter.FilterBody = pattern;
+            }
+            else
+            {
+                // ExFilterByUrl = true の場合は TextFromApi ではなく Text がマッチ対象になる
+                filter.ExFilterByUrl = true;
+
+                // ExUseNameField = false の場合は ExFilterBody のマッチ対象が Text と ScreenName の両方になる
+                filter.ExUseNameField = false;
+
+                filter.ExUseRegex = useRegex;
+                filter.ExFilterBody = pattern;
+            }
+
+            post = new PostClass { ScreenName = "hoge", Text = "<a href='http://example.com/123'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // FilterBody の片方だけではマッチしない
+            post = new PostClass { ScreenName = "hoge", Text = "<a href='http://example.com/aaa'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // FilterBody の片方だけではマッチしない
+            post = new PostClass { ScreenName = "aaa", Text = "<a href='http://example.com/123'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // TextFromApi に FilterBody の文字列が全て含まれている
+            post = new PostClass { ScreenName = "hoge", Text = "<a href='http://example.com/aaabbb'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            // ScreenName に FilterBody の文字列が全て含まれている
+            post = new PostClass { ScreenName = "aaabbb", Text = "<a href='http://example.com/123'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            // FilterBody と ScreenName に FilterBody の文字列がそれぞれ含まれている
+            post = new PostClass { ScreenName = "aaa", Text = "<a href='http://example.com/bbb'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            // 大小文字を区別する
+            if (!exclude)
+                filter.CaseSensitive = true;
+            else
+                filter.ExCaseSensitive = true;
+
+            post = new PostClass { ScreenName = "hoge", Text = "<a href='http://example.com/AaaBbb'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            post = new PostClass { ScreenName = "AaaBbb", Text = "<a href='http://example.com/123'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別しない
+            if (!exclude)
+                filter.CaseSensitive = false;
+            else
+                filter.ExCaseSensitive = false;
+
+            post = new PostClass { ScreenName = "hoge", Text = "<a href='http://example.com/AaaBbb'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            post = new PostClass { ScreenName = "AaaBbb", Text = "<a href='http://example.com/123'>t.co/hoge</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+        }
+
+        [TestCase(false, "hogehoge", false)]
+        [TestCase(false, "hogehoge", true)]
+        [TestCase(true, "(hoge){2}", false)]
+        [TestCase(true, "(hoge){2}", true)]
+        public void SourceTest(bool useRegex, string pattern, bool exclude)
+        {
+            var filter = new PostFilterRule();
+            PostClass post;
+
+            if (!exclude)
+            {
+                filter.UseRegex = useRegex;
+                filter.FilterSource = pattern;
+            }
+            else
+            {
+                filter.ExUseRegex = useRegex;
+                filter.ExFilterSource = pattern;
+            }
+
+            post = new PostClass { Source = "hogehoge" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            post = new PostClass { Source = "foo" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            if (!useRegex)
+            {
+                // FilterSource は完全一致 (UseRegex = false の場合)
+                post = new PostClass { Source = "_hogehoge_" };
+                Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+            }
+            else
+            {
+                // FilterSource は部分一致 (UseRegex = true の場合)
+                post = new PostClass { Source = "_hogehoge_" };
+                Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+            }
+
+            // 大小文字を区別する
+            if (!exclude)
+                filter.CaseSensitive = true;
+            else
+                filter.ExCaseSensitive = true;
+
+            post = new PostClass { Source = "HogeHoge" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別しない
+            if (!exclude)
+                filter.CaseSensitive = false;
+            else
+                filter.ExCaseSensitive = false;
+
+            post = new PostClass { Source = "HogeHoge" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+        }
+
+        [TestCase(false, "hogehoge", false)]
+        [TestCase(false, "hogehoge", true)]
+        [TestCase(true, "(hoge){2}", false)]
+        [TestCase(true, "(hoge){2}", true)]
+        public void SourceHtmlTest(bool useRegex, string pattern, bool exclude)
+        {
+            var filter = new PostFilterRule();
+            PostClass post;
+
+            if (!exclude)
+            {
+                // FilterByUrl = true の場合は Source ではなく SourceHtml がマッチ対象になる
+                filter.FilterByUrl = true;
+
+                filter.UseRegex = useRegex;
+                filter.FilterSource = pattern;
+            }
+            else
+            {
+                // ExFilterByUrl = true の場合は Source ではなく SourceHtml がマッチ対象になる
+                filter.ExFilterByUrl = true;
+
+                filter.ExUseRegex = useRegex;
+                filter.ExFilterSource = pattern;
+            }
+
+            // FilterSource は UseRegex の値に関わらず部分一致
+            post = new PostClass { SourceHtml = "<a href='http://example.com/hogehoge'>****</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            post = new PostClass { SourceHtml = "<a href='http://example.com/foo'>****</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別する
+            if (!exclude)
+                filter.CaseSensitive = true;
+            else
+                filter.ExCaseSensitive = true;
+
+            post = new PostClass { SourceHtml = "<a href='http://example.com/HogeHoge'>****</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+
+            // 大小文字を区別しない
+            if (!exclude)
+                filter.CaseSensitive = false;
+            else
+                filter.ExCaseSensitive = false;
+
+            post = new PostClass { SourceHtml = "<a href='http://example.com/HogeHoge'>****</a>" };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+        }
+
+        [Test]
+        public void IsRtTest([Values(true, false)] bool exclude)
+        {
+            var filter = new PostFilterRule();
+            PostClass post;
+
+            if (!exclude)
+                filter.FilterRt = true;
+            else
+                filter.ExFilterRt = true;
+
+            post = new PostClass { RetweetedBy = "hogehoge", RetweetedId = 123L };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(exclude ? MyCommon.HITRESULT.Exclude : MyCommon.HITRESULT.CopyAndMark));
+
+            post = new PostClass { };
+            Assert.That(filter.ExecFilter(post), Is.EqualTo(MyCommon.HITRESULT.None));
+        }
+    }
+}
index a10e328..ebc0798 100644 (file)
@@ -345,7 +345,7 @@ namespace OpenTween
             {
                 if (ListFilters.GetSelected(idx))
                 {
-                    _sts.Tabs[ListTabs.SelectedItem.ToString()].RemoveFilter((FiltersClass)ListFilters.Items[idx]);
+                    _sts.Tabs[ListTabs.SelectedItem.ToString()].RemoveFilter((PostFilterRule)ListFilters.Items[idx]);
                     ListFilters.Items.RemoveAt(idx);
                 }
             }
@@ -394,19 +394,19 @@ namespace OpenTween
 
             if (ListFilters.SelectedIndex > -1)
             {
-                FiltersClass fc = (FiltersClass)ListFilters.SelectedItem;
-                if (fc.SearchBoth)
+                PostFilterRule fc = (PostFilterRule)ListFilters.SelectedItem;
+                if (fc.UseNameField)
                 {
                     RadioAND.Checked = true;
                     RadioPLUS.Checked = false;
                     UID.Enabled = true;
                     MSG1.Enabled = true;
                     MSG2.Enabled = false;
-                    UID.Text = fc.NameFilter;
+                    UID.Text = fc.FilterName;
                     UID.SelectAll();
                     MSG1.Text = "";
                     MSG2.Text = "";
-                    foreach (string bf in fc.BodyFilter)
+                    foreach (string bf in fc.FilterBody)
                     {
                         MSG1.Text += bf + " ";
                     }
@@ -423,32 +423,32 @@ namespace OpenTween
                     UID.Text = "";
                     MSG1.Text = "";
                     MSG2.Text = "";
-                    foreach (string bf in fc.BodyFilter)
+                    foreach (string bf in fc.FilterBody)
                     {
                         MSG2.Text += bf + " ";
                     }
                     MSG2.Text = MSG2.Text.Trim();
                     MSG2.SelectAll();
                 }
-                TextSource.Text = fc.Source;
+                TextSource.Text = fc.FilterSource;
                 CheckRegex.Checked = fc.UseRegex;
-                CheckURL.Checked = fc.SearchUrl;
+                CheckURL.Checked = fc.FilterByUrl;
                 CheckCaseSensitive.Checked = fc.CaseSensitive;
-                CheckRetweet.Checked = fc.IsRt;
+                CheckRetweet.Checked = fc.FilterRt;
                 CheckLambda.Checked = fc.UseLambda;
 
-                if (fc.ExSearchBoth)
+                if (fc.ExUseNameField)
                 {
                     RadioExAnd.Checked = true;
                     RadioExPLUS.Checked = false;
                     ExUID.Enabled = true;
                     ExMSG1.Enabled = true;
                     ExMSG2.Enabled = false;
-                    ExUID.Text = fc.ExNameFilter;
+                    ExUID.Text = fc.ExFilterName;
                     ExUID.SelectAll();
                     ExMSG1.Text = "";
                     ExMSG2.Text = "";
-                    foreach (string bf in fc.ExBodyFilter)
+                    foreach (string bf in fc.ExFilterBody)
                     {
                         ExMSG1.Text += bf + " ";
                     }
@@ -465,21 +465,21 @@ namespace OpenTween
                     ExUID.Text = "";
                     ExMSG1.Text = "";
                     ExMSG2.Text = "";
-                    foreach (string bf in fc.ExBodyFilter)
+                    foreach (string bf in fc.ExFilterBody)
                     {
                         ExMSG2.Text += bf + " ";
                     }
                     ExMSG2.Text = ExMSG2.Text.Trim();
                     ExMSG2.SelectAll();
                 }
-                TextExSource.Text = fc.ExSource;
+                TextExSource.Text = fc.ExFilterSource;
                 CheckExRegex.Checked = fc.ExUseRegex;
-                CheckExURL.Checked = fc.ExSearchUrl;
+                CheckExURL.Checked = fc.ExFilterByUrl;
                 CheckExCaseSensitive.Checked = fc.ExCaseSensitive;
-                CheckExRetweet.Checked = fc.IsExRt;
+                CheckExRetweet.Checked = fc.ExFilterRt;
                 CheckExLambDa.Checked = fc.ExUseLambda;
 
-                if (fc.MoveFrom)
+                if (fc.MoveMatches)
                 {
                     OptMove.Checked = true;
                 }
@@ -487,7 +487,7 @@ namespace OpenTween
                 {
                     OptCopy.Checked = true;
                 }
-                CheckMark.Checked = fc.SetMark;
+                CheckMark.Checked = fc.MarkMatches;
 
                 ButtonEdit.Enabled = true;
                 ButtonDelete.Enabled = true;
@@ -565,86 +565,84 @@ namespace OpenTween
             }
 
             int i = ListFilters.SelectedIndex;
-            FiltersClass ft;
+            PostFilterRule ft;
 
-            ft = new FiltersClass();
+            ft = new PostFilterRule();
 
-            ft.MoveFrom = OptMove.Checked;
-            ft.SetMark = CheckMark.Checked;
+            ft.MoveMatches = OptMove.Checked;
+            ft.MarkMatches = CheckMark.Checked;
 
             string bdy = "";
             if (RadioAND.Checked)
             {
-                ft.NameFilter = UID.Text;
+                ft.FilterName = UID.Text;
                 TweenMain owner = (TweenMain)this.Owner;
                 int cnt = owner.AtIdSupl.ItemCount;
-                owner.AtIdSupl.AddItem("@" + ft.NameFilter);
+                owner.AtIdSupl.AddItem("@" + ft.FilterName);
                 if (cnt != owner.AtIdSupl.ItemCount)
                 {
                     owner.ModifySettingAtId = true;
                 }
-                ft.SearchBoth = true;
+                ft.UseNameField = true;
                 bdy = MSG1.Text;
             }
             else
             {
-                ft.NameFilter = "";
-                ft.SearchBoth = false;
+                ft.FilterName = "";
+                ft.UseNameField = false;
                 bdy = MSG2.Text;
             }
-            ft.Source = TextSource.Text.Trim();
+            ft.FilterSource = TextSource.Text.Trim();
 
             if (CheckRegex.Checked || CheckLambda.Checked)
             {
-                ft.BodyFilter.Add(bdy);
+                ft.FilterBody = new[] { bdy };
             }
             else
             {
-                string[] bf = bdy.Trim().Split((char)32);
-                foreach (string bfs in bf)
-                {
-                    if (!string.IsNullOrEmpty(bfs)) ft.BodyFilter.Add(bfs.Trim());
-                }
+                ft.FilterBody = bdy.Split((char)32)
+                    .Select(x => x.Trim())
+                    .Where(x => !string.IsNullOrEmpty(x))
+                    .ToArray();
             }
 
             ft.UseRegex = CheckRegex.Checked;
-            ft.SearchUrl = CheckURL.Checked;
+            ft.FilterByUrl = CheckURL.Checked;
             ft.CaseSensitive = CheckCaseSensitive.Checked;
-            ft.IsRt = CheckRetweet.Checked;
+            ft.FilterRt = CheckRetweet.Checked;
             ft.UseLambda = CheckLambda.Checked;
 
             bdy = "";
             if (RadioExAnd.Checked)
             {
-                ft.ExNameFilter = ExUID.Text;
-                ft.ExSearchBoth = true;
+                ft.ExFilterName = ExUID.Text;
+                ft.ExUseNameField = true;
                 bdy = ExMSG1.Text;
             }
             else
             {
-                ft.ExNameFilter = "";
-                ft.ExSearchBoth = false;
+                ft.ExFilterName = "";
+                ft.ExUseNameField = false;
                 bdy = ExMSG2.Text;
             }
-            ft.ExSource = TextExSource.Text.Trim();
+            ft.ExFilterSource = TextExSource.Text.Trim();
 
             if (CheckExRegex.Checked || CheckExLambDa.Checked)
             {
-                ft.ExBodyFilter.Add(bdy);
+                ft.ExFilterBody = new[] { bdy };
             }
             else
             {
-                string[] bf = bdy.Trim().Split((char)32);
-                foreach (string bfs in bf)
-                {
-                    if (!string.IsNullOrEmpty(bfs)) ft.ExBodyFilter.Add(bfs.Trim());
-                }
+                ft.ExFilterBody = bdy.Split((char)32)
+                    .Select(x => x.Trim())
+                    .Where(x => !string.IsNullOrEmpty(x))
+                    .ToArray();
             }
 
             ft.ExUseRegex = CheckExRegex.Checked;
-            ft.ExSearchUrl = CheckExURL.Checked;
+            ft.ExFilterByUrl = CheckExURL.Checked;
             ft.ExCaseSensitive = CheckExCaseSensitive.Checked;
-            ft.IsExRt = CheckExRetweet.Checked;
+            ft.ExFilterRt = CheckExRetweet.Checked;
             ft.ExUseLambda = CheckExLambDa.Checked;
 
             if (_mode == EDITMODE.AddNew)
@@ -654,7 +652,7 @@ namespace OpenTween
             }
             else
             {
-                _sts.Tabs[ListTabs.SelectedItem.ToString()].EditFilter((FiltersClass)ListFilters.SelectedItem, ft);
+                _sts.Tabs[ListTabs.SelectedItem.ToString()].EditFilter((PostFilterRule)ListFilters.SelectedItem, ft);
             }
 
             SetFilters(ListTabs.SelectedItem.ToString());
@@ -1089,8 +1087,8 @@ namespace OpenTween
             if (ListTabs.SelectedIndex > -1 && ListFilters.SelectedItem != null && ListFilters.SelectedIndex > 0)
             {
                 string tabname = ListTabs.SelectedItem.ToString();
-                FiltersClass selected = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex];
-                FiltersClass target = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex - 1];
+                PostFilterRule selected = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex];
+                PostFilterRule target = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex - 1];
                 int idx = ListFilters.SelectedIndex;
                 ListFilters.Items.RemoveAt(idx - 1);
                 ListFilters.Items.Insert(idx, target);
@@ -1104,8 +1102,8 @@ namespace OpenTween
             if (ListTabs.SelectedIndex > -1 && ListFilters.SelectedItem != null && ListFilters.SelectedIndex < ListFilters.Items.Count - 1)
             {
                 string tabname = ListTabs.SelectedItem.ToString();
-                FiltersClass selected = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex];
-                FiltersClass target = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex + 1];
+                PostFilterRule selected = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex];
+                PostFilterRule target = _sts.Tabs[tabname].Filters[ListFilters.SelectedIndex + 1];
                 int idx = ListFilters.SelectedIndex;
                 ListFilters.Items.RemoveAt(idx + 1);
                 ListFilters.Items.Insert(idx, target);
@@ -1130,20 +1128,20 @@ namespace OpenTween
                 }
 
                 string tabname = ListTabs.SelectedItem.ToString();
-                List<FiltersClass> filters = new List<FiltersClass>();
+                List<PostFilterRule> filters = new List<PostFilterRule>();
 
                 foreach (int idx in ListFilters.SelectedIndices)
                 {
-                    filters.Add(_sts.Tabs[tabname].Filters[idx].CopyTo(new FiltersClass()));
+                    filters.Add(_sts.Tabs[tabname].Filters[idx].Clone());
                 }
                 foreach (var tb in selectedTabs)
                 {
                     if (tb.TabName == tabname) continue;
 
-                    foreach (FiltersClass flt in filters)
+                    foreach (PostFilterRule flt in filters)
                     {
                         if (!tb.Filters.Contains(flt))
-                            tb.AddFilter(flt.CopyTo(new FiltersClass()));
+                            tb.AddFilter(flt.Clone());
                     }
                 }
                 SetFilters(tabname);
@@ -1165,28 +1163,28 @@ namespace OpenTween
                     selectedTabs = dialog.SelectedTabs;
                 }
                 string tabname = ListTabs.SelectedItem.ToString();
-                List<FiltersClass> filters = new List<FiltersClass>();
+                List<PostFilterRule> filters = new List<PostFilterRule>();
 
                 foreach (int idx in ListFilters.SelectedIndices)
                 {
-                    filters.Add(_sts.Tabs[tabname].Filters[idx].CopyTo(new FiltersClass()));
+                    filters.Add(_sts.Tabs[tabname].Filters[idx].Clone());
                 }
                 if (selectedTabs.Length == 1 && selectedTabs[0].TabName == tabname) return;
                 foreach (var tb in selectedTabs)
                 {
                     if (tb.TabName == tabname) continue;
 
-                    foreach (FiltersClass flt in filters)
+                    foreach (PostFilterRule flt in filters)
                     {
                         if (!tb.Filters.Contains(flt))
-                            tb.AddFilter(flt.CopyTo(new FiltersClass()));
+                            tb.AddFilter(flt.Clone());
                     }
                 }
                 for (int idx = ListFilters.Items.Count - 1; idx >= 0; idx--)
                 {
                     if (ListFilters.GetSelected(idx))
                     {
-                        _sts.Tabs[ListTabs.SelectedItem.ToString()].RemoveFilter((FiltersClass)ListFilters.Items[idx]);
+                        _sts.Tabs[ListTabs.SelectedItem.ToString()].RemoveFilter((PostFilterRule)ListFilters.Items[idx]);
                         ListFilters.Items.RemoveAt(idx);
                     }
                 }
index ac83c5f..d675889 100644 (file)
     <Compile Include="FilterDialog.Designer.cs">
       <DependentUpon>FilterDialog.cs</DependentUpon>
     </Compile>
+    <Compile Include="PostFilterRule.cs" />
     <Compile Include="HashtagManage.cs">
       <SubType>Form</SubType>
     </Compile>
diff --git a/OpenTween/PostFilterRule.cs b/OpenTween/PostFilterRule.cs
new file mode 100644 (file)
index 0000000..68e5a6a
--- /dev/null
@@ -0,0 +1,738 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2013 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Xml.Serialization;
+
+namespace OpenTween
+{
+    /// <summary>
+    /// タブで使用する振り分けルールを表すクラス
+    /// </summary>
+    [XmlType("FiltersClass")]
+    public class PostFilterRule
+    {
+        /// <summary>
+        /// Compile() メソッドの呼び出しが必要な状態か否か
+        /// </summary>
+        [XmlIgnore]
+        public bool IsDirty { get; protected set; }
+
+        /// <summary>
+        /// コンパイルされた振り分けルール
+        /// </summary>
+        [XmlIgnore]
+        protected Func<PostClass, MyCommon.HITRESULT> FilterDelegate;
+
+        /// <summary>
+        /// 振り分けルールの概要
+        /// </summary>
+        [XmlIgnore]
+        public string SummaryText
+        {
+            get { return this.MakeSummary(); }
+        }
+
+        /// <summary>
+        /// ExecFilter() メソッドの実行時に自動でコンパイルを実行する
+        /// </summary>
+        /// <remarks>
+        /// テスト用途以外では AutoCompile に頼らず事前に Compile() メソッドを呼び出すこと
+        /// </remarks>
+        internal static bool AutoCompile { get; set; }
+
+        [XmlElement("NameFilter")]
+        public string FilterName
+        {
+            get { return this._FilterName; }
+            set
+            {
+                this.IsDirty = true;
+                this._FilterName = value;
+            }
+        }
+        private string _FilterName;
+
+        [XmlElement("ExNameFilter")]
+        public string ExFilterName
+        {
+            get { return this._ExFilterName; }
+            set
+            {
+                this.IsDirty = true;
+                this._ExFilterName = value;
+            }
+        }
+        private string _ExFilterName;
+
+        [XmlArray("BodyFilterArray")]
+        public string[] FilterBody
+        {
+            get { return this._FilterBody; }
+            set
+            {
+                if (value == null)
+                    throw new ArgumentNullException();
+
+                this.IsDirty = true;
+                this._FilterBody = value;
+            }
+        }
+        private string[] _FilterBody = new string[0];
+
+        [XmlArray("ExBodyFilterArray")]
+        public string[] ExFilterBody
+        {
+            get { return this._ExFilterBody; }
+            set
+            {
+                if (value == null)
+                    throw new ArgumentNullException();
+
+                this.IsDirty = true;
+                this._ExFilterBody = value;
+            }
+        }
+        private string[] _ExFilterBody = new string[0];
+
+        [XmlElement("SearchBoth")]
+        public bool UseNameField
+        {
+            get { return this._UseNameField; }
+            set
+            {
+                this.IsDirty = true;
+                this._UseNameField = value;
+            }
+        }
+        private bool _UseNameField;
+
+        [XmlElement("ExSearchBoth")]
+        public bool ExUseNameField
+        {
+            get { return this._ExUseNameField; }
+            set
+            {
+                this.IsDirty = true;
+                this._ExUseNameField = value;
+            }
+        }
+        private bool _ExUseNameField;
+
+        [XmlElement("MoveForm")]
+        public bool MoveMatches
+        {
+            get { return this._MoveMatches; }
+            set
+            {
+                this.IsDirty = true;
+                this._MoveMatches = value;
+            }
+        }
+        private bool _MoveMatches;
+
+        [XmlElement("SetMark")]
+        public bool MarkMatches
+        {
+            get { return this._MarkMatches; }
+            set
+            {
+                this.IsDirty = true;
+                this._MarkMatches = value;
+            }
+        }
+        private bool _MarkMatches;
+
+        [XmlElement("SearchUrl")]
+        public bool FilterByUrl
+        {
+            get { return this._FilterByUrl; }
+            set
+            {
+                this.IsDirty = true;
+                this._FilterByUrl = value;
+            }
+        }
+        private bool _FilterByUrl;
+
+        [XmlElement("ExSearchUrl")]
+        public bool ExFilterByUrl
+        {
+            get { return this._ExFilterByUrl; }
+            set
+            {
+                this.IsDirty = true;
+                this._ExFilterByUrl = value;
+            }
+        }
+        private bool _ExFilterByUrl;
+
+        public bool CaseSensitive
+        {
+            get { return this._CaseSensitive; }
+            set
+            {
+                this.IsDirty = true;
+                this._CaseSensitive = value;
+            }
+        }
+        private bool _CaseSensitive;
+
+        public bool ExCaseSensitive
+        {
+            get { return this._ExCaseSensitive; }
+            set
+            {
+                this.IsDirty = true;
+                this._ExCaseSensitive = value;
+            }
+        }
+        private bool _ExCaseSensitive;
+
+        public bool UseLambda
+        {
+            get { return this._UseLambda; }
+            set
+            {
+                this.IsDirty = true;
+                this._UseLambda = value;
+            }
+        }
+        private bool _UseLambda;
+
+        public bool ExUseLambda
+        {
+            get { return this._ExUseLambda; }
+            set
+            {
+                this.IsDirty = true;
+                this._ExUseLambda = value;
+            }
+        }
+        private bool _ExUseLambda;
+
+        public bool UseRegex
+        {
+            get { return this._UseRegex; }
+            set
+            {
+                this.IsDirty = true;
+                this._UseRegex = value;
+            }
+        }
+        private bool _UseRegex;
+
+        public bool ExUseRegex
+        {
+            get { return this._ExUseRegex; }
+            set
+            {
+                this.IsDirty = true;
+                this._ExUseRegex = value;
+            }
+        }
+        private bool _ExUseRegex;
+
+        [XmlElement("IsRt")]
+        public bool FilterRt
+        {
+            get { return this._FilterRt; }
+            set
+            {
+                this.IsDirty = true;
+                this._FilterRt = value;
+            }
+        }
+        private bool _FilterRt;
+
+        [XmlElement("IsExRt")]
+        public bool ExFilterRt
+        {
+            get { return this._ExFilterRt; }
+            set
+            {
+                this.IsDirty = true;
+                this._ExFilterRt = value;
+            }
+        }
+        private bool _ExFilterRt;
+
+        [XmlElement("Source")]
+        public string FilterSource
+        {
+            get { return this._FilterSource; }
+            set
+            {
+                this.IsDirty = true;
+                this._FilterSource = value;
+            }
+        }
+        private string _FilterSource;
+
+        [XmlElement("ExSource")]
+        public string ExFilterSource
+        {
+            get { return this._ExFilterSource; }
+            set
+            {
+                this.IsDirty = true;
+                this._ExFilterSource = value;
+            }
+        }
+        private string _ExFilterSource;
+
+        public PostFilterRule()
+        {
+            this.IsDirty = true;
+
+            this.MarkMatches = true;
+            this.UseNameField = true;
+            this.ExUseNameField = true;
+        }
+
+        static PostFilterRule()
+        {
+            // TODO: TabsClass とかの改修が終わるまでデフォルト有効
+            PostFilterRule.AutoCompile = true;
+        }
+
+        /// <summary>
+        /// 振り分けルールをコンパイルします
+        /// </summary>
+        public void Compile()
+        {
+            var postParam = Expression.Parameter(typeof(PostClass), "x");
+
+            var matchExpr = this.MakeFiltersExpr(
+                postParam,
+                this.FilterName, this.FilterBody, this.FilterSource, this.FilterRt,
+                this.UseRegex, this.CaseSensitive, this.UseNameField, this.UseLambda, this.FilterByUrl);
+
+            var excludeExpr = this.MakeFiltersExpr(
+                postParam,
+                this.ExFilterName, this.ExFilterBody, this.ExFilterSource, this.ExFilterRt,
+                this.ExUseRegex, this.ExCaseSensitive, this.ExUseNameField, this.ExUseLambda, this.ExFilterByUrl);
+
+            Expression<Func<PostClass, MyCommon.HITRESULT>> filterExpr;
+
+            if (matchExpr != null)
+            {
+                MyCommon.HITRESULT hitResult;
+
+                if (this.MoveMatches)
+                    hitResult = MyCommon.HITRESULT.Move;
+                else if (this.MarkMatches)
+                    hitResult = MyCommon.HITRESULT.CopyAndMark;
+                else
+                    hitResult = MyCommon.HITRESULT.Copy;
+
+                if (excludeExpr != null)
+                {
+                    // x => matchExpr ? (!excludeExpr ? hitResult : None) : None
+                    filterExpr =
+                        Expression.Lambda<Func<PostClass, MyCommon.HITRESULT>>(
+                            Expression.Condition(
+                                matchExpr,
+                                Expression.Condition(
+                                    Expression.Not(excludeExpr),
+                                    Expression.Constant(hitResult),
+                                    Expression.Constant(MyCommon.HITRESULT.None)),
+                                Expression.Constant(MyCommon.HITRESULT.None)),
+                            postParam);
+                }
+                else
+                {
+                    // x => matchExpr ? hitResult : None
+                    filterExpr =
+                        Expression.Lambda<Func<PostClass, MyCommon.HITRESULT>>(
+                            Expression.Condition(
+                                matchExpr,
+                                Expression.Constant(hitResult),
+                                Expression.Constant(MyCommon.HITRESULT.None)),
+                            postParam);
+                }
+            }
+            else if (excludeExpr != null)
+            {
+                // x => excludeExpr ? Exclude : None
+                filterExpr =
+                    Expression.Lambda<Func<PostClass, MyCommon.HITRESULT>>(
+                        Expression.Condition(
+                            excludeExpr,
+                            Expression.Constant(MyCommon.HITRESULT.Exclude),
+                            Expression.Constant(MyCommon.HITRESULT.None)),
+                        postParam);
+            }
+            else // matchExpr == null && excludeExpr == null
+            {
+                filterExpr = x => MyCommon.HITRESULT.None;
+            }
+
+            this.FilterDelegate = filterExpr.Compile();
+            this.IsDirty = false;
+        }
+
+        protected virtual Expression MakeFiltersExpr(
+            ParameterExpression postParam,
+            string filterName, string[] filterBody, string filterSource, bool filterRt,
+            bool useRegex, bool caseSensitive, bool useNameField, bool useLambda, bool filterByUrl)
+        {
+            var filterExprs = new List<Expression>();
+
+            if (useNameField && !string.IsNullOrEmpty(filterName))
+            {
+                filterExprs.Add(Expression.OrElse(
+                    this.MakeGenericFilter(postParam, "ScreenName", filterName, useRegex, caseSensitive, exactMatch: true),
+                    this.MakeGenericFilter(postParam, "RetweetedBy", filterName, useRegex, caseSensitive, exactMatch: true)));
+            }
+            foreach (var body in filterBody)
+            {
+                if (string.IsNullOrEmpty(body))
+                    continue;
+
+                Expression bodyExpr;
+                if (useLambda)
+                {
+                    // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する
+                    Expression<Func<PostClass, bool>> lambdaExpr = x => false;
+                    bodyExpr = lambdaExpr.Body;
+                }
+                else
+                {
+                    if (filterByUrl)
+                        bodyExpr = this.MakeGenericFilter(postParam, "Text", body, useRegex, caseSensitive);
+                    else
+                        bodyExpr = this.MakeGenericFilter(postParam, "TextFromApi", body, useRegex, caseSensitive);
+
+                    if (!useNameField)
+                    {
+                        // useNameField = false の場合は ScreenName も filterBody のマッチ対象となる
+                        bodyExpr = Expression.OrElse(
+                            bodyExpr,
+                            this.MakeGenericFilter(postParam, "ScreenName", body, useRegex, caseSensitive));
+                    }
+                }
+
+                filterExprs.Add(bodyExpr);
+            }
+            if (!string.IsNullOrEmpty(filterSource))
+            {
+                if (filterByUrl)
+                    filterExprs.Add(this.MakeGenericFilter(postParam, "SourceHtml", filterSource, useRegex, caseSensitive));
+                else
+                    filterExprs.Add(this.MakeGenericFilter(postParam, "Source", filterSource, useRegex, caseSensitive, exactMatch: true));
+            }
+            if (filterRt)
+            {
+                // x.RetweetedId != null
+                filterExprs.Add(Expression.NotEqual(
+                    Expression.Property(
+                        postParam,
+                        typeof(PostClass).GetProperty("RetweetedId")),
+                    Expression.Constant(null)));
+            }
+
+            if (filterExprs.Count == 0)
+            {
+                return null;
+            }
+            else
+            {
+                // filterExpr[0] && filterExpr[1] && ...
+                var filterExpr = filterExprs[0];
+                foreach (var expr in filterExprs.Skip(1))
+                {
+                    filterExpr = Expression.AndAlso(filterExpr, expr);
+                }
+
+                return filterExpr;
+            }
+        }
+
+        protected Expression MakeGenericFilter(
+            ParameterExpression postParam, string targetFieldName, string pattern,
+            bool useRegex, bool caseSensitive, bool exactMatch = false)
+        {
+            if (useRegex)
+            {
+                var regex = new Regex(pattern, caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
+
+                // regex.IsMatch(x.<targetFieldName>)
+                return Expression.Call(
+                    Expression.Constant(regex),
+                    typeof(Regex).GetMethod("IsMatch", new[] { typeof(string) }),
+                    Expression.Property(
+                        postParam,
+                        typeof(PostClass).GetProperty(targetFieldName)));
+            }
+            else
+            {
+                var compOpt = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
+
+                if (exactMatch)
+                {
+                    // 完全一致
+                    // pattern.Equals(x.<targetFieldName>, compOpt)
+                    return Expression.Call(
+                        Expression.Constant(pattern),
+                        typeof(string).GetMethod("Equals", new[] { typeof(string), typeof(StringComparison) }),
+                        Expression.Property(
+                            postParam,
+                            typeof(PostClass).GetProperty(targetFieldName)),
+                        Expression.Constant(compOpt));
+
+                }
+                else
+                {
+                    // 部分一致
+                    // x.<targetFieldName>.IndexOf(pattern, compOpt) != -1
+                    return Expression.NotEqual(
+                        Expression.Call(
+                            Expression.Property(
+                                postParam,
+                                typeof(PostClass).GetProperty(targetFieldName)),
+                            typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) }),
+                            Expression.Constant(pattern),
+                            Expression.Constant(compOpt)),
+                        Expression.Constant(-1));
+                }
+            }
+        }
+
+        /// <summary>
+        /// ツイートの振り分けを実行する
+        /// </summary>
+        /// <param name="post">振り分けるツイート</param>
+        /// <returns>振り分け結果</returns>
+        public MyCommon.HITRESULT ExecFilter(PostClass post)
+        {
+            if (this.IsDirty)
+            {
+                if (!PostFilterRule.AutoCompile)
+                    throw new InvalidOperationException("振り分け実行前に Compile() を呼び出す必要があります");
+
+                this.Compile();
+            }
+
+            return this.FilterDelegate(post);
+        }
+
+        public PostFilterRule Clone()
+        {
+            return new PostFilterRule
+            {
+                FilterBody = this.FilterBody,
+                FilterName = this.FilterName,
+                FilterSource = this.FilterSource,
+                FilterRt = this.FilterRt,
+                FilterByUrl = this.FilterByUrl,
+                UseLambda = this.UseLambda,
+                UseNameField = this.UseNameField,
+                UseRegex = this.UseRegex,
+                CaseSensitive = this.CaseSensitive,
+                ExFilterBody = this.ExFilterBody,
+                ExFilterName = this.ExFilterName,
+                ExFilterSource = this.ExFilterSource,
+                ExFilterRt = this.ExFilterRt,
+                ExFilterByUrl = this.ExFilterByUrl,
+                ExUseLambda = this.ExUseLambda,
+                ExUseNameField = this.ExUseNameField,
+                ExUseRegex = this.ExUseRegex,
+                ExCaseSensitive = this.ExCaseSensitive,
+                MoveMatches = this.MoveMatches,
+                MarkMatches = this.MarkMatches,
+            };
+        }
+
+        /// <summary>
+        /// 振り分けルールの文字列表現を返します
+        /// </summary>
+        /// <returns>振り分けルールの概要</returns>
+        public override string ToString()
+        {
+            return this.SummaryText;
+        }
+
+        #region from Tween v1.1.0.0
+
+        // The code in this region block is based on code written by the following authors:
+        //   (C) 2007 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
+        //   (C) 2008 Moz (@syo68k)
+
+        /// <summary>
+        /// フィルタ一覧に表示する文言生成
+        /// </summary>
+        protected virtual string MakeSummary()
+        {
+            var fs = new StringBuilder();
+            if (!string.IsNullOrEmpty(this.FilterName) || this.FilterBody.Length > 0 || this.FilterRt || !string.IsNullOrEmpty(this.FilterSource))
+            {
+                if (this.UseNameField)
+                {
+                    if (!string.IsNullOrEmpty(this.FilterName))
+                    {
+                        fs.AppendFormat(Properties.Resources.SetFiltersText1, this.FilterName);
+                    }
+                    else
+                    {
+                        fs.Append(Properties.Resources.SetFiltersText2);
+                    }
+                }
+                if (this.FilterBody.Length > 0)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText3);
+                    foreach (var bf in this.FilterBody)
+                    {
+                        fs.Append(bf);
+                        fs.Append(" ");
+                    }
+                    fs.Length--;
+                    fs.Append(Properties.Resources.SetFiltersText4);
+                }
+                fs.Append("(");
+                if (this.UseNameField)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText5);
+                }
+                else
+                {
+                    fs.Append(Properties.Resources.SetFiltersText6);
+                }
+                if (this.UseRegex)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText7);
+                }
+                if (this.FilterByUrl)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText8);
+                }
+                if (this.CaseSensitive)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText13);
+                }
+                if (this.FilterRt)
+                {
+                    fs.Append("RT/");
+                }
+                if (this.UseLambda)
+                {
+                    fs.Append("LambdaExp/");
+                }
+                if (!string.IsNullOrEmpty(this.FilterSource))
+                {
+                    fs.AppendFormat("Src…{0}/", this.FilterSource);
+                }
+                fs.Length--;
+                fs.Append(")");
+            }
+            if (!string.IsNullOrEmpty(this.ExFilterName) || this.ExFilterBody.Length > 0 || this.ExFilterRt || !string.IsNullOrEmpty(this.ExFilterSource))
+            {
+                //除外
+                fs.Append(Properties.Resources.SetFiltersText12);
+                if (this.ExUseNameField)
+                {
+                    if (!string.IsNullOrEmpty(this.ExFilterName))
+                    {
+                        fs.AppendFormat(Properties.Resources.SetFiltersText1, this.ExFilterName);
+                    }
+                    else
+                    {
+                        fs.Append(Properties.Resources.SetFiltersText2);
+                    }
+                }
+                if (this.ExFilterBody.Length > 0)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText3);
+                    foreach (var bf in this.ExFilterBody)
+                    {
+                        fs.Append(bf);
+                        fs.Append(" ");
+                    }
+                    fs.Length--;
+                    fs.Append(Properties.Resources.SetFiltersText4);
+                }
+                fs.Append("(");
+                if (this.ExUseNameField)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText5);
+                }
+                else
+                {
+                    fs.Append(Properties.Resources.SetFiltersText6);
+                }
+                if (this.ExUseRegex)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText7);
+                }
+                if (this.ExFilterByUrl)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText8);
+                }
+                if (this.ExCaseSensitive)
+                {
+                    fs.Append(Properties.Resources.SetFiltersText13);
+                }
+                if (this.ExFilterRt)
+                {
+                    fs.Append("RT/");
+                }
+                if (this.ExUseLambda)
+                {
+                    fs.Append("LambdaExp/");
+                }
+                if (!string.IsNullOrEmpty(this.ExFilterSource))
+                {
+                    fs.AppendFormat("Src…{0}/", this.ExFilterSource);
+                }
+                fs.Length--;
+                fs.Append(")");
+            }
+
+            fs.Append("(");
+            if (this.MoveMatches)
+            {
+                fs.Append(Properties.Resources.SetFiltersText9);
+            }
+            else
+            {
+                fs.Append(Properties.Resources.SetFiltersText11);
+            }
+            if (!this.MoveMatches && this.MarkMatches)
+            {
+                fs.Append(Properties.Resources.SetFiltersText10);
+            }
+            else if (!this.MoveMatches)
+            {
+                fs.Length--;
+            }
+
+            fs.Append(")");
+
+            return fs.ToString();
+        }
+        #endregion
+    }
+}
index 95c7459..b0332db 100644 (file)
@@ -1,8 +1,16 @@
 更新履歴
 
 ==== Ver 1.1.3-beta1(2013/xx/xx)
+ * タブ振り分けルールの挙動が若干変わりました
+ * 基本的には Tween の Wiki に記載されている挙動に忠実にしたものですが、一部利用者には影響があるかもしれません
+ * http://sourceforge.jp/projects/tween/wiki/%E3%82%BF%E3%83%96%E6%8C%AF%E3%82%8A%E5%88%86%E3%81%91%E3%83%AB%E3%83%BC%E3%83%AB
+
  * CHG: Twitter API のエラー処理を改善
  * CHG: 旧 API v1 関連の機能を削除
+ * CHG: タブ振り分けルールの処理を改善
+   - 除外ルールが単独で指定された場合 (マッチルールが空欄) でなくても除外ルールがタブ全体への例外指定として作用してしまう挙動を修正
+   - (正規表現無効時) 除外ルールの「発言内容」を半角スペース区切りで複数指定したときに AND 条件ではなく OR 条件として判定してしまう挙動を修正
+   - URL 検索が有効の時は Source を完全一致ではなく部分一致でマッチするように修正
  * FIX: 「片思いユーザーリストを取得する」を無効にするとRT非表示設定が反映されない問題の修正 (thx @ui_nyan!)
  * FIX: ブロックされているユーザーのツイートをふぁぼった時にUserStreamsが切断されてしまう問題の修正
  * FIX: 片思いユーザーリストの更新に失敗すると希に片思い表示がされなくなる現象を修正
index 9d4526f..d872e71 100644 (file)
@@ -2018,7 +2018,7 @@ namespace OpenTween
     public sealed class TabClass
     {
         private bool _unreadManage = false;
-        private List<FiltersClass> _filters;
+        private List<PostFilterRule> _filters;
         private int _unreadCount = 0;
         private List<long> _ids;
         private List<TemporaryId> _tmpIds = new List<TemporaryId>();
@@ -2175,7 +2175,7 @@ namespace OpenTween
             SoundFile = "";
             OldestUnreadId = -1;
             TabName = "";
-            _filters = new List<FiltersClass>();
+            _filters = new List<PostFilterRule>();
             Protected = false;
             Notify = true;
             SoundFile = "";
@@ -2317,7 +2317,7 @@ namespace OpenTween
                 {
                     try
                     {
-                        switch (ft.IsHit(post))   //フィルタクラスでヒット判定
+                        switch (ft.ExecFilter(post))   //フィルタクラスでヒット判定
                         {
                             case MyCommon.HITRESULT.None:
                                 break;
@@ -2337,8 +2337,8 @@ namespace OpenTween
                     }
                     catch (NullReferenceException)
                     {
-                        //IsHitでNullRef出る場合あり。暫定対応
-                        MyCommon.TraceOut("IsHitでNullRef: " + ft.ToString());
+                        // ExecFilterでNullRef出る場合あり。暫定対応
+                        MyCommon.TraceOut("ExecFilterでNullRef: " + ft.ToString());
                         rslt = MyCommon.HITRESULT.None;
                     }
                 }
@@ -2451,7 +2451,7 @@ namespace OpenTween
             }
         }
 
-        public FiltersClass[] GetFilters()
+        public PostFilterRule[] GetFilters()
         {
             lock (this._lockObj)
             {
@@ -2459,7 +2459,7 @@ namespace OpenTween
             }
         }
 
-        public void RemoveFilter(FiltersClass filter)
+        public void RemoveFilter(PostFilterRule filter)
         {
             lock (this._lockObj)
             {
@@ -2468,7 +2468,7 @@ namespace OpenTween
             }
         }
 
-        public bool AddFilter(FiltersClass filter)
+        public bool AddFilter(PostFilterRule filter)
         {
             lock (this._lockObj)
             {
@@ -2479,33 +2479,33 @@ namespace OpenTween
             }
         }
 
-        public void EditFilter(FiltersClass original, FiltersClass modified)
+        public void EditFilter(PostFilterRule original, PostFilterRule modified)
         {
-            original.BodyFilter = modified.BodyFilter;
-            original.NameFilter = modified.NameFilter;
-            original.SearchBoth = modified.SearchBoth;
-            original.SearchUrl = modified.SearchUrl;
+            original.FilterBody = modified.FilterBody;
+            original.FilterName = modified.FilterName;
+            original.UseNameField = modified.UseNameField;
+            original.FilterByUrl = modified.FilterByUrl;
             original.UseRegex = modified.UseRegex;
             original.CaseSensitive = modified.CaseSensitive;
-            original.IsRt = modified.IsRt;
+            original.FilterRt = modified.FilterRt;
             original.UseLambda = modified.UseLambda;
-            original.Source = modified.Source;
-            original.ExBodyFilter = modified.ExBodyFilter;
-            original.ExNameFilter = modified.ExNameFilter;
-            original.ExSearchBoth = modified.ExSearchBoth;
-            original.ExSearchUrl = modified.ExSearchUrl;
+            original.FilterSource = modified.FilterSource;
+            original.ExFilterBody = modified.ExFilterBody;
+            original.ExFilterName = modified.ExFilterName;
+            original.ExUseNameField = modified.ExUseNameField;
+            original.ExFilterByUrl = modified.ExFilterByUrl;
             original.ExUseRegex = modified.ExUseRegex;
             original.ExCaseSensitive = modified.ExCaseSensitive;
-            original.IsExRt = modified.IsExRt;
+            original.ExFilterRt = modified.ExFilterRt;
             original.ExUseLambda = modified.ExUseLambda;
-            original.ExSource = modified.ExSource;
-            original.MoveFrom = modified.MoveFrom;
-            original.SetMark = modified.SetMark;
+            original.ExFilterSource = modified.ExFilterSource;
+            original.MoveMatches = modified.MoveMatches;
+            original.MarkMatches = modified.MarkMatches;
             this.FilterModified = true;
         }
 
         [XmlIgnore]
-        public List<FiltersClass> Filters
+        public List<PostFilterRule> Filters
         {
             get
             {
@@ -2523,7 +2523,7 @@ namespace OpenTween
             }
         }
 
-        public FiltersClass[] FilterArray
+        public PostFilterRule[] FilterArray
         {
             get
             {
@@ -2623,866 +2623,6 @@ namespace OpenTween
         }
     }
 
-    [Serializable]
-    public sealed class FiltersClass : System.IEquatable<FiltersClass>
-    {
-        private string _name = "";
-        private List<string> _body = new List<string>();
-        private bool _searchBoth = true;
-        private bool _searchUrl = false;
-        private bool _caseSensitive = false;
-        private bool _useRegex = false;
-        private bool _isRt = false;
-        private string _source = "";
-        private string _exname = "";
-        private List<string> _exbody = new List<string>();
-        private bool _exsearchBoth = true;
-        private bool _exsearchUrl = false;
-        private bool _exuseRegex = false;
-        private bool _excaseSensitive = false;
-        private bool _isExRt = false;
-        private string _exSource = "";
-        private bool _moveFrom = false;
-        private bool _setMark = true;
-        private bool _useLambda = false;
-        private bool _exuseLambda = false;
-
-        public FiltersClass() {}
-
-        //フィルタ一覧に表示する文言生成
-        private string MakeSummary()
-        {
-            var fs = new StringBuilder();
-            if (!string.IsNullOrEmpty(_name) || _body.Count > 0 || _isRt || !string.IsNullOrEmpty(_source))
-            {
-                if (_searchBoth)
-                {
-                    if (!string.IsNullOrEmpty(_name))
-                    {
-                        fs.AppendFormat(Properties.Resources.SetFiltersText1, _name);
-                    }
-                    else
-                    {
-                        fs.Append(Properties.Resources.SetFiltersText2);
-                    }
-                }
-                if (_body.Count > 0)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText3);
-                    foreach (var bf in _body)
-                    {
-                        fs.Append(bf);
-                        fs.Append(" ");
-                    }
-                    fs.Length--;
-                    fs.Append(Properties.Resources.SetFiltersText4);
-                }
-                fs.Append("(");
-                if (_searchBoth)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText5);
-                }
-                else
-                {
-                    fs.Append(Properties.Resources.SetFiltersText6);
-                }
-                if (_useRegex)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText7);
-                }
-                if (_searchUrl)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText8);
-                }
-                if (_caseSensitive)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText13);
-                }
-                if (_isRt)
-                {
-                    fs.Append("RT/");
-                }
-                if (_useLambda)
-                {
-                    fs.Append("LambdaExp/");
-                }
-                if (!string.IsNullOrEmpty(_source))
-                {
-                    fs.AppendFormat("Src…{0}/", _source);
-                }
-                fs.Length--;
-                fs.Append(")");
-            }
-            if (!string.IsNullOrEmpty(_exname) || _exbody.Count > 0 || _isExRt || !string.IsNullOrEmpty(_exSource))
-            {
-                //除外
-                fs.Append(Properties.Resources.SetFiltersText12);
-                if (_exsearchBoth)
-                {
-                    if (!string.IsNullOrEmpty(_exname))
-                    {
-                        fs.AppendFormat(Properties.Resources.SetFiltersText1, _exname);
-                    }
-                    else
-                    {
-                        fs.Append(Properties.Resources.SetFiltersText2);
-                    }
-                }
-                if (_exbody.Count > 0)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText3);
-                    foreach (var bf in _exbody)
-                    {
-                        fs.Append(bf);
-                        fs.Append(" ");
-                    }
-                    fs.Length--;
-                    fs.Append(Properties.Resources.SetFiltersText4);
-                }
-                fs.Append("(");
-                if (_exsearchBoth)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText5);
-                }
-                else
-                {
-                    fs.Append(Properties.Resources.SetFiltersText6);
-                }
-                if (_exuseRegex)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText7);
-                }
-                if (_exsearchUrl)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText8);
-                }
-                if (_excaseSensitive)
-                {
-                    fs.Append(Properties.Resources.SetFiltersText13);
-                }
-                if (_isExRt)
-                {
-                    fs.Append("RT/");
-                }
-                if (_exuseLambda)
-                {
-                    fs.Append("LambdaExp/");
-                }
-                if (!string.IsNullOrEmpty(_exSource))
-                {
-                    fs.AppendFormat("Src…{0}/", _exSource);
-                }
-                fs.Length--;
-                fs.Append(")");
-            }
-
-            fs.Append("(");
-            if (_moveFrom)
-            {
-                fs.Append(Properties.Resources.SetFiltersText9);
-            }
-            else
-            {
-                fs.Append(Properties.Resources.SetFiltersText11);
-            }
-            if (!_moveFrom && _setMark)
-            {
-                fs.Append(Properties.Resources.SetFiltersText10);
-            }
-            else if (!_moveFrom)
-            {
-                fs.Length--;
-            }
-
-            fs.Append(")");
-
-            return fs.ToString();
-        }
-
-        public string NameFilter
-        {
-            get
-            {
-                return _name;
-            }
-            set
-            {
-                _name = value;
-            }
-        }
-
-        public string ExNameFilter
-        {
-            get
-            {
-                return _exname;
-            }
-            set
-            {
-                _exname = value;
-            }
-        }
-
-        [XmlIgnore]
-        public List<string> BodyFilter
-        {
-            get
-            {
-                return _body;
-            }
-            set
-            {
-                _body = value;
-            }
-        }
-
-        public string[] BodyFilterArray
-        {
-            get
-            {
-                return _body.ToArray();
-            }
-            set
-            {
-                _body = new List<string>();
-                foreach (var filter in value)
-                {
-                    _body.Add(filter);
-                }
-            }
-        }
-
-        [XmlIgnore]
-        public List<string> ExBodyFilter
-        {
-            get
-            {
-                return _exbody;
-            }
-            set
-            {
-                _exbody = value;
-            }
-        }
-
-        public string[] ExBodyFilterArray
-        {
-            get
-            {
-                return _exbody.ToArray();
-            }
-            set
-            {
-                _exbody = new List<string>();
-                foreach (var filter in value)
-                {
-                    _exbody.Add(filter);
-                }
-            }
-        }
-
-        public bool SearchBoth
-        {
-            get
-            {
-                return _searchBoth;
-            }
-            set
-            {
-                _searchBoth = value;
-            }
-        }
-
-        public bool ExSearchBoth
-        {
-            get
-            {
-                return _exsearchBoth;
-            }
-            set
-            {
-                _exsearchBoth = value;
-            }
-        }
-
-        public bool MoveFrom
-        {
-            get
-            {
-                return _moveFrom;
-            }
-            set
-            {
-                _moveFrom = value;
-            }
-        }
-
-        public bool SetMark
-        {
-            get
-            {
-                return _setMark;
-            }
-            set
-            {
-                _setMark = value;
-            }
-        }
-
-        public bool SearchUrl
-        {
-            get
-            {
-                return _searchUrl;
-            }
-            set
-            {
-                _searchUrl = value;
-            }
-        }
-
-        public bool ExSearchUrl
-        {
-            get
-            {
-                return _exsearchUrl;
-            }
-            set
-            {
-                _exsearchUrl = value;
-            }
-        }
-
-        public bool CaseSensitive
-        {
-            get
-            {
-                return _caseSensitive;
-            }
-            set
-            {
-                _caseSensitive = value;
-            }
-        }
-
-        public bool ExCaseSensitive
-        {
-            get
-            {
-                return _excaseSensitive;
-            }
-            set
-            {
-                _excaseSensitive = value;
-            }
-        }
-
-        public bool UseLambda
-        {
-            get
-            {
-                return _useLambda;
-            }
-            set
-            {
-                _useLambda = value;
-            }
-        }
-
-        public bool ExUseLambda
-        {
-            get
-            {
-                return _exuseLambda;
-            }
-            set
-            {
-                _exuseLambda = value;
-            }
-        }
-
-        public bool UseRegex
-        {
-            get
-            {
-                return _useRegex;
-            }
-            set
-            {
-                _useRegex = value;
-            }
-        }
-
-        public bool ExUseRegex
-        {
-            get
-            {
-                return _exuseRegex;
-            }
-            set
-            {
-                _exuseRegex = value;
-            }
-        }
-
-        public bool IsRt
-        {
-            get
-            {
-                return _isRt;
-            }
-            set
-            {
-                _isRt = value;
-            }
-        }
-
-        public bool IsExRt
-        {
-            get
-            {
-                return _isExRt;
-            }
-            set
-            {
-                _isExRt = value;
-            }
-        }
-
-        public string Source
-        {
-            get
-            {
-                return _source;
-            }
-            set
-            {
-                _source = value;
-            }
-        }
-
-        public string ExSource
-        {
-            get
-            {
-                return _exSource;
-            }
-            set
-            {
-                _exSource = value;
-            }
-        }
-
-        public override string ToString()
-        {
-            return MakeSummary();
-        }
-
-        public bool ExecuteLambdaExpression(string expr, PostClass post)
-        {
-            return false;
-            // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する
-        }
-
-        public bool ExecuteExLambdaExpression(string expr, PostClass post)
-        {
-            return false;
-            // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する
-        }
-
-        public MyCommon.HITRESULT IsHit(PostClass post)
-        {
-            var bHit = true;
-            string tBody;
-            string tSource;
-            if (_searchUrl)
-            {
-                tBody = post.Text;
-                tSource = post.SourceHtml;
-            }
-            else
-            {
-                tBody = post.TextFromApi;
-                tSource = post.Source;
-            }
-            //検索オプション
-            System.StringComparison compOpt;
-            System.Text.RegularExpressions.RegexOptions rgOpt;
-            if (_caseSensitive)
-            {
-                compOpt = StringComparison.Ordinal;
-                rgOpt = RegexOptions.None;
-            }
-            else
-            {
-                compOpt = StringComparison.OrdinalIgnoreCase;
-                rgOpt = RegexOptions.IgnoreCase;
-            }
-            if (_searchBoth)
-            {
-                if (string.IsNullOrEmpty(_name) ||
-                    (!_useRegex &&
-                     (post.ScreenName.Equals(_name, compOpt) ||
-                      post.RetweetedBy.Equals(_name, compOpt)
-                     )
-                    ) ||
-                    (_useRegex &&
-                     (Regex.IsMatch(post.ScreenName, _name, rgOpt) ||
-                      (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, _name, rgOpt))
-                     )
-                    ))
-                {
-                    if (_useLambda)
-                    {
-                        if (!ExecuteLambdaExpression(_body[0], post)) bHit = false;
-                    }
-                    else
-                    {
-                        foreach (var fs in _body)
-                        {
-                            if (_useRegex)
-                            {
-                                if (!Regex.IsMatch(tBody, fs, rgOpt)) bHit = false;
-                            }
-                            else
-                            {
-                                if (_caseSensitive)
-                                {
-                                    if (!tBody.Contains(fs)) bHit = false;
-                                }
-                                else
-                                {
-                                    if (!tBody.ToLower().Contains(fs.ToLower())) bHit = false;
-                                }
-                            }
-                            if (!bHit) break;
-                        }
-                    }
-                }
-                else
-                {
-                    bHit = false;
-                }
-            }
-            else
-            {
-                if (_useLambda)
-                {
-                    if (!ExecuteLambdaExpression(_body[0], post)) bHit = false;
-                }
-                else
-                {
-                    foreach (var fs in _body)
-                    {
-                        if (_useRegex)
-                        {
-                            if (!(Regex.IsMatch(post.ScreenName, fs, rgOpt) ||
-                                 (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, fs, rgOpt)) ||
-                                 Regex.IsMatch(tBody, fs, rgOpt))) bHit = false;
-                        }
-                        else
-                        {
-                            if (_caseSensitive)
-                            {
-                                if (!(post.ScreenName.Contains(fs) ||
-                                    post.RetweetedBy.Contains(fs) ||
-                                    tBody.Contains(fs))) bHit = false;
-                            }
-                            else
-                            {
-                                if (!(post.ScreenName.ToLower().Contains(fs.ToLower()) ||
-                                    post.RetweetedBy.ToLower().Contains(fs.ToLower()) ||
-                                    tBody.ToLower().Contains(fs.ToLower()))) bHit = false;
-                            }
-                        }
-                        if (!bHit) break;
-                    }
-                }
-            }
-            if (_isRt)
-            {
-                if (post.RetweetedId == null) bHit = false;
-            }
-            if (!string.IsNullOrEmpty(_source))
-            {
-                if (_useRegex)
-                {
-                    if (!Regex.IsMatch(tSource, _source, rgOpt)) bHit = false;
-                }
-                else
-                {
-                    if (!tSource.Equals(_source, compOpt)) bHit = false;
-                }
-            }
-            if (bHit)
-            {
-                //除外判定
-                if (_exsearchUrl)
-                {
-                    tBody = post.Text;
-                    tSource = post.SourceHtml;
-                }
-                else
-                {
-                    tBody = post.TextFromApi;
-                    tSource = post.Source;
-                }
-
-                var exFlag = false;
-                if (!string.IsNullOrEmpty(_exname) || _exbody.Count > 0)
-                {
-                    if (_excaseSensitive)
-                    {
-                        compOpt = StringComparison.Ordinal;
-                        rgOpt = RegexOptions.None;
-                    }
-                    else
-                    {
-                        compOpt = StringComparison.OrdinalIgnoreCase;
-                        rgOpt = RegexOptions.IgnoreCase;
-                    }
-                    if (_exsearchBoth)
-                    {
-                        if (string.IsNullOrEmpty(_exname) ||
-                            (!_exuseRegex &&
-                             (post.ScreenName.Equals(_exname, compOpt) ||
-                              post.RetweetedBy.Equals(_exname, compOpt)
-                             )
-                            ) ||
-                            (_exuseRegex &&
-                                (Regex.IsMatch(post.ScreenName, _exname, rgOpt) ||
-                                 (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, _exname, rgOpt))
-                                )
-                            ))
-                        {
-                            if (_exbody.Count > 0)
-                            {
-                                if (_exuseLambda)
-                                {
-                                    if (ExecuteExLambdaExpression(_exbody[0], post)) exFlag = true;
-                                }
-                                else
-                                {
-                                    foreach (var fs in _exbody)
-                                    {
-                                        if (_exuseRegex)
-                                        {
-                                            if (Regex.IsMatch(tBody, fs, rgOpt)) exFlag = true;
-                                        }
-                                        else
-                                        {
-                                            if (_excaseSensitive)
-                                            {
-                                                if (tBody.Contains(fs)) exFlag = true;
-                                            }
-                                            else
-                                            {
-                                                if (tBody.ToLower().Contains(fs.ToLower())) exFlag = true;
-                                            }
-                                        }
-                                        if (exFlag) break;
-                                    }
-                                }
-                            }
-                            else
-                            {
-                                exFlag = true;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        if (_exuseLambda)
-                        {
-                            if (ExecuteExLambdaExpression(_exbody[0], post)) exFlag = true;
-                        }
-                        else
-                        {
-                            foreach (var fs in _exbody)
-                            {
-                                if (_exuseRegex)
-                                {
-                                    if (Regex.IsMatch(post.ScreenName, fs, rgOpt) ||
-                                       (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, fs, rgOpt)) ||
-                                       Regex.IsMatch(tBody, fs, rgOpt)) exFlag = true;
-                                }
-                                else
-                                {
-                                    if (_excaseSensitive)
-                                    {
-                                        if (post.ScreenName.Contains(fs) ||
-                                           post.RetweetedBy.Contains(fs) ||
-                                           tBody.Contains(fs)) exFlag = true;
-                                    }
-                                    else
-                                    {
-                                        if (post.ScreenName.ToLower().Contains(fs.ToLower()) ||
-                                           post.RetweetedBy.ToLower().Contains(fs.ToLower()) ||
-                                           tBody.ToLower().Contains(fs.ToLower())) exFlag = true;
-                                    }
-                                }
-                                if (exFlag) break;
-                            }
-                        }
-                    }
-                }
-                if (_isExRt)
-                {
-                    if (post.RetweetedId != null) exFlag = true;
-                }
-                if (!string.IsNullOrEmpty(_exSource))
-                {
-                    if (_exuseRegex)
-                    {
-                        if (Regex.IsMatch(tSource, _exSource, rgOpt)) exFlag = true;
-                    }
-                    else
-                    {
-                        if (tSource.Equals(_exSource, compOpt)) exFlag = true;
-                    }
-                }
-
-                if (string.IsNullOrEmpty(_name) && _body.Count == 0 && !_isRt && string.IsNullOrEmpty(_source))
-                {
-                    bHit = false;
-                }
-                if (bHit)
-                {
-                    if (!exFlag)
-                    {
-                        if (_moveFrom)
-                        {
-                            return MyCommon.HITRESULT.Move;
-                        }
-                        else
-                        {
-                            if (_setMark)
-                            {
-                                return MyCommon.HITRESULT.CopyAndMark;
-                            }
-                            return MyCommon.HITRESULT.Copy;
-                        }
-                    }
-                    else
-                    {
-                        return MyCommon.HITRESULT.Exclude;
-                    }
-                }
-                else
-                {
-                    if (exFlag)
-                    {
-                        return MyCommon.HITRESULT.Exclude;
-                    }
-                    else
-                    {
-                        return MyCommon.HITRESULT.None;
-                    }
-                }
-            }
-            else
-            {
-                return MyCommon.HITRESULT.None;
-            }
-        }
-
-        public bool Equals(FiltersClass other)
-        {
-            if (this.BodyFilter.Count != other.BodyFilter.Count) return false;
-            if (this.ExBodyFilter.Count != other.ExBodyFilter.Count) return false;
-            for (int i = 0; i < this.BodyFilter.Count; i++)
-            {
-                if (this.BodyFilter[i] != other.BodyFilter[i]) return false;
-            }
-            for (int i = 0; i < this.ExBodyFilter.Count; i++)
-            {
-                if (this.ExBodyFilter[i] != other.ExBodyFilter[i]) return false;
-            }
-
-            return (this.MoveFrom == other.MoveFrom) &
-                   (this.SetMark == other.SetMark) &
-                   (this.NameFilter == other.NameFilter) &
-                   (this.SearchBoth == other.SearchBoth) &
-                   (this.SearchUrl == other.SearchUrl) &
-                   (this.UseRegex == other.UseRegex) &
-                   (this.ExNameFilter == other.ExNameFilter) &
-                   (this.ExSearchBoth == other.ExSearchBoth) &
-                   (this.ExSearchUrl == other.ExSearchUrl) &
-                   (this.ExUseRegex == other.ExUseRegex) &
-                   (this.IsRt == other.IsRt) &
-                   (this.Source == other.Source) &
-                   (this.IsExRt == other.IsExRt) &
-                   (this.ExSource == other.ExSource) &
-                   (this.UseLambda == other.UseLambda) &
-                   (this.ExUseLambda == other.ExUseLambda);
-        }
-
-        public FiltersClass CopyTo(FiltersClass destination)
-        {
-            if (this.BodyFilter.Count > 0)
-            {
-                foreach (var flt in this.BodyFilter)
-                {
-                    destination.BodyFilter.Add(flt);
-                }
-            }
-
-            if (this.ExBodyFilter.Count > 0)
-            {
-                foreach (var flt in this.ExBodyFilter)
-                {
-                    destination.ExBodyFilter.Add(flt);
-                }
-            }
-
-            destination.MoveFrom = this.MoveFrom;
-            destination.SetMark = this.SetMark;
-            destination.NameFilter = this.NameFilter;
-            destination.SearchBoth = this.SearchBoth;
-            destination.SearchUrl = this.SearchUrl;
-            destination.UseRegex = this.UseRegex;
-            destination.ExNameFilter = this.ExNameFilter;
-            destination.ExSearchBoth = this.ExSearchBoth;
-            destination.ExSearchUrl = this.ExSearchUrl;
-            destination.ExUseRegex = this.ExUseRegex;
-            destination.IsRt = this.IsRt;
-            destination.Source = this.Source;
-            destination.IsExRt = this.IsExRt;
-            destination.ExSource = this.ExSource;
-            destination.UseLambda = this.UseLambda;
-            destination.ExUseLambda = this.ExUseLambda;
-            return destination;
-        }
-
-        public override bool Equals(object obj)
-        {
-            if (obj == null || this.GetType() != obj.GetType()) return false;
-            return this.Equals((FiltersClass)obj);
-        }
-
-        public override int GetHashCode()
-        {
-            return this.MoveFrom.GetHashCode() ^
-                   this.SetMark.GetHashCode() ^
-                   this.BodyFilter.GetHashCode() ^
-                   this.NameFilter.GetHashCode() ^
-                   this.SearchBoth.GetHashCode() ^
-                   this.SearchUrl.GetHashCode() ^
-                   this.UseRegex.GetHashCode() ^
-                   this.ExBodyFilter.GetHashCode() ^
-                   this.ExNameFilter.GetHashCode() ^
-                   this.ExSearchBoth.GetHashCode() ^
-                   this.ExSearchUrl.GetHashCode() ^
-                   this.ExUseRegex.GetHashCode() ^
-                   this.IsRt.GetHashCode() ^
-                   this.Source.GetHashCode() ^
-                   this.IsExRt.GetHashCode() ^
-                   this.ExSource.GetHashCode() ^
-                   this.UseLambda.GetHashCode() ^
-                   this.ExUseLambda.GetHashCode();
-        }
-    }
-
     //ソート比較クラス:ID比較のみ
     public sealed class IdComparerClass : IComparer<long>
     {
index 1980686..e561b84 100644 (file)
@@ -9058,21 +9058,21 @@ namespace OpenTween
                 PostClass post = _statuses[_curTab.Text, idx];
                 if (!ids.Contains(post.ScreenName))
                 {
-                    FiltersClass fc = new FiltersClass();
+                    PostFilterRule fc = new PostFilterRule();
                     ids.Add(post.ScreenName);
                     if (post.RetweetedId == null)
                     {
-                        fc.NameFilter = post.ScreenName;
+                        fc.FilterName = post.ScreenName;
                     }
                     else
                     {
-                        fc.NameFilter = post.RetweetedBy;
+                        fc.FilterName = post.RetweetedBy;
                     }
-                    fc.SearchBoth = true;
-                    fc.MoveFrom = mv;
-                    fc.SetMark = mk;
+                    fc.UseNameField = true;
+                    fc.MoveMatches = mv;
+                    fc.MarkMatches = mk;
                     fc.UseRegex = false;
-                    fc.SearchUrl = false;
+                    fc.FilterByUrl = false;
                     _statuses.Tabs[tabName].AddFilter(fc);
                 }
             }
@@ -11620,13 +11620,13 @@ namespace OpenTween
                 bool mk = false;
                 MoveOrCopy(ref mv, ref mk);
 
-                FiltersClass fc = new FiltersClass();
-                fc.NameFilter = name;
-                fc.SearchBoth = true;
-                fc.MoveFrom = mv;
-                fc.SetMark = mk;
+                PostFilterRule fc = new PostFilterRule();
+                fc.FilterName = name;
+                fc.UseNameField = true;
+                fc.MoveMatches = mv;
+                fc.MarkMatches = mk;
                 fc.UseRegex = false;
-                fc.SearchUrl = false;
+                fc.FilterByUrl = false;
                 _statuses.Tabs[tabName].AddFilter(fc);
 
                 try