OSDN Git Service

設定ファイルの読み込み・保存に関するコードをSettingManagerクラスに移動
[opentween/open-tween.git] / OpenTween / Tween.cs
index 14eccbe..f508b6a 100644 (file)
@@ -41,6 +41,7 @@ using System.Media;
 using System.Net;
 using System.Net.Http;
 using System.Reflection;
+using System.Runtime.InteropServices;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Threading;
@@ -51,6 +52,7 @@ using OpenTween.Api.DataModel;
 using OpenTween.Connection;
 using OpenTween.Models;
 using OpenTween.OpenTweenCustomControl;
+using OpenTween.Setting;
 using OpenTween.Thumbnail;
 
 namespace OpenTween
@@ -800,6 +802,7 @@ namespace OpenTween
 
             //Twitter用通信クラス初期化
             Networking.DefaultTimeout = TimeSpan.FromSeconds(this._cfgCommon.DefaultTimeOut);
+            Networking.UploadImageTimeout = TimeSpan.FromSeconds(this._cfgCommon.UploadImageTimeout);
             Networking.SetWebProxy(this._cfgLocal.ProxyType,
                 this._cfgLocal.ProxyAddress, this._cfgLocal.ProxyPort,
                 this._cfgLocal.ProxyUser, this._cfgLocal.ProxyPassword);
@@ -842,7 +845,7 @@ namespace OpenTween
             ImageSelector.Initialize(tw, this.tw.Configuration, _cfgCommon.UseImageServiceName, _cfgCommon.UseImageService);
 
             //ハッシュタグ/@id関連
-            AtIdSupl = new AtIdSupplement(SettingAtIdList.Load().AtIdList, "@");
+            AtIdSupl = new AtIdSupplement(SettingManager.AtIdList.AtIdList, "@");
             HashSupl = new AtIdSupplement(_cfgCommon.HashTags, "#");
             HashMgr = new HashtagManage(HashSupl,
                                     _cfgCommon.HashTags.ToArray(),
@@ -1195,31 +1198,17 @@ namespace OpenTween
 
         private void LoadConfig()
         {
-            _cfgCommon = SettingCommon.Load();
-            SettingCommon.Instance = this._cfgCommon;
-            if (_cfgCommon.UserAccounts == null || _cfgCommon.UserAccounts.Count == 0)
-            {
-                _cfgCommon.UserAccounts = new List<UserAccount>();
-                if (!string.IsNullOrEmpty(_cfgCommon.UserName))
-                {
-                    UserAccount account = new UserAccount();
-                    account.Username = _cfgCommon.UserName;
-                    account.UserId = _cfgCommon.UserId;
-                    account.Token = _cfgCommon.Token;
-                    account.TokenSecret = _cfgCommon.TokenSecret;
-
-                    _cfgCommon.UserAccounts.Add(account);
-                }
-            }
+            SettingManager.LoadAll();
 
-            _cfgLocal = SettingLocal.Load();
+            this._cfgCommon = SettingManager.Common;
+            this._cfgLocal = SettingManager.Local;
 
             // v1.2.4 以前の設定には ScaleDimension の項目がないため、現在の DPI と同じとして扱う
             if (_cfgLocal.ScaleDimension.IsEmpty)
                 _cfgLocal.ScaleDimension = this.CurrentAutoScaleDimensions;
 
-            var tabsSetting = SettingTabs.Load().Tabs;
-            foreach (var tabSetting in tabsSetting)
+            var tabSettings = SettingManager.Tabs;
+            foreach (var tabSetting in tabSettings.Tabs)
             {
                 TabModel tab;
                 switch (tabSetting.TabType)
@@ -1402,8 +1391,10 @@ namespace OpenTween
 
         private void RefreshTimeline()
         {
+            var curTabModel = this._statuses.Tabs[this._curTab.Text];
+
             // 現在表示中のタブのスクロール位置を退避
-            var curListScroll = this.SaveListViewScroll(this._curList, this._statuses.Tabs[this._curTab.Text]);
+            var curListScroll = this.SaveListViewScroll(this._curList, curTabModel);
 
             // 各タブのリスト上の選択位置などを退避
             var listSelections = this.SaveListViewSelection();
@@ -1418,48 +1409,56 @@ namespace OpenTween
 
             if (MyCommon._endingFlag) return;
 
-            //リストに反映&選択状態復元
-            try
+            // リストに反映&選択状態復元
+            foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
             {
-                foreach (TabPage tab in ListTab.TabPages)
+                var listView = (DetailsListView)tabPage.Tag;
+                var tabModel = this._statuses.Tabs[tabPage.Text];
+
+                if (listView.VirtualListSize != tabModel.AllCount || isDelete)
                 {
-                    DetailsListView lst = (DetailsListView)tab.Tag;
-                    TabModel tabInfo = _statuses.Tabs[tab.Text];
-                    if (isDelete || lst.VirtualListSize != tabInfo.AllCount)
+                    using (ControlTransaction.Update(listView))
                     {
-                        using (ControlTransaction.Update(lst))
-                        {
-                            if (lst.Equals(_curList))
-                            {
-                                this.PurgeListViewItemCache();
-                            }
-                            try
-                            {
-                                lst.VirtualListSize = tabInfo.AllCount; //リスト件数更新
-                            }
-                            catch (Exception)
-                            {
-                                //アイコン描画不具合あり?
-                            }
+                        if (listView == this._curList)
+                            this.PurgeListViewItemCache();
 
-                            // 選択位置などを復元
-                            this.RestoreListViewSelection(lst, tabInfo, listSelections[tabInfo.TabName]);
+                        try
+                        {
+                            // リスト件数更新
+                            listView.VirtualListSize = tabModel.AllCount;
+                        }
+                        catch (NullReferenceException ex)
+                        {
+                            // WinForms 内部で ListView.set_TopItem が発生させている例外
+                            // https://ja.osdn.net/ticket/browse.php?group_id=6526&tid=36588
+                            MyCommon.TraceOut(ex, $"TabType: {tabModel.TabType}, Count: {tabModel.AllCount}, ListSize: {listView.VirtualListSize}");
                         }
+
+                        // 選択位置などを復元
+                        this.RestoreListViewSelection(listView, tabModel, listSelections[tabModel.TabName]);
                     }
-                    if (tabInfo.UnreadCount > 0)
-                        if (this._cfgCommon.TabIconDisp)
-                            if (tab.ImageIndex == -1) tab.ImageIndex = 0; //タブアイコン
                 }
-                if (!this._cfgCommon.TabIconDisp) ListTab.Refresh();
             }
-            catch (Exception)
+
+            if (addCount > 0)
             {
-                //ex.Data["Msg"] = "Ref1, UseAPI=" + SettingDialog.UseAPI.ToString();
-                //throw;
+                if (this._cfgCommon.TabIconDisp)
+                {
+                    foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
+                    {
+                        var tabModel = this._statuses.Tabs[tabPage.Text];
+                        if (tabModel.UnreadCount > 0 && tabPage.ImageIndex != 0)
+                            tabPage.ImageIndex = 0; // 未読アイコン
+                    }
+                }
+                else
+                {
+                    this.ListTab.Refresh();
+                }
             }
 
             // スクロール位置を復元
-            this.RestoreListViewScroll(this._curList, this._statuses.Tabs[this._curTab.Text], curListScroll);
+            this.RestoreListViewScroll(this._curList, curTabModel, curListScroll);
 
             //新着通知
             NotifyNewPosts(notifyPosts, soundFile, addCount, newMentionOrDm);
@@ -1504,9 +1503,9 @@ namespace OpenTween
 
             if (listScroll.ScrollLockMode == ScrollLockMode.FixedToItem)
             {
-                var topItem = listView.TopItem;
-                if (topItem != null)
-                    listScroll.TopItemStatusId = tab.GetStatusIdAt(topItem.Index);
+                var topItemIndex = listView.TopItem?.Index ?? -1;
+                if (topItemIndex != -1 && topItemIndex < tab.AllCount)
+                    listScroll.TopItemStatusId = tab.GetStatusIdAt(topItemIndex);
             }
 
             return listScroll;
@@ -1570,32 +1569,35 @@ namespace OpenTween
                 var listView = (DetailsListView)tabPage.Tag;
                 var tab = _statuses.Tabs[tabPage.Text];
 
-                ListViewSelection listStatus;
-                if (listView.VirtualListSize != 0)
-                {
-                    listStatus = new ListViewSelection
-                    {
-                        SelectedStatusIds = this.GetSelectedStatusIds(listView, tab),
-                        FocusedStatusId = this.GetFocusedStatusId(listView, tab),
-                        SelectionMarkStatusId = this.GetSelectionMarkStatusId(listView, tab),
-                    };
-                }
-                else
-                {
-                    listStatus = new ListViewSelection
-                    {
-                        SelectedStatusIds = new long[0],
-                        SelectionMarkStatusId = null,
-                        FocusedStatusId = null,
-                    };
-                }
-
-                listsDict[tab.TabName] = listStatus;
+                listsDict[tab.TabName] = this.SaveListViewSelection(listView, tab);
             }
 
             return listsDict;
         }
 
+        /// <summary>
+        /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
+        /// </summary>
+        private ListViewSelection SaveListViewSelection(DetailsListView listView, TabModel tab)
+        {
+            if (listView.VirtualListSize == 0)
+            {
+                return new ListViewSelection
+                {
+                    SelectedStatusIds = new long[0],
+                    SelectionMarkStatusId = null,
+                    FocusedStatusId = null,
+                };
+            }
+
+            return new ListViewSelection
+            {
+                SelectedStatusIds = this.GetSelectedStatusIds(listView, tab),
+                FocusedStatusId = this.GetFocusedStatusId(listView, tab),
+                SelectionMarkStatusId = this.GetSelectionMarkStatusId(listView, tab),
+            };
+        }
+
         private long[] GetSelectedStatusIds(DetailsListView listView, TabModel tab)
         {
             var selectedIndices = listView.SelectedIndices;
@@ -1607,16 +1609,16 @@ namespace OpenTween
 
         private long? GetFocusedStatusId(DetailsListView listView, TabModel tab)
         {
-            var focusedItem = listView.FocusedItem;
+            var index = listView.FocusedItem?.Index ?? -1;
 
-            return focusedItem != null ? tab.GetStatusIdAt(focusedItem.Index) : (long?)null;
+            return index != -1 && index < tab.AllCount ? tab.GetStatusIdAt(index) : (long?)null;
         }
 
         private long? GetSelectionMarkStatusId(DetailsListView listView, TabModel tab)
         {
-            var selectionMarkIndex = listView.SelectionMark;
+            var index = listView.SelectionMark;
 
-            return selectionMarkIndex != -1 ? tab.GetStatusIdAt(selectionMarkIndex) : (long?)null;
+            return index != -1 && index < tab.AllCount ? tab.GetStatusIdAt(index) : (long?)null;
         }
 
         /// <summary>
@@ -2766,6 +2768,13 @@ namespace OpenTween
                 p.Report(errMsg);
                 this._myStatusError = true;
             }
+            catch (UnauthorizedAccessException ex)
+            {
+                // アップロード対象のファイルが開けなかった場合など
+                errMsg = $"Err:{ex.Message}(PostMessage)";
+                p.Report(errMsg);
+                this._myStatusError = true;
+            }
             finally
             {
                 // 使い終わった MediaItem は破棄する
@@ -2785,13 +2794,10 @@ namespace OpenTween
                 !errMsg.StartsWith("OK:", StringComparison.Ordinal) &&
                 !errMsg.StartsWith("Warn:", StringComparison.Ordinal))
             {
+                var message = string.Format(Properties.Resources.StatusUpdateFailed, errMsg, status.status);
+
                 var ret = MessageBox.Show(
-                    string.Format(
-                        "{0}   --->   [ " + errMsg + " ]" + Environment.NewLine +
-                        "\"" + status.status + "\"" + Environment.NewLine +
-                        "{1}",
-                        Properties.Resources.StatusUpdateFailed1,
-                        Properties.Resources.StatusUpdateFailed2),
+                    message,
                     "Failed to update status",
                     MessageBoxButtons.RetryCancel,
                     MessageBoxIcon.Question);
@@ -3361,7 +3367,7 @@ namespace OpenTween
                 FavorareMenuItem.Enabled = true;
                 ShowRelatedStatusesMenuItem.Enabled = true;  //PublicSearchの時問題出るかも
 
-                if (_curPost.IsProtect)
+                if (!_curPost.CanRetweetBy(this.twitterApi.CurrentUserId))
                 {
                     ReTweetStripMenuItem.Enabled = false;
                     ReTweetUnofficialStripMenuItem.Enabled = false;
@@ -3467,14 +3473,29 @@ namespace OpenTween
                         }
                         else
                         {
-                            if (post.RetweetedId != null && post.UserId == this.tw.UserId)
-                                // 他人に RT された自分のツイート
-                                await this.twitterApi.StatusesDestroy(post.RetweetedId.Value)
-                                    .IgnoreResponse();
-                            else
-                                // 自分のツイート or 自分が RT したツイート
+                            if (post.RetweetedByUserId == this.tw.UserId)
+                            {
+                                // 自分が RT したツイート (自分が RT した自分のツイートも含む)
+                                //   => RT を取り消し
                                 await this.twitterApi.StatusesDestroy(post.StatusId)
                                     .IgnoreResponse();
+                            }
+                            else
+                            {
+                                if (post.UserId == this.tw.UserId)
+                                {
+                                    if (post.RetweetedId != null)
+                                        // 他人に RT された自分のツイート
+                                        //   => RT 元の自分のツイートを削除
+                                        await this.twitterApi.StatusesDestroy(post.RetweetedId.Value)
+                                            .IgnoreResponse();
+                                    else
+                                        // 自分のツイート
+                                        //   => ツイートを削除
+                                        await this.twitterApi.StatusesDestroy(post.StatusId)
+                                            .IgnoreResponse();
+                                }
+                            }
                         }
                     }
                     catch (WebApiException ex)
@@ -3743,6 +3764,7 @@ namespace OpenTween
                     TwitterApiConnection.RestApiHost = this._cfgCommon.TwitterApiHost;
 
                     Networking.DefaultTimeout = TimeSpan.FromSeconds(this._cfgCommon.DefaultTimeOut);
+                    Networking.UploadImageTimeout = TimeSpan.FromSeconds(this._cfgCommon.UploadImageTimeout);
                     Networking.SetWebProxy(this._cfgLocal.ProxyType,
                         this._cfgLocal.ProxyAddress, this._cfgLocal.ProxyPort,
                         this._cfgLocal.ProxyUser, this._cfgLocal.ProxyPassword);
@@ -4798,9 +4820,11 @@ namespace OpenTween
             //文字数カウント
             var remainCount = this.tw.GetTextLengthRemain(statusText);
 
-            if (this.ImageSelector.Visible && !string.IsNullOrEmpty(this.ImageSelector.ServiceName))
+            var uploadService = this.ImageSelector.SelectedService;
+            if (this.ImageSelector.Visible && uploadService != null)
             {
-                remainCount -= this.tw.Configuration.CharactersReservedPerMedia;
+                // TODO: ImageSelector で選択中の画像の枚数が mediaCount 引数に渡るようにする
+                remainCount -= uploadService.GetReservedTextLength(1);
             }
 
             return remainCount;
@@ -4812,7 +4836,7 @@ namespace OpenTween
                 return;
 
             var listCache = this._listItemCache;
-            if (listCache != null && listCache.IsSupersetOf(e.StartIndex, e.EndIndex))
+            if (listCache?.TargetList == sender && listCache.IsSupersetOf(e.StartIndex, e.EndIndex))
             {
                 // If the newly requested cache is a subset of the old cache,
                 // no need to rebuild everything, so do nothing.
@@ -4826,7 +4850,7 @@ namespace OpenTween
         private void MyList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
         {
             var listCache = this._listItemCache;
-            if (listCache != null && listCache.TargetList == sender)
+            if (listCache?.TargetList == sender)
             {
                 ListViewItem item;
                 PostClass cacheItemPost;
@@ -5084,7 +5108,7 @@ namespace OpenTween
                         using (Font fnt = new Font(e.Item.Font, FontStyle.Bold))
                         {
                             TextRenderer.DrawText(e.Graphics,
-                                                    post.TextSingleLine,
+                                                    post.IsDeleted ? "(DELETED)" : post.TextSingleLine,
                                                     e.Item.Font,
                                                     Rectangle.Round(rct),
                                                     color,
@@ -5103,30 +5127,39 @@ namespace OpenTween
                                                     TextFormatFlags.NoPrefix);
                         }
                     }
-                    else if (drawLineCount == 1)
-                    {
-                        TextRenderer.DrawText(e.Graphics,
-                                                e.ColumnIndex != 2 ? e.SubItem.Text : post.TextSingleLine,
-                                                e.Item.Font,
-                                                Rectangle.Round(rct),
-                                                color,
-                                                TextFormatFlags.SingleLine |
-                                                TextFormatFlags.EndEllipsis |
-                                                TextFormatFlags.GlyphOverhangPadding |
-                                                TextFormatFlags.NoPrefix |
-                                                TextFormatFlags.VerticalCenter);
-                    }
                     else
                     {
-                        TextRenderer.DrawText(e.Graphics,
-                                                e.ColumnIndex != 2 ? e.SubItem.Text : post.TextSingleLine,
-                                                e.Item.Font,
-                                                Rectangle.Round(rct),
-                                                color,
-                                                TextFormatFlags.WordBreak |
-                                                TextFormatFlags.EndEllipsis |
-                                                TextFormatFlags.GlyphOverhangPadding |
-                                                TextFormatFlags.NoPrefix);
+                        string text;
+                        if (e.ColumnIndex != 2)
+                            text = e.SubItem.Text;
+                        else
+                            text = post.IsDeleted ? "(DELETED)" : post.TextSingleLine;
+
+                        if (drawLineCount == 1)
+                        {
+                            TextRenderer.DrawText(e.Graphics,
+                                                    text,
+                                                    e.Item.Font,
+                                                    Rectangle.Round(rct),
+                                                    color,
+                                                    TextFormatFlags.SingleLine |
+                                                    TextFormatFlags.EndEllipsis |
+                                                    TextFormatFlags.GlyphOverhangPadding |
+                                                    TextFormatFlags.NoPrefix |
+                                                    TextFormatFlags.VerticalCenter);
+                        }
+                        else
+                        {
+                            TextRenderer.DrawText(e.Graphics,
+                                                    text,
+                                                    e.Item.Font,
+                                                    Rectangle.Round(rct),
+                                                    color,
+                                                    TextFormatFlags.WordBreak |
+                                                    TextFormatFlags.EndEllipsis |
+                                                    TextFormatFlags.GlyphOverhangPadding |
+                                                    TextFormatFlags.NoPrefix);
+                        }
                     }
                     //if (e.ColumnIndex == 6) this.DrawListViewItemStateIcon(e, rct);
                 }
@@ -5460,74 +5493,89 @@ namespace OpenTween
         private void JumpUnreadMenuItem_Click(object sender, EventArgs e)
         {
             int bgnIdx = ListTab.TabPages.IndexOf(_curTab);
-            int idx = -1;
-            DetailsListView lst = null;
 
             if (ImageSelector.Enabled)
                 return;
 
+            TabModel foundTab = null;
+            int foundIndex = 0;
+
+            DetailsListView lst = null;
+
             //現在タブから最終タブまで探索
             for (int i = bgnIdx; i < ListTab.TabPages.Count; i++)
             {
-                //未読Index取得
-                idx = _statuses.Tabs[ListTab.TabPages[i].Text].NextUnreadIndex;
-                if (idx > -1)
+                var tabPage = this.ListTab.TabPages[i];
+                var tab = this._statuses.Tabs[tabPage.Text];
+                var unreadIndex = tab.NextUnreadIndex;
+
+                if (unreadIndex != -1)
                 {
                     ListTab.SelectedIndex = i;
-                    lst = (DetailsListView)ListTab.TabPages[i].Tag;
-                    //_curTab = ListTab.TabPages[i];
+                    foundTab = tab;
+                    foundIndex = unreadIndex;
+                    lst = (DetailsListView)tabPage.Tag;
                     break;
                 }
             }
 
             //未読みつからず&現在タブが先頭ではなかったら、先頭タブから現在タブの手前まで探索
-            if (idx == -1 && bgnIdx > 0)
+            if (foundTab == null && bgnIdx > 0)
             {
                 for (int i = 0; i < bgnIdx; i++)
                 {
-                    idx = _statuses.Tabs[ListTab.TabPages[i].Text].NextUnreadIndex;
-                    if (idx > -1)
+                    var tabPage = this.ListTab.TabPages[i];
+                    var tab = this._statuses.Tabs[tabPage.Text];
+                    var unreadIndex = tab.NextUnreadIndex;
+
+                    if (unreadIndex != -1)
                     {
                         ListTab.SelectedIndex = i;
-                        lst = (DetailsListView)ListTab.TabPages[i].Tag;
-                        //_curTab = ListTab.TabPages[i];
+                        foundTab = tab;
+                        foundIndex = unreadIndex;
+                        lst = (DetailsListView)tabPage.Tag;
                         break;
                     }
                 }
             }
 
-            //全部調べたが未読見つからず→先頭タブの最新発言へ
-            if (idx == -1)
+            if (foundTab == null)
             {
+                //全部調べたが未読見つからず→先頭タブの最新発言へ
                 ListTab.SelectedIndex = 0;
-                lst = (DetailsListView)ListTab.TabPages[0].Tag;
-                //_curTab = ListTab.TabPages[0];
+                var tabPage = this.ListTab.TabPages[0];
+                var tab = this._statuses.Tabs[tabPage.Text];
+
+                if (tab.AllCount == 0)
+                    return;
+
                 if (_statuses.SortOrder == SortOrder.Ascending)
-                    idx = lst.VirtualListSize - 1;
+                    foundIndex = tab.AllCount - 1;
                 else
-                    idx = 0;
+                    foundIndex = 0;
+
+                lst = (DetailsListView)tabPage.Tag;
             }
 
-            if (lst.VirtualListSize > 0 && idx > -1 && lst.VirtualListSize > idx)
+            SelectListItem(lst, foundIndex);
+
+            if (_statuses.SortMode == ComparerMode.Id)
             {
-                SelectListItem(lst, idx);
-                if (_statuses.SortMode == ComparerMode.Id)
+                if (_statuses.SortOrder == SortOrder.Ascending && lst.Items[foundIndex].Position.Y > lst.ClientSize.Height - _iconSz - 10 ||
+                    _statuses.SortOrder == SortOrder.Descending && lst.Items[foundIndex].Position.Y < _iconSz + 10)
                 {
-                    if (_statuses.SortOrder == SortOrder.Ascending && lst.Items[idx].Position.Y > lst.ClientSize.Height - _iconSz - 10 ||
-                       _statuses.SortOrder == SortOrder.Descending && lst.Items[idx].Position.Y < _iconSz + 10)
-                    {
-                        MoveTop();
-                    }
-                    else
-                    {
-                        lst.EnsureVisible(idx);
-                    }
+                    MoveTop();
                 }
                 else
                 {
-                    lst.EnsureVisible(idx);
+                    lst.EnsureVisible(foundIndex);
                 }
             }
+            else
+            {
+                lst.EnsureVisible(foundIndex);
+            }
+
             lst.Focus();
         }
 
@@ -6389,11 +6437,6 @@ namespace OpenTween
             foreach (int idx in _curList.SelectedIndices)
             {
                 PostClass post = _statuses.Tabs[_curTab.Text][idx];
-                if (post.IsProtect)
-                {
-                    IsProtected = true;
-                    continue;
-                }
                 if (post.IsDeleted) continue;
                 if (!isDm)
                 {
@@ -6427,28 +6470,30 @@ namespace OpenTween
 
         private void CopyIdUri()
         {
-            string clstr = "";
-            StringBuilder sb = new StringBuilder();
-            if (this._curTab == null) return;
-            if (this._statuses.GetTabByName(this._curTab.Text) == null) return;
-            if (this._statuses.GetTabByName(this._curTab.Text).TabType == MyCommon.TabUsageType.DirectMessage) return;
+            if (this._curTab == null)
+                return;
+
+            var tab = this._statuses.GetTabByName(this._curTab.Text);
+            if (tab == null || tab is DirectMessagesTabModel)
+                return;
+
+            var copyUrls = new List<string>();
             foreach (int idx in _curList.SelectedIndices)
             {
-                var post = _statuses.Tabs[_curTab.Text][idx];
-                sb.Append(MyCommon.GetStatusUrl(post));
-                sb.Append(Environment.NewLine);
+                var post = tab[idx];
+                copyUrls.Add(MyCommon.GetStatusUrl(post));
             }
-            if (sb.Length > 0)
+
+            if (copyUrls.Count == 0)
+                return;
+
+            try
             {
-                clstr = sb.ToString();
-                try
-                {
-                    Clipboard.SetDataObject(clstr, false, 5, 100);
-                }
-                catch (Exception ex)
-                {
-                    MessageBox.Show(ex.Message);
-                }
+                Clipboard.SetDataObject(string.Join(Environment.NewLine, copyUrls), false, 5, 100);
+            }
+            catch (ExternalException ex)
+            {
+                MessageBox.Show(ex.Message);
             }
         }
 
@@ -6501,16 +6546,19 @@ namespace OpenTween
 
         private void GoSamePostToAnotherTab(bool left)
         {
-            if (_curList.VirtualListSize == 0) return;
-            int fIdx = 0;
-            int toIdx = 0;
-            int stp = 1;
-            long targetId = 0;
+            if (this._curList.SelectedIndices.Count == 0)
+                return;
 
-            if (_statuses.Tabs[_curTab.Text].TabType == MyCommon.TabUsageType.DirectMessage) return; // Directタブは対象外(見つかるはずがない)
-            if (_curList.SelectedIndices.Count == 0) return; //未選択も処理しない
+            var tab = this._statuses.Tabs[this._curTab.Text];
 
-            targetId = GetCurTabPost(_curList.SelectedIndices[0]).StatusId;
+            // Directタブは対象外(見つかるはずがない)
+            if (tab.TabType == MyCommon.TabUsageType.DirectMessage)
+                return;
+
+            var selectedIndex = this._curList.SelectedIndices[0];
+            var selectedStatusId = tab.GetStatusIdAt(selectedIndex);
+
+            int fIdx, toIdx, stp;
 
             if (left)
             {
@@ -6541,42 +6589,45 @@ namespace OpenTween
                 stp = 1;
             }
 
-            bool found = false;
             for (int tabidx = fIdx; tabidx != toIdx; tabidx += stp)
             {
-                if (_statuses.Tabs[ListTab.TabPages[tabidx].Text].TabType == MyCommon.TabUsageType.DirectMessage) continue; // Directタブは対象外
-                for (int idx = 0; idx < ((DetailsListView)ListTab.TabPages[tabidx].Tag).VirtualListSize; idx++)
+                var targetTab = this._statuses.Tabs[this.ListTab.TabPages[tabidx].Text];
+
+                // Directタブは対象外
+                if (targetTab.TabType == MyCommon.TabUsageType.DirectMessage)
+                    continue;
+
+                var foundIndex = targetTab.IndexOf(selectedStatusId);
+                if (foundIndex != -1)
                 {
-                    if (_statuses.Tabs[ListTab.TabPages[tabidx].Text][idx].StatusId == targetId)
-                    {
-                        ListTab.SelectedIndex = tabidx;
-                        SelectListItem(_curList, idx);
-                        _curList.EnsureVisible(idx);
-                        found = true;
-                        break;
-                    }
+                    ListTab.SelectedIndex = tabidx;
+                    SelectListItem(_curList, foundIndex);
+                    _curList.EnsureVisible(foundIndex);
+                    return;
                 }
-                if (found) break;
             }
         }
 
         private void GoPost(bool forward)
         {
-            if (_curList.SelectedIndices.Count == 0 || _curPost == null) return;
-            int fIdx = 0;
-            int toIdx = 0;
-            int stp = 1;
+            if (_curList.SelectedIndices.Count == 0 || _curPost == null)
+                return;
+
+            var tab = this._statuses.Tabs[this._curTab.Text];
+            var selectedIndex = this._curList.SelectedIndices[0];
+
+            int fIdx, toIdx, stp;
 
             if (forward)
             {
-                fIdx = _curList.SelectedIndices[0] + 1;
-                if (fIdx > _curList.VirtualListSize - 1) return;
-                toIdx = _curList.VirtualListSize;
+                fIdx = selectedIndex + 1;
+                if (fIdx > tab.AllCount - 1) return;
+                toIdx = tab.AllCount;
                 stp = 1;
             }
             else
             {
-                fIdx = _curList.SelectedIndices[0] - 1;
+                fIdx = selectedIndex - 1;
                 if (fIdx < 0) return;
                 toIdx = -1;
                 stp = -1;
@@ -6593,9 +6644,10 @@ namespace OpenTween
             }
             for (int idx = fIdx; idx != toIdx; idx += stp)
             {
-                if (_statuses.Tabs[_curTab.Text][idx].RetweetedId == null)
+                var post = tab[idx];
+                if (post.RetweetedId == null)
                 {
-                    if (_statuses.Tabs[_curTab.Text][idx].ScreenName == name)
+                    if (post.ScreenName == name)
                     {
                         SelectListItem(_curList, idx);
                         _curList.EnsureVisible(idx);
@@ -6604,7 +6656,7 @@ namespace OpenTween
                 }
                 else
                 {
-                    if (_statuses.Tabs[_curTab.Text][idx].RetweetedBy == name)
+                    if (post.RetweetedBy == name)
                     {
                         SelectListItem(_curList, idx);
                         _curList.EnsureVisible(idx);
@@ -6616,21 +6668,24 @@ namespace OpenTween
 
         private void GoRelPost(bool forward)
         {
-            if (_curList.SelectedIndices.Count == 0) return;
+            if (this._curList.SelectedIndices.Count == 0)
+                return;
+
+            var tab = this._statuses.Tabs[this._curTab.Text];
+            var selectedIndex = this._curList.SelectedIndices[0];
+
+            int fIdx, toIdx, stp;
 
-            int fIdx = 0;
-            int toIdx = 0;
-            int stp = 1;
             if (forward)
             {
-                fIdx = _curList.SelectedIndices[0] + 1;
-                if (fIdx > _curList.VirtualListSize - 1) return;
-                toIdx = _curList.VirtualListSize;
+                fIdx = selectedIndex + 1;
+                if (fIdx > tab.AllCount - 1) return;
+                toIdx = tab.AllCount;
                 stp = 1;
             }
             else
             {
-                fIdx = _curList.SelectedIndices[0] - 1;
+                fIdx = selectedIndex - 1;
                 if (fIdx < 0) return;
                 toIdx = -1;
                 stp = -1;
@@ -6649,7 +6704,7 @@ namespace OpenTween
 
             for (int idx = fIdx; idx != toIdx; idx += stp)
             {
-                PostClass post = _statuses.Tabs[_curTab.Text][idx];
+                var post = tab[idx];
                 if (post.ScreenName == _anchorPost.ScreenName ||
                     post.RetweetedBy == _anchorPost.ScreenName ||
                     post.ScreenName == _anchorPost.RetweetedBy ||
@@ -7143,8 +7198,8 @@ namespace OpenTween
             if (_ignoreConfigSave || !this._cfgCommon.UseAtIdSupplement && AtIdSupl == null) return;
 
             ModifySettingAtId = false;
-            SettingAtIdList cfgAtId = new SettingAtIdList(AtIdSupl.GetItemList());
-            cfgAtId.Save();
+            SettingManager.AtIdList.AtIdList = this.AtIdSupl.GetItemList();
+            SettingManager.SaveAtIdList();
         }
 
         private void SaveConfigsCommon()
@@ -7207,7 +7262,7 @@ namespace OpenTween
                 _cfgCommon.UseImageService = ImageSelector.ServiceIndex;
                 _cfgCommon.UseImageServiceName = ImageSelector.ServiceName;
 
-                _cfgCommon.Save();
+                SettingManager.SaveCommon();
             }
         }
 
@@ -7248,13 +7303,13 @@ namespace OpenTween
                 _cfgLocal.FontInputFont = _fntInputFont;
 
                 if (_ignoreConfigSave) return;
-                _cfgLocal.Save();
+                SettingManager.SaveLocal();
             }
         }
 
         private void SaveConfigsTabs()
         {
-            var tabsSetting = new SettingTabs();
+            var tabSettingList = new List<SettingTabs.SettingTabItem>();
 
             var tabs = this.ListTab.TabPages.Cast<TabPage>()
                 .Select(x => this._statuses.Tabs[x.Text])
@@ -7294,10 +7349,11 @@ namespace OpenTween
                 if (listTab != null)
                     tabSetting.ListInfo = listTab.ListInfo;
 
-                tabsSetting.Tabs.Add(tabSetting);
+                tabSettingList.Add(tabSetting);
             }
 
-            tabsSetting.Save();
+            SettingManager.Tabs.Tabs = tabSettingList;
+            SettingManager.SaveTabs();
         }
 
         private async void OpenURLFileMenuItem_Click(object sender, EventArgs e)
@@ -7385,45 +7441,43 @@ namespace OpenTween
             this.TopMost = this._cfgCommon.AlwaysTop;
         }
 
-        public bool TabRename(ref string tabName)
+        public bool TabRename(string origTabName, out string newTabName)
         {
             //タブ名変更
-            string newTabText = null;
+            newTabName = null;
             using (InputTabName inputName = new InputTabName())
             {
-                inputName.TabName = tabName;
+                inputName.TabName = origTabName;
                 inputName.ShowDialog();
                 if (inputName.DialogResult == DialogResult.Cancel) return false;
-                newTabText = inputName.TabName;
+                newTabName = inputName.TabName;
             }
             this.TopMost = this._cfgCommon.AlwaysTop;
-            if (!string.IsNullOrEmpty(newTabText))
+            if (!string.IsNullOrEmpty(newTabName))
             {
                 //新タブ名存在チェック
                 for (int i = 0; i < ListTab.TabCount; i++)
                 {
-                    if (ListTab.TabPages[i].Text == newTabText)
+                    if (ListTab.TabPages[i].Text == newTabName)
                     {
-                        string tmp = string.Format(Properties.Resources.Tabs_DoubleClickText1, newTabText);
+                        string tmp = string.Format(Properties.Resources.Tabs_DoubleClickText1, newTabName);
                         MessageBox.Show(tmp, Properties.Resources.Tabs_DoubleClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                         return false;
                     }
                 }
-                //タブ名を変更
-                for (int i = 0; i < ListTab.TabCount; i++)
-                {
-                    if (ListTab.TabPages[i].Text == tabName)
-                    {
-                        ListTab.TabPages[i].Text = newTabText;
-                        break;
-                    }
-                }
-                _statuses.RenameTab(tabName, newTabText);
+
+                var tabPage = this.ListTab.TabPages.Cast<TabPage>()
+                    .FirstOrDefault(x => x.Text == origTabName);
+
+                // タブ名を変更
+                if (tabPage != null)
+                    tabPage.Text = newTabName;
+
+                _statuses.RenameTab(origTabName, newTabName);
 
                 SaveConfigsCommon();
                 SaveConfigsTabs();
-                _rclickTabName = newTabText;
-                tabName = newTabText;
+                _rclickTabName = newTabName;
                 return true;
             }
             else
@@ -7450,8 +7504,8 @@ namespace OpenTween
 
         private void ListTab_DoubleClick(object sender, MouseEventArgs e)
         {
-            string tn = ListTab.SelectedTab.Text;
-            TabRename(ref tn);
+            string _;
+            TabRename(this.ListTab.SelectedTab.Text, out _);
         }
 
         private void ListTab_MouseDown(object sender, MouseEventArgs e)
@@ -7944,8 +7998,9 @@ namespace OpenTween
             if (_statuses == null) return;
             if (_statuses.Tabs == null) return;
 
-            TabModel tb = _statuses.Tabs[_rclickTabName];
-            if (tb == null) return;
+            TabModel tb;
+            if (!this._statuses.Tabs.TryGetValue(this._rclickTabName, out tb))
+                return;
 
             NotifyDispMenuItem.Checked = tb.Notify;
             this.NotifyTbMenuItem.Checked = tb.Notify;
@@ -8660,8 +8715,8 @@ namespace OpenTween
             {
                 if (tb.Text == tabName)
                 {
-                    tb.ImageIndex = -1;
                     ((DetailsListView)tb.Tag).VirtualListSize = 0;
+                    tb.ImageIndex = -1;
                     break;
                 }
             }
@@ -9822,6 +9877,7 @@ namespace OpenTween
 
             _curTab = _tab;
             _curList = (DetailsListView)_tab.Tag;
+
             if (_curList.SelectedIndices.Count > 0)
             {
                 _curItemIndex = _curList.SelectedIndices[0];
@@ -10037,9 +10093,11 @@ namespace OpenTween
             //公式RT
             if (this.ExistCurrentPost)
             {
-                if (_curPost.IsProtect)
+                if (!_curPost.CanRetweetBy(this.twitterApi.CurrentUserId))
                 {
-                    MessageBox.Show("Protected.");
+                    if (this._curPost.IsProtect)
+                        MessageBox.Show("Protected.");
+
                     _DoFavRetweetFlags = false;
                     return;
                 }
@@ -10063,11 +10121,6 @@ namespace OpenTween
                 }
                 else
                 {
-                    if (_curPost.IsDm)
-                    {
-                        _DoFavRetweetFlags = false;
-                        return;
-                    }
                     if (!this._cfgCommon.RetweetNoConfirm)
                     {
                         string Questiontext = Properties.Resources.RetweetQuestion1;
@@ -10084,7 +10137,7 @@ namespace OpenTween
                 foreach (int idx in _curList.SelectedIndices)
                 {
                     PostClass post = GetCurTabPost(idx);
-                    if (!post.IsProtect && !post.IsDm)
+                    if (post.CanRetweetBy(this.twitterApi.CurrentUserId))
                         statusIds.Add(post.StatusId);
                 }
 
@@ -10204,7 +10257,9 @@ namespace OpenTween
         private void TabRenameMenuItem_Click(object sender, EventArgs e)
         {
             if (string.IsNullOrEmpty(_rclickTabName)) return;
-            TabRename(ref _rclickTabName);
+
+            string _;
+            TabRename(_rclickTabName, out _);
         }
 
         private async void BitlyToolStripMenuItem_Click(object sender, EventArgs e)
@@ -10754,38 +10809,16 @@ namespace OpenTween
             await this.doMoveToRTHome();
         }
 
-        private async void ListManageUserContextToolStripMenuItem_Click(object sender, EventArgs e)
+        private void ListManageUserContextToolStripMenuItem_Click(object sender, EventArgs e)
         {
             var screenName = this._curPost?.ScreenName;
             if (screenName != null)
-                await this.ListManageUserContext(screenName);
+                this.ListManageUserContext(screenName);
         }
 
-        public async Task ListManageUserContext(string screenName)
+        public void ListManageUserContext(string screenName)
         {
-            if (this._statuses.SubscribableLists.Count == 0)
-            {
-                try
-                {
-                    using (var dialog = new WaitingDialog(Properties.Resources.ListsGetting))
-                    {
-                        var cancellationToken = dialog.EnableCancellation();
-
-                        var task = this.tw.GetListsApi();
-                        await dialog.WaitForAsync(this, task);
-
-                        cancellationToken.ThrowIfCancellationRequested();
-                    }
-                }
-                catch (OperationCanceledException) { return; }
-                catch (WebApiException ex)
-                {
-                    MessageBox.Show("Failed to get lists. (" + ex.Message + ")");
-                    return;
-                }
-            }
-
-            using (MyLists listSelectForm = new MyLists(screenName, this.tw))
+            using (var listSelectForm = new MyLists(screenName, this.twitterApi))
             {
                 listSelectForm.ShowDialog(this);
             }
@@ -10956,7 +10989,7 @@ namespace OpenTween
                 this.OpenFavotterOpMenuItem.Enabled = true;
                 this.ShowRelatedStatusesMenuItem2.Enabled = true;  //PublicSearchの時問題出るかも
 
-                if (_curPost.IsProtect)
+                if (!_curPost.CanRetweetBy(this.twitterApi.CurrentUserId))
                 {
                     this.RtOpMenuItem.Enabled = false;
                     this.RtUnOpMenuItem.Enabled = false;
@@ -11317,8 +11350,7 @@ namespace OpenTween
                 ModifySettingCommon = true;
                 SaveConfigsAll(true);
 
-                if (ImageSelector.ServiceName.Equals("Twitter"))
-                    this.StatusText_TextChanged(null, null);
+                this.StatusText_TextChanged(null, null);
             }
         }
 
@@ -11332,28 +11364,35 @@ namespace OpenTween
         /// </summary>
         private void ProcClipboardFromStatusTextWhenCtrlPlusV()
         {
-            if (Clipboard.ContainsText())
-            {
-                // clipboardにテキストがある場合は貼り付け処理
-                this.StatusText.Paste(Clipboard.GetText());
-            }
-            else if (Clipboard.ContainsImage())
+            try
             {
-                // 画像があるので投稿処理を行う
-                if (MessageBox.Show(Properties.Resources.PostPictureConfirm3,
-                                   Properties.Resources.PostPictureWarn4,
-                                   MessageBoxButtons.OKCancel,
-                                   MessageBoxIcon.Question,
-                                   MessageBoxDefaultButton.Button2)
-                               == DialogResult.OK)
+                if (Clipboard.ContainsText())
+                {
+                    // clipboardにテキストがある場合は貼り付け処理
+                    this.StatusText.Paste(Clipboard.GetText());
+                }
+                else if (Clipboard.ContainsImage())
                 {
-                    // clipboardから画像を取得
-                    using (var image = Clipboard.GetImage())
+                    // 画像があるので投稿処理を行う
+                    if (MessageBox.Show(Properties.Resources.PostPictureConfirm3,
+                                       Properties.Resources.PostPictureWarn4,
+                                       MessageBoxButtons.OKCancel,
+                                       MessageBoxIcon.Question,
+                                       MessageBoxDefaultButton.Button2)
+                                   == DialogResult.OK)
                     {
-                        this.ImageSelector.BeginSelection(image);
+                        // clipboardから画像を取得
+                        using (var image = Clipboard.GetImage())
+                        {
+                            this.ImageSelector.BeginSelection(image);
+                        }
                     }
                 }
             }
+            catch (ExternalException ex)
+            {
+                MessageBox.Show(ex.Message);
+            }
         }
 #endregion
 
@@ -11510,23 +11549,23 @@ namespace OpenTween
         }
 
 #region "Userstream"
-        private void tw_PostDeleted(object sender, PostDeletedEventArgs e)
+        private async void tw_PostDeleted(object sender, PostDeletedEventArgs e)
         {
             try
             {
                 if (InvokeRequired && !IsDisposed)
                 {
-                    Invoke((Action) (async () =>
-                           {
-                               this._statuses.RemovePostFromAllTabs(e.StatusId, setIsDeleted: true);
-                               if (_curTab != null && _statuses.Tabs[_curTab.Text].Contains(e.StatusId))
-                               {
-                                   this.PurgeListViewItemCache();
-                                   ((DetailsListView)_curTab.Tag).Update();
-                                   if (_curPost != null && _curPost.StatusId == e.StatusId)
-                                       await this.DispSelectedPost(true);
-                               }
-                           }));
+                    await this.InvokeAsync(async () =>
+                    {
+                        this._statuses.RemovePostFromAllTabs(e.StatusId, setIsDeleted: true);
+                        if (_curTab != null && _statuses.Tabs[_curTab.Text].Contains(e.StatusId))
+                        {
+                            this.PurgeListViewItemCache();
+                            ((DetailsListView)_curTab.Tag).Update();
+                            if (_curPost != null && _curPost.StatusId == e.StatusId)
+                                await this.DispSelectedPost(true);
+                        }
+                    });
                     return;
                 }
             }
@@ -11568,13 +11607,13 @@ namespace OpenTween
             }
         }
 
-        private void tw_UserStreamStarted(object sender, EventArgs e)
+        private async void tw_UserStreamStarted(object sender, EventArgs e)
         {
             try
             {
                 if (InvokeRequired && !IsDisposed)
                 {
-                    Invoke((Action)(() => this.tw_UserStreamStarted(sender, e)));
+                    await this.InvokeAsync(() => this.tw_UserStreamStarted(sender, e));
                     return;
                 }
             }
@@ -11593,13 +11632,13 @@ namespace OpenTween
             StatusLabel.Text = "UserStream Started.";
         }
 
-        private void tw_UserStreamStopped(object sender, EventArgs e)
+        private async void tw_UserStreamStopped(object sender, EventArgs e)
         {
             try
             {
                 if (InvokeRequired && !IsDisposed)
                 {
-                    Invoke((Action)(() => this.tw_UserStreamStopped(sender, e)));
+                    await this.InvokeAsync(() => this.tw_UserStreamStopped(sender, e));
                     return;
                 }
             }
@@ -11632,13 +11671,13 @@ namespace OpenTween
             }
         }
 
-        private void tw_UserStreamEventArrived(object sender, UserStreamEventReceivedEventArgs e)
+        private async void tw_UserStreamEventArrived(object sender, UserStreamEventReceivedEventArgs e)
         {
             try
             {
                 if (InvokeRequired && !IsDisposed)
                 {
-                    Invoke((Action)(() => this.tw_UserStreamEventArrived(sender, e)));
+                    await this.InvokeAsync(() => this.tw_UserStreamEventArrived(sender, e));
                     return;
                 }
             }
@@ -11970,11 +12009,11 @@ namespace OpenTween
             await this.OpenUserAppointUrl();
         }
 
-        private void GrowlHelper_Callback(object sender, GrowlHelper.NotifyCallbackEventArgs e)
+        private async void GrowlHelper_Callback(object sender, GrowlHelper.NotifyCallbackEventArgs e)
         {
             if (Form.ActiveForm == null)
             {
-                this.BeginInvoke((Action) (() =>
+                await this.InvokeAsync(() =>
                 {
                     this.Visible = true;
                     if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
@@ -11988,7 +12027,7 @@ namespace OpenTween
                     {
                         if (!this.GoStatus(e.StatusId)) this.StatusText.Focus();
                     }
-                }));
+                });
             }
         }