OSDN Git Service

リスト対応とりあえず完了。要動作検証
[opentween/open-tween.git] / Tween / StatusDictionary.vb
1 ' Tween - Client of Twitter
2 ' Copyright (c) 2007-2010 kiri_feather (@kiri_feather) <kiri_feather@gmail.com>
3 '           (c) 2008-2010 Moz (@syo68k) <http://iddy.jp/profile/moz/>
4 '           (c) 2008-2010 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 Imports System.Web.HttpUtility
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     Private _RetweetedId As Long = 0
56     Private _searchTabName As String = ""
57
58     <FlagsAttribute()> _
59     Private Enum Statuses
60         None = 0
61         Protect = 1
62         Mark = 2
63         Read = 4
64         Reply = 8
65     End Enum
66
67     Public Sub New(ByVal Nickname As String, _
68             ByVal Data As String, _
69             ByVal OriginalData As String, _
70             ByVal ImageUrl As String, _
71             ByVal Name As String, _
72             ByVal PDate As Date, _
73             ByVal Id As Long, _
74             ByVal IsFav As Boolean, _
75             ByVal IsRead As Boolean, _
76             ByVal IsReply As Boolean, _
77             ByVal IsProtect As Boolean, _
78             ByVal IsOwl As Boolean, _
79             ByVal IsMark As Boolean, _
80             ByVal InReplyToUser As String, _
81             ByVal InReplyToId As Long, _
82             ByVal Source As String, _
83             ByVal ReplyToList As List(Of String), _
84             ByVal IsMe As Boolean, _
85             ByVal ImageIndex As Integer, _
86             ByVal IsDm As Boolean, _
87             ByVal Uid As Long, _
88             ByVal FilterHit As Boolean, _
89             ByVal RetweetedBy As String, _
90             ByVal RetweetedId As Long)
91         _Nick = Nickname
92         _Data = Data
93         _ImageUrl = ImageUrl
94         _Name = Name
95         _PDate = PDate
96         _Id = Id
97         _IsFav = IsFav
98         _OrgData = OriginalData
99         _IsRead = IsRead
100         _IsReply = IsReply
101         _IsProtect = IsProtect
102         _IsOWL = IsOwl
103         _IsMark = IsMark
104         _InReplyToUser = InReplyToUser
105         _InReplyToId = InReplyToId
106         _Source = Source
107         _ReplyToList = ReplyToList
108         _IsMe = IsMe
109         _ImageIndex = ImageIndex
110         _IsDm = IsDm
111         _Uid = Uid
112         _FilterHit = FilterHit
113         _RetweetedBy = RetweetedBy
114         _RetweetedId = RetweetedId
115     End Sub
116
117     Public Sub New()
118     End Sub
119
120     Public Property Nickname() As String
121         Get
122             Return _Nick
123         End Get
124         Set(ByVal value As String)
125             _Nick = value
126         End Set
127     End Property
128     Public Property Data() As String
129         Get
130             Return _Data
131         End Get
132         Set(ByVal value As String)
133             _Data = value
134         End Set
135     End Property
136     Public Property ImageUrl() As String
137         Get
138             Return _ImageUrl
139         End Get
140         Set(ByVal value As String)
141             _ImageUrl = value
142         End Set
143     End Property
144     Public Property Name() As String
145         Get
146             Return _Name
147         End Get
148         Set(ByVal value As String)
149             _Name = value
150         End Set
151     End Property
152     Public Property PDate() As Date
153         Get
154             Return _PDate
155         End Get
156         Set(ByVal value As Date)
157             _PDate = value
158         End Set
159     End Property
160     Public Property Id() As Long
161         Get
162             Return _Id
163         End Get
164         Set(ByVal value As Long)
165             _Id = value
166         End Set
167     End Property
168     Public Property IsFav() As Boolean
169         Get
170             If Me.RetweetedId > 0 AndAlso TabInformations.GetInstance.RetweetSource(Me.RetweetedId) IsNot Nothing Then
171                 Return TabInformations.GetInstance.RetweetSource(Me.RetweetedId).IsFav
172             Else
173                 Return _IsFav
174             End If
175         End Get
176         Set(ByVal value As Boolean)
177             _IsFav = value
178             If Me.RetweetedId > 0 AndAlso TabInformations.GetInstance.RetweetSource(Me.RetweetedId) IsNot Nothing Then
179                 TabInformations.GetInstance.RetweetSource(Me.RetweetedId).IsFav = value
180             End If
181         End Set
182     End Property
183     Public Property OriginalData() As String
184         Get
185             Return _OrgData
186         End Get
187         Set(ByVal value As String)
188             _OrgData = value
189         End Set
190     End Property
191     Public Property IsRead() As Boolean
192         Get
193             Return _IsRead
194         End Get
195         Set(ByVal value As Boolean)
196             If value Then
197                 _statuses = _statuses Or Statuses.Read
198             Else
199                 _statuses = _statuses And Not Statuses.Read
200             End If
201             _IsRead = value
202         End Set
203     End Property
204     Public Property IsReply() As Boolean
205         Get
206             Return _IsReply
207         End Get
208         Set(ByVal value As Boolean)
209             _IsReply = value
210         End Set
211     End Property
212     Public Property IsProtect() As Boolean
213         Get
214             Return _IsProtect
215         End Get
216         Set(ByVal value As Boolean)
217             If value Then
218                 _statuses = _statuses Or Statuses.Protect
219             Else
220                 _statuses = _statuses And Not Statuses.Protect
221             End If
222             _IsProtect = value
223         End Set
224     End Property
225     Public Property IsOwl() As Boolean
226         Get
227             Return _IsOWL
228         End Get
229         Set(ByVal value As Boolean)
230             _IsOWL = value
231         End Set
232     End Property
233     Public Property IsMark() As Boolean
234         Get
235             Return _IsMark
236         End Get
237         Set(ByVal value As Boolean)
238             If value Then
239                 _statuses = _statuses Or Statuses.Mark
240             Else
241                 _statuses = _statuses And Not Statuses.Mark
242             End If
243             _IsMark = value
244         End Set
245     End Property
246     Public Property InReplyToUser() As String
247         Get
248             Return _InReplyToUser
249         End Get
250         Set(ByVal value As String)
251             _InReplyToUser = value
252         End Set
253     End Property
254     Public Property InReplyToId() As Long
255         Get
256             Return _InReplyToId
257         End Get
258         Set(ByVal value As Long)
259             _InReplyToId = value
260         End Set
261     End Property
262     Public Property Source() As String
263         Get
264             Return _Source
265         End Get
266         Set(ByVal value As String)
267             _Source = value
268         End Set
269     End Property
270     Public Property ReplyToList() As List(Of String)
271         Get
272             Return _ReplyToList
273         End Get
274         Set(ByVal value As List(Of String))
275             _ReplyToList = value
276         End Set
277     End Property
278     Public Property IsMe() As Boolean
279         Get
280             Return _IsMe
281         End Get
282         Set(ByVal value As Boolean)
283             _IsMe = value
284         End Set
285     End Property
286     Public Property ImageIndex() As Integer
287         Get
288             Return _ImageIndex
289         End Get
290         Set(ByVal value As Integer)
291             _ImageIndex = value
292         End Set
293     End Property
294     Public Property IsDm() As Boolean
295         Get
296             Return _IsDm
297         End Get
298         Set(ByVal value As Boolean)
299             _IsDm = value
300         End Set
301     End Property
302     Public ReadOnly Property StatusIndex() As Integer
303         Get
304             Return _statuses
305         End Get
306     End Property
307     Public Property Uid() As Long
308         Get
309             Return _Uid
310         End Get
311         Set(ByVal value As Long)
312             _Uid = value
313         End Set
314     End Property
315     Public Property FilterHit() As Boolean
316         Get
317             Return _FilterHit
318         End Get
319         Set(ByVal value As Boolean)
320             _FilterHit = value
321         End Set
322     End Property
323     Public Property RetweetedBy() As String
324         Get
325             Return _RetweetedBy
326         End Get
327         Set(ByVal value As String)
328             _RetweetedBy = value
329         End Set
330     End Property
331     Public Property RetweetedId() As Long
332         Get
333             Return _RetweetedId
334         End Get
335         Set(ByVal value As Long)
336             _RetweetedId = value
337         End Set
338     End Property
339     Public Property RelTabName() As String
340         Get
341             Return _searchTabName
342         End Get
343         Set(ByVal value As String)
344             _searchTabName = value
345         End Set
346     End Property
347 End Class
348
349 Public NotInheritable Class TabInformations
350     '個別タブの情報をDictionaryで保持
351     Private _sorter As IdComparerClass
352     Private _tabs As New Dictionary(Of String, TabClass)
353     Private _statuses As New Dictionary(Of Long, PostClass)
354     Private _addedIds As List(Of Long)
355     Private _retweets As New Dictionary(Of Long, PostClass)
356     Private _removedTab As TabClass = Nothing
357
358     '発言の追加
359     'AddPost(複数回) -> DistributePosts          -> SubmitUpdate
360
361     'トランザクション用
362     Private _addCount As Integer
363     Private _soundFile As String
364     Private _notifyPosts As List(Of PostClass)
365     Private ReadOnly LockObj As New Object
366     Private ReadOnly LockUnread As New Object
367
368     Private Shared _instance As TabInformations = New TabInformations
369
370     'List
371     Private _lists As New List(Of ListElement)
372
373     Private Sub New()
374         _sorter = New IdComparerClass()
375     End Sub
376
377     Public Shared Function GetInstance() As TabInformations
378         Return _instance    'singleton
379     End Function
380
381     Public Property SubscribableLists() As List(Of ListElement)
382         Get
383             Return _lists
384         End Get
385         Set(ByVal value As List(Of ListElement))
386             If value IsNot Nothing AndAlso value.Count > 0 Then
387                 For Each tb As TabClass In Me.GetTabsByType(TabUsageType.Lists)
388                     For Each list As ListElement In value
389                         If tb.ListInfo.Id = list.Id Then
390                             tb.ListInfo = list
391                             Exit For
392                         End If
393                     Next
394                 Next
395             End If
396             _lists = value
397         End Set
398     End Property
399
400     Public Sub AddTab(ByVal TabName As String, ByVal TabType As TabUsageType, ByVal List As ListElement)
401         _tabs.Add(TabName, New TabClass(TabName, TabType, List))
402         _tabs(TabName).Sorter.Mode = _sorter.Mode
403         _tabs(TabName).Sorter.Order = _sorter.Order
404     End Sub
405
406     'Public Sub AddTab(ByVal TabName As String, ByVal Tab As TabClass)
407     '    _tabs.Add(TabName, Tab)
408     'End Sub
409
410     Public Sub RemoveTab(ByVal TabName As String)
411         SyncLock LockObj
412             If IsDefaultTab(TabName) Then Exit Sub '念のため
413             If _tabs(TabName).TabType <> TabUsageType.PublicSearch AndAlso _tabs(TabName).TabType <> TabUsageType.Lists Then
414                 Dim homeTab As TabClass = GetTabByType(TabUsageType.Home)
415                 Dim dmName As String = GetTabByType(TabUsageType.DirectMessage).TabName
416
417                 For idx As Integer = 0 To _tabs(TabName).AllCount - 1
418                     Dim exist As Boolean = False
419                     Dim Id As Long = _tabs(TabName).GetId(idx)
420                     For Each key As String In _tabs.Keys
421                         If Not key = TabName AndAlso key <> dmName Then
422                             If _tabs(key).Contains(Id) Then
423                                 exist = True
424                                 Exit For
425                             End If
426                         End If
427                     Next
428                     If Not exist Then homeTab.Add(Id, _statuses(Id).IsRead, False)
429                 Next
430             End If
431             If _removedTab IsNot Nothing Then _removedTab = Nothing
432             _removedTab = _tabs(TabName)
433             _tabs.Remove(TabName)
434         End SyncLock
435     End Sub
436
437     Public Property RemovedTab() As TabClass
438         Get
439             Return _removedTab
440         End Get
441         Set(ByVal value As TabClass)
442             _removedTab = value
443         End Set
444     End Property
445
446     Public Function ContainsTab(ByVal TabText As String) As Boolean
447         Return _tabs.ContainsKey(TabText)
448     End Function
449
450     Public Function ContainsTab(ByVal ts As TabClass) As Boolean
451         Return _tabs.ContainsValue(ts)
452     End Function
453
454     Public Property Tabs() As Dictionary(Of String, TabClass)
455         Get
456             Return _tabs
457         End Get
458         Set(ByVal value As Dictionary(Of String, TabClass))
459             _tabs = value
460         End Set
461     End Property
462
463     Public ReadOnly Property KeysTab() As Collections.Generic.Dictionary(Of String, TabClass).KeyCollection
464         Get
465             Return _tabs.Keys
466         End Get
467     End Property
468
469     Public Sub SortPosts()
470         For Each key As String In _tabs.Keys
471             _tabs(key).Sort()
472         Next
473     End Sub
474
475     Public Property SortOrder() As SortOrder
476         Get
477             Return _sorter.Order
478         End Get
479         Set(ByVal value As SortOrder)
480             _sorter.Order = value
481             For Each key As String In _tabs.Keys
482                 _tabs(key).Sorter.Order = value
483             Next
484         End Set
485     End Property
486
487     Public Property SortMode() As IdComparerClass.ComparerMode
488         Get
489             Return _sorter.Mode
490         End Get
491         Set(ByVal value As IdComparerClass.ComparerMode)
492             _sorter.Mode = value
493             For Each key As String In _tabs.Keys
494                 _tabs(key).Sorter.Mode = value
495             Next
496         End Set
497     End Property
498
499     Public Sub ToggleSortOrder(ByVal SortMode As IdComparerClass.ComparerMode)
500         If _sorter.Mode = SortMode Then
501             If _sorter.Order = Windows.Forms.SortOrder.Ascending Then
502                 _sorter.Order = Windows.Forms.SortOrder.Descending
503             Else
504                 _sorter.Order = Windows.Forms.SortOrder.Ascending
505             End If
506             For Each key As String In _tabs.Keys
507                 _tabs(key).Sorter.Order = _sorter.Order
508             Next
509         Else
510             _sorter.Mode = SortMode
511             _sorter.Order = Windows.Forms.SortOrder.Ascending
512             For Each key As String In _tabs.Keys
513                 _tabs(key).Sorter.Mode = SortMode
514                 _tabs(key).Sorter.Order = Windows.Forms.SortOrder.Ascending
515             Next
516         End If
517         Me.SortPosts()
518     End Sub
519
520     Public ReadOnly Property RetweetSource(ByVal Id As Long) As PostClass
521         Get
522             If _retweets.ContainsKey(Id) Then
523                 Return _retweets(Id)
524             Else
525                 Return Nothing
526             End If
527         End Get
528     End Property
529
530     Public Sub RemoveFavPost(ByVal Id As Long)
531         SyncLock LockObj
532             Dim post As PostClass = Nothing
533             Dim tab As TabClass = Me.GetTabByType(TabUsageType.Favorites)
534             Dim tn As String = tab.TabName
535             If _statuses.ContainsKey(Id) Then
536                 post = _statuses(Id)
537                 '指定タブから該当ID削除
538                 Dim tType As TabUsageType = tab.TabType
539                 If tab.Contains(Id) Then
540                     If tab.UnreadManage AndAlso Not post.IsRead Then    '未読管理
541                         SyncLock LockUnread
542                             tab.UnreadCount -= 1
543                             Me.SetNextUnreadId(Id, tab)
544                         End SyncLock
545                     End If
546                     tab.Remove(Id)
547                 End If
548                 'FavタブからRetweet発言を削除する場合は、他の同一参照Retweetも削除
549                 If tType = TabUsageType.Favorites AndAlso post.RetweetedId > 0 Then
550                     For i As Integer = 0 To tab.AllCount - 1
551                         Dim rPost As PostClass = Me.Item(tn, i)
552                         If rPost.RetweetedId > 0 AndAlso rPost.RetweetedId = post.RetweetedId Then
553                             If tab.UnreadManage AndAlso Not rPost.IsRead Then    '未読管理
554                                 SyncLock LockUnread
555                                     tab.UnreadCount -= 1
556                                     Me.SetNextUnreadId(rPost.Id, tab)
557                                 End SyncLock
558                             End If
559                             tab.Remove(rPost.Id)
560                         End If
561                     Next
562                 End If
563             End If
564             ''TabType=PublicSearchの場合(Postの保存先がTabClass内)
565             'If tab.Contains(Id) AndAlso _
566             '   (tab.TabType = TabUsageType.PublicSearch OrElse tab.TabType = TabUsageType.DirectMessage) Then
567             '    post = tab.Posts(Id)
568             '    If tab.UnreadManage AndAlso Not post.IsRead Then    '未読管理
569             '        SyncLock LockUnread
570             '            tab.UnreadCount -= 1
571             '            Me.SetNextUnreadId(Id, tab)
572             '        End SyncLock
573             '    End If
574             '    tab.Remove(Id)
575             'End If
576         End SyncLock
577     End Sub
578
579     Public Sub RemovePost(ByVal Id As Long)
580         SyncLock LockObj
581             Dim post As PostClass = Nothing
582             If _statuses.ContainsKey(Id) Then
583                 post = _statuses(Id)
584                 '各タブから該当ID削除
585                 For Each key As String In _tabs.Keys
586                     Dim tab As TabClass = _tabs(key)
587                     If tab.Contains(Id) Then
588                         If tab.UnreadManage AndAlso Not post.IsRead Then    '未読管理
589                             SyncLock LockUnread
590                                 tab.UnreadCount -= 1
591                                 Me.SetNextUnreadId(Id, tab)
592                             End SyncLock
593                         End If
594                         tab.Remove(Id)
595                     End If
596                 Next
597                 _statuses.Remove(Id)
598             End If
599             For Each tb As TabClass In _tabs.Values
600                 If (tb.TabType = TabUsageType.PublicSearch OrElse tb.TabType = TabUsageType.DirectMessage OrElse tb.TabType = TabUsageType.Lists) _
601                    AndAlso tb.Contains(Id) Then
602                     post = tb.Posts(Id)
603                     If tb.UnreadManage AndAlso Not post.IsRead Then
604                         SyncLock LockUnread
605                             tb.UnreadCount -= 1
606                             Me.SetNextUnreadId(Id, tb)
607                         End SyncLock
608                     End If
609                     tb.Remove(Id)
610                 End If
611             Next
612         End SyncLock
613     End Sub
614
615     Public Function GetOldestUnreadId(ByVal TabName As String) As Integer
616         Dim tb As TabClass = _tabs(TabName)
617         If tb.OldestUnreadId > -1 AndAlso _
618            tb.Contains(tb.OldestUnreadId) AndAlso _
619            tb.UnreadCount > 0 Then
620             '未読アイテムへ
621             Dim isRead As Boolean
622             If tb.TabType <> TabUsageType.PublicSearch AndAlso tb.TabType <> TabUsageType.DirectMessage AndAlso tb.TabType <> TabUsageType.Lists Then
623                 isRead = _statuses(tb.OldestUnreadId).IsRead
624             Else
625                 isRead = tb.Posts(tb.OldestUnreadId).IsRead
626             End If
627             If isRead Then
628                 '状態不整合(最古未読IDが実は既読)
629                 SyncLock LockUnread
630                     Me.SetNextUnreadId(-1, tb)  '頭から探索
631                 End SyncLock
632                 If tb.OldestUnreadId = -1 Then
633                     Return -1
634                 Else
635                     Return tb.IndexOf(tb.OldestUnreadId)
636                 End If
637             Else
638                 Return tb.IndexOf(tb.OldestUnreadId)    '最短経路
639             End If
640         Else
641             '一見未読なさそうだが、未読カウントはあるので探索
642             If tb.UnreadCount > 0 Then
643                 SyncLock LockUnread
644                     Me.SetNextUnreadId(-1, tb)
645                 End SyncLock
646                 If tb.OldestUnreadId = -1 Then
647                     Return -1
648                 Else
649                     Return tb.IndexOf(tb.OldestUnreadId)
650                 End If
651             Else
652                 Return -1
653             End If
654         End If
655     End Function
656
657     Private Sub SetNextUnreadId(ByVal CurrentId As Long, ByVal Tab As TabClass)
658         'CurrentID:今既読にしたID(OldestIDの可能性あり)
659         '最古未読が設定されていて、既読の場合(1発言以上存在)
660         Try
661             Dim posts As Dictionary(Of Long, PostClass)
662             If Tab.TabType <> TabUsageType.PublicSearch AndAlso Tab.TabType <> TabUsageType.DirectMessage AndAlso Tab.TabType <> TabUsageType.Lists Then
663                 posts = _statuses
664             Else
665                 posts = Tab.Posts
666             End If
667             If Tab.OldestUnreadId > -1 AndAlso _
668                posts.ContainsKey(Tab.OldestUnreadId) AndAlso _
669                posts.Item(Tab.OldestUnreadId).IsRead AndAlso _
670                _sorter.Mode = IdComparerClass.ComparerMode.Id Then     '次の未読探索
671                 If Tab.UnreadCount = 0 Then
672                     '未読数0→最古未読なし
673                     Tab.OldestUnreadId = -1
674                 ElseIf Tab.OldestUnreadId = CurrentId Then
675                     '最古IDを既読にしたタイミング→次のIDから続けて探索
676                     Dim idx As Integer = Tab.IndexOf(CurrentId)
677                     If idx > -1 Then
678                         '続きから探索
679                         FindUnreadId(idx, Tab)
680                     Else
681                         '頭から探索
682                         FindUnreadId(-1, Tab)
683                     End If
684                 Else
685                     '頭から探索
686                     FindUnreadId(-1, Tab)
687                 End If
688             Else
689                 '頭から探索
690                 FindUnreadId(-1, Tab)
691             End If
692         Catch ex As Generic.KeyNotFoundException
693             '頭から探索
694             FindUnreadId(-1, Tab)
695         End Try
696     End Sub
697
698     Private Sub FindUnreadId(ByVal StartIdx As Integer, ByVal Tab As TabClass)
699         If Tab.AllCount = 0 Then
700             Tab.OldestUnreadId = -1
701             Tab.UnreadCount = 0
702             Exit Sub
703         End If
704         Dim toIdx As Integer = 0
705         Dim stp As Integer = 1
706         Tab.OldestUnreadId = -1
707         If _sorter.Order = Windows.Forms.SortOrder.Ascending Then
708             If StartIdx = -1 Then
709                 StartIdx = 0
710             Else
711                 'StartIdx += 1
712                 If StartIdx > Tab.AllCount - 1 Then StartIdx = Tab.AllCount - 1 '念のため
713             End If
714             toIdx = Tab.AllCount - 1
715             If toIdx < 0 Then toIdx = 0 '念のため
716             stp = 1
717         Else
718             If StartIdx = -1 Then
719                 StartIdx = Tab.AllCount - 1
720             Else
721                 'StartIdx -= 1
722             End If
723             If StartIdx < 0 Then StartIdx = 0 '念のため
724             toIdx = 0
725             stp = -1
726         End If
727         If Tab.TabType <> TabUsageType.PublicSearch AndAlso Tab.TabType <> TabUsageType.DirectMessage AndAlso Tab.TabType <> TabUsageType.Lists Then
728             For i As Integer = StartIdx To toIdx Step stp
729                 If Not _statuses(Tab.GetId(i)).IsRead Then
730                     Tab.OldestUnreadId = Tab.GetId(i)
731                     Exit For
732                 End If
733             Next
734         Else
735             For i As Integer = StartIdx To toIdx Step stp
736                 If Not Tab.Posts(Tab.GetId(i)).IsRead Then
737                     Tab.OldestUnreadId = Tab.GetId(i)
738                     Exit For
739                 End If
740             Next
741         End If
742     End Sub
743
744     Public Function DistributePosts() As Integer
745         SyncLock LockObj
746             '戻り値は追加件数
747             'If _addedIds Is Nothing Then Return 0
748             'If _addedIds.Count = 0 Then Return 0
749
750             If _addedIds Is Nothing Then _addedIds = New List(Of Long)
751             If _notifyPosts Is Nothing Then _notifyPosts = New List(Of PostClass)
752             Me.Distribute()    'タブに仮振分
753             Dim retCnt As Integer = _addedIds.Count
754             _addCount += retCnt
755             _addedIds.Clear()
756             _addedIds = Nothing     '後始末
757             Return retCnt     '件数
758         End SyncLock
759     End Function
760
761     Public Function SubmitUpdate(ByRef soundFile As String, ByRef notifyPosts As PostClass()) As Integer
762         '注:メインスレッドから呼ぶこと
763         SyncLock LockObj
764             If _notifyPosts Is Nothing Then
765                 soundFile = ""
766                 notifyPosts = Nothing
767                 Return 0
768             End If
769
770             For Each tb As TabClass In _tabs.Values
771                 If tb.TabType = TabUsageType.PublicSearch OrElse tb.TabType = TabUsageType.DirectMessage OrElse tb.TabType = TabUsageType.Lists Then
772                     _addCount += tb.GetTemporaryCount
773                 End If
774                 tb.AddSubmit()  '振分確定(各タブに反映)
775             Next
776             Me.SortPosts()
777
778             soundFile = _soundFile
779             _soundFile = ""
780             notifyPosts = _notifyPosts.ToArray()
781             _notifyPosts.Clear()
782             _notifyPosts = Nothing
783             Dim retCnt As Integer = _addCount
784             _addCount = 0
785             Return retCnt    '件数(EndUpdateの戻り値と同じ)
786         End SyncLock
787     End Function
788
789     Private Sub Distribute()
790         '各タブのフィルターと照合。合致したらタブにID追加
791         '通知メッセージ用に、表示必要な発言リストと再生サウンドを返す
792         'notifyPosts = New List(Of PostClass)
793         Dim homeTab As TabClass = GetTabByType(TabUsageType.Home)
794         Dim replyTab As TabClass = GetTabByType(TabUsageType.Mentions)
795         Dim dmTab As TabClass = GetTabByType(TabUsageType.DirectMessage)
796         Dim favTab As TabClass = GetTabByType(TabUsageType.Favorites)
797         For Each id As Long In _addedIds
798             Dim post As PostClass = _statuses(id)
799             Dim add As Boolean = False  '通知リスト追加フラグ
800             Dim mv As Boolean = False   '移動フラグ(Recent追加有無)
801             For Each tn As String In _tabs.Keys
802                 Dim rslt As HITRESULT = HITRESULT.None
803                 rslt = _tabs(tn).AddFiltered(post)
804                 If rslt <> HITRESULT.None Then
805                     If rslt = HITRESULT.CopyAndMark Then post.IsMark = True 'マークあり
806                     If rslt = HITRESULT.Move Then
807                         mv = True '移動
808                         post.IsMark = False
809                     End If
810                     If _tabs(tn).Notify Then add = True '通知あり
811                     If Not _tabs(tn).SoundFile = "" AndAlso _soundFile = "" Then
812                         _soundFile = _tabs(tn).SoundFile 'wavファイル(未設定の場合のみ)
813                     End If
814                     post.FilterHit = True
815                 Else
816                     post.FilterHit = False
817                 End If
818             Next
819             If Not mv Then  '移動されなかったらRecentに追加
820                 homeTab.Add(post.Id, post.IsRead, True)
821                 If Not homeTab.SoundFile = "" AndAlso _soundFile = "" Then _soundFile = homeTab.SoundFile
822                 If homeTab.Notify Then add = True
823             End If
824             If post.IsReply Then    'ReplyだったらReplyタブに追加
825                 replyTab.Add(post.Id, post.IsRead, True)
826                 If Not replyTab.SoundFile = "" Then _soundFile = replyTab.SoundFile
827                 If replyTab.Notify Then add = True
828             End If
829             If post.IsFav Then    'Fav済み発言だったらFavoritesタブに追加
830                 If favTab.Contains(post.Id) Then
831                     '取得済みなら非通知
832                     '_soundFile = ""
833                     add = False
834                 Else
835                     favTab.Add(post.Id, post.IsRead, True)
836                     If Not favTab.SoundFile = "" AndAlso _soundFile = "" Then _soundFile = favTab.SoundFile
837                     If favTab.Notify Then add = True
838                 End If
839             End If
840             If add Then _notifyPosts.Add(post)
841         Next
842         For Each tb As TabClass In _tabs.Values
843             If tb.TabType = TabUsageType.PublicSearch OrElse tb.TabType = TabUsageType.DirectMessage OrElse tb.TabType = TabUsageType.Lists Then
844                 If tb.Notify Then
845                     If tb.GetTemporaryCount > 0 Then
846                         For Each post As PostClass In tb.GetTemporaryPosts
847                             Dim exist As Boolean = False
848                             For Each npost As PostClass In _notifyPosts
849                                 If npost.Id = post.Id Then
850                                     exist = True
851                                     Exit For
852                                 End If
853                             Next
854                             If Not exist Then _notifyPosts.Add(post)
855                         Next
856                         If tb.SoundFile <> "" Then
857                             If tb.TabType = TabUsageType.DirectMessage OrElse _soundFile = "" Then
858                                 _soundFile = tb.SoundFile
859                             End If
860                         End If
861                     End If
862                 End If
863             End If
864         Next
865     End Sub
866
867     Public Sub AddPost(ByVal Item As PostClass)
868         SyncLock LockObj
869             If Item.RelTabName = "" Then
870                 If Not Item.IsDm Then
871                     If _statuses.ContainsKey(Item.Id) Then
872                         If Item.IsFav Then
873                             _statuses.Item(Item.Id).IsFav = True
874                         Else
875                             Exit Sub        '追加済みなら何もしない
876                         End If
877                     Else
878                         _statuses.Add(Item.Id, Item)
879                     End If
880                     If Item.RetweetedId > 0 Then
881                         Me.AddRetweet(Item)
882                     End If
883                     If Item.IsFav AndAlso _retweets.ContainsKey(Item.Id) Then
884                         Exit Sub    'Fav済みのRetweet元発言は追加しない
885                     End If
886                     If _addedIds Is Nothing Then _addedIds = New List(Of Long) 'タブ追加用IDコレクション準備
887                     _addedIds.Add(Item.Id)
888                 Else
889                     'DM
890                     Dim tb As TabClass = Me.GetTabByType(TabUsageType.DirectMessage)
891                     If tb.Contains(Item.Id) Then Exit Sub
892                     tb.AddPostToInnerStorage(Item)
893                 End If
894             Else
895                 '公式検索、リストの場合
896                 Dim tb As TabClass
897                 If Me.Tabs.ContainsKey(Item.RelTabName) Then
898                     tb = Me.Tabs(Item.RelTabName)
899                 Else
900                     Exit Sub
901                 End If
902                 If tb Is Nothing Then Exit Sub
903                 If tb.Contains(Item.Id) Then Exit Sub
904                 'tb.Add(Item.Id, Item.IsRead, True)
905                 tb.AddPostToInnerStorage(Item)
906             End If
907         End SyncLock
908     End Sub
909
910     Private Sub AddRetweet(ByVal item As PostClass)
911         If _retweets.ContainsKey(item.RetweetedId) Then Exit Sub
912
913         _retweets.Add( _
914                     item.RetweetedId, _
915                     New PostClass( _
916                         item.Nickname, _
917                         item.Data, _
918                         item.OriginalData, _
919                         item.ImageUrl, _
920                         item.Name, _
921                         item.PDate, _
922                         item.RetweetedId, _
923                         item.IsFav, _
924                         item.IsRead, _
925                         item.IsReply, _
926                         item.IsProtect, _
927                         item.IsOwl, _
928                         item.IsMark, _
929                         item.InReplyToUser, _
930                         item.InReplyToId, _
931                         item.Source, _
932                         item.ReplyToList, _
933                         item.IsMe, _
934                         item.ImageIndex, _
935                         item.IsDm, _
936                         item.Uid, _
937                         item.FilterHit, _
938                         "", _
939                         0 _
940                     ) _
941                 )
942     End Sub
943
944     Public Sub SetRead(ByVal Read As Boolean, ByVal TabName As String, ByVal Index As Integer)
945         'Read:True=既読へ False=未読へ
946         Dim tb As TabClass = _tabs(TabName)
947
948         If tb.UnreadManage = False Then Exit Sub '未読管理していなければ終了
949
950         Dim Id As Long = tb.GetId(Index)
951         Dim post As PostClass
952         If tb.TabType <> TabUsageType.PublicSearch AndAlso tb.TabType <> TabUsageType.DirectMessage AndAlso tb.TabType <> TabUsageType.Lists Then
953             post = _statuses(Id)
954         Else
955             post = tb.Posts(Id)
956         End If
957         If post.IsRead = Read Then Exit Sub '状態変更なければ終了
958
959         post.IsRead = Read '指定の状態に変更
960
961         SyncLock LockUnread
962             If Read Then
963                 tb.UnreadCount -= 1
964                 Me.SetNextUnreadId(Id, tb)  '次の未読セット
965                 '他タブの最古未読IDはタブ切り替え時に。
966                 If tb.TabType = TabUsageType.PublicSearch OrElse tb.TabType = TabUsageType.DirectMessage OrElse tb.TabType = TabUsageType.Lists Then Exit Sub
967                 For Each key As String In _tabs.Keys
968                     If key <> TabName AndAlso _
969                        _tabs(key).UnreadManage AndAlso _
970                        _tabs(key).Contains(Id) AndAlso _
971                        (_tabs(key).TabType <> TabUsageType.PublicSearch AndAlso _tabs(key).TabType <> TabUsageType.DirectMessage AndAlso _tabs(key).TabType <> TabUsageType.Lists) Then
972                         _tabs(key).UnreadCount -= 1
973                         If _tabs(key).OldestUnreadId = Id Then _tabs(key).OldestUnreadId = -1
974                     End If
975                 Next
976             Else
977                 tb.UnreadCount += 1
978                 If tb.OldestUnreadId > Id OrElse tb.OldestUnreadId = -1 Then tb.OldestUnreadId = Id
979                 If tb.TabType = TabUsageType.PublicSearch OrElse tb.TabType = TabUsageType.DirectMessage OrElse tb.TabType = TabUsageType.Lists Then Exit Sub
980                 For Each key As String In _tabs.Keys
981                     If Not key = TabName AndAlso _
982                        _tabs(key).UnreadManage AndAlso _
983                        _tabs(key).Contains(Id) AndAlso _
984                        (_tabs(key).TabType <> TabUsageType.PublicSearch AndAlso _tabs(key).TabType <> TabUsageType.DirectMessage AndAlso _tabs(key).TabType <> TabUsageType.Lists) Then
985                         _tabs(key).UnreadCount += 1
986                         If _tabs(key).OldestUnreadId > Id Then _tabs(key).OldestUnreadId = Id
987                     End If
988                 Next
989             End If
990         End SyncLock
991     End Sub
992
993     Public Sub SetRead()
994         Dim tb As TabClass = GetTabByType(TabUsageType.Home)
995         If tb.UnreadManage = False Then Exit Sub
996
997         For i As Integer = 0 To tb.AllCount - 1
998             Dim id As Long = tb.GetId(i)
999             If Not _statuses(id).IsReply AndAlso _
1000                Not _statuses(id).IsRead AndAlso _
1001                Not _statuses(id).FilterHit Then
1002                 _statuses(id).IsRead = True
1003                 Me.SetNextUnreadId(id, tb)  '次の未読セット
1004                 For Each key As String In _tabs.Keys
1005                     If _tabs(key).UnreadManage AndAlso _
1006                        _tabs(key).Contains(id) Then
1007                         _tabs(key).UnreadCount -= 1
1008                         If _tabs(key).OldestUnreadId = id Then _tabs(key).OldestUnreadId = -1
1009                     End If
1010                 Next
1011             End If
1012         Next
1013     End Sub
1014
1015     Public ReadOnly Property Item(ByVal ID As Long) As PostClass
1016         Get
1017             If _statuses.ContainsKey(ID) Then Return _statuses(ID)
1018             For Each tb As TabClass In _tabs.Values
1019                 If (tb.TabType = TabUsageType.PublicSearch OrElse tb.TabType = TabUsageType.DirectMessage OrElse tb.TabType = TabUsageType.Lists) AndAlso _
1020                    tb.Contains(ID) Then
1021                     Return tb.Posts(ID)
1022                 End If
1023             Next
1024             Return Nothing
1025         End Get
1026     End Property
1027
1028     Public ReadOnly Property Item(ByVal TabName As String, ByVal Index As Integer) As PostClass
1029         Get
1030             'If Not _tabs.ContainsKey(TabName) Then Return Nothing
1031             If _tabs(TabName).TabType = TabUsageType.PublicSearch OrElse _tabs(TabName).TabType = TabUsageType.DirectMessage OrElse _tabs(TabName).TabType = TabUsageType.Lists Then
1032                 Return _tabs(TabName).Posts(_tabs(TabName).GetId(Index))
1033             Else
1034                 Return _statuses(_tabs(TabName).GetId(Index))
1035             End If
1036         End Get
1037     End Property
1038
1039     Public ReadOnly Property Item(ByVal TabName As String, ByVal StartIndex As Integer, ByVal EndIndex As Integer) As PostClass()
1040         Get
1041             Dim length As Integer = EndIndex - StartIndex + 1
1042             Dim posts() As PostClass = New PostClass(length - 1) {}
1043             If _tabs(TabName).TabType = TabUsageType.PublicSearch OrElse _tabs(TabName).TabType = TabUsageType.DirectMessage OrElse _tabs(TabName).TabType = TabUsageType.Lists Then
1044                 For i As Integer = 0 To length - 1
1045                     posts(i) = _tabs(TabName).Posts(_tabs(TabName).GetId(StartIndex + i))
1046                 Next i
1047             Else
1048                 For i As Integer = 0 To length - 1
1049                     posts(i) = _statuses(_tabs(TabName).GetId(StartIndex + i))
1050                 Next i
1051             End If
1052             Return posts
1053         End Get
1054     End Property
1055
1056     'Public ReadOnly Property ItemCount() As Integer
1057     '    Get
1058     '        SyncLock LockObj
1059     '            Return _statuses.Count   'DM,公式検索は除く
1060     '        End SyncLock
1061     '    End Get
1062     'End Property
1063
1064     Public Function ContainsKey(ByVal Id As Long) As Boolean
1065         'DM,公式検索は非対応
1066         SyncLock LockObj
1067             Return _statuses.ContainsKey(Id)
1068         End SyncLock
1069     End Function
1070
1071     Public Function ContainsKey(ByVal Id As Long, ByVal TabName As String) As Boolean
1072         'DM,公式検索は対応版
1073         SyncLock LockObj
1074             Return _tabs(TabName).Contains(Id)
1075         End SyncLock
1076     End Function
1077
1078     Public Sub SetUnreadManage(ByVal Manage As Boolean)
1079         If Manage Then
1080             For Each key As String In _tabs.Keys
1081                 Dim tb As TabClass = _tabs(key)
1082                 If tb.UnreadManage Then
1083                     SyncLock LockUnread
1084                         Dim cnt As Integer = 0
1085                         Dim oldest As Long = Long.MaxValue
1086                         Dim posts As Dictionary(Of Long, PostClass)
1087                         If tb.TabType <> TabUsageType.PublicSearch AndAlso tb.TabType <> TabUsageType.DirectMessage AndAlso tb.TabType <> TabUsageType.Lists Then
1088                             posts = _statuses
1089                         Else
1090                             posts = tb.Posts
1091                         End If
1092                         For Each id As Long In tb.BackupIds
1093                             If Not posts(id).IsRead Then
1094                                 cnt += 1
1095                                 If oldest > id Then oldest = id
1096                             End If
1097                         Next
1098                         If oldest = Long.MaxValue Then oldest = -1
1099                         tb.OldestUnreadId = oldest
1100                         tb.UnreadCount = cnt
1101                     End SyncLock
1102                 End If
1103             Next
1104         Else
1105             For Each key As String In _tabs.Keys
1106                 Dim tb As TabClass = _tabs(key)
1107                 If tb.UnreadManage AndAlso tb.UnreadCount > 0 Then
1108                     SyncLock LockUnread
1109                         tb.UnreadCount = 0
1110                         tb.OldestUnreadId = -1
1111                     End SyncLock
1112                 End If
1113             Next
1114         End If
1115     End Sub
1116
1117     Public Sub RenameTab(ByVal Original As String, ByVal NewName As String)
1118         Dim tb As TabClass = _tabs(Original)
1119         _tabs.Remove(Original)
1120         tb.TabName = NewName
1121         _tabs.Add(NewName, tb)
1122     End Sub
1123
1124     Public Sub FilterAll()
1125         SyncLock LockObj
1126             Dim tbr As TabClass = GetTabByType(TabUsageType.Home)
1127             Dim replyTab As TabClass = GetTabByType(TabUsageType.Mentions)
1128             For Each key As String In _tabs.Keys
1129                 Dim tb As TabClass = _tabs(key)
1130                 If tb.FilterModified Then
1131                     tb.FilterModified = False
1132                     Dim orgIds() As Long = tb.BackupIds()
1133                     tb.ClearIDs()
1134                     ''''''''''''''フィルター前のIDsを退避。どのタブにも含まれないidはrecentへ追加
1135                     ''''''''''''''moveフィルターにヒットした際、recentに該当あればrecentから削除
1136                     For Each id As Long In _statuses.Keys
1137                         Dim post As PostClass = _statuses.Item(id)
1138                         If post.IsDm Then Continue For
1139                         Dim rslt As HITRESULT = HITRESULT.None
1140                         rslt = tb.AddFiltered(post)
1141                         Select Case rslt
1142                             Case HITRESULT.CopyAndMark
1143                                 post.IsMark = True 'マークあり
1144                                 post.FilterHit = True
1145                             Case HITRESULT.Move
1146                                 tbr.Remove(post.Id, post.IsRead)
1147                                 post.IsMark = False
1148                                 post.FilterHit = True
1149                             Case HITRESULT.Copy
1150                                 post.IsMark = False
1151                                 post.FilterHit = True
1152                             Case HITRESULT.None
1153                                 If key = replyTab.TabName AndAlso post.IsReply Then replyTab.Add(post.Id, post.IsRead, True)
1154                                 If post.IsFav Then GetTabByType(TabUsageType.Favorites).Add(post.Id, post.IsRead, True)
1155                                 post.FilterHit = False
1156                         End Select
1157                     Next
1158                     tb.AddSubmit()  '振分確定
1159                     For Each id As Long In orgIds
1160                         Dim hit As Boolean = False
1161                         For Each tkey As String In _tabs.Keys
1162                             If _tabs(tkey).Contains(id) Then
1163                                 hit = True
1164                                 Exit For
1165                             End If
1166                         Next
1167                         If Not hit Then tbr.Add(id, _statuses(id).IsRead, False)
1168                     Next
1169                 End If
1170             Next
1171             Me.SortPosts()
1172         End SyncLock
1173     End Sub
1174
1175     Public Function GetId(ByVal TabName As String, ByVal IndexCollection As ListView.SelectedIndexCollection) As Long()
1176         If IndexCollection.Count = 0 Then Return Nothing
1177
1178         Dim tb As TabClass = _tabs(TabName)
1179         Dim Ids(IndexCollection.Count - 1) As Long
1180         For i As Integer = 0 To Ids.Length - 1
1181             Ids(i) = tb.GetId(IndexCollection(i))
1182         Next
1183         Return Ids
1184     End Function
1185
1186     Public Function GetId(ByVal TabName As String, ByVal Index As Integer) As Long
1187         Return _tabs(TabName).GetId(Index)
1188     End Function
1189
1190     Public Function IndexOf(ByVal TabName As String, ByVal Ids() As Long) As Integer()
1191         If Ids Is Nothing Then Return Nothing
1192         Dim idx(Ids.Length - 1) As Integer
1193         Dim tb As TabClass = _tabs(TabName)
1194         For i As Integer = 0 To Ids.Length - 1
1195             idx(i) = tb.IndexOf(Ids(i))
1196         Next
1197         Return idx
1198     End Function
1199
1200     Public Function IndexOf(ByVal TabName As String, ByVal Id As Long) As Integer
1201         Return _tabs(TabName).IndexOf(Id)
1202     End Function
1203
1204     Public Sub ClearTabIds(ByVal TabName As String)
1205         '不要なPostを削除
1206         SyncLock LockObj
1207             If _tabs(TabName).TabType <> TabUsageType.PublicSearch AndAlso _tabs(TabName).TabType <> TabUsageType.DirectMessage AndAlso _tabs(TabName).TabType <> TabUsageType.Lists Then
1208                 For Each Id As Long In _tabs(TabName).BackupIds
1209                     Dim Hit As Boolean = False
1210                     For Each tb As TabClass In _tabs.Values
1211                         If tb.Contains(Id) Then
1212                             Hit = True
1213                             Exit For
1214                         End If
1215                     Next
1216                     If Not Hit Then _statuses.Remove(Id)
1217                 Next
1218             End If
1219
1220             '指定タブをクリア
1221             _tabs(TabName).ClearIDs()
1222         End SyncLock
1223     End Sub
1224
1225     Public Sub SetTabUnreadManage(ByVal TabName As String, ByVal Manage As Boolean)
1226         Dim tb As TabClass = _tabs(TabName)
1227         SyncLock LockUnread
1228             If Manage Then
1229                 Dim cnt As Integer = 0
1230                 Dim oldest As Long = Long.MaxValue
1231                 Dim posts As Dictionary(Of Long, PostClass)
1232                 If tb.TabType <> TabUsageType.PublicSearch AndAlso tb.TabType <> TabUsageType.DirectMessage AndAlso tb.TabType <> TabUsageType.Lists Then
1233                     posts = _statuses
1234                 Else
1235                     posts = tb.Posts
1236                 End If
1237                 For Each id As Long In tb.BackupIds
1238                     If Not posts(id).IsRead Then
1239                         cnt += 1
1240                         If oldest > id Then oldest = id
1241                     End If
1242                 Next
1243                 If oldest = Long.MaxValue Then oldest = -1
1244                 tb.OldestUnreadId = oldest
1245                 tb.UnreadCount = cnt
1246             Else
1247                 tb.OldestUnreadId = -1
1248                 tb.UnreadCount = 0
1249             End If
1250         End SyncLock
1251         tb.UnreadManage = Manage
1252     End Sub
1253
1254     'Public Sub RefreshOwl(ByVal follower As List(Of String))
1255     '    SyncLock LockObj
1256     '        If follower.Count > 1 Then
1257     '            For Each id As Long In _statuses.Keys
1258     '                _statuses(id).IsOwl = Not follower.Contains(_statuses(id).Name.ToLower())
1259     '            Next
1260     '        Else
1261     '            For Each id As Long In _statuses.Keys
1262     '                _statuses(id).IsOwl = False
1263     '            Next
1264     '        End If
1265     '    End SyncLock
1266     'End Sub
1267
1268     Public Sub RefreshOwl(ByVal follower As List(Of Long))
1269         SyncLock LockObj
1270             If follower.Count > 0 Then
1271                 For Each post As PostClass In _statuses.Values
1272                     'If post.Uid = 0 OrElse post.IsDm Then Continue For
1273                     If post.IsMe Then
1274                         post.IsOwl = False
1275                     Else
1276                         post.IsOwl = Not follower.Contains(post.Uid)
1277                     End If
1278                 Next
1279             Else
1280                 For Each id As Long In _statuses.Keys
1281                     _statuses(id).IsOwl = False
1282                 Next
1283             End If
1284         End SyncLock
1285     End Sub
1286
1287     Public Function GetTabByType(ByVal tabType As TabUsageType) As TabClass
1288         'Home,Mentions,DM,Favは1つに制限する
1289         'その他のタイプを指定されたら、最初に合致したものを返す
1290         '合致しなければNothingを返す
1291         SyncLock LockObj
1292             For Each tb As TabClass In _tabs.Values
1293                 If tb IsNot Nothing AndAlso tb.TabType = tabType Then Return tb
1294             Next
1295             Return Nothing
1296         End SyncLock
1297     End Function
1298
1299     Public Function GetTabsByType(ByVal tabType As TabUsageType) As List(Of TabClass)
1300         '合致したタブをListで返す
1301         '合致しなければ空のListを返す
1302         SyncLock LockObj
1303             Dim tbs As New List(Of TabClass)
1304             For Each tb As TabClass In _tabs.Values
1305                 If tb.TabType = tabType Then tbs.Add(tb)
1306             Next
1307             Return tbs
1308         End SyncLock
1309     End Function
1310
1311     Public Function GetTabByName(ByVal tabName As String) As TabClass
1312         SyncLock LockObj
1313             If _tabs.ContainsKey(tabName) Then Return _tabs(tabName)
1314             Return Nothing
1315         End SyncLock
1316     End Function
1317
1318     ' デフォルトタブの判定処理
1319     Public Function IsDefaultTab(ByVal tabName As String) As Boolean
1320         If tabName IsNot Nothing AndAlso _
1321            _tabs.ContainsKey(tabName) AndAlso _
1322            (_tabs(tabName).TabType = TabUsageType.Home OrElse _
1323            _tabs(tabName).TabType = TabUsageType.Mentions OrElse _
1324            _tabs(tabName).TabType = TabUsageType.DirectMessage OrElse _
1325            _tabs(tabName).TabType = TabUsageType.Favorites) Then
1326             Return True
1327         Else
1328             Return False
1329         End If
1330     End Function
1331
1332     Public Function GetUniqueTabName() As String
1333         Dim tabNameTemp As String = "MyTab" + (_tabs.Count + 1).ToString
1334         For i As Integer = 2 To 100
1335             If _tabs.ContainsKey(tabNameTemp) Then
1336                 tabNameTemp = "MyTab" + (_tabs.Count + i).ToString
1337             Else
1338                 Exit For
1339             End If
1340         Next
1341         Return tabNameTemp
1342     End Function
1343
1344     Public ReadOnly Property Posts() As Dictionary(Of Long, PostClass)
1345         Get
1346             Return _statuses
1347         End Get
1348     End Property
1349 End Class
1350
1351 <Serializable()> _
1352 Public NotInheritable Class TabClass
1353     Private _unreadManage As Boolean = False
1354     Private _notify As Boolean = False
1355     Private _soundFile As String = ""
1356     Private _filters As List(Of FiltersClass)
1357     Private _oldestUnreadItem As Long = -1     'ID
1358     Private _unreadCount As Integer = 0
1359     Private _ids As List(Of Long)
1360     Private _filterMod As Boolean = False
1361     Private _tmpIds As New List(Of TemporaryId)
1362     Private _tabName As String = ""
1363     Private _tabType As TabUsageType = TabUsageType.Undefined
1364     Private _posts As New Dictionary(Of Long, PostClass)
1365     Private _sorter As New IdComparerClass
1366     Private _oldestId As Long = -1   '古いポスト取得用
1367
1368 #Region "検索"
1369     'Search query
1370     Private _searchLang As String = ""
1371     Private _searchWords As String = ""
1372
1373     Public Property SearchLang() As String
1374         Get
1375             Return _searchLang
1376         End Get
1377         Set(ByVal value As String)
1378             _searchLang = value
1379         End Set
1380     End Property
1381     Public Property SearchWords() As String
1382         Get
1383             Return _searchWords
1384         End Get
1385         Set(ByVal value As String)
1386             _searchWords = value.Trim
1387         End Set
1388     End Property
1389     Public ReadOnly Property SearchPage() As Integer
1390         Get
1391             Return ((_ids.Count \ 40) + 1)
1392         End Get
1393     End Property
1394     Private _beforeQuery As New Dictionary(Of String, String)
1395     Public Sub SaveQuery(ByVal more As Boolean)
1396         Dim qry As New Dictionary(Of String, String)
1397         If String.IsNullOrEmpty(_searchWords) Then
1398             _beforeQuery = qry
1399             Exit Sub
1400         End If
1401         qry.Add("q", _searchWords)
1402         If Not String.IsNullOrEmpty(_searchLang) Then qry.Add("lang", _searchLang)
1403         _beforeQuery = qry
1404     End Sub
1405
1406     Public Function IsQueryChanged() As Boolean
1407         Dim qry As New Dictionary(Of String, String)
1408         If Not String.IsNullOrEmpty(_searchWords) Then
1409             qry.Add("q", _searchWords)
1410             If Not String.IsNullOrEmpty(_searchLang) Then qry.Add("lang", _searchLang)
1411         End If
1412         If qry.Count <> _beforeQuery.Count Then Return True
1413
1414         For Each kvp As KeyValuePair(Of String, String) In qry
1415             If Not _beforeQuery.ContainsKey(kvp.Key) OrElse _beforeQuery(kvp.Key) <> kvp.Value Then
1416                 Return True
1417             End If
1418         Next
1419     End Function
1420 #End Region
1421
1422 #Region "リスト"
1423     Private _listInfo As ListElement
1424     Public Property ListInfo() As ListElement
1425         Get
1426             Return _listInfo
1427         End Get
1428         Set(ByVal value As ListElement)
1429             _listInfo = value
1430         End Set
1431     End Property
1432 #End Region
1433
1434     Public Property OldestId() As Long
1435         Get
1436             Return _oldestId
1437         End Get
1438         Set(ByVal value As Long)
1439             _oldestId = value
1440         End Set
1441     End Property
1442
1443     <Xml.Serialization.XmlIgnore()> _
1444     Public Property Posts() As Dictionary(Of Long, PostClass)
1445         Get
1446             Return _posts
1447         End Get
1448         Set(ByVal value As Dictionary(Of Long, PostClass))
1449             _posts = value
1450         End Set
1451     End Property
1452
1453     'Public Function SearchedPost(ByVal Id As Long) As PostClass
1454     '    If Not _posts.ContainsKey(Id) Then Return Nothing
1455     '    Return _posts(Id)
1456     'End Function
1457
1458     Public Function GetTemporaryPosts() As PostClass()
1459         Dim tempPosts As New List(Of PostClass)
1460         If _tmpIds.Count = 0 Then Return tempPosts.ToArray
1461         For Each tempId As TemporaryId In _tmpIds
1462             tempPosts.Add(_posts(tempId.Id))
1463         Next
1464         Return tempPosts.ToArray
1465     End Function
1466
1467     Public Function GetTemporaryCount() As Integer
1468         Return _tmpIds.Count
1469     End Function
1470
1471     Private Structure TemporaryId
1472         Public Id As Long
1473         Public Read As Boolean
1474
1475         Public Sub New(ByVal argId As Long, ByVal argRead As Boolean)
1476             Id = argId
1477             Read = argRead
1478         End Sub
1479     End Structure
1480
1481     Public Sub New()
1482         _filters = New List(Of FiltersClass)
1483         _notify = True
1484         _soundFile = ""
1485         _unreadManage = True
1486         _ids = New List(Of Long)
1487         _oldestUnreadItem = -1
1488         _tabType = TabUsageType.Undefined
1489         _listInfo = Nothing
1490     End Sub
1491
1492     Public Sub New(ByVal TabName As String, ByVal TabType As TabUsageType, ByVal list As ListElement)
1493         _tabName = TabName
1494         _filters = New List(Of FiltersClass)
1495         _notify = True
1496         _soundFile = ""
1497         _unreadManage = True
1498         _ids = New List(Of Long)
1499         _oldestUnreadItem = -1
1500         _tabType = TabType
1501         Me.ListInfo = list
1502         If TabType = TabUsageType.PublicSearch OrElse TabType = TabUsageType.DirectMessage OrElse TabType = TabUsageType.Lists Then
1503             _sorter.posts = _posts
1504         Else
1505             _sorter.posts = TabInformations.GetInstance.Posts
1506         End If
1507     End Sub
1508
1509     Public Sub Sort()
1510         _ids.Sort(_sorter.CmpMethod)
1511     End Sub
1512
1513     Public ReadOnly Property Sorter() As IdComparerClass
1514         Get
1515             Return _sorter
1516         End Get
1517     End Property
1518
1519     '無条件に追加
1520     Private Sub Add(ByVal ID As Long, ByVal Read As Boolean)
1521         If Me._ids.Contains(ID) Then Exit Sub
1522
1523         Me._ids.Add(ID)
1524
1525         If Not Read AndAlso Me._unreadManage Then
1526             Me._unreadCount += 1
1527             If Me._oldestUnreadItem = -1 Then
1528                 Me._oldestUnreadItem = ID
1529             Else
1530                 If ID < Me._oldestUnreadItem Then Me._oldestUnreadItem = ID
1531             End If
1532         End If
1533     End Sub
1534
1535     Public Sub Add(ByVal ID As Long, ByVal Read As Boolean, ByVal Temporary As Boolean)
1536         If Not Temporary Then
1537             Me.Add(ID, Read)
1538         Else
1539             _tmpIds.Add(New TemporaryId(ID, Read))
1540         End If
1541     End Sub
1542
1543     'フィルタに合致したら追加
1544     Public Function AddFiltered(ByVal post As PostClass) As HITRESULT
1545         'Try
1546         '    rwLock.AcquireReaderLock(System.Threading.Timeout.Infinite) '読み取りロック取得
1547         If Me.TabType = TabUsageType.PublicSearch OrElse Me.TabType = TabUsageType.DirectMessage OrElse Me.TabType = TabUsageType.Lists Then Return HITRESULT.None
1548
1549         Dim rslt As HITRESULT = HITRESULT.None
1550         '全フィルタ評価(優先順位あり)
1551         For Each ft As FiltersClass In _filters
1552             Select Case ft.IsHit(post)   'フィルタクラスでヒット判定
1553                 Case HITRESULT.None
1554                 Case HITRESULT.Copy
1555                     If rslt <> HITRESULT.CopyAndMark Then rslt = HITRESULT.Copy
1556                 Case HITRESULT.CopyAndMark
1557                     rslt = HITRESULT.CopyAndMark
1558                 Case HITRESULT.Move
1559                     rslt = HITRESULT.Move
1560                 Case HITRESULT.Exclude
1561                     rslt = HITRESULT.None
1562                     Exit For
1563             End Select
1564         Next
1565
1566         If rslt <> HITRESULT.None Then
1567             _tmpIds.Add(New TemporaryId(post.Id, post.IsRead))
1568         End If
1569         'Me.Add(ID, Read)
1570
1571         Return rslt 'マーク付けは呼び出し元で行うこと
1572
1573         'Finally
1574         '    rwLock.ReleaseReaderLock()
1575         'End Try
1576     End Function
1577
1578     '検索結果の追加
1579     Public Sub AddPostToInnerStorage(ByVal Post As PostClass)
1580         If _posts.ContainsKey(Post.Id) Then Exit Sub
1581         _posts.Add(Post.Id, Post)
1582         _tmpIds.Add(New TemporaryId(Post.Id, Post.IsRead))
1583     End Sub
1584
1585     Public Sub AddSubmit()
1586         If _tmpIds.Count = 0 Then Exit Sub
1587         For Each tId As TemporaryId In _tmpIds
1588             Me.Add(tId.Id, tId.Read)
1589         Next
1590         _tmpIds.Clear()
1591     End Sub
1592
1593     Public Sub Remove(ByVal Id As Long)
1594         If Not Me._ids.Contains(Id) Then Exit Sub
1595         Me._ids.Remove(Id)
1596         If Me.TabType = TabUsageType.PublicSearch OrElse Me.TabType = TabUsageType.DirectMessage OrElse Me.TabType = TabUsageType.Lists Then _posts.Remove(Id)
1597     End Sub
1598
1599     Public Sub Remove(ByVal Id As Long, ByVal Read As Boolean)
1600         If Not Me._ids.Contains(Id) Then Exit Sub
1601
1602         If Not Read AndAlso Me._unreadManage Then
1603             Me._unreadCount -= 1
1604             Me._oldestUnreadItem = -1
1605         End If
1606
1607         Me._ids.Remove(Id)
1608         If Me.TabType = TabUsageType.PublicSearch OrElse Me.TabType = TabUsageType.DirectMessage OrElse Me.TabType = TabUsageType.Lists Then _posts.Remove(Id)
1609     End Sub
1610
1611     Public Property UnreadManage() As Boolean
1612         Get
1613             Return _unreadManage
1614         End Get
1615         Set(ByVal value As Boolean)
1616             Me._unreadManage = value
1617             If Not value Then
1618                 Me._oldestUnreadItem = -1
1619                 Me._unreadCount = 0
1620             End If
1621         End Set
1622     End Property
1623
1624     Public Property Notify() As Boolean
1625         Get
1626             Return _notify
1627         End Get
1628         Set(ByVal value As Boolean)
1629             _notify = value
1630         End Set
1631     End Property
1632
1633     Public Property SoundFile() As String
1634         Get
1635             Return _soundFile
1636         End Get
1637         Set(ByVal value As String)
1638             _soundFile = value
1639         End Set
1640     End Property
1641
1642     <Xml.Serialization.XmlIgnore()> _
1643     Public Property OldestUnreadId() As Long
1644         Get
1645             Return _oldestUnreadItem
1646         End Get
1647         Set(ByVal value As Long)
1648             _oldestUnreadItem = value
1649         End Set
1650     End Property
1651
1652     <Xml.Serialization.XmlIgnore()> _
1653     Public Property UnreadCount() As Integer
1654         Get
1655             Return _unreadCount
1656         End Get
1657         Set(ByVal value As Integer)
1658             _unreadCount = value
1659         End Set
1660     End Property
1661
1662     Public ReadOnly Property AllCount() As Integer
1663         Get
1664             Return Me._ids.Count
1665         End Get
1666     End Property
1667
1668     Public Function GetFilters() As FiltersClass()
1669         Return _filters.ToArray()
1670     End Function
1671
1672     Public Sub RemoveFilter(ByVal filter As FiltersClass)
1673         _filters.Remove(filter)
1674         _filterMod = True
1675     End Sub
1676
1677     Public Function AddFilter(ByVal filter As FiltersClass) As Boolean
1678         If _filters.Contains(filter) Then Return False
1679         _filters.Add(filter)
1680         _filterMod = True
1681         Return True
1682     End Function
1683
1684     Public Sub EditFilter(ByVal original As FiltersClass, ByVal modified As FiltersClass)
1685         original.BodyFilter = modified.BodyFilter
1686         original.NameFilter = modified.NameFilter
1687         original.SearchBoth = modified.SearchBoth
1688         original.SearchUrl = modified.SearchUrl
1689         original.UseRegex = modified.UseRegex
1690         original.CaseSensitive = modified.CaseSensitive
1691         original.IsRt = modified.IsRt
1692         original.Source = modified.Source
1693         original.ExBodyFilter = modified.ExBodyFilter
1694         original.ExNameFilter = modified.ExNameFilter
1695         original.ExSearchBoth = modified.ExSearchBoth
1696         original.ExSearchUrl = modified.ExSearchUrl
1697         original.ExUseRegex = modified.ExUseRegex
1698         original.ExCaseSensitive = modified.ExCaseSensitive
1699         original.IsExRt = modified.IsExRt
1700         original.ExSource = modified.ExSource
1701         original.MoveFrom = modified.MoveFrom
1702         original.SetMark = modified.SetMark
1703         _filterMod = True
1704     End Sub
1705
1706     <Xml.Serialization.XmlIgnore()> _
1707     Public Property Filters() As List(Of FiltersClass)
1708         Get
1709             Return _filters
1710         End Get
1711         Set(ByVal value As List(Of FiltersClass))
1712             _filters = value
1713         End Set
1714     End Property
1715
1716     Public Property FilterArray() As FiltersClass()
1717         Get
1718             Return _filters.ToArray
1719         End Get
1720         Set(ByVal value As FiltersClass())
1721             For Each filters As FiltersClass In value
1722                 _filters.Add(filters)
1723             Next
1724         End Set
1725     End Property
1726
1727     Public Function Contains(ByVal ID As Long) As Boolean
1728         Return _ids.Contains(ID)
1729     End Function
1730
1731     Public Sub ClearIDs()
1732         _ids.Clear()
1733         _tmpIds.Clear()
1734         _unreadCount = 0
1735         _oldestUnreadItem = -1
1736         If _posts IsNot Nothing Then
1737             _posts.Clear()
1738         End If
1739     End Sub
1740
1741     Public Function GetId(ByVal Index As Integer) As Long
1742         Return _ids(Index)
1743     End Function
1744
1745     Public Function IndexOf(ByVal ID As Long) As Integer
1746         Return _ids.IndexOf(ID)
1747     End Function
1748
1749     <Xml.Serialization.XmlIgnore()> _
1750     Public Property FilterModified() As Boolean
1751         Get
1752             Return _filterMod
1753         End Get
1754         Set(ByVal value As Boolean)
1755             _filterMod = value
1756         End Set
1757     End Property
1758
1759     Public Function BackupIds() As Long()
1760         Return _ids.ToArray()
1761     End Function
1762
1763     Public Property TabName() As String
1764         Get
1765             Return _tabName
1766         End Get
1767         Set(ByVal value As String)
1768             _tabName = value
1769         End Set
1770     End Property
1771
1772     Public Property TabType() As TabUsageType
1773         Get
1774             Return _tabType
1775         End Get
1776         Set(ByVal value As TabUsageType)
1777             _tabType = value
1778             If _tabType = TabUsageType.PublicSearch OrElse _tabType = TabUsageType.DirectMessage OrElse _tabType = TabUsageType.Lists Then
1779                 _sorter.posts = _posts
1780             Else
1781                 _sorter.posts = TabInformations.GetInstance.Posts
1782             End If
1783         End Set
1784     End Property
1785
1786 End Class
1787
1788 <Serializable()> _
1789 Public NotInheritable Class FiltersClass
1790     Implements System.IEquatable(Of FiltersClass)
1791     Private _name As String = ""
1792     Private _body As New List(Of String)
1793     Private _searchBoth As Boolean = True
1794     Private _searchUrl As Boolean = False
1795     Private _caseSensitive As Boolean = False
1796     Private _useRegex As Boolean = False
1797     Private _isRt As Boolean = False
1798     Private _source As String = ""
1799     Private _exname As String = ""
1800     Private _exbody As New List(Of String)
1801     Private _exsearchBoth As Boolean = True
1802     Private _exsearchUrl As Boolean = False
1803     Private _exuseRegex As Boolean = False
1804     Private _excaseSensitive As Boolean = False
1805     Private _isExRt As Boolean = False
1806     Private _exSource As String = ""
1807     Private _moveFrom As Boolean = False
1808     Private _setMark As Boolean = True
1809
1810     'Public Sub New(ByVal NameFilter As String, _
1811     '    ByVal BodyFilter As List(Of String), _
1812     '    ByVal SearchBoth As Boolean, _
1813     '    ByVal SearchUrl As Boolean, _
1814     '    ByVal CaseSensitive As Boolean, _
1815     '    ByVal UseRegex As Boolean, _
1816     '    ByVal ParentTab As String, _
1817     '    ByVal ExNameFilter As String, _
1818     '    ByVal ExBodyFilter As List(Of String), _
1819     '    ByVal ExSearchBoth As Boolean, _
1820     '    ByVal ExSearchUrl As Boolean, _
1821     '    ByVal ExUseRegex As Boolean, _
1822     '    ByVal ExCaseSensitive As Boolean, _
1823     '    ByVal MoveFrom As Boolean, _
1824     '    ByVal SetMark As Boolean)
1825     '    _name = NameFilter
1826     '    _body = BodyFilter
1827     '    _searchBoth = SearchBoth
1828     '    _searchUrl = SearchUrl
1829     '    _caseSensitive = CaseSensitive
1830     '    _useRegex = UseRegex
1831     '    _exname = ExNameFilter
1832     '    _exbody = ExBodyFilter
1833     '    _exsearchBoth = ExSearchBoth
1834     '    _exsearchUrl = ExSearchUrl
1835     '    _exuseRegex = ExUseRegex
1836     '    _excaseSensitive = ExCaseSensitive
1837     '    _moveFrom = MoveFrom
1838     '    _setMark = SetMark
1839     '    '正規表現検証
1840     '    If _useRegex Then
1841     '        Try
1842     '            Dim rgx As New Regex(_name)
1843     '        Catch ex As Exception
1844     '            Throw New Exception(My.Resources.ButtonOK_ClickText3 + ex.Message)
1845     '            Exit Sub
1846     '        End Try
1847     '        For Each bs As String In _body
1848     '            Try
1849     '                Dim rgx As New Regex(bs)
1850     '            Catch ex As Exception
1851     '                Throw New Exception(My.Resources.ButtonOK_ClickText3 + ex.Message)
1852     '                Exit Sub
1853     '            End Try
1854     '        Next
1855     '    End If
1856     '    If _exuseRegex Then
1857     '        Try
1858     '            Dim rgx As New Regex(_exname)
1859     '        Catch ex As Exception
1860     '            Throw New Exception(My.Resources.ButtonOK_ClickText3 + ex.Message)
1861     '            Exit Sub
1862     '        End Try
1863     '        For Each bs As String In _exbody
1864     '            Try
1865     '                Dim rgx As New Regex(bs)
1866     '            Catch ex As Exception
1867     '                Throw New Exception(My.Resources.ButtonOK_ClickText3 + ex.Message)
1868     '                Exit Sub
1869     '            End Try
1870     '        Next
1871     '    End If
1872     'End Sub
1873
1874     Public Sub New()
1875
1876     End Sub
1877
1878     'フィルタ一覧に表示する文言生成
1879     Private Function MakeSummary() As String
1880         Dim fs As New System.Text.StringBuilder()
1881         If _name <> "" OrElse _body.Count > 0 OrElse _isRt OrElse _source <> "" Then
1882             If _searchBoth Then
1883                 If _name <> "" Then
1884                     fs.AppendFormat(My.Resources.SetFiltersText1, _name)
1885                 Else
1886                     fs.Append(My.Resources.SetFiltersText2)
1887                 End If
1888             End If
1889             If _body.Count > 0 Then
1890                 fs.Append(My.Resources.SetFiltersText3)
1891                 For Each bf As String In _body
1892                     fs.Append(bf)
1893                     fs.Append(" ")
1894                 Next
1895                 fs.Length -= 1
1896                 fs.Append(My.Resources.SetFiltersText4)
1897             End If
1898             fs.Append("(")
1899             If _searchBoth Then
1900                 fs.Append(My.Resources.SetFiltersText5)
1901             Else
1902                 fs.Append(My.Resources.SetFiltersText6)
1903             End If
1904             If _useRegex Then
1905                 fs.Append(My.Resources.SetFiltersText7)
1906             End If
1907             If _searchUrl Then
1908                 fs.Append(My.Resources.SetFiltersText8)
1909             End If
1910             If _caseSensitive Then
1911                 fs.Append(My.Resources.SetFiltersText13)
1912             End If
1913             If _isRt Then
1914                 fs.Append("RT/")
1915             End If
1916             If _source <> "" Then
1917                 fs.AppendFormat("Src…{0}/", _source)
1918             End If
1919             'If _moveFrom Then
1920             '    fs.Append(My.Resources.SetFiltersText9)
1921             'ElseIf _setMark Then
1922             '    fs.Append(My.Resources.SetFiltersText10)
1923             'Else
1924             '    fs.Append(My.Resources.SetFiltersText11)
1925             'End If
1926             fs.Length -= 1
1927             fs.Append(")")
1928         End If
1929         If _exname <> "" OrElse _exbody.Count > 0 OrElse _isExRt OrElse _exSource <> "" Then
1930             '除外
1931             fs.Append(My.Resources.SetFiltersText12)
1932             If _exsearchBoth Then
1933                 If _exname <> "" Then
1934                     fs.AppendFormat(My.Resources.SetFiltersText1, _exname)
1935                 Else
1936                     fs.Append(My.Resources.SetFiltersText2)
1937                 End If
1938             End If
1939             If _exbody.Count > 0 Then
1940                 fs.Append(My.Resources.SetFiltersText3)
1941                 For Each bf As String In _exbody
1942                     fs.Append(bf)
1943                     fs.Append(" ")
1944                 Next
1945                 fs.Length -= 1
1946                 fs.Append(My.Resources.SetFiltersText4)
1947             End If
1948             fs.Append("(")
1949             If _exsearchBoth Then
1950                 fs.Append(My.Resources.SetFiltersText5)
1951             Else
1952                 fs.Append(My.Resources.SetFiltersText6)
1953             End If
1954             If _exuseRegex Then
1955                 fs.Append(My.Resources.SetFiltersText7)
1956             End If
1957             If _exsearchUrl Then
1958                 fs.Append(My.Resources.SetFiltersText8)
1959             End If
1960             If _excaseSensitive Then
1961                 fs.Append(My.Resources.SetFiltersText13)
1962             End If
1963             If _isExRt Then
1964                 fs.Append("RT/")
1965             End If
1966             If _exSource <> "" Then
1967                 fs.AppendFormat("Src…{0}/", _exSource)
1968             End If
1969             fs.Length -= 1
1970             fs.Append(")")
1971         End If
1972
1973         fs.Append("(")
1974         If _moveFrom Then
1975             fs.Append(My.Resources.SetFiltersText9)
1976         Else
1977             fs.Append(My.Resources.SetFiltersText11)
1978         End If
1979         If Not _moveFrom AndAlso _setMark Then
1980             fs.Append(My.Resources.SetFiltersText10)
1981         ElseIf Not _moveFrom Then
1982             fs.Length -= 1
1983         End If
1984
1985         fs.Append(")")
1986
1987         Return fs.ToString()
1988     End Function
1989
1990     Public Property NameFilter() As String
1991         Get
1992             Return _name
1993         End Get
1994         Set(ByVal value As String)
1995             _name = value
1996         End Set
1997     End Property
1998
1999     Public Property ExNameFilter() As String
2000         Get
2001             Return _exname
2002         End Get
2003         Set(ByVal value As String)
2004             _exname = value
2005         End Set
2006     End Property
2007
2008     <Xml.Serialization.XmlIgnore()> _
2009     Public Property BodyFilter() As List(Of String)
2010         Get
2011             Return _body
2012         End Get
2013         Set(ByVal value As List(Of String))
2014             _body = value
2015         End Set
2016     End Property
2017
2018     Public Property BodyFilterArray() As String()
2019         Get
2020             Return _body.ToArray
2021         End Get
2022         Set(ByVal value As String())
2023             _body = New List(Of String)
2024             For Each filter As String In value
2025                 _body.Add(filter)
2026             Next
2027         End Set
2028     End Property
2029
2030     <Xml.Serialization.XmlIgnore()> _
2031     Public Property ExBodyFilter() As List(Of String)
2032         Get
2033             Return _exbody
2034         End Get
2035         Set(ByVal value As List(Of String))
2036             _exbody = value
2037         End Set
2038     End Property
2039
2040     Public Property ExBodyFilterArray() As String()
2041         Get
2042             Return _exbody.ToArray
2043         End Get
2044         Set(ByVal value As String())
2045             _exbody = New List(Of String)
2046             For Each filter As String In value
2047                 _exbody.Add(filter)
2048             Next
2049         End Set
2050     End Property
2051
2052     Public Property SearchBoth() As Boolean
2053         Get
2054             Return _searchBoth
2055         End Get
2056         Set(ByVal value As Boolean)
2057             _searchBoth = value
2058         End Set
2059     End Property
2060
2061     Public Property ExSearchBoth() As Boolean
2062         Get
2063             Return _exsearchBoth
2064         End Get
2065         Set(ByVal value As Boolean)
2066             _exsearchBoth = value
2067         End Set
2068     End Property
2069
2070     Public Property MoveFrom() As Boolean
2071         Get
2072             Return _moveFrom
2073         End Get
2074         Set(ByVal value As Boolean)
2075             _moveFrom = value
2076         End Set
2077     End Property
2078
2079     Public Property SetMark() As Boolean
2080         Get
2081             Return _setMark
2082         End Get
2083         Set(ByVal value As Boolean)
2084             _setMark = value
2085         End Set
2086     End Property
2087
2088     Public Property SearchUrl() As Boolean
2089         Get
2090             Return _searchUrl
2091         End Get
2092         Set(ByVal value As Boolean)
2093             _searchUrl = value
2094         End Set
2095     End Property
2096
2097     Public Property ExSearchUrl() As Boolean
2098         Get
2099             Return _exsearchUrl
2100         End Get
2101         Set(ByVal value As Boolean)
2102             _exsearchUrl = value
2103         End Set
2104     End Property
2105
2106     Public Property CaseSensitive() As Boolean
2107         Get
2108             Return _caseSensitive
2109         End Get
2110         Set(ByVal value As Boolean)
2111             _caseSensitive = value
2112         End Set
2113     End Property
2114
2115     Public Property ExCaseSensitive() As Boolean
2116         Get
2117             Return _excaseSensitive
2118         End Get
2119         Set(ByVal value As Boolean)
2120             _excaseSensitive = value
2121         End Set
2122     End Property
2123
2124     Public Property UseRegex() As Boolean
2125         Get
2126             Return _useRegex
2127         End Get
2128         Set(ByVal value As Boolean)
2129             _useRegex = value
2130         End Set
2131     End Property
2132
2133     Public Property ExUseRegex() As Boolean
2134         Get
2135             Return _exuseRegex
2136         End Get
2137         Set(ByVal value As Boolean)
2138             _exuseRegex = value
2139         End Set
2140     End Property
2141
2142     Public Property IsRt() As Boolean
2143         Get
2144             Return _isRt
2145         End Get
2146         Set(ByVal value As Boolean)
2147             _isRt = value
2148         End Set
2149     End Property
2150
2151     Public Property IsExRt() As Boolean
2152         Get
2153             Return _isExRt
2154         End Get
2155         Set(ByVal value As Boolean)
2156             _isExRt = value
2157         End Set
2158     End Property
2159
2160     Public Property Source() As String
2161         Get
2162             Return _source
2163         End Get
2164         Set(ByVal value As String)
2165             _source = value
2166         End Set
2167     End Property
2168
2169     Public Property ExSource() As String
2170         Get
2171             Return _exSource
2172         End Get
2173         Set(ByVal value As String)
2174             _exSource = value
2175         End Set
2176     End Property
2177
2178     Public Overrides Function ToString() As String
2179         Return MakeSummary()
2180     End Function
2181
2182     Public Function IsHit(ByVal post As PostClass) As HITRESULT
2183         Dim bHit As Boolean = True
2184         Dim tBody As String
2185         If _searchUrl Then
2186             tBody = post.OriginalData
2187         Else
2188             tBody = post.Data
2189         End If
2190         '検索オプション
2191         Dim compOpt As System.StringComparison
2192         Dim rgOpt As System.Text.RegularExpressions.RegexOptions
2193         If _caseSensitive Then
2194             compOpt = StringComparison.Ordinal
2195             rgOpt = RegexOptions.None
2196         Else
2197             compOpt = StringComparison.OrdinalIgnoreCase
2198             rgOpt = RegexOptions.IgnoreCase
2199         End If
2200         If _searchBoth Then
2201             If _name = "" OrElse _
2202                 post.Name.Equals(_name, compOpt) OrElse _
2203                 post.RetweetedBy.Equals(_name, compOpt) OrElse _
2204                 (_useRegex AndAlso (Regex.IsMatch(post.Name, _name, rgOpt) OrElse _
2205                                     Regex.IsMatch(post.RetweetedBy, _name, rgOpt))) Then
2206                 For Each fs As String In _body
2207                     If _useRegex Then
2208                         If Not Regex.IsMatch(tBody, fs, rgOpt) Then bHit = False
2209                     Else
2210                         If _caseSensitive Then
2211                             If Not tBody.Contains(fs) Then bHit = False
2212                         Else
2213                             If Not tBody.ToLower().Contains(fs.ToLower()) Then bHit = False
2214                         End If
2215                     End If
2216                     If Not bHit Then Exit For
2217                 Next
2218             Else
2219                 bHit = False
2220             End If
2221         Else
2222             For Each fs As String In _body
2223                 If _useRegex Then
2224                     If Not (Regex.IsMatch(post.Name, fs, rgOpt) OrElse _
2225                             Regex.IsMatch(post.RetweetedBy, fs, rgOpt) OrElse _
2226                             Regex.IsMatch(tBody, fs, rgOpt)) Then bHit = False
2227                 Else
2228                     If _caseSensitive Then
2229                         If Not (post.Name.Contains(fs) OrElse _
2230                                 post.RetweetedBy.Contains(fs) OrElse _
2231                                 tBody.Contains(fs)) Then bHit = False
2232                     Else
2233                         If Not (post.Name.ToLower().Contains(fs.ToLower()) OrElse _
2234                                 post.RetweetedBy.ToLower().Contains(fs.ToLower()) OrElse _
2235                                 tBody.ToLower().Contains(fs.ToLower())) Then bHit = False
2236                     End If
2237                 End If
2238                 If Not bHit Then Exit For
2239             Next
2240         End If
2241         If _isRt Then
2242             If post.RetweetedId = 0 Then bHit = False
2243         End If
2244         If Not String.IsNullOrEmpty(_source) Then
2245             If _useRegex Then
2246                 If Not Regex.IsMatch(post.Source, _source, rgOpt) Then bHit = False
2247             Else
2248                 If Not post.Source.Equals(_source, compOpt) Then bHit = False
2249             End If
2250         End If
2251         If bHit Then
2252             '除外判定
2253             If _exsearchUrl Then
2254                 tBody = post.OriginalData
2255             Else
2256                 tBody = post.Data
2257             End If
2258
2259             Dim exFlag As Boolean = False
2260             'If _name = "" AndAlso _body.Count = 0 Then
2261             '    exFlag = True
2262             '    'bHit = False
2263             'End If
2264             If _exname <> "" OrElse _exbody.Count > 0 Then
2265                 If _excaseSensitive Then
2266                     compOpt = StringComparison.Ordinal
2267                     rgOpt = RegexOptions.None
2268                 Else
2269                     compOpt = StringComparison.OrdinalIgnoreCase
2270                     rgOpt = RegexOptions.IgnoreCase
2271                 End If
2272                 If _exsearchBoth Then
2273                     If _exname = "" OrElse _
2274                         post.Name.Equals(_exname, compOpt) OrElse _
2275                         post.RetweetedBy.Equals(_exname, compOpt) OrElse _
2276                         (_exuseRegex AndAlso _
2277                             (Regex.IsMatch(post.Name, _exname, rgOpt) OrElse _
2278                              Regex.IsMatch(post.RetweetedBy, _exname, rgOpt))) Then
2279                         If _exbody.Count > 0 Then
2280                             For Each fs As String In _exbody
2281                                 If _exuseRegex Then
2282                                     If Regex.IsMatch(tBody, fs, rgOpt) Then exFlag = True
2283                                 Else
2284                                     If _excaseSensitive Then
2285                                         If tBody.Contains(fs) Then exFlag = True
2286                                     Else
2287                                         If tBody.ToLower().Contains(fs.ToLower()) Then exFlag = True
2288                                     End If
2289                                 End If
2290                                 If exFlag Then Exit For
2291                             Next
2292                         Else
2293                             exFlag = True
2294                         End If
2295                     End If
2296                 Else
2297                     For Each fs As String In _exbody
2298                         If _exuseRegex Then
2299                             If Regex.IsMatch(post.Name, fs, rgOpt) OrElse _
2300                                Regex.IsMatch(post.RetweetedBy, fs, rgOpt) OrElse _
2301                                Regex.IsMatch(tBody, fs, rgOpt) Then exFlag = True
2302                         Else
2303                             If _excaseSensitive Then
2304                                 If post.Name.Contains(fs) OrElse _
2305                                    post.RetweetedBy.Contains(fs) OrElse _
2306                                    tBody.Contains(fs) Then exFlag = True
2307                             Else
2308                                 If post.Name.ToLower().Contains(fs.ToLower()) OrElse _
2309                                    post.RetweetedBy.ToLower().Contains(fs.ToLower()) OrElse _
2310                                    tBody.ToLower().Contains(fs.ToLower()) Then exFlag = True
2311                             End If
2312                         End If
2313                         If exFlag Then Exit For
2314                     Next
2315                 End If
2316             End If
2317             If _isExRt Then
2318                 If post.RetweetedId > 0 Then exFlag = True
2319             End If
2320             If Not String.IsNullOrEmpty(_exSource) Then
2321                 If _exuseRegex Then
2322                     If Regex.IsMatch(post.Source, _exSource, rgOpt) Then exFlag = True
2323                 Else
2324                     If post.Source.Equals(_exSource, compOpt) Then exFlag = True
2325                 End If
2326             End If
2327
2328             If _name = "" AndAlso _body.Count = 0 AndAlso Not _isRt AndAlso _source = "" Then
2329                 bHit = False
2330             End If
2331             If bHit Then
2332                 If Not exFlag Then
2333                     'If _setMark Then Return HITRESULT.CopyAndMark
2334                     If _moveFrom Then
2335                         Return HITRESULT.Move
2336                     Else
2337                         If _setMark Then
2338                             Return HITRESULT.CopyAndMark
2339                         End If
2340                         Return HITRESULT.Copy
2341                     End If
2342                     'Return HITRESULT.Copy
2343                 Else
2344                     Return HITRESULT.Exclude
2345                 End If
2346             Else
2347                 If exFlag Then
2348                     Return HITRESULT.Exclude
2349                 Else
2350                     Return HITRESULT.None
2351                 End If
2352             End If
2353         Else
2354             Return HITRESULT.None
2355         End If
2356     End Function
2357
2358     Public Overloads Function Equals(ByVal other As FiltersClass) As Boolean _
2359      Implements System.IEquatable(Of Tween.FiltersClass).Equals
2360
2361         If Me.BodyFilter.Count <> other.BodyFilter.Count Then Return False
2362         If Me.ExBodyFilter.Count <> other.ExBodyFilter.Count Then Return False
2363         For i As Integer = 0 To Me.BodyFilter.Count - 1
2364             If Me.BodyFilter(i) <> other.BodyFilter(i) Then Return False
2365         Next
2366         For i As Integer = 0 To Me.ExBodyFilter.Count - 1
2367             If Me.ExBodyFilter(i) <> other.ExBodyFilter(i) Then Return False
2368         Next
2369
2370         Return (Me.MoveFrom = other.MoveFrom) And _
2371                (Me.SetMark = other.SetMark) And _
2372                (Me.NameFilter = other.NameFilter) And _
2373                (Me.SearchBoth = other.SearchBoth) And _
2374                (Me.SearchUrl = other.SearchUrl) And _
2375                (Me.UseRegex = other.UseRegex) And _
2376                (Me.ExNameFilter = other.ExNameFilter) And _
2377                (Me.ExSearchBoth = other.ExSearchBoth) And _
2378                (Me.ExSearchUrl = other.ExSearchUrl) And _
2379                (Me.ExUseRegex = other.ExUseRegex) And _
2380                (Me.IsRt = other.IsRt) And _
2381                (Me.Source = other.Source) And _
2382                (Me.IsExRt = other.IsExRt) And _
2383                (Me.ExSource = other.ExSource)
2384     End Function
2385
2386     Public Overrides Function Equals(ByVal obj As Object) As Boolean
2387         If (obj Is Nothing) OrElse Not (Me.GetType() Is obj.GetType()) Then Return False
2388         Return Me.Equals(CType(obj, FiltersClass))
2389     End Function
2390
2391     Public Overrides Function GetHashCode() As Integer
2392         Return Me.MoveFrom.GetHashCode Xor _
2393                Me.SetMark.GetHashCode Xor _
2394                Me.BodyFilter.GetHashCode Xor _
2395                Me.NameFilter.GetHashCode Xor _
2396                Me.SearchBoth.GetHashCode Xor _
2397                Me.SearchUrl.GetHashCode Xor _
2398                Me.UseRegex.GetHashCode Xor _
2399                Me.ExBodyFilter.GetHashCode Xor _
2400                Me.ExNameFilter.GetHashCode Xor _
2401                Me.ExSearchBoth.GetHashCode Xor _
2402                Me.ExSearchUrl.GetHashCode Xor _
2403                Me.ExUseRegex.GetHashCode Xor _
2404                Me.IsRt.GetHashCode Xor _
2405                Me.Source.GetHashCode Xor _
2406                Me.IsExRt.GetHashCode Xor _
2407                Me.ExSource.GetHashCode
2408     End Function
2409 End Class
2410
2411 'ソート比較クラス:ID比較のみ
2412 Public NotInheritable Class IdComparerClass
2413     Implements IComparer(Of Long)
2414
2415     ''' <summary>
2416     ''' 比較する方法
2417     ''' </summary>
2418     Public Enum ComparerMode
2419         Id
2420         Data
2421         Name
2422         Nickname
2423         Source
2424     End Enum
2425
2426     Private _order As SortOrder
2427     Private _mode As ComparerMode
2428     Private _statuses As Dictionary(Of Long, PostClass)
2429     Private _CmpMethod As Comparison(Of Long)
2430
2431     ''' <summary>
2432     ''' 昇順か降順か Setの際は同時に比較関数の切り替えを行う
2433     ''' </summary>
2434     Public Property Order() As SortOrder
2435         Get
2436             Return _order
2437         End Get
2438         Set(ByVal Value As SortOrder)
2439             _order = Value
2440             SetCmpMethod(_mode, _order)
2441         End Set
2442     End Property
2443
2444     ''' <summary>
2445     ''' 並び替えの方法 Setの際は同時に比較関数の切り替えを行う
2446     ''' </summary>
2447     Public Property Mode() As ComparerMode
2448         Get
2449             Return _mode
2450         End Get
2451         Set(ByVal Value As ComparerMode)
2452             _mode = Value
2453             SetCmpMethod(_mode, _order)
2454         End Set
2455     End Property
2456
2457     ''' <summary>
2458     ''' ListViewItemComparerクラスのコンストラクタ(引数付は未使用)
2459     ''' </summary>
2460     ''' <param name="col">並び替える列番号</param>
2461     ''' <param name="ord">昇順か降順か</param>
2462     ''' <param name="cmod">並び替えの方法</param>
2463     'Public Sub New(ByVal ord As SortOrder, ByVal SortMode As ComparerMode)
2464     '    _order = ord
2465     '    _mode = SortMode
2466     '    SetCmpMethod(_mode, _order)
2467     'End Sub
2468
2469     'Public Sub New(ByVal posts As Dictionary(Of Long, PostClass))
2470     '    _order = SortOrder.Ascending
2471     '    _mode = ComparerMode.Id
2472     '    _statuses = posts
2473     '    SetCmpMethod(_mode, _order)
2474     'End Sub
2475
2476     Public Sub New()
2477         _order = SortOrder.Ascending
2478         _mode = ComparerMode.Id
2479         SetCmpMethod(_mode, _order)
2480     End Sub
2481
2482     Public WriteOnly Property posts() As Dictionary(Of Long, PostClass)
2483         Set(ByVal value As Dictionary(Of Long, PostClass))
2484             _statuses = value
2485         End Set
2486     End Property
2487
2488     ' 指定したソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2489     Public Overloads ReadOnly Property CmpMethod(ByVal _sortmode As ComparerMode, ByVal _sortorder As SortOrder) As Comparison(Of Long)
2490         Get
2491             Dim _method As Comparison(Of Long) = Nothing
2492             If _sortorder = SortOrder.Ascending Then
2493                 ' 昇順
2494                 Select Case _sortmode
2495                     Case ComparerMode.Data
2496                         _method = AddressOf Compare_ModeData_Ascending
2497                     Case ComparerMode.Id
2498                         _method = AddressOf Compare_ModeId_Ascending
2499                     Case ComparerMode.Name
2500                         _method = AddressOf Compare_ModeName_Ascending
2501                     Case ComparerMode.Nickname
2502                         _method = AddressOf Compare_ModeNickName_Ascending
2503                     Case ComparerMode.Source
2504                         _method = AddressOf Compare_ModeSource_Ascending
2505                 End Select
2506             Else
2507                 ' 降順
2508                 Select Case _sortmode
2509                     Case ComparerMode.Data
2510                         _method = AddressOf Compare_ModeData_Descending
2511                     Case ComparerMode.Id
2512                         _method = AddressOf Compare_ModeId_Descending
2513                     Case ComparerMode.Name
2514                         _method = AddressOf Compare_ModeName_Descending
2515                     Case ComparerMode.Nickname
2516                         _method = AddressOf Compare_ModeNickName_Descending
2517                     Case ComparerMode.Source
2518                         _method = AddressOf Compare_ModeSource_Descending
2519                 End Select
2520             End If
2521             Return _method
2522         End Get
2523     End Property
2524
2525     ' ソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2526     ' (overload 現在の使用中の比較関数のアドレスを返す)
2527     Public Overloads ReadOnly Property CmpMethod() As Comparison(Of Long)
2528         Get
2529             Return _CmpMethod
2530         End Get
2531     End Property
2532
2533     ' ソートモードとソートオーダーに従い比較関数のアドレスを切り替え
2534     Private Sub SetCmpMethod(ByVal mode As ComparerMode, ByVal order As SortOrder)
2535         _CmpMethod = Me.CmpMethod(mode, order)
2536     End Sub
2537
2538     'xがyより小さいときはマイナスの数、大きいときはプラスの数、
2539     '同じときは0を返す (こちらは未使用 一応比較関数群呼び出しの形のまま残しておく)
2540     Public Function Compare(ByVal x As Long, ByVal y As Long) _
2541             As Integer Implements IComparer(Of Long).Compare
2542         Return _CmpMethod(x, y)
2543     End Function
2544
2545     ' 比較用関数群 いずれもステータスIDの順序を考慮する
2546     ' 注:ID比較でCTypeを使用しているが、abs(x-y)がInteger(Int32)に収まらないことはあり得ないのでこれでよい
2547     ' 本文比較 昇順
2548     Public Function Compare_ModeData_Ascending(ByVal x As Long, ByVal y As Long) As Integer
2549         Dim result As Integer = String.Compare(_statuses.Item(x).Data, _statuses.Item(y).Data)
2550         If result = 0 Then result = x.CompareTo(y)
2551         Return result
2552     End Function
2553
2554     ' 本文比較 降順
2555     Public Function Compare_ModeData_Descending(ByVal x As Long, ByVal y As Long) As Integer
2556         Dim result As Integer = String.Compare(_statuses.Item(y).Data, _statuses.Item(x).Data)
2557         If result = 0 Then result = y.CompareTo(x)
2558         Return result
2559     End Function
2560
2561     ' ステータスID比較 昇順
2562     Public Function Compare_ModeId_Ascending(ByVal x As Long, ByVal y As Long) As Integer
2563         Return x.CompareTo(y)
2564     End Function
2565
2566     ' ステータスID比較 降順
2567     Public Function Compare_ModeId_Descending(ByVal x As Long, ByVal y As Long) As Integer
2568         Return y.CompareTo(x)
2569     End Function
2570
2571     ' 表示名比較 昇順
2572     Public Function Compare_ModeName_Ascending(ByVal x As Long, ByVal y As Long) As Integer
2573         Dim result As Integer = String.Compare(_statuses.Item(x).Name, _statuses.Item(y).Name)
2574         If result = 0 Then result = x.CompareTo(y)
2575         Return result
2576     End Function
2577
2578     ' 表示名比較 降順
2579     Public Function Compare_ModeName_Descending(ByVal x As Long, ByVal y As Long) As Integer
2580         Dim result As Integer = String.Compare(_statuses.Item(y).Name, _statuses.Item(x).Name)
2581         If result = 0 Then result = y.CompareTo(x)
2582         Return result
2583     End Function
2584
2585     ' ユーザー名比較 昇順
2586     Public Function Compare_ModeNickName_Ascending(ByVal x As Long, ByVal y As Long) As Integer
2587         Dim result As Integer = String.Compare(_statuses.Item(x).Nickname, _statuses.Item(y).Nickname)
2588         If result = 0 Then result = x.CompareTo(y)
2589         Return result
2590     End Function
2591
2592     ' ユーザー名比較 降順
2593     Public Function Compare_ModeNickName_Descending(ByVal x As Long, ByVal y As Long) As Integer
2594         Dim result As Integer = String.Compare(_statuses.Item(y).Nickname, _statuses.Item(x).Nickname)
2595         If result = 0 Then result = y.CompareTo(x)
2596         Return result
2597     End Function
2598
2599     ' Source比較 昇順
2600     Public Function Compare_ModeSource_Ascending(ByVal x As Long, ByVal y As Long) As Integer
2601         Dim result As Integer = String.Compare(_statuses.Item(x).Source, _statuses.Item(y).Source)
2602         If result = 0 Then result = x.CompareTo(y)
2603         Return result
2604     End Function
2605
2606     ' Source比較 降順
2607     Public Function Compare_ModeSource_Descending(ByVal x As Long, ByVal y As Long) As Integer
2608         Dim result As Integer = String.Compare(_statuses.Item(y).Source, _statuses.Item(x).Source)
2609         If result = 0 Then result = y.CompareTo(x)
2610         Return result
2611     End Function
2612 End Class