OSDN Git Service

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