OSDN Git Service

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