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 // All rights reserved.
10 // This file is part of OpenTween.
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General Public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 // You should have received a copy of the GNU General Public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
28 using System.Collections.Generic;
30 using System.Linq.Expressions;
31 using System.Reflection;
33 using System.Text.RegularExpressions;
34 using System.Windows.Forms;
35 using System.Xml.Serialization;
39 public sealed class PostClass : ICloneable
41 public class StatusGeo
43 public double Lng { get; set; }
44 public double Lat { get; set; }
47 private string _textFromApi;
48 private string _ImageUrl;
49 private string _screenName;
50 private DateTime _createdAt;
51 private long _statusId;
55 private bool _IsReply;
56 private bool _IsExcludeReply;
57 private bool _IsProtect;
60 private string _InReplyToUser;
61 private long _InReplyToStatusId;
62 private string _Source;
63 private string _SourceHtml;
64 private List<string> _ReplyToList = new List<string>();
67 private States _states = States.None;
69 private bool _FilterHit;
70 private string _RetweetedBy = "";
71 private long _RetweetedId = 0;
72 private string _SearchTabName = "";
73 private bool _IsDeleted = false;
74 private long _InReplyToUserId = 0;
75 private StatusGeo _postGeo = new StatusGeo();
76 public int RetweetedCount { get; set; }
77 public long RetweetedByUserId { get; set; }
78 public Dictionary<string, string> Media { get; set; }
90 public PostClass(string Nickname,
104 string InReplyToUser,
105 long InReplyToStatusId,
108 List<string> ReplyToList,
119 _textFromApi = textFromApi;
120 _ImageUrl = ImageUrl;
121 _screenName = screenName;
122 _createdAt = createdAt;
123 _statusId = statusId;
128 _IsExcludeReply = IsExcludeReply;
129 _IsProtect = IsProtect;
132 _InReplyToUser = InReplyToUser;
133 _InReplyToStatusId = InReplyToStatusId;
135 _SourceHtml = SourceHtml;
136 _ReplyToList = ReplyToList;
140 _FilterHit = FilterHit;
141 _RetweetedBy = RetweetedBy;
142 _RetweetedId = RetweetedId;
149 RetweetedByUserId = 0;
150 Media = new Dictionary<string, string>();
153 public string Nickname
164 public string TextFromApi
172 _textFromApi = value;
175 public string ImageUrl
186 public string ScreenName
197 public DateTime CreatedAt
223 if (this.RetweetedId > 0 && TabInformations.GetInstance().RetweetSource(this.RetweetedId) != null)
225 return TabInformations.GetInstance().RetweetSource(this.RetweetedId).IsFav;
235 if (this.RetweetedId > 0 && TabInformations.GetInstance().RetweetSource(this.RetweetedId) != null)
237 TabInformations.GetInstance().RetweetSource(this.RetweetedId).IsFav = value;
274 public bool IsExcludeReply
278 return _IsExcludeReply;
282 _IsExcludeReply = value;
285 public bool IsProtect
295 _states = _states | States.Protect;
299 _states = _states & ~States.Protect;
325 _states = _states | States.Mark;
329 _states = _states & ~States.Mark;
334 public string InReplyToUser
338 return _InReplyToUser;
342 _InReplyToUser = value;
345 public long InReplyToStatusId
349 return _InReplyToStatusId;
355 _states = _states | States.Reply;
359 _states = _states & ~States.Reply;
361 _InReplyToStatusId = value;
365 public long InReplyToUserId
369 return _InReplyToUserId;
373 _InReplyToUserId = value;
387 public string SourceHtml
398 public List<string> ReplyToList
406 _ReplyToList = value;
431 //public readonly int StatusIndex
449 public bool FilterHit
460 public string RetweetedBy
468 _RetweetedBy = value;
471 public long RetweetedId
479 _RetweetedId = value;
482 public string RelTabName
486 return _SearchTabName;
490 _SearchTabName = value;
493 public bool IsDeleted
503 this.InReplyToStatusId = 0;
504 this.InReplyToUser = "";
505 this.InReplyToUserId = 0;
506 this.IsReply = false;
507 this.ReplyToList = new List<string>();
508 this._states = States.None;
514 public int FavoritedCount { get; set; }
516 public StatusGeo PostGeo
524 if (value != null && (value.Lat != 0 || value.Lng != 0))
526 _states |= States.Geo;
530 _states &= ~States.Geo;
536 public int StateIndex
540 return (int)_states - 1;
544 public PostClass Copy()
546 var post = (PostClass)((ICloneable)this).Clone();
547 post.ReplyToList = new List<string>(this.ReplyToList);
551 public override bool Equals(object obj)
553 if (obj == null || this.GetType() != obj.GetType()) return false;
554 return this.Equals((PostClass)obj);
557 public bool Equals(PostClass other)
559 if (other == null) return false;
560 return (this.Nickname == other.Nickname) &&
561 (this.TextFromApi == other.TextFromApi) &&
562 (this.ImageUrl == other.ImageUrl) &&
563 (this.ScreenName == other.ScreenName) &&
564 (this.CreatedAt == other.CreatedAt) &&
565 (this.StatusId == other.StatusId) &&
566 (this.IsFav == other.IsFav) &&
567 (this.Text == other.Text) &&
568 (this.IsRead == other.IsRead) &&
569 (this.IsReply == other.IsReply) &&
570 (this.IsExcludeReply == other.IsExcludeReply) &&
571 (this.IsProtect == other.IsProtect) &&
572 (this.IsOwl == other.IsOwl) &&
573 (this.IsMark == other.IsMark) &&
574 (this.InReplyToUser == other.InReplyToUser) &&
575 (this.InReplyToStatusId == other.InReplyToStatusId) &&
576 (this.Source == other.Source) &&
577 (this.SourceHtml == other.SourceHtml) &&
578 (this.ReplyToList.Equals(other.ReplyToList)) &&
579 (this.IsMe == other.IsMe) &&
580 (this.IsDm == other.IsDm) &&
581 (this.UserId == other.UserId) &&
582 (this.FilterHit == other.FilterHit) &&
583 (this.RetweetedBy == other.RetweetedBy) &&
584 (this.RetweetedId == other.RetweetedId) &&
585 (this.RelTabName == other.RelTabName) &&
586 (this.IsDeleted == other.IsDeleted) &&
587 (this.InReplyToUserId == other.InReplyToUserId);
591 public override int GetHashCode()
593 return this.StatusId.GetHashCode();
596 #region "IClonable.Clone"
597 object ICloneable.Clone()
599 return this.MemberwiseClone();
604 public sealed class TabInformations
606 //個別タブの情報をDictionaryで保持
607 private IdComparerClass _sorter;
608 private Dictionary<string, TabClass> _tabs = new Dictionary<string, TabClass>();
609 private Dictionary<long, PostClass> _statuses = new Dictionary<long, PostClass>();
610 private List<long> _addedIds;
611 private List<long> _deletedIds = new List<long>();
612 private Dictionary<long, PostClass> _retweets = new Dictionary<long, PostClass>();
613 private Stack<TabClass> _removedTab = new Stack<TabClass>();
614 private List<ScrubGeoInfo> _scrubGeo = new List<ScrubGeoInfo>();
616 private class ScrubGeoInfo
618 public long UserId = 0;
619 public long UpToStatusId = 0;
622 public List<long> BlockIds = new List<long>();
625 //AddPost(複数回) -> DistributePosts -> SubmitUpdate
628 private int _addCount;
629 private string _soundFile;
630 private List<PostClass> _notifyPosts;
631 private readonly object LockObj = new object();
632 private readonly object LockUnread = new object();
634 private static TabInformations _instance = new TabInformations();
637 private List<ListElement> _lists = new List<ListElement>();
639 private TabInformations()
641 _sorter = new IdComparerClass();
642 RemovedTab = _removedTab;
645 public static TabInformations GetInstance()
647 return _instance; //singleton
650 public List<ListElement> SubscribableLists
658 if (value != null && value.Count > 0)
660 foreach (var tb in this.GetTabsByType(MyCommon.TabUsageType.Lists))
662 foreach (var list in value)
664 if (tb.ListInfo.Id == list.Id)
676 public bool AddTab(string TabName, MyCommon.TabUsageType TabType, ListElement List)
678 if (_tabs.ContainsKey(TabName)) return false;
679 _tabs.Add(TabName, new TabClass(TabName, TabType, List));
680 _tabs[TabName].Sorter.Mode = _sorter.Mode;
681 _tabs[TabName].Sorter.Order = _sorter.Order;
685 //public void AddTab(string TabName, TabClass Tab)
687 // _tabs.Add(TabName, Tab);
690 public void RemoveTab(string TabName)
694 if (IsDefaultTab(TabName)) return; //念のため
695 if (!_tabs[TabName].IsInnerStorageTabType)
697 var homeTab = GetTabByType(MyCommon.TabUsageType.Home);
698 var dmName = GetTabByType(MyCommon.TabUsageType.DirectMessage).TabName;
700 for (int idx = 0; idx < _tabs[TabName].AllCount; ++idx)
703 var Id = _tabs[TabName].GetId(idx);
704 if (Id < 0) continue;
705 foreach (var key in _tabs.Keys)
707 if (key != TabName && key != dmName)
709 if (_tabs[key].Contains(Id))
716 if (!exist) homeTab.Add(Id, _statuses[Id].IsRead, false);
719 _removedTab.Push(_tabs[TabName]);
720 _tabs.Remove(TabName);
724 public Stack<TabClass> RemovedTab;
726 public bool ContainsTab(string TabText)
728 return _tabs.ContainsKey(TabText);
731 public bool ContainsTab(TabClass ts)
733 return _tabs.ContainsValue(ts);
736 public Dictionary<string, TabClass> Tabs
748 public Dictionary<string, TabClass>.KeyCollection KeysTab
756 public void SortPosts()
758 foreach (var key in _tabs.Keys)
764 public SortOrder SortOrder
768 return _sorter.Order;
772 _sorter.Order = value;
773 foreach (var key in _tabs.Keys)
775 _tabs[key].Sorter.Order = value;
780 public IdComparerClass.ComparerMode SortMode
788 _sorter.Mode = value;
789 foreach (var key in _tabs.Keys)
791 _tabs[key].Sorter.Mode = value;
796 public SortOrder ToggleSortOrder(IdComparerClass.ComparerMode SortMode)
798 if (_sorter.Mode == SortMode)
800 if (_sorter.Order == SortOrder.Ascending)
802 _sorter.Order = SortOrder.Descending;
806 _sorter.Order = SortOrder.Ascending;
808 foreach (var key in _tabs.Keys)
810 _tabs[key].Sorter.Order = _sorter.Order;
815 _sorter.Mode = SortMode;
816 _sorter.Order = SortOrder.Ascending;
817 foreach (var key in _tabs.Keys)
819 _tabs[key].Sorter.Mode = SortMode;
820 _tabs[key].Sorter.Order = SortOrder.Ascending;
824 return _sorter.Order;
827 // public PostClass RetweetSource(long Id)
831 // if (_retweets.ContainsKey(Id))
833 // return _retweets[Id];
841 public PostClass RetweetSource(long Id)
843 if (_retweets.ContainsKey(Id))
845 return _retweets[Id];
853 public void RemoveFavPost(long Id)
857 PostClass post = null;
858 var tab = this.GetTabByType(MyCommon.TabUsageType.Favorites);
859 var tn = tab.TabName;
860 if (_statuses.ContainsKey(Id))
862 post = _statuses[Id];
864 var tType = tab.TabType;
865 if (tab.Contains(Id))
867 if (tab.UnreadManage && !post.IsRead) //未読管理
872 this.SetNextUnreadId(Id, tab);
877 //FavタブからRetweet発言を削除する場合は、他の同一参照Retweetも削除
878 if (tType == MyCommon.TabUsageType.Favorites && post.RetweetedId > 0)
880 for (int i = 0; i < tab.AllCount; i++)
882 PostClass rPost = null;
887 catch (ArgumentOutOfRangeException)
891 if (rPost.RetweetedId > 0 && rPost.RetweetedId == post.RetweetedId)
893 if (tab.UnreadManage && !rPost.IsRead) //未読管理
898 this.SetNextUnreadId(rPost.StatusId, tab);
901 tab.Remove(rPost.StatusId);
906 //TabType=PublicSearchの場合(Postの保存先がTabClass内)
907 //if (tab.Contains(StatusId) &&
908 // (tab.TabType = MyCommon.TabUsageType.PublicSearch || tab.TabType = MyCommon.TabUsageType.DirectMessage))
910 // post = tab.Posts[StatusId];
911 // if (tab.UnreadManage && !post.IsRead) //未読管理
915 // tab.UnreadCount--;
916 // this.SetNextUnreadId(StatusId, tab);
919 // tab.Remove(StatusId);
924 public void ScrubGeoReserve(long id, long upToStatusId)
928 //this._scrubGeo.Add(new ScrubGeoInfo With {.UserId = id, .UpToStatusId = upToStatusId});
929 this.ScrubGeo(id, upToStatusId);
933 private void ScrubGeo(long userId, long upToStatusId)
937 var userPosts = from post in this._statuses.Values
938 where post.UserId == userId && post.UserId <= upToStatusId
941 foreach (var p in userPosts)
943 p.PostGeo = new PostClass.StatusGeo();
946 var userPosts2 = from tb in this.GetTabsInnerStorageType()
947 from post in tb.Posts.Values
948 where post.UserId == userId && post.UserId <= upToStatusId
951 foreach (var p in userPosts2)
953 p.PostGeo = new PostClass.StatusGeo();
958 public void RemovePostReserve(long id)
962 this._deletedIds.Add(id);
963 this.DeletePost(id); //UI選択行がずれるため、RemovePostは使用しない
967 public void RemovePost(long Id)
971 PostClass post = null;
972 //if (_statuses.ContainsKey(Id))
974 foreach (var key in _tabs.Keys)
976 var tab = _tabs[key];
977 if (tab.Contains(Id))
979 if (!tab.IsInnerStorageTabType)
981 post = _statuses[Id];
982 if (tab.UnreadManage && !post.IsRead) //未読管理
987 this.SetNextUnreadId(Id, tab);
991 else //未読数がずれる可能性があるためtab.Postsの未読も確認する
993 if (tab.UnreadManage && !tab.Posts[Id].IsRead) //未読管理
998 this.SetNextUnreadId(Id, tab);
1005 if (_statuses.ContainsKey(Id)) _statuses.Remove(Id);
1009 private void DeletePost(long Id)
1013 PostClass post = null;
1014 if (_statuses.ContainsKey(Id))
1016 post = _statuses[Id];
1017 post.IsDeleted = true;
1019 foreach (var tb in this.GetTabsInnerStorageType())
1021 if (tb.Contains(Id))
1023 post = tb.Posts[Id];
1024 post.IsDeleted = true;
1030 public int GetOldestUnreadIndex(string TabName)
1032 var tb = _tabs[TabName];
1033 if (tb.OldestUnreadId > -1 &&
1034 tb.Contains(tb.OldestUnreadId) &&
1039 if (!tb.IsInnerStorageTabType)
1041 isRead = _statuses[tb.OldestUnreadId].IsRead;
1045 isRead = tb.Posts[tb.OldestUnreadId].IsRead;
1049 //状態不整合(最古未読IDが実は既読)
1052 this.SetNextUnreadId(-1, tb); //頭から探索
1054 if (tb.OldestUnreadId == -1)
1060 return tb.IndexOf(tb.OldestUnreadId);
1065 return tb.IndexOf(tb.OldestUnreadId); //最短経路;
1070 //一見未読なさそうだが、未読カウントはあるので探索
1071 //if (tb.UnreadCount > 0)
1072 if (!(tb.UnreadManage && AppendSettingDialog.Instance.UnreadManage)) return -1;
1075 this.SetNextUnreadId(-1, tb);
1077 if (tb.OldestUnreadId == -1)
1083 return tb.IndexOf(tb.OldestUnreadId);
1092 private void SetNextUnreadId(long CurrentId, TabClass Tab)
1094 //CurrentID:今既読にしたID(OldestIDの可能性あり)
1095 //最古未読が設定されていて、既読の場合(1発言以上存在)
1098 Dictionary<long, PostClass> posts;
1099 if (!Tab.IsInnerStorageTabType)
1107 if (Tab.OldestUnreadId > -1 &&
1108 posts.ContainsKey(Tab.OldestUnreadId) &&
1109 posts[Tab.OldestUnreadId].IsRead &&
1110 _sorter.Mode == IdComparerClass.ComparerMode.Id) //次の未読探索
1112 if (Tab.UnreadCount == 0)
1115 Tab.OldestUnreadId = -1;
1117 else if (Tab.OldestUnreadId == CurrentId && CurrentId > -1)
1119 //最古IDを既読にしたタイミング→次のIDから続けて探索
1120 var idx = Tab.IndexOf(CurrentId);
1124 FindUnreadId(idx, Tab);
1129 FindUnreadId(-1, Tab);
1135 FindUnreadId(-1, Tab);
1141 FindUnreadId(-1, Tab);
1144 catch (KeyNotFoundException)
1147 FindUnreadId(-1, Tab);
1151 private void FindUnreadId(int StartIdx, TabClass Tab)
1153 if (Tab.AllCount == 0)
1155 Tab.OldestUnreadId = -1;
1156 Tab.UnreadCount = 0;
1161 Tab.OldestUnreadId = -1;
1162 if (_sorter.Order == SortOrder.Ascending)
1171 if (StartIdx > Tab.AllCount - 1) StartIdx = Tab.AllCount - 1; //念のため
1173 toIdx = Tab.AllCount - 1;
1174 if (toIdx < 0) toIdx = 0; //念のため
1181 StartIdx = Tab.AllCount - 1;
1187 if (StartIdx < 0) StartIdx = 0; //念のため
1192 Dictionary<long, PostClass> posts;
1193 if (!Tab.IsInnerStorageTabType)
1202 for (int i = StartIdx; ; i+= stp)
1204 var id = Tab.GetId(i);
1205 if (id > -1 && !posts[id].IsRead)
1207 Tab.OldestUnreadId = id;
1211 if (i == toIdx) break;
1215 public int DistributePosts()
1220 //if (_addedIds == null) return 0;
1221 //if (_addedIds.Count == 0) return 0;
1223 if (_addedIds == null) _addedIds = new List<long>();
1224 if (_notifyPosts == null) _notifyPosts = new List<PostClass>();
1227 this.Distribute(); //タブに仮振分
1229 catch (KeyNotFoundException)
1233 var retCnt = _addedIds.Count;
1234 _addCount += retCnt;
1236 _addedIds = null; //後始末
1241 public int SubmitUpdate(ref string soundFile,
1242 ref PostClass[] notifyPosts,
1243 ref bool isMentionIncluded,
1244 ref bool isDeletePost,
1250 if (_notifyPosts == null)
1257 foreach (var tb in _tabs.Values)
1259 if (tb.IsInnerStorageTabType)
1261 _addCount += tb.GetTemporaryCount();
1263 tb.AddSubmit(ref isMentionIncluded); //振分確定(各タブに反映)
1265 ////UserStreamで反映間隔10秒以下だったら、30秒ごとにソートする
1267 //static DateTime lastSort = DateTime.Now;
1268 //if (AppendSettingDialog.Instance.UserstreamPeriodInt < 10 && isUserStream)
1270 // if (Now.Subtract(lastSort) > TimeSpan.FromSeconds(30))
1272 // lastSort = DateTime.Now;
1273 // isUserStream = false;
1278 // isUserStream = false;
1280 if (!isUserStream || this.SortMode != IdComparerClass.ComparerMode.Id)
1286 isDeletePost = this._deletedIds.Count > 0;
1287 foreach (var id in this._deletedIds)
1289 //this.DeletePost(StatusId)
1290 this.RemovePost(id);
1292 this._deletedIds.Clear();
1295 soundFile = _soundFile;
1297 notifyPosts = _notifyPosts.ToArray();
1298 _notifyPosts.Clear();
1299 _notifyPosts = null;
1300 var retCnt = _addCount;
1302 return retCnt; //件数(EndUpdateの戻り値と同じ)
1306 private void Distribute()
1308 //各タブのフィルターと照合。合致したらタブにID追加
1309 //通知メッセージ用に、表示必要な発言リストと再生サウンドを返す
1310 //notifyPosts = new List<PostClass>();
1311 var homeTab = GetTabByType(MyCommon.TabUsageType.Home);
1312 var replyTab = GetTabByType(MyCommon.TabUsageType.Mentions);
1313 var dmTab = GetTabByType(MyCommon.TabUsageType.DirectMessage);
1314 var favTab = GetTabByType(MyCommon.TabUsageType.Favorites);
1315 foreach (var id in _addedIds)
1317 var post = _statuses[id];
1318 var add = false; //通知リスト追加フラグ
1319 var mv = false; //移動フラグ(Recent追加有無)
1320 var rslt = MyCommon.HITRESULT.None;
1321 post.IsExcludeReply = false;
1322 foreach (var tn in _tabs.Keys)
1324 rslt = _tabs[tn].AddFiltered(post);
1325 if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
1327 if (rslt == MyCommon.HITRESULT.CopyAndMark) post.IsMark = true; //マークあり
1328 if (rslt == MyCommon.HITRESULT.Move)
1331 post.IsMark = false;
1333 if (_tabs[tn].Notify) add = true; //通知あり
1334 if (_tabs[tn].SoundFile != "" && _soundFile == "")
1336 _soundFile = _tabs[tn].SoundFile; //wavファイル(未設定の場合のみ)
1338 post.FilterHit = true;
1342 if (rslt == MyCommon.HITRESULT.Exclude && _tabs[tn].TabType == MyCommon.TabUsageType.Mentions)
1344 post.IsExcludeReply = true;
1346 post.FilterHit = false;
1349 if (!mv) //移動されなかったらRecentに追加
1351 homeTab.Add(post.StatusId, post.IsRead, true);
1352 if (homeTab.SoundFile != "" && _soundFile == "") _soundFile = homeTab.SoundFile;
1353 if (homeTab.Notify) add = true;
1355 if (post.IsReply && !post.IsExcludeReply) //除外ルール適用のないReplyならReplyタブに追加
1357 replyTab.Add(post.StatusId, post.IsRead, true);
1358 if (replyTab.SoundFile != "") _soundFile = replyTab.SoundFile;
1359 if (replyTab.Notify) add = true;
1361 if (post.IsFav) //Fav済み発言だったらFavoritesタブに追加
1363 if (favTab.Contains(post.StatusId))
1371 favTab.Add(post.StatusId, post.IsRead, true);
1372 if (!string.IsNullOrEmpty(favTab.SoundFile) && string.IsNullOrEmpty(_soundFile)) _soundFile = favTab.SoundFile;
1373 if (favTab.Notify) add = true;
1376 if (add) _notifyPosts.Add(post);
1378 foreach (var tb in _tabs.Values)
1380 if (tb.IsInnerStorageTabType)
1384 if (tb.GetTemporaryCount() > 0)
1386 foreach (var post in tb.GetTemporaryPosts())
1389 foreach (var npost in _notifyPosts)
1391 if (npost.StatusId == post.StatusId)
1397 if (!exist) _notifyPosts.Add(post);
1399 if (tb.SoundFile != "")
1401 if (tb.TabType == MyCommon.TabUsageType.DirectMessage || _soundFile == "")
1403 _soundFile = tb.SoundFile;
1412 public void AddPost(PostClass Item)
1416 if (Item.RelTabName == "")
1420 if (_statuses.ContainsKey(Item.StatusId))
1424 if (Item.RetweetedId == 0)
1426 _statuses[Item.StatusId].IsFav = true;
1435 return; //追加済みなら何もしない
1440 if (Item.IsFav && Item.RetweetedId > 0) Item.IsFav = false;
1442 if (AppendSettingDialog.Instance.HideDuplicatedRetweets &&
1444 this._retweets.ContainsKey(Item.RetweetedId) &&
1445 this._retweets[Item.RetweetedId].RetweetedCount > 0) return;
1446 if (BlockIds.Contains(Item.UserId)) return;
1447 _statuses.Add(Item.StatusId, Item);
1449 if (Item.RetweetedId > 0)
1451 this.AddRetweet(Item);
1453 if (Item.IsFav && _retweets.ContainsKey(Item.StatusId))
1455 return; //Fav済みのRetweet元発言は追加しない
1457 if (_addedIds == null) _addedIds = new List<long>(); //タブ追加用IDコレクション準備
1458 _addedIds.Add(Item.StatusId);
1463 var tb = this.GetTabByType(MyCommon.TabUsageType.DirectMessage);
1464 if (tb.Contains(Item.StatusId)) return;
1465 tb.AddPostToInnerStorage(Item);
1472 if (this.Tabs.ContainsKey(Item.RelTabName))
1474 tb = this.Tabs[Item.RelTabName];
1480 if (tb == null) return;
1481 if (tb.Contains(Item.StatusId)) return;
1482 //tb.Add(Item.StatusId, Item.IsRead, true);
1483 tb.AddPostToInnerStorage(Item);
1488 private void AddRetweet(PostClass item)
1490 //true:追加、False:保持済み
1491 if (_retweets.ContainsKey(item.RetweetedId))
1493 _retweets[item.RetweetedId].RetweetedCount++;
1494 if (_retweets[item.RetweetedId].RetweetedCount > 10)
1496 _retweets[item.RetweetedId].RetweetedCount = 0;
1514 item.IsExcludeReply,
1519 item.InReplyToStatusId,
1532 _retweets[item.RetweetedId].RetweetedCount++;
1535 public void SetReadAllTab(bool Read, string TabName, int Index)
1537 //Read:true=既読へ false=未読へ
1538 var tb = _tabs[TabName];
1540 if (tb.UnreadManage == false) return; //未読管理していなければ終了
1542 var Id = tb.GetId(Index);
1545 if (!tb.IsInnerStorageTabType)
1547 post = _statuses[Id];
1551 post = tb.Posts[Id];
1554 if (post.IsRead == Read) return; //状態変更なければ終了
1563 this.SetNextUnreadId(Id, tb); //次の未読セット
1564 //他タブの最古未読IDはタブ切り替え時に。
1565 if (tb.IsInnerStorageTabType)
1568 if (_statuses.ContainsKey(Id) && !_statuses[Id].IsRead)
1570 foreach (var key in _tabs.Keys)
1572 if (_tabs[key].UnreadManage &&
1573 _tabs[key].Contains(Id) &&
1574 !_tabs[key].IsInnerStorageTabType)
1576 _tabs[key].UnreadCount--;
1577 if (_tabs[key].OldestUnreadId == Id) _tabs[key].OldestUnreadId = -1;
1580 _statuses[Id].IsRead = true;
1586 foreach (var key in _tabs.Keys)
1588 if (key != TabName &&
1589 _tabs[key].UnreadManage &&
1590 _tabs[key].Contains(Id) &&
1591 !_tabs[key].IsInnerStorageTabType)
1593 _tabs[key].UnreadCount--;
1594 if (_tabs[key].OldestUnreadId == Id) _tabs[key].OldestUnreadId = -1;
1599 foreach (var key in _tabs.Keys)
1601 if (key != TabName &&
1602 _tabs[key].Contains(Id) &&
1603 _tabs[key].IsInnerStorageTabType &&
1604 !_tabs[key].Posts[Id].IsRead)
1606 if (_tabs[key].UnreadManage)
1608 _tabs[key].UnreadCount--;
1609 if (_tabs[key].OldestUnreadId == Id) _tabs[key].OldestUnreadId = -1;
1611 _tabs[key].Posts[Id].IsRead = true;
1618 //if (tb.OldestUnreadId > Id || tb.OldestUnreadId = -1) tb.OldestUnreadId = Id
1619 if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1620 if (tb.IsInnerStorageTabType)
1623 if (_statuses.ContainsKey(Id) && _statuses[Id].IsRead)
1625 foreach (var key in _tabs.Keys)
1627 if (_tabs[key].UnreadManage &&
1628 _tabs[key].Contains(Id) &&
1629 !_tabs[key].IsInnerStorageTabType)
1630 _tabs[key].UnreadCount++;
1632 if (_tabs[key].OldestUnreadId > Id) _tabs[key].OldestUnreadId = Id;
1635 _statuses[Id].IsRead = false;
1641 foreach (var key in _tabs.Keys)
1643 if (key != TabName &&
1644 _tabs[key].UnreadManage &&
1645 _tabs[key].Contains(Id) &&
1646 !_tabs[key].IsInnerStorageTabType)
1648 _tabs[key].UnreadCount++;
1649 if (_tabs[key].OldestUnreadId > Id) _tabs[key].OldestUnreadId = Id;
1654 foreach (var key in _tabs.Keys)
1656 if (key != TabName &&
1657 _tabs[key].Contains(Id) &&
1658 _tabs[key].IsInnerStorageTabType &&
1659 _tabs[key].Posts[Id].IsRead)
1661 if (_tabs[key].UnreadManage)
1663 _tabs[key].UnreadCount++;
1664 if (_tabs[key].OldestUnreadId > Id) _tabs[key].OldestUnreadId = Id;
1666 _tabs[key].Posts[Id].IsRead = false;
1673 // TODO: パフォーマンスを勘案して、戻すか決める
1674 public void SetRead(bool Read, string TabName, int Index)
1676 //Read:true=既読へ false=未読へ
1677 var tb = _tabs[TabName];
1679 if (tb.UnreadManage == false) return; //未読管理していなければ終了
1681 var Id = tb.GetId(Index);
1684 if (!tb.IsInnerStorageTabType)
1686 post = _statuses[Id];
1690 post = tb.Posts[Id];
1693 if (post.IsRead = Read) return; //状態変更なければ終了
1695 post.IsRead = Read; //指定の状態に変更
1702 this.SetNextUnreadId(Id, tb); //次の未読セット
1703 //他タブの最古未読IDはタブ切り替え時に。
1704 if (tb.IsInnerStorageTabType) return;
1705 foreach (var key in _tabs.Keys)
1707 if (key != TabName &&
1708 _tabs[key].UnreadManage &&
1709 _tabs[key].Contains(Id) &&
1710 !_tabs[key].IsInnerStorageTabType)
1712 _tabs[key].UnreadCount--;
1713 if (_tabs[key].OldestUnreadId == Id) _tabs[key].OldestUnreadId = -1;
1720 //if (tb.OldestUnreadId > Id || tb.OldestUnreadId == -1) tb.OldestUnreadId = Id;
1721 if (tb.OldestUnreadId > Id) tb.OldestUnreadId = Id;
1722 if (tb.IsInnerStorageTabType) return;
1723 foreach (var key in _tabs.Keys)
1725 if (key != TabName &&
1726 _tabs[key].UnreadManage &&
1727 _tabs[key].Contains(Id) &&
1728 !_tabs[key].IsInnerStorageTabType)
1730 _tabs[key].UnreadCount++;
1731 if (_tabs[key].OldestUnreadId > Id) _tabs[key].OldestUnreadId = Id;
1738 public void SetRead()
1740 var tb = GetTabByType(MyCommon.TabUsageType.Home);
1741 if (tb.UnreadManage == false) return;
1745 for (int i = 0; i < tb.AllCount - 1; i++)
1747 var id = tb.GetId(i);
1749 if (!_statuses[id].IsReply &&
1750 !_statuses[id].IsRead &&
1751 !_statuses[id].FilterHit)
1753 _statuses[id].IsRead = true;
1754 this.SetNextUnreadId(id, tb); //次の未読セット
1755 foreach (var key in _tabs.Keys)
1757 if (_tabs[key].UnreadManage &&
1758 _tabs[key].Contains(id))
1760 _tabs[key].UnreadCount--;
1761 if (_tabs[key].OldestUnreadId == id) _tabs[key].OldestUnreadId = -1;
1769 public PostClass this[long ID]
1773 if (_statuses.ContainsKey(ID)) return _statuses[ID];
1774 foreach (var tb in this.GetTabsInnerStorageType())
1776 if (tb.Contains(ID))
1778 return tb.Posts[ID];
1785 public PostClass this[string TabName, int Index]
1789 if (!_tabs.ContainsKey(TabName)) throw new ArgumentException("TabName=" + TabName + " is not contained.");
1790 var id = _tabs[TabName].GetId(Index);
1791 if (id < 0) throw new ArgumentException("Index can//t find. Index=" + Index.ToString() + "/TabName=" + TabName);
1794 if (_tabs[TabName].IsInnerStorageTabType)
1796 return _tabs[TabName].Posts[_tabs[TabName].GetId(Index)];
1800 return _statuses[_tabs[TabName].GetId(Index)];
1803 catch (Exception ex)
1805 throw new Exception("Index=" + Index.ToString() + "/TabName=" + TabName, ex);
1810 public PostClass[] this[string TabName, int StartIndex, int EndIndex]
1814 var length = EndIndex - StartIndex + 1;
1815 var posts = new PostClass[length];
1816 if (_tabs[TabName].IsInnerStorageTabType)
1818 for (int i = 0; i < length; i++)
1820 posts[i] = _tabs[TabName].Posts[_tabs[TabName].GetId(StartIndex + i)];
1825 for (int i = 0; i < length; i++)
1827 posts[i] = _statuses[_tabs[TabName].GetId(StartIndex + i)];
1834 //public ReadOnly int ItemCount
1840 // return _statuses.Count //DM,公式検索は除く
1845 public bool ContainsKey(long Id)
1850 return _statuses.ContainsKey(Id);
1854 public bool ContainsKey(long Id, string TabName)
1859 if (_tabs.ContainsKey(TabName))
1861 return _tabs[TabName].Contains(Id);
1870 public void SetUnreadManage(bool Manage)
1874 foreach (var key in _tabs.Keys)
1876 var tb = _tabs[key];
1877 if (tb.UnreadManage)
1882 var oldest = long.MaxValue;
1883 Dictionary<long, PostClass> posts;
1884 if (!tb.IsInnerStorageTabType)
1892 foreach (var id in tb.BackupIds)
1894 if (!posts[id].IsRead)
1897 if (oldest > id) oldest = id;
1900 if (oldest == long.MaxValue) oldest = -1;
1901 tb.OldestUnreadId = oldest;
1902 tb.UnreadCount = cnt;
1909 foreach (var key in _tabs.Keys)
1911 var tb = _tabs[key];
1912 if (tb.UnreadManage && tb.UnreadCount > 0)
1917 tb.OldestUnreadId = -1;
1924 public void RenameTab(string Original, string NewName)
1926 var tb = _tabs[Original];
1927 _tabs.Remove(Original);
1928 tb.TabName = NewName;
1929 _tabs.Add(NewName, tb);
1932 public void FilterAll()
1936 var tbr = GetTabByType(MyCommon.TabUsageType.Home);
1937 var replyTab = GetTabByType(MyCommon.TabUsageType.Mentions);
1938 foreach (var tb in _tabs.Values.ToArray())
1940 if (tb.FilterModified)
1942 tb.FilterModified = false;
1943 var orgIds = tb.BackupIds;
1945 //////////////フィルター前のIDsを退避。どのタブにも含まれないidはrecentへ追加
1946 //////////////moveフィルターにヒットした際、recentに該当あればrecentから削除
1947 foreach (var id in _statuses.Keys)
1949 var post = _statuses[id];
1950 if (post.IsDm) continue;
1951 var rslt = MyCommon.HITRESULT.None;
1952 rslt = tb.AddFiltered(post);
1955 case MyCommon.HITRESULT.CopyAndMark:
1956 post.IsMark = true; //マークあり
1957 post.FilterHit = true;
1959 case MyCommon.HITRESULT.Move:
1960 tbr.Remove(post.StatusId, post.IsRead);
1961 post.IsMark = false;
1962 post.FilterHit = true;
1964 case MyCommon.HITRESULT.Copy:
1965 post.IsMark = false;
1966 post.FilterHit = true;
1968 case MyCommon.HITRESULT.Exclude:
1969 if (tb.TabName == replyTab.TabName && post.IsReply) post.IsExcludeReply = true;
1970 if (post.IsFav) GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, true);
1971 post.FilterHit = false;
1973 case MyCommon.HITRESULT.None:
1974 if (tb.TabName == replyTab.TabName && post.IsReply) replyTab.Add(post.StatusId, post.IsRead, true);
1975 if (post.IsFav) GetTabByType(MyCommon.TabUsageType.Favorites).Add(post.StatusId, post.IsRead, true);
1976 post.FilterHit = false;
1980 tb.AddSubmit(); //振分確定
1981 foreach (var id in orgIds)
1984 foreach (var tbTemp in _tabs.Values.ToArray())
1986 if (tbTemp.Contains(id))
1992 if (!hit) tbr.Add(id, _statuses[id].IsRead, false);
2000 public long[] GetId(string TabName, ListView.SelectedIndexCollection IndexCollection)
2002 if (IndexCollection.Count == 0) return null;
2004 var tb = _tabs[TabName];
2005 var Ids = new long[IndexCollection.Count];
2006 for (int i = 0; i < Ids.Length; i++)
2008 Ids[i] = tb.GetId(IndexCollection[i]);
2013 public long GetId(string TabName, int Index)
2015 return _tabs[TabName].GetId(Index);
2018 public int[] IndexOf(string TabName, long[] Ids)
2020 if (Ids == null) return null;
2021 var idx = new int[Ids.Length];
2022 var tb = _tabs[TabName];
2023 for (int i = 0; i < Ids.Length; i++)
2025 idx[i] = tb.IndexOf(Ids[i]);
2030 public int IndexOf(string TabName, long Id)
2032 return _tabs[TabName].IndexOf(Id);
2035 public void ClearTabIds(string TabName)
2040 if (!_tabs[TabName].IsInnerStorageTabType)
2042 foreach (var Id in _tabs[TabName].BackupIds)
2045 foreach (var tb in _tabs.Values)
2047 if (tb.Contains(Id))
2053 if (!Hit) _statuses.Remove(Id);
2058 _tabs[TabName].ClearIDs();
2062 public void SetTabUnreadManage(string TabName, bool Manage)
2064 var tb = _tabs[TabName];
2070 var oldest = long.MaxValue;
2071 Dictionary<long, PostClass> posts;
2072 if (!tb.IsInnerStorageTabType)
2080 foreach (var id in tb.BackupIds)
2082 if (!posts[id].IsRead)
2085 if (oldest > id) oldest = id;
2088 if (oldest == long.MaxValue) oldest = -1;
2089 tb.OldestUnreadId = oldest;
2090 tb.UnreadCount = cnt;
2094 tb.OldestUnreadId = -1;
2098 tb.UnreadManage = Manage;
2101 public void RefreshOwl(List<long> follower)
2105 if (follower.Count > 0)
2107 foreach (var post in _statuses.Values)
2109 //if (post.UserId = 0 || post.IsDm) Continue For
2116 post.IsOwl = !follower.Contains(post.UserId);
2122 foreach (var id in _statuses.Keys)
2124 _statuses[id].IsOwl = false;
2130 public TabClass GetTabByType(MyCommon.TabUsageType tabType)
2132 //Home,Mentions,DM,Favは1つに制限する
2133 //その他のタイプを指定されたら、最初に合致したものを返す
2137 foreach (var tb in _tabs.Values)
2139 if (tb != null && tb.TabType == tabType) return tb;
2145 public List<TabClass> GetTabsByType(MyCommon.TabUsageType tabType)
2151 var tbs = new List<TabClass>();
2152 foreach (var tb in _tabs.Values)
2154 if ((tabType & tb.TabType) == tb.TabType) tbs.Add(tb);
2160 public List<TabClass> GetTabsInnerStorageType()
2166 var tbs = new List<TabClass>();
2167 foreach (var tb in _tabs.Values)
2169 if (tb.IsInnerStorageTabType) tbs.Add(tb);
2175 public TabClass GetTabByName(string tabName)
2179 if (_tabs.ContainsKey(tabName)) return _tabs[tabName];
2185 public bool IsDefaultTab(string tabName)
2187 if (tabName != null &&
2188 _tabs.ContainsKey(tabName) &&
2189 (_tabs[tabName].TabType == MyCommon.TabUsageType.Home ||
2190 _tabs[tabName].TabType == MyCommon.TabUsageType.Mentions ||
2191 _tabs[tabName].TabType == MyCommon.TabUsageType.DirectMessage ||
2192 _tabs[tabName].TabType == MyCommon.TabUsageType.Favorites))
2203 public bool IsDistributableTab(string tabName)
2205 return tabName != null &&
2206 this._tabs.ContainsKey(tabName) &&
2207 (_tabs[tabName].TabType == MyCommon.TabUsageType.Mentions ||
2208 _tabs[tabName].TabType == MyCommon.TabUsageType.UserDefined);
2211 public string GetUniqueTabName()
2213 var tabNameTemp = "MyTab" + (_tabs.Count + 1).ToString();
2214 for (int i = 2; i <= 100; i++)
2216 if (_tabs.ContainsKey(tabNameTemp))
2218 tabNameTemp = "MyTab" + (_tabs.Count + i).ToString();
2228 public Dictionary<long, PostClass> Posts
2238 public sealed class TabClass
2240 private bool _unreadManage = false;
2241 private List<FiltersClass> _filters;
2242 private int _unreadCount = 0;
2243 private List<long> _ids;
2244 private List<TemporaryId> _tmpIds = new List<TemporaryId>();
2245 private MyCommon.TabUsageType _tabType = MyCommon.TabUsageType.Undefined;
2246 private IdComparerClass _sorter = new IdComparerClass();
2248 private readonly object _lockObj = new object();
2250 public string User { get; set; }
2254 private string _searchLang = "";
2255 private string _searchWords = "";
2256 private string _nextPageQuery = "";
2258 public string SearchLang
2267 _searchLang = value;
2270 public string SearchWords
2274 return _searchWords;
2279 _searchWords = value.Trim();
2283 public string NextPageQuery
2287 return _nextPageQuery;
2291 _nextPageQuery = value;
2295 public int GetSearchPage(int count)
2297 return ((_ids.Count / count) + 1);
2299 private Dictionary<string, string> _beforeQuery = new Dictionary<string, string>();
2300 public void SaveQuery(bool more)
2302 var qry = new Dictionary<string, string>();
2303 if (string.IsNullOrEmpty(_searchWords))
2308 qry.Add("q", _searchWords);
2309 if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2313 public bool IsQueryChanged()
2315 var qry = new Dictionary<string, string>();
2316 if (!string.IsNullOrEmpty(_searchWords))
2318 qry.Add("q", _searchWords);
2319 if (!string.IsNullOrEmpty(_searchLang)) qry.Add("lang", _searchLang);
2321 if (qry.Count != _beforeQuery.Count) return true;
2323 foreach (var kvp in qry)
2325 if (!_beforeQuery.ContainsKey(kvp.Key) || _beforeQuery[kvp.Key] != kvp.Value)
2335 private ListElement _listInfo;
2336 public ListElement ListInfo
2350 public PostClass RelationTargetPost { get; set; }
2353 public long OldestId = long.MaxValue;
2356 public long SinceId { get; set; }
2359 public Dictionary<long, PostClass> Posts { get; set; }
2361 public PostClass[] GetTemporaryPosts()
2363 var tempPosts = new List<PostClass>();
2364 if (_tmpIds.Count == 0) return tempPosts.ToArray();
2365 foreach (var tempId in _tmpIds)
2367 tempPosts.Add(Posts[tempId.Id]);
2369 return tempPosts.ToArray();
2372 public int GetTemporaryCount()
2374 return _tmpIds.Count;
2377 private struct TemporaryId
2382 public TemporaryId(long argId, bool argRead)
2391 Posts = new Dictionary<long, PostClass>();
2393 OldestUnreadId = -1;
2395 _filters = new List<FiltersClass>();
2398 _unreadManage = true;
2399 _ids = new List<long>();
2400 this.OldestUnreadId = -1;
2401 _tabType = MyCommon.TabUsageType.Undefined;
2405 public TabClass(string TabName, MyCommon.TabUsageType TabType, ListElement list)
2407 Posts = new Dictionary<long, PostClass>();
2409 OldestUnreadId = -1;
2410 this.TabName = TabName;
2411 _filters = new List<FiltersClass>();
2414 _unreadManage = true;
2415 _ids = new List<long>();
2416 this.OldestUnreadId = -1;
2418 this.ListInfo = list;
2419 if (this.IsInnerStorageTabType)
2421 _sorter.posts = Posts;
2425 _sorter.posts = TabInformations.GetInstance().Posts;
2431 if (_sorter.Mode == IdComparerClass.ComparerMode.Id)
2433 _ids.Sort(_sorter.CmpMethod());
2437 if (_sorter.Order == SortOrder.Ascending)
2439 switch (_sorter.Mode)
2441 case IdComparerClass.ComparerMode.Data:
2442 ar = _ids.OrderBy(n => _sorter.posts[n].TextFromApi).ToArray();
2444 case IdComparerClass.ComparerMode.Name:
2445 ar = _ids.OrderBy(n => _sorter.posts[n].ScreenName).ToArray();
2447 case IdComparerClass.ComparerMode.Nickname:
2448 ar = _ids.OrderBy(n => _sorter.posts[n].Nickname).ToArray();
2450 case IdComparerClass.ComparerMode.Source:
2451 ar = _ids.OrderBy(n => _sorter.posts[n].Source).ToArray();
2457 switch (_sorter.Mode)
2459 case IdComparerClass.ComparerMode.Data:
2460 ar = _ids.OrderByDescending(n => _sorter.posts[n].TextFromApi).ToArray();
2462 case IdComparerClass.ComparerMode.Name:
2463 ar = _ids.OrderByDescending(n => _sorter.posts[n].ScreenName).ToArray();
2465 case IdComparerClass.ComparerMode.Nickname:
2466 ar = _ids.OrderByDescending(n => _sorter.posts[n].Nickname).ToArray();
2468 case IdComparerClass.ComparerMode.Source:
2469 ar = _ids.OrderByDescending(n => _sorter.posts[n].Source).ToArray();
2473 _ids = new List<long>(ar);
2476 public IdComparerClass Sorter
2485 private void Add(long ID, bool Read)
2487 if (this._ids.Contains(ID)) return;
2489 if (this.Sorter.Mode == IdComparerClass.ComparerMode.Id)
2491 if (this.Sorter.Order == SortOrder.Ascending)
2497 this._ids.Insert(0, ID);
2505 if (!Read && this._unreadManage)
2507 this._unreadCount++;
2508 if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2509 //if (this.OldestUnreadId == -1)
2511 // this.OldestUnreadId = ID;
2515 // if (ID < this.OldestUnreadId) this.OldestUnreadId = ID;
2520 public void Add(long ID, bool Read, bool Temporary)
2528 _tmpIds.Add(new TemporaryId(ID, Read));
2533 public MyCommon.HITRESULT AddFiltered(PostClass post)
2535 if (this.IsInnerStorageTabType) return MyCommon.HITRESULT.None;
2537 var rslt = MyCommon.HITRESULT.None;
2539 lock (this._lockObj)
2541 foreach (var ft in _filters)
2545 switch (ft.IsHit(post)) //フィルタクラスでヒット判定
2547 case MyCommon.HITRESULT.None:
2549 case MyCommon.HITRESULT.Copy:
2550 if (rslt != MyCommon.HITRESULT.CopyAndMark) rslt = MyCommon.HITRESULT.Copy;
2552 case MyCommon.HITRESULT.CopyAndMark:
2553 rslt = MyCommon.HITRESULT.CopyAndMark;
2555 case MyCommon.HITRESULT.Move:
2556 rslt = MyCommon.HITRESULT.Move;
2558 case MyCommon.HITRESULT.Exclude:
2559 rslt = MyCommon.HITRESULT.Exclude;
2563 catch (NullReferenceException)
2565 //IsHitでNullRef出る場合あり。暫定対応
2566 MyCommon.TraceOut("IsHitでNullRef: " + ft.ToString());
2567 rslt = MyCommon.HITRESULT.None;
2574 if (rslt != MyCommon.HITRESULT.None && rslt != MyCommon.HITRESULT.Exclude)
2576 _tmpIds.Add(new TemporaryId(post.StatusId, post.IsRead));
2579 return rslt; //マーク付けは呼び出し元で行うこと
2583 public void AddPostToInnerStorage(PostClass Post)
2585 if (Posts.ContainsKey(Post.StatusId)) return;
2586 Posts.Add(Post.StatusId, Post);
2587 _tmpIds.Add(new TemporaryId(Post.StatusId, Post.IsRead));
2590 public void AddSubmit(ref bool isMentionIncluded)
2592 if (_tmpIds.Count == 0) return;
2593 _tmpIds.Sort((x, y) => x.Id.CompareTo(y.Id));
2594 foreach (var tId in _tmpIds)
2596 if (this.TabType == MyCommon.TabUsageType.Mentions && TabInformations.GetInstance()[tId.Id].IsReply) isMentionIncluded = true;
2597 this.Add(tId.Id, tId.Read);
2602 public void AddSubmit()
2604 bool mention = false;
2605 AddSubmit(ref mention);
2608 public void Remove(long Id)
2610 if (!this._ids.Contains(Id)) return;
2611 this._ids.Remove(Id);
2612 if (this.IsInnerStorageTabType) Posts.Remove(Id);
2615 public void Remove(long Id, bool Read)
2617 if (!this._ids.Contains(Id)) return;
2619 if (!Read && this._unreadManage)
2621 this._unreadCount--;
2622 this.OldestUnreadId = -1;
2625 this._ids.Remove(Id);
2626 if (this.IsInnerStorageTabType) Posts.Remove(Id);
2629 public bool UnreadManage
2633 return _unreadManage;
2637 this._unreadManage = value;
2640 this.OldestUnreadId = -1;
2641 this._unreadCount = 0;
2646 public bool Notify { get; set; }
2648 public string SoundFile { get; set; }
2651 public long OldestUnreadId { get; set; }
2654 public int UnreadCount
2658 return this.UnreadManage && AppendSettingDialog.Instance.UnreadManage ? _unreadCount : 0;
2662 if (value < 0) value = 0;
2663 _unreadCount = value;
2671 return this._ids.Count;
2675 public FiltersClass[] GetFilters()
2677 lock (this._lockObj)
2679 return _filters.ToArray();
2683 public void RemoveFilter(FiltersClass filter)
2685 lock (this._lockObj)
2687 _filters.Remove(filter);
2688 this.FilterModified = true;
2692 public bool AddFilter(FiltersClass filter)
2694 lock (this._lockObj)
2696 if (_filters.Contains(filter)) return false;
2697 _filters.Add(filter);
2698 this.FilterModified = true;
2703 public void EditFilter(FiltersClass original, FiltersClass modified)
2705 original.BodyFilter = modified.BodyFilter;
2706 original.NameFilter = modified.NameFilter;
2707 original.SearchBoth = modified.SearchBoth;
2708 original.SearchUrl = modified.SearchUrl;
2709 original.UseRegex = modified.UseRegex;
2710 original.CaseSensitive = modified.CaseSensitive;
2711 original.IsRt = modified.IsRt;
2712 original.UseLambda = modified.UseLambda;
2713 original.Source = modified.Source;
2714 original.ExBodyFilter = modified.ExBodyFilter;
2715 original.ExNameFilter = modified.ExNameFilter;
2716 original.ExSearchBoth = modified.ExSearchBoth;
2717 original.ExSearchUrl = modified.ExSearchUrl;
2718 original.ExUseRegex = modified.ExUseRegex;
2719 original.ExCaseSensitive = modified.ExCaseSensitive;
2720 original.IsExRt = modified.IsExRt;
2721 original.ExUseLambda = modified.ExUseLambda;
2722 original.ExSource = modified.ExSource;
2723 original.MoveFrom = modified.MoveFrom;
2724 original.SetMark = modified.SetMark;
2725 this.FilterModified = true;
2729 public List<FiltersClass> Filters
2733 lock (this._lockObj)
2740 lock (this._lockObj)
2747 public FiltersClass[] FilterArray
2751 lock (this._lockObj)
2753 return _filters.ToArray();
2758 lock (this._lockObj)
2760 foreach (var filters in value)
2762 _filters.Add(filters);
2767 public bool Contains(long ID)
2769 return _ids.Contains(ID);
2772 public void ClearIDs()
2777 this.OldestUnreadId = -1;
2784 public long GetId(int Index)
2786 return Index < _ids.Count ? _ids[Index] : -1;
2789 public int IndexOf(long ID)
2791 return _ids.IndexOf(ID);
2795 public bool FilterModified { get; set; }
2797 public long[] BackupIds
2801 return _ids.ToArray();
2805 public string TabName { get; set; }
2807 public MyCommon.TabUsageType TabType
2816 if (this.IsInnerStorageTabType)
2818 _sorter.posts = Posts;
2822 _sorter.posts = TabInformations.GetInstance().Posts;
2827 public bool IsInnerStorageTabType
2831 if (_tabType == MyCommon.TabUsageType.PublicSearch ||
2832 _tabType == MyCommon.TabUsageType.DirectMessage ||
2833 _tabType == MyCommon.TabUsageType.Lists ||
2834 _tabType == MyCommon.TabUsageType.UserTimeline ||
2835 _tabType == MyCommon.TabUsageType.Related)
2848 public sealed class FiltersClass : System.IEquatable<FiltersClass>
2850 private string _name = "";
2851 private List<string> _body = new List<string>();
2852 private bool _searchBoth = true;
2853 private bool _searchUrl = false;
2854 private bool _caseSensitive = false;
2855 private bool _useRegex = false;
2856 private bool _isRt = false;
2857 private string _source = "";
2858 private string _exname = "";
2859 private List<string> _exbody = new List<string>();
2860 private bool _exsearchBoth = true;
2861 private bool _exsearchUrl = false;
2862 private bool _exuseRegex = false;
2863 private bool _excaseSensitive = false;
2864 private bool _isExRt = false;
2865 private string _exSource = "";
2866 private bool _moveFrom = false;
2867 private bool _setMark = true;
2868 private bool _useLambda = false;
2869 private bool _exuseLambda = false;
2871 public FiltersClass() {}
2874 private string MakeSummary()
2876 var fs = new StringBuilder();
2877 if (!string.IsNullOrEmpty(_name) || _body.Count > 0 || _isRt || !string.IsNullOrEmpty(_source))
2881 if (!string.IsNullOrEmpty(_name))
2883 fs.AppendFormat(Properties.Resources.SetFiltersText1, _name);
2887 fs.Append(Properties.Resources.SetFiltersText2);
2890 if (_body.Count > 0)
2892 fs.Append(Properties.Resources.SetFiltersText3);
2893 foreach (var bf in _body)
2899 fs.Append(Properties.Resources.SetFiltersText4);
2904 fs.Append(Properties.Resources.SetFiltersText5);
2908 fs.Append(Properties.Resources.SetFiltersText6);
2912 fs.Append(Properties.Resources.SetFiltersText7);
2916 fs.Append(Properties.Resources.SetFiltersText8);
2920 fs.Append(Properties.Resources.SetFiltersText13);
2928 fs.Append("LambdaExp/");
2930 if (!string.IsNullOrEmpty(_source))
2932 fs.AppendFormat("Src…{0}/", _source);
2937 if (!string.IsNullOrEmpty(_exname) || _exbody.Count > 0 || _isExRt || !string.IsNullOrEmpty(_exSource))
2940 fs.Append(Properties.Resources.SetFiltersText12);
2943 if (!string.IsNullOrEmpty(_exname))
2945 fs.AppendFormat(Properties.Resources.SetFiltersText1, _exname);
2949 fs.Append(Properties.Resources.SetFiltersText2);
2952 if (_exbody.Count > 0)
2954 fs.Append(Properties.Resources.SetFiltersText3);
2955 foreach (var bf in _exbody)
2961 fs.Append(Properties.Resources.SetFiltersText4);
2966 fs.Append(Properties.Resources.SetFiltersText5);
2970 fs.Append(Properties.Resources.SetFiltersText6);
2974 fs.Append(Properties.Resources.SetFiltersText7);
2978 fs.Append(Properties.Resources.SetFiltersText8);
2980 if (_excaseSensitive)
2982 fs.Append(Properties.Resources.SetFiltersText13);
2990 fs.Append("LambdaExp/");
2992 if (!string.IsNullOrEmpty(_exSource))
2994 fs.AppendFormat("Src…{0}/", _exSource);
3003 fs.Append(Properties.Resources.SetFiltersText9);
3007 fs.Append(Properties.Resources.SetFiltersText11);
3009 if (!_moveFrom && _setMark)
3011 fs.Append(Properties.Resources.SetFiltersText10);
3013 else if (!_moveFrom)
3020 return fs.ToString();
3023 public string NameFilter
3035 public string ExNameFilter
3048 public List<string> BodyFilter
3060 public string[] BodyFilterArray
3064 return _body.ToArray();
3068 _body = new List<string>();
3069 foreach (var filter in value)
3077 public List<string> ExBodyFilter
3089 public string[] ExBodyFilterArray
3093 return _exbody.ToArray();
3097 _exbody = new List<string>();
3098 foreach (var filter in value)
3100 _exbody.Add(filter);
3105 public bool SearchBoth
3113 _searchBoth = value;
3117 public bool ExSearchBoth
3121 return _exsearchBoth;
3125 _exsearchBoth = value;
3129 public bool MoveFrom
3153 public bool SearchUrl
3165 public bool ExSearchUrl
3169 return _exsearchUrl;
3173 _exsearchUrl = value;
3177 public bool CaseSensitive
3181 return _caseSensitive;
3185 _caseSensitive = value;
3189 public bool ExCaseSensitive
3193 return _excaseSensitive;
3197 _excaseSensitive = value;
3201 public bool UseLambda
3213 public bool ExUseLambda
3217 return _exuseLambda;
3221 _exuseLambda = value;
3225 public bool UseRegex
3237 public bool ExUseRegex
3245 _exuseRegex = value;
3273 public string Source
3285 public string ExSource
3297 public override string ToString()
3299 return MakeSummary();
3302 public bool ExecuteLambdaExpression(string expr, PostClass post)
3305 // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する
3308 public bool ExecuteExLambdaExpression(string expr, PostClass post)
3311 // TODO DynamicQuery相当のGPLv3互換なライブラリで置換する
3314 public MyCommon.HITRESULT IsHit(PostClass post)
3322 tSource = post.SourceHtml;
3326 tBody = post.TextFromApi;
3327 tSource = post.Source;
3330 System.StringComparison compOpt;
3331 System.Text.RegularExpressions.RegexOptions rgOpt;
3334 compOpt = StringComparison.Ordinal;
3335 rgOpt = RegexOptions.None;
3339 compOpt = StringComparison.OrdinalIgnoreCase;
3340 rgOpt = RegexOptions.IgnoreCase;
3344 if (string.IsNullOrEmpty(_name) ||
3346 (post.ScreenName.Equals(_name, compOpt) ||
3347 post.RetweetedBy.Equals(_name, compOpt)
3351 (Regex.IsMatch(post.ScreenName, _name, rgOpt) ||
3352 (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, _name, rgOpt))
3358 if (!ExecuteLambdaExpression(_body[0], post)) bHit = false;
3362 foreach (var fs in _body)
3366 if (!Regex.IsMatch(tBody, fs, rgOpt)) bHit = false;
3372 if (!tBody.Contains(fs)) bHit = false;
3376 if (!tBody.ToLower().Contains(fs.ToLower())) bHit = false;
3392 if (!ExecuteLambdaExpression(_body[0], post)) bHit = false;
3396 foreach (var fs in _body)
3400 if (!(Regex.IsMatch(post.ScreenName, fs, rgOpt) ||
3401 (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, fs, rgOpt)) ||
3402 Regex.IsMatch(tBody, fs, rgOpt))) bHit = false;
3408 if (!(post.ScreenName.Contains(fs) ||
3409 post.RetweetedBy.Contains(fs) ||
3410 tBody.Contains(fs))) bHit = false;
3414 if (!(post.ScreenName.ToLower().Contains(fs.ToLower()) ||
3415 post.RetweetedBy.ToLower().Contains(fs.ToLower()) ||
3416 tBody.ToLower().Contains(fs.ToLower()))) bHit = false;
3425 if (post.RetweetedId == 0) bHit = false;
3427 if (!string.IsNullOrEmpty(_source))
3431 if (!Regex.IsMatch(tSource, _source, rgOpt)) bHit = false;
3435 if (!tSource.Equals(_source, compOpt)) bHit = false;
3444 tSource = post.SourceHtml;
3448 tBody = post.TextFromApi;
3449 tSource = post.Source;
3453 if (!string.IsNullOrEmpty(_exname) || _exbody.Count > 0)
3455 if (_excaseSensitive)
3457 compOpt = StringComparison.Ordinal;
3458 rgOpt = RegexOptions.None;
3462 compOpt = StringComparison.OrdinalIgnoreCase;
3463 rgOpt = RegexOptions.IgnoreCase;
3467 if (string.IsNullOrEmpty(_exname) ||
3469 (post.ScreenName.Equals(_exname, compOpt) ||
3470 post.RetweetedBy.Equals(_exname, compOpt)
3474 (Regex.IsMatch(post.ScreenName, _exname, rgOpt) ||
3475 (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, _exname, rgOpt))
3479 if (_exbody.Count > 0)
3483 if (ExecuteExLambdaExpression(_exbody[0], post)) exFlag = true;
3487 foreach (var fs in _exbody)
3491 if (Regex.IsMatch(tBody, fs, rgOpt)) exFlag = true;
3495 if (_excaseSensitive)
3497 if (tBody.Contains(fs)) exFlag = true;
3501 if (tBody.ToLower().Contains(fs.ToLower())) exFlag = true;
3518 if (ExecuteExLambdaExpression(_exbody[0], post)) exFlag = true;
3522 foreach (var fs in _exbody)
3526 if (Regex.IsMatch(post.ScreenName, fs, rgOpt) ||
3527 (!string.IsNullOrEmpty(post.RetweetedBy) && Regex.IsMatch(post.RetweetedBy, fs, rgOpt)) ||
3528 Regex.IsMatch(tBody, fs, rgOpt)) exFlag = true;
3532 if (_excaseSensitive)
3534 if (post.ScreenName.Contains(fs) ||
3535 post.RetweetedBy.Contains(fs) ||
3536 tBody.Contains(fs)) exFlag = true;
3540 if (post.ScreenName.ToLower().Contains(fs.ToLower()) ||
3541 post.RetweetedBy.ToLower().Contains(fs.ToLower()) ||
3542 tBody.ToLower().Contains(fs.ToLower())) exFlag = true;
3552 if (post.RetweetedId > 0) exFlag = true;
3554 if (!string.IsNullOrEmpty(_exSource))
3558 if (Regex.IsMatch(tSource, _exSource, rgOpt)) exFlag = true;
3562 if (tSource.Equals(_exSource, compOpt)) exFlag = true;
3566 if (string.IsNullOrEmpty(_name) && _body.Count == 0 && !_isRt && string.IsNullOrEmpty(_source))
3576 return MyCommon.HITRESULT.Move;
3582 return MyCommon.HITRESULT.CopyAndMark;
3584 return MyCommon.HITRESULT.Copy;
3589 return MyCommon.HITRESULT.Exclude;
3596 return MyCommon.HITRESULT.Exclude;
3600 return MyCommon.HITRESULT.None;
3606 return MyCommon.HITRESULT.None;
3610 public bool Equals(FiltersClass other)
3612 if (this.BodyFilter.Count != other.BodyFilter.Count) return false;
3613 if (this.ExBodyFilter.Count != other.ExBodyFilter.Count) return false;
3614 for (int i = 0; i < this.BodyFilter.Count; i++)
3616 if (this.BodyFilter[i] != other.BodyFilter[i]) return false;
3618 for (int i = 0; i < this.ExBodyFilter.Count; i++)
3620 if (this.ExBodyFilter[i] != other.ExBodyFilter[i]) return false;
3623 return (this.MoveFrom == other.MoveFrom) &
3624 (this.SetMark == other.SetMark) &
3625 (this.NameFilter == other.NameFilter) &
3626 (this.SearchBoth == other.SearchBoth) &
3627 (this.SearchUrl == other.SearchUrl) &
3628 (this.UseRegex == other.UseRegex) &
3629 (this.ExNameFilter == other.ExNameFilter) &
3630 (this.ExSearchBoth == other.ExSearchBoth) &
3631 (this.ExSearchUrl == other.ExSearchUrl) &
3632 (this.ExUseRegex == other.ExUseRegex) &
3633 (this.IsRt == other.IsRt) &
3634 (this.Source == other.Source) &
3635 (this.IsExRt == other.IsExRt) &
3636 (this.ExSource == other.ExSource) &
3637 (this.UseLambda == other.UseLambda) &
3638 (this.ExUseLambda == other.ExUseLambda);
3641 public FiltersClass CopyTo(FiltersClass destination)
3643 if (this.BodyFilter.Count > 0)
3645 foreach (var flt in this.BodyFilter)
3647 destination.BodyFilter.Add(string.Copy(flt));
3651 if (this.ExBodyFilter.Count > 0)
3653 foreach (var flt in this.ExBodyFilter)
3655 destination.ExBodyFilter.Add(string.Copy(flt));
3659 destination.MoveFrom = this.MoveFrom;
3660 destination.SetMark = this.SetMark;
3661 destination.NameFilter = this.NameFilter;
3662 destination.SearchBoth = this.SearchBoth;
3663 destination.SearchUrl = this.SearchUrl;
3664 destination.UseRegex = this.UseRegex;
3665 destination.ExNameFilter = this.ExNameFilter;
3666 destination.ExSearchBoth = this.ExSearchBoth;
3667 destination.ExSearchUrl = this.ExSearchUrl;
3668 destination.ExUseRegex = this.ExUseRegex;
3669 destination.IsRt = this.IsRt;
3670 destination.Source = this.Source;
3671 destination.IsExRt = this.IsExRt;
3672 destination.ExSource = this.ExSource;
3673 destination.UseLambda = this.UseLambda;
3674 destination.ExUseLambda = this.ExUseLambda;
3678 public override bool Equals(object obj)
3680 if (obj == null || this.GetType() != obj.GetType()) return false;
3681 return this.Equals((FiltersClass)obj);
3684 public override int GetHashCode()
3686 return this.MoveFrom.GetHashCode() ^
3687 this.SetMark.GetHashCode() ^
3688 this.BodyFilter.GetHashCode() ^
3689 this.NameFilter.GetHashCode() ^
3690 this.SearchBoth.GetHashCode() ^
3691 this.SearchUrl.GetHashCode() ^
3692 this.UseRegex.GetHashCode() ^
3693 this.ExBodyFilter.GetHashCode() ^
3694 this.ExNameFilter.GetHashCode() ^
3695 this.ExSearchBoth.GetHashCode() ^
3696 this.ExSearchUrl.GetHashCode() ^
3697 this.ExUseRegex.GetHashCode() ^
3698 this.IsRt.GetHashCode() ^
3699 this.Source.GetHashCode() ^
3700 this.IsExRt.GetHashCode() ^
3701 this.ExSource.GetHashCode() ^
3702 this.UseLambda.GetHashCode() ^
3703 this.ExUseLambda.GetHashCode();
3708 public sealed class IdComparerClass : IComparer<long>
3713 public enum ComparerMode
3722 private SortOrder _order;
3723 private ComparerMode _mode;
3724 private Dictionary<long, PostClass> _statuses;
3725 private Comparison<long> _CmpMethod;
3728 /// 昇順か降順か Setの際は同時に比較関数の切り替えを行う
3730 public SortOrder Order
3739 SetCmpMethod(_mode, _order);
3744 /// 並び替えの方法 Setの際は同時に比較関数の切り替えを行う
3746 public ComparerMode Mode
3755 SetCmpMethod(_mode, _order);
3760 /// ListViewItemComparerクラスのコンストラクタ(引数付は未使用)
3762 /// <param name="col">並び替える列番号</param>
3763 /// <param name="ord">昇順か降順か</param>
3764 /// <param name="cmod">並び替えの方法</param>
3766 public IdComparerClass()
3768 _order = SortOrder.Ascending;
3769 _mode = ComparerMode.Id;
3770 SetCmpMethod(_mode, _order);
3773 public Dictionary<long, PostClass> posts
3785 // 指定したソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
3786 public Comparison<long> CmpMethod(ComparerMode _sortmode, SortOrder _sortorder)
3790 Comparison<long> _method = null;
3791 if (_sortorder == SortOrder.Ascending)
3796 case ComparerMode.Data:
3797 _method = Compare_ModeData_Ascending;
3799 case ComparerMode.Id:
3800 _method = Compare_ModeId_Ascending;
3802 case ComparerMode.Name:
3803 _method = Compare_ModeName_Ascending;
3805 case ComparerMode.Nickname:
3806 _method = Compare_ModeNickName_Ascending;
3808 case ComparerMode.Source:
3809 _method = Compare_ModeSource_Ascending;
3818 case ComparerMode.Data:
3819 _method = Compare_ModeData_Descending;
3821 case ComparerMode.Id:
3822 _method = Compare_ModeId_Descending;
3824 case ComparerMode.Name:
3825 _method = Compare_ModeName_Descending;
3827 case ComparerMode.Nickname:
3828 _method = Compare_ModeNickName_Descending;
3830 case ComparerMode.Source:
3831 _method = Compare_ModeSource_Descending;
3839 // ソートモードとソートオーダーに従い使用する比較関数のアドレスを返す
3840 // (overload 現在の使用中の比較関数のアドレスを返す)
3841 public Comparison<long> CmpMethod()
3849 // ソートモードとソートオーダーに従い比較関数のアドレスを切り替え
3850 private void SetCmpMethod(ComparerMode mode, SortOrder order)
3852 _CmpMethod = this.CmpMethod(mode, order);
3855 //xがyより小さいときはマイナスの数、大きいときはプラスの数、
3856 //同じときは0を返す (こちらは未使用 一応比較関数群呼び出しの形のまま残しておく)
3857 int IComparer<long>.Compare(long x, long y)
3859 return _CmpMethod(x, y);
3862 // 比較用関数群 いずれもステータスIDの順序を考慮する
3864 public int Compare_ModeData_Ascending(long x, long y)
3866 var result = string.Compare(_statuses[x].TextFromApi, _statuses[y].TextFromApi);
3867 if (result == 0) result = x.CompareTo(y);
3872 public int Compare_ModeData_Descending(long x, long y)
3874 var result = string.Compare(_statuses[y].TextFromApi, _statuses[x].TextFromApi);
3875 if (result == 0) result = y.CompareTo(x);
3880 public int Compare_ModeId_Ascending(long x, long y)
3882 return x.CompareTo(y);
3886 public int Compare_ModeId_Descending(long x, long y)
3888 return y.CompareTo(x);
3892 public int Compare_ModeName_Ascending(long x, long y)
3894 var result = string.Compare(_statuses[x].ScreenName, _statuses[y].ScreenName);
3895 if (result == 0) result = x.CompareTo(y);
3900 public int Compare_ModeName_Descending(long x, long y)
3902 var result = string.Compare(_statuses[y].ScreenName, _statuses[x].ScreenName);
3903 if (result == 0) result = y.CompareTo(x);
3908 public int Compare_ModeNickName_Ascending(long x, long y)
3910 var result = string.Compare(_statuses[x].Nickname, _statuses[y].Nickname);
3911 if (result == 0) result = x.CompareTo(y);
3916 public int Compare_ModeNickName_Descending(long x, long y)
3918 var result = string.Compare(_statuses[y].Nickname, _statuses[x].Nickname);
3919 if (result == 0) result = y.CompareTo(x);
3924 public int Compare_ModeSource_Ascending(long x, long y)
3926 var result = string.Compare(_statuses[x].Source, _statuses[y].Source);
3927 if (result == 0) result = x.CompareTo(y);
3932 public int Compare_ModeSource_Descending(long x, long y)
3934 var result = string.Compare(_statuses[y].Source, _statuses[x].Source);
3935 if (result == 0) result = y.CompareTo(x);