OSDN Git Service

リポジトリ内改行コードのLFへの修正
[charactermanaj/CharacterManaJ.git] / src / main / java / charactermanaj / ui / PartsManageDialog.java
index 508f9db..c29c646 100644 (file)
-package charactermanaj.ui;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Component;\r
-import java.awt.Container;\r
-import java.awt.GridBagConstraints;\r
-import java.awt.GridBagLayout;\r
-import java.awt.Insets;\r
-import java.awt.Toolkit;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.KeyEvent;\r
-import java.awt.event.WindowAdapter;\r
-import java.awt.event.WindowEvent;\r
-import java.net.URI;\r
-import java.sql.Timestamp;\r
-import java.util.Arrays;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.Map;\r
-import java.util.Properties;\r
-import java.util.concurrent.Semaphore;\r
-import java.util.logging.Level;\r
-import java.util.logging.Logger;\r
-\r
-import javax.swing.AbstractAction;\r
-import javax.swing.Action;\r
-import javax.swing.ActionMap;\r
-import javax.swing.BorderFactory;\r
-import javax.swing.Box;\r
-import javax.swing.InputMap;\r
-import javax.swing.JButton;\r
-import javax.swing.JComponent;\r
-import javax.swing.JDialog;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JOptionPane;\r
-import javax.swing.JPanel;\r
-import javax.swing.JPopupMenu;\r
-import javax.swing.JRootPane;\r
-import javax.swing.JScrollPane;\r
-import javax.swing.JSeparator;\r
-import javax.swing.JTable;\r
-import javax.swing.JTextField;\r
-import javax.swing.KeyStroke;\r
-import javax.swing.UIManager;\r
-import javax.swing.event.DocumentEvent;\r
-import javax.swing.event.DocumentListener;\r
-import javax.swing.event.ListSelectionEvent;\r
-import javax.swing.event.ListSelectionListener;\r
-import javax.swing.event.TableModelEvent;\r
-import javax.swing.event.TableModelListener;\r
-import javax.swing.table.TableCellRenderer;\r
-import javax.swing.table.TableColumnModel;\r
-\r
-import charactermanaj.Main;\r
-import charactermanaj.model.AppConfig;\r
-import charactermanaj.model.CharacterData;\r
-import charactermanaj.model.PartsAuthorInfo;\r
-import charactermanaj.model.PartsCategory;\r
-import charactermanaj.model.PartsIdentifier;\r
-import charactermanaj.model.PartsManageData;\r
-import charactermanaj.model.PartsManageData.PartsKey;\r
-import charactermanaj.model.PartsManageData.PartsVersionInfo;\r
-import charactermanaj.model.PartsSpec;\r
-import charactermanaj.model.io.PartsInfoXMLReader;\r
-import charactermanaj.model.io.PartsInfoXMLWriter;\r
-import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;\r
-import charactermanaj.util.DesktopUtilities;\r
-import charactermanaj.util.ErrorMessageHelper;\r
-import charactermanaj.util.LocalizedResourcePropertyLoader;\r
-\r
-public class PartsManageDialog extends JDialog {\r
-\r
-       private static final long serialVersionUID = 1L;\r
-       \r
-       protected static final String STRINGS_RESOURCE = "languages/partsmanagedialog";\r
-       \r
-       \r
-       private static final Logger logger = Logger.getLogger(PartsManageDialog.class.getName());\r
-\r
-       private CharacterData characterData;\r
-       \r
-       private PartsManageTableModel partsManageTableModel;\r
-       \r
-       private JTable partsManageTable;\r
-       \r
-       private JTextField txtHomepage;\r
-       \r
-       private JTextField txtAuthor;\r
-       \r
-       private boolean updated;\r
-\r
-       \r
-       public PartsManageDialog(JFrame parent, CharacterData characterData) {\r
-               super(parent, true);\r
-               \r
-               if (characterData == null) {\r
-                       throw new IllegalArgumentException();\r
-               }\r
-               this.characterData = characterData;\r
-               \r
-               setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);\r
-               addWindowListener(new WindowAdapter() {\r
-                       @Override\r
-                       public void windowClosing(WindowEvent e) {\r
-                               onClose();\r
-                       }\r
-               });\r
-\r
-               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
-                               .getLocalizedProperties(STRINGS_RESOURCE);\r
-\r
-               setTitle(strings.getProperty("title"));\r
-\r
-               Container contentPane = getContentPane();\r
-               \r
-               // パーツリストテーブル\r
-               JPanel partsListPanel = new JPanel();\r
-               partsListPanel.setBorder(BorderFactory.createCompoundBorder(\r
-                               BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory\r
-                                               .createTitledBorder(strings.getProperty("partslist"))));\r
-\r
-               GridBagLayout partsListPanelLayout = new GridBagLayout();\r
-               partsListPanel.setLayout(partsListPanelLayout);\r
-               \r
-               partsManageTableModel = new PartsManageTableModel();\r
-               partsManageTable = new JTable(partsManageTableModel) {\r
-                       private static final long serialVersionUID = 1L;\r
-\r
-                       @Override\r
-                       public Component prepareRenderer(TableCellRenderer renderer,\r
-                                       int rowIdx, int columnIdx) {\r
-                               PartsManageTableModel.Columns column = PartsManageTableModel.Columns\r
-                                               .values()[columnIdx];\r
-                               Component comp = super.prepareRenderer(renderer, rowIdx, columnIdx);\r
-                               PartsManageTableRow row = partsManageTableModel.getRow(rowIdx);\r
-\r
-                               Timestamp current = row.getTimestamp();\r
-                               Timestamp lastModified = row.getLastModified();\r
-\r
-                               boolean warnings = false;\r
-\r
-                               if (current != null && !current.equals(lastModified)) {\r
-                                       // 現在のパーツの最終更新日と、パーツ管理情報の作成時のパーツの最終更新日が不一致の場合\r
-                                       warnings = true;\r
-                               }\r
-\r
-                               // 背景色、警告行は赤色に\r
-                               if (warnings && column == PartsManageTableModel.Columns.LastModified) {\r
-                                       AppConfig appConfig = AppConfig.getInstance();\r
-                                       Color invalidBgColor = appConfig.getInvalidBgColor();\r
-                                       comp.setBackground(invalidBgColor);\r
-                               } else {\r
-                                       if (isCellSelected(rowIdx, columnIdx)) {\r
-                                               comp.setBackground(getSelectionBackground());\r
-                                       } else {\r
-                                               comp.setBackground(getBackground());\r
-                                       }\r
-                               }\r
-\r
-                               return comp;\r
-                       }\r
-               };\r
-               partsManageTable.setShowGrid(true);\r
-               partsManageTable.setGridColor(AppConfig.getInstance().getGridColor());\r
-               partsManageTableModel.adjustColumnModel(partsManageTable.getColumnModel());\r
-               partsManageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);\r
-               partsManageTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);\r
-\r
-               JScrollPane partsManageTableSP = new JScrollPane(partsManageTable);\r
-               \r
-               partsManageTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {\r
-                       public void valueChanged(ListSelectionEvent e) {\r
-                               if (!e.getValueIsAdjusting()) {\r
-                                       onChangeSelection();\r
-                               }\r
-                       }\r
-               });\r
-               \r
-               partsManageTableModel.addTableModelListener(new TableModelListener() {\r
-                       public void tableChanged(TableModelEvent e) {\r
-                               onTableDataChange(e.getFirstRow(), e.getLastRow());\r
-                       }\r
-               });\r
-               \r
-               GridBagConstraints gbc = new GridBagConstraints();\r
-               gbc.gridx = 0;\r
-               gbc.gridy = 0;\r
-               gbc.gridheight = 1;\r
-               gbc.gridwidth = 4;\r
-               gbc.anchor = GridBagConstraints.EAST;\r
-               gbc.fill = GridBagConstraints.BOTH;\r
-               gbc.insets = new Insets(3, 3, 3, 3);\r
-               gbc.ipadx = 0;\r
-               gbc.ipady = 0;\r
-               gbc.weightx = 1.;\r
-               gbc.weighty = 1.;\r
-               partsListPanel.add(partsManageTableSP, gbc);\r
-               \r
-               Action actSortByName = new AbstractAction(strings.getProperty("sortByName")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onSortByName();\r
-                       }\r
-               };\r
-               Action actSortByAuthor = new AbstractAction(strings.getProperty("sortByAuthor")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onSortByAuthor();\r
-                       }\r
-               };\r
-               Action actSortByTimestamp = new AbstractAction(strings.getProperty("sortByTimestamp")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onSortByTimestamp();\r
-                       }\r
-               };\r
-               \r
-               gbc.gridx = 0;\r
-               gbc.gridy = 1;\r
-               gbc.gridheight = 1;\r
-               gbc.gridwidth = 1;\r
-               gbc.weightx = 0.;\r
-               gbc.weighty = 0.;\r
-               partsListPanel.add(new JButton(actSortByName), gbc);\r
-\r
-               gbc.gridx = 1;\r
-               gbc.gridy = 1;\r
-               gbc.weightx = 0.;\r
-               gbc.weighty = 0.;\r
-               partsListPanel.add(new JButton(actSortByAuthor), gbc);\r
-\r
-               gbc.gridx = 2;\r
-               gbc.gridy = 1;\r
-               gbc.weightx = 0.;\r
-               gbc.weighty = 0.;\r
-               partsListPanel.add(new JButton(actSortByTimestamp), gbc);\r
-\r
-               gbc.gridx = 3;\r
-               gbc.gridy = 1;\r
-               gbc.weightx = 1.;\r
-               gbc.weighty = 0.;\r
-               partsListPanel.add(Box.createHorizontalGlue(), gbc);\r
-\r
-               contentPane.add(partsListPanel, BorderLayout.CENTER);\r
-\r
-               // テーブルのコンテキストメニュー\r
-               final JPopupMenu popupMenu = new JPopupMenu();\r
-               Action actApplyAllLastModified = new AbstractAction(strings.getProperty("applyAllLastModified")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onApplyAllLastModified();\r
-                       }\r
-               };\r
-               Action actApplyAllDownloadURL = new AbstractAction(strings.getProperty("applyAllDownloadURL")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onApplyAllDownloadURL();\r
-                       }\r
-               };\r
-               Action actApplyAllVersion = new AbstractAction(strings.getProperty("applyAllVersion")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onApplyAllVersion();\r
-                       }\r
-               };\r
-               popupMenu.add(actApplyAllLastModified);\r
-               popupMenu.add(new JSeparator());\r
-               popupMenu.add(actApplyAllVersion);\r
-               popupMenu.add(actApplyAllDownloadURL);\r
-               \r
-               partsManageTable.setComponentPopupMenu(popupMenu);\r
-               \r
-               // 作者情報パネル\r
-               JPanel authorPanel = new JPanel();\r
-               authorPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory\r
-                               .createEmptyBorder(5, 5, 5, 5), BorderFactory\r
-                               .createTitledBorder(strings.getProperty("author.info"))));\r
-               GridBagLayout authorPanelLayout = new GridBagLayout();\r
-               authorPanel.setLayout(authorPanelLayout);\r
-               \r
-               gbc.gridx = 0;\r
-               gbc.gridy = 0;\r
-               gbc.gridheight = 1;\r
-               gbc.gridwidth = 1;\r
-               gbc.anchor = GridBagConstraints.EAST;\r
-               gbc.fill = GridBagConstraints.BOTH;\r
-               gbc.insets = new Insets(3, 3, 3, 3);\r
-               gbc.ipadx = 0;\r
-               gbc.ipady = 0;\r
-               gbc.weightx = 0.;\r
-               gbc.weighty = 0.;\r
-               authorPanel.add(new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc);\r
-\r
-               gbc.gridx = 1;\r
-               gbc.gridy = 0;\r
-               gbc.gridwidth = 2;\r
-               gbc.weightx = 1.;\r
-               txtAuthor = new JTextField();\r
-               authorPanel.add(txtAuthor, gbc);\r
-\r
-               gbc.gridx = 0;\r
-               gbc.gridy = 1;\r
-               gbc.gridwidth = 1;\r
-               gbc.weightx = 0.;\r
-               authorPanel.add(new JLabel(strings.getProperty("homepage"), JLabel.RIGHT), gbc);\r
-\r
-               gbc.gridx = 1;\r
-               gbc.gridy = 1;\r
-               gbc.gridwidth = 1;\r
-               gbc.weightx = 1.;\r
-               txtHomepage = new JTextField();\r
-               authorPanel.add(txtHomepage, gbc);\r
-\r
-               gbc.gridx = 2;\r
-               gbc.gridy = 1;\r
-               gbc.gridwidth = 1;\r
-               gbc.weightx = 0.;\r
-               Action actBrowseHomepage = new AbstractAction(strings.getProperty("open")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onBrosweHomepage();\r
-                       }\r
-               };\r
-               authorPanel.add(new JButton(actBrowseHomepage), gbc);\r
-\r
-               if (!DesktopUtilities.isSupported()) {\r
-                       actBrowseHomepage.setEnabled(false);\r
-               }\r
-               \r
-               txtAuthor.getDocument().addDocumentListener(new DocumentListener() {\r
-                       public void removeUpdate(DocumentEvent e) {\r
-                               onEditAuthor();\r
-                       }\r
-                       public void insertUpdate(DocumentEvent e) {\r
-                               onEditAuthor();\r
-                       }\r
-                       public void changedUpdate(DocumentEvent e) {\r
-                               onEditAuthor();\r
-                       }\r
-               });\r
-               txtHomepage.getDocument().addDocumentListener(new DocumentListener() {\r
-                       public void removeUpdate(DocumentEvent e) {\r
-                               onEditHomepage();\r
-                       }\r
-                       public void insertUpdate(DocumentEvent e) {\r
-                               onEditHomepage();\r
-                       }\r
-                       public void changedUpdate(DocumentEvent e) {\r
-                               onEditHomepage();\r
-                       }\r
-               });\r
-               \r
-               \r
-               // ボタンパネル\r
-               JPanel btnPanel = new JPanel();\r
-               btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 45));\r
-               GridBagLayout btnPanelLayout = new GridBagLayout();\r
-               btnPanel.setLayout(btnPanelLayout);\r
-               \r
-               Action actClose = new AbstractAction(strings.getProperty("cancel")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onClose();\r
-                       }\r
-               };\r
-               Action actOK = new AbstractAction(strings.getProperty("update")) {\r
-                       private static final long serialVersionUID = 1L;\r
-                       public void actionPerformed(ActionEvent e) {\r
-                               onOK();\r
-                       }\r
-               };\r
-               \r
-               gbc.gridx = 0;\r
-               gbc.gridy = 0;\r
-               gbc.weightx = 1.;\r
-               btnPanel.add(Box.createHorizontalGlue(), gbc);\r
-               \r
-               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;\r
-               gbc.gridy = 0;\r
-               gbc.weightx = 0.;\r
-               btnPanel.add(new JButton(actOK), gbc);\r
-\r
-               gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2;\r
-               gbc.gridy = 0;\r
-               gbc.weightx = 0.;\r
-               btnPanel.add(new JButton(actClose), gbc);\r
-               \r
-               // ダイアログ下部\r
-               JPanel southPanel = new JPanel(new BorderLayout());\r
-               southPanel.add(authorPanel, BorderLayout.NORTH);\r
-               southPanel.add(btnPanel, BorderLayout.SOUTH);\r
-\r
-               contentPane.add(southPanel, BorderLayout.SOUTH);\r
-               \r
-               // キーボード\r
-               \r
-               Toolkit tk = Toolkit.getDefaultToolkit();\r
-               JRootPane rootPane = getRootPane();\r
-               InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);\r
-               ActionMap am = rootPane.getActionMap();\r
-               im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closePartsManageDialog");\r
-               im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closePartsManageDialog");\r
-               am.put("closePartsManageDialog", actClose);\r
-\r
-               // モデル構築\r
-               partsManageTableModel.initModel(characterData);\r
-               \r
-               // ウィンドウ配置\r
-               setSize(500, 400);\r
-               setLocationRelativeTo(parent);\r
-       }\r
-\r
-       private Semaphore authorEditSemaphore = new Semaphore(1);\r
-       \r
-       protected void onChangeSelection() {\r
-               try {\r
-                       authorEditSemaphore.acquire();\r
-                       try {\r
-                               int [] selRows = partsManageTable.getSelectedRows();\r
-                               HashSet<String> authors = new HashSet<String>();\r
-                               for (int selRow : selRows) {\r
-                                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);\r
-                                       authors.add(row.getAuthor() == null ? "" : row.getAuthor());\r
-                               }\r
-                               if (authors.size() > 1) {\r
-                                       AppConfig appConfig = AppConfig.getInstance();\r
-                                       txtAuthor.setBackground(appConfig.getAuthorEditConflictBgColor());\r
-                                       txtHomepage.setBackground(appConfig.getAuthorEditConflictBgColor());\r
-                               } else {\r
-                                       Color bgColor = UIManager.getColor("TextField.background");\r
-                                       if (bgColor == null) {\r
-                                               bgColor = Color.white;\r
-                                       }\r
-                                       txtAuthor.setBackground(bgColor);\r
-                                       txtHomepage.setBackground(bgColor);\r
-                               }\r
-                               if (authors.isEmpty()) {\r
-                                       // 選択されているauthorがない場合は全部編集不可\r
-                                       txtAuthor.setEditable(false);\r
-                                       txtAuthor.setText("");\r
-                                       txtHomepage.setEditable(false);\r
-                                       txtHomepage.setText("");\r
-                               } else {\r
-                                       // 選択されているAuthorが1つ以上あればAuthorは編集可\r
-                                       txtAuthor.setEditable(true);\r
-                                       txtHomepage.setEditable(true);\r
-                                       if (authors.size() == 1) {\r
-                                               // 選択されているAuthorが一個であれば、それを表示\r
-                                               String author = authors.iterator().next();\r
-                                               String homepage = partsManageTableModel.getHomepage(author);\r
-                                               txtAuthor.setText(author);\r
-                                               txtHomepage.setText(homepage);\r
-                                       } else {\r
-                                               // 選択されているAuthorが二個以上あれば編集可能だがテキストには表示しない.\r
-                                               txtAuthor.setText("");\r
-                                               txtHomepage.setText("");\r
-                                       }\r
-                               }\r
-                       } finally {\r
-                               authorEditSemaphore.release();\r
-                       }\r
-\r
-               } catch (InterruptedException ex) {\r
-                       ErrorMessageHelper.showErrorDialog(this, ex);\r
-\r
-               } catch (RuntimeException ex) {\r
-                       ErrorMessageHelper.showErrorDialog(this, ex);\r
-               }\r
-       }\r
-       \r
-       protected void onTableDataChange(int firstRow, int lastRow) {\r
-               onChangeSelection();\r
-       }\r
-       \r
-       protected void onApplyAllLastModified() {\r
-               int[] selRows = partsManageTable.getSelectedRows();\r
-               if (selRows.length == 0) {\r
-                       Toolkit tk = Toolkit.getDefaultToolkit();\r
-                       tk.beep();\r
-                       return;\r
-               }\r
-               Arrays.sort(selRows);\r
-\r
-               for (int selRow : selRows) {\r
-                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);\r
-                       Timestamp dt = row.getTimestamp();\r
-                       row.setLastModified(dt);\r
-               }\r
-               partsManageTableModel.fireTableRowsUpdated(selRows[0],\r
-                               selRows[selRows.length - 1]);\r
-       }\r
-\r
-       protected void onApplyAllDownloadURL() {\r
-               int[] selRows = partsManageTable.getSelectedRows();\r
-               if (selRows.length == 0) {\r
-                       Toolkit tk = Toolkit.getDefaultToolkit();\r
-                       tk.beep();\r
-                       return;\r
-               }\r
-               Arrays.sort(selRows);\r
-\r
-               HashSet<String> authors = new HashSet<String>();\r
-               for (int selRow : selRows) {\r
-                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);\r
-                       authors.add(row.getAuthor() == null ? "" : row.getAuthor());\r
-               }\r
-\r
-               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
-                               .getLocalizedProperties(STRINGS_RESOURCE);\r
-\r
-               if (authors.size() > 1) {\r
-                       if (JOptionPane.showConfirmDialog(this,\r
-                                       strings.getProperty("confirm.authorConflict"),\r
-                                       strings.getProperty("confirm"),\r
-                                       JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) {\r
-                               return;\r
-                       }\r
-               }\r
-               \r
-               PartsManageTableRow firstRow = partsManageTableModel.getRow(selRows[0]);\r
-               String downloadURL = firstRow.getDownloadURL();\r
-               if (downloadURL == null) {\r
-                       downloadURL = "";\r
-               }\r
-               String downloadURL_new = JOptionPane.showInputDialog(this, strings.getProperty("input.downloadURL"), downloadURL);\r
-               if (downloadURL_new == null || downloadURL.equals(downloadURL_new)) {\r
-                       // キャンセルされたか、内容に変化ない場合は何もしない\r
-                       return;\r
-               }\r
-               \r
-               for (int selRow : selRows) {\r
-                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);\r
-                       row.setDownloadURL(downloadURL_new);\r
-\r
-                       Timestamp dt = row.getTimestamp();\r
-                       row.setLastModified(dt);\r
-               }\r
-               partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]);\r
-       }\r
-       \r
-       protected void onApplyAllVersion() {\r
-               Toolkit tk = Toolkit.getDefaultToolkit();\r
-               int[] selRows = partsManageTable.getSelectedRows();\r
-               if (selRows.length == 0) {\r
-                       tk.beep();\r
-                       return;\r
-               }\r
-               Arrays.sort(selRows);\r
-\r
-               HashSet<String> authors = new HashSet<String>();\r
-               for (int selRow : selRows) {\r
-                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);\r
-                       authors.add(row.getAuthor() == null ? "" : row.getAuthor());\r
-               }\r
-\r
-               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
-                               .getLocalizedProperties(STRINGS_RESOURCE);\r
-\r
-               if (authors.size() > 1) {\r
-                       if (JOptionPane.showConfirmDialog(this,\r
-                                       strings.getProperty("confirm.authorConflict"),\r
-                                       strings.getProperty("confirm"),\r
-                                       JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) {\r
-                               return;\r
-                       }\r
-               }\r
-\r
-               PartsManageTableRow firstRow = partsManageTableModel.getRow(selRows[0]);\r
-               double version = firstRow.getVersion();\r
-               String strVersion = (version < 0) ? "" : Double.toString(version);\r
-               String strVersion_new = JOptionPane.showInputDialog(this,\r
-                               strings.getProperty("input.version"), strVersion);\r
-               if (strVersion_new == null || strVersion.equals(strVersion_new)) {\r
-                       // キャンセルされたか、内容に変化ない場合は何もしない\r
-                       return;\r
-               }\r
-               double version_new;\r
-               try {\r
-                       version_new = Double.parseDouble(strVersion_new);\r
-               } catch (Exception ex) {\r
-                       // 数値として不正であれば何もしない.\r
-                       tk.beep();\r
-                       return;\r
-               }\r
-               \r
-               for (int selRow : selRows) {\r
-                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);\r
-                       row.setVersion(version_new);\r
-\r
-                       Timestamp dt = row.getTimestamp();\r
-                       row.setLastModified(dt);\r
-               }\r
-               partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]);\r
-       }\r
-       \r
-       protected void onEditHomepage() {\r
-               try {\r
-                       if (!authorEditSemaphore.tryAcquire()) {\r
-                               return;\r
-                       }\r
-                       try {\r
-                               String author = txtAuthor.getText();\r
-                               String homepage = txtHomepage.getText();\r
-                               partsManageTableModel.setHomepage(author, homepage);\r
-                       } finally {\r
-                               authorEditSemaphore.release();\r
-                       }\r
-               } catch (Exception ex) {\r
-                       ErrorMessageHelper.showErrorDialog(this, ex);\r
-               }\r
-       }\r
-\r
-       protected void onEditAuthor() {\r
-               try {\r
-                       if (!authorEditSemaphore.tryAcquire()) {\r
-                               return;\r
-                       }\r
-                       try {\r
-                               String author = txtAuthor.getText();\r
-                               int[] selRows = partsManageTable.getSelectedRows();\r
-                               int firstRow = -1;\r
-                               int lastRow = Integer.MAX_VALUE;\r
-                               for (int selRow : selRows) {\r
-                                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);\r
-                                       String oldValue = row.getAuthor();\r
-                                       if (oldValue == null || !oldValue.equals(author)) {\r
-                                               row.setAuthor(author);\r
-\r
-                                               Timestamp dt = row.getTimestamp();\r
-                                               row.setLastModified(dt);\r
-\r
-                                               firstRow = Math.max(firstRow, selRow);\r
-                                               lastRow = Math.min(lastRow, selRow);\r
-                                       }\r
-                               }\r
-                               \r
-                               String homepage = partsManageTableModel.getHomepage(author);\r
-                               if (homepage == null) {\r
-                                       homepage = "";\r
-                               }\r
-                               txtHomepage.setText(homepage);\r
-                               \r
-                               if (firstRow >= 0) {\r
-                                       partsManageTable.repaint();\r
-                               }\r
-                       } finally {\r
-                               authorEditSemaphore.release();                          \r
-                       }\r
-               } catch (Exception ex) {\r
-                       ErrorMessageHelper.showErrorDialog(this, ex);\r
-               }\r
-       }\r
-       \r
-       protected void onClose() {\r
-               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
-                               .getLocalizedProperties(STRINGS_RESOURCE);\r
-               if (JOptionPane.showConfirmDialog(this,\r
-                               strings.getProperty("confirm.cancel"),\r
-                               strings.getProperty("confirm"),\r
-                               JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {\r
-                       return;\r
-               }\r
-               updated = false;\r
-               dispose();\r
-       }\r
-       \r
-       protected void onBrosweHomepage() {\r
-               Toolkit tk = Toolkit.getDefaultToolkit();\r
-               String homepage = txtHomepage.getText();\r
-               if (homepage == null || homepage.trim().length() == 0) {\r
-                       tk.beep();\r
-                       return;\r
-               }\r
-               try {\r
-                       URI uri = new URI(homepage);\r
-                       DesktopUtilities.browse(uri);\r
-\r
-               } catch (Exception ex) {\r
-                       tk.beep();\r
-                       logger.log(Level.INFO, "browse failed. : " + homepage, ex);\r
-               }\r
-       }\r
-       \r
-       protected void onSortByAuthor() {\r
-               partsManageTableModel.sortByAuthor();\r
-       }\r
-       \r
-       protected void onSortByName() {\r
-               partsManageTableModel.sortByName();\r
-       }\r
-       \r
-       protected void onSortByTimestamp() {\r
-               partsManageTableModel.sortByTimestamp();\r
-       }\r
-       \r
-       protected void onOK() {\r
-               if (partsManageTable.isEditing()) {\r
-                       Toolkit tk = Toolkit.getDefaultToolkit();\r
-                       tk.beep();\r
-                       return;\r
-               }\r
-               \r
-               int mx = partsManageTableModel.getRowCount();\r
-\r
-               // 作者ごとのホームページ情報の取得\r
-               // (同一作者につきホームページは一つ)\r
-               HashMap<String, PartsAuthorInfo> authorInfoMap = new HashMap<String, PartsAuthorInfo>();\r
-               for (int idx = 0; idx < mx; idx++) {\r
-                       PartsManageTableRow row = partsManageTableModel.getRow(idx);\r
-                       String author = row.getAuthor();\r
-                       String homepage = row.getHomepage();\r
-                       if (author != null && author.trim().length() > 0) {\r
-                               PartsAuthorInfo authorInfo = authorInfoMap.get(author.trim());\r
-                               if (authorInfo == null) {\r
-                                       authorInfo = new PartsAuthorInfo();\r
-                                       authorInfo.setAuthor(author.trim());\r
-                                       authorInfoMap.put(authorInfo.getAuthor(), authorInfo);\r
-                               }\r
-                               authorInfo.setHomePage(homepage);\r
-                       }\r
-               }\r
-\r
-               PartsManageData partsManageData = new PartsManageData();\r
-               \r
-               // パーツごとの作者とバージョン、ダウンロード先の取得\r
-               for (int idx = 0; idx < mx; idx++) {\r
-                       PartsManageTableRow row = partsManageTableModel.getRow(idx);\r
-                       \r
-                       String author = row.getAuthor();\r
-                       PartsAuthorInfo partsAuthorInfo = null;\r
-                       if (author != null && author.trim().length() > 0) {\r
-                               partsAuthorInfo = authorInfoMap.get(author.trim());\r
-                       }\r
-                       \r
-                       double version = row.getVersion();\r
-                       String downloadURL = row.getDownloadURL();\r
-                       String localizedName = row.getLocalizedName();\r
-                       Timestamp lastModified = row.getLastModified();\r
-                       \r
-                       PartsManageData.PartsVersionInfo versionInfo = new PartsManageData.PartsVersionInfo();\r
-                       versionInfo.setVersion(version);\r
-                       versionInfo.setDownloadURL(downloadURL);\r
-                       versionInfo.setLastModified(lastModified);\r
-\r
-                       PartsIdentifier partsIdentifier = row.getPartsIdentifier();\r
-                       \r
-                       PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsIdentifier);\r
-                       partsManageData.putPartsInfo(partsKey, localizedName,\r
-                                       partsAuthorInfo, versionInfo);\r
-               }\r
-               \r
-               // パーツ管理情報を書き込む.\r
-               PartsInfoXMLWriter xmlWriter = new PartsInfoXMLWriter();\r
-               try {\r
-                       URI docBase = characterData.getDocBase();\r
-                       xmlWriter.savePartsManageData(docBase, partsManageData);\r
-\r
-               } catch (Exception ex) {\r
-                       ErrorMessageHelper.showErrorDialog(this, ex);\r
-                       return;\r
-               }\r
-               \r
-               updated = true;\r
-               dispose();\r
-       }\r
-\r
-       /**\r
-        * パーツ管理情報が更新されたか?\r
-        * \r
-        * @return 更新された場合はtrue、そうでなければfalse\r
-        */\r
-       public boolean isUpdated() {\r
-               return updated;\r
-       }\r
-}\r
-\r
-class PartsManageTableRow {\r
-       \r
-       private PartsIdentifier partsIdentifier;\r
-       \r
-       private Timestamp timestamp;\r
-       \r
-       private String localizedName;\r
-       \r
-       private String author;\r
-       \r
-       private String homepage;\r
-       \r
-       private String downloadURL;\r
-       \r
-       private double version;\r
-       \r
-       private Timestamp lastModified;\r
-\r
-       public PartsManageTableRow(PartsIdentifier partsIdentifier,\r
-                       PartsSpec partsSpec, Timestamp lastModified) {\r
-               if (partsIdentifier == null || partsSpec == null) {\r
-                       throw new IllegalArgumentException();\r
-               }\r
-               this.partsIdentifier = partsIdentifier;\r
-               this.localizedName = partsIdentifier.getLocalizedPartsName();\r
-\r
-               this.timestamp = new Timestamp(partsSpec.getPartsFiles().lastModified());\r
-               \r
-               this.lastModified = lastModified;\r
-               \r
-               PartsAuthorInfo autherInfo = partsSpec.getAuthorInfo();\r
-               if (autherInfo != null) {\r
-                       author = autherInfo.getAuthor();\r
-                       homepage = autherInfo.getHomePage();\r
-               }\r
-               if (author == null) {\r
-                       author = "";\r
-               }\r
-               if (homepage == null) {\r
-                       homepage = "";\r
-               }\r
-               downloadURL = partsSpec.getDownloadURL();\r
-               version = partsSpec.getVersion();\r
-       }\r
-       \r
-       public PartsIdentifier getPartsIdentifier() {\r
-               return partsIdentifier;\r
-       }\r
-       \r
-       public Timestamp getTimestamp() {\r
-               return timestamp;\r
-       }\r
-\r
-       public String getLocalizedName() {\r
-               return localizedName;\r
-       }\r
-       \r
-       public void setLocalizedName(String localizedName) {\r
-               if (localizedName == null || localizedName.trim().length() == 0) {\r
-                       throw new IllegalArgumentException();\r
-               }\r
-               this.localizedName = localizedName;\r
-       }\r
-       \r
-       public String getAuthor() {\r
-               return author;\r
-       }\r
-       \r
-       public String getDownloadURL() {\r
-               return downloadURL;\r
-       }\r
-       \r
-       public double getVersion() {\r
-               return version;\r
-       }\r
-       \r
-       public void setAuthor(String author) {\r
-               this.author = author;\r
-       }\r
-       \r
-       public void setDownloadURL(String downloadURL) {\r
-               this.downloadURL = downloadURL;\r
-       }\r
-       \r
-       public void setVersion(double version) {\r
-               this.version = version;\r
-       }\r
-       \r
-       public String getHomepage() {\r
-               return homepage;\r
-       }\r
-       \r
-       public void setHomepage(String homepage) {\r
-               this.homepage = homepage;\r
-       }\r
-       \r
-       public Timestamp getLastModified() {\r
-               return lastModified;\r
-       }\r
-\r
-       public void setLastModified(Timestamp lastModified) {\r
-               this.lastModified = lastModified;\r
-       }\r
-}\r
-\r
-class PartsManageTableModel extends AbstractTableModelWithComboBoxModel<PartsManageTableRow> {\r
-\r
-       private static final long serialVersionUID = 1L;\r
-\r
-       private static final Logger logger = Logger\r
-                       .getLogger(PartsManageTableModel.class.getName());\r
-\r
-       private static Properties strings = LocalizedResourcePropertyLoader\r
-                       .getCachedInstance().getLocalizedProperties(\r
-                                       PartsManageDialog.STRINGS_RESOURCE);\r
-\r
-       /**\r
-        * カラムの定義\r
-        */\r
-       public enum Columns {\r
-               PartsID("column.partsid", false, String.class) {\r
-                       @Override\r
-                       public Object getValue(PartsManageTableRow row) {\r
-                               return row.getPartsIdentifier().getPartsName();\r
-                       }\r
-               },\r
-               LastModified("column.lastmodified", false, String.class) {\r
-                       @Override\r
-                       public Object getValue(PartsManageTableRow row) {\r
-                               return row.getTimestamp().toString();\r
-                       }\r
-               },\r
-               Category("column.category", false, String.class) {\r
-                       @Override\r
-                       public Object getValue(PartsManageTableRow row) {\r
-                               return row.getPartsIdentifier().getPartsCategory()\r
-                                               .getLocalizedCategoryName();\r
-                       }\r
-               },\r
-               PartsName("column.partsname", true, String.class) {\r
-                       @Override\r
-                       public Object getValue(PartsManageTableRow row) {\r
-                               return row.getLocalizedName();\r
-                       }\r
-                       @Override\r
-                       public void setValue(PartsManageTableRow row, Object value) {\r
-                               String localizedName = (String) value;\r
-                               if (localizedName != null && localizedName.trim().length() > 0) {\r
-                                       String oldValue = row.getLocalizedName();\r
-                                       if (oldValue != null && oldValue.equals(localizedName)) {\r
-                                               return; // 変化なし\r
-                                       }\r
-                                       row.setLocalizedName(localizedName);\r
-                               }\r
-                       }\r
-               },\r
-               Author("column.author", true, String.class) {\r
-                       @Override\r
-                       public Object getValue(PartsManageTableRow row) {\r
-                               return row.getAuthor();\r
-                       }\r
-                       @Override\r
-                       public void setValue(PartsManageTableRow row, Object value) {\r
-                               String author = (String) value;\r
-                               if (author == null) {\r
-                                       author = "";\r
-                               }\r
-                               String oldValue = row.getAuthor();\r
-                               if (oldValue != null && oldValue.equals(author)) {\r
-                                       return; // 変化なし\r
-                               }\r
-                               row.setAuthor(author);\r
-                       }\r
-               },\r
-               Version("column.version", true, Double.class) {\r
-                       @Override\r
-                       public Object getValue(PartsManageTableRow row) {\r
-                               return row.getVersion() > 0 ? row.getVersion() : null;\r
-                       }\r
-                       @Override\r
-                       public void setValue(PartsManageTableRow row, Object value) {\r
-                               Double version = (Double) value;\r
-                               if (version == null || version.doubleValue() <= 0) {\r
-                                       version = Double.valueOf(0.);\r
-                               }\r
-                               Double oldValue = row.getVersion();\r
-                               if (oldValue != null && oldValue.equals(version)) {\r
-                                       return; // 変化なし\r
-                               }\r
-                               row.setVersion(version);\r
-                       }\r
-               },\r
-               DownloadURL("column.downloadURL", true, String.class) {\r
-                       @Override\r
-                       public Object getValue(PartsManageTableRow row) {\r
-                               return row.getDownloadURL();\r
-                       }\r
-                       @Override\r
-                       public void setValue(PartsManageTableRow row, Object value) {\r
-                               String downloadURL = (String) value;\r
-                               if (downloadURL == null) {\r
-                                       downloadURL = "";\r
-                               }\r
-                               String oldValue = row.getDownloadURL();\r
-                               if (oldValue != null && oldValue.equals(downloadURL)) {\r
-                                       return; // 変化なし\r
-                               }\r
-                               row.setDownloadURL(downloadURL);\r
-                       }\r
-               };\r
-\r
-               private final Class<?> columnClass;\r
-\r
-               private final boolean editable;\r
-\r
-               private final String columnName;\r
-\r
-               private String displayName;\r
-\r
-               private int width;\r
-\r
-               private Columns(String columnName, boolean editable,\r
-                               Class<?> columnClass) {\r
-                       this.columnName = columnName;\r
-                       this.editable = editable;\r
-                       this.columnClass = columnClass;\r
-               }\r
-\r
-               public abstract Object getValue(PartsManageTableRow row);\r
-\r
-               public boolean isEditable() {\r
-                       return editable;\r
-               }\r
-\r
-               public Class<?> getColumnClass() {\r
-                       return columnClass;\r
-               }\r
-\r
-               public String getDisplayName() {\r
-                       init();\r
-                       return displayName;\r
-               }\r
-\r
-               public int getWidth() {\r
-                       init();\r
-                       return width;\r
-               }\r
-\r
-               public void setValue(PartsManageTableRow row, Object value) {\r
-                       // 何もしない.\r
-               }\r
-\r
-               private void init() {\r
-                       if (displayName != null) {\r
-                               return;\r
-                       }\r
-                       displayName = strings.getProperty(columnName);\r
-                       width = Integer\r
-                                       .parseInt(strings.getProperty(columnName + ".width"));\r
-               }\r
-       }\r
-\r
-       \r
-       public int getColumnCount() {\r
-               return Columns.values().length;\r
-       }\r
-       \r
-       @Override\r
-       public String getColumnName(int column) {\r
-               return Columns.values()[column].getDisplayName();\r
-       }\r
-       \r
-       public void adjustColumnModel(TableColumnModel columnModel) {\r
-               Columns[] columns = Columns.values();\r
-               for (int idx = 0; idx < columns.length; idx++) {\r
-                       columnModel.getColumn(idx).setPreferredWidth(\r
-                                       columns[idx].getWidth());\r
-               }\r
-       }\r
-\r
-       public Object getValueAt(int rowIndex, int columnIndex) {\r
-               PartsManageTableRow row = getRow(rowIndex);\r
-               Columns column = Columns.values()[columnIndex];\r
-               return column.getValue(row);\r
-       }\r
-       \r
-       @Override\r
-       public void setValueAt(Object aValue, int rowIndex, int columnIndex) {\r
-               PartsManageTableRow row = getRow(rowIndex);\r
-               Columns column = Columns.values()[columnIndex];\r
-               if (!column.isEditable()) {\r
-                       return;\r
-               }\r
-               column.setValue(row, aValue);\r
-\r
-               // 更新日を最新にする\r
-               Timestamp dt = row.getTimestamp();\r
-               row.setLastModified(dt);\r
-\r
-               // 変更通知\r
-               fireTableRowsUpdated(rowIndex, columnIndex);\r
-       }\r
-       \r
-       @Override\r
-       public Class<?> getColumnClass(int columnIndex) {\r
-               Columns column = Columns.values()[columnIndex];\r
-               return column.getColumnClass();\r
-       }\r
-       \r
-       @Override\r
-       public boolean isCellEditable(int rowIndex, int columnIndex) {\r
-               Columns column = Columns.values()[columnIndex];\r
-               return column.isEditable();\r
-       }\r
-       \r
-       public void initModel(CharacterData characterData) {\r
-               if (characterData == null) {\r
-                       throw new IllegalArgumentException();\r
-               }\r
-               clear();\r
-\r
-               // 既存のパーツ管理情報ファイルがあれば読み込む\r
-               URI docBase = characterData.getDocBase();\r
-               PartsManageData partsManageData = null;\r
-               if (docBase != null) {\r
-                       try {\r
-                               PartsInfoXMLReader reader = new PartsInfoXMLReader();\r
-                               partsManageData = reader.loadPartsManageData(docBase);\r
-\r
-                       } catch (Exception ex) {\r
-                               logger.log(Level.WARNING, ex.toString(), ex);\r
-                       }\r
-               }\r
-               if (partsManageData == null) {\r
-                       partsManageData = new PartsManageData();\r
-               }\r
-\r
-               for (PartsCategory partsCategory : characterData.getPartsCategories()) {\r
-                       for (Map.Entry<PartsIdentifier, PartsSpec> entry : characterData\r
-                                       .getPartsSpecMap(partsCategory).entrySet()) {\r
-                               PartsIdentifier partsIdentifier = entry.getKey();\r
-                               PartsSpec partsSpec = entry.getValue();\r
-                               \r
-                               // パーツ管理情報ファイルから、パーツ管理情報を設定した時点の\r
-                               // ファイルサイズや更新日時などを読み取る.\r
-                               PartsKey partsKey = new PartsKey(partsIdentifier);\r
-                               PartsVersionInfo versionInfo = partsManageData\r
-                                               .getVersion(partsKey);\r
-\r
-                               Timestamp lastModified = null;\r
-\r
-                               if (versionInfo != null) {\r
-                                       lastModified = versionInfo.getLastModified();\r
-                               }\r
-\r
-                               PartsManageTableRow row = new PartsManageTableRow(\r
-                                               partsIdentifier, partsSpec, lastModified);\r
-                               addRow(row);\r
-                       }\r
-               }\r
-\r
-               sortByAuthor();\r
-       }\r
-\r
-       /**\r
-        * ホームページを設定する.<br>\r
-        * ホームページはAuthorに対して1つであるが、Authorが自由編集可能であるため便宜的にRowに持たせている.<br>\r
-        * 結果として同じAuthorに対して同じ値を設定する必要がある.<br>\r
-        * ホームページはテーブルに表示されないのでリスナーへの通知は行わない.<br>\r
-        * \r
-        * @param author\r
-        *            作者、空またはnullは何もしない.\r
-        * @param homepage\r
-        *            ホームページ\r
-        */\r
-       public void setHomepage(String author, String homepage) {\r
-               if (author == null || author.length() == 0) {\r
-                       return;\r
-               }\r
-               for (PartsManageTableRow row : elements) {\r
-                       String targetAuthor = row.getAuthor();\r
-                       if (targetAuthor == null) {\r
-                               targetAuthor = "";\r
-                       }\r
-                       if (targetAuthor.equals(author)) {\r
-                               row.setHomepage(homepage);\r
-                       }\r
-               }\r
-       }\r
-\r
-       /**\r
-        * ホームページを取得する.<br>\r
-        * 該当する作者がないか、作者がnullまたは空の場合は常にnullを返す.<br>\r
-        * \r
-        * @param author\r
-        *            作者\r
-        * @return ホームページ、またはnull\r
-        */\r
-       public String getHomepage(String author) {\r
-               if (author == null || author.length() == 0) {\r
-                       return null;\r
-               }\r
-               for (PartsManageTableRow row : elements) {\r
-                       String targetAuthor = row.getAuthor();\r
-                       if (targetAuthor == null) {\r
-                               targetAuthor = "";\r
-                       }\r
-                       if (targetAuthor.equals(author)) {\r
-                               return row.getHomepage();\r
-                       }\r
-               }\r
-               return null;\r
-       }\r
-       \r
-       protected static final Comparator<PartsManageTableRow> NAMED_SORTER\r
-               = new Comparator<PartsManageTableRow>() {\r
-               public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {\r
-                       // カテゴリで順序づけ\r
-                       int ret = o1.getPartsIdentifier().getPartsCategory().compareTo(\r
-                                       o2.getPartsIdentifier().getPartsCategory());\r
-                       if (ret == 0) {\r
-                               // 表示名で順序づけ\r
-                               String lnm1 = o1.getLocalizedName();\r
-                               String lnm2 = o2.getLocalizedName();\r
-                               if (lnm1 == null) {\r
-                                       lnm1 = "";\r
-                               }\r
-                               if (lnm2 == null) {\r
-                                       lnm2 = "";\r
-                               }\r
-                               ret = lnm1.compareTo(lnm2);\r
-                       }\r
-                       if (ret == 0) {\r
-                               // それでも判定できなければ元の識別子で判定する.\r
-                               ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());\r
-                       }\r
-                       return ret;\r
-               }\r
-       };\r
-       \r
-       public void sortByName() {\r
-               Collections.sort(elements, NAMED_SORTER);\r
-               fireTableDataChanged();\r
-       }\r
-       \r
-       public void sortByTimestamp() {\r
-               Collections.sort(elements, new Comparator<PartsManageTableRow>() {\r
-                       public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {\r
-                               // 更新日で順序づけ (新しいもの順)\r
-                               int ret = o1.getTimestamp().compareTo(o2.getTimestamp()) * -1;\r
-                               if (ret == 0) {\r
-                                       // それでも判定できなければ名前順と同じ\r
-                                       ret = NAMED_SORTER.compare(o1, o2);\r
-                               }\r
-                               return ret;\r
-                       }\r
-               });\r
-               fireTableDataChanged();\r
-       }\r
-\r
-       public void sortByAuthor() {\r
-               Collections.sort(elements, new Comparator<PartsManageTableRow>() {\r
-                       public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {\r
-                               // 作者で順序づけ\r
-                               String author1 = o1.getAuthor();\r
-                               String author2 = o2.getAuthor();\r
-                               if (author1 == null) {\r
-                                       author1 = "";\r
-                               }\r
-                               if (author2 == null) {\r
-                                       author2 = "";\r
-                               }\r
-                               int ret = author1.compareTo(author2);\r
-                               if (ret == 0) {\r
-                                       // それでも判定できなければ名前順と同じ\r
-                                       ret = NAMED_SORTER.compare(o1, o2);\r
-                               }\r
-                               return ret;\r
-                       }\r
-               });\r
-               fireTableDataChanged();\r
-       }\r
-}\r
+package charactermanaj.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.net.URI;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Semaphore;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.InputMap;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumnModel;
+
+import charactermanaj.Main;
+import charactermanaj.model.AppConfig;
+import charactermanaj.model.CharacterData;
+import charactermanaj.model.PartsAuthorInfo;
+import charactermanaj.model.PartsCategory;
+import charactermanaj.model.PartsIdentifier;
+import charactermanaj.model.PartsManageData;
+import charactermanaj.model.PartsManageData.PartsKey;
+import charactermanaj.model.PartsManageData.PartsVersionInfo;
+import charactermanaj.model.PartsSpec;
+import charactermanaj.model.io.PartsInfoXMLReader;
+import charactermanaj.model.io.PartsInfoXMLWriter;
+import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;
+import charactermanaj.util.DesktopUtilities;
+import charactermanaj.util.ErrorMessageHelper;
+import charactermanaj.util.LocalizedResourcePropertyLoader;
+
+public class PartsManageDialog extends JDialog {
+
+       private static final long serialVersionUID = 1L;
+       
+       protected static final String STRINGS_RESOURCE = "languages/partsmanagedialog";
+       
+       
+       private static final Logger logger = Logger.getLogger(PartsManageDialog.class.getName());
+
+       private CharacterData characterData;
+       
+       private PartsManageTableModel partsManageTableModel;
+       
+       private JTable partsManageTable;
+       
+       private JTextField txtHomepage;
+       
+       private JTextField txtAuthor;
+       
+       private boolean updated;
+
+       
+       public PartsManageDialog(JFrame parent, CharacterData characterData) {
+               super(parent, true);
+               
+               if (characterData == null) {
+                       throw new IllegalArgumentException();
+               }
+               this.characterData = characterData;
+               
+               setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+               addWindowListener(new WindowAdapter() {
+                       @Override
+                       public void windowClosing(WindowEvent e) {
+                               onClose();
+                       }
+               });
+
+               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
+                               .getLocalizedProperties(STRINGS_RESOURCE);
+
+               setTitle(strings.getProperty("title"));
+
+               Container contentPane = getContentPane();
+               
+               // パーツリストテーブル
+               JPanel partsListPanel = new JPanel();
+               partsListPanel.setBorder(BorderFactory.createCompoundBorder(
+                               BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory
+                                               .createTitledBorder(strings.getProperty("partslist"))));
+
+               GridBagLayout partsListPanelLayout = new GridBagLayout();
+               partsListPanel.setLayout(partsListPanelLayout);
+               
+               partsManageTableModel = new PartsManageTableModel();
+               partsManageTable = new JTable(partsManageTableModel) {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       public Component prepareRenderer(TableCellRenderer renderer,
+                                       int rowIdx, int columnIdx) {
+                               PartsManageTableModel.Columns column = PartsManageTableModel.Columns
+                                               .values()[columnIdx];
+                               Component comp = super.prepareRenderer(renderer, rowIdx, columnIdx);
+                               PartsManageTableRow row = partsManageTableModel.getRow(rowIdx);
+
+                               Timestamp current = row.getTimestamp();
+                               Timestamp lastModified = row.getLastModified();
+
+                               boolean warnings = false;
+
+                               if (current != null && !current.equals(lastModified)) {
+                                       // 現在のパーツの最終更新日と、パーツ管理情報の作成時のパーツの最終更新日が不一致の場合
+                                       warnings = true;
+                               }
+
+                               // 背景色、警告行は赤色に
+                               if (warnings && column == PartsManageTableModel.Columns.LastModified) {
+                                       AppConfig appConfig = AppConfig.getInstance();
+                                       Color invalidBgColor = appConfig.getInvalidBgColor();
+                                       comp.setBackground(invalidBgColor);
+                               } else {
+                                       if (isCellSelected(rowIdx, columnIdx)) {
+                                               comp.setBackground(getSelectionBackground());
+                                       } else {
+                                               comp.setBackground(getBackground());
+                                       }
+                               }
+
+                               return comp;
+                       }
+               };
+               partsManageTable.setShowGrid(true);
+               partsManageTable.setGridColor(AppConfig.getInstance().getGridColor());
+               partsManageTableModel.adjustColumnModel(partsManageTable.getColumnModel());
+               partsManageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+               partsManageTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+
+               JScrollPane partsManageTableSP = new JScrollPane(partsManageTable);
+               
+               partsManageTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+                       public void valueChanged(ListSelectionEvent e) {
+                               if (!e.getValueIsAdjusting()) {
+                                       onChangeSelection();
+                               }
+                       }
+               });
+               
+               partsManageTableModel.addTableModelListener(new TableModelListener() {
+                       public void tableChanged(TableModelEvent e) {
+                               onTableDataChange(e.getFirstRow(), e.getLastRow());
+                       }
+               });
+               
+               GridBagConstraints gbc = new GridBagConstraints();
+               gbc.gridx = 0;
+               gbc.gridy = 0;
+               gbc.gridheight = 1;
+               gbc.gridwidth = 4;
+               gbc.anchor = GridBagConstraints.EAST;
+               gbc.fill = GridBagConstraints.BOTH;
+               gbc.insets = new Insets(3, 3, 3, 3);
+               gbc.ipadx = 0;
+               gbc.ipady = 0;
+               gbc.weightx = 1.;
+               gbc.weighty = 1.;
+               partsListPanel.add(partsManageTableSP, gbc);
+               
+               Action actSortByName = new AbstractAction(strings.getProperty("sortByName")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onSortByName();
+                       }
+               };
+               Action actSortByAuthor = new AbstractAction(strings.getProperty("sortByAuthor")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onSortByAuthor();
+                       }
+               };
+               Action actSortByTimestamp = new AbstractAction(strings.getProperty("sortByTimestamp")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onSortByTimestamp();
+                       }
+               };
+               
+               gbc.gridx = 0;
+               gbc.gridy = 1;
+               gbc.gridheight = 1;
+               gbc.gridwidth = 1;
+               gbc.weightx = 0.;
+               gbc.weighty = 0.;
+               partsListPanel.add(new JButton(actSortByName), gbc);
+
+               gbc.gridx = 1;
+               gbc.gridy = 1;
+               gbc.weightx = 0.;
+               gbc.weighty = 0.;
+               partsListPanel.add(new JButton(actSortByAuthor), gbc);
+
+               gbc.gridx = 2;
+               gbc.gridy = 1;
+               gbc.weightx = 0.;
+               gbc.weighty = 0.;
+               partsListPanel.add(new JButton(actSortByTimestamp), gbc);
+
+               gbc.gridx = 3;
+               gbc.gridy = 1;
+               gbc.weightx = 1.;
+               gbc.weighty = 0.;
+               partsListPanel.add(Box.createHorizontalGlue(), gbc);
+
+               contentPane.add(partsListPanel, BorderLayout.CENTER);
+
+               // テーブルのコンテキストメニュー
+               final JPopupMenu popupMenu = new JPopupMenu();
+               Action actApplyAllLastModified = new AbstractAction(strings.getProperty("applyAllLastModified")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onApplyAllLastModified();
+                       }
+               };
+               Action actApplyAllDownloadURL = new AbstractAction(strings.getProperty("applyAllDownloadURL")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onApplyAllDownloadURL();
+                       }
+               };
+               Action actApplyAllVersion = new AbstractAction(strings.getProperty("applyAllVersion")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onApplyAllVersion();
+                       }
+               };
+               popupMenu.add(actApplyAllLastModified);
+               popupMenu.add(new JSeparator());
+               popupMenu.add(actApplyAllVersion);
+               popupMenu.add(actApplyAllDownloadURL);
+               
+               partsManageTable.setComponentPopupMenu(popupMenu);
+               
+               // 作者情報パネル
+               JPanel authorPanel = new JPanel();
+               authorPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory
+                               .createEmptyBorder(5, 5, 5, 5), BorderFactory
+                               .createTitledBorder(strings.getProperty("author.info"))));
+               GridBagLayout authorPanelLayout = new GridBagLayout();
+               authorPanel.setLayout(authorPanelLayout);
+               
+               gbc.gridx = 0;
+               gbc.gridy = 0;
+               gbc.gridheight = 1;
+               gbc.gridwidth = 1;
+               gbc.anchor = GridBagConstraints.EAST;
+               gbc.fill = GridBagConstraints.BOTH;
+               gbc.insets = new Insets(3, 3, 3, 3);
+               gbc.ipadx = 0;
+               gbc.ipady = 0;
+               gbc.weightx = 0.;
+               gbc.weighty = 0.;
+               authorPanel.add(new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc);
+
+               gbc.gridx = 1;
+               gbc.gridy = 0;
+               gbc.gridwidth = 2;
+               gbc.weightx = 1.;
+               txtAuthor = new JTextField();
+               authorPanel.add(txtAuthor, gbc);
+
+               gbc.gridx = 0;
+               gbc.gridy = 1;
+               gbc.gridwidth = 1;
+               gbc.weightx = 0.;
+               authorPanel.add(new JLabel(strings.getProperty("homepage"), JLabel.RIGHT), gbc);
+
+               gbc.gridx = 1;
+               gbc.gridy = 1;
+               gbc.gridwidth = 1;
+               gbc.weightx = 1.;
+               txtHomepage = new JTextField();
+               authorPanel.add(txtHomepage, gbc);
+
+               gbc.gridx = 2;
+               gbc.gridy = 1;
+               gbc.gridwidth = 1;
+               gbc.weightx = 0.;
+               Action actBrowseHomepage = new AbstractAction(strings.getProperty("open")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onBrosweHomepage();
+                       }
+               };
+               authorPanel.add(new JButton(actBrowseHomepage), gbc);
+
+               if (!DesktopUtilities.isSupported()) {
+                       actBrowseHomepage.setEnabled(false);
+               }
+               
+               txtAuthor.getDocument().addDocumentListener(new DocumentListener() {
+                       public void removeUpdate(DocumentEvent e) {
+                               onEditAuthor();
+                       }
+                       public void insertUpdate(DocumentEvent e) {
+                               onEditAuthor();
+                       }
+                       public void changedUpdate(DocumentEvent e) {
+                               onEditAuthor();
+                       }
+               });
+               txtHomepage.getDocument().addDocumentListener(new DocumentListener() {
+                       public void removeUpdate(DocumentEvent e) {
+                               onEditHomepage();
+                       }
+                       public void insertUpdate(DocumentEvent e) {
+                               onEditHomepage();
+                       }
+                       public void changedUpdate(DocumentEvent e) {
+                               onEditHomepage();
+                       }
+               });
+               
+               
+               // ボタンパネル
+               JPanel btnPanel = new JPanel();
+               btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 45));
+               GridBagLayout btnPanelLayout = new GridBagLayout();
+               btnPanel.setLayout(btnPanelLayout);
+               
+               Action actClose = new AbstractAction(strings.getProperty("cancel")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onClose();
+                       }
+               };
+               Action actOK = new AbstractAction(strings.getProperty("update")) {
+                       private static final long serialVersionUID = 1L;
+                       public void actionPerformed(ActionEvent e) {
+                               onOK();
+                       }
+               };
+               
+               gbc.gridx = 0;
+               gbc.gridy = 0;
+               gbc.weightx = 1.;
+               btnPanel.add(Box.createHorizontalGlue(), gbc);
+               
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;
+               gbc.gridy = 0;
+               gbc.weightx = 0.;
+               btnPanel.add(new JButton(actOK), gbc);
+
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2;
+               gbc.gridy = 0;
+               gbc.weightx = 0.;
+               btnPanel.add(new JButton(actClose), gbc);
+               
+               // ダイアログ下部
+               JPanel southPanel = new JPanel(new BorderLayout());
+               southPanel.add(authorPanel, BorderLayout.NORTH);
+               southPanel.add(btnPanel, BorderLayout.SOUTH);
+
+               contentPane.add(southPanel, BorderLayout.SOUTH);
+               
+               // キーボード
+               
+               Toolkit tk = Toolkit.getDefaultToolkit();
+               JRootPane rootPane = getRootPane();
+               InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+               ActionMap am = rootPane.getActionMap();
+               im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closePartsManageDialog");
+               im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closePartsManageDialog");
+               am.put("closePartsManageDialog", actClose);
+
+               // モデル構築
+               partsManageTableModel.initModel(characterData);
+               
+               // ウィンドウ配置
+               setSize(500, 400);
+               setLocationRelativeTo(parent);
+       }
+
+       private Semaphore authorEditSemaphore = new Semaphore(1);
+       
+       protected void onChangeSelection() {
+               try {
+                       authorEditSemaphore.acquire();
+                       try {
+                               int [] selRows = partsManageTable.getSelectedRows();
+                               HashSet<String> authors = new HashSet<String>();
+                               for (int selRow : selRows) {
+                                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);
+                                       authors.add(row.getAuthor() == null ? "" : row.getAuthor());
+                               }
+                               if (authors.size() > 1) {
+                                       AppConfig appConfig = AppConfig.getInstance();
+                                       txtAuthor.setBackground(appConfig.getAuthorEditConflictBgColor());
+                                       txtHomepage.setBackground(appConfig.getAuthorEditConflictBgColor());
+                               } else {
+                                       Color bgColor = UIManager.getColor("TextField.background");
+                                       if (bgColor == null) {
+                                               bgColor = Color.white;
+                                       }
+                                       txtAuthor.setBackground(bgColor);
+                                       txtHomepage.setBackground(bgColor);
+                               }
+                               if (authors.isEmpty()) {
+                                       // 選択されているauthorがない場合は全部編集不可
+                                       txtAuthor.setEditable(false);
+                                       txtAuthor.setText("");
+                                       txtHomepage.setEditable(false);
+                                       txtHomepage.setText("");
+                               } else {
+                                       // 選択されているAuthorが1つ以上あればAuthorは編集可
+                                       txtAuthor.setEditable(true);
+                                       txtHomepage.setEditable(true);
+                                       if (authors.size() == 1) {
+                                               // 選択されているAuthorが一個であれば、それを表示
+                                               String author = authors.iterator().next();
+                                               String homepage = partsManageTableModel.getHomepage(author);
+                                               txtAuthor.setText(author);
+                                               txtHomepage.setText(homepage);
+                                       } else {
+                                               // 選択されているAuthorが二個以上あれば編集可能だがテキストには表示しない.
+                                               txtAuthor.setText("");
+                                               txtHomepage.setText("");
+                                       }
+                               }
+                       } finally {
+                               authorEditSemaphore.release();
+                       }
+
+               } catch (InterruptedException ex) {
+                       ErrorMessageHelper.showErrorDialog(this, ex);
+
+               } catch (RuntimeException ex) {
+                       ErrorMessageHelper.showErrorDialog(this, ex);
+               }
+       }
+       
+       protected void onTableDataChange(int firstRow, int lastRow) {
+               onChangeSelection();
+       }
+       
+       protected void onApplyAllLastModified() {
+               int[] selRows = partsManageTable.getSelectedRows();
+               if (selRows.length == 0) {
+                       Toolkit tk = Toolkit.getDefaultToolkit();
+                       tk.beep();
+                       return;
+               }
+               Arrays.sort(selRows);
+
+               for (int selRow : selRows) {
+                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);
+                       Timestamp dt = row.getTimestamp();
+                       row.setLastModified(dt);
+               }
+               partsManageTableModel.fireTableRowsUpdated(selRows[0],
+                               selRows[selRows.length - 1]);
+       }
+
+       protected void onApplyAllDownloadURL() {
+               int[] selRows = partsManageTable.getSelectedRows();
+               if (selRows.length == 0) {
+                       Toolkit tk = Toolkit.getDefaultToolkit();
+                       tk.beep();
+                       return;
+               }
+               Arrays.sort(selRows);
+
+               HashSet<String> authors = new HashSet<String>();
+               for (int selRow : selRows) {
+                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);
+                       authors.add(row.getAuthor() == null ? "" : row.getAuthor());
+               }
+
+               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
+                               .getLocalizedProperties(STRINGS_RESOURCE);
+
+               if (authors.size() > 1) {
+                       if (JOptionPane.showConfirmDialog(this,
+                                       strings.getProperty("confirm.authorConflict"),
+                                       strings.getProperty("confirm"),
+                                       JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) {
+                               return;
+                       }
+               }
+               
+               PartsManageTableRow firstRow = partsManageTableModel.getRow(selRows[0]);
+               String downloadURL = firstRow.getDownloadURL();
+               if (downloadURL == null) {
+                       downloadURL = "";
+               }
+               String downloadURL_new = JOptionPane.showInputDialog(this, strings.getProperty("input.downloadURL"), downloadURL);
+               if (downloadURL_new == null || downloadURL.equals(downloadURL_new)) {
+                       // キャンセルされたか、内容に変化ない場合は何もしない
+                       return;
+               }
+               
+               for (int selRow : selRows) {
+                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);
+                       row.setDownloadURL(downloadURL_new);
+
+                       Timestamp dt = row.getTimestamp();
+                       row.setLastModified(dt);
+               }
+               partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]);
+       }
+       
+       protected void onApplyAllVersion() {
+               Toolkit tk = Toolkit.getDefaultToolkit();
+               int[] selRows = partsManageTable.getSelectedRows();
+               if (selRows.length == 0) {
+                       tk.beep();
+                       return;
+               }
+               Arrays.sort(selRows);
+
+               HashSet<String> authors = new HashSet<String>();
+               for (int selRow : selRows) {
+                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);
+                       authors.add(row.getAuthor() == null ? "" : row.getAuthor());
+               }
+
+               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
+                               .getLocalizedProperties(STRINGS_RESOURCE);
+
+               if (authors.size() > 1) {
+                       if (JOptionPane.showConfirmDialog(this,
+                                       strings.getProperty("confirm.authorConflict"),
+                                       strings.getProperty("confirm"),
+                                       JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) {
+                               return;
+                       }
+               }
+
+               PartsManageTableRow firstRow = partsManageTableModel.getRow(selRows[0]);
+               double version = firstRow.getVersion();
+               String strVersion = (version < 0) ? "" : Double.toString(version);
+               String strVersion_new = JOptionPane.showInputDialog(this,
+                               strings.getProperty("input.version"), strVersion);
+               if (strVersion_new == null || strVersion.equals(strVersion_new)) {
+                       // キャンセルされたか、内容に変化ない場合は何もしない
+                       return;
+               }
+               double version_new;
+               try {
+                       version_new = Double.parseDouble(strVersion_new);
+               } catch (Exception ex) {
+                       // 数値として不正であれば何もしない.
+                       tk.beep();
+                       return;
+               }
+               
+               for (int selRow : selRows) {
+                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);
+                       row.setVersion(version_new);
+
+                       Timestamp dt = row.getTimestamp();
+                       row.setLastModified(dt);
+               }
+               partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]);
+       }
+       
+       protected void onEditHomepage() {
+               try {
+                       if (!authorEditSemaphore.tryAcquire()) {
+                               return;
+                       }
+                       try {
+                               String author = txtAuthor.getText();
+                               String homepage = txtHomepage.getText();
+                               partsManageTableModel.setHomepage(author, homepage);
+                       } finally {
+                               authorEditSemaphore.release();
+                       }
+               } catch (Exception ex) {
+                       ErrorMessageHelper.showErrorDialog(this, ex);
+               }
+       }
+
+       protected void onEditAuthor() {
+               try {
+                       if (!authorEditSemaphore.tryAcquire()) {
+                               return;
+                       }
+                       try {
+                               String author = txtAuthor.getText();
+                               int[] selRows = partsManageTable.getSelectedRows();
+                               int firstRow = -1;
+                               int lastRow = Integer.MAX_VALUE;
+                               for (int selRow : selRows) {
+                                       PartsManageTableRow row = partsManageTableModel.getRow(selRow);
+                                       String oldValue = row.getAuthor();
+                                       if (oldValue == null || !oldValue.equals(author)) {
+                                               row.setAuthor(author);
+
+                                               Timestamp dt = row.getTimestamp();
+                                               row.setLastModified(dt);
+
+                                               firstRow = Math.max(firstRow, selRow);
+                                               lastRow = Math.min(lastRow, selRow);
+                                       }
+                               }
+                               
+                               String homepage = partsManageTableModel.getHomepage(author);
+                               if (homepage == null) {
+                                       homepage = "";
+                               }
+                               txtHomepage.setText(homepage);
+                               
+                               if (firstRow >= 0) {
+                                       partsManageTable.repaint();
+                               }
+                       } finally {
+                               authorEditSemaphore.release();                          
+                       }
+               } catch (Exception ex) {
+                       ErrorMessageHelper.showErrorDialog(this, ex);
+               }
+       }
+       
+       protected void onClose() {
+               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
+                               .getLocalizedProperties(STRINGS_RESOURCE);
+               if (JOptionPane.showConfirmDialog(this,
+                               strings.getProperty("confirm.cancel"),
+                               strings.getProperty("confirm"),
+                               JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
+                       return;
+               }
+               updated = false;
+               dispose();
+       }
+       
+       protected void onBrosweHomepage() {
+               Toolkit tk = Toolkit.getDefaultToolkit();
+               String homepage = txtHomepage.getText();
+               if (homepage == null || homepage.trim().length() == 0) {
+                       tk.beep();
+                       return;
+               }
+               try {
+                       URI uri = new URI(homepage);
+                       DesktopUtilities.browse(uri);
+
+               } catch (Exception ex) {
+                       tk.beep();
+                       logger.log(Level.INFO, "browse failed. : " + homepage, ex);
+               }
+       }
+       
+       protected void onSortByAuthor() {
+               partsManageTableModel.sortByAuthor();
+       }
+       
+       protected void onSortByName() {
+               partsManageTableModel.sortByName();
+       }
+       
+       protected void onSortByTimestamp() {
+               partsManageTableModel.sortByTimestamp();
+       }
+       
+       protected void onOK() {
+               if (partsManageTable.isEditing()) {
+                       Toolkit tk = Toolkit.getDefaultToolkit();
+                       tk.beep();
+                       return;
+               }
+               
+               int mx = partsManageTableModel.getRowCount();
+
+               // 作者ごとのホームページ情報の取得
+               // (同一作者につきホームページは一つ)
+               HashMap<String, PartsAuthorInfo> authorInfoMap = new HashMap<String, PartsAuthorInfo>();
+               for (int idx = 0; idx < mx; idx++) {
+                       PartsManageTableRow row = partsManageTableModel.getRow(idx);
+                       String author = row.getAuthor();
+                       String homepage = row.getHomepage();
+                       if (author != null && author.trim().length() > 0) {
+                               PartsAuthorInfo authorInfo = authorInfoMap.get(author.trim());
+                               if (authorInfo == null) {
+                                       authorInfo = new PartsAuthorInfo();
+                                       authorInfo.setAuthor(author.trim());
+                                       authorInfoMap.put(authorInfo.getAuthor(), authorInfo);
+                               }
+                               authorInfo.setHomePage(homepage);
+                       }
+               }
+
+               PartsManageData partsManageData = new PartsManageData();
+               
+               // パーツごとの作者とバージョン、ダウンロード先の取得
+               for (int idx = 0; idx < mx; idx++) {
+                       PartsManageTableRow row = partsManageTableModel.getRow(idx);
+                       
+                       String author = row.getAuthor();
+                       PartsAuthorInfo partsAuthorInfo = null;
+                       if (author != null && author.trim().length() > 0) {
+                               partsAuthorInfo = authorInfoMap.get(author.trim());
+                       }
+                       
+                       double version = row.getVersion();
+                       String downloadURL = row.getDownloadURL();
+                       String localizedName = row.getLocalizedName();
+                       Timestamp lastModified = row.getLastModified();
+                       
+                       PartsManageData.PartsVersionInfo versionInfo = new PartsManageData.PartsVersionInfo();
+                       versionInfo.setVersion(version);
+                       versionInfo.setDownloadURL(downloadURL);
+                       versionInfo.setLastModified(lastModified);
+
+                       PartsIdentifier partsIdentifier = row.getPartsIdentifier();
+                       
+                       PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsIdentifier);
+                       partsManageData.putPartsInfo(partsKey, localizedName,
+                                       partsAuthorInfo, versionInfo);
+               }
+               
+               // パーツ管理情報を書き込む.
+               PartsInfoXMLWriter xmlWriter = new PartsInfoXMLWriter();
+               try {
+                       URI docBase = characterData.getDocBase();
+                       xmlWriter.savePartsManageData(docBase, partsManageData);
+
+               } catch (Exception ex) {
+                       ErrorMessageHelper.showErrorDialog(this, ex);
+                       return;
+               }
+               
+               updated = true;
+               dispose();
+       }
+
+       /**
+        * パーツ管理情報が更新されたか?
+        * 
+        * @return 更新された場合はtrue、そうでなければfalse
+        */
+       public boolean isUpdated() {
+               return updated;
+       }
+}
+
+class PartsManageTableRow {
+       
+       private PartsIdentifier partsIdentifier;
+       
+       private Timestamp timestamp;
+       
+       private String localizedName;
+       
+       private String author;
+       
+       private String homepage;
+       
+       private String downloadURL;
+       
+       private double version;
+       
+       private Timestamp lastModified;
+
+       public PartsManageTableRow(PartsIdentifier partsIdentifier,
+                       PartsSpec partsSpec, Timestamp lastModified) {
+               if (partsIdentifier == null || partsSpec == null) {
+                       throw new IllegalArgumentException();
+               }
+               this.partsIdentifier = partsIdentifier;
+               this.localizedName = partsIdentifier.getLocalizedPartsName();
+
+               this.timestamp = new Timestamp(partsSpec.getPartsFiles().lastModified());
+               
+               this.lastModified = lastModified;
+               
+               PartsAuthorInfo autherInfo = partsSpec.getAuthorInfo();
+               if (autherInfo != null) {
+                       author = autherInfo.getAuthor();
+                       homepage = autherInfo.getHomePage();
+               }
+               if (author == null) {
+                       author = "";
+               }
+               if (homepage == null) {
+                       homepage = "";
+               }
+               downloadURL = partsSpec.getDownloadURL();
+               version = partsSpec.getVersion();
+       }
+       
+       public PartsIdentifier getPartsIdentifier() {
+               return partsIdentifier;
+       }
+       
+       public Timestamp getTimestamp() {
+               return timestamp;
+       }
+
+       public String getLocalizedName() {
+               return localizedName;
+       }
+       
+       public void setLocalizedName(String localizedName) {
+               if (localizedName == null || localizedName.trim().length() == 0) {
+                       throw new IllegalArgumentException();
+               }
+               this.localizedName = localizedName;
+       }
+       
+       public String getAuthor() {
+               return author;
+       }
+       
+       public String getDownloadURL() {
+               return downloadURL;
+       }
+       
+       public double getVersion() {
+               return version;
+       }
+       
+       public void setAuthor(String author) {
+               this.author = author;
+       }
+       
+       public void setDownloadURL(String downloadURL) {
+               this.downloadURL = downloadURL;
+       }
+       
+       public void setVersion(double version) {
+               this.version = version;
+       }
+       
+       public String getHomepage() {
+               return homepage;
+       }
+       
+       public void setHomepage(String homepage) {
+               this.homepage = homepage;
+       }
+       
+       public Timestamp getLastModified() {
+               return lastModified;
+       }
+
+       public void setLastModified(Timestamp lastModified) {
+               this.lastModified = lastModified;
+       }
+}
+
+class PartsManageTableModel extends AbstractTableModelWithComboBoxModel<PartsManageTableRow> {
+
+       private static final long serialVersionUID = 1L;
+
+       private static final Logger logger = Logger
+                       .getLogger(PartsManageTableModel.class.getName());
+
+       private static Properties strings = LocalizedResourcePropertyLoader
+                       .getCachedInstance().getLocalizedProperties(
+                                       PartsManageDialog.STRINGS_RESOURCE);
+
+       /**
+        * カラムの定義
+        */
+       public enum Columns {
+               PartsID("column.partsid", false, String.class) {
+                       @Override
+                       public Object getValue(PartsManageTableRow row) {
+                               return row.getPartsIdentifier().getPartsName();
+                       }
+               },
+               LastModified("column.lastmodified", false, String.class) {
+                       @Override
+                       public Object getValue(PartsManageTableRow row) {
+                               return row.getTimestamp().toString();
+                       }
+               },
+               Category("column.category", false, String.class) {
+                       @Override
+                       public Object getValue(PartsManageTableRow row) {
+                               return row.getPartsIdentifier().getPartsCategory()
+                                               .getLocalizedCategoryName();
+                       }
+               },
+               PartsName("column.partsname", true, String.class) {
+                       @Override
+                       public Object getValue(PartsManageTableRow row) {
+                               return row.getLocalizedName();
+                       }
+                       @Override
+                       public void setValue(PartsManageTableRow row, Object value) {
+                               String localizedName = (String) value;
+                               if (localizedName != null && localizedName.trim().length() > 0) {
+                                       String oldValue = row.getLocalizedName();
+                                       if (oldValue != null && oldValue.equals(localizedName)) {
+                                               return; // 変化なし
+                                       }
+                                       row.setLocalizedName(localizedName);
+                               }
+                       }
+               },
+               Author("column.author", true, String.class) {
+                       @Override
+                       public Object getValue(PartsManageTableRow row) {
+                               return row.getAuthor();
+                       }
+                       @Override
+                       public void setValue(PartsManageTableRow row, Object value) {
+                               String author = (String) value;
+                               if (author == null) {
+                                       author = "";
+                               }
+                               String oldValue = row.getAuthor();
+                               if (oldValue != null && oldValue.equals(author)) {
+                                       return; // 変化なし
+                               }
+                               row.setAuthor(author);
+                       }
+               },
+               Version("column.version", true, Double.class) {
+                       @Override
+                       public Object getValue(PartsManageTableRow row) {
+                               return row.getVersion() > 0 ? row.getVersion() : null;
+                       }
+                       @Override
+                       public void setValue(PartsManageTableRow row, Object value) {
+                               Double version = (Double) value;
+                               if (version == null || version.doubleValue() <= 0) {
+                                       version = Double.valueOf(0.);
+                               }
+                               Double oldValue = row.getVersion();
+                               if (oldValue != null && oldValue.equals(version)) {
+                                       return; // 変化なし
+                               }
+                               row.setVersion(version);
+                       }
+               },
+               DownloadURL("column.downloadURL", true, String.class) {
+                       @Override
+                       public Object getValue(PartsManageTableRow row) {
+                               return row.getDownloadURL();
+                       }
+                       @Override
+                       public void setValue(PartsManageTableRow row, Object value) {
+                               String downloadURL = (String) value;
+                               if (downloadURL == null) {
+                                       downloadURL = "";
+                               }
+                               String oldValue = row.getDownloadURL();
+                               if (oldValue != null && oldValue.equals(downloadURL)) {
+                                       return; // 変化なし
+                               }
+                               row.setDownloadURL(downloadURL);
+                       }
+               };
+
+               private final Class<?> columnClass;
+
+               private final boolean editable;
+
+               private final String columnName;
+
+               private String displayName;
+
+               private int width;
+
+               private Columns(String columnName, boolean editable,
+                               Class<?> columnClass) {
+                       this.columnName = columnName;
+                       this.editable = editable;
+                       this.columnClass = columnClass;
+               }
+
+               public abstract Object getValue(PartsManageTableRow row);
+
+               public boolean isEditable() {
+                       return editable;
+               }
+
+               public Class<?> getColumnClass() {
+                       return columnClass;
+               }
+
+               public String getDisplayName() {
+                       init();
+                       return displayName;
+               }
+
+               public int getWidth() {
+                       init();
+                       return width;
+               }
+
+               public void setValue(PartsManageTableRow row, Object value) {
+                       // 何もしない.
+               }
+
+               private void init() {
+                       if (displayName != null) {
+                               return;
+                       }
+                       displayName = strings.getProperty(columnName);
+                       width = Integer
+                                       .parseInt(strings.getProperty(columnName + ".width"));
+               }
+       }
+
+       
+       public int getColumnCount() {
+               return Columns.values().length;
+       }
+       
+       @Override
+       public String getColumnName(int column) {
+               return Columns.values()[column].getDisplayName();
+       }
+       
+       public void adjustColumnModel(TableColumnModel columnModel) {
+               Columns[] columns = Columns.values();
+               for (int idx = 0; idx < columns.length; idx++) {
+                       columnModel.getColumn(idx).setPreferredWidth(
+                                       columns[idx].getWidth());
+               }
+       }
+
+       public Object getValueAt(int rowIndex, int columnIndex) {
+               PartsManageTableRow row = getRow(rowIndex);
+               Columns column = Columns.values()[columnIndex];
+               return column.getValue(row);
+       }
+       
+       @Override
+       public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+               PartsManageTableRow row = getRow(rowIndex);
+               Columns column = Columns.values()[columnIndex];
+               if (!column.isEditable()) {
+                       return;
+               }
+               column.setValue(row, aValue);
+
+               // 更新日を最新にする
+               Timestamp dt = row.getTimestamp();
+               row.setLastModified(dt);
+
+               // 変更通知
+               fireTableRowsUpdated(rowIndex, columnIndex);
+       }
+       
+       @Override
+       public Class<?> getColumnClass(int columnIndex) {
+               Columns column = Columns.values()[columnIndex];
+               return column.getColumnClass();
+       }
+       
+       @Override
+       public boolean isCellEditable(int rowIndex, int columnIndex) {
+               Columns column = Columns.values()[columnIndex];
+               return column.isEditable();
+       }
+       
+       public void initModel(CharacterData characterData) {
+               if (characterData == null) {
+                       throw new IllegalArgumentException();
+               }
+               clear();
+
+               // 既存のパーツ管理情報ファイルがあれば読み込む
+               URI docBase = characterData.getDocBase();
+               PartsManageData partsManageData = null;
+               if (docBase != null) {
+                       try {
+                               PartsInfoXMLReader reader = new PartsInfoXMLReader();
+                               partsManageData = reader.loadPartsManageData(docBase);
+
+                       } catch (Exception ex) {
+                               logger.log(Level.WARNING, ex.toString(), ex);
+                       }
+               }
+               if (partsManageData == null) {
+                       partsManageData = new PartsManageData();
+               }
+
+               for (PartsCategory partsCategory : characterData.getPartsCategories()) {
+                       for (Map.Entry<PartsIdentifier, PartsSpec> entry : characterData
+                                       .getPartsSpecMap(partsCategory).entrySet()) {
+                               PartsIdentifier partsIdentifier = entry.getKey();
+                               PartsSpec partsSpec = entry.getValue();
+                               
+                               // パーツ管理情報ファイルから、パーツ管理情報を設定した時点の
+                               // ファイルサイズや更新日時などを読み取る.
+                               PartsKey partsKey = new PartsKey(partsIdentifier);
+                               PartsVersionInfo versionInfo = partsManageData
+                                               .getVersion(partsKey);
+
+                               Timestamp lastModified = null;
+
+                               if (versionInfo != null) {
+                                       lastModified = versionInfo.getLastModified();
+                               }
+
+                               PartsManageTableRow row = new PartsManageTableRow(
+                                               partsIdentifier, partsSpec, lastModified);
+                               addRow(row);
+                       }
+               }
+
+               sortByAuthor();
+       }
+
+       /**
+        * ホームページを設定する.<br>
+        * ホームページはAuthorに対して1つであるが、Authorが自由編集可能であるため便宜的にRowに持たせている.<br>
+        * 結果として同じAuthorに対して同じ値を設定する必要がある.<br>
+        * ホームページはテーブルに表示されないのでリスナーへの通知は行わない.<br>
+        * 
+        * @param author
+        *            作者、空またはnullは何もしない.
+        * @param homepage
+        *            ホームページ
+        */
+       public void setHomepage(String author, String homepage) {
+               if (author == null || author.length() == 0) {
+                       return;
+               }
+               for (PartsManageTableRow row : elements) {
+                       String targetAuthor = row.getAuthor();
+                       if (targetAuthor == null) {
+                               targetAuthor = "";
+                       }
+                       if (targetAuthor.equals(author)) {
+                               row.setHomepage(homepage);
+                       }
+               }
+       }
+
+       /**
+        * ホームページを取得する.<br>
+        * 該当する作者がないか、作者がnullまたは空の場合は常にnullを返す.<br>
+        * 
+        * @param author
+        *            作者
+        * @return ホームページ、またはnull
+        */
+       public String getHomepage(String author) {
+               if (author == null || author.length() == 0) {
+                       return null;
+               }
+               for (PartsManageTableRow row : elements) {
+                       String targetAuthor = row.getAuthor();
+                       if (targetAuthor == null) {
+                               targetAuthor = "";
+                       }
+                       if (targetAuthor.equals(author)) {
+                               return row.getHomepage();
+                       }
+               }
+               return null;
+       }
+       
+       protected static final Comparator<PartsManageTableRow> NAMED_SORTER
+               = new Comparator<PartsManageTableRow>() {
+               public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {
+                       // カテゴリで順序づけ
+                       int ret = o1.getPartsIdentifier().getPartsCategory().compareTo(
+                                       o2.getPartsIdentifier().getPartsCategory());
+                       if (ret == 0) {
+                               // 表示名で順序づけ
+                               String lnm1 = o1.getLocalizedName();
+                               String lnm2 = o2.getLocalizedName();
+                               if (lnm1 == null) {
+                                       lnm1 = "";
+                               }
+                               if (lnm2 == null) {
+                                       lnm2 = "";
+                               }
+                               ret = lnm1.compareTo(lnm2);
+                       }
+                       if (ret == 0) {
+                               // それでも判定できなければ元の識別子で判定する.
+                               ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());
+                       }
+                       return ret;
+               }
+       };
+       
+       public void sortByName() {
+               Collections.sort(elements, NAMED_SORTER);
+               fireTableDataChanged();
+       }
+       
+       public void sortByTimestamp() {
+               Collections.sort(elements, new Comparator<PartsManageTableRow>() {
+                       public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {
+                               // 更新日で順序づけ (新しいもの順)
+                               int ret = o1.getTimestamp().compareTo(o2.getTimestamp()) * -1;
+                               if (ret == 0) {
+                                       // それでも判定できなければ名前順と同じ
+                                       ret = NAMED_SORTER.compare(o1, o2);
+                               }
+                               return ret;
+                       }
+               });
+               fireTableDataChanged();
+       }
+
+       public void sortByAuthor() {
+               Collections.sort(elements, new Comparator<PartsManageTableRow>() {
+                       public int compare(PartsManageTableRow o1, PartsManageTableRow o2) {
+                               // 作者で順序づけ
+                               String author1 = o1.getAuthor();
+                               String author2 = o2.getAuthor();
+                               if (author1 == null) {
+                                       author1 = "";
+                               }
+                               if (author2 == null) {
+                                       author2 = "";
+                               }
+                               int ret = author1.compareTo(author2);
+                               if (ret == 0) {
+                                       // それでも判定できなければ名前順と同じ
+                                       ret = NAMED_SORTER.compare(o1, o2);
+                               }
+                               return ret;
+                       }
+               });
+               fireTableDataChanged();
+       }
+}