OSDN Git Service

ブロックの括弧を独立した行に書く (SA1500, SA1501, SA1502)
[opentween/open-tween.git] / OpenTween / FilterDialog.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 //           (c) 2008-2011 Moz (@syo68k)
4 //           (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 //           (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 //           (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 //           (c) 2011-2012 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
8 // All rights reserved.
9 //
10 // This file is part of OpenTween.
11 //
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General Public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
15 // any later version.
16 //
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 // for more details.
21 //
22 // You should have received a copy of the GNU General Public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
26
27 #nullable enable
28
29 using System;
30 using System.Collections.Generic;
31 using System.Collections.Specialized;
32 using System.ComponentModel;
33 using System.Data;
34 using System.Drawing;
35 using System.IO;
36 using System.Linq;
37 using System.Linq.Expressions;
38 using System.Text;
39 using System.Text.RegularExpressions;
40 using System.Windows.Forms;
41 using OpenTween.Models;
42
43 namespace OpenTween
44 {
45     public partial class FilterDialog : OTBaseForm
46     {
47         private EDITMODE mode;
48         private bool directAdd;
49         private MultiSelectionState multiSelState = MultiSelectionState.None;
50         private readonly TabInformations sts;
51
52         private List<TabModel> tabs = new List<TabModel>();
53         private int selectedTabIndex = -1;
54
55         private readonly List<string> idlist = new List<string>();
56
57         private enum EDITMODE
58         {
59             AddNew,
60             Edit,
61             None,
62         }
63
64         private enum EnableButtonMode
65         {
66             NotSelected,
67             Enable,
68             Disable,
69         }
70
71         [Flags]
72         private enum MultiSelectionState
73         {
74             None = 0,
75             MoveSelected = 1,
76             SelectAll = 2,
77         }
78
79         private EnableButtonMode RuleEnableButtonMode
80         {
81             get => this.ruleEnableButtonMode;
82             set
83             {
84                 this.ruleEnableButtonMode = value;
85
86                 this.buttonRuleToggleEnabled.Text = value == FilterDialog.EnableButtonMode.Enable
87                     ? Properties.Resources.EnableButtonCaption
88                     : Properties.Resources.DisableButtonCaption;
89                 this.buttonRuleToggleEnabled.Enabled = value != EnableButtonMode.NotSelected;
90             }
91         }
92         private EnableButtonMode ruleEnableButtonMode = FilterDialog.EnableButtonMode.NotSelected;
93
94         public TabModel? SelectedTab
95             => this.selectedTabIndex != -1 ? this.tabs[this.selectedTabIndex] : null;
96
97         public FilterDialog()
98         {
99             this.InitializeComponent();
100
101             this.sts = TabInformations.GetInstance();
102             this.RefreshListTabs();
103         }
104
105         private void RefreshListTabs()
106         {
107             this.tabs = this.sts.Tabs.Append(this.sts.MuteTab).ToList();
108
109             using (ControlTransaction.Update(this.ListTabs))
110             {
111                 var selectedTab = this.ListTabs.SelectedItem;
112                 this.ListTabs.Items.Clear();
113                 this.ListTabs.Items.AddRange(this.tabs.ToArray());
114                 this.ListTabs.SelectedIndex = this.tabs.FindIndex(x => x == selectedTab);
115             }
116         }
117
118         private void SetFilters(TabModel tab)
119         {
120             if (this.ListTabs.Items.Count == 0) return;
121
122             this.ListFilters.Items.Clear();
123
124             if (tab is FilterTabModel filterTab)
125                 this.ListFilters.Items.AddRange(filterTab.GetFilters());
126
127             if (this.ListFilters.Items.Count > 0)
128                 this.ListFilters.SelectedIndex = 0;
129             else
130                 this.ShowDetail();
131
132             if (tab.IsDefaultTabType)
133             {
134                 this.CheckProtected.Checked = true;
135                 this.CheckProtected.Enabled = false;
136             }
137             else
138             {
139                 this.CheckProtected.Checked = tab.Protected;
140                 this.CheckProtected.Enabled = true;
141             }
142
143             this.CheckManageRead.CheckedChanged -= this.CheckManageRead_CheckedChanged;
144             this.CheckManageRead.Checked = tab.UnreadManage;
145             this.CheckManageRead.CheckedChanged += this.CheckManageRead_CheckedChanged;
146
147             this.CheckNotifyNew.CheckedChanged -= this.CheckNotifyNew_CheckedChanged;
148             this.CheckNotifyNew.Checked = tab.Notify;
149             this.CheckNotifyNew.CheckedChanged += this.CheckNotifyNew_CheckedChanged;
150
151             var idx = this.ComboSound.Items.IndexOf(tab.SoundFile);
152             if (idx == -1) idx = 0;
153             this.ComboSound.SelectedIndex = idx;
154
155             if (this.directAdd) return;
156
157             if (tab.TabType == MyCommon.TabUsageType.Mute)
158             {
159                 this.ButtonRenameTab.Enabled = false;
160                 this.CheckManageRead.Enabled = false;
161                 this.CheckNotifyNew.Enabled = false;
162                 this.ComboSound.Enabled = false;
163
164                 this.GroupBox1.Visible = false;
165                 this.labelMuteTab.Visible = true;
166             }
167             else
168             {
169                 this.ButtonRenameTab.Enabled = true;
170                 this.CheckManageRead.Enabled = true;
171                 this.CheckNotifyNew.Enabled = true;
172                 this.ComboSound.Enabled = true;
173
174                 this.GroupBox1.Visible = true;
175                 this.labelMuteTab.Visible = false;
176             }
177
178             this.ListTabs.Enabled = true;
179             this.GroupTab.Enabled = true;
180             this.ListFilters.Enabled = true;
181             this.EditFilterGroup.Enabled = false;
182
183             if (tab.IsDistributableTabType)
184             {
185                 this.ButtonNew.Enabled = true;
186                 if (this.ListFilters.SelectedIndex > -1)
187                 {
188                     this.ButtonEdit.Enabled = true;
189                     this.ButtonDelete.Enabled = true;
190                     this.ButtonRuleUp.Enabled = true;
191                     this.ButtonRuleDown.Enabled = true;
192                     this.ButtonRuleCopy.Enabled = true;
193                     this.ButtonRuleMove.Enabled = true;
194                     this.buttonRuleToggleEnabled.Enabled = true;
195                 }
196                 else
197                 {
198                     this.ButtonEdit.Enabled = false;
199                     this.ButtonDelete.Enabled = false;
200                     this.ButtonRuleUp.Enabled = false;
201                     this.ButtonRuleDown.Enabled = false;
202                     this.ButtonRuleCopy.Enabled = false;
203                     this.ButtonRuleMove.Enabled = false;
204                     this.buttonRuleToggleEnabled.Enabled = false;
205                 }
206             }
207             else
208             {
209                 this.ButtonNew.Enabled = false;
210                 this.ButtonEdit.Enabled = false;
211                 this.ButtonDelete.Enabled = false;
212                 this.ButtonRuleUp.Enabled = false;
213                 this.ButtonRuleDown.Enabled = false;
214                 this.ButtonRuleCopy.Enabled = false;
215                 this.ButtonRuleMove.Enabled = false;
216                 this.buttonRuleToggleEnabled.Enabled = false;
217             }
218
219             this.LabelTabType.Text = tab.TabType switch
220             {
221                 MyCommon.TabUsageType.Home => Properties.Resources.TabUsageTypeName_Home,
222                 MyCommon.TabUsageType.Mentions => Properties.Resources.TabUsageTypeName_Mentions,
223                 MyCommon.TabUsageType.DirectMessage => Properties.Resources.TabUsageTypeName_DirectMessage,
224                 MyCommon.TabUsageType.Favorites => Properties.Resources.TabUsageTypeName_Favorites,
225                 MyCommon.TabUsageType.UserDefined => Properties.Resources.TabUsageTypeName_UserDefined,
226                 MyCommon.TabUsageType.PublicSearch => Properties.Resources.TabUsageTypeName_PublicSearch,
227                 MyCommon.TabUsageType.Lists => Properties.Resources.TabUsageTypeName_Lists,
228                 MyCommon.TabUsageType.Related => Properties.Resources.TabUsageTypeName_Related,
229                 MyCommon.TabUsageType.UserTimeline => Properties.Resources.TabUsageTypeName_UserTimeline,
230                 MyCommon.TabUsageType.Mute => "Mute",
231                 MyCommon.TabUsageType.SearchResults => "SearchResults",
232                 _ => "UNKNOWN",
233             };
234
235             if (tab.IsDefaultTabType || tab.Protected)
236             {
237                 this.ButtonDeleteTab.Enabled = false;
238             }
239             else
240             {
241                 this.ButtonDeleteTab.Enabled = true;
242             }
243             this.ButtonClose.Enabled = true;
244         }
245
246         public void SetCurrent(string tabName)
247         {
248             var index = this.tabs.FindIndex(x => x.TabName == tabName);
249             if (index == -1)
250                 throw new ArgumentException($"Unknown tab: {tabName}", nameof(tabName));
251
252             this.selectedTabIndex = index;
253         }
254
255         public void AddNewFilter(string id, string msg)
256         {
257             // 元フォームから直接呼ばれる
258             this.ButtonNew.Enabled = false;
259             this.ButtonEdit.Enabled = false;
260             this.ButtonRuleUp.Enabled = false;
261             this.ButtonRuleDown.Enabled = false;
262             this.ButtonRuleCopy.Enabled = false;
263             this.ButtonRuleMove.Enabled = false;
264             this.buttonRuleToggleEnabled.Enabled = false;
265             this.ButtonDelete.Enabled = false;
266             this.ButtonClose.Enabled = false;
267             this.EditFilterGroup.Enabled = true;
268             this.ListTabs.Enabled = false;
269             this.GroupTab.Enabled = false;
270             this.ListFilters.Enabled = false;
271
272             this.RadioAND.Checked = true;
273             this.RadioPLUS.Checked = false;
274             this.UID.Text = id;
275             this.UID.SelectAll();
276             this.MSG1.Text = msg;
277             this.MSG1.SelectAll();
278             this.MSG2.Text = id + msg;
279             this.MSG2.SelectAll();
280             this.TextSource.Text = "";
281             this.UID.Enabled = true;
282             this.MSG1.Enabled = true;
283             this.MSG2.Enabled = false;
284             this.CheckRegex.Checked = false;
285             this.CheckURL.Checked = false;
286             this.CheckCaseSensitive.Checked = false;
287             this.CheckRetweet.Checked = false;
288             this.CheckLambda.Checked = false;
289
290             this.RadioExAnd.Checked = true;
291             this.RadioExPLUS.Checked = false;
292             this.ExUID.Text = "";
293             this.ExUID.SelectAll();
294             this.ExMSG1.Text = "";
295             this.ExMSG1.SelectAll();
296             this.ExMSG2.Text = "";
297             this.ExMSG2.SelectAll();
298             this.TextExSource.Text = "";
299             this.ExUID.Enabled = true;
300             this.ExMSG1.Enabled = true;
301             this.ExMSG2.Enabled = false;
302             this.CheckExRegex.Checked = false;
303             this.CheckExURL.Checked = false;
304             this.CheckExCaseSensitive.Checked = false;
305             this.CheckExRetweet.Checked = false;
306             this.CheckExLambDa.Checked = false;
307
308             this.OptCopy.Checked = true;
309             this.CheckMark.Checked = true;
310             this.UID.Focus();
311             this.mode = EDITMODE.AddNew;
312             this.directAdd = true;
313         }
314
315         private void ButtonNew_Click(object sender, EventArgs e)
316         {
317             this.ButtonNew.Enabled = false;
318             this.ButtonEdit.Enabled = false;
319             this.ButtonClose.Enabled = false;
320             this.ButtonRuleUp.Enabled = false;
321             this.ButtonRuleDown.Enabled = false;
322             this.ButtonRuleCopy.Enabled = false;
323             this.ButtonRuleMove.Enabled = false;
324             this.buttonRuleToggleEnabled.Enabled = false;
325             this.ButtonDelete.Enabled = false;
326             this.ButtonClose.Enabled = false;
327             this.EditFilterGroup.Enabled = true;
328             this.ListTabs.Enabled = false;
329             this.GroupTab.Enabled = false;
330             this.ListFilters.Enabled = false;
331
332             this.RadioAND.Checked = true;
333             this.RadioPLUS.Checked = false;
334             this.UID.Text = "";
335             this.MSG1.Text = "";
336             this.MSG2.Text = "";
337             this.TextSource.Text = "";
338             this.UID.Enabled = true;
339             this.MSG1.Enabled = true;
340             this.MSG2.Enabled = false;
341             this.CheckRegex.Checked = false;
342             this.CheckURL.Checked = false;
343             this.CheckCaseSensitive.Checked = false;
344             this.CheckRetweet.Checked = false;
345             this.CheckLambda.Checked = false;
346
347             this.RadioExAnd.Checked = true;
348             this.RadioExPLUS.Checked = false;
349             this.ExUID.Text = "";
350             this.ExMSG1.Text = "";
351             this.ExMSG2.Text = "";
352             this.TextExSource.Text = "";
353             this.ExUID.Enabled = true;
354             this.ExMSG1.Enabled = true;
355             this.ExMSG2.Enabled = false;
356             this.CheckExRegex.Checked = false;
357             this.CheckExURL.Checked = false;
358             this.CheckExCaseSensitive.Checked = false;
359             this.CheckExRetweet.Checked = false;
360             this.CheckExLambDa.Checked = false;
361
362             this.OptCopy.Checked = true;
363             this.CheckMark.Checked = true;
364             this.UID.Focus();
365             this.mode = EDITMODE.AddNew;
366         }
367
368         private void ButtonEdit_Click(object sender, EventArgs e)
369         {
370             if (this.ListFilters.SelectedIndex == -1) return;
371
372             this.ShowDetail();
373
374             var idx = this.ListFilters.SelectedIndex;
375             this.ListFilters.SelectedIndex = -1;
376             this.ListFilters.SelectedIndex = idx;
377             this.ListFilters.Enabled = false;
378
379             this.ButtonNew.Enabled = false;
380             this.ButtonEdit.Enabled = false;
381             this.ButtonDelete.Enabled = false;
382             this.ButtonClose.Enabled = false;
383             this.ButtonRuleUp.Enabled = false;
384             this.ButtonRuleDown.Enabled = false;
385             this.ButtonRuleCopy.Enabled = false;
386             this.ButtonRuleMove.Enabled = false;
387             this.buttonRuleToggleEnabled.Enabled = false;
388             this.EditFilterGroup.Enabled = true;
389             this.ListTabs.Enabled = false;
390             this.GroupTab.Enabled = false;
391
392             this.mode = EDITMODE.Edit;
393         }
394
395         private void ButtonDelete_Click(object sender, EventArgs e)
396         {
397             var selectedCount = this.ListFilters.SelectedIndices.Count;
398             if (selectedCount == 0) return;
399
400             string tmp;
401
402             if (selectedCount == 1)
403             {
404                 tmp = string.Format(Properties.Resources.ButtonDelete_ClickText1, Environment.NewLine, this.ListFilters.SelectedItem);
405             }
406             else
407             {
408                 tmp = string.Format(Properties.Resources.ButtonDelete_ClickText3, selectedCount);
409             }
410
411             var rslt = MessageBox.Show(tmp, Properties.Resources.ButtonDelete_ClickText2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
412             if (rslt == DialogResult.Cancel) return;
413
414             var indices = this.ListFilters.SelectedIndices.Cast<int>().Reverse().ToArray();  // 後ろの要素から削除
415             var tab = (FilterTabModel)this.SelectedTab!;
416
417             using (ControlTransaction.Update(this.ListFilters))
418             {
419                 foreach (var idx in indices)
420                 {
421                     tab.RemoveFilter((PostFilterRule)this.ListFilters.Items[idx]);
422                     this.ListFilters.Items.RemoveAt(idx);
423                 }
424             }
425         }
426
427         private void ButtonCancel_Click(object sender, EventArgs e)
428         {
429             this.ListTabs.Enabled = true;
430             this.GroupTab.Enabled = true;
431             this.ListFilters.Enabled = true;
432             this.ListFilters.Focus();
433             if (this.ListFilters.SelectedIndex != -1)
434             {
435                 this.ShowDetail();
436             }
437             this.EditFilterGroup.Enabled = false;
438             this.ButtonNew.Enabled = true;
439             if (this.ListFilters.SelectedIndex > -1)
440             {
441                 this.ButtonEdit.Enabled = true;
442                 this.ButtonDelete.Enabled = true;
443                 this.ButtonRuleUp.Enabled = true;
444                 this.ButtonRuleDown.Enabled = true;
445                 this.ButtonRuleCopy.Enabled = true;
446                 this.ButtonRuleMove.Enabled = true;
447                 this.buttonRuleToggleEnabled.Enabled = true;
448             }
449             else
450             {
451                 this.ButtonEdit.Enabled = false;
452                 this.ButtonDelete.Enabled = false;
453                 this.ButtonRuleUp.Enabled = false;
454                 this.ButtonRuleDown.Enabled = false;
455                 this.ButtonRuleCopy.Enabled = false;
456                 this.ButtonRuleMove.Enabled = false;
457                 this.buttonRuleToggleEnabled.Enabled = false;
458             }
459             this.ButtonClose.Enabled = true;
460             if (this.directAdd)
461             {
462                 this.Close();
463             }
464         }
465
466         private void ShowDetail()
467         {
468             if (this.directAdd) return;
469
470             if (this.ListFilters.SelectedIndex > -1)
471             {
472                 var fc = (PostFilterRule)this.ListFilters.SelectedItem;
473                 if (fc.UseNameField)
474                 {
475                     this.RadioAND.Checked = true;
476                     this.RadioPLUS.Checked = false;
477                     this.UID.Enabled = true;
478                     this.MSG1.Enabled = true;
479                     this.MSG2.Enabled = false;
480                     this.UID.Text = fc.FilterName;
481                     this.UID.SelectAll();
482                     this.MSG1.Text = string.Join(" ", fc.FilterBody);
483                     this.MSG1.SelectAll();
484                     this.MSG2.Text = "";
485                 }
486                 else
487                 {
488                     this.RadioPLUS.Checked = true;
489                     this.RadioAND.Checked = false;
490                     this.UID.Enabled = false;
491                     this.MSG1.Enabled = false;
492                     this.MSG2.Enabled = true;
493                     this.UID.Text = "";
494                     this.MSG1.Text = "";
495                     this.MSG2.Text = string.Join(" ", fc.FilterBody);
496                     this.MSG2.SelectAll();
497                 }
498                 this.TextSource.Text = fc.FilterSource;
499                 this.CheckRegex.Checked = fc.UseRegex;
500                 this.CheckURL.Checked = fc.FilterByUrl;
501                 this.CheckCaseSensitive.Checked = fc.CaseSensitive;
502                 this.CheckRetweet.Checked = fc.FilterRt;
503                 this.CheckLambda.Checked = fc.UseLambda;
504
505                 if (fc.ExUseNameField)
506                 {
507                     this.RadioExAnd.Checked = true;
508                     this.RadioExPLUS.Checked = false;
509                     this.ExUID.Enabled = true;
510                     this.ExMSG1.Enabled = true;
511                     this.ExMSG2.Enabled = false;
512                     this.ExUID.Text = fc.ExFilterName;
513                     this.ExUID.SelectAll();
514                     this.ExMSG1.Text = string.Join(" ", fc.ExFilterBody);
515                     this.ExMSG1.SelectAll();
516                     this.ExMSG2.Text = "";
517                 }
518                 else
519                 {
520                     this.RadioExPLUS.Checked = true;
521                     this.RadioExAnd.Checked = false;
522                     this.ExUID.Enabled = false;
523                     this.ExMSG1.Enabled = false;
524                     this.ExMSG2.Enabled = true;
525                     this.ExUID.Text = "";
526                     this.ExMSG1.Text = "";
527                     this.ExMSG2.Text = string.Join(" ", fc.ExFilterBody);
528                     this.ExMSG2.SelectAll();
529                 }
530                 this.TextExSource.Text = fc.ExFilterSource;
531                 this.CheckExRegex.Checked = fc.ExUseRegex;
532                 this.CheckExURL.Checked = fc.ExFilterByUrl;
533                 this.CheckExCaseSensitive.Checked = fc.ExCaseSensitive;
534                 this.CheckExRetweet.Checked = fc.ExFilterRt;
535                 this.CheckExLambDa.Checked = fc.ExUseLambda;
536
537                 if (fc.MoveMatches)
538                 {
539                     this.OptMove.Checked = true;
540                 }
541                 else
542                 {
543                     this.OptCopy.Checked = true;
544                 }
545                 this.CheckMark.Checked = fc.MarkMatches;
546
547                 this.ButtonEdit.Enabled = true;
548                 this.ButtonDelete.Enabled = true;
549                 this.ButtonRuleUp.Enabled = true;
550                 this.ButtonRuleDown.Enabled = true;
551                 this.ButtonRuleCopy.Enabled = true;
552                 this.ButtonRuleMove.Enabled = true;
553                 this.buttonRuleToggleEnabled.Enabled = true;
554             }
555             else
556             {
557                 this.RadioAND.Checked = true;
558                 this.RadioPLUS.Checked = false;
559                 this.UID.Enabled = true;
560                 this.MSG1.Enabled = true;
561                 this.MSG2.Enabled = false;
562                 this.UID.Text = "";
563                 this.MSG1.Text = "";
564                 this.MSG2.Text = "";
565                 this.TextSource.Text = "";
566                 this.CheckRegex.Checked = false;
567                 this.CheckURL.Checked = false;
568                 this.CheckCaseSensitive.Checked = false;
569                 this.CheckRetweet.Checked = false;
570                 this.CheckLambda.Checked = false;
571
572                 this.RadioExAnd.Checked = true;
573                 this.RadioExPLUS.Checked = false;
574                 this.ExUID.Enabled = true;
575                 this.ExMSG1.Enabled = true;
576                 this.ExMSG2.Enabled = false;
577                 this.ExUID.Text = "";
578                 this.ExMSG1.Text = "";
579                 this.ExMSG2.Text = "";
580                 this.TextExSource.Text = "";
581                 this.CheckExRegex.Checked = false;
582                 this.CheckExURL.Checked = false;
583                 this.CheckExCaseSensitive.Checked = false;
584                 this.CheckExRetweet.Checked = false;
585                 this.CheckExLambDa.Checked = false;
586
587                 this.OptCopy.Checked = true;
588                 this.CheckMark.Checked = true;
589
590                 this.ButtonEdit.Enabled = false;
591                 this.ButtonDelete.Enabled = false;
592                 this.ButtonRuleUp.Enabled = false;
593                 this.ButtonRuleDown.Enabled = false;
594                 this.ButtonRuleCopy.Enabled = false;
595                 this.ButtonRuleMove.Enabled = false;
596                 this.buttonRuleToggleEnabled.Enabled = false;
597             }
598         }
599
600         private void RadioAND_CheckedChanged(object sender, EventArgs e)
601         {
602             var flg = this.RadioAND.Checked;
603             this.UID.Enabled = flg;
604             this.MSG1.Enabled = flg;
605             this.MSG2.Enabled = !flg;
606         }
607
608         private void ButtonOK_Click(object sender, EventArgs e)
609         {
610             // 入力チェック
611             if (!this.CheckMatchRule(out var isBlankMatch) || !this.CheckExcludeRule(out var isBlankExclude))
612             {
613                 return;
614             }
615             if (isBlankMatch && isBlankExclude)
616             {
617                 MessageBox.Show(Properties.Resources.ButtonOK_ClickText1, Properties.Resources.ButtonOK_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
618                 return;
619             }
620
621             var tab = (FilterTabModel)this.SelectedTab!;
622             var i = this.ListFilters.SelectedIndex;
623
624             PostFilterRule ft;
625             if (this.mode == EDITMODE.AddNew)
626                 ft = new PostFilterRule();
627             else
628                 ft = (PostFilterRule)this.ListFilters.SelectedItem;
629
630             if (tab.TabType != MyCommon.TabUsageType.Mute)
631             {
632                 ft.MoveMatches = this.OptMove.Checked;
633                 ft.MarkMatches = this.CheckMark.Checked;
634             }
635             else
636             {
637                 ft.MoveMatches = true;
638                 ft.MarkMatches = false;
639             }
640
641             var bdy = "";
642             if (this.RadioAND.Checked)
643             {
644                 ft.FilterName = this.UID.Text;
645                 var owner = (TweenMain)this.Owner;
646                 var cnt = owner.AtIdSupl.ItemCount;
647                 owner.AtIdSupl.AddItem("@" + ft.FilterName);
648                 if (cnt != owner.AtIdSupl.ItemCount)
649                 {
650                     owner.MarkSettingAtIdModified();
651                 }
652                 ft.UseNameField = true;
653                 bdy = this.MSG1.Text;
654             }
655             else
656             {
657                 ft.FilterName = "";
658                 ft.UseNameField = false;
659                 bdy = this.MSG2.Text;
660             }
661             ft.FilterSource = this.TextSource.Text;
662
663             if (this.CheckRegex.Checked || this.CheckLambda.Checked)
664             {
665                 ft.FilterBody = new[] { bdy };
666             }
667             else
668             {
669                 ft.FilterBody = bdy.Split(' ', ' ')
670                     .Where(x => !MyCommon.IsNullOrEmpty(x))
671                     .ToArray();
672             }
673
674             ft.UseRegex = this.CheckRegex.Checked;
675             ft.FilterByUrl = this.CheckURL.Checked;
676             ft.CaseSensitive = this.CheckCaseSensitive.Checked;
677             ft.FilterRt = this.CheckRetweet.Checked;
678             ft.UseLambda = this.CheckLambda.Checked;
679
680             bdy = "";
681             if (this.RadioExAnd.Checked)
682             {
683                 ft.ExFilterName = this.ExUID.Text;
684                 ft.ExUseNameField = true;
685                 bdy = this.ExMSG1.Text;
686             }
687             else
688             {
689                 ft.ExFilterName = "";
690                 ft.ExUseNameField = false;
691                 bdy = this.ExMSG2.Text;
692             }
693             ft.ExFilterSource = this.TextExSource.Text;
694
695             if (this.CheckExRegex.Checked || this.CheckExLambDa.Checked)
696             {
697                 ft.ExFilterBody = new[] { bdy };
698             }
699             else
700             {
701                 ft.ExFilterBody = bdy.Split(' ', ' ')
702                     .Where(x => !MyCommon.IsNullOrEmpty(x))
703                     .ToArray();
704             }
705
706             ft.ExUseRegex = this.CheckExRegex.Checked;
707             ft.ExFilterByUrl = this.CheckExURL.Checked;
708             ft.ExCaseSensitive = this.CheckExCaseSensitive.Checked;
709             ft.ExFilterRt = this.CheckExRetweet.Checked;
710             ft.ExUseLambda = this.CheckExLambDa.Checked;
711
712             if (this.mode == EDITMODE.AddNew)
713             {
714                 if (!tab.AddFilter(ft))
715                     MessageBox.Show(Properties.Resources.ButtonOK_ClickText4, Properties.Resources.ButtonOK_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Error);
716             }
717
718             this.SetFilters(tab);
719             this.ListFilters.SelectedIndex = -1;
720             if (this.mode == EDITMODE.AddNew)
721             {
722                 this.ListFilters.SelectedIndex = this.ListFilters.Items.Count - 1;
723             }
724             else
725             {
726                 this.ListFilters.SelectedIndex = i;
727             }
728             this.mode = EDITMODE.None;
729
730             if (this.directAdd)
731             {
732                 this.Close();
733             }
734         }
735
736         private bool IsValidLambdaExp(string text)
737             => false; // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する
738
739         private bool IsValidRegexp(string text)
740         {
741             try
742             {
743                 new Regex(text);
744             }
745             catch (Exception ex)
746             {
747                 MessageBox.Show(Properties.Resources.ButtonOK_ClickText3 + ex.Message, Properties.Resources.ButtonOK_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
748                 return false;
749             }
750             return true;
751         }
752
753         private bool CheckMatchRule(out bool isBlank)
754         {
755             isBlank = false;
756             if (this.RadioAND.Checked)
757             {
758                 if (MyCommon.IsNullOrEmpty(this.UID.Text) && MyCommon.IsNullOrEmpty(this.MSG1.Text) && MyCommon.IsNullOrEmpty(this.TextSource.Text) && this.CheckRetweet.Checked == false)
759                 {
760                     isBlank = true;
761                     return true;
762                 }
763                 if (this.CheckLambda.Checked)
764                 {
765                     if (!this.IsValidLambdaExp(this.UID.Text))
766                     {
767                         return false;
768                     }
769                     if (!this.IsValidLambdaExp(this.MSG1.Text))
770                     {
771                         return false;
772                     }
773                 }
774                 else if (this.CheckRegex.Checked)
775                 {
776                     if (!this.IsValidRegexp(this.UID.Text))
777                     {
778                         return false;
779                     }
780                     if (!this.IsValidRegexp(this.MSG1.Text))
781                     {
782                         return false;
783                     }
784                 }
785             }
786             else
787             {
788                 if (MyCommon.IsNullOrEmpty(this.MSG2.Text) && MyCommon.IsNullOrEmpty(this.TextSource.Text) && this.CheckRetweet.Checked == false)
789                 {
790                     isBlank = true;
791                     return true;
792                 }
793                 if (this.CheckLambda.Checked && !this.IsValidLambdaExp(this.MSG2.Text))
794                 {
795                     return false;
796                 }
797                 else if (this.CheckRegex.Checked && !this.IsValidRegexp(this.MSG2.Text))
798                 {
799                     return false;
800                 }
801             }
802
803             if (this.CheckRegex.Checked && !this.IsValidRegexp(this.TextSource.Text))
804             {
805                 return false;
806             }
807             return true;
808         }
809
810         private bool CheckExcludeRule(out bool isBlank)
811         {
812             isBlank = false;
813             if (this.RadioExAnd.Checked)
814             {
815                 if (MyCommon.IsNullOrEmpty(this.ExUID.Text) && MyCommon.IsNullOrEmpty(this.ExMSG1.Text) && MyCommon.IsNullOrEmpty(this.TextExSource.Text) && this.CheckExRetweet.Checked == false)
816                 {
817                     isBlank = true;
818                     return true;
819                 }
820                 if (this.CheckExLambDa.Checked)
821                 {
822                     if (!this.IsValidLambdaExp(this.ExUID.Text))
823                     {
824                         return false;
825                     }
826                     if (!this.IsValidLambdaExp(this.ExMSG1.Text))
827                     {
828                         return false;
829                     }
830                 }
831                 else if (this.CheckExRegex.Checked)
832                 {
833                     if (!this.IsValidRegexp(this.ExUID.Text))
834                     {
835                         return false;
836                     }
837                     if (!this.IsValidRegexp(this.ExMSG1.Text))
838                     {
839                         return false;
840                     }
841                 }
842             }
843             else
844             {
845                 if (MyCommon.IsNullOrEmpty(this.ExMSG2.Text) && MyCommon.IsNullOrEmpty(this.TextExSource.Text) && this.CheckExRetweet.Checked == false)
846                 {
847                     isBlank = true;
848                     return true;
849                 }
850                 if (this.CheckExLambDa.Checked && !this.IsValidLambdaExp(this.ExMSG2.Text))
851                 {
852                     return false;
853                 }
854                 else if (this.CheckExRegex.Checked && !this.IsValidRegexp(this.ExMSG2.Text))
855                 {
856                     return false;
857                 }
858             }
859
860             if (this.CheckExRegex.Checked && !this.IsValidRegexp(this.TextExSource.Text))
861             {
862                 return false;
863             }
864
865             return true;
866         }
867
868         private void ListFilters_SelectedIndexChanged(object sender, EventArgs e)
869         {
870             if (this.multiSelState != MultiSelectionState.None) // 複数選択処理中は無視する
871                 return;
872
873             this.ShowDetail();
874
875             var selectedCount = this.ListFilters.SelectedIndices.Count;
876             if (selectedCount == 0)
877             {
878                 this.RuleEnableButtonMode = EnableButtonMode.NotSelected;
879             }
880             else
881             {
882                 if (selectedCount == 1 ||
883                     this.RuleEnableButtonMode == EnableButtonMode.NotSelected)
884                 {
885                     var topItem = (PostFilterRule)this.ListFilters.SelectedItem;
886                     this.RuleEnableButtonMode = topItem.Enabled ? EnableButtonMode.Disable : EnableButtonMode.Enable;
887                 }
888             }
889         }
890
891         private void ButtonClose_Click(object sender, EventArgs e)
892             => this.Close();
893
894         private void FilterDialog_FormClosed(object sender, FormClosedEventArgs e)
895             => this.directAdd = false;
896
897         private void FilterDialog_KeyDown(object sender, KeyEventArgs e)
898         {
899             if (e.KeyCode == Keys.Escape)
900             {
901                 if (this.EditFilterGroup.Enabled)
902                     this.ButtonCancel_Click(this.ButtonCancel, EventArgs.Empty);
903                 else
904                     this.ButtonClose_Click(this.ButtonClose, EventArgs.Empty);
905             }
906         }
907
908         private void ListFilters_DoubleClick(object sender, EventArgs e)
909         {
910             var idx = this.ListFilters.SelectedIndex;
911             if (idx == -1) return;
912
913             var midx = this.ListFilters.IndexFromPoint(this.ListFilters.PointToClient(Control.MousePosition));
914             if (midx == ListBox.NoMatches || idx != midx) return;
915
916             this.ButtonEdit_Click(sender, e);
917         }
918
919         private void FilterDialog_Shown(object sender, EventArgs e)
920         {
921             this.ListTabs.DisplayMember = nameof(TabModel.TabName);
922
923             this.ComboSound.Items.Clear();
924             this.ComboSound.Items.Add("");
925             var oDir = new DirectoryInfo(Application.StartupPath + Path.DirectorySeparatorChar);
926             if (Directory.Exists(Path.Combine(Application.StartupPath, "Sounds")))
927             {
928                 oDir = oDir.GetDirectories("Sounds")[0];
929             }
930             foreach (var oFile in oDir.GetFiles("*.wav"))
931             {
932                 this.ComboSound.Items.Add(oFile.Name);
933             }
934
935             this.idlist.Clear();
936             foreach (var tmp in ((TweenMain)this.Owner).AtIdSupl.GetItemList())
937             {
938                 this.idlist.Add(tmp.Remove(0, 1));  // @文字削除
939             }
940             this.UID.AutoCompleteCustomSource.Clear();
941             this.UID.AutoCompleteCustomSource.AddRange(this.idlist.ToArray());
942
943             this.ExUID.AutoCompleteCustomSource.Clear();
944             this.ExUID.AutoCompleteCustomSource.AddRange(this.idlist.ToArray());
945
946             // 選択タブ変更
947             this.ListTabs.SelectedIndex = this.selectedTabIndex;
948         }
949
950         private void ListTabs_SelectedIndexChanged(object sender, EventArgs e)
951         {
952             this.selectedTabIndex = this.ListTabs.SelectedIndex;
953
954             var selectedTab = this.SelectedTab;
955             if (selectedTab != null)
956                 this.SetFilters(selectedTab);
957             else
958                 this.ListFilters.Items.Clear();
959         }
960
961         private async void ButtonAddTab_Click(object sender, EventArgs e)
962         {
963             string? tabName = null;
964             MyCommon.TabUsageType tabType;
965             using (var inputName = new InputTabName())
966             {
967                 inputName.TabName = this.sts.MakeTabName("MyTab");
968                 inputName.IsShowUsage = true;
969                 inputName.ShowDialog();
970                 if (inputName.DialogResult == DialogResult.Cancel) return;
971                 tabName = inputName.TabName;
972                 tabType = inputName.Usage;
973             }
974             if (!MyCommon.IsNullOrEmpty(tabName))
975             {
976                 // List対応
977                 ListElement? list = null;
978                 if (tabType == MyCommon.TabUsageType.Lists)
979                 {
980                     try
981                     {
982                         using var dialog = new WaitingDialog(Properties.Resources.ListsGetting);
983                         var cancellationToken = dialog.EnableCancellation();
984
985                         var task = ((TweenMain)this.Owner).TwitterInstance.GetListsApi();
986                         await dialog.WaitForAsync(this, task);
987
988                         cancellationToken.ThrowIfCancellationRequested();
989                     }
990                     catch (OperationCanceledException)
991                     {
992                         return;
993                     }
994                     catch (WebApiException ex)
995                     {
996                         MessageBox.Show("Failed to get lists. (" + ex.Message + ")");
997                     }
998
999                     using var listAvail = new ListAvailable();
1000
1001                     if (listAvail.ShowDialog(this) == DialogResult.Cancel)
1002                         return;
1003                     if (listAvail.SelectedList == null)
1004                         return;
1005                     list = listAvail.SelectedList;
1006                 }
1007
1008                 TabModel tab;
1009                 switch (tabType)
1010                 {
1011                     case MyCommon.TabUsageType.UserDefined:
1012                         tab = new FilterTabModel(tabName);
1013                         break;
1014                     case MyCommon.TabUsageType.PublicSearch:
1015                         tab = new PublicSearchTabModel(tabName);
1016                         break;
1017                     case MyCommon.TabUsageType.Lists:
1018                         tab = new ListTimelineTabModel(tabName, list!);
1019                         break;
1020                     default:
1021                         return;
1022                 }
1023
1024                 if (!this.sts.AddTab(tab) || !((TweenMain)this.Owner).AddNewTab(tab, startup: false))
1025                 {
1026                     var tmp = string.Format(Properties.Resources.AddTabMenuItem_ClickText1, tabName);
1027                     MessageBox.Show(tmp, Properties.Resources.AddTabMenuItem_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
1028                     return;
1029                 }
1030                 else
1031                 {
1032                     // タブ作成成功
1033                     this.RefreshListTabs();
1034                 }
1035             }
1036         }
1037
1038         private void ButtonDeleteTab_Click(object sender, EventArgs e)
1039         {
1040             var selectedTab = this.SelectedTab;
1041             if (selectedTab != null)
1042             {
1043                 var tb = selectedTab.TabName;
1044                 var idx = this.ListTabs.SelectedIndex;
1045                 if (((TweenMain)this.Owner).RemoveSpecifiedTab(tb, true))
1046                 {
1047                     this.RefreshListTabs();
1048                     idx -= 1;
1049                     if (idx < 0) idx = 0;
1050                     this.ListTabs.SelectedIndex = idx;
1051                 }
1052             }
1053         }
1054
1055         private void ButtonRenameTab_Click(object sender, EventArgs e)
1056         {
1057             var selectedTab = this.SelectedTab;
1058             if (selectedTab != null)
1059             {
1060                 var origTabName = selectedTab.TabName;
1061                 if (((TweenMain)this.Owner).TabRename(origTabName, out _))
1062                     this.RefreshListTabs();
1063             }
1064         }
1065
1066         private void CheckManageRead_CheckedChanged(object sender, EventArgs e)
1067         {
1068             var selectedTab = this.SelectedTab;
1069             if (selectedTab != null)
1070             {
1071                 ((TweenMain)this.Owner).ChangeTabUnreadManage(
1072                     selectedTab.TabName,
1073                     this.CheckManageRead.Checked);
1074             }
1075         }
1076
1077         private void ButtonUp_Click(object sender, EventArgs e)
1078         {
1079             var selectedIndex = this.ListTabs.SelectedIndex;
1080
1081             if (selectedIndex == -1 || selectedIndex == 0)
1082                 return;
1083
1084             var selectedTab = this.tabs[selectedIndex];
1085             var selectedTabName = selectedTab.TabName;
1086
1087             var targetTab = this.tabs[selectedIndex - 1];
1088             var targetTabName = targetTab.TabName;
1089
1090             // ミュートタブは移動禁止
1091             if (selectedTab.TabType == MyCommon.TabUsageType.Mute || targetTab.TabType == MyCommon.TabUsageType.Mute)
1092                 return;
1093
1094             var tweenMain = (TweenMain)this.Owner;
1095             tweenMain.ReOrderTab(selectedTabName, targetTabName, true);
1096
1097             this.RefreshListTabs();
1098         }
1099
1100         private void ButtonDown_Click(object sender, EventArgs e)
1101         {
1102             var selectedIndex = this.ListTabs.SelectedIndex;
1103
1104             if (selectedIndex == -1 || selectedIndex == this.ListTabs.Items.Count - 1)
1105                 return;
1106
1107             var selectedTab = this.tabs[selectedIndex];
1108             var selectedTabName = selectedTab.TabName;
1109
1110             var targetTab = this.tabs[selectedIndex + 1];
1111             var targetTabName = targetTab.TabName;
1112
1113             // ミュートタブは移動禁止
1114             if (selectedTab.TabType == MyCommon.TabUsageType.Mute || targetTab.TabType == MyCommon.TabUsageType.Mute)
1115                 return;
1116
1117             var tweenMain = (TweenMain)this.Owner;
1118             tweenMain.ReOrderTab(selectedTabName, targetTabName, false);
1119
1120             this.RefreshListTabs();
1121         }
1122
1123         private void CheckLocked_CheckedChanged(object sender, EventArgs e)
1124         {
1125             var selectedTab = this.SelectedTab;
1126             if (selectedTab != null)
1127             {
1128                 selectedTab.Protected = this.CheckProtected.Checked;
1129                 this.ButtonDeleteTab.Enabled = !this.CheckProtected.Checked;
1130             }
1131         }
1132
1133         private void CheckNotifyNew_CheckedChanged(object sender, EventArgs e)
1134         {
1135             var selectedTab = this.SelectedTab;
1136             if (selectedTab != null)
1137             {
1138                 selectedTab.Notify = this.CheckNotifyNew.Checked;
1139             }
1140         }
1141
1142         private void ComboSound_SelectedIndexChanged(object sender, EventArgs e)
1143         {
1144             var selectedTab = this.SelectedTab;
1145             if (selectedTab != null)
1146             {
1147                 var filename = "";
1148                 if (this.ComboSound.SelectedIndex > -1) filename = this.ComboSound.SelectedItem.ToString();
1149                 selectedTab.SoundFile = filename;
1150             }
1151         }
1152
1153         private void RadioExAnd_CheckedChanged(object sender, EventArgs e)
1154         {
1155             var flg = this.RadioExAnd.Checked;
1156             this.ExUID.Enabled = flg;
1157             this.ExMSG1.Enabled = flg;
1158             this.ExMSG2.Enabled = !flg;
1159         }
1160
1161         private void OptMove_CheckedChanged(object sender, EventArgs e)
1162             => this.CheckMark.Enabled = !this.OptMove.Checked;
1163
1164         private void ButtonRuleUp_Click(object sender, EventArgs e)
1165             => this.MoveSelectedRules(up: true);
1166
1167         private void ButtonRuleDown_Click(object sender, EventArgs e)
1168             => this.MoveSelectedRules(up: false);
1169
1170         private void MoveSelectedRules(bool up)
1171         {
1172             var selectedTab = this.SelectedTab;
1173             if (selectedTab == null || this.ListFilters.SelectedIndices.Count == 0)
1174                 return;
1175
1176             var indices = this.ListFilters.SelectedIndices.Cast<int>().ToArray();
1177
1178             int diff;
1179             if (up)
1180             {
1181                 if (indices[0] <= 0) return;
1182                 diff = -1;
1183             }
1184             else
1185             {
1186                 if (indices[indices.Length - 1] >= this.ListFilters.Items.Count - 1) return;
1187                 diff = +1;
1188                 Array.Reverse(indices);  // 逆順にして、下にある要素から処理する
1189             }
1190
1191             var lastSelIdx = indices[0] + diff;
1192             var tab = (FilterTabModel)selectedTab;
1193
1194             try
1195             {
1196                 this.multiSelState |= MultiSelectionState.MoveSelected;
1197
1198                 using (ControlTransaction.Update(this.ListFilters))
1199                 {
1200                     this.ListFilters.SelectedIndices.Clear();
1201
1202                     foreach (var idx in indices)
1203                     {
1204                         var tidx = idx + diff;
1205                         var target = (PostFilterRule)this.ListFilters.Items[tidx];
1206
1207                         // 移動先にある要素と位置を入れ替える
1208                         this.ListFilters.Items.RemoveAt(tidx);
1209                         this.ListFilters.Items.Insert(idx, target);
1210
1211                         // 移動方向の先頭要素以外なら選択する
1212                         if (tidx != lastSelIdx)
1213                             this.ListFilters.SelectedIndex = tidx;
1214                     }
1215
1216                     tab.FilterArray = this.ListFilters.Items.Cast<PostFilterRule>().ToArray();
1217
1218                     // 移動方向の先頭要素は最後に選択する
1219                     // ※移動方向への自動スクロール目的
1220                     this.ListFilters.SelectedIndex = lastSelIdx;
1221                 }
1222             }
1223             finally
1224             {
1225                 this.multiSelState &= ~MultiSelectionState.MoveSelected;
1226             }
1227         }
1228
1229         private void ButtonRuleToggleEnabled_Click(object sender, EventArgs e)
1230         {
1231             if (this.RuleEnableButtonMode == EnableButtonMode.NotSelected)
1232                 return;
1233
1234             var enabled = this.RuleEnableButtonMode == EnableButtonMode.Enable;
1235
1236             foreach (var idx in this.ListFilters.SelectedIndices.Cast<int>())
1237             {
1238                 var filter = (PostFilterRule)this.ListFilters.Items[idx];
1239                 if (filter.Enabled != enabled)
1240                 {
1241                     filter.Enabled = enabled;
1242
1243                     var itemRect = this.ListFilters.GetItemRectangle(idx);
1244                     this.ListFilters.Invalidate(itemRect);
1245                 }
1246             }
1247
1248             this.RuleEnableButtonMode = enabled ? EnableButtonMode.Disable : EnableButtonMode.Enable;
1249         }
1250
1251         private void ButtonRuleCopy_Click(object sender, EventArgs e)
1252         {
1253             var selectedTab = this.SelectedTab;
1254             if (selectedTab != null && this.ListFilters.SelectedItem != null)
1255             {
1256                 TabModel[] destinationTabs;
1257                 using (var dialog = new TabsDialog(this.sts))
1258                 {
1259                     dialog.MultiSelect = true;
1260                     dialog.Text = Properties.Resources.ButtonRuleCopy_ClickText1;
1261
1262                     if (dialog.ShowDialog(this) == DialogResult.Cancel) return;
1263
1264                     destinationTabs = dialog.SelectedTabs;
1265                 }
1266
1267                 var currentTab = (FilterTabModel)selectedTab;
1268                 var filters = new List<PostFilterRule>();
1269
1270                 foreach (int idx in this.ListFilters.SelectedIndices)
1271                 {
1272                     filters.Add(currentTab.FilterArray[idx].Clone());
1273                 }
1274                 foreach (var tb in destinationTabs.Cast<FilterTabModel>())
1275                 {
1276                     if (tb.TabName == currentTab.TabName) continue;
1277
1278                     foreach (var flt in filters)
1279                     {
1280                         if (!tb.FilterArray.Contains(flt))
1281                             tb.AddFilter(flt.Clone());
1282                     }
1283                 }
1284                 this.SetFilters(selectedTab);
1285             }
1286         }
1287
1288         private void ButtonRuleMove_Click(object sender, EventArgs e)
1289         {
1290             var selectedTab = this.SelectedTab;
1291             if (selectedTab != null && this.ListFilters.SelectedItem != null)
1292             {
1293                 TabModel[] destinationTabs;
1294                 using (var dialog = new TabsDialog(this.sts))
1295                 {
1296                     dialog.MultiSelect = true;
1297                     dialog.Text = Properties.Resources.ButtonRuleMove_ClickText1;
1298
1299                     if (dialog.ShowDialog(this) == DialogResult.Cancel) return;
1300
1301                     destinationTabs = dialog.SelectedTabs;
1302                 }
1303                 var currentTab = (FilterTabModel)selectedTab;
1304                 var filters = new List<PostFilterRule>();
1305
1306                 foreach (int idx in this.ListFilters.SelectedIndices)
1307                 {
1308                     filters.Add(currentTab.FilterArray[idx].Clone());
1309                 }
1310                 if (destinationTabs.Length == 1 && destinationTabs[0].TabName == currentTab.TabName) return;
1311                 foreach (var tb in destinationTabs.Cast<FilterTabModel>())
1312                 {
1313                     if (tb.TabName == currentTab.TabName) continue;
1314
1315                     foreach (var flt in filters)
1316                     {
1317                         if (!tb.FilterArray.Contains(flt))
1318                             tb.AddFilter(flt.Clone());
1319                     }
1320                 }
1321                 for (var idx = this.ListFilters.Items.Count - 1; idx >= 0; idx--)
1322                 {
1323                     if (this.ListFilters.GetSelected(idx))
1324                     {
1325                         currentTab.RemoveFilter((PostFilterRule)this.ListFilters.Items[idx]);
1326                         this.ListFilters.Items.RemoveAt(idx);
1327                     }
1328                 }
1329                 this.SetFilters(selectedTab);
1330             }
1331         }
1332
1333         private void FilterTextBox_KeyDown(object sender, KeyEventArgs e)
1334         {
1335             if (e.KeyCode == Keys.Space && e.Modifiers == (Keys.Shift | Keys.Control))
1336             {
1337                 var main = (TweenMain)this.Owner;
1338                 var tbox = (TextBox)sender;
1339                 if (tbox.SelectionStart > 0)
1340                 {
1341                     var endidx = tbox.SelectionStart - 1;
1342                     for (var i = tbox.SelectionStart - 1; i >= 0; i--)
1343                     {
1344                         var c = tbox.Text[i];
1345                         if (char.IsLetterOrDigit(c) || c == '_')
1346                         {
1347                             continue;
1348                         }
1349                         string startstr;
1350                         if (c == '@')
1351                         {
1352                             startstr = tbox.Text.Substring(i + 1, endidx - i);
1353                             main.ShowSuplDialog(tbox, main.AtIdSupl, startstr.Length + 1, startstr);
1354                         }
1355                         else if (c == '#')
1356                         {
1357                             startstr = tbox.Text.Substring(i + 1, endidx - i);
1358                             main.ShowSuplDialog(tbox, main.HashSupl, startstr.Length + 1, startstr);
1359                         }
1360                         else
1361                         {
1362                             break;
1363                         }
1364                     }
1365                     e.Handled = true;
1366                 }
1367             }
1368         }
1369
1370         private void FilterTextBox_KeyPress(object sender, KeyPressEventArgs e)
1371         {
1372             var main = (TweenMain)this.Owner;
1373             var tbox = (TextBox)sender;
1374             if (e.KeyChar == '@')
1375             {
1376                 // @マーク
1377                 main.ShowSuplDialog(tbox, main.AtIdSupl);
1378                 e.Handled = true;
1379             }
1380             else if (e.KeyChar == '#')
1381             {
1382                 main.ShowSuplDialog(tbox, main.HashSupl);
1383                 e.Handled = true;
1384             }
1385         }
1386
1387         private void ListFilters_DrawItem(object sender, DrawItemEventArgs e)
1388         {
1389             e.DrawBackground();
1390
1391             if (e.Index != -1)
1392             {
1393                 var filter = (PostFilterRule)this.ListFilters.Items[e.Index];
1394                 var isSelected = e.State.HasFlag(DrawItemState.Selected);
1395
1396                 Brush textBrush;
1397                 if (isSelected)
1398                     textBrush = SystemBrushes.HighlightText;
1399                 else if (filter.Enabled)
1400                     textBrush = SystemBrushes.WindowText;
1401                 else
1402                     textBrush = SystemBrushes.GrayText;
1403
1404                 e.Graphics.DrawString(filter.ToString(), e.Font, textBrush, e.Bounds);
1405             }
1406
1407             e.DrawFocusRectangle();
1408         }
1409
1410         private void ListFilters_KeyDown(object sender, KeyEventArgs e)
1411         {
1412             if (e.Control && e.KeyCode == Keys.A)
1413             {
1414                 var itemCount = this.ListFilters.Items.Count;
1415                 if (itemCount == 0) return;
1416
1417                 using (ControlTransaction.Update(this.ListFilters))
1418                 {
1419                     if (itemCount > 1)
1420                     {
1421                         try
1422                         {
1423                             this.multiSelState |= MultiSelectionState.SelectAll;
1424
1425                             for (var i = 1; i < itemCount; i++)
1426                             {
1427                                 this.ListFilters.SetSelected(i, true);
1428                             }
1429                         }
1430                         finally
1431                         {
1432                             this.multiSelState &= ~MultiSelectionState.SelectAll;
1433                         }
1434                     }
1435
1436                     this.ListFilters.SetSelected(0, true);
1437                 }
1438             }
1439         }
1440
1441         protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
1442         {
1443             base.ScaleControl(factor, specified);
1444             this.ListFilters.ItemHeight = this.ListFilters.Font.Height;
1445         }
1446     }
1447 }