OSDN Git Service

#30840 不要になっていたTemplate:Documentation絡みの処理も除去,
[wptscs/wpts.git] / Wptscs / ConfigForm.cs
1 // ================================================================================================
2 // <summary>
3 //      Wikipedia翻訳支援ツール設定画面クラスソース</summary>
4 //
5 // <copyright file="ConfigForm.cs" company="honeplusのメモ帳">
6 //      Copyright (C) 2013 Honeplus. All rights reserved.</copyright>
7 // <author>
8 //      Honeplus</author>
9 // ================================================================================================
10
11 namespace Honememo.Wptscs
12 {
13     using System;
14     using System.Collections.Generic;
15     using System.ComponentModel;
16     using System.Data;
17     using System.Drawing;
18     using System.Linq;
19     using System.Reflection;
20     using System.Text;
21     using System.Threading;
22     using System.Windows.Forms;
23     using Honememo.Utilities;
24     using Honememo.Wptscs.Models;
25     using Honememo.Wptscs.Properties;
26     using Honememo.Wptscs.Utilities;
27     using Honememo.Wptscs.Websites;
28
29     /// <summary>
30     /// Wikipedia翻訳支援ツール設定画面のクラスです。
31     /// </summary>
32     public partial class ConfigForm : Form
33     {
34         #region private変数
35
36         /// <summary>
37         /// 現在設定中のアプリケーションの設定。
38         /// </summary>
39         /// <remarks>設定画面を閉じた後は再読み込みされるので、必要に応じて随時更新してよい。</remarks>
40         private Config config;
41
42         /// <summary>
43         /// <seealso cref="comboBoxLanguage"/>で選択していたアイテムのバックアップ。
44         /// </summary>
45         private string comboBoxLanguageSelectedText;
46
47         #endregion
48
49         #region コンストラクタ
50
51         /// <summary>
52         /// コンストラクタ。
53         /// </summary>
54         /// <param name="config">設定対象のConfig。</param>
55         /// <remarks>configは設定画面の操作により随時更新される。呼び出し元では再読み込みすること。</remarks>
56         public ConfigForm(Config config)
57         {
58             this.InitializeComponent();
59
60             // 設定対象のConfigを受け取る
61             this.config = Honememo.Utilities.Validate.NotNull(config, "config");
62         }
63
64         #endregion
65         
66         #region フォームの各イベントのメソッド
67
68         /// <summary>
69         /// フォームロード時の処理。
70         /// </summary>
71         /// <param name="sender">イベント発生オブジェクト。</param>
72         /// <param name="e">発生したイベント。</param>
73         private void ConfigForm_Load(object sender, EventArgs e)
74         {
75             try
76             {
77                 // 各タブの内容を初期化する
78
79                 // 記事の置き換えタブの初期化
80                 this.ImportTranslationDictionaryView(this.dataGridViewItems, this.config.ItemTables);
81
82                 // 見出しの置き換えタブの初期化
83                 this.ImportTranslationTableView(this.dataGridViewHeading, this.config.HeadingTable);
84
85                 // サーバー/言語タブの初期化
86                 foreach (Website site in this.config.Websites)
87                 {
88                     this.comboBoxLanguage.Items.Add(site.Language.Code);
89                 }
90
91                 // その他タブの初期化
92                 this.textBoxCacheExpire.Text = Settings.Default.CacheExpire.Days.ToString();
93                 this.textBoxUserAgent.Text = Settings.Default.UserAgent;
94                 this.textBoxReferer.Text = Settings.Default.Referer;
95                 this.textBoxMaxConnectRetries.Text = Settings.Default.MaxConnectRetries.ToString();
96                 this.textBoxConnectRetryTime.Text = Settings.Default.ConnectRetryTime.ToString();
97                 this.checkBoxIgnoreError.Checked = Settings.Default.IgnoreError;
98                 this.labelApplicationName.Text = FormUtils.ApplicationName();
99                 AssemblyCopyrightAttribute copyright = Attribute.GetCustomAttribute(
100                     Assembly.GetExecutingAssembly(),
101                     typeof(AssemblyCopyrightAttribute)) as AssemblyCopyrightAttribute;
102                 if (copyright != null)
103                 {
104                     this.labelCopyright.Text = copyright.Copyright;
105                 }
106             }
107             catch (Exception ex)
108             {
109                 // 通常この処理では例外は発生しないはず(Configに読めているので)。想定外のエラー用
110                 FormUtils.ErrorDialog(Resources.ErrorMessageDevelopmentError, ex.Message, ex.StackTrace);
111             }
112         }
113
114         /// <summary>
115         /// OKボタン押下時の処理。
116         /// </summary>
117         /// <param name="sender">イベント発生オブジェクト。</param>
118         /// <param name="e">発生したイベント。</param>
119         private void ButtonOk_Click(object sender, EventArgs e)
120         {
121             try
122             {
123                 // 各タブの内容を設定ファイルに保存する
124
125                 // 記事の置き換えタブの保存
126                 this.config.ItemTables = this.ExportTranslationDictionaryView(this.dataGridViewItems);
127
128                 // 見出しの置き換えタブの保存
129                 this.config.HeadingTable = this.ExportTranslationTableView(this.dataGridViewHeading);
130
131                 // サーバー/言語タブの保存
132                 // ※ このタブはコンボボックス変更のタイミングで保存されるので、そのメソッドを呼ぶ
133                 this.ComboBoxLanguuage_SelectedIndexChanged(sender, e);
134
135                 // その他タブの保存
136                 Settings.Default.CacheExpire = new TimeSpan(int.Parse(this.textBoxCacheExpire.Text), 0, 0, 0);
137                 Settings.Default.UserAgent = this.textBoxUserAgent.Text;
138                 Settings.Default.Referer = this.textBoxReferer.Text;
139                 Settings.Default.MaxConnectRetries = int.Parse(this.textBoxMaxConnectRetries.Text);
140                 Settings.Default.ConnectRetryTime = int.Parse(this.textBoxConnectRetryTime.Text);
141                 Settings.Default.IgnoreError = this.checkBoxIgnoreError.Checked;
142
143                 // 設定をファイルに保存
144                 Settings.Default.Save();
145                 try
146                 {
147                     this.config.Save();
148
149                     // 全部成功なら画面を閉じる
150                     // ※ エラーの場合、どうしても駄目ならキャンセルボタンで閉じてもらう
151                     this.DialogResult = DialogResult.OK;
152                 }
153                 catch (Exception ex)
154                 {
155                     // 異常時はエラーメッセージを表示
156                     System.Diagnostics.Debug.WriteLine(ex.ToString());
157                     FormUtils.ErrorDialog(Resources.ErrorMessageConfigSaveFailed, ex.Message);
158                 }
159             }
160             catch (Exception ex)
161             {
162                 // 通常ファイル保存以外では例外は発生しないはず。想定外のエラー用
163                 FormUtils.ErrorDialog(Resources.ErrorMessageDevelopmentError, ex.Message, ex.StackTrace);
164             }
165         }
166
167         #endregion
168
169         #region 記事の置き換えタブのイベントのメソッド
170
171         /// <summary>
172         /// 記事の置き換え対訳表への行追加時の処理。
173         /// </summary>
174         /// <param name="sender">イベント発生オブジェクト。</param>
175         /// <param name="e">発生したイベント。</param>
176         private void DataGridViewItems_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
177         {
178             for (int i = e.RowIndex - 1; i < this.dataGridViewItems.Rows.Count; i++)
179             {
180                 // プログラムから追加された場合は現在のインデックス、画面から追加した場合は+1したインデックスが来る
181                 if (i >= 0)
182                 {
183                     this.dataGridViewItems.Rows[i].Cells["ColumnArrow"].Value = Resources.RightArrow;
184                 }
185             }
186         }
187
188         /// <summary>
189         /// 記事の置き換え対訳表のセル編集時のバリデート処理。
190         /// </summary>
191         /// <param name="sender">イベント発生オブジェクト。</param>
192         /// <param name="e">発生したイベント。</param>
193         private void DataGridViewItems_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
194         {
195             // 取得日時列のみチェック
196             if (this.dataGridViewItems.Columns[e.ColumnIndex].Name != "ColumnTimestamp")
197             {
198                 return;
199             }
200
201             // 空または日付として認識可能な値の場合OK
202             string value = e.FormattedValue.ToString();
203             DateTime dummy;
204             if (string.IsNullOrWhiteSpace(value) || DateTime.TryParse(value, out dummy))
205             {
206                 return;
207             }
208
209             // 不許可値の場合、NGメッセージを表示
210             this.dataGridViewItems.Rows[e.RowIndex].ErrorText = Resources.WarningMessageUnformatedTimestamp;
211             e.Cancel = true;
212         }
213
214         /// <summary>
215         /// 記事の置き換え対訳表のセル編集時のバリデート成功時の処理。
216         /// </summary>
217         /// <param name="sender">イベント発生オブジェクト。</param>
218         /// <param name="e">発生したイベント。</param>
219         private void DataGridViewItems_CellValidated(object sender, DataGridViewCellEventArgs e)
220         {
221             // 取得日時列の場合、バリデートNGメッセージを消す
222             // ※ 他の列で消さないのは、エラーを出しているのがRowValidatingの場合もあるから
223             if (this.dataGridViewItems.Columns[e.ColumnIndex].Name == "ColumnTimestamp")
224             {
225                 this.dataGridViewItems.Rows[e.RowIndex].ErrorText = string.Empty;
226             }
227         }
228
229         /// <summary>
230         /// 記事の置き換え対訳表のセル変更時の処理。
231         /// </summary>
232         /// <param name="sender">イベント発生オブジェクト。</param>
233         /// <param name="e">発生したイベント。</param>
234         private void DataGridViewItems_CellValueChanged(object sender, DataGridViewCellEventArgs e)
235         {
236             // 取得日時列が空の場合、有効期限が無期限として背景色を変更
237             // ※ ただし全列が空(新規行など)の場合は無視
238             if (e.RowIndex >= 0)
239             {
240                 DataGridViewRow row = this.dataGridViewItems.Rows[e.RowIndex];
241                 if (string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnTimestamp"]))
242                     && !this.IsEmptyDataGridViewItemsRow(row))
243                 {
244                     // 背景色を変更
245                     row.DefaultCellStyle.BackColor = Color.Bisque;
246                 }
247                 else if (row.InheritedStyle.BackColor != this.dataGridViewItems.DefaultCellStyle.BackColor)
248                 {
249                     // 背景色を戻す
250                     // ※ DefaultCellStyleプロパティにアクセスしたタイミングでインスタンスが
251                     //    作成されてしまうため、InheritedStyleを調べて変更が必要な場合だけアクセス
252                     row.DefaultCellStyle.BackColor = this.dataGridViewItems.DefaultCellStyle.BackColor;
253                 }
254             }
255         }
256
257         /// <summary>
258         /// 記事の置き換え対訳表の行編集時のバリデート処理。
259         /// </summary>
260         /// <param name="sender">イベント発生オブジェクト。</param>
261         /// <param name="e">発生したイベント。</param>
262         private void DataGridViewItems_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
263         {
264             // 翻訳元、記事名、翻訳先が未入力の場合、バリデートNGメッセージを表示
265             // ※ ただし全列が空(新規行など)の場合は無視
266             DataGridViewRow row = this.dataGridViewItems.Rows[e.RowIndex];
267             if ((string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnFromCode"]))
268                 || string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnToCode"]))
269                 || string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnFromTitle"])))
270                 && !this.IsEmptyDataGridViewItemsRow(row))
271             {
272                 row.ErrorText = Resources.WarningMessageEmptyTranslationDictionary;
273                 e.Cancel = true;
274             }
275         }
276
277         /// <summary>
278         /// 記事の置き換え対訳表を使用する<see cref="DataGridView"/>の値設定を行う。
279         /// </summary>
280         /// <param name="view">対訳表を表示するビュー。</param>
281         /// <param name="dictionaries">対訳表データ。</param>
282         private void ImportTranslationDictionaryView(DataGridView view, IList<TranslationDictionary> dictionaries)
283         {
284             // 初期設定以外の場合も想定して最初にクリア
285             view.Rows.Clear();
286             foreach (TranslationDictionary dic in dictionaries)
287             {
288                 foreach (KeyValuePair<string, TranslationDictionary.Item> item in dic)
289                 {
290                     // 行を追加しその行を取得
291                     DataGridViewRow row = view.Rows[view.Rows.Add()];
292
293                     // 1行分の初期値を設定。右矢印は別途イベントで追加すること
294                     row.Cells["ColumnFromCode"].Value = dic.From;
295                     row.Cells["ColumnFromTitle"].Value = item.Key;
296                     row.Cells["ColumnAlias"].Value = item.Value.Alias;
297                     row.Cells["ColumnToCode"].Value = dic.To;
298                     row.Cells["ColumnToTitle"].Value = item.Value.Word;
299                     if (item.Value.Timestamp.HasValue)
300                     {
301                         row.Cells["ColumnTimestamp"].Value = item.Value.Timestamp.Value.ToLocalTime().ToString("G");
302                     }
303                 }
304             }
305
306             // 取得日時の降順でソート、空の列は先頭にする
307             this.dataGridViewItems.Sort(new TranslationDictionaryViewComparer());
308
309             // 列幅をデータ長に応じて自動調整
310             // ※ 常に行ってしまうと、読み込みに非常に時間がかかるため
311             view.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
312         }
313
314         /// <summary>
315         /// 記事の置き換え対訳表を使用する<see cref="DataGridView"/>からデータを抽出する。
316         /// </summary>
317         /// <param name="view">対訳表を表示するビュー。</param>
318         /// <returns>対訳表データ。</returns>
319         private IList<TranslationDictionary> ExportTranslationDictionaryView(DataGridView view)
320         {
321             IList<TranslationDictionary> dictionaries = new List<TranslationDictionary>();
322             foreach (DataGridViewRow row in view.Rows)
323             {
324                 // 画面での追加用の最終行が空で渡されてくるので無視
325                 if (this.IsEmptyDataGridViewItemsRow(row))
326                 {
327                     continue;
328                 }
329
330                 // その行で対象とする言語を探索、無ければ新規作成
331                 string from = FormUtils.ToString(row.Cells["ColumnFromCode"]);
332                 string to = FormUtils.ToString(row.Cells["ColumnToCode"]);
333                 TranslationDictionary dic
334                     = TranslationDictionary.GetDictionaryNeedCreate(dictionaries, from, to);
335
336                 // 値を格納
337                 TranslationDictionary.Item item = new TranslationDictionary.Item
338                 {
339                     Word = FormUtils.ToString(row.Cells["ColumnToTitle"]),
340                     Alias = FormUtils.ToString(row.Cells["ColumnAlias"])
341                 };
342
343                 string timestamp = FormUtils.ToString(row.Cells["ColumnTimestamp"]);
344                 if (!string.IsNullOrWhiteSpace(timestamp))
345                 {
346                     item.Timestamp = DateTime.Parse(timestamp);
347
348                     // UTCでもなくタイムゾーンでも無い場合、ローカル時刻として設定する
349                     if (item.Timestamp.Value.Kind == DateTimeKind.Unspecified)
350                     {
351                         item.Timestamp = DateTime.SpecifyKind(item.Timestamp.Value, DateTimeKind.Local);
352                     }
353                 }
354
355                 dic[FormUtils.ToString(row.Cells["ColumnFromTitle"])] = item;
356             }
357
358             return dictionaries;
359         }
360         
361         /// <summary>
362         /// 記事の置き換え対訳表の行が空かを判定する。
363         /// </summary>
364         /// <param name="row">対訳表の1行。</param>
365         /// <returns>空の場合<c>true</c>。</returns>
366         private bool IsEmptyDataGridViewItemsRow(DataGridViewRow row)
367         {
368             return string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnFromCode"]))
369                 && string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnFromTitle"]))
370                 && string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnAlias"]))
371                 && string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnToCode"]))
372                 && string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnToTitle"]))
373                 && string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnTimestamp"]));
374         }
375
376         #endregion
377
378         #region 見出しの置き換えタブのイベントのメソッド
379
380         /// <summary>
381         /// 見出しの置き換え対訳表を使用する<see cref="DataGridView"/>の値設定を行う。
382         /// </summary>
383         /// <param name="view">対訳表を表示するビュー。</param>
384         /// <param name="table">対訳表データ。</param>
385         private void ImportTranslationTableView(DataGridView view, TranslationTable table)
386         {
387             // 初期設定以外の場合も想定して最初にクリア
388             view.Columns.Clear();
389
390             // 言語コードを列、語句を行とする。登録されている全言語分の列を作成する
391             foreach (Website site in this.config.Websites)
392             {
393                 this.AddTranslationTableColumn(view.Columns, site.Language.Code, this.GetHeaderLanguage(site.Language));
394             }
395
396             // 各行にデータを取り込み
397             foreach (IDictionary<string, string[]> record in table)
398             {
399                 // 行を追加しその行を取得
400                 DataGridViewRow row = view.Rows[view.Rows.Add()];
401
402                 foreach (KeyValuePair<string, string[]> cell in record)
403                 {
404                     // 上で登録した列では足りなかった場合、その都度生成する
405                     if (!view.Columns.Contains(cell.Key))
406                     {
407                         this.AddTranslationTableColumn(view.Columns, cell.Key, cell.Key);
408                     }
409
410                     // 改行区切りで表示
411                     row.Cells[cell.Key].Value = string.Join("\n", cell.Value);
412                 }
413             }
414
415             // 可能であれば現在表示中の言語の列の昇順でソートする
416             // ※ 無ければenで試みる
417             string code = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
418             if (view.Columns.Contains(code))
419             {
420                 view.Sort(view.Columns[code], ListSortDirection.Ascending);
421             }
422             else if (view.Columns.Contains("en"))
423             {
424                 view.Sort(view.Columns["en"], ListSortDirection.Ascending);
425             }
426         }
427
428         /// <summary>
429         /// 見出しの置き換え対訳表を使用する<see cref="DataGridView"/>からデータを抽出する。
430         /// </summary>
431         /// <param name="view">対訳表を表示するビュー。</param>
432         /// <returns>対訳表データ。</returns>
433         private TranslationTable ExportTranslationTableView(DataGridView view)
434         {
435             TranslationTable table = new TranslationTable();
436             foreach (DataGridViewRow row in view.Rows)
437             {
438                 IDictionary<string, string[]> record = new SortedDictionary<string, string[]>();
439                 foreach (DataGridViewCell cell in row.Cells)
440                 {
441                     // 空のセルは格納しない、該当の組み合わせは消える
442                     string value = FormUtils.ToString(cell);
443                     if (!string.IsNullOrWhiteSpace(value))
444                     {
445                         // 改行区切りの配列で格納
446                         record[cell.OwningColumn.Name] = CollectionUtils.Trim(value.Split('\n'));
447                     }
448                 }
449
450                 // 1件もデータが無い行は丸々カットする
451                 if (record.Count > 0)
452                 {
453                     table.Add(record);
454                 }
455             }
456
457             return table;
458         }
459
460         /// <summary>
461         /// 指定された情報を元に見出しの置き換え対訳表の列を追加する。
462         /// </summary>
463         /// <param name="columns">列コレクション。</param>
464         /// <param name="columnName">列名。</param>
465         /// <param name="headerText">列見出し。</param>
466         private void AddTranslationTableColumn(DataGridViewColumnCollection columns, string columnName, string headerText)
467         {
468             columns.Add(columnName, headerText);
469         }
470
471         /// <summary>
472         /// 指定された言語用の表示名を返す。
473         /// </summary>
474         /// <param name="lang">表示言語コード。</param>
475         /// <returns>表示名、無ければ言語コード。</returns>
476         private string GetHeaderLanguage(Language lang)
477         {
478             Language.LanguageName name;
479             if (lang.Names.TryGetValue(
480                 Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName, out name))
481             {
482                 if (!string.IsNullOrEmpty(name.Name))
483                 {
484                     return string.Format(Resources.HeadingViewHeaderText, name.Name, lang.Code);
485                 }
486             }
487
488             return lang.Code;
489         }
490
491         #endregion
492
493         #region 言語/サーバータブのイベントのメソッド
494
495         /// <summary>
496         /// 言語コンボボックス変更時の処理。
497         /// </summary>
498         /// <param name="sender">イベント発生オブジェクト。</param>
499         /// <param name="e">発生したイベント。</param>
500         private void ComboBoxLanguuage_SelectedIndexChanged(object sender, EventArgs e)
501         {
502             try
503             {
504                 // 変更前の設定を保存
505                 if (!string.IsNullOrEmpty(this.comboBoxLanguageSelectedText))
506                 {
507                     // 設定が存在しなければ自動生成される
508                     this.SaveChangedValue(this.GetMediaWikiNeedCreate(this.config.Websites, this.comboBoxLanguageSelectedText));
509                 }
510
511                 // 変更後の値に応じて、画面表示を更新
512                 if (!string.IsNullOrEmpty(this.comboBoxLanguage.Text))
513                 {
514                     // 設定が存在しなければ基本的に自動生成されるのでそのまま使用
515                     this.LoadCurrentValue(this.GetMediaWikiNeedCreate(this.config.Websites, this.comboBoxLanguage.Text));
516
517                     // 各入力欄を有効に
518                     this.buttonLanguageRemove.Enabled = true;
519                     this.groupBoxServer.Enabled = true;
520                     this.groupBoxLanguage.Enabled = true;
521
522                     // 現在の選択値を更新
523                     this.comboBoxLanguageSelectedText = this.comboBoxLanguage.Text;
524                 }
525                 else
526                 {
527                     // 各入力欄を無効に
528                     this.buttonLanguageRemove.Enabled = false;
529                     this.groupBoxServer.Enabled = false;
530                     this.groupBoxLanguage.Enabled = false;
531
532                     // 現在の選択値を更新
533                     this.comboBoxLanguageSelectedText = string.Empty;
534                 }
535             }
536             catch (Exception ex)
537             {
538                 // 通常この処理では例外は発生しないはず。想定外のエラー用
539                 FormUtils.ErrorDialog(Resources.ErrorMessageDevelopmentError, ex.Message, ex.StackTrace);
540             }
541         }
542
543         /// <summary>
544         /// 言語の追加ボタン押下時の処理。
545         /// </summary>
546         /// <param name="sender">イベント発生オブジェクト。</param>
547         /// <param name="e">発生したイベント。</param>
548         private void ButtonLunguageAdd_Click(object sender, EventArgs e)
549         {
550             // 言語追加用ダイアログで言語コードを入力
551             using (AddLanguageDialog form = new AddLanguageDialog(this.config))
552             {
553                 if (form.ShowDialog() == DialogResult.OK)
554                 {
555                     // 値をコンボボックスと見出しの対訳表に追加、登録した値を選択状態に変更
556                     this.comboBoxLanguage.Items.Add(form.LanguageCode);
557                     this.dataGridViewHeading.Columns.Add(form.LanguageCode, form.LanguageCode);
558                     this.comboBoxLanguage.SelectedItem = form.LanguageCode;
559                 }
560             }
561         }
562
563         /// <summary>
564         /// 言語の削除ボタン押下時の処理。
565         /// </summary>
566         /// <param name="sender">イベント発生オブジェクト。</param>
567         /// <param name="e">発生したイベント。</param>
568         private void ButtonLanguageRemove_Click(object sender, EventArgs e)
569         {
570             // 表示されている言語を設定から削除する
571             for (int i = this.config.Websites.Count - 1; i >= 0; i--)
572             {
573                 if (this.config.Websites[i].Language.Code == this.comboBoxLanguage.Text)
574                 {
575                     // 万が一複数あれば全て削除
576                     this.config.Websites.RemoveAt(i);
577                 }
578             }
579
580             // 値を見出しの対訳表とコンボボックスからも削除し、表示を更新する
581             this.comboBoxLanguageSelectedText = null;
582             this.dataGridViewHeading.Columns.Remove(this.comboBoxLanguage.Text);
583             this.comboBoxLanguage.Items.Remove(this.comboBoxLanguage.Text);
584             this.ComboBoxLanguuage_SelectedIndexChanged(sender, e);
585         }
586
587         /// <summary>
588         /// 各名前空間のIDボックスバリデート処理。
589         /// </summary>
590         /// <param name="sender">イベント発生オブジェクト。</param>
591         /// <param name="e">発生したイベント。</param>
592         private void TextBoxNamespace_Validating(object sender, CancelEventArgs e)
593         {
594             // 空か数値のみ許可
595             TextBox box = (TextBox)sender;
596             box.Text = StringUtils.DefaultString(box.Text).Trim();
597             int value;
598             if (!string.IsNullOrEmpty(box.Text) && !int.TryParse(box.Text, out value))
599             {
600                 this.errorProvider.SetError(box, Resources.WarningMessageIgnoreNumericNamespace);
601                 e.Cancel = true;
602             }
603         }
604
605         /// <summary>
606         /// 括弧のスタイルボックスバリデート処理。
607         /// </summary>
608         /// <param name="sender">イベント発生オブジェクト。</param>
609         /// <param name="e">発生したイベント。</param>
610         private void TextBoxBracket_Validating(object sender, CancelEventArgs e)
611         {
612             // 空か$1が含まれる文字列のみ許可
613             TextBox box = (TextBox)sender;
614             if (!string.IsNullOrEmpty(box.Text) && !box.Text.Contains("$1"))
615             {
616                 this.errorProvider.SetError(box, Resources.WarningMessageUnformatedBracket);
617                 e.Cancel = true;
618             }
619         }
620
621         /// <summary>
622         /// 言語の設定表の行編集時のバリデート処理。
623         /// </summary>
624         /// <param name="sender">イベント発生オブジェクト。</param>
625         /// <param name="e">発生したイベント。</param>
626         private void DataGridViewLanguageName_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
627         {
628             DataGridViewRow row = this.dataGridViewLanguageName.Rows[e.RowIndex];
629
630             // 空行(新規行など)の場合無視
631             if (FormUtils.IsEmptyRow(row))
632             {
633                 return;
634             }
635
636             // 言語コードは必須、またトリムして小文字に変換
637             string code = FormUtils.ToString(row.Cells["ColumnCode"]).Trim().ToLower();
638             row.Cells["ColumnCode"].Value = code;
639             if (string.IsNullOrEmpty(code))
640             {
641                 row.ErrorText = Resources.WarningMessageEmptyCodeColumn;
642                 e.Cancel = true;
643                 return;
644             }
645
646             // 略称を設定する場合、呼称を必須とする
647             if (!string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnShortName"]))
648                 && string.IsNullOrWhiteSpace(FormUtils.ToString(row.Cells["ColumnName"])))
649             {
650                 row.ErrorText = Resources.WarningMessageShortNameColumnOnly;
651                 e.Cancel = true;
652             }
653         }
654
655         /// <summary>
656         /// 言語の設定表バリデート処理。
657         /// </summary>
658         /// <param name="sender">イベント発生オブジェクト。</param>
659         /// <param name="e">発生したイベント。</param>
660         private void DataGridViewLanguageName_Validating(object sender, CancelEventArgs e)
661         {
662             // 言語コードの重複チェック
663             IDictionary<string, int> codeMap = new Dictionary<string, int>();
664             for (int i = 0; i < this.dataGridViewLanguageName.RowCount - 1; i++)
665             {
666                 string code = FormUtils.ToString(this.dataGridViewLanguageName["ColumnCode", i]);
667                 int y;
668                 if (codeMap.TryGetValue(code, out y))
669                 {
670                     // 重複の場合、両方の行にエラーを設定
671                     this.dataGridViewLanguageName.Rows[i].ErrorText = Resources.WarningMessageDuplicateCodeColumn;
672                     this.dataGridViewLanguageName.Rows[y].ErrorText = Resources.WarningMessageDuplicateCodeColumn;
673                     e.Cancel = true;
674                 }
675                 else
676                 {
677                     // それ以外はマップに出現行とともに追加
678                     codeMap[code] = i;
679                 }
680             }
681         }
682
683         #region イベント実装支援用メソッド
684
685         /// <summary>
686         /// コレクションから指定された言語のMediaWikiを取得する。
687         /// 存在しない場合は空のインスタンスを生成、コレクションに追加して返す。
688         /// </summary>
689         /// <param name="collection">翻訳元言語。</param>
690         /// <param name="lang">言語コード。</param>
691         /// <returns>翻訳パターン。存在しない場合は新たに作成した翻訳パターンを返す。</returns>
692         private MediaWiki GetMediaWikiNeedCreate(ICollection<Website> collection, string lang)
693         {
694             // 設定が存在すれば取得した値を返す
695             foreach (Website s in collection)
696             {
697                 if (s.Language.Code == lang)
698                 {
699                     if (s is MediaWiki)
700                     {
701                         return s as MediaWiki;
702                     }
703
704                     // 万が一同じ言語コードで違う型の値があったら上書き
705                     collection.Remove(s);
706                     break;
707                 }
708             }
709
710             // 存在しないか上書きの場合、作成した翻訳パターンをコレクションに追加し、返す
711             MediaWiki site = new MediaWiki(new Language(lang));
712             collection.Add(site);
713             return site;
714         }
715
716         /// <summary>
717         /// 指定されたLanguage設定を画面表示/編集用に読み込む。
718         /// </summary>
719         /// <param name="lang">読込元Language設定。</param>
720         /// <remarks>一部パラメータには初期値が存在するが、格納時に対処するため全て読み込む。</remarks>
721         private void LoadCurrentValue(Language lang)
722         {
723             // 言語情報を読み込み
724             // ※ Bracketは初期値があるパラメータのため、必ず値が返る
725             this.textBoxBracket.Text = lang.Bracket;
726
727             // 呼称の情報を表に設定
728             this.dataGridViewLanguageName.Rows.Clear();
729             foreach (KeyValuePair<string, Language.LanguageName> name in lang.Names)
730             {
731                 int index = this.dataGridViewLanguageName.Rows.Add();
732                 this.dataGridViewLanguageName["ColumnCode", index].Value = name.Key;
733                 this.dataGridViewLanguageName["ColumnName", index].Value = name.Value.Name;
734                 this.dataGridViewLanguageName["ColumnShortName", index].Value = name.Value.ShortName;
735             }
736
737             // 言語コードの昇順でソート
738             this.dataGridViewLanguageName.Sort(this.dataGridViewLanguageName.Columns["ColumnCode"], ListSortDirection.Ascending);
739         }
740
741         /// <summary>
742         /// 指定されたWebsite設定を画面表示/編集用に読み込む。
743         /// </summary>
744         /// <param name="site">読込元Website設定。</param>
745         private void LoadCurrentValue(Website site)
746         {
747             // Languageクラス分の読み込みを行う
748             this.LoadCurrentValue(site.Language);
749
750             // サイト情報を読み込み
751             this.textBoxLocation.Text = site.Location;
752         }
753
754         /// <summary>
755         /// 指定されたMediaWiki設定を画面表示/編集用に読み込む。
756         /// </summary>
757         /// <param name="site">読込元MediaWiki設定。</param>
758         /// <remarks>一部パラメータには初期値が存在するが、格納時に対処するため全て読み込む。</remarks>
759         private void LoadCurrentValue(MediaWiki site)
760         {
761             // Websiteクラス分の読み込みを行う
762             this.LoadCurrentValue((Website)site);
763
764             // MediaWikiクラス分の読み込み
765             this.textBoxContentApi.Text = StringUtils.DefaultString(site.ContentApi);
766             this.textBoxMetaApi.Text = StringUtils.DefaultString(site.MetaApi);
767             this.textBoxInterlanguageApi.Text = StringUtils.DefaultString(site.InterlanguageApi);
768             this.textBoxTemplateNamespace.Text = site.TemplateNamespace.ToString();
769             this.textBoxCategoryNamespace.Text = site.CategoryNamespace.ToString();
770             this.textBoxFileNamespace.Text = site.FileNamespace.ToString();
771             this.textBoxLinkInterwikiFormat.Text = StringUtils.DefaultString(site.LinkInterwikiFormat);
772             this.textBoxLangFormat.Text = StringUtils.DefaultString(site.LangFormat);
773             this.checkBoxHasLanguagePage.Checked = site.HasLanguagePage;
774         }
775
776         /// <summary>
777         /// 指定されたLanguage設定に画面上で変更された値の格納を行う。
778         /// </summary>
779         /// <param name="lang">格納先Language設定。</param>
780         /// <remarks>一部パラメータには初期値が存在するため、変更がある場合のみ格納する。</remarks>
781         private void SaveChangedValue(Language lang)
782         {
783             // Bracketは初期値を持つパラメータのため、変更された場合のみ格納する。
784             // ※ この値は前後の空白に意味があるため、Trimしてはいけない
785             string str = StringUtils.DefaultString(this.textBoxBracket.Text);
786             if (str != lang.Bracket)
787             {
788                 lang.Bracket = str;
789             }
790
791             // 表から呼称の情報も保存
792             lang.Names.Clear();
793             for (int y = 0; y < this.dataGridViewLanguageName.RowCount - 1; y++)
794             {
795                 // 値が入ってないとかはガードしているはずだが、一応チェック
796                 string code = FormUtils.ToString(this.dataGridViewLanguageName["ColumnCode", y]).Trim();
797                 if (!string.IsNullOrEmpty(code))
798                 {
799                     Language.LanguageName name = new Language.LanguageName();
800                     name.Name = FormUtils.ToString(this.dataGridViewLanguageName["ColumnName", y]).Trim();
801                     name.ShortName = FormUtils.ToString(this.dataGridViewLanguageName["ColumnShortName", y]).Trim();
802                     lang.Names[code] = name;
803                 }
804             }
805         }
806
807         /// <summary>
808         /// 指定されたWebsite設定に画面上で変更された値の格納を行う。
809         /// </summary>
810         /// <param name="site">格納先Website設定。</param>
811         /// <remarks>Websiteについては特に特殊な処理は無いため全て上書きする。</remarks>
812         private void SaveChangedValue(Website site)
813         {
814             // Languageクラス分の設定を行う
815             this.SaveChangedValue(site.Language);
816
817             // サイト情報を格納
818             site.Location = StringUtils.DefaultString(this.textBoxLocation.Text).Trim();
819         }
820
821         /// <summary>
822         /// 指定されたMediaWiki設定に画面上で変更された値の格納を行う。
823         /// </summary>
824         /// <param name="site">格納先MediaWiki設定。</param>
825         /// <remarks>一部パラメータには初期値が存在するため、変更がある場合のみ格納する。</remarks>
826         private void SaveChangedValue(MediaWiki site)
827         {
828             // Websiteクラス分の設定を行う
829             this.SaveChangedValue((Website)site);
830
831             // 初期値を持つパラメータがあるため、全て変更された場合のみ格納する。
832             // ※ もうちょっと綺麗に書きたかったが、うまい手が思いつかなかったので力技
833             //    MediaWikiクラス側で行わないのは、場合によっては意図的に初期値と同じ値を設定すること
834             //    もありえるから(初期値が変わる可能性がある場合など)。
835             string str = StringUtils.DefaultString(this.textBoxContentApi.Text).Trim();
836             if (str != site.ContentApi)
837             {
838                 site.ContentApi = str;
839             }
840             
841             str = StringUtils.DefaultString(this.textBoxMetaApi.Text).Trim();
842             if (str != site.MetaApi)
843             {
844                 site.MetaApi = str;
845             }
846
847             str = StringUtils.DefaultString(this.textBoxInterlanguageApi.Text).Trim();
848             if (str != site.InterlanguageApi)
849             {
850                 site.InterlanguageApi = str;
851             }
852
853             str = StringUtils.DefaultString(this.textBoxLinkInterwikiFormat.Text).Trim();
854             if (str != site.LinkInterwikiFormat)
855             {
856                 site.LinkInterwikiFormat = str;
857             }
858
859             str = StringUtils.DefaultString(this.textBoxLangFormat.Text).Trim();
860             if (str != site.LangFormat)
861             {
862                 site.LangFormat = str;
863             }
864
865             site.HasLanguagePage = this.checkBoxHasLanguagePage.Checked;
866
867             // 以下、数値へのparseは事前にチェックしてあるので、ここではチェックしない
868             if (!string.IsNullOrWhiteSpace(this.textBoxTemplateNamespace.Text))
869             {
870                 int num = int.Parse(this.textBoxTemplateNamespace.Text);
871                 if (site.TemplateNamespace != num)
872                 {
873                     site.TemplateNamespace = num;
874                 }
875             }
876
877             if (!string.IsNullOrWhiteSpace(this.textBoxCategoryNamespace.Text))
878             {
879                 int num = int.Parse(this.textBoxCategoryNamespace.Text);
880                 if (site.CategoryNamespace != num)
881                 {
882                     site.CategoryNamespace = num;
883                 }
884             }
885
886             if (!string.IsNullOrWhiteSpace(this.textBoxFileNamespace.Text))
887             {
888                 int num = int.Parse(this.textBoxFileNamespace.Text);
889                 if (site.FileNamespace != num)
890                 {
891                     site.FileNamespace = num;
892                 }
893             }
894         }
895
896         #endregion
897
898         #endregion
899
900         #region その他タブのイベントのメソッド
901         
902         /// <summary>
903         /// キャッシュ有効期限ボックスバリデート処理。
904         /// </summary>
905         /// <param name="sender">イベント発生オブジェクト。</param>
906         /// <param name="e">発生したイベント。</param>
907         private void TextBoxCacheExpire_Validating(object sender, CancelEventArgs e)
908         {
909             // 値が0以上の数値かをチェック
910             this.TextBoxGreaterThanValidating((TextBox)sender, e, 0, Resources.WarningMessageIgnoreCacheExpire);
911         }
912
913         /// <summary>
914         /// リトライ回数ボックスバリデート処理。
915         /// </summary>
916         /// <param name="sender">イベント発生オブジェクト。</param>
917         /// <param name="e">発生したイベント。</param>
918         private void TextBoxMaxConnectRetries_Validating(object sender, CancelEventArgs e)
919         {
920             // 値が0以上の数値かをチェック
921             this.TextBoxGreaterThanValidating((TextBox)sender, e, 0, Resources.WarningMessageIgnoreMaxConnectRetries);
922         }
923
924         /// <summary>
925         /// ウェイト時間ボックスバリデート処理。
926         /// </summary>
927         /// <param name="sender">イベント発生オブジェクト。</param>
928         /// <param name="e">発生したイベント。</param>
929         private void TextBoxConnectRetryTime_Validating(object sender, CancelEventArgs e)
930         {
931             // 値が0以上の数値かをチェック
932             this.TextBoxGreaterThanValidating((TextBox)sender, e, 0, Resources.WarningMessageIgnoreConnectRetryTime);
933         }
934
935         /// <summary>
936         /// ウェブサイトURLクリック時の処理。
937         /// </summary>
938         /// <param name="sender">イベント発生オブジェクト。</param>
939         /// <param name="e">発生したイベント。</param>
940         private void LinkLabelWebsite_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
941         {
942             // リンクを開く
943             System.Diagnostics.Process.Start(((LinkLabel)sender).Text);
944         }
945
946         #region イベント実装支援用メソッド
947
948         /// <summary>
949         /// メッセージのみ差し替え可能なテキストボックス用の値がxx以上の数値か、のバリデート処理。
950         /// </summary>
951         /// <param name="box">イベント発生テキストボックス。</param>
952         /// <param name="e">発生したイベント。</param>
953         /// <param name="num">比較対象の数値。</param>
954         /// <param name="message">バリデートメッセージ。</param>
955         private void TextBoxGreaterThanValidating(TextBox box, CancelEventArgs e, int num, string message)
956         {
957             box.Text = StringUtils.DefaultString(box.Text).Trim();
958             int value;
959             if (!int.TryParse(box.Text, out value) || value < num)
960             {
961                 this.errorProvider.SetError(box, message);
962                 e.Cancel = true;
963             }
964         }
965
966         #endregion
967
968         #endregion
969
970         #region 共通のイベントメソッド
971
972         /// <summary>
973         /// 汎用のエラープロバイダ初期化処理。
974         /// </summary>
975         /// <param name="sender">イベント発生オブジェクト。</param>
976         /// <param name="e">発生したイベント。</param>
977         private void ResetErrorProvider_Validated(object sender, EventArgs e)
978         {
979             this.errorProvider.SetError((Control)sender, null);
980         }
981
982         /// <summary>
983         /// 汎用の行編集時のエラーテキスト初期化処理。
984         /// </summary>
985         /// <param name="sender">イベント発生オブジェクト。</param>
986         /// <param name="e">発生したイベント。</param>
987         private void ResetErrorText_RowValidated(object sender, DataGridViewCellEventArgs e)
988         {
989             ((DataGridView)sender).Rows[e.RowIndex].ErrorText = string.Empty;
990         }
991
992         /// <summary>
993         /// 汎用のテーブルエラーテキスト初期化処理。
994         /// </summary>
995         /// <param name="sender">イベント発生オブジェクト。</param>
996         /// <param name="e">発生したイベント。</param>
997         private void ResetErrorText_Validated(object sender, EventArgs e)
998         {
999             // 全行のエラーメッセージを解除
1000             foreach (DataGridViewRow row in ((DataGridView)sender).Rows)
1001             {
1002                 row.ErrorText = string.Empty;
1003             }
1004         }
1005
1006         #endregion
1007
1008         #region 内部クラス
1009
1010         /// <summary>
1011         /// 記事の置き換え対訳表の日付並び替え用クラスです。
1012         /// </summary>
1013         /// <remarks>
1014         /// 取得日時の降順でソート、空の列は先頭にします。
1015         /// 取得日時が同じ場合、先頭の列から順に昇順でソート。
1016         /// </remarks>
1017         public class TranslationDictionaryViewComparer : System.Collections.IComparer
1018         {
1019             /// <summary>
1020             /// 取得日時が同じ場合にソートに用いる列名。
1021             /// </summary>
1022             private static readonly string[] SortOrder = new string[] { "ColumnFromCode", "ColumnToCode", "ColumnFromTitle" };
1023
1024             /// <summary>
1025             /// 2行を比較し、一方が他方より小さいか、等しいか、大きいかを示す値を返します。
1026             /// </summary>
1027             /// <param name="x">比較する最初の行です。</param>
1028             /// <param name="y">比較する2番目の行。</param>
1029             /// <returns>0未満:xはyより小さい, 0:xとyは等しい, 0より大きい:xはyより大きい。</returns>
1030             public int Compare(object x, object y)
1031             {
1032                 DataGridViewRow xrow = (DataGridViewRow)x;
1033                 DataGridViewRow yrow = (DataGridViewRow)y;
1034
1035                 // 取得日時列の降順でソート、ただし空の列は先頭にする
1036                 int compare = StringUtils.CompareNullsLast(
1037                     FormUtils.ToString(xrow.Cells["ColumnTimestamp"]),
1038                     FormUtils.ToString(yrow.Cells["ColumnTimestamp"]));
1039                 if (compare != 0)
1040                 {
1041                     return compare * -1;
1042                 }
1043
1044                 // 取得日時列が同じ場合、残りの列の昇順でソート
1045                 foreach (string column in SortOrder)
1046                 {
1047                     compare = string.Compare(
1048                         FormUtils.ToString(xrow.Cells[column]),
1049                         FormUtils.ToString(yrow.Cells[column]));
1050                     if (compare != 0)
1051                     {
1052                         return compare;
1053                     }
1054                 }
1055
1056                 return 0;
1057             }
1058         }
1059
1060         #endregion
1061     }
1062 }