OSDN Git Service

0d8f97534fada9d648328250c64ed3968c002bf5
[opentween/open-tween.git] / OpenTween / StatusDictionary.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 //           (c) 2008-2011 Moz (@syo68k)
4 //           (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 //           (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 //           (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 //           (c) 2011      Egtra (@egtra) <http://dev.activebasic.com/egtra/>
8 //           (c) 2012      kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
9 // All rights reserved.
10 //
11 // This file is part of OpenTween.
12 //
13 // This program is free software; you can redistribute it and/or modify it
14 // under the terms of the GNU General Public License as published by the Free
15 // Software Foundation; either version 3 of the License, or (at your option)
16 // any later version.
17 //
18 // This program is distributed in the hope that it will be useful, but
19 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 // for more details.
22 //
23 // You should have received a copy of the GNU General Public License along
24 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
25 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
26 // Boston, MA 02110-1301, USA.
27
28 using System;
29 using System.Collections.Generic;
30 using System.Linq;
31 using System.Linq.Expressions;
32 using System.Reflection;
33 using System.Text;
34 using System.Text.RegularExpressions;
35 using System.Windows.Forms;
36 using System.Xml.Serialization;
37
38 namespace OpenTween
39 {
40     public class PostClass : ICloneable
41     {
42         public class StatusGeo
43         {
44             public double Lng { get; set; }
45             public double Lat { get; set; }
46
47             public override bool Equals(object obj)
48             {
49                 var geo = obj as StatusGeo;
50                 return geo != null && geo.Lng == this.Lng && geo.Lat == this.Lat;
51             }
52
53             public override int GetHashCode()
54             {
55                 return this.Lng.GetHashCode() ^ this.Lat.GetHashCode();
56             }
57         }
58         public string Nickname { get; set; }
59         public string TextFromApi { get; set; }
60         public string ImageUrl { get; set; }
61         public string ScreenName { get; set; }
62         public DateTime CreatedAt { get; set; }
63         public long StatusId { get; set; }
64         private bool _IsFav;
65         public string Text { get; set; }
66         public bool IsRead { get; set; }
67         public bool IsReply { get; set; }
68         public bool IsExcludeReply { get; set; }
69         private bool _IsProtect;
70         public bool IsOwl { get; set; }
71         private bool _IsMark;
72         public string InReplyToUser { get; set; }
73         private long? _InReplyToStatusId;
74         public string Source { get; set; }
75         public string SourceHtml { get; set; }
76         public List<string> ReplyToList { get; set; }
77         public bool IsMe { get; set; }
78         public bool IsDm { get; set; }
79         public long UserId { get; set; }
80         public bool FilterHit { get; set; }
81         public string RetweetedBy { get; set; }
82         public long? RetweetedId { get; set; }
83         private bool _IsDeleted = false;
84         private StatusGeo _postGeo = new StatusGeo();
85         public int RetweetedCount { get; set; }
86         public long? RetweetedByUserId { get; set; }
87         public long? InReplyToUserId { get; set; }
88         public Dictionary<string, List<string>> Media { get; set; }
89
90         public string RelTabName { get; set; }
91         public int FavoritedCount { get; set; }
92
93         private States _states = States.None;
94
95         [Flags]
96         private enum States
97         {
98             None = 0,
99             Protect = 1,
100             Mark = 2,
101             Reply = 4,
102             Geo = 8,
103         }
104
105         public PostClass(string Nickname,
106                 string textFromApi,
107                 string text,
108                 string ImageUrl,
109                 string screenName,
110                 DateTime createdAt,
111                 long statusId,
112                 bool IsFav,
113                 bool IsRead,
114                 bool IsReply,
115                 bool IsExcludeReply,
116                 bool IsProtect,
117                 bool IsOwl,
118                 bool IsMark,
119                 string InReplyToUser,
120                 long? InReplyToStatusId,
121                 string Source,
122                 string SourceHtml,
123                 List<string> ReplyToList,
124                 bool IsMe,
125                 bool IsDm,
126                 long userId,
127                 bool FilterHit,
128                 string RetweetedBy,
129                 long? RetweetedId,
130                 StatusGeo Geo)
131             : this()
132         {
133             this.Nickname = Nickname;
134             this.TextFromApi = textFromApi;
135             this.ImageUrl = ImageUrl;
136             this.ScreenName = screenName;
137             this.CreatedAt = createdAt;
138             this.StatusId = statusId;
139             _IsFav = IsFav;
140             this.Text = text;
141             this.IsRead = IsRead;
142             this.IsReply = IsReply;
143             this.IsExcludeReply = IsExcludeReply;
144             _IsProtect = IsProtect;
145             this.IsOwl = IsOwl;
146             _IsMark = IsMark;
147             this.InReplyToUser = InReplyToUser;
148             _InReplyToStatusId = InReplyToStatusId;
149             this.Source = Source;
150             this.SourceHtml = SourceHtml;
151             this.ReplyToList = ReplyToList;
152             this.IsMe = IsMe;
153             this.IsDm = IsDm;
154             this.UserId = userId;
155             this.FilterHit = FilterHit;
156             this.RetweetedBy = RetweetedBy;
157             this.RetweetedId = RetweetedId;
158             _postGeo = Geo;
159         }
160
161         public PostClass()
162         {
163             RetweetedBy = "";
164             RelTabName = "";
165             Media = new Dictionary<string, List<string>>();
166             ReplyToList = new List<string>();
167         }
168
169         public string TextSingleLine
170         {
171             get
172             {
173                 return this.TextFromApi == null ? null : this.TextFromApi.Replace("\n", " ");
174             }
175         }
176
177         public bool IsFav
178         {
179             get
180             {
181                 if (this.RetweetedId != null)
182                 {
183                     var post = this.GetRetweetSource(this.RetweetedId.Value);
184                     if (post != null)
185                     {
186                         return post.IsFav;
187                     }
188                 }
189
190                 return _IsFav;
191             }
192             set
193             {
194                 _IsFav = value;
195                 if (this.RetweetedId != null)
196                 {
197                     var post = this.GetRetweetSource(this.RetweetedId.Value);
198                     if (post != null)
199                     {
200                         post.IsFav = value;
201                     }
202                 }
203             }
204         }
205
206         public bool IsProtect
207         {
208             get
209             {
210                 return _IsProtect;
211             }
212             set
213             {
214                 if (value)
215                 {
216                     _states = _states | States.Protect;
217                 }
218                 else
219                 {
220                     _states = _states & ~States.Protect;
221                 }
222                 _IsProtect = value;
223             }
224         }
225         public bool IsMark
226         {
227             get
228             {
229                 return _IsMark;
230             }
231             set
232             {
233                 if (value)
234                 {
235                     _states = _states | States.Mark;
236                 }
237                 else
238                 {
239                     _states = _states & ~States.Mark;
240                 }
241                 _IsMark = value;
242             }
243         }
244         public long? InReplyToStatusId
245         {
246             get
247             {
248                 return _InReplyToStatusId;
249             }
250             set
251             {
252                 if (value != null)
253                 {
254                     _states = _states | States.Reply;
255                 }
256                 else
257                 {
258                     _states = _states & ~States.Reply;
259                 }
260                 _InReplyToStatusId = value;
261             }
262         }
263
264         public bool IsDeleted
265         {
266             get
267             {
268                 return _IsDeleted;
269             }
270             set
271             {
272                 if (value)
273                 {
274                     this.InReplyToStatusId = null;
275                     this.InReplyToUser = "";
276                     this.InReplyToUserId = null;
277                     this.IsReply = false;
278                     this.ReplyToList = new List<string>();
279                     this._states = States.None;
280                 }
281                 _IsDeleted = value;
282             }
283         }
284
285         public StatusGeo PostGeo
286         {
287             get
288             {
289                 return _postGeo;
290             }
291             set
292             {
293                 if (value != null && (value.Lat != 0 || value.Lng != 0))
294                 {
295                     _states |= States.Geo;
296                 }
297                 else
298                 {
299                     _states &= ~States.Geo;
300                 }
301                 _postGeo = value;
302             }
303         }
304
305         public int StateIndex
306         {
307             get
308             {
309                 return (int)_states - 1;
310             }
311         }
312
313         protected virtual PostClass GetRetweetSource(long statusId)
314         {
315             return TabInformations.GetInstance().RetweetSource(statusId);
316         }
317
318         [Obsolete("Use PostClass.Clone() instead.")]
319         public PostClass Copy()
320         {
321             return this.Clone();
322         }
323
324         public PostClass Clone()
325         {
326             return (PostClass)((ICloneable)this).Clone();
327         }
328
329         public override bool Equals(object obj)
330         {
331             if (obj == null || this.GetType() != obj.GetType()) return false;
332             return this.Equals((PostClass)obj);
333         }
334
335         public bool Equals(PostClass other)
336         {
337             if (other == null) return false;
338             return (this.Nickname == other.Nickname) &&
339                     (this.TextFromApi == other.TextFromApi) &&
340                     (this.ImageUrl == other.ImageUrl) &&
341                     (this.ScreenName == other.ScreenName) &&
342                     (this.CreatedAt == other.CreatedAt) &&
343                     (this.StatusId == other.StatusId) &&
344                     (this.IsFav == other.IsFav) &&
345                     (this.Text == other.Text) &&
346                     (this.IsRead == other.IsRead) &&
347                     (this.IsReply == other.IsReply) &&
348                     (this.IsExcludeReply == other.IsExcludeReply) &&
349                     (this.IsProtect == other.IsProtect) &&
350                     (this.IsOwl == other.IsOwl) &&
351                     (this.IsMark == other.IsMark) &&
352                     (this.InReplyToUser == other.InReplyToUser) &&
353                     (this.InReplyToStatusId == other.InReplyToStatusId) &&
354                     (this.Source == other.Source) &&
355                     (this.SourceHtml == other.SourceHtml) &&
356                     (this.ReplyToList.SequenceEqual(other.ReplyToList)) &&
357                     (this.IsMe == other.IsMe) &&
358                     (this.IsDm == other.IsDm) &&
359                     (this.UserId == other.UserId) &&
360                     (this.FilterHit == other.FilterHit) &&
361                     (this.RetweetedBy == other.RetweetedBy) &&
362                     (this.RetweetedId == other.RetweetedId) &&
363                     (this.RelTabName == other.RelTabName) &&
364                     (this.IsDeleted == other.IsDeleted) &&
365                     (this.InReplyToUserId == other.InReplyToUserId);
366
367         }
368
369         public override int GetHashCode()
370         {
371             return this.StatusId.GetHashCode();
372         }
373
374 #region "IClonable.Clone"
375         object ICloneable.Clone()
376         {
377             var clone = (PostClass)this.MemberwiseClone();
378             clone.ReplyToList = new List<string>(this.ReplyToList);
379             clone.PostGeo = new StatusGeo { Lng = this.PostGeo.Lng, Lat = this.PostGeo.Lat };
380             clone.Media = new Dictionary<string, List<string>>(this.Media);
381
382             return clone;
383         }
384 #endregion
385     }
386
387     public sealed class TabInformations
388     {
389         //個別タブの情報をDictionaryで保持
390         private IdComparerClass _sorter;
391         private Dictionary<string, TabClass> _tabs = new Dictionary<string, TabClass>();
392         private Dictionary<long, PostClass> _statuses = new Dictionary<long, PostClass>();
393         private List<long> _addedIds;
394         private List<long> _deletedIds = new List<long>();
395         private Dictionary<long, PostClass> _retweets = new Dictionary<long, PostClass>();
396         private Stack<TabClass> _removedTab = new Stack<TabClass>();
397         private List<ScrubGeoInfo> _scrubGeo = new List<ScrubGeoInfo>();
398
399         private class ScrubGeoInfo
400         {
401             public long UserId = 0;
402             public long UpToStatusId = 0;
403         }
404
405         public List<long> BlockIds = new List<long>();
406         public List<long> MuteUserIds = new List<long>();
407
408         //発言の追加
409         //AddPost(複数回) -> DistributePosts          -> SubmitUpdate
410
411         //トランザクション用
412         private int _addCount;
413         private string _soundFile;
414         private List<PostClass> _notifyPosts;
415         private readonly object LockObj = new object();
416         private readonly object LockUnread = new object();
417
418         private static TabInformations _instance = new TabInformations();
419
420         //List
421         private List<ListElement> _lists = new List<ListElement>();
422
423         private TabInformations()
424         {
425             _sorter = new IdComparerClass();
426         }
427
428         public static TabInformations GetInstance()
429         {
430             return _instance;    //singleton
431         }
432
433         public List<ListElement> SubscribableLists
434         {
435             get
436             {
437                 return _lists;
438             }
439             set
440             {
441                 if (value != null && value.Count > 0)
442                 {
443                     foreach (var tb in this.GetTabsByType(MyCommon.TabUsageType.Lists))
444                     {
445                         foreach (var list in value)
446                         {
447                             if (tb.ListInfo.Id == list.Id)
448                             {
449                                 tb.ListInfo = list;
450                                 break;
451                             }
452                         }
453                     }
454                 }
455                 _lists = value;
456             }
457         }
458
459         public bool AddTab(string TabName, MyCommon.TabUsageType TabType, ListElement List)
460         {
461             if (_tabs.ContainsKey(TabName)) return false;
462             var tb = new TabClass(TabName, TabType, List);
463             _tabs.Add(TabName, tb);
464             tb.Sorter.Mode = _sorter.Mode;
465             tb.Sorter.Order = _sorter.Order;
466             return true;
467         }
468
469         //public void AddTab(string TabName, TabClass Tab)
470         //{
471         //    _tabs.Add(TabName, Tab);
472         //}
473
474         public void RemoveTab(string TabName)
475         {
476             lock (LockObj)
477             {
478                 if (IsDefaultTab(TabName)) return; //念のため
479                 var tb = _tabs[TabName];
480                 if (!tb.IsInnerStorageTabType)
481                 {
482                     var homeTab = GetTabByType(MyCommon.TabUsageType.Home);
483                     var dmTab = GetTabByType(MyCommon.TabUsageType.DirectMessage);
484
485                     for (int idx = 0; idx < tb.AllCount; ++idx)
486                     {
487                         var exist = false;
488                         var Id = tb.GetId(idx);
489                         if (Id < 0) continue;
490                         foreach (var tab in _tabs.Values)
491                         {
492                             if (tab != tb && tab != dmTab)
493                             {
494                                 if (tab.Contains(Id))
495                                 {
496                                     exist = true;
497                                     break;
498                                 }
499                             }
500                         }
501                         if (!exist) homeTab.Add(Id, _statuses[Id].IsRead, false);
502                     }
503                 }
504                 _removedTab.Push(tb);
505                 _tabs.Remove(TabName);
506             }
507         }
508
509         public Stack<TabClass> RemovedTab
510         {
511             get { return _removedTab; }
512         }
513
514         public bool ContainsTab(string TabText)
515         {
516             return _tabs.ContainsKey(TabText);
517         }
518
519         public bool ContainsTab(TabClass ts)
520         {
521             return _tabs.ContainsValue(ts);
522         }
523
524         /// <summary>
525         /// 指定されたタブ名を元に、既存のタブ名との重複を避けた名前を生成します
526         /// </summary>
527         /// <param name="baseTabName">作成したいタブ名</param>
528         /// <returns>生成されたタブ名</returns>
529         /// <exception cref="TabException">タブ名の生成を 100 回試行して失敗した場合</exception>
530         public string MakeTabName(string baseTabName)
531         {
532             return this.MakeTabName(baseTabName, 100);
533         }
534
535         /// <summary>
536         /// 指定されたタブ名を元に、既存のタブ名との重複を避けた名前を生成します
537         /// </summary>
538         /// <param name="baseTabName">作成したいタブ名</param>
539         /// <param name="retryCount">重複を避けたタブ名を生成する試行回数</param>
540         /// <returns>生成されたタブ名</returns>
541         /// <exception cref="TabException">retryCount で指定された回数だけタブ名の生成を試行して失敗した場合</exception>
542         public string MakeTabName(string baseTabName, int retryCount)
543         {
544             if (!this.ContainsTab(baseTabName))
545                 return baseTabName;
546
547             foreach (var i in Enumerable.Range(2, retryCount - 1))
548             {
549                 var tabName = baseTabName + i;
550                 if (!this.ContainsTab(tabName))
551                 {
552                     return tabName;
553                 }
554             }
555
556             var message = string.Format(Properties.Resources.TabNameDuplicate_Text, baseTabName);
557             throw new TabException(message);
558         }
559
560         public Dictionary<string, TabClass> Tabs
561         {
562             get
563             {
564                 return _tabs;
565             }
566             set
567             {
568                 _tabs = value;
569             }
570         }
571
572         public Dictionary<string, TabClass>.KeyCollection KeysTab
573         {
574             get
575             {
576                 return _tabs.Keys;
577             }
578         }
579
580         public void SortPosts()
581         {
582             foreach (var tab in _tabs.Values)
583             {
584                 tab.Sort();
585             }
586         }
587
588         public SortOrder SortOrder
589         {
590             get
591             {
592                 return _sorter.Order;
593             }
594             set
595             {
596                 _sorter.Order = value;
597                 foreach (var tab in _tabs.Values)
598                 {
599                     tab.Sorter.Order = value;
600                 }
601             }
602         }
603
604         public IdComparerClass.ComparerMode SortMode
605         {
606             get
607             {
608                 return _sorter.Mode;
609             }
610             set
611             {
612                 _sorter.Mode = value;
613                 foreach (var tab in _tabs.Values)
614                 {
615                     tab.Sorter.Mode = value;
616                 }
617             }
618         }
619
620         public SortOrder ToggleSortOrder(IdComparerClass.ComparerMode SortMode)
621         {
622             if (_sorter.Mode == SortMode)
623             {
624                 if (_sorter.Order == SortOrder.Ascending)
625                 {
626                     _sorter.Order = SortOrder.Descending;
627                 }
628                 else
629                 {
630                     _sorter.Order = SortOrder.Ascending;
631                 }
632                 foreach (var tab in _tabs.Values)
633                 {
634                     tab.Sorter.Order = _sorter.Order;
635                 }
636             }
637             else
638             {
639                 _sorter.Mode = SortMode;
640                 _sorter.Order = SortOrder.Ascending;
641                 foreach (var tab in _tabs.Values)
642                 {
643                     tab.Sorter.Mode = SortMode;
644                     tab.Sorter.Order = SortOrder.Ascending;
645                 }
646             }
647             this.SortPosts();
648             return _sorter.Order;
649         }
650
651     //    public PostClass RetweetSource(long Id)
652     //    {
653     //        get
654     //        {
655     //            if (_retweets.ContainsKey(Id))
656     //            {
657     //                return _retweets[Id];
658     //            }
659     //            else
660     //            {
661     //                return null;
662     //            }
663     //        }
664     //    }
665         public PostClass RetweetSource(long Id)
666         {
667             PostClass status;
668             return _retweets.TryGetValue(Id, out status)
669                 ? status
670                 : null;
671         }
672
673         public void RemoveFavPost(long Id)
674         {
675             lock (LockObj)
676             {
677                 PostClass post;
678                 var tab = this.GetTabByType(MyCommon.TabUsageType.Favorites);
679                 var tn = tab.TabName;
680
681                 if (_statuses.TryGetValue(Id, out post))
682                 {
683                     //指定タブから該当ID削除
684                     var tType = tab.TabType;
685                     if (tab.Contains(Id))
686                     {
687                         if (tab.UnreadManage && !post.IsRead)    //未読管理
688                         {
689                             lock (LockUnread)
690                             {
691                                 tab.UnreadCount--;
692                                 this.SetNextUnreadId(Id, tab);
693                             }
694                         }
695                         tab.Remove(Id);
696                     }
697                     //FavタブからRetweet発言を削除する場合は、他の同一参照Retweetも削除
698                     if (tType == MyCommon.TabUsageType.Favorites && post.RetweetedId != null)
699                     {
700                         for (int i = 0; i < tab.AllCount; i++)
701                         {
702                             PostClass rPost = null;
703                             try
704                             {
705                                 rPost = this[tn, i];
706                             }
707                             catch (ArgumentOutOfRangeException)
708                             {
709                                 break;
710                             }
711                             if (rPost.RetweetedId != null && rPost.RetweetedId == post.RetweetedId)
712                             {
713                                 if (tab.UnreadManage && !rPost.IsRead)    //未読管理
714                                 {
715                                     lock (LockUnread)
716                                     {
717                                         tab.UnreadCount--;
718                                         this.SetNextUnreadId(rPost.StatusId, tab);
719                                     }
720                                 }
721                                 tab.Remove(rPost.StatusId);
722                             }
723                         }
724                     }
725                 }
726                 //TabType=PublicSearchの場合(Postの保存先がTabClass内)
727                 //if (tab.Contains(StatusId) &&
728                 //   (tab.TabType = MyCommon.TabUsageType.PublicSearch || tab.TabType = MyCommon.TabUsageType.DirectMessage))
729                 //{
730                 //    post = tab.Posts[StatusId];
731                 //    if (tab.UnreadManage && !post.IsRead)    //未読管理
732                 //    {
733                 //        lock (LockUnread)
734                 //        {
735                 //            tab.UnreadCount--;
736                 //            this.SetNextUnreadId(StatusId, tab);
737                 //        }
738                 //    }
739                 //    tab.Remove(StatusId);
740                 //}
741             }
742         }
743
744         public void ScrubGeoReserve(long id, long upToStatusId)
745         {
746             lock (LockObj)
747             {
748                 //this._scrubGeo.Add(new ScrubGeoInfo With {.UserId = id, .UpToStatusId = upToStatusId});
749                 this.ScrubGeo(id, upToStatusId);
750             }
751         }
752
753         private void ScrubGeo(long userId, long upToStatusId)
754         {
755             lock (LockObj)
756             {
757                 var userPosts = from post in this._statuses.Values
758                                 where post.UserId == userId && post.UserId <= upToStatusId
759                                 select post;
760
761                 foreach (var p in userPosts)
762                 {
763                     p.PostGeo = new PostClass.StatusGeo();
764                 }
765
766                 var userPosts2 = from tb in this.GetTabsInnerStorageType()
767                                  from post in tb.Posts.Values
768                                  where post.UserId == userId && post.UserId <= upToStatusId
769                                  select post;
770
771                 foreach (var p in userPosts2)
772                 {
773                     p.PostGeo = new PostClass.StatusGeo();
774                 }
775             }
776         }
777
778         public void RemovePostReserve(long id)
779         {
780             lock (LockObj)
781             {
782                 this._deletedIds.Add(id);
783                 this.DeletePost(id);   //UI選択行がずれるため、RemovePostは使用しない
784             }
785         }
786
787         public void RemovePost(long Id)
788         {
789             lock (LockObj)
790             {
791                 //各タブから該当ID削除
792                 foreach (var tab in _tabs.Values)
793                 {
794                     if (tab.Contains(Id))
795                     {
796                         if (tab.UnreadManage && !tab.Posts[Id].IsRead)    //未読管理
797                         {
798                             lock (LockUnread)
799                             {
800                                 tab.UnreadCount--;
801                                 this.SetNextUnreadId(Id, tab);
802                             }
803                         }
804                         tab.Remove(Id);
805                     }
806                 }
807                 if (_statuses.ContainsKey(Id)) _statuses.Remove(Id);
808             }
809         }
810
811         private void DeletePost(long Id)
812         {
813             lock (LockObj)
814             {
815                 PostClass post;
816                 if (_statuses.TryGetValue(Id, out post))
817                 {
818                     post.IsDeleted = true;
819                 }
820                 foreach (var tb in this.GetTabsInnerStorageType())
821                 {
822                     if (tb.Contains(Id))
823                     {
824                         post = tb.Posts[Id];
825                         post.IsDeleted = true;
826                     }
827                 }
828             }
829         }
830
831         public int GetOldestUnreadIndex(string TabName)
832         {
833             var tb = _tabs[TabName];
834             if (tb.OldestUnreadId > -1 &&
835                 tb.Contains(tb.OldestUnreadId) &&
836                 tb.UnreadCount > 0)
837             {
838                 //未読アイテムへ
839                 bool isRead = tb.Posts[tb.OldestUnreadId].IsRead;
840                 if (isRead)
841                 {
842                     //状態不整合(最古未読IDが実は既読)
843                     lock (LockUnread)
844                     {
845                         this.SetNextUnreadId(-1, tb);  //頭から探索
846                     }
847                     if (tb.OldestUnreadId == -1)
848                     {
849                         return -1;
850                     }
851                     else
852                     {
853                         return tb.IndexOf(tb.OldestUnreadId);
854                     }
855                 }
856                 else
857                 {
858                     return tb.IndexOf(tb.OldestUnreadId);    //最短経路;
859                 }
860             }
861             else
862             {
863                 //一見未読なさそうだが、未読カウントはあるので探索
864                 //if (tb.UnreadCount > 0)
865                 if (!(tb.UnreadManage && AppendSettingDialog.Instance.UnreadManage)) return -1;
866                 lock (LockUnread)
867                 {
868                     this.SetNextUnreadId(-1, tb);
869                 }
870                 if (tb.OldestUnreadId == -1)
871                 {
872                     return -1;
873                 }
874                 else
875                 {
876                     return tb.IndexOf(tb.OldestUnreadId);
877                 }
878                 //else
879                 //{
880                 //    return -1;
881                 //}
882             }
883         }
884
885         private void SetNextUnreadId(long CurrentId, TabClass Tab)
886         {
887             //CurrentID:今既読にしたID(OldestIDの可能性あり)
888             //最古未読が設定されていて、既読の場合(1発言以上存在)
889             try
890             {
891                 Dictionary<long, PostClass> posts = Tab.Posts;
892
893                 PostClass oldestUnreadPost;
894                 if (Tab.OldestUnreadId > -1 &&
895                     posts.TryGetValue(Tab.OldestUnreadId, out oldestUnreadPost) &&
896                     oldestUnreadPost.IsRead &&
897                     _sorter.Mode == IdComparerClass.ComparerMode.Id)     //次の未読探索
898                 {
899                     if (Tab.UnreadCount == 0)
900                     {
901                         //未読数0→最古未読なし
902                         Tab.OldestUnreadId = -1;
903                     }
904                     else if (Tab.OldestUnreadId == CurrentId && CurrentId > -1)
905                     {
906                         //最古IDを既読にしたタイミング→次のIDから続けて探索
907                         var idx = Tab.IndexOf(CurrentId);
908                         if (idx > -1)
909                         {
910                             //続きから探索
911                             FindUnreadId(idx, Tab);
912                             return;
913                         }
914                     }
915                 }
916             }
917             catch (KeyNotFoundException)
918             {
919             }
920
921             //頭から探索
922             FindUnreadId(-1, Tab);
923         }
924
925         private void FindUnreadId(int StartIdx, TabClass Tab)
926         {
927             if (Tab.AllCount == 0)
928             {
929                 Tab.OldestUnreadId = -1;
930                 Tab.UnreadCount = 0;
931                 return;
932             }
933             var toIdx = 0;
934             var stp = 1;
935             Tab.OldestUnreadId = -1;
936             if (_sorter.Order == SortOrder.Ascending)
937             {
938                 if (StartIdx == -1)
939                 {
940                     StartIdx = 0;
941                 }
942                 else
943                 {
944                     //StartIdx++;
945                     if (StartIdx > Tab.AllCount - 1) StartIdx = Tab.AllCount - 1; //念のため
946                 }
947                 toIdx = Tab.AllCount - 1;
948                 if (toIdx < 0) toIdx = 0; //念のため
949                 stp = 1;
950             }
951             else
952             {
953                 if (StartIdx == -1)
954                 {
955                     StartIdx = Tab.AllCount - 1;
956                 }
957                 else
958                 {
959                     //StartIdx--;
960                 }
961                 if (StartIdx < 0) StartIdx = 0; //念のため
962                 toIdx = 0;
963                 stp = -1;
964             }
965
966             Dictionary<long, PostClass> posts = Tab.Posts;
967
968             for (int i = StartIdx; ; i+= stp)
969             {
970                 var id = Tab.GetId(i);
971                 if (id > -1 && !posts[id].IsRead)
972                 {
973                     Tab.OldestUnreadId = id;
974                     break;
975                 }
976
977                 if (i == toIdx) break;
978             }
979         }
980
981         public int DistributePosts()
982         {
983             lock (LockObj)
984             {
985                 //戻り値は追加件数
986                 //if (_addedIds == null) return 0;
987                 //if (_addedIds.Count == 0) return 0;
988
989                 if (_addedIds == null) _addedIds = new List<long>();
990                 if (_notifyPosts == null) _notifyPosts = new List<PostClass>();
991                 try
992                 {
993                     this.Distribute();    //タブに仮振分
994                 }
995                 catch (KeyNotFoundException)
996                 {
997                     //タブ変更により振分が失敗した場合
998                 }
999                 var retCnt = _addedIds.Count;
1000                 _addCount += retCnt;
1001                 _addedIds.Clear();
1002                 _addedIds = null;     //後始末
1003                 return retCnt;     //件数
1004             }
1005         }
1006
1007         public int SubmitUpdate(ref string soundFile,
1008                                 ref PostClass[] notifyPosts,
1009                                 ref bool isMentionIncluded,
1010                                 ref bool isDeletePost,
1011                                 bool isUserStream)
1012         {
1013             //注:メインスレッドから呼ぶこと
1014             lock (LockObj)
1015             {
1016                 if (_notifyPosts == null)
1017                 {
1018                     soundFile = "";
1019                     notifyPosts = null;
1020                     return 0;
1021                 }
1022
1023                 foreach (var tb in _tabs.Values)
1024                 {
1025                     if (tb.IsInnerStorageTabType)
1026                     {
1027                         _addCount += tb.GetTemporaryCount();
1028                     }
1029                     tb.AddSubmit(ref isMentionIncluded);  //振分確定(各タブに反映)
1030                 }
1031                 ////UserStreamで反映間隔10秒以下だったら、30秒ごとにソートする
1032                 ////10秒以上だったら毎回ソート
1033                 //static DateTime lastSort = DateTime.Now;
1034                 //if (AppendSettingDialog.Instance.UserstreamPeriodInt < 10 && isUserStream)
1035                 //{
1036                 //    if (Now.Subtract(lastSort) > TimeSpan.FromSeconds(30))
1037                 //    {
1038                 //        lastSort = DateTime.Now;
1039                 //        isUserStream = false;
1040                 //    }
1041                 //}
1042                 //else
1043                 //{
1044                 //    isUserStream = false;
1045                 //}
1046                 if (!isUserStream || this.SortMode != IdComparerClass.ComparerMode.Id)
1047                 {
1048                     this.SortPosts();
1049                 }
1050                 if (isUserStream)
1051                 {
1052                     isDeletePost = this._deletedIds.Count > 0;
1053                     foreach (var id in this._deletedIds)
1054                     {
1055                         //this.DeletePost(StatusId)
1056                         this.RemovePost(id);
1057                     }
1058                     this._deletedIds.Clear();
1059                 }
1060
1061                 soundFile = _soundFile;
1062                 _soundFile = "";
1063                 notifyPosts = _notifyPosts.ToArray();
1064                 _notifyPosts.Clear();
1065                 _notifyPosts = null;
1066                 var retCnt = _addCount;
1067                 _addCount = 0;
1068                 return retCnt;    //件数(EndUpdateの戻り値と同じ)
1069             }
1070         }
1071
1072         private void Distribute()
1073         {
1074             //各タブのフィルターと照合。合致したらタブにID追加
1075             //通知メッセージ用に、表示必要な発言リストと再生サウンドを返す
1076             //notifyPosts = new List<PostClass>();
1077             var homeTab = GetTabByType(MyCommon.TabUsageType.Home);
1078             var replyTab = GetTabByType(MyCommon.TabUsageType.Mentions);
1079             var dmTab = GetTabByType(MyCommon.TabUsageType.DirectMessage);
1080             var favTab = GetTabByType(MyCommon.TabUsageType.Favorites);
1081             foreach (var id in _addedIds)
1082             {
1083                 var post = _statuses[id];
1084                 var add = false;  //通知リスト追加フラグ
1085                 var mv = false;   //移動フラグ(Recent追加有無)
1086                 var rslt = MyCommon.HITRESULT.None;
1087                 post.IsExcludeReply = false;
1088                 foreach (var tab in _tabs.Values)
1089                 {
1090                     rslt = tab.AddFiltered(post);
1091                     if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
1092                     {
1093                         if (rslt == MyCommon.HITRESULT.CopyAndMark) post.IsMark = true; //マークあり
1094                         else if (rslt == MyCommon.HITRESULT.Move)
1095                         {
1096                             mv = true; //移動
1097                             post.IsMark = false;
1098                         }
1099                         if (tab.Notify) add = true; //通知あり
1100                         if (!string.IsNullOrEmpty(tab.SoundFile) && string.IsNullOrEmpty(_soundFile))
1101                         {
1102                             _soundFile = tab.SoundFile; //wavファイル(未設定の場合のみ)
1103                         }
1104                         post.FilterHit = true;
1105                     }
1106                     else
1107                     {
1108                         if (rslt == MyCommon.HITRESULT.Exclude && tab.TabType == MyCommon.TabUsageType.Mentions)
1109                         {
1110                             post.IsExcludeReply = true;
1111                         }
1112                         post.FilterHit = false;
1113                     }
1114                 }
1115                 if (!mv)  //移動されなかったらRecentに追加
1116                 {
1117                     homeTab.Add(post.StatusId, post.IsRead, true);
1118                     if (!string.IsNullOrEmpty(homeTab.SoundFile) && string.IsNullOrEmpty(_soundFile)) _soundFile = homeTab.SoundFile;
1119                     if (homeTab.Notify) add = true;
1120                 }
1121                 if (post.IsReply && !post.IsExcludeReply)    //除外ルール適用のないReplyならReplyタブに追加
1122                 {
1123                     replyTab.Add(post.StatusId, post.IsRead, true);
1124                     if (!string.IsNullOrEmpty(replyTab.SoundFile)) _soundFile = replyTab.SoundFile;
1125                     if (replyTab.Notify) add = true;
1126                 }
1127                 if (post.IsFav)    //Fav済み発言だったらFavoritesタブに追加
1128                 {
1129                     if (favTab.Contains(post.StatusId))
1130                     {
1131                         //取得済みなら非通知
1132                         //_soundFile = "";
1133                         add = false;
1134                     }
1135                     else
1136                     {
1137                         favTab.Add(post.StatusId, post.IsRead, true);
1138                         if (!string.IsNullOrEmpty(favTab.SoundFile) && string.IsNullOrEmpty(_soundFile)) _soundFile = favTab.SoundFile;
1139                         if (favTab.Notify) add = true;
1140                     }
1141                 }
1142                 if (add) _notifyPosts.Add(post);
1143             }
1144             foreach (var tb in _tabs.Values)
1145             {
1146                 if (tb.IsInnerStorageTabType)
1147                 {
1148                     if (tb.Notify)
1149                     {
1150                         if (tb.GetTemporaryCount() > 0)
1151                         {
1152                             foreach (var post in tb.GetTemporaryPosts())
1153                             {
1154                                 var exist = false;
1155                                 foreach (var npost in _notifyPosts)
1156                                 {
1157                                     if (npost.StatusId == post.StatusId)
1158                                     {
1159                                         exist = true;
1160                                         break;
1161                                     }
1162                                 }
1163                                 if (!exist) _notifyPosts.Add(post);
1164                             }
1165                             if (!string.IsNullOrEmpty(tb.SoundFile))
1166                             {
1167                                 if (tb.TabType == MyCommon.TabUsageType.DirectMessage || string.IsNullOrEmpty(_soundFile))
1168                                 {
1169                                     _soundFile = tb.SoundFile;
1170                                 }
1171                             }
1172                         }
1173                     }
1174                 }
1175             }
1176         }
1177
1178         public void AddPost(PostClass Item)
1179         {
1180             lock (LockObj)
1181             {
1182                 if (string.IsNullOrEmpty(Item.RelTabName))
1183                 {
1184                     if (!Item.IsDm)
1185                     {
1186                         PostClass status;
1187                         if (_statuses.TryGetValue(Item.StatusId, out status))
1188                         {
1189                             if (Item.IsFav)
1190                             {
1191                                 if (Item.RetweetedId == null)
1192                                 {
1193                                     status.IsFav = true;
1194                                 }
1195                                 else
1196                                 {
1197                                     Item.IsFav = false;
1198                                 }
1199                             }
1200                             else
1201                             {
1202                                 return;        //追加済みなら何もしない
1203                             }
1204                         }
1205                         else
1206                         {
1207                             if (Item.IsFav && Item.RetweetedId != null) Item.IsFav = false;
1208                             //既に持っている公式RTは捨てる
1209                             if (AppendSettingDialog.Instance.HideDuplicatedRetweets &&
1210                                 !Item.IsMe &&
1211                                 Item.RetweetedId != null &&
1212                                 this._retweets.TryGetValue(Item.RetweetedId.Value, out status) &&
1213                                 status.RetweetedCount > 0) return;
1214
1215                             if (BlockIds.Contains(Item.UserId))
1216                                 return;
1217
1218                             if (this.IsMuted(Item))
1219                                 return;
1220
1221                             _statuses.Add(Item.StatusId, Item);
1222                         }
1223                         if (Item.RetweetedId != null)
1224                         {
1225                             this.AddRetweet(Item);
1226                         }
1227                         if (Item.IsFav && _retweets.ContainsKey(Item.StatusId))
1228                         {
1229                             return;    //Fav済みのRetweet元発言は追加しない
1230                         }
1231                         if (_addedIds == null) _addedIds = new List<long>(); //タブ追加用IDコレクション準備
1232                         _addedIds.Add(Item.StatusId);
1233                     }
1234                     else
1235                     {
1236                         //DM
1237                         var tb = this.GetTabByType(MyCommon.TabUsageType.DirectMessage);
1238                         if (tb.Contains(Item.StatusId)) return;
1239                         tb.AddPostToInnerStorage(Item);
1240                     }
1241                 }
1242                 else
1243                 {
1244                     //公式検索、リスト、関連発言の場合
1245                     TabClass tb;
1246                     this.Tabs.TryGetValue(Item.RelTabName, out tb);
1247                     if (tb == null) return;
1248                     if (tb.Contains(Item.StatusId)) return;
1249                     //tb.Add(Item.StatusId, Item.IsRead, true);
1250                     tb.AddPostToInnerStorage(Item);
1251                 }
1252             }
1253         }
1254
1255         private bool IsMuted(PostClass post)
1256         {
1257             // Recent以外のツイートと、リプライはミュート対象外
1258             // 参照: https://support.twitter.com/articles/20171399-muting-users-on-twitter
1259             if (string.IsNullOrEmpty(post.RelTabName) || post.IsReply)
1260                 return false;
1261
1262             if (this.MuteUserIds.Contains(post.UserId))
1263                 return true;
1264
1265             if (post.RetweetedByUserId != null && this.MuteUserIds.Contains(post.RetweetedByUserId.Value))
1266                 return true;
1267
1268             return false;
1269         }
1270
1271         private void AddRetweet(PostClass item)
1272         {
1273             var retweetedId = item.RetweetedId.Value;
1274
1275             //true:追加、False:保持済み
1276             PostClass status;
1277             if (_retweets.TryGetValue(retweetedId, out status))
1278             {
1279                 status.RetweetedCount++;
1280                 if (status.RetweetedCount > 10)
1281                 {
1282                     status.RetweetedCount = 0;
1283                 }
1284                 return;
1285             }
1286
1287             _retweets.Add(
1288                         item.RetweetedId.Value,
1289                         new PostClass(
1290                             item.Nickname,
1291                             item.TextFromApi,
1292                             item.Text,
1293                             item.ImageUrl,
1294                             item.ScreenName,
1295                             item.CreatedAt,
1296                             item.RetweetedId.Value,
1297                             item.IsFav,
1298                             item.IsRead,
1299                             item.IsReply,
1300                             item.IsExcludeReply,
1301                             item.IsProtect,
1302                             item.IsOwl,
1303                             item.IsMark,
1304                             item.InReplyToUser,
1305                             item.InReplyToStatusId,
1306                             item.Source,
1307                             item.SourceHtml,
1308                             item.ReplyToList,
1309                             item.IsMe,
1310                             item.IsDm,
1311                             item.UserId,
1312                             item.FilterHit,
1313                             "",
1314                             null,
1315                             item.PostGeo
1316                         )
1317                     );
1318             _retweets[retweetedId].RetweetedCount++;
1319         }
1320
1321         public void SetReadAllTab(bool Read, string TabName, int Index)
1322         {
1323             //Read:true=既読へ false=未読へ
1324             var tb = _tabs[TabName];
1325
1326             if (tb.UnreadManage == false) return; //未読管理していなければ終了
1327
1328             var Id = tb.GetId(Index);
1329             if (Id < 0) return;
1330             PostClass post = tb.Posts[Id];
1331
1332             if (post.IsRead == Read) return; //状態変更なければ終了
1333
1334             post.IsRead = Read;
1335
1336             lock (LockUnread)
1337             {
1338                 if (Read)
1339                 {
1340                     tb.UnreadCount--;
1341                     this.SetNextUnreadId(Id, tb);  //次の未読セット
1342                     //他タブの最古未読IDはタブ切り替え時に。
1343                     if (tb.IsInnerStorageTabType)
1344                     {
1345                         //一般タブ
1346                         PostClass status;
1347                         if (_statuses.TryGetValue(Id, out status) && !status.IsRead)
1348                         {
1349                             foreach (var tab in _tabs.Values)
1350                             {
1351                                 if (tab.UnreadManage &&
1352                                     !tab.IsInnerStorageTabType &&
1353                                     tab.Contains(Id))
1354                                 {
1355                                     tab.UnreadCount--;
1356                                     if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1357                                 }
1358                             }
1359                             status.IsRead = true;
1360                         }
1361                     }
1362                     else
1363                     {
1364                         //一般タブ
1365                         foreach (var tab in _tabs.Values)
1366                         {
1367                             if (tab != tb &&
1368                                 tab.UnreadManage &&
1369                                 !tab.IsInnerStorageTabType &&
1370                                 tab.Contains(Id))
1371                             {
1372                                 tab.UnreadCount--;
1373                                 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1374                             }
1375                         }
1376                     }
1377                     //内部保存タブ
1378                     foreach (var tab in _tabs.Values)
1379                     {
1380                         if (tab != tb &&
1381                             tab.IsInnerStorageTabType &&
1382                             tab.Contains(Id))
1383                         {
1384                             var tPost = tab.Posts[Id];
1385                             if (!tPost.IsRead)
1386                             {
1387                                 if (tab.UnreadManage)
1388                                 {
1389                                     tab.UnreadCount--;
1390                                     if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1391                                 }
1392                                 tPost.IsRead = true;
1393                             }
1394                         }
1395                     }
1396                 }
1397                 else
1398                 {
1399                     tb.UnreadCount++;
1400                     //if (tb.OldestUnreadId > Id || tb.OldestUnreadId = -1) tb.OldestUnreadId = Id
1401                     if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1402                     if (tb.IsInnerStorageTabType)
1403                     {
1404                         //一般タブ
1405                         PostClass status;
1406                         if (_statuses.TryGetValue(Id, out status) && status.IsRead)
1407                         {
1408                             foreach (var tab in _tabs.Values)
1409                             {
1410                                 if (tab.UnreadManage &&
1411                                     !tab.IsInnerStorageTabType &&
1412                                     tab.Contains(Id))
1413                                 {
1414                                     tab.UnreadCount++;
1415                                     if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1416                                 }
1417                             }
1418                             status.IsRead = false;
1419                         }
1420                     }
1421                     else
1422                     {
1423                         //一般タブ
1424                         foreach (var tab in _tabs.Values)
1425                         {
1426                             if (tab != tb &&
1427                                 tab.UnreadManage &&
1428                                 !tab.IsInnerStorageTabType &&
1429                                 tab.Contains(Id))
1430                             {
1431                                 tab.UnreadCount++;
1432                                 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1433                             }
1434                         }
1435                     }
1436                     //内部保存タブ
1437                     foreach (var tab in _tabs.Values)
1438                     {
1439                         if (tab != tb &&
1440                             tab.IsInnerStorageTabType &&
1441                             tab.Contains(Id))
1442                         {
1443                             var tPost = tab.Posts[Id];
1444                             if (tPost.IsRead)
1445                             {
1446                                 if (tab.UnreadManage)
1447                                 {
1448                                     tab.UnreadCount++;
1449                                     if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1450                                 }
1451                                 tPost.IsRead = false;
1452                             }
1453                         }
1454                     }
1455                 }
1456             }
1457         }
1458
1459         // TODO: パフォーマンスを勘案して、戻すか決める
1460         public void SetRead(bool Read, string TabName, int Index)
1461         {
1462             //Read:true=既読へ false=未読へ
1463             var tb = _tabs[TabName];
1464
1465             if (tb.UnreadManage == false) return; //未読管理していなければ終了
1466
1467             var Id = tb.GetId(Index);
1468             if (Id < 0) return;
1469             PostClass post = tb.Posts[Id];
1470
1471             if (post.IsRead == Read) return; //状態変更なければ終了
1472
1473             post.IsRead = Read; //指定の状態に変更
1474
1475             lock (LockUnread)
1476             {
1477                 if (Read)
1478                 {
1479                     tb.UnreadCount--;
1480                     this.SetNextUnreadId(Id, tb);  //次の未読セット
1481                     //他タブの最古未読IDはタブ切り替え時に。
1482                     if (tb.IsInnerStorageTabType) return;
1483                     foreach (var tab in _tabs.Values)
1484                     {
1485                         if (tab != tb &&
1486                             tab.UnreadManage &&
1487                             !tab.IsInnerStorageTabType &&
1488                             tab.Contains(Id))
1489                         {
1490                             tab.UnreadCount--;
1491                             if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1492                         }
1493                     }
1494                 }
1495                 else
1496                 {
1497                     tb.UnreadCount++;
1498                     //if (tb.OldestUnreadId > Id || tb.OldestUnreadId == -1) tb.OldestUnreadId = Id;
1499                     if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1500                     if (tb.IsInnerStorageTabType) return;
1501                     foreach (var tab in _tabs.Values)
1502                     {
1503                         if (tab != tb &&
1504                             tab.UnreadManage &&
1505                             !tab.IsInnerStorageTabType &&
1506                             tab.Contains(Id))
1507                         {
1508                             tab.UnreadCount++;
1509                             if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1510                         }
1511                     }
1512                 }
1513             }
1514         }
1515
1516         public void SetRead()
1517         {
1518             var tb = GetTabByType(MyCommon.TabUsageType.Home);
1519             if (tb.UnreadManage == false) return;
1520
1521             lock (LockObj)
1522             {
1523                 for (int i = 0; i < tb.AllCount - 1; i++)
1524                 {
1525                     var id = tb.GetId(i);
1526                     if (id < 0) return;
1527                     var tPost = _statuses[id];
1528                     if (!tPost.IsReply &&
1529                         !tPost.IsRead &&
1530                         !tPost.FilterHit)
1531                     {
1532                         tPost.IsRead = true;
1533                         this.SetNextUnreadId(id, tb);  //次の未読セット
1534                         foreach (var tab in _tabs.Values)
1535                         {
1536                             if (tab.UnreadManage &&
1537                                 tab.Contains(id))
1538                             {
1539                                 tab.UnreadCount--;
1540                                 if (tab.OldestUnreadId == id) tab.OldestUnreadId = -1;
1541                             }
1542                         }
1543                     }
1544                 }
1545             }
1546         }
1547
1548         public PostClass this[long ID]
1549         {
1550             get
1551             {
1552                 PostClass status;
1553                 return _statuses.TryGetValue(ID, out status)
1554                     ? status
1555                     : this.GetTabsInnerStorageType()
1556                           .Where(t => t.Contains(ID))
1557                           .Select(t => t.Posts[ID]).FirstOrDefault();
1558             }
1559         }
1560
1561         public PostClass this[string TabName, int Index]
1562         {
1563             get
1564             {
1565                 TabClass tb;
1566                 if (!_tabs.TryGetValue(TabName, out tb)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1567                 var id = tb.GetId(Index);
1568                 if (id < 0) throw new ArgumentException("Index can't find. Index=" + Index.ToString() + "/TabName=" + TabName);
1569                 try
1570                 {
1571                     return tb.Posts[tb.GetId(Index)];
1572                 }
1573                 catch (Exception ex)
1574                 {
1575                     throw new Exception("Index=" + Index.ToString() + "/TabName=" + TabName, ex);
1576                 }
1577             }
1578         }
1579
1580         public PostClass[] this[string TabName, int StartIndex, int EndIndex]
1581         {
1582             get
1583             {
1584                 TabClass tb;
1585                 if (!_tabs.TryGetValue(TabName, out tb)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1586                 var length = EndIndex - StartIndex + 1;
1587                 var posts = new PostClass[length];
1588                 for (int i = 0; i < length; i++)
1589                 {
1590                     posts[i] = tb.Posts[tb.GetId(StartIndex + i)];
1591                 }
1592                 return posts;
1593             }
1594         }
1595
1596         //public ReadOnly int ItemCount
1597         //{
1598         //    get
1599         //    {
1600         //        lock (LockObj)
1601         //    {
1602         //            return _statuses.Count   //DM,公式検索は除く
1603         //        }
1604         //    }
1605         //}
1606
1607         public bool ContainsKey(long Id)
1608         {
1609             //DM,公式検索は非対応
1610             lock (LockObj)
1611             {
1612                 return _statuses.ContainsKey(Id);
1613             }
1614         }
1615
1616         public bool ContainsKey(long Id, string TabName)
1617         {
1618             //DM,公式検索は対応版
1619             lock (LockObj)
1620             {
1621                 TabClass tab;
1622                 return _tabs.TryGetValue(TabName, out tab) && tab.Contains(Id);
1623             }
1624         }
1625
1626         public void SetUnreadManage(bool Manage)
1627         {
1628             if (Manage)
1629             {
1630                 foreach (var tab in _tabs.Values)
1631                 {
1632                     if (tab.UnreadManage)
1633                     {
1634                         lock (LockUnread)
1635                         {
1636                             var cnt = 0;
1637                             var oldest = long.MaxValue;
1638                             Dictionary<long, PostClass> posts = tab.Posts;
1639                             foreach (var id in tab.BackupIds)
1640                             {
1641                                 if (!posts[id].IsRead)
1642                                 {
1643                                     cnt++;
1644                                     if (oldest > id) oldest = id;
1645                                 }
1646                             }
1647                             if (oldest == long.MaxValue) oldest = -1;
1648                             tab.OldestUnreadId = oldest;
1649                             tab.UnreadCount = cnt;
1650                         }
1651                     }
1652                 }
1653             }
1654             else
1655             {
1656                 foreach (var tab in _tabs.Values)
1657                 {
1658                     if (tab.UnreadManage && tab.UnreadCount > 0)
1659                     {
1660                         lock (LockUnread)
1661                         {
1662                             tab.UnreadCount = 0;
1663                             tab.OldestUnreadId = -1;
1664                         }
1665                     }
1666                 }
1667             }
1668         }
1669
1670         public void RenameTab(string Original, string NewName)
1671         {
1672             var tb = _tabs[Original];
1673             _tabs.Remove(Original);
1674             tb.TabName = NewName;
1675             _tabs.Add(NewName, tb);
1676         }
1677
1678         public void FilterAll()
1679         {
1680             lock (LockObj)
1681             {
1682                 var tbr = GetTabByType(MyCommon.TabUsageType.Home);
1683                 var replyTab = GetTabByType(MyCommon.TabUsageType.Mentions);
1684                 foreach (var tb in _tabs.Values.ToArray())
1685                 {
1686                     if (tb.FilterModified)
1687                     {
1688                         tb.FilterModified = false;
1689                         var orgIds = tb.BackupIds;
1690                         tb.ClearIDs();
1691                         //////////////フィルター前のIDsを退避。どのタブにも含まれないidはrecentへ追加
1692                         //////////////moveフィルターにヒットした際、recentに該当あればrecentから削除
1693                         foreach (var post in _statuses.Values)
1694                         {
1695                             if (post.IsDm) continue;
1696                             var rslt = MyCommon.HITRESULT.None;
1697                             rslt = tb.AddFiltered(post);
1698                             switch (rslt)
1699                             {
1700                                 case MyCommon.HITRESULT.CopyAndMark:
1701                                 post.IsMark = true; //マークあり
1702                                 post.FilterHit = true;
1703                                 break;
1704                                 case MyCommon.HITRESULT.Move:
1705                                 tbr.Remove(post.StatusId, post.IsRead);
1706                                 post.IsMark = false;
1707                                 post.FilterHit = true;
1708                                 break;
1709                             case MyCommon.HITRESULT.Copy:
1710                                 post.IsMark = false;
1711                                 post.FilterHit = true;
1712                                 break;
1713                             case MyCommon.HITRESULT.Exclude:
1714                                 if (tb.TabName == replyTab.TabName && post.IsReply) post.IsExcludeReply = true;
1715                                 if (post.IsFav) GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, true);
1716                                 post.FilterHit = false;
1717                                 break;
1718                             case MyCommon.HITRESULT.None:
1719                                 if (tb.TabName == replyTab.TabName && post.IsReply) replyTab.Add(post.StatusId, post.IsRead, true);
1720                                 if (post.IsFav) GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, true);
1721                                 post.FilterHit = false;
1722                                 break;
1723                             }
1724                         }
1725                         tb.AddSubmit();  //振分確定
1726                         foreach (var id in orgIds)
1727                         {
1728                             var hit = false;
1729                             foreach (var tbTemp in _tabs.Values.ToArray())
1730                             {
1731                                 if (tbTemp.Contains(id))
1732                                 {
1733                                     hit = true;
1734                                     break;
1735                                 }
1736                             }
1737                             if (!hit) tbr.Add(id, _statuses[id].IsRead, false);
1738                         }
1739                     }
1740                 }
1741                 this.SortPosts();
1742             }
1743         }
1744
1745         public long[] GetId(string TabName, ListView.SelectedIndexCollection IndexCollection)
1746         {
1747             if (IndexCollection.Count == 0) return null;
1748
1749             var tb = _tabs[TabName];
1750             var Ids = new long[IndexCollection.Count];
1751             for (int i = 0; i < Ids.Length; i++)
1752             {
1753                 Ids[i] = tb.GetId(IndexCollection[i]);
1754             }
1755             return Ids;
1756         }
1757
1758         public long GetId(string TabName, int Index)
1759         {
1760             return _tabs[TabName].GetId(Index);
1761         }
1762
1763         public int[] IndexOf(string TabName, long[] Ids)
1764         {
1765             if (Ids == null) return null;
1766             var idx = new int[Ids.Length];
1767             var tb = _tabs[TabName];
1768             for (int i = 0; i < Ids.Length; i++)
1769             {
1770                 idx[i] = tb.IndexOf(Ids[i]);
1771             }
1772             return idx;
1773         }
1774
1775         public int IndexOf(string TabName, long Id)
1776         {
1777             return _tabs[TabName].IndexOf(Id);
1778         }
1779
1780         public void ClearTabIds(string TabName)
1781         {
1782             //不要なPostを削除
1783             lock (LockObj)
1784             {
1785                 var tb = _tabs[TabName];
1786                 if (!tb.IsInnerStorageTabType)
1787                 {
1788                     foreach (var Id in tb.BackupIds)
1789                     {
1790                         var Hit = false;
1791                         foreach (var tab in _tabs.Values)
1792                         {
1793                             if (tab.Contains(Id))
1794                             {
1795                                 Hit = true;
1796                                 break;
1797                             }
1798                         }
1799                         if (!Hit) _statuses.Remove(Id);
1800                     }
1801                 }
1802
1803                 //指定タブをクリア
1804                 tb.ClearIDs();
1805             }
1806         }
1807
1808         public void SetTabUnreadManage(string TabName, bool Manage)
1809         {
1810             var tb = _tabs[TabName];
1811             lock (LockUnread)
1812             {
1813                 if (Manage)
1814                 {
1815                     var cnt = 0;
1816                     var oldest = long.MaxValue;
1817                     Dictionary<long, PostClass> posts = tb.Posts;
1818                     foreach (var id in tb.BackupIds)
1819                     {
1820                         if (!posts[id].IsRead)
1821                         {
1822                             cnt++;
1823                             if (oldest > id) oldest = id;
1824                         }
1825                     }
1826                     if (oldest == long.MaxValue) oldest = -1;
1827                     tb.OldestUnreadId = oldest;
1828                     tb.UnreadCount = cnt;
1829                 }
1830                 else
1831                 {
1832                     tb.OldestUnreadId = -1;
1833                     tb.UnreadCount = 0;
1834                 }
1835             }
1836             tb.UnreadManage = Manage;
1837         }
1838
1839         public void RefreshOwl(List<long> follower)
1840         {
1841             lock (LockObj)
1842             {
1843                 if (follower.Count > 0)
1844                 {
1845                     foreach (var post in _statuses.Values)
1846                     {
1847                         //if (post.UserId = 0 || post.IsDm) Continue For
1848                         if (post.IsMe)
1849                         {
1850                             post.IsOwl = false;
1851                         }
1852                         else
1853                         {
1854                             post.IsOwl = !follower.Contains(post.UserId);
1855                         }
1856                     }
1857                 }
1858                 else
1859                 {
1860                     foreach (var post in _statuses.Values)
1861                     {
1862                         post.IsOwl = false;
1863                     }
1864                 }
1865             }
1866         }
1867
1868         public TabClass GetTabByType(MyCommon.TabUsageType tabType)
1869         {
1870             //Home,Mentions,DM,Favは1つに制限する
1871             //その他のタイプを指定されたら、最初に合致したものを返す
1872             //合致しなければnullを返す
1873             lock (LockObj)
1874             {
1875                 foreach (var tab in _tabs.Values)
1876                 {
1877                     if (tab.TabType == tabType) return tab;
1878                 }
1879                 return null;
1880             }
1881         }
1882
1883         public List<TabClass> GetTabsByType(MyCommon.TabUsageType tabType)
1884         {
1885             //合致したタブをListで返す
1886             //合致しなければ空のListを返す
1887             lock (LockObj)
1888             {
1889                 var tbs = new List<TabClass>();
1890                 foreach (var tb in _tabs.Values)
1891                 {
1892                     if ((tabType & tb.TabType) == tb.TabType) tbs.Add(tb);
1893                 }
1894                 return tbs;
1895             }
1896         }
1897
1898         public List<TabClass> GetTabsInnerStorageType()
1899         {
1900             //合致したタブをListで返す
1901             //合致しなければ空のListを返す
1902             lock (LockObj)
1903             {
1904                 var tbs = new List<TabClass>();
1905                 foreach (var tb in _tabs.Values)
1906                 {
1907                     if (tb.IsInnerStorageTabType) tbs.Add(tb);
1908                 }
1909                 return tbs;
1910             }
1911         }
1912
1913         public TabClass GetTabByName(string tabName)
1914         {
1915             lock (LockObj)
1916             {
1917                 TabClass tab;
1918                 return _tabs.TryGetValue(tabName, out tab)
1919                     ? tab
1920                     : null;
1921             }
1922         }
1923
1924         // デフォルトタブの判定処理
1925         public bool IsDefaultTab(string tabName)
1926         {
1927             TabClass tab;
1928             if (tabName != null &&
1929                _tabs.TryGetValue(tabName, out tab) &&
1930                (tab.TabType == MyCommon.TabUsageType.Home ||
1931                tab.TabType == MyCommon.TabUsageType.Mentions ||
1932                tab.TabType == MyCommon.TabUsageType.DirectMessage ||
1933                tab.TabType == MyCommon.TabUsageType.Favorites))
1934             {
1935                 return true;
1936             }
1937
1938             return false;
1939         }
1940
1941         //振り分け可能タブの判定処理
1942         public bool IsDistributableTab(string tabName)
1943         {
1944             TabClass tab;
1945             if (tabName != null &&
1946                 _tabs.TryGetValue(tabName, out tab) &&
1947                 (tab.TabType == MyCommon.TabUsageType.Mentions ||
1948                 tab.TabType == MyCommon.TabUsageType.UserDefined))
1949             {
1950                 return true;
1951             }
1952
1953             return false;
1954         }
1955
1956         public string GetUniqueTabName()
1957         {
1958             var tabNameTemp = "MyTab" + (_tabs.Count + 1).ToString();
1959             for (int i = 2; i <= 100; i++)
1960             {
1961                 if (_tabs.ContainsKey(tabNameTemp))
1962                 {
1963                     tabNameTemp = "MyTab" + (_tabs.Count + i).ToString();
1964                 }
1965                 else
1966                 {
1967                     break;
1968                 }
1969             }
1970             return tabNameTemp;
1971         }
1972
1973         public Dictionary<long, PostClass> Posts
1974         {
1975             get
1976             {
1977                 return _statuses;
1978             }
1979         }
1980     }
1981
1982     [Serializable]
1983     public sealed class TabClass
1984     {
1985         private bool _unreadManage = false;
1986         private List<PostFilterRule> _filters;
1987         private int _unreadCount = 0;
1988         private List<long> _ids;
1989         private List<TemporaryId> _tmpIds = new List<TemporaryId>();
1990         private MyCommon.TabUsageType _tabType = MyCommon.TabUsageType.Undefined;
1991
1992         [NonSerialized]
1993         private IdComparerClass _sorter = new IdComparerClass();
1994
1995         private readonly object _lockObj = new object();
1996
1997         public string User { get; set; }
1998
1999 #region "検索"
2000         //Search query
2001         private string _searchLang = "";
2002         private string _searchWords = "";
2003         private string _nextPageQuery = "";
2004
2005         public string SearchLang
2006         {
2007             get
2008             {
2009                 return _searchLang;
2010             }
2011             set
2012             {
2013                 SinceId = 0;
2014                 _searchLang = value;
2015             }
2016         }
2017         public string SearchWords
2018         {
2019             get
2020             {
2021                 return _searchWords;
2022             }
2023             set
2024             {
2025                 SinceId = 0;
2026                 _searchWords = value.Trim();
2027             }
2028         }
2029
2030         public string NextPageQuery
2031         {
2032             get
2033             {
2034                 return _nextPageQuery;
2035             }
2036             set
2037             {
2038                 _nextPageQuery = value;
2039             }
2040         }
2041
2042         public int GetSearchPage(int count)
2043         {
2044             return ((_ids.Count / count) + 1);
2045         }
2046         private Dictionary<string, string> _beforeQuery = new Dictionary<string, string>();
2047         public void SaveQuery(bool more)
2048         {
2049             var qry = new Dictionary<string, string>();
2050             if (string.IsNullOrEmpty(_searchWords))
2051             {
2052                 _beforeQuery = qry;
2053                 return;
2054             }
2055             qry.Add("q", _searchWords);
2056             if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2057             _beforeQuery = qry;
2058         }
2059
2060         public bool IsQueryChanged()
2061         {
2062             var qry = new Dictionary<string, string>();
2063             if (!string.IsNullOrEmpty(_searchWords))
2064             {
2065                 qry.Add("q", _searchWords);
2066                 if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2067             }
2068             if (qry.Count != _beforeQuery.Count) return true;
2069
2070             foreach (var kvp in qry)
2071             {
2072                 if (!_beforeQuery.ContainsKey(kvp.Key) || _beforeQuery[kvp.Key] != kvp.Value)
2073                 {
2074                     return true;
2075                 }
2076             }
2077             return false;
2078         }
2079 #endregion
2080
2081 #region "リスト"
2082         [NonSerialized]
2083         private ListElement _listInfo;
2084         public ListElement ListInfo
2085         {
2086             get
2087             {
2088                 return _listInfo;
2089             }
2090             set
2091             {
2092                 _listInfo = value;
2093             }
2094         }
2095 #endregion
2096
2097         [XmlIgnore]
2098         public PostClass RelationTargetPost { get; set; }
2099
2100         [XmlIgnore]
2101         public long OldestId = long.MaxValue;
2102
2103         [XmlIgnore]
2104         public long SinceId { get; set; }
2105
2106         [XmlIgnore]
2107         public Dictionary<long, PostClass> Posts { get; private set; }
2108
2109         private Dictionary<long, PostClass> _innerPosts;
2110
2111         public PostClass[] GetTemporaryPosts()
2112         {
2113             var tempPosts = new List<PostClass>();
2114             if (_tmpIds.Count == 0) return tempPosts.ToArray();
2115             foreach (var tempId in _tmpIds)
2116             {
2117                 tempPosts.Add(Posts[tempId.Id]);
2118             }
2119             return tempPosts.ToArray();
2120         }
2121
2122         public int GetTemporaryCount()
2123         {
2124             return _tmpIds.Count;
2125         }
2126
2127         private struct TemporaryId
2128         {
2129             public long Id;
2130             public bool Read;
2131
2132             public TemporaryId(long argId, bool argRead)
2133             {
2134                 Id = argId;
2135                 Read = argRead;
2136             }
2137         }
2138
2139         public TabClass()
2140         {
2141             _innerPosts = new Dictionary<long, PostClass>();
2142             Posts = _innerPosts;
2143             SoundFile = "";
2144             OldestUnreadId = -1;
2145             TabName = "";
2146             _filters = new List<PostFilterRule>();
2147             Protected = false;
2148             Notify = true;
2149             SoundFile = "";
2150             _unreadManage = true;
2151             _ids = new List<long>();
2152             this.OldestUnreadId = -1;
2153             _tabType = MyCommon.TabUsageType.Undefined;
2154             _listInfo = null;
2155         }
2156
2157         public TabClass(string TabName, MyCommon.TabUsageType TabType, ListElement list) : this()
2158         {
2159             this.TabName = TabName;
2160             this.TabType = TabType;
2161             this.ListInfo = list;
2162         }
2163
2164         public void Sort()
2165         {
2166             if (_sorter.Mode == IdComparerClass.ComparerMode.Id)
2167             {
2168                 _ids.Sort(_sorter.CmpMethod());
2169                 return;
2170             }
2171             long[] ar = null;
2172             if (_sorter.Order == SortOrder.Ascending)
2173             {
2174                 switch (_sorter.Mode)
2175                 {
2176                     case IdComparerClass.ComparerMode.Data:
2177                         ar = _ids.OrderBy(n => _sorter.posts[n].TextFromApi).ToArray();
2178                         break;
2179                     case IdComparerClass.ComparerMode.Name:
2180                         ar = _ids.OrderBy(n => _sorter.posts[n].ScreenName).ToArray();
2181                         break;
2182                     case IdComparerClass.ComparerMode.Nickname:
2183                         ar = _ids.OrderBy(n => _sorter.posts[n].Nickname).ToArray();
2184                         break;
2185                     case IdComparerClass.ComparerMode.Source:
2186                         ar = _ids.OrderBy(n => _sorter.posts[n].Source).ToArray();
2187                         break;
2188                 }
2189             }
2190             else
2191             {
2192                 switch (_sorter.Mode)
2193                 {
2194                     case IdComparerClass.ComparerMode.Data:
2195                         ar = _ids.OrderByDescending(n => _sorter.posts[n].TextFromApi).ToArray();
2196                         break;
2197                     case IdComparerClass.ComparerMode.Name:
2198                         ar = _ids.OrderByDescending(n => _sorter.posts[n].ScreenName).ToArray();
2199                         break;
2200                     case IdComparerClass.ComparerMode.Nickname:
2201                         ar = _ids.OrderByDescending(n => _sorter.posts[n].Nickname).ToArray();
2202                         break;
2203                     case IdComparerClass.ComparerMode.Source:
2204                         ar = _ids.OrderByDescending(n => _sorter.posts[n].Source).ToArray();
2205                         break;
2206                 }
2207             }
2208             _ids = new List<long>(ar);
2209         }
2210
2211         public IdComparerClass Sorter
2212         {
2213             get
2214             {
2215                 return _sorter;
2216             }
2217         }
2218
2219         //無条件に追加
2220         private void Add(long ID, bool Read)
2221         {
2222             if (this._ids.Contains(ID)) return;
2223
2224             if (this.Sorter.Mode == IdComparerClass.ComparerMode.Id)
2225             {
2226                 if (this.Sorter.Order == SortOrder.Ascending)
2227                 {
2228                     this._ids.Add(ID);
2229                 }
2230                 else
2231                 {
2232                     this._ids.Insert(0, ID);
2233                 }
2234             }
2235             else
2236             {
2237                 this._ids.Add(ID);
2238             }
2239
2240             if (!Read && this._unreadManage)
2241             {
2242                 this._unreadCount++;
2243                 if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2244                 //if (this.OldestUnreadId == -1)
2245                 //{
2246                 //    this.OldestUnreadId = ID;
2247                 //}
2248                 //else
2249                 //{
2250                 //    if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2251                 //}
2252             }
2253         }
2254
2255         public void Add(long ID, bool Read, bool Temporary)
2256         {
2257             if (!Temporary)
2258             {
2259                 this.Add(ID, Read);
2260             }
2261             else
2262             {
2263                 _tmpIds.Add(new TemporaryId(ID, Read));
2264             }
2265         }
2266
2267         //フィルタに合致したら追加
2268         public MyCommon.HITRESULT AddFiltered(PostClass post)
2269         {
2270             if (this.IsInnerStorageTabType) return MyCommon.HITRESULT.None;
2271
2272             var rslt = MyCommon.HITRESULT.None;
2273             //全フィルタ評価(優先順位あり)
2274             lock (this._lockObj)
2275             {
2276                 foreach (var ft in _filters)
2277                 {
2278                     try
2279                     {
2280                         switch (ft.ExecFilter(post))   //フィルタクラスでヒット判定
2281                         {
2282                             case MyCommon.HITRESULT.None:
2283                                 break;
2284                             case MyCommon.HITRESULT.Copy:
2285                                 if (rslt != MyCommon.HITRESULT.CopyAndMark) rslt = MyCommon.HITRESULT.Copy;
2286                                 break;
2287                             case MyCommon.HITRESULT.CopyAndMark:
2288                                 rslt = MyCommon.HITRESULT.CopyAndMark;
2289                                 break;
2290                             case MyCommon.HITRESULT.Move:
2291                                 rslt = MyCommon.HITRESULT.Move;
2292                                 break;
2293                             case MyCommon.HITRESULT.Exclude:
2294                                 rslt = MyCommon.HITRESULT.Exclude;
2295                                 goto exit_for;
2296                         }
2297                     }
2298                     catch (NullReferenceException)
2299                     {
2300                         // ExecFilterでNullRef出る場合あり。暫定対応
2301                         MyCommon.TraceOut("ExecFilterでNullRef: " + ft.ToString());
2302                         rslt = MyCommon.HITRESULT.None;
2303                     }
2304                 }
2305             exit_for:
2306                 ;
2307             }
2308
2309             if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
2310             {
2311                 _tmpIds.Add(new TemporaryId(post.StatusId, post.IsRead));
2312             }
2313
2314             return rslt; //マーク付けは呼び出し元で行うこと
2315         }
2316
2317         //検索結果の追加
2318         public void AddPostToInnerStorage(PostClass Post)
2319         {
2320             if (_innerPosts.ContainsKey(Post.StatusId)) return;
2321             _innerPosts.Add(Post.StatusId, Post);
2322             _tmpIds.Add(new TemporaryId(Post.StatusId, Post.IsRead));
2323         }
2324
2325         public void AddSubmit(ref bool isMentionIncluded)
2326         {
2327             if (_tmpIds.Count == 0) return;
2328             _tmpIds.Sort((x, y) => x.Id.CompareTo(y.Id));
2329             foreach (var tId in _tmpIds)
2330             {
2331                 if (this.TabType == MyCommon.TabUsageType.Mentions && TabInformations.GetInstance()[tId.Id].IsReply) isMentionIncluded = true;
2332                 this.Add(tId.Id, tId.Read);
2333             }
2334             _tmpIds.Clear();
2335         }
2336
2337         public void AddSubmit()
2338         {
2339             bool mention = false;
2340             AddSubmit(ref mention);
2341         }
2342
2343         public void Remove(long Id)
2344         {
2345             if (!this._ids.Contains(Id)) return;
2346             this._ids.Remove(Id);
2347             if (this.IsInnerStorageTabType) _innerPosts.Remove(Id);
2348         }
2349
2350         public void Remove(long Id, bool Read)
2351         {
2352             if (!this._ids.Contains(Id)) return;
2353
2354             if (!Read && this._unreadManage)
2355             {
2356                 this._unreadCount--;
2357                 this.OldestUnreadId = -1;
2358             }
2359
2360             this._ids.Remove(Id);
2361             if (this.IsInnerStorageTabType) _innerPosts.Remove(Id);
2362         }
2363
2364         public bool UnreadManage
2365         {
2366             get
2367             {
2368                 return _unreadManage;
2369             }
2370             set
2371             {
2372                 this._unreadManage = value;
2373                 if (!value)
2374                 {
2375                     this.OldestUnreadId = -1;
2376                     this._unreadCount = 0;
2377                 }
2378             }
2379         }
2380
2381         // v1.0.5で「タブを固定(Locked)」から「タブを保護(Protected)」に名称変更
2382         [XmlElement(ElementName = "Locked")]
2383         public bool Protected { get; set; }
2384
2385         public bool Notify { get; set; }
2386
2387         public string SoundFile { get; set; }
2388
2389         [XmlIgnore]
2390         public long OldestUnreadId { get; set; }
2391
2392         [XmlIgnore]
2393         public int UnreadCount
2394         {
2395             get
2396             {
2397                 return this.UnreadManage && AppendSettingDialog.Instance.UnreadManage ? _unreadCount : 0;
2398             }
2399             set
2400             {
2401                 if (value < 0) value = 0;
2402                 _unreadCount = value;
2403             }
2404         }
2405
2406         public int AllCount
2407         {
2408             get
2409             {
2410                 return this._ids.Count;
2411             }
2412         }
2413
2414         public PostFilterRule[] GetFilters()
2415         {
2416             lock (this._lockObj)
2417             {
2418                 return _filters.ToArray();
2419             }
2420         }
2421
2422         public void RemoveFilter(PostFilterRule filter)
2423         {
2424             lock (this._lockObj)
2425             {
2426                 _filters.Remove(filter);
2427                 this.FilterModified = true;
2428             }
2429         }
2430
2431         public bool AddFilter(PostFilterRule filter)
2432         {
2433             lock (this._lockObj)
2434             {
2435                 if (_filters.Contains(filter)) return false;
2436                 _filters.Add(filter);
2437                 this.FilterModified = true;
2438                 return true;
2439             }
2440         }
2441
2442         public void EditFilter(PostFilterRule original, PostFilterRule modified)
2443         {
2444             original.FilterBody = modified.FilterBody;
2445             original.FilterName = modified.FilterName;
2446             original.UseNameField = modified.UseNameField;
2447             original.FilterByUrl = modified.FilterByUrl;
2448             original.UseRegex = modified.UseRegex;
2449             original.CaseSensitive = modified.CaseSensitive;
2450             original.FilterRt = modified.FilterRt;
2451             original.UseLambda = modified.UseLambda;
2452             original.FilterSource = modified.FilterSource;
2453             original.ExFilterBody = modified.ExFilterBody;
2454             original.ExFilterName = modified.ExFilterName;
2455             original.ExUseNameField = modified.ExUseNameField;
2456             original.ExFilterByUrl = modified.ExFilterByUrl;
2457             original.ExUseRegex = modified.ExUseRegex;
2458             original.ExCaseSensitive = modified.ExCaseSensitive;
2459             original.ExFilterRt = modified.ExFilterRt;
2460             original.ExUseLambda = modified.ExUseLambda;
2461             original.ExFilterSource = modified.ExFilterSource;
2462             original.MoveMatches = modified.MoveMatches;
2463             original.MarkMatches = modified.MarkMatches;
2464             this.FilterModified = true;
2465         }
2466
2467         [XmlIgnore]
2468         public List<PostFilterRule> Filters
2469         {
2470             get
2471             {
2472                 lock (this._lockObj)
2473                 {
2474                     return _filters;
2475                 }
2476             }
2477             set
2478             {
2479                 lock (this._lockObj)
2480                 {
2481                     _filters = value;
2482                 }
2483             }
2484         }
2485
2486         public PostFilterRule[] FilterArray
2487         {
2488             get
2489             {
2490                 lock (this._lockObj)
2491                 {
2492                     return _filters.ToArray();
2493                 }
2494             }
2495             set
2496             {
2497                 lock (this._lockObj)
2498                 {
2499                     foreach (var filters in value)
2500                     {
2501                         _filters.Add(filters);
2502                     }
2503                 }
2504             }
2505         }
2506         public bool Contains(long ID)
2507         {
2508             return _ids.Contains(ID);
2509         }
2510
2511         public void ClearIDs()
2512         {
2513             _ids.Clear();
2514             _tmpIds.Clear();
2515             _unreadCount = 0;
2516             this.OldestUnreadId = -1;
2517             _innerPosts.Clear();
2518         }
2519
2520         public long GetId(int Index)
2521         {
2522             return Index < _ids.Count ? _ids[Index] : -1;
2523         }
2524
2525         public int IndexOf(long ID)
2526         {
2527             return _ids.IndexOf(ID);
2528         }
2529
2530         [XmlIgnore]
2531         public bool FilterModified { get; set; }
2532
2533         public long[] BackupIds
2534         {
2535             get
2536             {
2537                 return _ids.ToArray();
2538             }
2539         }
2540
2541         public string TabName { get; set; }
2542
2543         public MyCommon.TabUsageType TabType
2544         {
2545             get
2546             {
2547                 return _tabType;
2548             }
2549             set
2550             {
2551                 _tabType = value;
2552                 if (this.IsInnerStorageTabType)
2553                 {
2554                     Posts = _innerPosts;
2555                 }
2556                 else
2557                 {
2558                     Posts = TabInformations.GetInstance().Posts;
2559                 }
2560                 _sorter.posts = Posts;
2561             }
2562         }
2563
2564         public bool IsInnerStorageTabType
2565         {
2566             get
2567             {
2568                 if (_tabType == MyCommon.TabUsageType.PublicSearch ||
2569                     _tabType == MyCommon.TabUsageType.DirectMessage ||
2570                     _tabType == MyCommon.TabUsageType.Lists ||
2571                     _tabType == MyCommon.TabUsageType.UserTimeline ||
2572                     _tabType == MyCommon.TabUsageType.Related)
2573                 {
2574                     return true;
2575                 }
2576                 else
2577                 {
2578                     return false;
2579                 }
2580             }
2581         }
2582     }
2583
2584     //ソート比較クラス:ID比較のみ
2585     public sealed class IdComparerClass : IComparer<long>
2586     {
2587         /// <summary>
2588         /// 比較する方法
2589         /// </summary>
2590         public enum ComparerMode
2591         {
2592             Id,
2593             Data,
2594             Name,
2595             Nickname,
2596             Source,
2597         }
2598
2599         private SortOrder _order;
2600         private ComparerMode _mode;
2601         private Dictionary<long, PostClass> _statuses;
2602         private Comparison<long> _CmpMethod;
2603
2604         /// <summary>
2605         /// 昇順か降順か Setの際は同時に比較関数の切り替えを行う
2606         /// </summary>
2607         public SortOrder Order
2608         {
2609             get
2610             {
2611                 return _order;
2612             }
2613             set
2614             {
2615                 _order = value;
2616                 SetCmpMethod(_mode, _order);
2617             }
2618         }
2619
2620         /// <summary>
2621         /// 並び替えの方法 Setの際は同時に比較関数の切り替えを行う
2622         /// </summary>
2623         public ComparerMode Mode
2624         {
2625             get
2626             {
2627                 return _mode;
2628             }
2629             set
2630             {
2631                 _mode = value;
2632                 SetCmpMethod(_mode, _order);
2633             }
2634         }
2635
2636         /// <summary>
2637         /// ListViewItemComparerクラスのコンストラクタ(引数付は未使用)
2638         /// </summary>
2639         /// <param name="col">並び替える列番号</param>
2640         /// <param name="ord">昇順か降順か</param>
2641         /// <param name="cmod">並び替えの方法</param>
2642
2643         public IdComparerClass()
2644         {
2645             _order = SortOrder.Ascending;
2646             _mode = ComparerMode.Id;
2647             SetCmpMethod(_mode, _order);
2648         }
2649
2650         public Dictionary<long, PostClass> posts
2651         {
2652             set
2653             {
2654                 _statuses = value;
2655             }
2656             get
2657             {
2658                 return _statuses;
2659             }
2660         }
2661
2662         // 指定したソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2663         public Comparison<long> CmpMethod(ComparerMode _sortmode, SortOrder _sortorder)
2664         {
2665             //get
2666             {
2667                 Comparison<long> _method = null;
2668                 if (_sortorder == SortOrder.Ascending)
2669                 {
2670                     // 昇順
2671                     switch (_sortmode)
2672                     {
2673                     case ComparerMode.Data:
2674                         _method = Compare_ModeData_Ascending;
2675                         break;
2676                     case ComparerMode.Id:
2677                         _method = Compare_ModeId_Ascending;
2678                         break;
2679                     case ComparerMode.Name:
2680                         _method = Compare_ModeName_Ascending;
2681                         break;
2682                     case ComparerMode.Nickname:
2683                         _method = Compare_ModeNickName_Ascending;
2684                         break;
2685                     case ComparerMode.Source:
2686                         _method = Compare_ModeSource_Ascending;
2687                         break;
2688                     }
2689                 }
2690                 else
2691                 {
2692                     // 降順
2693                     switch (_sortmode)
2694                     {
2695                     case ComparerMode.Data:
2696                         _method = Compare_ModeData_Descending;
2697                         break;
2698                     case ComparerMode.Id:
2699                         _method = Compare_ModeId_Descending;
2700                         break;
2701                     case ComparerMode.Name:
2702                         _method = Compare_ModeName_Descending;
2703                         break;
2704                     case ComparerMode.Nickname:
2705                         _method = Compare_ModeNickName_Descending;
2706                         break;
2707                     case ComparerMode.Source:
2708                         _method = Compare_ModeSource_Descending;
2709                         break;
2710                     }
2711                 }
2712                 return _method;
2713             }
2714         }
2715
2716         // ソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2717         // (overload 現在の使用中の比較関数のアドレスを返す)
2718         public Comparison<long> CmpMethod()
2719         {
2720             //get
2721             {
2722                 return _CmpMethod;
2723             }
2724         }
2725
2726         // ソートモードとソートオーダーに従い比較関数のアドレスを切り替え
2727         private void SetCmpMethod(ComparerMode mode, SortOrder order)
2728         {
2729             _CmpMethod = this.CmpMethod(mode, order);
2730         }
2731
2732         //xがyより小さいときはマイナスの数、大きいときはプラスの数、
2733         //同じときは0を返す (こちらは未使用 一応比較関数群呼び出しの形のまま残しておく)
2734         int IComparer<long>.Compare(long x, long y)
2735         {
2736             return _CmpMethod(x, y);
2737         }
2738
2739         // 比較用関数群 いずれもステータスIDの順序を考慮する
2740         // 本文比較 昇順
2741         public int Compare_ModeData_Ascending(long x, long y)
2742         {
2743             var result = string.Compare(_statuses[x].TextFromApi, _statuses[y].TextFromApi);
2744             if (result == 0) result = x.CompareTo(y);
2745             return result;
2746         }
2747
2748         // 本文比較 降順
2749         public int Compare_ModeData_Descending(long x, long y)
2750         {
2751             var result = string.Compare(_statuses[y].TextFromApi, _statuses[x].TextFromApi);
2752             if (result == 0) result = y.CompareTo(x);
2753             return result;
2754         }
2755
2756         // ステータスID比較 昇順
2757         public int Compare_ModeId_Ascending(long x, long y)
2758         {
2759             return x.CompareTo(y);
2760         }
2761
2762         // ステータスID比較 降順
2763         public int Compare_ModeId_Descending(long x, long y)
2764         {
2765             return y.CompareTo(x);
2766         }
2767
2768         // 表示名比較 昇順
2769         public int Compare_ModeName_Ascending(long x, long y)
2770         {
2771             var result = string.Compare(_statuses[x].ScreenName, _statuses[y].ScreenName);
2772             if (result == 0) result = x.CompareTo(y);
2773             return result;
2774         }
2775
2776         // 表示名比較 降順
2777         public int Compare_ModeName_Descending(long x, long y)
2778         {
2779             var result = string.Compare(_statuses[y].ScreenName, _statuses[x].ScreenName);
2780             if (result == 0) result = y.CompareTo(x);
2781             return result;
2782         }
2783
2784         // ユーザー名比較 昇順
2785         public int Compare_ModeNickName_Ascending(long x, long y)
2786         {
2787             var result = string.Compare(_statuses[x].Nickname, _statuses[y].Nickname);
2788             if (result == 0) result = x.CompareTo(y);
2789             return result;
2790         }
2791
2792         // ユーザー名比較 降順
2793         public int Compare_ModeNickName_Descending(long x, long y)
2794         {
2795             var result = string.Compare(_statuses[y].Nickname, _statuses[x].Nickname);
2796             if (result == 0) result = y.CompareTo(x);
2797             return result;
2798         }
2799
2800         // Source比較 昇順
2801         public int Compare_ModeSource_Ascending(long x, long y)
2802         {
2803             var result = string.Compare(_statuses[x].Source, _statuses[y].Source);
2804             if (result == 0) result = x.CompareTo(y);
2805             return result;
2806         }
2807
2808         // Source比較 降順
2809         public int Compare_ModeSource_Descending(long x, long y)
2810         {
2811             var result = string.Compare(_statuses[y].Source, _statuses[x].Source);
2812             if (result == 0) result = y.CompareTo(x);
2813             return result;
2814         }
2815     }
2816 }