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.
11 // This file is part of OpenTween.
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)
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
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.
29 using System.Collections.Generic;
31 using System.Linq.Expressions;
32 using System.Reflection;
34 using System.Text.RegularExpressions;
35 using System.Windows.Forms;
36 using System.Xml.Serialization;
40 public class PostClass : ICloneable
42 public class StatusGeo
44 public double Lng { get; set; }
45 public double Lat { get; set; }
47 public override bool Equals(object obj)
49 var geo = obj as StatusGeo;
50 return geo != null && geo.Lng == this.Lng && geo.Lat == this.Lat;
53 public override int GetHashCode()
55 return this.Lng.GetHashCode() ^ this.Lat.GetHashCode();
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; }
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; }
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; }
90 public string RelTabName { get; set; }
91 public int FavoritedCount { get; set; }
93 private States _states = States.None;
105 public PostClass(string Nickname,
119 string InReplyToUser,
120 long? InReplyToStatusId,
123 List<string> ReplyToList,
133 this.Nickname = Nickname;
134 this.TextFromApi = textFromApi;
135 this.ImageUrl = ImageUrl;
136 this.ScreenName = screenName;
137 this.CreatedAt = createdAt;
138 this.StatusId = statusId;
141 this.IsRead = IsRead;
142 this.IsReply = IsReply;
143 this.IsExcludeReply = IsExcludeReply;
144 _IsProtect = IsProtect;
147 this.InReplyToUser = InReplyToUser;
148 _InReplyToStatusId = InReplyToStatusId;
149 this.Source = Source;
150 this.SourceHtml = SourceHtml;
151 this.ReplyToList = ReplyToList;
154 this.UserId = userId;
155 this.FilterHit = FilterHit;
156 this.RetweetedBy = RetweetedBy;
157 this.RetweetedId = RetweetedId;
165 Media = new List<string>();
166 ReplyToList = new List<string>();
169 public string TextSingleLine
173 return this.TextFromApi == null ? null : this.TextFromApi.Replace("\n", " ");
181 if (this.RetweetedId != null)
183 var post = this.GetRetweetSource(this.RetweetedId.Value);
195 if (this.RetweetedId != null)
197 var post = this.GetRetweetSource(this.RetweetedId.Value);
206 public bool IsProtect
216 _states = _states | States.Protect;
220 _states = _states & ~States.Protect;
235 _states = _states | States.Mark;
239 _states = _states & ~States.Mark;
244 public long? InReplyToStatusId
248 return _InReplyToStatusId;
254 _states = _states | States.Reply;
258 _states = _states & ~States.Reply;
260 _InReplyToStatusId = value;
264 public bool IsDeleted
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;
285 public StatusGeo PostGeo
293 if (value != null && (value.Lat != 0 || value.Lng != 0))
295 _states |= States.Geo;
299 _states &= ~States.Geo;
305 public int StateIndex
309 return (int)_states - 1;
313 protected virtual PostClass GetRetweetSource(long statusId)
315 return TabInformations.GetInstance().RetweetSource(statusId);
318 [Obsolete("Use PostClass.Clone() instead.")]
319 public PostClass Copy()
324 public PostClass Clone()
326 return (PostClass)((ICloneable)this).Clone();
329 public override bool Equals(object obj)
331 if (obj == null || this.GetType() != obj.GetType()) return false;
332 return this.Equals((PostClass)obj);
335 public bool Equals(PostClass other)
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);
369 public override int GetHashCode()
371 return this.StatusId.GetHashCode();
374 #region "IClonable.Clone"
375 object ICloneable.Clone()
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);
387 public sealed class TabInformations
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>();
399 private class ScrubGeoInfo
401 public long UserId = 0;
402 public long UpToStatusId = 0;
405 public List<long> BlockIds = new List<long>();
406 public List<long> MuteUserIds = new List<long>();
409 //AddPost(複数回) -> DistributePosts -> SubmitUpdate
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();
418 private static TabInformations _instance = new TabInformations();
421 private List<ListElement> _lists = new List<ListElement>();
423 private TabInformations()
425 _sorter = new IdComparerClass();
428 public static TabInformations GetInstance()
430 return _instance; //singleton
433 public List<ListElement> SubscribableLists
441 if (value != null && value.Count > 0)
443 foreach (var tb in this.GetTabsByType(MyCommon.TabUsageType.Lists))
445 foreach (var list in value)
447 if (tb.ListInfo.Id == list.Id)
459 public bool AddTab(string TabName, MyCommon.TabUsageType TabType, ListElement List)
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;
469 //public void AddTab(string TabName, TabClass Tab)
471 // _tabs.Add(TabName, Tab);
474 public void RemoveTab(string TabName)
478 if (IsDefaultTab(TabName)) return; //念のため
479 var tb = _tabs[TabName];
480 if (!tb.IsInnerStorageTabType)
482 var homeTab = GetTabByType(MyCommon.TabUsageType.Home);
483 var dmTab = GetTabByType(MyCommon.TabUsageType.DirectMessage);
485 for (int idx = 0; idx < tb.AllCount; ++idx)
488 var Id = tb.GetId(idx);
489 if (Id < 0) continue;
490 foreach (var tab in _tabs.Values)
492 if (tab != tb && tab != dmTab)
494 if (tab.Contains(Id))
501 if (!exist) homeTab.Add(Id, _statuses[Id].IsRead, false);
504 _removedTab.Push(tb);
505 _tabs.Remove(TabName);
509 public Stack<TabClass> RemovedTab
511 get { return _removedTab; }
514 public bool ContainsTab(string TabText)
516 return _tabs.ContainsKey(TabText);
519 public bool ContainsTab(TabClass ts)
521 return _tabs.ContainsValue(ts);
525 /// 指定されたタブ名を元に、既存のタブ名との重複を避けた名前を生成します
527 /// <param name="baseTabName">作成したいタブ名</param>
528 /// <returns>生成されたタブ名</returns>
529 /// <exception cref="TabException">タブ名の生成を 100 回試行して失敗した場合</exception>
530 public string MakeTabName(string baseTabName)
532 return this.MakeTabName(baseTabName, 100);
536 /// 指定されたタブ名を元に、既存のタブ名との重複を避けた名前を生成します
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)
544 if (!this.ContainsTab(baseTabName))
547 foreach (var i in Enumerable.Range(2, retryCount - 1))
549 var tabName = baseTabName + i;
550 if (!this.ContainsTab(tabName))
556 var message = string.Format(Properties.Resources.TabNameDuplicate_Text, baseTabName);
557 throw new TabException(message);
560 public Dictionary<string, TabClass> Tabs
572 public Dictionary<string, TabClass>.KeyCollection KeysTab
580 public void SortPosts()
582 foreach (var tab in _tabs.Values)
588 public SortOrder SortOrder
592 return _sorter.Order;
596 _sorter.Order = value;
597 foreach (var tab in _tabs.Values)
599 tab.Sorter.Order = value;
604 public IdComparerClass.ComparerMode SortMode
612 _sorter.Mode = value;
613 foreach (var tab in _tabs.Values)
615 tab.Sorter.Mode = value;
620 public SortOrder ToggleSortOrder(IdComparerClass.ComparerMode SortMode)
622 if (_sorter.Mode == SortMode)
624 if (_sorter.Order == SortOrder.Ascending)
626 _sorter.Order = SortOrder.Descending;
630 _sorter.Order = SortOrder.Ascending;
632 foreach (var tab in _tabs.Values)
634 tab.Sorter.Order = _sorter.Order;
639 _sorter.Mode = SortMode;
640 _sorter.Order = SortOrder.Ascending;
641 foreach (var tab in _tabs.Values)
643 tab.Sorter.Mode = SortMode;
644 tab.Sorter.Order = SortOrder.Ascending;
648 return _sorter.Order;
651 // public PostClass RetweetSource(long Id)
655 // if (_retweets.ContainsKey(Id))
657 // return _retweets[Id];
665 public PostClass RetweetSource(long Id)
668 return _retweets.TryGetValue(Id, out status)
673 public void RemoveFavPost(long Id)
678 var tab = this.GetTabByType(MyCommon.TabUsageType.Favorites);
679 var tn = tab.TabName;
681 if (_statuses.TryGetValue(Id, out post))
684 var tType = tab.TabType;
685 if (tab.Contains(Id))
687 if (tab.UnreadManage && !post.IsRead) //未読管理
692 this.SetNextUnreadId(Id, tab);
697 //FavタブからRetweet発言を削除する場合は、他の同一参照Retweetも削除
698 if (tType == MyCommon.TabUsageType.Favorites && post.RetweetedId != null)
700 for (int i = 0; i < tab.AllCount; i++)
702 PostClass rPost = null;
707 catch (ArgumentOutOfRangeException)
711 if (rPost.RetweetedId != null && rPost.RetweetedId == post.RetweetedId)
713 if (tab.UnreadManage && !rPost.IsRead) //未読管理
718 this.SetNextUnreadId(rPost.StatusId, tab);
721 tab.Remove(rPost.StatusId);
726 //TabType=PublicSearchの場合(Postの保存先がTabClass内)
727 //if (tab.Contains(StatusId) &&
728 // (tab.TabType = MyCommon.TabUsageType.PublicSearch || tab.TabType = MyCommon.TabUsageType.DirectMessage))
730 // post = tab.Posts[StatusId];
731 // if (tab.UnreadManage && !post.IsRead) //未読管理
735 // tab.UnreadCount--;
736 // this.SetNextUnreadId(StatusId, tab);
739 // tab.Remove(StatusId);
744 public void ScrubGeoReserve(long id, long upToStatusId)
748 //this._scrubGeo.Add(new ScrubGeoInfo With {.UserId = id, .UpToStatusId = upToStatusId});
749 this.ScrubGeo(id, upToStatusId);
753 private void ScrubGeo(long userId, long upToStatusId)
757 var userPosts = from post in this._statuses.Values
758 where post.UserId == userId && post.UserId <= upToStatusId
761 foreach (var p in userPosts)
763 p.PostGeo = new PostClass.StatusGeo();
766 var userPosts2 = from tb in this.GetTabsInnerStorageType()
767 from post in tb.Posts.Values
768 where post.UserId == userId && post.UserId <= upToStatusId
771 foreach (var p in userPosts2)
773 p.PostGeo = new PostClass.StatusGeo();
778 public void RemovePostReserve(long id)
782 this._deletedIds.Add(id);
783 this.DeletePost(id); //UI選択行がずれるため、RemovePostは使用しない
787 public void RemovePost(long Id)
792 foreach (var tab in _tabs.Values)
794 if (tab.Contains(Id))
796 if (tab.UnreadManage && !tab.Posts[Id].IsRead) //未読管理
801 this.SetNextUnreadId(Id, tab);
807 if (_statuses.ContainsKey(Id)) _statuses.Remove(Id);
811 private void DeletePost(long Id)
816 if (_statuses.TryGetValue(Id, out post))
818 post.IsDeleted = true;
820 foreach (var tb in this.GetTabsInnerStorageType())
825 post.IsDeleted = true;
831 public int GetOldestUnreadIndex(string TabName)
833 var tb = _tabs[TabName];
834 if (tb.OldestUnreadId > -1 &&
835 tb.Contains(tb.OldestUnreadId) &&
839 bool isRead = tb.Posts[tb.OldestUnreadId].IsRead;
845 this.SetNextUnreadId(-1, tb); //頭から探索
847 if (tb.OldestUnreadId == -1)
853 return tb.IndexOf(tb.OldestUnreadId);
858 return tb.IndexOf(tb.OldestUnreadId); //最短経路;
863 //一見未読なさそうだが、未読カウントはあるので探索
864 //if (tb.UnreadCount > 0)
865 if (!(tb.UnreadManage && AppendSettingDialog.Instance.UnreadManage)) return -1;
868 this.SetNextUnreadId(-1, tb);
870 if (tb.OldestUnreadId == -1)
876 return tb.IndexOf(tb.OldestUnreadId);
885 private void SetNextUnreadId(long CurrentId, TabClass Tab)
887 //CurrentID:今既読にしたID(OldestIDの可能性あり)
888 //最古未読が設定されていて、既読の場合(1発言以上存在)
891 Dictionary<long, PostClass> posts = Tab.Posts;
893 PostClass oldestUnreadPost;
894 if (Tab.OldestUnreadId > -1 &&
895 posts.TryGetValue(Tab.OldestUnreadId, out oldestUnreadPost) &&
896 oldestUnreadPost.IsRead &&
897 _sorter.Mode == IdComparerClass.ComparerMode.Id) //次の未読探索
899 if (Tab.UnreadCount == 0)
902 Tab.OldestUnreadId = -1;
904 else if (Tab.OldestUnreadId == CurrentId && CurrentId > -1)
906 //最古IDを既読にしたタイミング→次のIDから続けて探索
907 var idx = Tab.IndexOf(CurrentId);
911 FindUnreadId(idx, Tab);
917 catch (KeyNotFoundException)
922 FindUnreadId(-1, Tab);
925 private void FindUnreadId(int StartIdx, TabClass Tab)
927 if (Tab.AllCount == 0)
929 Tab.OldestUnreadId = -1;
935 Tab.OldestUnreadId = -1;
936 if (_sorter.Order == SortOrder.Ascending)
945 if (StartIdx > Tab.AllCount - 1) StartIdx = Tab.AllCount - 1; //念のため
947 toIdx = Tab.AllCount - 1;
948 if (toIdx < 0) toIdx = 0; //念のため
955 StartIdx = Tab.AllCount - 1;
961 if (StartIdx < 0) StartIdx = 0; //念のため
966 Dictionary<long, PostClass> posts = Tab.Posts;
968 for (int i = StartIdx; ; i+= stp)
970 var id = Tab.GetId(i);
971 if (id > -1 && !posts[id].IsRead)
973 Tab.OldestUnreadId = id;
977 if (i == toIdx) break;
981 public int DistributePosts()
986 //if (_addedIds == null) return 0;
987 //if (_addedIds.Count == 0) return 0;
989 if (_addedIds == null) _addedIds = new List<long>();
990 if (_notifyPosts == null) _notifyPosts = new List<PostClass>();
993 this.Distribute(); //タブに仮振分
995 catch (KeyNotFoundException)
999 var retCnt = _addedIds.Count;
1000 _addCount += retCnt;
1002 _addedIds = null; //後始末
1007 public int SubmitUpdate(ref string soundFile,
1008 ref PostClass[] notifyPosts,
1009 ref bool isMentionIncluded,
1010 ref bool isDeletePost,
1016 if (_notifyPosts == null)
1023 foreach (var tb in _tabs.Values)
1025 if (tb.IsInnerStorageTabType)
1027 _addCount += tb.GetTemporaryCount();
1029 tb.AddSubmit(ref isMentionIncluded); //振分確定(各タブに反映)
1031 ////UserStreamで反映間隔10秒以下だったら、30秒ごとにソートする
1033 //static DateTime lastSort = DateTime.Now;
1034 //if (AppendSettingDialog.Instance.UserstreamPeriodInt < 10 && isUserStream)
1036 // if (Now.Subtract(lastSort) > TimeSpan.FromSeconds(30))
1038 // lastSort = DateTime.Now;
1039 // isUserStream = false;
1044 // isUserStream = false;
1046 if (!isUserStream || this.SortMode != IdComparerClass.ComparerMode.Id)
1052 isDeletePost = this._deletedIds.Count > 0;
1053 foreach (var id in this._deletedIds)
1055 //this.DeletePost(StatusId)
1056 this.RemovePost(id);
1058 this._deletedIds.Clear();
1061 soundFile = _soundFile;
1063 notifyPosts = _notifyPosts.ToArray();
1064 _notifyPosts.Clear();
1065 _notifyPosts = null;
1066 var retCnt = _addCount;
1068 return retCnt; //件数(EndUpdateの戻り値と同じ)
1072 private void Distribute()
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)
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)
1089 rslt = tab.AddFiltered(post);
1090 if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
1092 if (rslt == MyCommon.HITRESULT.CopyAndMark) post.IsMark = true; //マークあり
1093 else if (rslt == MyCommon.HITRESULT.Move)
1096 post.IsMark = false;
1098 if (tab.Notify) add = true; //通知あり
1099 if (!string.IsNullOrEmpty(tab.SoundFile) && string.IsNullOrEmpty(_soundFile))
1101 _soundFile = tab.SoundFile; //wavファイル(未設定の場合のみ)
1103 post.FilterHit = true;
1107 if (rslt == MyCommon.HITRESULT.Exclude && tab.TabType == MyCommon.TabUsageType.Mentions)
1109 post.IsExcludeReply = true;
1111 post.FilterHit = false;
1114 if (!mv) //移動されなかったらRecentに追加
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;
1120 if (post.IsReply && !post.IsExcludeReply) //除外ルール適用のないReplyならReplyタブに追加
1122 replyTab.Add(post.StatusId, post.IsRead, true);
1123 if (!string.IsNullOrEmpty(replyTab.SoundFile)) _soundFile = replyTab.SoundFile;
1124 if (replyTab.Notify) add = true;
1126 if (post.IsFav) //Fav済み発言だったらFavoritesタブに追加
1128 if (favTab.Contains(post.StatusId))
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;
1141 if (add) _notifyPosts.Add(post);
1143 foreach (var tb in _tabs.Values)
1145 if (tb.IsInnerStorageTabType)
1149 if (tb.GetTemporaryCount() > 0)
1151 foreach (var post in tb.GetTemporaryPosts())
1154 foreach (var npost in _notifyPosts)
1156 if (npost.StatusId == post.StatusId)
1162 if (!exist) _notifyPosts.Add(post);
1164 if (!string.IsNullOrEmpty(tb.SoundFile))
1166 if (tb.TabType == MyCommon.TabUsageType.DirectMessage || string.IsNullOrEmpty(_soundFile))
1168 _soundFile = tb.SoundFile;
1177 public void AddPost(PostClass Item)
1181 if (string.IsNullOrEmpty(Item.RelTabName))
1186 if (_statuses.TryGetValue(Item.StatusId, out status))
1190 if (Item.RetweetedId == null)
1192 status.IsFav = true;
1201 return; //追加済みなら何もしない
1206 if (Item.IsFav && Item.RetweetedId != null) Item.IsFav = false;
1208 if (AppendSettingDialog.Instance.HideDuplicatedRetweets &&
1210 Item.RetweetedId != null &&
1211 this._retweets.TryGetValue(Item.RetweetedId.Value, out status) &&
1212 status.RetweetedCount > 0) return;
1214 if (BlockIds.Contains(Item.UserId))
1217 if (this.IsMuted(Item))
1220 _statuses.Add(Item.StatusId, Item);
1222 if (Item.RetweetedId != null)
1224 this.AddRetweet(Item);
1226 if (Item.IsFav && _retweets.ContainsKey(Item.StatusId))
1228 return; //Fav済みのRetweet元発言は追加しない
1230 if (_addedIds == null) _addedIds = new List<long>(); //タブ追加用IDコレクション準備
1231 _addedIds.Add(Item.StatusId);
1236 var tb = this.GetTabByType(MyCommon.TabUsageType.DirectMessage);
1237 if (tb.Contains(Item.StatusId)) return;
1238 tb.AddPostToInnerStorage(Item);
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);
1254 private bool IsMuted(PostClass post)
1256 // Recent以外のツイートと、リプライはミュート対象外
1257 // 参照: https://support.twitter.com/articles/20171399-muting-users-on-twitter
1258 if (string.IsNullOrEmpty(post.RelTabName) || post.IsReply)
1261 if (this.MuteUserIds.Contains(post.UserId))
1264 if (post.RetweetedByUserId != null && this.MuteUserIds.Contains(post.RetweetedByUserId.Value))
1270 private void AddRetweet(PostClass item)
1272 var retweetedId = item.RetweetedId.Value;
1274 //true:追加、False:保持済み
1276 if (_retweets.TryGetValue(retweetedId, out status))
1278 status.RetweetedCount++;
1279 if (status.RetweetedCount > 10)
1281 status.RetweetedCount = 0;
1287 item.RetweetedId.Value,
1295 item.RetweetedId.Value,
1299 item.IsExcludeReply,
1304 item.InReplyToStatusId,
1317 _retweets[retweetedId].RetweetedCount++;
1320 public void SetReadAllTab(bool Read, string TabName, int Index)
1322 //Read:true=既読へ false=未読へ
1323 var tb = _tabs[TabName];
1325 if (tb.UnreadManage == false) return; //未読管理していなければ終了
1327 var Id = tb.GetId(Index);
1329 PostClass post = tb.Posts[Id];
1331 if (post.IsRead == Read) return; //状態変更なければ終了
1340 this.SetNextUnreadId(Id, tb); //次の未読セット
1341 //他タブの最古未読IDはタブ切り替え時に。
1342 if (tb.IsInnerStorageTabType)
1346 if (_statuses.TryGetValue(Id, out status) && !status.IsRead)
1348 foreach (var tab in _tabs.Values)
1350 if (tab.UnreadManage &&
1351 !tab.IsInnerStorageTabType &&
1355 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1358 status.IsRead = true;
1364 foreach (var tab in _tabs.Values)
1368 !tab.IsInnerStorageTabType &&
1372 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1377 foreach (var tab in _tabs.Values)
1380 tab.IsInnerStorageTabType &&
1383 var tPost = tab.Posts[Id];
1386 if (tab.UnreadManage)
1389 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1391 tPost.IsRead = true;
1399 //if (tb.OldestUnreadId > Id || tb.OldestUnreadId = -1) tb.OldestUnreadId = Id
1400 if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1401 if (tb.IsInnerStorageTabType)
1405 if (_statuses.TryGetValue(Id, out status) && status.IsRead)
1407 foreach (var tab in _tabs.Values)
1409 if (tab.UnreadManage &&
1410 !tab.IsInnerStorageTabType &&
1414 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1417 status.IsRead = false;
1423 foreach (var tab in _tabs.Values)
1427 !tab.IsInnerStorageTabType &&
1431 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1436 foreach (var tab in _tabs.Values)
1439 tab.IsInnerStorageTabType &&
1442 var tPost = tab.Posts[Id];
1445 if (tab.UnreadManage)
1448 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1450 tPost.IsRead = false;
1458 // TODO: パフォーマンスを勘案して、戻すか決める
1459 public void SetRead(bool Read, string TabName, int Index)
1461 //Read:true=既読へ false=未読へ
1462 var tb = _tabs[TabName];
1464 if (tb.UnreadManage == false) return; //未読管理していなければ終了
1466 var Id = tb.GetId(Index);
1468 PostClass post = tb.Posts[Id];
1470 if (post.IsRead == Read) return; //状態変更なければ終了
1472 post.IsRead = Read; //指定の状態に変更
1479 this.SetNextUnreadId(Id, tb); //次の未読セット
1480 //他タブの最古未読IDはタブ切り替え時に。
1481 if (tb.IsInnerStorageTabType) return;
1482 foreach (var tab in _tabs.Values)
1486 !tab.IsInnerStorageTabType &&
1490 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
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)
1504 !tab.IsInnerStorageTabType &&
1508 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1515 public void SetRead()
1517 var tb = GetTabByType(MyCommon.TabUsageType.Home);
1518 if (tb.UnreadManage == false) return;
1522 for (int i = 0; i < tb.AllCount - 1; i++)
1524 var id = tb.GetId(i);
1526 var tPost = _statuses[id];
1527 if (!tPost.IsReply &&
1531 tPost.IsRead = true;
1532 this.SetNextUnreadId(id, tb); //次の未読セット
1533 foreach (var tab in _tabs.Values)
1535 if (tab.UnreadManage &&
1539 if (tab.OldestUnreadId == id) tab.OldestUnreadId = -1;
1547 public PostClass this[long ID]
1552 return _statuses.TryGetValue(ID, out status)
1554 : this.GetTabsInnerStorageType()
1555 .Where(t => t.Contains(ID))
1556 .Select(t => t.Posts[ID]).FirstOrDefault();
1560 public PostClass this[string TabName, int Index]
1565 if (!_tabs.TryGetValue(TabName, out tb)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1570 public PostClass[] this[string TabName, int StartIndex, int EndIndex]
1575 if (!_tabs.TryGetValue(TabName, out tb)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1576 return tb[StartIndex, EndIndex];
1580 //public ReadOnly int ItemCount
1586 // return _statuses.Count //DM,公式検索は除く
1591 public bool ContainsKey(long Id)
1596 return _statuses.ContainsKey(Id);
1600 public bool ContainsKey(long Id, string TabName)
1606 return _tabs.TryGetValue(TabName, out tab) && tab.Contains(Id);
1610 public void SetUnreadManage(bool Manage)
1614 foreach (var tab in _tabs.Values)
1616 if (tab.UnreadManage)
1621 var oldest = long.MaxValue;
1622 Dictionary<long, PostClass> posts = tab.Posts;
1623 foreach (var id in tab.BackupIds)
1625 if (!posts[id].IsRead)
1628 if (oldest > id) oldest = id;
1631 if (oldest == long.MaxValue) oldest = -1;
1632 tab.OldestUnreadId = oldest;
1633 tab.UnreadCount = cnt;
1640 foreach (var tab in _tabs.Values)
1642 if (tab.UnreadManage && tab.UnreadCount > 0)
1646 tab.UnreadCount = 0;
1647 tab.OldestUnreadId = -1;
1654 public void RenameTab(string Original, string NewName)
1656 var tb = _tabs[Original];
1657 _tabs.Remove(Original);
1658 tb.TabName = NewName;
1659 _tabs.Add(NewName, tb);
1662 public void FilterAll()
1666 var tbr = GetTabByType(MyCommon.TabUsageType.Home);
1667 var replyTab = GetTabByType(MyCommon.TabUsageType.Mentions);
1668 foreach (var tb in _tabs.Values.ToArray())
1670 if (tb.FilterModified)
1672 tb.FilterModified = false;
1673 var orgIds = tb.BackupIds;
1675 //////////////フィルター前のIDsを退避。どのタブにも含まれないidはrecentへ追加
1676 //////////////moveフィルターにヒットした際、recentに該当あればrecentから削除
1677 foreach (var post in _statuses.Values)
1679 if (post.IsDm) continue;
1680 var rslt = MyCommon.HITRESULT.None;
1681 rslt = tb.AddFiltered(post);
1684 case MyCommon.HITRESULT.CopyAndMark:
1685 post.IsMark = true; //マークあり
1686 post.FilterHit = true;
1688 case MyCommon.HITRESULT.Move:
1689 tbr.Remove(post.StatusId, post.IsRead);
1690 post.IsMark = false;
1691 post.FilterHit = true;
1693 case MyCommon.HITRESULT.Copy:
1694 post.IsMark = false;
1695 post.FilterHit = true;
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;
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;
1709 tb.AddSubmit(); //振分確定
1710 foreach (var id in orgIds)
1713 foreach (var tbTemp in _tabs.Values.ToArray())
1715 if (tbTemp.Contains(id))
1721 if (!hit) tbr.Add(id, _statuses[id].IsRead, false);
1729 public long[] GetId(string TabName, ListView.SelectedIndexCollection IndexCollection)
1731 return _tabs[TabName].GetId(IndexCollection);
1734 public long GetId(string TabName, int Index)
1736 return _tabs[TabName].GetId(Index);
1739 public int[] IndexOf(string TabName, long[] Ids)
1741 return _tabs[TabName].IndexOf(Ids);
1744 public int IndexOf(string TabName, long Id)
1746 return _tabs[TabName].IndexOf(Id);
1749 public void ClearTabIds(string TabName)
1754 var tb = _tabs[TabName];
1755 if (!tb.IsInnerStorageTabType)
1757 foreach (var Id in tb.BackupIds)
1760 foreach (var tab in _tabs.Values)
1762 if (tab.Contains(Id))
1768 if (!Hit) _statuses.Remove(Id);
1777 public void SetTabUnreadManage(string TabName, bool Manage)
1779 var tb = _tabs[TabName];
1785 var oldest = long.MaxValue;
1786 Dictionary<long, PostClass> posts = tb.Posts;
1787 foreach (var id in tb.BackupIds)
1789 if (!posts[id].IsRead)
1792 if (oldest > id) oldest = id;
1795 if (oldest == long.MaxValue) oldest = -1;
1796 tb.OldestUnreadId = oldest;
1797 tb.UnreadCount = cnt;
1801 tb.OldestUnreadId = -1;
1805 tb.UnreadManage = Manage;
1808 public void RefreshOwl(List<long> follower)
1812 if (follower.Count > 0)
1814 foreach (var post in _statuses.Values)
1816 //if (post.UserId = 0 || post.IsDm) Continue For
1823 post.IsOwl = !follower.Contains(post.UserId);
1829 foreach (var post in _statuses.Values)
1837 public TabClass GetTabByType(MyCommon.TabUsageType tabType)
1839 //Home,Mentions,DM,Favは1つに制限する
1840 //その他のタイプを指定されたら、最初に合致したものを返す
1844 foreach (var tab in _tabs.Values)
1846 if (tab.TabType == tabType) return tab;
1852 public List<TabClass> GetTabsByType(MyCommon.TabUsageType tabType)
1858 var tbs = new List<TabClass>();
1859 foreach (var tb in _tabs.Values)
1861 if ((tabType & tb.TabType) == tb.TabType) tbs.Add(tb);
1867 public List<TabClass> GetTabsInnerStorageType()
1873 var tbs = new List<TabClass>();
1874 foreach (var tb in _tabs.Values)
1876 if (tb.IsInnerStorageTabType) tbs.Add(tb);
1882 public TabClass GetTabByName(string tabName)
1887 return _tabs.TryGetValue(tabName, out tab)
1894 public bool IsDefaultTab(string tabName)
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))
1911 public bool IsDistributableTab(string tabName)
1914 if (tabName != null &&
1915 _tabs.TryGetValue(tabName, out tab) &&
1916 (tab.TabType == MyCommon.TabUsageType.Mentions ||
1917 tab.TabType == MyCommon.TabUsageType.UserDefined))
1925 public string GetUniqueTabName()
1927 var tabNameTemp = "MyTab" + (_tabs.Count + 1).ToString();
1928 for (int i = 2; i <= 100; i++)
1930 if (_tabs.ContainsKey(tabNameTemp))
1932 tabNameTemp = "MyTab" + (_tabs.Count + i).ToString();
1942 public Dictionary<long, PostClass> Posts
1952 public sealed class TabClass
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;
1962 private IdComparerClass _sorter = new IdComparerClass();
1964 private readonly object _lockObj = new object();
1966 public string User { get; set; }
1970 private string _searchLang = "";
1971 private string _searchWords = "";
1972 private string _nextPageQuery = "";
1974 public string SearchLang
1983 _searchLang = value;
1986 public string SearchWords
1990 return _searchWords;
1995 _searchWords = value.Trim();
1999 public string NextPageQuery
2003 return _nextPageQuery;
2007 _nextPageQuery = value;
2011 public int GetSearchPage(int count)
2013 return ((_ids.Count / count) + 1);
2015 private Dictionary<string, string> _beforeQuery = new Dictionary<string, string>();
2016 public void SaveQuery(bool more)
2018 var qry = new Dictionary<string, string>();
2019 if (string.IsNullOrEmpty(_searchWords))
2024 qry.Add("q", _searchWords);
2025 if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2029 public bool IsQueryChanged()
2031 var qry = new Dictionary<string, string>();
2032 if (!string.IsNullOrEmpty(_searchWords))
2034 qry.Add("q", _searchWords);
2035 if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2037 if (qry.Count != _beforeQuery.Count) return true;
2039 foreach (var kvp in qry)
2041 if (!_beforeQuery.ContainsKey(kvp.Key) || _beforeQuery[kvp.Key] != kvp.Value)
2052 private ListElement _listInfo;
2053 public ListElement ListInfo
2067 public PostClass RelationTargetPost { get; set; }
2070 public long OldestId = long.MaxValue;
2073 public long SinceId { get; set; }
2076 public Dictionary<long, PostClass> Posts { get; private set; }
2078 private Dictionary<long, PostClass> _innerPosts;
2080 public PostClass[] GetTemporaryPosts()
2082 var tempPosts = new List<PostClass>();
2083 if (_tmpIds.Count == 0) return tempPosts.ToArray();
2084 foreach (var tempId in _tmpIds)
2086 tempPosts.Add(Posts[tempId.Id]);
2088 return tempPosts.ToArray();
2091 public int GetTemporaryCount()
2093 return _tmpIds.Count;
2096 private struct TemporaryId
2101 public TemporaryId(long argId, bool argRead)
2110 _innerPosts = new Dictionary<long, PostClass>();
2111 Posts = _innerPosts;
2113 OldestUnreadId = -1;
2115 _filters = new List<PostFilterRule>();
2119 _unreadManage = true;
2120 _ids = new List<long>();
2121 this.OldestUnreadId = -1;
2122 _tabType = MyCommon.TabUsageType.Undefined;
2126 public TabClass(string TabName, MyCommon.TabUsageType TabType, ListElement list) : this()
2128 this.TabName = TabName;
2129 this.TabType = TabType;
2130 this.ListInfo = list;
2135 if (_sorter.Mode == IdComparerClass.ComparerMode.Id)
2137 _ids.Sort(_sorter.CmpMethod());
2141 if (_sorter.Order == SortOrder.Ascending)
2143 switch (_sorter.Mode)
2145 case IdComparerClass.ComparerMode.Data:
2146 ar = _ids.OrderBy(n => _sorter.posts[n].TextFromApi).ToArray();
2148 case IdComparerClass.ComparerMode.Name:
2149 ar = _ids.OrderBy(n => _sorter.posts[n].ScreenName).ToArray();
2151 case IdComparerClass.ComparerMode.Nickname:
2152 ar = _ids.OrderBy(n => _sorter.posts[n].Nickname).ToArray();
2154 case IdComparerClass.ComparerMode.Source:
2155 ar = _ids.OrderBy(n => _sorter.posts[n].Source).ToArray();
2161 switch (_sorter.Mode)
2163 case IdComparerClass.ComparerMode.Data:
2164 ar = _ids.OrderByDescending(n => _sorter.posts[n].TextFromApi).ToArray();
2166 case IdComparerClass.ComparerMode.Name:
2167 ar = _ids.OrderByDescending(n => _sorter.posts[n].ScreenName).ToArray();
2169 case IdComparerClass.ComparerMode.Nickname:
2170 ar = _ids.OrderByDescending(n => _sorter.posts[n].Nickname).ToArray();
2172 case IdComparerClass.ComparerMode.Source:
2173 ar = _ids.OrderByDescending(n => _sorter.posts[n].Source).ToArray();
2177 _ids = new List<long>(ar);
2180 public IdComparerClass Sorter
2189 private void Add(long ID, bool Read)
2191 if (this._ids.Contains(ID)) return;
2193 if (this.Sorter.Mode == IdComparerClass.ComparerMode.Id)
2195 if (this.Sorter.Order == SortOrder.Ascending)
2201 this._ids.Insert(0, ID);
2209 if (!Read && this._unreadManage)
2211 this._unreadCount++;
2212 if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2213 //if (this.OldestUnreadId == -1)
2215 // this.OldestUnreadId = ID;
2219 // if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2224 public void Add(long ID, bool Read, bool Temporary)
2232 _tmpIds.Add(new TemporaryId(ID, Read));
2237 public MyCommon.HITRESULT AddFiltered(PostClass post)
2239 if (this.IsInnerStorageTabType) return MyCommon.HITRESULT.None;
2241 var rslt = MyCommon.HITRESULT.None;
2243 lock (this._lockObj)
2245 foreach (var ft in _filters)
2249 switch (ft.ExecFilter(post)) //フィルタクラスでヒット判定
2251 case MyCommon.HITRESULT.None:
2253 case MyCommon.HITRESULT.Copy:
2254 if (rslt != MyCommon.HITRESULT.CopyAndMark) rslt = MyCommon.HITRESULT.Copy;
2256 case MyCommon.HITRESULT.CopyAndMark:
2257 rslt = MyCommon.HITRESULT.CopyAndMark;
2259 case MyCommon.HITRESULT.Move:
2260 rslt = MyCommon.HITRESULT.Move;
2262 case MyCommon.HITRESULT.Exclude:
2263 rslt = MyCommon.HITRESULT.Exclude;
2267 catch (NullReferenceException)
2269 // ExecFilterでNullRef出る場合あり。暫定対応
2270 MyCommon.TraceOut("ExecFilterでNullRef: " + ft.ToString());
2271 rslt = MyCommon.HITRESULT.None;
2278 if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
2280 _tmpIds.Add(new TemporaryId(post.StatusId, post.IsRead));
2283 return rslt; //マーク付けは呼び出し元で行うこと
2287 public void AddPostToInnerStorage(PostClass Post)
2289 if (_innerPosts.ContainsKey(Post.StatusId)) return;
2290 _innerPosts.Add(Post.StatusId, Post);
2291 _tmpIds.Add(new TemporaryId(Post.StatusId, Post.IsRead));
2294 public void AddSubmit(ref bool isMentionIncluded)
2296 if (_tmpIds.Count == 0) return;
2297 _tmpIds.Sort((x, y) => x.Id.CompareTo(y.Id));
2298 foreach (var tId in _tmpIds)
2300 if (this.TabType == MyCommon.TabUsageType.Mentions && TabInformations.GetInstance()[tId.Id].IsReply) isMentionIncluded = true;
2301 this.Add(tId.Id, tId.Read);
2306 public void AddSubmit()
2308 bool mention = false;
2309 AddSubmit(ref mention);
2312 public void Remove(long Id)
2314 if (!this._ids.Contains(Id)) return;
2315 this._ids.Remove(Id);
2316 if (this.IsInnerStorageTabType) _innerPosts.Remove(Id);
2319 public void Remove(long Id, bool Read)
2321 if (!this._ids.Contains(Id)) return;
2323 if (!Read && this._unreadManage)
2325 this._unreadCount--;
2326 this.OldestUnreadId = -1;
2329 this._ids.Remove(Id);
2330 if (this.IsInnerStorageTabType) _innerPosts.Remove(Id);
2333 public bool UnreadManage
2337 return _unreadManage;
2341 this._unreadManage = value;
2344 this.OldestUnreadId = -1;
2345 this._unreadCount = 0;
2350 // v1.0.5で「タブを固定(Locked)」から「タブを保護(Protected)」に名称変更
2351 [XmlElement(ElementName = "Locked")]
2352 public bool Protected { get; set; }
2354 public bool Notify { get; set; }
2356 public string SoundFile { get; set; }
2359 public long OldestUnreadId { get; set; }
2362 public int UnreadCount
2366 return this.UnreadManage && AppendSettingDialog.Instance.UnreadManage ? _unreadCount : 0;
2370 if (value < 0) value = 0;
2371 _unreadCount = value;
2379 return this._ids.Count;
2383 public PostFilterRule[] GetFilters()
2385 lock (this._lockObj)
2387 return _filters.ToArray();
2391 public void RemoveFilter(PostFilterRule filter)
2393 lock (this._lockObj)
2395 _filters.Remove(filter);
2396 this.FilterModified = true;
2400 public bool AddFilter(PostFilterRule filter)
2402 lock (this._lockObj)
2404 if (_filters.Contains(filter)) return false;
2405 _filters.Add(filter);
2406 this.FilterModified = true;
2411 public void EditFilter(PostFilterRule original, PostFilterRule modified)
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;
2437 public List<PostFilterRule> Filters
2441 lock (this._lockObj)
2448 lock (this._lockObj)
2455 public PostFilterRule[] FilterArray
2459 lock (this._lockObj)
2461 return _filters.ToArray();
2466 lock (this._lockObj)
2468 foreach (var filters in value)
2470 _filters.Add(filters);
2475 public bool Contains(long ID)
2477 return _ids.Contains(ID);
2480 public void ClearIDs()
2485 this.OldestUnreadId = -1;
2486 _innerPosts.Clear();
2489 public PostClass this[int Index]
2493 var id = GetId(Index);
2494 if (id < 0) throw new ArgumentException("Index can't find. Index=" + Index.ToString() + "/TabName=" + TabName);
2499 public PostClass[] this[int StartIndex, int EndIndex]
2503 var length = EndIndex - StartIndex + 1;
2504 var posts = new PostClass[length];
2505 for (int i = 0; i < length; i++)
2507 posts[i] = Posts[GetId(StartIndex + i)];
2513 public long[] GetId(ListView.SelectedIndexCollection IndexCollection)
2515 if (IndexCollection.Count == 0) return null;
2517 var Ids = new long[IndexCollection.Count];
2518 for (int i = 0; i < Ids.Length; i++)
2520 Ids[i] = GetId(IndexCollection[i]);
2525 public long GetId(int Index)
2527 return Index < _ids.Count ? _ids[Index] : -1;
2530 public int[] IndexOf(long[] Ids)
2532 if (Ids == null) return null;
2533 var idx = new int[Ids.Length];
2534 for (int i = 0; i < Ids.Length; i++)
2536 idx[i] = IndexOf(Ids[i]);
2541 public int IndexOf(long ID)
2543 return _ids.IndexOf(ID);
2547 public bool FilterModified { get; set; }
2549 public long[] BackupIds
2553 return _ids.ToArray();
2557 public string TabName { get; set; }
2559 public MyCommon.TabUsageType TabType
2568 if (this.IsInnerStorageTabType)
2570 Posts = _innerPosts;
2574 Posts = TabInformations.GetInstance().Posts;
2576 _sorter.posts = Posts;
2580 public bool IsInnerStorageTabType
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)
2601 public sealed class IdComparerClass : IComparer<long>
2606 public enum ComparerMode
2615 private SortOrder _order;
2616 private ComparerMode _mode;
2617 private Dictionary<long, PostClass> _statuses;
2618 private Comparison<long> _CmpMethod;
2621 /// 昇順か降順か Setの際は同時に比較関数の切り替えを行う
2623 public SortOrder Order
2632 SetCmpMethod(_mode, _order);
2637 /// 並び替えの方法 Setの際は同時に比較関数の切り替えを行う
2639 public ComparerMode Mode
2648 SetCmpMethod(_mode, _order);
2653 /// ListViewItemComparerクラスのコンストラクタ(引数付は未使用)
2655 /// <param name="col">並び替える列番号</param>
2656 /// <param name="ord">昇順か降順か</param>
2657 /// <param name="cmod">並び替えの方法</param>
2659 public IdComparerClass()
2661 _order = SortOrder.Ascending;
2662 _mode = ComparerMode.Id;
2663 SetCmpMethod(_mode, _order);
2666 public Dictionary<long, PostClass> posts
2678 // 指定したソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2679 public Comparison<long> CmpMethod(ComparerMode _sortmode, SortOrder _sortorder)
2683 Comparison<long> _method = null;
2684 if (_sortorder == SortOrder.Ascending)
2689 case ComparerMode.Data:
2690 _method = Compare_ModeData_Ascending;
2692 case ComparerMode.Id:
2693 _method = Compare_ModeId_Ascending;
2695 case ComparerMode.Name:
2696 _method = Compare_ModeName_Ascending;
2698 case ComparerMode.Nickname:
2699 _method = Compare_ModeNickName_Ascending;
2701 case ComparerMode.Source:
2702 _method = Compare_ModeSource_Ascending;
2711 case ComparerMode.Data:
2712 _method = Compare_ModeData_Descending;
2714 case ComparerMode.Id:
2715 _method = Compare_ModeId_Descending;
2717 case ComparerMode.Name:
2718 _method = Compare_ModeName_Descending;
2720 case ComparerMode.Nickname:
2721 _method = Compare_ModeNickName_Descending;
2723 case ComparerMode.Source:
2724 _method = Compare_ModeSource_Descending;
2732 // ソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2733 // (overload 現在の使用中の比較関数のアドレスを返す)
2734 public Comparison<long> CmpMethod()
2742 // ソートモードとソートオーダーに従い比較関数のアドレスを切り替え
2743 private void SetCmpMethod(ComparerMode mode, SortOrder order)
2745 _CmpMethod = this.CmpMethod(mode, order);
2748 //xがyより小さいときはマイナスの数、大きいときはプラスの数、
2749 //同じときは0を返す (こちらは未使用 一応比較関数群呼び出しの形のまま残しておく)
2750 int IComparer<long>.Compare(long x, long y)
2752 return _CmpMethod(x, y);
2755 // 比較用関数群 いずれもステータスIDの順序を考慮する
2757 public int Compare_ModeData_Ascending(long x, long y)
2759 var result = string.Compare(_statuses[x].TextFromApi, _statuses[y].TextFromApi);
2760 if (result == 0) result = x.CompareTo(y);
2765 public int Compare_ModeData_Descending(long x, long y)
2767 var result = string.Compare(_statuses[y].TextFromApi, _statuses[x].TextFromApi);
2768 if (result == 0) result = y.CompareTo(x);
2773 public int Compare_ModeId_Ascending(long x, long y)
2775 return x.CompareTo(y);
2779 public int Compare_ModeId_Descending(long x, long y)
2781 return y.CompareTo(x);
2785 public int Compare_ModeName_Ascending(long x, long y)
2787 var result = string.Compare(_statuses[x].ScreenName, _statuses[y].ScreenName);
2788 if (result == 0) result = x.CompareTo(y);
2793 public int Compare_ModeName_Descending(long x, long y)
2795 var result = string.Compare(_statuses[y].ScreenName, _statuses[x].ScreenName);
2796 if (result == 0) result = y.CompareTo(x);
2801 public int Compare_ModeNickName_Ascending(long x, long y)
2803 var result = string.Compare(_statuses[x].Nickname, _statuses[y].Nickname);
2804 if (result == 0) result = x.CompareTo(y);
2809 public int Compare_ModeNickName_Descending(long x, long y)
2811 var result = string.Compare(_statuses[y].Nickname, _statuses[x].Nickname);
2812 if (result == 0) result = y.CompareTo(x);
2817 public int Compare_ModeSource_Ascending(long x, long y)
2819 var result = string.Compare(_statuses[x].Source, _statuses[y].Source);
2820 if (result == 0) result = x.CompareTo(y);
2825 public int Compare_ModeSource_Descending(long x, long y)
2827 var result = string.Compare(_statuses[y].Source, _statuses[x].Source);
2828 if (result == 0) result = y.CompareTo(x);