OSDN Git Service

0790リリース。Webモードで公式RT発言が取得できない問題に対応
[opentween/open-tween.git] / Tween / StatusDictionary.vb
1 ' Tween - Client of Twitter
2 ' Copyright (c) 2007-2009 kiri_feather (@kiri_feather) <kiri_feather@gmail.com>
3 '           (c) 2008-2009 Moz (@syo68k) <http://iddy.jp/profile/moz/>
4 '           (c) 2008-2009 takeshik (@takeshik) <http://www.takeshik.org/>
5 ' All rights reserved.
6
7 ' This file is part of Tween.
8
9 ' This program is free software; you can redistribute it and/or modify it
10 ' under the terms of the GNU General Public License as published by the Free
11 ' Software Foundation; either version 3 of the License, or (at your option)
12 ' any later version.
13
14 ' This program is distributed in the hope that it will be useful, but
15 ' WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 ' or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 ' for more details. 
18
19 ' You should have received a copy of the GNU General Public License along
20 ' with this program. If not, see <http://www.gnu.org/licenses/>, or write to
21 ' the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
22 ' Boston, MA 02110-1301, USA.
23
24 Imports System.Collections.Generic
25 Imports System.Collections.ObjectModel
26 Imports Tween.TweenCustomControl
27 Imports System.Text.RegularExpressions
28
29
30 Public NotInheritable Class PostClass
31     Private _Nick As String
32     Private _Data As String
33     Private _ImageUrl As String
34     Private _Name As String
35     Private _PDate As Date
36     Private _Id As Long
37     Private _IsFav As Boolean
38     Private _OrgData As String
39     Private _IsRead As Boolean
40     Private _IsReply As Boolean
41     Private _IsProtect As Boolean
42     Private _IsOWL As Boolean
43     Private _IsMark As Boolean
44     Private _InReplyToUser As String
45     Private _InReplyToId As Long
46     Private _Source As String
47     Private _ReplyToList As New List(Of String)
48     Private _IsMe As Boolean
49     Private _ImageIndex As Integer
50     Private _IsDm As Boolean
51     Private _statuses As Statuses = Statuses.None
52     Private _Uid As Long
53     Private _FilterHit As Boolean
54     Private _RetweetedBy As String
55
56     <FlagsAttribute()> _
57     Private Enum Statuses
58         None = 0
59         Protect = 1
60         Mark = 2
61         Read = 4
62         Reply = 8
63     End Enum
64
65     Public Sub New(ByVal Nickname As String, _
66             ByVal Data As String, _
67             ByVal OriginalData As String, _
68             ByVal ImageUrl As String, _
69             ByVal Name As String, _
70             ByVal PDate As Date, _
71             ByVal Id As Long, _
72             ByVal IsFav As Boolean, _
73             ByVal IsRead As Boolean, _
74             ByVal IsReply As Boolean, _
75             ByVal IsProtect As Boolean, _
76             ByVal IsOwl As Boolean, _
77             ByVal IsMark As Boolean, _
78             ByVal InReplyToUser As String, _
79             ByVal InReplyToId As Long, _
80             ByVal Source As String, _
81             ByVal ReplyToList As List(Of String), _
82             ByVal IsMe As Boolean, _
83             ByVal ImageIndex As Integer, _
84             ByVal IsDm As Boolean, _
85             ByVal Uid As Long, _
86             ByVal FilterHit As Boolean, _
87             ByVal RetweetedBy As String)
88         _Nick = Nickname
89         _Data = Data
90         _ImageUrl = ImageUrl
91         _Name = Name
92         _PDate = PDate
93         _Id = Id
94         _IsFav = IsFav
95         _OrgData = OriginalData
96         _IsRead = IsRead
97         _IsReply = IsReply
98         _IsProtect = IsProtect
99         _IsOWL = IsOwl
100         _IsMark = IsMark
101         _InReplyToUser = InReplyToUser
102         _InReplyToId = InReplyToId
103         _Source = Source
104         _ReplyToList = ReplyToList
105         _IsMe = IsMe
106         _ImageIndex = ImageIndex
107         _IsDm = IsDm
108         _Uid = Uid
109         _FilterHit = FilterHit
110         _RetweetedBy = RetweetedBy
111     End Sub
112
113     Public Sub New()
114     End Sub
115
116     Public Property Nickname() As String
117         Get
118             Return _Nick
119         End Get
120         Set(ByVal value As String)
121             _Nick = value
122         End Set
123     End Property
124     Public Property Data() As String
125         Get
126             Return _Data
127         End Get
128         Set(ByVal value As String)
129             _Data = value
130         End Set
131     End Property
132     Public Property ImageUrl() As String
133         Get
134             Return _ImageUrl
135         End Get
136         Set(ByVal value As String)
137             _ImageUrl = value
138         End Set
139     End Property
140     Public Property Name() As String
141         Get
142             Return _Name
143         End Get
144         Set(ByVal value As String)
145             _Name = value
146         End Set
147     End Property
148     Public Property PDate() As Date
149         Get
150             Return _PDate
151         End Get
152         Set(ByVal value As Date)
153             _PDate = value
154         End Set
155     End Property
156     Public Property Id() As Long
157         Get
158             Return _Id
159         End Get
160         Set(ByVal value As Long)
161             _Id = value
162         End Set
163     End Property
164     Public Property IsFav() As Boolean
165         Get
166             Return _IsFav
167         End Get
168         Set(ByVal value As Boolean)
169             _IsFav = value
170         End Set
171     End Property
172     Public Property OriginalData() As String
173         Get
174             Return _OrgData
175         End Get
176         Set(ByVal value As String)
177             _OrgData = value
178         End Set
179     End Property
180     Public Property IsRead() As Boolean
181         Get
182             Return _IsRead
183         End Get
184         Set(ByVal value As Boolean)
185             If value Then
186                 _statuses = _statuses Or Statuses.Read
187             Else
188                 _statuses = _statuses And Not Statuses.Read
189             End If
190             _IsRead = value
191         End Set
192     End Property
193     Public Property IsReply() As Boolean
194         Get
195             Return _IsReply
196         End Get
197         Set(ByVal value As Boolean)
198             _IsReply = value
199         End Set
200     End Property
201     Public Property IsProtect() As Boolean
202         Get
203             Return _IsProtect
204         End Get
205         Set(ByVal value As Boolean)
206             If value Then
207                 _statuses = _statuses Or Statuses.Protect
208             Else
209                 _statuses = _statuses And Not Statuses.Protect
210             End If
211             _IsProtect = value
212         End Set
213     End Property
214     Public Property IsOwl() As Boolean
215         Get
216             Return _IsOWL
217         End Get
218         Set(ByVal value As Boolean)
219             _IsOWL = value
220         End Set
221     End Property
222     Public Property IsMark() As Boolean
223         Get
224             Return _IsMark
225         End Get
226         Set(ByVal value As Boolean)
227             If value Then
228                 _statuses = _statuses Or Statuses.Mark
229             Else
230                 _statuses = _statuses And Not Statuses.Mark
231             End If
232             _IsMark = value
233         End Set
234     End Property
235     Public Property InReplyToUser() As String
236         Get
237             Return _InReplyToUser
238         End Get
239         Set(ByVal value As String)
240             _InReplyToUser = value
241         End Set
242     End Property
243     Public Property InReplyToId() As Long
244         Get
245             Return _InReplyToId
246         End Get
247         Set(ByVal value As Long)
248             _InReplyToId = value
249         End Set
250     End Property
251     Public Property Source() As String
252         Get
253             Return _Source
254         End Get
255         Set(ByVal value As String)
256             _Source = value
257         End Set
258     End Property
259     Public Property ReplyToList() As List(Of String)
260         Get
261             Return _ReplyToList
262         End Get
263         Set(ByVal value As List(Of String))
264             _ReplyToList = value
265         End Set
266     End Property
267     Public Property IsMe() As Boolean
268         Get
269             Return _IsMe
270         End Get
271         Set(ByVal value As Boolean)
272             _IsMe = value
273         End Set
274     End Property
275     Public Property ImageIndex() As Integer
276         Get
277             Return _ImageIndex
278         End Get
279         Set(ByVal value As Integer)
280             _ImageIndex = value
281         End Set
282     End Property
283     Public Property IsDm() As Boolean
284         Get
285             Return _IsDm
286         End Get
287         Set(ByVal value As Boolean)
288             _IsDm = value
289         End Set
290     End Property
291     Public ReadOnly Property StatusIndex() As Integer
292         Get
293             Return _statuses
294         End Get
295     End Property
296     Public Property Uid() As Long
297         Get
298             Return _Uid
299         End Get
300         Set(ByVal value As Long)
301             _Uid = value
302         End Set
303     End Property
304     Public Property FilterHit() As Boolean
305         Get
306             Return _FilterHit
307         End Get
308         Set(ByVal value As Boolean)
309             _FilterHit = value
310         End Set
311     End Property
312     Public Property RetweetedBy() As String
313         Get
314             Return _RetweetedBy
315         End Get
316         Set(ByVal value As String)
317             _RetweetedBy = value
318         End Set
319     End Property
320 End Class
321
322 Public NotInheritable Class TabInformations
323     '個別タブの情報をDictionaryで保持
324     Private _sorter As IdComparerClass
325     Private _tabs As New Dictionary(Of String, TabClass)
326     Private _statuses As Dictionary(Of Long, PostClass) = New Dictionary(Of Long, PostClass)
327     Private _addedIds As List(Of Long)
328
329     '発言の追加
330     'AddPost(複数回) -> DistributePosts          -> SubmitUpdate
331
332     'トランザクション用
333     Private _addCount As Integer
334     Private _soundFile As String
335     Private _notifyPosts As List(Of PostClass)
336     Private ReadOnly LockObj As New Object
337     Private ReadOnly LockUnread As New Object
338
339     Private Shared _instance As TabInformations = New TabInformations
340
341     Private Sub New()
342         _sorter = New IdComparerClass(Me)
343     End Sub
344
345     Public Shared Function GetInstance() As TabInformations
346         Return _instance    'singleton
347     End Function
348
349     Public Sub AddTab(ByVal TabName As String, ByVal TabType As TabUsageType)
350         _tabs.Add(TabName, New TabClass(TabName, TabType))
351     End Sub
352
353     Public Sub AddTab(ByVal TabName As String, ByVal Tab As TabClass)
354         _tabs.Add(TabName, Tab)
355     End Sub
356
357     Public Sub RemoveTab(ByVal TabName As String)
358         SyncLock LockObj
359             If IsDefaultTab(TabName) Then Exit Sub '念のため
360             Dim homeTab As TabClass = GetTabByType(TabUsageType.Home)
361             Dim dmName As String = GetTabByType(TabUsageType.DirectMessage).TabName
362
363             For idx As Integer = 0 To _tabs(TabName).AllCount - 1
364                 Dim exist As Boolean = False
365                 Dim Id As Long = _tabs(TabName).GetId(idx)
366                 For Each key As String In _tabs.Keys
367                     If Not key = TabName AndAlso key <> dmName Then
368                         If _tabs(key).Contains(Id) Then
369                             exist = True
370                             Exit For
371                         End If
372                     End If
373                 Next
374                 If Not exist Then homeTab.Add(Id, _statuses(Id).IsRead, False)
375             Next
376
377             _tabs.Remove(TabName)
378         End SyncLock
379     End Sub
380
381     Public Function ContainsTab(ByVal TabText As String) As Boolean
382         Return _tabs.ContainsKey(TabText)
383     End Function
384
385     Public Function ContainsTab(ByVal ts As TabClass) As Boolean
386         Return _tabs.ContainsValue(ts)
387     End Function
388
389     Public Property Tabs() As Dictionary(Of String, TabClass)
390         Get
391             Return _tabs
392         End Get
393         Set(ByVal value As Dictionary(Of String, TabClass))
394             _tabs = value
395         End Set
396     End Property
397
398     Public ReadOnly Property KeysTab() As Collections.Generic.Dictionary(Of String, TabClass).KeyCollection
399         Get
400             Return _tabs.Keys
401         End Get
402     End Property
403
404     Public Sub SortPosts()
405         For Each key As String In _tabs.Keys
406             _tabs(key).Sort(_sorter)
407         Next
408     End Sub
409
410     Public ReadOnly Property Sorter() As IdComparerClass
411         Get
412             Return _sorter
413         End Get
414     End Property
415
416     Public Property SortOrder() As SortOrder
417         Get
418             Return _sorter.Order
419         End Get
420         Set(ByVal value As SortOrder)
421             _sorter.Order = value
422         End Set
423     End Property
424
425     Public Property SortMode() As IdComparerClass.ComparerMode
426         Get
427             Return _sorter.Mode
428         End Get
429         Set(ByVal value As IdComparerClass.ComparerMode)
430             _sorter.Mode = value
431         End Set
432     End Property
433
434     Public Sub ToggleSortOrder(ByVal SortMode As IdComparerClass.ComparerMode)
435         If _sorter.Mode = SortMode Then
436             If _sorter.Order = Windows.Forms.SortOrder.Ascending Then
437                 _sorter.Order = Windows.Forms.SortOrder.Descending
438             Else
439                 _sorter.Order = Windows.Forms.SortOrder.Ascending
440             End If
441         Else
442             _sorter.Mode = SortMode
443             _sorter.Order = Windows.Forms.SortOrder.Ascending
444         End If
445         Me.SortPosts()
446     End Sub
447
448     Public Sub RemovePost(ByVal Name As String, ByVal Id As Long)
449         SyncLock LockObj
450             Dim post As PostClass = _statuses(Id)
451             '指定タブから該当ID削除
452             Dim tab As TabClass = _tabs(Name)
453             If tab.Contains(Id) Then
454                 If tab.UnreadManage AndAlso Not post.IsRead Then    '未読管理
455                     SyncLock LockUnread
456                         tab.UnreadCount -= 1
457                         Me.SetNextUnreadId(Id, tab)
458                     End SyncLock
459                 End If
460                 tab.Remove(Id)
461             End If
462         End SyncLock
463     End Sub
464
465     Public Sub RemovePost(ByVal Id As Long)
466         SyncLock LockObj
467             Dim post As PostClass = _statuses(Id)
468             '各タブから該当ID削除
469             For Each key As String In _tabs.Keys
470                 Dim tab As TabClass = _tabs(key)
471                 If tab.Contains(Id) Then
472                     If tab.UnreadManage AndAlso Not post.IsRead Then    '未読管理
473                         SyncLock LockUnread
474                             tab.UnreadCount -= 1
475                             Me.SetNextUnreadId(Id, tab)
476                         End SyncLock
477                     End If
478                     tab.Remove(Id)
479                 End If
480             Next
481             _statuses.Remove(Id)
482         End SyncLock
483     End Sub
484
485     Public Function GetOldestUnreadId(ByVal TabName As String) As Integer
486         Dim tb As TabClass = _tabs(TabName)
487         If tb.OldestUnreadId > -1 AndAlso _
488            tb.Contains(tb.OldestUnreadId) AndAlso _
489            tb.UnreadCount > 0 Then
490             '未読アイテムへ
491             If _statuses.Item(tb.OldestUnreadId).IsRead Then
492                 '状態不整合(最古未読IDが実は既読)
493                 SyncLock LockUnread
494                     Me.SetNextUnreadId(-1, tb)  '頭から探索
495                 End SyncLock
496                 If tb.OldestUnreadId = -1 Then
497                     Return -1
498                 Else
499                     Return tb.IndexOf(tb.OldestUnreadId)
500                 End If
501             Else
502                 Return tb.IndexOf(tb.OldestUnreadId)    '最短経路
503             End If
504         Else
505             '一見未読なさそうだが、未読カウントはあるので探索
506             If tb.UnreadCount > 0 Then
507                 SyncLock LockUnread
508                     Me.SetNextUnreadId(-1, tb)
509                 End SyncLock
510                 If tb.OldestUnreadId = -1 Then
511                     Return -1
512                 Else
513                     Return tb.IndexOf(tb.OldestUnreadId)
514                 End If
515             Else
516                 Return -1
517             End If
518         End If
519     End Function
520
521     Private Sub SetNextUnreadId(ByVal CurrentId As Long, ByVal Tab As TabClass)
522         'CurrentID:今既読にしたID(OldestIDの可能性あり)
523         '最古未読が設定されていて、既読の場合(1発言以上存在)
524         If Tab.OldestUnreadId > -1 AndAlso _
525            _statuses.ContainsKey(Tab.OldestUnreadId) AndAlso _
526            _statuses.Item(Tab.OldestUnreadId).IsRead AndAlso _
527            _sorter.Mode = IdComparerClass.ComparerMode.Id Then     '次の未読探索
528             If Tab.UnreadCount = 0 Then
529                 '未読数0→最古未読なし
530                 Tab.OldestUnreadId = -1
531             ElseIf Tab.OldestUnreadId = CurrentId Then
532                 '最古IDを既読にしたタイミング→次のIDから続けて探索
533                 Dim idx As Integer = Tab.IndexOf(CurrentId)
534                 If idx > -1 Then
535                     '続きから探索
536                     FindUnreadId(idx, Tab)
537                 Else
538                     '頭から探索
539                     FindUnreadId(-1, Tab)
540                 End If
541             Else
542                 '頭から探索
543                 FindUnreadId(-1, Tab)
544             End If
545         Else
546             '頭から探索
547             FindUnreadId(-1, Tab)
548         End If
549     End Sub
550
551     Private Sub FindUnreadId(ByVal StartIdx As Integer, ByVal Tab As TabClass)
552         If Tab.AllCount = 0 Then
553             Tab.OldestUnreadId = -1
554             Tab.UnreadCount = 0
555             Exit Sub
556         End If
557         Dim toIdx As Integer = 0
558         Dim stp As Integer = 1
559         Tab.OldestUnreadId = -1
560         If _sorter.Order = Windows.Forms.SortOrder.Ascending Then
561             If StartIdx = -1 Then
562                 StartIdx = 0
563             Else
564                 'StartIdx += 1
565                 If StartIdx > Tab.AllCount - 1 Then StartIdx = Tab.AllCount - 1 '念のため
566             End If
567             toIdx = Tab.AllCount - 1
568             If toIdx < 0 Then toIdx = 0 '念のため
569             stp = 1
570         Else
571             If StartIdx = -1 Then
572                 StartIdx = Tab.AllCount - 1
573             Else
574                 'StartIdx -= 1
575             End If
576             If StartIdx < 0 Then StartIdx = 0 '念のため
577             toIdx = 0
578             stp = -1
579         End If
580         For i As Integer = StartIdx To toIdx Step stp
581             If Not _statuses(Tab.GetId(i)).IsRead Then
582                 Tab.OldestUnreadId = Tab.GetId(i)
583                 Exit For
584             End If
585         Next
586     End Sub
587
588     Public Function DistributePosts() As Integer
589         SyncLock LockObj
590             '戻り値は追加件数
591             If _addedIds Is Nothing Then Return 0
592             If _addedIds.Count = 0 Then Return 0
593
594             If _notifyPosts Is Nothing Then _notifyPosts = New List(Of PostClass)
595             Me.Distribute()    'タブに仮振分
596             _addCount = _addedIds.Count
597             _addedIds.Clear()
598             _addedIds = Nothing     '後始末
599             Return _addCount     '件数
600         End SyncLock
601     End Function
602
603     Public Function SubmitUpdate(ByRef soundFile As String, ByRef notifyPosts As PostClass()) As Integer
604         '注:メインスレッドから呼ぶこと
605         SyncLock LockObj
606             If _notifyPosts Is Nothing Then
607                 soundFile = ""
608                 notifyPosts = Nothing
609                 Return 0
610             End If
611
612             For Each key As String In _tabs.Keys
613                 _tabs(key).AddSubmit()  '振分確定(各タブに反映)
614             Next
615             Me.SortPosts()
616
617             soundFile = _soundFile
618             _soundFile = ""
619             notifyPosts = _notifyPosts.ToArray()
620             _notifyPosts.Clear()
621             _notifyPosts = Nothing
622             Dim retCnt As Integer = _addCount
623             _addCount = 0
624             Return retCnt    '件数(EndUpdateの戻り値と同じ)
625         End SyncLock
626     End Function
627
628     Private Sub Distribute()
629         '各タブのフィルターと照合。合致したらタブにID追加
630         '通知メッセージ用に、表示必要な発言リストと再生サウンドを返す
631         'notifyPosts = New List(Of PostClass)
632         Dim homeTab As TabClass = GetTabByType(TabUsageType.Home)
633         Dim replyTab As TabClass = GetTabByType(TabUsageType.Mentions)
634         Dim dmTab As TabClass = GetTabByType(TabUsageType.DirectMessage)
635         Dim favTab As TabClass = GetTabByType(TabUsageType.Favorites)
636         For Each id As Long In _addedIds
637             Dim post As PostClass = _statuses(id)
638             If Not post.IsDm Then
639                 Dim add As Boolean = False  '通知リスト追加フラグ
640                 Dim mv As Boolean = False   '移動フラグ(Recent追加有無)
641                 For Each tn As String In _tabs.Keys
642                     Dim rslt As HITRESULT = _tabs(tn).AddFiltered(post.Id, post.IsRead, post.Name, post.Data, post.OriginalData)
643                     If rslt <> HITRESULT.None Then
644                         If rslt = HITRESULT.CopyAndMark Then post.IsMark = True 'マークあり
645                         If rslt = HITRESULT.Move Then
646                             mv = True '移動
647                             post.IsMark = False
648                         End If
649                         If _tabs(tn).Notify Then add = True '通知あり
650                         If Not _tabs(tn).SoundFile = "" AndAlso _soundFile = "" Then
651                             _soundFile = _tabs(tn).SoundFile 'wavファイル(未設定の場合のみ)
652                         End If
653                         post.FilterHit = True
654                     Else
655                         post.FilterHit = False
656                     End If
657                 Next
658                 If Not mv Then  '移動されなかったらRecentに追加
659                     homeTab.Add(post.Id, post.IsRead, True)
660                     If Not homeTab.SoundFile = "" AndAlso _soundFile = "" Then _soundFile = homeTab.SoundFile
661                     If homeTab.Notify Then add = True
662                 End If
663                 If post.IsReply Then    'ReplyだったらReplyタブに追加
664                     replyTab.Add(post.Id, post.IsRead, True)
665                     If Not replyTab.SoundFile = "" Then _soundFile = replyTab.SoundFile
666                     If replyTab.Notify Then add = True
667                 End If
668                 If post.IsFav Then    'Fav済み発言だったらFavoritesタブに追加
669                     If favTab.Contains(post.Id) Then
670                         '取得済みなら非通知
671                         _soundFile = ""
672                         add = False
673                     Else
674                         favTab.Add(post.Id, post.IsRead, True)
675                         If Not favTab.SoundFile = "" Then _soundFile = favTab.SoundFile
676                         If favTab.Notify Then add = True
677                     End If
678                 End If
679                 If add Then _notifyPosts.Add(post)
680             Else
681                 dmTab.Add(post.Id, post.IsRead, True)
682                 If dmTab.Notify Then _notifyPosts.Add(post)
683                 _soundFile = dmTab.SoundFile
684             End If
685         Next
686     End Sub
687
688     Public Sub AddPost(ByVal Item As PostClass)
689         SyncLock LockObj
690             If _statuses.ContainsKey(Item.Id) Then
691                 If Item.IsFav Then
692                     _statuses.Item(Item.Id).IsFav = True
693                 Else
694                     Exit Sub        '追加済みなら何もしない
695                 End If
696             Else
697                 _statuses.Add(Item.Id, Item)    'DMと区別しない?
698             End If
699             If _addedIds Is Nothing Then _addedIds = New List(Of Long) 'タブ追加用IDコレクション準備
700             _addedIds.Add(Item.Id)
701         End SyncLock
702     End Sub
703
704     Public Sub SetRead(ByVal Read As Boolean, ByVal TabName As String, ByVal Index As Integer)
705         'Read:True=既読へ False=未読へ
706         Dim tb As TabClass = _tabs(TabName)
707
708         If tb.UnreadManage = False Then Exit Sub '未読管理していなければ終了
709
710         Dim Id As Long = tb.GetId(Index)
711
712         If _statuses(Id).IsRead = Read Then Exit Sub '状態変更なければ終了
713
714         _statuses(Id).IsRead = Read '指定の状態に変更
715
716         SyncLock LockUnread
717             If Read Then
718                 tb.UnreadCount -= 1
719                 Me.SetNextUnreadId(Id, tb)  '次の未読セット
720                 '他タブの最古未読IDはタブ切り替え時に。
721                 For Each key As String In _tabs.Keys
722                     If key <> TabName AndAlso _
723                        _tabs(key).UnreadManage AndAlso _
724                        _tabs(key).Contains(Id) Then
725                         _tabs(key).UnreadCount -= 1
726                         If _tabs(key).OldestUnreadId = Id Then _tabs(key).OldestUnreadId = -1
727                     End If
728                 Next
729             Else
730                 tb.UnreadCount += 1
731                 If tb.OldestUnreadId > Id OrElse tb.OldestUnreadId = -1 Then tb.OldestUnreadId = Id
732                 For Each key As String In _tabs.Keys
733                     If Not key = TabName AndAlso _tabs(key).UnreadManage AndAlso _tabs(key).Contains(Id) Then
734                         _tabs(key).UnreadCount += 1
735                         If _tabs(key).OldestUnreadId > Id Then _tabs(key).OldestUnreadId = Id
736                     End If
737                 Next
738             End If
739         End SyncLock
740     End Sub
741
742     Public Sub SetRead()
743         Dim tb As TabClass = GetTabByType(TabUsageType.Home)
744         If tb.UnreadManage = False Then Exit Sub
745
746         For i As Integer = 0 To tb.AllCount - 1
747             Dim id As Long = tb.GetId(i)
748             If Not _statuses(id).IsDm AndAlso _
749                Not _statuses(id).IsReply AndAlso _
750                Not _statuses(id).IsRead AndAlso _
751                Not _statuses(id).FilterHit Then
752                 _statuses(id).IsRead = True
753                 Me.SetNextUnreadId(id, tb)  '次の未読セット
754                 For Each key As String In _tabs.Keys
755                     If _tabs(key).UnreadManage AndAlso _
756                        _tabs(key).Contains(id) Then
757                         _tabs(key).UnreadCount -= 1
758                         If _tabs(key).OldestUnreadId = id Then _tabs(key).OldestUnreadId = -1
759                     End If
760                 Next
761             End If
762         Next
763     End Sub
764
765     Public ReadOnly Property Item(ByVal ID As Long) As PostClass
766         Get
767             Return _statuses(ID)
768         End Get
769     End Property
770
771     Public ReadOnly Property Item(ByVal TabName As String, ByVal Index As Integer) As PostClass
772         Get
773             Return _statuses(_tabs(TabName).GetId(Index))
774         End Get
775     End Property
776
777     Public ReadOnly Property Item(ByVal TabName As String, ByVal StartIndex As Integer, ByVal EndIndex As Integer) As PostClass()
778         Get
779             Dim length As Integer = EndIndex - StartIndex + 1
780             Dim posts() As PostClass = New PostClass(length - 1) {}
781             For i As Integer = 0 To length - 1
782                 posts(i) = _statuses(_tabs(TabName).GetId(StartIndex + i))
783             Next i
784             Return posts
785         End Get
786     End Property
787
788     Public ReadOnly Property ItemCount() As Integer
789         Get
790             SyncLock LockObj
791                 Return _statuses.Count
792             End SyncLock
793         End Get
794     End Property
795
796     Public Function ContainsKey(ByVal Id As Long) As Boolean
797         SyncLock LockObj
798             Return _statuses.ContainsKey(Id)
799         End SyncLock
800     End Function
801
802     Public Sub SetUnreadManage(ByVal Manage As Boolean)
803         If Manage Then
804             For Each key As String In _tabs.Keys
805                 Dim tb As TabClass = _tabs(key)
806                 If tb.UnreadManage Then
807                     SyncLock LockUnread
808                         Dim cnt As Integer = 0
809                         Dim oldest As Long = Long.MaxValue
810                         For Each id As Long In tb.BackupIds
811                             If Not _statuses(id).IsRead Then
812                                 cnt += 1
813                                 If oldest > id Then oldest = id
814                             End If
815                         Next
816                         tb.OldestUnreadId = oldest
817                         tb.UnreadCount = cnt
818                     End SyncLock
819                 End If
820             Next
821         Else
822             For Each key As String In _tabs.Keys
823                 Dim tb As TabClass = _tabs(key)
824                 If tb.UnreadManage AndAlso tb.UnreadCount > 0 Then
825                     SyncLock LockUnread
826                         tb.UnreadCount = 0
827                         tb.OldestUnreadId = -1
828                     End SyncLock
829                 End If
830             Next
831         End If
832     End Sub
833
834     Public Sub RenameTab(ByVal Original As String, ByVal NewName As String)
835         Dim tb As TabClass = _tabs(Original)
836         _tabs.Remove(Original)
837         tb.TabName = NewName
838         _tabs.Add(NewName, tb)
839     End Sub
840
841     Public Sub FilterAll()
842         SyncLock LockObj
843             Dim tbr As TabClass = GetTabByType(TabUsageType.Home)
844             Dim replyTab As TabClass = GetTabByType(TabUsageType.Mentions)
845             For Each key As String In _tabs.Keys
846                 Dim tb As TabClass = _tabs(key)
847                 If tb.FilterModified Then
848                     tb.FilterModified = False
849                     Dim orgIds() As Long = tb.BackupIds()
850                     tb.ClearIDs()
851                     ''''''''''''''フィルター前のIDsを退避。どのタブにも含まれないidはrecentへ追加
852                     ''''''''''''''moveフィルターにヒットした際、recentに該当あればrecentから削除
853                     For Each id As Long In _statuses.Keys
854                         Dim post As PostClass = _statuses.Item(id)
855                         If post.IsDm Then Continue For
856                         Dim rslt As HITRESULT = tb.AddFiltered(post.Id, post.IsRead, post.Name, post.Data, post.OriginalData)
857                         Select Case rslt
858                             Case HITRESULT.CopyAndMark
859                                 post.IsMark = True 'マークあり
860                                 post.FilterHit = True
861                             Case HITRESULT.Move
862                                 tbr.Remove(post.Id, post.IsRead)
863                                 post.IsMark = False
864                                 post.FilterHit = True
865                             Case HITRESULT.Copy
866                                 post.IsMark = False
867                                 post.FilterHit = True
868                             Case HITRESULT.None
869                                 If key = replyTab.TabName AndAlso post.IsReply Then replyTab.Add(post.Id, post.IsRead, True)
870                                 If post.IsFav Then GetTabByType(TabUsageType.Favorites).Add(post.Id, post.IsRead, True)
871                                 post.FilterHit = False
872                         End Select
873                     Next
874                     tb.AddSubmit()  '振分確定
875                     For Each id As Long In orgIds
876                         Dim hit As Boolean = False
877                         For Each tkey As String In _tabs.Keys
878                             If _tabs(tkey).Contains(id) Then
879                                 hit = True
880                                 Exit For
881                             End If
882                         Next
883                         If Not hit Then tbr.Add(id, _statuses(id).IsRead, False)
884                     Next
885                 End If
886             Next
887
888             Me.SortPosts()
889         End SyncLock
890     End Sub
891
892     Public Function GetId(ByVal TabName As String, ByVal IndexCollection As ListView.SelectedIndexCollection) As Long()
893         If IndexCollection.Count = 0 Then Return Nothing
894
895         Dim tb As TabClass = _tabs(TabName)
896         Dim Ids(IndexCollection.Count - 1) As Long
897         For i As Integer = 0 To Ids.Length - 1
898             Ids(i) = tb.GetId(IndexCollection(i))
899         Next
900         Return Ids
901     End Function
902
903     Public Function GetId(ByVal TabName As String, ByVal Index As Integer) As Long
904         Return _tabs(TabName).GetId(Index)
905     End Function
906
907     Public Function IndexOf(ByVal TabName As String, ByVal Ids() As Long) As Integer()
908         If Ids Is Nothing Then Return Nothing
909         Dim idx(Ids.Length - 1) As Integer
910         Dim tb As TabClass = _tabs(TabName)
911         For i As Integer = 0 To Ids.Length - 1
912             idx(i) = tb.IndexOf(Ids(i))
913         Next
914         Return idx
915     End Function
916
917     Public Function IndexOf(ByVal TabName As String, ByVal Id As Long) As Integer
918         Return _tabs(TabName).IndexOf(Id)
919     End Function
920
921     Public Sub ClearTabIds(ByVal TabName As String)
922         '不要なPostを削除
923         For Each Id As Long In _tabs(TabName).BackupIds
924             Dim Hit As Boolean = False
925             For Each tb As TabClass In _tabs.Values
926                 If tb.Contains(Id) Then
927                     Hit = True
928                     Exit For
929                 End If
930             Next
931             If Not Hit Then _statuses.Remove(Id)
932         Next
933         '指定タブをクリア
934         _tabs(TabName).ClearIDs()
935     End Sub
936
937     Public Sub SetTabUnreadManage(ByVal TabName As String, ByVal Manage As Boolean)
938         Dim tb As TabClass = _tabs(TabName)
939         SyncLock LockUnread
940             If Manage Then
941                 Dim cnt As Integer = 0
942                 Dim oldest As Long = Long.MaxValue
943                 For Each id As Long In tb.BackupIds
944                     If Not _statuses(id).IsRead Then
945                         cnt += 1
946                         If oldest > id Then oldest = id
947                     End If
948                 Next
949                 tb.OldestUnreadId = oldest
950                 tb.UnreadCount = cnt
951             Else
952                 tb.OldestUnreadId = -1
953                 tb.UnreadCount = 0
954             End If
955         End SyncLock
956         tb.UnreadManage = Manage
957     End Sub
958
959     Public Sub RefreshOwl(ByVal follower As List(Of String))
960         SyncLock LockObj
961             For Each id As Long In _statuses.Keys
962                 If Not _statuses(id).IsDm Then _statuses(id).IsOwl = Not follower.Contains(_statuses(id).Name.ToLower())
963             Next
964         End SyncLock
965     End Sub
966
967     Public Function GetTabByType(ByVal tabType As TabUsageType) As TabClass
968         'Home,Mentions,DM,Favは1つに制限する
969         'その他のタイプを指定されたら、最初に合致したものを返す
970         '合致しなければNothingを返す
971         For Each tb As TabClass In _tabs.Values
972             If tb.TabType = tabType Then Return tb
973         Next
974         Return Nothing
975     End Function
976
977     ' デフォルトタブの判定処理
978     Public Function IsDefaultTab(ByVal tabName As String) As Boolean
979         If tabName IsNot Nothing AndAlso _
980            _tabs.ContainsKey(tabName) AndAlso _
981            (_tabs(tabName).TabType = TabUsageType.Home OrElse _
982            _tabs(tabName).TabType = TabUsageType.Mentions OrElse _
983            _tabs(tabName).TabType = TabUsageType.DirectMessage OrElse _
984            _tabs(tabName).TabType = TabUsageType.Favorites) Then
985             Return True
986         Else
987             Return False
988         End If
989     End Function
990 End Class
991
992 <Serializable()> _
993 Public NotInheritable Class TabClass
994     Private _unreadManage As Boolean = False
995     Private _notify As Boolean = False
996     Private _soundFile As String = ""
997     Private _filters As List(Of FiltersClass)
998     Private _oldestUnreadItem As Long = -1     'ID
999     Private _unreadCount As Integer = 0
1000     Private _ids As List(Of Long)
1001     Private _filterMod As Boolean = False
1002     Private _tmpIds As List(Of TempolaryId)
1003     Private _tabName As String = ""
1004     Private _tabType As TabUsageType = TabUsageType.Undefined
1005     'Private rwLock As New System.Threading.ReaderWriterLock()   'フィルタ用
1006
1007     Private Structure TempolaryId
1008         Public Id As Long
1009         Public Read As Boolean
1010
1011         Public Sub New(ByVal argId As Long, ByVal argRead As Boolean)
1012             Id = argId
1013             Read = argRead
1014         End Sub
1015     End Structure
1016
1017     Public Sub New()
1018         _filters = New List(Of FiltersClass)
1019         _notify = True
1020         _soundFile = ""
1021         _unreadManage = True
1022         _ids = New List(Of Long)
1023         _oldestUnreadItem = -1
1024         _tabType = TabUsageType.Undefined
1025     End Sub
1026
1027     Public Sub New(ByVal TabName As String, ByVal TabType As TabUsageType)
1028         Me.TabName = TabName
1029         _filters = New List(Of FiltersClass)
1030         _notify = True
1031         _soundFile = ""
1032         _unreadManage = True
1033         _ids = New List(Of Long)
1034         _oldestUnreadItem = -1
1035         _tabType = TabType
1036     End Sub
1037
1038     Public Sub Sort(ByVal Sorter As IdComparerClass)
1039         _ids.Sort(Sorter.CmpMethod)
1040     End Sub
1041
1042     '無条件に追加
1043     Private Sub Add(ByVal ID As Long, ByVal Read As Boolean)
1044         If Me._ids.Contains(ID) Then Exit Sub
1045
1046         Me._ids.Add(ID)
1047
1048         If Not Read AndAlso Me._unreadManage Then
1049             Me._unreadCount += 1
1050             If Me._oldestUnreadItem = -1 Then
1051                 Me._oldestUnreadItem = ID
1052             Else
1053                 If ID < Me._oldestUnreadItem Then Me._oldestUnreadItem = ID
1054             End If
1055         End If
1056     End Sub
1057
1058     Public Sub Add(ByVal ID As Long, ByVal Read As Boolean, ByVal Temporary As Boolean)
1059         If Not Temporary Then
1060             Me.Add(ID, Read)
1061         Else
1062             If _tmpIds Is Nothing Then _tmpIds = New List(Of TempolaryId)
1063             _tmpIds.Add(New TempolaryId(ID, Read))
1064         End If
1065     End Sub
1066
1067     'フィルタに合致したら追加
1068     Public Function AddFiltered(ByVal ID As Long, _
1069                                 ByVal Read As Boolean, _
1070                                 ByVal Name As String, _
1071                                 ByVal Body As String, _
1072                                 ByVal OrgData As String) As HITRESULT
1073         'Try
1074         '    rwLock.AcquireReaderLock(System.Threading.Timeout.Infinite) '読み取りロック取得
1075
1076         Dim rslt As HITRESULT = HITRESULT.None
1077         '全フィルタ評価(優先順位あり)
1078         For Each ft As FiltersClass In _filters
1079             Select Case ft.IsHit(Name, Body, OrgData)   'フィルタクラスでヒット判定
1080                 Case HITRESULT.None
1081                 Case HITRESULT.Copy
1082                     If rslt <> HITRESULT.CopyAndMark Then rslt = HITRESULT.Copy
1083                 Case HITRESULT.CopyAndMark
1084                     rslt = HITRESULT.CopyAndMark
1085                 Case HITRESULT.Move
1086                     rslt = HITRESULT.Move
1087                 Case HITRESULT.Exclude
1088                     rslt = HITRESULT.None
1089                     Exit For
1090             End Select
1091         Next
1092
1093         If rslt <> HITRESULT.None Then
1094             If _tmpIds Is Nothing Then _tmpIds = New List(Of TempolaryId)
1095             _tmpIds.Add(New TempolaryId(ID, Read))
1096         End If
1097         'Me.Add(ID, Read)
1098
1099         Return rslt 'マーク付けは呼び出し元で行うこと
1100
1101         'Finally
1102         '    rwLock.ReleaseReaderLock()
1103         'End Try
1104     End Function
1105
1106     Public Sub AddSubmit()
1107         If _tmpIds Is Nothing Then Exit Sub
1108         For Each tId As TempolaryId In _tmpIds
1109             Me.Add(tId.Id, tId.Read)
1110         Next
1111         _tmpIds.Clear()
1112         _tmpIds = Nothing
1113     End Sub
1114
1115     Public Sub Remove(ByVal Id As Long)
1116         If Not Me._ids.Contains(Id) Then Exit Sub
1117
1118         Me._ids.Remove(Id)
1119     End Sub
1120
1121     Public Sub Remove(ByVal Id As Long, ByVal Read As Boolean)
1122         If Not Me._ids.Contains(Id) Then Exit Sub
1123
1124         If Not Read AndAlso Me._unreadManage Then
1125             Me._unreadCount -= 1
1126             Me._oldestUnreadItem = -1
1127         End If
1128
1129         Me._ids.Remove(Id)
1130     End Sub
1131
1132     Public Property UnreadManage() As Boolean
1133         Get
1134             Return _unreadManage
1135         End Get
1136         Set(ByVal value As Boolean)
1137             Me._unreadManage = value
1138             If Not value Then
1139                 Me._oldestUnreadItem = -1
1140                 Me._unreadCount = 0
1141             End If
1142         End Set
1143     End Property
1144
1145     Public Property Notify() As Boolean
1146         Get
1147             Return _notify
1148         End Get
1149         Set(ByVal value As Boolean)
1150             _notify = value
1151         End Set
1152     End Property
1153
1154     Public Property SoundFile() As String
1155         Get
1156             Return _soundFile
1157         End Get
1158         Set(ByVal value As String)
1159             _soundFile = value
1160         End Set
1161     End Property
1162
1163     <Xml.Serialization.XmlIgnore()> _
1164     Public Property OldestUnreadId() As Long
1165         Get
1166             Return _oldestUnreadItem
1167         End Get
1168         Set(ByVal value As Long)
1169             _oldestUnreadItem = value
1170         End Set
1171     End Property
1172
1173     <Xml.Serialization.XmlIgnore()> _
1174     Public Property UnreadCount() As Integer
1175         Get
1176             Return _unreadCount
1177         End Get
1178         Set(ByVal value As Integer)
1179             _unreadCount = value
1180         End Set
1181     End Property
1182
1183     Public ReadOnly Property AllCount() As Integer
1184         Get
1185             Return Me._ids.Count
1186         End Get
1187     End Property
1188
1189     Public Function GetFilters() As FiltersClass()
1190         Return _filters.ToArray()
1191     End Function
1192
1193     Public Sub RemoveFilter(ByVal filter As FiltersClass)
1194         _filters.Remove(filter)
1195         _filterMod = True
1196     End Sub
1197
1198     Public Function AddFilter(ByVal filter As FiltersClass) As Boolean
1199         If _filters.Contains(filter) Then Return False
1200         _filters.Add(filter)
1201         _filterMod = True
1202         Return True
1203     End Function
1204
1205     Public Sub EditFilter(ByVal original As FiltersClass, ByVal modified As FiltersClass)
1206         original.BodyFilter = modified.BodyFilter
1207         original.NameFilter = modified.NameFilter
1208         original.SearchBoth = modified.SearchBoth
1209         original.SearchUrl = modified.SearchUrl
1210         original.UseRegex = modified.UseRegex
1211         original.CaseSensitive = modified.CaseSensitive
1212         original.ExBodyFilter = modified.ExBodyFilter
1213         original.ExNameFilter = modified.ExNameFilter
1214         original.ExSearchBoth = modified.ExSearchBoth
1215         original.ExSearchUrl = modified.ExSearchUrl
1216         original.ExUseRegex = modified.ExUseRegex
1217         original.ExCaseSensitive = modified.ExCaseSensitive
1218         original.MoveFrom = modified.MoveFrom
1219         original.SetMark = modified.SetMark
1220         _filterMod = True
1221     End Sub
1222
1223     <Xml.Serialization.XmlIgnore()> _
1224     Public Property Filters() As List(Of FiltersClass)
1225         Get
1226             Return _filters
1227         End Get
1228         Set(ByVal value As List(Of FiltersClass))
1229             _filters = value
1230         End Set
1231     End Property
1232
1233     Public Property FilterArray() As FiltersClass()
1234         Get
1235             Return _filters.ToArray
1236         End Get
1237         Set(ByVal value As FiltersClass())
1238             For Each filters As FiltersClass In value
1239                 _filters.Add(filters)
1240             Next
1241         End Set
1242     End Property
1243
1244     Public Function Contains(ByVal ID As Long) As Boolean
1245         Return _ids.Contains(ID)
1246     End Function
1247
1248     Public Sub ClearIDs()
1249         _ids.Clear()
1250         _unreadCount = 0
1251         _oldestUnreadItem = -1
1252     End Sub
1253
1254     Public Function GetId(ByVal Index As Integer) As Long
1255         Return _ids(Index)
1256     End Function
1257
1258     Public Function IndexOf(ByVal ID As Long) As Integer
1259         Return _ids.IndexOf(ID)
1260     End Function
1261
1262     <Xml.Serialization.XmlIgnore()> _
1263     Public Property FilterModified() As Boolean
1264         Get
1265             Return _filterMod
1266         End Get
1267         Set(ByVal value As Boolean)
1268             _filterMod = value
1269         End Set
1270     End Property
1271
1272     Public Function BackupIds() As Long()
1273         Return _ids.ToArray()
1274     End Function
1275
1276     Public Property TabName() As String
1277         Get
1278             Return _tabName
1279         End Get
1280         Set(ByVal value As String)
1281             _tabName = value
1282         End Set
1283     End Property
1284
1285     Public Property TabType() As TabUsageType
1286         Get
1287             Return _tabType
1288         End Get
1289         Set(ByVal value As TabUsageType)
1290             _tabType = value
1291         End Set
1292     End Property
1293 End Class
1294
1295 <Serializable()> _
1296 Public NotInheritable Class FiltersClass
1297     Implements System.IEquatable(Of FiltersClass)
1298     Private _name As String = ""
1299     Private _body As New List(Of String)
1300     Private _searchBoth As Boolean = True
1301     Private _searchUrl As Boolean = False
1302     Private _caseSensitive As Boolean = False
1303     Private _useRegex As Boolean = False
1304     Private _exname As String = ""
1305     Private _exbody As New List(Of String)
1306     Private _exsearchBoth As Boolean = True
1307     Private _exsearchUrl As Boolean = False
1308     Private _exuseRegex As Boolean = False
1309     Private _excaseSensitive As Boolean = False
1310     Private _moveFrom As Boolean = False
1311     Private _setMark As Boolean = True
1312
1313     Public Sub New(ByVal NameFilter As String, _
1314         ByVal BodyFilter As List(Of String), _
1315         ByVal SearchBoth As Boolean, _
1316         ByVal SearchUrl As Boolean, _
1317         ByVal CaseSensitive As Boolean, _
1318         ByVal UseRegex As Boolean, _
1319         ByVal ParentTab As String, _
1320         ByVal ExNameFilter As String, _
1321         ByVal ExBodyFilter As List(Of String), _
1322         ByVal ExSearchBoth As Boolean, _
1323         ByVal ExSearchUrl As Boolean, _
1324         ByVal ExUseRegex As Boolean, _
1325         ByVal ExCaseSensitive As Boolean, _
1326         ByVal MoveFrom As Boolean, _
1327         ByVal SetMark As Boolean)
1328         _name = NameFilter
1329         _body = BodyFilter
1330         _searchBoth = SearchBoth
1331         _searchUrl = SearchUrl
1332         _caseSensitive = CaseSensitive
1333         _useRegex = UseRegex
1334         _exname = ExNameFilter
1335         _exbody = ExBodyFilter
1336         _exsearchBoth = ExSearchBoth
1337         _exsearchUrl = ExSearchUrl
1338         _exuseRegex = ExUseRegex
1339         _excaseSensitive = ExCaseSensitive
1340         _moveFrom = MoveFrom
1341         _setMark = SetMark
1342         '正規表現検証
1343         If _useRegex Then
1344             Try
1345                 Dim rgx As New Regex(_name)
1346             Catch ex As Exception
1347                 Throw New Exception(My.Resources.ButtonOK_ClickText3 + ex.Message)
1348                 Exit Sub
1349             End Try
1350             For Each bs As String In _body
1351                 Try
1352                     Dim rgx As New Regex(bs)
1353                 Catch ex As Exception
1354                     Throw New Exception(My.Resources.ButtonOK_ClickText3 + ex.Message)
1355                     Exit Sub
1356                 End Try
1357             Next
1358         End If
1359         If _exuseRegex Then
1360             Try
1361                 Dim rgx As New Regex(_exname)
1362             Catch ex As Exception
1363                 Throw New Exception(My.Resources.ButtonOK_ClickText3 + ex.Message)
1364                 Exit Sub
1365             End Try
1366             For Each bs As String In _exbody
1367                 Try
1368                     Dim rgx As New Regex(bs)
1369                 Catch ex As Exception
1370                     Throw New Exception(My.Resources.ButtonOK_ClickText3 + ex.Message)
1371                     Exit Sub
1372                 End Try
1373             Next
1374         End If
1375     End Sub
1376
1377     Public Sub New()
1378
1379     End Sub
1380
1381     'フィルタ一覧に表示する文言生成
1382     Private Function MakeSummary() As String
1383         Dim fs As New System.Text.StringBuilder()
1384         If _name <> "" OrElse _body.Count > 0 Then
1385             If _searchBoth Then
1386                 If _name <> "" Then
1387                     fs.AppendFormat(My.Resources.SetFiltersText1, _name)
1388                 Else
1389                     fs.Append(My.Resources.SetFiltersText2)
1390                 End If
1391             End If
1392             If _body.Count > 0 Then
1393                 fs.Append(My.Resources.SetFiltersText3)
1394                 For Each bf As String In _body
1395                     fs.Append(bf)
1396                     fs.Append(" ")
1397                 Next
1398                 fs.Length -= 1
1399                 fs.Append(My.Resources.SetFiltersText4)
1400             End If
1401             fs.Append("(")
1402             If _searchBoth Then
1403                 fs.Append(My.Resources.SetFiltersText5)
1404             Else
1405                 fs.Append(My.Resources.SetFiltersText6)
1406             End If
1407             If _useRegex Then
1408                 fs.Append(My.Resources.SetFiltersText7)
1409             End If
1410             If _searchUrl Then
1411                 fs.Append(My.Resources.SetFiltersText8)
1412             End If
1413             If _caseSensitive Then
1414                 fs.Append(My.Resources.SetFiltersText13)
1415             End If
1416             'If _moveFrom Then
1417             '    fs.Append(My.Resources.SetFiltersText9)
1418             'ElseIf _setMark Then
1419             '    fs.Append(My.Resources.SetFiltersText10)
1420             'Else
1421             '    fs.Append(My.Resources.SetFiltersText11)
1422             'End If
1423             fs.Length -= 1
1424             fs.Append(")")
1425         End If
1426         If _exname <> "" OrElse _exbody.Count > 0 Then
1427             '除外
1428             fs.Append(My.Resources.SetFiltersText12)
1429             If _exsearchBoth Then
1430                 If _exname <> "" Then
1431                     fs.AppendFormat(My.Resources.SetFiltersText1, _exname)
1432                 Else
1433                     fs.Append(My.Resources.SetFiltersText2)
1434                 End If
1435             End If
1436             If _exbody.Count > 0 Then
1437                 fs.Append(My.Resources.SetFiltersText3)
1438                 For Each bf As String In _exbody
1439                     fs.Append(bf)
1440                     fs.Append(" ")
1441                 Next
1442                 fs.Length -= 1
1443                 fs.Append(My.Resources.SetFiltersText4)
1444             End If
1445             fs.Append("(")
1446             If _exsearchBoth Then
1447                 fs.Append(My.Resources.SetFiltersText5)
1448             Else
1449                 fs.Append(My.Resources.SetFiltersText6)
1450             End If
1451             If _exuseRegex Then
1452                 fs.Append(My.Resources.SetFiltersText7)
1453             End If
1454             If _exsearchUrl Then
1455                 fs.Append(My.Resources.SetFiltersText8)
1456             End If
1457             If _excaseSensitive Then
1458                 fs.Append(My.Resources.SetFiltersText13)
1459             End If
1460             fs.Length -= 1
1461             fs.Append(")")
1462         End If
1463
1464         fs.Append("(")
1465         If _moveFrom Then
1466             fs.Append(My.Resources.SetFiltersText9)
1467         Else
1468             fs.Append(My.Resources.SetFiltersText11)
1469         End If
1470         If Not _moveFrom AndAlso _setMark Then
1471             fs.Append(My.Resources.SetFiltersText10)
1472         ElseIf Not _moveFrom Then
1473             fs.Length -= 1
1474         End If
1475
1476         fs.Append(")")
1477
1478         Return fs.ToString()
1479     End Function
1480
1481     Public Property NameFilter() As String
1482         Get
1483             Return _name
1484         End Get
1485         Set(ByVal value As String)
1486             _name = value
1487         End Set
1488     End Property
1489
1490     Public Property ExNameFilter() As String
1491         Get
1492             Return _exname
1493         End Get
1494         Set(ByVal value As String)
1495             _exname = value
1496         End Set
1497     End Property
1498
1499     <Xml.Serialization.XmlIgnore()> _
1500     Public Property BodyFilter() As List(Of String)
1501         Get
1502             Return _body
1503         End Get
1504         Set(ByVal value As List(Of String))
1505             _body = value
1506         End Set
1507     End Property
1508
1509     Public Property BodyFilterArray() As String()
1510         Get
1511             Return _body.ToArray
1512         End Get
1513         Set(ByVal value As String())
1514             _body = New List(Of String)
1515             For Each filter As String In value
1516                 _body.Add(filter)
1517             Next
1518         End Set
1519     End Property
1520
1521     <Xml.Serialization.XmlIgnore()> _
1522     Public Property ExBodyFilter() As List(Of String)
1523         Get
1524             Return _exbody
1525         End Get
1526         Set(ByVal value As List(Of String))
1527             _exbody = value
1528         End Set
1529     End Property
1530
1531     Public Property ExBodyFilterArray() As String()
1532         Get
1533             Return _exbody.ToArray
1534         End Get
1535         Set(ByVal value As String())
1536             _exbody = New List(Of String)
1537             For Each filter As String In value
1538                 _exbody.Add(filter)
1539             Next
1540         End Set
1541     End Property
1542
1543     Public Property SearchBoth() As Boolean
1544         Get
1545             Return _searchBoth
1546         End Get
1547         Set(ByVal value As Boolean)
1548             _searchBoth = value
1549         End Set
1550     End Property
1551
1552     Public Property ExSearchBoth() As Boolean
1553         Get
1554             Return _exsearchBoth
1555         End Get
1556         Set(ByVal value As Boolean)
1557             _exsearchBoth = value
1558         End Set
1559     End Property
1560
1561     Public Property MoveFrom() As Boolean
1562         Get
1563             Return _moveFrom
1564         End Get
1565         Set(ByVal value As Boolean)
1566             _moveFrom = value
1567         End Set
1568     End Property
1569
1570     Public Property SetMark() As Boolean
1571         Get
1572             Return _setMark
1573         End Get
1574         Set(ByVal value As Boolean)
1575             _setMark = value
1576         End Set
1577     End Property
1578
1579     Public Property SearchUrl() As Boolean
1580         Get
1581             Return _searchUrl
1582         End Get
1583         Set(ByVal value As Boolean)
1584             _searchUrl = value
1585         End Set
1586     End Property
1587
1588     Public Property ExSearchUrl() As Boolean
1589         Get
1590             Return _exsearchUrl
1591         End Get
1592         Set(ByVal value As Boolean)
1593             _exsearchUrl = value
1594         End Set
1595     End Property
1596
1597     Public Property CaseSensitive() As Boolean
1598         Get
1599             Return _caseSensitive
1600         End Get
1601         Set(ByVal value As Boolean)
1602             _caseSensitive = value
1603         End Set
1604     End Property
1605
1606     Public Property ExCaseSensitive() As Boolean
1607         Get
1608             Return _excaseSensitive
1609         End Get
1610         Set(ByVal value As Boolean)
1611             _excaseSensitive = value
1612         End Set
1613     End Property
1614
1615     Public Property UseRegex() As Boolean
1616         Get
1617             Return _useRegex
1618         End Get
1619         Set(ByVal value As Boolean)
1620             _useRegex = value
1621         End Set
1622     End Property
1623
1624     Public Property ExUseRegex() As Boolean
1625         Get
1626             Return _exuseRegex
1627         End Get
1628         Set(ByVal value As Boolean)
1629             _exuseRegex = value
1630         End Set
1631     End Property
1632
1633     Public Overrides Function ToString() As String
1634         Return MakeSummary()
1635     End Function
1636
1637     Public Function IsHit(ByVal Name As String, ByVal Body As String, ByVal OrgData As String) As HITRESULT
1638         Dim bHit As Boolean = True
1639         Dim tBody As String
1640         If _searchUrl Then
1641             tBody = OrgData
1642         Else
1643             tBody = Body
1644         End If
1645         '検索オプション
1646         Dim compOpt As System.StringComparison
1647         Dim rgOpt As System.Text.RegularExpressions.RegexOptions
1648         If _caseSensitive Then
1649             compOpt = StringComparison.Ordinal
1650             rgOpt = RegexOptions.None
1651         Else
1652             compOpt = StringComparison.OrdinalIgnoreCase
1653             rgOpt = RegexOptions.IgnoreCase
1654         End If
1655         If _searchBoth Then
1656             If _name = "" OrElse Name.Equals(_name, compOpt) OrElse _
1657                             (_useRegex AndAlso Regex.IsMatch(Name, _name, rgOpt)) Then
1658                 For Each fs As String In _body
1659                     If _useRegex Then
1660                         If Regex.IsMatch(tBody, fs, rgOpt) = False Then bHit = False
1661                     Else
1662                         If _caseSensitive Then
1663                             If tBody.Contains(fs) = False Then bHit = False
1664                         Else
1665                             If tBody.ToLower().Contains(fs.ToLower()) = False Then bHit = False
1666                         End If
1667                     End If
1668                     If Not bHit Then Exit For
1669                 Next
1670             Else
1671                 bHit = False
1672             End If
1673         Else
1674             For Each fs As String In _body
1675                 If _useRegex Then
1676                     If Not (Regex.IsMatch(Name, fs, rgOpt) OrElse _
1677                             Regex.IsMatch(tBody, fs, rgOpt)) Then bHit = False
1678                 Else
1679                     If _caseSensitive Then
1680                         If Not (Name.Contains(fs) OrElse _
1681                                 tBody.Contains(fs)) Then bHit = False
1682                     Else
1683                         If Not (Name.ToLower().Contains(fs.ToLower()) OrElse _
1684                                 tBody.ToLower().Contains(fs.ToLower())) Then bHit = False
1685                     End If
1686                 End If
1687                 If Not bHit Then Exit For
1688             Next
1689         End If
1690         If bHit Then
1691             '除外判定
1692             Dim exFlag As Boolean = False
1693             'If _name = "" AndAlso _body.Count = 0 Then
1694             '    exFlag = True
1695             '    'bHit = False
1696             'End If
1697             If _exname <> "" OrElse _exbody.Count > 0 Then
1698                 If _excaseSensitive Then
1699                     compOpt = StringComparison.Ordinal
1700                     rgOpt = RegexOptions.None
1701                 Else
1702                     compOpt = StringComparison.OrdinalIgnoreCase
1703                     rgOpt = RegexOptions.IgnoreCase
1704                 End If
1705                 If _exsearchBoth Then
1706                     If _exname = "" OrElse Name.Equals(_exname, compOpt) OrElse _
1707                                     (_exuseRegex AndAlso Regex.IsMatch(Name, _exname, rgOpt)) Then
1708                         If _exbody.Count > 0 Then
1709                             For Each fs As String In _exbody
1710                                 If _exuseRegex Then
1711                                     If Regex.IsMatch(tBody, fs, rgOpt) Then exFlag = True
1712                                 Else
1713                                     If _excaseSensitive Then
1714                                         If tBody.Contains(fs) Then exFlag = True
1715                                     Else
1716                                         If tBody.ToLower().Contains(fs.ToLower()) Then exFlag = True
1717                                     End If
1718                                 End If
1719                                 If exFlag Then Exit For
1720                             Next
1721                         Else
1722                             exFlag = True
1723                         End If
1724                     End If
1725                 Else
1726                     For Each fs As String In _exbody
1727                         If _exuseRegex Then
1728                             If Regex.IsMatch(Name, fs, rgOpt) OrElse _
1729                                Regex.IsMatch(tBody, fs, rgOpt) Then exFlag = True
1730                         Else
1731                             If _excaseSensitive Then
1732                                 If Name.Contains(fs) OrElse _
1733                                    tBody.Contains(fs) Then exFlag = True
1734                             Else
1735                                 If Name.ToLower().Contains(fs.ToLower()) OrElse _
1736                                    tBody.ToLower().Contains(fs.ToLower()) Then exFlag = True
1737                             End If
1738                         End If
1739                         If exFlag Then Exit For
1740                     Next
1741                 End If
1742             End If
1743
1744             If _name = "" AndAlso _body.Count = 0 Then
1745                 bHit = False
1746             End If
1747             If bHit Then
1748                 If Not exFlag Then
1749                     'If _setMark Then Return HITRESULT.CopyAndMark
1750                     If _moveFrom Then
1751                         Return HITRESULT.Move
1752                     Else
1753                         If _setMark Then
1754                             Return HITRESULT.CopyAndMark
1755                         End If
1756                         Return HITRESULT.Copy
1757                     End If
1758                     'Return HITRESULT.Copy
1759                 Else
1760                     Return HITRESULT.Exclude
1761                 End If
1762             Else
1763                 If exFlag Then
1764                     Return HITRESULT.Exclude
1765                 Else
1766                     Return HITRESULT.None
1767                 End If
1768             End If
1769         Else
1770             Return HITRESULT.None
1771         End If
1772     End Function
1773
1774     Public Overloads Function Equals(ByVal other As FiltersClass) As Boolean _
1775      Implements System.IEquatable(Of Tween.FiltersClass).Equals
1776
1777         If Me.BodyFilter.Count <> other.BodyFilter.Count Then Return False
1778         If Me.ExBodyFilter.Count <> other.ExBodyFilter.Count Then Return False
1779         For i As Integer = 0 To Me.BodyFilter.Count - 1
1780             If Me.BodyFilter(i) <> other.BodyFilter(i) Then Return False
1781         Next
1782         For i As Integer = 0 To Me.ExBodyFilter.Count - 1
1783             If Me.ExBodyFilter(i) <> other.ExBodyFilter(i) Then Return False
1784         Next
1785
1786         Return (Me.MoveFrom = other.MoveFrom) And _
1787                (Me.SetMark = other.SetMark) And _
1788                (Me.NameFilter = other.NameFilter) And _
1789                (Me.SearchBoth = other.SearchBoth) And _
1790                (Me.SearchUrl = other.SearchUrl) And _
1791                (Me.UseRegex = other.UseRegex) And _
1792                (Me.ExNameFilter = other.ExNameFilter) And _
1793                (Me.ExSearchBoth = other.ExSearchBoth) And _
1794                (Me.ExSearchUrl = other.ExSearchUrl) And _
1795                (Me.ExUseRegex = other.ExUseRegex)
1796     End Function
1797
1798     Public Overrides Function Equals(ByVal obj As Object) As Boolean
1799         If (obj Is Nothing) OrElse Not (Me.GetType() Is obj.GetType()) Then Return False
1800         Return Me.Equals(CType(obj, FiltersClass))
1801     End Function
1802
1803     Public Overrides Function GetHashCode() As Integer
1804         Return Me.MoveFrom.GetHashCode Xor _
1805                Me.SetMark.GetHashCode Xor _
1806                Me.BodyFilter.GetHashCode Xor _
1807                Me.NameFilter.GetHashCode Xor _
1808                Me.SearchBoth.GetHashCode Xor _
1809                Me.SearchUrl.GetHashCode Xor _
1810                Me.UseRegex.GetHashCode Xor _
1811                Me.ExBodyFilter.GetHashCode Xor _
1812                Me.ExNameFilter.GetHashCode Xor _
1813                Me.ExSearchBoth.GetHashCode Xor _
1814                Me.ExSearchUrl.GetHashCode Xor _
1815                Me.ExUseRegex.GetHashCode
1816     End Function
1817 End Class
1818
1819 'ソート比較クラス:ID比較のみ
1820 Public NotInheritable Class IdComparerClass
1821     Implements IComparer(Of Long)
1822
1823     ''' <summary>
1824     ''' 比較する方法
1825     ''' </summary>
1826     Public Enum ComparerMode
1827         Id
1828         Data
1829         Name
1830         Nickname
1831         Source
1832     End Enum
1833
1834     Private _order As SortOrder
1835     Private _mode As ComparerMode
1836     Private _statuses As TabInformations
1837     Private _CmpMethod As Comparison(Of Long)
1838
1839     ''' <summary>
1840     ''' 昇順か降順か Setの際は同時に比較関数の切り替えを行う
1841     ''' </summary>
1842     Public Property Order() As SortOrder
1843         Get
1844             Return _order
1845         End Get
1846         Set(ByVal Value As SortOrder)
1847             _order = Value
1848             SetCmpMethod(_mode, _order)
1849         End Set
1850     End Property
1851
1852     ''' <summary>
1853     ''' 並び替えの方法 Setの際は同時に比較関数の切り替えを行う
1854     ''' </summary>
1855     Public Property Mode() As ComparerMode
1856         Get
1857             Return _mode
1858         End Get
1859         Set(ByVal Value As ComparerMode)
1860             _mode = Value
1861             SetCmpMethod(_mode, _order)
1862         End Set
1863     End Property
1864
1865     ''' <summary>
1866     ''' ListViewItemComparerクラスのコンストラクタ(引数付は未使用)
1867     ''' </summary>
1868     ''' <param name="col">並び替える列番号</param>
1869     ''' <param name="ord">昇順か降順か</param>
1870     ''' <param name="cmod">並び替えの方法</param>
1871     Public Sub New(ByVal ord As SortOrder, ByVal SortMode As ComparerMode)
1872         _order = ord
1873         _mode = SortMode
1874         SetCmpMethod(_mode, _order)
1875     End Sub
1876
1877     Public Sub New(ByVal TabInf As TabInformations)
1878         _order = SortOrder.Ascending
1879         _mode = ComparerMode.Id
1880         _statuses = TabInf
1881         SetCmpMethod(_mode, _order)
1882     End Sub
1883
1884     ' 指定したソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
1885     Public Overloads ReadOnly Property CmpMethod(ByVal _sortmode As ComparerMode, ByVal _sortorder As SortOrder) As Comparison(Of Long)
1886         Get
1887             Dim _method As Comparison(Of Long) = Nothing
1888             If _sortorder = SortOrder.Ascending Then
1889                 ' 昇順
1890                 Select Case _sortmode
1891                     Case ComparerMode.Data
1892                         _method = AddressOf Compare_ModeData_Ascending
1893                     Case ComparerMode.Id
1894                         _method = AddressOf Compare_ModeId_Ascending
1895                     Case ComparerMode.Name
1896                         _method = AddressOf Compare_ModeName_Ascending
1897                     Case ComparerMode.Nickname
1898                         _method = AddressOf Compare_ModeNickName_Ascending
1899                     Case ComparerMode.Source
1900                         _method = AddressOf Compare_ModeSource_Ascending
1901                 End Select
1902             Else
1903                 ' 降順
1904                 Select Case _sortmode
1905                     Case ComparerMode.Data
1906                         _method = AddressOf Compare_ModeData_Descending
1907                     Case ComparerMode.Id
1908                         _method = AddressOf Compare_ModeId_Descending
1909                     Case ComparerMode.Name
1910                         _method = AddressOf Compare_ModeName_Descending
1911                     Case ComparerMode.Nickname
1912                         _method = AddressOf Compare_ModeNickName_Descending
1913                     Case ComparerMode.Source
1914                         _method = AddressOf Compare_ModeSource_Descending
1915                 End Select
1916             End If
1917             Return _method
1918         End Get
1919     End Property
1920
1921     ' ソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
1922     ' (overload 現在の使用中の比較関数のアドレスを返す)
1923     Public Overloads ReadOnly Property CmpMethod() As Comparison(Of Long)
1924         Get
1925             Return _CmpMethod
1926         End Get
1927     End Property
1928
1929     ' ソートモードとソートオーダーに従い比較関数のアドレスを切り替え
1930     Private Sub SetCmpMethod(ByVal mode As ComparerMode, ByVal order As SortOrder)
1931         _CmpMethod = Me.CmpMethod(mode, order)
1932     End Sub
1933
1934     'xがyより小さいときはマイナスの数、大きいときはプラスの数、
1935     '同じときは0を返す (こちらは未使用 一応比較関数群呼び出しの形のまま残しておく)
1936     Public Function Compare(ByVal x As Long, ByVal y As Long) _
1937             As Integer Implements IComparer(Of Long).Compare
1938         Return _CmpMethod(x, y)
1939     End Function
1940
1941     ' 比較用関数群 いずれもステータスIDの順序を考慮する
1942     ' 注:ID比較でCTypeを使用しているが、abs(x-y)がInteger(Int32)に収まらないことはあり得ないのでこれでよい
1943     ' 本文比較 昇順
1944     Public Function Compare_ModeData_Ascending(ByVal x As Long, ByVal y As Long) As Integer
1945         Dim result As Integer = String.Compare(_statuses.Item(x).Data, _statuses.Item(y).Data)
1946         If result = 0 Then result = x.CompareTo(y)
1947         Return result
1948     End Function
1949
1950     ' 本文比較 降順
1951     Public Function Compare_ModeData_Descending(ByVal x As Long, ByVal y As Long) As Integer
1952         Dim result As Integer = String.Compare(_statuses.Item(y).Data, _statuses.Item(x).Data)
1953         If result = 0 Then result = y.CompareTo(x)
1954         Return result
1955     End Function
1956
1957     ' ステータスID比較 昇順
1958     Public Function Compare_ModeId_Ascending(ByVal x As Long, ByVal y As Long) As Integer
1959         Return x.CompareTo(y)
1960     End Function
1961
1962     ' ステータスID比較 降順
1963     Public Function Compare_ModeId_Descending(ByVal x As Long, ByVal y As Long) As Integer
1964         Return y.CompareTo(x)
1965     End Function
1966
1967     ' 表示名比較 昇順
1968     Public Function Compare_ModeName_Ascending(ByVal x As Long, ByVal y As Long) As Integer
1969         Dim result As Integer = String.Compare(_statuses.Item(x).Name, _statuses.Item(y).Name)
1970         If result = 0 Then result = x.CompareTo(y)
1971         Return result
1972     End Function
1973
1974     ' 表示名比較 降順
1975     Public Function Compare_ModeName_Descending(ByVal x As Long, ByVal y As Long) As Integer
1976         Dim result As Integer = String.Compare(_statuses.Item(y).Name, _statuses.Item(x).Name)
1977         If result = 0 Then result = y.CompareTo(x)
1978         Return result
1979     End Function
1980
1981     ' ユーザー名比較 昇順
1982     Public Function Compare_ModeNickName_Ascending(ByVal x As Long, ByVal y As Long) As Integer
1983         Dim result As Integer = String.Compare(_statuses.Item(x).Nickname, _statuses.Item(y).Nickname)
1984         If result = 0 Then result = x.CompareTo(y)
1985         Return result
1986     End Function
1987
1988     ' ユーザー名比較 降順
1989     Public Function Compare_ModeNickName_Descending(ByVal x As Long, ByVal y As Long) As Integer
1990         Dim result As Integer = String.Compare(_statuses.Item(y).Nickname, _statuses.Item(x).Nickname)
1991         If result = 0 Then result = y.CompareTo(x)
1992         Return result
1993     End Function
1994
1995     ' Source比較 昇順
1996     Public Function Compare_ModeSource_Ascending(ByVal x As Long, ByVal y As Long) As Integer
1997         Dim result As Integer = String.Compare(_statuses.Item(x).Source, _statuses.Item(y).Source)
1998         If result = 0 Then result = x.CompareTo(y)
1999         Return result
2000     End Function
2001
2002     ' Source比較 降順
2003     Public Function Compare_ModeSource_Descending(ByVal x As Long, ByVal y As Long) As Integer
2004         Dim result As Integer = String.Compare(_statuses.Item(y).Source, _statuses.Item(x).Source)
2005         If result = 0 Then result = y.CompareTo(x)
2006         Return result
2007     End Function
2008 End Class