OSDN Git Service

c29c646bc33babfa6eb71bffe2a6f973df70a5d0
[charactermanaj/CharacterManaJ.git] / src / main / java / charactermanaj / ui / PartsManageDialog.java
1 package charactermanaj.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.Container;
7 import java.awt.GridBagConstraints;
8 import java.awt.GridBagLayout;
9 import java.awt.Insets;
10 import java.awt.Toolkit;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.KeyEvent;
13 import java.awt.event.WindowAdapter;
14 import java.awt.event.WindowEvent;
15 import java.net.URI;
16 import java.sql.Timestamp;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Map;
23 import java.util.Properties;
24 import java.util.concurrent.Semaphore;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
27
28 import javax.swing.AbstractAction;
29 import javax.swing.Action;
30 import javax.swing.ActionMap;
31 import javax.swing.BorderFactory;
32 import javax.swing.Box;
33 import javax.swing.InputMap;
34 import javax.swing.JButton;
35 import javax.swing.JComponent;
36 import javax.swing.JDialog;
37 import javax.swing.JFrame;
38 import javax.swing.JLabel;
39 import javax.swing.JOptionPane;
40 import javax.swing.JPanel;
41 import javax.swing.JPopupMenu;
42 import javax.swing.JRootPane;
43 import javax.swing.JScrollPane;
44 import javax.swing.JSeparator;
45 import javax.swing.JTable;
46 import javax.swing.JTextField;
47 import javax.swing.KeyStroke;
48 import javax.swing.UIManager;
49 import javax.swing.event.DocumentEvent;
50 import javax.swing.event.DocumentListener;
51 import javax.swing.event.ListSelectionEvent;
52 import javax.swing.event.ListSelectionListener;
53 import javax.swing.event.TableModelEvent;
54 import javax.swing.event.TableModelListener;
55 import javax.swing.table.TableCellRenderer;
56 import javax.swing.table.TableColumnModel;
57
58 import charactermanaj.Main;
59 import charactermanaj.model.AppConfig;
60 import charactermanaj.model.CharacterData;
61 import charactermanaj.model.PartsAuthorInfo;
62 import charactermanaj.model.PartsCategory;
63 import charactermanaj.model.PartsIdentifier;
64 import charactermanaj.model.PartsManageData;
65 import charactermanaj.model.PartsManageData.PartsKey;
66 import charactermanaj.model.PartsManageData.PartsVersionInfo;
67 import charactermanaj.model.PartsSpec;
68 import charactermanaj.model.io.PartsInfoXMLReader;
69 import charactermanaj.model.io.PartsInfoXMLWriter;
70 import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;
71 import charactermanaj.util.DesktopUtilities;
72 import charactermanaj.util.ErrorMessageHelper;
73 import charactermanaj.util.LocalizedResourcePropertyLoader;
74
75 public class PartsManageDialog extends JDialog {
76
77         private static final long serialVersionUID = 1L;
78         
79         protected static final String STRINGS_RESOURCE = "languages/partsmanagedialog";
80         
81         
82         private static final Logger logger = Logger.getLogger(PartsManageDialog.class.getName());
83
84         private CharacterData characterData;
85         
86         private PartsManageTableModel partsManageTableModel;
87         
88         private JTable partsManageTable;
89         
90         private JTextField txtHomepage;
91         
92         private JTextField txtAuthor;
93         
94         private boolean updated;
95
96         
97         public PartsManageDialog(JFrame parent, CharacterData characterData) {
98                 super(parent, true);
99                 
100                 if (characterData == null) {
101                         throw new IllegalArgumentException();
102                 }
103                 this.characterData = characterData;
104                 
105                 setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
106                 addWindowListener(new WindowAdapter() {
107                         @Override
108                         public void windowClosing(WindowEvent e) {
109                                 onClose();
110                         }
111                 });
112
113                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
114                                 .getLocalizedProperties(STRINGS_RESOURCE);
115
116                 setTitle(strings.getProperty("title"));
117
118                 Container contentPane = getContentPane();
119                 
120                 // パーツリストテーブル
121                 JPanel partsListPanel = new JPanel();
122                 partsListPanel.setBorder(BorderFactory.createCompoundBorder(
123                                 BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory
124                                                 .createTitledBorder(strings.getProperty("partslist"))));
125
126                 GridBagLayout partsListPanelLayout = new GridBagLayout();
127                 partsListPanel.setLayout(partsListPanelLayout);
128                 
129                 partsManageTableModel = new PartsManageTableModel();
130                 partsManageTable = new JTable(partsManageTableModel) {
131                         private static final long serialVersionUID = 1L;
132
133                         @Override
134                         public Component prepareRenderer(TableCellRenderer renderer,
135                                         int rowIdx, int columnIdx) {
136                                 PartsManageTableModel.Columns column = PartsManageTableModel.Columns
137                                                 .values()[columnIdx];
138                                 Component comp = super.prepareRenderer(renderer, rowIdx, columnIdx);
139                                 PartsManageTableRow row = partsManageTableModel.getRow(rowIdx);
140
141                                 Timestamp current = row.getTimestamp();
142                                 Timestamp lastModified = row.getLastModified();
143
144                                 boolean warnings = false;
145
146                                 if (current != null && !current.equals(lastModified)) {
147                                         // 現在のパーツの最終更新日と、パーツ管理情報の作成時のパーツの最終更新日が不一致の場合
148                                         warnings = true;
149                                 }
150
151                                 // 背景色、警告行は赤色に
152                                 if (warnings && column == PartsManageTableModel.Columns.LastModified) {
153                                         AppConfig appConfig = AppConfig.getInstance();
154                                         Color invalidBgColor = appConfig.getInvalidBgColor();
155                                         comp.setBackground(invalidBgColor);
156                                 } else {
157                                         if (isCellSelected(rowIdx, columnIdx)) {
158                                                 comp.setBackground(getSelectionBackground());
159                                         } else {
160                                                 comp.setBackground(getBackground());
161                                         }
162                                 }
163
164                                 return comp;
165                         }
166                 };
167                 partsManageTable.setShowGrid(true);
168                 partsManageTable.setGridColor(AppConfig.getInstance().getGridColor());
169                 partsManageTableModel.adjustColumnModel(partsManageTable.getColumnModel());
170                 partsManageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
171                 partsManageTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
172
173                 JScrollPane partsManageTableSP = new JScrollPane(partsManageTable);
174                 
175                 partsManageTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
176                         public void valueChanged(ListSelectionEvent e) {
177                                 if (!e.getValueIsAdjusting()) {
178                                         onChangeSelection();
179                                 }
180                         }
181                 });
182                 
183                 partsManageTableModel.addTableModelListener(new TableModelListener() {
184                         public void tableChanged(TableModelEvent e) {
185                                 onTableDataChange(e.getFirstRow(), e.getLastRow());
186                         }
187                 });
188                 
189                 GridBagConstraints gbc = new GridBagConstraints();
190                 gbc.gridx = 0;
191                 gbc.gridy = 0;
192                 gbc.gridheight = 1;
193                 gbc.gridwidth = 4;
194                 gbc.anchor = GridBagConstraints.EAST;
195                 gbc.fill = GridBagConstraints.BOTH;
196                 gbc.insets = new Insets(3, 3, 3, 3);
197                 gbc.ipadx = 0;
198                 gbc.ipady = 0;
199                 gbc.weightx = 1.;
200                 gbc.weighty = 1.;
201                 partsListPanel.add(partsManageTableSP, gbc);
202                 
203                 Action actSortByName = new AbstractAction(strings.getProperty("sortByName")) {
204                         private static final long serialVersionUID = 1L;
205                         public void actionPerformed(ActionEvent e) {
206                                 onSortByName();
207                         }
208                 };
209                 Action actSortByAuthor = new AbstractAction(strings.getProperty("sortByAuthor")) {
210                         private static final long serialVersionUID = 1L;
211                         public void actionPerformed(ActionEvent e) {
212                                 onSortByAuthor();
213                         }
214                 };
215                 Action actSortByTimestamp = new AbstractAction(strings.getProperty("sortByTimestamp")) {
216                         private static final long serialVersionUID = 1L;
217                         public void actionPerformed(ActionEvent e) {
218                                 onSortByTimestamp();
219                         }
220                 };
221                 
222                 gbc.gridx = 0;
223                 gbc.gridy = 1;
224                 gbc.gridheight = 1;
225                 gbc.gridwidth = 1;
226                 gbc.weightx = 0.;
227                 gbc.weighty = 0.;
228                 partsListPanel.add(new JButton(actSortByName), gbc);
229
230                 gbc.gridx = 1;
231                 gbc.gridy = 1;
232                 gbc.weightx = 0.;
233                 gbc.weighty = 0.;
234                 partsListPanel.add(new JButton(actSortByAuthor), gbc);
235
236                 gbc.gridx = 2;
237                 gbc.gridy = 1;
238                 gbc.weightx = 0.;
239                 gbc.weighty = 0.;
240                 partsListPanel.add(new JButton(actSortByTimestamp), gbc);
241
242                 gbc.gridx = 3;
243                 gbc.gridy = 1;
244                 gbc.weightx = 1.;
245                 gbc.weighty = 0.;
246                 partsListPanel.add(Box.createHorizontalGlue(), gbc);
247
248                 contentPane.add(partsListPanel, BorderLayout.CENTER);
249
250                 // テーブルのコンテキストメニュー
251                 final JPopupMenu popupMenu = new JPopupMenu();
252                 Action actApplyAllLastModified = new AbstractAction(strings.getProperty("applyAllLastModified")) {
253                         private static final long serialVersionUID = 1L;
254                         public void actionPerformed(ActionEvent e) {
255                                 onApplyAllLastModified();
256                         }
257                 };
258                 Action actApplyAllDownloadURL = new AbstractAction(strings.getProperty("applyAllDownloadURL")) {
259                         private static final long serialVersionUID = 1L;
260                         public void actionPerformed(ActionEvent e) {
261                                 onApplyAllDownloadURL();
262                         }
263                 };
264                 Action actApplyAllVersion = new AbstractAction(strings.getProperty("applyAllVersion")) {
265                         private static final long serialVersionUID = 1L;
266                         public void actionPerformed(ActionEvent e) {
267                                 onApplyAllVersion();
268                         }
269                 };
270                 popupMenu.add(actApplyAllLastModified);
271                 popupMenu.add(new JSeparator());
272                 popupMenu.add(actApplyAllVersion);
273                 popupMenu.add(actApplyAllDownloadURL);
274                 
275                 partsManageTable.setComponentPopupMenu(popupMenu);
276                 
277                 // 作者情報パネル
278                 JPanel authorPanel = new JPanel();
279                 authorPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory
280                                 .createEmptyBorder(5, 5, 5, 5), BorderFactory
281                                 .createTitledBorder(strings.getProperty("author.info"))));
282                 GridBagLayout authorPanelLayout = new GridBagLayout();
283                 authorPanel.setLayout(authorPanelLayout);
284                 
285                 gbc.gridx = 0;
286                 gbc.gridy = 0;
287                 gbc.gridheight = 1;
288                 gbc.gridwidth = 1;
289                 gbc.anchor = GridBagConstraints.EAST;
290                 gbc.fill = GridBagConstraints.BOTH;
291                 gbc.insets = new Insets(3, 3, 3, 3);
292                 gbc.ipadx = 0;
293                 gbc.ipady = 0;
294                 gbc.weightx = 0.;
295                 gbc.weighty = 0.;
296                 authorPanel.add(new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc);
297
298                 gbc.gridx = 1;
299                 gbc.gridy = 0;
300                 gbc.gridwidth = 2;
301                 gbc.weightx = 1.;
302                 txtAuthor = new JTextField();
303                 authorPanel.add(txtAuthor, gbc);
304
305                 gbc.gridx = 0;
306                 gbc.gridy = 1;
307                 gbc.gridwidth = 1;
308                 gbc.weightx = 0.;
309                 authorPanel.add(new JLabel(strings.getProperty("homepage"), JLabel.RIGHT), gbc);
310
311                 gbc.gridx = 1;
312                 gbc.gridy = 1;
313                 gbc.gridwidth = 1;
314                 gbc.weightx = 1.;
315                 txtHomepage = new JTextField();
316                 authorPanel.add(txtHomepage, gbc);
317
318                 gbc.gridx = 2;
319                 gbc.gridy = 1;
320                 gbc.gridwidth = 1;
321                 gbc.weightx = 0.;
322                 Action actBrowseHomepage = new AbstractAction(strings.getProperty("open")) {
323                         private static final long serialVersionUID = 1L;
324                         public void actionPerformed(ActionEvent e) {
325                                 onBrosweHomepage();
326                         }
327                 };
328                 authorPanel.add(new JButton(actBrowseHomepage), gbc);
329
330                 if (!DesktopUtilities.isSupported()) {
331                         actBrowseHomepage.setEnabled(false);
332                 }
333                 
334                 txtAuthor.getDocument().addDocumentListener(new DocumentListener() {
335                         public void removeUpdate(DocumentEvent e) {
336                                 onEditAuthor();
337                         }
338                         public void insertUpdate(DocumentEvent e) {
339                                 onEditAuthor();
340                         }
341                         public void changedUpdate(DocumentEvent e) {
342                                 onEditAuthor();
343                         }
344                 });
345                 txtHomepage.getDocument().addDocumentListener(new DocumentListener() {
346                         public void removeUpdate(DocumentEvent e) {
347                                 onEditHomepage();
348                         }
349                         public void insertUpdate(DocumentEvent e) {
350                                 onEditHomepage();
351                         }
352                         public void changedUpdate(DocumentEvent e) {
353                                 onEditHomepage();
354                         }
355                 });
356                 
357                 
358                 // ボタンパネル
359                 JPanel btnPanel = new JPanel();
360                 btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 45));
361                 GridBagLayout btnPanelLayout = new GridBagLayout();
362                 btnPanel.setLayout(btnPanelLayout);
363                 
364                 Action actClose = new AbstractAction(strings.getProperty("cancel")) {
365                         private static final long serialVersionUID = 1L;
366                         public void actionPerformed(ActionEvent e) {
367                                 onClose();
368                         }
369                 };
370                 Action actOK = new AbstractAction(strings.getProperty("update")) {
371                         private static final long serialVersionUID = 1L;
372                         public void actionPerformed(ActionEvent e) {
373                                 onOK();
374                         }
375                 };
376                 
377                 gbc.gridx = 0;
378                 gbc.gridy = 0;
379                 gbc.weightx = 1.;
380                 btnPanel.add(Box.createHorizontalGlue(), gbc);
381                 
382                 gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;
383                 gbc.gridy = 0;
384                 gbc.weightx = 0.;
385                 btnPanel.add(new JButton(actOK), gbc);
386
387                 gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2;
388                 gbc.gridy = 0;
389                 gbc.weightx = 0.;
390                 btnPanel.add(new JButton(actClose), gbc);
391                 
392                 // ダイアログ下部
393                 JPanel southPanel = new JPanel(new BorderLayout());
394                 southPanel.add(authorPanel, BorderLayout.NORTH);
395                 southPanel.add(btnPanel, BorderLayout.SOUTH);
396
397                 contentPane.add(southPanel, BorderLayout.SOUTH);
398                 
399                 // キーボード
400                 
401                 Toolkit tk = Toolkit.getDefaultToolkit();
402                 JRootPane rootPane = getRootPane();
403                 InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
404                 ActionMap am = rootPane.getActionMap();
405                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closePartsManageDialog");
406                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closePartsManageDialog");
407                 am.put("closePartsManageDialog", actClose);
408
409                 // モデル構築
410                 partsManageTableModel.initModel(characterData);
411                 
412                 // ウィンドウ配置
413                 setSize(500, 400);
414                 setLocationRelativeTo(parent);
415         }
416
417         private Semaphore authorEditSemaphore = new Semaphore(1);
418         
419         protected void onChangeSelection() {
420                 try {
421                         authorEditSemaphore.acquire();
422                         try {
423                                 int [] selRows = partsManageTable.getSelectedRows();
424                                 HashSet<String> authors = new HashSet<String>();
425                                 for (int selRow : selRows) {
426                                         PartsManageTableRow row = partsManageTableModel.getRow(selRow);
427                                         authors.add(row.getAuthor() == null ? "" : row.getAuthor());
428                                 }
429                                 if (authors.size() > 1) {
430                                         AppConfig appConfig = AppConfig.getInstance();
431                                         txtAuthor.setBackground(appConfig.getAuthorEditConflictBgColor());
432                                         txtHomepage.setBackground(appConfig.getAuthorEditConflictBgColor());
433                                 } else {
434                                         Color bgColor = UIManager.getColor("TextField.background");
435                                         if (bgColor == null) {
436                                                 bgColor = Color.white;
437                                         }
438                                         txtAuthor.setBackground(bgColor);
439                                         txtHomepage.setBackground(bgColor);
440                                 }
441                                 if (authors.isEmpty()) {
442                                         // 選択されているauthorがない場合は全部編集不可
443                                         txtAuthor.setEditable(false);
444                                         txtAuthor.setText("");
445                                         txtHomepage.setEditable(false);
446                                         txtHomepage.setText("");
447                                 } else {
448                                         // 選択されているAuthorが1つ以上あればAuthorは編集可
449                                         txtAuthor.setEditable(true);
450                                         txtHomepage.setEditable(true);
451                                         if (authors.size() == 1) {
452                                                 // 選択されているAuthorが一個であれば、それを表示
453                                                 String author = authors.iterator().next();
454                                                 String homepage = partsManageTableModel.getHomepage(author);
455                                                 txtAuthor.setText(author);
456                                                 txtHomepage.setText(homepage);
457                                         } else {
458                                                 // 選択されているAuthorが二個以上あれば編集可能だがテキストには表示しない.
459                                                 txtAuthor.setText("");
460                                                 txtHomepage.setText("");
461                                         }
462                                 }
463                         } finally {
464                                 authorEditSemaphore.release();
465                         }
466
467                 } catch (InterruptedException ex) {
468                         ErrorMessageHelper.showErrorDialog(this, ex);
469
470                 } catch (RuntimeException ex) {
471                         ErrorMessageHelper.showErrorDialog(this, ex);
472                 }
473         }
474         
475         protected void onTableDataChange(int firstRow, int lastRow) {
476                 onChangeSelection();
477         }
478         
479         protected void onApplyAllLastModified() {
480                 int[] selRows = partsManageTable.getSelectedRows();
481                 if (selRows.length == 0) {
482                         Toolkit tk = Toolkit.getDefaultToolkit();
483                         tk.beep();
484                         return;
485                 }
486                 Arrays.sort(selRows);
487
488                 for (int selRow : selRows) {
489                         PartsManageTableRow row = partsManageTableModel.getRow(selRow);
490                         Timestamp dt = row.getTimestamp();
491                         row.setLastModified(dt);
492                 }
493                 partsManageTableModel.fireTableRowsUpdated(selRows[0],
494                                 selRows[selRows.length - 1]);
495         }
496
497         protected void onApplyAllDownloadURL() {
498                 int[] selRows = partsManageTable.getSelectedRows();
499                 if (selRows.length == 0) {
500                         Toolkit tk = Toolkit.getDefaultToolkit();
501                         tk.beep();
502                         return;
503                 }
504                 Arrays.sort(selRows);
505
506                 HashSet<String> authors = new HashSet<String>();
507                 for (int selRow : selRows) {
508                         PartsManageTableRow row = partsManageTableModel.getRow(selRow);
509                         authors.add(row.getAuthor() == null ? "" : row.getAuthor());
510                 }
511
512                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
513                                 .getLocalizedProperties(STRINGS_RESOURCE);
514
515                 if (authors.size() > 1) {
516                         if (JOptionPane.showConfirmDialog(this,
517                                         strings.getProperty("confirm.authorConflict"),
518                                         strings.getProperty("confirm"),
519                                         JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) {
520                                 return;
521                         }
522                 }
523                 
524                 PartsManageTableRow firstRow = partsManageTableModel.getRow(selRows[0]);
525                 String downloadURL = firstRow.getDownloadURL();
526                 if (downloadURL == null) {
527                         downloadURL = "";
528                 }
529                 String downloadURL_new = JOptionPane.showInputDialog(this, strings.getProperty("input.downloadURL"), downloadURL);
530                 if (downloadURL_new == null || downloadURL.equals(downloadURL_new)) {
531                         // キャンセルされたか、内容に変化ない場合は何もしない
532                         return;
533                 }
534                 
535                 for (int selRow : selRows) {
536                         PartsManageTableRow row = partsManageTableModel.getRow(selRow);
537                         row.setDownloadURL(downloadURL_new);
538
539                         Timestamp dt = row.getTimestamp();
540                         row.setLastModified(dt);
541                 }
542                 partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]);
543         }
544         
545         protected void onApplyAllVersion() {
546                 Toolkit tk = Toolkit.getDefaultToolkit();
547                 int[] selRows = partsManageTable.getSelectedRows();
548                 if (selRows.length == 0) {
549                         tk.beep();
550                         return;
551                 }
552                 Arrays.sort(selRows);
553
554                 HashSet<String> authors = new HashSet<String>();
555                 for (int selRow : selRows) {
556                         PartsManageTableRow row = partsManageTableModel.getRow(selRow);
557                         authors.add(row.getAuthor() == null ? "" : row.getAuthor());
558                 }
559
560                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
561                                 .getLocalizedProperties(STRINGS_RESOURCE);
562
563                 if (authors.size() > 1) {
564                         if (JOptionPane.showConfirmDialog(this,
565                                         strings.getProperty("confirm.authorConflict"),
566                                         strings.getProperty("confirm"),
567                                         JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) {
568                                 return;
569                         }
570                 }
571
572                 PartsManageTableRow firstRow = partsManageTableModel.getRow(selRows[0]);
573                 double version = firstRow.getVersion();
574                 String strVersion = (version < 0) ? "" : Double.toString(version);
575                 String strVersion_new = JOptionPane.showInputDialog(this,
576                                 strings.getProperty("input.version"), strVersion);
577                 if (strVersion_new == null || strVersion.equals(strVersion_new)) {
578                         // キャンセルされたか、内容に変化ない場合は何もしない
579                         return;
580                 }
581                 double version_new;
582                 try {
583                         version_new = Double.parseDouble(strVersion_new);
584                 } catch (Exception ex) {
585                         // 数値として不正であれば何もしない.
586                         tk.beep();
587                         return;
588                 }
589                 
590                 for (int selRow : selRows) {
591                         PartsManageTableRow row = partsManageTableModel.getRow(selRow);
592                         row.setVersion(version_new);
593
594                         Timestamp dt = row.getTimestamp();
595                         row.setLastModified(dt);
596                 }
597                 partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]);
598         }
599         
600         protected void onEditHomepage() {
601                 try {
602                         if (!authorEditSemaphore.tryAcquire()) {
603                                 return;
604                         }
605                         try {
606                                 String author = txtAuthor.getText();
607                                 String homepage = txtHomepage.getText();
608                                 partsManageTableModel.setHomepage(author, homepage);
609                         } finally {
610                                 authorEditSemaphore.release();
611                         }
612                 } catch (Exception ex) {
613                         ErrorMessageHelper.showErrorDialog(this, ex);
614                 }
615         }
616
617         protected void onEditAuthor() {
618                 try {
619                         if (!authorEditSemaphore.tryAcquire()) {
620                                 return;
621                         }
622                         try {
623                                 String author = txtAuthor.getText();
624                                 int[] selRows = partsManageTable.getSelectedRows();
625                                 int firstRow = -1;
626                                 int lastRow = Integer.MAX_VALUE;
627                                 for (int selRow : selRows) {
628                                         PartsManageTableRow row = partsManageTableModel.getRow(selRow);
629                                         String oldValue = row.getAuthor();
630                                         if (oldValue == null || !oldValue.equals(author)) {
631                                                 row.setAuthor(author);
632
633                                                 Timestamp dt = row.getTimestamp();
634                                                 row.setLastModified(dt);
635
636                                                 firstRow = Math.max(firstRow, selRow);
637                                                 lastRow = Math.min(lastRow, selRow);
638                                         }
639                                 }
640                                 
641                                 String homepage = partsManageTableModel.getHomepage(author);
642                                 if (homepage == null) {
643                                         homepage = "";
644                                 }
645                                 txtHomepage.setText(homepage);
646                                 
647                                 if (firstRow >= 0) {
648                                         partsManageTable.repaint();
649                                 }
650                         } finally {
651                                 authorEditSemaphore.release();                          
652                         }
653                 } catch (Exception ex) {
654                         ErrorMessageHelper.showErrorDialog(this, ex);
655                 }
656         }
657         
658         protected void onClose() {
659                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
660                                 .getLocalizedProperties(STRINGS_RESOURCE);
661                 if (JOptionPane.showConfirmDialog(this,
662                                 strings.getProperty("confirm.cancel"),
663                                 strings.getProperty("confirm"),
664                                 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
665                         return;
666                 }
667                 updated = false;
668                 dispose();
669         }
670         
671         protected void onBrosweHomepage() {
672                 Toolkit tk = Toolkit.getDefaultToolkit();
673                 String homepage = txtHomepage.getText();
674                 if (homepage == null || homepage.trim().length() == 0) {
675                         tk.beep();
676                         return;
677                 }
678                 try {
679                         URI uri = new URI(homepage);
680                         DesktopUtilities.browse(uri);
681
682                 } catch (Exception ex) {
683                         tk.beep();
684                         logger.log(Level.INFO, "browse failed. : " + homepage, ex);
685                 }
686         }
687         
688         protected void onSortByAuthor() {
689                 partsManageTableModel.sortByAuthor();
690         }
691         
692         protected void onSortByName() {
693                 partsManageTableModel.sortByName();
694         }
695         
696         protected void onSortByTimestamp() {
697                 partsManageTableModel.sortByTimestamp();
698         }
699         
700         protected void onOK() {
701                 if (partsManageTable.isEditing()) {
702                         Toolkit tk = Toolkit.getDefaultToolkit();
703                         tk.beep();
704                         return;
705                 }
706                 
707                 int mx = partsManageTableModel.getRowCount();
708
709                 // 作者ごとのホームページ情報の取得
710                 // (同一作者につきホームページは一つ)
711                 HashMap<String, PartsAuthorInfo> authorInfoMap = new HashMap<String, PartsAuthorInfo>();
712                 for (int idx = 0; idx < mx; idx++) {
713                         PartsManageTableRow row = partsManageTableModel.getRow(idx);
714                         String author = row.getAuthor();
715                         String homepage = row.getHomepage();
716                         if (author != null && author.trim().length() > 0) {
717                                 PartsAuthorInfo authorInfo = authorInfoMap.get(author.trim());
718                                 if (authorInfo == null) {
719                                         authorInfo = new PartsAuthorInfo();
720                                         authorInfo.setAuthor(author.trim());
721                                         authorInfoMap.put(authorInfo.getAuthor(), authorInfo);
722                                 }
723                                 authorInfo.setHomePage(homepage);
724                         }
725                 }
726
727                 PartsManageData partsManageData = new PartsManageData();
728                 
729                 // パーツごとの作者とバージョン、ダウンロード先の取得
730                 for (int idx = 0; idx < mx; idx++) {
731                         PartsManageTableRow row = partsManageTableModel.getRow(idx);
732                         
733                         String author = row.getAuthor();
734                         PartsAuthorInfo partsAuthorInfo = null;
735                         if (author != null && author.trim().length() > 0) {
736                                 partsAuthorInfo = authorInfoMap.get(author.trim());
737                         }
738                         
739                         double version = row.getVersion();
740                         String downloadURL = row.getDownloadURL();
741                         String localizedName = row.getLocalizedName();
742                         Timestamp lastModified = row.getLastModified();
743                         
744                         PartsManageData.PartsVersionInfo versionInfo = new PartsManageData.PartsVersionInfo();
745                         versionInfo.setVersion(version);
746                         versionInfo.setDownloadURL(downloadURL);
747                         versionInfo.setLastModified(lastModified);
748
749                         PartsIdentifier partsIdentifier = row.getPartsIdentifier();
750                         
751                         PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsIdentifier);
752                         partsManageData.putPartsInfo(partsKey, localizedName,
753                                         partsAuthorInfo, versionInfo);
754                 }
755                 
756                 // パーツ管理情報を書き込む.
757                 PartsInfoXMLWriter xmlWriter = new PartsInfoXMLWriter();
758                 try {
759                         URI docBase = characterData.getDocBase();
760                         xmlWriter.savePartsManageData(docBase, partsManageData);
761
762                 } catch (Exception ex) {
763                         ErrorMessageHelper.showErrorDialog(this, ex);
764                         return;
765                 }
766                 
767                 updated = true;
768                 dispose();
769         }
770
771         /**
772          * パーツ管理情報が更新されたか?
773          * 
774          * @return 更新された場合はtrue、そうでなければfalse
775          */
776         public boolean isUpdated() {
777                 return updated;
778         }
779 }
780
781 class PartsManageTableRow {
782         
783         private PartsIdentifier partsIdentifier;
784         
785         private Timestamp timestamp;
786         
787         private String localizedName;
788         
789         private String author;
790         
791         private String homepage;
792         
793         private String downloadURL;
794         
795         private double version;
796         
797         private Timestamp lastModified;
798
799         public PartsManageTableRow(PartsIdentifier partsIdentifier,
800                         PartsSpec partsSpec, Timestamp lastModified) {
801                 if (partsIdentifier == null || partsSpec == null) {
802                         throw new IllegalArgumentException();
803                 }
804                 this.partsIdentifier = partsIdentifier;
805                 this.localizedName = partsIdentifier.getLocalizedPartsName();
806
807                 this.timestamp = new Timestamp(partsSpec.getPartsFiles().lastModified());
808                 
809                 this.lastModified = lastModified;
810                 
811                 PartsAuthorInfo autherInfo = partsSpec.getAuthorInfo();
812                 if (autherInfo != null) {
813                         author = autherInfo.getAuthor();
814                         homepage = autherInfo.getHomePage();
815                 }
816                 if (author == null) {
817                         author = "";
818                 }
819                 if (homepage == null) {
820                         homepage = "";
821                 }
822                 downloadURL = partsSpec.getDownloadURL();
823                 version = partsSpec.getVersion();
824         }
825         
826         public PartsIdentifier getPartsIdentifier() {
827                 return partsIdentifier;
828         }
829         
830         public Timestamp getTimestamp() {
831                 return timestamp;
832         }
833
834         public String getLocalizedName() {
835                 return localizedName;
836         }
837         
838         public void setLocalizedName(String localizedName) {
839                 if (localizedName == null || localizedName.trim().length() == 0) {
840                         throw new IllegalArgumentException();
841                 }
842                 this.localizedName = localizedName;
843         }
844         
845         public String getAuthor() {
846                 return author;
847         }
848         
849         public String getDownloadURL() {
850                 return downloadURL;
851         }
852         
853         public double getVersion() {
854                 return version;
855         }
856         
857         public void setAuthor(String author) {
858                 this.author = author;
859         }
860         
861         public void setDownloadURL(String downloadURL) {
862                 this.downloadURL = downloadURL;
863         }
864         
865         public void setVersion(double version) {
866                 this.version = version;
867         }
868         
869         public String getHomepage() {
870                 return homepage;
871         }
872         
873         public void setHomepage(String homepage) {
874                 this.homepage = homepage;
875         }
876         
877         public Timestamp getLastModified() {
878                 return lastModified;
879         }
880
881         public void setLastModified(Timestamp lastModified) {
882                 this.lastModified = lastModified;
883         }
884 }
885
886 class PartsManageTableModel extends AbstractTableModelWithComboBoxModel<PartsManageTableRow> {
887
888         private static final long serialVersionUID = 1L;
889
890         private static final Logger logger = Logger
891                         .getLogger(PartsManageTableModel.class.getName());
892
893         private static Properties strings = LocalizedResourcePropertyLoader
894                         .getCachedInstance().getLocalizedProperties(
895                                         PartsManageDialog.STRINGS_RESOURCE);
896
897         /**
898          * カラムの定義
899          */
900         public enum Columns {
901                 PartsID("column.partsid", false, String.class) {
902                         @Override
903                         public Object getValue(PartsManageTableRow row) {
904                                 return row.getPartsIdentifier().getPartsName();
905                         }
906                 },
907                 LastModified("column.lastmodified", false, String.class) {
908                         @Override
909                         public Object getValue(PartsManageTableRow row) {
910                                 return row.getTimestamp().toString();
911                         }
912                 },
913                 Category("column.category", false, String.class) {
914                         @Override
915                         public Object getValue(PartsManageTableRow row) {
916                                 return row.getPartsIdentifier().getPartsCategory()
917                                                 .getLocalizedCategoryName();
918                         }
919                 },
920                 PartsName("column.partsname", true, String.class) {
921                         @Override
922                         public Object getValue(PartsManageTableRow row) {
923                                 return row.getLocalizedName();
924                         }
925                         @Override
926                         public void setValue(PartsManageTableRow row, Object value) {
927                                 String localizedName = (String) value;
928                                 if (localizedName != null && localizedName.trim().length() > 0) {
929                                         String oldValue = row.getLocalizedName();
930                                         if (oldValue != null && oldValue.equals(localizedName)) {
931                                                 return; // 変化なし
932                                         }
933                                         row.setLocalizedName(localizedName);
934                                 }
935                         }
936                 },
937                 Author("column.author", true, String.class) {
938                         @Override
939                         public Object getValue(PartsManageTableRow row) {
940                                 return row.getAuthor();
941                         }
942                         @Override
943                         public void setValue(PartsManageTableRow row, Object value) {
944                                 String author = (String) value;
945                                 if (author == null) {
946                                         author = "";
947                                 }
948                                 String oldValue = row.getAuthor();
949                                 if (oldValue != null && oldValue.equals(author)) {
950                                         return; // 変化なし
951                                 }
952                                 row.setAuthor(author);
953                         }
954                 },
955                 Version("column.version", true, Double.class) {
956                         @Override
957                         public Object getValue(PartsManageTableRow row) {
958                                 return row.getVersion() > 0 ? row.getVersion() : null;
959                         }
960                         @Override
961                         public void setValue(PartsManageTableRow row, Object value) {
962                                 Double version = (Double) value;
963                                 if (version == null || version.doubleValue() <= 0) {
964                                         version = Double.valueOf(0.);
965                                 }
966                                 Double oldValue = row.getVersion();
967                                 if (oldValue != null && oldValue.equals(version)) {
968                                         return; // 変化なし
969                                 }
970                                 row.setVersion(version);
971                         }
972                 },
973                 DownloadURL("column.downloadURL", true, String.class) {
974                         @Override
975                         public Object getValue(PartsManageTableRow row) {
976                                 return row.getDownloadURL();
977                         }
978                         @Override
979                         public void setValue(PartsManageTableRow row, Object value) {
980                                 String downloadURL = (String) value;
981                                 if (downloadURL == null) {
982                                         downloadURL = "";
983                                 }
984                                 String oldValue = row.getDownloadURL();
985                                 if (oldValue != null && oldValue.equals(downloadURL)) {
986                                         return; // 変化なし
987                                 }
988                                 row.setDownloadURL(downloadURL);
989                         }
990                 };
991
992                 private final Class<?> columnClass;
993
994                 private final boolean editable;
995
996                 private final String columnName;
997
998                 private String displayName;
999
1000                 private int width;
1001
1002                 private Columns(String columnName, boolean editable,
1003                                 Class<?> columnClass) {
1004                         this.columnName = columnName;
1005                         this.editable = editable;
1006                         this.columnClass = columnClass;
1007                 }
1008
1009                 public abstract Object getValue(PartsManageTableRow row);
1010
1011                 public boolean isEditable() {
1012                         return editable;
1013                 }
1014
1015                 public Class<?> getColumnClass() {
1016                         return columnClass;
1017                 }
1018
1019                 public String getDisplayName() {
1020                         init();
1021                         return displayName;
1022                 }
1023
1024                 public int getWidth() {
1025                         init();
1026                         return width;
1027                 }
1028
1029                 public void setValue(PartsManageTableRow row, Object value) {
1030                         // 何もしない.
1031                 }
1032
1033                 private void init() {
1034                         if (displayName != null) {
1035                                 return;
1036                         }
1037                         displayName = strings.getProperty(columnName);
1038                         width = Integer
1039                                         .parseInt(strings.getProperty(columnName + ".width"));
1040                 }
1041         }
1042
1043         
1044         public int getColumnCount() {
1045                 return Columns.values().length;
1046         }
1047         
1048         @Override
1049         public String getColumnName(int column) {
1050                 return Columns.values()[column].getDisplayName();
1051         }
1052         
1053         public void adjustColumnModel(TableColumnModel columnModel) {
1054                 Columns[] columns = Columns.values();
1055                 for (int idx = 0; idx < columns.length; idx++) {
1056                         columnModel.getColumn(idx).setPreferredWidth(
1057                                         columns[idx].getWidth());
1058                 }
1059         }
1060
1061         public Object getValueAt(int rowIndex, int columnIndex) {
1062                 PartsManageTableRow row = getRow(rowIndex);
1063                 Columns column = Columns.values()[columnIndex];
1064                 return column.getValue(row);
1065         }
1066         
1067         @Override
1068         public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
1069                 PartsManageTableRow row = getRow(rowIndex);
1070                 Columns column = Columns.values()[columnIndex];
1071                 if (!column.isEditable()) {
1072                         return;
1073                 }
1074                 column.setValue(row, aValue);
1075
1076                 // 更新日を最新にする
1077                 Timestamp dt = row.getTimestamp();
1078                 row.setLastModified(dt);
1079
1080                 // 変更通知
1081                 fireTableRowsUpdated(rowIndex, columnIndex);
1082         }
1083         
1084         @Override
1085         public Class<?> getColumnClass(int columnIndex) {
1086                 Columns column = Columns.values()[columnIndex];
1087                 return column.getColumnClass();
1088         }
1089         
1090         @Override
1091         public boolean isCellEditable(int rowIndex, int columnIndex) {
1092                 Columns column = Columns.values()[columnIndex];
1093                 return column.isEditable();
1094         }
1095         
1096         public void initModel(CharacterData characterData) {
1097                 if (characterData == null) {
1098                         throw new IllegalArgumentException();
1099                 }
1100                 clear();
1101
1102                 // 既存のパーツ管理情報ファイルがあれば読み込む
1103                 URI docBase = characterData.getDocBase();
1104                 PartsManageData partsManageData = null;
1105                 if (docBase != null) {
1106                         try {
1107                                 PartsInfoXMLReader reader = new PartsInfoXMLReader();
1108                                 partsManageData = reader.loadPartsManageData(docBase);
1109
1110                         } catch (Exception ex) {
1111                                 logger.log(Level.WARNING, ex.toString(), ex);
1112                         }
1113                 }
1114                 if (partsManageData == null) {
1115                         partsManageData = new PartsManageData();
1116                 }
1117
1118                 for (PartsCategory partsCategory : characterData.getPartsCategories()) {
1119                         for (Map.Entry<PartsIdentifier, PartsSpec> entry : characterData
1120                                         .getPartsSpecMap(partsCategory).entrySet()) {
1121                                 PartsIdentifier partsIdentifier = entry.getKey();
1122                                 PartsSpec partsSpec = entry.getValue();
1123                                 
1124                                 // パーツ管理情報ファイルから、パーツ管理情報を設定した時点の
1125                                 // ファイルサイズや更新日時などを読み取る.
1126                                 PartsKey partsKey = new PartsKey(partsIdentifier);
1127                                 PartsVersionInfo versionInfo = partsManageData
1128                                                 .getVersion(partsKey);
1129
1130                                 Timestamp lastModified = null;
1131
1132                                 if (versionInfo != null) {
1133                                         lastModified = versionInfo.getLastModified();
1134                                 }
1135
1136                                 PartsManageTableRow row = new PartsManageTableRow(
1137                                                 partsIdentifier, partsSpec, lastModified);
1138                                 addRow(row);
1139                         }
1140                 }
1141
1142                 sortByAuthor();
1143         }
1144
1145         /**
1146          * ホームページを設定する.<br>
1147          * ホームページはAuthorに対して1つであるが、Authorが自由編集可能であるため便宜的にRowに持たせている.<br>
1148          * 結果として同じAuthorに対して同じ値を設定する必要がある.<br>
1149          * ホームページはテーブルに表示されないのでリスナーへの通知は行わない.<br>
1150          * 
1151          * @param author
1152          *            作者、空またはnullは何もしない.
1153          * @param homepage
1154          *            ホームページ
1155          */
1156         public void setHomepage(String author, String homepage) {
1157                 if (author == null || author.length() == 0) {
1158                         return;
1159                 }
1160                 for (PartsManageTableRow row : elements) {
1161                         String targetAuthor = row.getAuthor();
1162                         if (targetAuthor == null) {
1163                                 targetAuthor = "";
1164                         }
1165                         if (targetAuthor.equals(author)) {
1166                                 row.setHomepage(homepage);
1167                         }
1168                 }
1169         }
1170
1171         /**
1172          * ホームページを取得する.<br>
1173          * 該当する作者がないか、作者がnullまたは空の場合は常にnullを返す.<br>
1174          * 
1175          * @param author
1176          *            作者
1177          * @return ホームページ、またはnull
1178          */
1179         public String getHomepage(String author) {
1180                 if (author == null || author.length() == 0) {
1181                         return null;
1182                 }
1183                 for (PartsManageTableRow row : elements) {
1184                         String targetAuthor = row.getAuthor();
1185                         if (targetAuthor == null) {
1186                                 targetAuthor = "";
1187                         }
1188                         if (targetAuthor.equals(author)) {
1189                                 return row.getHomepage();
1190                         }
1191                 }
1192                 return null;
1193         }
1194         
1195         protected static final Comparator<PartsManageTableRow> NAMED_SORTER
1196                 = new Comparator<PartsManageTableRow>() {
1197                 public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {
1198                         // カテゴリで順序づけ
1199                         int ret = o1.getPartsIdentifier().getPartsCategory().compareTo(
1200                                         o2.getPartsIdentifier().getPartsCategory());
1201                         if (ret == 0) {
1202                                 // 表示名で順序づけ
1203                                 String lnm1 = o1.getLocalizedName();
1204                                 String lnm2 = o2.getLocalizedName();
1205                                 if (lnm1 == null) {
1206                                         lnm1 = "";
1207                                 }
1208                                 if (lnm2 == null) {
1209                                         lnm2 = "";
1210                                 }
1211                                 ret = lnm1.compareTo(lnm2);
1212                         }
1213                         if (ret == 0) {
1214                                 // それでも判定できなければ元の識別子で判定する.
1215                                 ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());
1216                         }
1217                         return ret;
1218                 }
1219         };
1220         
1221         public void sortByName() {
1222                 Collections.sort(elements, NAMED_SORTER);
1223                 fireTableDataChanged();
1224         }
1225         
1226         public void sortByTimestamp() {
1227                 Collections.sort(elements, new Comparator<PartsManageTableRow>() {
1228                         public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {
1229                                 // 更新日で順序づけ (新しいもの順)
1230                                 int ret = o1.getTimestamp().compareTo(o2.getTimestamp()) * -1;
1231                                 if (ret == 0) {
1232                                         // それでも判定できなければ名前順と同じ
1233                                         ret = NAMED_SORTER.compare(o1, o2);
1234                                 }
1235                                 return ret;
1236                         }
1237                 });
1238                 fireTableDataChanged();
1239         }
1240
1241         public void sortByAuthor() {
1242                 Collections.sort(elements, new Comparator<PartsManageTableRow>() {
1243                         public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {
1244                                 // 作者で順序づけ
1245                                 String author1 = o1.getAuthor();
1246                                 String author2 = o2.getAuthor();
1247                                 if (author1 == null) {
1248                                         author1 = "";
1249                                 }
1250                                 if (author2 == null) {
1251                                         author2 = "";
1252                                 }
1253                                 int ret = author1.compareTo(author2);
1254                                 if (ret == 0) {
1255                                         // それでも判定できなければ名前順と同じ
1256                                         ret = NAMED_SORTER.compare(o1, o2);
1257                                 }
1258                                 return ret;
1259                         }
1260                 });
1261                 fireTableDataChanged();
1262         }
1263 }