OSDN Git Service

使用されていないローカル変数を削除 (CA1804)
[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 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 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 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 favTab = GetTabByType(MyCommon.TabUsageType.Favorites);
1080             foreach (var id in _addedIds)
1081             {
1082                 var post = _statuses[id];
1083                 var add = false;  //通知リスト追加フラグ
1084                 var mv = false;   //移動フラグ(Recent追加有無)
1085                 var rslt = MyCommon.HITRESULT.None;
1086                 post.IsExcludeReply = false;
1087                 foreach (var tab in _tabs.Values)
1088                 {
1089                     rslt = tab.AddFiltered(post);
1090                     if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
1091                     {
1092                         if (rslt == MyCommon.HITRESULT.CopyAndMark) post.IsMark = true; //マークあり
1093                         else if (rslt == MyCommon.HITRESULT.Move)
1094                         {
1095                             mv = true; //移動
1096                             post.IsMark = false;
1097                         }
1098                         if (tab.Notify) add = true; //通知あり
1099                         if (!string.IsNullOrEmpty(tab.SoundFile) && string.IsNullOrEmpty(_soundFile))
1100                         {
1101                             _soundFile = tab.SoundFile; //wavファイル(未設定の場合のみ)
1102                         }
1103                         post.FilterHit = true;
1104                     }
1105                     else
1106                     {
1107                         if (rslt == MyCommon.HITRESULT.Exclude && tab.TabType == MyCommon.TabUsageType.Mentions)
1108                         {
1109                             post.IsExcludeReply = true;
1110                         }
1111                         post.FilterHit = false;
1112                     }
1113                 }
1114                 if (!mv)  //移動されなかったらRecentに追加
1115                 {
1116                     homeTab.Add(post.StatusId, post.IsRead, true);
1117                     if (!string.IsNullOrEmpty(homeTab.SoundFile) && string.IsNullOrEmpty(_soundFile)) _soundFile = homeTab.SoundFile;
1118                     if (homeTab.Notify) add = true;
1119                 }
1120                 if (post.IsReply && !post.IsExcludeReply)    //除外ルール適用のないReplyならReplyタブに追加
1121                 {
1122                     replyTab.Add(post.StatusId, post.IsRead, true);
1123                     if (!string.IsNullOrEmpty(replyTab.SoundFile)) _soundFile = replyTab.SoundFile;
1124                     if (replyTab.Notify) add = true;
1125                 }
1126                 if (post.IsFav)    //Fav済み発言だったらFavoritesタブに追加
1127                 {
1128                     if (favTab.Contains(post.StatusId))
1129                     {
1130                         //取得済みなら非通知
1131                         //_soundFile = "";
1132                         add = false;
1133                     }
1134                     else
1135                     {
1136                         favTab.Add(post.StatusId, post.IsRead, true);
1137                         if (!string.IsNullOrEmpty(favTab.SoundFile) && string.IsNullOrEmpty(_soundFile)) _soundFile = favTab.SoundFile;
1138                         if (favTab.Notify) add = true;
1139                     }
1140                 }
1141                 if (add) _notifyPosts.Add(post);
1142             }
1143             foreach (var tb in _tabs.Values)
1144             {
1145                 if (tb.IsInnerStorageTabType)
1146                 {
1147                     if (tb.Notify)
1148                     {
1149                         if (tb.GetTemporaryCount() > 0)
1150                         {
1151                             foreach (var post in tb.GetTemporaryPosts())
1152                             {
1153                                 var exist = false;
1154                                 foreach (var npost in _notifyPosts)
1155                                 {
1156                                     if (npost.StatusId == post.StatusId)
1157                                     {
1158                                         exist = true;
1159                                         break;
1160                                     }
1161                                 }
1162                                 if (!exist) _notifyPosts.Add(post);
1163                             }
1164                             if (!string.IsNullOrEmpty(tb.SoundFile))
1165                             {
1166                                 if (tb.TabType == MyCommon.TabUsageType.DirectMessage || string.IsNullOrEmpty(_soundFile))
1167                                 {
1168                                     _soundFile = tb.SoundFile;
1169                                 }
1170                             }
1171                         }
1172                     }
1173                 }
1174             }
1175         }
1176
1177         public void AddPost(PostClass Item)
1178         {
1179             lock (LockObj)
1180             {
1181                 if (string.IsNullOrEmpty(Item.RelTabName))
1182                 {
1183                     if (!Item.IsDm)
1184                     {
1185                         PostClass status;
1186                         if (_statuses.TryGetValue(Item.StatusId, out status))
1187                         {
1188                             if (Item.IsFav)
1189                             {
1190                                 if (Item.RetweetedId == null)
1191                                 {
1192                                     status.IsFav = true;
1193                                 }
1194                                 else
1195                                 {
1196                                     Item.IsFav = false;
1197                                 }
1198                             }
1199                             else
1200                             {
1201                                 return;        //追加済みなら何もしない
1202                             }
1203                         }
1204                         else
1205                         {
1206                             if (Item.IsFav && Item.RetweetedId != null) Item.IsFav = false;
1207                             //既に持っている公式RTは捨てる
1208                             if (AppendSettingDialog.Instance.HideDuplicatedRetweets &&
1209                                 !Item.IsMe &&
1210                                 Item.RetweetedId != null &&
1211                                 this._retweets.TryGetValue(Item.RetweetedId.Value, out status) &&
1212                                 status.RetweetedCount > 0) return;
1213
1214                             if (BlockIds.Contains(Item.UserId))
1215                                 return;
1216
1217                             if (this.IsMuted(Item))
1218                                 return;
1219
1220                             _statuses.Add(Item.StatusId, Item);
1221                         }
1222                         if (Item.RetweetedId != null)
1223                         {
1224                             this.AddRetweet(Item);
1225                         }
1226                         if (Item.IsFav && _retweets.ContainsKey(Item.StatusId))
1227                         {
1228                             return;    //Fav済みのRetweet元発言は追加しない
1229                         }
1230                         if (_addedIds == null) _addedIds = new List<long>(); //タブ追加用IDコレクション準備
1231                         _addedIds.Add(Item.StatusId);
1232                     }
1233                     else
1234                     {
1235                         //DM
1236                         var tb = this.GetTabByType(MyCommon.TabUsageType.DirectMessage);
1237                         if (tb.Contains(Item.StatusId)) return;
1238                         tb.AddPostToInnerStorage(Item);
1239                     }
1240                 }
1241                 else
1242                 {
1243                     //公式検索、リスト、関連発言の場合
1244                     TabClass tb;
1245                     this.Tabs.TryGetValue(Item.RelTabName, out tb);
1246                     if (tb == null) return;
1247                     if (tb.Contains(Item.StatusId)) return;
1248                     //tb.Add(Item.StatusId, Item.IsRead, true);
1249                     tb.AddPostToInnerStorage(Item);
1250                 }
1251             }
1252         }
1253
1254         private bool IsMuted(PostClass post)
1255         {
1256             // Recent以外のツイートと、リプライはミュート対象外
1257             // 参照: https://support.twitter.com/articles/20171399-muting-users-on-twitter
1258             if (string.IsNullOrEmpty(post.RelTabName) || post.IsReply)
1259                 return false;
1260
1261             if (this.MuteUserIds.Contains(post.UserId))
1262                 return true;
1263
1264             if (post.RetweetedByUserId != null && this.MuteUserIds.Contains(post.RetweetedByUserId.Value))
1265                 return true;
1266
1267             return false;
1268         }
1269
1270         private void AddRetweet(PostClass item)
1271         {
1272             var retweetedId = item.RetweetedId.Value;
1273
1274             //true:追加、False:保持済み
1275             PostClass status;
1276             if (_retweets.TryGetValue(retweetedId, out status))
1277             {
1278                 status.RetweetedCount++;
1279                 if (status.RetweetedCount > 10)
1280                 {
1281                     status.RetweetedCount = 0;
1282                 }
1283                 return;
1284             }
1285
1286             _retweets.Add(
1287                         item.RetweetedId.Value,
1288                         new PostClass(
1289                             item.Nickname,
1290                             item.TextFromApi,
1291                             item.Text,
1292                             item.ImageUrl,
1293                             item.ScreenName,
1294                             item.CreatedAt,
1295                             item.RetweetedId.Value,
1296                             item.IsFav,
1297                             item.IsRead,
1298                             item.IsReply,
1299                             item.IsExcludeReply,
1300                             item.IsProtect,
1301                             item.IsOwl,
1302                             item.IsMark,
1303                             item.InReplyToUser,
1304                             item.InReplyToStatusId,
1305                             item.Source,
1306                             item.SourceHtml,
1307                             item.ReplyToList,
1308                             item.IsMe,
1309                             item.IsDm,
1310                             item.UserId,
1311                             item.FilterHit,
1312                             "",
1313                             null,
1314                             item.PostGeo
1315                         )
1316                     );
1317             _retweets[retweetedId].RetweetedCount++;
1318         }
1319
1320         public void SetReadAllTab(bool Read, string TabName, int Index)
1321         {
1322             //Read:true=既読へ false=未読へ
1323             var tb = _tabs[TabName];
1324
1325             if (tb.UnreadManage == false) return; //未読管理していなければ終了
1326
1327             var Id = tb.GetId(Index);
1328             if (Id < 0) return;
1329             PostClass post = tb.Posts[Id];
1330
1331             if (post.IsRead == Read) return; //状態変更なければ終了
1332
1333             post.IsRead = Read;
1334
1335             lock (LockUnread)
1336             {
1337                 if (Read)
1338                 {
1339                     tb.UnreadCount--;
1340                     this.SetNextUnreadId(Id, tb);  //次の未読セット
1341                     //他タブの最古未読IDはタブ切り替え時に。
1342                     if (tb.IsInnerStorageTabType)
1343                     {
1344                         //一般タブ
1345                         PostClass status;
1346                         if (_statuses.TryGetValue(Id, out status) && !status.IsRead)
1347                         {
1348                             foreach (var tab in _tabs.Values)
1349                             {
1350                                 if (tab.UnreadManage &&
1351                                     !tab.IsInnerStorageTabType &&
1352                                     tab.Contains(Id))
1353                                 {
1354                                     tab.UnreadCount--;
1355                                     if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1356                                 }
1357                             }
1358                             status.IsRead = true;
1359                         }
1360                     }
1361                     else
1362                     {
1363                         //一般タブ
1364                         foreach (var tab in _tabs.Values)
1365                         {
1366                             if (tab != tb &&
1367                                 tab.UnreadManage &&
1368                                 !tab.IsInnerStorageTabType &&
1369                                 tab.Contains(Id))
1370                             {
1371                                 tab.UnreadCount--;
1372                                 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1373                             }
1374                         }
1375                     }
1376                     //内部保存タブ
1377                     foreach (var tab in _tabs.Values)
1378                     {
1379                         if (tab != tb &&
1380                             tab.IsInnerStorageTabType &&
1381                             tab.Contains(Id))
1382                         {
1383                             var tPost = tab.Posts[Id];
1384                             if (!tPost.IsRead)
1385                             {
1386                                 if (tab.UnreadManage)
1387                                 {
1388                                     tab.UnreadCount--;
1389                                     if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1390                                 }
1391                                 tPost.IsRead = true;
1392                             }
1393                         }
1394                     }
1395                 }
1396                 else
1397                 {
1398                     tb.UnreadCount++;
1399                     //if (tb.OldestUnreadId > Id || tb.OldestUnreadId = -1) tb.OldestUnreadId = Id
1400                     if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1401                     if (tb.IsInnerStorageTabType)
1402                     {
1403                         //一般タブ
1404                         PostClass status;
1405                         if (_statuses.TryGetValue(Id, out status) && status.IsRead)
1406                         {
1407                             foreach (var tab in _tabs.Values)
1408                             {
1409                                 if (tab.UnreadManage &&
1410                                     !tab.IsInnerStorageTabType &&
1411                                     tab.Contains(Id))
1412                                 {
1413                                     tab.UnreadCount++;
1414                                     if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1415                                 }
1416                             }
1417                             status.IsRead = false;
1418                         }
1419                     }
1420                     else
1421                     {
1422                         //一般タブ
1423                         foreach (var tab in _tabs.Values)
1424                         {
1425                             if (tab != tb &&
1426                                 tab.UnreadManage &&
1427                                 !tab.IsInnerStorageTabType &&
1428                                 tab.Contains(Id))
1429                             {
1430                                 tab.UnreadCount++;
1431                                 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1432                             }
1433                         }
1434                     }
1435                     //内部保存タブ
1436                     foreach (var tab in _tabs.Values)
1437                     {
1438                         if (tab != tb &&
1439                             tab.IsInnerStorageTabType &&
1440                             tab.Contains(Id))
1441                         {
1442                             var tPost = tab.Posts[Id];
1443                             if (tPost.IsRead)
1444                             {
1445                                 if (tab.UnreadManage)
1446                                 {
1447                                     tab.UnreadCount++;
1448                                     if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1449                                 }
1450                                 tPost.IsRead = false;
1451                             }
1452                         }
1453                     }
1454                 }
1455             }
1456         }
1457
1458         // TODO: パフォーマンスを勘案して、戻すか決める
1459         public void SetRead(bool Read, string TabName, int Index)
1460         {
1461             //Read:true=既読へ false=未読へ
1462             var tb = _tabs[TabName];
1463
1464             if (tb.UnreadManage == false) return; //未読管理していなければ終了
1465
1466             var Id = tb.GetId(Index);
1467             if (Id < 0) return;
1468             PostClass post = tb.Posts[Id];
1469
1470             if (post.IsRead == Read) return; //状態変更なければ終了
1471
1472             post.IsRead = Read; //指定の状態に変更
1473
1474             lock (LockUnread)
1475             {
1476                 if (Read)
1477                 {
1478                     tb.UnreadCount--;
1479                     this.SetNextUnreadId(Id, tb);  //次の未読セット
1480                     //他タブの最古未読IDはタブ切り替え時に。
1481                     if (tb.IsInnerStorageTabType) return;
1482                     foreach (var tab in _tabs.Values)
1483                     {
1484                         if (tab != tb &&
1485                             tab.UnreadManage &&
1486                             !tab.IsInnerStorageTabType &&
1487                             tab.Contains(Id))
1488                         {
1489                             tab.UnreadCount--;
1490                             if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1491                         }
1492                     }
1493                 }
1494                 else
1495                 {
1496                     tb.UnreadCount++;
1497                     //if (tb.OldestUnreadId > Id || tb.OldestUnreadId == -1) tb.OldestUnreadId = Id;
1498                     if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1499                     if (tb.IsInnerStorageTabType) return;
1500                     foreach (var tab in _tabs.Values)
1501                     {
1502                         if (tab != tb &&
1503                             tab.UnreadManage &&
1504                             !tab.IsInnerStorageTabType &&
1505                             tab.Contains(Id))
1506                         {
1507                             tab.UnreadCount++;
1508                             if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1509                         }
1510                     }
1511                 }
1512             }
1513         }
1514
1515         public void SetRead()
1516         {
1517             var tb = GetTabByType(MyCommon.TabUsageType.Home);
1518             if (tb.UnreadManage == false) return;
1519
1520             lock (LockObj)
1521             {
1522                 for (int i = 0; i < tb.AllCount - 1; i++)
1523                 {
1524                     var id = tb.GetId(i);
1525                     if (id < 0) return;
1526                     var tPost = _statuses[id];
1527                     if (!tPost.IsReply &&
1528                         !tPost.IsRead &&
1529                         !tPost.FilterHit)
1530                     {
1531                         tPost.IsRead = true;
1532                         this.SetNextUnreadId(id, tb);  //次の未読セット
1533                         foreach (var tab in _tabs.Values)
1534                         {
1535                             if (tab.UnreadManage &&
1536                                 tab.Contains(id))
1537                             {
1538                                 tab.UnreadCount--;
1539                                 if (tab.OldestUnreadId == id) tab.OldestUnreadId = -1;
1540                             }
1541                         }
1542                     }
1543                 }
1544             }
1545         }
1546
1547         public PostClass this[long ID]
1548         {
1549             get
1550             {
1551                 PostClass status;
1552                 return _statuses.TryGetValue(ID, out status)
1553                     ? status
1554                     : this.GetTabsInnerStorageType()
1555                           .Where(t => t.Contains(ID))
1556                           .Select(t => t.Posts[ID]).FirstOrDefault();
1557             }
1558         }
1559
1560         public PostClass this[string TabName, int Index]
1561         {
1562             get
1563             {
1564                 TabClass tb;
1565                 if (!_tabs.TryGetValue(TabName, out tb)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1566                 return tb[Index];
1567             }
1568         }
1569
1570         public PostClass[] this[string TabName, int StartIndex, int EndIndex]
1571         {
1572             get
1573             {
1574                 TabClass tb;
1575                 if (!_tabs.TryGetValue(TabName, out tb)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1576                 return tb[StartIndex, EndIndex];
1577             }
1578         }
1579
1580         //public ReadOnly int ItemCount
1581         //{
1582         //    get
1583         //    {
1584         //        lock (LockObj)
1585         //    {
1586         //            return _statuses.Count   //DM,公式検索は除く
1587         //        }
1588         //    }
1589         //}
1590
1591         public bool ContainsKey(long Id)
1592         {
1593             //DM,公式検索は非対応
1594             lock (LockObj)
1595             {
1596                 return _statuses.ContainsKey(Id);
1597             }
1598         }
1599
1600         public bool ContainsKey(long Id, string TabName)
1601         {
1602             //DM,公式検索は対応版
1603             lock (LockObj)
1604             {
1605                 TabClass tab;
1606                 return _tabs.TryGetValue(TabName, out tab) && tab.Contains(Id);
1607             }
1608         }
1609
1610         public void SetUnreadManage(bool Manage)
1611         {
1612             if (Manage)
1613             {
1614                 foreach (var tab in _tabs.Values)
1615                 {
1616                     if (tab.UnreadManage)
1617                     {
1618                         lock (LockUnread)
1619                         {
1620                             var cnt = 0;
1621                             var oldest = long.MaxValue;
1622                             Dictionary<long, PostClass> posts = tab.Posts;
1623                             foreach (var id in tab.BackupIds)
1624                             {
1625                                 if (!posts[id].IsRead)
1626                                 {
1627                                     cnt++;
1628                                     if (oldest > id) oldest = id;
1629                                 }
1630                             }
1631                             if (oldest == long.MaxValue) oldest = -1;
1632                             tab.OldestUnreadId = oldest;
1633                             tab.UnreadCount = cnt;
1634                         }
1635                     }
1636                 }
1637             }
1638             else
1639             {
1640                 foreach (var tab in _tabs.Values)
1641                 {
1642                     if (tab.UnreadManage && tab.UnreadCount > 0)
1643                     {
1644                         lock (LockUnread)
1645                         {
1646                             tab.UnreadCount = 0;
1647                             tab.OldestUnreadId = -1;
1648                         }
1649                     }
1650                 }
1651             }
1652         }
1653
1654         public void RenameTab(string Original, string NewName)
1655         {
1656             var tb = _tabs[Original];
1657             _tabs.Remove(Original);
1658             tb.TabName = NewName;
1659             _tabs.Add(NewName, tb);
1660         }
1661
1662         public void FilterAll()
1663         {
1664             lock (LockObj)
1665             {
1666                 var tbr = GetTabByType(MyCommon.TabUsageType.Home);
1667                 var replyTab = GetTabByType(MyCommon.TabUsageType.Mentions);
1668                 foreach (var tb in _tabs.Values.ToArray())
1669                 {
1670                     if (tb.FilterModified)
1671                     {
1672                         tb.FilterModified = false;
1673                         var orgIds = tb.BackupIds;
1674                         tb.ClearIDs();
1675                         //////////////フィルター前のIDsを退避。どのタブにも含まれないidはrecentへ追加
1676                         //////////////moveフィルターにヒットした際、recentに該当あればrecentから削除
1677                         foreach (var post in _statuses.Values)
1678                         {
1679                             if (post.IsDm) continue;
1680                             var rslt = MyCommon.HITRESULT.None;
1681                             rslt = tb.AddFiltered(post);
1682                             switch (rslt)
1683                             {
1684                                 case MyCommon.HITRESULT.CopyAndMark:
1685                                 post.IsMark = true; //マークあり
1686                                 post.FilterHit = true;
1687                                 break;
1688                                 case MyCommon.HITRESULT.Move:
1689                                 tbr.Remove(post.StatusId, post.IsRead);
1690                                 post.IsMark = false;
1691                                 post.FilterHit = true;
1692                                 break;
1693                             case MyCommon.HITRESULT.Copy:
1694                                 post.IsMark = false;
1695                                 post.FilterHit = true;
1696                                 break;
1697                             case MyCommon.HITRESULT.Exclude:
1698                                 if (tb.TabName == replyTab.TabName && post.IsReply) post.IsExcludeReply = true;
1699                                 if (post.IsFav) GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, true);
1700                                 post.FilterHit = false;
1701                                 break;
1702                             case MyCommon.HITRESULT.None:
1703                                 if (tb.TabName == replyTab.TabName && post.IsReply) replyTab.Add(post.StatusId, post.IsRead, true);
1704                                 if (post.IsFav) GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, true);
1705                                 post.FilterHit = false;
1706                                 break;
1707                             }
1708                         }
1709                         tb.AddSubmit();  //振分確定
1710                         foreach (var id in orgIds)
1711                         {
1712                             var hit = false;
1713                             foreach (var tbTemp in _tabs.Values.ToArray())
1714                             {
1715                                 if (tbTemp.Contains(id))
1716                                 {
1717                                     hit = true;
1718                                     break;
1719                                 }
1720                             }
1721                             if (!hit) tbr.Add(id, _statuses[id].IsRead, false);
1722                         }
1723                     }
1724                 }
1725                 this.SortPosts();
1726             }
1727         }
1728
1729         public long[] GetId(string TabName, ListView.SelectedIndexCollection IndexCollection)
1730         {
1731             return _tabs[TabName].GetId(IndexCollection);
1732         }
1733
1734         public long GetId(string TabName, int Index)
1735         {
1736             return _tabs[TabName].GetId(Index);
1737         }
1738
1739         public int[] IndexOf(string TabName, long[] Ids)
1740         {
1741             return _tabs[TabName].IndexOf(Ids);
1742         }
1743
1744         public int IndexOf(string TabName, long Id)
1745         {
1746             return _tabs[TabName].IndexOf(Id);
1747         }
1748
1749         public void ClearTabIds(string TabName)
1750         {
1751             //不要なPostを削除
1752             lock (LockObj)
1753             {
1754                 var tb = _tabs[TabName];
1755                 if (!tb.IsInnerStorageTabType)
1756                 {
1757                     foreach (var Id in tb.BackupIds)
1758                     {
1759                         var Hit = false;
1760                         foreach (var tab in _tabs.Values)
1761                         {
1762                             if (tab.Contains(Id))
1763                             {
1764                                 Hit = true;
1765                                 break;
1766                             }
1767                         }
1768                         if (!Hit) _statuses.Remove(Id);
1769                     }
1770                 }
1771
1772                 //指定タブをクリア
1773                 tb.ClearIDs();
1774             }
1775         }
1776
1777         public void SetTabUnreadManage(string TabName, bool Manage)
1778         {
1779             var tb = _tabs[TabName];
1780             lock (LockUnread)
1781             {
1782                 if (Manage)
1783                 {
1784                     var cnt = 0;
1785                     var oldest = long.MaxValue;
1786                     Dictionary<long, PostClass> posts = tb.Posts;
1787                     foreach (var id in tb.BackupIds)
1788                     {
1789                         if (!posts[id].IsRead)
1790                         {
1791                             cnt++;
1792                             if (oldest > id) oldest = id;
1793                         }
1794                     }
1795                     if (oldest == long.MaxValue) oldest = -1;
1796                     tb.OldestUnreadId = oldest;
1797                     tb.UnreadCount = cnt;
1798                 }
1799                 else
1800                 {
1801                     tb.OldestUnreadId = -1;
1802                     tb.UnreadCount = 0;
1803                 }
1804             }
1805             tb.UnreadManage = Manage;
1806         }
1807
1808         public void RefreshOwl(List<long> follower)
1809         {
1810             lock (LockObj)
1811             {
1812                 if (follower.Count > 0)
1813                 {
1814                     foreach (var post in _statuses.Values)
1815                     {
1816                         //if (post.UserId = 0 || post.IsDm) Continue For
1817                         if (post.IsMe)
1818                         {
1819                             post.IsOwl = false;
1820                         }
1821                         else
1822                         {
1823                             post.IsOwl = !follower.Contains(post.UserId);
1824                         }
1825                     }
1826                 }
1827                 else
1828                 {
1829                     foreach (var post in _statuses.Values)
1830                     {
1831                         post.IsOwl = false;
1832                     }
1833                 }
1834             }
1835         }
1836
1837         public TabClass GetTabByType(MyCommon.TabUsageType tabType)
1838         {
1839             //Home,Mentions,DM,Favは1つに制限する
1840             //その他のタイプを指定されたら、最初に合致したものを返す
1841             //合致しなければnullを返す
1842             lock (LockObj)
1843             {
1844                 foreach (var tab in _tabs.Values)
1845                 {
1846                     if (tab.TabType == tabType) return tab;
1847                 }
1848                 return null;
1849             }
1850         }
1851
1852         public List<TabClass> GetTabsByType(MyCommon.TabUsageType tabType)
1853         {
1854             //合致したタブをListで返す
1855             //合致しなければ空のListを返す
1856             lock (LockObj)
1857             {
1858                 var tbs = new List<TabClass>();
1859                 foreach (var tb in _tabs.Values)
1860                 {
1861                     if ((tabType & tb.TabType) == tb.TabType) tbs.Add(tb);
1862                 }
1863                 return tbs;
1864             }
1865         }
1866
1867         public List<TabClass> GetTabsInnerStorageType()
1868         {
1869             //合致したタブをListで返す
1870             //合致しなければ空のListを返す
1871             lock (LockObj)
1872             {
1873                 var tbs = new List<TabClass>();
1874                 foreach (var tb in _tabs.Values)
1875                 {
1876                     if (tb.IsInnerStorageTabType) tbs.Add(tb);
1877                 }
1878                 return tbs;
1879             }
1880         }
1881
1882         public TabClass GetTabByName(string tabName)
1883         {
1884             lock (LockObj)
1885             {
1886                 TabClass tab;
1887                 return _tabs.TryGetValue(tabName, out tab)
1888                     ? tab
1889                     : null;
1890             }
1891         }
1892
1893         // デフォルトタブの判定処理
1894         public bool IsDefaultTab(string tabName)
1895         {
1896             TabClass tab;
1897             if (tabName != null &&
1898                _tabs.TryGetValue(tabName, out tab) &&
1899                (tab.TabType == MyCommon.TabUsageType.Home ||
1900                tab.TabType == MyCommon.TabUsageType.Mentions ||
1901                tab.TabType == MyCommon.TabUsageType.DirectMessage ||
1902                tab.TabType == MyCommon.TabUsageType.Favorites))
1903             {
1904                 return true;
1905             }
1906
1907             return false;
1908         }
1909
1910         //振り分け可能タブの判定処理
1911         public bool IsDistributableTab(string tabName)
1912         {
1913             TabClass tab;
1914             if (tabName != null &&
1915                 _tabs.TryGetValue(tabName, out tab) &&
1916                 (tab.TabType == MyCommon.TabUsageType.Mentions ||
1917                 tab.TabType == MyCommon.TabUsageType.UserDefined))
1918             {
1919                 return true;
1920             }
1921
1922             return false;
1923         }
1924
1925         public string GetUniqueTabName()
1926         {
1927             var tabNameTemp = "MyTab" + (_tabs.Count + 1).ToString();
1928             for (int i = 2; i <= 100; i++)
1929             {
1930                 if (_tabs.ContainsKey(tabNameTemp))
1931                 {
1932                     tabNameTemp = "MyTab" + (_tabs.Count + i).ToString();
1933                 }
1934                 else
1935                 {
1936                     break;
1937                 }
1938             }
1939             return tabNameTemp;
1940         }
1941
1942         public Dictionary<long, PostClass> Posts
1943         {
1944             get
1945             {
1946                 return _statuses;
1947             }
1948         }
1949     }
1950
1951     [Serializable]
1952     public sealed class TabClass
1953     {
1954         private bool _unreadManage = false;
1955         private List<PostFilterRule> _filters;
1956         private int _unreadCount = 0;
1957         private List<long> _ids;
1958         private List<TemporaryId> _tmpIds = new List<TemporaryId>();
1959         private MyCommon.TabUsageType _tabType = MyCommon.TabUsageType.Undefined;
1960
1961         [NonSerialized]
1962         private IdComparerClass _sorter = new IdComparerClass();
1963
1964         private readonly object _lockObj = new object();
1965
1966         public string User { get; set; }
1967
1968 #region "検索"
1969         //Search query
1970         private string _searchLang = "";
1971         private string _searchWords = "";
1972         private string _nextPageQuery = "";
1973
1974         public string SearchLang
1975         {
1976             get
1977             {
1978                 return _searchLang;
1979             }
1980             set
1981             {
1982                 SinceId = 0;
1983                 _searchLang = value;
1984             }
1985         }
1986         public string SearchWords
1987         {
1988             get
1989             {
1990                 return _searchWords;
1991             }
1992             set
1993             {
1994                 SinceId = 0;
1995                 _searchWords = value.Trim();
1996             }
1997         }
1998
1999         public string NextPageQuery
2000         {
2001             get
2002             {
2003                 return _nextPageQuery;
2004             }
2005             set
2006             {
2007                 _nextPageQuery = value;
2008             }
2009         }
2010
2011         public int GetSearchPage(int count)
2012         {
2013             return ((_ids.Count / count) + 1);
2014         }
2015         private Dictionary<string, string> _beforeQuery = new Dictionary<string, string>();
2016         public void SaveQuery(bool more)
2017         {
2018             var qry = new Dictionary<string, string>();
2019             if (string.IsNullOrEmpty(_searchWords))
2020             {
2021                 _beforeQuery = qry;
2022                 return;
2023             }
2024             qry.Add("q", _searchWords);
2025             if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2026             _beforeQuery = qry;
2027         }
2028
2029         public bool IsQueryChanged()
2030         {
2031             var qry = new Dictionary<string, string>();
2032             if (!string.IsNullOrEmpty(_searchWords))
2033             {
2034                 qry.Add("q", _searchWords);
2035                 if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2036             }
2037             if (qry.Count != _beforeQuery.Count) return true;
2038
2039             foreach (var kvp in qry)
2040             {
2041                 if (!_beforeQuery.ContainsKey(kvp.Key) || _beforeQuery[kvp.Key] != kvp.Value)
2042                 {
2043                     return true;
2044                 }
2045             }
2046             return false;
2047         }
2048 #endregion
2049
2050 #region "リスト"
2051         [NonSerialized]
2052         private ListElement _listInfo;
2053         public ListElement ListInfo
2054         {
2055             get
2056             {
2057                 return _listInfo;
2058             }
2059             set
2060             {
2061                 _listInfo = value;
2062             }
2063         }
2064 #endregion
2065
2066         [XmlIgnore]
2067         public PostClass RelationTargetPost { get; set; }
2068
2069         [XmlIgnore]
2070         public long OldestId = long.MaxValue;
2071
2072         [XmlIgnore]
2073         public long SinceId { get; set; }
2074
2075         [XmlIgnore]
2076         public Dictionary<long, PostClass> Posts { get; private set; }
2077
2078         private Dictionary<long, PostClass> _innerPosts;
2079
2080         public PostClass[] GetTemporaryPosts()
2081         {
2082             var tempPosts = new List<PostClass>();
2083             if (_tmpIds.Count == 0) return tempPosts.ToArray();
2084             foreach (var tempId in _tmpIds)
2085             {
2086                 tempPosts.Add(Posts[tempId.Id]);
2087             }
2088             return tempPosts.ToArray();
2089         }
2090
2091         public int GetTemporaryCount()
2092         {
2093             return _tmpIds.Count;
2094         }
2095
2096         private struct TemporaryId
2097         {
2098             public long Id;
2099             public bool Read;
2100
2101             public TemporaryId(long argId, bool argRead)
2102             {
2103                 Id = argId;
2104                 Read = argRead;
2105             }
2106         }
2107
2108         public TabClass()
2109         {
2110             _innerPosts = new Dictionary<long, PostClass>();
2111             Posts = _innerPosts;
2112             SoundFile = "";
2113             OldestUnreadId = -1;
2114             TabName = "";
2115             _filters = new List<PostFilterRule>();
2116             Protected = false;
2117             Notify = true;
2118             SoundFile = "";
2119             _unreadManage = true;
2120             _ids = new List<long>();
2121             this.OldestUnreadId = -1;
2122             _tabType = MyCommon.TabUsageType.Undefined;
2123             _listInfo = null;
2124         }
2125
2126         public TabClass(string TabName, MyCommon.TabUsageType TabType, ListElement list) : this()
2127         {
2128             this.TabName = TabName;
2129             this.TabType = TabType;
2130             this.ListInfo = list;
2131         }
2132
2133         public void Sort()
2134         {
2135             if (_sorter.Mode == IdComparerClass.ComparerMode.Id)
2136             {
2137                 _ids.Sort(_sorter.CmpMethod());
2138                 return;
2139             }
2140             long[] ar = null;
2141             if (_sorter.Order == SortOrder.Ascending)
2142             {
2143                 switch (_sorter.Mode)
2144                 {
2145                     case IdComparerClass.ComparerMode.Data:
2146                         ar = _ids.OrderBy(n => _sorter.posts[n].TextFromApi).ToArray();
2147                         break;
2148                     case IdComparerClass.ComparerMode.Name:
2149                         ar = _ids.OrderBy(n => _sorter.posts[n].ScreenName).ToArray();
2150                         break;
2151                     case IdComparerClass.ComparerMode.Nickname:
2152                         ar = _ids.OrderBy(n => _sorter.posts[n].Nickname).ToArray();
2153                         break;
2154                     case IdComparerClass.ComparerMode.Source:
2155                         ar = _ids.OrderBy(n => _sorter.posts[n].Source).ToArray();
2156                         break;
2157                 }
2158             }
2159             else
2160             {
2161                 switch (_sorter.Mode)
2162                 {
2163                     case IdComparerClass.ComparerMode.Data:
2164                         ar = _ids.OrderByDescending(n => _sorter.posts[n].TextFromApi).ToArray();
2165                         break;
2166                     case IdComparerClass.ComparerMode.Name:
2167                         ar = _ids.OrderByDescending(n => _sorter.posts[n].ScreenName).ToArray();
2168                         break;
2169                     case IdComparerClass.ComparerMode.Nickname:
2170                         ar = _ids.OrderByDescending(n => _sorter.posts[n].Nickname).ToArray();
2171                         break;
2172                     case IdComparerClass.ComparerMode.Source:
2173                         ar = _ids.OrderByDescending(n => _sorter.posts[n].Source).ToArray();
2174                         break;
2175                 }
2176             }
2177             _ids = new List<long>(ar);
2178         }
2179
2180         public IdComparerClass Sorter
2181         {
2182             get
2183             {
2184                 return _sorter;
2185             }
2186         }
2187
2188         //無条件に追加
2189         private void Add(long ID, bool Read)
2190         {
2191             if (this._ids.Contains(ID)) return;
2192
2193             if (this.Sorter.Mode == IdComparerClass.ComparerMode.Id)
2194             {
2195                 if (this.Sorter.Order == SortOrder.Ascending)
2196                 {
2197                     this._ids.Add(ID);
2198                 }
2199                 else
2200                 {
2201                     this._ids.Insert(0, ID);
2202                 }
2203             }
2204             else
2205             {
2206                 this._ids.Add(ID);
2207             }
2208
2209             if (!Read && this._unreadManage)
2210             {
2211                 this._unreadCount++;
2212                 if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2213                 //if (this.OldestUnreadId == -1)
2214                 //{
2215                 //    this.OldestUnreadId = ID;
2216                 //}
2217                 //else
2218                 //{
2219                 //    if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2220                 //}
2221             }
2222         }
2223
2224         public void Add(long ID, bool Read, bool Temporary)
2225         {
2226             if (!Temporary)
2227             {
2228                 this.Add(ID, Read);
2229             }
2230             else
2231             {
2232                 _tmpIds.Add(new TemporaryId(ID, Read));
2233             }
2234         }
2235
2236         //フィルタに合致したら追加
2237         public MyCommon.HITRESULT AddFiltered(PostClass post)
2238         {
2239             if (this.IsInnerStorageTabType) return MyCommon.HITRESULT.None;
2240
2241             var rslt = MyCommon.HITRESULT.None;
2242             //全フィルタ評価(優先順位あり)
2243             lock (this._lockObj)
2244             {
2245                 foreach (var ft in _filters)
2246                 {
2247                     try
2248                     {
2249                         switch (ft.ExecFilter(post))   //フィルタクラスでヒット判定
2250                         {
2251                             case MyCommon.HITRESULT.None:
2252                                 break;
2253                             case MyCommon.HITRESULT.Copy:
2254                                 if (rslt != MyCommon.HITRESULT.CopyAndMark) rslt = MyCommon.HITRESULT.Copy;
2255                                 break;
2256                             case MyCommon.HITRESULT.CopyAndMark:
2257                                 rslt = MyCommon.HITRESULT.CopyAndMark;
2258                                 break;
2259                             case MyCommon.HITRESULT.Move:
2260                                 rslt = MyCommon.HITRESULT.Move;
2261                                 break;
2262                             case MyCommon.HITRESULT.Exclude:
2263                                 rslt = MyCommon.HITRESULT.Exclude;
2264                                 goto exit_for;
2265                         }
2266                     }
2267                     catch (NullReferenceException)
2268                     {
2269                         // ExecFilterでNullRef出る場合あり。暫定対応
2270                         MyCommon.TraceOut("ExecFilterでNullRef: " + ft.ToString());
2271                         rslt = MyCommon.HITRESULT.None;
2272                     }
2273                 }
2274             exit_for:
2275                 ;
2276             }
2277
2278             if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
2279             {
2280                 _tmpIds.Add(new TemporaryId(post.StatusId, post.IsRead));
2281             }
2282
2283             return rslt; //マーク付けは呼び出し元で行うこと
2284         }
2285
2286         //検索結果の追加
2287         public void AddPostToInnerStorage(PostClass Post)
2288         {
2289             if (_innerPosts.ContainsKey(Post.StatusId)) return;
2290             _innerPosts.Add(Post.StatusId, Post);
2291             _tmpIds.Add(new TemporaryId(Post.StatusId, Post.IsRead));
2292         }
2293
2294         public void AddSubmit(ref bool isMentionIncluded)
2295         {
2296             if (_tmpIds.Count == 0) return;
2297             _tmpIds.Sort((x, y) => x.Id.CompareTo(y.Id));
2298             foreach (var tId in _tmpIds)
2299             {
2300                 if (this.TabType == MyCommon.TabUsageType.Mentions && TabInformations.GetInstance()[tId.Id].IsReply) isMentionIncluded = true;
2301                 this.Add(tId.Id, tId.Read);
2302             }
2303             _tmpIds.Clear();
2304         }
2305
2306         public void AddSubmit()
2307         {
2308             bool mention = false;
2309             AddSubmit(ref mention);
2310         }
2311
2312         public void Remove(long Id)
2313         {
2314             if (!this._ids.Contains(Id)) return;
2315             this._ids.Remove(Id);
2316             if (this.IsInnerStorageTabType) _innerPosts.Remove(Id);
2317         }
2318
2319         public void Remove(long Id, bool Read)
2320         {
2321             if (!this._ids.Contains(Id)) return;
2322
2323             if (!Read && this._unreadManage)
2324             {
2325                 this._unreadCount--;
2326                 this.OldestUnreadId = -1;
2327             }
2328
2329             this._ids.Remove(Id);
2330             if (this.IsInnerStorageTabType) _innerPosts.Remove(Id);
2331         }
2332
2333         public bool UnreadManage
2334         {
2335             get
2336             {
2337                 return _unreadManage;
2338             }
2339             set
2340             {
2341                 this._unreadManage = value;
2342                 if (!value)
2343                 {
2344                     this.OldestUnreadId = -1;
2345                     this._unreadCount = 0;
2346                 }
2347             }
2348         }
2349
2350         // v1.0.5で「タブを固定(Locked)」から「タブを保護(Protected)」に名称変更
2351         [XmlElement(ElementName = "Locked")]
2352         public bool Protected { get; set; }
2353
2354         public bool Notify { get; set; }
2355
2356         public string SoundFile { get; set; }
2357
2358         [XmlIgnore]
2359         public long OldestUnreadId { get; set; }
2360
2361         [XmlIgnore]
2362         public int UnreadCount
2363         {
2364             get
2365             {
2366                 return this.UnreadManage && AppendSettingDialog.Instance.UnreadManage ? _unreadCount : 0;
2367             }
2368             set
2369             {
2370                 if (value < 0) value = 0;
2371                 _unreadCount = value;
2372             }
2373         }
2374
2375         public int AllCount
2376         {
2377             get
2378             {
2379                 return this._ids.Count;
2380             }
2381         }
2382
2383         public PostFilterRule[] GetFilters()
2384         {
2385             lock (this._lockObj)
2386             {
2387                 return _filters.ToArray();
2388             }
2389         }
2390
2391         public void RemoveFilter(PostFilterRule filter)
2392         {
2393             lock (this._lockObj)
2394             {
2395                 _filters.Remove(filter);
2396                 this.FilterModified = true;
2397             }
2398         }
2399
2400         public bool AddFilter(PostFilterRule filter)
2401         {
2402             lock (this._lockObj)
2403             {
2404                 if (_filters.Contains(filter)) return false;
2405                 _filters.Add(filter);
2406                 this.FilterModified = true;
2407                 return true;
2408             }
2409         }
2410
2411         public void EditFilter(PostFilterRule original, PostFilterRule modified)
2412         {
2413             original.FilterBody = modified.FilterBody;
2414             original.FilterName = modified.FilterName;
2415             original.UseNameField = modified.UseNameField;
2416             original.FilterByUrl = modified.FilterByUrl;
2417             original.UseRegex = modified.UseRegex;
2418             original.CaseSensitive = modified.CaseSensitive;
2419             original.FilterRt = modified.FilterRt;
2420             original.UseLambda = modified.UseLambda;
2421             original.FilterSource = modified.FilterSource;
2422             original.ExFilterBody = modified.ExFilterBody;
2423             original.ExFilterName = modified.ExFilterName;
2424             original.ExUseNameField = modified.ExUseNameField;
2425             original.ExFilterByUrl = modified.ExFilterByUrl;
2426             original.ExUseRegex = modified.ExUseRegex;
2427             original.ExCaseSensitive = modified.ExCaseSensitive;
2428             original.ExFilterRt = modified.ExFilterRt;
2429             original.ExUseLambda = modified.ExUseLambda;
2430             original.ExFilterSource = modified.ExFilterSource;
2431             original.MoveMatches = modified.MoveMatches;
2432             original.MarkMatches = modified.MarkMatches;
2433             this.FilterModified = true;
2434         }
2435
2436         [XmlIgnore]
2437         public List<PostFilterRule> Filters
2438         {
2439             get
2440             {
2441                 lock (this._lockObj)
2442                 {
2443                     return _filters;
2444                 }
2445             }
2446             set
2447             {
2448                 lock (this._lockObj)
2449                 {
2450                     _filters = value;
2451                 }
2452             }
2453         }
2454
2455         public PostFilterRule[] FilterArray
2456         {
2457             get
2458             {
2459                 lock (this._lockObj)
2460                 {
2461                     return _filters.ToArray();
2462                 }
2463             }
2464             set
2465             {
2466                 lock (this._lockObj)
2467                 {
2468                     foreach (var filters in value)
2469                     {
2470                         _filters.Add(filters);
2471                     }
2472                 }
2473             }
2474         }
2475         public bool Contains(long ID)
2476         {
2477             return _ids.Contains(ID);
2478         }
2479
2480         public void ClearIDs()
2481         {
2482             _ids.Clear();
2483             _tmpIds.Clear();
2484             _unreadCount = 0;
2485             this.OldestUnreadId = -1;
2486             _innerPosts.Clear();
2487         }
2488
2489         public PostClass this[int Index]
2490         {
2491             get
2492             {
2493                 var id = GetId(Index);
2494                 if (id < 0) throw new ArgumentException("Index can't find. Index=" + Index.ToString() + "/TabName=" + TabName);
2495                 return Posts[id];
2496             }
2497         }
2498
2499         public PostClass[] this[int StartIndex, int EndIndex]
2500         {
2501             get
2502             {
2503                 var length = EndIndex - StartIndex + 1;
2504                 var posts = new PostClass[length];
2505                 for (int i = 0; i < length; i++)
2506                 {
2507                     posts[i] = Posts[GetId(StartIndex + i)];
2508                 }
2509                 return posts;
2510             }
2511         }
2512
2513         public long[] GetId(ListView.SelectedIndexCollection IndexCollection)
2514         {
2515             if (IndexCollection.Count == 0) return null;
2516
2517             var Ids = new long[IndexCollection.Count];
2518             for (int i = 0; i < Ids.Length; i++)
2519             {
2520                 Ids[i] = GetId(IndexCollection[i]);
2521             }
2522             return Ids;
2523         }
2524
2525         public long GetId(int Index)
2526         {
2527             return Index < _ids.Count ? _ids[Index] : -1;
2528         }
2529
2530         public int[] IndexOf(long[] Ids)
2531         {
2532             if (Ids == null) return null;
2533             var idx = new int[Ids.Length];
2534             for (int i = 0; i < Ids.Length; i++)
2535             {
2536                 idx[i] = IndexOf(Ids[i]);
2537             }
2538             return idx;
2539         }
2540
2541         public int IndexOf(long ID)
2542         {
2543             return _ids.IndexOf(ID);
2544         }
2545
2546         [XmlIgnore]
2547         public bool FilterModified { get; set; }
2548
2549         public long[] BackupIds
2550         {
2551             get
2552             {
2553                 return _ids.ToArray();
2554             }
2555         }
2556
2557         public string TabName { get; set; }
2558
2559         public MyCommon.TabUsageType TabType
2560         {
2561             get
2562             {
2563                 return _tabType;
2564             }
2565             set
2566             {
2567                 _tabType = value;
2568                 if (this.IsInnerStorageTabType)
2569                 {
2570                     Posts = _innerPosts;
2571                 }
2572                 else
2573                 {
2574                     Posts = TabInformations.GetInstance().Posts;
2575                 }
2576                 _sorter.posts = Posts;
2577             }
2578         }
2579
2580         public bool IsInnerStorageTabType
2581         {
2582             get
2583             {
2584                 if (_tabType == MyCommon.TabUsageType.PublicSearch ||
2585                     _tabType == MyCommon.TabUsageType.DirectMessage ||
2586                     _tabType == MyCommon.TabUsageType.Lists ||
2587                     _tabType == MyCommon.TabUsageType.UserTimeline ||
2588                     _tabType == MyCommon.TabUsageType.Related)
2589                 {
2590                     return true;
2591                 }
2592                 else
2593                 {
2594                     return false;
2595                 }
2596             }
2597         }
2598     }
2599
2600     //ソート比較クラス:ID比較のみ
2601     public sealed class IdComparerClass : IComparer<long>
2602     {
2603         /// <summary>
2604         /// 比較する方法
2605         /// </summary>
2606         public enum ComparerMode
2607         {
2608             Id,
2609             Data,
2610             Name,
2611             Nickname,
2612             Source,
2613         }
2614
2615         private SortOrder _order;
2616         private ComparerMode _mode;
2617         private Dictionary<long, PostClass> _statuses;
2618         private Comparison<long> _CmpMethod;
2619
2620         /// <summary>
2621         /// 昇順か降順か Setの際は同時に比較関数の切り替えを行う
2622         /// </summary>
2623         public SortOrder Order
2624         {
2625             get
2626             {
2627                 return _order;
2628             }
2629             set
2630             {
2631                 _order = value;
2632                 SetCmpMethod(_mode, _order);
2633             }
2634         }
2635
2636         /// <summary>
2637         /// 並び替えの方法 Setの際は同時に比較関数の切り替えを行う
2638         /// </summary>
2639         public ComparerMode Mode
2640         {
2641             get
2642             {
2643                 return _mode;
2644             }
2645             set
2646             {
2647                 _mode = value;
2648                 SetCmpMethod(_mode, _order);
2649             }
2650         }
2651
2652         /// <summary>
2653         /// ListViewItemComparerクラスのコンストラクタ(引数付は未使用)
2654         /// </summary>
2655         /// <param name="col">並び替える列番号</param>
2656         /// <param name="ord">昇順か降順か</param>
2657         /// <param name="cmod">並び替えの方法</param>
2658
2659         public IdComparerClass()
2660         {
2661             _order = SortOrder.Ascending;
2662             _mode = ComparerMode.Id;
2663             SetCmpMethod(_mode, _order);
2664         }
2665
2666         public Dictionary<long, PostClass> posts
2667         {
2668             set
2669             {
2670                 _statuses = value;
2671             }
2672             get
2673             {
2674                 return _statuses;
2675             }
2676         }
2677
2678         // 指定したソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2679         public Comparison<long> CmpMethod(ComparerMode _sortmode, SortOrder _sortorder)
2680         {
2681             //get
2682             {
2683                 Comparison<long> _method = null;
2684                 if (_sortorder == SortOrder.Ascending)
2685                 {
2686                     // 昇順
2687                     switch (_sortmode)
2688                     {
2689                     case ComparerMode.Data:
2690                         _method = Compare_ModeData_Ascending;
2691                         break;
2692                     case ComparerMode.Id:
2693                         _method = Compare_ModeId_Ascending;
2694                         break;
2695                     case ComparerMode.Name:
2696                         _method = Compare_ModeName_Ascending;
2697                         break;
2698                     case ComparerMode.Nickname:
2699                         _method = Compare_ModeNickName_Ascending;
2700                         break;
2701                     case ComparerMode.Source:
2702                         _method = Compare_ModeSource_Ascending;
2703                         break;
2704                     }
2705                 }
2706                 else
2707                 {
2708                     // 降順
2709                     switch (_sortmode)
2710                     {
2711                     case ComparerMode.Data:
2712                         _method = Compare_ModeData_Descending;
2713                         break;
2714                     case ComparerMode.Id:
2715                         _method = Compare_ModeId_Descending;
2716                         break;
2717                     case ComparerMode.Name:
2718                         _method = Compare_ModeName_Descending;
2719                         break;
2720                     case ComparerMode.Nickname:
2721                         _method = Compare_ModeNickName_Descending;
2722                         break;
2723                     case ComparerMode.Source:
2724                         _method = Compare_ModeSource_Descending;
2725                         break;
2726                     }
2727                 }
2728                 return _method;
2729             }
2730         }
2731
2732         // ソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2733         // (overload 現在の使用中の比較関数のアドレスを返す)
2734         public Comparison<long> CmpMethod()
2735         {
2736             //get
2737             {
2738                 return _CmpMethod;
2739             }
2740         }
2741
2742         // ソートモードとソートオーダーに従い比較関数のアドレスを切り替え
2743         private void SetCmpMethod(ComparerMode mode, SortOrder order)
2744         {
2745             _CmpMethod = this.CmpMethod(mode, order);
2746         }
2747
2748         //xがyより小さいときはマイナスの数、大きいときはプラスの数、
2749         //同じときは0を返す (こちらは未使用 一応比較関数群呼び出しの形のまま残しておく)
2750         int IComparer<long>.Compare(long x, long y)
2751         {
2752             return _CmpMethod(x, y);
2753         }
2754
2755         // 比較用関数群 いずれもステータスIDの順序を考慮する
2756         // 本文比較 昇順
2757         public int Compare_ModeData_Ascending(long x, long y)
2758         {
2759             var result = string.Compare(_statuses[x].TextFromApi, _statuses[y].TextFromApi);
2760             if (result == 0) result = x.CompareTo(y);
2761             return result;
2762         }
2763
2764         // 本文比較 降順
2765         public int Compare_ModeData_Descending(long x, long y)
2766         {
2767             var result = string.Compare(_statuses[y].TextFromApi, _statuses[x].TextFromApi);
2768             if (result == 0) result = y.CompareTo(x);
2769             return result;
2770         }
2771
2772         // ステータスID比較 昇順
2773         public int Compare_ModeId_Ascending(long x, long y)
2774         {
2775             return x.CompareTo(y);
2776         }
2777
2778         // ステータスID比較 降順
2779         public int Compare_ModeId_Descending(long x, long y)
2780         {
2781             return y.CompareTo(x);
2782         }
2783
2784         // 表示名比較 昇順
2785         public int Compare_ModeName_Ascending(long x, long y)
2786         {
2787             var result = string.Compare(_statuses[x].ScreenName, _statuses[y].ScreenName);
2788             if (result == 0) result = x.CompareTo(y);
2789             return result;
2790         }
2791
2792         // 表示名比較 降順
2793         public int Compare_ModeName_Descending(long x, long y)
2794         {
2795             var result = string.Compare(_statuses[y].ScreenName, _statuses[x].ScreenName);
2796             if (result == 0) result = y.CompareTo(x);
2797             return result;
2798         }
2799
2800         // ユーザー名比較 昇順
2801         public int Compare_ModeNickName_Ascending(long x, long y)
2802         {
2803             var result = string.Compare(_statuses[x].Nickname, _statuses[y].Nickname);
2804             if (result == 0) result = x.CompareTo(y);
2805             return result;
2806         }
2807
2808         // ユーザー名比較 降順
2809         public int Compare_ModeNickName_Descending(long x, long y)
2810         {
2811             var result = string.Compare(_statuses[y].Nickname, _statuses[x].Nickname);
2812             if (result == 0) result = y.CompareTo(x);
2813             return result;
2814         }
2815
2816         // Source比較 昇順
2817         public int Compare_ModeSource_Ascending(long x, long y)
2818         {
2819             var result = string.Compare(_statuses[x].Source, _statuses[y].Source);
2820             if (result == 0) result = x.CompareTo(y);
2821             return result;
2822         }
2823
2824         // Source比較 降順
2825         public int Compare_ModeSource_Descending(long x, long y)
2826         {
2827             var result = string.Compare(_statuses[y].Source, _statuses[x].Source);
2828             if (result == 0) result = y.CompareTo(x);
2829             return result;
2830         }
2831     }
2832 }