OSDN Git Service

7521e2374fd84bcbd056d7104f20cbaeb247ac1c
[opentween/open-tween.git] / OpenTween / MediaSelectorPanel.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 spx (@5px)
3 // Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
4 // All rights reserved.
5 //
6 // This file is part of OpenTween.
7 //
8 // This program is free software; you can redistribute it and/or modify it
9 // under the terms of the GNU General Public License as published by the Free
10 // Software Foundation; either version 3 of the License, or (at your option)
11 // any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 // for more details.
17 //
18 // You should have received a copy of the GNU General Public License along
19 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
20 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
21 // Boston, MA 02110-1301, USA.
22
23 #nullable enable
24
25 using System;
26 using System.Collections.Generic;
27 using System.ComponentModel;
28 using System.Data;
29 using System.Diagnostics.CodeAnalysis;
30 using System.Linq;
31 using System.Text;
32 using System.Threading.Tasks;
33 using System.Windows.Forms;
34
35 namespace OpenTween
36 {
37     public partial class MediaSelectorPanel : UserControl
38     {
39         public event EventHandler<EventArgs>? BeginSelecting;
40
41         public event EventHandler<EventArgs>? EndSelecting;
42
43         public event EventHandler<EventArgs>? FilePickDialogOpening;
44
45         public event EventHandler<EventArgs>? FilePickDialogClosed;
46
47         public event EventHandler<EventArgs>? SelectedServiceChanged;
48
49         [Browsable(false)]
50         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
51         public MediaSelector Model { get; } = new();
52
53         [Browsable(false)]
54         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
55         public OpenFileDialog? FilePickDialog { get; set; }
56
57         public MediaSelectorPanel()
58         {
59             this.InitializeComponent();
60
61             this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage;
62
63             this.MediaListView.LargeImageList = this.Model.ThumbnailList.ImageList;
64
65             var thumbnailWidth = 75 * this.DeviceDpi / 96;
66             this.Model.ThumbnailList.ImageList.ImageSize = new(thumbnailWidth, thumbnailWidth);
67
68             this.Model.PropertyChanged +=
69                 (s, e) => this.TryInvoke(() => this.Model_PropertyChanged(s, e));
70             this.Model.MediaItems.ListChanged +=
71                 (s, e) => this.TryInvoke(() => this.Model_MediaItems_ListChanged(s, e));
72
73             this.UpdateSelectedMedia();
74             this.UpdateAltTextPanelVisible();
75         }
76
77         /// <summary>
78         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
79         /// </summary>
80         public void BeginSelection()
81         {
82             this.BeginSelecting?.Invoke(this, EventArgs.Empty);
83             this.Enabled = true;
84             this.Visible = true;
85         }
86
87         /// <summary>
88         /// 選択処理を終了してコントロールを隠す。
89         /// </summary>
90         public void EndSelection()
91         {
92             this.EndSelecting?.Invoke(this, EventArgs.Empty);
93             this.Visible = false;
94             this.Enabled = false;
95             this.Model.ClearMediaItems();
96         }
97
98         /// <summary>
99         /// 選択された投稿先名と投稿する MediaItem を取得する。MediaItem は不要になったら呼び出し側にて破棄すること。
100         /// </summary>
101         public bool TryGetSelectedMedia([NotNullWhen(true)] out string? imageService, [NotNullWhen(true)] out IMediaItem[]? mediaItems)
102         {
103             var selectedServiceName = this.Model.SelectedMediaServiceName;
104
105             var error = this.Model.Validate(out var rejectedMedia);
106             if (error != MediaSelectorErrorType.None)
107             {
108                 var message = error switch
109                 {
110                     MediaSelectorErrorType.MediaItemNotSet
111                         => Properties.Resources.PostPictureWarn1,
112                     MediaSelectorErrorType.ServiceNotSelected
113                         => Properties.Resources.PostPictureWarn1,
114                     MediaSelectorErrorType.UnsupportedFileExtension
115                         => string.Format(
116                             Properties.Resources.PostPictureWarn3,
117                             selectedServiceName,
118                             this.MakeAvailableServiceText(rejectedMedia!),
119                             rejectedMedia!.Extension,
120                             rejectedMedia!.Name
121                         ),
122                     MediaSelectorErrorType.FileSizeExceeded
123                         => string.Format(
124                             Properties.Resources.PostPictureWarn5,
125                             selectedServiceName,
126                             this.MakeAvailableServiceText(rejectedMedia!),
127                             rejectedMedia!.Name
128                         ),
129                     _ => throw new NotImplementedException(),
130                 };
131
132                 MessageBox.Show(
133                     message,
134                     Properties.Resources.PostPictureWarn2,
135                     MessageBoxButtons.OK,
136                     MessageBoxIcon.Warning
137                 );
138
139                 imageService = null;
140                 mediaItems = null;
141                 return false;
142             }
143
144             imageService = selectedServiceName;
145             mediaItems = this.Model.DetachMediaItems();
146             return true;
147         }
148
149         private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
150         {
151             switch (e.PropertyName)
152             {
153                 case nameof(MediaSelector.MediaServices):
154                     this.UpdateImageServiceComboItems();
155                     break;
156                 case nameof(MediaSelector.SelectedMediaServiceName):
157                     this.UpdateImageServiceComboSelection();
158                     this.UpdateAltTextPanelVisible();
159                     this.SelectedServiceChanged?.Invoke(this, EventArgs.Empty);
160                     break;
161                 case nameof(MediaSelector.SelectedMediaItemId):
162                     this.UpdateSelectedMedia();
163                     break;
164                 default:
165                     break;
166             }
167         }
168
169         private void Model_MediaItems_ListChanged(object sender, ListChangedEventArgs e)
170         {
171             void AddMediaListViewItem(IMediaItem media, int index)
172                 => this.MediaListView.Items.Insert(index, media.Name, media.Id.ToString());
173
174             switch (e.ListChangedType)
175             {
176                 case ListChangedType.ItemAdded:
177                     var addedMedia = this.Model.MediaItems[e.NewIndex];
178                     AddMediaListViewItem(addedMedia, e.NewIndex);
179                     break;
180                 case ListChangedType.Reset:
181                     this.MediaListView.Items.Clear();
182                     foreach (var (media, index) in this.Model.MediaItems.WithIndex())
183                         AddMediaListViewItem(media, index);
184                     break;
185                 default:
186                     throw new NotImplementedException();
187             }
188         }
189
190         private void UpdateImageServiceComboItems()
191         {
192             using (ControlTransaction.Update(this.ImageServiceCombo))
193             {
194                 this.ImageServiceCombo.Items.Clear();
195
196                 // Add service names to combobox
197                 var serviceNames = this.Model.MediaServices.Select(x => x.Key).ToArray();
198                 this.ImageServiceCombo.Items.AddRange(serviceNames);
199
200                 this.UpdateImageServiceComboSelection();
201             }
202         }
203
204         private void UpdateImageServiceComboSelection()
205             => this.ImageServiceCombo.SelectedIndex = this.Model.SelectedMediaServiceIndex;
206
207         private void AddMediaButton_Click(object sender, EventArgs e)
208         {
209             var service = this.Model.SelectedMediaService;
210
211             if (this.FilePickDialog == null || service == null) return;
212             this.FilePickDialog.Filter = service.SupportedFormatsStrForDialog;
213             this.FilePickDialog.Title = Properties.Resources.PickPictureDialog1;
214             this.FilePickDialog.FileName = "";
215
216             this.FilePickDialogOpening?.Invoke(this, EventArgs.Empty);
217
218             try
219             {
220                 if (this.FilePickDialog.ShowDialog() == DialogResult.Cancel) return;
221             }
222             finally
223             {
224                 this.FilePickDialogClosed?.Invoke(this, EventArgs.Empty);
225             }
226
227             this.Model.AddMediaItemFromFilePath(this.FilePickDialog.FileNames);
228         }
229
230         private string MakeAvailableServiceText(IMediaItem media)
231         {
232             var ext = media.Extension;
233             var fileSize = media.Size;
234
235             var availableServiceNames = this.Model.GetAvailableServiceNames(ext, fileSize);
236             if (availableServiceNames.Length == 0)
237                 return Properties.Resources.PostPictureWarn6;
238
239             return string.Join(", ", availableServiceNames);
240         }
241
242         private void ImageCancelButton_Click(object sender, EventArgs e)
243             => this.EndSelection();
244
245         private void UpdateAltTextPanelVisible()
246             => this.AlternativeTextPanel.Visible = this.Model.CanUseAltText;
247
248         private void UpdateSelectedMedia()
249         {
250             using (ControlTransaction.Update(this))
251             {
252                 var selectedMedia = this.Model.SelectedMediaItem;
253                 if (selectedMedia == null)
254                 {
255                     this.AlternativeTextBox.Text = "";
256                     this.AlternativeTextPanel.Enabled = false;
257                     this.ImageSelectedPicture.ShowInitialImage();
258                 }
259                 else
260                 {
261                     this.AlternativeTextBox.Text = selectedMedia.AltText;
262                     this.AlternativeTextPanel.Enabled = true;
263                     this.ImageSelectedPicture.Image = selectedMedia.CreateImage();
264                 }
265             }
266         }
267
268         private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
269             => this.Model.SelectedMediaServiceName = this.ImageServiceCombo.Text;
270
271         private void MediaListView_SelectedIndexChanged(object sender, EventArgs e)
272         {
273             var indices = this.MediaListView.SelectedIndices;
274             if (indices.Count == 0)
275                 return;
276
277             this.Model.SelectedMediaItemIndex = indices[0];
278         }
279
280         private void AlternativeTextBox_Validated(object sender, EventArgs e)
281             => this.Model.SetSelectedMediaAltText(this.AlternativeTextBox.Text);
282
283         protected override void Dispose(bool disposing)
284         {
285             if (disposing)
286             {
287                 this.components?.Dispose();
288                 this.Model.Dispose();
289             }
290
291             base.Dispose(disposing);
292         }
293     }
294 }