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 Dictionary<string, 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 Dictionary<string, 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 Dictionary<string, 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 dmTab = GetTabByType(MyCommon.TabUsageType.DirectMessage);
1080 var favTab = GetTabByType(MyCommon.TabUsageType.Favorites);
1081 foreach (var id in _addedIds)
1083 var post = _statuses[id];
1084 var add = false; //通知リスト追加フラグ
1085 var mv = false; //移動フラグ(Recent追加有無)
1086 var rslt = MyCommon.HITRESULT.None;
1087 post.IsExcludeReply = false;
1088 foreach (var tab in _tabs.Values)
1090 rslt = tab.AddFiltered(post);
1091 if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
1093 if (rslt == MyCommon.HITRESULT.CopyAndMark) post.IsMark = true; //マークあり
1094 else if (rslt == MyCommon.HITRESULT.Move)
1097 post.IsMark = false;
1099 if (tab.Notify) add = true; //通知あり
1100 if (!string.IsNullOrEmpty(tab.SoundFile) && string.IsNullOrEmpty(_soundFile))
1102 _soundFile = tab.SoundFile; //wavファイル(未設定の場合のみ)
1104 post.FilterHit = true;
1108 if (rslt == MyCommon.HITRESULT.Exclude && tab.TabType == MyCommon.TabUsageType.Mentions)
1110 post.IsExcludeReply = true;
1112 post.FilterHit = false;
1115 if (!mv) //移動されなかったらRecentに追加
1117 homeTab.Add(post.StatusId, post.IsRead, true);
1118 if (!string.IsNullOrEmpty(homeTab.SoundFile) && string.IsNullOrEmpty(_soundFile)) _soundFile = homeTab.SoundFile;
1119 if (homeTab.Notify) add = true;
1121 if (post.IsReply && !post.IsExcludeReply) //除外ルール適用のないReplyならReplyタブに追加
1123 replyTab.Add(post.StatusId, post.IsRead, true);
1124 if (!string.IsNullOrEmpty(replyTab.SoundFile)) _soundFile = replyTab.SoundFile;
1125 if (replyTab.Notify) add = true;
1127 if (post.IsFav) //Fav済み発言だったらFavoritesタブに追加
1129 if (favTab.Contains(post.StatusId))
1137 favTab.Add(post.StatusId, post.IsRead, true);
1138 if (!string.IsNullOrEmpty(favTab.SoundFile) && string.IsNullOrEmpty(_soundFile)) _soundFile = favTab.SoundFile;
1139 if (favTab.Notify) add = true;
1142 if (add) _notifyPosts.Add(post);
1144 foreach (var tb in _tabs.Values)
1146 if (tb.IsInnerStorageTabType)
1150 if (tb.GetTemporaryCount() > 0)
1152 foreach (var post in tb.GetTemporaryPosts())
1155 foreach (var npost in _notifyPosts)
1157 if (npost.StatusId == post.StatusId)
1163 if (!exist) _notifyPosts.Add(post);
1165 if (!string.IsNullOrEmpty(tb.SoundFile))
1167 if (tb.TabType == MyCommon.TabUsageType.DirectMessage || string.IsNullOrEmpty(_soundFile))
1169 _soundFile = tb.SoundFile;
1178 public void AddPost(PostClass Item)
1182 if (string.IsNullOrEmpty(Item.RelTabName))
1187 if (_statuses.TryGetValue(Item.StatusId, out status))
1191 if (Item.RetweetedId == null)
1193 status.IsFav = true;
1202 return; //追加済みなら何もしない
1207 if (Item.IsFav && Item.RetweetedId != null) Item.IsFav = false;
1209 if (AppendSettingDialog.Instance.HideDuplicatedRetweets &&
1211 Item.RetweetedId != null &&
1212 this._retweets.TryGetValue(Item.RetweetedId.Value, out status) &&
1213 status.RetweetedCount > 0) return;
1215 if (BlockIds.Contains(Item.UserId))
1218 if (this.IsMuted(Item))
1221 _statuses.Add(Item.StatusId, Item);
1223 if (Item.RetweetedId != null)
1225 this.AddRetweet(Item);
1227 if (Item.IsFav && _retweets.ContainsKey(Item.StatusId))
1229 return; //Fav済みのRetweet元発言は追加しない
1231 if (_addedIds == null) _addedIds = new List<long>(); //タブ追加用IDコレクション準備
1232 _addedIds.Add(Item.StatusId);
1237 var tb = this.GetTabByType(MyCommon.TabUsageType.DirectMessage);
1238 if (tb.Contains(Item.StatusId)) return;
1239 tb.AddPostToInnerStorage(Item);
1246 this.Tabs.TryGetValue(Item.RelTabName, out tb);
1247 if (tb == null) return;
1248 if (tb.Contains(Item.StatusId)) return;
1249 //tb.Add(Item.StatusId, Item.IsRead, true);
1250 tb.AddPostToInnerStorage(Item);
1255 private bool IsMuted(PostClass post)
1257 // Recent以外のツイートと、リプライはミュート対象外
1258 // 参照: https://support.twitter.com/articles/20171399-muting-users-on-twitter
1259 if (string.IsNullOrEmpty(post.RelTabName) || post.IsReply)
1262 if (this.MuteUserIds.Contains(post.UserId))
1265 if (post.RetweetedByUserId != null && this.MuteUserIds.Contains(post.RetweetedByUserId.Value))
1271 private void AddRetweet(PostClass item)
1273 var retweetedId = item.RetweetedId.Value;
1275 //true:追加、False:保持済み
1277 if (_retweets.TryGetValue(retweetedId, out status))
1279 status.RetweetedCount++;
1280 if (status.RetweetedCount > 10)
1282 status.RetweetedCount = 0;
1288 item.RetweetedId.Value,
1296 item.RetweetedId.Value,
1300 item.IsExcludeReply,
1305 item.InReplyToStatusId,
1318 _retweets[retweetedId].RetweetedCount++;
1321 public void SetReadAllTab(bool Read, string TabName, int Index)
1323 //Read:true=既読へ false=未読へ
1324 var tb = _tabs[TabName];
1326 if (tb.UnreadManage == false) return; //未読管理していなければ終了
1328 var Id = tb.GetId(Index);
1330 PostClass post = tb.Posts[Id];
1332 if (post.IsRead == Read) return; //状態変更なければ終了
1341 this.SetNextUnreadId(Id, tb); //次の未読セット
1342 //他タブの最古未読IDはタブ切り替え時に。
1343 if (tb.IsInnerStorageTabType)
1347 if (_statuses.TryGetValue(Id, out status) && !status.IsRead)
1349 foreach (var tab in _tabs.Values)
1351 if (tab.UnreadManage &&
1352 !tab.IsInnerStorageTabType &&
1356 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1359 status.IsRead = true;
1365 foreach (var tab in _tabs.Values)
1369 !tab.IsInnerStorageTabType &&
1373 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1378 foreach (var tab in _tabs.Values)
1381 tab.IsInnerStorageTabType &&
1384 var tPost = tab.Posts[Id];
1387 if (tab.UnreadManage)
1390 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1392 tPost.IsRead = true;
1400 //if (tb.OldestUnreadId > Id || tb.OldestUnreadId = -1) tb.OldestUnreadId = Id
1401 if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1402 if (tb.IsInnerStorageTabType)
1406 if (_statuses.TryGetValue(Id, out status) && status.IsRead)
1408 foreach (var tab in _tabs.Values)
1410 if (tab.UnreadManage &&
1411 !tab.IsInnerStorageTabType &&
1415 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1418 status.IsRead = false;
1424 foreach (var tab in _tabs.Values)
1428 !tab.IsInnerStorageTabType &&
1432 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1437 foreach (var tab in _tabs.Values)
1440 tab.IsInnerStorageTabType &&
1443 var tPost = tab.Posts[Id];
1446 if (tab.UnreadManage)
1449 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1451 tPost.IsRead = false;
1459 // TODO: パフォーマンスを勘案して、戻すか決める
1460 public void SetRead(bool Read, string TabName, int Index)
1462 //Read:true=既読へ false=未読へ
1463 var tb = _tabs[TabName];
1465 if (tb.UnreadManage == false) return; //未読管理していなければ終了
1467 var Id = tb.GetId(Index);
1469 PostClass post = tb.Posts[Id];
1471 if (post.IsRead == Read) return; //状態変更なければ終了
1473 post.IsRead = Read; //指定の状態に変更
1480 this.SetNextUnreadId(Id, tb); //次の未読セット
1481 //他タブの最古未読IDはタブ切り替え時に。
1482 if (tb.IsInnerStorageTabType) return;
1483 foreach (var tab in _tabs.Values)
1487 !tab.IsInnerStorageTabType &&
1491 if (tab.OldestUnreadId == Id) tab.OldestUnreadId = -1;
1498 //if (tb.OldestUnreadId > Id || tb.OldestUnreadId == -1) tb.OldestUnreadId = Id;
1499 if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1500 if (tb.IsInnerStorageTabType) return;
1501 foreach (var tab in _tabs.Values)
1505 !tab.IsInnerStorageTabType &&
1509 if (tab.OldestUnreadId > Id) tab.OldestUnreadId = Id;
1516 public void SetRead()
1518 var tb = GetTabByType(MyCommon.TabUsageType.Home);
1519 if (tb.UnreadManage == false) return;
1523 for (int i = 0; i < tb.AllCount - 1; i++)
1525 var id = tb.GetId(i);
1527 var tPost = _statuses[id];
1528 if (!tPost.IsReply &&
1532 tPost.IsRead = true;
1533 this.SetNextUnreadId(id, tb); //次の未読セット
1534 foreach (var tab in _tabs.Values)
1536 if (tab.UnreadManage &&
1540 if (tab.OldestUnreadId == id) tab.OldestUnreadId = -1;
1548 public PostClass this[long ID]
1553 return _statuses.TryGetValue(ID, out status)
1555 : this.GetTabsInnerStorageType()
1556 .Where(t => t.Contains(ID))
1557 .Select(t => t.Posts[ID]).FirstOrDefault();
1561 public PostClass this[string TabName, int Index]
1566 if (!_tabs.TryGetValue(TabName, out tb)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1567 var id = tb.GetId(Index);
1568 if (id < 0) throw new ArgumentException("Index can't find. Index=" + Index.ToString() + "/TabName=" + TabName);
1571 return tb.Posts[tb.GetId(Index)];
1573 catch (Exception ex)
1575 throw new Exception("Index=" + Index.ToString() + "/TabName=" + TabName, ex);
1580 public PostClass[] this[string TabName, int StartIndex, int EndIndex]
1585 if (!_tabs.TryGetValue(TabName, out tb)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1586 var length = EndIndex - StartIndex + 1;
1587 var posts = new PostClass[length];
1588 for (int i = 0; i < length; i++)
1590 posts[i] = tb.Posts[tb.GetId(StartIndex + i)];
1596 //public ReadOnly int ItemCount
1602 // return _statuses.Count //DM,公式検索は除く
1607 public bool ContainsKey(long Id)
1612 return _statuses.ContainsKey(Id);
1616 public bool ContainsKey(long Id, string TabName)
1622 return _tabs.TryGetValue(TabName, out tab) && tab.Contains(Id);
1626 public void SetUnreadManage(bool Manage)
1630 foreach (var tab in _tabs.Values)
1632 if (tab.UnreadManage)
1637 var oldest = long.MaxValue;
1638 Dictionary<long, PostClass> posts = tab.Posts;
1639 foreach (var id in tab.BackupIds)
1641 if (!posts[id].IsRead)
1644 if (oldest > id) oldest = id;
1647 if (oldest == long.MaxValue) oldest = -1;
1648 tab.OldestUnreadId = oldest;
1649 tab.UnreadCount = cnt;
1656 foreach (var tab in _tabs.Values)
1658 if (tab.UnreadManage && tab.UnreadCount > 0)
1662 tab.UnreadCount = 0;
1663 tab.OldestUnreadId = -1;
1670 public void RenameTab(string Original, string NewName)
1672 var tb = _tabs[Original];
1673 _tabs.Remove(Original);
1674 tb.TabName = NewName;
1675 _tabs.Add(NewName, tb);
1678 public void FilterAll()
1682 var tbr = GetTabByType(MyCommon.TabUsageType.Home);
1683 var replyTab = GetTabByType(MyCommon.TabUsageType.Mentions);
1684 foreach (var tb in _tabs.Values.ToArray())
1686 if (tb.FilterModified)
1688 tb.FilterModified = false;
1689 var orgIds = tb.BackupIds;
1691 //////////////フィルター前のIDsを退避。どのタブにも含まれないidはrecentへ追加
1692 //////////////moveフィルターにヒットした際、recentに該当あればrecentから削除
1693 foreach (var post in _statuses.Values)
1695 if (post.IsDm) continue;
1696 var rslt = MyCommon.HITRESULT.None;
1697 rslt = tb.AddFiltered(post);
1700 case MyCommon.HITRESULT.CopyAndMark:
1701 post.IsMark = true; //マークあり
1702 post.FilterHit = true;
1704 case MyCommon.HITRESULT.Move:
1705 tbr.Remove(post.StatusId, post.IsRead);
1706 post.IsMark = false;
1707 post.FilterHit = true;
1709 case MyCommon.HITRESULT.Copy:
1710 post.IsMark = false;
1711 post.FilterHit = true;
1713 case MyCommon.HITRESULT.Exclude:
1714 if (tb.TabName == replyTab.TabName && post.IsReply) post.IsExcludeReply = true;
1715 if (post.IsFav) GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, true);
1716 post.FilterHit = false;
1718 case MyCommon.HITRESULT.None:
1719 if (tb.TabName == replyTab.TabName && post.IsReply) replyTab.Add(post.StatusId, post.IsRead, true);
1720 if (post.IsFav) GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, true);
1721 post.FilterHit = false;
1725 tb.AddSubmit(); //振分確定
1726 foreach (var id in orgIds)
1729 foreach (var tbTemp in _tabs.Values.ToArray())
1731 if (tbTemp.Contains(id))
1737 if (!hit) tbr.Add(id, _statuses[id].IsRead, false);
1745 public long[] GetId(string TabName, ListView.SelectedIndexCollection IndexCollection)
1747 if (IndexCollection.Count == 0) return null;
1749 var tb = _tabs[TabName];
1750 var Ids = new long[IndexCollection.Count];
1751 for (int i = 0; i < Ids.Length; i++)
1753 Ids[i] = tb.GetId(IndexCollection[i]);
1758 public long GetId(string TabName, int Index)
1760 return _tabs[TabName].GetId(Index);
1763 public int[] IndexOf(string TabName, long[] Ids)
1765 if (Ids == null) return null;
1766 var idx = new int[Ids.Length];
1767 var tb = _tabs[TabName];
1768 for (int i = 0; i < Ids.Length; i++)
1770 idx[i] = tb.IndexOf(Ids[i]);
1775 public int IndexOf(string TabName, long Id)
1777 return _tabs[TabName].IndexOf(Id);
1780 public void ClearTabIds(string TabName)
1785 var tb = _tabs[TabName];
1786 if (!tb.IsInnerStorageTabType)
1788 foreach (var Id in tb.BackupIds)
1791 foreach (var tab in _tabs.Values)
1793 if (tab.Contains(Id))
1799 if (!Hit) _statuses.Remove(Id);
1808 public void SetTabUnreadManage(string TabName, bool Manage)
1810 var tb = _tabs[TabName];
1816 var oldest = long.MaxValue;
1817 Dictionary<long, PostClass> posts = tb.Posts;
1818 foreach (var id in tb.BackupIds)
1820 if (!posts[id].IsRead)
1823 if (oldest > id) oldest = id;
1826 if (oldest == long.MaxValue) oldest = -1;
1827 tb.OldestUnreadId = oldest;
1828 tb.UnreadCount = cnt;
1832 tb.OldestUnreadId = -1;
1836 tb.UnreadManage = Manage;
1839 public void RefreshOwl(List<long> follower)
1843 if (follower.Count > 0)
1845 foreach (var post in _statuses.Values)
1847 //if (post.UserId = 0 || post.IsDm) Continue For
1854 post.IsOwl = !follower.Contains(post.UserId);
1860 foreach (var post in _statuses.Values)
1868 public TabClass GetTabByType(MyCommon.TabUsageType tabType)
1870 //Home,Mentions,DM,Favは1つに制限する
1871 //その他のタイプを指定されたら、最初に合致したものを返す
1875 foreach (var tab in _tabs.Values)
1877 if (tab.TabType == tabType) return tab;
1883 public List<TabClass> GetTabsByType(MyCommon.TabUsageType tabType)
1889 var tbs = new List<TabClass>();
1890 foreach (var tb in _tabs.Values)
1892 if ((tabType & tb.TabType) == tb.TabType) tbs.Add(tb);
1898 public List<TabClass> GetTabsInnerStorageType()
1904 var tbs = new List<TabClass>();
1905 foreach (var tb in _tabs.Values)
1907 if (tb.IsInnerStorageTabType) tbs.Add(tb);
1913 public TabClass GetTabByName(string tabName)
1918 return _tabs.TryGetValue(tabName, out tab)
1925 public bool IsDefaultTab(string tabName)
1928 if (tabName != null &&
1929 _tabs.TryGetValue(tabName, out tab) &&
1930 (tab.TabType == MyCommon.TabUsageType.Home ||
1931 tab.TabType == MyCommon.TabUsageType.Mentions ||
1932 tab.TabType == MyCommon.TabUsageType.DirectMessage ||
1933 tab.TabType == MyCommon.TabUsageType.Favorites))
1942 public bool IsDistributableTab(string tabName)
1945 if (tabName != null &&
1946 _tabs.TryGetValue(tabName, out tab) &&
1947 (tab.TabType == MyCommon.TabUsageType.Mentions ||
1948 tab.TabType == MyCommon.TabUsageType.UserDefined))
1956 public string GetUniqueTabName()
1958 var tabNameTemp = "MyTab" + (_tabs.Count + 1).ToString();
1959 for (int i = 2; i <= 100; i++)
1961 if (_tabs.ContainsKey(tabNameTemp))
1963 tabNameTemp = "MyTab" + (_tabs.Count + i).ToString();
1973 public Dictionary<long, PostClass> Posts
1983 public sealed class TabClass
1985 private bool _unreadManage = false;
1986 private List<PostFilterRule> _filters;
1987 private int _unreadCount = 0;
1988 private List<long> _ids;
1989 private List<TemporaryId> _tmpIds = new List<TemporaryId>();
1990 private MyCommon.TabUsageType _tabType = MyCommon.TabUsageType.Undefined;
1993 private IdComparerClass _sorter = new IdComparerClass();
1995 private readonly object _lockObj = new object();
1997 public string User { get; set; }
2001 private string _searchLang = "";
2002 private string _searchWords = "";
2003 private string _nextPageQuery = "";
2005 public string SearchLang
2014 _searchLang = value;
2017 public string SearchWords
2021 return _searchWords;
2026 _searchWords = value.Trim();
2030 public string NextPageQuery
2034 return _nextPageQuery;
2038 _nextPageQuery = value;
2042 public int GetSearchPage(int count)
2044 return ((_ids.Count / count) + 1);
2046 private Dictionary<string, string> _beforeQuery = new Dictionary<string, string>();
2047 public void SaveQuery(bool more)
2049 var qry = new Dictionary<string, string>();
2050 if (string.IsNullOrEmpty(_searchWords))
2055 qry.Add("q", _searchWords);
2056 if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2060 public bool IsQueryChanged()
2062 var qry = new Dictionary<string, string>();
2063 if (!string.IsNullOrEmpty(_searchWords))
2065 qry.Add("q", _searchWords);
2066 if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2068 if (qry.Count != _beforeQuery.Count) return true;
2070 foreach (var kvp in qry)
2072 if (!_beforeQuery.ContainsKey(kvp.Key) || _beforeQuery[kvp.Key] != kvp.Value)
2083 private ListElement _listInfo;
2084 public ListElement ListInfo
2098 public PostClass RelationTargetPost { get; set; }
2101 public long OldestId = long.MaxValue;
2104 public long SinceId { get; set; }
2107 public Dictionary<long, PostClass> Posts { get; private set; }
2109 private Dictionary<long, PostClass> _innerPosts;
2111 public PostClass[] GetTemporaryPosts()
2113 var tempPosts = new List<PostClass>();
2114 if (_tmpIds.Count == 0) return tempPosts.ToArray();
2115 foreach (var tempId in _tmpIds)
2117 tempPosts.Add(Posts[tempId.Id]);
2119 return tempPosts.ToArray();
2122 public int GetTemporaryCount()
2124 return _tmpIds.Count;
2127 private struct TemporaryId
2132 public TemporaryId(long argId, bool argRead)
2141 _innerPosts = new Dictionary<long, PostClass>();
2142 Posts = _innerPosts;
2144 OldestUnreadId = -1;
2146 _filters = new List<PostFilterRule>();
2150 _unreadManage = true;
2151 _ids = new List<long>();
2152 this.OldestUnreadId = -1;
2153 _tabType = MyCommon.TabUsageType.Undefined;
2157 public TabClass(string TabName, MyCommon.TabUsageType TabType, ListElement list) : this()
2159 this.TabName = TabName;
2160 this.TabType = TabType;
2161 this.ListInfo = list;
2166 if (_sorter.Mode == IdComparerClass.ComparerMode.Id)
2168 _ids.Sort(_sorter.CmpMethod());
2172 if (_sorter.Order == SortOrder.Ascending)
2174 switch (_sorter.Mode)
2176 case IdComparerClass.ComparerMode.Data:
2177 ar = _ids.OrderBy(n => _sorter.posts[n].TextFromApi).ToArray();
2179 case IdComparerClass.ComparerMode.Name:
2180 ar = _ids.OrderBy(n => _sorter.posts[n].ScreenName).ToArray();
2182 case IdComparerClass.ComparerMode.Nickname:
2183 ar = _ids.OrderBy(n => _sorter.posts[n].Nickname).ToArray();
2185 case IdComparerClass.ComparerMode.Source:
2186 ar = _ids.OrderBy(n => _sorter.posts[n].Source).ToArray();
2192 switch (_sorter.Mode)
2194 case IdComparerClass.ComparerMode.Data:
2195 ar = _ids.OrderByDescending(n => _sorter.posts[n].TextFromApi).ToArray();
2197 case IdComparerClass.ComparerMode.Name:
2198 ar = _ids.OrderByDescending(n => _sorter.posts[n].ScreenName).ToArray();
2200 case IdComparerClass.ComparerMode.Nickname:
2201 ar = _ids.OrderByDescending(n => _sorter.posts[n].Nickname).ToArray();
2203 case IdComparerClass.ComparerMode.Source:
2204 ar = _ids.OrderByDescending(n => _sorter.posts[n].Source).ToArray();
2208 _ids = new List<long>(ar);
2211 public IdComparerClass Sorter
2220 private void Add(long ID, bool Read)
2222 if (this._ids.Contains(ID)) return;
2224 if (this.Sorter.Mode == IdComparerClass.ComparerMode.Id)
2226 if (this.Sorter.Order == SortOrder.Ascending)
2232 this._ids.Insert(0, ID);
2240 if (!Read && this._unreadManage)
2242 this._unreadCount++;
2243 if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2244 //if (this.OldestUnreadId == -1)
2246 // this.OldestUnreadId = ID;
2250 // if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2255 public void Add(long ID, bool Read, bool Temporary)
2263 _tmpIds.Add(new TemporaryId(ID, Read));
2268 public MyCommon.HITRESULT AddFiltered(PostClass post)
2270 if (this.IsInnerStorageTabType) return MyCommon.HITRESULT.None;
2272 var rslt = MyCommon.HITRESULT.None;
2274 lock (this._lockObj)
2276 foreach (var ft in _filters)
2280 switch (ft.ExecFilter(post)) //フィルタクラスでヒット判定
2282 case MyCommon.HITRESULT.None:
2284 case MyCommon.HITRESULT.Copy:
2285 if (rslt != MyCommon.HITRESULT.CopyAndMark) rslt = MyCommon.HITRESULT.Copy;
2287 case MyCommon.HITRESULT.CopyAndMark:
2288 rslt = MyCommon.HITRESULT.CopyAndMark;
2290 case MyCommon.HITRESULT.Move:
2291 rslt = MyCommon.HITRESULT.Move;
2293 case MyCommon.HITRESULT.Exclude:
2294 rslt = MyCommon.HITRESULT.Exclude;
2298 catch (NullReferenceException)
2300 // ExecFilterでNullRef出る場合あり。暫定対応
2301 MyCommon.TraceOut("ExecFilterでNullRef: " + ft.ToString());
2302 rslt = MyCommon.HITRESULT.None;
2309 if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
2311 _tmpIds.Add(new TemporaryId(post.StatusId, post.IsRead));
2314 return rslt; //マーク付けは呼び出し元で行うこと
2318 public void AddPostToInnerStorage(PostClass Post)
2320 if (_innerPosts.ContainsKey(Post.StatusId)) return;
2321 _innerPosts.Add(Post.StatusId, Post);
2322 _tmpIds.Add(new TemporaryId(Post.StatusId, Post.IsRead));
2325 public void AddSubmit(ref bool isMentionIncluded)
2327 if (_tmpIds.Count == 0) return;
2328 _tmpIds.Sort((x, y) => x.Id.CompareTo(y.Id));
2329 foreach (var tId in _tmpIds)
2331 if (this.TabType == MyCommon.TabUsageType.Mentions && TabInformations.GetInstance()[tId.Id].IsReply) isMentionIncluded = true;
2332 this.Add(tId.Id, tId.Read);
2337 public void AddSubmit()
2339 bool mention = false;
2340 AddSubmit(ref mention);
2343 public void Remove(long Id)
2345 if (!this._ids.Contains(Id)) return;
2346 this._ids.Remove(Id);
2347 if (this.IsInnerStorageTabType) _innerPosts.Remove(Id);
2350 public void Remove(long Id, bool Read)
2352 if (!this._ids.Contains(Id)) return;
2354 if (!Read && this._unreadManage)
2356 this._unreadCount--;
2357 this.OldestUnreadId = -1;
2360 this._ids.Remove(Id);
2361 if (this.IsInnerStorageTabType) _innerPosts.Remove(Id);
2364 public bool UnreadManage
2368 return _unreadManage;
2372 this._unreadManage = value;
2375 this.OldestUnreadId = -1;
2376 this._unreadCount = 0;
2381 // v1.0.5で「タブを固定(Locked)」から「タブを保護(Protected)」に名称変更
2382 [XmlElement(ElementName = "Locked")]
2383 public bool Protected { get; set; }
2385 public bool Notify { get; set; }
2387 public string SoundFile { get; set; }
2390 public long OldestUnreadId { get; set; }
2393 public int UnreadCount
2397 return this.UnreadManage && AppendSettingDialog.Instance.UnreadManage ? _unreadCount : 0;
2401 if (value < 0) value = 0;
2402 _unreadCount = value;
2410 return this._ids.Count;
2414 public PostFilterRule[] GetFilters()
2416 lock (this._lockObj)
2418 return _filters.ToArray();
2422 public void RemoveFilter(PostFilterRule filter)
2424 lock (this._lockObj)
2426 _filters.Remove(filter);
2427 this.FilterModified = true;
2431 public bool AddFilter(PostFilterRule filter)
2433 lock (this._lockObj)
2435 if (_filters.Contains(filter)) return false;
2436 _filters.Add(filter);
2437 this.FilterModified = true;
2442 public void EditFilter(PostFilterRule original, PostFilterRule modified)
2444 original.FilterBody = modified.FilterBody;
2445 original.FilterName = modified.FilterName;
2446 original.UseNameField = modified.UseNameField;
2447 original.FilterByUrl = modified.FilterByUrl;
2448 original.UseRegex = modified.UseRegex;
2449 original.CaseSensitive = modified.CaseSensitive;
2450 original.FilterRt = modified.FilterRt;
2451 original.UseLambda = modified.UseLambda;
2452 original.FilterSource = modified.FilterSource;
2453 original.ExFilterBody = modified.ExFilterBody;
2454 original.ExFilterName = modified.ExFilterName;
2455 original.ExUseNameField = modified.ExUseNameField;
2456 original.ExFilterByUrl = modified.ExFilterByUrl;
2457 original.ExUseRegex = modified.ExUseRegex;
2458 original.ExCaseSensitive = modified.ExCaseSensitive;
2459 original.ExFilterRt = modified.ExFilterRt;
2460 original.ExUseLambda = modified.ExUseLambda;
2461 original.ExFilterSource = modified.ExFilterSource;
2462 original.MoveMatches = modified.MoveMatches;
2463 original.MarkMatches = modified.MarkMatches;
2464 this.FilterModified = true;
2468 public List<PostFilterRule> Filters
2472 lock (this._lockObj)
2479 lock (this._lockObj)
2486 public PostFilterRule[] FilterArray
2490 lock (this._lockObj)
2492 return _filters.ToArray();
2497 lock (this._lockObj)
2499 foreach (var filters in value)
2501 _filters.Add(filters);
2506 public bool Contains(long ID)
2508 return _ids.Contains(ID);
2511 public void ClearIDs()
2516 this.OldestUnreadId = -1;
2517 _innerPosts.Clear();
2520 public long GetId(int Index)
2522 return Index < _ids.Count ? _ids[Index] : -1;
2525 public int IndexOf(long ID)
2527 return _ids.IndexOf(ID);
2531 public bool FilterModified { get; set; }
2533 public long[] BackupIds
2537 return _ids.ToArray();
2541 public string TabName { get; set; }
2543 public MyCommon.TabUsageType TabType
2552 if (this.IsInnerStorageTabType)
2554 Posts = _innerPosts;
2558 Posts = TabInformations.GetInstance().Posts;
2560 _sorter.posts = Posts;
2564 public bool IsInnerStorageTabType
2568 if (_tabType == MyCommon.TabUsageType.PublicSearch ||
2569 _tabType == MyCommon.TabUsageType.DirectMessage ||
2570 _tabType == MyCommon.TabUsageType.Lists ||
2571 _tabType == MyCommon.TabUsageType.UserTimeline ||
2572 _tabType == MyCommon.TabUsageType.Related)
2585 public sealed class IdComparerClass : IComparer<long>
2590 public enum ComparerMode
2599 private SortOrder _order;
2600 private ComparerMode _mode;
2601 private Dictionary<long, PostClass> _statuses;
2602 private Comparison<long> _CmpMethod;
2605 /// 昇順か降順か Setの際は同時に比較関数の切り替えを行う
2607 public SortOrder Order
2616 SetCmpMethod(_mode, _order);
2621 /// 並び替えの方法 Setの際は同時に比較関数の切り替えを行う
2623 public ComparerMode Mode
2632 SetCmpMethod(_mode, _order);
2637 /// ListViewItemComparerクラスのコンストラクタ(引数付は未使用)
2639 /// <param name="col">並び替える列番号</param>
2640 /// <param name="ord">昇順か降順か</param>
2641 /// <param name="cmod">並び替えの方法</param>
2643 public IdComparerClass()
2645 _order = SortOrder.Ascending;
2646 _mode = ComparerMode.Id;
2647 SetCmpMethod(_mode, _order);
2650 public Dictionary<long, PostClass> posts
2662 // 指定したソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2663 public Comparison<long> CmpMethod(ComparerMode _sortmode, SortOrder _sortorder)
2667 Comparison<long> _method = null;
2668 if (_sortorder == SortOrder.Ascending)
2673 case ComparerMode.Data:
2674 _method = Compare_ModeData_Ascending;
2676 case ComparerMode.Id:
2677 _method = Compare_ModeId_Ascending;
2679 case ComparerMode.Name:
2680 _method = Compare_ModeName_Ascending;
2682 case ComparerMode.Nickname:
2683 _method = Compare_ModeNickName_Ascending;
2685 case ComparerMode.Source:
2686 _method = Compare_ModeSource_Ascending;
2695 case ComparerMode.Data:
2696 _method = Compare_ModeData_Descending;
2698 case ComparerMode.Id:
2699 _method = Compare_ModeId_Descending;
2701 case ComparerMode.Name:
2702 _method = Compare_ModeName_Descending;
2704 case ComparerMode.Nickname:
2705 _method = Compare_ModeNickName_Descending;
2707 case ComparerMode.Source:
2708 _method = Compare_ModeSource_Descending;
2716 // ソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
2717 // (overload 現在の使用中の比較関数のアドレスを返す)
2718 public Comparison<long> CmpMethod()
2726 // ソートモードとソートオーダーに従い比較関数のアドレスを切り替え
2727 private void SetCmpMethod(ComparerMode mode, SortOrder order)
2729 _CmpMethod = this.CmpMethod(mode, order);
2732 //xがyより小さいときはマイナスの数、大きいときはプラスの数、
2733 //同じときは0を返す (こちらは未使用 一応比較関数群呼び出しの形のまま残しておく)
2734 int IComparer<long>.Compare(long x, long y)
2736 return _CmpMethod(x, y);
2739 // 比較用関数群 いずれもステータスIDの順序を考慮する
2741 public int Compare_ModeData_Ascending(long x, long y)
2743 var result = string.Compare(_statuses[x].TextFromApi, _statuses[y].TextFromApi);
2744 if (result == 0) result = x.CompareTo(y);
2749 public int Compare_ModeData_Descending(long x, long y)
2751 var result = string.Compare(_statuses[y].TextFromApi, _statuses[x].TextFromApi);
2752 if (result == 0) result = y.CompareTo(x);
2757 public int Compare_ModeId_Ascending(long x, long y)
2759 return x.CompareTo(y);
2763 public int Compare_ModeId_Descending(long x, long y)
2765 return y.CompareTo(x);
2769 public int Compare_ModeName_Ascending(long x, long y)
2771 var result = string.Compare(_statuses[x].ScreenName, _statuses[y].ScreenName);
2772 if (result == 0) result = x.CompareTo(y);
2777 public int Compare_ModeName_Descending(long x, long y)
2779 var result = string.Compare(_statuses[y].ScreenName, _statuses[x].ScreenName);
2780 if (result == 0) result = y.CompareTo(x);
2785 public int Compare_ModeNickName_Ascending(long x, long y)
2787 var result = string.Compare(_statuses[x].Nickname, _statuses[y].Nickname);
2788 if (result == 0) result = x.CompareTo(y);
2793 public int Compare_ModeNickName_Descending(long x, long y)
2795 var result = string.Compare(_statuses[y].Nickname, _statuses[x].Nickname);
2796 if (result == 0) result = y.CompareTo(x);
2801 public int Compare_ModeSource_Ascending(long x, long y)
2803 var result = string.Compare(_statuses[x].Source, _statuses[y].Source);
2804 if (result == 0) result = x.CompareTo(y);
2809 public int Compare_ModeSource_Descending(long x, long y)
2811 var result = string.Compare(_statuses[y].Source, _statuses[x].Source);
2812 if (result == 0) result = y.CompareTo(x);