OSDN Git Service

26f75004f5b5d1d878506d937bd425d41341361e
[wptscs/wpts.git] / Wptscs / ConfigForm.cs
1 // ================================================================================================
2 // <summary>
3 //      Wikipedia翻訳支援ツール設定画面クラスソース</summary>
4 //
5 // <copyright file="ConfigForm.cs" company="honeplusのメモ帳">
6 //      Copyright (C) 2012 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.textBoxExportPath.Text = StringUtils.DefaultString(site.ExportPath);
766             this.textBoxMetaApi.Text = StringUtils.DefaultString(site.MetaApi);
767             this.textBoxTemplateNamespace.Text = site.TemplateNamespace.ToString();
768             this.textBoxCategoryNamespace.Text = site.CategoryNamespace.ToString();
769             this.textBoxFileNamespace.Text = site.FileNamespace.ToString();
770             this.textBoxRedirect.Text = StringUtils.DefaultString(site.Redirect);
771
772             // Template:Documentionは改行区切りのマルチテキストとして扱う
773             StringBuilder b = new StringBuilder();
774             foreach (string s in site.DocumentationTemplates)
775             {
776                 b.Append(s).Append(Environment.NewLine);
777             }
778
779             this.textBoxDocumentationTemplate.Text = b.ToString();
780             this.textBoxDocumentationTemplateDefaultPage.Text = StringUtils.DefaultString(site.DocumentationTemplateDefaultPage);
781             this.textBoxLinkInterwikiFormat.Text = StringUtils.DefaultString(site.LinkInterwikiFormat);
782             this.textBoxLangFormat.Text = StringUtils.DefaultString(site.LangFormat);
783             this.checkBoxHasLanguagePage.Checked = site.HasLanguagePage;
784         }
785
786         /// <summary>
787         /// 指定されたLanguage設定に画面上で変更された値の格納を行う。
788         /// </summary>
789         /// <param name="lang">格納先Language設定。</param>
790         /// <remarks>一部パラメータには初期値が存在するため、変更がある場合のみ格納する。</remarks>
791         private void SaveChangedValue(Language lang)
792         {
793             // Bracketは初期値を持つパラメータのため、変更された場合のみ格納する。
794             // ※ この値は前後の空白に意味があるため、Trimしてはいけない
795             string str = StringUtils.DefaultString(this.textBoxBracket.Text);
796             if (str != lang.Bracket)
797             {
798                 lang.Bracket = str;
799             }
800
801             // 表から呼称の情報も保存
802             lang.Names.Clear();
803             for (int y = 0; y < this.dataGridViewLanguageName.RowCount - 1; y++)
804             {
805                 // 値が入ってないとかはガードしているはずだが、一応チェック
806                 string code = FormUtils.ToString(this.dataGridViewLanguageName["ColumnCode", y]).Trim();
807                 if (!String.IsNullOrEmpty(code))
808                 {
809                     Language.LanguageName name = new Language.LanguageName();
810                     name.Name = FormUtils.ToString(this.dataGridViewLanguageName["ColumnName", y]).Trim();
811                     name.ShortName = FormUtils.ToString(this.dataGridViewLanguageName["ColumnShortName", y]).Trim();
812                     lang.Names[code] = name;
813                 }
814             }
815         }
816
817         /// <summary>
818         /// 指定されたWebsite設定に画面上で変更された値の格納を行う。
819         /// </summary>
820         /// <param name="site">格納先Website設定。</param>
821         /// <remarks>Websiteについては特に特殊な処理は無いため全て上書きする。</remarks>
822         private void SaveChangedValue(Website site)
823         {
824             // Languageクラス分の設定を行う
825             this.SaveChangedValue(site.Language);
826
827             // サイト情報を格納
828             site.Location = StringUtils.DefaultString(this.textBoxLocation.Text).Trim();
829         }
830
831         /// <summary>
832         /// 指定されたMediaWiki設定に画面上で変更された値の格納を行う。
833         /// </summary>
834         /// <param name="site">格納先MediaWiki設定。</param>
835         /// <remarks>一部パラメータには初期値が存在するため、変更がある場合のみ格納する。</remarks>
836         private void SaveChangedValue(MediaWiki site)
837         {
838             // Websiteクラス分の設定を行う
839             this.SaveChangedValue((Website)site);
840
841             // 初期値を持つパラメータがあるため、全て変更された場合のみ格納する。
842             // ※ もうちょっと綺麗に書きたかったが、うまい手が思いつかなかったので力技
843             //    MediaWikiクラス側で行わないのは、場合によっては意図的に初期値と同じ値を設定すること
844             //    もありえるから(初期値が変わる可能性がある場合など)。
845             string str = StringUtils.DefaultString(this.textBoxExportPath.Text).Trim();
846             if (str != site.ExportPath)
847             {
848                 site.ExportPath = str;
849             }
850             
851             str = StringUtils.DefaultString(this.textBoxMetaApi.Text).Trim();
852             if (str != site.MetaApi)
853             {
854                 site.MetaApi = str;
855             }
856
857             str = StringUtils.DefaultString(this.textBoxRedirect.Text).Trim();
858             if (str != site.Redirect)
859             {
860                 site.Redirect = str;
861             }
862
863             // Template:Documentionの設定は行ごとに格納
864             // ※ この値は初期値を持たないパラメータ
865             site.DocumentationTemplates.Clear();
866             foreach (string s in StringUtils.DefaultString(this.textBoxDocumentationTemplate.Text).Split('\n'))
867             {
868                 if (!String.IsNullOrWhiteSpace(s))
869                 {
870                     site.DocumentationTemplates.Add(s.Trim());
871                 }
872             }
873
874             str = StringUtils.DefaultString(this.textBoxDocumentationTemplateDefaultPage.Text).Trim();
875             if (str != site.DocumentationTemplateDefaultPage)
876             {
877                 site.DocumentationTemplateDefaultPage = str;
878             }
879
880             str = StringUtils.DefaultString(this.textBoxLinkInterwikiFormat.Text).Trim();
881             if (str != site.LinkInterwikiFormat)
882             {
883                 site.LinkInterwikiFormat = str;
884             }
885
886             str = StringUtils.DefaultString(this.textBoxLangFormat.Text).Trim();
887             if (str != site.LangFormat)
888             {
889                 site.LangFormat = str;
890             }
891
892             site.HasLanguagePage = this.checkBoxHasLanguagePage.Checked;
893
894             // 以下、数値へのparseは事前にチェックしてあるので、ここではチェックしない
895             if (!String.IsNullOrWhiteSpace(this.textBoxTemplateNamespace.Text))
896             {
897                 int num = int.Parse(this.textBoxTemplateNamespace.Text);
898                 if (site.TemplateNamespace != num)
899                 {
900                     site.TemplateNamespace = num;
901                 }
902             }
903
904             if (!String.IsNullOrWhiteSpace(this.textBoxCategoryNamespace.Text))
905             {
906                 int num = int.Parse(this.textBoxCategoryNamespace.Text);
907                 if (site.CategoryNamespace != num)
908                 {
909                     site.CategoryNamespace = num;
910                 }
911             }
912
913             if (!String.IsNullOrWhiteSpace(this.textBoxFileNamespace.Text))
914             {
915                 int num = int.Parse(this.textBoxFileNamespace.Text);
916                 if (site.FileNamespace != num)
917                 {
918                     site.FileNamespace = num;
919                 }
920             }
921         }
922
923         #endregion
924
925         #endregion
926
927         #region その他タブのイベントのメソッド
928         
929         /// <summary>
930         /// キャッシュ有効期限ボックスバリデート処理。
931         /// </summary>
932         /// <param name="sender">イベント発生オブジェクト。</param>
933         /// <param name="e">発生したイベント。</param>
934         private void TextBoxCacheExpire_Validating(object sender, CancelEventArgs e)
935         {
936             // 値が0以上の数値かをチェック
937             this.TextBoxGreaterThanValidating((TextBox)sender, e, 0, Resources.WarningMessageIgnoreCacheExpire);
938         }
939
940         /// <summary>
941         /// リトライ回数ボックスバリデート処理。
942         /// </summary>
943         /// <param name="sender">イベント発生オブジェクト。</param>
944         /// <param name="e">発生したイベント。</param>
945         private void TextBoxMaxConnectRetries_Validating(object sender, CancelEventArgs e)
946         {
947             // 値が0以上の数値かをチェック
948             this.TextBoxGreaterThanValidating((TextBox)sender, e, 0, Resources.WarningMessageIgnoreMaxConnectRetries);
949         }
950
951         /// <summary>
952         /// ウェイト時間ボックスバリデート処理。
953         /// </summary>
954         /// <param name="sender">イベント発生オブジェクト。</param>
955         /// <param name="e">発生したイベント。</param>
956         private void TextBoxConnectRetryTime_Validating(object sender, CancelEventArgs e)
957         {
958             // 値が0以上の数値かをチェック
959             this.TextBoxGreaterThanValidating((TextBox)sender, e, 0, Resources.WarningMessageIgnoreConnectRetryTime);
960         }
961
962         /// <summary>
963         /// ウェブサイトURLクリック時の処理。
964         /// </summary>
965         /// <param name="sender">イベント発生オブジェクト。</param>
966         /// <param name="e">発生したイベント。</param>
967         private void LinkLabelWebsite_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
968         {
969             // リンクを開く
970             System.Diagnostics.Process.Start(((LinkLabel)sender).Text);
971         }
972
973         #region イベント実装支援用メソッド
974
975         /// <summary>
976         /// メッセージのみ差し替え可能なテキストボックス用の値がxx以上の数値か、のバリデート処理。
977         /// </summary>
978         /// <param name="box">イベント発生テキストボックス。</param>
979         /// <param name="e">発生したイベント。</param>
980         /// <param name="num">比較対象の数値。</param>
981         /// <param name="message">バリデートメッセージ。</param>
982         private void TextBoxGreaterThanValidating(TextBox box, CancelEventArgs e, int num, string message)
983         {
984             box.Text = StringUtils.DefaultString(box.Text).Trim();
985             int value;
986             if (!int.TryParse(box.Text, out value) || value < num)
987             {
988                 this.errorProvider.SetError(box, message);
989                 e.Cancel = true;
990             }
991         }
992
993         #endregion
994
995         #endregion
996
997         #region 共通のイベントメソッド
998
999         /// <summary>
1000         /// 汎用のエラープロバイダ初期化処理。
1001         /// </summary>
1002         /// <param name="sender">イベント発生オブジェクト。</param>
1003         /// <param name="e">発生したイベント。</param>
1004         private void ResetErrorProvider_Validated(object sender, EventArgs e)
1005         {
1006             this.errorProvider.SetError((Control)sender, null);
1007         }
1008
1009         /// <summary>
1010         /// 汎用の行編集時のエラーテキスト初期化処理。
1011         /// </summary>
1012         /// <param name="sender">イベント発生オブジェクト。</param>
1013         /// <param name="e">発生したイベント。</param>
1014         private void ResetErrorText_RowValidated(object sender, DataGridViewCellEventArgs e)
1015         {
1016             ((DataGridView)sender).Rows[e.RowIndex].ErrorText = String.Empty;
1017         }
1018
1019         /// <summary>
1020         /// 汎用のテーブルエラーテキスト初期化処理。
1021         /// </summary>
1022         /// <param name="sender">イベント発生オブジェクト。</param>
1023         /// <param name="e">発生したイベント。</param>
1024         private void ResetErrorText_Validated(object sender, EventArgs e)
1025         {
1026             // 全行のエラーメッセージを解除
1027             foreach (DataGridViewRow row in ((DataGridView)sender).Rows)
1028             {
1029                 row.ErrorText = String.Empty;
1030             }
1031         }
1032
1033         #endregion
1034
1035         #region 内部クラス
1036
1037         /// <summary>
1038         /// 記事の置き換え対訳表の日付並び替え用クラスです。
1039         /// </summary>
1040         /// <remarks>
1041         /// 取得日時の降順でソート、空の列は先頭にします。
1042         /// 取得日時が同じ場合、先頭の列から順に昇順でソート。
1043         /// </remarks>
1044         public class TranslationDictionaryViewComparer : System.Collections.IComparer
1045         {
1046             /// <summary>
1047             /// 取得日時が同じ場合にソートに用いる列名。
1048             /// </summary>
1049             private static readonly string[] sortOrder = new string[] { "ColumnFromCode", "ColumnToCode", "ColumnFromTitle" };
1050
1051             /// <summary>
1052             /// 2行を比較し、一方が他方より小さいか、等しいか、大きいかを示す値を返します。
1053             /// </summary>
1054             /// <param name="x">比較する最初の行です。</param>
1055             /// <param name="y">比較する2番目の行。</param>
1056             /// <returns>0未満:xはyより小さい, 0:xとyは等しい, 0より大きい:xはyより大きい。</returns>
1057             public int Compare(object x, object y)
1058             {
1059                 DataGridViewRow xrow = (DataGridViewRow)x;
1060                 DataGridViewRow yrow = (DataGridViewRow)y;
1061
1062                 // 取得日時列の降順でソート、ただし空の列は先頭にする
1063                 int compare = StringUtils.CompareNullsLast(
1064                     FormUtils.ToString(xrow.Cells["ColumnTimestamp"]),
1065                     FormUtils.ToString(yrow.Cells["ColumnTimestamp"]));
1066                 if (compare != 0)
1067                 {
1068                     return compare * -1;
1069                 }
1070
1071                 // 取得日時列が同じ場合、残りの列の昇順でソート
1072                 foreach (string column in sortOrder)
1073                 {
1074                     compare = String.Compare(
1075                         FormUtils.ToString(xrow.Cells[column]),
1076                         FormUtils.ToString(yrow.Cells[column]));
1077                     if (compare != 0)
1078                     {
1079                         return compare;
1080                     }
1081                 }
1082
1083                 return 0;
1084             }
1085         }
1086
1087         #endregion
1088     }
1089 }